Skip to content

Instantly share code, notes, and snippets.

@apaap
Last active November 7, 2019 09:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apaap/960b2dc92c1618e1d4e9583b861dd678 to your computer and use it in GitHub Desktop.
Save apaap/960b2dc92c1618e1d4e9583b861dd678 to your computer and use it in GitHub Desktop.
Simulator for ECA rules 18, 90, and 150
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