1-- The derangement formula: d(n) = ∑_{k ≤ n} (−1)^k (n choose k) (n − k)!,2-- the inclusion–exclusion count of the permutations with no fixed point,3-- proved in the integers from the one-step recurrence4-- d(n + 1) = (n + 1) d(n) + (−1)^(n + 1).5-- Part of Cumulus, the Grace17 mathematics library.6-- Copyright (c) 2026 Grant Passmore.7-- Released under the Apache License 2.0. See LICENSE in the repository root.8-- Authors: Grant Passmore910module Grace.Cumulus.Combinatorics.DerangementFormula11import Derangements12import Int1314-- The partial sums ∑_{k < j} (−1)^k (n choose k) (n − k)!: the term of15-- index k counts the permutations of n points fixing k chosen points,16-- with the sign of inclusion–exclusion.17def derangementSum (n j : Nat) → Integer18| n, zero => integerZero19| n, succ(k) => derangementSum(n, k) +20 integerPow(-1, k) * integerOfNat(mul(choose(n, k), factorial(sub(n, k))))2122theorem derangementSum.integer (n j : Nat)23shows Integer(derangementSum(n, j))24proof25induction j26| zero =>27simp [derangementSum]28exact integerZero.integer29| succ(k) using ih =>30simp [derangementSum]31have minus : Integer(-1) bytypecheck32have power := integerPow.integer at -1, k with minus33have term := integerOfNat.integer at (n choose k) * (n - k)!34typecheck3536theorem derangementSum.zero (n : Nat)37shows derangementSum(n, 0) =038proof39simp [derangementSum]4041theorem derangementSum.succ (n k : Nat)42shows derangementSum(n, k +1) = derangementSum(n, k) +43 integerPow(-1, k) * integerOfNat((n choose k) * (n - k)!)44proof45have one : k +1= succ(k) := add.right_one with*46rw one47simp [derangementSum]4849-- The term of index k grows by the factor n + 1 when n grows by one.50theorem choose.factorial_step (n k : Nat)51requires le : k ≤ n52shows ((n +1) choose k) * (n +1 - k)! = (n +1) * ((n choose k) * (n - k)!)53proof54obtain⟨d, dNat, shape⟩:= le55have form : n = k + d56rw← shape57rfl58have difference : n - k = d59have commute : k + d = d + k byring60rw [form, commute]61exact sub.add_right at d, k62have differenceSucc : n +1 - k = d +163have commute : k + d +1= (d +1) + k byring64rw [form, commute]65exact sub.add_right at d +1, k66have leSucc : k ≤ n +1byarith67have top := choose.factorial_form at n +1, k with leSucc68have bottom := choose.factorial_form at n, k with le69rw [difference, differenceSucc]70rw differenceSucc at top71rw difference at bottom72have chooseTop := choose.natural at n +1, k73have chooseBottom := choose.natural at n, k74have closedK := factorial.closed at k75have closedD := factorial.closed at d76have closedD1 := factorial.closed at d +177have closedN := factorial.closed at n78haveleft : k! * (((n +1) choose k) * (d +1)!) = (n +1)!79have reorder : k! * (((n +1) choose k) * (d +1)!) =80 ((n +1) choose k) * (k! * (d +1)!) byring81rw [reorder, top]82rfl83haveright : k! * ((n +1) * ((n choose k) * d!)) = (n +1)!84have reorder : k! * ((n +1) * ((n choose k) * d!)) =85 (n +1) * ((n choose k) * (k! * d!)) byring86rw [reorder, bottom, factorial.succ]87rfl88have balanced : k! * (((n +1) choose k) * (d +1)!) =89 k! * ((n +1) * ((n choose k) * d!))90rw [left, right]91rfl92exact mul.left_cancel_positive with (factorial.nonzero at k), balanced9394-- Up to the last term, the sums for n + 1 and for n differ by the factor95-- n + 1.96theorem derangementSum.scale (n j : Nat)97shows j ≤ n +1→98 derangementSum(n +1, j) = integerOfNat(n +1) * derangementSum(n, j)99proof100induction j101| zero =>102intro le103rw [derangementSum.zero, derangementSum.zero]104have top := integerOfNat.integer at n +1105ring106| k +1using ih =>107intro le108have kLe : k ≤ n byarith109have kLeSucc : k ≤ n +1byarith110have inner := ih with kLeSucc111rw [derangementSum.succ, derangementSum.succ, inner]112have term := choose.factorial_step with kLe113rw [term, integerOfNat.mul]114have minus : Integer(-1) bytypecheck115have power := integerPow.integer at -1, k with minus116have sumInt := derangementSum.integer at n, k117have topInt := integerOfNat.integer at n +1118have termInt := integerOfNat.integer at (n choose k) * (n - k)!119ring120121-- The one-step recurrence d(n + 1) = (n + 1) d(n) + (−1)^(n + 1), in ℤ.122theorem derangement.succ_integer (n : Nat)123shows integerOfNat(derangement(n +1)) =124 integerOfNat(n +1) * integerOfNat(derangement(n)) + integerPow(-1, n +1)125proof126have minus : Integer(-1) bytypecheck127have alternatives := parity.exhaustive at n128cases alternatives with even | odd129 . have iff := Even.iff_two_mul at n130obtain⟨k, kNat, shape⟩:= iff.forward with even131rw shape132have step := derangement.odd_succ at k133have cast : integerOfNat(derangement(2* k +1) +1) =134 integerOfNat((2* k +1) * derangement(2* k))135rw step136rfl137rw [integerOfNat.add, integerOfNat.mul, integerOfNat.one] at cast138have sign : integerPow(-1, 2* k +1) = -1139have double : 2* k +1= k + k +1byring140rw [double, integerPow.neg_double_add_one, integerPow.one_left]141rfl142rw sign143have dInt := integerOfNat.integer at derangement(2* k +1)144haveunfold : integerOfNat(derangement(2* k +1)) =145 (integerOfNat(derangement(2* k +1)) +1) + -1byring146rw [unfold, cast]147have topInt := integerOfNat.integer at2* k +1148have previousInt := integerOfNat.integer at derangement(2* k)149ring150 . have iff := Odd.iff_two_mul_add_one at n151obtain⟨k, kNat, shape⟩:= iff.forward with odd152rw shape153have step := derangement.even_succ at k154have two : 2* k +1+1=2* k +2byring155rw two156have cast : integerOfNat(derangement(2* k +2)) =157 integerOfNat((2* k +2) * derangement(2* k +1) +1)158rw step159rfl160rw [integerOfNat.add, integerOfNat.mul, integerOfNat.one] at cast161have sign : integerPow(-1, 2* k +2) =1162have double : 2* k +2= (k +1) + (k +1) byring163rw [double, integerPow.neg_double, integerPow.one_left]164rfl165rw [sign, cast]166rfl167168-- The derangement formula.169theorem derangement.closed_form (n : Nat)170shows integerOfNat(derangement(n)) = derangementSum(n, n +1)171proof172induction n173| zero =>174have shape := derangementSum.succ at0, 0175have term : (0 choose 0) * (0 - 0)! =1bycompute176rw [derangement.zero, integerOfNat.one, shape, term]177rw [derangementSum.zero, integerPow.zero, integerOfNat.one]178ring179| k +1using ih =>180have step := derangement.succ_integer at k181have last : ((k +1) choose (k +1)) * (k +1 - (k +1))! =1182rw [choose.diagonal, sub.self]183compute184have scale := derangementSum.scale at k, k +1185have scaled := scale with (LessEqual.reflexive at k +1)186rw [derangementSum.succ, last, integerOfNat.one, scaled, step, ih]187have minus : Integer(-1) bytypecheck188have power := integerPow.integer at -1, k +1with minus189have sumInt := derangementSum.integer at k, k +1190have topInt := integerOfNat.integer at k +1191ring192