1-- The cubic expansion of a multigraph along a rotation system: the vertices2-- are the edge ends, the spoke of an edge joins its two ends, and the ring3-- edges at a vertex run around the cycle of its ends. The expansion is a4-- graph, and it is cubic and loopless when every degree is at least two.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.GraphTheory.DoubleCover.Expansion11import RotationSystems1213-- Section: The expanded graph.1415-- The expanded graph has the ends as vertices. The spoke (0, e) of an edge16-- e joins its ends (e, 0) and (e, 1); the ring edge (1, h) of an end h joins17-- h to σ(h), so the ring edges at a vertex run around the cycle of its ends.18def spokes (E : Set) : Set:= {0} × E19def rings (E : Set) : Set:= {1} × halfEdges(E)20def expandedEdges (E : Set) : Set:= spokes(E) ∪ rings(E)21def expandedLeft (E σ : Set) : Set:=22 { p ∈ expandedEdges(E) × halfEdges(E) |23∃ e ∈ E, p = ((0, e), (e, 0)) ∨24 (∃ h ∈ halfEdges(E), p = ((1, h), h)) }25def expandedRight (E σ : Set) : Set:=26 { p ∈ expandedEdges(E) × halfEdges(E) |27∃ e ∈ E, p = ((0, e), (e, 1)) ∨28 (∃ h ∈ halfEdges(E), p = ((1, h), σ(h))) }2930theorem spokes.member (E e : Set)31requires edge : e ∈ E32shows (0, e) ∈ spokes(E)33proof34unfold spokes35simp [product.pair_membership, edge]3637theorem rings.member (E h : Set)38requiresend : h ∈ halfEdges(E)39shows (1, h) ∈ rings(E)40proof41unfold rings42simp [product.pair_membership, end]4344theorem spokes.edge (E e : Set)45requires edge : e ∈ E46shows (0, e) ∈ expandedEdges(E)47proof48unfold expandedEdges49simp [(spokes.member with edge)]5051theorem rings.edge (E h : Set)52requiresend : h ∈ halfEdges(E)53shows (1, h) ∈ expandedEdges(E)54proof55unfold expandedEdges56simp [(rings.member withend)]5758-- An edge of the expansion is a spoke or a ring edge.59theorem expandedEdges.cases (E x : Set)60requires member : x ∈ expandedEdges(E)61shows (∃ e ∈ E, x = (0, e)) ∨ (∃ h ∈ halfEdges(E), x = (1, h))62proof63unfold expandedEdges at member64havesplit:= union2.membership.forward with member65casessplitwith spoke |ring66 . unfold spokes at spoke67obtain⟨_, z, hz, e, he, rfl⟩:= product.membership.forward with spoke68simpat hz69left70refine⟨e, he, _⟩71rw hz72 . unfold rings atring73obtain⟨_, z, hz, h, hh, rfl⟩:= product.membership.forward withring74simpat hz75right76refine⟨h, hh, _⟩77rw hz7879-- Spokes and ring edges are told apart by their first coordinate.80theorem expandedEdges.apart (E : Set)81shows Disjoint(spokes(E), rings(E))82proof83have heads : Disjoint({0}, {1})84apply Disjoint.iff.backward85intros x hx86simpat hx87rw hx88simp89unfold spokes90unfold rings91exact product.disjoint_of_left with heads9293-- The expansion of a finite graph is finite.94theorem spokes.finite (E : Set)95requires finiteEdges : Finite(E)96shows Finite(spokes(E))97proof98unfold spokes99exact Finite.product with Finite.singleton, finiteEdges100101theorem rings.finite (E : Set)102requires finiteEdges : Finite(E)103shows Finite(rings(E))104proof105unfold rings106have finiteEnds := halfEdges.finite with finiteEdges107exact Finite.product with Finite.singleton, finiteEnds108109theorem expandedEdges.finite (E : Set)110requires finiteEdges : Finite(E)111shows Finite(expandedEdges(E))112proof113unfold expandedEdges114exact Finite.union with (spokes.finite with finiteEdges),115 (rings.finite with finiteEdges)116117-- The end maps, read off their graphs: a spoke (0, e) runs from (e, 0) to118-- (e, 1), a ring edge (1, h) from h to σ(h).119theorem expandedLeft.of_relates (E σ x y : Set)120requires related : relates(expandedLeft(E, σ), x, y)121shows (∃ e ∈ E, x = (0, e) ∧ y = (e, 0)) ∨122 (∃ h ∈ halfEdges(E), x = (1, h) ∧ y = h)123proof124obtain⟨_, e, he, shape⟩:= related125rcases shape with spoke |⟨h, hh, ring⟩126 . obtain⟨rfl, rfl⟩:= orderedPair.injective with spoke127left128exact⟨e, he, rfl, rfl⟩129 . obtain⟨rfl, rfl⟩:= orderedPair.injective withring130right131exact⟨h, hh, rfl, rfl⟩132133theorem expandedRight.of_relates (E σ x y : Set)134requires related : relates(expandedRight(E, σ), x, y)135shows (∃ e ∈ E, x = (0, e) ∧ y = (e, 1)) ∨136 (∃ h ∈ halfEdges(E), x = (1, h) ∧ y = σ(h))137proof138obtain⟨_, e, he, shape⟩:= related139rcases shape with spoke |⟨h, hh, ring⟩140 . obtain⟨rfl, rfl⟩:= orderedPair.injective with spoke141left142exact⟨e, he, rfl, rfl⟩143 . obtain⟨rfl, rfl⟩:= orderedPair.injective withring144right145exact⟨h, hh, rfl, rfl⟩146147theorem expandedLeft.spoke_relates (E σ e : Set)148requires edge : e ∈ E149shows relates(expandedLeft(E, σ), (0, e), (e, 0))150proof151apply expandedLeft.membership.backward152refine⟨(product.pair with (spokes.edge with edge),153 (halfEdges.left with edge)), e, edge, _⟩154left155rfl156157theorem expandedRight.spoke_relates (E σ e : Set)158requires edge : e ∈ E159shows relates(expandedRight(E, σ), (0, e), (e, 1))160proof161apply expandedRight.membership.backward162refine⟨(product.pair with (spokes.edge with edge),163 (halfEdges.right with edge)), e, edge, _⟩164left165rfl166167theorem expandedLeft.ring_relates (E σ h : Set)168requiresend : h ∈ halfEdges(E)169shows relates(expandedLeft(E, σ), (1, h), h)170proof171obtain⟨e, he, _, _, _⟩:= halfEdges.shape withend172apply expandedLeft.membership.backward173refine⟨(product.pair with (rings.edge withend), end), e, he, _⟩174right175exact⟨h, end, rfl⟩176177theorem expandedRight.ring_relates (E σ h : Set)178requires function : σ : halfEdges(E) → halfEdges(E)179requiresend : h ∈ halfEdges(E)180shows relates(expandedRight(E, σ), (1, h), σ(h))181proof182obtain⟨e, he, _, _, _⟩:= halfEdges.shape withend183have image := FunctionBetween.apply_closed with function, end184apply expandedRight.membership.backward185refine⟨(product.pair with (rings.edge withend), image), e, he, _⟩186right187exact⟨h, end, rfl⟩188189theorem expandedLeft.function (E σ : Set)190shows expandedLeft(E, σ) : expandedEdges(E) → halfEdges(E)191proof192constructor193 . intros p member194exact member.left195constructor196 . intros x y relatedY z relatedZ197have shapeY := expandedLeft.of_relates with relatedY198have shapeZ := expandedLeft.of_relates with relatedZ199rcases shapeY with⟨e, he, rfl, rfl⟩|⟨h, hh, rfl, rfl⟩200 . rcases shapeZ with⟨f, hf, same, rfl⟩|⟨k, hk, same, rfl⟩201 . obtain⟨_, rfl⟩:= orderedPair.injective with same202rfl203 . obtain⟨zeroOne, _⟩:= orderedPair.injective with same204contradiction205 . rcases shapeZ with⟨f, hf, same, rfl⟩|⟨k, hk, same, rfl⟩206 . obtain⟨oneZero, _⟩:= orderedPair.injective with same207contradiction208 . obtain⟨_, rfl⟩:= orderedPair.injective with same209rfl210 . intros x member211havesplit:= expandedEdges.cases with member212rcasessplitwith⟨e, he, rfl⟩|⟨h, hh, rfl⟩213 . exact⟨(e, 0), (halfEdges.left with he),214 (expandedLeft.spoke_relates with he)⟩215 . exact⟨h, hh, (expandedLeft.ring_relates with hh)⟩216217theorem expandedRight.function (E σ : Set)218requires function : σ : halfEdges(E) → halfEdges(E)219shows expandedRight(E, σ) : expandedEdges(E) → halfEdges(E)220proof221constructor222 . intros p member223exact member.left224constructor225 . intros x y relatedY z relatedZ226have shapeY := expandedRight.of_relates with relatedY227have shapeZ := expandedRight.of_relates with relatedZ228rcases shapeY with⟨e, he, rfl, rfl⟩|⟨h, hh, rfl, rfl⟩229 . rcases shapeZ with⟨f, hf, same, rfl⟩|⟨k, hk, same, rfl⟩230 . obtain⟨_, rfl⟩:= orderedPair.injective with same231rfl232 . obtain⟨zeroOne, _⟩:= orderedPair.injective with same233contradiction234 . rcases shapeZ with⟨f, hf, same, rfl⟩|⟨k, hk, same, rfl⟩235 . obtain⟨oneZero, _⟩:= orderedPair.injective with same236contradiction237 . obtain⟨_, rfl⟩:= orderedPair.injective with same238rfl239 . intros x member240havesplit:= expandedEdges.cases with member241rcasessplitwith⟨e, he, rfl⟩|⟨h, hh, rfl⟩242 . exact⟨(e, 1), (halfEdges.right with he),243 (expandedRight.spoke_relates with he)⟩244 . exact⟨σ(h), (FunctionBetween.apply_closed with function, hh),245 (expandedRight.ring_relates with function, hh)⟩246247theorem expansion.graph (V E leftEnd rightEnd σ : Set)248requires graph : Graph(V, E, leftEnd, rightEnd)249requires rotation : RotationSystem(V, E, leftEnd, rightEnd, σ)250shows Graph(halfEdges(E), expandedEdges(E),251 expandedLeft(E, σ), expandedRight(E, σ))252proof253have function := RotationSystem.function with rotation254exact⟨expandedLeft.function, (expandedRight.function with function)⟩255256-- The values of the end maps.257theorem expandedLeft.spoke (E σ e : Set)258requires edge : e ∈ E259shows expandedLeft(E, σ)((0, e)) = (e, 0)260proof261exact FunctionBetween.apply_value with expandedLeft.function,262 (spokes.edge with edge), (expandedLeft.spoke_relates with edge)263264theorem expandedRight.spoke (E σ e : Set)265requires function : σ : halfEdges(E) → halfEdges(E)266requires edge : e ∈ E267shows expandedRight(E, σ)((0, e)) = (e, 1)268proof269exact FunctionBetween.apply_value with (expandedRight.function with function),270 (spokes.edge with edge), (expandedRight.spoke_relates with edge)271272theorem expandedLeft.ring (E σ h : Set)273requiresend : h ∈ halfEdges(E)274shows expandedLeft(E, σ)((1, h)) = h275proof276exact FunctionBetween.apply_value with expandedLeft.function,277 (rings.edge withend), (expandedLeft.ring_relates withend)278279theorem expandedRight.ring (E σ h : Set)280requires function : σ : halfEdges(E) → halfEdges(E)281requiresend : h ∈ halfEdges(E)282shows expandedRight(E, σ)((1, h)) = σ(h)283proof284exact FunctionBetween.apply_value with (expandedRight.function with function),285 (rings.edge withend), (expandedRight.ring_relates with function, end)286287-- Section: The ends of the expansion.288289-- The spoke end at h = (e, i) is side i of the spoke of e.290theorem spokes.ends (E σ e i : Set)291requires function : σ : halfEdges(E) → halfEdges(E)292requires edge : e ∈ E293requires side : i ∈2294shows incidentEnds(expandedLeft(E, σ), expandedRight(E, σ), spokes(E),295 (e, i)) = {((0, e), i)}296proof297ext p298constructor299 . intro hp300obtain⟨x, hx, j, hj, rfl⟩:= incidentEnds.shape with hp301have ending := (incidentEnds.pair.forward with hp).right302unfold spokes at hx303obtain⟨_, z, hz, f, hf, rfl⟩:= product.membership.forward with hx304simpat hz305rw hz at ending306rw hz307rw [(expandedLeft.spoke with hf), (expandedRight.spoke with function, hf)]308at ending309rcases ending with⟨rfl, same⟩|⟨rfl, same⟩310 . obtain⟨rfl, rfl⟩:= orderedPair.injective with same311simp312 . obtain⟨rfl, rfl⟩:= orderedPair.injective with same313simp314 . intro hp315simpat hp316rw hp317have spoke := spokes.member with edge318have sides := membership.two.forward with side319rcases sides withrfl|rfl320 . simp [incidentEnds.pair, spoke,321 (expandedLeft.spoke at E, σ, e with edge)]322 . simp [incidentEnds.pair, spoke, (expandedRight.spoke with function, edge)]323324-- The ring ends at h = (e, i): the left end of its own ring edge and the325-- right end of the ring edge of the end σ carries to h.326theorem rings.ends (E σ e i : Set)327requires bijective : BijectiveBetween(σ, halfEdges(E), halfEdges(E))328requires edge : e ∈ E329requires side : i ∈2330shows incidentEnds(expandedLeft(E, σ), expandedRight(E, σ), rings(E),331 (e, i)) =332 {((1, (e, i)), 0),333 ((1, converse(σ, halfEdges(E), halfEdges(E))((e, i))), 1)}334proof335have function := Injective.function with (Bijective.injective with bijective)336haveend : (e, i) ∈ halfEdges(E) :=337 halfEdges.pair.backward with⟨edge, side⟩338have inverse := converse.function_of_bijective with bijective339have preimage := FunctionBetween.apply_closed with inverse, end340have back := Bijective.apply_inverse with bijective, end341ext p342constructor343 . intro hp344obtain⟨x, hx, j, hj, rfl⟩:= incidentEnds.shape with hp345have ending := (incidentEnds.pair.forward with hp).right346unfold rings at hx347obtain⟨_, z, hz, k, hk, rfl⟩:= product.membership.forward with hx348simpat hz349rw hz at ending350rw hz351rw [(expandedLeft.ring with hk), (expandedRight.ring with function, hk)]352at ending353rcases ending with⟨rfl, rfl⟩|⟨rfl, same⟩354 . simp355 . have forward := Bijective.inverse_apply with bijective, hk356rw same at forward357rw← forward358simp359 . intro hp360simpat hp361rcases hp withrfl|rfl362 . simp [incidentEnds.pair, (rings.member withend),363 (expandedLeft.ring at E, σ, (e, i) withend)]364 . simp [incidentEnds.pair, (rings.member with preimage),365 (expandedRight.ring with function, preimage), back]366367-- Each end meets one spoke and two ring edges.368theorem spokes.degree (E σ e i : Set)369requires function : σ : halfEdges(E) → halfEdges(E)370requires edge : e ∈ E371requires side : i ∈2372shows degreeIn(expandedLeft(E, σ), expandedRight(E, σ), spokes(E),373 (e, i)) =1374proof375unfold degreeIn376rw spokes.ends with function, edge, side377exact cardinality.singleton378379theorem rings.degree (E σ e i : Set)380requires bijective : BijectiveBetween(σ, halfEdges(E), halfEdges(E))381requires edge : e ∈ E382requires side : i ∈2383shows degreeIn(expandedLeft(E, σ), expandedRight(E, σ), rings(E),384 (e, i)) =2385proof386have fresh : ((1, converse(σ, halfEdges(E), halfEdges(E))((e, i))), 1) ∉387 {((1, (e, i)), 0)}388intro same389simpat same390unfold degreeIn391rw [(rings.ends with bijective, edge, side), pair.eq_union2,392 (cardinality.union_disjoint with Finite.singleton, Finite.singleton,393 (Disjoint.singleton_of_fresh with fresh)),394 cardinality.singleton, cardinality.singleton]395compute396397-- The ring edges form an even edge set of the expansion.398theorem rings.even (E σ : Set)399requires finiteEdges : Finite(E)400requires bijective : BijectiveBetween(σ, halfEdges(E), halfEdges(E))401shows EvenEdgeSet(halfEdges(E), expandedLeft(E, σ), expandedRight(E, σ),402 rings(E))403proof404constructor405 . exact rings.finite with finiteEdges406 . intros h hh407obtain⟨e, he, i, hi, rfl⟩:= halfEdges.shape with hh408rw rings.degree with bijective, he, hi409exact Even.two410411-- With every degree at least two the expansion is cubic and loopless: each412-- end meets its spoke and two ring edges, a spoke joins the two distinct413-- ends of its edge, and a ring edge joins an end to a different end.414theorem expansion.cubic (V E leftEnd rightEnd σ : Set)415requires graph : Graph(V, E, leftEnd, rightEnd)416requires finiteEdges : Finite(E)417requires rotation : RotationSystem(V, E, leftEnd, rightEnd, σ)418requires degrees : ∀ v ∈ V, 2≤ degreeIn(leftEnd, rightEnd, E, v)419shows Cubic(halfEdges(E), expandedEdges(E),420 expandedLeft(E, σ), expandedRight(E, σ))421proof422have function := RotationSystem.function with rotation423constructor424 . intros h hh425obtain⟨e, he, i, hi, rfl⟩:= halfEdges.shape with hh426unfold expandedEdges427rw [(degreeIn.union_disjoint with (spokes.finite with finiteEdges),428 (rings.finite with finiteEdges), expandedEdges.apart),429 (spokes.degree with function, he, hi),430 (rings.degree with rotation.left, he, hi)]431compute432 . intros x hx433havesplit:= expandedEdges.cases with hx434rcasessplitwith⟨e, he, rfl⟩|⟨h, hh, rfl⟩435 . rw [(expandedLeft.spoke with he), (expandedRight.spoke with function, he)]436intro same437have sides := (orderedPair.injective with same).right438contradiction439 . rw [(expandedLeft.ring with hh), (expandedRight.ring with function, hh)]440have moves := RotationSystem.moves441with graph, finiteEdges, rotation, degrees, hh442cc443