threshold-computers / tools /build_tile.py
CharlesCNorton
neural_tile: a self-assembling tile computer in the abstract tile assembly model. A tile binds at a site when the summed strength of its matching glues reaches tau, which is the Heaviside gate H(strength.match - tau), so growth is governed by threshold neurons. Verified: the binding decision equals the gate; a general 2-input rule-tile set grows value(x,y)=f(W,S) for f in XOR/AND/OR (529 tiles each, checked against the recurrence, XOR = Sierpinski/Rule 90); a binary counter grows one integer per row (8-bit, 255 rows, row y encodes y) with carry by cooperative binding; both directed (deterministic). Turing-universal at tau=2 (Winfree 1998). Ships variants/neural_tile.safetensors (glue tables + binding-gate weights); eval_all skips it; README section and counts updated (9 standalone machines, 28-file family).
4dbae82
Raw
History Blame Contribute Delete
4.19 kB
"""Ship the self-assembling tile computer as variants/neural_tile.safetensors: a
tile set (the binary counter) stored as its glue tables, together with the
binding gate that governs growth. A tile binds at a site when the summed
strength of its matching glues meets tau, which is the Heaviside gate
H(strength . match - tau) with per-tile weights = glue strengths and bias = -tau.
Round-trips the file, regrows the counter, and confirms row y encodes y."""
from __future__ import annotations
import json
import os
import sys
import torch
from safetensors.torch import save_file, load_file
from safetensors import safe_open
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(ROOT, "src"))
import tile as T
OUT = os.path.join(ROOT, "variants", "neural_tile.safetensors")
NBITS = 8
TAU = 2
def main() -> int:
ts = T.counter_tileset()
strength = {"edge": 2}
glues = sorted({g for t in ts for g in (t.N, t.E, t.S, t.W) if g})
gid = {g: i for i, g in enumerate(glues)}
tile_glues = torch.tensor([[gid.get(t.N, -1), gid.get(t.E, -1),
gid.get(t.S, -1), gid.get(t.W, -1)] for t in ts],
dtype=torch.long)
glue_strength = torch.tensor([strength.get(g, 1) for g in glues], dtype=torch.long)
# per-tile binding-gate weights = strengths of the tile's four glues (0 = null)
bind_w = torch.tensor([[strength.get(g, 1) if g else 0 for g in (t.N, t.E, t.S, t.W)]
for t in ts], dtype=torch.long)
tensors = {"tile_glues": tile_glues, "glue_strength": glue_strength,
"binding_weight": bind_w, "binding_bias": torch.tensor(-TAU)}
meta = {"machine": "tile", "tau": str(TAU), "glues": json.dumps(glues),
"tile_names": json.dumps([t.name for t in ts]), "program": "binary counter"}
save_file(tensors, OUT, metadata=meta)
print(f"Built {os.path.relpath(OUT, ROOT)}: binary-counter tile set")
print(f" tiles={len(ts)} glues={len(glues)} tau={TAU} size={os.path.getsize(OUT)} bytes")
# round-trip: reconstruct the tiles from the file and regrow the counter
t = load_file(OUT)
with safe_open(OUT, framework="pt") as f:
m = f.metadata()
gl = json.loads(m["glues"])
strg = {gl[i]: int(s) for i, s in enumerate(t["glue_strength"].tolist())}
tiles = []
for row, name in zip(t["tile_glues"].tolist(), json.loads(m["tile_names"])):
sides = [gl[i] if i >= 0 else "" for i in row]
tiles.append(T.Tile(N=sides[0], E=sides[1], S=sides[2], W=sides[3], name=name))
rows = (1 << NBITS) - 1
A, det = T.grow(tiles, T.counter_seed(NBITS), int(m["tau"]), strg,
(0, 0, NBITS, rows))
bad = filled = 0
for y in range(1, rows + 1):
cells = [A.get((x, y)) for x in range(NBITS)]
if any(c is None for c in cells):
continue
filled += 1
v = sum((1 if c.N == "b1" else 0) << (NBITS - 1 - x) for x, c in enumerate(cells))
if v != (y & ((1 << NBITS) - 1)):
bad += 1
print(f" round-trip regrow {NBITS}-bit counter: directed={det} rows={filled} "
f"row y == y {'OK' if bad == 0 else f'FAIL({bad})'}")
# the stored binding gate reproduces the model's binding decision
gate_ok = True
Atest = {(1, 0): T.Tile(N="b0"), (2, 0): T.Tile(N="b0")}
for ti, tt in enumerate(tiles):
for site in [(1, 1), (2, 1)]:
w = t["binding_weight"][ti].tolist()
match = [1 if tt.glue(side) and Atest.get((site[0] + dx, site[1] + dy))
and tt.glue(side) == Atest[(site[0] + dx, site[1] + dy)].glue(opp) else 0
for dx, dy, side, opp in T._SIDES]
gate = 1 if sum(wi * mi for wi, mi in zip(w, match)) + int(t["binding_bias"]) >= 0 else 0
if gate != int(T.binds(Atest, site[0], site[1], tt, TAU, strg)):
gate_ok = False
print(f" stored binding gate H(weight.match - tau) matches growth rule: "
f"{'OK' if gate_ok else 'FAIL'}")
return 0 if (bad == 0 and det and gate_ok) else 1
if __name__ == "__main__":
sys.exit(main())