Search code examples
pythonarraysmatrixconways-game-of-lifeautomaton

identifying and replacing elements in an nxn array in python to make a cellular automaton


I have been struggling to get started on this assignment for university on cellular automaton. The premis is simple enough, given an array in the form bellow where 1s represent black squares (or live cells) and 0s white squares (or dead cells).

world1 = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0]
 [0 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0]
 [0 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0]
 [0 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0]
 [0 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0]
 [0 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0]
 [0 0 1 0 0 0 0 1 0 1 0 0 0 0 1 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 1 1 1 0 0 0 1 1 1 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]

At each step in time ("tick"), the following transitions occur to all cells simultaneously:

Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.

Any live cell with two or three live neighbours lives on to the next generation.

Any live cell with more than three live neighbours dies, as if by overpopulation.

Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction . Here neighbours refer to the 8 squares surrounding each element

Now, as a physics student I can see the maths is fairly simple but I really have no idea how to structure the code and do so that it can be turned into an animation later. I can also see that there should be some exceptions for elements at the edge. Bellow is my attempt so far... it runs but returns nothing. As im sure you can tell im fairly new at programming!

import numpy as np
world1 = np.loadtxt('data/Pulsar.txt',dtype=np.int8) #loading data
def tick(world):
    north = world[i,i-1] #defining neighboughs as se, ne , n etc ..
    south = world[i,i+1]
    west = world[i+1,i]
    east = world[i-1,i]
    se = world[i+1,i+1]
    sw = world[i+1,i-1]
    ne = world[i-1,i+1]
    nw = world[i-1,i-1]
    neibours = (north, south, west, east, se, sw, ne, nw) #list of neighbough values
    np.where(world.all==0 and sum(neibours)==3, 1, world ) #replacing elements in array 
    np.where(world.all==1 and sum(neibours)<=2, 0, world )
    np.where(world.all==1 and sum(neibours)==2 or 3, 1, world )
    np.where(world.all==1 and sum(neibours)>=4, 0, world )


print(tick(world1))

Solution

  • I think the main issue is that you're conflating handling of a single cell and the entire matrix in your tick function.

    It starts by appearing to handle a single cell (references i which I assume is the index of the 'current' cell, although not really defined anywhere...), but then you seem to be trying to use some numpy magic to apply it to all cells as a vectorized operation. It's probably possible, but for the sake of simplicity, I would start with being more explicit.

    A couple of other things you should probably consider:

    • You need to perform some computation for each cell of the matrix - there should be a loop iterating all cells of a matrix...
    • The state of a cell post tick, should probably not affect the computation of a neighboring cell pre tick - for each tick you want to create a new copy of the matrix and populate it with the new state, instead of writing the changes in place and allowing the matrix to be in a 'polluted' in-between tick state.

    So, very roughly (in pseudo-python):

    def tick(matrix):
      new_state = np.array(X,Y) 
      for i in range(X):
        for j in range(Y):
          # this is where most of your `tick` function comes in,
          # adjusted to compute new state for a single given cell
          new_state[i][j] = compute_cell_state(i,j,matrix) 
      return new_state
    
    for t in range(ticks):
      matrix = tick(matrix)
      print(matrix)