comparison 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
comparison
equal deleted inserted replaced
43:6cd37534c12e 44:362d4ec7e794
1 """Chapter 6, exercise 4 in Allen Downey's Think Complexity book.
2
3 The goal of this exercise is to implement a Turing machine.
4 See http://en.wikipedia.org/wiki/Turing_machine.
5
6 1. Start with a copy of CA.py named TM.py. Add attributes to represent the
7 location of the head, the action table and the state register.
8
9 2. Override step to implement a Turing machine update.
10
11 3. For the action table, use the rules for a 3-state busy beaver.
12
13 """
14 import numpy
15
16 # Tape head direction constants; None is also a legitimate value to indicate
17 # no movement:
18
19 LEFT, RIGHT = range(2)
20
21
22 class TapeError(Exception):
23 """Raised when the tape head goes out of our array bounds."""
24 pass
25
26
27 class TM(object):
28 """
29 A Turing Machine based upon the cellular automaton (CA.py) in Think
30 Complexity.
31
32 The tape is divided into cells where each cell can contain a symbol from
33 a finite alphabet. We use unsigned 8-bit integers, where 0 is the special
34 blank symbol.
35 The tape head can read & write symbols onto the tape at its current
36 position. The tape head can be moved according to the action table.
37 A state register stores the machine's current state. A state value of None
38 indicates the machine should halt.
39 An action table controls how the machine operates. Each entry in the table
40 is of the form:
41 (state, input) => (write_symbol, direction, new_state)
42
43 where:
44 state: current machine state
45 input: current symbol under the machine's tape head
46 write_symbol: the symbol to write onto the tape; None means don't write
47 anything
48 direction: the direction to move the head; can be either LEFT, RIGHT, or
49 None to indicate no movement.
50 next_state: the new state to transition to
51
52 """
53 def __init__(self, init_state, actions, n=100, m=50):
54 """Parameters:
55
56 init_state : the initial state of the machine
57
58 actions : the action table dictionary:
59 the keys are of the form (state, input)
60 the values are a tuple of the form (write_symbol, direction,
61 next_state)
62
63 n : the number of rows in the numpy array
64 m : the number of columns in the numpy array
65
66 The caller is free to intialize the first row in the array and/or move
67 the head prior to calling loop() or step(). Otherwise the tape will be
68 all blanks and the head placed in the middle.
69
70 """
71 # We use a numpy array to store the state of the tape at each step.
72 self.n = n
73 self.m = m
74 self.array = numpy.zeros((n, m), dtype=numpy.int8)
75
76 # the head is initially in the center of the first row
77 self.next = 1 # the next row to update
78 self.head = m / 2 # head position
79 self.actions = actions # action table
80 self.state = init_state # state
81
82 def loop(self, steps=1):
83 """Attempts to execute the given number of steps. May exit early if the
84 machine is already halted or halts during execution.
85
86 Returns the number of steps executed.
87
88 """
89 actual = 0 # actual number of steps executed
90 for i in xrange(steps):
91 if self.state is None:
92 break # machine has halted
93 self.step()
94 actual += 1
95
96 return actual
97
98 def step(self):
99 """Executes one time step by computing the next row of the array."""
100 i = self.next - 1 # the previous state of the tape
101 j = self.next # the row to write
102 self.next += 1
103
104 a = self.array
105 a[j, :] = a[i, :] # copy previous state of the tape to next row
106
107 # read the symbol on the tape at the head position
108 symbol = a[j, self.head]
109
110 # see what we are supposed to do
111 write_symbol, direction, next_state = self.actions[self.state, symbol]
112
113 # write a new symbol
114 if write_symbol is not None:
115 a[j, self.head] = write_symbol
116
117 # move the head
118 if direction == LEFT:
119 if self.head > 0:
120 self.head -= 1
121 else:
122 raise TapeError("ran out tape moving LEFT")
123 elif direction == RIGHT:
124 if self.head < self.m - 1:
125 self.head += 1
126 else:
127 raise TapeError("ran out tape moving RIGHT")
128
129 # change state
130 self.state = next_state
131
132 def get_array(self, start=0, end=None):
133 """Gets a slice of columns from the array, with slice indices
134 (start, end). Avoid copying if possible.
135 """
136 if start==0 and end==None:
137 return self.array
138 else:
139 return self.array[:, start:end]
140
141
142 if __name__ == '__main__':
143 # Test the Turing Machine with the rules for a 3-state, 2 symbol busy beaver
144
145 A, B, C = range(3)
146
147 actions = {
148 (A, 0): (1, RIGHT, B),
149 (B, 0): (1, LEFT, A),
150 (C, 0): (1, LEFT, B),
151 (A, 1): (1, LEFT, C),
152 (B, 1): (1, RIGHT, B),
153 (C, 1): (1, RIGHT, None),
154 }
155
156 n = 15
157 m = 10
158 tm = TM(A, actions, n, m)
159
160 count = tm.loop(n - 1)
161 print count
162 print tm.array