neuropercolation/cellular_automaton/neighborhood.py

238 lines
9.5 KiB
Python
Raw Normal View History

2019-02-23 15:20:48 +00:00
"""
Copyright 2019 Richard Feistenauer
2018-12-01 19:37:18 +00:00
2019-02-23 15:20:48 +00:00
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
2018-12-01 19:37:18 +00:00
2019-02-23 15:20:48 +00:00
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.
"""
import enum
import operator
import itertools
2019-02-24 15:37:00 +00:00
import math
2019-02-23 15:20:48 +00:00
class EdgeRule(enum.Enum):
""" Enum for different possibilities to handle the edge of the automaton. """
2019-02-16 17:05:26 +00:00
IGNORE_EDGE_CELLS = 0
IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS = 1
2018-12-01 19:37:18 +00:00
FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS = 2
2018-12-02 16:45:08 +00:00
class Neighborhood:
""" Defines which cells should be considered to be neighbors during evolution of cellular automaton."""
def __init__(self, edge_rule=EdgeRule.IGNORE_EDGE_CELLS, radius=1):
""" General class for all Neighborhoods.
:param edge_rule: Rule to define, how cells on the edge of the grid will be handled.
:param radius: If radius > 1 it grows the neighborhood
by adding the neighbors of the neighbors radius times.
2018-12-08 21:33:13 +00:00
"""
self._rel_neighbors = None
self._grid_dimensions = []
self._radius = radius
2019-02-16 17:05:26 +00:00
self.__edge_rule = edge_rule
2018-12-01 19:37:18 +00:00
2018-12-08 21:33:13 +00:00
def calculate_cell_neighbor_coordinates(self, cell_coordinate, grid_dimensions):
2019-02-16 17:05:26 +00:00
""" Get a list of absolute coordinates for the cell neighbors.
The EdgeRule can reduce the returned neighbor count.
:param cell_coordinate: The coordinate of the cell.
:param grid_dimensions: The dimensions of the grid, to apply the edge the rule.
:return: list of absolute coordinates for the cells neighbors.
2018-12-08 21:33:13 +00:00
"""
self.__lazy_initialize_relative_neighborhood(grid_dimensions)
return tuple(self._neighbors_generator(cell_coordinate))
2018-12-01 19:37:18 +00:00
def __lazy_initialize_relative_neighborhood(self, grid_dimensions):
self._grid_dimensions = grid_dimensions
if self._rel_neighbors is None:
self._create_relative_neighborhood()
2019-02-23 15:20:48 +00:00
def _create_relative_neighborhood(self):
self._rel_neighbors = tuple(self._neighborhood_generator())
2018-12-01 19:37:18 +00:00
def _neighborhood_generator(self):
for coordinate in itertools.product(range(-self._radius, self._radius + 1), repeat=len(self._grid_dimensions)):
if self._neighbor_rule(coordinate) and coordinate != (0, ) * len(self._grid_dimensions):
yield tuple(reversed(coordinate))
2018-12-01 19:37:18 +00:00
def _neighbor_rule(self, rel_neighbor): # pylint: disable=no-self-use, unused-argument
return True
2018-12-01 19:37:18 +00:00
def get_neighbor_by_relative_coordinate(self, neighbors, rel_coordinate):
return neighbors[self._rel_neighbors.index(rel_coordinate)]
2018-12-01 19:37:18 +00:00
def _neighbors_generator(self, cell_coordinate):
on_edge = self.__is_coordinate_on_an_edge(cell_coordinate)
if self.__edge_rule != EdgeRule.IGNORE_EDGE_CELLS or not on_edge: # pylint: disable=too-many-nested-blocks
for rel_n in self._rel_neighbors:
if on_edge:
n, n_folded = zip(*[(ni + ci, (ni + di + ci) % di)
for ci, ni, di in zip(cell_coordinate, rel_n, self._grid_dimensions)])
if self.__edge_rule == EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS or n == n_folded:
yield n_folded
else:
yield tuple(map(operator.add, rel_n, cell_coordinate))
def __is_coordinate_on_an_edge(self, coordinate):
2021-01-09 12:51:20 +00:00
return any(not(self._radius-1 < ci < di-self._radius) for ci, di in zip(coordinate, self._grid_dimensions))
2018-12-08 21:33:13 +00:00
2018-12-01 19:37:18 +00:00
2018-12-02 16:45:08 +00:00
class MooreNeighborhood(Neighborhood):
2019-02-24 15:37:00 +00:00
""" Moore defined a neighborhood with a radius applied on a the non euclidean distance to other cells in the grid.
2019-02-24 11:37:26 +00:00
Example:
2019-02-24 15:37:00 +00:00
2 dimensions
2019-02-24 11:37:26 +00:00
C = cell of interest
2019-02-24 15:37:00 +00:00
N = neighbor of cell
X = no neighbor of cell
2019-02-24 11:37:26 +00:00
Radius 1 Radius 2
X X X X X N N N N N
X N N N X N N N N N
X N C N X N N C N N
X N N N X N N N N N
X X X X X N N N N N
"""
2019-02-16 17:05:26 +00:00
class VonNeumannNeighborhood(Neighborhood):
2019-02-24 15:37:00 +00:00
""" Von Neumann defined a neighborhood with a radius applied to Manhatten distance
2019-02-24 11:37:26 +00:00
(steps between cells without diagonal movement).
Example:
2019-02-24 15:37:00 +00:00
2 dimensions
2019-02-24 11:37:26 +00:00
C = cell of interest
2019-02-24 15:37:00 +00:00
N = neighbor of cell
X = no neighbor of cell
2019-02-24 11:37:26 +00:00
Radius 1 Radius 2
X X X X X X X N X X
X X N X X X N N N X
X N C N X N N C N N
X X N X X X N N N X
X X X X X X X N X X
"""
def _neighbor_rule(self, rel_neighbor):
2019-02-16 17:05:26 +00:00
cross_sum = 0
for coordinate_i in rel_neighbor:
cross_sum += abs(coordinate_i)
return cross_sum <= self._radius
2019-02-24 15:37:00 +00:00
class RadialNeighborhood(Neighborhood):
""" Neighborhood with a radius applied to euclidean distance + delta
Example:
2 dimensions
C = cell of interest
N = neighbor of cell
X = no neighbor of cell
Radius 2 Radius 3
X X X X X X X X X N N N X X
X X N N N X X X N N N N N X
X N N N N N X N N N N N N N
X N N C N N X N N N C N N N
X N N N N N X N N N N N N N
X X N N N X X X N N N N N X
X X X X X X X X X N N N X X
"""
def __init__(self, *args, delta_=.25, **kwargs):
2019-02-24 15:37:00 +00:00
self.delta = delta_
super().__init__(*args, **kwargs)
2019-02-24 15:37:00 +00:00
def _neighbor_rule(self, rel_neighbor):
2019-02-24 15:37:00 +00:00
cross_sum = 0
for coordinate_i in rel_neighbor:
cross_sum += pow(coordinate_i, 2)
return math.sqrt(cross_sum) <= self._radius + self.delta
2019-02-24 15:37:00 +00:00
class HexagonalNeighborhood(Neighborhood):
""" Defines a Hexagonal neighborhood in a rectangular two dimensional grid:
Example:
Von Nexagonal neighborhood in 2 dimensions with radius 1 and 2
C = cell of interest
N = neighbor of cell
X = no neighbor of cell
Radius 1 Radius 2
X X X X X X N N N X
X N N X N N N N
X N C N X N N C N N
X N N X N N N N
X X X X X X N N N X
Rectangular representation: Radius 1
Row % 2 == 0 Row % 2 == 1
N N X X N N
N C N N C N
N N X X N N
Rectangular representation: Radius 2
Row % 2 == 0 Row % 2 == 1
X N N N X X N N N X
N N N N X X N N N N
N N C N N N N C N N
N N N N X X N N N N
X N N N X X N N N X
"""
def __init__(self, *args, radius=1, **kwargs):
super().__init__(radius=radius, *args, **kwargs)
self.__calculate_hexagonal_neighborhood(radius)
def __calculate_hexagonal_neighborhood(self, radius):
neighbor_lists = [[(0, 0)], [(0, 0)]]
for radius_i in range(1, radius + 1):
for i, neighbor in enumerate(neighbor_lists):
neighbor = _grow_neighbours(neighbor)
neighbor = self.__add_rectangular_neighbours(neighbor, radius_i, i % 2 == 1)
neighbor = sorted(neighbor, key=(lambda ne: [ne[1], ne[0]]))
neighbor.remove((0, 0))
neighbor_lists[i] = neighbor
self._neighbor_lists = neighbor_lists
def get_neighbor_by_relative_coordinate(self, neighbors, rel_coordinate): # pragma: no cover
2019-02-24 15:37:00 +00:00
raise NotImplementedError
def calculate_cell_neighbor_coordinates(self, cell_coordinate, grid_dimensions):
self._rel_neighbors = self._neighbor_lists[cell_coordinate[1] % 2]
return super().calculate_cell_neighbor_coordinates(cell_coordinate, grid_dimensions)
2019-02-24 15:37:00 +00:00
@staticmethod
def __add_rectangular_neighbours(neighbours, radius, is_odd):
new_neighbours = []
for x in range(0, radius + 1):
if is_odd:
x -= int(radius/2)
else:
x -= int((radius + 1) / 2)
for y in range(-radius, radius + 1):
new_neighbours.append((x, y))
new_neighbours.extend(neighbours)
return list(set(new_neighbours))
2019-02-16 17:05:26 +00:00
2019-02-24 15:37:00 +00:00
def _grow_neighbours(neighbours):
new_neighbours = neighbours[:]
for n in neighbours:
new_neighbours.append((n[0], n[1] - 1))
new_neighbours.append((n[0] - 1, n[1]))
new_neighbours.append((n[0] + 1, n[1]))
new_neighbours.append((n[0], n[1] + 1))
return list(set(new_neighbours))