r
/
fifth
1
Fork 0

Testing Configuration

master
Joshua Potter 2015-06-20 09:16:26 -04:00
parent 75c9dc22fb
commit df9e7206c2
5 changed files with 51 additions and 92 deletions

View File

@ -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):
""" """

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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