Parts of the path-counting machine #
⚠️ Unreviewed by Bolton
The machine that will witness PP ⊆ PSPACE enumerates choice sequences, runs one path of the
probabilistic machine along each, and keeps a running count. Its inner call is
NTM.choiceTM, which is time-bounded rather than space-bounded: every path halts within the
protocol's own time bound, so the window follows from TM.keepsWindowOn_of_haltsIn with no space
hypothesis at all. That is simpler than the corresponding step for PH ⊆ PSPACE, whose inner
call is only space-bounded.
An architectural constraint on the body #
The body must clear the simulated machine's work tapes between iterations, and the only
content-agnostic reset available is TM.resetTapesTM, whose wipe phase (TM.wipeLoop_hoareTime)
requires the real output tape blank. The rewind phase (TM.rewindList_hoareTime) does not — it
asks only that the output be TM.Parked.
That rules out the obvious design. TM.ifTM branches on the real output tape's verdict cell, so
using the simulation as a conditional's test would leave the verdict sitting on the output and
block the next iteration's wipe; and there is no subroutine that clears the output tape. The
verdict therefore has to be written to a work tape — the simulation output-retargeted, as in
NTM.choiceTM_placed_keepsWindow — and republished on the output only for the moment the branch
needs it. TM.writeOutputBitTM is the one-transition subroutine that does the republishing; the
library had no way to move a bit from a work tape to the output, which is why the constraint
above looked fatal.
Main results #
NTM.acceptCount_add,NTM.mem_iff_polyHorizon— the comparison is stable under enlarging the horizon, so the machine may use a polynomial bound in place of the protocol's own (uncomputable) time functionhasBinaryString_unique,hasBinaryNat_value_unique— a binary tape determines its contentstallyState,tallyIdx,tallyIdx_tallyState— the counting machine's tape state, and the index read back off itnatTape,tallyWork,tallyState_iff— that state, fully pinnedbinarySucc_tallyState,binarySucc_tallyState_counter,binarySucc_tallyState_rej— the three increment stages, from the library's canonical successorNTM.choiceStream_of_hasBinaryString,NTM.choiceStream_eq_choicesOfNat— a binary counter tape is the choice sequence of its valueNTM.choiceTM_choiceCells— the path simulation leaves the counter's digits aloneNTM.choiceTM_dropChoice_eq,NTM.choiceTM_verdict— what the simulation computes from a counter value, and that its verdict is the enumeration's acceptance testNTM.choiceTM_haltsIn— a path simulation halts within the protocol's time boundNTM.choiceTM_keepsWindowOn— and therefore keeps a windowNTM.choiceTM_placed_keepsWindow,NTM.choiceTM_lifted_keepsWindow— and so does the call once placed in the larger machine, with or without output retargetingnatTape_parked— the numeric tapes satisfy the side condition the reset subroutines demandNTM.acceptCount_eq_card_range— the accepting-path count is a count over counter valuesNTM.tally,NTM.tally_eq_card— the running accumulation computes that countNTM.mem_iff_two_mul_tally— what the counting machine decidesNTM.tally_add_compl,NTM.lt_two_mul_tally_iff,NTM.mem_iff_tally_lt_tally— and the same as a comparison of two counters, so the machine never forms2 ^ TNTM.acceptsAt,NTM.mem_iff_tally_lt_tally_poly— the complete machine specificationNTM.tallyStep,NTM.tallyStep_iterate— the loop invariant on (counter, accepting tally, rejecting tally)NTM.mem_iff_iterate_tallyStep— membership as one iterated function and a comparisonNTM.outSlot,NTM.tallyPre,NTM.tallyPost,NTM.tallyLoop_hoareTime_of_hoare— the counting loop from a Hoare contract for the body and one for the test, chained through fully pinned tape states, which is the interface a machine construction meetsNTM.tallyLoop_keepsWindow_of_hoare— the same loop's space bound, one iteration wideNTM.delayNTM_char,NTM.acceptCount_eq_zero_of_qstart_eq_qhalt,NTM.not_mem_of_qstart_eq_qhalt— thePPcharacterisation transported to the delayed machine, and the degenerate case that licenses itNTM.choiceTM_delay_dropChoice_eq,NTM.choiceTM_delay_haltsIn— what the loop body's simulation computes, entered where a composed machine can enter itNTM.tally_eq_acceptCount,NTM.tally_cmp_iff— the tally is the accepting countNTM.cmp_horizon_iff,NTM.cmp_horizon_iff'— thePPcomparison does not depend on the horizon, past the point where every path has haltedNTM.tallyLoop_hoareTime,NTM.tallyLoop_hoareTime_of_body— the counting loop, granted only that the body advances the triple
A binary tape determines its contents #
The indexed loop rule reads the iteration count back off the tapes, so the counter's encoding has to be unambiguous. It is: a tape's digits are pinned cell by cell and the first blank marks the end, so two representations on the same tape coincide.
A tape carries at most one binary string.
A tape carries at most one natural number.
The counting machine's tape state #
The tape state of the counting machine. Three designated work tapes carry the counter and the two tallies in canonical binary; every other tape, the input, and the output are pinned.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The iteration count read back off the counter tape. The input and output tapes are ignored;
they are present because TM.loopTM_hoareTime_indexed takes the index as a function of the whole
tape state.
Equations
- Complexity.tallyIdx cIdx x✝¹ work x✝ = Classical.epsilon fun (v : ℕ) => (work cIdx).HasBinaryNat v
Instances For
Reading the index back is faithful. This is the hypothesis TM.loopTM_hoareTime_indexed
needs: the loop's variant can be computed from the tapes it is looking at.
The canonical tape for a natural number.
Equations
Instances For
The tape state is fully pinned. Tape.HasBinaryNat determines a tape outright, so the
counting machine's state names every tape exactly — which is what lets the library's framed
subroutine contracts, whose preconditions pin all three tape components, be applied to it.
A canonical number tape is parked: its head is off the left marker and no cell beyond the marker holds one. This is the side condition the rewind and wipe subroutines ask of every tape they carry along.
The three increment stages. Advancing one of the numeric tapes by one is the library's canonical successor applied to the pinned state; the output tape is carried along untouched, whatever it holds.
The counter-increment stage.
The rejecting-tally-increment stage.
A path simulation halts within the protocol's own time bound. All paths of tm halt
within f |x| steps, and choiceTM follows one of them step for step, so it halts too.
A path simulation keeps a window. Since every path halts within f |x| + 1 steps and a
head moves at most one cell per step, nothing travels more than that far beyond where it started.
No space hypothesis on tm is needed — its time bound does the work.
The accepting-path count is a count over counter values. NTM.acceptCount ranges over
functions Fin T → Bool; the counting machine ranges over the numbers below 2 ^ T. The
correspondence of choicesOfNat and natOfChoices identifies the two counts, so the machine's
tally is the quantity PP compares against half.
The running tally #
A counting loop does not compute a cardinality; it accumulates. tally is that accumulation, and
tally_eq_card identifies it with the cardinality — which is the loop invariant the machine's
correctness proof will carry.
The running count of the values below N satisfying P, as a loop accumulates it.
Equations
- Complexity.NTM.tally P 0 = 0
- Complexity.NTM.tally P N.succ = Complexity.NTM.tally P N + if P N = true then 1 else 0
Instances For
What the counting machine decides. Membership is a comparison between 2 ^ T and twice a
running tally over counter values — an accumulation a loop performs and a comparison of two
binary naturals. Neither probability, nor rationals, nor a quantifier over functions remains.
The threshold is a comparison of two counters. More than half of the range satisfies P
exactly when the tally of P exceeds the tally of its complement. The machine therefore never
has to form 2 ^ T or multiply: it keeps two counters and compares them.
What the counting machine decides, as a comparison of two counters.
The counter tape is the choice sequence #
What the path simulator reads off a binary counter tape. NTM.choiceStream maps every
cell that is not Γ.one to false, including the blanks past the counter's last digit — and
those are exactly the high bits of the number, which are zero. So a tape carrying a binary string
is the choice sequence it encodes, padded with false for free.
The count is stable under enlarging the horizon #
A counting machine cannot evaluate the protocol's own time bound f — it is an arbitrary
function known only to be O(n^m). It must therefore count over a polynomial horizon it can
compute. That is sound because every path has already halted: extending the horizon multiplies
both the accepting count and the total by the same factor, so the comparison is unchanged.
Extending the horizon multiplies the accepting count by the number of extensions.
The comparison is unchanged by a larger, computable horizon. Counting over p |x| choice
sequences instead of f |x| scales both sides by the same power of two. This is what lets the
machine use a polynomial it can evaluate in place of the protocol's own time function.
The PP comparison does not depend on the horizon, as long as the horizon is past the
point where every path has halted. Extending it multiplies both the accepting count and the total
by the same power of two. This is what lets the counting machine run to a horizon of its own
choosing rather than the one its specification names.
The per-value acceptance test the counting loop performs: run the path selected by counter
value v and report whether it halts accepting.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The degenerate machine decides the empty language. A machine that starts halted never
moves, and its output tape is blank, so no choice sequence is accepting and the PP comparison
fails on every input. Ruling this case out is what licenses the qstart ≠ qhalt hypothesis the
delay construction needs.
The PP characterisation survives the delay. NTM.delayNTM spends two extra steps and
doubles the count of accepting paths; the horizon's own doubling absorbs exactly that, so the
comparison 2 ^ T < 2 * acceptCount is unchanged. This is what lets the counting machine
simulate a machine whose first transition ignores its choice bit — the only kind a composed
machine can enter, since no stage can be handed a head at cell zero.
The complete machine specification for PP. Everything on the right is something a
machine performs: iterate a counter to a polynomial bound it can evaluate, simulate one path per
counter value, keep two tallies, and compare them. No probability, no rationals, no quantifier
over the function space, and no reference to the protocol's own time function survive.
The inner call, fully placed. The path simulator sits inside a machine with m extra
tapes and writes its verdict onto a work tape rather than the real output. Lifting is free and
the output redirection costs one cell, so the whole placed call keeps a window one wider than the
simulation's own. This is the form in which the counting machine invokes it.
The loop invariant #
The counting machine carries three numbers on its tapes: the counter, the accepting tally, and
the rejecting tally. tallyStep is one iteration's effect on that triple, and tallyStep_iterate
is the invariant relating the state after N iterations to tally — the fact the machine's
correctness proof carries through the loop.
One iteration of the counting loop, on the triple (counter, accepting tally, rejecting tally).
Equations
Instances For
The loop invariant. After N iterations from the zero state, the counter reads N and
the two tallies read the counts of the values below N satisfying and failing P.
What the counting machine must compute, as a single iterated function. Running the loop
2 ^ T times from the zero state and comparing the two tallies decides membership. This is the
form the machine's Hoare contract will take: an iteration count, one step function, and a
comparison of two components of the final state.
The counting loop, granted a body that realises one step. With the tape state of
tallyState and the index read back by tallyIdx, the indexed loop rule reduces the whole run to
two obligations: one iteration advances the state by tallyStep, and the loop halts at the final
count. Everything about counting — the invariant, the variant, the fuel — is discharged here.
The output tape as the counting loop uses it: a single verdict cell, and blanks beyond it.
TM.loopTM inspects exactly this cell after rewinding the output, so the loop's whole
interaction with its output tape is the choice of one symbol.
Equations
Instances For
A verdict slot never carries a stray left marker, so it is parked.
The tape state the counting loop sits in at its own start state. Every tape is named
outright: the three numeric registers hold the count and the two tallies, every other work tape
is back at its resting contents, and the output carries the previous check's verdict, which for a
continuing iteration is 0.
Equations
- One or more equations did not get rendered due to their size.
Instances For
The tape state the loop's test leaves behind: the same registers, with the verdict slot
holding 1 exactly when the count has reached its horizon.
Equations
- One or more equations did not get rendered due to their size.
Instances For
A pinned tally state is parked on every tape, given that the resting tapes are.
The same, for the state the test leaves behind.
The counting loop from two Hoare contracts. This is the interface the machine construction actually meets: a contract saying the body advances the tally by one index, and a contract saying the test reports whether the horizon has been reached.
TM.loopTM is a do-while — its body runs before its first test — so a loop that halts after N
tallies performs its N-th body pass on the terminating iteration, not on a continuing one.
The indexed rule is therefore applied at N - 1, which is why the body is never asked to run at
index N and why 1 ≤ N is needed.
The counting loop keeps a window one iteration wide. The loop runs exponentially many iterations, so no bound derived from its total running time can be polynomial; what is polynomial is a single iteration, and every state the loop returns to has all its heads at cell one.
The path simulation leaves the counter's digits alone. choiceTM writes every choice bit
back unchanged and only advances the head, so the counter tape's contents survive the run — the
body has merely to rewind the head to restore the encoding for the next iteration.
A counter tape reads exactly as the choice sequence of its value. Combining the encoding
bridge with the bit correspondence: if the choice tape carries the canonical representation of
v, then the stream the path simulator consumes is choicesOfNat T v — no padding, no copy, and
no fixed-width counter.
A canonical binary tape carries its left marker and nothing else does.
What the simulation computes from a counter value. Started with the counter tape carrying
v and the machine's own tapes in their initial configuration, the path simulator produces
exactly the trace of tm along the choice sequence choicesOfNat T v.
What the loop body's simulation computes. Started from the delayed machine's
post-sentinel configuration — every head at cell one, the counter carrying v, the machine's own
tapes blank — the path simulator produces exactly the source machine's trace along the choice
sequence v encodes.
Two corrections cancel here. Entering after the sentinel step costs one step of the delayed
machine, and the delay itself costs one; together they are exactly the two steps
NTM.delayNTM_trace_initCfg charges, so the horizon T on the counter is the source machine's
own horizon, with no off-by-one left over.
The loop body's simulation, run to a halt with its verdict. The counter's value selects a path, the simulation follows it to the end, and the halted configuration's verdict cell holds exactly the bit the tally is counting.
The verdict the loop body reads is the enumeration's acceptance test.
The counting loop, granted only that the body advances the triple. The body's obligation
no longer mentions iterates or tallies: from a state holding (v, a, r) it must reach the state
holding (v + 1, a + [P v], r + [¬P v]). All the counting bookkeeping is discharged here.
The inner call as the loop's test, placed. TM.ifTM branches on the real output tape's
verdict cell, and the path simulator already writes tm's verdict there — so the simulation can
serve directly as the conditional's test, with no output retargeting. Only the lift into the
larger tape space is needed, and that is free.