Skip to content

Commit 1c53304

Browse files
yuanyi-350michaellee94
authored andcommitted
feat(Combinatorics/Enumerative/Bell): sum over partition shapes (leanprover-community#39693)
Kill TODO in `Mathlib/Combinatorics/Enumerative/Bell.lean` which proves `Nat.bell` as a sum of `Multiset.bell` over partition shapes
1 parent 1ca9eb0 commit 1c53304

2 files changed

Lines changed: 134 additions & 35 deletions

File tree

Mathlib/Combinatorics/Enumerative/Bell.lean

Lines changed: 107 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
/-
22
Copyright (c) 2024 Antoine Chambert-Loir & María-Inés de Frutos—Fernández. All rights reserved.
33
Released under Apache 2.0 license as described in the file LICENSE.
4-
Authors: Antoine Chambert-Loir, María-Inés de Frutos—Fernández, Yu Shao, Beibei Xiong, Weijie Jiang
4+
Authors: Antoine Chambert-Loir, María-Inés de Frutos—Fernández, Yu Shao, Beibei Xiong, Weijie Jiang,
5+
Yi Yuan
56
-/
67
module
78

9+
public import Mathlib.Combinatorics.Enumerative.Partition.Basic
810
public import Mathlib.Data.Nat.Choose.Multinomial
911

1012
/-! # Bell numbers for multisets
@@ -74,15 +76,12 @@ private theorem bell_mul_eq_lemma {x : ℕ} (hx : x ≠ 0) :
7476
ring_nf
7577

7678
theorem bell_mul_eq (m : Multiset ℕ) :
77-
m.bell * (m.map (fun j ↦ j !)).prod * ∏ j ∈ (m.toFinset.erase 0), (m.count j)!
78-
= m.sum ! := by
79+
m.bell * (m.map (fun j ↦ j !)).prod * ∏ j ∈ (m.toFinset.erase 0), (m.count j)! = m.sum ! := by
7980
unfold bell
8081
rw [← Nat.mul_right_inj (a := ∏ i ∈ m.toFinset, (i * count i m)!) (by positivity)]
81-
simp only [← mul_assoc]
82-
rw [Nat.multinomial_spec]
83-
simp only [mul_assoc]
84-
rw [mul_comm]
85-
apply congr_arg₂
82+
simp only [← mul_assoc, Nat.multinomial_spec]
83+
rw [mul_assoc, mul_assoc, mul_comm]
84+
congr
8685
· rw [mul_comm, mul_assoc, ← Finset.prod_mul_distrib, Finset.prod_multiset_map_count]
8786
suffices this : _ by
8887
by_cases hm : 0 ∈ m.toFinset
@@ -94,30 +93,50 @@ theorem bell_mul_eq (m : Multiset ℕ) :
9493
nth_rewrite 3 [← Finset.erase_eq_of_notMem hm]
9594
exact this
9695
rw [← Finset.prod_mul_distrib]
97-
apply Finset.prod_congr rfl
98-
intro x hx
96+
congr! 1 with x hx
9997
rw [← mul_assoc, bell_mul_eq_lemma]
10098
simp only [Finset.mem_erase, ne_eq, mem_toFinset] at hx
10199
simp only [ne_eq, hx.1, not_false_eq_true]
102-
· apply congr_arg
103-
rw [Finset.sum_multiset_count]
100+
· rw [Finset.sum_multiset_count]
104101
simp only [smul_eq_mul, mul_comm]
105102

106103
theorem bell_eq (m : Multiset ℕ) :
107-
m.bell = m.sum ! / ((m.map (fun j ↦ j !)).prod *
108-
∏ j ∈ (m.toFinset.erase 0), (m.count j)!) := by
104+
m.bell = m.sum ! / ((m.map fun j ↦ j !).prod * ∏ j ∈ m.toFinset.erase 0, (m.count j)!) := by
109105
rw [← Nat.mul_left_inj, Nat.div_mul_cancel _]
110106
· rw [← mul_assoc]
111107
exact bell_mul_eq m
112108
· rw [← bell_mul_eq, mul_assoc]
113109
apply Nat.dvd_mul_left
114110
· rw [← Nat.pos_iff_ne_zero]
115-
apply Nat.mul_pos
116-
· simp only [CanonicallyOrderedAdd.multiset_prod_pos, mem_map, forall_exists_index, and_imp,
117-
forall_apply_eq_imp_iff₂]
118-
exact fun _ _ ↦ Nat.factorial_pos _
119-
· apply Finset.prod_pos
120-
exact fun _ _ ↦ Nat.factorial_pos _
111+
exact Nat.mul_pos (by simp [Nat.factorial_pos]) (by positivity)
112+
113+
private theorem bell_cons_mul_count (m : Multiset ℕ) {a : ℕ} (ha : a ≠ 0) :
114+
(a ::ₘ m).bell * (a ::ₘ m).count a = (m.sum + a).choose a * m.bell := by
115+
let rest := ∏ j ∈ (m.toFinset.erase 0).erase a, (m.count j)!
116+
have hrest : rest = ∏ j ∈ ((a ::ₘ m).toFinset.erase 0).erase a, ((a ::ₘ m).count j)! := by
117+
unfold rest
118+
congr! 1 with j hj
119+
· grind [Multiset.toFinset_cons]
120+
· simp [Finset.mem_erase.mp hj]
121+
let c := (m.map (· !)).prod * (m.count a)! * rest
122+
have hm0 : m.bell * c = m.sum ! := by
123+
have hsplit : (m.count a)! * rest = ∏ j ∈ m.toFinset.erase 0, (m.count j)! := by
124+
by_cases hmem : a ∈ m.toFinset.erase 0
125+
· rw [← Finset.mul_prod_erase _ _ hmem]
126+
· have hcount : m.count a = 0 := by grind [Multiset.count_eq_zero_of_notMem]
127+
simp [rest, Finset.erase_eq_of_notMem hmem, hcount]
128+
simpa [c, hsplit, mul_assoc] using Multiset.bell_mul_eq m
129+
have hm : m.sum ! * a ! = m.bell * a ! * c := by grind
130+
have hc : 0 < a ! * c := Nat.mul_pos (by positivity) <|
131+
Nat.mul_pos (by simp [Nat.factorial_pos]) (by positivity)
132+
apply Nat.eq_of_mul_eq_mul_right hc
133+
calc
134+
_ = (m.sum + a)! := by
135+
have hq := Multiset.bell_mul_eq (a ::ₘ m)
136+
rw [← Finset.mul_prod_erase _ _ (a := a) (by simp [*]), ← hrest] at hq
137+
simpa [c, Nat.factorial_succ, add_comm, mul_assoc, mul_left_comm] using hq
138+
_ = ((m.sum + a).choose a * m.bell) * (a ! * c) := by
139+
simp [← Nat.add_choose_mul_factorial_mul_factorial, mul_assoc, hm]
121140

122141
end Multiset
123142

@@ -128,7 +147,7 @@ of `n`-element subsets. -/
128147
def uniformBell (m n : ℕ) : ℕ := bell (replicate m n)
129148

130149
theorem uniformBell_eq (m n : ℕ) : m.uniformBell n =
131-
∏ p ∈ (Finset.range m), Nat.choose (p * n + n - 1) (n - 1) := by
150+
∏ p ∈ (Finset.range m), choose (p * n + n - 1) (n - 1) := by
132151
unfold uniformBell bell
133152
rw [toFinset_replicate]
134153
split_ifs with hm
@@ -172,43 +191,96 @@ theorem uniformBell_eq_div (m : ℕ) {n : ℕ} (hn : n ≠ 0) :
172191
uniformBell m n = (m * n)! / (n ! ^ m * m !) := by
173192
rw [eq_comm]
174193
apply Nat.div_eq_of_eq_mul_left
175-
· exact Nat.mul_pos (Nat.pow_pos (Nat.factorial_pos n)) m.factorial_pos
194+
· exact Nat.mul_pos (Nat.pow_pos n.factorial_pos) m.factorial_pos
176195
· rw [← mul_assoc, ← uniformBell_mul_eq _ hn]
177196

178197
/--
179198
The `n`th standard Bell number,
180199
which counts the number of partitions of a set of cardinality `n`.
181-
182-
## TODO
183-
184-
Prove that `Nat.bell n` is equal to the sum of `Multiset.bell m`
185-
over all multisets `m : Multiset ℕ` such that `m.sum = n`.
186200
-/
187201
protected def bell : ℕ → ℕ
188202
| 0 => 1
189-
| n + 1 => ∑ i : Fin n.succ, choose n i * Nat.bell (n - i)
203+
| n + 1 => ∑ i ≤ n, choose n i * (n - i).bell
190204

191205
theorem bell_succ (n : ℕ) :
192-
Nat.bell (n + 1) = ∑ i : Fin n.succ, Nat.choose n i * Nat.bell (n - i) := by
206+
(n + 1).bell = ∑ i ≤ n, choose n i * (n - i).bell := by
193207
rw [Nat.bell]
194208

195209
theorem bell_succ' (n : ℕ) :
196-
Nat.bell (n + 1) = ∑ ij ∈ Finset.antidiagonal n, Nat.choose n ij.1 * Nat.bell ij.2 := by
210+
(n + 1).bell = ∑ ij ∈ Finset.antidiagonal n, choose n ij.1 * ij.2.bell := by
197211
rw [Nat.bell_succ,
198-
Finset.Nat.sum_antidiagonal_eq_sum_range_succ (fun x y => Nat.choose n x * Nat.bell y) n,
199-
Finset.sum_range]
200-
212+
← Nat.range_succ_eq_Iic,
213+
← Finset.Nat.sum_antidiagonal_eq_sum_range_succ (fun x y ↦ choose n x * y.bell) n]
201214

202215
@[simp]
203216
theorem bell_zero : Nat.bell 0 = 1 := by
204217
simp [Nat.bell]
205218

206219
@[simp]
207220
theorem bell_one : Nat.bell 1 = 1 := by
208-
simp [Nat.bell]
221+
have : Finset.Iic 0 = {0} := Eq.symm (Finset.eq_of_veq rfl)
222+
simp [Nat.bell, this]
209223

210224
@[simp]
211225
theorem bell_two : Nat.bell 2 = 2 := by
212-
simp [Nat.bell]
226+
have : Finset.Iic 1 = {0, 1} := Finset.eq_of_veq rfl
227+
simp [Nat.bell, this]
228+
229+
theorem bell_eq_sum_erase {n : ℕ} (p : (n + 1).Partition) :
230+
p.parts.bell = ∑ a ∈ p.parts.toFinset, n.choose (a - 1) * (p.parts.erase a).bell := by
231+
apply Nat.eq_of_mul_eq_mul_left n.succ_pos
232+
calc
233+
_ = (∑ a ∈ p.parts.toFinset, p.parts.count a * a) * p.parts.bell := by
234+
rw [succ_eq_add_one, mul_eq_mul_right_iff]
235+
left
236+
simpa [smul_eq_mul, p.parts_sum] using Finset.sum_multiset_count p.parts
237+
_ = ∑ a ∈ p.parts.toFinset, a * (p.parts.count a * p.parts.bell) := by grind [Finset.sum_mul]
238+
_ = ∑ a ∈ p.parts.toFinset, (n + 1) * (n.choose (a - 1) * (p.parts.erase a).bell) := by
239+
congr! 1 with a ha
240+
have ha0 : a ≠ 0 := by grind
241+
have hsum : (p.parts.erase a).sum + a = n + 1 := by
242+
simpa [p.parts_sum, add_comm] using congrArg Multiset.sum (cons_erase (mem_dedup.mp ha))
243+
grind [Nat.add_one_mul_choose_eq, cons_erase, bell_cons_mul_count]
244+
_ = _ := by rw [Finset.mul_sum]
245+
246+
private def sigmaPartitionWithPartEquiv (n : ℕ) :
247+
(Σ i : Fin n.succ, {p : (n + 1).Partition // (i + 1 : ℕ) ∈ p.parts}) ≃
248+
Σ p : (n + 1).Partition, {a : ℕ // a ∈ p.parts.toFinset} where
249+
toFun x := ⟨x.2.1, ⟨(x.1 + 1 : ℕ), by simpa using x.2.2⟩⟩
250+
invFun x := ⟨⟨x.2.1 - 1, by grind⟩, ⟨x.1, by grind⟩⟩
251+
left_inv x := by simp
252+
right_inv x := by grind
253+
254+
/-- `Nat.bell n` is equal to the sum of `Multiset.bell m` over all multisets `m : Multiset ℕ` such
255+
that `m.sum = n`. -/
256+
theorem bell_eq_sum_partition (n : ℕ) : n.bell = ∑ p : n.Partition, p.parts.bell := by
257+
refine Nat.strong_induction_on n ?_
258+
rintro (_ | n) ih
259+
· simp
260+
rw [Nat.bell_succ]
261+
calc
262+
_ = ∑ i ≤ n, ∑ q : (n - i).Partition, n.choose i * q.parts.bell := by
263+
congr! with i
264+
simp [ih (n - i) _, Finset.mul_sum]
265+
_ = ∑ i ≤ n, ∑ p : {p : (n + 1).Partition // (i + 1 : ℕ) ∈ p.parts},
266+
choose n i * (p.1.parts.erase (i + 1)).bell := by
267+
congr! with i hi
268+
have : i ≤ n := Finset.mem_Iic.mp hi
269+
have h1 : 1 ≤ (i + 1 : ℕ) := by lia
270+
have h2 : (i + 1 : ℕ) ≤ n + 1 := by lia
271+
have hsub : n + 1 - (i + 1 : ℕ) = n - i := by lia
272+
exact hsub ▸ (Fintype.sum_equiv (Partition.partitionWithPartEquiv h1 h2) _ _ (fun _ ↦ rfl)).symm
273+
_ = ∑ x : Σ p : (n + 1).Partition, p.parts.toFinset,
274+
choose n (x.2.1 - 1) * (x.1.parts.erase x.2.1).bell := by
275+
rw [← Nat.range_succ_eq_Iic, Finset.sum_range, ← Fintype.sum_sigma']
276+
refine Fintype.sum_equiv (sigmaPartitionWithPartEquiv n) _ _ ?_
277+
simp [sigmaPartitionWithPartEquiv]
278+
_ = ∑ p : (n + 1).Partition, ∑ a : p.parts.toFinset, choose n (a - 1) * (p.parts.erase a).bell :=
279+
Fintype.sum_sigma' fun (p : (n + 1).Partition) (a : p.parts.toFinset) ↦
280+
choose n (a - 1) * (p.parts.erase a.1).bell
281+
_ = _ := by
282+
congr! with p
283+
rw [bell_eq_sum_erase p]
284+
exact p.parts.toFinset.sum_coe_sort (fun a ↦ choose n (a - 1) * (p.parts.erase a).bell)
213285

214286
end Nat

Mathlib/Combinatorics/Enumerative/Partition/Basic.lean

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,33 @@ theorem countRestricted_two (n : ℕ) : countRestricted n 2 = distincts n := by
229229
def oddDistincts (n : ℕ) : Finset n.Partition :=
230230
odds n ∩ distincts n
231231

232+
/-- If `1 ≤ a` and `a ≤ n`, partitions of `n` containing `a` as a part are equivalent to
233+
partitions of `n - a`. The forward map removes one occurrence of `a`, and the inverse adds `a` as
234+
a part. -/
235+
def partitionWithPartEquiv {n a : ℕ} (ha1 : 1 ≤ a) (ha : a ≤ n) :
236+
{p : n.Partition // a ∈ p.parts} ≃ (n - a).Partition where
237+
toFun p := by
238+
refine ⟨p.1.parts.erase a, ?_, ?_⟩
239+
· intro _ hi
240+
exact p.1.parts_pos (p.1.parts.erase_subset a hi)
241+
· have hs : a + (p.1.parts.erase a).sum = n := by
242+
simpa [p.1.parts_sum] using congrArg Multiset.sum (Multiset.cons_erase p.2)
243+
lia
244+
invFun q := ⟨⟨a ::ₘ q.parts, by grind, by simp [q.parts_sum, ha]⟩, by simp⟩
245+
left_inv p := Subtype.ext <| Partition.ext <| cons_erase p.property
246+
right_inv q := Partition.ext <| erase_cons_head a q.parts
247+
248+
@[simp]
249+
theorem partitionWithPartEquiv_apply_parts {n a : ℕ} (ha1 : 1 ≤ a) (ha : a ≤ n)
250+
(p : {p : n.Partition // a ∈ p.parts}) :
251+
(partitionWithPartEquiv ha1 ha p).parts = p.1.parts.erase a := by
252+
dsimp [partitionWithPartEquiv]
253+
254+
@[simp]
255+
theorem partitionWithPartEquiv_symm_apply_parts {n a : ℕ} (ha1 : 1 ≤ a) (ha : a ≤ n)
256+
(p : (n - a).Partition) : ((partitionWithPartEquiv ha1 ha).symm p).1.parts = a ::ₘ p.parts := by
257+
dsimp [partitionWithPartEquiv]
258+
232259
end Partition
233260

234261
end Nat

0 commit comments

Comments
 (0)