1-- The handshake parity lemma for rooms and doors: with every room having2-- at most two doors and every door on one or two rooms, an odd number of3-- boundary doors forces an odd number of rooms with exactly one door4-- 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.Cardinal.Handshake10import DoubleCounting11import SumParity1213def cofiber (I R d : Set) : Set:= { r ∈ R | (r, d) ∈ I }14def completeRooms (I R D : Set) : Set:= { r ∈ R | cardinality(fiber(I, D, r)) =1 }15def boundaryDoors (I R D : Set) : Set:= { d ∈ D | cardinality(cofiber(I, R, d)) =1 }1617-- The converse relation: its fibres are the cofibres, and it has the18-- same size.19theorem converse.subset (I R D : Set)20shows converse(I, R, D) ⊆ D × R21proof22intros p hp23have parts := converse.membership.forward with hp24exact parts.left2526theorem cofiber.converse (I R D d : Set)27requires hd : d ∈ D28shows fiber(converse(I, R, D), R, d) = cofiber(I, R, d)29proof30ext r31constructor32 . intro hr33have parts := fiber.membership.forward with hr34have converseParts := converse.membership.forward with parts.right35obtain⟨x, hx, y, hy, shape, related⟩:= converseParts.right36have coordinates := orderedPair.injective with shape37rw [coordinates.left, coordinates.right]38exact cofiber.membership.backward with⟨hx, related⟩39 . intro hr40have parts := cofiber.membership.forward with hr41have related : relates(I, r, d) := parts.right42have member := converse.relates with parts.left, hd, related43have inConverse : (d, r) ∈ converse(I, R, D) := member44exact fiber.membership.backward with⟨parts.left, inConverse⟩4546theorem converse.image (I R D : Set)47requires inside : I ⊆ R × D48shows functionImage(productSwap(R, D), I, D × R) = converse(I, R, D)49proof50have function := Injective.function with (Bijective.injective with (productSwap.bijective at R, D))51ext q52constructor53 . intro hq54simp [functionImage.membership] at hq55obtain⟨bound, p, hp, shape⟩:= hq56obtain⟨boundP, x, hx, y, hy, pairShape⟩:= product.membership.forward with (inside with p, hp)57have swapped := productSwap.pair with hx, hy58rw [pairShape, swapped] at shape59rw pairShape at hp60have related : relates(I, x, y) := hp61have member := converse.relates with hx, hy, related62rw shape63exact member64 . intro hq65have parts := converse.membership.forward with hq66obtain⟨x, hx, y, hy, shape, related⟩:= parts.right67have inside2 : (x, y) ∈ R × D := product.pair with hx, hy68have swapped := productSwap.pair with hx, hy69rw [shape, ← swapped]70have closed := FunctionBetween.apply_closed with function, inside271simp [functionImage.membership]72constructor73 . exact closed74 . witness (x, y)75constructor76 . exact related77 . rfl7879theorem converse.cardinality (I R D : Set)80requires finiteR : Finite(R)81requires finiteD : Finite(D)82requires inside : I ⊆ R × D83shows cardinality(converse(I, R, D)) = cardinality(I)84proof85have bijective := productSwap.bijective at R, D86have injective := Bijective.injective with bijective87have function := Injective.function with injective88have finiteI := Finite.subset with (Finite.product with finiteR, finiteD), inside89have closed : ∀ p ∈ I, productSwap(R, D)(p) ∈ D × R90intros p hp91exact FunctionBetween.apply_closed with function, (inside with p, hp)92have restricted := restrict.function with function, inside, closed93have restrictedInjective : InjectiveBetween(restrict(productSwap(R, D), I), I, converse(I, R, D))94constructor95 . have image := converse.image with inside96rw← image97apply restrict.function with function, inside98intros p hp99have value := FunctionBetween.apply_closed with function, (inside with p, hp)100simp [functionImage.membership]101 . intros p hp q hq z hz hpz hqz102have valueP := FunctionBetween.apply_value with restricted, hp, hpz103have valueQ := FunctionBetween.apply_value with restricted, hq, hqz104have stepP := restrict.apply with function, inside, closed, hp105have stepQ := restrict.apply with function, inside, closed, hq106rw stepP at valueP107rw stepQ at valueQ108have same : productSwap(R, D)(p) = productSwap(R, D)(q) bycc109exact Injective.apply_cancel with injective, (inside with p, hp), (inside with q, hq), same110have restrictedSurjective : SurjectiveBetween(restrict(productSwap(R, D), I), I, converse(I, R, D))111constructor112 . exact restrictedInjective.left113 . intros q hq114have image := converse.image with inside115rw← image at hq116simp [functionImage.membership] at hq117obtain⟨bound, p, hp, shape⟩:= hq118refine⟨p, hp, ?_⟩119have related := FunctionBetween.relates_apply with restrictedInjective.left, hp120have step := restrict.apply with function, inside, closed, hp121 rwa [step, ← shape] at related122have bijectiveRestricted : BijectiveBetween(restrict(productSwap(R, D), I), I, converse(I, R, D)) :=⟨restrictedInjective, restrictedSurjective⟩123have same := cardinality.bijective with finiteI, bijectiveRestricted124 symm125exact same126127-- The handshake lemma.128theorem handshake (I R D : Set)129requires finiteR : Finite(R)130requires finiteD : Finite(D)131requires inside : I ⊆ R × D132requires rooms : ∀ r ∈ R, cardinality(fiber(I, D, r)) =0∨ cardinality(fiber(I, D, r)) =1∨ cardinality(fiber(I, D, r)) =2133requires doors : ∀ d ∈ D, cardinality(cofiber(I, R, d)) =1∨ cardinality(cofiber(I, R, d)) =2134requires boundary : Odd(cardinality(boundaryDoors(I, R, D)))135shows Odd(cardinality(completeRooms(I, R, D)))136proof137have sizeR : cardinality(R) = cardinality(R) byrfl138have sizeD : cardinality(D) = cardinality(D) byrfl139have correctR := cardinality.correct with finiteR140have correctD := cardinality.correct with finiteD141have natR := correctR.left142have natD := correctD.left143have rowCount := relation.count with finiteR, finiteD, sizeR, inside144have converseInside := converse.subset at I, R, D145have columnCount := relation.count with finiteD, finiteR, sizeD, converseInside146have sameCount := converse.cardinality with finiteR, finiteD, inside147have rowGraph := fiberSize.function at I, R, D with finiteD148have columnGraph := fiberSize.function at converse(I, R, D), D, R with finiteR149have rowWeights : ∀ r ∈ R, fiberSize(I, R, D)(r) =0∨ fiberSize(I, R, D)(r) =1∨ fiberSize(I, R, D)(r) =2150intros r hr151have value := fiberSize.value at I, R, D, r with finiteD152rw value153exact rooms with r, hr154have columnWeights : ∀ d ∈ D, fiberSize(converse(I, R, D), D, R)(d) =0∨ fiberSize(converse(I, R, D), D, R)(d) =1∨ fiberSize(converse(I, R, D), D, R)(d) =2155intros d hd156have value := fiberSize.value at converse(I, R, D), D, R, d with finiteR157have same := cofiber.converse at I, R, D, d with hd158rw [value, same]159havesplit:= doors with d, hd160casessplitwith one | two161 . right162left163exact one164 . right165right166exact two167have rowParity := natSum.parity_of_size with finiteR, sizeR, rowWeights168have columnParity := natSum.parity_of_size with finiteD, sizeD, columnWeights169have rowOnes : ones(R, fiberSize(I, R, D)) = completeRooms(I, R, D)170ext r171constructor172 . intro hr173have parts := ones.membership.forward with hr174have inR := parts.left175have value := fiberSize.value at I, R, D, r with finiteD, inR176have weight := parts.right177have one : cardinality(fiber(I, D, r)) =1bycc178exact completeRooms.membership.backward with⟨parts.left, one⟩179 . intro hr180have parts := completeRooms.membership.forward with hr181have inR := parts.left182have value := fiberSize.value at I, R, D, r with finiteD, inR183have count := parts.right184have one : fiberSize(I, R, D)(r) =1bycc185exact ones.membership.backward with⟨parts.left, one⟩186have columnOnes : ones(D, fiberSize(converse(I, R, D), D, R)) = boundaryDoors(I, R, D)187ext d188constructor189 . intro hd190have parts := ones.membership.forward with hd191have inD := parts.left192have value := fiberSize.value at converse(I, R, D), D, R, d with finiteR, inD193have same := cofiber.converse at I, R, D, d with parts.left194have weight := parts.right195have one : cardinality(cofiber(I, R, d)) =1bycc196exact boundaryDoors.membership.backward with⟨parts.left, one⟩197 . intro hd198have parts := boundaryDoors.membership.forward with hd199have inD := parts.left200have value := fiberSize.value at converse(I, R, D), D, R, d with finiteR, inD201have same := cofiber.converse at I, R, D, d with parts.left202have count := parts.right203have one : fiberSize(converse(I, R, D), D, R)(d) =1bycc204exact ones.membership.backward with⟨parts.left, one⟩205rw rowOnes at rowParity206rw columnOnes at columnParity207rw← rowCount at rowParity208rw [← columnCount, sameCount] at columnParity209have completeInside : completeRooms(I, R, D) ⊆ R210intros r hr211have parts := completeRooms.membership.forward with hr212exact parts.left213have finiteComplete := Finite.subset with finiteR, completeInside214have correctComplete := cardinality.correct with finiteComplete215have natComplete := correctComplete.left216have notEvenBoundary := Odd.not_even with boundary217have notEvenI : ¬Even(cardinality(I))218intro evenI219exact notEvenBoundary with (columnParity.forward with evenI)220have notEvenComplete : ¬Even(cardinality(completeRooms(I, R, D)))221intro evenComplete222exact notEvenI with (rowParity.backward with evenComplete)223have characterization := Odd.iff_not_even at cardinality(completeRooms(I, R, D))224exact characterization.backward with notEvenComplete225