Nested files together slightly
parent
929454ec41
commit
e931af0020
|
@ -23,20 +23,117 @@ with the ALWAYS_PASS flag set in the given ruleset the configuration is bundled
|
||||||
"""
|
"""
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
|
|
||||||
|
class Neighborhood:
|
||||||
|
"""
|
||||||
|
Specifies the cells that should be considered when referencing a particular cell.
|
||||||
|
|
||||||
|
The neighborhood is a wrapper class that stores information regarding a particular cell.
|
||||||
|
Offsets must be added separate from instantiation, since it isn't always necessary to
|
||||||
|
perform this computation in the first place (for example, if an ALWAYS_PASS flag is passed
|
||||||
|
as opposed to a MATCH flag).
|
||||||
|
|
||||||
|
It may be helpful to consider a configuration as a template of a neighborhood, and a neighborhood
|
||||||
|
as an instantiation of a configuration (one with concrete values as opposed to templated ones).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, flat_index, bit_index, total):
|
||||||
|
"""
|
||||||
|
Initializes the center cell.
|
||||||
|
|
||||||
|
Offsetted cells belonging in the given neighborhood must be added separately.
|
||||||
|
"""
|
||||||
|
self.total = total
|
||||||
|
self.bit_index = bit_index
|
||||||
|
self.flat_index = flat_index
|
||||||
|
|
||||||
|
self.states = np.array([])
|
||||||
|
self.bit_indices = np.array([])
|
||||||
|
self.flat_indices = np.array([])
|
||||||
|
|
||||||
|
|
||||||
|
def process_offsets(self, plane, offsets):
|
||||||
|
"""
|
||||||
|
Given the plane and offsets, determines the cells in the given neighborhood.
|
||||||
|
|
||||||
|
This is rather expensive to call on every cell in a grid, so should be used with caution.
|
||||||
|
Namely, this is useful when we need to determine matches within a threshold, since total cells
|
||||||
|
of a neighborhood are precomputed in the ruleset.
|
||||||
|
|
||||||
|
For example, if we need an exact match of a configuration, we have to first process all the
|
||||||
|
offsets of a neighborhood to determine that it indeed matches the configuration (if this was
|
||||||
|
not called, self.offsets would remain empty).
|
||||||
|
"""
|
||||||
|
flat_indices, bit_indices, _ = zip(*offsets)
|
||||||
|
|
||||||
|
states = []
|
||||||
|
for i in range(len(flat_indices)):
|
||||||
|
bit_index = bit_indices[i]
|
||||||
|
flat_index = flat_indices[i]
|
||||||
|
states.append(plane.grid.flat[flat_index][bit_index])
|
||||||
|
|
||||||
|
self.states = np.array(states)
|
||||||
|
self.bit_indices = np.array(bit_indices)
|
||||||
|
self.flat_indices = np.array(flat_indices)
|
||||||
|
|
||||||
|
|
||||||
class Configuration:
|
class Configuration:
|
||||||
"""
|
"""
|
||||||
Represents an expected neighborhood; to be compared to an actual neighborhood in a CAM.
|
Represents an expected neighborhood; to be compared to an actual neighborhood in a CAM.
|
||||||
|
|
||||||
A configuration allows exact specification of a neighborhood, not the actual state of a neighborhood.
|
A configuration allows specification of a neighborhood, not the actual state of a neighborhood.
|
||||||
It is merely used for reference by a ruleset, which takes in a series of configurations and
|
It is merely used for reference by a ruleset, which takes in a series of configurations and
|
||||||
the next state of a cell depending on a configuration.
|
returns the state referenced by the first configuration that passes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# An offset contains the flat_offset, which refers to the bitarray of the plane.grid.flat that
|
# An offset contains the flat_offset, which refers to the bitarray of the plane.grid.flat that
|
||||||
# a given offset is pointing to. The bit_offset refers to the index of the bitarray at the
|
# a given offset is pointing to. The bit_offset refers to the index of the bitarray at the
|
||||||
# given flat_offset. State describes the expected state at the given (flat_offset, bit_offset).
|
# given flat_offset. State describes the expected state at the given (flat_offset, bit_offset).
|
||||||
|
|
||||||
Offset = namedtuple('Offset', ['flat_offset', 'bit_offset', 'state'])
|
Offset = namedtuple('Offset', ['flat_offset', 'bit_offset', 'state'])
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def moore(plane, value=1):
|
||||||
|
"""
|
||||||
|
Returns a neighborhood corresponding to the Moore neighborhood.
|
||||||
|
|
||||||
|
The Moore neighborhood consists of all adjacent cells. In 2D, these correspond to the 8 touching cells
|
||||||
|
N, NE, E, SE, S, SW, S, and NW. In 3D, this corresponds to all cells in the "backward" and "forward"
|
||||||
|
layer that adjoin the nine cells in the "center" layer. This concept can be extended to N dimensions.
|
||||||
|
Note the center cell is excluded, so the total number of offsets are 3^N - 1.
|
||||||
|
"""
|
||||||
|
offsets = {}
|
||||||
|
variants = ([-1, 0, 1],) * len(grid.shape)
|
||||||
|
for current in it.product(*variants):
|
||||||
|
if any(current):
|
||||||
|
offsets[current] = value
|
||||||
|
|
||||||
|
return offsets
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def neumann(plane, value=1):
|
||||||
|
"""
|
||||||
|
Returns a neighborhood corresponding to the Von Neumann neighborhood.
|
||||||
|
|
||||||
|
The Von Neumann neighborhood consists of adjacent cells that directly share a face with the current cell.
|
||||||
|
In 2D, these correspond to the 4 touching cells N, S, E, W. In 3D, we include the "backward" and "forward"
|
||||||
|
cell. This concept can be extended to N dimensions.
|
||||||
|
|
||||||
|
Note the center cell is excluded, so the total number of offsets are 2N.
|
||||||
|
"""
|
||||||
|
offsets = []
|
||||||
|
variant = [0] * len(grid.shape)
|
||||||
|
for i in range(len(variant)):
|
||||||
|
for j in [-1, 1]:
|
||||||
|
variant[i] = j
|
||||||
|
offsets[tuple(variant)] = value
|
||||||
|
variant[i] = 0
|
||||||
|
|
||||||
|
return offsets
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, next_state, **kwargs):
|
def __init__(self, next_state, **kwargs):
|
||||||
"""
|
"""
|
||||||
@next_state: Represents the next state of a cell given a configuration passes.
|
@next_state: Represents the next state of a cell given a configuration passes.
|
||||||
|
@ -44,14 +141,15 @@ class Configuration:
|
||||||
@kwargs : If supplied, should be a dictionary containing an 'offsets' key, corresponding
|
@kwargs : If supplied, should be a dictionary containing an 'offsets' key, corresponding
|
||||||
to a dictionary of offsets (they should be coordinates in N-dimensional space
|
to a dictionary of offsets (they should be coordinates in N-dimensional space
|
||||||
referring to the offsets checked in a given neighborhood) with an expected
|
referring to the offsets checked in a given neighborhood) with an expected
|
||||||
state value and a 'shape' key, corresponding to the shape of the grid in question.
|
state value and a 'plane' key, corresponding to the plane in question.
|
||||||
"""
|
"""
|
||||||
self.offsets = []
|
self.offsets = []
|
||||||
self.next_state = next_state
|
self.next_state = next_state
|
||||||
if 'shape' in kwargs and 'offsets' in kwargs:
|
if 'plane' in kwargs and 'offsets' in kwargs:
|
||||||
self.extend_offsets(kwargs['shape'], kwargs['offsets'])
|
self.extend_offsets(kwargs['plane'], kwargs['offsets'])
|
||||||
|
|
||||||
def extend_offsets(shape, offsets):
|
|
||||||
|
def extend_offsets(self, plane, offsets):
|
||||||
"""
|
"""
|
||||||
Allow for customizing of configuration.
|
Allow for customizing of configuration.
|
||||||
|
|
||||||
|
@ -60,32 +158,63 @@ class Configuration:
|
||||||
of the value at the first coordinate.
|
of the value at the first coordinate.
|
||||||
"""
|
"""
|
||||||
for coor, bit in offsets.items():
|
for coor, bit in offsets.items():
|
||||||
flat_index, gridprod = 0, 1
|
flat_index, bit_index = plane.flatten(coor)
|
||||||
for i in reversed(range(len(coor[:-1]))):
|
self.offsets.append(Offset(flat_index, bit_index, bit))
|
||||||
flat_index += coor[i] * gridprod
|
|
||||||
gridprod *= shape[i]
|
|
||||||
self.offsets.append(Offset(flat_index, coor[-1], bit))
|
|
||||||
|
|
||||||
def passes(self, f_index, grid, vfunc, *args):
|
|
||||||
|
def passes(self, plane, neighborhood, vfunc, *args):
|
||||||
"""
|
"""
|
||||||
Checks if a given configuration passes, and if so, returns the next state.
|
Determines whether a passed neighborhood satisfies the given configuration.
|
||||||
|
|
||||||
@vfunc is an arbitrary function that takes in a flattened grid, a list of indices, and a list of values (which,
|
The configuration is considered passing or failing based on the provided vfunc; if successful,
|
||||||
if zipped with indices, correspond to the expected value of the cell at the given index). The function should
|
the bit centered in the neighborhood should be set to the next state as determined by the
|
||||||
merely verify that a list of indices "passes" some expectation.
|
configuration.
|
||||||
|
|
||||||
For example, if an "exact match" function is passed, it should merely verify that the cells at the passed indices
|
Note the distinction between success and next state
|
||||||
exactly match the exact expectated cells in the list of values. It will return True or False depending.
|
vfunc denotes that the configuration passes; that is, a configuration determines the next
|
||||||
|
state of a cell but should only be heeded if the configuration passes. The next state, which
|
||||||
|
is either a 0, 1, or a function that returns a 0 or 1 is the actual new value of the cell.
|
||||||
"""
|
"""
|
||||||
# We ensure all indices are within the given grid
|
if not vfunc(plane, neighborhood, *args):
|
||||||
indices = (f_index + self.offsets) % grid.size
|
return (False, None)
|
||||||
|
elif callable(self.next_state):
|
||||||
# Note the distinction between success and next_state here; vfunc (validity function) tells whether the given
|
return (True, self.next_state(plane, neighborhood, *args))
|
||||||
# configuration passes. If it does, no other configurations need to be checked and the next state is returned.
|
|
||||||
success = vfunc(f_index, grid.flat, indices, self.states, *args)
|
|
||||||
if callable(self.next_state):
|
|
||||||
return (success, self.next_state(f_index, grid.flat, indices, self.states, *args))
|
|
||||||
else:
|
else:
|
||||||
return (success, self.next_state)
|
return (True, self.next_state)
|
||||||
|
|
||||||
|
|
||||||
|
def matches(self, plane, neighborhood):
|
||||||
|
"""
|
||||||
|
Determines that neighborhood matches expectation exactly.
|
||||||
|
|
||||||
|
Note this behaves like the _tolerates method with a tolerance of 1.
|
||||||
|
"""
|
||||||
|
neighborhood.process_offsets(plane, self.offsets)
|
||||||
|
bits = np.array([offset[2] for offset in self.offsets])
|
||||||
|
|
||||||
|
return not np.count_nonzero(bits ^ neighborhood.states)
|
||||||
|
|
||||||
|
|
||||||
|
def tolerates(self, plane, neighborhood, tolerance):
|
||||||
|
"""
|
||||||
|
Determines that neighborhood matches expectation within tolerance.
|
||||||
|
|
||||||
|
We see that the percentage of actual matches are greater than or equal to the given tolerance level. If so, we
|
||||||
|
consider this cell to be alive. Note tolerance must be a value 0 <= t <= 1.
|
||||||
|
"""
|
||||||
|
neighborhood.process_offsets(plane, self.offsets)
|
||||||
|
bits = np.array([offset[2] for offset in self.offsets])
|
||||||
|
non_matches = np.count_nonzero(bits ^ neighborhood.states)
|
||||||
|
|
||||||
|
return (non_matches / len(bits)) >= tolerance
|
||||||
|
|
||||||
|
|
||||||
|
def satisfies(self, plane, neighborhood, valid_func, *args):
|
||||||
|
"""
|
||||||
|
Allows custom function to relay next state of given cell.
|
||||||
|
|
||||||
|
The passed function is passed the given plane and a neighborhood corresponding to the cell
|
||||||
|
being processed at the moment.
|
||||||
|
"""
|
||||||
|
return valid_func(plane, neighborhood, *args)
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@author: jrpotter
|
|
||||||
@date: June 4th, 2015
|
|
||||||
"""
|
|
||||||
|
|
||||||
class InvalidFormat(Exception):
|
|
||||||
"""
|
|
||||||
Called when parsing an invalid format.
|
|
||||||
|
|
||||||
For example, in MCell and RLE, numbers should be in ascending order.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, value):
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
return repr(self.value)
|
|
|
@ -1,58 +0,0 @@
|
||||||
"""
|
|
||||||
|
|
||||||
@author: jrpotter
|
|
||||||
@date: June 5th, 2015
|
|
||||||
"""
|
|
||||||
|
|
||||||
class Neighborhood:
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
def __init__(self, f_index, b_offset, states, indices):
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
self.index = -1
|
|
||||||
self.total = -1
|
|
||||||
self.states = np.array([])
|
|
||||||
self.indices = np.array([])
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def moore(cls, grid, value=1):
|
|
||||||
"""
|
|
||||||
Returns a neighborhood corresponding to the Moore neighborhood.
|
|
||||||
|
|
||||||
The Moore neighborhood consists of all adjacent cells. In 2D, these correspond to the 8 touching cells
|
|
||||||
N, NE, E, SE, S, SW, S, and NW. In 3D, this corresponds to all cells in the "backward" and "forward"
|
|
||||||
layer that adjoin the nine cells in the "center" layer. This concept can be extended to N dimensions.
|
|
||||||
|
|
||||||
Note the center cell is excluded, so the total number of offsets are 3^N - 1.
|
|
||||||
"""
|
|
||||||
offsets = {}
|
|
||||||
variants = ([-1, 0, 1],) * len(grid.shape)
|
|
||||||
for current in it.product(*variants):
|
|
||||||
if any(current):
|
|
||||||
offsets[current] = value
|
|
||||||
|
|
||||||
return offsets
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def neumann(cls, grid, value=1):
|
|
||||||
"""
|
|
||||||
Returns a neighborhood corresponding to the Von Neumann neighborhood.
|
|
||||||
|
|
||||||
The Von Neumann neighborhood consists of adjacent cells that directly share a face with the current cell.
|
|
||||||
In 2D, these correspond to the 4 touching cells N, S, E, W. In 3D, we include the "backward" and "forward"
|
|
||||||
cell. This concept can be extended to N dimensions.
|
|
||||||
|
|
||||||
Note the center cell is excluded, so the total number of offsets are 2N.
|
|
||||||
"""
|
|
||||||
offsets = {}
|
|
||||||
variant = [0] * len(grid.shape)
|
|
||||||
for i in range(len(variant)):
|
|
||||||
for j in [-1, 1]:
|
|
||||||
variant[i] = j
|
|
||||||
offsets[tuple(variant)] = value
|
|
||||||
variant[i] = 0
|
|
||||||
|
|
||||||
return offsets
|
|
|
@ -1,13 +1,32 @@
|
||||||
"""
|
"""
|
||||||
A collection of utilities that can ease construction of CAMs.
|
Parsers CAM languages for quick construction of CAMs.
|
||||||
|
|
||||||
|
For example, when considering CAMs of life, most follow the same formula; check
|
||||||
|
the total number of cells in a given neighborhood and if there are a certain
|
||||||
|
number around an off cell, turn it on, and vice versa. Thus the parser takes
|
||||||
|
in a generic language regarding this and constructs the necessary functions for
|
||||||
|
the user.
|
||||||
|
|
||||||
@author: jrpotter
|
|
||||||
@date: June 4th, 2015
|
@date: June 4th, 2015
|
||||||
"""
|
"""
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import ruleset as rs
|
import ruleset as r
|
||||||
import exceptions as ce
|
import configuration as c
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidFormat(Exception):
|
||||||
|
"""
|
||||||
|
Called when parsing an invalid format.
|
||||||
|
|
||||||
|
For example, in MCell and RLE, numbers should be in ascending order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return repr(self.value)
|
||||||
|
|
||||||
|
|
||||||
class CAMParser:
|
class CAMParser:
|
||||||
|
@ -33,28 +52,30 @@ class CAMParser:
|
||||||
@offsets: Represents the Moore neighborhood corresponding to the given CAM
|
@offsets: Represents the Moore neighborhood corresponding to the given CAM
|
||||||
"""
|
"""
|
||||||
self.sfunc = None
|
self.sfunc = None
|
||||||
self.offsets = rs.Configuration.moore(cam.master)
|
self.offsets = c.Configuration.moore(cam.master)
|
||||||
self.ruleset = rs.Ruleset(rs.Ruleset.Method.ALWAYS_PASS)
|
self.ruleset = r.Ruleset(rsRuleset.Method.ALWAYS_PASS)
|
||||||
|
|
||||||
if re.match(CAMParser.MCELL_FORMAT, notation):
|
if re.match(CAMParser.MCELL_FORMAT, notation):
|
||||||
x, y = notation.split('/')
|
x, y = notation.split('/')
|
||||||
if all(map(self._numasc, [x, y])):
|
if all(map(self._numasc, [x, y])):
|
||||||
self.sfunc = self._mcell(x, y)
|
self.sfunc = self._mcell(x, y)
|
||||||
else:
|
else:
|
||||||
raise ce.InvalidFormat("Non-ascending values in MCELL format")
|
raise InvalidFormat("Non-ascending values in MCELL format")
|
||||||
|
|
||||||
elif re.match(CAMParser.RLE_FORMAT, notation):
|
elif re.match(CAMParser.RLE_FORMAT, notation):
|
||||||
B, S = map(lambda x: x[1:], notation.split('/'))
|
B, S = map(lambda x: x[1:], notation.split('/'))
|
||||||
if all(map(self._numasc, [B, S])):
|
if all(map(self._numasc, [B, S])):
|
||||||
self.sfunc = self._mcell(S, B)
|
self.sfunc = self._mcell(S, B)
|
||||||
else:
|
else:
|
||||||
raise ce.InvalidFormat("Non-ascending values in RLE format")
|
raise InvalidFormat("Non-ascending values in RLE format")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ce.InvalidFormat("No supported format passed to parser.")
|
raise InvalidFormat("No supported format passed to parser.")
|
||||||
|
|
||||||
# Add configuration to given CAM
|
# Add configuration to given CAM
|
||||||
self.ruleset.addConfiguration(cam.master, self.sfunc, self.offsets)
|
config = c.Configuration(self.sfunc, plane=cam.master, offsets=self.offsets)
|
||||||
|
self.ruleset.configurations.append(config)
|
||||||
|
|
||||||
|
|
||||||
def _numasc(self, value):
|
def _numasc(self, value):
|
||||||
"""
|
"""
|
||||||
|
@ -65,6 +86,7 @@ class CAMParser:
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _mcell(self, x, y):
|
def _mcell(self, x, y):
|
||||||
"""
|
"""
|
||||||
MCell Notation
|
MCell Notation
|
||||||
|
|
16
src/plane.py
16
src/plane.py
|
@ -46,6 +46,7 @@ class Plane:
|
||||||
for i in range(self.grid.size):
|
for i in range(self.grid.size):
|
||||||
self.grid.flat[i] = bitarray(self.N)
|
self.grid.flat[i] = bitarray(self.N)
|
||||||
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
"""
|
"""
|
||||||
Indices supported are the same as those of the numpy array, except for when accessing an individual bit.
|
Indices supported are the same as those of the numpy array, except for when accessing an individual bit.
|
||||||
|
@ -89,6 +90,7 @@ class Plane:
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return Plane((self.N,), tmp)
|
return Plane((self.N,), tmp)
|
||||||
|
|
||||||
|
|
||||||
def randomize(self):
|
def randomize(self):
|
||||||
"""
|
"""
|
||||||
Sets values of grid to random values.
|
Sets values of grid to random values.
|
||||||
|
@ -105,3 +107,17 @@ class Plane:
|
||||||
tmp = np.array([r.randrange(0, max_u) for i in range(len(self.grid))])
|
tmp = np.array([r.randrange(0, max_u) for i in range(len(self.grid))])
|
||||||
self.grid = tmp.reshape(self.grid.shape)
|
self.grid = tmp.reshape(self.grid.shape)
|
||||||
|
|
||||||
|
|
||||||
|
def flatten(self, coordinate):
|
||||||
|
"""
|
||||||
|
Converts a coordinate (which could be used to access a bit in a plane) and "flattens" it.
|
||||||
|
|
||||||
|
By this we mean we convert the coordinate into an index and bit offset corresponding to
|
||||||
|
the plane grid converted to 1D (think numpy.ndarray.flat).
|
||||||
|
"""
|
||||||
|
flat_index, gridprod = 0, 1
|
||||||
|
for i in reversed(range(len(coordinate[:-1]))):
|
||||||
|
flat_index += coordinate[i] * gridprod
|
||||||
|
gridprod *= shape[i]
|
||||||
|
|
||||||
|
return flat_index, coordinate[-1]
|
||||||
|
|
|
@ -65,16 +65,6 @@ class Ruleset:
|
||||||
"""
|
"""
|
||||||
next_grid = []
|
next_grid = []
|
||||||
|
|
||||||
# Determine which function should be used to test success
|
|
||||||
if self.method == Ruleset.Method.MATCH:
|
|
||||||
vfunc = self._matches
|
|
||||||
elif self.method == Ruleset.Method.TOLERATE:
|
|
||||||
vfunc = self._tolerates
|
|
||||||
elif self.method == Ruleset.Method.SATISFY:
|
|
||||||
vfunc = self._satisfies
|
|
||||||
elif self.method == Ruleset.Method.ALWAYS_PASS:
|
|
||||||
vfunc = lambda *args: True
|
|
||||||
|
|
||||||
# We apply our method a row at a time, to take advantage of being able to sum the totals
|
# We apply our method a row at a time, to take advantage of being able to sum the totals
|
||||||
# of a neighborhood in a batch manner. We try to apply a configuration to every bit of a
|
# of a neighborhood in a batch manner. We try to apply a configuration to every bit of a
|
||||||
# row, mark those that fail, and try the next configuration on the failed bits until
|
# row, mark those that fail, and try the next configuration on the failed bits until
|
||||||
|
@ -97,8 +87,9 @@ class Ruleset:
|
||||||
# sum of the ith index of the summation of every 9 chunks of numbers (this is done a row at a time).
|
# sum of the ith index of the summation of every 9 chunks of numbers (this is done a row at a time).
|
||||||
neighboring = []
|
neighboring = []
|
||||||
for flat_offset, bit_offset in config.offsets:
|
for flat_offset, bit_offset in config.offsets:
|
||||||
neighbor = str(plane.grid.flat[flat_index + flat_offset])
|
neighbor = plane.grid.flat[flat_index + flat_offset]
|
||||||
neighboring.append(int(neighbor[bit_offset+1:] + neighbor[:bit_offset]))
|
cycled = neighbor[bit_offset:] + neighbor[:bit_offset]
|
||||||
|
neighboring.append(int(cycled.to01()))
|
||||||
|
|
||||||
# Chunk into groups of 9 and sum all values
|
# Chunk into groups of 9 and sum all values
|
||||||
# These summations represent the total number of active states in a given neighborhood
|
# These summations represent the total number of active states in a given neighborhood
|
||||||
|
@ -107,6 +98,16 @@ class Ruleset:
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
totals = list(map(sum, zip(totals, chunk)))
|
totals = list(map(sum, zip(totals, chunk)))
|
||||||
|
|
||||||
|
# Determine which function should be used to test success
|
||||||
|
if self.method == Ruleset.Method.MATCH:
|
||||||
|
vfunc = config.matches
|
||||||
|
elif self.method == Ruleset.Method.TOLERATE:
|
||||||
|
vfunc = config.tolerates
|
||||||
|
elif self.method == Ruleset.Method.SATISFY:
|
||||||
|
vfunc = config.satisfies
|
||||||
|
elif self.method == Ruleset.Method.ALWAYS_PASS:
|
||||||
|
vfunc = lambda *args: True
|
||||||
|
|
||||||
# Apply change to all successful configurations
|
# Apply change to all successful configurations
|
||||||
for bit_index in to_update:
|
for bit_index in to_update:
|
||||||
neighborhood = Neighborhood(flat_index, bit_index, totals[bit_index])
|
neighborhood = Neighborhood(flat_index, bit_index, totals[bit_index])
|
||||||
|
@ -126,30 +127,4 @@ class Ruleset:
|
||||||
for i in range(plane.grid.size):
|
for i in range(plane.grid.size):
|
||||||
plane.grid.flat[i] = next_grid[i]
|
plane.grid.flat[i] = next_grid[i]
|
||||||
|
|
||||||
def _matches(self, f_index, f_grid, indices, states):
|
|
||||||
"""
|
|
||||||
Determines that neighborhood matches expectation exactly.
|
|
||||||
|
|
||||||
Note this functions like the tolerate method with a tolerance of 1.
|
|
||||||
"""
|
|
||||||
return not np.count_nonzero(f_grid[indices] ^ states)
|
|
||||||
|
|
||||||
def _tolerates(self, f_index, f_grid, indices, states, tolerance):
|
|
||||||
"""
|
|
||||||
Determines that neighborhood matches expectation within tolerance.
|
|
||||||
|
|
||||||
We see that the percentage of actual matches are greater than or equal to the given tolerance level. If so, we
|
|
||||||
consider this cell to be alive. Note tolerance must be a value 0 <= t <= 1.
|
|
||||||
"""
|
|
||||||
non_matches = np.count_nonzero(f_grid[indices] ^ states)
|
|
||||||
return (non_matches / len(f_grid)) >= tolerance
|
|
||||||
|
|
||||||
def _satisfies(self, f_index, f_grid, indices, states, valid_func):
|
|
||||||
"""
|
|
||||||
Allows custom function to relay next state of given cell.
|
|
||||||
|
|
||||||
The passed function is supplied the list of 2-tuple elements, of which the first is a Cell and the second is
|
|
||||||
the expected state as declared in the Neighborhood, as well as the grid and cell in question.
|
|
||||||
"""
|
|
||||||
return valid_func(f_index, f_grid, indices, states)
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue