Research source · Plain text
StackEncoding.lean
site/public/formal/persistent-stacks/lean/FPRDTransfer/StackEncoding.lean
248 lines. Source is displayed for inspection; it is not executed by this page.
File fingerprint
SHA-256: 3f5067c5c5f7dc940583e96d2c72e2cebdd480173743ecce01a346ee0d1623e9
/-
The persistent-stack encoding inside a scaffold.
A node's label is a tuple with one component per logical stack; component `j`
holds the top symbol of stack `j` at that node, or `none` to mean "stack `j`
is empty here". Port `j` holds the predecessor for stack `j`. Decoding
stack `j` from a node walks port `j`, reading component `j`, and stops at the
first `none`.
The `CleanEmpty` invariant below is the load-bearing discovery of this
formalization. Nothing in the informal argument states it, but without it the
encoding is *wrong* for a pop of an empty stack: an empty stack's port may
still point at an older node whose `j`-component is labelled, and the
prescribed pop would then read that stale symbol and resurrect it.
`CleanEmpty` says this never happens, and `Compile` proves the compiler
maintains it.
-/
import FPRDTransfer.LMR
import FPRDTransfer.StackMachine
namespace FPRD
/-- The working alphabet: one component per logical stack. -/
abbrev Tuple (s : Nat) (A : Type) := Fin s → Option A
variable {s : Nat} {A : Type}
/-- Component `j` of the label at node `v`; `none` when `v` is unlabelled.
Written with `Option.bind` rather than a `match` so that it and the reader in
`Compile` reduce to syntactically identical terms. -/
def labelComp (S : Scaffold s (Tuple s A)) (v : Nat) (j : Fin s) : Option A :=
(S.label v).bind fun g => g j
/-- `labelComp` extended to a possibly missing node. -/
def labelCompOpt (S : Scaffold s (Tuple s A)) (v : Option Nat) (j : Fin s) :
Option A :=
v.bind fun u => labelComp S u j
@[simp] theorem labelCompOpt_none (S : Scaffold s (Tuple s A)) (j : Fin s) :
labelCompOpt S none j = none := rfl
@[simp] theorem labelCompOpt_some (S : Scaffold s (Tuple s A)) (u : Nat) (j : Fin s) :
labelCompOpt S (some u) j = labelComp S u j := rfl
/-- Decode stack `j` from node `v`, top symbol first.
The `m < v` guard keeps this total on every scaffold, including one that used
`SELF`; a self-loop is not a predecessor, so decoding stops there. For the
scaffolds built by the compiler the guard always holds, by `Backward`. -/
def decode (S : Scaffold s (Tuple s A)) (j : Fin s) (v : Nat) : List A :=
match labelComp S v j with
| none => []
| some a =>
match S.edge v j with
| none => [a]
| some m => if _h : m < v then a :: decode S j m else [a]
termination_by v
decreasing_by exact _h
/-- Decode from a possibly missing node. -/
def decodeOpt (S : Scaffold s (Tuple s A)) (j : Fin s) : Option Nat → List A
| none => []
| some m => decode S j m
@[simp] theorem decodeOpt_none (S : Scaffold s (Tuple s A)) (j : Fin s) :
decodeOpt S j none = [] := rfl
@[simp] theorem decodeOpt_some (S : Scaffold s (Tuple s A)) (j : Fin s) (m : Nat) :
decodeOpt S j (some m) = decode S j m := rfl
theorem decode_empty (S : Scaffold s (Tuple s A)) (j : Fin s) (v : Nat)
(h : labelComp S v j = none) : decode S j v = [] := by
rw [decode, h]
theorem decode_edge_none (S : Scaffold s (Tuple s A)) (j : Fin s) (v : Nat) (a : A)
(h : labelComp S v j = some a) (he : S.edge v j = none) :
decode S j v = [a] := by
rw [decode, h, he]
theorem decode_edge_some (S : Scaffold s (Tuple s A)) (j : Fin s) (v m : Nat) (a : A)
(h : labelComp S v j = some a) (he : S.edge v j = some m) (hlt : m < v) :
decode S j v = a :: decode S j m := by
rw [decode, h, he]
simp [hlt]
/-- The head of a decoded stack is exactly the label component: the machine's
view of the stack tops is faithful. -/
theorem decode_head (S : Scaffold s (Tuple s A)) (j : Fin s) (v : Nat) :
(decode S j v).head? = labelComp S v j := by
cases h : labelComp S v j with
| none => rw [decode_empty S j v h]; rfl
| some a =>
cases he : S.edge v j with
| none => rw [decode_edge_none S j v a h he]; rfl
| some m =>
by_cases hlt : m < v
· rw [decode_edge_some S j v m a h he hlt]; rfl
· rw [decode, h, he]; simp [hlt]
/-- Under `Backward`, a nonempty decoded stack splits into its top symbol and
the decode of its port target. -/
theorem decode_cons (S : Scaffold s (Tuple s A)) (hb : S.Backward) (j : Fin s)
(v : Nat) (a : A) (h : labelComp S v j = some a) :
decode S j v = a :: decodeOpt S j (S.edge v j) := by
cases he : S.edge v j with
| none => rw [decode_edge_none S j v a h he]; simp
| some m =>
have hlt : m < v := hb v j m he
rw [decode_edge_some S j v m a h he hlt]
simp
/-! ### A structurally recursive twin, for kernel computation
`decode` is defined by well-founded recursion, which the kernel does not
reduce, so concrete checks cannot be closed by `decide` through it.
`decodeAux` is the same function with an explicit fuel parameter, hence
structurally recursive and reducible. `decode_eq_decodeAux` licenses using it:
the examples state their claims about `decode` and compute with `decodeAux`. -/
def decodeAux (S : Scaffold s (Tuple s A)) (j : Fin s) : Nat → Nat → List A
| 0, _ => []
| fuel + 1, v =>
match labelComp S v j with
| none => []
| some a =>
match S.edge v j with
| none => [a]
| some m => if m < v then a :: decodeAux S j fuel m else [a]
theorem decodeAux_eq (S : Scaffold s (Tuple s A)) (j : Fin s) :
∀ (fuel v : Nat), v < fuel → decodeAux S j fuel v = decode S j v := by
intro fuel
induction fuel with
| zero => intro v hv; exact absurd hv (by omega)
| succ f ih =>
intro v hv
cases h : labelComp S v j with
| none =>
rw [decode_empty S j v h]
simp only [decodeAux, h]
| some a =>
cases he : S.edge v j with
| none =>
rw [decode_edge_none S j v a h he]
simp only [decodeAux, h, he]
| some m =>
by_cases hlt : m < v
· rw [decode_edge_some S j v m a h he hlt]
simp only [decodeAux, h, he, if_pos hlt]
rw [ih m (by omega)]
· rw [decode, h, he]
simp only [decodeAux, h, he, hlt, if_false]
simp
theorem decode_eq_decodeAux (S : Scaffold s (Tuple s A)) (j : Fin s) (v : Nat) :
decode S j v = decodeAux S j (v + 1) v :=
(decodeAux_eq S j (v + 1) v (Nat.lt_succ_self v)).symm
/-- Empty stacks have clean predecessor chains: if node `v` ends stack `j`,
then its port-`j` target also ends stack `j`. -/
def CleanEmpty (S : Scaffold s (Tuple s A)) : Prop :=
∀ v j, labelComp S v j = none → labelCompOpt S (S.edge v j) j = none
/-- The consequence that makes an empty pop correct. -/
theorem decodeOpt_of_empty (S : Scaffold s (Tuple s A)) (hc : CleanEmpty S)
(v : Nat) (j : Fin s) (h : labelComp S v j = none) :
decodeOpt S j (S.edge v j) = [] := by
have hcv := hc v j h
cases he : S.edge v j with
| none => rfl
| some m =>
rw [he] at hcv
simp only [labelCompOpt_some] at hcv
simp only [decodeOpt_some]
exact decode_empty S j m hcv
/-- Decoding only inspects nodes at most `n`, so two scaffolds agreeing up to
`n` decode alike from `n`. Proved with an explicit bound so that no
strong-induction facility is needed. -/
theorem decode_congr_bounded (S S' : Scaffold s (Tuple s A)) (j : Fin s) :
∀ (bound n : Nat), n ≤ bound →
(∀ u, u ≤ n → S'.label u = S.label u) →
(∀ u, u ≤ n → S'.edge u j = S.edge u j) →
decode S' j n = decode S j n := by
intro bound
induction bound with
| zero =>
intro n hn hlab hedge
have hn0 : n = 0 := Nat.le_zero.mp hn
subst hn0
have hl : labelComp S' 0 j = labelComp S 0 j := by
simp only [labelComp, hlab 0 (Nat.le_refl 0)]
cases h : labelComp S 0 j with
| none =>
rw [decode_empty S' j 0 (by rw [hl, h]), decode_empty S j 0 h]
| some a =>
have he : S'.edge 0 j = S.edge 0 j := hedge 0 (Nat.le_refl 0)
cases hE : S.edge 0 j with
| none =>
rw [decode_edge_none S' j 0 a (by rw [hl, h]) (by rw [he, hE]),
decode_edge_none S j 0 a h hE]
| some m =>
rw [decode, hl, h, he, hE, decode, h, hE]
simp
| succ b ih =>
intro n hn hlab hedge
have hl : labelComp S' n j = labelComp S n j := by
simp only [labelComp, hlab n (Nat.le_refl n)]
cases h : labelComp S n j with
| none =>
rw [decode_empty S' j n (by rw [hl, h]), decode_empty S j n h]
| some a =>
have he : S'.edge n j = S.edge n j := hedge n (Nat.le_refl n)
cases hE : S.edge n j with
| none =>
rw [decode_edge_none S' j n a (by rw [hl, h]) (by rw [he, hE]),
decode_edge_none S j n a h hE]
| some m =>
by_cases hlt : m < n
· have hmb : m ≤ b := by omega
have hrec : decode S' j m = decode S j m :=
ih m hmb (fun u hu => hlab u (by omega)) (fun u hu => hedge u (by omega))
rw [decode_edge_some S' j n m a (by rw [hl, h]) (by rw [he, hE]) hlt,
decode_edge_some S j n m a h hE hlt, hrec]
· rw [decode, hl, h, he, hE, decode, h, hE]
simp [hlt]
theorem decode_congr (S S' : Scaffold s (Tuple s A)) (j : Fin s) (n : Nat)
(hlab : ∀ u, u ≤ n → S'.label u = S.label u)
(hedge : ∀ u, u ≤ n → S'.edge u j = S.edge u j) :
decode S' j n = decode S j n :=
decode_congr_bounded S S' j n n (Nat.le_refl n) hlab hedge
/-- The base scaffold decodes to empty stacks and is clean. -/
theorem base_labelComp (j : Fin s) (v : Nat) :
labelComp (Scaffold.base s (Tuple s A)) v j = none := rfl
theorem base_decode (j : Fin s) (v : Nat) :
decode (Scaffold.base s (Tuple s A)) j v = [] :=
decode_empty _ j v (base_labelComp j v)
theorem base_cleanEmpty : CleanEmpty (Scaffold.base s (Tuple s A)) := by
intro v j _
rfl
end FPRD