1-- Double counting: the size of a finite relation is the sum, over the2-- first coordinates, of the sizes of its fibres3-- Part of Cumulus, the Grace17 mathematics library.4-- Copyright (c) 2026 Grant Passmore.5-- Released under the Apache License 2.0. See LICENSE in the repository root.6-- Authors: Grant Passmore78module Grace.Cumulus.Cardinal.DoubleCounting9import NaturalSum1011def fiber (I D r : Set) : Set:= { d ∈ D | (r, d) ∈ I }1213theorem fiber.finite (I D r : Set)14requires finite : Finite(D)15shows Finite(fiber(I, D, r))16proof17have inside : fiber(I, D, r) ⊆ D18intros d hd19have parts := fiber.membership.forward with hd20exact parts.left21exact Finite.subset with finite, inside2223defgraph fiberSize (I R D : Set) (r ∈ R) : ℕ := cardinality(fiber(I, D, r))24requires finiteD : Finite(D)25proof26have finite := fiber.finite at I, D, r with finiteD27have correct := cardinality.correct with finite28exact nat.omega.forward with correct.left2930-- The row of the relation over a point is a copy of its fibre.31theorem fiber.row (I D a : Set)32shows I ∩ ({a} × D) = {a} × fiber(I, D, a)33proof34haveself : a ∈ {a} bysimp [singleton.membership]35ext p36constructor37 . intro hp38have both := intersection.membership.forward with hp39obtain⟨bound, x, hx, d, hd, shape⟩:= product.membership.forward with both.right40have same := singleton.membership.forward with hx41rw same at shape42have inI := both.left43rw shape at inI44have member : d ∈ fiber(I, D, a) := fiber.membership.backward with⟨hd, inI⟩45rw shape46exact product.pair withself, member47 . intro hp48obtain⟨bound, x, hx, d, hd, shape⟩:= product.membership.forward with hp49have same := singleton.membership.forward with hx50rw same at shape51have parts := fiber.membership.forward with hd52apply intersection.membership.backward53rw shape54exact⟨parts.right, (product.pair withself, parts.left)⟩5556theorem fiber.restrict (I D R r : Set)57requires hr : r ∈ R58shows fiber(I ∩ (R × D), D, r) = fiber(I, D, r)59proof60ext d61constructor62 . intro hd63have parts := fiber.membership.forward with hd64have both := intersection.membership.forward with parts.right65exact fiber.membership.backward with⟨parts.left, both.left⟩66 . intro hd67have parts := fiber.membership.forward with hd68apply fiber.membership.backward69refine⟨parts.left, ?_⟩70apply intersection.membership.backward71exact⟨parts.right, (product.pair with hr, parts.left)⟩7273-- Splitting a relation at a point of its domain.74theorem relation.split (I R D a : Set)75requires inside : I ⊆ R × D76requires ha : a ∈ R77shows I = (I ∩ ((R ∖ {a}) × D)) ∪ ({a} × fiber(I, D, a))78proof79rw← fiber.row80ext p81constructor82 . intro hp83obtain⟨bound, r, hr, d, hd, shape⟩:= product.membership.forward with (inside with p, hp)84havesplit : r = a ∨ r ≠ a bytauto85casessplitwith same | different86 . apply union2.right87apply intersection.membership.backward88refine⟨hp, ?_⟩89rw [shape, same]90haveself : a ∈ {a} bysimp [singleton.membership]91exact product.pair withself, hd92 . apply union2.left93apply intersection.membership.backward94refine⟨hp, ?_⟩95rw shape96have kept : r ∈ R ∖ {a} := difference.singleton_membership.backward with⟨hr, different⟩97exact product.pair with kept, hd98 . intro hp99havesplit:= union2.membership.forward with hp100casessplitwithleft|right101 . have both := intersection.membership.forward withleft102exact both.left103 . have both := intersection.membership.forward withright104exact both.left105106theorem relation.split_disjoint (I R D a : Set)107shows Disjoint(I ∩ ((R ∖ {a}) × D), {a} × fiber(I, D, a))108proof109apply Disjoint.iff.backward110intros p hp hp2111have both := intersection.membership.forward with hp112obtain⟨bound, r, hr, d, hd, shape⟩:= product.membership.forward with both.right113rw shape at hp2114have coordinates := product.pair_membership.forward with hp2115have same := singleton.membership.forward with coordinates.left116have parts := difference.singleton_membership.forward with hr117exact parts.right with same118119-- The count.120theorem relation.count (I R D : Set, n : Nat)121requires finiteR : Finite(R)122requires finiteD : Finite(D)123requires size : cardinality(R) = n124requires inside : I ⊆ R × D125shows cardinality(I) = natSum(R, fiberSize(I, R, D))126proof127induction n generalizing R I128| zero =>129intros I R finiteR size inside130have has := cardinality.correct with finiteR131rw size at has132have empty := HasCardinality.empty_of_zero at R with has133have emptyI : I =∅134apply Subset.antisymmetric135 . intros p hp136have bound := inside with p, hp137rw [empty, product.left_empty] at bound138exact bound139 . exact empty.subset at I140rw [emptyI, empty, natSum.empty]141rw← nat.zero142exact cardinality.natural at0143| succ(k) using ih =>144intros I R finiteR size inside145have has := cardinality.correct with finiteR146rw size at has147obtain⟨a, ha⟩:= HasCardinality.exists_member with has148have removed := cardinality.remove with finiteR, ha149rw size at removed150have smaller := Naturals.injective.succ with removed151have restSize : cardinality(R ∖ {a}) = k152 symm153exact smaller154have restFinite := Finite.subset with finiteR, (difference.subset_left at R, {a})155have restInside : I ∩ ((R ∖ {a}) × D) ⊆ (R ∖ {a}) × D := intersection.subset_right at I, (R ∖ {a}) × D156have restCount := ih with I ∩ ((R ∖ {a}) × D), R ∖ {a}, restFinite, restSize, restInside157have graph := fiberSize.function at I, R, D with finiteD158have restGraph := fiberSize.function at I ∩ ((R ∖ {a}) × D), R ∖ {a}, D with finiteD159have agree : ∀ r ∈ R ∖ {a}, fiberSize(I ∩ ((R ∖ {a}) × D), R ∖ {a}, D)(r) = fiberSize(I, R, D)(r)160intros r hr161have hrR := difference.subset_left at R, {a} with r, hr162have valueRest := fiberSize.value at I ∩ ((R ∖ {a}) × D), R ∖ {a}, D, r with finiteD163have value := fiberSize.value at I, R, D, r with finiteD164have restricted := fiber.restrict at I, D, R ∖ {a}, r with hr165rw [valueRest, value, restricted]166rfl167have sameSum := natSum.congr with agree168have weights : ∀ r ∈ R ∖ {a}, fiberSize(I, R, D)(r) ∈ ℕ169intros r hr170have hrR := difference.subset_left at R, {a} with r, hr171exact FunctionBetween.apply_closed with graph, hrR172have weightA := FunctionBetween.apply_closed with graph, ha173have fresh : ¬a ∈ R ∖ {a}174intro inside2175have parts := difference.singleton_membership.forward with inside2176apply parts.right177rfl178have inserted := natSum.insert with restFinite, weights, weightA, fresh179have restore := insert.restore with ha180rw restore at inserted181havesplit:= relation.split with inside, ha182have disjoint := relation.split_disjoint at I, R, D, a183have finiteI := Finite.subset with (Finite.product with finiteR, finiteD), inside184have finiteRest := Finite.subset with finiteI, (intersection.subset_left at I, (R ∖ {a}) × D)185have finiteRow := Finite.product with (Finite.singleton at a), (fiber.finite at I, D, a with finiteD)186have added := cardinality.union_disjoint with finiteRest, finiteRow, disjoint187have rowCount := cardinality.singleton_product at a, fiber(I, D, a) with (fiber.finite at I, D, a with finiteD)188have valueA := fiberSize.value at I, R, D, a with finiteD189have leftCount : cardinality(I) = cardinality((I ∩ ((R ∖ {a}) × D)) ∪ ({a} × fiber(I, D, a)))190rw←split191rfl192rw [leftCount, added, rowCount, restCount, sameSum, inserted, valueA]193rfl194