@hviana/sema 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (170) hide show
  1. package/AGENTS.md +6 -5
  2. package/CITATION.cff +49 -0
  3. package/HOW_IT_WORKS.md +11 -12
  4. package/README.md +7 -5
  5. package/dist/example/demo.js +25 -25
  6. package/dist/example/train_base.d.ts +19 -21
  7. package/dist/example/train_base.js +1700 -1952
  8. package/dist/src/alphabet.d.ts +3 -3
  9. package/dist/src/alphabet.js +24 -27
  10. package/dist/src/alu/src/alu.d.ts +169 -194
  11. package/dist/src/alu/src/alu.js +374 -414
  12. package/dist/src/alu/src/expr.d.ts +36 -43
  13. package/dist/src/alu/src/expr.js +252 -278
  14. package/dist/src/alu/src/index.d.ts +9 -98
  15. package/dist/src/alu/src/index.js +10 -64
  16. package/dist/src/alu/src/kernel-arith.d.ts +1 -5
  17. package/dist/src/alu/src/kernel-arith.js +195 -285
  18. package/dist/src/alu/src/kernel-bits.d.ts +1 -5
  19. package/dist/src/alu/src/kernel-bits.js +81 -115
  20. package/dist/src/alu/src/kernel-logic.js +33 -63
  21. package/dist/src/alu/src/kernel-nd.js +155 -195
  22. package/dist/src/alu/src/kernel-numeric.d.ts +16 -80
  23. package/dist/src/alu/src/kernel-numeric.js +259 -331
  24. package/dist/src/alu/src/operation.d.ts +112 -132
  25. package/dist/src/alu/src/operation.js +149 -163
  26. package/dist/src/alu/src/parser.d.ts +168 -175
  27. package/dist/src/alu/src/parser.js +382 -421
  28. package/dist/src/alu/src/resonance.d.ts +16 -25
  29. package/dist/src/alu/src/resonance.js +49 -56
  30. package/dist/src/alu/src/text.d.ts +3 -7
  31. package/dist/src/alu/src/text.js +31 -37
  32. package/dist/src/alu/src/value.d.ts +14 -14
  33. package/dist/src/alu/src/value.js +150 -160
  34. package/dist/src/alu/test/alu.test.js +543 -656
  35. package/dist/src/bytes.d.ts +1 -5
  36. package/dist/src/bytes.js +33 -40
  37. package/dist/src/config.d.ts +99 -102
  38. package/dist/src/config.js +78 -83
  39. package/dist/src/derive/src/deduction.d.ts +60 -62
  40. package/dist/src/derive/src/deduction.js +103 -109
  41. package/dist/src/derive/src/index.d.ts +1 -7
  42. package/dist/src/derive/src/priority-queue.d.ts +8 -8
  43. package/dist/src/derive/src/priority-queue.js +57 -61
  44. package/dist/src/derive/src/rewrite.d.ts +15 -18
  45. package/dist/src/derive/src/rewrite.js +59 -67
  46. package/dist/src/derive/src/trie.d.ts +56 -56
  47. package/dist/src/derive/src/trie.js +175 -179
  48. package/dist/src/derive/test/derive.test.js +96 -100
  49. package/dist/src/extension.d.ts +13 -16
  50. package/dist/src/geometry.d.ts +29 -43
  51. package/dist/src/geometry.js +324 -268
  52. package/dist/src/index.d.ts +2 -17
  53. package/dist/src/index.js +2 -12
  54. package/dist/src/ingest-cache.d.ts +25 -30
  55. package/dist/src/ingest-cache.js +108 -127
  56. package/dist/src/mind/articulation.d.ts +1 -5
  57. package/dist/src/mind/articulation.js +77 -112
  58. package/dist/src/mind/attention.d.ts +40 -108
  59. package/dist/src/mind/attention.js +754 -872
  60. package/dist/src/mind/canonical.d.ts +4 -19
  61. package/dist/src/mind/canonical.js +27 -31
  62. package/dist/src/mind/graph-search.d.ts +201 -225
  63. package/dist/src/mind/graph-search.js +742 -821
  64. package/dist/src/mind/index.d.ts +2 -10
  65. package/dist/src/mind/junction.d.ts +31 -58
  66. package/dist/src/mind/junction.js +172 -237
  67. package/dist/src/mind/learning.d.ts +16 -52
  68. package/dist/src/mind/learning.js +143 -165
  69. package/dist/src/mind/match.d.ts +20 -69
  70. package/dist/src/mind/match.js +259 -318
  71. package/dist/src/mind/mechanisms/alu.js +16 -16
  72. package/dist/src/mind/mechanisms/cast.d.ts +9 -13
  73. package/dist/src/mind/mechanisms/cast.js +363 -456
  74. package/dist/src/mind/mechanisms/confluence.d.ts +8 -12
  75. package/dist/src/mind/mechanisms/confluence.js +152 -183
  76. package/dist/src/mind/mechanisms/cover.d.ts +2 -8
  77. package/dist/src/mind/mechanisms/cover.js +148 -210
  78. package/dist/src/mind/mechanisms/extraction.d.ts +8 -34
  79. package/dist/src/mind/mechanisms/extraction.js +234 -288
  80. package/dist/src/mind/mechanisms/recall.d.ts +6 -10
  81. package/dist/src/mind/mechanisms/recall.js +141 -185
  82. package/dist/src/mind/mind.d.ts +194 -155
  83. package/dist/src/mind/mind.js +392 -289
  84. package/dist/src/mind/pipeline-mechanism.d.ts +112 -124
  85. package/dist/src/mind/pipeline-mechanism.js +161 -172
  86. package/dist/src/mind/pipeline.d.ts +4 -14
  87. package/dist/src/mind/pipeline.js +125 -189
  88. package/dist/src/mind/primitives.d.ts +20 -33
  89. package/dist/src/mind/primitives.js +124 -120
  90. package/dist/src/mind/rationale.d.ts +86 -98
  91. package/dist/src/mind/rationale.js +113 -121
  92. package/dist/src/mind/reasoning.d.ts +2 -13
  93. package/dist/src/mind/reasoning.js +129 -166
  94. package/dist/src/mind/recognition.d.ts +1 -4
  95. package/dist/src/mind/recognition.js +198 -210
  96. package/dist/src/mind/resonance.d.ts +5 -22
  97. package/dist/src/mind/resonance.js +0 -0
  98. package/dist/src/mind/trace.d.ts +6 -25
  99. package/dist/src/mind/trace.js +50 -58
  100. package/dist/src/mind/traverse.d.ts +29 -58
  101. package/dist/src/mind/traverse.js +344 -356
  102. package/dist/src/mind/types.d.ts +122 -127
  103. package/dist/src/mind/types.js +60 -69
  104. package/dist/src/rabitq-ivf/src/database.d.ts +113 -0
  105. package/dist/src/rabitq-ivf/src/database.js +201 -0
  106. package/dist/src/rabitq-ivf/src/index.d.ts +7 -0
  107. package/dist/src/{rabitq-hnsw → rabitq-ivf}/src/index.js +1 -3
  108. package/dist/src/rabitq-ivf/src/ivf.d.ts +200 -0
  109. package/dist/src/rabitq-ivf/src/ivf.js +1165 -0
  110. package/dist/src/rabitq-ivf/src/prng.d.ts +19 -0
  111. package/dist/src/rabitq-ivf/src/prng.js +36 -0
  112. package/dist/src/rabitq-ivf/src/rabitq.d.ts +95 -0
  113. package/dist/src/rabitq-ivf/src/rabitq.js +283 -0
  114. package/dist/src/sema.d.ts +9 -13
  115. package/dist/src/sema.js +26 -40
  116. package/dist/src/store-sqlite.d.ts +171 -166
  117. package/dist/src/store-sqlite.js +768 -662
  118. package/dist/src/store.d.ts +597 -630
  119. package/dist/src/store.js +1434 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +33 -15
  123. package/index.html +65 -0
  124. package/package.json +1 -1
  125. package/src/alu/README.md +1 -1
  126. package/src/alu/src/index.ts +1 -1
  127. package/src/config.ts +19 -27
  128. package/src/geometry.ts +93 -1
  129. package/src/index.ts +6 -11
  130. package/src/mind/attention.ts +11 -6
  131. package/src/mind/mechanisms/recall.ts +29 -13
  132. package/src/mind/mind.ts +250 -19
  133. package/src/mind/primitives.ts +43 -6
  134. package/src/mind/recognition.ts +26 -8
  135. package/src/mind/traverse.ts +47 -0
  136. package/src/mind/types.ts +20 -21
  137. package/src/rabitq-ivf/README.md +56 -0
  138. package/src/rabitq-ivf/src/database.ts +276 -0
  139. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  140. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  141. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  142. package/src/store-sqlite.ts +204 -9
  143. package/src/store.ts +27 -34
  144. package/test/08-storage.test.mjs +3 -3
  145. package/test/13-conversation.test.mjs +190 -20
  146. package/test/14-scaling.test.mjs +2 -2
  147. package/test/35-ivf.test.mjs +263 -0
  148. package/test/36-bloom.test.mjs +123 -0
  149. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  150. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  151. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  152. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  153. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  154. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  155. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  156. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  157. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  158. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  159. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  160. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  161. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  162. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  163. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  164. package/src/rabitq-hnsw/README.md +0 -303
  165. package/src/rabitq-hnsw/src/database.ts +0 -492
  166. package/src/rabitq-hnsw/src/heap.ts +0 -90
  167. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  168. package/src/rabitq-hnsw/src/store.ts +0 -994
  169. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  170. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -5,76 +5,35 @@
5
5
  // host's halo space, so the polymorphic inverse is exercised with zero coupling).
6
6
  import { test } from "node:test";
7
7
  import assert from "node:assert/strict";
8
- import {
9
- addBits,
10
- Alu,
11
- asReal,
12
- compareBits,
13
- converge,
14
- decimalCodec,
15
- diff,
16
- dot,
17
- freeVariables,
18
- int,
19
- integrate,
20
- interpolate,
21
- isNd,
22
- linsolve,
23
- matMul,
24
- mulBits,
25
- nd,
26
- negateBits,
27
- NO_RESONANCE,
28
- odeSolve,
29
- OperationRegistry,
30
- optimize,
31
- parseValue,
32
- polyEval,
33
- powerEig,
34
- prefetchRecognisedOps,
35
- real,
36
- registerArith,
37
- registerBits,
38
- registerLogic,
39
- registerNd,
40
- registerNumeric,
41
- regress,
42
- signBits,
43
- solve,
44
- symbol,
45
- tagOf,
46
- topSingular,
47
- } from "../src/index.js";
8
+ import { addBits, Alu, asReal, compareBits, converge, decimalCodec, diff, dot, freeVariables, int, integrate, interpolate, isNd, linsolve, matMul, mulBits, nd, negateBits, NO_RESONANCE, odeSolve, OperationRegistry, optimize, parseValue, polyEval, powerEig, prefetchRecognisedOps, real, registerArith, registerBits, registerLogic, registerNd, registerNumeric, regress, signBits, solve, symbol, tagOf, topSingular, } from "../src/index.js";
48
9
  // ── helpers ─────────────────────────────────────────────────────────────────
49
10
  const enc = (s) => new TextEncoder().encode(s);
50
11
  const dec = (b) => new TextDecoder().decode(b);
51
- const close = (a, b, eps = 1e-6) =>
52
- assert.ok(Math.abs(a - b) <= eps, `${a} !~ ${b} (eps ${eps})`);
12
+ const close = (a, b, eps = 1e-6) => assert.ok(Math.abs(a - b) <= eps, `${a} !~ ${b} (eps ${eps})`);
53
13
  /** Build an ALU Value from a JS literal — a TEST fixture, NOT a parser: the
54
14
  * kernel no longer reads list structure from bytes (that is the host's
55
15
  * meaning-level job — see src/mind/mind.ts recogniseValue), so the kernel's own tests
56
16
  * construct Values directly. An array → nd, an integer → int, a fractional
57
17
  * number → real, a string → an opaque symbol (its bytes verbatim). */
58
- const V = (x) =>
59
- Array.isArray(x)
18
+ const V = (x) => Array.isArray(x)
60
19
  ? nd(x.map(V))
61
20
  : typeof x === "number"
62
- ? (Number.isInteger(x) ? int(BigInt(x)) : real(x))
63
- : symbol(enc(x));
21
+ ? (Number.isInteger(x) ? int(BigInt(x)) : real(x))
22
+ : symbol(enc(x));
64
23
  /** A logic-only registry + context — the substrate the bit bootstrap runs on. */
65
24
  function logicCtx() {
66
- const r = new OperationRegistry();
67
- registerLogic(r);
68
- return r.context(NO_RESONANCE, { tol: 1e-10, maxIter: 1000 });
25
+ const r = new OperationRegistry();
26
+ registerLogic(r);
27
+ return r.context(NO_RESONANCE, { tol: 1e-10, maxIter: 1000 });
69
28
  }
70
29
  /** A full registry context (all kernels). */
71
30
  function fullCtx(resonance = NO_RESONANCE) {
72
- const r = new OperationRegistry();
73
- registerLogic(r);
74
- registerBits(r);
75
- registerArith(r);
76
- registerNumeric(r);
77
- return { r, ctx: r.context(resonance, { tol: 1e-12, maxIter: 2000 }) };
31
+ const r = new OperationRegistry();
32
+ registerLogic(r);
33
+ registerBits(r);
34
+ registerArith(r);
35
+ registerNumeric(r);
36
+ return { r, ctx: r.context(resonance, { tol: 1e-12, maxIter: 2000 }) };
78
37
  }
79
38
  const B = (v) => (v.domain === "bit" ? v.b : NaN);
80
39
  const N = (v) => (v.domain === "int" ? v.n : NaN);
@@ -82,703 +41,640 @@ const N = (v) => (v.domain === "int" ? v.n : NaN);
82
41
  // 1 — Logic: the completeness layer. nand is the one axiom; every gate derives.
83
42
  // ─────────────────────────────────────────────────────────────────────────────
84
43
  test("nand truth table is the irreducible axiom", () => {
85
- const ctx = logicCtx();
86
- const t = (a, b) => B(ctx.apply("nand", [int(BigInt(a)), int(BigInt(b))]));
87
- assert.equal(t(0, 0), 1);
88
- assert.equal(t(0, 1), 1);
89
- assert.equal(t(1, 0), 1);
90
- assert.equal(t(1, 1), 0);
44
+ const ctx = logicCtx();
45
+ const t = (a, b) => B(ctx.apply("nand", [int(BigInt(a)), int(BigInt(b))]));
46
+ assert.equal(t(0, 0), 1);
47
+ assert.equal(t(0, 1), 1);
48
+ assert.equal(t(1, 0), 1);
49
+ assert.equal(t(1, 1), 0);
91
50
  });
92
51
  test("not/and/or derive from nand and match their truth tables", () => {
93
- const ctx = logicCtx();
94
- const bit01 = (n) => int(BigInt(n));
95
- assert.equal(B(ctx.apply("not", [bit01(0)])), 1);
96
- assert.equal(B(ctx.apply("not", [bit01(1)])), 0);
97
- for (
98
- const [a, b, and, or] of [
99
- [0, 0, 0, 0],
100
- [0, 1, 0, 1],
101
- [1, 0, 0, 1],
102
- [1, 1, 1, 1],
103
- ]
104
- ) {
105
- assert.equal(B(ctx.apply("and", [bit01(a), bit01(b)])), and);
106
- assert.equal(B(ctx.apply("or", [bit01(a), bit01(b)])), or);
107
- }
52
+ const ctx = logicCtx();
53
+ const bit01 = (n) => int(BigInt(n));
54
+ assert.equal(B(ctx.apply("not", [bit01(0)])), 1);
55
+ assert.equal(B(ctx.apply("not", [bit01(1)])), 0);
56
+ for (const [a, b, and, or] of [
57
+ [0, 0, 0, 0],
58
+ [0, 1, 0, 1],
59
+ [1, 0, 0, 1],
60
+ [1, 1, 1, 1],
61
+ ]) {
62
+ assert.equal(B(ctx.apply("and", [bit01(a), bit01(b)])), and);
63
+ assert.equal(B(ctx.apply("or", [bit01(a), bit01(b)])), or);
64
+ }
108
65
  });
109
66
  test("nor/xor/xnor/implies/iff all derive from nand", () => {
110
- const ctx = logicCtx();
111
- const bit01 = (n) => int(BigInt(n));
112
- const truth = (name) =>
113
- [0, 1].flatMap((a) =>
114
- [0, 1].map((b) => B(ctx.apply(name, [bit01(a), bit01(b)])))
115
- );
116
- assert.deepEqual(truth("nor"), [1, 0, 0, 0]);
117
- assert.deepEqual(truth("xor"), [0, 1, 1, 0]);
118
- assert.deepEqual(truth("xnor"), [1, 0, 0, 1]);
119
- assert.deepEqual(truth("implies"), [1, 1, 0, 1]);
120
- assert.deepEqual(truth("iff"), [1, 0, 0, 1]);
67
+ const ctx = logicCtx();
68
+ const bit01 = (n) => int(BigInt(n));
69
+ const truth = (name) => [0, 1].flatMap((a) => [0, 1].map((b) => B(ctx.apply(name, [bit01(a), bit01(b)]))));
70
+ assert.deepEqual(truth("nor"), [1, 0, 0, 0]);
71
+ assert.deepEqual(truth("xor"), [0, 1, 1, 0]);
72
+ assert.deepEqual(truth("xnor"), [1, 0, 0, 1]);
73
+ assert.deepEqual(truth("implies"), [1, 1, 0, 1]);
74
+ assert.deepEqual(truth("iff"), [1, 0, 0, 1]);
121
75
  });
122
76
  test("mux(s,a,b) selects a when s=0, b when s=1 — the bridge to control flow", () => {
123
- const ctx = logicCtx();
124
- const bit01 = (n) => int(BigInt(n));
125
- // s=0 → a
126
- assert.equal(B(ctx.apply("mux", [bit01(0), bit01(0), bit01(1)])), 0);
127
- assert.equal(B(ctx.apply("mux", [bit01(0), bit01(1), bit01(0)])), 1);
128
- // s=1 → b
129
- assert.equal(B(ctx.apply("mux", [bit01(1), bit01(0), bit01(1)])), 1);
130
- assert.equal(B(ctx.apply("mux", [bit01(1), bit01(1), bit01(0)])), 0);
77
+ const ctx = logicCtx();
78
+ const bit01 = (n) => int(BigInt(n));
79
+ // s=0 → a
80
+ assert.equal(B(ctx.apply("mux", [bit01(0), bit01(0), bit01(1)])), 0);
81
+ assert.equal(B(ctx.apply("mux", [bit01(0), bit01(1), bit01(0)])), 1);
82
+ // s=1 → b
83
+ assert.equal(B(ctx.apply("mux", [bit01(1), bit01(0), bit01(1)])), 1);
84
+ assert.equal(B(ctx.apply("mux", [bit01(1), bit01(1), bit01(0)])), 0);
131
85
  });
132
86
  // ─────────────────────────────────────────────────────────────────────────────
133
87
  // 2 — Arithmetic: the bit-vector bootstrap is EXACT (the nand→everything proof).
134
88
  // ─────────────────────────────────────────────────────────────────────────────
135
89
  test("full_adder built from xor/and/or matches its truth table", () => {
136
- const { r, ctx } = fullCtx();
137
- assert.ok(r.get("bits.fullAdder"));
138
- // sum in bit 0, carry in bit 1.
139
- const fa = (a, b, c) =>
140
- N(ctx.apply("bits.fullAdder", [
141
- int(BigInt(a)),
142
- int(BigInt(b)),
143
- int(BigInt(c)),
90
+ const { r, ctx } = fullCtx();
91
+ assert.ok(r.get("bits.fullAdder"));
92
+ // sum in bit 0, carry in bit 1.
93
+ const fa = (a, b, c) => N(ctx.apply("bits.fullAdder", [
94
+ int(BigInt(a)),
95
+ int(BigInt(b)),
96
+ int(BigInt(c)),
144
97
  ]));
145
- assert.equal(fa(0, 0, 0), 0n); // sum 0 carry 0
146
- assert.equal(fa(1, 0, 0), 1n); // sum 1 carry 0
147
- assert.equal(fa(1, 1, 0), 2n); // sum 0 carry 1
148
- assert.equal(fa(1, 1, 1), 3n); // sum 1 carry 1
98
+ assert.equal(fa(0, 0, 0), 0n); // sum 0 carry 0
99
+ assert.equal(fa(1, 0, 0), 1n); // sum 1 carry 0
100
+ assert.equal(fa(1, 1, 0), 2n); // sum 0 carry 1
101
+ assert.equal(fa(1, 1, 1), 3n); // sum 1 carry 1
149
102
  });
150
103
  test("ripple add / two's-complement negate / shift-add multiply equal native bigint", () => {
151
- const ctx = logicCtx();
152
- const cases = [
153
- [0n, 0n],
154
- [5n, 7n],
155
- [12n, 30n],
156
- [-3n, 8n],
157
- [-15n, -9n],
158
- [123n, 456n],
159
- [1000n, -1n],
160
- ];
161
- for (const [a, b] of cases) {
162
- assert.equal(addBits(ctx, a, b), a + b, `add ${a}+${b}`);
163
- assert.equal(negateBits(ctx, a), -a, `negate ${a}`);
164
- assert.equal(mulBits(ctx, a, b), a * b, `mul ${a}*${b}`);
165
- assert.equal(
166
- signBits(ctx, a),
167
- a > 0n ? 1n : a < 0n ? -1n : 0n,
168
- `sign ${a}`,
169
- );
170
- assert.equal(
171
- compareBits(ctx, a, b),
172
- a > b ? 1n : a < b ? -1n : 0n,
173
- `compare ${a}?${b}`,
174
- );
175
- }
104
+ const ctx = logicCtx();
105
+ const cases = [
106
+ [0n, 0n],
107
+ [5n, 7n],
108
+ [12n, 30n],
109
+ [-3n, 8n],
110
+ [-15n, -9n],
111
+ [123n, 456n],
112
+ [1000n, -1n],
113
+ ];
114
+ for (const [a, b] of cases) {
115
+ assert.equal(addBits(ctx, a, b), a + b, `add ${a}+${b}`);
116
+ assert.equal(negateBits(ctx, a), -a, `negate ${a}`);
117
+ assert.equal(mulBits(ctx, a, b), a * b, `mul ${a}*${b}`);
118
+ assert.equal(signBits(ctx, a), a > 0n ? 1n : a < 0n ? -1n : 0n, `sign ${a}`);
119
+ assert.equal(compareBits(ctx, a, b), a > b ? 1n : a < b ? -1n : 0n, `compare ${a}?${b}`);
120
+ }
176
121
  });
177
122
  test("the bit bootstrap stays exact past 2^53", () => {
178
- const ctx = logicCtx();
179
- const a = 9007199254740993n; // 2^53 + 1, not representable as a double
180
- const b = 1000000007n;
181
- assert.equal(mulBits(ctx, a, b), a * b);
123
+ const ctx = logicCtx();
124
+ const a = 9007199254740993n; // 2^53 + 1, not representable as a double
125
+ const b = 1000000007n;
126
+ assert.equal(mulBits(ctx, a, b), a * b);
182
127
  });
183
128
  // ─────────────────────────────────────────────────────────────────────────────
184
129
  // 3 — Arithmetic: polymorphic primitives + the derived field and order.
185
130
  // ─────────────────────────────────────────────────────────────────────────────
186
131
  test("add/multiply are exact on ints and lift to reals", () => {
187
- const { ctx } = fullCtx();
188
- assert.equal(N(ctx.apply("add", [int(2n), int(2n)])), 4n);
189
- assert.equal(N(ctx.apply("multiply", [int(6n), int(7n)])), 42n);
190
- // A real operand lifts the whole expression.
191
- const r = ctx.apply("add", [int(2n), real(0.5)]);
192
- assert.equal(r.domain, "real");
193
- close(asReal(r), 2.5);
132
+ const { ctx } = fullCtx();
133
+ assert.equal(N(ctx.apply("add", [int(2n), int(2n)])), 4n);
134
+ assert.equal(N(ctx.apply("multiply", [int(6n), int(7n)])), 42n);
135
+ // A real operand lifts the whole expression.
136
+ const r = ctx.apply("add", [int(2n), real(0.5)]);
137
+ assert.equal(r.domain, "real");
138
+ close(asReal(r), 2.5);
194
139
  });
195
140
  test("subtract = add∘negate, divide = multiply∘reciprocal", () => {
196
- const { ctx } = fullCtx();
197
- assert.equal(N(ctx.apply("subtract", [int(10n), int(3n)])), 7n);
198
- close(asReal(ctx.apply("divide", [int(7n), int(2n)])), 3.5);
141
+ const { ctx } = fullCtx();
142
+ assert.equal(N(ctx.apply("subtract", [int(10n), int(3n)])), 7n);
143
+ close(asReal(ctx.apply("divide", [int(7n), int(2n)])), 3.5);
199
144
  });
200
145
  test("every comparison = sign∘subtract", () => {
201
- const { ctx } = fullCtx();
202
- const b = (name, x, y) => ctx.apply(name, [real(x), real(y)]);
203
- assert.equal(B(b("lt", 2, 3)), 1);
204
- assert.equal(B(b("lt", 3, 3)), 0);
205
- assert.equal(B(b("le", 3, 3)), 1);
206
- assert.equal(B(b("gt", 5, 3)), 1);
207
- assert.equal(B(b("ge", 2, 3)), 0);
208
- assert.equal(B(b("eq", 4, 4)), 1);
209
- assert.equal(B(b("ne", 4, 5)), 1);
146
+ const { ctx } = fullCtx();
147
+ const b = (name, x, y) => ctx.apply(name, [real(x), real(y)]);
148
+ assert.equal(B(b("lt", 2, 3)), 1);
149
+ assert.equal(B(b("lt", 3, 3)), 0);
150
+ assert.equal(B(b("le", 3, 3)), 1);
151
+ assert.equal(B(b("gt", 5, 3)), 1);
152
+ assert.equal(B(b("ge", 2, 3)), 0);
153
+ assert.equal(B(b("eq", 4, 4)), 1);
154
+ assert.equal(B(b("ne", 4, 5)), 1);
210
155
  });
211
156
  test("abs/min/max/power/gcd derive correctly", () => {
212
- const { ctx } = fullCtx();
213
- assert.equal(N(ctx.apply("abs", [int(-9n)])), 9n);
214
- assert.equal(N(ctx.apply("min", [int(3n), int(8n)])), 3n);
215
- assert.equal(N(ctx.apply("max", [int(3n), int(8n)])), 8n);
216
- assert.equal(N(ctx.apply("power", [int(2n), int(10n)])), 1024n);
217
- close(asReal(ctx.apply("power", [real(2), int(-2n)])), 0.25);
218
- assert.equal(N(ctx.apply("gcd", [int(48n), int(36n)])), 12n);
157
+ const { ctx } = fullCtx();
158
+ assert.equal(N(ctx.apply("abs", [int(-9n)])), 9n);
159
+ assert.equal(N(ctx.apply("min", [int(3n), int(8n)])), 3n);
160
+ assert.equal(N(ctx.apply("max", [int(3n), int(8n)])), 8n);
161
+ assert.equal(N(ctx.apply("power", [int(2n), int(10n)])), 1024n);
162
+ close(asReal(ctx.apply("power", [real(2), int(-2n)])), 0.25);
163
+ assert.equal(N(ctx.apply("gcd", [int(48n), int(36n)])), 12n);
219
164
  });
220
165
  test("real power via exp∘log agrees with Math.pow", () => {
221
- const { ctx } = fullCtx();
222
- close(asReal(ctx.apply("power", [real(2), real(0.5)])), Math.SQRT2, 1e-6);
166
+ const { ctx } = fullCtx();
167
+ close(asReal(ctx.apply("power", [real(2), real(0.5)])), Math.SQRT2, 1e-6);
223
168
  });
224
169
  // ─────────────────────────────────────────────────────────────────────────────
225
170
  // 4 — Arithmetic: polynomial / vector / matrix / linsolve (structured arith).
226
171
  // ─────────────────────────────────────────────────────────────────────────────
227
172
  test("polyEval (Horner), dot, matMul", () => {
228
- // 1 + 2x + 3x^2 at x=2 → 1 + 4 + 12 = 17
229
- close(polyEval([1, 2, 3], 2), 17);
230
- close(dot([1, 2, 3], [4, 5, 6]), 32);
231
- assert.deepEqual(matMul([[1, 2], [3, 4]], [[5, 6], [7, 8]]), [
232
- [19, 22],
233
- [43, 50],
234
- ]);
173
+ // 1 + 2x + 3x^2 at x=2 → 1 + 4 + 12 = 17
174
+ close(polyEval([1, 2, 3], 2), 17);
175
+ close(dot([1, 2, 3], [4, 5, 6]), 32);
176
+ assert.deepEqual(matMul([[1, 2], [3, 4]], [[5, 6], [7, 8]]), [
177
+ [19, 22],
178
+ [43, 50],
179
+ ]);
235
180
  });
236
181
  test("linsolve solves a 3x3 system (Gaussian elimination)", () => {
237
- // x + y + z = 6 ; 2y + 5z = -4 ; 2x + 5y - z = 27 → (5, 3, -2)
238
- const x = linsolve([[1, 1, 1], [0, 2, 5], [2, 5, -1]], [6, -4, 27]);
239
- assert.ok(x);
240
- close(x[0], 5);
241
- close(x[1], 3);
242
- close(x[2], -2);
182
+ // x + y + z = 6 ; 2y + 5z = -4 ; 2x + 5y - z = 27 → (5, 3, -2)
183
+ const x = linsolve([[1, 1, 1], [0, 2, 5], [2, 5, -1]], [6, -4, 27]);
184
+ assert.ok(x);
185
+ close(x[0], 5);
186
+ close(x[1], 3);
187
+ close(x[2], -2);
243
188
  });
244
189
  test("linsolve returns null for a singular matrix", () => {
245
- assert.equal(linsolve([[1, 2], [2, 4]], [3, 6]), null);
190
+ assert.equal(linsolve([[1, 2], [2, 4]], [3, 6]), null);
246
191
  });
247
192
  // ─────────────────────────────────────────────────────────────────────────────
248
193
  // 5 — Numerical: the limit layer. Each op is a converge instance.
249
194
  // ─────────────────────────────────────────────────────────────────────────────
250
195
  test("converge reaches a contraction's fixed point", () => {
251
- // x ← (x + 2/x)/2 converges to √2.
252
- const fp = converge((x) => (x + 2 / x) / 2, 1, 1e-12, 1000);
253
- close(fp, Math.SQRT2, 1e-9);
196
+ // x ← (x + 2/x)/2 converges to √2.
197
+ const fp = converge((x) => (x + 2 / x) / 2, 1, 1e-12, 1000);
198
+ close(fp, Math.SQRT2, 1e-9);
254
199
  });
255
200
  test("diff ≈ analytic derivative", () => {
256
- close(diff((x) => x * x, 3, 1e-10, 200), 6, 1e-4);
257
- close(diff(Math.sin, 0, 1e-10, 200), 1, 1e-4);
201
+ close(diff((x) => x * x, 3, 1e-10, 200), 6, 1e-4);
202
+ close(diff(Math.sin, 0, 1e-10, 200), 1, 1e-4);
258
203
  });
259
204
  test("integrate ≈ closed form", () => {
260
- close(integrate((x) => x, 0, 1, 1e-10, 50), 0.5, 1e-6);
261
- close(integrate((x) => x * x, 0, 3, 1e-10, 50), 9, 1e-5);
205
+ close(integrate((x) => x, 0, 1, 1e-10, 50), 0.5, 1e-6);
206
+ close(integrate((x) => x * x, 0, 3, 1e-10, 50), 9, 1e-5);
262
207
  });
263
208
  test("solve (Newton) finds a root", () => {
264
- close(solve((x) => x * x - 2, 1, 1e-12, 200), Math.SQRT2, 1e-8);
209
+ close(solve((x) => x * x - 2, 1, 1e-12, 200), Math.SQRT2, 1e-8);
265
210
  });
266
211
  test("exp/log/sin/cos/sqrt converge to the right limits", () => {
267
- const { ctx } = fullCtx();
268
- const f = (name, x) => asReal(ctx.apply(name, [real(x)]));
269
- close(f("exp", 0), 1, 1e-9);
270
- close(f("exp", 1), Math.E, 1e-7);
271
- close(f("log", Math.E), 1, 1e-7);
272
- close(f("sin", 0), 0, 1e-9);
273
- close(f("sin", Math.PI / 2), 1, 1e-7);
274
- close(f("cos", 0), 1, 1e-9);
275
- close(f("sqrt", 2), Math.SQRT2, 1e-8);
276
- close(f("sqrt", 144), 12, 1e-8);
212
+ const { ctx } = fullCtx();
213
+ const f = (name, x) => asReal(ctx.apply(name, [real(x)]));
214
+ close(f("exp", 0), 1, 1e-9);
215
+ close(f("exp", 1), Math.E, 1e-7);
216
+ close(f("log", Math.E), 1, 1e-7);
217
+ close(f("sin", 0), 0, 1e-9);
218
+ close(f("sin", Math.PI / 2), 1, 1e-7);
219
+ close(f("cos", 0), 1, 1e-9);
220
+ close(f("sqrt", 2), Math.SQRT2, 1e-8);
221
+ close(f("sqrt", 144), 12, 1e-8);
277
222
  });
278
223
  test("optimize finds a minimum; odeSolve integrates y'=y", () => {
279
- close(optimize((x) => (x - 3) * (x - 3), 0, 1e-12, 500), 3, 1e-4);
280
- close(odeSolve((_t, y) => y, 0, 1, 1, 2000), Math.E, 1e-4);
224
+ close(optimize((x) => (x - 3) * (x - 3), 0, 1e-12, 500), 3, 1e-4);
225
+ close(odeSolve((_t, y) => y, 0, 1, 1, 2000), Math.E, 1e-4);
281
226
  });
282
227
  test("regress recovers a known line; interpolate is linear", () => {
283
- // y = 2x + 1 sampled exactly → coeffs [1, 2].
284
- const xs = [0, 1, 2, 3];
285
- const ys = xs.map((x) => 2 * x + 1);
286
- const c = regress(xs, ys, 1);
287
- assert.ok(c);
288
- close(c[0], 1, 1e-6);
289
- close(c[1], 2, 1e-6);
290
- close(interpolate([0, 10], [0, 100], 5), 50);
228
+ // y = 2x + 1 sampled exactly → coeffs [1, 2].
229
+ const xs = [0, 1, 2, 3];
230
+ const ys = xs.map((x) => 2 * x + 1);
231
+ const c = regress(xs, ys, 1);
232
+ assert.ok(c);
233
+ close(c[0], 1, 1e-6);
234
+ close(c[1], 2, 1e-6);
235
+ close(interpolate([0, 10], [0, 100], 5), 50);
291
236
  });
292
237
  test("powerEig / topSingular by power iteration", () => {
293
- // Diagonal(5, 2): dominant eigenvalue 5.
294
- const { value } = powerEig([[5, 0], [0, 2]], 1e-12, 1000);
295
- close(value, 5, 1e-6);
296
- // Largest singular value of diag(3,4) is 4.
297
- close(topSingular([[3, 0], [0, 4]], 1e-12, 1000), 4, 1e-6);
238
+ // Diagonal(5, 2): dominant eigenvalue 5.
239
+ const { value } = powerEig([[5, 0], [0, 2]], 1e-12, 1000);
240
+ close(value, 5, 1e-6);
241
+ // Largest singular value of diag(3,4) is 4.
242
+ close(topSingular([[3, 0], [0, 4]], 1e-12, 1000), 4, 1e-6);
298
243
  });
299
244
  // ─────────────────────────────────────────────────────────────────────────────
300
245
  // 6 — The Operation model: derived is indistinguishable; one-line extension.
301
246
  // ─────────────────────────────────────────────────────────────────────────────
302
247
  test("a derived op has the same record shape as a primitive", () => {
303
- const { r } = fullCtx();
304
- const nand = r.get("nand");
305
- const sub = r.get("subtract");
306
- assert.equal(nand.primitive, true);
307
- assert.equal(sub.primitive, false);
308
- // Same fields; a caller cannot tell them apart structurally.
309
- for (const k of ["name", "arity", "primitive", "forms", "fn"]) {
310
- assert.ok(k in nand && k in sub);
311
- }
248
+ const { r } = fullCtx();
249
+ const nand = r.get("nand");
250
+ const sub = r.get("subtract");
251
+ assert.equal(nand.primitive, true);
252
+ assert.equal(sub.primitive, false);
253
+ // Same fields; a caller cannot tell them apart structurally.
254
+ for (const k of ["name", "arity", "primitive", "forms", "fn"]) {
255
+ assert.ok(k in nand && k in sub);
256
+ }
312
257
  });
313
258
  test("a brand-new derived op is registered in one call and computes", () => {
314
- const { r, ctx } = fullCtx();
315
- r.derive("hypot", 2, ["hypot"], (args, c) =>
316
- c.apply("sqrt", [
317
- c.apply("add", [
318
- c.apply("multiply", [args[0], args[0]]),
319
- c.apply("multiply", [args[1], args[1]]),
320
- ]),
259
+ const { r, ctx } = fullCtx();
260
+ r.derive("hypot", 2, ["hypot"], (args, c) => c.apply("sqrt", [
261
+ c.apply("add", [
262
+ c.apply("multiply", [args[0], args[0]]),
263
+ c.apply("multiply", [args[1], args[1]]),
264
+ ]),
321
265
  ]));
322
- close(asReal(ctx.apply("hypot", [real(3), real(4)])), 5, 1e-7);
266
+ close(asReal(ctx.apply("hypot", [real(3), real(4)])), 5, 1e-7);
323
267
  });
324
268
  // ─────────────────────────────────────────────────────────────────────────────
325
269
  // 7 — Values: parse, the codec round-trip, and the polymorphic inverse.
326
270
  // ─────────────────────────────────────────────────────────────────────────────
327
271
  test("parseValue: ints, reals, and symbols", () => {
328
- assert.deepEqual(parseValue(enc("42")), int(42n));
329
- assert.deepEqual(parseValue(enc("-7")), int(-7n));
330
- assert.deepEqual(parseValue(enc("3.5")), real(3.5));
331
- assert.deepEqual(parseValue(enc("1e3")), real(1000));
332
- // A token that merely begins with a digit stays a symbol.
333
- assert.equal(parseValue(enc("3dogs")).domain, "symbol");
334
- assert.equal(parseValue(enc("large")).domain, "symbol");
272
+ assert.deepEqual(parseValue(enc("42")), int(42n));
273
+ assert.deepEqual(parseValue(enc("-7")), int(-7n));
274
+ assert.deepEqual(parseValue(enc("3.5")), real(3.5));
275
+ assert.deepEqual(parseValue(enc("1e3")), real(1000));
276
+ // A token that merely begins with a digit stays a symbol.
277
+ assert.equal(parseValue(enc("3dogs")).domain, "symbol");
278
+ assert.equal(parseValue(enc("large")).domain, "symbol");
335
279
  });
336
280
  test("decimalCodec round-trips and formats reals deterministically", () => {
337
- const codec = decimalCodec(6);
338
- assert.equal(dec(codec.encode(int(42n))), "42");
339
- assert.equal(dec(codec.encode(real(0.5))), "0.5");
340
- assert.equal(dec(codec.encode(real(2))), "2"); // trailing zeros trimmed
341
- assert.equal(dec(codec.encode(real(-0))), "0"); // -0 normalised
342
- // round-trip
343
- const v = codec.decode(enc("8"));
344
- assert.deepEqual(v, int(8n));
281
+ const codec = decimalCodec(6);
282
+ assert.equal(dec(codec.encode(int(42n))), "42");
283
+ assert.equal(dec(codec.encode(real(0.5))), "0.5");
284
+ assert.equal(dec(codec.encode(real(2))), "2"); // trailing zeros trimmed
285
+ assert.equal(dec(codec.encode(real(-0))), "0"); // -0 normalised
286
+ // round-trip
287
+ const v = codec.decode(enc("8"));
288
+ assert.deepEqual(v, int(8n));
345
289
  });
346
290
  test("inverse is polymorphic: number negates, symbol resonates to its opposite", () => {
347
- // Stub resonance: "large" ↔ "small", modality-agnostic over bytes.
348
- const opposites = new Map([["large", "small"]]);
349
- const stub = {
350
- opposite: (b) => {
351
- const o = opposites.get(dec(b));
352
- return o ? enc(o) : null;
353
- },
354
- recogniseOp: () => null,
355
- };
356
- const { ctx } = fullCtx(stub);
357
- // numeric inverse = negate
358
- assert.equal(N(ctx.apply("inverse", [int(3n)])), -3n);
359
- // symbol inverse = resonant opposite
360
- const r = ctx.apply("inverse", [symbol(enc("large"))]);
361
- assert.equal(r.domain, "symbol");
362
- assert.equal(dec(r.bytes), "small");
363
- // a symbol with no known opposite is left unchanged (never fabricated)
364
- const u = ctx.apply("inverse", [symbol(enc("zorp"))]);
365
- assert.equal(dec(u.bytes), "zorp");
291
+ // Stub resonance: "large" ↔ "small", modality-agnostic over bytes.
292
+ const opposites = new Map([["large", "small"]]);
293
+ const stub = {
294
+ opposite: (b) => {
295
+ const o = opposites.get(dec(b));
296
+ return o ? enc(o) : null;
297
+ },
298
+ recogniseOp: () => null,
299
+ };
300
+ const { ctx } = fullCtx(stub);
301
+ // numeric inverse = negate
302
+ assert.equal(N(ctx.apply("inverse", [int(3n)])), -3n);
303
+ // symbol inverse = resonant opposite
304
+ const r = ctx.apply("inverse", [symbol(enc("large"))]);
305
+ assert.equal(r.domain, "symbol");
306
+ assert.equal(dec(r.bytes), "small");
307
+ // a symbol with no known opposite is left unchanged (never fabricated)
308
+ const u = ctx.apply("inverse", [symbol(enc("zorp"))]);
309
+ assert.equal(dec(u.bytes), "zorp");
366
310
  });
367
311
  // ─────────────────────────────────────────────────────────────────────────────
368
312
  // 8 — The Alu facade: the synchronous scanner and applyBytes.
369
313
  // ─────────────────────────────────────────────────────────────────────────────
370
314
  test("scan finds numeric operands and symbolic operators in raw bytes", () => {
371
- const u = new Alu();
372
- const { operands, operators } = u.scan(enc("12+30"));
373
- assert.deepEqual(operands.map((o) => [o.i, o.j]), [[0, 2], [3, 5]]);
374
- assert.equal(operands[0].value.domain, "int");
375
- assert.equal(operators.length, 1);
376
- assert.equal(operators[0].name, "add");
377
- assert.deepEqual([operators[0].i, operators[0].j], [2, 3]);
315
+ const u = new Alu();
316
+ const { operands, operators } = u.scan(enc("12+30"));
317
+ assert.deepEqual(operands.map((o) => [o.i, o.j]), [[0, 2], [3, 5]]);
318
+ assert.equal(operands[0].value.domain, "int");
319
+ assert.equal(operators.length, 1);
320
+ assert.equal(operators[0].name, "add");
321
+ assert.deepEqual([operators[0].i, operators[0].j], [2, 3]);
378
322
  });
379
323
  test("scan keeps a multi-digit / decimal number whole, ignores a bare trailing dot", () => {
380
- const u = new Alu();
381
- const a = u.scan(enc("3.14"));
382
- assert.equal(a.operands.length, 1);
383
- assert.equal(a.operands[0].value.domain, "real");
384
- // a trailing "." is not part of the numeral
385
- const b = u.scan(enc("2+2."));
386
- assert.deepEqual(b.operands.map((o) => o.j - o.i), [1, 1]);
324
+ const u = new Alu();
325
+ const a = u.scan(enc("3.14"));
326
+ assert.equal(a.operands.length, 1);
327
+ assert.equal(a.operands[0].value.domain, "real");
328
+ // a trailing "." is not part of the numeral
329
+ const b = u.scan(enc("2+2."));
330
+ assert.deepEqual(b.operands.map((o) => o.j - o.i), [1, 1]);
387
331
  });
388
332
  test("scan resolves the longest symbolic operator form", () => {
389
- const u = new Alu();
390
- const { operators } = u.scan(enc("3<=4"));
391
- assert.equal(operators.length, 1);
392
- assert.equal(operators[0].name, "le"); // "<=" beats "<"
333
+ const u = new Alu();
334
+ const { operators } = u.scan(enc("3<=4"));
335
+ assert.equal(operators.length, 1);
336
+ assert.equal(operators[0].name, "le"); // "<=" beats "<"
393
337
  });
394
338
  test("applyBytes computes the canonical result bytes", () => {
395
- const u = new Alu();
396
- assert.equal(dec(u.applyBytes("add", [enc("2"), enc("2")])), "4");
397
- assert.equal(dec(u.applyBytes("multiply", [enc("6"), enc("7")])), "42");
398
- assert.equal(dec(u.applyBytes("subtract", [enc("10"), enc("3")])), "7");
399
- assert.equal(dec(u.applyBytes("divide", [enc("7"), enc("2")])), "3.5");
400
- // an unknown op or an unparseable operand → null (the rule simply won't fire)
401
- assert.equal(u.applyBytes("nope", [enc("1"), enc("2")]), null);
339
+ const u = new Alu();
340
+ assert.equal(dec(u.applyBytes("add", [enc("2"), enc("2")])), "4");
341
+ assert.equal(dec(u.applyBytes("multiply", [enc("6"), enc("7")])), "42");
342
+ assert.equal(dec(u.applyBytes("subtract", [enc("10"), enc("3")])), "7");
343
+ assert.equal(dec(u.applyBytes("divide", [enc("7"), enc("2")])), "3.5");
344
+ // an unknown op or an unparseable operand → null (the rule simply won't fire)
345
+ assert.equal(u.applyBytes("nope", [enc("1"), enc("2")]), null);
402
346
  });
403
347
  test("a computed result is itself an operand (composition for free)", () => {
404
- const u = new Alu();
405
- const first = u.applyBytes("add", [enc("2"), enc("3")]); // "5"
406
- const second = u.applyBytes("multiply", [first, enc("4")]); // "20"
407
- assert.equal(dec(second), "20");
348
+ const u = new Alu();
349
+ const first = u.applyBytes("add", [enc("2"), enc("3")]); // "5"
350
+ const second = u.applyBytes("multiply", [first, enc("4")]); // "20"
351
+ assert.equal(dec(second), "20");
408
352
  });
409
353
  // ─────────────────────────────────────────────────────────────────────────────
410
354
  // 9 — Expressions: a numerical op acts on a FUNCTION, evaluated by a recursive
411
355
  // application of the same ALU (the "recursive call" case).
412
356
  // ─────────────────────────────────────────────────────────────────────────────
413
357
  test("evalExpression evaluates through the kernel, auto-detecting the variable", () => {
414
- const u = new Alu();
415
- // x^2 + 1 at x=3 → 10; variable auto-detected.
416
- close(u.evalExpression(enc("x^2 + 1"), "", 3), 10);
417
- // a different one-letter variable, any script, still auto-detected
418
- close(u.evalExpression(enc("t*t"), "", 4), 16);
419
- // a named function resolves against the kernel
420
- close(u.evalExpression(enc("sin(x)"), "x", Math.PI / 2), 1, 1e-6);
421
- // constants
422
- close(u.evalExpression(enc("2*pi"), "", 0), 2 * Math.PI);
423
- // a malformed expression declines
424
- assert.equal(u.evalExpression(enc("x +"), "x", 1), null);
358
+ const u = new Alu();
359
+ // x^2 + 1 at x=3 → 10; variable auto-detected.
360
+ close(u.evalExpression(enc("x^2 + 1"), "", 3), 10);
361
+ // a different one-letter variable, any script, still auto-detected
362
+ close(u.evalExpression(enc("t*t"), "", 4), 16);
363
+ // a named function resolves against the kernel
364
+ close(u.evalExpression(enc("sin(x)"), "x", Math.PI / 2), 1, 1e-6);
365
+ // constants
366
+ close(u.evalExpression(enc("2*pi"), "", 0), 2 * Math.PI);
367
+ // a malformed expression declines
368
+ assert.equal(u.evalExpression(enc("x +"), "x", 1), null);
425
369
  });
426
370
  test("freeVariables lists candidates, excluding function names", () => {
427
- const u = new Alu();
428
- const isFn = (n) => u.arityOf(n) === 1 && u.has(n);
429
- assert.deepEqual(freeVariables("sin(x) + x", isFn), ["x"]);
430
- assert.deepEqual(freeVariables("a*b + c", isFn).sort(), ["a", "b", "c"]);
371
+ const u = new Alu();
372
+ const isFn = (n) => u.arityOf(n) === 1 && u.has(n);
373
+ assert.deepEqual(freeVariables("sin(x) + x", isFn), ["x"]);
374
+ assert.deepEqual(freeVariables("a*b + c", isFn).sort(), ["a", "b", "c"]);
431
375
  });
432
376
  test("diff: the derivative op acts on an expression operand", () => {
433
- const u = new Alu();
434
- // d/dx (x^2) at 3 = 6. Operand 0 is the expression symbol, operand 1 the point.
435
- const r = u.apply("diff", [symbol(enc("x^2")), real(3)]);
436
- assert.ok(r);
437
- close(asReal(r), 6, 1e-4);
377
+ const u = new Alu();
378
+ // d/dx (x^2) at 3 = 6. Operand 0 is the expression symbol, operand 1 the point.
379
+ const r = u.apply("diff", [symbol(enc("x^2")), real(3)]);
380
+ assert.ok(r);
381
+ close(asReal(r), 6, 1e-4);
438
382
  });
439
383
  test("integrate: definite integral over an expression", () => {
440
- const u = new Alu();
441
- // ∫₀¹ x dx = 0.5 ; ∫₀³ x^2 dx = 9
442
- close(
443
- asReal(u.apply("integrate", [symbol(enc("x")), real(0), real(1)])),
444
- 0.5,
445
- 1e-5,
446
- );
447
- close(
448
- asReal(u.apply("integrate", [symbol(enc("x^2")), real(0), real(3)])),
449
- 9,
450
- 1e-4,
451
- );
384
+ const u = new Alu();
385
+ // ∫₀¹ x dx = 0.5 ; ∫₀³ x^2 dx = 9
386
+ close(asReal(u.apply("integrate", [symbol(enc("x")), real(0), real(1)])), 0.5, 1e-5);
387
+ close(asReal(u.apply("integrate", [symbol(enc("x^2")), real(0), real(3)])), 9, 1e-4);
452
388
  });
453
389
  test("solve: a root of an expression near a guess", () => {
454
- const u = new Alu();
455
- // root of x^2 - 2 near 1 → √2
456
- close(
457
- asReal(u.apply("solve", [symbol(enc("x^2 - 2")), real(1)])),
458
- Math.SQRT2,
459
- 1e-6,
460
- );
390
+ const u = new Alu();
391
+ // root of x^2 - 2 near 1 → √2
392
+ close(asReal(u.apply("solve", [symbol(enc("x^2 - 2")), real(1)])), Math.SQRT2, 1e-6);
461
393
  });
462
394
  test("limit: the value an expression approaches (a converge instance)", () => {
463
- const u = new Alu();
464
- // lim_{x→0} sin(x)/x = 1 — the removable singularity is skirted by sampling.
465
- close(asReal(u.apply("limit", [symbol(enc("sin(x)/x")), real(0)])), 1, 1e-4);
395
+ const u = new Alu();
396
+ // lim_{x→0} sin(x)/x = 1 — the removable singularity is skirted by sampling.
397
+ close(asReal(u.apply("limit", [symbol(enc("sin(x)/x")), real(0)])), 1, 1e-4);
466
398
  });
467
399
  test("optimize: a minimiser of an expression", () => {
468
- const u = new Alu();
469
- // min of (x-3)^2 is at x=3
470
- close(
471
- asReal(u.apply("optimize", [symbol(enc("(x-3)^2")), real(0)])),
472
- 3,
473
- 1e-3,
474
- );
400
+ const u = new Alu();
401
+ // min of (x-3)^2 is at x=3
402
+ close(asReal(u.apply("optimize", [symbol(enc("(x-3)^2")), real(0)])), 3, 1e-3);
475
403
  });
476
404
  test("an expression op declines (null) when no evaluator can read the operand", () => {
477
- const u = new Alu();
478
- // a non-expression symbol → the evaluator fails → apply returns null
479
- assert.equal(u.apply("diff", [symbol(enc("@@@")), real(1)]), null);
405
+ const u = new Alu();
406
+ // a non-expression symbol → the evaluator fails → apply returns null
407
+ assert.equal(u.apply("diff", [symbol(enc("@@@")), real(1)]), null);
480
408
  });
481
409
  // ─────────────────────────────────────────────────────────────────────────────
482
410
  // 10 — The operation CONCEPT vocabulary (the generic, resonant recognition seed).
483
411
  // ─────────────────────────────────────────────────────────────────────────────
484
412
  test("conceptAnchors exposes the operation vocabulary for resonant recognition", () => {
485
- const u = new Alu();
486
- const dec = new TextDecoder();
487
- const byName = new Map();
488
- for (const { name, form } of u.conceptAnchors()) {
489
- const a = byName.get(name) ?? [];
490
- a.push(dec.decode(form));
491
- byName.set(name, a);
492
- }
493
- // The numerical ops a query would NOT spell with a symbol still have named
494
- // concepts a host can resonate against — generic over operations, not a fixed
495
- // symbol table.
496
- assert.ok(byName.get("integrate").includes("integral"));
497
- assert.ok(byName.get("diff").includes("derivative"));
498
- assert.ok(byName.get("limit").includes("limit"));
499
- assert.ok(byName.get("add").includes("plus"));
500
- // Forms the literal scanner already reads in full are NOT anchors: pure
501
- // operator symbols and numerals stay on the literal path.
502
- for (const forms of byName.values()) {
503
- assert.ok(!forms.includes("<="));
504
- assert.ok(!forms.includes("0"));
505
- }
413
+ const u = new Alu();
414
+ const dec = new TextDecoder();
415
+ const byName = new Map();
416
+ for (const { name, form } of u.conceptAnchors()) {
417
+ const a = byName.get(name) ?? [];
418
+ a.push(dec.decode(form));
419
+ byName.set(name, a);
420
+ }
421
+ // The numerical ops a query would NOT spell with a symbol still have named
422
+ // concepts a host can resonate against — generic over operations, not a fixed
423
+ // symbol table.
424
+ assert.ok(byName.get("integrate").includes("integral"));
425
+ assert.ok(byName.get("diff").includes("derivative"));
426
+ assert.ok(byName.get("limit").includes("limit"));
427
+ assert.ok(byName.get("add").includes("plus"));
428
+ // Forms the literal scanner already reads in full are NOT anchors: pure
429
+ // operator symbols and numerals stay on the literal path.
430
+ for (const forms of byName.values()) {
431
+ assert.ok(!forms.includes("<="));
432
+ assert.ok(!forms.includes("0"));
433
+ }
506
434
  });
507
435
  test("prefetchRecognisedOps bridges async recognition to a sync map", async () => {
508
- // A stub host resonance: "the rate of change of" means a derivative.
509
- const stub = {
510
- recogniseOp: async (b) => dec(b).includes("rate of change") ? "diff" : null,
511
- opposite: async () => null,
512
- };
513
- const map = await prefetchRecognisedOps(stub, [
514
- enc("the rate of change of"),
515
- enc("nonsense"),
516
- ]);
517
- assert.equal(map.get("the rate of change of"), "diff");
518
- assert.equal(map.has("nonsense"), false);
436
+ // A stub host resonance: "the rate of change of" means a derivative.
437
+ const stub = {
438
+ recogniseOp: async (b) => dec(b).includes("rate of change") ? "diff" : null,
439
+ opposite: async () => null,
440
+ };
441
+ const map = await prefetchRecognisedOps(stub, [
442
+ enc("the rate of change of"),
443
+ enc("nonsense"),
444
+ ]);
445
+ assert.equal(map.get("the rate of change of"), "diff");
446
+ assert.equal(map.has("nonsense"), false);
519
447
  });
520
448
  // ─────────────────────────────────────────────────────────────────────────────
521
449
  // 11 — N-dimensional values: the recursive container. Representation + codec.
522
450
  // ─────────────────────────────────────────────────────────────────────────────
523
451
  /** A full registry with the nd kernel too — the substrate for the nd tests. */
524
452
  function ndCtx(resonance = NO_RESONANCE) {
525
- const r = new OperationRegistry();
526
- registerLogic(r);
527
- registerBits(r);
528
- registerArith(r);
529
- registerNumeric(r);
530
- registerNd(r);
531
- return { r, ctx: r.context(resonance, { tol: 1e-12, maxIter: 2000 }) };
453
+ const r = new OperationRegistry();
454
+ registerLogic(r);
455
+ registerBits(r);
456
+ registerArith(r);
457
+ registerNumeric(r);
458
+ registerNd(r);
459
+ return { r, ctx: r.context(resonance, { tol: 1e-12, maxIter: 2000 }) };
532
460
  }
533
461
  test("nd is the recursive container: nested, ragged, heterogeneous", () => {
534
- // A list of any element values — including other lists (nesting) of unequal
535
- // length (ragged) and mixed domains (heterogeneous).
536
- const v = nd([
537
- int(1n),
538
- real(3.5),
539
- symbol(enc("large")),
540
- nd([int(2n), int(3n)]), // a nested sub-list, shorter than the outer
541
- ]);
542
- assert.equal(tagOf(v), "nd");
543
- assert.ok(isNd(v));
544
- assert.equal(v.items.length, 4);
545
- assert.equal(tagOf(v.items[3]), "nd");
546
- // a scalar is NOT an nd, even a numeric one
547
- assert.equal(isNd(int(1n)), false);
462
+ // A list of any element values — including other lists (nesting) of unequal
463
+ // length (ragged) and mixed domains (heterogeneous).
464
+ const v = nd([
465
+ int(1n),
466
+ real(3.5),
467
+ symbol(enc("large")),
468
+ nd([int(2n), int(3n)]), // a nested sub-list, shorter than the outer
469
+ ]);
470
+ assert.equal(tagOf(v), "nd");
471
+ assert.ok(isNd(v));
472
+ assert.equal(v.items.length, 4);
473
+ assert.equal(tagOf(v.items[3]), "nd");
474
+ // a scalar is NOT an nd, even a numeric one
475
+ assert.equal(isNd(int(1n)), false);
548
476
  });
549
477
  test("decimalCodec encodes an nd to its canonical bracket spelling (nested, mixed)", () => {
550
- // The bracket literal is the kernel's canonical OUTPUT spelling of a list — one
551
- // deterministic form (the analogue of decimal for a number), so two derivations
552
- // of the same list agree byte-for-byte. The kernel BUILDS the Value (the host
553
- // recognised its structure); the codec only spells it.
554
- const codec = decimalCodec(6);
555
- const out = (v) => dec(codec.encode(v));
556
- // flat
557
- assert.equal(out(V([1, 2, 3])), "[1,2,3]");
558
- // nested (a matrix) and ragged
559
- assert.equal(out(V([[1, 2], [3, 4, 5]])), "[[1,2],[3,4,5]]");
560
- // heterogeneous: int, real, symbol all spell their own form
561
- assert.equal(out(V([1, 3.5, "large"])), "[1,3.5,large]");
562
- // empty list
563
- assert.equal(out(V([])), "[]");
478
+ // The bracket literal is the kernel's canonical OUTPUT spelling of a list — one
479
+ // deterministic form (the analogue of decimal for a number), so two derivations
480
+ // of the same list agree byte-for-byte. The kernel BUILDS the Value (the host
481
+ // recognised its structure); the codec only spells it.
482
+ const codec = decimalCodec(6);
483
+ const out = (v) => dec(codec.encode(v));
484
+ // flat
485
+ assert.equal(out(V([1, 2, 3])), "[1,2,3]");
486
+ // nested (a matrix) and ragged
487
+ assert.equal(out(V([[1, 2], [3, 4, 5]])), "[[1,2],[3,4,5]]");
488
+ // heterogeneous: int, real, symbol all spell their own form
489
+ assert.equal(out(V([1, 3.5, "large"])), "[1,3.5,large]");
490
+ // empty list
491
+ assert.equal(out(V([])), "[]");
564
492
  });
565
493
  test("parseValue reads only SCALARS — a bracket literal decodes to an opaque symbol", () => {
566
- // The kernel no longer parses list STRUCTURE from bytes (that is the host's
567
- // meaning-level job — src/mind/mind.ts recogniseValue). So decode is scalar-only: a
568
- // bracket literal is not a list to the kernel, it is an opaque symbol, exactly
569
- // like any other form that merely contains a bracket.
570
- assert.equal(parseValue(enc("[1,2,3]")).domain, "symbol");
571
- assert.equal(parseValue(enc("[1,2")).domain, "symbol");
572
- assert.equal(parseValue(enc("[1,,2]")).domain, "symbol");
573
- assert.equal(parseValue(enc("not a list")).domain, "symbol");
574
- // the scalar floor still grounds a numeral
575
- assert.deepEqual(parseValue(enc("42")), int(42n));
494
+ // The kernel no longer parses list STRUCTURE from bytes (that is the host's
495
+ // meaning-level job — src/mind/mind.ts recogniseValue). So decode is scalar-only: a
496
+ // bracket literal is not a list to the kernel, it is an opaque symbol, exactly
497
+ // like any other form that merely contains a bracket.
498
+ assert.equal(parseValue(enc("[1,2,3]")).domain, "symbol");
499
+ assert.equal(parseValue(enc("[1,2")).domain, "symbol");
500
+ assert.equal(parseValue(enc("[1,,2]")).domain, "symbol");
501
+ assert.equal(parseValue(enc("not a list")).domain, "symbol");
502
+ // the scalar floor still grounds a numeral
503
+ assert.deepEqual(parseValue(enc("42")), int(42n));
576
504
  });
577
505
  // ─────────────────────────────────────────────────────────────────────────────
578
506
  // 12 — Broadcast: EVERY scalar op lifts over nd automatically (one mechanism).
579
507
  // ─────────────────────────────────────────────────────────────────────────────
580
508
  test("arithmetic broadcasts: list∘list zips, list∘scalar holds the scalar", () => {
581
- const { ctx } = ndCtx();
582
- const out = (r) => dec(decimalCodec(6).encode(r));
583
- // element-wise over two lists
584
- assert.equal(out(ctx.apply("add", [V([1, 2, 3]), V([4, 5, 6])])), "[5,7,9]");
585
- assert.equal(
586
- out(ctx.apply("multiply", [V([1, 2, 3]), V([10, 10, 10])])),
587
- "[10,20,30]",
588
- );
589
- // a scalar operand is held constant against the list
590
- assert.equal(out(ctx.apply("add", [V([1, 2, 3]), int(10n)])), "[11,12,13]");
591
- assert.equal(out(ctx.apply("subtract", [int(10n), V([1, 2, 3])])), "[9,8,7]");
509
+ const { ctx } = ndCtx();
510
+ const out = (r) => dec(decimalCodec(6).encode(r));
511
+ // element-wise over two lists
512
+ assert.equal(out(ctx.apply("add", [V([1, 2, 3]), V([4, 5, 6])])), "[5,7,9]");
513
+ assert.equal(out(ctx.apply("multiply", [V([1, 2, 3]), V([10, 10, 10])])), "[10,20,30]");
514
+ // a scalar operand is held constant against the list
515
+ assert.equal(out(ctx.apply("add", [V([1, 2, 3]), int(10n)])), "[11,12,13]");
516
+ assert.equal(out(ctx.apply("subtract", [int(10n), V([1, 2, 3])])), "[9,8,7]");
592
517
  });
593
518
  test("broadcast RECURSES through nesting (a matrix op is the same code)", () => {
594
- const { ctx } = ndCtx();
595
- const out = (r) => dec(decimalCodec(6).encode(r));
596
- // nd-of-nd lifts twice with no extra machinery
597
- assert.equal(
598
- out(ctx.apply("add", [V([[1, 2], [3, 4]]), V([[10, 20], [30, 40]])])),
599
- "[[11,22],[33,44]]",
600
- );
601
- // scalar against a matrix reaches every leaf
602
- assert.equal(
603
- out(ctx.apply("multiply", [V([[1, 2], [3, 4]]), int(2n)])),
604
- "[[2,4],[6,8]]",
605
- );
519
+ const { ctx } = ndCtx();
520
+ const out = (r) => dec(decimalCodec(6).encode(r));
521
+ // nd-of-nd lifts twice with no extra machinery
522
+ assert.equal(out(ctx.apply("add", [V([[1, 2], [3, 4]]), V([[10, 20], [30, 40]])])), "[[11,22],[33,44]]");
523
+ // scalar against a matrix reaches every leaf
524
+ assert.equal(out(ctx.apply("multiply", [V([[1, 2], [3, 4]]), int(2n)])), "[[2,4],[6,8]]");
606
525
  });
607
526
  test("broadcast spans op classes: logic, comparison, transcendental, inverse", () => {
608
- const opposites = new Map([["hot", "cold"], ["up", "down"]]);
609
- const stub = {
610
- opposite: (b) => opposites.has(dec(b)) ? enc(opposites.get(dec(b))) : null,
611
- recogniseOp: () => null,
612
- };
613
- const { ctx } = ndCtx(stub);
614
- const out = (r) => dec(decimalCodec(6).encode(r));
615
- // logic gate over a bit-list
616
- assert.equal(out(ctx.apply("not", [V([1, 0, 1])])), "[0,1,0]");
617
- // comparison → a list of truth bits
618
- assert.equal(out(ctx.apply("gt", [V([1, 5, 3]), int(2n)])), "[0,1,1]");
619
- // a transcendental over a list
620
- assert.equal(out(ctx.apply("sqrt", [V([1, 4, 9])])), "[1,2,3]");
621
- // the POLYMORPHIC INVERSE broadcasts: numbers negate, symbols resonate, in one
622
- // heterogeneous list — each element dispatched on its own domain.
623
- assert.equal(
624
- out(ctx.apply("inverse", [V(["hot", 3, "up"])])),
625
- "[cold,-3,down]",
626
- );
527
+ const opposites = new Map([["hot", "cold"], ["up", "down"]]);
528
+ const stub = {
529
+ opposite: (b) => opposites.has(dec(b)) ? enc(opposites.get(dec(b))) : null,
530
+ recogniseOp: () => null,
531
+ };
532
+ const { ctx } = ndCtx(stub);
533
+ const out = (r) => dec(decimalCodec(6).encode(r));
534
+ // logic gate over a bit-list
535
+ assert.equal(out(ctx.apply("not", [V([1, 0, 1])])), "[0,1,0]");
536
+ // comparison → a list of truth bits
537
+ assert.equal(out(ctx.apply("gt", [V([1, 5, 3]), int(2n)])), "[0,1,1]");
538
+ // a transcendental over a list
539
+ assert.equal(out(ctx.apply("sqrt", [V([1, 4, 9])])), "[1,2,3]");
540
+ // the POLYMORPHIC INVERSE broadcasts: numbers negate, symbols resonate, in one
541
+ // heterogeneous list — each element dispatched on its own domain.
542
+ assert.equal(out(ctx.apply("inverse", [V(["hot", 3, "up"])])), "[cold,-3,down]");
627
543
  });
628
544
  test("broadcast over lists of unequal length declines (no silent truncation)", () => {
629
- const { ctx } = ndCtx();
630
- assert.throws(() => ctx.apply("add", [V([1, 2, 3]), V([4, 5])]));
545
+ const { ctx } = ndCtx();
546
+ assert.throws(() => ctx.apply("add", [V([1, 2, 3]), V([4, 5])]));
631
547
  });
632
548
  // ─────────────────────────────────────────────────────────────────────────────
633
549
  // 13 — The nd core: nd / length / at are the only ops that touch `items`.
634
550
  // ─────────────────────────────────────────────────────────────────────────────
635
551
  test("core: nd packs, length counts, at projects (negative = from end)", () => {
636
- const { ctx } = ndCtx();
637
- const out = (r) => dec(decimalCodec(6).encode(r));
638
- // pack
639
- assert.equal(out(ctx.apply("nd", [int(1n), int(2n), int(3n)])), "[1,2,3]");
640
- assert.equal(out(ctx.apply("nd", [])), "[]");
641
- // length
642
- const xs = V([5, 6, 7, 8]);
643
- assert.equal(N(ctx.apply("length", [xs])), 4n);
644
- // at, forward and from the end
645
- assert.equal(N(ctx.apply("at", [xs, int(0n)])), 5n);
646
- assert.equal(N(ctx.apply("at", [xs, int(-1n)])), 8n);
647
- // out of range declines (throws → the facade maps it to "rule does not fire")
648
- assert.throws(() => ctx.apply("at", [xs, int(9n)]));
552
+ const { ctx } = ndCtx();
553
+ const out = (r) => dec(decimalCodec(6).encode(r));
554
+ // pack
555
+ assert.equal(out(ctx.apply("nd", [int(1n), int(2n), int(3n)])), "[1,2,3]");
556
+ assert.equal(out(ctx.apply("nd", [])), "[]");
557
+ // length
558
+ const xs = V([5, 6, 7, 8]);
559
+ assert.equal(N(ctx.apply("length", [xs])), 4n);
560
+ // at, forward and from the end
561
+ assert.equal(N(ctx.apply("at", [xs, int(0n)])), 5n);
562
+ assert.equal(N(ctx.apply("at", [xs, int(-1n)])), 8n);
563
+ // out of range declines (throws → the facade maps it to "rule does not fire")
564
+ assert.throws(() => ctx.apply("at", [xs, int(9n)]));
649
565
  });
650
566
  // ─────────────────────────────────────────────────────────────────────────────
651
567
  // 14 — Higher-order ops: the FUNCTION ARGUMENT is ANY existing operation.
652
568
  // ─────────────────────────────────────────────────────────────────────────────
653
569
  test("reduce folds by ANY binary op — +, *, max are sum, product, maximum", () => {
654
- const { ctx } = ndCtx();
655
- const xs = V([3, 1, 4, 1, 5, 9, 2, 6]);
656
- // The op argument is a Value naming an operation; resolveOp turns it into the
657
- // canonical op, so reduce reuses the whole scalar vocabulary as folds.
658
- assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("+"))])), 31n); // sum
659
- assert.equal(
660
- N(ctx.apply("reduce", [V([1, 2, 3, 4]), symbol(enc("*"))])),
661
- 24n,
662
- );
663
- assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("max"))])), 9n); // maximum
664
- assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("min"))])), 1n); // minimum
665
- // a seed makes the fold total over an empty list
666
- assert.equal(N(ctx.apply("reduce", [V([]), symbol(enc("+")), int(0n)])), 0n);
570
+ const { ctx } = ndCtx();
571
+ const xs = V([3, 1, 4, 1, 5, 9, 2, 6]);
572
+ // The op argument is a Value naming an operation; resolveOp turns it into the
573
+ // canonical op, so reduce reuses the whole scalar vocabulary as folds.
574
+ assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("+"))])), 31n); // sum
575
+ assert.equal(N(ctx.apply("reduce", [V([1, 2, 3, 4]), symbol(enc("*"))])), 24n);
576
+ assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("max"))])), 9n); // maximum
577
+ assert.equal(N(ctx.apply("reduce", [xs, symbol(enc("min"))])), 1n); // minimum
578
+ // a seed makes the fold total over an empty list
579
+ assert.equal(N(ctx.apply("reduce", [V([]), symbol(enc("+")), int(0n)])), 0n);
667
580
  });
668
581
  test("reduce by a binary op BROADCASTS — a column sum falls out of nesting", () => {
669
- const { ctx } = ndCtx();
670
- const out = (r) => dec(decimalCodec(6).encode(r));
671
- // reduce(rows, +) folds the row-LISTS with "+", and "+" itself broadcasts over
672
- // them — so summing a list of rows yields the column sums, no matrix code.
673
- assert.equal(
674
- out(ctx.apply("reduce", [
675
- V([[1, 2, 3], [4, 5, 6]]),
676
- symbol(enc("+")),
677
- ])),
678
- "[5,7,9]",
679
- );
582
+ const { ctx } = ndCtx();
583
+ const out = (r) => dec(decimalCodec(6).encode(r));
584
+ // reduce(rows, +) folds the row-LISTS with "+", and "+" itself broadcasts over
585
+ // them — so summing a list of rows yields the column sums, no matrix code.
586
+ assert.equal(out(ctx.apply("reduce", [
587
+ V([[1, 2, 3], [4, 5, 6]]),
588
+ symbol(enc("+")),
589
+ ])), "[5,7,9]");
680
590
  });
681
591
  test("map applies a unary op to each element; works over nesting", () => {
682
- const { ctx } = ndCtx();
683
- const out = (r) => dec(decimalCodec(6).encode(r));
684
- assert.equal(
685
- out(ctx.apply("map", [V([1, 2, 3]), symbol(enc("negate"))])),
686
- "[-1,-2,-3]",
687
- );
688
- assert.equal(
689
- out(ctx.apply("map", [V([1, 4, 9]), symbol(enc("sqrt"))])),
690
- "[1,2,3]",
691
- );
592
+ const { ctx } = ndCtx();
593
+ const out = (r) => dec(decimalCodec(6).encode(r));
594
+ assert.equal(out(ctx.apply("map", [V([1, 2, 3]), symbol(enc("negate"))])), "[-1,-2,-3]");
595
+ assert.equal(out(ctx.apply("map", [V([1, 4, 9]), symbol(enc("sqrt"))])), "[1,2,3]");
692
596
  });
693
597
  test("filter keeps elements a unary predicate accepts; find returns the first", () => {
694
- const { ctx } = ndCtx();
695
- const out = (r) => dec(decimalCodec(6).encode(r));
696
- const xs = V([0, 5, 0, 3, 0, 8]);
697
- // "sign" is 0 for zero, 1 for positive → a truthiness predicate
698
- assert.equal(out(ctx.apply("filter", [xs, symbol(enc("sign"))])), "[5,3,8]");
699
- assert.equal(N(ctx.apply("find", [xs, symbol(enc("sign"))])), 5n);
700
- // find with no match returns the empty nd (graph-evidenced "nothing"), not a guess
701
- const none = ctx.apply("find", [
702
- V([0, 0, 0]),
703
- symbol(enc("sign")),
704
- ]);
705
- assert.ok(isNd(none) && none.items.length === 0);
598
+ const { ctx } = ndCtx();
599
+ const out = (r) => dec(decimalCodec(6).encode(r));
600
+ const xs = V([0, 5, 0, 3, 0, 8]);
601
+ // "sign" is 0 for zero, 1 for positive → a truthiness predicate
602
+ assert.equal(out(ctx.apply("filter", [xs, symbol(enc("sign"))])), "[5,3,8]");
603
+ assert.equal(N(ctx.apply("find", [xs, symbol(enc("sign"))])), 5n);
604
+ // find with no match returns the empty nd (graph-evidenced "nothing"), not a guess
605
+ const none = ctx.apply("find", [
606
+ V([0, 0, 0]),
607
+ symbol(enc("sign")),
608
+ ]);
609
+ assert.ok(isNd(none) && none.items.length === 0);
706
610
  });
707
611
  test("structural plumbing: concat, reverse, flatten, zip, range, rank, shape", () => {
708
- const { ctx } = ndCtx();
709
- const out = (r) => dec(decimalCodec(6).encode(r));
710
- assert.equal(out(ctx.apply("concat", [V([1, 2]), V([3, 4])])), "[1,2,3,4]");
711
- assert.equal(out(ctx.apply("reverse", [V([1, 2, 3])])), "[3,2,1]");
712
- assert.equal(out(ctx.apply("flatten", [V([[1, 2], [3, 4]])])), "[1,2,3,4]");
713
- assert.equal(
714
- out(ctx.apply("zip", [
715
- V([1, 2, 3]),
716
- V([4, 5, 6]),
717
- ])),
718
- "[[1,4],[2,5],[3,6]]",
719
- );
720
- assert.equal(out(ctx.apply("range", [int(4n)])), "[0,1,2,3]");
721
- assert.equal(out(ctx.apply("range", [int(2n), int(5n)])), "[2,3,4]");
722
- // rank = nesting depth; shape = size down the first axis
723
- assert.equal(N(ctx.apply("rank", [V([[1, 2], [3, 4]])])), 2n);
724
- assert.equal(N(ctx.apply("rank", [int(7n)])), 0n);
725
- assert.equal(out(ctx.apply("shape", [V([[1, 2, 3], [4, 5, 6]])])), "[2,3]");
612
+ const { ctx } = ndCtx();
613
+ const out = (r) => dec(decimalCodec(6).encode(r));
614
+ assert.equal(out(ctx.apply("concat", [V([1, 2]), V([3, 4])])), "[1,2,3,4]");
615
+ assert.equal(out(ctx.apply("reverse", [V([1, 2, 3])])), "[3,2,1]");
616
+ assert.equal(out(ctx.apply("flatten", [V([[1, 2], [3, 4]])])), "[1,2,3,4]");
617
+ assert.equal(out(ctx.apply("zip", [
618
+ V([1, 2, 3]),
619
+ V([4, 5, 6]),
620
+ ])), "[[1,4],[2,5],[3,6]]");
621
+ assert.equal(out(ctx.apply("range", [int(4n)])), "[0,1,2,3]");
622
+ assert.equal(out(ctx.apply("range", [int(2n), int(5n)])), "[2,3,4]");
623
+ // rank = nesting depth; shape = size down the first axis
624
+ assert.equal(N(ctx.apply("rank", [V([[1, 2], [3, 4]])])), 2n);
625
+ assert.equal(N(ctx.apply("rank", [int(7n)])), 0n);
626
+ assert.equal(out(ctx.apply("shape", [V([[1, 2, 3], [4, 5, 6]])])), "[2,3]");
726
627
  });
727
628
  // ─────────────────────────────────────────────────────────────────────────────
728
629
  // 15 — resolveOp: the function argument is resolved by the SAME machinery as any
729
630
  // operator — a surface form, then (when not literal) its resonant meaning.
730
631
  // ─────────────────────────────────────────────────────────────────────────────
731
632
  test("resolveOp: a literal surface form names its canonical op", () => {
732
- const { ctx } = ndCtx();
733
- // canonical name, a synonym spelling, and a glyph all name the same op
734
- assert.equal(ctx.resolveOp(symbol(enc("add"))), "add");
735
- assert.equal(ctx.resolveOp(symbol(enc("plus"))), "add");
736
- assert.equal(ctx.resolveOp(symbol(enc("+"))), "add");
737
- assert.equal(ctx.resolveOp(symbol(enc("max"))), "max");
738
- // nothing that names an op → null (the higher-order op then declines)
739
- assert.equal(ctx.resolveOp(symbol(enc("zzz"))), null);
633
+ const { ctx } = ndCtx();
634
+ // canonical name, a synonym spelling, and a glyph all name the same op
635
+ assert.equal(ctx.resolveOp(symbol(enc("add"))), "add");
636
+ assert.equal(ctx.resolveOp(symbol(enc("plus"))), "add");
637
+ assert.equal(ctx.resolveOp(symbol(enc("+"))), "add");
638
+ assert.equal(ctx.resolveOp(symbol(enc("max"))), "max");
639
+ // nothing that names an op → null (the higher-order op then declines)
640
+ assert.equal(ctx.resolveOp(symbol(enc("zzz"))), null);
740
641
  });
741
642
  test("resolveOp disambiguates a shared surface form by arity", () => {
742
- // Build a registry where one surface "@" is claimed by both a unary and a
743
- // binary op, to exercise the arity tie-break directly (the kernel happens to
744
- // share no surface across arities today, so this proves the mechanism).
745
- const r = new OperationRegistry();
746
- r.prim("u_at", 1, ["@"], (a) => a[0]);
747
- r.prim("b_at", 2, ["@"], (a) => a[0]);
748
- const ctx = r.context(NO_RESONANCE, { tol: 1e-10, maxIter: 100 });
749
- assert.equal(ctx.resolveOp(symbol(enc("@")), 1), "u_at");
750
- assert.equal(ctx.resolveOp(symbol(enc("@")), 2), "b_at");
751
- // no arity hint → the first claimant (registration order)
752
- assert.equal(ctx.resolveOp(symbol(enc("@"))), "u_at");
643
+ // Build a registry where one surface "@" is claimed by both a unary and a
644
+ // binary op, to exercise the arity tie-break directly (the kernel happens to
645
+ // share no surface across arities today, so this proves the mechanism).
646
+ const r = new OperationRegistry();
647
+ r.prim("u_at", 1, ["@"], (a) => a[0]);
648
+ r.prim("b_at", 2, ["@"], (a) => a[0]);
649
+ const ctx = r.context(NO_RESONANCE, { tol: 1e-10, maxIter: 100 });
650
+ assert.equal(ctx.resolveOp(symbol(enc("@")), 1), "u_at");
651
+ assert.equal(ctx.resolveOp(symbol(enc("@")), 2), "b_at");
652
+ // no arity hint → the first claimant (registration order)
653
+ assert.equal(ctx.resolveOp(symbol(enc("@"))), "u_at");
753
654
  });
754
655
  test("resolveOp falls through to RESONANCE when the bytes are not a literal form", () => {
755
- // The host's resonance maps a meaning ("grand total of") to an op; resolveOp
756
- // uses it exactly as the scan would for an operator a query does not spell.
757
- const stub = {
758
- opposite: () => null,
759
- recogniseOp: (b) => dec(b).includes("grand total") ? "add" : null,
760
- };
761
- const { ctx } = ndCtx(stub);
762
- assert.equal(ctx.resolveOp(symbol(enc("the grand total of"))), "add");
763
- // and a reduce driven by that meaning folds correctly
764
- assert.equal(
765
- N(ctx.apply("reduce", [
766
- V([10, 20, 30]),
767
- symbol(enc("the grand total of")),
768
- ])),
769
- 60n,
770
- );
656
+ // The host's resonance maps a meaning ("grand total of") to an op; resolveOp
657
+ // uses it exactly as the scan would for an operator a query does not spell.
658
+ const stub = {
659
+ opposite: () => null,
660
+ recogniseOp: (b) => dec(b).includes("grand total") ? "add" : null,
661
+ };
662
+ const { ctx } = ndCtx(stub);
663
+ assert.equal(ctx.resolveOp(symbol(enc("the grand total of"))), "add");
664
+ // and a reduce driven by that meaning folds correctly
665
+ assert.equal(N(ctx.apply("reduce", [
666
+ V([10, 20, 30]),
667
+ symbol(enc("the grand total of")),
668
+ ])), 60n);
771
669
  });
772
670
  test("a higher-order op declines (throws) when its operation argument is unrecognised", () => {
773
- const { ctx } = ndCtx();
774
- // no surface form, no resonance → resolveFn throws → the Alu facade would map
775
- // this to "rule does not fire".
776
- assert.throws(() =>
777
- ctx.apply("reduce", [
778
- V([1, 2, 3]),
779
- symbol(enc("flibbertigibbet")),
780
- ])
781
- );
671
+ const { ctx } = ndCtx();
672
+ // no surface form, no resonance → resolveFn throws → the Alu facade would map
673
+ // this to "rule does not fire".
674
+ assert.throws(() => ctx.apply("reduce", [
675
+ V([1, 2, 3]),
676
+ symbol(enc("flibbertigibbet")),
677
+ ]));
782
678
  });
783
679
  // ─────────────────────────────────────────────────────────────────────────────
784
680
  // 16 — The Alu facade over nd: applyValues (Values in, canonical bytes out).
@@ -787,38 +683,29 @@ test("a higher-order op declines (throws) when its operation argument is unrecog
787
683
  // facade as an already-assembled Value, never as a bracket byte string.
788
684
  // ─────────────────────────────────────────────────────────────────────────────
789
685
  test("applyValues computes nd results: broadcast, reduce, and a composed pipeline", () => {
790
- const u = new Alu();
791
- // broadcast — two list Values in, the canonical bracket spelling out
792
- assert.equal(
793
- dec(u.applyValues("add", [V([1, 2, 3]), V([4, 5, 6])])),
794
- "[5,7,9]",
795
- );
796
- // reduce with an operator surface form (the op argument is a symbol Value)
797
- assert.equal(
798
- dec(u.applyValues("reduce", [V([1, 2, 3, 4]), symbol(enc("+"))])),
799
- "10",
800
- );
801
- // a computed nd is itself a valid operand (composition for free): map then reduce
802
- const mapped = u.applyValues("map", [
803
- V([1, 2, 3, 4]),
804
- symbol(enc("negate")),
805
- ]);
806
- assert.equal(dec(mapped), "[-1,-2,-3,-4]");
807
- // an unknown op or a declining computation → null (the rule does not fire)
808
- assert.equal(
809
- u.applyValues("reduce", [V([1, 2]), symbol(enc("nonexistent-op"))]),
810
- null,
811
- );
686
+ const u = new Alu();
687
+ // broadcast — two list Values in, the canonical bracket spelling out
688
+ assert.equal(dec(u.applyValues("add", [V([1, 2, 3]), V([4, 5, 6])])), "[5,7,9]");
689
+ // reduce with an operator surface form (the op argument is a symbol Value)
690
+ assert.equal(dec(u.applyValues("reduce", [V([1, 2, 3, 4]), symbol(enc("+"))])), "10");
691
+ // a computed nd is itself a valid operand (composition for free): map then reduce
692
+ const mapped = u.applyValues("map", [
693
+ V([1, 2, 3, 4]),
694
+ symbol(enc("negate")),
695
+ ]);
696
+ assert.equal(dec(mapped), "[-1,-2,-3,-4]");
697
+ // an unknown op or a declining computation → null (the rule does not fire)
698
+ assert.equal(u.applyValues("reduce", [V([1, 2]), symbol(enc("nonexistent-op"))]), null);
812
699
  });
813
700
  test("applyBytes is SCALAR-only: it does not read list structure from bytes", () => {
814
- const u = new Alu();
815
- // applyBytes decodes operands through the codec, which is scalar-only — a
816
- // bracket literal becomes an opaque symbol, so arithmetic over it declines.
817
- // Reading list STRUCTURE from bytes is recogniseValue's job (§17), not the
818
- // codec's; applyBytes stays the pure scalar-operand path the search uses.
819
- assert.equal(u.applyBytes("add", [enc("[1,2,3]"), enc("[4,5,6]")]), null);
820
- // scalar arithmetic through the byte facade is unaffected
821
- assert.equal(dec(u.applyBytes("add", [enc("2"), enc("2")])), "4");
701
+ const u = new Alu();
702
+ // applyBytes decodes operands through the codec, which is scalar-only — a
703
+ // bracket literal becomes an opaque symbol, so arithmetic over it declines.
704
+ // Reading list STRUCTURE from bytes is recogniseValue's job (§17), not the
705
+ // codec's; applyBytes stays the pure scalar-operand path the search uses.
706
+ assert.equal(u.applyBytes("add", [enc("[1,2,3]"), enc("[4,5,6]")]), null);
707
+ // scalar arithmetic through the byte facade is unaffected
708
+ assert.equal(dec(u.applyBytes("add", [enc("2"), enc("2")])), "4");
822
709
  });
823
710
  // ─────────────────────────────────────────────────────────────────────────────
824
711
  // 17 — recogniseValue: the bytes→Value boundary. A span is a scalar, or a LIST
@@ -827,51 +714,51 @@ test("applyBytes is SCALAR-only: it does not read list structure from bytes", ()
827
714
  // layered over scan, so the kernel itself only ever computes on Values.
828
715
  // ─────────────────────────────────────────────────────────────────────────────
829
716
  test("recogniseValue reads a SCALAR — a numeral, or an opaque symbol", () => {
830
- const u = new Alu();
831
- assert.deepEqual(u.recogniseValue(enc("42")), int(42n));
832
- assert.deepEqual(u.recogniseValue(enc("3.5")), real(3.5));
833
- // a single word / learnt form / operator name stays an opaque symbol
834
- assert.equal(u.recogniseValue(enc("large")).domain, "symbol");
835
- assert.equal(u.recogniseValue(enc("+")).domain, "symbol");
836
- // surrounding whitespace is trimmed before the reading
837
- assert.deepEqual(u.recogniseValue(enc(" 7 ")), int(7n));
717
+ const u = new Alu();
718
+ assert.deepEqual(u.recogniseValue(enc("42")), int(42n));
719
+ assert.deepEqual(u.recogniseValue(enc("3.5")), real(3.5));
720
+ // a single word / learnt form / operator name stays an opaque symbol
721
+ assert.equal(u.recogniseValue(enc("large")).domain, "symbol");
722
+ assert.equal(u.recogniseValue(enc("+")).domain, "symbol");
723
+ // surrounding whitespace is trimmed before the reading
724
+ assert.deepEqual(u.recogniseValue(enc(" 7 ")), int(7n));
838
725
  });
839
726
  test("recogniseValue reads a CONTAINER list, separator spelling not privileged", () => {
840
- const u = new Alu();
841
- const enc6 = decimalCodec(6);
842
- const to = (s) => dec(enc6.encode(u.recogniseValue(enc(s))));
843
- // comma, comma+space, or bare space inside the brackets — all the same list
844
- for (const s of ["[1,2,3]", "[1, 2, 3]", "[1 2 3]"]) {
845
- assert.equal(to(s), "[1,2,3]", s);
846
- }
847
- // nested (a matrix), ragged, and heterogeneous all recurse element-wise
848
- assert.equal(to("[[1,2],[3,4,5]]"), "[[1,2],[3,4,5]]");
849
- assert.equal(to("[1,3.5,large]"), "[1,3.5,large]");
850
- assert.equal(to("[]"), "[]"); // empty container → the empty list
851
- // a parsed container really is an nd with the right element domains
852
- const v = u.recogniseValue(enc("[1,3.5,large]"));
853
- assert.ok(isNd(v));
854
- assert.deepEqual(v.items.map(tagOf), ["int", "real", "symbol"]);
727
+ const u = new Alu();
728
+ const enc6 = decimalCodec(6);
729
+ const to = (s) => dec(enc6.encode(u.recogniseValue(enc(s))));
730
+ // comma, comma+space, or bare space inside the brackets — all the same list
731
+ for (const s of ["[1,2,3]", "[1, 2, 3]", "[1 2 3]"]) {
732
+ assert.equal(to(s), "[1,2,3]", s);
733
+ }
734
+ // nested (a matrix), ragged, and heterogeneous all recurse element-wise
735
+ assert.equal(to("[[1,2],[3,4,5]]"), "[[1,2],[3,4,5]]");
736
+ assert.equal(to("[1,3.5,large]"), "[1,3.5,large]");
737
+ assert.equal(to("[]"), "[]"); // empty container → the empty list
738
+ // a parsed container really is an nd with the right element domains
739
+ const v = u.recogniseValue(enc("[1,3.5,large]"));
740
+ assert.ok(isNd(v));
741
+ assert.deepEqual(v.items.map(tagOf), ["int", "real", "symbol"]);
855
742
  });
856
743
  test("recogniseValue reads a bare SEQUENCE by ANY consistent connective", () => {
857
- const u = new Alu();
858
- const enc6 = decimalCodec(6);
859
- const to = (s) => dec(enc6.encode(u.recogniseValue(enc(s))));
860
- // space, comma, comma+space, or the word " and " between ≥2 numeric operands
861
- for (const s of ["1 2 3", "1,2,3", "1, 2, 3", "1 and 2 and 3"]) {
862
- assert.equal(to(s), "[1,2,3]", s);
863
- }
744
+ const u = new Alu();
745
+ const enc6 = decimalCodec(6);
746
+ const to = (s) => dec(enc6.encode(u.recogniseValue(enc(s))));
747
+ // space, comma, comma+space, or the word " and " between ≥2 numeric operands
748
+ for (const s of ["1 2 3", "1,2,3", "1, 2, 3", "1 and 2 and 3"]) {
749
+ assert.equal(to(s), "[1,2,3]", s);
750
+ }
864
751
  });
865
752
  test("recogniseValue does NOT over-read: inconsistent or wordy spans stay scalar", () => {
866
- const u = new Alu();
867
- // a single numeral is a scalar, never a one-element list
868
- assert.equal(u.recogniseValue(enc("5")).domain, "int");
869
- // an INCONSISTENT separator ("space" then "comma") is not one sequence → symbol
870
- assert.equal(u.recogniseValue(enc("1 2,3")).domain, "symbol");
871
- // leftover material at an edge (a trailing word) is not a clean sequence
872
- assert.equal(u.recogniseValue(enc("1 2 buckle")).domain, "symbol");
873
- // a bare run of words (no numeric operands) stays an opaque symbol
874
- assert.equal(u.recogniseValue(enc("large tall")).domain, "symbol");
875
- // a stray unbalanced bracket is not a container → opaque symbol
876
- assert.equal(u.recogniseValue(enc("[1,2")).domain, "symbol");
753
+ const u = new Alu();
754
+ // a single numeral is a scalar, never a one-element list
755
+ assert.equal(u.recogniseValue(enc("5")).domain, "int");
756
+ // an INCONSISTENT separator ("space" then "comma") is not one sequence → symbol
757
+ assert.equal(u.recogniseValue(enc("1 2,3")).domain, "symbol");
758
+ // leftover material at an edge (a trailing word) is not a clean sequence
759
+ assert.equal(u.recogniseValue(enc("1 2 buckle")).domain, "symbol");
760
+ // a bare run of words (no numeric operands) stays an opaque symbol
761
+ assert.equal(u.recogniseValue(enc("large tall")).domain, "symbol");
762
+ // a stray unbalanced bracket is not a container → opaque symbol
763
+ assert.equal(u.recogniseValue(enc("[1,2")).domain, "symbol");
877
764
  });