Assembling the counting loop's body #
⚠️ Unreviewed by Bolton
The loop invariant names every tape: the counter and the two tallies carry their numbers, the horizon and the wipe's register carry theirs, and everything else rests blank. This file records what that bank looks like at each named index and proves the body's stages against it.
Main results #
NTM.bodyBank— the bank the loop invariant pins, with its value at every named indexNTM.blankSlot_hoareTime— the body's first stage: blank the verdict slotNTM.publish_hoareTime— the stage that copies the verdict into the output slotNTM.simCfg_entry— the simulation stage is entered on exactly the loop's own bankNTM.afterSim,NTM.simTM_hoareTime— the tape state the simulation stage leaves the body inNTM.afterPark,NTM.parkStage_hoareTime— the cleanup stage that follows itNTM.afterPublish,NTM.publishStage_hoareTime— the stage that publishes the verdictNTM.afterBump,NTM.bumpStage_hoareTime— the arithmetic stageNTM.wipeStage_hoareTime— the wipe, which returns the bank to the loop invariant's shapeNTM.afterSim_trans,NTM.afterPark_trans,NTM.afterPublish_trans,NTM.afterBump_trans— the body's intermediate states survive a phase boundaryNTM.bodyTM,NTM.bodyTM_hoareTime,NTM.bodyTM_hoareTime_mid— the loop's body, its contract, and that contract in the shape the loop rule asks forNTM.tallyLoop_full,NTM.tallyLoop_full_bounded— body and test together, running the tally to its horizon, with both running times written in terms of the horizon aloneNTM.tallyLoop_keepsWindow_bounded,NTM.tallyLoop_keepsWindowOn— and the loop's space bound at the same layout, packaged for compositionNTM.prologueTM,NTM.prologueTM_hoareTime— the machine's prologue and its contractNTM.ppPark_hoareTime,NTM.ppMachine,NTM.ppMachine_hoareTime— the parking step, the whole counting machine, and its contractNTM.loopEpilogue_keepsWindowOn,NTM.prologueRest_keepsWindowOn,NTM.ppMachine_keepsWindow— the parts composed in space, and the whole machine's windowNTM.lt_iff_succ_sub_eq_zero,NTM.epilogueTM— the comparison the machine ends withNTM.epiloguePreTM_hoareTime— the epilogue's arithmeticNTM.epilogueEq_hoareTime— its comparisonNTM.afterEq,NTM.epiloguePostTM_hoareTime— its publicationNTM.epilogueTM_hoareTime,NTM.epilogueTM_keepsWindowOn— the three chained, in time and spaceNTM.binaryEqTime_le_of_le,NTM.bodyTime_le— the two running times are uniform over the loopNTM.bodyTM_keepsWindowOn— one pass of the body stays inside a window of its own widthNTM.bodyBank_eq_of— the bridge from the wipe's result back to the loop invariant
The bank the loop invariant pins at count v with tallies a and r.
Equations
- Complexity.NTM.bodyBank k N H v a r = Complexity.tallyWork (Complexity.NTM.cIdx k) (Complexity.NTM.aIdx k) (Complexity.NTM.rIdx k) (Complexity.NTM.bodyRest k N H) (v, a, r)
Instances For
The body's first stage: blank the verdict slot. The loop returns to its start state with the previous check's verdict still in the slot; the wipe that ends the body needs it blank, and so does the simulation, whose real output tape must start blank.
The bridge back to the loop invariant. After the body's wipe, a bank that carries the right numbers on the named registers and rests blank elsewhere is the bank the loop invariant pins at the next index.
The stage's entry configuration is the loop's own bank. Placing the simulation beside the
registers and redirecting its output puts exactly the tapes the loop invariant names where the
stage expects them: the machine's own tapes blank, the counter at v, the registers untouched,
and the verdict tape blank.
The real input tape, parked at cell one, with x on it.
Equations
Instances For
The tape state the simulation stage leaves the body in. Nothing is pinned but the registers: the simulated machine's tapes and the verdict tape hold whatever the run put there, and the heads are only bounded. What survives is enough — the counter's digits, the registers, the verdict bit, and a blank real output.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The simulation stage's contract. From the loop's bank with a blank output slot, the
stage halts within the horizon and leaves the body in NTM.afterSim, carrying the acceptance bit
of path v.
The tape state after the body's cleanup. Every head is parked, the counter has been
rewound and reads as v again, and the verdict tape is at cell one so its bit can be published.
The simulated machine's own tapes still hold whatever the run left; the wipe deals with them.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The tape state once the verdict is published. The slot now holds a symbol that is 1
exactly when the path accepted, which is what TM.ifTM branches on.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The body's publishing stage. One transition copies the verdict tape's cell into the output slot; nothing else on any tape moves.
The tape state after the tallies are bumped. The count has advanced, the chosen tally has grown by one, and the verdict slot is blank again — ready for the wipe, which needs it so.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The body's arithmetic stage. The verdict in the slot picks which tally grows; the count grows too, and the slot is blanked on the way out.
The body's last stage. Blanking the simulated machine's tapes and the verdict tape returns the bank to exactly the shape the loop invariant names — at the next index, with the tallies advanced. This is where the body closes.
The post-simulation state survives a phase boundary. It is stated in terms of cells and head bounds rather than exact tapes, which is what makes it stable: a transition preserves every cell and never pushes a head outward. A predicate that pinned the tapes could not survive here, because a halted simulation may leave a head on the marker, which the boundary then moves.
The pinned states between the body's later stages survive a phase boundary too, and for the easy reason: every tape they name is parked, so the boundary is the identity.
The same, once the verdict is in the slot.
The same, once the tallies have been bumped.
The counting loop's body. Blank the verdict slot, simulate one path, put the heads back, publish the verdict, bump the tallies and the count, and wipe the scratch tapes.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The body's running time: the six stages plus the five transitions between them.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The body's contract. One pass advances the count by one and the accepting or rejecting tally by one, according to whether the path selected by the counter accepts.
The tape state the body hands to the test: the bank at the next index, verdict slot blank.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The body meets the loop rule's obligation. Its contract is the tally step: the count advances and exactly one of the two tallies grows.
The counting loop. Body and test together run the tally to its horizon: the counter walks
from 0 to N, and the two tallies end holding how many of the paths accepted and how many
did not.
The comparison's running time is uniform over the loop. Every count the loop compares is at most the horizon, and a smaller number has no more binary digits, so one bound serves every iteration — which is what the loop rule demands.
The body's running time with every value-dependent part replaced by its bound at the horizon.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The body's running time is uniform over the loop. Only three parts of it depend on the iteration — the three counter increments — and each is bounded by the width of the horizon, since neither the count nor either tally ever exceeds it.
The head bound the test's rewinds need: enough for the horizon's digits.
Equations
- Complexity.NTM.testB N = N.bits.length + 3
Instances For
The test's running time at the horizon.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The counting loop with concrete bounds. Both running times are now written in terms of the horizon alone, which is what a polynomial space bound will need.
The body keeps a window. Its running time bounds how far any head can drift during one pass, and every tape it starts from is parked at cell one, so the whole pass stays inside a window of that width — independent of which iteration it is.
The counting loop's space bound at the concrete layout. Every configuration the loop ever reaches fits inside a window one iteration wide — and one iteration's width is a polynomial in the horizon, not in the exponentially many iterations.
The comparison the epilogue actually performs. Testing r < a directly would need a
strict comparator; testing (r + 1) - a = 0 needs only truncated subtraction and an equality
test against zero, both of which the subroutine library already has — and it avoids having to
complement the verdict afterwards.
The counting machine's epilogue. Bump the rejecting tally, subtract the accepting one from it, clear the counter to reuse as scratch, test the difference against zero, and publish the answer. The counter's value is spent by this point, which is what frees a register for the test's result.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The epilogue's tail: put the equality test's answer at cell one and publish it.
Equations
Instances For
The counting machine's epilogue. Bump the rejecting tally, subtract the accepting one from it, clear the counter to reuse as scratch, test the difference against zero, and publish the answer.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The epilogue's arithmetic, contracted. Three pinned stages: bump the rejecting tally, subtract the accepting one from it, and clear the counter — whose value is spent — to free a register for the comparison that follows.
The state between the epilogue's comparison and its publication: the answer sits on the counter tape, which the comparison used as its result register.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The epilogue's comparison. Testing the difference against zero decides r < a; the
answer lands on the counter tape, which the arithmetic stage cleared for exactly this purpose.
The state between the epilogue's comparison and its publication survives a phase boundary: every tape it names is parked, so the boundary is the identity.
The epilogue's running time: its three stages and the two transitions between them.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The epilogue's contract. From the bank the loop leaves — the counter at the horizon, the
two tallies holding their counts — the machine writes 1 into the verdict slot exactly when the
rejecting tally is smaller than the accepting one.
The epilogue keeps a window. It runs for a bounded time from tapes parked at cell one, so no head can leave a window of that width.
The register-arithmetic subroutines state their contracts in the TM.EmitPred shape, whose
output component is an accumulator of emitted bits. With nothing emitted that is just the blank
tape, so those contracts are pinned after all.
A pinned contract with a blank output is an TM.EmitPred contract with nothing emitted.
The counting machine's prologue. Measure the input, evaluate the horizon polynomial on a unary register, turn that register into the binary horizon with one increment, bump the wipe height, and clear the scratch.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The prologue lands on the loop's starting bank. The horizon sits on nIdx, the wipe
height on regIdx, and everything else — the counter, both tallies, the scratch registers — is
blank, which is what NTM.bodyBank at index zero says.
A bound covering the input length and every Horner accumulator the prologue forms.
Equations
- Complexity.NTM.prologueCap p lx = ((Complexity.TM.polyCoeffs p).sum + 1) * (lx + 1) ^ (Complexity.TM.polyCoeffs p).length
Instances For
The prologue's running time.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The prologue's contract. From the blank bank it lands on the loop's starting bank: the
horizon on nIdx, the wipe height on regIdx, everything else blank.
The prologue keeps a window. It runs for a bounded time from blank tapes parked at cell one, so nothing travels beyond a window of that width.
The whole counting machine. Park the heads off the left marker, set up the horizon and the wipe height, run the tally to its horizon, then compare the two tallies and publish.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The parking step. One transition off the initial configuration puts every head at cell one, which is where every stage of the machine expects to be entered.
The whole machine's running time.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The counting machine's contract. From its initial configuration it halts with the verdict
slot holding 1 exactly when the accepting paths outnumber the rejecting ones.
The counting loop's space bound, packaged as a conditional window contract so it can be composed with the machine's other parts.
The loop's starting configuration is inside any window at least one cell wide, and carries its left markers.
The loop and the epilogue, composed in space.
The prologue joined to the rest, in space.
The whole machine keeps a polynomial window. Every configuration it reaches from its
initial one fits inside S cells.
The horizon's width is the exponent plus one. Every bound the machine's parts state in terms of the horizon's number of digits is therefore a bound in terms of the exponent — which is what makes them polynomial in the input length rather than exponential.
The Horner cap, as a polynomial. The space bound the surrounding obligation asks for must be
a Polynomial ℕ, so each arithmetic expression the machine's parts are bounded by has to be
mirrored by a polynomial whose evaluation reproduces it.
Equations
- Complexity.NTM.capPoly p = Polynomial.C ((Complexity.TM.polyCoeffs p).sum + 1) * (Polynomial.X + 1) ^ (Complexity.TM.polyCoeffs p).length
Instances For
The operation budget, as a polynomial.
Equations
- Complexity.NTM.opBudgetPoly p = Polynomial.C 32 * (Complexity.NTM.capPoly p + Polynomial.C 2) ^ 3
Instances For
The layer budget, as a polynomial.
Equations
Instances For
The prologue's running time, as a polynomial. Only a bound is possible — the running time
involves max, which no polynomial reproduces — so the maxima are replaced by sums.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The loop's window is linear in the exponent. Its width is stated through the digit counts
of the horizon and of the two tallies; each of those is T + O(1), so the width is too — even
though the horizon itself is 2 ^ T.
The epilogue's window is linear in the exponent too. Its cost is stated through the digit
counts of the two tallies and of their difference, all of which are T + O(1).
The machine's space bound, as a polynomial. The sum of the three parts' widths: the prologue's, the loop's, and the epilogue's.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The comparison the surrounding obligation names: after 2 ^ p |x| tally steps, does the
accepting component exceed the rejecting one?
Equations
- One or more equations did not get rendered due to their size.
Instances For
The counting machine decides the PP comparison. It runs at the horizon p.eval |x| + 1
— one more than the specification names, so that the horizon is never zero, which the simulation
needs — and NTM.cmp_horizon_iff' says the comparison is the same either way.
The counting machine runs in polynomial space. Every configuration it reaches fits inside
NTM.ppSpacePoly cells — a polynomial in the input length, even though the machine's own running
time is exponential.
The trivial machine: it writes 0 and halts. This is what serves for a source machine
that starts halted, where no path can accept and the comparison is always false.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The trivial machine's running time.
Equations
- Complexity.NTM.zeroTime = 1 + 1 + (3 * (max (max (Complexity.TM.binarySuccTime 0) (Complexity.TM.binarySuccTime 1)) 1 + 1) + 1)
Instances For
The trivial machine's contract. It halts with 0 in the verdict slot.
The trivial machine's space bound, as a polynomial: a constant.
Instances For
A source that starts halted fails the comparison. No path accepts, so the accepting tally
is zero and cannot exceed the rejecting one — which is why the trivial machine, publishing 0
unconditionally, decides this case.