1-- Forests and spanning trees as edge sets: a forest contains no nonempty2-- even edge set, a spanning tree connects V with one edge fewer than3-- vertices, and adding an edge to a tree closes exactly one even set.4-- 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.GraphTheory.Multigraph.Trees10import Components1112predicate Forest (V leftEnd rightEnd F : Set) :=13 Finite(F) ∧ (∀ C ⊆ F, EvenEdgeSet(V, leftEnd, rightEnd, C) → C =∅)1415predicate SpanningTree (V leftEnd rightEnd T : Set) :=16 Finite(T) ∧ Connects(V, leftEnd, rightEnd, T)17∧ cardinality(T) +1= cardinality(V)1819theorem Forest.subset (V leftEnd rightEnd F G : Set)20requires forest : Forest(V, leftEnd, rightEnd, F)21requires subset : G ⊆ F22shows Forest(V, leftEnd, rightEnd, G)23proof24obtain⟨finite, acyclic⟩:= forest25constructor26 . exact Finite.subset with finite, subset27 . intros C bound even28exact acyclic with C, (Subset.transitive with bound, subset), even2930-- In a forest the ends of an edge are joined only through that edge: a path31-- around it would close a nonempty even set, and a loop is one by itself.32theorem Forest.ends_apart (V leftEnd rightEnd F e : Set)33requires ends : ∀ f ∈ F, leftEnd(f) ∈ V ∧ rightEnd(f) ∈ V34requires forest : Forest(V, leftEnd, rightEnd, F)35requires edge : e ∈ F36shows¬Joined(leftEnd, rightEnd, F ∖ {e}, leftEnd(e), rightEnd(e))37proof38intro joined39obtain⟨finite, acyclic⟩:= forest40have restSubset := difference.subset_left at F, {e}41have restFinite := Finite.subset with finite, restSubset42have restEnds := ends_in.subset with ends, restSubset43have edgeSubset : {e} ⊆ F := singleton.subset_iff.backward with edge44 by_cases loop : leftEnd(e) = rightEnd(e)45 . have even := EvenEdgeSet.loop at V, leftEnd, rightEnd, e with loop46have empty := acyclic with {e}, edgeSubset, even47exact singleton.ne_empty with empty48 . obtain⟨P, subsetP, path⟩:=49 Joined.path with restFinite, restEnds, loop, joined50have finiteP := Finite.subset with restFinite, subsetP51have outside : ¬e ∈ P52intro inP53have inRest := subsetP with e, inP54simpat inRest55have even := EvenEdgeSet.of_path_edge with finiteP, outside, loop, path56have inside : P ∪ {e} ⊆ F bysimp57have empty := acyclic with P ∪ {e}, inside, even58rw union2.eq_empty_iff at empty59exact singleton.ne_empty with empty.right6061-- Adding an edge between vertices a forest does not join keeps it a forest:62-- an even set through the new edge would join its ends by the old edges.63theorem Forest.add_edge (V leftEnd rightEnd F e : Set)64requires finiteVertices : Finite(V)65requires ends : ∀ f ∈ F, leftEnd(f) ∈ V ∧ rightEnd(f) ∈ V66requires edgeEnds : leftEnd(e) ∈ V ∧ rightEnd(e) ∈ V67requires forest : Forest(V, leftEnd, rightEnd, F)68requires apart : ¬Joined(leftEnd, rightEnd, F, leftEnd(e), rightEnd(e))69shows Forest(V, leftEnd, rightEnd, F ∪ {e})70proof71obtain⟨finite, acyclic⟩:= forest72have moreEnds := ends_in.union_edge with ends, edgeEnds73constructor74 . exact Finite.union with finite, Finite.singleton75 . intros C bound even76have endsC := ends_in.subset with moreEnds, bound77have inF : C ∖ {e} ⊆ F bysimp78 by_cases inC : e ∈ C79 . exfalso80have joined := EvenEdgeSet.ends_joined with81 finiteVertices, endsC, even, inC82exact apart with (Joined.monotone with inF, joined)83 . rw difference.singleton_of_absent with inC at inF84exact acyclic with C, inF, even8586-- A forest on a finite vertex set has at most |V| − c edges, c the number of87-- its components: adding its edges one at a time, each merges two components,88-- since a forest joins the ends of an edge only through that edge.89theorem Forest.edge_bound (V leftEnd rightEnd F : Set)90requires finiteVertices : Finite(V)91requires ends : ∀ e ∈ F, leftEnd(e) ∈ V ∧ rightEnd(e) ∈ V92requires forest : Forest(V, leftEnd, rightEnd, F)93shows cardinality(F) + cardinality(componentsBy(V, leftEnd, rightEnd, F)) ≤94 cardinality(V)95proof96obtain⟨finite, _⟩:= forest97induction finite with G e98| empty =>99rw [(componentsBy.empty_count with finiteVertices), cardinality.empty]100arith101| insert using ih =>102have GEnds := ends_in.subset with ends, GSubset103have edgeEnds := ends with e, eMember104have apart : ¬Joined(leftEnd, rightEnd, G, leftEnd(e), rightEnd(e))105have rest : G ⊆ F ∖ {e} bysimp106have apartRest := Forest.ends_apart with ends, forest, eMember107intro joined108exact apartRest with (Joined.monotone with rest, joined)109have count := componentsBy.add_edge_apart with110 finiteVertices, GEnds, edgeEnds, apart111have grown := cardinality.union_singleton with GFinite, eFresh112arith113114-- An edge set on V with at least |V| − 1 edges that does not connect V115-- holds a nonempty even edge set: the key counting step of tree packing.116theorem EvenEdgeSet.of_dense_disconnected (V leftEnd rightEnd F : Set)117requires finiteVertices : Finite(V)118requires finite : Finite(F)119requires ends : ∀ e ∈ F, leftEnd(e) ∈ V ∧ rightEnd(e) ∈ V120requires dense : cardinality(V) ≤ cardinality(F) +1121requires disconnected : ¬Connects(V, leftEnd, rightEnd, F)122shows∃ C ⊆ F, C ≠∅∧ EvenEdgeSet(V, leftEnd, rightEnd, C)123proof124by_contra none125-- Without a nonempty even subset, F is a forest with at least two components.126have forest : Forest(V, leftEnd, rightEnd, F)127constructor128 . exact finite129 . intros C bound even130by_contra nonempty131exact none with⟨C, bound, nonempty, even⟩132have upper := Forest.edge_bound with finiteVertices, ends, forest133have two := componentsBy.two_of_disconnected with finiteVertices, disconnected134arith135136-- A spanning tree is a forest: an edge of an even subset could be dropped137-- without disconnecting V, leaving too few edges for a connected edge set.138theorem SpanningTree.forest (V leftEnd rightEnd T : Set)139requires finiteVertices : Finite(V)140requires ends : ∀ e ∈ T, leftEnd(e) ∈ V ∧ rightEnd(e) ∈ V141requires tree : SpanningTree(V, leftEnd, rightEnd, T)142shows Forest(V, leftEnd, rightEnd, T)143proof144obtain⟨finite, connected, count⟩:= tree145constructor146 . exact finite147 . intros C bound even148by_contra nonempty149obtain⟨e, he⟩:= Nonempty.of_ne_empty with nonempty150have inT := bound with e, he151have restSubset := difference.subset_left at T, {e}152have restFinite := Finite.subset with finite, restSubset153have restEnds := ends_in.subset with ends, restSubset154have edgeEnds := ends with e, inT155have endsC := ends_in.subset with ends, bound156have joinedC := EvenEdgeSet.ends_joined with finiteVertices, endsC, even, he157have restC : C ∖ {e} ⊆ T ∖ {e} :=158 difference.subset_of_subset with bound159have bridged := Joined.monotone with restC, joinedC160have same := componentsBy.add_edge_joined with restEnds, edgeEnds, bridged161have restored : (T ∖ {e}) ∪ {e} = T byext y; simp162rw restored at same163have lower := componentsBy.lower_bound with164 finiteVertices, restFinite, restEnds165rw← same at lower166have one := componentsBy.le_one_of_connected with connected167have removed := cardinality.remove with finite, inT168rw removed at count169arith170171-- A connected forest on a nonempty vertex set has exactly |V| − 1 edges.172theorem SpanningTree.of_connected_forest (V leftEnd rightEnd T : Set)173requires finiteVertices : Finite(V)174requires nonempty : V ≠∅175requires ends : ∀ e ∈ T, leftEnd(e) ∈ V ∧ rightEnd(e) ∈ V176requires forest : Forest(V, leftEnd, rightEnd, T)177requires connected : Connects(V, leftEnd, rightEnd, T)178shows SpanningTree(V, leftEnd, rightEnd, T)179proof180obtain⟨finite, _⟩:= forest181have upper := Forest.edge_bound with finiteVertices, ends, forest182have lower := componentsBy.lower_bound with finiteVertices, finite, ends183have one := componentsBy.connected_count with connected, nonempty184refine⟨finite, connected, _⟩185arith186187-- The forests inside an edge set, a finite family when the edge set is finite.188def forestsIn (V leftEnd rightEnd E : Set) : Set:=189 { F ∈𝒫(E) | Forest(V, leftEnd, rightEnd, F) }190191-- Every connected finite multigraph has a spanning tree: a maximal forest192-- inside E is connected, else an edge of E leaving one of its components193-- could be added to it.194theorem SpanningTree.exists (V E leftEnd rightEnd : Set)195requires graph : Graph(V, E, leftEnd, rightEnd)196requires finiteVertices : Finite(V)197requires finiteEdges : Finite(E)198requires nonempty : V ≠∅199requires connected : Connects(V, leftEnd, rightEnd, E)200shows∃ T ⊆ E, SpanningTree(V, leftEnd, rightEnd, T)201proof202have endsE := Graph.ends with graph203have finiteFamily : Finite(forestsIn(V, leftEnd, rightEnd, E))204have subset : forestsIn(V, leftEnd, rightEnd, E) ⊆𝒫(E)205intros F hF206obtain⟨inPowerset, _⟩:= hF207exact inPowerset208exact Finite.subset with (Finite.powerset with finiteEdges), subset209have emptyForest : ∅∈ forestsIn(V, leftEnd, rightEnd, E)210have emptySubset : ∅∈𝒫(E) :=211 powerset.membership_iff.backward with empty.subset212have forest : Forest(V, leftEnd, rightEnd, ∅)213constructor214 . exact Finite.empty215 . intros C bound even216exact Subset.antisymmetric with bound, empty.subset217exact⟨emptySubset, forest⟩218obtain⟨T, inFamily, _, maximal⟩:=219 Finite.maximal_extension with finiteFamily, emptyForest220obtain⟨inPowerset, forest⟩:= inFamily221have subsetE := powerset.membership_iff.forward with inPowerset222have endsT := ends_in.subset with endsE, subsetE223have connectedT : Connects(V, leftEnd, rightEnd, T)224by_contra disconnected225obtain⟨u, hu, v, hv, apart⟩:= Connects.witness_apart with disconnected226have closedT := componentBy.closed at V, leftEnd, rightEnd, T, u227with endsT228have selfU := componentBy.self at V, leftEnd, rightEnd, T, u with hu229-- Some edge of E leaves the T-component of u, since v lies outside it.230have crossingEdge : ∃ e ∈ E,231¬(leftEnd(e) ∈ componentBy(V, leftEnd, rightEnd, T, u) ↔232 rightEnd(e) ∈ componentBy(V, leftEnd, rightEnd, T, u))233by_contra none234have closedE : ClosedUnder(leftEnd, rightEnd, E,235 componentBy(V, leftEnd, rightEnd, T, u))236intros e he237by_contra unbalanced238exact none with⟨e, he, unbalanced⟩239have inComponent := connected with u, hu, v, hv,240 componentBy(V, leftEnd, rightEnd, T, u), selfU, closedE241exact apart with inComponent.right242obtain⟨e, he, unbalanced⟩:= crossingEdge243have edgeEnds := endsE with e, he244have outside : ¬e ∈ T245intro inT246exact unbalanced with (closedT with e, inT)247have apartEnds : ¬Joined(leftEnd, rightEnd, T, leftEnd(e), rightEnd(e))248intro joined249exact unbalanced with (Joined.balanced with250 endsT, edgeEnds.left, edgeEnds.right, joined, closedT)251-- Then T ∪ {e} is a larger forest inside E, against maximality.252have bigger := Forest.add_edge with253 finiteVertices, endsT, edgeEnds, forest, apartEnds254have biggerSubset : T ∪ {e} ⊆ E := Subset.union2_iff.backward with255⟨subsetE, (singleton.subset_iff.backward with he)⟩256have biggerIn : T ∪ {e} ∈ forestsIn(V, leftEnd, rightEnd, E) :=257⟨(powerset.membership_iff.backward with biggerSubset), bigger⟩258have equal := maximal with T ∪ {e}, biggerIn, union2.subset_left259have member : e ∈ T ∪ {e} bysimp260rw equal at member261exact outside with member262refine⟨T, subsetE, _⟩263exact SpanningTree.of_connected_forest with264 finiteVertices, nonempty, endsT, forest, connectedT265266-- The fundamental even set of an edge outside a spanning tree: an even set267-- inside T ∪ {e} that contains e.268theorem SpanningTree.fundamental (V leftEnd rightEnd T e : Set)269requires finiteVertices : Finite(V)270requires ends : (∀ f ∈ T, leftEnd(f) ∈ V ∧ rightEnd(f) ∈ V)271∧ leftEnd(e) ∈ V ∧ rightEnd(e) ∈ V272requires tree : SpanningTree(V, leftEnd, rightEnd, T)273requires outside : ¬e ∈ T274shows∃ C ⊆ T ∪ {e}, e ∈ C ∧ EvenEdgeSet(V, leftEnd, rightEnd, C)275proof276obtain⟨finite, connected, _⟩:= tree277obtain⟨endsT, leftIn, rightIn⟩:= ends278have member : e ∈ {e} bysimp279 by_cases loop : leftEnd(e) = rightEnd(e)280 . refine⟨{e}, union2.subset_right, member, _⟩281exact EvenEdgeSet.loop at V, leftEnd, rightEnd, e with loop282 . have joined := connected with leftEnd(e), leftIn, rightEnd(e), rightIn283obtain⟨P, subsetP, path⟩:=284 Joined.path with finite, endsT, loop, joined285have finiteP := Finite.subset with finite, subsetP286have outsideP : ¬e ∈ P287intro inP288exact outside with (subsetP with e, inP)289have even := EvenEdgeSet.of_path_edge with finiteP, outsideP, loop, path290have inside : P ∪ {e} ⊆ T ∪ {e} := union2.monotone_left with subsetP291have inC : e ∈ P ∪ {e} bysimp292exact⟨P ∪ {e}, inside, inC, even⟩293294-- Exchanging an edge of a tree for another edge of the even set it closes295-- gives a tree again: the one-edge exchange of the tree-packing proof.296theorem SpanningTree.exchange (V leftEnd rightEnd T C e f : Set)297requires finiteVertices : Finite(V)298requires ends : (∀ g ∈ T, leftEnd(g) ∈ V ∧ rightEnd(g) ∈ V)299∧ leftEnd(e) ∈ V ∧ rightEnd(e) ∈ V300requires tree : SpanningTree(V, leftEnd, rightEnd, T)301requires outside : ¬e ∈ T302requires even : EvenEdgeSet(V, leftEnd, rightEnd, C)303requires closes : C ⊆ T ∪ {e} ∧ e ∈ C304requires other : f ∈ C ∧ f ≠ e305shows SpanningTree(V, leftEnd, rightEnd, (T ∖ {f}) ∪ {e})306proof307obtain⟨finite, connected, count⟩:= tree308obtain⟨endsT, edgeEnds⟩:= ends309obtain⟨inside, inC⟩:= closes310obtain⟨fInC, different⟩:= other311have fInT : f ∈ T bysimp312have fEnds := endsT with f, fInT313have restSubset := difference.subset_left at T, {f}314have restFinite := Finite.subset with finite, restSubset315have restEnds := ends_in.subset with endsT, restSubset316have newEnds := ends_in.union_edge with restEnds, edgeEnds317have keep := union2.subset_left at T ∖ {f}, {e}318refine⟨(Finite.union with restFinite, Finite.singleton), _, _⟩319 . -- The ends of f stay joined through C ∖ {f}, so dropping f from320-- T ∪ {e} loses no connection.321have endsC := ends_in.subset with (ends_in.union_edge with endsT, edgeEnds),322 inside323have joinedC := EvenEdgeSet.ends_joined with324 finiteVertices, endsC, even, fInC325have restC : C ∖ {f} ⊆ (T ∖ {f}) ∪ {e}326intros g hg327obtain⟨inCg, notF⟩:= difference.singleton_membership.forward with hg328have inBig := inside with g, inCg329simp330have bridged := Joined.monotone with restC, joinedC331have grow : T ⊆ ((T ∖ {f}) ∪ {e}) ∪ {f}332intros g hg333simp334intros u hu v hv335have joinedBig := Joined.monotone with grow, (connected with u, hu, v, hv)336exact Joined.drop_edge with newEnds, fEnds, joinedBig, bridged337 . have fresh : ¬e ∈ T ∖ {f}338intro inRest339exact outside with (restSubset with e, inRest)340have inserted := cardinality.union_singleton with restFinite, fresh341have removed := cardinality.remove_add_one with finite, fInT342 rwa [inserted, ← removed]343