diff --git a/examples/just a test.py b/examples/just a test.py new file mode 100644 index 0000000..cc2d1f7 --- /dev/null +++ b/examples/just a test.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Created on Wed Aug 16 22:17:28 2023 + +@author: astral +""" +import pygame +pygame.init() +win = pygame.display.set_mode((500, 500)) +pygame.display.set_caption("Test") +black = (0, 0, 0) +sysfont = pygame.font.SysFont(None, 50) +win.fill((255, 255, 255)) + +running = True +while running: + win.fill((255, 255, 255)) + digit = sysfont.render("Test", 1, black) + win.blit(digit, (50, 50)) + pygame.display.update() + for event in pygame.event.get(): + if event.type == pygame.QUIT: + running = False + +pygame.quit() \ No newline at end of file diff --git a/neuropercolation/__init__.py b/neuropercolation/__init__.py index 2698fb5..6b02add 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 CellularAutomaton +from .automaton import Neuropercolation, NeuropercolationCoupled from .display import CAWindow diff --git a/neuropercolation/automaton.py b/neuropercolation/automaton.py index 6875424..b04f316 100644 --- a/neuropercolation/automaton.py +++ b/neuropercolation/automaton.py @@ -19,9 +19,12 @@ from typing import Sequence import abc import itertools import recordclass +import random -from cellular_automaton import Neighborhood +from cellular_automaton import Neighborhood, VonNeumannNeighborhood, EdgeRule +ALIVE = [1] +DEAD = [0] CELL = recordclass.make_dataclass("Cell", ("state", "is_active", "is_dirty", "neighbors"), @@ -42,6 +45,7 @@ class CellularAutomatonCreator(abc.ABC): self._current_state = {} self._next_state = {} self.__make_cellular_automaton_state() + self.dumped = False def get_dimension(self): return self._dimension @@ -49,22 +53,23 @@ class CellularAutomatonCreator(abc.ABC): dimension = property(get_dimension) def __make_cellular_automaton_state(self): - self.__make_cells() + self.__make_cells() self.__add_neighbors() def __make_cells(self): - for coord in itertools.product(*[range(d) for d in self._dimension]): + for coord in itertools.product(*[range(d) for d in [*self._dimension]]): # doubled the layers in thrid dimension cell_state = self.init_cell_state(coord) self._current_state[coord] = CELL(cell_state) self._next_state[coord] = CELL(cell_state) - + def __add_neighbors(self): calculate_neighbor_coordinates = self._neighborhood.calculate_cell_neighbor_coordinates coordinates = self._current_state.keys() for coordinate, cell_c, cell_n in zip(coordinates, self._current_state.values(), self._next_state.values()): - n_coord = calculate_neighbor_coordinates(coordinate, self._dimension) - cell_c.neighbors = list([self._current_state[nc] for nc in n_coord]) - cell_n.neighbors = list([self._next_state[nc] for nc in n_coord]) + pre_coord = calculate_neighbor_coordinates(coordinate, [*self._dimension[:2]]) + n_coord = tuple([(*coord,*coordinate[2:]) for coord in pre_coord]) + cell_c.neighbors = list([self._current_state[nc] for nc in n_coord if nc!=coordinate]) + cell_n.neighbors = list([self._next_state[nc] for nc in n_coord if nc!=coordinate]) def init_cell_state(self, cell_coordinate: Sequence) -> Sequence: # pragma: no cover """ Will be called to initialize a cells state. @@ -74,35 +79,17 @@ class CellularAutomatonCreator(abc.ABC): raise NotImplementedError -class CellularAutomaton(CellularAutomatonCreator, abc.ABC): - """ This class represents a cellular automaton. - It can be created with n dimensions and can handle different neighborhood definitions. - - It is intended to be uses as base class. - Override `init_cell_state()` to define the state the cell(s) are initiated with. - Override `evolve()` to define the rule that is aplied on every evolution step of this automaton. - """ - def __init__(self, neighborhood: Neighborhood, *args, **kwargs): - """ Initiates a cellular automaton by the use of the `init_cell_state` method. - :param neighborhood: Defines which cells are considered neighbors. - :param dimension: Iterable of len = dimensions - (e.g. [4, 3, 3, 3] = 4 x 3 x 3 x 3 cells in a four dimensional cube). - """ - super().__init__(neighborhood=neighborhood, *args, **kwargs) +class Neuropercolation(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, 2], + neighborhood=VonNeumannNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)) self._evolution_step = 0 - self._active = True - - def is_active(self): - return self._active - - def reactivate(self): - """ Sets all cells active again """ - for cell in self._current_state.values(): - cell.is_active = True - cell.is_dirty = True - self._active = True - - active = property(is_active) + self.epsilon = eps + + def init_cell_state(self, coord): # pylint: disable=no-self-use + return DEAD def get_cells(self): return self._current_state @@ -126,7 +113,6 @@ class CellularAutomaton(CellularAutomatonCreator, abc.ABC): :param times: The number of evolution steps processed with one call of this method. """ for _ in itertools.repeat(None, times): - self._active = False 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 @@ -134,28 +120,122 @@ class CellularAutomaton(CellularAutomatonCreator, abc.ABC): def __evolve_cells(self, this_state, next_state): evolve_cell = self.__evolve_cell evolution_rule = self.evolve_rule - for old, new in zip(this_state.values(), next_state.values()): - if old.is_active: - new_state = evolution_rule(old.state.copy(), [n.state for n in old.neighbors]) - old.is_active = False - evolve_cell(old, new, new_state) + for coord, old, new in zip(this_state.keys(), this_state.values(), next_state.values()): + coord_c = tuple([*coord[:2],int(1-coord[2])]) + old_c = this_state[coord_c] + new_state = evolution_rule(old.state.copy(), old_c.state.copy(), [n.state for n in old.neighbors], coord[2], coord_c[2]) #inverse the inhibitory layer's action + + 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 - self._active |= changed - if changed: - cell.is_active = True - for n in cell.neighbors: - n.is_active = True + + def evolve_rule(self, last_cell_state, link_last_state, neighbors_last_states, cell_lay, link_cell_lay): + new_cell_state = last_cell_state + if link_cell_lay==0: + alive_neighbours = self.__count_alive_neighbours(neighbors_last_states)+link_last_state[0] # adjust for excitatory link cells + else: + alive_neighbours = self.__count_alive_neighbours(neighbors_last_states)+(1-link_last_state[0]) # adjust for inhibitory link cells + + 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 + + @staticmethod + def __count_alive_neighbours(neighbours): + alive_neighbors = [] + for n in neighbours: + if n == ALIVE: + alive_neighbors.append(1) + return len(alive_neighbors) + +class NeuropercolationCoupled(CellularAutomatonCreator, abc.ABC): + + def __init__(self, dim, eps, coupling=[], *args, **kwargs): + super().__init__(dimension=[dim, dim, 2, 2], + neighborhood=VonNeumannNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)) + self._evolution_step = 0 + self.epsilon = eps + self.coupling = coupling + + def init_cell_state(self, coord): # pylint: disable=no-self-use + return DEAD + + def get_cells(self): + return self._current_state - def evolve_rule(self, last_cell_state: Sequence, neighbors_last_states: Sequence) -> Sequence: # pragma: no cover - """ Calculates and sets new state of 'cell'. - 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 + 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. """ - raise NotImplementedError + 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): #this will be overwritten by heir + 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()): + if coord[:2] in self.coupling: + coord_c = tuple([*coord[:2],coord[2],int(1-coord[3])]) + old_c = this_state[coord_c] + new_state = evolution_rule(old.state.copy(), old_c.state.copy(), [n.state for n in old.neighbors], 0) + else: + coord_c = tuple([*coord[:2],int(1-coord[2]),coord[3]]) + old_c = this_state[coord_c] + new_state = evolution_rule(old.state.copy(), old_c.state.copy(), [n.state for n in old.neighbors], coord_c[2]) #inverse the inhibitory layer's action + + 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, link_last_state, neighbors_last_states, other_layer): + new_cell_state = last_cell_state + if other_layer==0: + alive_neighbours = self.__count_alive_neighbours(neighbors_last_states)+link_last_state[0] # adjust for excitatory link cells + else: + alive_neighbours = self.__count_alive_neighbours(neighbors_last_states)+(1-link_last_state[0]) # adjust for inhibitory link cells + + 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 \ No newline at end of file diff --git a/neuropercolation/display.py b/neuropercolation/display.py index 79c9741..79a403d 100644 --- a/neuropercolation/display.py +++ b/neuropercolation/display.py @@ -22,7 +22,7 @@ import collections import contextlib from typing import Sequence -from . import CellularAutomaton +from . import Neuropercolation, NeuropercolationCoupled _Rect = collections.namedtuple(typename="Rect", field_names=["left", "top", "width", "height"]) @@ -65,7 +65,7 @@ class PygameEngine: class CAWindow: def __init__(self, - cellular_automaton: CellularAutomaton, + cellular_automaton: Neuropercolation, window_size=(1000, 800), stretch_cells=False, draw_engine=None, @@ -117,8 +117,7 @@ class CAWindow: 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) \ - and self._cellular_automaton.active + return (self._cellular_automaton.evolution_step < last_evolution_step or last_evolution_step <= 0) def __calculate_cell_display_size(self, stretch_cells): # pragma: no cover grid_dimension = self._cellular_automaton.dimension