Skip to content

Commit 9ea21c7

Browse files
committed
add lecture 22 after class
1 parent 2c7b477 commit 9ea21c7

1 file changed

Lines changed: 275 additions & 0 deletions

File tree

Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
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

Comments
 (0)