Reduced memory consumption
parent
df9e7206c2
commit
bde3658812
|
@ -15,22 +15,5 @@ if __name__ == '__main__':
|
|||
c = cam.CAM(1, 100, 2)
|
||||
p = cam_parser.CAMParser('B3/S23', c)
|
||||
|
||||
#c.randomize()
|
||||
|
||||
# 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)
|
||||
c.randomize()
|
||||
c.start(cam.CAM.Show.WINDOW, 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 ruleset as r
|
||||
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:
|
||||
"""
|
||||
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:
|
||||
* MCell Notation (x/y)
|
||||
|
@ -60,23 +40,22 @@ class CAMParser:
|
|||
if all(map(self._numasc, [x, y])):
|
||||
self.sfunc = self._mcell(x, y)
|
||||
else:
|
||||
raise InvalidFormat("Non-ascending values in MCELL format")
|
||||
raise ValueError("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 InvalidFormat("Non-ascending values in RLE format")
|
||||
raise ValueError("Non-ascending values in RLE format")
|
||||
|
||||
else:
|
||||
raise InvalidFormat("No supported format passed to parser.")
|
||||
raise ValueError("No supported format passed to parser.")
|
||||
|
||||
# Add configuration to given CAM
|
||||
config = c.Configuration(self.sfunc, plane=cam.master, offsets=self.offsets)
|
||||
self.ruleset.configurations.append(config)
|
||||
|
||||
|
||||
def _numasc(self, value):
|
||||
"""
|
||||
Check the given value is a string of ascending numbers.
|
||||
|
@ -86,7 +65,6 @@ class CAMParser:
|
|||
else:
|
||||
return False
|
||||
|
||||
|
||||
def _mcell(self, x, y):
|
||||
"""
|
||||
MCell Notation
|
||||
|
@ -99,7 +77,7 @@ class CAMParser:
|
|||
"""
|
||||
x, y = list(map(int, x)), list(map(int, y))
|
||||
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)
|
||||
else:
|
||||
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
|
||||
as opposed to a MATCH flag).
|
||||
"""
|
||||
def __init__(self, index):
|
||||
def __init__(self, flat_index):
|
||||
"""
|
||||
Initializes the center cell.
|
||||
|
||||
Offsetted cells belonging in the given neighborhood must be added separately.
|
||||
"""
|
||||
self.total = 0
|
||||
self.index = index
|
||||
self.neighbors = bitarray()
|
||||
self.flat_index = flat_index
|
||||
|
||||
def populate(self, plane, offsets):
|
||||
"""
|
||||
|
@ -40,8 +40,8 @@ class Neighborhood:
|
|||
"""
|
||||
self.neighbors = bitarray()
|
||||
for offset in offsets:
|
||||
f_index = plane.flatten(offset) + self.index
|
||||
self.neighbors.append(plane.bits[f_index % len(plane.bits)])
|
||||
index = plane.flatten(offset) + self.flat_index
|
||||
self.neighbors.append(plane.bits[index % len(plane.bits)])
|
||||
|
||||
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 time
|
||||
import curses
|
||||
import itertools
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.animation as ani
|
||||
|
@ -22,7 +9,14 @@ import matplotlib.animation as ani
|
|||
|
||||
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):
|
||||
|
@ -87,7 +81,7 @@ class ConsoleDisplay(_Display):
|
|||
|
||||
# Construct the necessary planes
|
||||
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)
|
||||
self.overlays.append(pad)
|
||||
if i > 0:
|
||||
|
@ -116,7 +110,7 @@ class ConsoleDisplay(_Display):
|
|||
elif ch == curses.KEY_RIGHT:
|
||||
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.
|
||||
|
||||
|
@ -126,18 +120,22 @@ class ConsoleDisplay(_Display):
|
|||
overlay.clear()
|
||||
|
||||
line = 0
|
||||
grid = np.append(pl.grid[self.y:], pl.grid[:self.y])
|
||||
for bits in grid.flat:
|
||||
for i in range(plane.shape[0]):
|
||||
overlay.move(line, 0)
|
||||
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]
|
||||
|
||||
# Draw only active states
|
||||
for k, g in itertools.groupby(cycle):
|
||||
values = list(g)
|
||||
if any(values):
|
||||
overlay.addstr('+' * len(values))
|
||||
else:
|
||||
pass
|
||||
y, x = overlay.getyx()
|
||||
overlay.move(y, x + len(values))
|
||||
|
||||
|
@ -161,10 +159,10 @@ class ConsoleDisplay(_Display):
|
|||
self._shift(self.stdscr.getch())
|
||||
|
||||
# Cycle around grid
|
||||
for i, pl in enumerate(self.cam.planes):
|
||||
if pl.dirty:
|
||||
pl.dirty = False
|
||||
self._draw_overlay(self.overlays[i], pl)
|
||||
for i, plane in enumerate(self.cam.planes):
|
||||
if plane.dirty:
|
||||
plane.dirty = False
|
||||
self._draw_overlay(self.overlays[i], plane)
|
||||
self.overlays[i].noutrefresh(0, 0, 0, 0, max_y-1, max_x-1)
|
||||
|
||||
# Prepare for next loop
|
||||
|
@ -203,7 +201,7 @@ class WindowDisplay(_Display):
|
|||
# for proper superimposition
|
||||
self.matrices = []
|
||||
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)
|
||||
|
||||
def _valid(self):
|
||||
|
@ -223,7 +221,7 @@ class WindowDisplay(_Display):
|
|||
"""
|
||||
self.cam.tick(self.rules, *self.tick_args)
|
||||
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]]
|
||||
else:
|
||||
pass
|
||||
|
|
|
@ -147,9 +147,9 @@ class Plane:
|
|||
method below.
|
||||
"""
|
||||
if self.N > 0:
|
||||
max_unsigned = reduce(operator.mul, self.shape, 1)
|
||||
sequence = bin(random.randrange(0, max_unsigned))[2:]
|
||||
self.bits = bitarray(sequence.zfill(max_unsigned))
|
||||
bit_count = reduce(operator.mul, self.shape, 1)
|
||||
sequence = bin(random.randrange(0, 2**bit_count-1))[2:]
|
||||
self.bits = bitarray(sequence.zfill(bit_count))
|
||||
|
||||
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 numpy as np
|
||||
import configuration as c
|
||||
|
@ -75,21 +67,22 @@ class Ruleset:
|
|||
# which states do not pass for each configuration
|
||||
current_states = enumerate(plane.bits)
|
||||
for config in self.configurations:
|
||||
|
||||
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 = []
|
||||
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
|
||||
# Note if you need actual states of the neighborhood, make sure to
|
||||
# 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