]]>

The class and a few methods are defined in NeuralFunctions.py. The function createRandomWeights is currently set up to create a 9 neuron network when test = True and a completely random network when test = False.

The weights are defined as:

# Neural Network Class Functions # NeuralFunctions.py # Written in Python. See http://www.python.org/ # Placed in the public domain. # Chad Bonner 1.31.2015 # Based on Back-Propagation Neural Networks by # Neil Schemenauer <nas@arctrix.com> print "NeuralNet v1.0" import math import random import numpy as np weightLimit = 5 #Somewhat arbitrary choice #-------------Support Functions---------------- random.seed() def createRandomWeights(n, weightLimit, test): # n = number of neurons # create matrix for weights if test == True: weights = np.array([[0.0 for i in range(n)] for j in range(n)]) weights[2,0] = random.uniform(-weightLimit,weightLimit) weights[2,1] = random.uniform(-weightLimit,weightLimit) weights[3,0] = random.uniform(-weightLimit,weightLimit) weights[3,1] = random.uniform(-weightLimit,weightLimit) weights[4,2] = random.uniform(-weightLimit,weightLimit) weights[4,3] = random.uniform(-weightLimit,weightLimit) else: weights = np.array([[random.uniform(-weightLimit,weightLimit) for i in range(n)] for j in range(n)]) return weights def createNets(numNets, sizeNet, numOut): # Create population of neural nets # Create a set of network connection weights with random weights # Instantiate nets and load with weights # Set test = True to use predefined weights test = True nets = [] #create empty variable to hold neural nets for id in range(numNets): #create weights for neuron connections weights = createRandomWeights(sizeNet, weightLimit, test) #instantiate a neural net # id=0, number of neurons, number of outputs, dna nets.append(NeuralNet(id, sizeNet, numOut, weights)) return nets # our sigmoid function, tanh is a little nicer than the standard 1/(1+e^-x) def sigmoid(x): # init output variable y = np.array([0.0]*len(x)) for i in range(len(x)): y[i] = math.tanh(x[i])*2 return y #------------ Neural Net Class Definition ------------------------------ class NeuralNet: def __init__(self, id, n, no, weights): # n = number of neurons # no = number of outputs self.id = id self.n = n self.no = no self.weights = weights # activations for nodes self.ai = np.array([0]*self.n) #make all inputs neutral self.an = np.array([0]*self.n) #neuron outputs self.summ = np.array([0]*self.n) #init neuron activation levels to 0 def identifyNet(self): print 'Net ', self.id def update(self, neuronInputs): # init neuron activation levels to 0 self.summ = np.array([0]*self.n) # Apply inputs to input neurons self.ai[:len(neuronInputs)] = neuronInputs # Add neuron inputs to activation level self.summ = self.ai + np.dot(self.weights, self.an) # set neuron outputs self.an = sigmoid(self.summ) # net output is the output from the last no neurons out = (self.an[self.n-self.no:]) #print "out", out return out

# Evolution.py # # Written in Python. See http://www.python.org/ # Placed in the public domain. # Chad Bonner 1.31.2015 from __future__ import division import NeuralFunctions as nf import weightedRandomChoice as wrc import random import numpy as np import csv import operator from collections import defaultdict # Setup numGenerations = 50 #number of generations numIterations = 4 #number of times to run each net numNets =2000 #Size of the population sizeNet = 5 #Number of neurons in each net numOut = 1 #Number of outputs for each net nets = [] #create empty variable to hold neural nets nextGenNets = [] #create empty variable to hold neural nets netFitness = {} #dictionary to hold net index and fitness indexParents = [] #index of nets to be parents of next gen parentDNA = [] #dna of parents childDNA = [] #dna of children in the next gen inputs = np.array([[0,0],[1,0],[0,1],[1,1]]) #============================================================================ #inputs = np.array([[0,0,0,0],[0,0,0,1],[0,0,1,0],[0,0,1,1], # [0,1,0,0],[0,1,0,1],[0,1,1,0],[0,1,1,1], # [1,0,0,0],[1,0,0,1],[1,0,1,0],[1,0,1,1], # [1,1,0,0],[1,1,0,1],[1,1,1,0],[1,1,1,1]]) #============================================================================ def netEvaluate(nets, inputs): #Apply input to each net and evaluate fitness fitness = {} #dictionary to hold net index and fitness for index, net in enumerate(nets): #let signals progagate through net for iterations #then look at output fitness[index] = 0 for i in range(len(inputs)): #print "Input:", i for c in range(numIterations): #allow for propgation to output out = net.update(inputs[i]) out = net.update(inputs[i]) #============================================================================ #Modify fitness here to suit application #Arbitrary choice of matching for input pattern 0101. A correct match #will have an output of 1 for an input of 0101 and an output of -1 for #all other input patterns. #Set out = [1,0] for even input # if np.any(np.all(np.equal(inputs[i], # [[0,0,0,0],[0,0,1,0],[0,1,0,0],[0,1,1,0],[1,0,0,0], # [1,0,1,0],[1,1,0,0],[1,1,1,0]]), 1)): # if out[0] >= 1 and out[1] < 0.2: # fitness[index] = fitness[index] + 1 #award points # else: # if out[0] < 0.2 and out[1] >= 1: # fitness[index] = fitness[index] + 1 #award #============================================================================ #============================================================================ # #Modify fitness here to suit application if np.any(np.all(np.equal(inputs[i], [[0,1],[1,0]]), 1)): #print "Should be 1: Out =", out if out >= [0.9]: fitness[index] = fitness[index] + 1 #award points #print "Rewarded for +1 output" else: #print "Should be 0: Out =", out if out < [0.2]: fitness[index] = fitness[index] + 1 #award #print "Rewarded for 0 output" if fitness[index] == 4: fitness[index] = 15 #award a lot of points for correct answer #============================================================================ return fitness def writeFile(f, generation, fitness, fitIndex, nets, inputs, writeAll): # Write data to CSV file outputs = [] #create empty variable to hold net outputs writer = csv.writer(f, delimiter=',') if writeAll == True: for net in nets: writer.writerow(['Generation', 'Fitness', 'ID', 'SizeNet', 'NumOut', 'Weights']) writer.writerow([generation, fitness, net.id, net.n, net.no]) for j in range(net.n): writer.writerow(net.weights[j]) else: writer.writerow(['Generation', 'Fitness', 'ID', 'SizeNet', 'NumOut', 'Weights']) writer.writerow([generation, fitness, nets[fitIndex].id, nets[fitIndex].n, nets[fitIndex].no]) for i in range(len(inputs)): for c in range(numIterations): nets[fitIndex].update(inputs[i]) outputs.append(nets[fitIndex].update(inputs[i])) writer.writerow(outputs) print outputs for j in range(nets[fitIndex].n): writer.writerow(nets[fitIndex].weights[j]) return def breed(netFitness, nets): # Create new population by combining # parents's dna (sexual reproduction). # Each net can combine with any other net, even itself. #print netFitness pairs = zip(netFitness.values(),netFitness.keys()) pairs.sort() pairs.reverse() nextGenNets = [] # clear next gen variable for use for c, net in enumerate(nets): mom = wrc.weighted_random_choice(pairs) #print "mom", mom dad = wrc.weighted_random_choice(pairs) #print "dad", dad childDNA = np.array([[0.0 for i in range(net.n)] for j in range(net.n)]) for neuron in range(net.n): #take one neuron from each parent and combine to form child childDNA[neuron,:] = nets[random.choice((mom,dad))].weights[neuron,:] # now create new net using child dna # instantiate a neural net nextGenNets.append(nf.NeuralNet(c, net.n, net.no, childDNA)) return nextGenNets # ------------------------- Main Routine ------------------------------ #def main(): #Open file to save data csvfile = open('data_out.csv', 'wb') #Create and evaluate initial population of neural nets print "Genesis!" nets = nf.createNets(numNets, sizeNet, numOut) # Apply input to each net and evaluate fitness netFitness = netEvaluate(nets, inputs) fitHistogram = defaultdict(int) #variable to hold count of fitness values #Count frequency of fitness values for value in netFitness.values(): fitHistogram[value] += 1 #print fitHistogram print "Generation Fitness:", "%.3f"%((sum(netFitness.values())/numNets)) # Identify net with maximum fitness and save to log file mostFit = max(netFitness.iteritems(), key=operator.itemgetter(1))[0] mostFitness = netFitness[mostFit] print "Most Fit:", mostFit writeFile(csvfile, 0, mostFitness, mostFit, nets, inputs, False) # Go forth and multiply for generation in range(1, numGenerations): #Breed next generation nets = breed(netFitness, nets) print " " print "Generation:", generation, "born" # Apply input to each net and evaluate fitness netFitness = netEvaluate(nets, inputs) fitHistogram = defaultdict(int) #variable to hold count of fitness values #Count frequency of fitness values for value in netFitness.values(): fitHistogram[value] += 1 print fitHistogram print "Generation Fitness:", "%.3f"%((sum(netFitness.values())/numNets)) # Identify net with maximum fitness and save to log file mostFit = max(netFitness.iteritems(), key=operator.itemgetter(1))[0] mostFitness = netFitness[mostFit] print "Most Fit:", mostFit writeFile(csvfile, generation, mostFitness, mostFit, nets, inputs, False) #print "Best net saved" #print "Iteration", generation, "complete!" csvfile.close() print "Done!"

Currently it takes about 12 seconds to run a population of 50 nets of 500 neurons each for 50 cycles. I’m using numpy to speed things up. I’m sure there are still opportunities for improvement. Creating the nets actually takes longer than running them as shown below (click to enlarge).

]]>1. Any cell with fewer than two live neighbors dies

2. Any living cell with two or three living neighbors lives

3. Any living cell with more than three living neighbors dies

4. Any dead cell with exactly three living neighbors becomes a living cell

Highlife is a cellular automaton similar to Life except for the addition of a birth rule. Whenever an empty cell is surrounded by 6 live cells a live cell is born so the rules are:

1. Any cell with fewer than two live neighbors dies

2. Any living cell with two or three living neighbors lives

3. Any living cell with more than three living neighbors dies

4. Any dead cell with exactly three living neighbors becomes a living cell

5. Any dead cell with exactly six living neighbors becomes a living cell

Another way to represent this is to use a shorthand notation like the following:

Life B3S23 – Birth 3 neighbors, Survive 2 or 3 neighbors

HighLife B36S23 – Birth 3 or 6 neighbors, Survive 2 or 3 neighbors

The addition of another rule may seem like a small change and many of the same patterns exist in both Life and HighLife but there is an interesting difference. A small simple pattern known as the replicator exists that creates copies of itself every 12 generations. As far as anyone knows a small replicator like this doesn’t exist in Life. So, for that reason, I’ve decided to modify my code so that I can run either Life or HighLife.

Doing so was fairly easy and only involved changing a few lines of code

def updateState(currentState): ''' This function is where the actual game of life calculations happen for each cell ''' nextState = copy.deepcopy(currentState) #deepcopy could be moved to gol.py. <1% runtime though neighborCount = scipy.signal.convolve2d(currentState, config.neighbors, mode="same",boundary="wrap") #B3/S23 #nextState[np.where((neighborCount != 2) & (neighborCount != 3))] = 0 #nextState[np.where(neighborCount == 3)]= 1 #B36/S23 nextState[np.where((neighborCount != 2) & (neighborCount != 3))] = 0 nextState[np.where(neighborCount == 3)] = 1 nextState[np.where((neighborCount == 6) & (currentState == 0))] = 1 return nextState

I’ve commented out the Life lines of code so that I can switch back and forth easily and added in the HighLife code. When I first did this I made what seemed like a straightforward change of:

def updateState(currentState): nextState[np.where((neighborCount == 3) | (neighborCount == 6))] = 1

however the replicator didn’t work. It took me a while to figure out what was going on and how to correctly implement the rule. What I was missing is that only dead cells with 6 neighbors should become living cells and living cells with 6 neighbors should die. My first attempt didn’t look at the current state but just made the cell live in the next generation. Once I understood this it still took a while to figure out how to correct it. Fortunately the Numpy where function can look at more than one array at a time so the line

def updateState(currentState): nextState[np.where((neighborCount == 6) & (currentState == 0))] = 1

looks at both the number of neighbors *and* the current state of the cell so that only dead cells become living.

Everything is easy once you know how.

So once I had the new HighLife code working I was able to create the replicator

and let it do its thing. As you can see one replicator first becomes two and then four.

One thing interesting to note is that it’s been known for a long time that Conway’s rules don’t produce self replicators from random fields of cells but other rules do. I find this odd since Conway’s rules are Turing complete and should be able to perform any computation that any other rule set does. Fortunately the software I’ve written makes it easy to use other rule sets and I’ll be exploring those as well. Considering that John Conway himself stated the following about HighLife (rule B36/S23) I’ll likely explore that next.

]]>

“It seems to me that ‘B36/S23’ is really the game I should have found, since it’s so rich in nice things.”

As you can see I’ve added three buttons (without a great deal of aesthetic care) with one of the buttons giving the option to turn the display on or off. I’ll add some profiling numbers but with the display off the update rate is about 3 times faster than when the display is on. Alternately you can get the same update rate for a third of the processor load. Adding the option to disable the display required another architectural change to the code much like changing to the Tkinter graphical display. However, doing so made it very easy to add other options that I’ll talk about later.

The standard way of making the status of variables available to all Python modules is to declare them in a configuration file that gets imported into the modules that use those variables. I took the variables I’ve defined (number of rows, columns, update rate, etc.) and moved them into a separate file, gol_config.py that I then import as config.

The first step is to create the button in the main module gol.py:

#display on/off button bttn_display = tk.Button(root, text="Display On/Off", command=gol_defs.display_state) bttn_display.pack()

This creates the button and defines the function, gol_defs.display_state, called when the button is pressed. This function is very simple and just changes the state of the Boolean variable in gol_configs.py

def display_state(): #change state of display to turn it on or off config.display = not(config.display)

Then in the main program loop the value of config.display is checked and used to control whether the display is updated or not.

if config.display == True: #create state image pgm = gol_defs.write_file(currentState) state = tk.PhotoImage(data=pgm) #set image size state = state.zoom(width, height) tkimg[0] = state #update label with image label.configure(image = tkimg[0])

]]>

for neighbor in neighbors: # % does modulo indexing to create toroidal universe neighborR = (Rindex + neighbor[1]) % len(currentState) neighborC = (Cindex + neighbor[0]) % len(currentState[0]) if currentState[neighborR][neighborC] == 1: count += 1

Since Python is an interpreted language this is a slow process but fortunately there are much faster ways to do the same thing. One way to speed this process up is to use an idea from image processing – convolution. This works by taking a small matrix, the kernel, and repeatedly applying it to each cell of a larger matrix in the following way. If the kernel is represented as:

a b c d e f g h i

and the larger grid is

A B C D E F G H I J K L M N O P Q R S T U V W X

Then applying the kernel to cell H gives:

Result = a*A + b*B + c*C + d*G + e*H + f*I + g*M + h*N + i*O

If the kernel is defined as

1 1 1 1 0 1 1 1 1

then for each cell we apply it to the result is the number of neighbors that are alive around the cell. The 0 in the middle keeps the cell itself from being counted. This is exactly what’s needed to calculate the number of neighbors so we can apply the game of life rules.

For instance if the grid contains

1 0 1 0 1 0 0 0 0 0 0 0 0 0 0 0

and we apply the kernel to the second cell in the second row from the top we get

Result = 1*1 + 0*1 + 1*1 + 1*1 + 0*0 + 0*1 + 0*1 + 0*1 + 0*1 = 3

which is the count of the number of alive neighbors. Since the count = 3 according to the game of life rules the cell will change from dead to alive.

The kernel is held in the neighbor variable in the main gol.py module. Now, instead of relative values used to calculate a neighbor index the variable will hold a 1 in each location that we want to consider as a neighbor. This kernel is then applied to each cell in the grid and used to calculate the number of alive neighbors.

So how is this faster? All I’ve done is replace a huge number of iterations with a huge number of iterations.

The difference is that I can now use SciPy which is a math, science and engineering library for Python that can speed up matrix operations tremendously. Replacing the existing code with a call to the Scipy convolve2d function does the convolution in a fraction of the time.

#Module import import scipy.signal # Function Definitions def updateState(currentState, neighbors): nextState = copy.deepcopy(currentState) neighborCount = scipy.signal.convolve2d(currentState, neighbors, mode="same",boundary="wrap") Rindex = 0 for row in neighborCount: Cindex = 0 for cell in row: count = neighborCount[Rindex,Cindex] if count == 3: nextState[Rindex][Cindex] = 1 elif count != (2 or 3): nextState[Rindex][Cindex] = 0 Cindex += 1 Rindex += 1 return nextState

This takes the currentState grid, the neighbors kernel and convolves them to create a new matrix called neighborCount. Each cell in neighborCount contains the number of neighbors that are alive for that cell. Then all I have to do is iterate through neighborCount, read it and apply the game of life rules to find the next state of the cells in the grid. This happens much faster than the code in Hurry Up part 1. Now if I run the profiler I get the following (click to enlarge):

New Code:

Old Code:

The new code only takes a little over 3% of the run time versus the 35% the old code took. So what does this mean for the overall run time? For 500 iterations I get:

New Old Improvement 100 cells x 100 cells = 10 seconds 15 seconds 33% 250 cells x 250 cells = 63 seconds 92 seconds 32% 300 cells x 300 cells = 89 seconds 151 seconds 41% 500 cells x 500 cells = 243 seconds 393 seconds 38%

The improvement seen by the profiler shows up as a real improvement in the runtime. Notice that the improvement actually increases for larger grid sizes since the relative amount of time spent iterating over the grid is larger.

Getting better.

The next place to work on is in the update to the nextState array which again requires iterating through the whole array.

Rindex = 0 for row in neighborCount: Cindex = 0 for cell in row: count = neighborCount[Rindex,Cindex] if count == 3: nextState[Rindex][Cindex] = 1 elif count != (2 or 3): nextState[Rindex][Cindex] = 0 Cindex += 1 Rindex += 1

Using NumPy, which is part of SciPy, I can replace this with the NumPy where function.

nextState[np.where(neighborCount == 3)] = 1 nextState[np.where((neighborCount != 2) & (neighborCount != 3))] = 0 return nextState

The where function returns an array of indices of all cells that match the condition. These indices are then used to set the values of the cells in nextState. Of course to do this I had to go through and change all of the standard 2D lists I’ve been using into NumPy arrays but it was worth it. This and a few other small improvements result in (500 cycles):

New Old Improvement 100 cells x 100 cells = 1.2 seconds 15 seconds 92% 250 cells x 250 cells = 5.8 seconds 92 seconds 94% 300 cells x 300 cells = 8.3 seconds 151 seconds 95% 500 cells x 500 cells = 22.4 seconds 393 seconds 94%]]>

Once you’re up and running then you can do whatever you want with it. I uploaded my python modules using scp:

$ scp gol_ver2.0.py chad@54.203.101.174:~/projects/gol_ver2.0.py $ scp gol_defs.py chad@54.203.101.174:~/projects/gol_defs.py

and then used SSH to log into the machine:

ssh -X chad@54.203.101.174

The -X turns on X forwarding so that I can use the graphical display option. For now though it’s glacially slow even for very small grids that result in small images. I’m not sure what’s going on there but I intend to find out. One thing I’ve tried is using compression and choosing a faster compression algorithm. It turns out that the default algorithm used by SSH is fairly slow. This is simple to change when you SSH in:

ssh -c arcfour,blowfish-cbc -XC chad@54.203.101.174

The arcfour and blowfish ciphers are quite a bit faster than the default though this didn’t really make a difference in the results I got. There’s some other problem slowing it down. I was kind of bummed about this (still am) but it got me to thinking about how to measure whether something interesting is happening in a game of life. That then goes back to the old question of what is life? It’s hard to define but you know it when you see it. Well, even if I could see it I don’t want to spend all my time staring at the monitor. The whole reason for doing this is so I can have a large universe running for long periods of time and just take a look at the interesting things when they occur. I’ll have to figure out how to measure things like the complexity of the grid, self organization and so on.

]]>So what does this have to do with this blog? Well, now that I have a nice high resolution graphical display for my Game of Life program I’d like to be able to have runs with large numbers of cells. To some degree I can. Using the time command in a Linux terminal (time python yourprogram.py) with a Core i7 processor for 500 iterations I measured the following times for each run with no display and no sleep time between iterations:

100 cells x 100 cells = 15 seconds 250 cells x 250 cells = 92 seconds 300 cells x 300 cells = 151 seconds 500 cells x 500 cells = 393 seconds

Not bad but not exactly what I had in mind for an Autoverse. So how can I make it faster? The first thing to do is find out why it’s slow. For that we need to profile the code and see where it spends most of its time. Fortunately there are some tools to make it simple.

cProfile is a profiler that watches the code while it’s running and measures how long it spends in different areas.

$ python -m cProfile -o output.file gol.py

This results in a file that can be read using the pstats method

$ python >>> import pstats >>> p = pstats.Stats('output.file')

some things you can do with it then:

>>> p.strip_dirs().sort_stats(-1).print_stats()

strips out all the directory information and prints the results.

You can also sort by the total time and print out the top 10 time suckers:

>>> p.sort_stats('cumulative').print_stats(10)

What’s really nice though is to use Gprof2Dot to get a graph of the data.

$ python gprof2dot.py -f pstats output.file | dot -Tpng -o output.png

This is what it gave me (click to enlarge):

No great surprises here. The most time is spent in the gol_defs updateState function indexing the neighbors to see who’s alive and who’s dead. For a 500 cell x 500 cell grid run for 50 cycles this is done approximately 200 million times. 35% of the execution time is spent here. Seems like a good place to start.

]]>

With the ASCII display I was able to implement it as a function that could be called as needed. Tkinter comes with it’s own event loop that handles updating the window and different events that might happen during operation. Tkinter windows have the ‘after’ method to schedule a function that is called after a certain number of milliseconds. The code that updates the state of the cells goes into this function.

For example:

#Module import import Tkinter as tk # Update function def update(currentState): #this is where your code goes that does #whatever it is you want to do every #x milliseconds...100 for example currentState = newState #reset the after method to call update again in 100 ms root.after((100), update, currentState) #Initialize fps = 10 #number of times to update each second # Create the root window root = tk.Tk() #variable to hold image tkimg = [None] tkimg[0] = state #create label with image label = tk.Label(root, image=tkimg[0]) label.pack(side="right") #Run root.after((1000/fps), gol_update, currentState) root.mainloop() # main event handler

The images that are displayed are in PGM format. This format is very easy to use and allows for a simple transfer from array data to image data for display.

The functions to do so are defined in pgm_write.py. Currently I’m actually writing to a file which is then opened and displayed. This is not an efficient way to do this so I’ll change it soon to just store the image in a variable…but for now that’s the way it is.

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 ''' Chad Bonner Dec.19.2013 Game of Life PGM Image Write Function pgm_write.py ''' def write_file(image): #set up number of rows and colums variables from array size width = len(image[0]) height = len(image) # define PGM Header pgmHeader = 'P5' + '\n' + str(width) + ' ' + str(height) + ' ' + str(1) + '\n' # open file for writing filename = 'state.pgm' try: fout=open(filename, 'wb') except IOError, er: print "Cannot open file" sys.exit() # write the header to the file fout.write(pgmHeader) # write the data to the file for row in image: for cell in row: #create byte for value of each cell pixel = chr(cell) fout.write(pixel) # close the file fout.close()

To keep things from getting too unwieldy I took the functions used for the game of life mechanics and separated them into a different file gol_defs.py:

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 ''' Chad Bonner Dec.19.2013 Game of Life Function Definitions gol_defs.py ''' #Module import import copy import sys import time import random # Function Definitions def updateState(currentState, neighbors): ''' this function is where the actual game of life calculations happen for each cell count = 0 for each neighbor if currentState == alive count = ++ if count == 3 nextState = alive else if count != 2 or 3 nextState = dead ''' nextState = copy.deepcopy(currentState) Rindex = 0 for row in currentState: Cindex = 0 for cell in row: count = 0 for neighbor in neighbors: # % does modulo indexing to create toroidal universe neighborR = (Rindex + neighbor[1]) % len(currentState) neighborC = (Cindex + neighbor[0]) % len(currentState[0]) if currentState[neighborR][neighborC] == 1: count += 1 if count == 3: nextState[Rindex][Cindex] = 1 elif count != (2 or 3): nextState[Rindex][Cindex] = 0 Cindex += 1 Rindex += 1 return nextState def display(universe): #very simple routine to display animated cell state in terminal sys.stderr.write("\x1b[2J\x1b[H") #clear screen and reposition cursor for row in universe: for column in row: if column == 0: sys.stdout.write(' ') #leave empty for 0 else: sys.stdout.write('#') #fill in for 1 print "\r" sys.stdout.flush() def populate(numR, numC, use_seed, insertR, insertC, seed): #populate either with a seed or with random 1s and 0s if use_seed == 0: #Populate with random field of 1s and 0s currentState = [[random.randint(0,1) for i in range(numC)] for j in range(numR)] else: #Populate with seed currentState = [[0 for i in range(numC)] for j in range(numR)] nextState = [[0 for i in range(numC)] for j in range(numR)] Cseed = len(seed[0]) Rseed = len(seed) for i in range(Rseed): for j in range(Cseed): currentState[insertR+i][insertC+j] = seed[i][j] return currentState

This lets me concentrate on just the higher level display and control functions in the main module:

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 ''' Chad Bonner Dec.21.2013 Game of Life gol.py ''' #Module import import Tkinter as tk import sys import time import gol_defs import pgm_write import datetime #GOL Update Loop def gol_update(currentState): #update state with gol modulei currentState = gol_defs.updateState(currentState, neighbors) #select display type if display == 3: #Display option 3 - graphical display pgm_write.write_file(currentState) #open state image state = tk.PhotoImage(file="state.pgm") #set image size width = dis_width/state.width() height = dis_height/state.height() state = state.zoom(width, height) tkimg[0] = state #update label with image label.configure(image = tkimg[0]) root.after((1000/fps), gol_update, currentState) return currentState #Declarations run = True rows = 250 #num rows of matrix columns = 250 #num columns of matrix fps = 10 #number of frames per second to display sleepTime = 1.0/fps #calculate time to sleep between updates use_seed = 0 #0=don't use seed, 1=use seed display = 0 #0=no display, 1=info display, 2=ascii graphics, 3=tkinter dis_width = 500 #display width and height dis_height = 500 #define locations of 8 neighbor cells neighbors = [[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]] seed = [[0,0,1], #glider [1,0,1], [0,1,1]] #Initialize currentState = gol_defs.populate(rows, columns, use_seed, 3, 3, seed) root = tk.Tk() #Initialize display according to display type selected if display == 3: tkimg = [None] #used to keep image from being garbage collected pgm_write.write_file(currentState) state = tk.PhotoImage(file="state.pgm") #open state image #set image size width = dis_width/state.width() height = dis_height/state.height() state = state.zoom(width, height) tkimg[0] = state #create label with image label = tk.Label(root, image=tkimg[0]) label.pack(side="right") #Run root.after((1000/fps), gol_update, currentState) root.mainloop() else: while run == True: if display == 2: #display ASCII graphics gol_defs.display(currentState) currentState = gol_update(currentState) time.sleep(sleepTime) elif display == 1: #display info only print "working..." currentState = gol_update(currentState) else: #no display currentState = gol_update(currentState)

This is basically the same structure as in the cut down example above. What I’ve done here though is enable choosing the type of display to use. I want to choose between a graphical display, ASCII display, just printing useful information or no display at all for maximum speed. Since Tkinter requires initializing a window before it can do anything I decided to break the main loop into two parts. If I choose to use the graphical display then I use the after method of updating. If I choose one of the other display options then I use have to do my own looping with a while loop.

]]>1. Any cell with fewer than two live neighbors dies

2. Any living cell with two or three living neighbors lives

3. Any living cell with more than three living neighbors dies

4. Any dead cell with exactly three living neighbors becomes a living cell

While there are many different types of cellular automata I’ve decided to use this one for a few reasons: it’s very well known, easy to code, and most importantly it is Turing complete so anything that can be computed can be done within the Game of Life (GOL).

The algorithm for the game of life is shown in the following pseudo-code:

for each cell

count = 0

for each neighbor

if currentState == alive

count = ++

if count == 3

nextState = alive

else if count != 2 or 3

nextState = dead

I’m starting with a straightforward, ‘naive’ implementation in Python just to get something up and running. Of course nothing is ever as simple as the pseudo-code so there are a number of functions necessary to set everything up, run the algorithm and display the results.

First things first…let’s make some declarations about the nature of the universe:

#Declarations rows = 40 #num rows of matrix columns = 80 #num columns of matrix fps = 20 #number of frames per second to display cycles = 500 #number of simulation iterations use_seed = 1 #0=no, 1=yes #define locations of 8 neighbor cells neighbors = [[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]] seed = [[0,0,1], #glider [1,0,1], [0,1,1]] #seed = [[0,1,0], #blinker # [0,1,0], # [0,1,0]]

This defines a grid that is 40 rows by 80 columns and is updated 20 times per second for 500 cycles. The interesting part here is the definition of the neighbors which defines the way the grid is connected. Each cell has eight neighbors with the relationship to the cell as shown below. Defining the neighbors in a list makes it easy to iterate over them later.

I’ve also defined a couple of starter patterns (seeds) that I can use just to make sure everything is working. The blinker alternates between horizontal and vertical and the glider glides. Now it’s time to initialize the grid with living and dead cells:

#Initialize currentState = populate(rows, columns, use_seed, 3, 3, seed)

which calls the populate function:

def populate(numR, numC, use_seed, insertR, insertC, seed): #populate either with a seed or with random 1s and 0s if use_seed == 0: #Populate with random field of 1s and 0s currentState = [[random.randint(0,1) for i in range(numC)] for j in range(numR)] else: #Populate with seed currentState = [[0 for i in range(numC)] for j in range(numR)] nextState = [[0 for i in range(numC)] for j in range(numR)] Cseed = len(seed[0]) Rseed = len(seed) for i in range(Rseed): for j in range(Cseed): currentState[insertR+i][insertC+j] = seed[i][j] return currentState

This function either places the predefined seed into the grid or fills it with a random set of living and dead cells.

We then get into the main loop which calls the code that actually runs the GOL algorithm and then displays the results:

#Main print "Running" for i in range(cycles): currentState = update_state(currentState, neighbors) #print "Cycle ", i display(currentState,fps) print "Done"

I’ve included the pseudo-code algorithm in the update_state function as a reminder to myself later when I go back and look at this.

def update_state(currentState, neighbors): ''' this function is where the actual game of life calculations happen for each cell count = 0 for each neighbor if currentState == alive count = ++ if count == 3 nextState = alive else if count != 2 or 3 nextState = dead ''' nextState = copy.deepcopy(currentState) Rindex = 0 for row in currentState: Cindex = 0 for cell in row: count = 0 for neighbor in neighbors: # % does modulo indexing to create toroidal universe neighborR = (Rindex + neighbor[1]) % len(currentState) neighborC = (Cindex + neighbor[0]) % len(currentState[0]) if currentState[neighborR][neighborC] == 1: count += 1 if count == 3: nextState[Rindex][Cindex] = 1 elif count != (2 or 3): nextState[Rindex][Cindex] = 0 Cindex += 1 Rindex += 1 return nextState

Like the code says…this basically just iterates over the cells, counts the number of living neighbors each cell has and sets its state according to the rules. I’m just using Python lists here and iterating over each item in the list so this isn’t the fastest way to do it. I plan to switch over to a NumPy and SciPy implementation later though to speed it up. For now though this is straightforward, easy and best of all…works. The indexing of the neighbor cells is done using modulo math which turns the grid into a toroid so that when it reaches the edge it doesn’t just fall off. Instead it reaches back around to the other side and looks at the cells there.

So now that we have our updated population of cells how do we see the results? For the work so far I’ve just been using Vim and terminal windows in Linux as my development environment. While this doesn’t have much going for it in the way of fancy graphics it does make it easy for me to work locally, transfer my code to the Amazon computers and run the code there as well. So, I wanted a way to display the results of each iteration in a terminal window without a lot of fuss. The display function below does just that:

def display(universe, fps): #very simple routine to display animated cell state in terminal #calculate time to sleep sleepTime = 1.0/fps #clear screen and reposition cursor sys.stderr.write("\x1b[2J\x1b[H") for row in universe: for column in row: if column == 0: #leave empty for 0 sys.stdout.write(' ') else: #fill in for 1 sys.stdout.write('#') print "\r" sys.stdout.flush() time.sleep(sleepTime)

Essentially this just takes the current state values and goes through printing characters to the screen…a blank for a 0 and # for a 1. It then goes to sleep for a bit before returning so that the display is updated at the fps rate. This line does just what it says:

sys.stderr.write("\x1b[2J\x1b[H") #clear screen and reposition cursor

That way we have a smooth animation of the activity rather than a scrolling mess of printouts. Of course the resolution isn’t great…this is ASCII art after all…but it suffices for making sure everything is working as it should.

Complete Game of Life code listing:

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 ''' Chad Bonner Nov.23.2013 Game of Life ''' #Module import import copy import sys import time import random # Function Definitions def update_state(currentState, neighbors): ''' this function is where the actual game of life calculations happen for each cell count = 0 for each neighbor if currentState == alive count = ++ if count == 3 nextState = alive else if count != 2 or 3 nextState = dead ''' nextState = copy.deepcopy(currentState) Rindex = 0 for row in currentState: Cindex = 0 for cell in row: count = 0 for neighbor in neighbors: # % does modulo indexing to create toroidal universe neighborR = (Rindex + neighbor[1]) % len(currentState) neighborC = (Cindex + neighbor[0]) % len(currentState[0]) if currentState[neighborR][neighborC] == 1: count += 1 if count == 3: nextState[Rindex][Cindex] = 1 elif count != (2 or 3): nextState[Rindex][Cindex] = 0 Cindex += 1 Rindex += 1 return nextState def display(universe, fps): #very simple routine to display animated cell state in terminal #calculate time to sleep sleepTime = 1.0/fps #clear screen and reposition cursor sys.stderr.write("\x1b[2J\x1b[H") for row in universe: for column in row: if column == 0: #leave empty for 0 sys.stdout.write(' ') else: #fill in for 1 sys.stdout.write('#') print "\r" sys.stdout.flush() time.sleep(sleepTime) def populate(numR, numC, use_seed, insertR, insertC, seed): #populate either with a seed or with random 1s and 0s if use_seed == 0: #Populate with random field of 1s and 0s currentState = [[random.randint(0,1) for i in range(numC)] for j in range(numR)] else: #Populate with seed currentState = [[0 for i in range(numC)] for j in range(numR)] nextState = [[0 for i in range(numC)] for j in range(numR)] Cseed = len(seed[0]) Rseed = len(seed) for i in range(Rseed): for j in range(Cseed): currentState[insertR+i][insertC+j] = seed[i][j] return currentState #Declarations rows = 40 #num rows of matrix columns = 80 #num columns of matrix fps = 1 #number of frames per second to display cycles = 500 #number of simulation iterations use_seed = 0 #0=no, 1=yes #define locations of 8 neighbor cells neighbors = [[-1,-1],[0,-1],[1,-1],[-1,0],[1,0],[-1,1],[0,1],[1,1]] seed = [[0,0,1], #glider [1,0,1], [0,1,1]] #seed = [[0,1,0], #blinker # [0,1,0], # [0,1,0]] #Initialize currentState = populate(rows, columns, use_seed, 3, 3, seed) #Main print "Running" for i in range(cycles): currentState = update_state(currentState, neighbors) #print "Cycle ", i display(currentState,fps) print "Done"