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
8.59 kB
"""Self-assembling tile computer (abstract tile assembly model).
Computation is the growth of a crystal. The program is a finite set of square
tiles; each edge carries a glue label with an integer strength. A seed is placed
and tiles accrete onto the assembly by one rule: a tile binds at an empty site
if the summed strength of the glues that match its already-present neighbors is
at least the temperature tau. That binding rule is a threshold gate,
bind = H( sum_d strength_d * match_d - tau ),
a weighted sum of matching-glue indicators against tau, so every attachment is
decided by the same Heaviside neuron the rest of the repository is built from.
At tau = 2 the model is Turing-universal (Winfree 1998): a directed tile set
grows a unique structure, and that structure is the trace of a computation.
Sides are N,E,S,W; a tile's N glue abuts its north neighbor's S glue, and so on.
A glue label "" is the null glue (strength 0, matches nothing). Glue strengths
are a property of the label (matching glues have equal strength), held in a map.
"""
from __future__ import annotations
from typing import Dict, List, Optional, Tuple
# side -> (dx, dy, my_side, neighbor_side)
_SIDES = [(0, 1, "N", "S"), (1, 0, "E", "W"), (0, -1, "S", "N"), (-1, 0, "W", "E")]
class Tile:
__slots__ = ("N", "E", "S", "W", "name")
def __init__(self, N="", E="", S="", W="", name=""):
self.N, self.E, self.S, self.W, self.name = N, E, S, W, name
def glue(self, side):
return getattr(self, side)
def bind_strength(A: Dict[Tuple[int, int], Tile], x: int, y: int, t: Tile,
strength: Dict[str, int]) -> int:
"""Summed strength of t's glues that match the abutting neighbor glues."""
s = 0
for dx, dy, side, opp in _SIDES:
nb = A.get((x + dx, y + dy))
if nb is None:
continue
g = t.glue(side)
if g and g == nb.glue(opp):
s += strength.get(g, 1)
return s
def binds(A, x, y, t, tau, strength) -> bool:
"""The threshold-gate binding decision: H(sum strength*match - tau)."""
return bind_strength(A, x, y, t, strength) >= tau
def grow(tileset: List[Tile], seed: Dict[Tuple[int, int], Tile], tau: int,
strength: Dict[str, int], bounds: Tuple[int, int, int, int],
max_tiles: int = 100000) -> Tuple[Dict[Tuple[int, int], Tile], bool]:
"""Directed growth from a seed. Returns (assembly, deterministic): at every
site at most one tile binds when the set is directed, so the assembly is
unique. deterministic=False flags a site where two tiles could bind."""
x0, y0, x1, y1 = bounds
A = dict(seed)
deterministic = True
changed = True
while changed and len(A) < max_tiles:
changed = False
frontier = set()
for (x, y) in list(A):
for dx, dy, _, _ in _SIDES:
p = (x + dx, y + dy)
if p not in A and x0 <= p[0] <= x1 and y0 <= p[1] <= y1:
frontier.add(p)
for (x, y) in frontier:
binders = [t for t in tileset if binds(A, x, y, t, tau, strength)]
if len(binders) == 1:
A[(x, y)] = binders[0]
changed = True
elif len(binders) > 1:
deterministic = False
return A, deterministic
# ---------------------------------------------------------------------------
# XOR / Sierpinski tile set: value(x,y) = value(x-1,y) XOR value(x,y-1)
# ---------------------------------------------------------------------------
def rule2_tileset(fn) -> List[Tile]:
"""Rule tiles for value(x,y) = fn(W-input, S-input): four tiles, each binds
cooperatively (S and W, strength 1 each = tau) and emits fn on N and E."""
ts = []
for s in (0, 1):
for w in (0, 1):
v = fn(w, s)
ts.append(Tile(N=f"v{v}", E=f"v{v}", S=f"v{s}", W=f"v{w}",
name=f"R w{w} s{s} -> {v}"))
return ts
def sierpinski_tileset() -> List[Tile]:
return rule2_tileset(lambda w, s: w ^ s)
def _row_col_seed(bottom: List[int], left: List[int]):
"""Seed the bottom row (y=0) and left column (x=0) with fixed value tiles,
presenting value glues north and east for the rule tiles above/right."""
seed = {}
for x, b in enumerate(bottom):
seed[(x, 0)] = Tile(N=f"v{b}", E="", S="", W="", name=f"seedB{x}={b}")
for y, l in enumerate(left):
if y == 0:
continue
seed[(0, y)] = Tile(N="", E=f"v{l}", S="", W="", name=f"seedL{y}={l}")
return seed
def _test_binding_gate():
"""The binding decision is exactly the Heaviside threshold gate."""
strength = {"v0": 1, "v1": 1}
ts = sierpinski_tileset()
A = {(1, 0): Tile(N="v1"), (0, 1): Tile(E="v0")}
bad = 0
for t in ts:
for x, y in [(1, 1)]:
w = sum(strength.get(t.glue(side), 1)
for dx, dy, side, opp in _SIDES
if A.get((x + dx, y + dy)) and t.glue(side)
and t.glue(side) == A[(x + dx, y + dy)].glue(opp))
gate = 1 if (w - 2) >= 0 else 0 # H(sum*match - tau)
if gate != int(binds(A, x, y, t, 2, strength)):
bad += 1
print(f" binding decision == Heaviside gate H(sum-tau): {'OK' if bad == 0 else 'FAIL'}")
return bad == 0
def _test_rule2(fn, name, n=24):
strength = {"v0": 1, "v1": 1}
bottom = [1 if x == 0 else 0 for x in range(n)]
left = [1 if y == 0 else 0 for y in range(n)]
seed = _row_col_seed(bottom, left)
A, det = grow(rule2_tileset(fn), seed, 2, strength, (0, 0, n - 1, n - 1))
def val(x, y):
t = A.get((x, y))
return None if t is None else (1 if t.N == "v1" else 0)
ref = {(x, 0): bottom[x] for x in range(n)}
ref.update({(0, y): left[y] for y in range(n)})
for y in range(1, n):
for x in range(1, n):
ref[(x, y)] = fn(ref[(x - 1, y)], ref[(x, y - 1)])
filled = bad = 0
for y in range(1, n):
for x in range(1, n):
v = val(x, y)
if v is not None:
filled += 1
bad += v != ref[(x, y)]
tag = "OK" if (det and bad == 0 and filled > 0) else "FAIL"
print(f" rule-tile CA fn={name:3s}: directed={det} placed={filled} "
f"every tile = fn(W,S) {tag}")
return det and bad == 0 and filled > 0
# ---------------------------------------------------------------------------
# Binary counter: each row is the row below plus one. LSB is the right column;
# carry propagates west by cooperative binding (S = bit below, E = carry in).
# ---------------------------------------------------------------------------
def counter_tileset() -> List[Tile]:
ts = []
for b in (0, 1):
for c in (0, 1):
ts.append(Tile(N=f"b{b ^ c}", E=f"c{c}", S=f"b{b}", W=f"c{b & c}",
name=f"C b{b} c{c} -> b{b ^ c} carry{b & c}"))
ts.append(Tile(N="edge", E="", S="edge", W="c1", name="edge(+1 injector)"))
return ts
def counter_seed(n: int):
"""Bottom row (y=0) all zero, plus the right-edge +1 injector column base."""
seed = {}
for x in range(n):
seed[(x, 0)] = Tile(N="b0", name=f"seed b0 col{x}")
seed[(n, 0)] = Tile(N="edge", W="c1", name="seed edge")
return seed
def _test_counter(n=6, rows=None):
rows = rows or (1 << n) - 1
strength = {"edge": 2} # value/carry glues default 1
A, det = grow(counter_tileset(), counter_seed(n), 2, strength,
(0, 0, n, rows))
def rowval(y):
bits = []
for x in range(n):
t = A.get((x, y))
if t is None:
return None
bits.append(1 if t.N == "b1" else 0)
return sum(bit << (n - 1 - x) for x, bit in enumerate(bits))
bad = filled = 0
for y in range(1, rows + 1):
v = rowval(y)
if v is not None:
filled += 1
if v != (y & ((1 << n) - 1)):
bad += 1
print(f" binary counter {n}-bit: directed={det} rows grown={filled} "
f"row y encodes the integer y {'OK' if bad == 0 else f'FAIL({bad})'}")
return det and bad == 0 and filled == rows
if __name__ == "__main__":
print("Self-assembling tile computer")
a = _test_binding_gate()
b = all(_test_rule2(fn, nm) for fn, nm in
[(lambda w, s: w ^ s, "XOR"), (lambda w, s: w & s, "AND"),
(lambda w, s: w | s, "OR")])
c = _test_counter()
print("PASS" if (a and b and c) else "FAIL")