Outline
- Multi-tape Turing Machines
- HW Problems
- RAMs
Simulating `k`-tapes with `1`-tape
Theorem. Given any `k`-tape machine `M` that operates within time `f(n)` deciding `L`, we can construct a `1`-tape machine `M'`operating within time `O((f(n))^2)` deciding `L`.
Proof. Let `M=(Q, Sigma, Gamma, q_0, F)` be a `k`-tape machine.
- The idea is `M`'s tape alphabet, `Gamma`, is going to be expanded to include symbol `#` to denote the last used
square of a tape. We are also going to add to the new tape alphabet, `Gamma'`, a symbol `\ul{b}` for each symbol `b` in `Gamma`.
- A configuration of `M` can now be written as:
`(q, #w_1\ul{a_1}v_1#w_2\ul{a_2}v_2#ldots#w_k\ul{a_k}v_k#)`
Here the underscored character indicates the position of the tape head for the given tape.
- So except for the state, which we can keep track of in `Q'`, the rest of the configuration is a string over `Gamma'`.
- We will use new states in `Q'` to keep track of the state of `M` during a simulation step.
- To simulate `M`, we first convert the input into the initial configuration of `M` viewed as a string.
- Then to simulate a step we scan left to right the current configuration string, noting what symbol is being read by each tape in our finite control.
- Next we rewind the tape and we then do passes again to update each tapes configuration.
- In the worst case we need to expand the number of tape squares of each tape by `1`. So we could need `(k(f(|x|)+1)+1` passes to simulate `1` step of the `k`-tape machine.
- So simulating `f(|x|)` steps take at most `f(|x|)((k(f(|x|)+1)+1)` times which is `O((f(n))^2)`.
Random Access Machines (RAMs)
- We next look at a computation model which is closer to a modern day computer.
- A Random Access Machine (RAM) consists of a program which acts on an arrays of registers. You can think of an array element
as corresponding to a memory location in a computer.
- RAMs were first defined in Shepherdson and Sturgis, JACM. Vol. 10. Iss. 2. pp. 217--255. 1963.
- Unlike a usual computer, which might be able to store 32 or 64 bits in a memory word, each register is capable of storing an arbitrarily large integers (either positive or negative).
- Register 0 is called the accumulator. It is where any computations will be done.
- A RAM program consists of a finite sequence of instructions `P=(p_1, p_2, ldots, p_n)`.
- The machine has a program counter which says what instruction is to be executed next.
- This initially starts at the first instruction. An input `w=w_1, ldots ,w_n` is initially placed symbol-wise into registers `1` through `n`. (We assume some encoding of the alphabet into the integers). All other registers are `0`.
- A step of the machine consists of looking at the instruction pointed to by the program counter, executing that instruction, then adding 1 to the program counter if the instruction does not modify the program counter and is not the halt instruction.
- Upon halting, the output of the computation is the contents of the accumulator. For languages, we say w is in the language of the RAM, if the accumulator is positive, and is not in the language otherwise.
The RAM Instruction Set
The allowable RAM instructions are listed below:
- Read j /* read into register j into accumulator */
- Read (j) /* look up value v of register j then read register v into the accumulator*/
- Store j /* store accumulator's value into register j */
- Store (j) /* look up value v of register j then store acumulators values into register v*/
- Load x /* set the accumulator's value to x */
- Add j /* add the value of register j to the accumulator's value */
- Sub j /* subtract the value of register j from the accumulator's value */
- Half /* divide accumulator's value by 2 round down (aka shift left)*/
- Jump j /* set the program counter to be the jth instruction */
- JPos j /* if the accumulator is positive, then set the program counter to be j */
- JZero j /* if the accumulator is zero, then set the program counter to be j */
- JNeg j /* if the accumulator is negative, then set the program counter to be j */
- HALT /* stop execution */
Example Program for Multiplication
Suppose we input into Register 1 and 2 two number `i_1` and `i_2` and we would like to multiply these two numbers. The following
program could do this:
- Read 1 //(Register 1 contains `i_1` ; during the `k`th iteration
- Store 5 // Register 5 contains `i_1 cdot 2^k`. At the start `k=0`)
- Read 2
- Store 2 //(Register 2 contains `lfloor i_2/2^k rfloor` just before we increment `k` )
- Half //(now accumulator is `lfloor i_2/2^{k+1} rfloor`, we've chopped off the low order bit of `i_2`.
The `k`th iteration begins)
- Store 3 // (Register 3 contains half Register 2, `lfloor i_2/2^{k+1} rfloor` )
- Add 3 //(so now accumulator is twice Register 3, `2 cdot lfloor i_2/2^{k+1} rfloor`)
- Sub 2 //(accumulator will be `0` if low order bit of what was stored in register 2 is `0`)
- JZero 13
- Read 4 //( so this and the next instruction add Register 5 to Register 4
only if the `k`th least significant bit of `i_2` is nonzero)
- Add 5
- Store 4 //(Register 4 contains `i_1 cdot (i_2 mod 2^k))`
- Read 5
- Add 5
- Store 5 // Register 5 contains `i_1 cdot 2^{k+1}`, so can start next round
- Read 3
- JZero 19
- Jump 4 //(if not, we repeat)
- Read 4 //(the result)
- Halt
Runtime of a RAM
- Let `D` be a set of finite sequences of integers.
- A program `P` computes a function `f` from `D` to the integers if for all `I` in `D`, the program `P` halts with `f(I)`
in its accumulator
- Let `\mbox{len}(i)` be the binary length of integer `i`.
- The length of the input `I` is defined as `\mbox{len}(I)=sum_j \mbox{len}(i_j)`. Here `j` is running over the input registers
- We say `P` runs in time `g(n)` if for all `I`, such that `\mbox{len}(I) = n`, it runs at most `g(\mbox{len}(I))` steps.
Simulation Set-up
Theorem. If `L` is in `mbox{TIME}(f(n))`, then there is a RAM
which computes it in times `O(f(n))`.
Proof: Let `M` be the TM recognizing `L`. We assume one blank space is added to any input and the tape alphabet has been encoded as integers. The RAM first moves the input to Registers 4 through `n+3`. This is actually a little tricky to do. First, the RAM reads Registers 1 and 2. Since the tape alphabet of `M` is finite, the RAM can "remember" these values without having to write them to some other register by branching to different subroutines to execute. As these values are now remembered, Register 1 can be used as a counter to count up. By doing Read (1) instructions and incrementing Register 1, until the encoding of the `\square` for the end of the input is found we scan to the end of the input. We look one register before this, read it, and store it into Register 2. Adding 3 to Register 1, we can then load the accumulator with Register 2's values and do a Store (1). This moves the `n`th symbol to Register `n+3`. By subtracting 4 from Register 1 and doing a Read (1) we can get the next symbol to move and so on. We are now almost ready to begin the simulation.
Proof cont'd
Register 1 is used to hold the current tape square number being read by the TM in the simulation and this is initially set to 4 , the first square of the input. Register 3 holds a special start of tape symbol. The program now tries to simulate steps of the Turing machine. The program has a sequence of instructions simulating each state `q` of `M` and has a sub-sequence of these instruction, `N(q,j)`, for handling the transition for state `q` while reading the `j`th type of alphabet symbol.
`N(q,j)`
Suppose `delta(q, j) = (p, k, D)`. Here `D` is a direction. To simulate this we do:
N(q,j) Load j
N(q,j)+1 Store 2
N(q,j)+2 Read (1)
N(q,j)+3 Sub 2 //(if the tape position we are
//reading has value `j` this will be `0`)
N(q,j)+4 JZero N(q, j) + 6
N(q,j)+5 Jump N(q, j+1) //(if we are not reading a
//`j` check if we are reading a `j+1`)
N(q,j)+6 Load k
N(q,j)+7 Store (1)
N(q,j)+8 Load -1, 0, 1 //(depending on which direction the move was)
N(q,j)+9 Add 1
N(q,j)+10 Store 1
N(q,j)+11 Jump N(p, k)
Simulating RAMs on TMs
Theorem. If `L` is recognized by a RAM in time `f(n)`
then it is in `mbox{TIME}(O(f(n)^3))`.
Proof: Let `P` be a RAM program. We will simulate it by a seven tape machine. The first tape will be used to hold the input string and it will never be overwritten. The second tape will be used to represent the content of all the registers. This will be represented by a sequence of semicolon separated pairs `i, v`. Here i says the register (which may be `0`) and `v` says its value. When a register is updated we copy the pair to the end of our sequence, update the value, then `X` over the old value. An example sequence might be: `0, 101; XXX; 1, 10; `
The states of `M` are split into `m` groups where `m` is the number of instructions in `P`. Each group implements one instruction. Tape 3 is used to store the current program counter. This is initially `1`. At the start of the simulation Tape 2 is initialized to the input configuration of a RAM based on the contents of the input tape. Thereafter, at the start of simulating an instruction. The program counter is read and the start state of the group of states of `M` for that instruction is entered. (see next slide)
Proof cont'd
An instruction is then processed, Tape 2 is updated, and the program counter on Tape 3 is updated, then the next step can be simulated and so on. Most instructions are reasonably straightforward to carry out: To process an instruction that uses indirect addressing of the form (j), Tape 4 is used to store the value `k` of the Register `j` so that we can then go access Register `k` on Tape 2. For operations like Add and Sub, Tape 5 and Tape 6 are used to store the operands and Tape 7 is used to compute the result. If the RAM halts, the contents of Register 0 (the accumulator) are looked up on Tape 2, and the TM accepts if the value is positive.