increased api documentation
This commit is contained in:
parent
4507327bd5
commit
9964e3b483
@ -79,8 +79,9 @@ class MyRule(Rule):
|
||||
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 package provides a module for visualization in a pygame window for common two dimensional automatons.
|
||||
|
||||
To add another kind of display option e.g. for other dimensions or hexagonal grids you can extrend the provided implementation or build your own.
|
||||
The visual part of this module is fully decoupled and thus should be easily replaceable.
|
||||
|
||||
## Examples
|
||||
@ -93,3 +94,6 @@ Those two example automaton implementations should provide a good start for your
|
||||
## Dependencies
|
||||
As mentioned above the module depends on [pygame](https://www.pygame.org/news) for visualisation.
|
||||
This is the only dependency however.
|
||||
|
||||
## Licence
|
||||
This package is distributed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0), see [LICENSE](./LICENSE.txt)
|
@ -21,14 +21,20 @@ from ctypes import c_int
|
||||
|
||||
|
||||
class CellularAutomatonProcessor:
|
||||
""" This class is responsible for the evolution of the cells. """
|
||||
|
||||
def __init__(self, cellular_automaton):
|
||||
self._ca = cellular_automaton
|
||||
|
||||
def evolve_x_times(self, x):
|
||||
""" Evolve all cells x times.
|
||||
:param x: The number of evolution steps processed with the call of this method.
|
||||
"""
|
||||
for x in range(x):
|
||||
self.evolve()
|
||||
|
||||
def evolve(self):
|
||||
""" Evolve all cells """
|
||||
self._ca.current_evolution_step += 1
|
||||
i = self._ca.current_evolution_step
|
||||
r = self._ca.evolution_rule.evolve_cell
|
||||
@ -48,6 +54,14 @@ class CellularAutomatonProcessor:
|
||||
|
||||
|
||||
class CellularAutomatonMultiProcessor(CellularAutomatonProcessor):
|
||||
""" This is a variant of CellularAutomatonProcessor that uses multi processing.
|
||||
The evolution of the cells will be outsourced to new processes.
|
||||
|
||||
WARNING:
|
||||
This variant has high memory use!
|
||||
The inter process communication overhead can make this variant slower than single processing!
|
||||
"""
|
||||
|
||||
def __init__(self, cellular_automaton, process_count: int = 2):
|
||||
multiprocessing.freeze_support()
|
||||
if process_count < 1:
|
||||
@ -57,9 +71,9 @@ class CellularAutomatonMultiProcessor(CellularAutomatonProcessor):
|
||||
|
||||
self.evolve_range = range(len(self._ca.cells))
|
||||
self._ca.current_evolution_step = RawValue(c_int, self._ca.current_evolution_step)
|
||||
self.__init_processes_and_clean_cell_instances(process_count)
|
||||
self.__init_processes(process_count)
|
||||
|
||||
def __init_processes_and_clean_cell_instances(self, process_count):
|
||||
def __init_processes(self, process_count):
|
||||
self.pool = multiprocessing.Pool(processes=process_count,
|
||||
initializer=_init_process,
|
||||
initargs=(tuple(self._ca.cells.values()),
|
||||
|
@ -23,22 +23,31 @@ class Cell:
|
||||
self._neighbor_states = neighbors
|
||||
|
||||
def is_set_for_redraw(self):
|
||||
""" Flag indicating a change in the cells state since last call of 'was_redrawn'. """
|
||||
return self._state.is_set_for_redraw()
|
||||
|
||||
def was_redrawn(self):
|
||||
""" Should be called after this cell was drawn to prevent unnecessary redraws. """
|
||||
self._state.was_redrawn()
|
||||
|
||||
def get_current_state(self, evolution_step):
|
||||
return self._state.get_state_of_evolution_step(evolution_step)
|
||||
|
||||
def evolve_if_ready(self, rule, evolution_step):
|
||||
""" When there was a change in this cell or one of its neighbours,
|
||||
evolution rule is called and the new state and redraw flag gets set if necessary.
|
||||
If there was a change neighbours will be notified.
|
||||
"""
|
||||
if self._state.is_active(evolution_step):
|
||||
new_state = rule(list(self._state.get_state_of_last_evolution_step(evolution_step)),
|
||||
[list(n.get_state_of_last_evolution_step(evolution_step)) for n in self._neighbor_states])
|
||||
self.set_new_state_and_activate(new_state, evolution_step)
|
||||
self.__set_new_state_and_consider_activation(new_state, evolution_step)
|
||||
|
||||
def set_new_state_and_activate(self, new_state: cell_state.CellState, evolution_step):
|
||||
def __set_new_state_and_consider_activation(self, new_state: cell_state.CellState, evolution_step):
|
||||
changed = self._state.set_state_of_evolution_step(new_state, evolution_step)
|
||||
self.__activate_if_necessary(changed, evolution_step)
|
||||
|
||||
def __activate_if_necessary(self, changed, evolution_step):
|
||||
if changed:
|
||||
self._state.set_active_for_next_evolution_step(evolution_step)
|
||||
for n in self._neighbor_states:
|
||||
|
@ -35,20 +35,19 @@ class CellState:
|
||||
def is_active(self, current_evolution_step):
|
||||
""" Returns the active status for the requested evolution_step
|
||||
:param current_evolution_step: The evolution_step of interest.
|
||||
:return: True if the cell state is set active for this evolution_step.
|
||||
:return: True if the cell or one of its neighbours changed in the last evolution step.
|
||||
"""
|
||||
return self._active[self._calculate_slot(current_evolution_step)]
|
||||
|
||||
def set_active_for_next_evolution_step(self, current_evolution_step):
|
||||
""" Sets the cell active for the next evolution_step, so it will be evolved.
|
||||
:param current_evolution_step: The current evolution_step index.
|
||||
:return:
|
||||
"""
|
||||
self._active[self._calculate_slot(current_evolution_step + 1)] = True
|
||||
|
||||
def is_set_for_redraw(self):
|
||||
""" States if this state should be redrawn.
|
||||
:return: True if redraw is needed.
|
||||
:return: True if state changed since last call of 'was_redrawn'.
|
||||
"""
|
||||
return self._dirty
|
||||
|
||||
@ -62,7 +61,7 @@ class CellState:
|
||||
def get_state_of_evolution_step(self, evolution_step):
|
||||
""" Returns the state of the evolution_step.
|
||||
:param evolution_step: Uses the evolution_step index, to differ between concurrent states.
|
||||
:return The state of this evolution_step.
|
||||
:return The state of the requested evolution_step.
|
||||
"""
|
||||
return self._state_slots[self._calculate_slot(evolution_step)]
|
||||
|
||||
@ -71,6 +70,7 @@ class CellState:
|
||||
:param new_state: The new state to set.
|
||||
:param evolution_step: The evolution_step index, to differ between concurrent states.
|
||||
:return True if the state really changed.
|
||||
:raises IndexError: If the state length changed.
|
||||
"""
|
||||
changed = self._set_new_state_if_valid(new_state, evolution_step)
|
||||
self._dirty |= changed
|
||||
@ -111,9 +111,8 @@ class CellState:
|
||||
|
||||
|
||||
class SynchronousCellState(CellState):
|
||||
"""
|
||||
CellState version using shared values for multi processing purpose.
|
||||
"""
|
||||
""" CellState version using shared values for multi processing purpose. """
|
||||
|
||||
def __init__(self, initial_state=(0., ), draw_first_state=True):
|
||||
super().__init__(initial_state, draw_first_state)
|
||||
self._state_slots = [RawArray(c_float, initial_state) for i in range(self.__class__._state_save_slot_count)]
|
||||
|
@ -33,10 +33,11 @@ class _CASurface:
|
||||
return [self.__rect.width / grid_dimension[0], self.__rect.height / grid_dimension[1]]
|
||||
|
||||
def redraw_cellular_automaton(self):
|
||||
update_rectangles = list(self.__cell_redraw_dirty_rectangles())
|
||||
""" Redraws those cells which changed their state since last redraw. """
|
||||
update_rectangles = list(self.__redraw_dirty_cells())
|
||||
pygame.display.update(update_rectangles)
|
||||
|
||||
def __cell_redraw_dirty_rectangles(self):
|
||||
def __redraw_dirty_cells(self):
|
||||
for coordinate, cell in self._cellular_automaton.get_cells().items():
|
||||
if cell.is_set_for_redraw():
|
||||
yield from self.__redraw_cell(cell, coordinate)
|
||||
|
@ -26,6 +26,8 @@ from .cell_state import CellState, SynchronousCellState
|
||||
|
||||
|
||||
class CAFactory:
|
||||
""" This factory provides an easy way to create cellular automatons with single or multi processing. """
|
||||
|
||||
@staticmethod
|
||||
def make_single_process_cellular_automaton(dimension,
|
||||
neighborhood: Neighborhood,
|
||||
@ -67,7 +69,7 @@ class CAFactory:
|
||||
cells = {}
|
||||
for coordinate, cell_state in cell_states.items():
|
||||
n_coordinates = neighborhood.calculate_cell_neighbor_coordinates(coordinate, dimension)
|
||||
neighbor_states = [cell_states[tuple(nc)] for nc in n_coordinates]
|
||||
neighbor_states = tuple([cell_states[tuple(nc)] for nc in n_coordinates])
|
||||
cells[coordinate] = Cell(cell_state, neighbor_states)
|
||||
return cells
|
||||
|
||||
|
@ -45,7 +45,7 @@ class Neighborhood:
|
||||
self.__grid_dimensions = grid_dimensions
|
||||
return list(self.__neighbors_generator(cell_coordinate))
|
||||
|
||||
def get_neighbor_id_from_rel(self, rel_coordinate):
|
||||
def get_id_of_neighbor_from_relative_coordinate(self, rel_coordinate):
|
||||
return self._rel_neighbors.index(rel_coordinate)
|
||||
|
||||
def __neighbors_generator(self, cell_coordinate):
|
||||
@ -70,12 +70,45 @@ class Neighborhood:
|
||||
|
||||
|
||||
class MooreNeighborhood(Neighborhood):
|
||||
""" Defines a Moore neighborhood:
|
||||
Moore defined a neighborhood with a radius applied on a the non euclidean distance to other cells in the grid.
|
||||
Example:
|
||||
Moor neighborhood in 2 dimensions with radius 1 and 2
|
||||
C = cell of interest
|
||||
N = neighbour of cell
|
||||
X = no neighbour of cell
|
||||
|
||||
Radius 1 Radius 2
|
||||
X X X X X N N N N N
|
||||
X N N N X N N N N N
|
||||
X N C N X N N C N N
|
||||
X N N N X N N N N N
|
||||
X X X X X N N N N N
|
||||
"""
|
||||
|
||||
def __init__(self, edge_rule: EdgeRule = EdgeRule.IGNORE_EDGE_CELLS, range_=1, dimension=2):
|
||||
super().__init__(tuple(_rel_neighbor_generator(dimension, range_, lambda rel_n: True)),
|
||||
edge_rule)
|
||||
|
||||
|
||||
class VonNeumannNeighborhood(Neighborhood):
|
||||
""" Defines a Von Neumann neighborhood:
|
||||
Von Neumann defined a neighborhood with a radius applied to Manhatten distance
|
||||
(steps between cells without diagonal movement).
|
||||
Example:
|
||||
Von Neumann neighborhood in 2 dimensions with radius 1 and 2
|
||||
C = cell of interest
|
||||
N = neighbour of cell
|
||||
X = no neighbour of cell
|
||||
|
||||
Radius 1 Radius 2
|
||||
X X X X X X X N X X
|
||||
X X N X X X N N N X
|
||||
X N C N X N N C N N
|
||||
X X N X X X N N N X
|
||||
X X X X X X X N X X
|
||||
"""
|
||||
|
||||
def __init__(self, edge_rule: EdgeRule = EdgeRule.IGNORE_EDGE_CELLS, range_=1, dimension=2):
|
||||
self.range_ = range_
|
||||
super().__init__(tuple(_rel_neighbor_generator(dimension, range_, self.neighbor_rule)),
|
||||
|
@ -20,30 +20,36 @@ from . import neighborhood
|
||||
|
||||
|
||||
class Rule:
|
||||
""" Base class for evolution rules.
|
||||
This class has to be inherited if the cellular automaton is supposed to have any effect.
|
||||
"""
|
||||
|
||||
def __init__(self, neighborhood_: neighborhood.Neighborhood):
|
||||
self._neighborhood = neighborhood_
|
||||
|
||||
def _get_neighbor_by_relative_coordinate(self, neighbours, rel_coordinate):
|
||||
return neighbours[self._neighborhood.get_neighbor_id_from_rel(rel_coordinate)]
|
||||
return neighbours[self._neighborhood.get_id_of_neighbor_from_relative_coordinate(rel_coordinate)]
|
||||
|
||||
@abc.abstractmethod
|
||||
def evolve_cell(self, last_cell_state, neighbors_last_states):
|
||||
""" Calculates and sets new state of 'cell'.
|
||||
:param last_cell_state: The cells current state to calculate new state for.
|
||||
:param neighbors_last_states: The cells neighbors current states.
|
||||
:return: New state.
|
||||
A cells evolution will only be called if it or at least one of its neighbors has changed last evolution_step cycle.
|
||||
A cells evolution will only be called if it or at least one of its neighbors has changed last evolution_step.
|
||||
:param last_cell_state: The cells state previous to the evolution step.
|
||||
:param neighbors_last_states: The cells neighbors current states.
|
||||
:return: New state. The state after this evolution step
|
||||
"""
|
||||
return last_cell_state
|
||||
|
||||
@abc.abstractmethod
|
||||
def init_state(self, cell_coordinate):
|
||||
""" Set the initial state for the cell with the given coordinate.
|
||||
""" Will be called to initialize a cells state.
|
||||
:param cell_coordinate: Cells coordinate.
|
||||
:return: Iterable that represents the state
|
||||
:return: Iterable that represents the initial cell state
|
||||
Has to be compatible with 'multiprocessing.sharedctype.RawArray' when using multi processing.
|
||||
"""
|
||||
return [0]
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_state_draw_color(self, current_state):
|
||||
""" Return the draw color for the current state """
|
||||
return [0, 0, 0]
|
||||
|
@ -18,6 +18,8 @@ from . import Rule
|
||||
|
||||
|
||||
class CellularAutomatonState:
|
||||
""" Holds all relevant information about the cellular automaton """
|
||||
|
||||
def __init__(self, cells, dimension, evolution_rule: Rule):
|
||||
self.cells = cells
|
||||
self.dimension = dimension
|
||||
|
@ -1,4 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright 2019 Richard Feistenauer
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import random
|
||||
from cellular_automaton import *
|
||||
@ -8,7 +23,7 @@ ALIVE = [1.0]
|
||||
DEAD = [0]
|
||||
|
||||
|
||||
class TestRule(Rule):
|
||||
class ConwaysRule(Rule):
|
||||
random_seed = random.seed(13)
|
||||
|
||||
def init_state(self, cell_coordinate):
|
||||
@ -45,6 +60,6 @@ if __name__ == "__main__":
|
||||
neighborhood = MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)
|
||||
ca = CAFactory.make_multi_process_cellular_automaton(dimension=[100, 100],
|
||||
neighborhood=neighborhood,
|
||||
rule=TestRule,
|
||||
rule=ConwaysRule,
|
||||
processes=4)
|
||||
ca_window = CAWindow(cellular_automaton=ca, evolution_steps_per_draw=1)
|
||||
|
@ -1,10 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Copyright 2019 Richard Feistenauer
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
import random
|
||||
from cellular_automaton import *
|
||||
|
||||
|
||||
class TestRule(Rule):
|
||||
class StarfallRule(Rule):
|
||||
""" A basic cellular automaton that just copies one neighbour state so get some motion in the grid. """
|
||||
random_seed = random.seed(1000)
|
||||
|
||||
def init_state(self, cell_coordinate):
|
||||
@ -23,5 +39,5 @@ 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)
|
||||
rule=StarfallRule)
|
||||
ca_window = CAWindow(cellular_automaton=ca, evolution_steps_per_draw=1)
|
||||
|
Loading…
Reference in New Issue
Block a user