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 | """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()) | |