@hviana/sema 0.1.5 → 0.1.6

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 (160) 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 +1687 -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 +17 -42
  51. package/dist/src/geometry.js +235 -266
  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 +749 -871
  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 +126 -185
  82. package/dist/src/mind/mind.d.ts +129 -154
  83. package/dist/src/mind/mind.js +258 -288
  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 +8 -32
  89. package/dist/src/mind/primitives.js +99 -117
  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 +183 -208
  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 +16 -58
  101. package/dist/src/mind/traverse.js +304 -357
  102. package/dist/src/mind/types.d.ts +120 -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 +169 -166
  117. package/dist/src/store-sqlite.js +762 -662
  118. package/dist/src/store.d.ts +586 -630
  119. package/dist/src/store.js +1423 -1581
  120. package/dist/src/vec.d.ts +5 -9
  121. package/dist/src/vec.js +61 -73
  122. package/example/train_base.ts +13 -10
  123. package/package.json +1 -1
  124. package/src/alu/README.md +1 -1
  125. package/src/alu/src/index.ts +1 -1
  126. package/src/config.ts +19 -27
  127. package/src/index.ts +6 -11
  128. package/src/rabitq-ivf/README.md +56 -0
  129. package/src/rabitq-ivf/src/database.ts +276 -0
  130. package/src/{rabitq-hnsw → rabitq-ivf}/src/index.ts +2 -5
  131. package/src/rabitq-ivf/src/ivf.ts +1330 -0
  132. package/src/{rabitq-hnsw → rabitq-ivf}/src/prng.ts +1 -1
  133. package/src/store-sqlite.ts +196 -9
  134. package/src/store.ts +8 -32
  135. package/test/08-storage.test.mjs +3 -3
  136. package/test/14-scaling.test.mjs +2 -2
  137. package/test/35-ivf.test.mjs +263 -0
  138. package/test/36-bloom.test.mjs +123 -0
  139. package/dist/src/rabitq-hnsw/src/database.d.ts +0 -202
  140. package/dist/src/rabitq-hnsw/src/database.js +0 -405
  141. package/dist/src/rabitq-hnsw/src/heap.d.ts +0 -22
  142. package/dist/src/rabitq-hnsw/src/heap.js +0 -94
  143. package/dist/src/rabitq-hnsw/src/hnsw.d.ts +0 -125
  144. package/dist/src/rabitq-hnsw/src/hnsw.js +0 -500
  145. package/dist/src/rabitq-hnsw/src/index.d.ts +0 -15
  146. package/dist/src/rabitq-hnsw/src/prng.d.ts +0 -19
  147. package/dist/src/rabitq-hnsw/src/prng.js +0 -38
  148. package/dist/src/rabitq-hnsw/src/rabitq.d.ts +0 -95
  149. package/dist/src/rabitq-hnsw/src/rabitq.js +0 -299
  150. package/dist/src/rabitq-hnsw/src/store.d.ts +0 -162
  151. package/dist/src/rabitq-hnsw/src/store.js +0 -916
  152. package/dist/src/rabitq-hnsw/test/hnsw.test.d.ts +0 -1
  153. package/dist/src/rabitq-hnsw/test/hnsw.test.js +0 -1197
  154. package/src/rabitq-hnsw/README.md +0 -303
  155. package/src/rabitq-hnsw/src/database.ts +0 -492
  156. package/src/rabitq-hnsw/src/heap.ts +0 -90
  157. package/src/rabitq-hnsw/src/hnsw.ts +0 -514
  158. package/src/rabitq-hnsw/src/store.ts +0 -994
  159. package/src/rabitq-hnsw/test/hnsw.test.ts +0 -1213
  160. /package/src/{rabitq-hnsw → rabitq-ivf}/src/rabitq.ts +0 -0
@@ -31,408 +31,336 @@ import { linsolve } from "./kernel-arith.js";
31
31
  * `maxIter` is reached. Returns the last iterate — the refinement's fixed
32
32
  * point. This is the single irreducible operator of the numerical layer. */
33
33
  export function converge(step, x0, tol, maxIter) {
34
- let x = x0;
35
- for (let i = 0; i < maxIter; i++) {
36
- const next = step(x);
37
- const scale = Math.max(1, Math.abs(next), Math.abs(x));
38
- if (Math.abs(next - x) <= tol * scale) {
39
- return next;
34
+ let x = x0;
35
+ for (let i = 0; i < maxIter; i++) {
36
+ const next = step(x);
37
+ const scale = Math.max(1, Math.abs(next), Math.abs(x));
38
+ if (Math.abs(next - x) <= tol * scale)
39
+ return next;
40
+ x = next;
40
41
  }
41
- x = next;
42
- }
43
- return x;
42
+ return x;
44
43
  }
45
44
  /** Numerical derivative f′(a) by a central difference refined as h → 0 — a
46
45
  * `converge` instance over shrinking step sizes (Richardson-style halving). */
47
46
  export function diff(f, a, tol, maxIter) {
48
- let h = Math.max(1e-2, Math.abs(a) * 1e-2) || 1e-2;
49
- let prev = (f(a + h) - f(a - h)) / (2 * h);
50
- for (let i = 0; i < maxIter; i++) {
51
- h /= 2;
52
- const cur = (f(a + h) - f(a - h)) / (2 * h);
53
- const scale = Math.max(1, Math.abs(cur), Math.abs(prev));
54
- if (Math.abs(cur - prev) <= tol * scale) {
55
- return cur;
47
+ let h = Math.max(1e-2, Math.abs(a) * 1e-2) || 1e-2;
48
+ let prev = (f(a + h) - f(a - h)) / (2 * h);
49
+ for (let i = 0; i < maxIter; i++) {
50
+ h /= 2;
51
+ const cur = (f(a + h) - f(a - h)) / (2 * h);
52
+ const scale = Math.max(1, Math.abs(cur), Math.abs(prev));
53
+ if (Math.abs(cur - prev) <= tol * scale)
54
+ return cur;
55
+ prev = cur;
56
+ if (h < 1e-13)
57
+ break; // floor: finer steps amplify round-off
56
58
  }
57
- prev = cur;
58
- if (h < 1e-13) {
59
- break; // floor: finer steps amplify round-off
60
- }
61
- }
62
- return prev;
59
+ return prev;
63
60
  }
64
61
  /** A root of f near x0, by Newton's method: converge x − f(x)/f′(x), with f′
65
62
  * from {@link diff}. Falls back to a secant step if the derivative vanishes. */
66
63
  export function solve(f, x0, tol, maxIter) {
67
- return converge(
68
- (x) => {
69
- const fx = f(x);
70
- let d = diff(f, x, tol, maxIter);
71
- if (Math.abs(d) < 1e-14) {
72
- // Derivative ~0: nudge with a secant over a small offset.
73
- const h = 1e-6 * (Math.abs(x) + 1);
74
- d = (f(x + h) - fx) / h;
64
+ return converge((x) => {
65
+ const fx = f(x);
66
+ let d = diff(f, x, tol, maxIter);
75
67
  if (Math.abs(d) < 1e-14) {
76
- return x;
68
+ // Derivative ~0: nudge with a secant over a small offset.
69
+ const h = 1e-6 * (Math.abs(x) + 1);
70
+ d = (f(x + h) - fx) / h;
71
+ if (Math.abs(d) < 1e-14)
72
+ return x;
77
73
  }
78
- }
79
- return x - fx / d;
80
- },
81
- x0,
82
- tol,
83
- maxIter,
84
- );
74
+ return x - fx / d;
75
+ }, x0, tol, maxIter);
85
76
  }
86
77
  /** Definite integral of f over [a, b], by composite Simpson refined until the
87
78
  * estimate stops moving — a `converge` over partition count. */
88
79
  export function integrate(f, a, b, tol, maxIter) {
89
- const simpson = (n) => {
90
- const h = (b - a) / n;
91
- let s = f(a) + f(b);
92
- for (let i = 1; i < n; i++) {
93
- s += (i % 2 === 0 ? 2 : 4) * f(a + i * h);
94
- }
95
- return (s * h) / 3;
96
- };
97
- let n = 2;
98
- let prev = simpson(n);
99
- for (let i = 0; i < maxIter; i++) {
100
- n *= 2;
101
- const cur = simpson(n);
102
- const scale = Math.max(1, Math.abs(cur), Math.abs(prev));
103
- if (Math.abs(cur - prev) <= tol * scale) {
104
- return cur;
80
+ const simpson = (n) => {
81
+ const h = (b - a) / n;
82
+ let s = f(a) + f(b);
83
+ for (let i = 1; i < n; i++)
84
+ s += (i % 2 === 0 ? 2 : 4) * f(a + i * h);
85
+ return (s * h) / 3;
86
+ };
87
+ let n = 2;
88
+ let prev = simpson(n);
89
+ for (let i = 0; i < maxIter; i++) {
90
+ n *= 2;
91
+ const cur = simpson(n);
92
+ const scale = Math.max(1, Math.abs(cur), Math.abs(prev));
93
+ if (Math.abs(cur - prev) <= tol * scale)
94
+ return cur;
95
+ prev = cur;
105
96
  }
106
- prev = cur;
107
- }
108
- return prev;
97
+ return prev;
109
98
  }
110
99
  /** e^x by its Taylor series, summed until a term is below tolerance — a
111
100
  * `converge` over partial sums. (Range-reduction-free; adequate for the
112
101
  * arguments a query produces, and exact in the limit.) */
113
102
  export function expSeries(x, tol, maxIter) {
114
- let term = 1;
115
- let sum = 1;
116
- for (let n = 1; n < maxIter; n++) {
117
- term *= x / n;
118
- sum += term;
119
- if (Math.abs(term) <= tol * Math.max(1, Math.abs(sum))) {
120
- break;
103
+ let term = 1;
104
+ let sum = 1;
105
+ for (let n = 1; n < maxIter; n++) {
106
+ term *= x / n;
107
+ sum += term;
108
+ if (Math.abs(term) <= tol * Math.max(1, Math.abs(sum)))
109
+ break;
121
110
  }
122
- }
123
- return sum;
111
+ return sum;
124
112
  }
125
113
  /** ln(x), x > 0, as the root of e^t − x = 0 (Newton via {@link solve}, with a
126
114
  * good initial guess from the host log to keep iteration counts low). */
127
115
  export function logNewton(x, tol, maxIter) {
128
- if (x <= 0) {
129
- return NaN;
130
- }
131
- return solve(
132
- (t) => expSeries(t, tol, maxIter) - x,
133
- Math.log(x),
134
- tol,
135
- maxIter,
136
- );
116
+ if (x <= 0)
117
+ return NaN;
118
+ return solve((t) => expSeries(t, tol, maxIter) - x, Math.log(x), tol, maxIter);
137
119
  }
138
120
  /** sin / cos by their Taylor series (a `converge` over partial sums), with
139
121
  * argument reduction into [−π, π] for accuracy. */
140
122
  export function sinSeries(x, tol, maxIter) {
141
- const TWO_PI = 2 * Math.PI;
142
- let a = x % TWO_PI;
143
- if (a > Math.PI) {
144
- a -= TWO_PI;
145
- }
146
- if (a < -Math.PI) {
147
- a += TWO_PI;
148
- }
149
- let term = a;
150
- let sum = a;
151
- for (let n = 1; n < maxIter; n++) {
152
- term *= (-a * a) / ((2 * n) * (2 * n + 1));
153
- sum += term;
154
- if (Math.abs(term) <= tol) {
155
- break;
123
+ const TWO_PI = 2 * Math.PI;
124
+ let a = x % TWO_PI;
125
+ if (a > Math.PI)
126
+ a -= TWO_PI;
127
+ if (a < -Math.PI)
128
+ a += TWO_PI;
129
+ let term = a;
130
+ let sum = a;
131
+ for (let n = 1; n < maxIter; n++) {
132
+ term *= (-a * a) / ((2 * n) * (2 * n + 1));
133
+ sum += term;
134
+ if (Math.abs(term) <= tol)
135
+ break;
156
136
  }
157
- }
158
- return sum;
137
+ return sum;
159
138
  }
160
139
  export function cosSeries(x, tol, maxIter) {
161
- const TWO_PI = 2 * Math.PI;
162
- let a = x % TWO_PI;
163
- if (a > Math.PI) {
164
- a -= TWO_PI;
165
- }
166
- if (a < -Math.PI) {
167
- a += TWO_PI;
168
- }
169
- let term = 1;
170
- let sum = 1;
171
- for (let n = 1; n < maxIter; n++) {
172
- term *= (-a * a) / ((2 * n - 1) * (2 * n));
173
- sum += term;
174
- if (Math.abs(term) <= tol) {
175
- break;
140
+ const TWO_PI = 2 * Math.PI;
141
+ let a = x % TWO_PI;
142
+ if (a > Math.PI)
143
+ a -= TWO_PI;
144
+ if (a < -Math.PI)
145
+ a += TWO_PI;
146
+ let term = 1;
147
+ let sum = 1;
148
+ for (let n = 1; n < maxIter; n++) {
149
+ term *= (-a * a) / ((2 * n - 1) * (2 * n));
150
+ sum += term;
151
+ if (Math.abs(term) <= tol)
152
+ break;
176
153
  }
177
- }
178
- return sum;
154
+ return sum;
179
155
  }
180
156
  /** √x as the positive root of t² − x = 0 (Newton via {@link solve}). */
181
157
  export function sqrtNewton(x, tol, maxIter) {
182
- if (x < 0) {
183
- return NaN;
184
- }
185
- if (x === 0) {
186
- return 0;
187
- }
188
- return solve((t) => t * t - x, Math.max(x, 1), tol, maxIter);
158
+ if (x < 0)
159
+ return NaN;
160
+ if (x === 0)
161
+ return 0;
162
+ return solve((t) => t * t - x, Math.max(x, 1), tol, maxIter);
189
163
  }
190
164
  /** A local minimum of f near x0: a root of f′ = 0 (optimize = solve(diff f)). */
191
165
  export function optimize(f, x0, tol, maxIter) {
192
- return solve((x) => diff(f, x, tol, maxIter), x0, tol, maxIter);
166
+ return solve((x) => diff(f, x, tol, maxIter), x0, tol, maxIter);
193
167
  }
194
168
  /** Integrate an ODE y′ = f(t, y) from (t0, y0) to t1 by classical RK4 stepping
195
169
  * — explicit stepping is the layer's other use of refinement (here a fixed
196
170
  * fine step; an implicit step would close with {@link solve}). */
197
171
  export function odeSolve(f, t0, y0, t1, steps = 1000) {
198
- const h = (t1 - t0) / steps;
199
- let t = t0;
200
- let y = y0;
201
- for (let i = 0; i < steps; i++) {
202
- const k1 = f(t, y);
203
- const k2 = f(t + h / 2, y + (h / 2) * k1);
204
- const k3 = f(t + h / 2, y + (h / 2) * k2);
205
- const k4 = f(t + h, y + h * k3);
206
- y += (h / 6) * (k1 + 2 * k2 + 2 * k3 + k4);
207
- t += h;
208
- }
209
- return y;
172
+ const h = (t1 - t0) / steps;
173
+ let t = t0;
174
+ let y = y0;
175
+ for (let i = 0; i < steps; i++) {
176
+ const k1 = f(t, y);
177
+ const k2 = f(t + h / 2, y + (h / 2) * k1);
178
+ const k3 = f(t + h / 2, y + (h / 2) * k2);
179
+ const k4 = f(t + h, y + h * k3);
180
+ y += (h / 6) * (k1 + 2 * k2 + 2 * k3 + k4);
181
+ t += h;
182
+ }
183
+ return y;
210
184
  }
211
185
  /** Least-squares polynomial regression of degree `deg` over (x, y) samples:
212
186
  * build the normal equations and solve them with {@link linsolve} — regression
213
187
  * reduces to structured arithmetic. Returns coefficients constant-first. */
214
188
  export function regress(xs, ys, deg) {
215
- const m = deg + 1;
216
- // Vandermonde-style normal matrix AᵀA and vector Aᵀy.
217
- const ata = Array.from({ length: m }, () => new Array(m).fill(0));
218
- const aty = new Array(m).fill(0);
219
- for (let s = 0; s < xs.length; s++) {
220
- const pows = new Array(2 * deg + 1);
221
- pows[0] = 1;
222
- for (let p = 1; p <= 2 * deg; p++) {
223
- pows[p] = pows[p - 1] * xs[s];
224
- }
225
- for (let i = 0; i < m; i++) {
226
- for (let j = 0; j < m; j++) {
227
- ata[i][j] += pows[i + j];
228
- }
229
- aty[i] += pows[i] * ys[s];
189
+ const m = deg + 1;
190
+ // Vandermonde-style normal matrix AᵀA and vector Aᵀy.
191
+ const ata = Array.from({ length: m }, () => new Array(m).fill(0));
192
+ const aty = new Array(m).fill(0);
193
+ for (let s = 0; s < xs.length; s++) {
194
+ const pows = new Array(2 * deg + 1);
195
+ pows[0] = 1;
196
+ for (let p = 1; p <= 2 * deg; p++)
197
+ pows[p] = pows[p - 1] * xs[s];
198
+ for (let i = 0; i < m; i++) {
199
+ for (let j = 0; j < m; j++)
200
+ ata[i][j] += pows[i + j];
201
+ aty[i] += pows[i] * ys[s];
202
+ }
230
203
  }
231
- }
232
- return linsolve(ata, aty);
204
+ return linsolve(ata, aty);
233
205
  }
234
206
  /** Linear interpolation of y at `xq` over sorted sample points (xs, ys). */
235
207
  export function interpolate(xs, ys, xq) {
236
- if (xq <= xs[0]) {
237
- return ys[0];
238
- }
239
- const n = xs.length;
240
- if (xq >= xs[n - 1]) {
241
- return ys[n - 1];
242
- }
243
- for (let i = 0; i < n - 1; i++) {
244
- if (xq >= xs[i] && xq <= xs[i + 1]) {
245
- const t = (xq - xs[i]) / (xs[i + 1] - xs[i]);
246
- return ys[i] + t * (ys[i + 1] - ys[i]);
208
+ if (xq <= xs[0])
209
+ return ys[0];
210
+ const n = xs.length;
211
+ if (xq >= xs[n - 1])
212
+ return ys[n - 1];
213
+ for (let i = 0; i < n - 1; i++) {
214
+ if (xq >= xs[i] && xq <= xs[i + 1]) {
215
+ const t = (xq - xs[i]) / (xs[i + 1] - xs[i]);
216
+ return ys[i] + t * (ys[i + 1] - ys[i]);
217
+ }
247
218
  }
248
- }
249
- return ys[n - 1];
219
+ return ys[n - 1];
250
220
  }
251
221
  /** Dominant eigenpair of a (preferably symmetric) matrix by POWER ITERATION —
252
222
  * the canonical "iteration = converge" of this layer. Returns the eigenvalue
253
223
  * (Rayleigh quotient) and its unit eigenvector. */
254
224
  export function powerEig(A, tol, maxIter) {
255
- const n = A.length;
256
- // Start from a normalised all-ones vector: deterministic (no PRNG), and
257
- // generically NOT aligned with any single eigenvector of a diagonal/symmetric
258
- // matrix, so iteration is not trapped on a non-dominant axis (a unit basis
259
- // vector would be, e.g. e₀ is fixed under diag(9,16) and converges to the
260
- // wrong eigenvalue).
261
- let v = new Array(n).fill(1 / Math.sqrt(n));
262
- let lambda = 0;
263
- for (let it = 0; it < maxIter; it++) {
264
- // w = A v
265
- const w = A.map((row) => row.reduce((s, a, j) => s + a * v[j], 0));
266
- const norm = Math.sqrt(w.reduce((s, x) => s + x * x, 0)) || 1;
267
- const vn = w.map((x) => x / norm);
268
- // Rayleigh quotient vᵀAv.
269
- const Av = A.map((row) => row.reduce((s, a, j) => s + a * vn[j], 0));
270
- const lam = vn.reduce((s, x, i) => s + x * Av[i], 0);
271
- if (Math.abs(lam - lambda) <= tol * Math.max(1, Math.abs(lam))) {
272
- return { value: lam, vector: vn };
225
+ const n = A.length;
226
+ // Start from a normalised all-ones vector: deterministic (no PRNG), and
227
+ // generically NOT aligned with any single eigenvector of a diagonal/symmetric
228
+ // matrix, so iteration is not trapped on a non-dominant axis (a unit basis
229
+ // vector would be, e.g. e₀ is fixed under diag(9,16) and converges to the
230
+ // wrong eigenvalue).
231
+ let v = new Array(n).fill(1 / Math.sqrt(n));
232
+ let lambda = 0;
233
+ for (let it = 0; it < maxIter; it++) {
234
+ // w = A v
235
+ const w = A.map((row) => row.reduce((s, a, j) => s + a * v[j], 0));
236
+ const norm = Math.sqrt(w.reduce((s, x) => s + x * x, 0)) || 1;
237
+ const vn = w.map((x) => x / norm);
238
+ // Rayleigh quotient vᵀAv.
239
+ const Av = A.map((row) => row.reduce((s, a, j) => s + a * vn[j], 0));
240
+ const lam = vn.reduce((s, x, i) => s + x * Av[i], 0);
241
+ if (Math.abs(lam - lambda) <= tol * Math.max(1, Math.abs(lam))) {
242
+ return { value: lam, vector: vn };
243
+ }
244
+ lambda = lam;
245
+ v = vn;
273
246
  }
274
- lambda = lam;
275
- v = vn;
276
- }
277
- return { value: lambda, vector: v };
247
+ return { value: lambda, vector: v };
278
248
  }
279
249
  /** Largest singular value of A: √(dominant eigenvalue of AᵀA) — power iteration
280
250
  * again, the prototypical `converge`. */
281
251
  export function topSingular(A, tol, maxIter) {
282
- const n = A[0].length;
283
- // AᵀA (n×n).
284
- const ata = Array.from({ length: n }, () => new Array(n).fill(0));
285
- for (let i = 0; i < n; i++) {
286
- for (let j = 0; j < n; j++) {
287
- let s = 0;
288
- for (let r = 0; r < A.length; r++) {
289
- s += A[r][i] * A[r][j];
290
- }
291
- ata[i][j] = s;
252
+ const n = A[0].length;
253
+ // AᵀA (n×n).
254
+ const ata = Array.from({ length: n }, () => new Array(n).fill(0));
255
+ for (let i = 0; i < n; i++) {
256
+ for (let j = 0; j < n; j++) {
257
+ let s = 0;
258
+ for (let r = 0; r < A.length; r++)
259
+ s += A[r][i] * A[r][j];
260
+ ata[i][j] = s;
261
+ }
292
262
  }
293
- }
294
- const { value } = powerEig(ata, tol, maxIter);
295
- return Math.sqrt(Math.max(0, value));
263
+ const { value } = powerEig(ata, tol, maxIter);
264
+ return Math.sqrt(Math.max(0, value));
296
265
  }
297
266
  /** Register the scalar transcendentals as ops (they have surface forms). The
298
267
  * matrix routines stay exported functions — there is no infix surface for a
299
268
  * matrix, and auto-firing them from query spans is neither wanted nor safe. */
300
269
  export function registerNumeric(r) {
301
- const one = (name, forms, f) => {
302
- r.derive(
303
- name,
304
- 1,
305
- forms,
306
- (args, ctx) => real(f(asReal(args[0]), ctx.rt.tol, ctx.rt.maxIter)),
307
- );
308
- };
309
- one("exp", ["exp", "e^"], expSeries);
310
- // ── named constants ───────────────────────────────────────────────────────
311
- // Nullary ops, so a constant is RESOLVED like any operation (registry forms),
312
- // never special-cased by a parser: the expression grammar reads "an arity-0
313
- // op" as "a constant" generically.
314
- r.derive("pi", 0, ["pi", "π"], () => real(Math.PI));
315
- r.derive("euler", 0, ["e"], () => real(Math.E));
316
- one("log", ["log", "ln"], logNewton);
317
- one("sin", ["sin"], sinSeries);
318
- one("cos", ["cos"], cosSeries);
319
- one("sqrt", ["sqrt", "√"], sqrtNewton);
320
- // ── operations over an EXPRESSION (the recursive-call case) ───────────────
321
- // A derivative, an integral, a limit, an optimisation — these act on a
322
- // FUNCTION, given as a symbol (the expression's bytes) plus point/bounds. The
323
- // function is sampled by ctx.evalExpr, which evaluates the expression by a
324
- // RECURSIVE application of the same ALU (see expr.ts). Their surface `forms`
325
- // are CONCEPT forms — words a query might literally carry ("derivative",
326
- // "integral", "limit") — but the GENERIC, modality-agnostic recognition is by
327
- // resonance over these same concepts (pipeline.ts's recogniseOp), so an operation
328
- // the bytes do not spell at all is still recognised by meaning. These ops
329
- // decline (return their first operand unchanged is wrong; instead they throw,
330
- // which the Alu facade maps to "rule does not fire") when no evaluator is
331
- // wired or the expression is unparseable — synthesis stays evidence-based.
332
- const exprOf = (v) => {
333
- if (v.domain === "symbol") {
334
- return v.bytes;
335
- }
336
- // A bare number is a constant expression — encode it back to bytes so the
337
- // evaluator sees a literal.
338
- throw new Error("expression operand expected");
339
- };
340
- const evalAt = (ctx, expr, vbl, at) => {
341
- if (!ctx.evalExpr) {
342
- throw new Error("no expression evaluator");
343
- }
344
- const y = ctx.evalExpr(expr, vbl, at);
345
- if (y === null || !Number.isFinite(y)) {
346
- throw new Error("expression failed");
347
- }
348
- return y;
349
- };
350
- // diff: derivative of f at a point. args = [expr, at].
351
- r.derive(
352
- "diff",
353
- 2,
354
- ["derivative", "differentiate", "d/dx", "diff"],
355
- (args, ctx) => {
356
- const expr = exprOf(args[0]);
357
- const at = asReal(args[1]);
358
- return real(
359
- diff((x) => evalAt(ctx, expr, "", x), at, ctx.rt.tol, ctx.rt.maxIter),
360
- );
361
- },
362
- { expression: true },
363
- );
364
- // integrate: definite integral of f over [a, b]. args = [expr, a, b].
365
- r.derive("integrate", 3, ["integral", "integrate", "∫"], (args, ctx) => {
366
- const expr = exprOf(args[0]);
367
- const a = asReal(args[1]);
368
- const b = asReal(args[2]);
369
- return real(
370
- integrate(
371
- (x) => evalAt(ctx, expr, "", x),
372
- a,
373
- b,
374
- ctx.rt.tol,
375
- ctx.rt.maxIter,
376
- ),
377
- );
378
- }, { expression: true });
379
- // solve: a root of f near a guess. args = [expr, guess].
380
- r.derive("solve", 2, ["solve", "root", "zero"], (args, ctx) => {
381
- const expr = exprOf(args[0]);
382
- const guess = asReal(args[1]);
383
- return real(
384
- solve((x) => evalAt(ctx, expr, "", x), guess, ctx.rt.tol, ctx.rt.maxIter),
385
- );
386
- }, { expression: true });
387
- // limit: the value f approaches as its variable → a, estimated by sampling
388
- // ever closer from both sides and converging (a limit IS a converge instance).
389
- r.derive("limit", 2, ["limit", "lim"], (args, ctx) => {
390
- const expr = exprOf(args[0]);
391
- const a = asReal(args[1]);
392
- const sample = (h) => {
393
- // average of the two one-sided samples, skirting the point itself
394
- const left = evalAt(ctx, expr, "", a - h);
395
- const right = evalAt(ctx, expr, "", a + h);
396
- return (left + right) / 2;
270
+ const one = (name, forms, f) => {
271
+ r.derive(name, 1, forms, (args, ctx) => real(f(asReal(args[0]), ctx.rt.tol, ctx.rt.maxIter)));
397
272
  };
398
- let h = 0.1;
399
- let prev = sample(h);
400
- for (let i = 0; i < ctx.rt.maxIter; i++) {
401
- h /= 2;
402
- const cur = sample(h);
403
- const scale = Math.max(1, Math.abs(cur), Math.abs(prev));
404
- if (Math.abs(cur - prev) <= ctx.rt.tol * scale) {
405
- return real(cur);
406
- }
407
- prev = cur;
408
- if (h < 1e-13) {
409
- break;
410
- }
411
- }
412
- return real(prev);
413
- }, { expression: true });
414
- // optimize: a local minimiser of f near a guess (= solve(diff f)). args =
415
- // [expr, guess].
416
- r.derive(
417
- "optimize",
418
- 2,
419
- ["minimize", "minimise", "optimize", "argmin"],
420
- (args, ctx) => {
421
- const expr = exprOf(args[0]);
422
- const guess = asReal(args[1]);
423
- return real(
424
- optimize(
425
- (x) => evalAt(ctx, expr, "", x),
426
- guess,
427
- ctx.rt.tol,
428
- ctx.rt.maxIter,
429
- ),
430
- );
431
- },
432
- { expression: true },
433
- );
434
- // converge and the matrix routines (regress, eig, svd, interpolate) take
435
- // FUNCTION or ARRAY operands with no flat infix surface; they are offered
436
- // through the pure exports above and used internally (e.g. sqrt = solve(t²−x),
437
- // optimize = solve(diff f)).
273
+ one("exp", ["exp", "e^"], expSeries);
274
+ // ── named constants ───────────────────────────────────────────────────────
275
+ // Nullary ops, so a constant is RESOLVED like any operation (registry forms),
276
+ // never special-cased by a parser: the expression grammar reads "an arity-0
277
+ // op" as "a constant" generically.
278
+ r.derive("pi", 0, ["pi", "π"], () => real(Math.PI));
279
+ r.derive("euler", 0, ["e"], () => real(Math.E));
280
+ one("log", ["log", "ln"], logNewton);
281
+ one("sin", ["sin"], sinSeries);
282
+ one("cos", ["cos"], cosSeries);
283
+ one("sqrt", ["sqrt", "√"], sqrtNewton);
284
+ // ── operations over an EXPRESSION (the recursive-call case) ───────────────
285
+ // A derivative, an integral, a limit, an optimisation — these act on a
286
+ // FUNCTION, given as a symbol (the expression's bytes) plus point/bounds. The
287
+ // function is sampled by ctx.evalExpr, which evaluates the expression by a
288
+ // RECURSIVE application of the same ALU (see expr.ts). Their surface `forms`
289
+ // are CONCEPT forms words a query might literally carry ("derivative",
290
+ // "integral", "limit") — but the GENERIC, modality-agnostic recognition is by
291
+ // resonance over these same concepts (pipeline.ts's recogniseOp), so an operation
292
+ // the bytes do not spell at all is still recognised by meaning. These ops
293
+ // decline (return their first operand unchanged is wrong; instead they throw,
294
+ // which the Alu facade maps to "rule does not fire") when no evaluator is
295
+ // wired or the expression is unparseable — synthesis stays evidence-based.
296
+ const exprOf = (v) => {
297
+ if (v.domain === "symbol")
298
+ return v.bytes;
299
+ // A bare number is a constant expression — encode it back to bytes so the
300
+ // evaluator sees a literal.
301
+ throw new Error("expression operand expected");
302
+ };
303
+ const evalAt = (ctx, expr, vbl, at) => {
304
+ if (!ctx.evalExpr)
305
+ throw new Error("no expression evaluator");
306
+ const y = ctx.evalExpr(expr, vbl, at);
307
+ if (y === null || !Number.isFinite(y))
308
+ throw new Error("expression failed");
309
+ return y;
310
+ };
311
+ // diff: derivative of f at a point. args = [expr, at].
312
+ r.derive("diff", 2, ["derivative", "differentiate", "d/dx", "diff"], (args, ctx) => {
313
+ const expr = exprOf(args[0]);
314
+ const at = asReal(args[1]);
315
+ return real(diff((x) => evalAt(ctx, expr, "", x), at, ctx.rt.tol, ctx.rt.maxIter));
316
+ }, { expression: true });
317
+ // integrate: definite integral of f over [a, b]. args = [expr, a, b].
318
+ r.derive("integrate", 3, ["integral", "integrate", "∫"], (args, ctx) => {
319
+ const expr = exprOf(args[0]);
320
+ const a = asReal(args[1]);
321
+ const b = asReal(args[2]);
322
+ return real(integrate((x) => evalAt(ctx, expr, "", x), a, b, ctx.rt.tol, ctx.rt.maxIter));
323
+ }, { expression: true });
324
+ // solve: a root of f near a guess. args = [expr, guess].
325
+ r.derive("solve", 2, ["solve", "root", "zero"], (args, ctx) => {
326
+ const expr = exprOf(args[0]);
327
+ const guess = asReal(args[1]);
328
+ return real(solve((x) => evalAt(ctx, expr, "", x), guess, ctx.rt.tol, ctx.rt.maxIter));
329
+ }, { expression: true });
330
+ // limit: the value f approaches as its variable → a, estimated by sampling
331
+ // ever closer from both sides and converging (a limit IS a converge instance).
332
+ r.derive("limit", 2, ["limit", "lim"], (args, ctx) => {
333
+ const expr = exprOf(args[0]);
334
+ const a = asReal(args[1]);
335
+ const sample = (h) => {
336
+ // average of the two one-sided samples, skirting the point itself
337
+ const left = evalAt(ctx, expr, "", a - h);
338
+ const right = evalAt(ctx, expr, "", a + h);
339
+ return (left + right) / 2;
340
+ };
341
+ let h = 0.1;
342
+ let prev = sample(h);
343
+ for (let i = 0; i < ctx.rt.maxIter; i++) {
344
+ h /= 2;
345
+ const cur = sample(h);
346
+ const scale = Math.max(1, Math.abs(cur), Math.abs(prev));
347
+ if (Math.abs(cur - prev) <= ctx.rt.tol * scale)
348
+ return real(cur);
349
+ prev = cur;
350
+ if (h < 1e-13)
351
+ break;
352
+ }
353
+ return real(prev);
354
+ }, { expression: true });
355
+ // optimize: a local minimiser of f near a guess (= solve(diff f)). args =
356
+ // [expr, guess].
357
+ r.derive("optimize", 2, ["minimize", "minimise", "optimize", "argmin"], (args, ctx) => {
358
+ const expr = exprOf(args[0]);
359
+ const guess = asReal(args[1]);
360
+ return real(optimize((x) => evalAt(ctx, expr, "", x), guess, ctx.rt.tol, ctx.rt.maxIter));
361
+ }, { expression: true });
362
+ // converge and the matrix routines (regress, eig, svd, interpolate) take
363
+ // FUNCTION or ARRAY operands with no flat infix surface; they are offered
364
+ // through the pure exports above and used internally (e.g. sqrt = solve(t²−x),
365
+ // optimize = solve(diff f)).
438
366
  }