neuropercolation/cellular_automaton/cell_state.py
2019-02-24 12:37:26 +01:00

137 lines
5.6 KiB
Python

"""
Copyright 2019 Richard Feistenauer
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from multiprocessing.sharedctypes import RawArray, RawValue
from ctypes import c_float, c_bool
class CellState:
"""
This is the base class for all cell states.
When using the cellular automaton display, inherit this class and implement get_state_draw_color.
"""
_state_save_slot_count = 2
def __init__(self, initial_state=(0., ), draw_first_state=True):
self._state_slots = [list(initial_state) for i in range(self.__class__._state_save_slot_count)]
self._active = [False for i in range(self.__class__._state_save_slot_count)]
self._active[0] = True
self._dirty = draw_first_state
def is_active(self, current_evolution_step):
""" Returns the active status for the requested evolution_step
:param current_evolution_step: The evolution_step of interest.
:return: True if the cell or one of its neighbours changed in the last evolution step.
"""
return self._active[self._calculate_slot(current_evolution_step)]
def set_active_for_next_evolution_step(self, current_evolution_step):
""" Sets the cell active for the next evolution_step, so it will be evolved.
:param current_evolution_step: The current evolution_step index.
"""
self._active[self._calculate_slot(current_evolution_step + 1)] = True
def is_set_for_redraw(self):
""" States if this state should be redrawn.
:return: True if state changed since last call of 'was_redrawn'.
"""
return self._dirty
def was_redrawn(self):
""" Remove the state from redraw cycle until next state change """
self._dirty = False
def get_state_of_last_evolution_step(self, current_evolution_step):
return self.get_state_of_evolution_step(current_evolution_step - 1)
def get_state_of_evolution_step(self, evolution_step):
""" Returns the state of the evolution_step.
:param evolution_step: Uses the evolution_step index, to differ between concurrent states.
:return The state of the requested evolution_step.
"""
return self._state_slots[self._calculate_slot(evolution_step)]
def set_state_of_evolution_step(self, new_state, evolution_step):
""" Sets the new state for the evolution_step.
:param new_state: The new state to set.
:param evolution_step: The evolution_step index, to differ between concurrent states.
:return True if the state really changed.
:raises IndexError: If the state length changed.
"""
changed = self._set_new_state_if_valid(new_state, evolution_step)
self._dirty |= changed
self._active[self._calculate_slot(evolution_step)] = False
return changed
def _set_new_state_if_valid(self, new_state, evolution_step):
current_state = self.get_state_of_evolution_step(evolution_step)
if len(new_state) != len(current_state):
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):
if current_state[i] != ns:
current_state[i] = ns
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
@classmethod
def _calculate_slot(cls, evolution_step):
return evolution_step % cls._state_save_slot_count
class SynchronousCellState(CellState):
""" CellState version using shared values for multi processing purpose. """
def __init__(self, initial_state=(0., ), draw_first_state=True):
super().__init__(initial_state, draw_first_state)
self._state_slots = [RawArray(c_float, initial_state) for i in range(self.__class__._state_save_slot_count)]
self._active = [RawValue(c_bool, False) for i in range(self.__class__._state_save_slot_count)]
self._active[0].value = True
self._dirty = RawValue(c_bool, draw_first_state)
def set_active_for_next_evolution_step(self, current_evolution_step):
self._active[self._calculate_slot(current_evolution_step + 1)].value = True
def is_set_for_redraw(self):
return self._dirty.value
def was_redrawn(self):
self._dirty.value = False
def set_state_of_evolution_step(self, new_state, evolution_step):
changed = self._set_new_state_if_valid(new_state, evolution_step)
self._dirty.value |= changed
self._active[self._calculate_slot(evolution_step)].value = False
return changed