Additional Features

Langton’s Lambda

One way to specify CA rules is with rule tables. Rule tables are enumerations of all possible neighbourhood states together with their cell state mappings. For any given neighbourhood state, a rule table provides the associated cell state value. CellPyLib provides a built-in function for creating random rule tables. The following snippet demonstrates its usage:

import cellpylib as cpl

rule_table, actual_lambda, quiescent_state = cpl.random_rule_table(lambda_val=0.45, k=4, r=2,
                                                                   strong_quiescence=True,
                                                                   isotropic=True)
cellular_automaton = cpl.init_random(128, k=4)

# use the built-in table_rule to use the generated rule table
cellular_automaton = cpl.evolve(cellular_automaton, timesteps=200,
                                apply_rule=lambda n, c, t: cpl.table_rule(n, rule_table), r=2)

The following plots demonstrate the effect of varying the lambda parameter:

_images/phase_transition.png

C. G. Langton describes the lambda parameter, and the transition from order to criticality to chaos in cellular automata while varying the lambda parameter, in the paper:

Langton, C. G. (1990). Computation at the edge of chaos: phase transitions
and emergent computation. Physica D: Nonlinear Phenomena, 42(1-3), 12-37.

Reversible CA

Elementary CA can be explicitly made to be reversible. CellPyLib has a class, ReversibleRule, which can be used to decorate a rule, making it reversible. The following example demonstrates the creation of the elementary reversible CA rule 90R:

import cellpylib as cpl

cellular_automaton = cpl.init_random(200)
rule = cpl.ReversibleRule(cellular_automaton[0], 90)

cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100,
                                apply_rule=ule)

cpl.plot(cellular_automaton)
_images/rule90R.png

Asynchronous CA

Typically, evolving a CA involves the synchronous updating of all the cells in a given timestep. However, it is also possible to consider CA in which the cells are updated asynchronously. There are various schemes for achieving this. Wikipedia has a page dedicated to this topic.

CellPyLib has a class, AsynchronousRule, which can be used to decorate a rule, making it asynchronous. In the following example, the rule 60 sequential CA from the notes of A New Kind of Science (Chapter 9, section 10: Sequential cellular automata) is implemented:

import cellpylib as cpl

cellular_automaton = cpl.init_simple(21)

rule = cpl.AsynchronousRule(apply_rule=lambda n, c, t: cpl.nks_rule(n, 60),
                         update_order=range(1, 20))

cellular_automaton = cpl.evolve(cellular_automaton, timesteps=19*20,
                                apply_rule=rule)

# get every 19th row, including the first, as a cycle is completed every 19 rows
cpl.plot(cellular_automaton[::19])
_images/rule60sequential.png

The AsynchronousRule requires the specification of an update order (if none is provided, then an order is constructed based on the number of cells in the CA). An update order specifies which cell will be updated as the CA evolves. For example, the update order [2, 3, 1] states that cell 2 will be updated in the next timestep, followed by cell 3 in the subsequent timestep, and then cell 1 in the timestep after that. This update order is adhered to for the entire evolution of the CA. Cells that are not being updated do not have the rule applied to them in that timestep.

An option is provided to randomize the update order at the end of each cycle (i.e. timestep). This is equivalent to selecting a cell randomly at each timestep to update, leaving all others unchanged during that timestep. The following example demonstrates a simplistic 2D CA in which a cell is picked randomly at each timestep, and its state is updated to a value of 1 (all cells begin with a state of 0):

import cellpylib as cpl

cellular_automaton = cpl.init_simple2d(50, 50, val=0)

apply_rule = cpl.AsynchronousRule(apply_rule=lambda n, c, t: 1, num_cells=(50, 50),
                                  randomize_each_cycle=True)

cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=50,
                                  neighbourhood='Moore', apply_rule=apply_rule)

cpl.plot2d_animate(cellular_automaton, interval=200, autoscale=True)
_images/async_random.gif

CTRBL Rules

There exists a class of important CA that exhibit the property of self-reproduction. That is, there are patterns observed that reproduce themselves during the evolution of these CA. This phenomenon has obvious relevance to the study of Biological systems. These CA are typically 2-dimensional, with a von Neumann neighbourhood of radius 1. The convention when specifying the rules for these CA is to enumerate the rule table using the states of the center (C), top (T), right (R), bottom (B), and left (L) cells in the von Neumann neighbourhood.

Such CTRBL CA are supported in CellPyLib, through the CTRBLRule class. A particularly well-known CA in this class is Langton’s Loop. CellPyLib has a built-in implementation of this CA, available through the LangtonsLoop class.

Here is a simple example of a 2D CA that uses a CTRBL rule:

import cellpylib as cpl

ctrbl_rule = cpl.CTRBLRule(rule_table={
    (0, 1, 0, 0, 0): 1,
    (1, 1, 0, 0, 0): 0,
    (0, 0, 0, 0, 0): 0,
    (1, 0, 0, 0, 0): 1,
    (0, 0, 1, 1, 0): 0,
    (1, 1, 1, 1, 1): 1,
    (0, 1, 0, 1, 0): 0,
    (1, 1, 1, 0, 1): 1,
    (1, 0, 1, 0, 1): 1,
    (0, 1, 1, 1, 1): 1,
    (0, 0, 1, 1, 1): 0,
    (1, 1, 0, 0, 1): 1
}, add_rotations=True)

cellular_automaton = cpl.init_simple2d(rows=10, cols=10)

cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=60,
                                  apply_rule=ctrbl_rule, neighbourhood="von Neumann")

cpl.plot2d_animate(cellular_automaton)
_images/ctrbl.gif

It is a binary CA that always appears to evolve to some stable attractor state.

Custom Rules

A rule is a callable that contains the logic that will be applied to each cell of the CA at each timestep. Any kind of callable is valid, but the callable must accept 3 arguments: n, c and t. Furthermore, the callable must return the state of the current cell at the next timestep. The n argument is the neighbourhood, which is a NumPy array of length 2r + 1 representing the state of the neighbourhood of the cell (for 1D CA), where r is the neighbourhood radius. The state of the current cell will always be located at the “center” of the neighbourhood. The c argument is the cell identity, which is a scalar representing the index of the cell in the cellular automaton array. Finally, the t argument is an integer representing the time step in the evolution.

Any kind of callable is supported, and this is particularly useful if more complex handling, like statefulness, is required by the rule. For complex rules, the recommended approach is to define a class for the rule, which provides a __call__ function which accepts the n, c, and t arguments. The BaseRule class is provided for users to extend, which ensures that the custom rule is implemented with the correct __call__ signature.

As an example, below is a custom rule that simply keeps track of how many times each cell has been invoked:

import cellpylib as cpl
from collections import defaultdict

class CustomRule(cpl.BaseRule):

    def __init__(self):
        self.count = defaultdict(int)

    def __call__(self, n, c, t):
        self.count[c] += 1
        return self.count[c]

rule = CustomRule()

cellular_automaton = cpl.init_simple(11)

cellular_automaton = cpl.evolve(cellular_automaton, timesteps=10,
                                apply_rule=rule)

cpl.plot(cellular_automaton)
_images/custom_rule.png