Zulip Chat Archive
Stream: new members
Topic: separate proofs or inline?
Jakub Nowak (Aug 27 2025 at 19:53):
Which of the two approaches do you think is better? Writing implementation and proving its desired properties separately, or to have it together?
In the code I've made namespace Approach1 and namespace Approach2.
In the 2nd approach there's a function odd_cycle_of_odd_walk : (p : G.walk u u) → p.length.bodd → Σ v : V, { p : G.Walk v v // p.IsCycle ∧ p.length.bodd } which extracts odd cycle from an odd-length walk.
In the 1st approach odd_cycle_of_odd_walk signature is odd_cycle_of_odd_walk : G.Walk u u → Σ v : V, G.Walk v v. The function does the same as in the 2nd approach, but the proof of the desired property is in a separate theorem odd_cycle_of_odd_walk.is_odd_cycle : (p : G.walk u u) → p.length.bodd → (odd_cycle_of_odd_walk p).snd.IsCycle ∧ (odd_cycle_of_odd_walk p).snd.length.bodd. (Well, it isn't cause it's sorry and I wasn't able to do it)
import Mathlib
open SimpleGraph
open Walk
variable {V : Type u} {G : SimpleGraph V}
namespace Approach1
private def odd_cycle_of_odd_walk_step [DecidableEq V] {u v : V}
(pref : G.Walk u v) (suf : G.Walk v u) :
Σ w : V, G.Walk w w :=
match suf with
| .nil => ⟨u, Walk.nil⟩ -- unreachable
| .cons' v w u hAdj suf' =>
if h : w ∈ pref.support then
let pref_pre_part : G.Walk u w := pref.takeUntil w h
let pref_cycle_part : G.Walk w v := pref.dropUntil w h
let cycle : G.Walk v v := Walk.cons hAdj pref_cycle_part
if cycle.length.bodd then
⟨v, cycle⟩
else
odd_cycle_of_odd_walk_step pref_pre_part suf'
else
odd_cycle_of_odd_walk_step (Walk.concat pref hAdj) suf'
private def odd_cycle_of_odd_walk [DecidableEq V] (p : G.Walk u u) : Σ v : V, G.Walk v v :=
odd_cycle_of_odd_walk_step Walk.nil p
theorem odd_cycle_of_odd_walk.is_odd_cycle [DecidableEq V] (p : G.Walk u u) (hOdd : p.length.bodd) :
(odd_cycle_of_odd_walk p).snd.IsCycle ∧ (odd_cycle_of_odd_walk p).snd.length.bodd := by
sorry
end Approach1
namespace Approach2
theorem SimpleGraph.Walk.cons_isCycle_of_path_length_ne_1 {u v : V}
(h : G.Adj u v) (p : G.Walk v u) (hp : p.IsPath) (hl : p.length ≠ 1) :
(cons h p).IsCycle := by
rw [cons_isCycle_iff]
apply And.intro
· assumption
· intro hh
apply hl
clear hl
cases p with
| nil => contradiction
| cons hAdj rest =>
rename_i w
rw [cons_isPath_iff] at hp
obtain ⟨hp₀, hp₁⟩ := hp
rw [edges_cons, List.mem_cons] at hh
rw [length_cons, Nat.add_eq_right]
obtain hh | hh := hh
· have : u = w := by
rw [Sym2.eq, Sym2.rel_iff', Prod.swap_prod_mk, Prod.mk.injEq, Prod.mk.injEq] at hh
obtain hh | hh := hh <;> simp [hh]
subst this
rwa [length_eq_zero_iff, ←isPath_iff_eq_nil]
· have : v ∈ rest.support := snd_mem_support_of_mem_edges rest hh
contradiction
private def odd_cycle_of_odd_walk_step [DecidableEq V] {u v : V}
(pref : G.Path u v) (suf : G.Walk v u) (hOdd : (pref.val.append suf).length.bodd) :
Σ w : V, { p : G.Walk w w // p.IsCycle ∧ p.length.bodd } :=
let ⟨pref, hPref⟩ := pref
match suf with
| .nil => by
exfalso
simp only [] at hOdd
simp_all
| .cons' v w u hAdj suf' =>
have hOdd : pref.length.bodd = suf'.length.bodd := by simp_all
if h : w ∈ pref.support then
let pref_pre_part : G.Walk u w := pref.takeUntil w h
have : pref_pre_part.IsPath := hPref.takeUntil h
let pref_cycle_part : G.Walk w v := pref.dropUntil w h
have : pref_cycle_part.IsPath := hPref.dropUntil h
let cycle : G.Walk v v := Walk.cons hAdj pref_cycle_part
-- `cycle` is not necessarily a cycle here, it might have a length of 2.
if _ : cycle.length.bodd then
have : cycle.IsCycle := by
apply SimpleGraph.Walk.cons_isCycle_of_path_length_ne_1
· assumption
· intro
subst cycle
simp_all
⟨v, cycle, ‹cycle.IsCycle›, ‹cycle.length.bodd›⟩
else
odd_cycle_of_odd_walk_step ⟨pref_pre_part, ‹pref_pre_part.IsPath›⟩ suf' (by
simp only []
subst cycle
have : pref = pref_pre_part.append pref_cycle_part := Eq.symm (Walk.take_spec pref h)
rw [this] at hOdd
simp_all)
else
let next_pref : G.Walk u w := pref.concat hAdj
have : next_pref.IsPath := hPref.concat h hAdj
odd_cycle_of_odd_walk_step ⟨next_pref, ‹next_pref.IsPath›⟩ suf' (by
simp only []
subst next_pref
simpa)
private def odd_cycle_of_odd_walk [DecidableEq V] (p : G.Walk u u) (hOdd : p.length.bodd) :
Σ v : V, { p : G.Walk v v // p.IsCycle ∧ p.length.bodd } :=
odd_cycle_of_odd_walk_step Path.nil p (by simpa)
end Approach2
Kyle Miller (Aug 27 2025 at 20:35):
I think the first approach is usually preferable.
Interleaving proofs with the definition makes it hard to read, and it can lead to some more complex dependent types that are harder to work with.
Also, you're going to have a lot more lemmas about the definition. Why encode just these couple lemmas (IsCycle and odd length) into the definition and not others?
Jakub Nowak (Aug 28 2025 at 13:50):
Kyle Miller said:
Also, you're going to have a lot more lemmas about the definition.
Well, not really. In this case it's private def used as a lemma for an actual theorem.
Kyle Miller (Aug 28 2025 at 16:04):
You're confident that no one would ever find use for this definition in other contexts?
People have used docs#SimpleGraph.Walk.bypass and its supporting takeUntil/dropUntil even though they were only for proving that if there's a walk between two vertices there's a path between them.
Jakub Nowak (Aug 28 2025 at 16:52):
I see. And bypass is indeed defined as G.Walk u v → G.Walk u v.
Last updated: Dec 20 2025 at 21:32 UTC