Vectorize Conway's Game of Life in pure numpy?

Question:

I’m wondering if there is a way to implement Conway’s game of life without resorting to for loops, if statements and other control structures typical of programming.
It should be pretty easy to vectorize for loops, but how would you convert the checks on the neighborhood to a matrix operation?

The base logic is something like this:

def neighbors(cell, distance=1):
    """Return the neighbors of cell."""
    x, y = cell
    r = xrange(0 - distance, 1 + distance)
    return ((x + i, y + j) # new cell offset from center
            for i in r for j in r # iterate over range in 2d
            if not i == j == 0) # exclude the center cell

I hope this is not considered as off-topic by the mods, I’m genuinely curios and I am just starting out with CAs.

Cheers

Asked By: Daniele Grattarola

||

Answers:

The answer to your question is “yes, it is possible” (particularly the board updates from board n to board n+1).

I describe the process in detail here. The main technique to generate the neighborhood around a central cell involves using “strides” (the way that numpy and other array computation systems know how to walk across rows and columns of elements when they are really stored in memory in flat 1D thing) in a custom fashion to generate neighborhoods around cells. I describe that process here.

One last comment: since Game of Life iterates from state n to state n+1, while you could literally remove all imperative looping, it doesn’t really make sense to take out that top-level control loop. So, has a loop: for round in range(num_rounds): board.update() where board.update doesn’t use loops (except to do some side calculations … again, you could remove those but it would make the program longer and less elegant).

To give you a concrete example (and be more compatible with StackOverflow’s answer requirements), here’s some select cutting and pasting from my posts to generate the central neighborhoods from a simple 4×4 board [apologies, this is python 2 code, you’ll have to modify the prints a bit]:

board = np.arange(16).reshape((4,4))
print board
print board.shape

We want to pick out the four “complete” neighborhoods centered around 5, 6, 7, and 8. Let’s look at the neighborhood for 5. What is the shape of the result? 3×3. What are the strides? Well, to walk across a row is still just walking one element at a time and to get to the next row is still 4 elements at a time. These are the same as the strides in the original. The difference is we don’t take “everything”, we just take a selection. Let’s see if that actually works:

from numpy.lib.stride_tricks import as_strided
neighbors = as_strided(board, shape=(3,3), strides=board.strides)
print neighbors

Ok, nice. Now, if we want all four neighborhoods, what is the output shape? We have several 3×3 results. How many? In this case, we have 2×2 of them (for each of the “center” cells). This gives a shape of (2,2,3,3) – the neighborhoods are the inner dimensions and the organization of the neighborhoods is the outer dimensions.

So, our strides (in terms of elements) end up being (4,0) within one neighborhood and (4,0) for progressing neighborhood to neighborhood. The total stride (element wise) is: (4,0,4,0). But, the component strides (our outer two dimensions) are the same as the strides of the board. This means that our neighborhood strides are board.strides + board.strides.

print board.strides + board.strides

neighborhoods = as_strided(board, 
                           shape=(2,2,3,3), 
                           strides=board.strides+board.strides)

print neighborhoods[0,0]
print neighborhoods[-1, -1]
Answered By: MrDrFenner

I was interested on the same thing,

Here is a vectorized implementation (no for loops)

https://gist.github.com/dvd101x/2f22de487dc5c615f5a4bb6528bf8754

Answered By: David Contreras