As luck would have it, my day job will require me to teach a mini-class on writing computer simulations for scientific purposes next week, and one of the examples I thought I’d include turned out to be just the thing I needed to provide an illustration for the review of John Allen Paulos’ Irreligion (2007) I’ve got in the works. Since the details are a little beyond the scope of that review, I might as well make another post out of them.
First, the punchline:
This is a fifty-frame animation of Conway’s Game of Life which begins with a 32×32 grid of random values and successively advances by applying the transition rules of that famous automaton. The purpose of the exercise is twofold: first, to demonstrate some basic programming techniques and show how much you can do in Python with half an hour of free time; and second, to see how persistent and cyclic features arise from random configurations.
I chose to establish periodic boundary conditions, so that patterns leaving one side wrap around to the other (readers of my generation will recall the game Asteroids). This means we’re playing Life on a torus — is there anything donuts can’t do? As before, we’ll be using the Python language with a few extra modules for stuff like visual output.
Now, the code!
#!/usr/bin/python # conway.py # Blake Stacey # (bstacey at alum.removethis.mit.andthis.edu) import math, scipy, random, pylab def iTest(x, p): if(x <= p): return 0 else: return 1 fP = 0.5 # probability of dead square iGridEdge = 32 # size of grid iTrials = 50 # number of times to loop random.seed() imThis = scipy.array(scipy.rand(iGridEdge, iGridEdge)) imThat = scipy.array(scipy.zeros((iGridEdge, iGridEdge))) for i in range(iGridEdge): for j in range(iGridEdge): imThis[i][j] = iTest(imThis[i][j], fP) pylab.ion() pylab.hold(False) for t in range(iTrials): for i in range(iGridEdge): for j in range(iGridEdge): # bounds checking iPlus = (i + 1) % iGridEdge iMinus = (i - 1) % iGridEdge jPlus = (j + 1) % iGridEdge jMinus = (j - 1) % iGridEdge # count number of neighbors iNext = (imThis[i][jPlus] + imThis[iPlus][j] + imThis[iMinus][j] + imThis[i][jMinus] + imThis[iPlus][jPlus] + imThis[iMinus][jMinus] + imThis[iPlus][jMinus] + imThis[iMinus][jPlus]) iCurrent = imThis[i][j] # update rule if(iCurrent == 0): if(iNext == 3): imThat[i][j] = 1 else: imThat[i][j] = 0 else: if(iNext <= 1): imThat[i][j] = 0 elif(iNext > 3): imThat[i][j] = 0 elif((iNext == 2) or (iNext == 3)): imThat[i][j] = 1 # end loop over elements # swap buffers imOther = imThis imThis = imThat imThat = imOther # print summary statistic and display image print sum(sum(imThis)) / float((iGridEdge * iGridEdge)) pylab.imshow(imThis, cmap = pylab.cm.prism) pylab.draw()
By sticking a pylab.savefig
command after the pylab.imshow
call, the images can be written to files:
pylab.imshow(imThis, cmap = pylab.cm.prism) pylab.savefig("frame_" + str(t) + ".png") pylab.draw()
There’s an awful lot of whitespace in the resulting images, and the quickest hack for, well, hacking it off is to use ImageMagick:
convert -crop 682x682+272+83 frame_0.png trimmed_0.png
I actually did all fifty frames with a bash loop:
for i in `seq 0 49`; do convert -crop 682x682+272+83 frame_$i.png $i.png; done
Noting that the images had more resolution than was really necessary, I sized them down:
for i in `seq 0 49`; do convert -resize 100x100 $i.png resize_$i.png; done
Then I made the frames into an animated GIF, realized that they were out of order because the single-digit names were not prefixed with 0, fixed that problem, and made a new animation:
convert -delay 20 -loop 0 resize*.png animation.gif
The result was the image seen above. Here, I used a random initial configuration, because I had allotted myself half an hour to do the whole thing and I didn’t feel like specifying anything more detailed. However, you can put in a feature like a glider with code like this:
imThis = scipy.array(scipy.zeros((iGridEdge, iGridEdge))) imThat = scipy.array(scipy.zeros((iGridEdge, iGridEdge))) imThis[5][5] = 1 imThis[5][6] = 1 imThis[5][7] = 1 imThis[6][5] = 1 imThis[7][6] = 1
Lately, I’ve found myself growing tired of the usual creationist-bashing and snarky atheology which is the easiest type of science blogging to produce. Consequently, I plan to transition Sunclipse over to more math and physics posts, more “breakfast experiments” with programming and data mining, and more happy ramblings in the “bibliophilia” category.
This is great, it will be a lot more fun hacking around with this example than the usual boring examples I’d be using while getting to know Python better. Looking forward to more math and physics goodies too, thanks.
As before, a port (which, of course, has many spiritual siblings).
I’ve been meaning to tinker with some Python, as well as cellular automata. Thanks for the example of both.
Thank you, everyone, for the kind words, and for the Javascript port.
You don’t mind I can borrow and modify this code for non specific initial configurations? I wanted to show some of friends various patterns like the beehives and oscillators, but was too lazy to sit and code it from scratch. Besides, I can use this as an opportunity to get familiar with python :)
Thanks for the lovely post.
Whoops, typo there. “non specific” should read “specific”
Sure, go ahead. Installation instructions for the various libraries used are here.
Unfortunately, though Life is interesting, it’s been tainted by Wolfram’s grandiosity in my head.
I’m just thinking, though, in re Paul’s comments about the “usual boring examples”, if God built the universe as a giant cellular automaton, is there a “Hello World!” galaxy around somewhere?
One reason to write about these things is to redeem them from the Wolfram experience, and make sure the general rubric of “complexity” is not just “that stuff Wolfram goes on about.”
;-)
I’ve been fascinated by the Game of Life ever since I was quite young; I don’t know why. It’s just incredible how such complex and unexpected behavior can arise from such simple rules. 32×32 cells is pretty puny, though. I wrote a LabVIEW GoL simulator as a programming exercise not long ago.
Hey, you aren’t following PEP 8!
Yeah, I import multiple modules on the same line, my tabs and spaces are all screwed up, and I never put enough kitten blood in my morning milkshake.