Research source · Plain text
RealtimeTransfer.lean
site/public/formal/persistent-stacks/lean/FPRDTransfer/RealtimeTransfer.lean
282 lines. Source is displayed for inspection; it is not executed by this page.
File fingerprint
SHA-256: 567e1f7b1d166a2699bb8383cd742b8f116822f3a51a4f0c7103850efa9bf3aa
/-
The strict real-time transfer.
A deterministic ordinary single-head multitape machine that performs exactly
one global transition per arriving input symbol is encoded as a
letter-synchronous machine with `2 * m` stacks -- two per tape, by the zipper --
performing at most one action on each stack per symbol. Composing with
`compile_correct` gives a single LMR scaffolding automaton, of degree `2 * m`
and distance `2`, that appends one node per input symbol and whose scaffold
decodes to the machine's tape contents after every prefix.
There is no `|x|`-advice tape and no post-input cleanup: acceptance is the
control state reached after the last symbol, on both sides.
-/
import FPRDTransfer.Compile
import FPRDTransfer.TapeZipper
namespace FPRD
variable {Sym Q T : Type} {m : Nat}
/-- A deterministic multitape machine with one head per tape, making exactly
one transition per arriving input symbol. It sees only the scanned symbols. -/
structure MultiTapeMachine (Sym : Type) (Q : Type) (T : Type) (m : Nat) where
blank : T
step : Q → Sym → (Fin m → T) → Q × (Fin m → T × Move)
init : Q
accept : Q → Bool
/-- A multitape configuration. -/
structure TapeConfig (Q : Type) (T : Type) (m : Nat) where
ctrl : Q
tapes : Fin m → Tape T
namespace MultiTapeMachine
/-- The all-blank tape. -/
def initTape (b : T) : Tape T where
left := []
cur := b
right := []
def stepConfig (N : MultiTapeMachine Sym Q T m) (c : TapeConfig Q T m) (x : Sym) :
TapeConfig Q T m :=
let r := N.step c.ctrl x (fun i => (c.tapes i).cur)
{ ctrl := r.1
tapes := fun i => (c.tapes i).apply (r.2 i).1 N.blank (r.2 i).2 }
@[simp] theorem stepConfig_ctrl (N : MultiTapeMachine Sym Q T m) (c : TapeConfig Q T m)
(x : Sym) :
(N.stepConfig c x).ctrl = (N.step c.ctrl x (fun i => (c.tapes i).cur)).1 := rfl
@[simp] theorem stepConfig_tapes (N : MultiTapeMachine Sym Q T m) (c : TapeConfig Q T m)
(x : Sym) (i : Fin m) :
(N.stepConfig c x).tapes i =
(c.tapes i).apply ((N.step c.ctrl x (fun i => (c.tapes i).cur)).2 i).1 N.blank
((N.step c.ctrl x (fun i => (c.tapes i).cur)).2 i).2 := rfl
def initConfig (N : MultiTapeMachine Sym Q T m) : TapeConfig Q T m where
ctrl := N.init
tapes := fun _ => initTape N.blank
def run (N : MultiTapeMachine Sym Q T m) (w : List Sym) : TapeConfig Q T m :=
w.foldl N.stepConfig N.initConfig
def decides (N : MultiTapeMachine Sym Q T m) (w : List Sym) : Bool :=
N.accept (N.run w).ctrl
end MultiTapeMachine
/-! ### Indexing the two stacks of each tape -/
/-- The stack holding the cells left of tape `i`'s head. -/
def leftIdx (i : Fin m) : Fin (2 * m) :=
⟨2 * i.val, by have := i.isLt; omega⟩
/-- The stack holding the cells right of tape `i`'s head. -/
def rightIdx (i : Fin m) : Fin (2 * m) :=
⟨2 * i.val + 1, by have := i.isLt; omega⟩
/-- Which tape a stack index belongs to. -/
def tapeOf (j : Fin (2 * m)) : Fin m :=
⟨j.val / 2, by have := j.isLt; omega⟩
/-- Whether a stack index is a tape's left stack. -/
def isLeftIdx (j : Fin (2 * m)) : Bool := j.val % 2 == 0
@[simp] theorem tapeOf_leftIdx (i : Fin m) : tapeOf (leftIdx i) = i := by
apply Fin.ext
simp only [tapeOf, leftIdx]
omega
@[simp] theorem tapeOf_rightIdx (i : Fin m) : tapeOf (rightIdx i) = i := by
apply Fin.ext
simp only [tapeOf, rightIdx]
omega
@[simp] theorem isLeftIdx_leftIdx (i : Fin m) : isLeftIdx (leftIdx i) = true := by
have hmod : (leftIdx i).val % 2 = 0 := by simp only [leftIdx]; omega
simp [isLeftIdx, hmod]
@[simp] theorem isLeftIdx_rightIdx (i : Fin m) : isLeftIdx (rightIdx i) = false := by
have hmod : (rightIdx i).val % 2 = 1 := by simp only [rightIdx]; omega
simp [isLeftIdx, hmod]
namespace MultiTapeMachine
/-- The stack action for one stack index, given this step's writes and moves. -/
def stackOp (writes : Fin m → T × Move) (j : Fin (2 * m)) : StackOp T :=
if isLeftIdx j then Tape.leftOp (writes (tapeOf j)).1 (writes (tapeOf j)).2
else Tape.rightOp (writes (tapeOf j)).1 (writes (tapeOf j)).2
@[simp] theorem stackOp_left (writes : Fin m → T × Move) (i : Fin m) :
stackOp writes (leftIdx i) = Tape.leftOp (writes i).1 (writes i).2 := by
simp [stackOp]
@[simp] theorem stackOp_right (writes : Fin m → T × Move) (i : Fin m) :
stackOp writes (rightIdx i) = Tape.rightOp (writes i).1 (writes i).2 := by
simp [stackOp]
/-- The next scanned symbol, computed from the finite control and the stack
tops only. A blank appears exactly when the relevant stack is empty. -/
def nextCurEnc (b : T) (writes : Fin m → T × Move) (tops : Fin (2 * m) → Option T)
(i : Fin m) : T :=
match (writes i).2 with
| .right => (tops (rightIdx i)).getD b
| .left => (tops (leftIdx i)).getD b
| .stay => (writes i).1
/-- The zipper encoding: `2 * m` stacks, the scanned symbols in the control. -/
def toStack (N : MultiTapeMachine Sym Q T m) :
StackMachine Sym (Q × (Fin m → T)) T (2 * m) where
step := fun qc x tops =>
(((N.step qc.1 x qc.2).1, nextCurEnc N.blank (N.step qc.1 x qc.2).2 tops),
stackOp (N.step qc.1 x qc.2).2)
init := (N.init, fun _ => N.blank)
accept := fun qc => N.accept qc.1
@[simp] theorem toStack_step_fst (N : MultiTapeMachine Sym Q T m)
(qc : Q × (Fin m → T)) (x : Sym) (tops : Fin (2 * m) → Option T) :
(N.toStack.step qc x tops).1 =
((N.step qc.1 x qc.2).1, nextCurEnc N.blank (N.step qc.1 x qc.2).2 tops) := rfl
@[simp] theorem toStack_step_snd (N : MultiTapeMachine Sym Q T m)
(qc : Q × (Fin m → T)) (x : Sym) (tops : Fin (2 * m) → Option T) :
(N.toStack.step qc x tops).2 = stackOp (N.step qc.1 x qc.2).2 := rfl
@[simp] theorem toStack_accept (N : MultiTapeMachine Sym Q T m) (qc : Q × (Fin m → T)) :
N.toStack.accept qc = N.accept qc.1 := rfl
/-- The correspondence between a multitape configuration and the encoded
stack configuration. -/
structure TapeSim (N : MultiTapeMachine Sym Q T m) (c : TapeConfig Q T m)
(d : Config (Q × (Fin m → T)) T (2 * m)) : Prop where
ctrl : c.ctrl = d.ctrl.1
cur : ∀ i, (c.tapes i).cur = d.ctrl.2 i
left : ∀ i, d.stacks (leftIdx i) = (c.tapes i).left
right : ∀ i, d.stacks (rightIdx i) = (c.tapes i).right
theorem tapeSim_init (N : MultiTapeMachine Sym Q T m) :
N.TapeSim N.initConfig N.toStack.initConfig where
ctrl := rfl
cur := fun _ => rfl
left := fun _ => rfl
right := fun _ => rfl
theorem tapeSim_step (N : MultiTapeMachine Sym Q T m) {c : TapeConfig Q T m}
{d : Config (Q × (Fin m → T)) T (2 * m)} (h : N.TapeSim c d) (x : Sym) :
N.TapeSim (N.stepConfig c x) (N.toStack.stepConfig d x) := by
have hq : d.ctrl.1 = c.ctrl := h.ctrl.symm
have hcur : d.ctrl.2 = fun i => (c.tapes i).cur := (funext h.cur).symm
refine { ctrl := ?_, cur := ?_, left := ?_, right := ?_ }
· simp only [StackMachine.stepConfig_ctrl, toStack_step_fst, stepConfig_ctrl, hq, hcur]
· intro i
simp only [StackMachine.stepConfig_ctrl, toStack_step_fst, stepConfig_tapes, hq, hcur,
Tape.apply_cur, Tape.nextCur_of_tops, nextCurEnc, h.left i, h.right i]
cases ((N.step c.ctrl x fun i => (c.tapes i).cur).2 i).2 <;> rfl
· intro i
simp only [StackMachine.stepConfig_stacks, toStack_step_snd, stepConfig_tapes, hq, hcur,
stackOp_left, Tape.apply_left, h.left i]
· intro i
simp only [StackMachine.stepConfig_stacks, toStack_step_snd, stepConfig_tapes, hq, hcur,
stackOp_right, Tape.apply_right, h.right i]
theorem tapeSim_run (N : MultiTapeMachine Sym Q T m) :
∀ (w : List Sym) (c : TapeConfig Q T m) (d : Config (Q × (Fin m → T)) T (2 * m)),
N.TapeSim c d →
N.TapeSim (w.foldl N.stepConfig c) (w.foldl N.toStack.stepConfig d) := by
intro w
induction w with
| nil => intro c d h; exact h
| cons x w ih =>
intro c d h
simp only [List.foldl_cons]
exact ih _ _ (tapeSim_step N h x)
/-- **Strict multitape transfer, stack level.** -/
theorem toStack_correct (N : MultiTapeMachine Sym Q T m) (w : List Sym) :
N.TapeSim (N.run w) (N.toStack.run w) :=
tapeSim_run N w N.initConfig N.toStack.initConfig (tapeSim_init N)
/-! ### The composed theorem -/
/-- **Strict real-time transfer.** For every input word, the scaffolding
automaton compiled from a strict single-transition-per-symbol multitape machine
has the same control state, and its scaffold decodes at the top to exactly the
two zipper halves of every tape.
Degree is `2 * m`, distance is `2`, one node is appended per input symbol, and
acceptance is the final control state. -/
theorem realtime_transfer (N : MultiTapeMachine Sym Q T m) (w : List Sym) :
(∀ i, decode ((compile N.toStack).run w).2 (leftIdx i)
((compile N.toStack).run w).2.top = ((N.run w).tapes i).left) ∧
(∀ i, decode ((compile N.toStack).run w).2 (rightIdx i)
((compile N.toStack).run w).2.top = ((N.run w).tapes i).right) ∧
(N.run w).ctrl = ((compile N.toStack).run w).1.1 := by
have hs := toStack_correct N w
refine ⟨fun i => ?_, fun i => ?_, ?_⟩
· rw [compile_stacks N.toStack w (leftIdx i)]; exact hs.left i
· rw [compile_stacks N.toStack w (rightIdx i)]; exact hs.right i
· rw [← compile_ctrl N.toStack w]; exact hs.ctrl
/-- The compiled scaffolding automaton decides exactly the multitape machine's
language. -/
theorem realtime_transfer_decides (N : MultiTapeMachine Sym Q T m) (w : List Sym) :
(compile N.toStack).decides w = N.decides w := by
rw [compile_decides]
show N.toStack.accept (N.toStack.run w).ctrl = N.accept (N.run w).ctrl
rw [toStack_accept, ← (toStack_correct N w).ctrl]
/-! ### Reconstructing each source tape from the scaffold
`realtime_transfer` identifies the two decoded zipper halves. The scanned
symbol is carried in the compiled finite control. The definitions and
theorems below package those three facts as equality of `Tape` values and then
as agreement at every integer offset, including the blank-padded tails. -/
/-- Tape `i` reconstructed from the compiled scaffold and finite control after
reading `w`. -/
def scaffoldTape (N : MultiTapeMachine Sym Q T m) (w : List Sym) (i : Fin m) :
Tape T where
left :=
decode ((compile N.toStack).run w).2 (leftIdx i) ((compile N.toStack).run w).2.top
cur := ((compile N.toStack).run w).1.2 i
right :=
decode ((compile N.toStack).run w).2 (rightIdx i) ((compile N.toStack).run w).2.top
/-- The tape reconstructed from the compiled scaffold is exactly the source
machine's tape after every input word. -/
theorem realtime_transfer_tape (N : MultiTapeMachine Sym Q T m) (w : List Sym)
(i : Fin m) : scaffoldTape N w i = (N.run w).tapes i := by
have h := realtime_transfer N w
have hcur : ((compile N.toStack).run w).1.2 i = ((N.run w).tapes i).cur := by
rw [← compile_ctrl N.toStack w]
exact ((toStack_correct N w).cur i).symm
simp only [scaffoldTape, h.1 i, h.2.1 i, hcur]
/-- The reconstructed and source tapes agree at every integer offset from the
head, including offsets in their implicit blank tails. -/
theorem realtime_transfer_tape_contents (N : MultiTapeMachine Sym Q T m)
(w : List Sym) (i : Fin m) (z : Int) :
(scaffoldTape N w i).get N.blank z = ((N.run w).tapes i).get N.blank z := by
rw [realtime_transfer_tape]
/-- The compiled automaton meets LMR's typing constraints: every named port
word has length at most the distance, and `SELF` is never used. -/
theorem realtime_transfer_wellFormed (N : MultiTapeMachine Sym Q T m) :
(compile N.toStack).DistanceOk ∧ (compile N.toStack).SelfFree :=
⟨compile_distanceOk N.toStack, compile_selfFree N.toStack⟩
/-- The compiled automaton satisfies the structural constraints established
here and decides exactly the source machine's language. Finiteness enters only
when this operational theorem is composed with the scaffold-to-PEG theorem. -/
theorem realtime_transfer_wellFormed_decides (N : MultiTapeMachine Sym Q T m) :
(compile N.toStack).DistanceOk ∧ (compile N.toStack).SelfFree ∧
∀ w : List Sym, (compile N.toStack).decides w = N.decides w :=
⟨compile_distanceOk _, compile_selfFree _, realtime_transfer_decides N⟩
end MultiTapeMachine
end FPRD