@kernel.chat/kbot 3.41.0 → 3.42.0
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/README.md +5 -5
- package/dist/agent-teams.d.ts +1 -1
- package/dist/agent-teams.d.ts.map +1 -1
- package/dist/agent-teams.js +36 -3
- package/dist/agent-teams.js.map +1 -1
- package/dist/agents/specialists.d.ts.map +1 -1
- package/dist/agents/specialists.js +20 -0
- package/dist/agents/specialists.js.map +1 -1
- package/dist/channels/kbot-channel.js +8 -31
- package/dist/channels/kbot-channel.js.map +1 -1
- package/dist/cli.js +8 -8
- package/dist/digest.js +1 -1
- package/dist/digest.js.map +1 -1
- package/dist/email-service.d.ts.map +1 -1
- package/dist/email-service.js +1 -2
- package/dist/email-service.js.map +1 -1
- package/dist/episodic-memory.d.ts.map +1 -1
- package/dist/episodic-memory.js +14 -0
- package/dist/episodic-memory.js.map +1 -1
- package/dist/learned-router.d.ts.map +1 -1
- package/dist/learned-router.js +29 -0
- package/dist/learned-router.js.map +1 -1
- package/dist/tools/email.d.ts.map +1 -1
- package/dist/tools/email.js +2 -3
- package/dist/tools/email.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/lab-bio.d.ts +2 -0
- package/dist/tools/lab-bio.d.ts.map +1 -0
- package/dist/tools/lab-bio.js +1392 -0
- package/dist/tools/lab-bio.js.map +1 -0
- package/dist/tools/lab-chem.d.ts +2 -0
- package/dist/tools/lab-chem.d.ts.map +1 -0
- package/dist/tools/lab-chem.js +1257 -0
- package/dist/tools/lab-chem.js.map +1 -0
- package/dist/tools/lab-core.d.ts +2 -0
- package/dist/tools/lab-core.d.ts.map +1 -0
- package/dist/tools/lab-core.js +2452 -0
- package/dist/tools/lab-core.js.map +1 -0
- package/dist/tools/lab-data.d.ts +2 -0
- package/dist/tools/lab-data.d.ts.map +1 -0
- package/dist/tools/lab-data.js +2464 -0
- package/dist/tools/lab-data.js.map +1 -0
- package/dist/tools/lab-earth.d.ts +2 -0
- package/dist/tools/lab-earth.d.ts.map +1 -0
- package/dist/tools/lab-earth.js +1124 -0
- package/dist/tools/lab-earth.js.map +1 -0
- package/dist/tools/lab-math.d.ts +2 -0
- package/dist/tools/lab-math.d.ts.map +1 -0
- package/dist/tools/lab-math.js +3021 -0
- package/dist/tools/lab-math.js.map +1 -0
- package/dist/tools/lab-physics.d.ts +2 -0
- package/dist/tools/lab-physics.d.ts.map +1 -0
- package/dist/tools/lab-physics.js +2423 -0
- package/dist/tools/lab-physics.js.map +1 -0
- package/package.json +2 -3
|
@@ -0,0 +1,3021 @@
|
|
|
1
|
+
// kbot Lab Math Tools — Pure & Applied Mathematics
|
|
2
|
+
// Self-contained implementations: no external dependencies beyond Node.js built-ins.
|
|
3
|
+
// Covers symbolic computation, linear algebra, optimization, number theory,
|
|
4
|
+
// graph theory, combinatorics, differential equations, probability,
|
|
5
|
+
// Fourier analysis, and OEIS lookup.
|
|
6
|
+
import { registerTool } from './index.js';
|
|
7
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
+
// §0 SHARED HELPERS
|
|
9
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
10
|
+
/** Format a number for display, avoiding floating-point noise */
|
|
11
|
+
function fmt(n, digits = 8) {
|
|
12
|
+
if (Number.isInteger(n))
|
|
13
|
+
return String(n);
|
|
14
|
+
const s = n.toPrecision(digits);
|
|
15
|
+
// strip trailing zeros after decimal
|
|
16
|
+
if (s.includes('.'))
|
|
17
|
+
return s.replace(/\.?0+$/, '');
|
|
18
|
+
return s;
|
|
19
|
+
}
|
|
20
|
+
/** Safe JSON parse with error message */
|
|
21
|
+
function safeParse(s, label) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(s);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
throw new Error(`Invalid JSON for ${label}: ${s}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Gamma function via Lanczos approximation (real positive arguments) */
|
|
30
|
+
function gammaFn(z) {
|
|
31
|
+
if (z < 0.5) {
|
|
32
|
+
return Math.PI / (Math.sin(Math.PI * z) * gammaFn(1 - z));
|
|
33
|
+
}
|
|
34
|
+
z -= 1;
|
|
35
|
+
const g = 7;
|
|
36
|
+
const c = [
|
|
37
|
+
0.99999999999980993,
|
|
38
|
+
676.5203681218851,
|
|
39
|
+
-1259.1392167224028,
|
|
40
|
+
771.32342877765313,
|
|
41
|
+
-176.61502916214059,
|
|
42
|
+
12.507343278686905,
|
|
43
|
+
-0.13857109526572012,
|
|
44
|
+
9.9843695780195716e-6,
|
|
45
|
+
1.5056327351493116e-7,
|
|
46
|
+
];
|
|
47
|
+
let x = c[0];
|
|
48
|
+
for (let i = 1; i < g + 2; i++) {
|
|
49
|
+
x += c[i] / (z + i);
|
|
50
|
+
}
|
|
51
|
+
const t = z + g + 0.5;
|
|
52
|
+
return Math.sqrt(2 * Math.PI) * Math.pow(t, z + 0.5) * Math.exp(-t) * x;
|
|
53
|
+
}
|
|
54
|
+
/** Log-gamma for large arguments */
|
|
55
|
+
function lgamma(x) {
|
|
56
|
+
return Math.log(gammaFn(x));
|
|
57
|
+
}
|
|
58
|
+
/** Beta function */
|
|
59
|
+
function betaFn(a, b) {
|
|
60
|
+
return gammaFn(a) * gammaFn(b) / gammaFn(a + b);
|
|
61
|
+
}
|
|
62
|
+
/** Regularized incomplete beta function via continued fraction (Lentz) */
|
|
63
|
+
function betaInc(x, a, b) {
|
|
64
|
+
if (x <= 0)
|
|
65
|
+
return 0;
|
|
66
|
+
if (x >= 1)
|
|
67
|
+
return 1;
|
|
68
|
+
const lnBeta = lgamma(a) + lgamma(b) - lgamma(a + b);
|
|
69
|
+
const front = Math.exp(Math.log(x) * a + Math.log(1 - x) * b - lnBeta) / a;
|
|
70
|
+
// Use the symmetry relation for better convergence
|
|
71
|
+
if (x > (a + 1) / (a + b + 2)) {
|
|
72
|
+
return 1 - betaInc(1 - x, b, a);
|
|
73
|
+
}
|
|
74
|
+
// Lentz continued fraction
|
|
75
|
+
let f = 1, c = 1, d = 1 - (a + b) * x / (a + 1);
|
|
76
|
+
if (Math.abs(d) < 1e-30)
|
|
77
|
+
d = 1e-30;
|
|
78
|
+
d = 1 / d;
|
|
79
|
+
f = d;
|
|
80
|
+
for (let m = 1; m <= 200; m++) {
|
|
81
|
+
// even step
|
|
82
|
+
let num = m * (b - m) * x / ((a + 2 * m - 1) * (a + 2 * m));
|
|
83
|
+
d = 1 + num * d;
|
|
84
|
+
if (Math.abs(d) < 1e-30)
|
|
85
|
+
d = 1e-30;
|
|
86
|
+
d = 1 / d;
|
|
87
|
+
c = 1 + num / c;
|
|
88
|
+
if (Math.abs(c) < 1e-30)
|
|
89
|
+
c = 1e-30;
|
|
90
|
+
f *= d * c;
|
|
91
|
+
// odd step
|
|
92
|
+
num = -(a + m) * (a + b + m) * x / ((a + 2 * m) * (a + 2 * m + 1));
|
|
93
|
+
d = 1 + num * d;
|
|
94
|
+
if (Math.abs(d) < 1e-30)
|
|
95
|
+
d = 1e-30;
|
|
96
|
+
d = 1 / d;
|
|
97
|
+
c = 1 + num / c;
|
|
98
|
+
if (Math.abs(c) < 1e-30)
|
|
99
|
+
c = 1e-30;
|
|
100
|
+
const delta = d * c;
|
|
101
|
+
f *= delta;
|
|
102
|
+
if (Math.abs(delta - 1) < 1e-12)
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
return front * f;
|
|
106
|
+
}
|
|
107
|
+
/** Regularized incomplete gamma function P(a,x) via series */
|
|
108
|
+
function gammaPSeries(a, x) {
|
|
109
|
+
if (x < 0)
|
|
110
|
+
return 0;
|
|
111
|
+
if (x === 0)
|
|
112
|
+
return 0;
|
|
113
|
+
let sum = 1 / a;
|
|
114
|
+
let term = 1 / a;
|
|
115
|
+
for (let n = 1; n < 300; n++) {
|
|
116
|
+
term *= x / (a + n);
|
|
117
|
+
sum += term;
|
|
118
|
+
if (Math.abs(term) < Math.abs(sum) * 1e-14)
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
return sum * Math.exp(-x + a * Math.log(x) - lgamma(a));
|
|
122
|
+
}
|
|
123
|
+
/** Regularized incomplete gamma function Q(a,x) via continued fraction */
|
|
124
|
+
function gammaQCF(a, x) {
|
|
125
|
+
let f = x + 1 - a, c = 1e30, d = 1 / f;
|
|
126
|
+
let h = d;
|
|
127
|
+
for (let i = 1; i <= 300; i++) {
|
|
128
|
+
const an = -i * (i - a);
|
|
129
|
+
const bn = x + 2 * i + 1 - a;
|
|
130
|
+
d = bn + an * d;
|
|
131
|
+
if (Math.abs(d) < 1e-30)
|
|
132
|
+
d = 1e-30;
|
|
133
|
+
d = 1 / d;
|
|
134
|
+
c = bn + an / c;
|
|
135
|
+
if (Math.abs(c) < 1e-30)
|
|
136
|
+
c = 1e-30;
|
|
137
|
+
const delta = d * c;
|
|
138
|
+
h *= delta;
|
|
139
|
+
if (Math.abs(delta - 1) < 1e-14)
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
return Math.exp(-x + a * Math.log(x) - lgamma(a)) * h;
|
|
143
|
+
}
|
|
144
|
+
/** Regularized lower incomplete gamma P(a, x) */
|
|
145
|
+
function gammaP(a, x) {
|
|
146
|
+
if (x < a + 1)
|
|
147
|
+
return gammaPSeries(a, x);
|
|
148
|
+
return 1 - gammaQCF(a, x);
|
|
149
|
+
}
|
|
150
|
+
/** Error function via Abramowitz & Stegun 7.1.26 */
|
|
151
|
+
function erf(x) {
|
|
152
|
+
const sign = x < 0 ? -1 : 1;
|
|
153
|
+
x = Math.abs(x);
|
|
154
|
+
const t = 1 / (1 + 0.3275911 * x);
|
|
155
|
+
const y = 1 - (((((1.061405429 * t - 1.453152027) * t) + 1.421413741) * t - 0.284496736) * t + 0.254829592) * t * Math.exp(-x * x);
|
|
156
|
+
return sign * y;
|
|
157
|
+
}
|
|
158
|
+
/** Standard normal CDF (Abramowitz & Stegun) */
|
|
159
|
+
function normalCDF(x) {
|
|
160
|
+
return 0.5 * (1 + erf(x / Math.SQRT2));
|
|
161
|
+
}
|
|
162
|
+
/** Standard normal PDF */
|
|
163
|
+
function normalPDF(x) {
|
|
164
|
+
return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);
|
|
165
|
+
}
|
|
166
|
+
/** Normal quantile (inverse CDF) via rational approximation (Peter Acklam) */
|
|
167
|
+
function normalQuantile(p) {
|
|
168
|
+
if (p <= 0)
|
|
169
|
+
return -Infinity;
|
|
170
|
+
if (p >= 1)
|
|
171
|
+
return Infinity;
|
|
172
|
+
if (p === 0.5)
|
|
173
|
+
return 0;
|
|
174
|
+
const a = [
|
|
175
|
+
-3.969683028665376e+01, 2.209460984245205e+02,
|
|
176
|
+
-2.759285104469687e+02, 1.383577518672690e+02,
|
|
177
|
+
-3.066479806614716e+01, 2.506628277459239e+00,
|
|
178
|
+
];
|
|
179
|
+
const b = [
|
|
180
|
+
-5.447609879822406e+01, 1.615858368580409e+02,
|
|
181
|
+
-1.556989798598866e+02, 6.680131188771972e+01,
|
|
182
|
+
-1.328068155288572e+01,
|
|
183
|
+
];
|
|
184
|
+
const c = [
|
|
185
|
+
-7.784894002430293e-03, -3.223964580411365e-01,
|
|
186
|
+
-2.400758277161838e+00, -2.549732539343734e+00,
|
|
187
|
+
4.374664141464968e+00, 2.938163982698783e+00,
|
|
188
|
+
];
|
|
189
|
+
const d = [
|
|
190
|
+
7.784695709041462e-03, 3.224671290700398e-01,
|
|
191
|
+
2.445134137142996e+00, 3.754408661907416e+00,
|
|
192
|
+
];
|
|
193
|
+
const pLow = 0.02425, pHigh = 1 - pLow;
|
|
194
|
+
let q, r;
|
|
195
|
+
if (p < pLow) {
|
|
196
|
+
q = Math.sqrt(-2 * Math.log(p));
|
|
197
|
+
return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
|
|
198
|
+
((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1);
|
|
199
|
+
}
|
|
200
|
+
else if (p <= pHigh) {
|
|
201
|
+
q = p - 0.5;
|
|
202
|
+
r = q * q;
|
|
203
|
+
return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q /
|
|
204
|
+
(((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
q = Math.sqrt(-2 * Math.log(1 - p));
|
|
208
|
+
return -((((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /
|
|
209
|
+
((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/** Tokenizer for math expressions */
|
|
213
|
+
function tokenize(expr) {
|
|
214
|
+
const tokens = [];
|
|
215
|
+
let i = 0;
|
|
216
|
+
while (i < expr.length) {
|
|
217
|
+
if (/\s/.test(expr[i])) {
|
|
218
|
+
i++;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if ('+-*/^(),'.includes(expr[i])) {
|
|
222
|
+
tokens.push(expr[i]);
|
|
223
|
+
i++;
|
|
224
|
+
}
|
|
225
|
+
else if (/[0-9.]/.test(expr[i])) {
|
|
226
|
+
let num = '';
|
|
227
|
+
while (i < expr.length && /[0-9.eE\-+]/.test(expr[i])) {
|
|
228
|
+
// handle scientific notation carefully
|
|
229
|
+
if ((expr[i] === '-' || expr[i] === '+') && num.length > 0 && !/[eE]/.test(num[num.length - 1]))
|
|
230
|
+
break;
|
|
231
|
+
num += expr[i];
|
|
232
|
+
i++;
|
|
233
|
+
}
|
|
234
|
+
tokens.push(num);
|
|
235
|
+
}
|
|
236
|
+
else if (/[a-zA-Z_]/.test(expr[i])) {
|
|
237
|
+
let id = '';
|
|
238
|
+
while (i < expr.length && /[a-zA-Z0-9_]/.test(expr[i])) {
|
|
239
|
+
id += expr[i];
|
|
240
|
+
i++;
|
|
241
|
+
}
|
|
242
|
+
tokens.push(id);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
i++;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return tokens;
|
|
249
|
+
}
|
|
250
|
+
const KNOWN_FNS = new Set(['sin', 'cos', 'tan', 'exp', 'log', 'ln', 'sqrt', 'abs', 'asin', 'acos', 'atan', 'sinh', 'cosh', 'tanh']);
|
|
251
|
+
/** Recursive descent parser: expr → term ((+|-) term)* */
|
|
252
|
+
function parseExpr(tokens, pos) {
|
|
253
|
+
let left = parseTerm(tokens, pos);
|
|
254
|
+
while (pos.i < tokens.length && (tokens[pos.i] === '+' || tokens[pos.i] === '-')) {
|
|
255
|
+
const op = tokens[pos.i];
|
|
256
|
+
pos.i++;
|
|
257
|
+
const right = parseTerm(tokens, pos);
|
|
258
|
+
left = op === '+' ? { type: 'add', left, right } : { type: 'sub', left, right };
|
|
259
|
+
}
|
|
260
|
+
return left;
|
|
261
|
+
}
|
|
262
|
+
/** term → unary ((*|/) unary)* */
|
|
263
|
+
function parseTerm(tokens, pos) {
|
|
264
|
+
let left = parseUnary(tokens, pos);
|
|
265
|
+
while (pos.i < tokens.length && (tokens[pos.i] === '*' || tokens[pos.i] === '/')) {
|
|
266
|
+
const op = tokens[pos.i];
|
|
267
|
+
pos.i++;
|
|
268
|
+
const right = parseUnary(tokens, pos);
|
|
269
|
+
left = op === '*' ? { type: 'mul', left, right } : { type: 'div', left, right };
|
|
270
|
+
}
|
|
271
|
+
return left;
|
|
272
|
+
}
|
|
273
|
+
/** unary → (-) unary | power */
|
|
274
|
+
function parseUnary(tokens, pos) {
|
|
275
|
+
if (pos.i < tokens.length && tokens[pos.i] === '-') {
|
|
276
|
+
pos.i++;
|
|
277
|
+
const arg = parseUnary(tokens, pos);
|
|
278
|
+
if (arg.type === 'num')
|
|
279
|
+
return { type: 'num', value: -arg.value };
|
|
280
|
+
return { type: 'neg', arg };
|
|
281
|
+
}
|
|
282
|
+
if (pos.i < tokens.length && tokens[pos.i] === '+') {
|
|
283
|
+
pos.i++;
|
|
284
|
+
return parseUnary(tokens, pos);
|
|
285
|
+
}
|
|
286
|
+
return parsePower(tokens, pos);
|
|
287
|
+
}
|
|
288
|
+
/** power → atom (^ unary)? */
|
|
289
|
+
function parsePower(tokens, pos) {
|
|
290
|
+
let base = parseAtom(tokens, pos);
|
|
291
|
+
if (pos.i < tokens.length && tokens[pos.i] === '^') {
|
|
292
|
+
pos.i++;
|
|
293
|
+
const exp = parseUnary(tokens, pos); // right-associative
|
|
294
|
+
base = { type: 'pow', base, exp };
|
|
295
|
+
}
|
|
296
|
+
return base;
|
|
297
|
+
}
|
|
298
|
+
/** atom → number | fn(expr) | var | (expr) */
|
|
299
|
+
function parseAtom(tokens, pos) {
|
|
300
|
+
if (pos.i >= tokens.length)
|
|
301
|
+
throw new Error('Unexpected end of expression');
|
|
302
|
+
const tok = tokens[pos.i];
|
|
303
|
+
// Number
|
|
304
|
+
if (/^[0-9]/.test(tok) || (tok === '.' && pos.i + 1 < tokens.length)) {
|
|
305
|
+
pos.i++;
|
|
306
|
+
return { type: 'num', value: parseFloat(tok) };
|
|
307
|
+
}
|
|
308
|
+
// Named constant
|
|
309
|
+
if (tok === 'pi' || tok === 'PI') {
|
|
310
|
+
pos.i++;
|
|
311
|
+
return { type: 'num', value: Math.PI };
|
|
312
|
+
}
|
|
313
|
+
if (tok === 'e' && (pos.i + 1 >= tokens.length || tokens[pos.i + 1] !== '(')) {
|
|
314
|
+
// Only treat 'e' as Euler's number if not followed by '(' (which would be a function)
|
|
315
|
+
if (pos.i + 1 >= tokens.length || !(/[a-zA-Z]/.test(tokens[pos.i + 1]))) {
|
|
316
|
+
pos.i++;
|
|
317
|
+
return { type: 'num', value: Math.E };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Function call
|
|
321
|
+
if (KNOWN_FNS.has(tok) && pos.i + 1 < tokens.length && tokens[pos.i + 1] === '(') {
|
|
322
|
+
const fnName = tok === 'ln' ? 'log' : tok;
|
|
323
|
+
pos.i += 2; // skip name + (
|
|
324
|
+
const arg = parseExpr(tokens, pos);
|
|
325
|
+
if (pos.i < tokens.length && tokens[pos.i] === ')')
|
|
326
|
+
pos.i++;
|
|
327
|
+
return { type: 'fn', name: fnName, arg };
|
|
328
|
+
}
|
|
329
|
+
// Parenthesized expression
|
|
330
|
+
if (tok === '(') {
|
|
331
|
+
pos.i++;
|
|
332
|
+
const inner = parseExpr(tokens, pos);
|
|
333
|
+
if (pos.i < tokens.length && tokens[pos.i] === ')')
|
|
334
|
+
pos.i++;
|
|
335
|
+
return inner;
|
|
336
|
+
}
|
|
337
|
+
// Variable
|
|
338
|
+
if (/^[a-zA-Z_]/.test(tok)) {
|
|
339
|
+
pos.i++;
|
|
340
|
+
return { type: 'var', name: tok };
|
|
341
|
+
}
|
|
342
|
+
throw new Error(`Unexpected token: ${tok}`);
|
|
343
|
+
}
|
|
344
|
+
function parse(expr) {
|
|
345
|
+
const tokens = tokenize(expr);
|
|
346
|
+
const pos = { i: 0 };
|
|
347
|
+
const result = parseExpr(tokens, pos);
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
/** Symbolic differentiation */
|
|
351
|
+
function differentiate(expr, v) {
|
|
352
|
+
switch (expr.type) {
|
|
353
|
+
case 'num': return { type: 'num', value: 0 };
|
|
354
|
+
case 'var': return { type: 'num', value: expr.name === v ? 1 : 0 };
|
|
355
|
+
case 'neg': return { type: 'neg', arg: differentiate(expr.arg, v) };
|
|
356
|
+
case 'add': return { type: 'add', left: differentiate(expr.left, v), right: differentiate(expr.right, v) };
|
|
357
|
+
case 'sub': return { type: 'sub', left: differentiate(expr.left, v), right: differentiate(expr.right, v) };
|
|
358
|
+
case 'mul': {
|
|
359
|
+
// Product rule: f'g + fg'
|
|
360
|
+
const f = expr.left, g = expr.right;
|
|
361
|
+
const df = differentiate(f, v), dg = differentiate(g, v);
|
|
362
|
+
return { type: 'add', left: { type: 'mul', left: df, right: g }, right: { type: 'mul', left: f, right: dg } };
|
|
363
|
+
}
|
|
364
|
+
case 'div': {
|
|
365
|
+
// Quotient rule: (f'g - fg') / g^2
|
|
366
|
+
const f = expr.left, g = expr.right;
|
|
367
|
+
const df = differentiate(f, v), dg = differentiate(g, v);
|
|
368
|
+
return {
|
|
369
|
+
type: 'div',
|
|
370
|
+
left: { type: 'sub', left: { type: 'mul', left: df, right: g }, right: { type: 'mul', left: f, right: dg } },
|
|
371
|
+
right: { type: 'pow', base: g, exp: { type: 'num', value: 2 } },
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
case 'pow': {
|
|
375
|
+
const base = expr.base, exp = expr.exp;
|
|
376
|
+
const baseHasVar = containsVar(base, v);
|
|
377
|
+
const expHasVar = containsVar(exp, v);
|
|
378
|
+
if (!baseHasVar && !expHasVar)
|
|
379
|
+
return { type: 'num', value: 0 };
|
|
380
|
+
if (!expHasVar) {
|
|
381
|
+
// Power rule: n * f^(n-1) * f'
|
|
382
|
+
return {
|
|
383
|
+
type: 'mul', left: { type: 'mul', left: exp, right: { type: 'pow', base, exp: { type: 'sub', left: exp, right: { type: 'num', value: 1 } } } },
|
|
384
|
+
right: differentiate(base, v),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
if (!baseHasVar) {
|
|
388
|
+
// a^g(x) = a^g(x) * ln(a) * g'(x)
|
|
389
|
+
return {
|
|
390
|
+
type: 'mul', left: { type: 'mul', left: expr, right: { type: 'fn', name: 'log', arg: base } },
|
|
391
|
+
right: differentiate(exp, v),
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
// General case f(x)^g(x) = e^(g*ln(f)) → differentiate that
|
|
395
|
+
const lnForm = { type: 'pow', base: { type: 'num', value: Math.E }, exp: { type: 'mul', left: exp, right: { type: 'fn', name: 'log', arg: base } } };
|
|
396
|
+
return differentiate(lnForm, v);
|
|
397
|
+
}
|
|
398
|
+
case 'fn': {
|
|
399
|
+
const arg = expr.arg;
|
|
400
|
+
const da = differentiate(arg, v);
|
|
401
|
+
let inner;
|
|
402
|
+
switch (expr.name) {
|
|
403
|
+
case 'sin':
|
|
404
|
+
inner = { type: 'fn', name: 'cos', arg };
|
|
405
|
+
break;
|
|
406
|
+
case 'cos':
|
|
407
|
+
inner = { type: 'neg', arg: { type: 'fn', name: 'sin', arg } };
|
|
408
|
+
break;
|
|
409
|
+
case 'tan':
|
|
410
|
+
inner = { type: 'pow', base: { type: 'fn', name: 'cos', arg }, exp: { type: 'num', value: -2 } };
|
|
411
|
+
break;
|
|
412
|
+
case 'exp':
|
|
413
|
+
inner = expr;
|
|
414
|
+
break;
|
|
415
|
+
case 'log':
|
|
416
|
+
inner = { type: 'div', left: { type: 'num', value: 1 }, right: arg };
|
|
417
|
+
break;
|
|
418
|
+
case 'sqrt':
|
|
419
|
+
inner = { type: 'div', left: { type: 'num', value: 1 }, right: { type: 'mul', left: { type: 'num', value: 2 }, right: { type: 'fn', name: 'sqrt', arg } } };
|
|
420
|
+
break;
|
|
421
|
+
case 'asin':
|
|
422
|
+
inner = { type: 'div', left: { type: 'num', value: 1 }, right: { type: 'fn', name: 'sqrt', arg: { type: 'sub', left: { type: 'num', value: 1 }, right: { type: 'pow', base: arg, exp: { type: 'num', value: 2 } } } } };
|
|
423
|
+
break;
|
|
424
|
+
case 'acos':
|
|
425
|
+
inner = { type: 'neg', arg: { type: 'div', left: { type: 'num', value: 1 }, right: { type: 'fn', name: 'sqrt', arg: { type: 'sub', left: { type: 'num', value: 1 }, right: { type: 'pow', base: arg, exp: { type: 'num', value: 2 } } } } } };
|
|
426
|
+
break;
|
|
427
|
+
case 'atan':
|
|
428
|
+
inner = { type: 'div', left: { type: 'num', value: 1 }, right: { type: 'add', left: { type: 'num', value: 1 }, right: { type: 'pow', base: arg, exp: { type: 'num', value: 2 } } } };
|
|
429
|
+
break;
|
|
430
|
+
case 'sinh':
|
|
431
|
+
inner = { type: 'fn', name: 'cosh', arg };
|
|
432
|
+
break;
|
|
433
|
+
case 'cosh':
|
|
434
|
+
inner = { type: 'fn', name: 'sinh', arg };
|
|
435
|
+
break;
|
|
436
|
+
case 'tanh':
|
|
437
|
+
inner = { type: 'sub', left: { type: 'num', value: 1 }, right: { type: 'pow', base: { type: 'fn', name: 'tanh', arg }, exp: { type: 'num', value: 2 } } };
|
|
438
|
+
break;
|
|
439
|
+
default: throw new Error(`Cannot differentiate function: ${expr.name}`);
|
|
440
|
+
}
|
|
441
|
+
// Chain rule
|
|
442
|
+
return { type: 'mul', left: inner, right: da };
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function containsVar(expr, v) {
|
|
447
|
+
switch (expr.type) {
|
|
448
|
+
case 'num': return false;
|
|
449
|
+
case 'var': return expr.name === v;
|
|
450
|
+
case 'neg': return containsVar(expr.arg, v);
|
|
451
|
+
case 'fn': return containsVar(expr.arg, v);
|
|
452
|
+
case 'add':
|
|
453
|
+
case 'sub':
|
|
454
|
+
case 'mul':
|
|
455
|
+
case 'div':
|
|
456
|
+
return containsVar(expr.left, v) || containsVar(expr.right, v);
|
|
457
|
+
case 'pow': return containsVar(expr.base, v) || containsVar(expr.exp, v);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/** Simplify an expression tree (basic algebraic rules) */
|
|
461
|
+
function simplify(expr) {
|
|
462
|
+
switch (expr.type) {
|
|
463
|
+
case 'num':
|
|
464
|
+
case 'var': return expr;
|
|
465
|
+
case 'neg': {
|
|
466
|
+
const a = simplify(expr.arg);
|
|
467
|
+
if (a.type === 'num')
|
|
468
|
+
return { type: 'num', value: -a.value };
|
|
469
|
+
if (a.type === 'neg')
|
|
470
|
+
return a.arg;
|
|
471
|
+
return { type: 'neg', arg: a };
|
|
472
|
+
}
|
|
473
|
+
case 'add': {
|
|
474
|
+
const l = simplify(expr.left), r = simplify(expr.right);
|
|
475
|
+
if (l.type === 'num' && r.type === 'num')
|
|
476
|
+
return { type: 'num', value: l.value + r.value };
|
|
477
|
+
if (l.type === 'num' && l.value === 0)
|
|
478
|
+
return r;
|
|
479
|
+
if (r.type === 'num' && r.value === 0)
|
|
480
|
+
return l;
|
|
481
|
+
return { type: 'add', left: l, right: r };
|
|
482
|
+
}
|
|
483
|
+
case 'sub': {
|
|
484
|
+
const l = simplify(expr.left), r = simplify(expr.right);
|
|
485
|
+
if (l.type === 'num' && r.type === 'num')
|
|
486
|
+
return { type: 'num', value: l.value - r.value };
|
|
487
|
+
if (r.type === 'num' && r.value === 0)
|
|
488
|
+
return l;
|
|
489
|
+
if (l.type === 'num' && l.value === 0)
|
|
490
|
+
return { type: 'neg', arg: r };
|
|
491
|
+
return { type: 'sub', left: l, right: r };
|
|
492
|
+
}
|
|
493
|
+
case 'mul': {
|
|
494
|
+
const l = simplify(expr.left), r = simplify(expr.right);
|
|
495
|
+
if (l.type === 'num' && r.type === 'num')
|
|
496
|
+
return { type: 'num', value: l.value * r.value };
|
|
497
|
+
if (l.type === 'num' && l.value === 0)
|
|
498
|
+
return { type: 'num', value: 0 };
|
|
499
|
+
if (r.type === 'num' && r.value === 0)
|
|
500
|
+
return { type: 'num', value: 0 };
|
|
501
|
+
if (l.type === 'num' && l.value === 1)
|
|
502
|
+
return r;
|
|
503
|
+
if (r.type === 'num' && r.value === 1)
|
|
504
|
+
return l;
|
|
505
|
+
return { type: 'mul', left: l, right: r };
|
|
506
|
+
}
|
|
507
|
+
case 'div': {
|
|
508
|
+
const l = simplify(expr.left), r = simplify(expr.right);
|
|
509
|
+
if (l.type === 'num' && r.type === 'num' && r.value !== 0)
|
|
510
|
+
return { type: 'num', value: l.value / r.value };
|
|
511
|
+
if (l.type === 'num' && l.value === 0)
|
|
512
|
+
return { type: 'num', value: 0 };
|
|
513
|
+
if (r.type === 'num' && r.value === 1)
|
|
514
|
+
return l;
|
|
515
|
+
return { type: 'div', left: l, right: r };
|
|
516
|
+
}
|
|
517
|
+
case 'pow': {
|
|
518
|
+
const b = simplify(expr.base), e = simplify(expr.exp);
|
|
519
|
+
if (b.type === 'num' && e.type === 'num')
|
|
520
|
+
return { type: 'num', value: Math.pow(b.value, e.value) };
|
|
521
|
+
if (e.type === 'num' && e.value === 0)
|
|
522
|
+
return { type: 'num', value: 1 };
|
|
523
|
+
if (e.type === 'num' && e.value === 1)
|
|
524
|
+
return b;
|
|
525
|
+
return { type: 'pow', base: b, exp: e };
|
|
526
|
+
}
|
|
527
|
+
case 'fn': {
|
|
528
|
+
const a = simplify(expr.arg);
|
|
529
|
+
if (a.type === 'num') {
|
|
530
|
+
const fns = {
|
|
531
|
+
sin: Math.sin, cos: Math.cos, tan: Math.tan, exp: Math.exp,
|
|
532
|
+
log: Math.log, sqrt: Math.sqrt, abs: Math.abs,
|
|
533
|
+
asin: Math.asin, acos: Math.acos, atan: Math.atan,
|
|
534
|
+
sinh: Math.sinh, cosh: Math.cosh, tanh: Math.tanh,
|
|
535
|
+
};
|
|
536
|
+
if (fns[expr.name])
|
|
537
|
+
return { type: 'num', value: fns[expr.name](a.value) };
|
|
538
|
+
}
|
|
539
|
+
return { type: 'fn', name: expr.name, arg: a };
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/** Apply simplification repeatedly until stable */
|
|
544
|
+
function deepSimplify(expr, maxIter = 20) {
|
|
545
|
+
let prev = exprToString(expr);
|
|
546
|
+
let cur = expr;
|
|
547
|
+
for (let i = 0; i < maxIter; i++) {
|
|
548
|
+
cur = simplify(cur);
|
|
549
|
+
const s = exprToString(cur);
|
|
550
|
+
if (s === prev)
|
|
551
|
+
break;
|
|
552
|
+
prev = s;
|
|
553
|
+
}
|
|
554
|
+
return cur;
|
|
555
|
+
}
|
|
556
|
+
/** Convert expression tree back to string */
|
|
557
|
+
function exprToString(expr) {
|
|
558
|
+
switch (expr.type) {
|
|
559
|
+
case 'num': return fmt(expr.value);
|
|
560
|
+
case 'var': return expr.name;
|
|
561
|
+
case 'neg': {
|
|
562
|
+
const s = exprToString(expr.arg);
|
|
563
|
+
return expr.arg.type === 'add' || expr.arg.type === 'sub' ? `-(${s})` : `-${s}`;
|
|
564
|
+
}
|
|
565
|
+
case 'add': return `${exprToString(expr.left)} + ${exprToString(expr.right)}`;
|
|
566
|
+
case 'sub': {
|
|
567
|
+
const rs = exprToString(expr.right);
|
|
568
|
+
const r = expr.right.type === 'add' || expr.right.type === 'sub' ? `(${rs})` : rs;
|
|
569
|
+
return `${exprToString(expr.left)} - ${r}`;
|
|
570
|
+
}
|
|
571
|
+
case 'mul': {
|
|
572
|
+
const ls = needsParensMul(expr.left) ? `(${exprToString(expr.left)})` : exprToString(expr.left);
|
|
573
|
+
const rs = needsParensMul(expr.right) ? `(${exprToString(expr.right)})` : exprToString(expr.right);
|
|
574
|
+
return `${ls} * ${rs}`;
|
|
575
|
+
}
|
|
576
|
+
case 'div': {
|
|
577
|
+
const ls = needsParensMul(expr.left) ? `(${exprToString(expr.left)})` : exprToString(expr.left);
|
|
578
|
+
const rs = needsParensDiv(expr.right) ? `(${exprToString(expr.right)})` : exprToString(expr.right);
|
|
579
|
+
return `${ls} / ${rs}`;
|
|
580
|
+
}
|
|
581
|
+
case 'pow': {
|
|
582
|
+
const bs = needsParensPow(expr.base) ? `(${exprToString(expr.base)})` : exprToString(expr.base);
|
|
583
|
+
const es = needsParensPow(expr.exp) ? `(${exprToString(expr.exp)})` : exprToString(expr.exp);
|
|
584
|
+
return `${bs}^${es}`;
|
|
585
|
+
}
|
|
586
|
+
case 'fn': return `${expr.name}(${exprToString(expr.arg)})`;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function needsParensMul(e) { return e.type === 'add' || e.type === 'sub'; }
|
|
590
|
+
function needsParensDiv(e) { return e.type === 'add' || e.type === 'sub' || e.type === 'mul' || e.type === 'div'; }
|
|
591
|
+
function needsParensPow(e) { return e.type === 'add' || e.type === 'sub' || e.type === 'mul' || e.type === 'div' || e.type === 'neg'; }
|
|
592
|
+
/** Evaluate expression tree numerically with variable bindings */
|
|
593
|
+
function evalExpr(expr, vars) {
|
|
594
|
+
switch (expr.type) {
|
|
595
|
+
case 'num': return expr.value;
|
|
596
|
+
case 'var': {
|
|
597
|
+
if (expr.name in vars)
|
|
598
|
+
return vars[expr.name];
|
|
599
|
+
throw new Error(`Undefined variable: ${expr.name}`);
|
|
600
|
+
}
|
|
601
|
+
case 'neg': return -evalExpr(expr.arg, vars);
|
|
602
|
+
case 'add': return evalExpr(expr.left, vars) + evalExpr(expr.right, vars);
|
|
603
|
+
case 'sub': return evalExpr(expr.left, vars) - evalExpr(expr.right, vars);
|
|
604
|
+
case 'mul': return evalExpr(expr.left, vars) * evalExpr(expr.right, vars);
|
|
605
|
+
case 'div': return evalExpr(expr.left, vars) / evalExpr(expr.right, vars);
|
|
606
|
+
case 'pow': return Math.pow(evalExpr(expr.base, vars), evalExpr(expr.exp, vars));
|
|
607
|
+
case 'fn': {
|
|
608
|
+
const a = evalExpr(expr.arg, vars);
|
|
609
|
+
const fns = {
|
|
610
|
+
sin: Math.sin, cos: Math.cos, tan: Math.tan, exp: Math.exp,
|
|
611
|
+
log: Math.log, sqrt: Math.sqrt, abs: Math.abs,
|
|
612
|
+
asin: Math.asin, acos: Math.acos, atan: Math.atan,
|
|
613
|
+
sinh: Math.sinh, cosh: Math.cosh, tanh: Math.tanh,
|
|
614
|
+
};
|
|
615
|
+
if (fns[expr.name])
|
|
616
|
+
return fns[expr.name](a);
|
|
617
|
+
throw new Error(`Unknown function: ${expr.name}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
/** Taylor series expansion of f(x) around x=a, up to order n */
|
|
622
|
+
function taylorSeries(expr, variable, a, n) {
|
|
623
|
+
const terms = [];
|
|
624
|
+
let current = expr;
|
|
625
|
+
let factorial = 1;
|
|
626
|
+
for (let k = 0; k <= n; k++) {
|
|
627
|
+
if (k > 0)
|
|
628
|
+
factorial *= k;
|
|
629
|
+
const val = evalExpr(current, { [variable]: a });
|
|
630
|
+
if (Math.abs(val) > 1e-14) {
|
|
631
|
+
const coeff = val / factorial;
|
|
632
|
+
if (k === 0) {
|
|
633
|
+
terms.push(fmt(coeff));
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
const xTerm = a === 0 ? (k === 1 ? variable : `${variable}^${k}`) : (k === 1 ? `(${variable} - ${fmt(a)})` : `(${variable} - ${fmt(a)})^${k}`);
|
|
637
|
+
const coeffStr = Math.abs(coeff) === 1 ? (coeff < 0 ? '-' : '') : fmt(coeff) + '*';
|
|
638
|
+
terms.push(`${coeffStr}${xTerm}`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
current = differentiate(current, variable);
|
|
642
|
+
current = deepSimplify(current);
|
|
643
|
+
}
|
|
644
|
+
return terms.length > 0 ? terms.join(' + ').replace(/\+ -/g, '- ') : '0';
|
|
645
|
+
}
|
|
646
|
+
function matCreate(rows, cols, fill = 0) {
|
|
647
|
+
return Array.from({ length: rows }, () => Array(cols).fill(fill));
|
|
648
|
+
}
|
|
649
|
+
function matClone(m) {
|
|
650
|
+
return m.map(row => [...row]);
|
|
651
|
+
}
|
|
652
|
+
function matRows(m) { return m.length; }
|
|
653
|
+
function matCols(m) { return m[0]?.length ?? 0; }
|
|
654
|
+
function matTranspose(m) {
|
|
655
|
+
const [r, c] = [matRows(m), matCols(m)];
|
|
656
|
+
const t = matCreate(c, r);
|
|
657
|
+
for (let i = 0; i < r; i++)
|
|
658
|
+
for (let j = 0; j < c; j++)
|
|
659
|
+
t[j][i] = m[i][j];
|
|
660
|
+
return t;
|
|
661
|
+
}
|
|
662
|
+
function matMul(a, b) {
|
|
663
|
+
const [ar, ac, bc] = [matRows(a), matCols(a), matCols(b)];
|
|
664
|
+
if (ac !== matRows(b))
|
|
665
|
+
throw new Error(`Matrix dimension mismatch: ${ar}x${ac} * ${matRows(b)}x${bc}`);
|
|
666
|
+
const result = matCreate(ar, bc);
|
|
667
|
+
for (let i = 0; i < ar; i++)
|
|
668
|
+
for (let j = 0; j < bc; j++)
|
|
669
|
+
for (let k = 0; k < ac; k++)
|
|
670
|
+
result[i][j] += a[i][k] * b[k][j];
|
|
671
|
+
return result;
|
|
672
|
+
}
|
|
673
|
+
function matAdd(a, b) {
|
|
674
|
+
const [r, c] = [matRows(a), matCols(a)];
|
|
675
|
+
if (r !== matRows(b) || c !== matCols(b))
|
|
676
|
+
throw new Error('Matrix dimension mismatch for addition');
|
|
677
|
+
const result = matCreate(r, c);
|
|
678
|
+
for (let i = 0; i < r; i++)
|
|
679
|
+
for (let j = 0; j < c; j++)
|
|
680
|
+
result[i][j] = a[i][j] + b[i][j];
|
|
681
|
+
return result;
|
|
682
|
+
}
|
|
683
|
+
function matSub(a, b) {
|
|
684
|
+
const [r, c] = [matRows(a), matCols(a)];
|
|
685
|
+
const result = matCreate(r, c);
|
|
686
|
+
for (let i = 0; i < r; i++)
|
|
687
|
+
for (let j = 0; j < c; j++)
|
|
688
|
+
result[i][j] = a[i][j] - b[i][j];
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
function matScale(m, s) {
|
|
692
|
+
return m.map(row => row.map(v => v * s));
|
|
693
|
+
}
|
|
694
|
+
function matIdentity(n) {
|
|
695
|
+
const m = matCreate(n, n);
|
|
696
|
+
for (let i = 0; i < n; i++)
|
|
697
|
+
m[i][i] = 1;
|
|
698
|
+
return m;
|
|
699
|
+
}
|
|
700
|
+
/** LU decomposition with partial pivoting. Returns { L, U, P } where PA = LU */
|
|
701
|
+
function matLU(m) {
|
|
702
|
+
const n = matRows(m);
|
|
703
|
+
if (n !== matCols(m))
|
|
704
|
+
throw new Error('LU decomposition requires a square matrix');
|
|
705
|
+
const U = matClone(m);
|
|
706
|
+
const L = matIdentity(n);
|
|
707
|
+
const P = matIdentity(n);
|
|
708
|
+
let pivotSign = 1;
|
|
709
|
+
for (let k = 0; k < n; k++) {
|
|
710
|
+
// Partial pivoting
|
|
711
|
+
let maxVal = Math.abs(U[k][k]), maxRow = k;
|
|
712
|
+
for (let i = k + 1; i < n; i++) {
|
|
713
|
+
if (Math.abs(U[i][k]) > maxVal) {
|
|
714
|
+
maxVal = Math.abs(U[i][k]);
|
|
715
|
+
maxRow = i;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
if (maxRow !== k) {
|
|
719
|
+
[U[k], U[maxRow]] = [U[maxRow], U[k]];
|
|
720
|
+
[P[k], P[maxRow]] = [P[maxRow], P[k]];
|
|
721
|
+
pivotSign *= -1;
|
|
722
|
+
// Swap L below diagonal
|
|
723
|
+
for (let j = 0; j < k; j++) {
|
|
724
|
+
[L[k][j], L[maxRow][j]] = [L[maxRow][j], L[k][j]];
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (Math.abs(U[k][k]) < 1e-14)
|
|
728
|
+
continue; // singular
|
|
729
|
+
for (let i = k + 1; i < n; i++) {
|
|
730
|
+
const factor = U[i][k] / U[k][k];
|
|
731
|
+
L[i][k] = factor;
|
|
732
|
+
for (let j = k; j < n; j++) {
|
|
733
|
+
U[i][j] -= factor * U[k][j];
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return { L, U, P, pivotSign };
|
|
738
|
+
}
|
|
739
|
+
/** Determinant via LU decomposition */
|
|
740
|
+
function matDet(m) {
|
|
741
|
+
const n = matRows(m);
|
|
742
|
+
if (n !== matCols(m))
|
|
743
|
+
throw new Error('Determinant requires a square matrix');
|
|
744
|
+
if (n === 1)
|
|
745
|
+
return m[0][0];
|
|
746
|
+
if (n === 2)
|
|
747
|
+
return m[0][0] * m[1][1] - m[0][1] * m[1][0];
|
|
748
|
+
const { U, pivotSign } = matLU(m);
|
|
749
|
+
let det = pivotSign;
|
|
750
|
+
for (let i = 0; i < n; i++)
|
|
751
|
+
det *= U[i][i];
|
|
752
|
+
return det;
|
|
753
|
+
}
|
|
754
|
+
/** Matrix inverse via Gauss-Jordan elimination */
|
|
755
|
+
function matInverse(m) {
|
|
756
|
+
const n = matRows(m);
|
|
757
|
+
if (n !== matCols(m))
|
|
758
|
+
throw new Error('Inverse requires a square matrix');
|
|
759
|
+
// Augmented matrix [m | I]
|
|
760
|
+
const aug = m.map((row, i) => {
|
|
761
|
+
const ext = Array(n).fill(0);
|
|
762
|
+
ext[i] = 1;
|
|
763
|
+
return [...row, ...ext];
|
|
764
|
+
});
|
|
765
|
+
// Forward elimination
|
|
766
|
+
for (let k = 0; k < n; k++) {
|
|
767
|
+
let maxVal = Math.abs(aug[k][k]), maxRow = k;
|
|
768
|
+
for (let i = k + 1; i < n; i++) {
|
|
769
|
+
if (Math.abs(aug[i][k]) > maxVal) {
|
|
770
|
+
maxVal = Math.abs(aug[i][k]);
|
|
771
|
+
maxRow = i;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (maxVal < 1e-14)
|
|
775
|
+
throw new Error('Matrix is singular — no inverse exists');
|
|
776
|
+
if (maxRow !== k)
|
|
777
|
+
[aug[k], aug[maxRow]] = [aug[maxRow], aug[k]];
|
|
778
|
+
const pivot = aug[k][k];
|
|
779
|
+
for (let j = 0; j < 2 * n; j++)
|
|
780
|
+
aug[k][j] /= pivot;
|
|
781
|
+
for (let i = 0; i < n; i++) {
|
|
782
|
+
if (i === k)
|
|
783
|
+
continue;
|
|
784
|
+
const factor = aug[i][k];
|
|
785
|
+
for (let j = 0; j < 2 * n; j++)
|
|
786
|
+
aug[i][j] -= factor * aug[k][j];
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return aug.map(row => row.slice(n));
|
|
790
|
+
}
|
|
791
|
+
/** Matrix rank via row echelon form */
|
|
792
|
+
function matRank(m) {
|
|
793
|
+
const r = matRows(m), c = matCols(m);
|
|
794
|
+
const a = matClone(m);
|
|
795
|
+
let rank = 0;
|
|
796
|
+
for (let col = 0; col < c && rank < r; col++) {
|
|
797
|
+
let maxRow = rank, maxVal = Math.abs(a[rank][col]);
|
|
798
|
+
for (let i = rank + 1; i < r; i++) {
|
|
799
|
+
if (Math.abs(a[i][col]) > maxVal) {
|
|
800
|
+
maxVal = Math.abs(a[i][col]);
|
|
801
|
+
maxRow = i;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (maxVal < 1e-12)
|
|
805
|
+
continue;
|
|
806
|
+
[a[rank], a[maxRow]] = [a[maxRow], a[rank]];
|
|
807
|
+
const pivot = a[rank][col];
|
|
808
|
+
for (let j = col; j < c; j++)
|
|
809
|
+
a[rank][j] /= pivot;
|
|
810
|
+
for (let i = 0; i < r; i++) {
|
|
811
|
+
if (i === rank)
|
|
812
|
+
continue;
|
|
813
|
+
const factor = a[i][col];
|
|
814
|
+
for (let j = col; j < c; j++)
|
|
815
|
+
a[i][j] -= factor * a[rank][j];
|
|
816
|
+
}
|
|
817
|
+
rank++;
|
|
818
|
+
}
|
|
819
|
+
return rank;
|
|
820
|
+
}
|
|
821
|
+
/** Eigenvalues via QR iteration (for real symmetric matrices — works reasonably for general small matrices) */
|
|
822
|
+
function matEigenvalues(m, maxIter = 300) {
|
|
823
|
+
const n = matRows(m);
|
|
824
|
+
if (n !== matCols(m))
|
|
825
|
+
throw new Error('Eigenvalues require a square matrix');
|
|
826
|
+
let A = matClone(m);
|
|
827
|
+
// Shift-and-invert QR iteration
|
|
828
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
829
|
+
// Wilkinson shift
|
|
830
|
+
const shift = A[n - 1][n - 1];
|
|
831
|
+
const shiftI = matScale(matIdentity(n), shift);
|
|
832
|
+
const As = matSub(A, shiftI);
|
|
833
|
+
// QR decomposition via Gram-Schmidt
|
|
834
|
+
const { Q, R } = qrDecompose(As);
|
|
835
|
+
A = matAdd(matMul(R, Q), shiftI);
|
|
836
|
+
// Check convergence: subdiagonal elements
|
|
837
|
+
let offDiag = 0;
|
|
838
|
+
for (let i = 1; i < n; i++)
|
|
839
|
+
offDiag += Math.abs(A[i][i - 1]);
|
|
840
|
+
if (offDiag < 1e-12)
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
return Array.from({ length: n }, (_, i) => A[i][i]).sort((a, b) => b - a);
|
|
844
|
+
}
|
|
845
|
+
/** QR decomposition via modified Gram-Schmidt */
|
|
846
|
+
function qrDecompose(m) {
|
|
847
|
+
const [rows, cols] = [matRows(m), matCols(m)];
|
|
848
|
+
const Q = matCreate(rows, cols);
|
|
849
|
+
const R = matCreate(cols, cols);
|
|
850
|
+
// Column vectors
|
|
851
|
+
const vecs = [];
|
|
852
|
+
for (let j = 0; j < cols; j++) {
|
|
853
|
+
vecs.push(Array.from({ length: rows }, (_, i) => m[i][j]));
|
|
854
|
+
}
|
|
855
|
+
const u = [];
|
|
856
|
+
for (let j = 0; j < cols; j++) {
|
|
857
|
+
let v = [...vecs[j]];
|
|
858
|
+
for (let k = 0; k < j; k++) {
|
|
859
|
+
const dot = v.reduce((s, vi, i) => s + vi * u[k][i], 0);
|
|
860
|
+
R[k][j] = dot;
|
|
861
|
+
v = v.map((vi, i) => vi - dot * u[k][i]);
|
|
862
|
+
}
|
|
863
|
+
const norm = Math.sqrt(v.reduce((s, vi) => s + vi * vi, 0));
|
|
864
|
+
R[j][j] = norm;
|
|
865
|
+
if (norm < 1e-14) {
|
|
866
|
+
u.push(v.map(() => 0));
|
|
867
|
+
}
|
|
868
|
+
else {
|
|
869
|
+
u.push(v.map(vi => vi / norm));
|
|
870
|
+
}
|
|
871
|
+
for (let i = 0; i < rows; i++)
|
|
872
|
+
Q[i][j] = u[j][i];
|
|
873
|
+
}
|
|
874
|
+
return { Q, R };
|
|
875
|
+
}
|
|
876
|
+
/** SVD via eigendecomposition of A^T A (compact, for small matrices) */
|
|
877
|
+
function matSVD(m) {
|
|
878
|
+
const mt = matTranspose(m);
|
|
879
|
+
const ata = matMul(mt, m);
|
|
880
|
+
const eigenvals = matEigenvalues(ata);
|
|
881
|
+
const singularValues = eigenvals.map(v => Math.sqrt(Math.max(0, v)));
|
|
882
|
+
// For display purposes, return singular values sorted descending
|
|
883
|
+
singularValues.sort((a, b) => b - a);
|
|
884
|
+
return {
|
|
885
|
+
U: matCreate(matRows(m), matRows(m)), // placeholder — full U computation is expensive
|
|
886
|
+
S: singularValues,
|
|
887
|
+
V: matCreate(matCols(m), matCols(m)), // placeholder
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
function matToString(m, label) {
|
|
891
|
+
const lines = [];
|
|
892
|
+
if (label)
|
|
893
|
+
lines.push(`**${label}:**`);
|
|
894
|
+
lines.push('```');
|
|
895
|
+
const widths = Array(matCols(m)).fill(0);
|
|
896
|
+
const formatted = m.map(row => row.map((v, j) => {
|
|
897
|
+
const s = fmt(v);
|
|
898
|
+
widths[j] = Math.max(widths[j], s.length);
|
|
899
|
+
return s;
|
|
900
|
+
}));
|
|
901
|
+
for (const row of formatted) {
|
|
902
|
+
lines.push('[ ' + row.map((s, j) => s.padStart(widths[j])).join(' ') + ' ]');
|
|
903
|
+
}
|
|
904
|
+
lines.push('```');
|
|
905
|
+
return lines.join('\n');
|
|
906
|
+
}
|
|
907
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
908
|
+
// §3 NUMBER THEORY HELPERS
|
|
909
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
910
|
+
/** Modular exponentiation: base^exp mod m (BigInt) */
|
|
911
|
+
function modPow(base, exp, m) {
|
|
912
|
+
if (m === 1n)
|
|
913
|
+
return 0n;
|
|
914
|
+
let result = 1n;
|
|
915
|
+
base = ((base % m) + m) % m;
|
|
916
|
+
while (exp > 0n) {
|
|
917
|
+
if (exp & 1n)
|
|
918
|
+
result = (result * base) % m;
|
|
919
|
+
exp >>= 1n;
|
|
920
|
+
base = (base * base) % m;
|
|
921
|
+
}
|
|
922
|
+
return result;
|
|
923
|
+
}
|
|
924
|
+
/** Miller-Rabin primality test */
|
|
925
|
+
function millerRabin(n, k = 20) {
|
|
926
|
+
if (n < 2n)
|
|
927
|
+
return false;
|
|
928
|
+
if (n === 2n || n === 3n)
|
|
929
|
+
return true;
|
|
930
|
+
if (n % 2n === 0n)
|
|
931
|
+
return false;
|
|
932
|
+
// Write n-1 as 2^r * d
|
|
933
|
+
let d = n - 1n, r = 0n;
|
|
934
|
+
while (d % 2n === 0n) {
|
|
935
|
+
d >>= 1n;
|
|
936
|
+
r++;
|
|
937
|
+
}
|
|
938
|
+
// Deterministic witnesses for small values
|
|
939
|
+
const witnesses = n < 3215031751n
|
|
940
|
+
? [2n, 3n, 5n, 7n]
|
|
941
|
+
: n < 3317044064679887385961981n
|
|
942
|
+
? [2n, 3n, 5n, 7n, 11n, 13n, 17n, 19n, 23n, 29n, 31n, 37n]
|
|
943
|
+
: [];
|
|
944
|
+
const testWitnesses = witnesses.length > 0 ? witnesses : (() => {
|
|
945
|
+
const ws = [];
|
|
946
|
+
for (let i = 0; i < k; i++) {
|
|
947
|
+
// Random witness in [2, n-2]
|
|
948
|
+
const w = BigInt(Math.floor(Math.random() * Number(n < 1000000n ? n - 4n : 999996n))) + 2n;
|
|
949
|
+
ws.push(w);
|
|
950
|
+
}
|
|
951
|
+
return ws;
|
|
952
|
+
})();
|
|
953
|
+
outer: for (const a of testWitnesses) {
|
|
954
|
+
if (a >= n)
|
|
955
|
+
continue;
|
|
956
|
+
let x = modPow(a, d, n);
|
|
957
|
+
if (x === 1n || x === n - 1n)
|
|
958
|
+
continue;
|
|
959
|
+
for (let i = 0n; i < r - 1n; i++) {
|
|
960
|
+
x = (x * x) % n;
|
|
961
|
+
if (x === n - 1n)
|
|
962
|
+
continue outer;
|
|
963
|
+
}
|
|
964
|
+
return false; // composite
|
|
965
|
+
}
|
|
966
|
+
return true;
|
|
967
|
+
}
|
|
968
|
+
/** Trial division up to sqrt(n) */
|
|
969
|
+
function trialDivision(n) {
|
|
970
|
+
if (n <= 1n)
|
|
971
|
+
return [];
|
|
972
|
+
const factors = [];
|
|
973
|
+
while (n % 2n === 0n) {
|
|
974
|
+
factors.push(2n);
|
|
975
|
+
n /= 2n;
|
|
976
|
+
}
|
|
977
|
+
let d = 3n;
|
|
978
|
+
while (d * d <= n) {
|
|
979
|
+
while (n % d === 0n) {
|
|
980
|
+
factors.push(d);
|
|
981
|
+
n /= d;
|
|
982
|
+
}
|
|
983
|
+
d += 2n;
|
|
984
|
+
}
|
|
985
|
+
if (n > 1n)
|
|
986
|
+
factors.push(n);
|
|
987
|
+
return factors;
|
|
988
|
+
}
|
|
989
|
+
/** Pollard's rho factorization */
|
|
990
|
+
function pollardRho(n) {
|
|
991
|
+
if (n % 2n === 0n)
|
|
992
|
+
return 2n;
|
|
993
|
+
let x = BigInt(Math.floor(Math.random() * Number(n < 1000000n ? n : 1000000n))) + 2n;
|
|
994
|
+
let y = x;
|
|
995
|
+
let c = BigInt(Math.floor(Math.random() * Number(n < 1000000n ? n : 1000000n))) + 1n;
|
|
996
|
+
let d = 1n;
|
|
997
|
+
const gcd = (a, b) => {
|
|
998
|
+
a = a < 0n ? -a : a;
|
|
999
|
+
b = b < 0n ? -b : b;
|
|
1000
|
+
while (b) {
|
|
1001
|
+
[a, b] = [b, a % b];
|
|
1002
|
+
}
|
|
1003
|
+
return a;
|
|
1004
|
+
};
|
|
1005
|
+
while (d === 1n) {
|
|
1006
|
+
x = (x * x + c) % n;
|
|
1007
|
+
y = (y * y + c) % n;
|
|
1008
|
+
y = (y * y + c) % n;
|
|
1009
|
+
d = gcd(x > y ? x - y : y - x, n);
|
|
1010
|
+
}
|
|
1011
|
+
return d === n ? pollardRho(n) : d; // retry if trivial factor
|
|
1012
|
+
}
|
|
1013
|
+
/** Full factorization using trial division + Pollard's rho */
|
|
1014
|
+
function factorize(n) {
|
|
1015
|
+
if (n <= 1n)
|
|
1016
|
+
return [];
|
|
1017
|
+
if (millerRabin(n))
|
|
1018
|
+
return [n];
|
|
1019
|
+
// Try trial division for small factors first
|
|
1020
|
+
const smallFactors = [];
|
|
1021
|
+
let remaining = n;
|
|
1022
|
+
let d = 2n;
|
|
1023
|
+
while (d * d <= remaining && d < 100000n) {
|
|
1024
|
+
while (remaining % d === 0n) {
|
|
1025
|
+
smallFactors.push(d);
|
|
1026
|
+
remaining /= d;
|
|
1027
|
+
}
|
|
1028
|
+
d += (d === 2n ? 1n : 2n);
|
|
1029
|
+
}
|
|
1030
|
+
if (remaining <= 1n)
|
|
1031
|
+
return smallFactors;
|
|
1032
|
+
if (millerRabin(remaining))
|
|
1033
|
+
return [...smallFactors, remaining];
|
|
1034
|
+
// Pollard's rho for larger factors
|
|
1035
|
+
const stack = [remaining];
|
|
1036
|
+
const factors = [...smallFactors];
|
|
1037
|
+
let attempts = 0;
|
|
1038
|
+
while (stack.length > 0 && attempts < 100) {
|
|
1039
|
+
const val = stack.pop();
|
|
1040
|
+
if (val <= 1n)
|
|
1041
|
+
continue;
|
|
1042
|
+
if (millerRabin(val)) {
|
|
1043
|
+
factors.push(val);
|
|
1044
|
+
continue;
|
|
1045
|
+
}
|
|
1046
|
+
const factor = pollardRho(val);
|
|
1047
|
+
stack.push(factor);
|
|
1048
|
+
stack.push(val / factor);
|
|
1049
|
+
attempts++;
|
|
1050
|
+
}
|
|
1051
|
+
return factors.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
1052
|
+
}
|
|
1053
|
+
function bigGcd(a, b) {
|
|
1054
|
+
a = a < 0n ? -a : a;
|
|
1055
|
+
b = b < 0n ? -b : b;
|
|
1056
|
+
while (b) {
|
|
1057
|
+
[a, b] = [b, a % b];
|
|
1058
|
+
}
|
|
1059
|
+
return a;
|
|
1060
|
+
}
|
|
1061
|
+
function bigLcm(a, b) {
|
|
1062
|
+
if (a === 0n || b === 0n)
|
|
1063
|
+
return 0n;
|
|
1064
|
+
const aa = a < 0n ? -a : a;
|
|
1065
|
+
const bb = b < 0n ? -b : b;
|
|
1066
|
+
return (aa / bigGcd(aa, bb)) * bb;
|
|
1067
|
+
}
|
|
1068
|
+
/** Euler's totient function */
|
|
1069
|
+
function eulerTotient(n) {
|
|
1070
|
+
if (n <= 0n)
|
|
1071
|
+
return 0n;
|
|
1072
|
+
const factors = factorize(n);
|
|
1073
|
+
const primes = [...new Set(factors)];
|
|
1074
|
+
let result = n;
|
|
1075
|
+
for (const p of primes) {
|
|
1076
|
+
result = result / p * (p - 1n);
|
|
1077
|
+
}
|
|
1078
|
+
return result;
|
|
1079
|
+
}
|
|
1080
|
+
/** Chinese Remainder Theorem: solve x === r_i (mod m_i) */
|
|
1081
|
+
function chineseRemainder(remainders, moduli) {
|
|
1082
|
+
if (remainders.length !== moduli.length)
|
|
1083
|
+
throw new Error('Mismatched remainders and moduli');
|
|
1084
|
+
let M = 1n;
|
|
1085
|
+
for (const m of moduli)
|
|
1086
|
+
M *= m;
|
|
1087
|
+
let x = 0n;
|
|
1088
|
+
for (let i = 0; i < moduli.length; i++) {
|
|
1089
|
+
const Mi = M / moduli[i];
|
|
1090
|
+
// Extended GCD to find Mi^(-1) mod moduli[i]
|
|
1091
|
+
const inv = modInverse(Mi, moduli[i]);
|
|
1092
|
+
x = (x + remainders[i] * Mi * inv) % M;
|
|
1093
|
+
}
|
|
1094
|
+
return { solution: ((x % M) + M) % M, modulus: M };
|
|
1095
|
+
}
|
|
1096
|
+
function modInverse(a, m) {
|
|
1097
|
+
let [old_r, r] = [a, m];
|
|
1098
|
+
let [old_s, s] = [1n, 0n];
|
|
1099
|
+
while (r !== 0n) {
|
|
1100
|
+
const q = old_r / r;
|
|
1101
|
+
[old_r, r] = [r, old_r - q * r];
|
|
1102
|
+
[old_s, s] = [s, old_s - q * s];
|
|
1103
|
+
}
|
|
1104
|
+
if (old_r !== 1n)
|
|
1105
|
+
throw new Error(`No modular inverse: gcd(${a}, ${m}) = ${old_r}`);
|
|
1106
|
+
return ((old_s % m) + m) % m;
|
|
1107
|
+
}
|
|
1108
|
+
function parseGraph(s) {
|
|
1109
|
+
const g = safeParse(s, 'graph');
|
|
1110
|
+
if (!g.nodes || !g.edges)
|
|
1111
|
+
throw new Error('Graph must have "nodes" and "edges" arrays');
|
|
1112
|
+
return g;
|
|
1113
|
+
}
|
|
1114
|
+
function buildAdj(g, directed = false) {
|
|
1115
|
+
const adj = new Map();
|
|
1116
|
+
for (const n of g.nodes)
|
|
1117
|
+
adj.set(String(n), []);
|
|
1118
|
+
for (const e of g.edges) {
|
|
1119
|
+
const from = String(e.from), to = String(e.to), w = e.weight ?? 1;
|
|
1120
|
+
adj.get(from)?.push({ to, weight: w });
|
|
1121
|
+
if (!directed)
|
|
1122
|
+
adj.get(to)?.push({ to: from, weight: w });
|
|
1123
|
+
}
|
|
1124
|
+
return adj;
|
|
1125
|
+
}
|
|
1126
|
+
/** Dijkstra shortest path */
|
|
1127
|
+
function dijkstra(g, source, target) {
|
|
1128
|
+
const adj = buildAdj(g);
|
|
1129
|
+
const dist = new Map();
|
|
1130
|
+
const prev = new Map();
|
|
1131
|
+
const visited = new Set();
|
|
1132
|
+
for (const n of g.nodes) {
|
|
1133
|
+
dist.set(String(n), Infinity);
|
|
1134
|
+
prev.set(String(n), null);
|
|
1135
|
+
}
|
|
1136
|
+
dist.set(source, 0);
|
|
1137
|
+
// Simple priority queue (array-based, adequate for small graphs)
|
|
1138
|
+
const pq = [[0, source]];
|
|
1139
|
+
while (pq.length > 0) {
|
|
1140
|
+
pq.sort((a, b) => a[0] - b[0]);
|
|
1141
|
+
const [d, u] = pq.shift();
|
|
1142
|
+
if (visited.has(u))
|
|
1143
|
+
continue;
|
|
1144
|
+
visited.add(u);
|
|
1145
|
+
if (target && u === target)
|
|
1146
|
+
break;
|
|
1147
|
+
for (const { to: v, weight: w } of (adj.get(u) || [])) {
|
|
1148
|
+
const alt = d + w;
|
|
1149
|
+
if (alt < (dist.get(v) ?? Infinity)) {
|
|
1150
|
+
dist.set(v, alt);
|
|
1151
|
+
prev.set(v, u);
|
|
1152
|
+
pq.push([alt, v]);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
return { dist, prev };
|
|
1157
|
+
}
|
|
1158
|
+
function reconstructPath(prev, target) {
|
|
1159
|
+
const path = [];
|
|
1160
|
+
let cur = target;
|
|
1161
|
+
while (cur !== null) {
|
|
1162
|
+
path.unshift(cur);
|
|
1163
|
+
cur = prev.get(cur) ?? null;
|
|
1164
|
+
}
|
|
1165
|
+
return path;
|
|
1166
|
+
}
|
|
1167
|
+
/** Kruskal MST */
|
|
1168
|
+
function kruskalMST(g) {
|
|
1169
|
+
// Union-Find
|
|
1170
|
+
const parent = new Map();
|
|
1171
|
+
const rank = new Map();
|
|
1172
|
+
for (const n of g.nodes) {
|
|
1173
|
+
const s = String(n);
|
|
1174
|
+
parent.set(s, s);
|
|
1175
|
+
rank.set(s, 0);
|
|
1176
|
+
}
|
|
1177
|
+
const find = (x) => {
|
|
1178
|
+
if (parent.get(x) !== x)
|
|
1179
|
+
parent.set(x, find(parent.get(x)));
|
|
1180
|
+
return parent.get(x);
|
|
1181
|
+
};
|
|
1182
|
+
const union = (x, y) => {
|
|
1183
|
+
const rx = find(x), ry = find(y);
|
|
1184
|
+
if (rx === ry)
|
|
1185
|
+
return false;
|
|
1186
|
+
const rankX = rank.get(rx), rankY = rank.get(ry);
|
|
1187
|
+
if (rankX < rankY)
|
|
1188
|
+
parent.set(rx, ry);
|
|
1189
|
+
else if (rankX > rankY)
|
|
1190
|
+
parent.set(ry, rx);
|
|
1191
|
+
else {
|
|
1192
|
+
parent.set(ry, rx);
|
|
1193
|
+
rank.set(rx, rankX + 1);
|
|
1194
|
+
}
|
|
1195
|
+
return true;
|
|
1196
|
+
};
|
|
1197
|
+
const sorted = [...g.edges].sort((a, b) => (a.weight ?? 1) - (b.weight ?? 1));
|
|
1198
|
+
const mstEdges = [];
|
|
1199
|
+
let total = 0;
|
|
1200
|
+
for (const e of sorted) {
|
|
1201
|
+
if (union(String(e.from), String(e.to))) {
|
|
1202
|
+
mstEdges.push(e);
|
|
1203
|
+
total += e.weight ?? 1;
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
return { edges: mstEdges, totalWeight: total };
|
|
1207
|
+
}
|
|
1208
|
+
/** BFS components */
|
|
1209
|
+
function bfsComponents(g) {
|
|
1210
|
+
const adj = buildAdj(g);
|
|
1211
|
+
const visited = new Set();
|
|
1212
|
+
const components = [];
|
|
1213
|
+
for (const n of g.nodes) {
|
|
1214
|
+
const s = String(n);
|
|
1215
|
+
if (visited.has(s))
|
|
1216
|
+
continue;
|
|
1217
|
+
const comp = [];
|
|
1218
|
+
const queue = [s];
|
|
1219
|
+
visited.add(s);
|
|
1220
|
+
while (queue.length > 0) {
|
|
1221
|
+
const u = queue.shift();
|
|
1222
|
+
comp.push(u);
|
|
1223
|
+
for (const { to: v } of (adj.get(u) || [])) {
|
|
1224
|
+
if (!visited.has(v)) {
|
|
1225
|
+
visited.add(v);
|
|
1226
|
+
queue.push(v);
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
components.push(comp);
|
|
1231
|
+
}
|
|
1232
|
+
return components;
|
|
1233
|
+
}
|
|
1234
|
+
/** Topological sort (Kahn's algorithm) */
|
|
1235
|
+
function topologicalSort(g) {
|
|
1236
|
+
const adj = new Map();
|
|
1237
|
+
const inDeg = new Map();
|
|
1238
|
+
for (const n of g.nodes) {
|
|
1239
|
+
const s = String(n);
|
|
1240
|
+
adj.set(s, []);
|
|
1241
|
+
inDeg.set(s, 0);
|
|
1242
|
+
}
|
|
1243
|
+
for (const e of g.edges) {
|
|
1244
|
+
adj.get(String(e.from))?.push(String(e.to));
|
|
1245
|
+
inDeg.set(String(e.to), (inDeg.get(String(e.to)) ?? 0) + 1);
|
|
1246
|
+
}
|
|
1247
|
+
const queue = [];
|
|
1248
|
+
for (const [n, d] of inDeg)
|
|
1249
|
+
if (d === 0)
|
|
1250
|
+
queue.push(n);
|
|
1251
|
+
const order = [];
|
|
1252
|
+
while (queue.length > 0) {
|
|
1253
|
+
const u = queue.shift();
|
|
1254
|
+
order.push(u);
|
|
1255
|
+
for (const v of (adj.get(u) || [])) {
|
|
1256
|
+
const d = (inDeg.get(v) ?? 0) - 1;
|
|
1257
|
+
inDeg.set(v, d);
|
|
1258
|
+
if (d === 0)
|
|
1259
|
+
queue.push(v);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
return order.length === g.nodes.length ? order : null; // null = cycle
|
|
1263
|
+
}
|
|
1264
|
+
/** Greedy graph coloring */
|
|
1265
|
+
function greedyColoring(g) {
|
|
1266
|
+
const adj = buildAdj(g);
|
|
1267
|
+
const color = new Map();
|
|
1268
|
+
for (const n of g.nodes) {
|
|
1269
|
+
const s = String(n);
|
|
1270
|
+
const usedColors = new Set();
|
|
1271
|
+
for (const { to } of (adj.get(s) || [])) {
|
|
1272
|
+
if (color.has(to))
|
|
1273
|
+
usedColors.add(color.get(to));
|
|
1274
|
+
}
|
|
1275
|
+
let c = 0;
|
|
1276
|
+
while (usedColors.has(c))
|
|
1277
|
+
c++;
|
|
1278
|
+
color.set(s, c);
|
|
1279
|
+
}
|
|
1280
|
+
return color;
|
|
1281
|
+
}
|
|
1282
|
+
/** Betweenness centrality (Brandes' algorithm) */
|
|
1283
|
+
function betweennessCentrality(g) {
|
|
1284
|
+
const adj = buildAdj(g);
|
|
1285
|
+
const nodes = g.nodes.map(String);
|
|
1286
|
+
const cb = new Map();
|
|
1287
|
+
for (const n of nodes)
|
|
1288
|
+
cb.set(n, 0);
|
|
1289
|
+
for (const s of nodes) {
|
|
1290
|
+
const stack = [];
|
|
1291
|
+
const pred = new Map();
|
|
1292
|
+
const sigma = new Map();
|
|
1293
|
+
const dist = new Map();
|
|
1294
|
+
const delta = new Map();
|
|
1295
|
+
for (const n of nodes) {
|
|
1296
|
+
pred.set(n, []);
|
|
1297
|
+
sigma.set(n, 0);
|
|
1298
|
+
dist.set(n, -1);
|
|
1299
|
+
delta.set(n, 0);
|
|
1300
|
+
}
|
|
1301
|
+
sigma.set(s, 1);
|
|
1302
|
+
dist.set(s, 0);
|
|
1303
|
+
const queue = [s];
|
|
1304
|
+
while (queue.length > 0) {
|
|
1305
|
+
const v = queue.shift();
|
|
1306
|
+
stack.push(v);
|
|
1307
|
+
for (const { to: w } of (adj.get(v) || [])) {
|
|
1308
|
+
if (dist.get(w) < 0) {
|
|
1309
|
+
queue.push(w);
|
|
1310
|
+
dist.set(w, dist.get(v) + 1);
|
|
1311
|
+
}
|
|
1312
|
+
if (dist.get(w) === dist.get(v) + 1) {
|
|
1313
|
+
sigma.set(w, sigma.get(w) + sigma.get(v));
|
|
1314
|
+
pred.get(w).push(v);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
while (stack.length > 0) {
|
|
1319
|
+
const w = stack.pop();
|
|
1320
|
+
for (const v of pred.get(w)) {
|
|
1321
|
+
delta.set(v, delta.get(v) + (sigma.get(v) / sigma.get(w)) * (1 + delta.get(w)));
|
|
1322
|
+
}
|
|
1323
|
+
if (w !== s) {
|
|
1324
|
+
cb.set(w, cb.get(w) + delta.get(w));
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
// Normalize for undirected graph
|
|
1329
|
+
for (const [n, v] of cb)
|
|
1330
|
+
cb.set(n, v / 2);
|
|
1331
|
+
return cb;
|
|
1332
|
+
}
|
|
1333
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1334
|
+
// §5 COMBINATORICS HELPERS (BigInt)
|
|
1335
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1336
|
+
function bigFactorial(n) {
|
|
1337
|
+
if (n < 0)
|
|
1338
|
+
throw new Error('Factorial undefined for negative numbers');
|
|
1339
|
+
let result = 1n;
|
|
1340
|
+
for (let i = 2; i <= n; i++)
|
|
1341
|
+
result *= BigInt(i);
|
|
1342
|
+
return result;
|
|
1343
|
+
}
|
|
1344
|
+
function bigPerm(n, k) {
|
|
1345
|
+
if (k < 0 || k > n)
|
|
1346
|
+
return 0n;
|
|
1347
|
+
let result = 1n;
|
|
1348
|
+
for (let i = n; i > n - k; i--)
|
|
1349
|
+
result *= BigInt(i);
|
|
1350
|
+
return result;
|
|
1351
|
+
}
|
|
1352
|
+
function bigComb(n, k) {
|
|
1353
|
+
if (k < 0 || k > n)
|
|
1354
|
+
return 0n;
|
|
1355
|
+
if (k > n - k)
|
|
1356
|
+
k = n - k;
|
|
1357
|
+
let result = 1n;
|
|
1358
|
+
for (let i = 0; i < k; i++) {
|
|
1359
|
+
result = result * BigInt(n - i) / BigInt(i + 1);
|
|
1360
|
+
}
|
|
1361
|
+
return result;
|
|
1362
|
+
}
|
|
1363
|
+
/** Catalan number C_n = C(2n,n)/(n+1) */
|
|
1364
|
+
function catalanNumber(n) {
|
|
1365
|
+
return bigComb(2 * n, n) / BigInt(n + 1);
|
|
1366
|
+
}
|
|
1367
|
+
/** Stirling numbers of the second kind S(n,k) — number of partitions of n into k non-empty subsets */
|
|
1368
|
+
function stirling2(n, k) {
|
|
1369
|
+
if (n === 0 && k === 0)
|
|
1370
|
+
return 1n;
|
|
1371
|
+
if (n === 0 || k === 0 || k > n)
|
|
1372
|
+
return 0n;
|
|
1373
|
+
// Explicit formula: S(n,k) = (1/k!) * sum_{j=0}^{k} (-1)^(k-j) * C(k,j) * j^n
|
|
1374
|
+
let sum = 0n;
|
|
1375
|
+
for (let j = 0; j <= k; j++) {
|
|
1376
|
+
const term = bigComb(k, j) * bigPowInt(BigInt(j), n);
|
|
1377
|
+
if ((k - j) % 2 === 0)
|
|
1378
|
+
sum += term;
|
|
1379
|
+
else
|
|
1380
|
+
sum -= term;
|
|
1381
|
+
}
|
|
1382
|
+
return sum / bigFactorial(k);
|
|
1383
|
+
}
|
|
1384
|
+
function bigPowInt(base, exp) {
|
|
1385
|
+
let result = 1n;
|
|
1386
|
+
let b = base;
|
|
1387
|
+
let e = exp;
|
|
1388
|
+
while (e > 0) {
|
|
1389
|
+
if (e & 1)
|
|
1390
|
+
result *= b;
|
|
1391
|
+
b *= b;
|
|
1392
|
+
e >>= 1;
|
|
1393
|
+
}
|
|
1394
|
+
return result;
|
|
1395
|
+
}
|
|
1396
|
+
/** Bell number B_n = sum_{k=0}^{n} S(n,k) */
|
|
1397
|
+
function bellNumber(n) {
|
|
1398
|
+
let sum = 0n;
|
|
1399
|
+
for (let k = 0; k <= n; k++)
|
|
1400
|
+
sum += stirling2(n, k);
|
|
1401
|
+
return sum;
|
|
1402
|
+
}
|
|
1403
|
+
/** Integer partition number p(n) via dynamic programming */
|
|
1404
|
+
function partitionNumber(n) {
|
|
1405
|
+
if (n < 0)
|
|
1406
|
+
return 0n;
|
|
1407
|
+
const dp = Array(n + 1).fill(0n);
|
|
1408
|
+
dp[0] = 1n;
|
|
1409
|
+
for (let i = 1; i <= n; i++) {
|
|
1410
|
+
for (let j = i; j <= n; j++) {
|
|
1411
|
+
dp[j] += dp[j - i];
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
return dp[n];
|
|
1415
|
+
}
|
|
1416
|
+
/** Derangement number D_n = n! * sum_{k=0}^{n} (-1)^k / k! */
|
|
1417
|
+
function derangement(n) {
|
|
1418
|
+
if (n === 0)
|
|
1419
|
+
return 1n;
|
|
1420
|
+
if (n === 1)
|
|
1421
|
+
return 0n;
|
|
1422
|
+
// Use recurrence: D(n) = (n-1)(D(n-1) + D(n-2))
|
|
1423
|
+
let prev2 = 1n; // D(0)
|
|
1424
|
+
let prev1 = 0n; // D(1)
|
|
1425
|
+
for (let i = 2; i <= n; i++) {
|
|
1426
|
+
const cur = BigInt(i - 1) * (prev1 + prev2);
|
|
1427
|
+
prev2 = prev1;
|
|
1428
|
+
prev1 = cur;
|
|
1429
|
+
}
|
|
1430
|
+
return prev1;
|
|
1431
|
+
}
|
|
1432
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1433
|
+
// §6 DIFFERENTIAL EQUATIONS HELPERS
|
|
1434
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1435
|
+
/** Parse a math expression string and return an evaluable function of (x, y) */
|
|
1436
|
+
function parseODEFunc(eqStr) {
|
|
1437
|
+
const tree = parse(eqStr);
|
|
1438
|
+
return (x, y) => evalExpr(tree, { x, y, t: x });
|
|
1439
|
+
}
|
|
1440
|
+
/** Euler method for dy/dx = f(x, y) */
|
|
1441
|
+
function eulerMethod(f, x0, y0, xEnd, h) {
|
|
1442
|
+
const xs = [x0];
|
|
1443
|
+
const ys = [y0];
|
|
1444
|
+
let x = x0, y = y0;
|
|
1445
|
+
const steps = Math.ceil((xEnd - x0) / h);
|
|
1446
|
+
for (let i = 0; i < steps; i++) {
|
|
1447
|
+
y = y + h * f(x, y);
|
|
1448
|
+
x = x + h;
|
|
1449
|
+
xs.push(x);
|
|
1450
|
+
ys.push(y);
|
|
1451
|
+
}
|
|
1452
|
+
return { x: xs, y: ys };
|
|
1453
|
+
}
|
|
1454
|
+
/** Classical RK4 */
|
|
1455
|
+
function rk4Method(f, x0, y0, xEnd, h) {
|
|
1456
|
+
const xs = [x0];
|
|
1457
|
+
const ys = [y0];
|
|
1458
|
+
let x = x0, y = y0;
|
|
1459
|
+
const steps = Math.ceil((xEnd - x0) / h);
|
|
1460
|
+
for (let i = 0; i < steps; i++) {
|
|
1461
|
+
const k1 = h * f(x, y);
|
|
1462
|
+
const k2 = h * f(x + h / 2, y + k1 / 2);
|
|
1463
|
+
const k3 = h * f(x + h / 2, y + k2 / 2);
|
|
1464
|
+
const k4 = h * f(x + h, y + k3);
|
|
1465
|
+
y = y + (k1 + 2 * k2 + 2 * k3 + k4) / 6;
|
|
1466
|
+
x = x + h;
|
|
1467
|
+
xs.push(x);
|
|
1468
|
+
ys.push(y);
|
|
1469
|
+
}
|
|
1470
|
+
return { x: xs, y: ys };
|
|
1471
|
+
}
|
|
1472
|
+
/** Adaptive RK45 (Dormand-Prince) */
|
|
1473
|
+
function rk45Method(f, x0, y0, xEnd, tol = 1e-6) {
|
|
1474
|
+
// Dormand-Prince coefficients
|
|
1475
|
+
const a2 = 1 / 5, a3 = 3 / 10, a4 = 4 / 5, a5 = 8 / 9;
|
|
1476
|
+
const b21 = 1 / 5;
|
|
1477
|
+
const b31 = 3 / 40, b32 = 9 / 40;
|
|
1478
|
+
const b41 = 44 / 45, b42 = -56 / 15, b43 = 32 / 9;
|
|
1479
|
+
const b51 = 19372 / 6561, b52 = -25360 / 2187, b53 = 64448 / 6561, b54 = -212 / 729;
|
|
1480
|
+
const b61 = 9017 / 3168, b62 = -355 / 33, b63 = 46732 / 5247, b64 = 49 / 176, b65 = -5103 / 18656;
|
|
1481
|
+
// 5th order weights
|
|
1482
|
+
const c1 = 35 / 384, c3 = 500 / 1113, c4 = 125 / 192, c5 = -2187 / 6784, c6 = 11 / 84;
|
|
1483
|
+
// 4th order weights (for error estimate)
|
|
1484
|
+
const d1 = 5179 / 57600, d3 = 7571 / 16695, d4 = 393 / 640, d5 = -92097 / 339200, d6 = 187 / 2100, d7 = 1 / 40;
|
|
1485
|
+
const xs = [x0];
|
|
1486
|
+
const ys = [y0];
|
|
1487
|
+
let x = x0, y = y0;
|
|
1488
|
+
let h = (xEnd - x0) / 100;
|
|
1489
|
+
const hMin = (xEnd - x0) * 1e-12;
|
|
1490
|
+
const hMax = (xEnd - x0) / 4;
|
|
1491
|
+
let maxSteps = 10000;
|
|
1492
|
+
while (x < xEnd - hMin && maxSteps-- > 0) {
|
|
1493
|
+
if (x + h > xEnd)
|
|
1494
|
+
h = xEnd - x;
|
|
1495
|
+
const k1 = h * f(x, y);
|
|
1496
|
+
const k2 = h * f(x + a2 * h, y + b21 * k1);
|
|
1497
|
+
const k3 = h * f(x + a3 * h, y + b31 * k1 + b32 * k2);
|
|
1498
|
+
const k4 = h * f(x + a4 * h, y + b41 * k1 + b42 * k2 + b43 * k3);
|
|
1499
|
+
const k5 = h * f(x + a5 * h, y + b51 * k1 + b52 * k2 + b53 * k3 + b54 * k4);
|
|
1500
|
+
const k6 = h * f(x + h, y + b61 * k1 + b62 * k2 + b63 * k3 + b64 * k4 + b65 * k5);
|
|
1501
|
+
// 5th order solution
|
|
1502
|
+
const y5 = y + c1 * k1 + c3 * k3 + c4 * k4 + c5 * k5 + c6 * k6;
|
|
1503
|
+
// 4th order solution
|
|
1504
|
+
const k7 = h * f(x + h, y5);
|
|
1505
|
+
const y4 = y + d1 * k1 + d3 * k3 + d4 * k4 + d5 * k5 + d6 * k6 + d7 * k7;
|
|
1506
|
+
const err = Math.abs(y5 - y4);
|
|
1507
|
+
if (err <= tol || h <= hMin) {
|
|
1508
|
+
x = x + h;
|
|
1509
|
+
y = y5;
|
|
1510
|
+
xs.push(x);
|
|
1511
|
+
ys.push(y);
|
|
1512
|
+
}
|
|
1513
|
+
// Adjust step size
|
|
1514
|
+
const s = err > 0 ? 0.84 * Math.pow(tol / err, 0.25) : 2;
|
|
1515
|
+
h = Math.min(hMax, Math.max(hMin, h * Math.min(4, Math.max(0.1, s))));
|
|
1516
|
+
}
|
|
1517
|
+
return { x: xs, y: ys };
|
|
1518
|
+
}
|
|
1519
|
+
/** Poisson PMF */
|
|
1520
|
+
function poissonPMF(k, lambda) {
|
|
1521
|
+
return Math.exp(-lambda + k * Math.log(lambda) - lgamma(k + 1));
|
|
1522
|
+
}
|
|
1523
|
+
/** Poisson CDF */
|
|
1524
|
+
function poissonCDF(k, lambda) {
|
|
1525
|
+
let sum = 0;
|
|
1526
|
+
for (let i = 0; i <= Math.floor(k); i++)
|
|
1527
|
+
sum += poissonPMF(i, lambda);
|
|
1528
|
+
return sum;
|
|
1529
|
+
}
|
|
1530
|
+
/** Binomial PMF */
|
|
1531
|
+
function binomialPMF(k, n, p) {
|
|
1532
|
+
const lnC = lgamma(n + 1) - lgamma(k + 1) - lgamma(n - k + 1);
|
|
1533
|
+
return Math.exp(lnC + k * Math.log(p) + (n - k) * Math.log(1 - p));
|
|
1534
|
+
}
|
|
1535
|
+
/** Binomial CDF */
|
|
1536
|
+
function binomialCDF(k, n, p) {
|
|
1537
|
+
let sum = 0;
|
|
1538
|
+
for (let i = 0; i <= Math.floor(k); i++)
|
|
1539
|
+
sum += binomialPMF(i, n, p);
|
|
1540
|
+
return sum;
|
|
1541
|
+
}
|
|
1542
|
+
/** Exponential PDF */
|
|
1543
|
+
function exponentialPDF(x, lambda) {
|
|
1544
|
+
return x < 0 ? 0 : lambda * Math.exp(-lambda * x);
|
|
1545
|
+
}
|
|
1546
|
+
/** Exponential CDF */
|
|
1547
|
+
function exponentialCDF(x, lambda) {
|
|
1548
|
+
return x < 0 ? 0 : 1 - Math.exp(-lambda * x);
|
|
1549
|
+
}
|
|
1550
|
+
/** Gamma PDF */
|
|
1551
|
+
function gammaPDF(x, alpha, beta) {
|
|
1552
|
+
if (x <= 0)
|
|
1553
|
+
return 0;
|
|
1554
|
+
return Math.exp((alpha - 1) * Math.log(x) - x / beta - lgamma(alpha) - alpha * Math.log(beta));
|
|
1555
|
+
}
|
|
1556
|
+
/** Gamma CDF */
|
|
1557
|
+
function gammaCDF(x, alpha, beta) {
|
|
1558
|
+
if (x <= 0)
|
|
1559
|
+
return 0;
|
|
1560
|
+
return gammaP(alpha, x / beta);
|
|
1561
|
+
}
|
|
1562
|
+
/** Chi-squared PDF = Gamma(k/2, 2) */
|
|
1563
|
+
function chi2PDF(x, k) { return gammaPDF(x, k / 2, 2); }
|
|
1564
|
+
function chi2CDF(x, k) { return gammaCDF(x, k / 2, 2); }
|
|
1565
|
+
/** Student's t PDF */
|
|
1566
|
+
function studentTPDF(x, nu) {
|
|
1567
|
+
const coeff = gammaFn((nu + 1) / 2) / (Math.sqrt(nu * Math.PI) * gammaFn(nu / 2));
|
|
1568
|
+
return coeff * Math.pow(1 + x * x / nu, -(nu + 1) / 2);
|
|
1569
|
+
}
|
|
1570
|
+
/** Student's t CDF via incomplete beta */
|
|
1571
|
+
function studentTCDF(x, nu) {
|
|
1572
|
+
const t2 = x * x;
|
|
1573
|
+
const ib = betaInc(nu / (nu + t2), nu / 2, 0.5);
|
|
1574
|
+
return x >= 0 ? 1 - 0.5 * ib : 0.5 * ib;
|
|
1575
|
+
}
|
|
1576
|
+
/** F-distribution PDF */
|
|
1577
|
+
function fDistPDF(x, d1, d2) {
|
|
1578
|
+
if (x <= 0)
|
|
1579
|
+
return 0;
|
|
1580
|
+
const num = Math.pow(d1 * x, d1) * Math.pow(d2, d2);
|
|
1581
|
+
const den = Math.pow(d1 * x + d2, d1 + d2);
|
|
1582
|
+
return Math.sqrt(num / den) / (x * betaFn(d1 / 2, d2 / 2));
|
|
1583
|
+
}
|
|
1584
|
+
/** F-distribution CDF via incomplete beta */
|
|
1585
|
+
function fDistCDF(x, d1, d2) {
|
|
1586
|
+
if (x <= 0)
|
|
1587
|
+
return 0;
|
|
1588
|
+
return betaInc(d1 * x / (d1 * x + d2), d1 / 2, d2 / 2);
|
|
1589
|
+
}
|
|
1590
|
+
/** Beta distribution PDF */
|
|
1591
|
+
function betaDistPDF(x, alpha, beta) {
|
|
1592
|
+
if (x <= 0 || x >= 1)
|
|
1593
|
+
return 0;
|
|
1594
|
+
return Math.pow(x, alpha - 1) * Math.pow(1 - x, beta - 1) / betaFn(alpha, beta);
|
|
1595
|
+
}
|
|
1596
|
+
/** Beta distribution CDF */
|
|
1597
|
+
function betaDistCDF(x, alpha, beta) {
|
|
1598
|
+
if (x <= 0)
|
|
1599
|
+
return 0;
|
|
1600
|
+
if (x >= 1)
|
|
1601
|
+
return 1;
|
|
1602
|
+
return betaInc(x, alpha, beta);
|
|
1603
|
+
}
|
|
1604
|
+
/** Weibull PDF */
|
|
1605
|
+
function weibullPDF(x, k, lambda) {
|
|
1606
|
+
if (x < 0)
|
|
1607
|
+
return 0;
|
|
1608
|
+
return (k / lambda) * Math.pow(x / lambda, k - 1) * Math.exp(-Math.pow(x / lambda, k));
|
|
1609
|
+
}
|
|
1610
|
+
/** Weibull CDF */
|
|
1611
|
+
function weibullCDF(x, k, lambda) {
|
|
1612
|
+
if (x < 0)
|
|
1613
|
+
return 0;
|
|
1614
|
+
return 1 - Math.exp(-Math.pow(x / lambda, k));
|
|
1615
|
+
}
|
|
1616
|
+
function cMul(a, b) {
|
|
1617
|
+
return { re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re };
|
|
1618
|
+
}
|
|
1619
|
+
function cAdd(a, b) {
|
|
1620
|
+
return { re: a.re + b.re, im: a.im + b.im };
|
|
1621
|
+
}
|
|
1622
|
+
function cSub(a, b) {
|
|
1623
|
+
return { re: a.re - b.re, im: a.im - b.im };
|
|
1624
|
+
}
|
|
1625
|
+
function cAbs(a) {
|
|
1626
|
+
return Math.sqrt(a.re * a.re + a.im * a.im);
|
|
1627
|
+
}
|
|
1628
|
+
/** Radix-2 Cooley-Tukey FFT (in-place, iterative) */
|
|
1629
|
+
function fft(data, inverse = false) {
|
|
1630
|
+
const n = data.length;
|
|
1631
|
+
if (n === 0)
|
|
1632
|
+
return [];
|
|
1633
|
+
// n must be power of 2
|
|
1634
|
+
if ((n & (n - 1)) !== 0)
|
|
1635
|
+
throw new Error('FFT requires power-of-2 length');
|
|
1636
|
+
// Bit-reversal permutation
|
|
1637
|
+
const result = [...data];
|
|
1638
|
+
let j = 0;
|
|
1639
|
+
for (let i = 1; i < n; i++) {
|
|
1640
|
+
let bit = n >> 1;
|
|
1641
|
+
while (j & bit) {
|
|
1642
|
+
j ^= bit;
|
|
1643
|
+
bit >>= 1;
|
|
1644
|
+
}
|
|
1645
|
+
j ^= bit;
|
|
1646
|
+
if (i < j)
|
|
1647
|
+
[result[i], result[j]] = [result[j], result[i]];
|
|
1648
|
+
}
|
|
1649
|
+
// Butterfly stages
|
|
1650
|
+
for (let len = 2; len <= n; len *= 2) {
|
|
1651
|
+
const ang = (2 * Math.PI / len) * (inverse ? -1 : 1);
|
|
1652
|
+
const wLen = { re: Math.cos(ang), im: Math.sin(ang) };
|
|
1653
|
+
for (let i = 0; i < n; i += len) {
|
|
1654
|
+
let w = { re: 1, im: 0 };
|
|
1655
|
+
for (let k = 0; k < len / 2; k++) {
|
|
1656
|
+
const u = result[i + k];
|
|
1657
|
+
const v = cMul(result[i + k + len / 2], w);
|
|
1658
|
+
result[i + k] = cAdd(u, v);
|
|
1659
|
+
result[i + k + len / 2] = cSub(u, v);
|
|
1660
|
+
w = cMul(w, wLen);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
if (inverse) {
|
|
1665
|
+
for (let i = 0; i < n; i++) {
|
|
1666
|
+
result[i].re /= n;
|
|
1667
|
+
result[i].im /= n;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return result;
|
|
1671
|
+
}
|
|
1672
|
+
/** Pad array to next power of 2 */
|
|
1673
|
+
function padToPow2(data) {
|
|
1674
|
+
let n = 1;
|
|
1675
|
+
while (n < data.length)
|
|
1676
|
+
n *= 2;
|
|
1677
|
+
const padded = [...data];
|
|
1678
|
+
while (padded.length < n)
|
|
1679
|
+
padded.push({ re: 0, im: 0 });
|
|
1680
|
+
return padded;
|
|
1681
|
+
}
|
|
1682
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1683
|
+
// §9 OPTIMIZATION HELPERS
|
|
1684
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1685
|
+
/**
|
|
1686
|
+
* Simplex method for LP: maximize c^T x subject to Ax <= b, x >= 0
|
|
1687
|
+
* Input: c (objective coefficients), A (constraint matrix), b (RHS)
|
|
1688
|
+
*/
|
|
1689
|
+
function simplex(c, A, b) {
|
|
1690
|
+
const m = A.length; // number of constraints
|
|
1691
|
+
const n = c.length; // number of decision variables
|
|
1692
|
+
// Build tableau: [A | I | b] with objective row [-c | 0 | 0]
|
|
1693
|
+
const tableau = [];
|
|
1694
|
+
for (let i = 0; i < m; i++) {
|
|
1695
|
+
const row = [...A[i]];
|
|
1696
|
+
// Slack variables
|
|
1697
|
+
for (let j = 0; j < m; j++)
|
|
1698
|
+
row.push(i === j ? 1 : 0);
|
|
1699
|
+
row.push(b[i]);
|
|
1700
|
+
tableau.push(row);
|
|
1701
|
+
}
|
|
1702
|
+
// Objective row
|
|
1703
|
+
const objRow = c.map(v => -v);
|
|
1704
|
+
for (let j = 0; j < m; j++)
|
|
1705
|
+
objRow.push(0);
|
|
1706
|
+
objRow.push(0);
|
|
1707
|
+
tableau.push(objRow);
|
|
1708
|
+
const totalCols = n + m + 1;
|
|
1709
|
+
const maxIter = 1000;
|
|
1710
|
+
for (let iter = 0; iter < maxIter; iter++) {
|
|
1711
|
+
// Find pivot column: most negative in objective row
|
|
1712
|
+
let pivotCol = -1, minVal = -1e-10;
|
|
1713
|
+
for (let j = 0; j < totalCols - 1; j++) {
|
|
1714
|
+
if (tableau[m][j] < minVal) {
|
|
1715
|
+
minVal = tableau[m][j];
|
|
1716
|
+
pivotCol = j;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
if (pivotCol === -1)
|
|
1720
|
+
break; // optimal
|
|
1721
|
+
// Find pivot row: minimum ratio test
|
|
1722
|
+
let pivotRow = -1, minRatio = Infinity;
|
|
1723
|
+
for (let i = 0; i < m; i++) {
|
|
1724
|
+
if (tableau[i][pivotCol] > 1e-10) {
|
|
1725
|
+
const ratio = tableau[i][totalCols - 1] / tableau[i][pivotCol];
|
|
1726
|
+
if (ratio < minRatio) {
|
|
1727
|
+
minRatio = ratio;
|
|
1728
|
+
pivotRow = i;
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1732
|
+
if (pivotRow === -1)
|
|
1733
|
+
return { solution: [], value: Infinity, status: 'unbounded' };
|
|
1734
|
+
// Pivot
|
|
1735
|
+
const pivot = tableau[pivotRow][pivotCol];
|
|
1736
|
+
for (let j = 0; j < totalCols; j++)
|
|
1737
|
+
tableau[pivotRow][j] /= pivot;
|
|
1738
|
+
for (let i = 0; i <= m; i++) {
|
|
1739
|
+
if (i === pivotRow)
|
|
1740
|
+
continue;
|
|
1741
|
+
const factor = tableau[i][pivotCol];
|
|
1742
|
+
for (let j = 0; j < totalCols; j++)
|
|
1743
|
+
tableau[i][j] -= factor * tableau[pivotRow][j];
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
// Extract solution
|
|
1747
|
+
const solution = Array(n).fill(0);
|
|
1748
|
+
for (let j = 0; j < n; j++) {
|
|
1749
|
+
let basicRow = -1, isBasic = true;
|
|
1750
|
+
for (let i = 0; i < m; i++) {
|
|
1751
|
+
if (Math.abs(tableau[i][j] - 1) < 1e-10) {
|
|
1752
|
+
if (basicRow !== -1) {
|
|
1753
|
+
isBasic = false;
|
|
1754
|
+
break;
|
|
1755
|
+
}
|
|
1756
|
+
basicRow = i;
|
|
1757
|
+
}
|
|
1758
|
+
else if (Math.abs(tableau[i][j]) > 1e-10) {
|
|
1759
|
+
isBasic = false;
|
|
1760
|
+
break;
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
if (isBasic && basicRow !== -1)
|
|
1764
|
+
solution[j] = tableau[basicRow][totalCols - 1];
|
|
1765
|
+
}
|
|
1766
|
+
return { solution, value: tableau[m][totalCols - 1], status: 'optimal' };
|
|
1767
|
+
}
|
|
1768
|
+
/** Gradient descent for f(x) where x is a vector */
|
|
1769
|
+
function gradientDescent(fStr, vars, x0, lr, maxIter, tol) {
|
|
1770
|
+
const tree = parse(fStr);
|
|
1771
|
+
const gradTrees = vars.map(v => deepSimplify(differentiate(tree, v)));
|
|
1772
|
+
let x = [...x0];
|
|
1773
|
+
let bestVal = Infinity;
|
|
1774
|
+
let iters = 0;
|
|
1775
|
+
for (let i = 0; i < maxIter; i++) {
|
|
1776
|
+
const bindings = {};
|
|
1777
|
+
vars.forEach((v, j) => { bindings[v] = x[j]; });
|
|
1778
|
+
const val = evalExpr(tree, bindings);
|
|
1779
|
+
const grad = gradTrees.map(gt => evalExpr(gt, bindings));
|
|
1780
|
+
if (Math.abs(val - bestVal) < tol && i > 0) {
|
|
1781
|
+
iters = i;
|
|
1782
|
+
break;
|
|
1783
|
+
}
|
|
1784
|
+
bestVal = val;
|
|
1785
|
+
iters = i + 1;
|
|
1786
|
+
// Update
|
|
1787
|
+
x = x.map((xi, j) => xi - lr * grad[j]);
|
|
1788
|
+
}
|
|
1789
|
+
const finalBindings = {};
|
|
1790
|
+
vars.forEach((v, j) => { finalBindings[v] = x[j]; });
|
|
1791
|
+
return { solution: x, value: evalExpr(tree, finalBindings), iterations: iters };
|
|
1792
|
+
}
|
|
1793
|
+
/** Newton's method for finding roots of f(x) = 0 (single variable) */
|
|
1794
|
+
function newtonMethod(fStr, variable, x0, maxIter, tol) {
|
|
1795
|
+
const tree = parse(fStr);
|
|
1796
|
+
const dTree = deepSimplify(differentiate(tree, variable));
|
|
1797
|
+
let x = x0;
|
|
1798
|
+
for (let i = 0; i < maxIter; i++) {
|
|
1799
|
+
const fx = evalExpr(tree, { [variable]: x });
|
|
1800
|
+
if (Math.abs(fx) < tol)
|
|
1801
|
+
return { root: x, iterations: i + 1, converged: true };
|
|
1802
|
+
const dfx = evalExpr(dTree, { [variable]: x });
|
|
1803
|
+
if (Math.abs(dfx) < 1e-14)
|
|
1804
|
+
return { root: x, iterations: i + 1, converged: false };
|
|
1805
|
+
x = x - fx / dfx;
|
|
1806
|
+
}
|
|
1807
|
+
return { root: x, iterations: maxIter, converged: Math.abs(evalExpr(tree, { [variable]: x })) < tol };
|
|
1808
|
+
}
|
|
1809
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1810
|
+
// §10 TOOL REGISTRATION
|
|
1811
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
1812
|
+
export function registerLabMathTools() {
|
|
1813
|
+
// ── Tool 1: symbolic_compute ─────────────────────────────────────────────
|
|
1814
|
+
registerTool({
|
|
1815
|
+
name: 'symbolic_compute',
|
|
1816
|
+
description: 'Evaluate symbolic math: differentiate, integrate (basic polynomial), simplify expressions, Taylor series expansion, solve equations (Newton). Supports polynomial, trig, exp, log functions. For complex cases beyond built-in support, generates Python/SymPy code.',
|
|
1817
|
+
parameters: {
|
|
1818
|
+
expression: { type: 'string', description: 'Math expression, e.g. "x^3 + sin(x)" or "exp(x)*cos(x)"', required: true },
|
|
1819
|
+
operation: { type: 'string', description: 'Operation: differentiate | integrate | simplify | series | solve', required: true },
|
|
1820
|
+
variable: { type: 'string', description: 'Variable name (default: "x")', required: false },
|
|
1821
|
+
params: { type: 'string', description: 'JSON params, e.g. {"order": 5, "point": 0} for series, {"x0": 1} for solve', required: false },
|
|
1822
|
+
},
|
|
1823
|
+
tier: 'free',
|
|
1824
|
+
async execute(args) {
|
|
1825
|
+
const exprStr = String(args.expression);
|
|
1826
|
+
const operation = String(args.operation).toLowerCase();
|
|
1827
|
+
const variable = String(args.variable || 'x');
|
|
1828
|
+
const params = args.params ? safeParse(String(args.params), 'params') : {};
|
|
1829
|
+
try {
|
|
1830
|
+
const tree = parse(exprStr);
|
|
1831
|
+
switch (operation) {
|
|
1832
|
+
case 'differentiate':
|
|
1833
|
+
case 'diff':
|
|
1834
|
+
case 'derivative': {
|
|
1835
|
+
const order = Number(params.order ?? 1);
|
|
1836
|
+
let result = tree;
|
|
1837
|
+
for (let i = 0; i < order; i++) {
|
|
1838
|
+
result = differentiate(result, variable);
|
|
1839
|
+
result = deepSimplify(result);
|
|
1840
|
+
}
|
|
1841
|
+
const resultStr = exprToString(result);
|
|
1842
|
+
return [
|
|
1843
|
+
`## Derivative`,
|
|
1844
|
+
`**Expression:** \`${exprStr}\``,
|
|
1845
|
+
`**d${order > 1 ? `^${order}` : ''}/d${variable}${order > 1 ? `^${order}` : ''}:** \`${resultStr}\``,
|
|
1846
|
+
'',
|
|
1847
|
+
order === 1 ? `Applying standard differentiation rules (power, chain, product/quotient).` : `Applied differentiation ${order} times with simplification at each step.`,
|
|
1848
|
+
].join('\n');
|
|
1849
|
+
}
|
|
1850
|
+
case 'integrate':
|
|
1851
|
+
case 'integral': {
|
|
1852
|
+
// Basic polynomial integration + known forms
|
|
1853
|
+
// For complex integrals, generate SymPy code
|
|
1854
|
+
const result = tryIntegrate(tree, variable);
|
|
1855
|
+
if (result) {
|
|
1856
|
+
const resultStr = exprToString(deepSimplify(result));
|
|
1857
|
+
return [
|
|
1858
|
+
`## Integral`,
|
|
1859
|
+
`**Expression:** \`${exprStr}\``,
|
|
1860
|
+
`**Integral w.r.t. ${variable}:** \`${resultStr} + C\``,
|
|
1861
|
+
].join('\n');
|
|
1862
|
+
}
|
|
1863
|
+
// Fallback: generate SymPy code
|
|
1864
|
+
return [
|
|
1865
|
+
`## Integral (SymPy)`,
|
|
1866
|
+
`Built-in integration cannot handle this expression directly.`,
|
|
1867
|
+
`Use the following Python/SymPy code:`,
|
|
1868
|
+
'```python',
|
|
1869
|
+
`from sympy import symbols, integrate, sin, cos, tan, exp, log, sqrt`,
|
|
1870
|
+
`${variable} = symbols('${variable}')`,
|
|
1871
|
+
`expr = ${exprStr.replace(/\^/g, '**')}`,
|
|
1872
|
+
`result = integrate(expr, ${variable})`,
|
|
1873
|
+
`print(result)`,
|
|
1874
|
+
'```',
|
|
1875
|
+
].join('\n');
|
|
1876
|
+
}
|
|
1877
|
+
case 'simplify': {
|
|
1878
|
+
const simplified = deepSimplify(tree);
|
|
1879
|
+
return [
|
|
1880
|
+
`## Simplification`,
|
|
1881
|
+
`**Input:** \`${exprStr}\``,
|
|
1882
|
+
`**Simplified:** \`${exprToString(simplified)}\``,
|
|
1883
|
+
].join('\n');
|
|
1884
|
+
}
|
|
1885
|
+
case 'series':
|
|
1886
|
+
case 'taylor': {
|
|
1887
|
+
const order = Number(params.order ?? 5);
|
|
1888
|
+
const point = Number(params.point ?? 0);
|
|
1889
|
+
const series = taylorSeries(tree, variable, point, order);
|
|
1890
|
+
return [
|
|
1891
|
+
`## Taylor Series`,
|
|
1892
|
+
`**Expression:** \`${exprStr}\``,
|
|
1893
|
+
`**Around ${variable} = ${point}, order ${order}:**`,
|
|
1894
|
+
`\`${series}\``,
|
|
1895
|
+
`+ O(${variable}^${order + 1})`,
|
|
1896
|
+
].join('\n');
|
|
1897
|
+
}
|
|
1898
|
+
case 'solve':
|
|
1899
|
+
case 'root': {
|
|
1900
|
+
const x0 = Number(params.x0 ?? 0);
|
|
1901
|
+
const maxIter = Number(params.max_iter ?? 100);
|
|
1902
|
+
const tol = Number(params.tol ?? 1e-10);
|
|
1903
|
+
const result = newtonMethod(exprStr, variable, x0, maxIter, tol);
|
|
1904
|
+
return [
|
|
1905
|
+
`## Root Finding (Newton's Method)`,
|
|
1906
|
+
`**Equation:** \`${exprStr} = 0\``,
|
|
1907
|
+
`**Starting point:** ${variable} = ${x0}`,
|
|
1908
|
+
`**Root:** ${variable} = ${fmt(result.root)}`,
|
|
1909
|
+
`**Converged:** ${result.converged ? 'Yes' : 'No'} (${result.iterations} iterations)`,
|
|
1910
|
+
result.converged ? '' : `\n*Try a different starting point (x0) for better convergence.*`,
|
|
1911
|
+
].join('\n');
|
|
1912
|
+
}
|
|
1913
|
+
default:
|
|
1914
|
+
return `Unknown operation: "${operation}". Supported: differentiate, integrate, simplify, series, solve`;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
catch (err) {
|
|
1918
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
1919
|
+
}
|
|
1920
|
+
},
|
|
1921
|
+
});
|
|
1922
|
+
// ── Tool 2: matrix_operations ────────────────────────────────────────────
|
|
1923
|
+
registerTool({
|
|
1924
|
+
name: 'matrix_operations',
|
|
1925
|
+
description: 'Matrix operations: multiply, determinant, inverse, eigenvalues (QR iteration), SVD (singular values), rank, transpose, LU decomposition, add, subtract. Pure TypeScript — works for matrices up to ~50x50.',
|
|
1926
|
+
parameters: {
|
|
1927
|
+
matrix: { type: 'string', description: 'JSON 2D array, e.g. "[[1,2],[3,4]]"', required: true },
|
|
1928
|
+
operation: { type: 'string', description: 'Operation: multiply | determinant | inverse | eigenvalues | svd | rank | transpose | lu | add | subtract', required: true },
|
|
1929
|
+
matrix_b: { type: 'string', description: 'Second matrix (JSON 2D array) for multiply/add/subtract', required: false },
|
|
1930
|
+
},
|
|
1931
|
+
tier: 'free',
|
|
1932
|
+
async execute(args) {
|
|
1933
|
+
const m = safeParse(String(args.matrix), 'matrix');
|
|
1934
|
+
const op = String(args.operation).toLowerCase();
|
|
1935
|
+
try {
|
|
1936
|
+
switch (op) {
|
|
1937
|
+
case 'multiply':
|
|
1938
|
+
case 'mul': {
|
|
1939
|
+
if (!args.matrix_b)
|
|
1940
|
+
return '**Error:** matrix_b required for multiplication';
|
|
1941
|
+
const b = safeParse(String(args.matrix_b), 'matrix_b');
|
|
1942
|
+
const result = matMul(m, b);
|
|
1943
|
+
return `## Matrix Multiplication\n${matToString(m, 'A')} x\n${matToString(b, 'B')} =\n${matToString(result, 'A * B')}`;
|
|
1944
|
+
}
|
|
1945
|
+
case 'add': {
|
|
1946
|
+
if (!args.matrix_b)
|
|
1947
|
+
return '**Error:** matrix_b required for addition';
|
|
1948
|
+
const b = safeParse(String(args.matrix_b), 'matrix_b');
|
|
1949
|
+
const result = matAdd(m, b);
|
|
1950
|
+
return `## Matrix Addition\n${matToString(result, 'A + B')}`;
|
|
1951
|
+
}
|
|
1952
|
+
case 'subtract':
|
|
1953
|
+
case 'sub': {
|
|
1954
|
+
if (!args.matrix_b)
|
|
1955
|
+
return '**Error:** matrix_b required for subtraction';
|
|
1956
|
+
const b = safeParse(String(args.matrix_b), 'matrix_b');
|
|
1957
|
+
const result = matSub(m, b);
|
|
1958
|
+
return `## Matrix Subtraction\n${matToString(result, 'A - B')}`;
|
|
1959
|
+
}
|
|
1960
|
+
case 'determinant':
|
|
1961
|
+
case 'det': {
|
|
1962
|
+
const det = matDet(m);
|
|
1963
|
+
return `## Determinant\n${matToString(m, 'A')}\n**det(A) = ${fmt(det)}**`;
|
|
1964
|
+
}
|
|
1965
|
+
case 'inverse':
|
|
1966
|
+
case 'inv': {
|
|
1967
|
+
const inv = matInverse(m);
|
|
1968
|
+
return `## Matrix Inverse\n${matToString(m, 'A')}\n${matToString(inv, 'A^(-1)')}`;
|
|
1969
|
+
}
|
|
1970
|
+
case 'eigenvalues':
|
|
1971
|
+
case 'eigen':
|
|
1972
|
+
case 'eig': {
|
|
1973
|
+
const eigs = matEigenvalues(m);
|
|
1974
|
+
return [
|
|
1975
|
+
`## Eigenvalues`,
|
|
1976
|
+
matToString(m, 'A'),
|
|
1977
|
+
`**Eigenvalues:** ${eigs.map(e => fmt(e)).join(', ')}`,
|
|
1978
|
+
'',
|
|
1979
|
+
`*Computed via QR iteration with Wilkinson shift.*`,
|
|
1980
|
+
].join('\n');
|
|
1981
|
+
}
|
|
1982
|
+
case 'svd': {
|
|
1983
|
+
const { S } = matSVD(m);
|
|
1984
|
+
return [
|
|
1985
|
+
`## Singular Value Decomposition`,
|
|
1986
|
+
matToString(m, 'A'),
|
|
1987
|
+
`**Singular values:** ${S.map(s => fmt(s)).join(', ')}`,
|
|
1988
|
+
`**Rank (numeric):** ${S.filter(s => s > 1e-10).length}`,
|
|
1989
|
+
'',
|
|
1990
|
+
`*Singular values computed via eigenvalues of A^T A.*`,
|
|
1991
|
+
].join('\n');
|
|
1992
|
+
}
|
|
1993
|
+
case 'rank': {
|
|
1994
|
+
const r = matRank(m);
|
|
1995
|
+
return `## Matrix Rank\n${matToString(m, 'A')}\n**rank(A) = ${r}**`;
|
|
1996
|
+
}
|
|
1997
|
+
case 'transpose':
|
|
1998
|
+
case 'trans':
|
|
1999
|
+
case 't': {
|
|
2000
|
+
const t = matTranspose(m);
|
|
2001
|
+
return `## Transpose\n${matToString(m, 'A')}\n${matToString(t, 'A^T')}`;
|
|
2002
|
+
}
|
|
2003
|
+
case 'lu': {
|
|
2004
|
+
const { L, U, P } = matLU(m);
|
|
2005
|
+
return [
|
|
2006
|
+
`## LU Decomposition (PA = LU)`,
|
|
2007
|
+
matToString(P, 'P (permutation)'),
|
|
2008
|
+
matToString(L, 'L (lower triangular)'),
|
|
2009
|
+
matToString(U, 'U (upper triangular)'),
|
|
2010
|
+
].join('\n');
|
|
2011
|
+
}
|
|
2012
|
+
default:
|
|
2013
|
+
return `Unknown operation: "${op}". Supported: multiply, determinant, inverse, eigenvalues, svd, rank, transpose, lu, add, subtract`;
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
catch (err) {
|
|
2017
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2018
|
+
}
|
|
2019
|
+
},
|
|
2020
|
+
});
|
|
2021
|
+
// ── Tool 3: optimization_solve ───────────────────────────────────────────
|
|
2022
|
+
registerTool({
|
|
2023
|
+
name: 'optimization_solve',
|
|
2024
|
+
description: 'Optimization: Linear Programming (Simplex method), gradient descent (multi-variable), Newton\'s method (root-finding/minimization). Specify objective function, constraints, and method.',
|
|
2025
|
+
parameters: {
|
|
2026
|
+
problem_type: { type: 'string', description: 'Type: linear_programming | minimize | root_finding', required: true },
|
|
2027
|
+
objective: { type: 'string', description: 'Objective function or expression. For LP: comma-separated coefficients "3,5" for max 3x+5y. For minimize/root: expression like "x^2 + y^2"', required: true },
|
|
2028
|
+
constraints: { type: 'string', description: 'JSON array. For LP: [{"coeffs":[1,0],"rhs":4,"type":"<="},...]. For minimize: {"vars":["x","y"],"x0":[0,0],"lr":0.01,"max_iter":1000,"tol":1e-6}', required: false },
|
|
2029
|
+
method: { type: 'string', description: 'Method: simplex | gradient_descent | newton (default depends on problem_type)', required: false },
|
|
2030
|
+
},
|
|
2031
|
+
tier: 'free',
|
|
2032
|
+
async execute(args) {
|
|
2033
|
+
const pType = String(args.problem_type).toLowerCase().replace(/[\s_-]+/g, '_');
|
|
2034
|
+
const objective = String(args.objective);
|
|
2035
|
+
const method = String(args.method || '').toLowerCase();
|
|
2036
|
+
try {
|
|
2037
|
+
switch (pType) {
|
|
2038
|
+
case 'linear_programming':
|
|
2039
|
+
case 'lp': {
|
|
2040
|
+
const c = objective.split(',').map(Number);
|
|
2041
|
+
const constraints = args.constraints ? safeParse(String(args.constraints), 'constraints') : [];
|
|
2042
|
+
const A = constraints.map(con => con.coeffs);
|
|
2043
|
+
const b = constraints.map(con => con.rhs);
|
|
2044
|
+
const result = simplex(c, A, b);
|
|
2045
|
+
const varNames = c.map((_, i) => `x${i + 1}`);
|
|
2046
|
+
const objStr = c.map((ci, i) => `${ci}${varNames[i]}`).join(' + ');
|
|
2047
|
+
return [
|
|
2048
|
+
`## Linear Programming (Simplex)`,
|
|
2049
|
+
`**Maximize:** ${objStr}`,
|
|
2050
|
+
`**Subject to:**`,
|
|
2051
|
+
...constraints.map((con, i) => `- ${con.coeffs.map((ci, j) => `${ci}${varNames[j]}`).join(' + ')} ${con.type || '<='} ${con.rhs}`),
|
|
2052
|
+
`- All variables >= 0`,
|
|
2053
|
+
'',
|
|
2054
|
+
`**Status:** ${result.status}`,
|
|
2055
|
+
`**Optimal value:** ${fmt(result.value)}`,
|
|
2056
|
+
`**Solution:** ${result.solution.map((v, i) => `${varNames[i]} = ${fmt(v)}`).join(', ')}`,
|
|
2057
|
+
].join('\n');
|
|
2058
|
+
}
|
|
2059
|
+
case 'minimize':
|
|
2060
|
+
case 'min': {
|
|
2061
|
+
const params = args.constraints ? safeParse(String(args.constraints), 'constraints') : {};
|
|
2062
|
+
const vars = params.vars || ['x'];
|
|
2063
|
+
const x0 = params.x0 || vars.map(() => 0);
|
|
2064
|
+
const lr = params.lr ?? 0.01;
|
|
2065
|
+
const maxIter = params.max_iter ?? 1000;
|
|
2066
|
+
const tol = params.tol ?? 1e-8;
|
|
2067
|
+
if (method === 'newton' && vars.length === 1) {
|
|
2068
|
+
// Newton's method to find minimum: solve f'(x) = 0
|
|
2069
|
+
const tree = parse(objective);
|
|
2070
|
+
const dStr = exprToString(deepSimplify(differentiate(tree, vars[0])));
|
|
2071
|
+
const result = newtonMethod(dStr, vars[0], x0[0], maxIter, tol);
|
|
2072
|
+
const minVal = evalExpr(tree, { [vars[0]]: result.root });
|
|
2073
|
+
return [
|
|
2074
|
+
`## Minimization (Newton's Method)`,
|
|
2075
|
+
`**Objective:** \`${objective}\``,
|
|
2076
|
+
`**Minimum at:** ${vars[0]} = ${fmt(result.root)}`,
|
|
2077
|
+
`**Minimum value:** ${fmt(minVal)}`,
|
|
2078
|
+
`**Converged:** ${result.converged} (${result.iterations} iterations)`,
|
|
2079
|
+
].join('\n');
|
|
2080
|
+
}
|
|
2081
|
+
const result = gradientDescent(objective, vars, x0, lr, maxIter, tol);
|
|
2082
|
+
return [
|
|
2083
|
+
`## Minimization (Gradient Descent)`,
|
|
2084
|
+
`**Objective:** \`${objective}\``,
|
|
2085
|
+
`**Learning rate:** ${lr}`,
|
|
2086
|
+
`**Solution:** ${vars.map((v, i) => `${v} = ${fmt(result.solution[i])}`).join(', ')}`,
|
|
2087
|
+
`**Minimum value:** ${fmt(result.value)}`,
|
|
2088
|
+
`**Iterations:** ${result.iterations}`,
|
|
2089
|
+
].join('\n');
|
|
2090
|
+
}
|
|
2091
|
+
case 'root_finding':
|
|
2092
|
+
case 'root': {
|
|
2093
|
+
const params = args.constraints ? safeParse(String(args.constraints), 'constraints') : {};
|
|
2094
|
+
const variable = params.var || 'x';
|
|
2095
|
+
const x0 = params.x0 ?? 0;
|
|
2096
|
+
const result = newtonMethod(objective, variable, x0, params.max_iter ?? 100, params.tol ?? 1e-10);
|
|
2097
|
+
return [
|
|
2098
|
+
`## Root Finding (Newton's Method)`,
|
|
2099
|
+
`**Equation:** \`${objective} = 0\``,
|
|
2100
|
+
`**Root:** ${variable} = ${fmt(result.root)}`,
|
|
2101
|
+
`**Converged:** ${result.converged} (${result.iterations} iterations)`,
|
|
2102
|
+
].join('\n');
|
|
2103
|
+
}
|
|
2104
|
+
default:
|
|
2105
|
+
return `Unknown problem type: "${pType}". Supported: linear_programming, minimize, root_finding`;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
catch (err) {
|
|
2109
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2110
|
+
}
|
|
2111
|
+
},
|
|
2112
|
+
});
|
|
2113
|
+
// ── Tool 4: number_theory ────────────────────────────────────────────────
|
|
2114
|
+
registerTool({
|
|
2115
|
+
name: 'number_theory',
|
|
2116
|
+
description: 'Number theory operations: primality testing (Miller-Rabin), factorization (trial division + Pollard\'s rho), GCD, LCM, modular exponentiation, Euler\'s totient, Chinese Remainder Theorem. Uses BigInt for precision.',
|
|
2117
|
+
parameters: {
|
|
2118
|
+
operation: { type: 'string', description: 'Operation: is_prime | factorize | gcd | lcm | mod_pow | totient | crt', required: true },
|
|
2119
|
+
numbers: { type: 'string', description: 'Comma-separated numbers or JSON. For mod_pow: "base,exp,mod". For crt: {"remainders":[2,3],"moduli":[5,7]}', required: true },
|
|
2120
|
+
},
|
|
2121
|
+
tier: 'free',
|
|
2122
|
+
async execute(args) {
|
|
2123
|
+
const op = String(args.operation).toLowerCase().replace(/[\s_-]+/g, '_');
|
|
2124
|
+
const numStr = String(args.numbers);
|
|
2125
|
+
try {
|
|
2126
|
+
switch (op) {
|
|
2127
|
+
case 'is_prime':
|
|
2128
|
+
case 'prime':
|
|
2129
|
+
case 'primality': {
|
|
2130
|
+
const n = BigInt(numStr.trim());
|
|
2131
|
+
const result = millerRabin(n);
|
|
2132
|
+
return [
|
|
2133
|
+
`## Primality Test (Miller-Rabin)`,
|
|
2134
|
+
`**n = ${n}**`,
|
|
2135
|
+
`**Result:** ${result ? 'PRIME' : 'COMPOSITE'}`,
|
|
2136
|
+
result ? '' : `**Factors:** ${factorize(n).join(' x ')}`,
|
|
2137
|
+
].join('\n');
|
|
2138
|
+
}
|
|
2139
|
+
case 'factorize':
|
|
2140
|
+
case 'factor': {
|
|
2141
|
+
const n = BigInt(numStr.trim());
|
|
2142
|
+
const factors = factorize(n);
|
|
2143
|
+
// Group factors
|
|
2144
|
+
const counts = new Map();
|
|
2145
|
+
for (const f of factors) {
|
|
2146
|
+
const k = f.toString();
|
|
2147
|
+
counts.set(k, (counts.get(k) ?? 0) + 1);
|
|
2148
|
+
}
|
|
2149
|
+
const factorStr = [...counts.entries()].map(([p, e]) => e > 1 ? `${p}^${e}` : p).join(' * ');
|
|
2150
|
+
return [
|
|
2151
|
+
`## Prime Factorization`,
|
|
2152
|
+
`**n = ${n}**`,
|
|
2153
|
+
`**Factors:** ${factorStr}`,
|
|
2154
|
+
`**Distinct primes:** ${[...counts.keys()].join(', ')}`,
|
|
2155
|
+
`**Number of divisors:** ${[...counts.values()].reduce((p, e) => p * (e + 1), 1)}`,
|
|
2156
|
+
].join('\n');
|
|
2157
|
+
}
|
|
2158
|
+
case 'gcd': {
|
|
2159
|
+
const nums = numStr.split(',').map(s => BigInt(s.trim()));
|
|
2160
|
+
let result = nums[0];
|
|
2161
|
+
for (let i = 1; i < nums.length; i++)
|
|
2162
|
+
result = bigGcd(result, nums[i]);
|
|
2163
|
+
return `## GCD\n**gcd(${nums.join(', ')}) = ${result}**`;
|
|
2164
|
+
}
|
|
2165
|
+
case 'lcm': {
|
|
2166
|
+
const nums = numStr.split(',').map(s => BigInt(s.trim()));
|
|
2167
|
+
let result = nums[0];
|
|
2168
|
+
for (let i = 1; i < nums.length; i++)
|
|
2169
|
+
result = bigLcm(result, nums[i]);
|
|
2170
|
+
return `## LCM\n**lcm(${nums.join(', ')}) = ${result}**`;
|
|
2171
|
+
}
|
|
2172
|
+
case 'mod_pow':
|
|
2173
|
+
case 'modpow':
|
|
2174
|
+
case 'modular_exponentiation': {
|
|
2175
|
+
const parts = numStr.split(',').map(s => BigInt(s.trim()));
|
|
2176
|
+
if (parts.length < 3)
|
|
2177
|
+
return '**Error:** Need 3 values: base, exponent, modulus';
|
|
2178
|
+
const [base, exp, mod] = parts;
|
|
2179
|
+
const result = modPow(base, exp, mod);
|
|
2180
|
+
return `## Modular Exponentiation\n**${base}^${exp} mod ${mod} = ${result}**`;
|
|
2181
|
+
}
|
|
2182
|
+
case 'totient':
|
|
2183
|
+
case 'euler_totient':
|
|
2184
|
+
case 'phi': {
|
|
2185
|
+
const n = BigInt(numStr.trim());
|
|
2186
|
+
const result = eulerTotient(n);
|
|
2187
|
+
return [
|
|
2188
|
+
`## Euler's Totient`,
|
|
2189
|
+
`**phi(${n}) = ${result}**`,
|
|
2190
|
+
`*Number of integers in [1, ${n}] coprime to ${n}.*`,
|
|
2191
|
+
].join('\n');
|
|
2192
|
+
}
|
|
2193
|
+
case 'crt':
|
|
2194
|
+
case 'chinese_remainder': {
|
|
2195
|
+
const data = safeParse(numStr, 'CRT input');
|
|
2196
|
+
const remainders = data.remainders.map(BigInt);
|
|
2197
|
+
const moduli = data.moduli.map(BigInt);
|
|
2198
|
+
const result = chineseRemainder(remainders, moduli);
|
|
2199
|
+
const eqs = remainders.map((r, i) => `x = ${r} (mod ${moduli[i]})`).join('\n- ');
|
|
2200
|
+
return [
|
|
2201
|
+
`## Chinese Remainder Theorem`,
|
|
2202
|
+
`**System:**`,
|
|
2203
|
+
`- ${eqs}`,
|
|
2204
|
+
'',
|
|
2205
|
+
`**Solution:** x = ${result.solution} (mod ${result.modulus})`,
|
|
2206
|
+
].join('\n');
|
|
2207
|
+
}
|
|
2208
|
+
default:
|
|
2209
|
+
return `Unknown operation: "${op}". Supported: is_prime, factorize, gcd, lcm, mod_pow, totient, crt`;
|
|
2210
|
+
}
|
|
2211
|
+
}
|
|
2212
|
+
catch (err) {
|
|
2213
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2214
|
+
}
|
|
2215
|
+
},
|
|
2216
|
+
});
|
|
2217
|
+
// ── Tool 5: graph_theory ─────────────────────────────────────────────────
|
|
2218
|
+
registerTool({
|
|
2219
|
+
name: 'graph_theory',
|
|
2220
|
+
description: 'Graph algorithms: Dijkstra shortest path, Kruskal MST, BFS/DFS connected components, betweenness centrality, greedy coloring, topological sort. Input: JSON graph {nodes, edges} where edges are {from, to, weight?}.',
|
|
2221
|
+
parameters: {
|
|
2222
|
+
graph: { type: 'string', description: 'JSON graph: {"nodes":["A","B","C"],"edges":[{"from":"A","to":"B","weight":1},...]}}', required: true },
|
|
2223
|
+
operation: { type: 'string', description: 'Operation: dijkstra | mst | components | centrality | coloring | topological_sort', required: true },
|
|
2224
|
+
params: { type: 'string', description: 'JSON params. For dijkstra: {"source":"A","target":"B"}', required: false },
|
|
2225
|
+
},
|
|
2226
|
+
tier: 'free',
|
|
2227
|
+
async execute(args) {
|
|
2228
|
+
const g = parseGraph(String(args.graph));
|
|
2229
|
+
const op = String(args.operation).toLowerCase().replace(/[\s_-]+/g, '_');
|
|
2230
|
+
const params = args.params ? safeParse(String(args.params), 'params') : {};
|
|
2231
|
+
try {
|
|
2232
|
+
switch (op) {
|
|
2233
|
+
case 'dijkstra':
|
|
2234
|
+
case 'shortest_path': {
|
|
2235
|
+
const source = String(params.source ?? g.nodes[0]);
|
|
2236
|
+
const target = params.target ? String(params.target) : undefined;
|
|
2237
|
+
const { dist, prev } = dijkstra(g, source, target);
|
|
2238
|
+
if (target) {
|
|
2239
|
+
const path = reconstructPath(prev, target);
|
|
2240
|
+
const d = dist.get(target) ?? Infinity;
|
|
2241
|
+
return [
|
|
2242
|
+
`## Shortest Path (Dijkstra)`,
|
|
2243
|
+
`**From:** ${source} **To:** ${target}`,
|
|
2244
|
+
`**Distance:** ${d === Infinity ? 'unreachable' : fmt(d)}`,
|
|
2245
|
+
`**Path:** ${path.join(' -> ')}`,
|
|
2246
|
+
].join('\n');
|
|
2247
|
+
}
|
|
2248
|
+
const lines = [`## Shortest Paths from ${source} (Dijkstra)`, ''];
|
|
2249
|
+
for (const [node, d] of dist) {
|
|
2250
|
+
const path = reconstructPath(prev, node);
|
|
2251
|
+
lines.push(`- **${node}:** distance = ${d === Infinity ? 'inf' : fmt(d)}${path.length > 1 ? ` (${path.join(' -> ')})` : ''}`);
|
|
2252
|
+
}
|
|
2253
|
+
return lines.join('\n');
|
|
2254
|
+
}
|
|
2255
|
+
case 'mst':
|
|
2256
|
+
case 'minimum_spanning_tree': {
|
|
2257
|
+
const { edges, totalWeight } = kruskalMST(g);
|
|
2258
|
+
return [
|
|
2259
|
+
`## Minimum Spanning Tree (Kruskal)`,
|
|
2260
|
+
`**Total weight:** ${fmt(totalWeight)}`,
|
|
2261
|
+
`**Edges:**`,
|
|
2262
|
+
...edges.map(e => `- ${e.from} -- ${e.to} (weight: ${e.weight ?? 1})`),
|
|
2263
|
+
].join('\n');
|
|
2264
|
+
}
|
|
2265
|
+
case 'components':
|
|
2266
|
+
case 'connected_components':
|
|
2267
|
+
case 'bfs': {
|
|
2268
|
+
const comps = bfsComponents(g);
|
|
2269
|
+
return [
|
|
2270
|
+
`## Connected Components (BFS)`,
|
|
2271
|
+
`**Number of components:** ${comps.length}`,
|
|
2272
|
+
...comps.map((c, i) => `- Component ${i + 1}: {${c.join(', ')}} (size: ${c.length})`),
|
|
2273
|
+
].join('\n');
|
|
2274
|
+
}
|
|
2275
|
+
case 'centrality':
|
|
2276
|
+
case 'betweenness':
|
|
2277
|
+
case 'betweenness_centrality': {
|
|
2278
|
+
const cb = betweennessCentrality(g);
|
|
2279
|
+
const sorted = [...cb.entries()].sort((a, b) => b[1] - a[1]);
|
|
2280
|
+
return [
|
|
2281
|
+
`## Betweenness Centrality (Brandes)`,
|
|
2282
|
+
`| Node | Centrality |`,
|
|
2283
|
+
`|------|-----------|`,
|
|
2284
|
+
...sorted.map(([n, c]) => `| ${n} | ${fmt(c, 4)} |`),
|
|
2285
|
+
].join('\n');
|
|
2286
|
+
}
|
|
2287
|
+
case 'coloring':
|
|
2288
|
+
case 'greedy_coloring': {
|
|
2289
|
+
const colors = greedyColoring(g);
|
|
2290
|
+
const numColors = new Set(colors.values()).size;
|
|
2291
|
+
return [
|
|
2292
|
+
`## Graph Coloring (Greedy)`,
|
|
2293
|
+
`**Chromatic number (upper bound):** ${numColors}`,
|
|
2294
|
+
`**Assignment:**`,
|
|
2295
|
+
...[...colors.entries()].map(([n, c]) => `- ${n}: color ${c}`),
|
|
2296
|
+
].join('\n');
|
|
2297
|
+
}
|
|
2298
|
+
case 'topological_sort':
|
|
2299
|
+
case 'topo_sort':
|
|
2300
|
+
case 'toposort': {
|
|
2301
|
+
const order = topologicalSort(g);
|
|
2302
|
+
if (!order)
|
|
2303
|
+
return `## Topological Sort\n**Error:** Graph contains a cycle — no topological ordering exists.`;
|
|
2304
|
+
return [
|
|
2305
|
+
`## Topological Sort (Kahn's Algorithm)`,
|
|
2306
|
+
`**Order:** ${order.join(' -> ')}`,
|
|
2307
|
+
].join('\n');
|
|
2308
|
+
}
|
|
2309
|
+
default:
|
|
2310
|
+
return `Unknown operation: "${op}". Supported: dijkstra, mst, components, centrality, coloring, topological_sort`;
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
catch (err) {
|
|
2314
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2315
|
+
}
|
|
2316
|
+
},
|
|
2317
|
+
});
|
|
2318
|
+
// ── Tool 6: combinatorics ────────────────────────────────────────────────
|
|
2319
|
+
registerTool({
|
|
2320
|
+
name: 'combinatorics',
|
|
2321
|
+
description: 'Combinatorics: permutations P(n,k), combinations C(n,k), Catalan numbers, Stirling numbers (2nd kind), Bell numbers, integer partition numbers, derangements, factorial. Uses BigInt for arbitrary precision.',
|
|
2322
|
+
parameters: {
|
|
2323
|
+
operation: { type: 'string', description: 'Operation: permutations | combinations | catalan | stirling | bell | partition | derangement | factorial', required: true },
|
|
2324
|
+
n: { type: 'number', description: 'Primary parameter n', required: true },
|
|
2325
|
+
k: { type: 'number', description: 'Secondary parameter k (for permutations, combinations, stirling)', required: false },
|
|
2326
|
+
},
|
|
2327
|
+
tier: 'free',
|
|
2328
|
+
async execute(args) {
|
|
2329
|
+
const op = String(args.operation).toLowerCase();
|
|
2330
|
+
const n = Number(args.n);
|
|
2331
|
+
const k = args.k !== undefined ? Number(args.k) : undefined;
|
|
2332
|
+
if (!Number.isInteger(n) || n < 0)
|
|
2333
|
+
return '**Error:** n must be a non-negative integer';
|
|
2334
|
+
if (n > 1000)
|
|
2335
|
+
return '**Error:** n too large (max 1000 to avoid excessive computation)';
|
|
2336
|
+
try {
|
|
2337
|
+
switch (op) {
|
|
2338
|
+
case 'permutations':
|
|
2339
|
+
case 'perm':
|
|
2340
|
+
case 'p': {
|
|
2341
|
+
if (k === undefined)
|
|
2342
|
+
return '**Error:** k is required for permutations';
|
|
2343
|
+
const result = bigPerm(n, k);
|
|
2344
|
+
return `## Permutations\n**P(${n}, ${k}) = ${result}**\n\n*Number of ways to arrange ${k} items from ${n} distinct items.*`;
|
|
2345
|
+
}
|
|
2346
|
+
case 'combinations':
|
|
2347
|
+
case 'comb':
|
|
2348
|
+
case 'c':
|
|
2349
|
+
case 'choose': {
|
|
2350
|
+
if (k === undefined)
|
|
2351
|
+
return '**Error:** k is required for combinations';
|
|
2352
|
+
const result = bigComb(n, k);
|
|
2353
|
+
return `## Combinations\n**C(${n}, ${k}) = ${result}**\n\n*Number of ways to choose ${k} items from ${n} distinct items (order doesn't matter).*`;
|
|
2354
|
+
}
|
|
2355
|
+
case 'catalan': {
|
|
2356
|
+
const result = catalanNumber(n);
|
|
2357
|
+
return [
|
|
2358
|
+
`## Catalan Number`,
|
|
2359
|
+
`**C_${n} = ${result}**`,
|
|
2360
|
+
'',
|
|
2361
|
+
`*Counts: valid parenthesizations, binary trees with ${n + 1} leaves, monotonic lattice paths, non-crossing partitions, etc.*`,
|
|
2362
|
+
].join('\n');
|
|
2363
|
+
}
|
|
2364
|
+
case 'stirling':
|
|
2365
|
+
case 'stirling2': {
|
|
2366
|
+
if (k === undefined)
|
|
2367
|
+
return '**Error:** k is required for Stirling numbers';
|
|
2368
|
+
const result = stirling2(n, k);
|
|
2369
|
+
return [
|
|
2370
|
+
`## Stirling Number (2nd Kind)`,
|
|
2371
|
+
`**S(${n}, ${k}) = ${result}**`,
|
|
2372
|
+
'',
|
|
2373
|
+
`*Number of ways to partition ${n} elements into ${k} non-empty subsets.*`,
|
|
2374
|
+
].join('\n');
|
|
2375
|
+
}
|
|
2376
|
+
case 'bell': {
|
|
2377
|
+
const result = bellNumber(n);
|
|
2378
|
+
return [
|
|
2379
|
+
`## Bell Number`,
|
|
2380
|
+
`**B_${n} = ${result}**`,
|
|
2381
|
+
'',
|
|
2382
|
+
`*Total number of partitions of a set with ${n} elements.*`,
|
|
2383
|
+
].join('\n');
|
|
2384
|
+
}
|
|
2385
|
+
case 'partition':
|
|
2386
|
+
case 'partitions': {
|
|
2387
|
+
const result = partitionNumber(n);
|
|
2388
|
+
return [
|
|
2389
|
+
`## Partition Number`,
|
|
2390
|
+
`**p(${n}) = ${result}**`,
|
|
2391
|
+
'',
|
|
2392
|
+
`*Number of ways to write ${n} as a sum of positive integers (order doesn't matter).*`,
|
|
2393
|
+
].join('\n');
|
|
2394
|
+
}
|
|
2395
|
+
case 'derangement':
|
|
2396
|
+
case 'derangements':
|
|
2397
|
+
case 'subfactorial': {
|
|
2398
|
+
const result = derangement(n);
|
|
2399
|
+
return [
|
|
2400
|
+
`## Derangement`,
|
|
2401
|
+
`**D_${n} = ${result}**`,
|
|
2402
|
+
'',
|
|
2403
|
+
`*Number of permutations of ${n} elements with no fixed points.*`,
|
|
2404
|
+
`*Probability of a random permutation being a derangement: ~1/e = ${fmt(Number(result) / Number(bigFactorial(n)), 6)}*`,
|
|
2405
|
+
].join('\n');
|
|
2406
|
+
}
|
|
2407
|
+
case 'factorial':
|
|
2408
|
+
case 'fact': {
|
|
2409
|
+
const result = bigFactorial(n);
|
|
2410
|
+
const str = result.toString();
|
|
2411
|
+
return [
|
|
2412
|
+
`## Factorial`,
|
|
2413
|
+
`**${n}! = ${str.length > 100 ? str.slice(0, 50) + '...' + str.slice(-50) : str}**`,
|
|
2414
|
+
str.length > 100 ? `*(${str.length} digits)*` : '',
|
|
2415
|
+
].join('\n');
|
|
2416
|
+
}
|
|
2417
|
+
default:
|
|
2418
|
+
return `Unknown operation: "${op}". Supported: permutations, combinations, catalan, stirling, bell, partition, derangement, factorial`;
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
catch (err) {
|
|
2422
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2423
|
+
}
|
|
2424
|
+
},
|
|
2425
|
+
});
|
|
2426
|
+
// ── Tool 7: differential_eq ──────────────────────────────────────────────
|
|
2427
|
+
registerTool({
|
|
2428
|
+
name: 'differential_eq',
|
|
2429
|
+
description: 'Solve first-order ODEs numerically: dy/dx = f(x, y). Methods: Euler, RK4 (classical Runge-Kutta), RK45 (adaptive Dormand-Prince). Parses math expressions with x, y variables.',
|
|
2430
|
+
parameters: {
|
|
2431
|
+
equation: { type: 'string', description: 'The RHS f(x,y) of dy/dx = f(x,y), e.g. "x + y" or "-2*y + sin(x)"', required: true },
|
|
2432
|
+
initial_conditions: { type: 'string', description: 'JSON: {"x0": 0, "y0": 1}', required: true },
|
|
2433
|
+
x_end: { type: 'number', description: 'End value of x', required: true },
|
|
2434
|
+
method: { type: 'string', description: 'Method: euler | rk4 | rk45 (default: rk4)', required: false },
|
|
2435
|
+
step_size: { type: 'number', description: 'Step size h (default: 0.1, ignored for rk45 which is adaptive)', required: false },
|
|
2436
|
+
},
|
|
2437
|
+
tier: 'free',
|
|
2438
|
+
async execute(args) {
|
|
2439
|
+
const eqStr = String(args.equation);
|
|
2440
|
+
const ic = safeParse(String(args.initial_conditions), 'initial_conditions');
|
|
2441
|
+
const xEnd = Number(args.x_end);
|
|
2442
|
+
const method = String(args.method || 'rk4').toLowerCase();
|
|
2443
|
+
const h = Number(args.step_size || 0.1);
|
|
2444
|
+
try {
|
|
2445
|
+
const f = parseODEFunc(eqStr);
|
|
2446
|
+
let result;
|
|
2447
|
+
switch (method) {
|
|
2448
|
+
case 'euler':
|
|
2449
|
+
result = eulerMethod(f, ic.x0, ic.y0, xEnd, h);
|
|
2450
|
+
break;
|
|
2451
|
+
case 'rk4':
|
|
2452
|
+
result = rk4Method(f, ic.x0, ic.y0, xEnd, h);
|
|
2453
|
+
break;
|
|
2454
|
+
case 'rk45':
|
|
2455
|
+
case 'adaptive':
|
|
2456
|
+
case 'dormand_prince':
|
|
2457
|
+
result = rk45Method(f, ic.x0, ic.y0, xEnd);
|
|
2458
|
+
break;
|
|
2459
|
+
default:
|
|
2460
|
+
return `Unknown method: "${method}". Supported: euler, rk4, rk45`;
|
|
2461
|
+
}
|
|
2462
|
+
// Show a subset of points (at most 25)
|
|
2463
|
+
const step = Math.max(1, Math.floor(result.x.length / 25));
|
|
2464
|
+
const lines = [
|
|
2465
|
+
`## ODE Solution`,
|
|
2466
|
+
`**Equation:** dy/dx = ${eqStr}`,
|
|
2467
|
+
`**Initial condition:** y(${ic.x0}) = ${ic.y0}`,
|
|
2468
|
+
`**Method:** ${method.toUpperCase()}${method !== 'rk45' ? `, h = ${h}` : ' (adaptive)'}`,
|
|
2469
|
+
`**Points computed:** ${result.x.length}`,
|
|
2470
|
+
'',
|
|
2471
|
+
`| x | y |`,
|
|
2472
|
+
`|---|---|`,
|
|
2473
|
+
];
|
|
2474
|
+
for (let i = 0; i < result.x.length; i += step) {
|
|
2475
|
+
lines.push(`| ${fmt(result.x[i])} | ${fmt(result.y[i])} |`);
|
|
2476
|
+
}
|
|
2477
|
+
// Always include the last point
|
|
2478
|
+
if ((result.x.length - 1) % step !== 0) {
|
|
2479
|
+
const last = result.x.length - 1;
|
|
2480
|
+
lines.push(`| ${fmt(result.x[last])} | ${fmt(result.y[last])} |`);
|
|
2481
|
+
}
|
|
2482
|
+
lines.push('', `**y(${fmt(xEnd)}) = ${fmt(result.y[result.y.length - 1])}**`);
|
|
2483
|
+
return lines.join('\n');
|
|
2484
|
+
}
|
|
2485
|
+
catch (err) {
|
|
2486
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2487
|
+
}
|
|
2488
|
+
},
|
|
2489
|
+
});
|
|
2490
|
+
// ── Tool 8: probability_calc ─────────────────────────────────────────────
|
|
2491
|
+
registerTool({
|
|
2492
|
+
name: 'probability_calc',
|
|
2493
|
+
description: 'Probability distributions: PDF/PMF, CDF, quantiles (inverse CDF), moments. Supports: normal, poisson, binomial, exponential, gamma, chi_squared, student_t, f, beta, weibull.',
|
|
2494
|
+
parameters: {
|
|
2495
|
+
distribution: { type: 'string', description: 'Distribution name: normal | poisson | binomial | exponential | gamma | chi_squared | student_t | f | beta | weibull', required: true },
|
|
2496
|
+
operation: { type: 'string', description: 'Operation: pdf | cdf | quantile | moments', required: true },
|
|
2497
|
+
params: { type: 'string', description: 'JSON with distribution params + query value. E.g. {"x":1.96,"mean":0,"std":1} for normal, {"k":5,"lambda":3} for poisson', required: true },
|
|
2498
|
+
},
|
|
2499
|
+
tier: 'free',
|
|
2500
|
+
async execute(args) {
|
|
2501
|
+
const dist = String(args.distribution).toLowerCase().replace(/[\s_-]+/g, '_');
|
|
2502
|
+
const operation = String(args.operation).toLowerCase();
|
|
2503
|
+
const p = safeParse(String(args.params), 'params');
|
|
2504
|
+
try {
|
|
2505
|
+
const results = [];
|
|
2506
|
+
let distDesc = '';
|
|
2507
|
+
switch (dist) {
|
|
2508
|
+
case 'normal':
|
|
2509
|
+
case 'gaussian': {
|
|
2510
|
+
const mu = p.mean ?? p.mu ?? 0;
|
|
2511
|
+
const sigma = p.std ?? p.sigma ?? 1;
|
|
2512
|
+
distDesc = `Normal(mu=${mu}, sigma=${sigma})`;
|
|
2513
|
+
if (operation === 'pdf') {
|
|
2514
|
+
const x = p.x ?? 0;
|
|
2515
|
+
const z = (x - mu) / sigma;
|
|
2516
|
+
results.push({ value: normalPDF(z) / sigma, label: `PDF(${x})` });
|
|
2517
|
+
}
|
|
2518
|
+
else if (operation === 'cdf') {
|
|
2519
|
+
const x = p.x ?? 0;
|
|
2520
|
+
const z = (x - mu) / sigma;
|
|
2521
|
+
results.push({ value: normalCDF(z), label: `CDF(${x})` });
|
|
2522
|
+
results.push({ value: 1 - normalCDF(z), label: `P(X > ${x})` });
|
|
2523
|
+
}
|
|
2524
|
+
else if (operation === 'quantile') {
|
|
2525
|
+
const prob = p.p ?? p.probability ?? 0.5;
|
|
2526
|
+
const z = normalQuantile(prob);
|
|
2527
|
+
results.push({ value: mu + sigma * z, label: `Quantile(${prob})` });
|
|
2528
|
+
}
|
|
2529
|
+
else if (operation === 'moments') {
|
|
2530
|
+
results.push({ value: mu, label: 'Mean' });
|
|
2531
|
+
results.push({ value: sigma * sigma, label: 'Variance' });
|
|
2532
|
+
results.push({ value: sigma, label: 'Std Dev' });
|
|
2533
|
+
results.push({ value: 0, label: 'Skewness' });
|
|
2534
|
+
results.push({ value: 3, label: 'Kurtosis' });
|
|
2535
|
+
}
|
|
2536
|
+
break;
|
|
2537
|
+
}
|
|
2538
|
+
case 'poisson': {
|
|
2539
|
+
const lambda = p.lambda ?? p.rate ?? 1;
|
|
2540
|
+
distDesc = `Poisson(lambda=${lambda})`;
|
|
2541
|
+
if (operation === 'pdf' || operation === 'pmf') {
|
|
2542
|
+
const k = Math.floor(p.k ?? p.x ?? 0);
|
|
2543
|
+
results.push({ value: poissonPMF(k, lambda), label: `P(X = ${k})` });
|
|
2544
|
+
}
|
|
2545
|
+
else if (operation === 'cdf') {
|
|
2546
|
+
const k = Math.floor(p.k ?? p.x ?? 0);
|
|
2547
|
+
results.push({ value: poissonCDF(k, lambda), label: `P(X <= ${k})` });
|
|
2548
|
+
}
|
|
2549
|
+
else if (operation === 'moments') {
|
|
2550
|
+
results.push({ value: lambda, label: 'Mean' });
|
|
2551
|
+
results.push({ value: lambda, label: 'Variance' });
|
|
2552
|
+
results.push({ value: 1 / Math.sqrt(lambda), label: 'Skewness' });
|
|
2553
|
+
}
|
|
2554
|
+
break;
|
|
2555
|
+
}
|
|
2556
|
+
case 'binomial': {
|
|
2557
|
+
const n = Math.floor(p.n ?? 10);
|
|
2558
|
+
const prob = p.p ?? p.probability ?? 0.5;
|
|
2559
|
+
distDesc = `Binomial(n=${n}, p=${prob})`;
|
|
2560
|
+
if (operation === 'pdf' || operation === 'pmf') {
|
|
2561
|
+
const k = Math.floor(p.k ?? p.x ?? 0);
|
|
2562
|
+
results.push({ value: binomialPMF(k, n, prob), label: `P(X = ${k})` });
|
|
2563
|
+
}
|
|
2564
|
+
else if (operation === 'cdf') {
|
|
2565
|
+
const k = Math.floor(p.k ?? p.x ?? 0);
|
|
2566
|
+
results.push({ value: binomialCDF(k, n, prob), label: `P(X <= ${k})` });
|
|
2567
|
+
}
|
|
2568
|
+
else if (operation === 'moments') {
|
|
2569
|
+
results.push({ value: n * prob, label: 'Mean' });
|
|
2570
|
+
results.push({ value: n * prob * (1 - prob), label: 'Variance' });
|
|
2571
|
+
results.push({ value: (1 - 2 * prob) / Math.sqrt(n * prob * (1 - prob)), label: 'Skewness' });
|
|
2572
|
+
}
|
|
2573
|
+
break;
|
|
2574
|
+
}
|
|
2575
|
+
case 'exponential': {
|
|
2576
|
+
const lambda = p.lambda ?? p.rate ?? 1;
|
|
2577
|
+
distDesc = `Exponential(lambda=${lambda})`;
|
|
2578
|
+
if (operation === 'pdf') {
|
|
2579
|
+
const x = p.x ?? 0;
|
|
2580
|
+
results.push({ value: exponentialPDF(x, lambda), label: `PDF(${x})` });
|
|
2581
|
+
}
|
|
2582
|
+
else if (operation === 'cdf') {
|
|
2583
|
+
const x = p.x ?? 0;
|
|
2584
|
+
results.push({ value: exponentialCDF(x, lambda), label: `CDF(${x})` });
|
|
2585
|
+
}
|
|
2586
|
+
else if (operation === 'quantile') {
|
|
2587
|
+
const prob = p.p ?? p.probability ?? 0.5;
|
|
2588
|
+
results.push({ value: -Math.log(1 - prob) / lambda, label: `Quantile(${prob})` });
|
|
2589
|
+
}
|
|
2590
|
+
else if (operation === 'moments') {
|
|
2591
|
+
results.push({ value: 1 / lambda, label: 'Mean' });
|
|
2592
|
+
results.push({ value: 1 / (lambda * lambda), label: 'Variance' });
|
|
2593
|
+
results.push({ value: 2, label: 'Skewness' });
|
|
2594
|
+
}
|
|
2595
|
+
break;
|
|
2596
|
+
}
|
|
2597
|
+
case 'gamma': {
|
|
2598
|
+
const alpha = p.alpha ?? p.shape ?? p.k ?? 1;
|
|
2599
|
+
const beta = p.beta ?? p.scale ?? p.theta ?? 1;
|
|
2600
|
+
distDesc = `Gamma(alpha=${alpha}, beta=${beta})`;
|
|
2601
|
+
if (operation === 'pdf') {
|
|
2602
|
+
const x = p.x ?? 0;
|
|
2603
|
+
results.push({ value: gammaPDF(x, alpha, beta), label: `PDF(${x})` });
|
|
2604
|
+
}
|
|
2605
|
+
else if (operation === 'cdf') {
|
|
2606
|
+
const x = p.x ?? 0;
|
|
2607
|
+
results.push({ value: gammaCDF(x, alpha, beta), label: `CDF(${x})` });
|
|
2608
|
+
}
|
|
2609
|
+
else if (operation === 'moments') {
|
|
2610
|
+
results.push({ value: alpha * beta, label: 'Mean' });
|
|
2611
|
+
results.push({ value: alpha * beta * beta, label: 'Variance' });
|
|
2612
|
+
results.push({ value: 2 / Math.sqrt(alpha), label: 'Skewness' });
|
|
2613
|
+
}
|
|
2614
|
+
break;
|
|
2615
|
+
}
|
|
2616
|
+
case 'chi_squared':
|
|
2617
|
+
case 'chi2':
|
|
2618
|
+
case 'chisquared': {
|
|
2619
|
+
const k = p.k ?? p.df ?? 1;
|
|
2620
|
+
distDesc = `Chi-squared(k=${k})`;
|
|
2621
|
+
if (operation === 'pdf') {
|
|
2622
|
+
const x = p.x ?? 0;
|
|
2623
|
+
results.push({ value: chi2PDF(x, k), label: `PDF(${x})` });
|
|
2624
|
+
}
|
|
2625
|
+
else if (operation === 'cdf') {
|
|
2626
|
+
const x = p.x ?? 0;
|
|
2627
|
+
results.push({ value: chi2CDF(x, k), label: `CDF(${x})` });
|
|
2628
|
+
results.push({ value: 1 - chi2CDF(x, k), label: `p-value (right tail)` });
|
|
2629
|
+
}
|
|
2630
|
+
else if (operation === 'moments') {
|
|
2631
|
+
results.push({ value: k, label: 'Mean' });
|
|
2632
|
+
results.push({ value: 2 * k, label: 'Variance' });
|
|
2633
|
+
results.push({ value: Math.sqrt(8 / k), label: 'Skewness' });
|
|
2634
|
+
}
|
|
2635
|
+
break;
|
|
2636
|
+
}
|
|
2637
|
+
case 'student_t':
|
|
2638
|
+
case 't': {
|
|
2639
|
+
const nu = p.nu ?? p.df ?? 1;
|
|
2640
|
+
distDesc = `Student's t(nu=${nu})`;
|
|
2641
|
+
if (operation === 'pdf') {
|
|
2642
|
+
const x = p.x ?? 0;
|
|
2643
|
+
results.push({ value: studentTPDF(x, nu), label: `PDF(${x})` });
|
|
2644
|
+
}
|
|
2645
|
+
else if (operation === 'cdf') {
|
|
2646
|
+
const x = p.x ?? 0;
|
|
2647
|
+
results.push({ value: studentTCDF(x, nu), label: `CDF(${x})` });
|
|
2648
|
+
results.push({ value: 2 * (1 - studentTCDF(Math.abs(x), nu)), label: `Two-tailed p-value` });
|
|
2649
|
+
}
|
|
2650
|
+
else if (operation === 'moments') {
|
|
2651
|
+
results.push({ value: nu > 1 ? 0 : NaN, label: 'Mean' });
|
|
2652
|
+
results.push({ value: nu > 2 ? nu / (nu - 2) : Infinity, label: 'Variance' });
|
|
2653
|
+
results.push({ value: nu > 3 ? 0 : NaN, label: 'Skewness' });
|
|
2654
|
+
}
|
|
2655
|
+
break;
|
|
2656
|
+
}
|
|
2657
|
+
case 'f':
|
|
2658
|
+
case 'f_distribution':
|
|
2659
|
+
case 'fisher': {
|
|
2660
|
+
const d1 = p.d1 ?? p.df1 ?? 1;
|
|
2661
|
+
const d2 = p.d2 ?? p.df2 ?? 1;
|
|
2662
|
+
distDesc = `F(d1=${d1}, d2=${d2})`;
|
|
2663
|
+
if (operation === 'pdf') {
|
|
2664
|
+
const x = p.x ?? 0;
|
|
2665
|
+
results.push({ value: fDistPDF(x, d1, d2), label: `PDF(${x})` });
|
|
2666
|
+
}
|
|
2667
|
+
else if (operation === 'cdf') {
|
|
2668
|
+
const x = p.x ?? 0;
|
|
2669
|
+
results.push({ value: fDistCDF(x, d1, d2), label: `CDF(${x})` });
|
|
2670
|
+
results.push({ value: 1 - fDistCDF(x, d1, d2), label: `p-value (right tail)` });
|
|
2671
|
+
}
|
|
2672
|
+
else if (operation === 'moments') {
|
|
2673
|
+
results.push({ value: d2 > 2 ? d2 / (d2 - 2) : Infinity, label: 'Mean' });
|
|
2674
|
+
results.push({ value: d2 > 4 ? 2 * d2 * d2 * (d1 + d2 - 2) / (d1 * (d2 - 2) * (d2 - 2) * (d2 - 4)) : Infinity, label: 'Variance' });
|
|
2675
|
+
}
|
|
2676
|
+
break;
|
|
2677
|
+
}
|
|
2678
|
+
case 'beta': {
|
|
2679
|
+
const alpha = p.alpha ?? p.a ?? 1;
|
|
2680
|
+
const beta = p.beta ?? p.b ?? 1;
|
|
2681
|
+
distDesc = `Beta(alpha=${alpha}, beta=${beta})`;
|
|
2682
|
+
if (operation === 'pdf') {
|
|
2683
|
+
const x = p.x ?? 0.5;
|
|
2684
|
+
results.push({ value: betaDistPDF(x, alpha, beta), label: `PDF(${x})` });
|
|
2685
|
+
}
|
|
2686
|
+
else if (operation === 'cdf') {
|
|
2687
|
+
const x = p.x ?? 0.5;
|
|
2688
|
+
results.push({ value: betaDistCDF(x, alpha, beta), label: `CDF(${x})` });
|
|
2689
|
+
}
|
|
2690
|
+
else if (operation === 'moments') {
|
|
2691
|
+
const ab = alpha + beta;
|
|
2692
|
+
results.push({ value: alpha / ab, label: 'Mean' });
|
|
2693
|
+
results.push({ value: alpha * beta / (ab * ab * (ab + 1)), label: 'Variance' });
|
|
2694
|
+
results.push({ value: 2 * (beta - alpha) * Math.sqrt(ab + 1) / ((ab + 2) * Math.sqrt(alpha * beta)), label: 'Skewness' });
|
|
2695
|
+
}
|
|
2696
|
+
break;
|
|
2697
|
+
}
|
|
2698
|
+
case 'weibull': {
|
|
2699
|
+
const k = p.k ?? p.shape ?? 1;
|
|
2700
|
+
const lambda = p.lambda ?? p.scale ?? 1;
|
|
2701
|
+
distDesc = `Weibull(k=${k}, lambda=${lambda})`;
|
|
2702
|
+
if (operation === 'pdf') {
|
|
2703
|
+
const x = p.x ?? 0;
|
|
2704
|
+
results.push({ value: weibullPDF(x, k, lambda), label: `PDF(${x})` });
|
|
2705
|
+
}
|
|
2706
|
+
else if (operation === 'cdf') {
|
|
2707
|
+
const x = p.x ?? 0;
|
|
2708
|
+
results.push({ value: weibullCDF(x, k, lambda), label: `CDF(${x})` });
|
|
2709
|
+
}
|
|
2710
|
+
else if (operation === 'quantile') {
|
|
2711
|
+
const prob = p.p ?? p.probability ?? 0.5;
|
|
2712
|
+
results.push({ value: lambda * Math.pow(-Math.log(1 - prob), 1 / k), label: `Quantile(${prob})` });
|
|
2713
|
+
}
|
|
2714
|
+
else if (operation === 'moments') {
|
|
2715
|
+
results.push({ value: lambda * gammaFn(1 + 1 / k), label: 'Mean' });
|
|
2716
|
+
results.push({ value: lambda * lambda * (gammaFn(1 + 2 / k) - Math.pow(gammaFn(1 + 1 / k), 2)), label: 'Variance' });
|
|
2717
|
+
}
|
|
2718
|
+
break;
|
|
2719
|
+
}
|
|
2720
|
+
default:
|
|
2721
|
+
return `Unknown distribution: "${dist}". Supported: normal, poisson, binomial, exponential, gamma, chi_squared, student_t, f, beta, weibull`;
|
|
2722
|
+
}
|
|
2723
|
+
if (results.length === 0)
|
|
2724
|
+
return `No results computed. Check operation "${operation}" is valid for ${dist}.`;
|
|
2725
|
+
return [
|
|
2726
|
+
`## ${distDesc}`,
|
|
2727
|
+
`**Operation:** ${operation}`,
|
|
2728
|
+
'',
|
|
2729
|
+
...results.map(r => `- **${r.label}** = ${fmt(r.value)}`),
|
|
2730
|
+
].join('\n');
|
|
2731
|
+
}
|
|
2732
|
+
catch (err) {
|
|
2733
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2734
|
+
}
|
|
2735
|
+
},
|
|
2736
|
+
});
|
|
2737
|
+
// ── Tool 9: fourier_analysis ─────────────────────────────────────────────
|
|
2738
|
+
registerTool({
|
|
2739
|
+
name: 'fourier_analysis',
|
|
2740
|
+
description: 'Fourier analysis: FFT (Cooley-Tukey radix-2), power spectrum, inverse FFT, frequency identification. Input data as comma-separated values, auto-pads to power of 2.',
|
|
2741
|
+
parameters: {
|
|
2742
|
+
data: { type: 'string', description: 'Comma-separated real values, e.g. "1,0,-1,0,1,0,-1,0"', required: true },
|
|
2743
|
+
sample_rate: { type: 'number', description: 'Sample rate in Hz (default: 1)', required: false },
|
|
2744
|
+
operation: { type: 'string', description: 'Operation: fft | power_spectrum | ifft | identify_frequencies', required: true },
|
|
2745
|
+
},
|
|
2746
|
+
tier: 'free',
|
|
2747
|
+
async execute(args) {
|
|
2748
|
+
const dataStr = String(args.data);
|
|
2749
|
+
const sampleRate = Number(args.sample_rate || 1);
|
|
2750
|
+
const operation = String(args.operation).toLowerCase().replace(/[\s_-]+/g, '_');
|
|
2751
|
+
try {
|
|
2752
|
+
const values = dataStr.split(',').map(s => parseFloat(s.trim()));
|
|
2753
|
+
if (values.some(isNaN))
|
|
2754
|
+
return '**Error:** Data contains non-numeric values';
|
|
2755
|
+
let complexData = values.map(v => ({ re: v, im: 0 }));
|
|
2756
|
+
complexData = padToPow2(complexData);
|
|
2757
|
+
const N = complexData.length;
|
|
2758
|
+
switch (operation) {
|
|
2759
|
+
case 'fft': {
|
|
2760
|
+
const result = fft(complexData);
|
|
2761
|
+
const lines = [
|
|
2762
|
+
`## FFT (Cooley-Tukey)`,
|
|
2763
|
+
`**Input length:** ${values.length} (padded to ${N})`,
|
|
2764
|
+
`**Sample rate:** ${sampleRate} Hz`,
|
|
2765
|
+
'',
|
|
2766
|
+
`| Bin | Frequency (Hz) | Magnitude | Phase (rad) |`,
|
|
2767
|
+
`|-----|---------------|-----------|-------------|`,
|
|
2768
|
+
];
|
|
2769
|
+
const show = Math.min(N / 2, 20);
|
|
2770
|
+
for (let i = 0; i < show; i++) {
|
|
2771
|
+
const freq = i * sampleRate / N;
|
|
2772
|
+
const mag = cAbs(result[i]);
|
|
2773
|
+
const phase = Math.atan2(result[i].im, result[i].re);
|
|
2774
|
+
lines.push(`| ${i} | ${fmt(freq)} | ${fmt(mag)} | ${fmt(phase)} |`);
|
|
2775
|
+
}
|
|
2776
|
+
if (N / 2 > 20)
|
|
2777
|
+
lines.push(`*... showing first 20 of ${N / 2} frequency bins*`);
|
|
2778
|
+
return lines.join('\n');
|
|
2779
|
+
}
|
|
2780
|
+
case 'power_spectrum':
|
|
2781
|
+
case 'spectrum': {
|
|
2782
|
+
const result = fft(complexData);
|
|
2783
|
+
const lines = [
|
|
2784
|
+
`## Power Spectrum`,
|
|
2785
|
+
`**Input length:** ${values.length} (padded to ${N})`,
|
|
2786
|
+
`**Frequency resolution:** ${fmt(sampleRate / N)} Hz`,
|
|
2787
|
+
'',
|
|
2788
|
+
`| Frequency (Hz) | Power (dB) | Magnitude |`,
|
|
2789
|
+
`|---------------|-----------|-----------|`,
|
|
2790
|
+
];
|
|
2791
|
+
const show = Math.min(N / 2, 25);
|
|
2792
|
+
for (let i = 0; i < show; i++) {
|
|
2793
|
+
const freq = i * sampleRate / N;
|
|
2794
|
+
const mag = cAbs(result[i]) * 2 / N;
|
|
2795
|
+
const power = mag > 0 ? 20 * Math.log10(mag) : -Infinity;
|
|
2796
|
+
lines.push(`| ${fmt(freq)} | ${power === -Infinity ? '-inf' : fmt(power)} | ${fmt(mag)} |`);
|
|
2797
|
+
}
|
|
2798
|
+
return lines.join('\n');
|
|
2799
|
+
}
|
|
2800
|
+
case 'ifft':
|
|
2801
|
+
case 'inverse_fft': {
|
|
2802
|
+
// Parse as complex: "re1+im1j, re2+im2j, ..." or just real values
|
|
2803
|
+
let inputComplex;
|
|
2804
|
+
if (dataStr.includes('j') || dataStr.includes('i')) {
|
|
2805
|
+
inputComplex = dataStr.split(',').map(s => {
|
|
2806
|
+
s = s.trim().replace(/i/g, 'j');
|
|
2807
|
+
const match = s.match(/^([+-]?[\d.]+)?([+-][\d.]+)?j?$/);
|
|
2808
|
+
if (!match)
|
|
2809
|
+
return { re: parseFloat(s), im: 0 };
|
|
2810
|
+
return { re: parseFloat(match[1] || '0'), im: parseFloat(match[2] || '0') };
|
|
2811
|
+
});
|
|
2812
|
+
}
|
|
2813
|
+
else {
|
|
2814
|
+
inputComplex = values.map(v => ({ re: v, im: 0 }));
|
|
2815
|
+
}
|
|
2816
|
+
inputComplex = padToPow2(inputComplex);
|
|
2817
|
+
const result = fft(inputComplex, true);
|
|
2818
|
+
const realParts = result.map(c => fmt(c.re)).slice(0, values.length);
|
|
2819
|
+
return [
|
|
2820
|
+
`## Inverse FFT`,
|
|
2821
|
+
`**Output:** [${realParts.join(', ')}]`,
|
|
2822
|
+
`*(${result.length} points, showing first ${values.length})*`,
|
|
2823
|
+
].join('\n');
|
|
2824
|
+
}
|
|
2825
|
+
case 'identify_frequencies':
|
|
2826
|
+
case 'identify':
|
|
2827
|
+
case 'peaks': {
|
|
2828
|
+
const result = fft(complexData);
|
|
2829
|
+
const magnitudes = [];
|
|
2830
|
+
for (let i = 1; i < N / 2; i++) {
|
|
2831
|
+
const freq = i * sampleRate / N;
|
|
2832
|
+
const mag = cAbs(result[i]) * 2 / N;
|
|
2833
|
+
magnitudes.push({ freq, mag, bin: i });
|
|
2834
|
+
}
|
|
2835
|
+
// Sort by magnitude, take top peaks
|
|
2836
|
+
magnitudes.sort((a, b) => b.mag - a.mag);
|
|
2837
|
+
const threshold = magnitudes.length > 0 ? magnitudes[0].mag * 0.1 : 0;
|
|
2838
|
+
const peaks = magnitudes.filter(m => m.mag > threshold).slice(0, 10);
|
|
2839
|
+
return [
|
|
2840
|
+
`## Dominant Frequencies`,
|
|
2841
|
+
`**Sample rate:** ${sampleRate} Hz`,
|
|
2842
|
+
`**Nyquist frequency:** ${sampleRate / 2} Hz`,
|
|
2843
|
+
'',
|
|
2844
|
+
`| Frequency (Hz) | Magnitude | Relative |`,
|
|
2845
|
+
`|---------------|-----------|----------|`,
|
|
2846
|
+
...peaks.map(p => `| ${fmt(p.freq)} | ${fmt(p.mag)} | ${fmt(p.mag / peaks[0].mag * 100)}% |`),
|
|
2847
|
+
'',
|
|
2848
|
+
`*Found ${peaks.length} significant frequency component(s) above 10% of peak.*`,
|
|
2849
|
+
].join('\n');
|
|
2850
|
+
}
|
|
2851
|
+
default:
|
|
2852
|
+
return `Unknown operation: "${operation}". Supported: fft, power_spectrum, ifft, identify_frequencies`;
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
catch (err) {
|
|
2856
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2857
|
+
}
|
|
2858
|
+
},
|
|
2859
|
+
});
|
|
2860
|
+
// ── Tool 10: oeis_lookup ─────────────────────────────────────────────────
|
|
2861
|
+
registerTool({
|
|
2862
|
+
name: 'oeis_lookup',
|
|
2863
|
+
description: 'Look up integer sequences in the OEIS (Online Encyclopedia of Integer Sequences). Search by sequence values or keywords.',
|
|
2864
|
+
parameters: {
|
|
2865
|
+
sequence: { type: 'string', description: 'Comma-separated integers "1,1,2,3,5,8" or keyword query "catalan numbers"', required: true },
|
|
2866
|
+
search_type: { type: 'string', description: 'Search type: sequence | keyword (default: auto-detect)', required: false },
|
|
2867
|
+
},
|
|
2868
|
+
tier: 'free',
|
|
2869
|
+
async execute(args) {
|
|
2870
|
+
const query = String(args.sequence).trim();
|
|
2871
|
+
let searchType = String(args.search_type || '').toLowerCase();
|
|
2872
|
+
// Auto-detect: if all comma-separated parts are numbers, treat as sequence
|
|
2873
|
+
if (!searchType) {
|
|
2874
|
+
const parts = query.split(',').map(s => s.trim());
|
|
2875
|
+
searchType = parts.every(p => /^-?\d+$/.test(p)) ? 'sequence' : 'keyword';
|
|
2876
|
+
}
|
|
2877
|
+
try {
|
|
2878
|
+
const searchQuery = searchType === 'sequence'
|
|
2879
|
+
? query.split(',').map(s => s.trim()).join(',')
|
|
2880
|
+
: query;
|
|
2881
|
+
const url = `https://oeis.org/search?fmt=json&q=${encodeURIComponent(searchQuery)}&start=0`;
|
|
2882
|
+
const controller = new AbortController();
|
|
2883
|
+
const timeout = setTimeout(() => controller.abort(), 15000);
|
|
2884
|
+
const res = await fetch(url, {
|
|
2885
|
+
headers: { 'User-Agent': 'KBot/3.4 (Math Tools)' },
|
|
2886
|
+
signal: controller.signal,
|
|
2887
|
+
});
|
|
2888
|
+
clearTimeout(timeout);
|
|
2889
|
+
if (!res.ok) {
|
|
2890
|
+
return `**Error:** OEIS returned HTTP ${res.status}. The service may be temporarily unavailable.`;
|
|
2891
|
+
}
|
|
2892
|
+
const data = await res.json();
|
|
2893
|
+
if (!data.results || data.results.length === 0) {
|
|
2894
|
+
return `## OEIS Lookup\n**Query:** ${query}\n**No results found.**\n\nTry a longer sequence or different keywords.`;
|
|
2895
|
+
}
|
|
2896
|
+
const lines = [
|
|
2897
|
+
`## OEIS Lookup`,
|
|
2898
|
+
`**Query:** ${query} (${searchType})`,
|
|
2899
|
+
`**Results:** ${data.count ?? data.results.length} found`,
|
|
2900
|
+
'',
|
|
2901
|
+
];
|
|
2902
|
+
for (const seq of data.results.slice(0, 5)) {
|
|
2903
|
+
const id = `A${String(seq.number).padStart(6, '0')}`;
|
|
2904
|
+
const terms = seq.data?.split(',').slice(0, 15).join(', ') || '';
|
|
2905
|
+
lines.push(`### [${id}](https://oeis.org/${id}) — ${seq.name}`);
|
|
2906
|
+
lines.push(`**Terms:** ${terms}, ...`);
|
|
2907
|
+
if (seq.formula?.length)
|
|
2908
|
+
lines.push(`**Formula:** ${seq.formula[0]}`);
|
|
2909
|
+
if (seq.comment?.length)
|
|
2910
|
+
lines.push(`**Note:** ${seq.comment[0]}`);
|
|
2911
|
+
lines.push('');
|
|
2912
|
+
}
|
|
2913
|
+
return lines.join('\n');
|
|
2914
|
+
}
|
|
2915
|
+
catch (err) {
|
|
2916
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
2917
|
+
return '**Error:** OEIS request timed out (15s). Try again or check your connection.';
|
|
2918
|
+
}
|
|
2919
|
+
return `**Error:** ${err instanceof Error ? err.message : String(err)}`;
|
|
2920
|
+
}
|
|
2921
|
+
},
|
|
2922
|
+
});
|
|
2923
|
+
}
|
|
2924
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2925
|
+
// INTERNAL: Basic symbolic integration for simple forms
|
|
2926
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
2927
|
+
/** Attempt basic symbolic integration. Returns null if too complex. */
|
|
2928
|
+
function tryIntegrate(expr, v) {
|
|
2929
|
+
// Constant (wrt v)
|
|
2930
|
+
if (!containsVar(expr, v)) {
|
|
2931
|
+
return { type: 'mul', left: expr, right: { type: 'var', name: v } };
|
|
2932
|
+
}
|
|
2933
|
+
switch (expr.type) {
|
|
2934
|
+
case 'var': {
|
|
2935
|
+
// integral of x dx = x^2 / 2
|
|
2936
|
+
if (expr.name === v) {
|
|
2937
|
+
return { type: 'div', left: { type: 'pow', base: expr, exp: { type: 'num', value: 2 } }, right: { type: 'num', value: 2 } };
|
|
2938
|
+
}
|
|
2939
|
+
return null;
|
|
2940
|
+
}
|
|
2941
|
+
case 'num': {
|
|
2942
|
+
return { type: 'mul', left: expr, right: { type: 'var', name: v } };
|
|
2943
|
+
}
|
|
2944
|
+
case 'neg': {
|
|
2945
|
+
const inner = tryIntegrate(expr.arg, v);
|
|
2946
|
+
return inner ? { type: 'neg', arg: inner } : null;
|
|
2947
|
+
}
|
|
2948
|
+
case 'add': {
|
|
2949
|
+
const l = tryIntegrate(expr.left, v);
|
|
2950
|
+
const r = tryIntegrate(expr.right, v);
|
|
2951
|
+
return l && r ? { type: 'add', left: l, right: r } : null;
|
|
2952
|
+
}
|
|
2953
|
+
case 'sub': {
|
|
2954
|
+
const l = tryIntegrate(expr.left, v);
|
|
2955
|
+
const r = tryIntegrate(expr.right, v);
|
|
2956
|
+
return l && r ? { type: 'sub', left: l, right: r } : null;
|
|
2957
|
+
}
|
|
2958
|
+
case 'mul': {
|
|
2959
|
+
// c * f(x) or f(x) * c
|
|
2960
|
+
if (!containsVar(expr.left, v)) {
|
|
2961
|
+
const inner = tryIntegrate(expr.right, v);
|
|
2962
|
+
return inner ? { type: 'mul', left: expr.left, right: inner } : null;
|
|
2963
|
+
}
|
|
2964
|
+
if (!containsVar(expr.right, v)) {
|
|
2965
|
+
const inner = tryIntegrate(expr.left, v);
|
|
2966
|
+
return inner ? { type: 'mul', left: expr.right, right: inner } : null;
|
|
2967
|
+
}
|
|
2968
|
+
return null; // product of two variable expressions — not handled
|
|
2969
|
+
}
|
|
2970
|
+
case 'div': {
|
|
2971
|
+
// f(x) / c
|
|
2972
|
+
if (!containsVar(expr.right, v)) {
|
|
2973
|
+
const inner = tryIntegrate(expr.left, v);
|
|
2974
|
+
return inner ? { type: 'div', left: inner, right: expr.right } : null;
|
|
2975
|
+
}
|
|
2976
|
+
// 1 / x
|
|
2977
|
+
if (expr.left.type === 'num' && expr.left.value === 1 && expr.right.type === 'var' && expr.right.name === v) {
|
|
2978
|
+
return { type: 'fn', name: 'log', arg: { type: 'fn', name: 'abs', arg: { type: 'var', name: v } } };
|
|
2979
|
+
}
|
|
2980
|
+
return null;
|
|
2981
|
+
}
|
|
2982
|
+
case 'pow': {
|
|
2983
|
+
// x^n where n is constant
|
|
2984
|
+
if (expr.base.type === 'var' && expr.base.name === v && !containsVar(expr.exp, v)) {
|
|
2985
|
+
if (expr.exp.type === 'num' && expr.exp.value === -1) {
|
|
2986
|
+
// integral of x^(-1) = ln|x|
|
|
2987
|
+
return { type: 'fn', name: 'log', arg: { type: 'fn', name: 'abs', arg: { type: 'var', name: v } } };
|
|
2988
|
+
}
|
|
2989
|
+
// integral of x^n = x^(n+1) / (n+1)
|
|
2990
|
+
const newExp = { type: 'add', left: expr.exp, right: { type: 'num', value: 1 } };
|
|
2991
|
+
return { type: 'div', left: { type: 'pow', base: expr.base, exp: newExp }, right: newExp };
|
|
2992
|
+
}
|
|
2993
|
+
// e^x
|
|
2994
|
+
if (expr.base.type === 'num' && Math.abs(expr.base.value - Math.E) < 1e-10 && expr.exp.type === 'var' && expr.exp.name === v) {
|
|
2995
|
+
return expr;
|
|
2996
|
+
}
|
|
2997
|
+
// a^x = a^x / ln(a)
|
|
2998
|
+
if (!containsVar(expr.base, v) && expr.exp.type === 'var' && expr.exp.name === v) {
|
|
2999
|
+
return { type: 'div', left: expr, right: { type: 'fn', name: 'log', arg: expr.base } };
|
|
3000
|
+
}
|
|
3001
|
+
return null;
|
|
3002
|
+
}
|
|
3003
|
+
case 'fn': {
|
|
3004
|
+
// Only if the argument is just the variable
|
|
3005
|
+
if (expr.arg.type !== 'var' || expr.arg.name !== v) {
|
|
3006
|
+
// Check for simple chain rule: f(ax+b)
|
|
3007
|
+
// For now, return null for complex arguments
|
|
3008
|
+
return null;
|
|
3009
|
+
}
|
|
3010
|
+
switch (expr.name) {
|
|
3011
|
+
case 'sin': return { type: 'neg', arg: { type: 'fn', name: 'cos', arg: expr.arg } };
|
|
3012
|
+
case 'cos': return { type: 'fn', name: 'sin', arg: expr.arg };
|
|
3013
|
+
case 'exp': return expr;
|
|
3014
|
+
case 'log': return { type: 'sub', left: { type: 'mul', left: { type: 'var', name: v }, right: { type: 'fn', name: 'log', arg: { type: 'var', name: v } } }, right: { type: 'var', name: v } };
|
|
3015
|
+
// 1/cos^2(x) dx = tan(x)
|
|
3016
|
+
default: return null;
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
//# sourceMappingURL=lab-math.js.map
|