diff --git a/examples/highlife.py b/examples/highlife.py index 32de68d..b528000 100644 --- a/examples/highlife.py +++ b/examples/highlife.py @@ -10,11 +10,10 @@ if __name__ == '__main__': sys.path.append(os.path.abspath('src')) import cam - import util as u - import ruleset as rs + import cam_parser c = cam.CAM(1, 100, 2) - p = u.CAMParser('B368/S23', c) + p = cam_parser.CAMParser('B368/S23', c) c.randomize() c.start_plot(100, p.ruleset) diff --git a/examples/life.py b/examples/life.py index fa10655..205ab3c 100644 --- a/examples/life.py +++ b/examples/life.py @@ -10,11 +10,10 @@ if __name__ == '__main__': sys.path.append(os.path.abspath('src')) import cam - import util as u - import ruleset as rs + import cam_parser c = cam.CAM(1, 100, 2) - p = u.CAMParser('B3/S23', c) + p = cam_parser.CAMParser('B3/S23', c) c.randomize() - c.start_plot(400, p.ruleset) + c.start_plot(100, p.ruleset) diff --git a/examples/life_without_death.py b/examples/life_without_death.py index e494827..b5d214d 100644 --- a/examples/life_without_death.py +++ b/examples/life_without_death.py @@ -10,11 +10,10 @@ if __name__ == '__main__': sys.path.append(os.path.abspath('src')) import cam - import util as u - import ruleset as rs + import cam_parser c = cam.CAM(1, 100, 2) - p = u.CAMParser('B3/S012345678', c) + p = cam_parser.CAMParser('B3/S012345678', c) c.randomize() c.start_plot(100, p.ruleset) diff --git a/examples/morley.py b/examples/morley.py index 245a456..6a2df77 100644 --- a/examples/morley.py +++ b/examples/morley.py @@ -10,11 +10,10 @@ if __name__ == '__main__': sys.path.append(os.path.abspath('src')) import cam - import util as u - import ruleset as rs + import cam_parser c = cam.CAM(1, 100, 2) - p = u.CAMParser('B368/S245', c) + p = cam_parser.CAMParser('B368/S245', c) c.randomize() c.start_plot(100, p.ruleset) diff --git a/examples/replicator.py b/examples/replicator.py index 00bb3ef..3cb948f 100644 --- a/examples/replicator.py +++ b/examples/replicator.py @@ -10,11 +10,10 @@ if __name__ == '__main__': sys.path.append(os.path.abspath('src')) import cam - import util as u - import ruleset as rs + import cam_parser c = cam.CAM(1, 100, 2) - p = u.CAMParser('B1357/S1357', c) + p = cam_parser.CAMParser('B1357/S1357', c) c.randomize() c.start_plot(100, p.ruleset) diff --git a/examples/seeds.py b/examples/seeds.py index cdc02f8..a85f5d7 100644 --- a/examples/seeds.py +++ b/examples/seeds.py @@ -10,11 +10,10 @@ if __name__ == '__main__': sys.path.append(os.path.abspath('src')) import cam - import cam_util as u - import ruleset as rs + import cam_parser c = cam.CAM(1, 100, 2) - p = u.CAMParser('B2/S', c) + p = cam_parser.CAMParser('B2/S', c) c.randomize() c.start_plot(100, p.ruleset) diff --git a/src/cam.py b/src/cam.py index ac610ae..5bc8464 100644 --- a/src/cam.py +++ b/src/cam.py @@ -7,6 +7,8 @@ all methods needed (i.e. supported) to interact/configure with the cellular auto @date: June 01, 2015 """ +import plane + import time import matplotlib.pyplot as plt import matplotlib.animation as ani @@ -37,10 +39,12 @@ class CAM: plane_count = max(cps, 1) grid_dimen = (states,) * dimen - self.planes = [Plane(grid_dimen) for i in range(cps)] + self.planes = [plane.Plane(grid_dimen) for i in range(cps)] + self.master = self.planes[0] self.ticks = [(0, 1)] self.total = 0 + def tick(self, rules, *args): """ Modify all states in a given CAM "simultaneously". @@ -53,7 +57,8 @@ class CAM: self.total += 1 for i, j in self.ticks: if self.total % j == 0: - rules.applyTo(self.planes[i], *args) + rules.apply_to(self.planes[i], *args) + def start_plot(self, clock, rules, *args): """ @@ -68,17 +73,18 @@ class CAM: ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) - mshown = plt.matshow(self.planes[0].bits(), fig.number, cmap='Greys') + mshown = plt.matshow(self.master.bits(), fig.number, cmap='Greys') def animate(frame): self.tick(rules, *args) - mshown.set_array(self.planes[0].bits()) + mshown.set_array(self.master.bits()) return [mshown] ani.FuncAnimation(fig, animate, interval=clock) plt.axis('off') plt.show() + def start_console(self, clock, rules, *args): """ Initates main console loop. @@ -87,7 +93,17 @@ class CAM: TODO: Incorporate curses, instead of just printing repeatedly. """ while True: - print(self.planes[0].bits()) + print(self.master.bits()) time.sleep(clock / 1000) self.tick(rules, *args) + + def randomize(self): + """ + Convenience function to randomize individual planes. + """ + self.master.randomize() + for plane in self.planes[1:]: + plane.grid = self.master.grid + + diff --git a/src/parser.py b/src/cam_parser.py similarity index 92% rename from src/parser.py rename to src/cam_parser.py index e3c56d5..f04c881 100644 --- a/src/parser.py +++ b/src/cam_parser.py @@ -53,7 +53,7 @@ class CAMParser: """ self.sfunc = None self.offsets = c.Configuration.moore(cam.master) - self.ruleset = r.Ruleset(rsRuleset.Method.ALWAYS_PASS) + self.ruleset = r.Ruleset(r.Ruleset.Method.ALWAYS_PASS) if re.match(CAMParser.MCELL_FORMAT, notation): x, y = notation.split('/') @@ -98,12 +98,11 @@ class CAMParser: 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) + def next_state(plane, neighborhood, *args): + if plane.grid.flat[neighborhood.flat_index]: + return int(neighborhood.total in x) else: - return int(total in y) + return int(neighborhood.total in y) return next_state diff --git a/src/configuration.py b/src/configuration.py index 444492b..b5f42a2 100644 --- a/src/configuration.py +++ b/src/configuration.py @@ -21,6 +21,8 @@ with the ALWAYS_PASS flag set in the given ruleset the configuration is bundled @date: June 5th, 2015 """ +import numpy as np +from itertools import product from collections import namedtuple @@ -104,8 +106,8 @@ class Configuration: Note the center cell is excluded, so the total number of offsets are 3^N - 1. """ offsets = {} - variants = ([-1, 0, 1],) * len(grid.shape) - for current in it.product(*variants): + variants = ([-1, 0, 1],) * len(plane.shape) + for current in product(*variants): if any(current): offsets[current] = value @@ -124,7 +126,7 @@ class Configuration: Note the center cell is excluded, so the total number of offsets are 2N. """ offsets = [] - variant = [0] * len(grid.shape) + variant = [0] * len(plane.shape) for i in range(len(variant)): for j in [-1, 1]: variant[i] = j @@ -159,7 +161,7 @@ class Configuration: """ for coor, bit in offsets.items(): flat_index, bit_index = plane.flatten(coor) - self.offsets.append(Offset(flat_index, bit_index, bit)) + self.offsets.append(Configuration.Offset(flat_index, bit_index, bit)) def passes(self, plane, neighborhood, vfunc, *args): diff --git a/src/plane.py b/src/plane.py index d865f7f..4d5a9b3 100644 --- a/src/plane.py +++ b/src/plane.py @@ -44,7 +44,7 @@ class Plane: else: self.grid = np.empty(shape[:-1], dtype=np.object) for i in range(self.grid.size): - self.grid.flat[i] = bitarray(self.N) + self.grid.flat[i] = self.N * bitarray('0') def __getitem__(self, index): @@ -95,17 +95,21 @@ class Plane: """ Sets values of grid to random values. - Since numbers of the grid may be larger than numpy can handle natively (i.e. too big - for C long types), we use the python random module instead. + By default, newly initialized bitarrays are random, but in a weird way I'm not sure I + understand. For example, constructing bitarrays in a loop appear to set every bitarray + after the first to 0, and, if I put a print statement afterwards, all bitarrays maintain + the same value. I'm not really too interested in figuring this out, so I use the alternate + method below. """ if len(self.shape) > 0: import random as r max_u = 2**self.N - 1 + gen = lambda: bin(r.randrange(0, max_u))[2:] if len(self.shape) == 1: - self.grid = r.randrange(0, max_u) + self.grid = bitarray(gen().zfill(self.N)) else: - tmp = np.array([r.randrange(0, max_u) for i in range(len(self.grid))]) - self.grid = tmp.reshape(self.grid.shape) + for i in range(self.grid.size): + self.grid.flat[i] = bitarray(gen().zfill(self.N)) def flatten(self, coordinate): @@ -118,6 +122,21 @@ class Plane: flat_index, gridprod = 0, 1 for i in reversed(range(len(coordinate[:-1]))): flat_index += coordinate[i] * gridprod - gridprod *= shape[i] + gridprod *= self.shape[i] return flat_index, coordinate[-1] + + + def bits(self): + """ + Expands out bitarray into individual bits. + + This is useful for display in matplotlib for example, but does take a dimension more space. + """ + if len(self.shape) == 1: + return np.array(self.grid) + else: + tmp = np.array([list(self.grid.flat[i]) for i in range(self.grid.size)]) + return np.reshape(tmp, self.shape) + + diff --git a/src/ruleset.py b/src/ruleset.py index 237159c..2899aa9 100644 --- a/src/ruleset.py +++ b/src/ruleset.py @@ -7,8 +7,9 @@ said neighborhood that yield an "on" or "off" state on the cell a ruleset is bei @date: May 31st, 2015 """ import enum - import numpy as np +import configuration as c +from bitarray import bitarray class Ruleset: @@ -71,8 +72,8 @@ class Ruleset: # either all bits pass or configurations are exhausted for flat_index, value in enumerate(plane.grid.flat): - next_row = bitarray(self.N) - to_update = range(0, self.N) + next_row = bitarray(plane.N) + to_update = range(0, plane.N) for config in self.configurations: next_update = [] @@ -86,17 +87,18 @@ class Ruleset: # no overflowing of a single column can occur. We can then find the total of the ith neighborhood by checking the # sum of the ith index of the summation of every 9 chunks of numbers (this is done a row at a time). neighboring = [] - for flat_offset, bit_offset in config.offsets: - neighbor = plane.grid.flat[flat_index + flat_offset] + for flat_offset, bit_offset, _ in config.offsets: + neighbor = plane.grid.flat[(flat_index + flat_offset) % plane.N] cycled = neighbor[bit_offset:] + neighbor[:bit_offset] neighboring.append(int(cycled.to01())) # Chunk into groups of 9 and sum all values # These summations represent the total number of active states in a given neighborhood - totals = [0] * self.N - chunks = map(sum, [offset_totals[i:i+9] for i in range(0, len(neighboring), 9)]) + totals = [0] * plane.N + chunks = list(map(sum, [neighboring[i:i+9] for i in range(0, len(neighboring), 9)])) for chunk in chunks: - totals = list(map(sum, zip(totals, chunk))) + i_chunk = list(map(int, str(chunk).zfill(plane.N))) + totals = list(map(sum, zip(totals, i_chunk))) # Determine which function should be used to test success if self.method == Ruleset.Method.MATCH: @@ -110,8 +112,8 @@ class Ruleset: # Apply change to all successful configurations for bit_index in to_update: - neighborhood = Neighborhood(flat_index, bit_index, totals[bit_index]) - success, state = config.passes(neighborhood, vfunc, *args) + neighborhood = c.Neighborhood(flat_index, bit_index, totals[bit_index]) + success, state = config.passes(plane, neighborhood, vfunc, *args) if success: next_row[bit_index] = state else: