|
| 1 | +import Mathlib.Tactic |
| 2 | +import Mathlib.Combinatorics.SimpleGraph.Hamiltonian |
| 3 | +import Mathlib.Combinatorics.Digraph.Orientation |
| 4 | +import Mathlib.Data.List.Basic |
| 5 | + |
| 6 | +open Function NNReal |
| 7 | +set_option linter.unusedVariables false |
| 8 | + |
| 9 | + |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | + |
| 14 | + |
| 15 | +/- |
| 16 | +# Announcement |
| 17 | +
|
| 18 | +Next week, there is an online conference, *Lean Together*. |
| 19 | +If you want to learn about the new things going |
| 20 | +on in the Lean community, you can attend some of the talks: |
| 21 | +https://leanprover-community.github.io/lt2026/schedule.html |
| 22 | +
|
| 23 | +# Last time |
| 24 | +
|
| 25 | +Clean code and formalisation best practices |
| 26 | +
|
| 27 | +- Code style: a coherent style increases readability |
| 28 | +- Linters: automatic checkers for style and other issues |
| 29 | + - syntax linters run as you type |
| 30 | + - environment linters run when you write `#lint` |
| 31 | + (at the bottom of your file) |
| 32 | +- Golfing: making proofs shorter |
| 33 | + (which can make proofs more readable, but not if you overdo it) |
| 34 | +- Profiling to identify slow parts of your proof |
| 35 | +
|
| 36 | +# Today: graph theory in Lean |
| 37 | +-/ |
| 38 | + |
| 39 | + |
| 40 | +/-! |
| 41 | +**What is a graph?** |
| 42 | +
|
| 43 | +A graph has a collection of vertices and |
| 44 | +a set of edges between these vertices. |
| 45 | +
|
| 46 | +There are many variations on graphs. |
| 47 | +Questions: |
| 48 | +1. Are the edges directed? |
| 49 | +2. Can there be multiple edges between two vertices? |
| 50 | +3. Can there be loops? |
| 51 | +
|
| 52 | +
|
| 53 | +
|
| 54 | +Mathlib has multiple notions of graphs: |
| 55 | +* A `Quiver` is a directed multigraph |
| 56 | + (answers "yes" to all questions) |
| 57 | +* A `Digraph` is a directed graph |
| 58 | + ("no" to question 2, "yes" to 1 and 3) |
| 59 | +* A `SimpleGraph` answers "no" to all questions. |
| 60 | +
|
| 61 | +How do we define these notions? |
| 62 | +-/ |
| 63 | + |
| 64 | +structure MyQuiver (V : Type*) where |
| 65 | + Hom : V → V → Sort* |
| 66 | + |
| 67 | +#check MyQuiver |
| 68 | +#check MyQuiver.Hom |
| 69 | + |
| 70 | +structure MyDigraph (V : Type*) where |
| 71 | + Adj : V → V → Prop |
| 72 | + |
| 73 | +#check SimpleGraph |
| 74 | + |
| 75 | +example : SimpleGraph ℕ where |
| 76 | + Adj u v := u ≠ v |
| 77 | + -- `aesop_graph` was able to automatically prove symmetry and irreflexivity |
| 78 | + |
| 79 | + |
| 80 | + |
| 81 | +/- |
| 82 | +Note that `SimpleGraph` is a structure, not a class. |
| 83 | +This makes it easier to talk about |
| 84 | +multiple simple graphs on the same type. |
| 85 | +-/ |
| 86 | + |
| 87 | + |
| 88 | +namespace SimpleGraph |
| 89 | +variable {α V : Type*} (G : SimpleGraph V) |
| 90 | +/-! |
| 91 | +Let's focus on simple graphs. |
| 92 | +
|
| 93 | +Set-theoretically, undirected graphs are most easily defined |
| 94 | +using unordered pairs `{x, y}` as edges. |
| 95 | +
|
| 96 | +In Lean, we can define unordered pairs in `α` |
| 97 | +as a quotient of `α × α`. |
| 98 | +-/ |
| 99 | + |
| 100 | +inductive UnorderedPair.Rel (α : Type*) : α × α → α × α → Prop |
| 101 | + | refl (x y : α) : Rel α (x, y) (x, y) |
| 102 | + | swap (x y : α) : Rel α (x, y) (y, x) |
| 103 | + |
| 104 | +def UnorderedPair.setoid (α : Type*) : Setoid (α × α) := |
| 105 | + ⟨UnorderedPair.Rel α, sorry⟩ |
| 106 | + |
| 107 | +def UnorderedPair (α : Type*) := Quotient (UnorderedPair.setoid α) |
| 108 | + |
| 109 | +/-! The following definition is equivalent, |
| 110 | +and is called `Sym2` in Mathlib -/ |
| 111 | +def UnorderedPair' (α : Type*) := Quot (UnorderedPair.Rel α) |
| 112 | + |
| 113 | +/-! The notation for an unordered pair is `s(x, y)` |
| 114 | +(instead of `{x, y}`). -/ |
| 115 | +example (x y : α) : Sym2 α := s(x, y) |
| 116 | + |
| 117 | +example {x y : α} : s(x, y) = s(y, x) := |
| 118 | + Sym2.eq_swap |
| 119 | + |
| 120 | +example {x y u v : α} : s(x, y) = s(u, v) ↔ |
| 121 | + (x = u ∧ y = v) ∨ (x = v ∧ y = u) := |
| 122 | + Sym2.eq_iff |
| 123 | + |
| 124 | + |
| 125 | +/-! A `SimpleGraph` can equivalently be defined as |
| 126 | +a collection of unordered pairs without singletons |
| 127 | +`s(x, x)`. -/ |
| 128 | +example (S : Set (Sym2 V)) : SimpleGraph V := |
| 129 | + fromEdgeSet S |
| 130 | + |
| 131 | +example (G : SimpleGraph V) : Set (Sym2 V) := |
| 132 | + edgeSet G |
| 133 | + |
| 134 | +/-- A *dart* or *oriented edge* is |
| 135 | +a pair of adjacent vertices in the graph `G`. -/ |
| 136 | +structure MyDart (G : SimpleGraph V) extends V × V where |
| 137 | + adj : G.Adj fst snd |
| 138 | + |
| 139 | +/-- A *walk* (sometimes also called *path*) |
| 140 | +is a sequence of adjacent vertices. -/ |
| 141 | +inductive MyWalk (G : SimpleGraph V) : V → V → Type _ |
| 142 | + | nil {u : V} : MyWalk G u u |
| 143 | + | cons {u v w : V} (h : G.Adj u v) (p : MyWalk G v w) : MyWalk G u w |
| 144 | + |
| 145 | + |
| 146 | +/-! Given a walk, we can define the vertices, |
| 147 | +darts and edges it visits in order, as a list. -/ |
| 148 | +def MyWalk.support {u v : V} : G.MyWalk u v → List V |
| 149 | + | nil (u := u) => [u] |
| 150 | + | cons _ p => u :: p.support |
| 151 | + |
| 152 | +def MyWalk.darts {u v : V} : G.MyWalk u v → List G.Dart |
| 153 | + | nil => [] |
| 154 | + | cons h p => ⟨(u, _), h⟩ :: p.darts |
| 155 | + |
| 156 | +def edges {u v : V} (p : G.MyWalk u v) : List (Sym2 V) := p.darts.map Dart.edge |
| 157 | + |
| 158 | +/-- A walk is a *path* if no vertex is visited twice. -/ |
| 159 | +structure MyWalk.IsPath {u v : V} (p : G.MyWalk u v) : Prop where |
| 160 | + support_nodup : p.support.Nodup |
| 161 | + |
| 162 | +/-- A walk is a *cycle* if it ends at the starting point, and if we |
| 163 | + remove the last edge from the walk, we get a path. -/ |
| 164 | +structure MyWalk.IsCycle {u : V} (p : G.MyWalk u u) : Prop where |
| 165 | + ne_nil : p ≠ nil |
| 166 | + support_nodup : p.support.tail.Nodup |
| 167 | + |
| 168 | + |
| 169 | +/-- Subgraphs might want to restrict both |
| 170 | +the vertex set and the edge set. |
| 171 | +-/ |
| 172 | +structure MySubgraph (G : SimpleGraph V) where |
| 173 | + /-- Vertices of the subgraph -/ |
| 174 | + verts : Set V |
| 175 | + /-- Edges of the subgraph -/ |
| 176 | + Adj : V → V → Prop |
| 177 | + adj_sub : ∀ {v w : V}, Adj v w → G.Adj v w |
| 178 | + edge_vert : ∀ {v w : V}, Adj v w → v ∈ verts |
| 179 | + symm : Symmetric Adj |
| 180 | + |
| 181 | +end SimpleGraph |
| 182 | + |
| 183 | +/- |
| 184 | +# Algorithms on graphs |
| 185 | +
|
| 186 | +As an example, we will discuss Dijkstra's algorithm. |
| 187 | +It takes a graph where every vertex `(u, v)` has a weight `w(u, v)` |
| 188 | +and a starting vertex, |
| 189 | +and computes the shortest path from the starting vertex |
| 190 | +to any other vertex. |
| 191 | +
|
| 192 | +We do this by keeping track of a computed distance `d(v)` for each edge `v`. |
| 193 | +Initially, the computed distance is 0 for the starting vertex and `∞` |
| 194 | +for all others. |
| 195 | +At each step: |
| 196 | +- we visit an unvisited vertex `u` with minimal computed distance |
| 197 | +- we iterate over all `u`'s neighbors `v` |
| 198 | +- we set `d(v)` to the minimum of `d(v)` and `d(u) + w(u, v)` |
| 199 | +-/ |
| 200 | + |
| 201 | +structure WeightedGraph (V : Type*) extends SimpleGraph V where |
| 202 | + weight : V × V → ℚ≥0 |
| 203 | + weight_symm (v w : V) : weight (v, w) = weight (w, v) |
| 204 | + |
| 205 | +variable |
| 206 | + {V : Type} [Fintype V] [Encodable V] [DecidableEq V] |
| 207 | + {G : WeightedGraph V} {u v : V} [DecidableRel G.Adj] |
| 208 | + |
| 209 | +/-- The length of a walk is the sum of the length of its edges. -/ |
| 210 | +def pathLength (p : G.Walk u v) : ℚ≥0 := |
| 211 | + List.sum (p.darts.map (fun d ↦ G.weight d.toProd)) |
| 212 | + |
| 213 | +namespace Dijkstra |
| 214 | +open Encodable |
| 215 | + |
| 216 | +structure State (G : WeightedGraph V) where |
| 217 | + /-- starting node -/ |
| 218 | + start : V |
| 219 | + /-- computed distance to the starting node -/ |
| 220 | + dist : V → WithTop ℚ≥0 |
| 221 | + /-- unvisited edges -/ |
| 222 | + unvisited : List V |
| 223 | +deriving Inhabited |
| 224 | + |
| 225 | +def State.initial (start : V) : State G where |
| 226 | + start := start |
| 227 | + dist v := if v = start then 0 else ⊤ |
| 228 | + unvisited := Encodable.sortedUniv V |
| 229 | + |
| 230 | +def dijkstraStep (S : State G) : Option (State G) := do |
| 231 | + -- We find the unvisted vertex with the minimal distance |
| 232 | + -- (and halt if there is no unvisited vertex) |
| 233 | + let some (v : V) := S.unvisited.argmin S.dist | failure |
| 234 | + if S.dist v = ⊤ then |
| 235 | + failure |
| 236 | + let newDist (w : V) : WithTop ℚ≥0 := |
| 237 | + if G.Adj v w then min (S.dist w) (S.dist v + G.weight (v, w)) else S.dist w |
| 238 | + let newState : State G := { |
| 239 | + start := S.start |
| 240 | + dist := newDist |
| 241 | + unvisited := S.unvisited.filter (· ≠ v) |
| 242 | + } |
| 243 | + return newState |
| 244 | + |
| 245 | + |
| 246 | +/- Now we iterate this |V| many steps. -/ |
| 247 | +variable (G) in |
| 248 | +def dijkstra (start : V) : State G := Id.run do |
| 249 | + let mut S : State G := State.initial start |
| 250 | + for _ in List.range (Fintype.card V) do |
| 251 | + match dijkstraStep S with |
| 252 | + | none => break |
| 253 | + | some S' => S := S' |
| 254 | + return S |
| 255 | + |
| 256 | + |
| 257 | +/- Let's try this on an example graph, where the graph is just a chain. -/ |
| 258 | + |
| 259 | +abbrev finNGraph (n : ℕ) : WeightedGraph (Fin n) where |
| 260 | + Adj u v := |(u : ℤ) - (v : ℤ)| = 1 |
| 261 | + symm := sorry |
| 262 | + loopless := sorry |
| 263 | + weight := fun (u, v) ↦ ⟨(1 : ℚ) / max u v, sorry⟩ |
| 264 | + weight_symm := sorry |
| 265 | + |
| 266 | +#eval! ((dijkstra (finNGraph 5) 0).dist 4).get! |
| 267 | + |
| 268 | + |
| 269 | +/- We could now prove correctness of this algorithm. -/ |
| 270 | +lemma dijkstra_correct (start v : V) : |
| 271 | + IsGLB (Set.range (fun p ↦ pathLength p : G.Walk start v → WithTop ℚ≥0)) |
| 272 | + ((dijkstra G start).dist v) := by |
| 273 | + sorry |
| 274 | + |
| 275 | +end Dijkstra |
0 commit comments