Spaces:
Sleeping
Sleeping
File size: 5,569 Bytes
bf9c466 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | -- 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
|