Spaces:
Sleeping
Sleeping
| -- File: lean/ShortestPathSpec.lean | |
| -- Shortest Path (Dijkstra/Bellman-Ford) Specification | |
| -- Agents migrate a C++ Dijkstra to Rust. | |
| -- Graph: List (from, to, weight) with non-negative Nat weights. | |
| namespace ShortestPathSpec | |
| abbrev Graph := List (Nat × Nat × Nat) -- (from, to, weight) | |
| -- Safe index into a list (returns none if out of bounds) | |
| def listGet (l : List α) (i : Nat) : Option α := | |
| (l.drop i).head? | |
| -- Update distance at index i in the distance list | |
| def updateDist (dists : List (Option Nat)) (i d : Nat) : List (Option Nat) := | |
| List.zipWith | |
| (fun j curr => | |
| if j == i then | |
| match curr with | |
| | none => some d | |
| | some existing => some (min existing d) | |
| else curr) | |
| (List.range dists.length) | |
| dists | |
| -- Relax one edge (fr → to with weight w) | |
| def relaxEdge (dists : List (Option Nat)) (fr to w : Nat) : List (Option Nat) := | |
| match listGet dists fr with | |
| | some (some df) => updateDist dists to (df + w) | |
| | _ => dists | |
| -- One Bellman-Ford pass over all edges | |
| def relaxPass (dists : List (Option Nat)) (edges : Graph) : List (Option Nat) := | |
| edges.foldl (fun d e => relaxEdge d e.1 e.2.1 e.2.2) dists | |
| -- Dijkstra via n-1 Bellman-Ford iterations (correct for non-negative weights) | |
| def dijkstra (graph : Graph) (n src : Nat) : List (Option Nat) := | |
| let init := (List.range n).map (fun i => if i == src then some 0 else none) | |
| (List.range (n - 1)).foldl (fun d _ => relaxPass d graph) init | |
| -- Shortest distance between two nodes | |
| def shortestDist (graph : Graph) (n src dst : Nat) : Option Nat := | |
| match listGet (dijkstra graph n src) dst with | |
| | some (some d) => some d | |
| | _ => none | |
| -- listGet via getElem? | |
| theorem listGet_eq (l : List α) (i : Nat) : | |
| listGet l i = l[i]? := List.head?_drop | |
| -- (List.range n)[src]? = some src when src < n | |
| theorem range_getElem? (src n : Nat) (h : src < n) : | |
| (List.range n)[src]? = some src := by | |
| induction n generalizing src with | |
| | zero => omega | |
| | succ n ih => | |
| rw [List.range_succ] | |
| by_cases hlt : src < n | |
| · rw [List.getElem?_append_left (by simp [List.length_range, hlt])] | |
| exact ih src hlt | |
| · have heq : src = n := Nat.le_antisymm (Nat.lt_succ_iff.mp h) (by omega) | |
| subst heq | |
| rw [List.getElem?_append_right (by simp [List.length_range])] | |
| simp [Nat.sub_self] | |
| -- Initial dist list has some (some 0) at src | |
| theorem listGet_init (n src : Nat) (h : src < n) : | |
| listGet ((List.range n).map (fun i => if i == src then some 0 else none)) src = | |
| some (some 0) := by | |
| rw [listGet_eq, List.getElem?_map, range_getElem? src n h] | |
| simp | |
| -- updateDist preserves some (some 0) at position src | |
| theorem updateDist_preserves_zero (dists : List (Option Nat)) (i d src : Nat) | |
| (h : listGet dists src = some (some 0)) : | |
| listGet (updateDist dists i d) src = some (some 0) := by | |
| rw [listGet_eq] at h ⊢ | |
| have hlen : src < dists.length := by | |
| rcases Nat.lt_or_ge src dists.length with lt | ge | |
| · exact lt | |
| · exact absurd h (by simp [List.getElem?_eq_none ge]) | |
| unfold updateDist | |
| rw [List.getElem?_zipWith, range_getElem? src dists.length hlen, h] | |
| -- Goal: some ((fun j curr => if j==i then … else curr) src (some 0)) = some (some 0) | |
| simp only -- beta-reduce | |
| by_cases h_eq : i = src | |
| · subst h_eq; simp | |
| · simp [beq_eq_false_iff_ne.mpr (Ne.symm h_eq)] | |
| -- relaxEdge preserves some (some 0) at src | |
| theorem relaxEdge_preserves_zero (dists : List (Option Nat)) (fr to w src : Nat) | |
| (h : listGet dists src = some (some 0)) : | |
| listGet (relaxEdge dists fr to w) src = some (some 0) := by | |
| unfold relaxEdge | |
| split | |
| · exact updateDist_preserves_zero dists to _ src h | |
| · exact h | |
| -- relaxPass preserves some (some 0) at src | |
| theorem relaxPass_preserves_zero (dists : List (Option Nat)) (edges : Graph) (src : Nat) | |
| (h : listGet dists src = some (some 0)) : | |
| listGet (relaxPass dists edges) src = some (some 0) := by | |
| unfold relaxPass | |
| induction edges generalizing dists with | |
| | nil => simpa | |
| | cons e rest ih => | |
| simp only [List.foldl_cons] | |
| exact ih _ (relaxEdge_preserves_zero dists e.1 e.2.1 e.2.2 src h) | |
| -- foldl of relaxPass preserves the invariant (generalizing over init) | |
| theorem foldl_relaxPass_preserves_zero | |
| (rounds : Nat) (graph : Graph) (src : Nat) (init : List (Option Nat)) | |
| (h : listGet init src = some (some 0)) : | |
| listGet ((List.range rounds).foldl (fun d _ => relaxPass d graph) init) src = | |
| some (some 0) := by | |
| induction rounds generalizing init with | |
| | zero => simpa | |
| | succ k ih => | |
| rw [List.range_succ, List.foldl_append] | |
| simp only [List.foldl_cons, List.foldl_nil] | |
| exact relaxPass_preserves_zero _ graph src (ih _ h) | |
| -- Self-distance is always 0 for valid source nodes | |
| theorem self_distance_zero (graph : Graph) (n src : Nat) (h : src < n) : | |
| shortestDist graph n src src = some 0 := by | |
| let init := (List.range n).map (fun i => if i == src then some 0 else none) | |
| unfold shortestDist | |
| rw [show dijkstra graph n src = | |
| (List.range (n - 1)).foldl (fun d _ => relaxPass d graph) init from rfl, | |
| foldl_relaxPass_preserves_zero (n - 1) graph src init (listGet_init n src h)] | |
| -- DRT checks | |
| example : shortestDist [(0, 1, 5), (1, 2, 3)] 3 0 2 = some 8 := by native_decide | |
| example : shortestDist [(0, 1, 5)] 3 0 2 = none := by native_decide | |
| example : shortestDist [] 3 0 0 = some 0 := by native_decide | |
| example : shortestDist [(0, 1, 1), (0, 2, 4), (1, 2, 2)] 3 0 2 = some 3 := by native_decide | |
| example : dijkstra [(0, 1, 5), (1, 2, 3)] 3 0 = [some 0, some 5, some 8] := by native_decide | |
| end ShortestPathSpec | |