Last active
November 7, 2019 09:58
-
-
Save apaap/960b2dc92c1618e1d4e9583b861dd678 to your computer and use it in GitHub Desktop.
Simulator for ECA rules 18, 90, and 150
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LCA: | |
"""Simulate the Linear CA (XOR CA rule) known as Rule 90 on a circular grid | |
Usage: l = LCA(width) OR l = LCA(width, rule) | |
where width is an integer > 1 and rule is one of [18, 90, 150]""" | |
maxbuf = 8 | |
def __init__(self, width, rule=90): | |
if rule == 18: | |
self.stepfun = LCA.step18 | |
elif rule == 90: | |
self.stepfun = LCA.step90 | |
elif rule == 150: | |
self.stepfun = LCA.step150 | |
else: | |
raise ValueError("Rule number must be one of [18, 90, 150] (default 90).") | |
self.state = 0; # The current cell configuration | |
self.generation = 0 | |
self.width = width; | |
self.buf = min(maxbuf, width) | |
self.mask = (2**width - 1) << self.buf | |
self.rhmask = (2**self.buf - 1) | |
self.lhmask = (2**self.buf - 1) << (width + self.buf) | |
def set(self, s): | |
"""Set the initial state""" | |
self.state = s << self.buf | |
self.state &= self.mask | |
self.generation = 0 | |
def get(self): | |
"""Get the current state""" | |
return (self.state & self.mask) >> self.buf | |
@staticmethod | |
def step18(s, gen): | |
for _ in range(gen): | |
s = ((s << 1) ^ (s >> 1)) & ~s | |
return s | |
@staticmethod | |
def step90(s, gen): | |
for _ in range(gen): | |
s = (s << 1) ^ (s >> 1) | |
return s | |
@staticmethod | |
def step150(s, gen): | |
for _ in range(gen): | |
s = ((s << 1) ^ (s >> 1)) ^ s | |
return s | |
def step(self, gen=1): | |
"""Evolve the state of the CA using Rule 90 by between one to eight generations""" | |
if gen < 1 or gen > self.buf: | |
print('Requested number of generations ({}) is outside allowed values (1-{})'.format(gen, self.buf)) | |
s = self.state | |
# s &= self.mask # Should be unnecessary | |
s |= (s >> self.width) & self.rhmask | |
s |= (s << self.width) & self.lhmask | |
s = self.stepfun(s, gen) | |
self.state = s & self.mask | |
self.generation += gen | |
def advance(self, gen): | |
"""Evolve the state of the CA by <gen> generations""" | |
for _ in range(gen // self.buf): | |
self.step(self.buf) | |
if (gen % self.buf): | |
self.step(gen % self.buf) | |
def display(self): | |
"""Show the current state as a string of ones and zeros""" | |
print(" " + format(self.get(), '0{}b'.format(self.width))) | |
# Method based on Golly's oscar.py OSCillation AnalyzeR | |
# @staticmethod | |
def oscar(self, gen=1, display=True): | |
"""Detects the eventual period of oscillation of an LCA configuration""" | |
statelist = [] | |
genlist = [] | |
def oscillating(): | |
# return True if the pattern is empty, stable or oscillating | |
s = self.state | |
pos = 0 | |
listlen = len(statelist) | |
while pos < listlen: | |
if s > statelist[pos]: | |
pos += 1 | |
elif s < statelist[pos]: | |
# shorten list and append info below | |
del statelist[pos : listlen] | |
del genlist[pos : listlen] | |
break | |
else: | |
# s == statelist[pos] so pattern is oscillating | |
return self.generation - genlist[pos] | |
statelist.append(s) | |
genlist.append(self.generation) | |
return False | |
while True: | |
period = oscillating() | |
if period: | |
break | |
self.step(gen) | |
if display: | |
print("Detected oscillation with period {}.".format(period)) | |
self.display() | |
return period | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment