second try

This commit is contained in:
Richard Feistenauer 2019-01-31 13:38:48 +01:00
parent a8d17f6fe9
commit be57f89e78
8 changed files with 169 additions and 221 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
__pycache__ __pycache__
.coverage .coverage
htmlcov/ htmlcov/
*.orig

View File

@ -1,63 +1,51 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import random
from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonProcessor
from cellular_automaton.ca_rule import Rule
from cellular_automaton.ca_neighborhood import MooreNeighborhood, EdgeRule
from cellular_automaton.ca_display import PyGameFor2D
from cellular_automaton.ca_cell_state import CellState from cellular_automaton.ca_cell_state import CellState
from cellular_automaton.ca_grid import Grid from cellular_automaton.ca_rule import Rule
class TestRule(Rule): class TestRule(Rule):
def evolve_cell(self, cell, iteration_index):
if cell.state is None:
return self._init_state(cell)
else:
return self._evolve_state(cell, iteration_index)
@staticmethod @staticmethod
def _evolve_state(cell, iteration_index): def evolve_cell(last_cell_state, last_neighbour_states):
try: try:
left_neighbour_state = cell.neighbours[0].state.get_status_of_iteration(iteration_index - 1) return last_neighbour_states[0]
active = cell.state.set_status_of_iteration(left_neighbour_state, iteration_index)
if active:
cell.is_set_for_redraw = True
return active
except IndexError: except IndexError:
return False pass
return False
@staticmethod
def _init_state(cell): class MyState(CellState):
def __init__(self):
rand = random.randrange(0, 101, 1) rand = random.randrange(0, 101, 1)
if rand <= 99: init = 0
cell.state = MyStatus(0) if rand > 99:
return False init = 1
else:
cell.state = MyStatus(1)
cell.is_set_for_redraw = True
return True
super().__init__(init, draw_first_state=False)
class MyStatus(CellState):
def __init__(self, initial_state):
super().__init__([initial_state])
def get_state_draw_color(self, iteration): def get_state_draw_color(self, iteration):
red = 0 red = 0
if self._state_slots[iteration % 2][0]: if self.get_state_of_last_iteration(iteration)[0]:
red = 255 red = 255
return [red, 0, 0] return [red, 0, 0]
if __name__ == "__main__": if __name__ == "__main__":
import random
from multiprocessing import freeze_support
from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonProcessor
from cellular_automaton.ca_neighborhood import MooreNeighborhood, EdgeRule
from cellular_automaton.ca_display import PyGameFor2D
from cellular_automaton.ca_grid import Grid
freeze_support()
random.seed(1000) random.seed(1000)
rule = TestRule() rule = TestRule()
grid = Grid(dimension=[400, 400], grid = Grid(dimension=[400, 400],
neighborhood=MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)) neighborhood=MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS),
state_class=MyState)
ca = CellularAutomaton(grid, rule) ca = CellularAutomaton(grid, rule)
ca_window = PyGameFor2D(window_size=[1000, 800], cellular_automaton=ca) ca_window = PyGameFor2D(window_size=[1000, 800], cellular_automaton=ca)
ca_processor = CellularAutomatonProcessor(process_count=8) ca_processor = CellularAutomatonProcessor(process_count=2, cellular_automaton=ca)
ca_window.main_loop(cellular_automaton_processor=ca_processor, ca_window.main_loop(cellular_automaton_processor=ca_processor,
ca_iterations_per_draw=5) ca_iterations_per_draw=5)

View File

@ -1,7 +1,61 @@
import multiprocessing
from cellular_automaton.ca_cell_state import CellState
class Cell: class Cell:
def __init__(self, name, coordinate: list): def __init__(self, name, state_class: CellState.__class__, coordinate: list):
self.name = name self._name = name
self.coordinate = coordinate self._coordinate = coordinate
self.neighbours = [] self._state = state_class()
self.state = None self._neighbours = []
self.is_set_for_redraw = False self._active = multiprocessing.Value('i', 1)
self._age = multiprocessing.Value('i', 0)
def set_neighbours(self, neighbours):
self._neighbours = neighbours
def get_coordinate(self):
return self._coordinate
def evolve_if_ready(self, rule):
if self._neighbours_are_younger():
if self._is_active():
new_state = rule(self.get_current_state(), self.get_neighbour_states())
self.set_new_state_and_activate(new_state)
self.increase_age()
def _neighbours_are_younger(self):
for n in self._neighbours:
if n.get_age() < self.get_age():
return False
def get_age(self):
return self._age.value
def _is_active(self):
return self._active.value > self._age.value
def get_current_state(self):
return self._state.get_state_of_iteration(self._age.value)
def get_neighbour_states(self):
return [n.get_state_from_iteration(self._age.value) for n in self._neighbours]
def set_new_state_and_activate(self, new_state: CellState):
changed = self._state.set_current_state(new_state, self._age.value + 1)
if changed:
self._set_active()
def _set_active(self):
self.set_active_for_next_iteration(self._age.value)
for n in self._neighbours:
n.set_active_for_next_iteration(self._age.value)
def set_active_for_next_iteration(self, iteration):
self._active.value = max(self._active.value, iteration + 1)
def increase_age(self):
with self._age.get_lock():
self._age += 1

View File

@ -1,30 +1,65 @@
from multiprocessing import Array, Value
class CellState: class CellState:
""" """
This is the base class for all cell states. This is the base class for all cell states.
When using the cellular automaton display, inherit this class and implement get_state_draw_color. When using the cellular automaton display, inherit this class and implement get_state_draw_color.
""" """
def __init__(self, initial_state, state_save_slot_count=2): def __init__(self, initial_state=(0., ), state_save_slot_count=2, draw_first_state=True):
self._state_save_slot_count = state_save_slot_count self._state_save_slot_count = state_save_slot_count
self._state_slots = [initial_state] * state_save_slot_count self._state_slots = [Array('d', initial_state)] * state_save_slot_count
if draw_first_state:
self._dirty = Value('i', 1)
else:
self._dirty = Value('i', 0)
def set_status_of_iteration(self, new_status, iteration): def is_set_for_redraw(self):
""" Will set the new status for the iteration modulo number of saved states. return self._dirty != 1
:param new_status: The new status to set.
def get_state_changes(self):
return self._dirty
def set_for_redraw(self):
self._dirty = 1
def was_redrawn(self):
self._dirty = 0
def set_current_state(self, new_state, current_iteration_index):
return self.set_state_of_iteration(new_state, current_iteration_index)
def get_state_of_last_iteration(self, current_iteration_index):
return self.get_state_of_iteration(current_iteration_index - 1)
def set_state_of_iteration(self, new_state, iteration):
""" Will set the new state for the iteration modulo number of saved states.
:param new_state: The new state to set.
:param iteration: Uses the iteration index, to differ between concurrent states. :param iteration: Uses the iteration index, to differ between concurrent states.
:return True if status has changed. :return True if state has changed.
""" """
slot_count = self._state_save_slot_count slot_count = self._state_save_slot_count
states = self._state_slots states = self._state_slots
states[iteration % slot_count] = new_status current_state = states[iteration % slot_count]
return states[(iteration - 1) % slot_count] \ changed = False
!= states[iteration % slot_count] for i in range(len(current_state)):
try:
if current_state[i] != new_state[i]:
changed = True
def get_status_of_iteration(self, iteration): current_state[i] = new_state[i]
""" Will return the status for the iteration modulo number of saved states. except IndexError:
raise IndexError("New State length or type is invalid!")
self._dirty |= changed
return changed
def get_state_of_iteration(self, iteration):
""" Will return the state for the iteration modulo number of saved states.
:param iteration: Uses the iteration index, to differ between concurrent states. :param iteration: Uses the iteration index, to differ between concurrent states.
:return The status for this iteration. :return The state for this iteration.
""" """
return self._state_slots[iteration % self._state_save_slot_count] return self._state_slots[iteration % self._state_save_slot_count]

View File

@ -59,25 +59,27 @@ class PyGameFor2D:
while running: while running:
time_ca_start = time.time() time_ca_start = time.time()
cellular_automaton_processor.evolve_x_times(self._cellular_automaton, ca_iterations_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()
self._print_process_duration(time_ca_end, time_ca_start, time_ds_end) self._print_process_duration(time_ca_end, time_ca_start, time_ds_end)
time.sleep(0.5)
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
cellular_automaton_processor.stop()
running = False running = False
def _cell_redraw_rectangles(cells, evolution_index, display_info): def _cell_redraw_rectangles(cells, evolution_index, display_info):
for cell in cells: for cell in cells:
if cell.is_set_for_redraw: if cell.state.is_set_for_redraw():
cell_color = cell.state.get_state_draw_color(evolution_index) cell_color = cell.state.get_state_draw_color(evolution_index)
cell_pos = _calculate_cell_position(display_info.cell_size, cell) cell_pos = _calculate_cell_position(display_info.cell_size, cell)
surface_pos = list(map(operator.add, cell_pos, display_info.grid_pos)) surface_pos = list(map(operator.add, cell_pos, display_info.grid_pos))
yield display_info.screen.fill(cell_color, (surface_pos, display_info.cell_size)) yield display_info.screen.fill(cell_color, (surface_pos, display_info.cell_size))
cell.is_set_for_redraw = False cell.state.was_redrawn()
def _calculate_cell_position(cell_size, cell): def _calculate_cell_position(cell_size, cell):

View File

@ -3,10 +3,11 @@ from cellular_automaton.ca_neighborhood import Neighborhood
class Grid: class Grid:
def __init__(self, dimension: list, neighborhood: Neighborhood): def __init__(self, dimension: list, neighborhood: Neighborhood, state_class):
self._dimension = dimension self._dimension = dimension
self._cells = {} self._cells = {}
self._active_cells = {} self._active_cells = {}
self._state_class = state_class
self._init_cells(neighborhood) self._init_cells(neighborhood)
@ -58,7 +59,7 @@ class Grid:
self._recursive_step_down_dimensions(coordinate, dimension_index, self._create_cells) self._recursive_step_down_dimensions(coordinate, dimension_index, self._create_cells)
except IndexError: except IndexError:
coordinate_string = _join_coordinate(coordinate) coordinate_string = _join_coordinate(coordinate)
self._cells[coordinate_string] = Cell(coordinate_string, coordinate) self._cells[coordinate_string] = Cell(coordinate_string, self._state_class, coordinate)
def _recursive_step_down_dimensions(self, coordinate, dimension_index, recursion_method): def _recursive_step_down_dimensions(self, coordinate, dimension_index, recursion_method):
""" For the range of the current dimension, recalls the recursion method. """ For the range of the current dimension, recalls the recursion method.
@ -72,9 +73,9 @@ class Grid:
def _set_cell_neighbours(self, neighborhood): def _set_cell_neighbours(self, neighborhood):
for cell in self._cells.values(): for cell in self._cells.values():
neighbours_coordinates = neighborhood.calculate_cell_neighbor_coordinates(cell.coordinate, neighbours_coordinates = neighborhood.calculate_cell_neighbor_coordinates(cell.get_coordinate(),
self._dimension) self._dimension)
cell.neighbours = list(map(self._get_cell_by_coordinate, neighbours_coordinates)) cell.set_neighbours(list(map(self._get_cell_by_coordinate, neighbours_coordinates)))
def _get_cell_by_coordinate(self, coordinate): def _get_cell_by_coordinate(self, coordinate):
return self._cells[_join_coordinate(coordinate)] return self._cells[_join_coordinate(coordinate)]

View File

@ -1,4 +1,3 @@
from cellular_automaton.ca_cell import Cell
from abc import abstractmethod from abc import abstractmethod
@ -6,11 +5,12 @@ class Rule:
def __init__(self): def __init__(self):
pass pass
@staticmethod
@abstractmethod @abstractmethod
def evolve_cell(self, cell: Cell, iteration_index: int): def evolve_cell(last_cell_state, last_neighbour_states):
""" Calculates and sets new state of 'cell'. """ Calculates and sets new state of 'cell'.
:param cell: The cell to calculate new state for. :param last_cell_state: The cells current state to calculate new state for.
:param iteration_index: The current iteration index, to choose the correct state. :param last_neighbour_states: The cells neighbours 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 neighbours has changed last iteration cycle.
""" """

View File

@ -1,9 +1,7 @@
import multiprocessing
from cellular_automaton.ca_grid import Grid from cellular_automaton.ca_grid import Grid
from cellular_automaton.ca_rule import Rule from cellular_automaton.ca_rule import Rule
from cellular_automaton.ca_cell_state import CellState
from multiprocessing import Process, Pipe, Array, Value
import multiprocessing
class CellularAutomaton: class CellularAutomaton:
@ -13,160 +11,29 @@ class CellularAutomaton:
self.evolution_iteration_index = 0 self.evolution_iteration_index = 0
class _EvolutionProcess:
def __init__(self, process: Process, pipe: Pipe):
self.process = process
self.pipe = pipe
self.cell = None
class CellularAutomatonProcessor: class CellularAutomatonProcessor:
def __init__(self, process_count: int = 1): def __init__(self, cellular_automaton, process_count: int = 1):
self._processes = list(_create_processes(process_count)) self.active = multiprocessing.Value('i', 1)
cells = list(cellular_automaton.grid.get_cells().values())
chunk_size = int(len(cells) / process_count)
self._processes = [multiprocessing.Process(target=_process_routine,
name=str(i),
args=(cells[i*chunk_size:i*chunk_size + chunk_size],
cellular_automaton.evolution_rule,
self.active))
for i in range(process_count)]
for p in self._processes:
p.start()
self.__cellular_automaton = None self.__cellular_automaton = None
def evolve_x_times(self, cellular_automaton: CellularAutomaton, evolution_steps: int): def stop(self):
""" Evolve all cells for x time steps. self.active.value = 0
:param cellular_automaton: The cellular automaton to evolve. for p in self._processes:
:param evolution_steps: The count of evolutions done. p.join()
:return: True if all cells are inactive
"""
for evo in range(evolution_steps):
finished = self.evolve(cellular_automaton)
if finished:
return True
return False
def evolve(self, cellular_automaton: CellularAutomaton):
""" Evolves all active cells for one time step.
:param cellular_automaton: The cellular automaton to evolve.
:return: True if all cells are inactive.
"""
self.__cellular_automaton = cellular_automaton
if self._is_evolution_finished():
print("finished")
return True
else:
cellular_automaton.evolution_iteration_index += 1
self._evolve_all_active_cells()
return False
def _is_evolution_finished(self):
return len(self.__cellular_automaton.grid.get_active_cell_names()) == 0
def _evolve_all_active_cells(self):
active_cells = self.__cellular_automaton.grid.get_active_cells()
self.__cellular_automaton.grid.clear_active_cells()
self._evolve_cells(active_cells.values())
print(len(self.__cellular_automaton.grid.get_active_cells()))
def _evolve_cells(self, cells):
cellular_automaton = self.__cellular_automaton
processes = self._processes
process_count = len(processes)
for i, cell in enumerate(cells):
evolution_process = processes[i % process_count]
if evolution_process.cell:
response = evolution_process.pipe.recv()
evolved_cell = evolution_process.cell
evolved_cell.state = response[0]
evolved_cell.is_set_for_redraw |= response[1]
if evolved_cell.is_set_for_redraw:
cellular_automaton.grid.set_cells_active([evolved_cell] + evolved_cell.neighbours)
evolution_process.cell = None
cell_info = self.read_cell_info(cell, cellular_automaton.evolution_iteration_index)
evolution_process.pipe.send(cell_info)
evolution_process.cell = cell
for evolution_process in processes:
response = evolution_process.pipe.recv()
cell = evolution_process.cell
cell.state = response[0]
cell.is_set_for_redraw |= response[1]
if cell.is_set_for_redraw:
cellular_automaton.grid.set_cells_active([cell] + cell.neighbours)
evolution_process.cell = None
# if cellular_automaton.evolution_rule.evolve_cell(cell.state, cellular_automaton.evolution_iteration_index):
# cellular_automaton.grid.set_cells_active([cell] + cell.neighbours)
@staticmethod
def read_cell_info(cell, iteration):
coordinate = cell.coordinate
return [coordinate, cell.state, [n.state for n in cell.neighbours], iteration]
# if cell.state is not None:
# cell_state = cell.state.get_status_of_iteration(iteration - 1)
# else:
# cell_state = None
#
# neighbor_states = []
# for neighbor in cell.neighbours:
# if neighbor.state is not None:
# neighbor_state = neighbor.state.get_status_of_iteration(iteration - 1)
# else:
# neighbor_state = None
# neighbor_states.append(neighbor_state)
#
# return [coordinate, cell_state, neighbor_states]
def _create_processes(count): def _process_routine(cells, rule, active):
for i in range(count): while active.value == 1:
parent_pipe_connection, child_pipe_connection = Pipe() for cell in cells:
p = Process(target=process_routine, args=(child_pipe_connection, )) cell.evolve_if_ready(rule)
p.start()
yield _EvolutionProcess(p, parent_pipe_connection)
def process_routine(pipe_conn: Pipe):
while True:
info = pipe_conn.recv()
pipe_conn.send(evolve_cell(*info))
def evolve_cell(coordinate, cell_state, neighbor_states, index):
if cell_state is None:
return _init_state()
else:
new_state = _evolve_state(cell_state, neighbor_states, index)
if new_state is None:
print(",".join([str(x) for x in neighbor_states]))
return [cell_state, False]
else:
changed = cell_state.set_status_of_iteration(new_state, index)
return [cell_state, changed]
def _evolve_state(cell_state, neighbor_states, index):
try:
left_neighbour_state = neighbor_states[0].get_status_of_iteration(index - 1)
return left_neighbour_state
except (IndexError, AttributeError):
return None
import random
def _init_state():
rand = random.randrange(0, 101, 1)
if rand <= 99:
return [MyStatus(0), False]
else:
return [MyStatus(1), True]
class MyStatus(CellState):
def __init__(self, initial_state):
super().__init__(initial_state)
def get_state_draw_color(self, iteration):
red = 0
if self._state_slots[iteration % 2][0]:
red = 255
return [red, 0, 0]
def __str__(self):
return super().__str__()