r
/
fifth
1
Fork 0

Designing plane

master
Joshua Potter 2015-06-05 08:57:14 -04:00
parent 2f844b2b12
commit 863d974281
7 changed files with 246 additions and 116 deletions

View File

@ -17,4 +17,4 @@ if __name__ == '__main__':
p = u.CAMParser('B3/S23', c) p = u.CAMParser('B3/S23', c)
c.randomize() c.randomize()
c.start_plot(200, p.ruleset) c.start_plot(400, p.ruleset)

22
src/bitmanip.py Normal file
View File

@ -0,0 +1,22 @@
"""
A series of functions related to bit manipulation of numbers.
@author: jrpotter
@date: June 5th, 2015
"""
def max_unsigned(bit_count):
"""
"""
return 2**bit_count - 1
def bits_of(value, size):
"""
"""
base = bin(value)[2:]
if size > len(base):
return "{}{}".format("0" * (size - len(base)), base)
else:
return base

101
src/configuration.py Normal file
View File

@ -0,0 +1,101 @@
"""
@author: jrpotter
@date: June 5th, 2015
"""
class Configuration:
"""
Represents an expected neighborhood; to be compared to an actual neighborhood in a CAM.
A configuration allows exact specification of a neighborhood, not the actual state of a neighborhood.
It is merely used for reference by a ruleset, which takes in a series of configurations and
the next state of a cell depending on a configuration.
"""
def __init__(self, grid, next_state, offsets={}):
"""
@next_state: Represents the next state of a cell given a configuration passes.
This should be an [0|1|Function that returns 0 or 1]
@offsets: A dictionary of offsets containing N-tuple keys and [-1, 0, 1] values.
Note N must be the same dimension as the grid's dimensions, as it specifies
the offset from any given cell in the grid.
"""
self.next_state = next_state
# The grid we work with is flattened, so that we can simply access single indices (as opposed
# to N-ary tuples). This also allows for multiple index accessing via the numpy list indexing
# method
states = []
f_offsets = []
for k, v in offsets.items():
states.append(v)
f_offsets.append(util.flatten(k, grid))
self.states = np.array(states)
self.offsets = np.array(f_offsets)
def passes(self, f_index, grid, vfunc, *args):
"""
Checks if a given configuration passes, and if so, returns the next state.
@vfunc is an arbitrary function that takes in a flattened grid, a list of indices, and a list of values (which,
if zipped with indices, correspond to the expected value of the cell at the given index). The function should
merely verify that a list of indices "passes" some expectation.
For example, if an "exact match" function is passed, it should merely verify that the cells at the passed indices
exactly match the exact expectated cells in the list of values. It will return True or False depending.
"""
# We ensure all indices are within the given grid
indices = (f_index + self.offsets) % grid.size
# Note the distinction between success and next_state here; vfunc (validity function) tells whether the given
# configuration passes. If it does, no other configurations need to be checked and the next state is returned.
success = vfunc(f_index, grid.flat, indices, self.states, *args)
if callable(self.next_state):
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

11
src/neighborhood.py Normal file
View File

@ -0,0 +1,11 @@
"""
@author: jrpotter
@date: June 5th, 2015
"""
class Neighborhood:
"""
"""
pass

View File

@ -10,21 +10,6 @@ import ruleset as rs
import exceptions as ce import exceptions as ce
def flatten(coordinates, grid):
"""
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.
"""
index = 0
gridprod = 1
for i in reversed(range(len(coordinates))):
index += coordinates[i] * gridprod
gridprod *= grid.shape[i]
return index
class CAMParser: class CAMParser:
""" """
The following builds rulesets based on the passed string. The following builds rulesets based on the passed string.

110
src/plane.py Normal file
View File

@ -0,0 +1,110 @@
"""
Wrapper of a numpy array of bits.
For the sake of efficiency, rather than work with an (m x m x ... x m) N-dimensional grid, we instead work with
a 1D array of size (N-1)^m and reshape the grid if ever necessary. All bits of any given row is represented by
a number whose binary representation expands to the given number. A 1 at index i in turn corresponds to an on
state at the ith index of the given row. This holds for 0 as well.
For example, given a 100 x 100 CAM, we represent this underneath as a 1-D array of 100 integers, each of which's
binary expansion will be 100 bits long (and padded with 0's if necessary).
@author: jrpotter
@date: June 05, 2015
"""
import numpy as np
import bitmanip as bm
class Plane:
"""
Represents a bit plane, with underlying usage of numpy arrays.
The following allows conversion between our given representation of a grid, and the user's expected
representation of a grid. This allows accessing of bits in the same manner as one would access a
numpy grid, without the same bloat as a straightforward N-dimensional grid of booleans for instance.
"""
def __init__(self, size, N):
"""
Construction of a plane. There are three cases:
If N == 0: We have an undefined plane. Nothing is in it.
If N == 1: We have a 1D plane. This is represented by a single number.
Otherwise: We have an N-D plane. Everything operates as expected.
If N happens to be negative, an exception is thrown.
"""
if N < 0:
raise ValueError('Negative dimension nonsensical')
elif N == 0:
self.shape = ()
self.grid = np.array([], dtype=np.object)
elif N == 1:
self.shape = (size,)
self.grid = np.array([0], dtype=np.object)
else:
self.shape = (size,) * N
self.grid = np.zeros((size**(N-1),), dtype=np.object)
def __getitem__(self, idx):
"""
Indices supported are the same as those of the numpy array, except for when accessing an individual bit.
When reaching the "last" dimension of the given array, we access the bit of the number at the second
to last dimension, since we are working in (N-1)-dimensional space. Unless this last dimension is reached,
we always return a plane object (otherwise an actual 0 or 1).
"""
# Passed in coordinates, access incrementally
# Note this could be a tuple of slices or numbers
if type(idx) in [tuple]:
tmp = self
for i in idx:
tmp = tmp[i]
return tmp
# Reached last dimension, return bits instead
elif len(self.shape) == 1:
bits = bm.bits_of(self.grid[0], self.shape[0])[idx]
if isinstance(idx, slice):
return list(map(int, bits))
else:
return int(bits)
# Simply relay to numpy methods
# Note this doesn't necessarily return a grid in the same notion but
# 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
# TODO: Reconsider this...
else:
full = np.reshape(self.grid, self.shape[:-1])[idx]
tmp = cls(1, len(self.shape) - 1)
tmp.grid = full.flat
return tmp
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.
"""
index = 0
gridprod = 1
for i in reversed(range(len(coordinates))):
index += coordinates[i] * gridprod
gridprod *= self.dimen[i]
return index
def randomize(self):
"""
"""
self.grid = np.random.random_integers(0, bm.max_unsigned(dimen), self.grid.shape)
def bitmatrix(self):
"""
"""
pass

View File

@ -12,105 +12,6 @@ import itertools as it
import numpy as np import numpy as np
import util
class Configuration:
"""
Represents an expected neighborhood; to be compared to an actual neighborhood in a CAM.
A configuration allows exact specification of a neighborhood, not the actual state of a neighborhood.
It is merely used for reference by a ruleset, which takes in a series of configurations and
the next state of a cell depending on a configuration.
"""
def __init__(self, grid, next_state, offsets={}):
"""
@next_state: Represents the next state of a cell given a configuration passes.
This should be an [0|1|Function that returns 0 or 1]
@offsets: A dictionary of offsets containing N-tuple keys and [-1, 0, 1] values.
Note N must be the same dimension as the grid's dimensions, as it specifies
the offset from any given cell in the grid.
"""
self.next_state = next_state
# The grid we work with is flattened, so that we can simply access single indices (as opposed
# to N-ary tuples). This also allows for multiple index accessing via the numpy list indexing
# method
states = []
f_offsets = []
for k, v in offsets.items():
states.append(v)
f_offsets.append(util.flatten(k, grid))
self.states = np.array(states)
self.offsets = np.array(f_offsets)
def passes(self, f_index, grid, vfunc, *args):
"""
Checks if a given configuration passes, and if so, returns the next state.
@vfunc is an arbitrary function that takes in a flattened grid, a list of indices, and a list of values (which,
if zipped with indices, correspond to the expected value of the cell at the given index). The function should
merely verify that a list of indices "passes" some expectation.
For example, if an "exact match" function is passed, it should merely verify that the cells at the passed indices
exactly match the exact expectated cells in the list of values. It will return True or False depending.
"""
# We ensure all indices are within the given grid
indices = (f_index + self.offsets) % grid.size
# Note the distinction between success and next_state here; vfunc (validity function) tells whether the given
# configuration passes. If it does, no other configurations need to be checked and the next state is returned.
success = vfunc(f_index, grid.flat, indices, self.states, *args)
if callable(self.next_state):
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
class Ruleset: class Ruleset:
""" """
@ -207,7 +108,7 @@ class Ruleset:
We see that the percentage of actual matches are greater than or equal to the given tolerance level. If so, we We see that the percentage of actual matches are greater than or equal to the given tolerance level. If so, we
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.
""" """
non_matches = np.count_nonzero(f_grid[inices] ^ states) non_matches = np.count_nonzero(f_grid[indices] ^ states)
return (non_matches / len(f_grid)) >= tolerance return (non_matches / len(f_grid)) >= tolerance
def _satisfies(self, f_index, f_grid, indices, states, valid_func): def _satisfies(self, f_index, f_grid, indices, states, valid_func):