Documentation

Complexitylib.Models.TuringMachine.Combinators.Internal.Window

Machines that respect a space window #

A TM.HoareSpace contract bounds every configuration reachable from a start state, which makes it awkward to compose: the body of a loop is entered again and again, never from its own start. The step-local strengthening below composes freely. A machine respects a window when one step from any configuration inside the window lands inside the window again — regardless of where in its own execution it happens to be.

Respecting a window immediately gives a space contract, and the property is inherited by the loop combinator from its body and test, because every phase loopTM interposes between them idles the input and work tapes (TM.loop_head_bound). This is what lets a loop run for as long as it likes without its space bound growing.

TM.RespectsWindow is the step-local form, convenient when it applies. It is too strong for a machine that simulates another one, whose own space bound holds only along its own run, so the weaker TM.KeepsWindowstarted anywhere inside the window, everything reached stays inside — is the notion the composition rules are stated for. Every entry into a loop body is at its start state, so this is exactly as much as a loop needs.

A subroutine that simulates another machine is not robust even in that sense: run from a start whose scratch tapes hold garbage, the simulated machine is not on any run its own space bound covers. TM.KeepsWindowOn therefore carries a precondition on the starting tapes, which a composition rule must re-establish at each entry from the previous stage's postcondition — and TM.halted_unique is what makes such a postcondition usable, since it says the halted configuration a stage reaches is the only one.

The two forms meet at TM.seqTM_keepsWindow_of_post: a stage that needs blank scratch, prefixed by a stage that clears it, is robust again. So a loop body assembled that way satisfies the plain TM.KeepsWindow hypothesis of TM.loopTM_keepsWindow, and no precondition-carrying loop rule is needed.

Main definitions #

Main results #

def Complexity.TM.CfgStartInvariant {n : } {Q : Type} (c : Cfg n Q) :

The tapes of a configuration all carry their left-end markers.

Equations
Instances For
    def Complexity.TM.RespectsWindow {n : } (tm : TM n) (inputLength space : ) :

    The machine respects a space window: one step from any configuration inside the window lands inside the window again — including the output head, so the discipline is the one TM.DecidesInSpace asks for. Unlike TM.HoareSpace this says nothing about where the machine started, so it survives being re-entered, which is what a loop body needs.

    Equations
    • One or more equations did not get rendered due to their size.
    Instances For
      theorem Complexity.TM.CfgStartInvariant.step {n : } {tm : TM n} {c c' : Cfg n tm.Q} (h : CfgStartInvariant c) (hstep : tm.step c = some c') :

      The start-marker invariant is preserved by a step.

      theorem Complexity.TM.hoareSpace_of_respectsWindow {n : } (tm : TM n) {inputLength space : } (h : tm.RespectsWindow inputLength space) :
      tm.HoareSpace (fun (inp : Tape) (work : Fin nTape) (out : Tape) => { state := tm.qstart, input := inp, work := work, output := out }.WithinDecisionSpace inputLength space CfgStartInvariant { state := tm.qstart, input := inp, work := work, output := out }) inputLength space

      Respecting a window is a space contract.

      theorem Complexity.TM.seqTM_respectsWindow {n : } (tm₁ tm₂ : TM n) {inputLength space : } (hs : 1 space) (h₁ : tm₁.RespectsWindow inputLength space) (h₂ : tm₂.RespectsWindow inputLength space) :
      (tm₁.seqTM tm₂).RespectsWindow inputLength space

      Sequential composition inherits the window. The one step interposed between the two machines idles every tape, so it cannot push a head outward.

      theorem Complexity.TM.loopTM_respectsWindow {n : } (tmBody tmTest : TM n) {inputLength space : } (hs : 1 space) (hbody : tmBody.RespectsWindow inputLength space) (htest : tmTest.RespectsWindow inputLength space) :
      (tmBody.loopTM tmTest).RespectsWindow inputLength space

      The loop inherits its window from its body and test. Every phase loopTM interposes between the two idles the input and work tapes, so the interposed steps cannot push a head outward; the simulated steps are covered by the parts' own contracts. The bound is therefore independent of how many iterations run.

      Determinism at the halt #

      theorem Complexity.TM.reachesIn_halted_unique {n : } {tm : TM n} {s t : } {c a b : Cfg n tm.Q} :
      tm.reachesIn s c atm.reachesIn t c btm.halted atm.halted ba = b

      A deterministic run reaches at most one halted configuration: the halt is the run's endpoint, whichever way it is reached. This is what lets a stage's postcondition be used — the configuration a composition rule meets at the phase boundary is the one the stage's Hoare triple describes.

      theorem Complexity.TM.halted_unique {n : } {tm : TM n} {c a b : Cfg n tm.Q} (ha : tm.reaches c a) (hb : tm.reaches c b) (hha : tm.halted a) (hhb : tm.halted b) :
      a = b

      The unbounded form of TM.reachesIn_halted_unique.

      Keeping a window along a run #

      def Complexity.TM.KeepsWindow {n : } (tm : TM n) (inputLength space : ) :

      The machine keeps its window: started anywhere inside the window with its left markers intact, every configuration it reaches is still inside the window. This is weaker than TM.RespectsWindow — it says nothing about configurations the machine cannot reach from a windowed start — and it is what a subroutine that simulates another machine can actually satisfy.

      Equations
      • One or more equations did not get rendered due to their size.
      Instances For
        theorem Complexity.TM.RespectsWindow.keepsWindow {n : } {tm : TM n} {inputLength space : } (h : tm.RespectsWindow inputLength space) :
        tm.KeepsWindow inputLength space

        A machine that never steps out of the window certainly keeps it.

        theorem Complexity.TM.seqTM_keepsWindow {n : } (tm₁ tm₂ : TM n) {inputLength space : } (hs : 1 space) (h₁ : tm₁.KeepsWindow inputLength space) (h₂ : tm₂.KeepsWindow inputLength space) :
        (tm₁.seqTM tm₂).KeepsWindow inputLength space

        Sequential composition keeps the window. Each phase is entered at its machine's start state with windowed tapes, and the single interposed step idles every tape.

        theorem Complexity.TM.loopTM_keepsWindow {n : } (tmBody tmTest : TM n) {inputLength space : } (hs : 1 space) (hbody : tmBody.KeepsWindow inputLength space) (htest : tmTest.KeepsWindow inputLength space) :
        (tmBody.loopTM tmTest).KeepsWindow inputLength space

        The loop keeps the window, however many iterations it runs. Body and test are each entered at their own start state with windowed tapes, and every step the loop interposes between them idles the input and work tapes, so nothing accumulates across iterations.

        theorem Complexity.TM.ifTM_keepsWindow {n : } (tmTest tmThen tmElse : TM n) {inputLength space : } (hs : 1 space) (htest : tmTest.KeepsWindow inputLength space) (hthen : tmThen.KeepsWindow inputLength space) (helse : tmElse.KeepsWindow inputLength space) :
        (tmTest.ifTM tmThen tmElse).KeepsWindow inputLength space

        The conditional keeps the window. Test and branch are each entered at their own start state with windowed tapes, and the steps between them idle every tape.

        def Complexity.TM.KeepsWindowOn {n : } (tm : TM n) (pre : Cfg n tm.QProp) (inputLength space : ) :

        The machine keeps its window on runs that start from a configuration satisfying pre. The precondition is what a subroutine simulating another machine needs: its space bound only covers runs whose scratch tapes started blank, so a composition rule has to re-establish that at every entry.

        Equations
        Instances For
          theorem Complexity.TM.KeepsWindow.keepsWindowOn {n : } {tm : TM n} {inputLength space : } (h : tm.KeepsWindow inputLength space) {pre : Cfg n tm.QProp} (hpre : ∀ (c : Cfg n tm.Q), pre cc.state = tm.qstart c.WithinDecisionSpace inputLength space CfgStartInvariant c) :
          tm.KeepsWindowOn pre inputLength space

          Keeping the window unconditionally is keeping it on any precondition that pins the start state and the window.

          theorem Complexity.TM.KeepsWindowOn.mono {n : } {tm : TM n} {pre pre' : Cfg n tm.QProp} {inputLength space : } (h : tm.KeepsWindowOn pre inputLength space) (hpre : ∀ (c : Cfg n tm.Q), pre' cpre c) :
          tm.KeepsWindowOn pre' inputLength space

          Weakening the precondition of a windowed contract.

          theorem Complexity.TM.seqTM_keepsWindowOn {n : } (tm₁ tm₂ : TM n) {inputLength space : } (hs : 1 space) {pre₁ : Cfg n tm₁.QProp} {pre₂ : Cfg n tm₂.QProp} {mid : TapePred n} (hpre₁ : ∀ (c : Cfg n tm₁.Q), pre₁ cc.state = tm₁.qstart c.WithinDecisionSpace inputLength space CfgStartInvariant c) (h₁ : tm₁.KeepsWindowOn pre₁ inputLength space) (h₁post : ∀ (c : Cfg n tm₁.Q), pre₁ c∃ (e : Cfg n tm₁.Q), tm₁.reaches c e tm₁.halted e mid e.input e.work e.output) (h₂ : tm₂.KeepsWindowOn pre₂ inputLength space) (htrans : ∀ (inp : Tape) (work : Fin nTape) (out : Tape), mid inp work outpre₂ { state := tm₂.qstart, input := transitionInput inp, work := fun (i : Fin n) => transitionTape (work i), output := transitionTape out }) :
          (tm₁.seqTM tm₂).KeepsWindowOn (fun (c : Cfg n (tm₁.seqTM tm₂).Q) => ∃ (d : Cfg n tm₁.Q), pre₁ d c = tm₁.phase1Wrap tm₂ d) inputLength space

          Sequential composition keeps the window, with preconditions. The second machine's precondition is re-established from the first machine's postcondition: TM.halted_unique says the configuration met at the phase boundary is the very one the first machine's Hoare triple describes, and the interposed step transforms its tapes in the fixed way transitionTape records.

          theorem Complexity.TM.seqTM_keepsWindow_of_post {n : } (tm₁ tm₂ : TM n) {inputLength space : } (hs : 1 space) {pre₂ : Cfg n tm₂.QProp} {mid : TapePred n} (h₁ : tm₁.KeepsWindow inputLength space) (h₁post : ∀ (c : Cfg n tm₁.Q), c.state = tm₁.qstartc.WithinDecisionSpace inputLength spaceCfgStartInvariant c∃ (e : Cfg n tm₁.Q), tm₁.reaches c e tm₁.halted e mid e.input e.work e.output) (h₂ : tm₂.KeepsWindowOn pre₂ inputLength space) (htrans : ∀ (inp : Tape) (work : Fin nTape) (out : Tape), mid inp work outpre₂ { state := tm₂.qstart, input := transitionInput inp, work := fun (i : Fin n) => transitionTape (work i), output := transitionTape out }) :
          (tm₁.seqTM tm₂).KeepsWindow inputLength space

          A sequential composition is robust as soon as its first stage is. This is what makes the unconditional rules usable in practice. A subroutine that simulates another machine needs its scratch tapes blank, so it only satisfies the precondition-carrying contract; but prefix it with a stage that clears the scratch — one that is robust — and the composite is robust too, because the first stage's postcondition supplies the second stage's precondition. The composite can then be dropped straight into TM.loopTM_keepsWindow, whose body hypothesis is the unconditional one.

          Time-bounded subroutines keep a window #

          theorem Complexity.TM.keepsWindowOn_of_haltsIn {n : } {tm : TM n} {pre : Cfg n tm.QProp} {inputLength h t : } (hwork : ∀ (c : Cfg n tm.Q), pre c∀ (i : Fin n), (c.work i).head h) (hinput : ∀ (c : Cfg n tm.Q), pre cc.input.head inputLength + h + 1) (houtput : ∀ (c : Cfg n tm.Q), pre cc.output.head h + 1) (hhalt : ∀ (c : Cfg n tm.Q), pre c∃ (e : Cfg n tm.Q), t't, tm.reachesIn t' c e tm.halted e) :
          tm.KeepsWindowOn pre inputLength (h + t)

          A subroutine that halts in t steps keeps a window t cells wider than its start. Every head moves at most one cell per step, and a deterministic run cannot outlast its halt, so a configuration starting with its heads inside h can only have pushed them to h + t.

          This is the bridge from the library's existing time contracts: any subroutine with a TM.HoareTime-style halting bound acquires a window contract, with no new tape analysis.

          theorem Complexity.TM.keepsWindowOn_of_hoareTime {n : } {tm : TM n} {pre post : TapePred n} {b : } (h : tm.HoareTime pre post b) {inputLength h₀ : } (hwork : ∀ (inp : Tape) (work : Fin nTape) (out : Tape), pre inp work out∀ (i : Fin n), (work i).head h₀) (hinput : ∀ (inp : Tape) (work : Fin nTape) (out : Tape), pre inp work outinp.head inputLength + h₀ + 1) (houtput : ∀ (inp : Tape) (work : Fin nTape) (out : Tape), pre inp work outout.head h₀ + 1) :
          tm.KeepsWindowOn (fun (c : Cfg n tm.Q) => c.state = tm.qstart pre c.input c.work c.output) inputLength (h₀ + b)

          Every TM.HoareTime contract yields a window contract. The library's subroutines are specified by halting-time triples; this converts any of them, with no tape analysis, provided the precondition pins where the heads start. The window is the starting bound plus the running time, since a head moves at most one cell per step.

          theorem Complexity.TM.keepsWindowOn_of_hoareTime_pinned {n : } {tm : TM n} {post : TapePred n} {b : } {inp₀ : Tape} {work₀ : Fin nTape} {out₀ : Tape} (h : tm.HoareTime (fun (inp : Tape) (work : Fin nTape) (out : Tape) => inp = inp₀ work = work₀ out = out₀) post b) {inputLength h₀ : } (hwork : ∀ (i : Fin n), (work₀ i).head h₀) (hinput : inp₀.head inputLength + h₀ + 1) (houtput : out₀.head h₀ + 1) :
          tm.KeepsWindowOn (fun (c : Cfg n tm.Q) => c.state = tm.qstart c.input = inp₀ c.work = work₀ c.output = out₀) inputLength (h₀ + b)

          The pinned form, matching how the library states its framed subroutine contracts: the precondition names the three tapes exactly, so the head bounds are three facts about literals.

          theorem Complexity.TM.keepsWindow_of_haltsIn {n : } {tm : TM n} {inputLength h t : } (hhalt : ∀ (c : Cfg n tm.Q), c.state = tm.qstartc.WithinDecisionSpace inputLength hCfgStartInvariant c∃ (e : Cfg n tm.Q), t't, tm.reachesIn t' c e tm.halted e) :
          tm.KeepsWindowOn (fun (c : Cfg n tm.Q) => c.state = tm.qstart c.WithinDecisionSpace inputLength h CfgStartInvariant c) inputLength (h + t)

          The unconditional form: a machine that halts within t steps from every configuration inside the window keeps the widened window.

          Interoperation with the library's space contracts #

          theorem Complexity.TM.KeepsWindowOn.hoareSpace {n : } {tm : TM n} {pre : TapePred n} {inputLength space : } (h : tm.KeepsWindowOn (fun (c : Cfg n tm.Q) => c.state = tm.qstart pre c.input c.work c.output) inputLength space) :
          tm.HoareSpace pre inputLength space

          A window contract is a TM.HoareSpace contract. TM.HoareSpace is the library's own anchored space judgement; it bounds Cfg.WithinAuxSpace, which ignores the output tape, whereas a window additionally bounds the output head as TM.DecidesInSpace requires. So a window is the stronger statement, and anything established with the composition rules above can be handed to TM.seqTM_hoareTimeSpace and TM.computesInSpace_of_hoareTimeSpace.

          theorem Complexity.TM.KeepsWindow.hoareSpace {n : } {tm : TM n} {inputLength space : } (h : tm.KeepsWindow inputLength space) {pre : TapePred n} (hpre : ∀ (inp : Tape) (work : Fin nTape) (out : Tape), pre inp work out{ state := tm.qstart, input := inp, work := work, output := out }.WithinDecisionSpace inputLength space CfgStartInvariant { state := tm.qstart, input := inp, work := work, output := out }) :
          tm.HoareSpace pre inputLength space

          The unconditional form likewise yields a TM.HoareSpace contract, on any precondition that pins the window and the left markers.

          From a window to a space-bounded decider #

          theorem Complexity.TM.initCfg_withinDecisionSpace {n : } (tm : TM n) (x : List Bool) (inputLength space : ) :
          (tm.initCfg x).WithinDecisionSpace inputLength space

          The initial configuration parks every head at the left end, so it sits inside every window.

          Every tape of the initial configuration carries its left-end marker.

          theorem Complexity.TM.decidesInSpace_of_respectsWindow {n : } {tm : TM n} {L : Language} {S : } (hwin : ∀ (x : List Bool), tm.RespectsWindow x.length (S x.length)) (hdec : ∀ (x : List Bool), ∃ (c' : Cfg n tm.Q), tm.reaches (tm.initCfg x) c' tm.halted c' (x Lc'.output.cells 1 = Γ.one) (xLc'.output.cells 1 = Γ.zero)) :

          A machine that respects its window and decides L decides L in that space. This is the landing gear: a construction assembled from TM.seqTM_respectsWindow and TM.loopTM_respectsWindow needs only its correctness proof to become a DSPACE membership.

          theorem Complexity.TM.decidesInSpace_of_keepsWindow {n : } {tm : TM n} {L : Language} {S : } (hwin : ∀ (x : List Bool), tm.KeepsWindow x.length (S x.length)) (hdec : ∀ (x : List Bool), ∃ (c' : Cfg n tm.Q), tm.reaches (tm.initCfg x) c' tm.halted c' (x Lc'.output.cells 1 = Γ.one) (xLc'.output.cells 1 = Γ.zero)) :

          A machine that keeps its window and decides L decides L in that space.

          theorem Complexity.TM.loopTM_keepsWindow_indexed {n : } (tmBody tmTest : TM n) {inputLength space : } (E : TapePred n) (N b : ) (hstep : j < N, ∀ (inp : Tape) (work : Fin nTape) (out : Tape), E j inp work out∃ (inp' : Tape) (work' : Fin nTape) (out' : Tape) (t : ), 1 t t b (tmBody.loopTM tmTest).reachesIn t { state := (tmBody.loopTM tmTest).qstart, input := inp, work := work, output := out } { state := (tmBody.loopTM tmTest).qstart, input := inp', work := work', output := out' } E (j + 1) inp' work' out') (hstop : ∀ (inp : Tape) (work : Fin nTape) (out : Tape), E N inp work out∃ (c' : Cfg n (tmBody.loopTM tmTest).Q), tb, (tmBody.loopTM tmTest).reachesIn t { state := (tmBody.loopTM tmTest).qstart, input := inp, work := work, output := out } c' (tmBody.loopTM tmTest).halted c') (hiter : jN, ∀ (inp : Tape) (work : Fin nTape) (out : Tape), E j inp work out∀ (c : Cfg n (tmBody.loopTM tmTest).Q), tb, (tmBody.loopTM tmTest).reachesIn t { state := (tmBody.loopTM tmTest).qstart, input := inp, work := work, output := out } cc.WithinDecisionSpace inputLength space) (j : ) :
          j N∀ (inp : Tape) (work : Fin nTape) (out : Tape), E j inp work out∀ (c : Cfg n (tmBody.loopTM tmTest).Q), (tmBody.loopTM tmTest).reaches { state := (tmBody.loopTM tmTest).qstart, input := inp, work := work, output := out } cc.WithinDecisionSpace inputLength space

          A loop keeps a window one iteration at a time. The loop's total run is exponentially long when the counter is, so no bound of the form "space ≤ time" can help. What is true is that every configuration the loop passes through lies within one iteration of some indexed state, and each iteration is short: hiter asks only that a single iteration stay inside the window.

          Determinism is what makes the reduction work. A run that leaves the current iteration must pass through the next iteration's start configuration, because two runs from the same configuration are prefixes of one another.

          theorem Complexity.TM.loopTM_keepsWindow_indexed_of_parked {n : } (tmBody tmTest : TM n) {inputLength : } (E : TapePred n) (N b : ) (hstep : j < N, ∀ (inp : Tape) (work : Fin nTape) (out : Tape), E j inp work out∃ (inp' : Tape) (work' : Fin nTape) (out' : Tape) (t : ), 1 t t b (tmBody.loopTM tmTest).reachesIn t { state := (tmBody.loopTM tmTest).qstart, input := inp, work := work, output := out } { state := (tmBody.loopTM tmTest).qstart, input := inp', work := work', output := out' } E (j + 1) inp' work' out') (hstop : ∀ (inp : Tape) (work : Fin nTape) (out : Tape), E N inp work out∃ (c' : Cfg n (tmBody.loopTM tmTest).Q), tb, (tmBody.loopTM tmTest).reachesIn t { state := (tmBody.loopTM tmTest).qstart, input := inp, work := work, output := out } c' (tmBody.loopTM tmTest).halted c') (hparked : jN, ∀ (inp : Tape) (work : Fin nTape) (out : Tape), E j inp work out(∀ (i : Fin n), (work i).head 1) inp.head inputLength + 1 out.head 1) (j : ) :
          j N∀ (inp : Tape) (work : Fin nTape) (out : Tape), E j inp work out∀ (c : Cfg n (tmBody.loopTM tmTest).Q), (tmBody.loopTM tmTest).reaches { state := (tmBody.loopTM tmTest).qstart, input := inp, work := work, output := out } cc.WithinDecisionSpace inputLength (1 + b)

          A loop whose indexed states are parked keeps a window of one iteration's width. Inside a single iteration no head can travel further than the iteration is long, and each indexed state has every head at cell one, so 1 + b cells suffice — for the whole run, however many iterations it takes.

          theorem Complexity.TM.KeepsWindowOn.mono_space {n : } {tm : TM n} {pre : Cfg n tm.QProp} {inputLength s s' : } (h : tm.KeepsWindowOn pre inputLength s) (hs : s s') :
          tm.KeepsWindowOn pre inputLength s'

          Widening the window of a conditional window contract. Composing stages of different widths means widening each to their maximum first.

          def Complexity.TM.LoopTapeInv {n : } (tmBody tmTest : TM n) (PB PT : TapePred n) (PL : LoopPhaseTapePred n) (c : Cfg n (tmBody.loopTM tmTest).Q) :

          The tape conditions a loop configuration must satisfy for the phase-based window rule: the body and the test are entered on tapes their own window rules accept, and the bookkeeping phases between them on tapes that carry those conditions along.

          Equations
          • One or more equations did not get rendered due to their size.
          Instances For
            theorem Complexity.TM.loopTM_keepsWindowOn_phases {n : } (tmBody tmTest : TM n) {inputLength space : } (hs : 1 space) (PB PT : TapePred n) (PL : LoopPhaseTapePred n) (hbodyW : tmBody.KeepsWindowOn (fun (d : Cfg n tmBody.Q) => d.state = tmBody.qstart PB d.input d.work d.output) inputLength space) (htestW : tmTest.KeepsWindowOn (fun (d : Cfg n tmTest.Q) => d.state = tmTest.qstart PT d.input d.work d.output) inputLength space) (hBT : ∀ (c c' : Cfg n (tmBody.loopTM tmTest).Q), c.state = Sum.inl tmBody.qhalt(∃ (d : Cfg n tmBody.Q), d.state = tmBody.qstart PB d.input d.work d.output tmBody.reaches d { state := tmBody.qhalt, input := c.input, work := c.work, output := c.output })(tmBody.loopTM tmTest).step c = some c'tmBody.LoopTapeInv tmTest PB PT PL c') (hTL : ∀ (c c' : Cfg n (tmBody.loopTM tmTest).Q), c.state = Sum.inr (Sum.inr tmTest.qhalt)(∃ (d : Cfg n tmTest.Q), d.state = tmTest.qstart PT d.input d.work d.output tmTest.reaches d { state := tmTest.qhalt, input := c.input, work := c.work, output := c.output })(tmBody.loopTM tmTest).step c = some c'tmBody.LoopTapeInv tmTest PB PT PL c') (hLL : ∀ (c c' : Cfg n (tmBody.loopTM tmTest).Q) (ph : LoopPhase), c.state = Sum.inr (Sum.inl ph)PL ph c.input c.work c.output(tmBody.loopTM tmTest).step c = some c'tmBody.LoopTapeInv tmTest PB PT PL c') (c₀ : Cfg n (tmBody.loopTM tmTest).Q) :
            c₀.state = (tmBody.loopTM tmTest).qstartPB c₀.input c₀.work c₀.outputc₀.WithinDecisionSpace inputLength spaceCfgStartInvariant c₀∀ (c : Cfg n (tmBody.loopTM tmTest).Q), (tmBody.loopTM tmTest).reaches c₀ cc.WithinDecisionSpace inputLength space

            A loop keeps a window, given windows for its phases. TM.loopTM_keepsWindow asks each phase to keep the window from any windowed configuration, which a phase that simulates another machine cannot promise: started anywhere, such a machine need not even halt. This rule asks instead for a window on the states each phase is actually entered at, plus three obligations saying that the loop's own steps between phases carry those states along.

            PB and PT describe the tapes the body and the test are entered with, PL those of the rewind-and-check phases between them.

            theorem Complexity.TM.loop_phase_step_tapes {n : } (tmBody tmTest : TM n) {c c' : Cfg n (tmBody.loopTM tmTest).Q} {ph : LoopPhase} (hstate : c.state = Sum.inr (Sum.inl ph)) (hph : ph LoopPhase.done) (hout : c.output.StartInvariant) (hstep : (tmBody.loopTM tmTest).step c = some c') :

            A bookkeeping step of a loop moves only the output head. Between the test and the next iteration the loop rewinds its output tape and reads one cell; the work tapes and the input are idled, and the output's contents are written back unchanged.

            theorem Complexity.TM.loop_rewind_step_state {n : } (tmBody tmTest : TM n) {c c' : Cfg n (tmBody.loopTM tmTest).Q} (hstate : c.state = Sum.inr (Sum.inl LoopPhase.rewindOut)) (hstep : (tmBody.loopTM tmTest).step c = some c') :

            Where a rewind step goes. It keeps rewinding until the output head reads the marker, and then moves on to the check.

            theorem Complexity.TM.loop_check_step_tapes {n : } (tmBody tmTest : TM n) {c c' : Cfg n (tmBody.loopTM tmTest).Q} (hstate : c.state = Sum.inr (Sum.inl LoopPhase.check)) (hread : c.output.read Γ.start) (hstep : (tmBody.loopTM tmTest).step c = some c') :
            c'.input = transitionInput c.input (∀ (i : Fin n), c'.work i = transitionTape (c.work i)) c'.output = c.output

            A check step leaves every tape exactly as it found it, when the output head is off the marker — which it is, since the rewind has just put it at cell one.