/- Port of `Language/Graphs.agda`. Representation note: `nodes : Vec (List BasicStmt) size` becomes `nodes : Fin size → List BasicStmt`. With that, the `Data.Vec` lookup/append lemma stack (`lookup-++ˡ/ʳ`, `cast-is-id`, …) lifts into mathlib's `Fin.append` with `Fin.append_left` / `Fin.append_right`. Correspondence: _↑ˡ_/_↑ʳ_ (on Fin) ↦ Fin.castAdd / Fin.natAdd (mathlib) _↑ˡⁱ_/_↑ʳⁱ_ ↦ liftIdxL / liftIdxR _↑ˡᵉ_/_↑ʳᵉ_ ↦ liftEdgeL / liftEdgeR _∙_ ↦ Graph.comp (scoped notation ∙) _↦_ ↦ Graph.link (scoped notation ⤳) loop ↦ Graph.loop _skipto_ ↦ Graph.skipto _[_] ↦ Graph.nodes (plain application) singleton, wrap ↦ Graph.singleton, Graph.wrap buildCfg ↦ buildCfg indices ↦ List.finRange (mathlib; `fins` from Utils.agda) indices-complete ↦ List.mem_finRange indices-Unique ↦ List.nodup_finRange predecessors ↦ Graph.predecessors edge⇒predecessor ↦ Graph.mem_predecessors_of_edge predecessor⇒edge ↦ Graph.edge_of_mem_predecessors -/ import Spa.Language.Base import Mathlib.Data.Fin.Tuple.Basic import Mathlib.Data.List.ProdSigma import Mathlib.Data.List.FinRange namespace Spa structure Graph where size : ℕ nodes : Fin size → List BasicStmt edges : List (Fin size × Fin size) inputs : List (Fin size) outputs : List (Fin size) namespace Graph abbrev Index (g : Graph) : Type := Fin g.size abbrev Edge (g : Graph) : Type := g.Index × g.Index /-- Agda: `_↑ˡⁱ_`. -/ def liftIdxL {n : ℕ} (l : List (Fin n)) (m : ℕ) : List (Fin (n + m)) := l.map (Fin.castAdd m) /-- Agda: `_↑ʳⁱ_`. -/ def liftIdxR (n : ℕ) {m : ℕ} (l : List (Fin m)) : List (Fin (n + m)) := l.map (Fin.natAdd n) /-- Agda: `_↑ˡᵉ_` (with `_↑ˡ_` on pairs inlined). -/ def liftEdgeL {n : ℕ} (l : List (Fin n × Fin n)) (m : ℕ) : List (Fin (n + m) × Fin (n + m)) := l.map (fun e => (e.1.castAdd m, e.2.castAdd m)) /-- Agda: `_↑ʳᵉ_` (with `_↑ʳ_` on pairs inlined). -/ def liftEdgeR (n : ℕ) {m : ℕ} (l : List (Fin m × Fin m)) : List (Fin (n + m) × Fin (n + m)) := l.map (fun e => (e.1.natAdd n, e.2.natAdd n)) /-- Agda: `_∙_` — disjoint union. -/ def comp (g₁ g₂ : Graph) : Graph where size := g₁.size + g₂.size nodes := Fin.append g₁.nodes g₂.nodes edges := liftEdgeL g₁.edges g₂.size ++ liftEdgeR g₁.size g₂.edges inputs := liftIdxL g₁.inputs g₂.size ++ liftIdxR g₁.size g₂.inputs outputs := liftIdxL g₁.outputs g₂.size ++ liftIdxR g₁.size g₂.outputs @[inherit_doc] scoped infixr:70 " ∙ " => Graph.comp /-- Agda: `_↦_` — sequencing: all outputs of `g₁` feed all inputs of `g₂`. -/ def link (g₁ g₂ : Graph) : Graph where size := g₁.size + g₂.size nodes := Fin.append g₁.nodes g₂.nodes edges := liftEdgeL g₁.edges g₂.size ++ liftEdgeR g₁.size g₂.edges ++ (liftIdxL g₁.outputs g₂.size).product (liftIdxR g₁.size g₂.inputs) inputs := liftIdxL g₁.inputs g₂.size outputs := liftIdxR g₁.size g₂.outputs @[inherit_doc] scoped infixr:70 " ⤳ " => Graph.link /-- The entry node of a `loop` graph. -/ def loopIn (g : Graph) : Fin (2 + g.size) := (0 : Fin 2).castAdd g.size /-- The exit node of a `loop` graph. -/ def loopOut (g : Graph) : Fin (2 + g.size) := (1 : Fin 2).castAdd g.size /-- Agda: `loop`. -/ def loop (g : Graph) : Graph where size := 2 + g.size nodes := Fin.append (fun _ : Fin 2 => []) g.nodes edges := liftEdgeR 2 g.edges ++ (liftIdxR 2 g.inputs).map (g.loopIn, ·) ++ (liftIdxR 2 g.outputs).map (·, g.loopOut) ++ [(g.loopOut, g.loopIn), (g.loopIn, g.loopOut)] inputs := [g.loopIn] outputs := [g.loopOut] @[simp] theorem loop_inputs (g : Graph) : (loop g).inputs = [g.loopIn] := rfl @[simp] theorem loop_outputs (g : Graph) : (loop g).outputs = [g.loopOut] := rfl /-- Agda: `_skipto_` (unused by `buildCfg`, ported for parity). -/ def skipto (g₁ g₂ : Graph) : Graph where size := g₁.size + g₂.size nodes := Fin.append g₁.nodes g₂.nodes edges := liftEdgeL g₁.edges g₂.size ++ liftEdgeR g₁.size g₂.edges ++ (liftIdxL g₁.inputs g₂.size).product (liftIdxR g₁.size g₂.inputs) inputs := liftIdxL g₁.inputs g₂.size outputs := liftIdxR g₁.size g₂.inputs /-- Agda: `singleton`. -/ def singleton (bss : List BasicStmt) : Graph where size := 1 nodes := fun _ => bss edges := [] inputs := [0] outputs := [0] /-- Agda: `wrap`. -/ def wrap (g : Graph) : Graph := singleton [] ⤳ g ⤳ singleton [] end Graph open Graph in /-- Agda: `buildCfg`. -/ def buildCfg : Stmt → Graph | .basic bs => Graph.singleton [bs] | .andThen s₁ s₂ => buildCfg s₁ ⤳ buildCfg s₂ | .ifElse _ s₁ s₂ => buildCfg s₁ ∙ buildCfg s₂ | .whileLoop _ s => Graph.loop (buildCfg s) namespace Graph variable (g : Graph) /-- Agda: `indices` (`fins` is mathlib's `List.finRange`). -/ def indices : List g.Index := List.finRange g.size /-- Agda: `indices-complete`. -/ theorem mem_indices (idx : g.Index) : idx ∈ g.indices := List.mem_finRange idx /-- Agda: `indices-Unique`. -/ theorem nodup_indices : g.indices.Nodup := List.nodup_finRange g.size /-- Agda: `predecessors`. -/ def predecessors (idx : g.Index) : List g.Index := g.indices.filter (fun idx' => (idx', idx) ∈ g.edges) /-- Agda: `edge⇒predecessor`. -/ theorem mem_predecessors_of_edge {idx₁ idx₂ : g.Index} (h : (idx₁, idx₂) ∈ g.edges) : idx₁ ∈ g.predecessors idx₂ := List.mem_filter.mpr ⟨g.mem_indices idx₁, by simpa using h⟩ /-- Agda: `predecessor⇒edge`. -/ theorem edge_of_mem_predecessors {idx₁ idx₂ : g.Index} (h : idx₁ ∈ g.predecessors idx₂) : (idx₁, idx₂) ∈ g.edges := by simpa using (List.mem_filter.mp h).2 end Graph end Spa