Incremental array Prim execution
A single prepared index list is reused by all extraction scans. The graph is
supplied as stored adjacency lists satisfying Adjacency.represents.
Initialization writes the queue cells and the index-list cells; there is no
conversion from an unordered edge-set representation inside this algorithm.
Each successful extraction deactivates one cell and reads just that vertex's
adjacency row. The loop returns its chosen edges, final array, and counters.
namespace CLRS.MST.ExecutablePrim.ArrayPrimopen Finsetvariable {n : Nat} {E : Type} [LinearOrder E]def indexPrefix (n : Nat) : Nat → List (Fin n) × Nat
| 0 => ([], 0)
| i + 1 =>
let old := indexPrefix n i
if h : i < n then (⟨i,h⟩ :: old.1, old.2 + 1) else old
theorem indexPrefix_spec (i : Nat) (hi : i ≤ n) :
(indexPrefix n i).1.length = i ∧ (indexPrefix n i).2 = i ∧
∀ v : Fin n, v ∈ (indexPrefix n i).1 ↔ v.val < i := by
induction i with
| zero => simp [indexPrefix]
| succ i ih =>
have old := ih (by omega)
have hin : i < n := by omega
simp only [indexPrefix, hin, ↓reduceDIte, List.length_cons, old.1, old.2.1]
refine ⟨trivial,trivial,?_⟩
intro v
simp only [List.mem_cons, old.2.2 v, Fin.ext_iff]
omegaThe counters refer to queue cells, adjacency rows, comparisons, and visits.
structure Result (n : Nat) (E : Type) where
edges : List E
cells : Cells n E
rounds : Nat
extracts : Nat
queueReads : Nat
queueWrites : Nat
comparisons : Nat
edgeVisits : Nat
adjacencyReads : Nat
preparationWrites : Nat
deriving Reprdef run {G : FiniteGraph (Fin n) E} (adj : Adjacency G) (w : E → Nat)
(indices : List (Fin n)) : Nat → Cells n E → Result n E
| 0, q => ⟨[],q,0,0,0,0,0,0,0,0⟩
| fuel + 1, q =>
let choice := scan q indices
match choice.choice with
| none => ⟨[],q,1,0,choice.reads,0,choice.comparisons,0,0,0⟩
| some (u,c) =>
match c.parent with
| none => ⟨[],q,1,0,choice.reads,0,choice.comparisons,0,0,0⟩
| some e =>
let next := advance adj w q u
let rest := run adj w indices fuel next.cells
⟨e :: rest.edges, rest.cells, rest.rounds + 1, rest.extracts + 1,
choice.reads + 1 + next.edgeVisits + rest.queueReads,
1 + next.writes + rest.queueWrites,
choice.comparisons + next.tests + rest.comparisons,
next.edgeVisits + rest.edgeVisits, rest.adjacencyReads + 1, 0⟩Start at the supplied root, scan its row, then allow n further extraction attempts.
def execute {G : FiniteGraph (Fin n) E} (adj : Adjacency G) (w : E → Nat)
(root : Fin n) : Result n E :=
let initial := initialCells (E := E) n
let indices := indexPrefix n n
let seed := advance adj w initial.1 root
let rest := run adj w indices.1 n seed.cells
{ rest with
extracts := rest.extracts + 1
queueReads := 1 + seed.edgeVisits + rest.queueReads
queueWrites := 1 + seed.writes + rest.queueWrites
comparisons := seed.tests + rest.comparisons
edgeVisits := seed.edgeVisits + rest.edgeVisits
adjacencyReads := rest.adjacencyReads + 1
preparationWrites := initial.2 + indices.2 }Active vertices are the remaining potential, weighted by any nonnegative cost.
def remaining (weight : Fin n → Nat) (q : Cells n E) : Nat :=
∑ v : Fin n, if (read q v).active then weight v else 0
omit [LinearOrder E] in
theorem remaining_deactivate (weight : Fin n → Nat) (q : Cells n E) (u : Fin n)
(hu : (read q u).active = true) :
remaining weight (deactivate q u) + weight u = remaining weight q := by
have hnew := Finset.sum_erase_add (Finset.univ : Finset (Fin n))
(fun v => if (read (deactivate q u) v).active then weight v else 0) (mem_univ u)
have hold := Finset.sum_erase_add (Finset.univ : Finset (Fin n))
(fun v => if (read q v).active then weight v else 0) (mem_univ u)
have hs : (∑ v ∈ Finset.univ.erase u,
if (read (deactivate q u) v).active then weight v else 0) =
∑ v ∈ Finset.univ.erase u, if (read q v).active then weight v else 0 := by
apply Finset.sum_congr rfl
intro v hv
have huv : u ≠ v := (Finset.ne_of_mem_erase hv).symm
simp [huv]
rw [hs] at hnew
simp only [deactivate_active, ↓reduceIte, Bool.false_eq_true, Nat.add_zero] at hnew
simp only [hu, ↓reduceIte] at hold
simp only [remaining, deactivate_active]
omega@[simp] theorem remaining_advance {G : FiniteGraph (Fin n) E} (adj : Adjacency G)
(w : E → Nat) (weight : Fin n → Nat) (q : Cells n E) (u : Fin n) :
remaining weight (advance adj w q u).cells = remaining weight (deactivate q u) := by
simp [remaining, advance]A removed adjacency row is subtracted from the remaining scan potential.
theorem advance_remaining {G : FiniteGraph (Fin n) E} (adj : Adjacency G)
(w : E → Nat) (q : Cells n E) (u : Fin n) (hu : (read q u).active = true) :
remaining (fun v => (adj.rows[v.val]).length) (advance adj w q u).cells +
(advance adj w q u).edgeVisits = remaining (fun v => (adj.rows[v.val]).length) q := by
rw [remaining_advance]
have hc := (relaxAll_counts w (deactivate q u) adj.rows[u.val]).1
change remaining _ (deactivate q u) +
(relaxAll w (deactivate q u) adj.rows[u.val]).edgeVisits = _
rw [hc]
exact remaining_deactivate _ q u huThe inequalities account for every counter emitted by the loop.
theorem run_counts {G : FiniteGraph (Fin n) E} (adj : Adjacency G) (w : E → Nat)
(indices : List (Fin n)) (fuel : Nat) (q : Cells n E) :
let r := run adj w indices fuel q
r.rounds ≤ fuel ∧
r.extracts + remaining (fun _ => 1) r.cells ≤ remaining (fun _ => 1) q ∧
r.edgeVisits + remaining (fun v => (adj.rows[v.val]).length) r.cells ≤
remaining (fun v => (adj.rows[v.val]).length) q ∧
r.queueReads ≤ fuel * indices.length + r.extracts + r.edgeVisits ∧
r.queueWrites ≤ r.extracts + r.edgeVisits ∧
r.comparisons ≤ fuel * indices.length + r.edgeVisits ∧
r.adjacencyReads = r.extracts ∧ r.preparationWrites = 0 := by
induction fuel generalizing q with
| zero => simp [run]
| succ fuel ih =>
have hc := scan_counts q indices
simp only [run, Nat.add_mul, Nat.one_mul]
split
· simp only
refine ⟨?_, ?_, ?_, ?_, ?_, ?_, trivial, trivial⟩ <;> omega
· rename_i u c hscan
have hu := (scan_some q indices u c hscan).2.2.1
have hcell := (scan_some q indices u c hscan).2.1
rw [hcell] at hu
split
· simp only
refine ⟨?_, ?_, ?_, ?_, ?_, ?_, trivial, trivial⟩ <;> omega
· have hi := ih (advance adj w q u).cells
have hp := advance_remaining adj w q u hu
have hv : remaining (fun _ => 1) (advance adj w q u).cells + 1 =
remaining (fun _ => 1) q := by
rw [remaining_advance]; exact remaining_deactivate _ q u hu
have ha := relaxAll_counts w (deactivate q u) adj.rows[u.val]
change (advance adj w q u).edgeVisits = _ ∧
(advance adj w q u).tests ≤ _ ∧
(advance adj w q u).writes ≤ (advance adj w q u).tests at ha
simp only
refine ⟨?_, ?_, ?_, ?_, ?_, ?_, ?_, trivial⟩ <;> omegaThe generated execution visits at most the input's two incidences per edge.
theorem execute_edgeVisits_le {G : FiniteGraph (Fin n) E} (adj : Adjacency G)
(w : E → Nat) (root : Fin n) : (execute adj w root).edgeVisits ≤ 2 * G.edges.card := by
have hc := (run_counts adj w (indexPrefix n n).1 n
(advance adj w (initialCells n).1 root).cells).2.2.1
have hs := advance_remaining adj w (initialCells (E := E) n).1 root (by simp)
have ht : remaining (fun v => (adj.rows[v.val]).length) (initialCells (E := E) n).1 =
2 * G.edges.card := by simp [remaining, adj.total_length]
rw [ht] at hs
simp only [execute]
omegaEach active vertex is extracted at most once, including the initial root.
theorem execute_extracts_le {G : FiniteGraph (Fin n) E} (adj : Adjacency G)
(w : E → Nat) (root : Fin n) : (execute adj w root).extracts ≤ n := by
have hc := (run_counts adj w (indexPrefix n n).1 n
(advance adj w (initialCells n).1 root).cells).2.1
have hs : remaining (fun _ => 1) (advance adj w (initialCells (E := E) n).1 root).cells + 1 = n := by
rw [remaining_advance]
simpa [remaining] using remaining_deactivate (fun _ => 1)
(initialCells (E := E) n).1 root (by simp)
simp only [execute]
omegaThe actual work units include preparation, array reads/writes, and key comparisons.
def cellWork (r : Result n E) : Nat :=
r.preparationWrites + r.queueReads + r.queueWrites + r.adjacencyReads + r.comparisons
theorem execute_cost_bound {G : FiniteGraph (Fin n) E} (adj : Adjacency G)
(w : E → Nat) (root : Fin n) :
cellWork (execute adj w root) ≤ 2 * n * n + 5 * n + 6 * G.edges.card := by
have hi := indexPrefix_spec n (Nat.le_refl n)
have hc := run_counts adj w (indexPrefix n n).1 n
(advance adj w (initialCells n).1 root).cells
have hs := relaxAll_counts w (deactivate (initialCells (E := E) n).1 root)
adj.rows[root.val]
have hr := advance_remaining adj w (initialCells (E := E) n).1 root (by simp)
have hv : remaining (fun _ => 1) (advance adj w (initialCells (E := E) n).1 root).cells + 1 =
remaining (fun _ => 1) (initialCells (E := E) n).1 := by
rw [remaining_advance]; exact remaining_deactivate _ _ root (by simp)
have hinit : remaining (fun _ => 1) (initialCells (E := E) n).1 = n := by
simp [remaining]
have htotal : remaining (fun v => (adj.rows[v.val]).length) (initialCells (E := E) n).1 =
2 * G.edges.card := by simp [remaining, adj.total_length]
change (advance adj w (initialCells n).1 root).edgeVisits = _ ∧
(advance adj w (initialCells n).1 root).tests ≤ _ ∧
(advance adj w (initialCells n).1 root).writes ≤
(advance adj w (initialCells n).1 root).tests at hs
rw [hinit] at hv
rw [htotal] at hr
simp only [cellWork, execute, initialCells_writes, hi.1, hi.2.1] at *
nlinarith [hc.2.1, hc.2.2.1, hc.2.2.2.1, hc.2.2.2.2.1, hc.2.2.2.2.2.1]When the finite index universe is exactly the graph vertices, the bound uses |V|.
theorem execute_cost_bound_vertices {G : FiniteGraph (Fin n) E} (adj : Adjacency G)
(w : E → Nat) (root : Fin n) (hV : G.vertices = Finset.univ) :
cellWork (execute adj w root) ≤
2 * G.vertices.card * G.vertices.card + 5 * G.vertices.card + 6 * G.edges.card := by
simpa [hV] using execute_cost_bound adj w rootend CLRS.MST.ExecutablePrim.ArrayPrim