Hacking apgsearch

For scripts to aid with computation or simulation in cellular automata.
Post Reply
wildmyron
Posts: 1542
Joined: August 9th, 2013, 12:45 am
Location: Western Australia

Hacking apgsearch

Post by wildmyron » October 15th, 2014, 3:09 am

This thread is to discuss variations of apgsearch which go beyond incremental improvements and minor changes to the program.

First off, a few ideas:
wildmyron wrote:
dvgrn wrote:
wildmyron wrote:I also have been searching for glider reactions with a collection of common ash objects. There are a lot of reactions like this (not just switch engines)...
Are you generating ash object positions with a script, and classifying them with apgsearch? I've been thinking about trying something like this, but other things keep getting in the way.
Yes, I have. I've been meaning to describe what I'm doing and I'd like to get some feedback. At this stage I haven't posted the code to do it because I'm still making changes to the soup function, and that causes the results for a given soupid to change.
In the next post I will present apgsearch-blonksoup.py
dvgrn wrote:I'm interested in a couple of possible variants of this idea for modifying apgsearch. One is an exhaustive search for switch engines in the glider+2 blonks search space, for some reasonable MxN (to be gradually increased). Another is a search for switch engines and other interesting things, starting from guaranteed glider-constructible pieces (a few well-spaced small still lifes plus some number of incoming gliders and/or *WSSes, to get an active reaction going.) That kind of thing might go in a new thread called something like "Hacking apgsearch", if you want to go that route.
dvgrn wrote:A third search project, which might be most interesting for Sparse Life early-universe analysis, is a recursive enumeration of single glider collisions into blonks. Let G+blonk settle into quiescence, then hit each possible ash pattern with every possible new G from every direction... collect all those settled ash patterns, and repeat until switch engines appear. Seems as if this should only take about four or five gliders...!
NickGotts wrote:I've uploaded to "Golly scripts" in the Scripts forum, code to test whether the current position is "quiescent", i.e. consists solely of still lifes, oscillators, gliders and *WSSs, with the gliders and *WSSs being so placed that they will never interact with each other, or with any of the still lifes or oscillators. It's much less ambitious than the stabilisation test in apgsearch, but serves my purposes (mainly, looking for switch-engines in the output of multiple patterns) well.
NickGotts posted testquiescence which could help reduce the higher incidence of error correction in apgsearch-blonksoup. This occurs because an escaping glider from a stabilised reaction can collide with a blonk placed elsewhere in the original pattern.
wildmyron wrote:
Extrementhusiast wrote:How well would the apgsearch method work on a rule like sansdomino_s13?
My impression is that it would work really well after some adaptation. There are several places where semi-totalistic rules and rule notation are assumed. In particular, the auxillary rules and various tests that check which rule is being used. Some work would be required to generate the auxillary rules and also account for assumptions about the rule string (such as the test for glider existence). You could either create custom rules for APG_CoalesceObjects, APG_ClassifyObjects, and APG_ContagiousLife; or modify the rule generator to be able to handle rules which are not semi-totalistic. My impression is that APG_ClassifyObjects would require the most work to adapt, but I haven't thought through the details. I have been thinking about similar adaptations for multi-state rules, but I suspect that's even more complicated.
I have an in-progress implementation of apgsearch for a non-totalisitic rule - auxillary rules generated by hand. APG_ClassifyObjects only needs adaptation if different common objects need to be handled with it. APG_CoalesceObjects turned out to be harder than I thought because the "bridge inductor" rules are more complicated than simply testing number of alive neighbours.

With all of these ideas for variations, it would also be nice to have the core stabilisation, object identification, and rule generation code in a separate library which each of these ideas could draw on, rather than having it incorporated into each individual variation.
Last edited by wildmyron on October 15th, 2014, 3:24 am, edited 1 time in total.
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.

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

Re: Hacking apgsearch

Post by wildmyron » October 15th, 2014, 3:23 am

Here is apgsearch-blonksoup.py

The main difference with the original apgsearch is that the 16x16 random soup is replaced with a random field of blocks and blinkers, and a single glider. This version creates a relatively sparse distribution of objects, placing up to 25 objects on a diagonal rectangle of size 128x256 (measured in full diagonals - I think). The glider is positioned to collide with one blonk in such a way as to produce a large explosion due to the sparsity of the field of blonks. Without this step, very many iterations result in rather boring outcomes. Each additional blonk placed is (conservatively) tested to ensure it won't interact with any other blonks.

The rate of incidence of rare objects is lower than for the original, but everything detected is easily synthesizable - although clean up for SL synthesis would be laborious if the naive approach is used.
Attachments
apgsearch-blonksoup-v0.3.zip
apgsearch-blonksoup-v0.3.py
(20.31 KiB) Downloaded 1043 times
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.

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

Re: Hacking apgsearch

Post by wildmyron » October 22nd, 2014, 11:47 am

wildmyron wrote:
wildmyron wrote:
Extrementhusiast wrote:How well would the apgsearch method work on a rule like sansdomino_s13?
My impression is that it would work really well after some adaptation. There are several places where semi-totalistic rules and rule notation are assumed. In particular, the auxillary rules and various tests that check which rule is being used. Some work would be required to generate the auxillary rules and also account for assumptions about the rule string (such as the test for glider existence). You could either create custom rules for APG_CoalesceObjects, APG_ClassifyObjects, and APG_ContagiousLife; or modify the rule generator to be able to handle rules which are not semi-totalistic. My impression is that APG_ClassifyObjects would require the most work to adapt, but I haven't thought through the details. I have been thinking about similar adaptations for multi-state rules, but I suspect that's even more complicated.
I have an in-progress implementation of apgsearch for a non-totalisitic rule - auxillary rules generated by hand. APG_ClassifyObjects only needs adaptation if different common objects need to be handled with it. APG_CoalesceObjects turned out to be harder than I thought because the "bridge inductor" rules are more complicated than simply testing number of alive neighbours.
Attached is an implementation of apgsearch for the non-totalistic rule on a hexagonal neighbourhood - 22da, aka hex_B2/S2op.

This is a proof of concept showing the adaptability of apgsearch. I am continuing work on the non-totalistic Moore neighbourhood version, focusing on the rule generator as manually writing auxillary rules is rather tedious.

This hexagonal rule implementation was rather interesting - I had some extra spare time to devote to it but didn't get around to everything I wanted to adapt. In particular the ContagiousLife rule which psuedo_bangbang() depends on isn't included. I don't think I'll spend any more time on that because it doesn't seem to be necessary in this rule - particularly as there are no still life objects. As noted in the comments, the naturally occurring rake significantly slows down the search speed, however I did run one search to a million soups. I think all the small oscillators except for the p10 showed up and a range of puffers where detected. A few puffers have multiple descriptions due to near by gliders which seem to get caught up by linearlyse().

The IdentifyGliders_22da rule may be useful elsewhere, and it shows how similar techniques can be used to detect other small gliders. I chose to only detect the glider in one phase, as this reduced the potential for misidentification of other objects. As a result, CoalesceObjects_22da requires 17 states so that it can be used to step the pattern and identify all gliders in each phase.

Edit: I forgot to mention that the common objects list has only been populated with a few objects, and as such the scoring is pretty much useless. Given the lack of diversity of naturally occurring objects in 22da I don't think I'll put any more work into it.

Edit: I cut a few corners in designing the IdentifyGliders_22da rule - it was no where near as rigorous as the original. This is entirely due to the size of the 22da glider and the choice to retain the 4 step process by using two central nodes to detect the glider rather than one. The result of this is that some almost glider patterns - particularly gliders which are not yet quite fully emitted by a puffer - are partly identified as a glider which may then be evolved into incorrect patterns during the rest of the glider detection.

In two of its phases, the 22da glider fits within a size two hexagonal neighbourhood of a central cell, so a similar rigorous process is possible in 7 steps rather than 4. However, my current estimate for how many states are required is 33 and I don't have the motivation at the moment to repair the situation by implementing the rule.
Attachments
apgsearch-hex-22da.zip
apgsearch implementation for 22da
(16.05 KiB) Downloaded 960 times
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
dvgrn
Moderator
Posts: 10610
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Hacking apgsearch

Post by dvgrn » November 17th, 2014, 10:29 am

wildmyron wrote:The main difference with the original apgsearch is that the 16x16 random soup is replaced with a random field of blocks and blinkers, and a single glider.
I'm getting ready to adjust apgsearch-blonksoup to run an exhaustive search on small constellations of common ash objects -- no random sampling, just every possible single-glider collision with every possible well-spaced m-by-n constellation.
TestPage-10x7.mc.gz
Exhaustive enumeration of 10x7 constellations of common still lifes (and all smaller rectangles)
(1.54 MiB) Downloaded 1041 times
Here's the rather suboptimal code that generated the above stamp collection:

build-constellation-v10.py: [fair warning: this script clobbers the clipboard with output statistics]

Code: Select all

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

MAXOBJ=10
xsize,ysize=10,7

def getinp(s):
###########################
  temp=g.getcell(x,y)
  g.setcell(x,y,5)
  g.show(s+"Ptr:"+str(ptr)+" x:" +str(x)+" y:"+str(y))
  g.fit()
  g.update()
  g.setcell(x,y,temp)
  # return
  k=g.getevent()
  count=0
  while k=="":
    sleep(.01)
    k=g.getevent()
    count+=1
  if k=="key q none": g.exit()
  return
###########################

patdict={}
# for i in range(2,xsize+1):
#   for j in range(2,ysize+1):
#     patdict[str([i,j])]=[]
objs=["2o$2o!","2o$obo$bo!","b2o$obo$bo!","bo$obo$b2o!","bo$obo$2o!", # block and boat
         "b2o$o2bo$b2o!","bo$obo$obo$bo!","bo$obo$bo!","b2o$o2bo$o2bo$b2o!", # beehive, tub, pond
         "2o$obo$b2o!", "b2o$obo$2o!", "b2o$o2bo$obo$bo!", # ship, loaf1
         "b2o$o2bo$bobo$2bo!","2bo$bobo$o2bo$b2o!","bo$obo$o2bo$b2o!"] # other loaves
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")
  neighbors=g.evolve(item,1)
  g.setrule("B2345678/S012345678")
  zone=g.evolve(neighbors,1)
  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,[],[]
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]: patdict[keystr]+=[filledlist[:]]
        # 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
  # getinp("Overlap type " + str(overlap) + ".  Lenlist="+str(len(searchlist))+".")
  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)
g.exit("Search is complete.")
So according to this script, there are 226059 different ways to pack blocks, boats, beehives, tubs, ponds, ships and loaves into a 10x7 or smaller rectangle, with no induction allowed (I have to be clear about that, because otherwise the list is much longer, but then constellations are a good bit harder to find a glider recipe for in general, especially a slow recipe).

Rotations and reflections count, but translations of smaller bounding boxes inside 10x7 are only counted once, of course. This way it's only necessary to hit all these constellations with gliders from one direction, and we'll have tried everything in this particular search space.

If someone could prove me wrong and locate well-spaced packing #226060, I would be most grateful (and I'd fix my script before I try any longer runs.) Also if someone feels like adding blinkers and/or long boats, barges, aircraft carriers, eaters, etc., please go ahead -- it should mostly be a trivial change to the object list, though P2 objects would require a little more work to get the right neighborlist and zonelist.

EDIT: Come to think of it, it's just a preliminary objp2 = g.evolve(item, 1) with a rule of B3/S123, before the other two g.evolve steps. Then the patterns would have to be built using objp2list instead of objlist, and the blinkers would come out looking like plus signs and would need some post-processing to produce all 2^(N-1) phase options for N blinkers. Or with a little more work you could get all the phases listed as separate constellation, by adding both blinker phases to the object list but constraining the system to add only phase-0 P2 objects unless a P2 object has previously been added.

----------------------------------------

I doubt I'll attempt anything as big as a 10x10 search -- too many ways to arrange blocks once you can get up to nine of them into the frame. 9x9 might not be too unreasonable, but at some point very soon the script should be altered to dump constellations to a file or files, instead of keeping them all in memory.

The primary purpose of all this is to look for a very large volume of cheap glider splitters and glider turners with different timings -- probably using something like a 9x16 rectangle with MAXOBJ=2 or 3. As a byproduct, we might improve a scattering of glider recipes for high-bit-count still lifes, and maybe see a few interesting switch engines as well.

I suppose it would be nice to include a check for the same constellation reappearing, unmoved or moved or rotated or with extra junk -- but I think someone else will have to take care of that part of the coding.

User avatar
codeholic
Moderator
Posts: 1147
Joined: September 13th, 2011, 8:23 am
Location: Hamburg, Germany

Re: Hacking apgsearch

Post by codeholic » November 25th, 2014, 5:56 pm

I tried to run a dirtily hacked version of apgsearch in the hope to find alternative backends for the pufferfish.

Code: Select all

diff --git a/apgsearch-2014-10-14-v0.5.py.bak b/apgsearch-2014-10-14-v0.5.py
index d06562f..a9a52ab 100755
--- a/apgsearch-2014-10-14-v0.5.py.bak
+++ b/apgsearch-2014-10-14-v0.5.py
@@ -40,6 +40,8 @@ import hashlib
 import datetime
 import os
 
+PUFFERFISH = g.parse('3bo7bo$2b3o5b3o$b2o2bo3bo2b2o$3b3o3b3o2$4bo5bo$2bo2bo3bo2bo$o5bobo5bo$2o4bobo4b2o$6bobo$3bobo3bobo$4bo5bo!')
+
 # Takes approximately 350 microseconds to construct a 16-by-16 soup based
 # on a SHA-256 cryptographic hash in the obvious way.
 def hashsoup(instring):
@@ -59,7 +61,9 @@ def hashsoup(instring):
                 thesoup.append(k + 8*(j % 2))
                 thesoup.append(int(j / 2))
 
-    g.putcells(thesoup, -8, -8)
+    g.putcells(thesoup, 0, 0)
+    g.putcells(g.transform(thesoup, 0, 0, -1), 0, 0)
+    g.putcells(PUFFERFISH, -7, -12)
 
 
 # Obtains a canonical representation of any oscillator/spaceship that (in
@@ -1180,6 +1184,7 @@ class Soup:
                    "xp46_330279cx1aad3zx4e93x855bcy8cc": ("trans-twin-bees-shuttle", 35),
                    "yl144_1_16_afb5f3db909e60548f086e22ee3353ac": ("block-laying switch engine", 16),
                    "yl384_1_59_7aeb1999980c43b4945fb7fcdb023326": ("glider-producing switch engine", 17),
+                   "yl12_1_8_c7310da81295b6611e6e4e34a80a5523": ("pufferfish", 0),
                    "xp10_9hr": ("[HighLife] p10", 6),
                    "xp7_13090c8": ("[HighLife] p7", 9),
                    "xq48_07z8ca7zy1e531": ("[HighLife] bomber", 9),
Unfortunately, large amount of pufferfish makes it prohibitedly slow (approx. 2 soups per second). Is it possible to tweak it in a way that it would run faster?
Ivan Fomichev

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

Re: Hacking apgsearch

Post by wildmyron » November 25th, 2014, 11:09 pm

codeholic wrote:Unfortunately, large amount of pufferfish makes it prohibitedly slow (approx. 2 soups per second). Is it possible to tweak it in a way that it would run faster?
I haven't tried running your version, so my comments are just guesses. Any soup in which the pufferfish survives will run until stabilisation fails, and from your description I imagine that a significant fraction of soups result in an unscathed pufferfish. In this scenario all of the stabilisation tests are time consuming and you're probably better off running the soup for some predetermined number of generations, say 2^15. You can do this by replacing stabilise3() with a single g.step() (with appropriate rule, base and step of course). If this results in a large number of pathological objects being detected, it's because the rest of the soup hasn't stabilised yet and you'll need to increase the step, or disable error correction (set skipErrorCorrection to True).

Then, when APGCoalesceObjects and APGClassifyObjects are run, they would run for the maximum allowed which is 2^12. This is completely unnecessary because they only need to run long enough to connect up isolated parts of oscillators and ships, which is guaranteed to happen within one period of the highest period object in the combined ashes (and perhaps a bit longer for APGClassifyObjects where very large objects are present). You could safely reduce stepsize to around 7 and you should get equivalent results. With the replacement of stabilise3() above, you'll need to manually set stepsize instead, so just set it to 6, 7 or 8. You'll also want to remove the conditional test for infinite growth in census() seeing as it's pretty much guaranteed to be present. If you seem to be getting lots of partial objects, you can increase the stepsize to improve object detection.
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
codeholic
Moderator
Posts: 1147
Joined: September 13th, 2011, 8:23 am
Location: Hamburg, Germany

Re: Hacking apgsearch

Post by codeholic » November 26th, 2014, 2:29 am

Thank you for advice! I'll try it out.
Ivan Fomichev

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

Re: Hacking apgsearch

Post by wildmyron » November 26th, 2014, 3:26 am

codeholic wrote:Thank you for advice! I'll try it out.
You're welcome.

On reflection, I think that evolving the pattern for 2^12 or 2^13 gen is almost certain to be sufficient, in place of the 2^15 I mentioned above.

--------
dvgrn wrote:I'm getting ready to adjust apgsearch-blonksoup to run an exhaustive search on small constellations of common ash objects -- no random sampling, just every possible single-glider collision with every possible well-spaced m-by-n constellation.
I'm very interested to see the results of this search when you get to the point of running it. Having less than a million constellations I imagine that it will be pretty quick to run through apgsearch. Might be a different story with additional components / larger area allowed. Have you had any thought to dividing the workload?
dvgrn wrote:If someone could prove me wrong and locate well-spaced packing #226060, I would be most grateful (and I'd fix my script before I try any longer runs.)
I had a careful look through the output for blocks only, and the output for blocks and beehives in a smaller area. Looks to me as though you've comprehensively enumerated all possible constellations.

I also like the way you generate the mask. You may have noticed the block mask in apgsearch-blonksoup is larger than it needs to be. The reason for this is that I didn't see a way to place a new block near an existing blinker using the minimal mask, while ensuring that the blinker in its alternate phase didn't interact with the block. As you allude to above, using a pattern with all active cells (plus sign for the blinker) to check against mask for new objects is a solution to that problem.

--------

My work on the version of apgsearch for isotropic, semi-totalistic rules has stalled, but the rule generator is complete. I do intend to get back to this and post the completed project, but in the meantime if anyone is interested I can post the rule generator on its own.
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
dvgrn
Moderator
Posts: 10610
Joined: May 17th, 2009, 11:00 pm
Location: Madison, WI
Contact:

Re: Hacking apgsearch

Post by dvgrn » November 26th, 2014, 2:10 pm

wildmyron wrote:
dvgrn wrote:I'm getting ready to adjust apgsearch-blonksoup to run an exhaustive search on small constellations of common ash objects -- no random sampling, just every possible single-glider collision with every possible well-spaced m-by-n constellation.
I'm very interested to see the results of this search when you get to the point of running it. Having less than a million constellations I imagine that it will be pretty quick to run through apgsearch. Might be a different story with additional components / larger area allowed. Have you had any thought to dividing the workload?
Yes, I did the exhaustive enumeration specifically to make it possible to distribute the work for larger m-by-n.

The "easy" way to subdivide the workload would be to do a preliminary run to list all starting constellations with MAXOBJ=1 or 2 or 3 (depending on how many subtasks we want). Then the search from each starting constellation can be run on a separate computer, with the results sent back to a central server.

This is just the kind of job that it would be nice to be able to post somewhere as a list of reasonable-sized tasks, along with an associated script. The script could download the next available task, process it, and return the results to the server, which would verify it and remove that task from the list. The server would need the usual reset for stranded jobs -- if a result doesn't come back for a particular task in a reasonable amount of time, make it available again.

Probably it wouldn't be worth producing an enumeration of actual constellations this way -- for even moderate-sized m and n, this would be too many gigabytes of data getting collected at one point. Better to report back only the tiny fraction of collisions that are interesting in some way, along the same general lines as apgsearch.

------------------------------

For now, it's a lot easier to keep everything on one computer -- it looks as if with a little patience it will be possible to generate a very nice collection of glider turners and splitters, with the assurance that there are no others that fit inside a given m-by-n rectangle.

Part of my motivation for finishing this project has just disappeared, though -- simsim314 just posted a really nice collection of sub-10x10 two-still-life glider splitters on another thread.

User avatar
praosylen
Posts: 2443
Joined: September 13th, 2014, 5:36 pm
Location: Pembina University, Home of the Gliders
Contact:

Re: Hacking apgsearch

Post by praosylen » December 17th, 2015, 4:24 pm

Hello everyone,

I made a hacked version of apgsearch that works on all 2-state isotropic Moore-neighborhood cellular automata (tlife, SansDomino rules, etc.). Here it is:
apgsearch-2015-12-17-v0.54+0.1i.py.zip
(150.75 KiB) Downloaded 984 times
Instructions on how to use it: First, decompress the file (on Mac, clicking on it should work; on Windows, I'm not sure; maybe try right-clicking?). Save a .rule file for the rule you want to census in Golly's Rules folder (the filename minus extension and the rulename must match exactly, even capitalization; trees do not work, only tables). Either drag and drop the icon for the script out of Downloads onto Golly's icon or an open window of Golly; go to the File menu on Golly and select "Run Script..." and find its location; or copy it into Golly's Scripts folder and click on it in a window of Golly. Then, input the number of soups, the seed, the rule (capitalization matters), and the symmetry.

It is important that the .rule file be manually put into Golly's Rules folder, otherwise Golly won't be able to find it, and that the table contain no lines that start with variables (don't be fooled by the extraneous code in saveClassifyObjects). Apart from that, not much has changed from the regular apgsearch versions. NOTE: This version does NOT report its findings to Catagolue, as it might break Catagolue's safety measures (I figured I shouldn't test it).

The only parts of the code I changed were the comments, the RuleGenerator class (to handle non-totalistic rules), the common_names dict (to recognize some tlife objects), and the object-scoring code (to filter out 2xp160s and possibly p320s in tlife). If there are any bugs, tell me and I will try to fix them.

It does run slower than the other versions of apgsearch, because it cannot use HashLife, so has to opt for the (comparatively slower) RuleLoader algorithm. Also, do not try to run it on the rule butterfly, because all of the puffers result in it running about 2 or 3 soups per second.
Last edited by praosylen on December 17th, 2015, 10:29 pm, edited 1 time in total.
former username: A for Awesome
praosylen#5847 (Discord)

The only decision I made was made
of flowers, to jump universes to one of springtime in
a land of former winter, where no invisible walls stood,
or could stand for more than a few hours at most...

drc
Posts: 1664
Joined: December 3rd, 2015, 4:11 pm

Re: Hacking apgsearch

Post by drc » December 17th, 2015, 5:14 pm

A for awesome wrote:Hello everyone,

I made a hacked version of apgsearch that works on all 2-state isotropic Moore-neighborhood cellular automata (tlife, SansDomino rules, etc.). Here it is:
apgsearch-2015-12-17-v0.54+0.1i.py.zip
Instructions on how to use it: First, decompress the file (on Mac, clicking on it should work; on Windows, I'm not sure; maybe try right-clicking?). Save a .rule file for the rule you want to census in Golly's Rules folder (the filename minus extension and the rulename must match exactly, even capitalization; trees do not work, only tables). Either drag and drop the icon for the script out of Downloads onto Golly's icon or an open window of Golly; go to the File menu on Golly and select "Run Script..." and find its location; or copy it into Golly's Scripts folder and click on it in a window of Golly. Then, input the number of soups, the seed, the rule (capitalization matters), and the symmetry.

It is important that the .rule file be manually put into Golly's Rules folder, otherwise Golly won't be able to find it. Apart from that, not much has changed from the regular apgsearch versions. NOTE: This version does NOT report its findings to Catagolue, as it might break Catagolue's safety measures (I figured I shouldn't test it).

The only parts of the code I changed were the comments, the RuleGenerator class (to handle non-totalistic rules), the common_names dict (to recognize some tlife objects), and the object-scoring code (to filter out 2xp160s and possibly p320s in tlife). If there are any bugs, tell me and I will try to fix them.

It does run slower than the other versions of apgsearch, because it cannot use HashLife, so has to opt for the (comparatively slower) RuleLoader algorithm. Also, do not try to run it on the rule butterfly, because all of the puffers result in it running about 2 or 3 soups per second.
I still get the error:

line 37 in <module>
from glife import rect, pattern
ImportError: No module named glife

User avatar
praosylen
Posts: 2443
Joined: September 13th, 2014, 5:36 pm
Location: Pembina University, Home of the Gliders
Contact:

Re: Hacking apgsearch

Post by praosylen » December 17th, 2015, 6:04 pm

drc wrote: I still get the error:

line 37 in <module>
from glife import rect, pattern
ImportError: No module named glife
I don't know exactly what's going on, but my suggestion would be to randomly click on one of Golly's built-in scripts before running the hacked apgsearch, because I remember reading in some other topic about a similar error and that was the solution given.
former username: A for Awesome
praosylen#5847 (Discord)

The only decision I made was made
of flowers, to jump universes to one of springtime in
a land of former winter, where no invisible walls stood,
or could stand for more than a few hours at most...

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

Re: Hacking apgsearch

Post by Scorbie » December 17th, 2015, 6:46 pm

drc wrote:I still get the error:

line 37 in <module>
from glife import rect, pattern
ImportError: No module named glife
That's related to python's importing system. As a permanent fix, copy the glife folder (and its files) to where your script is in. Like this:

Code: Select all

My_Script_Directory
ㄴapgsearch
ㄴglife
  ㄴ__init__.py
  ㄴ....

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

Re: Hacking apgsearch

Post by Saka » December 18th, 2015, 1:49 am

A for awesome wrote:Hello everyone,

I made a hacked version of apgsearch that works on all 2-state isotropic Moore-neighborhood cellular automata (tlife, SansDomino rules, etc.). Here it is:(attachment)
MAN that is useful! Does it upload the haul to the server?
EDIT: It renders this:

Code: Select all

x = 38, y = 87, rule = tlife
16bo$6b2o7bobo$5bo2bo6bobo$5bobo8bo$6bo7$28b2o$28b2o6$b2o13b3o17bo$o2b
o13bo17bobo$b2o32bobo$9b2o25bo$9b2o7$25b2o$25b2o55$17b3o$18bo!
As "oversized"

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

Re: Hacking apgsearch

Post by Scorbie » December 18th, 2015, 10:03 am

A for awesome wrote:Hello everyone,

I made a hacked version of apgsearch that works on all 2-state isotropic Moore-neighborhood cellular automata (tlife, SansDomino rules, etc.). Here it is:
Glad to see this :) Thanks for your work! (I think wildmyron worked on somthing similar too...?)
For some reason, x-rule-pre is REALLY slow... (0 soups/second, took about two hours to process 600 soups.) Is it just my really old computer? Some strange things:
1 )Most of the time apgsearch behaves just like normal apgsearch (I mean flickering), but the screen is empty. (pop=0)
2) There were a whole lot of beehives in the unidentified objects page that shows up after the search is finished.
3) There's xq1_0 that is saved as the following:

Code: Select all

x = 40, y = 40, rule = x-rule-pre
b$
b$
b$
b$
b$
!
Which comes out empty when opened in golly.

EDIT: More things to note:
No gliders marked as xq
No oscilators noted other than the blinker, which itself is unstable.

User avatar
praosylen
Posts: 2443
Joined: September 13th, 2014, 5:36 pm
Location: Pembina University, Home of the Gliders
Contact:

Re: Hacking apgsearch

Post by praosylen » December 18th, 2015, 10:21 am

Scorbie wrote: For some reason, x-rule-pre is REALLY slow... (0 soups/second, took about two hours to process 600 soups.) Is it just my really old computer? Some strange things:
1 )Most of the time apgsearch behaves just like normal apgsearch (I mean flickering), but the screen is empty. (pop=0)
2) There were a whole lot of beehives in the unidentified objects page that shows up after the search is finished.
3) There's xq1_0 that is saved as the following:

Code: Select all

x = 40, y = 40, rule = x-rule-pre
b$
b$
b$
b$
b$
!
Which comes out empty when opened in golly.
1. Wow, that's weird... Maybe I should include a rule to expunge dominoes and duoplets in the next version (maybe also the common x-pre gliders)?
2. No idea what that's about; will try to figure out what's going on.
3. What does the sample soup produce? Is there an unusual oscillator produced? If so, it probably got separated improperly.

Would you check whether the rule table is correct? (Same as the one in your Rules folder; no lines starting with variables.) There might be an error pertaining to the ClassifyObjects or ContagiousLife rules; either of those issues could cause that error.
former username: A for Awesome
praosylen#5847 (Discord)

The only decision I made was made
of flowers, to jump universes to one of springtime in
a land of former winter, where no invisible walls stood,
or could stand for more than a few hours at most...

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

Re: Hacking apgsearch

Post by Scorbie » December 18th, 2015, 10:29 am

Before any diagnosis, I may have done something wrong when I made that profile thingy. I'll get a new version and run it again... Please wait for a moment...
A for awesome wrote:Would you check whether the rule table is correct? (Same as the one in your Rules folder; no lines starting with variables.) There might be an error pertaining to the ClassifyObjects or ContagiousLife rules; either of those issues could cause that erro
Yikes. Didn't know that a rule file should not have a line starting with variables... But any variable declaration should start with a variable isn't it? Do you mean that the rule file should have no variables?

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

Re: Hacking apgsearch

Post by Scorbie » December 18th, 2015, 10:34 am

Stripped the last post for clarity.
Moved x-rule-pre to Golly's Rules folder instead of my custom rule folder, used vanilla apgsearch hack and umm...

Code: Select all

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 11807, in <module>
    soup.rg.saveAllRules()
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 602, in saveAllRules
    self.saveClassifyObjects()
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 1225, in saveClassifyObjects
    i = lines1[q]
IndexError: list index out of range
Whoops...

User avatar
praosylen
Posts: 2443
Joined: September 13th, 2014, 5:36 pm
Location: Pembina University, Home of the Gliders
Contact:

Re: Hacking apgsearch

Post by praosylen » December 18th, 2015, 10:43 am

Scorbie wrote:Yikes. Didn't know that a rule file should not have a line starting with variables... But any variable declaration should start with a variable isn't it? Do you mean that the rule file should have no variables?
Variable declarations are allowed; and they start with "var", not a variable name. Transitions in the rule table were too hard to implement (I may allow them in the next version).
Scorbie wrote:Stripped the last post for clarity.
Moved x-rule-pre to Golly's Rules folder instead of my custom rule folder, used vanilla apgsearch hack and umm...

Code: Select all

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 11807, in <module>
    soup.rg.saveAllRules()
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 602, in saveAllRules
    self.saveClassifyObjects()
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 1225, in saveClassifyObjects
    i = lines1[q]
IndexError: list index out of range
Whoops...
No idea what that's about; could you post your .rule file? I will get on it as soon as I have access to a non-school computer; probably about 4 hours from now.

P.S. Why did you put it in your Perl scripts folder? It's a Python script! (I don't think it makes any real difference, though.)
former username: A for Awesome
praosylen#5847 (Discord)

The only decision I made was made
of flowers, to jump universes to one of springtime in
a land of former winter, where no invisible walls stood,
or could stand for more than a few hours at most...

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

Re: Hacking apgsearch

Post by wildmyron » December 18th, 2015, 3:09 pm

Scorbie wrote:
A for awesome wrote:Hello everyone,

I made a hacked version of apgsearch that works on all 2-state isotropic Moore-neighborhood cellular automata (tlife, SansDomino rules, etc.). Here it is:
Glad to see this :) Thanks for your work! (I think wildmyron worked on somthing similar too...?)
Indeed, great to see some interest in this. I have indeed worked on something similar and despite much hesitation I'm just going to post it in it's current state in the hope that it can help further development. I took a different approach to the rule generating - it's quite interesting to see the way this version interprets an existing rule file and generates the auxillary rules from that. Instead I based the rule generator on EricG's rule table generating script for non-totalistic rules "Hensel-TableGen.py".

The rule generator class is actually in a separate file which makes it easier to use parts of it for other purposes (e.g. isotropicRulegen.py does basically the same as Hensel-TableGen.py but using the http://www.ibiblio.org/lifepatterns/neighbors2.html version of Hensel's notation. I had intended to seperate the soup generating and censusing code into a separate file as well and then have a totalistic and non-totalistic version of apgsearch with custom code for each and all the common code in separate files, but I'm not very good at finishing off such projects. I have a different version of APG_ExpungeObjects - named APG_ExpungeSmallSL - which handles dominoes and I-trominoes as well as blocks. This file has to be manually copied to the rules folder because it's not created by the rule generator. Another version of that identifies SL checkers instead of I-trominoes. Using the most appropriate expunge rule for the rule being searched is one of the stumbling blocks on my TODO list.

Another stumbling block was how to deal with rules with common puffers. As a stop gap I've reduced the maximum step that the auxillary rules run for which reduces the size of the puffers that need to be censused and disabled error correction. Additionally, I believe the stabilisation detection should also be customised for the rule being searched - particularly so that the common oscillator periods don't thwart the stabilisation detection. Those p160 oscillators in tlife are a real headache in that regard.

As far as object scoring goes - that's currently a real mess. I was very tempted to remove it completely and just focus on the census results rather than the soups. I've also tried to improve the non-interacting spaceship separation somewhat, but that's still unreliable.

As for performance - it's usable for butterfly. I see about 20-30 soups/sec with C1 soups and more for D8_4. Other rules / symmetries are variable in their performance depending on puffer frequency and common oscillator periods.
Attachments
apgsearch-isotropic-v0.2.zip
WIP
(20.65 KiB) Downloaded 906 times
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
praosylen
Posts: 2443
Joined: September 13th, 2014, 5:36 pm
Location: Pembina University, Home of the Gliders
Contact:

Re: Hacking apgsearch

Post by praosylen » December 18th, 2015, 5:18 pm

A for awesome wrote:
Scorbie wrote:Moved x-rule-pre to Golly's Rules folder instead of my custom rule folder, used vanilla apgsearch hack and umm...

Code: Select all

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 11807, in <module>
    soup.rg.saveAllRules()
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 602, in saveAllRules
    self.saveClassifyObjects()
  File "/home/scorbie/Apps/golly/Scripts/Perl/apgsearch-2015-12-17-v0.54+0.1i.py", line 1225, in saveClassifyObjects
    i = lines1[q]
IndexError: list index out of range
Whoops...
No idea what that's about; could you post your .rule file? I will get on it as soon as I have access to a non-school computer; probably about 4 hours from now.
Here's a dirty patch for the errors you are getting: Replace lines 1054 to the end of RuleGenerator.saveContagiousLife() with this:

Code: Select all

    def saveCoalesceObjects(self):

        comments = """
A variant of HistoricalLife which separates a field of ash into
distinct objects.

state 0:  vacuum
state 1:  ON
state 2:  OFF
"""
        table = "n_states:3\n"
        table += "neighborhood:Moore\n"
        
        if ruletype: #Outer-totalistic
            table += "symmetries:permute\n\n"
    
            table += self.newvars(["a","b","c","d","e","f","g","h","i"], [0, 1, 2])
            table += self.newvars(["da","db","dc","dd","de","df","dg","dh","di"], [0, 2])
            table += self.newvars(["la","lb","lc","ld","le","lf","lg","lh","li"], [1])
    
            minperc = 10
    
            for i in xrange(9):
                if (self.bee[i]):
                    if (minperc == 10):
                        minperc = i
                    table += self.scoline("l","d",0,1,i)
                    table += self.scoline("l","d",2,1,i)
                if (self.ess[i]):
                    table += self.scoline("l","d",1,1,i)
    
            table += "\n# Bridge inductors\n"
    
            for i in xrange(9):
                if (i >= minperc):
                    table += self.scoline("l","d",0,2,i)
    
            table += self.scoline("","",1,2,0)
        else: #Isotropic non-totalistic
            rule1 = open(g.getdir("app") + "Rules/" + self.slashed + ".rule", "r")
            lines1 = rule1.read().split("\n")
            rule1.close()
            for q in xrange(len(lines1)-1):
                if lines1[q].startswith("@TABLE"):
                    lines1 = lines1[q:]
                    break
            vars = []
            for q in xrange(len(lines1)-1): #Copy symmetries and vars
                i = lines1[q]
                if i[:2] == "sy" or i[:1] == "sy":
                    table += i + "\n\n"
                if i[:2] == "va" or i[:1] == "va":
                    table += self.newvar(i[4:5].replace("=", ""), [0, 1, 2])
                    vars.append(i[4:5].replace("=", ""))
                if i != "":
                    if i[0] == "0" or i[0] == "1":
                        break
            
            alpha = "abcdefghijklmnopqrstuvwxyz"
            vars2 = []
            for i in alpha: 
                if not i in [n[0] for n in vars]: #Create new set of vars for OFF cells
                    table += self.newvars([i + j for j in alpha[:9]], [0, 2])
                    vars2 = [i + j for j in alpha[:9]]
                    break
                    
            for i in alpha: 
                if not i in [n[0] for n in vars] and not i in [n[0] for n in vars2]:
                    for j in xrange(5-len(vars)):
                        table += self.newvar(i + alpha[j], [0, 1, 2])
                        vars.append(i + alpha[j])
                    break
            
            for i in lines1:
                q = i.split("#")[0].replace(" ", "").split(",")
                if len(q) > 1 and not i.startswith("var"):
                    vn = 0
                    vn2 = 0
                    for j in q[:-1]:
                        if j == "0":
                            table += vars2[vn2]
                            vn2 += 1
                        elif j == "1":
                            table += "1"
                        elif j != "#":
                            g.show(str(len(vars)))
                            table += vars[vn]
                            vn += 1
                        table += ","
                    table += str(2-int(q[len(q)-1]))
                    table += "\n"
                
            for i in xrange(256): #Get all B3+ rules
                ncells = 0
                for j in xrange(8):
                    if (i & 2**j) > 0:
                        ncells += 1
                if ncells == 3:
                    q = "0,"
                    vn = 0
                    for j in xrange(8):
                        if i & 2**j > 0:
                            q += str((i & 2**j)/2**j) + ","
                        else:
                            q += vars[vn] + ","
                            vn += 1
                    q += "2\n"
                    table += q
                
        colours = """
0    0    0    0
1  255  255  255
2  127  127  127
"""
        self.saverule("APG_CoalesceObjects_"+self.alphanumeric, comments, table, colours)
    
    def saveClassifyObjects(self):

        comments = """
This passively classifies objects as either still-lifes, p2 oscillators
or higher-period oscillators. It is mandatory that one first runs the
rule CoalesceObjects.

state 0:  vacuum
state 1:  input ON
state 2:  input OFF

state 3:  ON, will die
state 4:  OFF, will remain off
state 5:  ON, will survive
state 6:  OFF, will become alive

state 7:  ON, still-life
state 8:  OFF, still-life

state 9:  ON, p2 oscillator
state 10: OFF, p2 oscillator

state 11: ON, higher-period object
state 12: OFF, higher-period object
"""
        table = "n_states:17\n"
        table += "neighborhood:Moore\n"
        if ruletype: #Outer-totalistic
            table += "symmetries:permute\n\n"
    
            table += self.newvars(["a","b","c","d","e","f","g","h","i"], range(0, 17, 1))
            table += self.newvars(["la","lb","lc","ld","le","lf","lg","lh","li"], range(1, 17, 2))
            table += self.newvars(["da","db","dc","dd","de","df","dg","dh","di"], range(0, 17, 2))
            table += self.newvars(["pa","pb","pc","pd","pe","pf","pg","ph","pi"], [0, 3, 4])
            table += self.newvars(["qa","qb","qc","qd","qe","qf","qg","qh","qi"], [5, 6])
    #Serious modifications necessary:
            for i in xrange(9):
                if (self.bee[i]):
                    table += self.scoline("l","d",2,6,i)
                    table += self.scoline("q","p",3,9,i)
                    table += self.scoline("q","p",4,12,i)
                if (self.ess[i]):
                    table += self.scoline("l","d",1,5,i)
                    table += self.scoline("q","p",5,7,i)
                    table += self.scoline("q","p",6,12,i)
            table += self.scoline("","",2,4,0)
            table += self.scoline("","",1,3,0)
            table += self.scoline("","",5,11,0)
            table += self.scoline("","",3,11,0)
            table += self.scoline("","",4,8,0)
            table += self.scoline("","",6,10,0)
        
        else: #Isotropic non-totalistic
            rule1 = open(g.getdir("app") + "Rules/" + self.slashed + ".rule", "r")
            lines1 = rule1.read().split("\n")
            rule1.close()
            for q in xrange(len(lines1)-1):
                if lines1[q].startswith("@TABLE"):
                    lines1 = lines1[q:]
                    break
                if lines1[0].startswith("@TREE"):
                    g.warn("apgsearch v.0.54+0.1i does not support rule trees")
            vars = []
            for q in xrange(len(lines1)-1): #Copy symmetries and vars
                i = lines1[q]
                if i[:2] == "sy" or i[:1] == "sy":
                    table += i + "\n\n"
                if i[:2] == "va" or i[:1] == "va":
                    table += self.newvar(i[4:5].replace("=", ""), [0, 1, 2])
                    vars.append(i[4:5].replace("=", ""))
                if i != "":
                    if i[0] == "0" or i[0] == "1":
                        break
            alpha = "abcdefghijklmnopqrstuvwxyz"
            ovars = []
            for i in alpha: 
                if not i in [n[0] for n in vars]: #Create new set of vars for ON cells
                    table += self.newvars([i + j for j in alpha[:9]], [1, 5, 6])
                    ovars = [i + j for j in alpha[:9]]
                    break
            
            dvars = []
            for i in alpha: 
                if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars]: #Create new set of vars for OFF cells
                    table += self.newvars([i + j for j in alpha[:9]], [0, 2, 3, 4])
                    dvars = [i + j for j in alpha[:9]]
                    break
                    
            for i in alpha: 
                if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars] and not i in [n[0] for n in dvars]:
                    for j in xrange(8-len(vars)):
                        table += self.newvar(i + alpha[j], [0, 1, 2, 3, 4, 5, 6])
                        vars.append(i + alpha[j])
                    break
            
            for i in lines1:
                q = i.split("#")[0].replace(" ", "").split(",")
                if len(q) > 1:
                    vn = 0
                    ovn = 0
                    dvn = 0
                    if q[0] == "0" or q[0] == "1":
                        if q[0] == "0":
                            table += "2"
                        elif q[0] == "1":
                            table += "1"
                        elif q[0] != "#":
                            table += vars[vn]
                            vn += 1
                        table += ","
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += "1"
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        table += str(4-int(q[0])+2*int(q[len(q)-1]))
                        table += "\n"
                    elif not i.startswith("var"): #Line starts with a variable.
                        table += vars[vn] + ","
                        vn += 1
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += "1"
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        table += str(4+2*int(q[len(q)-1]))
                        table += "\n1,"
                        vn = 0
                        for j in q[1:-1]:
                            if j == "0":
                                table += "2"
                            elif j == "1":
                                table += "1"
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        table += str(3+2*int(q[len(q)-1]))
                        table += "\n"
            table += "2," + ",".join(vars[:8]) + ",4\n"
            table += "1," + ",".join(vars[:8]) + ",5\n"
                    
            for i in lines1:
                q = i.split("#")[0].replace(" ", "").split(",")
                if len(q) > 1:
                    vn = 0
                    ovn = 0
                    dvn = 0
                    if q[0] == "0" or q[0] == "1":
                        table += str(4+2*int(q[0])) + ","
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += ovars[ovn]
                                ovn += 1
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        if q[0] == "0" and q[len(q)-1] == "0":
                            table += "8"
                        if q[0] == "1" and q[len(q)-1] == "0":
                            table += "10"
                        if q[0] == "0" and q[len(q)-1] == "1":
                            table += "12"
                        if q[0] == "1" and q[len(q)-1] == "1":
                            table += "12"
                        table += "\n"
                    elif not i.startswith("var"): #Line starts with a variable.
                        table += "5,"
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += ovars[ovn]
                                ovn += 1
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        if q[len(q)-1] == "0":
                            table += "7"
                        if q[len(q)-1] == "1":
                            table += "11"
                        table += "\n3,"
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += ovars[ovn]
                                ovn += 1
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        if q[len(q)-1] == "0":
                            table += "9"
                        if q[len(q)-1] == "1":
                            table += "11"
                        table += "\n"
                        
            for i in lines1:
                q = i.split("#")[0].replace(" ", "").split(",")
                if len(q) > 1:
                    vn = 0
                    ovn = 0
                    dvn = 0
                    if q[0] == "0" or q[0] == "1":
                        table += str(3+2*int(q[0])) + ","
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += ovars[ovn]
                                ovn += 1
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        if q[0] == "0" and q[len(q)-1] == "0":
                            table += "11"
                        if q[0] == "1" and q[len(q)-1] == "0":
                            table += "11"
                        if q[0] == "0" and q[len(q)-1] == "1":
                            table += "9"
                        if q[0] == "1" and q[len(q)-1] == "1":
                            table += "7"
                        table += "\n"
                    elif not i.startswith("var"): #Line starts with a variable.
                        table += "6,"
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += ovars[ovn]
                                ovn += 1
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        if q[len(q)-1] == "0":
                            table += "12"
                        if q[len(q)-1] == "1":
                            table += "10"
                        table += "\n4,"
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += ovars[ovn]
                                ovn += 1
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        if q[len(q)-1] == "0":
                            table += "8"
                        if q[len(q)-1] == "1":
                            table += "12"
                        table += "\n"
            table += "4," + ",".join(vars[:8]) + ",8\n"
            table += "3," + ",".join(vars[:8]) + ",11\n"
            table += "6," + ",".join(vars[:8]) + ",12\n"
            table += "5," + ",".join(vars[:8]) + ",7\n"
                        
        colours = """
0    0    0    0
1  255  255  255
2  127  127  127
7    0    0  255
8    0    0  127
9  255    0    0
10 127    0    0
11   0  255    0
12   0  127    0
13 255  255    0
14 127  127    0
"""
        self.saverule("APG_ClassifyObjects_"+self.alphanumeric, comments, table, colours)

    def savePropagateClassifications(self):
        
        comments = """This propagates the result of running ClassifyObjects for two generations.
"""
        
        table = "n_states:17\n"
        table += "neighborhood:Moore\n"
        table += "symmetries:permute\n\n"
    
        table += self.newvars(["a","b","c","d","e","f","g","h","i"], range(0, 17, 1))
        
        table += """
7,11,b,c,d,e,f,g,h,11
7,12,b,c,d,e,f,g,h,11
7,9,b,c,d,e,f,g,h,9
7,10,b,c,d,e,f,g,h,9
8,11,b,c,d,e,f,g,h,12
8,12,b,c,d,e,f,g,h,12
8,9,b,c,d,e,f,g,h,10
8,10,b,c,d,e,f,g,h,10

7,13,b,c,d,e,f,g,h,11
7,14,b,c,d,e,f,g,h,11
8,13,b,c,d,e,f,g,h,14
8,14,b,c,d,e,f,g,h,14
9,13,b,c,d,e,f,g,h,11
9,14,b,c,d,e,f,g,h,11
10,13,b,c,d,e,f,g,h,14
10,14,b,c,d,e,f,g,h,14

9,11,b,c,d,e,f,g,h,11
9,12,b,c,d,e,f,g,h,11
10,11,b,c,d,e,f,g,h,12
10,12,b,c,d,e,f,g,h,12

13,11,b,c,d,e,f,g,h,11
13,12,b,c,d,e,f,g,h,11
14,11,b,c,d,e,f,g,h,12
14,12,b,c,d,e,f,g,h,12
13,9,b,c,d,e,f,g,h,11
14,9,b,c,d,e,f,g,h,12
"""
        colours = """
0    0    0    0
1  255  255  255
2  127  127  127
7    0    0  255
8    0    0  127
9  255    0    0
10 127    0    0
11   0  255    0
12   0  127    0
13 255  255    0
14 127  127    0
"""

        self.saverule("APG_PropagateClassification", comments, table, colours)
        #foo = "" + 2
    def saveContagiousLife(self):

        comments = """
A variant of HistoricalLife used for detecting dependencies between
islands.

state 0:  vacuum
state 1:  ON
state 2:  OFF
"""
        table = "n_states:7\n"
        table += "neighborhood:Moore\n"
        
        if ruletype:
            table += "symmetries:permute\n\n"

            table += self.newvars(["a","b","c","d","e","f","g","h","i"], range(0, 7, 1))
            table += self.newvars(["la","lb","lc","ld","le","lf","lg","lh","li"], range(1, 7, 2))
            table += self.newvars(["da","db","dc","dd","de","df","dg","dh","di"], range(0, 7, 2))
            table += self.newvar("p",[3, 4])
            table += self.newvars(["ta","tb","tc","td","te","tf","tg","th","ti"], [3])
            table += self.newvars(["qa","qb","qc","qd","qe","qf","qg","qh","qi"], [0, 1, 2, 4, 5, 6])

            for i in xrange(9):
                if (self.bee[i]):
                    table += self.scoline("l","d",4,3,i)
                    table += self.scoline("l","d",2,1,i)
                    table += self.scoline("l","d",0,1,i)
                    table += self.scoline("l","d",6,5,i)
                    table += self.scoline("t","q",0,4,i)
                if (self.ess[i]):
                    table += self.scoline("l","d",3,3,i)
                    table += self.scoline("l","d",5,5,i)
                    table += self.scoline("l","d",1,1,i)

            table += "# Default behaviour (death):\n"
            table += self.scoline("","",1,2,0)
            table += self.scoline("","",5,6,0)
            table += self.scoline("","",3,4,0)
        else:
            rule1 = open(g.getdir("app") + "Rules/" + self.slashed + ".rule", "r")
            lines1 = rule1.read().split("\n")
            rule1.close()
            for q in xrange(len(lines1)-1):
                if lines1[q].startswith("@TABLE"):
                    lines1 = lines1[q:]
                    break
            vars = []
            for q in xrange(len(lines1)-1): #Copy symmetries and vars
                i = lines1[q]
                if i[:2] == "sy" or i[:1] == "sy":
                    table += i + "\n\n"
                if i[:2] == "va" or i[:1] == "va":
                    table += self.newvar(i[4:5].replace("=", ""), [0, 1, 2])
                    vars.append(i[4:5].replace("=", ""))
                if i != "":
                    if i[0] == "0" or i[0] == "1":
                        break
            alpha = "abcdefghijklmnopqrstuvwxyz"
            ovars = []
            for i in alpha: 
                if not i in [n[0] for n in vars]: #Create new set of vars for ON cells
                    table += self.newvars([i + j for j in alpha[:9]], [1, 3, 5])
                    ovars = [i + j for j in alpha[:9]]
                    break
            dvars = []
            for i in alpha: 
                if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars]: #Create new set of vars for OFF cells
                    table += self.newvars([i + j for j in alpha[:9]], [0, 2, 4, 6])
                    dvars = [i + j for j in alpha[:9]]
                    break
                    
            for i in alpha: 
                if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars] and not i in [n[0] for n in dvars]:
                    for j in xrange(8-len(vars)):
                        table += self.newvar(i + alpha[j], [0, 1, 2, 3, 4, 5, 6])
                        vars.append(i + alpha[j])
                    break
            
            qvars = []
            for i in alpha:
                if not i in [n[0] for n in vars] and not i in [n[0] for n in ovars] and not i in [n[0] for n in dvars]:
                    table += self.newvars([i + j for j in alpha[:9]], [0, 1, 2, 4, 5, 6])
                    qvars = [i + j for j in alpha[:9]]
                    break
            
            for i in lines1:
                q = i.split("#")[0].replace(" ", "").split(",")
                if len(q) > 1 and not i.startswith("var"):
                    vn = 0
                    ovn = 0
                    dvn = 0
                    qvn = 0
                    table += str(2-int(q[0])) + ","
                    for j in q[1:-1]:
                        if j == "0":
                            table += dvars[dvn]
                            dvn += 1
                        elif j == "1":
                            table += ovars[ovn]
                            ovn += 1
                        elif j != "#":
                            table += vars[vn]
                            vn += 1
                        table += ","
                    if q[len(q)-1] == "0":
                        table += "2"
                    if q[len(q)-1] == "1":
                        table += "1"
                    table += "\n"
                    vn = 0
                    ovn = 0
                    dvn = 0
                    qvn = 0
                    table += str(4-int(q[0])) + ","
                    for j in q[1:-1]:
                        if j == "0":
                            table += dvars[dvn]
                            dvn += 1
                        elif j == "1":
                            table += ovars[ovn]
                            ovn += 1
                        elif j != "#":
                            table += vars[vn]
                            vn += 1
                        table += ","
                    if q[len(q)-1] == "0":
                        table += "4"
                    if q[len(q)-1] == "1":
                        table += "3"
                    table += "\n"
                    vn = 0
                    ovn = 0
                    dvn = 0
                    qvn = 0
                    table += str(6-int(q[0])) + ","
                    for j in q[1:-1]:
                        if j == "0":
                            table += dvars[dvn]
                            dvn += 1
                        elif j == "1":
                            table += ovars[ovn]
                            ovn += 1
                        elif j != "#":
                            table += vars[vn]
                            vn += 1
                        table += ","
                    if q[len(q)-1] == "0":
                        table += "6"
                    if q[len(q)-1] == "1":
                        table += "5"
                    table += "\n"
                    
            for i in lines1:
                q = i.split("#")[0].replace(" ", "").split(",")
                if len(q) > 1:
                    vn = 0
                    ovn = 0
                    dvn = 0
                    qvn = 0
                    if q[0] == "0":
                        table += "0,"
                        for j in q[1:-1]:
                            if j == "0":
                                table += dvars[dvn]
                                dvn += 1
                            elif j == "1":
                                table += ovars[ovn]
                                ovn += 1
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        if q[len(q)-1] == "1":
                            table += "1"
                        table += "\n"
                        
            for i in lines1:
                q = i.split("#")[0].replace(" ", "").split(",")
                if len(q) > 1:
                    vn = 0
                    ovn = 0
                    dvn = 0
                    qvn = 0
                    if q[0] == "0":
                        table += "0,"
                        for j in q[1:-1]:
                            if j == "0":
                                table += qvars[qvn]
                                qvn += 1
                            elif j == "1":
                                table += "3"
                            elif j != "#":
                                table += vars[vn]
                                vn += 1
                            table += ","
                        if q[len(q)-1] == "1":
                            table += "4"
                        table += "\n"

        colours = """
0    0    0    0
1    0    0  255
2    0    0  127
3  255    0    0
4  127    0    0
5    0  255    0
6    0  127    0
"""
        self.saverule("APG_ContagiousLife_"+self.alphanumeric, comments, table, colours)
Frankly, I don't know why that error only showed up in x-rule-pre. Still don't know what else is going on with x-rule-pre, too.
former username: A for Awesome
praosylen#5847 (Discord)

The only decision I made was made
of flowers, to jump universes to one of springtime in
a land of former winter, where no invisible walls stood,
or could stand for more than a few hours at most...

drc
Posts: 1664
Joined: December 3rd, 2015, 4:11 pm

Re: Hacking apgsearch

Post by drc » December 18th, 2015, 6:35 pm

Scorbie wrote:
drc wrote:I still get the error:

line 37 in <module>
from glife import rect, pattern
ImportError: No module named glife
That's related to python's importing system. As a permanent fix, copy the glife folder (and its files) to where your script is in. Like this:

Code: Select all

My_Script_Directory
ㄴapgsearch
ㄴglife
  ㄴ__init__.py
  ㄴ....
I don't have a glife folder. Mabye somebody can provide it?

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

Re: Hacking apgsearch

Post by Scorbie » December 18th, 2015, 7:49 pm

Whoops, my bad. it's in Scripts/Python/glife.

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

Re: Hacking apgsearch

Post by Saka » December 19th, 2015, 1:32 am

wildmyron wrote:
Scorbie wrote:
A for awesome wrote:Hello everyone,

I made a hacked version of apgsearch that works on all 2-state isotropic Moore-neighborhood cellular automata (tlife, SansDomino rules, etc.). Here it is:
Glad to see this :) Thanks for your work! (I think wildmyron worked on somthing similar too...?)
Indeed, great to see some interest in this. I have indeed worked on something similar and despite much hesitation I'm just going to post it in it's current state in the hope that it can help further development. I took a different approach to the rule generating - it's quite interesting to see the way this version interprets an existing rule file and generates the auxillary rules from that. Instead I based the rule generator on EricG's rule table generating script for non-totalistic rules "Hensel-TableGen.py".

The rule generator class is actually in a separate file which makes it easier to use parts of it for other purposes (e.g. isotropicRulegen.py does basically the same as Hensel-TableGen.py but using the http://www.ibiblio.org/lifepatterns/neighbors2.html version of Hensel's notation. I had intended to seperate the soup generating and censusing code into a separate file as well and then have a totalistic and non-totalistic version of apgsearch with custom code for each and all the common code in separate files, but I'm not very good at finishing off such projects. I have a different version of APG_ExpungeObjects - named APG_ExpungeSmallSL - which handles dominoes and I-trominoes as well as blocks. This file has to be manually copied to the rules folder because it's not created by the rule generator. Another version of that identifies SL checkers instead of I-trominoes. Using the most appropriate expunge rule for the rule being searched is one of the stumbling blocks on my TODO list.
I tried it and it said
err.png
err.png (233.44 KiB) Viewed 50319 times
Any fix?

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

Re: Hacking apgsearch

Post by wildmyron » December 19th, 2015, 1:43 am

Saka wrote:I tried it and it said
Python Error msg wrote:...
g.setrule("APG_ExpungeSmallSL")
RuntimeError: Given rule is not valid in any algorithm.
Any fix?
Earlier, I wrote:...
I have a different version of APG_ExpungeObjects - named APG_ExpungeSmallSL - which handles dominoes and I-trominoes as well as blocks. This file has to be manually copied to the rules folder because it's not created by the rule generator.
...
The rule file is in the zip file. Copy it to your rules folder and try again.
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.

Post Reply