Clean up comments in Graphs.lean and Program.lean

This commit is contained in:
2026-08-09 18:17:02 -05:00
parent c0542d0811
commit df4d072f22
2 changed files with 32 additions and 45 deletions

View File

@@ -215,33 +215,29 @@ lemma wrap_outputs (g : GGraph (Option β)) :
/-! ### Embeddings /-! ### Embeddings
Each composition operator includes its operands into the result via an index To be able to reason compositionally about traces through the graphs,
translation that preserves node payloads and edges. `Embed` captures exactly we need to be able to reason about how a trace within a sub-graph maps
those two facts, so anything defined from `nodes` and `edges` (traces, node to the full graph. Fortunately, graphs are built using composition operators,
labels, …) can be transported along an embedding once, instead of once per and these composition operators always include their arguments as embedded
operator. subgraphs in the full result. Moreover, each embedding "just" offsets the
existing node IDs by a given amount.
`Embed` is deliberately a structure rather than a class: for `g ⤳ g`, both the This section formalizes this fact by providing an `Embed` type that
left and the right inclusion inhabit the same type `Embed g (g ⤳ g)`, so represents an offset-based embedding, and showing that such an embedding
instance resolution could silently pick the wrong copy. Embeddings into a exists for all arguments given to graph composition operators. Furthermore,
composed graph are non-canonical by design; a named witness says which because of the offset-based embedding, we can determine whether a node
inclusion is meant. came from a particular subgraph simply by examining its offset and sub-graph
size. This is captured by `Embed.mem_range_iff`. -/
Concretely, every embedding here is a *constant index shift*: `∙` and `⤳` lay
their operands out in consecutive blocks via `Fin.append`, and `loop` prepends its
two synthetic nodes. Storing the offset rather than an arbitrary function makes
the range of an embedding an interval by construction (`Embed.mem_range_iff`),
which is the "is this node inside that subgraph?" test. -/
/-- Translate an index of `g` into `h` by a constant offset. -/ /-- Translate an index of `g` into `h` by a constant offset. -/
def shift {g h : GGraph α} (off : ) (hle : g.size + off h.size) (i : g.Index) : private def shift {g h : GGraph α} (off : ) (hle : g.size + off h.size) (i : g.Index) :
h.Index := off + i.val, by omega h.Index := off + i.val, by omega
@[simp] lemma shift_val {g h : GGraph α} {off : } (hle : g.size + off h.size) @[simp] lemma shift_val {g h : GGraph α} {off : } (hle : g.size + off h.size)
(i : g.Index) : (shift (h := h) off hle i).val = off + i.val := rfl (i : g.Index) : (shift (h := h) off hle i).val = off + i.val := rfl
/-- An embedding of `g` into `h`: a constant index shift preserving node payloads /-- A special-case embedding of `g` into `h` in which all edges and nodes
and edges. -/ of `g` are present in `h` at a given offset `off`. -/
structure Embed (g h : GGraph α) where structure Embed (g h : GGraph α) where
off : off :
size_le : g.size + off h.size size_le : g.size + off h.size
@@ -269,7 +265,7 @@ lemma Embed.mem_range_iff {g h : GGraph α} (e : Embed g h) (j : h.Index) :
the `Fin.append` lemmas are stated in — so this lets them keep those proofs the `Fin.append` lemmas are stated in — so this lets them keep those proofs
verbatim while `Embed` stores only the offset. The trailing two arguments are verbatim while `Embed` stores only the offset. The trailing two arguments are
boilerplate at every call site and default to discharging themselves. -/ boilerplate at every call site and default to discharging themselves. -/
def Embed.ofIndexMap {g h : GGraph α} (off : ) (k : g.Index h.Index) private def Embed.ofIndexMap {g h : GGraph α} (off : ) (k : g.Index h.Index)
(hn : i, h.nodes (k i) = g.nodes i) (hn : i, h.nodes (k i) = g.nodes i)
(hem : {e : g.Edge}, e g.edges (k e.1, k e.2) h.edges) (hem : {e : g.Edge}, e g.edges (k e.1, k e.2) h.edges)
(hle : g.size + off h.size := by (hle : g.size + off h.size := by
@@ -283,45 +279,49 @@ def Embed.ofIndexMap {g h : GGraph α} (off : ) (k : g.Index → h.Index)
have hs : i, shift (h := h) off hle i = k i := fun i => Fin.ext (by simp [hk]) have hs : i, shift (h := h) off hle i = k i := fun i => Fin.ext (by simp [hk])
rw [hs, hs]; exact hem hmem rw [hs, hs]; exact hem hmem
/-- Embeddings compose; offsets add. -/ /-- Embeddings compose (offsets add). -/
def Embed.trans {g₁ g₂ g₃ : GGraph α} (e₁ : Embed g₁ g₂) (e₂ : Embed g₂ g₃) : def Embed.trans {g₁ g₂ g₃ : GGraph α} (e₁ : Embed g₁ g₂) (e₂ : Embed g₂ g₃) :
Embed g₁ g₃ := Embed g₁ g₃ :=
.ofIndexMap (e₂.off + e₁.off) (fun i => e₂.f (e₁.f i)) ofIndexMap (e₂.off + e₁.off) (fun i => e₂.f (e₁.f i))
(fun i => (e₂.nodes_eq (e₁.f i)).trans (e₁.nodes_eq i)) (fun i => (e₂.nodes_eq (e₁.f i)).trans (e₁.nodes_eq i))
(fun he => e₂.edges_mem (e₁.edges_mem he)) (fun he => e₂.edges_mem (e₁.edges_mem he))
(hle := by have := e₁.size_le; have := e₂.size_le; omega) (hle := by have := e₁.size_le; have := e₂.size_le; omega)
(hk := fun i => by simp; omega) (hk := fun i => by simp; omega)
/-! The five inclusions `Stmt.cfg` uses, one per composition operator. Each `_f`
lemma recovers the `Fin.castAdd`/`Fin.natAdd` form for callers stating indices
that way (`Spa/Language/Properties.lean`). -/
/-- The left operand's inclusion into a sequenced graph. -/ /-- The left operand's inclusion into a sequenced graph. -/
def Embed.sequenceLeft (g₁ g₂ : GGraph α) : Embed g₁ (g₁ g₂) := def Embed.sequenceLeft (g₁ g₂ : GGraph α) : Embed g₁ (g₁ g₂) :=
.ofIndexMap 0 (fun i => i.castAdd g₂.size) (Fin.append_left g₁.nodes g₂.nodes) ofIndexMap 0 (fun i => i.castAdd g₂.size) (Fin.append_left g₁.nodes g₂.nodes)
(fun he => List.mem_append_left _ (List.mem_append_left _ (List.mem_map_of_mem _ he))) (fun he => List.mem_append_left _ (List.mem_append_left _ (List.mem_map_of_mem _ he)))
/-- The right operand's inclusion into a sequenced graph. -/ /-- The right operand's inclusion into a sequenced graph. -/
def Embed.sequenceRight (g₁ g₂ : GGraph α) : Embed g₂ (g₁ g₂) := def Embed.sequenceRight (g₁ g₂ : GGraph α) : Embed g₂ (g₁ g₂) :=
.ofIndexMap g₁.size (fun i => i.natAdd g₁.size) (Fin.append_right g₁.nodes g₂.nodes) ofIndexMap g₁.size (fun i => i.natAdd g₁.size) (Fin.append_right g₁.nodes g₂.nodes)
(fun he => List.mem_append_left _ (List.mem_append_right _ (List.mem_map_of_mem _ he))) (fun he => List.mem_append_left _ (List.mem_append_right _ (List.mem_map_of_mem _ he)))
/-- The left operand's inclusion into an overlaid graph. -/ /-- The left operand's inclusion into an overlaid graph. -/
def Embed.overlayLeft (g₁ g₂ : GGraph α) : Embed g₁ (g₁ g₂) := def Embed.overlayLeft (g₁ g₂ : GGraph α) : Embed g₁ (g₁ g₂) :=
.ofIndexMap 0 (fun i => i.castAdd g₂.size) (Fin.append_left g₁.nodes g₂.nodes) ofIndexMap 0 (fun i => i.castAdd g₂.size) (Fin.append_left g₁.nodes g₂.nodes)
(fun he => List.mem_append_left _ (List.mem_map_of_mem _ he)) (fun he => List.mem_append_left _ (List.mem_map_of_mem _ he))
/-- The right operand's inclusion into an overlaid graph. -/ /-- The right operand's inclusion into an overlaid graph. -/
def Embed.overlayRight (g₁ g₂ : GGraph α) : Embed g₂ (g₁ g₂) := def Embed.overlayRight (g₁ g₂ : GGraph α) : Embed g₂ (g₁ g₂) :=
.ofIndexMap g₁.size (fun i => i.natAdd g₁.size) (Fin.append_right g₁.nodes g₂.nodes) ofIndexMap g₁.size (fun i => i.natAdd g₁.size) (Fin.append_right g₁.nodes g₂.nodes)
(fun he => List.mem_append_right _ (List.mem_map_of_mem _ he)) (fun he => List.mem_append_right _ (List.mem_map_of_mem _ he))
/-- The body's inclusion into a `loop` graph. -/ /-- The body's inclusion into a `loop` graph. -/
def Embed.loop (g : GGraph (Option β)) : Embed g (GGraph.loop g) := def Embed.loop (g : GGraph (Option β)) : Embed g (GGraph.loop g) :=
.ofIndexMap 2 (fun i => i.natAdd 2) (Fin.append_right (fun _ : Fin 2 => none) g.nodes) ofIndexMap 2 (fun i => i.natAdd 2) (Fin.append_right (fun _ : Fin 2 => none) g.nodes)
(fun he => List.mem_append_left _ (List.mem_append_left _ (fun he => List.mem_append_left _ (List.mem_append_left _
(List.mem_append_left _ (List.mem_map_of_mem _ he)))) (List.mem_append_left _ (List.mem_map_of_mem _ he))))
/-- A `singleton` subgraph has exactly one node; this is where it sits in the ambient graph. -/
def Embed.singletonIndex {a : α} {h : GGraph α} (e : Embed (singleton a) h) : h.Index :=
e.f 0, Nat.zero_lt_one
@[simp] lemma Embed.nodes_singletonIndex {a : α} {h : GGraph α}
(e : Embed (singleton a) h) : h.nodes e.singletonIndex = a :=
e.nodes_eq 0, Nat.zero_lt_one
/-! `shift` puts the offset on the left (`off + i.val`), matching `Fin.natAdd`, so /-! `shift` puts the offset on the left (`off + i.val`), matching `Fin.natAdd`, so
every right inclusion is *definitionally* the form `Spa/Language/Properties.lean` every right inclusion is *definitionally* the form `Spa/Language/Properties.lean`
states its trace indices in. Only the left inclusions need a bridge, since their states its trace indices in. Only the left inclusions need a bridge, since their
@@ -331,18 +331,6 @@ Deliberately not `@[simp]`: they rewrite `Embed.f` back into `Fin.castAdd` form,
discarding the offset that `Embed.mem_range_iff` — and the subgraph-containment discarding the offset that `Embed.mem_range_iff` — and the subgraph-containment
tests built on it — reason with. -/ tests built on it — reason with. -/
/-- A `singleton` subgraph has exactly one node; this is where it sits in the
ambient graph. This is how a traversal that has descended to a `Stmt.basic`
reads off its CFG index, since `(Stmt.basic bs).cfg` is `singleton (some bs)`. -/
def Embed.singletonIndex {a : α} {h : GGraph α} (e : Embed (singleton a) h) : h.Index :=
e.f 0, Nat.zero_lt_one
/-- …and the node there carries exactly that statement — so an index obtained this
way comes with its payload already identified, with no lookup and no `Option`. -/
@[simp] lemma Embed.nodes_singletonIndex {a : α} {h : GGraph α}
(e : Embed (singleton a) h) : h.nodes e.singletonIndex = a :=
e.nodes_eq 0, Nat.zero_lt_one
lemma Embed.sequenceLeft_f (g₁ g₂ : GGraph α) (i : g₁.Index) : lemma Embed.sequenceLeft_f (g₁ g₂ : GGraph α) (i : g₁.Index) :
(Embed.sequenceLeft g₁ g₂).f i = i.castAdd g₂.size := Fin.ext (Nat.zero_add _) (Embed.sequenceLeft g₁ g₂).f i = i.castAdd g₂.size := Fin.ext (Nat.zero_add _)
lemma Embed.overlayLeft_f (g₁ g₂ : GGraph α) (i : g₁.Index) : lemma Embed.overlayLeft_f (g₁ g₂ : GGraph α) (i : g₁.Index) :

View File

@@ -31,8 +31,7 @@ def cfg : Graph := Graph.wrap p.rootStmt.cfg
/-- A state in the control flow `Spa.Graph` of this program. -/ /-- A state in the control flow `Spa.Graph` of this program. -/
abbrev State : Type := p.cfg.Index abbrev State : Type := p.cfg.Index
/-- The root statement's CFG sits inside the program's CFG (that graph `wrap`ped /-- The root statement's CFG sits inside the program's CFG. -/
in an entry and an exit node). -/
def rootEmbed : GGraph.Embed p.rootStmt.cfg p.cfg := def rootEmbed : GGraph.Embed p.rootStmt.cfg p.cfg :=
(GGraph.Embed.sequenceLeft p.rootStmt.cfg (Graph.singleton none)).trans (GGraph.Embed.sequenceLeft p.rootStmt.cfg (Graph.singleton none)).trans
(GGraph.Embed.sequenceRight (Graph.singleton none) _) (GGraph.Embed.sequenceRight (Graph.singleton none) _)