Imports
import Mathlib
import CLRSLean.FourthEdition.Chapter_22.Section_22_1_Bellman_Ford
import CLRSLean.FourthEdition.Chapter_22.Section_22_3_Dijkstra23.3. Johnson's Algorithm for Sparse Graphs
Johnson's algorithm computes all-pairs shortest paths in a weighted directed graph with no negative-weight cycles. It works in three stages:
-
Potential via Bellman-Ford: add a new source vertex
swith zero-weight edges to every vertex, run Bellman-Ford froms, and seth(v) = delta(s, v). This construction assumes globalNoNegCycle; it does not return a negative-cycle failure or implement an abort branch. -
Reweighting: define a new weight function
w^(u,v) = w(u,v) + h(u) - h(v). By the triangle inequality of shortest paths,w^ >= 0for every edge, so Dijkstra's algorithm can be used from every vertex. -
|V| x Dijkstra: run Dijkstra from every vertex on the reweighted graph and recover the true distances via
delta(u,v) = delta^(u,v) - h(u) + h(v).
Main results:
-
Theorem
reweightedWeight_nonneg: reweighted edge weights are nonnegative. -
Theorem
reweightedWalkWeight_eq:w^(p) = w(p) + h(u) - h(v)for any walkpfromutov(telescoping property). -
Theorem
reweighted_isShortestDist: reweighted shortest distances equal original distances shifted byh(u) - h(v). -
Lemma
noNegCycle_johnsonAugmentedGraph: the augmented graph has no negative cycles iff the original graph has none. -
Lemma
johnsonPotential_triangle: the Bellman-Ford potential satisfiesh(v) ≤ h(u) + w(u, v)for every edge(u, v). -
Theorem
johnsonDist_isShortestDist: CLRS Theorem 23.5 — end-to-end correctness of Johnson's algorithm:johnsonDistcomputes the exact all-pairs shortest-path distances. -
Lemma
johnsonAugmentedGraph_edges_card: the augmented graph has|V| + |E|edges. -
Theorem
johnsonCost_eq: an independent heap-backend budget|V|·(|V| + |E|)·(log₂|V| + 2). This formula alone does not count a concrete queue or stored distance table. -
Theorem
johnsonCost_le: the2·|V|·(|V| + |E|)·(log₂|V| + 1)O-bound corollary.
The section is complete: the augmented-graph potential construction, triangle inequality, reweighting nonnegativity, end-to-end Johnson correctness, and the running-time bound are all proved.
namespace CLRSnamespace Chapter24open Finsetnamespace WeightedGraphvariable {V : Type*} [Fintype V] [DecidableEq V] (G : WeightedGraph V)Johnson's algorithm preliminary definitions
The augmented graph for Johnson's algorithm: add a fresh source vertex
none with zero-weight edges to every original vertex.
All original edges are preserved, with the original weights. There are no
edges entering none, so any negative cycle in the original graph remains a
negative cycle (and no new negative cycles are introduced).
noncomputable def johnsonAugmentedGraph : WeightedGraph (Option V) :=
{ edges := (Finset.image (fun (v : V) => (none, some v)) Finset.univ) ∪
(Finset.image (fun ((u, v) : V × V) => (some u, some v)) G.edges)
, w := fun u v =>
match u, v with
| none, some v => 0
| some u, some v => G.w u v
| _, _ => 0
}@[simp] theorem mem_edges_johnsonAugmentedGraph_source (v : V) :
(none, some v) ∈ G.johnsonAugmentedGraph.edges := by
unfold johnsonAugmentedGraph; simp@[simp] theorem mem_edges_johnsonAugmentedGraph_edge (u v : V) :
(some u, some v) ∈ G.johnsonAugmentedGraph.edges ↔ (u, v) ∈ G.edges := by
unfold johnsonAugmentedGraph; simp
There are no edges entering none in the augmented graph.
theorem no_incoming_to_none_johnsonAugmentedGraph (u : Option V) :
(u, none) ∉ G.johnsonAugmentedGraph.edges := by
unfold johnsonAugmentedGraph; simpReweighting with a potential function
The reweighted weight function w^(u,v) = w(u,v) + h(u) - h(v), where
h : V -> RR is a potential function (typically delta(s, .) from Bellman-Ford).
def reweightedWeight (h : V → ℝ) (u v : V) : ℝ :=
G.w u v + h u - h v@[simp] theorem reweightedWeight_eq (h : V → ℝ) (u v : V) :
G.reweightedWeight h u v = G.w u v + h u - h v := rfl
The reweighted graph G^ has the same edge set as G but with the
reweighted weight function w^.
noncomputable def reweightedGraph (h : V → ℝ) : WeightedGraph V :=
{ edges := G.edges
, w := G.reweightedWeight h
}@[simp] theorem edges_reweightedGraph (h : V → ℝ) :
(G.reweightedGraph h).edges = G.edges := rfl@[simp] theorem w_reweightedGraph (h : V → ℝ) (u v : V) :
(G.reweightedGraph h).w u v = G.reweightedWeight h u v := rfl
Telescoping property. For any walk p from u to v, the reweighted
walk weight equals the original walk weight plus h(u) - h(v).
theorem reweightedWalkWeight_eq (h : V → ℝ) (u v : V) (p : List V)
(hp : G.IsWalkFrom u v p) : walkWeight (G.reweightedWeight h) p = walkWeight G.w p + h u - h v := by
induction p generalizing u v with
| nil => exact absurd rfl hp.ne_nil
| cons a as ih =>
have ha_u : a = u := by
have hh := hp.head; simpa using hh
rw [ha_u]
rw [ha_u] at hp
cases as with
| nil =>
have hv_u : v = u := by
have hl := hp.last; simp at hl; exact hl.symm
rw [hv_u]; simp [walkWeight, reweightedWeight]
| cons b bs =>
have h_chain : List.IsChain G.Adj (u :: b :: bs) := hp.chain
have h_chain_rest : List.IsChain G.Adj (b :: bs) := by
cases h_chain with
| cons_cons _ htail => exact htail
have h_walk_rest : G.IsWalkFrom b v (b :: bs) :=
⟨h_chain_rest, by simp, by simpa using hp.last⟩
simp [walkWeight, reweightedWeight]
rw [ih b v h_walk_rest]
ring
Nonnegativity of reweighted weights. If the potential h satisfies
the triangle inequality h(v) <= h(u) + w(u, v) for every edge (u, v), then
every reweighted edge weight is nonnegative.
theorem reweightedWeight_nonneg (h : V → ℝ)
(h_triangle : ∀ u v, (u, v) ∈ G.edges → h v ≤ h u + G.w u v) :
∀ u v, (u, v) ∈ G.edges → 0 ≤ G.reweightedWeight h u v := by
intro u v h_edge
dsimp [reweightedWeight]
have hineq : h v ≤ h u + G.w u v := h_triangle u v h_edge
linarithWalk equivalence: original graph ↔ reweighted graph
lemma IsWalkFrom_reweighted_iff (h : V → ℝ) (u v : V) (p : List V) :
(G.reweightedGraph h).IsWalkFrom u v p ↔ G.IsWalkFrom u v p := by
have h_adj_eq : (G.reweightedGraph h).Adj = G.Adj := by
ext x y; simp [WeightedGraph.Adj, edges_reweightedGraph]
constructor
· intro hw; rcases hw with ⟨hc, hh, hl⟩; refine ⟨?_, hh, hl⟩; rw [← h_adj_eq]; exact hc
· intro hw; rcases hw with ⟨hc, hh, hl⟩; refine ⟨?_, hh, hl⟩; rw [h_adj_eq]; exact hcLift telescoping property to WithTop ℝ
lemma reweightedWalkWeight_eq_withtop (h : V → ℝ) (u v : V) (p : List V)
(hp : G.IsWalkFrom u v p) :
(walkWeight (G.reweightedWeight h) p : WithTop ℝ) =
(walkWeight G.w p : WithTop ℝ) + (h u : WithTop ℝ) - (h v : WithTop ℝ) := by
have h_rw := reweightedWalkWeight_eq G h u v p hp
calc
(walkWeight (G.reweightedWeight h) p : WithTop ℝ) =
((walkWeight G.w p + h u - h v : ℝ) : WithTop ℝ) := by exact_mod_cast h_rw
_ = (walkWeight G.w p : WithTop ℝ) + (h u : WithTop ℝ) - (h v : WithTop ℝ) := by simpWithTop helpers
private lemma add_sub_cancel {a b c : WithTop ℝ} (hc : c ≠ ⊤) (h : a + c ≤ b + c) : a ≤ b :=
(WithTop.add_le_add_iff_right hc).mp h
private lemma add_sub_assoc {a b c : WithTop ℝ} (hb : b ≠ ⊤) (hc : c ≠ ⊤) :
(a + b) - c = a + (b - c) := by
have ⟨b', hb'⟩ := Option.ne_none_iff_exists'.mp hb
have ⟨c', hc'⟩ := Option.ne_none_iff_exists'.mp hc
subst hb' hc'
by_cases ha : a = ⊤
· subst ha
calc
((⊤ : WithTop ℝ) + (b' : WithTop ℝ)) - (c' : WithTop ℝ) = (⊤ : WithTop ℝ) - (c' : WithTop ℝ) := by simp
_ = (⊤ : WithTop ℝ) := by simp
_ = (⊤ : WithTop ℝ) + ((b' : WithTop ℝ) - (c' : WithTop ℝ)) := by rw [WithTop.top_add]
· have ⟨a', ha'⟩ := Option.ne_none_iff_exists'.mp ha
subst ha'
-- ℝ equality: (a'+b'-c') = (a'+(b'-c')) in ℝ, lifted to WithTop ℝ
calc
((a' : ℝ) : WithTop ℝ) + ((b' : ℝ) : WithTop ℝ) - ((c' : ℝ) : WithTop ℝ) =
(((a' + b' - c' : ℝ) : ℝ) : WithTop ℝ) := by simp
_ = (((a' + (b' - c') : ℝ) : ℝ) : WithTop ℝ) := by rw [show (a' + b' - c' : ℝ) = (a' + (b' - c') : ℝ) by ring]
_ = ((a' : ℝ) : WithTop ℝ) + (((b' : ℝ) : WithTop ℝ) - ((c' : ℝ) : WithTop ℝ)) := by simpShortest-path preservation under reweighting
Shortest-path preservation. For any potential h, the reweighted
shortest distance equals the original shortest distance shifted by
h(u) - h(v). This holds without any feasibility assumption on h.
theorem reweighted_isShortestDist (h : V → ℝ) (u v : V) (d : WithTop ℝ) :
(G.reweightedGraph h).IsShortestDist u v
(d + (h u : WithTop ℝ) - (h v : WithTop ℝ)) ↔
G.IsShortestDist u v d := by
have h_fin_hu : (h u : WithTop ℝ) ≠ ⊤ := by simp
have h_fin_hv : (h v : WithTop ℝ) ≠ ⊤ := by simp
have h_fin_diff : ((h u : WithTop ℝ) - (h v : WithTop ℝ)) ≠ ⊤ := by simp
have h_walk_iff := IsWalkFrom_reweighted_iff G h
have h_rw_eq := reweightedWalkWeight_eq_withtop G h
constructor
· intro h_rsd; rcases h_rsd with ⟨h_lower, h_att⟩; constructor
· intro p hp
have hp_hat : (G.reweightedGraph h).IsWalkFrom u v p := (h_walk_iff u v p).mpr hp
have h_bound := h_lower p hp_hat
have h_rw' := h_rw_eq u v p hp
have h_bound' : d + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) ≤
(walkWeight G.w p : WithTop ℝ) + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) := by
-- Regroup using add_sub_assoc and use h_bound + h_rw'
simpa [add_sub_assoc h_fin_hu h_fin_hv] using
calc
d + (h u : WithTop ℝ) - (h v : WithTop ℝ) ≤
(walkWeight (G.reweightedWeight h) p : WithTop ℝ) := h_bound
_ = (walkWeight G.w p : WithTop ℝ) + (h u : WithTop ℝ) - (h v : WithTop ℝ) := h_rw'
exact add_sub_cancel h_fin_diff h_bound'
· rcases h_att with (h_dtop | ⟨p, hp_hat, hpw⟩)
· -- h_dtop: (d + h_u - h_v) = ⊤ in reweighted graph
-- Need: d = ⊤ in original graph. Since h_u, h_v are finite,
-- (d + h_u) - h_v = ⊤ implies d + h_u = ⊤, which implies d = ⊤.
by_cases hd_top : d = ⊤
· exact Or.inl hd_top
· exfalso
-- d ≠ ⊤, h_u ≠ ⊤, h_v ≠ ⊤ → d + h_u - h_v ≠ ⊤, contradicting h_dtop
have h_sum_fin : d + (h u : WithTop ℝ) - (h v : WithTop ℝ) ≠ ⊤ := by
-- All three are finite ℝ values, so the sum is finite
-- Proof: case analysis to extract the ℝ values
rcases Option.ne_none_iff_exists'.mp hd_top with ⟨d', hd'⟩
rcases Option.ne_none_iff_exists'.mp h_fin_hu with ⟨hu', hhu'⟩
rcases Option.ne_none_iff_exists'.mp h_fin_hv with ⟨hv', hhv'⟩
rw [hd', hhu', hhv']
simp
exact h_sum_fin h_dtop
· right
have hp : G.IsWalkFrom u v p := (h_walk_iff u v p).mp hp_hat
have h_rw' := h_rw_eq u v p hp
have hpw' : (walkWeight G.w p : WithTop ℝ) = d := by
have h_eq : (walkWeight G.w p : WithTop ℝ) + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) =
d + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) := by
calc
(walkWeight G.w p : WithTop ℝ) + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) =
(walkWeight G.w p : WithTop ℝ) + (h u : WithTop ℝ) - (h v : WithTop ℝ) := by
rw [add_sub_assoc h_fin_hu h_fin_hv]
_ = (walkWeight (G.reweightedWeight h) p : WithTop ℝ) := by symm; exact h_rw'
_ = d + (h u : WithTop ℝ) - (h v : WithTop ℝ) := hpw
_ = d + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) := by
rw [add_sub_assoc h_fin_hu h_fin_hv]
apply le_antisymm
· apply add_sub_cancel h_fin_diff; exact h_eq.le
· apply add_sub_cancel h_fin_diff; exact h_eq.ge
refine ⟨p, hp, hpw'⟩
· intro h_sd; rcases h_sd with ⟨h_lower, h_att⟩; constructor
· intro p hp_hat
have hp : G.IsWalkFrom u v p := (h_walk_iff u v p).mp hp_hat
have h_rw' := h_rw_eq u v p hp
rw [add_sub_assoc h_fin_hu h_fin_hv]
have h_add_both : d + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) ≤
(walkWeight G.w p : WithTop ℝ) + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) := by
gcongr; exact h_lower p hp
calc
d + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) ≤
(walkWeight G.w p : WithTop ℝ) + ((h u : WithTop ℝ) - (h v : WithTop ℝ)) := h_add_both
_ = (walkWeight G.w p : WithTop ℝ) + (h u : WithTop ℝ) - (h v : WithTop ℝ) := by
rw [add_sub_assoc h_fin_hu h_fin_hv]
_ = (walkWeight (G.reweightedWeight h) p : WithTop ℝ) := h_rw'.symm
· rcases h_att with (h_dtop | ⟨p, hp, hpw⟩)
· -- h_dtop: d = ⊤ in original graph
-- Need: d + h_u - h_v = ⊤ in reweighted graph
refine Or.inl ?_
rw [h_dtop]; simp
· right
have hp_hat : (G.reweightedGraph h).IsWalkFrom u v p := (h_walk_iff u v p).mpr hp
have h_rw' := h_rw_eq u v p hp
refine ⟨p, hp_hat, ?_⟩
calc
(walkWeight (G.reweightedWeight h) p : WithTop ℝ) =
(walkWeight G.w p : WithTop ℝ) + (h u : WithTop ℝ) - (h v : WithTop ℝ) := h_rw'
_ = d + (h u : WithTop ℝ) - (h v : WithTop ℝ) := by rw [hpw]Negative-cycle equivalence for the augmented graph
No edge in johnsonAugmentedGraph targets none.
lemma adj_target_ne_none (u v : Option V)
(h_adj : G.johnsonAugmentedGraph.Adj u v) : v ≠ none := by
intro h_eq; subst h_eq
have h_edge : (u, none) ∈ G.johnsonAugmentedGraph.edges := h_adj
exact G.no_incoming_to_none_johnsonAugmentedGraph u h_edge
If a chain in johnsonAugmentedGraph does not start at none, then
none never appears in the chain.
lemma chain_no_none (l : List (Option V))
(h_chain : List.IsChain G.johnsonAugmentedGraph.Adj l)
(h_head_ne_none : l.head? ≠ some none) : none ∉ l := by
induction l with
| nil => simp
| cons x l' ih =>
rw [List.isChain_cons] at h_chain
rcases h_chain with ⟨h_adj, h_chain_tail⟩
have hx_ne_none : x ≠ none := by
intro h_eq; subst h_eq; apply h_head_ne_none; simp
have h_l'_head_ne_none : l'.head? ≠ some none := by
intro h_eq
have h_mem : none ∈ l'.head? := by rw [h_eq]; simp
have h_adj_x_none : G.johnsonAugmentedGraph.Adj x none := h_adj none h_mem
exact adj_target_ne_none G x none h_adj_x_none rfl
have h_none_notin_l' : none ∉ l' := ih h_chain_tail h_l'_head_ne_none
intro h
cases h with
| head _ => exact hx_ne_none rfl
| tail _ h_mem => exact h_none_notin_l' h_mem
The only walk from none to none in the augmented graph is [none].
lemma walk_from_none_to_none_singleton (c : List (Option V))
(hc : G.johnsonAugmentedGraph.IsWalkFrom none none c) : c = [none] := by
have h_last : c.getLast? = some none := hc.last
rcases List.getLast?_eq_some_iff.mp h_last with ⟨c₁, hc_eq⟩
subst hc_eq
by_cases h_c₁_empty : c₁ = []
· subst h_c₁_empty; simp
· have h_chain : List.IsChain G.johnsonAugmentedGraph.Adj (c₁ ++ [none]) := hc.chain
rw [List.isChain_append] at h_chain
rcases h_chain with ⟨_, _, h_adj_cond⟩
have h_c₁_last_some : c₁.getLast? ≠ none := by
intro h_eq; apply h_c₁_empty
exact (List.getLast?_eq_none_iff.mp h_eq)
rcases Option.ne_none_iff_exists'.mp h_c₁_last_some with ⟨last_c₁, h_last_c₁⟩
have h_adj_last_none : G.johnsonAugmentedGraph.Adj last_c₁ none :=
h_adj_cond last_c₁ (by rw [h_last_c₁]; simp) none (by simp)
exfalso
exact (adj_target_ne_none G last_c₁ none h_adj_last_none) rfl
Extend a walk by a single edge u → v at the end.
lemma IsWalkFrom.append_step (hp : G.IsWalkFrom s u p) (h_edge : G.Adj u v) :
G.IsWalkFrom s v (p ++ [v]) := by
have h_chain : List.IsChain G.Adj (p ++ [v]) :=
List.IsChain.append hp.chain (List.isChain_singleton v)
(by
intro x hx_last
-- hx_last : p.getLast? = some x (by definition of ∈ for Option)
rw [hp.last] at hx_last
-- hx_last : some u = some x
have hx_eq : x = u := (Option.some_inj.mp hx_last).symm
subst hx_eq
intro y hy_head
-- hy_head : [v].head? = some y (by definition of ∈ for Option)
-- [v].head? = some v (by simp)
have hy_eq : y = v := by
-- hy_head: [v].head? = some y, and [v].head? = some v
-- So some y = some v, hence y = v
have hh : [v].head? = some y := hy_head
have hv : [v].head? = some v := by simp
rw [hv] at hh
-- hh: some v = some y
exact (Option.some_inj.mp hh).symm
subst hy_eq
exact h_edge)
have h_head : (p ++ [v]).head? = some s := by
have hp_ne_nil : p ≠ [] := hp.ne_nil
have hp_head_val : p.head? = some s := hp.head
cases p with
| nil => exact absurd rfl hp_ne_nil
| cons a as =>
-- p = a :: as, so (a :: as ++ [v]).head? = some a
-- and hp.head gives some a = some s
have h_head_a : (a :: as).head? = some a := by simp
rw [h_head_a] at hp_head_val
-- hp_head_val : some a = some s
simp [hp_head_val]
have h_last : (p ++ [v]).getLast? = some v := by simp
exact ⟨h_chain, h_head, h_last⟩The weight of a walk extended by a single edge.
lemma walkWeight_append_step (hp : G.IsWalkFrom s u p) (h_edge : G.Adj u v) :
(walkWeight G.w (p ++ [v]) : WithTop ℝ) =
(walkWeight G.w p : WithTop ℝ) + (G.w u v : WithTop ℝ) := by
rcases List.getLast?_eq_some_iff.mp hp.last with ⟨q, hq⟩
subst hq
simp [walkWeight_concat G.w q u v]
An auxiliary function that strips Option.some from an element.
private def unsome (default : V) (x : Option V) : V :=
match x with | some v => v | none => default
For a none-free list, the chain in G' lifts to a chain in G.
private lemma chain_none_free_map (default : V) (l : List (Option V))
(h_chain : List.IsChain G.johnsonAugmentedGraph.Adj l)
(h_no_none : none ∉ l) :
List.IsChain G.Adj (l.map (unsome default)) := by
induction l with
| nil => exact List.IsChain.nil
| cons x l' ih =>
rw [List.isChain_cons] at h_chain
rcases h_chain with ⟨h_adj, h_chain_tail⟩
have hx_ne_none : x ≠ none := by
intro h_eq; subst h_eq; exact h_no_none (by simp)
have hl'_no_none : none ∉ l' := by
intro h; exact h_no_none (by simp [h])
-- Determine x (must be some a since ≠ none)
obtain ⟨a, hx_eq⟩ := Option.ne_none_iff_exists'.mp hx_ne_none
subst hx_eq
-- Now x = some a
have ih_map : List.IsChain G.Adj (l'.map (unsome default)) :=
ih h_chain_tail hl'_no_none
rw [show (some a :: l').map (unsome default) = a :: l'.map (unsome default) by
simp [unsome]]
apply List.IsChain.cons ih_map
intro y hy
-- hy : y ∈ (l'.map (unsome default)).head?
-- We need G.Adj a y. Analyze l'.
cases l' with
| nil => simp at hy
| cons z l'' =>
-- z must be some b (since none ∉ l')
have hz_ne_none : z ≠ none := by
intro h_eq; subst h_eq; exact hl'_no_none (by simp)
obtain ⟨b, hz_eq⟩ := Option.ne_none_iff_exists'.mp hz_ne_none
subst hz_eq
-- Now l' = some b :: l''
-- The head of (some b :: l'').map (unsome default) is b
have h_head_map : ((some b :: l'').map (unsome default)).head? = some b := by
simp [unsome]
rw [h_head_map] at hy
-- Now hy : y ∈ some b, i.e. some b = some y, so b = y
have hy_eq : b = y := Option.some_inj.mp hy
subst hy_eq
-- Need G.Adj a b. From h_adj: G'.Adj (some a) (some b)
have h_adj' : G.johnsonAugmentedGraph.Adj (some a) (some b) :=
h_adj (some b) (by simp)
exact (G.mem_edges_johnsonAugmentedGraph_edge a b).mp h_adj'
For a none-free list, the walk weights in G' and G agree.
private lemma walkWeight_none_free_eq (default : V) (c : List (Option V))
(h_no_none : none ∉ c) :
walkWeight G.johnsonAugmentedGraph.w c =
walkWeight G.w (c.map (unsome default)) := by
induction c with
| nil => simp [walkWeight]
| cons x cs ih =>
cases x with
| none => exfalso; exact h_no_none (by simp)
| some a =>
cases cs with
| nil =>
-- c = [some a]; both sides are 0
simp [walkWeight]
| cons y rest =>
have h_cs_no_none : none ∉ (y :: rest) := by
intro h; apply h_no_none; simp [h]
cases y with
| none => exfalso; exact h_cs_no_none (by simp)
| some b =>
-- c = some a :: some b :: rest
-- walkWeight G'.w c = G.w a b + walkWeight G'.w (some b :: rest)
-- walkWeight G.w (c.map f) = G.w a b + walkWeight G.w (b :: rest.map f)
have h_ih := ih h_cs_no_none
-- h_ih: walkWeight G'.w (some b :: rest) = walkWeight G.w ((some b :: rest).map (unsome default))
-- Compute RHS map:
have h_map_rest : ((some b :: rest).map (unsome default)) = b :: (rest.map (unsome default)) := by
simp [unsome]
rw [h_map_rest] at h_ih
-- Now h_ih: walkWeight G'.w (some b :: rest) = walkWeight G.w (b :: rest.map (unsome default))
-- Expand both sides using walkWeight formula
calc
walkWeight G.johnsonAugmentedGraph.w (some a :: some b :: rest)
= G.johnsonAugmentedGraph.w (some a) (some b) +
walkWeight G.johnsonAugmentedGraph.w (some b :: rest) := by simp [walkWeight]
_ = G.w a b + walkWeight G.johnsonAugmentedGraph.w (some b :: rest) := by
simp [johnsonAugmentedGraph]
_ = G.w a b + walkWeight G.w (b :: rest.map (unsome default)) := by rw [h_ih]
_ = walkWeight G.w (a :: b :: rest.map (unsome default)) := by simp [walkWeight]
_ = walkWeight G.w ((some a :: some b :: rest).map (unsome default)) := by
unfold unsome; simp
Project a none-free walk in G' to a walk in G with the same weight.
private lemma exists_walk_in_G_of_none_free_walk (x' : V) (c : List (Option V))
(hc : G.johnsonAugmentedGraph.IsWalkFrom (some x') (some x') c)
(h_no_none : none ∉ c) :
∃ (c' : List V),
G.IsWalkFrom x' x' c' ∧
walkWeight G.johnsonAugmentedGraph.w c = walkWeight G.w c' := by
let c' := c.map (unsome x')
have h_chain_c' : List.IsChain G.Adj c' :=
chain_none_free_map G x' c hc.chain h_no_none
have h_head_c' : c'.head? = some x' := by
have h_head_c : c.head? = some (some x') := hc.head
rcases List.head?_eq_some_iff.mp h_head_c with ⟨cs, hc_eq⟩
subst hc_eq
simp [c', unsome]
have h_last_c' : c'.getLast? = some x' := by
rcases List.getLast?_eq_some_iff.mp hc.last with ⟨q, hq⟩
subst hq
simp [c', unsome]
have h_walk_c' : G.IsWalkFrom x' x' c' :=
⟨h_chain_c', h_head_c', h_last_c'⟩
have h_wt_eq : walkWeight G.johnsonAugmentedGraph.w c = walkWeight G.w c' :=
walkWeight_none_free_eq G x' c h_no_none
exact ⟨c', h_walk_c', h_wt_eq⟩
If G has no negative cycles, then johnsonAugmentedGraph also has none.
lemma noNegCycle_johnsonAugmentedGraph (hNC : G.NoNegCycle) :
G.johnsonAugmentedGraph.NoNegCycle := by
intro x c hc
cases x with
| none =>
have h_single : c = [none] := walk_from_none_to_none_singleton G c hc
subst h_single; simp [walkWeight]
| some x' =>
have h_head_ne_none : c.head? ≠ some none := by
rw [hc.head]; simp
have h_no_none : none ∉ c := chain_no_none G c hc.chain h_head_ne_none
rcases exists_walk_in_G_of_none_free_walk G x' c hc h_no_none with ⟨c', hc'_walk, hw_eq⟩
rw [hw_eq]
exact hNC x' c' hc'_walkJohnson potential via Bellman-Ford on the augmented graph
A direct walk from none to some v in the augmented graph.
lemma isWalkFrom_none_some (v : V) :
G.johnsonAugmentedGraph.IsWalkFrom none (some v) [none, some v] := by
let G' := G.johnsonAugmentedGraph
have h_chain : List.IsChain G'.Adj [none, some v] := by
rw [List.isChain_cons]
refine ⟨?_, List.isChain_singleton _⟩
intro y hy
have hy_eq : y = some v := by
have h_head_val : [some v].head? = some (some v) := by simp
rw [h_head_val] at hy
exact (Option.some_inj.mp hy).symm
subst hy_eq
unfold G' johnsonAugmentedGraph WeightedGraph.Adj
simp
refine ⟨h_chain, ?_, ?_⟩
· simp
· simp
The Bellman-Ford distance from none to some v in the augmented graph
is finite (not ⊤), because there is a direct zero-weight edge.
lemma relaxDist_none_some_ne_top (hNC : G.NoNegCycle) (v : V) :
G.johnsonAugmentedGraph.relaxDist none
(Fintype.card (Option V) - 1) (some v) ≠ ⊤ := by
let G' := G.johnsonAugmentedGraph
have h_no_neg : G'.NoNegCycle := noNegCycle_johnsonAugmentedGraph G hNC
have h_sd := G'.relaxDist_isShortestDist h_no_neg none (some v)
rcases h_sd with ⟨h_lower, _⟩
have h_walk : G'.IsWalkFrom none (some v) [none, some v] :=
isWalkFrom_none_some G v
have h_bound : G'.relaxDist none (Fintype.card (Option V) - 1) (some v) ≤
(walkWeight G'.w [none, some v] : WithTop ℝ) :=
h_lower [none, some v] h_walk
have h_wt : (walkWeight G'.w [none, some v] : WithTop ℝ) = (0 : WithTop ℝ) := by
simp [walkWeight, G', johnsonAugmentedGraph]
rw [h_wt] at h_bound
intro h_eq; rw [h_eq] at h_bound; simpa using h_bound
The Johnson potential h(v) is the shortest-path distance from
none to some v in the augmented graph, computed by Bellman-Ford.
noncomputable def johnsonPotential (hNC : G.NoNegCycle) (v : V) : ℝ :=
(G.johnsonAugmentedGraph.relaxDist none
(Fintype.card (Option V) - 1) (some v)).untop
(relaxDist_none_some_ne_top G hNC v)
The potential cast to WithTop ℝ equals the Bellman-Ford relaxation.
lemma johnsonPotential_eq (hNC : G.NoNegCycle) (v : V) :
(G.johnsonPotential hNC v : WithTop ℝ) =
G.johnsonAugmentedGraph.relaxDist none
(Fintype.card (Option V) - 1) (some v) := by
unfold johnsonPotential
simp [relaxDist_none_some_ne_top G hNC v]
The Johnson potential is the shortest-path distance from none to some v
in the augmented graph.
lemma johnsonPotential_isShortestDist (hNC : G.NoNegCycle) (v : V) :
G.johnsonAugmentedGraph.IsShortestDist none (some v)
(G.johnsonPotential hNC v) := by
rw [johnsonPotential_eq G hNC]
have h_no_neg : G.johnsonAugmentedGraph.NoNegCycle :=
noNegCycle_johnsonAugmentedGraph G hNC
exact G.johnsonAugmentedGraph.relaxDist_isShortestDist h_no_neg none (some v)Triangle inequality for the Johnson potential
Triangle inequality for the Johnson potential. For every edge
(u, v) in G, we have h(v) ≤ h(u) + w(u, v).
lemma johnsonPotential_triangle (hNC : G.NoNegCycle) (u v : V)
(h_edge : (u, v) ∈ G.edges) :
G.johnsonPotential hNC v ≤ G.johnsonPotential hNC u + G.w u v := by
let G' := G.johnsonAugmentedGraph
let hu := G.johnsonPotential hNC u
let hv := G.johnsonPotential hNC v
have h_sd_u : G'.IsShortestDist none (some u) (hu : WithTop ℝ) :=
johnsonPotential_isShortestDist G hNC u
have h_sd_v : G'.IsShortestDist none (some v) (hv : WithTop ℝ) :=
johnsonPotential_isShortestDist G hNC v
rcases h_sd_u.2 with (h_top | ⟨p_u, hp_walk, hp_weight⟩)
· have h_fin : (hu : WithTop ℝ) ≠ ⊤ := by simp
exact absurd h_top h_fin
· have h_edge' : G'.Adj (some u) (some v) := by
unfold G' johnsonAugmentedGraph WeightedGraph.Adj; simp [h_edge]
have h_walk_v : G'.IsWalkFrom none (some v) (p_u ++ [some v]) :=
IsWalkFrom.append_step (G := G') hp_walk h_edge'
have h_weight_v : (walkWeight G'.w (p_u ++ [some v]) : WithTop ℝ) =
(hu : WithTop ℝ) + (G.w u v : WithTop ℝ) := by
rw [walkWeight_append_step G' hp_walk h_edge']
rw [hp_weight]
have h_w_uv : (G'.w (some u) (some v) : WithTop ℝ) = (G.w u v : WithTop ℝ) := by
unfold G' johnsonAugmentedGraph; simp
rw [h_w_uv]
have h_ineq : (hv : WithTop ℝ) ≤ (walkWeight G'.w (p_u ++ [some v]) : WithTop ℝ) :=
h_sd_v.1 (p_u ++ [some v]) h_walk_v
rw [h_weight_v] at h_ineq
exact_mod_cast h_ineqNonnegativity of the reweighted graph
With the Johnson potential, every edge weight in the reweighted graph is nonnegative, satisfying Dijkstra's precondition.
lemma reweightedGraph_nonneg (hNC : G.NoNegCycle) :
(G.reweightedGraph (G.johnsonPotential hNC)).Nonneg := by
rw [WeightedGraph.Nonneg]
intro u v h_edge
rw [w_reweightedGraph, reweightedWeight_eq]
have h_triangle := johnsonPotential_triangle G hNC u v h_edge
linarithWith the Johnson potential, the reweighted graph has no negative cycles.
lemma reweightedGraph_noNegCycle (hNC : G.NoNegCycle) :
(G.reweightedGraph (G.johnsonPotential hNC)).NoNegCycle :=
noNegCycle_of_nonneg (G := G.reweightedGraph (G.johnsonPotential hNC)) (G.reweightedGraph_nonneg hNC)Johnson's all-pairs shortest paths
A WithTop ℝ algebra identity for finite adjustments.
private lemma add_sub_add_sub_eq (a : WithTop ℝ) (b c : ℝ) :
a = (a + (c : WithTop ℝ) - (b : WithTop ℝ)) + (b : WithTop ℝ) - (c : WithTop ℝ) := by
induction a using WithTop.recTopCoe with
| top => simp
| coe a => simp
Johnson's all-pairs shortest-path distance. Run Bellman-Ford from u
in the reweighted graph, then adjust by h(v) - h(u).
noncomputable def johnsonDist (hNC : G.NoNegCycle) (u v : V) : WithTop ℝ :=
let h := G.johnsonPotential hNC
let d_hat := (G.reweightedGraph h).relaxDist u (Fintype.card V - 1) v
d_hat + (h v : WithTop ℝ) - (h u : WithTop ℝ)
Theorem (Johnson correctness). johnsonDist hNC u v equals the
shortest-path distance δ(u, v) in the original graph G. (CLRS Theorem 23.5)
theorem johnsonDist_isShortestDist (hNC : G.NoNegCycle) (u v : V) :
G.IsShortestDist u v (G.johnsonDist hNC u v) := by
let h := G.johnsonPotential hNC
have h_no_neg : (G.reweightedGraph h).NoNegCycle := G.reweightedGraph_noNegCycle hNC
let d_hat := (G.reweightedGraph h).relaxDist u (Fintype.card V - 1) v
have h_sd_dhat : (G.reweightedGraph h).IsShortestDist u v d_hat :=
(G.reweightedGraph h).relaxDist_isShortestDist h_no_neg u v
unfold johnsonDist
-- Let d := d_hat + h(v) - h(u). Then d + h(u) - h(v) = d_hat.
-- So by reweighted_isShortestDist.mp, G.IsShortestDist u v d.
have h_algebra : ((d_hat + (h v : WithTop ℝ) - (h u : WithTop ℝ)) +
(h u : WithTop ℝ) - (h v : WithTop ℝ)) = d_hat :=
(add_sub_add_sub_eq d_hat (h u) (h v)).symm
have h_equiv := G.reweighted_isShortestDist h u v
(d_hat + (h v : WithTop ℝ) - (h u : WithTop ℝ))
rw [← h_algebra] at h_sd_dhat
exact h_equiv.mp h_sd_dhatGeneral triangle inequality
The fundamental triangle inequality for shortest-path distances: for any edge
(u, v), the shortest distance to v is at most the shortest distance to u plus
the edge weight. No sign assumptions needed.
theorem isShortestDist_edge_ineq (s u v : V) (δ : V → WithTop ℝ)
(hδ : ∀ t, G.IsShortestDist s t (δ t)) (h_edge : (u, v) ∈ G.edges) :
δ v ≤ δ u + (G.w u v : WithTop ℝ) := by
rcases (hδ u).2 with hutop | ⟨q, hq, hqw⟩
· rw [hutop]; simp
· have hq_ne : q ≠ [] := hq.ne_nil
have h_last : q.getLast hq_ne = u := by
have htemp := List.getLast?_eq_getLast_of_ne_nil hq_ne
have h_eq_some : some u = some (q.getLast hq_ne) := by
rw [← hq.last, htemp]
exact (Option.some_inj.mp h_eq_some).symm
have h_walk : G.IsWalkFrom s v (q ++ [v]) := by
refine ⟨?_, ?_, ?_⟩
· refine hq.chain.append (List.isChain_singleton v) ?_
intro a ha b hb
have ha_u : a = u := by
rw [Option.mem_def, hq.last] at ha
exact (Option.some.inj ha).symm
subst ha_u
have hb_v : b = v := by
have hsing : [v].head? = some v := by simp
rw [hsing, Option.mem_def] at hb
simpa using hb.symm
subst hb_v
exact h_edge
· rw [List.head?_append_of_ne_nil _ hq_ne]
exact hq.head
· simp
have h_weight : (walkWeight G.w (q ++ [v]) : WithTop ℝ) = δ u + (G.w u v : WithTop ℝ) := by
calc
(walkWeight G.w (q ++ [v]) : WithTop ℝ) =
((walkWeight G.w q + G.w (q.getLast hq_ne) v : ℝ) : WithTop ℝ) := by
exact_mod_cast walkWeight_append_singleton G.w q hq_ne v
_ = (walkWeight G.w q : WithTop ℝ) + (G.w (q.getLast hq_ne) v : WithTop ℝ) := by simp
_ = (walkWeight G.w q : WithTop ℝ) + (G.w u v : WithTop ℝ) := by rw [h_last]
_ = δ u + (G.w u v : WithTop ℝ) := by rw [hqw]
have h_bound : δ v ≤ (walkWeight G.w (q ++ [v]) : WithTop ℝ) := (hδ v).1 _ h_walk
rw [h_weight] at h_bound
exact h_bound
Every edge in the Johnson-augmented graph targets a some _ vertex.
lemma edge_target_some {u v : Option V}
(h : (u, v) ∈ G.johnsonAugmentedGraph.edges) : ∃ v', v = some v' := by
unfold johnsonAugmentedGraph at h
rcases Finset.mem_union.mp h with (h' | h')
· rcases Finset.mem_image.mp h' with ⟨v', _, h_eq⟩
have hv : v = some v' := by
have h_snd := congrArg Prod.snd h_eq
simpa using h_snd.symm
exact ⟨v', hv⟩
· rcases Finset.mem_image.mp h' with ⟨⟨u', v''⟩, _, h_eq⟩
have hv : v = some v'' := by
have h_snd := congrArg Prod.snd h_eq
simpa using h_snd.symm
exact ⟨v'', hv⟩
Project a walk in the augmented graph from some a to some b to a walk
in G from a to b with the same weight.
lemma walk_johnsonAugmented_some_projection (a b : V) (c : List (Option V))
(hc : G.johnsonAugmentedGraph.IsWalkFrom (some a) (some b) c) :
∃ (c' : List V), G.IsWalkFrom a b c' ∧
walkWeight G.johnsonAugmentedGraph.w c = walkWeight G.w c' := by
let G' := G.johnsonAugmentedGraph
induction c generalizing a b with
| nil => exact absurd rfl hc.ne_nil
| cons x xs ih =>
have hx : x = some a := by
have hh := hc.head; simp at hh
-- hh : some x = some (some a)
simpa using hh
-- Use rw instead of subst to preserve the IH
rw [hx] at hc ⊢
-- Now c = some a :: xs
rcases (List.isChain_cons_iff G'.Adj (some a) xs).mp hc.chain with
(hxs_nil | ⟨y, ys, h_adj, h_chain_rest, hxs_eq⟩)
· -- xs = []
rw [hxs_nil] at hc ⊢
-- c = [some a]
have hab : a = b := by
have hl := hc.last; simp at hl; simpa using hl
rw [hab]
refine ⟨[b], ⟨List.isChain_singleton b, by simp, by simp⟩, ?_⟩
simp [walkWeight]
· -- xs = y :: ys; rewrite everything with this
rw [hxs_eq] at hc ih ⊢
-- c = some a :: y :: ys
-- h_adj : G'.Adj (some a) y
rcases edge_target_some G h_adj with ⟨c', hy⟩
rw [hy] at hc ih h_adj h_chain_rest ⊢
-- y = some c'; c = some a :: some c' :: ys
have h_edge_orig : (a, c') ∈ G.edges :=
(G.mem_edges_johnsonAugmentedGraph_edge a c').mp h_adj
-- h_chain_rest : IsChain G'.Adj (some c' :: ys)
-- Build rest walk
have h_rest_head : (some c' :: ys).head? = some (some c') := by simp
have h_rest_last : (some c' :: ys).getLast? = some (some b) := by
have hl := hc.last; simpa using hl
have h_rest_walk : G'.IsWalkFrom (some c') (some b) (some c' :: ys) :=
⟨h_chain_rest, h_rest_head, h_rest_last⟩
rcases ih c' b h_rest_walk with ⟨c'_walk, h_walk, h_weight_eq⟩
-- c'_walk starts at c' and ends at b; deconstruct into c' :: rest
have h_c'_walk_ne_nil : c'_walk ≠ [] := h_walk.ne_nil
rcases List.exists_cons_of_ne_nil h_c'_walk_ne_nil with ⟨d, rest, h_c'_walk_eq⟩
-- d = c' because the walk starts at c'
have hd_eq_c' : d = c' := by
have h_walk_head := h_walk.head
rw [h_c'_walk_eq] at h_walk_head
simp at h_walk_head
simpa using h_walk_head
rw [hd_eq_c'] at h_c'_walk_eq
-- h_c'_walk_eq : c'_walk = c' :: rest
have h_walk' : G.IsWalkFrom c' b (c' :: rest) := by
rwa [h_c'_walk_eq] at h_walk
have h_weight_eq' : walkWeight G'.w (some c' :: ys) = walkWeight G.w (c' :: rest) := by
rwa [h_c'_walk_eq] at h_weight_eq
-- Build full walk a :: c' :: rest
have h_full_chain : List.IsChain G.Adj (a :: c' :: rest) := by
rw [List.isChain_cons_iff G.Adj a (c' :: rest)]
right; exact ⟨c', rest, h_edge_orig, h_walk'.chain, rfl⟩
have h_full_last : (a :: c' :: rest).getLast? = some b := by
have hlast := h_walk'.last; simpa using hlast
have h_full_walk : G.IsWalkFrom a b (a :: c' :: rest) :=
⟨h_full_chain, by simp, h_full_last⟩
have h_weight_eq_full : walkWeight G'.w (some a :: some c' :: ys) =
walkWeight G.w (a :: c' :: rest) := by
calc
walkWeight G'.w (some a :: some c' :: ys) =
G'.w (some a) (some c') + walkWeight G'.w (some c' :: ys) := rfl
_ = G.w a c' + walkWeight G'.w (some c' :: ys) := by simp [G', johnsonAugmentedGraph]
_ = G.w a c' + walkWeight G.w (c' :: rest) := by rw [h_weight_eq']
_ = walkWeight G.w (a :: c' :: rest) := rfl
exact ⟨a :: c' :: rest, h_full_walk, h_weight_eq_full⟩Johnson potential function
The Johnson potential h(v) = δ(none, some v) from Bellman-Ford on the
augmented graph. Finite because of the direct none→some v edge (weight 0).
lemma johnsonPotential_finite (_hNC : G.NoNegCycle) (v : V) :
G.johnsonAugmentedGraph.relaxDist none (Fintype.card (Option V) - 1) (some v) ≠ ⊤ := by
let G' := G.johnsonAugmentedGraph
have h1 : G'.relaxDist none 1 (some v) ≤ (0 : WithTop ℝ) := by
calc
G'.relaxDist none 1 (some v) = G'.relaxStep (G'.relaxDist none 0) (some v) := rfl
_ ≤ G'.relaxDist none 0 none + (G'.w none (some v) : WithTop ℝ) :=
G'.relaxStep_le_pred (by simp [G', mem_edges_johnsonAugmentedGraph_source])
_ = (0 : WithTop ℝ) + (0 : WithTop ℝ) := by simp [G', johnsonAugmentedGraph]
_ = (0 : WithTop ℝ) := by simp
have hcardV : 1 ≤ Fintype.card V := Fintype.card_pos_iff.mpr ⟨v⟩
have hcard_eq : Fintype.card (Option V) - 1 = Fintype.card V := by
simp [Fintype.card_option]
rw [hcard_eq]
have h_noninc : ∀ m, 1 ≤ m → G'.relaxDist none m (some v) ≤ (0 : WithTop ℝ) :=
Nat.le_induction h1 (fun n hn hle_n =>
calc
G'.relaxDist none (n + 1) (some v) ≤ G'.relaxDist none n (some v) :=
G'.relaxDist_succ_le none n (some v)
_ ≤ (0 : WithTop ℝ) := hle_n
)
have h_final : G'.relaxDist none (Fintype.card V) (some v) ≤ (0 : WithTop ℝ) :=
h_noninc (Fintype.card V) hcardV
intro htop; rw [htop] at h_final; simpa using h_finalNonnegative reweighted weights
theorem johnsonReweightedNonneg (hNC : G.NoNegCycle) :
(G.reweightedGraph (G.johnsonPotential hNC)).Nonneg := by
intro u v h_edge
have h_edge_orig : (u, v) ∈ G.edges := by
simpa [reweightedGraph] using h_edge
have h_triangle := G.johnsonPotential_triangle hNC u v h_edge_orig
dsimp [reweightedGraph, reweightedWeight]
linarithJohnson all-pairs distance
noncomputable def johnsonAllPairsDist (hNC : G.NoNegCycle) (u v : V) : WithTop ℝ :=
let h := G.johnsonPotential hNC
let G_hat := G.reweightedGraph h
let st := G_hat.dijkstraLoop u (Fintype.card V)
let d_hat := st.d v
d_hat + (h v : WithTop ℝ) - (h u : WithTop ℝ)End-to-end correctness
theorem johnsonAllPairsDist_correct (hNC : G.NoNegCycle) (u v : V) :
G.IsShortestDist u v (G.johnsonAllPairsDist hNC u v) := by
let h := G.johnsonPotential hNC
let G_hat := G.reweightedGraph h
have hnn : G_hat.Nonneg := G.johnsonReweightedNonneg hNC
have hNC_hat : G_hat.NoNegCycle := G_hat.noNegCycle_of_nonneg hnn
let δ_hat (v : V) : WithTop ℝ := G_hat.relaxDist u (Fintype.card V - 1) v
have hδ_hat (v : V) : G_hat.IsShortestDist u v (δ_hat v) :=
G_hat.relaxDist_isShortestDist hNC_hat u v
have h_dijkstra (v : V) : (G_hat.dijkstraLoop u (Fintype.card V)).d v = δ_hat v :=
G_hat.dijkstraLoop_correct hnn u δ_hat hδ_hat (Fintype.card V)
(le_refl (Fintype.card V)) v
-- Expand johnsonAllPairsDist using the locally defined h and G_hat
-- The local h and G_hat are definitionally equal to the ones in the definition
have h_expand : G.johnsonAllPairsDist hNC u v =
(G_hat.dijkstraLoop u (Fintype.card V)).d v + (h v : WithTop ℝ) - (h u : WithTop ℝ) := by
unfold johnsonAllPairsDist
rfl
rw [h_expand]
rw [h_dijkstra v]
-- Goal: G.IsShortestDist u v (δ_hat v + (h v : WithTop ℝ) - (h u : WithTop ℝ))
-- reweighted_isShortestDist: (G_hat).IsShortestDist u v (d + h_u - h_v) ↔ G.IsShortestDist u v d
-- Let d := δ_hat v + h_v - h_u; then d + h_u - h_v = δ_hat v
have h_fin_hu : (h u : WithTop ℝ) ≠ ⊤ := by simp
have h_fin_hv : (h v : WithTop ℝ) ≠ ⊤ := by simp
set d := δ_hat v + (h v : WithTop ℝ) - (h u : WithTop ℝ) with hd
have h_eq : d + (h u : WithTop ℝ) - (h v : WithTop ℝ) = δ_hat v := by
dsimp [d]
rcases Option.ne_none_iff_exists'.mp h_fin_hu with ⟨hu_val, hhu⟩
rcases Option.ne_none_iff_exists'.mp h_fin_hv with ⟨hv_val, hhv⟩
rw [hhu, hhv]
by_cases hδ_top : δ_hat v = ⊤
· rw [hδ_top]; simp
· rcases Option.ne_none_iff_exists'.mp hδ_top with ⟨δv_val, hδv⟩
rw [hδv]; simp
have h_sd_d : G_hat.IsShortestDist u v (d + (h u : WithTop ℝ) - (h v : WithTop ℝ)) := by
rw [h_eq]; exact hδ_hat v
exact (G.reweighted_isShortestDist h u v d).mp h_sd_dWork bound: O(V² log V + V E log V)
Johnson's algorithm runs one Bellman-Ford pass on the augmented graph (to build
the potential h) and then |V| Dijkstra passes on the reweighted graph (which
shares G's |E| edges). In the binary-heap cost model of Section 22.3 this is
(|V| - 1)·(|V| + |E|) edge relaxations plus |V|·(|V| + |E|)·(log₂|V| + 1) heap
operations, i.e. O(V² log V + V E log V).
The two image components of johnsonAugmentedGraph.edges are disjoint:
edges out of none never collide with lifted original edges.
lemma johnsonAugmentedGraph_edges_disjoint (G : WeightedGraph V) :
Disjoint (Finset.image (fun (v : V) => (none, some v)) (Finset.univ : Finset V))
(Finset.image (fun ((u, v) : V × V) => (some u, some v)) G.edges) := by
rw [Finset.disjoint_left]
intro e heA heB
rcases Finset.mem_image.mp heA with ⟨v, _, rfl⟩
rcases Finset.mem_image.mp heB with ⟨p, _, h⟩
have hnone : some p.1 = none := congrArg Prod.fst h
simp at hnone
The augmented graph has |V| + |E| edges: |V| zero-weight edges out of
none plus the |E| original edges.
lemma johnsonAugmentedGraph_edges_card (G : WeightedGraph V) :
G.johnsonAugmentedGraph.edges.card = Fintype.card V + G.edges.card := by
have hA_inj : Function.Injective (fun v : V => ((none : Option V), some v)) := by
intro a b h; exact Option.some.inj (congrArg Prod.snd h)
have hB_inj : Function.Injective (fun ((u, v) : V × V) => (some u, some v)) := by
intro a b h
exact Prod.ext (Option.some.inj (congrArg Prod.fst h)) (Option.some.inj (congrArg Prod.snd h))
have hA : ((Finset.univ : Finset V).image (fun v : V => ((none : Option V), some v))).card = Fintype.card V :=
(Finset.card_image_of_injective (Finset.univ : Finset V) hA_inj).trans Finset.card_univ
have hB : (G.edges.image (fun ((u, v) : V × V) => (some u, some v))).card = G.edges.card :=
Finset.card_image_of_injective G.edges hB_inj
unfold johnsonAugmentedGraph
rw [Finset.card_union_of_disjoint (johnsonAugmentedGraph_edges_disjoint G), hA, hB]
Total Johnson work: one Bellman-Ford pass on the augmented graph (potential)
plus |V| Dijkstra passes on the reweighted graph. Noncomputable only because
johnsonAugmentedGraph is.
noncomputable def johnsonCost (G : WeightedGraph V) : ℕ :=
G.johnsonAugmentedGraph.bellmanFordWork +
Fintype.card V * dijkstraWork (Fintype.card V) G.edges.card
Johnson work bound. In the binary-heap cost model, Johnson's algorithm
runs in |V|·(|V| + |E|)·(log₂|V| + 2), i.e. O(V² log V + V E log V).
theorem johnsonCost_eq (G : WeightedGraph V) :
G.johnsonCost = Fintype.card V * (Fintype.card V + G.edges.card) * (Nat.log2 (Fintype.card V) + 2) := by
unfold johnsonCost bellmanFordWork dijkstraWork
rw [johnsonAugmentedGraph_edges_card]
have hcard : Fintype.card (Option V) - 1 = Fintype.card V := by
rw [Fintype.card_option]; simp
rw [hcard]
ring_nf
O(V² log V + V E log V) work. Since log₂|V| + 2 ≤ 2·(log₂|V| + 1),
Johnson's work is at most 2·|V|·(|V| + |E|)·(log₂|V| + 1).
theorem johnsonCost_le (G : WeightedGraph V) :
G.johnsonCost ≤ 2 * Fintype.card V * (Fintype.card V + G.edges.card) * (Nat.log2 (Fintype.card V) + 1) := by
rw [johnsonCost_eq]
calc
Fintype.card V * (Fintype.card V + G.edges.card) * (Nat.log2 (Fintype.card V) + 2)
≤ Fintype.card V * (Fintype.card V + G.edges.card) * (2 * (Nat.log2 (Fintype.card V) + 1)) := by
exact Nat.mul_le_mul_left _ (by omega)
_ = 2 * Fintype.card V * (Fintype.card V + G.edges.card) * (Nat.log2 (Fintype.card V) + 1) := by ac_rflend WeightedGraphend Chapter24end CLRSDefinitions and proofs
CLRSLean.FourthEdition.Chapter_23.Section_23_3_Johnsons_Algorithm.Dijkstra
Stored Dijkstra with a list scan queue. The accumulated work counts candidate visits, row-cell evaluations and writes, queue construction, and successful settlements. This indexed real-operation model treats array access and real arithmetic as primitive; it does not claim a binary-heap bound or a bit-complexity bound.
noncomputable sectionnamespace CLRS.Chapter24.JohnsonExecutionopen MatrixExecution (Stored)structure QueueState (n : Nat) where
row : Vector
queue : List (Fin n)def view (st : QueueState n) : WeightedGraph.DijkstraState (Fin n) where
S := Finset.univ \ st.queue.toFinset
d := vecRead st.rowdef initState (weights : Stored) (s : Fin n) : QueueState n × Nat :=
let row := vector (fun v : Fin n => (if v = s then 0 else MatrixExecution.read weights s v, 1))
let queue := initialQueue s n le_rfl
(⟨row, queue.1⟩, row.writes + row.visits + queue.2)
@[simp] theorem initState_work (weights : Stored) (s : Fin n) :
(initState weights s).2 = 3 * n := by
simp only [initState, vector_writes, initialQueue_visits]
rw [vector_visits _ 1 (by intros; rfl)]
omega@[simp] theorem initState_nodup (weights : Stored) (s : Fin n) :
(initState weights s).1.queue.Nodup := initialQueue_nodup s n le_rfltheorem initState_length_le (weights : Stored) (s : Fin n) :
(initState weights s).1.queue.length ≤ n := initialQueue_length_le s n le_rfldef MatrixCorrect (G : WeightedGraph (Fin n)) (weights : Stored) : Prop :=
∀ i j, MatrixExecution.read weights i j =
if (i, j) ∈ G.edges then (G.w i j : WithTop ℝ) else ⊤theorem initState_view (G : WeightedGraph (Fin n)) (weights : Stored) (hw : MatrixCorrect G weights)
(s : Fin n) : view (initState weights s).1 = G.dijkstraInit s := by
apply WeightedGraph.DijkstraState.ext
· ext v
simp [view, initState, initialQueue_mem, v.isLt, WeightedGraph.dijkstraInit]
· funext v
simp [view, initState, hw s v, WeightedGraph.dijkstraInit]def relaxRow (weights : Stored) (row : Vector) (u : Fin n) : Vector :=
vector (fun v : Fin n => (min (vecRead row v) (vecRead row u + MatrixExecution.read weights u v), 1))@[simp] theorem relaxRow_writes (weights : Stored) (row : Vector) (u : Fin n) :
(relaxRow weights row u).writes = n := vector_writes _@[simp] theorem relaxRow_visits (weights : Stored) (row : Vector) (u : Fin n) :
(relaxRow weights row u).visits = n := by
simpa [relaxRow] using vector_visits (fun v : Fin n =>
(min (vecRead row v) (vecRead row u + MatrixExecution.read weights u v), 1)) 1 (by intros; rfl)def advance (weights : Stored) (st : QueueState n) (u : Fin n) (rest : List (Fin n)) : QueueState n :=
⟨relaxRow weights st.row u, rest⟩
theorem advance_view (G : WeightedGraph (Fin n)) (weights : Stored) (hw : MatrixCorrect G weights)
(st : QueueState n) (hq : st.queue.Nodup) (u : Fin n) (rest : List (Fin n))
(hp : st.queue.Perm (u :: rest)) :
view (advance weights st u rest) = G.dijkstraSettle (view st) u := by
have hnew := List.nodup_cons.mp (hp.nodup_iff.mp hq)
apply WeightedGraph.DijkstraState.ext
· ext v
have hm : v ∈ st.queue ↔ v = u ∨ v ∈ rest := by simpa using hp.mem_iff
simp only [view, advance, WeightedGraph.dijkstraSettle, Finset.mem_sdiff,
Finset.mem_univ, List.mem_toFinset, true_and, Finset.mem_insert]
rw [hm]
by_cases hv : v = u
· subst v; simp [hnew.1]
· simp [hv]
· funext v
simp only [view, advance, relaxRow, vector_read, WeightedGraph.dijkstraSettle]
rw [hw u v]
by_cases he : (u, v) ∈ G.edges <;> simp [he]def run (weights : Stored) : Nat → QueueState n → QueueState n × Nat
| 0, st => (st, 0)
| k + 1, st =>
let found := extractMin (vecRead st.row) st.queue
match found.1 with
| none => (st, found.2)
| some (u, rest) =>
let next := advance weights st u rest
let result := run weights k next
(result.1, found.2 + next.row.writes + next.row.visits + 1 + result.2)
theorem run_invariant (G : WeightedGraph (Fin n)) (weights : Stored) (hw : MatrixCorrect G weights)
(hnn : G.Nonneg) (s : Fin n) (δ : Fin n → WithTop ℝ)
(hδ : ∀ v, G.IsShortestDist s v (δ v)) (k : Nat)
(st : QueueState n) (hq : st.queue.Nodup)
(hinv : G.DijkstraInvariant hnn s δ hδ (view st)) :
G.DijkstraInvariant hnn s δ hδ (view (run weights k st).1) := by
induction k generalizing st with
| zero => exact hinv
| succ k ih =>
simp only [run]
split
· exact hinv
· rename_i u rest he
obtain ⟨hp, hm⟩ := extractMin_spec (vecRead st.row) st.queue he
have hn := List.nodup_cons.mp (hp.nodup_iff.mp hq)
apply ih _ hn.2
rw [advance_view G weights hw st hq u rest hp]
apply G.dijkstraSettle_invariant hnn s δ hδ (view st) hinv u
· have hu : u ∈ st.queue := hp.mem_iff.mpr (by simp)
simpa [view] using hu
· intro v hv
apply hm v
simpa [view] using hvtheorem run_queue_empty (weights : Stored) (k : Nat) (st : QueueState n)
(hlen : st.queue.length ≤ k) : (run weights k st).1.queue = [] := by
induction k generalizing st with
| zero => simpa [run] using hlen
| succ k ih =>
simp only [run]
split
· rename_i he
exact (extractMin_none (vecRead st.row) st.queue).mp he
· rename_i u rest he
obtain ⟨hp, _⟩ := extractMin_spec (vecRead st.row) st.queue he
have hl := hp.length_eq
apply ih
change rest.length ≤ k
simp only [List.length_cons] at hl
omega
theorem run_work_le (weights : Stored) (k : Nat) (st : QueueState n)
(hlen : st.queue.length ≤ n) : (run weights k st).2 ≤ k * (3 * n + 1) := by
induction k generalizing st with
| zero => simp [run]
| succ k ih =>
simp only [run]
split
· simp only [extractMin_visits]
nlinarith
· rename_i u rest he
obtain ⟨hp, _⟩ := extractMin_spec (vecRead st.row) st.queue he
have hl := hp.length_eq
have hr : rest.length ≤ n := by simp only [List.length_cons] at hl; omega
have hind := ih (advance weights st u rest) hr
simp only [advance] at hind
simp only [extractMin_visits, advance, relaxRow_writes, relaxRow_visits]
nlinarithdef dijkstraStored (weights : Stored) (s : Fin n) : Vector × Nat :=
let initial := initState weights s
let result := run weights n initial.1
(result.1.row, initial.2 + result.2)
theorem dijkstraStored_correct (G : WeightedGraph (Fin n)) (weights : Stored)
(hw : MatrixCorrect G weights) (hnn : G.Nonneg) (s : Fin n) (δ : Fin n → WithTop ℝ)
(hδ : ∀ v, G.IsShortestDist s v (δ v)) (v : Fin n) :
vecRead (dijkstraStored weights s).1 v = δ v := by
have hi : G.DijkstraInvariant hnn s δ hδ (view (initState weights s).1) := by
rw [initState_view G weights hw s]
exact G.dijkstraInit_invariant hnn s δ hδ
have hf := run_invariant G weights hw hnn s δ hδ n (initState weights s).1
(initState_nodup weights s) hi
have hq := run_queue_empty weights n (initState weights s).1 (initState_length_le weights s)
exact hf.hsettled v (by simp [view, hq])theorem dijkstraStored_work_le (weights : Stored) (s : Fin n) :
(dijkstraStored weights s).2 ≤ 3 * n ^ 2 + 4 * n := by
have hr := run_work_le weights n (initState weights s).1 (initState_length_le weights s)
simp only [dijkstraStored, initState_work]
nlinarithend CLRS.Chapter24.JohnsonExecutionCLRSLean.FourthEdition.Chapter_23.Section_23_3_Johnsons_Algorithm.DijkstraCore
Dijkstra settlement with an explicit minimum
This proof isolates the standard invariant update from the native classical minimum selector. A counted queue may supply any actual unsettled minimum, including its own deterministic tie choice.
namespace CLRS.Chapter24.WeightedGraphvariable {V : Type*} [Fintype V] [DecidableEq V] (G : WeightedGraph V)Settle the chosen vertex and relax every outgoing edge.
noncomputable def dijkstraSettle (st : DijkstraState V) (u : V) : DijkstraState V :=
{ S := insert u st.S
d := fun v => if (u, v) ∈ G.edges then min (st.d v) (st.d u + (G.w u v : WithTop ℝ)) else st.d v }Any unsettled minimum preserves the native Dijkstra invariant.
theorem dijkstraSettle_invariant (hnn : G.Nonneg) (s : V) (δ : V → WithTop ℝ)
(hδ : ∀ v, G.IsShortestDist s v (δ v))
(st : DijkstraState V) (h_inv : DijkstraInvariant G hnn s δ hδ st)
(u : V) (hu_notin_S : u ∉ st.S) (hu_min_all : ∀ y, y ∉ st.S → st.d u ≤ st.d y) :
DijkstraInvariant G hnn s δ hδ (G.dijkstraSettle st u) := by
have h_du_eq_δu : st.d u = δ u :=
G.extractMin_correct_of_invariant hnn s δ hδ st h_inv u hu_notin_S hu_min_all
let S' := insert u st.S
let d' := fun v => if (u, v) ∈ G.edges then min (st.d v) (st.d u + (G.w u v : WithTop ℝ)) else st.d v
have h_s_S' : s ∈ S' := Finset.mem_insert_of_mem h_inv.hsS
have h_settled' : ∀ x ∈ S', d' x = δ x := by
intro x hx
rcases Finset.mem_insert.1 hx with (he | hx_S)
· subst x
-- x = u
dsimp [d']
by_cases h_edge_uu : (u, u) ∈ G.edges
· have h_nonneg_w : 0 ≤ G.w u u := hnn u u h_edge_uu
have h_add : st.d u ≤ st.d u + (G.w u u : WithTop ℝ) := by
have h_nonneg_w' : (0 : WithTop ℝ) ≤ (G.w u u : WithTop ℝ) := by exact_mod_cast h_nonneg_w
exact le_add_of_nonneg_right h_nonneg_w'
simp [h_edge_uu]
have h_min_eq : min (st.d u) (st.d u + (G.w u u : WithTop ℝ)) = st.d u :=
min_eq_left h_add
rw [h_min_eq, h_du_eq_δu]
· simp [h_edge_uu, h_du_eq_δu]
· -- x ∈ st.S
have h_dx_eq_δx : st.d x = δ x := h_inv.hsettled x hx_S
dsimp [d']
by_cases h_edge_ux : (u, x) ∈ G.edges
· have h_ineq : δ x ≤ δ u + (G.w u x : WithTop ℝ) :=
G.delta_le_delta_add_edge hnn s δ hδ u x h_edge_ux
have h_add : st.d x ≤ st.d u + (G.w u x : WithTop ℝ) := by
rw [h_dx_eq_δx, h_du_eq_δu]
exact h_ineq
simp [h_edge_ux]
have h_min_eq : min (st.d x) (st.d u + (G.w u x : WithTop ℝ)) = st.d x :=
min_eq_left h_add
rw [h_min_eq, h_dx_eq_δx]
· simp [h_edge_ux, h_dx_eq_δx]
have h_htent' : ∀ y ∉ S', ∀ x ∈ S', (x, y) ∈ G.edges → d' y ≤ δ x + (G.w x y : WithTop ℝ) := by
intro y hy_S' x hx_S' h_edge
have hy_notin_S : y ∉ st.S := by
intro hy_S; apply hy_S'; simp [S', hy_S]
rcases Finset.mem_insert.1 hx_S' with (he | hx_S)
· subst x
-- x = u
dsimp [d']
have h_edge_uy : (u, y) ∈ G.edges := h_edge
calc
(if (u, y) ∈ G.edges then min (st.d y) (st.d u + (G.w u y : WithTop ℝ)) else st.d y)
= min (st.d y) (st.d u + (G.w u y : WithTop ℝ)) := by simp [h_edge_uy]
_ ≤ st.d u + (G.w u y : WithTop ℝ) := min_le_right _ _
_ = δ u + (G.w u y : WithTop ℝ) := by rw [h_du_eq_δu]
· -- x ∈ st.S
have h_old_htent : st.d y ≤ δ x + (G.w x y : WithTop ℝ) :=
h_inv.htent y hy_notin_S x hx_S h_edge
dsimp [d']
by_cases h_edge_uy : (u, y) ∈ G.edges
· calc
(if (u, y) ∈ G.edges then min (st.d y) (st.d u + (G.w u y : WithTop ℝ)) else st.d y)
= min (st.d y) (st.d u + (G.w u y : WithTop ℝ)) := by simp [h_edge_uy]
_ ≤ st.d y := min_le_left _ _
_ ≤ δ x + (G.w x y : WithTop ℝ) := h_old_htent
· simp [h_edge_uy, h_old_htent]
have h_valid' : ∀ y ∉ S', δ y ≤ d' y := by
intro y hy_S'
have hy_notin_S : y ∉ st.S := by
intro hy_S; apply hy_S'; simp [S', hy_S]
have h_old_valid : δ y ≤ st.d y := h_inv.hvalid y hy_notin_S
by_cases h_edge_uy : (u, y) ∈ G.edges
· have h_ineq : δ y ≤ δ u + (G.w u y : WithTop ℝ) :=
G.delta_le_delta_add_edge hnn s δ hδ u y h_edge_uy
have h_hvalid_via_add : δ y ≤ st.d u + (G.w u y : WithTop ℝ) := by
rw [h_du_eq_δu]
exact h_ineq
simpa [d', h_edge_uy] using le_min_iff.mpr ⟨h_old_valid, h_hvalid_via_add⟩
· simpa [d', h_edge_uy] using h_old_valid
exact ⟨h_s_S', h_settled', h_htent', h_valid'⟩end CLRS.Chapter24.WeightedGraphCLRSLean.FourthEdition.Chapter_23.Section_23_3_Johnsons_Algorithm.Execution
Stored Johnson execution with an indexed scan queue
Vertices are explicitly Fin n. The returned array retains one complete row per
source. Execution first materializes the augmented edge matrix, computes and caches
Bellman–Ford potentials, and materializes one reweighted matrix. Each source then
runs stored Dijkstra once and appends its restored row. No shortest-distance oracle
is called by the execution definitions.
The returned work is the sum of actual candidate visits, row evaluations/writes, queue construction/settlements, and completed-row appends. In this indexed real-operation model, the dense matrices and scan queue give a cubic upper bound. Indexed array access, graph edge/weight queries, and real arithmetic are primitives; Lean finite-set membership implementation, persistent-array copying, bit complexity, and a binary-heap runtime are outside this model. Correctness requires a graph with no negative cycle; no rejection API is implemented.
noncomputable sectionnamespace CLRS.Chapter24.JohnsonExecutionExecute one source, then materialize the restored original-weight row.
def sourceRow (prepared : Prepared n) (s : Fin n) : Vector × Nat :=
let computed := dijkstraStored prepared.weights s
let out := vector (fun v : Fin n =>
(vecRead computed.1 v + vecRead prepared.potential v - vecRead prepared.potential s, 1))
(out, computed.2 + out.writes + out.visits)
theorem sourceRow_work_le (prepared : Prepared n) (s : Fin n) :
(sourceRow prepared s).2 ≤ 3 * n ^ 2 + 6 * n := by
have hd := dijkstraStored_work_le prepared.weights s
simp only [sourceRow, vector_writes]
rw [vector_visits _ 1 (by intros; rfl)]
nlinarith
theorem sourceRow_read (G : WeightedGraph (Fin n)) (hNC : G.NoNegCycle) (s v : Fin n) :
vecRead (sourceRow (prepare G) s).1 v = G.johnsonAllPairsDist hNC s v := by
let Gh := G.reweightedGraph (G.johnsonPotential hNC)
have hnn : Gh.Nonneg := G.johnsonReweightedNonneg hNC
let δ : Fin n → WithTop ℝ := fun v => Gh.relaxDist s (Fintype.card (Fin n) - 1) v
have hδ : ∀ v, Gh.IsShortestDist s v (δ v) :=
fun v => Gh.relaxDist_isShortestDist (Gh.noNegCycle_of_nonneg hnn) s v
have hd := dijkstraStored_correct Gh (prepare G).weights (prepare_weights G hNC) hnn s δ hδ v
have hl := Gh.dijkstraLoop_correct hnn s δ hδ n (by simp) v
simp only [sourceRow, vector_read, prepare_potential G hNC, hd]
simpa [WeightedGraph.johnsonAllPairsDist, Gh] using
congrArg (fun d : WithTop ℝ => d + (G.johnsonPotential hNC v : WithTop ℝ) -
(G.johnsonPotential hNC s : WithTop ℝ)) hl.symmstructure Result (n : Nat) where
rows : Array Vector
work : NatAppend one completed source row per iteration; the inner cell loop never reruns Dijkstra.
def sourceRows (prepared : Prepared n) : (k : Nat) → k ≤ n → Result n
| 0, _ => ⟨#[], 0⟩
| k + 1, hk =>
let previous := sourceRows prepared k (by omega)
let next := sourceRow prepared ⟨k, by omega⟩
⟨previous.rows.push next.1, previous.work + next.2 + 1⟩@[simp] theorem sourceRows_size (prepared : Prepared n) (k : Nat) (hk : k ≤ n) :
(sourceRows prepared k hk).rows.size = k := by
induction k with
| zero => rfl
| succ k ih => simp [sourceRows, ih]
theorem sourceRows_get (prepared : Prepared n) (k : Nat) (hk : k ≤ n)
(i : Nat) (hi : i < k) :
(sourceRows prepared k hk).rows[i]? = some (sourceRow prepared ⟨i, by omega⟩).1 := by
induction k with
| zero => omega
| succ k ih =>
by_cases he : i = k
· subst i
simp only [sourceRows, Array.getElem?_push, sourceRows_size, ite_true]
· have hi' : i < k := by omega
simp only [sourceRows, Array.getElem?_push]
rw [sourceRows_size]
simp only [he, if_false]
exact ih (by omega) hi'
theorem sourceRows_work_le (prepared : Prepared n) (k : Nat) (hk : k ≤ n) :
(sourceRows prepared k hk).work ≤ k * (3 * n ^ 2 + 6 * n + 1) := by
induction k with
| zero => simp [sourceRows]
| succ k ih =>
have hs := sourceRow_work_le prepared (⟨k, by omega⟩ : Fin n)
have hp := ih (by omega)
simp only [sourceRows]
nlinarithdef read (result : Result n) (i j : Fin n) : WithTop ℝ :=
((result.rows[i.val]?).map (fun row => vecRead row j)).getD ⊤Stored Johnson execution for explicitly indexed vertices, with a scan priority queue.
def johnsonStored (G : WeightedGraph (Fin n)) : Result n :=
let prepared := prepare G
let rows := sourceRows prepared n (Nat.le_refl n)
{ rows with work := prepared.work + rows.work }@[simp] theorem johnsonStored_size (G : WeightedGraph (Fin n)) :
(johnsonStored G).rows.size = n := sourceRows_size _ _ _theorem johnsonStored_read (G : WeightedGraph (Fin n)) (hNC : G.NoNegCycle) (i j : Fin n) :
read (johnsonStored G) i j = G.johnsonAllPairsDist hNC i j := by
simp only [read, johnsonStored, sourceRows_get _ n (Nat.le_refl n) i.val i.isLt,
Option.map_some, Option.getD_some]
exact sourceRow_read G hNC i j
theorem johnsonStored_correct (G : WeightedGraph (Fin n)) (hNC : G.NoNegCycle) (i j : Fin n) :
G.IsShortestDist i j (read (johnsonStored G) i j) := by
rw [johnsonStored_read G hNC]
exact G.johnsonAllPairsDist_correct hNC i jEvery source has its own retained, fully materialized row.
theorem johnsonStored_row (G : WeightedGraph (Fin n)) (i : Fin n) :
(johnsonStored G).rows[i.val]? = some (sourceRow (prepare G) i).1 :=
sourceRows_get _ n (Nat.le_refl n) i.val i.isLt@[simp] theorem sourceRow_width (prepared : Prepared n) (s : Fin n) :
(sourceRow prepared s).1.cells.size = n := by
simp [sourceRow, vector]A specification bridge useful for finite examples, independent of Dijkstra tie choices.
theorem johnsonStored_eq_relaxDist (G : WeightedGraph (Fin n)) (hNC : G.NoNegCycle)
(i j : Fin n) : read (johnsonStored G) i j = G.relaxDist i (n - 1) j := by
have hs := johnsonStored_correct G hNC i j
have hr : G.IsShortestDist i j (G.relaxDist i (n - 1) j) := by
simpa using G.relaxDist_isShortestDist hNC i j
have hle {a b : WithTop ℝ} (ha : G.IsShortestDist i j a)
(hb : G.IsShortestDist i j b) : a ≤ b := by
obtain ht | ⟨p, hp, hw⟩ := hb.2
· rw [ht]; exact le_top
· rw [← hw]; exact ha.1 p hp
exact le_antisymm (hle hs hr) (hle hr hs)theorem johnsonStored_work_le_polynomial (G : WeightedGraph (Fin n)) :
(johnsonStored G).work ≤ 4 * n ^ 3 + 14 * n ^ 2 + 12 * n + 4 := by
have hs := sourceRows_work_le (prepare G) n (Nat.le_refl n)
simp only [johnsonStored, prepare_work]
nlinariththeorem johnsonStored_work_le (G : WeightedGraph (Fin n)) :
(johnsonStored G).work ≤ 16 * (n + 1) ^ 3 := by
have hs := johnsonStored_work_le_polynomial G
nlinarith [Nat.zero_le (n ^ 3), Nat.zero_le (n ^ 2)]end CLRS.Chapter24.JohnsonExecutionCLRSLean.FourthEdition.Chapter_23.Section_23_3_Johnsons_Algorithm.Potential
Stored Johnson preprocessing: materialize the augmented edge matrix, execute synchronous Bellman–Ford rounds, retain the potential vector, and materialize one reweighted matrix. Correctness assumes absence of negative cycles; execution does not implement a negative-cycle failure API. Counters use the indexed real-operation model of the storage primitives.
noncomputable sectionnamespace CLRS.Chapter24.JohnsonExecutionopen MatrixExecution (Stored)def potentialPhase (G : WeightedGraph (Fin n)) : Vector × Nat :=
let e := finSuccEquiv n
let weights := edgeTable G.johnsonAugmentedGraph e
let relaxed := bfLoop weights (e.symm none) n
let potential := vector (fun v : Fin n => (vecRead relaxed.1 (e.symm (some v)), 1))
(potential, weights.cellWrites + weights.candidateVisits + relaxed.2 + potential.writes + potential.visits)
theorem potentialPhase_read (G : WeightedGraph (Fin n)) (hNC : G.NoNegCycle) (v : Fin n) :
vecRead (potentialPhase G).1 v = (G.johnsonPotential hNC v : WithTop ℝ) := by
unfold potentialPhase
rw [vector_read, bfLoop_read G.johnsonAugmentedGraph (finSuccEquiv n) _
(edgeTable_read G.johnsonAugmentedGraph (finSuccEquiv n))]
simpa using (G.johnsonPotential_eq hNC v).symm
theorem potentialPhase_work (G : WeightedGraph (Fin n)) :
(potentialPhase G).2 = 2 * (n + 1) ^ 2 + 2 * (n + 1) +
n * ((n + 1) * (n + 3)) + 2 * n := by
simp only [potentialPhase, edgeTable_writes, edgeTable_visits, bfLoop_work, vector_writes]
rw [vector_visits _ 1 (by intros; rfl)]
ringdef reweightTable (G : WeightedGraph (Fin n)) (potential : Vector) : Stored :=
MatrixExecution.tabulate (fun u v : Fin n =>
(if (u, v) ∈ G.edges then (G.w u v : WithTop ℝ) + vecRead potential u - vecRead potential v else ⊤, 1))@[simp] theorem reweightTable_writes (G : WeightedGraph (Fin n)) (potential : Vector) :
(reweightTable G potential).cellWrites = n * n := MatrixExecution.tabulate_writes _@[simp] theorem reweightTable_visits (G : WeightedGraph (Fin n)) (potential : Vector) :
(reweightTable G potential).candidateVisits = n * n := by
unfold reweightTable
simpa using MatrixExecution.tabulate_visits
(fun u v : Fin n =>
(if (u, v) ∈ G.edges then (G.w u v : WithTop ℝ) + vecRead potential u - vecRead potential v else ⊤, 1))
1 (by intros; rfl)structure Prepared (n : Nat) where
potential : Vector
weights : Stored
work : Natdef prepare (G : WeightedGraph (Fin n)) : Prepared n :=
let potential := potentialPhase G
let weights := reweightTable G potential.1
⟨potential.1, weights, potential.2 + weights.cellWrites + weights.candidateVisits⟩@[simp] theorem prepare_potential (G : WeightedGraph (Fin n)) (hNC : G.NoNegCycle) (v : Fin n) :
vecRead (prepare G).potential v = (G.johnsonPotential hNC v : WithTop ℝ) :=
potentialPhase_read G hNC vtheorem prepare_weights (G : WeightedGraph (Fin n)) (hNC : G.NoNegCycle) (u v : Fin n) :
MatrixExecution.read (prepare G).weights u v =
if (u, v) ∈ (G.reweightedGraph (G.johnsonPotential hNC)).edges then
((G.reweightedGraph (G.johnsonPotential hNC)).w u v : WithTop ℝ) else ⊤ := by
simp only [prepare, reweightTable, MatrixExecution.tabulate_read, potentialPhase_read G hNC]
simp only [WeightedGraph.reweightedGraph, WeightedGraph.reweightedWeight]
split <;> simp [WithTop.coe_add]theorem prepare_work (G : WeightedGraph (Fin n)) :
(prepare G).work = n ^ 3 + 8 * n ^ 2 + 11 * n + 4 := by
simp only [prepare, potentialPhase_work, reweightTable_writes, reweightTable_visits]
ringend CLRS.Chapter24.JohnsonExecutionCLRSLean.FourthEdition.Chapter_23.Section_23_3_Johnsons_Algorithm.Queue
Counted scan queue
Minimum extraction traverses an explicit queue and returns the remaining queue without its selected minimum. Its counter counts actual candidate visits. This is a linear scan queue, with no binary-heap logarithmic bound claimed.
noncomputable sectionnamespace CLRS.Chapter24.JohnsonExecutionScan a list queue, returning its minimum and the remaining queue. Each nonempty recursive frame records one actual candidate visit.
def extractMin (d : α → WithTop ℝ) : List α → Option (α × List α) × Nat
| [] => (none, 0)
| x :: xs =>
let tail := extractMin d xs
match tail.1 with
| none => (some (x, []), tail.2 + 1)
| some (y, ys) =>
if d x ≤ d y then (some (x, xs), tail.2 + 1)
else (some (y, x :: ys), tail.2 + 1)@[simp] theorem extractMin_visits (d : α → WithTop ℝ) (xs : List α) :
(extractMin d xs).2 = xs.length := by
induction xs with
| nil => rfl
| cons x xs ih =>
simp only [extractMin]
split
· simp [ih]
· split <;> simp [ih]@[simp] theorem extractMin_none (d : α → WithTop ℝ) (xs : List α) :
(extractMin d xs).1 = none ↔ xs = [] := by
cases xs with
| nil => simp [extractMin]
| cons x xs =>
simp only [extractMin]
split
· simp
· split <;> simptheorem extractMin_spec (d : α → WithTop ℝ) (xs : List α) {u rest}
(h : (extractMin d xs).1 = some (u, rest)) :
xs.Perm (u :: rest) ∧ ∀ v ∈ xs, d u ≤ d v := by
induction xs generalizing u rest with
| nil => simp [extractMin] at h
| cons x xs ih =>
simp only [extractMin] at h
split at h
· rename_i ht
have he := (extractMin_none d xs).mp ht
subst xs
have he : (x, []) = (u, rest) := Option.some.inj h
cases he
exact ⟨.refl _, by simp⟩
· rename_i y ys ht
obtain ⟨hp, hm⟩ := ih ht
split at h
· rename_i hxy
have he : (x, xs) = (u, rest) := Option.some.inj h
cases he
refine ⟨.refl _, ?_⟩
intro v hv
rcases List.mem_cons.mp hv with rfl | hv
· exact le_rfl
· exact le_trans hxy (hm v hv)
· rename_i hxy
have he : (y, x :: ys) = (u, rest) := Option.some.inj h
cases he
refine ⟨(List.Perm.cons x hp).trans (.swap u x ys), ?_⟩
intro v hv
rcases List.mem_cons.mp hv with rfl | hv
· exact le_of_lt (lt_of_not_ge hxy)
· exact hm v hvBuild the explicit unsettled queue, counting each enumerated vertex.
def initialQueue (s : Fin n) : (k : Nat) → k ≤ n → List (Fin n) × Nat
| 0, _ => ([], 0)
| k + 1, hk =>
let rest := initialQueue s k (by omega)
let v : Fin n := ⟨k, by omega⟩
(if v = s then rest.1 else v :: rest.1, rest.2 + 1)@[simp] theorem initialQueue_visits (s : Fin n) (k : Nat) (hk : k ≤ n) :
(initialQueue s k hk).2 = k := by
induction k with
| zero => rfl
| succ k ih => simp [initialQueue, ih]
theorem initialQueue_mem (s : Fin n) (k : Nat) (hk : k ≤ n) (v : Fin n) :
v ∈ (initialQueue s k hk).1 ↔ v.val < k ∧ v ≠ s := by
induction k with
| zero => simp [initialQueue]
| succ k ih =>
simp only [initialQueue]
split
· rename_i he
rw [ih]
constructor
· intro h; exact ⟨by omega, h.2⟩
· rintro ⟨hv, hvs⟩
refine ⟨?_, hvs⟩
have hne : v.val ≠ k := by
intro heq
apply hvs
exact (Fin.ext heq).trans he
omega
· rename_i he
rw [List.mem_cons, ih]
constructor
· rintro (rfl | ⟨hv, hvs⟩)
· exact ⟨by simp, he⟩
· exact ⟨by omega, hvs⟩
· rintro ⟨hv, hvs⟩
by_cases heq : v.val = k
· left; exact Fin.ext heq
· right; exact ⟨by omega, hvs⟩
theorem initialQueue_nodup (s : Fin n) (k : Nat) (hk : k ≤ n) :
(initialQueue s k hk).1.Nodup := by
induction k with
| zero => simp [initialQueue]
| succ k ih =>
simp only [initialQueue]
split
· exact ih _
· rw [List.nodup_cons]
exact ⟨by simp [initialQueue_mem], ih _⟩
theorem initialQueue_length_le (s : Fin n) (k : Nat) (hk : k ≤ n) :
(initialQueue s k hk).1.length ≤ k := by
induction k with
| zero => simp [initialQueue]
| succ k ih =>
have hh := ih (by omega)
simp only [initialQueue]
split
· exact Nat.le_trans hh (Nat.le_succ _)
· simpa only [List.length_cons] using Nat.succ_le_succ hhend CLRS.Chapter24.JohnsonExecutionCLRSLean.FourthEdition.Chapter_23.Section_23_3_Johnsons_Algorithm.Storage
Stored rows and synchronous Bellman–Ford execution
Rows and edge tables are materialized before use. Every cell append and scanned candidate contributes to a counter returned by the actual loops. Bellman–Ford reads the previous stored row and a cached dense edge table; no recursive relaxation oracle is called during a round. Exact real arithmetic, graph edge/weight queries, and indexed array access are abstract primitives. The model excludes Lean finite-set membership implementation, bit costs, and persistent-array copying.
noncomputable sectionnamespace CLRS.Chapter24.JohnsonExecutionopen CLRS.Chapter15.DPExecutionopen MatrixExecution (Stored)abbrev Vector := Row (WithTop ℝ)def vecRead (row : Vector) (i : Fin n) : WithTop ℝ := row.cells[i.val]?.getD ⊤def vector (f : Fin n → WithTop ℝ × Nat) : Vector :=
buildRow (fun i => if hi : i < n then f ⟨i, hi⟩ else (⊤, 0)) n@[simp] theorem vector_read (f : Fin n → WithTop ℝ × Nat) (i : Fin n) :
vecRead (vector f) i = (f i).1 := by
simp [vecRead, vector, i.isLt]@[simp] theorem vector_writes (f : Fin n → WithTop ℝ × Nat) : (vector f).writes = n := by
simp [vector]
theorem vector_visits (f : Fin n → WithTop ℝ × Nat) (c : Nat) (hc : ∀ i, (f i).2 = c) :
(vector f).visits = n * c := by
rw [vector, buildRow_visits]
calc
_ = ∑ _i ∈ Finset.range n, c := by
apply Finset.sum_congr rfl
intro i hi
simp [Finset.mem_range.mp hi, hc]
_ = _ := by simpvariable {V : Type} [Fintype V] [DecidableEq V]def edgeTable (G : WeightedGraph V) (e : Fin n ≃ V) : Stored :=
MatrixExecution.tabulate (fun i j => (if (e i, e j) ∈ G.edges then (G.w (e i) (e j) : WithTop ℝ) else ⊤, 1))@[simp] theorem edgeTable_read (G : WeightedGraph V) (e : Fin n ≃ V) (i j : Fin n) :
MatrixExecution.read (edgeTable G e) i j =
if (e i, e j) ∈ G.edges then (G.w (e i) (e j) : WithTop ℝ) else ⊤ := by
simp [edgeTable]@[simp] theorem edgeTable_writes (G : WeightedGraph V) (e : Fin n ≃ V) :
(edgeTable G e).cellWrites = n * n := MatrixExecution.tabulate_writes _@[simp] theorem edgeTable_visits (G : WeightedGraph V) (e : Fin n ≃ V) :
(edgeTable G e).candidateVisits = n * n := by
simpa [edgeTable] using MatrixExecution.tabulate_visits
(fun i j : Fin n => (if (e i, e j) ∈ G.edges then (G.w (e i) (e j) : WithTop ℝ) else ⊤, 1)) 1
(by intros; rfl)def bfRound (weights : Stored) (prev : Vector) (n : Nat) : Vector :=
vector (fun v : Fin n =>
let candidates := MatrixExecution.scanFin (fun u : Fin n =>
vecRead prev u + MatrixExecution.read weights u v)
(min (vecRead prev v) candidates.1, candidates.2 + 1))@[simp] theorem bfRound_writes (weights : Stored) (prev : Vector) (n : Nat) :
(bfRound weights prev n).writes = n := vector_writes _@[simp] theorem bfRound_visits (weights : Stored) (prev : Vector) (n : Nat) :
(bfRound weights prev n).visits = n * (n + 1) := by
apply vector_visits
intro i
simpomit [DecidableEq V] in
private theorem inf_fin_equiv (e : Fin n ≃ V) (f : V → WithTop ℝ) :
Finset.univ.inf (fun i : Fin n => f (e i)) = Finset.univ.inf f := by
apply le_antisymm
· apply Finset.le_inf
intro v _
simpa using (Finset.inf_le (s := Finset.univ) (f := fun i : Fin n => f (e i))
(Finset.mem_univ (e.symm v)))
· apply Finset.le_inf
intro i _
exact Finset.inf_le (Finset.mem_univ (e i))
theorem bfRound_read (G : WeightedGraph V) (e : Fin n ≃ V) (weights : Stored)
(hw : ∀ i j, MatrixExecution.read weights i j =
if (e i, e j) ∈ G.edges then (G.w (e i) (e j) : WithTop ℝ) else ⊤)
(prev : Vector) (d : V → WithTop ℝ) (hd : ∀ i, vecRead prev i = d (e i)) (v : Fin n) :
vecRead (bfRound weights prev n) v = G.relaxStep d (e v) := by
simp only [bfRound, vector_read, MatrixExecution.scanFin_value, hd, hw]
simp_rw [add_ite, add_top]
rw [inf_fin_equiv e (fun u => if (u, e v) ∈ G.edges then d u + (G.w u (e v) : WithTop ℝ) else ⊤)]
simp [WeightedGraph.relaxStep, WeightedGraph.preds, Finset.inf_ite]def bfLoop (weights : Stored) (s : Fin n) : Nat → Vector × Nat
| 0 =>
let initial := vector (fun i : Fin n => (if i = s then 0 else ⊤, 1))
(initial, initial.writes + initial.visits)
| k + 1 =>
let prev := bfLoop weights s k
let next := bfRound weights prev.1 n
(next, prev.2 + next.writes + next.visits)
theorem bfLoop_read (G : WeightedGraph V) (e : Fin n ≃ V) (weights : Stored)
(hw : ∀ i j, MatrixExecution.read weights i j =
if (e i, e j) ∈ G.edges then (G.w (e i) (e j) : WithTop ℝ) else ⊤)
(s : Fin n) (k : Nat) (v : Fin n) :
vecRead (bfLoop weights s k).1 v = G.relaxDist (e s) k (e v) := by
induction k generalizing v with
| zero => simp [bfLoop, WeightedGraph.relaxDist, e.injective.eq_iff]
| succ k ih =>
change vecRead (bfRound weights (bfLoop weights s k).1 n) v = _
rw [bfRound_read G e weights hw _ (G.relaxDist (e s) k) (fun i => ?_)]
· rfl
· exact ih i
@[simp] theorem bfLoop_work (weights : Stored) (s : Fin n) (k : Nat) :
(bfLoop weights s k).2 = 2 * n + k * (n * (n + 2)) := by
induction k with
| zero =>
simp only [bfLoop, vector_writes]
rw [vector_visits _ 1 (by intros; rfl)]
omega
| succ k ih =>
simp only [bfLoop, bfRound_writes, bfRound_visits, ih]
ringend CLRS.Chapter24.JohnsonExecution