Testing Configuration
parent
75c9dc22fb
commit
df9e7206c2
|
@ -41,7 +41,7 @@ class Neighborhood:
|
||||||
self.neighbors = bitarray()
|
self.neighbors = bitarray()
|
||||||
for offset in offsets:
|
for offset in offsets:
|
||||||
f_index = plane.flatten(offset) + self.index
|
f_index = plane.flatten(offset) + self.index
|
||||||
self.neighbors.append(plane[f_index % len(plane.bits)])
|
self.neighbors.append(plane.bits[f_index % len(plane.bits)])
|
||||||
|
|
||||||
self.total = len(self.neighbors)
|
self.total = len(self.neighbors)
|
||||||
|
|
||||||
|
@ -207,7 +207,7 @@ class Configuration:
|
||||||
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 'plane' key, corresponding to the plane in question.
|
state value and a 'plane' key, corresponding to the plane in question.
|
||||||
"""
|
"""
|
||||||
self.offsets = bitarray()
|
self.offsets = []
|
||||||
self.sequence = bitarray()
|
self.sequence = bitarray()
|
||||||
self.next_state = next_state
|
self.next_state = next_state
|
||||||
if 'plane' in kwargs and 'offsets' in kwargs:
|
if 'plane' in kwargs and 'offsets' in kwargs:
|
||||||
|
@ -222,8 +222,7 @@ 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():
|
||||||
f_index = plane.flatten(coor)
|
self.offsets.append(coor)
|
||||||
self.offsets.append(f_index)
|
|
||||||
self.sequence.append(bit)
|
self.sequence.append(bit)
|
||||||
|
|
||||||
def passes(self, plane, neighborhood, vfunc, *args):
|
def passes(self, plane, neighborhood, vfunc, *args):
|
||||||
|
@ -253,7 +252,7 @@ class Configuration:
|
||||||
Note this behaves like the _tolerates method with a tolerance of 1.
|
Note this behaves like the _tolerates method with a tolerance of 1.
|
||||||
"""
|
"""
|
||||||
neighborhood.populate(plane, self.offsets)
|
neighborhood.populate(plane, self.offsets)
|
||||||
return not self.sequence ^ neighborhood.neighbors
|
return (self.sequence ^ neighborhood.neighbors).count() == 0
|
||||||
|
|
||||||
def tolerates(self, plane, neighborhood, tolerance):
|
def tolerates(self, plane, neighborhood, tolerance):
|
||||||
"""
|
"""
|
||||||
|
@ -264,7 +263,7 @@ class Configuration:
|
||||||
"""
|
"""
|
||||||
neighborhood.populate(plane, self.offsets)
|
neighborhood.populate(plane, self.offsets)
|
||||||
non_matches = self.sequence ^ neighborhood.neighbors
|
non_matches = self.sequence ^ neighborhood.neighbors
|
||||||
return (non_matches / len(self.sequence)) >= tolerance
|
return ((len(self.sequence) - non_matches.count()) / len(self.sequence)) >= tolerance
|
||||||
|
|
||||||
def satisfies(self, plane, neighborhood, valid_func, *args):
|
def satisfies(self, plane, neighborhood, valid_func, *args):
|
||||||
"""
|
"""
|
||||||
|
|
17
src/plane.py
17
src/plane.py
|
@ -7,23 +7,6 @@ from bitarray import bitarray
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
|
|
||||||
class Coordinate:
|
|
||||||
"""
|
|
||||||
Allow normilization between flat indices and offsets.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, index, plane):
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
if type(index) is tuple:
|
|
||||||
self.index = index
|
|
||||||
self.flat = plane.flatten(index)
|
|
||||||
else:
|
|
||||||
self.flat = index
|
|
||||||
self.index = plane.unflatten(index)
|
|
||||||
|
|
||||||
|
|
||||||
class Plane:
|
class Plane:
|
||||||
"""
|
"""
|
||||||
Represents a cell plane, with underlying usage of bitarrays.
|
Represents a cell plane, with underlying usage of bitarrays.
|
||||||
|
|
|
@ -9,6 +9,7 @@ said neighborhood that yield an "on" or "off" state on the cell a ruleset is bei
|
||||||
import enum
|
import enum
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import configuration as c
|
import configuration as c
|
||||||
|
|
||||||
from bitarray import bitarray
|
from bitarray import bitarray
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,60 +68,17 @@ class Ruleset:
|
||||||
|
|
||||||
# These are the states of configurations that pass (note if all configurations
|
# These are the states of configurations that pass (note if all configurations
|
||||||
# fail for any state, the state remains the same)
|
# fail for any state, the state remains the same)
|
||||||
next_states = plane.bits.copy()
|
next_plane = plane.bits.copy()
|
||||||
|
|
||||||
# These are the states we attempt to apply a configuration to
|
# These are the states we attempt to apply a configuration to
|
||||||
# Since totals are computed simultaneously, we save which states do not pass
|
# Since totals are computed for a configuration at once, we save
|
||||||
# for each configuration
|
# which states do not pass for each configuration
|
||||||
current_states = enumerate(plane.bits)
|
current_states = enumerate(plane.bits)
|
||||||
for config in self.configurations:
|
for config in self.configurations:
|
||||||
totals = Neighborhood.get_totals(plane, config.offsets)
|
totals = c.Neighborhood.get_totals(plane, config.offsets)
|
||||||
|
|
||||||
|
next_states = []
|
||||||
|
for index, state in current_states:
|
||||||
for index, state in enumerate(plane.bits):
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 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
|
|
||||||
# row, mark those that fail, and try the next configuration on the failed bits until
|
|
||||||
# either all bits pass or configurations are exhausted
|
|
||||||
for flat_index, value in enumerate(plane.grid.flat):
|
|
||||||
|
|
||||||
next_row = bitarray(plane.N)
|
|
||||||
to_update = range(0, plane.N)
|
|
||||||
for config in self.configurations:
|
|
||||||
|
|
||||||
next_update = []
|
|
||||||
|
|
||||||
# After profiling with a previous version, I found that going through each index and totaling the number
|
|
||||||
# of active states was taking much longer than I liked. Instead, we compute as many neighborhoods as possible
|
|
||||||
# simultaneously, avoiding explicit summation via the "sum" function, at least for each state separately.
|
|
||||||
#
|
|
||||||
# Because the states are now represented as numbers, we instead convert each number to their binary representation
|
|
||||||
# and add the binary representations together. We do this in chunks of 9, depending on the number of offsets, so
|
|
||||||
# no overflowing of a single column can occur. We can then find the total of the ith neighborhood by checking the
|
|
||||||
# sum of the ith index of the summation of every 9 chunks of numbers (this is done a row at a time).
|
|
||||||
neighboring = []
|
|
||||||
for flat_offset, bit_offset, _ in config.offsets:
|
|
||||||
neighbor = plane.grid.flat[(flat_index + flat_offset) % plane.N]
|
|
||||||
cycled = neighbor[bit_offset:] + neighbor[:bit_offset]
|
|
||||||
neighboring.append(int(cycled.to01()))
|
|
||||||
|
|
||||||
# Chunk into groups of 9 and sum all values
|
|
||||||
# These summations represent the total number of active states in a given neighborhood
|
|
||||||
totals = [0] * plane.N
|
|
||||||
chunks = map(sum, [neighboring[i:i+9] for i in range(0, len(neighboring), 9)])
|
|
||||||
for chunk in chunks:
|
|
||||||
i_chunk = map(int, str(chunk).zfill(plane.N))
|
|
||||||
totals = map(sum, zip(totals, i_chunk))
|
|
||||||
totals = list(totals)
|
|
||||||
|
|
||||||
# Determine which function should be used to test success
|
# Determine which function should be used to test success
|
||||||
if self.method == Ruleset.Method.MATCH:
|
if self.method == Ruleset.Method.MATCH:
|
||||||
|
@ -132,24 +90,21 @@ class Ruleset:
|
||||||
elif self.method == Ruleset.Method.ALWAYS_PASS:
|
elif self.method == Ruleset.Method.ALWAYS_PASS:
|
||||||
vfunc = lambda *args: True
|
vfunc = lambda *args: True
|
||||||
|
|
||||||
# Apply change to all successful configurations
|
# Passes a mostly uninitialized neighborhood to the given function
|
||||||
|
# Note if you need actual states of the neighborhood, make sure to
|
||||||
|
# call the neighborhood's populate function
|
||||||
|
neighborhood = c.Neighborhood(index)
|
||||||
|
neighborhood.total = totals[index]
|
||||||
|
|
||||||
for bit_index in to_update:
|
# Apply changes to for any successful configurations
|
||||||
neighborhood = c.Neighborhood(flat_index, bit_index, totals[bit_index])
|
success, to_state = config.passes(plane, neighborhood, vfunc, *args)
|
||||||
success, state = config.passes(plane, neighborhood, vfunc, *args)
|
|
||||||
if success:
|
if success:
|
||||||
next_row[bit_index] = state
|
next_plane[index] = to_state
|
||||||
else:
|
else:
|
||||||
next_update.append(bit_index)
|
next_states.append((index, state))
|
||||||
|
|
||||||
# Apply next configuration to given indices
|
current_states = next_states
|
||||||
to_update = next_update
|
|
||||||
|
|
||||||
# We must update all states after each next state is computed
|
|
||||||
next_grid.append(next_row)
|
|
||||||
|
|
||||||
# Can now apply the updates simultaneously
|
|
||||||
for i in range(plane.grid.size):
|
|
||||||
plane.grid.flat[i] = next_grid[i]
|
|
||||||
|
|
||||||
|
# All configurations tested, transition plane
|
||||||
|
plane.bits = next_plane
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ sys.path.insert(0, os.path.join('..', 'src'))
|
||||||
|
|
||||||
import plane
|
import plane
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from neighborhood import Neighborhood
|
from configuration import Neighborhood
|
||||||
from configuration import Configuration
|
from configuration import Configuration
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ class TestConfiguration:
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.neighborhood = Neighborhood(0)
|
self.neighborhood = Neighborhood(0)
|
||||||
|
|
||||||
self.plane2d = plane.Plane((100, 100))
|
self.plane2d = plane.Plane((10, 10))
|
||||||
self.config2d = Configuration(0, plane=self.plane2d, offsets={
|
self.config2d = Configuration(0, plane=self.plane2d, offsets={
|
||||||
(-1, -1): 1,
|
(-1, -1): 1,
|
||||||
(-1, 0): 1,
|
(-1, 0): 1,
|
||||||
|
@ -51,4 +51,13 @@ class TestConfiguration:
|
||||||
self.plane2d[[(-1, -1), (-1, 0), (1, -1), (0, 0)]] = 1
|
self.plane2d[[(-1, -1), (-1, 0), (1, -1), (0, 0)]] = 1
|
||||||
assert self.config2d.matches(self.plane2d, self.neighborhood)
|
assert self.config2d.matches(self.plane2d, self.neighborhood)
|
||||||
|
|
||||||
|
def test_toleranceNeighborhood(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
assert not self.config2d.tolerates(self.plane2d, self.neighborhood, 0.5)
|
||||||
|
self.plane2d[(-1, -1)] = 1
|
||||||
|
assert not self.config2d.tolerates(self.plane2d, self.neighborhood, 0.5)
|
||||||
|
self.plane2d[(-1, 0)] = 1
|
||||||
|
assert self.config2d.tolerates(self.plane2d, self.neighborhood, 0.5)
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ sys.path.insert(0, os.path.join('..', 'src'))
|
||||||
|
|
||||||
import plane
|
import plane
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from neighborhood import Neighborhood
|
from configuration import Neighborhood
|
||||||
|
|
||||||
|
|
||||||
class TestNeighborhood:
|
class TestNeighborhood:
|
||||||
|
@ -87,3 +87,16 @@ class TestNeighborhood:
|
||||||
assert np.count_nonzero(np.array(t1)) == 200
|
assert np.count_nonzero(np.array(t1)) == 200
|
||||||
assert np.count_nonzero(np.array(t2)) == 20000
|
assert np.count_nonzero(np.array(t2)) == 20000
|
||||||
|
|
||||||
|
def test_neighborhoodPopulate(self):
|
||||||
|
"""
|
||||||
|
Neighborhood Populate.
|
||||||
|
"""
|
||||||
|
self.plane2d[10] = 1
|
||||||
|
self.neigh2d.populate(self.plane2d, self.offsets2d)
|
||||||
|
assert self.neigh2d.neighbors.count() == 0
|
||||||
|
self.plane2d[1] = 1
|
||||||
|
self.neigh2d.populate(self.plane2d, self.offsets2d)
|
||||||
|
assert self.neigh2d.neighbors.count() == 1
|
||||||
|
self.plane2d[self.offsets2d] = 1
|
||||||
|
self.neigh2d.populate(self.plane2d, self.offsets2d)
|
||||||
|
assert self.neigh2d.neighbors.count() == 2
|
||||||
|
|
Loading…
Reference in New Issue