Research source · Plain text

LMR.lean

site/public/formal/persistent-stacks/lean/FPRDTransfer/LMR.lean

306 lines. Source is displayed for inspection; it is not executed by this page.

File fingerprint

SHA-256: a5ffd800621bde316dfcb8946b8254a750a4b23500bd45418fac29711ab8cbb8

/-
Loff-Moreira-Reis scaffolding automata.

Definitions 10-15 of "The computational power of Parsing Expression Grammars",
arXiv:1902.08272v2, transcribed at the operational level used by the transfer.
In particular:

* nodes are `0, 1, ..., top`, and node `0` is the unlabelled base;
* the degree `d` and distance `k` are free parameters of the automaton;
* `N_k` is a *partial d-ary labelled tree* -- the unfolding, carrying no node
  identity, so no transition can test whether two port words alias;
* exactly one node is appended per input symbol;
* a new edge is named by a port word of length at most `k` from the old top,
  or by `SELF`, or is missing;
* acceptance is membership of the final control state in `F`.

The carriers are polymorphic Lean types. Finiteness of the input alphabet,
control, and scaffold alphabet is imposed when applying the published finite
automaton theorem, rather than by `Fintype` fields on these operational
definitions. Auxiliary predicates such as `Backward`, `DistanceOk`, and
`SelfFree` state properties consumed by the transfer proof; they do not
strengthen the ambient automaton datatype.
-/

namespace FPRD

universe u

/-- Definition 12's edge-naming syntax: `[d)^{<=k} u {SELF, \emptyset}`.  The
length restriction is imposed separately, by `Automaton.DistanceOk`, so that
this datatype stays free of proof carrying. -/
inductive Target (d : Nat) where
  | path : List (Fin d) → Target d
  | self : Target d
  | missing : Target d

/-- Definition 10's operational carrier for a `(d, Γ)`-scaffold.

The ambient function representation can contain data above `top`. `base`
starts without such data, `append` preserves its absence on generated runs,
and the transfer's `Sim.aboveLabel` and `Sim.aboveEdge` fields record that
reachable invariant explicitly. -/
structure Scaffold (d : Nat) (Gamma : Type u) where
  top : Nat
  label : Nat → Option Gamma
  edge : Nat → Fin d → Option Nat

namespace Scaffold

variable {d : Nat} {Gamma : Type u}

/-- Definition 10's "edges point backwards", as a predicate.  A scaffold built
without `SELF` satisfies the strict form recorded here. -/
def Backward (S : Scaffold d Gamma) : Prop :=
  ∀ v i m, S.edge v i = some m → m < v

/-- Definition 10's path following.  `follow v p` is `v_k`, or `none` if `p` is
an invalid path from `v`. -/
def follow (S : Scaffold d Gamma) : Nat → List (Fin d) → Option Nat
  | v, [] => some v
  | v, i :: p =>
    match S.edge v i with
    | none => none
    | some u => S.follow u p

theorem follow_nil (S : Scaffold d Gamma) (v : Nat) : S.follow v [] = some v := rfl

theorem follow_cons (S : Scaffold d Gamma) (v : Nat) (i : Fin d) (p : List (Fin d)) :
    S.follow v (i :: p) =
      match S.edge v i with
      | none => none
      | some u => S.follow u p := rfl

theorem follow_cons_none (S : Scaffold d Gamma) (v : Nat) (i : Fin d)
    (p : List (Fin d)) (h : S.edge v i = none) : S.follow v (i :: p) = none := by
  rw [follow_cons, h]

theorem follow_cons_some (S : Scaffold d Gamma) (v : Nat) (i : Fin d)
    (p : List (Fin d)) (u : Nat) (h : S.edge v i = some u) :
    S.follow v (i :: p) = S.follow u p := by
  rw [follow_cons, h]

theorem follow_single (S : Scaffold d Gamma) (v : Nat) (i : Fin d) :
    S.follow v [i] = S.edge v i := by
  cases he : S.edge v i with
  | none => rw [S.follow_cons_none v i [] he]
  | some u => rw [S.follow_cons_some v i [] u he, S.follow_nil u]

/-- Under `Backward`, following a path never moves forward. -/
theorem follow_le (S : Scaffold d Gamma) (hb : S.Backward) :
    ∀ (p : List (Fin d)) (v u : Nat), S.follow v p = some u → u ≤ v := by
  intro p
  induction p with
  | nil =>
    intro v u h
    simp [follow_nil] at h
    omega
  | cons i p ih =>
    intro v u h
    rw [follow_cons] at h
    cases hv : S.edge v i with
    | none => rw [hv] at h; simp at h
    | some w =>
      rw [hv] at h
      have hw : w < v := hb v i w hv
      have := ih w u h
      omega

/-- The empty scaffold `S_0` of Definition 12: one unlabelled base node. -/
def base (d : Nat) (Gamma : Type u) : Scaffold d Gamma where
  top := 0
  label := fun _ => none
  edge := fun _ _ => none

theorem base_backward (d : Nat) (Gamma : Type u) : (base d Gamma).Backward := by
  intro v i m h
  simp [base] at h

/-- Definition 13's target resolution: a port word is followed from the old
top `t`, `SELF` names the node being created, and `missing` stays missing. -/
def resolve (S : Scaffold d Gamma) (t new : Nat) : Target d → Option Nat
  | .path p => S.follow t p
  | .self => some new
  | .missing => none

/-- Definition 13's `Step` on the scaffold: append one node, labelled `g`,
whose edges are named by `tgt`. -/
def append (S : Scaffold d Gamma) (g : Gamma) (tgt : Fin d → Target d) :
    Scaffold d Gamma where
  top := S.top + 1
  label := fun v => if v = S.top + 1 then some g else S.label v
  edge := fun v i =>
    if v = S.top + 1 then S.resolve S.top (S.top + 1) (tgt i) else S.edge v i

@[simp] theorem append_top (S : Scaffold d Gamma) (g : Gamma) (tgt : Fin d → Target d) :
    (S.append g tgt).top = S.top + 1 := rfl

@[simp] theorem append_label_new (S : Scaffold d Gamma) (g : Gamma)
    (tgt : Fin d → Target d) : (S.append g tgt).label (S.top + 1) = some g := by
  simp [append]

theorem append_label_old (S : Scaffold d Gamma) (g : Gamma) (tgt : Fin d → Target d)
    {v : Nat} (h : v ≠ S.top + 1) : (S.append g tgt).label v = S.label v := by
  simp [append, h]

@[simp] theorem append_edge_new (S : Scaffold d Gamma) (g : Gamma)
    (tgt : Fin d → Target d) (i : Fin d) :
    (S.append g tgt).edge (S.top + 1) i = S.resolve S.top (S.top + 1) (tgt i) := by
  simp [append]

theorem append_edge_old (S : Scaffold d Gamma) (g : Gamma) (tgt : Fin d → Target d)
    {v : Nat} (h : v ≠ S.top + 1) (i : Fin d) :
    (S.append g tgt).edge v i = S.edge v i := by
  simp [append, h]

/-- `append` preserves strict backwardness provided no target is `SELF`. -/
theorem append_backward (S : Scaffold d Gamma) (hb : S.Backward) (g : Gamma)
    (tgt : Fin d → Target d) (hself : ∀ i, tgt i ≠ .self) :
    (S.append g tgt).Backward := by
  intro v i m h
  by_cases hv : v = S.top + 1
  · subst hv
    rw [append_edge_new] at h
    cases htg : tgt i with
    | path p =>
      rw [htg] at h
      simp [resolve] at h
      have := follow_le S hb p S.top m h
      omega
    | self => exact absurd htg (hself i)
    | missing => rw [htg] at h; simp [resolve] at h
  · rw [append_edge_old S g tgt hv i] at h
    exact hb v i m h

end Scaffold

/-- Definition 11's `N_k(d, Γ)`: the partial `d`-ary `Γ`-labelled trees.

`missing` is the paper's `N_k(S, \emptyset) = \emptyset`; it is a separate
constructor rather than an `Option` wrapper because Lean forbids nesting an
inductive under a parameterised type whose parameter mentions the inductive.
The three cases at depth zero -- `missing`, `leaf none`, `leaf (some g)` --
keep "absent", "present but unlabelled", and "present and labelled" distinct,
exactly as the paper's `Γ ∪ {∅}` with a separate `∅` for a missing child. -/
inductive Unfold (d : Nat) (Gamma : Type u) : Nat → Type u where
  | missing : {k : Nat} → Unfold d Gamma k
  | leaf : Option Gamma → Unfold d Gamma 0
  | node : {k : Nat} → Option Gamma → (Fin d → Unfold d Gamma k) →
      Unfold d Gamma (k + 1)

namespace Unfold

variable {d : Nat} {Gamma : Type u} {k : Nat}

/-- Whether the root of an unfolding is a present node at all. -/
def present : Unfold d Gamma k → Bool
  | .missing => false
  | .leaf _ => true
  | .node _ _ => true

/-- The label at the root, `none` when the node is missing *or* unlabelled.

A transition is free to use `present` as well; the compiler in `Compile`
deliberately uses only `labelOf`, which is strictly less information. -/
def labelOf : Unfold d Gamma k → Option Gamma
  | .missing => none
  | .leaf l => l
  | .node l _ => l

/-- The child unfolding along one port; `missing` propagates. -/
def child : Unfold d Gamma (k + 1) → Fin d → Unfold d Gamma k
  | .missing, _ => .missing
  | .node _ ch, i => ch i

end Unfold

namespace Scaffold

variable {d : Nat} {Gamma : Type u}

/-- Definition 11's `N_k(S, v)` for a present node. -/
def viewAt (S : Scaffold d Gamma) : (k : Nat) → Nat → Unfold d Gamma k
  | 0, v => .leaf (S.label v)
  | k + 1, v =>
    .node (S.label v) fun i =>
      match S.edge v i with
      | none => .missing
      | some u => S.viewAt k u

@[simp] theorem viewAt_labelOf (S : Scaffold d Gamma) (k v : Nat) :
    (S.viewAt k v).labelOf = S.label v := by
  cases k <;> rfl

@[simp] theorem viewAt_child (S : Scaffold d Gamma) (k v : Nat) (i : Fin d) :
    (S.viewAt (k + 1) v).child i =
      match S.edge v i with
      | none => .missing
      | some u => S.viewAt k u := rfl

end Scaffold

/-- Definition 12's operational automaton. `Sym` is the input alphabet and `Q`
the control carrier; their finiteness is an external specialization. -/
structure Automaton (Sym : Type) (Q : Type) (Gamma : Type u) (d k : Nat) where
  delta : Q → Sym → Unfold d Gamma k → Q × Gamma × (Fin d → Target d)
  init : Q
  accept : Q → Bool

namespace Automaton

variable {Sym Q : Type} {Gamma : Type u} {d k : Nat}

/-- Definition 12's typing constraint on port words, stated separately. -/
def DistanceOk (A : Automaton Sym Q Gamma d k) : Prop :=
  ∀ q x n j p, (A.delta q x n).2.2 j = .path p → p.length ≤ k

/-- Definition 12's requirement that no target is `SELF` is *not* part of the
model; this predicate records automata that happen not to use it. -/
def SelfFree (A : Automaton Sym Q Gamma d k) : Prop :=
  ∀ q x n j, (A.delta q x n).2.2 j ≠ .self

/-- Definition 13's single step of computation. -/
def step (A : Automaton Sym Q Gamma d k) (c : Q × Scaffold d Gamma) (x : Sym) :
    Q × Scaffold d Gamma :=
  let r := A.delta c.1 x (c.2.viewAt k c.2.top)
  (r.1, c.2.append r.2.1 r.2.2)

/-- Definition 14's computation on a word. -/
def run (A : Automaton Sym Q Gamma d k) (w : List Sym) : Q × Scaffold d Gamma :=
  w.foldl A.step (A.init, Scaffold.base d Gamma)

/-- Definition 15.  `A(x)` is accepting iff `q_n ∈ F`. -/
def decides (A : Automaton Sym Q Gamma d k) (w : List Sym) : Bool :=
  A.accept (A.run w).1

@[simp] theorem run_nil (A : Automaton Sym Q Gamma d k) :
    A.run [] = (A.init, Scaffold.base d Gamma) := rfl

theorem run_append (A : Automaton Sym Q Gamma d k) (w : List Sym) (x : Sym) :
    A.run (w ++ [x]) = A.step (A.run w) x := by
  simp [run, List.foldl_append]

/-- Every input symbol appends exactly one scaffold node. Since the base node
has index zero, the final top index is the input length. -/
theorem run_top (A : Automaton Sym Q Gamma d k) (w : List Sym) :
    (A.run w).2.top = w.length := by
  have fold_top :
      ∀ (u : List Sym) (c : Q × Scaffold d Gamma),
        (u.foldl A.step c).2.top = c.2.top + u.length := by
    intro u
    induction u with
    | nil =>
      intro c
      rfl
    | cons x xs ih =>
      intro c
      simp only [List.foldl_cons, List.length_cons]
      rw [ih (A.step c x)]
      simp only [step, Scaffold.append]
      omega
  simpa [run, Scaffold.base] using
    fold_top w (A.init, Scaffold.base d Gamma)

end Automaton

end FPRD