File size: 4,185 Bytes
4dbae82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
"""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())