Ian07 wrote: February 18th, 2020, 11:20 pm
GUYTU6J wrote: February 18th, 2020, 8:50 am
What tool is suitable for checking soups quickly, by the way?
I've been using
lifelib to display all soups for a given object. Particularly I hacked together this code a while ago:
Code: Select all
samples = lt.download_samples('b3s23','xp30_w33z8kqrqk8zzzx33')
collisions = lt.pattern("")
shift = 0
for sample in samples['4Glider_stdin']:
collisions += sample.shift(shift, 0)
shift += 200
collisions.viewer()
Once the rule has been compiled, just run this in a Python notebook, replacing the apgcode with that of the desired object.
Before 2022, Glider_stdin results are usually extracted through a Python notebook running through lifelib, as far as I know. This is extremely inconvenient as it requires a lot of manual intervention, and it often takes a long time to complete such task on an entire stdin symmetry. Therefore, a few months ago, I wrote a program to search for results in the Glider_stdin censuses and have been using it weekly ever since March. However, one part of the program, which is the function that validates syntheses, is slightly modified from code taken from
contrsynth.py in Adam P. Goucher's Catagolue repository. Before you run the program, clone the Shinjuku repository, and place the program into the main directory of Shinjuku. Alternatively, move the Shinjuku repository to the site-packages directory that stores Python modules, whose path depends on the operating system. Here is the program:
Code: Select all
print("Importing modules. This may take a while.")
import lifelib, ssl, urllib.request, os, math, requests
sess = lifelib.load_rules("b3s23")
lt = sess.lifetree()
ssl._create_default_https_context = ssl._create_unverified_context
def getcost(apgcode):
if apgcode[1] == "s":
sl_size = int(apgcode.split("s")[1].split("_")[0])
else:
sl_size = 0
import requests
synthesis = requests.get("https://catagolue.hatsya.com/textsamples/" + apgcode + "/b3s23/synthesis").text
try:
cost = synthesis.split("costs ")[1].split(" gliders")[0]
if int(cost) > sl_size and sl_size > 0:
print("This object costs " + "\u001b[33;1m\u001b[1m" + cost + "\u001b[0m gliders.")
else:
print("This object costs " + "\u001b[32;1m\u001b[1m" + cost + "\u001b[0m gliders.")
return int(cost)
except:
print("This object is \u001b[31m\u001b[1munsynthesized\u001b[0m.")
return None
#Credit: Adam P. Goucher
#Source file: https://gitlab.com/apgoucher/catagolue/-/blob/master/initialise/contrsynth.py
#Modified for connecting to check_glider_stdin (returning True/False value) and more effective junk removal. Most instructions from the online copy remain intact.
#Checks the validity of the synthesis
#The function 'contrsynth' is named after contrsynth.py in the Catagolue repository, which is the original source of most of the code.
import shinjuku
from shinjuku.gliderset import gset
from shinjuku.search import read_components
from shinjuku.transcode import encode_comp, decode_comp, realise_comp, remove_standard_spaceships
from shinjuku.checks import rewind_check
from shinjuku.synthtools import split_mosaic as split_synthesis
def is_good(synthline):
out_str = decode_comp(synthline)[2]
pat = realise_comp(synthline)
actual_str = pat.oscar(verbose=False, return_apgcode=True).get('apgcode', 'rubbish')
return (out_str == actual_str)
#knowns = read_components()
def contrsynth(rle_list):
engulfed = set([])
for rle in rle_list:
contrib_lines = []
for radius in [5, 8, 13, 21]:
try:
subpatterns = [c for c in split_synthesis(rle, radius=radius-1, maxtime=1024)]
for s in subpatterns:
w = s.wechsler
if (w != '#'):
if w in engulfed:
continue
engulfed.add(w)
try:
dst = s[4096].apgcode
if s[4096][-4096] == s:
continue
x = remove_standard_spaceships(s)
g = gset.extract(x)
src = (x - g.s()).apgcode
directions = 0
for salvo in g.l:
if salvo.nonempty():
directions += 1
if (salvo[4].centre() != salvo.centre()):
return False
if (src == 'xs0_0') and (directions <= 1):
return False
else:
c = encode_comp(s)
if rewind_check(*realise_comp(c, separate=True)):
contrib_lines.append(c)
else:
return False
except (ValueError, KeyError, TypeError):
return False
except (ValueError, KeyError, TypeError):
return False
#contrib_lines = [s for s in set(contrib_lines) if (s not in knowns) and ('ov_' not in s)]
contrib_lines = [s for s in contrib_lines if is_good(s)]
if len(contrib_lines) != 0:
return True
else:
return False
#To test whenever there is a change, please uncomment these statements above.
from os import listdir
from time import time
from math import floor
def get_contents(rule, symmetry, ratelimit, dir="/users/carsoncheng/hauls/"):
allHauls = listdir(dir)
contents_old = []
contents = []
#print("Defining file objects...")
now = floor(time())
for item in allHauls:
if item.find(".txt") != -1:
if ratelimit == True:
#Restrict the collection of hauls to only the last 3 weeks
timestamp = item.split(".")[1]
if now - int(timestamp) < 1814400:
contents_old.append(open(dir + item, "r").read())
else:
contents_old.append(open(dir + item, "r").read())
for report in contents_old:
metadata = report.split("@CENSUS TABLE")[0]
if rule == report.split("@RULE")[1].split("\n")[0] and symmetry == (metadata.split("@SYMMETRY")[1].split("\n")[0]):
contents.append(report)
return contents
def get_object(pref, pref2, apgcode, rule, symmetry, contents, dir="/users/carsoncheng/hauls/"):
rule = " " + rule
symmetry = " " + symmetry
samplesoups = []
#print("Collecting statistics on the object...")
count = 0
for report in contents:
census = report.split("@CENSUS TABLE")[1].split("@SAMPLE_SOUPIDS")[0]
objects = census.split("\n")
if pref == "y":
sample_soups = report.split("@SAMPLE_SOUPIDS")[1]
root = report.split("@ROOT ")[1].split("\n")[0]
for object in objects:
if object.split(" ")[0] == apgcode:
count += int(object.split(" ")[1])
if pref == "y":
sample_objs = sample_soups.split("\n")
for sample_objects in sample_objs:
if sample_objects.split(" ")[0] == apgcode:
samples = sample_objects.split(" ")[1:]
for item in samples:
samplesoups.append(root + str(item))
length = len(samplesoups)
'''if length > 250:
#Too many soups, stopping now.
break'''
if pref == "y":
final_output = ""
for item in samplesoups:
final_output = final_output + ("https://catagolue.hatsya.com/hashsoup/" + symmetry[1:] + "/" + item + "/" + rule[1:] + "\n")
if pref2 == "f":
file = open(rule[1:] + symmetry[1:] + " " + apgcode + " samples.txt", "a")
file.write(final_output)
return final_output
if pref2 == "r":
return final_output
else:
print(final_output)
if pref == "y":
final_output = ""
for item in samplesoups:
final_output = final_output + ("https://catagolue.hatsya.com/hashsoup/" + symmetry[1:] + "/" + item + "/" + rule[1:] + "\n")
if pref2 == "f":
file = open(rule[1:] + symmetry[1:] + " " + apgcode + " samples.txt", "a")
file.write(final_output)
return final_output
if pref2 == "r":
return final_output
else:
print(final_output)
sess = lifelib.load_rules("b3s23")
lt = sess.lifetree()
prohibited_apgcodes = []
def unique(arr):
newarr = []
for item in arr:
if item not in newarr:
newarr.append(item)
return newarr
def pop(pat):
"""Compute the minimum population of the given pattern.
Because the latter is already associated with a rule no
further arguments are required."""
return min(pat[i].population for i in range(pat.period))
def check_glider_stdin(apgcode, period, minpop, symmetry, object_cost, contents):
#print(apgcode)
sample2 = get_object("y", "r", apgcode, "b3s23", symmetry, contents)
#print(sample2)
#Uncomment the line above if you want to find the date of a sample soup that is not on Catagolue.
soups = []
if sample2 != "":
sample_soups = sample2.split("/hashsoup/" + symmetry)
else:
if symmetry == 'moog_stdin':
object_page = urllib.request.urlopen("https://catagolue.hatsya.com/object/s1" + apgcode + "/b3s23").read() + urllib.request.urlopen("https://catagolue.hatsya.com/object/s2" + apgcode + "/b3s23").read() + urllib.request.urlopen("https://catagolue.hatsya.com/object/" + apgcode + "/b3s23").read()
else:
object_page = urllib.request.urlopen("https://catagolue.hatsya.com/object/" + apgcode + "/b3s23").read()
sample_soups = unique(str(object_page).split("/hashsoup/" + symmetry))
sample_soups.pop(0)
for item in sample_soups:
index = 0
soups.append(item.split('b3s23')[0])
index += 1
print("Processing " + str(len(soups)) + " sample soups...")
for item in soups:
try:
soup_rle = item.split("-")[1].split("/")[0]
pattern = lt.pattern(soup_rle)
num_gliders = math.floor(pattern.population / 5)
pattern_ash = pattern[500]
pop = pattern_ash.population
"""if apgcode[0] != "x" or apgcode[1] != "s":
try:
encode = pattern_ash.apgcode
except KeyError:
encode = 0
else:
encode = 0"""
#if encode == apgcode or pop == minpop:
if pop == minpop:
#print("Clean glider synthesis found:\n " + soup_rle)
try:
print(object_cost)
if object_cost > num_gliders:
if contrsynth([soup_rle]):
print("Clean glider synthesis found:")
print(soup_rle)
'''else:
print("\u001b[31m\u001b[1mPotentially unrewindable\u001b[0m clean glider synthesis found:")
print(soup_rle)'''
url = 'https://catagolue.hatsya.com/postsynth'
post = requests.post(url, data = {'content': "x = 0, y = 0, rule = B3/S23\n" + soup_rle})
print("RLE posted on Catagolue.")
except:
if contrsynth([soup_rle]):
print("Clean glider synthesis found:")
print(soup_rle)
url = 'https://catagolue.hatsya.com/postsynth'
post = requests.post(url, data = {'content': "x = 0, y = 0, rule = B3/S23\n" + soup_rle})
print("RLE posted on Catagolue.")
elif pop - minpop < 10 and pop - minpop > 0:
if object_cost == 0:
object_cost = getcost(apgcode)
try:
if pattern_ash.apgcode == apgcode and object_cost > num_gliders and contrsynth([soup_rle]):
print("\u001b[32;1m\u001b[1mClean glider synthesis found\u001b[0m:")
print(soup_rle)
url = 'https://catagolue.hatsya.com/postsynth'
post = requests.post(url, data = {'content': "x = 0, y = 0, rule = B3/S23\n" + soup_rle})
print("RLE posted on Catagolue.")
elif object_cost > (num_gliders + 1) and contrsynth([soup_rle]):
print("A glider synthesis can be obtained using just a little cleanup:")
print(soup_rle)
print("\u001b[33;1m\u001b[1mPossible reduction.\u001b[0m")
except:
if contrsynth([soup_rle]):
print("A glider synthesis can be obtained using just a little cleanup:")
print(soup_rle)
print("\u001b[33;1m\u001b[1mPossible synthesis.\u001b[0m")
elif pop - minpop < 20 and pop - minpop > 10:
if object_cost == 0:
object_cost = getcost(apgcode)
try:
if object_cost > (num_gliders + 2) and contrsynth([soup_rle]):
print("A glider synthesis can be obtained using slightly more cleanup:")
print(soup_rle)
print("\u001b[33;1m\u001b[1mPossible reduction.\u001b[0m")
except:
if contrsynth([soup_rle]):
print("A glider synthesis can be obtained using slightly more cleanup:")
print(soup_rle)
print("\u001b[33;1m\u001b[1mPossible synthesis.\u001b[0m")
except IndexError:
#print("Invalid soup " + item + " detected. Going on anyway.")
pass
print("The operation completed successfully.")
symmetry = input("Which symmetry do you want to investigate? ")
try:
num_gliders = int(symmetry.split("G")[0])
except:
num_gliders = int(input("How many gliders should the threshold be? "))
period = 0
minpop = 0
textcensus = str(urllib.request.urlopen("https://catagolue.hatsya.com/textcensus/b3s23/" + symmetry).read())
apgcodes = textcensus.split("\\n")
print("Obtaining contents of files...")
contents = get_contents(' b3s23', ' ' + symmetry, True)
#print(contents)
print("complete.")
for item in apgcodes:
try:
apgcode = item.split('","')[0].split('"')[1]
if apgcode == "apgcode":
pass
if apgcode[0] == "x" and apgcode[1] == "p" or apgcode[0] == "x" and apgcode[1] == "q":
print(apgcode)
minpop = pop(lt.pattern(apgcode))
try:
object_cost = getcost(apgcode)
if object_cost > num_gliders:
try:
check_glider_stdin(apgcode, period, minpop, symmetry, object_cost, contents)
except:
check_glider_stdin(apgcode, period, minpop, symmetry, object_cost, contents)
else:
print("No need to investigate this object.")
except TypeError:
check_glider_stdin(apgcode, period, minpop, symmetry, None, contents)
elif apgcode[0] == "x" and apgcode[1] == "s":
print(apgcode)
try:
minpop = int(apgcode.split("s")[1].split("_")[0])
except:
print("Failed to find minimum population!")
minpop = 0
if apgcode[0] == "x" and apgcode not in prohibited_apgcodes:
object_cost = getcost(apgcode)
try:
if object_cost > num_gliders:
try:
check_glider_stdin(apgcode, period, minpop, symmetry, object_cost, contents)
except:
print("Could not make connection. Not investigating this object.")
else:
print("No need to investigate this object.")
except:
try:
check_glider_stdin(apgcode, period, minpop, symmetry, object_cost, contents)
except:
print("Could not make connection. Not investigating this object.")
except ZeroDivisionError:
print("It seems like all objects have been searched in this stdin symmetry.")
raise SystemExit()
Advantages:
- It saves time by checking all objects in quick succession instead of requiring manual intervention for each object checked.
- It points out reductions by itself by measuring the soup's population in a certain generation against the minimum population of the object.
Disadvantages:
- It may omit some reductions because it only estimates the cost of cleanup by population, rather than by the number of objects in the soup.
- It does not submit syntheses to Catagolue automatically because it is currently incapable of finding optimal cleanup by itself. Therefore, it still requires manual intervention in order to get the reductions on Catagolue.
How to spot reductions:
- Wherever you find "possible reduction" in the console as you run the program, the RLE above it is the soup that was reported by the program.
Also, modify the "dir" parameter in the "get_object" and "get_contents" functions to match the directory where you store your local hauls for the Glider_stdin censuses.