added readme

This commit is contained in:
Richard Feistenauer 2019-02-23 16:20:10 +01:00
parent 1782d32e8c
commit 23517f45ff
7 changed files with 166 additions and 44 deletions

View File

@ -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.

View File

@ -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

View File

@ -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)

View 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)

View 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
View File