Script request thread

For scripts to aid with computation or simulation in cellular automata.
User avatar
Apple Bottom
Posts: 1034
Joined: July 27th, 2015, 2:06 pm
Contact:

Re: Script request thread

Post by Apple Bottom » February 10th, 2018, 6:29 am

Majestas32 wrote:Isotropic. And I think Calcyman talked about every rule actually having a strobing checkerboard dual?
Yes.
If you speak, your speech must be better than your silence would have been. — Arabian proverb

Catagolue: Apple Bottom • Life Wiki: Apple Bottom • Twitter: @_AppleBottom_

Proud member of the Pattern Raiders!

AforAmpere
Posts: 1334
Joined: July 1st, 2016, 3:58 pm

Re: Script request thread

Post by AforAmpere » February 16th, 2018, 11:21 am

An analysation script for the 3-glider collision database from 2718281828. It would take an input, like this:

Code: Select all

x = 7, y = 7, rule = B3/S23
$b3o$bo2bo$5bo$2bobo!
Then it would output the 3-glider collision that produced it.
I manage the 5S project, which collects all known spaceship speeds in Isotropic Non-totalistic rules. I also wrote EPE, a tool for searching in the INT rulespace.

Things to work on:
- Find (7,1)c/8 and 9c/10 ships in non-B0 INT.
- EPE improvements.

hkoenig
Posts: 258
Joined: June 20th, 2009, 11:40 am

Re: Script request thread

Post by hkoenig » February 16th, 2018, 4:02 pm

I took the 3-Glider collisions data and created a database from it.

Each record contains fields for the header, the rle (including the bounds), a modified apgcode (no prefix) the locations, directions and phases of each glider, the number of generations until stability (up to 200), an object count, and the final census (including location).

I did find some problems, none with the data itself. There are 285 records that have the Gliders with not enough space between them, so the application wasn’t able to separate them. If the bit count was 15, I went ahead and just ran them, and marked the record. (I guess I could go through by hand and fix things up a bit..) There are also a number of records with just two Gliders in them. Again, those are marked, and I didn’t run them.

My app also doesn’t recognize escaping Gliders. When I wrote it I converted a bunch of code from 20 years ago, and it appears that that got left out. Adding back in will take some time, and I decided that I’d rather get this out as is, and fix it later. There are some other improvements I could make, too.

According to my SQLite searches, there are 18400 results with one object. There are 29 collisions that result in only a Fishhook/Eater. There are 22 collisions that result in a single Beacon, 474 that give a single Tub, 3 that result in a Hat (my lists only have two of ‘em). The only Bi-Pond is also known. And "3g_41_2_1_7_1"/"532wsg8zw4kc" is the only collision that results in a Pentadecathlon.

The database is SQLite, and 76.8Meg in size. I did a “.dump” of the database to a text file and that text file (and the table definition) compresses down to 10Meg. I figure that’s a better way to distribute this, as it allows someone else to tweak the file to work with other varieties of databases.

Since I can’t post a file that big, I’ve passed it along to Dave Greene so he can post it for me.

The database could be expanded to include another table with the apgcode for the first few generations (say, up to 25, if that many) for each collision. Then one could search for a particular pattern, and get a list of all such collisions and the generation in which it occurs. (Disk space is cheap, and databases are good at pattern matching with strings.) On the other end, a table with the last generations for dying patterns would be useful for finding collisions that generate a particular spark. Neither of these would be difficult to create.

EDIT by dvgrn: Attachments added --
Gliders3DB.dump.zip
SQLite database dump file
(9.51 MiB) Downloaded 301 times
Build-Gliders3DB.txt
Terminal commands to reconstitute the SQLite database from the dump file
(175 Bytes) Downloaded 298 times

AforAmpere
Posts: 1334
Joined: July 1st, 2016, 3:58 pm

Re: Script request thread

Post by AforAmpere » February 16th, 2018, 8:53 pm

I haven't got the sqlite3 to work yet, so is there some way to search for a pattern manually, and how do the coordinate systems for the entries work?
Also, does this come up in the files somewhere? It would indicate the presence of a spark I have been looking for a synthesis of.

Code: Select all

x = 17, y = 11, rule = B3/S23
13b3o2$o$o$o2$2b3o2$o$o14b2o$o14b2o!
I manage the 5S project, which collects all known spaceship speeds in Isotropic Non-totalistic rules. I also wrote EPE, a tool for searching in the INT rulespace.

Things to work on:
- Find (7,1)c/8 and 9c/10 ships in non-B0 INT.
- EPE improvements.

dani
Posts: 1222
Joined: October 27th, 2017, 3:43 pm

Re: Script request thread

Post by dani » February 16th, 2018, 9:13 pm

How do I find a reaction that shoots one of two ships at a few common still lifes and looks for a reaction that pulls it (-1,0) and outputs the same ship heading the other way, all in a specific rule?

I was thinking it could run soups and pick random still lifes out of there, and position them close enough together

hkoenig
Posts: 258
Joined: June 20th, 2009, 11:40 am

Re: Script request thread

Post by hkoenig » February 17th, 2018, 1:38 am

is there some way to search for a pattern manually, and how do the coordinate systems for the entries work?
I guess I should have documented the census data better.

The object ids are from the .SOF files you can find at http://pentadecathlon.com/objects/class1/class1.php and http://pentadecathlon.com/objects/class2/class2.php. Each object is shown there in a standard format. Inside the square brackets are the offset of the object from an arbitrary origin based on the original collision (NW bounding box corner). After that is the rotation and reflection: 'R' for clockwise, 'L' for counter-clockwise and 'T' for 180 degrees, ‘H’ for mirrored vertically (along a horizontal axis) and ‘V’ for mirrored horizontally. Gliders and spaceships have their directions (’N’, ‘NE’, ‘E’, etc.) and both spaceships and oscillators can include a phase, 'P<n>', the number of generations to advance.

So for Gliders, for example, here’s one with the standard/default form 5P4H1V1 [0,0] NE:

Code: Select all

x=3, y=3
3o$o$bo!
and here’s one specified by "5P4H1V1.1 [4,3] SE P1"

x=3, y=3, h=4, v=4
obo$b2o$bo!

Here's a pre-Honeyfarm and the result:

x=4, y=3
3o$o2bo$b2o!
x=13, y=13, h=-5, v=-6
6bo$5bobo$5bobo$6bo2$b2o7b2o$o2bo5bo2bo$b2o7b2o2$6bo$5bobo$5bobo$6bo!

6.4 [0,-6] L ; 6.4 [-5,-1] ; 6.4 [4,-1] ; 6.4 [0,3] L

In order to save some space, all the glider columns leave off the redundant 5P4H1V1.1. 30 characters per record adds up.
does this come up in the files somewhere?
As it is now, there's probably not a good way to search for that. I mentioned to Dave that maybe I should include the apgcode of the final pattern, as a sort of hashcode.

The apgcode here would be "70gggy633zezy9111" and since it's position and rotation independent (if I did it right…) , it should be searchable. So I'll add that (along with a few other changes I'd like to do). I could probably get to it soon, but it may not get done until early next week. Then you could just search the text file for that.

User avatar
Saka
Posts: 3627
Joined: June 19th, 2015, 8:50 pm
Location: Indonesia
Contact:

Re: Script request thread

Post by Saka » February 18th, 2018, 1:13 am

What about a script that picks a random rule from a given rule range?

hkoenig
Posts: 258
Joined: June 20th, 2009, 11:40 am

Re: Script request thread

Post by hkoenig » February 19th, 2018, 12:49 pm

Also, does this come up in the files somewhere? It would indicate the presence of a spark I have been looking for a synthesis of.
Looks like there are 30 collisions that result in this constellation, probably all have the same ancestor, since they take about 180 generations to stabilize. Here are some of them--

Code: Select all

x=14, y=8
bo$2bo$3o2$12bo$b2o8b2o$2b2o7bobo$bo!
and

Code: Select all

x=10, y=8
8bo$9bo$7b3o$bo$2bo$3o3bo$6b2o$5bobo!
The updated database is done, and I'll try to get it posted later today.

Update:

Here are the updated files:
http://pentadecathlon.com/public/Build-Gliders3DB
and
http://pentadecathlon.com/public/Gliders3DB.txt.zip
Last edited by hkoenig on February 19th, 2018, 8:29 pm, edited 1 time in total.

User avatar
77topaz
Posts: 1496
Joined: January 12th, 2018, 9:19 pm

Re: Script request thread

Post by 77topaz » February 19th, 2018, 5:50 pm

Majestas32 wrote:
danny wrote:
77topaz wrote:Is it possible to make a script that, given an apgcode, looks through Catagolue for rules where that apgcode has appeared and then returns the rule in which that pattern is the most common relative to other patterns (i.e. in which it appears the highest in the list of most common objects)?
This gets a +1 from me because I need to find that one rule where the bun evolves into a monogram
I also need to find the rule with the xp104_hexeh and xp14_eh so +1

Also the xp170 OMOS from my avatar
Has anyone looked into this yet?

wildmyron
Posts: 1542
Joined: August 9th, 2013, 12:45 am
Location: Western Australia

Re: Script request thread

Post by wildmyron » February 20th, 2018, 1:23 am

77topaz wrote:
77topaz wrote:Is it possible to make a script that, given an apgcode, looks through Catagolue for rules where that apgcode has appeared and then returns the rule in which that pattern is the most common relative to other patterns (i.e. in which it appears the highest in the list of most common objects)?
Has anyone looked into this yet?
I haven't written anything related to CataGoLue, but I suspect that anyone who does is (and definitely should be) wary of running scripts that make lots of requests (over many different rules in this case) because that kind of thing can result in disk operations going over quota and therefore CataGoLue being down for the rest of the day.
danny wrote:How do I find a reaction that shoots one of two ships at a few common still lifes and looks for a reaction that pulls it (-1,0) and outputs the same ship heading the other way, all in a specific rule?

I was thinking it could run soups and pick random still lifes out of there, and position them close enough together
The second part is pretty hard to do intelligently - I think better if you specify manually to the search which still lifes you want to include in the search.

sngdetect (aka Stable and Glider) does a variation of what you want, but it only supports Life and it generates all still life constellations which fit in a given bounding box - which means it searches a lot of collisions with rare still lifes which are very unlikely to reappear. I modified it to search B2/S0 a long time ago, which I don't think was too difficult, but that was custom to B2/S0 and there'd probably be a bit more work involved to get it to support isotropic rules. I also wrote some Python scripts to do something similar (also in B2/S0) but unfortunately I lost a lot of my B2/S0 work a long time ago and not all of it was backed up. The procedure went something like:
  • Run a variation of build-constellation-v10.py to generate constellations of dots and save them to a file. The constellations are stored in a non-canonical apgcode format - this ensures all orientations of the constellation end up in the file and the collision script only needs to hit each constellation from one side (or two sides if the bounding box is rectangular)
  • Run a collision script which takes each constellation from a file and collides a moon into it on every lane in which it might interact. This script would run each pattern for a fixed number of generations, discarding any that exploded or died out and then save any which were interesting. Interesting in this case was pretty much everything that wasn't discarded, but in general it might include any pattern where a high period object is generated, or the original dot constellation reappears. You can test for the later by detecting any escaping ships and then comparing the remaining ash to the original pattern (This is why I used non-canonical apgcodes).
Here's the first script I used to generate the dot constellations which you should be able to reuse for Snowflakes (for example), but unfortunately I don't have a copy of the collision script.
If you want more than just dots, add the rle representations of the still lifes to the 'objs' array. You should add all unique representations after rotations and reflections. See the original for demonstration of this.

Code: Select all

# build-dot-field-v10.py
import golly as g
from time import sleep
from glife.text import make_text

MAXOBJ=5
xsize,ysize=5,12

# Obtains an alphanumeric representation of a pattern that 
# fits within a 40-by-40 bounding box. This representation is
# alphanumeric and lowercase, and so much more compact than RLE. Compare:
#
# It is a generalisation of a notation created by Allan Weschler in 1992.
# By Adam P. Goucher
def canonise_orientation(rect):

    representation = ""
    ox, oy, length, breadth = rect
    a, b, c, d = 1, 0, 0, 1
    
    chars = "0123456789abcdefghijklmnopqrstuvwxyz"

    for v in xrange(int((breadth-1)/5)+1):
        zeroes = 0
        if (v != 0):
            representation += "z"
        for u in xrange(length):
            baudot = 0
            for w in xrange(5):
                x = ox + a*u + b*(5*v + w)
                y = oy + c*u + d*(5*v + w)
                baudot = (baudot >> 1) + 16*g.getcell(x, y)
            if (baudot == 0):
                zeroes += 1
            else:
                if (zeroes > 0):
                    if (zeroes == 1):
                        representation += "0"
                    elif (zeroes == 2):
                        representation += "w"
                    elif (zeroes == 3):
                        representation += "x"
                    else:
                        representation += "y"
                        representation += chars[zeroes - 4]
                zeroes = 0
                if baudot >= len(chars): g.warn("baudot = " + str(baudot))
                representation += chars[baudot]
    return representation

# check that a layer is available for canonisation
testlayer = g.getlayer()
canonlayer = -1
for i in xrange(g.numlayers()):
    if g.getname(i) == "canonical":
        canonlayer = i
        break
if canonlayer == -1 and g.numlayers() == g.maxlayers():
    g.exit("You need to delete a layer.")
if canonlayer == -1:
    canonlayer = g.addlayer()
    g.setrule('B2/S0')
    g.new("canonical")
g.setlayer(testlayer)

patdict={}
# for i in range(2,xsize+1):
#   for j in range(2,ysize+1):
#     patdict[str([i,j])]=[]
objs=["o!", ] # dot
objlist=[]

for i in range(len(objs)):
  # normalize so that the first ON cell in the list is always (0,0)
  templist=g.parse(objs[i])
  objlist+=[g.transform(templist,-templist[0],-templist[1])]
numobjs=len(objlist)
zonelist=[]
for item in objlist:
  g.setrule("B12345678/S012345678")
  zone=g.evolve(item,2)
  zonelist+=[zone]    # includes cells for object also

g.setrule("LifeHistory")
nearlist=[[i,j] for i in range(-1,xsize+1) for j in range(-1,ysize+1) if i<0 or i>=xsize or j<0 or j>=ysize]
count,x,y,ptr,filledlist,searchlist=0,0,0,0,[],[]
canonlist=[]
while y==0 or len(searchlist)>0:
  overlap=([x,y] in nearlist)
  # place current object
  #############################################
  # TODO:  if there's an overlap, set ptr to max value, then handle all cases with same code at the bottom
  if overlap==0:
    # g.show(str(ptr))
    obj=g.transform(objlist[ptr],x,y)
    objpairs=[[obj[i],obj[i+1]] for i in range(0,len(obj),2)]
    for item in objpairs:
      if item in nearlist:
        overlap=1
        break
    if overlap==0:
      zone=g.transform(zonelist[ptr],x,y)
      zonepairs=[[zone[i],zone[i+1]] for i in range(0,len(zone),2)]
      for item in zonepairs:
        if item in filledlist:
          overlap=2
          break
      if overlap==0:
        g.new("TestPage")
        newptr,newx,newy=ptr+1,x,y
        if newptr>=len(objlist): newptr,newx=0,newx+1
        if newx>=xsize-1: newptr,newx,newy=0,0,y+1
        searchlist.append([newx,newy,newptr,nearlist[:],filledlist[:]]) # add next state to search to the stack
        nearlist+=zonepairs
        filledlist+=objpairs
        for i,j in nearlist: g.setcell(i,j,2)
        minx, maxx, miny, maxy = 999,-999,999,-999
        for i,j in filledlist:
          g.setcell(i,j,1)
          if i<minx:minx=i
          elif i>maxx:maxx=i
          if j<miny: miny=j
          elif j>maxy: maxy=j         
        # Take a snapshot of the current successful placement
        # [e.g., successful if two objects placed, etc.]
        if minx==0 and miny==0:
          keystr=str(maxx+1)+"x"+str(maxy+1)
          if keystr not in patdict: patdict[keystr]=[]
          if filledlist not in patdict[keystr]:
            # Add to pattern list
            patdict[keystr]+=[filledlist[:]]
            # Add canonical representation to list
            g.setlayer(canonlayer)
            g.new("")
            g.putcells([v for cell in filledlist for v in cell])
            canon = canonise_orientation(g.getrect())
            canonlist.append("xs" + str(len(filledlist)) + "_" + canon)
            g.setlayer(testlayer)
        # Now continue searching from where we are
        count+=1
        if count%100==0:
          g.show(str(count))
          g.update()       
        ptr,x=0,x+4
      else:  # the neighbor zone for this object overlaps some already placed object
        ptr+=1
        if ptr>=len(objlist): ptr,x=0,x+1
    else: # the object itself overlaps some already placed object's neighborhood
      ptr+=1
      if ptr>=len(objlist): ptr,x=0,x+1
  else:
    ptr,x=0,x+1
  if x>=xsize-1: ptr,x,y=0,0,y+1
  if y>=ysize-1 or len(searchlist)>=MAXOBJ:
    # we've reached the end of the frame.
    # Time to clear the last placed object, go back to that x,y
    if searchlist==[]:
      g.new("TestPage")
      g.exit("Search is complete.")
    x,y,ptr,nearlist,filledlist=searchlist.pop()

# no point in looking at configurations with no object on the first line
g.new("TestPage")
outstr="Total count = " + str(count)
t=make_text(outstr,"mono")
g.putcells(t,0,-20)
for item in sorted(patdict):
  y=0
  t = make_text(item + " ("+str(len(patdict[item]))+")","mono")
  outstr+="\n" + item + " ("+str(len(patdict[item]))+")"
  g.putcells(t,x-6,y)
  y+=30
  for pat in patdict[item]:
    for cell in pat:
      g.setcell(cell[0]+x,cell[1]+y,1)
    y+=20
  x+=100
g.setclipstr(outstr)

results = "@Dot constellations in fields of size " + str(xsize) + " x " + str(ysize) + "\n"
for patt in canonlist:
  results += patt + "\n"
filename = "dot-field.txt"
try:
  f = open(filename, 'w')
  f.write(results)
  f.close()
except:
  g.warn("Unable to create results file:\n" + filename)

g.exit("Search is complete.")
The 5S project (Smallest Spaceships Supporting Specific Speeds) is now maintained by AforAmpere. The latest collection is hosted on GitHub and contains well over 1,000,000 spaceships.

Semi-active here - recovering from a severe case of LWTDS.

User avatar
77topaz
Posts: 1496
Joined: January 12th, 2018, 9:19 pm

Re: Script request thread

Post by 77topaz » February 20th, 2018, 1:38 am

wildmyron wrote:I haven't written anything related to CataGoLue, but I suspect that anyone who does is (and definitely should be) wary of running scripts that make lots of requests (over many different rules in this case) because that kind of thing can result in disk operations going over quota and therefore CataGoLue being down for the rest of the day.
Are you saying that could happen just from viewing/searching pages, as opposed to uploading things to the server via apgsearch?

wildmyron
Posts: 1542
Joined: August 9th, 2013, 12:45 am
Location: Western Australia

Re: Script request thread

Post by wildmyron » February 20th, 2018, 2:17 am

77topaz wrote:
wildmyron wrote:I haven't written anything related to CataGoLue, but I suspect that anyone who does is (and definitely should be) wary of running scripts that make lots of requests (over many different rules in this case) because that kind of thing can result in disk operations going over quota and therefore CataGoLue being down for the rest of the day.
Are you saying that could happen just from viewing/searching pages, as opposed to uploading things to the server via apgsearch?
I'm thinking of scripts like this one which read dynamically generated pages (see references therein for details of the problem). On reflection, as you're only interested in a single object you can get away with just loading one page per rule the object works in (Limited to rules which have been searched, available from Apple Bottom's list.

In other words: I have no idea what will and what won't have an impact on CataGoLue's operation and therefore I won't be touching anything to do with automated requests to it.
The 5S project (Smallest Spaceships Supporting Specific Speeds) is now maintained by AforAmpere. The latest collection is hosted on GitHub and contains well over 1,000,000 spaceships.

Semi-active here - recovering from a severe case of LWTDS.

User avatar
Apple Bottom
Posts: 1034
Joined: July 27th, 2015, 2:06 pm
Contact:

Re: Script request thread

Post by Apple Bottom » February 20th, 2018, 6:35 am

77topaz wrote:Are you saying that could happen just from viewing/searching pages, as opposed to uploading things to the server via apgsearch?
Of course.

If you want to data-mine Catagolue, it's probably best to use an offline copy. The /textcensus endpoint is a good starting point for that (if you don't need sample soups etc. anyway); I've been using that myself for weekly backups to explore.

If anyone's interested, I can share these on archive.org so that we won't all have to start hitting Catagolue with thousands of requests. ;)
If you speak, your speech must be better than your silence would have been. — Arabian proverb

Catagolue: Apple Bottom • Life Wiki: Apple Bottom • Twitter: @_AppleBottom_

Proud member of the Pattern Raiders!

User avatar
dvgrn
Moderator
Posts: 10612
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Script request thread

Post by dvgrn » February 22nd, 2018, 7:14 pm

On Discord, danny wrote:i want a script that adds or removes a random transition from a rule
that isn't b012a...
Here's a candidate script:

Code: Select all

-- change-random-isotropic-bit-v1.0.lua

local g = golly()
local gp = require "gplus"
local split = gp.split

function fixrule(s)
  return (gp.split(s,":")) -- remove bounded grid spec, if any
end

g.addlayer("temp") -- keep and test the current rule
fixedrule=fixrule(g.getrule())
g.setrule(fixedrule)

bitlist={{-1,-1,256},{0,-1,128},{1,-1,64},{-1,0,32},{0,0,16},{1,0,8},{-1,1,4},{0,1,2},{1,1,1}}
for j = 1, 9 do
    x, y, bit = table.unpack(bitlist[j])
    for i=0, 511 do
        if i&bit>0 then g.setcell(i*5+x,y,1) end
    end
end

g.run(1)
bits={}
ind=1
local rulestr, invstr, revinvstr="","",""
for k=0,2555,5 do
    if g.getcell(k,0)==1 then
        rulestr=rulestr.."1"
        invstr=invstr.."0"
        revinvstr="0"..revinvstr   
    else
        rulestr=rulestr.."0"
        invstr=invstr.."1"
        revinvstr="1"..revinvstr
    end
end

-- build base64 lookup table
local lookupstr="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
bdict={}
for i=1,64 do
    local w=""
    for j=0,5 do
        if (i-1)&2^j>0 then w="1"..w else w="0"..w end
    end
    bdict[w]=lookupstr:sub(i,i)
end

-- decide whether to use standard, inverted, or inverted-reversed bitstring, based on fixedrule
-- (assuming Golly is correctly simulating rules with B0, with or without S8, to avoid strobing)

-- the following won't work for anisotropic non-totalistic rules defined by MAP strings, so
-- we'd have to decode the MAP rulestring into a 512-bit string in that case, check first and last digits.

if string.match(fixedrule,"B0") then rulestr = string.match(fixedrule,"S.*8") and revinvstr or invstr end

g.dellayer()

-- lookup table for isotropic bits
-- Each of the 102 isotropic bits corresponds to one or more bits in the 512-bit binary MAP string.
-- In this script we're assuming the rule in question is isotropic,
-- so technically we just need to check one of the neighborhoods to know the bit setting for all of them.
-- However, we'll check all the anisotropic bits, just to prove that everything is working...
isotropicbits ={{"0",0},
                {"1c",1,4,64,256},
                {"1e",2,8,32,128},
                {"2a",3,6,9,36,72,192,288,384},
                {"2c",5,65,260,320},
                {"2e",10,34,136,160},
                {"2i",40,130},
                {"2k",12,33,66,96,129,132,258,264},
                {"2n",68,257},
                {"3c",69,261,321,324},
                {"3e",42,138,162,168},
                {"3a",11,38,200,416},
                {"3i",7,73,292,448},
                {"3k",98,140,161,266},
                {"3n",13,37,67,193,262,328,352,388},
                {"3j",14,35,74,137,164,224,290,392},
                {"3q",70,76,100,196,259,265,289,385},
                {"3r",41,44,104,131,134,194,296,386},
                {"3y",97,133,268,322},
                {"4c",325},
                {"4e",170},
                {"4a",15,39,75,201,294,420,456,480},
                {"4i",45,195,360,390},
                {"4k",99,141,165,225,270,330,354,396},
                {"4n",71,77,263,293,329,356,449,452},
                {"4j",106,142,163,169,172,226,298,394},
                {"4q",102,204,267,417},
                {"4r",43,46,139,166,202,232,418,424},
                {"4y",101,197,269,323,326,332,353,389},
                {"4t",105,135,300,450},
                {"4w",78,228,291,393},
                {"4z",108,198,297,387},
                {"5c",171,174,234,426},
                {"5e",327,333,357,453},
                {"5a",79,295,457,484},
                {"5i",47,203,422,488},
                {"5k",229,334,355,397},
                {"5n",107,143,167,233,302,428,458,482},
                {"5j",103,205,271,331,358,421,460,481},
                {"5q",110,206,230,236,299,395,419,425},
                {"5r",109,199,301,361,364,391,451,454},
                {"5y",173,227,362,398},
                {"6c",175,235,430,490},
                {"6e",335,359,461,485},
                {"6a",111,207,303,423,459,486,489,492},
                {"6i",365,455},
                {"6k",231,237,363,366,399,429,462,483},
                {"6n",238,427},
                {"7c",239,431,491,494},
                {"7e",367,463,487,493},
                {"8",495}
               }

-- pick a random bit to flip that isn't B012a
bschoice = math.random(2)    -- 1=B, 2=S
-- flipping any bit would look like this:
-- flippedbit = isotropicbits[math.random(#isotropicbits)]

if bschoice==1 then   -- with B bits, don't use the first four
  flippedbit = isotropicbits[math.random(#isotropicbits-4)+4]
else
  flippedbit = isotropicbits[math.random(#isotropicbits)]
end
-- g.note("Chosen bit is "..flippedbit[1])
finalrule = "B"
for i,bitlist in ipairs(isotropicbits) do
  sum=0
  bitname = bitlist[1]
  for j=2,#bitlist do
    value = bitlist[j]
    sum=sum+tonumber(string.sub(rulestr,value+1,value+1))
  end
  if sum==#bitlist-1 then s=bitname else s="" end   -- assuming isotropic rule here
  if bitname==flippedbit[1] and bschoice==1 then 
    -- g.note("B bitname ="..bitname.." and s="..s)
    s = (s=="") and bitname or ""
  end
  finalrule = finalrule..s
end
finalrule = finalrule.."/S"
for i,bitlist in ipairs(isotropicbits) do
  sum=0
  bitname = bitlist[1]
  for j=2,#bitlist do
    value = bitlist[j]+16
    sum=sum+tonumber(string.sub(rulestr,value+1,value+1))
  end
  if sum==#bitlist-1 then s=bitname else s="" end   -- assuming isotropic rule here
  if bitname==flippedbit[1] and bschoice==2 then
    -- g.note("S bitname ="..bitname.." and s="..s)
    s = (s=="") and bitname or ""
  end
  finalrule = finalrule..s
end
-- g.note(finalrule)
g.setrule(finalrule)
canonicalrule = g.getrule()
-- g.note(canonicalrule)
I may clean it up a bit and possibly add some more explanatory comments so other people can do this kind of thing more easily. Might as well post this version as it stands, and see if any bugs or feature requests show up.

As it stands, the script only works for isotropic rules. It will happily analyze an anisotropic MAP rule, but if an isotropic neighborhood isn't present in the MAP rule in all orientations, the script won't count it as one of the isotropic ON bits. It would be easy to change that to something like a majority vote, but there would be a lot of ties. Or it wouldn't take much to have the script change just one anisotropic MAP bit at random.

... Mostly nobody has figured out what anisotropic MAP rules are good for yet, though, so let's just stick with wandering around in isotropic non-totalistic rules for now.

It might be worth having separate scripts for "add a random isotropic bit" and "remove a random isotropic bit".
Maybe have the above be a third separate script, where either adding or removing can happen. Again this would just need a few trivial changes toward the bottom of the script.

If you don't like the random change that the script chooses, Undo will work to get back to the previous rule. The script figures out the rule by experiment every time, so even if you're running a rule table that's equivalent to an isotropic rule, the script should still work fine.

The script I started from was supposed to be working correctly for B0 rules, but I just found and fixed-I-think a bug in it that affected B0../S..8 rules. So obviously that previous script has never been tested too well. If anyone finds a problem with either script, please let me know.

User avatar
77topaz
Posts: 1496
Joined: January 12th, 2018, 9:19 pm

Re: Script request thread

Post by 77topaz » February 22nd, 2018, 7:23 pm

Apple Bottom wrote:If anyone's interested, I can share these on archive.org so that we won't all have to start hitting Catagolue with thousands of requests. ;)
That seems like a good idea, though we'd still need the script to be able to search that database.

User avatar
Apple Bottom
Posts: 1034
Joined: July 27th, 2015, 2:06 pm
Contact:

Re: Script request thread

Post by Apple Bottom » February 23rd, 2018, 4:52 am

77topaz wrote:That seems like a good idea, though we'd still need the script to be able to search that database.
Sure, here's last week's data.

EDIT: and here's this week's.
Last edited by Apple Bottom on February 24th, 2018, 4:30 pm, edited 1 time in total.
If you speak, your speech must be better than your silence would have been. — Arabian proverb

Catagolue: Apple Bottom • Life Wiki: Apple Bottom • Twitter: @_AppleBottom_

Proud member of the Pattern Raiders!

User avatar
Saka
Posts: 3627
Joined: June 19th, 2015, 8:50 pm
Location: Indonesia
Contact:

Re: Script request thread

Post by Saka » February 23rd, 2018, 11:38 pm

Saka wrote:What about a script that picks a random rule from a given rule range?

dani
Posts: 1222
Joined: October 27th, 2017, 3:43 pm

Re: Script request thread

Post by dani » February 24th, 2018, 1:47 am

Saka wrote:
Saka wrote:What about a script that picks a random rule from a given rule range?
+1 support, having a random variant picker that still keeps some dynamics would be pretty cool

User avatar
dvgrn
Moderator
Posts: 10612
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Script request thread

Post by dvgrn » February 24th, 2018, 8:45 am

danny wrote:
Saka wrote:
Saka wrote:What about a script that picks a random rule from a given rule range?
+1 support, having a random variant picker that still keeps some dynamics would be pretty cool
What's the format for specifying the rule range? And is the randomness just 50-50 odds of the presence of each isotropic bit in the "maybe there, maybe not" range, or should it be something more complicated like "remove at least one and at most three isotropic bits from such-and-such list, and add (something similar) from such-and-such other list"?

I might write the script once those questions are answered specifically enough. On the other hand, answering those questions is 90% of the work, so whoever provides the answers almost might as well just go ahead and finish the script.

(Also I'm always trying to recruit more people to do enough scripting to be able to handle this kind of request without thinking it's a big deal... and this request is on the easy end of the scale, so it would be a good practice script. Could just re-use most of the change-one-isotropic-bit script.)

User avatar
Majestas32
Posts: 549
Joined: November 20th, 2017, 12:22 pm
Location: 'Merica

Re: Script request thread

Post by Majestas32 » February 24th, 2018, 12:00 pm

The format will probably be in macbi's partial notation and probs just remove or add 1 bit in the list at random
Searching:
b2-a5k6n7cs12-i3ij4k5j8
b2-a3c7cs12-i

Currently looking for help searching these rules.

User avatar
dvgrn
Moderator
Posts: 10612
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Script request thread

Post by dvgrn » February 24th, 2018, 12:44 pm

Majestas32 wrote:The format will probably be in macbi's partial notation and probs just remove or add 1 bit in the list at random
Macbi describes his partial notation like this:
Macbi wrote:... put a "p" at the front of the rule to indicate that the rule is only being "partially" specified. After that:
If a number isn't present then none of those transitions are allowed.
If a number is present without any letters after it then all of its transitions are allowed.
If a number is present with some letters after it, followed by a minus sign, followed by some more letters, then the letters in the first group must be in the rule, the letters in the second group can't be in the rule, and any letters not mentioned can be in the rule or not.
Also if you just write "p" then it allows all of the rules.

So for example the command "-r pB34a/S2-c3a-c" would search for rules in which any B3 transitions were allowed to be present or not, any B4 were allowed to be present or not except B4a has to be, any S2 transitions would be allowed or not except S2c can't be used, and all S3 transitions can be used or not except S3a must be present and S3c can't be. No other transitions would be allowed.
That seems straightforward enough, and we can just steal the parsing code from LLS_rules.py (and LLS_literal_manipulation.py). EDIT: Took out the LLS_formatting.py stuff, which seems to be only necessary when a rule is being parsed that's already in dictionary form.

Code: Select all

import golly as g
import re

################## from LLS_literal_manipulation
#
possible_transitions = {"0":sorted("c"),
                        "1":sorted("ce"),
                        "2":sorted("cekain"),
                        "3":sorted("cekainyqjr"),
                        "4":sorted("cekainyqjrtwz"),
                        "5":sorted("cekainyqjr"),
                        "6":sorted("cekain"),
                        "7":sorted("ce"),
                        "8":sorted("c")}

def rule_from_rulestring(rulestring, indent = 0, verbosity = 0):

    if rulestring == None:
        return None
    else:

        rule = {}

        rulestring = rulestring.strip()
        original_rulestring = rulestring

        partial_flag = False

        if rulestring[0] in ["p", "P"]:
            partial_flag = True
            if len(rulestring) == 1:
                rulestring = "B012345678/S012345678"
            else:
                rulestring = rulestring[1:]

        rulestring = re.sub(' ', '', rulestring.upper())

        rulestrings = re.split("/", rulestring)

        if len(rulestrings) == 1:
            assert "B" in rulestring or "S" in rulestring, 'Rule string not recognised (no "B" or "S")'
            B_position = rulestring.find("B")
            S_position = rulestring.find("S")
            rulestring = rulestring.strip("BS")
            rulestrings = re.split("[BS]*", rulestring)
            assert len(rulestrings) < 3, "Rule string not recognised"
            if B_position > S_position:
                birth_string = rulestrings[1] if len(rulestrings) == 2 else ""
                survival_string = rulestrings[0]
            else:
                birth_string = rulestrings[0]
                survival_string = rulestrings[1] if len(rulestrings) == 2 else ""
        else:
            assert len(rulestrings) == 2, 'Rule string not recognised (too many "/"s)'
            if "S" in rulestrings[0] or "B" in rulestrings[1]:
                birth_string = rulestrings[1]
                survival_string = rulestrings[0]
            else:
                birth_string = rulestrings[0]
                survival_string = rulestrings[1]

        assert "S" not in birth_string and "B" not in survival_string, "Rule string not recognised"

        birth_string = re.sub('B', '', birth_string).lower()
        survival_string = re.sub('S', '', survival_string).lower()

        assert (birth_string == "" or birth_string[0] in "012345678") and (survival_string == "" or survival_string[0] in "012345678"), "Rule string not recognised"

        if partial_flag:
            variable_number = 0

        for BS_letter, rulestring in zip(["B", "S"], [birth_string, survival_string]):
            transitions = []
            previous_number = 0

            if rulestring != "":
                for position in range(1,len(rulestring)):
                    if rulestring[position] in "012345678":
                        transitions.append(rulestring[previous_number:position])
                        previous_number = position
                transitions.append(rulestring[previous_number:])

            for transition in transitions:
                number_of_neighbours = transition[0]
                if not partial_flag:
                    if len(transition) == 1:
                        for character in possible_transitions[number_of_neighbours]:
                            rule[BS_letter + number_of_neighbours + character] = "1"
                    elif transition[1] == "-":
                        banned_characters = transition[2:]
                        assert all(character in possible_transitions[number_of_neighbours] for character in banned_characters), "Unrecognized character"
                        for character in possible_transitions[number_of_neighbours]:
                            if character in banned_characters:
                                rule[BS_letter + number_of_neighbours + character] = "0"
                            else:
                                rule[BS_letter + number_of_neighbours + character] = "1"
                    else:
                        characters = transition[1:]
                        assert all(character in possible_transitions[number_of_neighbours] for character in characters), "Unrecognized character"
                        for character in possible_transitions[number_of_neighbours]:
                            if character in characters:
                                rule[BS_letter + number_of_neighbours + character] = "1"
                            else:
                                rule[BS_letter + number_of_neighbours + character] = "0"
                else:
                    if len(transition) == 1:
                        for character in possible_transitions[number_of_neighbours]:
                            rule[BS_letter + number_of_neighbours + character] = "rule_variable_" + str(variable_number)
                            variable_number += 1
                    else:
                        characters = transition[1:]
                        if "-" in characters:
                            characters, banned_characters = re.split("-", characters)
                        else:
                            banned_characters = ""

                        for character in possible_transitions[number_of_neighbours]:
                            if character in characters:
                                rule[BS_letter + number_of_neighbours + character] = "1"
                            elif character in banned_characters:
                                rule[BS_letter + number_of_neighbours + character] = "0"
                            else:
                                rule[BS_letter + number_of_neighbours + character] = "rule_variable_" + str(variable_number)
                                variable_number += 1


            for number_of_neighbours in "012345678":
                if not rule.has_key(BS_letter + number_of_neighbours + "c"):
                    for character in possible_transitions[number_of_neighbours]:
                         rule[BS_letter + number_of_neighbours + character] = "0"

        return rule
As a test of the above,

Code: Select all

g.note(str(rule_from_rulestring("pB34a/S2-c3a-c")))
returns a dictionary with all the transitions marked either "0" (don't include), "1" (include), or "rule_variable_#" (maybe include):

Code: Select all

{'S4e': '0', 'S4a': '0', 'S4c': '0', 'S4n': '0', 'S4i': '0', 'S4j': '0', 'S4k': '0', 'S4t': '0', 'S4w': '0', 'S4q': '0', 'S4r': '0', 'S4y': '0', 'S4z': '0', 'B2n': '0', 'B2k': '0', 'B2i': '0', 'B2e': '0', 'B2c': '0', 'B2a': '0', 'S5e': '0', 'S5c': '0', 'S5a': '0', 'S5n': '0', 'S5k': '0', 'S5j': '0', 'S5i': '0', 'S5r': '0', 'S5q': '0', 'S5y': '0', 'B5y': '0', 'B5r': '0', 'B5q': '0', 'B5j': '0', 'B5k': '0', 'B5i': '0', 'B5n': '0', 'B5c': '0', 'B5a': '0', 'B5e': '0', 'S6n': '0', 'B0c': '0', 'S6k': '0', 'S6i': '0', 'S6e': '0', 'S6c': '0', 'S6a': '0', 'B8c': '0', 'S7c': '0', 'S7e': '0', 'B3y': 'rule_variable_9', 'B3q': 'rule_variable_7', 'B3r': 'rule_variable_8', 'B3n': 'rule_variable_6', 'B3i': 'rule_variable_3', 'B3j': 'rule_variable_4', 'B3k': 'rule_variable_5', 'B3e': 'rule_variable_2', 'B3a': 'rule_variable_0', 'B3c': 'rule_variable_1', 'S8c': '0', 'B6c': '0', 'B6a': '0', 'B6e': '0', 'B6k': '0', 'B6i': '0', 'S0c': '0', 'B6n': '0', 'B1e': '0', 'B1c': '0', 'S1c': '0', 'S1e': '0', 'S2c': '0', 'S2a': 'rule_variable_22', 'S2e': 'rule_variable_23', 'S2k': 'rule_variable_25', 'S2i': 'rule_variable_24', 'S2n': 'rule_variable_26', 'B4t': 'rule_variable_18', 'B4w': 'rule_variable_19', 'B4q': 'rule_variable_16', 'B4r': 'rule_variable_17', 'B4y': 'rule_variable_20', 'B4z': 'rule_variable_21', 'B4e': 'rule_variable_11', 'S3n': 'rule_variable_31', 'B4a': '1', 'B4c': 'rule_variable_10', 'B4n': 'rule_variable_15', 'B4i': 'rule_variable_12', 'B4k': 'rule_variable_14', 'B4j': 'rule_variable_13', 'S3y': 'rule_variable_34', 'S3q': 'rule_variable_32', 'S3r': 'rule_variable_33', 'B7c': '0', 'S3i': 'rule_variable_28', 'B7e': '0', 'S3k': 'rule_variable_30', 'S3j': 'rule_variable_29', 'S3e': 'rule_variable_27', 'S3a': '1', 'S3c': '0'}
The rest I'll leave as an exercise for the enterprising reader. Make whatever random choices are needed, and then string the chosen isotropic bits back into a valid format for Golly. Making a valid rulestring is easy -- you just have to get rid of all the BS... except you add one B at the beginning of the B bits, and one /S at the beginning of the S bits.

Then you use g.setrule(), and then immediately ask Golly what the canonical format is with g.getrule().

AforAmpere
Posts: 1334
Joined: July 1st, 2016, 3:58 pm

Re: Script request thread

Post by AforAmpere » February 24th, 2018, 2:53 pm

As a variant of some stuff above, a script that takes a rule, say B3/S2-i34q, and converts it to a list, B3a, B3c, B3e, B3k, etc. for all included transitions. I would like it to be Golly independent.
I manage the 5S project, which collects all known spaceship speeds in Isotropic Non-totalistic rules. I also wrote EPE, a tool for searching in the INT rulespace.

Things to work on:
- Find (7,1)c/8 and 9c/10 ships in non-B0 INT.
- EPE improvements.

User avatar
Extrementhusiast
Posts: 1966
Joined: June 16th, 2009, 11:24 pm
Location: USA

Re: Script request thread

Post by Extrementhusiast » February 24th, 2018, 8:21 pm

What's the most efficient way to store all cells in one layer corresponding to cells in another layer into a cell list? (Or, in other words, is there a way to perform a modified AND-paste where a (possibly specific) nonzero cell state in the pattern being pasted keeps the cell underneath, whereas a state 0 cell in such turns the cell underneath to state 0?)

Let me explain this in more detail. In one layer, I have a ~4096×4096 pattern of four nonzero states. In a second layer, I have an identical pattern, except that all nonzero cells are either state 1 or state 2. I want to add a cell in the first layer into the final cell list iff that cell has the same coordinates as a state 2 cell in the second layer. (Using getcells is a nonstarter in this instance, as the particular shapes I wish to isolate sometimes wrap around each other.) After everything is all said and done, I want the state 2 cells in the second layer to be gone.

I do have the following rules already available to use as tools:
  • A three-state rule to keep all state 1 cells and remove all state 2 cells
  • A three-state rule to remove all state 1 cells and keep all state 2 cells
  • A set of rules to increment all nonzero states by 1 until a certain (adjustable) highest state, after which it becomes state 0 (this is actually just //n in Generations)
  • A 256-state rule to decrement all nonzero states by 1
  • A 256-state rule to set all nonzero states to state 1
I have two candidate methods that I have come up with:

First method:
  1. Remove all state 2 cells from second layer (leaving only the positions of cells we don't want)
  2. Increment second layer by 4 (those cells are now state 5)
  3. Paste second layer onto first (all cells we don't want are now state 5; possibly very time-consuming)
  4. Increment first layer by 1, using state 5 as the upper limit (removing all cells we don't want, but incrementing those that we do)
  5. Decrement first layer by 1 (to compensate)
  6. Store all of first layer into final cell list
Second method:
  1. Store all of second layer as a different cell list
  2. Remove all state 1 cells from second layer
  3. Create a temporary third layer, this time blank
  4. Running through all cells in second layer over its bounding box (possibly very time-consuming):
    1. If this cell is state 0, ignore it (creating the potential for massive waste in the case of an L-shaped region)
    2. Otherwise, check what state the corresponding cell in first layer is, and set cell of that state in third layer (one at a time)
  5. Paste the other cell list onto the second layer (restoring it, as I find using Undo in scripts can get relatively unreliable if performed thousands or millions of times)
  6. Remove all state 2 cells from second layer (as desired in the end)
  7. Store all of third layer into final cell list
Is the first or the second method better, or is there a third method that beats them both?
I Like My Heisenburps! (and others)

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

Re: Script request thread

Post by Scorbie » February 24th, 2018, 9:32 pm

I like the first method better, but can't you just blow up the state 5 cells with a custom rule?
I'm not an expert, but I think looping through cells should be slower than letting Golly do the job.

User avatar
77topaz
Posts: 1496
Joined: January 12th, 2018, 9:19 pm

Re: Script request thread

Post by 77topaz » March 7th, 2018, 7:13 pm

How about a script that takes a single switch engine and then tries every possible combination of that switch engine plus N objects from a certain list of small stable objects (e.g. block, blinker, beehive, etc.) and reports when it finds something interesting like a stable puffer?

The semi-secret extra goal of this would be to also (help) find out whether a one-engine Cordership is possible.

Post Reply