Created parser for generic life automata
parent
ead7725eb1
commit
bde75c012e
|
@ -10,26 +10,11 @@ if __name__ == '__main__':
|
||||||
sys.path.append(os.path.abspath('src'))
|
sys.path.append(os.path.abspath('src'))
|
||||||
|
|
||||||
import cam
|
import cam
|
||||||
|
import util as u
|
||||||
import ruleset as rs
|
import ruleset as rs
|
||||||
|
|
||||||
|
|
||||||
def high_life(f_index, f_grid, indices, states, *args):
|
|
||||||
total = sum(f_grid[indices])
|
|
||||||
if not f_grid[f_index]:
|
|
||||||
if total == 3 or total == 6 or total == 8:
|
|
||||||
return rs.Configuration.OFF
|
|
||||||
else:
|
|
||||||
if total == 2 or total == 3:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
|
|
||||||
return rs.Configuration.OFF
|
|
||||||
|
|
||||||
|
|
||||||
c = cam.CAM(1, 100, 2)
|
c = cam.CAM(1, 100, 2)
|
||||||
|
p = u.CAMParser('B368/S23', c)
|
||||||
|
|
||||||
c.randomize()
|
c.randomize()
|
||||||
|
c.start_plot(100, p.ruleset)
|
||||||
r = rs.Ruleset(rs.Ruleset.Method.SATISFY)
|
|
||||||
offsets = rs.Configuration.moore(c.master)
|
|
||||||
r.addConfiguration(c.master, high_life, offsets)
|
|
||||||
|
|
||||||
c.start_plot(100, r, lambda *args: True)
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""
|
"""
|
||||||
B3/S34: Game of Life
|
B3/S23: Game of Life
|
||||||
|
|
||||||
@author: jrpotter
|
@author: jrpotter
|
||||||
@date: June 01, 2015
|
@date: June 01, 2015
|
||||||
|
@ -10,33 +10,11 @@ if __name__ == '__main__':
|
||||||
sys.path.append(os.path.abspath('src'))
|
sys.path.append(os.path.abspath('src'))
|
||||||
|
|
||||||
import cam
|
import cam
|
||||||
|
import util as u
|
||||||
import ruleset as rs
|
import ruleset as rs
|
||||||
|
|
||||||
|
|
||||||
def game_of_life(f_index, f_grid, indices, states, *args):
|
|
||||||
"""
|
|
||||||
Rules of the Game of Life.
|
|
||||||
|
|
||||||
Note we ignore the second component of the neighbors tuples since
|
|
||||||
life depends on all neighbors
|
|
||||||
"""
|
|
||||||
total = sum(f_grid[indices])
|
|
||||||
if f_grid[f_index]:
|
|
||||||
if total < 2 or total > 3:
|
|
||||||
return rs.Configuration.OFF
|
|
||||||
else:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
elif total == 3:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
else:
|
|
||||||
return rs.Configuration.OFF
|
|
||||||
|
|
||||||
|
|
||||||
c = cam.CAM(1, 100, 2)
|
c = cam.CAM(1, 100, 2)
|
||||||
|
p = u.CAMParser('B3/S23', c)
|
||||||
|
|
||||||
c.randomize()
|
c.randomize()
|
||||||
|
c.start_plot(100, p.ruleset)
|
||||||
r = rs.Ruleset(rs.Ruleset.Method.SATISFY)
|
|
||||||
offsets = rs.Configuration.moore(c.master)
|
|
||||||
r.addConfiguration(c.master, game_of_life, offsets)
|
|
||||||
|
|
||||||
c.start_plot(100, r, lambda *args: True)
|
|
||||||
|
|
|
@ -10,22 +10,11 @@ if __name__ == '__main__':
|
||||||
sys.path.append(os.path.abspath('src'))
|
sys.path.append(os.path.abspath('src'))
|
||||||
|
|
||||||
import cam
|
import cam
|
||||||
|
import util as u
|
||||||
import ruleset as rs
|
import ruleset as rs
|
||||||
|
|
||||||
|
|
||||||
def lwd(f_index, f_grid, indices, states, *args):
|
|
||||||
total = sum(f_grid[indices])
|
|
||||||
if not f_grid[f_index] and total == 3:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
else:
|
|
||||||
return f_grid[f_index]
|
|
||||||
|
|
||||||
|
|
||||||
c = cam.CAM(1, 100, 2)
|
c = cam.CAM(1, 100, 2)
|
||||||
|
p = u.CAMParser('B3/S012345678', c)
|
||||||
|
|
||||||
c.randomize()
|
c.randomize()
|
||||||
|
c.start_plot(100, p.ruleset)
|
||||||
r = rs.Ruleset(rs.Ruleset.Method.SATISFY)
|
|
||||||
offsets = rs.Configuration.moore(c.master)
|
|
||||||
r.addConfiguration(c.master, lwd, offsets)
|
|
||||||
|
|
||||||
c.start_plot(100, r, lambda *args: True)
|
|
||||||
|
|
|
@ -10,26 +10,11 @@ if __name__ == '__main__':
|
||||||
sys.path.append(os.path.abspath('src'))
|
sys.path.append(os.path.abspath('src'))
|
||||||
|
|
||||||
import cam
|
import cam
|
||||||
|
import util as u
|
||||||
import ruleset as rs
|
import ruleset as rs
|
||||||
|
|
||||||
|
|
||||||
def morley(f_index, f_grid, indices, states, *args):
|
|
||||||
total = sum(f_grid[indices])
|
|
||||||
if not f_grid[f_index]:
|
|
||||||
if total == 3 or total == 6 or total == 8:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
else:
|
|
||||||
if total == 2 or total == 4 or total == 5:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
|
|
||||||
return rs.Configuration.OFF
|
|
||||||
|
|
||||||
|
|
||||||
c = cam.CAM(1, 100, 2)
|
c = cam.CAM(1, 100, 2)
|
||||||
|
p = u.CAMParser('B368/S245', c)
|
||||||
|
|
||||||
c.randomize()
|
c.randomize()
|
||||||
|
c.start_plot(100, p.ruleset)
|
||||||
r = rs.Ruleset(rs.Ruleset.Method.SATISFY)
|
|
||||||
offsets = rs.Configuration.moore(c.master)
|
|
||||||
r.addConfiguration(c.master, morley, offsets)
|
|
||||||
|
|
||||||
c.start_plot(100, r, lambda *args: True)
|
|
||||||
|
|
|
@ -10,26 +10,11 @@ if __name__ == '__main__':
|
||||||
sys.path.append(os.path.abspath('src'))
|
sys.path.append(os.path.abspath('src'))
|
||||||
|
|
||||||
import cam
|
import cam
|
||||||
|
import util as u
|
||||||
import ruleset as rs
|
import ruleset as rs
|
||||||
|
|
||||||
|
|
||||||
def replicator(f_index, f_grid, indices, states, *args):
|
|
||||||
total = sum(f_grid[indices])
|
|
||||||
if not f_grid[f_index]:
|
|
||||||
if total % 2 == 1:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
else:
|
|
||||||
if total % 2 == 1:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
|
|
||||||
return rs.Configuration.OFF
|
|
||||||
|
|
||||||
|
|
||||||
c = cam.CAM(1, 100, 2)
|
c = cam.CAM(1, 100, 2)
|
||||||
|
p = u.CAMParser('B1357/S1357', c)
|
||||||
|
|
||||||
c.randomize()
|
c.randomize()
|
||||||
|
c.start_plot(100, p.ruleset)
|
||||||
r = rs.Ruleset(rs.Ruleset.Method.SATISFY)
|
|
||||||
offsets = rs.Configuration.moore(c.master)
|
|
||||||
r.addConfiguration(c.master, replicator, offsets)
|
|
||||||
|
|
||||||
c.start_plot(100, r, lambda *args: True)
|
|
||||||
|
|
|
@ -10,22 +10,11 @@ if __name__ == '__main__':
|
||||||
sys.path.append(os.path.abspath('src'))
|
sys.path.append(os.path.abspath('src'))
|
||||||
|
|
||||||
import cam
|
import cam
|
||||||
|
import cam_util as u
|
||||||
import ruleset as rs
|
import ruleset as rs
|
||||||
|
|
||||||
|
|
||||||
def seeds(f_index, f_grid, indices, states, *args):
|
|
||||||
total = sum(f_grid[indices])
|
|
||||||
if not f_grid[f_index] and total == 2:
|
|
||||||
return rs.Configuration.ON
|
|
||||||
else:
|
|
||||||
return rs.Configuration.OFF
|
|
||||||
|
|
||||||
|
|
||||||
c = cam.CAM(1, 100, 2)
|
c = cam.CAM(1, 100, 2)
|
||||||
|
p = u.CAMParser('B2/S', c)
|
||||||
|
|
||||||
c.randomize()
|
c.randomize()
|
||||||
|
c.start_plot(100, p.ruleset)
|
||||||
r = rs.Ruleset(rs.Ruleset.Method.SATISFY)
|
|
||||||
offsets = rs.Configuration.moore(c.master)
|
|
||||||
r.addConfiguration(c.master, seeds, offsets)
|
|
||||||
|
|
||||||
c.start_plot(100, r, lambda *args: True)
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@author: jrpotter
|
||||||
|
@date: June 4th, 2015
|
||||||
|
"""
|
||||||
|
|
||||||
|
class InvalidFormat(Exception):
|
||||||
|
"""
|
||||||
|
Called when parsing an invalid format.
|
||||||
|
|
||||||
|
For example, in MCell and RLE, numbers should be in ascending order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, value):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
return repr(self.value)
|
|
@ -12,22 +12,7 @@ import itertools as it
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
import util
|
||||||
|
|
||||||
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 Configuration:
|
class Configuration:
|
||||||
|
@ -39,20 +24,11 @@ class Configuration:
|
||||||
the next state of a cell depending on a configuration.
|
the next state of a cell depending on a configuration.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Possible states a cell can take
|
|
||||||
#
|
|
||||||
# If a configuration passes, the cell's state will be on or off if ON or OFF was passed respectively.
|
|
||||||
# If IGNORE, then the state remains the same, but no further configurations will be checked by the
|
|
||||||
# ruleset.
|
|
||||||
ON = 1
|
|
||||||
OFF = 0
|
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, grid, next_state, offsets={}):
|
def __init__(self, grid, next_state, offsets={}):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@next_state: Represents the next state of a cell given a configuration passes.
|
@next_state: Represents the next state of a cell given a configuration passes.
|
||||||
This should be an [ON|OFF|Function that returns ON or Off]
|
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.
|
@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
|
Note N must be the same dimension as the grid's dimensions, as it specifies
|
||||||
|
@ -68,12 +44,11 @@ class Configuration:
|
||||||
f_offsets = []
|
f_offsets = []
|
||||||
for k, v in offsets.items():
|
for k, v in offsets.items():
|
||||||
states.append(v)
|
states.append(v)
|
||||||
f_offsets.append(flatten(k, grid))
|
f_offsets.append(util.flatten(k, grid))
|
||||||
|
|
||||||
self.states = np.array(states)
|
self.states = np.array(states)
|
||||||
self.offsets = np.array(f_offsets)
|
self.offsets = np.array(f_offsets)
|
||||||
|
|
||||||
|
|
||||||
def passes(self, f_index, grid, vfunc, *args):
|
def passes(self, f_index, grid, vfunc, *args):
|
||||||
"""
|
"""
|
||||||
Checks if a given configuration passes, and if so, returns the next state.
|
Checks if a given configuration passes, and if so, returns the next state.
|
||||||
|
@ -96,9 +71,8 @@ class Configuration:
|
||||||
else:
|
else:
|
||||||
return (success, self.next_state)
|
return (success, self.next_state)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def moore(cls, grid, value=ON):
|
def moore(cls, grid, value=1):
|
||||||
"""
|
"""
|
||||||
Returns a neighborhood corresponding to the Moore neighborhood.
|
Returns a neighborhood corresponding to the Moore neighborhood.
|
||||||
|
|
||||||
|
@ -116,9 +90,8 @@ class Configuration:
|
||||||
|
|
||||||
return offsets
|
return offsets
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def neumann(cls, grid, value=ON):
|
def neumann(cls, grid, value=1):
|
||||||
"""
|
"""
|
||||||
Returns a neighborhood corresponding to the Von Neumann neighborhood.
|
Returns a neighborhood corresponding to the Von Neumann neighborhood.
|
||||||
|
|
||||||
|
@ -139,7 +112,6 @@ class Configuration:
|
||||||
return offsets
|
return offsets
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Ruleset:
|
class Ruleset:
|
||||||
"""
|
"""
|
||||||
The primary class of this module, which saves configurations of cells that yield the next state.
|
The primary class of this module, which saves configurations of cells that yield the next state.
|
||||||
|
@ -171,7 +143,7 @@ class Ruleset:
|
||||||
MATCH = 0
|
MATCH = 0
|
||||||
TOLERATE = 1
|
TOLERATE = 1
|
||||||
SATISFY = 2
|
SATISFY = 2
|
||||||
|
ALWAYS_PASS = 3
|
||||||
|
|
||||||
def __init__(self, method):
|
def __init__(self, method):
|
||||||
"""
|
"""
|
||||||
|
@ -181,7 +153,6 @@ class Ruleset:
|
||||||
self.method = method
|
self.method = method
|
||||||
self.configurations = []
|
self.configurations = []
|
||||||
|
|
||||||
|
|
||||||
def addConfiguration(self, grid, next_state, offsets):
|
def addConfiguration(self, grid, next_state, offsets):
|
||||||
"""
|
"""
|
||||||
Creates a configuration and saves said configuration.
|
Creates a configuration and saves said configuration.
|
||||||
|
@ -189,7 +160,6 @@ class Ruleset:
|
||||||
config = Configuration(grid, next_state, offsets)
|
config = Configuration(grid, next_state, offsets)
|
||||||
self.configurations.append(config)
|
self.configurations.append(config)
|
||||||
|
|
||||||
|
|
||||||
def applyTo(self, f_index, grid, *args):
|
def applyTo(self, f_index, grid, *args):
|
||||||
"""
|
"""
|
||||||
Depending on a given method, applies ruleset to a cell.
|
Depending on a given method, applies ruleset to a cell.
|
||||||
|
@ -213,6 +183,8 @@ class Ruleset:
|
||||||
vfunc = self._tolerates
|
vfunc = self._tolerates
|
||||||
elif self.method == Ruleset.Method.SATISFY:
|
elif self.method == Ruleset.Method.SATISFY:
|
||||||
vfunc = self._satisfies
|
vfunc = self._satisfies
|
||||||
|
elif self.method == Ruleset.Method.ALWAYS_PASS:
|
||||||
|
vfunc = lambda *args: True
|
||||||
|
|
||||||
# Apply the function if possible
|
# Apply the function if possible
|
||||||
if vfunc is not None:
|
if vfunc is not None:
|
||||||
|
@ -224,7 +196,6 @@ class Ruleset:
|
||||||
|
|
||||||
return grid.flat[f_index]
|
return grid.flat[f_index]
|
||||||
|
|
||||||
|
|
||||||
def _matches(self, f_index, f_grid, indices, states):
|
def _matches(self, f_index, f_grid, indices, states):
|
||||||
"""
|
"""
|
||||||
Determines that neighborhood matches expectation exactly.
|
Determines that neighborhood matches expectation exactly.
|
||||||
|
@ -233,7 +204,6 @@ class Ruleset:
|
||||||
"""
|
"""
|
||||||
return not np.count_nonzero(f_grid[indices] ^ states)
|
return not np.count_nonzero(f_grid[indices] ^ states)
|
||||||
|
|
||||||
|
|
||||||
def _tolerates(self, f_index, f_grid, indices, states, tolerance):
|
def _tolerates(self, f_index, f_grid, indices, states, tolerance):
|
||||||
"""
|
"""
|
||||||
Determines that neighborhood matches expectation within tolerance.
|
Determines that neighborhood matches expectation within tolerance.
|
||||||
|
@ -244,7 +214,6 @@ class Ruleset:
|
||||||
non_matches = np.count_nonzero(f_grid[inices] ^ states)
|
non_matches = np.count_nonzero(f_grid[inices] ^ 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):
|
||||||
"""
|
"""
|
||||||
Allows custom function to relay next state of given cell.
|
Allows custom function to relay next state of given cell.
|
||||||
|
@ -254,4 +223,3 @@ class Ruleset:
|
||||||
"""
|
"""
|
||||||
return valid_func(f_index, f_grid, indices, states)
|
return valid_func(f_index, f_grid, indices, states)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
"""
|
||||||
|
A collection of utilities that can ease construction of CAMs.
|
||||||
|
|
||||||
|
@author: jrpotter
|
||||||
|
@date: June 4th, 2015
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
|
||||||
|
import ruleset as rs
|
||||||
|
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:
|
||||||
|
"""
|
||||||
|
The following builds rulesets based on the passed string.
|
||||||
|
|
||||||
|
Following notation is supported:
|
||||||
|
* MCell Notation (x/y)
|
||||||
|
* RLE Format (By/Sx)
|
||||||
|
|
||||||
|
For reference: http://en.wikipedia.org/wiki/Life-like_cellular_automaton
|
||||||
|
"""
|
||||||
|
|
||||||
|
RLE_FORMAT = r'B\d*/S\d*$'
|
||||||
|
MCELL_FORMAT = r'\d*/\d*$'
|
||||||
|
|
||||||
|
def __init__(self, notation, cam):
|
||||||
|
"""
|
||||||
|
Parses the passed notation and saves values into members.
|
||||||
|
|
||||||
|
@sfunc: Represents the function that returns the next given state.
|
||||||
|
@ruleset: A created ruleset that matches always
|
||||||
|
@offsets: Represents the Moore neighborhood corresponding to the given CAM
|
||||||
|
"""
|
||||||
|
self.sfunc = None
|
||||||
|
self.offsets = rs.Configuration.moore(cam.master)
|
||||||
|
self.ruleset = rs.Ruleset(rs.Ruleset.Method.ALWAYS_PASS)
|
||||||
|
|
||||||
|
if re.match(CAMParser.MCELL_FORMAT, notation):
|
||||||
|
x, y = notation.split('/')
|
||||||
|
if all(map(self._numasc, [x, y])):
|
||||||
|
self.sfunc = self._mcell(x, y)
|
||||||
|
else:
|
||||||
|
raise ce.InvalidFormat("Non-ascending values in MCELL format")
|
||||||
|
|
||||||
|
elif re.match(CAMParser.RLE_FORMAT, notation):
|
||||||
|
B, S = map(lambda x: x[1:], notation.split('/'))
|
||||||
|
if all(map(self._numasc, [B, S])):
|
||||||
|
self.sfunc = self._mcell(S, B)
|
||||||
|
else:
|
||||||
|
raise ce.InvalidFormat("Non-ascending values in RLE format")
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ce.InvalidFormat("No supported format passed to parser.")
|
||||||
|
|
||||||
|
# Add configuration to given CAM
|
||||||
|
self.ruleset.addConfiguration(cam.master, self.sfunc, self.offsets)
|
||||||
|
|
||||||
|
def _numasc(self, value):
|
||||||
|
"""
|
||||||
|
Check the given value is a string of ascending numbers.
|
||||||
|
"""
|
||||||
|
if all(map(str.isnumeric, value)):
|
||||||
|
return ''.join(sorted(value)) == value
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _mcell(self, x, y):
|
||||||
|
"""
|
||||||
|
MCell Notation
|
||||||
|
|
||||||
|
A rule is written as a string x/y where each of x and y is a sequence of distinct digits from 0 to 8, in
|
||||||
|
numerical order. The presence of a digit d in the x string means that a live cell with d live neighbors
|
||||||
|
survives into the next generation of the pattern, and the presence of d in the y string means that a dead
|
||||||
|
cell with d live neighbors becomes alive in the next generation. For instance, in this notation,
|
||||||
|
Conway's Game of Life is denoted 23/3
|
||||||
|
"""
|
||||||
|
x, y = list(map(int, x)), list(map(int, y))
|
||||||
|
def next_state(f_index, f_grid, indices, states, *args):
|
||||||
|
total = sum(f_grid[indices])
|
||||||
|
if f_grid[f_index]:
|
||||||
|
return int(total in x)
|
||||||
|
else:
|
||||||
|
return int(total in y)
|
||||||
|
|
||||||
|
return next_state
|
||||||
|
|
Loading…
Reference in New Issue