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