Zulip Chat Archive
Stream: graph theory
Topic: Help me learn good style: IsTree
Ewan Davies (Oct 06 2025 at 19:40):
I have been trying to learn how to work with graphs in mathlib. I tried to prove that a finite nontrivial tree (i.e. at least two vertices) has at least two leaves. My favorite proof of this is to consider a longest path, which by nontriviality has at least two vertices. Then by acyclicity and maximality of the length, the two endpoints are leaves. I struggled to express that proof, and instead relied on a few things already in mathlib about leaves and degree sums. Of course I'd love to write code suitable for inclusion in mathlib, so can I get some review on style from more experienced developers?
- Is it OK to use grind everywhere? Are explicit chains of rw and apply preferable?
- Are there some natural shortenings of my proofs that I don't know because I'm too inexperienced?
- How bad are my naming conventions?
Here's the code:
Edit: thanks Aaron.
twoleaves
Aaron Liu (Oct 06 2025 at 19:47):
you can put long snippets of code inside a spoiler tag and it will be collapsed
like this
````spoiler foobar
```lean4
def foo := 1
def bar := foo
-- 3
#eval foo + bar + bar
```
````
and it shows up like this
foobar
Vlad Tsyrklevich (Oct 06 2025 at 20:58):
Ewan Davies said:
- Is it OK to use grind everywhere? Are explicit chains of rw and apply preferable?
- Are there some natural shortenings of my proofs that I don't know because I'm too inexperienced?
- How bad are my naming conventions?
I think using grind is better than a long proof, if you could replace grind with a single theorem statement I would do that though.
In general, you don't need to squeeze simps (e.g. use simp? for terminal simps [e.g. simps that close the goal]), this makes it a bit easier to read IMO.
Instead of doing a proof by apply or by exact you can just use an explicit term.
I think the second calc proof is easier to read in the version I give below where it is replaced by rewrites instead. I unrolled the first one as well in the _ver2 proof but I think I actually prefer _ver1 with the calc left-in.
I have not given any thought as to whether there's a better way to write the proof in terms of what's in mathlib now, instead I just tried to golf down what you already wrote:
Vlad Tsyrklevich (Oct 06 2025 at 22:56):
It does seem to be an oversight that we don't have an easy way to conjure a longest path in a finite graph. I've written the following though it needs further cleaning-up before being PRed. Hopefully it unblocks from you experimenting with the original approach you wanted to try.
import Mathlib.Combinatorics.SimpleGraph.Acyclic
namespace SimpleGraph
variable {V : Type*} {G : SimpleGraph V}
theorem eq_of_eq_bot_walk (hG : G = ⊥) {u v : V} (p : G.Walk u v) : u = v := by
cases p <;> simp_all
theorem nil_of_eq_bot_isPath (hG : G = ⊥) {u v : V} (p : G.Walk u v) (hp : p.IsPath) : p.Nil := by
have := eq_of_eq_bot_walk hG p
subst this
simp [p.isPath_iff_eq_nil.mp hp]
theorem exists_adj_of_ne_bot (h : G ≠ ⊥) : ∃ a b : V, G.Adj a b := by
contrapose! h
exact le_bot_iff.mp h
lemma exists_isPath_forall_isPath_length_le_length [N : Nonempty V] [Fintype V] :
∃ (u v : V) (p : G.Walk u v) (_ : p.IsPath),
∀ (u' v' : V) (p' : G.Walk u' v') (_ : p'.IsPath), p'.length ≤ p.length := by
classical
let maxPathLen :=
Nat.findGreatest (fun n ↦ ∃ (u v : V) (p : G.Walk u v), p.IsPath ∧ p.length = n) (Fintype.card V - 1)
by_cases h' : G = ⊥
· obtain ⟨x⟩ := N
refine ⟨x, x, Walk.nil, by simp, fun u v p hp ↦ ?_⟩
simpa [Walk.nil_iff_length_eq] using nil_of_eq_bot_isPath h' p hp
· have : 0 < maxPathLen := by
simp only [Nat.findGreatest_pos, maxPathLen]
obtain ⟨u, v, hadj⟩ := exists_adj_of_ne_bot h'
have : 2 ≤ Fintype.card V := @Fintype.one_lt_card V _ ⟨u, v, hadj.ne⟩
exact ⟨1, by simp, by grind, ⟨u, v, hadj.toWalk, by simp [hadj.ne], by simp⟩⟩
obtain ⟨h₁, h₂, h₃⟩ := Nat.findGreatest_eq_iff.mp (by gcongr : Nat.findGreatest _ _ = maxPathLen)
obtain ⟨u, v, p, hp₁, hp₂⟩ := h₂ (by grind)
refine ⟨u, v, p, hp₁, fun u' v' p' hp' ↦ ?_⟩
by_contra! hh
have := hp'.length_lt
grind
Ewan Davies (Oct 06 2025 at 23:27):
Thanks for the comments on the county proofs, I am happy to learn more tactics and good style. At the moment I seem to create endless have statements and I suspect that is poor form. But grind plays nicely with careful haves.
I'll take a while to digest your suggestion for conjuring a longest path. But someone (Rida Hamadani) plumbed in diameter, so I was hoping that this stub would be a good place to start:
/-- A nontrivial tree has two distinct vertices of degree one. --/
lemma IsTree.exists_two_verts_degree_one_of_nontrivial_via_path
[DecidableEq V] [Fintype V] [Nontrivial V] [DecidableRel G.Adj]
(h_tree : G.IsTree) : ∃ u v, G.degree u = 1 ∧ G.degree v = 1 ∧ u ≠ v := by
have : Nonempty V := Nontrivial.to_nonempty
have : ∃ (u : V) (v : V), G.dist u v = G.diam := SimpleGraph.exists_dist_eq_diam
match this with
| ⟨u, v, hdiam⟩ =>
-- u and v are endpoints of a maximum path in G
have hne : u ≠ v := by
-- follows from Nontrival V and h_tree
sorry
have : G.degree u = 1 ∧ G.degree v = 1 := by
-- the same argument should work for both
sorry
sorry
Vlad Tsyrklevich (Oct 07 2025 at 09:39):
Vlad Tsyrklevich said:
it needs further cleaning-up before being PRed
Just FYI, the PR is #30293
Last updated: Dec 20 2025 at 21:32 UTC