refactoring and dynamic neighbourhood

This commit is contained in:
Richard Feistenauer 2019-02-16 18:05:26 +01:00
parent 4c44cc1002
commit 1b177ff686
15 changed files with 258 additions and 209 deletions

View File

@ -1,43 +1,43 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import random import random
from multiprocessing import freeze_support from cellular_automaton.ca_rule import Rule
from cellular_automaton import * from cellular_automaton.ca_cell_state import CellState, SynchronousCellState
class TestRule(Rule): class TestRule(Rule):
@staticmethod @staticmethod
def evolve_cell(last_cell_state, neighbours_last_states): def evolve_cell(last_cell_state, neighbors_last_states):
try: try:
return neighbours_last_states[0] return neighbors_last_states[0]
except IndexError: except IndexError:
return last_cell_state return last_cell_state
class MyState(SynchronousCellState): # class MyState(SynchronousCellState):
class MyState(CellState):
random_seed = random.seed(1000)
def __init__(self): def __init__(self):
rand = random.randrange(0, 101, 1) rand = random.randrange(0, 101, 1)
init = max(.0, float(rand - 99)) init = max(.0, float(rand - 99))
super().__init__((init,), draw_first_state=init > 0) super().__init__((init,), draw_first_state=init > 0)
def get_state_draw_color(self, iteration): def get_state_draw_color(self, evolution_step):
state1 = self.get_state_of_iteration(iteration)[0] state = self.get_state_of_evolution_step(evolution_step)[0]
return [255 if state1 else 0, 0, 0] return [255 if state else 0, 0, 0]
def make_cellular_automaton(dimension, neighborhood, rule, state_class):
cells = CAFactory.make_cellular_automaton(dimension=dimension, neighborhood=neighborhood, state_class=state_class)
return CellularAutomaton(cells, dimension, rule)
if __name__ == "__main__": if __name__ == "__main__":
freeze_support() from cellular_automaton import *
random.seed(1000)
# best single is 400/400 with 0,2 ca speed and 0,09 redraw / multi is 300/300 with 0.083 # 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) neighborhood = MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)
ca = make_cellular_automaton(dimension=[100, 100], neighborhood=neighborhood, rule=TestRule(), state_class=MyState) ca = CAFactory.make_cellular_automaton(dimension=[100, 100],
ca_processor = CellularAutomatonMultiProcessor(cellular_automaton=ca, process_count=4) 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 = PyGameFor2D(window_size=[1000, 800], cellular_automaton=ca)
ca_window.main_loop(cellular_automaton_processor=ca_processor, ca_iterations_per_draw=1) ca_window.main_loop(cellular_automaton_processor=ca_processor, evolution_steps_per_draw=1)

Binary file not shown.

View File

@ -1,7 +1,8 @@
from .ca_cell import * from .ca_cell import *
from .ca_cell_state import * from .ca_cell_state import *
from .ca_display import * from .ca_display import *
from .ca_factory import *
from .ca_neighborhood import * from .ca_neighborhood import *
from .ca_rule import * from .ca_rule import *
from .ca_state import *
from .cellular_automaton import * from .cellular_automaton import *
from .ca_factory import *

View File

@ -5,19 +5,17 @@ from typing import Type
class Cell: class Cell:
def __init__(self, state_class: Type[CellState]): def __init__(self, state_class: Type[CellState]):
self.state = state_class() self.state = state_class()
self.neighbours = [] self.neighbor_states = []
@staticmethod def evolve_if_ready(self, rule, evolution_step):
def evolve_if_ready(cell, rule, iteration): if self.state.is_active(evolution_step):
if cell.state.is_active(iteration): new_state = rule(self.state.get_state_of_last_evolution_step(evolution_step),
new_state = rule(cell.state.get_state_of_last_iteration(iteration), [n.get_state_of_last_evolution_step(evolution_step) for n in self.neighbor_states])
[n.get_state_of_last_iteration(iteration) for n in cell.neighbours]) self.set_new_state_and_activate(new_state, evolution_step)
Cell.set_new_state_and_activate(cell, new_state, iteration)
@staticmethod def set_new_state_and_activate(self, new_state: CellState, evolution_step):
def set_new_state_and_activate(cell, new_state: CellState, iteration): changed = self.state.set_state_of_evolution_step(new_state, evolution_step)
changed = cell.state.set_state_of_iteration(new_state, iteration)
if changed: if changed:
cell.state.set_active_for_next_iteration(iteration) self.state.set_active_for_next_evolution_step(evolution_step)
for n in cell.neighbours: for n in self.neighbor_states:
n.set_active_for_next_iteration(iteration) n.set_active_for_next_evolution_step(evolution_step)

View File

@ -16,63 +16,82 @@ class CellState:
self._active[0] = True self._active[0] = True
self._dirty = draw_first_state self._dirty = draw_first_state
def is_active(self, iteration): def is_active(self, current_evolution_step):
return self._active[self._calculate_slot(iteration)] """ 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 self._active[self._calculate_slot(current_evolution_step)]
def set_active_for_next_iteration(self, iteration): def set_active_for_next_evolution_step(self, current_evolution_step):
self._active[self._calculate_slot(iteration + 1)] = True """ 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): def is_set_for_redraw(self):
""" States if this state should be redrawn.
:return: True if redraw is needed.
"""
return self._dirty return self._dirty
def was_redrawn(self): def was_redrawn(self):
""" Remove the state from redraw cycle until next state change """
self._dirty = False self._dirty = False
def get_state_of_last_iteration(self, current_iteration_index): def get_state_of_last_evolution_step(self, current_evolution_step):
return self.get_state_of_iteration(current_iteration_index - 1) return self.get_state_of_evolution_step(current_evolution_step - 1)
def get_state_of_iteration(self, iteration): def get_state_of_evolution_step(self, evolution_step):
""" Will return the state for the iteration modulo number of saved states. """ Returns the state of the evolution_step.
:param iteration: Uses the iteration index, to differ between concurrent states. :param evolution_step: Uses the evolution_step index, to differ between concurrent states.
:return The state for this iteration. :return The state of this evolution_step.
""" """
return self._state_slots[self._calculate_slot(iteration)] return self._state_slots[self._calculate_slot(evolution_step)]
def set_state_of_iteration(self, new_state, iteration): def set_state_of_evolution_step(self, new_state, evolution_step):
""" Will set the new state for the iteration modulo number of saved states. """ Sets the new state for the evolution_step.
:param new_state: The new state to set. :param new_state: The new state to set.
:param iteration: Uses the iteration index, to differ between concurrent states. :param evolution_step: The evolution_step index, to differ between concurrent states.
:return True if state has changed. :return True if the state really changed.
""" """
self._change_state_values(new_state, iteration) changed = self._set_new_state_if_valid(new_state, evolution_step)
changed = self._did_state_change(iteration)
self._dirty |= changed self._dirty |= changed
self._active[self._calculate_slot(iteration)] = False self._active[self._calculate_slot(evolution_step)] = False
return changed return changed
def _did_state_change(self, iteration): def _set_new_state_if_valid(self, new_state, evolution_step):
for a, b in zip(self._state_slots[self._calculate_slot(iteration)], current_state = self.get_state_of_evolution_step(evolution_step)
self._state_slots[self._calculate_slot(iteration - 1)]):
if a != b:
return True
return False
def _change_state_values(self, new_state, iteration):
current_state = self.get_state_of_iteration(iteration)
if len(new_state) != len(current_state): if len(new_state) != len(current_state):
raise IndexError("State length may not change!") raise IndexError("State length may not change!")
self.__change_current_state_values(current_state, new_state)
return self.__did_state_change(evolution_step)
@staticmethod
def __change_current_state_values(current_state, new_state):
for i, ns in enumerate(new_state): for i, ns in enumerate(new_state):
if current_state[i] != ns: if current_state[i] != ns:
current_state[i] = ns current_state[i] = ns
def get_state_draw_color(self, iteration): def __did_state_change(self, evolution_step):
for a, b in zip(self.get_state_of_evolution_step(evolution_step),
self.get_state_of_last_evolution_step(evolution_step)):
if a != b:
return True
return False
def get_state_draw_color(self, evolution_step):
""" When implemented should return the color representing the requested state.
:param evolution_step: Requested evolution_step.
:return: Color of the state as rgb tuple
"""
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def _calculate_slot(cls, iteration): def _calculate_slot(cls, evolution_step):
return iteration % cls._state_save_slot_count return evolution_step % cls._state_save_slot_count
class SynchronousCellState(CellState): class SynchronousCellState(CellState):
@ -86,8 +105,8 @@ class SynchronousCellState(CellState):
self._active[0].value = True self._active[0].value = True
self._dirty = RawValue(c_bool, draw_first_state) self._dirty = RawValue(c_bool, draw_first_state)
def set_active_for_next_iteration(self, iteration): def set_active_for_next_evolution_step(self, current_evolution_step):
self._active[self._calculate_slot(iteration + 1)].value = True self._active[self._calculate_slot(current_evolution_step + 1)].value = True
def is_set_for_redraw(self): def is_set_for_redraw(self):
return self._dirty.value return self._dirty.value
@ -95,13 +114,8 @@ class SynchronousCellState(CellState):
def was_redrawn(self): def was_redrawn(self):
self._dirty.value = False self._dirty.value = False
def set_state_of_iteration(self, new_state, iteration): def set_state_of_evolution_step(self, new_state, evolution_step):
self._change_state_values(new_state, iteration) changed = self._set_new_state_if_valid(new_state, evolution_step)
changed = self._did_state_change(iteration)
self._dirty.value |= changed self._dirty.value |= changed
self._active[self._calculate_slot(iteration)].value = False self._active[self._calculate_slot(evolution_step)].value = False
return changed return changed
@classmethod
def _calculate_slot(cls, iteration):
return iteration % cls._state_save_slot_count

View File

@ -7,7 +7,8 @@ import pstats
from pympler import asizeof from pympler import asizeof
from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonProcessor from cellular_automaton.ca_state import CellularAutomatonState
from cellular_automaton.cellular_automaton import CellularAutomatonProcessor
class _DisplayInfo: class _DisplayInfo:
@ -19,7 +20,7 @@ class _DisplayInfo:
class DisplayFor2D: class DisplayFor2D:
def __init__(self, grid_rect: list, cellular_automaton: CellularAutomaton, screen): def __init__(self, grid_rect: list, cellular_automaton: CellularAutomatonState, screen):
self._cellular_automaton = cellular_automaton self._cellular_automaton = cellular_automaton
cell_size = self._calculate_cell_display_size(grid_rect[-2:]) cell_size = self._calculate_cell_display_size(grid_rect[-2:])
self._display_info = _DisplayInfo(grid_rect[-2:], grid_rect[:2], cell_size, screen) self._display_info = _DisplayInfo(grid_rect[-2:], grid_rect[:2], cell_size, screen)
@ -31,8 +32,8 @@ class DisplayFor2D:
def _cell_redraw_rectangles(self): def _cell_redraw_rectangles(self):
for coordinate, cell in self._cellular_automaton.cells.items(): for coordinate, cell in self._cellular_automaton.cells.items():
if cell.state.is_set_for_redraw(): if cell.state.is_set_for_redraw():
cell_color = cell.state.get_state_draw_color(self._cellular_automaton.evolution_iteration_index) cell_color = cell.state.get_state_draw_color(self._cellular_automaton.current_evolution_step)
cell_pos = _calculate_cell_position(self._display_info.cell_size, coordinate) cell_pos = self._calculate_cell_position(self._display_info.cell_size, coordinate)
surface_pos = list(map(operator.add, cell_pos, self._display_info.grid_pos)) surface_pos = list(map(operator.add, cell_pos, self._display_info.grid_pos))
yield self._display_info.screen.fill(cell_color, (surface_pos, self._display_info.cell_size)) yield self._display_info.screen.fill(cell_color, (surface_pos, self._display_info.cell_size))
cell.state.was_redrawn() cell.state.was_redrawn()
@ -41,9 +42,13 @@ class DisplayFor2D:
grid_dimension = self._cellular_automaton.dimension grid_dimension = self._cellular_automaton.dimension
return list(map(operator.truediv, grid_size, grid_dimension)) return list(map(operator.truediv, grid_size, grid_dimension))
@staticmethod
def _calculate_cell_position(cell_size, coordinate):
return list(map(operator.mul, cell_size, coordinate))
class PyGameFor2D: class PyGameFor2D:
def __init__(self, window_size: list, cellular_automaton: CellularAutomaton): def __init__(self, window_size: list, cellular_automaton: CellularAutomatonState):
self._window_size = window_size self._window_size = window_size
self._cellular_automaton = cellular_automaton self._cellular_automaton = cellular_automaton
pygame.init() pygame.init()
@ -63,7 +68,7 @@ class PyGameFor2D:
update_rect = self._screen.blit(label, pos) update_rect = self._screen.blit(label, pos)
pygame.display.update(update_rect) pygame.display.update(update_rect)
def main_loop(self, cellular_automaton_processor: CellularAutomatonProcessor, ca_iterations_per_draw): def main_loop(self, cellular_automaton_processor: CellularAutomatonProcessor, evolution_steps_per_draw):
running = True running = True
cellular_automaton_processor.evolve() cellular_automaton_processor.evolve()
first = True first = True
@ -75,7 +80,7 @@ class PyGameFor2D:
self._evolve_with_performance(cellular_automaton_processor) self._evolve_with_performance(cellular_automaton_processor)
first = False first = False
else: else:
cellular_automaton_processor.evolve_x_times(ca_iterations_per_draw) cellular_automaton_processor.evolve_x_times(evolution_steps_per_draw)
time_ca_end = time.time() time_ca_end = time.time()
self.ca_display.redraw_cellular_automaton() self.ca_display.redraw_cellular_automaton()
time_ds_end = time.time() time_ds_end = time.time()
@ -95,7 +100,3 @@ class PyGameFor2D:
p.sort_stats('time').print_stats(10) p.sort_stats('time').print_stats(10)
print("TOTAL TIME: " + "{0:.4f}".format(time_ca_end - time_ca_start) + "s") print("TOTAL TIME: " + "{0:.4f}".format(time_ca_end - time_ca_start) + "s")
print("SIZE: " + "{0:.4f}".format(size / (1024 * 1024)) + "MB") print("SIZE: " + "{0:.4f}".format(size / (1024 * 1024)) + "MB")
def _calculate_cell_position(cell_size, coordinate):
return list(map(operator.mul, cell_size, coordinate))

View File

@ -1,5 +1,4 @@
from cellular_automaton.ca_cell import Cell, CellState from cellular_automaton import *
from cellular_automaton.ca_neighborhood import Neighborhood
from typing import Type from typing import Type
import itertools import itertools
@ -7,12 +6,12 @@ import itertools
class CAFactory: class CAFactory:
@staticmethod @staticmethod
def make_cellular_automaton(dimension, def make_cellular_automaton(dimension,
neighborhood: Type[Neighborhood], neighborhood: Neighborhood,
state_class: Type[CellState]): state_class: Type[CellState],
rule: Type[Rule]):
cells = CAFactory._make_cells(dimension, state_class) cells = CAFactory._make_cells(dimension, state_class)
CAFactory._apply_neighbourhood_to_cells(cells, neighborhood, dimension) CAFactory._apply_neighborhood_to_cells(cells, neighborhood, dimension)
return cells return CellularAutomatonState(cells, dimension, rule)
@staticmethod @staticmethod
def _make_cells(dimension, state_class): def _make_cells(dimension, state_class):
@ -22,8 +21,8 @@ class CAFactory:
return cells return cells
@staticmethod @staticmethod
def _apply_neighbourhood_to_cells(cells, neighborhood, dimension): def _apply_neighborhood_to_cells(cells, neighborhood, dimension):
for coordinate, cell in cells.items(): for coordinate, cell in cells.items():
n_coordinates = neighborhood.calculate_cell_neighbor_coordinates(coordinate, dimension) n_coordinates = neighborhood.calculate_cell_neighbor_coordinates(coordinate, dimension)
cell.neighbours = [cells[tuple(nc)].state for nc in n_coordinates] cell.neighbor_states = [cells[tuple(nc)].state for nc in n_coordinates]

View File

@ -1,56 +1,75 @@
from enum import Enum from enum import Enum
from operator import add from operator import add
from itertools import product
class EdgeRule(Enum): class EdgeRule(Enum):
IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS = 0 IGNORE_EDGE_CELLS = 0
IGNORE_EDGE_CELLS = 1 IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS = 1
FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS = 2 FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS = 2
class Neighborhood: class Neighborhood:
def __init__(self, neighbours_relative: list, edge_rule: EdgeRule): def __init__(self, neighbors_relative, edge_rule: EdgeRule):
""" Defines a neighborhood for cells. """ Defines a neighborhood of a cell.
:param neighbours_relative: List of relative coordinates of cells neighbours. :param neighbors_relative: List of relative coordinates for cell neighbors.
:param edge_rule: EdgeRule to define, how cells on the edge of the grid will be handled. :param edge_rule: EdgeRule to define, how cells on the edge of the grid will be handled.
""" """
self._rel_neighbors = neighbours_relative self._rel_neighbors = neighbors_relative
self.edge_rule = edge_rule self.__edge_rule = edge_rule
self.grid_dimensions = [] self.__grid_dimensions = []
def calculate_cell_neighbor_coordinates(self, cell_coordinate, grid_dimensions): def calculate_cell_neighbor_coordinates(self, cell_coordinate, grid_dimensions):
""" Get a list of coordinates for the cell neighbors. The EdgeRule can reduce the returned neighbor count. """ Get a list of absolute coordinates for the cell neighbors.
:param cell_coordinate: The coordinate of the cell to get the neighbors The EdgeRule can reduce the returned neighbor count.
:param grid_dimensions: The dimensions of the grid, to apply edge the rule. :param cell_coordinate: The coordinate of the cell.
:return: :param grid_dimensions: The dimensions of the grid, to apply the edge the rule.
:return: list of absolute coordinates for the cells neighbors.
""" """
self.grid_dimensions = grid_dimensions self.__grid_dimensions = grid_dimensions
return list(self._neighbours_generator(cell_coordinate)) return list(self.__neighbors_generator(cell_coordinate))
def _neighbours_generator(self, cell_coordinate): def __neighbors_generator(self, cell_coordinate):
if not self._does_ignore_edge_cell_rule_apply(cell_coordinate): if not self.__does_ignore_edge_cell_rule_apply(cell_coordinate):
for rel_n in self._rel_neighbors: for rel_n in self._rel_neighbors:
yield from self._calculate_abs_neighbour_and_decide_validity(cell_coordinate, rel_n) yield from self.__calculate_abs_neighbor_and_decide_validity(cell_coordinate, rel_n)
def _calculate_abs_neighbour_and_decide_validity(self, cell_coordinate, rel_n): def __calculate_abs_neighbor_and_decide_validity(self, cell_coordinate, rel_n):
n = list(map(add, rel_n, cell_coordinate)) n = list(map(add, rel_n, cell_coordinate))
n_folded = self._apply_edge_overflow(n) n_folded = self.__apply_edge_overflow(n)
if n == n_folded or self.edge_rule == EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS: if n == n_folded or self.__edge_rule == EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS:
yield n_folded yield n_folded
def _does_ignore_edge_cell_rule_apply(self, coordinate): def __does_ignore_edge_cell_rule_apply(self, coordinate):
return self.edge_rule == EdgeRule.IGNORE_EDGE_CELLS and self._is_coordinate_on_an_edge(coordinate) return self.__edge_rule == EdgeRule.IGNORE_EDGE_CELLS and self.__is_coordinate_on_an_edge(coordinate)
def _is_coordinate_on_an_edge(self, coordinate): def __is_coordinate_on_an_edge(self, coordinate):
return all(0 == ci or ci == di-1 for ci, di in zip(coordinate, self.grid_dimensions)) return all(0 == ci or ci == di-1 for ci, di in zip(coordinate, self.__grid_dimensions))
def _apply_edge_overflow(self, n): def __apply_edge_overflow(self, n):
return list(map(lambda ni, di: (ni + di) % di, n, self.grid_dimensions)) return list(map(lambda ni, di: (ni + di) % di, n, self.__grid_dimensions))
class MooreNeighborhood(Neighborhood): class MooreNeighborhood(Neighborhood):
def __init__(self, edge_rule: EdgeRule = EdgeRule.IGNORE_EDGE_CELLS): def __init__(self, edge_rule: EdgeRule = EdgeRule.IGNORE_EDGE_CELLS, range_=1, dimension=2):
super().__init__([[-1, -1], [0, -1], [1, -1], super().__init__(tuple(_rel_neighbor_generator(dimension, range_, lambda rel_n: True)),
[-1, 0], [1, 0],
[-1, 1], [0, 1], [1, 1]],
edge_rule) edge_rule)
class VonNeumannNeighborhood(Neighborhood):
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)),
edge_rule)
def neighbor_rule(self, rel_n):
cross_sum = 0
for ci in rel_n:
cross_sum += abs(ci)
return cross_sum <= self.range_
def _rel_neighbor_generator(dimension, range_, rule):
for c in product(range(-range_, range_ + 1), repeat=dimension):
if rule(c) and c != (0, ) * dimension:
yield tuple(reversed(c))

View File

@ -7,11 +7,11 @@ class Rule:
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def evolve_cell(last_cell_state, neighbours_last_states): def evolve_cell(last_cell_state, neighbors_last_states):
""" Calculates and sets new state of 'cell'. """ Calculates and sets new state of 'cell'.
:param last_cell_state: The cells current state to calculate new state for. :param last_cell_state: The cells current state to calculate new state for.
:param neighbours_last_states: The cells neighbours current states. :param neighbors_last_states: The cells neighbors current states.
:return: True if state changed, False if not. :return: True if state changed, False if not.
A cells evolution will only be called if it or at least one of its neighbours has changed last iteration cycle. A cells evolution will only be called if it or at least one of its neighbors has changed last evolution_step cycle.
""" """
return False return False

View File

@ -0,0 +1,10 @@
from cellular_automaton.ca_rule import Rule
from typing import Type
class CellularAutomatonState:
def __init__(self, cells, dimension, evolution_rule: Type[Rule]):
self.cells = cells
self.dimension = dimension
self.evolution_rule = evolution_rule
self.current_evolution_step = -1

View File

@ -1,18 +1,8 @@
import multiprocessing import multiprocessing
from multiprocessing import freeze_support
from cellular_automaton.ca_rule import Rule
from cellular_automaton.ca_cell import Cell
from ctypes import c_int from ctypes import c_int
class CellularAutomaton:
def __init__(self, cells, dimension, evolution_rule: Rule):
self.cells = cells
self.dimension = dimension
self.evolution_rule = evolution_rule
self.evolution_iteration_index = -1
class CellularAutomatonProcessor: class CellularAutomatonProcessor:
def __init__(self, cellular_automaton): def __init__(self, cellular_automaton):
self._ca = cellular_automaton self._ca = cellular_automaton
@ -22,51 +12,51 @@ class CellularAutomatonProcessor:
self.evolve() self.evolve()
def evolve(self): def evolve(self):
self._ca.evolution_iteration_index += 1 self._ca.current_evolution_step += 1
i = self._ca.evolution_iteration_index i = self._ca.current_evolution_step
r = self._ca.evolution_rule.evolve_cell r = self._ca.evolution_rule.evolve_cell
list(map(lambda c: Cell.evolve_if_ready((c.state, c.neighbours), r, i), tuple(self._ca.cells.items()))) list(map(lambda c: c.evolve_if_ready(r, i), tuple(self._ca.cells.values())))
# print(sum(1 for c in self._ca.cells if c.state.is_set_for_redraw()))
class CellularAutomatonMultiProcessor(CellularAutomatonProcessor): class CellularAutomatonMultiProcessor(CellularAutomatonProcessor):
def __init__(self, cellular_automaton, process_count: int = 2): def __init__(self, cellular_automaton, process_count: int = 2):
freeze_support()
if process_count < 1: if process_count < 1:
raise ValueError raise ValueError
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.evolution_iteration_index = multiprocessing.RawValue(c_int, -1) self.shared_evolution_step = multiprocessing.RawValue(c_int, self._ca.current_evolution_step)
self.__init_processes_and_clean_cell_instances(process_count)
def __init_processes_and_clean_cell_instances(self, process_count):
self.pool = multiprocessing.Pool(processes=process_count, self.pool = multiprocessing.Pool(processes=process_count,
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.evolution_iteration_index)) self.shared_evolution_step))
self._evolve_method = self.pool.map
for cell in self._ca.cells.values(): for cell in self._ca.cells.values():
del cell.neighbours del cell.neighbor_states
def evolve(self): def evolve(self):
self._ca.evolution_iteration_index += 1 self._ca.current_evolution_step += 1
self.evolution_iteration_index.value = self._ca.evolution_iteration_index 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)
global_cells = None global_cells = None
global_rule = None global_rule = None
global_iteration = None global_evolution_step = None
def _init_process(cells, rule, index): def _init_process(cells, rule, index):
global global_rule, global_cells, global_iteration global global_rule, global_cells, global_evolution_step
global_cells = cells global_cells = cells
global_rule = rule global_rule = rule
global_iteration = index global_evolution_step = index
def _process_routine(i): def _process_routine(i):
Cell.evolve_if_ready(global_cells[i], global_rule.evolve_cell, global_iteration.value) global_cells[i].evolve_if_ready(global_rule.evolve_cell, global_evolution_step.value)

View File

@ -13,27 +13,27 @@ class TestState(CellState):
class TestCellState(unittest.TestCase): class TestCellState(unittest.TestCase):
def setUp(self): def setUp(self):
self.cell = Cell(TestState) self.cell = Cell(TestState)
self.neighbours = [TestState() for x in range(5)] self.neighbors = [TestState() for x in range(5)]
for neighbour in self.neighbours: for neighbor in self.neighbors:
neighbour.set_state_of_iteration((0, ), 0) neighbor.set_state_of_evolution_step((0, ), 0)
self.cell.neighbours = self.neighbours self.cell.neighbor_states = self.neighbors
def cell_and_neighbours_active(self, iteration): def cell_and_neighbors_active(self, evolution_step):
self.neighbours.append(self.cell.state) self.neighbors.append(self.cell.state)
all_active = True all_active = True
for state in self.neighbours: for state in self.neighbors:
if not state.is_active(iteration): if not state.is_active(evolution_step):
all_active = False all_active = False
return all_active return all_active
def test_evolve_activation(self): def test_evolve_activation(self):
Cell.evolve_if_ready(self.cell, (lambda a, b: (1,)), 0) self.cell.evolve_if_ready((lambda a, b: (1,)), 0)
all_active = self.cell_and_neighbours_active(1) all_active = self.cell_and_neighbors_active(1)
self.assertTrue(all_active) self.assertTrue(all_active)
def test_evolve_activation_on_no_change(self): def test_evolve_activation_on_no_change(self):
Cell.evolve_if_ready(self.cell, (lambda a, b: (0,)), 0) self.cell.evolve_if_ready((lambda a, b: (0,)), 0)
all_active = self.cell_and_neighbours_active(1) all_active = self.cell_and_neighbors_active(1)
self.assertFalse(all_active) self.assertFalse(all_active)

View File

@ -10,33 +10,33 @@ class TestCellState(unittest.TestCase):
self.cell_state = cs.SynchronousCellState(initial_state=(0,), draw_first_state=False) self.cell_state = cs.SynchronousCellState(initial_state=(0,), draw_first_state=False)
def test_get_state_with_overflow(self): def test_get_state_with_overflow(self):
self.cell_state.set_state_of_iteration(new_state=(1,), iteration=0) self.cell_state.set_state_of_evolution_step(new_state=(1,), evolution_step=0)
self.assertEqual(tuple(self.cell_state.get_state_of_iteration(2)), (1,)) self.assertEqual(tuple(self.cell_state.get_state_of_evolution_step(2)), (1,))
def test_set_state_with_overflow(self): def test_set_state_with_overflow(self):
self.cell_state.set_state_of_iteration(new_state=(1,), iteration=3) self.cell_state.set_state_of_evolution_step(new_state=(1,), evolution_step=3)
self.assertEqual(tuple(self.cell_state.get_state_of_iteration(1)), (1,)) self.assertEqual(tuple(self.cell_state.get_state_of_evolution_step(1)), (1,))
def test_set_state_does_not_effect_all_slots(self): def test_set_state_does_not_effect_all_slots(self):
self.cell_state.set_state_of_iteration(new_state=(1,), iteration=0) self.cell_state.set_state_of_evolution_step(new_state=(1,), evolution_step=0)
self.assertEqual(tuple(self.cell_state.get_state_of_iteration(1)), (0,)) self.assertEqual(tuple(self.cell_state.get_state_of_evolution_step(1)), (0,))
def test_redraw_state_on_change(self): def test_redraw_state_on_change(self):
self.cell_state.set_state_of_iteration(new_state=(1,), iteration=0) self.cell_state.set_state_of_evolution_step(new_state=(1,), evolution_step=0)
self.assertTrue(self.cell_state.is_set_for_redraw()) self.assertTrue(self.cell_state.is_set_for_redraw())
def test_redraw_state_on_nochange(self): def test_redraw_state_on_nochange(self):
self.cell_state.set_state_of_iteration(new_state=(0,), iteration=0) self.cell_state.set_state_of_evolution_step(new_state=(0,), evolution_step=0)
self.assertFalse(self.cell_state.is_set_for_redraw()) self.assertFalse(self.cell_state.is_set_for_redraw())
def test_active_state_after_set(self): def test_active_state_after_set(self):
self.cell_state.set_state_of_iteration(new_state=(1,), iteration=0) self.cell_state.set_state_of_evolution_step(new_state=(1,), evolution_step=0)
self.assertFalse(self.cell_state.is_active(0)) self.assertFalse(self.cell_state.is_active(0))
self.assertFalse(self.cell_state.is_active(1)) self.assertFalse(self.cell_state.is_active(1))
def test_set_active_for_next_iteration(self): def test_set_active_for_next_evolution_step(self):
self.cell_state.set_state_of_iteration(new_state=(1,), iteration=0) self.cell_state.set_state_of_evolution_step(new_state=(1,), evolution_step=0)
self.cell_state.set_active_for_next_iteration(0) self.cell_state.set_active_for_next_evolution_step(0)
self.assertFalse(self.cell_state.is_active(0)) self.assertFalse(self.cell_state.is_active(0))
self.assertTrue(self.cell_state.is_active(1)) self.assertTrue(self.cell_state.is_active(1))
@ -44,7 +44,7 @@ class TestCellState(unittest.TestCase):
self.assertRaises(IndexError, self.__set_state_with_new_length) self.assertRaises(IndexError, self.__set_state_with_new_length)
def __set_state_with_new_length(self): def __set_state_with_new_length(self):
return self.cell_state.set_state_of_iteration(new_state=(1, 1), iteration=0) return self.cell_state.set_state_of_evolution_step(new_state=(1, 1), evolution_step=0)
def test_redraw_flag(self): def test_redraw_flag(self):
self.cell_state = cs.SynchronousCellState(initial_state=(0,), draw_first_state=True) self.cell_state = cs.SynchronousCellState(initial_state=(0,), draw_first_state=True)

View File

@ -12,56 +12,56 @@ class TestFac(CAFactory):
return CAFactory._make_cells(dimension, state_class) return CAFactory._make_cells(dimension, state_class)
@staticmethod @staticmethod
def apply_neighbourhood(cells, neighborhood, dimension): def apply_neighborhood(cells, neighborhood, dimension):
return CAFactory._apply_neighbourhood_to_cells(cells, neighborhood, dimension) return CAFactory._apply_neighborhood_to_cells(cells, neighborhood, dimension)
class TestCAFactory(unittest.TestCase): class TestCAFactory(unittest.TestCase):
def setUp(self):
self._neighborhood = MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS)
def test_make_ca_calls_correct_methods(self): def test_make_ca_calls_correct_methods(self):
with mock.patch.object(CAFactory, '_make_cells', return_value={1: True}) as m1: with mock.patch.object(CAFactory, '_make_cells', return_value={1: True}) as m1:
with mock.patch.object(CAFactory, '_apply_neighbourhood_to_cells') as m2: with mock.patch.object(CAFactory, '_apply_neighborhood_to_cells') as m2:
CAFactory.make_cellular_automaton([10], Neighborhood, CellState) CAFactory.make_cellular_automaton([10], self._neighborhood, CellState, Rule())
m1.assert_called_once_with([10], CellState) m1.assert_called_once_with([10], CellState)
m2.assert_called_once_with({1: True}, Neighborhood, [10]) m2.assert_called_once_with({1: True}, self._neighborhood, [10])
def test_make_ca_returns_correct_values(self): def test_make_ca_returns_correct_values(self):
with mock.patch.object(CAFactory, '_make_cells', return_value={1: True}): with mock.patch.object(CAFactory, '_make_cells', return_value={1: True}):
with mock.patch.object(CAFactory, '_apply_neighbourhood_to_cells'): with mock.patch.object(CAFactory, '_apply_neighborhood_to_cells'):
cells = CAFactory.make_cellular_automaton([10], Neighborhood, CellState) ca = CAFactory.make_cellular_automaton([10], self._neighborhood, CellState, Rule())
self.assertEqual(tuple(cells.values()), (True, )) self.assertIsInstance(ca, CellularAutomatonState)
self.assertEqual(tuple(ca.cells.values()), (True, ))
def test_1dimension_coordinates(self): def test_1dimension_coordinates(self):
fac = TestFac() c = TestFac.make_cells([3], CellState)
c = fac.make_cells([3], CellState)
self.assertEqual(list(c.keys()), [(0,), (1,), (2,)]) self.assertEqual(list(c.keys()), [(0,), (1,), (2,)])
def test_2dimension_coordinates(self): def test_2dimension_coordinates(self):
fac = TestFac() c = TestFac.make_cells([2, 2], CellState)
c = fac.make_cells([2, 2], CellState)
self.assertEqual(list(c.keys()), [(0, 0), (0, 1), (1, 0), (1, 1)]) self.assertEqual(list(c.keys()), [(0, 0), (0, 1), (1, 0), (1, 1)])
def test_3dimension_coordinates(self): def test_3dimension_coordinates(self):
fac = TestFac() c = TestFac.make_cells([2, 2, 2], CellState)
c = fac.make_cells([2, 2, 2], CellState)
self.assertEqual(list(c.keys()), [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), self.assertEqual(list(c.keys()), [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1),
(1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]) (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)])
def test_apply_neighbourhood(self): def test_apply_neighborhood(self):
fac = TestFac() cells = TestFac.make_cells([3, 3], CellState)
cells = fac.make_cells([3, 3], CellState) TestFac.apply_neighborhood(cells, self._neighborhood, [3, 3])
fac.apply_neighbourhood(cells, MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS), [3, 3])
neighbours = self.__create_neighbour_list_of_cell((1, 1), cells) neighbors = self.__create_neighbor_list_of_cell((1, 1), cells)
self.assertEqual(set(neighbours), set(cells[(1, 1)].neighbours)) self.assertEqual(set(neighbors), set(cells[(1, 1)].neighbor_states))
@staticmethod @staticmethod
def __create_neighbour_list_of_cell(cell_id, cells): def __create_neighbor_list_of_cell(cell_id, cells):
neighbours = [] neighbors = []
for c in cells.values(): for c in cells.values():
if c != cells[cell_id]: if c != cells[cell_id]:
neighbours.append(c.state) neighbors.append(c.state)
return neighbours return neighbors
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -7,11 +7,12 @@ import unittest
class TestNeighborhood(unittest.TestCase): class TestNeighborhood(unittest.TestCase):
@staticmethod @staticmethod
def check_neighbors(neighborhood, neighborhood_sets): def check_neighbors(neighborhood, neighborhood_sets, dimension=(3, 3)):
for neighborhood_set in neighborhood_sets: for neighborhood_set in neighborhood_sets:
neighbors = neighborhood.calculate_cell_neighbor_coordinates(neighborhood_set[0], [3, 3]) neighbors = neighborhood.calculate_cell_neighbor_coordinates(neighborhood_set[0], dimension)
if neighborhood_set[1] != neighbors: if neighborhood_set[1] != neighbors:
print("Error neighbours do not fit (expected, real): ", (neighborhood_set[1]), neighbors) print("\nrel_n:", neighborhood._rel_neighbors)
print("\nWrong neighbors (expected, real): ", (neighborhood_set[1]), neighbors)
return False return False
return True return True
@ -36,6 +37,22 @@ class TestNeighborhood(unittest.TestCase):
n22 = [[2, 2], [[1, 1], [2, 1], [0, 1], [1, 2], [0, 2], [1, 0], [2, 0], [0, 0]]] n22 = [[2, 2], [[1, 1], [2, 1], [0, 1], [1, 2], [0, 2], [1, 0], [2, 0], [0, 0]]]
self.assertTrue(self.check_neighbors(neighborhood, [n00, n11, n22])) self.assertTrue(self.check_neighbors(neighborhood, [n00, n11, n22]))
def test_von_neumann_r1(self):
neighborhood = csn.VonNeumannNeighborhood(csn.EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)
n1 = [[1, 1], [[1, 0], [0, 1], [2, 1], [1, 2]]]
self.assertTrue(self.check_neighbors(neighborhood, [n1]))
def test_von_neumann_r2(self):
neighborhood = csn.VonNeumannNeighborhood(csn.EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS, range_=2)
n1 = [[2, 2], [[2, 0], [1, 1], [2, 1], [3, 1], [0, 2], [1, 2], [3, 2], [4, 2], [1, 3], [2, 3], [3, 3], [2, 4]]]
self.assertTrue(self.check_neighbors(neighborhood, [n1], dimension=[5, 5]))
def test_von_neumann_d3(self):
neighborhood = csn.VonNeumannNeighborhood(csn.EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS,
dimension=3)
n1 = [[1, 1, 1], [[1, 1, 0], [1, 0, 1], [0, 1, 1], [2, 1, 1], [1, 2, 1], [1, 1, 2]]]
self.assertTrue(self.check_neighbors(neighborhood, [n1], dimension=[3, 3, 3]))
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()