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:

PastedText.txt

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