Reduced memory consumption
parent
df9e7206c2
commit
bde3658812
|
@ -15,22 +15,5 @@ if __name__ == '__main__':
|
||||||
c = cam.CAM(1, 100, 2)
|
c = cam.CAM(1, 100, 2)
|
||||||
p = cam_parser.CAMParser('B3/S23', c)
|
p = cam_parser.CAMParser('B3/S23', c)
|
||||||
|
|
||||||
#c.randomize()
|
c.randomize()
|
||||||
|
c.start(cam.CAM.Show.WINDOW, clock=50, rules=p.ruleset)
|
||||||
# Glider Gun 9x36
|
|
||||||
from bitarray import bitarray
|
|
||||||
row = [1<<11
|
|
||||||
,1<<13|1<<11
|
|
||||||
,1<<23|1<<22|1<<15|1<<14|1<<1|1<<0
|
|
||||||
,1<<24|1<<20|1<<15|1<<14|1<<1|1<<0
|
|
||||||
,1<<35|1<<34|1<<25|1<<19|1<<15|1<<14
|
|
||||||
,1<<35|1<<34|1<<25|1<<21|1<<19|1<<18|1<<13|1<<11
|
|
||||||
,1<<25|1<<19|1<<11
|
|
||||||
,1<<24|1<<20
|
|
||||||
,1<<23|1<<22
|
|
||||||
]
|
|
||||||
|
|
||||||
for i in range(9):
|
|
||||||
c.master.grid[35+i][12:48] = bitarray(bin(row[i])[2:].zfill(36))
|
|
||||||
|
|
||||||
c.start(cam.CAM.Show.CONSOLE, clock=50, rules=p.ruleset)
|
|
||||||
|
|
|
@ -1,37 +1,17 @@
|
||||||
"""
|
|
||||||
Parsers CAM languages for quick construction of CAMs.
|
|
||||||
|
|
||||||
For example, when considering CAMs of life, most follow the same formula; check
|
|
||||||
the total number of cells in a given neighborhood and if there are a certain
|
|
||||||
number around an off cell, turn it on, and vice versa. Thus the parser takes
|
|
||||||
in a generic language regarding this and constructs the necessary functions for
|
|
||||||
the user.
|
|
||||||
|
|
||||||
@date: June 4th, 2015
|
|
||||||
"""
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import ruleset as r
|
import ruleset as r
|
||||||
import configuration as c
|
import configuration as c
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
|
|
||||||
class CAMParser:
|
class CAMParser:
|
||||||
"""
|
"""
|
||||||
The following builds rulesets based on the passed string.
|
Parses CAM languages for quick construction of CAMs.
|
||||||
|
|
||||||
|
For example, when considering CAMs of life, most follow the same formula; check
|
||||||
|
the total number of cells in a given neighborhood and if there are a certain
|
||||||
|
number around an off cell, turn it on, and vice versa. Thus the parser takes
|
||||||
|
in a generic language regarding this and constructs the necessary functions for
|
||||||
|
the user.
|
||||||
|
|
||||||
Following notation is supported:
|
Following notation is supported:
|
||||||
* MCell Notation (x/y)
|
* MCell Notation (x/y)
|
||||||
|
@ -60,23 +40,22 @@ class CAMParser:
|
||||||
if all(map(self._numasc, [x, y])):
|
if all(map(self._numasc, [x, y])):
|
||||||
self.sfunc = self._mcell(x, y)
|
self.sfunc = self._mcell(x, y)
|
||||||
else:
|
else:
|
||||||
raise InvalidFormat("Non-ascending values in MCELL format")
|
raise ValueError("Non-ascending values in MCELL format")
|
||||||
|
|
||||||
elif re.match(CAMParser.RLE_FORMAT, notation):
|
elif re.match(CAMParser.RLE_FORMAT, notation):
|
||||||
B, S = map(lambda x: x[1:], notation.split('/'))
|
B, S = map(lambda x: x[1:], notation.split('/'))
|
||||||
if all(map(self._numasc, [B, S])):
|
if all(map(self._numasc, [B, S])):
|
||||||
self.sfunc = self._mcell(S, B)
|
self.sfunc = self._mcell(S, B)
|
||||||
else:
|
else:
|
||||||
raise InvalidFormat("Non-ascending values in RLE format")
|
raise ValueError("Non-ascending values in RLE format")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise InvalidFormat("No supported format passed to parser.")
|
raise ValueError("No supported format passed to parser.")
|
||||||
|
|
||||||
# Add configuration to given CAM
|
# Add configuration to given CAM
|
||||||
config = c.Configuration(self.sfunc, plane=cam.master, offsets=self.offsets)
|
config = c.Configuration(self.sfunc, plane=cam.master, offsets=self.offsets)
|
||||||
self.ruleset.configurations.append(config)
|
self.ruleset.configurations.append(config)
|
||||||
|
|
||||||
|
|
||||||
def _numasc(self, value):
|
def _numasc(self, value):
|
||||||
"""
|
"""
|
||||||
Check the given value is a string of ascending numbers.
|
Check the given value is a string of ascending numbers.
|
||||||
|
@ -86,7 +65,6 @@ class CAMParser:
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def _mcell(self, x, y):
|
def _mcell(self, x, y):
|
||||||
"""
|
"""
|
||||||
MCell Notation
|
MCell Notation
|
||||||
|
@ -99,7 +77,7 @@ class CAMParser:
|
||||||
"""
|
"""
|
||||||
x, y = list(map(int, x)), list(map(int, y))
|
x, y = list(map(int, x)), list(map(int, y))
|
||||||
def next_state(plane, neighborhood, *args):
|
def next_state(plane, neighborhood, *args):
|
||||||
if plane.grid.flat[neighborhood.flat_index][neighborhood.bit_index]:
|
if plane.bits[neighborhood.flat_index]:
|
||||||
return int(neighborhood.total in x)
|
return int(neighborhood.total in x)
|
||||||
else:
|
else:
|
||||||
return int(neighborhood.total in y)
|
return int(neighborhood.total in y)
|
||||||
|
|
|
@ -20,15 +20,15 @@ class Neighborhood:
|
||||||
perform this computation in the first place (for example, if an ALWAYS_PASS flag is passed
|
perform this computation in the first place (for example, if an ALWAYS_PASS flag is passed
|
||||||
as opposed to a MATCH flag).
|
as opposed to a MATCH flag).
|
||||||
"""
|
"""
|
||||||
def __init__(self, index):
|
def __init__(self, flat_index):
|
||||||
"""
|
"""
|
||||||
Initializes the center cell.
|
Initializes the center cell.
|
||||||
|
|
||||||
Offsetted cells belonging in the given neighborhood must be added separately.
|
Offsetted cells belonging in the given neighborhood must be added separately.
|
||||||
"""
|
"""
|
||||||
self.total = 0
|
self.total = 0
|
||||||
self.index = index
|
|
||||||
self.neighbors = bitarray()
|
self.neighbors = bitarray()
|
||||||
|
self.flat_index = flat_index
|
||||||
|
|
||||||
def populate(self, plane, offsets):
|
def populate(self, plane, offsets):
|
||||||
"""
|
"""
|
||||||
|
@ -40,8 +40,8 @@ class Neighborhood:
|
||||||
"""
|
"""
|
||||||
self.neighbors = bitarray()
|
self.neighbors = bitarray()
|
||||||
for offset in offsets:
|
for offset in offsets:
|
||||||
f_index = plane.flatten(offset) + self.index
|
index = plane.flatten(offset) + self.flat_index
|
||||||
self.neighbors.append(plane.bits[f_index % len(plane.bits)])
|
self.neighbors.append(plane.bits[index % len(plane.bits)])
|
||||||
|
|
||||||
self.total = len(self.neighbors)
|
self.total = len(self.neighbors)
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,7 @@
|
||||||
"""
|
|
||||||
Allow for displaying 2D/3D CAMs.
|
|
||||||
|
|
||||||
Two means of viewing the CAM are provided: either through the console via the curses
|
|
||||||
library or through a GUI display via the matplotlib library. Note the console display
|
|
||||||
only supports 2D CAMs while the GUI supports 2D/3D automata.
|
|
||||||
|
|
||||||
Both methods allow for the ability to display multiple cell planes at a time, with
|
|
||||||
additional support for ECHOs and TRACing.
|
|
||||||
|
|
||||||
@date: June 16th, 2015
|
|
||||||
"""
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import curses
|
import curses
|
||||||
import itertools
|
import itertools
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import matplotlib.pyplot as plt
|
import matplotlib.pyplot as plt
|
||||||
import matplotlib.animation as ani
|
import matplotlib.animation as ani
|
||||||
|
@ -22,7 +9,14 @@ import matplotlib.animation as ani
|
||||||
|
|
||||||
class _Display:
|
class _Display:
|
||||||
"""
|
"""
|
||||||
The base class for visualization of the CAM.
|
Allow for displaying 2D/3D CAMs.
|
||||||
|
|
||||||
|
Two means of viewing the CAM are provided: either through the console via the curses
|
||||||
|
library or through a GUI display via the matplotlib library. Note the console display
|
||||||
|
only supports 2D CAMs while the GUI supports 2D/3D automata.
|
||||||
|
|
||||||
|
Both methods allow for the ability to display multiple cell planes at a time, with
|
||||||
|
additional support for ECHOs and TRACing.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, cam, clock, rules, *args):
|
def __init__(self, cam, clock, rules, *args):
|
||||||
|
@ -87,7 +81,7 @@ class ConsoleDisplay(_Display):
|
||||||
|
|
||||||
# Construct the necessary planes
|
# Construct the necessary planes
|
||||||
self.overlays = []
|
self.overlays = []
|
||||||
for i, pl in enumerate(self.cam.planes):
|
for i, plane in enumerate(self.cam.planes):
|
||||||
pad = curses.newpad(self.width+1, self.height+1)
|
pad = curses.newpad(self.width+1, self.height+1)
|
||||||
self.overlays.append(pad)
|
self.overlays.append(pad)
|
||||||
if i > 0:
|
if i > 0:
|
||||||
|
@ -116,7 +110,7 @@ class ConsoleDisplay(_Display):
|
||||||
elif ch == curses.KEY_RIGHT:
|
elif ch == curses.KEY_RIGHT:
|
||||||
self.x = (self.x - 1) % self.width
|
self.x = (self.x - 1) % self.width
|
||||||
|
|
||||||
def _draw_overlay(self, overlay, pl):
|
def _draw_overlay(self, overlay, plane):
|
||||||
"""
|
"""
|
||||||
Draw the grid onto the overlay.
|
Draw the grid onto the overlay.
|
||||||
|
|
||||||
|
@ -126,18 +120,22 @@ class ConsoleDisplay(_Display):
|
||||||
overlay.clear()
|
overlay.clear()
|
||||||
|
|
||||||
line = 0
|
line = 0
|
||||||
grid = np.append(pl.grid[self.y:], pl.grid[:self.y])
|
for i in range(plane.shape[0]):
|
||||||
for bits in grid.flat:
|
|
||||||
overlay.move(line, 0)
|
overlay.move(line, 0)
|
||||||
line += 1
|
line += 1
|
||||||
|
|
||||||
# We go through and group the bits apart so
|
# Make sure to account for movement
|
||||||
|
y_offset = ((i + self.y) % plane.shape[0]) * plane.shape[1]
|
||||||
|
bits = plane.bits[y_offset:y_offset+plane.shape[1]]
|
||||||
cycle = bits[self.x:] + bits[:self.x]
|
cycle = bits[self.x:] + bits[:self.x]
|
||||||
|
|
||||||
|
# Draw only active states
|
||||||
for k, g in itertools.groupby(cycle):
|
for k, g in itertools.groupby(cycle):
|
||||||
values = list(g)
|
values = list(g)
|
||||||
if any(values):
|
if any(values):
|
||||||
overlay.addstr('+' * len(values))
|
overlay.addstr('+' * len(values))
|
||||||
else:
|
else:
|
||||||
|
pass
|
||||||
y, x = overlay.getyx()
|
y, x = overlay.getyx()
|
||||||
overlay.move(y, x + len(values))
|
overlay.move(y, x + len(values))
|
||||||
|
|
||||||
|
@ -161,10 +159,10 @@ class ConsoleDisplay(_Display):
|
||||||
self._shift(self.stdscr.getch())
|
self._shift(self.stdscr.getch())
|
||||||
|
|
||||||
# Cycle around grid
|
# Cycle around grid
|
||||||
for i, pl in enumerate(self.cam.planes):
|
for i, plane in enumerate(self.cam.planes):
|
||||||
if pl.dirty:
|
if plane.dirty:
|
||||||
pl.dirty = False
|
plane.dirty = False
|
||||||
self._draw_overlay(self.overlays[i], pl)
|
self._draw_overlay(self.overlays[i], plane)
|
||||||
self.overlays[i].noutrefresh(0, 0, 0, 0, max_y-1, max_x-1)
|
self.overlays[i].noutrefresh(0, 0, 0, 0, max_y-1, max_x-1)
|
||||||
|
|
||||||
# Prepare for next loop
|
# Prepare for next loop
|
||||||
|
@ -203,7 +201,7 @@ class WindowDisplay(_Display):
|
||||||
# for proper superimposition
|
# for proper superimposition
|
||||||
self.matrices = []
|
self.matrices = []
|
||||||
for plane in self.cam.planes:
|
for plane in self.cam.planes:
|
||||||
mshown = plt.matshow(plane.bits(), self.fig.number, cmap='Greys')
|
mshown = plt.matshow(plane.matrix(), self.fig.number, cmap='Greys')
|
||||||
self.matrices.append(mshown)
|
self.matrices.append(mshown)
|
||||||
|
|
||||||
def _valid(self):
|
def _valid(self):
|
||||||
|
@ -223,7 +221,7 @@ class WindowDisplay(_Display):
|
||||||
"""
|
"""
|
||||||
self.cam.tick(self.rules, *self.tick_args)
|
self.cam.tick(self.rules, *self.tick_args)
|
||||||
if len(self.cam.master.shape) == 2:
|
if len(self.cam.master.shape) == 2:
|
||||||
self.matrices[0].set_array(self.cam.master.bits())
|
self.matrices[0].set_array(self.cam.master.matrix())
|
||||||
return [self.matrices[0]]
|
return [self.matrices[0]]
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -147,9 +147,9 @@ class Plane:
|
||||||
method below.
|
method below.
|
||||||
"""
|
"""
|
||||||
if self.N > 0:
|
if self.N > 0:
|
||||||
max_unsigned = reduce(operator.mul, self.shape, 1)
|
bit_count = reduce(operator.mul, self.shape, 1)
|
||||||
sequence = bin(random.randrange(0, max_unsigned))[2:]
|
sequence = bin(random.randrange(0, 2**bit_count-1))[2:]
|
||||||
self.bits = bitarray(sequence.zfill(max_unsigned))
|
self.bits = bitarray(sequence.zfill(bit_count))
|
||||||
|
|
||||||
def flatten(self, coordinates):
|
def flatten(self, coordinates):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,11 +1,3 @@
|
||||||
"""
|
|
||||||
The following determines the next state of a given cell in a CAM.
|
|
||||||
|
|
||||||
The ruleset takes in a collection of rules specifying neighborhoods, as well as the configurations of
|
|
||||||
said neighborhood that yield an "on" or "off" state on the cell a ruleset is being applied to.
|
|
||||||
|
|
||||||
@date: May 31st, 2015
|
|
||||||
"""
|
|
||||||
import enum
|
import enum
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import configuration as c
|
import configuration as c
|
||||||
|
@ -75,21 +67,22 @@ class Ruleset:
|
||||||
# which states do not pass for each configuration
|
# which states do not pass for each configuration
|
||||||
current_states = enumerate(plane.bits)
|
current_states = enumerate(plane.bits)
|
||||||
for config in self.configurations:
|
for config in self.configurations:
|
||||||
|
|
||||||
totals = c.Neighborhood.get_totals(plane, config.offsets)
|
totals = c.Neighborhood.get_totals(plane, config.offsets)
|
||||||
|
|
||||||
|
# Determine which function should be used to test success
|
||||||
|
if self.method == Ruleset.Method.MATCH:
|
||||||
|
vfunc = config.matches
|
||||||
|
elif self.method == Ruleset.Method.TOLERATE:
|
||||||
|
vfunc = config.tolerates
|
||||||
|
elif self.method == Ruleset.Method.SATISFY:
|
||||||
|
vfunc = config.satisfies
|
||||||
|
elif self.method == Ruleset.Method.ALWAYS_PASS:
|
||||||
|
vfunc = lambda *args: True
|
||||||
|
|
||||||
next_states = []
|
next_states = []
|
||||||
for index, state in current_states:
|
for index, state in current_states:
|
||||||
|
|
||||||
# Determine which function should be used to test success
|
|
||||||
if self.method == Ruleset.Method.MATCH:
|
|
||||||
vfunc = config.matches
|
|
||||||
elif self.method == Ruleset.Method.TOLERATE:
|
|
||||||
vfunc = config.tolerates
|
|
||||||
elif self.method == Ruleset.Method.SATISFY:
|
|
||||||
vfunc = config.satisfies
|
|
||||||
elif self.method == Ruleset.Method.ALWAYS_PASS:
|
|
||||||
vfunc = lambda *args: True
|
|
||||||
|
|
||||||
# Passes a mostly uninitialized neighborhood to the given function
|
# Passes a mostly uninitialized neighborhood to the given function
|
||||||
# Note if you need actual states of the neighborhood, make sure to
|
# Note if you need actual states of the neighborhood, make sure to
|
||||||
# call the neighborhood's populate function
|
# call the neighborhood's populate function
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import os, sys
|
||||||
|
sys.path.insert(0, os.path.join('..', 'src'))
|
||||||
|
|
||||||
|
import plane
|
||||||
|
import ruleset as r
|
||||||
|
import configuration as c
|
||||||
|
|
||||||
|
class TestRuleset:
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.plane2d = plane.Plane((100, 100))
|
||||||
|
self.config = c.Configuration(1, plane=self.plane2d, offsets={
|
||||||
|
(-1, 0): 1,
|
||||||
|
(-1, 1): 0,
|
||||||
|
( 0, 1): 1,
|
||||||
|
( 1, 1): 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_alwaysPassRuleset(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
# No configurations
|
||||||
|
tmp_r = r.Ruleset(r.Ruleset.Method.ALWAYS_PASS)
|
||||||
|
tmp_p = self.plane2d.bits.copy()
|
||||||
|
tmp_r.apply_to(self.plane2d)
|
||||||
|
assert tmp_p == self.plane2d.bits
|
||||||
|
|
||||||
|
# One configuration
|
||||||
|
tmp_r.configurations.append(self.config)
|
||||||
|
tmp_r.apply_to(self.plane2d)
|
||||||
|
assert tmp_p != self.plane2d.bits
|
||||||
|
assert self.plane2d.bits.count() == 100 * 100
|
||||||
|
|
||||||
|
def test_matchRuleset(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
# No configurations
|
||||||
|
tmp_r = r.Ruleset(r.Ruleset.Method.MATCH)
|
||||||
|
tmp_p = self.plane2d.bits.copy()
|
||||||
|
tmp_r.apply_to(self.plane2d)
|
||||||
|
assert tmp_p == self.plane2d.bits
|
||||||
|
|
||||||
|
# One configuration
|
||||||
|
tmp_r.configurations.append(self.config)
|
||||||
|
tmp_r.apply_to(self.plane2d)
|
||||||
|
assert tmp_p == self.plane2d.bits
|
||||||
|
self.plane2d[[(-1, 0), (0, 1), (1, 1)]] = 1
|
||||||
|
tmp_r.apply_to(self.plane2d)
|
||||||
|
assert self.plane2d.bits.count() == 4
|
||||||
|
|
||||||
|
def test_tolerateRuleset(self):
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
# No configurations
|
||||||
|
tmp_r = r.Ruleset(r.Ruleset.Method.TOLERATE)
|
||||||
|
tmp_p = self.plane2d.bits.copy()
|
||||||
|
tmp_r.apply_to(self.plane2d)
|
||||||
|
assert tmp_p == self.plane2d.bits
|
||||||
|
|
||||||
|
# One configuration
|
||||||
|
tmp_r.configurations.append(self.config)
|
||||||
|
tmp_r.apply_to(self.plane2d, 0.5)
|
||||||
|
assert tmp_p == self.plane2d.bits
|
||||||
|
|
||||||
|
self.plane2d[(-1, 0)] = 1
|
||||||
|
tmp_r.apply_to(self.plane2d, 0.5)
|
||||||
|
assert self.plane2d.bits.count() == 4
|
||||||
|
|
Loading…
Reference in New Issue