Mercurial > public > think_complexity
diff TM.py @ 44:362d4ec7e794
Got a first draft Turing Machine working for chapter 6,
exercise 4. Next I have to figure out how to draw it
with a TMDrawer class.
author | Brian Neal <bgneal@gmail.com> |
---|---|
date | Wed, 16 Jan 2013 21:44:02 -0600 |
parents | |
children | 1804f09a7adb |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/TM.py Wed Jan 16 21:44:02 2013 -0600 @@ -0,0 +1,162 @@ +"""Chapter 6, exercise 4 in Allen Downey's Think Complexity book. + +The goal of this exercise is to implement a Turing machine. +See http://en.wikipedia.org/wiki/Turing_machine. + +1. Start with a copy of CA.py named TM.py. Add attributes to represent the + location of the head, the action table and the state register. + +2. Override step to implement a Turing machine update. + +3. For the action table, use the rules for a 3-state busy beaver. + +""" +import numpy + +# Tape head direction constants; None is also a legitimate value to indicate +# no movement: + +LEFT, RIGHT = range(2) + + +class TapeError(Exception): + """Raised when the tape head goes out of our array bounds.""" + pass + + +class TM(object): + """ + A Turing Machine based upon the cellular automaton (CA.py) in Think + Complexity. + + The tape is divided into cells where each cell can contain a symbol from + a finite alphabet. We use unsigned 8-bit integers, where 0 is the special + blank symbol. + The tape head can read & write symbols onto the tape at its current + position. The tape head can be moved according to the action table. + A state register stores the machine's current state. A state value of None + indicates the machine should halt. + An action table controls how the machine operates. Each entry in the table + is of the form: + (state, input) => (write_symbol, direction, new_state) + + where: + state: current machine state + input: current symbol under the machine's tape head + write_symbol: the symbol to write onto the tape; None means don't write + anything + direction: the direction to move the head; can be either LEFT, RIGHT, or + None to indicate no movement. + next_state: the new state to transition to + + """ + def __init__(self, init_state, actions, n=100, m=50): + """Parameters: + + init_state : the initial state of the machine + + actions : the action table dictionary: + the keys are of the form (state, input) + the values are a tuple of the form (write_symbol, direction, + next_state) + + n : the number of rows in the numpy array + m : the number of columns in the numpy array + + The caller is free to intialize the first row in the array and/or move + the head prior to calling loop() or step(). Otherwise the tape will be + all blanks and the head placed in the middle. + + """ + # We use a numpy array to store the state of the tape at each step. + self.n = n + self.m = m + self.array = numpy.zeros((n, m), dtype=numpy.int8) + + # the head is initially in the center of the first row + self.next = 1 # the next row to update + self.head = m / 2 # head position + self.actions = actions # action table + self.state = init_state # state + + def loop(self, steps=1): + """Attempts to execute the given number of steps. May exit early if the + machine is already halted or halts during execution. + + Returns the number of steps executed. + + """ + actual = 0 # actual number of steps executed + for i in xrange(steps): + if self.state is None: + break # machine has halted + self.step() + actual += 1 + + return actual + + def step(self): + """Executes one time step by computing the next row of the array.""" + i = self.next - 1 # the previous state of the tape + j = self.next # the row to write + self.next += 1 + + a = self.array + a[j, :] = a[i, :] # copy previous state of the tape to next row + + # read the symbol on the tape at the head position + symbol = a[j, self.head] + + # see what we are supposed to do + write_symbol, direction, next_state = self.actions[self.state, symbol] + + # write a new symbol + if write_symbol is not None: + a[j, self.head] = write_symbol + + # move the head + if direction == LEFT: + if self.head > 0: + self.head -= 1 + else: + raise TapeError("ran out tape moving LEFT") + elif direction == RIGHT: + if self.head < self.m - 1: + self.head += 1 + else: + raise TapeError("ran out tape moving RIGHT") + + # change state + self.state = next_state + + def get_array(self, start=0, end=None): + """Gets a slice of columns from the array, with slice indices + (start, end). Avoid copying if possible. + """ + if start==0 and end==None: + return self.array + else: + return self.array[:, start:end] + + +if __name__ == '__main__': + # Test the Turing Machine with the rules for a 3-state, 2 symbol busy beaver + + A, B, C = range(3) + + actions = { + (A, 0): (1, RIGHT, B), + (B, 0): (1, LEFT, A), + (C, 0): (1, LEFT, B), + (A, 1): (1, LEFT, C), + (B, 1): (1, RIGHT, B), + (C, 1): (1, RIGHT, None), + } + + n = 15 + m = 10 + tm = TM(A, actions, n, m) + + count = tm.loop(n - 1) + print count + print tm.array