Wow, perfect, it's the last required piece ... I can throw away all the HD1 HD13 stuff
. And the first row of results is sufficient.
BTW: ... the version with tree cutting replaced by depth reduction works nicely (and is significantly slower as a lot of early cuted trees are considered interesting now) ... it rediscovered all the recipes (in at most 1 deeper search then the full search would done) ... I am not searching on depth 6 for the hd0 yet ... so ruslesprouts final discovery was not in the range.
(And actually I have finished search to the depth 6 with subtree cutting so I would need the search to depth at least 7 to find it ... it would proabably be out of my patience range.)
I have added interruptability to preserve information what was already searched.
Code: Select all
import sqlite3
import sys
import argparse
import re
import itertools
import functools
import pathlib
import os
import signal
from multiprocessing import Pool
import multiprocessing
from datetime import timedelta
import math
from arg_parser import range_str_to_list
from components import pattern_components
from font import write_text
from life_history import write_life_history
from lifetree import lt
from gliders import PI_BLOCKS, mk_glider, single_channel_stream
from parse_p120_recipes import parse_p120_recipe
from speedometer import Speedometer
argparser = argparse.ArgumentParser(
prog="oncoming.py", description="Search for glider patterns"
)
class ToolkitDef:
min_spacing: int
lane_offset: int
period: int
name: str
def __init__(self, str):
m = re.match(r"sc(\d+)b(\d+)p(\d+)", str)
if not m:
raise ValueError(
f"Toolkit {str} is invalid. Expected something like sc90b5p120"
)
self.min_spacing = int(m.group(1))
self.lane_offset = int(m.group(2))
self.period = int(m.group(3))
self.name = str
argparser.add_argument(
"--toolkit",
default="sc90b5p120",
type=ToolkitDef,
help="The oncoming gliders to search. Format sc[min spacing]b[lane_offset]p[period]. E.g., sc90b5p120",
)
argparser.add_argument(
"--depth", type=int, help="The number of gliders to include in salvos"
)
argparser.add_argument(
"--print-rle",
type=str,
default="",
help="Prints a rle showing the given recipes. Skips processing anything else.",
)
argparser.add_argument(
"--required-prefix",
type=str,
default="",
help="results not containing info with given prefix are not reported",
)
argparser.add_argument(
"--required-prefixes",
type=str,
default="",
help="results not containing info with a prefix from the list are not reported",
)
argparser.add_argument(
"--required",
type=str,
default="",
help="results not containing all given exact semicolon separated infos are not reported",
)
argparser.add_argument(
"--ignored",
type=str,
default="",
help="given semicolon separated infos are not reported, but it does not block reporting the salvo",
)
argparser.add_argument(
"--avoid-prefix",
type=str,
default=")(",
help="results containing info with given prefix are not reported",
)
argparser.add_argument(
"--n-gun-gliders", type=int, default=10, help="How many gun gliders to simulate"
)
argparser.add_argument(
"--simulate-gens", type=int, default=None, help="How many generations to simulate"
)
argparser.add_argument(
"--max-population", type=int, default=float('inf'), help="The maximum population results to report"
)
argparser.add_argument(
"--min-population", type=int, default=0, help="The minimum population results to report"
)
argparser.add_argument(
"--max-tree-population", type=int, default=float('inf'), help="The maximum population allowed in intermediates"
)
argparser.add_argument(
"--max-tree-width", type=int, default=float('inf'), help="The maximum width allowed in intermediates"
)
argparser.add_argument(
"--max-tree-height", type=int, default=float('inf'), help="The maximum height allowed in intermediates"
)
argparser.add_argument(
"--max-tree-x", type=int, default=float('inf'), help="The maximum x allowed in intermediates"
)
argparser.add_argument(
"--max-tree-y", type=int, default=float('inf'), help="The maximum y allowed in intermediates"
)
argparser.add_argument(
"--min-tree-x", type=int, default=-float('inf'), help="The minimum x allowed in intermediates"
)
argparser.add_argument(
"--min-tree-y", type=int, default=-float('inf'), help="The minimum y allowed in intermediates"
)
class SubtreeDef:
options: tuple[tuple[int]]
def __init__(self, str):
depths = ()
for d in str.split(";"):
options = range_str_to_list(d)
depths = depths + (tuple(options),)
self.options = depths
argparser.add_argument(
"--subtree",
default=[],
type=SubtreeDef,
action="append",
help="Search range. E.g. '1;90-255' to search the glider 1 followed by any glider in [90,255], or '1,3,5,7' to search the starting gliders 1,3,5 and 7. Use comma to separate at the same depth, then semicolon to separate depths. Ranges are inclusive.",
)
argparser.add_argument(
"--max-delay", type=int, default=255, help="Maximum delay for a glider."
)
argparser.add_argument(
"--check-for-shutoff",
type=str,
help="Checks if the given sequences shut off the glider stream by sending 120 extra gun gliders and comparing the state",
)
argparser.add_argument(
"--find-minimum-follow",
type=str,
help="Finds the minimum follow distance for the given recipes",
)
argparser.add_argument("--use-gun-rle", type=str, help="Uses the rle as the gun")
argparser.add_argument(
"--extract-recipes",
type=pathlib.Path,
help="Extracts the recipes from the given file. Outputs a JSON.",
)
argparser.add_argument(
"--to-apgluxe",
action=argparse.BooleanOptionalAction,
default=False,
help="Just print out the rles, don't simulate anything.",
)
argparser.add_argument(
"--only-gliders",
action=argparse.BooleanOptionalAction,
default=False,
help="Only process glider recipes.",
)
argparser.add_argument(
"--do-not-report-gliders",
action=argparse.BooleanOptionalAction,
default=False,
help="Do not report glider including results.",
)
argparser.add_argument(
"--swim-upstream-after",
action=argparse.BooleanOptionalAction,
default=False,
help="Send 2,96,96,96... after each part of the subtree."
)
argparser.add_argument(
"--concurrent",
action=argparse.BooleanOptionalAction,
default=False,
help="Use multiple threads for searching."
)
argparser.add_argument(
"--view-orientations",
type=str,
default=None
)
args = argparser.parse_args()
args.starting_point = "not started yet"
def where_we_are(*a):
print(f"Interrupted at search from: {args.starting_point}")
raise Exception("Interrupted!")
signal.signal(signal.SIGINT, where_we_are)
simulate_gens = args.simulate_gens or args.toolkit.period * args.n_gun_gliders
simkin_gun = lt.pattern("""x = 37, y = 26, rule = B3/S23
3$11bo$11b3o$14bo$13b2o5$2b2o5b2o$2b2o5b2o2$6b2o$6b2o4$29b2o$16bo12b2o
$14bobo$14b3o9b2o5b2o$14bo11b2o5b2o!
""")(-490, -501)
gun_period = args.toolkit.period
def mk_fake_gun(n):
fake_gun = sum(
[mk_glider(0, gun_period * x) for x in range(0, n)], start=lt.pattern("")
)
fake_gun = fake_gun("rot180")(-10 + args.toolkit.lane_offset, -11)
return fake_gun
fake_gun = mk_fake_gun(args.n_gun_gliders)
expected_incoming_gliders_pattern = fake_gun[simulate_gens]
expected_incoming_gliders = pattern_components(expected_incoming_gliders_pattern)
# phase 0 gliders
canonical_se = mk_glider(0, 0)("rot180").centre()
canonical_nw = mk_glider(0, 0).centre()
canonical_ne = mk_glider(0, 0)("rot270").centre()
canonical_sw = mk_glider(0, 0)("rot90").centre()
def recipe_stream_to_delays(stream):
gliders = stream.split(",")
result = []
total = 0
for g in gliders:
m = re.match(r"^\(\s*(\d+)\s*\)\s*(?:\s*(\d+)\s*\+\s*)?(\d+)$", g.strip())
if m:
minimum_follow = int(m.group(1))
addition = m.group(2)
if addition:
minimum_follow += int(addition)
next_parity = int(m.group(3))
parity = (total + minimum_follow) % 8
g = minimum_follow + ((next_parity - parity) % 8)
total += g
result.append(g)
else:
g = int(g)
total += g
result.append(g)
return result
def find_minimum_follow(recipes):
expected_incoming_gliders = pattern_components(fake_gun[simulate_gens])
for i, r in enumerate(recipes):
if not r.strip():
continue
delays = recipe_stream_to_delays(r)
total = sum(delays)
p = fake_gun + single_channel_stream(delays)
p = p[simulate_gens]
escaping_gliders = []
for c in pattern_components(p):
for i, e in enumerate(expected_incoming_gliders):
if c == e:
# this is one of the escaping gliders
escaping_gliders.append(i)
n_escaping = max(escaping_gliders) + 1
# now we know which gliders participate. Remove the rest.
envelope = lt.pattern("5o$5o$5o$5o$5o").centre()
pattern = mk_fake_gun(args.n_gun_gliders - n_escaping) + single_channel_stream(
delays
)
@functools.lru_cache(maxsize=None)
def pattern_at_gen(n):
if n == 0:
return pattern
return pattern_at_gen(n - 1)[1]
def try_follow(i):
follow_up_glider = mk_glider(0, total + i)
glider_envelope = lt.pattern()
for j in range(0, simulate_gens + i):
follow_up_glider = follow_up_glider[1]
glider_envelope += follow_up_glider.convolve(envelope)
if (pattern_at_gen(j + 1) & glider_envelope).nonempty():
return False
return True
low = args.toolkit.min_spacing
r = 1
if try_follow(low):
return f",({low})"
else:
while r<=1024:
hi = args.toolkit.min_spacing+r
r += r
if try_follow(hi):
while (hi-low>1):
test=(hi+low)//2
if try_follow(test):
hi=test
else:
low=test
return f",({hi})"
else:
low=hi
else:
return f",({hi}+???)"
if args.find_minimum_follow:
follow = find_minimum_follow(args.find_minimum_follow.split(";"))
print(",".join(map(str, delays)) + follow)
sys.exit(0)
required_split = args.required.split(";")
args.required = set()
for req in required_split:
if req != "":
args.required.add(req)
args.required_prefixes = args.required_prefixes.split(";")
ignored_split = args.ignored.split(";")
args.ignored = set()
for ign in ignored_split:
args.ignored.add(ign)
args.gun_X = False
if args.use_gun_rle:
def remove_gliders(p):
p = p + lt.pattern() # copy
for c in pattern_components(p):
cc = c.centre()
if (
cc == canonical_se.centre()
or cc == canonical_se[1].centre()
or cc == canonical_se[2].centre()
or cc == canonical_se[3].centre()
):
p -= c
return p
args.speboes = ["block(l-3,", "block(l4,", "beehive(l-3,o0", "beehive(l4,o1", "pond(l-4,", "pond(l5,", "boat(l-4,o1", "boat(l5,o2", "ship(l-4,o1", "ship(l5,o1"]
if args.use_gun_rle == 'breaksnark0Speboe': # just to catalogue speboes (block l-3,l4; beehive l-3o0, l4o1; pond l-4,l5; boat l-401,l5o2; ship l-4o1,l5o1)
args.use_gun_rle = '''x = 75, y = 119, rule = B3/S23
9b2o$8bobo$8b2o5$7b2o7bo$6bobo6bobo$6b2o7b2o4$22b2o$14b2o5bo2bo$13bobo
5bo2bo$14bo7b2o3$19b2o6b2o$18bo2bo4bo2bo$18bo2bo5b2o$19b2o3$26bo5b2o$
25bobo4b2o$25bobo$26bo3$31b2o$31b2o14$65b2o$64bobo$58b2o4bo$56bo2bo2b
2ob4o$56b2obobobobo2bo$59bobobobo$59bobob2o$60bo2$73b2o$64b2o7bo$64b2o
5bobo$71b2o4$72bo$70bobo$71b2o$61b2o$62bo$59b3o$59bo48$b2o$obo$2bo!'''
if args.use_gun_rle == 'breaksnark0':
args.use_gun_rle = '''x = 75, y = 73, rule = B3/S23
65b2o$64bobo$58b2o4bo$56bo2bo2b2ob4o$56b2obobobobo2bo$59bobobobo$59bob
ob2o$60bo2$73b2o$64b2o7bo$64b2o5bobo$71b2o4$72bo$70bobo$71b2o$61b2o$
62bo$59b3o$59bo48$b2o$obo$2bo!'''
if args.use_gun_rle == 'breaksnark1':
args.use_gun_rle = '''x = 134, y = 133, rule = B3/S23
124b2o$123bobo$117b2o4bo$115bo2bo2b2ob4o$115b2obobobobo2bo$118bobobobo
$118bobob2o$119bo2$132b2o$123b2o7bo$123b2o5bobo$130b2o4$131bo$129bobo$
130b2o$120b2o$121bo$118b3o$118bo108$b2o$obo$2bo!'''
if args.use_gun_rle == 'breaksnark-1':
args.use_gun_rle = '''x = 135, y = 132, rule = B3/S23
125b2o$124bobo$118b2o4bo$116bo2bo2b2ob4o$116b2obobobobo2bo$119bobobobo
$119bobob2o$120bo2$133b2o$124b2o7bo$124b2o5bobo$131b2o4$132bo$130bobo$
131b2o$121b2o$122bo$119b3o$119bo107$b2o$obo$2bo!'''
if args.use_gun_rle == 'breaksnark13':
args.use_gun_rle = '''x = 121, y = 132, rule = B3/S23
111b2o$110bobo$104b2o4bo$102bo2bo2b2ob4o$102b2obobobobo2bo$105bobobobo
$105bobob2o$106bo2$119b2o$110b2o7bo$110b2o5bobo$117b2o4$118bo$116bobo$
117b2o$107b2o$108bo$105b3o$105bo107$b2o$obo$2bo!'''
if args.use_gun_rle == 'breaksnark-13':
args.use_gun_rle = '''x = 147, y = 132, rule = B3/S23
137b2o$136bobo$130b2o4bo$128bo2bo2b2ob4o$128b2obobobobo2bo$131bobobobo
$131bobob2o$132bo2$145b2o$136b2o7bo$136b2o5bobo$143b2o4$144bo$142bobo$
143b2o$133b2o$134bo$131b3o$131bo107$b2o$obo$2bo!'''
if args.use_gun_rle == 'hooked':
args.use_gun_rle = '''x = 75, y = 79, rule = B3/S23
9bo$9b3o$12bo$11b2o5$2o5b2o$2o5b2o2$4b2o$4b2o$9b2o$8bo2bo$7b2o$8b2o17b
2o$9bo17b2o2$24b2o5b2o$24b2o5b2o32$47b2o$47bo$48b3o$50bo21$73bo$74bo$
72b3o!'''
if args.use_gun_rle == 'perp_gli':
args.use_gun_rle = '''x = 70, y = 84, rule = B3/S23
68bo$69bo$67b3o79$3o$2bo$bo!'''
if args.use_gun_rle == '120':
args.use_gun_rle = '''x = 83, y = 51, rule = B3/S23
14bo$14bo$14bo16$2o$obo64bo$bo65bo$67bo$17bo6b2o$16bobo5b2o37b3o3b3o$
16bobo$17bo49bo$34bo32bo$9b2o23b3o30bo$8bobo26bo$9bo26b2o5$25b2o5b2o$
25b2o5b2o46b2o$79bo2bo$29b2o49b2o$24bo4b2o$5b2o16bobo$5b2o16bobo$24bo$
52b2o$39bo12b2o$37bobo$37b3o9b2o5b2o$37bo11b2o5b2o$12bo$6b2o3bobo$5bo
2bo2b2o$6b2o!'''
if args.use_gun_rle == '120C':
args.use_gun_rle = '''x = 44, y = 47, rule = B3/S23
o$o$o20$3bo6b2o$2bobo5b2o$2bobo$3bo$20bo$20b3o$23bo$22b2o5$11b2o5b2o$11b2o5b2o2$15b2o$15b2o4$38b2o$25bo12b2o$23bobo$23b3o9b2o5b2o$23bo11b2o5b2o!'''
if args.use_gun_rle == '120X':
args.gun_X = True
args.use_gun_rle = '''x = 99, y = 58, rule = B3/S23
9bo$9b3o$12bo$11b2o5$2o5b2o$2o5b2o2$4b2o$4b2o$9b2o$8bo2bo$7b2o$8b2o3bo
13b2o$9bo4bo12b2o$12b3o$24b2o5b2o$24b2o5b2o25$82bo$81bobo4b2o$82bobo3b
obo$83bo6bo4b2o$86b4ob2o2bo2bo$86bo2bobobobob2o$76bo12bobobobo$75bobo
12b2obobo$75bobo16bo$76bo2$89b2o$89b2o!'''
args.required.add("barge(l46,o0,d26)")
args.required.add("beehive(l34,o0,d25)")
args.required.add("blinker(l26,o0,d12)")
args.required.add("blinker(l26,o1,d6)")
args.required.add("block(l43,o0,d41)")
args.ignored.add("glider(l6)(0B0)(ph0)")
gun = lt.pattern(args.use_gun_rle.strip())
gun_without_gliders = remove_gliders(gun)
glider_appears = gun
# now, let's fast forward the gun until we see an output glider.
for i in range(0, args.toolkit.period):
if remove_gliders(glider_appears) != glider_appears:
# there's a glider
break
glider_appears = glider_appears[1]
glider_appears = glider_appears[(args.n_gun_gliders - 1) * args.toolkit.period]
# and make the gliders canonical
rect = (-float("inf"), 0, 0, 0)
for i in range(0, 4):
found = False
for c in pattern_components(glider_appears):
if c.centre() == canonical_se.centre():
rect = max(rect, tuple(c.getrect()))
found = True
if found:
break
glider_appears = glider_appears[1]
else:
raise Exception("Couldn't find gliders?")
# shift the gun so that it lines up with the fake gun.
fx, fy, _, _ = mk_fake_gun(1).getrect()
x, y, _, _ = rect
fake_gun = glider_appears(fx - x, fy - y)
if args.extract_recipes:
def parse_object(obj):
m = re.match(r"^'([\w ]+)((?:\([^)]*\))*)'$", obj.strip())
if not m:
raise SyntaxError(f"Invalid object: {obj}")
name = m.group(1)
info = m.group(2)
return {"name": name, "info": info}
gun_glider_names = set()
for i in range(0, args.n_gun_gliders):
gun_glider_names.add(f"g{i}")
def parse_objects(objs):
results = []
gun_gliders = set()
for o in re.split(r"('[^']*')", objs):
o = o.strip()
if not o or o == ",":
continue
obj = parse_object(o)
if obj["name"] in gun_glider_names:
gun_gliders.add(obj["name"])
else:
results.append(obj)
for i in range(0, args.n_gun_gliders):
if not f"g{i}" in gun_gliders:
break
consumed = args.n_gun_gliders - i
if i != len(gun_gliders):
results.append("!skipped glider")
if i == 0:
results.append("!consumed all")
return {"consumed": consumed, "objects": results}
with open(args.extract_recipes) as results_file:
for line in results_file:
m = re.match(
r"""^\((\d+(?:, \d+)*,?)\)\s*\[('[^']*'(?:, '[^']*')*)?\]$""",
line.strip(),
)
if not m:
raise SyntaxError(f"Invalid line: {line}")
stream = tuple(map(int, filter(bool, m.group(1).split(","))))
info = parse_objects(m.group(2) or "")
if info["consumed"] < 10 and len(info["objects"]) == 1:
print(stream, info)
sys.exit(0)
friendly_names = {
"xq4_153": "glider",
"xs4_33": "block",
"xp2_7": "blinker",
"xs6_696": "beehive",
"xs7_2596": "loaf",
"xs5_253": "boat",
"xs6_356": "ship",
"xs8_6996": "pond",
"xs4_252": "tub",
"xs7_25ac": "long boat",
"xs12_g8o653z11": "ship tie",
"xp2_7e": "toad",
"xp2_318c": "beacon",
"xs14_g88m952z121": "half bakery",
"xs7_178c": "eater 1",
"xs6_25a4": "barge",
"xs6_bd": "snake",
"xs11_g8o652z11": "boat tie ship",
"xq4_6frc": "lwss",
"xs8_178k8": "tub_with_tail",
}
class HashablePattern:
def __init__(self, c: lifelib.Pattern, digest: int):
self.c = c
self.digest = digest
def __hash__(self):
return self.digest
def __eq__(self, other):
return self.digest == other.digest
@functools.lru_cache(maxsize=None)
def pattern_orientation(c):
c = c.c
orientations = [c.digest()]
for o in ["rot270", "rot180", "rot90", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]:
orientations.append(c(o).digest())
orientations = sorted(set(orientations))
return orientations.index(c.digest())
def component_info(c, alt=False):
if c == c[1]:
# still life
code = c.apgcode
name = friendly_names.get(code, code)
x, y, w, h = c.getrect()
lane = x - y
depth = x + y + w + h
orientation = pattern_orientation(HashablePattern(c, c.digest()))
if args.gun_X:
if depth < -50:
return ""
return f"{name}(l{lane},o{orientation},d{depth})"
elif c == c[120]:
# oscillator
code = c.apgcode
name = friendly_names.get(code, code)
x, y, w, h = c.getrect()
if (name == 'blinker'): # coordinates of the center ??
x = x + w//2
y = y + h//2
lane = x - y
depth = x + y + w + h
if args.gun_X:
if depth < -50:
return ""
orientation = pattern_orientation(HashablePattern(c, c.digest()))
if (name == 'blinker') and alt:
# skip blinkers when processing the alt.
return None
return f"{name}(l{lane},o{orientation},d{depth})"
elif c.centre() == c[4].centre():
# spaceship
code = c.apgcode
name = friendly_names.get(code, code)
x, y, w, h = c.getrect()
x4, y4, _, _ = c[4].getrect()
if x4 - x == 0 or y4 - y == 0:
# xWSS, need to process even if alt is true.
return f"{name}({x},{y})({x4-x},{y4-y})"
elif alt:
# ignore gliders in alt -- we've already got them.
return None
# give the incoming gliders the names g0, g1, etc.
for i, e in enumerate(expected_incoming_gliders):
if c == e:
return f"g{i}"
# glider phase/color info
match (x4 - x, y4 - y):
case (1, 1):
# same direction as gun
canonical = canonical_se
direction = "B"
case (1, -1):
# gun side 90 degree
canonical = canonical_ne
direction = "R"
case (-1, 1):
# recipe side 90 degree
canonical = canonical_sw
direction = "L"
case (-1, -1):
# same direction as recipe
canonical = canonical_nw
direction = "F"
if c.centre() == canonical:
phase = 0
phase_mod2 = "0"
elif c[1].centre() == canonical:
phase = 1
phase_mod2 = "1"
elif c[2].centre() == canonical:
phase = 2
phase_mod2 = "0"
elif c[3].centre() == canonical:
phase = 3
phase_mod2 = "1"
lx, ly, lw, lh = c[phase].getrect()
lane = lx - ly
depth = lx + ly + lw + lh
match (x4 - x, y4 - y):
case (1, 1):
location = f"l{lane}"
color = "01"[lane % 2]
case (1, -1):
location = f"d{depth}"
color = "01"[depth % 2]
case (-1, 1):
location = f"d{depth}"
color = "01"[depth % 2]
case (-1, -1):
location = f"l{lane}"
color = "01"[lane % 2]
return f"{name}({location})({color}{direction}{phase_mod2})(ph{phase})"
else:
# not an object
return None
args.min_big_pop_so_farA = float('inf') # soory for using args
args.min_big_pop_so_farB = float('inf') # soory for using args
def evaluate(s, patt):
rect = patt.getrect()
x, y, w, h = 0, 0, -1, -1
if rect:
x, y, w, h = rect
if x < args.min_tree_x:
args.tree_continue = False
return
if y < args.min_tree_y:
args.tree_continue = False
return
if w > args.max_tree_width:
args.tree_continue = False
return
if h > args.max_tree_height:
args.tree_continue = False
return
if x+w > args.max_tree_x:
args.tree_continue = False
return
if y+h > args.max_tree_y:
args.tree_continue = False
return
if patt.population > args.max_population:
if patt.population < args.min_big_pop_so_farA:
args.min_big_pop_so_farA = patt.population
#print(s, 'defining min_big_pop_so_far A' + f"#{args.min_big_pop_so_farA}" )
else:
#print(s, 'too big A' + f"#{patt.population} >= {args.min_big_pop_so_farA}" )
return
if (patt - expected_incoming_gliders_pattern).population > args.max_population:
if (patt - expected_incoming_gliders_pattern).population < args.min_big_pop_so_farB:
args.min_big_pop_so_farB = (patt - expected_incoming_gliders_pattern).population
#print(s, 'defining min_big_pop_so_far B' + f"#{args.min_big_pop_so_farB}" )
else:
#print(s, 'too big B' + f"#{(patt - expected_incoming_gliders_pattern).population} >= {args.min_big_pop_so_farB}" )
return
if patt.population < args.min_population:
print(s, 'too small A' + f"#{patt.population}")
return
if (patt - expected_incoming_gliders_pattern).population < args.min_population:
print(s, 'too small B' + f"#{patt.population}")
return
infos = set()
infos.add(f"#{patt.population}")
infos.add(f"<{x},{y},{w},{h}>")
if patt.population == 0:
infos.add("empty")
required_cnt = len(args.required)
if args.required_prefix == "":
if len(args.required_prefixes)>0:
args.required_prefix = args.required_prefixes[0]
required_prefix_matched = args.required_prefix == ""
if args.only_gliders:
if patt.population % 5 or patt.population >= 5 + 5 * args.n_gun_gliders:
return
diff = patt - reference
if diff.population != 5:
return
if diff.centre() != diff[4].centre():
return
gli_info = component_info(diff)
if gli_info.startswith(args.avoid_prefix):
return
if gli_info.startswith(args.required_prefix):
required_prefix_matched = True
if not gli_info.startswith("glider"):
return
for i in range(patt.population // 5 - 1):
infos.add(f"g{i}")
infos.add(gli_info)
else:
components = pattern_components(patt)
has_unidentified = False
for c in components:
info = component_info(c, False)
if info is not None:
if info.startswith("glider") and args.do_not_report_gliders:
return
for speboe in args.speboes:
if info.startswith(speboe):
info = 'speboe'+info
break
if info!="":
if info.startswith(args.avoid_prefix):
return
if info.startswith(args.required_prefix):
required_prefix_matched = True
if info in args.required:
required_cnt -= 1
elif not info in args.ignored:
infos.add(info)
else:
has_unidentified = True
if has_unidentified:
components1 = pattern_components(patt[1])
for c in components1:
info = component_info(c, True)
if info is not None:
if info.startswith(args.avoid_prefix):
return
if info.startswith(args.required_prefix):
required_prefix_matched = True
if not info in args.required and not info in args.ignored:
infos.add(info)
#infos.add(f"!{required_cnt}")
#infos.add(f"!{required_prefix_matched}")
if not required_prefix_matched:
for req_prefix in args.required_prefixes:
for info in infos:
if info.startswith(req_prefix):
required_prefix_matched = True
break
if required_prefix_matched:
break
if not required_prefix_matched:
return
if required_cnt>0:
return
return (s, sorted(infos))
if args.print_rle:
macros = {
'sc90b5p120': {
'construct_arm': 'mode sc, 0, 126, 102, 100, 195, 90, 91, 95, 98, 105, 90, 101, 141, 94, 159, 92, 146, 99, 90, 152, 139, 144, 92, 161, 131, 116, 101, 114, 111, 112, 93, 127, 98, 102, 114, 107, 157, 90, 90, 90, 91, 91, 243, 113, 139, 108, 95, 127, 121, 99, 257, 144, 94, 218, 148, 226, 111, 119, 100, 95, 91, 138, 201, 221, 216, 138, 125, mode p120, set 28 (mod 120), (90)'
}
}
green_patt = lt.pattern("")
red_patt = lt.pattern("")
recipes = args.print_rle.split(";")
for i, r in enumerate(recipes):
if not r.strip():
continue
parse = parse_p120_recipe(r, macros.get(args.toolkit.name, {}))
gliders_int = parse.delays
#gtxt = ""
#for g in gliders_int:
# gtxt += f",{g}"
#print(f"#{gliders_int}")
if parse.start_mode == "p120":
green_patt += fake_gun(200 * i, 0) + single_channel_stream(gliders_int)(
200 * i, 0
)
red_patt += write_text(" ".join(map(str, gliders_int)))(200 * i, 0)
elif parse.start_mode == "sc":
green_patt += PI_BLOCKS[1](200 * i, 0) + single_channel_stream(gliders_int)(200*i, 0)
red_patt += write_text(" ".join(map(str, gliders_int)))(200 * i, 0)
#follow = "" #find_minimum_follow(gtxt[1:]) -- does not work as expected
result = evaluate(f"{gliders_int}", green_patt[args.simulate_gens])
print(f"#{result}")
print(write_life_history(green=green_patt, red=red_patt))
sys.exit(0)
if args.check_for_shutoff:
print("Checking if patterns shut off the stream.")
recipes = args.check_for_shutoff.split(";")
for i, r in enumerate(recipes):
gliders_int = recipe_stream_to_delays(r)
original = fake_gun + single_channel_stream(gliders_int)
extended_fake_gun = mk_fake_gun(args.n_gun_gliders + 120)
extended = extended_fake_gun + single_channel_stream(gliders_int)
original = original[simulate_gens]
extended_simulate_gens = simulate_gens + args.toolkit.period * 120 * 4
extended = extended[extended_simulate_gens]
# allow the first n+20 gliders to possibly escape, but the rest should be absorbed.
expected_gun_positions = mk_fake_gun(args.n_gun_gliders + 20)[
extended_simulate_gens
]
if original == extended:
print("Confirmed shutoff!")
print(gliders_int)
elif (original - expected_gun_positions) == (extended - expected_gun_positions):
print("Possible shutoff with escaping gun gliders")
print(gliders_int)
sys.exit(0)
reference = fake_gun[simulate_gens]
if args.view_orientations:
if args.view_orientations in friendly_names.values():
args.view_orientations = next(k for k,v in friendly_names.items() if v == args.view_orientations)
p = lt.pattern(args.view_orientations)
results = {}
orientations = [p]
for o in ["rot270", "rot180", "rot90", "flip_x", "flip_y", "swap_xy", "swap_xy_flip"]:
orientations.append(p(o))
for oriented in orientations:
x, y, w, h = oriented.getrect()
oriented = oriented(-x + 1, -y + 1)
data = ['.'] * (w+2) * (h*2)
for (cx, cy) in oriented.coords():
data[cy*(w+2) + cx] = '#'
data = ''.join(data)
lines = []
for i in range(0, h+2):
lines.append(data[i*(w+2):(i+1)*(w+2)])
lines = '\n'.join(lines)
results[pattern_orientation(HashablePattern(oriented, oriented.digest()))] = lines
for orientation, view in sorted(results.items()):
print()
print(f'o{orientation}')
print('==========================')
print(view)
sys.exit(0)
def process_concurrent(s):
stream_to_check = s
if args.swim_upstream_after:
stream_to_check = parse_p120_recipe(','.join(map(str, s)) + ',(90), 2, 96, 96, 96, 96, 96, 96, 96, 96', {}).delays
patt = fake_gun + single_channel_stream(stream_to_check)
patt_n = patt[simulate_gens]
result = evaluate(s, patt_n)
return result
def nontrivial(evaldescription):
for i in range(len(evaldescription)):
if evaldescription[i]==f"g{i}":
continue
if evaldescription[i]=="glider(l0)(ph3)(WB1)":
continue
return True
seen = []
def recurse(s, depth=0):
stream_to_check = s
if args.swim_upstream_after:
stream_to_check = parse_p120_recipe(','.join(map(str, s)) + ',(90), 2, 96, 96, 96, 96, 96, 96, 96, 96', {}).delays
patt = fake_gun + single_channel_stream(stream_to_check)
depth_step = 1
if not args.to_apgluxe:
patt_n = patt[simulate_gens]
if (patt_n - expected_incoming_gliders_pattern).population > args.max_tree_population:
#print("big\n"+(patt_n - expected_incoming_gliders_pattern).rle_string())
return
key = (patt_n - expected_incoming_gliders_pattern).rle_string()
if key in seen[depth+1]:
#print("seen:"+key)
depth_step += 1
else:
args.seen = False
seen[depth+1].add(key)
args.tree_continue = True
e=evaluate(s, patt_n) # could reset args.tree_continue
if not args.tree_continue:
depth_step += 1
if e:
#if nontrivial(e[1]):
print(e, flush=True)
else:
print(patt.rle_string()[len("#CLL state-numbering golly\n") :])
if depth - depth_step >= 0:
seen[depth]=set()
for n in range(args.toolkit.min_spacing, args.max_delay + 1):
recurse(s + (n,), depth - depth_step)
if __name__ == "__main__":
"""
p = lt.pattern('')
for d1 in range(0, 8):
p += (fake_gun + mk_glider(0, d1) + mk_glider(0, d1 + 90))(100*(d1), 0)
print(p.rle_string())
Results:
0 - delete
1 - block on gun side
2 - backwards kickback
3 - pond on gun side
4 - delete
5 - pond on recipe side
6 - forwards kickback
7 - block on recipe side
"""
if args.concurrent:
print("Generating jobs...", file=sys.stderr)
if not args.subtree:
args.subtree = [SubTreeDef("0-7")]
len_all_options = 0
for subtree in args.subtree:
for depth in range(0, args.depth - len(subtree.options) + 1):
deeper = (tuple(range(args.toolkit.min_spacing, args.max_delay + 1)),) * depth
len_all_options += math.prod(map(len, subtree.options + deeper))
print('Total:', len_all_options, file=sys.stderr)
def all_options():
for subtree in args.subtree:
for depth in range(0, args.depth - len(subtree.options) + 1):
for starting_point in itertools.product(*subtree.options):
deeper = (tuple(range(args.toolkit.min_spacing, args.max_delay + 1)),) * depth
for d in itertools.product(*map(lambda a: (a,), starting_point), *deeper):
yield d
multiprocessing.set_start_method('spawn')
with Pool(processes=os.cpu_count() - 1) as pool:
speedo = Speedometer()
print("Starting...", file=sys.stderr)
for result in pool.imap_unordered(process_concurrent, all_options(), chunksize=256):
if speedo.tick(1):
current_per_s = speedo.get_current_speed_and_reset()
avg_per_s = speedo.overall_speed()
done = speedo.n_finished
percent = done/len_all_options*100
estimate_s = int((len_all_options - done)/avg_per_s)
estimate_min = estimate_s // 60
estimate_hr = estimate_min // 60
if estimate_hr > 0:
estimate = f"{estimate_hr}h {estimate_min % 60}m {estimate_s % 60}s"
elif estimate_min > 0:
estimate = f"{estimate_min % 60}m {estimate_s % 60}s"
else:
estimate = f"{estimate_s}s"
print(f"{current_per_s:.2f}/s, {avg_per_s:.2f}/s avg, {done}/{len_all_options} ({percent:0.2f}%), {estimate} left", file=sys.stderr)
if result:
print(*result)
pool.join()
else:
if not args.subtree:
args.subtree = [SubtreeDef("0-7")]
for d in range(0,args.depth+2):
seen.append(set())
for subtree in args.subtree:
for starting_point in itertools.product(*subtree.options):
args.seen = True
seen[args.depth - len(starting_point)+1]=set()
if args.starting_point == "not started yet":
print(f"search started, at starting point: {starting_point}")
args.starting_point = starting_point
print(f"{starting_point}", end = " ", file=sys.stderr, flush=True)
recurse(starting_point, depth=args.depth - len(starting_point))
print(f"all_seen = {args.seen}", file=sys.stderr, flush=True)
print(f"search done, last starting point: {args.starting_point}")
We can send the backward glider, destroy sending further, destroy cleanly and convert to speboe.
(I will let the search at hd0 to continue for a while to find a speboe on the other side of the lane, but we can easily live without it, and wiki could already be updated). It would be nice to have backward glider 4HD shifted to either side ... as (one side) universal pull speboes.
The backward glider 4HD shifted ("glider(l5)(1B" resp. "glider(l-3)(1B") would be nice to have as OGGCA destroyal result as well.