r
/
fifth
1
Fork 0

Revising changes throughout codebase

master
Joshua Potter 2015-06-05 20:45:57 -04:00
parent dd6d4936fb
commit 5202d62498
6 changed files with 136 additions and 95 deletions

View File

@ -1,5 +1,4 @@
"""
A series of functions related to bit manipulation of numbers.
@author: jrpotter
@date: June 5th, 2015
@ -11,7 +10,7 @@ def max_unsigned(bit_count):
return 2**bit_count - 1
def bits_of(value, size):
def bits_of(value, size = 0):
"""
"""
@ -20,3 +19,9 @@ def bits_of(value, size):
return "{}{}".format("0" * (size - len(base)), base)
else:
return base
def cycle(itr, offset):
"""
"""
return itr[:offset] + itr[offset+1:]

View File

@ -9,11 +9,6 @@ all methods needed (i.e. supported) to interact/configure the cellular automata
@date: June 01, 2015
"""
import time
import copy
import ruleset as rs
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as ani
@ -34,33 +29,18 @@ class CAM:
def __init__(self, cps=1, states=100, dimen=2):
"""
@cps: Cell planes. By default this is 1, but can be any positive number. Any non-positive number
is assumed to be 1.
@states: The number of cells that should be included in any dimension. The number of total states
will be cps * states^dimen
@dimen: The dimensions of the cellular automata. For example, for an N-tuple array, the dimension is N.
"""
plane_count = max(cps, 1)
grid_dimen = (states,) * dimen
self.planes = np.zeros((plane_count,) + grid_dimen, dtype='int32')
self.master = self.planes[0]
def randomize(self, propagate=True):
"""
Set the master grid to a random configuration.
If propagate is set to True, also immediately change all other cell planes to match.
"""
self.master[:] = np.random.random_integers(0, 1, self.master.shape)
for plane in self.planes[1:]:
plane[:] = self.master
self.planes = [Plane(grid_dimen) for i in range(cps)]
self.ticks = [(0, 1)]
self.total = 0
def tick(self, rules, *args):
"""
@ -71,12 +51,10 @@ 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).
"""
tmp = np.copy(self.master)
for i in range(len(self.master.flat)):
tmp.flat[i] = rules.applyTo(i, self.master, *args)
self.master[:] = tmp
self.total += 1
for i, j in self.ticks:
if self.total % j == 0:
rules.applyTo(self.planes[i], *args)
def start_plot(self, clock, rules, *args):
"""
@ -91,18 +69,17 @@ class CAM:
ax.get_xaxis().set_visible(False)
ax.get_yaxis().set_visible(False)
mshown = plt.matshow(self.master, fig.number, cmap='Greys')
mshown = plt.matshow(self.planes[0].bits(), fig.number, cmap='Greys')
def animate(frame):
self.tick(rules, *args)
mshown.set_array(self.master)
mshown.set_array(self.planes[0].bits())
return [mshown]
ani.FuncAnimation(fig, animate, interval=clock)
plt.axis('off')
plt.show()
def start_console(self, clock, rules, *args):
"""
Initates main console loop.
@ -111,8 +88,7 @@ class CAM:
TODO: Incorporate curses, instead of just printing repeatedly.
"""
while True:
print(self.master)
print(self.planes[0].bits())
time.sleep(clock / 1000)
self.tick(rules, *args)

View File

@ -59,43 +59,3 @@ class Configuration:
return (success, self.next_state(f_index, grid.flat, indices, self.states, *args))
else:
return (success, self.next_state)
@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

View File

@ -8,4 +8,51 @@ class Neighborhood:
"""
"""
pass
def __init__(self, index, offsets):
"""
"""
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

View File

@ -32,14 +32,14 @@ class Plane:
If shape is length 1, we have a 1D plane. This is represented by a single number.
Otherwise, we have an N-D plane. Everything operates as expected.
"""
self.shape = tuple(shape)
self.shape = shape
if len(shape) == 0:
self.grid = None
elif len(shape) == 1:
self.grid = 0
else:
self.grid = np.zeros((np.prod(shape[:-1]),), dtype=np.object)
self.grid = np.zeros(shape[:-1], dtype=np.object)
def __getitem__(self, idx):
"""
@ -67,12 +67,9 @@ class Plane:
return int(bits)
# Simply relay to numpy methods
# We check if we reach an actual number as opposed to a tuple
# does still allow further indexing if desired. In addition, we can
# be confident idx is either a list or a number so the final dimension
# cannot be accessed from here
# We check if we encounter a list or number as opposed to a tuple, and allow further indexing if desired.
else:
tmp = np.reshape(self.grid, self.shape[:-1])[idx]
tmp = self.grid[idx]
try:
plane = Plane(tmp.shape + self.shape[-1:])
plane.grid = tmp.flat
@ -82,11 +79,26 @@ class Plane:
return plane
def f_bits(self, f_index, str_type=True):
"""
Return the binary representation of the given number at the supplied index.
If the user wants a string type, we make sure to pad the returned number to reflect
the actual states at the given index.
"""
value = bin(self.planes[0].flat[f_index])[2:]
if not str_type:
return int(value)
else:
return "{}{}".format("0" * (self.shape[-1] - len(value)), value)
def _flatten(coordinates):
"""
Given the coordinates of a matrix, returns the index of the flat matrix.
This is merely a convenience function to convert between N-dimensional space to 1D.
TODO: Delete this method?
"""
index = 0
gridprod = 1
@ -98,9 +110,18 @@ class Plane:
def randomize(self):
"""
Sets values of grid to random values.
Since numbers of the grid may be larger than numpy can handle natively (i.e. too big
for C long types), we use the python random module instead.
"""
self.grid = np.random.random_integers(0, bm.max_unsigned(dimen), self.grid.shape)
if len(self.shape) > 0:
import random as r
max_u = bm.max_unsigned(self.shape[-1])
if len(self.shape) == 1:
self.grid = r.randrange(0, max_u)
else:
self.grid = np.array([r.randrange(0, max_u) for i in range(len(self.grid))])
def bitmatrix(self):
"""

View File

@ -61,22 +61,53 @@ class Ruleset:
config = Configuration(grid, next_state, offsets)
self.configurations.append(config)
def applyTo(self, f_index, grid, *args):
def applyTo(self, plane, *args):
"""
Depending on a given method, applies ruleset to a cell.
Depending on the set method, applies ruleset to each cell in the plane.
@cell_index: The index of the cell in question, as offset by self.grid.flat. That means the index should be
a single number (not a tuple!).
@args: If our method is TOLERATE, we pass in a value in set [0, 1]. This specifies the threshold between a
passing (i.e. percentage of matches in a configuration is > arg) and failing. If our method is SATISFY,
arg should be a function returning a BOOL, which takes in a current cell's value, and the
value of its neighbors.
Note we first compute all neighborhoods in a batch manner and then test that a configuration
passes on the supplied neighborhood.
@args: If our method is TOLERATE, we pass in a value in set [0, 1]. This specifies the threshold between a
passing (i.e. percentage of matches in a configuration is > arg) and failing. If our method is SATISFY,
arg should be a function returning a BOOL, which takes in a current cell's value, and the
value of its neighbors.
"""
master = plane.grid.flat
for config in self.configurations:
# Determine the correct function to use
# Construct neighborhoods
#
# 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).
# TODO: Config offsets should be flat index, bit offset
neighborhoods = []
values = []
for f_index, offset in config.offsets:
val = plane.f_bits([f_index])
values.append(int(val[offset+1:] + val[:offset]))
# Chunk into groups of 9 and sum all values
chunks = [values[i:i+9] for i in range(0, len(values), 9)]
summands = map(sum, chunks)
# Construct neighborhoods for each value in list
if self.method == Ruleset.Method.MATCH:
vfunc = self._matches
elif self.method == Ruleset.Method.TOLERATE:
@ -91,6 +122,7 @@ class Ruleset:
if passed:
return state
# If no configuration passes, we leave the state unchanged
return grid.flat[f_index]
def _matches(self, f_index, f_grid, indices, states):