From 7d5f579a34631ca602a2c8d6c62748a379f71759 Mon Sep 17 00:00:00 2001 From: timofej Date: Thu, 17 Aug 2023 23:45:53 +0200 Subject: [PATCH] Add Simulate1Layer --- neuropercolation/__init__.py | 4 +- neuropercolation/automaton.py | 68 ++++++++++++++++++ neuropercolation/display.py | 127 +++++++++++++++++++++++++++++++++- 3 files changed, 195 insertions(+), 4 deletions(-) diff --git a/neuropercolation/__init__.py b/neuropercolation/__init__.py index e983c43..6b32362 100644 --- a/neuropercolation/__init__.py +++ b/neuropercolation/__init__.py @@ -17,5 +17,5 @@ limitations under the License. from .neighborhood import Neighborhood, MooreNeighborhood, RadialNeighborhood, VonNeumannNeighborhood, \ HexagonalNeighborhood, EdgeRule -from .automaton import Neuropercolation, NeuropercolationCoupled -from .display import Simulate2Layers, Simulate4Layers +from .automaton import Neurolattice, Neuropercolation, NeuropercolationCoupled +from .display import Simulate1Layer, Simulate2Layers, Simulate4Layers diff --git a/neuropercolation/automaton.py b/neuropercolation/automaton.py index 67a3653..463ee7c 100644 --- a/neuropercolation/automaton.py +++ b/neuropercolation/automaton.py @@ -79,6 +79,74 @@ class CellularAutomatonCreator(abc.ABC): raise NotImplementedError +class Neurolattice(CellularAutomatonCreator, abc.ABC): + """ Cellular automaton with the evolution rules of conways game of life """ + + def __init__(self, dim, eps, *args, **kwargs): + super().__init__(dimension=[dim, dim], + neighborhood=VonNeumannNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)) + self._evolution_step = 0 + self.epsilon = eps + + def init_cell_state(self, coord): # pylint: disable=no-self-use + return DEAD + + def get_cells(self): + return self._current_state + + def set_cells(self, cells): + """ Sets the cell states both as current and next states """ + for (coordinate, c_cell), n_cell in zip(self._current_state.items(), self._next_state.values()): + new_cell_state = cells[coordinate].state + c_cell.state = new_cell_state + n_cell.state = new_cell_state + + cells = property(get_cells, set_cells) + + def get_evolution_step(self): + return self._evolution_step + + evolution_step = property(get_evolution_step) + + def evolve(self, times=1): + """ Evolve all cells x times. + :param times: The number of evolution steps processed with one call of this method. + """ + for _ in itertools.repeat(None, times): + self.__evolve_cells(self._current_state, self._next_state) + self._current_state, self._next_state = self._next_state, self._current_state + self._evolution_step += 1 + + def __evolve_cells(self, this_state, next_state): + evolve_cell = self.__evolve_cell + evolution_rule = self.evolve_rule + for coord, old, new in zip(this_state.keys(), this_state.values(), next_state.values()): + new_state = evolution_rule(old.state.copy(), [n.state[0] for n in old.neighbors]) + evolve_cell(old, new, new_state) + + def __evolve_cell(self, old, cell, new_state): + changed = new_state != old.state + cell.state = new_state + cell.is_dirty |= changed + old.is_dirty |= changed + + def evolve_rule(self, last_cell_state, neighbors_last_states): + new_cell_state = last_cell_state + alive_neighbours = sum(neighbors_last_states)+last_cell_state[0] + + CASE = (random.random()>=self.epsilon) + if alive_neighbours > 2: + if CASE: + new_cell_state = ALIVE + else: + new_cell_state = DEAD + else: + if CASE: + new_cell_state = DEAD + else: + new_cell_state = ALIVE + return new_cell_state + class Neuropercolation(CellularAutomatonCreator, abc.ABC): """ Cellular automaton with the evolution rules of conways game of life """ diff --git a/neuropercolation/display.py b/neuropercolation/display.py index 6cf244b..c152285 100644 --- a/neuropercolation/display.py +++ b/neuropercolation/display.py @@ -18,14 +18,13 @@ limitations under the License. import os import json -import pbjson import time import operator import collections import contextlib from typing import Sequence -from . import Neuropercolation, NeuropercolationCoupled +from . import Neurolattice, Neuropercolation, NeuropercolationCoupled _Rect = collections.namedtuple(typename="Rect", field_names=["left", "top", "width", "height"]) @@ -66,6 +65,130 @@ class PygameEngine: return True +class Simulate1Layer: + def __init__(self, + dim, + eps, + res=4, + draw='pygame', + path='/cloud/Public/_data/neuropercolation/test/', + state_to_color_cb=None, + *args, **kwargs): + """ + Creates a window to render a 2D CellularAutomaton. + :param cellular_automaton: The automaton to display and evolve + :param window_size: The Window size (default: 1000 x 800) + :param stretch_cells: Stretches cells to fit into window size. (default: false) + Activating it can result in black lines throughout the automaton. + :param draw_engine: The draw_engine (default: pygame) + :param state_to_color_cb: A callback to define the draw color of CA states (default: red for states != 0) + """ + super().__init__(*args, **kwargs) + self._cellular_automaton = Neurolattice(dim,eps) + self.__cell_size = [res,res] + self.__dimension = dim + self.__epsilon = eps + self.__gridside = dim*res + self.__rect = _Rect(left=0, top=30, width=dim*res, height=dim*res) + self.__draw_engine = PygameEngine((self.__rect.width,self.__rect.height+self.__rect.top)) if draw == 'pygame' else draw + self.__state_to_color = self._get_cell_color if state_to_color_cb is None else state_to_color_cb + self.__path = path + self.__state_list = [] + + def run(self, + evolutions_per_second=0, + evolutions_per_draw=1, + last_evolution_step=0,): + """ + Evolves and draws the CellularAutomaton + :param evolutions_per_second: 0 = as fast as possible | > 0 to slow down the CellularAutomaton + :param evolutions_per_draw: Amount of evolutions done before screen gets redrawn. + :param last_evolution_step: 0 = infinite | > 0 evolution step at which this method will stop + Warning: is blocking until finished + """ + self._append_state() + with contextlib.suppress(KeyboardInterrupt): + while self._is_not_user_terminated() and self._not_at_the_end(last_evolution_step): + time_ca_start = time.time() + self._cellular_automaton.evolve(evolutions_per_draw) + self._append_state() + time_ca_end = time.time() + self._redraw_dirty_cells() + time_ds_end = time.time() + self.print_process_info(evolve_duration=(time_ca_end - time_ca_start), + draw_duration=(time_ds_end - time_ca_end), + evolution_step=self._cellular_automaton.evolution_step, + runlendig=len(str(last_evolution_step))) + self._sleep_to_keep_rate(time.time() - time_ca_start, evolutions_per_second) + try: + self.__draw_engine._pygame.quit() + except: + print('Failed to quit pygame') + self._save_state_list() + + def _append_state(self): + automaton_state = [[0 for n in range(self.__dimension)] for m in range(self.__dimension)] + for coord, cell in self._cellular_automaton._current_state.items(): + x,y = coord + automaton_state[y][x] = int(cell.state[0]) + + automaton_state = [''.join(str(cells) for cells in rows) for rows in automaton_state] + automaton_state = '.'.join(str(rows) for rows in automaton_state) + self.__state_list.append(automaton_state) + + def _save_state_list(self): + if not os.path.exists(self.__path): + os.makedirs(self.__path) + + with open(self.__path+f"eps={round(self.__epsilon,3):.3f}_states.txt", 'w', encoding='utf-8') as f: + json.dump(self.__state_list, f, indent=1) + + def _sleep_to_keep_rate(self, time_taken, evolutions_per_second): # pragma: no cover + if evolutions_per_second > 0: + rest_time = 1.0 / evolutions_per_second - time_taken + if rest_time > 0: + time.sleep(rest_time) + + def _not_at_the_end(self, last_evolution_step): + return (self._cellular_automaton.evolution_step < last_evolution_step or last_evolution_step <= 0) + + def _redraw_dirty_cells(self): + self.__draw_engine.update_rectangles(list(self.__redraw_dirty_cells())) + + def __redraw_dirty_cells(self): + for coordinate, cell in self._cellular_automaton.cells.items(): + if cell.is_dirty: + yield self.__redraw_cell(cell, coordinate) + + def __redraw_cell(self, cell, coordinate): + cell_color = self.__state_to_color(cell.state) + cell_pos = self.__calculate_cell_position_in_the_grid(coordinate) + surface_pos = self.__calculate_cell_position_on_screen(cell_pos) + cell.is_dirty = False + return self.__draw_cell_surface(surface_pos, cell_color) + + def _get_cell_color(self, current_state: Sequence) -> Sequence: + """ Returns the color of the cell depending on its current state """ + return 255 if current_state[0] else 0, 0, 0 + + def __calculate_cell_position_in_the_grid(self, coord): + return list(map(operator.mul, + self.__cell_size, + coord[:2])) + + def __calculate_cell_position_on_screen(self, cell_pos): + return [self.__rect.left + cell_pos[0], self.__rect.top + cell_pos[1]] + + def __draw_cell_surface(self, surface_pos, cell_color): + return self.__draw_engine.fill_surface_with_color((surface_pos, self.__cell_size), cell_color) + + def print_process_info(self, evolve_duration, draw_duration, evolution_step, runlendig): + self.__draw_engine.fill_surface_with_color(((0, 0), (self.__rect.width, 30))) + self.__draw_engine.write_text((0, 5), f'Step: {evolution_step:>{runlendig}} FPS: {int(1/(evolve_duration+draw_duration))}') + + def _is_not_user_terminated(self): + return self.__draw_engine.is_active() + class Simulate2Layers: def __init__(self, dim,