def test_neighbourhood(self): adjacency_matrix = [[1., .9, 0., 0., 1.], [1., 1., 1., 0., 0.], [0., 1., 1., 1., 0.], [0., 0., 1., 1., 1.], [1., 0., 0., 1., .8]] initial_conditions = [2., 3., 4., 5., 6.] def evaluate_neighbourhoods(ctx): c = ctx.node_index if c == 0: np.testing.assert_equal([2., 3., 6.], ctx.activities) np.testing.assert_equal([0, 1, 4], ctx.neighbour_indices) np.testing.assert_equal([1., 1., 1.], ctx.weights) elif c == 1: np.testing.assert_equal([2., 3., 4.], ctx.activities) np.testing.assert_equal([0, 1, 2], ctx.neighbour_indices) np.testing.assert_equal([.9, 1., 1.], ctx.weights) elif c == 2: np.testing.assert_equal([3., 4., 5.], ctx.activities) np.testing.assert_equal([1, 2, 3], ctx.neighbour_indices) np.testing.assert_equal([1., 1., 1.], ctx.weights) elif c == 3: np.testing.assert_equal([4., 5., 6.], ctx.activities) np.testing.assert_equal([2, 3, 4], ctx.neighbour_indices) np.testing.assert_equal([1., 1., 1.], ctx.weights) elif c == 4: np.testing.assert_equal([2., 5., 6.], ctx.activities) np.testing.assert_equal([0, 3, 4], ctx.neighbour_indices) np.testing.assert_equal([1., 1., .8], ctx.weights) ntm.evolve(initial_conditions, adjacency_matrix, timesteps=2, activity_rule=evaluate_neighbourhoods)
def test_evolution(self): points = [(0, 1), (0.23, 0.5), (0.6, 0.77), (0.33, 0.88)] A, B, C, D, n, dt, timesteps = 120, 120, 40, 120, 5, 1e-05, 1000 tsp_net = HopfieldTankTSPNet(points, dt=dt, A=A, B=B, C=C, D=D, n=n) initial_conditions = [-0.022395203920575254, -0.023184407969001723, -0.022224769264670836, -0.022411201286076467, -0.02308227809621827, -0.023463757363683353, -0.0222625041883513, -0.0228662974775357, -0.023547421145956916, -0.02142219621197953, -0.022375730795237334, -0.022534578294732176, -0.021526161923257372, -0.02003505371915011, -0.023113647911417328, -0.023001396844357286] activities, _ = evolve(initial_conditions, tsp_net.adjacency_matrix, tsp_net.activity_rule, timesteps=timesteps, parallel=True) permutation_matrix = tsp_net.get_permutation_matrix(activities) expected_permutation_matrix = [[4.81578032e-08, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 7.50011973e-01, 0.0], [0.0, 1.0, 0.0, 0.0]] np.testing.assert_almost_equal(expected_permutation_matrix, permutation_matrix) _, _, tour_length = tsp_net.get_tour_graph(points, permutation_matrix) self.assertEqual(1.6510914079204857, tour_length)
def test_past_activities_multiple_past(self): adjacency_matrix = [[1, 1], [1, 1]] initial_conditions = [1, 1] past_conditions = [[-1, -1], [0, 0]] def activity_rule(ctx): p = ctx.past_activities t = ctx.timestep if t == 1: self.assertEqual(p, [[-1, -1], [0, 0]]) if t == 2: self.assertEqual(p, [[0, 0], [1, 1]]) if t == 3: self.assertEqual(p, [[1, 1], [2, 2]]) return ctx.current_activity + 1 activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, activity_rule, timesteps=4, past_conditions=past_conditions) np.testing.assert_equal(activities, [[1, 1], [2, 2], [3, 3], [4, 4]])
def test_sequential_left_to_right(self): expected = self._convert_to_matrix("rule60_sequential_simple_init.ca") adjacency_matrix = adjacency.cellular_automaton(n=21) initial_conditions = [0]*10 + [1] + [0]*10 r = AsynchronousRule(activity_rule=lambda ctx: rules.nks_ca_rule(ctx, 60), update_order=range(1, 20)) activities, adjacencies = evolve(initial_conditions, adjacency_matrix, timesteps=19*20, activity_rule=r.activity_rule) np.testing.assert_equal(expected, activities[::19])
def test_sequential_random(self): expected = self._convert_to_matrix("rule90_sequential_simple_init.ca") adjacency_matrix = adjacency.cellular_automaton(n=21) initial_conditions = [0]*10 + [1] + [0]*10 update_order = [19, 11, 4, 9, 6, 16, 10, 2, 17, 1, 12, 15, 5, 3, 8, 18, 7, 13, 14] r = AsynchronousRule(activity_rule=lambda ctx: rules.nks_ca_rule(ctx, 90), update_order=update_order) activities, adjacencies = evolve(initial_conditions, adjacency_matrix, timesteps=19*20, activity_rule=r.activity_rule) np.testing.assert_equal(expected, activities[::19])
def test_fsm(self): states = {'locked': 0, 'unlocked': 1} transitions = {'PUSH': 'p', 'COIN': 'c'} adjacency_matrix = [[1]] initial_conditions = [states['locked']] events = "cpcpp" def fsm_rule(ctx): if ctx.input == transitions['PUSH']: return states['locked'] else: # COIN event return states['unlocked'] activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, input=events, activity_rule=fsm_rule) np.testing.assert_equal([[0], [1], [0], [1], [0], [0]], activities)
import netomaton as ntm import numpy as np if __name__ == '__main__': """ This demo is inspired by "Collective dynamics of ‘small-world’ networks", by Duncan J. Watts and Steven H. Strogatz (Nature 393, no. 6684 (1998): 440). Towards the end of the paper, they state: "For cellular automata charged with the computational task of density classification, we find that a simple ‘majority-rule’ running on a small-world graph can outperform all known human and genetic algorithm-generated rules running on a ring lattice." The code below attempts to reproduce the experiment they are referring to. """ adjacency_matrix = ntm.network.watts_strogatz_graph(n=149, k=8, p=0.5) initial_conditions = np.random.randint(0, 2, 149) print("density of 1s: %s" % (np.count_nonzero(initial_conditions) / 149)) activities, adjacencies = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=149, activity_rule=lambda ctx: ntm.rules.majority_rule(ctx)) ntm.plot_grid(activities)
import netomaton as ntm if __name__ == '__main__': rule_table, actual_lambda, quiescent_state = ntm.random_rule_table(lambda_val=0.0, k=4, r=2, strong_quiescence=True, isotropic=True) lambda_vals = [0.15, 0.37, 0.75] ca_list = [] titles = [] for i in range(0, 3): adjacency_matrix = ntm.network.cellular_automaton(n=128, r=2) initial_conditions = ntm.init_random(128, k=4) rule_table, actual_lambda = ntm.table_walk_through(rule_table, lambda_vals[i], k=4, r=2, quiescent_state=quiescent_state, strong_quiescence=True) print(actual_lambda) # evolve the cellular automaton for 200 time steps activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=200, activity_rule=lambda ctx: ntm.table_rule(ctx, rule_table)) ca_list.append(activities) avg_node_entropy = ntm.average_node_entropy(activities) avg_mutual_information = ntm.average_mutual_information(activities) titles.append(r'$\lambda$ = %s, $\widebar{H}$ = %s, $\widebar{I}$ = %s' % (lambda_vals[i], "{:.4}".format(avg_node_entropy), "{:.4}".format(avg_mutual_information))) ntm.plot_grid_multiple(ca_list, titles=titles)
# NKS page 443 - Rule 122R adjacency_matrix = ntm.network.cellular_automaton(n=100) # carefully chosen initial conditions previous_state = [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1] initial_conditions = [1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1] r = ntm.ReversibleRule(lambda ctx: ntm.rules.nks_ca_rule(ctx, 122)) activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=1002, activity_rule=r.activity_rule, past_conditions=[previous_state]) timestep = [] average_node_entropies = [] for i, c in enumerate(activities): timestep.append(i) bit_string = ''.join([str(x) for x in c]) average_node_entropies.append(ntm.average_node_entropy(activities[:i+1])) print("%s, %s" % (i, average_node_entropies[-1])) plt.subplot(3, 1, (1, 2)) plt.title("Avg. Node (Shannon) Entropy") plt.gca().set_xlim(0, 1002) plt.gca().axes.xaxis.set_ticks([]) plt.plot(timestep, average_node_entropies)
import netomaton as ntm if __name__ == '__main__': adjacency_matrix = ntm.network.cellular_automaton2d( rows=60, cols=60, r=1, neighbourhood='von Neumann') initial_conditions = ntm.init_simple2d(60, 60) activities, _ = ntm.evolve( initial_conditions, adjacency_matrix, timesteps=30, activity_rule=lambda ctx: ntm.rules.totalistic_ca(ctx, k=2, rule=26)) # the evolution of a 2D cellular automaton can be animated ntm.animate(activities, shape=(60, 60), interval=150) adjacency_matrix = ntm.network.cellular_automaton(n=200) initial_conditions = [0] * 100 + [1] + [0] * 99 activities, _ = ntm.evolve( initial_conditions, adjacency_matrix, timesteps=100, activity_rule=lambda ctx: ntm.rules.nks_ca_rule(ctx, 30)) # the evolution of a 1D cellular automaton can be animated ntm.animate(activities, shape=(200, )) adjacency_matrix = ntm.network.cellular_automaton(n=225) initial_conditions = [0] * 112 + [1] + [0] * 112 activities, _ = ntm.evolve( initial_conditions, adjacency_matrix, timesteps=100, activity_rule=lambda ctx: ntm.rules.nks_ca_rule(ctx, 30))
A, B, C, D, n, dt, timesteps = 300, 300, 100, 300, 12, 1e-05, 1000 # avg. 2.9605078829924527, 70% convergence # A, B, C, D, n, dt, timesteps = 400, 400, 150, 400, 12, 1e-05, 1000 # avg. 3.1818680173024543, 80% convergence # A, B, C, D, n, dt, timesteps = 500, 500, 150, 300, 12, 1e-05, 1000 # avg. 3.4807220504123797, 100% convergence tsp_net = ntm.HopfieldTankTSPNet(points, dt=dt, A=A, B=B, C=C, D=D, n=n) adjacency_matrix = tsp_net.adjacency_matrix # -0.022 was chosen so that the sum of V for all nodes is 10; some noise is added to break the symmetry initial_conditions = [ -0.022 + np.random.uniform(-0.1 * 0.02, 0.1 * 0.02) for _ in range(len(adjacency_matrix)) ] activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, tsp_net.activity_rule, timesteps=timesteps, parallel=True) ntm.animate(activities, shape=(10, 10)) permutation_matrix = tsp_net.get_permutation_matrix(activities) print(permutation_matrix) G, pos, length = tsp_net.get_tour_graph(points, permutation_matrix) print(length) tsp_net.plot_tour(G, pos)
import matplotlib.pyplot as plt import netomaton as ntm import numpy as np if __name__ == '__main__': # NKS page 442 - Rule 122R adjacency_matrix = ntm.network.cellular_automaton(n=100) initial_conditions = [0]*40 + [1]*20 + [0]*40 r = ntm.ReversibleRule(lambda ctx: ntm.rules.nks_ca_rule(ctx, 122)) activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=1000, activity_rule=r.activity_rule, past_conditions=[initial_conditions]) timestep = [] average_node_entropies = [] for i, c in enumerate(activities): timestep.append(i) bit_string = ''.join([str(x) for x in c]) average_node_entropies.append(ntm.average_node_entropy(activities[:i+1])) print("%s, %s" % (i, average_node_entropies[-1])) plt.subplot(3, 1, (1, 2)) plt.title("Avg. Node (Shannon) Entropy") plt.gca().set_xlim(0, 1000) plt.gca().axes.xaxis.set_ticks([]) plt.plot(timestep, average_node_entropies) plt.subplot(3, 1, 3) plt.gca().axes.yaxis.set_ticks([])
# Light Weight Space Ship (LWSS) initial_conditions[1125] = 1 initial_conditions[1128] = 1 initial_conditions[1184] = 1 initial_conditions[1244] = 1 initial_conditions[1248] = 1 initial_conditions[1304] = 1 initial_conditions[1305] = 1 initial_conditions[1306] = 1 initial_conditions[1307] = 1 # Glider initial_conditions[1710] = 1 initial_conditions[1771] = 1 initial_conditions[1829] = 1 initial_conditions[1830] = 1 initial_conditions[1831] = 1 # Blinker initial_conditions[2415] = 1 initial_conditions[2416] = 1 initial_conditions[2417] = 1 activities, _ = ntm.evolve( initial_conditions, adjacency_matrix, timesteps=60, activity_rule=lambda ctx: ntm.rules.game_of_life_rule(ctx)) ntm.animate(activities, shape=(60, 60))
import netomaton as ntm if __name__ == '__main__': adjacency_matrix = ntm.network.cellular_automaton2d(60, 60, r=1, neighbourhood="Hex") initial_conditions = ntm.init_simple2d(60, 60) activities, _ = ntm.evolve( initial_conditions, adjacency_matrix, timesteps=31, activity_rule=lambda ctx: 1 if sum(ctx.activities) == 1 else ctx.current_activity) ntm.animate_hex(activities, shape=(60, 60), interval=150)
import math import netomaton as ntm if __name__ == '__main__': adjacency_matrix = ntm.network.cellular_automaton(n=200) initial_conditions = [0.0]*100 + [1.0] + [0.0]*99 # NKS page 157 def activity_rule(ctx): activities = ctx.activities result = (sum(activities) / len(activities)) * (3 / 2) frac, whole = math.modf(result) return frac activities, adjacencies = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=150, activity_rule=activity_rule) ntm.plot_grid(activities)
import netomaton as ntm import numpy as np if __name__ == '__main__': # set r to 3, for a neighbourhood size of 7 adjacency_matrix = ntm.network.cellular_automaton(149, r=3) initial_conditions = np.random.randint(0, 2, 149) # Mitchell et al. discovered this rule using a Genetic Algorithm rule_number = 6667021275756174439087127638698866559 print("density of 1s: %s" % (np.count_nonzero(initial_conditions) / 149)) activities, adjacencies = ntm.evolve( initial_conditions, adjacency_matrix, timesteps=149, activity_rule=lambda ctx: ntm.rules.binary_ca_rule(ctx, rule_number)) ntm.plot_grid(activities)
import netomaton as ntm import numpy as np if __name__ == '__main__': sandpile = ntm.Sandpile(rows=60, cols=60) initial_conditions = np.random.randint(5, size=3600) def perturb(pctx): # drop a grain on some node at the 85th timestep if pctx.timestep == 85 and pctx.node_index == 1034: return pctx.node_activity + 1 return pctx.node_activity activities, _ = ntm.evolve(initial_conditions, sandpile.adjacency_matrix, timesteps=110, activity_rule=sandpile.activity_rule, perturbation=perturb) ntm.animate(activities, shape=(60, 60), interval=150)
# If a coin is given in the Locked state, it transitions to Unlocked. # If a push is given in the Unlocked state, it transitions to Locked. # If a coin is given in the Unlocked state, it remains Unlocked. states = {'locked': 0, 'unlocked': 1} transitions = {'PUSH': 'p', 'COIN': 'c'} # a FSM can be thought of as a Network Automaton with a single node adjacency_matrix = [[1]] # the FSM starts off in the Locked state initial_conditions = [states['locked']] events = "cpcpp" def fsm_rule(ctx): if ctx.input == transitions['PUSH']: return states['locked'] else: # COIN event return states['unlocked'] activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, input=events, activity_rule=fsm_rule) print("final state: %s" % activities[-1][0]) ntm.plot_grid(activities)
import netomaton as ntm if __name__ == '__main__': adjacency_matrix = ntm.network.cellular_automaton(n=200) initial_conditions = ntm.init_random(200) activities, _ = ntm.evolve( initial_conditions, adjacency_matrix, timesteps=1000, activity_rule=lambda ctx: ntm.rules.nks_ca_rule(ctx, 30)) # calculate the average node entropy; the value will be ~0.999 in this case avg_node_entropy = ntm.average_node_entropy(activities) print(avg_node_entropy)
import netomaton as ntm if __name__ == '__main__': adjacency_matrix = ntm.network.cellular_automaton(n=200) initial_conditions = [0]*100 + [1] + [0]*99 activities, adjacencies = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=100, activity_rule=lambda ctx: ntm.rules.totalistic_ca(ctx, k=3, rule=777)) ntm.plot_grid(activities)
0, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] half_one = [ 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] half_two = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1 ] half_zero = [-1 if x == 0 else x for x in half_zero] half_one = [-1 if x == 0 else x for x in half_one] half_two = [-1 if x == 0 else x for x in half_two] initial_conditions = half_two activities, adjacencies = ntm.evolve( initial_conditions, hopfield_net.adjacency_matrix, timesteps=hopfield_net.num_nodes * 7, activity_rule=hopfield_net.activity_rule) # view the weights, stored in the adjacency matrix # plot_grid(hopfield_net.adjacency_matrix) # view the time evolution of the Hopfield net as it completes the given pattern ntm.animate(activities[::hopfield_net.num_nodes], shape=(6, 5), interval=150)
] neighbourhood_v = [ ctx.activities[i][1] for i, idx in enumerate(ctx.neighbour_indices) if idx != ctx.node_index ] diffusion_u = sum(neighbourhood_u) - (4 * prev_u) diffusion_v = sum(neighbourhood_v) - (4 * prev_v) inter_u = (-prev_u * prev_v**2) + f * (1 - prev_u) inter_v = (prev_u * prev_v**2) - (k) * prev_v new_u = prev_u + (r_u * diffusion_u) + inter_u new_v = prev_v + (r_v * diffusion_v) + inter_v return new_u, new_v activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=3000, activity_rule=react_diffuse) # we want to visualize the concentrations of U only activities = [[j[0] for j in i] for i in activities] ntm.animate(activities, shape=(100, 100), colormap='RdYlBu', vmin=0.2, interval=25)
def noisy_rule_90(ctx): """ This rule implements the continuous cellular automaton with generalization of Rule 90, described on pages 325 and 976 of Stephen Wolfram's "A New Kind of Science". """ x = ctx.activities[0] + ctx.activities[2] # λ[x_] := Exp[-10 (x - 1)^2] + Exp[-10 (x - 3)^2] result = np.exp(-10 * ((x - 1)**2)) + np.exp(-10 * ((x - 3)**2)) return result def noisy_rule_30(ctx): """ This rule implements the continuous cellular automaton with generalization of Rule 30, described on pages 325 and 976 of Stephen Wolfram's "A New Kind of Science". """ x = ctx.activities[0] + ctx.activities[1] + ctx.activities[2] + ( ctx.activities[1] * ctx.activities[2]) # λ[x_] := Exp[-10 (x - 1)^2] + Exp[-10 (x - 3)^2] result = np.exp(-10 * ((x - 1)**2)) + np.exp(-10 * ((x - 3)**2)) return result activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=100, activity_rule=noisy_rule_30, perturbation=perturbation) ntm.plot_grid(activities)
HEAD['q6']: { CELL[' ']: [HEAD['q6'], CELL[' '], TuringMachine.STAY], CELL['a']: [HEAD['q6'], CELL['a'], TuringMachine.STAY], CELL['b']: [HEAD['q6'], CELL['b'], TuringMachine.STAY], CELL['c']: [HEAD['q6'], CELL['c'], TuringMachine.STAY], CELL['x']: [HEAD['q6'], CELL['x'], TuringMachine.STAY], CELL['y']: [HEAD['q6'], CELL['y'], TuringMachine.STAY], CELL['z']: [HEAD['q6'], CELL['z'], TuringMachine.STAY] } } tape = " aabbcc " tm = HeadCentricTuringMachine(tape=[CELL[t] for t in tape], rule_table=rule_table, initial_head_state=HEAD['q0'], initial_head_position=2, terminating_state=HEAD['q6'], max_timesteps=50) activities, _ = ntm.evolve(tm.initial_conditions, tm.adjacency_matrix, activity_rule=tm.activity_rule, input=tm.input_function) tape_history, head_activities = tm.activities_for_plotting(activities) ntm.plot_grid(tape_history, node_annotations=head_activities, show_grid=True)
CELL['d']: [HEAD['down'], CELL['e'], TuringMachine.RIGHT], CELL['e']: [HEAD['down'], CELL['c'], TuringMachine.LEFT] } } tape = "bbbbbbaeaaaaaaa" tm = TapeCentricTuringMachine(n=len(tape), rule_table=rule_table, initial_head_state=HEAD['up'], initial_head_position=8) initial_conditions = [CELL[t] for t in tape] activities, _ = ntm.evolve(initial_conditions, tm.adjacency_matrix, activity_rule=tm.activity_rule, timesteps=58) ntm.plot_grid(activities, node_annotations=tm.head_activities(activities), show_grid=True) # The following is a longer evolution, to show that ECA Rule 110 is emulated; # it will start when the plot rendered above is closed. tape = "b" * 50 + "ae" + "a" * 51 initial_conditions = [CELL[t] for t in tape] tm = TapeCentricTuringMachine(n=len(tape), rule_table=rule_table,
Each of the 120 nodes represents a body that can contain some amount of heat. Reproduces the plot at the top of Wolfram's NKS, page 163. See: https://www.wolframscience.com/nks/p163--partial-differential-equations/ See: http://hplgit.github.io/num-methods-for-PDEs/doc/pub/diffu/sphinx/._main_diffu001.html """ space = np.linspace(25, -25, 120) initial_conditions = [np.exp(-x**2) for x in space] adjacency_matrix = ntm.network.cellular_automaton(120) a = 0.25 dt = .5 dx = .5 F = a * dt / dx**2 def activity_rule(ctx): current = ctx.current_activity left = ctx.activities[0] right = ctx.activities[2] return current + F * (right - 2 * current + left) activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, activity_rule, timesteps=75) ntm.plot_grid(activities)
import netomaton as ntm if __name__ == '__main__': # NKS page 437 - Rule 214R adjacency_matrix = ntm.network.cellular_automaton(n=63) # run the CA forward for 32 steps to get the initial condition for the next evolution initial_conditions = [0] * 31 + [1] + [0] * 31 r = ntm.ReversibleRule(lambda ctx: ntm.rules.nks_ca_rule(ctx, 214)) activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=32, activity_rule=r.activity_rule, past_conditions=[initial_conditions]) # use the last state of the CA as the initial, previous state for this evolution r = ntm.ReversibleRule(lambda ctx: ntm.rules.nks_ca_rule(ctx, 214)) initial_conditions = activities[-2] activities, _ = ntm.evolve(initial_conditions, adjacency_matrix, timesteps=62, activity_rule=r.activity_rule, past_conditions=[activities[-1]]) ntm.plot_grid(activities)