SparkyLifeSearch

For scripts to aid with computation or simulation in cellular automata.
Post Reply
User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

SparkyLifeSearch

Post by Scorbie » August 27th, 2015, 9:09 pm

This is a small script that aims in finding spark-assisted oscillators in Life.
Currently it is in a 'junk script' stage and doesn't actually find anything, but I'm posting it here just in case someone gets interested and develops it.
The idea was from Jason Summers, who (I think) wrote his own C program to search for spark assisted oscs.
It emulates Life with sparks with this rule (SparkyLife.rule)

Code: Select all

@RULE SparkyLife

# This rule Emulates Life perturbation with sparks.
# Two gens in SparkyLife equals to one gen in normal Life.

@TABLE

# States
# 0: OFF cell
# 1: ON cell on even gens
# 2: ON cell on odd gens (auxillary state)
# 3: ON spark
# 4: OFF spark

n_states:5
neighborhood:Moore
symmetries:rotate8

var on1 = {1,3}
var on2 = {1,3}
var on3 = {1,3}
var off1 = {0,4}
var off2 = {0,4}
var off3 = {0,4}
var off4 = {0,4}
var off5 = {0,4}
var off6 = {0,4}
var off7 = {0,4}
var off8 = {0,4}
var a1 = {0,1,2,3,4}
var a2 = {0,1,2,3,4}
var a3 = {0,1,2,3,4}
var a4 = {0,1,2,3,4}
var a5 = {0,1,2,3,4}
var a6 = {0,1,2,3,4}
var a7 = {0,1,2,3,4}
var a8 = {0,1,2,3,4}
var b = {0,3,4}

# Gen 2k -> 2k+1
# Evolve ON cells as normal Life, considering ON sparks.
# The states change from 1 to 2.

0,off1,off2,off3,off4,off5,on1,on2,on3,2
0,off1,off2,off3,off4,on1,off5,on2,on3,2
0,off1,off2,off3,off4,on1,on2,off5,on3,2
0,off1,off2,off3,on1,off4,off5,on2,on3,2
0,off1,off2,off3,on1,off4,on2,off5,on3,2
0,off1,off2,off3,on1,on2,off4,off5,on3,2
0,off1,off2,on1,off3,off4,on2,off5,on3,2
1,off1,off2,off3,off4,off5,off6,on1,on2,2
1,off1,off2,off3,off4,off5,on1,off6,on2,2
1,off1,off2,off3,off4,on1,off5,off6,on2,2
1,off1,off2,off3,on1,off4,off5,off6,on2,2
1,off1,off2,off3,off4,off5,on1,on2,on3,2
1,off1,off2,off3,off4,on1,off5,on2,on3,2
1,off1,off2,off3,off4,on1,on2,off5,on3,2
1,off1,off2,off3,on1,off4,off5,on2,on3,2
1,off1,off2,off3,on1,off4,on2,off5,on3,2
1,off1,off2,off3,on1,on2,off4,off5,on3,2
1,off1,off2,on1,off3,off4,on2,off5,on3,2
1,off1,off2,off3,off4,off5,off6,off7,a1,0
1,a1,a2,a3,a4,1,1,1,1,0
1,off1,a1,a2,1,off2,1,1,1,0
1,off1,a1,a2,1,1,off2,1,1,0
1,off1,off2,off3,1,1,1,off4,1,0
1,off1,off2,1,off3,off4,1,1,1,0
1,off1,off2,1,off3,1,off4,1,1,0
1,off1,off2,1,off3,1,1,off4,1,0
1,off1,off2,1,1,off3,off4,1,1,0
1,off1,off2,1,1,off3,1,off4,1,0
1,off1,1,off2,1,off3,1,off4,1,0

# Gen 2k+1 -> 2k+2
# ON cells don't change, but their states change from 2 to 1.
# ON sparks turn off iff there is an ON cell nearby.

2,a1,a2,a3,a4,a5,a6,a7,a8,1
3,a1,a2,a3,a4,a5,a6,a7,2,4

# OFF sparks turn ON again.
4,0,0,0,0,0,0,0,b,3

@COLORS
1 255 255 255
2 128 128 128
3 255 255 0
4 128 128 0
EDIT by Andrew: Removed blank lines at start of code so Golly will recognize clipboard data as a rule rather than a pattern.

And generates random soups and tests oscillation with this script(SparkyLifeSearch):

Code: Select all

import golly as g
import random
        
# List of symmetries
C1 =   lambda (x, y): [(x, y)]
C21 =  lambda (x, y): [(x, y), (-x, -y)]
C22 =  lambda (x, y): [(x, y), (-x, -y-1)]
C24 =  lambda (x, y): [(x, y), (-x-1, -y-1)]
C41 =  lambda (x, y): [(x, y), (-y, x), (-x, -y), (y, -x)]
C44 =  lambda (x, y): [(x, y), (-y-1, x), (-x-1, -y-1), (y, -x-1)]
D21 =  lambda (x, y): [(x, y), (x, -y)]
D22 =  lambda (x, y): [(x, y), (x, -y-1)]
D2X =  lambda (x, y): [(x, y), (y, x)]
D41 =  lambda (x, y): [(x, y), (x, -y), (-x, y), (-x, -y)]
D42 =  lambda (x, y): [(x, y), (x, -y-1), (-x, y), (-x, -y-1)]
D44 =  lambda (x, y): [(x, y), (x, -y-1), (-x-1, y), (-x-1, -y-1)]
D4X1 = lambda (x, y): [(x, y), (y, x), (-x, -y), (-y, -x)]
D4X4 = lambda (x, y): [(x, y), (y, x), (-x-1, -y-1), (-y-1, -x-1)]
D81 =  lambda (x, y): [(x, y), (x, -y), (-x, y), (-x, -y), (y, x), (y, -x), (-y, x), (-y, -x)]
D84 =  lambda (x, y): [(x, y), (x, -y-1), (-x-1, y), (-x-1, -y-1), (y, x), (y, -x-1), (-y-1, x), (-y-1, -x-1)]

class SparkSoup(object):

    symmetrydict = {'C1':C1, 'C21':C21, 'C22':C22, 'C24':C24, 'C41':C41, 'C44':C44,\
                    'D21':D21, 'D22':D22, 'D2X':D2X, 'D41':D41, 'D42':D42, 'D44':D44,\
                    'D4X1':D4X1, 'D4X4':D4X4, 'D81':D81, 'D84':D84}
    
    def __init__(self, width, height, symstring, sparkdist):
        
        self.halfwidth = width/2
        self.halfheight = height/2
        self.widthparity = width%2
        self.heightparity = height%2
        self.symmetry = self.symmetrydict[symstring]
        self.canonicalcells = self.getcanonicalcells(width, height)
        self.sparkdist = sparkdist
    
    def getcanonicalcells(self, width, height):
        hrange = xrange((-height+1)/2, (height+1)/2)
        wrange = xrange((-width+1)/2, (width+1)/2)
        cells = [(w, h) for w in wrange for h in hrange]
        canonical  = []
        dependent = []
        for cell in cells:
            if cell not in dependent:
                canonical.append(cell)
                dependent += self.symmetry(cell)
        return canonical
            
    def randfill(self):
        
        # Generate random soup
        n = len(self.canonicalcells)
        canonicalbits = random.getrandbits(n)
        oncells = []
        oncelllist = []
        
        for i in xrange(n):
            if canonicalbits & (1<<i):
                oncells += self.symmetry(self.canonicalcells[i])
        
        for oncell in oncells:
            oncelllist.append(oncell[0])
            oncelllist.append(oncell[1])
            oncelllist.append(1)
        
        # Add sparks
        rand = random.getrandbits(2)
        if rand&1:
            xmin = 0
            xmax = self.halfwidth
            ymin = self.halfheight+2
            ymax = self.halfheight+self.sparkdist
        else:
            xmin = self.halfwidth+2
            xmax = self.halfwidth+self.sparkdist
            ymin = 0
            ymax = self.halfheight
            
        x = random.randint(xmin,xmax)
        y = random.randint(ymin,ymax)
        
        for (u,v) in self.symmetry((x, y)):
            oncelllist.append(u)
            oncelllist.append(v)            
            oncelllist.append(3)
        
        # For domino sparks
        if rand&2:
            if rand&1:
                for (u,v) in self.symmetry((x+1, y)):
                    oncelllist.append(u)
                    oncelllist.append(v)            
                    oncelllist.append(3)
            else:
                for (u,v) in self.symmetry((x, y+1)):
                    oncelllist.append(u)
                    oncelllist.append(v)            
                    oncelllist.append(3)
                
            
            
        if len(oncelllist) %2 == 0:
            oncelllist.append(0)
            
        return oncelllist   
    
        
def symtest():
    
    g.new('Symmetry Test')
    g.setrule('SparkyLife')
    g.setstep(1)
    g.setbase(2)
    
    symmetries = [['C1'], ['C21', 'C22', 'C24'], ['C41', 'C44'], ['D21', 'D22', 'D2X'],\
                  ['D41', 'D42', 'D44', 'D4X1', 'D4X4'],['D81', 'D84']]
    for i in range(len(symmetries)):
        for j in range(len(symmetries[i])):
            samplesoup = SparkSoup(16, 16, symmetries[i][j], 5)
            g.putcells(samplesoup.randfill(), 40*j, 40*i)
    g.fit()
            
#symtest()

# Code from oscar.py
class Oscar(object):

    def __init__(self):
    
        # initialize lists
        self.hashlist = []        # for pattern hash values
        self.genlist = []         # corresponding generation counts
        self.poplist = []         # corresponding population counts
        self.period = None

    def reset(self):
        
        # initialize lists
        self.hashlist = []        # for pattern hash values
        self.genlist = []         # corresponding generation counts
        self.poplist = []         # corresponding population counts
        self.period = None
        
    def oscillating(self, prect):
        
        # Test if pattern (without sparks) is empty:
        cells = g.getcells(prect)
        curpop = len(cells) # Current population * 3 (optionally +1)
        for i in cells[::3]:
            if i==1 or i==2:
                break
        else:
            return True
        curgen = int(g.getgen())
        
        # Get current pattrn and create hash
        h = g.hash(prect)

        # determine where to insert h into self.hashlist
        pos = 0
        listlen = len(self.hashlist)
        while pos < listlen:
            if h > self.hashlist[pos]:
                pos += 1
            elif h < self.hashlist[pos]:
                # shorten lists and append info below
                del self.hashlist[pos : listlen]
                del self.genlist[pos : listlen]
                del self.poplist[pos : listlen]
                break
            else:
                # h == self.hashlist[pos] so pattern is probably oscillating, but just in
                # case this is a hash collision we also compare pop count and box size
                if (curpop == self.poplist[pos]):
                    self.period = (curgen - self.genlist[pos])/2 # 2 SparkyLife gens = 1 Life gen
                    return True
                else:
                    # look at next matching hash value or insert if no more
                    pos += 1

        # store hash/gen/pop/box info at same position in various lists
        self.hashlist.insert(pos, h)
        self.genlist.insert(pos, curgen)
        self.poplist.insert(pos, curpop)
        return False

def main():

    g.setrule('SparkyLife')
    
    sym = g.getstring('SparkSoup symmetry?\n C1, C21, C22, C24, C41, C44, D21, D22, D2X, D41, D42, D44, D4X1, D4X4, D81, D84', 'C1').upper()
    width = int(g.getstring('SparkSoup width?', '8'))
    if 'C4' in sym or 'D4' in sym or 'D8' in sym or 'X' in sym: # Soup should be square
        height = width
    else:
        height = int(g.getstring('SparkSoup height?', '8'))
    
    sparkdist = int(g.getstring('Spark distance?','5'))
    
    soup = SparkSoup(width, height, sym, sparkdist)
    
    oscar = Oscar()
    # Detect whether the soup is oscillating:
    while True:
        g.new('SparkyLifeSearch')
        g.setbase(2)
        g.setstep(1)
        g.putcells(soup.randfill())
        souprect = g.getrect()
        oscar.reset()
        while not oscar.oscillating(souprect):
            g.step()
        if oscar.period > 3:
            g.reset()
            g.setstep(1)
            g.show("Oscillator detected (period = " + str(oscar.period) + ")")
            break
        
main()
I am not sure if I can spend more time regarding this.
I am posting it here as soon as I finished the script and saw it working, so I haven't tested it thoroughly.
It seems that it can't search C1, C2, C4, and D2 too well.
And it seems that it reports periods of stabilized patterns, but I don't know why.

Any comments/optimizations to the code are welcome.
Last edited by Scorbie on August 27th, 2015, 10:07 pm, edited 1 time in total.

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: SparkyLifeSearch

Post by Scorbie » August 27th, 2015, 10:04 pm

The only newish thing it found is this one. I'll post if anything interesting comes along.

Code: Select all

x = 6, y = 6, rule = SparkyLife
5.C2$4.2A$4.2A$2.2A$C.2A!

Bullet51
Posts: 663
Joined: July 21st, 2014, 4:35 am

Re: SparkyLifeSearch

Post by Bullet51 » August 28th, 2015, 3:18 am

Impressive!
I have found this:

Code: Select all

x = 13, y = 13, rule = b3s23
5bobo2$5b3o$4bo3bo$3bo5bo$obo7bobo$2bo7bo$obo7bobo$3bo5bo$4bo3bo$5b3o
2$5bobo!
and these not-so-promising ones:

Code: Select all

x = 46, y = 17, rule = SparkyLife
8.D25.2D3.2D$7.A.A23.2A5.2A$8.A24.2A.A.A.2A$7.A.A13.C$8.A14.D6.2A2.A
5.A2.2A$8.A13.A.A4.D2A.A7.A.2AD$23.A5.D15.D$.A.A9.A.A7.A7.A11.A$D.A.
2A5.2A.A.D$.A.A9.A.A7.A7.A11.A$23.A5.D15.D$8.A13.A.A4.D2A.A7.A.2AD$8.
A14.D6.2A2.A5.A2.2A$7.A.A13.C$8.A24.2A.A.A.2A$7.A.A23.2A5.2A$8.D25.2D
3.2D!
Still drifting.

Bullet51
Posts: 663
Joined: July 21st, 2014, 4:35 am

Re: SparkyLifeSearch

Post by Bullet51 » August 28th, 2015, 4:03 am

The completed one:

Code: Select all

x = 25, y = 25, rule = B3/S23
6b2o9b2o$5bo2bo7bo2bo$5b3o2b5o2b3o$8b2o5b2o$7bo9bo$b2o4b2obo3bob2o4b2o
$obo9bo9bobo$obob2o13b2obobo$bobobo5b3o5bobobo$3bo6bo3bo6bo$2bo2bo3bo
5bo3bo2bo$2bo5bo7bo5bo$2bo3bobo7bobo3bo$2bo5bo7bo5bo$2bo2bo3bo5bo3bo2b
o$3bo6bo3bo6bo$bobobo5b3o5bobobo$obob2o13b2obobo$obo9bo9bobo$b2o4b2obo
3bob2o4b2o$7bo9bo$8b2o5b2o$5b3o2b5o2b3o$5bo2bo7bo2bo$6b2o9b2o!
And a p15:

Code: Select all

x = 19, y = 19, rule = SparkyLife
7.2C.2C5$8.3A$7.A3.A$C5.A5.A5.C$C4.A7.A4.C$5.A7.A$C4.A7.A4.C$C5.A5.A
5.C$7.A3.A$8.3A5$7.2C.2C!
Probably a bug: SLS sometimes give non-spark-assisted oscillators:

Code: Select all

x = 20, y = 20, rule = SparkyLife
8.2C6$11.A$10.3A$C8.A.3A$C7.A3.A$7.A3.A7.C$6.3A.A8.C$7.3A$8.A6$10.2C!
And even false positives (this one is p7 by SLS):

Code: Select all

x = 8, y = 13, rule = SparkyLife
4.C2$2.4A$A.3A.2A$4A$2A2.4A$2.A.2A.A$2A2.4A$4A$A.3A.2A$2.4A2$4.C!
Still drifting.

User avatar
Scorbie
Posts: 1692
Joined: December 7th, 2013, 1:05 am

Re: SparkyLifeSearch

Post by Scorbie » August 28th, 2015, 4:17 am

@Bullet51 I'm glad that somebody is interested in the search.
Bullet51 wrote:Impressive!
I have found this:

Code: Select all

x = 13, y = 13, rule = b3s23
5bobo2$5b3o$4bo3bo$3bo5bo$obo7bobo$2bo7bo$obo7bobo$3bo5bo$4bo3bo$5b3o
2$5bobo!
The completed one:

Code: Select all

x = 25, y = 25, rule = B3/S23
6b2o9b2o$5bo2bo7bo2bo$5b3o2b5o2b3o$8b2o5b2o$7bo9bo$b2o4b2obo3bob2o4b2o
$obo9bo9bobo$obob2o13b2obobo$bobobo5b3o5bobobo$3bo6bo3bo6bo$2bo2bo3bo
5bo3bo2bo$2bo5bo7bo5bo$2bo3bobo7bobo3bo$2bo5bo7bo5bo$2bo2bo3bo5bo3bo2b
o$3bo6bo3bo6bo$bobobo5b3o5bobobo$obob2o13b2obobo$obo9bo9bobo$b2o4b2obo
3bob2o4b2o$7bo9bo$8b2o5b2o$5b3o2b5o2b3o$5bo2bo7bo2bo$6b2o9b2o!
That is a known oscillator rotor (Octagon III, I think.), but what I didn't notice is that the new interacting mechanism.
I guess the search isn't too useless :)
Bullet51 wrote:And a p15:

Code: Select all

x = 19, y = 19, rule = SparkyLife
7.2C.2C5$8.3A$7.A3.A$C5.A5.A5.C$C4.A7.A4.C$5.A7.A$C4.A7.A4.C$C5.A5.A
5.C$7.A3.A$8.3A5$7.2C.2C!
There's a slight chance that it can be bellmanized or spark-assisted to make a true p15 in Life. I didn't intend the doublespark to work that way, but at least it's kinda interesting.
Bullet51 wrote:Probably a bug: SLS sometimes give non-spark-assisted oscillators:

Code: Select all

x = 20, y = 20, rule = SparkyLife
8.2C6$11.A$10.3A$C8.A.3A$C7.A3.A$7.A3.A7.C$6.3A.A8.C$7.3A$8.A6$10.2C!
And even false positives (this one is p7 by SLS):

Code: Select all

x = 8, y = 13, rule = SparkyLife
4.C2$2.4A$A.3A.2A$4A$2A2.4A$2.A.2A.A$2A2.4A$4A$A.3A.2A$2.4A2$4.C!
About non-spark-assisted oscillators: That's because the oscillator detection is pretty naive. I didn't want to put too much time on a test project, so I just cited oscar.py. I was expecting that to happen.

About false positives: That's what bothers me. I'm not sure why that happens.

Anyway, WHEN I have more time, I'll try to enhance oscillator detection code.

Post Reply