feat(MultiTapeTM): concatenate the outputs of two machines - #877
Open
crei wants to merge 1 commit into
Open
Conversation
`concat tm₀ tm₁` runs `tm₀`, rewinds the native input head, and runs `tm₁`. The output tape is write-only and a machine only ever appends to it, so the composite outputs the output of `tm₀` followed by the output of `tm₁`, and no intermediate result is stored anywhere. The two machines get disjoint blocks of work tapes, `tm₀` the first `k₀` and `tm₁` the last `k₁`. That is what makes it unnecessary to know anything about the configuration `tm₀` halts in: `tm₁` starts on blank tapes, exactly as it would on its own, and the tapes `tm₀` leaves behind are never touched again. Rewinding the input in between is what lets `tm₁` read the same input. Plumbing/ExtendTapes.lean: * `eq_embed_initCfg` reads the embedding backwards, and is what hands a fresh block of tapes to a machine: a configuration in that machine's initial state whose own block is blank and rewound, with the input head at the start, *is* its embedded initial configuration, whatever the other tapes hold and whatever output has been produced. So a combinator can start a machine on a block without knowing anything about the configuration the previous one left behind, beyond its output and that it did not touch this block. * `workTapes_embed_of_notMem_range`, `workTapePos_embed_of_notMem_range`: the idleness of the extra tapes, which the module docstring claimed but which was observable only through `spaceUsedByTape_extra`. * `extend_eq_self`, `initCfg_extendTapes` (factored out of `runFrom_extendTapes`) and `spaceUsed_extendTapes`. NormalForms/RewindInput.lean: * `spaceUsed_rewindInput_le`: normalizing costs no space. The rewind moves no work-tape head, so every cell the normalized machine visits was already visited by the original one. * `rewindInput_halts_spaceUsed`: the normal form together with its cost, so a combinator that feeds one input to two machines never has to unfold the rewind. Only the time bound grows, by the input length plus two. * `initCfg_rewindInput`. Plumbing/Concat.lean (new): * `concatTapeLeft`, `concatTapeRight`: the two blocks of work tapes, and their disjointness. * `concatPrefix`, `concat`: the first phase and the composite. * `computesInTimeAndSpace_concat`: the composite computes the concatenation. The extra time is one input rewind; the extra space is `k₀ + k₁`, since whichever machine is not running has an idle head on each of its tapes and each such head still counts for the cell it sits on. The rewind itself contributes nothing. Combinators/Concat.lean (new, with a README for the new directory): * `computableInTimeAndSpace_concat`: the function-level result, stated for an arbitrary function together with the assumption that its encoding is the concatenation of the two encodings, rather than for a fixed pairing. So it covers whatever the caller encodes, and the caller is the one who has to know that the concatenation of the two encodings is again injective. * `computableInTimeAndSpace_pair`: the pair, the typical case. The remaining supporting results, all of which the concatenation needs and none of which is specific to it: * `TapeLemmas`: `visitedByTapeHead_add` and `spaceUsed_add_le` split the space of a run into its two phases; `spaceUsed_eq_of_workTapePos` reduces space usage to the head positions alone, which is what lets a machine be replaced by a simulation of it; and `spaceUsed_le_of_workTapePos_mem` is the sharp bound for a phase that moves no head, which visits no new cell at all rather than one per tape. * `Plumbing/Basic`: `Cfg.prependOutput` and `runFrom_prependOutput`: a run does not depend on the output accumulated before it, which is what lets a machine run after another has already written part of the output. * `Plumbing/Sequential`: `initCfg_seq` and `spaceUsed_seq_le`. * `Plumbing/Rewind`: `step_input_workTapePos` and `runFrom_input_workTapePos`, since the input rewind moves no work-tape head. `Plumbing/Basic` now imports `TapeLemmas` rather than `Deterministic` directly, which is also what brings `TapeLemmas` into the import graph of this directory, and `Plumbing/ExtendTapes` imports `Plumbing/Basic` for `Cfg.prependOutput`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
concat tm₀ tm₁runstm₀, rewinds the native input head, and runstm₁. The output tape is write-only and a machine only ever appends to it, so the composite outputs the output oftm₀followed by the output oftm₁, and no intermediate result is stored anywhere.The two machines get disjoint blocks of work tapes,
tm₀the firstk₀andtm₁the lastk₁. That is what makes it unnecessary to know anything about the configurationtm₀halts in:tm₁starts on blank tapes, exactly as it would on its own, and the tapestm₀leaves behind are never touched again. Rewinding the input in between is what letstm₁read the same input.Plumbing/ExtendTapes.lean:
eq_embed_initCfgreads the embedding backwards, and is what hands a fresh block of tapes to a machine: a configuration in that machine's initial state whose own block is blank and rewound, with the input head at the start, is its embedded initial configuration, whatever the other tapes hold and whatever output has been produced. So a combinator can start a machine on a block without knowing anything about the configuration the previous one left behind, beyond its output and that it did not touch this block.workTapes_embed_of_notMem_range,workTapePos_embed_of_notMem_range: the idleness of the extra tapes, which the module docstring claimed but which was observable only throughspaceUsedByTape_extra.extend_eq_self,initCfg_extendTapes(factored out ofrunFrom_extendTapes) andspaceUsed_extendTapes.NormalForms/RewindInput.lean:
spaceUsed_rewindInput_le: normalizing costs no space. The rewind moves no work-tape head, so every cell the normalized machine visits was already visited by the original one.rewindInput_halts_spaceUsed: the normal form together with its cost, so a combinator that feeds one input to two machines never has to unfold the rewind. Only the time bound grows, by the input length plus two.initCfg_rewindInput.Plumbing/Concat.lean (new):
concatTapeLeft,concatTapeRight: the two blocks of work tapes, and their disjointness.concatPrefix,concat: the first phase and the composite.computesInTimeAndSpace_concat: the composite computes the concatenation. The extra time is one input rewind; the extra space isk₀ + k₁, since whichever machine is not running has an idle head on each of its tapes and each such head still counts for the cell it sits on. The rewind itself contributes nothing.Combinators/Concat.lean (new, with a README for the new directory):
computableInTimeAndSpace_concat: the function-level result, stated for an arbitrary function together with the assumption that its encoding is the concatenation of the two encodings, rather than for a fixed pairing. So it covers whatever the caller encodes, and the caller is the one who has to know that the concatenation of the two encodings is again injective.computableInTimeAndSpace_pair: the pair, the typical case.The remaining supporting results, all of which the concatenation needs and none of which is specific to it:
TapeLemmas:visitedByTapeHead_addandspaceUsed_add_lesplit the space of a run into its two phases;spaceUsed_eq_of_workTapePosreduces space usage to the head positions alone, which is what lets a machine be replaced by a simulation of it; andspaceUsed_le_of_workTapePos_memis the sharp bound for a phase that moves no head, which visits no new cell at all rather than one per tape.Plumbing/Basic:Cfg.prependOutputandrunFrom_prependOutput: a run does not depend on the output accumulated before it, which is what lets a machine run after another has already written part of the output.Plumbing/Sequential:initCfg_seqandspaceUsed_seq_le.Plumbing/Rewind:step_input_workTapePosandrunFrom_input_workTapePos, since the input rewind moves no work-tape head.Plumbing/Basicnow importsTapeLemmasrather thanDeterministicdirectly, which is also what bringsTapeLemmasinto the import graph of this directory, andPlumbing/ExtendTapesimportsPlumbing/BasicforCfg.prependOutput.AI disclosure: Created using Claude.