diff --git a/scripts/main_ui.py b/scripts/main_ui.py index 71e62de..d3af0cd 100644 --- a/scripts/main_ui.py +++ b/scripts/main_ui.py @@ -2,7 +2,7 @@ import random -from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonEvolver +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 @@ -12,22 +12,32 @@ from cellular_automaton.ca_grid import Grid class TestRule(Rule): def evolve_cell(self, cell, iteration_index): - active = False - neighbors = cell.neighbours if cell.state is None: - rand = random.randrange(0, 101, 1) - if rand <= 99: - cell.state = MyStatus(0) - else: - cell.state = MyStatus(1) - cell.is_set_for_redraw = True - active = True - elif len(neighbors) == 8: - left_neighbour_state = neighbors[0].state.get_status_of_iteration(iteration_index - 1) + return self._init_state(cell) + else: + return self._evolve_state(cell, iteration_index) + + @staticmethod + def _evolve_state(cell, iteration_index): + try: + left_neighbour_state = cell.neighbours[0].state.get_status_of_iteration(iteration_index - 1) active = cell.state.set_status_of_iteration(left_neighbour_state, iteration_index) if active: cell.is_set_for_redraw = True - return active + return active + except IndexError: + return False + + @staticmethod + def _init_state(cell): + rand = random.randrange(0, 101, 1) + if rand <= 99: + cell.state = MyStatus(0) + return False + else: + cell.state = MyStatus(1) + cell.is_set_for_redraw = True + return True class MyStatus(CellState): @@ -44,8 +54,10 @@ class MyStatus(CellState): if __name__ == "__main__": random.seed(1000) rule = TestRule() - ca = CellularAutomaton(Grid([400, 400], MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)), - rule) - ca_evolver = CellularAutomatonEvolver(2) - ca_window = PyGameFor2D([1000, 800], ca, ca_evolver, 5) - ca_window.main_loop() + grid = Grid(dimension=[400, 400], + neighborhood=MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)) + ca = CellularAutomaton(grid, rule) + ca_window = PyGameFor2D(window_size=[1000, 800], cellular_automaton=ca) + ca_processor = CellularAutomatonProcessor(process_count=8) + ca_window.main_loop(cellular_automaton_processor=ca_processor, + ca_iterations_per_draw=5) diff --git a/src/cellular_automaton/ca_cell_state.py b/src/cellular_automaton/ca_cell_state.py index b01ceae..70a0105 100644 --- a/src/cellular_automaton/ca_cell_state.py +++ b/src/cellular_automaton/ca_cell_state.py @@ -30,3 +30,6 @@ class CellState: def get_state_draw_color(self, iteration): raise NotImplementedError + + def __str__(self): + return str(self._state_slots) diff --git a/src/cellular_automaton/ca_display.py b/src/cellular_automaton/ca_display.py index 5a520bc..d8e6236 100644 --- a/src/cellular_automaton/ca_display.py +++ b/src/cellular_automaton/ca_display.py @@ -2,7 +2,7 @@ import pygame import time import operator -from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonEvolver +from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonProcessor class _DisplayInfo: @@ -33,22 +33,16 @@ class DisplayFor2D: class PyGameFor2D: - def __init__(self, - windows_size: list, - cellular_automaton: CellularAutomaton, - cellular_automaton_evolver: CellularAutomatonEvolver, - ca_iterations_per_draw): - self._window_size = windows_size + def __init__(self, window_size: list, cellular_automaton: CellularAutomaton): + self._window_size = window_size self._cellular_automaton = cellular_automaton - self._cellular_automaton_evolver = cellular_automaton_evolver - self._ca_steps_per_draw = ca_iterations_per_draw pygame.init() pygame.display.set_caption("Cellular Automaton") self._screen = pygame.display.set_mode(self._window_size) self._font = pygame.font.SysFont("monospace", 15) - self.ca_display = DisplayFor2D([0, 30, windows_size[0], windows_size[1]-30], cellular_automaton, self._screen) + self.ca_display = DisplayFor2D([0, 30, window_size[0], window_size[1] - 30], cellular_automaton, self._screen) def _print_process_duration(self, time_ca_end, time_ca_start, time_ds_end): self._screen.fill([0, 0, 0], ((0, 0), (self._window_size[0], 30))) @@ -60,12 +54,12 @@ class PyGameFor2D: update_rect = self._screen.blit(label, pos) pygame.display.update(update_rect) - def main_loop(self): + def main_loop(self, cellular_automaton_processor: CellularAutomatonProcessor, ca_iterations_per_draw): running = True while running: time_ca_start = time.time() - self._cellular_automaton_evolver.evolve_x_times(self._cellular_automaton, self._ca_steps_per_draw) + cellular_automaton_processor.evolve_x_times(self._cellular_automaton, ca_iterations_per_draw) time_ca_end = time.time() self.ca_display._redraw_cellular_automaton() time_ds_end = time.time() diff --git a/src/cellular_automaton/cellular_automaton.py b/src/cellular_automaton/cellular_automaton.py index e740f10..502caa6 100644 --- a/src/cellular_automaton/cellular_automaton.py +++ b/src/cellular_automaton/cellular_automaton.py @@ -1,5 +1,9 @@ from cellular_automaton.ca_grid import Grid 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: @@ -9,14 +13,22 @@ class CellularAutomaton: self.evolution_iteration_index = 0 -class CellularAutomatonEvolver: +class _EvolutionProcess: + def __init__(self, process: Process, pipe: Pipe): + self.process = process + self.pipe = pipe + self.cell = None + + +class CellularAutomatonProcessor: def __init__(self, process_count: int = 1): - self.__processes = process_count + self._processes = list(_create_processes(process_count)) self.__cellular_automaton = None def evolve_x_times(self, cellular_automaton: CellularAutomaton, evolution_steps: int): """ Evolve all cells for x time steps. - :param evolution_steps: the count of evolutions done. + :param cellular_automaton: The cellular automaton to evolve. + :param evolution_steps: The count of evolutions done. :return: True if all cells are inactive """ for evo in range(evolution_steps): @@ -27,10 +39,12 @@ class CellularAutomatonEvolver: 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 @@ -44,11 +58,115 @@ class CellularAutomatonEvolver: 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: list): + def _evolve_cells(self, cells): cellular_automaton = self.__cellular_automaton - for cell in cells: - active = cellular_automaton.evolution_rule.evolve_cell(cell, cellular_automaton.evolution_iteration_index) + 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 - if active: + 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): + for i in range(count): + parent_pipe_connection, child_pipe_connection = Pipe() + p = Process(target=process_routine, args=(child_pipe_connection, )) + 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__()