1-- Counting by fibres: a finite set is the sum of the sizes of the preimages2-- of a function on it, sums of constant and bounded weights, the3-- generalized pigeonhole principle, and the size of a disjoint union of a4-- finite family5-- 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.Partitions11import Permutations1213-- Preimages of single points.14theorem functionPreimage.point_membership (f A b x : Set)15shows x ∈ functionPreimage(f, A, {b}) ↔ x ∈ A ∧ f(x) = b16proof17constructor18 . intro member19have parts := functionPreimage.membership.forward with member20have equal := singleton.membership.forward with parts.right21exact⟨parts.left, equal⟩22 . intro parts23apply functionPreimage.membership.backward24refine⟨parts.left, ?_⟩25rw parts.right26exact singleton.self at b2728theorem functionPreimage.point_finite (f A b : Set)29requires finite : Finite(A)30shows Finite(functionPreimage(f, A, {b}))31proof32exact Finite.subset with finite, (functionPreimage.subset at f, A, {b})3334theorem functionPreimage.point_nonempty (f A B b : Set)35requires surjective : SurjectiveBetween(f, A, B)36requires hb : b ∈ B37shows Nonempty(functionPreimage(f, A, {b}))38proof39have function := Surjective.function with surjective40obtain⟨x, hx, related⟩:= surjective.right with b, hb41have value := FunctionBetween.apply_value with function, hx, related42exact⟨x, functionPreimage.point_membership.backward with⟨hx, value⟩⟩4344theorem functionPreimage.point_le_one (f A B b : Set)45requires injective : InjectiveBetween(f, A, B)46shows cardinality(functionPreimage(f, A, {b})) ≤147proof48have alternatives : Nonempty(functionPreimage(f, A, {b})) ∨¬Nonempty(functionPreimage(f, A, {b})) bytauto49cases alternatives with nonempty | empty50 . obtain⟨x, hx⟩:= nonempty51have partsX := functionPreimage.point_membership.forward with hx52have inside : functionPreimage(f, A, {b}) ⊆ {x}53intros y hy54have partsY := functionPreimage.point_membership.forward with hy55have same : f(y) = f(x)56rw [partsX.right, partsY.right]57rfl58have equal := Injective.apply_cancel with59 injective, partsY.left, partsX.left, same60rw equal61exact singleton.self at x62exact cardinality.le_one_of_subset_singleton with inside63 . have isEmpty : functionPreimage(f, A, {b}) =∅64by_contra different65apply empty66exact Nonempty.of_ne_empty with different67rw [isEmpty, cardinality.empty]68arith6970-- The fibre of the converse over a point is the preimage of that point.71theorem fiber.converse_point (f A B b : Set)72requires function : f : A → B73shows fiber(converse(f, A, B), A, b) = functionPreimage(f, A, {b})74proof75ext x76constructor77 . intro hx78have parts := fiber.membership.forward with hx79have converseParts := converse.membership.forward with parts.right80obtain⟨u, hu, y, hy, shape, related⟩:= converseParts.right81have coordinates := orderedPair.injective with shape82rw [coordinates.left, coordinates.right]83have value := FunctionBetween.apply_value with function, hu, related84exact functionPreimage.point_membership.backward with⟨hu, value⟩85 . intro hx86have parts := functionPreimage.point_membership.forward with hx87have related := FunctionBetween.relates_apply with function, parts.left88rw parts.right at related89have inB : b ∈ B90rw← parts.right91exact FunctionBetween.apply_closed with function, parts.left92have member := converse.relates with parts.left, inB, related93have inConverse : (b, x) ∈ converse(f, A, B) := member94exact fiber.membership.backward with⟨parts.left, inConverse⟩9596defgraph preimageSize (f A B : Set) (b ∈ B) : ℕ := cardinality(functionPreimage(f, A, {b}))97requires finiteA : Finite(A)98proof99have finite := functionPreimage.point_finite at f, A, b with finiteA100have natural := cardinality.nat with finite101exact nat.omega.forward with natural102103-- The converse graph of a function is a copy of its domain.104defgraph converseEntry (f A B : Set) (x ∈ A) : converse(f, A, B) := (f(x), x)105requires function : f : A → B106proof107have value := FunctionBetween.apply_closed with function, member108have related := FunctionBetween.relates_apply with function, member109exact converse.relates with member, value, related110111theorem converseEntry.injective (f A B : Set)112requires function : f : A → B113shows InjectiveBetween(converseEntry(f, A, B), A, converse(f, A, B))114proof115have graph := converseEntry.function at f, A, B with function116apply Injective.of_pointwise with graph117intros x hx y hy same118have valueX := converseEntry.value at f, A, B, x with function, hx119have valueY := converseEntry.value at f, A, B, y with function, hy120rw [valueX, valueY] at same121have coordinates := orderedPair.injective with same122exact coordinates.right123124theorem converseEntry.surjective (f A B : Set)125requires function : f : A → B126shows SurjectiveBetween(converseEntry(f, A, B), A, converse(f, A, B))127proof128have graph := converseEntry.function at f, A, B with function129unfold SurjectiveBetween130refine⟨graph, ?_⟩131intros p hp132have parts := converse.membership.forward with hp133obtain⟨x, hx, y, hy, shape, related⟩:= parts.right134have value := FunctionBetween.apply_value with function, hx, related135have entry := converseEntry.value at f, A, B, x with function, hx136refine⟨x, hx, ?_⟩137have target : converseEntry(f, A, B)(x) = p138rw [entry, shape, value]139rfl140rw← target141exact FunctionBetween.relates_apply with graph, hx142143theorem converseEntry.bijective (f A B : Set)144requires function : f : A → B145shows BijectiveBetween(converseEntry(f, A, B), A, converse(f, A, B))146proof147have injective := converseEntry.injective with function148have surjective := converseEntry.surjective with function149exact⟨injective, surjective⟩150151theorem converse.count (f A B : Set)152requires function : f : A → B153requires finite : Finite(A)154shows cardinality(converse(f, A, B)) = cardinality(A)155proof156have bijective := converseEntry.bijective with function157 symm158exact cardinality.bijective with finite, bijective159160theorem converse.finite_of_function (f A B : Set)161requires function : f : A → B162requires finite : Finite(A)163shows Finite(converse(f, A, B))164proof165have bijective := converseEntry.bijective with function166exact Finite.of_equipotent with finite, ⟨converseEntry(f, A, B), bijective⟩167168-- A finite set is the sum of the sizes of the preimages of the points of169-- the codomain.170theorem cardinality.sum_of_preimages (f A B : Set)171requires function : f : A → B172requires finiteA : Finite(A)173requires finiteB : Finite(B)174shows cardinality(A) = natSum(B, preimageSize(f, A, B))175proof176have inside : converse(f, A, B) ⊆ B × A := converse.relation at f, A, B177have size : cardinality(B) = cardinality(B) byrfl178have natB := cardinality.nat with finiteB179have count := relation.count at converse(f, A, B), B, A, cardinality(B) with180 finiteB, finiteA, size, inside181have graph := converse.count with function, finiteA182rw graph at count183have agree : ∀ b ∈ B, fiberSize(converse(f, A, B), B, A)(b) = preimageSize(f, A, B)(b)184intros b hb185haveleft:= fiberSize.value at converse(f, A, B), B, A, b with finiteA, hb186haveright:= preimageSize.value at f, A, B, b with finiteA, hb187have same := fiber.converse_point at f, A, B, b with function188rw [left, right, same]189rfl190rw count191exact natSum.congr with agree192193-- Sums of constant and of bounded weights.194theorem natSum.constant_of_cardinality (n : Nat)195shows∀ V, ∀ w, ∀ c, Nat(c) → HasCardinality(V, n) → (∀ v ∈ V, w(v) = c) →196 natSum(V, w) = n * c197proof198induction n199| zero =>200intros V w c cNat counted agree201have empty := HasCardinality.empty_of_zero at V with counted202rw [empty, natSum.empty]203ring204| k +1using ih =>205intros V w c cNat counted agree206have hk : k +1= succ(k) := add.right_one with*207rw hk at counted208obtain⟨a, member⟩:= HasCardinality.exists_member with counted209have rest := HasCardinality.remove at V, a, k with counted, member210have fresh : ¬a ∈ V ∖ {a} bysimp [difference.membership, singleton.membership]211have restored := insert.restore with member212have restAgree : ∀ v ∈ V ∖ {a}, w(v) = c213intros v hv214exact agree with v, (difference.subset_left at V, {a} with v, hv)215have restSum := ih with V ∖ {a}, w, c, cNat, rest, restAgree216have restFinite := Finite.from_cardinality with rest217have cMember := nat.omega.forward at c with cNat218have weights : ∀ v ∈ V ∖ {a}, w(v) ∈ ℕ219intros v hv220have same := restAgree with v, hv221rw same222exact cMember223have valueA := agree with a, member224have weight : w(a) ∈ ℕ225rw valueA226exact cMember227havesplit:= natSum.insert at V ∖ {a}, w, a with228 restFinite, weights, weight, fresh229rw restored atsplit230rw [split, restSum, valueA]231ring232233theorem natSum.constant (V w : Set) (c : Nat)234requires finite : Finite(V)235requires agree : ∀ v ∈ V, w(v) = c236shows natSum(V, w) = cardinality(V) * c237proof238have counted := cardinality.correct with finite239have natV := cardinality.nat with finite240have all := natSum.constant_of_cardinality at cardinality(V)241exact all with V, w, c, assumption, counted, agree242243theorem natSum.bounded_of_cardinality (n : Nat)244shows∀ V, ∀ w, ∀ c, Nat(c) → HasCardinality(V, n) →245 (∀ v ∈ V, w(v) ∈ ℕ ∧ LessEqual(w(v), c)) → natSum(V, w) ≤ n * c246proof247induction n248| zero =>249intros V w c cNat counted bounds250have empty := HasCardinality.empty_of_zero at V with counted251rw [empty, natSum.empty]252have zero : 0* c =0byring253rw zero254arith255| k +1using ih =>256intros V w c cNat counted bounds257have hk : k +1= succ(k) := add.right_one with*258rw hk at counted259obtain⟨a, member⟩:= HasCardinality.exists_member with counted260have rest := HasCardinality.remove at V, a, k with counted, member261have fresh : ¬a ∈ V ∖ {a} bysimp [difference.membership, singleton.membership]262have restored := insert.restore with member263have restBounds : ∀ v ∈ V ∖ {a}, w(v) ∈ ℕ ∧ LessEqual(w(v), c)264intros v hv265exact bounds with v, (difference.subset_left at V, {a} with v, hv)266have restSum := ih with V ∖ {a}, w, c, cNat, rest, restBounds267have restFinite := Finite.from_cardinality with rest268have weights : ∀ v ∈ V ∖ {a}, w(v) ∈ ℕ269intros v hv270have parts := restBounds with v, hv271exact parts.left272have boundA := bounds with a, member273have weight := boundA.left274have weightNat := nat.omega.backward with weight275havesplit:= natSum.insert at V ∖ {a}, w, a with276 restFinite, weights, weight, fresh277rw restored atsplit278rwsplit279have expand : (k +1) * c = k * c + c byring280rw expand281have sumNat := natSum.natural with restFinite, weights282have productNat : Nat(k * c) bytypecheck283have valueBound := boundA.right284arith285286theorem natSum.le_of_bounded (V w : Set) (c : Nat)287requires finite : Finite(V)288requires bounds : ∀ v ∈ V, w(v) ∈ ℕ ∧ LessEqual(w(v), c)289shows natSum(V, w) ≤ cardinality(V) * c290proof291have counted := cardinality.correct with finite292have natV := cardinality.nat with finite293have all := natSum.bounded_of_cardinality at cardinality(V)294exact all with V, w, c, assumption, counted, bounds295296theorem natSum.lower_of_cardinality (n : Nat)297shows∀ V, ∀ w, ∀ c, Nat(c) → HasCardinality(V, n) →298 (∀ v ∈ V, w(v) ∈ ℕ ∧ LessEqual(c, w(v))) → n * c ≤ natSum(V, w)299proof300induction n301| zero =>302intros V w c cNat counted bounds303have empty := HasCardinality.empty_of_zero at V with counted304rw [empty, natSum.empty]305have zero : 0* c =0byring306rw zero307arith308| k +1using ih =>309intros V w c cNat counted bounds310have hk : k +1= succ(k) := add.right_one with*311rw hk at counted312obtain⟨a, member⟩:= HasCardinality.exists_member with counted313have rest := HasCardinality.remove at V, a, k with counted, member314have fresh : ¬a ∈ V ∖ {a} bysimp [difference.membership, singleton.membership]315have restored := insert.restore with member316have restBounds : ∀ v ∈ V ∖ {a}, w(v) ∈ ℕ ∧ LessEqual(c, w(v))317intros v hv318exact bounds with v, (difference.subset_left at V, {a} with v, hv)319have restSum := ih with V ∖ {a}, w, c, cNat, rest, restBounds320have restFinite := Finite.from_cardinality with rest321have weights : ∀ v ∈ V ∖ {a}, w(v) ∈ ℕ322intros v hv323have parts := restBounds with v, hv324exact parts.left325have boundA := bounds with a, member326have weight := boundA.left327have weightNat := nat.omega.backward with weight328havesplit:= natSum.insert at V ∖ {a}, w, a with329 restFinite, weights, weight, fresh330rw restored atsplit331rwsplit332have expand : (k +1) * c = k * c + c byring333rw expand334have sumNat := natSum.natural with restFinite, weights335have productNat : Nat(k * c) bytypecheck336have valueBound := boundA.right337arith338339theorem natSum.ge_of_bounded (V w : Set) (c : Nat)340requires finite : Finite(V)341requires bounds : ∀ v ∈ V, w(v) ∈ ℕ ∧ LessEqual(c, w(v))342shows cardinality(V) * c ≤ natSum(V, w)343proof344have counted := cardinality.correct with finite345have natV := cardinality.nat with finite346have all := natSum.lower_of_cardinality at cardinality(V)347exact all with V, w, c, assumption, counted, bounds348349-- The generalized pigeonhole principle: more than k · |B| points cannot350-- all land in preimages of size at most k.351theorem pigeonhole.large_preimage (f A B : Set) (k : Nat)352requires function : f : A → B353requires finiteA : Finite(A)354requires finiteB : Finite(B)355requires more : k * cardinality(B) < cardinality(A)356shows∃ b ∈ B, k < cardinality(functionPreimage(f, A, {b}))357proof358by_contra none359have bounds : ∀ b ∈ B, preimageSize(f, A, B)(b) ∈ ℕ ∧ LessEqual(preimageSize(f, A, B)(b), k)360intros b hb361have value := preimageSize.value at f, A, B, b with finiteA, hb362have finitePre := functionPreimage.point_finite at f, A, b with finiteA363have natPre := cardinality.nat with finitePre364rw value365constructor366 . exact nat.omega.forward with natPre367 . have notMore : ¬k < cardinality(functionPreimage(f, A, {b}))368intro h369apply none370exact⟨b, hb, h⟩371exact LessEqual.of_not_Less with notMore372have total := natSum.le_of_bounded at B, preimageSize(f, A, B), k with373 finiteB, bounds374have sum := cardinality.sum_of_preimages with function, finiteA, finiteB375rw← sum at total376have natA := cardinality.nat with finiteA377have natB := cardinality.nat with finiteB378have commute : cardinality(B) * k = k * cardinality(B) byring379rw commute at total380have strict := Less.transitive_LessEqual with more, total381exact Less.irreflexive at k * cardinality(B) with strict382383theorem pigeonhole.small_preimage (f A B : Set) (k : Nat)384requires function : f : A → B385requires finiteA : Finite(A)386requires finiteB : Finite(B)387requires fewer : cardinality(A) < k * cardinality(B)388shows∃ b ∈ B, cardinality(functionPreimage(f, A, {b})) < k389proof390by_contra none391have bounds : ∀ b ∈ B, preimageSize(f, A, B)(b) ∈ ℕ ∧ LessEqual(k, preimageSize(f, A, B)(b))392intros b hb393have value := preimageSize.value at f, A, B, b with finiteA, hb394have finitePre := functionPreimage.point_finite at f, A, b with finiteA395have natPre := cardinality.nat with finitePre396rw value397constructor398 . exact nat.omega.forward with natPre399 . have notFewer : ¬cardinality(functionPreimage(f, A, {b})) < k400intro h401apply none402exact⟨b, hb, h⟩403exact LessEqual.of_not_Less with notFewer404have total := natSum.ge_of_bounded at B, preimageSize(f, A, B), k with405 finiteB, bounds406have sum := cardinality.sum_of_preimages with function, finiteA, finiteB407rw← sum at total408have natA := cardinality.nat with finiteA409have natB := cardinality.nat with finiteB410have commute : cardinality(B) * k = k * cardinality(B) byring411rw commute at total412have strict := Less.transitive_LessEqual with fewer, total413exact Less.irreflexive at cardinality(A) with strict414415-- Disjoint unions of finite families.416predicate PairwiseDisjoint (F : Set) :=∀ S ∈ F, ∀ T ∈ F, S ≠ T → Disjoint(S, T)417418theorem PairwiseDisjoint.subset (F G : Set)419requires subset : G ⊆ F420requires disjoint : PairwiseDisjoint(F)421shows PairwiseDisjoint(G)422proof423intros S hS T hT different424exact disjoint with S, (subset with S, hS), T, (subset with T, hT), different425426theorem PairwiseDisjoint.member_union (F S : Set)427requires disjoint : PairwiseDisjoint(insert(S, F))428requires fresh : ¬S ∈ F429shows Disjoint(S, ⋃F)430proof431apply Disjoint.iff.backward432intros x hx inUnion433obtain⟨T, hT, hxT⟩:= union.membership.forward with inUnion434have different : S ≠ T435intro equal436apply fresh437rw equal438exact hT439have pair := disjoint with S, (insert.self at S, F), T, (insert.contains with440 hT), different441have free := Disjoint.iff.forward with pair442exact free with x, hx, hxT443444defgraph sizeMap (F : Set) (S ∈ F) : ℕ := cardinality(S)445requires members : ∀ S ∈ F, Finite(S)446proof447have finite := members with S, member448have natural := cardinality.nat with finite449exact nat.omega.forward with natural450451theorem cardinality.big_union_of_cardinality (n : Nat)452shows∀ F, HasCardinality(F, n) → (∀ S ∈ F, Finite(S)) → PairwiseDisjoint(F) →453 cardinality(⋃F) = natSum(F, sizeMap(F))454proof455induction n456| zero =>457intros F counted members disjoint458have empty := HasCardinality.empty_of_zero at F with counted459rw [empty, union.empty, natSum.empty]460exact cardinality.empty461| k +1using ih =>462intros F counted members disjoint463have hk : k +1= succ(k) := add.right_one with*464rw hk at counted465obtain⟨S, member⟩:= HasCardinality.exists_member with counted466have rest := HasCardinality.remove at F, S, k with counted, member467have fresh : ¬S ∈ F ∖ {S} bysimp [difference.membership, singleton.membership]468have restored := insert.restore with member469have restSubset := difference.subset_left at F, {S}470have restMembers : ∀ T ∈ F ∖ {S}, Finite(T)471intros T hT472exact members with T, (restSubset with T, hT)473have restDisjoint := PairwiseDisjoint.subset with restSubset, disjoint474have restCount := ih with F ∖ {S}, rest, restMembers, restDisjoint475have restFinite := Finite.from_cardinality with rest476have finiteS := members with S, member477have finiteUnion := Finite.big_union with restFinite, restMembers478rw← restored at disjoint479have separated := PairwiseDisjoint.member_union with disjoint, fresh480havesplit:= union.insert at F ∖ {S}, S481rw restored atsplit482have count := cardinality.union_disjoint with483 finiteS, finiteUnion, separated484have weights : ∀ T ∈ F ∖ {S}, sizeMap(F)(T) ∈ ℕ485intros T hT486have value := sizeMap.value at F, T with members, (restSubset with T, hT)487rw value488exact nat.omega.forward with (cardinality.nat with (members with T, (restSubset with T, hT)))489have weight : sizeMap(F)(S) ∈ ℕ490have value := sizeMap.value at F, S with members, member491rw value492exact nat.omega.forward with (cardinality.nat with finiteS)493have sum := natSum.insert at F ∖ {S}, sizeMap(F), S with494 restFinite, weights, weight, fresh495rw restored at sum496have agree : ∀ T ∈ F ∖ {S}, sizeMap(F)(T) = sizeMap(F ∖ {S})(T)497intros T hT498haveleft:= sizeMap.value at F, T with members, (restSubset with T, hT)499haveright:= sizeMap.value at F ∖ {S}, T with restMembers, hT500rw [left, right]501rfl502have same := natSum.congr with agree503have valueS := sizeMap.value at F, S with members, member504rw [split, count, restCount, sum, same, valueS]505have natS := cardinality.nat with finiteS506have restWeights : ∀ T ∈ F ∖ {S}, sizeMap(F ∖ {S})(T) ∈ ℕ507intros T hT508have value := sizeMap.value at F ∖ {S}, T with restMembers, hT509rw value510exact nat.omega.forward with (cardinality.nat with (restMembers with T, hT))511have sumNat := natSum.natural at F ∖ {S}, sizeMap(F ∖ {S}) with512 restFinite, restWeights513exact add.commutative at natSum(F ∖ {S}, sizeMap(F ∖ {S})), cardinality(S) with sumNat, natS514515-- The size of a disjoint union of a finite family of finite sets.516theorem cardinality.big_union_disjoint (F : Set)517requires finite : Finite(F)518requires members : ∀ S ∈ F, Finite(S)519requires disjoint : PairwiseDisjoint(F)520shows cardinality(⋃F) = natSum(F, sizeMap(F))521proof522have counted := cardinality.correct with finite523have natF := cardinality.nat with finite524have all := cardinality.big_union_of_cardinality at cardinality(F)525exact all with F, counted, members, disjoint526527theorem cardinality.big_union_constant (F : Set) (c : Nat)528requires finite : Finite(F)529requires members : ∀ S ∈ F, Finite(S)530requires disjoint : PairwiseDisjoint(F)531requires sizes : ∀ S ∈ F, cardinality(S) = c532shows cardinality(⋃F) = cardinality(F) * c533proof534have count := cardinality.big_union_disjoint with finite, members, disjoint535have agree : ∀ S ∈ F, sizeMap(F)(S) = c536intros S hS537have value := sizeMap.value at F, S with members, hS538rw value539exact sizes with S, hS540rw count541exact natSum.constant with finite, agree542