r
/
fifth
1
Fork 0

Reduced memory consumption

master
Joshua Potter 2015-06-20 21:05:55 -04:00
parent df9e7206c2
commit bde3658812
7 changed files with 130 additions and 102 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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):
""" """

View File

@ -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

76
tests/ruleset_test.py Normal file
View File

@ -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