r
/
fifth
1
Fork 0

Removed Cell class and use ints instead

master
jrpotter 2015-06-01 08:30:00 -04:00
parent a0f62d9d4c
commit 0236418ef1
6 changed files with 106 additions and 184 deletions

View File

@ -2,15 +2,15 @@ import cam
import ruleset as rs
import neighborhood as nh
def game_of_life(cell, neighbors):
def game_of_life(coordinate, grid, neighbors):
"""
Rules of the Game of Life.
Note we ignore the second component of the neighbors tuples since
life depends on all neighbors
"""
total = sum(map(lambda x: int(x[0].value), neighbors))
if cell.value:
total = sum(map(lambda x: x[1], neighbors))
if grid[coordinate]:
if total < 2 or total > 3:
return False
else:
@ -24,5 +24,5 @@ if __name__ == '__main__':
c = cam.CAM(1, (100, 100))
c.randomize()
r = rs.Ruleset(rs.Rule.SATISFY)
n = nh.Neighborhood.moore(c.master.grid, True)
c.start_plot(50, r, n, game_of_life)
n = nh.Neighborhood.moore(c.master, True)
c.start_plot(100, r, n, game_of_life)

View File

@ -1,12 +1,13 @@
"""
@author: jrpotter
@date: June 01, 2015
"""
import time
import copy
import camtools
import ruleset as rs
import cell_plane as cp
import neighborhood as nh
import numpy as np
@ -23,14 +24,12 @@ class CAM:
all methods needed (i.e. supported) to interact/configure the cellular automata as desired.
"""
def __init__(self, cps=1, dimen=(100,100)):
def __init__(self, cps=1, dimen=(100, 100)):
"""
"""
cps = max(cps, 1)
self._dimen = dimen
self._planes = [cp.CellPlane(dimen) for i in range(cps)]
self.master = self._planes[0]
self.planes = np.zeros((max(cps, 1),) + dimen, dtype=np.int32)
self.master = self.planes[0]
def start_plot(self, clock, ruleset, neighborhood, *args):
@ -46,11 +45,11 @@ class CAM:
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
mshown = plt.matshow(self.master.to_binary(), fig.number)
mshown = plt.matshow(self.master, fig.number)
def animate(frame):
self.tick(ruleset, neighborhood, *args)
mshown.set_array(self.master.to_binary())
mshown.set_array(self.master)
fig.canvas.draw()
ani.FuncAnimation(fig, animate, interval=clock)
@ -64,7 +63,7 @@ class CAM:
"""
while True:
print(self.to_binary())
print(self.master)
time.sleep(clock / 1000)
self.tick(ruleset, neighborhood, *args)
@ -77,17 +76,15 @@ class CAM:
is placed into the master grid. Depending on the timing specifications set by the user, this
may also change secondary cell planes (the master is always updated on each tick).
"""
self.master.grid[:] = rs.Ruleset.update(self.master.grid, ruleset, neighborhood, *args)
tmp = np.copy(self.master)
for i in range(len(self.master.flat)):
tmp.flat[i] = ruleset.call(i, self.master, neighborhood, *args)
self.master[:] = tmp
def randomize(self):
"""
Set the master grid to a random configuration.
"""
@np.vectorize
def v_random(cell):
cell.value = np.random.random_integers(0, 1)
return cell
v_random(self.master.grid)
self.master[:] = np.random.random_integers(0, 1, self.master.shape)

36
src/camtools.py Normal file
View File

@ -0,0 +1,36 @@
"""
@author: jrpotter
@date: June 01, 2015
"""
import numpy as np
def flatten(coordinates, grid):
"""
Given the coordinates of a matrix, returns the index of the flat matrix.
"""
index = 0
for i in range(len(coordinates)):
index += coordinates[i] * np.prod(grid.shape[i+1:], dtype=np.int32)
return index
def unflatten(index, grid):
"""
Given an index of a flat matrix, returns the corresponding coordinates.
"""
coordinates = []
for i in range(len(grid.shape)):
tmp = np.prod(grid.shape[i+1:], dtype=np.int32)
coordinates.append(index // tmp)
index -= tmp * coordinates[-1]
return tuple(coordinates)
def comp_add(coor1, coor2):
"""
Adds components of coordinates element-wise.
"""
return tuple(map(sum, zip(coor1, coor2)))

View File

@ -1,69 +0,0 @@
"""
"""
import numpy as np
import matplotlib.pyplot as plt
class Cell:
"""
Represents a "cell" in a CellPlane.
Note we keep track of the index for vectorization purposes. By maintaining each index
and batch updating via the given index, we can much more efficiently update the entire
cell plane.
"""
def __init__(self, value, *index):
self.value = value
self.index = index
class CellPlane:
"""
A CellPlane represents a layer of the grids that can be placed on top of one another in a 2D CAM.
The use of multiple cell plane allow for more intricate states of life and death, though there
exists only a single master cell plane that controls the others. That is, the master cell plane has
a CAM ruleset applied to it, and the other cell planes merely copy the master, though this can
be delayed and have different color mappings.
For example, by setting a delay of two ticks on the second cell plane of a 2-level CAM configuration,
one can allow for ECHOing, providing a more intuitive sense of "velocity" based on the master.
That is not to say one could not have multiple CAM's operating simultaneously though. We can consider
a configuration to consist of an arbitrary number of planes, of which one is the master, but multiple
masters can exist in separate CAMs that can interact with one another.
"""
@staticmethod
@np.vectorize
def _populate(*indices):
"""
The following joins indices in N-dimensions together.
This information is stored in a cell (with initial value False) in order for batch processing
to be performed when actually updating values and computing whether a cell is on or off. For
example, if exploring a 4D array, we want to be able to know which cells we need to check the
status of, but this is relative to the current cell, whose position we do not know unless that
information is stored with the current cell.
"""
return Cell(False, *indices)
def __init__(self, dimen):
"""
"""
self.grid = CellPlane._populate(*np.indices(dimen))
def to_binary(self):
"""
"""
vfunc = np.vectorize(lambda x: int(x.value))
return vfunc(self.grid)

View File

@ -4,7 +4,8 @@
@author: jrpotter
@date: May 31st, 2015
"""
import itertools
import camtools as ct
import itertools as it
class Neighborhood:
@ -23,45 +24,7 @@ class Neighborhood:
at this point isn't possible.
"""
class NeighborhoodKey:
"""
Allows proper sorting of neighborhoods.
Lists should be returned in order, where cell's with smaller indices (in most significant axis first)
are listed before cell's with larger ones. For example, in a 3D grid, the neighbors corresponding to:
offsets = (-1, -1, -1), (-1, 1, 0), (-1, 0, -1), and (1, 0, -1)
are returned in the following order:
offsets = (-1, -1, -1), (-1, 0, -1), (1, 0, -1), (-1, 1, 0)
since the z-axis is most significant, followed by the y-axis, and lastly the x-axis.
"""
def __init__(self, obj, *args):
self.obj = obj
def __lt__(self, other):
return self.compare(self.obj, other.obj) < 0
def __gt__(self, other):
return self.compare(self.obj, other.obj) > 0
def __eq__(self, other):
return self.compare(self.obj, other.obj) == 0
def __le__(self, other):
return self.compare(self.obj, other.obj) <= 0
def __ge__(self, other):
return self.compare(self.obj, other.obj) >= 0
def __ne__(self, other):
return self.compare(self.obj, other.obj) != 0
def compare(self, other):
for i in reversed(range(len(a))):
if a[i] < b[i]:
return -1
elif a[i] > b[i]:
return 1
return 0
def __init__(self, grid, wrap_around=True):
def __init__(self, wrap_around=True):
"""
Sets up an empty neighborhood.
@ -69,42 +32,41 @@ class Neighborhood:
Note the offsets have a tuple as a key representing the position being offsetted by, and as a value,
the current state the given cell at the offset is checked to be.
"""
self.grid = grid
self.offsets = {}
self.wrap_around = wrap_around
def neighbors(self, cell):
def neighbors(self, index, grid):
"""
Returns all cells in the given neighborhood.
The returned cells are grouped with the value the cell is checked to be (a 2-tuple (Cell, value) pair).
These are sorted based on the NeighborhoodKey comparison class defined above.
The returned list of indices represent the index in question, the value at the given index, and
the expected value as defined in the offsets.
"""
cells = []
for k in sorted(self.offsets.keys()):
position = [sum(x) for x in zip(cell.index, k)]
for i in range(len(position)):
indices = []
for key in sorted(self.offsets.keys()):
if self.wrap_around:
position[i] = position[i] % self.grid.shape[i]
elif i < 0 or i >= self.grid.shape[i]:
break
f_index = (key + index) % len(grid.flat)
indices.append((f_index, grid.flat[f_index], self.offsets[key]))
else:
cells.append((self.grid[tuple(position)], self.offsets[k]))
pass
return cells
return indices
def extend(self, offsets, strict=False):
def extend(self, offsets, grid, strict=False):
"""
Adds new offsets to the instance member offsets.
We complain if the strict flag is set to True and an offset has already been declared with a different value.
Note also that all offsets are indices of the flattened matrix. This allows for quick row indexing as opposed
to individual coordinates.
"""
f_offsets = {ct.flatten(k, grid): v for k, v in offsets.items()}
if not strict:
self.offsets.update(offsets)
self.offsets.update(f_offsets)
else:
for k in offsets.keys():
for k in f_offsets.keys():
value = self.offsets.get(k, None)
if value is None:
self.offsets[k] = offsets[k]
@ -125,12 +87,12 @@ class Neighborhood:
"""
offsets = {}
variants = ([-1, 0, 1],) * len(grid.shape)
for current in itertools.product(*variants):
for current in it.product(*variants):
if any(current):
offsets[current] = value
m_neighborhood = cls(grid, wrap_around)
m_neighborhood.extend(offsets)
m_neighborhood = cls(wrap_around)
m_neighborhood.extend(offsets, grid)
return m_neighborhood
@ -154,8 +116,8 @@ class Neighborhood:
offsets[tuple(variant)] = value
variant[i] = 0
n_neighborhood = cls(grid, wrap_around)
n_neighborhood.extend(offsets)
n_neighborhood = cls(wrap_around)
n_neighborhood.extend(offsets, grid)
return n_neighborhood

View File

@ -5,9 +5,8 @@
@author: jrpotter
@date: May 31st, 2015
"""
import copy
import enum
import numpy as np
import camtools
class Rule(enum.Enum):
@ -29,26 +28,6 @@ class Ruleset:
a neighborhood instance's offsets member.
"""
@staticmethod
@np.vectorize
def update(cell, rules, neighborhood, *args):
"""
Allow for batch processing of rules.
We choose our processing function based on the specified rule and update every cell in the grid simultaneously
via a vectorization.
"""
tmp = copy.deepcopy(cell)
if rules.method == Rule.MATCH:
tmp.value = rules.matches(cell, neighborhood, *args)
elif rules.method == Rule.TOLERATE:
tmp.value = rules.tolerate(cell, neighborhood, *args)
elif rules.method == Rule.SATISFY:
tmp.value = rules.satisfies(cell, neighborhood, *args)
return tmp
def __init__(self, method):
"""
@ -56,22 +35,22 @@ class Ruleset:
self.method = method
def matches(self, cell, neighborhood):
def matches(self, index, grid, neighborhood):
"""
Determines that neighborhood matches expectation exactly.
Note this is just like the tolerate method with a tolerance of 1, but
recoding allows for short circuiting.
"""
residents = neighborhood.neighbors(cell)
residents = neighborhood.neighbors(index, grid)
for resident in residents:
if resident[0].value != resident[1]:
if grid[resident[0]] != resident[1]:
return False
return True
def tolerate(self, cell, neighborhood, tolerance):
def tolerate(self, index, grid, neighborhood, tolerance):
"""
Determines that neighborhood matches expectation within tolerance.
@ -79,23 +58,40 @@ class Ruleset:
consider this cell to be alive. Note tolerance must be a value 0 <= t <= 1.
"""
matches = 0
residents = neighborhood.neighbors(cell)
residents = neighborhood.neighbors(index, grid)
for resident in residents:
if resident[0].value == resident[1]:
if grid[resident[0]] == resident[1]:
matches += 1
return (matches / len(residents)) >= tolerance
def satisfies(self, cell, neighborhood, valid_func):
def satisfies(self, index, grid, neighborhood, 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.
"""
residents = neighborhood.neighbors(cell)
residents = neighborhood.neighbors(index, grid)
coordinate = camtools.unflatten(index, grid)
return valid_func(cell, residents)
return valid_func(coordinate, grid, residents)
def call(self, index, grid, neighborhood, *args):
"""
Allow for batch processing of rules.
We choose our processing function based on the specified rule and update every cell in the grid simultaneously
via a vectorization.
"""
if self.method == Rule.MATCH:
func = self.matches
elif self.method == Rule.TOLERATE:
func = self.tolerate
elif self.method == Rule.SATISFY:
func = self.satisfies
return int(func(index, grid, neighborhood, *args))