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

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

View File

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

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

View File

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

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

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