# mathlibdocumentation

combinatorics.composition

# Compositions

A composition of a natural number n is a decomposition n = i₀ + ... + i_{k-1} of n into a sum of positive integers. Combinatorially, it corresponds to a decomposition of {0, ..., n-1} into non-empty blocks of consecutive integers, where the iⱼ are the lengths of the blocks. This notion is closely related to that of a partition of n, but in a composition of n the order of the iⱼs matters.

We implement two different structures covering these two viewpoints on compositions. The first one, made of a list of positive integers summing to n, is the main one and is called composition n. The second one is useful for combinatorial arguments (for instance to show that the number of compositions of n is 2^(n-1)). It is given by a subset of {0, ..., n} containing 0 and n, where the elements of the subset (other than n) correspond to the leftmost points of each block. The main API is built on composition n, and we provide an equivalence between the two types.

## Main functions

• c : composition n is a structure, made of a list of integers which are all positive and add up to n.
• composition_card states that the cardinality of composition n is exactly 2^(n-1), which is proved by constructing an equiv with composition_as_set n (see below), which is itself in bijection with the subsets of fin (n-1) (this holds even for n = 0, where - is nat subtraction).

Let c : composition n be a composition of n. Then

• c.blocks is the list of blocks in c.
• c.length is the number of blocks in the composition.
• c.blocks_fun : fin c.length → ℕ is the realization of c.blocks as a function on fin c.length. This is the main object when using compositions to understand the composition of analytic functions.
• c.size_up_to : ℕ → ℕ is the sum of the size of the blocks up to i.;
• c.embedding i : fin (c.blocks_fun i) → fin n is the increasing embedding of the i-th block in fin n;
• c.index j, for j : fin n, is the index of the block containing j.

Compositions can also be used to split lists. Let l be a list of length n and c a composition of n.

• l.split_wrt_composition c is a list of lists, made of the slices of l corresponding to the blocks of c.
• join_split_wrt_composition states that splitting a list and then joining it gives back the original list.
• split_wrt_composition_join states that joining a list of lists, and then splitting it back according to the right composition, gives back the original list of lists.

We turn to the second viewpoint on compositions, that we realize as a finset of fin (n+1). c : composition_as_set n is a structure made of a finset of fin (n+1) called c.boundaries and proofs that it contains 0 and n. (Taking a finset of fin n containing 0 would not make sense in the edge case n = 0, while the previous description works in all cases). The elements of this set (other than n) correspond to leftmost points of blocks. Thus, there is an equiv between composition n and composition_as_set n. We only construct basic API on composition_as_set (notably c.length and c.blocks) to be able to construct this equiv, called composition_equiv n. Since there is a straightforward equiv between composition_as_set n and finsets of {1, ..., n-1} (obtained by removing 0 and n from a composition_as_set and called composition_as_set_equiv n), we deduce that composition_as_set n and composition n are both fintypes of cardinality 2^(n - 1) (see composition_as_set_card and composition_card).

## Implementation details

The main motivation for this structure and its API is in the construction of the composition of formal multilinear series, and the proof that the composition of analytic functions is analytic.

The representation of a composition as a list is very handy as lists are very flexible and already have a well-developed API.

## Tags

Composition, partition

## References

https://en.wikipedia.org/wiki/Composition_(combinatorics)

theorem composition.ext_iff {n : } (x y : composition n) :
x = y x.blocks = y.blocks

@[ext]
structure composition  :
→ Type

A composition of n is a list of positive integers summing to n.

theorem composition.ext {n : } (x y : composition n) :
x.blocks = y.blocksx = y

@[ext]
structure composition_as_set  :
→ Type

Combinatorial viewpoint on a composition of n, by seeing it as non-empty blocks of consecutive integers in {0, ..., n-1}. We register every block by its left end-point, yielding a finset containing 0. As this does not make sense for n = 0, we add n to this finset, and get a finset of {0, ..., n} containing 0 and n. This is the data in the structure composition_as_set n.

theorem composition_as_set.ext_iff {n : } (x y : composition_as_set n) :
x = y

theorem composition_as_set.ext {n : } (x y : composition_as_set n) :
x = y

### Compositions

A composition of an integer n is a decomposition n = i₀ + ... + i_{k-1} of n into a sum of positive integers.

@[instance]

Equations
def composition.length {n : } :

The length of a composition, i.e., the number of blocks in the composition.

Equations
def composition.blocks_fun {n : } (c : composition n) :

The blocks of a composition, seen as a function on fin c.length. When composing analytic functions using compositions, this is the main player.

Equations
theorem composition.sum_blocks_fun {n : } (c : composition n) :
∑ (i : fin c.length), c.blocks_fun i = n

@[simp]
theorem composition.one_le_blocks {n : } (c : composition n) {i : } :
i c.blocks1 i

@[simp]
theorem composition.one_le_blocks' {n : } (c : composition n) {i : } (h : i < c.length) :
1 c.blocks.nth_le i h

@[simp]
theorem composition.blocks_pos' {n : } (c : composition n) (i : ) (h : i < c.length) :
0 < c.blocks.nth_le i h

theorem composition.length_le {n : } (c : composition n) :

theorem composition.length_pos_of_pos {n : } (c : composition n) :
0 < n0 < c.length

def composition.size_up_to {n : } :

The sum of the sizes of the blocks in a composition up to i.

Equations
@[simp]
theorem composition.size_up_to_zero {n : } (c : composition n) :

theorem composition.size_up_to_of_length_le {n : } (c : composition n) (i : ) :
c.length ic.size_up_to i = n

@[simp]

theorem composition.size_up_to_le {n : } (c : composition n) (i : ) :

theorem composition.size_up_to_succ {n : } (c : composition n) {i : } (h : i < c.length) :
c.size_up_to (i + 1) = c.size_up_to i + c.blocks.nth_le i h

theorem composition.size_up_to_strict_mono {n : } (c : composition n) {i : } :
i < c.lengthc.size_up_to i < c.size_up_to (i + 1)

def composition.boundary {n : } (c : composition n) :
fin (c.length + 1)fin (n + 1)

The i-th boundary of a composition, i.e., the leftmost point of the i-th block. We include a virtual point at the right of the last block, to make for a nice equiv with composition_as_set n.

Equations
@[simp]
theorem composition.boundary_zero {n : } (c : composition n) :
c.boundary 0 = 0

@[simp]

def composition.boundaries {n : } :
finset (fin (n + 1))

The boundaries of a composition, i.e., the leftmost point of all the blocks. We include a virtual point at the right of the last block, to make for a nice equiv with composition_as_set n.

Equations

To c : composition n, one can associate a composition_as_set n by registering the leftmost point of each block, and adding a virtual point at the right of the last block.

Equations

The canonical increasing bijection between fin (c.length + 1) and c.boundaries is exactly c.boundary.

def composition.embedding {n : } (c : composition n) (i : fin c.length) :
fin (c.blocks_fun i)fin n

Embedding the i-th block of a composition (identified with fin (c.blocks_fun i)) into fin n at the relevant position.

Equations
theorem composition.embedding_injective {n : } (c : composition n) (i : fin c.length) :

theorem composition.index_exists {n : } (c : composition n) {j : } :
j < n(∃ (i : ), j < c.size_up_to i.succ i < c.length)

index_exists asserts there is some i so j < c.size_up_to (i+1). In the next definition we use nat.find to produce the minimal such index.

def composition.index {n : } (c : composition n) :
fin n

c.index j is the index of the block in the composition c containing j.

Equations
theorem composition.size_up_to_index_le {n : } (c : composition n) (j : fin n) :

def composition.inv_embedding {n : } (c : composition n) (j : fin n) :
fin (c.blocks_fun (c.index j))

Mapping an element j of fin n to the element in the block containing it, identified with fin (c.blocks_fun (c.index j)) through the canonical increasing bijection.

Equations
theorem composition.embedding_comp_inv {n : } (c : composition n) (j : fin n) :
c.embedding (c.index j) (c.inv_embedding j) = j

theorem composition.disjoint_range {n : } (c : composition n) {i₁ i₂ : fin c.length} :
i₁ i₂disjoint (set.range (c.embedding i₁)) (set.range (c.embedding i₂))

The embeddings of different blocks of a composition are disjoint.

theorem composition.mem_range_embedding {n : } (c : composition n) (j : fin n) :

theorem composition.mem_range_embedding_iff' {n : } (c : composition n) {j : fin n} {i : fin c.length} :

theorem composition.index_embedding {n : } (c : composition n) (i : fin c.length) (j : fin (c.blocks_fun i)) :
c.index (c.embedding i j) = i

theorem composition.inv_embedding_comp {n : } (c : composition n) (i : fin c.length) (j : fin (c.blocks_fun i)) :

def composition.blocks_fin_equiv {n : } (c : composition n) :
(Σ (i : fin c.length), fin (c.blocks_fun i)) fin n

Equivalence between the disjoint union of the blocks (each of them seen as fin (c.blocks_fun i)) with fin n.

Equations
theorem composition.blocks_fun_congr {n₁ n₂ : } (c₁ : composition n₁) (c₂ : composition n₂) (i₁ : fin c₁.length) (i₂ : fin c₂.length) :
n₁ = n₂c₁.blocks = c₂.blocksi₁ = i₂c₁.blocks_fun i₁ = c₂.blocks_fun i₂

theorem composition.sigma_eq_iff_blocks_eq {c c' : Σ (n : ), } :
c = c' c.snd.blocks = c'.snd.blocks

Two compositions (possibly of different integers) coincide if and only if they have the same sequence of blocks.

def composition.ones (n : ) :

The composition made of blocks all of size 1.

Equations
@[instance]
def composition.inhabited {n : } :

Equations
@[simp]
theorem composition.ones_length (n : ) :
= n

@[simp]
theorem composition.ones_blocks (n : ) :
= n

@[simp]
theorem composition.ones_blocks_fun (n : ) (i : fin ) :
= 1

@[simp]
theorem composition.ones_size_up_to (n i : ) :
= min i n

@[simp]
theorem composition.ones_embedding {n : } (i : fin ) (h : 0 < ) :
0, h⟩ = i, _⟩

theorem composition.eq_ones_iff {n : } {c : composition n} :
∀ (i : ), i c.blocksi = 1

theorem composition.ne_ones_iff {n : } {c : composition n} :
∃ (i : ) (H : i c.blocks), 1 < i

def composition.single (n : ) :
0 < n

The composition made of a single block of size n.

Equations
@[simp]
theorem composition.single_length {n : } (h : 0 < n) :
h).length = 1

@[simp]
theorem composition.single_blocks {n : } (h : 0 < n) :
h).blocks = [n]

@[simp]
theorem composition.single_blocks_fun {n : } (h : 0 < n) (i : fin h).length) :
h).blocks_fun i = n

@[simp]
theorem composition.single_embedding {n : } (h : 0 < n) (i : fin n) :
h).embedding 0, _⟩ i = i

theorem composition.eq_single_iff {n : } {h : 0 < n} {c : composition n} :
c = c.length = 1

### Splitting a list

Given a list of length n and a composition c of n, one can split l into c.length sublists of respective lengths c.blocks_fun 0, ..., c.blocks_fun (c.length-1). This is inverse to the join operation.

def list.split_wrt_composition_aux {α : Type u_1} :
list αlist (list α)

Auxiliary for list.split_wrt_composition.

Equations
• l.split_wrt_composition_aux (n :: ns) = list.split_wrt_composition_aux._match_1 (λ (l₂ : list α), l)
• list.split_wrt_composition_aux._match_1 _f_1 (l₁, l₂) = l₁ :: _f_1 l₂
def list.split_wrt_composition {n : } {α : Type u_1} :
list αlist (list α)

Given a list of length n and a composition [i₁, ..., iₖ] of n, split l into a list of k lists corresponding to the blocks of the composition, of respective lengths i₁, ..., iₖ. This makes sense mostly when n = l.length, but this is not necessary for the definition.

Equations
theorem list.split_wrt_composition_aux_cons {α : Type u_1} (l : list α) (n : ) (ns : list ) :

theorem list.length_split_wrt_composition_aux {α : Type u_1} (l : list α) (ns : list ) :

@[simp]
theorem list.length_split_wrt_composition {n : } {α : Type u_1} (l : list α) (c : composition n) :

When one splits a list along a composition c, the number of sublists thus created is c.length.

theorem list.map_length_split_wrt_composition_aux {α : Type u_1} {ns : list } {l : list α} :
ns.sum l.length

theorem list.map_length_split_wrt_composition {α : Type u_1} (l : list α) (c : composition l.length) :

When one splits a list along a composition c, the lengths of the sublists thus created are given by the block sizes in c.

theorem list.length_pos_of_mem_split_wrt_composition {α : Type u_1} {l l' : list α} {c : composition l.length} :
l' 0 < l'.length

theorem list.nth_le_split_wrt_composition_aux {α : Type u_1} (l : list α) (ns : list ) {i : } (hi : i < .length) :
.nth_le i hi = list.drop ns).sum (list.take (list.take (i + 1) ns).sum l)

theorem list.nth_le_split_wrt_composition {n : } {α : Type u_1} (l : list α) (c : composition n) {i : } (hi : i < .length) :
.nth_le i hi = list.drop (c.size_up_to i) (list.take (c.size_up_to (i + 1)) l)

The i-th sublist in the splitting of a list l along a composition c, is the slice of l between the indices c.size_up_to i and c.size_up_to (i+1), i.e., the indices in the i-th block of the composition.

theorem list.join_split_wrt_composition_aux {α : Type u_1} {ns : list } {l : list α} :
ns.sum = l.length.join = l

@[simp]
theorem list.join_split_wrt_composition {α : Type u_1} (l : list α) (c : composition l.length) :
.join = l

If one splits a list along a composition, and then joins the sublists, one gets back the original list.

@[simp]
theorem list.split_wrt_composition_join {α : Type u_1} (L : list (list α)) (c : composition L.join.length) :

If one joins a list of lists and then splits the join along the right composition, one gets back the original list of lists.

### Compositions as sets

Combinatorial viewpoints on compositions, seen as finite subsets of fin (n+1) containing 0 and n, where the points of the set (other than n) correspond to the leftmost points of each block.

def composition_as_set_equiv (n : ) :
finset (fin (n - 1))

Bijection between compositions of n and subsets of {0, ..., n-2}, defined by considering the restriction of the subset to {1, ..., n-1} and shifting to the left by one.

Equations
@[instance]

Equations
theorem composition_as_set_card (n : ) :
= 2 ^ (n - 1)

Number of blocks in a composition_as_set.

Equations
def composition_as_set.boundary {n : } (c : composition_as_set n) :
fin (n + 1)

Canonical increasing bijection from fin c.boundaries.card to c.boundaries.

Equations
@[simp]

Size of the i-th block in a composition_as_set, seen as a function on fin c.length.

Equations

List of the sizes of the blocks in a composition_as_set.

Equations

Associating a composition n to a composition_as_set n, by registering the sizes of the blocks as a list of positive integers.

Equations

### Equivalence between compositions and compositions as sets

In this section, we explain how to go back and forth between a composition and a composition_as_set, by showing that their blocks and length and boundaries correspond to each other, and construct an equivalence between them called composition_equiv.

def composition_equiv (n : ) :

Equivalence between composition n and composition_as_set n.

Equations
@[instance]
def composition_fintype (n : ) :

Equations
theorem composition_card (n : ) :
= 2 ^ (n - 1)