Scanning several tapes in lockstep with a finite control #
Most of what a machine does to a fixed-width register is a scan: check that a layout is
well formed, read a bounded amount of information out of it, compare it against another
register, rewrite it. Each of those is a finite automaton walking the register from left to
right, and none of them can be written with TM.loopTM, whose body forgets its control state
between iterations.
Complexity.Scanner is that automaton, and Complexity.TM.twoPassTM runs it. The scan is two
passes: rightward to the first blank, gathering into the control state, then leftward back to the
left marker, this time checking what it passes against everything the rightward pass learned. A
single verdict bit is left on a result tape. Two passes is what makes the pattern useful — one
pass cannot act on what it has not yet read.
The scan only ever reads. That costs nothing, because on a nondeterministic machine anything that would have to be computed can instead be guessed and checked — and checking is a scan. Tape contents are therefore invariant across a scan, which is what keeps its contract short.
All the tapes move together, so a column of the scan is the tuple of symbols under the heads.
Tape 0 is the one whose blank ends the rightward pass; the others are read for as long as it
lasts.
Main definitions #
Complexity.Scanner— a two-pass finite-state transducer overj + 1tapesComplexity.TM.twoPassTM— the machine that runs itComplexity.Scanner.runR,Complexity.Scanner.runL,Complexity.Scanner.run— what a scan computesComplexity.Scanner.prefixed— read parameters off the first cells, then run the check they chooseComplexity.Scanner.bitsStep— the reader that puts several registers' leading bits in the controlComplexity.Scanner.chunkRun,Complexity.Scanner.chunkStepCell— folding three columns at a timeComplexity.Scanner.comap— run a check on a wider tape set by naming its columnsComplexity.Scanner.upTo,Complexity.Scanner.after— freeze a check once it has read its own cells, or start it after a prefix, so checks of different lengths and positions share a scanComplexity.Scanner.all,Complexity.Scanner.or— run several checks in one scan, conjoining or disjoining their verdictsComplexity.Scanner.eq,Complexity.Scanner.eq_range_run— the comparison scanner, and what it decides when restricted to a range of cellsComplexity.Scanner.isConst,Complexity.Scanner.isConst_cell,Complexity.Scanner.isNotConst,Complexity.Scanner.isNotConst_cell— checking one cell against a fixed symbol, or against its failing to be oneComplexity.Scanner.isConst_range_run,Complexity.Scanner.isNotConst_range_run— the same over a range of cells, which is how a field is tested for being all zerosComplexity.Scanner.plusOne— the increment-check scannerComplexity.Scanner.andAll,Complexity.Scanner.andSome,Complexity.Scanner.andFirst,Complexity.Scanner.firstCol— the scanners that combine verdicts
Main results #
Complexity.TM.checkTM,Complexity.TM.checkTM_hoareTime— a check run on the whole register set, reading the columns its map namesComplexity.TM.twoPassCfg_run— a scan of a length-lentape takes2 * len + 3steps and halts with the automaton's verdict on the result tape, every other tape untouchedComplexity.TM.twoPassTM_hoareTime— the same as a Hoare triple, for compositionComplexity.Scanner.eq_run— the smallest example: comparing two of the tapesComplexity.Scanner.prefixed_run— a parameterized scan reports what its parameters choseComplexity.Scanner.bitsStep_run— what the field reader has readComplexity.Scanner.cellFold_chunk— three cells make a chunkComplexity.Scanner.cellFold_shift,Complexity.Scanner.runR_eq_cellFold— a check proved at the start of a scan applies wherever the scan puts itComplexity.Scanner.plusOne_run— one scan checks that one register holds one more than anotherComplexity.Scanner.andAll_run,Complexity.Scanner.andSome_run,Complexity.Scanner.andFirst_run,Complexity.Scanner.firstCol_run— and one scan reports any function of the verdicts
A two-pass finite-state transducer over j + 1 tapes scanned in lockstep. The rightward pass
only reads; the leftward pass may rewrite the column it is on.
- σ : Type
The control states of the automaton.
- decEqσ : DecidableEq self.σ
- start : self.σ
Where the rightward pass starts.
The rightward pass: read a column, update the state.
The leftward pass: read a column, update the state.
The verdict left on the result tape.
Instances For
Scanners that only read on the way out #
When everything a check needs is available before the cells it has to check — which is arranged by laying the parameters out in the first few cells of the scan and starting the data after them — the leftward pass has nothing to do.
Running a scanner on a wider tape set #
A check is written against the few registers it reads. Running it on a machine that has many registers is a matter of saying which columns those are — not of moving the registers next to one another, and not of giving each check its own copy of them.
Stopping a check early #
Checks read different numbers of cells, but a machine's scan has one length. A right-only check can simply be frozen once it has read the cells it cares about: the columns past that point are read and discarded, so a short check and a long one can share a scan.
A scanner whose leftward pass does nothing — the shape of every check built with
Complexity.Scanner.ofRight or Complexity.Scanner.prefixed.
Instances For
The saturating position counter a frozen check carries.
Equations
- Complexity.Scanner.upToIdx w p = ⟨min p w, ⋯⟩
Instances For
Skip the first w cells, then run a scanner over the rest. Together with
Complexity.Scanner.upTo this restricts a check to any range of cells, which is what pins a
guessed value against a field that does not sit at the start of its register.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Running several checks in one scan #
A machine has one result tape, and a two-pass scan writes it at the very end. Rather than give each check its own tape and combine the verdicts afterwards, the checks run together: one automaton whose state is the tuple of theirs, whose verdict is their conjunction. Each component still runs exactly the scan it would have run alone, so the lemmas about the individual checks apply unchanged.
Run two scanners at once, emitting the disjunction of their verdicts: the walk's step either keeps its configuration or advances it.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Reading parameters before checking #
A check usually needs a few values — a symbol to compare against, a direction to move — that are themselves on a register. Laying them out in the first cells of the scan and starting the data after them lets one scan do both: read the parameters into the control, then run a check chosen by them.
The accumulated parameters after reading p columns.
Equations
- Complexity.Scanner.auxRun a₀ readStep cols 0 = a₀
- Complexity.Scanner.auxRun a₀ readStep cols p.succ = readStep (Complexity.Scanner.auxRun a₀ readStep cols p) (cols (p + 1))
Instances For
The state of the parameterized check after q columns beyond the parameter block.
Equations
- Complexity.Scanner.mainRun c a mainStep cols t₀ 0 = t₀
- Complexity.Scanner.mainRun c a mainStep cols t₀ p.succ = mainStep a (Complexity.Scanner.mainRun c a mainStep cols t₀ p) (cols (c + p + 1))
Instances For
Read c columns of parameters, then check.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Through the parameter block, the scan is just accumulating parameters.
Past the parameter block, the scan runs the check the parameters chose.
What a parameterized scan reports: the check the parameters chose, run on the data after them.
Reading fixed-width fields into the control #
The parameters a check needs — a state, a symbol, a direction — sit in the first few cells of
their own registers. This reads all of them at once: s registers, w cells each, into a table
of bits the check can consult.
A field reader sees only the registers it names. Two scans whose named registers agree read the same table — which is what makes several checks reading the same parameter register agree on the parameters.
Folding three columns at a time #
A marked block stores each cell as a chunk of three bits — the head marker first, so that a
rightward pass knows whether the head is on a cell before it reads that cell's symbol. Checks on
such a block are naturally written a chunk at a time, and Complexity.Scanner.cellFold_chunk says
that a cell-level fold which buffers two columns and acts on the third computes exactly the
chunk-level fold.
A plain fold over the columns from off + 1 onwards.
Equations
- Complexity.Scanner.cellFold g cols off s₀ 0 = s₀
- Complexity.Scanner.cellFold g cols off s₀ p.succ = g (Complexity.Scanner.cellFold g cols off s₀ p) (cols (off + p + 1))
Instances For
A fold that consumes three columns at a time.
Equations
- Complexity.Scanner.chunkRun f cols off x₀ 0 = x₀
- Complexity.Scanner.chunkRun f cols off x₀ p.succ = f (Complexity.Scanner.chunkRun f cols off x₀ p) (cols (off + 3 * p + 1)) (cols (off + 3 * p + 2)) (cols (off + 3 * p + 3))
Instances For
A first scanner: register equality #
Check that a register carries a fixed symbol. Frozen to one cell by
Complexity.Scanner.upTo this reads a single cell, which is how a one-cell register — a
direction, say — is checked against a constant.
Equations
- Complexity.Scanner.isConst j a g = Complexity.Scanner.ofRight Bool true (fun (s : Bool) (cols : Fin (j + 1) → Complexity.Γ) => s && decide (cols a = g)) id
Instances For
Check that a register does not carry a fixed symbol. Frozen to one cell this is the
negation of Complexity.Scanner.isConst, which is what a loop's test needs when a failed check
should stop it.
Equations
- Complexity.Scanner.isNotConst j a g = Complexity.Scanner.ofRight Bool false (fun (s : Bool) (cols : Fin (j + 1) → Complexity.Γ) => s || decide (cols a ≠ g)) id
Instances For
A scanner that takes a conjunction #
Each check writes its verdict to its own register; one more scan over those registers reports whether they all said yes.
Report whether every scanned tape carries Γ.one throughout.
Equations
- Complexity.Scanner.andAll j = Complexity.Scanner.ofRight Bool true (fun (s : Bool) (cols : Fin (j + 1) → Complexity.Γ) => s && decide (∀ (i : Fin (j + 1)), cols i = Complexity.Γ.one)) id
Instances For
Report whether the designated tapes all carry Γ.one. The verdict registers of a
composite check are scattered — each check owns a contiguous block, so their result tapes are
not adjacent — so the combining scan reads every tape and looks only at the ones it is told
to.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Report whether the designated tapes carry Γ.one in their first cell. A verdict register
holds a single bit and blanks after it, so a scan whose length is set by some longer register must
look only at the first column.
Equations
- One or more equations did not get rendered due to their size.
Instances For
Report an arbitrary function of the first column. Verdict registers hold a single bit each, so a composite decision — "these checks all passed, or those did" — is a function of one column, however long the scan turns out to be.
Equations
- One or more equations did not get rendered due to their size.
Instances For
A scanner that checks an increment #
Registers hold little-endian bits, so a scan meets them least significant first — which is the order an increment carries in.
The value of the first p bits of a register, read little-endian.
Equations
- Complexity.Scanner.valUpTo f 0 = 0
- Complexity.Scanner.valUpTo f p.succ = Complexity.Scanner.valUpTo f p + if f (p + 1) = true then 2 ^ p else 0
Instances For
The control phases of TM.twoPassTM.
- right : TwoPassPhase
Walking right to the first blank of tape
0. - left : TwoPassPhase
Walking back left to the left marker.
- emit : TwoPassPhase
Publishing the verdict.
- done : TwoPassPhase
Halted.
Instances For
Equations
- One or more equations did not get rendered due to their size.
The control states of the scanning machine: a phase and the automaton's own state.
Equations
Instances For
Equations
Equations
- Complexity.TM.instFintypeTwoPassQ S = { elems := Finset.univ ×ˢ Finset.univ, complete := ⋯ }
What one step does #
The tapes a scan leaves alone: their heads are off the left marker, so every step writes them back unchanged and idles them.
The input head is off the left marker.
The result head is off the left marker.
The output head is off the left marker.
Instances For
The rightward pass, while tape 0 has not run out.
The turn: tape 0 has run out, so the scan starts back.
The leftward pass, while the left marker is not yet in sight.
The leftward pass reaches the marker and the scan turns to publishing.
Publishing the verdict: one bit onto the result tape, then halt.
What a whole scan does #
The tape shape a scan expects: every tape carries its left marker and nothing else does, and
tape 0 runs for exactly len cells.
Cell zero of every scanned tape is the left marker.
No other cell is.
Tape
0is non-blank throughout its length.And blank immediately after.
Instances For
The rightward pass.
The leftward pass.
A whole scan. Started with every scanned head on cell one, the machine walks tape 0 to
its first blank and back, and halts with the automaton's verdict written on the result tape.
Nothing else on any tape moves.
The scan as a Hoare triple, which is the form TM.seqTM and TM.loopTM compose.
A check on the whole register set. The scanner names the columns it reads, so a check needs neither its registers moved next to one another nor a private copy of them.
Equations
- Complexity.TM.checkTM S f = Complexity.TM.twoPassTM (S.comap f)
Instances For
Its contract: the verdict of the scanner, read off the named columns.