Removed Cell class and use ints instead
parent
a0f62d9d4c
commit
0236418ef1
|
@ -2,15 +2,15 @@ import cam
|
||||||
import ruleset as rs
|
import ruleset as rs
|
||||||
import neighborhood as nh
|
import neighborhood as nh
|
||||||
|
|
||||||
def game_of_life(cell, neighbors):
|
def game_of_life(coordinate, grid, neighbors):
|
||||||
"""
|
"""
|
||||||
Rules of the Game of Life.
|
Rules of the Game of Life.
|
||||||
|
|
||||||
Note we ignore the second component of the neighbors tuples since
|
Note we ignore the second component of the neighbors tuples since
|
||||||
life depends on all neighbors
|
life depends on all neighbors
|
||||||
"""
|
"""
|
||||||
total = sum(map(lambda x: int(x[0].value), neighbors))
|
total = sum(map(lambda x: x[1], neighbors))
|
||||||
if cell.value:
|
if grid[coordinate]:
|
||||||
if total < 2 or total > 3:
|
if total < 2 or total > 3:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
@ -24,5 +24,5 @@ if __name__ == '__main__':
|
||||||
c = cam.CAM(1, (100, 100))
|
c = cam.CAM(1, (100, 100))
|
||||||
c.randomize()
|
c.randomize()
|
||||||
r = rs.Ruleset(rs.Rule.SATISFY)
|
r = rs.Ruleset(rs.Rule.SATISFY)
|
||||||
n = nh.Neighborhood.moore(c.master.grid, True)
|
n = nh.Neighborhood.moore(c.master, True)
|
||||||
c.start_plot(50, r, n, game_of_life)
|
c.start_plot(100, r, n, game_of_life)
|
||||||
|
|
31
src/cam.py
31
src/cam.py
|
@ -1,12 +1,13 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@author: jrpotter
|
||||||
|
@date: June 01, 2015
|
||||||
"""
|
"""
|
||||||
import time
|
import time
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
import camtools
|
||||||
import ruleset as rs
|
import ruleset as rs
|
||||||
import cell_plane as cp
|
|
||||||
import neighborhood as nh
|
import neighborhood as nh
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -23,14 +24,12 @@ class CAM:
|
||||||
all methods needed (i.e. supported) to interact/configure the cellular automata as desired.
|
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.planes = np.zeros((max(cps, 1),) + dimen, dtype=np.int32)
|
||||||
self._dimen = dimen
|
self.master = self.planes[0]
|
||||||
self._planes = [cp.CellPlane(dimen) for i in range(cps)]
|
|
||||||
self.master = self._planes[0]
|
|
||||||
|
|
||||||
|
|
||||||
def start_plot(self, clock, ruleset, neighborhood, *args):
|
def start_plot(self, clock, ruleset, neighborhood, *args):
|
||||||
|
@ -46,11 +45,11 @@ class CAM:
|
||||||
ax.get_xaxis().set_visible(False)
|
ax.get_xaxis().set_visible(False)
|
||||||
ax.get_yaxis().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):
|
def animate(frame):
|
||||||
self.tick(ruleset, neighborhood, *args)
|
self.tick(ruleset, neighborhood, *args)
|
||||||
mshown.set_array(self.master.to_binary())
|
mshown.set_array(self.master)
|
||||||
fig.canvas.draw()
|
fig.canvas.draw()
|
||||||
|
|
||||||
ani.FuncAnimation(fig, animate, interval=clock)
|
ani.FuncAnimation(fig, animate, interval=clock)
|
||||||
|
@ -64,7 +63,7 @@ class CAM:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
print(self.to_binary())
|
print(self.master)
|
||||||
time.sleep(clock / 1000)
|
time.sleep(clock / 1000)
|
||||||
self.tick(ruleset, neighborhood, *args)
|
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
|
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).
|
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):
|
def randomize(self):
|
||||||
"""
|
"""
|
||||||
Set the master grid to a random configuration.
|
Set the master grid to a random configuration.
|
||||||
"""
|
"""
|
||||||
@np.vectorize
|
self.master[:] = np.random.random_integers(0, 1, self.master.shape)
|
||||||
def v_random(cell):
|
|
||||||
cell.value = np.random.random_integers(0, 1)
|
|
||||||
return cell
|
|
||||||
|
|
||||||
v_random(self.master.grid)
|
|
||||||
|
|
||||||
|
|
|
@ -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)))
|
|
@ -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)
|
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
@author: jrpotter
|
@author: jrpotter
|
||||||
@date: May 31st, 2015
|
@date: May 31st, 2015
|
||||||
"""
|
"""
|
||||||
import itertools
|
import camtools as ct
|
||||||
|
import itertools as it
|
||||||
|
|
||||||
|
|
||||||
class Neighborhood:
|
class Neighborhood:
|
||||||
|
@ -23,45 +24,7 @@ class Neighborhood:
|
||||||
at this point isn't possible.
|
at this point isn't possible.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class NeighborhoodKey:
|
def __init__(self, wrap_around=True):
|
||||||
"""
|
|
||||||
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):
|
|
||||||
"""
|
"""
|
||||||
Sets up an empty neighborhood.
|
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,
|
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.
|
the current state the given cell at the offset is checked to be.
|
||||||
"""
|
"""
|
||||||
self.grid = grid
|
|
||||||
self.offsets = {}
|
self.offsets = {}
|
||||||
self.wrap_around = wrap_around
|
self.wrap_around = wrap_around
|
||||||
|
|
||||||
|
|
||||||
def neighbors(self, cell):
|
def neighbors(self, index, grid):
|
||||||
"""
|
"""
|
||||||
Returns all cells in the given neighborhood.
|
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).
|
The returned list of indices represent the index in question, the value at the given index, and
|
||||||
These are sorted based on the NeighborhoodKey comparison class defined above.
|
the expected value as defined in the offsets.
|
||||||
"""
|
"""
|
||||||
cells = []
|
indices = []
|
||||||
for k in sorted(self.offsets.keys()):
|
for key in sorted(self.offsets.keys()):
|
||||||
position = [sum(x) for x in zip(cell.index, k)]
|
if self.wrap_around:
|
||||||
for i in range(len(position)):
|
f_index = (key + index) % len(grid.flat)
|
||||||
if self.wrap_around:
|
indices.append((f_index, grid.flat[f_index], self.offsets[key]))
|
||||||
position[i] = position[i] % self.grid.shape[i]
|
|
||||||
elif i < 0 or i >= self.grid.shape[i]:
|
|
||||||
break
|
|
||||||
else:
|
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.
|
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.
|
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:
|
if not strict:
|
||||||
self.offsets.update(offsets)
|
self.offsets.update(f_offsets)
|
||||||
else:
|
else:
|
||||||
for k in offsets.keys():
|
for k in f_offsets.keys():
|
||||||
value = self.offsets.get(k, None)
|
value = self.offsets.get(k, None)
|
||||||
if value is None:
|
if value is None:
|
||||||
self.offsets[k] = offsets[k]
|
self.offsets[k] = offsets[k]
|
||||||
|
@ -125,12 +87,12 @@ class Neighborhood:
|
||||||
"""
|
"""
|
||||||
offsets = {}
|
offsets = {}
|
||||||
variants = ([-1, 0, 1],) * len(grid.shape)
|
variants = ([-1, 0, 1],) * len(grid.shape)
|
||||||
for current in itertools.product(*variants):
|
for current in it.product(*variants):
|
||||||
if any(current):
|
if any(current):
|
||||||
offsets[current] = value
|
offsets[current] = value
|
||||||
|
|
||||||
m_neighborhood = cls(grid, wrap_around)
|
m_neighborhood = cls(wrap_around)
|
||||||
m_neighborhood.extend(offsets)
|
m_neighborhood.extend(offsets, grid)
|
||||||
|
|
||||||
return m_neighborhood
|
return m_neighborhood
|
||||||
|
|
||||||
|
@ -154,8 +116,8 @@ class Neighborhood:
|
||||||
offsets[tuple(variant)] = value
|
offsets[tuple(variant)] = value
|
||||||
variant[i] = 0
|
variant[i] = 0
|
||||||
|
|
||||||
n_neighborhood = cls(grid, wrap_around)
|
n_neighborhood = cls(wrap_around)
|
||||||
n_neighborhood.extend(offsets)
|
n_neighborhood.extend(offsets, grid)
|
||||||
|
|
||||||
return n_neighborhood
|
return n_neighborhood
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,8 @@
|
||||||
@author: jrpotter
|
@author: jrpotter
|
||||||
@date: May 31st, 2015
|
@date: May 31st, 2015
|
||||||
"""
|
"""
|
||||||
import copy
|
|
||||||
import enum
|
import enum
|
||||||
import numpy as np
|
import camtools
|
||||||
|
|
||||||
|
|
||||||
class Rule(enum.Enum):
|
class Rule(enum.Enum):
|
||||||
|
@ -29,26 +28,6 @@ class Ruleset:
|
||||||
a neighborhood instance's offsets member.
|
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):
|
def __init__(self, method):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -56,22 +35,22 @@ class Ruleset:
|
||||||
self.method = method
|
self.method = method
|
||||||
|
|
||||||
|
|
||||||
def matches(self, cell, neighborhood):
|
def matches(self, index, grid, neighborhood):
|
||||||
"""
|
"""
|
||||||
Determines that neighborhood matches expectation exactly.
|
Determines that neighborhood matches expectation exactly.
|
||||||
|
|
||||||
Note this is just like the tolerate method with a tolerance of 1, but
|
Note this is just like the tolerate method with a tolerance of 1, but
|
||||||
recoding allows for short circuiting.
|
recoding allows for short circuiting.
|
||||||
"""
|
"""
|
||||||
residents = neighborhood.neighbors(cell)
|
residents = neighborhood.neighbors(index, grid)
|
||||||
for resident in residents:
|
for resident in residents:
|
||||||
if resident[0].value != resident[1]:
|
if grid[resident[0]] != resident[1]:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def tolerate(self, cell, neighborhood, tolerance):
|
def tolerate(self, index, grid, neighborhood, tolerance):
|
||||||
"""
|
"""
|
||||||
Determines that neighborhood matches expectation within 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.
|
consider this cell to be alive. Note tolerance must be a value 0 <= t <= 1.
|
||||||
"""
|
"""
|
||||||
matches = 0
|
matches = 0
|
||||||
residents = neighborhood.neighbors(cell)
|
residents = neighborhood.neighbors(index, grid)
|
||||||
for resident in residents:
|
for resident in residents:
|
||||||
if resident[0].value == resident[1]:
|
if grid[resident[0]] == resident[1]:
|
||||||
matches += 1
|
matches += 1
|
||||||
|
|
||||||
return (matches / len(residents)) >= tolerance
|
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.
|
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 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.
|
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))
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue