Skip to content

refactor(MultiTapeTM): Make the deterministic machine a nondeterministic one - #821

Draft
barni120400 wants to merge 18 commits into
leanprover:mainfrom
barni120400:multitape/ntm-deterministic
Draft

refactor(MultiTapeTM): Make the deterministic machine a nondeterministic one#821
barni120400 wants to merge 18 commits into
leanprover:mainfrom
barni120400:multitape/ntm-deterministic

Conversation

@barni120400

@barni120400 barni120400 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Builds on #820. GitHub shows that PR's commit too, since its branch is not on this repository and cannot be used as a base. Only the commits after 3e67a9b belong here, and they are exactly:

barni120400/cslib@multitape/ntm...multitape/ntm-deterministic

MultiTapeTM now extends MultiTapeNTM, permitting exactly the transition its transition function prescribes, with ofTr building one from a transition function. Everything defined for the nondeterministic machine therefore applies to it directly: initCfg and ComputesInTimeAndSpace are no longer defined for it, Step, ComputationPath and the Computes notions are available on it unchanged, and the separate embedding file is gone.

What is left is what follows from there being no choice to make. step_iff identifies the inherited Step with the graph of step; a computation path can then only follow runFrom, and runPath supplies one of every length, giving computesInExactTimeAndSpace_iff_runFrom. The previous embedding proved one direction of that; it is now an equivalence, so runFrom, spaceUsed and the TapeLemmas results keep their meaning.

Then several things move out of the deterministic file, because none of them were deterministic:

  • Stepping. step and Step each carried the same case analysis, differing only in choosing an action by a function or by a relation. Cfg.stepWith, Cfg.StepWith and Cfg.stepWith_iff state that once in Configuration.lean, which mentions no machine. Both machines then define a step in a line, and step_iff is that lemma applied.
  • What a step does to a tape. workTapePos_step_le and step_workTapes_eq_of_ne hold because of what applying an action does, not because of determinism. They are now stated of Action.apply and of Step, and the deterministic machine gets them by its own step being one of those steps.
  • The space bounds, whose proofs are cardinality arguments about a list of configurations. spaceUsedOfCfgs_le and spaceUsedOfCfgs_mono give ComputationPath.space_le, a bound for nondeterministic machines that the library did not have, and leave spaceUsed_linear and spaceUsed_mono as corollaries.

spaceUsed was describing what ComputationPath.space already described, so it is now that: the space of the machine's own run, (tm.runPath cfg t).space. The three views agree by rfl — a path's space, a sum over the tapes, and spaceUsedOfCfgs of the configurations passed through.

Two changes to Nondeterministic.lean that a reader of #820 should see called out:

  1. ComputationPath gains nothing and loses its endpoints. It designated one end and not the other, so the two ends of one list were described in two different ways. It is now a non-empty list whose consecutive elements are joined by a step, with start and last read off it. Fixing the endpoints was the only thing IsChainFromTo added over IsChain and non-emptiness, so it is no longer used here; ComputesSuchThat asks that a path start at the initial configuration, which the type used to carry.
  2. A path may start anywhere, which is what lets spaceUsed and the results about it be read off one.

Also removes declarations nothing referred to (runFrom_add, runFrom_output_eq_of_halt, spaceUsedByTape_le_spaceUsed, spaceUsed_zero_tapes_eq_zero, outputSymbol_of_halt, step_output, outputSymbol, haltsAtStep, halting_step_unique, not_halts_of_repeat_nonhalt) and TransitionRelation, which was the graph of step and so is the inherited Step; relatesInSteps_iff_runFrom_eq is stated over Step.

NB: claude was used heavily throughout.

@barni120400
barni120400 marked this pull request as draft August 20, 2026 08:58
@barni120400
barni120400 force-pushed the multitape/ntm-deterministic branch 3 times, most recently from ca7bec3 to a03c9d4 Compare August 20, 2026 15:44
avivbarnatan-air and others added 6 commits August 29, 2026 11:30
A nondeterministic machine replaces the transition function by a
transition relation. Configurations and the effect of an action move to a
shared `MultiTape/Configuration.lean`; no existing statement changes
meaning.

A computation path is the list of configurations it passes through
together with a proof that they form a chain of steps from the initial
configuration to the one it ends at, so `List.IsChainFromTo` carries the
whole validity condition. `MultiTapeTM.toNTM` embeds the deterministic
machine and `toNTM_computes` shows the embedding preserves computation in
bounded time and space.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…eterministic one

`MultiTapeTM` now extends `MultiTapeNTM` with a transition function and the
requirement that the permitted transitions are exactly the ones it prescribes.
A deterministic machine is therefore literally a nondeterministic one rather
than something translated into one, and `DeterministicToNondeterministic.lean`
is removed.

Everything stated of the general machine now applies unchanged: `initCfg` and
`ComputesInTimeAndSpace` are no longer defined here, and `Step`,
`ComputationPath`, `Computes`, `ComputesInExactTime`, `ComputesInExactSpace` and
`ComputesInExactTimeAndSpace` are available on a `MultiTapeTM` directly.

What is left is what follows from there being no choice to make. `step_iff`
identifies the inherited `Step` with the graph of `step`; from it a computation
path can only follow `runFrom`, which is one induction on the path index rather
than one per measure, since the output tape lives in the configuration and a
path carries no labels. `runPath` supplies a path of every length, and together
they give `computesInExactTimeAndSpace_iff_runFrom`, an equivalence where
`toNTM_computes` was a one-way implication.

`ofTr` builds a machine from an initial state and a transition function and
keeps it computable: relation to function needs choice, so `tr` stays data and
`Tr_iff` records that the two agree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…function

`Tr` is stored but fully determined by `tr`, so making `Tr_iff` a simp lemma
lets it normalise away wherever it appears. `step_iff` no longer names it, and
`Tr` is now absent from the deterministic file outside the structure itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ralise the rest

Now that a deterministic machine is a nondeterministic one, most of what
`Deterministic.lean` carried is either unused or true of any machine.

Removed as unreachable: `runFrom_add`, `runFrom_output_eq_of_halt`,
`spaceUsedByTape_le_spaceUsed`, `spaceUsed_zero_tapes_eq_zero`,
`outputSymbol_of_halt`, `step_output`, `haltsAtStep`, `halting_step_unique` and
`not_halts_of_repeat_nonhalt`. Nothing in the library referred to them.

`TransitionRelation` was the graph of `step`, which is what the inherited `Step`
already is, so it is dropped and `relatesInSteps_iff_runFrom_eq` is stated over
`Step`.

The space bounds turn out to mention no machine at all: their proofs are
cardinality arguments about a list of configurations. `spaceUsedOfCfgs_le` and
`spaceUsedOfCfgs_mono` move to `Configuration.lean`, which gives
`ComputationPath.space_le` for nondeterministic machines, a bound the library did
not have, and leaves `spaceUsed_linear` a corollary rather than its own proof.

`visitedByTapeHead` becomes `visitedOfCfgs` at a step index, so there is now one
notion of which cells a computation touches, and `spaceUsed_eq_spaceUsedOfCfgs`
holds by `rfl`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… core

`step` and `Step` each carried their own copy of the same case analysis: a
halted configuration idles, a running one applies the chosen action. Only the
choosing differed, by a function in one case and by a relation in the other.

`Cfg.stepWith` and `Cfg.StepWith` move that to `Configuration.lean`, which
mentions no machine, and `Cfg.stepWith_iff` states there once that choosing from
the graph of a function is choosing its value. Both machines now define stepping
in a line, and `step_iff` is that lemma applied.

`outputSymbol` is removed. The output tape is part of a configuration, so the
symbol a step emits is already recorded in `Cfg.output` and nothing referred to
it, leaving the case analysis in exactly one place.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…lding

Marking `Cfg.stepWith` a simp definition, as `Action.apply` beside it already
is, lets its two cases fire from a hypothesis on the state. The proofs that
reasoned about a step no longer name the internals: `unfold step Cfg.stepWith`
becomes `simp [step, hstate]`.

Only the function is marked. The relation stays opaque so that `Step` continues
to normalise through `step_iff` instead of unfolding into its cases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@barni120400
barni120400 force-pushed the multitape/ntm-deterministic branch from a03c9d4 to ef26fcc Compare August 29, 2026 13:02
avivbarnatan-air and others added 12 commits August 29, 2026 16:44
…its run

`spaceUsed` described the same thing as `ComputationPath.space` and was defined
separately. It is now that space, read off the machine's own run.

`ComputationPath` gains the configuration it starts from. It was pinned to
`initCfg`, but `spaceUsed` and the results about it speak of a run from any
configuration, so the path could not express them. `IsChainFromTo` already takes
both endpoints; only the start was being fixed.

`runPath cfg t` is then the run from `cfg`, `spaceUsed cfg t` is its space, and
`visitedByTapeHead` is the cells its configurations touch. The three views of
space agree by `rfl`: as a path's space, as a sum over the tapes, and as
`spaceUsedOfCfgs` of the configurations passed through.

`spaceUsed_linear` and `spaceUsed_mono` follow from the results about any run,
`ComputationPath.space_le` and `spaceUsedOfCfgs_mono`, rather than being proved
again for the deterministic case.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…th ends read off it

The path designated one end and not the other: `start` was a parameter and
`last` a field, so the two ends of the same list were described in two different
ways.

Neither is designated now. A path is a non-empty list of configurations whose
consecutive elements are joined by a step, and `start` and `last` are its head
and last element. `IsChainFromTo` is no longer needed, since fixing the
endpoints was the only thing it added over `IsChain` and non-emptiness.

`ComputesSuchThat` asks that a path start at the initial configuration, which
the type used to carry. The deterministic side gains `runPath_start` beside
`runPath_last`, and the results about a path speak of `p.start` where they
spoke of a parameter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…has steps

`workTapePos_step_le` and `step_workTapes_eq_of_ne` were proved for the
deterministic machine, but neither uses determinism: a head moves by at most one
cell and no cell but the one under it changes because that is what applying an
action does.

Both are now stated of `Action.apply` in `Configuration.lean` and of `Step` in
`Nondeterministic.lean`, so they hold of any machine. The deterministic machine
gets them by its own step being one of those steps, which is a line each rather
than a proof.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tself

`step_of_halt` was proved twice, once of `Step` and once of `step`. The
deterministic one is now the nondeterministic one applied to the machine's own
step, so `step_iff` moves up to sit with `step`, where it belongs: it is the
fact the rest of the deterministic development rests on.

Also restores two `@[simp]` attributes, on `step_of_halt` and `runFrom_of_halt`,
that were dropped by mistake when neighbouring declarations were removed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`workTapePos_step_le` and `step_workTapes_eq_of_ne` were restated for the
deterministic machine, a line each, only to supply `tm.Step c (tm.step c)` to
the general result. That fact is now stated once, as `step_step`, and the
general results are applied to it where they are used. A further such result
costs nothing.

`path_length` was `length_cfgs` under another name and is dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`runFrom` iterated `step` through `Function.iterate`, so reaching the
configuration after one more step took a lemma. Defining it by recursion makes
`runFrom_zero` and `runFrom_succ_eq_step'` hold by `rfl`, and
`runFrom_succ_eq_step`, which peeled a step off the front, is unused and gone.

`step_step` was `step_iff.mpr rfl` under a name, and is inlined at its three
uses.

`step_of_halt` stays. The nondeterministic one is an equivalence about the
relation, `Step c c' ↔ c' = c`, which cannot rewrite the term `step cfg`; the
deterministic one is the equation that can, which is what makes it useful to
`simp`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s about

`runFrom` goes back to iterating `step` with `^[·]`.

The deterministic file carried four lemmas saying a computation path follows
`runFrom`, all in service of `computesInExactTimeAndSpace_iff_runFrom`. What
they rest on is not `runFrom` but the uniqueness of a step, so it is stated of
`MultiTapeNTM`: a machine whose steps are unique has one run of each length from
a given configuration, `ComputationPath.cfgs_eq`.

The deterministic file supplies that uniqueness, which `step_iff` gives in a
line, and reads the measures off its own run. `path_getElem`, `path_cfgs`,
`path_last` and `path_space` are gone.

`ComputesInExactTimeAndSpace` was never redefined here: it is the
nondeterministic notion, and the theorem states it by step index.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A run is now built rather than described. `ComputationPath.single` is the run of
no steps and `ComputationPath.concat` extends one by a step, so the machine's
own run is a recursion on the number of steps, and `runFrom` is the
configuration it ends in. `spaceUsed` was already the run's space, so both
measures are now read off the same object.

Uniqueness is stated of the general machine and as an equality of runs, not just
of their configurations: a machine whose steps are unique has exactly one run of
each length from each configuration, `ComputationPath.eq_of_start_of_time`.

That is what removes the existential. `ComputesInExactTimeAndSpace` quantifies
over runs, as it must for a machine with a choice; for one without, the run in
question is the machine's own, and
`computesInExactTimeAndSpace_iff_runFrom` says so with nothing existential left.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…fast one

Building the run says what running is; iterating `step` says how to do it.
`runFrom_eq_iterate` states they agree, so the `Function.iterate` API is
available on `runFrom` again, and a `csimp` lemma has the compiler evaluate it
by iteration rather than by building a list of configurations.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Appending a configuration leaves one thing to check, that the run's last
configuration steps to it, which is the hypothesis.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A run is a list, so proving something of every run meant reasoning about
indices. `ComputationPath.induction` gives it the induction of an inductive
definition instead: a run is the run of no steps, or one more step on a shorter
run. The representation is unchanged, so the list API that `space` and
uniqueness rest on still applies; only the way to reason about a run is added.

`ComputationPath.reflTransGen`, that a run reaches its last configuration from
its first, is the first thing proved that way. `IsChainFromTo` supplied it
before the endpoints stopped being part of a run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…bound bounds

`workTapePos_step_le` and `workTapes_step_eq_of_ne` are lemmas about what a step
does to a tape, so they join the others in `TapeLemmas`, stated of any machine.

`space_le` becomes `space_le_linear`, matching `spaceUsed_linear`, which it now
proves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants