Skip to content

Commit e577c16

Browse files
Rida-Hamadanivlad902SnirBroshi
committed
feat(SimpleGraph): construct a cycle of two distinct paths with same start and end (leanprover-community#33506)
Co-authored-by: Vlad Tsyrklevich <vlad@tsyrklevi.ch> Co-authored-by: Snir Broshi <26556598+SnirBroshi@users.noreply.github.com>
1 parent 6da0491 commit e577c16

5 files changed

Lines changed: 112 additions & 5 deletions

File tree

Mathlib/Combinatorics/SimpleGraph/Paths.lean

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,18 @@ theorem reverse_isTrail_iff {u v : V} (p : G.Walk u v) : p.reverse.IsTrail ↔ p
150150
convert! h.reverse _
151151
try rw [reverse_reverse]
152152

153+
@[simp]
154+
theorem isTrail_append {u v w : V} (p : G.Walk u v) (q : G.Walk v w) :
155+
(p.append q).IsTrail ↔ p.IsTrail ∧ q.IsTrail ∧ p.edges.Disjoint q.edges := by
156+
simp [Walk.isTrail_def, List.nodup_append']
157+
153158
theorem IsTrail.of_append_left {u v w : V} {p : G.Walk u v} {q : G.Walk v w}
154159
(h : (p.append q).IsTrail) : p.IsTrail := by
155-
rw [isTrail_def, edges_append, List.nodup_append] at h
156-
exact ⟨h.1
160+
simp_all
157161

158162
theorem IsTrail.of_append_right {u v w : V} {p : G.Walk u v} {q : G.Walk v w}
159163
(h : (p.append q).IsTrail) : q.IsTrail := by
160-
rw [isTrail_def, edges_append, List.nodup_append] at h
161-
exact ⟨h.2.1
164+
simp_all
162165

163166
theorem IsTrail.count_edges_le_one [DecidableEq V] {u v : V} {p : G.Walk u v} (h : p.IsTrail)
164167
(e : Sym2 V) : p.edges.count e ≤ 1 :=
@@ -716,7 +719,97 @@ end Path
716719

717720
namespace Walk
718721

719-
variable {G} [DecidableEq V] {u u' v v' : V}
722+
variable {G} {u v : V}
723+
724+
theorem IsPath.length_eq_one_of_mem_edges {p : G.Walk u v} (hp : p.IsPath) (h : s(u, v) ∈ p.edges) :
725+
p.length = 1 := by
726+
suffices p.length - 1 = 0 by grind [length_edges]
727+
rw [← hp.getVert_eq_start_iff <| p.length.sub_le 1]
728+
exact (hp.eq_penultimate_of_mem_edges <| Sym2.eq_swap ▸ h).symm
729+
730+
theorem IsPath.eq_adj_toWalk_of_mem_edges {p : G.Walk u v} (hp : p.IsPath) (h : s(u, v) ∈ p.edges) :
731+
p = (p.adj_of_mem_edges h).toWalk := by
732+
apply p.ext_getVert_le_length <| by simp [hp.length_eq_one_of_mem_edges h]
733+
intro _ hl
734+
cases Nat.le_one_iff_eq_zero_or_eq_one.mp (hp.length_eq_one_of_mem_edges h ▸ hl) with
735+
| inl hl => simp [hl]
736+
| inr hl =>
737+
rw [hl, getVert_cons_succ, getVert_zero, ← hp.length_eq_one_of_mem_edges h, getVert_length]
738+
739+
theorem IsPath.disjoint_edges_of_disjoint_support {p : G.Walk u v} {q : G.Walk v u} (hp : p.IsPath)
740+
(hd : p.support.tail.Disjoint q.support.tail) (hl : p.length ≠ 1) :
741+
p.edges.Disjoint q.edges := by
742+
simp only [List.disjoint_left] at hd ⊢
743+
contrapose! hd
744+
obtain ⟨⟨a, b⟩, hep, heq⟩ := hd
745+
have := p.mem_support_iff.mp <| p.fst_mem_support_of_mem_edges hep
746+
have := p.mem_support_iff.mp <| p.snd_mem_support_of_mem_edges hep
747+
have := q.mem_support_iff.mp <| q.fst_mem_support_of_mem_edges heq
748+
have := q.mem_support_iff.mp <| q.snd_mem_support_of_mem_edges heq
749+
grind [p.adj_of_mem_edges hep |>.ne, length_eq_one_of_mem_edges]
750+
751+
lemma IsPath.isCycle_append {p : G.Walk u v} {q : G.Walk v u} (hp : p.IsPath) (hq : q.IsPath)
752+
(h : p.support.tail.Disjoint q.support.tail) (hn : 1 < p.length ∨ 1 < q.length) :
753+
(p.append q).IsCycle := by
754+
rw [isCycle_def, isTrail_append]
755+
refine ⟨⟨hp.isTrail, hq.isTrail, ?_⟩, ?_, ?_⟩
756+
· grind [IsPath.disjoint_edges_of_disjoint_support, List.Disjoint.symm]
757+
· grind [nil_append_iff, length_eq_zero_iff]
758+
· rw [tail_support_append, List.nodup_append']
759+
exact ⟨hp.support_nodup.tail, hq.support_nodup.tail, h⟩
760+
761+
/--
762+
Given two distinct paths with the same endpoints, we can extract a subwalk from each such that their
763+
concatenation, with one reversed, forms a cycle.
764+
-/
765+
theorem IsPath.exists_isCycle_of_ne {p q : G.Walk u v} (hp : p.IsPath) (hq : q.IsPath)
766+
(h : p ≠ q) :
767+
∃ (u' v' : V) (p' q' : G.Walk u' v'),
768+
p'.IsSubwalk p ∧ q'.IsSubwalk q ∧ (p'.append q'.reverse).IsCycle := by
769+
induction hs : p.length using Nat.strongRec generalizing u v with | ind s ih =>
770+
by_cases! hw : ∃ w, w ∈ p.support ∧ w ∈ q.support ∧ w ≠ u ∧ w ≠ v
771+
· classical
772+
have ⟨w, hwp, hwq, hwu, hwv⟩ := hw
773+
by_cases! p.takeUntil w hwp ≠ q.takeUntil w hwq
774+
· have := ih _ (hs ▸ length_takeUntil_lt_length hwp hwv) (hp.takeUntil hwp) (hq.takeUntil hwq)
775+
grind [isSubwalk_takeUntil, IsSubwalk.trans]
776+
· have := ih _ (hs ▸ length_dropUntil_lt_length hwp hwu) (hp.dropUntil hwp) (hq.dropUntil hwq)
777+
<| by grind [take_spec]
778+
grind [isSubwalk_dropUntil, IsSubwalk.trans]
779+
· refine ⟨u, v, p, q, p.isSubwalk_rfl, q.isSubwalk_rfl, ?_⟩
780+
refine hp.isCycle_append (isPath_reverse_iff q |>.mpr hq) (fun _ ↦ ?_) ?_
781+
· grind [dropLast_support_concat, IsPath.support_nodup, support_reverse, cons_tail_support]
782+
· grind [length_reverse, eq_of_length_le_one]
783+
784+
open List in
785+
/--
786+
Given two distinct paths, `p` and `q`, with same endpoints, we can extract a cycle whose support
787+
is a sublist of `p.support ++ q.support.reverse.tail`.
788+
-/
789+
theorem IsPath.exists_isCycle_sublist_of_ne {p q : G.Walk u v} (hp : p.IsPath)
790+
(hq : q.IsPath) (h : p ≠ q) :
791+
∃ w, w ∈ p.support ∧ w ∈ q.support ∧
792+
∃ c : G.Walk w w, c.IsCycle ∧ c.support <+ (p.support ++ q.support.reverse.tail) := by
793+
have ⟨u', v', p', q', hp', hq', hcyc⟩ := hp.exists_isCycle_of_ne hq h
794+
use u', hp'.support_subset p'.start_mem_support, hq'.support_subset q'.start_mem_support
795+
refine ⟨_, hcyc, ?_⟩
796+
rw [support_append, support_reverse]
797+
refine .append ?_ <| .tail <| .reverse ?_
798+
· exact isSubwalk_iff_support_isInfix.mp hp' |>.sublist
799+
· exact isSubwalk_iff_support_isInfix.mp hq' |>.sublist
800+
801+
/--
802+
Given two distinct paths with same endpoints, we can extract a cycle whose length is less than or
803+
equal to the sum of their lengths.
804+
-/
805+
theorem IsPath.exists_isCycle_length_le_add_of_ne {p q : G.Walk u v} (hp : p.IsPath)
806+
(hq : q.IsPath) (h : p ≠ q) :
807+
∃ w, w ∈ p.support ∧ w ∈ q.support ∧
808+
∃ c : G.Walk w w, c.IsCycle ∧ c.length ≤ p.length + q.length := by
809+
obtain ⟨w, hw₁, hw₂, c, hc₁, hc₂⟩ := hp.exists_isCycle_sublist_of_ne hq h
810+
use w, hw₁, hw₂, c, hc₁, by grind [hc₂.length_le]
811+
812+
variable [DecidableEq V] {u' v' : V}
720813

721814
/-- Given a walk, produces a walk from it by bypassing subwalks between repeated vertices.
722815
The result is a path, as shown in `SimpleGraph.Walk.bypass_isPath`.

Mathlib/Combinatorics/SimpleGraph/Walk/Basic.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ theorem exists_length_eq_zero_iff {u v : V} : (∃ p : G.Walk u v, p.length = 0)
108108
lemma exists_length_eq_one_iff {u v : V} : (∃ (p : G.Walk u v), p.length = 1) ↔ G.Adj u v :=
109109
fun ⟨_, hp⟩ ↦ adj_of_length_eq_one hp, (⟨·.toWalk, by simp⟩)⟩
110110

111+
theorem eq_of_length_le_one {p q : G.Walk u v} (hp : p.length ≤ 1) (hq : q.length ≤ 1) : p = q := by
112+
grind [cases Walk, length_cons, Adj.ne]
113+
111114
/-- The `support` of a walk is the list of vertices it visits in order. -/
112115
def support {u v : V} : G.Walk u v → List V
113116
| nil => [u]

Mathlib/Combinatorics/SimpleGraph/Walk/Decomp.lean

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,10 @@ lemma length_takeUntil_lt_length {u v w : V} {p : G.Walk v w} (h : u ∈ p.suppo
304304

305305
@[deprecated (since := "2026-05-25")] alias length_takeUntil_lt := length_takeUntil_lt_length
306306

307+
lemma length_dropUntil_lt_length {u v w : V} {p : G.Walk v w} (h : u ∈ p.support) (huv : u ≠ v) :
308+
(p.dropUntil u h).length < p.length := by
309+
grind [length_dropUntil, cons_tail_support]
310+
307311
lemma takeUntil_takeUntil {w x : V} (p : G.Walk u v) (hw : w ∈ p.support)
308312
(hx : x ∈ (p.takeUntil w hw).support) :
309313
(p.takeUntil w hw).takeUntil x hx =

Mathlib/Data/List/Basic.lean

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,10 @@ theorem get_tail (l : List α) (i) (h : i < l.tail.length)
499499
l.tail.get ⟨i, h⟩ = l.get ⟨i + 1, h'⟩ := by
500500
simp
501501

502+
theorem getElem_mem_tail {k : ℕ} (l : List α) (h : k ≠ 0) (hk : k < l.length) :
503+
l[k]'hk ∈ l.tail := by
504+
cases l <;> grind
505+
502506
/-! ### sublists -/
503507

504508
attribute [refl] List.Sublist.refl

Mathlib/Data/List/Nodup.lean

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,9 @@ theorem nodup_concat (l : List α) (u : α) : (l.concat u).Nodup ↔ u ∉ l ∧
231231
rw [← nodup_reverse]
232232
simp
233233

234+
@[simp, grind ←] protected lemma Nodup.tail {l : List α} (h : Nodup l) : Nodup l.tail :=
235+
l.tail_sublist.nodup h
236+
234237
lemma nodup_tail_reverse (l : List α) (h : l[0]? = l.getLast?) :
235238
Nodup l.reverse.tail ↔ Nodup l.tail := by
236239
induction l with

0 commit comments

Comments
 (0)