@hviana/sema 0.1.3 → 0.1.5
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.
- package/dist/example/demo.js +25 -25
- package/dist/example/train_base.d.ts +21 -19
- package/dist/example/train_base.js +1952 -1684
- package/dist/src/alphabet.d.ts +3 -3
- package/dist/src/alphabet.js +27 -24
- package/dist/src/alu/src/alu.d.ts +194 -169
- package/dist/src/alu/src/alu.js +414 -374
- package/dist/src/alu/src/expr.d.ts +43 -36
- package/dist/src/alu/src/expr.js +278 -252
- package/dist/src/alu/src/index.d.ts +98 -9
- package/dist/src/alu/src/index.js +63 -9
- package/dist/src/alu/src/kernel-arith.d.ts +5 -1
- package/dist/src/alu/src/kernel-arith.js +285 -195
- package/dist/src/alu/src/kernel-bits.d.ts +5 -1
- package/dist/src/alu/src/kernel-bits.js +115 -81
- package/dist/src/alu/src/kernel-logic.js +63 -33
- package/dist/src/alu/src/kernel-nd.js +195 -155
- package/dist/src/alu/src/kernel-numeric.d.ts +80 -16
- package/dist/src/alu/src/kernel-numeric.js +331 -259
- package/dist/src/alu/src/operation.d.ts +132 -112
- package/dist/src/alu/src/operation.js +163 -149
- package/dist/src/alu/src/parser.d.ts +175 -168
- package/dist/src/alu/src/parser.js +421 -382
- package/dist/src/alu/src/resonance.d.ts +25 -16
- package/dist/src/alu/src/resonance.js +56 -49
- package/dist/src/alu/src/text.d.ts +7 -3
- package/dist/src/alu/src/text.js +37 -31
- package/dist/src/alu/src/value.d.ts +14 -14
- package/dist/src/alu/src/value.js +160 -150
- package/dist/src/alu/test/alu.test.js +656 -543
- package/dist/src/bytes.d.ts +5 -1
- package/dist/src/bytes.js +40 -33
- package/dist/src/config.d.ts +102 -102
- package/dist/src/config.js +83 -83
- package/dist/src/derive/src/deduction.d.ts +62 -60
- package/dist/src/derive/src/deduction.js +109 -103
- package/dist/src/derive/src/index.d.ts +7 -1
- package/dist/src/derive/src/priority-queue.d.ts +8 -8
- package/dist/src/derive/src/priority-queue.js +61 -57
- package/dist/src/derive/src/rewrite.d.ts +18 -15
- package/dist/src/derive/src/rewrite.js +67 -59
- package/dist/src/derive/src/trie.d.ts +56 -56
- package/dist/src/derive/src/trie.js +179 -175
- package/dist/src/derive/test/derive.test.js +100 -96
- package/dist/src/extension.d.ts +16 -13
- package/dist/src/geometry.d.ts +42 -17
- package/dist/src/geometry.js +266 -235
- package/dist/src/index.d.ts +17 -2
- package/dist/src/index.js +7 -1
- package/dist/src/ingest-cache.d.ts +30 -25
- package/dist/src/ingest-cache.js +127 -108
- package/dist/src/mind/articulation.d.ts +5 -1
- package/dist/src/mind/articulation.js +112 -77
- package/dist/src/mind/attention.d.ts +108 -40
- package/dist/src/mind/attention.js +871 -749
- package/dist/src/mind/canonical.d.ts +19 -4
- package/dist/src/mind/canonical.js +31 -27
- package/dist/src/mind/graph-search.d.ts +225 -201
- package/dist/src/mind/graph-search.js +821 -742
- package/dist/src/mind/index.d.ts +10 -2
- package/dist/src/mind/junction.d.ts +58 -31
- package/dist/src/mind/junction.js +237 -172
- package/dist/src/mind/learning.d.ts +52 -16
- package/dist/src/mind/learning.js +165 -143
- package/dist/src/mind/match.d.ts +69 -20
- package/dist/src/mind/match.js +318 -259
- package/dist/src/mind/mechanisms/alu.js +16 -16
- package/dist/src/mind/mechanisms/cast.d.ts +13 -9
- package/dist/src/mind/mechanisms/cast.js +456 -363
- package/dist/src/mind/mechanisms/confluence.d.ts +12 -8
- package/dist/src/mind/mechanisms/confluence.js +183 -152
- package/dist/src/mind/mechanisms/cover.d.ts +8 -2
- package/dist/src/mind/mechanisms/cover.js +210 -148
- package/dist/src/mind/mechanisms/extraction.d.ts +34 -8
- package/dist/src/mind/mechanisms/extraction.js +288 -234
- package/dist/src/mind/mechanisms/recall.d.ts +10 -6
- package/dist/src/mind/mechanisms/recall.js +185 -126
- package/dist/src/mind/mind.d.ts +154 -129
- package/dist/src/mind/mind.js +288 -258
- package/dist/src/mind/pipeline-mechanism.d.ts +124 -112
- package/dist/src/mind/pipeline-mechanism.js +172 -161
- package/dist/src/mind/pipeline.d.ts +14 -4
- package/dist/src/mind/pipeline.js +189 -125
- package/dist/src/mind/primitives.d.ts +32 -8
- package/dist/src/mind/primitives.js +117 -99
- package/dist/src/mind/rationale.d.ts +98 -86
- package/dist/src/mind/rationale.js +121 -113
- package/dist/src/mind/reasoning.d.ts +13 -2
- package/dist/src/mind/reasoning.js +166 -129
- package/dist/src/mind/recognition.d.ts +4 -1
- package/dist/src/mind/recognition.js +208 -183
- package/dist/src/mind/resonance.d.ts +22 -5
- package/dist/src/mind/resonance.js +0 -0
- package/dist/src/mind/trace.d.ts +25 -6
- package/dist/src/mind/trace.js +58 -50
- package/dist/src/mind/traverse.d.ts +58 -16
- package/dist/src/mind/traverse.js +357 -304
- package/dist/src/mind/types.d.ts +127 -120
- package/dist/src/mind/types.js +69 -60
- package/dist/src/rabitq-hnsw/src/database.d.ts +179 -177
- package/dist/src/rabitq-hnsw/src/database.js +359 -342
- package/dist/src/rabitq-hnsw/src/heap.d.ts +12 -12
- package/dist/src/rabitq-hnsw/src/heap.js +80 -75
- package/dist/src/rabitq-hnsw/src/hnsw.d.ts +103 -103
- package/dist/src/rabitq-hnsw/src/hnsw.js +454 -428
- package/dist/src/rabitq-hnsw/src/index.d.ts +6 -1
- package/dist/src/rabitq-hnsw/src/prng.d.ts +10 -10
- package/dist/src/rabitq-hnsw/src/prng.js +27 -25
- package/dist/src/rabitq-hnsw/src/rabitq.d.ts +69 -69
- package/dist/src/rabitq-hnsw/src/rabitq.js +262 -246
- package/dist/src/rabitq-hnsw/src/store.d.ts +132 -132
- package/dist/src/rabitq-hnsw/src/store.js +843 -752
- package/dist/src/rabitq-hnsw/test/hnsw.test.js +1054 -805
- package/dist/src/sema.d.ts +35 -0
- package/dist/src/sema.js +77 -0
- package/dist/src/store-sqlite.d.ts +166 -144
- package/dist/src/store-sqlite.js +660 -578
- package/dist/src/store.d.ts +630 -593
- package/dist/src/store.js +1581 -1440
- package/dist/src/vec.d.ts +9 -5
- package/dist/src/vec.js +73 -61
- package/package.json +2 -2
- package/src/sema.ts +102 -0
|
@@ -5,35 +5,76 @@
|
|
|
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 {
|
|
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";
|
|
9
48
|
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
10
49
|
const enc = (s) => new TextEncoder().encode(s);
|
|
11
50
|
const dec = (b) => new TextDecoder().decode(b);
|
|
12
|
-
const close = (a, b, eps = 1e-6) =>
|
|
51
|
+
const close = (a, b, eps = 1e-6) =>
|
|
52
|
+
assert.ok(Math.abs(a - b) <= eps, `${a} !~ ${b} (eps ${eps})`);
|
|
13
53
|
/** Build an ALU Value from a JS literal — a TEST fixture, NOT a parser: the
|
|
14
54
|
* kernel no longer reads list structure from bytes (that is the host's
|
|
15
55
|
* meaning-level job — see src/mind/mind.ts recogniseValue), so the kernel's own tests
|
|
16
56
|
* construct Values directly. An array → nd, an integer → int, a fractional
|
|
17
57
|
* number → real, a string → an opaque symbol (its bytes verbatim). */
|
|
18
|
-
const V = (x) =>
|
|
58
|
+
const V = (x) =>
|
|
59
|
+
Array.isArray(x)
|
|
19
60
|
? nd(x.map(V))
|
|
20
61
|
: typeof x === "number"
|
|
21
|
-
|
|
22
|
-
|
|
62
|
+
? (Number.isInteger(x) ? int(BigInt(x)) : real(x))
|
|
63
|
+
: symbol(enc(x));
|
|
23
64
|
/** A logic-only registry + context — the substrate the bit bootstrap runs on. */
|
|
24
65
|
function logicCtx() {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
66
|
+
const r = new OperationRegistry();
|
|
67
|
+
registerLogic(r);
|
|
68
|
+
return r.context(NO_RESONANCE, { tol: 1e-10, maxIter: 1000 });
|
|
28
69
|
}
|
|
29
70
|
/** A full registry context (all kernels). */
|
|
30
71
|
function fullCtx(resonance = NO_RESONANCE) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 }) };
|
|
37
78
|
}
|
|
38
79
|
const B = (v) => (v.domain === "bit" ? v.b : NaN);
|
|
39
80
|
const N = (v) => (v.domain === "int" ? v.n : NaN);
|
|
@@ -41,640 +82,703 @@ const N = (v) => (v.domain === "int" ? v.n : NaN);
|
|
|
41
82
|
// 1 — Logic: the completeness layer. nand is the one axiom; every gate derives.
|
|
42
83
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
43
84
|
test("nand truth table is the irreducible axiom", () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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);
|
|
50
91
|
});
|
|
51
92
|
test("not/and/or derive from nand and match their truth tables", () => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
+
}
|
|
65
108
|
});
|
|
66
109
|
test("nor/xor/xnor/implies/iff all derive from nand", () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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]);
|
|
75
121
|
});
|
|
76
122
|
test("mux(s,a,b) selects a when s=0, b when s=1 — the bridge to control flow", () => {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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);
|
|
85
131
|
});
|
|
86
132
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
87
133
|
// 2 — Arithmetic: the bit-vector bootstrap is EXACT (the nand→everything proof).
|
|
88
134
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
135
|
test("full_adder built from xor/and/or matches its truth table", () => {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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)),
|
|
97
144
|
]));
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
|
102
149
|
});
|
|
103
150
|
test("ripple add / two's-complement negate / shift-add multiply equal native bigint", () => {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
}
|
|
121
176
|
});
|
|
122
177
|
test("the bit bootstrap stays exact past 2^53", () => {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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);
|
|
127
182
|
});
|
|
128
183
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
129
184
|
// 3 — Arithmetic: polymorphic primitives + the derived field and order.
|
|
130
185
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
131
186
|
test("add/multiply are exact on ints and lift to reals", () => {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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);
|
|
139
194
|
});
|
|
140
195
|
test("subtract = add∘negate, divide = multiply∘reciprocal", () => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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);
|
|
144
199
|
});
|
|
145
200
|
test("every comparison = sign∘subtract", () => {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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);
|
|
155
210
|
});
|
|
156
211
|
test("abs/min/max/power/gcd derive correctly", () => {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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);
|
|
164
219
|
});
|
|
165
220
|
test("real power via exp∘log agrees with Math.pow", () => {
|
|
166
|
-
|
|
167
|
-
|
|
221
|
+
const { ctx } = fullCtx();
|
|
222
|
+
close(asReal(ctx.apply("power", [real(2), real(0.5)])), Math.SQRT2, 1e-6);
|
|
168
223
|
});
|
|
169
224
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
170
225
|
// 4 — Arithmetic: polynomial / vector / matrix / linsolve (structured arith).
|
|
171
226
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
172
227
|
test("polyEval (Horner), dot, matMul", () => {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
]);
|
|
180
235
|
});
|
|
181
236
|
test("linsolve solves a 3x3 system (Gaussian elimination)", () => {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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);
|
|
188
243
|
});
|
|
189
244
|
test("linsolve returns null for a singular matrix", () => {
|
|
190
|
-
|
|
245
|
+
assert.equal(linsolve([[1, 2], [2, 4]], [3, 6]), null);
|
|
191
246
|
});
|
|
192
247
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
193
248
|
// 5 — Numerical: the limit layer. Each op is a converge instance.
|
|
194
249
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
195
250
|
test("converge reaches a contraction's fixed point", () => {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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);
|
|
199
254
|
});
|
|
200
255
|
test("diff ≈ analytic derivative", () => {
|
|
201
|
-
|
|
202
|
-
|
|
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);
|
|
203
258
|
});
|
|
204
259
|
test("integrate ≈ closed form", () => {
|
|
205
|
-
|
|
206
|
-
|
|
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);
|
|
207
262
|
});
|
|
208
263
|
test("solve (Newton) finds a root", () => {
|
|
209
|
-
|
|
264
|
+
close(solve((x) => x * x - 2, 1, 1e-12, 200), Math.SQRT2, 1e-8);
|
|
210
265
|
});
|
|
211
266
|
test("exp/log/sin/cos/sqrt converge to the right limits", () => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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);
|
|
222
277
|
});
|
|
223
278
|
test("optimize finds a minimum; odeSolve integrates y'=y", () => {
|
|
224
|
-
|
|
225
|
-
|
|
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);
|
|
226
281
|
});
|
|
227
282
|
test("regress recovers a known line; interpolate is linear", () => {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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);
|
|
236
291
|
});
|
|
237
292
|
test("powerEig / topSingular by power iteration", () => {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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);
|
|
243
298
|
});
|
|
244
299
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
245
300
|
// 6 — The Operation model: derived is indistinguishable; one-line extension.
|
|
246
301
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
247
302
|
test("a derived op has the same record shape as a primitive", () => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
+
}
|
|
257
312
|
});
|
|
258
313
|
test("a brand-new derived op is registered in one call and computes", () => {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
]),
|
|
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
|
+
]),
|
|
265
321
|
]));
|
|
266
|
-
|
|
322
|
+
close(asReal(ctx.apply("hypot", [real(3), real(4)])), 5, 1e-7);
|
|
267
323
|
});
|
|
268
324
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
269
325
|
// 7 — Values: parse, the codec round-trip, and the polymorphic inverse.
|
|
270
326
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
271
327
|
test("parseValue: ints, reals, and symbols", () => {
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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");
|
|
279
335
|
});
|
|
280
336
|
test("decimalCodec round-trips and formats reals deterministically", () => {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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));
|
|
289
345
|
});
|
|
290
346
|
test("inverse is polymorphic: number negates, symbol resonates to its opposite", () => {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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");
|
|
310
366
|
});
|
|
311
367
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
312
368
|
// 8 — The Alu facade: the synchronous scanner and applyBytes.
|
|
313
369
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
314
370
|
test("scan finds numeric operands and symbolic operators in raw bytes", () => {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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]);
|
|
322
378
|
});
|
|
323
379
|
test("scan keeps a multi-digit / decimal number whole, ignores a bare trailing dot", () => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
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]);
|
|
331
387
|
});
|
|
332
388
|
test("scan resolves the longest symbolic operator form", () => {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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 "<"
|
|
337
393
|
});
|
|
338
394
|
test("applyBytes computes the canonical result bytes", () => {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
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);
|
|
346
402
|
});
|
|
347
403
|
test("a computed result is itself an operand (composition for free)", () => {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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");
|
|
352
408
|
});
|
|
353
409
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
354
410
|
// 9 — Expressions: a numerical op acts on a FUNCTION, evaluated by a recursive
|
|
355
411
|
// application of the same ALU (the "recursive call" case).
|
|
356
412
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
357
413
|
test("evalExpression evaluates through the kernel, auto-detecting the variable", () => {
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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);
|
|
369
425
|
});
|
|
370
426
|
test("freeVariables lists candidates, excluding function names", () => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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"]);
|
|
375
431
|
});
|
|
376
432
|
test("diff: the derivative op acts on an expression operand", () => {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
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);
|
|
382
438
|
});
|
|
383
439
|
test("integrate: definite integral over an expression", () => {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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
|
+
);
|
|
388
452
|
});
|
|
389
453
|
test("solve: a root of an expression near a guess", () => {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
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
|
+
);
|
|
393
461
|
});
|
|
394
462
|
test("limit: the value an expression approaches (a converge instance)", () => {
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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);
|
|
398
466
|
});
|
|
399
467
|
test("optimize: a minimiser of an expression", () => {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
+
);
|
|
403
475
|
});
|
|
404
476
|
test("an expression op declines (null) when no evaluator can read the operand", () => {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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);
|
|
408
480
|
});
|
|
409
481
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
410
482
|
// 10 — The operation CONCEPT vocabulary (the generic, resonant recognition seed).
|
|
411
483
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
412
484
|
test("conceptAnchors exposes the operation vocabulary for resonant recognition", () => {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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
|
+
}
|
|
434
506
|
});
|
|
435
507
|
test("prefetchRecognisedOps bridges async recognition to a sync map", async () => {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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);
|
|
447
519
|
});
|
|
448
520
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
449
521
|
// 11 — N-dimensional values: the recursive container. Representation + codec.
|
|
450
522
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
451
523
|
/** A full registry with the nd kernel too — the substrate for the nd tests. */
|
|
452
524
|
function ndCtx(resonance = NO_RESONANCE) {
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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 }) };
|
|
460
532
|
}
|
|
461
533
|
test("nd is the recursive container: nested, ragged, heterogeneous", () => {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
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);
|
|
476
548
|
});
|
|
477
549
|
test("decimalCodec encodes an nd to its canonical bracket spelling (nested, mixed)", () => {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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([])), "[]");
|
|
492
564
|
});
|
|
493
565
|
test("parseValue reads only SCALARS — a bracket literal decodes to an opaque symbol", () => {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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));
|
|
504
576
|
});
|
|
505
577
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
506
578
|
// 12 — Broadcast: EVERY scalar op lifts over nd automatically (one mechanism).
|
|
507
579
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
508
580
|
test("arithmetic broadcasts: list∘list zips, list∘scalar holds the scalar", () => {
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
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]");
|
|
517
592
|
});
|
|
518
593
|
test("broadcast RECURSES through nesting (a matrix op is the same code)", () => {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
+
);
|
|
525
606
|
});
|
|
526
607
|
test("broadcast spans op classes: logic, comparison, transcendental, inverse", () => {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
+
);
|
|
543
627
|
});
|
|
544
628
|
test("broadcast over lists of unequal length declines (no silent truncation)", () => {
|
|
545
|
-
|
|
546
|
-
|
|
629
|
+
const { ctx } = ndCtx();
|
|
630
|
+
assert.throws(() => ctx.apply("add", [V([1, 2, 3]), V([4, 5])]));
|
|
547
631
|
});
|
|
548
632
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
549
633
|
// 13 — The nd core: nd / length / at are the only ops that touch `items`.
|
|
550
634
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
551
635
|
test("core: nd packs, length counts, at projects (negative = from end)", () => {
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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)]));
|
|
565
649
|
});
|
|
566
650
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
567
651
|
// 14 — Higher-order ops: the FUNCTION ARGUMENT is ANY existing operation.
|
|
568
652
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
569
653
|
test("reduce folds by ANY binary op — +, *, max are sum, product, maximum", () => {
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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);
|
|
580
667
|
});
|
|
581
668
|
test("reduce by a binary op BROADCASTS — a column sum falls out of nesting", () => {
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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
|
+
);
|
|
590
680
|
});
|
|
591
681
|
test("map applies a unary op to each element; works over nesting", () => {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
+
);
|
|
596
692
|
});
|
|
597
693
|
test("filter keeps elements a unary predicate accepts; find returns the first", () => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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);
|
|
610
706
|
});
|
|
611
707
|
test("structural plumbing: concat, reverse, flatten, zip, range, rank, shape", () => {
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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]");
|
|
627
726
|
});
|
|
628
727
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
629
728
|
// 15 — resolveOp: the function argument is resolved by the SAME machinery as any
|
|
630
729
|
// operator — a surface form, then (when not literal) its resonant meaning.
|
|
631
730
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
632
731
|
test("resolveOp: a literal surface form names its canonical op", () => {
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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);
|
|
641
740
|
});
|
|
642
741
|
test("resolveOp disambiguates a shared surface form by arity", () => {
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
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");
|
|
654
753
|
});
|
|
655
754
|
test("resolveOp falls through to RESONANCE when the bytes are not a literal form", () => {
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
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
|
+
);
|
|
669
771
|
});
|
|
670
772
|
test("a higher-order op declines (throws) when its operation argument is unrecognised", () => {
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
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
|
+
);
|
|
678
782
|
});
|
|
679
783
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
680
784
|
// 16 — The Alu facade over nd: applyValues (Values in, canonical bytes out).
|
|
@@ -683,29 +787,38 @@ test("a higher-order op declines (throws) when its operation argument is unrecog
|
|
|
683
787
|
// facade as an already-assembled Value, never as a bracket byte string.
|
|
684
788
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
685
789
|
test("applyValues computes nd results: broadcast, reduce, and a composed pipeline", () => {
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
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
|
+
);
|
|
699
812
|
});
|
|
700
813
|
test("applyBytes is SCALAR-only: it does not read list structure from bytes", () => {
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
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");
|
|
709
822
|
});
|
|
710
823
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
711
824
|
// 17 — recogniseValue: the bytes→Value boundary. A span is a scalar, or a LIST
|
|
@@ -714,51 +827,51 @@ test("applyBytes is SCALAR-only: it does not read list structure from bytes", ()
|
|
|
714
827
|
// layered over scan, so the kernel itself only ever computes on Values.
|
|
715
828
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
716
829
|
test("recogniseValue reads a SCALAR — a numeral, or an opaque symbol", () => {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
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));
|
|
725
838
|
});
|
|
726
839
|
test("recogniseValue reads a CONTAINER list, separator spelling not privileged", () => {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
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"]);
|
|
742
855
|
});
|
|
743
856
|
test("recogniseValue reads a bare SEQUENCE by ANY consistent connective", () => {
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
+
}
|
|
751
864
|
});
|
|
752
865
|
test("recogniseValue does NOT over-read: inconsistent or wordy spans stay scalar", () => {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
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");
|
|
764
877
|
});
|