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