added readme
This commit is contained in:
parent
1782d32e8c
commit
23517f45ff
95
README.md
95
README.md
@ -0,0 +1,95 @@
|
|||||||
|
# Cellular Automaton
|
||||||
|
This package provides an cellular automaton for [`Python® 3`](https://www.python.org/)
|
||||||
|
|
||||||
|
A cellular automaton defines a grid of cells and a set of rules.
|
||||||
|
All cells then evolve their state depending on their neighbours state simultaneously.
|
||||||
|
|
||||||
|
For further information on cellular automatons consult e.g. [mathworld.wolfram.com](http://mathworld.wolfram.com/CellularAutomaton.html)
|
||||||
|
|
||||||
|
## Yet another cellular automaton module?
|
||||||
|
It is not the first python module to provide a cellular automaton,
|
||||||
|
but it is to my best knowledge the first that provides all of the following features:
|
||||||
|
- easy to use
|
||||||
|
- n dimensional
|
||||||
|
- multi process capable
|
||||||
|
- speed optimized
|
||||||
|
- documented
|
||||||
|
- tested
|
||||||
|
|
||||||
|
I originally did not plan to write a new cellular automaton module,
|
||||||
|
but when searching for one, I just found some minimalistic implementations,
|
||||||
|
that had little or no documentation with an API that really did not fit the problem
|
||||||
|
and Code that was desperately asking for some refactoring.
|
||||||
|
|
||||||
|
So I started to write my own module with the goal to provide an user friendly API
|
||||||
|
and acceptable documentation. During the implementation I figured, why not just provide
|
||||||
|
n dimensional support and with reading Clean Code from Robert C. Martin the urge
|
||||||
|
to have a clean and tested code with a decent coverage added some more requirements.
|
||||||
|
The speed optimization and multi process capability was more of challenge for myself.
|
||||||
|
IMHO the module now reached an acceptable speed, but there is still room for improvements.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
To start and use the automaton you will have to define three things:
|
||||||
|
- The neighborhood
|
||||||
|
- The dimensions of the grid
|
||||||
|
- The evolution rule
|
||||||
|
|
||||||
|
`````python
|
||||||
|
neighborhood = MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS)
|
||||||
|
ca = CAFactory.make_single_process_cellular_automaton(dimension=[100, 100],
|
||||||
|
neighborhood=neighborhood,
|
||||||
|
rule=MyRule)
|
||||||
|
``````
|
||||||
|
|
||||||
|
### Neighbourhood
|
||||||
|
The Neighborhood defines for a cell neighbours in relative coordinates.
|
||||||
|
The evolution of a cell will depend solely on those neighbours.
|
||||||
|
|
||||||
|
The Edge Rule passed as parameter to the Neighborhood defines, how cells on the edge of the grid will be handled.
|
||||||
|
There are three options:
|
||||||
|
- Ignore edge cells: Edge cells will have no neighbours and thus not evolve.
|
||||||
|
- Ignore missing neighbours: Edge cells will add the neighbours that exist. This results in varying count of neighbours on edge cells.
|
||||||
|
- First and last cell of each dimension are neighbours: All cells will have the same neighbour count and no edge exists.
|
||||||
|
|
||||||
|
### Dimension
|
||||||
|
A list or Tuple which states each dimensions size.
|
||||||
|
The example above defines a two dimensional grid with 100 x 100 cells.
|
||||||
|
|
||||||
|
There is no limitation in how many dimensions you choose but your memory and processor power.
|
||||||
|
|
||||||
|
### Rule
|
||||||
|
The Rule has three tasks:
|
||||||
|
- Set the initial value for all cells.
|
||||||
|
- Evolve a cell in respect to its neighbours.
|
||||||
|
- (optional) define how the cell should be drawn.
|
||||||
|
|
||||||
|
`````python
|
||||||
|
class MyRule(Rule):
|
||||||
|
|
||||||
|
def init_state(self, cell_coordinate):
|
||||||
|
return (1, 1)
|
||||||
|
|
||||||
|
def evolve_cell(self, last_cell_state, neighbors_last_states):
|
||||||
|
return self._get_neighbor_by_relative_coordinate(neighbors_last_states, (-1, -1))
|
||||||
|
|
||||||
|
def get_state_draw_color(self, current_state):
|
||||||
|
return [255 if current_state[0] else 0, 0, 0]
|
||||||
|
`````
|
||||||
|
|
||||||
|
Just inherit from `cellular_automaton.rule:Rule` and define the evolution rule and initial state.
|
||||||
|
|
||||||
|
## Visualisation
|
||||||
|
The module provides a pygame window for common two dimensional.
|
||||||
|
To add another kind of display option e.g. for other dimensions or hexagonal grids you can extrend the provided implementation or build you own.
|
||||||
|
The visual part of this module is fully decoupled and thus should be easily replaceable.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
The package contains two examples:
|
||||||
|
- [simple_star_fall](./examples/simple_star_fall.py)
|
||||||
|
- [conways_game_of_life](./examples/conways_game_of_life.py)
|
||||||
|
|
||||||
|
Those two example automaton implementations should provide a good start for your own automaton.
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
As mentioned above the module depends on [pygame](https://www.pygame.org/news) for visualisation.
|
||||||
|
This is the only dependency however.
|
@ -17,6 +17,18 @@ class CellularAutomatonProcessor:
|
|||||||
r = self._ca.evolution_rule.evolve_cell
|
r = self._ca.evolution_rule.evolve_cell
|
||||||
list(map(lambda c: c.evolve_if_ready(r, i), tuple(self._ca.cells.values())))
|
list(map(lambda c: c.evolve_if_ready(r, i), tuple(self._ca.cells.values())))
|
||||||
|
|
||||||
|
def get_dimension(self):
|
||||||
|
return self._ca.dimension
|
||||||
|
|
||||||
|
def get_cells(self):
|
||||||
|
return self._ca.cells
|
||||||
|
|
||||||
|
def get_current_evolution_step(self):
|
||||||
|
return self._ca.current_evolution_step
|
||||||
|
|
||||||
|
def get_current_rule(self):
|
||||||
|
return self._ca.evolution_rule
|
||||||
|
|
||||||
|
|
||||||
class CellularAutomatonMultiProcessor(CellularAutomatonProcessor):
|
class CellularAutomatonMultiProcessor(CellularAutomatonProcessor):
|
||||||
def __init__(self, cellular_automaton, process_count: int = 2):
|
def __init__(self, cellular_automaton, process_count: int = 2):
|
||||||
@ -27,7 +39,7 @@ class CellularAutomatonMultiProcessor(CellularAutomatonProcessor):
|
|||||||
super().__init__(cellular_automaton)
|
super().__init__(cellular_automaton)
|
||||||
|
|
||||||
self.evolve_range = range(len(self._ca.cells))
|
self.evolve_range = range(len(self._ca.cells))
|
||||||
self.shared_evolution_step = multiprocessing.RawValue(c_int, self._ca.current_evolution_step)
|
self._ca.current_evolution_step = multiprocessing.RawValue(c_int, self._ca.current_evolution_step)
|
||||||
|
|
||||||
self.__init_processes_and_clean_cell_instances(process_count)
|
self.__init_processes_and_clean_cell_instances(process_count)
|
||||||
|
|
||||||
@ -36,15 +48,17 @@ class CellularAutomatonMultiProcessor(CellularAutomatonProcessor):
|
|||||||
initializer=_init_process,
|
initializer=_init_process,
|
||||||
initargs=(tuple(self._ca.cells.values()),
|
initargs=(tuple(self._ca.cells.values()),
|
||||||
self._ca.evolution_rule,
|
self._ca.evolution_rule,
|
||||||
self.shared_evolution_step))
|
self._ca.current_evolution_step))
|
||||||
for cell in self._ca.cells.values():
|
for cell in self._ca.cells.values():
|
||||||
del cell.neighbor_states
|
del cell.neighbor_states
|
||||||
|
|
||||||
def evolve(self):
|
def evolve(self):
|
||||||
self._ca.current_evolution_step += 1
|
self._ca.current_evolution_step += 1
|
||||||
self.shared_evolution_step.value = self._ca.current_evolution_step
|
|
||||||
self.pool.map(_process_routine, self.evolve_range)
|
self.pool.map(_process_routine, self.evolve_range)
|
||||||
|
|
||||||
|
def get_current_evolution_step(self):
|
||||||
|
return self._ca.current_evolution_step.value
|
||||||
|
|
||||||
|
|
||||||
global_cells = None
|
global_cells = None
|
||||||
global_rule = None
|
global_rule = None
|
@ -1,41 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import random
|
|
||||||
from cellular_automaton import *
|
|
||||||
|
|
||||||
|
|
||||||
class TestRule(Rule):
|
|
||||||
@staticmethod
|
|
||||||
def evolve_cell(last_cell_state, neighbors_last_states):
|
|
||||||
try:
|
|
||||||
return neighbors_last_states[0]
|
|
||||||
except IndexError:
|
|
||||||
return last_cell_state
|
|
||||||
|
|
||||||
|
|
||||||
# class MyState(SynchronousCellState):
|
|
||||||
class MyState(CellState):
|
|
||||||
random_seed = random.seed(1000)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
rand = random.randrange(0, 101, 1)
|
|
||||||
init = max(.0, float(rand - 99))
|
|
||||||
super().__init__((init,), draw_first_state=init > 0)
|
|
||||||
|
|
||||||
def get_state_draw_color(self, evolution_step):
|
|
||||||
state = self.get_state_of_evolution_step(evolution_step)[0]
|
|
||||||
return [255 if state else 0, 0, 0]
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# best single is 400/400 with 0,2 ca speed and 0,09 redraw / multi is 300/300 with 0.083
|
|
||||||
neighborhood = MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)
|
|
||||||
ca = CAFactory.make_cellular_automaton(dimension=[100, 100],
|
|
||||||
neighborhood=neighborhood,
|
|
||||||
rule=TestRule(),
|
|
||||||
state_class=MyState)
|
|
||||||
# ca_processor = CellularAutomatonMultiProcessor(cellular_automaton=ca, process_count=4)
|
|
||||||
ca_processor = CellularAutomatonProcessor(cellular_automaton=ca)
|
|
||||||
|
|
||||||
ca_window = PyGameFor2D(window_size=[1000, 800], cellular_automaton=ca)
|
|
||||||
ca_window.main_loop(cellular_automaton_processor=ca_processor, evolution_steps_per_draw=1)
|
|
27
examples/conways_game_of_life.py
Normal file
27
examples/conways_game_of_life.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import random
|
||||||
|
from cellular_automaton import *
|
||||||
|
|
||||||
|
|
||||||
|
class TestRule(Rule):
|
||||||
|
random_seed = random.seed(1000)
|
||||||
|
|
||||||
|
def init_state(self, cell_coordinate):
|
||||||
|
rand = random.randrange(0, 101, 1)
|
||||||
|
init = max(.0, float(rand - 99))
|
||||||
|
return (init,)
|
||||||
|
|
||||||
|
def evolve_cell(self, last_cell_state, neighbors_last_states):
|
||||||
|
return self._get_neighbor_by_relative_coordinate(neighbors_last_states, (-1, -1))
|
||||||
|
|
||||||
|
def get_state_draw_color(self, current_state):
|
||||||
|
return [255 if current_state[0] else 0, 0, 0]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
neighborhood = MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)
|
||||||
|
ca = CAFactory.make_single_process_cellular_automaton(dimension=[100, 100],
|
||||||
|
neighborhood=neighborhood,
|
||||||
|
rule=TestRule)
|
||||||
|
ca_window = CAWindow(cellular_automaton=ca, evolution_steps_per_draw=1)
|
27
examples/simple_star_fall.py
Normal file
27
examples/simple_star_fall.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import random
|
||||||
|
from cellular_automaton import *
|
||||||
|
|
||||||
|
|
||||||
|
class TestRule(Rule):
|
||||||
|
random_seed = random.seed(1000)
|
||||||
|
|
||||||
|
def init_state(self, cell_coordinate):
|
||||||
|
rand = random.randrange(0, 101, 1)
|
||||||
|
init = max(.0, float(rand - 99))
|
||||||
|
return (init,)
|
||||||
|
|
||||||
|
def evolve_cell(self, last_cell_state, neighbors_last_states):
|
||||||
|
return self._get_neighbor_by_relative_coordinate(neighbors_last_states, (-1, -1))
|
||||||
|
|
||||||
|
def get_state_draw_color(self, current_state):
|
||||||
|
return [255 if current_state[0] else 0, 0, 0]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
neighborhood = MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)
|
||||||
|
ca = CAFactory.make_single_process_cellular_automaton(dimension=[100, 100],
|
||||||
|
neighborhood=neighborhood,
|
||||||
|
rule=TestRule)
|
||||||
|
ca_window = PyGameFor2D(cellular_automaton=ca, evolution_steps_per_draw=1)
|
0
test/test_automaton.py
Normal file
0
test/test_automaton.py
Normal file
Loading…
Reference in New Issue
Block a user