I've been working in Reaction-Diffusion cellular automata with the cellpylib library for a course in my university (I wrote it all in one script so you don't have to install/download anything). I'd like to save the evolution of the automata data to a csv file to run some statistics. That is, I'd like to save the data in columns where the first column is 'number of "1"' and the second column: 'time steps'.

Thus, I need help in:

(1) Creating a variable that saves the amount of '1' per time step (I think so).

(2) I need to export all that data to a csv file (number of "1" and the corresponding iteration, from 1 to time_steps in the code below).

The code is the following.

#Libraries
import matplotlib
matplotlib.matplotlib_fname()
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.animation as animation
import numpy as np
import csv

# Conditions 
#############################
theta = 1              # this is the condition for Moore neighbourhood
Int = 100              # this is the iteration speed (just for visualization)
time_steps = 100       # Iterations
size = 8               # this is the size of the matrix (8x8)
#############################

# Definitions
def plot2d_animate(ca, title=''):
    c = mpl.colors.ListedColormap(['green', 'red', 'black', 'gray'])
    n = mpl.colors.Normalize(vmin=0,vmax=3)   
    fig = plt.figure()
    plt.title(title)
    im = plt.imshow(ca[0], animated=True, cmap=c, norm=n)
    i = {'index': 0}
    def updatefig(*args):
        i['index'] += 1
        if i['index'] == len(ca):
            i['index'] = 0
        im.set_array(ca[i['index']])
        return im,
    ani = animation.FuncAnimation(fig, updatefig, interval=Int, blit=True)
    plt.show()


def init_simple2d(rows, cols, val=1, dtype=np.int):
    x = np.zeros((rows, cols), dtype=dtype)
    x[x.shape[0]//2][x.shape[1]//2] = val
    return np.array([x])

def evolve2d(cellular_automaton, timesteps, apply_rule, r=1, neighbourhood='Moore'):
    _, rows, cols = cellular_automaton.shape
    array = np.zeros((timesteps, rows, cols), dtype=cellular_automaton.dtype)
    array[0] = cellular_automaton

    von_neumann_mask = np.zeros((2*r + 1, 2*r + 1), dtype=bool)
    for i in range(len(von_neumann_mask)):
        mask_size = np.absolute(r - i)
        von_neumann_mask[i][:mask_size] = 1
        if mask_size != 0:
            von_neumann_mask[i][-mask_size:] = 1

    def get_neighbourhood(cell_layer, row, col):
        row_indices = [0]*(2*r+1)
        for i in range(-r,r+1):
            row_indices[i+r]=(i+row) % cell_layer.shape[0]
        col_indices = [0]*(2*r+1)
        for i in range(-r,r+1):
            col_indices[i+r]=(i+col) % cell_layer.shape[1]
        n = cell_layer[np.ix_(row_indices, col_indices)]
        if neighbourhood == 'Moore':
            return n
        elif neighbourhood == 'von Neumann':
            return np.ma.masked_array(n, von_neumann_mask)
        else:
            raise Exception("unknown neighbourhood type: %s" % neighbourhood)

    for t in range(1, timesteps):
        cell_layer = array[t - 1]
        for row, cell_row in enumerate(cell_layer):
            for col, cell in enumerate(cell_row):
                n = get_neighbourhood(cell_layer, row, col)
                array[t][row][col] = apply_rule(n, (row, col), t)
    return array


def ca_reaction_diffusion(neighbourhood, c, t):
    center_cell = neighbourhood[1][1]
    total = np.sum(neighbourhood==1)
    if total >= theta and center_cell==0: 
            return 1
    elif center_cell == 1:
            return 2
    elif center_cell == 2:
            return 3
    elif center_cell == 3:
            return 0
    else:
            return 0

# Initial condition
cellular_automaton = init_simple2d(size, size, val=0, dtype=int)

# Excitable initial cells
cellular_automaton[:, [1,2], [1,1]] = 1

# The evolution
cellular_automaton = evolve2d(cellular_automaton,
                              timesteps=time_steps,
                              neighbourhood='Moore',
                              apply_rule=ca_reaction_diffusion)


animation=plot2d_animate(cellular_automaton)

Explanation of the code: As you can see, there are 4 states: 0 (green), 1 (red), 2 (black) and 3 (gray). The way the automata evolves is with the cellular_automaton conditions. That is, for example, if a center cell has a value of 0 (excitable cell) and at least one cell (theta value) on its Moore neighbourhood is in state 1, in the following time step the same cell will be at state 1 (excited).

To notice: The configuration of this matrix is toroidal, and the definitions are taken from the cellpylib library.

I've been stuck with this for over a week, so I'd really appreciate some help. Thanks in advance!

1 Answers

1
Sachin Raghavendran On Best Solutions

I am not well-experienced in this subject matter (and I was not fully clear on what you intended for me to do). I went through and implemented the counting of the specific "0", "1", "2" and "3" value cells in "evolve2d" function. This code should be viewed as "starter code"; whatever specifically you are trying to do should piggyback off of what I have given you. Additionally, this task could have been accomplished through some better code design and definitely, better planning of your function locations (as part of better coding practice and overall cleaner code that is easy to debug). Please peruse and UNDERSTAND the changes that I made.

#Libraries
import matplotlib
matplotlib.matplotlib_fname()
import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.animation as animation
import numpy as np
import csv

# Conditions
#############################
theta = 1              # this is the condition for Moore neighbourhood
iter_speed = 100              # this is the iteration speed (just for visualization)
time_steps = 100       # Iterations
size = 8               # this is the size of the matrix (8x8)
#############################

# Definitions
def plot2d_animate(ca, title=''):
    c = mpl.colors.ListedColormap(['green', 'red', 'black', 'gray'])
    n = mpl.colors.Normalize(vmin=0,vmax=3)
    fig = plt.figure()
    plt.title(title)
    im = plt.imshow(ca[0], animated=True, cmap=c, norm=n)
    i = {'index': 0}
    def updatefig(*args):
        i['index'] += 1
        if i['index'] == len(ca):
            i['index'] = 0
        im.set_array(ca[i['index']])
        return im,
    ani = animation.FuncAnimation(fig, updatefig, interval=iter_speed, blit=True)
    plt.show()
#############I ADDED EXTRA ARGUMENTs FOR THE FUNCTION BELOW
def get_neighbourhood(cell_layer, row, col, r = 1, neighbourhood = "Moore"):
    row_indices = [0]*(2*r+1)
    for i in range(-r,r+1):
        row_indices[i+r]=(i+row) % cell_layer.shape[0]
    col_indices = [0]*(2*r+1)
    for i in range(-r,r+1):
        col_indices[i+r]=(i+col) % cell_layer.shape[1]
    n = cell_layer[np.ix_(row_indices, col_indices)]
    if neighbourhood == 'Moore':
        return n
    elif neighbourhood == 'von Neumann':
        return np.ma.masked_array(n, von_neumann_mask)
    else:
        raise Exception("unknown neighbourhood type: %s" % neighbourhood)

def init_simple2d(rows, cols, val=1, dtype=np.int):
    x = np.zeros((rows, cols), dtype=dtype)
    x[x.shape[0]//2][x.shape[1]//2] = val
    return np.array([x])
#Inner functions was moved due to bad coding practice. Arguments were also changed. Make sure you understand what I did.
def evolve2d(cellular_automaton, timesteps, apply_rule, r=1, neighbourhood='Moore'):
    _, rows, cols = cellular_automaton.shape
    array = np.zeros((timesteps, rows, cols), dtype=cellular_automaton.dtype)
    array[0] = cellular_automaton

    von_neumann_mask = np.zeros((2*r + 1, 2*r + 1), dtype=bool)
    for i in range(len(von_neumann_mask)):
        mask_size = np.absolute(r - i)
        von_neumann_mask[i][:mask_size] = 1
        if mask_size != 0:
            von_neumann_mask[i][-mask_size:] = 1
    #################################################
    #These lists keep track of values over the course of the function:
    Result_0 = ["Number of 0"]
    Result_1 = ["Number of 1"]
    Result_2 = ["Number of 2"]
    Result_3 = ["Number of 3"]
    #################################################
    for t in range(1, timesteps):
    #################################################
        #This dictionary keeps track of values per timestep
        value_iter_tracker = {0: 0, 1: 0, 2: 0, 3: 0 }
    #################################################
        cell_layer = array[t - 1]
        for row, cell_row in enumerate(cell_layer):
            for col, cell in enumerate(cell_row):
                n = get_neighbourhood(cell_layer, row, col)
                ################################################
                res = apply_rule(n, (row, col), t)
                value_iter_tracker[res]+=1
                array[t][row][col] = res
                ################################################
        print(value_iter_tracker)
        ########################################################
        #Now we need to add the results of the iteration dictionary to the corresponding
        #lists in order to eventually export to the csv
        Result_0.append(value_iter_tracker[0])
        Result_1.append(value_iter_tracker[1])
        Result_2.append(value_iter_tracker[2])
        Result_3.append(value_iter_tracker[3])
        ########################################################
    ############################################################
    #function call to export lists to a csv:
    timesteps_result = list(range(1, timesteps))
    timesteps_result = ["Time Step"] + timesteps_result
    #If you don't understand what is going on here, put print statement and/or read docs
    vals = zip(timesteps_result, Result_0, Result_1, Result_2, Result_3)
    write_to_csv_file(list(vals))
    ############################################################

    return array

################################################################################
#THIS CODE IS FROM:
#https://stackoverflow.com/questions/14037540/writing-a-python-list-of-lists-to-a-csv-file
import pandas as pd
def write_to_csv_file(data):
    data = [list(x) for x in data]
    my_df = pd.DataFrame(data)
    my_df.to_csv('output1.csv', index=False, header=False)
################################################################################


def ca_reaction_diffusion(neighbourhood, c, t):
    center_cell = neighbourhood[1][1]
    total = np.sum(neighbourhood==1)
    if total >= theta and center_cell==0:
            return 1
    elif center_cell == 1:
            return 2
    elif center_cell == 2:
            return 3
    elif center_cell == 3:
            return 0
    else:
            return 0

# Initial condition
cellular_automaton = init_simple2d(size, size, val=0, dtype=int)

# Excitable initial cells
cellular_automaton[:, [1,2], [1,1]] = 1

# The evolution
cellular_automaton = evolve2d(cellular_automaton,
                              timesteps=time_steps,
                              neighbourhood='Moore',
                              apply_rule=ca_reaction_diffusion)




animation=plot2d_animate(cellular_automaton)

I have left comments that should clarify the changes that I made. Essentially, when you call the evolve2d function, a csv file called "output1.csv" is created with the timestep results. I used the pandas package to write the data into a csv but other methods could have been used as well. I will leave it to you to take advantage of the changes that I made for your use. Hope this helps.