├── tests ├── __init__.py ├── resources │ ├── sandpile_add_grain.txt │ ├── sandpile_add_grain.ca │ ├── sandpile_prior_history.ca │ ├── rule60_sequential_simple_init.ca │ ├── rule90_sequential_simple_init.ca │ ├── info.txt │ ├── sandpile.ca │ └── hopfield_net_weights.txt ├── test_ctrbl_rule.py ├── test_hopfield_net.py └── test_apen.py ├── requirements.txt ├── doc ├── license.rst ├── _static │ ├── ctrbl.gif │ ├── rule30.png │ ├── evoloop.gif │ ├── rule90R.png │ ├── sandpile.gif │ ├── block_ca_1d.png │ ├── block_ca_2d.gif │ ├── custom_rule.png │ ├── sdsr_loop.gif │ ├── async_random.gif │ ├── block_ca_1d_2.png │ ├── block_ca_1d_3.png │ ├── continuous_ca.png │ ├── game_of_life.gif │ ├── hopfield_net.gif │ ├── tot3_rule777.png │ ├── wireworld_xor.gif │ ├── avg_cell_entropy.png │ ├── langtons_loops.gif │ ├── phase_transition.png │ ├── rule60sequential.png │ ├── sandpile_growing.gif │ ├── wireworld_diodes.gif │ ├── fredkin_moore_demo.gif │ ├── sandpile_add_grain.gif │ ├── hopfield_net_weights.png │ ├── memoize-recursive-1D.png │ ├── tot_rule126_2d_moore.gif │ ├── tot_rule126_2d_moore.png │ ├── avg_mutual_information.png │ ├── density_classification.png │ ├── fredkin_multicolor_demo.gif │ ├── fredkin_von_neumann_demo.gif │ └── tot_rule126_2d_moore_t10.png ├── installation.rst ├── citing.rst ├── make.bat ├── Makefile ├── continuous.rst ├── gol.rst ├── collatz.rst ├── langtons_loop.rst ├── colors.rst ├── reference.rst ├── twodim.rst ├── index.rst ├── eca.rst ├── neighbourhood.rst ├── conf.py ├── complexity.rst ├── sdsr_evoloop.rst ├── hopfield.rst ├── wireworld.rst ├── fredkin.rst ├── sandpile.rst ├── block_ca.rst └── additional.rst ├── paper ├── rule30.png ├── paper.bib └── paper.md ├── resources ├── rule30.png ├── rule90R.png ├── block_ca_1d.png ├── block_ca_2d.gif ├── game_of_life.gif ├── hopfield_net.png ├── tot3_rule777.png ├── avg_cell_entropy.png ├── phase_transition.png ├── tot_rule126_2d_moore.png ├── avg_mutual_information.png └── density_classification.png ├── demos ├── cellpylib_memoization_demo.py ├── totalistic_2d_demo.py ├── cellpylib_demo.py ├── evoloop_demo.py ├── sdsr_loop_demo.py ├── totalistic_demo.py ├── langtons_loop_demo.py ├── block_ca1_demo.py ├── memoization_comparison_demo.py ├── collatz_conjecture_demo.py ├── sandpile_demo.py ├── density_classification_demo.py ├── custom_rule_demo.py ├── sandpile_add_grain_demo.py ├── continuous_demo.py ├── sandpile_growing_demo.py ├── block_ca2_demo.py ├── rule_table_demo.py ├── asynchronous_demo.py ├── game_of_life_demo.py ├── sandpile_dynamic_demo.py ├── block_ca4_demo.py ├── reversible_demo.py ├── ctrbl_rule_demo.py ├── block_ca3_demo.py ├── async_random_demo.py ├── fredkin_moore_demo.py ├── fredkin_von_neumann_demo.py ├── fredkin_multicolor_demo.py ├── rule_table_walkthrough_demo.py ├── rule122R_entropy_demo.py ├── wireworld_diodes_demo.py ├── wireworld_xor_demo.py ├── hopfield_net_demo.py ├── block2d_rotated_ca_demo.py ├── block2d_basic_ca_demo.py ├── sandpile_add_grain_demo.txt └── block2d_rotated_initial_conditions.txt ├── environment.yml ├── .gitignore ├── cellpylib ├── __init__.py ├── apen.py ├── hopfield_net.py ├── ctrbl_rule.py ├── sandpile.py ├── entropy.py ├── sdsr_loop.py ├── bien.py └── rule_tables.py ├── .github └── workflows │ └── python-app.yml ├── setup.py ├── CHANGELOG.md └── LICENSE.txt /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.15.4 2 | matplotlib==3.0.2 -------------------------------------------------------------------------------- /doc/license.rst: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | 4 | .. include:: ../LICENSE.txt -------------------------------------------------------------------------------- /paper/rule30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/paper/rule30.png -------------------------------------------------------------------------------- /doc/_static/ctrbl.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/ctrbl.gif -------------------------------------------------------------------------------- /doc/_static/rule30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/rule30.png -------------------------------------------------------------------------------- /resources/rule30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/rule30.png -------------------------------------------------------------------------------- /resources/rule90R.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/rule90R.png -------------------------------------------------------------------------------- /doc/_static/evoloop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/evoloop.gif -------------------------------------------------------------------------------- /doc/_static/rule90R.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/rule90R.png -------------------------------------------------------------------------------- /doc/_static/sandpile.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/sandpile.gif -------------------------------------------------------------------------------- /doc/_static/block_ca_1d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/block_ca_1d.png -------------------------------------------------------------------------------- /doc/_static/block_ca_2d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/block_ca_2d.gif -------------------------------------------------------------------------------- /doc/_static/custom_rule.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/custom_rule.png -------------------------------------------------------------------------------- /doc/_static/sdsr_loop.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/sdsr_loop.gif -------------------------------------------------------------------------------- /resources/block_ca_1d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/block_ca_1d.png -------------------------------------------------------------------------------- /resources/block_ca_2d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/block_ca_2d.gif -------------------------------------------------------------------------------- /resources/game_of_life.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/game_of_life.gif -------------------------------------------------------------------------------- /resources/hopfield_net.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/hopfield_net.png -------------------------------------------------------------------------------- /resources/tot3_rule777.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/tot3_rule777.png -------------------------------------------------------------------------------- /doc/_static/async_random.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/async_random.gif -------------------------------------------------------------------------------- /doc/_static/block_ca_1d_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/block_ca_1d_2.png -------------------------------------------------------------------------------- /doc/_static/block_ca_1d_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/block_ca_1d_3.png -------------------------------------------------------------------------------- /doc/_static/continuous_ca.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/continuous_ca.png -------------------------------------------------------------------------------- /doc/_static/game_of_life.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/game_of_life.gif -------------------------------------------------------------------------------- /doc/_static/hopfield_net.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/hopfield_net.gif -------------------------------------------------------------------------------- /doc/_static/tot3_rule777.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/tot3_rule777.png -------------------------------------------------------------------------------- /doc/_static/wireworld_xor.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/wireworld_xor.gif -------------------------------------------------------------------------------- /doc/_static/avg_cell_entropy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/avg_cell_entropy.png -------------------------------------------------------------------------------- /doc/_static/langtons_loops.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/langtons_loops.gif -------------------------------------------------------------------------------- /doc/_static/phase_transition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/phase_transition.png -------------------------------------------------------------------------------- /doc/_static/rule60sequential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/rule60sequential.png -------------------------------------------------------------------------------- /doc/_static/sandpile_growing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/sandpile_growing.gif -------------------------------------------------------------------------------- /doc/_static/wireworld_diodes.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/wireworld_diodes.gif -------------------------------------------------------------------------------- /resources/avg_cell_entropy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/avg_cell_entropy.png -------------------------------------------------------------------------------- /resources/phase_transition.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/phase_transition.png -------------------------------------------------------------------------------- /doc/_static/fredkin_moore_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/fredkin_moore_demo.gif -------------------------------------------------------------------------------- /doc/_static/sandpile_add_grain.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/sandpile_add_grain.gif -------------------------------------------------------------------------------- /resources/tot_rule126_2d_moore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/tot_rule126_2d_moore.png -------------------------------------------------------------------------------- /doc/_static/hopfield_net_weights.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/hopfield_net_weights.png -------------------------------------------------------------------------------- /doc/_static/memoize-recursive-1D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/memoize-recursive-1D.png -------------------------------------------------------------------------------- /doc/_static/tot_rule126_2d_moore.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/tot_rule126_2d_moore.gif -------------------------------------------------------------------------------- /doc/_static/tot_rule126_2d_moore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/tot_rule126_2d_moore.png -------------------------------------------------------------------------------- /resources/avg_mutual_information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/avg_mutual_information.png -------------------------------------------------------------------------------- /resources/density_classification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/resources/density_classification.png -------------------------------------------------------------------------------- /doc/_static/avg_mutual_information.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/avg_mutual_information.png -------------------------------------------------------------------------------- /doc/_static/density_classification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/density_classification.png -------------------------------------------------------------------------------- /doc/_static/fredkin_multicolor_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/fredkin_multicolor_demo.gif -------------------------------------------------------------------------------- /doc/_static/fredkin_von_neumann_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/fredkin_von_neumann_demo.gif -------------------------------------------------------------------------------- /doc/_static/tot_rule126_2d_moore_t10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lantunes/cellpylib/HEAD/doc/_static/tot_rule126_2d_moore_t10.png -------------------------------------------------------------------------------- /doc/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ------------ 3 | 4 | CellPyLib can be installed via pip: 5 | 6 | .. prompt:: bash $ 7 | 8 | pip install cellpylib 9 | 10 | Requirements for using this library are Python 3.6, numpy 1.15.4, and matplotlib 3.0.2. -------------------------------------------------------------------------------- /tests/resources/sandpile_add_grain.txt: -------------------------------------------------------------------------------- 1 | 0 0 0 0 0 0 0 0 0 0 2 | 0 1 0 3 0 2 2 2 0 0 3 | 0 3 3 2 1 1 1 2 2 0 4 | 0 2 3 0 0 1 0 2 3 0 5 | 0 0 3 2 3 3 2 2 1 0 6 | 0 2 0 0 1 1 3 2 1 0 7 | 0 2 3 1 0 3 2 2 2 0 8 | 0 1 2 1 3 3 1 0 2 0 9 | 0 3 2 2 2 2 3 2 3 0 10 | 0 0 0 0 0 0 0 0 0 0 -------------------------------------------------------------------------------- /demos/cellpylib_memoization_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import time 3 | 4 | start = time.time() 5 | cpl.evolve(cpl.init_simple(1000), timesteps=500, 6 | apply_rule=lambda n, c, t: cpl.nks_rule(n, 30), memoize=True) 7 | 8 | print(f"Elapsed: {time.time() - start:.2f} seconds") 9 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: cellpylib_dev_env 2 | 3 | channels: 4 | - conda-forge 5 | 6 | dependencies: 7 | - python=3.6 8 | - matplotlib=3.0.2 9 | - numpy=1.15.4 10 | - sphinx=3.5.4 11 | - pytest 12 | - pytest-cov 13 | - pip 14 | - pip: 15 | - sphinx-rtd-theme 16 | - sphinx-prompt==1.4.0 -------------------------------------------------------------------------------- /demos/totalistic_2d_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | cellular_automaton = cpl.init_simple2d(60, 60) 4 | 5 | # evolve the cellular automaton for 30 time steps 6 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=30, neighbourhood='Moore', 7 | apply_rule=lambda n, c, t: cpl.totalistic_rule(n, k=2, rule=126)) 8 | 9 | cpl.plot2d(cellular_automaton) 10 | -------------------------------------------------------------------------------- /demos/cellpylib_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | cellular_automaton = cpl.init_simple(200) 4 | # cellular_automaton = cpl.init_random(200) 5 | 6 | # evolve the cellular automaton for 100 time steps 7 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100, memoize=True, 8 | apply_rule=lambda n, c, t: cpl.nks_rule(n, 30)) 9 | 10 | cpl.plot(cellular_automaton) 11 | -------------------------------------------------------------------------------- /demos/evoloop_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | evoloop = cpl.Evoloop() 4 | 5 | # the initial conditions consist of a single loop 6 | cellular_automaton = evoloop.init_species13_loop((100, 100), 40, 15) 7 | 8 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=700, 9 | apply_rule=evoloop, memoize="recursive") 10 | 11 | cpl.plot2d_animate(cellular_automaton) 12 | -------------------------------------------------------------------------------- /demos/sdsr_loop_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | sdsr_loop = cpl.SDSRLoop() 4 | 5 | # the initial conditions consist of a single loop 6 | cellular_automaton = sdsr_loop.init_loops(1, (100, 100), [40], [40]) 7 | 8 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=700, 9 | apply_rule=sdsr_loop, memoize="recursive") 10 | 11 | cpl.plot2d_animate(cellular_automaton) 12 | -------------------------------------------------------------------------------- /demos/totalistic_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | cellular_automaton = cpl.init_simple(200) 4 | # cellular_automaton = cpl.init_random(200, k=3) 5 | 6 | # evolve the cellular automaton for 100 time steps 7 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100, 8 | apply_rule=lambda n, c, t: cpl.totalistic_rule(n, k=3, rule=777)) 9 | 10 | cpl.plot(cellular_automaton) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | data 2 | *.pyc 3 | *-checkpoint.ipynb 4 | .DS_Store 5 | *.log 6 | .idea 7 | *.iml 8 | .sublime-project 9 | .project 10 | .pydevproject 11 | *.pdf 12 | .env 13 | src/ 14 | .settings 15 | 16 | # Setuptools distribution folder. 17 | /dist/ 18 | 19 | # Python egg metadata, regenerated from source files by setuptools. 20 | /*.egg-info 21 | 22 | /doc/_build/ 23 | /site/ 24 | /.cache/ 25 | /.pytest_cache/ 26 | /build/ 27 | 28 | .coverage -------------------------------------------------------------------------------- /demos/langtons_loop_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | langtons_loop = cpl.LangtonsLoop() 4 | 5 | # the initial conditions consist of a single loop 6 | cellular_automaton = langtons_loop.init_loops(1, (75, 75), [40], [25]) 7 | 8 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=500, 9 | apply_rule=langtons_loop, memoize="recursive") 10 | 11 | cpl.plot2d_animate(cellular_automaton) 12 | -------------------------------------------------------------------------------- /demos/block_ca1_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | """ 5 | Block CA at the top of NKS page 460 6 | """ 7 | 8 | initial_conditions = np.array([[0]*13 + [1]*2 + [0]*201]) 9 | 10 | 11 | def block_rule(n, t): 12 | if n == (1, 1): return 1, 1 13 | elif n == (1, 0): return 1, 0 14 | elif n == (0, 1): return 0, 0 15 | elif n == (0, 0): return 0, 1 16 | 17 | 18 | ca = cpl.evolve_block(initial_conditions, block_size=2, timesteps=200, apply_rule=block_rule) 19 | 20 | cpl.plot(ca) 21 | -------------------------------------------------------------------------------- /demos/memoization_comparison_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import time 3 | 4 | start = time.time() 5 | cpl.evolve(cpl.init_simple(600), timesteps=300, 6 | apply_rule=lambda n, c, t: cpl.nks_rule(n, 30)) 7 | print(f"Without memoization: {time.time() - start:.2f} seconds elapsed") 8 | 9 | start = time.time() 10 | cpl.evolve(cpl.init_simple(600), timesteps=300, 11 | apply_rule=lambda n, c, t: cpl.nks_rule(n, 30), memoize=True) 12 | print(f"With memoization: {time.time() - start:.2f} seconds elapsed") 13 | -------------------------------------------------------------------------------- /demos/collatz_conjecture_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | 5 | initial = np.array([[17]], dtype=np.int) 6 | 7 | 8 | def activity_rule(n, c, t): 9 | n = n[1] 10 | if n % 2 == 0: 11 | # number is even 12 | return n / 2 13 | else: 14 | return 3*n + 1 15 | 16 | 17 | cellular_automaton = cpl.evolve(initial, apply_rule=activity_rule, 18 | timesteps=lambda ca, t: True if ca[-1][0] != 1 else False) 19 | 20 | print([i[0] for i in cellular_automaton]) 21 | -------------------------------------------------------------------------------- /demos/sandpile_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | 5 | n_rows = 45 6 | n_cols = 45 7 | sandpile = cpl.Sandpile(n_rows, n_cols) 8 | 9 | initial = np.random.randint(5, size=n_rows*n_cols).reshape((1, n_rows, n_cols)) 10 | # we're using a closed boundary, so make the boundary cells 0 11 | initial[0, 0, :], initial[0, n_rows-1, :], initial[0, :, 0], initial[0, :, n_cols-1] = 0, 0, 0, 0 12 | 13 | ca = cpl.evolve2d(initial, timesteps=50, apply_rule=sandpile, neighbourhood="von Neumann") 14 | 15 | cpl.plot2d_animate(ca) 16 | -------------------------------------------------------------------------------- /demos/density_classification_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | cellular_automaton = cpl.init_random(149) 5 | 6 | print("density of 1s: %s" % (np.count_nonzero(cellular_automaton) / 149)) 7 | 8 | # M. Mitchell et al. discovered this rule using a Genetic Algorithm 9 | rule_number = 6667021275756174439087127638698866559 10 | 11 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=149, 12 | apply_rule=lambda n, c, t: cpl.binary_rule(n, rule_number), r=3) 13 | 14 | cpl.plot(cellular_automaton) 15 | -------------------------------------------------------------------------------- /demos/custom_rule_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | from collections import defaultdict 3 | 4 | 5 | class CustomRule(cpl.BaseRule): 6 | 7 | def __init__(self): 8 | self.count = defaultdict(int) 9 | 10 | def __call__(self, n, c, t): 11 | self.count[c] += 1 12 | return self.count[c] 13 | 14 | 15 | rule = CustomRule() 16 | 17 | cellular_automaton = cpl.init_simple(11) 18 | 19 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=10, 20 | apply_rule=rule) 21 | 22 | cpl.plot(cellular_automaton) 23 | -------------------------------------------------------------------------------- /demos/sandpile_add_grain_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | 5 | n_rows = 45 6 | n_cols = 45 7 | sandpile = cpl.Sandpile(n_rows, n_cols) 8 | sandpile.add_grain(cell_index=(23, 23), timestep=1) 9 | 10 | initial = np.loadtxt('sandpile_add_grain_demo.txt', dtype=int) 11 | initial = np.array([initial]) 12 | 13 | ca = cpl.evolve2d(initial, timesteps=cpl.until_fixed_point(), 14 | apply_rule=sandpile, neighbourhood="von Neumann") 15 | 16 | print("Number of timesteps to reach fixed point: %s" % len(ca)) 17 | cpl.plot2d_animate(ca) 18 | -------------------------------------------------------------------------------- /demos/continuous_demo.py: -------------------------------------------------------------------------------- 1 | import math 2 | from pprint import pprint 3 | 4 | import numpy as np 5 | 6 | import cellpylib as cpl 7 | 8 | cellular_automaton = cpl.init_simple(200, dtype=np.float64) 9 | 10 | 11 | # NKS page 157 12 | def apply_rule(n, c, t): 13 | result = (sum(n) / len(n)) * (3 / 2) 14 | frac, whole = math.modf(result) 15 | return frac 16 | 17 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=150, 18 | apply_rule=apply_rule) 19 | 20 | pprint(cellular_automaton[:6, 95:106].tolist(), width=100) 21 | 22 | cpl.plot(cellular_automaton) 23 | -------------------------------------------------------------------------------- /demos/sandpile_growing_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | """ 4 | We repeatedly drop a grain of sand in the middle, allowing the sandpile to grow. 5 | After a grain is dropped, the system is allowed to evolve until a fixed point, 6 | where no further change occurs, before the next grain is dropped. 7 | """ 8 | n = 50 9 | sandpile = cpl.Sandpile(n, n) 10 | ca = cpl.init_simple2d(n, n, val=5) 11 | 12 | for i in range(300): 13 | ca[-1, n//2, n//2] += 1 14 | ca = cpl.evolve2d(ca, apply_rule=sandpile, 15 | timesteps=cpl.until_fixed_point(), neighbourhood='Moore') 16 | 17 | cpl.plot2d_animate(ca) 18 | -------------------------------------------------------------------------------- /demos/block_ca2_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | """ 5 | Block CA from 6 | https://writings.stephenwolfram.com/2023/02/computational-foundations-for-the-second-law-of-thermodynamics/ 7 | """ 8 | 9 | initial_conditions = np.array([[0]*4 + [1] + [0]*7 + [1] + [0]*4 + [1] + [0]*20]) 10 | 11 | 12 | def block_rule(n, t): 13 | if n == (1, 1): return 1, 1 14 | elif n == (1, 0): return 0, 1 15 | elif n == (0, 1): return 1, 0 16 | elif n == (0, 0): return 0, 0 17 | 18 | 19 | ca = cpl.evolve_block(initial_conditions, block_size=2, timesteps=100, apply_rule=block_rule) 20 | 21 | cpl.plot(ca) 22 | -------------------------------------------------------------------------------- /demos/rule_table_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | rule_table, actual_lambda, quiescent_state = cpl.random_rule_table(lambda_val=0.37, k=4, r=2, 4 | strong_quiescence=True, isotropic=True) 5 | 6 | # cellular_automaton = cpl.init_simple(128, val=1) 7 | cellular_automaton = cpl.init_random(128, k=4, n_randomized=20) 8 | 9 | # evolve the cellular automaton for 200 time steps 10 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=200, 11 | apply_rule=lambda n, c, t: cpl.table_rule(n, rule_table), r=2) 12 | 13 | cpl.plot(cellular_automaton) 14 | -------------------------------------------------------------------------------- /demos/asynchronous_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | # implements the rule 60 sequential automaton from the NKS Notes on 4 | # Chapter 9, section 10: "Sequential cellular automata" 5 | # http://www.wolframscience.com/nks/notes-9-10--sequential-cellular-automata/ 6 | cellular_automaton = cpl.init_simple(21) 7 | 8 | apply_rule = cpl.AsynchronousRule(apply_rule=lambda n, c, t: cpl.nks_rule(n, 60), update_order=range(1, 20)) 9 | 10 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=19*20, apply_rule=apply_rule) 11 | 12 | # get every 19th row, including the first, as a cycle is completed every 19 rows 13 | cpl.plot(cellular_automaton[::19]) 14 | -------------------------------------------------------------------------------- /demos/game_of_life_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | # Glider 4 | cellular_automaton = cpl.init_simple2d(60, 60) 5 | cellular_automaton[:, [28,29,30,30], [30,31,29,31]] = 1 6 | 7 | # Blinker 8 | cellular_automaton[:, [40,40,40], [15,16,17]] = 1 9 | 10 | # Light Weight Space Ship (LWSS) 11 | cellular_automaton[:, [18,18,19,20,21,21,21,21,20], [45,48,44,44,44,45,46,47,48]] = 1 12 | 13 | # evolve the cellular automaton for 60 time steps 14 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=60, neighbourhood='Moore', 15 | apply_rule=cpl.game_of_life_rule, memoize="recursive") 16 | 17 | cpl.plot2d_animate(cellular_automaton) 18 | -------------------------------------------------------------------------------- /demos/sandpile_dynamic_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | np.random.seed(0) 4 | 5 | 6 | n_rows = 45 7 | n_cols = 45 8 | sandpile = cpl.Sandpile(n_rows, n_cols) 9 | 10 | initial = np.random.randint(5, size=n_rows*n_cols).reshape((1, n_rows, n_cols)) 11 | # we're using a closed boundary, so make the boundary cells 0 12 | initial[0, 0, :], initial[0, n_rows-1, :], initial[0, :, 0], initial[0, :, n_cols-1] = 0, 0, 0, 0 13 | 14 | ca = cpl.evolve2d(initial, timesteps=cpl.until_fixed_point(), 15 | apply_rule=sandpile, neighbourhood="von Neumann") 16 | 17 | print("Number of timesteps to reach fixed point: %s" % len(ca)) 18 | cpl.plot2d_animate(ca) 19 | -------------------------------------------------------------------------------- /demos/block_ca4_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | """ 5 | Block CA at the bottom of NKS page 462 6 | """ 7 | 8 | initial_conditions = np.array([[0]*30 + [2]*30 + [0]*30]) 9 | 10 | 11 | def block_rule(n, t): 12 | if n == (1, 1): return 1, 1 13 | elif n == (1, 0): return 0, 2 14 | elif n == (0, 1): return 2, 0 15 | elif n == (0, 0): return 0, 0 16 | elif n == (2, 2): return 2, 2 17 | elif n == (2, 0): return 1, 0 18 | elif n == (0, 2): return 0, 1 19 | elif n == (2, 1): return 2, 1 20 | elif n == (1, 2): return 1, 2 21 | 22 | 23 | ca = cpl.evolve_block(initial_conditions, block_size=2, timesteps=4500, apply_rule=block_rule) 24 | 25 | cpl.plot(ca[-500:]) 26 | -------------------------------------------------------------------------------- /demos/reversible_demo.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import cellpylib as cpl 4 | 5 | # NKS page 437 - Rule 214R 6 | 7 | # run the CA forward for 32 steps to get the initial condition for the next evolution 8 | cellular_automaton = cpl.init_simple(63) 9 | rule = cpl.ReversibleRule(cellular_automaton[0], 214) 10 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=32, apply_rule=rule) 11 | 12 | # use the last state of the CA as the initial, previous state for this evolution 13 | rule = cpl.ReversibleRule(cellular_automaton[-1], 214) 14 | cellular_automaton = np.array([cellular_automaton[-2]]) 15 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=62, apply_rule=rule) 16 | 17 | cpl.plot(cellular_automaton) 18 | -------------------------------------------------------------------------------- /demos/ctrbl_rule_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | ctrbl_rule = cpl.CTRBLRule(rule_table={ 4 | (0, 1, 0, 0, 0): 1, 5 | (1, 1, 0, 0, 0): 0, 6 | (0, 0, 0, 0, 0): 0, 7 | (1, 0, 0, 0, 0): 1, 8 | (0, 0, 1, 1, 0): 0, 9 | (1, 1, 1, 1, 1): 1, 10 | (0, 1, 0, 1, 0): 0, 11 | (1, 1, 1, 0, 1): 1, 12 | (1, 0, 1, 0, 1): 1, 13 | (0, 1, 1, 1, 1): 1, 14 | (0, 0, 1, 1, 1): 0, 15 | (1, 1, 0, 0, 1): 1 16 | }, add_rotations=True) 17 | 18 | cellular_automaton = cpl.init_simple2d(rows=10, cols=10) 19 | 20 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=60, 21 | apply_rule=ctrbl_rule, neighbourhood="von Neumann") 22 | 23 | cpl.plot2d_animate(cellular_automaton) 24 | -------------------------------------------------------------------------------- /demos/block_ca3_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | """ 5 | Block CA from 6 | https://writings.stephenwolfram.com/2023/02/computational-foundations-for-the-second-law-of-thermodynamics/ 7 | """ 8 | 9 | initial_conditions = np.array([[0]*25 + [2]*17 + [0]*24]) 10 | 11 | 12 | def block_rule(n, t): 13 | if n == (1, 1): return 2, 2 14 | elif n == (1, 0): return 1, 0 15 | elif n == (0, 1): return 0, 1 16 | elif n == (0, 0): return 0, 0 17 | elif n == (2, 2): return 1, 1 18 | elif n == (2, 0): return 0, 2 19 | elif n == (0, 2): return 2, 0 20 | elif n == (2, 1): return 2, 1 21 | elif n == (1, 2): return 1, 2 22 | 23 | 24 | ca = cpl.evolve_block(initial_conditions, block_size=2, timesteps=200, apply_rule=block_rule) 25 | 26 | cpl.plot(ca) 27 | -------------------------------------------------------------------------------- /demos/async_random_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | 4 | cellular_automaton = cpl.init_simple2d(50, 50, val=0) 5 | 6 | 7 | # During each timestep, we'll check each cell if it should be the one updated according to the 8 | # update order. At the end of a timestep, the update order index is advanced, but if the 9 | # update order is randomized at the end of each timestep, then this is equivalent to picking 10 | # a cell randomly to update at each timestep. 11 | apply_rule = cpl.AsynchronousRule(apply_rule=lambda n, c, t: 1, num_cells=(50, 50), 12 | randomize_each_cycle=True) 13 | 14 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=50, 15 | neighbourhood='Moore', apply_rule=apply_rule) 16 | 17 | cpl.plot2d_animate(cellular_automaton, interval=200, autoscale=True) 18 | -------------------------------------------------------------------------------- /doc/citing.rst: -------------------------------------------------------------------------------- 1 | Citing 2 | ------ 3 | 4 | This project has been published in the 5 | `Journal of Open Source Software `_. 6 | 7 | This project may be cited as: 8 | 9 | **Antunes, L. M. (2021). CellPyLib: A Python Library for working with Cellular Automata. 10 | Journal of Open Source Software, 6(67), 3608.** 11 | 12 | 13 | BibTeX: 14 | 15 | .. code-block:: 16 | 17 | @article{Antunes2021, 18 | doi = {10.21105/joss.03608}, 19 | url = {https://doi.org/10.21105/joss.03608}, 20 | year = {2021}, 21 | publisher = {The Open Journal}, 22 | volume = {6}, 23 | number = {67}, 24 | pages = {3608}, 25 | author = {Luis M. Antunes}, 26 | title = {CellPyLib: A Python Library for working with Cellular Automata}, 27 | journal = {Journal of Open Source Software} 28 | } 29 | -------------------------------------------------------------------------------- /doc/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd -------------------------------------------------------------------------------- /demos/fredkin_moore_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | cellular_automaton = cpl.init_simple2d(60, 60) 5 | # the letter "E" 6 | cellular_automaton[0][28][28] = 1 7 | cellular_automaton[0][28][29] = 1 8 | cellular_automaton[0][28][30] = 1 9 | cellular_automaton[0][29][28] = 1 10 | cellular_automaton[0][30][28] = 1 11 | cellular_automaton[0][30][29] = 1 12 | cellular_automaton[0][30][30] = 1 13 | cellular_automaton[0][31][28] = 1 14 | cellular_automaton[0][32][28] = 1 15 | cellular_automaton[0][32][29] = 1 16 | cellular_automaton[0][32][30] = 1 17 | 18 | def activity_rule(n, c, t): 19 | current_activity = n[1][1] 20 | return (np.sum(n) - current_activity) % 2 21 | 22 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=20, 23 | apply_rule=activity_rule, neighbourhood="Moore") 24 | 25 | cpl.plot2d_animate(cellular_automaton, interval=350) 26 | -------------------------------------------------------------------------------- /demos/fredkin_von_neumann_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | cellular_automaton = cpl.init_simple2d(60, 60) 5 | # the letter "E" 6 | cellular_automaton[0][28][28] = 1 7 | cellular_automaton[0][28][29] = 1 8 | cellular_automaton[0][28][30] = 1 9 | cellular_automaton[0][29][28] = 1 10 | cellular_automaton[0][30][28] = 1 11 | cellular_automaton[0][30][29] = 1 12 | cellular_automaton[0][30][30] = 1 13 | cellular_automaton[0][31][28] = 1 14 | cellular_automaton[0][32][28] = 1 15 | cellular_automaton[0][32][29] = 1 16 | cellular_automaton[0][32][30] = 1 17 | 18 | def activity_rule(n, c, t): 19 | current_activity = n[1][1] 20 | return (np.sum(n) - current_activity) % 2 21 | 22 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=20, 23 | apply_rule=activity_rule, neighbourhood="von Neumann") 24 | 25 | cpl.plot2d_animate(cellular_automaton, interval=350) 26 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | github: 18 | @make clean html 19 | @rm -rf ../site/docs 20 | @mkdir -p ../site/docs 21 | @cp -a _build/html/. ../site/docs 22 | @touch ../site/docs/.nojekyll 23 | @echo "cellpylib.org\c" > ../site/docs/CNAME 24 | 25 | # Catch-all target: route all unknown targets to Sphinx using the new 26 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 27 | %: Makefile 28 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 29 | -------------------------------------------------------------------------------- /demos/fredkin_multicolor_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | cellular_automaton = cpl.init_simple2d(60, 60) 5 | # the letter "E" 6 | cellular_automaton[0][28][28] = 0 7 | cellular_automaton[0][28][29] = 1 8 | cellular_automaton[0][28][30] = 2 9 | cellular_automaton[0][29][28] = 3 10 | cellular_automaton[0][30][28] = 4 11 | cellular_automaton[0][30][29] = 5 12 | cellular_automaton[0][30][30] = 6 13 | cellular_automaton[0][31][28] = 7 14 | cellular_automaton[0][32][28] = 8 15 | cellular_automaton[0][32][29] = 9 16 | cellular_automaton[0][32][30] = 10 17 | 18 | def activity_rule(n, c, t): 19 | current_activity = n[1][1] 20 | return (np.sum(n) - current_activity) % 11 21 | 22 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=23, 23 | apply_rule=activity_rule, neighbourhood="von Neumann") 24 | 25 | cpl.plot2d_animate(cellular_automaton, interval=350, colormap='viridis') 26 | -------------------------------------------------------------------------------- /tests/resources/sandpile_add_grain.ca: -------------------------------------------------------------------------------- 1 | {{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 3, 0, 2, 2, 2, 0, 0}, {0, 3, 3, 2, 1, 1, 1, 2, 2, 0}, {0, 2, 3, 0, 0, 1, 0, 2, 3, 0}, {0, 0, 3, 2, 3, 3, 2, 2, 1, 0}, {0, 2, 0, 0, 1, 1, 3, 2, 1, 0}, {0, 2, 3, 1, 0, 3, 2, 2, 2, 0}, {0, 1, 2, 1, 3, 3, 1, 0, 2, 0}, {0, 3, 2, 2, 2, 2, 3, 2, 3, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 3, 0, 2, 2, 2, 0, 0}, {0, 3, 3, 2, 1, 1, 1, 2, 2, 0}, {0, 2, 3, 1, 0, 1, 0, 2, 3, 0}, {0, 0, 3, 2, 3, 3, 2, 2, 1, 0}, {0, 2, 0, 0, 1, 1, 3, 2, 1, 0}, {0, 2, 3, 1, 0, 3, 2, 2, 2, 0}, {0, 1, 2, 1, 3, 3, 1, 0, 2, 0}, {0, 3, 2, 2, 2, 2, 3, 2, 3, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 1, 0, 3, 0, 2, 2, 2, 0, 0}, {0, 3, 3, 2, 1, 1, 1, 2, 2, 0}, {0, 2, 3, 1, 0, 1, 0, 2, 3, 0}, {0, 0, 3, 2, 3, 3, 2, 2, 1, 0}, {0, 2, 0, 0, 1, 1, 3, 2, 1, 0}, {0, 2, 3, 1, 0, 3, 2, 2, 2, 0}, {0, 1, 2, 1, 3, 3, 1, 0, 2, 0}, {0, 3, 2, 2, 2, 2, 3, 2, 3, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}} -------------------------------------------------------------------------------- /doc/continuous.rst: -------------------------------------------------------------------------------- 1 | Continuous CA 2 | ------------- 3 | 4 | Cellular Automata needn't consist of discrete activities. The units in an automaton can also take on continuous-valued 5 | activities (i.e. states). 6 | 7 | The example below implements a continuous-valued Cellular Automaton from Stephen Wolfram's book `A New Kind of Science`, 8 | found on page 157: 9 | 10 | .. code-block:: 11 | 12 | import math 13 | import numpy as np 14 | import cellpylib as cpl 15 | 16 | cellular_automaton = cpl.init_simple(200, dtype=np.float64) 17 | 18 | def apply_rule(n, c, t): 19 | result = (sum(n) / len(n)) * (3 / 2) 20 | frac, whole = math.modf(result) 21 | return frac 22 | 23 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=150, 24 | apply_rule=apply_rule) 25 | 26 | cpl.plot(cellular_automaton) 27 | 28 | .. image:: _static/continuous_ca.png 29 | :width: 400 30 | 31 | **References:** 32 | 33 | *Wolfram, S. (2002). A New Kind of Science (page 157). Champaign, IL: Wolfram Media.* 34 | -------------------------------------------------------------------------------- /tests/resources/sandpile_prior_history.ca: -------------------------------------------------------------------------------- 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, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 6, 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, 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, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 3, 1, 0, 0, 0}, {0, 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, 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, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 1, 4, 1, 0, 0, 0}, {0, 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, 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, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 0, 0, 0, 0}, {0, 0, 0, 0, 2, 0, 2, 0, 0, 0}, {0, 0, 0, 0, 0, 2, 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}}} -------------------------------------------------------------------------------- /cellpylib/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | CellPyLib 3 | ========= 4 | 5 | CellPyLib is a library for working with Cellular Automata. 6 | 7 | For complete documentation, see: https://cellpylib.org 8 | """ 9 | 10 | __version__ = "2.4.0" 11 | 12 | from .ca_functions import BaseRule, AsynchronousRule, ReversibleRule, binary_rule, init_simple, nks_rule, \ 13 | totalistic_rule, plot_multiple, bits_to_int, int_to_bits, init_random, plot, evolve, until_fixed_point, NKSRule, \ 14 | BinaryRule, TotalisticRule, evolve_block 15 | 16 | from .rule_tables import random_rule_table, table_walk_through, table_rule 17 | 18 | from .entropy import mutual_information, average_cell_entropy, average_mutual_information, shannon_entropy, \ 19 | joint_shannon_entropy 20 | 21 | from .ca_functions2d import evolve2d, plot2d, plot2d_slice, plot2d_animate, plot2d_spacetime, init_simple2d, \ 22 | init_random2d, game_of_life_rule, evolve2d_block 23 | 24 | from .bien import binary_derivative, cyclic_binary_derivative, ktbien, tbien, bien 25 | 26 | from .apen import apen 27 | 28 | from .hopfield_net import HopfieldNet 29 | 30 | from .ctrbl_rule import CTRBLRule 31 | 32 | from .langtons_loop import LangtonsLoop 33 | 34 | from .sdsr_loop import SDSRLoop 35 | 36 | from .evoloop import Evoloop 37 | 38 | from .sandpile import Sandpile 39 | -------------------------------------------------------------------------------- /.github/workflows/python-app.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies and run tests with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Testing - master 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-20.04 16 | strategy: 17 | matrix: 18 | include: 19 | - python-version: 3.6 20 | numpy-version: 1.15.4 21 | matplotlib-version: 3.0.2 22 | - python-version: 3.7 23 | numpy-version: 1.16.6 24 | matplotlib-version: 3.4.3 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | - name: Set up Python ${{ matrix.python-version }} 29 | uses: actions/setup-python@v2 30 | with: 31 | python-version: ${{ matrix.python-version }} 32 | - name: Install dependencies 33 | run: | 34 | python -m pip install --upgrade pip 35 | pip install pytest 36 | pip install numpy==${{ matrix.numpy-version }} 37 | pip install matplotlib==${{ matrix.matplotlib-version }} 38 | - name: Test with pytest 39 | run: | 40 | pytest 41 | 42 | -------------------------------------------------------------------------------- /tests/resources/rule60_sequential_simple_init.ca: -------------------------------------------------------------------------------- 1 | {{0, 0, 0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 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, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0}} 2 | -------------------------------------------------------------------------------- /tests/resources/rule90_sequential_simple_init.ca: -------------------------------------------------------------------------------- 1 | {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0}, {0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0}, {0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0}, {0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0}, {0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0}} 2 | -------------------------------------------------------------------------------- /doc/gol.rst: -------------------------------------------------------------------------------- 1 | Conway's Game of Life 2 | --------------------- 3 | 4 | Conway's Game of Life is a very famous 2D Cellular Automaton. It uses a simple rule to give rise to a complex system 5 | that is capable of universal computation, in addition to its ability to entertain and fascinate. 6 | 7 | CellPyLib has a built-in function, :py:func:`~cellpylib.ca_functions2d.game_of_life_rule`, that can be used to produce 8 | the Game of Life 2D CA: 9 | 10 | .. code-block:: 11 | 12 | import cellpylib as cpl 13 | 14 | # Glider 15 | cellular_automaton = cpl.init_simple2d(60, 60) 16 | cellular_automaton[:, [28,29,30,30], [30,31,29,31]] = 1 17 | 18 | # Blinker 19 | cellular_automaton[:, [40,40,40], [15,16,17]] = 1 20 | 21 | # Light Weight Space Ship (LWSS) 22 | cellular_automaton[:, [18,18,19,20,21,21,21,21,20], [45,48,44,44,44,45,46,47,48]] = 1 23 | 24 | # evolve the cellular automaton for 60 time steps 25 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=60, neighbourhood='Moore', 26 | apply_rule=cpl.game_of_life_rule, memoize='recursive') 27 | 28 | cpl.plot2d_animate(cellular_automaton) 29 | 30 | .. image:: _static/game_of_life.gif 31 | :width: 350 32 | 33 | **References:** 34 | 35 | *Conway, J. (1970). The game of life. Scientific American, 223(4), 4.* 36 | -------------------------------------------------------------------------------- /doc/collatz.rst: -------------------------------------------------------------------------------- 1 | The Collatz Conjecture 2 | ---------------------- 3 | 4 | The Collatz conjecture states that by iteratively applying a particular rule to successive numbers, beginning from any 5 | number, the result will eventually be `1`. 6 | 7 | Below is an example of a rule that demonstrates the Collatz conjecture, and also demonstrates the use of a callable for 8 | the ``timesteps`` argument of the :py:func:`~cellpylib.ca_functions.evolve` function, since, in principle, it isn't 9 | known how many iterations are required before the system evolves to a state consisting of the value `1`. 10 | 11 | .. code-block:: 12 | 13 | import cellpylib as cpl 14 | import numpy as np 15 | 16 | initial = np.array([[17]], dtype=np.int) 17 | 18 | def activity_rule(n, c, t): 19 | n = n[1] 20 | if n % 2 == 0: 21 | # number is even 22 | return n / 2 23 | else: 24 | return 3*n + 1 25 | 26 | cellular_automaton = cpl.evolve(initial, apply_rule=activity_rule, 27 | timesteps=lambda ca, t: True if ca[-1][0] != 1 else False) 28 | 29 | print([i[0] for i in cellular_automaton]) 30 | 31 | The program above should print: 32 | 33 | .. code-block:: 34 | 35 | [17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1] 36 | 37 | **References:** 38 | 39 | https://en.wikipedia.org/wiki/Collatz_conjecture 40 | -------------------------------------------------------------------------------- /demos/rule_table_walkthrough_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | 3 | rule_table, actual_lambda, quiescent_state = cpl.random_rule_table(lambda_val=0.0, k=4, r=2, 4 | strong_quiescence=True, isotropic=True) 5 | 6 | lambda_vals = [0.15, 0.37, 0.75] 7 | ca_list = [] 8 | titles = [] 9 | for i in range(0, 3): 10 | # cellular_automaton = cpl.init_simple(128, val=1) 11 | cellular_automaton = cpl.init_random(128, k=4) 12 | 13 | rule_table, actual_lambda = cpl.table_walk_through(rule_table, lambda_vals[i], k=4, r=2, 14 | quiescent_state=quiescent_state, strong_quiescence=True) 15 | print(actual_lambda) 16 | 17 | # evolve the cellular automaton for 200 time steps 18 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=200, 19 | apply_rule=lambda n, c, t: cpl.table_rule(n, rule_table), r=2) 20 | 21 | ca_list.append(cellular_automaton) 22 | avg_cell_entropy = cpl.average_cell_entropy(cellular_automaton) 23 | avg_mutual_information = cpl.average_mutual_information(cellular_automaton) 24 | titles.append(r'$\lambda$ = %s, $\widebar{H}$ = %s, $\widebar{I}$ = %s' % 25 | (lambda_vals[i], "{:.4}".format(avg_cell_entropy), "{:.4}".format(avg_mutual_information))) 26 | 27 | cpl.plot_multiple(ca_list, titles) 28 | -------------------------------------------------------------------------------- /demos/rule122R_entropy_demo.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | import cellpylib as cpl 5 | 6 | # NKS page 442 - Rule 122R 7 | cellular_automaton = np.array([[0]*40 + [1]*20 + [0]*40]) 8 | rule = cpl.ReversibleRule(cellular_automaton[0], 122) 9 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=1000, apply_rule=rule) 10 | 11 | timestep = [] 12 | bientropies = [] 13 | shannon_entropies = [] 14 | average_cell_entropies = [] 15 | apentropies = [] 16 | for i, c in enumerate(cellular_automaton): 17 | timestep.append(i) 18 | bit_string = ''.join([str(x) for x in c]) 19 | bientropies.append(cpl.ktbien(bit_string)) 20 | shannon_entropies.append(cpl.shannon_entropy(bit_string)) 21 | average_cell_entropies.append(cpl.average_cell_entropy(cellular_automaton[:i+1])) 22 | apentropies.append(cpl.apen(bit_string, m=1, r=0)) 23 | print("%s, %s, %s, %s" % (i, bientropies[-1], shannon_entropies[-1], apentropies[-1])) 24 | 25 | plt.figure(1) 26 | plt.title("KTBiEn") 27 | plt.plot(timestep, bientropies) 28 | 29 | plt.figure(2) 30 | plt.title("Shannon Information") 31 | plt.plot(timestep, shannon_entropies) 32 | 33 | plt.figure(3) 34 | plt.title("ApEn") 35 | plt.plot(timestep, apentropies) 36 | 37 | plt.figure(4) 38 | plt.title("Avg. Cell (Shannon) Entropy") 39 | plt.plot(timestep, average_cell_entropies) 40 | 41 | plt.figure(5) 42 | cpl.plot(cellular_automaton) 43 | 44 | 45 | -------------------------------------------------------------------------------- /doc/langtons_loop.rst: -------------------------------------------------------------------------------- 1 | Langton's Loops 2 | --------------- 3 | 4 | In 1984, Christopher Langton described a type of 2-dimensional cellular automaton that exhibits a self-replicating 5 | dynamic loop structure. A branch of Artificial Life research developed from this work, resulting in better insight into 6 | self-replicating processes, which has obvious relevance to Biology and living systems. 7 | 8 | Below is an example of Langton's loop. This example makes use of the :py:class:`~cellpylib.langtons_loop.LangtonsLoop` 9 | class, which is an extension of the :py:class:`~cellpylib.ctrbl_rule.CTRBLRule` class, which can be used for 10 | constructing any kind of rule based on a von Neumann neighbourhood which considers the Center, Top, Right, Bottom and 11 | Left cells explicitly. 12 | 13 | .. code-block:: 14 | 15 | import cellpylib as cpl 16 | 17 | langtons_loop = cpl.LangtonsLoop() 18 | 19 | # the initial conditions consist of a single loop 20 | cellular_automaton = langtons_loop.init_loops(1, (75, 75), [40], [25]) 21 | 22 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=500, 23 | apply_rule=langtons_loop, memoize="recursive") 24 | 25 | cpl.plot2d_animate(cellular_automaton) 26 | 27 | .. image:: _static/langtons_loops.gif 28 | :width: 500 29 | 30 | **References:** 31 | 32 | *Langton, C. G. (1984). Self-reproduction in Cellular Automata. Physica D: Nonlinear Phenomena, 10(1-2), 135-144.* 33 | 34 | https://en.wikipedia.org/wiki/Langton%27s_loops 35 | -------------------------------------------------------------------------------- /demos/wireworld_diodes_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | from matplotlib.colors import ListedColormap 4 | 5 | 6 | def wireworld_rule(n, c, t): 7 | current_activity = n[1][1] 8 | if current_activity == 0: # empty 9 | return 0 10 | if current_activity == 1: # electron head 11 | return 2 12 | if current_activity == 2: # electron tail 13 | return 3 14 | if current_activity == 3: # conductor 15 | electron_head_count = np.count_nonzero(n == 1) 16 | return 1 if electron_head_count == 1 or electron_head_count == 2 else 3 17 | 18 | 19 | cellular_automata = np.array([[ 20 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 21 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 22 | [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0], 23 | [2, 1, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3], 24 | [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0], 25 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 26 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 27 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 28 | [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0], 29 | [2, 1, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3], 30 | [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0], 31 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 32 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 33 | ]]) 34 | 35 | cellular_automata = cpl.evolve2d(cellular_automata, timesteps=15, 36 | apply_rule=wireworld_rule, neighbourhood="Moore") 37 | 38 | cpl.plot2d_animate(cellular_automata, show_grid=True, show_margin=False, scale=0.3, 39 | colormap=ListedColormap(["black", "blue", "red", "yellow"])) -------------------------------------------------------------------------------- /doc/colors.rst: -------------------------------------------------------------------------------- 1 | Varying the Number of Colors 2 | ---------------------------- 3 | 4 | The number of states, or colors, that a cell can adopt is given by `k`. For example, a binary cellular automaton, in 5 | which a cell can assume only values of 0 and 1, has `k = 2`. CellPyLib supports any value of `k`. A built-in function, 6 | :py:func:`~cellpylib.ca_functions.totalistic_rule`, is an implementation of the Totalistic cellular automaton rule, as 7 | described in Stephen Wolfram's `A New Kind of Science`. The code snippet below illustrates using this rule. A value of 8 | `k` of 3 is used, but any value between (and including) 2 and 36 is currently supported. The rule number is given in 9 | base 10 but is interpreted as the rule in base `k` (thus rule 777 corresponds to '1001210' when `k = 3`). 10 | 11 | .. code-block:: 12 | 13 | import cellpylib as cpl 14 | 15 | cellular_automaton = cpl.init_simple(200) 16 | 17 | # evolve the CA, using totalistic rule 777 for a 3-color CA 18 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100, 19 | apply_rule=lambda n, c, t: cpl.totalistic_rule(n, k=3, rule=777)) 20 | 21 | cpl.plot(cellular_automaton) 22 | 23 | .. image:: _static/tot3_rule777.png 24 | :width: 400 25 | 26 | Alternatively, the :py:class:`~cellpylib.ca_functions.TotalisticRule` class can be used: 27 | 28 | .. code-block:: 29 | 30 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100, 31 | apply_rule=cpl.TotalisticRule(k=3, rule=777)) 32 | 33 | **References:** 34 | 35 | *Wolfram, S. (2002). A New Kind of Science. Champaign, IL: Wolfram Media.* 36 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import re 3 | 4 | INIT_FILE = "cellpylib/__init__.py" 5 | 6 | with open(INIT_FILE) as fid: 7 | file_contents = fid.read() 8 | match = re.search(r"^__version__\s?=\s?['\"]([^'\"]*)['\"]", file_contents, re.M) 9 | if match: 10 | version = match.group(1) 11 | else: 12 | raise RuntimeError("Unable to find version string in %s" % INIT_FILE) 13 | 14 | setup(name="cellpylib", 15 | version=version, 16 | description="CellPyLib, A library for working with Cellular Automata, for Python.", 17 | long_description="CellPyLib is a library for working with Cellular Automata, for Python. " 18 | "Currently, only 1- and 2-dimensional k-color cellular automata with " 19 | "periodic boundary conditions are supported. The size of the " 20 | "neighbourhood can be adjusted. ", 21 | license="Apache License 2.0", 22 | classifiers=[ 23 | 'Development Status :: 5 - Production/Stable', 24 | 'Intended Audience :: Developers', 25 | 'Topic :: Scientific/Engineering :: Artificial Intelligence', 26 | 'License :: OSI Approved :: Apache Software License', 27 | 'Programming Language :: Python :: 3.6', 28 | ], 29 | url='http://github.com/lantunes/cellpylib', 30 | author="Luis M. Antunes", 31 | author_email="lantunes@gmail.com", 32 | packages=["cellpylib"], 33 | keywords=["cellular automata", "complexity", "complex systems", "computation", "non-linear dynamics"], 34 | python_requires='>3.6', 35 | install_requires=["numpy >= 1.15.4", "matplotlib >= 3.0.2"]) 36 | -------------------------------------------------------------------------------- /cellpylib/apen.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | def apen(sequence, m=1, r=0): 5 | """ 6 | Calculates the Approximate Entropy, or ApEn, of the given sequence, as described in: 7 | 8 | .. code-block:: text 9 | 10 | Pincus, S. M.; Gladstone, I. M.; Ehrenkranz, R. A. (1991). 11 | "A Regularity Statistic For Medical Data Analysis". 12 | Journal of Clinical Monitoring and Computing. 7 (4): 335-345. 13 | 14 | The implementation here is based on the Python implementation described in: 15 | https://en.wikipedia.org/wiki/Approximate_entropy 16 | 17 | :param sequence: a string of whole numbers, such as '012301', or an array of whole numbers, such as [0,1,2,3,0,1], 18 | or a numpy array of whole numbers 19 | 20 | :param m: the length of compared runs of data 21 | 22 | :param r: a filtering level 23 | 24 | :return: a real number, representing the approximate entropy (ApEn) for the given sequence 25 | """ 26 | if type(sequence) is str: 27 | U = np.array([int(x) for x in sequence]) 28 | elif type(sequence) is list: 29 | U = np.array(sequence) 30 | elif type(sequence) is np.ndarray: 31 | U = sequence 32 | else: 33 | raise TypeError("unsupported sequence type: %s" % type(sequence)) 34 | 35 | N = len(U) 36 | 37 | def maximum_distance(x_i, x_j): 38 | return max([abs(ua - va) for ua, va in zip(x_i, x_j)]) 39 | 40 | def phi(m): 41 | x = [[U[j] for j in range(i, i + m - 1 + 1)] for i in range(N - m + 1)] 42 | C = [len([1 for x_j in x if maximum_distance(x_i, x_j) <= r]) / (N - m + 1.0) for x_i in x] 43 | return (1 / (N - m + 1.0)) * sum(np.log(C)) 44 | 45 | return abs(phi(m + 1) - phi(m)) 46 | -------------------------------------------------------------------------------- /doc/reference.rst: -------------------------------------------------------------------------------- 1 | Reference 2 | ========= 3 | 4 | cellpylib.apen 5 | -------------- 6 | 7 | .. automodule:: cellpylib.apen 8 | :members: 9 | :undoc-members: 10 | 11 | cellpylib.bien 12 | -------------- 13 | 14 | .. automodule:: cellpylib.bien 15 | :members: 16 | :undoc-members: 17 | 18 | cellpylib.ca_functions 19 | ---------------------- 20 | 21 | .. automodule:: cellpylib.ca_functions 22 | :members: 23 | :undoc-members: 24 | 25 | cellpylib.ca_functions2d 26 | ------------------------ 27 | 28 | .. automodule:: cellpylib.ca_functions2d 29 | :members: 30 | :undoc-members: 31 | 32 | cellpylib.ctrbl_rule 33 | -------------------- 34 | 35 | .. automodule:: cellpylib.ctrbl_rule 36 | :members: 37 | :undoc-members: 38 | 39 | cellpylib.entropy 40 | ----------------- 41 | 42 | .. automodule:: cellpylib.entropy 43 | :members: 44 | :undoc-members: 45 | 46 | cellpylib.hopfield_net 47 | ---------------------- 48 | 49 | .. automodule:: cellpylib.hopfield_net 50 | :members: 51 | :undoc-members: 52 | 53 | cellpylib.langtons_loop 54 | ----------------------- 55 | 56 | .. automodule:: cellpylib.langtons_loop 57 | :members: 58 | :undoc-members: 59 | 60 | cellpylib.sdsr_loop 61 | ----------------------- 62 | 63 | .. automodule:: cellpylib.sdsr_loop 64 | :members: 65 | :undoc-members: 66 | 67 | cellpylib.evoloop 68 | ----------------------- 69 | 70 | .. automodule:: cellpylib.evoloop 71 | :members: 72 | :undoc-members: 73 | 74 | cellpylib.rule_tables 75 | --------------------- 76 | 77 | .. automodule:: cellpylib.rule_tables 78 | :members: 79 | :undoc-members: 80 | 81 | cellpylib.sandpile 82 | ----------------------- 83 | 84 | .. automodule:: cellpylib.sandpile 85 | :members: 86 | :undoc-members: 87 | -------------------------------------------------------------------------------- /doc/twodim.rst: -------------------------------------------------------------------------------- 1 | Two-Dimensional CA 2 | ------------------ 3 | 4 | CellPyLib supports 2-dimensional cellular automata with periodic boundary conditions. The number of states, `k`, can be 5 | any whole number. The neighbourhood radius, `r`, can also be any whole number, and both Moore and von Neumann 6 | neighbourhood types are supported. The following snippet demonstrates creating a 2D totalistic cellular automaton: 7 | 8 | .. code-block:: 9 | 10 | import cellpylib as cpl 11 | 12 | # initialize a 60x60 2D cellular automaton 13 | cellular_automaton = cpl.init_simple2d(60, 60) 14 | 15 | # evolve the cellular automaton for 30 time steps, 16 | # applying totalistic rule 126 to each cell with a Moore neighbourhood 17 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=30, neighbourhood='Moore', 18 | apply_rule=lambda n, c, t: cpl.totalistic_rule(n, k=2, rule=126)) 19 | 20 | cpl.plot2d(cellular_automaton) 21 | 22 | .. image:: _static/tot_rule126_2d_moore.png 23 | :width: 250 24 | 25 | The image above represents the state at the final timestep. However, the state of the CA at any timestep can be 26 | visualized using the :py:class:`~cellpylib.ca_functions2d.plot2d` ``timestep`` argument. For example, in the code 27 | snippet below, the state at the 10th timestep is plotted: 28 | 29 | .. code-block:: 30 | 31 | cpl.plot2d(cellular_automaton, timestep=10) 32 | 33 | .. image:: _static/tot_rule126_2d_moore_t10.png 34 | :width: 255 35 | 36 | Note that 2D CA can also be animated, so that the entire evolution of the CA can be visualized, using the 37 | :py:class:`~cellpylib.ca_functions2d.plot2d_animate` function: 38 | 39 | .. code-block:: 40 | 41 | cpl.plot2d_animate(cellular_automaton) 42 | 43 | .. image:: _static/tot_rule126_2d_moore.gif 44 | :width: 350 45 | -------------------------------------------------------------------------------- /tests/test_ctrbl_rule.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pytest 3 | import cellpylib as cpl 4 | import numpy as np 5 | 6 | 7 | class TestCTRBLRule(unittest.TestCase): 8 | 9 | def test_rotations(self): 10 | ctrbl = cpl.CTRBLRule({ 11 | (0, 1, 2, 3, 4): "a", 12 | (5, 6, 7, 8, 9): "b" 13 | }) 14 | self.assertEqual(ctrbl.rule_table, { 15 | (0, 1, 2, 3, 4): "a", 16 | (5, 6, 7, 8, 9): "b" 17 | }) 18 | 19 | ctrbl = cpl.CTRBLRule({ 20 | (0, 1, 2, 3, 4): "a", 21 | (5, 6, 7, 8, 9): "b" 22 | }, add_rotations=True) 23 | self.assertEqual(ctrbl.rule_table, { 24 | (0, 1, 2, 3, 4): "a", 25 | (0, 4, 1, 2, 3): "a", 26 | (0, 3, 4, 1, 2): "a", 27 | (0, 2, 3, 4, 1): "a", 28 | (5, 6, 7, 8, 9): "b", 29 | (5, 9, 6, 7, 8): "b", 30 | (5, 8, 9, 6, 7): "b", 31 | (5, 7, 8, 9, 6): "b" 32 | }) 33 | 34 | def test_rule(self): 35 | ctrbl_rule = cpl.CTRBLRule({ 36 | (0, 1, 2, 3, 4): "a", 37 | (5, 6, 7, 8, 9): "b" 38 | }) 39 | 40 | n = np.array([ 41 | [0, 1, 0], 42 | [4, 0, 2], 43 | [0, 3, 0] 44 | ]) 45 | activity = ctrbl_rule(n, 4, 1) 46 | self.assertEqual("a", activity) 47 | 48 | def test_activity_rule_does_not_exist(self): 49 | ctrbl_rule = cpl.CTRBLRule({ 50 | (5, 6, 7, 8, 9): "b" 51 | }) 52 | 53 | with pytest.raises(Exception) as e: 54 | n = np.array([ 55 | [0, 1, 0], 56 | [4, 0, 2], 57 | [0, 3, 0] 58 | ]) 59 | ctrbl_rule(n, 4, 1) 60 | self.assertEqual(e.value.args, ("neighbourhood state (0, 1, 2, 3, 4) not in rule table",)) 61 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. _contents: 2 | 3 | CellPyLib 4 | ========= 5 | 6 | CellPyLib is a Python library for working with Cellular Automata (CA). It provides a concise and simple interface for 7 | defining and analyzing 1- and 2-dimensional CA. The CA can consist of discrete or continuous states. Neighbourhood 8 | radii are adjustable, and in the 2-dimensional case, both Moore and von Neumann neighbourhoods are supported. 9 | 10 | With CellPyLib, it is trivial to create Elementary CA, and CA with totalistic rules. These rules are provided as part 11 | of the library. Additionally, the library provides a means for creating asynchronous CA, and reversible CA. Finally, an 12 | implementation of C. G. Langton's approach for creating CA rules using the lambda value is provided, allowing for the 13 | exploration of complex systems, phase transitions and emergent computation. 14 | 15 | Utility functions for plotting and viewing the evolved CA are provided. These tools make it easy to visualize the 16 | results of CA evolution. Moreover, utility functions for computing the information-theoretic properties of CA, such as 17 | the Shannon entropy and mutual information, are provided. 18 | 19 | .. toctree:: 20 | :caption: Using CellPyLib 21 | :maxdepth: 5 22 | 23 | installation 24 | working 25 | additional 26 | citing 27 | 28 | .. toctree:: 29 | :caption: Tutorials 30 | :maxdepth: 5 31 | 32 | eca 33 | neighbourhood 34 | colors 35 | complexity 36 | continuous 37 | collatz 38 | twodim 39 | gol 40 | wireworld 41 | fredkin 42 | hopfield 43 | langtons_loop 44 | sdsr_evoloop 45 | sandpile 46 | block_ca 47 | 48 | .. toctree:: 49 | :caption: API Docs and License 50 | :maxdepth: 5 51 | 52 | reference 53 | Source 54 | license 55 | 56 | Indices and tables 57 | ================== 58 | 59 | * :ref:`genindex` 60 | * :ref:`modindex` 61 | * :ref:`search` -------------------------------------------------------------------------------- /demos/wireworld_xor_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | from matplotlib.colors import ListedColormap 4 | 5 | 6 | def wireworld_rule(n, c, t): 7 | current_activity = n[1][1] 8 | if current_activity == 0: # empty 9 | return 0 10 | if current_activity == 1: # electron head 11 | return 2 12 | if current_activity == 2: # electron tail 13 | return 3 14 | if current_activity == 3: # conductor 15 | electron_head_count = np.count_nonzero(n == 1) 16 | return 1 if electron_head_count == 1 or electron_head_count == 2 else 3 17 | 18 | 19 | cellular_automata = np.array([[ 20 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 21 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 22 | [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 23 | [0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0], 24 | [0, 0, 0, 3, 1, 2, 3, 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], 25 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 3, 0, 0, 0, 0], 26 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 3, 3, 3, 2], 27 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0], 28 | [0, 0, 0, 3, 3, 2, 1, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0], 29 | [0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0], 30 | [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 31 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 32 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 33 | ]]) 34 | 35 | cellular_automata = cpl.evolve2d(cellular_automata, timesteps=25, 36 | apply_rule=wireworld_rule, neighbourhood="Moore") 37 | 38 | cpl.plot2d_animate(cellular_automata, show_grid=True, show_margin=False, scale=0.3, 39 | colormap=ListedColormap(["black", "blue", "red", "yellow"])) 40 | -------------------------------------------------------------------------------- /doc/eca.rst: -------------------------------------------------------------------------------- 1 | Elementary CA 2 | ------------- 3 | 4 | Elementary CA (ECA) were studied extensively by Stephen Wolfram in his book `A New Kind of Science`. These are perhaps 5 | the simplest kind of CA that one can conceive, with 2 states and a neighbourhood consisting of 3 cells (i.e. a radius of 6 | 1). There are a total of 256 ECA (i.e. there are 256 different ways of specifying a rule table for the 8 possible binary 7 | states of a neighbourhood). It is thus possible to exhaustively explore this space of discrete dynamical systems. As 8 | such, it is one of the most studied and well understood type of CA. 9 | 10 | CellPyLib supports the creation of ECA through the :py:func:`~cellpylib.ca_functions.nks_rule` function. This function 11 | accepts as a parameter the rule number, using the convention introduced by Stephen Wolfram in his book `A New Kind of 12 | Science`. The rule number uniquely identifies an ECA. For example, Rule 30 is a famous ECA. Its behaviour is very 13 | complex, and it remains poorly understood. Questions regarding its evolution remain unanswered at the time of this 14 | writing, and there is even a `Rule 30 Prize `_, offered to those who can answer 15 | fundamental questions about this fascinating dynamical system. 16 | 17 | The following code snippet demonstrates creating and visualizing Rule 30 with CellPyLib: 18 | 19 | .. code-block:: 20 | 21 | import cellpylib as cpl 22 | 23 | cellular_automaton = cpl.init_simple(200) 24 | 25 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100, memoize=True, 26 | apply_rule=lambda n, c, t: cpl.nks_rule(n, 30)) 27 | cpl.plot(cellular_automaton) 28 | 29 | .. image:: _static/rule30.png 30 | :width: 400 31 | 32 | Alternatively, the :py:class:`~cellpylib.ca_functions.NKSRule` class can be used: 33 | 34 | .. code-block:: 35 | 36 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100, memoize=True, 37 | apply_rule=cpl.NKSRule(30)) 38 | 39 | **References:** 40 | 41 | *Wolfram, S. (2002). A New Kind of Science. Champaign, IL: Wolfram Media.* 42 | -------------------------------------------------------------------------------- /doc/neighbourhood.rst: -------------------------------------------------------------------------------- 1 | Varying the Neighbourhood Size 2 | ------------------------------ 3 | 4 | The size of the cell neighbourhood can be varied by setting the parameter ``r`` when calling the 5 | :py:func:`~cellpylib.ca_functions.evolve` function. The value of ``r`` represents the number of cells to the left and 6 | to the right of the cell under consideration. Thus, to get a neighbourhood size of 3, ``r`` should be 1, and to get a 7 | neighbourhood size of 7, ``r`` should be 3. As an example, consider the work of M. Mitchell et al. involving the 8 | creation (discovery) of a cellular automaton that solves the density classification problem: if the initial random 9 | binary vector contains more than 50% of 1s, then a cellular automaton that solves this problem will give rise to a 10 | vector that contains only 1s after a fixed number of time steps, and likewise for the case of 0s. A very effective 11 | cellular automaton that solves this problem most of the time was found using a Genetic Algorithm. 12 | 13 | .. code-block:: 14 | 15 | import cellpylib as cpl 16 | 17 | cellular_automaton = cpl.init_random(149) 18 | 19 | # Mitchell et al. discovered this rule using a Genetic Algorithm 20 | rule_number = 6667021275756174439087127638698866559 21 | 22 | # evolve the CA, setting r to 3, for a neighbourhood size of 7 23 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=149, 24 | apply_rule=lambda n, c, t: cpl.binary_rule(n, rule_number), 25 | r=3) 26 | 27 | cpl.plot(cellular_automaton) 28 | 29 | .. image:: _static/density_classification.png 30 | :width: 400 31 | 32 | Alternatively, the :py:class:`~cellpylib.ca_functions.BinaryRule` class can be used: 33 | 34 | .. code-block:: 35 | 36 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=149, 37 | apply_rule=cpl.BinaryRule(rule_number), r=3) 38 | 39 | **References:** 40 | 41 | *Melanie Mitchell, James P. Crutchfield, and Rajarshi Das, "Evolving Cellular Automata with Genetic Algorithms: 42 | A Review of Recent Work", In Proceedings of the First International Conference on Evolutionary Computation and Its 43 | Applications (EvCA'96), Russian Academy of Sciences (1996).* -------------------------------------------------------------------------------- /tests/resources/info.txt: -------------------------------------------------------------------------------- 1 | The cellular automata in the .ca-ending files in this directory were obtained from: 2 | 3 | rule0_simple_init.ca: 4 | http://atlas.wolfram.com/01/01/0/01_01_16_0.html 5 | 6 | rule0_random_init.ca: 7 | http://atlas.wolfram.com/01/01/0/01_01_21_0.html 8 | 9 | rule30_simple_init.ca: 10 | http://atlas.wolfram.com/01/01/30/01_01_16_30.html 11 | 12 | rule30_random_init.ca: 13 | http://atlas.wolfram.com/01/01/30/01_01_21_30.html 14 | 15 | rule126_simple_init.ca: 16 | http://atlas.wolfram.com/01/01/126/01_01_16_126.html 17 | 18 | rule126_random_init.ca: 19 | http://atlas.wolfram.com/01/01/126/01_01_21_126.html 20 | 21 | rule225_simple_init.ca: 22 | http://atlas.wolfram.com/01/01/225/01_01_16_225.html 23 | 24 | rule225_random_init.ca: 25 | http://atlas.wolfram.com/01/01/225/01_01_21_225.html 26 | 27 | rule255_simple_init.ca: 28 | http://atlas.wolfram.com/01/01/255/01_01_16_255.html 29 | 30 | rule255_random_init.ca: 31 | http://atlas.wolfram.com/01/01/255/01_01_21_255.html 32 | 33 | tot3_rule777_random_init.ca: 34 | http://atlas.wolfram.com/01/02/777/01_02_21_777.html 35 | 36 | tot3_rule777_simple_init.ca: 37 | http://atlas.wolfram.com/01/02/777/01_02_16_777.html 38 | 39 | tot4_rule107396_random_init.ca: 40 | http://atlas.wolfram.com/01/03/107396/01_03_21_107396.html 41 | 42 | tot4_rule107396_simple_init.ca: 43 | http://atlas.wolfram.com/01/03/107396/01_03_16_107396.html 44 | 45 | tot_rule26_2d_n5_simple_init.ca: 46 | CellularAutomaton[<|"TotalisticCode" -> 26, "Dimension" -> 2,"Neighborhood" -> 5|>, PadLeft[{{1}}, {60, 60}, 0, Floor[{60, 60}/2]], 30] 47 | 48 | tot_rule126_2d_n9_simple_init.ca: 49 | CellularAutomaton[<|"TotalisticCode" -> 126, "Dimension" -> 2,"Neighborhood" -> 9|>, PadLeft[{{1}}, {60, 60}, 0, Floor[{60, 60}/2]], 30] 50 | 51 | rule150R_simple_init.ca: 52 | CellularAutomaton[{Mod[Total[Flatten[#]], 2] &, {}, {{-1, 0}, {0, -1}, {0, 0}, {0, 1}}, 2}, {{{1}, {1}}, 0}, 100] 53 | 54 | rule60_sequential_simple_init.ca: 55 | NKS Notes on Chapter 9, section 10: "Sequential cellular automata" 56 | http://www.wolframscience.com/nks/notes-9-10--sequential-cellular-automata/ 57 | 58 | rule90_sequential_simple_init.ca: 59 | NKS Notes on Chapter 9, section 10: "Sequential cellular automata" 60 | http://www.wolframscience.com/nks/notes-9-10--sequential-cellular-automata/ 61 | -------------------------------------------------------------------------------- /demos/hopfield_net_demo.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import cellpylib as cpl 4 | 5 | """ 6 | Based on: http://neupy.com/2015/09/20/discrete_hopfield_network.html 7 | """ 8 | # patterns for training 9 | zero = [ 10 | 0, 1, 1, 1, 0, 11 | 1, 0, 0, 0, 1, 12 | 1, 0, 0, 0, 1, 13 | 1, 0, 0, 0, 1, 14 | 1, 0, 0, 0, 1, 15 | 0, 1, 1, 1, 0, 16 | 0, 0, 0, 0, 0] # we add this last row so that we get an odd number of 17 | # total cells, so that we can specify a radius that includes exactly all the cells 18 | one = [ 19 | 0, 1, 1, 0, 0, 20 | 0, 0, 1, 0, 0, 21 | 0, 0, 1, 0, 0, 22 | 0, 0, 1, 0, 0, 23 | 0, 0, 1, 0, 0, 24 | 0, 0, 1, 0, 0, 25 | 0, 0, 0, 0, 0] 26 | 27 | two = [ 28 | 1, 1, 1, 0, 0, 29 | 0, 0, 0, 1, 0, 30 | 0, 0, 0, 1, 0, 31 | 0, 1, 1, 0, 0, 32 | 1, 0, 0, 0, 0, 33 | 1, 1, 1, 1, 1, 34 | 0, 0, 0, 0, 0] 35 | # replace the zeroes with -1 to make these vectors bipolar instead of binary 36 | one = [-1 if x == 0 else x for x in one] 37 | two = [-1 if x == 0 else x for x in two] 38 | zero = [-1 if x == 0 else x for x in zero] 39 | 40 | P = [zero, one, two] 41 | 42 | # patterns to evaluate 43 | half_zero = [ 44 | 0, 1, 1, 1, 0, 45 | 1, 0, 0, 0, 1, 46 | 1, 0, 0, 0, 1, 47 | 0, 0, 0, 0, 0, 48 | 0, 0, 0, 0, 0, 49 | 0, 0, 0, 0, 0, 50 | 0, 0, 0, 0, 0] 51 | 52 | half_one = [ 53 | 0, 0, 1, 0, 0, 54 | 0, 0, 1, 0, 0, 55 | 0, 0, 1, 0, 0, 56 | 0, 0, 1, 0, 0, 57 | 0, 0, 0, 0, 0, 58 | 0, 0, 0, 0, 0, 59 | 0, 0, 0, 0, 0] 60 | 61 | half_two = [ 62 | 0, 0, 0, 0, 0, 63 | 0, 0, 0, 0, 0, 64 | 0, 0, 0, 0, 0, 65 | 0, 1, 1, 0, 0, 66 | 1, 0, 0, 0, 0, 67 | 1, 1, 1, 1, 1, 68 | 0, 0, 0, 0, 0] 69 | half_zero = [-1 if x == 0 else x for x in half_zero] 70 | half_one = [-1 if x == 0 else x for x in half_one] 71 | half_two = [-1 if x == 0 else x for x in half_two] 72 | 73 | cellular_automaton = np.array([half_two]) 74 | 75 | hopfield_net = cpl.HopfieldNet(num_cells=35) 76 | 77 | hopfield_net.train(P) 78 | 79 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=155, 80 | apply_rule=hopfield_net.apply_rule, r=hopfield_net.r) 81 | 82 | cpl.plot(hopfield_net.W) 83 | cpl.plot2d_animate(np.reshape(cellular_automaton, (155, 7, 5))) 84 | -------------------------------------------------------------------------------- /demos/block2d_rotated_ca_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | """ 5 | 2D Block CA from 6 | https://writings.stephenwolfram.com/2023/02/computational-foundations-for-the-second-law-of-thermodynamics/ 7 | """ 8 | 9 | initial_conditions = np.loadtxt('block2d_rotated_initial_conditions.txt', dtype=int) 10 | initial_conditions = np.array([initial_conditions]) 11 | 12 | 13 | def make_block2d_rule(): 14 | base_rules = { 15 | ((0, 0), (0, 0)): ((0, 0), (0, 0)), 16 | ((0, 0), (0, 2)): ((2, 0), (0, 0)), 17 | ((2, 0), (0, 0)): ((0, 0), (0, 2)), 18 | ((0, 0), (2, 0)): ((0, 2), (0, 0)), 19 | ((0, 2), (0, 0)): ((0, 0), (2, 0)), 20 | ((0, 0), (2, 2)): ((2, 2), (0, 0)), 21 | ((2, 2), (0, 0)): ((0, 0), (2, 2)), 22 | ((0, 2), (0, 2)): ((2, 0), (2, 0)), 23 | ((2, 0), (2, 0)): ((0, 2), (0, 2)), 24 | ((0, 2), (2, 0)): ((2, 0), (0, 2)), 25 | ((2, 0), (0, 2)): ((0, 2), (2, 0)), 26 | ((0, 2), (2, 2)): ((2, 2), (2, 0)), 27 | ((2, 2), (2, 0)): ((0, 2), (2, 2)), 28 | ((2, 0), (2, 2)): ((2, 2), (0, 2)), 29 | ((2, 2), (0, 2)): ((2, 0), (2, 2)), 30 | ((2, 2), (2, 2)): ((2, 2), (2, 2)), 31 | # wall rules 32 | ((0, 0), (1, 1)): ((0, 0), (1, 1)), 33 | ((0, 1), (1, 1)): ((0, 1), (1, 1)), 34 | ((0, 2), (1, 1)): ((2, 0), (1, 1)), 35 | ((2, 0), (1, 1)): ((0, 2), (1, 1)), 36 | ((2, 1), (1, 1)): ((2, 1), (1, 1)), 37 | ((2, 2), (1, 1)): ((2, 2), (1, 1)), 38 | ((1, 1), (1, 1)): ((1, 1), (1, 1)), 39 | ((1, 0), (0, 0)): ((1, 0), (0, 0)), 40 | ((1, 0), (0, 2)): ((1, 0), (0, 2)), 41 | } 42 | rules = {} 43 | # add rotations 44 | for r, v in base_rules.items(): 45 | rules[r] = v 46 | for _ in range(3): 47 | r = ((r[1][0], r[0][0]), (r[1][1], r[0][1])) 48 | v = ((v[1][0], v[0][0]), (v[1][1], v[0][1])) 49 | if r not in rules: 50 | rules[r] = v 51 | def _apply_rule(n, t): 52 | n = tuple(tuple(i) for i in n) 53 | return rules[n] 54 | return _apply_rule 55 | 56 | 57 | ca = cpl.evolve2d_block(initial_conditions, block_size=(2, 2), 58 | timesteps=251, apply_rule=make_block2d_rule()) 59 | 60 | cpl.plot2d_animate(ca) 61 | -------------------------------------------------------------------------------- /cellpylib/hopfield_net.py: -------------------------------------------------------------------------------- 1 | from .ca_functions import * 2 | 3 | 4 | class HopfieldNet: 5 | """ 6 | An implementation of the Hopfield network. Due to limitations of this implementation, only an odd number of cells 7 | is supported. 8 | 9 | For more information on Hopfield networks, see: 10 | 11 | .. code-block:: text 12 | 13 | Hopfield, J. J. (1982). Neural networks and physical systems with emergent collective 14 | computational abilities. Proceedings of the national academy of sciences, 79(8), 2554-2558. 15 | """ 16 | def __init__(self, num_cells): 17 | """ 18 | Create an instance of the Hopfield network. 19 | 20 | :param num_cells: the number of cells in this Hopfield network; only an odd number of cells are supported in 21 | this implementation 22 | """ 23 | self.apply_rule = AsynchronousRule(apply_rule=self._rule, num_cells=num_cells) 24 | self._r = num_cells // 2 25 | 26 | def train(self, P): 27 | """ 28 | The training set consists of patterns to be learned by this net. The patterns should be composed of 29 | bipolar ({-1,1}), and not binary ({0,1}), values. 30 | 31 | :param P: the set of training patterns 32 | """ 33 | self._W = np.zeros((len(P[0]), len(P[0])), dtype=np.int32) 34 | for p in P: 35 | for i in range(len(p)): 36 | for j in range(len(p)): 37 | if i ==j: 38 | self._W[i, j] = 0 39 | else: 40 | self._W[i, j] += p[i]*p[j] 41 | 42 | def _rule(self, n, c, t): 43 | left_neighbours = n[0 : len(n)//2] 44 | right_neighbours = n[len(n)//2 + 1 :] 45 | V = 0 46 | for j, left_V in enumerate(left_neighbours): 47 | V += self._W[c - self._r + j, c] * left_V 48 | for j, right_V in enumerate(right_neighbours): 49 | V += self._W[(c + j + 1) % len(n), c] * right_V 50 | return 1 if V >= 0 else -1 51 | 52 | @property 53 | def W(self): 54 | """ 55 | Returns the learned weight matrix. 56 | 57 | :return: the learned weight matrix 58 | """ 59 | return self._W 60 | 61 | @property 62 | def r(self): 63 | """ 64 | The radius of this automaton. 65 | 66 | :return: the radius 67 | """ 68 | return self._r 69 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('..')) 16 | import sphinx_rtd_theme 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'CellPyLib' 21 | copyright = '2023, Luis M. Antunes' 22 | author = 'Luis M. Antunes' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | "sphinx_rtd_theme", 32 | "sphinx.ext.autodoc", 33 | "sphinx-prompt" 34 | ] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # List of patterns, relative to source directory, that match files and 40 | # directories to ignore when looking for source files. 41 | # This pattern also affects html_static_path and html_extra_path. 42 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 43 | 44 | 45 | # -- Options for HTML output ------------------------------------------------- 46 | 47 | # The theme to use for HTML and HTML Help pages. See the documentation for 48 | # a list of builtin themes. 49 | # 50 | html_theme = "sphinx_rtd_theme" 51 | html_copy_source = False 52 | html_show_sourcelink = False 53 | 54 | master_doc = "index" 55 | 56 | # If true, the current module name will be prepended to all description 57 | # unit titles (such as .. function::). 58 | add_module_names = False 59 | 60 | # Add any paths that contain custom static files (such as style sheets) here, 61 | # relative to this directory. They are copied after the builtin static files, 62 | # so a file named "default.css" will overwrite the builtin "default.css". 63 | html_static_path = ['_static'] 64 | # html_logo = '_static/logo.png' 65 | -------------------------------------------------------------------------------- /cellpylib/ctrbl_rule.py: -------------------------------------------------------------------------------- 1 | from .ca_functions import BaseRule 2 | 3 | 4 | class CTRBLRule(BaseRule): 5 | """ 6 | A rule that operates on von Neumann neighbourhoods, taking into account the states of a cell's 7 | neighbours at the top, right, bottom and left positions. Only supports 2D automata with periodic boundaries and a 8 | radius of 1. 9 | """ 10 | def __init__(self, rule_table, add_rotations=False): 11 | """ 12 | Creates a CTRBLRule. 13 | 14 | :param rule_table: a dictionary with keys being a 5-tuple representing the states of the CTRBL cells, and values 15 | being a single value representing the image state (i.e. the state of the Center cell in 16 | the next timestep); all combinations of states must exist, otherwise, if the combination of 17 | states does not exist in the rule table, an exception will be raised 18 | 19 | :param add_rotations: whether rotations in the rule table are implied, and should be included (default is False) 20 | """ 21 | self._rule_table = self._init_rule_table(rule_table, add_rotations) 22 | 23 | def __call__(self, n, c, t): 24 | """ 25 | The CTRBL rule to apply. 26 | 27 | :param n: the neighbourhood 28 | 29 | :param c: the index of the current cell 30 | 31 | :param t: the current timestep 32 | 33 | :return: the activity of the current cell at the next timestep 34 | """ 35 | current_activity = n[1][1] 36 | top = n[0][1] 37 | right = n[1][2] 38 | bottom = n[2][1] 39 | left = n[1][0] 40 | key = (current_activity, top, right, bottom, left) 41 | if key not in self._rule_table: 42 | raise ValueError("neighbourhood state (%s, %s, %s, %s, %s) not in rule table" % key) 43 | return self._rule_table[key] 44 | 45 | @property 46 | def rule_table(self): 47 | """ 48 | The rule table for this CTRBL rule. 49 | 50 | :return: the rule table 51 | """ 52 | return self._rule_table 53 | 54 | @staticmethod 55 | def _init_rule_table(rule_table, add_rotations): 56 | new_rule_table = {} 57 | for rule, image in rule_table.items(): 58 | new_rule_table[rule] = image 59 | if add_rotations: 60 | r = list(rule) 61 | for _ in range(3): 62 | r.insert(1, r.pop(4)) 63 | new_rule_table[tuple(r)] = image 64 | return new_rule_table 65 | -------------------------------------------------------------------------------- /tests/test_hopfield_net.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import cellpylib as cpl 3 | import numpy as np 4 | import os 5 | 6 | THIS_DIR = os.path.dirname(os.path.abspath(__file__)) 7 | 8 | 9 | class TestHopfieldNet(unittest.TestCase): 10 | 11 | def test_hopfield_net(self): 12 | np.random.seed(0) 13 | 14 | # patterns for training 15 | zero = [ 16 | 0, 1, 1, 1, 0, 17 | 1, 0, 0, 0, 1, 18 | 1, 0, 0, 0, 1, 19 | 1, 0, 0, 0, 1, 20 | 1, 0, 0, 0, 1, 21 | 0, 1, 1, 1, 0, 22 | 0, 0, 0, 0, 0] 23 | one = [ 24 | 0, 1, 1, 0, 0, 25 | 0, 0, 1, 0, 0, 26 | 0, 0, 1, 0, 0, 27 | 0, 0, 1, 0, 0, 28 | 0, 0, 1, 0, 0, 29 | 0, 0, 1, 0, 0, 30 | 0, 0, 0, 0, 0] 31 | two = [ 32 | 1, 1, 1, 0, 0, 33 | 0, 0, 0, 1, 0, 34 | 0, 0, 0, 1, 0, 35 | 0, 1, 1, 0, 0, 36 | 1, 0, 0, 0, 0, 37 | 1, 1, 1, 1, 1, 38 | 0, 0, 0, 0, 0] 39 | # replace the zeroes with -1 to make these vectors bipolar instead of binary 40 | one = [-1 if x == 0 else x for x in one] 41 | two = [-1 if x == 0 else x for x in two] 42 | zero = [-1 if x == 0 else x for x in zero] 43 | P = [zero, one, two] 44 | 45 | hopfield_net = cpl.HopfieldNet(num_cells=35) 46 | hopfield_net.train(P) 47 | 48 | expected_weights = self._convert_to_ndarray("hopfield_net_weights.txt") 49 | np.testing.assert_equal(expected_weights, hopfield_net.W) 50 | 51 | expected_activities = self._convert_to_ndarray("hopfield_net.ca") 52 | 53 | half_two = [ 54 | 0, 0, 0, 0, 0, 55 | 0, 0, 0, 0, 0, 56 | 0, 0, 0, 0, 0, 57 | 0, 1, 1, 0, 0, 58 | 1, 0, 0, 0, 0, 59 | 1, 1, 1, 1, 1, 60 | 0, 0, 0, 0, 0] 61 | half_two = [-1 if x == 0 else x for x in half_two] 62 | 63 | cellular_automaton = np.array([half_two]) 64 | 65 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=155, 66 | apply_rule=hopfield_net.apply_rule, r=hopfield_net.r) 67 | 68 | np.testing.assert_equal(expected_activities, cellular_automaton) 69 | 70 | def _convert_to_ndarray(self, filename, dtype=int): 71 | with open(os.path.join(THIS_DIR, 'resources', filename), 'r') as content_file: 72 | content = content_file.read() 73 | content = content.replace('[[', '') 74 | content = content.replace(']]', '') 75 | content = content.replace('[', '') 76 | content = content.replace('],', ';') 77 | content = [[dtype(i) for i in x.split(',')] for x in content.split(';')] 78 | return np.array(content) -------------------------------------------------------------------------------- /doc/complexity.rst: -------------------------------------------------------------------------------- 1 | Measures of Complexity 2 | ---------------------- 3 | 4 | CellPyLib provides various built-in functions which can act as measures of complexity in the cellular automata being 5 | examined. These are the information-theoretic properties known as the Shannon entropy and mutual information, 6 | implemented in the :py:func:`~cellpylib.entropy.average_cell_entropy` and 7 | :py:func:`~cellpylib.entropy.average_mutual_information` functions. 8 | 9 | Average Cell Entropy 10 | ~~~~~~~~~~~~~~~~~~~~ 11 | 12 | Average cell entropy can reveal something about the presence of information within cellular automata dynamics. The 13 | built-in function :py:func:`~cellpylib.entropy.average_cell_entropy` provides the average Shannon entropy per single 14 | cell in a given cellular automaton. The following snippet demonstrates the calculation of the average cell entropy: 15 | 16 | .. code-block:: 17 | 18 | import cellpylib as cpl 19 | 20 | cellular_automaton = cpl.init_random(200) 21 | 22 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=1000, 23 | apply_rule=lambda n, c, t: cpl.nks_rule(n, 30)) 24 | 25 | # calculate the average cell entropy; the value will be ~0.999 in this case 26 | avg_cell_entropy = cpl.average_cell_entropy(cellular_automaton) 27 | 28 | The following plots illustrate how average cell entropy changes as a function of Langton's lambda: 29 | 30 | .. image:: _static/avg_cell_entropy.png 31 | :width: 650 32 | 33 | Average Mutual Information 34 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 35 | 36 | The degree to which a cell state is correlated to its state in the next time step can be described using mutual 37 | information. Ideal levels of correlation are required for effective processing of information. The built-in function 38 | :py:func:`~cellpylib.entropy.average_mutual_information` provides the average mutual information between a cell and 39 | itself in the next time step (the temporal distance can be adjusted). The following snippet demonstrates the calculation 40 | of the average mutual information: 41 | 42 | .. code-block:: 43 | 44 | import cellpylib as cpl 45 | 46 | cellular_automaton = cpl.init_random(200) 47 | 48 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=1000, 49 | apply_rule=lambda n, c, t: cpl.nks_rule(n, 30)) 50 | 51 | # calculate the average mutual information between a cell and itself in the next time step 52 | avg_mutual_information = cpl.average_mutual_information(cellular_automaton) 53 | 54 | The following plots illustrate how average mutual information changes as a function of Langton's lambda: 55 | 56 | .. image:: _static/avg_mutual_information.png 57 | :width: 650 58 | 59 | **References** 60 | 61 | *Langton, C. G. (1990). Computation at the edge of chaos: phase transitions and emergent computation. 62 | Physica D: Nonlinear Phenomena, 42(1-3), 12-37.* 63 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [2.4.0] - 2023-02-14 11 | 12 | ### Added 13 | 14 | - Added `evolve_block` function 15 | - Added `evolve2d_block` function 16 | - Added block CA demos 17 | 18 | ## [2.3.1] - 2021-12-25 19 | 20 | ### Changed 21 | 22 | - Changed `plot2d_animate` so that it returns the animation object, to address a problem arising in Spyder IDE 23 | 24 | ## [2.3.0] - 2021-12-01 25 | 26 | ### Added 27 | 28 | - Added support for `memoize='recursive'` option of `evolve` and `evolve2d` functions 29 | - Added `NKSRule`, `BinaryRule` and `TotalisticRule` classes 30 | 31 | ## [2.2.0] - 2021-11-30 32 | 33 | ### Added 34 | 35 | - Added SDSR loop and Evoloop implementations 36 | - Added `memoize` option to `evolve` and `evolve2d` functions 37 | 38 | ## [2.1.0] - 2021-11-16 39 | 40 | ### Added 41 | 42 | - Added more Sandpile demos and more content to the Sandpile tutorial in the docs 43 | 44 | ### Changed 45 | 46 | - Changed interpretation of the `cellular_automaton` argument in `evolve` and `evolve2d` such that a history of states can be provided 47 | 48 | ## [2.0.0] - 2021-11-10 49 | 50 | ### Added 51 | 52 | - Added more test coverage 53 | - Added `CHANGELOG.md` 54 | - Added docs and tests for `bits_to_int` and `int_to_bits` functions 55 | - Added more documentation to functions in `entropy.py` and `bien.py`, and to `plot2d_slice` and `plot2d_spacetime` 56 | - Added the `BaseRule` class, which provides a base for custom callable rules 57 | - Added built-in `Sandpile` implementation 58 | - Added `show=True` argument to plotting function signatures 59 | - Added `show_grid`, `show_margin` and `scale` arguments to `plot2d` and `plot2d_slice` functions 60 | 61 | ### Changed 62 | 63 | - Addressing test warnings by making subtle adjustments to the code, such as using `np.int32` instead of `np.int` 64 | - Replaced copyright notice in `README.md` with link to Apache License 2.0 65 | - Importing modules explicitly in `__init__.py` to avoid polluting namespace 66 | - Changed `AsynchronousRule`, `ReversibleRule`, and `CTRBLRule` so that they extend `BaseRule` and implement `__call__` 67 | - Changed plotting function signatures so that they accept `imshow` keyword args 68 | - Changed the `evolve` and `evolve2d` functions so that the `timesteps` parameter can alternatively be a callable, 69 | so that models where the number of timesteps is not known in advance are supported 70 | 71 | ## [1.1.0] - 2021-08-02 72 | 73 | ### Added 74 | 75 | - Added support for CTRBL rules 76 | - Added Langton's Loop implementation 77 | - Added Wireworld demo code 78 | - Added more optional arguments to `plot2d_animate` function signature 79 | 80 | ## [1.0.0] - 2021-07-29 81 | 82 | ### Added 83 | 84 | - Initial stable release 85 | - Added more documentation to code 86 | -------------------------------------------------------------------------------- /doc/sdsr_evoloop.rst: -------------------------------------------------------------------------------- 1 | Sayama's SDSR Loop and Evoloop 2 | ------------------------------ 3 | 4 | In 1998, as a successor to Langton's Loop, Hiroki Sayama introduced the structurally dissolvable self-reproducing (SDSR) 5 | loop. Structural dissolution represents a form of death for a loop, and the potential for individual loops to replace 6 | others exists. This property introduces an intriguing dynamic behaviour with the potential for evolution. 7 | 8 | Below is an example of the SDSR loop. This example makes use of the :py:class:`~cellpylib.sdsr_loop.SDSRLoop` class, 9 | which is an extension of the :py:class:`~cellpylib.langtons_loop.LangtonsLoop` class, which can be used for 10 | constructing any kind of rule based on a von Neumann neighbourhood which considers the Center, Top, Right, Bottom and 11 | Left cells explicitly. 12 | 13 | .. code-block:: 14 | 15 | import cellpylib as cpl 16 | 17 | sdsr_loop = cpl.SDSRLoop() 18 | 19 | # the initial conditions consist of a single loop 20 | cellular_automaton = sdsr_loop.init_loops(1, (100, 100), [40], [40]) 21 | 22 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=700, 23 | apply_rule=sdsr_loop, memoize="recursive") 24 | 25 | cpl.plot2d_animate(cellular_automaton) 26 | 27 | .. image:: _static/sdsr_loop.gif 28 | :width: 500 29 | 30 | While the potential for evolution exists with the SDSR loop, it is the Evoloop which truly exhibits it. Also introduced 31 | by H. Sayama, the Evoloop is an SDSR loop with various phenotypes, that interact and compete with each other for space. 32 | 33 | Below is an example of the Evoloop. This example makes use of the :py:class:`~cellpylib.evoloop.Evoloop` class, 34 | which is an extension of the :py:class:`~cellpylib.ctrbl_rule.CTRBLRule` class, which can be used for constructing any 35 | kind of rule based on a von Neumann neighbourhood which considers the Center, Top, Right, Bottom and Left cells 36 | explicitly. 37 | 38 | .. code-block:: 39 | 40 | import cellpylib as cpl 41 | 42 | evoloop = cpl.Evoloop() 43 | 44 | # the initial conditions consist of a single loop 45 | cellular_automaton = evoloop.init_species13_loop((100, 100), 40, 15) 46 | 47 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=700, 48 | apply_rule=evoloop, memoize="recursive") 49 | 50 | cpl.plot2d_animate(cellular_automaton) 51 | 52 | .. image:: _static/evoloop.gif 53 | :width: 500 54 | 55 | **References** 56 | 57 | *Sayama, H. (1998). Constructing evolutionary systems on a simple deterministic cellular automata space. 58 | PhD, University of Tokyo, Department of Information Science.* 59 | 60 | *Sayama, H. (1998, August). Introduction of structural dissolution into Langton's self-reproducing loop. 61 | In Proceedings of the sixth international conference on Artificial life (pp. 114-122).* 62 | 63 | *Sayama, H. (1999). A new structurally dissolvable self-reproducing loop evolving in a simple cellular automata space. 64 | Artificial Life, 5(4), 343-365.* -------------------------------------------------------------------------------- /tests/resources/sandpile.ca: -------------------------------------------------------------------------------- 1 | {{{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 4, 2, 1, 0, 1, 1, 0, 1, 0}, {0, 0, 3, 0, 2, 3, 0, 1, 3, 0}, {0, 0, 1, 1, 1, 0, 2, 4, 3, 0}, {0, 4, 2, 0, 0, 4, 0, 4, 1, 0}, {0, 2, 2, 0, 1, 1, 1, 1, 3, 0}, {0, 3, 0, 3, 4, 1, 2, 4, 3, 0}, {0, 4, 3, 4, 4, 4, 0, 4, 3, 0}, {0, 1, 1, 3, 0, 0, 1, 2, 4, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 1, 0}, {0, 1, 3, 0, 2, 3, 0, 2, 3, 0}, {0, 1, 1, 1, 1, 1, 3, 1, 4, 0}, {0, 0, 3, 0, 1, 0, 2, 1, 2, 0}, {0, 3, 2, 0, 2, 2, 1, 3, 3, 0}, {0, 4, 0, 5, 1, 3, 3, 1, 4, 0}, {0, 0, 5, 1, 3, 1, 2, 1, 5, 0}, {0, 2, 1, 4, 1, 1, 1, 4, 0, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 1, 0}, {0, 1, 3, 0, 2, 3, 0, 2, 4, 0}, {0, 1, 1, 1, 1, 1, 3, 2, 0, 0}, {0, 0, 3, 0, 1, 0, 2, 1, 3, 0}, {0, 4, 2, 1, 2, 2, 1, 3, 4, 0}, {0, 0, 3, 1, 2, 3, 3, 2, 1, 0}, {0, 2, 1, 4, 3, 1, 2, 3, 2, 0}, {0, 2, 3, 0, 2, 1, 2, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 2, 0}, {0, 1, 3, 0, 2, 3, 0, 3, 0, 0}, {0, 1, 1, 1, 1, 1, 3, 2, 1, 0}, {0, 1, 3, 0, 1, 0, 2, 1, 4, 0}, {0, 0, 3, 1, 2, 2, 1, 4, 0, 0}, {0, 1, 3, 2, 2, 3, 3, 2, 2, 0}, {0, 2, 2, 0, 4, 1, 2, 3, 2, 0}, {0, 2, 3, 1, 2, 1, 2, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 2, 0}, {0, 1, 3, 0, 2, 3, 0, 3, 0, 0}, {0, 1, 1, 1, 1, 1, 3, 2, 2, 0}, {0, 1, 3, 0, 1, 0, 2, 3, 0, 0}, {0, 0, 3, 1, 2, 2, 2, 0, 2, 0}, {0, 1, 3, 2, 3, 3, 3, 3, 2, 0}, {0, 2, 2, 1, 0, 2, 2, 3, 2, 0}, {0, 2, 3, 1, 3, 1, 2, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 2, 0}, {0, 1, 3, 0, 2, 3, 0, 3, 0, 0}, {0, 1, 1, 1, 1, 1, 3, 2, 2, 0}, {0, 1, 3, 0, 1, 0, 2, 3, 0, 0}, {0, 0, 3, 1, 2, 2, 2, 0, 2, 0}, {0, 1, 3, 2, 3, 3, 3, 3, 2, 0}, {0, 2, 2, 1, 0, 2, 2, 3, 2, 0}, {0, 2, 3, 1, 3, 1, 2, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 2, 0}, {0, 1, 3, 0, 2, 3, 0, 3, 0, 0}, {0, 1, 1, 1, 1, 1, 3, 2, 2, 0}, {0, 1, 3, 0, 1, 0, 2, 3, 0, 0}, {0, 0, 3, 1, 2, 2, 2, 0, 2, 0}, {0, 1, 3, 2, 3, 3, 3, 3, 2, 0}, {0, 2, 2, 1, 0, 2, 2, 3, 2, 0}, {0, 2, 3, 1, 3, 1, 2, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 2, 0}, {0, 1, 3, 0, 2, 3, 0, 3, 0, 0}, {0, 1, 1, 1, 1, 1, 3, 2, 2, 0}, {0, 1, 3, 0, 1, 0, 2, 3, 0, 0}, {0, 0, 3, 1, 2, 2, 2, 0, 2, 0}, {0, 1, 3, 2, 3, 3, 3, 3, 2, 0}, {0, 2, 2, 1, 0, 2, 2, 3, 2, 0}, {0, 2, 3, 1, 3, 1, 2, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 2, 0}, {0, 1, 3, 0, 2, 3, 0, 3, 0, 0}, {0, 1, 1, 1, 1, 1, 3, 2, 2, 0}, {0, 1, 3, 0, 1, 0, 2, 3, 0, 0}, {0, 0, 3, 1, 2, 2, 2, 0, 2, 0}, {0, 1, 3, 2, 3, 3, 3, 3, 2, 0}, {0, 2, 2, 1, 0, 2, 2, 3, 2, 0}, {0, 2, 3, 1, 3, 1, 2, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}, {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, {0, 0, 3, 1, 0, 1, 1, 0, 2, 0}, {0, 1, 3, 0, 2, 3, 0, 3, 0, 0}, {0, 1, 1, 1, 1, 1, 3, 2, 2, 0}, {0, 1, 3, 0, 1, 0, 2, 3, 0, 0}, {0, 0, 3, 1, 2, 2, 2, 0, 2, 0}, {0, 1, 3, 2, 3, 3, 3, 3, 2, 0}, {0, 2, 2, 1, 0, 2, 2, 3, 2, 0}, {0, 2, 3, 1, 3, 1, 2, 0, 2, 0}, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}} -------------------------------------------------------------------------------- /demos/block2d_basic_ca_demo.py: -------------------------------------------------------------------------------- 1 | import cellpylib as cpl 2 | import numpy as np 3 | 4 | """ 5 | 2D Block CA from 6 | https://writings.stephenwolfram.com/2023/02/computational-foundations-for-the-second-law-of-thermodynamics/ 7 | """ 8 | 9 | initial_conditions = np.array([[ 10 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 11 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 12 | [1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1], 13 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 14 | [1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1], 15 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 16 | [1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1], 17 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 18 | [1, 0, 2, 0, 2, 0, 2, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 1], 19 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 20 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 21 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 22 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 23 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 24 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 25 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 26 | [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], 27 | [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], 28 | ]]) 29 | 30 | 31 | def make_block2d_rule(): 32 | base_rules = { 33 | ((0, 0), (0, 0)): ((0, 0), (0, 0)), 34 | ((0, 0), (0, 2)): ((2, 0), (0, 0)), 35 | ((2, 0), (0, 0)): ((0, 0), (0, 2)), 36 | ((0, 0), (2, 0)): ((0, 2), (0, 0)), 37 | ((0, 2), (0, 0)): ((0, 0), (2, 0)), 38 | ((0, 0), (2, 2)): ((2, 2), (0, 0)), 39 | ((2, 2), (0, 0)): ((0, 0), (2, 2)), 40 | ((0, 2), (0, 2)): ((2, 0), (2, 0)), 41 | ((2, 0), (2, 0)): ((0, 2), (0, 2)), 42 | ((0, 2), (2, 0)): ((2, 0), (0, 2)), 43 | ((2, 0), (0, 2)): ((0, 2), (2, 0)), 44 | ((0, 2), (2, 2)): ((2, 2), (2, 0)), 45 | ((2, 2), (2, 0)): ((0, 2), (2, 2)), 46 | ((2, 0), (2, 2)): ((2, 2), (0, 2)), 47 | ((2, 2), (0, 2)): ((2, 0), (2, 2)), 48 | ((2, 2), (2, 2)): ((2, 2), (2, 2)), 49 | # wall rules 50 | ((0, 0), (1, 1)): ((0, 0), (1, 1)), 51 | ((0, 1), (1, 1)): ((0, 1), (1, 1)), 52 | ((0, 2), (1, 1)): ((2, 0), (1, 1)), 53 | ((2, 0), (1, 1)): ((0, 2), (1, 1)), 54 | ((2, 1), (1, 1)): ((2, 1), (1, 1)), 55 | ((2, 2), (1, 1)): ((2, 2), (1, 1)), 56 | ((1, 1), (1, 1)): ((1, 1), (1, 1)), 57 | } 58 | rules = {} 59 | # add rotations 60 | for r, v in base_rules.items(): 61 | rules[r] = v 62 | for _ in range(3): 63 | r = ((r[1][0], r[0][0]), (r[1][1], r[0][1])) 64 | v = ((v[1][0], v[0][0]), (v[1][1], v[0][1])) 65 | if r not in rules: 66 | rules[r] = v 67 | def _apply_rule(n, t): 68 | n = tuple(tuple(i) for i in n) 69 | return rules[n] 70 | return _apply_rule 71 | 72 | 73 | ca = cpl.evolve2d_block(initial_conditions, block_size=(2, 2), 74 | timesteps=40, apply_rule=make_block2d_rule()) 75 | 76 | cpl.plot2d_animate(ca) 77 | -------------------------------------------------------------------------------- /paper/paper.bib: -------------------------------------------------------------------------------- 1 | @book{ilachinski2001cellular, 2 | title={{Cellular Automata: A Discrete Universe}}, 3 | author={Ilachinski, Andrew}, 4 | year={2001}, 5 | publisher={World Scientific Publishing Company}, 6 | doi={10.1142/4702} 7 | } 8 | 9 | @book{wolfram2002new, 10 | title={{A New Kind of Science}}, 11 | author={Wolfram, Stephen}, 12 | volume={5}, 13 | year={2002}, 14 | publisher={Wolfram media Champaign, IL} 15 | } 16 | 17 | @article{von1951general, 18 | title={{The General and Logical Theory of Automata, Cerebral Mechanisms in Behavior. The Hixon Symposium}}, 19 | author={Von Neumann, John}, 20 | journal={New York: John Wiley\&Sons}, 21 | year={1951} 22 | } 23 | 24 | @article{langton1990computation, 25 | title={{Computation at the Edge of Chaos: Phase Transitions and Emergent Computation}}, 26 | author={Langton, Chris G}, 27 | journal={Physica D: Nonlinear Phenomena}, 28 | volume={42}, 29 | number={1-3}, 30 | pages={12--37}, 31 | year={1990}, 32 | publisher={Elsevier}, 33 | doi={10.1016/0167-2789(90)90064-v} 34 | } 35 | 36 | @Article{harris2020array, 37 | title={Array programming with {NumPy}}, 38 | author={Charles R. Harris and K. Jarrod Millman and St{\'{e}}fan J. 39 | van der Walt and Ralf Gommers and Pauli Virtanen and David 40 | Cournapeau and Eric Wieser and Julian Taylor and Sebastian 41 | Berg and Nathaniel J. Smith and Robert Kern and Matti Picus 42 | and Stephan Hoyer and Marten H. van Kerkwijk and Matthew 43 | Brett and Allan Haldane and Jaime Fern{\'{a}}ndez del 44 | R{\'{i}}o and Mark Wiebe and Pearu Peterson and Pierre 45 | G{\'{e}}rard-Marchant and Kevin Sheppard and Tyler Reddy and 46 | Warren Weckesser and Hameer Abbasi and Christoph Gohlke and 47 | Travis E. Oliphant}, 48 | year={2020}, 49 | month=sep, 50 | journal={Nature}, 51 | volume={585}, 52 | number={7825}, 53 | pages={357--362}, 54 | doi={10.1038/s41586-020-2649-2}, 55 | publisher={Springer Science and Business Media {LLC}}, 56 | url={https://doi.org/10.1038/s41586-020-2649-2} 57 | } 58 | 59 | @Article{Hunter:2007, 60 | Author={Hunter, J. D.}, 61 | Title={Matplotlib: A 2D graphics environment}, 62 | Journal={Computing in Science \& Engineering}, 63 | Volume={9}, 64 | Number={3}, 65 | Pages={90--95}, 66 | abstract={Matplotlib is a 2D graphics package used for Python for 67 | application development, interactive scripting, and publication-quality 68 | image generation across user interfaces and operating systems.}, 69 | publisher={IEEE COMPUTER SOC}, 70 | doi={10.1109/MCSE.2007.55}, 71 | year=2007 72 | } 73 | 74 | @article{nichele2017deep, 75 | title={{Deep Learning with Cellular Automaton-based Reservoir Computing}}, 76 | author={Nichele, Stefano and Molund, Andreas}, 77 | year={2017}, 78 | journal={Complex Systems}, 79 | volume={26}, 80 | number={4}, 81 | pages={319--240}, 82 | publisher={Complex Systems Publications Inc}, 83 | doi={10.25088/complexsystems.26.4.319} 84 | } 85 | 86 | @article{mordvintsev2020growing, 87 | author = {Mordvintsev, Alexander and Randazzo, Ettore and Niklasson, Eyvind and Levin, Michael}, 88 | title = {Growing Neural Cellular Automata}, 89 | journal = {Distill}, 90 | year = {2020}, 91 | note = {https://distill.pub/2020/growing-ca}, 92 | doi = {10.23915/distill.00023} 93 | } -------------------------------------------------------------------------------- /doc/hopfield.rst: -------------------------------------------------------------------------------- 1 | Hopfield Network 2 | ---------------- 3 | 4 | The Hopfield Network can be thought of as a cellular automaton where all cells are neighbours of eachother. The cells 5 | (or neurons) are binary units, and the activity rule is a simple threshold rule, where the weighted inputs to a cell are 6 | summed and compared to a threshold value. The weights are learned from the training data. 7 | 8 | CellPyLib includes a built-in implementation of a Hopfield Network, in the 9 | :py:class:`~cellpylib.hopfield_net.HopfieldNet` class, based on the idea that the Hopfield Network can be viewed as a 10 | kind of cellular automaton. 11 | 12 | To use it, we must first train the network, by giving it a set of patterns: 13 | 14 | .. code-block:: 15 | 16 | import cellpylib as cpl 17 | import numpy as np 18 | 19 | hopfield_net = cpl.HopfieldNet(num_cells=35) 20 | 21 | zero = [ 22 | 0, 1, 1, 1, 0, 23 | 1, 0, 0, 0, 1, 24 | 1, 0, 0, 0, 1, 25 | 1, 0, 0, 0, 1, 26 | 1, 0, 0, 0, 1, 27 | 0, 1, 1, 1, 0, 28 | 0, 0, 0, 0, 0] 29 | one = [ 30 | 0, 1, 1, 0, 0, 31 | 0, 0, 1, 0, 0, 32 | 0, 0, 1, 0, 0, 33 | 0, 0, 1, 0, 0, 34 | 0, 0, 1, 0, 0, 35 | 0, 0, 1, 0, 0, 36 | 0, 0, 0, 0, 0] 37 | two = [ 38 | 1, 1, 1, 0, 0, 39 | 0, 0, 0, 1, 0, 40 | 0, 0, 0, 1, 0, 41 | 0, 1, 1, 0, 0, 42 | 1, 0, 0, 0, 0, 43 | 1, 1, 1, 1, 1, 44 | 0, 0, 0, 0, 0] 45 | # replace the zeroes with -1 to make these vectors bipolar instead of binary 46 | one = [-1 if x == 0 else x for x in one] 47 | two = [-1 if x == 0 else x for x in two] 48 | zero = [-1 if x == 0 else x for x in zero] 49 | 50 | P = [zero, one, two] 51 | 52 | hopfield_net.train(P) 53 | 54 | As shown above, we must instantiate an instance of a :py:class:`~cellpylib.hopfield_net.HopfieldNet`, specifying the 55 | number of cells. Then, we must call :py:func:`~cellpylib.hopfield_net.HopfieldNet.train`, providing a list of training 56 | examples. *NOTE: Only Hopfield Networks with an odd number of cells is currently supported due to limitations of the 57 | implementation.* 58 | 59 | Using the :py:class:`~cellpylib.hopfield_net.HopfieldNet` involves providing a potentially incomplete pattern, and 60 | evolving the network for a pre-specified number of timesteps. The network state should settle into a pattern that 61 | resembles those seen during training. It acts like a content-addressable (associative) memory. 62 | 63 | .. code-block:: 64 | 65 | half_two = [ 66 | 0, 0, 0, 0, 0, 67 | 0, 0, 0, 0, 0, 68 | 0, 0, 0, 0, 0, 69 | 0, 1, 1, 0, 0, 70 | 1, 0, 0, 0, 0, 71 | 1, 1, 1, 1, 1, 72 | 0, 0, 0, 0, 0] 73 | half_two = [-1 if x == 0 else x for x in half_two] 74 | 75 | cellular_automaton = np.array([half_two]) 76 | 77 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=155, 78 | apply_rule=hopfield_net.apply_rule, r=hopfield_net.r) 79 | 80 | cpl.plot(hopfield_net.W) 81 | cpl.plot2d_animate(np.reshape(cellular_automaton, (155, 7, 5))) 82 | 83 | .. image:: _static/hopfield_net_weights.png 84 | :width: 300 85 | 86 | .. image:: _static/hopfield_net.gif 87 | :width: 400 88 | 89 | **References:** 90 | 91 | *J. J. Hopfield, "Neural networks and physical systems with emergent collective computational abilities", 92 | Proceedings of the National Academy of Sciences of the USA, vol. 79 no. 8 pp. 2554–2558, April 1982.* 93 | 94 | https://en.wikipedia.org/wiki/Hopfield_network 95 | 96 | http://neupy.com/2015/09/20/discrete_hopfield_network.html 97 | -------------------------------------------------------------------------------- /cellpylib/sandpile.py: -------------------------------------------------------------------------------- 1 | from .ca_functions import BaseRule 2 | 3 | 4 | class Sandpile(BaseRule): 5 | """ 6 | A rule that operates on von Neumann neighbourhoods with a radius of 1. A sandpile is a 2D cellular automaton and 7 | dynamical system that displays self-organized criticality. It was introduced by Bak, Tang and Wiesenfeld in 1987. 8 | """ 9 | def __init__(self, rows, cols, is_closed_boundary=True): 10 | """ 11 | Creates a Sandpile. 12 | 13 | :param rows: the number of rows in this 2D CA 14 | 15 | :param cols: the number of columns in this 2D CA 16 | 17 | :param is_closed_boundary: whether or not the sandpile's boundary is closed; if it is closed, then all the 18 | boundary cells will maintain a value of 0 (default is True) 19 | """ 20 | self._K = 4 # this value is hard-coded because the neighbourhood type, "von Neumann", is fixed 21 | self._rows = rows 22 | self._cols = cols 23 | self._is_closed_boundary = is_closed_boundary 24 | self._grain_additions = [] 25 | 26 | def add_grain(self, cell_index, timestep): 27 | """ 28 | Drop a grain of sand at the given cell in the given timestep. 29 | 30 | :param cell_index: a 2-tuple representing the row index and column index of the cell that will have 31 | a grain of sand added 32 | 33 | :param timestep: the timestep at which the grain addition will occur 34 | """ 35 | self._grain_additions.append(_GrainAddition(cell_index, timestep)) 36 | 37 | def _is_in_boundary(self, c): 38 | """ 39 | Returns True if the given cell (as a 2-tuple, representing its coordinates) is a boundary cell. 40 | 41 | :param c: a 2-tuple representing the row- and column-index of the cell 42 | 43 | :return: True if the given cell is a boundary cell, False otherwise 44 | """ 45 | return c[0] == 0 or c[0] == self._rows - 1 or c[1] == 0 or c[1] == self._cols - 1 46 | 47 | def __call__(self, n, c, t): 48 | """ 49 | The Sandpile rule to apply. 50 | 51 | :param n: the neighbourhood 52 | 53 | :param c: the index of the current cell 54 | 55 | :param t: the current timestep 56 | 57 | :return: the activity of the current cell at the next timestep 58 | """ 59 | if self._is_closed_boundary and self._is_in_boundary(c): 60 | return 0 # closed boundary conditions 61 | 62 | for grain_addition in self._grain_additions: 63 | if t == grain_addition.timestep and c == grain_addition.cell_index: 64 | return n[1][1] + 1 65 | 66 | # this cell's activity is the value of the center of the von Neumann neighbourhood 67 | current_activity = n[1][1] 68 | new_activity = current_activity 69 | 70 | # this assumes a von Neumann neighbourhood of radius 1 71 | neighbour_activities = [n[0][1], n[1][0], n[1][2], n[2][1]] 72 | 73 | for neighbour_activity in neighbour_activities: 74 | 75 | if neighbour_activity >= self._K: 76 | new_activity += 1 77 | 78 | if current_activity >= self._K: 79 | new_activity -= self._K 80 | 81 | return new_activity 82 | 83 | 84 | class _GrainAddition: 85 | """ 86 | A representation of the addition of a grain of sand to the sandpile. 87 | """ 88 | def __init__(self, cell_index, timestep): 89 | """ 90 | Create an instance of a `_GrainAddition`. 91 | 92 | :param cell_index: the index of the cell that will have a grain of sand added 93 | 94 | :param timestep: the timestep at which the grain addition occurs 95 | """ 96 | self.cell_index = cell_index 97 | self.timestep = timestep 98 | -------------------------------------------------------------------------------- /tests/resources/hopfield_net_weights.txt: -------------------------------------------------------------------------------- 1 | [[0, -1, -1, -1, 1, -1, 1, -1, 3, -1, -1, 1, -1, 3, -1, -1, 3, 1, 1, -1, 1, 1, -1, 1, -1, 3, 1, -1, 1, 3, 1, 1, 1, 1, 1], [-1, 0, 3, -1, -3, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -1, 1, -3, -1, 1, -3, -1, -3, -1, -1, 1, 3, 1, -1, -3, -3, -3, -3, -3], [-1, 3, 0, -1, -3, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -1, 1, -3, -1, 1, -3, -1, -3, -1, -1, 1, 3, 1, -1, -3, -3, -3, -3, -3], [-1, -1, -1, 0, 1, 3, 1, -1, -1, 3, 3, 1, -1, -1, 3, 3, -1, -3, 1, 3, 1, 1, -1, 1, 3, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1], [1, -3, -3, 1, 0, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 3, 3, 3, 3, 3], [-1, -1, -1, 3, 1, 0, 1, -1, -1, 3, 3, 1, -1, -1, 3, 3, -1, -3, 1, 3, 1, 1, -1, 1, 3, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1], [1, -3, -3, 1, 3, 1, 0, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 3, 3, 3, 3, 3], [-1, -1, -1, -1, 1, -1, 1, 0, -1, -1, -1, 1, 3, -1, -1, -1, -1, 1, 1, -1, -3, 1, 3, 1, -1, -1, -3, -1, -3, -1, 1, 1, 1, 1, 1], [3, -1, -1, -1, 1, -1, 1, -1, 0, -1, -1, 1, -1, 3, -1, -1, 3, 1, 1, -1, 1, 1, -1, 1, -1, 3, 1, -1, 1, 3, 1, 1, 1, 1, 1], [-1, -1, -1, 3, 1, 3, 1, -1, -1, 0, 3, 1, -1, -1, 3, 3, -1, -3, 1, 3, 1, 1, -1, 1, 3, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1], [-1, -1, -1, 3, 1, 3, 1, -1, -1, 3, 0, 1, -1, -1, 3, 3, -1, -3, 1, 3, 1, 1, -1, 1, 3, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 3, 3, 3, 3, 3], [-1, -1, -1, -1, 1, -1, 1, 3, -1, -1, -1, 1, 0, -1, -1, -1, -1, 1, 1, -1, -3, 1, 3, 1, -1, -1, -3, -1, -3, -1, 1, 1, 1, 1, 1], [3, -1, -1, -1, 1, -1, 1, -1, 3, -1, -1, 1, -1, 0, -1, -1, 3, 1, 1, -1, 1, 1, -1, 1, -1, 3, 1, -1, 1, 3, 1, 1, 1, 1, 1], [-1, -1, -1, 3, 1, 3, 1, -1, -1, 3, 3, 1, -1, -1, 0, 3, -1, -3, 1, 3, 1, 1, -1, 1, 3, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1], [-1, -1, -1, 3, 1, 3, 1, -1, -1, 3, 3, 1, -1, -1, 3, 0, -1, -3, 1, 3, 1, 1, -1, 1, 3, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1], [3, -1, -1, -1, 1, -1, 1, -1, 3, -1, -1, 1, -1, 3, -1, -1, 0, 1, 1, -1, 1, 1, -1, 1, -1, 3, 1, -1, 1, 3, 1, 1, 1, 1, 1], [1, 1, 1, -3, -1, -3, -1, 1, 1, -3, -3, -1, 1, 1, -3, -3, 1, 0, -1, -3, -1, -1, 1, -1, -3, 1, -1, 1, -1, 1, -1, -1, -1, -1, -1], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 0, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 3, 3, 3, 3, 3], [-1, -1, -1, 3, 1, 3, 1, -1, -1, 3, 3, 1, -1, -1, 3, 3, -1, -3, 1, 0, 1, 1, -1, 1, 3, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1], [1, 1, 1, 1, -1, 1, -1, -3, 1, 1, 1, -1, -3, 1, 1, 1, 1, -1, -1, 1, 0, -1, -3, -1, 1, 1, 3, 1, 3, 1, -1, -1, -1, -1, -1], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 0, 1, 3, 1, 1, -1, -3, -1, 1, 3, 3, 3, 3, 3], [-1, -1, -1, -1, 1, -1, 1, 3, -1, -1, -1, 1, 3, -1, -1, -1, -1, 1, 1, -1, -3, 1, 0, 1, -1, -1, -3, -1, -3, -1, 1, 1, 1, 1, 1], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 0, 1, 1, -1, -3, -1, 1, 3, 3, 3, 3, 3], [-1, -1, -1, 3, 1, 3, 1, -1, -1, 3, 3, 1, -1, -1, 3, 3, -1, -3, 1, 3, 1, 1, -1, 1, 0, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1], [3, -1, -1, -1, 1, -1, 1, -1, 3, -1, -1, 1, -1, 3, -1, -1, 3, 1, 1, -1, 1, 1, -1, 1, -1, 0, 1, -1, 1, 3, 1, 1, 1, 1, 1], [1, 1, 1, 1, -1, 1, -1, -3, 1, 1, 1, -1, -3, 1, 1, 1, 1, -1, -1, 1, 3, -1, -3, -1, 1, 1, 0, 1, 3, 1, -1, -1, -1, -1, -1], [-1, 3, 3, -1, -3, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -1, 1, -3, -1, 1, -3, -1, -3, -1, -1, 1, 0, 1, -1, -3, -3, -3, -3, -3], [1, 1, 1, 1, -1, 1, -1, -3, 1, 1, 1, -1, -3, 1, 1, 1, 1, -1, -1, 1, 3, -1, -3, -1, 1, 1, 3, 1, 0, 1, -1, -1, -1, -1, -1], [3, -1, -1, -1, 1, -1, 1, -1, 3, -1, -1, 1, -1, 3, -1, -1, 3, 1, 1, -1, 1, 1, -1, 1, -1, 3, 1, -1, 1, 0, 1, 1, 1, 1, 1], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 0, 3, 3, 3, 3], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 3, 0, 3, 3, 3], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 3, 3, 0, 3, 3], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 3, 3, 3, 0, 3], [1, -3, -3, 1, 3, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 1, -1, 3, 1, -1, 3, 1, 3, 1, 1, -1, -3, -1, 1, 3, 3, 3, 3, 0]] -------------------------------------------------------------------------------- /demos/sandpile_add_grain_demo.txt: -------------------------------------------------------------------------------- 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 0 0 0 0 0 0 0 2 | 0 1 0 3 0 2 2 2 0 1 1 2 3 1 3 2 3 0 3 3 3 3 2 3 0 1 1 1 2 1 3 3 2 1 3 0 1 1 3 0 0 2 3 0 0 3 | 0 3 3 2 1 1 1 2 2 3 3 2 2 3 0 3 3 3 2 1 3 3 0 2 1 2 3 2 2 2 1 2 3 3 2 0 0 3 2 3 1 1 1 1 0 4 | 0 2 3 0 0 1 0 2 3 2 3 0 2 3 1 2 2 2 2 1 3 2 2 2 3 3 3 3 2 0 2 3 3 1 3 3 0 1 1 2 1 0 2 2 0 5 | 0 0 3 2 3 3 2 2 1 3 1 3 3 1 3 2 0 2 1 2 2 2 3 3 3 2 2 2 2 2 2 1 2 3 0 2 3 0 3 2 0 3 1 1 0 6 | 0 2 0 0 1 1 3 2 1 3 2 1 2 2 3 3 2 0 2 3 1 3 2 2 3 0 2 3 1 3 2 1 0 2 3 2 3 2 1 3 2 0 3 1 0 7 | 0 2 3 1 0 3 2 2 2 3 2 3 1 2 2 1 1 3 3 2 2 1 2 3 1 2 3 3 2 2 3 3 3 1 3 1 1 3 3 2 3 2 3 3 0 8 | 0 1 2 1 3 3 1 0 2 1 2 3 2 3 3 2 2 0 3 2 1 0 3 0 3 1 3 1 2 0 1 3 2 2 3 3 0 1 1 3 0 2 2 0 0 9 | 0 3 2 2 2 2 3 2 3 0 2 2 2 3 0 3 3 3 3 2 2 3 1 3 3 0 1 3 2 2 2 2 1 2 3 2 3 2 2 1 3 3 2 2 0 10 | 0 3 2 2 2 2 1 1 3 2 3 2 3 2 3 2 2 2 1 2 2 3 3 2 3 3 1 3 3 0 2 3 2 2 3 2 3 1 3 2 3 1 1 0 0 11 | 0 2 2 1 3 1 3 2 3 2 1 0 2 0 2 0 3 0 3 2 3 1 3 1 0 2 3 0 1 2 3 3 3 1 3 2 1 3 0 3 1 1 3 3 0 12 | 0 3 0 3 2 3 2 3 2 2 2 3 3 3 3 3 3 3 3 1 0 3 1 3 2 2 3 2 2 1 3 2 3 2 2 1 3 2 3 1 3 3 2 2 0 13 | 0 1 3 2 1 1 2 2 3 2 1 2 1 0 3 2 2 2 3 2 2 1 1 3 2 3 2 3 3 2 2 1 2 3 3 1 1 2 1 1 3 2 0 2 0 14 | 0 3 3 3 3 3 2 2 3 1 2 3 3 1 2 2 2 2 3 3 2 2 3 3 1 1 1 1 3 2 1 1 3 1 0 3 3 2 3 3 1 1 2 3 0 15 | 0 3 1 1 3 1 2 1 1 0 2 3 2 2 0 3 3 2 1 0 3 1 3 2 2 1 2 3 0 2 2 3 3 2 2 2 1 1 2 1 3 2 1 0 0 16 | 0 2 3 0 2 3 3 2 2 3 2 2 1 2 3 3 2 3 2 2 3 3 2 2 3 0 2 2 3 2 3 2 2 3 2 3 1 2 1 1 2 2 2 1 0 17 | 0 2 0 2 2 0 2 3 3 3 2 3 3 2 3 2 3 3 3 1 2 3 0 3 1 2 3 1 1 3 2 3 2 3 1 3 0 3 3 2 2 3 2 1 0 18 | 0 0 3 3 1 3 1 0 1 2 1 2 2 2 1 0 3 2 1 2 3 2 2 3 3 3 1 2 3 0 3 2 3 0 1 1 1 2 1 1 1 2 3 2 0 19 | 0 3 0 2 3 3 2 3 1 3 3 3 3 2 3 2 3 1 2 2 3 2 2 2 1 2 3 1 3 3 2 0 2 3 3 3 2 2 2 3 2 3 2 0 0 20 | 0 3 2 3 3 1 1 1 2 2 2 2 0 2 3 0 2 1 2 3 1 0 3 3 1 2 3 2 1 2 3 3 2 0 1 3 2 1 2 2 1 2 3 2 0 21 | 0 2 0 2 1 0 1 0 3 2 2 3 1 2 3 3 2 1 0 3 2 2 1 1 2 1 2 2 2 3 3 1 2 1 3 3 2 2 2 0 3 2 3 2 0 22 | 0 1 2 0 2 1 2 2 1 3 2 3 3 2 2 1 3 2 3 3 2 3 2 3 2 3 2 2 1 2 1 3 1 1 1 0 1 2 3 1 1 2 2 2 0 23 | 0 0 2 3 0 3 1 3 2 3 2 3 0 1 1 3 1 3 2 1 1 3 2 1 1 3 3 2 2 2 2 2 1 3 1 3 0 3 1 1 3 3 3 1 0 24 | 0 1 2 3 3 3 2 3 3 0 3 3 3 2 3 3 0 2 1 2 2 3 2 3 0 2 3 3 0 3 3 3 0 1 2 2 2 2 2 3 0 2 2 1 0 25 | 0 2 2 1 3 2 2 1 0 2 3 2 1 2 1 1 3 3 3 3 3 3 0 3 2 3 2 2 2 2 1 3 3 0 3 2 2 3 3 0 2 3 1 2 0 26 | 0 1 2 3 2 3 3 3 2 2 2 2 2 2 3 2 3 2 2 3 1 0 2 3 3 2 0 1 3 2 3 3 3 2 2 0 3 1 1 1 3 3 0 2 0 27 | 0 0 2 3 1 0 3 2 3 2 3 2 1 1 2 2 2 3 2 3 3 1 2 2 1 3 3 3 1 1 0 3 0 3 3 2 3 1 3 2 3 1 1 1 0 28 | 0 2 1 3 1 2 3 2 1 2 1 2 2 1 0 3 2 3 2 1 3 1 3 0 3 1 3 1 2 3 2 3 2 2 3 0 2 2 2 1 3 1 2 0 0 29 | 0 3 1 3 3 3 3 1 1 2 2 2 3 3 3 0 3 2 1 3 3 2 2 3 0 3 3 2 2 0 2 1 0 0 1 1 2 2 3 1 2 0 2 2 0 30 | 0 1 2 3 2 3 1 0 1 1 3 2 2 1 2 3 2 2 1 2 2 3 2 3 3 2 2 2 2 3 3 2 0 3 2 3 2 2 3 0 3 2 3 0 0 31 | 0 2 2 1 2 3 3 3 1 2 2 3 3 1 3 0 2 0 3 2 1 2 3 2 2 3 2 1 3 3 2 3 2 1 3 2 3 2 3 2 1 3 1 2 0 32 | 0 2 3 0 3 1 1 0 1 3 2 0 3 3 3 2 2 3 1 0 2 2 2 3 2 2 2 1 1 3 0 3 2 1 0 3 3 0 2 1 2 1 2 3 0 33 | 0 2 1 2 1 3 1 0 3 3 1 2 3 1 1 1 2 1 3 2 1 1 1 1 3 2 1 3 3 3 3 1 3 2 2 1 3 3 1 3 3 2 0 3 0 34 | 0 3 0 3 1 1 2 3 1 1 3 0 3 3 1 2 2 2 1 1 0 1 2 3 2 2 3 1 1 1 2 2 2 1 3 1 2 3 0 2 3 3 3 0 0 35 | 0 1 3 3 3 2 1 0 2 3 0 3 1 2 3 3 0 2 3 3 3 0 3 0 1 1 3 2 1 3 1 3 3 1 3 2 1 3 3 3 2 3 1 2 0 36 | 0 2 0 3 1 1 3 2 0 2 1 1 2 1 3 1 3 3 1 1 0 3 3 2 3 3 3 3 1 3 3 3 2 3 1 3 0 2 2 0 3 1 3 2 0 37 | 0 0 3 3 3 0 3 1 3 3 1 3 3 3 3 0 3 0 3 3 2 2 1 2 2 3 3 2 2 0 2 3 3 0 3 3 2 1 2 3 0 2 1 2 0 38 | 0 3 2 3 1 3 2 2 3 0 2 3 3 2 2 3 3 1 3 1 3 3 2 2 2 1 2 1 3 2 3 0 3 2 3 2 2 0 3 2 2 2 3 3 0 39 | 0 1 1 2 2 3 2 2 3 1 2 3 3 1 0 2 2 3 1 3 3 3 1 2 1 0 3 1 3 2 1 3 2 3 2 2 1 2 3 1 2 2 1 2 0 40 | 0 2 1 2 2 3 0 2 3 1 3 2 3 1 3 0 2 3 3 1 0 2 3 0 3 1 2 3 3 3 3 3 2 1 3 3 3 2 2 0 2 3 0 2 0 41 | 0 3 1 3 3 2 3 1 1 3 3 1 0 2 2 3 1 1 1 2 1 1 2 3 1 1 1 1 3 2 1 2 1 1 1 2 2 2 3 3 3 1 3 2 0 42 | 0 3 3 0 2 2 2 3 2 1 0 3 2 3 1 3 2 3 3 3 2 3 3 1 3 0 2 2 2 2 2 2 3 1 3 0 3 3 2 3 3 1 2 0 0 43 | 0 2 2 2 1 2 1 1 2 1 3 1 3 1 3 3 1 3 3 1 2 0 3 2 1 1 1 3 3 0 1 1 3 3 1 3 2 3 0 1 1 2 3 1 0 44 | 0 0 3 1 3 2 3 3 3 2 0 0 2 1 2 1 1 0 3 3 3 3 2 0 1 1 2 3 3 3 3 2 1 2 2 2 0 3 3 3 2 1 3 1 0 45 | 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 0 0 0 0 0 0 0 46 | -------------------------------------------------------------------------------- /cellpylib/entropy.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | import numpy as np 4 | 5 | 6 | def shannon_entropy(string): 7 | """ 8 | Calculates the Shannon entropy for the given string. 9 | 10 | :param string: any string, such as '000101001', '12402', or 'aBcd1234ef5g' 11 | 12 | :return: a real number representing the Shannon entropy, in bits 13 | """ 14 | symbols = dict.fromkeys(list(string)) 15 | symbol_probabilities = [float(string.count(symbol)) / len(string) for symbol in symbols] 16 | H = -sum([p_symbol * math.log(p_symbol, 2.0) for p_symbol in symbol_probabilities]) 17 | return H + 0 # add 0 as a workaround so we don't end up with -0.0 18 | 19 | 20 | def average_cell_entropy(cellular_automaton): 21 | """ 22 | Calculates the average cell entropy in the given cellular automaton, where entropy is the Shannon entropy. 23 | In the case of a 1D cellular automaton, the state of a cell over time is represented as a string, and its entropy 24 | is calculated. The same is done for all cells in this cellular automaton, and the average entropy is returned. 25 | 26 | :param cellular_automaton: the cellular automaton to perform this operation on 27 | 28 | :return: a real number representing the average cell Shannon entropy, in bits 29 | """ 30 | num_cols = cellular_automaton.shape[1] 31 | entropies = [] 32 | for i in range(0, num_cols): 33 | cell_states_over_time = ''.join([str(x) for x in cellular_automaton[:, i]]) 34 | entropy = shannon_entropy(cell_states_over_time) 35 | entropies.append(entropy) 36 | return np.mean(entropies) 37 | 38 | 39 | def joint_shannon_entropy(stringX, stringY): 40 | """ 41 | Calculates the joint Shannon entropy between the given strings, which must be of the same length. 42 | 43 | :param stringX: any string, such as '000101001', '12402', or 'aBcd1234ef5g' 44 | 45 | :param stringY: any string, such as '000101001', '12402', or 'aBcd1234ef5g' 46 | 47 | :return: a real number representing the joint Shannon entropy between the given strings, in bits 48 | """ 49 | X = np.array(list(stringX)) 50 | Y = np.array(list(stringY)) 51 | joint_symbol_probabilities = [] 52 | for x in set(X): 53 | for y in set(Y): 54 | joint_symbol_probabilities.append(np.mean(np.logical_and(X == x, Y == y))) 55 | return sum([-p * np.log2(p) for p in joint_symbol_probabilities if p != 0]) 56 | 57 | 58 | def mutual_information(stringX, stringY): 59 | """ 60 | Calculates the mutual information between the given strings, which must be of the same length. 61 | 62 | :param stringX: any string, such as '000101001', '12402', or 'aBcd1234ef5g' 63 | 64 | :param stringY: any string, such as '000101001', '12402', or 'aBcd1234ef5g' 65 | 66 | :return: a real number representing the mutual information between the given strings, in bits 67 | """ 68 | return shannon_entropy(stringX) + shannon_entropy(stringY) - joint_shannon_entropy(stringX, stringY) 69 | 70 | 71 | def average_mutual_information(cellular_automaton, temporal_distance=1): 72 | """ 73 | Calculates the average mutual information between a cell and itself at the next n time steps, given by the 74 | specified temporal distance. A temporal distance of 1 means the next time step. 75 | 76 | For example, consider the following string, '00101010110', which represents the state of a cell over 11 time steps. 77 | The strings which will be used for the computation of the mutual information between a cell and itself at the 78 | next time step are: '0010101011' and '0101010110', since we pair each time-step value with its next value: 79 | 80 | .. code-block:: text 81 | 82 | " 00101010110" 83 | "00101010110 " 84 | 85 | :param cellular_automaton: the cellular automaton to perform this operation on 86 | 87 | :param temporal_distance: the size of temporal separation, where the value must be greater than 0 and 88 | less than the number of time steps. 89 | 90 | :return: a real number representing the average mutual information between a cell and itself at the next time step, 91 | in bits 92 | """ 93 | num_cols = cellular_automaton.shape[1] 94 | if not (0 < temporal_distance < num_cols): 95 | raise ValueError("the temporal distance must be greater than 0 and less than the number of time steps") 96 | mutual_informations = [] 97 | for i in range(0, num_cols): 98 | cell_states_over_time = ''.join([str(x) for x in cellular_automaton[:, i]]) 99 | mi = mutual_information(cell_states_over_time[:-temporal_distance], cell_states_over_time[temporal_distance:]) 100 | mutual_informations.append(mi) 101 | return np.mean(mutual_informations) 102 | -------------------------------------------------------------------------------- /doc/wireworld.rst: -------------------------------------------------------------------------------- 1 | Wireworld 2 | --------- 3 | 4 | Wireworld is a Turing-complete Cellular Automaton, first described by Brian Silverman in 1987. Wireworld can be used to 5 | simulate electronic gates, or logic elements. 6 | 7 | An example of Wireworld diodes, implemented with CellPyLib, is given below: 8 | 9 | .. code-block:: 10 | 11 | import cellpylib as cpl 12 | import numpy as np 13 | from matplotlib.colors import ListedColormap 14 | 15 | 16 | def wireworld_rule(n, c, t): 17 | current_activity = n[1][1] 18 | if current_activity == 0: # empty 19 | return 0 20 | if current_activity == 1: # electron head 21 | return 2 22 | if current_activity == 2: # electron tail 23 | return 3 24 | if current_activity == 3: # conductor 25 | electron_head_count = np.count_nonzero(n == 1) 26 | return 1 if electron_head_count == 1 or electron_head_count == 2 else 3 27 | 28 | 29 | cellular_automata = np.array([[ 30 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 31 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 32 | [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0], 33 | [2, 1, 3, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3], 34 | [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0], 35 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 36 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 37 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 38 | [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0], 39 | [2, 1, 3, 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3], 40 | [0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0], 41 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 42 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 43 | ]]) 44 | 45 | cellular_automata = cpl.evolve2d(cellular_automata, timesteps=15, 46 | apply_rule=wireworld_rule, neighbourhood="Moore") 47 | 48 | cpl.plot2d_animate(cellular_automata, show_grid=True, show_margin=False, scale=0.3, 49 | colormap=ListedColormap(["black", "blue", "red", "yellow"])) 50 | 51 | .. image:: _static/wireworld_diodes.gif 52 | :width: 300 53 | 54 | An example of a Wireworld XOR gate, implemented with CellPyLib, gate is given below: 55 | 56 | .. code-block:: 57 | 58 | import cellpylib as cpl 59 | import numpy as np 60 | from matplotlib.colors import ListedColormap 61 | 62 | 63 | def wireworld_rule(n, c, t): 64 | current_activity = n[1][1] 65 | if current_activity == 0: # empty 66 | return 0 67 | if current_activity == 1: # electron head 68 | return 2 69 | if current_activity == 2: # electron tail 70 | return 3 71 | if current_activity == 3: # conductor 72 | electron_head_count = np.count_nonzero(n == 1) 73 | return 1 if electron_head_count == 1 or electron_head_count == 2 else 3 74 | 75 | 76 | cellular_automata = np.array([[ 77 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 78 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 79 | [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 80 | [0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0], 81 | [0, 0, 0, 3, 1, 2, 3, 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0], 82 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 3, 0, 0, 0, 0], 83 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 3, 3, 3, 3, 2], 84 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 0, 0, 0, 0], 85 | [0, 0, 0, 3, 3, 2, 1, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0], 86 | [0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 2, 1, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0], 87 | [0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 88 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 89 | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 90 | ]]) 91 | 92 | cellular_automata = cpl.evolve2d(cellular_automata, timesteps=25, 93 | apply_rule=wireworld_rule, neighbourhood="Moore") 94 | 95 | cpl.plot2d_animate(cellular_automata, show_grid=True, show_margin=False, scale=0.3, 96 | colormap=ListedColormap(["black", "blue", "red", "yellow"])) 97 | 98 | .. image:: _static/wireworld_xor.gif 99 | :width: 400 100 | 101 | **References:** 102 | 103 | https://en.wikipedia.org/wiki/Wireworld 104 | 105 | *Dewdney, A K (January 1990). "Computer recreations: The cellular automata programs that create Wireworld, Rugworld and 106 | other diversions". Scientific American. 262 (1): 146–149.* 107 | -------------------------------------------------------------------------------- /doc/fredkin.rst: -------------------------------------------------------------------------------- 1 | Fredkin's Self-Replicating CA 2 | ----------------------------- 3 | 4 | Ed Fredkin described an interesting cellular automaton that exhibits self-replication. The CA is 2-dimensional, and can 5 | consist of two or more colors. To compute the state of a cell at the next timestep, one sums the states of the 6 | neighbouring cells, modulo `p`, where `p` represents the number of colors. The neighborhood can be either of the Moore 7 | or von Neumann type. As the CA evolves, copies of the initial configuration will be produced. The examples below of 8 | these CA are based on John D. Cook's blog posts, 9 | `here `_ and 10 | `here `_: 11 | 12 | The following is an example of the Fredkin self-replicating CA with a von Neumann neighborhood, implemented with 13 | CellPyLib: 14 | 15 | .. code-block:: 16 | 17 | import cellpylib as cpl 18 | import numpy as np 19 | 20 | cellular_automaton = cpl.init_simple2d(60, 60) 21 | # the letter "E" 22 | cellular_automaton[0][28][28] = 1 23 | cellular_automaton[0][28][29] = 1 24 | cellular_automaton[0][28][30] = 1 25 | cellular_automaton[0][29][28] = 1 26 | cellular_automaton[0][30][28] = 1 27 | cellular_automaton[0][30][29] = 1 28 | cellular_automaton[0][30][30] = 1 29 | cellular_automaton[0][31][28] = 1 30 | cellular_automaton[0][32][28] = 1 31 | cellular_automaton[0][32][29] = 1 32 | cellular_automaton[0][32][30] = 1 33 | 34 | def activity_rule(n, c, t): 35 | current_activity = n[1][1] 36 | return (np.sum(n) - current_activity) % 2 37 | 38 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=20, 39 | apply_rule=activity_rule, neighbourhood="von Neumann") 40 | 41 | cpl.plot2d_animate(cellular_automaton, interval=350) 42 | 43 | .. image:: _static/fredkin_von_neumann_demo.gif 44 | :width: 350 45 | 46 | The following is an example of the Fredkin self-replicating CA with a Moore neighborhood, implemented with CellPyLib: 47 | 48 | .. code-block:: 49 | 50 | import cellpylib as cpl 51 | import numpy as np 52 | 53 | cellular_automaton = cpl.init_simple2d(60, 60) 54 | # the letter "E" 55 | cellular_automaton[0][28][28] = 1 56 | cellular_automaton[0][28][29] = 1 57 | cellular_automaton[0][28][30] = 1 58 | cellular_automaton[0][29][28] = 1 59 | cellular_automaton[0][30][28] = 1 60 | cellular_automaton[0][30][29] = 1 61 | cellular_automaton[0][30][30] = 1 62 | cellular_automaton[0][31][28] = 1 63 | cellular_automaton[0][32][28] = 1 64 | cellular_automaton[0][32][29] = 1 65 | cellular_automaton[0][32][30] = 1 66 | 67 | def activity_rule(n, c, t): 68 | current_activity = n[1][1] 69 | return (np.sum(n) - current_activity) % 2 70 | 71 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=20, 72 | apply_rule=activity_rule, neighbourhood="Moore") 73 | 74 | cpl.plot2d_animate(cellular_automaton, interval=350) 75 | 76 | .. image:: _static/fredkin_moore_demo.gif 77 | :width: 350 78 | 79 | The following is an example of the Fredkin self-replicating multi-color CA with a von Neumann neighborhood, implemented 80 | with CellPyLib: 81 | 82 | .. code-block:: 83 | 84 | import cellpylib as cpl 85 | import numpy as np 86 | 87 | cellular_automaton = cpl.init_simple2d(60, 60) 88 | # the letter "E" 89 | cellular_automaton[0][28][28] = 0 90 | cellular_automaton[0][28][29] = 1 91 | cellular_automaton[0][28][30] = 2 92 | cellular_automaton[0][29][28] = 3 93 | cellular_automaton[0][30][28] = 4 94 | cellular_automaton[0][30][29] = 5 95 | cellular_automaton[0][30][30] = 6 96 | cellular_automaton[0][31][28] = 7 97 | cellular_automaton[0][32][28] = 8 98 | cellular_automaton[0][32][29] = 9 99 | cellular_automaton[0][32][30] = 10 100 | 101 | def activity_rule(n, c, t): 102 | current_activity = n[1][1] 103 | return (np.sum(n) - current_activity) % 11 104 | 105 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=23, 106 | apply_rule=activity_rule, neighbourhood="von Neumann") 107 | 108 | cpl.plot2d_animate(cellular_automaton, interval=350, colormap="viridis") 109 | 110 | .. image:: _static/fredkin_multicolor_demo.gif 111 | :width: 350 112 | 113 | **References:** 114 | 115 | *Edwin R. Banks, Information Processing and Transmission in Cellular Automata. MIT dissertation. January 1971.* 116 | 117 | https://www.johndcook.com/blog/2021/05/03/self-reproducing-cellular-automata/ 118 | 119 | https://www.johndcook.com/blog/2021/05/03/multicolor-reproducing-ca/ 120 | -------------------------------------------------------------------------------- /cellpylib/sdsr_loop.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from .langtons_loop import LangtonsLoop 3 | 4 | 5 | class SDSRLoop(LangtonsLoop): 6 | """ 7 | An implementation of H. Sayama's SDSR loop. For more information, see: 8 | 9 | .. code-block:: text 10 | 11 | Sayama, H. (1998). Constructing evolutionary systems on a simple deterministic cellular automata space. 12 | PhD, University of Tokyo, Department of Information Science. 13 | """ 14 | def __init__(self): 15 | """ 16 | Create an SDSR Loop. 17 | """ 18 | super().__init__() 19 | 20 | # Define rule '11152->8' and its rotationally symmetric ones. 21 | self._rule_table[(1, 1, 1, 5, 2)] = 8 22 | self._rule_table[(1, 2, 1, 1, 5)] = 8 23 | self._rule_table[(1, 5, 2, 1, 1)] = 8 24 | self._rule_table[(1, 1, 5, 2, 1)] = 8 25 | 26 | def __call__(self, n, c, t): 27 | """ 28 | From: 29 | Sayama, H. (1998). Constructing evolutionary systems on a simple deterministic cellular automata space. 30 | PhD, University of Tokyo, Department of Information Science. 31 | 32 | :param n: the neighbourhood 33 | 34 | :param c: the index of the current cell 35 | 36 | :param t: the current timestep 37 | 38 | :return: the activity of the current cell at the next timestep 39 | """ 40 | current_activity = n[1][1] 41 | top = n[0][1] 42 | right = n[1][2] 43 | bottom = n[2][1] 44 | left = n[1][0] 45 | key = (current_activity, top, right, bottom, left) 46 | 47 | if key not in self._rule_table: 48 | trbl = (top, right, bottom, left) 49 | in_tube = self._is_in_tube(top, right, bottom, left) 50 | new_activity = None 51 | 52 | # Let 0->1 if it is in the tube and next to 1. Let all other 0s remain as is. 53 | if current_activity == 0: 54 | if in_tube and 1 in trbl: 55 | new_activity = 1 56 | else: 57 | new_activity = 0 58 | 59 | # Let 1->7 if it is in the tube and next to 7. Else, let 1->6 if it is in the tube and next to 6. 60 | # Else, let 1->4 if it is in the tube and next to 4. 61 | if current_activity == 1 and in_tube: 62 | if 7 in trbl: 63 | new_activity = 7 64 | elif 6 in trbl: 65 | new_activity = 6 66 | elif 4 in trbl: 67 | new_activity = 4 68 | 69 | # Let 4,6,7->0 if it is in the tube and next to 0. 70 | if current_activity in (4, 6, 7) and in_tube and 0 in trbl: 71 | new_activity = 0 72 | 73 | # Let 2->1 if it is next to 3. Else, let 2 remain as is if it is next to another 2. 74 | if current_activity == 2 and 3 in trbl: 75 | new_activity = 1 76 | elif current_activity == 2 and 2 in trbl: 77 | new_activity = 2 78 | 79 | # Let 8->0 with no condition. 80 | if current_activity == 8: 81 | new_activity = 0 82 | 83 | # To all the undefined situations in whose four neighbourhood (TRBL) there is at least one site in state 8, 84 | # apply the following: 85 | if 8 in trbl: 86 | # Let 0,1->8 if there is at least one site in state 2,3,...,7 in its four neighbourhood (TRBL), 87 | # otherwise let 0->0 and 1->1 88 | if current_activity == 0 or current_activity == 1: 89 | if np.any([i in trbl for i in (2, 3, 4, 5, 6, 7)]): 90 | new_activity = 8 91 | elif current_activity == 0: 92 | new_activity = 0 93 | elif current_activity == 1: 94 | new_activity = 1 95 | 96 | # Let 2,3,5->0. 97 | if current_activity in (2, 3, 5): 98 | new_activity = 0 99 | 100 | # Let 4,6,7->1. 101 | if current_activity in (4, 6, 7): 102 | new_activity = 1 103 | 104 | # Clear up all the undefined situations by letting 0->0 and 1,2,...,7->8. 105 | if new_activity is None and current_activity == 0: 106 | new_activity = 0 107 | if new_activity is None and current_activity in (1, 2, 3, 4, 5, 6, 7): 108 | new_activity = 8 109 | 110 | return new_activity 111 | 112 | return self._rule_table[key] 113 | 114 | def _is_in_tube(self, top, right, bottom, left): 115 | k = 0 116 | for site in [top, right, bottom, left]: 117 | if site in (1, 2, 4, 6, 7): 118 | k += 1 119 | return k >= 2 120 | -------------------------------------------------------------------------------- /doc/sandpile.rst: -------------------------------------------------------------------------------- 1 | Sandpiles 2 | --------- 3 | 4 | A sandpile is a cellular automaton and dynamical system that displays self-organized criticality. It was introduced by 5 | Bak, Tang and Wiesenfeld in 1987. 6 | 7 | Below is an example of a sandpile using the built-in :py:class:`~cellpylib.sandpile.Sandpile` class. The boundary of 8 | the 2D CA can be either closed or open. If the boundary is closed, then all boundary cells should have a value of 0. 9 | 10 | .. code-block:: 11 | 12 | import cellpylib as cpl 13 | import numpy as np 14 | 15 | n_rows = 45 16 | n_cols = 45 17 | sandpile = cpl.Sandpile(n_rows, n_cols) 18 | 19 | ca = np.random.randint(5, size=n_rows*n_cols).reshape((1, n_rows, n_cols)) 20 | # we're using a closed boundary, so make the boundary cells 0 21 | ca[0, 0, :], ca[0, n_rows-1, :], ca[0, :, 0], ca[0, :, n_cols-1] = 0, 0, 0, 0 22 | 23 | ca = cpl.evolve2d(ca, timesteps=50, apply_rule=sandpile, neighbourhood="von Neumann") 24 | 25 | cpl.plot2d_animate(ca) 26 | 27 | 28 | .. image:: _static/sandpile.gif 29 | :width: 500 30 | 31 | Note that in the example above, the number of timesteps is fixed at `50`. However, for any random initial conditions, it 32 | isn't obvious how many timesteps are necessary for the system to reach a stable state, or fixed point, in its evolution. 33 | The library provides a function, :py:func:`~cellpylib.ca_functions.until_fixed_point`, that can be called to provide a 34 | callable for the ``timesteps`` argument of the :py:func:`~cellpylib.ca_functions2d.evolve2d` function. By calling this 35 | function, instead of providing a fixed number, the sandpile will evolve until there is no further change in the state of 36 | the system. Below is an example demonstrating this: 37 | 38 | .. code-block:: 39 | 40 | import cellpylib as cpl 41 | import numpy as np 42 | np.random.seed(0) 43 | 44 | n_rows = 45 45 | n_cols = 45 46 | sandpile = cpl.Sandpile(n_rows, n_cols) 47 | 48 | initial = np.random.randint(5, size=n_rows*n_cols).reshape((1, n_rows, n_cols)) 49 | # we're using a closed boundary, so make the boundary cells 0 50 | initial[0, 0, :], initial[0, n_rows-1, :], initial[0, :, 0], initial[0, :, n_cols-1] = 0, 0, 0, 0 51 | 52 | ca = cpl.evolve2d(initial, timesteps=cpl.until_fixed_point(), 53 | apply_rule=sandpile, neighbourhood="von Neumann") 54 | 55 | print("Number of timesteps to reach fixed point: %s" % len(ca)) 56 | cpl.plot2d_animate(ca) 57 | 58 | The above program will print out the following: 59 | 60 | .. code-block:: 61 | 62 | Number of timesteps to reach fixed point: 51 63 | 64 | If one perturbs a sandpile that has reached a fixed point in its evolution, then the sandpile should reconfigure itself 65 | and eventually reach a fixed point once again. This can be demonstrated using the 66 | :py:func:`~cellpylib.sandpile.Sandpile.add_grain` function of the :py:class:`~cellpylib.sandpile.Sandpile` class. In the 67 | example below, we begin with a sandpile that has reached a fixed point (this configuration is assumed to exist in a file 68 | called `sandpile_add_grain_demo.txt`, located in the same directory as the program). We drop a grain of sand on the cell 69 | located at row with index `23` and column with index `23` at timestep `1`. 70 | 71 | .. code-block:: 72 | 73 | import cellpylib as cpl 74 | import numpy as np 75 | 76 | n_rows = 45 77 | n_cols = 45 78 | sandpile = cpl.Sandpile(n_rows, n_cols) 79 | sandpile.add_grain(cell_index=(23, 23), timestep=1) 80 | 81 | initial = np.loadtxt('sandpile_add_grain_demo.txt', dtype=int) 82 | initial = np.array([initial]) 83 | 84 | ca = cpl.evolve2d(initial, timesteps=cpl.until_fixed_point(), 85 | apply_rule=sandpile, neighbourhood="von Neumann") 86 | 87 | print("Number of timesteps to reach fixed point: %s" % len(ca)) 88 | cpl.plot2d_animate(ca) 89 | 90 | 91 | .. image:: _static/sandpile_add_grain.gif 92 | :width: 500 93 | 94 | Imagine dropping a single grain of sand repeatedly on the same cell in the center of a grid, allowing the sandpile to 95 | attain a stable state (i.e. fixed point) before dropping the next grain. This can be demonstrated with the following 96 | code snippet: 97 | 98 | .. code-block:: 99 | 100 | import cellpylib as cpl 101 | 102 | n = 50 103 | sandpile = cpl.Sandpile(n, n) 104 | ca = cpl.init_simple2d(n, n, val=5) 105 | 106 | for i in range(300): 107 | ca[-1, n//2, n//2] += 1 108 | ca = cpl.evolve2d(ca, apply_rule=sandpile, 109 | timesteps=cpl.until_fixed_point(), neighbourhood='Moore') 110 | 111 | cpl.plot2d_animate(ca) 112 | 113 | .. image:: _static/sandpile_growing.gif 114 | 115 | Above, we take advantage of the fact that the `ca` argument to :py:func:`~cellpylib.ca_functions2d.evolve2d` can 116 | contain a history of prior states, and that the evolution continues from the last state. 117 | 118 | **References:** 119 | 120 | *Bak, Per, Chao Tang, and Kurt Wiesenfeld. "Self-organized criticality." Physical review A 38.1 (1988): 364.* 121 | 122 | https://en.wikipedia.org/wiki/Abelian_sandpile_model 123 | -------------------------------------------------------------------------------- /cellpylib/bien.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from .entropy import shannon_entropy 4 | 5 | 6 | def binary_derivative(string): 7 | """ 8 | Calculates the binary derivative of the given string, according to: 9 | 10 | .. code-block:: text 11 | 12 | Nathanson, M. B. (1971). Derivatives of binary sequences. 13 | SIAM Journal on Applied Mathematics, 21(3), 407-412 14 | 15 | The derivative of a binary string is simply the bitwise exclusive OR between a binary digit in the string and 16 | its successor, if a successor exists. For example, the derivative of the binary string '01010101' is the binary 17 | string '1111111'. 18 | 19 | :param string: a binary string, such as '110011' 20 | 21 | :return: a binary string representing the binary derivative of the given string 22 | """ 23 | result = [] 24 | for i, d in enumerate(string): 25 | if i - 1 == len(string) - 2: 26 | break 27 | result.append(int(string[i]) ^ int(string[i + 1])) 28 | return ''.join([str(x) for x in result]) 29 | 30 | 31 | def bien(string): 32 | """ 33 | Calculate the BiEntropy of the given binary string, according to: 34 | 35 | .. code-block:: text 36 | 37 | Croll, G. J. (2013). BiEntropy-The Approximate Entropy of a Finite Binary String. 38 | arXiv preprint arXiv:1305.0954. 39 | 40 | The BiEntropy can be used to compare two binary strings in terms of the relative order and disorder of all the 41 | digits. It makes use of a weighted average of the Shannon entropies of all but the last binary derivative of the 42 | given string. This version of BiEntropy is suitable for strings with length <= 32. 43 | 44 | :param string: a binary string, such as '110011' 45 | 46 | :return: a real number representing the BiEntropy of the given string 47 | """ 48 | tot = 0.0 49 | n = len(string) 50 | for k in range(n - 1): 51 | tot += shannon_entropy(string) * 2**k 52 | string = binary_derivative(string) 53 | return (1 / (2**(n - 1) - 1)) * tot 54 | 55 | 56 | def tbien(string): 57 | """ 58 | Calculates the logarithmic weighting BiEntropy of the given string, according to: 59 | 60 | .. code-block:: text 61 | 62 | Croll, G. J. (2013). BiEntropy-The Approximate Entropy of a Finite Binary String. 63 | arXiv preprint arXiv:1305.0954. 64 | 65 | The Logarithmic BiEntropy can be used to compare two binary strings in terms of the relative order and disorder of 66 | all the digits. It makes use of a logarithmic weighted average of the Shannon entropies of all but the last binary 67 | derivative of the given string. This version of BiEntropy is suitable for strings with length > 32. 68 | 69 | :param string: a binary string, such as '110011' 70 | 71 | :return: a real number representing the logarithmic weighting BiEntropy of the given string 72 | """ 73 | tot = 0.0 74 | tot_log = 0.0 75 | n = len(string) 76 | for k in range(n - 1): 77 | lg = math.log(k + 2, 2.0) 78 | tot += shannon_entropy(string) * lg 79 | tot_log += lg 80 | string = binary_derivative(string) 81 | return (1 / tot_log) * tot 82 | 83 | 84 | def cyclic_binary_derivative(string): 85 | """ 86 | Calculates the cyclic binary derivative, which is the "binary string of length n formed by XORing adjacent pairs of 87 | digits including the last and the first." See: 88 | 89 | .. code-block:: text 90 | 91 | Croll, G. J. (2018). The BiEntropy of Some Knots on the Simple Cubic Lattice. 92 | arXiv preprint arXiv:1802.03772. 93 | 94 | The cyclic derivative of a binary string is simply the bitwise exclusive OR between a binary digit in the string and 95 | its successor, where the successor of the last digit in the string is the first digit in the string. For example, 96 | the cyclic derivative of the binary string '01010101' is the binary string '11111111'. 97 | 98 | :param string: a binary string, such as '110011' 99 | 100 | :return: a binary string representing the cyclic binary derivative of the given string 101 | """ 102 | result = [] 103 | for i, d in enumerate(string): 104 | s = string[i] 105 | if i == len(string) - 1: 106 | next_s = string[0] 107 | else: 108 | next_s = string[i + 1] 109 | result.append(int(s) ^ int(next_s)) 110 | return ''.join([str(x) for x in result]) 111 | 112 | 113 | def ktbien(string): 114 | """ 115 | Calculates the knot logarithmic weighting BiEntropy of the given string, according to: 116 | 117 | .. code-block:: text 118 | 119 | Croll, G. J. (2018). The BiEntropy of Some Knots on the Simple Cubic Lattice. 120 | arXiv preprint arXiv:1802.03772. 121 | 122 | The Logarithmic Knot BiEntropy can be used to compare two binary strings in terms of the relative order and disorder 123 | of all the digits. It makes use of a logarithmic weighted average of the Shannon entropies of all but the last 124 | cyclic binary derivative of the given string. This version of BiEntropy is suitable for strings with length > 32. 125 | 126 | :param string: a binary string, such as '110011' 127 | 128 | :return: a real number representing the knot logarithmic weighting BiEntropy of the given string 129 | """ 130 | tot = 0.0 131 | tot_log = 0.0 132 | n = len(string) 133 | for k in range(n - 1): 134 | lg = math.log(k + 2, 2.0) 135 | tot += shannon_entropy(string) * lg 136 | tot_log += lg 137 | string = cyclic_binary_derivative(string) 138 | return (1 / tot_log) * tot 139 | -------------------------------------------------------------------------------- /doc/block_ca.rst: -------------------------------------------------------------------------------- 1 | Block CA and The Second Law 2 | --------------------------- 3 | 4 | In a typical CA, each cell is processed one-by-one, taking into account its surrounding neighbourhood cells. However, in 5 | Block CA, the cells are processed in groups known as blocks. The rules of Block CA do not act on a single cell, but 6 | rather act on each block of cells as a whole. With this format, it becomes easier to set up rules which are reversible 7 | and which conserve quantities, such as cell colors. This makes Block CA useful for emulating physical systems. 8 | 9 | Below is an example of a Block CA implemented with CellPyLib, which makes use of the built-in function 10 | :py:func:`~cellpylib.ca_functions.evolve_block`. This CA was taken from Stephen Wolfram's book `A New Kind of Science`, 11 | on page 460: 12 | 13 | .. code-block:: 14 | 15 | import cellpylib as cpl 16 | import numpy as np 17 | 18 | initial_conditions = np.array([[0]*13 + [1]*2 + [0]*201]) 19 | 20 | def block_rule(n, t): 21 | if n == (1, 1): return 1, 1 22 | elif n == (1, 0): return 1, 0 23 | elif n == (0, 1): return 0, 0 24 | elif n == (0, 0): return 0, 1 25 | 26 | ca = cpl.evolve_block(initial_conditions, block_size=2, 27 | timesteps=200, apply_rule=block_rule) 28 | 29 | cpl.plot(ca) 30 | 31 | .. image:: _static/block_ca_1d.png 32 | :width: 450 33 | :align: center 34 | 35 | Block CA can be comprised of more than two colors, as the following example demonstrates, taken from Stephen Wolfram's 36 | book `A New Kind of Science`, on the bottom of page 462: 37 | 38 | .. code-block:: 39 | 40 | import cellpylib as cpl 41 | import numpy as np 42 | 43 | initial_conditions = np.array([[0]*30 + [2]*30 + [0]*30]) 44 | 45 | def block_rule(n, t): 46 | if n == (1, 1): return 1, 1 47 | elif n == (1, 0): return 0, 2 48 | elif n == (0, 1): return 2, 0 49 | elif n == (0, 0): return 0, 0 50 | elif n == (2, 2): return 2, 2 51 | elif n == (2, 0): return 1, 0 52 | elif n == (0, 2): return 0, 1 53 | elif n == (2, 1): return 2, 1 54 | elif n == (1, 2): return 1, 2 55 | 56 | ca = cpl.evolve_block(initial_conditions, block_size=2, 57 | timesteps=4500, apply_rule=block_rule) 58 | 59 | cpl.plot(ca[-500:]) 60 | 61 | .. image:: _static/block_ca_1d_2.png 62 | :width: 200 63 | :align: center 64 | 65 | The Second Law 66 | ~~~~~~~~~~~~~~ 67 | 68 | In a series of `articles `_, 69 | Stephen Wolfram shared his thoughts on the computational origins of the Second Law of 70 | Thermodynamics. In the first part of the series, he illustrates his ideas with a number of different Block CA, such as 71 | the one shown here: 72 | 73 | .. image:: _static/block_ca_1d_3.png 74 | :width: 200 75 | :align: center 76 | 77 | The code for this Block CA is given below: 78 | 79 | .. code-block:: 80 | 81 | import cellpylib as cpl 82 | import numpy as np 83 | 84 | initial_conditions = np.array([[0]*25 + [2]*17 + [0]*24]) 85 | 86 | def block_rule(n, t): 87 | if n == (1, 1): return 2, 2 88 | elif n == (1, 0): return 1, 0 89 | elif n == (0, 1): return 0, 1 90 | elif n == (0, 0): return 0, 0 91 | elif n == (2, 2): return 1, 1 92 | elif n == (2, 0): return 0, 2 93 | elif n == (0, 2): return 2, 0 94 | elif n == (2, 1): return 2, 1 95 | elif n == (1, 2): return 1, 2 96 | 97 | ca = cpl.evolve_block(initial_conditions, block_size=2, 98 | timesteps=200, apply_rule=block_rule) 99 | 100 | cpl.plot(ca) 101 | 102 | Simple cellular automata systems with reversible and color-conserving rules give rise to the same "randomization" 103 | seen in physical systems consisting of diffusing particles, as this "gas-like" 2D Block CA demonstrates: 104 | 105 | .. image:: _static/block_ca_2d.gif 106 | :width: 500 107 | :align: center 108 | 109 | CellPyLib supports 2D Block CA through the built-in function :py:func:`~cellpylib.ca_functions2d.evolve2d_block`. 110 | The code for this 2D Block CA is given below: 111 | 112 | .. code-block:: 113 | 114 | import cellpylib as cpl 115 | import numpy as np 116 | 117 | # visit https://github.com/lantunes/cellpylib/tree/master/demos 118 | # for the initial conditions file 119 | initial_conditions = np.loadtxt('block2d_rotated_initial_conditions.txt', dtype=int) 120 | initial_conditions = np.array([initial_conditions]) 121 | 122 | def make_block2d_rule(): 123 | base_rules = { 124 | ((0, 0), (0, 0)): ((0, 0), (0, 0)), 125 | ((0, 0), (0, 2)): ((2, 0), (0, 0)), 126 | ((2, 0), (0, 0)): ((0, 0), (0, 2)), 127 | ((0, 0), (2, 0)): ((0, 2), (0, 0)), 128 | ((0, 2), (0, 0)): ((0, 0), (2, 0)), 129 | ((0, 0), (2, 2)): ((2, 2), (0, 0)), 130 | ((2, 2), (0, 0)): ((0, 0), (2, 2)), 131 | ((0, 2), (0, 2)): ((2, 0), (2, 0)), 132 | ((2, 0), (2, 0)): ((0, 2), (0, 2)), 133 | ((0, 2), (2, 0)): ((2, 0), (0, 2)), 134 | ((2, 0), (0, 2)): ((0, 2), (2, 0)), 135 | ((0, 2), (2, 2)): ((2, 2), (2, 0)), 136 | ((2, 2), (2, 0)): ((0, 2), (2, 2)), 137 | ((2, 0), (2, 2)): ((2, 2), (0, 2)), 138 | ((2, 2), (0, 2)): ((2, 0), (2, 2)), 139 | ((2, 2), (2, 2)): ((2, 2), (2, 2)), 140 | # wall rules 141 | ((0, 0), (1, 1)): ((0, 0), (1, 1)), 142 | ((0, 1), (1, 1)): ((0, 1), (1, 1)), 143 | ((0, 2), (1, 1)): ((2, 0), (1, 1)), 144 | ((2, 0), (1, 1)): ((0, 2), (1, 1)), 145 | ((2, 1), (1, 1)): ((2, 1), (1, 1)), 146 | ((2, 2), (1, 1)): ((2, 2), (1, 1)), 147 | ((1, 1), (1, 1)): ((1, 1), (1, 1)), 148 | ((1, 0), (0, 0)): ((1, 0), (0, 0)), 149 | ((1, 0), (0, 2)): ((1, 0), (0, 2)), 150 | } 151 | rules = {} 152 | # add rotations 153 | for r, v in base_rules.items(): 154 | rules[r] = v 155 | for _ in range(3): 156 | r = ((r[1][0], r[0][0]), (r[1][1], r[0][1])) 157 | v = ((v[1][0], v[0][0]), (v[1][1], v[0][1])) 158 | if r not in rules: 159 | rules[r] = v 160 | def _apply_rule(n, t): 161 | n = tuple(tuple(i) for i in n) 162 | return rules[n] 163 | return _apply_rule 164 | 165 | ca = cpl.evolve2d_block(initial_conditions, block_size=(2, 2), 166 | timesteps=251, apply_rule=make_block2d_rule()) 167 | 168 | cpl.plot2d_animate(ca) 169 | 170 | 171 | **References:** 172 | 173 | https://en.wikipedia.org/wiki/Block_cellular_automaton 174 | 175 | https://www.wolframscience.com/nks/p459--conserved-quantities-and-continuum-phenomena/ 176 | 177 | https://writings.stephenwolfram.com/2023/02/computational-foundations-for-the-second-law-of-thermodynamics/ 178 | -------------------------------------------------------------------------------- /cellpylib/rule_tables.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | import numpy as np 4 | 5 | 6 | def table_rule(neighbourhood, table): 7 | """ 8 | A rule where the state is converted into a string, and looked up in the given table, to yield the return value. 9 | 10 | :param neighbourhood: a k-color array of length 2r + 1 11 | 12 | :param table: a table (map) of string representations of each neighbourhood state to the associated next 13 | cell state value; for example, for k = 2 and r = 2, a valid table might be: 14 | {'101': 1, '111': 0, '011': 0, '110': 1, '000': 0, '100': 0, '010': 0, '001': 1} 15 | 16 | :return: a number, from 0 to k - 1, associated with the given state as specified in the given table 17 | """ 18 | state_repr = ''.join(str(x) for x in neighbourhood) 19 | if not state_repr in table: 20 | raise ValueError("could not find state '%s' in table" % state_repr) 21 | return table[state_repr] 22 | 23 | 24 | def random_rule_table(k, r, lambda_val=None, quiescent_state=None, strong_quiescence=False, isotropic=False): 25 | """ 26 | Constructs and returns a random rule table using the "random-table" method, as described in: 27 | 28 | .. code-block:: text 29 | 30 | Langton, C. G. (1990). Computation at the edge of chaos: phase transitions 31 | and emergent computation. 32 | Physica D: Nonlinear Phenomena, 42(1-3), 12-37. 33 | 34 | :param k: the number of cell states 35 | 36 | :param r: the radius of the cellular automaton neighbourhood 37 | 38 | :param lambda_val: a real number in (0., 1.), representing the value of lambda; if None, a default value of 39 | 1.0 - 1/k will be used, where all states will be represented equally in the rule table 40 | 41 | :param quiescent_state: the state, a number in {0,...,k - 1}, to use as the quiescent state 42 | 43 | :param strong_quiescence: if True, all neighbourhood states uniform in cell state i will map to cell state i 44 | 45 | :param isotropic: if True, all planar rotations of a neighbourhood state will map to the same cell state 46 | 47 | :return: a tuple containing: a table describing a rule, constructed using the "random-table" table method as 48 | described by C. G. Langton, the actual lambda value, and the quiescent state used 49 | """ 50 | states = [] 51 | n = 2*r + 1 52 | for i in range(0, k**n): 53 | states.append(np.base_repr(i, k).zfill(n)) 54 | table = {} 55 | if lambda_val is None: 56 | lambda_val = 1. - (1. / k) 57 | if quiescent_state is None: 58 | quiescent_state = np.random.randint(k, dtype=np.int32) 59 | if not (0 <= quiescent_state <= k - 1): 60 | raise ValueError("quiescent state must be a number in {0,...,k - 1}") 61 | other_states = [x for x in range(0, k) if x != quiescent_state] 62 | quiescent_state_count = 0 63 | for state in states: 64 | if strong_quiescence and len(set(state)) == 1: 65 | # if the cell states in neighbourhood are all the same, e.g. '111' 66 | cell_state = int(state[0], k) 67 | if cell_state == quiescent_state: quiescent_state_count += 1 68 | else: 69 | state_reversed = state[::-1] 70 | if isotropic and state_reversed in table: 71 | cell_state = table[state_reversed] 72 | if cell_state == quiescent_state: quiescent_state_count += 1 73 | else: 74 | if random.random() < (1. - lambda_val): 75 | cell_state = quiescent_state 76 | quiescent_state_count += 1 77 | else: 78 | cell_state = random.choice(other_states) 79 | table[state] = cell_state 80 | actual_lambda_val = (k**n - quiescent_state_count) / k**n 81 | return table, actual_lambda_val, quiescent_state 82 | 83 | 84 | def table_walk_through(rule_table, lambda_val, k, r, quiescent_state, strong_quiescence=False, isotropic=False): 85 | """ 86 | Perturbs the given rule table using the "table-walk-through" approach described in: 87 | 88 | .. code-block:: text 89 | 90 | Langton, C. G. (1990). Computation at the edge of chaos: phase transitions 91 | and emergent computation. 92 | Physica D: Nonlinear Phenomena, 42(1-3), 12-37. 93 | 94 | The table's actual lambda value will be increased or decreased, incrementally and stochastically, until it reaches 95 | the given lambda value. 96 | 97 | :param rule_table: a table (map) of string representations of each neighbourhood state to the associated next 98 | cell state value; for example, for k = 2 and r = 2, a valid table might be: 99 | {'101': 1, '111': 0, '011': 0, '110': 1, '000': 0, '100': 0, '010': 0, '001': 1} 100 | 101 | :param lambda_val: a real number in (0., 1.), representing the value of lambda 102 | 103 | :param k: the number of cell states 104 | 105 | :param r: the radius of the cellular automaton neighbourhood 106 | 107 | :param quiescent_state: the state, a number in {0,...,k - 1}, to use as the quiescent state 108 | 109 | :param strong_quiescence: if True, all neighbourhood states uniform in cell state i will map to cell state i 110 | 111 | :param isotropic: if True, all planar rotations of a neighbourhood state will map to the same cell state 112 | 113 | :return: a tuple containing: a table describing a rule, constructed using the "table-walk-through" method as 114 | described by C. G. Langton, the actual lambda value 115 | """ 116 | def actual_lambda(): 117 | n = 2*r + 1 118 | transitions_to_quiescent_state = list(rule_table.values()).count(quiescent_state) 119 | return (k**n - transitions_to_quiescent_state) / k**n 120 | actual_lambda_val = actual_lambda() 121 | if actual_lambda_val == lambda_val: 122 | return rule_table, actual_lambda_val 123 | if actual_lambda_val > lambda_val: 124 | # reduce lambda 125 | attempts = 0 126 | while actual_lambda() > lambda_val and attempts < len(rule_table): 127 | attempts += 1 128 | states_to_others = [k for k in rule_table.keys() if rule_table[k] != quiescent_state] 129 | if strong_quiescence: 130 | # remove states that are all the same (i.e. '111'); presumably the strong quiescence condition is already 131 | # enforced in the incoming rule table, and so these states shouldn't be changed 132 | states_to_others = [s for s in states_to_others if len(set(s)) != 1] 133 | if len(states_to_others) == 0: 134 | break 135 | state_to_perturb = random.choice(states_to_others) 136 | rule_table[state_to_perturb] = quiescent_state 137 | if isotropic: 138 | state_to_perturb_reversed = state_to_perturb[::-1] 139 | rule_table[state_to_perturb_reversed] = rule_table[state_to_perturb] 140 | elif actual_lambda_val < lambda_val: 141 | # increase lambda 142 | attempts = 0 143 | while actual_lambda() < lambda_val and attempts < len(rule_table): 144 | attempts += 1 145 | states_to_quiescent = [k for k in rule_table.keys() if rule_table[k] == quiescent_state] 146 | if strong_quiescence: 147 | # remove states that are all the same (i.e. '111') 148 | states_to_quiescent = [s for s in states_to_quiescent if len(set(s)) != 1] 149 | if len(states_to_quiescent) == 0: 150 | break 151 | state_to_perturb = random.choice(states_to_quiescent) 152 | other_states = [x for x in range(0, k) if x != quiescent_state] 153 | rule_table[state_to_perturb] = random.choice(other_states) 154 | if isotropic: 155 | state_to_perturb_reversed = state_to_perturb[::-1] 156 | rule_table[state_to_perturb_reversed] = rule_table[state_to_perturb] 157 | return rule_table, actual_lambda() 158 | -------------------------------------------------------------------------------- /demos/block2d_rotated_initial_conditions.txt: -------------------------------------------------------------------------------- 1 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 4 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 5 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 6 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 7 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 9 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 10 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 11 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 12 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 13 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 14 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 15 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 16 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 17 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 18 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 19 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 20 | 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 21 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 22 | 1 1 1 1 1 1 1 1 1 1 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 0 1 1 1 1 1 1 1 1 1 1 1 1 23 | 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 24 | 1 1 1 1 1 1 1 1 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 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 25 | 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 26 | 1 1 1 1 1 1 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 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 27 | 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 28 | 1 1 1 1 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 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 29 | 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 30 | 1 1 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 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 31 | 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 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 32 | 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 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 33 | 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 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 34 | 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 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 35 | 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 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 36 | 1 1 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 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 37 | 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 38 | 1 1 1 1 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 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 39 | 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 40 | 1 1 1 1 1 1 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 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 41 | 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 42 | 1 1 1 1 1 1 1 1 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 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 43 | 1 1 1 1 1 1 1 1 1 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 0 0 0 1 1 1 1 1 1 1 1 1 1 1 44 | 1 1 1 1 1 1 1 1 1 1 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 0 1 1 1 1 1 1 1 1 1 1 1 1 45 | 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 46 | 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 47 | 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 48 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 49 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 50 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 51 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 52 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 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 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 53 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 54 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 55 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 56 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 57 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 58 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 59 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 60 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 61 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 62 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 63 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 64 | 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -------------------------------------------------------------------------------- /paper/paper.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 'CellPyLib: A Python Library for working with Cellular Automata' 3 | tags: 4 | - Python 5 | - Cellular Automata 6 | - complex systems 7 | - non-linear dynamics 8 | - discrete dynamical systems 9 | authors: 10 | - name: Luis M. Antunes 11 | orcid: 0000-0002-4867-5635 12 | affiliation: 1 13 | affiliations: 14 | - name: Department of Chemistry, University of Reading, Whiteknights, Reading RG6 6DX, United Kingdom 15 | index: 1 16 | date: 28 July 2021 17 | bibliography: paper.bib 18 | --- 19 | 20 | # Summary 21 | 22 | Cellular Automata (CA) are discrete dynamical systems with a rich history [@ilachinski2001cellular]. Introduced by John 23 | von Neumann and Stanislaw Ulam in the 1940s [@von1951general], CA have continued to fascinate, as their conceptual 24 | simplicity serves as a powerful microscope that allows us to explore the nature of computation and complexity, and the 25 | origins of emergence. Far from being an antiquated computational model, investigators are utilizing CA in novel and 26 | creative ways, such as the incorporation with Deep Learning [@nichele2017deep; @mordvintsev2020growing]. Popularized 27 | and investigated by Stephen Wolfram in his book *A New Kind of Science* [@wolfram2002new], CA remain premier reminders 28 | of a common theme in the study of the physical world: that simple systems and rules can give rise to remarkable 29 | complexity. They are a laboratory for the study of the origins of the complexity we see in the world around us. 30 | 31 | `CellPyLib` is a Python library for working with CA. It provides a concise and simple interface for defining and 32 | analyzing 1- and 2-dimensional CA. The CA can consist of discrete or continuous states. Neighbourhood radii are 33 | adjustable, and in the 2-dimensional case, both Moore and von Neumann neighbourhoods are supported. With `CellPyLib`, it 34 | is trivial to create Elementary CA, and CA with totalistic rules, as these rules are provided as part of the library. 35 | Additionally, the library provides a means for creating asynchronous and reversible CA. Finally, an implementation 36 | of C. G. Langton's approach for creating CA rules using the lambda value is provided, allowing for the exploration of 37 | complex systems, phase transitions and emergent computation [@langton1990computation]. 38 | 39 | Utility functions for plotting and viewing the evolved CA are also provided. These tools make it easy to visualize the 40 | results of CA evolution, and include the option of creating animations of the evolution itself. Moreover, utility 41 | functions for computing the information-theoretic properties of CA, such as the Shannon entropy and mutual information, 42 | are included. 43 | 44 | # Statement of need 45 | 46 | The Python software ecosystem is lacking when it comes to Cellular Automata. A web search reveals that while there are 47 | some projects dedicated to the simulation of CA, most are not general-purpose, focusing only on certain CA systems, and 48 | are generally missing a substantial test suite, hindering their future extensibility and maintainability. In short, 49 | there appears to be a dearth of robust and flexible libraries for working with CA in Python. 50 | 51 | Currently, many scientists choose Python as their main tool for computational tasks. Though researchers can choose to 52 | implement CA themselves, this is error-prone, as there are some subtleties when it comes to correctly handling issues 53 | such as boundary conditions on periodic lattices, or constructing von Neumann neighbourhoods with radius greater than 1, 54 | for example. Researchers may be dissuaded from incorporating CA into their research if they are forced to work with 55 | unfamiliar languages and technologies, or are required to devote considerable effort to the implementation and testing 56 | of non-trivial algorithms. The availability of a user-friendly Python library for CA will likely encourage more 57 | researchers to consider these fascinating dynamical and computational systems. Moreover, having a standard 58 | implementation of CA in the Python environment helps to ensure that scientific results are reproducible. CellPyLib is a 59 | Python library aimed to meet this need, which supports the creation and analysis of models that exist on a regular 60 | array or uniform grid, such as elementary CA, and 2D CA with Moore or von Neumann neighbourhoods. 61 | 62 | Researchers and students alike should find CellPyLib useful. Students and instructors can use CellPyLib in an 63 | educational context if they would like to learn about elementary CA and 2D CA on a uniform grid. Researchers in both the 64 | computer and physical sciences can use CellPyLib to answer serious questions about the computational and natural worlds. 65 | For example, the Abelian sandpile model included in the library can be used as part of a university course on complex 66 | systems to demonstrate the phenomenon of self-organized criticality. The same model may be used by professional 67 | physicists wishing to explore self-organized criticality more deeply. 68 | 69 | While CellPyLib is expected to be of interest to students, educators, and researchers, there are certain scenarios in 70 | which alternate tools would be more appropriate. For example, if a researcher would like to evolve CA with a very large 71 | number of cells, or for very many iterations, in a timely fashion, then an implementation that is optimized for the 72 | specific model in question would be more appropriate. Also, if the model is not constrained to a uniform grid, then 73 | other solutions should be sought. 74 | 75 | # Example Usage 76 | 77 | `CellPyLib` can be readily installed using `pip`: 78 | 79 | ``` 80 | $ pip install cellpylib 81 | ``` 82 | 83 | It has minimal dependencies, depending only on the commonly used libraries NumPy [@harris2020array] and Matplotlib 84 | [@Hunter:2007]. 85 | 86 | The following example illustrates the evolution of the Rule 30 CA, described in `A New Kind of Science` 87 | [@wolfram2002new], as implemented with `CellPyLib`: 88 | 89 | ```python 90 | import cellpylib as cpl 91 | 92 | cellular_automaton = cpl.init_simple(200) 93 | 94 | rule = lambda n, c, t: cpl.nks_rule(n, 30) 95 | 96 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100, 97 | apply_rule=rule) 98 | ``` 99 | 100 | First, the initial conditions are instantiated using the function `init_simple`, which, in this example, creates a 101 | 200-dimensional vector consisting of zeroes, except for the component in the center of the vector, which is initialized 102 | with a value of 1. Next, the system is subjected to evolution by calling the `evolve` function. The system evolves under 103 | the rule specified through the `apply_rule` parameter. Any function that accepts the three arguments `n`, `c` and `t` 104 | can be supplied as a rule, but in this case the built-in function `nks_rule` is invoked to provide Rule 30. The CA is 105 | evolved for 100 `timesteps`, or 100 applications of the rule to the initial and subsequent conditions. 106 | 107 | During each timestep, the function supplied to `apply_rule` is invoked for each cell. The `n` argument refers to the 108 | neighbourhood of the current cell, and consists of an array (in the 1-dimensional CA case) of the activities (i.e. 109 | states) of the cells comprising the current cell's neighbourhood (an array with length 3, in the case of a 1-dimensional 110 | CA with radius of 1). The `c` argument refers to index of the cell under consideration. It serves as a label identifying 111 | the current cell. The `t` argument is an integer specifying the current timestep. 112 | 113 | Finally, to visualize the results, the `plot` function can be utilized: 114 | 115 | ```python 116 | cpl.plot(cellular_automaton) 117 | ``` 118 | 119 | ![Rule 30, as rendered with CellPyLib.\label{fig:rule30}](rule30.png){ width=60% } 120 | 121 | The result is rendered, as depicted in \autoref{fig:rule30}. 122 | 123 | # Scope 124 | 125 | While `CellPyLib` is a general-purpose library that allows for the implementation of a wide variety of CA, it is 126 | important to note that CA constitute a very broad class of models. `CellPyLib` focuses on those that are constrained to 127 | a regular array or uniform grid, such as elementary CA, and 2D CA with Moore or von Neumann neighbourhoods. 128 | 129 | # References -------------------------------------------------------------------------------- /tests/test_apen.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import pytest 4 | import numpy as np 5 | 6 | import cellpylib as cpl 7 | 8 | 9 | class TestApproximateEntropy(unittest.TestCase): 10 | 11 | def test_apen_string(self): 12 | self.assertAlmostEqual(cpl.apen('112012210'), 0.845297799381828) 13 | self.assertAlmostEqual(cpl.apen('200022211'), 0.6720110042418417) 14 | self.assertAlmostEqual(cpl.apen('210222001'), 0.845297799381828) 15 | self.assertAlmostEqual(cpl.apen('101211011'), 0.7108955985334597) 16 | self.assertAlmostEqual(cpl.apen('102012212'), 0.8452977993818283) 17 | self.assertAlmostEqual(cpl.apen('101221002'), 0.8075424578717401) 18 | self.assertAlmostEqual(cpl.apen('111002012'), 0.8452977993818283) 19 | self.assertAlmostEqual(cpl.apen('202122111'), 0.7679050283924354) 20 | self.assertAlmostEqual(cpl.apen('011101220'), 0.6720110042418419) 21 | self.assertAlmostEqual(cpl.apen('011220201'), 0.6342556627317537) 22 | self.assertAlmostEqual(cpl.apen('222100001'), 0.4333181911312869) 23 | self.assertAlmostEqual(cpl.apen('212112021'), 0.5292122152818808) 24 | self.assertAlmostEqual(cpl.apen('020210222'), 0.5572868307502927) 25 | self.assertAlmostEqual(cpl.apen('110201211'), 0.9111277563603284) 26 | self.assertAlmostEqual(cpl.apen('022220112'), 0.6724349432497736) 27 | self.assertAlmostEqual(cpl.apen('111101112'), 0.38980394055976986) 28 | self.assertAlmostEqual(cpl.apen('120211111'), 0.5376088033934733) 29 | self.assertAlmostEqual(cpl.apen('221022012'), 0.9111277563603284) 30 | self.assertAlmostEqual(cpl.apen('020220021'), 0.5292122152818806) 31 | self.assertAlmostEqual(cpl.apen('000022102'), 0.5572868307502927) 32 | self.assertAlmostEqual(cpl.apen('001120021'), 0.8452977993818283) 33 | self.assertAlmostEqual(cpl.apen('120020002'), 0.3840000356103066) 34 | self.assertAlmostEqual(cpl.apen('121111011'), 0.7025554552711372) 35 | self.assertAlmostEqual(cpl.apen('002222201'), 0.4494060535808746) 36 | self.assertAlmostEqual(cpl.apen('220200202'), 0.5685207485814306) 37 | self.assertAlmostEqual(cpl.apen('011021100'), 0.7679050283924354) 38 | self.assertAlmostEqual(cpl.apen('122100221'), 0.6720110042418419) 39 | self.assertAlmostEqual(cpl.apen('002110121'), 0.8452977993818283) 40 | self.assertAlmostEqual(cpl.apen('020000220'), 0.6843741748545092) 41 | self.assertAlmostEqual(cpl.apen('011221122'), 0.594618233252449) 42 | self.assertAlmostEqual(cpl.apen('022121011'), 0.8452977993818283) 43 | self.assertAlmostEqual(cpl.apen('100212002'), 0.6720110042418421) 44 | self.assertAlmostEqual(cpl.apen('111101211'), 0.7025554552711372) 45 | self.assertAlmostEqual(cpl.apen('002010020'), 0.7108955985334596) 46 | self.assertAlmostEqual(cpl.apen('200211210'), 0.8075424578717401) 47 | self.assertAlmostEqual(cpl.apen('221101201'), 0.8452977993818283) 48 | self.assertAlmostEqual(cpl.apen('000021121'), 0.4333181911312871) 49 | self.assertAlmostEqual(cpl.apen('002110011'), 0.7679050283924354) 50 | self.assertAlmostEqual(cpl.apen('021210020'), 0.6720110042418419) 51 | self.assertAlmostEqual(cpl.apen('011212010'), 0.6720110042418419) 52 | self.assertAlmostEqual(cpl.apen('112012201'), 0.49872420910185533) 53 | self.assertAlmostEqual(cpl.apen('121012110'), 0.5645541660803557) 54 | self.assertAlmostEqual(cpl.apen('220121021'), 0.8452977993818285) 55 | self.assertAlmostEqual(cpl.apen('202011012'), 0.6342556627317537) 56 | self.assertAlmostEqual(cpl.apen('002101201'), 0.8452977993818285) 57 | self.assertAlmostEqual(cpl.apen('220002100'), 0.7305736258902786) 58 | self.assertAlmostEqual(cpl.apen('210021021'), 0.22227605448121235) 59 | self.assertAlmostEqual(cpl.apen('122201001'), 0.6342556627317537) 60 | self.assertAlmostEqual(cpl.apen('012210220'), 0.8452977993818285) 61 | self.assertAlmostEqual(cpl.apen('201200120'), 0.260031395991301) 62 | self.assertAlmostEqual(cpl.apen('112102202'), 0.845297799381828) 63 | self.assertAlmostEqual(cpl.apen('221120022'), 0.9111277563603284) 64 | self.assertAlmostEqual(cpl.apen('021000021'), 0.3258613529698009) 65 | self.assertAlmostEqual(cpl.apen('100222220'), 0.4494060535808748) 66 | self.assertAlmostEqual(cpl.apen('021010121'), 0.49872420910185555) 67 | self.assertAlmostEqual(cpl.apen('221202222'), 0.7025554552711372) 68 | self.assertAlmostEqual(cpl.apen('221012222'), 0.5376088033934734) 69 | self.assertAlmostEqual(cpl.apen('000122100'), 0.6724349432497738) 70 | self.assertAlmostEqual(cpl.apen('011020211'), 0.6720110042418419) 71 | self.assertAlmostEqual(cpl.apen('220112002'), 0.845297799381828) 72 | self.assertAlmostEqual(cpl.apen('020002222'), 0.6339267665519992) 73 | self.assertAlmostEqual(cpl.apen('212210200'), 0.8452977993818285) 74 | self.assertAlmostEqual(cpl.apen('010120120'), 0.26003139599130076) 75 | self.assertAlmostEqual(cpl.apen('010101100'), 0.5685207485814302) 76 | self.assertAlmostEqual(cpl.apen('101101002'), 0.5292122152818806) 77 | self.assertAlmostEqual(cpl.apen('222111100'), 0.4333181911312869) 78 | self.assertAlmostEqual(cpl.apen('011121010'), 0.622692848720861) 79 | self.assertAlmostEqual(cpl.apen('100011011'), 0.6993327845225673) 80 | self.assertAlmostEqual(cpl.apen('020021100'), 0.737840961220342) 81 | self.assertAlmostEqual(cpl.apen('220100102'), 0.6720110042418419) 82 | self.assertAlmostEqual(cpl.apen('000110212'), 0.8452977993818283) 83 | self.assertAlmostEqual(cpl.apen('200101100'), 0.622692848720861) 84 | self.assertAlmostEqual(cpl.apen('102001022'), 0.6720110042418419) 85 | self.assertAlmostEqual(cpl.apen('111122121'), 0.6843741748545092) 86 | self.assertAlmostEqual(cpl.apen('101210102'), 0.4333181911312871) 87 | self.assertAlmostEqual(cpl.apen('110000022'), 0.39126737094036934) 88 | self.assertAlmostEqual(cpl.apen('211120210'), 0.6720110042418419) 89 | self.assertAlmostEqual(cpl.apen('012011200'), 0.49872420910185533) 90 | self.assertAlmostEqual(cpl.apen('221211112'), 0.6339267665519991) 91 | self.assertAlmostEqual(cpl.apen('101112110'), 0.6454895805628911) 92 | self.assertAlmostEqual(cpl.apen('110222121'), 0.7679050283924354) 93 | self.assertAlmostEqual(cpl.apen('010002011'), 0.7959796438608473) 94 | self.assertAlmostEqual(cpl.apen('200011102'), 0.6720110042418419) 95 | self.assertAlmostEqual(cpl.apen('121200001'), 0.4333181911312871) 96 | self.assertAlmostEqual(cpl.apen('110011121'), 0.818776375702878) 97 | self.assertAlmostEqual(cpl.apen('001201021'), 0.8452977993818283) 98 | self.assertAlmostEqual(cpl.apen('101222022'), 0.6724349432497736) 99 | self.assertAlmostEqual(cpl.apen('212101102'), 0.6720110042418419) 100 | self.assertAlmostEqual(cpl.apen('201220200'), 0.7024990104218669) 101 | self.assertAlmostEqual(cpl.apen('022100020'), 0.7959796438608473) 102 | self.assertAlmostEqual(cpl.apen('200112200'), 0.6720110042418417) 103 | self.assertAlmostEqual(cpl.apen('201211000'), 0.845297799381828) 104 | self.assertAlmostEqual(cpl.apen('000101202'), 0.737840961220342) 105 | self.assertAlmostEqual(cpl.apen('100211110'), 0.557286830750293) 106 | self.assertAlmostEqual(cpl.apen('122000022'), 0.5292122152818806) 107 | self.assertAlmostEqual(cpl.apen('102221002'), 0.49872420910185555) 108 | self.assertAlmostEqual(cpl.apen('202110020'), 0.6720110042418419) 109 | self.assertAlmostEqual(cpl.apen('002101202'), 0.8452977993818283) 110 | self.assertAlmostEqual(cpl.apen('120021201'), 0.6342556627317537) 111 | self.assertAlmostEqual(cpl.apen('010122022'), 0.6720110042418419) 112 | 113 | def test_apen_list(self): 114 | self.assertAlmostEqual(cpl.apen([1, 1, 2, 0, 1, 2, 2, 1, 0]), 0.845297799381828) 115 | self.assertAlmostEqual(cpl.apen([2, 0, 0, 0, 2, 2, 2, 1, 1]), 0.6720110042418417) 116 | self.assertAlmostEqual(cpl.apen([2, 1, 0, 2, 2, 2, 0, 0, 1]), 0.845297799381828) 117 | self.assertAlmostEqual(cpl.apen([1, 0, 1, 2, 1, 1, 0, 1, 1]), 0.7108955985334597) 118 | self.assertAlmostEqual(cpl.apen([1, 0, 2, 0, 1, 2, 2, 1, 2]), 0.8452977993818283) 119 | self.assertAlmostEqual(cpl.apen([1, 0, 1, 2, 2, 1, 0, 0, 2]), 0.8075424578717401) 120 | 121 | def test_apen_ndarray(self): 122 | self.assertAlmostEqual(cpl.apen(np.array([1, 1, 2, 0, 1, 2, 2, 1, 0])), 0.845297799381828) 123 | self.assertAlmostEqual(cpl.apen(np.array([2, 0, 0, 0, 2, 2, 2, 1, 1])), 0.6720110042418417) 124 | self.assertAlmostEqual(cpl.apen(np.array([2, 1, 0, 2, 2, 2, 0, 0, 1])), 0.845297799381828) 125 | self.assertAlmostEqual(cpl.apen(np.array([1, 0, 1, 2, 1, 1, 0, 1, 1])), 0.7108955985334597) 126 | self.assertAlmostEqual(cpl.apen(np.array([1, 0, 2, 0, 1, 2, 2, 1, 2])), 0.8452977993818283) 127 | self.assertAlmostEqual(cpl.apen(np.array([1, 0, 1, 2, 2, 1, 0, 0, 2])), 0.8075424578717401) 128 | 129 | def test_apen_unsupported_sequence_type(self): 130 | with pytest.raises(Exception) as e: 131 | cpl.apen({1, 2, 3}) 132 | self.assertTrue("unsupported sequence type: " in str(e.value)) 133 | -------------------------------------------------------------------------------- /doc/additional.rst: -------------------------------------------------------------------------------- 1 | Additional Features 2 | ------------------- 3 | 4 | Langton's Lambda 5 | ~~~~~~~~~~~~~~~~ 6 | 7 | One way to specify CA rules is with rule tables. Rule tables are enumerations of all possible neighbourhood states 8 | together with their cell state mappings. For any given neighbourhood state, a rule table provides the associated cell 9 | state value. CellPyLib provides a built-in function for creating random rule tables. The following snippet demonstrates 10 | its usage: 11 | 12 | .. code-block:: 13 | 14 | import cellpylib as cpl 15 | 16 | rule_table, actual_lambda, quiescent_state = cpl.random_rule_table(lambda_val=0.45, k=4, r=2, 17 | strong_quiescence=True, 18 | isotropic=True) 19 | cellular_automaton = cpl.init_random(128, k=4) 20 | 21 | # use the built-in table_rule to use the generated rule table 22 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=200, 23 | apply_rule=lambda n, c, t: cpl.table_rule(n, rule_table), r=2) 24 | 25 | The following plots demonstrate the effect of varying the lambda parameter: 26 | 27 | .. image:: _static/phase_transition.png 28 | :width: 650 29 | 30 | C. G. Langton describes the lambda parameter, and the transition from order to criticality to chaos in cellular 31 | automata while varying the lambda parameter, in the paper: 32 | 33 | .. code-block:: text 34 | 35 | Langton, C. G. (1990). Computation at the edge of chaos: phase transitions 36 | and emergent computation. Physica D: Nonlinear Phenomena, 42(1-3), 12-37. 37 | 38 | Reversible CA 39 | ~~~~~~~~~~~~~ 40 | 41 | Elementary CA can be explicitly made to be reversible. CellPyLib has a class, 42 | :py:class:`~cellpylib.ca_functions.ReversibleRule`, which can be used to decorate a rule, making it reversible. The 43 | following example demonstrates the creation of the elementary reversible CA rule 90R: 44 | 45 | .. code-block:: 46 | 47 | import cellpylib as cpl 48 | 49 | cellular_automaton = cpl.init_random(200) 50 | rule = cpl.ReversibleRule(cellular_automaton[0], 90) 51 | 52 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=100, 53 | apply_rule=ule) 54 | 55 | cpl.plot(cellular_automaton) 56 | 57 | .. image:: _static/rule90R.png 58 | :width: 400 59 | 60 | Asynchronous CA 61 | ~~~~~~~~~~~~~~~ 62 | 63 | Typically, evolving a CA involves the synchronous updating of all the cells in a given timestep. However, it is also 64 | possible to consider CA in which the cells are updated asynchronously. There are various schemes for achieving this. 65 | `Wikipedia `_ has a page dedicated to this topic. 66 | 67 | CellPyLib has a class, :py:class:`~cellpylib.ca_functions.AsynchronousRule`, which can be used to decorate a rule, making 68 | it asynchronous. In the following example, the rule 60 sequential CA from the notes of `A New Kind of Science` (Chapter 69 | 9, section 10: `Sequential cellular automata `_) 70 | is implemented: 71 | 72 | .. code-block:: 73 | 74 | import cellpylib as cpl 75 | 76 | cellular_automaton = cpl.init_simple(21) 77 | 78 | rule = cpl.AsynchronousRule(apply_rule=lambda n, c, t: cpl.nks_rule(n, 60), 79 | update_order=range(1, 20)) 80 | 81 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=19*20, 82 | apply_rule=rule) 83 | 84 | # get every 19th row, including the first, as a cycle is completed every 19 rows 85 | cpl.plot(cellular_automaton[::19]) 86 | 87 | .. image:: _static/rule60sequential.png 88 | :width: 300 89 | 90 | The :py:class:`~cellpylib.ca_functions.AsynchronousRule` requires the specification of an update order (if none is 91 | provided, then an order is constructed based on the number of cells in the CA). An update order specifies which cell 92 | will be updated as the CA evolves. For example, the update order `[2, 3, 1]` states that cell `2` will be updated in the 93 | next timestep, followed by cell `3` in the subsequent timestep, and then cell `1` in the timestep after that. This 94 | update order is adhered to for the entire evolution of the CA. Cells that are not being updated do not have the 95 | rule applied to them in that timestep. 96 | 97 | An option is provided to randomize the update order at the end of each cycle (i.e. timestep). This is equivalent to 98 | selecting a cell randomly at each timestep to update, leaving all others unchanged during that timestep. The following 99 | example demonstrates a simplistic 2D CA in which a cell is picked randomly at each timestep, and its state is updated to 100 | a value of `1` (all cells begin with a state of `0`): 101 | 102 | .. code-block:: 103 | 104 | import cellpylib as cpl 105 | 106 | cellular_automaton = cpl.init_simple2d(50, 50, val=0) 107 | 108 | apply_rule = cpl.AsynchronousRule(apply_rule=lambda n, c, t: 1, num_cells=(50, 50), 109 | randomize_each_cycle=True) 110 | 111 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=50, 112 | neighbourhood='Moore', apply_rule=apply_rule) 113 | 114 | cpl.plot2d_animate(cellular_automaton, interval=200, autoscale=True) 115 | 116 | .. image:: _static/async_random.gif 117 | :width: 350 118 | 119 | 120 | CTRBL Rules 121 | ~~~~~~~~~~~ 122 | 123 | There exists a class of important CA that exhibit the property of self-reproduction. That is, there are patterns 124 | observed that reproduce themselves during the evolution of these CA. This phenomenon has obvious relevance to the study 125 | of Biological systems. These CA are typically 2-dimensional, with a von Neumann neighbourhood of radius 1. The 126 | convention when specifying the rules for these CA is to enumerate the rule table using the states of the center (C), top 127 | (T), right (R), bottom (B), and left (L) cells in the von Neumann neighbourhood. 128 | 129 | Such CTRBL CA are supported in CellPyLib, through the :py:class:`~cellpylib.ctrbl_rule.CTRBLRule` class. A particularly 130 | well-known CA in this class is Langton's Loop. CellPyLib has a built-in implementation of this CA, available through 131 | the :py:class:`~cellpylib.langtons_loop.LangtonsLoop` class. 132 | 133 | Here is a simple example of a 2D CA that uses a CTRBL rule: 134 | 135 | .. code-block:: 136 | 137 | import cellpylib as cpl 138 | 139 | ctrbl_rule = cpl.CTRBLRule(rule_table={ 140 | (0, 1, 0, 0, 0): 1, 141 | (1, 1, 0, 0, 0): 0, 142 | (0, 0, 0, 0, 0): 0, 143 | (1, 0, 0, 0, 0): 1, 144 | (0, 0, 1, 1, 0): 0, 145 | (1, 1, 1, 1, 1): 1, 146 | (0, 1, 0, 1, 0): 0, 147 | (1, 1, 1, 0, 1): 1, 148 | (1, 0, 1, 0, 1): 1, 149 | (0, 1, 1, 1, 1): 1, 150 | (0, 0, 1, 1, 1): 0, 151 | (1, 1, 0, 0, 1): 1 152 | }, add_rotations=True) 153 | 154 | cellular_automaton = cpl.init_simple2d(rows=10, cols=10) 155 | 156 | cellular_automaton = cpl.evolve2d(cellular_automaton, timesteps=60, 157 | apply_rule=ctrbl_rule, neighbourhood="von Neumann") 158 | 159 | cpl.plot2d_animate(cellular_automaton) 160 | 161 | .. image:: _static/ctrbl.gif 162 | :width: 400 163 | 164 | It is a binary CA that always appears to evolve to some stable attractor state. 165 | 166 | Custom Rules 167 | ~~~~~~~~~~~~ 168 | 169 | 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 170 | callable is valid, but the callable must accept 3 arguments: ``n``, ``c`` and ``t``. Furthermore, the callable must 171 | return the state of the current cell at the next timestep. The ``n`` argument is the neighbourhood, which is a NumPy 172 | array of length `2r + 1` representing the state of the neighbourhood of the cell (for 1D CA), where ``r`` is the 173 | neighbourhood radius. The state of the current cell will always be located at the "center" of the neighbourhood. The 174 | ``c`` argument is the cell identity, which is a scalar representing the index of the cell in the cellular automaton 175 | array. Finally, the ``t`` argument is an integer representing the time step in the evolution. 176 | 177 | Any kind of callable is supported, and this is particularly useful if more complex handling, like statefulness, is 178 | required by the rule. For complex rules, the recommended approach is to define a class for the rule, which provides 179 | a ``__call__`` function which accepts the ``n``, ``c``, and ``t`` arguments. The 180 | :py:class:`~cellpylib.ca_functions.BaseRule` class is provided for users to extend, which ensures that the custom rule 181 | is implemented with the correct ``__call__`` signature. 182 | 183 | As an example, below is a custom rule that simply keeps track of how many times each cell has been invoked: 184 | 185 | .. code-block:: 186 | 187 | import cellpylib as cpl 188 | from collections import defaultdict 189 | 190 | class CustomRule(cpl.BaseRule): 191 | 192 | def __init__(self): 193 | self.count = defaultdict(int) 194 | 195 | def __call__(self, n, c, t): 196 | self.count[c] += 1 197 | return self.count[c] 198 | 199 | rule = CustomRule() 200 | 201 | cellular_automaton = cpl.init_simple(11) 202 | 203 | cellular_automaton = cpl.evolve(cellular_automaton, timesteps=10, 204 | apply_rule=rule) 205 | 206 | cpl.plot(cellular_automaton) 207 | 208 | .. image:: _static/custom_rule.png 209 | :width: 250 210 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. --------------------------------------------------------------------------------