From c4955cd652f8f02e95592aa4593be18a5f58216d Mon Sep 17 00:00:00 2001 From: Weixuan Yuan Date: Tue, 30 Jun 2026 23:37:50 +0800 Subject: [PATCH 1/3] Refactor vertex sequence structures --- GraphLib/Graph/Adjacency.lean | 159 ++++++ GraphLib/Graph/Subgraph.lean | 4 - .../Theory/Structures/InSimpleDiGraph.lean | 445 +++++++++++++++ GraphLib/Theory/Structures/InSimpleGraph.lean | 451 ++++++++++++++++ GraphLib/Theory/Structures/Path.lean | 1 + GraphLib/Theory/Structures/SimpleCycle.lean | 308 +++++++++++ GraphLib/Theory/Structures/SimplePath.lean | 251 +++++++++ GraphLib/Theory/Structures/SimpleWalk.lean | 342 +++++++++++- GraphLib/Theory/Structures/VertexSeq.lean | 509 ++---------------- .../Theory/Structures/VertexSeq/Append.lean | 204 +++++++ .../Theory/Structures/VertexSeq/Basic.lean | 207 +++++++ .../Theory/Structures/VertexSeq/Edges.lean | 322 +++++++++++ .../Theory/Structures/VertexSeq/Erase.lean | 126 +++++ .../Theory/Structures/VertexSeq/Index.lean | 39 ++ .../Theory/Structures/VertexSeq/MapZip.lean | 137 +++++ .../Structures/VertexSeq/Predicates.lean | 160 ++++++ .../Theory/Structures/VertexSeq/Subseq.lean | 329 +++++++++++ 17 files changed, 3507 insertions(+), 487 deletions(-) create mode 100644 GraphLib/Graph/Adjacency.lean create mode 100644 GraphLib/Theory/Structures/InSimpleDiGraph.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/Append.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/Basic.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/Edges.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/Erase.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/Index.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/MapZip.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/Predicates.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/Subseq.lean diff --git a/GraphLib/Graph/Adjacency.lean b/GraphLib/Graph/Adjacency.lean new file mode 100644 index 0000000..2031666 --- /dev/null +++ b/GraphLib/Graph/Adjacency.lean @@ -0,0 +1,159 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Graph.Basic + +/-! +# Adjacency + +This file equips each of the four graph structures from +`GraphLib.Graph.Basic` (`Graph`, `SimpleGraph`, `DiGraph`, `SimpleDiGraph`) +with an adjacency relation `Adj` on vertices, together with its basic API. + +`Adj` is the primitive vertex relation that downstream files build on: a +walk lives in a graph when consecutive vertices are adjacent, a proper +colouring assigns distinct colours to adjacent vertices, and so on. We +therefore place it directly above `GraphLib.Graph.Basic` and below +`GraphLib.Graph.Degree`, where the neighbour and degree functions are +defined in terms of the same edge-membership conditions. + +## Main definitions + +* `Graph.Adj` — `u` and `v` are adjacent when a single edge has them as + endpoints; loops may make a vertex adjacent to itself. +* `SimpleGraph.Adj` — `u` and `v` are adjacent when the unordered pair + `s(u, v)` is an edge, necessarily with distinct endpoints. +* `DiGraph.Adj`, `SimpleDiGraph.Adj` — there is an arc *from* `u` *to* `v`. + Directed adjacency is not symmetric. + +## Main API + +* `Adj.symm`, `adj_comm` — symmetry of adjacency, for the undirected + types `Graph` and `SimpleGraph` only. +* `Adj.ne` — adjacent vertices are distinct. Available only for the simple + types, where it follows from looplessness; for the multigraph types a + loop genuinely makes a vertex adjacent to itself, so no such lemma holds. +* `Adj.left_mem`, `Adj.right_mem` — the endpoints of an adjacency are + vertices of the graph. + +## Design choices + +* **One definition per graph type.** As elsewhere in `GraphLib.Graph`, we + spell out `Adj` separately for each of the four structures rather than + factoring through a typeclass, keeping each definition concrete. +* **Adjacency is pure edge-existence; it does not exclude loops.** `Adj` is + simply "there is an edge joining the two vertices". For the simple types + irreflexivity is automatic from looplessness, but for the multigraph + types (`Graph`, `DiGraph`) a loop at `v` makes `Adj v v` hold. Excluding + the vertex itself is the job of the *open* neighbourhood, not of the + adjacency relation, so the `\ {v}` lives in `GraphLib.Graph.Degree`. +* **Directed adjacency is asymmetric.** `DiGraph.Adj G u v` and + `SimpleDiGraph.Adj G u v` mean there is an arc with source `u` and + target `v`; no `symm` lemma is provided for them. +-/ + +namespace GraphLib +variable {α β : Type*} + +open scoped GraphLib + +/-! ## Adjacency relations -/ + +/-- Two vertices are *adjacent* in the multigraph `G` when some edge has +them as its endpoints. A loop at `v` makes `v` adjacent to itself. -/ +@[grind] def Graph.Adj (G : Graph α β) (u v : α) : Prop := + ∃ e ∈ E(G), u ∈ e ∧ v ∈ e + +/-- Two vertices are *adjacent* in the simple graph `G` when `s(u, v)` is an +edge. -/ +@[grind] def SimpleGraph.Adj (G : SimpleGraph α) (u v : α) : Prop := + s(u, v) ∈ E(G) + +/-- There is an *arc* from `u` to `v` in the directed multigraph `G` when +some edge points from `u` to `v`. A loop at `v` is a self-arc. -/ +@[grind] def DiGraph.Adj (G : DiGraph α β) (u v : α) : Prop := + (u, v) ∈ E(G) + +/-- There is an *arc* from `u` to `v` in the simple directed graph `G` when +`(u, v)` is an edge. -/ +@[grind] def SimpleDiGraph.Adj (G : SimpleDiGraph α) (u v : α) : Prop := + (u, v) ∈ E(G) + +/-! ## Symmetry (undirected types) -/ + +/-- Adjacency in a multigraph is symmetric. -/ +@[symm, grind →] lemma Graph.Adj.symm {G : Graph α β} {u v : α} (h : G.Adj u v) : + G.Adj v u := by + obtain ⟨e, he, hu, hv⟩ := h + exact ⟨e, he, hv, hu⟩ + +/-- Adjacency in a multigraph is symmetric. -/ +lemma Graph.adj_comm (G : Graph α β) (u v : α) : G.Adj u v ↔ G.Adj v u := + ⟨Graph.Adj.symm, Graph.Adj.symm⟩ + +/-- Adjacency in a simple graph is symmetric. -/ +@[symm, grind →] lemma SimpleGraph.Adj.symm {G : SimpleGraph α} {u v : α} (h : G.Adj u v) : + G.Adj v u := by + change s(v, u) ∈ E(G) + rw [show s(v, u) = s(u, v) from Sym2.eq_swap] + exact h + +/-- Adjacency in a simple graph is symmetric. -/ +lemma SimpleGraph.adj_comm (G : SimpleGraph α) (u v : α) : G.Adj u v ↔ G.Adj v u := + ⟨SimpleGraph.Adj.symm, SimpleGraph.Adj.symm⟩ + +/-! ## Adjacent vertices are distinct + +These hold only for the simple types, where looplessness rules out loops. +For the multigraph types a loop makes a vertex adjacent to itself, so the +analogous statements are false. -/ + +/-- Adjacent vertices in a simple graph are distinct, by looplessness. -/ +@[grind →] lemma SimpleGraph.Adj.ne {G : SimpleGraph α} {u v : α} (h : G.Adj u v) : u ≠ v := by + have hnd := G.loopless h + rwa [Sym2.mk_isDiag_iff] at hnd + +/-- The endpoints of an arc in a simple directed graph are distinct, by +looplessness. -/ +@[grind →] lemma SimpleDiGraph.Adj.ne {G : SimpleDiGraph α} {u v : α} (h : G.Adj u v) : u ≠ v := + G.loopless h + +/-! ## Endpoints are vertices -/ + +/-- The left endpoint of an adjacency is a vertex of the multigraph. -/ +@[grind →] lemma Graph.Adj.left_mem {G : Graph α β} {u v : α} (h : G.Adj u v) : u ∈ V(G) := by + obtain ⟨e, he, hu, _⟩ := h + exact G.incidence he hu + +/-- The right endpoint of an adjacency is a vertex of the multigraph. -/ +@[grind →] lemma Graph.Adj.right_mem {G : Graph α β} {u v : α} (h : G.Adj u v) : v ∈ V(G) := by + obtain ⟨e, he, _, hv⟩ := h + exact G.incidence he hv + +/-- The left endpoint of an adjacency is a vertex of the simple graph. -/ +@[grind →] lemma SimpleGraph.Adj.left_mem {G : SimpleGraph α} {u v : α} (h : G.Adj u v) : + u ∈ V(G) := G.incidence h (by simp) + +/-- The right endpoint of an adjacency is a vertex of the simple graph. -/ +@[grind →] lemma SimpleGraph.Adj.right_mem {G : SimpleGraph α} {u v : α} (h : G.Adj u v) : + v ∈ V(G) := G.incidence h (by simp) + +/-- The source of an arc is a vertex of the directed multigraph. -/ +@[grind →] lemma DiGraph.Adj.left_mem {G : DiGraph α β} {u v : α} (h : G.Adj u v) : u ∈ V(G) := + (G.incidence h).1 + +/-- The target of an arc is a vertex of the directed multigraph. -/ +@[grind →] lemma DiGraph.Adj.right_mem {G : DiGraph α β} {u v : α} (h : G.Adj u v) : v ∈ V(G) := + (G.incidence h).2 + +/-- The source of an arc is a vertex of the simple directed graph. -/ +@[grind →] lemma SimpleDiGraph.Adj.left_mem {G : SimpleDiGraph α} {u v : α} (h : G.Adj u v) : + u ∈ V(G) := (G.incidence h).1 + +/-- The target of an arc is a vertex of the simple directed graph. -/ +@[grind →] lemma SimpleDiGraph.Adj.right_mem {G : SimpleDiGraph α} {u v : α} (h : G.Adj u v) : + v ∈ V(G) := (G.incidence h).2 + +end GraphLib diff --git a/GraphLib/Graph/Subgraph.lean b/GraphLib/Graph/Subgraph.lean index c233b4f..dd820e6 100644 --- a/GraphLib/Graph/Subgraph.lean +++ b/GraphLib/Graph/Subgraph.lean @@ -150,10 +150,6 @@ instance {α : Type*} : GetElem (SimpleDiGraph α) (Set α) (SimpleDiGraph α) (fun _ _ => True) where getElem G S _ := G.induce S -notation G ≤ H -notation G < H -notation G \ e -notation G - v end Notation diff --git a/GraphLib/Theory/Structures/InSimpleDiGraph.lean b/GraphLib/Theory/Structures/InSimpleDiGraph.lean new file mode 100644 index 0000000..8204261 --- /dev/null +++ b/GraphLib/Theory/Structures/InSimpleDiGraph.lean @@ -0,0 +1,445 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Graph.Adjacency +import GraphLib.Graph.Subgraph +import GraphLib.Theory.Structures.SimpleWalk + +/-! +# Vertex sequences realized in a simple directed graph + +A `VertexSeq α` is purely combinatorial: it records an ordered list of vertices, +but it does not know about any graph. This file starts the directed analogue of +`GraphLib.Theory.Structures.InSimpleGraph` by connecting vertex sequences and +simple walks to a `SimpleDiGraph`. + +## Main definitions + +* `SimpleDiGraph.IsVertexSeqIn G w` — the vertex sequence `w` is realized in + the simple directed graph `G`: its head is a vertex of `G`, and each + consecutive step follows a directed edge of `G`. +* `SimpleDiGraph.IsSimpleWalkIn G w` — the simple walk `w` is realized in `G` through + its underlying vertex sequence. + +## Main API + +* Constructor characterizations: `singleton_iff`, `cons_iff`. +* Vertex membership: `head_mem`, `tail_mem`, `mem_vertexSet`. +* Arc-list characterization: `iff_arcs`, `mem_edgeSet_of_mem_arcs`, and the + generated-graph characterization `iff_toSimpleDiGraph_subgraphOf`. +* Closure under direction-preserving `VertexSeq` operations: `prepend`, + `append`, `dropHead`, `dropTail`, `prefixUntil`, `suffixFrom`, `takeWhile`, + `dropWhile`, `loopErase`, `cycleErase`, and monotonicity `mono` under + `SimpleDiGraph.subgraphOf`. +* Thin `IsSimpleWalkIn` wrappers for the corresponding `SimpleWalk` + operations. +* `nonstalling` — a realized sequence never stalls, because adjacency in a + simple directed graph forces distinct endpoints. + +## Design choices + +* **Directed adjacency.** The `cons` constructor uses `G.Adj w.tail u`, meaning + an arc from the previous tail to the newly appended vertex. Unlike the + undirected simple-graph version, no symmetry is expected. +* **No reverse closure.** Reversing a sequence reverses every arc, so it is not + generally realized in the same directed graph. +* **Thin simple-walk layer.** `IsSimpleWalkIn` is only a wrapper around + `IsVertexSeqIn` on `w.val`; later API should normally be proved by delegating + to the vertex sequence layer. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleDiGraph + +/-! ## Vertex sequences realized in a simple directed graph -/ + +/-- A vertex sequence is *realized in* a simple directed graph `G` when its head +is a vertex of `G` and each consecutive pair follows a directed edge of `G`. -/ +@[grind] inductive IsVertexSeqIn (G : SimpleDiGraph α) : VertexSeq α → Prop + | singleton (v : α) (hv : v ∈ V(G)) : IsVertexSeqIn G (.singleton v) + | cons (w : VertexSeq α) (u : α) + (hw : IsVertexSeqIn G w) + (ha : G.Adj w.tail u) : + IsVertexSeqIn G (w.cons u) + +namespace IsVertexSeqIn + +/-! ## Constructor characterizations -/ + +/-- A singleton is realized in `G` exactly when its vertex is in `G`. -/ +@[simp, grind =] lemma singleton_iff (G : SimpleDiGraph α) (v : α) : + G.IsVertexSeqIn (.singleton v) ↔ v ∈ V(G) := + ⟨fun h => by cases h; assumption, IsVertexSeqIn.singleton v⟩ + +/-- A `cons` is realized in `G` exactly when its prefix is realized and the new +step is an arc. -/ +@[simp, grind =] lemma cons_iff (G : SimpleDiGraph α) (w : VertexSeq α) (u : α) : + G.IsVertexSeqIn (w.cons u) ↔ G.IsVertexSeqIn w ∧ G.Adj w.tail u := by + constructor + · intro h; cases h with | cons w u hw ha => exact ⟨hw, ha⟩ + · intro ⟨hw, ha⟩; exact .cons w u hw ha + +/-! ## Vertex membership -/ + +/-- The head of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma head_mem (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.head ∈ V(G) := by + induction hw with + | singleton v hv => exact hv + | cons w u hw ha ih => exact ih + +/-- The tail of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma tail_mem (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.tail ∈ V(G) := by + induction hw with + | singleton v hv => exact hv + | cons w u hw ha ih => exact ha.right_mem + +/-- Every vertex of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma mem_vertexSet (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : ∀ v ∈ w, v ∈ V(G) := by + induction hw <;> grind [SimpleDiGraph.Adj.right_mem] + +/-- A realized sequence never stalls: consecutive vertices differ, because +adjacency in a simple directed graph forces distinct endpoints. Hence it +underlies a `SimpleWalk`. -/ +@[grind →] lemma nonstalling (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.nonstalling := by + induction hw <;> grind + +/-! ## Closure under sequence operations -/ + +/-- Appending two realized sequences along an arc is realized. -/ +@[grind] lemma append (G : SimpleDiGraph α) {w1 w2 : VertexSeq α} + (h1 : G.IsVertexSeqIn w1) (h2 : G.IsVertexSeqIn w2) + (ha : G.Adj w1.tail w2.head) : G.IsVertexSeqIn (w1.append w2) := by + revert ha + induction h2 with + | singleton v hv => intro ha; exact .cons w1 v h1 ha + | cons w u hw hadj ih => + intro ha + exact .cons (w1.append w) u (ih ha) (by grind [VertexSeq.tail_append]) + +/-- Prepending a vertex along an arc to the head preserves realization. -/ +@[grind →] lemma prepend (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) {u : α} (ha : G.Adj u w.head) : + G.IsVertexSeqIn ((VertexSeq.singleton u).append w) := + append G (.singleton u ha.left_mem) hw ha + +/-- Dropping the last vertex preserves realization. -/ +@[grind →] lemma dropTail (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.dropTail := by + induction hw <;> grind + +/-- Dropping the first vertex preserves realization. -/ +@[grind →] lemma dropHead (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.dropHead := by + induction hw with + | singleton v hv => exact .singleton v hv + | cons w u hw ha ih => + cases w with + | singleton x => exact .singleton u ha.right_mem + | cons t x => exact .cons _ u ih (by rw [VertexSeq.tail_dropHead]; exact ha) + +/-- Realization is monotone under passing to a supergraph. -/ +@[grind →] lemma mono (G H : SimpleDiGraph α) {w : VertexSeq α} + (hw : H.IsVertexSeqIn w) (hsub : SimpleDiGraph.subgraphOf H G) : + G.IsVertexSeqIn w := by + induction hw with + | singleton v hv => exact .singleton v (hsub.1 hv) + | cons w u hw ha ih => exact .cons w u ih (hsub.2 ha) + +/-- Taking the prefix up to the first occurrence of `v` preserves realization. -/ +@[grind →] lemma prefixUntil [DecidableEq α] (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : + ∀ (v : α) (h : v ∈ w), G.IsVertexSeqIn (w.prefixUntil v h) := by + induction hw with + | singleton x hx => intro v h; grind + | cons w u hw ha ih => + intro v h + by_cases h2 : v ∈ w <;> grind + +/-- Dropping to the suffix from the first occurrence of `v` preserves +realization. -/ +@[grind →] lemma suffixFrom [DecidableEq α] (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : + ∀ (v : α) (h : v ∈ w), G.IsVertexSeqIn (w.suffixFrom v h) := by + induction hw with + | singleton x hx => intro v h; grind + | cons w u hw ha ih => + intro v h + by_cases h2 : v ∈ w <;> + grind [VertexSeq.tail_suffixFrom, SimpleDiGraph.Adj.right_mem] + +/-- Taking the longest prefix on which `p` holds (plus its first failure) +preserves realization. -/ +lemma takeWhile (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) (p : α → Prop) [DecidablePred p] : + G.IsVertexSeqIn (w.takeWhile p) := by + induction hw with + | singleton x hx => exact .singleton x hx + | cons w u hw ha ih => + change G.IsVertexSeqIn (if ∃ v ∈ w.toList, ¬ p v then w.takeWhile p else w.cons u) + by_cases hc : ∃ v ∈ w.toList, ¬ p v + · rw [if_pos hc]; exact ih + · rw [if_neg hc]; exact .cons w u hw ha + +/-- Dropping the longest prefix on which `p` holds preserves realization. -/ +lemma dropWhile (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) (p : α → Prop) [DecidablePred p] : + ∀ (h : ∃ v ∈ w.toList, ¬ p v), G.IsVertexSeqIn (w.dropWhile p h) := by + induction hw with + | singleton x hx => intro h; exact .singleton x hx + | cons w u hw ha ih => + intro h + change G.IsVertexSeqIn + (if hq : ∃ v ∈ w.toList, ¬ p v then (w.dropWhile p hq).cons u else .singleton u) + by_cases hc : ∃ v ∈ w.toList, ¬ p v + · rw [dif_pos hc] + exact .cons (w.dropWhile p hc) u (ih hc) + (by rw [VertexSeq.tail_dropWhile]; exact ha) + · rw [dif_neg hc]; exact .singleton u ha.right_mem + +/-- If `G` has no arcs, every realized sequence has length zero. -/ +lemma length_eq_zero_of_no_edges (G : SimpleDiGraph α) (hE : E(G) = ∅) + {w : VertexSeq α} (hw : G.IsVertexSeqIn w) : w.length = 0 := by + induction hw <;> grind + +/-- Removing immediate stalls preserves realization. (On a realized — hence +non-stalling — sequence `loopErase` is in fact the identity.) -/ +lemma loopErase [DecidableEq α] (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.loopErase := by + rw [VertexSeq.loopErase_eq_self_of_nonstalling w (nonstalling G hw)] + exact hw + +/-- Cycle erasure preserves realization: dropping the detour between two +occurrences of a vertex keeps the sequence realized in `G`. -/ +lemma cycleErase [DecidableEq α] (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.cycleErase := by + revert hw + fun_induction VertexSeq.cycleErase w <;> + intro hw <;> grind [IsVertexSeqIn.prefixUntil, VertexSeq.tail_cycleErase] + +/-! ## Arc-list characterization -/ + +/-- The arc-list view of realization, bridging the adjacency-based inductive +definition: `w` is realized in `G` exactly when its head is a vertex of `G` and +every arc it traverses is an edge of `G`. -/ +theorem iff_arcs (G : SimpleDiGraph α) (w : VertexSeq α) : + G.IsVertexSeqIn w ↔ w.head ∈ V(G) ∧ ∀ a ∈ w.arcs, a ∈ E(G) := by + constructor + · intro hw + refine ⟨head_mem G hw, ?_⟩ + induction hw with + | singleton v hv => intro a ha; simp [VertexSeq.arcs] at ha + | cons w u hw ha ih => + intro a hmem + rw [VertexSeq.mem_arcs_cons] at hmem + rcases hmem with hmem | rfl + · exact ih a hmem + · exact ha + · induction w with + | singleton v => intro h; exact .singleton v h.1 + | cons w u ih => + intro h + rw [cons_iff] + refine ⟨ih ⟨h.1, fun a ha => h.2 a ?_⟩, h.2 (w.tail, u) ?_⟩ + · rw [VertexSeq.mem_arcs_cons]; exact Or.inl ha + · rw [VertexSeq.mem_arcs_cons]; exact Or.inr rfl + +/-- Any arc traversed by a realized vertex sequence is an edge of the graph. -/ +@[grind →] lemma mem_edgeSet_of_mem_arcs (G : SimpleDiGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) {a : α × α} (ha : a ∈ w.arcs) : a ∈ E(G) := + ((iff_arcs G w).1 hw).2 a ha + +/-- The final step of a non-trivial realized vertex sequence is an adjacency in +the graph. -/ +@[grind →] lemma adj_dropTail_tail_tail_of_length_ne_zero (G : SimpleDiGraph α) + {w : VertexSeq α} (hw : G.IsVertexSeqIn w) (h : w.length ≠ 0) : + G.Adj w.dropTail.tail w.tail := by + change (w.dropTail.tail, w.tail) ∈ E(G) + exact mem_edgeSet_of_mem_arcs G hw + (by + rw [VertexSeq.arcs_eq_dropTail_concat_of_length_ne_zero w h] + simp [List.concat_eq_append]) + +end IsVertexSeqIn + +/-! ## Simple walks realized in a simple directed graph -/ + +/-- A simple walk is realized in `G` when its underlying vertex sequence is +realized in `G`. -/ +@[grind] def IsSimpleWalkIn (G : SimpleDiGraph α) (w : SimpleWalk α) : Prop := + G.IsVertexSeqIn w.val + +namespace IsSimpleWalkIn + +/-! ## Bridge to `IsVertexSeqIn` -/ + +/-- Realization of a simple walk is realization of its underlying vertex +sequence. -/ +@[simp, grind =] lemma isSimpleWalkIn_iff_isVertexSeqIn + (G : SimpleDiGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ G.IsVertexSeqIn w.val := Iff.rfl + +/-! ## Vertex membership -/ + +/-- The head of a realized walk is a vertex of `G`. -/ +@[grind →] lemma head_mem (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : w.head ∈ V(G) := + IsVertexSeqIn.head_mem G hw + +/-- The tail of a realized walk is a vertex of `G`. -/ +@[grind →] lemma tail_mem (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : w.tail ∈ V(G) := + IsVertexSeqIn.tail_mem G hw + +/-- Every vertex visited by a realized walk is a vertex of `G`. -/ +@[grind →] lemma mem_vertexSet (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : ∀ v ∈ w.support, v ∈ V(G) := + IsVertexSeqIn.mem_vertexSet G hw + +/-! ## Vertex and arc membership -/ + +/-- Any arc traversed by a realized walk is an edge of `G`. -/ +@[grind →] lemma mem_edgeSet_of_mem_arcs (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) {a : α × α} (ha : a ∈ w.arcs) : a ∈ E(G) := + IsVertexSeqIn.mem_edgeSet_of_mem_arcs G hw ha + +/-- A walk is realized in `G` exactly when its head is a vertex of `G` and every +arc it traverses is an edge of `G`. -/ +theorem iff_arcs (G : SimpleDiGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ w.head ∈ V(G) ∧ ∀ a ∈ w.arcs, a ∈ E(G) := + IsVertexSeqIn.iff_arcs G w.val + +/-! ## Traversed arcs and the generated simple directed graph -/ + +/-- All arcs traversed by a realized simple walk are edges of `G`, as a bundled +arc-list predicate. -/ +lemma arcs_subset_edgeSet (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : ∀ a ∈ w.arcs, a ∈ E(G) := + (iff_arcs G w).1 hw |>.2 + +/-- The simple directed graph generated by a realized simple walk is a subgraph +of `G`. -/ +lemma toSimpleDiGraph_subgraphOf (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : SimpleDiGraph.subgraphOf w.toSimpleDiGraph G := by + refine ⟨?_, ?_⟩ + · intro v hv + exact mem_vertexSet G hw v (by simpa using hv) + · intro a ha + exact mem_edgeSet_of_mem_arcs G hw (by simpa using ha) + +/-- If the simple directed graph generated by a simple walk is a subgraph of +`G`, then the walk is realized in `G`. -/ +lemma of_toSimpleDiGraph_subgraphOf (G : SimpleDiGraph α) {w : SimpleWalk α} + (hsub : SimpleDiGraph.subgraphOf w.toSimpleDiGraph G) : G.IsSimpleWalkIn w := by + rw [iff_arcs] + refine ⟨?_, ?_⟩ + · exact hsub.1 (by simp [SimpleWalk.support]) + · intro a ha + exact hsub.2 (by simpa using ha) + +/-- A simple walk is realized in `G` exactly when its generated simple directed +graph is a subgraph of `G`. -/ +theorem iff_toSimpleDiGraph_subgraphOf (G : SimpleDiGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ SimpleDiGraph.subgraphOf w.toSimpleDiGraph G := + ⟨toSimpleDiGraph_subgraphOf G, of_toSimpleDiGraph_subgraphOf G⟩ + +/-! ## Closure under walk operations -/ + +/-- Dropping the last vertex of a realized walk preserves realization. -/ +@[grind →] lemma dropTail (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.dropTail := + IsVertexSeqIn.dropTail G hw + +/-- Dropping the first vertex of a realized walk preserves realization. -/ +@[grind →] lemma dropHead (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.dropHead := + IsVertexSeqIn.dropHead G hw + +/-- Taking a prefix of a realized walk preserves realization. -/ +@[grind →] lemma prefixUntil [DecidableEq α] (G : SimpleDiGraph α) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (v : α) (h : v ∈ w.val) : + G.IsSimpleWalkIn (w.prefixUntil v h) := + IsVertexSeqIn.prefixUntil G hw v h + +/-- Taking a suffix of a realized walk preserves realization. -/ +@[grind →] lemma suffixFrom [DecidableEq α] (G : SimpleDiGraph α) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (v : α) (h : v ∈ w.val) : + G.IsSimpleWalkIn (w.suffixFrom v h) := + IsVertexSeqIn.suffixFrom G hw v h + +/-- Taking the longest prefix on which `p` holds (plus its first failure) +preserves realization. -/ +lemma takeWhile (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) (p : α → Prop) [DecidablePred p] : + G.IsSimpleWalkIn (w.takeWhile p) := + IsVertexSeqIn.takeWhile G hw p + +/-- Dropping the longest prefix on which `p` holds preserves realization. -/ +lemma dropWhile (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) (p : α → Prop) [DecidablePred p] + (h : ∃ v ∈ w.val.toList, ¬ p v) : + G.IsSimpleWalkIn (w.dropWhile p h) := + IsVertexSeqIn.dropWhile G hw p h + +/-- Loop erasure preserves realization. On a simple walk this operation is the +identity on the underlying sequence. -/ +lemma loopErase [DecidableEq α] (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn (w.loopErase) := + IsVertexSeqIn.loopErase G hw + +/-- Cycle erasure preserves realization. -/ +lemma cycleErase [DecidableEq α] (G : SimpleDiGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn (w.cycleErase) := + IsVertexSeqIn.cycleErase G hw + +/-- Realization is monotone under passing to a supergraph. -/ +@[grind →] lemma mono (G H : SimpleDiGraph α) {w : SimpleWalk α} + (hw : H.IsSimpleWalkIn w) (hsub : SimpleDiGraph.subgraphOf H G) : + G.IsSimpleWalkIn w := + IsVertexSeqIn.mono G H hw hsub + +/-- If `G` has no arcs, every realized walk in `G` has length zero. -/ +lemma length_eq_zero_of_no_edges (G : SimpleDiGraph α) (hE : E(G) = ∅) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) : w.length = 0 := + IsVertexSeqIn.length_eq_zero_of_no_edges G hE hw + +/-! ## Joining walks -/ + +/-- Appending two realized walks along an arc of `G` preserves realization. -/ +lemma append (G : SimpleDiGraph α) {p q : SimpleWalk α} + (hp : G.IsSimpleWalkIn p) (hq : G.IsSimpleWalkIn q) + (ha : G.Adj p.tail q.head) : + G.IsSimpleWalkIn (p.append q ha.ne) := + IsVertexSeqIn.append G hp hq ha + +/-- Gluing two realized walks at a shared endpoint preserves realization. -/ +lemma glue (G : SimpleDiGraph α) {p q : SimpleWalk α} + (hp : G.IsSimpleWalkIn p) (hq : G.IsSimpleWalkIn q) + (h : p.tail = q.head) : + G.IsSimpleWalkIn (p.glue q h) := by + unfold SimpleWalk.glue + by_cases hlen : p.val.length = 0 + · rw [dif_pos hlen] + exact hq + · rw [dif_neg hlen] + exact IsVertexSeqIn.append G (IsVertexSeqIn.dropTail G hp) hq + (by + have h' : p.val.tail = q.val.head := h + rw [← h'] + exact IsVertexSeqIn.adj_dropTail_tail_tail_of_length_ne_zero G hp hlen) + +end IsSimpleWalkIn + +end SimpleDiGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/InSimpleGraph.lean b/GraphLib/Theory/Structures/InSimpleGraph.lean index a1ec9f1..531fd2e 100644 --- a/GraphLib/Theory/Structures/InSimpleGraph.lean +++ b/GraphLib/Theory/Structures/InSimpleGraph.lean @@ -3,3 +3,454 @@ Copyright (c) 2026 Basil Rohner. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan -/ +import GraphLib.Graph.Adjacency +import GraphLib.Graph.Subgraph +import GraphLib.Theory.Structures.SimpleWalk + +/-! +# Vertex sequences realized in a simple graph + +A `VertexSeq α` is a purely combinatorial object: it knows nothing about any +graph. This file connects vertex sequences to a `SimpleGraph` through the +predicate `SimpleGraph.IsVertexSeqIn`, which says that the sequence is *realized* +in `G`: its first vertex is a vertex of `G` and every consecutive pair is an edge +of `G` (phrased through `SimpleGraph.Adj`). + +This is the undirected analogue intended to mirror, in the `GraphLib` style, the +`IsVertexSeqIn` development from the legacy `GraphAlgorithms` library. The +primary definition is inductive and phrased through `Adj`; the edge-list +characterization appears later as a bridge to generated subgraphs and edge-set +reasoning. + +## Main definitions + +* `SimpleGraph.IsVertexSeqIn G w` — `w` is realized in `G`. +* `SimpleGraph.IsSimpleWalkIn G w` — a `SimpleWalk` is realized in `G` through its + underlying vertex sequence. + +## Main API + +* `SimpleGraph.IsVertexSeqIn.singleton_iff`, `cons_iff` — the two constructor + characterizations, which drive most downstream proofs. +* Vertex membership: `head_mem`, `tail_mem`, `mem_vertexSet`. +* Closure under the `VertexSeq` operations: `prepend`, `append`, `reverse`, + `dropHead`, `dropTail`, `prefixUntil`, `suffixFrom`, `takeWhile`, `dropWhile`, + `loopErase`, `cycleErase`, and monotonicity `mono` under passing to a + subgraph (`SimpleGraph.subgraphOf`). +* Thin `IsSimpleWalkIn` wrappers for the corresponding `SimpleWalk` operations. +* `nonstalling` — a realized sequence never stalls (so it is a `SimpleWalk`), + because adjacency in a simple graph forces distinct endpoints. + +## Design choices + +* **Adjacency, not edge sets.** The `cons` step is stated with `G.Adj w.tail u` + rather than `s(w.tail, u) ∈ E(G)`. The two are definitionally equal, but `Adj` + is the intended primitive and carries the reusable `symm`/`ne`/`left_mem`/ + `right_mem` API. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +/-! ## The realized-in predicate -/ + +/-- A vertex sequence is *realized in* `G` when its head is a vertex of `G` and +each consecutive pair is an edge of `G`. -/ +@[grind] inductive IsVertexSeqIn (G : SimpleGraph α) : VertexSeq α → Prop + | singleton (v : α) (hv : v ∈ V(G)) : IsVertexSeqIn G (.singleton v) + | cons (w : VertexSeq α) (u : α) + (hw : IsVertexSeqIn G w) + (he : G.Adj w.tail u) : + IsVertexSeqIn G (w.cons u) + +namespace IsVertexSeqIn + +/-! ## Constructor characterizations -/ + +/-- A singleton is realized in `G` exactly when its vertex is in `G`. -/ +@[simp, grind =] lemma singleton_iff (G : SimpleGraph α) (v : α) : + G.IsVertexSeqIn (.singleton v) ↔ v ∈ V(G) := + ⟨fun h => by cases h; assumption, IsVertexSeqIn.singleton v⟩ + +/-- A `cons` is realized in `G` exactly when its prefix is realized and the new +step is an edge. -/ +@[simp, grind =] lemma cons_iff (G : SimpleGraph α) (w : VertexSeq α) (u : α) : + G.IsVertexSeqIn (w.cons u) ↔ G.IsVertexSeqIn w ∧ G.Adj w.tail u := by + constructor + · intro h; cases h with | cons w u hw he => exact ⟨hw, he⟩ + · intro ⟨hw, he⟩; exact .cons w u hw he + +/-! ## Vertex membership -/ + +/-- The head of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma head_mem (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.head ∈ V(G) := by + induction hw with + | singleton v hv => exact hv + | cons w u hw he ih => exact ih + +/-- The tail of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma tail_mem (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.tail ∈ V(G) := by + induction hw with + | singleton v hv => exact hv + | cons w u hw he ih => exact he.right_mem + +/-- Every vertex of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma mem_vertexSet (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : ∀ v ∈ w, v ∈ V(G) := by + induction hw <;> grind [SimpleGraph.Adj.right_mem] + +/-- A realized sequence never stalls: consecutive vertices differ, because +adjacency in a simple graph forces distinct endpoints. Hence it underlies a +`SimpleWalk`. -/ +@[grind →] lemma nonstalling (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.nonstalling := by + induction hw <;> grind + +/-! ## Closure under sequence operations -/ + +/-- Appending two realized sequences along an edge is realized. -/ +@[grind] lemma append (G : SimpleGraph α) {w1 w2 : VertexSeq α} + (h1 : G.IsVertexSeqIn w1) (h2 : G.IsVertexSeqIn w2) + (he : G.Adj w1.tail w2.head) : G.IsVertexSeqIn (w1.append w2) := by + revert he + induction h2 with + | singleton v hv => intro he; exact .cons w1 v h1 he + | cons w u hw hadj ih => + intro he + exact .cons (w1.append w) u (ih he) (by grind [VertexSeq.tail_append]) + +/-- Prepending a vertex along an edge to the head preserves realization. -/ +@[grind →] lemma prepend (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) {u : α} (he : G.Adj u w.head) : + G.IsVertexSeqIn ((VertexSeq.singleton u).append w) := + append G (.singleton u he.left_mem) hw he + +/-- Reversing a realized sequence preserves realization (adjacency is symmetric +in a simple graph). -/ +@[grind →] lemma reverse (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.reverse := by + induction hw with + | singleton v hv => exact .singleton v hv + | cons w u hw he ih => + have hu : G.Adj u w.reverse.head := by + rw [VertexSeq.head_reverse]; exact he.symm + simpa [VertexSeq.reverse] using prepend G ih hu + +/-- Dropping the last vertex preserves realization. -/ +@[grind →] lemma dropTail (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.dropTail := by + induction hw <;> grind + +/-- Dropping the first vertex preserves realization. -/ +@[grind →] lemma dropHead (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.dropHead := by + induction hw with + | singleton v hv => exact .singleton v hv + | cons w u hw he ih => + cases w with + | singleton x => exact .singleton u he.right_mem + | cons t x => exact .cons _ u ih (by rw [VertexSeq.tail_dropHead]; exact he) + +/-- Realization is monotone under passing to a supergraph. -/ +@[grind →] lemma mono (G H : SimpleGraph α) {w : VertexSeq α} + (hw : H.IsVertexSeqIn w) (hsub : SimpleGraph.subgraphOf H G) : + G.IsVertexSeqIn w := by + induction hw with + | singleton v hv => exact .singleton v (hsub.1 hv) + | cons w u hw he ih => exact .cons w u ih (hsub.2 he) + +/-- Taking the prefix up to the first occurrence of `v` preserves realization. -/ +@[grind →] lemma prefixUntil [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : + ∀ (v : α) (h : v ∈ w), G.IsVertexSeqIn (w.prefixUntil v h) := by + induction hw with + | singleton x hx => intro v h; grind + | cons w u hw he ih => + intro v h + by_cases h2 : v ∈ w <;> grind + +/-- Dropping to the suffix from the first occurrence of `v` preserves +realization. -/ +@[grind →] lemma suffixFrom [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : + ∀ (v : α) (h : v ∈ w), G.IsVertexSeqIn (w.suffixFrom v h) := by + induction hw with + | singleton x hx => intro v h; grind + | cons w u hw he ih => + intro v h + by_cases h2 : v ∈ w <;> + grind [VertexSeq.tail_suffixFrom, SimpleGraph.Adj.right_mem] + +/-- Taking the longest prefix on which `p` holds (plus its first failure) +preserves realization. -/ +lemma takeWhile (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) (p : α → Prop) [DecidablePred p] : + G.IsVertexSeqIn (w.takeWhile p) := by + induction hw with + | singleton x hx => exact .singleton x hx + | cons w u hw he ih => + change G.IsVertexSeqIn (if ∃ v ∈ w.toList, ¬ p v then w.takeWhile p else w.cons u) + by_cases hc : ∃ v ∈ w.toList, ¬ p v + · rw [if_pos hc]; exact ih + · rw [if_neg hc]; exact .cons w u hw he + +/-- Dropping the longest prefix on which `p` holds preserves realization. -/ +lemma dropWhile (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) (p : α → Prop) [DecidablePred p] : + ∀ (h : ∃ v ∈ w.toList, ¬ p v), G.IsVertexSeqIn (w.dropWhile p h) := by + induction hw with + | singleton x hx => intro h; exact .singleton x hx + | cons w u hw he ih => + intro h + change G.IsVertexSeqIn + (if hq : ∃ v ∈ w.toList, ¬ p v then (w.dropWhile p hq).cons u else .singleton u) + by_cases hc : ∃ v ∈ w.toList, ¬ p v + · rw [dif_pos hc] + exact .cons (w.dropWhile p hc) u (ih hc) + (by rw [VertexSeq.tail_dropWhile]; exact he) + · rw [dif_neg hc]; exact .singleton u he.right_mem + +/-- If `G` has no edges, every realized sequence has length zero. -/ +lemma length_eq_zero_of_no_edges (G : SimpleGraph α) (hE : E(G) = ∅) + {w : VertexSeq α} (hw : G.IsVertexSeqIn w) : w.length = 0 := by + induction hw <;> grind + +/-- Removing immediate stalls preserves realization. (On a realized — hence +non-stalling — sequence `loopErase` is in fact the identity.) -/ +lemma loopErase [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.loopErase := by + rw [VertexSeq.loopErase_eq_self_of_nonstalling w (nonstalling G hw)] + exact hw + +/-- Cycle erasure preserves realization: dropping the detour between two +occurrences of a vertex keeps the sequence realized in `G`. -/ +lemma cycleErase [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.cycleErase := by + revert hw + fun_induction VertexSeq.cycleErase w <;> + intro hw <;> grind [IsVertexSeqIn.prefixUntil, VertexSeq.tail_cycleErase] + +/-! ## Edge-set characterization -/ + +/-- The edge-set view of realization, bridging the adjacency-based inductive +definition: `w` is realized in `G` exactly when its head is a vertex of `G` and +every edge it traverses is an edge of `G`. -/ +theorem iff_edges (G : SimpleGraph α) (w : VertexSeq α) : + G.IsVertexSeqIn w ↔ w.head ∈ V(G) ∧ ∀ e ∈ w.edges, e ∈ E(G) := by + constructor + · intro hw + refine ⟨head_mem G hw, ?_⟩ + induction hw with + | singleton v hv => intro e he; simp [VertexSeq.edges] at he + | cons w u hw he ih => + intro e hmem + rw [VertexSeq.mem_edges_cons] at hmem + rcases hmem with hmem | rfl + · exact ih e hmem + · exact he + · induction w with + | singleton v => intro h; exact .singleton v h.1 + | cons w u ih => + intro h + rw [cons_iff] + refine ⟨ih ⟨h.1, fun e he => h.2 e ?_⟩, h.2 s(w.tail, u) ?_⟩ + · rw [VertexSeq.mem_edges_cons]; exact Or.inl he + · rw [VertexSeq.mem_edges_cons]; exact Or.inr rfl + +/-- Any edge traversed by a realized vertex sequence is an edge of the graph. -/ +@[grind →] lemma mem_edgeSet_of_mem_edges (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) {e : Sym2 α} (he : e ∈ w.edges) : e ∈ E(G) := + ((iff_edges G w).1 hw).2 e he + +/-- The final step of a non-trivial realized vertex sequence is an adjacency in +the graph. -/ +@[grind →] lemma adj_dropTail_tail_tail_of_length_ne_zero (G : SimpleGraph α) + {w : VertexSeq α} (hw : G.IsVertexSeqIn w) (h : w.length ≠ 0) : + G.Adj w.dropTail.tail w.tail := by + change s(w.dropTail.tail, w.tail) ∈ E(G) + exact mem_edgeSet_of_mem_edges G hw + (by + rw [VertexSeq.edges_eq_dropTail_concat_of_length_ne_zero w h] + simp [List.concat_eq_append]) + +end IsVertexSeqIn + +/-! ## Simple walks realized in a graph -/ + +/-- A simple walk is realized in `G` when its underlying vertex sequence is +realized in `G`. -/ +@[grind] def IsSimpleWalkIn (G : SimpleGraph α) (w : SimpleWalk α) : Prop := + G.IsVertexSeqIn w.val + +namespace IsSimpleWalkIn + +/-! ## Bridge to `IsVertexSeqIn` -/ + +/-- Realization of a simple walk is realization of its underlying vertex +sequence. -/ +@[simp, grind =] lemma isSimpleWalkIn_iff_isVertexSeqIn (G : SimpleGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ G.IsVertexSeqIn w.val := Iff.rfl + +/-! ## Vertex and edge membership -/ + +/-- The head of a realized walk is a vertex of `G`. -/ +@[grind →] lemma head_mem (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : w.head ∈ V(G) := + IsVertexSeqIn.head_mem G hw + +/-- The tail of a realized walk is a vertex of `G`. -/ +@[grind →] lemma tail_mem (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : w.tail ∈ V(G) := + IsVertexSeqIn.tail_mem G hw + +/-- Every vertex visited by a realized walk is a vertex of `G`. -/ +@[grind →] lemma mem_vertexSet (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : ∀ v ∈ w.support, v ∈ V(G) := + IsVertexSeqIn.mem_vertexSet G hw + +/-- Any edge traversed by a realized walk is an edge of `G`. -/ +@[grind →] lemma mem_edgeSet_of_mem_edges (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) {e : Sym2 α} (he : e ∈ w.edges) : e ∈ E(G) := + IsVertexSeqIn.mem_edgeSet_of_mem_edges G hw he + +/-- A walk is realized in `G` exactly when its head is a vertex of `G` and every +edge it traverses is an edge of `G`. -/ +theorem iff_edges (G : SimpleGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ w.head ∈ V(G) ∧ ∀ e ∈ w.edges, e ∈ E(G) := + IsVertexSeqIn.iff_edges G w.val + +/-! ## Traversed edges and the generated simple graph -/ + +/-- All edges traversed by a realized simple walk are edges of `G`, as a bundled +edge-list predicate. -/ +lemma edges_subset_edgeSet (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : ∀ e ∈ w.edges, e ∈ E(G) := + (iff_edges G w).1 hw |>.2 + +/-- The simple graph generated by a realized simple walk is a subgraph of `G`. -/ +lemma toSimpleGraph_subgraphOf (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : SimpleGraph.subgraphOf w.toSimpleGraph G := by + refine ⟨?_, ?_⟩ + · intro v hv + exact mem_vertexSet G hw v (by simpa using hv) + · intro e he + exact mem_edgeSet_of_mem_edges G hw (by simpa using he) + +/-- If the graph generated by a simple walk is a subgraph of `G`, then the walk +is realized in `G`. -/ +lemma of_toSimpleGraph_subgraphOf (G : SimpleGraph α) {w : SimpleWalk α} + (hsub : SimpleGraph.subgraphOf w.toSimpleGraph G) : G.IsSimpleWalkIn w := by + rw [iff_edges] + refine ⟨?_, ?_⟩ + · exact hsub.1 (by simp [SimpleWalk.support]) + · intro e he + exact hsub.2 (by simpa using he) + +/-- A simple walk is realized in `G` exactly when its generated simple graph is +a subgraph of `G`. -/ +theorem iff_toSimpleGraph_subgraphOf (G : SimpleGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ SimpleGraph.subgraphOf w.toSimpleGraph G := + ⟨toSimpleGraph_subgraphOf G, of_toSimpleGraph_subgraphOf G⟩ + +/-! ## Closure under walk operations -/ + +/-- Reversing a realized walk preserves realization. -/ +@[grind →] lemma reverse (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.reverse := + IsVertexSeqIn.reverse G hw + +/-- Dropping the last vertex of a realized walk preserves realization. -/ +@[grind →] lemma dropTail (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.dropTail := + IsVertexSeqIn.dropTail G hw + +/-- Dropping the first vertex of a realized walk preserves realization. -/ +@[grind →] lemma dropHead (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.dropHead := + IsVertexSeqIn.dropHead G hw + +/-- Taking a prefix of a realized walk preserves realization. -/ +@[grind →] lemma prefixUntil [DecidableEq α] (G : SimpleGraph α) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (v : α) (h : v ∈ w.val) : + G.IsSimpleWalkIn (w.prefixUntil v h) := + IsVertexSeqIn.prefixUntil G hw v h + +/-- Taking a suffix of a realized walk preserves realization. -/ +@[grind →] lemma suffixFrom [DecidableEq α] (G : SimpleGraph α) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (v : α) (h : v ∈ w.val) : + G.IsSimpleWalkIn (w.suffixFrom v h) := + IsVertexSeqIn.suffixFrom G hw v h + +/-- Taking the longest prefix on which `p` holds (plus its first failure) +preserves realization. -/ +lemma takeWhile (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) (p : α → Prop) [DecidablePred p] : + G.IsSimpleWalkIn (w.takeWhile p) := + IsVertexSeqIn.takeWhile G hw p + +/-- Dropping the longest prefix on which `p` holds preserves realization. -/ +lemma dropWhile (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) (p : α → Prop) [DecidablePred p] + (h : ∃ v ∈ w.val.toList, ¬ p v) : + G.IsSimpleWalkIn (w.dropWhile p h) := + IsVertexSeqIn.dropWhile G hw p h + +/-- Loop erasure preserves realization. On a simple walk this operation is the +identity on the underlying sequence. -/ +lemma loopErase [DecidableEq α] (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn (w.loopErase) := + IsVertexSeqIn.loopErase G hw + +/-- Cycle erasure preserves realization. -/ +lemma cycleErase [DecidableEq α] (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn (w.cycleErase) := + IsVertexSeqIn.cycleErase G hw + +/-- Realization is monotone under passing to a supergraph. -/ +@[grind →] lemma mono (G H : SimpleGraph α) {w : SimpleWalk α} + (hw : H.IsSimpleWalkIn w) (hsub : SimpleGraph.subgraphOf H G) : + G.IsSimpleWalkIn w := + IsVertexSeqIn.mono G H hw hsub + +/-- If `G` has no edges, every realized walk in `G` has length zero. -/ +lemma length_eq_zero_of_no_edges (G : SimpleGraph α) (hE : E(G) = ∅) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) : w.length = 0 := + IsVertexSeqIn.length_eq_zero_of_no_edges G hE hw + +/-! ## Joining walks -/ + +/-- Appending two realized walks along an edge of `G` preserves realization. -/ +lemma append (G : SimpleGraph α) {p q : SimpleWalk α} + (hp : G.IsSimpleWalkIn p) (hq : G.IsSimpleWalkIn q) + (he : G.Adj p.tail q.head) : + G.IsSimpleWalkIn (p.append q he.ne) := + IsVertexSeqIn.append G hp hq he + +/-- Gluing two realized walks at a shared endpoint preserves realization. -/ +lemma glue (G : SimpleGraph α) {p q : SimpleWalk α} + (hp : G.IsSimpleWalkIn p) (hq : G.IsSimpleWalkIn q) + (h : p.tail = q.head) : + G.IsSimpleWalkIn (p.glue q h) := by + unfold SimpleWalk.glue + by_cases hlen : p.val.length = 0 + · rw [dif_pos hlen] + exact hq + · rw [dif_neg hlen] + exact IsVertexSeqIn.append G (IsVertexSeqIn.dropTail G hp) hq + (by + have h' : p.val.tail = q.val.head := h + rw [← h'] + exact IsVertexSeqIn.adj_dropTail_tail_tail_of_length_ne_zero G hp hlen) + +end IsSimpleWalkIn + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/Path.lean b/GraphLib/Theory/Structures/Path.lean index a1ec9f1..9cce4df 100644 --- a/GraphLib/Theory/Structures/Path.lean +++ b/GraphLib/Theory/Structures/Path.lean @@ -3,3 +3,4 @@ Copyright (c) 2026 Basil Rohner. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan -/ + diff --git a/GraphLib/Theory/Structures/SimpleCycle.lean b/GraphLib/Theory/Structures/SimpleCycle.lean index a1ec9f1..d299d17 100644 --- a/GraphLib/Theory/Structures/SimpleCycle.lean +++ b/GraphLib/Theory/Structures/SimpleCycle.lean @@ -3,3 +3,311 @@ Copyright (c) 2026 Basil Rohner. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan -/ +import GraphLib.Theory.Structures.SimplePath + +/-! +# Simple cycles + +A `SimpleCycle α` is a closed `SimpleWalk α` of length at least three whose +interior (the walk with its final repeated vertex dropped) is a simple path. +It is represented as a subtype of `SimpleWalk`. + +The length-≥-3 bound is the *undirected* convention; the directed notion +(which admits length-two cycles) will get its own `SimpleDiCycle`. The `arcs` +API here only reflects a chosen traversal orientation and is not canonical. + +## Main definitions + +* `SimpleWalk.IsCycle` — a closed simple walk whose dropped-tail walk is a path. +* `SimpleCycle` — a simple walk bundled with a proof of `SimpleWalk.IsCycle`. +-/ + +variable {α : Type*} + +namespace SimpleWalk + +/-- A simple walk is a cycle when it is closed, has length at least three, +and its dropped-tail walk is a path. -/ +@[grind] def IsCycle (w : SimpleWalk α) : Prop := + 3 ≤ w.length ∧ w.closed ∧ w.dropTail.nodup + +/-- Reversal preserves the simple-cycle property. -/ +lemma isCycle_reverse (w : SimpleWalk α) (h : IsCycle w) : + IsCycle w.reverse := by + rcases h with ⟨hlen, hclosed, hnodup⟩ + refine ⟨?_, ?_, ?_⟩ + · change 3 ≤ w.val.reverse.length + simpa using hlen + · change w.val.reverse.closed + simpa [VertexSeq.closed] using hclosed.symm + · exact VertexSeq.nodup_reverse_dropTail_of_cycle w.val hclosed hnodup + +end SimpleWalk + +/-- A simple cycle is a simple walk that is a cycle. -/ +def SimpleCycle (α : Type*) := + { w : SimpleWalk α // SimpleWalk.IsCycle w } + +namespace SimpleCycle + +/-! ## Basic accessors -/ + +/-- The underlying simple walk. -/ +abbrev val (c : SimpleCycle α) : SimpleWalk α := c.1 + +/-- The underlying vertex sequence. -/ +abbrev vertices (c : SimpleCycle α) : VertexSeq α := c.val.val + +/-- The list of vertices visited by the cycle. -/ +abbrev support (c : SimpleCycle α) : List α := (vertices c).toList + +/-- The unordered edges traversed by the cycle. -/ +abbrev edges (c : SimpleCycle α) : List (Sym2 α) := (vertices c).edges + +/-- The directed arcs traversed by the cycle. -/ +abbrev arcs (c : SimpleCycle α) : List (α × α) := (vertices c).arcs + +/-- The first vertex of the cycle. -/ +abbrev head (c : SimpleCycle α) : α := (vertices c).head + +/-- The last vertex of the cycle (equal to its head, since a cycle is closed). -/ +abbrev tail (c : SimpleCycle α) : α := (vertices c).tail + +/-- The number of edges in the cycle. -/ +abbrev length (c : SimpleCycle α) : ℕ := (vertices c).length + +/-- The interior of the cycle: the vertex sequence with its final (repeated) +vertex dropped. -/ +def interior (c : SimpleCycle α) : SimplePath α := + ⟨c.val.dropTail, c.2.2.2⟩ + +/-- A cycle is closed: its first and last vertex coincide. -/ +lemma closed (c : SimpleCycle α) : c.val.closed := c.2.2.1 + +/-- A simple cycle is, in particular, a simple walk. -/ +instance : Coe (SimpleCycle α) (SimpleWalk α) := + ⟨val⟩ + +/-! ## reverse -/ + +/-- Reverse the orientation of a simple cycle. -/ +@[grind] def reverse (c : SimpleCycle α) : SimpleCycle α := + ⟨c.val.reverse, SimpleWalk.isCycle_reverse c.val c.2⟩ + +/-! ## reroot -/ + +/-- Re-root a simple cycle at any vertex on it. -/ +def reroot [DecidableEq α] (c : SimpleCycle α) (u : α) (hu : u ∈ vertices c) : + SimpleCycle α := + if hhead : u = head c then + c + else + ⟨(c.val.suffixFrom u hu).glue (c.val.prefixUntil u hu) (by + change (c.val.val.suffixFrom u hu).tail = (c.val.val.prefixUntil u hu).head + rw [VertexSeq.tail_suffixFrom, VertexSeq.head_prefixUntil] + exact (SimpleCycle.closed c).symm), + by + have hsplit := VertexSeq.dropTail_prefixUntil_append_suffixFrom + (vertices c) u hu hhead + let pre : VertexSeq α := (vertices c).prefixUntil u hu + let suf : VertexSeq α := (vertices c).suffixFrom u hu + have hpre_pos : pre.length ≠ 0 := by + intro hz + have h_eq := VertexSeq.head_eq_tail_of_length_zero pre hz + have h_eq' : (vertices c).head = u := by + simpa [pre] using h_eq + exact hhead h_eq'.symm + have hsuf_pos : suf.length ≠ 0 := by + intro hz + have h_eq := VertexSeq.head_eq_tail_of_length_zero suf hz + have h_eq' : u = (vertices c).tail := by + simpa [suf] using h_eq + exact hhead (by + rw [← SimpleCycle.closed c] at h_eq' + exact h_eq') + have hsplit' : pre.dropTail.append suf = vertices c := by + simpa [pre, suf] using hsplit + have hsplitLen := congrArg VertexSeq.length hsplit' + have hsplitLen' : pre.dropTail.length + suf.length + 1 = (vertices c).length := by + simpa [VertexSeq.length_append] using hsplitLen + have hpreLen := VertexSeq.dropTail_length_succ pre hpre_pos + have hsufLen := VertexSeq.dropTail_length_succ suf hsuf_pos + have hdropSplit := congrArg VertexSeq.dropTail hsplit' + have hleft : (pre.dropTail.append suf.dropTail).nodup := by + have hdrop : + (pre.dropTail.append suf).dropTail = pre.dropTail.append suf.dropTail := + VertexSeq.dropTail_append_of_length_ne_zero pre.dropTail suf hsuf_pos + rw [← hdrop] + rw [hdropSplit] + exact c.2.2.2 + have hrotDrop : (suf.dropTail.append pre).dropTail = suf.dropTail.append pre.dropTail := + VertexSeq.dropTail_append_of_length_ne_zero suf.dropTail pre hpre_pos + have hsuf_pos' : ((c.val.suffixFrom u hu).val.length ≠ 0) := by + simpa [suf] using hsuf_pos + simp [SimpleWalk.IsCycle, SimpleWalk.glue, hsuf_pos'] + refine ⟨?_, ?_, ?_⟩ + · change 3 ≤ (suf.dropTail.append pre).length + have hrotLen : (suf.dropTail.append pre).length = (vertices c).length := by + simp [VertexSeq.length_append] + omega + rw [hrotLen] + exact c.2.1 + · change (suf.dropTail.append pre).closed + simp [VertexSeq.closed, pre, suf] + · change (suf.dropTail.append pre).dropTail.nodup + rw [hrotDrop] + exact VertexSeq.nodup_append_comm pre.dropTail suf.dropTail hleft⟩ + +/-! ## edges -/ + +/-- The number of traversed edges equals the cycle's length. -/ +@[simp] lemma length_edges (c : SimpleCycle α) : (edges c).length = length c := + VertexSeq.length_edges (vertices c) + +/-- A simple cycle has at least three traversed edges. -/ +lemma three_le_length_edges (c : SimpleCycle α) : 3 ≤ (edges c).length := by + rw [length_edges] + exact c.2.1 + +/-- A simple cycle traverses at least one edge. -/ +lemma edges_ne_nil (c : SimpleCycle α) : edges c ≠ [] := by + intro h + have hlen := three_le_length_edges c + rw [h] at hlen + simp at hlen + +/-- The edge list is the interior path's edge list plus the closing edge. -/ +lemma interior_edges (c : SimpleCycle α) : + edges c = SimplePath.edges (interior c) ++ [s(SimplePath.tail (interior c), tail c)] := by + have hlen : 3 ≤ (vertices c).length := c.2.1 + have hpos : (vertices c).length ≠ 0 := by omega + change (vertices c).edges = + (vertices c).dropTail.edges ++ [s((vertices c).dropTail.tail, (vertices c).tail)] + rw [VertexSeq.edges_eq_dropTail_concat_of_length_ne_zero (vertices c) hpos] + simp [List.concat_eq_append] + +/-- A simple cycle traverses each edge at most once. -/ +lemma edges_nodup (c : SimpleCycle α) : (edges c).Nodup := by + rw [interior_edges] + rw [List.nodup_append] + refine ⟨SimplePath.edges_nodup (interior c), by simp, ?_⟩ + intro a ha b hb hab + simp only [List.mem_singleton] at hb + subst hb + subst hab + have hclosedTail : tail c = SimplePath.head (interior c) := by + change (vertices c).tail = (vertices c).dropTail.head + rw [VertexSeq.head_dropTail] + exact (closed c).symm + have hmem : + s((SimplePath.vertices (interior c)).head, + (SimplePath.vertices (interior c)).tail) ∈ + (SimplePath.vertices (interior c)).edges := by + simpa [hclosedTail, Sym2.eq_swap] using ha + have hle := VertexSeq.length_le_one_of_mem_edges_head_tail + (SimplePath.vertices (interior c)) + (SimplePath.nodup (interior c)) hmem + have hlen : 3 ≤ (vertices c).length := c.2.1 + have hpos : (vertices c).length ≠ 0 := by omega + have hdrop := VertexSeq.dropTail_length_succ (vertices c) hpos + change (vertices c).dropTail.length + 1 = (vertices c).length at hdrop + change (vertices c).dropTail.length ≤ 1 at hle + omega + +/-- Reversal reverses the edge list. -/ +@[simp] lemma edges_reverse (c : SimpleCycle α) : + edges (reverse c) = (edges c).reverse := + VertexSeq.edges_reverse (vertices c) + +/-! ## arcs -/ + +/-- The number of traversed arcs equals the cycle's length. -/ +@[simp] lemma length_arcs (c : SimpleCycle α) : (arcs c).length = length c := + VertexSeq.length_arcs (vertices c) + +/-- A simple cycle has at least three traversed arcs. -/ +lemma three_le_length_arcs (c : SimpleCycle α) : 3 ≤ (arcs c).length := by + rw [length_arcs] + exact c.2.1 + +/-- A simple cycle traverses at least one arc. -/ +lemma arcs_ne_nil (c : SimpleCycle α) : arcs c ≠ [] := by + intro h + have hlen := three_le_length_arcs c + rw [h] at hlen + simp at hlen + +/-- The arc list is the interior path's arc list plus the closing arc. -/ +lemma interior_arcs (c : SimpleCycle α) : + arcs c = + SimplePath.arcs (interior c) ++ [(SimplePath.tail (interior c), tail c)] := by + have hlen : 3 ≤ (vertices c).length := c.2.1 + have hpos : (vertices c).length ≠ 0 := by omega + change (vertices c).arcs = + (vertices c).dropTail.arcs ++ [((vertices c).dropTail.tail, (vertices c).tail)] + rw [VertexSeq.arcs_eq_dropTail_concat_of_length_ne_zero (vertices c) hpos] + simp [List.concat_eq_append] + +/-- A simple cycle traverses each directed arc at most once. -/ +lemma arcs_nodup (c : SimpleCycle α) : (arcs c).Nodup := by + rw [interior_arcs] + rw [List.nodup_append] + refine ⟨SimplePath.arcs_nodup (interior c), by simp, ?_⟩ + intro a ha b hb hab + simp only [List.mem_singleton] at hb + subst hb + subst hab + have hclosedTail : tail c = SimplePath.head (interior c) := by + change (vertices c).tail = (vertices c).dropTail.head + rw [VertexSeq.head_dropTail] + exact (closed c).symm + have hmem : + ((SimplePath.vertices (interior c)).tail, + (SimplePath.vertices (interior c)).head) ∈ + (SimplePath.vertices (interior c)).arcs := by + simpa [hclosedTail] using ha + have hle := VertexSeq.length_le_one_of_mem_arcs_tail_head + (SimplePath.vertices (interior c)) + (SimplePath.nodup (interior c)) hmem + have hlen : 3 ≤ (vertices c).length := c.2.1 + have hpos : (vertices c).length ≠ 0 := by omega + have hdrop := VertexSeq.dropTail_length_succ (vertices c) hpos + change (vertices c).dropTail.length + 1 = (vertices c).length at hdrop + change (vertices c).dropTail.length ≤ 1 at hle + omega + +/-- Reversal reverses the arc list and swaps every arc's endpoints. -/ +@[simp] lemma arcs_reverse (c : SimpleCycle α) : + arcs (reverse c) = (arcs c).reverse.map (fun a : α × α => (a.2, a.1)) := + VertexSeq.arcs_reverse (vertices c) + +/-! ## constructors -/ + +/-- Close a simple path by adding an edge from its tail back to its head. -/ +def ofPathClosing (p : SimplePath α) + (hlen : 2 ≤ (SimplePath.vertices p).length) : + SimpleCycle α := + let w : SimpleWalk α := + ⟨VertexSeq.cons (SimplePath.vertices p) (SimplePath.head p), by + have hp : (SimplePath.vertices p).nodup := SimplePath.nodup p + have htail_ne : + (SimplePath.vertices p).tail ≠ (SimplePath.vertices p).head := by + intro h + have hzero := VertexSeq.length_zero_of_nodup_head_eq_tail + (SimplePath.vertices p) hp h.symm + omega + exact ⟨p.val.nonstalling, htail_ne⟩⟩ + ⟨w, by + refine ⟨?_, ?_, ?_⟩ + · change 3 ≤ (VertexSeq.cons (SimplePath.vertices p) + (SimplePath.head p)).length + simp [VertexSeq.length] + omega + · change (VertexSeq.cons (SimplePath.vertices p) + (SimplePath.head p)).closed + simp [VertexSeq.closed] + · change (VertexSeq.cons (SimplePath.vertices p) + (SimplePath.head p)).dropTail.nodup + simpa using SimplePath.nodup p⟩ + +end SimpleCycle diff --git a/GraphLib/Theory/Structures/SimplePath.lean b/GraphLib/Theory/Structures/SimplePath.lean index a1ec9f1..15bdb20 100644 --- a/GraphLib/Theory/Structures/SimplePath.lean +++ b/GraphLib/Theory/Structures/SimplePath.lean @@ -3,3 +3,254 @@ Copyright (c) 2026 Basil Rohner. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan -/ +import GraphLib.Theory.Structures.SimpleWalk + +/-! +# Simple paths + +A `SimplePath α` is a `SimpleWalk α` whose vertices are pairwise distinct. +This file keeps paths as a subtype of simple walks, so every path can be used +where a simple walk is expected. + +## Main definitions + +* `SimpleWalk.IsPath` — a simple walk with no repeated vertices. +* `SimplePath` — a simple walk bundled with a proof that its vertices are + pairwise distinct. +-/ + +variable {α : Type*} + +namespace SimpleWalk + +/-- A simple walk is a path when it has no repeated vertices. -/ +abbrev IsPath (w : SimpleWalk α) : Prop := w.nodup + +end SimpleWalk + +/-- A simple path is a simple walk with no repeated vertex. -/ +def SimplePath (α : Type*) := + { w : SimpleWalk α // SimpleWalk.IsPath w } + +namespace SimplePath + +/-! ## Basic accessors -/ + +/-- The underlying simple walk. -/ +abbrev val (p : SimplePath α) : SimpleWalk α := p.1 + +/-- The underlying vertex sequence. -/ +abbrev vertices (p : SimplePath α) : VertexSeq α := p.val.val + +/-- The list of vertices visited by the path. -/ +abbrev support (p : SimplePath α) : List α := (vertices p).toList + +/-- The unordered edges traversed by the path. -/ +abbrev edges (p : SimplePath α) : List (Sym2 α) := (vertices p).edges + +/-- The directed arcs traversed by the path. -/ +abbrev arcs (p : SimplePath α) : List (α × α) := (vertices p).arcs + +/-- The first vertex of the path. -/ +abbrev head (p : SimplePath α) : α := (vertices p).head + +/-- The last vertex of the path. -/ +abbrev tail (p : SimplePath α) : α := (vertices p).tail + +/-- The number of edges in the path. -/ +abbrev length (p : SimplePath α) : ℕ := (vertices p).length + +/-- A path has no repeated vertices. -/ +lemma nodup (p : SimplePath α) : (vertices p).nodup := p.2 + + +/-- A simple path is, in particular, a simple walk. -/ +instance : Coe (SimplePath α) (SimpleWalk α) := + ⟨val⟩ + +/-! ## dropHead, dropTail -/ + +/-- Drop the first vertex of a path. Removing an endpoint preserves `nodup`. -/ +def dropHead (p : SimplePath α) : SimplePath α := + ⟨p.val.dropHead, by + exact VertexSeq.nodup_dropHead (vertices p) (nodup p)⟩ + +/-- Drop the last vertex of a path. Removing an endpoint preserves `nodup`. -/ +def dropTail (p : SimplePath α) : SimplePath α := + ⟨p.val.dropTail, by + exact VertexSeq.nodup_dropTail (vertices p) (nodup p)⟩ + +/-! ## append -/ + +/-- Concatenate two vertex-disjoint paths, keeping both joining vertices. +The disjointness hypothesis is what preserves `nodup`; it also rules out a +stall at the junction. -/ +def append (p q : SimplePath α) + (hdisj : ∀ v : α, v ∈ vertices p → v ∈ vertices q → False) : SimplePath α := + ⟨p.val.append q.val (by + intro h + exact hdisj (tail p) (VertexSeq.tail_mem (vertices p)) (by + simp [tail, vertices, h])), by + exact VertexSeq.nodup_append (vertices p) (vertices q) (nodup p) (nodup q) hdisj⟩ + +/-- Concatenate two paths meeting at a shared vertex, dropping the duplicated +joining vertex. The remaining vertices of `p` must be disjoint from `q`. -/ +def glue (p q : SimplePath α) (h : tail p = head q) + (hdisj : ∀ v : α, v ∈ (vertices p).dropTail → v ∈ vertices q → False) : + SimplePath α := + ⟨p.val.glue q.val h, by + unfold SimpleWalk.glue + by_cases hp : (vertices p).length = 0 + · simpa [vertices, hp] using nodup q + · simpa [vertices, hp] using + VertexSeq.nodup_append (vertices p).dropTail (vertices q) + (VertexSeq.nodup_dropTail (vertices p) (nodup p)) (nodup q) hdisj⟩ + +/-! ## reverse -/ + +/-- Reverse a path. Reversal preserves `nodup`. -/ +def reverse (p : SimplePath α) : SimplePath α := + ⟨p.val.reverse, by + exact VertexSeq.nodup_reverse (vertices p) (nodup p)⟩ + +/-! ## prefixUntil, suffixFrom -/ + +/-- The prefix of a path ending at the first occurrence of `v`. A contiguous +prefix of a path is a path. -/ +def prefixUntil [DecidableEq α] (p : SimplePath α) (v : α) (h : v ∈ vertices p) : + SimplePath α := + ⟨p.val.prefixUntil v h, by + exact VertexSeq.nodup_prefixUntil (vertices p) v h (nodup p)⟩ + +/-- The suffix of a path starting at the first occurrence of `v`. A contiguous +suffix of a path is a path. -/ +def suffixFrom [DecidableEq α] (p : SimplePath α) (v : α) (h : v ∈ vertices p) : + SimplePath α := + ⟨p.val.suffixFrom v h, by + exact VertexSeq.nodup_suffixFrom (vertices p) v h (nodup p)⟩ + +/-! ## map -/ + +/-- Map a path through an injective function. Injectivity preserves distinct +vertices. -/ +def map {β : Type*} (f : α → β) (hf : Function.Injective f) (p : SimplePath α) : + SimplePath β := + ⟨p.val.map f hf, by + exact VertexSeq.nodup_map f hf (vertices p) (nodup p)⟩ + +/-! ## takeWhile, dropWhile -/ + +/-- Take a contiguous prefix of a path, including the first failure if any. -/ +def takeWhile (p : SimplePath α) (q : α → Prop) [DecidablePred q] : + SimplePath α := + ⟨p.val.takeWhile q, by + exact VertexSeq.nodup_takeWhile (vertices p) q (nodup p)⟩ + +/-- Drop the longest prefix of a path on which `q` holds. The result is a +contiguous suffix, hence a path. -/ +def dropWhile (p : SimplePath α) (q : α → Prop) [DecidablePred q] + (h : ∃ v ∈ (vertices p).toList, ¬ q v) : SimplePath α := + ⟨p.val.dropWhile q h, by + exact VertexSeq.nodup_dropWhile (vertices p) q h (nodup p)⟩ + +/-! ## splitAt -/ + +/-- Split a path at every occurrence of `v`. Every produced piece is a +contiguous subpath. -/ +def splitAt [DecidableEq α] (p : SimplePath α) (v : α) : List (SimplePath α) := + ((vertices p).splitAt v).pmap + (fun w (hw : w.nonstalling ∧ w.nodup) => ⟨⟨w, hw.1⟩, hw.2⟩) + (by + intro w hw + exact ⟨VertexSeq.nonstalling_splitAt (vertices p) p.val.nonstalling v w hw, + VertexSeq.nodup_splitAt (vertices p) (nodup p) v w hw⟩) + +/-! ## zip -/ + +/-- Zip two paths into a path of pairs. Pairwise distinct first components are +enough to make the pairs pairwise distinct. -/ +def zip {β : Type*} (p : SimplePath α) (q : SimplePath β) : + SimplePath (α × β) := + ⟨p.val.zip q.val, by + exact VertexSeq.nodup_zip (vertices p) (vertices q) (nodup p)⟩ + +/-! ## loopErase, cycleErase -/ + +/-- Remove immediate stalls from a path. Since a path has no repeated vertices, +this preserves `nodup`. -/ +def loopErase [DecidableEq α] (p : SimplePath α) : SimplePath α := + ⟨p.val.loopErase, by + exact VertexSeq.nodup_loopErase (vertices p) (nodup p)⟩ + +/-- Cycle erasure of any simple walk produces a simple path. -/ +def cycleErase [DecidableEq α] (w : SimpleWalk α) : SimplePath α := + ⟨w.cycleErase, by + exact w.val.cycleErase_nodup⟩ + +/-! ## edges -/ + +/-- The number of traversed edges equals the path's length. -/ +@[simp] lemma length_edges (p : SimplePath α) : (edges p).length = length p := + VertexSeq.length_edges (vertices p) + +/-- A path traverses each edge at most once (a path is a trail). -/ +lemma edges_nodup (p : SimplePath α) : (edges p).Nodup := + VertexSeq.nodup_edges_of_nodup (vertices p) (nodup p) + +/-- Reversal reverses the edge list. -/ +@[simp] lemma edges_reverse (p : SimplePath α) : + edges (reverse p) = (edges p).reverse := + VertexSeq.edges_reverse (vertices p) + +/-- Dropping the last vertex cannot introduce new edges. -/ +lemma edges_dropTail_subset (p : SimplePath α) : edges (dropTail p) ⊆ edges p := + VertexSeq.edges_dropTail_subset (vertices p) + +/-- Dropping the first vertex cannot introduce new edges. -/ +lemma edges_dropHead_subset (p : SimplePath α) : edges (dropHead p) ⊆ edges p := + VertexSeq.edges_dropHead_subset (vertices p) + +/-- Taking a prefix cannot introduce new edges. -/ +lemma edges_prefixUntil_subset [DecidableEq α] (p : SimplePath α) (v : α) + (h : v ∈ vertices p) : edges (prefixUntil p v h) ⊆ edges p := + VertexSeq.edges_prefixUntil_subset (vertices p) v h + +/-- Taking a suffix cannot introduce new edges. -/ +lemma edges_suffixFrom_subset [DecidableEq α] (p : SimplePath α) (v : α) + (h : v ∈ vertices p) : edges (suffixFrom p v h) ⊆ edges p := + VertexSeq.edges_suffixFrom_subset (vertices p) v h + +/-! ## arcs -/ + +/-- The number of traversed arcs equals the path's length. -/ +@[simp] lemma length_arcs (p : SimplePath α) : (arcs p).length = length p := + VertexSeq.length_arcs (vertices p) + +/-- A path traverses each directed arc at most once. -/ +lemma arcs_nodup (p : SimplePath α) : (arcs p).Nodup := + VertexSeq.nodup_arcs_of_nodup (vertices p) (nodup p) + +/-- Reversal reverses the arc list and swaps every arc's endpoints. -/ +@[simp] lemma arcs_reverse (p : SimplePath α) : + arcs (reverse p) = (arcs p).reverse.map (fun a : α × α => (a.2, a.1)) := + VertexSeq.arcs_reverse (vertices p) + +/-- Dropping the last vertex cannot introduce new arcs. -/ +lemma arcs_dropTail_subset (p : SimplePath α) : arcs (dropTail p) ⊆ arcs p := + VertexSeq.arcs_dropTail_subset (vertices p) + +/-- Dropping the first vertex cannot introduce new arcs. -/ +lemma arcs_dropHead_subset (p : SimplePath α) : arcs (dropHead p) ⊆ arcs p := + VertexSeq.arcs_dropHead_subset (vertices p) + +/-- Taking a prefix cannot introduce new arcs. -/ +lemma arcs_prefixUntil_subset [DecidableEq α] (p : SimplePath α) (v : α) + (h : v ∈ vertices p) : arcs (prefixUntil p v h) ⊆ arcs p := + VertexSeq.arcs_prefixUntil_subset (vertices p) v h + +/-- Taking a suffix cannot introduce new arcs. -/ +lemma arcs_suffixFrom_subset [DecidableEq α] (p : SimplePath α) (v : α) + (h : v ∈ vertices p) : arcs (suffixFrom p v h) ⊆ arcs p := + VertexSeq.arcs_suffixFrom_subset (vertices p) v h + +end SimplePath diff --git a/GraphLib/Theory/Structures/SimpleWalk.lean b/GraphLib/Theory/Structures/SimpleWalk.lean index 2c13346..f311a76 100644 --- a/GraphLib/Theory/Structures/SimpleWalk.lean +++ b/GraphLib/Theory/Structures/SimpleWalk.lean @@ -17,34 +17,324 @@ makes the resulting graph loopless). variable {α : Type*} -/-- A simple walk: a `VertexSeq` with no immediate backtracking. -/ +/-- A simple walk: a `VertexSeq` with no two consecutive vertices equal. -/ def SimpleWalk (α : Type*) := { w : VertexSeq α // w.nonstalling } -namespace VertexSeq - -/-- The unordered edges traversed by a vertex sequence (the consecutive -pairs `s(w.tail, v)` for each `cons` step). -/ -@[grind] def edges : VertexSeq α → List (Sym2 α) - | .singleton _ => [] - | .cons w v => w.edges.concat s(w.tail, v) - -/-- The directed arcs traversed by a vertex sequence (the consecutive ordered -pairs `(w.tail, v)` for each `cons` step). -/ -@[grind] def arcs : VertexSeq α → List (α × α) - | .singleton _ => [] - | .cons w v => w.arcs.concat (w.tail, v) - -end VertexSeq +-- `VertexSeq.edges` and `VertexSeq.arcs` (the traversed edges/arcs) live in +-- `VertexSeq/Edges.lean`, since they are purely combinatorial. namespace SimpleWalk open GraphLib +/-! ## Basic accessors -/ + /-- The underlying vertex sequence. -/ abbrev val (w : SimpleWalk α) : VertexSeq α := w.1 /-- The non-stalling proof. -/ -lemma nonstalling (w : SimpleWalk α) : w.val.nonstalling := w.2 +@[simp, grind] lemma nonstalling (w : SimpleWalk α) : w.val.nonstalling := w.2 + +/-- The list of vertices visited by the walk. -/ +abbrev support (w : SimpleWalk α) : List α := w.val.toList + +/-- The unordered edges traversed by the walk. -/ +abbrev edges (w : SimpleWalk α) : List (Sym2 α) := w.val.edges + +/-- The directed arcs traversed by the walk. -/ +abbrev arcs (w : SimpleWalk α) : List (α × α) := w.val.arcs + +/-- The first vertex of the walk. -/ +abbrev head (w : SimpleWalk α) : α := w.val.head + +/-- The last vertex of the walk. -/ +abbrev tail (w : SimpleWalk α) : α := w.val.tail + +/-- The number of edges in the walk. -/ +abbrev length (w : SimpleWalk α) : ℕ := w.val.length + +/-- The walk has no repeated vertex (i.e. it is a path). -/ +abbrev nodup (w : SimpleWalk α) : Prop := w.val.nodup + +/-- The walk is closed: its first and last vertex coincide. -/ +abbrev closed (w : SimpleWalk α) : Prop := w.val.closed + +/-- A simple walk is, in particular, a vertex sequence. -/ +instance : Coe (SimpleWalk α) (VertexSeq α) := + ⟨val⟩ + +/-! ## dropHead, dropTail -/ + +/-- Drop the first vertex of the simple walk (returns it unchanged when it is a +singleton). Removing an endpoint cannot introduce a stall. -/ +def dropHead (w : SimpleWalk α) : SimpleWalk α := + ⟨w.val.dropHead, by have := w.nonstalling; grind⟩ + +/-- Drop the last vertex of the simple walk (returns it unchanged when it is a +singleton). Removing an endpoint cannot introduce a stall. -/ +def dropTail (w : SimpleWalk α) : SimpleWalk α := + ⟨w.val.dropTail, by have := w.nonstalling; grind⟩ + +/-! ## append -/ + +/-- Concatenate two simple walks, keeping both joining vertices. The hypothesis +`p.tail ≠ q.head` ensures the junction does not create a stall. -/ +def append (p q : SimpleWalk α) (h : p.val.tail ≠ q.val.head) : SimpleWalk α := + ⟨p.val.append q.val, by + exact (VertexSeq.nonstalling_append p.val q.val).2 + ⟨p.nonstalling, q.nonstalling, h⟩⟩ + +/-- Concatenate two simple walks meeting at a shared vertex (`p.tail = q.head`), +dropping the duplicated joining vertex. -/ +def glue (p q : SimpleWalk α) (h : p.val.tail = q.val.head) : SimpleWalk α := + if hp : p.val.length = 0 then + q + else + ⟨p.val.dropTail.append q.val, by + have hpns : p.val.dropTail.nonstalling := + VertexSeq.nonstalling_dropTail p.val p.nonstalling + have hjoin : p.val.dropTail.tail ≠ q.val.head := by + rw [← h] + clear h q + obtain ⟨s, hs⟩ := p + induction s with + | singleton v => + simp [VertexSeq.length] at hp + | cons s v ih => + cases s with + | singleton u => + simpa [VertexSeq.dropTail, VertexSeq.tail] using hs.2 + | cons t u => + simpa [VertexSeq.dropTail, VertexSeq.tail] using hs.2 + exact (VertexSeq.nonstalling_append p.val.dropTail q.val).2 + ⟨hpns, q.nonstalling, hjoin⟩⟩ + +/-! ## reverse -/ + +/-- Reverse a simple walk: the head becomes the tail and vice versa. Reversal +only swaps the order of the consecutive pairs, so non-stalling is preserved. -/ +def reverse (w : SimpleWalk α) : SimpleWalk α := + ⟨w.val.reverse, by have := w.nonstalling; grind⟩ + +/-! ## prefixUntil, suffixFrom -/ + +/-- The prefix of `w` ending at the first occurrence of `v`, inclusive of that +vertex. The hypothesis guarantees such a vertex exists. A contiguous prefix of +a non-stalling walk is non-stalling. -/ +def prefixUntil [DecidableEq α] (w : SimpleWalk α) (v : α) (h : v ∈ w.val) : + SimpleWalk α := + ⟨w.val.prefixUntil v h, by grind⟩ + +/-- The suffix of `w` starting at the first occurrence of `v`, inclusive of that +vertex. The hypothesis guarantees such a vertex exists. A contiguous suffix of +a non-stalling walk is non-stalling. -/ +def suffixFrom [DecidableEq α] (w : SimpleWalk α) (v : α) (h : v ∈ w.val) : + SimpleWalk α := + ⟨w.val.suffixFrom v h, by grind⟩ + +/-! ## map -/ + +/-- Map a simple walk through an injective function. Injectivity ensures +distinct consecutive vertices stay distinct, so non-stalling is preserved. -/ +def map {β : Type*} (f : α → β) (hf : Function.Injective f) (w : SimpleWalk α) : + SimpleWalk β := + ⟨w.val.map f, by + have hmap : ∀ s : VertexSeq α, s.nonstalling → (s.map f).nonstalling := by + intro s hs + induction s with + | singleton v => + simp [VertexSeq.map, VertexSeq.nonstalling] + | cons p v ih => + constructor + · exact ih hs.1 + · intro h + have hf_tail : f p.tail = f v := by + simpa [VertexSeq.tail_map] using h + exact hs.2 (hf hf_tail) + exact hmap w.val w.nonstalling⟩ + +/-! ## takeWhile, dropWhile -/ + +/-- Take every vertex of `w` satisfying `p`, plus the first failure (if any). +The result is a contiguous prefix, hence non-stalling. -/ +def takeWhile (w : SimpleWalk α) (p : α → Prop) [DecidablePred p] : + SimpleWalk α := + ⟨w.val.takeWhile p, by grind⟩ + +/-- Drop the longest prefix of `w` on which `p` holds; the result starts at the +first failure. The hypothesis ensures a failure exists. The result is a +contiguous suffix, hence non-stalling. -/ +def dropWhile (w : SimpleWalk α) (p : α → Prop) [DecidablePred p] + (h : ∃ v ∈ w.val.toList, ¬ p v) : SimpleWalk α := + ⟨w.val.dropWhile p h, by grind⟩ + +/-! ## splitAt -/ + +/-- Split `w` into a list of pieces at every occurrence of the vertex `v`. Each +piece is a contiguous sub-walk of `w`, hence non-stalling. -/ +def splitAt [DecidableEq α] (w : SimpleWalk α) (v : α) : List (SimpleWalk α) := + (w.val.splitAt v).pmap (fun p hp => ⟨p, hp⟩) + (VertexSeq.nonstalling_splitAt w.val w.nonstalling v) + +/-! ## zip -/ + +/-- Zip two simple walks into a simple walk of pairs. Consecutive pairs differ +in their first component (since `w` is non-stalling), so the result is +non-stalling. -/ +def zip {β : Type*} (w : SimpleWalk α) (w' : SimpleWalk β) : + SimpleWalk (α × β) := + ⟨w.val.zip w'.val, by grind⟩ + +/-! ## loopErase, cycleErase -/ + +/-- Remove immediate stalls. On a simple walk this is the identity, and the +result is non-stalling by construction. -/ +def loopErase [DecidableEq α] (w : SimpleWalk α) : SimpleWalk α := + ⟨w.val.loopErase, w.val.loopErase_nonstalling⟩ + +/-- Cycle erasure: whenever a vertex repeats, drop the intermediate detour. The +result has no repeated vertex, and `nodup` implies `nonstalling`. -/ +def cycleErase [DecidableEq α] (w : SimpleWalk α) : SimpleWalk α := + ⟨w.val.cycleErase, VertexSeq.nodup_nonstalling _ w.val.cycleErase_nodup⟩ + +/-! ## edges + +The walk's traversed edges, lifted from the underlying vertex sequence. Most +lemmas are thin wrappers around the `VertexSeq` versions through `.val`. -/ + +/-- The number of traversed edges equals the walk's length. -/ +@[simp] lemma length_edges (w : SimpleWalk α) : w.edges.length = w.length := + VertexSeq.length_edges w.val + +/-- Reversal reverses the edge list. -/ +@[simp] lemma edges_reverse (w : SimpleWalk α) : + w.reverse.edges = w.edges.reverse := + VertexSeq.edges_reverse w.val + +/-- The edges of an append are those of the operands plus the joining edge. -/ +lemma edges_append (p q : SimpleWalk α) (h : p.val.tail ≠ q.val.head) : + (p.append q h).edges = p.edges ++ [s(p.tail, q.head)] ++ q.edges := + VertexSeq.edges_append p.val q.val + +/-- Gluing at a shared vertex concatenates the edge lists: the dropped duplicate +vertex carries no new edge beyond the one already ending `p`. -/ +lemma edges_glue (p q : SimpleWalk α) (h : p.val.tail = q.val.head) : + (p.glue q h).edges = p.edges ++ q.edges := by + rw [SimpleWalk.glue] + split + · next hp => simp [edges, VertexSeq.edges_eq_nil_of_length_eq_zero p.val hp] + · next hp => + change (p.val.dropTail.append q.val).edges = p.val.edges ++ q.val.edges + rw [VertexSeq.edges_append, + VertexSeq.edges_eq_dropTail_concat_of_length_ne_zero p.val hp, ← h, + List.concat_eq_append] + +/-- On a simple walk `loopErase` is the identity, so it leaves the edges +unchanged. -/ +@[simp] lemma edges_loopErase [DecidableEq α] (w : SimpleWalk α) : + w.loopErase.edges = w.edges := by + change w.val.loopErase.edges = w.val.edges + rw [VertexSeq.loopErase_eq_self_of_nonstalling w.val w.nonstalling] + +/-- Cycle erasure cannot introduce new edges. -/ +lemma edges_cycleErase_subset [DecidableEq α] (w : SimpleWalk α) : + w.cycleErase.edges ⊆ w.edges := + VertexSeq.edges_cycleErase_subset w.val + +/-- Dropping the last vertex cannot introduce new edges. -/ +lemma edges_dropTail_subset (w : SimpleWalk α) : w.dropTail.edges ⊆ w.edges := + VertexSeq.edges_dropTail_subset w.val + +/-- Dropping the first vertex cannot introduce new edges. -/ +lemma edges_dropHead_subset (w : SimpleWalk α) : w.dropHead.edges ⊆ w.edges := + VertexSeq.edges_dropHead_subset w.val + +/-- Taking a prefix cannot introduce new edges. -/ +lemma edges_prefixUntil_subset [DecidableEq α] (w : SimpleWalk α) (v : α) + (h : v ∈ w.val) : (w.prefixUntil v h).edges ⊆ w.edges := + VertexSeq.edges_prefixUntil_subset w.val v h + +/-- Taking a suffix cannot introduce new edges. -/ +lemma edges_suffixFrom_subset [DecidableEq α] (w : SimpleWalk α) (v : α) + (h : v ∈ w.val) : (w.suffixFrom v h).edges ⊆ w.edges := + VertexSeq.edges_suffixFrom_subset w.val v h + +/-- Any endpoint of a traversed edge is a vertex of the walk. -/ +lemma mem_of_mem_edges {e : Sym2 α} {v : α} (w : SimpleWalk α) + (he : e ∈ w.edges) (hv : v ∈ e) : v ∈ w.support := + VertexSeq.mem_of_mem_edges w.val he hv + +/-! ## arcs + +The walk's traversed arcs, lifted from the underlying vertex sequence. Most +lemmas are thin wrappers around the `VertexSeq` versions through `.val`. -/ + +/-- The number of traversed arcs equals the walk's length. -/ +@[simp] lemma length_arcs (w : SimpleWalk α) : w.arcs.length = w.length := + VertexSeq.length_arcs w.val + +/-- Reversal reverses the arc list and swaps every arc's endpoints. -/ +@[simp] lemma arcs_reverse (w : SimpleWalk α) : + w.reverse.arcs = w.arcs.reverse.map (fun a : α × α => (a.2, a.1)) := + VertexSeq.arcs_reverse w.val + +/-- The arcs of an append are those of the operands plus the joining arc. -/ +lemma arcs_append (p q : SimpleWalk α) (h : p.val.tail ≠ q.val.head) : + (p.append q h).arcs = p.arcs ++ [(p.tail, q.head)] ++ q.arcs := + VertexSeq.arcs_append p.val q.val + +/-- Gluing at a shared vertex concatenates the arc lists: the dropped duplicate +vertex carries no new arc beyond the one already ending `p`. -/ +lemma arcs_glue (p q : SimpleWalk α) (h : p.val.tail = q.val.head) : + (p.glue q h).arcs = p.arcs ++ q.arcs := by + rw [SimpleWalk.glue] + split + · next hp => simp [arcs, VertexSeq.arcs_eq_nil_of_length_eq_zero p.val hp] + · next hp => + change (p.val.dropTail.append q.val).arcs = p.val.arcs ++ q.val.arcs + rw [VertexSeq.arcs_append, + VertexSeq.arcs_eq_dropTail_concat_of_length_ne_zero p.val hp, ← h, + List.concat_eq_append] + +/-- On a simple walk `loopErase` is the identity, so it leaves the arcs +unchanged. -/ +@[simp] lemma arcs_loopErase [DecidableEq α] (w : SimpleWalk α) : + w.loopErase.arcs = w.arcs := by + change w.val.loopErase.arcs = w.val.arcs + rw [VertexSeq.loopErase_eq_self_of_nonstalling w.val w.nonstalling] + +/-- Cycle erasure cannot introduce new arcs. -/ +lemma arcs_cycleErase_subset [DecidableEq α] (w : SimpleWalk α) : + w.cycleErase.arcs ⊆ w.arcs := + VertexSeq.arcs_cycleErase_subset w.val + +/-- Dropping the last vertex cannot introduce new arcs. -/ +lemma arcs_dropTail_subset (w : SimpleWalk α) : w.dropTail.arcs ⊆ w.arcs := + VertexSeq.arcs_dropTail_subset w.val + +/-- Dropping the first vertex cannot introduce new arcs. -/ +lemma arcs_dropHead_subset (w : SimpleWalk α) : w.dropHead.arcs ⊆ w.arcs := + VertexSeq.arcs_dropHead_subset w.val + +/-- Taking a prefix cannot introduce new arcs. -/ +lemma arcs_prefixUntil_subset [DecidableEq α] (w : SimpleWalk α) (v : α) + (h : v ∈ w.val) : (w.prefixUntil v h).arcs ⊆ w.arcs := + VertexSeq.arcs_prefixUntil_subset w.val v h + +/-- Taking a suffix cannot introduce new arcs. -/ +lemma arcs_suffixFrom_subset [DecidableEq α] (w : SimpleWalk α) (v : α) + (h : v ∈ w.val) : (w.suffixFrom v h).arcs ⊆ w.arcs := + VertexSeq.arcs_suffixFrom_subset w.val v h + +/-- The source of a traversed arc is a vertex of the walk. -/ +lemma fst_mem_of_mem_arcs {a : α × α} (w : SimpleWalk α) + (ha : a ∈ w.arcs) : a.1 ∈ w.support := + VertexSeq.fst_mem_of_mem_arcs w.val ha + +/-- The target of a traversed arc is a vertex of the walk. -/ +lemma snd_mem_of_mem_arcs {a : α × α} (w : SimpleWalk α) + (ha : a ∈ w.arcs) : a.2 ∈ w.support := + VertexSeq.snd_mem_of_mem_arcs w.val ha /-- View a simple walk as a `SimpleGraph`. The non-stalling property provides the looplessness axiom. -/ @@ -78,7 +368,7 @@ def toSimpleGraph (w : SimpleWalk α) : SimpleGraph α where rcases he with he_old | he_new · exact ih hq.1 he_old · subst he_new - simp [Sym2.isDiag_iff_proj_eq] + simp [Sym2.mk_isDiag_iff] exact hq.2 /-- View a simple walk as a `SimpleDiGraph`. The non-stalling property @@ -113,4 +403,20 @@ def toSimpleDiGraph (w : SimpleWalk α) : SimpleDiGraph α where · exact ih hq.1 ha_old · subst ha_new; exact hq.2 +/-- The edges of `w.toSimpleGraph` are exactly the edges traversed by `w`. -/ +@[simp] lemma mem_edgeSet_toSimpleGraph (w : SimpleWalk α) {e : Sym2 α} : + e ∈ w.toSimpleGraph.edgeSet ↔ e ∈ w.edges := Iff.rfl + +/-- The vertices of `w.toSimpleGraph` are exactly the vertices visited by `w`. -/ +@[simp] lemma mem_vertexSet_toSimpleGraph (w : SimpleWalk α) {v : α} : + v ∈ w.toSimpleGraph.vertexSet ↔ v ∈ w.support := Iff.rfl + +/-- The edges of `w.toSimpleDiGraph` are exactly the arcs traversed by `w`. -/ +@[simp] lemma mem_edgeSet_toSimpleDiGraph (w : SimpleWalk α) {a : α × α} : + a ∈ w.toSimpleDiGraph.edgeSet ↔ a ∈ w.arcs := Iff.rfl + +/-- The vertices of `w.toSimpleDiGraph` are exactly the vertices visited by `w`. -/ +@[simp] lemma mem_vertexSet_toSimpleDiGraph (w : SimpleWalk α) {v : α} : + v ∈ w.toSimpleDiGraph.vertexSet ↔ v ∈ w.support := Iff.rfl + end SimpleWalk diff --git a/GraphLib/Theory/Structures/VertexSeq.lean b/GraphLib/Theory/Structures/VertexSeq.lean index 2016eca..db18db0 100644 --- a/GraphLib/Theory/Structures/VertexSeq.lean +++ b/GraphLib/Theory/Structures/VertexSeq.lean @@ -3,7 +3,14 @@ Copyright (c) 2026 Basil Rohner. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan -/ -import Mathlib.Data.Sym.Sym2 +import GraphLib.Theory.Structures.VertexSeq.Basic +import GraphLib.Theory.Structures.VertexSeq.Predicates +import GraphLib.Theory.Structures.VertexSeq.Append +import GraphLib.Theory.Structures.VertexSeq.MapZip +import GraphLib.Theory.Structures.VertexSeq.Subseq +import GraphLib.Theory.Structures.VertexSeq.Erase +import GraphLib.Theory.Structures.VertexSeq.Edges +import GraphLib.Theory.Structures.VertexSeq.Index /-! # Vertex sequences @@ -12,468 +19,40 @@ A `VertexSeq α` is a non-empty inductive sequence of vertices, with a `singleton` base case and a right-extending `cons`. It is the underlying carrier for walks, paths and cycles in the graph theory library. -## Main definitions - -* `VertexSeq α` — non-empty sequence of vertices. -* `VertexSeq.head`, `VertexSeq.tail` — first and last vertex. -* `VertexSeq.length` — number of *edges* (one less than the vertex count). -* `VertexSeq.toList` — the underlying list of visited vertices. -* `VertexSeq.append`, `VertexSeq.reverse` — concatenation and reversal. -* `VertexSeq.dropHead`, `VertexSeq.dropTail` — drop the first or last vertex. +This file defines nothing itself: it is an *umbrella* module that merely +re-exports (imports) the `VertexSeq` development, which is split across +`GraphLib/Theory/Structures/VertexSeq/`: + +* `Basic` — the carrier, `length`/`head`/`tail`/`toList`, membership/subset, + and `dropHead`/`dropTail`. +* `Predicates` — `nodup`, `nonstalling`, `closed`. +* `Append` — `append`, `reverse` and their laws. +* `MapZip` — `map`, `foldl`, `foldr`, `zip`, `any`, `all` and the `Functor` + instance. +* `Subseq` — `prefixUntil`, `suffixFrom`, `takeWhile`, `dropWhile`, `splitAt`. +* `Erase` — `loopErase`, `cycleErase`. +* `Edges` — `edges`, `arcs` (the traversed edges/arcs, as lists). +* `Index` — `GetElem` and `insert`. + +Downstream files should keep importing this umbrella; the split is internal. + +## Module dependency graph + +Direct imports between the submodules (an arrow `A → B` means "`A` imports +`B`"). The acyclic spine is `Basic ← Predicates ← Append ← Subseq ← Erase ← +Edges`, with `MapZip` branching off `Predicates` and `Index` off `Basic`: +```text + Basic + ╱ ╲ + Predicates Index + ╱ ╲ + Append MapZip + │ + Subseq + │ + Erase + │ + Edges +``` +This umbrella imports all eight submodules. -/ - -variable {α : Type*} - -/-- A type that admits a right-extension operation. -/ -class Snoc (γ δ : Type*) where - /-- Append a single element on the right. -/ - snoc : γ → δ → γ - -scoped[Snoc] infixl:67 " :+ " => Snoc.snoc - -/-- A non-empty inductive sequence of vertices. `cons w v` extends `w` on the -right by appending the vertex `v`. -/ -@[grind] inductive VertexSeq (α : Type*) - | singleton (v : α) : VertexSeq α - | cons (w : VertexSeq α) (v : α) : VertexSeq α - -instance : Snoc (VertexSeq α) α := ⟨VertexSeq.cons⟩ - --- The matching `Snoc (Walk α ε) (α × ε)` instance lives in `Walk.lean`, since --- `Walk` is only defined there (and that file imports this one). - -namespace VertexSeq - -scoped infixl:67 " :+ " => VertexSeq.cons - -/-! ## Basic accessors -/ - -/-- The number of *edges* in the sequence: `0` for a `singleton`, otherwise one -plus the length of the prefix. -/ -@[grind] def length : VertexSeq α → ℕ - | .singleton _ => 0 - | .cons w _ => 1 + w.length - -/-- The first vertex of the sequence. -/ -@[grind] def head : VertexSeq α → α - | .singleton v => v - | .cons w _ => w.head - -/-- The last vertex of the sequence. -/ -@[grind] def tail : VertexSeq α → α - | .singleton v => v - | .cons _ v => v - -/-- The list of vertices visited by the sequence, in order from head to tail. -/ -@[grind] def toList : VertexSeq α → List α - | .singleton v => [v] - | .cons w v => w.toList.concat v - -@[simp, grind =] lemma head_singleton (u : α) : - (VertexSeq.singleton u).head = u := rfl - -@[simp, grind =] lemma head_cons (w : VertexSeq α) (u : α) : - (w.cons u).head = w.head := rfl - -@[simp, grind =] lemma tail_singleton (u : α) : - (VertexSeq.singleton u).tail = u := rfl - -@[simp, grind =] lemma tail_cons (w : VertexSeq α) (u : α) : - (w.cons u).tail = u := rfl - -/-- The `head` belongs to the underlying list of vertices. -/ -@[simp, grind] lemma head_mem (w : VertexSeq α) : w.head ∈ w.toList := by - induction w with - | singleton _ => - simp [head, toList] - | cons w _ ih => - simp [head, toList] - exact Or.inl ih - -/-- The `tail` belongs to the underlying list of vertices. -/ -@[simp, grind] lemma tail_mem (w : VertexSeq α) : w.tail ∈ w.toList := by - cases w with - | singleton _ => simp [tail, toList] - | cons _ _ => simp [tail, toList] - -/-- The underlying list has `length + 1` vertices. -/ -lemma length_toList (w : VertexSeq α) : w.toList.length = w.length + 1 := by - induction w <;> grind - -/-- `toList` is injective. -/ -@[grind] theorem toList_injective : Function.Injective (@toList α) := by - intro x y hxy - induction x generalizing y with - | singleton v => - cases y with - | singleton w => simpa [toList] using hxy - | cons w u => - exfalso - simp only [toList] at hxy - have hlen := congrArg List.length hxy - simp only [List.length_singleton, List.length_concat] at hlen - have := length_toList w - omega - | cons w v ih => - cases y with - | singleton w' => - exfalso - simp only [toList] at hxy - have hlen := congrArg List.length hxy - simp only [List.length_singleton, List.length_concat] at hlen - have := length_toList w - omega - | cons w' v' => - simp only [toList, List.concat_eq_append] at hxy - have hlen : w.toList.length = w'.toList.length := by - have hl := congrArg List.length hxy - simp only [List.length_append, List.length_singleton] at hl - omega - obtain ⟨hw, hv⟩ := List.append_inj hxy hlen - obtain rfl := ih hw - simp at hv - exact congrArg _ hv - -/-! ## Membership -/ - -instance : Membership α (VertexSeq α) := ⟨fun w v ↦ v ∈ w.toList⟩ - -@[simp, grind] theorem mem_def {v : α} (w : VertexSeq α) : - v ∈ w ↔ v ∈ w.toList := Iff.rfl - -@[simp] lemma mem_cons (v u : α) (w : VertexSeq α) : - v ∈ w :+ u ↔ v ∈ w ∨ v = u := by - grind - -instance [DecidableEq α] (v : α) (w : VertexSeq α) : Decidable (v ∈ w) := - inferInstanceAs (Decidable (v ∈ w.toList)) - -instance : HasSubset (VertexSeq α) := ⟨fun w1 w2 ↦ ∀ v ∈ w1, v ∈ w2⟩ - -@[simp, grind] theorem subset_def {w1 w2 : VertexSeq α} : - w1 ⊆ w2 ↔ ∀ v ∈ w1, v ∈ w2 := Iff.rfl - -/-! ## dropHead, dropTail -/ - -/-- Drop the first vertex of the sequence (returns the sequence unchanged when -it is a singleton). -/ -@[grind] def dropHead : VertexSeq α → VertexSeq α - | .singleton v => .singleton v - | .cons (.singleton _) v => .singleton v - | .cons w v => w.dropHead :+ v - -/-- Drop the last vertex of the sequence (returns the sequence unchanged when -it is a singleton). -/ -@[grind] def dropTail : VertexSeq α → VertexSeq α - | .singleton v => .singleton v - | .cons w _ => w - -/-! ## append, reverse, and their laws -/ - -/-- Concatenate two sequences. The joining vertices `p.tail` and `q.head` are -*both* preserved. If they are equal the duplicate is intentional (the caller -may drop it with `dropTail`). -/ -@[grind] def append : VertexSeq α → VertexSeq α → VertexSeq α - | w, .singleton v => .cons w v - | w, .cons u v => .cons (append w u) v - -instance : Append (VertexSeq α) := ⟨VertexSeq.append⟩ - -@[simp] lemma mem_append (v : α) (w1 w2 : VertexSeq α) : - v ∈ w1 ++ w2 ↔ v ∈ w1 ∨ v ∈ w2 := by - fun_induction append w1 w2 <;> expose_names - · constructor - · intro h - sorry - · sorry - · sorry - -/-- Reverse a sequence: the head becomes the tail and vice versa. -/ -@[grind] def reverse : VertexSeq α → VertexSeq α - | .singleton v => .singleton v - | .cons w v => append (.singleton v) (reverse w) - -@[simp] lemma mem_reverse (v : α) (w : VertexSeq α) : - v ∈ w ↔ v ∈ w.reverse := by - fun_induction reverse w <;> sorry - -/-- Length of an append is the sum of lengths plus one (for the duplicated -joining vertex contributing an extra edge). -/ -@[simp, grind =] lemma length_append (p q : VertexSeq α) : - (p.append q).length = p.length + q.length + 1 := by - fun_induction append p q <;> grind - -/-- `tail` of an append is the tail of the right operand. -/ -@[simp, grind =] lemma tail_append (p q : VertexSeq α) : - (p.append q).tail = q.tail := by - fun_induction append <;> simp_all [tail] - -/-- `head` of an append is the head of the left operand. -/ -@[simp, grind =] lemma head_append (p q : VertexSeq α) : - (p.append q).head = p.head := by - fun_induction append <;> simp_all [head] - -/-- Appending a singleton on the right yields `x` as the new tail. -/ -@[simp, grind =] lemma tail_append_singleton (p : VertexSeq α) (x : α) : - (p.append (.singleton x)).tail = x := by - grind - -/-- Appending on the left of a singleton makes `x` the new head. -/ -@[simp, grind =] lemma head_singleton_append (p : VertexSeq α) (x : α) : - ((VertexSeq.singleton x).append p).head = x := by - grind - -/-- `append` is associative. -/ -@[simp, grind =] lemma append_assoc (p q r : VertexSeq α) : - (p.append q).append r = p.append (q.append r) := by - fun_induction append q r <;> simp_all [append] - -/-- Reversing a singleton leaves it unchanged. -/ -@[grind =] lemma reverse_singleton (v : α) : - (VertexSeq.singleton v).reverse = .singleton v := rfl - -/-- Reverse distributes over `append`, swapping the order of operands. -/ -@[simp, grind =] lemma reverse_append (p q : VertexSeq α) : - (p.append q).reverse = q.reverse.append p.reverse := by - fun_induction append <;> simp_all [reverse] - -/-- `reverse` is an involution. -/ -@[simp, grind =] lemma reverse_reverse (p : VertexSeq α) : - p.reverse.reverse = p := by - fun_induction reverse p <;> grind - -/-- The head of the reverse is the tail of the original. -/ -@[simp, grind =] lemma head_reverse (p : VertexSeq α) : - p.reverse.head = p.tail := by - fun_induction reverse p <;> grind - -/-- The tail of the reverse is the head of the original. -/ -@[simp, grind =] lemma tail_reverse (p : VertexSeq α) : - p.reverse.tail = p.head := by - fun_induction reverse p <;> grind - -/-- Dropping the tail does not affect the head. -/ -@[simp, grind =] lemma head_dropTail (p : VertexSeq α) : - p.dropTail.head = p.head := by - cases p <;> grind - -/-! ## prefixUntil, suffixFrom, loopErase -/ - -/-- The prefix of `w` ending at the first vertex satisfying the predicate `p`, -inclusive of that vertex. The hypothesis guarantees such a vertex exists. -/ -@[grind] def prefixUntil [DecidableEq α] (w : VertexSeq α) (v : α) - (h : v ∈ w) : VertexSeq α := - match w with - | .singleton x => .singleton x - | .cons w2 x => - if h2 : v ∈ w2 then prefixUntil w2 v h2 - else w2 :+ x - -/-- The suffix of `w` starting at the first vertex satisfying the predicate -`p`, inclusive of that vertex. The hypothesis guarantees such a vertex -exists. -/ -@[grind] def suffixFrom [DecidableEq α] (w : VertexSeq α) (v : α) - (h : v ∈ w) : VertexSeq α := - match w with - | .singleton x => .singleton x - | .cons w2 x => - if h2 : v ∈ w2 then .cons (suffixFrom w2 v h2) x - else .singleton x - -@[simp] lemma length_prefixUntil_le [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.prefixUntil v h).length ≤ w.length := by - fun_induction prefixUntil w v h <;> grind - -@[simp] lemma length_suffixFrom_le [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.suffixFrom v h).length ≤ w.length := by - fun_induction suffixFrom w v h <;> grind - -@[simp] lemma head_prefixUntil [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.prefixUntil v h).head = w.head := by - fun_induction prefixUntil w v h <;> grind - -@[simp] lemma tail_prefixUntil [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.prefixUntil v h).tail = v := by - fun_induction prefixUntil w v h <;> grind - -@[simp] lemma prefixUntil_subset [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.prefixUntil v h) ⊆ w := by - fun_induction prefixUntil <;> grind - -@[simp] lemma head_suffixFrom [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.suffixFrom v h).head = v := by - fun_induction suffixFrom w v h <;> grind - -@[simp] lemma tail_suffixFrom [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.suffixFrom v h).tail = w.tail := by - fun_induction suffixFrom w v h <;> grind - -/-- todo: polish -/ -@[simp] lemma suffixFrom_subset [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.suffixFrom v h) ⊆ w := by - fun_induction suffixFrom w v h - · grind - · expose_names - intro u hu - apply (mem_cons u x (w2.suffixFrom v h_1)).1 at hu - cases hu - · expose_names - grind - · grind - grind - -/- # Sub-VertexSeq -/ - -/- # Map, Foldl, Foldr, Zip, All, Any, nodup, nostall -/ - -def map {β : Type*} (f : α → β) : VertexSeq α → VertexSeq β - | .singleton v => singleton (f v) - | .cons w v => w.map f :+ f v - -def foldl {β : Type*} (f : β → α → β) (b : β) : VertexSeq α → β - | .singleton v => f b v - | .cons w v => f (w.foldl f b) v - -def foldr {β : Type*} (f : α → β → β) (b : β) : VertexSeq α → β - | .singleton v => f v b - | .cons w v => w.foldr f (f v b) - -def zip {β : Type*} : VertexSeq α → VertexSeq β → VertexSeq (α × β) - | .singleton v, .singleton u => .singleton (v, u) - | .singleton v, .cons _ u => .singleton (v, u) - | .cons _ v, .singleton u => .singleton (v, u) - | .cons w v, .cons y u => .cons (w.zip y) (v, u) - -def any (p : α → Prop) : VertexSeq α → Prop - | .singleton v => p v - | .cons w v => w.any p ∨ p v - -def all (p : α → Prop) : VertexSeq α → Prop - | .singleton v => p v - | .cons w v => w.all p ∧ p v - -/-- The sequence has no repeated vertex. -/ -def nodup : VertexSeq α → Prop - | .singleton _ => True - | .cons w v => w.nodup ∧ v ∉ w - -/-- The sequence never stalls: no two consecutive vertices are equal. -/ -def nonstalling : VertexSeq α → Prop - | .singleton _ => True - | .cons w v => w.nonstalling ∧ w.tail ≠ v - -/-- A vertex sequence is *closed* when its first and last vertex coincide. -/ -def closed (w : VertexSeq α) : Prop := w.head = w.tail - -@[grind] lemma nodup_nonstalling [DecidableEq α] (w : VertexSeq α) : - w.nodup → w.nonstalling := by - sorry - -/-! ## takeWhile, dropWhile -/ - -/-- Take every vertex of `w` satisfying `p`, plus the first failure (if any). -If every vertex satisfies `p`, the whole sequence is returned. -/ -@[grind] def takeWhile (w : VertexSeq α) (p : α → Prop) [DecidablePred p] : - VertexSeq α := - match w with - | .singleton x => .singleton x - | .cons q x => - if ∃ v ∈ q.toList, ¬ p v then takeWhile q p - else q :+ x - -/-- Drop the longest prefix of `w` on which `p` holds; the result starts at -the first failure. The hypothesis ensures a failure exists. -/ -@[grind] def dropWhile (w : VertexSeq α) (p : α → Prop) [DecidablePred p] - (h : ∃ v ∈ w.toList, ¬ p v) : VertexSeq α := - match w with - | .singleton x => .singleton x - | .cons q x => - if hq : ∃ v ∈ q.toList, ¬ p v then (dropWhile q p hq) :+ x - else .singleton x - -/-! ## splitAt -/ - -/-- Split `w` into a list of pieces at every occurrence of the vertex `v`. -The split point `v` is *duplicated*: it appears as the tail of one piece and -the head of the next, so that re-concatenating the pieces recovers `w`. -/ -@[grind] def splitAt [DecidableEq α] : VertexSeq α → α → List (VertexSeq α) - | .singleton x, _ => [.singleton x] - | .cons q x, v => - if x = v then appendToLast (splitAt q v) v ++ [.singleton v] - else appendToLast (splitAt q v) x -where - appendToLast : List (VertexSeq α) → α → List (VertexSeq α) - | [], _ => [] - | [w], x => [w :+ x] - | p :: ps, x => p :: appendToLast ps x - -/-! ## Indexing and insertion -/ - -instance : GetElem (VertexSeq α) ℕ α (fun w i ↦ i < w.toList.length) where - getElem w i h := w.toList[i] - -/-- Insert the vertex `v` at index `i`, shifting later vertices one position -to the right. If `i` exceeds `w.toList.length`, `v` is appended at the end. -/ -@[grind] def insert : VertexSeq α → ℕ → α → VertexSeq α - | .singleton x, 0, v => .cons (.singleton v) x - | .singleton x, _ + 1, v => .cons (.singleton x) v - | .cons q x, i, v => - if i ≤ q.length then .cons (insert q i v) x - else if i = q.length + 1 then .cons (q :+ v) x - else .cons (q :+ x) v - -/- # Structural Manipulations -/ - - -/-- Remove immediate stalls (consecutive duplicate vertices). The result -satisfies `nonstalling`. -/ -@[grind] def loopErase [DecidableEq α] : VertexSeq α → VertexSeq α - | .singleton v => .singleton v - | .cons w v => - if w.tail = v then loopErase w - else .cons (loopErase w) v - -@[grind] lemma loopErase_nonstalling [DecidableEq α] (w : VertexSeq α) : - w.loopErase.nonstalling := by - sorry - -/-- Cycle erasure: whenever a vertex repeats, drop the intermediate detour -between its two occurrences. The result satisfies `nodup`. -/ -@[grind] def cycleErase [DecidableEq α] : VertexSeq α → VertexSeq α - | .singleton v => .singleton v - | .cons w v => - if h : v ∈ w then - cycleErase (prefixUntil w v h) - else - .cons (cycleErase w) v - termination_by p => p.length - decreasing_by - · simp [length] - grind [length_prefixUntil_le] - · simp [length] - -@[grind] lemma cycleErase_nodup [DecidableEq α] (w : VertexSeq α) : - w.cycleErase.nodup := by - sorry - -/-! ## Functor instance -/ - -instance : Functor VertexSeq where map := VertexSeq.map - -@[simp] lemma map_id (w : VertexSeq α) : w.map id = w := by - induction w with - | singleton _ => rfl - | cons w v ih => simp [map, ih] - -lemma map_comp {β γ : Type*} (f : α → β) (g : β → γ) (w : VertexSeq α) : - w.map (g ∘ f) = (w.map f).map g := by - induction w with - | singleton _ => rfl - | cons w v ih => simp [map, ih] - -instance : LawfulFunctor VertexSeq where - map_const := rfl - id_map := map_id - comp_map f g w := map_comp f g w - -end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Append.lean b/GraphLib/Theory/Structures/VertexSeq/Append.lean new file mode 100644 index 0000000..63a6bdb --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/Append.lean @@ -0,0 +1,204 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.VertexSeq.Predicates + +/-! +# Vertex sequences: append and reverse + +Concatenation and reversal of vertex sequences, with their head/tail/length +laws, associativity and involutivity, and the preservation of `nodup` and +`nonstalling`. + +## Main definitions + +* `VertexSeq.append` — concatenation, keeping both joining vertices. +* `VertexSeq.reverse` — reversal, swapping head and tail. +-/ + +variable {α : Type*} + +namespace VertexSeq + +/-! ## append, reverse, and their laws -/ + +/-- Concatenate two sequences. The joining vertices `p.tail` and `q.head` are +*both* preserved. If they are equal the duplicate is intentional (the caller +may drop it with `dropTail`). -/ +@[grind] def append : VertexSeq α → VertexSeq α → VertexSeq α + | w, .singleton v => .cons w v + | w, .cons u v => .cons (append w u) v + +instance : Append (VertexSeq α) := ⟨VertexSeq.append⟩ + +@[simp, grind =] lemma toList_append (p q : VertexSeq α) : + (p.append q).toList = p.toList ++ q.toList := by + induction q generalizing p with + | singleton v => + simp [append, toList, List.concat_eq_append] + | cons q v ih => + simp [append, toList, ih, List.concat_eq_append, List.append_assoc] + +@[simp] lemma mem_append (v : α) (w1 w2 : VertexSeq α) : + v ∈ w1 ++ w2 ↔ v ∈ w1 ∨ v ∈ w2 := by + change v ∈ (w1.append w2).toList ↔ v ∈ w1.toList ∨ v ∈ w2.toList + rw [toList_append, List.mem_append] + +/-- Reverse a sequence: the head becomes the tail and vice versa. -/ +@[grind] def reverse : VertexSeq α → VertexSeq α + | .singleton v => .singleton v + | .cons w v => append (.singleton v) (reverse w) + +@[simp] lemma mem_reverse (v : α) (w : VertexSeq α) : + v ∈ w ↔ v ∈ w.reverse := by + induction w with + | singleton _ => simp [reverse] + | cons w x ih => + have ih' : v ∈ w.toList ↔ v ∈ w.reverse.toList := by + simpa [mem_def] using ih + simp [reverse, toList_append, toList, List.concat_eq_append, ih', + or_comm] + +/-- Length of an append is the sum of lengths plus one (for the duplicated +joining vertex contributing an extra edge). -/ +@[simp, grind =] lemma length_append (p q : VertexSeq α) : + (p.append q).length = p.length + q.length + 1 := by + fun_induction append p q <;> grind + +/-- `tail` of an append is the tail of the right operand. -/ +@[simp, grind =] lemma tail_append (p q : VertexSeq α) : + (p.append q).tail = q.tail := by + fun_induction append <;> simp_all [tail] + +/-- `head` of an append is the head of the left operand. -/ +@[simp, grind =] lemma head_append (p q : VertexSeq α) : + (p.append q).head = p.head := by + fun_induction append <;> simp_all [head] + +/-- Appending a singleton on the right yields `x` as the new tail. -/ +@[simp, grind =] lemma tail_append_singleton (p : VertexSeq α) (x : α) : + (p.append (.singleton x)).tail = x := by + grind + +/-- Appending on the left of a singleton makes `x` the new head. -/ +@[simp, grind =] lemma head_singleton_append (p : VertexSeq α) (x : α) : + ((VertexSeq.singleton x).append p).head = x := by + grind + +/-- `append` is associative. -/ +@[simp, grind =] lemma append_assoc (p q r : VertexSeq α) : + (p.append q).append r = p.append (q.append r) := by + fun_induction append q r <;> simp_all [append] + +/-- Reversing a singleton leaves it unchanged. -/ +@[grind =] lemma reverse_singleton (v : α) : + (VertexSeq.singleton v).reverse = .singleton v := rfl + +/-- Reverse distributes over `append`, swapping the order of operands. -/ +@[simp, grind =] lemma reverse_append (p q : VertexSeq α) : + (p.append q).reverse = q.reverse.append p.reverse := by + fun_induction append <;> simp_all [reverse] + +/-- `reverse` is an involution. -/ +@[simp, grind =] lemma reverse_reverse (p : VertexSeq α) : + p.reverse.reverse = p := by + fun_induction reverse p <;> grind + +/-- The head of the reverse is the tail of the original. -/ +@[simp, grind =] lemma head_reverse (p : VertexSeq α) : + p.reverse.head = p.tail := by + fun_induction reverse p <;> grind + +/-- The tail of the reverse is the head of the original. -/ +@[simp, grind =] lemma tail_reverse (p : VertexSeq α) : + p.reverse.tail = p.head := by + fun_induction reverse p <;> grind + +/-! ## Nodup and non-stalling preservation -/ + +/-- Appending disjoint `nodup` sequences preserves `nodup`. -/ +@[grind] lemma nodup_append (p q : VertexSeq α) (hp : p.nodup) (hq : q.nodup) + (hdisj : ∀ v : α, v ∈ p → v ∈ q → False) : (p.append q).nodup := by + induction q with + | singleton v => + simp [append, nodup] + exact ⟨hp, fun hv => hdisj v hv (by grind)⟩ + | cons q v ih => + simp [append, nodup] at hq ⊢ + refine ⟨ih hq.1 (fun x hx hq' => hdisj x hx (by grind)), ?_⟩ + constructor + · intro hpv + exact hdisj v (by simpa [mem_def] using hpv) (by grind) + · exact hq.2 + +/-- Reversal preserves `nodup`. -/ +@[grind] lemma nodup_reverse (w : VertexSeq α) (h : w.nodup) : + w.reverse.nodup := by + fun_induction reverse w <;> grind [nodup_append, mem_reverse] + +/-- An `append` is non-stalling exactly when both operands are and the joining +vertices differ. -/ +@[grind] lemma nonstalling_append (p q : VertexSeq α) : + (p.append q).nonstalling ↔ + p.nonstalling ∧ q.nonstalling ∧ p.tail ≠ q.head := by + fun_induction append p q <;> grind + +/-- Reversal preserves non-stalling. -/ +@[grind] lemma nonstalling_reverse (w : VertexSeq α) : + w.reverse.nonstalling ↔ w.nonstalling := by + fun_induction reverse w <;> grind + +/-! ## Interaction of dropTail with append and reverse -/ + +/-- Reversal preserves length. -/ +@[simp, grind =] lemma length_reverse (w : VertexSeq α) : + w.reverse.length = w.length := by + fun_induction reverse w <;> grind + +/-- For a non-trivial right operand, dropping the tail of an append drops the +tail of that operand. -/ +@[simp, grind =] lemma dropTail_append_of_length_ne_zero (p q : VertexSeq α) + (hq : q.length ≠ 0) : + (p.append q).dropTail = p.append q.dropTail := by + cases q with + | singleton v => + simp [length] at hq + | cons q v => + rfl + +/-- Dropping the tail of a reverse is reversing the `dropHead`. -/ +@[simp, grind =] lemma dropTail_reverse (w : VertexSeq α) : + w.reverse.dropTail = w.dropHead.reverse := by + induction w with + | singleton v => + rfl + | cons w v ih => + cases w with + | singleton u => + rfl + | cons t u => + rw [reverse] + rw [dropTail_append_of_length_ne_zero] + · exact congrArg (fun x => (singleton v).append x) (by simpa [reverse] using ih) + · simp [length_reverse, length] + +/-- The reversed interior of a cycle (drop the repeated tail) is `nodup`. -/ +@[grind] lemma nodup_reverse_dropTail_of_cycle (w : VertexSeq α) + (hclosed : w.closed) (hnodup : w.dropTail.nodup) : + w.reverse.dropTail.nodup := by + rw [dropTail_reverse] + exact nodup_reverse w.dropHead (nodup_dropHead_of_closed_dropTail w hclosed hnodup) + +/-- `nodup` of an append is symmetric in its operands. -/ +lemma nodup_append_comm (p q : VertexSeq α) (h : (p.append q).nodup) : + (q.append p).nodup := by + rw [nodup_iff_toList_nodup] + rw [toList_append] + have hlist : (p.toList ++ q.toList).Nodup := by + simpa [toList_append] using (nodup_iff_toList_nodup (p.append q)).1 h + rw [List.nodup_append] at hlist ⊢ + aesop + +end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Basic.lean b/GraphLib/Theory/Structures/VertexSeq/Basic.lean new file mode 100644 index 0000000..4bc927b --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/Basic.lean @@ -0,0 +1,207 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import Mathlib.Data.List.Basic + +/-! +# Vertex sequences: carrier and basic accessors + +A `VertexSeq α` is a non-empty inductive sequence of vertices, with a +`singleton` base case and a right-extending `cons`. It is the underlying +carrier for walks, paths and cycles in the graph theory library. + +This file sets up the carrier together with its most primitive structure: the +`length`/`head`/`tail`/`toList` accessors, the `Membership` and `HasSubset` +instances, and the endpoint-dropping operations `dropHead`/`dropTail`. + +## Main definitions + +* `VertexSeq α` — non-empty sequence of vertices. +* `VertexSeq.head`, `VertexSeq.tail` — first and last vertex. +* `VertexSeq.length` — number of *edges* (one less than the vertex count). +* `VertexSeq.toList` — the underlying list of visited vertices. +* `VertexSeq.dropHead`, `VertexSeq.dropTail` — drop the first or last vertex. +-/ + +variable {α : Type*} + +/-- A type that admits a right-extension operation. -/ +class Snoc (γ δ : Type*) where + /-- Append a single element on the right. -/ + snoc : γ → δ → γ + +scoped[Snoc] infixl:67 " :+ " => Snoc.snoc + +/-- A non-empty inductive sequence of vertices. `cons w v` extends `w` on the +right by appending the vertex `v`. -/ +@[grind] inductive VertexSeq (α : Type*) + | singleton (v : α) : VertexSeq α + | cons (w : VertexSeq α) (v : α) : VertexSeq α + +instance : Snoc (VertexSeq α) α := ⟨VertexSeq.cons⟩ + +-- The matching `Snoc (Walk α ε) (α × ε)` instance lives in `Walk.lean`, since +-- `Walk` is only defined there (and that file imports this one). + +namespace VertexSeq + +scoped infixl:67 " :+ " => VertexSeq.cons + +/-! ## Basic accessors -/ + +/-- The number of *edges* in the sequence: `0` for a `singleton`, otherwise one +plus the length of the prefix. -/ +@[grind] def length : VertexSeq α → ℕ + | .singleton _ => 0 + | .cons w _ => 1 + w.length + +/-- The first vertex of the sequence. -/ +@[grind] def head : VertexSeq α → α + | .singleton v => v + | .cons w _ => w.head + +/-- The last vertex of the sequence. -/ +@[grind] def tail : VertexSeq α → α + | .singleton v => v + | .cons _ v => v + +/-- The list of vertices visited by the sequence, in order from head to tail. -/ +@[grind] def toList : VertexSeq α → List α + | .singleton v => [v] + | .cons w v => w.toList.concat v + +@[simp, grind =] lemma head_singleton (u : α) : + (VertexSeq.singleton u).head = u := rfl + +@[simp, grind =] lemma head_cons (w : VertexSeq α) (u : α) : + (w.cons u).head = w.head := rfl + +@[simp, grind =] lemma tail_singleton (u : α) : + (VertexSeq.singleton u).tail = u := rfl + +@[simp, grind =] lemma tail_cons (w : VertexSeq α) (u : α) : + (w.cons u).tail = u := rfl + +/-- The `head` belongs to the underlying list of vertices. -/ +@[simp, grind] lemma head_mem (w : VertexSeq α) : w.head ∈ w.toList := by + induction w with + | singleton _ => + simp [head, toList] + | cons w _ ih => + simp [head, toList] + exact Or.inl ih + +/-- The `tail` belongs to the underlying list of vertices. -/ +@[simp, grind] lemma tail_mem (w : VertexSeq α) : w.tail ∈ w.toList := by + cases w with + | singleton _ => simp [tail, toList] + | cons _ _ => simp [tail, toList] + +/-- The underlying list has `length + 1` vertices. -/ +lemma length_toList (w : VertexSeq α) : w.toList.length = w.length + 1 := by + induction w <;> grind + +/-- `toList` is injective. -/ +@[grind] theorem toList_injective : Function.Injective (@toList α) := by + intro x y hxy + induction x generalizing y with + | singleton v => + cases y with + | singleton w => simpa [toList] using hxy + | cons w u => + exfalso + simp only [toList] at hxy + have hlen := congrArg List.length hxy + simp only [List.length_singleton, List.length_concat] at hlen + have := length_toList w + omega + | cons w v ih => + cases y with + | singleton w' => + exfalso + simp only [toList] at hxy + have hlen := congrArg List.length hxy + simp only [List.length_singleton, List.length_concat] at hlen + have := length_toList w + omega + | cons w' v' => + simp only [toList, List.concat_eq_append] at hxy + have hlen : w.toList.length = w'.toList.length := by + have hl := congrArg List.length hxy + simp only [List.length_append, List.length_singleton] at hl + omega + obtain ⟨hw, hv⟩ := List.append_inj hxy hlen + obtain rfl := ih hw + simp at hv + exact congrArg _ hv + +/-! ## Membership -/ + +instance : Membership α (VertexSeq α) := ⟨fun w v ↦ v ∈ w.toList⟩ + +@[simp, grind] theorem mem_def {v : α} (w : VertexSeq α) : + v ∈ w ↔ v ∈ w.toList := Iff.rfl + +@[simp] lemma mem_cons (v u : α) (w : VertexSeq α) : + v ∈ w :+ u ↔ v ∈ w ∨ v = u := by + grind + +instance [DecidableEq α] (v : α) (w : VertexSeq α) : Decidable (v ∈ w) := + inferInstanceAs (Decidable (v ∈ w.toList)) + +instance : HasSubset (VertexSeq α) := ⟨fun w1 w2 ↦ ∀ v ∈ w1, v ∈ w2⟩ + +@[simp, grind] theorem subset_def {w1 w2 : VertexSeq α} : + w1 ⊆ w2 ↔ ∀ v ∈ w1, v ∈ w2 := Iff.rfl + +/-! ## dropHead, dropTail -/ + +/-- Drop the first vertex of the sequence (returns the sequence unchanged when +it is a singleton). -/ +@[grind] def dropHead : VertexSeq α → VertexSeq α + | .singleton v => .singleton v + | .cons (.singleton _) v => .singleton v + | .cons w v => w.dropHead :+ v + +/-- Drop the last vertex of the sequence (returns the sequence unchanged when +it is a singleton). -/ +@[grind] def dropTail : VertexSeq α → VertexSeq α + | .singleton v => .singleton v + | .cons w _ => w + +/-- Dropping the tail does not affect the head. -/ +@[simp, grind =] lemma head_dropTail (p : VertexSeq α) : + p.dropTail.head = p.head := by + cases p <;> grind + +/-- Dropping the first vertex does not affect the tail. -/ +@[simp, grind =] lemma tail_dropHead (w : VertexSeq α) : + w.dropHead.tail = w.tail := by + fun_induction dropHead w <;> grind + +/-- Dropping the tail of a `cons` recovers the prefix length. -/ +@[simp, grind =] lemma length_dropTail_cons (w : VertexSeq α) (v : α) : + (w :+ v).dropTail.length = w.length := rfl + +/-- A length-zero sequence is a single vertex, so its head and tail agree. -/ +@[simp, grind =] lemma head_eq_tail_of_length_zero (w : VertexSeq α) + (h : w.length = 0) : w.head = w.tail := by + cases w with + | singleton v => + rfl + | cons w v => + simp [length] at h + +/-- For a non-trivial sequence, dropping the tail drops exactly one edge. -/ +@[simp, grind =] lemma dropTail_length_succ (w : VertexSeq α) (h : w.length ≠ 0) : + w.dropTail.length + 1 = w.length := by + cases w with + | singleton v => + exact (h rfl).elim + | cons w v => + simp [length, dropTail] + omega + +end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Edges.lean b/GraphLib/Theory/Structures/VertexSeq/Edges.lean new file mode 100644 index 0000000..dcf6912 --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/Edges.lean @@ -0,0 +1,322 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import Mathlib.Data.Sym.Sym2 +import GraphLib.Theory.Structures.VertexSeq.Erase + +/-! +# Vertex sequences: traversed edges + +The edges and arcs traversed by a vertex sequence: the consecutive vertex pairs +of each `cons` step, viewed either as unordered `Sym2 α` edges or as ordered +`α × α` arcs. + +These are purely combinatorial — they mention no graph. They are kept as lists, +mirroring how the vertex side uses `toList` rather than a `Set`: order and +multiplicity are retained (needed for trails/Eulerian walks), and a graph's +`edgeSet : Set (Sym2 α)` is reached on demand via `e ∈ w.edges` (see +`SimpleGraph.IsVertexSeqIn`). + +## Main definitions + +* `VertexSeq.edges` — the list of unordered edges traversed. +* `VertexSeq.arcs` — the list of directed arcs traversed. +-/ + +variable {α : Type*} + +namespace VertexSeq + +/-- The unordered edges traversed by a vertex sequence (the consecutive +pairs `s(w.tail, v)` for each `cons` step). -/ +@[grind] def edges : VertexSeq α → List (Sym2 α) + | .singleton _ => [] + | .cons w v => w.edges.concat s(w.tail, v) + +/-- The directed arcs traversed by a vertex sequence (the consecutive ordered +pairs `(w.tail, v)` for each `cons` step). -/ +@[grind] def arcs : VertexSeq α → List (α × α) + | .singleton _ => [] + | .cons w v => w.arcs.concat (w.tail, v) + +/-! ## Basic computation rules -/ + +@[simp, grind =] lemma edges_singleton (v : α) : (singleton v).edges = [] := rfl + +@[simp, grind =] lemma edges_cons (w : VertexSeq α) (v : α) : + (w :+ v).edges = w.edges.concat s(w.tail, v) := rfl + +/-- Membership in the edges of a `cons`: an old edge or the new last edge. -/ +@[simp] lemma mem_edges_cons {e : Sym2 α} (w : VertexSeq α) (v : α) : + e ∈ (w :+ v).edges ↔ e ∈ w.edges ∨ e = s(w.tail, v) := by + simp [edges, List.concat_eq_append] + +@[simp, grind =] lemma arcs_singleton (v : α) : (singleton v).arcs = [] := rfl + +@[simp, grind =] lemma arcs_cons (w : VertexSeq α) (v : α) : + (w :+ v).arcs = w.arcs.concat (w.tail, v) := rfl + +/-- Membership in the arcs of a `cons`: an old arc or the new last arc. -/ +@[simp] lemma mem_arcs_cons {a : α × α} (w : VertexSeq α) (v : α) : + a ∈ (w :+ v).arcs ↔ a ∈ w.arcs ∨ a = (w.tail, v) := by + simp [arcs, List.concat_eq_append] + +/-! ## Length and last edge -/ + +/-- The number of traversed edges equals `length` (one less than the number of +vertices). -/ +@[simp, grind =] lemma length_edges (w : VertexSeq α) : + w.edges.length = w.length := by + induction w with + | singleton v => rfl + | cons w v ih => simp only [edges, length, List.length_concat, ih]; omega + +/-- A length-zero sequence (a single vertex) traverses no edges. -/ +@[simp, grind =] lemma edges_eq_nil_of_length_eq_zero (w : VertexSeq α) + (h : w.length = 0) : w.edges = [] := by + cases w with + | singleton v => rfl + | cons w v => simp [length] at h + +/-- For a non-trivial sequence, the traversed edges are those of the +dropped-tail sequence plus the final edge. -/ +@[grind →] lemma edges_eq_dropTail_concat_of_length_ne_zero (w : VertexSeq α) + (h : w.length ≠ 0) : + w.edges = w.dropTail.edges.concat s(w.dropTail.tail, w.tail) := by + cases w with + | singleton v => exact (h rfl).elim + | cons w v => rfl + +/-- The number of traversed arcs equals `length` (one less than the number of +vertices). -/ +@[simp, grind =] lemma length_arcs (w : VertexSeq α) : + w.arcs.length = w.length := by + induction w with + | singleton v => rfl + | cons w v ih => simp only [arcs, length, List.length_concat, ih]; omega + +/-- A length-zero sequence (a single vertex) traverses no arcs. -/ +@[simp, grind =] lemma arcs_eq_nil_of_length_eq_zero (w : VertexSeq α) + (h : w.length = 0) : w.arcs = [] := by + cases w with + | singleton v => rfl + | cons w v => simp [length] at h + +/-- For a non-trivial sequence, the traversed arcs are those of the +dropped-tail sequence plus the final arc. -/ +@[grind →] lemma arcs_eq_dropTail_concat_of_length_ne_zero (w : VertexSeq α) + (h : w.length ≠ 0) : + w.arcs = w.dropTail.arcs.concat (w.dropTail.tail, w.tail) := by + cases w with + | singleton v => exact (h rfl).elim + | cons w v => rfl + +/-! ## Endpoints -/ + +/-- Any endpoint of a traversed edge is a vertex of the sequence. -/ +@[grind →] lemma mem_of_mem_edges {e : Sym2 α} {x : α} (w : VertexSeq α) + (he : e ∈ w.edges) (hx : x ∈ e) : x ∈ w := by + induction w with + | singleton v => simp [edges] at he + | cons w v ih => grind [mem_edges_cons, mem_cons, tail_mem] + +/-- The source of a traversed arc is a vertex of the sequence. -/ +@[grind →] lemma fst_mem_of_mem_arcs {a : α × α} (w : VertexSeq α) + (ha : a ∈ w.arcs) : a.1 ∈ w := by + induction w with + | singleton v => simp [arcs] at ha + | cons w v ih => grind [mem_arcs_cons, mem_cons, tail_mem] + +/-- The target of a traversed arc is a vertex of the sequence. -/ +@[grind →] lemma snd_mem_of_mem_arcs {a : α × α} (w : VertexSeq α) + (ha : a ∈ w.arcs) : a.2 ∈ w := by + induction w with + | singleton v => simp [arcs] at ha + | cons w v ih => grind [mem_arcs_cons, mem_cons, tail_mem] + +/-! ## Nodup of edges -/ + +/-- A duplicate-free sequence traverses each edge at most once (a path is a +trail). -/ +@[grind] lemma nodup_edges_of_nodup (w : VertexSeq α) (h : w.nodup) : + w.edges.Nodup := by + induction w with + | singleton v => simp [edges] + | cons w v ih => + obtain ⟨hw, hv⟩ := h + rw [edges_cons, List.concat_eq_append, List.nodup_append] + refine ⟨ih hw, by simp, ?_⟩ + intro a ha b hb hab + simp only [List.mem_singleton] at hb + subst hb; subst hab + exact hv (mem_of_mem_edges w ha (by simp)) + +/-- The only way the edge between a duplicate-free sequence's two endpoints can +be traversed is if the sequence is a single edge: otherwise the endpoints are +not consecutive. Used to show a cycle's closing edge is fresh. -/ +@[grind →] lemma length_le_one_of_mem_edges_head_tail (w : VertexSeq α) + (h : w.nodup) (he : s(w.head, w.tail) ∈ w.edges) : w.length ≤ 1 := by + cases w with + | singleton v => simp [edges] at he + | cons w v => + obtain ⟨hw, hv⟩ := h + simp only [head_cons, tail_cons, mem_edges_cons] at he + rcases he with he | he + · exact absurd (mem_of_mem_edges w he (by simp)) hv + · have hht : w.head = w.tail := by + rw [Sym2.eq_iff] at he + rcases he with ⟨h1, _⟩ | ⟨h1, _⟩ + · exact h1 + · exact absurd (h1 ▸ head_mem w) hv + have : w.length = 0 := length_zero_of_nodup_head_eq_tail w hw hht + simp [length, this] + +/-! ## Nodup of arcs -/ + +/-- A duplicate-free sequence traverses each directed arc at most once. -/ +@[grind] lemma nodup_arcs_of_nodup (w : VertexSeq α) (h : w.nodup) : + w.arcs.Nodup := by + induction w with + | singleton v => simp [arcs] + | cons w v ih => + obtain ⟨hw, hv⟩ := h + rw [arcs_cons, List.concat_eq_append, List.nodup_append] + refine ⟨ih hw, by simp, ?_⟩ + intro a ha b hb hab + simp only [List.mem_singleton] at hb + subst hb; subst hab + exact hv (snd_mem_of_mem_arcs w ha) + +/-- In a duplicate-free sequence, an arc from the tail back to the head cannot +occur except in the degenerate length-at-most-one case. -/ +@[grind →] lemma length_le_one_of_mem_arcs_tail_head (w : VertexSeq α) + (h : w.nodup) (ha : (w.tail, w.head) ∈ w.arcs) : w.length ≤ 1 := by + cases w with + | singleton v => simp [arcs] at ha + | cons w v => + obtain ⟨hw, hv⟩ := h + simp only [head_cons, tail_cons, mem_arcs_cons] at ha + rcases ha with ha | ha + · exact absurd (fst_mem_of_mem_arcs w ha) hv + · have hhead : w.head = v := congrArg Prod.snd ha + exact (hv (by rw [← hhead]; exact head_mem w)).elim + +/-! ## append, reverse -/ + +/-- The edges of an append are those of the operands plus the joining edge +`s(p.tail, q.head)` between them. -/ +@[simp, grind =] lemma edges_append (p q : VertexSeq α) : + (p.append q).edges = p.edges ++ [s(p.tail, q.head)] ++ q.edges := by + induction q generalizing p with + | singleton v => simp [append, edges, List.concat_eq_append] + | cons q v ih => + simp only [append, edges_cons, tail_append, head_cons, ih, + List.concat_eq_append, List.append_assoc] + +/-- Reversing a sequence reverses its list of (undirected) edges. -/ +@[simp, grind =] lemma edges_reverse (w : VertexSeq α) : + w.reverse.edges = w.edges.reverse := by + induction w with + | singleton v => rfl + | cons w v ih => + rw [reverse, edges_append, edges_cons, ih] + simp [head_reverse, List.concat_eq_append, Sym2.eq_swap] + +/-- The arcs of an append are those of the operands plus the joining arc +`(p.tail, q.head)` between them. -/ +@[simp, grind =] lemma arcs_append (p q : VertexSeq α) : + (p.append q).arcs = p.arcs ++ [(p.tail, q.head)] ++ q.arcs := by + induction q generalizing p with + | singleton v => simp [append, arcs, List.concat_eq_append] + | cons q v ih => + simp only [append, arcs_cons, tail_append, head_cons, ih, + List.concat_eq_append, List.append_assoc] + +/-- Reversing a sequence reverses the arc list and swaps every arc's endpoints. -/ +@[simp, grind =] lemma arcs_reverse (w : VertexSeq α) : + w.reverse.arcs = w.arcs.reverse.map (fun a : α × α => (a.2, a.1)) := by + induction w with + | singleton v => rfl + | cons w v ih => + rw [reverse, arcs_append, arcs_cons, ih] + simp [head_reverse, List.concat_eq_append] + +/-! ## Subsequence operations do not introduce new edges -/ + +/-- Dropping the last vertex cannot introduce new traversed edges. -/ +@[grind] lemma edges_dropTail_subset (w : VertexSeq α) : + w.dropTail.edges ⊆ w.edges := by + cases w <;> simp [dropTail, edges, List.concat_eq_append] + +/-- Dropping the first vertex cannot introduce new traversed edges. -/ +@[grind] lemma edges_dropHead_subset (w : VertexSeq α) : + w.dropHead.edges ⊆ w.edges := by + intro e he + fun_induction dropHead w <;> grind [mem_edges_cons, tail_dropHead] + +/-- Taking a prefix cannot introduce new traversed edges. -/ +@[grind] lemma edges_prefixUntil_subset [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) : (w.prefixUntil v h).edges ⊆ w.edges := by + intro e he + fun_induction prefixUntil w v h <;> grind [mem_edges_cons] + +/-- Taking a suffix cannot introduce new traversed edges. -/ +@[grind] lemma edges_suffixFrom_subset [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) : (w.suffixFrom v h).edges ⊆ w.edges := by + intro e he + fun_induction suffixFrom w v h <;> grind [mem_edges_cons, tail_suffixFrom] + +/-- Loop erasure cannot introduce new traversed edges. -/ +@[grind] lemma edges_loopErase_subset [DecidableEq α] (w : VertexSeq α) : + w.loopErase.edges ⊆ w.edges := by + intro e he + fun_induction loopErase w <;> grind [mem_edges_cons, tail_loopErase] + +/-- Cycle erasure cannot introduce new traversed edges. -/ +@[grind] lemma edges_cycleErase_subset [DecidableEq α] (w : VertexSeq α) : + w.cycleErase.edges ⊆ w.edges := by + intro e he + fun_induction cycleErase w <;> + grind [mem_edges_cons, edges_prefixUntil_subset, tail_cycleErase] + +/-! ## Subsequence operations do not introduce new arcs -/ + +/-- Dropping the last vertex cannot introduce new traversed arcs. -/ +@[grind] lemma arcs_dropTail_subset (w : VertexSeq α) : + w.dropTail.arcs ⊆ w.arcs := by + cases w <;> simp [dropTail, arcs, List.concat_eq_append] + +/-- Dropping the first vertex cannot introduce new traversed arcs. -/ +@[grind] lemma arcs_dropHead_subset (w : VertexSeq α) : + w.dropHead.arcs ⊆ w.arcs := by + intro a ha + fun_induction dropHead w <;> grind [mem_arcs_cons, tail_dropHead] + +/-- Taking a prefix cannot introduce new traversed arcs. -/ +@[grind] lemma arcs_prefixUntil_subset [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) : (w.prefixUntil v h).arcs ⊆ w.arcs := by + intro a ha + fun_induction prefixUntil w v h <;> grind [mem_arcs_cons] + +/-- Taking a suffix cannot introduce new traversed arcs. -/ +@[grind] lemma arcs_suffixFrom_subset [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) : (w.suffixFrom v h).arcs ⊆ w.arcs := by + intro a ha + fun_induction suffixFrom w v h <;> grind [mem_arcs_cons, tail_suffixFrom] + +/-- Loop erasure cannot introduce new traversed arcs. -/ +@[grind] lemma arcs_loopErase_subset [DecidableEq α] (w : VertexSeq α) : + w.loopErase.arcs ⊆ w.arcs := by + intro a ha + fun_induction loopErase w <;> grind [mem_arcs_cons, tail_loopErase] + +/-- Cycle erasure cannot introduce new traversed arcs. -/ +@[grind] lemma arcs_cycleErase_subset [DecidableEq α] (w : VertexSeq α) : + w.cycleErase.arcs ⊆ w.arcs := by + intro a ha + fun_induction cycleErase w <;> + grind [mem_arcs_cons, arcs_prefixUntil_subset, tail_cycleErase] + +end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Erase.lean b/GraphLib/Theory/Structures/VertexSeq/Erase.lean new file mode 100644 index 0000000..bb35164 --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/Erase.lean @@ -0,0 +1,126 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.VertexSeq.Subseq + +/-! +# Vertex sequences: erasure operations + +Two ways to remove redundancy from a vertex sequence: + +* `VertexSeq.loopErase` — remove immediate stalls (consecutive duplicates); the + result is `nonstalling`. +* `VertexSeq.cycleErase` — remove the detour between the two occurrences of any + repeated vertex; the result is `nodup`. +-/ + +variable {α : Type*} + +namespace VertexSeq + +/-! ## loopErase -/ + +/-- Remove immediate stalls (consecutive duplicate vertices). The result +satisfies `nonstalling`. -/ +@[grind] def loopErase [DecidableEq α] : VertexSeq α → VertexSeq α + | .singleton v => .singleton v + | .cons w v => + if w.tail = v then loopErase w + else .cons (loopErase w) v + +/-- `loopErase` preserves the tail. -/ +@[simp, grind =] lemma tail_loopErase [DecidableEq α] (w : VertexSeq α) : + w.loopErase.tail = w.tail := by + induction w with + | singleton _ => rfl + | cons w v ih => + by_cases h : w.tail = v <;> simp [loopErase, h, ih] + +@[grind] lemma loopErase_nonstalling [DecidableEq α] (w : VertexSeq α) : + w.loopErase.nonstalling := by + induction w with + | singleton _ => grind [loopErase] + | cons w v ih => + by_cases h : w.tail = v <;> grind [loopErase, tail_loopErase] + +/-! ## cycleErase -/ + +/-- Cycle erasure: whenever a vertex repeats, drop the intermediate detour +between its two occurrences. The result satisfies `nodup`. -/ +@[grind] def cycleErase [DecidableEq α] : VertexSeq α → VertexSeq α + | .singleton v => .singleton v + | .cons w v => + if h : v ∈ w then + cycleErase (prefixUntil w v h) + else + .cons (cycleErase w) v + termination_by p => p.length + decreasing_by + · simp [length] + grind [length_prefixUntil_le] + · simp [length] + +/-- Membership in `cycleErase` implies membership in the original sequence. -/ +@[grind] lemma cycleErase_subset [DecidableEq α] (w : VertexSeq α) : + w.cycleErase ⊆ w := by + intro x hx + fun_induction cycleErase w <;> grind [prefixUntil_subset] + +@[grind] lemma cycleErase_nodup [DecidableEq α] (w : VertexSeq α) : + w.cycleErase.nodup := by + fun_induction cycleErase w <;> grind [cycleErase_subset] + +/-! ## Nodup preservation under loopErase -/ + +/-- Membership in `loopErase` implies membership in the original sequence. -/ +@[grind] lemma loopErase_subset [DecidableEq α] (w : VertexSeq α) : + w.loopErase ⊆ w := by + intro x hx + induction w with + | singleton v => + grind [loopErase] + | cons w v ih => + by_cases htail : w.tail = v + · have hxw : x ∈ w := by + exact ih (by simpa [loopErase, htail] using hx) + grind + · have hx' : x ∈ w.loopErase :+ v := by + simpa [loopErase, htail] using hx + rcases (mem_cons x v w.loopErase).1 hx' with hxold | rfl + · exact (mem_cons x v w).2 (Or.inl (ih hxold)) + · exact (mem_cons x x w).2 (Or.inr rfl) + +/-- `loopErase` preserves `nodup`. -/ +@[grind] lemma nodup_loopErase [DecidableEq α] (w : VertexSeq α) (hw : w.nodup) : + w.loopErase.nodup := by + induction w with + | singleton v => grind + | cons w v ih => + by_cases htail : w.tail = v + · grind + · simp [loopErase, htail, nodup] at hw ⊢ + exact ⟨ih hw.1, fun hv => hw.2 (loopErase_subset w v hv)⟩ + +/-! ## Interaction with non-stalling and the tail -/ + +/-- On a non-stalling sequence, `loopErase` is the identity: there are no +consecutive duplicates to remove. -/ +@[grind =] lemma loopErase_eq_self_of_nonstalling [DecidableEq α] (w : VertexSeq α) + (h : w.nonstalling) : w.loopErase = w := by + induction w with + | singleton v => rfl + | cons w v ih => + obtain ⟨hns, hne⟩ := h + have hstep : (w.cons v).loopErase = (w.loopErase).cons v := by + change (if w.tail = v then w.loopErase else (w.loopErase).cons v) = _ + rw [if_neg hne] + rw [hstep, ih hns] + +/-- `cycleErase` preserves the tail vertex. -/ +@[grind =] lemma tail_cycleErase [DecidableEq α] (w : VertexSeq α) : + w.cycleErase.tail = w.tail := by + fun_induction cycleErase w <;> grind [tail_prefixUntil] + +end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Index.lean b/GraphLib/Theory/Structures/VertexSeq/Index.lean new file mode 100644 index 0000000..9e86a17 --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/Index.lean @@ -0,0 +1,39 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.VertexSeq.Basic + +/-! +# Vertex sequences: indexing and insertion + +Positional access into a vertex sequence via the underlying list, and insertion +of a vertex at a given index. + +## Main definitions + +* `GetElem (VertexSeq α) ℕ α` — index into the visited vertices. +* `VertexSeq.insert` — insert a vertex at a position. +-/ + +variable {α : Type*} + +namespace VertexSeq + +/-! ## Indexing and insertion -/ + +instance : GetElem (VertexSeq α) ℕ α (fun w i ↦ i < w.toList.length) where + getElem w i h := w.toList[i] + +/-- Insert the vertex `v` at index `i`, shifting later vertices one position +to the right. If `i` exceeds `w.toList.length`, `v` is appended at the end. -/ +@[grind] def insert : VertexSeq α → ℕ → α → VertexSeq α + | .singleton x, 0, v => .cons (.singleton v) x + | .singleton x, _ + 1, v => .cons (.singleton x) v + | .cons q x, i, v => + if i ≤ q.length then .cons (insert q i v) x + else if i = q.length + 1 then .cons (q :+ v) x + else .cons (q :+ x) v + +end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/MapZip.lean b/GraphLib/Theory/Structures/VertexSeq/MapZip.lean new file mode 100644 index 0000000..6695547 --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/MapZip.lean @@ -0,0 +1,137 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.VertexSeq.Predicates + +/-! +# Vertex sequences: map, fold, zip + +The higher-order traversals over a vertex sequence and the `Functor` instance, +together with the membership and `nodup`/`nonstalling` lemmas they need. + +## Main definitions + +* `VertexSeq.map`, `VertexSeq.foldl`, `VertexSeq.foldr` — the standard + traversals. +* `VertexSeq.zip` — pointwise pairing of two sequences. +* `VertexSeq.any`, `VertexSeq.all` — existential/universal predicates. +-/ + +variable {α : Type*} + +namespace VertexSeq + +/-! ## map, foldl, foldr, zip, any, all -/ + +@[grind] def map {β : Type*} (f : α → β) : VertexSeq α → VertexSeq β + | .singleton v => singleton (f v) + | .cons w v => w.map f :+ f v + +@[simp, grind =] lemma toList_map {β : Type*} (f : α → β) (w : VertexSeq α) : + (w.map f).toList = w.toList.map f := by + induction w with + | singleton v => rfl + | cons w v ih => + simp [map, toList, ih, List.concat_eq_append] + +@[simp, grind =] lemma head_map {β : Type*} (f : α → β) (w : VertexSeq α) : + (w.map f).head = f w.head := by + induction w with + | singleton v => rfl + | cons w v ih => exact ih + +@[simp, grind =] lemma tail_map {β : Type*} (f : α → β) (w : VertexSeq α) : + (w.map f).tail = f w.tail := by + induction w <;> rfl + +@[simp, grind =] lemma length_map {β : Type*} (f : α → β) (w : VertexSeq α) : + (w.map f).length = w.length := by + induction w with + | singleton v => rfl + | cons w v ih => simp [map, length, ih] + +@[grind] def foldl {β : Type*} (f : β → α → β) (b : β) : VertexSeq α → β + | .singleton v => f b v + | .cons w v => f (w.foldl f b) v + +@[grind] def foldr {β : Type*} (f : α → β → β) (b : β) : VertexSeq α → β + | .singleton v => f v b + | .cons w v => w.foldr f (f v b) + +@[grind] def zip {β : Type*} : VertexSeq α → VertexSeq β → VertexSeq (α × β) + | .singleton v, .singleton u => .singleton (v, u) + | .singleton v, .cons _ u => .singleton (v, u) + | .cons _ v, .singleton u => .singleton (v, u) + | .cons w v, .cons y u => .cons (w.zip y) (v, u) + +/-- The tail of a `zip` is the pair of tails. -/ +@[simp, grind =] lemma tail_zip {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) : + (w.zip w').tail = (w.tail, w'.tail) := by + fun_induction zip w w' <;> grind + +@[grind] def any (p : α → Prop) : VertexSeq α → Prop + | .singleton v => p v + | .cons w v => w.any p ∨ p v + +@[grind] def all (p : α → Prop) : VertexSeq α → Prop + | .singleton v => p v + | .cons w v => w.all p ∧ p v + +/-! ## Membership and preservation -/ + +/-- Membership in a mapped sequence is membership in the original sequence +through the mapping function. -/ +@[grind] lemma mem_map {β : Type*} (f : α → β) (w : VertexSeq α) (b : β) : + b ∈ w.map f ↔ ∃ a : α, a ∈ w ∧ f a = b := by + induction w <;> grind + +/-- Mapping through an injective function preserves `nodup`. -/ +@[grind] lemma nodup_map {β : Type*} (f : α → β) (hf : Function.Injective f) + (w : VertexSeq α) (h : w.nodup) : (w.map f).nodup := by + induction w <;> grind + +/-- Membership in a zip projects to membership in the left operand. -/ +@[grind] lemma fst_mem_of_mem_zip {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) + {x : α × β} (h : x ∈ w.zip w') : x.1 ∈ w := by + fun_induction zip w w' <;> grind + +/-- Zipping with a `nodup` left operand yields a `nodup` sequence of pairs. -/ +@[grind] lemma nodup_zip {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) + (hw : w.nodup) : (w.zip w').nodup := by + fun_induction zip w w' with + | case1 => grind + | case2 => grind + | case3 => grind + | case4 w v y u ih => + simp [nodup] at hw ⊢ + exact ⟨ih hw.1, fun hmem => hw.2 (fst_mem_of_mem_zip w y hmem)⟩ + +/-- Zipping with a non-stalling left operand yields a non-stalling sequence +(the first components of consecutive pairs already differ). -/ +@[grind] lemma nonstalling_zip {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) + (hw : w.nonstalling) : (w.zip w').nonstalling := by + fun_induction zip w w' <;> grind + +/-! ## Functor instance -/ + +instance : Functor VertexSeq where map := VertexSeq.map + +@[simp] lemma map_id (w : VertexSeq α) : w.map id = w := by + induction w with + | singleton _ => rfl + | cons w v ih => simp [map, ih] + +lemma map_comp {β γ : Type*} (f : α → β) (g : β → γ) (w : VertexSeq α) : + w.map (g ∘ f) = (w.map f).map g := by + induction w with + | singleton _ => rfl + | cons w v ih => simp [map, ih] + +instance : LawfulFunctor VertexSeq where + map_const := rfl + id_map := map_id + comp_map f g w := map_comp f g w + +end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Predicates.lean b/GraphLib/Theory/Structures/VertexSeq/Predicates.lean new file mode 100644 index 0000000..c245c9e --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/Predicates.lean @@ -0,0 +1,160 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.VertexSeq.Basic + +/-! +# Vertex sequences: structural predicates + +The three predicates that classify the shape of a vertex sequence: + +* `VertexSeq.nodup` — no repeated vertex. +* `VertexSeq.nonstalling` — no two consecutive vertices are equal. +* `VertexSeq.closed` — the first and last vertex coincide. + +This file states their definitions together with the preservation lemmas that +only involve the endpoint-dropping operations of `Basic`; preservation under +`append`, `reverse`, the subsequence operations, etc. lives alongside those +operations in their own files. +-/ + +variable {α : Type*} + +namespace VertexSeq + +/-! ## Definitions -/ + +/-- The sequence has no repeated vertex. -/ +@[grind] def nodup : VertexSeq α → Prop + | .singleton _ => True + | .cons w v => w.nodup ∧ v ∉ w + +/-- The sequence never stalls: no two consecutive vertices are equal. -/ +@[grind] def nonstalling : VertexSeq α → Prop + | .singleton _ => True + | .cons w v => w.nonstalling ∧ w.tail ≠ v + +/-- A vertex sequence is *closed* when its first and last vertex coincide. -/ +@[grind] def closed (w : VertexSeq α) : Prop := w.head = w.tail + +@[grind] lemma nodup_nonstalling (w : VertexSeq α) (h : w.nodup) : + w.nonstalling := by induction w <;> grind + +/-! ## Nodup preservation under dropHead, dropTail -/ + +/-- A `dropHead` result is contained in the original sequence. -/ +@[grind] lemma dropHead_subset (w : VertexSeq α) : w.dropHead ⊆ w := by + intro v hv + fun_induction dropHead w <;> grind + +/-- Dropping the first vertex preserves `nodup`. -/ +@[grind] lemma nodup_dropHead (w : VertexSeq α) (h : w.nodup) : + w.dropHead.nodup := by + induction w with + | singleton _ => grind + | cons w v ih => + cases w with + | singleton _ => grind + | cons t u => + simp [nodup] at h ⊢ + exact ⟨ih h.1, fun hv => h.2 (dropHead_subset (t :+ u) v hv)⟩ + +/-- Dropping the last vertex preserves `nodup`. -/ +@[grind] lemma nodup_dropTail (w : VertexSeq α) (h : w.nodup) : + w.dropTail.nodup := by + cases w <;> grind + +/-! ## Non-stalling preservation under dropHead, dropTail -/ + +/-- Dropping the first vertex preserves non-stalling. -/ +@[grind] lemma nonstalling_dropHead (w : VertexSeq α) (h : w.nonstalling) : + w.dropHead.nonstalling := by + fun_induction dropHead w <;> grind + +/-- Dropping the last vertex preserves non-stalling. -/ +@[grind] lemma nonstalling_dropTail (w : VertexSeq α) (h : w.nonstalling) : + w.dropTail.nonstalling := by + cases w <;> grind + +/-! ## Nodup, closedness and the underlying list -/ + +/-- A closed `nodup` sequence has length zero: a repeated endpoint forces a +duplicate unless the sequence is a single vertex. -/ +@[grind] lemma length_zero_of_nodup_head_eq_tail (w : VertexSeq α) + (hnd : w.nodup) (hht : w.head = w.tail) : w.length = 0 := by + cases w with + | singleton v => + rfl + | cons w v => + exfalso + exact hnd.2 (by + have h : w.head = v := by simpa using hht + change v ∈ w + rw [← h] + exact head_mem w) + +/-- In a non-trivial `nodup` sequence, the head does not reappear after dropping +it. -/ +@[grind] lemma head_not_mem_dropHead_of_nodup (w : VertexSeq α) + (hnd : w.nodup) (hpos : w.length ≠ 0) : w.head ∉ w.dropHead := by + induction w with + | singleton v => + exact (hpos rfl).elim + | cons w v ih => + cases w with + | singleton u => + simpa [dropHead, nodup, mem_def, toList, eq_comm] using hnd.2 + | cons t u => + intro hmem + simp [nodup] at hnd + rcases (mem_cons ((t :+ u).head) v (t :+ u).dropHead).1 hmem with hprefix | hlast + · exact ih hnd.1 (by simp [length]) hprefix + · exact hnd.2 (by + rw [← hlast] + exact head_mem (t :+ u)) + +/-- For a closed sequence with a `nodup` interior (drop the repeated tail), the +opposite interior (drop the repeated head) is also `nodup`. -/ +@[grind] lemma nodup_dropHead_of_closed_dropTail (w : VertexSeq α) + (hclosed : w.closed) (hnodup : w.dropTail.nodup) : + w.dropHead.nodup := by + cases w with + | singleton v => + grind [dropHead] + | cons w v => + cases w with + | singleton u => + grind [dropHead, nodup] + | cons t u => + simp [dropTail, nodup] at hnodup ⊢ + constructor + · exact nodup_dropHead (t :+ u) hnodup + · intro hv + exact head_not_mem_dropHead_of_nodup (t :+ u) hnodup (by simp [length]) + (by simpa [closed] using hclosed ▸ hv) + +/-- `nodup` is exactly `Nodup` of the underlying list. -/ +lemma nodup_iff_toList_nodup (w : VertexSeq α) : + w.nodup ↔ w.toList.Nodup := by + induction w with + | singleton v => + simp [nodup, toList] + | cons w v ih => + constructor + · intro h + rw [toList, List.concat_eq_append, List.nodup_append] + refine ⟨ih.mp h.1, by simp, ?_⟩ + intro a ha b hb hab + simp at hb + subst hb + subst hab + exact h.2 (by simpa [mem_def] using ha) + · intro h + rw [toList, List.concat_eq_append, List.nodup_append] at h + refine ⟨ih.mpr h.1, ?_⟩ + intro hv + exact h.2.2 v (by simpa [mem_def] using hv) v (by simp) rfl + +end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Subseq.lean b/GraphLib/Theory/Structures/VertexSeq/Subseq.lean new file mode 100644 index 0000000..2db4fb6 --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/Subseq.lean @@ -0,0 +1,329 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.VertexSeq.Append + +/-! +# Vertex sequences: subsequence operations + +Operations that carve a contiguous piece out of a vertex sequence, together +with their head/tail/length/subset laws and the preservation of `nodup` and +`nonstalling`. + +## Main definitions + +* `VertexSeq.prefixUntil`, `VertexSeq.suffixFrom` — the prefix/suffix cut at a + chosen vertex. +* `VertexSeq.takeWhile`, `VertexSeq.dropWhile` — the prefix/suffix cut at the + first vertex failing a predicate. +* `VertexSeq.splitAt` — split into pieces at every occurrence of a vertex. +-/ + +variable {α : Type*} + +namespace VertexSeq + +/-! ## prefixUntil, suffixFrom -/ + +/-- The prefix of `w` ending at the first occurrence of the vertex `v`, +inclusive of that vertex. The hypothesis guarantees such a vertex exists. -/ +@[grind] def prefixUntil [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) : VertexSeq α := + match w with + | .singleton x => .singleton x + | .cons w2 x => + if h2 : v ∈ w2 then prefixUntil w2 v h2 + else w2 :+ x + +/-- The suffix of `w` starting at the first occurrence of the vertex `v`, +inclusive of that vertex. The hypothesis guarantees such a vertex exists. -/ +@[grind] def suffixFrom [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) : VertexSeq α := + match w with + | .singleton x => .singleton x + | .cons w2 x => + if h2 : v ∈ w2 then .cons (suffixFrom w2 v h2) x + else .singleton x + +@[simp] lemma length_prefixUntil_le [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.prefixUntil v h).length ≤ w.length := by + fun_induction prefixUntil w v h <;> grind + +@[simp] lemma length_suffixFrom_le [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.suffixFrom v h).length ≤ w.length := by + fun_induction suffixFrom w v h <;> grind + +@[simp] lemma head_prefixUntil [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.prefixUntil v h).head = w.head := by + fun_induction prefixUntil w v h <;> grind + +@[simp] lemma tail_prefixUntil [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.prefixUntil v h).tail = v := by + fun_induction prefixUntil w v h <;> grind + +@[simp] lemma prefixUntil_subset [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.prefixUntil v h) ⊆ w := by + fun_induction prefixUntil <;> grind + +@[simp] lemma head_suffixFrom [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.suffixFrom v h).head = v := by + fun_induction suffixFrom w v h <;> grind + +@[simp] lemma tail_suffixFrom [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.suffixFrom v h).tail = w.tail := by + fun_induction suffixFrom w v h <;> grind + +/-- A `suffixFrom` result is contained in the original sequence. -/ +@[simp] lemma suffixFrom_subset [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.suffixFrom v h) ⊆ w := by + fun_induction suffixFrom w v h + · grind + · expose_names + intro u hu + apply (mem_cons u x (w2.suffixFrom v h_1)).1 at hu + cases hu + · expose_names + grind + · grind + grind + +/-! ## takeWhile, dropWhile -/ + +/-- Take every vertex of `w` satisfying `p`, plus the first failure (if any). +If every vertex satisfies `p`, the whole sequence is returned. -/ +@[grind] def takeWhile (w : VertexSeq α) (p : α → Prop) [DecidablePred p] : + VertexSeq α := + match w with + | .singleton x => .singleton x + | .cons q x => + if ∃ v ∈ q.toList, ¬ p v then takeWhile q p + else q :+ x + +/-- Drop the longest prefix of `w` on which `p` holds; the result starts at +the first failure. The hypothesis ensures a failure exists. -/ +@[grind] def dropWhile (w : VertexSeq α) (p : α → Prop) [DecidablePred p] + (h : ∃ v ∈ w.toList, ¬ p v) : VertexSeq α := + match w with + | .singleton x => .singleton x + | .cons q x => + if hq : ∃ v ∈ q.toList, ¬ p v then (dropWhile q p hq) :+ x + else .singleton x + +/-- `dropWhile` returns a suffix ending at the original tail, so it preserves +the tail. -/ +@[simp, grind =] lemma tail_dropWhile (w : VertexSeq α) (p : α → Prop) + [DecidablePred p] (h : ∃ v ∈ w.toList, ¬ p v) : + (w.dropWhile p h).tail = w.tail := by + fun_induction dropWhile w p h <;> grind + +/-! ## splitAt -/ + +/-- Split `w` into a list of pieces at every occurrence of the vertex `v`. +The split point `v` is *duplicated*: it appears as the tail of one piece and +the head of the next, so that re-concatenating the pieces recovers `w`. -/ +@[grind] def splitAt [DecidableEq α] : VertexSeq α → α → List (VertexSeq α) + | .singleton x, _ => [.singleton x] + | .cons q x, v => + if x = v then appendToLast (splitAt q v) v ++ [.singleton v] + else appendToLast (splitAt q v) x +where + appendToLast : List (VertexSeq α) → α → List (VertexSeq α) + | [], _ => [] + | [w], x => [w :+ x] + | p :: ps, x => p :: appendToLast ps x + +/-! ## Nodup preservation -/ + +/-- A `prefixUntil` of a `nodup` sequence is `nodup`. -/ +@[grind] lemma nodup_prefixUntil [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) (hw : w.nodup) : (w.prefixUntil v h).nodup := by + fun_induction prefixUntil w v h <;> grind + +/-- A `suffixFrom` of a `nodup` sequence is `nodup`. -/ +@[grind] lemma nodup_suffixFrom [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) (hw : w.nodup) : (w.suffixFrom v h).nodup := by + fun_induction suffixFrom w v h <;> grind [suffixFrom_subset] + +/-- `takeWhile` preserves `nodup`. -/ +@[grind] lemma nodup_takeWhile (w : VertexSeq α) (p : α → Prop) + [DecidablePred p] (hw : w.nodup) : (w.takeWhile p).nodup := by + fun_induction takeWhile w p <;> grind + +/-- A `dropWhile` result is a suffix of the original sequence. -/ +@[grind] lemma dropWhile_subset (w : VertexSeq α) (p : α → Prop) + [DecidablePred p] (h : ∃ v ∈ w.toList, ¬ p v) : (w.dropWhile p h) ⊆ w := by + intro y hy + fun_induction dropWhile w p h <;> grind + +/-- `dropWhile` preserves `nodup`. -/ +@[grind] lemma nodup_dropWhile (w : VertexSeq α) (p : α → Prop) + [DecidablePred p] (h : ∃ v ∈ w.toList, ¬ p v) (hw : w.nodup) : + (w.dropWhile p h).nodup := by + fun_induction dropWhile w p h <;> grind [dropWhile_subset] + +/-- If every piece of `L` is contained in `w`, then every piece of +`appendToLast L x` is contained in `w :+ x`. -/ +@[grind] lemma appendToLast_subset (L : List (VertexSeq α)) (x : α) + (w : VertexSeq α) (hL : ∀ p ∈ L, p ⊆ w) : + ∀ p ∈ splitAt.appendToLast L x, p ⊆ w :+ x := by + induction L with + | nil => + grind [splitAt.appendToLast] + | cons p ps ih => + cases ps with + | nil => + intro r hr y hy + have hr' : r = p :+ x := by + simpa [splitAt.appendToLast] using hr + subst r + rcases (mem_cons y x p).1 hy with hyp | rfl + · exact (mem_cons y x w).2 (Or.inl (hL p (by simp) y hyp)) + · exact (mem_cons y y w).2 (Or.inr rfl) + | cons q qs => + intro r hr y hy + have hr' : r = p ∨ r ∈ splitAt.appendToLast (q :: qs) x := by + simpa [splitAt.appendToLast] using hr + rcases hr' with rfl | hr' + · exact (mem_cons y x w).2 (Or.inl (hL r (by simp) y hy)) + · have htail : ∀ s ∈ q :: qs, s ⊆ w := by + intro s hs + exact hL s (by simp [hs]) + exact ih htail r hr' y hy + +/-- Every piece produced by `splitAt` is contained in the original sequence. -/ +@[grind] lemma splitAt_subset [DecidableEq α] (w : VertexSeq α) (v : α) : + ∀ p ∈ w.splitAt v, p ⊆ w := by + fun_induction splitAt w v <;> grind [appendToLast_subset] + +/-- If every piece of `L` is nodup and avoids `x`, then `appendToLast L x` +has only nodup pieces. -/ +@[grind] lemma nodup_appendToLast (L : List (VertexSeq α)) (x : α) + (hL : ∀ p ∈ L, p.nodup) (havoid : ∀ p ∈ L, x ∉ p) : + ∀ p ∈ splitAt.appendToLast L x, p.nodup := by + induction L with + | nil => + grind [splitAt.appendToLast] + | cons p ps ih => + cases ps with + | nil => + intro r hr + have hr' : r = p :+ x := by + simpa [splitAt.appendToLast] using hr + subst r + exact ⟨hL p (by simp), havoid p (by simp)⟩ + | cons q qs => + intro r hr + have hr' : r = p ∨ r ∈ splitAt.appendToLast (q :: qs) x := by + simpa [splitAt.appendToLast] using hr + rcases hr' with rfl | hr' + · exact hL r (by simp) + · have htail : ∀ s ∈ q :: qs, s.nodup := by + intro s hs + exact hL s (by simp [hs]) + have havoid' : ∀ s ∈ q :: qs, x ∉ s := by + intro s hs + exact havoid s (by simp [hs]) + exact ih htail havoid' r hr' + +/-- Each piece of a `splitAt` of a `nodup` sequence is `nodup`. -/ +@[grind] lemma nodup_splitAt [DecidableEq α] (w : VertexSeq α) + (hw : w.nodup) (v : α) : ∀ p ∈ w.splitAt v, p.nodup := by + induction w with + | singleton x => + intro p hp + have hp' : p = VertexSeq.singleton x := by + simpa [splitAt] using hp + subst p + grind + | cons q x ih => + intro p hp + simp [nodup] at hw + by_cases hx : x = v + · have hp' : p ∈ splitAt.appendToLast (q.splitAt v) v ∨ + p ∈ [VertexSeq.singleton v] := by + simpa [splitAt, hx] using hp + rcases hp' with hp_append | hp_single + · exact nodup_appendToLast (q.splitAt v) v (ih hw.1) + (fun s hs hsv => hw.2 (by + rw [hx] + exact splitAt_subset q v s hs v hsv)) p hp_append + · have hp_eq : p = VertexSeq.singleton v := by + simpa using hp_single + subst p + grind + · exact nodup_appendToLast (q.splitAt v) x (ih hw.1) + (fun s hs hsx => hw.2 (splitAt_subset q v s hs x hsx)) p + (by simpa [splitAt, hx] using hp) + +/-! ## Non-stalling preservation -/ + +/-- A `prefixUntil` of a non-stalling sequence is non-stalling. -/ +@[grind] lemma nonstalling_prefixUntil [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) (hw : w.nonstalling) : (w.prefixUntil v h).nonstalling := by + fun_induction prefixUntil w v h <;> grind + +/-- A `suffixFrom` of a non-stalling sequence is non-stalling. -/ +@[grind] lemma nonstalling_suffixFrom [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) (hw : w.nonstalling) : (w.suffixFrom v h).nonstalling := by + fun_induction suffixFrom w v h <;> grind + +/-- `takeWhile` preserves non-stalling. -/ +@[grind] lemma nonstalling_takeWhile (w : VertexSeq α) (p : α → Prop) + [DecidablePred p] (hw : w.nonstalling) : (w.takeWhile p).nonstalling := by + fun_induction takeWhile w p <;> grind + +/-- `dropWhile` preserves non-stalling. -/ +@[grind] lemma nonstalling_dropWhile (w : VertexSeq α) (p : α → Prop) + [DecidablePred p] (h : ∃ v ∈ w.toList, ¬ p v) (hw : w.nonstalling) : + (w.dropWhile p h).nonstalling := by + fun_induction dropWhile w p h <;> grind + +/-- `appendToLast` extends the final piece, so the last piece becomes the old +last piece with `x` appended. -/ +@[simp, grind =] lemma getLast?_appendToLast (L : List (VertexSeq α)) (x : α) : + (splitAt.appendToLast L x).getLast? = (L.getLast?).map (· :+ x) := by + fun_induction splitAt.appendToLast L x <;> grind + +/-- If every piece of `L` is non-stalling and the final piece does not end at +`x`, then every piece of `appendToLast L x` is non-stalling. -/ +@[grind] lemma nonstalling_appendToLast (L : List (VertexSeq α)) (x : α) + (hL : ∀ p ∈ L, p.nonstalling) (hlast : ∀ p ∈ L.getLast?, p.tail ≠ x) : + ∀ p ∈ splitAt.appendToLast L x, p.nonstalling := by + fun_induction splitAt.appendToLast L x <;> grind + +/-- Every piece produced by `splitAt` ends at the original tail. -/ +@[grind] lemma tail_getLast?_splitAt [DecidableEq α] (w : VertexSeq α) (v : α) : + ∀ p ∈ (w.splitAt v).getLast?, p.tail = w.tail := by + fun_induction splitAt w v <;> grind + +/-- Each piece of a `splitAt` of a non-stalling sequence is non-stalling. -/ +@[grind] lemma nonstalling_splitAt [DecidableEq α] (w : VertexSeq α) + (hw : w.nonstalling) (v : α) : ∀ p ∈ w.splitAt v, p.nonstalling := by + fun_induction splitAt w v <;> grind + +/-! ## Reassembling a sequence from its cut at a vertex -/ + +/-- Cutting at an interior vertex `v` and re-gluing the prefix (with its +duplicated `v` dropped) to the suffix recovers the original sequence. -/ +@[simp, grind →] lemma dropTail_prefixUntil_append_suffixFrom [DecidableEq α] + (w : VertexSeq α) (v : α) (h : v ∈ w) (hne : v ≠ w.head) : + (w.prefixUntil v h).dropTail.append (w.suffixFrom v h) = w := by + induction w generalizing v with + | singleton x => + exact (hne (by grind)).elim + | cons w x ih => + by_cases hmem : v ∈ w + · simp [prefixUntil, suffixFrom, hmem] + by_cases hvhead : v = w.head + · subst hvhead + grind + · exact congrArg (fun q => q :+ x) (ih v hmem hvhead) + · have hvx : v = x := by + have hv : v ∈ w ∨ v = x := (mem_cons v x w).1 h + exact hv.elim (fun hw => (hmem hw).elim) id + subst hvx + simp [prefixUntil, suffixFrom, hmem, append, dropTail] + +end VertexSeq From c58b09ff519010f49cbeb2b13002391ff0e9b32a Mon Sep 17 00:00:00 2001 From: Weixuan Yuan Date: Mon, 13 Jul 2026 23:30:24 +0800 Subject: [PATCH 2/3] Girth related stuff --- GraphLib/Graph/Degree.lean | 5 +- GraphLib/Theory/Connectivity/Basic.lean | 36 +- GraphLib/Theory/Matching/Basic.lean | 29 +- .../Theory/Structures/InSimpleDiGraph.lean | 26 +- GraphLib/Theory/Structures/InSimpleGraph.lean | 455 +----------- .../Structures/InSimpleGraph/Cycle.lean | 218 ++++++ .../Theory/Structures/InSimpleGraph/Path.lean | 83 +++ .../Structures/InSimpleGraph/VertexSeq.lean | 229 ++++++ .../Theory/Structures/InSimpleGraph/Walk.lean | 189 +++++ GraphLib/Theory/Structures/SimpleCycle.lean | 667 +++++++++++++----- .../SimpleGraph_only/Bipartite.lean | 107 +++ .../Structures/SimpleGraph_only/Girth.lean | 286 ++++++++ .../SimpleGraph_only/MooreBound.lean | 61 ++ .../SimpleGraph_only/MooreBound/Bounds.lean | 151 ++++ .../SimpleGraph_only/MooreBound/Core.lean | 204 ++++++ .../SimpleGraph_only/MooreBound/Counting.lean | 66 ++ .../MooreBound/HalfLayers.lean | 357 ++++++++++ .../MooreBound/RootedLayers.lean | 251 +++++++ GraphLib/Theory/Structures/SimplePath.lean | 75 +- GraphLib/Theory/Structures/SimpleWalk.lean | 159 ++--- GraphLib/Theory/Structures/VertexSeq.lean | 36 +- .../Theory/Structures/VertexSeq/Append.lean | 117 ++- .../Theory/Structures/VertexSeq/Basic.lean | 128 ++-- .../Structures/VertexSeq/CommonPrefix.lean | 65 ++ .../Theory/Structures/VertexSeq/Edges.lean | 91 +-- .../Theory/Structures/VertexSeq/Erase.lean | 85 +-- .../Theory/Structures/VertexSeq/Index.lean | 31 +- .../Theory/Structures/VertexSeq/MapZip.lean | 90 ++- .../Structures/VertexSeq/Predicates.lean | 134 ++-- .../Theory/Structures/VertexSeq/Subseq.lean | 296 ++++---- 30 files changed, 3437 insertions(+), 1290 deletions(-) create mode 100644 GraphLib/Theory/Structures/InSimpleGraph/Cycle.lean create mode 100644 GraphLib/Theory/Structures/InSimpleGraph/Path.lean create mode 100644 GraphLib/Theory/Structures/InSimpleGraph/VertexSeq.lean create mode 100644 GraphLib/Theory/Structures/InSimpleGraph/Walk.lean create mode 100644 GraphLib/Theory/Structures/SimpleGraph_only/Bipartite.lean create mode 100644 GraphLib/Theory/Structures/SimpleGraph_only/Girth.lean create mode 100644 GraphLib/Theory/Structures/SimpleGraph_only/MooreBound.lean create mode 100644 GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Bounds.lean create mode 100644 GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Core.lean create mode 100644 GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Counting.lean create mode 100644 GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/HalfLayers.lean create mode 100644 GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/RootedLayers.lean create mode 100644 GraphLib/Theory/Structures/VertexSeq/CommonPrefix.lean diff --git a/GraphLib/Graph/Degree.lean b/GraphLib/Graph/Degree.lean index 1b51d6d..37e644a 100644 --- a/GraphLib/Graph/Degree.lean +++ b/GraphLib/Graph/Degree.lean @@ -4,6 +4,7 @@ Released under Apache 2.0 license as described in the file LICENSE. Authors: Basil Rohner -/ import GraphLib.Graph.Basic +import GraphLib.Graph.Adjacency import Mathlib.Data.Set.Card import Mathlib.Data.ENat.Lattice @@ -240,9 +241,6 @@ noncomputable def SimpleDiGraph.minInDegree (G : SimpleDiGraph α) : ℕ∞ := def SimpleGraph.avgDegree (G : SimpleGraph α) : ℕ := sorry -def SimpleGraph.adj (G : SimpleGraph α) (v w : α) : Prop := - sorry - def SimpleGraph.inc (G : SimpleGraph α) (e : E(G)) (v : ) notation δ(G) @@ -251,6 +249,5 @@ notation δ-(G) notation Δ(G) notation Δ+(G) notation Δ-(G) -notation v ∼G w end GraphLib diff --git a/GraphLib/Theory/Connectivity/Basic.lean b/GraphLib/Theory/Connectivity/Basic.lean index e89a6a6..4993c11 100644 --- a/GraphLib/Theory/Connectivity/Basic.lean +++ b/GraphLib/Theory/Connectivity/Basic.lean @@ -2,36 +2,8 @@ # `GraphLib.Theory.Connectivity` Placeholder. Connected components, cuts, and vertex/edge connectivity. --/ - -def SimpleGraph.cutVertex - -def SimpleGraph.cutEdge - -def SimpleGraph.vertexCut - -def SimpleGraph.edgeCut - -def SimpleGraph.boundary - -def SimpleGraph.vertexConnectivity - -def SimpleGraph.edgeConnectivity -def SimpleGraph.vertexSeparator - -def SimpleGraph.edgeSeparator - -def SimpleGraph.STvertexCut - -def SimpleGraph.STedgeCut - -def SimpleGraph.isStronglyConnectedComponent - -def SimpleGraph.stronglyConnectedComponents - -notation κ(G) -notation κ'(G) -notation ∂(G,S) - -def SimpleGraph.connected (G : SimpleGraph α) : Prop := +The connectivity API has not been implemented yet. Keeping this module as an +empty placeholder lets downstream code import it without publishing incomplete +declarations. +-/ diff --git a/GraphLib/Theory/Matching/Basic.lean b/GraphLib/Theory/Matching/Basic.lean index d735fbf..eaa17f5 100644 --- a/GraphLib/Theory/Matching/Basic.lean +++ b/GraphLib/Theory/Matching/Basic.lean @@ -7,7 +7,6 @@ import Mathlib.Data.Set.Card import Mathlib.Data.Set.Finite.Basic import GraphLib.Graph.Basic import GraphLib.Graph.Subgraph -import GraphLib.Theory.Walks.Basic /-! # Matchings, augmenting paths, Berge's theorem, and friends @@ -49,24 +48,16 @@ variable {α β : Type*} structure Matching (G : Graph α β) where edges : Set (Edge α β) - disjoint : ∀ e ∈ E(G), ∀ f ∈ E(G), e ≠ f → Disjoint e.endpoints.toFinset f.endpoints.toFinset + edges_subset : edges ⊆ G.edgeSet + disjoint : ∀ e ∈ edges, ∀ f ∈ edges, e ≠ f → + ∀ v, v ∈ e.endpoints → v ∉ f.endpoints -def Matching.size (G : Graph α β) (M : Matching G) := M.edges.card +noncomputable def Matching.size (G : Graph α β) (M : Matching G) : ℕ := M.edges.ncard -def Matching.IsMaximal - -def Matching.IsMaximum - -def Matching.IsPerfect - -def Matching.covered - -def Path.augmenting - -def Path.alternating - -def Matching.augment - -def Matching.union +/-! +The remaining matching operations listed above are intentionally left for a +future implementation instead of being exposed as declarations without types +or definitions. +-/ -def Matching.xor +end GraphLib diff --git a/GraphLib/Theory/Structures/InSimpleDiGraph.lean b/GraphLib/Theory/Structures/InSimpleDiGraph.lean index 8204261..2fbb26d 100644 --- a/GraphLib/Theory/Structures/InSimpleDiGraph.lean +++ b/GraphLib/Theory/Structures/InSimpleDiGraph.lean @@ -27,7 +27,7 @@ simple walks to a `SimpleDiGraph`. * Constructor characterizations: `singleton_iff`, `cons_iff`. * Vertex membership: `head_mem`, `tail_mem`, `mem_vertexSet`. -* Arc-list characterization: `iff_arcs`, `mem_edgeSet_of_mem_arcs`, and the +* Arc-list characterization: `iff_arcs`, `arc_mem`, and the generated-graph characterization `iff_toSimpleDiGraph_subgraphOf`. * Closure under direction-preserving `VertexSeq` operations: `prepend`, `append`, `dropHead`, `dropTail`, `prefixUntil`, `suffixFrom`, `takeWhile`, @@ -208,7 +208,7 @@ lemma dropWhile (G : SimpleDiGraph α) {w : VertexSeq α} · rw [dif_neg hc]; exact .singleton u ha.right_mem /-- If `G` has no arcs, every realized sequence has length zero. -/ -lemma length_eq_zero_of_no_edges (G : SimpleDiGraph α) (hE : E(G) = ∅) +lemma length_zero_of_no_edges (G : SimpleDiGraph α) (hE : E(G) = ∅) {w : VertexSeq α} (hw : G.IsVertexSeqIn w) : w.length = 0 := by induction hw <;> grind @@ -255,19 +255,19 @@ theorem iff_arcs (G : SimpleDiGraph α) (w : VertexSeq α) : · rw [VertexSeq.mem_arcs_cons]; exact Or.inr rfl /-- Any arc traversed by a realized vertex sequence is an edge of the graph. -/ -@[grind →] lemma mem_edgeSet_of_mem_arcs (G : SimpleDiGraph α) {w : VertexSeq α} +@[grind →] lemma arc_mem (G : SimpleDiGraph α) {w : VertexSeq α} (hw : G.IsVertexSeqIn w) {a : α × α} (ha : a ∈ w.arcs) : a ∈ E(G) := ((iff_arcs G w).1 hw).2 a ha /-- The final step of a non-trivial realized vertex sequence is an adjacency in the graph. -/ -@[grind →] lemma adj_dropTail_tail_tail_of_length_ne_zero (G : SimpleDiGraph α) +@[grind →] lemma last_adj (G : SimpleDiGraph α) {w : VertexSeq α} (hw : G.IsVertexSeqIn w) (h : w.length ≠ 0) : G.Adj w.dropTail.tail w.tail := by change (w.dropTail.tail, w.tail) ∈ E(G) - exact mem_edgeSet_of_mem_arcs G hw + exact arc_mem G hw (by - rw [VertexSeq.arcs_eq_dropTail_concat_of_length_ne_zero w h] + rw [VertexSeq.arcs_eq_dropTail_concat w h] simp [List.concat_eq_append]) end IsVertexSeqIn @@ -285,7 +285,7 @@ namespace IsSimpleWalkIn /-- Realization of a simple walk is realization of its underlying vertex sequence. -/ -@[simp, grind =] lemma isSimpleWalkIn_iff_isVertexSeqIn +@[simp, grind =] lemma iff_isVertexSeqIn (G : SimpleDiGraph α) (w : SimpleWalk α) : G.IsSimpleWalkIn w ↔ G.IsVertexSeqIn w.val := Iff.rfl @@ -309,9 +309,9 @@ sequence. -/ /-! ## Vertex and arc membership -/ /-- Any arc traversed by a realized walk is an edge of `G`. -/ -@[grind →] lemma mem_edgeSet_of_mem_arcs (G : SimpleDiGraph α) {w : SimpleWalk α} +@[grind →] lemma arc_mem (G : SimpleDiGraph α) {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) {a : α × α} (ha : a ∈ w.arcs) : a ∈ E(G) := - IsVertexSeqIn.mem_edgeSet_of_mem_arcs G hw ha + IsVertexSeqIn.arc_mem G hw ha /-- A walk is realized in `G` exactly when its head is a vertex of `G` and every arc it traverses is an edge of `G`. -/ @@ -335,7 +335,7 @@ lemma toSimpleDiGraph_subgraphOf (G : SimpleDiGraph α) {w : SimpleWalk α} · intro v hv exact mem_vertexSet G hw v (by simpa using hv) · intro a ha - exact mem_edgeSet_of_mem_arcs G hw (by simpa using ha) + exact arc_mem G hw (by simpa using ha) /-- If the simple directed graph generated by a simple walk is a subgraph of `G`, then the walk is realized in `G`. -/ @@ -409,9 +409,9 @@ lemma cycleErase [DecidableEq α] (G : SimpleDiGraph α) {w : SimpleWalk α} IsVertexSeqIn.mono G H hw hsub /-- If `G` has no arcs, every realized walk in `G` has length zero. -/ -lemma length_eq_zero_of_no_edges (G : SimpleDiGraph α) (hE : E(G) = ∅) +lemma length_zero_of_no_edges (G : SimpleDiGraph α) (hE : E(G) = ∅) {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) : w.length = 0 := - IsVertexSeqIn.length_eq_zero_of_no_edges G hE hw + IsVertexSeqIn.length_zero_of_no_edges G hE hw /-! ## Joining walks -/ @@ -436,7 +436,7 @@ lemma glue (G : SimpleDiGraph α) {p q : SimpleWalk α} (by have h' : p.val.tail = q.val.head := h rw [← h'] - exact IsVertexSeqIn.adj_dropTail_tail_tail_of_length_ne_zero G hp hlen) + exact IsVertexSeqIn.last_adj G hp hlen) end IsSimpleWalkIn diff --git a/GraphLib/Theory/Structures/InSimpleGraph.lean b/GraphLib/Theory/Structures/InSimpleGraph.lean index 531fd2e..1ed24ae 100644 --- a/GraphLib/Theory/Structures/InSimpleGraph.lean +++ b/GraphLib/Theory/Structures/InSimpleGraph.lean @@ -3,43 +3,46 @@ Copyright (c) 2026 Basil Rohner. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan -/ -import GraphLib.Graph.Adjacency -import GraphLib.Graph.Subgraph -import GraphLib.Theory.Structures.SimpleWalk +import GraphLib.Theory.Structures.InSimpleGraph.VertexSeq +import GraphLib.Theory.Structures.InSimpleGraph.Walk +import GraphLib.Theory.Structures.InSimpleGraph.Path +import GraphLib.Theory.Structures.InSimpleGraph.Cycle /-! -# Vertex sequences realized in a simple graph +# Vertex sequences, walks, paths and cycles realized in a simple graph A `VertexSeq α` is a purely combinatorial object: it knows nothing about any -graph. This file connects vertex sequences to a `SimpleGraph` through the +graph. This development connects vertex sequences to a `SimpleGraph` through the predicate `SimpleGraph.IsVertexSeqIn`, which says that the sequence is *realized* in `G`: its first vertex is a vertex of `G` and every consecutive pair is an edge of `G` (phrased through `SimpleGraph.Adj`). -This is the undirected analogue intended to mirror, in the `GraphLib` style, the -`IsVertexSeqIn` development from the legacy `GraphAlgorithms` library. The -primary definition is inductive and phrased through `Adj`; the edge-list -characterization appears later as a bridge to generated subgraphs and edge-set -reasoning. +This file defines nothing itself: it is an *umbrella* module that merely +re-exports (imports) the `InSimpleGraph` development, which is split across +`GraphLib/Theory/Structures/InSimpleGraph/`: + +* `VertexSeq` — `IsVertexSeqIn`, its constructor characterizations, vertex and + edge membership, closure under the `VertexSeq` operations, and the edge-set + characterization. +* `Walk` — `IsSimpleWalkIn`, the bridge to `IsVertexSeqIn`, the generated-subgraph + characterization, and closure under the walk operations (`append`, `glue`, …). +* `Path` — `IsSimplePathIn`, extension by a fresh adjacent vertex, and the + vertex-count bound. +* `Cycle` — `IsSimpleCycleIn`, `HasSimpleCycle`, `IsAcyclic`, and the + path-to-cycle construction lemmas. + +Downstream files should keep importing this umbrella; the split is internal. The +submodules form the acyclic spine `VertexSeq ← Walk ← Path ← Cycle`. ## Main definitions * `SimpleGraph.IsVertexSeqIn G w` — `w` is realized in `G`. * `SimpleGraph.IsSimpleWalkIn G w` — a `SimpleWalk` is realized in `G` through its underlying vertex sequence. - -## Main API - -* `SimpleGraph.IsVertexSeqIn.singleton_iff`, `cons_iff` — the two constructor - characterizations, which drive most downstream proofs. -* Vertex membership: `head_mem`, `tail_mem`, `mem_vertexSet`. -* Closure under the `VertexSeq` operations: `prepend`, `append`, `reverse`, - `dropHead`, `dropTail`, `prefixUntil`, `suffixFrom`, `takeWhile`, `dropWhile`, - `loopErase`, `cycleErase`, and monotonicity `mono` under passing to a - subgraph (`SimpleGraph.subgraphOf`). -* Thin `IsSimpleWalkIn` wrappers for the corresponding `SimpleWalk` operations. -* `nonstalling` — a realized sequence never stalls (so it is a `SimpleWalk`), - because adjacency in a simple graph forces distinct endpoints. +* `SimpleGraph.IsSimplePathIn G p` — a `SimplePath` is realized in `G`. +* `SimpleGraph.IsSimpleCycleIn G c` — a `SimpleCycle` is realized in `G`. +* `SimpleGraph.HasSimpleCycle G` — `G` contains a simple cycle. +* `SimpleGraph.IsAcyclic G` — `G` contains no simple cycle. ## Design choices @@ -48,409 +51,3 @@ reasoning. is the intended primitive and carries the reusable `symm`/`ne`/`left_mem`/ `right_mem` API. -/ - -variable {α : Type*} - -namespace GraphLib - -open scoped GraphLib - -namespace SimpleGraph - -/-! ## The realized-in predicate -/ - -/-- A vertex sequence is *realized in* `G` when its head is a vertex of `G` and -each consecutive pair is an edge of `G`. -/ -@[grind] inductive IsVertexSeqIn (G : SimpleGraph α) : VertexSeq α → Prop - | singleton (v : α) (hv : v ∈ V(G)) : IsVertexSeqIn G (.singleton v) - | cons (w : VertexSeq α) (u : α) - (hw : IsVertexSeqIn G w) - (he : G.Adj w.tail u) : - IsVertexSeqIn G (w.cons u) - -namespace IsVertexSeqIn - -/-! ## Constructor characterizations -/ - -/-- A singleton is realized in `G` exactly when its vertex is in `G`. -/ -@[simp, grind =] lemma singleton_iff (G : SimpleGraph α) (v : α) : - G.IsVertexSeqIn (.singleton v) ↔ v ∈ V(G) := - ⟨fun h => by cases h; assumption, IsVertexSeqIn.singleton v⟩ - -/-- A `cons` is realized in `G` exactly when its prefix is realized and the new -step is an edge. -/ -@[simp, grind =] lemma cons_iff (G : SimpleGraph α) (w : VertexSeq α) (u : α) : - G.IsVertexSeqIn (w.cons u) ↔ G.IsVertexSeqIn w ∧ G.Adj w.tail u := by - constructor - · intro h; cases h with | cons w u hw he => exact ⟨hw, he⟩ - · intro ⟨hw, he⟩; exact .cons w u hw he - -/-! ## Vertex membership -/ - -/-- The head of a realized sequence is a vertex of `G`. -/ -@[grind →] lemma head_mem (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : w.head ∈ V(G) := by - induction hw with - | singleton v hv => exact hv - | cons w u hw he ih => exact ih - -/-- The tail of a realized sequence is a vertex of `G`. -/ -@[grind →] lemma tail_mem (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : w.tail ∈ V(G) := by - induction hw with - | singleton v hv => exact hv - | cons w u hw he ih => exact he.right_mem - -/-- Every vertex of a realized sequence is a vertex of `G`. -/ -@[grind →] lemma mem_vertexSet (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : ∀ v ∈ w, v ∈ V(G) := by - induction hw <;> grind [SimpleGraph.Adj.right_mem] - -/-- A realized sequence never stalls: consecutive vertices differ, because -adjacency in a simple graph forces distinct endpoints. Hence it underlies a -`SimpleWalk`. -/ -@[grind →] lemma nonstalling (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : w.nonstalling := by - induction hw <;> grind - -/-! ## Closure under sequence operations -/ - -/-- Appending two realized sequences along an edge is realized. -/ -@[grind] lemma append (G : SimpleGraph α) {w1 w2 : VertexSeq α} - (h1 : G.IsVertexSeqIn w1) (h2 : G.IsVertexSeqIn w2) - (he : G.Adj w1.tail w2.head) : G.IsVertexSeqIn (w1.append w2) := by - revert he - induction h2 with - | singleton v hv => intro he; exact .cons w1 v h1 he - | cons w u hw hadj ih => - intro he - exact .cons (w1.append w) u (ih he) (by grind [VertexSeq.tail_append]) - -/-- Prepending a vertex along an edge to the head preserves realization. -/ -@[grind →] lemma prepend (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) {u : α} (he : G.Adj u w.head) : - G.IsVertexSeqIn ((VertexSeq.singleton u).append w) := - append G (.singleton u he.left_mem) hw he - -/-- Reversing a realized sequence preserves realization (adjacency is symmetric -in a simple graph). -/ -@[grind →] lemma reverse (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.reverse := by - induction hw with - | singleton v hv => exact .singleton v hv - | cons w u hw he ih => - have hu : G.Adj u w.reverse.head := by - rw [VertexSeq.head_reverse]; exact he.symm - simpa [VertexSeq.reverse] using prepend G ih hu - -/-- Dropping the last vertex preserves realization. -/ -@[grind →] lemma dropTail (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.dropTail := by - induction hw <;> grind - -/-- Dropping the first vertex preserves realization. -/ -@[grind →] lemma dropHead (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.dropHead := by - induction hw with - | singleton v hv => exact .singleton v hv - | cons w u hw he ih => - cases w with - | singleton x => exact .singleton u he.right_mem - | cons t x => exact .cons _ u ih (by rw [VertexSeq.tail_dropHead]; exact he) - -/-- Realization is monotone under passing to a supergraph. -/ -@[grind →] lemma mono (G H : SimpleGraph α) {w : VertexSeq α} - (hw : H.IsVertexSeqIn w) (hsub : SimpleGraph.subgraphOf H G) : - G.IsVertexSeqIn w := by - induction hw with - | singleton v hv => exact .singleton v (hsub.1 hv) - | cons w u hw he ih => exact .cons w u ih (hsub.2 he) - -/-- Taking the prefix up to the first occurrence of `v` preserves realization. -/ -@[grind →] lemma prefixUntil [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : - ∀ (v : α) (h : v ∈ w), G.IsVertexSeqIn (w.prefixUntil v h) := by - induction hw with - | singleton x hx => intro v h; grind - | cons w u hw he ih => - intro v h - by_cases h2 : v ∈ w <;> grind - -/-- Dropping to the suffix from the first occurrence of `v` preserves -realization. -/ -@[grind →] lemma suffixFrom [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : - ∀ (v : α) (h : v ∈ w), G.IsVertexSeqIn (w.suffixFrom v h) := by - induction hw with - | singleton x hx => intro v h; grind - | cons w u hw he ih => - intro v h - by_cases h2 : v ∈ w <;> - grind [VertexSeq.tail_suffixFrom, SimpleGraph.Adj.right_mem] - -/-- Taking the longest prefix on which `p` holds (plus its first failure) -preserves realization. -/ -lemma takeWhile (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) (p : α → Prop) [DecidablePred p] : - G.IsVertexSeqIn (w.takeWhile p) := by - induction hw with - | singleton x hx => exact .singleton x hx - | cons w u hw he ih => - change G.IsVertexSeqIn (if ∃ v ∈ w.toList, ¬ p v then w.takeWhile p else w.cons u) - by_cases hc : ∃ v ∈ w.toList, ¬ p v - · rw [if_pos hc]; exact ih - · rw [if_neg hc]; exact .cons w u hw he - -/-- Dropping the longest prefix on which `p` holds preserves realization. -/ -lemma dropWhile (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) (p : α → Prop) [DecidablePred p] : - ∀ (h : ∃ v ∈ w.toList, ¬ p v), G.IsVertexSeqIn (w.dropWhile p h) := by - induction hw with - | singleton x hx => intro h; exact .singleton x hx - | cons w u hw he ih => - intro h - change G.IsVertexSeqIn - (if hq : ∃ v ∈ w.toList, ¬ p v then (w.dropWhile p hq).cons u else .singleton u) - by_cases hc : ∃ v ∈ w.toList, ¬ p v - · rw [dif_pos hc] - exact .cons (w.dropWhile p hc) u (ih hc) - (by rw [VertexSeq.tail_dropWhile]; exact he) - · rw [dif_neg hc]; exact .singleton u he.right_mem - -/-- If `G` has no edges, every realized sequence has length zero. -/ -lemma length_eq_zero_of_no_edges (G : SimpleGraph α) (hE : E(G) = ∅) - {w : VertexSeq α} (hw : G.IsVertexSeqIn w) : w.length = 0 := by - induction hw <;> grind - -/-- Removing immediate stalls preserves realization. (On a realized — hence -non-stalling — sequence `loopErase` is in fact the identity.) -/ -lemma loopErase [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.loopErase := by - rw [VertexSeq.loopErase_eq_self_of_nonstalling w (nonstalling G hw)] - exact hw - -/-- Cycle erasure preserves realization: dropping the detour between two -occurrences of a vertex keeps the sequence realized in `G`. -/ -lemma cycleErase [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.cycleErase := by - revert hw - fun_induction VertexSeq.cycleErase w <;> - intro hw <;> grind [IsVertexSeqIn.prefixUntil, VertexSeq.tail_cycleErase] - -/-! ## Edge-set characterization -/ - -/-- The edge-set view of realization, bridging the adjacency-based inductive -definition: `w` is realized in `G` exactly when its head is a vertex of `G` and -every edge it traverses is an edge of `G`. -/ -theorem iff_edges (G : SimpleGraph α) (w : VertexSeq α) : - G.IsVertexSeqIn w ↔ w.head ∈ V(G) ∧ ∀ e ∈ w.edges, e ∈ E(G) := by - constructor - · intro hw - refine ⟨head_mem G hw, ?_⟩ - induction hw with - | singleton v hv => intro e he; simp [VertexSeq.edges] at he - | cons w u hw he ih => - intro e hmem - rw [VertexSeq.mem_edges_cons] at hmem - rcases hmem with hmem | rfl - · exact ih e hmem - · exact he - · induction w with - | singleton v => intro h; exact .singleton v h.1 - | cons w u ih => - intro h - rw [cons_iff] - refine ⟨ih ⟨h.1, fun e he => h.2 e ?_⟩, h.2 s(w.tail, u) ?_⟩ - · rw [VertexSeq.mem_edges_cons]; exact Or.inl he - · rw [VertexSeq.mem_edges_cons]; exact Or.inr rfl - -/-- Any edge traversed by a realized vertex sequence is an edge of the graph. -/ -@[grind →] lemma mem_edgeSet_of_mem_edges (G : SimpleGraph α) {w : VertexSeq α} - (hw : G.IsVertexSeqIn w) {e : Sym2 α} (he : e ∈ w.edges) : e ∈ E(G) := - ((iff_edges G w).1 hw).2 e he - -/-- The final step of a non-trivial realized vertex sequence is an adjacency in -the graph. -/ -@[grind →] lemma adj_dropTail_tail_tail_of_length_ne_zero (G : SimpleGraph α) - {w : VertexSeq α} (hw : G.IsVertexSeqIn w) (h : w.length ≠ 0) : - G.Adj w.dropTail.tail w.tail := by - change s(w.dropTail.tail, w.tail) ∈ E(G) - exact mem_edgeSet_of_mem_edges G hw - (by - rw [VertexSeq.edges_eq_dropTail_concat_of_length_ne_zero w h] - simp [List.concat_eq_append]) - -end IsVertexSeqIn - -/-! ## Simple walks realized in a graph -/ - -/-- A simple walk is realized in `G` when its underlying vertex sequence is -realized in `G`. -/ -@[grind] def IsSimpleWalkIn (G : SimpleGraph α) (w : SimpleWalk α) : Prop := - G.IsVertexSeqIn w.val - -namespace IsSimpleWalkIn - -/-! ## Bridge to `IsVertexSeqIn` -/ - -/-- Realization of a simple walk is realization of its underlying vertex -sequence. -/ -@[simp, grind =] lemma isSimpleWalkIn_iff_isVertexSeqIn (G : SimpleGraph α) (w : SimpleWalk α) : - G.IsSimpleWalkIn w ↔ G.IsVertexSeqIn w.val := Iff.rfl - -/-! ## Vertex and edge membership -/ - -/-- The head of a realized walk is a vertex of `G`. -/ -@[grind →] lemma head_mem (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : w.head ∈ V(G) := - IsVertexSeqIn.head_mem G hw - -/-- The tail of a realized walk is a vertex of `G`. -/ -@[grind →] lemma tail_mem (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : w.tail ∈ V(G) := - IsVertexSeqIn.tail_mem G hw - -/-- Every vertex visited by a realized walk is a vertex of `G`. -/ -@[grind →] lemma mem_vertexSet (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : ∀ v ∈ w.support, v ∈ V(G) := - IsVertexSeqIn.mem_vertexSet G hw - -/-- Any edge traversed by a realized walk is an edge of `G`. -/ -@[grind →] lemma mem_edgeSet_of_mem_edges (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) {e : Sym2 α} (he : e ∈ w.edges) : e ∈ E(G) := - IsVertexSeqIn.mem_edgeSet_of_mem_edges G hw he - -/-- A walk is realized in `G` exactly when its head is a vertex of `G` and every -edge it traverses is an edge of `G`. -/ -theorem iff_edges (G : SimpleGraph α) (w : SimpleWalk α) : - G.IsSimpleWalkIn w ↔ w.head ∈ V(G) ∧ ∀ e ∈ w.edges, e ∈ E(G) := - IsVertexSeqIn.iff_edges G w.val - -/-! ## Traversed edges and the generated simple graph -/ - -/-- All edges traversed by a realized simple walk are edges of `G`, as a bundled -edge-list predicate. -/ -lemma edges_subset_edgeSet (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : ∀ e ∈ w.edges, e ∈ E(G) := - (iff_edges G w).1 hw |>.2 - -/-- The simple graph generated by a realized simple walk is a subgraph of `G`. -/ -lemma toSimpleGraph_subgraphOf (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : SimpleGraph.subgraphOf w.toSimpleGraph G := by - refine ⟨?_, ?_⟩ - · intro v hv - exact mem_vertexSet G hw v (by simpa using hv) - · intro e he - exact mem_edgeSet_of_mem_edges G hw (by simpa using he) - -/-- If the graph generated by a simple walk is a subgraph of `G`, then the walk -is realized in `G`. -/ -lemma of_toSimpleGraph_subgraphOf (G : SimpleGraph α) {w : SimpleWalk α} - (hsub : SimpleGraph.subgraphOf w.toSimpleGraph G) : G.IsSimpleWalkIn w := by - rw [iff_edges] - refine ⟨?_, ?_⟩ - · exact hsub.1 (by simp [SimpleWalk.support]) - · intro e he - exact hsub.2 (by simpa using he) - -/-- A simple walk is realized in `G` exactly when its generated simple graph is -a subgraph of `G`. -/ -theorem iff_toSimpleGraph_subgraphOf (G : SimpleGraph α) (w : SimpleWalk α) : - G.IsSimpleWalkIn w ↔ SimpleGraph.subgraphOf w.toSimpleGraph G := - ⟨toSimpleGraph_subgraphOf G, of_toSimpleGraph_subgraphOf G⟩ - -/-! ## Closure under walk operations -/ - -/-- Reversing a realized walk preserves realization. -/ -@[grind →] lemma reverse (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.reverse := - IsVertexSeqIn.reverse G hw - -/-- Dropping the last vertex of a realized walk preserves realization. -/ -@[grind →] lemma dropTail (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.dropTail := - IsVertexSeqIn.dropTail G hw - -/-- Dropping the first vertex of a realized walk preserves realization. -/ -@[grind →] lemma dropHead (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.dropHead := - IsVertexSeqIn.dropHead G hw - -/-- Taking a prefix of a realized walk preserves realization. -/ -@[grind →] lemma prefixUntil [DecidableEq α] (G : SimpleGraph α) - {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (v : α) (h : v ∈ w.val) : - G.IsSimpleWalkIn (w.prefixUntil v h) := - IsVertexSeqIn.prefixUntil G hw v h - -/-- Taking a suffix of a realized walk preserves realization. -/ -@[grind →] lemma suffixFrom [DecidableEq α] (G : SimpleGraph α) - {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (v : α) (h : v ∈ w.val) : - G.IsSimpleWalkIn (w.suffixFrom v h) := - IsVertexSeqIn.suffixFrom G hw v h - -/-- Taking the longest prefix on which `p` holds (plus its first failure) -preserves realization. -/ -lemma takeWhile (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) (p : α → Prop) [DecidablePred p] : - G.IsSimpleWalkIn (w.takeWhile p) := - IsVertexSeqIn.takeWhile G hw p - -/-- Dropping the longest prefix on which `p` holds preserves realization. -/ -lemma dropWhile (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) (p : α → Prop) [DecidablePred p] - (h : ∃ v ∈ w.val.toList, ¬ p v) : - G.IsSimpleWalkIn (w.dropWhile p h) := - IsVertexSeqIn.dropWhile G hw p h - -/-- Loop erasure preserves realization. On a simple walk this operation is the -identity on the underlying sequence. -/ -lemma loopErase [DecidableEq α] (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn (w.loopErase) := - IsVertexSeqIn.loopErase G hw - -/-- Cycle erasure preserves realization. -/ -lemma cycleErase [DecidableEq α] (G : SimpleGraph α) {w : SimpleWalk α} - (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn (w.cycleErase) := - IsVertexSeqIn.cycleErase G hw - -/-- Realization is monotone under passing to a supergraph. -/ -@[grind →] lemma mono (G H : SimpleGraph α) {w : SimpleWalk α} - (hw : H.IsSimpleWalkIn w) (hsub : SimpleGraph.subgraphOf H G) : - G.IsSimpleWalkIn w := - IsVertexSeqIn.mono G H hw hsub - -/-- If `G` has no edges, every realized walk in `G` has length zero. -/ -lemma length_eq_zero_of_no_edges (G : SimpleGraph α) (hE : E(G) = ∅) - {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) : w.length = 0 := - IsVertexSeqIn.length_eq_zero_of_no_edges G hE hw - -/-! ## Joining walks -/ - -/-- Appending two realized walks along an edge of `G` preserves realization. -/ -lemma append (G : SimpleGraph α) {p q : SimpleWalk α} - (hp : G.IsSimpleWalkIn p) (hq : G.IsSimpleWalkIn q) - (he : G.Adj p.tail q.head) : - G.IsSimpleWalkIn (p.append q he.ne) := - IsVertexSeqIn.append G hp hq he - -/-- Gluing two realized walks at a shared endpoint preserves realization. -/ -lemma glue (G : SimpleGraph α) {p q : SimpleWalk α} - (hp : G.IsSimpleWalkIn p) (hq : G.IsSimpleWalkIn q) - (h : p.tail = q.head) : - G.IsSimpleWalkIn (p.glue q h) := by - unfold SimpleWalk.glue - by_cases hlen : p.val.length = 0 - · rw [dif_pos hlen] - exact hq - · rw [dif_neg hlen] - exact IsVertexSeqIn.append G (IsVertexSeqIn.dropTail G hp) hq - (by - have h' : p.val.tail = q.val.head := h - rw [← h'] - exact IsVertexSeqIn.adj_dropTail_tail_tail_of_length_ne_zero G hp hlen) - -end IsSimpleWalkIn - -end SimpleGraph - -end GraphLib diff --git a/GraphLib/Theory/Structures/InSimpleGraph/Cycle.lean b/GraphLib/Theory/Structures/InSimpleGraph/Cycle.lean new file mode 100644 index 0000000..6f3b0e1 --- /dev/null +++ b/GraphLib/Theory/Structures/InSimpleGraph/Cycle.lean @@ -0,0 +1,218 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.InSimpleGraph.Path +import GraphLib.Theory.Structures.SimpleCycle +import Mathlib.Data.Set.Card + +/-! +# Simple cycles realized in a simple graph + +`SimpleGraph.IsSimpleCycleIn G c` says a `SimpleCycle` is realized in `G`, +together with `HasSimpleCycle` / `IsAcyclic` and the cycle-construction lemmas +that build a realized cycle out of realized paths. + +Part of the `InSimpleGraph` folder; see the umbrella module +`GraphLib.Theory.Structures.InSimpleGraph`. + +## Main definitions + +* `SimpleGraph.IsSimpleCycleIn` — a simple cycle realized in a graph. +* `SimpleGraph.HasSimpleCycle` — the graph contains a realized simple cycle. +* `SimpleGraph.IsAcyclic` — the graph contains no realized simple cycle. + +## Main results + +* `SimpleGraph.IsSimpleCycleIn.ofPathClosing` — close a realized path into a cycle. +* `SimpleGraph.IsSimpleCycleIn.ofTwoPaths` — obtain a realized cycle from two paths. +* `SimpleGraph.hasSimpleCycle_of_subgraph` — cycles pass from subgraphs to supergraphs. +* `SimpleGraph.isAcyclic_of_subgraph` — acyclicity passes from supergraphs to subgraphs. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +/-! ## Simple cycles realized in a graph -/ + +/-- A simple cycle is realized in `G` when its underlying simple walk is +realized in `G`. -/ +@[grind] def IsSimpleCycleIn (G : SimpleGraph α) (c : SimpleCycle α) : Prop := + G.IsSimpleWalkIn c.val + +namespace IsSimpleCycleIn + +/-- Realization of a simple cycle is realization of its underlying simple walk. -/ +@[simp, grind =] lemma iff_isSimpleWalkIn + (G : SimpleGraph α) (c : SimpleCycle α) : + G.IsSimpleCycleIn c ↔ G.IsSimpleWalkIn c.val := Iff.rfl + +/-- Reversing a realized cycle preserves realization. -/ +@[grind →] lemma reverse (G : SimpleGraph α) {c : SimpleCycle α} + (hc : G.IsSimpleCycleIn c) : G.IsSimpleCycleIn c.reverse := + IsSimpleWalkIn.reverse G hc + +/-- Every edge traversed by a realized cycle is an edge of the graph. -/ +@[grind →] lemma edge_mem (G : SimpleGraph α) {c : SimpleCycle α} + (hc : G.IsSimpleCycleIn c) {e : Sym2 α} (he : e ∈ c.edges) : e ∈ E(G) := + IsSimpleWalkIn.edge_mem G hc he + +/-- The generated graph of a realized cycle is a subgraph of `G`. -/ +lemma toSimpleGraph_subgraphOf (G : SimpleGraph α) {c : SimpleCycle α} + (hc : G.IsSimpleCycleIn c) : SimpleGraph.subgraphOf c.val.toSimpleGraph G := + IsSimpleWalkIn.toSimpleGraph_subgraphOf G hc + +/-- Realization is monotone under passing to a supergraph. -/ +@[grind →] lemma mono (G H : SimpleGraph α) {c : SimpleCycle α} + (hc : H.IsSimpleCycleIn c) (hsub : SimpleGraph.subgraphOf H G) : + G.IsSimpleCycleIn c := + IsSimpleWalkIn.mono G H hc hsub + +/-- Closing a realized simple path by an edge from its tail to its head gives a +realized simple cycle. -/ +lemma ofPathClosing (G : SimpleGraph α) {p : SimplePath α} + (hp : G.IsSimplePathIn p) (hclose : G.Adj p.tail p.head) + (hlen : 2 ≤ p.length) : + G.IsSimpleCycleIn (SimpleCycle.ofPathClosing p hlen) := by + change G.IsSimpleWalkIn (SimpleCycle.ofPathClosing p hlen).val + change G.IsVertexSeqIn (VertexSeq.cons (SimplePath.vertices p) (SimplePath.head p)) + exact IsVertexSeqIn.cons (SimplePath.vertices p) (SimplePath.head p) hp hclose + +/-- Two realized internally disjoint paths with the same endpoints form a +realized simple cycle. -/ +lemma ofInternallyDisjointPaths (G : SimpleGraph α) {P Q : SimplePath α} + (hP : G.IsSimplePathIn P) (hQ : G.IsSimplePathIn Q) + (hhead : P.head = Q.head) (htail : P.tail = Q.tail) + (hab : P.head ≠ P.tail) + (hint : ∀ z, z ∈ P.vertices → z ∈ Q.vertices → + z = P.head ∨ z = P.tail) + (hlen : 3 ≤ P.length + Q.length) : + G.IsSimpleCycleIn + (SimpleCycle.ofInternallyDisjointPaths P Q hhead htail hab hint hlen) := by + change G.IsSimpleWalkIn + (SimpleCycle.ofInternallyDisjointPaths P Q hhead htail hab hint hlen).val + unfold SimpleCycle.ofInternallyDisjointPaths + apply IsSimpleWalkIn.glue G hP (IsSimpleWalkIn.reverse G hQ) + change P.vertices.tail = Q.vertices.reverse.head + simpa only [VertexSeq.head_reverse] using htail + +/-- Two distinct realized simple paths with the same endpoints determine a +realized simple cycle. -/ +lemma ofTwoPaths [DecidableEq α] (G : SimpleGraph α) {p q : SimplePath α} + (hp : G.IsSimplePathIn p) (hq : G.IsSimplePathIn q) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : + G.IsSimpleCycleIn (SimpleCycle.ofTwoPaths p q hhead htail hne) := by + rw [iff_isSimpleWalkIn, IsSimpleWalkIn.iff_edges] + refine ⟨IsSimpleWalkIn.mem_vertexSet G hp + (SimpleCycle.head_ofTwoPaths_mem_left p q hhead htail hne), ?_⟩ + intro e he + rcases SimpleCycle.edges_ofTwoPaths_subset p q hhead htail hne he with hep | heq + · exact IsSimpleWalkIn.edge_mem G hp hep + · exact IsSimpleWalkIn.edge_mem G hq heq + +/-- The length of a realized simple cycle is bounded by the number of vertices +of the ambient graph. -/ +lemma length_le_ncard_vertexSet (G : SimpleGraph α) (hV : V(G).Finite) + {c : SimpleCycle α} (hc : G.IsSimpleCycleIn c) : + c.length ≤ V(G).ncard := by + have hinterior : G.IsSimplePathIn (SimpleCycle.interior c) := + IsSimpleWalkIn.dropTail G hc + have hpath := IsSimplePathIn.length_succ_le_ncard_vertexSet G hV hinterior + have hthree : 3 ≤ (SimpleCycle.vertices c).length := c.2.1 + have hpos : (SimpleCycle.vertices c).length ≠ 0 := by omega + have hlen_cycle : (SimpleCycle.interior c).length + 1 = c.length := by + simpa [SimpleCycle.interior, SimplePath.length, SimpleWalk.dropTail, + SimpleCycle.length, SimpleCycle.vertices] using + VertexSeq.length_dropTail_succ (SimpleCycle.vertices c) hpos + omega + +/-! ## Existence of a short cycle from realized paths + +Where `ofPathClosing` / `ofInternallyDisjointPaths` / `ofTwoPaths` above are the +constructor bridges — they say *this particular* cycle is realized — the two +lemmas here are the existence corollaries a caller actually wants: some realized +cycle exists, and it is no longer than the paths it was built from. The length +bound is what the Moore argument consumes. + +They depend on neither girth nor the Moore development, so they live here in the +`InSimpleGraph` layer rather than in the Moore-bound file that first needed +them. -/ + +/-- If a vertex on a realized simple path is adjacent to the tail and the +suffix starting at that vertex has length at least two, then closing that suffix +gives a simple cycle. -/ +lemma exists_length_le_succ_of_adj_mem (G : SimpleGraph α) [DecidableEq α] + {p : SimplePath α} (hp : G.IsSimplePathIn p) {y : α} + (hy_mem : y ∈ p.vertices) (hadj : G.Adj p.tail y) + (hlen : 2 ≤ (p.vertices.suffixFrom y hy_mem).length) : + ∃ c : SimpleCycle α, G.IsSimpleCycleIn c ∧ c.length ≤ p.length + 1 := by + let q : SimplePath α := p.suffixFrom y hy_mem + have hq : G.IsSimplePathIn q := + SimpleGraph.IsSimpleWalkIn.suffixFrom G hp y hy_mem + have hclose : G.Adj q.tail q.head := by + change G.Adj (p.vertices.suffixFrom y hy_mem).tail + (p.vertices.suffixFrom y hy_mem).head + simpa only [VertexSeq.tail_suffixFrom, VertexSeq.head_suffixFrom] using hadj + refine ⟨SimpleCycle.ofPathClosing q hlen, + IsSimpleCycleIn.ofPathClosing G hq hclose hlen, ?_⟩ + have hq_le : (p.vertices.suffixFrom y hy_mem).length ≤ p.vertices.length := + VertexSeq.length_suffixFrom_le p.vertices y hy_mem + change (VertexSeq.cons (p.vertices.suffixFrom y hy_mem) + (p.vertices.suffixFrom y hy_mem).head).length ≤ p.vertices.length + 1 + simpa only [VertexSeq.length, Nat.add_comm] using Nat.add_le_add_right hq_le 1 + +/-- Two distinct realized simple paths with common endpoints determine a +realized simple cycle whose length is at most the sum of their lengths. -/ +lemma exists_length_le_add_of_two_paths (G : SimpleGraph α) + {p q : SimplePath α} (hp : G.IsSimplePathIn p) (hq : G.IsSimplePathIn q) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : + ∃ c : SimpleCycle α, G.IsSimpleCycleIn c ∧ c.length ≤ p.length + q.length := by + classical + exact ⟨SimpleCycle.ofTwoPaths p q hhead htail hne, + ofTwoPaths G hp hq hhead htail hne, + SimpleCycle.length_ofTwoPaths p q hhead htail hne⟩ + +end IsSimpleCycleIn + +/-! ## Acyclicity -/ + +/-- `G` contains a simple cycle. -/ +@[grind] def HasSimpleCycle (G : SimpleGraph α) : Prop := + ∃ c : SimpleCycle α, G.IsSimpleCycleIn c + +/-- `G` is acyclic when it contains no simple cycle. -/ +@[grind] def IsAcyclic (G : SimpleGraph α) : Prop := + ¬ G.HasSimpleCycle + +/-- A simple cycle in a subgraph is also a simple cycle in the ambient graph. -/ +lemma hasSimpleCycle_of_subgraph (G H : SimpleGraph α) + (hsub : SimpleGraph.subgraphOf H G) (hH : H.HasSimpleCycle) : + G.HasSimpleCycle := by + obtain ⟨c, hc⟩ := hH + exact ⟨c, IsSimpleCycleIn.mono G H hc hsub⟩ + +/-- If `H` is a subgraph of the acyclic ambient graph `G`, then `H` is acyclic. -/ +lemma isAcyclic_of_subgraph (G H : SimpleGraph α) + (hsub : SimpleGraph.subgraphOf H G) (hG : G.IsAcyclic) : + H.IsAcyclic := by + intro hH + exact hG (hasSimpleCycle_of_subgraph G H hsub hH) + +/-- An edge-free graph is acyclic. -/ +@[grind →] lemma isAcyclic_of_no_edges (G : SimpleGraph α) (hE : E(G) = ∅) : + G.IsAcyclic := by + rintro ⟨c, hc⟩ + obtain ⟨e, he⟩ := List.exists_mem_of_ne_nil c.edges c.edges_ne_nil + simpa only [hE, Set.mem_empty_iff_false] using IsSimpleCycleIn.edge_mem G hc he + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/InSimpleGraph/Path.lean b/GraphLib/Theory/Structures/InSimpleGraph/Path.lean new file mode 100644 index 0000000..2972dec --- /dev/null +++ b/GraphLib/Theory/Structures/InSimpleGraph/Path.lean @@ -0,0 +1,83 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.InSimpleGraph.Walk +import GraphLib.Theory.Structures.SimplePath +import Mathlib.Data.Set.Card + +/-! +# Simple paths realized in a simple graph + +`SimpleGraph.IsSimplePathIn G p` says a `SimplePath` is realized in `G` through +its underlying simple walk, with the path-specific API (extension by a fresh +adjacent vertex, the vertex-count bound). + +Part of the `InSimpleGraph` folder; see the umbrella module +`GraphLib.Theory.Structures.InSimpleGraph`. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +/-! ## Simple paths realized in a graph -/ + +/-- A simple path is realized in `G` when its underlying simple walk is +realized in `G`. -/ +@[grind] def IsSimplePathIn (G : SimpleGraph α) (p : SimplePath α) : Prop := + G.IsSimpleWalkIn p.val + +namespace IsSimplePathIn + +/-- Realization of a simple path is realization of its underlying simple walk. -/ +@[simp, grind =] lemma iff_isSimpleWalkIn (G : SimpleGraph α) (p : SimplePath α) : + G.IsSimplePathIn p ↔ G.IsSimpleWalkIn p.val := Iff.rfl + +/-- A singleton path is realized exactly when its vertex is in the graph. -/ +lemma singleton (G : SimpleGraph α) {v : α} (hv : v ∈ V(G)) : + G.IsSimplePathIn (SimplePath.singleton v) := + IsVertexSeqIn.singleton v hv + +/-- Extending a realized simple path by a fresh adjacent vertex gives a realized +simple path whose length is one larger. -/ +lemma exists_longer_of_adj_not_mem (G : SimpleGraph α) {p : SimplePath α} {v : α} + (hp : G.IsSimplePathIn p) (hadj : G.Adj p.tail v) (hnot : v ∉ p.vertices) : + ∃ q : SimplePath α, G.IsSimplePathIn q ∧ q.length = p.length + 1 := by + have hdisj : ∀ u : α, u ∈ p.vertices → + u ∈ (SimplePath.singleton v).vertices → False := by + grind + refine ⟨p.append (SimplePath.singleton v) hdisj, + IsSimpleWalkIn.append G hp (singleton G hadj.right_mem) hadj, ?_⟩ + simp + +/-- The number of vertices of a realized simple path is bounded by the number +of vertices of the ambient graph. -/ +lemma length_succ_le_ncard_vertexSet (G : SimpleGraph α) (hV : V(G).Finite) + {p : SimplePath α} (hp : G.IsSimplePathIn p) : + p.length + 1 ≤ V(G).ncard := by + classical + let S : Set α := {v | v ∈ p.support} + have hS : S ⊆ V(G) := fun v hv => IsSimpleWalkIn.mem_vertexSet G hp hv + have hnodup : p.support.Nodup := by + simpa [SimplePath.support] using + (VertexSeq.nodup_iff_toList_nodup (SimplePath.vertices p)).1 (SimplePath.nodup p) + have hS_card : S.ncard = p.support.length := by + rw [show S = (p.support.toFinset : Set α) by ext v; simp [S], Set.ncard_coe_finset, + List.toFinset_card_of_nodup hnodup] + have hlen : p.support.length = p.length + 1 := by + simpa [SimplePath.support, SimplePath.length] using + VertexSeq.length_toList (SimplePath.vertices p) + have hcard : S.ncard ≤ V(G).ncard := Set.ncard_le_ncard hS hV + omega + +end IsSimplePathIn + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/InSimpleGraph/VertexSeq.lean b/GraphLib/Theory/Structures/InSimpleGraph/VertexSeq.lean new file mode 100644 index 0000000..bc7b866 --- /dev/null +++ b/GraphLib/Theory/Structures/InSimpleGraph/VertexSeq.lean @@ -0,0 +1,229 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Graph.Adjacency +import GraphLib.Graph.Subgraph +import GraphLib.Theory.Structures.VertexSeq + +/-! +# Vertex sequences realized in a simple graph + +The predicate `SimpleGraph.IsVertexSeqIn G w` says the vertex sequence `w` is +*realized* in `G`: its head is a vertex of `G` and every consecutive pair is an +edge of `G` (phrased through `SimpleGraph.Adj`). This is the base layer of the +`InSimpleGraph` development; walks, paths and cycles build on it. + +## Design choices + +* **Adjacency, not edge sets.** The `cons` step is stated with `G.Adj w.tail u` + rather than `s(w.tail, u) ∈ E(G)`. The two are definitionally equal, but `Adj` + is the intended primitive and carries the reusable `symm`/`ne`/`left_mem`/ + `right_mem` API. + +Part of the `InSimpleGraph` folder; see the umbrella module +`GraphLib.Theory.Structures.InSimpleGraph` for the overview. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +/-! ## The realized-in predicate -/ + +/-- A vertex sequence is *realized in* `G` when its head is a vertex of `G` and +each consecutive pair is an edge of `G`. -/ +@[grind] inductive IsVertexSeqIn (G : SimpleGraph α) : VertexSeq α → Prop + | singleton (v : α) (hv : v ∈ V(G)) : IsVertexSeqIn G (.singleton v) + | cons (w : VertexSeq α) (u : α) + (hw : IsVertexSeqIn G w) + (he : G.Adj w.tail u) : + IsVertexSeqIn G (w.cons u) + +namespace IsVertexSeqIn + +/-! ## Constructor characterizations -/ + +/-- A singleton is realized in `G` exactly when its vertex is in `G`. -/ +@[simp, grind =] lemma singleton_iff (G : SimpleGraph α) (v : α) : + G.IsVertexSeqIn (.singleton v) ↔ v ∈ V(G) := + ⟨fun h => by cases h; assumption, IsVertexSeqIn.singleton v⟩ + +/-- A `cons` is realized in `G` exactly when its prefix is realized and the new +step is an edge. -/ +@[simp, grind =] lemma cons_iff (G : SimpleGraph α) (w : VertexSeq α) (u : α) : + G.IsVertexSeqIn (w.cons u) ↔ G.IsVertexSeqIn w ∧ G.Adj w.tail u := by + constructor + · intro h; cases h with | cons w u hw he => exact ⟨hw, he⟩ + · intro ⟨hw, he⟩; exact .cons w u hw he + +/-! ## Vertex membership -/ + +/-- The head of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma head_mem (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.head ∈ V(G) := by + induction hw with + | singleton v hv => exact hv + | cons w u hw he ih => exact ih + +/-- The tail of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma tail_mem (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.tail ∈ V(G) := by + induction hw with + | singleton v hv => exact hv + | cons w u hw he ih => exact he.right_mem + +/-- Every vertex of a realized sequence is a vertex of `G`. -/ +@[grind →] lemma mem_vertexSet (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) {v : α} (hv : v ∈ w) : v ∈ V(G) := by + revert v + induction hw <;> grind [SimpleGraph.Adj.right_mem] + +/-! ## Shape of a realized sequence -/ + +/-- A realized sequence never stalls: consecutive vertices differ, because +adjacency in a simple graph forces distinct endpoints. Hence it underlies a +`SimpleWalk`. -/ +@[grind →] lemma nonstalling (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : w.nonstalling := by + induction hw <;> grind + +/-! ## Closure under sequence operations -/ + +/-- Appending two realized sequences along an edge is realized. -/ +@[grind] lemma append (G : SimpleGraph α) {w1 w2 : VertexSeq α} + (h1 : G.IsVertexSeqIn w1) (h2 : G.IsVertexSeqIn w2) + (he : G.Adj w1.tail w2.head) : G.IsVertexSeqIn (w1.append w2) := by + induction h2 <;> grind [VertexSeq.tail_append] + +/-- Prepending a vertex along an edge to the head preserves realization. -/ +@[grind →] lemma prepend (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) {u : α} (he : G.Adj u w.head) : + G.IsVertexSeqIn ((VertexSeq.singleton u).append w) := + append G (.singleton u he.left_mem) hw he + +/-- Reversing a realized sequence preserves realization (adjacency is symmetric +in a simple graph). -/ +@[grind →] lemma reverse (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.reverse := by + induction hw <;> grind [VertexSeq.reverse, VertexSeq.head_reverse] + +/-- Dropping the last vertex preserves realization. -/ +@[grind →] lemma dropTail (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.dropTail := by + induction hw <;> grind + +/-- Dropping the first vertex preserves realization. -/ +@[grind →] lemma dropHead (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.dropHead := by + induction hw with + | singleton v hv => exact .singleton v hv + | cons w u hw he ih => + cases w with + | singleton x => exact .singleton u he.right_mem + | cons t x => exact .cons _ u ih (by rw [VertexSeq.tail_dropHead]; exact he) + +/-- Taking the prefix up to the first occurrence of `v` preserves realization. -/ +@[grind →] lemma prefixUntil [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) (v : α) (h : v ∈ w) : + G.IsVertexSeqIn (w.prefixUntil v h) := by + fun_induction VertexSeq.prefixUntil w v h <;> grind + +/-- Dropping to the suffix from the first occurrence of `v` preserves +realization. -/ +@[grind →] lemma suffixFrom [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) (v : α) (h : v ∈ w) : + G.IsVertexSeqIn (w.suffixFrom v h) := by + fun_induction VertexSeq.suffixFrom w v h <;> + grind [VertexSeq.tail_suffixFrom, SimpleGraph.Adj.right_mem] + +/-- Taking the longest prefix on which `p` holds (plus its first failure) +preserves realization. -/ +lemma takeWhile (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) (p : α → Prop) [DecidablePred p] : + G.IsVertexSeqIn (w.takeWhile p) := by + fun_induction VertexSeq.takeWhile w p <;> grind + +/-- Dropping the longest prefix on which `p` holds preserves realization. -/ +lemma dropWhile (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) (p : α → Prop) [DecidablePred p] + (h : ∃ v ∈ w.toList, ¬ p v) : G.IsVertexSeqIn (w.dropWhile p h) := by + fun_induction VertexSeq.dropWhile w p h <;> + grind [VertexSeq.tail_dropWhile, SimpleGraph.Adj.right_mem] + +/-- Removing immediate stalls preserves realization. (On a realized — hence +non-stalling — sequence `loopErase` is in fact the identity.) -/ +lemma loopErase [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.loopErase := by + rw [VertexSeq.loopErase_eq_self_of_nonstalling w (nonstalling G hw)] + exact hw + +/-- Cycle erasure preserves realization: dropping the detour between two +occurrences of a vertex keeps the sequence realized in `G`. -/ +lemma cycleErase [DecidableEq α] (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : G.IsVertexSeqIn w.cycleErase := by + revert hw + fun_induction VertexSeq.cycleErase w <;> + intro hw <;> grind [IsVertexSeqIn.prefixUntil, VertexSeq.tail_cycleErase] + +/-! ## Monotonicity -/ + +/-- Realization is monotone under passing to a supergraph. -/ +@[grind →] lemma mono (G H : SimpleGraph α) {w : VertexSeq α} + (hw : H.IsVertexSeqIn w) (hsub : SimpleGraph.subgraphOf H G) : + G.IsVertexSeqIn w := by + induction hw with + | singleton v hv => exact .singleton v (hsub.1 hv) + | cons w u hw he ih => exact .cons w u ih (hsub.2 he) + +/-! ## Edge-free graphs -/ + +/-- If `G` has no edges, every realized sequence has length zero. -/ +lemma length_zero_of_no_edges (G : SimpleGraph α) (hE : E(G) = ∅) + {w : VertexSeq α} (hw : G.IsVertexSeqIn w) : w.length = 0 := by + induction hw <;> grind + +/-! ## Edge-set characterization -/ + +/-- The edge-set view of realization, bridging the adjacency-based inductive +definition: `w` is realized in `G` exactly when its head is a vertex of `G` and +every edge it traverses is an edge of `G`. -/ +theorem iff_edges (G : SimpleGraph α) (w : VertexSeq α) : + G.IsVertexSeqIn w ↔ w.head ∈ V(G) ∧ ∀ e ∈ w.edges, e ∈ E(G) := by + constructor + · intro hw + refine ⟨head_mem G hw, ?_⟩ + induction hw <;> grind [VertexSeq.mem_edges_cons] + · induction w with + | singleton v => intro h; exact .singleton v h.1 + | cons w u ih => + intro h + rw [cons_iff] + refine ⟨ih ⟨h.1, fun e he => h.2 e ?_⟩, h.2 s(w.tail, u) ?_⟩ + · rw [VertexSeq.mem_edges_cons]; exact Or.inl he + · rw [VertexSeq.mem_edges_cons]; exact Or.inr rfl + +/-- Any edge traversed by a realized vertex sequence is an edge of the graph. -/ +@[grind →] lemma edge_mem (G : SimpleGraph α) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) {e : Sym2 α} (he : e ∈ w.edges) : e ∈ E(G) := + ((iff_edges G w).1 hw).2 e he + +/-- The final step of a non-trivial realized vertex sequence is an adjacency in +the graph. -/ +@[grind →] lemma last_adj (G : SimpleGraph α) + {w : VertexSeq α} (hw : G.IsVertexSeqIn w) (h : w.length ≠ 0) : + G.Adj w.dropTail.tail w.tail := by + apply edge_mem G hw + rw [VertexSeq.edges_eq_dropTail_concat w h] + simp [List.concat_eq_append] + +end IsVertexSeqIn + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/InSimpleGraph/Walk.lean b/GraphLib/Theory/Structures/InSimpleGraph/Walk.lean new file mode 100644 index 0000000..d986a58 --- /dev/null +++ b/GraphLib/Theory/Structures/InSimpleGraph/Walk.lean @@ -0,0 +1,189 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.InSimpleGraph.VertexSeq +import GraphLib.Theory.Structures.SimpleWalk + +/-! +# Simple walks realized in a simple graph + +`SimpleGraph.IsSimpleWalkIn G w` says a `SimpleWalk` is realized in `G` through +its underlying vertex sequence. This file lifts the `IsVertexSeqIn` API to +`SimpleWalk`, adds the edge-set / generated-subgraph bridge, and closure under +the walk operations (`append`, `glue`, `prefixUntil`, …). + +Part of the `InSimpleGraph` folder; see the umbrella module +`GraphLib.Theory.Structures.InSimpleGraph`. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +/-! ## Simple walks realized in a graph -/ + +/-- A simple walk is realized in `G` when its underlying vertex sequence is +realized in `G`. -/ +@[grind] def IsSimpleWalkIn (G : SimpleGraph α) (w : SimpleWalk α) : Prop := + G.IsVertexSeqIn w.val + +namespace IsSimpleWalkIn + +/-! ## Bridge to `IsVertexSeqIn` -/ + +/-- Realization of a simple walk is realization of its underlying vertex +sequence. -/ +@[simp, grind =] lemma iff_isVertexSeqIn (G : SimpleGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ G.IsVertexSeqIn w.val := Iff.rfl + +/-! ## Vertex and edge membership -/ + +/-- The head of a realized walk is a vertex of `G`. -/ +@[grind →] lemma head_mem (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : w.head ∈ V(G) := + IsVertexSeqIn.head_mem G hw + +/-- The tail of a realized walk is a vertex of `G`. -/ +@[grind →] lemma tail_mem (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : w.tail ∈ V(G) := + IsVertexSeqIn.tail_mem G hw + +/-- Every vertex visited by a realized walk is a vertex of `G`. -/ +@[grind →] lemma mem_vertexSet (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) {v : α} (hv : v ∈ w.support) : v ∈ V(G) := + IsVertexSeqIn.mem_vertexSet G hw hv + +/-- Any edge traversed by a realized walk is an edge of `G`. -/ +@[grind →] lemma edge_mem (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) {e : Sym2 α} (he : e ∈ w.edges) : e ∈ E(G) := + IsVertexSeqIn.edge_mem G hw he + +/-- A walk is realized in `G` exactly when its head is a vertex of `G` and every +edge it traverses is an edge of `G`. -/ +theorem iff_edges (G : SimpleGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ w.head ∈ V(G) ∧ ∀ e ∈ w.edges, e ∈ E(G) := + IsVertexSeqIn.iff_edges G w.val + +/-! ## Traversed edges and the generated simple graph -/ + +/-- The simple graph generated by a realized simple walk is a subgraph of `G`. -/ +lemma toSimpleGraph_subgraphOf (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : SimpleGraph.subgraphOf w.toSimpleGraph G := by + exact ⟨fun v hv => mem_vertexSet G hw (by simpa using hv), + fun e he => edge_mem G hw (by simpa using he)⟩ + +/-- If the graph generated by a simple walk is a subgraph of `G`, then the walk +is realized in `G`. -/ +lemma of_toSimpleGraph_subgraphOf (G : SimpleGraph α) {w : SimpleWalk α} + (hsub : SimpleGraph.subgraphOf w.toSimpleGraph G) : G.IsSimpleWalkIn w := by + rw [iff_edges] + exact ⟨hsub.1 (by simp [SimpleWalk.support]), + fun e he => hsub.2 (by simpa using he)⟩ + +/-- A simple walk is realized in `G` exactly when its generated simple graph is +a subgraph of `G`. -/ +theorem iff_toSimpleGraph_subgraphOf (G : SimpleGraph α) (w : SimpleWalk α) : + G.IsSimpleWalkIn w ↔ SimpleGraph.subgraphOf w.toSimpleGraph G := + ⟨toSimpleGraph_subgraphOf G, of_toSimpleGraph_subgraphOf G⟩ + +/-! ## Closure under walk operations -/ + +/-- Reversing a realized walk preserves realization. -/ +@[grind →] lemma reverse (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.reverse := + IsVertexSeqIn.reverse G hw + +/-- Dropping the last vertex of a realized walk preserves realization. -/ +@[grind →] lemma dropTail (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.dropTail := + IsVertexSeqIn.dropTail G hw + +/-- Dropping the first vertex of a realized walk preserves realization. -/ +@[grind →] lemma dropHead (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn w.dropHead := + IsVertexSeqIn.dropHead G hw + +/-- Taking a prefix of a realized walk preserves realization. -/ +@[grind →] lemma prefixUntil [DecidableEq α] (G : SimpleGraph α) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (v : α) (h : v ∈ w.val) : + G.IsSimpleWalkIn (w.prefixUntil v h) := + IsVertexSeqIn.prefixUntil G hw v h + +/-- Taking a suffix of a realized walk preserves realization. -/ +@[grind →] lemma suffixFrom [DecidableEq α] (G : SimpleGraph α) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (v : α) (h : v ∈ w.val) : + G.IsSimpleWalkIn (w.suffixFrom v h) := + IsVertexSeqIn.suffixFrom G hw v h + +/-- Taking the longest prefix on which `p` holds (plus its first failure) +preserves realization. -/ +lemma takeWhile (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) (p : α → Prop) [DecidablePred p] : + G.IsSimpleWalkIn (w.takeWhile p) := + IsVertexSeqIn.takeWhile G hw p + +/-- Dropping the longest prefix on which `p` holds preserves realization. -/ +lemma dropWhile (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) (p : α → Prop) [DecidablePred p] + (h : ∃ v ∈ w.val.toList, ¬ p v) : + G.IsSimpleWalkIn (w.dropWhile p h) := + IsVertexSeqIn.dropWhile G hw p h + +/-- Loop erasure preserves realization. On a simple walk this operation is the +identity on the underlying sequence. -/ +lemma loopErase [DecidableEq α] (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn (w.loopErase) := + IsVertexSeqIn.loopErase G hw + +/-- Cycle erasure preserves realization. -/ +lemma cycleErase [DecidableEq α] (G : SimpleGraph α) {w : SimpleWalk α} + (hw : G.IsSimpleWalkIn w) : G.IsSimpleWalkIn (w.cycleErase) := + IsVertexSeqIn.cycleErase G hw + +/-! ## Joining walks -/ + +/-- Appending two realized walks along an edge of `G` preserves realization. -/ +lemma append (G : SimpleGraph α) {p q : SimpleWalk α} + (hp : G.IsSimpleWalkIn p) (hq : G.IsSimpleWalkIn q) + (he : G.Adj p.tail q.head) : + G.IsSimpleWalkIn (p.append q he.ne) := + IsVertexSeqIn.append G hp hq he + +/-- Gluing two realized walks at a shared endpoint preserves realization. -/ +lemma glue (G : SimpleGraph α) {p q : SimpleWalk α} + (hp : G.IsSimpleWalkIn p) (hq : G.IsSimpleWalkIn q) + (h : p.tail = q.head) : + G.IsSimpleWalkIn (p.glue q h) := by + unfold SimpleWalk.glue + split + · exact hq + · rename_i hlen + apply IsVertexSeqIn.append G (IsVertexSeqIn.dropTail G hp) hq + simpa only [h] using IsVertexSeqIn.last_adj G hp hlen + +/-! ## Monotonicity -/ + +/-- Realization is monotone under passing to a supergraph. -/ +@[grind →] lemma mono (G H : SimpleGraph α) {w : SimpleWalk α} + (hw : H.IsSimpleWalkIn w) (hsub : SimpleGraph.subgraphOf H G) : + G.IsSimpleWalkIn w := + IsVertexSeqIn.mono G H hw hsub + +/-! ## Edge-free graphs -/ + +/-- If `G` has no edges, every realized walk in `G` has length zero. -/ +lemma length_zero_of_no_edges (G : SimpleGraph α) (hE : E(G) = ∅) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) : w.length = 0 := + IsVertexSeqIn.length_zero_of_no_edges G hE hw + +end IsSimpleWalkIn + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/SimpleCycle.lean b/GraphLib/Theory/Structures/SimpleCycle.lean index d299d17..31cc6c8 100644 --- a/GraphLib/Theory/Structures/SimpleCycle.lean +++ b/GraphLib/Theory/Structures/SimpleCycle.lean @@ -20,6 +20,18 @@ API here only reflects a chosen traversal orientation and is not canonical. * `SimpleWalk.IsCycle` — a closed simple walk whose dropped-tail walk is a path. * `SimpleCycle` — a simple walk bundled with a proof of `SimpleWalk.IsCycle`. +* `SimpleCycle.interior` — the simple path obtained by dropping the repeated endpoint. +* `SimpleCycle.reverse`, `SimpleCycle.reroot` — change the chosen traversal or root. +* `SimpleCycle.ofPathClosing` — close one path to form a cycle. +* `SimpleCycle.ofInternallyDisjointPaths` — form a cycle from internally disjoint paths. +* `SimpleCycle.ofTwoPaths` — select a cycle from two distinct paths with common endpoints. + +## Main results + +* `SimpleCycle.edges_nodup` — a simple cycle traverses no edge twice. +* `SimpleCycle.length_ofTwoPaths` — the selected cycle is no longer than the input paths. +* `SimpleCycle.head_ofTwoPaths_mem_left` — the selected root lies on the first path. +* `SimpleCycle.edges_ofTwoPaths_subset` — every selected edge comes from an input path. -/ variable {α : Type*} @@ -31,16 +43,16 @@ and its dropped-tail walk is a path. -/ @[grind] def IsCycle (w : SimpleWalk α) : Prop := 3 ≤ w.length ∧ w.closed ∧ w.dropTail.nodup +namespace IsCycle + /-- Reversal preserves the simple-cycle property. -/ -lemma isCycle_reverse (w : SimpleWalk α) (h : IsCycle w) : +lemma reverse (w : SimpleWalk α) (h : IsCycle w) : IsCycle w.reverse := by - rcases h with ⟨hlen, hclosed, hnodup⟩ - refine ⟨?_, ?_, ?_⟩ - · change 3 ≤ w.val.reverse.length - simpa using hlen - · change w.val.reverse.closed - simpa [VertexSeq.closed] using hclosed.symm - · exact VertexSeq.nodup_reverse_dropTail_of_cycle w.val hclosed hnodup + obtain ⟨hlen, hclosed, hnodup⟩ := h + exact ⟨by simpa using hlen, by simpa [VertexSeq.closed] using hclosed.symm, + VertexSeq.nodup_reverse_dropTail_of_closed w.val hclosed hnodup⟩ + +end IsCycle end SimpleWalk @@ -76,11 +88,6 @@ abbrev tail (c : SimpleCycle α) : α := (vertices c).tail /-- The number of edges in the cycle. -/ abbrev length (c : SimpleCycle α) : ℕ := (vertices c).length -/-- The interior of the cycle: the vertex sequence with its final (repeated) -vertex dropped. -/ -def interior (c : SimpleCycle α) : SimplePath α := - ⟨c.val.dropTail, c.2.2.2⟩ - /-- A cycle is closed: its first and last vertex coincide. -/ lemma closed (c : SimpleCycle α) : c.val.closed := c.2.2.1 @@ -88,75 +95,487 @@ lemma closed (c : SimpleCycle α) : c.val.closed := c.2.2.1 instance : Coe (SimpleCycle α) (SimpleWalk α) := ⟨val⟩ +/-! ## interior -/ + +/-- The interior of the cycle: the vertex sequence with its final (repeated) +vertex dropped. -/ +def interior (c : SimpleCycle α) : SimplePath α := + ⟨c.val.dropTail, c.2.2.2⟩ + +attribute [simp, grind] SimpleCycle.val SimpleCycle.vertices SimpleCycle.support + SimpleCycle.edges SimpleCycle.arcs SimpleCycle.head SimpleCycle.tail SimpleCycle.length + SimpleCycle.interior + +/-! ## Constructors -/ + +/-! ### ofPathClosing -/ + +/-- The simple walk obtained by appending the starting vertex to a path of +length at least two. -/ +private def walkOfPathClosing (p : SimplePath α) (hlen : 2 ≤ p.vertices.length) : + SimpleWalk α := + ⟨VertexSeq.cons p.vertices p.head, ⟨p.val.nonstalling, fun h => by + have hzero := VertexSeq.length_zero_of_nodup_closed p.vertices p.nodup + (show p.vertices.closed from h.symm) + omega⟩⟩ + +/-- Closing a path of length at least two satisfies the cycle conditions. -/ +private lemma isCycle_ofPathClosing (p : SimplePath α) + (hlen : 2 ≤ p.vertices.length) : + SimpleWalk.IsCycle (walkOfPathClosing p hlen) := by + change 3 ≤ (VertexSeq.cons p.vertices p.head).length ∧ + (VertexSeq.cons p.vertices p.head).closed ∧ + (VertexSeq.cons p.vertices p.head).dropTail.nodup + refine ⟨?_, by simp [VertexSeq.closed], by simpa using p.nodup⟩ + simp [VertexSeq.length] at hlen ⊢ + omega + +/-- Close a simple path by adding an edge from its tail back to its head. -/ +def ofPathClosing (p : SimplePath α) + (hlen : 2 ≤ (SimplePath.vertices p).length) : + SimpleCycle α := + ⟨walkOfPathClosing p hlen, isCycle_ofPathClosing p hlen⟩ + +/-! ### ofInternallyDisjointPaths -/ + +/-- Two paths with the same tail can be glued after reversing the second one. -/ +private lemma tail_eq_head_reverse_of_tail_eq (P Q : SimplePath α) + (htail : P.tail = Q.tail) : P.val.tail = Q.val.reverse.head := by + simpa using htail + +/-- The walk obtained by following two internally disjoint paths in opposite +directions is a simple cycle. -/ +private lemma isCycle_ofInternallyDisjointPaths (P Q : SimplePath α) + (hhead : P.head = Q.head) (htail : P.tail = Q.tail) + (hab : P.head ≠ P.tail) + (hint : ∀ z, z ∈ P.vertices → z ∈ Q.vertices → + z = P.head ∨ z = P.tail) + (hlen : 3 ≤ P.length + Q.length) : + SimpleWalk.IsCycle + (SimpleWalk.glue P.val Q.val.reverse + (tail_eq_head_reverse_of_tail_eq P Q htail)) := by + have hPpos : P.vertices.length ≠ 0 := fun h0 => + hab (VertexSeq.head_eq_tail_of_length_zero P.vertices h0) + have hQpos : Q.vertices.length ≠ 0 := fun h0 => + hab (hhead.trans ((VertexSeq.head_eq_tail_of_length_zero Q.vertices h0).trans htail.symm)) + have hQrevPos : Q.vertices.reverse.length ≠ 0 := by simpa using hQpos + have hQrevNodup := VertexSeq.nodup_reverse Q.vertices Q.nodup + have hdisj : ∀ z : α, z ∈ P.vertices.dropTail → + z ∈ Q.vertices.reverse.dropTail → False := by + intro z hzP hzQ + have hzP2 := VertexSeq.dropTail_subset P.vertices z hzP + have hzQ2 : z ∈ Q.vertices := + VertexSeq.mem_reverse.1 + (VertexSeq.dropTail_subset Q.vertices.reverse z hzQ) + have hPt := VertexSeq.tail_not_mem_dropTail_of_nodup P.vertices P.nodup hPpos + have hQt := VertexSeq.tail_not_mem_dropTail_of_nodup Q.vertices.reverse + hQrevNodup hQrevPos + have hQrt : Q.vertices.reverse.tail = Q.head := VertexSeq.tail_reverse Q.vertices + grind [hint z hzP2 hzQ2] + unfold SimpleWalk.glue + rw [dif_neg hPpos] + refine ⟨?_, ?_, ?_⟩ + · change 3 ≤ (P.vertices.dropTail.append Q.vertices.reverse).length + have := VertexSeq.length_dropTail_succ P.vertices hPpos + change 3 ≤ P.vertices.length + Q.vertices.length at hlen + rw [VertexSeq.length_append, VertexSeq.length_reverse] + omega + · change (P.vertices.dropTail.append Q.vertices.reverse).closed + simp [VertexSeq.closed, hhead] + · change (P.vertices.dropTail.append Q.vertices.reverse).dropTail.nodup + rw [VertexSeq.dropTail_append P.vertices.dropTail Q.vertices.reverse hQrevPos] + exact VertexSeq.nodup_append _ _ (VertexSeq.nodup_dropTail P.vertices P.nodup) + (VertexSeq.nodup_dropTail _ hQrevNodup) hdisj + +/-- Construct a simple cycle by following one internally disjoint path and +returning along the reverse of the other. -/ +def ofInternallyDisjointPaths (P Q : SimplePath α) + (hhead : P.head = Q.head) (htail : P.tail = Q.tail) + (hab : P.head ≠ P.tail) + (hint : ∀ z, z ∈ P.vertices → z ∈ Q.vertices → + z = P.head ∨ z = P.tail) + (hlen : 3 ≤ P.length + Q.length) : SimpleCycle α := + ⟨SimpleWalk.glue P.val Q.val.reverse + (tail_eq_head_reverse_of_tail_eq P Q htail), + isCycle_ofInternallyDisjointPaths P Q hhead htail hab hint hlen⟩ + +/-- The length of the cycle obtained from two internally disjoint paths is the +sum of the two path lengths. -/ +@[simp] lemma length_ofInternallyDisjointPaths (P Q : SimplePath α) + (hhead : P.head = Q.head) (htail : P.tail = Q.tail) + (hab : P.head ≠ P.tail) + (hint : ∀ z, z ∈ P.vertices → z ∈ Q.vertices → + z = P.head ∨ z = P.tail) + (hlen : 3 ≤ P.length + Q.length) : + (ofInternallyDisjointPaths P Q hhead htail hab hint hlen).length = + P.length + Q.length := by + have hPpos : P.vertices.length ≠ 0 := fun h0 => + hab (VertexSeq.head_eq_tail_of_length_zero P.vertices h0) + unfold ofInternallyDisjointPaths SimpleWalk.glue + grind [VertexSeq.length_append, VertexSeq.length_reverse, + VertexSeq.length_dropTail_succ] + +/-- The cycle from two internally disjoint paths is rooted at the head they +share: the traversal starts along `P`. -/ +@[simp] lemma head_ofInternallyDisjointPaths (P Q : SimplePath α) + (hhead : P.head = Q.head) (htail : P.tail = Q.tail) + (hab : P.head ≠ P.tail) + (hint : ∀ z, z ∈ P.vertices → z ∈ Q.vertices → + z = P.head ∨ z = P.tail) + (hlen : 3 ≤ P.length + Q.length) : + (ofInternallyDisjointPaths P Q hhead htail hab hint hlen).head = P.head := by + have hPpos : P.length ≠ 0 := fun h0 => + hab (VertexSeq.head_eq_tail_of_length_zero P.vertices h0) + change (P.val.glue Q.val.reverse + (tail_eq_head_reverse_of_tail_eq P Q htail)).head = P.head + unfold SimpleWalk.glue + rw [dif_neg hPpos] + change (P.vertices.dropTail.append Q.vertices.reverse).head = P.vertices.head + rw [VertexSeq.head_append, VertexSeq.head_dropTail] + +/-- Every edge of the cycle from two internally disjoint paths is traversed by +one of them: the return leg only reverses `Q`, which leaves its edge set alone. -/ +lemma edges_ofInternallyDisjointPaths_subset (P Q : SimplePath α) + (hhead : P.head = Q.head) (htail : P.tail = Q.tail) + (hab : P.head ≠ P.tail) + (hint : ∀ z, z ∈ P.vertices → z ∈ Q.vertices → + z = P.head ∨ z = P.tail) + (hlen : 3 ≤ P.length + Q.length) {e : Sym2 α} + (he : e ∈ (ofInternallyDisjointPaths P Q hhead htail hab hint hlen).edges) : + e ∈ P.edges ∨ e ∈ Q.edges := by + change e ∈ (P.val.glue Q.val.reverse + (tail_eq_head_reverse_of_tail_eq P Q htail)).edges at he + rw [SimpleWalk.edges_glue, List.mem_append] at he + rcases he with heP | heQ + · exact Or.inl heP + · exact Or.inr (by grind [VertexSeq.edges_reverse]) + +/-! ### ofTwoPaths -/ + +/-- The vertex where two paths with a common head diverge: the last vertex of the +longest common prefix of their vertex lists. + +Defined via `List.commonPrefix`, so no index arithmetic is involved. Compare the +earlier `findIdx`-on-`zip` definition, which forced every downstream lemma to +carry dependent `getElem` bounds. -/ +private def divergenceVertex [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) : α := + (List.commonPrefix p.vertices.toList q.vertices.toList).getLast + (List.commonPrefix_ne_nil (x := p.head) (by grind) (by grind)) + +/-- The three facts that make the divergence vertex what it is: the two suffixes +from it end together, it is not that common endpoint, and the two paths take +different next steps out of it. -/ +private lemma divergenceVertex_core [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : + let a := divergenceVertex p q hhead + ∃ hap : a ∈ p.vertices, ∃ haq : a ∈ q.vertices, + (p.suffixFrom a hap).tail = (q.suffixFrom a haq).tail ∧ + a ≠ (p.suffixFrom a hap).tail ∧ + (p.suffixFrom a hap).vertices.dropHead.head ≠ + (q.suffixFrom a haq).vertices.dropHead.head := by + classical + have hnp : p.vertices.toList.Nodup := (VertexSeq.nodup_iff_toList_nodup _).1 p.nodup + have hnq : q.vertices.toList.Nodup := (VertexSeq.nodup_iff_toList_nodup _).1 q.nodup + obtain ⟨r₁, r₂, h₁, h₂, hdiff⟩ := + List.commonPrefix_split p.vertices.toList q.vertices.toList + have hcne : List.commonPrefix p.vertices.toList q.vertices.toList ≠ [] := + List.commonPrefix_ne_nil (x := p.head) (by grind) (by grind) + set c := List.commonPrefix p.vertices.toList q.vertices.toList with hc + set a := divergenceVertex p q hhead with hadefa + have hadef : a = c.getLast hcne := rfl + have hsplitc : c.dropLast ++ [a] = c := by + rw [hadef]; exact List.dropLast_append_getLast hcne + have hlp : p.vertices.toList = c.dropLast ++ a :: r₁ := by grind + have hlq : q.vertices.toList = c.dropLast ++ a :: r₂ := by grind + have hap : a ∈ p.vertices := by grind + have haq : a ∈ q.vertices := by grind + have hsp : (p.vertices.suffixFrom a hap).toList = a :: r₁ := + VertexSeq.toList_suffixFrom_eq_of_split _ p.nodup a hap _ _ hlp + have hsq : (q.vertices.suffixFrom a haq).toList = a :: r₂ := + VertexSeq.toList_suffixFrom_eq_of_split _ q.nodup a haq _ _ hlq + -- Each path ends at the last entry of its own `a :: rᵢ`; establish this once. + have htp : p.vertices.tail = (a :: r₁).getLast (by simp) := by + rw [← VertexSeq.getLast_toList p.vertices (by grind)] + grind + have htq : q.vertices.tail = (a :: r₂).getLast (by simp) := by + rw [← VertexSeq.getLast_toList q.vertices (by grind)] + grind + -- Both remainders are non-empty: otherwise one vertex list is a prefix of the + -- other, and a shared last vertex plus `nodup` would force the two paths to be + -- equal, contradicting `hne`. + have hr₁ : r₁ ≠ [] := by + rintro rfl + have hr₂ : r₂ = [] := by + rcases r₂ with _ | ⟨y, ys⟩ + · rfl + · exfalso + have hmem := List.getLast_mem (l := y :: ys) (by simp) + grind [List.getLast_cons, List.nodup_append] + exact hne (VertexSeq.toList_injective (by grind)) + have hr₂ : r₂ ≠ [] := by + rintro rfl + have hmem := List.getLast_mem (l := r₁) hr₁ + grind [List.getLast_cons, List.nodup_append] + have hane : a ∉ r₁ := by grind [List.nodup_append] + refine ⟨hap, haq, by grind [VertexSeq.tail_suffixFrom], ?_, ?_⟩ + · have hmem := List.getLast_mem (l := r₁) hr₁ + grind [VertexSeq.tail_suffixFrom, List.getLast_cons] + · have hd₁ := VertexSeq.head_dropHead_of_toList_eq_cons_cons _ a (r₁.head hr₁) r₁.tail + (by rw [hsp, List.cons_head_tail hr₁]) + have hd₂ := VertexSeq.head_dropHead_of_toList_eq_cons_cons _ a (r₂.head hr₂) r₂.tail + (by rw [hsq, List.cons_head_tail hr₂]) + have := hdiff (r₁.head hr₁) (by grind [List.head?_eq_some_head]) (r₂.head hr₂) + (by grind [List.head?_eq_some_head]) + grind + +/-- The full divergence specification used by `buildOfTwoPaths`. The four extra +components are routine consequences of `divergenceVertex_core`. -/ +private lemma divergenceVertex_spec [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : + let a := divergenceVertex p q hhead + ∃ hap : a ∈ p.vertices, ∃ haq : a ∈ q.vertices, + let p' := p.suffixFrom a hap + let q' := q.suffixFrom a haq + p'.head = a ∧ q'.head = a ∧ p'.tail = q'.tail ∧ + p'.length ≠ 0 ∧ q'.length ≠ 0 ∧ a ≠ p'.tail ∧ + p'.vertices.dropHead.head ≠ q'.vertices.dropHead.head := by + obtain ⟨hap, haq, htaileq, hane, hsecond⟩ := divergenceVertex_core p q hhead htail hne + refine ⟨hap, haq, ?_, ?_, htaileq, ?_, ?_, hane, hsecond⟩ <;> + grind [VertexSeq.head_suffixFrom] + +private lemma divergenceVertex_mem_left [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : + divergenceVertex p q hhead ∈ p.vertices := + (divergenceVertex_core p q hhead htail hne).1 + +private lemma divergenceVertex_mem_right [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : + divergenceVertex p q hhead ∈ q.vertices := + (divergenceVertex_core p q hhead htail hne).2.1 + +private def firstRejoinVertex [DecidableEq α] (a : α) (p q : SimplePath α) + (hwit : ∃ z ∈ p.vertices.toList, ¬(z = a ∨ z ∉ q.vertices)) : α := + (p.vertices.dropWhile (fun z => z = a ∨ z ∉ q.vertices) hwit).head + +private lemma firstRejoinVertex_spec [DecidableEq α] (a : α) (p q : SimplePath α) + (hpa : p.head = a) (hqa : q.head = a) + (hsecond : p.vertices.dropHead.head ≠ q.vertices.dropHead.head) + (hwit : ∃ z ∈ p.vertices.toList, ¬(z = a ∨ z ∉ q.vertices)) : + let b := firstRejoinVertex a p q hwit + ∃ hbP : b ∈ p.vertices, ∃ hbQ : b ∈ q.vertices, + let P := p.prefixUntil b hbP + let Q := q.prefixUntil b hbQ + P.head = Q.head ∧ P.tail = Q.tail ∧ P.head ≠ P.tail ∧ + (∀ z, z ∈ P.vertices → z ∈ Q.vertices → + z = P.head ∨ z = P.tail) ∧ + 3 ≤ P.length + Q.length := by + let pred : α → Prop := fun z => z = a ∨ z ∉ q.vertices + let r := p.vertices.dropWhile pred hwit + let b := firstRejoinVertex a p q hwit + have hbP : b ∈ p.vertices := + VertexSeq.dropWhile_subset p.vertices pred hwit _ (VertexSeq.head_mem r) + have hbNot : ¬pred b := VertexSeq.not_pred_head_dropWhile p.vertices pred hwit + have hba : b ≠ a := fun h => hbNot (Or.inl h) + have hbQ : b ∈ q.vertices := by by_contra h; exact hbNot (Or.inr h) + refine ⟨hbP, hbQ, ?_⟩ + let P := p.prefixUntil b hbP + let Q := q.prefixUntil b hbQ + have hPhead : P.head = a := (VertexSeq.head_prefixUntil p.vertices b hbP).trans hpa + have hQhead : Q.head = a := (VertexSeq.head_prefixUntil q.vertices b hbQ).trans hqa + have hPtail : P.tail = b := VertexSeq.tail_prefixUntil p.vertices b hbP + have hQtail : Q.tail = b := VertexSeq.tail_prefixUntil q.vertices b hbQ + have hPpos : P.length ≠ 0 := + VertexSeq.length_prefixUntil_ne_zero p.vertices b hbP (by grind) + have hQpos : Q.length ≠ 0 := + VertexSeq.length_prefixUntil_ne_zero q.vertices b hbQ (by grind) + refine ⟨hPhead.trans hQhead.symm, hPtail.trans hQtail.symm, + fun heq => hba (hPtail.symm.trans (heq.symm.trans hPhead)), ?_, ?_⟩ + · intro z hzP hzQ + have hzQ' : z ∈ q.vertices := VertexSeq.prefixUntil_subset q.vertices b hbQ z hzQ + have hzFirst := VertexSeq.eq_head_dropWhile_or_pred_of_mem_prefixUntil + p.vertices pred hwit (z := z) (by simpa [P, b, r, pred] using hzP) + rcases hzFirst with hzb | hza | hznot + · exact Or.inr (hzb.trans hPtail.symm) + · exact Or.inl (hza.trans hPhead.symm) + · exact (hznot hzQ').elim + · by_contra h3 + change ¬3 ≤ P.length + Q.length at h3 + have hP1 : P.length = 1 := by omega + have hQ1 : Q.length = 1 := by omega + exact hsecond + ((VertexSeq.head_dropHead_prefixUntil_of_length_one p.vertices b hbP hP1).trans + (VertexSeq.head_dropHead_prefixUntil_of_length_one q.vertices b hbQ hQ1).symm) + +private lemma firstRejoinVertex_mem_left [DecidableEq α] (a : α) + (p q : SimplePath α) + (hwit : ∃ z ∈ p.vertices.toList, ¬(z = a ∨ z ∉ q.vertices)) : + firstRejoinVertex a p q hwit ∈ p.vertices := by + exact VertexSeq.dropWhile_subset p.vertices (fun z => z = a ∨ z ∉ q.vertices) + hwit _ (VertexSeq.head_mem _) + +private lemma firstRejoinVertex_mem_right [DecidableEq α] (a : α) + (p q : SimplePath α) + (hwit : ∃ z ∈ p.vertices.toList, ¬(z = a ∨ z ∉ q.vertices)) : + firstRejoinVertex a p q hwit ∈ q.vertices := by + have hnot := VertexSeq.not_pred_head_dropWhile p.vertices + (fun z => z = a ∨ z ∉ q.vertices) hwit + by_contra h + exact hnot (Or.inr h) + +private structure OfTwoPathsData (p q : SimplePath α) where + cycle : SimpleCycle α + length_le : cycle.length ≤ p.length + q.length + head_mem_left : cycle.head ∈ p.vertices + edges_subset : ∀ e ∈ cycle.edges, e ∈ p.edges ∨ e ∈ q.edges + +private def buildOfTwoPaths [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : OfTwoPathsData p q := by + let a := divergenceVertex p q hhead + let hap := divergenceVertex_mem_left p q hhead htail hne + let haq := divergenceVertex_mem_right p q hhead htail hne + let p' := p.suffixFrom a hap + let q' := q.suffixFrom a haq + have hd : p'.head = a ∧ q'.head = a ∧ p'.tail = q'.tail ∧ + p'.length ≠ 0 ∧ q'.length ≠ 0 ∧ a ≠ p'.tail ∧ + p'.vertices.dropHead.head ≠ q'.vertices.dropHead.head := by + obtain ⟨hap', haq', h⟩ := divergenceVertex_spec p q hhead htail hne + simpa [a, p', q'] using h + obtain ⟨hpHead, hqHead, hpTail, -, -, haTail, hsecond⟩ := hd + have hwit : ∃ z ∈ p'.vertices.toList, ¬(z = a ∨ z ∉ q'.vertices) := + ⟨p'.tail, VertexSeq.tail_mem p'.vertices, by + rintro (hta | htq) + · exact haTail hta.symm + · exact htq (hpTail.symm ▸ VertexSeq.tail_mem q'.vertices)⟩ + let b := firstRejoinVertex a p' q' hwit + let hbP := firstRejoinVertex_mem_left a p' q' hwit + let hbQ := firstRejoinVertex_mem_right a p' q' hwit + let P := p'.prefixUntil b hbP + let Q := q'.prefixUntil b hbQ + have hr : P.head = Q.head ∧ P.tail = Q.tail ∧ P.head ≠ P.tail ∧ + (∀ z, z ∈ P.vertices → z ∈ Q.vertices → + z = P.head ∨ z = P.tail) ∧ 3 ≤ P.length + Q.length := by + obtain ⟨hbP', hbQ', h⟩ := + firstRejoinVertex_spec a p' q' hpHead hqHead hsecond hwit + simpa [b, P, Q] using h + obtain ⟨hPQHead, hPQTail, hPNe, hint, hlen⟩ := hr + refine ⟨ofInternallyDisjointPaths P Q hPQHead hPQTail hPNe hint hlen, ?_, ?_, ?_⟩ + · change (ofInternallyDisjointPaths P Q hPQHead hPQTail hPNe hint hlen).length ≤ _ + rw [length_ofInternallyDisjointPaths] + have hPle : P.length ≤ p'.length := VertexSeq.length_prefixUntil_le p'.vertices b hbP + have hQle : Q.length ≤ q'.length := VertexSeq.length_prefixUntil_le q'.vertices b hbQ + have hp'le : p'.length ≤ p.length := VertexSeq.length_suffixFrom_le p.vertices a hap + have hq'le : q'.length ≤ q.length := VertexSeq.length_suffixFrom_le q.vertices a haq + omega + · rw [head_ofInternallyDisjointPaths] + exact VertexSeq.suffixFrom_subset p.vertices a hap _ + (VertexSeq.prefixUntil_subset p'.vertices b hbP _ (VertexSeq.head_mem P.vertices)) + · intro e he + rcases edges_ofInternallyDisjointPaths_subset P Q hPQHead hPQTail hPNe hint hlen he + with heP | heQ + · exact Or.inl (SimplePath.edges_suffixFrom_subset p a hap + (SimplePath.edges_prefixUntil_subset p' b hbP heP)) + · exact Or.inr (SimplePath.edges_suffixFrom_subset q a haq + (SimplePath.edges_prefixUntil_subset q' b hbQ heQ)) + +/-- Two distinct simple paths with the same endpoints determine a simple cycle +by taking the segment between their first divergence and first reconvergence. -/ +def ofTwoPaths [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : SimpleCycle α := + (buildOfTwoPaths p q hhead htail hne).cycle + +/-- The cycle selected from two distinct paths is no longer than the two paths +combined. -/ +lemma length_ofTwoPaths [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : + (ofTwoPaths p q hhead htail hne).length ≤ p.length + q.length := + (buildOfTwoPaths p q hhead htail hne).length_le + +/-- The selected cycle is rooted at a vertex of the first path. -/ +lemma head_ofTwoPaths_mem_left [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) : + (ofTwoPaths p q hhead htail hne).head ∈ p.vertices := + (buildOfTwoPaths p q hhead htail hne).head_mem_left + +/-- Every edge of the selected cycle comes from one of the two input paths. -/ +lemma edges_ofTwoPaths_subset [DecidableEq α] (p q : SimplePath α) + (hhead : p.head = q.head) (htail : p.tail = q.tail) + (hne : p.vertices ≠ q.vertices) {e : Sym2 α} + (he : e ∈ (ofTwoPaths p q hhead htail hne).edges) : + e ∈ p.edges ∨ e ∈ q.edges := + (buildOfTwoPaths p q hhead htail hne).edges_subset e he + /-! ## reverse -/ /-- Reverse the orientation of a simple cycle. -/ @[grind] def reverse (c : SimpleCycle α) : SimpleCycle α := - ⟨c.val.reverse, SimpleWalk.isCycle_reverse c.val c.2⟩ + ⟨c.val.reverse, SimpleWalk.IsCycle.reverse c.val c.2⟩ /-! ## reroot -/ +/-- The suffix rooted at a cycle vertex can be glued to the preceding prefix. -/ +private lemma tail_suffixFrom_eq_head_prefixUntil [DecidableEq α] (c : SimpleCycle α) + (u : α) (hu : u ∈ vertices c) : + (c.val.suffixFrom u hu).val.tail = (c.val.prefixUntil u hu).val.head := by + simpa using (closed c).symm + +/-- Re-rooting the underlying walk of a simple cycle at a non-head vertex +preserves the cycle property. -/ +private lemma isCycle_reroot_glue [DecidableEq α] (c : SimpleCycle α) (u : α) + (hu : u ∈ vertices c) (hhead : u ≠ head c) : + SimpleWalk.IsCycle + ((c.val.suffixFrom u hu).glue (c.val.prefixUntil u hu) + (tail_suffixFrom_eq_head_prefixUntil c u hu)) := by + have hsplit := VertexSeq.dropTail_prefixUntil_append_suffixFrom + (vertices c) u hu hhead + let pre : VertexSeq α := (vertices c).prefixUntil u hu + let suf : VertexSeq α := (vertices c).suffixFrom u hu + have hpre_pos : pre.length ≠ 0 := fun hz => + hhead (by simpa [pre] using (VertexSeq.head_eq_tail_of_length_zero pre hz).symm) + have hsuf_pos : suf.length ≠ 0 := by + intro hz + have huTail : u = (vertices c).tail := by + simpa [suf] using VertexSeq.head_eq_tail_of_length_zero suf hz + exact hhead (huTail.trans (closed c).symm) + have hsplit' : pre.dropTail.append suf = vertices c := by simpa [pre, suf] using hsplit + have hleft : (pre.dropTail.append suf.dropTail).nodup := by + rw [← VertexSeq.dropTail_append pre.dropTail suf hsuf_pos, + congrArg VertexSeq.dropTail hsplit'] + exact c.2.2.2 + have hsuf_pos' : ((c.val.suffixFrom u hu).val.length ≠ 0) := by simpa [suf] using hsuf_pos + have hlenAll : pre.dropTail.length + suf.length + 1 = (vertices c).length := by + simpa [VertexSeq.length_append] using congrArg VertexSeq.length hsplit' + have hpreLen := VertexSeq.length_dropTail_succ pre hpre_pos + have hsufLen := VertexSeq.length_dropTail_succ suf hsuf_pos + have h3 : 3 ≤ (vertices c).length := c.2.1 + simp only [SimpleWalk.IsCycle, SimpleWalk.glue, hsuf_pos'] + refine ⟨?_, ?_, ?_⟩ + · change 3 ≤ (suf.dropTail.append pre).length + rw [VertexSeq.length_append] + omega + · change (suf.dropTail.append pre).closed + simp [VertexSeq.closed, pre, suf] + · change (suf.dropTail.append pre).dropTail.nodup + rw [VertexSeq.dropTail_append suf.dropTail pre hpre_pos] + exact VertexSeq.nodup_append_comm pre.dropTail suf.dropTail hleft + /-- Re-root a simple cycle at any vertex on it. -/ def reroot [DecidableEq α] (c : SimpleCycle α) (u : α) (hu : u ∈ vertices c) : SimpleCycle α := if hhead : u = head c then c else - ⟨(c.val.suffixFrom u hu).glue (c.val.prefixUntil u hu) (by - change (c.val.val.suffixFrom u hu).tail = (c.val.val.prefixUntil u hu).head - rw [VertexSeq.tail_suffixFrom, VertexSeq.head_prefixUntil] - exact (SimpleCycle.closed c).symm), - by - have hsplit := VertexSeq.dropTail_prefixUntil_append_suffixFrom - (vertices c) u hu hhead - let pre : VertexSeq α := (vertices c).prefixUntil u hu - let suf : VertexSeq α := (vertices c).suffixFrom u hu - have hpre_pos : pre.length ≠ 0 := by - intro hz - have h_eq := VertexSeq.head_eq_tail_of_length_zero pre hz - have h_eq' : (vertices c).head = u := by - simpa [pre] using h_eq - exact hhead h_eq'.symm - have hsuf_pos : suf.length ≠ 0 := by - intro hz - have h_eq := VertexSeq.head_eq_tail_of_length_zero suf hz - have h_eq' : u = (vertices c).tail := by - simpa [suf] using h_eq - exact hhead (by - rw [← SimpleCycle.closed c] at h_eq' - exact h_eq') - have hsplit' : pre.dropTail.append suf = vertices c := by - simpa [pre, suf] using hsplit - have hsplitLen := congrArg VertexSeq.length hsplit' - have hsplitLen' : pre.dropTail.length + suf.length + 1 = (vertices c).length := by - simpa [VertexSeq.length_append] using hsplitLen - have hpreLen := VertexSeq.dropTail_length_succ pre hpre_pos - have hsufLen := VertexSeq.dropTail_length_succ suf hsuf_pos - have hdropSplit := congrArg VertexSeq.dropTail hsplit' - have hleft : (pre.dropTail.append suf.dropTail).nodup := by - have hdrop : - (pre.dropTail.append suf).dropTail = pre.dropTail.append suf.dropTail := - VertexSeq.dropTail_append_of_length_ne_zero pre.dropTail suf hsuf_pos - rw [← hdrop] - rw [hdropSplit] - exact c.2.2.2 - have hrotDrop : (suf.dropTail.append pre).dropTail = suf.dropTail.append pre.dropTail := - VertexSeq.dropTail_append_of_length_ne_zero suf.dropTail pre hpre_pos - have hsuf_pos' : ((c.val.suffixFrom u hu).val.length ≠ 0) := by - simpa [suf] using hsuf_pos - simp [SimpleWalk.IsCycle, SimpleWalk.glue, hsuf_pos'] - refine ⟨?_, ?_, ?_⟩ - · change 3 ≤ (suf.dropTail.append pre).length - have hrotLen : (suf.dropTail.append pre).length = (vertices c).length := by - simp [VertexSeq.length_append] - omega - rw [hrotLen] - exact c.2.1 - · change (suf.dropTail.append pre).closed - simp [VertexSeq.closed, pre, suf] - · change (suf.dropTail.append pre).dropTail.nodup - rw [hrotDrop] - exact VertexSeq.nodup_append_comm pre.dropTail suf.dropTail hleft⟩ + ⟨(c.val.suffixFrom u hu).glue (c.val.prefixUntil u hu) + (tail_suffixFrom_eq_head_prefixUntil c u hu), + isCycle_reroot_glue c u hu hhead⟩ /-! ## edges -/ @@ -171,46 +590,28 @@ lemma three_le_length_edges (c : SimpleCycle α) : 3 ≤ (edges c).length := by /-- A simple cycle traverses at least one edge. -/ lemma edges_ne_nil (c : SimpleCycle α) : edges c ≠ [] := by - intro h - have hlen := three_le_length_edges c - rw [h] at hlen - simp at hlen + grind [three_le_length_edges] /-- The edge list is the interior path's edge list plus the closing edge. -/ -lemma interior_edges (c : SimpleCycle α) : +lemma edges_eq_interior_concat (c : SimpleCycle α) : edges c = SimplePath.edges (interior c) ++ [s(SimplePath.tail (interior c), tail c)] := by - have hlen : 3 ≤ (vertices c).length := c.2.1 - have hpos : (vertices c).length ≠ 0 := by omega - change (vertices c).edges = - (vertices c).dropTail.edges ++ [s((vertices c).dropTail.tail, (vertices c).tail)] - rw [VertexSeq.edges_eq_dropTail_concat_of_length_ne_zero (vertices c) hpos] - simp [List.concat_eq_append] + have hpos : (vertices c).length ≠ 0 := by grind + simpa [List.concat_eq_append] using VertexSeq.edges_eq_dropTail_concat (vertices c) hpos /-- A simple cycle traverses each edge at most once. -/ lemma edges_nodup (c : SimpleCycle α) : (edges c).Nodup := by - rw [interior_edges] - rw [List.nodup_append] + rw [edges_eq_interior_concat, List.nodup_append] refine ⟨SimplePath.edges_nodup (interior c), by simp, ?_⟩ intro a ha b hb hab simp only [List.mem_singleton] at hb - subst hb - subst hab + subst hb; subst hab have hclosedTail : tail c = SimplePath.head (interior c) := by - change (vertices c).tail = (vertices c).dropTail.head - rw [VertexSeq.head_dropTail] - exact (closed c).symm - have hmem : - s((SimplePath.vertices (interior c)).head, - (SimplePath.vertices (interior c)).tail) ∈ - (SimplePath.vertices (interior c)).edges := by - simpa [hclosedTail, Sym2.eq_swap] using ha - have hle := VertexSeq.length_le_one_of_mem_edges_head_tail - (SimplePath.vertices (interior c)) - (SimplePath.nodup (interior c)) hmem + grind [VertexSeq.head_dropTail, closed] + rw [hclosedTail] at ha + have hle := VertexSeq.length_le_one_of_closing_edge_mem_swap + (SimplePath.vertices (interior c)) (SimplePath.nodup (interior c)) ha have hlen : 3 ≤ (vertices c).length := c.2.1 - have hpos : (vertices c).length ≠ 0 := by omega - have hdrop := VertexSeq.dropTail_length_succ (vertices c) hpos - change (vertices c).dropTail.length + 1 = (vertices c).length at hdrop + have hdrop := VertexSeq.length_dropTail_succ (vertices c) (by omega) change (vertices c).dropTail.length ≤ 1 at hle omega @@ -232,82 +633,28 @@ lemma three_le_length_arcs (c : SimpleCycle α) : 3 ≤ (arcs c).length := by /-- A simple cycle traverses at least one arc. -/ lemma arcs_ne_nil (c : SimpleCycle α) : arcs c ≠ [] := by - intro h - have hlen := three_le_length_arcs c - rw [h] at hlen - simp at hlen + grind [three_le_length_arcs] /-- The arc list is the interior path's arc list plus the closing arc. -/ -lemma interior_arcs (c : SimpleCycle α) : +lemma arcs_eq_interior_concat (c : SimpleCycle α) : arcs c = SimplePath.arcs (interior c) ++ [(SimplePath.tail (interior c), tail c)] := by - have hlen : 3 ≤ (vertices c).length := c.2.1 - have hpos : (vertices c).length ≠ 0 := by omega - change (vertices c).arcs = - (vertices c).dropTail.arcs ++ [((vertices c).dropTail.tail, (vertices c).tail)] - rw [VertexSeq.arcs_eq_dropTail_concat_of_length_ne_zero (vertices c) hpos] - simp [List.concat_eq_append] + have hpos : (vertices c).length ≠ 0 := by grind + simpa [List.concat_eq_append] using VertexSeq.arcs_eq_dropTail_concat (vertices c) hpos /-- A simple cycle traverses each directed arc at most once. -/ lemma arcs_nodup (c : SimpleCycle α) : (arcs c).Nodup := by - rw [interior_arcs] - rw [List.nodup_append] + rw [arcs_eq_interior_concat, List.nodup_append] refine ⟨SimplePath.arcs_nodup (interior c), by simp, ?_⟩ - intro a ha b hb hab - simp only [List.mem_singleton] at hb - subst hb - subst hab have hclosedTail : tail c = SimplePath.head (interior c) := by - change (vertices c).tail = (vertices c).dropTail.head - rw [VertexSeq.head_dropTail] - exact (closed c).symm - have hmem : - ((SimplePath.vertices (interior c)).tail, - (SimplePath.vertices (interior c)).head) ∈ - (SimplePath.vertices (interior c)).arcs := by - simpa [hclosedTail] using ha - have hle := VertexSeq.length_le_one_of_mem_arcs_tail_head - (SimplePath.vertices (interior c)) - (SimplePath.nodup (interior c)) hmem + grind [VertexSeq.head_dropTail, closed] have hlen : 3 ≤ (vertices c).length := c.2.1 - have hpos : (vertices c).length ≠ 0 := by omega - have hdrop := VertexSeq.dropTail_length_succ (vertices c) hpos - change (vertices c).dropTail.length + 1 = (vertices c).length at hdrop - change (vertices c).dropTail.length ≤ 1 at hle - omega + have hdrop := VertexSeq.length_dropTail_succ (vertices c) (by omega) + grind [VertexSeq.length_le_one_of_closing_arc_mem] /-- Reversal reverses the arc list and swaps every arc's endpoints. -/ @[simp] lemma arcs_reverse (c : SimpleCycle α) : arcs (reverse c) = (arcs c).reverse.map (fun a : α × α => (a.2, a.1)) := VertexSeq.arcs_reverse (vertices c) -/-! ## constructors -/ - -/-- Close a simple path by adding an edge from its tail back to its head. -/ -def ofPathClosing (p : SimplePath α) - (hlen : 2 ≤ (SimplePath.vertices p).length) : - SimpleCycle α := - let w : SimpleWalk α := - ⟨VertexSeq.cons (SimplePath.vertices p) (SimplePath.head p), by - have hp : (SimplePath.vertices p).nodup := SimplePath.nodup p - have htail_ne : - (SimplePath.vertices p).tail ≠ (SimplePath.vertices p).head := by - intro h - have hzero := VertexSeq.length_zero_of_nodup_head_eq_tail - (SimplePath.vertices p) hp h.symm - omega - exact ⟨p.val.nonstalling, htail_ne⟩⟩ - ⟨w, by - refine ⟨?_, ?_, ?_⟩ - · change 3 ≤ (VertexSeq.cons (SimplePath.vertices p) - (SimplePath.head p)).length - simp [VertexSeq.length] - omega - · change (VertexSeq.cons (SimplePath.vertices p) - (SimplePath.head p)).closed - simp [VertexSeq.closed] - · change (VertexSeq.cons (SimplePath.vertices p) - (SimplePath.head p)).dropTail.nodup - simpa using SimplePath.nodup p⟩ - end SimpleCycle diff --git a/GraphLib/Theory/Structures/SimpleGraph_only/Bipartite.lean b/GraphLib/Theory/Structures/SimpleGraph_only/Bipartite.lean new file mode 100644 index 0000000..85e9029 --- /dev/null +++ b/GraphLib/Theory/Structures/SimpleGraph_only/Bipartite.lean @@ -0,0 +1,107 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.InSimpleGraph +import Mathlib.Algebra.Group.Nat.Even + +/-! +# Bipartite simple graphs + +A simple graph is *bipartite* when it admits a proper two-colouring: a colouring +of the vertices by `Bool` under which every edge joins vertices of opposite +colour. Along a realized vertex sequence the colour therefore flips across each +edge, so the two endpoints agree in colour exactly when the sequence has even +length. Specialising this to closed walks — in particular simple cycles — shows +that every cycle in a bipartite graph is even. + +The girth consequence (`SimpleGraph.girth.even_of_isBipartite`) lives in +`GraphLib.Theory.Structures.SimpleGraph_only.Girth`, so that the basic colouring +definitions here do not depend on the girth development. + +## Main definitions + +* `SimpleGraph.IsProperTwoColoring G color` — `color` is a proper two-colouring. +* `SimpleGraph.IsBipartite G` — `G` admits a proper two-colouring. + +## Main results + +* `SimpleGraph.IsBipartite.even_length_of_closed` — a realized closed walk in a + bipartite graph has even length. +* `SimpleGraph.IsBipartite.even_length_of_isSimpleCycleIn` — every realized simple + cycle in a bipartite graph has even length. + +## Implementation notes + +The colouring formulation is chosen because the parity argument reduces to a +single `Bool` flip along each edge, avoiding any set-partition bookkeeping. The +workhorse is `IsProperTwoColoring.same_color_iff_even`, an `iff` proved by +induction on `IsVertexSeqIn`; the closed-walk and cycle statements are short +corollaries of it, and live in the `IsBipartite` namespace because that is the +predicate they assume. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +/-! ## Bipartite graphs -/ + +/-- A *proper two-colouring* of `G` is a colouring `color : α → Bool` under which +every edge joins vertices of opposite colour. -/ +@[grind] def IsProperTwoColoring (G : SimpleGraph α) (color : α → Bool) : Prop := + ∀ ⦃u v : α⦄, G.Adj u v → color u ≠ color v + +/-- `G` is *bipartite* when it admits a proper two-colouring. -/ +@[grind] def IsBipartite (G : SimpleGraph α) : Prop := + ∃ color : α → Bool, G.IsProperTwoColoring color + +namespace IsProperTwoColoring + +/-! ## Colour parity along a realized sequence -/ + +/-- Under a proper two-colouring the endpoints of a realized vertex sequence have +equal colour exactly when the sequence has even length: the colour flips across +every edge. This is the workhorse behind the cycle and girth statements. -/ +lemma same_color_iff_even (G : SimpleGraph α) {color : α → Bool} + (hcol : G.IsProperTwoColoring color) {w : VertexSeq α} + (hw : G.IsVertexSeqIn w) : color w.head = color w.tail ↔ Even w.length := by + induction hw with + | singleton v hv => simp [VertexSeq.length] + | cons w u hw he ih => + have hne : color w.tail ≠ color u := hcol he + simp only [VertexSeq.head_cons, VertexSeq.tail_cons, VertexSeq.length] + rw [Nat.add_comm 1 w.length, Nat.even_add_one, ← ih] + revert hne + cases color w.head <;> cases color w.tail <;> cases color u <;> decide + +end IsProperTwoColoring + +namespace IsBipartite + +/-! ## Closed walks and cycles are even -/ + +/-- A realized closed walk in a bipartite graph has even length. -/ +lemma even_length_of_closed (G : SimpleGraph α) (hG : G.IsBipartite) + {w : SimpleWalk α} (hw : G.IsSimpleWalkIn w) (hcl : w.closed) : + Even w.length := by + obtain ⟨color, hcol⟩ := hG + have hw' : G.IsVertexSeqIn w.val := hw + have hcl' : w.val.head = w.val.tail := hcl + exact (IsProperTwoColoring.same_color_iff_even G hcol hw').1 (congrArg color hcl') + +/-- Every realized simple cycle in a bipartite graph has even length. -/ +lemma even_length_of_isSimpleCycleIn (G : SimpleGraph α) (hG : G.IsBipartite) + {c : SimpleCycle α} (hc : G.IsSimpleCycleIn c) : Even c.length := + even_length_of_closed G hG hc c.closed + +end IsBipartite + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/SimpleGraph_only/Girth.lean b/GraphLib/Theory/Structures/SimpleGraph_only/Girth.lean new file mode 100644 index 0000000..ffecd64 --- /dev/null +++ b/GraphLib/Theory/Structures/SimpleGraph_only/Girth.lean @@ -0,0 +1,286 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import GraphLib.Theory.Structures.InSimpleGraph +import GraphLib.Theory.Structures.SimpleGraph_only.Bipartite +import Mathlib.Data.ENat.Lattice +import Mathlib.Data.Set.Card + +/-! +# Girth + +The girth of a simple graph is the length of its shortest simple cycle. We use +`WithTop ℕ`, so acyclic graphs have girth `⊤`. + +The realized-cycle predicates `SimpleGraph.IsSimpleCycleIn`, `HasSimpleCycle` +and `IsAcyclic` live in `GraphLib.Theory.Structures.InSimpleGraph`. + +## Main definitions + +* `SimpleGraph.girth G` — the least length of a simple cycle in `G`, or `⊤`. + +## Main results + +* `SimpleGraph.girth.ge_iff` / `SimpleGraph.girth.le_iff` — the lower- and + upper-bound characterizations; nearly everything else is a corollary of these. +* `SimpleGraph.girth.infinite_iff_isAcyclic` — infinite girth is acyclicity. +* `SimpleGraph.girth.exists_cycle` — a finite girth is attained by some cycle. +* `SimpleGraph.girth.finite_of_two_le_degree` — minimum degree two forces a cycle. +* `SimpleGraph.girth.even_of_isBipartite` — the girth of a bipartite graph is + even. The basic bipartite colouring API lives in + `GraphLib.Theory.Structures.SimpleGraph_only.Bipartite`; only this girth + corollary depends on the girth development, so it lives here. + +## Implementation notes + +`girth` is defined as a nested infimum `⨅ c, ⨅ _ : G.IsSimpleCycleIn c, c.length` +over `ℕ∞`. The empty-index convention makes the girth of an acyclic graph `⊤` +automatically, and the infimum API (`le_iInf`, `iInf₂_le`) drives the basic +lemmas below; see `girth.ge_iff` for the workhorse characterization. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +/-! ## Neighbour sets and degree + +TEMPORARY: `neighborSet` and `degree` logically belong in +`GraphLib/Graph/Degree.lean`, which is being developed by a collaborator. They +live here only so that `finite_of_two_le_degree` can be stated while `Degree.lean` +is in flux. Do not treat this placement as a precedent: no further degree API +should be added here, and these definitions should move to `Graph/Degree.lean` +once it is ready. -/ + +/-- The neighbours of `v` in the simple graph `G`. + +TEMPORARY placement; belongs in `GraphLib/Graph/Degree.lean` (see section note). -/ +def neighborSet (G : SimpleGraph α) (v : α) : Set α := + {u | G.Adj u v} + +/-- The degree of `v` in the simple graph `G`. Returns `0` if `v` has +infinitely many neighbours. + +TEMPORARY placement; belongs in `GraphLib/Graph/Degree.lean` (see section note). -/ +noncomputable def degree (G : SimpleGraph α) (v : α) : ℕ := + (G.neighborSet v).ncard + +/-! ## Girth -/ + +/-- The girth of a simple graph: the length of a shortest simple cycle, or `⊤` +when there is no simple cycle. -/ +noncomputable def girth (G : SimpleGraph α) : ℕ∞ := + ⨅ (c : SimpleCycle α) (_ : G.IsSimpleCycleIn c), (c.length : ℕ∞) + +namespace girth + +/-! ## Lower bounds -/ + +/-- Characterization of lower bounds for the girth: `n ≤ girth` iff `n` bounds the +length of every simple cycle in `G`. This is the workhorse behind the lemmas +below. -/ +@[grind =] lemma ge_iff (G : SimpleGraph α) {n : ℕ∞} : n ≤ G.girth ↔ + ∀ c : SimpleCycle α, G.IsSimpleCycleIn c → n ≤ c.length := by + simp [girth, le_iInf_iff] + +/-- Every simple cycle in `G` gives an upper bound on the girth. -/ +@[grind →] lemma le_length (G : SimpleGraph α) {c : SimpleCycle α} + (hc : G.IsSimpleCycleIn c) : G.girth ≤ c.length := + iInf₂_le c hc + +/-- If `n` is strictly below the girth, every realized simple cycle has length +strictly greater than `n`. -/ +lemma lt_cycle_length (G : SimpleGraph α) {c : SimpleCycle α} + (hc : G.IsSimpleCycleIn c) {n : ℕ} (h : (n : ℕ∞) < G.girth) : + n < c.length := by + have h' : (n : ℕ∞) < (c.length : ℕ∞) := h.trans_le (le_length G hc) + exact_mod_cast h' + +/-- The girth is at least three. This is true also for acyclic graphs, where the +girth is `⊤`. -/ +@[grind! .] lemma three_le (G : SimpleGraph α) : (3 : ℕ∞) ≤ G.girth := + (ge_iff G).2 fun c _ => by exact_mod_cast c.2.1 + +/-! ## Infinite girth and acyclicity -/ + +/-- A simple graph is acyclic exactly when its girth is infinite. -/ +@[grind =] lemma infinite_iff_isAcyclic (G : SimpleGraph α) : + G.girth = ⊤ ↔ G.IsAcyclic := by + simp [girth, iInf_eq_top, IsAcyclic, HasSimpleCycle] + +/-- A graph with no simple cycle has infinite girth. -/ +lemma infinite_of_isAcyclic (G : SimpleGraph α) (hG : G.IsAcyclic) : + G.girth = ⊤ := + (infinite_iff_isAcyclic G).2 hG + +/-- A simple graph has finite girth exactly when it contains a simple cycle. -/ +@[grind =] lemma finite_iff_hasSimpleCycle (G : SimpleGraph α) : + G.girth ≠ ⊤ ↔ G.HasSimpleCycle := by + rw [← not_iff_not] + simp [infinite_iff_isAcyclic, IsAcyclic] + +/-! ## Attainment -/ + +/-- When the girth is finite, it is attained: some simple cycle in `G` has length +equal to the girth. -/ +lemma exists_cycle (G : SimpleGraph α) (h : G.girth ≠ ⊤) : + ∃ c : SimpleCycle α, G.IsSimpleCycleIn c ∧ (c.length : ℕ∞) = G.girth := by + classical + obtain ⟨c₀, hc₀⟩ := (finite_iff_hasSimpleCycle G).1 h + have hex : ∃ n : ℕ, ∃ c : SimpleCycle α, G.IsSimpleCycleIn c ∧ c.length = n := + ⟨c₀.length, c₀, hc₀, rfl⟩ + obtain ⟨c, hc, hlen⟩ := Nat.find_spec hex + refine ⟨c, hc, le_antisymm ?_ (le_length G hc)⟩ + rw [ge_iff] + intro c' hc' + rw [hlen] + exact_mod_cast Nat.find_min' hex ⟨c', hc', rfl⟩ + +/-- A realized simple cycle whose length is minimal among all realized simple +cycles attains the girth. -/ +lemma eq_length_of_minimal (G : SimpleGraph α) {c : SimpleCycle α} + (hc : G.IsSimpleCycleIn c) + (hmin : ∀ c' : SimpleCycle α, G.IsSimpleCycleIn c' → c.length ≤ c'.length) : + G.girth = c.length := by + refine le_antisymm (le_length G hc) ?_ + rw [ge_iff]; intro c' hc' + exact_mod_cast hmin c' hc' + +/-! ## Upper bounds -/ + +/-- A realized simple cycle of length at most `n` gives the upper bound +`girth ≤ n`. -/ +@[grind →] lemma le_of_length_le (G : SimpleGraph α) {c : SimpleCycle α} + (hc : G.IsSimpleCycleIn c) {n : ℕ∞} (hlen : (c.length : ℕ∞) ≤ n) : + G.girth ≤ n := + (le_length G hc).trans hlen + +/-- Upper bounds for the girth by a natural number: `girth ≤ n` exactly when `G` +contains a realized simple cycle of length at most `n`. The bound is stated over +`ℕ`, since for the bound `⊤` the right-hand side would fail on acyclic graphs. +This is the upper-bound companion of `ge_iff`. -/ +@[grind =] lemma le_iff (G : SimpleGraph α) {n : ℕ} : + G.girth ≤ n ↔ ∃ c : SimpleCycle α, G.IsSimpleCycleIn c ∧ c.length ≤ n := by + constructor + · intro h + have hfin : G.girth ≠ ⊤ := ne_top_of_le_ne_top (ENat.coe_ne_top n) h + obtain ⟨c, hc, hlen⟩ := exists_cycle G hfin + refine ⟨c, hc, ?_⟩ + have hle : (c.length : ℕ∞) ≤ (n : ℕ∞) := hlen.symm ▸ h + exact_mod_cast hle + · rintro ⟨c, hc, hlen⟩ + exact le_of_length_le G hc (by exact_mod_cast hlen) + +/-- If `G` has finite girth, then its girth is at most the number of vertices. -/ +lemma le_ncard_vertexSet (G : SimpleGraph α) (hV : V(G).Finite) + (hG : G.girth ≠ ⊤) : G.girth ≤ V(G).ncard := by + obtain ⟨c, hc, hlen⟩ := exists_cycle G hG + rw [← hlen] + exact_mod_cast IsSimpleCycleIn.length_le_ncard_vertexSet G hV hc + +/-! ## Finiteness from a degree bound -/ + +/-- A finite nonempty simple graph with every vertex of degree at least two has +finite girth. -/ +lemma finite_of_two_le_degree (G : SimpleGraph α) (hV : V(G).Finite) + (hne : V(G).Nonempty) + (hdeg : ∀ v : α, v ∈ V(G) → 2 ≤ G.degree v) : + G.girth ≠ ⊤ := by + classical + let P : ℕ → Prop := + fun n => ∃ p : SimplePath α, G.IsSimplePathIn p ∧ p.length = n + obtain ⟨v, hv⟩ := hne + have hP0 : P 0 := + ⟨SimplePath.singleton v, IsSimplePathIn.singleton G hv, rfl⟩ + let L := Nat.findGreatest P V(G).ncard + have hPL : P L := + Nat.findGreatest_spec (P := P) (m := 0) (n := V(G).ncard) + (Nat.zero_le _) hP0 + obtain ⟨p, hp, hplen⟩ := hPL + have hmax : ∀ q : SimplePath α, G.IsSimplePathIn q → q.length ≤ L := by + intro q hq + have hq_bound : q.length ≤ V(G).ncard := by + have := IsSimplePathIn.length_succ_le_ncard_vertexSet G hV hq + omega + by_contra hnot + have hlt : L < q.length := Nat.lt_of_not_ge hnot + exact (Nat.findGreatest_is_greatest (P := P) hlt hq_bound) ⟨q, hq, rfl⟩ + -- Extending the maximal path `p` by a fresh neighbour of its tail is + -- impossible: it would produce a realized path of length `L + 1 > L`. + have hcontra : ∀ y : α, G.Adj p.tail y → y ∉ p.vertices → False := by + intro y hadj hy_not + obtain ⟨q, hq, hq_length⟩ := IsSimplePathIn.exists_longer_of_adj_not_mem G hp hadj hy_not + have hq_max := hmax q hq + rw [hq_length, hplen] at hq_max + omega + have htail_mem : p.tail ∈ V(G) := SimpleGraph.IsSimpleWalkIn.tail_mem G hp + have htail_deg : 1 < (G.neighborSet p.tail).ncard := by + have htwo := hdeg p.tail htail_mem + simpa [degree] using lt_of_lt_of_le Nat.one_lt_two htwo + by_cases hzero : p.length = 0 + · -- `p` is a single vertex, so any neighbour of `p.tail` is fresh. + obtain ⟨y, hy, _⟩ := Set.exists_ne_of_one_lt_ncard htail_deg p.tail + refine (hcontra y hy.symm ?_).elim + intro hymem + have hlen : p.vertices.length = 0 := hzero + exact hy.ne (by grind) + · -- Pick a neighbour `y` of `p.tail` other than the second-to-last vertex. + let prev : α := p.vertices.dropTail.tail + obtain ⟨y, hy, hy_ne_prev⟩ := Set.exists_ne_of_one_lt_ncard htail_deg prev + by_cases hy_mem : y ∈ p.vertices + · -- `y` lies on `p`, so the suffix from `y` closes into a cycle. + have hq_len : 2 ≤ (p.vertices.suffixFrom y hy_mem).length := by + by_contra hlt + have hle : (p.vertices.suffixFrom y hy_mem).length ≤ 1 := by + change ¬ 2 ≤ (p.vertices.suffixFrom y hy_mem).length at hlt + omega + rcases VertexSeq.eq_tail_or_eq_penultimate_of_length_suffixFrom_le_one + p.vertices hy_mem hzero hle with htail | hprev + · exact hy.ne htail + · exact hy_ne_prev hprev + obtain ⟨c, hc, _⟩ := + IsSimpleCycleIn.exists_length_le_succ_of_adj_mem G hp hy_mem hy.symm hq_len + exact (finite_iff_hasSimpleCycle G).2 ⟨c, hc⟩ + · -- `y` is fresh, contradicting maximality. + exact (hcontra y hy.symm hy_mem).elim + +/-! ## Subgraphs -/ + +/-- Passing to a subgraph can only increase the girth. -/ +@[grind →] lemma le_of_subgraph (G H : SimpleGraph α) + (hsub : SimpleGraph.subgraphOf H G) : G.girth ≤ H.girth := by + by_cases hH : H.girth = ⊤ + · rw [hH]; exact le_top + · obtain ⟨c, hc, hlen⟩ := exists_cycle H hH + rw [← hlen] + exact le_length G (IsSimpleCycleIn.mono G H hc hsub) + +/-! ## Edge-free graphs -/ + +/-- An edge-free graph has infinite girth. -/ +lemma infinite_of_no_edges (G : SimpleGraph α) (hE : E(G) = ∅) : + G.girth = ⊤ := infinite_of_isAcyclic G (isAcyclic_of_no_edges G hE) + +/-! ## Girth of bipartite graphs -/ + +/-- The girth of a bipartite graph is even. Here `Even (⊤ : ℕ∞)` holds, so this +single statement also covers the acyclic case where the girth is `⊤`. -/ +theorem even_of_isBipartite (G : SimpleGraph α) (hG : G.IsBipartite) : + Even G.girth := by + by_cases h : G.girth = ⊤ + · rw [h]; exact ⟨⊤, by simp⟩ + · obtain ⟨c, hc, hlen⟩ := exists_cycle G h + obtain ⟨r, hr⟩ := IsBipartite.even_length_of_isSimpleCycleIn G hG hc + exact ⟨(r : ℕ∞), by rw [← hlen, hr, Nat.cast_add]⟩ + +end girth + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound.lean b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound.lean new file mode 100644 index 0000000..7775a1f --- /dev/null +++ b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound.lean @@ -0,0 +1,61 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Weixuan Yuan +-/ +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.Counting +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.Core +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.RootedLayers +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.HalfLayers +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.Bounds + +/-! +# Moore bounds for simple graphs + +This module states the odd-girth and even-girth Moore bounds for finite simple +graphs. The proofs count the vertices in the breadth-first layers around a root +vertex, respectively around a central edge. + +This file defines nothing itself: it is an *umbrella* module that merely +re-exports (imports) the Moore development, which is split across +`GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/`: + +* `Counting` — the two purely set-theoretic counting lemmas (`namespace Set`). +* `Core` — fresh neighbours of a path\'s tail, the short cycle a chord would + create, and the two lemmas that both layer families consume. +* `RootedLayers` — `IsRootedPath` / `rootLayer`: the layers around a root vertex. +* `HalfLayers` — `IsAvoidingRootedPath` / `halfLayer`: the layers around one end + of a central edge. +* `Bounds` — the two theorems. + +The submodules form the acyclic spine +`Counting ← Core ← RootedLayers ← HalfLayers ← Bounds`. + +## Main results + +* `SimpleGraph.mooreBound_odd` — if `girth G ≥ 2 * r + 1` and every vertex has + degree at least `δ`, then + `1 + δ * ∑ i ∈ range r, (δ - 1)^i ≤ |V(G)|`. +* `SimpleGraph.mooreBound_even` — if `girth G ≥ 2 * r` and every vertex has + degree at least `δ`, then + `2 * ∑ i ∈ range r, (δ - 1)^i ≤ |V(G)|`. + +Everything else is implementation detail and lives in the `SimpleGraph.MooreBound` +namespace: the layer families, the paths that witness them, and the counting +lemmas that make them grow. Only the two theorems above are public API. + +## Implementation notes + +Both statements include a nonempty vertex-set hypothesis. Without it, the lower +degree hypothesis is vacuous on the empty graph, while the odd bound with +`r = 0` asserts `1 ≤ |V(G)|`. + +The odd bound does not require `2 ≤ δ`: its rooted construction starts from the +given nonempty vertex set. The even bound retains this hypothesis because it is +needed to obtain the central edge from which the two half-trees grow. + +The degree API used here is the temporary `SimpleGraph.neighborSet` / +`SimpleGraph.degree` API currently available from +`GraphLib.Theory.Structures.SimpleGraph_only.Girth`; this development +deliberately does not depend on `GraphLib.Graph.Degree`. +-/ diff --git a/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Bounds.lean b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Bounds.lean new file mode 100644 index 0000000..5aa0f26 --- /dev/null +++ b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Bounds.lean @@ -0,0 +1,151 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Weixuan Yuan +-/ +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.HalfLayers + +/-! +# The Moore bounds + +The two counting theorems themselves. Everything they stand on lives in the other +files of the `MooreBound` folder; see the umbrella module +`GraphLib.Theory.Structures.SimpleGraph_only.MooreBound`. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +open MooreBound + +/-! ## The Moore bounds -/ + +/-- Odd-girth version of the Moore bound. -/ +theorem mooreBound_odd (G : SimpleGraph α) (δ r : ℕ) + (hV : V(G).Finite) + (hne : V(G).Nonempty) + (hmin : ∀ v : α, v ∈ V(G) → δ ≤ G.degree v) + (hgirth : (2 * r + 1 : ℕ∞) ≤ G.girth) : + 1 + δ * (∑ i ∈ Finset.range r, (δ - 1) ^ i) ≤ V(G).ncard := by + obtain ⟨x, hx⟩ := hne + -- any index sum that is at most `2 * r` stays strictly below the girth + have hlt_girth : ∀ n : ℕ, n ≤ 2 * r → (n : ℕ∞) < G.girth := by + intro n hn + have hn' : (n : ℕ∞) ≤ (2 * r : ℕ∞) := by exact_mod_cast hn + have hr_lt : (2 * r : ℕ∞) < (2 * r + 1 : ℕ∞) := by + exact_mod_cast Nat.lt_succ_self (2 * r) + exact hn'.trans_lt (hr_lt.trans_le hgirth) + -- the layers `0, 1, …, r` are pairwise disjoint + have hdisj : ∀ ⦃i j : ℕ⦄, i ∈ Finset.range (r + 1) → j ∈ Finset.range (r + 1) → + i ≠ j → Disjoint (rootLayer G x i) (rootLayer G x j) := by + intro i j hi hj hij + rw [Finset.mem_range] at hi hj + exact disjoint_rootLayer_of_ne_of_lt_girth G hij (hlt_girth _ (by omega)) + have hsum_eq := Set.ncard_biUnion_finset_eq_sum (Finset.range (r + 1)) + (fun i => rootLayer G x i) + (fun i _ => hV.subset (rootLayer_subset_vertexSet G x i)) hdisj + -- the union of the layers is contained in the vertex set + have hsub : (⋃ i ∈ Finset.range (r + 1), rootLayer G x i) ⊆ V(G) := by + exact Set.iUnion_subset fun i => Set.iUnion_subset fun _ => + rootLayer_subset_vertexSet G x i + have hcard_le : ∑ i ∈ Finset.range (r + 1), (rootLayer G x i).ncard ≤ V(G).ncard := by + rw [← hsum_eq]; exact Set.ncard_le_ncard hsub hV + -- lower bound each nonzero layer + have hsum_lb : δ * (∑ i ∈ Finset.range r, (δ - 1) ^ i) ≤ + ∑ i ∈ Finset.range r, (rootLayer G x (i + 1)).ncard := by + rw [Finset.mul_sum] + exact Finset.sum_le_sum + (fun i hi => le_ncard_rootLayer_succ G hV hx hmin (by + have hi' : i < r := Finset.mem_range.mp hi + exact_mod_cast hlt_girth (2 * (i + 1)) (by omega))) + have hsplit : ∑ i ∈ Finset.range (r + 1), (rootLayer G x i).ncard + = (∑ i ∈ Finset.range r, (rootLayer G x (i + 1)).ncard) + (rootLayer G x 0).ncard := + Finset.sum_range_succ' (fun i => (rootLayer G x i).ncard) r + calc 1 + δ * (∑ i ∈ Finset.range r, (δ - 1) ^ i) + ≤ 1 + ∑ i ∈ Finset.range r, (rootLayer G x (i + 1)).ncard := by omega + _ = (rootLayer G x 0).ncard + ∑ i ∈ Finset.range r, (rootLayer G x (i + 1)).ncard := by + rw [ncard_rootLayer_zero G hx] + _ = ∑ i ∈ Finset.range (r + 1), (rootLayer G x i).ncard := by rw [hsplit]; omega + _ ≤ V(G).ncard := hcard_le + +/-- Even-girth version of the Moore bound. -/ +theorem mooreBound_even (G : SimpleGraph α) (δ r : ℕ) + (hV : V(G).Finite) + (hne : V(G).Nonempty) + (hδ : 2 ≤ δ) + (hmin : ∀ v : α, v ∈ V(G) → δ ≤ G.degree v) + (hgirth : (2 * r : ℕ∞) ≤ G.girth) : + 2 * (∑ i ∈ Finset.range r, (δ - 1) ^ i) ≤ V(G).ncard := by + classical + -- a central edge `x -- y` + have hmin2 : ∀ v : α, v ∈ V(G) → 2 ≤ G.degree v := + fun v hv => le_trans hδ (hmin v hv) + obtain ⟨x, y, hxy⟩ := exists_adj_of_nonempty_of_two_le_degree G hne hmin2 + -- any index below `2 * r` stays strictly below the girth + have hlt_girth : ∀ n : ℕ, n < 2 * r → (n : ℕ∞) < G.girth := by + intro n hn + have hn' : (n : ℕ∞) < (2 * r : ℕ∞) := by exact_mod_cast hn + exact hn'.trans_le hgirth + -- two half-trees grown from the ends of the edge + have hSideDisj : ∀ (a b : α) ⦃i j : ℕ⦄, + i ∈ Finset.range r → j ∈ Finset.range r → i ≠ j → + Disjoint (halfLayer G a b i) (halfLayer G a b j) := by + intro a b i j hi hj hij + rw [Finset.mem_range] at hi hj + exact disjoint_halfLayer_of_ne_of_lt_girth G hij (hlt_girth _ (by omega)) + have hAdisj : ∀ ⦃i j : ℕ⦄, i ∈ Finset.range r → j ∈ Finset.range r → i ≠ j → + Disjoint (halfLayer G x y i) (halfLayer G x y j) := hSideDisj x y + have hBdisj : ∀ ⦃i j : ℕ⦄, i ∈ Finset.range r → j ∈ Finset.range r → i ≠ j → + Disjoint (halfLayer G y x i) (halfLayer G y x j) := hSideDisj y x + have hSideSub : ∀ a b : α, (⋃ i ∈ Finset.range r, halfLayer G a b i) ⊆ V(G) := by + intro a b + exact Set.iUnion_subset fun i => Set.iUnion_subset fun _ => + halfLayer_subset_vertexSet G a b i + have hAsub : (⋃ i ∈ Finset.range r, halfLayer G x y i) ⊆ V(G) := hSideSub x y + have hBsub : (⋃ i ∈ Finset.range r, halfLayer G y x i) ⊆ V(G) := hSideSub y x + have hAfin := hV.subset hAsub + have hBfin := hV.subset hBsub + -- the two half-trees are disjoint from each other + have hABdisj : Disjoint (⋃ i ∈ Finset.range r, halfLayer G x y i) + (⋃ i ∈ Finset.range r, halfLayer G y x i) := by + rw [Set.disjoint_left] + intro v hvA hvB + simp only [Set.mem_iUnion, exists_prop] at hvA hvB + obtain ⟨i, hi, hvi⟩ := hvA + obtain ⟨j, hj, hvj⟩ := hvB + rw [Finset.mem_range] at hi hj + exact Set.disjoint_left.mp + (disjoint_halfLayer_opposite_of_lt_girth G hxy (hlt_girth _ (by omega))) hvi hvj + -- lower bounds on the two half-trees + have hSideLb : ∀ {a b : α}, G.Adj a b → + (∑ i ∈ Finset.range r, (δ - 1) ^ i) ≤ + (⋃ i ∈ Finset.range r, halfLayer G a b i).ncard := by + intro a b hab + rw [Set.ncard_biUnion_finset_eq_sum (Finset.range r) (fun i => halfLayer G a b i) + (fun i _ => hV.subset (halfLayer_subset_vertexSet G a b i)) (hSideDisj a b)] + exact Finset.sum_le_sum + (fun i hi => le_ncard_halfLayer G hV hab hmin (by + have hi' : i < r := Finset.mem_range.mp hi + exact_mod_cast hlt_girth (2 * i) (by omega))) + have hAlb := hSideLb hxy + have hBlb := hSideLb hxy.symm + have hABsub : (⋃ i ∈ Finset.range r, halfLayer G x y i) ∪ + (⋃ i ∈ Finset.range r, halfLayer G y x i) ⊆ V(G) := Set.union_subset hAsub hBsub + calc 2 * (∑ i ∈ Finset.range r, (δ - 1) ^ i) + = (∑ i ∈ Finset.range r, (δ - 1) ^ i) + (∑ i ∈ Finset.range r, (δ - 1) ^ i) := by + rw [two_mul] + _ ≤ (⋃ i ∈ Finset.range r, halfLayer G x y i).ncard + + (⋃ i ∈ Finset.range r, halfLayer G y x i).ncard := Nat.add_le_add hAlb hBlb + _ = ((⋃ i ∈ Finset.range r, halfLayer G x y i) ∪ + (⋃ i ∈ Finset.range r, halfLayer G y x i)).ncard := + (Set.ncard_union_eq hABdisj hAfin hBfin).symm + _ ≤ V(G).ncard := Set.ncard_le_ncard hABsub hV + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Core.lean b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Core.lean new file mode 100644 index 0000000..e1884d7 --- /dev/null +++ b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Core.lean @@ -0,0 +1,204 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Weixuan Yuan +-/ +import GraphLib.Theory.Structures.SimpleGraph_only.Girth +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.Counting + +/-! +# Moore bounds: fresh neighbours and the shared counting core + +The parts of the Moore argument that do not mention a layer family: the fresh +neighbours of a path's tail, the short cycle a chord would create, and the two +lemmas (`pred_le_ncard_freshNeighborSet`, `false_of_two_paths_of_common_fresh_neighbor`) +that the odd and even layer families both consume. + +Part of the `MooreBound` folder; see the umbrella module +`GraphLib.Theory.Structures.SimpleGraph_only.MooreBound`. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +/-! ## Degree helpers + +TEMPORARY: these three lemmas are pure `neighborSet` / `degree` API with no +dependence on the Moore development. They logically belong in +`GraphLib/Graph/Degree.lean` (alongside the `neighborSet` / `degree` definitions +they use, which are themselves temporarily housed in +`GraphLib.Theory.Structures.SimpleGraph_only.Girth`; see the section note there). +They live here at the top of the file only so that the Moore proofs below can use +them while `Degree.lean` is being developed by a collaborator. Do not treat this +placement as a precedent: no further degree API should be added here, and these +should move to `Graph/Degree.lean` once it is ready. -/ + +/-- Every neighbour of a vertex is a vertex of the ambient graph. -/ +lemma neighborSet_subset_vertexSet (G : SimpleGraph α) (v : α) : + G.neighborSet v ⊆ V(G) := by + intro u hu + exact SimpleGraph.Adj.left_mem hu + +/-- A vertex with degree at least two has a neighbour different from any fixed +vertex. -/ +lemma exists_neighbor_ne_of_two_le_degree (G : SimpleGraph α) {v w : α} + (hdeg : 2 ≤ G.degree v) : + ∃ u : α, G.Adj v u ∧ u ≠ w := by + classical + obtain ⟨u, hu, huw⟩ := Set.exists_ne_of_one_lt_ncard + (by simpa [degree] using lt_of_lt_of_le Nat.one_lt_two hdeg) w + exact ⟨u, hu.symm, huw⟩ + +/-- A nonempty graph whose vertices all have degree at least two contains an +edge. -/ +lemma exists_adj_of_nonempty_of_two_le_degree (G : SimpleGraph α) + (hne : V(G).Nonempty) + (hmin : ∀ v : α, v ∈ V(G) → 2 ≤ G.degree v) : + ∃ x y : α, G.Adj x y := by + obtain ⟨x, hx⟩ := hne + obtain ⟨y, hxy, _⟩ := exists_neighbor_ne_of_two_le_degree + (G := G) (v := x) (w := x) (hmin x hx) + exact ⟨x, y, hxy⟩ + +namespace MooreBound + +/-! ## Fresh neighbours -/ + +/-- The fresh neighbours of the tail of a path: neighbours that have not +already appeared on the path. -/ +def freshNeighborSet (G : SimpleGraph α) (p : SimplePath α) : Set α := + {u | G.Adj p.tail u ∧ u ∉ p.vertices} + +/-- Fresh neighbours are vertices of the ambient graph. -/ +lemma freshNeighborSet_subset_vertexSet (G : SimpleGraph α) (p : SimplePath α) : + freshNeighborSet G p ⊆ V(G) := by + intro u hu + exact hu.1.right_mem + +/-! ## Short cycles from rooted paths -/ + +/-- Under a girth lower bound, a neighbour of the tail that already lies on a +short realized path can only be the tail itself or the penultimate vertex. -/ +lemma eq_tail_or_eq_penultimate_of_adj_mem_of_lt_girth (G : SimpleGraph α) + {p : SimplePath α} {y : α} + (hp : G.IsSimplePathIn p) (hy_mem : y ∈ p.vertices) + (hadj : G.Adj p.tail y) (hpos : p.length ≠ 0) + (hgirth : ((p.length + 1 : ℕ) : ℕ∞) < G.girth) : + y = p.tail ∨ y = p.vertices.dropTail.tail := by + classical + by_cases hle : (p.vertices.suffixFrom y hy_mem).length ≤ 1 + · exact VertexSeq.eq_tail_or_eq_penultimate_of_length_suffixFrom_le_one + p.vertices hy_mem hpos hle + · obtain ⟨c, hc, hc_len⟩ := IsSimpleCycleIn.exists_length_le_succ_of_adj_mem + G hp hy_mem hadj (by omega) + have hlong := girth.lt_cycle_length G hc hgirth + omega + +/-- In a graph of sufficiently large girth, all neighbours of the tail except +possibly the penultimate vertex are fresh neighbours. Consequently, the number +of fresh neighbours is at least `degree - 1`. -/ +lemma pred_degree_le_ncard_freshNeighborSet_of_lt_girth (G : SimpleGraph α) + (hV : V(G).Finite) {p : SimplePath α} + (hp : G.IsSimplePathIn p) (hpos : p.length ≠ 0) + (hgirth : ((p.length + 1 : ℕ) : ℕ∞) < G.girth) : + G.degree p.tail - 1 ≤ (freshNeighborSet G p).ncard := by + classical + let prev : α := p.vertices.dropTail.tail + have hsubset : G.neighborSet p.tail \ {prev} ⊆ freshNeighborSet G p := by + rintro u ⟨hu_neigh, hu_ne_prev⟩ + refine ⟨hu_neigh.symm, ?_⟩ + intro hu_mem + rcases eq_tail_or_eq_penultimate_of_adj_mem_of_lt_girth + G hp hu_mem hu_neigh.symm hpos hgirth with hu_tail | hu_prev + · exact hu_neigh.symm.ne hu_tail.symm + · exact hu_ne_prev hu_prev + have hfresh_fin : (freshNeighborSet G p).Finite := + hV.subset (freshNeighborSet_subset_vertexSet G p) + calc + G.degree p.tail - 1 = (G.neighborSet p.tail).ncard - 1 := rfl + _ ≤ (G.neighborSet p.tail \ {prev}).ncard := + Set.pred_ncard_le_ncard_diff_singleton (G.neighborSet p.tail) prev + _ ≤ (freshNeighborSet G p).ncard := + Set.ncard_le_ncard hsubset hfresh_fin + +/-! ## Shared core of the two layer-growth lemmas + +`mul_ncard_rootLayer_le_succ_of_lt_girth` (`RootedLayers.lean`) and +`mul_ncard_halfLayer_le_succ_of_lt_girth` (`HalfLayers.lean`) prove the same +statement for two different layer families (rooted paths, and rooted paths +avoiding the far end of a central edge). The lemmas here are exactly the parts of +that argument which do not mention the layer family at all, so both callers share +them: + +* `Set.mul_ncard_le_ncard_of_children` (in `Counting.lean`) — the counting + skeleton; +* `pred_le_ncard_freshNeighborSet` — the per-vertex lower bound; +* `false_of_two_paths_of_common_fresh_neighbor` — the disjointness core. -/ + +/-- The fresh neighbours of a realized path of length `n ≠ 0` number at least `δ - 1`, +given the minimum-degree hypothesis and a girth bound comfortably above `n`. -/ +lemma pred_le_ncard_freshNeighborSet (G : SimpleGraph α) (hV : V(G).Finite) + {δ n : ℕ} {p : SimplePath α} {v : α} + (hmin : ∀ w : α, w ∈ V(G) → δ ≤ G.degree w) + (hp : G.IsSimplePathIn p) (hpt : p.tail = v) (hpl : p.length = n) (hpos : n ≠ 0) + (hgirth : (2 * (n + 1) : ℕ∞) < G.girth) : + δ - 1 ≤ (freshNeighborSet G p).ncard := by + have hp_girth : ((p.length + 1 : ℕ) : ℕ∞) < G.girth := by + have hle : ((p.length + 1 : ℕ) : ℕ∞) ≤ (2 * (n + 1) : ℕ∞) := by + rw [hpl]; exact_mod_cast (show n + 1 ≤ 2 * (n + 1) by omega) + exact hle.trans_lt hgirth + have hpred := pred_degree_le_ncard_freshNeighborSet_of_lt_girth G hV hp (by omega) hp_girth + rw [hpt] at hpred + have hdeg : δ ≤ G.degree v := hmin v (hpt ▸ IsSimpleWalkIn.tail_mem G hp) + omega + +/-- Disjointness core: two realized simple paths of equal length `n` from a common head, +with *distinct* tails, cannot share a fresh neighbour `u`. Extending both by `u` would give +two distinct paths with the same endpoints, hence a cycle of length at most `2 * (n + 1)`, +contradicting the girth bound. -/ +lemma false_of_two_paths_of_common_fresh_neighbor (G : SimpleGraph α) + {p q : SimplePath α} {x u : α} {n : ℕ} + (hp : G.IsSimplePathIn p) (hq : G.IsSimplePathIn q) + (hph : p.head = x) (hqh : q.head = x) + (hpl : p.length = n) (hql : q.length = n) + (hne : p.tail ≠ q.tail) + (hpadj : G.Adj p.tail u) (hqadj : G.Adj q.tail u) + (hpu : u ∉ p.vertices) (hqu : u ∉ q.vertices) + (hgirth : (2 * (n + 1) : ℕ∞) < G.girth) : False := by + have hdisjp : ∀ z, z ∈ p.vertices → + z ∈ (SimplePath.singleton u).vertices → False := by grind + have hdisjq : ∀ z, z ∈ q.vertices → + z ∈ (SimplePath.singleton u).vertices → False := by grind + set p' : SimplePath α := p.append (SimplePath.singleton u) hdisjp with hp'def + set q' : SimplePath α := q.append (SimplePath.singleton u) hdisjq with hq'def + have hp'v : p'.vertices = p.vertices.cons u := rfl + have hq'v : q'.vertices = q.vertices.cons u := rfl + have hp'real : G.IsSimplePathIn p' := + IsVertexSeqIn.append G hp (IsVertexSeqIn.singleton u hpadj.right_mem) hpadj + have hq'real : G.IsSimplePathIn q' := + IsVertexSeqIn.append G hq (IsVertexSeqIn.singleton u hqadj.right_mem) hqadj + have hp'h : p'.head = x := by simp [hp'v, hph] + have hq'h : q'.head = x := by simp [hq'v, hqh] + have hp't : p'.tail = u := by simp [hp'v] + have hq't : q'.tail = u := by simp [hq'v] + have hp'l : p'.length = n + 1 := by simp [hp'v, VertexSeq.length, hpl]; omega + have hq'l : q'.length = n + 1 := by simp [hq'v, VertexSeq.length, hql]; omega + -- the two extended paths differ, since their penultimate vertices are the distinct tails + have hdistinct : p'.vertices ≠ q'.vertices := fun heq => + hne (congrArg VertexSeq.tail ((VertexSeq.cons.injEq _ _ _ _).mp (hp'v ▸ hq'v ▸ heq)).1) + obtain ⟨c, hc, hcle⟩ := IsSimpleCycleIn.exists_length_le_add_of_two_paths G + hp'real hq'real (hp'h.trans hq'h.symm) (hp't.trans hq't.symm) hdistinct + have hlong := girth.lt_cycle_length G hc hgirth + have hsum : p'.length + q'.length = 2 * (n + 1) := by omega + exact (not_lt_of_ge (hcle.trans_eq hsum) hlong).elim + +end MooreBound + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Counting.lean b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Counting.lean new file mode 100644 index 0000000..dd61e4a --- /dev/null +++ b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/Counting.lean @@ -0,0 +1,66 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Weixuan Yuan +-/ +import Mathlib.Algebra.BigOperators.Group.Finset.Basic +import Mathlib.Algebra.Order.BigOperators.Group.Finset +import Mathlib.Algebra.BigOperators.Ring.Finset +import Mathlib.Algebra.BigOperators.Finprod +import Mathlib.Data.Set.Card.Arithmetic + +/-! +# Counting lemmas for the Moore bounds + +Two purely set-theoretic counting lemmas. Neither mentions a graph: they are the +skeleton on which both Moore bounds are built, and they live in `namespace Set` +so that the graph development can stay free of ad-hoc cardinality reasoning. + +## Main results + +* `Set.ncard_biUnion_finset_eq_sum` — the size of a disjoint finite union is the + sum of the sizes. +* `Set.mul_ncard_le_ncard_of_children` — if every element of `L` owns at least `k` + elements of `N`, and distinct elements own disjoint sets, then `k * |L| ≤ |N|`. +-/ + +namespace Set + +lemma ncard_biUnion_finset_eq_sum {ι β : Type*} (s : Finset ι) + (f : ι → Set β) + (hfin : ∀ i ∈ s, (f i).Finite) + (hdisj : ∀ ⦃i j : ι⦄, i ∈ s → j ∈ s → i ≠ j → Disjoint (f i) (f j)) : + (⋃ i ∈ s, f i).ncard = ∑ i ∈ s, (f i).ncard := by + have h := _root_.Set.Finite.ncard_biUnion s.finite_toSet (s := f) + (fun i hi => hfin i (by simpa using hi)) + (fun i hi j hj hij => hdisj (by simpa using hi) (by simpa using hj) hij) + rwa [finsum_mem_coe_finset] at h + +/-- Counting skeleton: if every element of a finite set `L` has a "child set" `F v` of size +at least `k` inside `N`, and distinct elements have disjoint child sets, then +`k * |L| ≤ |N|`. Purely set-theoretic; no graph theory is involved. -/ +lemma mul_ncard_le_ncard_of_children {β : Type*} {L N : Set β} {F : β → Set β} {k : ℕ} + (hLfin : L.Finite) (hNfin : N.Finite) + (hFsub : ∀ v ∈ L, F v ⊆ N) + (hFcard : ∀ v ∈ L, k ≤ (F v).ncard) + (hFdisj : ∀ ⦃v v' : β⦄, v ∈ L → v' ∈ L → v ≠ v' → Disjoint (F v) (F v')) : + k * L.ncard ≤ N.ncard := by + classical + have hFfin : ∀ v ∈ L, (F v).Finite := fun v hv => hNfin.subset (hFsub v hv) + have hbUsub : (⋃ v ∈ hLfin.toFinset, F v) ⊆ N := + Set.iUnion_subset fun v => Set.iUnion_subset fun hv => hFsub v (by simpa using hv) + have hsum := ncard_biUnion_finset_eq_sum hLfin.toFinset F + (fun v hv => hFfin v (by rwa [Set.Finite.mem_toFinset] at hv)) + (fun v v' hv hv' hne => hFdisj (by rwa [Set.Finite.mem_toFinset] at hv) + (by rwa [Set.Finite.mem_toFinset] at hv') hne) + have hle_sum : ∑ _v ∈ hLfin.toFinset, k ≤ ∑ v ∈ hLfin.toFinset, (F v).ncard := + Finset.sum_le_sum (fun v hv => hFcard v (by rwa [Set.Finite.mem_toFinset] at hv)) + have hconst : ∑ _v ∈ hLfin.toFinset, k = k * L.ncard := by + rw [Finset.sum_const, smul_eq_mul, Set.ncard_eq_toFinset_card _ hLfin, mul_comm] + calc k * L.ncard + = ∑ _v ∈ hLfin.toFinset, k := hconst.symm + _ ≤ ∑ v ∈ hLfin.toFinset, (F v).ncard := hle_sum + _ = (⋃ v ∈ hLfin.toFinset, F v).ncard := hsum.symm + _ ≤ N.ncard := Set.ncard_le_ncard hbUsub hNfin + +end Set diff --git a/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/HalfLayers.lean b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/HalfLayers.lean new file mode 100644 index 0000000..4d0b5c5 --- /dev/null +++ b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/HalfLayers.lean @@ -0,0 +1,357 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Weixuan Yuan +-/ +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.RootedLayers + +/-! +# Moore bounds: the half-layers around a central edge + +The breadth-first layers grown from one endpoint of a central edge while avoiding +the other: `IsAvoidingRootedPath`, the layer `halfLayer` it cuts out, and the +growth and disjointness estimates. This is the family behind the even Moore bound. + +Part of the `MooreBound` folder; see the umbrella module +`GraphLib.Theory.Structures.SimpleGraph_only.MooreBound`. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +namespace MooreBound + +/-! ## Even half-layer counting -/ + +/-- A rooted path from the first endpoint `x` of an ordered central edge that +avoids its other endpoint `banned`. -/ +@[grind] def IsAvoidingRootedPath (G : SimpleGraph α) (x banned : α) (i : ℕ) + (v : α) : Prop := + ∃ p : SimplePath α, + G.IsSimplePathIn p ∧ p.head = x ∧ p.tail = v ∧ p.length = i ∧ + banned ∉ p.vertices + +namespace IsAvoidingRootedPath + +/-- A chosen path witnessing an avoiding rooted path. -/ +noncomputable def path {G : SimpleGraph α} {x banned v : α} {i : ℕ} + (h : IsAvoidingRootedPath G x banned i v) : SimplePath α := + Classical.choose h + +/-- The chosen witness path is realized in the ambient graph. -/ +lemma path_isSimplePathIn {G : SimpleGraph α} {x banned v : α} {i : ℕ} + (h : IsAvoidingRootedPath G x banned i v) : G.IsSimplePathIn h.path := + (Classical.choose_spec h).1 + +/-- The chosen witness path starts at the root. -/ +lemma path_head {G : SimpleGraph α} {x banned v : α} {i : ℕ} + (h : IsAvoidingRootedPath G x banned i v) : h.path.head = x := + (Classical.choose_spec h).2.1 + +/-- The chosen witness path ends at the half-layer vertex. -/ +lemma path_tail {G : SimpleGraph α} {x banned v : α} {i : ℕ} + (h : IsAvoidingRootedPath G x banned i v) : h.path.tail = v := + (Classical.choose_spec h).2.2.1 + +/-- The chosen witness path has the prescribed length. -/ +lemma path_length {G : SimpleGraph α} {x banned v : α} {i : ℕ} + (h : IsAvoidingRootedPath G x banned i v) : h.path.length = i := + (Classical.choose_spec h).2.2.2.1 + +/-- The chosen witness path avoids the banned vertex. -/ +lemma path_not_banned {G : SimpleGraph α} {x banned v : α} {i : ℕ} + (h : IsAvoidingRootedPath G x banned i v) : banned ∉ h.path.vertices := + (Classical.choose_spec h).2.2.2.2 + +/-- An avoiding rooted path is, in particular, a rooted path. -/ +lemma isRootedPath {G : SimpleGraph α} {x banned v : α} {i : ℕ} + (h : IsAvoidingRootedPath G x banned i v) : IsRootedPath G x i v := + ⟨h.path, h.path_isSimplePathIn, h.path_head, h.path_tail, h.path_length⟩ + +/-- The singleton path witnesses the zero-th half-layer, provided the banned +vertex is different from the root. -/ +lemma singleton (G : SimpleGraph α) {x banned : α} (hx : x ∈ V(G)) + (hxb : x ≠ banned) : IsAvoidingRootedPath G x banned 0 x := by + refine ⟨SimplePath.singleton x, IsSimplePathIn.singleton G hx, rfl, rfl, rfl, ?_⟩ + change banned ∉ VertexSeq.singleton x + simpa only [VertexSeq.mem_singleton] using hxb.symm + +/-- Extending an avoiding rooted path by a fresh neighbour different from the +banned vertex stays in the next half-layer. -/ +lemma succ_of_adj_not_mem (G : SimpleGraph α) {x banned v u : α} {i : ℕ} + (h : IsAvoidingRootedPath G x banned i v) (hadj : G.Adj v u) + (hnot : u ∉ h.path.vertices) (hub : u ≠ banned) : + IsAvoidingRootedPath G x banned (i + 1) u := by + have hdisj : ∀ z : α, z ∈ h.path.vertices → + z ∈ (SimplePath.singleton u).vertices → False := by + grind + have hstep : G.Adj h.path.tail (SimplePath.singleton u).head := by + simpa only [SimplePath.head_singleton, h.path_tail] using hadj + have hnb := h.path_not_banned + refine ⟨h.path.append (SimplePath.singleton u) hdisj, + IsSimpleWalkIn.append G h.path_isSimplePathIn + (IsSimplePathIn.singleton G hadj.right_mem) hstep, ?_, ?_, ?_, ?_⟩ <;> + simp [h.path_head, h.path_length] + grind + +end IsAvoidingRootedPath + +/-- The vertices reached from `x` by rooted paths avoiding `banned`. -/ +def halfLayer (G : SimpleGraph α) (x banned : α) (i : ℕ) : Set α := + {v | IsAvoidingRootedPath G x banned i v} + +/-- Membership in a half-layer is witnessed by an avoiding rooted path that +avoids the banned endpoint. -/ +@[simp, grind =] lemma mem_halfLayer (G : SimpleGraph α) {x banned v : α} {i : ℕ} : + v ∈ halfLayer G x banned i ↔ IsAvoidingRootedPath G x banned i v := + Iff.rfl + +/-- Every half-layer is contained in the ambient vertex set. -/ +lemma halfLayer_subset_vertexSet (G : SimpleGraph α) (x banned : α) (i : ℕ) : + halfLayer G x banned i ⊆ V(G) := by + intro v hv + exact IsRootedPath.tail_mem G hv.isRootedPath + +/-- The zero-th half-layer is the singleton root. -/ +@[simp] lemma halfLayer_zero (G : SimpleGraph α) {x banned : α} (hx : x ∈ V(G)) + (hxb : x ≠ banned) : halfLayer G x banned 0 = {x} := by + ext v + constructor <;> grind [IsAvoidingRootedPath.singleton] + +/-- The zero-th half-layer has one vertex. -/ +lemma ncard_halfLayer_zero (G : SimpleGraph α) {x banned : α} + (hx : x ∈ V(G)) (hxb : x ≠ banned) : + (halfLayer G x banned 0).ncard = 1 := by + rw [halfLayer_zero G hx hxb] + exact Set.ncard_singleton x + +/-- Prefixing an avoiding rooted path from the opposite endpoint by the central +edge gives an ordinary rooted path from this endpoint. -/ +lemma isRootedPath_of_adj_of_isAvoidingRootedPath (G : SimpleGraph α) {x y v : α} + {i : ℕ} (hxy : G.Adj x y) (h : IsAvoidingRootedPath G y x i v) : + IsRootedPath G x (i + 1) v := by + have hnb := h.path_not_banned + have hdisj : ∀ z : α, z ∈ (SimplePath.singleton x).vertices → + z ∈ h.path.vertices → False := by + grind + have hstep : G.Adj (SimplePath.singleton x).tail h.path.head := by + simpa only [SimplePath.tail_singleton, h.path_head] using hxy + refine ⟨(SimplePath.singleton x).append h.path hdisj, + IsSimpleWalkIn.append G (IsSimplePathIn.singleton G hxy.left_mem) + h.path_isSimplePathIn hstep, ?_, ?_, ?_⟩ <;> + simp [h.path_tail, h.path_length] + +/-- The endpoint of a sufficiently short avoiding rooted path is not adjacent +to the banned endpoint of the central edge. -/ +lemma not_adj_banned_of_isAvoidingRootedPath_of_lt_girth (G : SimpleGraph α) + {x banned v : α} {i : ℕ} + (hxb : G.Adj x banned) (h : IsAvoidingRootedPath G x banned i v) + (hpos : i ≠ 0) (hgirth : (i + 2 : ℕ∞) < G.girth) : + ¬ G.Adj v banned := by + classical + intro hvb + have hnb := h.path_not_banned + have hdisj : ∀ z : α, z ∈ h.path.vertices → + z ∈ (SimplePath.singleton banned).vertices → False := by + grind + set q : SimplePath α := h.path.append (SimplePath.singleton banned) hdisj with hqdef + have hq_len : q.length = i + 1 := by + simp [hqdef, h.path_length] + have hq_two : 2 ≤ q.length := by omega + have hq_real : G.IsSimplePathIn q := + IsSimpleWalkIn.append G h.path_isSimplePathIn + (IsSimplePathIn.singleton G hxb.right_mem) + (by simpa only [SimplePath.head_singleton, h.path_tail] using hvb) + have hends : q.tail = banned ∧ q.head = x := by + simp [hqdef, h.path_head] + have hclose : G.Adj q.tail q.head := by + rw [hends.1, hends.2]; exact hxb.symm + have hcle : (SimpleCycle.ofPathClosing q hq_two).length ≤ i + 2 := by + change 1 + q.length ≤ i + 2 + omega + exact absurd (girth.lt_cycle_length G + (IsSimpleCycleIn.ofPathClosing G hq_real hclose hq_two) hgirth) (not_lt_of_ge hcle) + +/-- The first half-layer has at least `δ - 1` vertices. -/ +lemma pred_le_ncard_halfLayer_one (G : SimpleGraph α) (hV : V(G).Finite) + {δ : ℕ} {x banned : α} + (hxb : G.Adj x banned) + (hmin : ∀ v : α, v ∈ V(G) → δ ≤ G.degree v) : + δ - 1 ≤ (halfLayer G x banned 1).ncard := by + classical + have hsub : G.neighborSet x \ {banned} ⊆ halfLayer G x banned 1 := by + rintro u ⟨hux, hub⟩ + have hxu : G.Adj x u := hux.symm + have hub' : u ≠ banned := by simpa [Set.mem_singleton_iff] using hub + have hdisj : ∀ z : α, z ∈ (SimplePath.singleton x).vertices → + z ∈ (SimplePath.singleton u).vertices → False := by + grind + refine ⟨(SimplePath.singleton x).append (SimplePath.singleton u) hdisj, + IsSimpleWalkIn.append G (IsSimplePathIn.singleton G hxb.left_mem) + (IsSimplePathIn.singleton G hxu.right_mem) hxu, ?_, ?_, ?_, ?_⟩ <;> + simp + grind + have hhalf_fin : (halfLayer G x banned 1).Finite := + hV.subset (halfLayer_subset_vertexSet G x banned 1) + have hpred : G.degree x - 1 ≤ (G.neighborSet x \ {banned}).ncard := by + simpa [degree] using Set.pred_ncard_le_ncard_diff_singleton (G.neighborSet x) banned + exact (Nat.sub_le_sub_right (hmin x hxb.left_mem) 1).trans + (hpred.trans (Set.ncard_le_ncard hsub hhalf_fin)) + +/-- Successive nonzero half-layers grow by a factor of at least `δ - 1`. -/ +lemma mul_ncard_halfLayer_le_succ_of_lt_girth (G : SimpleGraph α) + (hV : V(G).Finite) {δ i : ℕ} {x banned : α} + (hxb : G.Adj x banned) + (hmin : ∀ v : α, v ∈ V(G) → δ ≤ G.degree v) + (hgirth : (2 * (i + 2) : ℕ∞) < G.girth) : + (δ - 1) * (halfLayer G x banned (i + 1)).ncard ≤ + (halfLayer G x banned (i + 2)).ncard := by + classical + have hLfin : (halfLayer G x banned (i + 1)).Finite := + hV.subset (halfLayer_subset_vertexSet G x banned (i + 1)) + have hNfin : (halfLayer G x banned (i + 2)).Finite := + hV.subset (halfLayer_subset_vertexSet G x banned (i + 2)) + set F : α → Set α := fun v => + if h : IsAvoidingRootedPath G x banned (i + 1) v then freshNeighborSet G h.path else ∅ + with hFdef + have hFv : ∀ v, (hv : IsAvoidingRootedPath G x banned (i + 1) v) → + F v = freshNeighborSet G hv.path := by + intro v hv + simp only [hFdef, dif_pos hv] + have hgirth' : (2 * (((i + 1 : ℕ) : ℕ∞) + 1)) < G.girth := by + push_cast; convert hgirth using 2 + refine Set.mul_ncard_le_ncard_of_children (F := F) hLfin hNfin ?_ ?_ ?_ + · -- children lie in the next half-layer: a child cannot be the banned endpoint, + -- since that would close a cycle shorter than the girth + intro v hv u hu + have hvO : IsAvoidingRootedPath G x banned (i + 1) v := hv + rw [hFv v hvO] at hu + obtain ⟨hadj, hnot⟩ := hu + have hub : u ≠ banned := by + intro hub + subst hub + have hshort : (i + 1 + 2 : ℕ∞) < G.girth := by + apply lt_of_le_of_lt _ hgirth + exact_mod_cast (show i + 1 + 2 ≤ 2 * (i + 2) by omega) + exact not_adj_banned_of_isAvoidingRootedPath_of_lt_girth G hxb hvO (by omega) hshort + (hvO.path_tail ▸ hadj) + exact IsAvoidingRootedPath.succ_of_adj_not_mem G hvO (hvO.path_tail ▸ hadj) hnot hub + · -- each child set has at least `δ - 1` elements + intro v hv + have hvO : IsAvoidingRootedPath G x banned (i + 1) v := hv + rw [hFv v hvO] + exact pred_le_ncard_freshNeighborSet G hV hmin hvO.path_isSimplePathIn + hvO.path_tail hvO.path_length (by omega) hgirth' + · -- distinct source vertices produce disjoint child sets + intro v v' hv hv' hne + rw [Set.disjoint_left] + intro u hu hu' + have hvO : IsAvoidingRootedPath G x banned (i + 1) v := hv + have hv'O : IsAvoidingRootedPath G x banned (i + 1) v' := hv' + rw [hFv v hvO] at hu + rw [hFv v' hv'O] at hu' + obtain ⟨hadj, hnot⟩ := hu + obtain ⟨hadj', hnot'⟩ := hu' + exact false_of_two_paths_of_common_fresh_neighbor G + hvO.path_isSimplePathIn hv'O.path_isSimplePathIn hvO.path_head hv'O.path_head + hvO.path_length hv'O.path_length + (by rw [hvO.path_tail, hv'O.path_tail]; exact hne) hadj hadj' hnot hnot' hgirth' + +/-- Distinct same-side half-layers are disjoint when the sum of their indices +is below the girth. -/ +lemma disjoint_halfLayer_of_ne_of_lt_girth (G : SimpleGraph α) + {x banned : α} {i j : ℕ} (hij : i ≠ j) + (hgirth : ((i + j : ℕ) : ℕ∞) < G.girth) : + Disjoint (halfLayer G x banned i) (halfLayer G x banned j) := by + classical + rw [Set.disjoint_left] + intro v hvi hvj + obtain ⟨p, hp, hpx, hpv, hplen, _⟩ := hvi + obtain ⟨q, hq, hqx, hqv, hqlen, _⟩ := hvj + have hne : p.vertices ≠ q.vertices := fun hvertices => hij (by + rw [← hplen, ← hqlen]; exact congrArg VertexSeq.length hvertices) + obtain ⟨c, hc, hcle⟩ := IsSimpleCycleIn.exists_length_le_add_of_two_paths G hp hq + (hpx.trans hqx.symm) (hpv.trans hqv.symm) hne + have hlong := girth.lt_cycle_length G hc hgirth + omega + +/-- Opposite half-layers below radius `r` are disjoint under an even girth +lower bound. -/ +lemma disjoint_halfLayer_opposite_of_lt_girth (G : SimpleGraph α) + {x y : α} {i j : ℕ} + (hxy : G.Adj x y) (hgirth : ((i + j + 1 : ℕ) : ℕ∞) < G.girth) : + Disjoint (halfLayer G x y i) (halfLayer G y x j) := by + classical + rw [Set.disjoint_left] + intro v hvi hvj + obtain ⟨p, hp, hpx, hpv, hplen, hpy⟩ := hvi + obtain ⟨qOpp, hqOpp, hqOpp_y, hqOpp_v, hqOpp_len, hqOpp_notx⟩ := hvj + have hdisj : ∀ z : α, z ∈ (SimplePath.singleton x).vertices → + z ∈ qOpp.vertices → False := by + grind + have hstep : G.Adj (SimplePath.singleton x).tail qOpp.head := by + simpa only [SimplePath.tail_singleton, hqOpp_y] using hxy + set q : SimplePath α := (SimplePath.singleton x).append qOpp hdisj with hqdef + have hq : G.IsSimplePathIn q := + IsSimpleWalkIn.append G (IsSimplePathIn.singleton G hxy.left_mem) hqOpp hstep + have hqx : q.head = x := by + simp [hqdef] + have hqv : q.tail = v := by + simp [hqdef, hqOpp_v] + have hqlen : q.length = j + 1 := by + simp [hqdef, hqOpp_len] + -- `p` avoids `y`, but the prefixed path `q` visits it, so they are distinct + have hne : p.vertices ≠ q.vertices := by + have hyq : y ∈ q.vertices := by + simp [hqdef, ← hqOpp_y] + exact fun heq => hpy (heq ▸ hyq) + obtain ⟨c, hc, hcle⟩ := IsSimpleCycleIn.exists_length_le_add_of_two_paths G hp hq + (hpx.trans hqx.symm) (hpv.trans hqv.symm) hne + have hlong := girth.lt_cycle_length G hc hgirth + omega + +/-- Nonzero same-side half-layer growth plus the first-layer estimate give the +expected lower bound for every half-layer below radius `r`. -/ +lemma le_ncard_halfLayer (G : SimpleGraph α) (hV : V(G).Finite) + {δ i : ℕ} {x banned : α} + (hxb : G.Adj x banned) + (hmin : ∀ v : α, v ∈ V(G) → δ ≤ G.degree v) + (hgirth : (2 * i : ℕ∞) < G.girth) : + (δ - 1) ^ i ≤ (halfLayer G x banned i).ncard := by + induction i with + | zero => + rw [pow_zero, ncard_halfLayer_zero G hxb.left_mem hxb.ne] + | succ i ih => + cases i with + | zero => + simpa using pred_le_ncard_halfLayer_one G hV hxb hmin + | succ i => + have hgirthPrev : (2 * (i + 1) : ℕ∞) < G.girth := by + apply lt_of_le_of_lt _ hgirth + exact_mod_cast (show 2 * (i + 1) ≤ 2 * (i + 1 + 1) by omega) + have hprev : (δ - 1) ^ (i + 1) ≤ + (halfLayer G x banned (i + 1)).ncard := ih hgirthPrev + have hgirthGrowth : (2 * (i + 2) : ℕ∞) < G.girth := by + simpa [Nat.add_assoc] using hgirth + have hgrowth := + mul_ncard_halfLayer_le_succ_of_lt_girth + G hV (δ := δ) (i := i) (x := x) (banned := banned) + hxb hmin hgirthGrowth + calc + (δ - 1) ^ (i + 1 + 1) = (δ - 1) * (δ - 1) ^ (i + 1) := by + rw [pow_succ] + ac_rfl + _ ≤ (δ - 1) * (halfLayer G x banned (i + 1)).ncard := + Nat.mul_le_mul_left _ hprev + _ ≤ (halfLayer G x banned (i + 2)).ncard := hgrowth + +end MooreBound + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/RootedLayers.lean b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/RootedLayers.lean new file mode 100644 index 0000000..f61acd8 --- /dev/null +++ b/GraphLib/Theory/Structures/SimpleGraph_only/MooreBound/RootedLayers.lean @@ -0,0 +1,251 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Weixuan Yuan +-/ +import GraphLib.Theory.Structures.SimpleGraph_only.MooreBound.Core + +/-! +# Moore bounds: the rooted layers + +The breadth-first layers around a root vertex: `IsRootedPath` (a path of a given +length from the root), the layer `rootLayer` it cuts out, and the growth estimate +that makes successive layers multiply by `δ - 1`. This is the family behind the +odd Moore bound. + +Part of the `MooreBound` folder; see the umbrella module +`GraphLib.Theory.Structures.SimpleGraph_only.MooreBound`. +-/ + +variable {α : Type*} + +namespace GraphLib + +open scoped GraphLib + +namespace SimpleGraph + +namespace MooreBound + +/-! ## Rooted layers -/ + +/-- A vertex `v` is reached from the root `x` by a realized simple path of +length `i`. -/ +@[grind] def IsRootedPath (G : SimpleGraph α) (x : α) (i : ℕ) (v : α) : Prop := + ∃ p : SimplePath α, G.IsSimplePathIn p ∧ p.head = x ∧ p.tail = v ∧ p.length = i + +namespace IsRootedPath + +/-- A chosen path witnessing a rooted path. -/ +noncomputable def path {G : SimpleGraph α} {x v : α} {i : ℕ} + (h : IsRootedPath G x i v) : SimplePath α := + Classical.choose h + +/-- The chosen witness path for a rooted path is realized in the graph. -/ +lemma path_isSimplePathIn {G : SimpleGraph α} {x v : α} {i : ℕ} + (h : IsRootedPath G x i v) : G.IsSimplePathIn h.path := + (Classical.choose_spec h).1 + +/-- The chosen witness path for a rooted path starts at the root. -/ +lemma path_head {G : SimpleGraph α} {x v : α} {i : ℕ} + (h : IsRootedPath G x i v) : h.path.head = x := + (Classical.choose_spec h).2.1 + +/-- The chosen witness path for a rooted path ends at the endpoint. -/ +lemma path_tail {G : SimpleGraph α} {x v : α} {i : ℕ} + (h : IsRootedPath G x i v) : h.path.tail = v := + (Classical.choose_spec h).2.2.1 + +/-- The chosen witness path for a rooted path has the prescribed length. -/ +lemma path_length {G : SimpleGraph α} {x v : α} {i : ℕ} + (h : IsRootedPath G x i v) : h.path.length = i := + (Classical.choose_spec h).2.2.2 + +/-- The root of a rooted path is a vertex of the ambient graph. -/ +@[grind →] lemma head_mem (G : SimpleGraph α) {x v : α} {i : ℕ} + (h : IsRootedPath G x i v) : x ∈ V(G) := by + obtain ⟨p, hp, hhead, _, _⟩ := h + rw [← hhead] + exact IsSimpleWalkIn.head_mem G hp + +/-- The endpoint of a rooted path is a vertex of the ambient graph. -/ +@[grind →] lemma tail_mem (G : SimpleGraph α) {x v : α} {i : ℕ} + (h : IsRootedPath G x i v) : v ∈ V(G) := by + obtain ⟨p, hp, _, htail, _⟩ := h + rw [← htail] + exact IsSimpleWalkIn.tail_mem G hp + +/-- The singleton path witnesses a rooted path of length zero. -/ +lemma singleton (G : SimpleGraph α) {x : α} (hx : x ∈ V(G)) : + IsRootedPath G x 0 x := + ⟨SimplePath.singleton x, IsSimplePathIn.singleton G hx, rfl, rfl, rfl⟩ + +/-- Extending a rooted path by a fresh adjacent vertex gives a rooted path in +the next layer. -/ +lemma succ_of_adj_not_mem (G : SimpleGraph α) {x v u : α} {i : ℕ} + (h : IsRootedPath G x i v) (hadj : G.Adj v u) + (hnot : u ∉ h.path.vertices) : + IsRootedPath G x (i + 1) u := by + have hdisj : ∀ z : α, z ∈ h.path.vertices → + z ∈ (SimplePath.singleton u).vertices → False := by + grind + have hstep : G.Adj h.path.tail (SimplePath.singleton u).head := by + simpa only [SimplePath.head_singleton, h.path_tail] using hadj + refine ⟨h.path.append (SimplePath.singleton u) hdisj, + IsSimpleWalkIn.append G h.path_isSimplePathIn + (IsSimplePathIn.singleton G hadj.right_mem) hstep, ?_, ?_, ?_⟩ <;> + simp [h.path_head, h.path_length] + +end IsRootedPath + +/-- The vertices reached from `x` by realized simple paths of length `i`. -/ +def rootLayer (G : SimpleGraph α) (x : α) (i : ℕ) : Set α := + {v | IsRootedPath G x i v} + +/-- Membership in a rooted layer is witnessed by a realized rooted path of the +prescribed length. -/ +@[simp, grind =] lemma mem_rootLayer (G : SimpleGraph α) {x v : α} {i : ℕ} : + v ∈ rootLayer G x i ↔ IsRootedPath G x i v := Iff.rfl + +/-- Every rooted layer is contained in the ambient vertex set. -/ +lemma rootLayer_subset_vertexSet (G : SimpleGraph α) (x : α) (i : ℕ) : + rootLayer G x i ⊆ V(G) := by grind + +/-- The zero-th rooted layer is the singleton root. -/ +@[simp] lemma rootLayer_zero (G : SimpleGraph α) {x : α} (hx : x ∈ V(G)) : + rootLayer G x 0 = {x} := by + ext v; constructor + · grind + · grind[IsRootedPath.singleton] + +/-- The zero-th rooted layer has one vertex. -/ +lemma ncard_rootLayer_zero (G : SimpleGraph α) {x : α} (hx : x ∈ V(G)) : + (rootLayer G x 0).ncard = 1 := by grind [Set.ncard_singleton,rootLayer_zero] + +/-! ## Neighbours and the first rooted layer -/ + +/-- The neighbours of `x` inject into the first rooted layer at `x`. -/ +lemma neighborSet_subset_rootLayer_one (G : SimpleGraph α) {x : α} (hx : x ∈ V(G)) : + G.neighborSet x ⊆ rootLayer G x 1 := by + intro u hu + have hxu : G.Adj x u := hu.symm + have hdisj : ∀ z : α, z ∈ (SimplePath.singleton x).vertices → + z ∈ (SimplePath.singleton u).vertices → False := by + grind + refine ⟨(SimplePath.singleton x).append (SimplePath.singleton u) hdisj, + IsSimpleWalkIn.append G (IsSimplePathIn.singleton G hx) + (IsSimplePathIn.singleton G hxu.right_mem) hxu, ?_, ?_, ?_⟩ <;> + simp + +/-- The first rooted layer has at least the degree of the root. -/ +lemma degree_le_ncard_rootLayer_one (G : SimpleGraph α) (hV : V(G).Finite) + {x : α} (hx : x ∈ V(G)) : + G.degree x ≤ (rootLayer G x 1).ncard := by + have hroot_fin : (rootLayer G x 1).Finite := + hV.subset (rootLayer_subset_vertexSet G x 1) + simpa [degree] using + Set.ncard_le_ncard (neighborSet_subset_rootLayer_one G hx) hroot_fin + +/-! ## Odd rooted-layer counting -/ + +/-- Distinct rooted layers are disjoint when the sum of their indices is below +the girth. -/ +lemma disjoint_rootLayer_of_ne_of_lt_girth (G : SimpleGraph α) {x : α} {i j : ℕ} + (hij : i ≠ j) (hgirth : (((i + j : ℕ) : ℕ∞)) < G.girth) : + Disjoint (rootLayer G x i) (rootLayer G x j) := by + classical + rw [Set.disjoint_left] + intro v hvi hvj + obtain ⟨p, hp, hpx, hpv, hplen⟩ := hvi + obtain ⟨q, hq, hqx, hqv, hqlen⟩ := hvj + have hne : p.vertices ≠ q.vertices := fun hvertices => hij (by + rw [← hplen, ← hqlen]; exact congrArg VertexSeq.length hvertices) + obtain ⟨c, hc, hcle⟩ := IsSimpleCycleIn.exists_length_le_add_of_two_paths G hp hq + (hpx.trans hqx.symm) (hpv.trans hqv.symm) hne + have hlong := girth.lt_cycle_length G hc hgirth + omega + +/-- Successive odd Moore layers grow by a factor of at least `δ - 1` after the +first layer. -/ +lemma mul_ncard_rootLayer_le_succ_of_lt_girth (G : SimpleGraph α) + (hV : V(G).Finite) {δ i : ℕ} {x : α} + (hmin : ∀ v : α, v ∈ V(G) → δ ≤ G.degree v) + (hgirth : (2 * (i + 2) : ℕ∞) < G.girth) : + (δ - 1) * (rootLayer G x (i + 1)).ncard ≤ + (rootLayer G x (i + 2)).ncard := by + classical + have hLfin : (rootLayer G x (i + 1)).Finite := + hV.subset (rootLayer_subset_vertexSet G x (i + 1)) + have hNfin : (rootLayer G x (i + 2)).Finite := + hV.subset (rootLayer_subset_vertexSet G x (i + 2)) + -- child set: the fresh neighbours of a chosen witness path for `v` + set F : α → Set α := fun v => + if h : IsRootedPath G x (i + 1) v then freshNeighborSet G h.path else ∅ with hFdef + have hFv : ∀ v, (hv : IsRootedPath G x (i + 1) v) → + F v = freshNeighborSet G hv.path := by + intro v hv; simp only [hFdef, dif_pos hv] + have hgirth' : (2 * (((i + 1 : ℕ) : ℕ∞) + 1)) < G.girth := by + push_cast; convert hgirth using 2 + refine Set.mul_ncard_le_ncard_of_children (F := F) hLfin hNfin ?_ ?_ ?_ + · -- children lie in the next layer + intro v hv u hu + have hvR : IsRootedPath G x (i + 1) v := hv + rw [hFv v hvR] at hu + obtain ⟨hadj, hnot⟩ := hu + exact IsRootedPath.succ_of_adj_not_mem G hvR (hvR.path_tail ▸ hadj) hnot + · -- each child set has at least `δ - 1` elements + intro v hv + have hvR : IsRootedPath G x (i + 1) v := hv + rw [hFv v hvR] + exact pred_le_ncard_freshNeighborSet G hV hmin hvR.path_isSimplePathIn + hvR.path_tail hvR.path_length (by omega) hgirth' + · -- distinct source vertices produce disjoint child sets: a common fresh neighbour + -- would give two distinct length-`(i+2)` paths from `x` to it + intro v v' hv hv' hne + rw [Set.disjoint_left] + intro u hu hu' + have hvR : IsRootedPath G x (i + 1) v := hv + have hv'R : IsRootedPath G x (i + 1) v' := hv' + rw [hFv v hvR] at hu + rw [hFv v' hv'R] at hu' + obtain ⟨hadj, hnot⟩ := hu + obtain ⟨hadj', hnot'⟩ := hu' + exact false_of_two_paths_of_common_fresh_neighbor G + hvR.path_isSimplePathIn hv'R.path_isSimplePathIn hvR.path_head hv'R.path_head + hvR.path_length hv'R.path_length + (by rw [hvR.path_tail, hv'R.path_tail]; exact hne) hadj hadj' hnot hnot' hgirth' + +/-- Nonzero rooted layers in the odd Moore tree have the expected lower bound. -/ +lemma le_ncard_rootLayer_succ (G : SimpleGraph α) (hV : V(G).Finite) + {δ i : ℕ} {x : α} + (hx : x ∈ V(G)) + (hmin : ∀ v : α, v ∈ V(G) → δ ≤ G.degree v) + (hgirth : (2 * (i + 1) : ℕ∞) < G.girth) : + δ * (δ - 1) ^ i ≤ (rootLayer G x (i + 1)).ncard := by + induction i with + | zero => + simpa using (hmin x hx).trans (degree_le_ncard_rootLayer_one G hV hx) + | succ i ih => + have hgirthPrev : (2 * (i + 1) : ℕ∞) < G.girth := by + apply lt_of_le_of_lt _ hgirth + exact_mod_cast (show 2 * (i + 1) ≤ 2 * (i + 1 + 1) by omega) + have hprev : δ * (δ - 1) ^ i ≤ (rootLayer G x (i + 1)).ncard := + ih hgirthPrev + have hgirthGrowth : (2 * (i + 2) : ℕ∞) < G.girth := by + simpa [Nat.add_assoc] using hgirth + have hgrowth := + mul_ncard_rootLayer_le_succ_of_lt_girth + G hV (δ := δ) (i := i) (x := x) hmin hgirthGrowth + calc + δ * (δ - 1) ^ (i + 1) = (δ - 1) * (δ * (δ - 1) ^ i) := by + rw [pow_succ] + ac_rfl + _ ≤ (δ - 1) * (rootLayer G x (i + 1)).ncard := + Nat.mul_le_mul_left _ hprev + _ ≤ (rootLayer G x (i + 2)).ncard := hgrowth + +end MooreBound + +end SimpleGraph + +end GraphLib diff --git a/GraphLib/Theory/Structures/SimplePath.lean b/GraphLib/Theory/Structures/SimplePath.lean index 15bdb20..f783903 100644 --- a/GraphLib/Theory/Structures/SimplePath.lean +++ b/GraphLib/Theory/Structures/SimplePath.lean @@ -17,6 +17,16 @@ where a simple walk is expected. * `SimpleWalk.IsPath` — a simple walk with no repeated vertices. * `SimplePath` — a simple walk bundled with a proof that its vertices are pairwise distinct. +* `SimplePath.singleton`, `SimplePath.cycleErase` — the two constructors: a + one-vertex path, and the path underlying any simple walk. +* `SimplePath.append`, `SimplePath.glue` — join two paths, keeping resp. dropping + the duplicated joining vertex. +* `SimplePath.reverse`, `SimplePath.dropHead`, `SimplePath.dropTail`, + `SimplePath.prefixUntil`, `SimplePath.suffixFrom`, `SimplePath.takeWhile`, + `SimplePath.dropWhile`, `SimplePath.splitAt`, `SimplePath.map`, + `SimplePath.zip`, `SimplePath.loopErase` — the `SimpleWalk` operations, lifted. + +Every operation is the `SimpleWalk` one plus a proof that `nodup` survives. -/ variable {α : Type*} @@ -63,11 +73,37 @@ abbrev length (p : SimplePath α) : ℕ := (vertices p).length /-- A path has no repeated vertices. -/ lemma nodup (p : SimplePath α) : (vertices p).nodup := p.2 +attribute [simp, grind] SimplePath.val SimplePath.vertices SimplePath.length + SimplePath.head SimplePath.tail SimplePath.support SimplePath.edges SimplePath.arcs /-- A simple path is, in particular, a simple walk. -/ instance : Coe (SimplePath α) (SimpleWalk α) := ⟨val⟩ +/-! ## Constructors -/ + +/-- The singleton path at `v`: a single vertex and no edges. -/ +def singleton (v : α) : SimplePath α := + ⟨⟨VertexSeq.singleton v, by simp [VertexSeq.nonstalling]⟩, by simp [SimpleWalk.IsPath, + SimpleWalk.nodup, VertexSeq.nodup]⟩ + +/-- The vertex sequence of a singleton path. This is the bridge that lets `simp` +see through `SimplePath.singleton` down to the `VertexSeq` layer, where the +`singleton` API lives. -/ +@[simp, grind =] lemma vertices_singleton (v : α) : + (singleton v : SimplePath α).vertices = VertexSeq.singleton v := rfl + +@[simp] lemma length_singleton (v : α) : (singleton v : SimplePath α).length = 0 := rfl + +@[simp] lemma head_singleton (v : α) : (singleton v : SimplePath α).head = v := rfl + +@[simp] lemma tail_singleton (v : α) : (singleton v : SimplePath α).tail = v := rfl + +/-- Cycle erasure of any simple walk produces a simple path. -/ +def cycleErase [DecidableEq α] (w : SimpleWalk α) : SimplePath α := + ⟨w.cycleErase, by + exact w.val.nodup_cycleErase⟩ + /-! ## dropHead, dropTail -/ /-- Drop the first vertex of a path. Removing an endpoint preserves `nodup`. -/ @@ -80,7 +116,7 @@ def dropTail (p : SimplePath α) : SimplePath α := ⟨p.val.dropTail, by exact VertexSeq.nodup_dropTail (vertices p) (nodup p)⟩ -/-! ## append -/ +/-! ## append, glue -/ /-- Concatenate two vertex-disjoint paths, keeping both joining vertices. The disjointness hypothesis is what preserves `nodup`; it also rules out a @@ -93,18 +129,29 @@ def append (p q : SimplePath α) simp [tail, vertices, h])), by exact VertexSeq.nodup_append (vertices p) (vertices q) (nodup p) (nodup q) hdisj⟩ +/-- The vertex sequence of an append is the append of the vertex sequences. This +is the bridge that lets `simp` see through `SimplePath.append` down to the +`VertexSeq` layer, where `head_append` / `tail_append` / `length_append` / +`mem_append` live. -/ +@[simp, grind =] lemma vertices_append (p q : SimplePath α) + (hdisj : ∀ v : α, v ∈ vertices p → v ∈ vertices q → False) : + (p.append q hdisj).vertices = (vertices p).append (vertices q) := rfl + /-- Concatenate two paths meeting at a shared vertex, dropping the duplicated -joining vertex. The remaining vertices of `p` must be disjoint from `q`. -/ +joining vertex. When `p` is nontrivial its remaining vertices must be disjoint +from `q`; when `p` is a single vertex the result is just `q`, so no disjointness +is required. Guarding the hypothesis by `length ≠ 0` keeps the singleton-left +case usable, since `dropTail` leaves a singleton unchanged. -/ def glue (p q : SimplePath α) (h : tail p = head q) - (hdisj : ∀ v : α, v ∈ (vertices p).dropTail → v ∈ vertices q → False) : + (hdisj : (vertices p).length ≠ 0 → + ∀ v : α, v ∈ (vertices p).dropTail → v ∈ vertices q → False) : SimplePath α := ⟨p.val.glue q.val h, by unfold SimpleWalk.glue by_cases hp : (vertices p).length = 0 · simpa [vertices, hp] using nodup q - · simpa [vertices, hp] using - VertexSeq.nodup_append (vertices p).dropTail (vertices q) - (VertexSeq.nodup_dropTail (vertices p) (nodup p)) (nodup q) hdisj⟩ + · simpa [vertices, hp] using VertexSeq.nodup_append _ _ + (VertexSeq.nodup_dropTail _ (nodup p)) (nodup q) (hdisj hp)⟩ /-! ## reverse -/ @@ -162,8 +209,8 @@ def splitAt [DecidableEq α] (p : SimplePath α) (v : α) : List (SimplePath α) (fun w (hw : w.nonstalling ∧ w.nodup) => ⟨⟨w, hw.1⟩, hw.2⟩) (by intro w hw - exact ⟨VertexSeq.nonstalling_splitAt (vertices p) p.val.nonstalling v w hw, - VertexSeq.nodup_splitAt (vertices p) (nodup p) v w hw⟩) + exact ⟨VertexSeq.nonstalling_splitAt (vertices p) p.val.nonstalling v hw, + VertexSeq.nodup_splitAt (vertices p) (nodup p) v hw⟩) /-! ## zip -/ @@ -174,7 +221,7 @@ def zip {β : Type*} (p : SimplePath α) (q : SimplePath β) : ⟨p.val.zip q.val, by exact VertexSeq.nodup_zip (vertices p) (vertices q) (nodup p)⟩ -/-! ## loopErase, cycleErase -/ +/-! ## loopErase -/ /-- Remove immediate stalls from a path. Since a path has no repeated vertices, this preserves `nodup`. -/ @@ -182,10 +229,8 @@ def loopErase [DecidableEq α] (p : SimplePath α) : SimplePath α := ⟨p.val.loopErase, by exact VertexSeq.nodup_loopErase (vertices p) (nodup p)⟩ -/-- Cycle erasure of any simple walk produces a simple path. -/ -def cycleErase [DecidableEq α] (w : SimpleWalk α) : SimplePath α := - ⟨w.cycleErase, by - exact w.val.cycleErase_nodup⟩ +attribute [simp, grind] SimplePath.reverse SimplePath.dropTail SimplePath.dropHead + SimplePath.prefixUntil SimplePath.suffixFrom /-! ## edges -/ @@ -195,7 +240,7 @@ def cycleErase [DecidableEq α] (w : SimpleWalk α) : SimplePath α := /-- A path traverses each edge at most once (a path is a trail). -/ lemma edges_nodup (p : SimplePath α) : (edges p).Nodup := - VertexSeq.nodup_edges_of_nodup (vertices p) (nodup p) + VertexSeq.edges_nodup (vertices p) (nodup p) /-- Reversal reverses the edge list. -/ @[simp] lemma edges_reverse (p : SimplePath α) : @@ -228,7 +273,7 @@ lemma edges_suffixFrom_subset [DecidableEq α] (p : SimplePath α) (v : α) /-- A path traverses each directed arc at most once. -/ lemma arcs_nodup (p : SimplePath α) : (arcs p).Nodup := - VertexSeq.nodup_arcs_of_nodup (vertices p) (nodup p) + VertexSeq.arcs_nodup (vertices p) (nodup p) /-- Reversal reverses the arc list and swaps every arc's endpoints. -/ @[simp] lemma arcs_reverse (p : SimplePath α) : diff --git a/GraphLib/Theory/Structures/SimpleWalk.lean b/GraphLib/Theory/Structures/SimpleWalk.lean index f311a76..7f7638c 100644 --- a/GraphLib/Theory/Structures/SimpleWalk.lean +++ b/GraphLib/Theory/Structures/SimpleWalk.lean @@ -10,9 +10,24 @@ import GraphLib.Theory.Structures.VertexSeq # Simple walks A `SimpleWalk α` is a `VertexSeq α` whose consecutive vertices differ — i.e. -a non-stalling sequence. This file also provides the conversions to -`SimpleGraph` and `SimpleDiGraph` (the non-stalling property is exactly what -makes the resulting graph loopless). +a non-stalling sequence. + +## Main definitions + +* `SimpleWalk α` — a non-stalling vertex sequence. +* `SimpleWalk.append`, `SimpleWalk.glue` — join two walks, keeping resp. dropping + the duplicated joining vertex. +* `SimpleWalk.reverse`, `SimpleWalk.dropHead`, `SimpleWalk.dropTail`, + `SimpleWalk.prefixUntil`, `SimpleWalk.suffixFrom`, `SimpleWalk.takeWhile`, + `SimpleWalk.dropWhile`, `SimpleWalk.splitAt`, `SimpleWalk.map`, + `SimpleWalk.zip` — the `VertexSeq` operations, lifted. +* `SimpleWalk.loopErase`, `SimpleWalk.cycleErase` — the erasure operations. +* `SimpleWalk.edges`, `SimpleWalk.arcs` — the traversed edges/arcs. +* `SimpleWalk.toSimpleGraph`, `SimpleWalk.toSimpleDiGraph` — the generated + graphs. Non-stalling is exactly what makes them loopless. + +Every operation is the `VertexSeq` one plus a proof that non-stalling survives, +and every lemma below is a thin wrapper around its `VertexSeq` counterpart. -/ variable {α : Type*} @@ -59,6 +74,9 @@ abbrev nodup (w : SimpleWalk α) : Prop := w.val.nodup /-- The walk is closed: its first and last vertex coincide. -/ abbrev closed (w : SimpleWalk α) : Prop := w.val.closed +attribute [simp, grind] SimpleWalk.val SimpleWalk.length SimpleWalk.head SimpleWalk.tail + SimpleWalk.closed SimpleWalk.nodup SimpleWalk.support SimpleWalk.edges SimpleWalk.arcs + /-- A simple walk is, in particular, a vertex sequence. -/ instance : Coe (SimpleWalk α) (VertexSeq α) := ⟨val⟩ @@ -75,7 +93,7 @@ singleton). Removing an endpoint cannot introduce a stall. -/ def dropTail (w : SimpleWalk α) : SimpleWalk α := ⟨w.val.dropTail, by have := w.nonstalling; grind⟩ -/-! ## append -/ +/-! ## append, glue -/ /-- Concatenate two simple walks, keeping both joining vertices. The hypothesis `p.tail ≠ q.head` ensures the junction does not create a stall. -/ @@ -91,21 +109,14 @@ def glue (p q : SimpleWalk α) (h : p.val.tail = q.val.head) : SimpleWalk α := q else ⟨p.val.dropTail.append q.val, by - have hpns : p.val.dropTail.nonstalling := - VertexSeq.nonstalling_dropTail p.val p.nonstalling + have hpns := VertexSeq.nonstalling_dropTail p.val p.nonstalling have hjoin : p.val.dropTail.tail ≠ q.val.head := by rw [← h] - clear h q obtain ⟨s, hs⟩ := p - induction s with - | singleton v => - simp [VertexSeq.length] at hp - | cons s v ih => - cases s with - | singleton u => - simpa [VertexSeq.dropTail, VertexSeq.tail] using hs.2 - | cons t u => - simpa [VertexSeq.dropTail, VertexSeq.tail] using hs.2 + cases s with + | singleton v => simp [VertexSeq.length] at hp + | cons s v => + cases s <;> simpa [VertexSeq.dropTail, VertexSeq.tail] using hs.2 exact (VertexSeq.nonstalling_append p.val.dropTail q.val).2 ⟨hpns, q.nonstalling, hjoin⟩⟩ @@ -138,20 +149,7 @@ def suffixFrom [DecidableEq α] (w : SimpleWalk α) (v : α) (h : v ∈ w.val) : distinct consecutive vertices stay distinct, so non-stalling is preserved. -/ def map {β : Type*} (f : α → β) (hf : Function.Injective f) (w : SimpleWalk α) : SimpleWalk β := - ⟨w.val.map f, by - have hmap : ∀ s : VertexSeq α, s.nonstalling → (s.map f).nonstalling := by - intro s hs - induction s with - | singleton v => - simp [VertexSeq.map, VertexSeq.nonstalling] - | cons p v ih => - constructor - · exact ih hs.1 - · intro h - have hf_tail : f p.tail = f v := by - simpa [VertexSeq.tail_map] using h - exact hs.2 (hf hf_tail) - exact hmap w.val w.nonstalling⟩ + ⟨w.val.map f, VertexSeq.nonstalling_map f hf w.val w.nonstalling⟩ /-! ## takeWhile, dropWhile -/ @@ -174,7 +172,7 @@ def dropWhile (w : SimpleWalk α) (p : α → Prop) [DecidablePred p] piece is a contiguous sub-walk of `w`, hence non-stalling. -/ def splitAt [DecidableEq α] (w : SimpleWalk α) (v : α) : List (SimpleWalk α) := (w.val.splitAt v).pmap (fun p hp => ⟨p, hp⟩) - (VertexSeq.nonstalling_splitAt w.val w.nonstalling v) + (fun _ hp => VertexSeq.nonstalling_splitAt w.val w.nonstalling v hp) /-! ## zip -/ @@ -190,12 +188,12 @@ def zip {β : Type*} (w : SimpleWalk α) (w' : SimpleWalk β) : /-- Remove immediate stalls. On a simple walk this is the identity, and the result is non-stalling by construction. -/ def loopErase [DecidableEq α] (w : SimpleWalk α) : SimpleWalk α := - ⟨w.val.loopErase, w.val.loopErase_nonstalling⟩ + ⟨w.val.loopErase, w.val.nonstalling_loopErase⟩ /-- Cycle erasure: whenever a vertex repeats, drop the intermediate detour. The result has no repeated vertex, and `nodup` implies `nonstalling`. -/ def cycleErase [DecidableEq α] (w : SimpleWalk α) : SimpleWalk α := - ⟨w.val.cycleErase, VertexSeq.nodup_nonstalling _ w.val.cycleErase_nodup⟩ + ⟨w.val.cycleErase, VertexSeq.nonstalling_of_nodup _ w.val.nodup_cycleErase⟩ /-! ## edges @@ -221,13 +219,11 @@ vertex carries no new edge beyond the one already ending `p`. -/ lemma edges_glue (p q : SimpleWalk α) (h : p.val.tail = q.val.head) : (p.glue q h).edges = p.edges ++ q.edges := by rw [SimpleWalk.glue] - split - · next hp => simp [edges, VertexSeq.edges_eq_nil_of_length_eq_zero p.val hp] - · next hp => - change (p.val.dropTail.append q.val).edges = p.val.edges ++ q.val.edges - rw [VertexSeq.edges_append, - VertexSeq.edges_eq_dropTail_concat_of_length_ne_zero p.val hp, ← h, - List.concat_eq_append] + split <;> rename_i hp + · simp [edges, VertexSeq.edges_nil_of_length_zero p.val hp] + · change (p.val.dropTail.append q.val).edges = p.val.edges ++ q.val.edges + rw [VertexSeq.edges_append, VertexSeq.edges_eq_dropTail_concat p.val hp, + ← h, List.concat_eq_append] /-- On a simple walk `loopErase` is the identity, so it leaves the edges unchanged. -/ @@ -260,9 +256,9 @@ lemma edges_suffixFrom_subset [DecidableEq α] (w : SimpleWalk α) (v : α) VertexSeq.edges_suffixFrom_subset w.val v h /-- Any endpoint of a traversed edge is a vertex of the walk. -/ -lemma mem_of_mem_edges {e : Sym2 α} {v : α} (w : SimpleWalk α) +lemma mem_of_edge_mem {e : Sym2 α} {v : α} (w : SimpleWalk α) (he : e ∈ w.edges) (hv : v ∈ e) : v ∈ w.support := - VertexSeq.mem_of_mem_edges w.val he hv + VertexSeq.mem_of_edge_mem w.val he hv /-! ## arcs @@ -288,13 +284,11 @@ vertex carries no new arc beyond the one already ending `p`. -/ lemma arcs_glue (p q : SimpleWalk α) (h : p.val.tail = q.val.head) : (p.glue q h).arcs = p.arcs ++ q.arcs := by rw [SimpleWalk.glue] - split - · next hp => simp [arcs, VertexSeq.arcs_eq_nil_of_length_eq_zero p.val hp] - · next hp => - change (p.val.dropTail.append q.val).arcs = p.val.arcs ++ q.val.arcs - rw [VertexSeq.arcs_append, - VertexSeq.arcs_eq_dropTail_concat_of_length_ne_zero p.val hp, ← h, - List.concat_eq_append] + split <;> rename_i hp + · simp [arcs, VertexSeq.arcs_nil_of_length_zero p.val hp] + · change (p.val.dropTail.append q.val).arcs = p.val.arcs ++ q.val.arcs + rw [VertexSeq.arcs_append, VertexSeq.arcs_eq_dropTail_concat p.val hp, + ← h, List.concat_eq_append] /-- On a simple walk `loopErase` is the identity, so it leaves the arcs unchanged. -/ @@ -327,14 +321,23 @@ lemma arcs_suffixFrom_subset [DecidableEq α] (w : SimpleWalk α) (v : α) VertexSeq.arcs_suffixFrom_subset w.val v h /-- The source of a traversed arc is a vertex of the walk. -/ -lemma fst_mem_of_mem_arcs {a : α × α} (w : SimpleWalk α) +lemma fst_mem_of_arc_mem {a : α × α} (w : SimpleWalk α) (ha : a ∈ w.arcs) : a.1 ∈ w.support := - VertexSeq.fst_mem_of_mem_arcs w.val ha + VertexSeq.fst_mem_of_arc_mem w.val ha /-- The target of a traversed arc is a vertex of the walk. -/ -lemma snd_mem_of_mem_arcs {a : α × α} (w : SimpleWalk α) +lemma snd_mem_of_arc_mem {a : α × α} (w : SimpleWalk α) (ha : a ∈ w.arcs) : a.2 ∈ w.support := - VertexSeq.snd_mem_of_mem_arcs w.val ha + VertexSeq.snd_mem_of_arc_mem w.val ha + +-- These operations are thin `Subtype` wrappers around their `VertexSeq` +-- counterparts, so unfolding them is exactly how `simp`/`grind` gets down to the +-- `VertexSeq` API where the real lemmas live. Tagging the definitions is +-- deliberate; do not replace it with per-lemma `simp` lemmas. +attribute [simp, grind] SimpleWalk.reverse SimpleWalk.dropTail SimpleWalk.dropHead + SimpleWalk.prefixUntil SimpleWalk.suffixFrom SimpleWalk.loopErase + +/-! ## Generated graphs -/ /-- View a simple walk as a `SimpleGraph`. The non-stalling property provides the looplessness axiom. -/ @@ -343,33 +346,11 @@ def toSimpleGraph (w : SimpleWalk α) : SimpleGraph α where edgeSet := { e | e ∈ w.val.edges } incidence' := by intro e he v hv - obtain ⟨q, hq⟩ := w - induction q with - | singleton _ => simp [VertexSeq.edges] at he - | cons w' u ih => - simp [VertexSeq.edges] at he - rcases he with he_old | he_new - · have hns : w'.nonstalling := hq.1 - have hv' : v ∈ w'.toList := ih hns he_old - simp [VertexSeq.toList] - exact Or.inl hv' - · subst he_new - simp [Sym2.mem_iff] at hv - rcases hv with rfl | rfl - · simp [VertexSeq.toList] - · simp [VertexSeq.toList] + exact VertexSeq.mem_of_edge_mem w.val he hv loopless' := by intro e he obtain ⟨q, hq⟩ := w - induction q with - | singleton _ => simp [VertexSeq.edges] at he - | cons w' u ih => - simp [VertexSeq.edges] at he - rcases he with he_old | he_new - · exact ih hq.1 he_old - · subst he_new - simp [Sym2.mk_isDiag_iff] - exact hq.2 + induction q <;> grind [VertexSeq.edges, VertexSeq.mem_edges_cons, Sym2.mk_isDiag_iff] /-- View a simple walk as a `SimpleDiGraph`. The non-stalling property provides the looplessness axiom. -/ @@ -378,30 +359,12 @@ def toSimpleDiGraph (w : SimpleWalk α) : SimpleDiGraph α where edgeSet := { a | a ∈ w.val.arcs } incidence' := by intro a ha - obtain ⟨q, hq⟩ := w - induction q with - | singleton _ => simp [VertexSeq.arcs] at ha - | cons w' u ih => - simp [VertexSeq.arcs] at ha - rcases ha with ha_old | ha_new - · obtain ⟨h1, h2⟩ := ih hq.1 ha_old - refine ⟨?_, ?_⟩ - · simp [VertexSeq.toList]; exact Or.inl h1 - · simp [VertexSeq.toList]; exact Or.inl h2 - · subst ha_new - refine ⟨?_, ?_⟩ - · simp [VertexSeq.toList] - · simp [VertexSeq.toList] + exact ⟨VertexSeq.fst_mem_of_arc_mem w.val ha, + VertexSeq.snd_mem_of_arc_mem w.val ha⟩ loopless' := by intro a ha obtain ⟨q, hq⟩ := w - induction q with - | singleton _ => simp [VertexSeq.arcs] at ha - | cons w' u ih => - simp [VertexSeq.arcs] at ha - rcases ha with ha_old | ha_new - · exact ih hq.1 ha_old - · subst ha_new; exact hq.2 + induction q <;> grind [VertexSeq.arcs, VertexSeq.mem_arcs_cons] /-- The edges of `w.toSimpleGraph` are exactly the edges traversed by `w`. -/ @[simp] lemma mem_edgeSet_toSimpleGraph (w : SimpleWalk α) {e : Sym2 α} : diff --git a/GraphLib/Theory/Structures/VertexSeq.lean b/GraphLib/Theory/Structures/VertexSeq.lean index db18db0..8135ca9 100644 --- a/GraphLib/Theory/Structures/VertexSeq.lean +++ b/GraphLib/Theory/Structures/VertexSeq.lean @@ -11,6 +11,7 @@ import GraphLib.Theory.Structures.VertexSeq.Subseq import GraphLib.Theory.Structures.VertexSeq.Erase import GraphLib.Theory.Structures.VertexSeq.Edges import GraphLib.Theory.Structures.VertexSeq.Index +import GraphLib.Theory.Structures.VertexSeq.CommonPrefix /-! # Vertex sequences @@ -33,26 +34,31 @@ re-exports (imports) the `VertexSeq` development, which is split across * `Erase` — `loopErase`, `cycleErase`. * `Edges` — `edges`, `arcs` (the traversed edges/arcs, as lists). * `Index` — `GetElem` and `insert`. +* `CommonPrefix` — `List.commonPrefix`, the index-free tool used to locate the + point where two vertex sequences diverge. It mentions no `VertexSeq` and + depends on none of the above; it sits here only for want of a `List` utility + module (see `AGENTS/Remark.md`). Downstream files should keep importing this umbrella; the split is internal. ## Module dependency graph -Direct imports between the submodules (an arrow `A → B` means "`A` imports -`B`"). The acyclic spine is `Basic ← Predicates ← Append ← Subseq ← Erase ← -Edges`, with `MapZip` branching off `Predicates` and `Index` off `Basic`: +The acyclic spine is `Basic ← Predicates ← Append ← Subseq ← Erase ← Edges`, with +`Index` branching off `Basic` and `MapZip` off `Predicates` (its `nodup`/ +`nonstalling` preservation lemmas need the predicates). `CommonPrefix` is +free-standing: it imports only Mathlib. ```text - Basic - ╱ ╲ - Predicates Index - ╱ ╲ - Append MapZip - │ - Subseq - │ - Erase - │ - Edges + Basic CommonPrefix + ╱ ╲ (free-standing) + Predicates Index + ╱ ╲ +Append MapZip + │ +Subseq + │ +Erase + │ +Edges ``` -This umbrella imports all eight submodules. +This umbrella imports all nine submodules. -/ diff --git a/GraphLib/Theory/Structures/VertexSeq/Append.lean b/GraphLib/Theory/Structures/VertexSeq/Append.lean index 63a6bdb..d0e213f 100644 --- a/GraphLib/Theory/Structures/VertexSeq/Append.lean +++ b/GraphLib/Theory/Structures/VertexSeq/Append.lean @@ -22,7 +22,7 @@ variable {α : Type*} namespace VertexSeq -/-! ## append, reverse, and their laws -/ +/-! ## append -/ /-- Concatenate two sequences. The joining vertices `p.tail` and `q.head` are *both* preserved. If they are equal the duplicate is intentional (the caller @@ -35,63 +35,56 @@ instance : Append (VertexSeq α) := ⟨VertexSeq.append⟩ @[simp, grind =] lemma toList_append (p q : VertexSeq α) : (p.append q).toList = p.toList ++ q.toList := by - induction q generalizing p with - | singleton v => - simp [append, toList, List.concat_eq_append] - | cons q v ih => - simp [append, toList, ih, List.concat_eq_append, List.append_assoc] + fun_induction append p q <;> + simp_all [toList, List.concat_eq_append, List.append_assoc] @[simp] lemma mem_append (v : α) (w1 w2 : VertexSeq α) : v ∈ w1 ++ w2 ↔ v ∈ w1 ∨ v ∈ w2 := by change v ∈ (w1.append w2).toList ↔ v ∈ w1.toList ∨ v ∈ w2.toList rw [toList_append, List.mem_append] -/-- Reverse a sequence: the head becomes the tail and vice versa. -/ -@[grind] def reverse : VertexSeq α → VertexSeq α - | .singleton v => .singleton v - | .cons w v => append (.singleton v) (reverse w) - -@[simp] lemma mem_reverse (v : α) (w : VertexSeq α) : - v ∈ w ↔ v ∈ w.reverse := by - induction w with - | singleton _ => simp [reverse] - | cons w x ih => - have ih' : v ∈ w.toList ↔ v ∈ w.reverse.toList := by - simpa [mem_def] using ih - simp [reverse, toList_append, toList, List.concat_eq_append, ih', - or_comm] - /-- Length of an append is the sum of lengths plus one (for the duplicated joining vertex contributing an extra edge). -/ @[simp, grind =] lemma length_append (p q : VertexSeq α) : (p.append q).length = p.length + q.length + 1 := by fun_induction append p q <;> grind -/-- `tail` of an append is the tail of the right operand. -/ -@[simp, grind =] lemma tail_append (p q : VertexSeq α) : - (p.append q).tail = q.tail := by - fun_induction append <;> simp_all [tail] - /-- `head` of an append is the head of the left operand. -/ @[simp, grind =] lemma head_append (p q : VertexSeq α) : (p.append q).head = p.head := by fun_induction append <;> simp_all [head] -/-- Appending a singleton on the right yields `x` as the new tail. -/ -@[simp, grind =] lemma tail_append_singleton (p : VertexSeq α) (x : α) : - (p.append (.singleton x)).tail = x := by - grind +/-- `tail` of an append is the tail of the right operand. -/ +@[simp, grind =] lemma tail_append (p q : VertexSeq α) : + (p.append q).tail = q.tail := by + fun_induction append <;> simp_all [tail] /-- Appending on the left of a singleton makes `x` the new head. -/ @[simp, grind =] lemma head_singleton_append (p : VertexSeq α) (x : α) : ((VertexSeq.singleton x).append p).head = x := by grind +/-- Appending a singleton on the right yields `x` as the new tail. -/ +@[simp, grind =] lemma tail_append_singleton (p : VertexSeq α) (x : α) : + (p.append (.singleton x)).tail = x := by + grind + /-- `append` is associative. -/ @[simp, grind =] lemma append_assoc (p q r : VertexSeq α) : (p.append q).append r = p.append (q.append r) := by fun_induction append q r <;> simp_all [append] +/-! ## reverse -/ + +/-- Reverse a sequence: the head becomes the tail and vice versa. -/ +@[grind] def reverse : VertexSeq α → VertexSeq α + | .singleton v => .singleton v + | .cons w v => append (.singleton v) (reverse w) + +@[simp] lemma mem_reverse {v : α} {w : VertexSeq α} : + v ∈ w.reverse ↔ v ∈ w := by + induction w <;> grind [reverse] + /-- Reversing a singleton leaves it unchanged. -/ @[grind =] lemma reverse_singleton (v : α) : (VertexSeq.singleton v).reverse = .singleton v := rfl @@ -116,22 +109,28 @@ joining vertex contributing an extra edge). -/ p.reverse.tail = p.head := by fun_induction reverse p <;> grind +/-- Reversal preserves length. -/ +@[simp, grind =] lemma length_reverse (w : VertexSeq α) : + w.reverse.length = w.length := by + fun_induction reverse w <;> grind + /-! ## Nodup and non-stalling preservation -/ /-- Appending disjoint `nodup` sequences preserves `nodup`. -/ @[grind] lemma nodup_append (p q : VertexSeq α) (hp : p.nodup) (hq : q.nodup) (hdisj : ∀ v : α, v ∈ p → v ∈ q → False) : (p.append q).nodup := by induction q with - | singleton v => - simp [append, nodup] - exact ⟨hp, fun hv => hdisj v hv (by grind)⟩ + | singleton v => grind [append, nodup] | cons q v ih => - simp [append, nodup] at hq ⊢ - refine ⟨ih hq.1 (fun x hx hq' => hdisj x hx (by grind)), ?_⟩ - constructor - · intro hpv - exact hdisj v (by simpa [mem_def] using hpv) (by grind) - · exact hq.2 + have hkey := ih hq.1 (fun x hx hq' => hdisj x hx (by grind)) + have hv : v ∉ p := fun hvp => hdisj v hvp (by grind) + grind [append, nodup, mem_append] + +/-- `nodup` of an append is symmetric in its operands. -/ +lemma nodup_append_comm (p q : VertexSeq α) (h : (p.append q).nodup) : + (q.append p).nodup := by + simp only [nodup_iff_toList_nodup, toList_append, List.nodup_append] at h ⊢ + grind /-- Reversal preserves `nodup`. -/ @[grind] lemma nodup_reverse (w : VertexSeq α) (h : w.nodup) : @@ -150,16 +149,13 @@ vertices differ. -/ w.reverse.nonstalling ↔ w.nonstalling := by fun_induction reverse w <;> grind -/-! ## Interaction of dropTail with append and reverse -/ +/-! ## Interaction of dropTail with append and reverse -/-- Reversal preserves length. -/ -@[simp, grind =] lemma length_reverse (w : VertexSeq α) : - w.reverse.length = w.length := by - fun_induction reverse w <;> grind +Together with the one `nodup` lemma that has to wait for `dropTail_reverse`. -/ /-- For a non-trivial right operand, dropping the tail of an append drops the tail of that operand. -/ -@[simp, grind =] lemma dropTail_append_of_length_ne_zero (p q : VertexSeq α) +@[simp, grind =] lemma dropTail_append (p q : VertexSeq α) (hq : q.length ≠ 0) : (p.append q).dropTail = p.append q.dropTail := by cases q with @@ -171,34 +167,15 @@ tail of that operand. -/ /-- Dropping the tail of a reverse is reversing the `dropHead`. -/ @[simp, grind =] lemma dropTail_reverse (w : VertexSeq α) : w.reverse.dropTail = w.dropHead.reverse := by - induction w with - | singleton v => - rfl - | cons w v ih => - cases w with - | singleton u => - rfl - | cons t u => - rw [reverse] - rw [dropTail_append_of_length_ne_zero] - · exact congrArg (fun x => (singleton v).append x) (by simpa [reverse] using ih) - · simp [length_reverse, length] - -/-- The reversed interior of a cycle (drop the repeated tail) is `nodup`. -/ -@[grind] lemma nodup_reverse_dropTail_of_cycle (w : VertexSeq α) + induction w <;> + grind [reverse, dropHead, dropTail_append, length_reverse, append] + +/-- The reversed interior of a closed sequence (drop the repeated tail) is +`nodup`. -/ +@[grind] lemma nodup_reverse_dropTail_of_closed (w : VertexSeq α) (hclosed : w.closed) (hnodup : w.dropTail.nodup) : w.reverse.dropTail.nodup := by rw [dropTail_reverse] exact nodup_reverse w.dropHead (nodup_dropHead_of_closed_dropTail w hclosed hnodup) -/-- `nodup` of an append is symmetric in its operands. -/ -lemma nodup_append_comm (p q : VertexSeq α) (h : (p.append q).nodup) : - (q.append p).nodup := by - rw [nodup_iff_toList_nodup] - rw [toList_append] - have hlist : (p.toList ++ q.toList).Nodup := by - simpa [toList_append] using (nodup_iff_toList_nodup (p.append q)).1 h - rw [List.nodup_append] at hlist ⊢ - aesop - end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Basic.lean b/GraphLib/Theory/Structures/VertexSeq/Basic.lean index 4bc927b..5e0d04f 100644 --- a/GraphLib/Theory/Structures/VertexSeq/Basic.lean +++ b/GraphLib/Theory/Structures/VertexSeq/Basic.lean @@ -72,6 +72,9 @@ plus the length of the prefix. -/ | .singleton v => [v] | .cons w v => w.toList.concat v +@[simp, grind =] lemma length_singleton (u : α) : + (VertexSeq.singleton u).length = 0 := rfl + @[simp, grind =] lemma head_singleton (u : α) : (VertexSeq.singleton u).head = u := rfl @@ -84,21 +87,6 @@ plus the length of the prefix. -/ @[simp, grind =] lemma tail_cons (w : VertexSeq α) (u : α) : (w.cons u).tail = u := rfl -/-- The `head` belongs to the underlying list of vertices. -/ -@[simp, grind] lemma head_mem (w : VertexSeq α) : w.head ∈ w.toList := by - induction w with - | singleton _ => - simp [head, toList] - | cons w _ ih => - simp [head, toList] - exact Or.inl ih - -/-- The `tail` belongs to the underlying list of vertices. -/ -@[simp, grind] lemma tail_mem (w : VertexSeq α) : w.tail ∈ w.toList := by - cases w with - | singleton _ => simp [tail, toList] - | cons _ _ => simp [tail, toList] - /-- The underlying list has `length + 1` vertices. -/ lemma length_toList (w : VertexSeq α) : w.toList.length = w.length + 1 := by induction w <;> grind @@ -107,35 +95,8 @@ lemma length_toList (w : VertexSeq α) : w.toList.length = w.length + 1 := by @[grind] theorem toList_injective : Function.Injective (@toList α) := by intro x y hxy induction x generalizing y with - | singleton v => - cases y with - | singleton w => simpa [toList] using hxy - | cons w u => - exfalso - simp only [toList] at hxy - have hlen := congrArg List.length hxy - simp only [List.length_singleton, List.length_concat] at hlen - have := length_toList w - omega - | cons w v ih => - cases y with - | singleton w' => - exfalso - simp only [toList] at hxy - have hlen := congrArg List.length hxy - simp only [List.length_singleton, List.length_concat] at hlen - have := length_toList w - omega - | cons w' v' => - simp only [toList, List.concat_eq_append] at hxy - have hlen : w.toList.length = w'.toList.length := by - have hl := congrArg List.length hxy - simp only [List.length_append, List.length_singleton] at hl - omega - obtain ⟨hw, hv⟩ := List.append_inj hxy hlen - obtain rfl := ih hw - simp at hv - exact congrArg _ hv + | singleton v => cases y <;> grind [toList, length_toList] + | cons w v ih => cases y <;> grind [toList, length_toList, List.append_inj] /-! ## Membership -/ @@ -144,6 +105,10 @@ instance : Membership α (VertexSeq α) := ⟨fun w v ↦ v ∈ w.toList⟩ @[simp, grind] theorem mem_def {v : α} (w : VertexSeq α) : v ∈ w ↔ v ∈ w.toList := Iff.rfl +/-- A vertex belongs to a singleton sequence exactly when it is that singleton vertex. -/ +@[simp, grind] lemma mem_singleton {v u : α} : v ∈ singleton u ↔ v = u := by + simp [mem_def, toList] + @[simp] lemma mem_cons (v u : α) (w : VertexSeq α) : v ∈ w :+ u ↔ v ∈ w ∨ v = u := by grind @@ -156,6 +121,28 @@ instance : HasSubset (VertexSeq α) := ⟨fun w1 w2 ↦ ∀ v ∈ w1, v ∈ w2 @[simp, grind] theorem subset_def {w1 w2 : VertexSeq α} : w1 ⊆ w2 ↔ ∀ v ∈ w1, v ∈ w2 := Iff.rfl +/-- The `head` belongs to the underlying list of vertices. -/ +@[simp, grind] lemma head_mem (w : VertexSeq α) : w.head ∈ w.toList := by + induction w <;> grind + +/-- The `tail` belongs to the underlying list of vertices. -/ +@[simp, grind] lemma tail_mem (w : VertexSeq α) : w.tail ∈ w.toList := by + cases w with + | singleton _ => simp [tail, toList] + | cons _ _ => simp [tail, toList] + +/-! ## Length-zero sequences -/ + +/-- A length-zero sequence is a single vertex, so its head and tail agree. -/ +@[simp, grind =] lemma head_eq_tail_of_length_zero (w : VertexSeq α) + (h : w.length = 0) : w.head = w.tail := by + cases w <;> grind + +/-- A length-zero sequence is the singleton at its head. -/ +lemma eq_singleton_of_length_zero (w : VertexSeq α) + (h : w.length = 0) : w = singleton w.head := by + cases w <;> grind + /-! ## dropHead, dropTail -/ /-- Drop the first vertex of the sequence (returns the sequence unchanged when @@ -181,27 +168,48 @@ it is a singleton). -/ w.dropHead.tail = w.tail := by fun_induction dropHead w <;> grind +/-- Appending a vertex does not change the head remaining after `dropHead`, +provided the original sequence is nontrivial. -/ +lemma head_dropHead_cons (w : VertexSeq α) (x : α) + (h : w.length ≠ 0) : (w.cons x).dropHead.head = w.dropHead.head := by + cases w with + | singleton _ => exact (h rfl).elim + | cons _ _ => rfl + /-- Dropping the tail of a `cons` recovers the prefix length. -/ @[simp, grind =] lemma length_dropTail_cons (w : VertexSeq α) (v : α) : (w :+ v).dropTail.length = w.length := rfl -/-- A length-zero sequence is a single vertex, so its head and tail agree. -/ -@[simp, grind =] lemma head_eq_tail_of_length_zero (w : VertexSeq α) - (h : w.length = 0) : w.head = w.tail := by - cases w with - | singleton v => - rfl - | cons w v => - simp [length] at h - /-- For a non-trivial sequence, dropping the tail drops exactly one edge. -/ -@[simp, grind =] lemma dropTail_length_succ (w : VertexSeq α) (h : w.length ≠ 0) : +@[simp, grind =] lemma length_dropTail_succ (w : VertexSeq α) (h : w.length ≠ 0) : w.dropTail.length + 1 = w.length := by - cases w with - | singleton v => - exact (h rfl).elim - | cons w v => - simp [length, dropTail] - omega + cases w <;> grind + +/-- A `dropHead` result is contained in the original sequence. -/ +@[grind] lemma dropHead_subset (w : VertexSeq α) : w.dropHead ⊆ w := by + intro v hv + fun_induction dropHead w <;> grind + +/-- Dropping the tail keeps every remaining vertex in the sequence. -/ +@[simp] lemma dropTail_subset (w : VertexSeq α) : w.dropTail ⊆ w := by + intro v hv + cases w <;> grind + +/-- After dropping the head of a length-one sequence, the remaining head is +the original tail. -/ +lemma head_dropHead_eq_tail_of_length_one (w : VertexSeq α) + (h : w.length = 1) : w.dropHead.head = w.tail := by + cases w <;> grind [eq_singleton_of_length_zero] + +/-! ## Head and last of the underlying list -/ + +/-- The head of the underlying list is the head vertex. -/ +@[simp, grind =] lemma head?_toList (w : VertexSeq α) : w.toList.head? = some w.head := by + induction w <;> grind [toList, head, List.head?_append] + +/-- The last entry of the underlying list is the tail vertex. -/ +@[simp, grind =] lemma getLast_toList (w : VertexSeq α) (h : w.toList ≠ []) : + w.toList.getLast h = w.tail := by + induction w <;> grind [toList, tail] end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/CommonPrefix.lean b/GraphLib/Theory/Structures/VertexSeq/CommonPrefix.lean new file mode 100644 index 0000000..4e8f25a --- /dev/null +++ b/GraphLib/Theory/Structures/VertexSeq/CommonPrefix.lean @@ -0,0 +1,65 @@ +/- +Copyright (c) 2026 Basil Rohner. All rights reserved. +Released under Apache 2.0 license as described in the file LICENSE. +Authors: Basil Rohner, Sorrachai Yingchareonthawornchai, Weixuan Yuan +-/ +import Mathlib.Data.List.Basic + +/-! +# Longest common prefix of two lists + +A structurally recursive `List.commonPrefix`, together with the single splitting +lemma that its clients need. Everything here is index-free: no `getElem`, no +`findIdx`, no bound arithmetic. + +This is what lets `SimpleCycle` locate the point where two paths diverge without +dragging dependent `getElem` bounds through every step. + +The file lives under `VertexSeq/` only because the library has no general-purpose +`List` utility module yet; it mentions no `VertexSeq`. See `AGENTS/Remark.md`. + +## Main definitions + +* `List.commonPrefix` — the longest common prefix of two lists. + +## Main results + +* `List.commonPrefix_split` — the common prefix cuts both lists, and the first + entries after the cut differ. +-/ + +variable {α : Type*} + +namespace List + +/-- The longest common prefix of two lists. -/ +@[grind] def commonPrefix [DecidableEq α] : List α → List α → List α + | x :: xs, y :: ys => if x = y then x :: commonPrefix xs ys else [] + | _, _ => [] + +/-- The common prefix cuts both lists, and whatever follows the cut starts with +two different entries (when both remainders are non-empty). + +This single lemma carries everything a caller needs: the two splittings and the +divergence of the successors. -/ +lemma commonPrefix_split [DecidableEq α] (l₁ l₂ : List α) : + ∃ r₁ r₂, l₁ = commonPrefix l₁ l₂ ++ r₁ ∧ l₂ = commonPrefix l₁ l₂ ++ r₂ ∧ + ∀ x ∈ r₁.head?, ∀ y ∈ r₂.head?, x ≠ y := by + induction l₁ generalizing l₂ with + | nil => exact ⟨[], l₂, by grind, by grind, by grind⟩ + | cons x xs ih => + cases l₂ with + | nil => exact ⟨x :: xs, [], by grind, by grind, by grind⟩ + | cons y ys => + by_cases hxy : x = y + · obtain ⟨r₁, r₂, h₁, h₂, hne⟩ := ih ys + exact ⟨r₁, r₂, by grind, by grind, by grind⟩ + · exact ⟨x :: xs, y :: ys, by grind, by grind, by grind⟩ + +/-- Two lists starting at the same entry have a non-empty common prefix. -/ +lemma commonPrefix_ne_nil [DecidableEq α] {l₁ l₂ : List α} {x : α} + (h₁ : l₁.head? = some x) (h₂ : l₂.head? = some x) : + commonPrefix l₁ l₂ ≠ [] := by + cases l₁ <;> cases l₂ <;> grind + +end List diff --git a/GraphLib/Theory/Structures/VertexSeq/Edges.lean b/GraphLib/Theory/Structures/VertexSeq/Edges.lean index dcf6912..dd53776 100644 --- a/GraphLib/Theory/Structures/VertexSeq/Edges.lean +++ b/GraphLib/Theory/Structures/VertexSeq/Edges.lean @@ -7,7 +7,7 @@ import Mathlib.Data.Sym.Sym2 import GraphLib.Theory.Structures.VertexSeq.Erase /-! -# Vertex sequences: traversed edges +# Vertex sequences: traversed edges and arcs The edges and arcs traversed by a vertex sequence: the consecutive vertex pairs of each `cons` step, viewed either as unordered `Sym2 α` edges or as ordered @@ -63,7 +63,7 @@ pairs `(w.tail, v)` for each `cons` step). -/ a ∈ (w :+ v).arcs ↔ a ∈ w.arcs ∨ a = (w.tail, v) := by simp [arcs, List.concat_eq_append] -/-! ## Length and last edge -/ +/-! ## Lengths and final steps -/ /-- The number of traversed edges equals `length` (one less than the number of vertices). -/ @@ -74,7 +74,7 @@ vertices). -/ | cons w v ih => simp only [edges, length, List.length_concat, ih]; omega /-- A length-zero sequence (a single vertex) traverses no edges. -/ -@[simp, grind =] lemma edges_eq_nil_of_length_eq_zero (w : VertexSeq α) +@[simp, grind =] lemma edges_nil_of_length_zero (w : VertexSeq α) (h : w.length = 0) : w.edges = [] := by cases w with | singleton v => rfl @@ -82,7 +82,7 @@ vertices). -/ /-- For a non-trivial sequence, the traversed edges are those of the dropped-tail sequence plus the final edge. -/ -@[grind →] lemma edges_eq_dropTail_concat_of_length_ne_zero (w : VertexSeq α) +@[grind →] lemma edges_eq_dropTail_concat (w : VertexSeq α) (h : w.length ≠ 0) : w.edges = w.dropTail.edges.concat s(w.dropTail.tail, w.tail) := by cases w with @@ -98,7 +98,7 @@ vertices). -/ | cons w v ih => simp only [arcs, length, List.length_concat, ih]; omega /-- A length-zero sequence (a single vertex) traverses no arcs. -/ -@[simp, grind =] lemma arcs_eq_nil_of_length_eq_zero (w : VertexSeq α) +@[simp, grind =] lemma arcs_nil_of_length_zero (w : VertexSeq α) (h : w.length = 0) : w.arcs = [] := by cases w with | singleton v => rfl @@ -106,7 +106,7 @@ vertices). -/ /-- For a non-trivial sequence, the traversed arcs are those of the dropped-tail sequence plus the final arc. -/ -@[grind →] lemma arcs_eq_dropTail_concat_of_length_ne_zero (w : VertexSeq α) +@[grind →] lemma arcs_eq_dropTail_concat (w : VertexSeq α) (h : w.length ≠ 0) : w.arcs = w.dropTail.arcs.concat (w.dropTail.tail, w.tail) := by cases w with @@ -116,21 +116,21 @@ dropped-tail sequence plus the final arc. -/ /-! ## Endpoints -/ /-- Any endpoint of a traversed edge is a vertex of the sequence. -/ -@[grind →] lemma mem_of_mem_edges {e : Sym2 α} {x : α} (w : VertexSeq α) +@[grind →] lemma mem_of_edge_mem {e : Sym2 α} {x : α} (w : VertexSeq α) (he : e ∈ w.edges) (hx : x ∈ e) : x ∈ w := by induction w with | singleton v => simp [edges] at he | cons w v ih => grind [mem_edges_cons, mem_cons, tail_mem] /-- The source of a traversed arc is a vertex of the sequence. -/ -@[grind →] lemma fst_mem_of_mem_arcs {a : α × α} (w : VertexSeq α) +@[grind →] lemma fst_mem_of_arc_mem {a : α × α} (w : VertexSeq α) (ha : a ∈ w.arcs) : a.1 ∈ w := by induction w with | singleton v => simp [arcs] at ha | cons w v ih => grind [mem_arcs_cons, mem_cons, tail_mem] /-- The target of a traversed arc is a vertex of the sequence. -/ -@[grind →] lemma snd_mem_of_mem_arcs {a : α × α} (w : VertexSeq α) +@[grind →] lemma snd_mem_of_arc_mem {a : α × α} (w : VertexSeq α) (ha : a ∈ w.arcs) : a.2 ∈ w := by induction w with | singleton v => simp [arcs] at ha @@ -140,68 +140,43 @@ dropped-tail sequence plus the final arc. -/ /-- A duplicate-free sequence traverses each edge at most once (a path is a trail). -/ -@[grind] lemma nodup_edges_of_nodup (w : VertexSeq α) (h : w.nodup) : +@[grind] lemma edges_nodup (w : VertexSeq α) (h : w.nodup) : w.edges.Nodup := by - induction w with - | singleton v => simp [edges] - | cons w v ih => - obtain ⟨hw, hv⟩ := h - rw [edges_cons, List.concat_eq_append, List.nodup_append] - refine ⟨ih hw, by simp, ?_⟩ - intro a ha b hb hab - simp only [List.mem_singleton] at hb - subst hb; subst hab - exact hv (mem_of_mem_edges w ha (by simp)) + induction w <;> + grind [edges, edges_cons, List.nodup_append, mem_of_edge_mem] /-- The only way the edge between a duplicate-free sequence's two endpoints can be traversed is if the sequence is a single edge: otherwise the endpoints are not consecutive. Used to show a cycle's closing edge is fresh. -/ -@[grind →] lemma length_le_one_of_mem_edges_head_tail (w : VertexSeq α) +@[grind →] lemma length_le_one_of_closing_edge_mem (w : VertexSeq α) (h : w.nodup) (he : s(w.head, w.tail) ∈ w.edges) : w.length ≤ 1 := by - cases w with - | singleton v => simp [edges] at he - | cons w v => - obtain ⟨hw, hv⟩ := h - simp only [head_cons, tail_cons, mem_edges_cons] at he - rcases he with he | he - · exact absurd (mem_of_mem_edges w he (by simp)) hv - · have hht : w.head = w.tail := by - rw [Sym2.eq_iff] at he - rcases he with ⟨h1, _⟩ | ⟨h1, _⟩ - · exact h1 - · exact absurd (h1 ▸ head_mem w) hv - have : w.length = 0 := length_zero_of_nodup_head_eq_tail w hw hht - simp [length, this] + cases w <;> grind [edges, mem_edges_cons, mem_of_edge_mem, Sym2.eq_iff] + +/-- The endpoint-swapped form of `length_le_one_of_closing_edge_mem`. `Sym2` +equality is symmetric, but `Sym2.eq_swap` is a permutative rewrite that `grind` +cannot apply on its own, so callers that meet the closing edge written +`s(tail, head)` need this version by name. + +Deliberately left untagged: as a `@[grind →]` rule it fires on every `Sym2` +membership and drives `grind` into the `Quot`/`Sym2.Rel` internals, which breaks +the neighbouring `SimpleCycle.arcs_nodup`. -/ +lemma length_le_one_of_closing_edge_mem_swap (w : VertexSeq α) + (h : w.nodup) (he : s(w.tail, w.head) ∈ w.edges) : w.length ≤ 1 := + length_le_one_of_closing_edge_mem w h (by rwa [Sym2.eq_swap]) /-! ## Nodup of arcs -/ /-- A duplicate-free sequence traverses each directed arc at most once. -/ -@[grind] lemma nodup_arcs_of_nodup (w : VertexSeq α) (h : w.nodup) : +@[grind] lemma arcs_nodup (w : VertexSeq α) (h : w.nodup) : w.arcs.Nodup := by - induction w with - | singleton v => simp [arcs] - | cons w v ih => - obtain ⟨hw, hv⟩ := h - rw [arcs_cons, List.concat_eq_append, List.nodup_append] - refine ⟨ih hw, by simp, ?_⟩ - intro a ha b hb hab - simp only [List.mem_singleton] at hb - subst hb; subst hab - exact hv (snd_mem_of_mem_arcs w ha) + induction w <;> + grind [arcs, arcs_cons, List.nodup_append, snd_mem_of_arc_mem] /-- In a duplicate-free sequence, an arc from the tail back to the head cannot occur except in the degenerate length-at-most-one case. -/ -@[grind →] lemma length_le_one_of_mem_arcs_tail_head (w : VertexSeq α) +@[grind →] lemma length_le_one_of_closing_arc_mem (w : VertexSeq α) (h : w.nodup) (ha : (w.tail, w.head) ∈ w.arcs) : w.length ≤ 1 := by - cases w with - | singleton v => simp [arcs] at ha - | cons w v => - obtain ⟨hw, hv⟩ := h - simp only [head_cons, tail_cons, mem_arcs_cons] at ha - rcases ha with ha | ha - · exact absurd (fst_mem_of_mem_arcs w ha) hv - · have hhead : w.head = v := congrArg Prod.snd ha - exact (hv (by rw [← hhead]; exact head_mem w)).elim + cases w <;> grind [arcs, mem_arcs_cons, fst_mem_of_arc_mem] /-! ## append, reverse -/ @@ -243,7 +218,7 @@ occur except in the degenerate length-at-most-one case. -/ rw [reverse, arcs_append, arcs_cons, ih] simp [head_reverse, List.concat_eq_append] -/-! ## Subsequence operations do not introduce new edges -/ +/-! ## Operations do not introduce new edges -/ /-- Dropping the last vertex cannot introduce new traversed edges. -/ @[grind] lemma edges_dropTail_subset (w : VertexSeq α) : @@ -281,7 +256,7 @@ occur except in the degenerate length-at-most-one case. -/ fun_induction cycleErase w <;> grind [mem_edges_cons, edges_prefixUntil_subset, tail_cycleErase] -/-! ## Subsequence operations do not introduce new arcs -/ +/-! ## Operations do not introduce new arcs -/ /-- Dropping the last vertex cannot introduce new traversed arcs. -/ @[grind] lemma arcs_dropTail_subset (w : VertexSeq α) : diff --git a/GraphLib/Theory/Structures/VertexSeq/Erase.lean b/GraphLib/Theory/Structures/VertexSeq/Erase.lean index bb35164..102f8bc 100644 --- a/GraphLib/Theory/Structures/VertexSeq/Erase.lean +++ b/GraphLib/Theory/Structures/VertexSeq/Erase.lean @@ -14,6 +14,10 @@ Two ways to remove redundancy from a vertex sequence: result is `nonstalling`. * `VertexSeq.cycleErase` — remove the detour between the two occurrences of any repeated vertex; the result is `nodup`. + +## Main definitions + +* `VertexSeq.loopErase`, `VertexSeq.cycleErase`. -/ variable {α : Type*} @@ -38,13 +42,32 @@ satisfies `nonstalling`. -/ | cons w v ih => by_cases h : w.tail = v <;> simp [loopErase, h, ih] -@[grind] lemma loopErase_nonstalling [DecidableEq α] (w : VertexSeq α) : +/-- Membership in `loopErase` implies membership in the original sequence. -/ +@[grind] lemma loopErase_subset [DecidableEq α] (w : VertexSeq α) : + w.loopErase ⊆ w := by + intro x hx + induction w <;> grind [loopErase] + +/-- `loopErase` always produces a non-stalling sequence: that is the point of +the operation. -/ +@[grind] lemma nonstalling_loopErase [DecidableEq α] (w : VertexSeq α) : w.loopErase.nonstalling := by induction w with | singleton _ => grind [loopErase] | cons w v ih => by_cases h : w.tail = v <;> grind [loopErase, tail_loopErase] +/-- `loopErase` preserves `nodup`. -/ +@[grind] lemma nodup_loopErase [DecidableEq α] (w : VertexSeq α) (hw : w.nodup) : + w.loopErase.nodup := by + induction w <;> grind [loopErase_subset] + +/-- On a non-stalling sequence, `loopErase` is the identity: there are no +consecutive duplicates to remove. -/ +@[grind =] lemma loopErase_eq_self_of_nonstalling [DecidableEq α] (w : VertexSeq α) + (h : w.nonstalling) : w.loopErase = w := by + induction w <;> grind + /-! ## cycleErase -/ /-- Cycle erasure: whenever a vertex repeats, drop the intermediate detour @@ -62,65 +85,21 @@ between its two occurrences. The result satisfies `nodup`. -/ grind [length_prefixUntil_le] · simp [length] +/-- `cycleErase` preserves the tail vertex. -/ +@[grind =] lemma tail_cycleErase [DecidableEq α] (w : VertexSeq α) : + w.cycleErase.tail = w.tail := by + fun_induction cycleErase w <;> grind [tail_prefixUntil] + /-- Membership in `cycleErase` implies membership in the original sequence. -/ @[grind] lemma cycleErase_subset [DecidableEq α] (w : VertexSeq α) : w.cycleErase ⊆ w := by intro x hx fun_induction cycleErase w <;> grind [prefixUntil_subset] -@[grind] lemma cycleErase_nodup [DecidableEq α] (w : VertexSeq α) : +/-- `cycleErase` always produces a duplicate-free sequence: that is the point of +the operation. -/ +@[grind] lemma nodup_cycleErase [DecidableEq α] (w : VertexSeq α) : w.cycleErase.nodup := by fun_induction cycleErase w <;> grind [cycleErase_subset] -/-! ## Nodup preservation under loopErase -/ - -/-- Membership in `loopErase` implies membership in the original sequence. -/ -@[grind] lemma loopErase_subset [DecidableEq α] (w : VertexSeq α) : - w.loopErase ⊆ w := by - intro x hx - induction w with - | singleton v => - grind [loopErase] - | cons w v ih => - by_cases htail : w.tail = v - · have hxw : x ∈ w := by - exact ih (by simpa [loopErase, htail] using hx) - grind - · have hx' : x ∈ w.loopErase :+ v := by - simpa [loopErase, htail] using hx - rcases (mem_cons x v w.loopErase).1 hx' with hxold | rfl - · exact (mem_cons x v w).2 (Or.inl (ih hxold)) - · exact (mem_cons x x w).2 (Or.inr rfl) - -/-- `loopErase` preserves `nodup`. -/ -@[grind] lemma nodup_loopErase [DecidableEq α] (w : VertexSeq α) (hw : w.nodup) : - w.loopErase.nodup := by - induction w with - | singleton v => grind - | cons w v ih => - by_cases htail : w.tail = v - · grind - · simp [loopErase, htail, nodup] at hw ⊢ - exact ⟨ih hw.1, fun hv => hw.2 (loopErase_subset w v hv)⟩ - -/-! ## Interaction with non-stalling and the tail -/ - -/-- On a non-stalling sequence, `loopErase` is the identity: there are no -consecutive duplicates to remove. -/ -@[grind =] lemma loopErase_eq_self_of_nonstalling [DecidableEq α] (w : VertexSeq α) - (h : w.nonstalling) : w.loopErase = w := by - induction w with - | singleton v => rfl - | cons w v ih => - obtain ⟨hns, hne⟩ := h - have hstep : (w.cons v).loopErase = (w.loopErase).cons v := by - change (if w.tail = v then w.loopErase else (w.loopErase).cons v) = _ - rw [if_neg hne] - rw [hstep, ih hns] - -/-- `cycleErase` preserves the tail vertex. -/ -@[grind =] lemma tail_cycleErase [DecidableEq α] (w : VertexSeq α) : - w.cycleErase.tail = w.tail := by - fun_induction cycleErase w <;> grind [tail_prefixUntil] - end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Index.lean b/GraphLib/Theory/Structures/VertexSeq/Index.lean index 9e86a17..8bc2ef6 100644 --- a/GraphLib/Theory/Structures/VertexSeq/Index.lean +++ b/GraphLib/Theory/Structures/VertexSeq/Index.lean @@ -15,17 +15,46 @@ of a vertex at a given index. * `GetElem (VertexSeq α) ℕ α` — index into the visited vertices. * `VertexSeq.insert` — insert a vertex at a position. + +`insert` currently has no lemmas and no clients inside GraphLib; it is kept as the +one positional *editing* operation the carrier is expected to offer. -/ variable {α : Type*} namespace VertexSeq -/-! ## Indexing and insertion -/ +/-! ## Indexing -/ instance : GetElem (VertexSeq α) ℕ α (fun w i ↦ i < w.toList.length) where getElem w i h := w.toList[i] +/-- Indexing position zero returns the head vertex. -/ +lemma getElem_zero_eq_head (w : VertexSeq α) : + w.toList[0]'(by rw [length_toList]; omega) = w.head := by + induction w <;> grind [toList, head, List.getElem_append_left, length_toList] + +/-- For a nontrivial sequence, the head after `dropHead` is the vertex at +index one. -/ +lemma head_dropHead_eq_getElem_one (w : VertexSeq α) + (h : w.length ≠ 0) : + w.dropHead.head = w.toList[1]'(by rw [length_toList]; omega) := by + induction w <;> + grind [toList, head, dropHead, length_toList, List.getElem_append_left, + head_dropHead_cons, eq_singleton_of_length_zero] + +/-- If the vertex list starts `a :: b :: _`, then the head after `dropHead` is `b`. +The index-free face of `head_dropHead_eq_getElem_one`. -/ +lemma head_dropHead_of_toList_eq_cons_cons (w : VertexSeq α) (a b : α) (rest : List α) + (h : w.toList = a :: b :: rest) : w.dropHead.head = b := by + have hpos : w.length ≠ 0 := by + have := length_toList w + grind + rw [head_dropHead_eq_getElem_one w hpos] + grind + +/-! ## insert -/ + /-- Insert the vertex `v` at index `i`, shifting later vertices one position to the right. If `i` exceeds `w.toList.length`, `v` is appended at the end. -/ @[grind] def insert : VertexSeq α → ℕ → α → VertexSeq α diff --git a/GraphLib/Theory/Structures/VertexSeq/MapZip.lean b/GraphLib/Theory/Structures/VertexSeq/MapZip.lean index 6695547..92eb40f 100644 --- a/GraphLib/Theory/Structures/VertexSeq/MapZip.lean +++ b/GraphLib/Theory/Structures/VertexSeq/MapZip.lean @@ -11,6 +11,11 @@ import GraphLib.Theory.Structures.VertexSeq.Predicates The higher-order traversals over a vertex sequence and the `Functor` instance, together with the membership and `nodup`/`nonstalling` lemmas they need. +`foldl`, `foldr`, `any` and `all` are the standard traversal API of the carrier. +They currently have no lemmas and no clients inside GraphLib; they are kept +because they are the operations a caller writing a new traversal (a trail check, +a colouring pass) reaches for first. + ## Main definitions * `VertexSeq.map`, `VertexSeq.foldl`, `VertexSeq.foldr` — the standard @@ -23,63 +28,65 @@ variable {α : Type*} namespace VertexSeq -/-! ## map, foldl, foldr, zip, any, all -/ +/-! ## Definitions -/ +/-- Apply `f` to every vertex of the sequence. -/ @[grind] def map {β : Type*} (f : α → β) : VertexSeq α → VertexSeq β | .singleton v => singleton (f v) | .cons w v => w.map f :+ f v -@[simp, grind =] lemma toList_map {β : Type*} (f : α → β) (w : VertexSeq α) : - (w.map f).toList = w.toList.map f := by - induction w with - | singleton v => rfl - | cons w v ih => - simp [map, toList, ih, List.concat_eq_append] - -@[simp, grind =] lemma head_map {β : Type*} (f : α → β) (w : VertexSeq α) : - (w.map f).head = f w.head := by - induction w with - | singleton v => rfl - | cons w v ih => exact ih - -@[simp, grind =] lemma tail_map {β : Type*} (f : α → β) (w : VertexSeq α) : - (w.map f).tail = f w.tail := by - induction w <;> rfl - -@[simp, grind =] lemma length_map {β : Type*} (f : α → β) (w : VertexSeq α) : - (w.map f).length = w.length := by - induction w with - | singleton v => rfl - | cons w v ih => simp [map, length, ih] - +/-- Fold from the head towards the tail, carrying an accumulator. -/ @[grind] def foldl {β : Type*} (f : β → α → β) (b : β) : VertexSeq α → β | .singleton v => f b v | .cons w v => f (w.foldl f b) v +/-- Fold from the tail back towards the head, carrying an accumulator. -/ @[grind] def foldr {β : Type*} (f : α → β → β) (b : β) : VertexSeq α → β | .singleton v => f v b | .cons w v => w.foldr f (f v b) +/-- Pair up two sequences vertex by vertex. The result has the length of the +shorter operand; the surplus of the longer one is dropped. -/ @[grind] def zip {β : Type*} : VertexSeq α → VertexSeq β → VertexSeq (α × β) | .singleton v, .singleton u => .singleton (v, u) | .singleton v, .cons _ u => .singleton (v, u) | .cons _ v, .singleton u => .singleton (v, u) | .cons w v, .cons y u => .cons (w.zip y) (v, u) -/-- The tail of a `zip` is the pair of tails. -/ -@[simp, grind =] lemma tail_zip {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) : - (w.zip w').tail = (w.tail, w'.tail) := by - fun_induction zip w w' <;> grind - +/-- Some vertex of the sequence satisfies `p`. -/ @[grind] def any (p : α → Prop) : VertexSeq α → Prop | .singleton v => p v | .cons w v => w.any p ∨ p v +/-- Every vertex of the sequence satisfies `p`. -/ @[grind] def all (p : α → Prop) : VertexSeq α → Prop | .singleton v => p v | .cons w v => w.all p ∧ p v -/-! ## Membership and preservation -/ +/-! ## map -/ + +@[simp, grind =] lemma toList_map {β : Type*} (f : α → β) (w : VertexSeq α) : + (w.map f).toList = w.toList.map f := by + induction w with + | singleton v => rfl + | cons w v ih => + simp [map, toList, ih, List.concat_eq_append] + +@[simp, grind =] lemma head_map {β : Type*} (f : α → β) (w : VertexSeq α) : + (w.map f).head = f w.head := by + induction w with + | singleton v => rfl + | cons w v ih => exact ih + +@[simp, grind =] lemma tail_map {β : Type*} (f : α → β) (w : VertexSeq α) : + (w.map f).tail = f w.tail := by + induction w <;> rfl + +@[simp, grind =] lemma length_map {β : Type*} (f : α → β) (w : VertexSeq α) : + (w.map f).length = w.length := by + induction w with + | singleton v => rfl + | cons w v ih => simp [map, length, ih] /-- Membership in a mapped sequence is membership in the original sequence through the mapping function. -/ @@ -92,8 +99,21 @@ through the mapping function. -/ (w : VertexSeq α) (h : w.nodup) : (w.map f).nodup := by induction w <;> grind +/-- Mapping through an injective function preserves non-stalling: injectivity +keeps distinct consecutive vertices distinct. -/ +@[grind] lemma nonstalling_map {β : Type*} (f : α → β) (hf : Function.Injective f) + (w : VertexSeq α) (h : w.nonstalling) : (w.map f).nonstalling := by + induction w <;> grind + +/-! ## zip -/ + +/-- The tail of a `zip` is the pair of tails. -/ +@[simp, grind =] lemma tail_zip {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) : + (w.zip w').tail = (w.tail, w'.tail) := by + fun_induction zip w w' <;> grind + /-- Membership in a zip projects to membership in the left operand. -/ -@[grind] lemma fst_mem_of_mem_zip {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) +@[grind] lemma fst_mem_of_zip_mem {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) {x : α × β} (h : x ∈ w.zip w') : x.1 ∈ w := by fun_induction zip w w' <;> grind @@ -101,12 +121,10 @@ through the mapping function. -/ @[grind] lemma nodup_zip {β : Type*} (w : VertexSeq α) (w' : VertexSeq β) (hw : w.nodup) : (w.zip w').nodup := by fun_induction zip w w' with - | case1 => grind - | case2 => grind - | case3 => grind | case4 w v y u ih => - simp [nodup] at hw ⊢ - exact ⟨ih hw.1, fun hmem => hw.2 (fst_mem_of_mem_zip w y hmem)⟩ + have hkey : (v, u) ∉ w.zip y := fun hm => hw.2 (fst_mem_of_zip_mem w y hm) + grind [ih hw.1] + | _ => grind /-- Zipping with a non-stalling left operand yields a non-stalling sequence (the first components of consecutive pairs already differ). -/ diff --git a/GraphLib/Theory/Structures/VertexSeq/Predicates.lean b/GraphLib/Theory/Structures/VertexSeq/Predicates.lean index c245c9e..af0818e 100644 --- a/GraphLib/Theory/Structures/VertexSeq/Predicates.lean +++ b/GraphLib/Theory/Structures/VertexSeq/Predicates.lean @@ -18,6 +18,10 @@ This file states their definitions together with the preservation lemmas that only involve the endpoint-dropping operations of `Basic`; preservation under `append`, `reverse`, the subsequence operations, etc. lives alongside those operations in their own files. + +## Main definitions + +* `VertexSeq.nodup`, `VertexSeq.nonstalling`, `VertexSeq.closed`. -/ variable {α : Type*} @@ -39,81 +43,45 @@ namespace VertexSeq /-- A vertex sequence is *closed* when its first and last vertex coincide. -/ @[grind] def closed (w : VertexSeq α) : Prop := w.head = w.tail -@[grind] lemma nodup_nonstalling (w : VertexSeq α) (h : w.nodup) : +/-! ## Implications between the predicates -/ + +/-- A duplicate-free sequence never stalls: a stall would repeat a vertex. -/ +@[grind] lemma nonstalling_of_nodup (w : VertexSeq α) (h : w.nodup) : w.nonstalling := by induction w <;> grind -/-! ## Nodup preservation under dropHead, dropTail -/ +/-- A closed `nodup` sequence has length zero: a repeated endpoint forces a +duplicate unless the sequence is a single vertex. -/ +@[grind] lemma length_zero_of_nodup_closed (w : VertexSeq α) + (hnd : w.nodup) (hclosed : w.closed) : w.length = 0 := by + cases w <;> grind -/-- A `dropHead` result is contained in the original sequence. -/ -@[grind] lemma dropHead_subset (w : VertexSeq α) : w.dropHead ⊆ w := by - intro v hv - fun_induction dropHead w <;> grind +/-! ## Nodup preservation under dropHead, dropTail -/ /-- Dropping the first vertex preserves `nodup`. -/ @[grind] lemma nodup_dropHead (w : VertexSeq α) (h : w.nodup) : w.dropHead.nodup := by - induction w with - | singleton _ => grind - | cons w v ih => - cases w with - | singleton _ => grind - | cons t u => - simp [nodup] at h ⊢ - exact ⟨ih h.1, fun hv => h.2 (dropHead_subset (t :+ u) v hv)⟩ + induction w <;> grind [dropHead_subset] /-- Dropping the last vertex preserves `nodup`. -/ @[grind] lemma nodup_dropTail (w : VertexSeq α) (h : w.nodup) : w.dropTail.nodup := by cases w <;> grind -/-! ## Non-stalling preservation under dropHead, dropTail -/ - -/-- Dropping the first vertex preserves non-stalling. -/ -@[grind] lemma nonstalling_dropHead (w : VertexSeq α) (h : w.nonstalling) : - w.dropHead.nonstalling := by - fun_induction dropHead w <;> grind - -/-- Dropping the last vertex preserves non-stalling. -/ -@[grind] lemma nonstalling_dropTail (w : VertexSeq α) (h : w.nonstalling) : - w.dropTail.nonstalling := by - cases w <;> grind - -/-! ## Nodup, closedness and the underlying list -/ - -/-- A closed `nodup` sequence has length zero: a repeated endpoint forces a -duplicate unless the sequence is a single vertex. -/ -@[grind] lemma length_zero_of_nodup_head_eq_tail (w : VertexSeq α) - (hnd : w.nodup) (hht : w.head = w.tail) : w.length = 0 := by - cases w with - | singleton v => - rfl - | cons w v => - exfalso - exact hnd.2 (by - have h : w.head = v := by simpa using hht - change v ∈ w - rw [← h] - exact head_mem w) - /-- In a non-trivial `nodup` sequence, the head does not reappear after dropping it. -/ @[grind] lemma head_not_mem_dropHead_of_nodup (w : VertexSeq α) (hnd : w.nodup) (hpos : w.length ≠ 0) : w.head ∉ w.dropHead := by - induction w with - | singleton v => - exact (hpos rfl).elim - | cons w v ih => - cases w with - | singleton u => - simpa [dropHead, nodup, mem_def, toList, eq_comm] using hnd.2 - | cons t u => - intro hmem - simp [nodup] at hnd - rcases (mem_cons ((t :+ u).head) v (t :+ u).dropHead).1 hmem with hprefix | hlast - · exact ih hnd.1 (by simp [length]) hprefix - · exact hnd.2 (by - rw [← hlast] - exact head_mem (t :+ u)) + induction w <;> grind [dropHead_subset] + +/-- In a non-trivial `nodup` sequence, the tail does not reappear after dropping +it. -/ +@[grind] lemma tail_not_mem_dropTail_of_nodup (w : VertexSeq α) + (hnd : w.nodup) (hpos : w.length ≠ 0) : w.tail ∉ w.dropTail := by + cases w with + | singleton u => simp [length] at hpos + | cons w u => + simp only [dropTail, tail_cons] + simpa [nodup] using hnd.2 /-- For a closed sequence with a `nodup` interior (drop the repeated tail), the opposite interior (drop the repeated head) is also `nodup`. -/ @@ -121,40 +89,30 @@ opposite interior (drop the repeated head) is also `nodup`. -/ (hclosed : w.closed) (hnodup : w.dropTail.nodup) : w.dropHead.nodup := by cases w with - | singleton v => - grind [dropHead] + | singleton v => grind [dropHead] | cons w v => - cases w with - | singleton u => - grind [dropHead, nodup] - | cons t u => - simp [dropTail, nodup] at hnodup ⊢ - constructor - · exact nodup_dropHead (t :+ u) hnodup - · intro hv - exact head_not_mem_dropHead_of_nodup (t :+ u) hnodup (by simp [length]) - (by simpa [closed] using hclosed ▸ hv) + cases w <;> + grind [dropHead, nodup, closed, head_not_mem_dropHead_of_nodup, nodup_dropHead] + +/-! ## Non-stalling preservation under dropHead, dropTail -/ + +/-- Dropping the first vertex preserves non-stalling. -/ +@[grind] lemma nonstalling_dropHead (w : VertexSeq α) (h : w.nonstalling) : + w.dropHead.nonstalling := by + fun_induction dropHead w <;> grind + +/-- Dropping the last vertex preserves non-stalling. -/ +@[grind] lemma nonstalling_dropTail (w : VertexSeq α) (h : w.nonstalling) : + w.dropTail.nonstalling := by + cases w <;> grind + +/-! ## Nodup and the underlying list -/ /-- `nodup` is exactly `Nodup` of the underlying list. -/ lemma nodup_iff_toList_nodup (w : VertexSeq α) : w.nodup ↔ w.toList.Nodup := by - induction w with - | singleton v => - simp [nodup, toList] - | cons w v ih => - constructor - · intro h - rw [toList, List.concat_eq_append, List.nodup_append] - refine ⟨ih.mp h.1, by simp, ?_⟩ - intro a ha b hb hab - simp at hb - subst hb - subst hab - exact h.2 (by simpa [mem_def] using ha) - · intro h - rw [toList, List.concat_eq_append, List.nodup_append] at h - refine ⟨ih.mpr h.1, ?_⟩ - intro hv - exact h.2.2 v (by simpa [mem_def] using hv) v (by simp) rfl + induction w <;> + simp_all [nodup, toList, List.concat_eq_append, List.nodup_append, mem_def] + all_goals grind end VertexSeq diff --git a/GraphLib/Theory/Structures/VertexSeq/Subseq.lean b/GraphLib/Theory/Structures/VertexSeq/Subseq.lean index 2db4fb6..9c88d9a 100644 --- a/GraphLib/Theory/Structures/VertexSeq/Subseq.lean +++ b/GraphLib/Theory/Structures/VertexSeq/Subseq.lean @@ -67,6 +67,11 @@ inclusive of that vertex. The hypothesis guarantees such a vertex exists. -/ (v : α) (h : v ∈ w) : (w.prefixUntil v h) ⊆ w := by fun_induction prefixUntil <;> grind +/-- A `suffixFrom` result is contained in the original sequence. -/ +@[simp] lemma suffixFrom_subset [DecidableEq α] (w : VertexSeq α) + (v : α) (h : v ∈ w) : (w.suffixFrom v h) ⊆ w := by + fun_induction suffixFrom w v h <;> grind[mem_cons] + @[simp] lemma head_suffixFrom [DecidableEq α] (w : VertexSeq α) (v : α) (h : v ∈ w) : (w.suffixFrom v h).head = v := by fun_induction suffixFrom w v h <;> grind @@ -75,19 +80,59 @@ inclusive of that vertex. The hypothesis guarantees such a vertex exists. -/ (v : α) (h : v ∈ w) : (w.suffixFrom v h).tail = w.tail := by fun_induction suffixFrom w v h <;> grind -/-- A `suffixFrom` result is contained in the original sequence. -/ -@[simp] lemma suffixFrom_subset [DecidableEq α] (w : VertexSeq α) - (v : α) (h : v ∈ w) : (w.suffixFrom v h) ⊆ w := by - fun_induction suffixFrom w v h - · grind - · expose_names - intro u hu - apply (mem_cons u x (w2.suffixFrom v h_1)).1 at hu - cases hu - · expose_names - grind - · grind - grind +/-- For a sequence without repeated vertices, cutting a suffix at the vertex +at index `i` agrees on lists with dropping the first `i` entries. -/ +lemma toList_suffixFrom_eq_drop [DecidableEq α] (w : VertexSeq α) + (hw : w.nodup) (i : ℕ) (hi : i < w.toList.length) : + (w.suffixFrom w.toList[i] (List.getElem_mem hi)).toList = w.toList.drop i := by + induction w generalizing i <;> + grind [toList, suffixFrom, length_toList, List.getElem_append_left, List.drop_append] + +/-- If the vertex list of `w` splits as `u ++ a :: v`, then the suffix starting at +`a` is exactly `a :: v`. + +This is the index-free face of `toList_suffixFrom_eq_drop`: callers describe the +cut by a list splitting instead of an index, so no dependent `getElem` bound has +to be carried around. -/ +lemma toList_suffixFrom_eq_of_split [DecidableEq α] (w : VertexSeq α) (hw : w.nodup) + (a : α) (ha : a ∈ w) (u v : List α) (h : w.toList = u ++ a :: v) : + (w.suffixFrom a ha).toList = a :: v := by + have hi : u.length < w.toList.length := by grind + have hget : w.toList[u.length] = a := by grind [List.getElem_append_right] + subst hget + have hdrop := toList_suffixFrom_eq_drop w hw u.length hi + grind [List.drop_left] + +/-- Cutting a nontrivial prefix does not change the head remaining after +`dropHead`. -/ +lemma head_dropHead_prefixUntil [DecidableEq α] (w : VertexSeq α) + (b : α) (hb : b ∈ w) (hpos : (w.prefixUntil b hb).length ≠ 0) : + (w.prefixUntil b hb).dropHead.head = w.dropHead.head := by + induction w <;> grind + +/-- A `prefixUntil` cut at a vertex other than the head is non-trivial: its head is +the original head and its tail is the cut point, so a length-zero cut would force +them to coincide. -/ +lemma length_prefixUntil_ne_zero [DecidableEq α] (w : VertexSeq α) (v : α) (h : v ∈ w) + (hne : v ≠ w.head) : (w.prefixUntil v h).length ≠ 0 := fun hz => + hne (((tail_prefixUntil w v h).symm.trans + (head_eq_tail_of_length_zero _ hz).symm).trans (head_prefixUntil w v h)) + +/-- If the `prefixUntil` cut at `v` has length one, then `v` is the second vertex: +it is what remains at the head after dropping the original head. -/ +lemma head_dropHead_prefixUntil_of_length_one [DecidableEq α] (w : VertexSeq α) (v : α) + (h : v ∈ w) (h1 : (w.prefixUntil v h).length = 1) : w.dropHead.head = v := + (head_dropHead_prefixUntil w v h (by omega)).symm.trans + ((head_dropHead_eq_tail_of_length_one _ h1).trans (tail_prefixUntil w v h)) + +/-- If the suffix from `v` has length at most one, then `v` is the last or the +second-to-last (penultimate) vertex of `w`. The disjunctive conclusion makes a +poor automatic rule, so this is left untagged and used by name. -/ +lemma eq_tail_or_eq_penultimate_of_length_suffixFrom_le_one [DecidableEq α] + (w : VertexSeq α) {v : α} (hv : v ∈ w) (hpos : w.length ≠ 0) + (h : (w.suffixFrom v hv).length ≤ 1) : + v = w.tail ∨ v = w.dropTail.tail := by + cases w <;> grind /-! ## takeWhile, dropWhile -/ @@ -118,6 +163,29 @@ the tail. -/ (w.dropWhile p h).tail = w.tail := by fun_induction dropWhile w p h <;> grind +/-- A `dropWhile` result is a suffix of the original sequence. -/ +@[grind] lemma dropWhile_subset (w : VertexSeq α) (p : α → Prop) + [DecidablePred p] (h : ∃ v ∈ w.toList, ¬ p v) : (w.dropWhile p h) ⊆ w := by + intro y hy + fun_induction dropWhile w p h <;> grind + +/-- The head remaining after `dropWhile` does not satisfy the dropped +predicate. -/ +lemma not_pred_head_dropWhile (w : VertexSeq α) (pred : α → Prop) + [DecidablePred pred] (h : ∃ z ∈ w.toList, ¬ pred z) : + ¬ pred (w.dropWhile pred h).head := by + fun_induction dropWhile w pred h <;> grind + +/-- A vertex in the prefix ending at the first vertex that fails `pred` is +either that endpoint or satisfies `pred`. -/ +lemma eq_head_dropWhile_or_pred_of_mem_prefixUntil [DecidableEq α] + (w : VertexSeq α) (pred : α → Prop) [DecidablePred pred] + (h : ∃ z ∈ w.toList, ¬ pred z) {z : α} + (hz : z ∈ w.prefixUntil (w.dropWhile pred h).head + (dropWhile_subset w pred h _ (head_mem _))) : + z = (w.dropWhile pred h).head ∨ pred z := by + fun_induction dropWhile w pred h <;> grind [prefixUntil] + /-! ## splitAt -/ /-- Split `w` into a list of pieces at every occurrence of the vertex `v`. @@ -134,6 +202,45 @@ where | [w], x => [w :+ x] | p :: ps, x => p :: appendToLast ps x +/-- If every piece of `L` is contained in `w`, then every piece of +`appendToLast L x` is contained in `w :+ x`. -/ +@[grind] lemma appendToLast_subset (L : List (VertexSeq α)) (x : α) + (w : VertexSeq α) (hL : ∀ p ∈ L, p ⊆ w) {q : VertexSeq α} + (hq : q ∈ splitAt.appendToLast L x) : q ⊆ w :+ x := by + revert q + induction L with + | nil => grind [splitAt.appendToLast] + | cons p ps ih => + cases ps <;> grind [splitAt.appendToLast, mem_cons, hL p (by simp)] + +/-- Every piece produced by `splitAt` is contained in the original sequence. -/ +@[grind] lemma splitAt_subset [DecidableEq α] (w : VertexSeq α) (v : α) + {p : VertexSeq α} (hp : p ∈ w.splitAt v) : p ⊆ w := by + revert p + fun_induction splitAt w v <;> grind [appendToLast_subset] + +/-- `appendToLast` extends the final piece, so the last piece becomes the old +last piece with `x` appended. -/ +@[simp, grind =] lemma getLast?_appendToLast (L : List (VertexSeq α)) (x : α) : + (splitAt.appendToLast L x).getLast? = (L.getLast?).map (· :+ x) := by + fun_induction splitAt.appendToLast L x <;> grind + +/-- Every piece produced by `splitAt` ends at the original tail. -/ +@[grind] lemma tail_getLast?_splitAt [DecidableEq α] (w : VertexSeq α) (v : α) + {p : VertexSeq α} (hp : p ∈ (w.splitAt v).getLast?) : p.tail = w.tail := by + revert p + fun_induction splitAt w v <;> grind + +/-! ## Reassembling a sequence from its cut at a vertex -/ + +/-- Cutting at an interior vertex `v` and re-gluing the prefix (with its +duplicated `v` dropped) to the suffix recovers the original sequence. -/ +@[simp, grind →] lemma dropTail_prefixUntil_append_suffixFrom [DecidableEq α] + (w : VertexSeq α) (v : α) (h : v ∈ w) (hne : v ≠ w.head) : + (w.prefixUntil v h).dropTail.append (w.suffixFrom v h) = w := by + fun_induction prefixUntil w v h <;> + grind [suffixFrom, append, dropTail, mem_cons] + /-! ## Nodup preservation -/ /-- A `prefixUntil` of a `nodup` sequence is `nodup`. -/ @@ -151,111 +258,35 @@ where [DecidablePred p] (hw : w.nodup) : (w.takeWhile p).nodup := by fun_induction takeWhile w p <;> grind -/-- A `dropWhile` result is a suffix of the original sequence. -/ -@[grind] lemma dropWhile_subset (w : VertexSeq α) (p : α → Prop) - [DecidablePred p] (h : ∃ v ∈ w.toList, ¬ p v) : (w.dropWhile p h) ⊆ w := by - intro y hy - fun_induction dropWhile w p h <;> grind - /-- `dropWhile` preserves `nodup`. -/ @[grind] lemma nodup_dropWhile (w : VertexSeq α) (p : α → Prop) [DecidablePred p] (h : ∃ v ∈ w.toList, ¬ p v) (hw : w.nodup) : (w.dropWhile p h).nodup := by fun_induction dropWhile w p h <;> grind [dropWhile_subset] -/-- If every piece of `L` is contained in `w`, then every piece of -`appendToLast L x` is contained in `w :+ x`. -/ -@[grind] lemma appendToLast_subset (L : List (VertexSeq α)) (x : α) - (w : VertexSeq α) (hL : ∀ p ∈ L, p ⊆ w) : - ∀ p ∈ splitAt.appendToLast L x, p ⊆ w :+ x := by - induction L with - | nil => - grind [splitAt.appendToLast] - | cons p ps ih => - cases ps with - | nil => - intro r hr y hy - have hr' : r = p :+ x := by - simpa [splitAt.appendToLast] using hr - subst r - rcases (mem_cons y x p).1 hy with hyp | rfl - · exact (mem_cons y x w).2 (Or.inl (hL p (by simp) y hyp)) - · exact (mem_cons y y w).2 (Or.inr rfl) - | cons q qs => - intro r hr y hy - have hr' : r = p ∨ r ∈ splitAt.appendToLast (q :: qs) x := by - simpa [splitAt.appendToLast] using hr - rcases hr' with rfl | hr' - · exact (mem_cons y x w).2 (Or.inl (hL r (by simp) y hy)) - · have htail : ∀ s ∈ q :: qs, s ⊆ w := by - intro s hs - exact hL s (by simp [hs]) - exact ih htail r hr' y hy - -/-- Every piece produced by `splitAt` is contained in the original sequence. -/ -@[grind] lemma splitAt_subset [DecidableEq α] (w : VertexSeq α) (v : α) : - ∀ p ∈ w.splitAt v, p ⊆ w := by - fun_induction splitAt w v <;> grind [appendToLast_subset] - /-- If every piece of `L` is nodup and avoids `x`, then `appendToLast L x` has only nodup pieces. -/ @[grind] lemma nodup_appendToLast (L : List (VertexSeq α)) (x : α) - (hL : ∀ p ∈ L, p.nodup) (havoid : ∀ p ∈ L, x ∉ p) : - ∀ p ∈ splitAt.appendToLast L x, p.nodup := by + (hL : ∀ p ∈ L, p.nodup) (havoid : ∀ p ∈ L, x ∉ p) {q : VertexSeq α} + (hq : q ∈ splitAt.appendToLast L x) : q.nodup := by + revert q induction L with - | nil => - grind [splitAt.appendToLast] - | cons p ps ih => - cases ps with - | nil => - intro r hr - have hr' : r = p :+ x := by - simpa [splitAt.appendToLast] using hr - subst r - exact ⟨hL p (by simp), havoid p (by simp)⟩ - | cons q qs => - intro r hr - have hr' : r = p ∨ r ∈ splitAt.appendToLast (q :: qs) x := by - simpa [splitAt.appendToLast] using hr - rcases hr' with rfl | hr' - · exact hL r (by simp) - · have htail : ∀ s ∈ q :: qs, s.nodup := by - intro s hs - exact hL s (by simp [hs]) - have havoid' : ∀ s ∈ q :: qs, x ∉ s := by - intro s hs - exact havoid s (by simp [hs]) - exact ih htail havoid' r hr' + | nil => grind [splitAt.appendToLast] + | cons p ps ih => cases ps <;> grind [splitAt.appendToLast] /-- Each piece of a `splitAt` of a `nodup` sequence is `nodup`. -/ @[grind] lemma nodup_splitAt [DecidableEq α] (w : VertexSeq α) - (hw : w.nodup) (v : α) : ∀ p ∈ w.splitAt v, p.nodup := by + (hw : w.nodup) (v : α) {p : VertexSeq α} (hp : p ∈ w.splitAt v) : p.nodup := by + revert p induction w with - | singleton x => - intro p hp - have hp' : p = VertexSeq.singleton x := by - simpa [splitAt] using hp - subst p - grind + | singleton x => grind | cons q x ih => - intro p hp - simp [nodup] at hw - by_cases hx : x = v - · have hp' : p ∈ splitAt.appendToLast (q.splitAt v) v ∨ - p ∈ [VertexSeq.singleton v] := by - simpa [splitAt, hx] using hp - rcases hp' with hp_append | hp_single - · exact nodup_appendToLast (q.splitAt v) v (ih hw.1) - (fun s hs hsv => hw.2 (by - rw [hx] - exact splitAt_subset q v s hs v hsv)) p hp_append - · have hp_eq : p = VertexSeq.singleton v := by - simpa using hp_single - subst p - grind - · exact nodup_appendToLast (q.splitAt v) x (ih hw.1) - (fun s hs hsx => hw.2 (splitAt_subset q v s hs x hsx)) p - (by simpa [splitAt, hx] using hp) + have havoid : ∀ s ∈ q.splitAt v, x ∉ s := fun s hs hsx => + hw.2 (splitAt_subset q v hs x hsx) + have hkey : ∀ s ∈ splitAt.appendToLast (q.splitAt v) x, s.nodup := + fun s hs => nodup_appendToLast (q.splitAt v) x + (fun t ht => ih hw.1 ht) havoid hs + grind /-! ## Non-stalling preservation -/ @@ -280,50 +311,37 @@ has only nodup pieces. -/ (w.dropWhile p h).nonstalling := by fun_induction dropWhile w p h <;> grind -/-- `appendToLast` extends the final piece, so the last piece becomes the old -last piece with `x` appended. -/ -@[simp, grind =] lemma getLast?_appendToLast (L : List (VertexSeq α)) (x : α) : - (splitAt.appendToLast L x).getLast? = (L.getLast?).map (· :+ x) := by - fun_induction splitAt.appendToLast L x <;> grind - /-- If every piece of `L` is non-stalling and the final piece does not end at `x`, then every piece of `appendToLast L x` is non-stalling. -/ @[grind] lemma nonstalling_appendToLast (L : List (VertexSeq α)) (x : α) - (hL : ∀ p ∈ L, p.nonstalling) (hlast : ∀ p ∈ L.getLast?, p.tail ≠ x) : - ∀ p ∈ splitAt.appendToLast L x, p.nonstalling := by + (hL : ∀ p ∈ L, p.nonstalling) (hlast : ∀ p ∈ L.getLast?, p.tail ≠ x) + {q : VertexSeq α} (hq : q ∈ splitAt.appendToLast L x) : q.nonstalling := by + revert q fun_induction splitAt.appendToLast L x <;> grind -/-- Every piece produced by `splitAt` ends at the original tail. -/ -@[grind] lemma tail_getLast?_splitAt [DecidableEq α] (w : VertexSeq α) (v : α) : - ∀ p ∈ (w.splitAt v).getLast?, p.tail = w.tail := by - fun_induction splitAt w v <;> grind - /-- Each piece of a `splitAt` of a non-stalling sequence is non-stalling. -/ @[grind] lemma nonstalling_splitAt [DecidableEq α] (w : VertexSeq α) - (hw : w.nonstalling) (v : α) : ∀ p ∈ w.splitAt v, p.nonstalling := by + (hw : w.nonstalling) (v : α) {p : VertexSeq α} + (hp : p ∈ w.splitAt v) : p.nonstalling := by + revert p fun_induction splitAt w v <;> grind -/-! ## Reassembling a sequence from its cut at a vertex -/ - -/-- Cutting at an interior vertex `v` and re-gluing the prefix (with its -duplicated `v` dropped) to the suffix recovers the original sequence. -/ -@[simp, grind →] lemma dropTail_prefixUntil_append_suffixFrom [DecidableEq α] - (w : VertexSeq α) (v : α) (h : v ∈ w) (hne : v ≠ w.head) : - (w.prefixUntil v h).dropTail.append (w.suffixFrom v h) = w := by - induction w generalizing v with - | singleton x => - exact (hne (by grind)).elim - | cons w x ih => - by_cases hmem : v ∈ w - · simp [prefixUntil, suffixFrom, hmem] - by_cases hvhead : v = w.head - · subst hvhead - grind - · exact congrArg (fun q => q :+ x) (ih v hmem hvhead) - · have hvx : v = x := by - have hv : v ∈ w ∨ v = x := (mem_cons v x w).1 h - exact hv.elim (fun hw => (hmem hw).elim) id - subst hvx - simp [prefixUntil, suffixFrom, hmem, append, dropTail] +/-! ## First vertex satisfying a predicate -/ + +/-- Scanning `q` from its head, the first vertex satisfying `P` other than the +head yields a `prefixUntil` cut whose only `P`-vertices are its two endpoints: +the cut point `b` itself (the tail of the prefix) and `q.head` (its head). + +This is the vertex-sequence analogue of locating the first predicate hit; it is +used to isolate a minimal prefix that meets a given set only at its two ends. -/ +lemma exists_prefixUntil_pred_eq_head_or_tail [DecidableEq α] (P : α → Prop) + (q : VertexSeq α) (hz : ∃ z ∈ q, P z ∧ z ≠ q.head) : + ∃ b, ∃ hb : b ∈ q, P b ∧ b ≠ q.head ∧ + ∀ z ∈ q.prefixUntil b hb, P z → z = b ∨ z = q.head := by + classical + set p : α → Prop := fun z => ¬ (P z ∧ z ≠ q.head) with hp + have h : ∃ z ∈ q.toList, ¬ p z := by grind + refine ⟨_, dropWhile_subset q p h _ (head_mem _), ?_, ?_, ?_⟩ <;> + grind [not_pred_head_dropWhile, eq_head_dropWhile_or_pred_of_mem_prefixUntil] end VertexSeq From 85f2f34bd3faed9e8b2801d6fb57ce5042597b1b Mon Sep 17 00:00:00 2001 From: Weixuan Yuan Date: Tue, 14 Jul 2026 00:06:11 +0800 Subject: [PATCH 3/3] Remove stale walks import --- GraphLib.lean | 1 - 1 file changed, 1 deletion(-) diff --git a/GraphLib.lean b/GraphLib.lean index 9cc1e18..914c1c6 100644 --- a/GraphLib.lean +++ b/GraphLib.lean @@ -1,7 +1,6 @@ import GraphLib.Graph.Basic import GraphLib.Theory.Basic -import GraphLib.Theory.Walks.Basic import GraphLib.Theory.Trees.Basic import GraphLib.Theory.Connectivity.Basic import GraphLib.Theory.Spectral.Basic