1-- Vandermonde's identity: the convolution of two rows of Pascal's triangle2-- is an entry of a later row, the sum of the squares of a row is the central3-- binomial coefficient, and the bounds that follow from it4-- Part of Cumulus, the Grace17 mathematics library.5-- Copyright (c) 2026 Grant Passmore.6-- Released under the Apache License 2.0. See LICENSE in the repository root.7-- Authors: Grant Passmore89module Grace.Cumulus.Combinatorics.Vandermonde10import Lucas11import Nat.SubtractionLemmas1213-- The partial convolution ∑_{i < k} (m choose i) · (n choose (r − i)).14def chooseConvolution (m n r k : Nat) →Nat15| m, n, r, zero =>016| m, n, r, succ(k) =>17 chooseConvolution(m, n, r, k) + choose(m, k) * choose(n, sub(r, k))18descends k1920theorem chooseConvolution.zero (m n r : Nat)21shows chooseConvolution(m, n, r, 0) =022proof23simp [chooseConvolution]2425theorem chooseConvolution.succ (m n r k : Nat)26shows chooseConvolution(m, n, r, k +1) =27 chooseConvolution(m, n, r, k) + (m choose k) * (n choose (r - k))28proof29have hk : k +1= succ(k) := add.right_one with*30rw hk31simp [chooseConvolution]3233theorem chooseConvolution.natural (m n r k : Nat)34showsNat(chooseConvolution(m, n, r, k))35proof36induction k37| zero =>38rw chooseConvolution.zero39typecheck40| j +1using ih =>41rw chooseConvolution.succ42have leftNat := choose.natural at m, j43have subNat := sub.closed at r, j44have rightNat := choose.natural at n, r - j45typecheck4647theorem chooseConvolution.one (m n r : Nat)48shows chooseConvolution(m, n, r, 1) = n choose r49proof50have one : 0+1=1bycompute51rw← one52rw [chooseConvolution.succ, chooseConvolution.zero, choose.zero_right]53rw sub.zero_right54have cNat := choose.natural at n, r55ring5657-- Row 0 of the triangle contributes only its first entry.58theorem chooseConvolution.zero_left (n r k : Nat)59shows chooseConvolution(0, n, r, k +1) = n choose r60proof61induction k62| zero =>63have one : 0+1=1bycompute64rw one65exact chooseConvolution.one at0, n, r66| j +1using ih =>67rw [chooseConvolution.succ, ih]68have hj : j +1= succ(j) := add.right_one with*69rw [hj, choose.zero_left]70have cNat := choose.natural at n, r71have subNat := sub.closed at r, succ(j)72have rightNat := choose.natural at n, r - succ(j)73ring7475-- Pascal's rule for the convolution: the first row advances by one when the76-- target splits into the sums for r + 1 and for r.77theorem chooseConvolution.pascal (m n r k : Nat)78shows chooseConvolution(m +1, n, r +1, k +1) =79 chooseConvolution(m, n, r +1, k +1) + chooseConvolution(m, n, r, k)80proof81induction k82| zero =>83have one : 0+1=1bycompute84rw [one, chooseConvolution.one, chooseConvolution.one]85rw chooseConvolution.zero86have cNat := choose.natural at n, r +187ring88| j +1using ih =>89have top := chooseConvolution.succ at m +1, n, r +1, j +190have middle := chooseConvolution.succ at m, n, r +1, j +191have low := chooseConvolution.succ at m, n, r, j92have pascalRule := choose.pascal at m, j93have shift : r +1 - (j +1) = r - j := sub.add_one_both at r, j94rw [top, ih, middle, low, pascalRule, shift]95have aNat := chooseConvolution.natural at m, n, r +1, j +196have bNat := chooseConvolution.natural at m, n, r, j97have cNat := choose.natural at m, j98have dNat := choose.natural at m, j +199have subNat := sub.closed at r, j100have eNat := choose.natural at n, r - j101ring102103-- Vandermonde's identity ∑_{i ≤ r} (m choose i) · (n choose (r − i)) =104-- (m + n) choose r.105theorem choose.vandermonde (m n : Nat)106shows∀ r, Nat(r) → chooseConvolution(m, n, r, r +1) = (m + n) choose r107proof108induction m109| zero =>110intros r rNat111have shape : 0+ n = n byring112rw [shape, chooseConvolution.zero_left]113rfl114| j +1using ih =>115intros r rNat116havesplit:= natural.zero_or_successor at r117casessplitwith isZero | isSucc118 . have one : 0+1=1bycompute119rw [isZero, one, chooseConvolution.one]120rw [choose.zero_right, choose.zero_right]121rfl122 . obtain⟨k, kNat, shape⟩:= isSucc123have hk : succ(k) = k +1124 symm125exact add.right_one at k126rw [shape, hk, chooseConvolution.pascal]127have first := ih with k +1, assumption128have second := ih with k, kNat129rw [first, second]130have commute : j +1+ n = (j + n) +1byring131rw [commute, choose.pascal]132have aNat := choose.natural at j + n, k133have bNat := choose.natural at j + n, k +1134ring135136theorem choose.vandermonde_at (m n r : Nat)137shows chooseConvolution(m, n, r, r +1) = (m + n) choose r138proof139have all := choose.vandermonde at m, n140exact all with r, assumption141142-- The convolution is symmetric in its two rows.143theorem chooseConvolution.symmetric (m n r : Nat)144shows chooseConvolution(m, n, r, r +1) = chooseConvolution(n, m, r, r +1)145proof146rw [choose.vandermonde_at, choose.vandermonde_at]147have commute : m + n = n + m byring148rw commute149rfl150151theorem choose.vandermonde_one (m n : Nat)152shows chooseConvolution(m, n, 1, 2) = m + n153proof154have two : 1+1=2bycompute155rw← two156rw [choose.vandermonde_at, choose.one_right]157rfl158159-- Partial sums of the squares of a row.160def chooseSquareSum (n k : Nat) →Nat161| n, zero =>0162| n, succ(k) => chooseSquareSum(n, k) + choose(n, k) * choose(n, k)163descends k164165theorem chooseSquareSum.zero (n : Nat)166shows chooseSquareSum(n, 0) =0167proof168simp [chooseSquareSum]169170theorem chooseSquareSum.succ (n k : Nat)171shows chooseSquareSum(n, k +1) =172 chooseSquareSum(n, k) + (n choose k) * (n choose k)173proof174have hk : k +1= succ(k) := add.right_one with*175rw hk176simp [chooseSquareSum]177178theorem chooseSquareSum.natural (n k : Nat)179showsNat(chooseSquareSum(n, k))180proof181induction k182| zero =>183rw chooseSquareSum.zero184typecheck185| j +1using ih =>186rw chooseSquareSum.succ187have cNat := choose.natural at n, j188typecheck189190-- Below the diagonal the self-convolution of a row is the sum of its squares.191theorem chooseSquareSum.convolution (n k : Nat)192requires le : k ≤ n +1193shows chooseConvolution(n, n, n, k) = chooseSquareSum(n, k)194proof195induction k196| zero =>197intro le198rw [chooseConvolution.zero, chooseSquareSum.zero]199rfl200| j +1using ih =>201intro le202have earlier : j ≤ n +1byarith203have inner := ih with earlier204rw [chooseConvolution.succ, chooseSquareSum.succ, inner]205have jLe : j ≤ n byarith206have mirror := choose.symmetric at n, j with jLe207rw← mirror208rfl209210-- ∑_{k ≤ n} (n choose k)² = 2n choose n.211theorem choose.central_square_sum (n : Nat)212shows chooseSquareSum(n, n +1) = (2* n) choose n213proof214have le : n +1≤ n +1byarith215have convolution := chooseSquareSum.convolution at n, n +1with le216rw← convolution217rw choose.vandermonde_at218have double : n + n =2* n byring219rw double220rfl221222theorem chooseSquareSum.monotone (n k d : Nat)223shows chooseSquareSum(n, k) ≤ chooseSquareSum(n, k + d)224proof225induction d226| zero =>227have shape : k +0= k byring228rw shape229have sumNat := chooseSquareSum.natural at n, k230exact LessEqual.reflexive at chooseSquareSum(n, k)231| j +1using ih =>232have shape : k + (j +1) = (k + j) +1byring233rw [shape, chooseSquareSum.succ]234have sumNat := chooseSquareSum.natural at n, k235have sumNatJ := chooseSquareSum.natural at n, k + j236have cNat := choose.natural at n, k + j237have squareNat : Nat((n choose (k + j)) * (n choose (k + j))) bytypecheck238arith239240theorem chooseSquareSum.term_le (n k m : Nat)241requires below : k < m242shows (n choose k) * (n choose k) ≤ chooseSquareSum(n, m)243proof244induction m245| zero =>246intro below247have cNat := choose.natural at n, k248have squareNat : Nat((n choose k) * (n choose k)) bytypecheck249arith250| j +1using ih =>251intro below252rw chooseSquareSum.succ253have sumNat := chooseSquareSum.natural at n, j254have cNat := choose.natural at n, k255have cNatJ := choose.natural at n, j256have squareNat : Nat((n choose k) * (n choose k)) bytypecheck257have squareNatJ : Nat((n choose j) * (n choose j)) bytypecheck258have alternatives : k = j ∨ k ≠ j bytauto259cases alternatives with same | different260 . rw same261arith262 . have hj : j +1= succ(j) := add.right_one with*263rw hj at below264have strict := Less.of_successor_upper_ne with below, different265have bound := ih with strict266arith267268-- Every square of a row entry is bounded by the central coefficient.269theorem choose.square_le_central (n k : Nat)270requires le : k ≤ n271shows (n choose k) * (n choose k) ≤ (2* n) choose n272proof273rw← choose.central_square_sum274have below : k < n +1byarith275exact chooseSquareSum.term_le with below276277theorem choose.le_central_of_le (n k : Nat)278requires le : k ≤ n279shows n choose k ≤ (2* n) choose n280proof281have square := choose.square_le_central with le282have cNat := choose.natural at n, k283haveself:= LessEqual.mul_self at n choose k284have centralNat := choose.natural at2* n, n285exact LessEqual.transitive withself, square286287-- The row sum is bounded by the sum of the squares.288theorem chooseRowSum.le_square_sum (n k : Nat)289shows chooseRowSum(n, k) ≤ chooseSquareSum(n, k)290proof291induction k292| zero =>293rw [chooseRowSum.zero, chooseSquareSum.zero]294exact LessEqual.reflexive at0295| j +1using ih =>296rw [chooseRowSum.succ, chooseSquareSum.succ]297have cNat := choose.natural at n, j298haveself:= LessEqual.mul_self at n choose j299have rowNat := chooseRowSum.natural at n, j300have squareNat := chooseSquareSum.natural at n, j301exact LessEqual.add with ih, self302303-- The central binomial coefficient dominates 2 ^ n.304theorem choose.central_ge_pow (n : Nat)305shows2 ^ n ≤ (2* n) choose n306proof307rw [← choose.central_square_sum, ← chooseRowSum.full]308exact chooseRowSum.le_square_sum at n, n +1309