CharlesCNorton commited on
Commit
4dbae82
·
1 Parent(s): 44ae225

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).

Browse files
README.md CHANGED
@@ -41,9 +41,10 @@ variants/neural_reflect.safetensors interpreter who
41
  variants/neural_attractor.safetensors energy-based solver; a multiplier run backward factors
42
  variants/neural_reversible.safetensors reversible arithmetic core, a bijection with no erasure
43
  variants/neural_ca.safetensors reversible cellular-automaton medium (no processor)
 
44
  ```
45
 
46
- Eight further machines are detailed in their own sections below, and together
47
  they carry the family from the smallest possible processor to several results
48
  about what a threshold network can be. `neural_subleq8` is a Turing-complete
49
  one-instruction computer whose entire control flow is a single threshold
@@ -69,7 +70,10 @@ reconstruct its input, a processor with no Landauer erasure floor. And
69
  `neural_ca` has no processor at all: one fixed reversible rule applied to every
70
  2x2 block of a lattice (a Margolus cellular automaton), where a particle
71
  collision computes an AND gate and ballistic transport plus collisions are the
72
- billiard-ball universality primitives.
 
 
 
73
 
74
  ---
75
 
@@ -365,7 +369,7 @@ Every weight and bias tensor in the canonical model fits in `int8`. The eval pip
365
 
366
  The 8-bit arithmetic and ALU tests use strategic sampling rather than the full 65,536-case sweep because exhaustive coverage at 8-bit is feasible but not necessary given that the circuits are constructed gate-by-gate. The 16-bit and 32-bit arithmetic tests sample edge cases only; full exhaustive coverage at those widths is infeasible without specialized hardware.
367
 
368
- `src/eval_all.py` runs the unified suite. Exit code is the number of failing variants (0 means all pass). **Testing is evaluation, not rebuilding**: `python src/eval_all.py variants/` scores all 18 fitness variants against the shipped weights in about two minutes (~6 s each, the composed float netlists evaluated in `NetlistEvaluator`'s leveled mode) and cleanly skips the eight standalone machines. Rebuilding the models (`tools/build_all.py`, ~50 min for all 18) is a separate step, needed only when the circuit constructions in `src/build.py` change; routine verification never rebuilds. The batched evaluator is population-safe: every chained intermediate (carry, borrow, mux select) is computed per population slot, so `tools/prune_weights.py`'s parallel fitness screens are exact rather than slot-0 approximations.
369
 
370
  ---
371
 
@@ -529,7 +533,7 @@ then the byte-for-byte safetensors file of the host itself.
529
 
530
  The equality is machine-checked rather than observed on one run:
531
 
532
- - the recipe codec round-trips every file in the family (all 27 shipped
533
  `.safetensors`, 971 MB, byte-identical and sha-verified);
534
  - the constructor program is executed on three independently-verified
535
  backends — a pure-integer reference, the gate-graph `SubleqThresholdCPU`
@@ -706,6 +710,39 @@ python tools/build_ca.py # ship the block rule as a ternary matrix tile (per
706
 
707
  ---
708
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709
  ## Threshold logic
710
 
711
  A threshold gate computes a Boolean function by taking a weighted sum of binary inputs and comparing the result to a threshold; the output is 1 when the sum meets or exceeds the threshold and 0 otherwise. Equivalently, it is a neuron with Heaviside step activation, integer weights, and an integer bias.
@@ -878,10 +915,11 @@ Loss components: BCE on output bits, BCE on extracted A and B bits (2× weight),
878
 
879
  ```
880
  neural_computer.safetensors canonical model (32-bit, 64 KB, ~8.61M params)
881
- variants/ 18 fitness variants + 8 standalone machines
882
  (neural_subleq8, neural_rv32, neural_matrix8,
883
  neural_subleq8io, neural_reflect,
884
- neural_attractor, neural_reversible, neural_ca)
 
885
  src/ the library (run scripts as `python src/<name>.py`)
886
  ├── build.py generator (one safetensors per invocation; also `subleq`, `rv32`)
887
  ├── quantize.py min integer dtypes + ternary verification/repair
@@ -901,15 +939,17 @@ src/ the library (run scripts as `python src/<nam
901
  │ Bennett construction
902
  ├── reversible_cpu.py reversible register machine: bijective step, backward execution
903
  ├── reversible_prog.py structured reversible programs (multiply, Fibonacci, Janus IF)
904
- ── ca.py neural_ca: reversible Margolus cellular automaton, threshold
905
- block rule, lattice reversibility and billiard-ball dynamics
 
 
906
  tools/ build_all.py (build + quantize + verify every profile),
907
  cpu_programs.py (assembler + CPU program suite), test_cpu.py
908
  (program suite vs a variant), play.py (interactive demo),
909
  prune_weights.py (GPU-batched weight reduction),
910
  build_attractor.py / test_attractor.py (neural_attractor),
911
  build_reversible.py / reversible_matrix.py (neural_reversible),
912
- build_ca.py (neural_ca matrix tile)
913
  llm_integration/ SmolLM2 extractor + circuit wrapper + training code
914
  ├── circuits.py FrozenThresholdCircuits (loads safetensors, exposes
915
  │ add_8bit / sub_8bit / mul_8bit / compare_*)
 
41
  variants/neural_attractor.safetensors energy-based solver; a multiplier run backward factors
42
  variants/neural_reversible.safetensors reversible arithmetic core, a bijection with no erasure
43
  variants/neural_ca.safetensors reversible cellular-automaton medium (no processor)
44
+ variants/neural_tile.safetensors self-assembling tile computer (computation as growth)
45
  ```
46
 
47
+ Nine further machines are detailed in their own sections below, and together
48
  they carry the family from the smallest possible processor to several results
49
  about what a threshold network can be. `neural_subleq8` is a Turing-complete
50
  one-instruction computer whose entire control flow is a single threshold
 
70
  `neural_ca` has no processor at all: one fixed reversible rule applied to every
71
  2x2 block of a lattice (a Margolus cellular automaton), where a particle
72
  collision computes an AND gate and ballistic transport plus collisions are the
73
+ billiard-ball universality primitives. And `neural_tile` computes by
74
+ self-assembly: a tile set whose binding rule is a threshold gate grows a crystal
75
+ that is the trace of a computation, a Sierpinski (Rule 90) pattern or a binary
76
+ counter, Turing-universal at temperature 2 (Winfree 1998).
77
 
78
  ---
79
 
 
369
 
370
  The 8-bit arithmetic and ALU tests use strategic sampling rather than the full 65,536-case sweep because exhaustive coverage at 8-bit is feasible but not necessary given that the circuits are constructed gate-by-gate. The 16-bit and 32-bit arithmetic tests sample edge cases only; full exhaustive coverage at those widths is infeasible without specialized hardware.
371
 
372
+ `src/eval_all.py` runs the unified suite. Exit code is the number of failing variants (0 means all pass). **Testing is evaluation, not rebuilding**: `python src/eval_all.py variants/` scores all 18 fitness variants against the shipped weights in about two minutes (~6 s each, the composed float netlists evaluated in `NetlistEvaluator`'s leveled mode) and cleanly skips the nine standalone machines. Rebuilding the models (`tools/build_all.py`, ~50 min for all 18) is a separate step, needed only when the circuit constructions in `src/build.py` change; routine verification never rebuilds. The batched evaluator is population-safe: every chained intermediate (carry, borrow, mux select) is computed per population slot, so `tools/prune_weights.py`'s parallel fitness screens are exact rather than slot-0 approximations.
373
 
374
  ---
375
 
 
533
 
534
  The equality is machine-checked rather than observed on one run:
535
 
536
+ - the recipe codec round-trips every file in the family (all 28 shipped
537
  `.safetensors`, 971 MB, byte-identical and sha-verified);
538
  - the constructor program is executed on three independently-verified
539
  backends — a pure-integer reference, the gate-graph `SubleqThresholdCPU`
 
710
 
711
  ---
712
 
713
+ ## neural_tile — computation as self-assembly
714
+
715
+ A tile computer in the abstract tile assembly model. The program is a finite set
716
+ of square tiles with glue labels and integer strengths on their edges; a seed is
717
+ placed and tiles accrete onto the assembly by one rule: a tile binds at an empty
718
+ site when the summed strength of its glues that match the already-present
719
+ neighbors reaches the temperature tau. That binding decision is the Heaviside
720
+ gate `H(sum_d strength_d * match_d - tau)`, weights the glue strengths and bias
721
+ `-tau`, so every attachment is a threshold neuron and the assembly grows site by
722
+ site under the family's gate. At tau = 2 the model is Turing-universal
723
+ (Winfree 1998).
724
+
725
+ Two directed tile sets are verified. The rule-tile set computes
726
+ `value(x,y) = f(value(x-1,y), value(x,y-1))` for any 2-input `f`: with `f` = XOR
727
+ the assembly is the Sierpinski triangle (Rule 90), and AND and OR grow their own
728
+ patterns, each of 529 tiles checked cell by cell against the recurrence. The
729
+ binary counter grows one integer per row: at 8-bit width it fills 255 rows and
730
+ row y encodes the integer y, with the increment carry propagating westward by
731
+ cooperative binding. Both tile sets are directed, so a unique tile binds at each
732
+ site and the assembly is deterministic.
733
+
734
+ The shipped `variants/neural_tile.safetensors` stores the counter tile set as its
735
+ glue tables together with the per-tile binding-gate weights (glue strengths) and
736
+ bias (`-tau`); reloading it regrows the counter and reproduces the binding
737
+ decision from the gate.
738
+
739
+ ```bash
740
+ python src/tile.py # binding gate, XOR/AND/OR rule tiles, binary counter
741
+ python tools/build_tile.py # ship + regrow variants/neural_tile.safetensors
742
+ ```
743
+
744
+ ---
745
+
746
  ## Threshold logic
747
 
748
  A threshold gate computes a Boolean function by taking a weighted sum of binary inputs and comparing the result to a threshold; the output is 1 when the sum meets or exceeds the threshold and 0 otherwise. Equivalently, it is a neuron with Heaviside step activation, integer weights, and an integer bias.
 
915
 
916
  ```
917
  neural_computer.safetensors canonical model (32-bit, 64 KB, ~8.61M params)
918
+ variants/ 18 fitness variants + 9 standalone machines
919
  (neural_subleq8, neural_rv32, neural_matrix8,
920
  neural_subleq8io, neural_reflect,
921
+ neural_attractor, neural_reversible, neural_ca,
922
+ neural_tile)
923
  src/ the library (run scripts as `python src/<name>.py`)
924
  ├── build.py generator (one safetensors per invocation; also `subleq`, `rv32`)
925
  ├── quantize.py min integer dtypes + ternary verification/repair
 
939
  │ Bennett construction
940
  ├── reversible_cpu.py reversible register machine: bijective step, backward execution
941
  ├── reversible_prog.py structured reversible programs (multiply, Fibonacci, Janus IF)
942
+ ── ca.py neural_ca: reversible Margolus cellular automaton, threshold
943
+ block rule, lattice reversibility and billiard-ball dynamics
944
+ └── tile.py neural_tile: abstract tile assembly, threshold binding rule,
945
+ XOR/Sierpinski and binary-counter tile sets
946
  tools/ build_all.py (build + quantize + verify every profile),
947
  cpu_programs.py (assembler + CPU program suite), test_cpu.py
948
  (program suite vs a variant), play.py (interactive demo),
949
  prune_weights.py (GPU-batched weight reduction),
950
  build_attractor.py / test_attractor.py (neural_attractor),
951
  build_reversible.py / reversible_matrix.py (neural_reversible),
952
+ build_ca.py (neural_ca matrix tile), build_tile.py (neural_tile)
953
  llm_integration/ SmolLM2 extractor + circuit wrapper + training code
954
  ├── circuits.py FrozenThresholdCircuits (loads safetensors, exposes
955
  │ add_8bit / sub_8bit / mul_8bit / compare_*)
src/eval_all.py CHANGED
@@ -673,6 +673,7 @@ MACHINE_VERIFIER = {
673
  "attractor": "tools/test_attractor.py",
674
  "reversible": "src/reversible.py",
675
  "ca": "src/ca.py",
 
676
  }
677
 
678
 
 
673
  "attractor": "tools/test_attractor.py",
674
  "reversible": "src/reversible.py",
675
  "ca": "src/ca.py",
676
+ "tile": "src/tile.py",
677
  }
678
 
679
 
src/tile.py ADDED
@@ -0,0 +1,222 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Self-assembling tile computer (abstract tile assembly model).
2
+
3
+ Computation is the growth of a crystal. The program is a finite set of square
4
+ tiles; each edge carries a glue label with an integer strength. A seed is placed
5
+ and tiles accrete onto the assembly by one rule: a tile binds at an empty site
6
+ if the summed strength of the glues that match its already-present neighbors is
7
+ at least the temperature tau. That binding rule is a threshold gate,
8
+
9
+ bind = H( sum_d strength_d * match_d - tau ),
10
+
11
+ a weighted sum of matching-glue indicators against tau, so every attachment is
12
+ decided by the same Heaviside neuron the rest of the repository is built from.
13
+ At tau = 2 the model is Turing-universal (Winfree 1998): a directed tile set
14
+ grows a unique structure, and that structure is the trace of a computation.
15
+
16
+ Sides are N,E,S,W; a tile's N glue abuts its north neighbor's S glue, and so on.
17
+ A glue label "" is the null glue (strength 0, matches nothing). Glue strengths
18
+ are a property of the label (matching glues have equal strength), held in a map.
19
+ """
20
+ from __future__ import annotations
21
+ from typing import Dict, List, Optional, Tuple
22
+
23
+ # side -> (dx, dy, my_side, neighbor_side)
24
+ _SIDES = [(0, 1, "N", "S"), (1, 0, "E", "W"), (0, -1, "S", "N"), (-1, 0, "W", "E")]
25
+
26
+
27
+ class Tile:
28
+ __slots__ = ("N", "E", "S", "W", "name")
29
+
30
+ def __init__(self, N="", E="", S="", W="", name=""):
31
+ self.N, self.E, self.S, self.W, self.name = N, E, S, W, name
32
+
33
+ def glue(self, side):
34
+ return getattr(self, side)
35
+
36
+
37
+ def bind_strength(A: Dict[Tuple[int, int], Tile], x: int, y: int, t: Tile,
38
+ strength: Dict[str, int]) -> int:
39
+ """Summed strength of t's glues that match the abutting neighbor glues."""
40
+ s = 0
41
+ for dx, dy, side, opp in _SIDES:
42
+ nb = A.get((x + dx, y + dy))
43
+ if nb is None:
44
+ continue
45
+ g = t.glue(side)
46
+ if g and g == nb.glue(opp):
47
+ s += strength.get(g, 1)
48
+ return s
49
+
50
+
51
+ def binds(A, x, y, t, tau, strength) -> bool:
52
+ """The threshold-gate binding decision: H(sum strength*match - tau)."""
53
+ return bind_strength(A, x, y, t, strength) >= tau
54
+
55
+
56
+ def grow(tileset: List[Tile], seed: Dict[Tuple[int, int], Tile], tau: int,
57
+ strength: Dict[str, int], bounds: Tuple[int, int, int, int],
58
+ max_tiles: int = 100000) -> Tuple[Dict[Tuple[int, int], Tile], bool]:
59
+ """Directed growth from a seed. Returns (assembly, deterministic): at every
60
+ site at most one tile binds when the set is directed, so the assembly is
61
+ unique. deterministic=False flags a site where two tiles could bind."""
62
+ x0, y0, x1, y1 = bounds
63
+ A = dict(seed)
64
+ deterministic = True
65
+ changed = True
66
+ while changed and len(A) < max_tiles:
67
+ changed = False
68
+ frontier = set()
69
+ for (x, y) in list(A):
70
+ for dx, dy, _, _ in _SIDES:
71
+ p = (x + dx, y + dy)
72
+ if p not in A and x0 <= p[0] <= x1 and y0 <= p[1] <= y1:
73
+ frontier.add(p)
74
+ for (x, y) in frontier:
75
+ binders = [t for t in tileset if binds(A, x, y, t, tau, strength)]
76
+ if len(binders) == 1:
77
+ A[(x, y)] = binders[0]
78
+ changed = True
79
+ elif len(binders) > 1:
80
+ deterministic = False
81
+ return A, deterministic
82
+
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # XOR / Sierpinski tile set: value(x,y) = value(x-1,y) XOR value(x,y-1)
86
+ # ---------------------------------------------------------------------------
87
+ def rule2_tileset(fn) -> List[Tile]:
88
+ """Rule tiles for value(x,y) = fn(W-input, S-input): four tiles, each binds
89
+ cooperatively (S and W, strength 1 each = tau) and emits fn on N and E."""
90
+ ts = []
91
+ for s in (0, 1):
92
+ for w in (0, 1):
93
+ v = fn(w, s)
94
+ ts.append(Tile(N=f"v{v}", E=f"v{v}", S=f"v{s}", W=f"v{w}",
95
+ name=f"R w{w} s{s} -> {v}"))
96
+ return ts
97
+
98
+
99
+ def sierpinski_tileset() -> List[Tile]:
100
+ return rule2_tileset(lambda w, s: w ^ s)
101
+
102
+
103
+ def _row_col_seed(bottom: List[int], left: List[int]):
104
+ """Seed the bottom row (y=0) and left column (x=0) with fixed value tiles,
105
+ presenting value glues north and east for the rule tiles above/right."""
106
+ seed = {}
107
+ for x, b in enumerate(bottom):
108
+ seed[(x, 0)] = Tile(N=f"v{b}", E="", S="", W="", name=f"seedB{x}={b}")
109
+ for y, l in enumerate(left):
110
+ if y == 0:
111
+ continue
112
+ seed[(0, y)] = Tile(N="", E=f"v{l}", S="", W="", name=f"seedL{y}={l}")
113
+ return seed
114
+
115
+
116
+ def _test_binding_gate():
117
+ """The binding decision is exactly the Heaviside threshold gate."""
118
+ strength = {"v0": 1, "v1": 1}
119
+ ts = sierpinski_tileset()
120
+ A = {(1, 0): Tile(N="v1"), (0, 1): Tile(E="v0")}
121
+ bad = 0
122
+ for t in ts:
123
+ for x, y in [(1, 1)]:
124
+ w = sum(strength.get(t.glue(side), 1)
125
+ for dx, dy, side, opp in _SIDES
126
+ if A.get((x + dx, y + dy)) and t.glue(side)
127
+ and t.glue(side) == A[(x + dx, y + dy)].glue(opp))
128
+ gate = 1 if (w - 2) >= 0 else 0 # H(sum*match - tau)
129
+ if gate != int(binds(A, x, y, t, 2, strength)):
130
+ bad += 1
131
+ print(f" binding decision == Heaviside gate H(sum-tau): {'OK' if bad == 0 else 'FAIL'}")
132
+ return bad == 0
133
+
134
+
135
+ def _test_rule2(fn, name, n=24):
136
+ strength = {"v0": 1, "v1": 1}
137
+ bottom = [1 if x == 0 else 0 for x in range(n)]
138
+ left = [1 if y == 0 else 0 for y in range(n)]
139
+ seed = _row_col_seed(bottom, left)
140
+ A, det = grow(rule2_tileset(fn), seed, 2, strength, (0, 0, n - 1, n - 1))
141
+
142
+ def val(x, y):
143
+ t = A.get((x, y))
144
+ return None if t is None else (1 if t.N == "v1" else 0)
145
+
146
+ ref = {(x, 0): bottom[x] for x in range(n)}
147
+ ref.update({(0, y): left[y] for y in range(n)})
148
+ for y in range(1, n):
149
+ for x in range(1, n):
150
+ ref[(x, y)] = fn(ref[(x - 1, y)], ref[(x, y - 1)])
151
+
152
+ filled = bad = 0
153
+ for y in range(1, n):
154
+ for x in range(1, n):
155
+ v = val(x, y)
156
+ if v is not None:
157
+ filled += 1
158
+ bad += v != ref[(x, y)]
159
+ tag = "OK" if (det and bad == 0 and filled > 0) else "FAIL"
160
+ print(f" rule-tile CA fn={name:3s}: directed={det} placed={filled} "
161
+ f"every tile = fn(W,S) {tag}")
162
+ return det and bad == 0 and filled > 0
163
+
164
+
165
+ # ---------------------------------------------------------------------------
166
+ # Binary counter: each row is the row below plus one. LSB is the right column;
167
+ # carry propagates west by cooperative binding (S = bit below, E = carry in).
168
+ # ---------------------------------------------------------------------------
169
+ def counter_tileset() -> List[Tile]:
170
+ ts = []
171
+ for b in (0, 1):
172
+ for c in (0, 1):
173
+ ts.append(Tile(N=f"b{b ^ c}", E=f"c{c}", S=f"b{b}", W=f"c{b & c}",
174
+ name=f"C b{b} c{c} -> b{b ^ c} carry{b & c}"))
175
+ ts.append(Tile(N="edge", E="", S="edge", W="c1", name="edge(+1 injector)"))
176
+ return ts
177
+
178
+
179
+ def counter_seed(n: int):
180
+ """Bottom row (y=0) all zero, plus the right-edge +1 injector column base."""
181
+ seed = {}
182
+ for x in range(n):
183
+ seed[(x, 0)] = Tile(N="b0", name=f"seed b0 col{x}")
184
+ seed[(n, 0)] = Tile(N="edge", W="c1", name="seed edge")
185
+ return seed
186
+
187
+
188
+ def _test_counter(n=6, rows=None):
189
+ rows = rows or (1 << n) - 1
190
+ strength = {"edge": 2} # value/carry glues default 1
191
+ A, det = grow(counter_tileset(), counter_seed(n), 2, strength,
192
+ (0, 0, n, rows))
193
+
194
+ def rowval(y):
195
+ bits = []
196
+ for x in range(n):
197
+ t = A.get((x, y))
198
+ if t is None:
199
+ return None
200
+ bits.append(1 if t.N == "b1" else 0)
201
+ return sum(bit << (n - 1 - x) for x, bit in enumerate(bits))
202
+
203
+ bad = filled = 0
204
+ for y in range(1, rows + 1):
205
+ v = rowval(y)
206
+ if v is not None:
207
+ filled += 1
208
+ if v != (y & ((1 << n) - 1)):
209
+ bad += 1
210
+ print(f" binary counter {n}-bit: directed={det} rows grown={filled} "
211
+ f"row y encodes the integer y {'OK' if bad == 0 else f'FAIL({bad})'}")
212
+ return det and bad == 0 and filled == rows
213
+
214
+
215
+ if __name__ == "__main__":
216
+ print("Self-assembling tile computer")
217
+ a = _test_binding_gate()
218
+ b = all(_test_rule2(fn, nm) for fn, nm in
219
+ [(lambda w, s: w ^ s, "XOR"), (lambda w, s: w & s, "AND"),
220
+ (lambda w, s: w | s, "OR")])
221
+ c = _test_counter()
222
+ print("PASS" if (a and b and c) else "FAIL")
tools/build_tile.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Ship the self-assembling tile computer as variants/neural_tile.safetensors: a
2
+ tile set (the binary counter) stored as its glue tables, together with the
3
+ binding gate that governs growth. A tile binds at a site when the summed
4
+ strength of its matching glues meets tau, which is the Heaviside gate
5
+ H(strength . match - tau) with per-tile weights = glue strengths and bias = -tau.
6
+ Round-trips the file, regrows the counter, and confirms row y encodes y."""
7
+ from __future__ import annotations
8
+ import json
9
+ import os
10
+ import sys
11
+
12
+ import torch
13
+ from safetensors.torch import save_file, load_file
14
+ from safetensors import safe_open
15
+
16
+ ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
17
+ sys.path.insert(0, os.path.join(ROOT, "src"))
18
+ import tile as T
19
+
20
+ OUT = os.path.join(ROOT, "variants", "neural_tile.safetensors")
21
+ NBITS = 8
22
+ TAU = 2
23
+
24
+
25
+ def main() -> int:
26
+ ts = T.counter_tileset()
27
+ strength = {"edge": 2}
28
+ glues = sorted({g for t in ts for g in (t.N, t.E, t.S, t.W) if g})
29
+ gid = {g: i for i, g in enumerate(glues)}
30
+ tile_glues = torch.tensor([[gid.get(t.N, -1), gid.get(t.E, -1),
31
+ gid.get(t.S, -1), gid.get(t.W, -1)] for t in ts],
32
+ dtype=torch.long)
33
+ glue_strength = torch.tensor([strength.get(g, 1) for g in glues], dtype=torch.long)
34
+ # per-tile binding-gate weights = strengths of the tile's four glues (0 = null)
35
+ bind_w = torch.tensor([[strength.get(g, 1) if g else 0 for g in (t.N, t.E, t.S, t.W)]
36
+ for t in ts], dtype=torch.long)
37
+ tensors = {"tile_glues": tile_glues, "glue_strength": glue_strength,
38
+ "binding_weight": bind_w, "binding_bias": torch.tensor(-TAU)}
39
+ meta = {"machine": "tile", "tau": str(TAU), "glues": json.dumps(glues),
40
+ "tile_names": json.dumps([t.name for t in ts]), "program": "binary counter"}
41
+ save_file(tensors, OUT, metadata=meta)
42
+ print(f"Built {os.path.relpath(OUT, ROOT)}: binary-counter tile set")
43
+ print(f" tiles={len(ts)} glues={len(glues)} tau={TAU} size={os.path.getsize(OUT)} bytes")
44
+
45
+ # round-trip: reconstruct the tiles from the file and regrow the counter
46
+ t = load_file(OUT)
47
+ with safe_open(OUT, framework="pt") as f:
48
+ m = f.metadata()
49
+ gl = json.loads(m["glues"])
50
+ strg = {gl[i]: int(s) for i, s in enumerate(t["glue_strength"].tolist())}
51
+ tiles = []
52
+ for row, name in zip(t["tile_glues"].tolist(), json.loads(m["tile_names"])):
53
+ sides = [gl[i] if i >= 0 else "" for i in row]
54
+ tiles.append(T.Tile(N=sides[0], E=sides[1], S=sides[2], W=sides[3], name=name))
55
+
56
+ rows = (1 << NBITS) - 1
57
+ A, det = T.grow(tiles, T.counter_seed(NBITS), int(m["tau"]), strg,
58
+ (0, 0, NBITS, rows))
59
+ bad = filled = 0
60
+ for y in range(1, rows + 1):
61
+ cells = [A.get((x, y)) for x in range(NBITS)]
62
+ if any(c is None for c in cells):
63
+ continue
64
+ filled += 1
65
+ v = sum((1 if c.N == "b1" else 0) << (NBITS - 1 - x) for x, c in enumerate(cells))
66
+ if v != (y & ((1 << NBITS) - 1)):
67
+ bad += 1
68
+ print(f" round-trip regrow {NBITS}-bit counter: directed={det} rows={filled} "
69
+ f"row y == y {'OK' if bad == 0 else f'FAIL({bad})'}")
70
+
71
+ # the stored binding gate reproduces the model's binding decision
72
+ gate_ok = True
73
+ Atest = {(1, 0): T.Tile(N="b0"), (2, 0): T.Tile(N="b0")}
74
+ for ti, tt in enumerate(tiles):
75
+ for site in [(1, 1), (2, 1)]:
76
+ w = t["binding_weight"][ti].tolist()
77
+ match = [1 if tt.glue(side) and Atest.get((site[0] + dx, site[1] + dy))
78
+ and tt.glue(side) == Atest[(site[0] + dx, site[1] + dy)].glue(opp) else 0
79
+ for dx, dy, side, opp in T._SIDES]
80
+ gate = 1 if sum(wi * mi for wi, mi in zip(w, match)) + int(t["binding_bias"]) >= 0 else 0
81
+ if gate != int(T.binds(Atest, site[0], site[1], tt, TAU, strg)):
82
+ gate_ok = False
83
+ print(f" stored binding gate H(weight.match - tau) matches growth rule: "
84
+ f"{'OK' if gate_ok else 'FAIL'}")
85
+ return 0 if (bad == 0 and det and gate_ok) else 1
86
+
87
+
88
+ if __name__ == "__main__":
89
+ sys.exit(main())
variants/neural_tile.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ba6e721eef8359d84ec58c46c1436c1298ac8191539adf9cc2c03ff9123583a8
3
+ size 920