An engineering walkthrough · the nks-symbolic project · June 2026
This is the record of reproducing, in a small Rust engine, essentially every quantitative experiment published about symbolic systems in Wolfram’s A New Kind of Science: the rewriting systems of §3.10, their combinator relatives, and the cellular-automaton emulations behind the book’s universality argument. It is also a record of how — the dead ends, the conventions that had to be excavated, and the places where the reproduction overtook the original.
Every discrepancy met along the way — 264 vs 263, the 7050, the “≈49”, the edge zeros — resolved to a counting convention or a definition. Not one was a behavioral disagreement with the book.
The book defines an evolution step as Mathematica’s expr /. rules (p. 102;
notes b and i). We treated that as a formal contract: on curried expressions it is a pre-order traversal —
try the rules at each node, first match wins; on a match, emit the instantiated right-hand side and descend
into neither the replacement nor the matched region; otherwise head, then argument. The consequences are
subtle enough that each gets a test mirroring a kernel one-liner:
a /. {a→b, b→c} ⇒ b — replacements are never rescanned;f[a] /. {a→g, f[g]→X} ⇒ f[g] — parents match against their original children;f[a] /. {f[a]→X, a→b} ⇒ X — outermost position beats rule order;_ binds nothing.Process. wolframscience.com 403s generic fetchers; plain curl with a browser user-agent
got every page. The decisive move came before any Rust was written: a local Wolfram kernel pinned the
canonical trajectory — first six states verbatim, twelve leaf counts, and “evolves after 264
steps” resolved to first fixed state at 263. Coding against pinned ground truth, the whole suite
passed on its first run.
Beyond the book. The book had one implementation; now there are two. A differential fuzzer runs
random rule systems on both engines and compares every state of every trajectory string-exactly
against wolframscript: 48 random systems, the canonical trajectory, and a state 1,500 steps and
~1,500 levels deep — all byte-identical.
Page 103 makes two crisp claims, and both land: a fixed configuration of 256 opening + closing
brackets after “264 steps” (fixed_at = 263), and 65,536 bracket pairs
after “65,555 steps” (fixed_at = 65,554) — one counting convention,
confirmed twice. The conserved quantity of note (e),
val(f[a]) = 2val(f) + val(a), holds at every step, predicts both
endpoints, and later became the tool for manufacturing benchmark systems of any size.
Process. The 65,555-step run was the first real failure: retaining the trajectory held billions of
shared nodes and the OS killed the process. The fix — streaming evolution, one state retained —
brought it to 173 s in 182 MB. The deeper lesson: fixed points run 65,536 levels deep, so
nothing may recurse on expression depth. Even Drop is a hand-written worklist; the
standard destructor would overflow unwinding a fixed point.
Beyond the book — by an asymptotic margin. On identical
Nest[# /. rule &, …] workloads, cross-verified state by state:
| fixed point m | steps | Rust | Wolfram 14.1 | ratio |
|---|---|---|---|---|
| 512 | 519 | 0.01 s | 0.24 s | 24× |
| 1,024 | 1,031 | 0.04 s | 1.97 s | 49× |
| 2,048 | 2,055 | 0.15 s | 16.6 s | 111× |
| 4,096 | 4,106 | 0.59 s | 135.6 s | 230× |
| 6,144 | 6,150 | 1.32 s | 496.8 s | 376× |
| 65,536 | 65,554 | 173 s | ≈ a week (extrapolated) | — |
The gap is a complexity class, not a constant: our totals grow as m2, the kernel’s as m3.04 across the whole range. Probes isolated why: the kernel’s no-match scan is excellent (linear; 8.6 ms over 65k nodes), but a rewrite at depth d costs it O(d²) to rebuild, versus O(d) for a persistent shared tree. The extrapolated week is moot anyway — with default settings the kernel hard-crashes building a fresh 16k-deep chain in one pass, a wall the big run would hit near depth 104.
x
while the rule bound x_. Mathematica’s → eagerly re-evaluates its
right-hand side, so the matcher received e[x_][y_] → BIG[BIG[y]]. Documented semantics, not
a bug (:> is immune) — and the suspicious data was caught in review, not by its author.
All headline timings re-validated under renamed variables, matching to within run-to-run noise.To enumerate symbolic systems efficiently you need what the book never gives them: a rule number. The
indexing is built on Szudzik’s elegant pairing function (NKS 2006) —
pair(x,y) = y²+x if x<y, else x²+x+y — a bijection
whose inverse needs one integer square root, and which fills square shells:
Expressions number recursively (atoms first, then f[a] = unpair(n−A)), making the
enumeration depth-ordered and downward closed. A system is pair(lhs_id, rhs_id) over
{e, x, y, z}, with a restricted-growth condition giving each rule exactly one id. The
paper’s own fixtures anchor it — Cantor order really does put s[s][s][s] at
number 741 — and a closed-form Catalan–Stirling count cross-validates the id scan through
every class up to (4,4). The p. 102 rule is system 7432.
Decode the first ids and the depth shells fill in order — everything of height h before anything of height h+1:
| id | expression | height | shell |
|---|---|---|---|
| 0 | e | 1 | N1 = 1 |
| 1 | e[e] | 2 | N2 = 2 |
| 2 | e[e[e]] | 3 | N3 = 5 |
| 3 | e[e][e] | 3 | |
| 4 | e[e][e[e]] | 3 | |
| 5 | e[e[e[e]]] | 4 | … |
| 25 | e[e][e[e]][e[e][e[e]]] | 4 | N4 = 26 |
| 26 | e[e[e[e[e]]]] | 5 | first of the next shell |
System ids stream the same way — identities flagged, α-duplicates silently absent:
$ nks-symbolic enumerate --from 0 --to 40 --list
0 1+1 id e -> e
2 1+1 - _ -> e
3 1+1 id x_ -> x
16 1+2 - e -> e[e]
17 1+2 - _ -> e[e]
20 2+1 - e[e] -> e
24 2+2 id e[e] -> e[e]
26 1+2 - x_ -> e[x]
30 2+1 - e[_] -> e
31 2+1 - e[x_] -> x
34 2+2 - e[_] -> e[e]
35 2+2 id e[x_] -> e[x]
37 1+2 - x_ -> x[e]
ids 0..40: 40 scanned, 13 canonical systems (4 identity)
$ nks-symbolic id decode --kind rule 7432
e[x_][y_] -> x[x[y]]
lhs_id 36 rhs_id 86 leaves 3+3 vars 2 identity false
e[x_] is pair(0,1)+4 = 5; e[x_][y_] is pair(5,2)+4 = 36.
x[y] is pair(1,2)+4 = 9; x[x[y]] is pair(1,9)+4 = 86.
The system: pair(36, 86) = 86²+36 = 7432. Every id in this document unwinds the same way,
one integer square root per node.And here is the enumeration behaving — every canonical non-identity system among the first forty-odd ids, run from the book’s initial condition, with the p. 102 rule closing the set from id 7432:
Sweeping the space is then just an id range. Every system with both sides of height ≤ 3 —
ids 0…163,215 — classifies in 3.3 seconds: 11,713 canonical systems; 10,868 fix, 49 go
periodic, 331 outgrow the cap, 465 run on irregularly (specimens like 14462,
x_[x_][y_] → y[e][x[e]], wander aperiodically). The book’s taxonomy, as a
histogram:
The p. 104 figure itself is reproduced too. Its six rules exist only in the page image, so they were transcribed from magnified crops of the 1588-pixel scan and validated by running them: the regenerated panels match the printed morphologies, and the maximum size-differences (48, 25, 8, 1260, 1802, 483) land on the printed plot axes (≈40, 30, 6, 1500, 2000, 400).
The densest published dataset is the combinator-properties note (p. 1122): a behavioral census of SK expressions by size, under exactly the §3.10 step. Every number reproduces — and the full census sharpens what the book sampled:
| size | expressions | already fixed | max steps | largest fixed point | non-fixing |
|---|---|---|---|---|---|
| 1–6 | 2 … 2,688 | 2, 4, 12, 40, 144, 544 | 1, 1, 2, 3, 4, 7 | 1, 2, 3, 4, 6, 10 | none |
| 7 | 16,896 | 2,128 | 12 | 41 | exactly the 2 named growers |
| 8 | 109,824 | 8,544 exact | — | — | 70: 61 capped · 7 ongoing · 2 period-3 |
The size-8 sweep takes 0.75 s and turns “it appears that 49 show exponential growth” into a
hard census — including a find the note doesn’t mention: its period-3 case has a sibling,
s[s][s][s[s]][s][s[k]], also period 3. Trajectory details land exactly: the “7050” is
the repeating cycle’s maximum (the transient peaks at 8156); the 1263→17 case fixes at step 97 under
the same convention as p. 103; the two size-7 growers hit Depth 14 at steps 26 and 25 and stay.
The single-combinator system of note p. 1123 got the treatment its minimality claim invites: all
1,033,412 single-symbol expressions of sizes 1–14, swept. Sizes ≤13 all fix; at size 14
exactly three grow — the two s→j[j] translations of the SK growers plus the
book’s verbatim third. The book asserted; the sweep proves it in five minutes.
The later chapters — nearly missed, recovered by a reader’s instinct that more material existed — carry the book’s universality argument over two constructions. Both run.
Symbolic systems emulate cellular automata (note p. 1113): ten rules — eight transcribing the CA table over {p, q} = {0, 1}, two sweeping the tape — with the exact correspondence CA step t = symbolic step t(t+n+3). States at aligned steps decode literally:
Combinators emulate rule 110 (notes pp. 1121–1122; the p. 713 figure). Each
ingredient was rebuilt and verified in sequence — the ToC abstraction algorithm, Church
numerals, the generic cell-update combinator (size 61 for rule 90, as printed), the verbatim size-16 rule-90
combinator, pair-encoded cell lists — then w, the one-full-step combinator, transcribed
from the note’s deeply nested text: 674 leaves, and repeatedly applying it reproduces rule 110 row
by row, at a cost matching the note’s “roughly (100+35n)t + 33t²”:
rule 110: w size 674 leaves; 1155 passes for t=3, n=7 (note's rough formula: 1332)
rule 90: w size 672 leaves; 1155 passes for t=3, n=7 (note's rough formula: 1332)
w got every cell right except
the two fresh edge cells, which came out 0. Mirror-rule diagnostics showed the interior perfectly aligned
— a convention, not a bug. w computes the rule over the existing window and pads a
zero per side; the note’s cryptic “padding with 0 on either side” means exactly this. Pre-pad
the initial condition with t zeros per side and every cell is either computed correctly or lies beyond the
light cone, where zero is the truth.Matching the book’s aesthetics was its own small reverse-engineering job: the original page scans, magnified, dictated italic serif on a character grid, white-filled near-square boxes over opaque outlined gray bands, no step numbers, and a rule icon that is itself a variable-flow diagram. The generator reproduces all of it for any system — then goes one step past the book’s grayscale:
The honest remainder, none of it load-bearing: the inner-part depth pattern and steps-to-fix bound of
note (e); the tree-form and Polish-notation representations (notes h, c); the h[t]
size-formula case at combinator size 8; a growth-rate fitter to reconcile the book’s eyeballed
“≈49 exponential” with our hard 70-system census; the operator-algebra translation
(p. 1172); the confluent-emulation variant (note 9.11). Each is a bounded afternoon.
| claim | source | result | test |
|---|---|---|---|
| First states and leaf counts of the canonical system | p. 102 | exact | book::first_states_match_the_kernel |
| “264 steps” to 256+256 brackets = Nest[e,e,256] | p. 103 | exact (263/264 convention) | book::fixed_point_at_263… |
| “65,555 steps” to 65,536 bracket pairs | p. 103 | exact (65,554) | book::second_initial_condition… |
| Conserved 2x+y invariant; all inits reach Nest[e,e,m] | note (e) p. 897 | exact | book::invariant_is_conserved… |
| Expression counts Binomial[2n−2,n−1]·kⁿ/n | note (d) p. 896 | exact | enumerate::counts_match_book_formula |
| No-e left-hand sides give balanced trees; x_→x[x] doubles | note (f) p. 897 | exact | semantics::rule_applies_at_the_root_atom |
| e[x_][_]→e[x[e[e][e]][e]] “can be quite complex” | note (f) p. 897 | non-fixing ≥ 2000 steps | survey_rules::note_f_complex… |
| The ReplaceAll scan-order/overlap/rescan contract | p. 102; note (i) | exact + kernel-differential | semantics::*, differential::* |
| id = s[k][k]; b = s[k[s]][k] composes | note p. 1121 | exact | semantics::combinators_from_p1121 |
| SK census sizes 1–6 (counts, fixed-now, max steps, largest fixed) | note p. 1122 | 24/24 numbers | survey_combinators::sizes_1_to_6… |
| Size 7: 2 growers of 16,896; ≤12 steps; largest 41; 2,128 fixed-now | note p. 1122 | exact | survey_combinators::size_7… |
| Size 8: 8,544 fixed points among 109,824 | note p. 1122 | exact; full census 70 non-fixing | survey_combinators::size_8_aggregate… |
| 7050-then-period-3; 1263→17 after 98 steps; fixed point of size 80 | note p. 1122 | exact (+ transient peak 8156; sibling found) | survey_combinators::size_8_named_cases |
| Size-7 growers: Depth 14 after 26 and 25 steps, then stays | note p. 1122 | exact | survey_combinators::…depth_plateau |
| j-system: smallest growth at size 14; the three identities | note p. 1123 | proven over 1,033,412 inits | survey_combinators::j_system… |
| Symbolic system emulates any elementary CA; map t(t+n+3) | note p. 1113 | rules 90/110/30 | emulation::symbolic_system_emulates_* |
| Cell combinators: generic size 61; verbatim size-16 (rule 90) | notes pp. 1121–2 | exact, all 8 neighborhoods | emulation::generic_cell_combinator… |
| w emulates rule 110; “roughly (100+35n)t+33t²” | p. 713; note p. 1122 | exact rows; 1155 vs 1332 | emulation::w_combinator… |
| p. 104: the six depicted systems and their size-difference plots | p. 104 | rules transcribed from the scan; ranges match axes | p104::p104_panels… |
| Szudzik fixtures: 741, shell grid, depth shells, ordering | NKS 2006 paper | exact | numbering::*, pairing::* |
| “≈49 exponential” at size 8 (rate bucketing) | note p. 1122 | census 70 non-fixing; rates unfitted | — |
| Operator-algebra translation; confluence variant; h[t]; notes (c, h) | pp. 1172, 1036, 1122 | open | — |
| module | provides |
|---|---|
expr | Immutable Rc-shared application trees; cached hash, LeafCount, Depth, height; iterative Eq/Drop. |
pattern, matching | Patterns (atom/blank/app), templates with constant folding, ordered rule sets; SameQ repeats. |
rewrite | step = exact ReplaceAll; first-match mode; replace_repeated; tracing: match positions, source→target pairs, per-variable flows. |
evolve | evolve (NestList) and evolve_stream (O(1) retention); fixed-point detection, growth caps. |
classify, survey | Fixed / Periodic / Capped / Ongoing with cycle detection; rule-axis and init-axis sweep drivers. |
pairing, godel | Elegant and Cantor pairing, exact isqrt; expression and system codecs with canonicality and depth shells. |
parse, print | Mathematica-notation subset in; InputForm-identical out; bracket rows; marked/spanned printing for figures. |
enumerate, invariant | Note (d)’s expression generator; the 2x+y conserved quantity. |
CLI: run (summary | exprs | sizes | brackets | matches), reduce,
id encode/decode, enumerate, survey rules|inits.
examples/p102.rs replays the book checks; examples/trace_svg.rs draws every trace
figure here; docs/fig_*.py regenerate the rest.
Drop. Recursion survives only where input size bounds
it.--ignored.The nks-symbolic repository · sources:
wolframscience.com/nks (§3.10 pp. 102–104 and notes; notes pp. 1113, 1121–1123, 1145, 1172;
main text pp. 711–714) and szudzik.com/ElegantPairing.pdf.