exprify 1.0.4 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/HISTORY.md +49 -0
- package/README.md +109 -182
- package/SECURITY.md +18 -0
- package/bin/cli.mjs +234 -0
- package/dist/exprify.cjs.cjs +3558 -1220
- package/dist/exprify.cjs.cjs.map +1 -1
- package/dist/exprify.esm.js +3558 -1220
- package/dist/exprify.esm.js.map +1 -1
- package/dist/exprify.js +3560 -1222
- package/dist/exprify.js.map +1 -1
- package/dist/exprify.min.js +2 -2
- package/dist/exprify.min.js.map +1 -1
- package/package.json +44 -17
- package/src/core/context.js +35 -27
- package/src/core/exprify.js +880 -0
- package/src/function/executor.js +29 -20
- package/src/function/internal.js +1150 -153
- package/src/function/registry.js +23 -16
- package/src/index.js +1 -1
- package/src/math/bignumber.js +31 -0
- package/src/math/fraction.js +112 -0
- package/src/math/operations.js +38 -24
- package/src/parser/astBuild.js +276 -214
- package/src/parser/evaluator.js +431 -171
- package/src/parser/tokenizer.js +179 -146
- package/src/utils/decimal.js +264 -0
- package/src/utils/globalUnits.js +43 -35
- package/src/utils/matrix.js +14 -14
- package/src/utils/store.js +69 -47
- package/src/variables/store.js +18 -15
- package/src/core/Exprify.js +0 -369
package/src/function/internal.js
CHANGED
|
@@ -1,29 +1,33 @@
|
|
|
1
|
-
import { unwrapDenseMatrix, wrapDenseMatrix } from
|
|
1
|
+
import { unwrapDenseMatrix, wrapDenseMatrix } from '../utils/matrix.js';
|
|
2
|
+
import { fraction as makeFrac, isFraction, numer, denom } from '../math/fraction.js';
|
|
3
|
+
import { bigNumber as makeBN, isBigNumber } from '../math/bignumber.js';
|
|
2
4
|
|
|
5
|
+
/** @param {any[]} matrix */
|
|
3
6
|
function validateSquareMatrix(matrix) {
|
|
4
7
|
matrix = unwrapDenseMatrix(matrix);
|
|
5
8
|
if (!Array.isArray(matrix) || matrix.length === 0) {
|
|
6
|
-
throw new Error(
|
|
9
|
+
throw new Error('det() expects a non-empty matrix');
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
if (!matrix.every(Array.isArray)) {
|
|
10
|
-
throw new Error(
|
|
13
|
+
throw new Error('det() expects a 2D matrix');
|
|
11
14
|
}
|
|
12
15
|
|
|
13
16
|
const size = matrix.length;
|
|
14
17
|
if (!matrix.every((row) => row.length === size)) {
|
|
15
|
-
throw new Error(
|
|
18
|
+
throw new Error('det() expects a square matrix');
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
for (const row of matrix) {
|
|
19
22
|
for (const value of row) {
|
|
20
|
-
if (typeof value !==
|
|
21
|
-
throw new Error(
|
|
23
|
+
if (typeof value !== 'number' && typeof value !== 'bigint') {
|
|
24
|
+
throw new Error('det() matrix values must be numeric');
|
|
22
25
|
}
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
|
|
30
|
+
/** @param {any[]} matrix */
|
|
27
31
|
function determinant(matrix) {
|
|
28
32
|
matrix = unwrapDenseMatrix(matrix);
|
|
29
33
|
validateSquareMatrix(matrix);
|
|
@@ -33,31 +37,37 @@ function determinant(matrix) {
|
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
if (matrix.length === 2) {
|
|
36
|
-
return
|
|
40
|
+
return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
|
|
37
41
|
}
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
43
|
+
// Laplace expansion: sum of (-1)^col * M[0][col] * det(minor)
|
|
44
|
+
return matrix[0].reduce(
|
|
45
|
+
(/** @type {number} */ sum, /** @type {number} */ value, /** @type {number} */ columnIndex) => {
|
|
46
|
+
const minor = matrix
|
|
47
|
+
.slice(1)
|
|
48
|
+
.map((row) =>
|
|
49
|
+
row.filter((/** @type {any} */ _, /** @type {number} */ index) => index !== columnIndex)
|
|
50
|
+
);
|
|
51
|
+
const cofactor = columnIndex % 2 === 0 ? value : -value;
|
|
52
|
+
return sum + cofactor * determinant(minor);
|
|
53
|
+
},
|
|
54
|
+
0
|
|
55
|
+
);
|
|
51
56
|
}
|
|
52
57
|
|
|
58
|
+
/** @param {any} value */
|
|
53
59
|
function asMatrixData(value) {
|
|
54
60
|
const data = unwrapDenseMatrix(value);
|
|
55
61
|
if (!Array.isArray(data)) {
|
|
56
|
-
throw new Error(
|
|
62
|
+
throw new Error('Expected matrix data');
|
|
57
63
|
}
|
|
58
64
|
return data;
|
|
59
65
|
}
|
|
60
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @param {any[]} coefficients
|
|
69
|
+
* @param {number[]} constants
|
|
70
|
+
*/
|
|
61
71
|
function solveLinearSystem(coefficients, constants) {
|
|
62
72
|
const n = coefficients.length;
|
|
63
73
|
const augmented = coefficients.map((row, rowIndex) => [...row, constants[rowIndex]]);
|
|
@@ -75,7 +85,7 @@ function solveLinearSystem(coefficients, constants) {
|
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
if (maxValue === 0) {
|
|
78
|
-
throw new Error(
|
|
88
|
+
throw new Error('Linear system is singular');
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
if (maxRow !== pivot) {
|
|
@@ -88,7 +98,9 @@ function solveLinearSystem(coefficients, constants) {
|
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
for (let row = 0; row < n; row++) {
|
|
91
|
-
if (row === pivot)
|
|
101
|
+
if (row === pivot) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
92
104
|
const factor = augmented[row][pivot];
|
|
93
105
|
for (let col = pivot; col <= n; col++) {
|
|
94
106
|
augmented[row][col] -= factor * augmented[pivot][col];
|
|
@@ -99,6 +111,7 @@ function solveLinearSystem(coefficients, constants) {
|
|
|
99
111
|
return augmented.map((row) => row[n]);
|
|
100
112
|
}
|
|
101
113
|
|
|
114
|
+
/** @param {any} input */
|
|
102
115
|
function lupDecomposition(input) {
|
|
103
116
|
const matrix = asMatrixData(input).map((row) => [...row]);
|
|
104
117
|
validateSquareMatrix(matrix);
|
|
@@ -119,7 +132,7 @@ function lupDecomposition(input) {
|
|
|
119
132
|
}
|
|
120
133
|
|
|
121
134
|
if (maxValue === 0) {
|
|
122
|
-
throw new Error(
|
|
135
|
+
throw new Error('Matrix is singular');
|
|
123
136
|
}
|
|
124
137
|
|
|
125
138
|
if (maxRow !== pivot) {
|
|
@@ -137,8 +150,12 @@ function lupDecomposition(input) {
|
|
|
137
150
|
|
|
138
151
|
const L = matrix.map((row, rowIndex) =>
|
|
139
152
|
row.map((value, colIndex) => {
|
|
140
|
-
if (rowIndex === colIndex)
|
|
141
|
-
|
|
153
|
+
if (rowIndex === colIndex) {
|
|
154
|
+
return 1;
|
|
155
|
+
}
|
|
156
|
+
if (rowIndex > colIndex) {
|
|
157
|
+
return value;
|
|
158
|
+
}
|
|
142
159
|
return 0;
|
|
143
160
|
})
|
|
144
161
|
);
|
|
@@ -150,10 +167,14 @@ function lupDecomposition(input) {
|
|
|
150
167
|
return {
|
|
151
168
|
L: wrapDenseMatrix(L),
|
|
152
169
|
U: wrapDenseMatrix(U),
|
|
153
|
-
p: permutation
|
|
170
|
+
p: permutation,
|
|
154
171
|
};
|
|
155
172
|
}
|
|
156
173
|
|
|
174
|
+
/**
|
|
175
|
+
* @param {any} aInput
|
|
176
|
+
* @param {{ exprify: string; data: any; size: number[]; }} bInput
|
|
177
|
+
*/
|
|
157
178
|
function linearSolve(aInput, bInput) {
|
|
158
179
|
const { L, U, p } = lupDecomposition(aInput);
|
|
159
180
|
const a = asMatrixData(aInput);
|
|
@@ -161,7 +182,7 @@ function linearSolve(aInput, bInput) {
|
|
|
161
182
|
const bVector = Array.isArray(bData[0]) ? bData.map((row) => row[0]) : bData;
|
|
162
183
|
|
|
163
184
|
if (a.length !== bVector.length) {
|
|
164
|
-
throw new Error(
|
|
185
|
+
throw new Error('Right-hand side dimension mismatch');
|
|
165
186
|
}
|
|
166
187
|
|
|
167
188
|
const permutedB = p.map((index) => bVector[index]);
|
|
@@ -186,6 +207,10 @@ function linearSolve(aInput, bInput) {
|
|
|
186
207
|
return wrapDenseMatrix(x.map((value) => [value]));
|
|
187
208
|
}
|
|
188
209
|
|
|
210
|
+
/**
|
|
211
|
+
* @param {any} aInput
|
|
212
|
+
* @param {any} qInput
|
|
213
|
+
*/
|
|
189
214
|
function solveLyapunov(aInput, qInput) {
|
|
190
215
|
const A = asMatrixData(aInput).map((row) => [...row]);
|
|
191
216
|
const Q = asMatrixData(qInput).map((row) => [...row]);
|
|
@@ -194,7 +219,7 @@ function solveLyapunov(aInput, qInput) {
|
|
|
194
219
|
|
|
195
220
|
const n = A.length;
|
|
196
221
|
if (Q.length !== n) {
|
|
197
|
-
throw new Error(
|
|
222
|
+
throw new Error('A and Q must have the same dimensions');
|
|
198
223
|
}
|
|
199
224
|
|
|
200
225
|
const coefficients = [];
|
|
@@ -224,39 +249,50 @@ function solveLyapunov(aInput, qInput) {
|
|
|
224
249
|
return wrapDenseMatrix(X);
|
|
225
250
|
}
|
|
226
251
|
|
|
252
|
+
/**
|
|
253
|
+
* @param {any[]} coefficients
|
|
254
|
+
* @param {number} x
|
|
255
|
+
*/
|
|
227
256
|
function evaluatePolynomial(coefficients, x) {
|
|
228
|
-
return coefficients.reduce((sum, coefficient, index) => sum +
|
|
257
|
+
return coefficients.reduce((sum, coefficient, index) => sum + coefficient * x ** index, 0);
|
|
229
258
|
}
|
|
230
259
|
|
|
260
|
+
/**
|
|
261
|
+
* @param {any[]} coefficients
|
|
262
|
+
* @param {number} root
|
|
263
|
+
*/
|
|
231
264
|
function syntheticDivide(coefficients, root) {
|
|
232
265
|
const descending = [...coefficients].reverse();
|
|
233
266
|
const quotient = [descending[0]];
|
|
234
267
|
|
|
235
268
|
for (let index = 1; index < descending.length - 1; index++) {
|
|
236
|
-
quotient.push(descending[index] +
|
|
269
|
+
quotient.push(descending[index] + quotient[index - 1] * root);
|
|
237
270
|
}
|
|
238
271
|
|
|
239
|
-
const remainder = descending[descending.length - 1] +
|
|
272
|
+
const remainder = descending[descending.length - 1] + quotient[quotient.length - 1] * root;
|
|
240
273
|
return {
|
|
241
274
|
quotient: quotient.reverse(),
|
|
242
|
-
remainder
|
|
275
|
+
remainder,
|
|
243
276
|
};
|
|
244
277
|
}
|
|
245
278
|
|
|
279
|
+
/**
|
|
280
|
+
* @param {any[]} coefficients
|
|
281
|
+
*/
|
|
246
282
|
function solveQuadratic(coefficients) {
|
|
247
283
|
const [c, b, a] = coefficients;
|
|
248
|
-
const discriminant =
|
|
284
|
+
const discriminant = b ** 2 - 4 * a * c;
|
|
249
285
|
if (discriminant < 0) {
|
|
250
|
-
throw new Error(
|
|
286
|
+
throw new Error('Only real roots are supported');
|
|
251
287
|
}
|
|
252
288
|
|
|
253
289
|
const sqrtDisc = Math.sqrt(discriminant);
|
|
254
|
-
return [
|
|
255
|
-
(-b + sqrtDisc) / (2 * a),
|
|
256
|
-
(-b - sqrtDisc) / (2 * a)
|
|
257
|
-
];
|
|
290
|
+
return [(-b + sqrtDisc) / (2 * a), (-b - sqrtDisc) / (2 * a)];
|
|
258
291
|
}
|
|
259
292
|
|
|
293
|
+
/**
|
|
294
|
+
* @param {any[]} coefficients
|
|
295
|
+
*/
|
|
260
296
|
function polynomialRoots(...coefficients) {
|
|
261
297
|
while (coefficients.length > 1 && coefficients[coefficients.length - 1] === 0) {
|
|
262
298
|
coefficients.pop();
|
|
@@ -264,7 +300,7 @@ function polynomialRoots(...coefficients) {
|
|
|
264
300
|
|
|
265
301
|
const degree = coefficients.length - 1;
|
|
266
302
|
if (degree < 1) {
|
|
267
|
-
throw new Error(
|
|
303
|
+
throw new Error('polynomialRoot() expects at least a linear polynomial');
|
|
268
304
|
}
|
|
269
305
|
|
|
270
306
|
if (degree === 1) {
|
|
@@ -276,9 +312,9 @@ function polynomialRoots(...coefficients) {
|
|
|
276
312
|
return solveQuadratic(coefficients);
|
|
277
313
|
}
|
|
278
314
|
|
|
315
|
+
// Rational root theorem: possible roots are divisors of the constant term
|
|
279
316
|
if (degree === 3) {
|
|
280
317
|
const constant = coefficients[0];
|
|
281
|
-
const leading = coefficients[3];
|
|
282
318
|
const candidates = [];
|
|
283
319
|
const limit = Math.abs(constant);
|
|
284
320
|
|
|
@@ -297,33 +333,58 @@ function polynomialRoots(...coefficients) {
|
|
|
297
333
|
}
|
|
298
334
|
}
|
|
299
335
|
|
|
300
|
-
throw new Error(
|
|
336
|
+
throw new Error('polynomialRoot() currently supports degree up to 3');
|
|
301
337
|
}
|
|
302
338
|
|
|
339
|
+
/**
|
|
340
|
+
* @param {any[]} a
|
|
341
|
+
* @param {any[]} b
|
|
342
|
+
*/
|
|
303
343
|
function dotProduct(a, b) {
|
|
304
|
-
return a.reduce((sum, value, index) => sum +
|
|
344
|
+
return a.reduce((sum, value, index) => sum + value * b[index], 0);
|
|
305
345
|
}
|
|
306
346
|
|
|
347
|
+
/**
|
|
348
|
+
* @param {any[]} vector
|
|
349
|
+
*/
|
|
307
350
|
function vectorNorm(vector) {
|
|
308
351
|
return Math.sqrt(dotProduct(vector, vector));
|
|
309
352
|
}
|
|
310
353
|
|
|
354
|
+
/**
|
|
355
|
+
* @param {any[]} vector
|
|
356
|
+
* @param {number} scalar
|
|
357
|
+
*/
|
|
311
358
|
function scaleVector(vector, scalar) {
|
|
312
359
|
return vector.map((value) => value * scalar);
|
|
313
360
|
}
|
|
314
361
|
|
|
362
|
+
/**
|
|
363
|
+
* @param {any} a
|
|
364
|
+
* @param {any} b
|
|
365
|
+
*/
|
|
315
366
|
function subtractVectors(a, b) {
|
|
316
|
-
return a.map(
|
|
367
|
+
return a.map(
|
|
368
|
+
(/** @type {number} */ value, /** @type {string | number} */ index) => value - b[index]
|
|
369
|
+
);
|
|
317
370
|
}
|
|
318
371
|
|
|
372
|
+
/**
|
|
373
|
+
* @param {any[]} matrix
|
|
374
|
+
*/
|
|
319
375
|
function transpose(matrix) {
|
|
320
|
-
return matrix[0].map((_,
|
|
376
|
+
return matrix[0].map((/** @type {any} */ _, /** @type {string | number} */ colIndex) =>
|
|
377
|
+
matrix.map((row) => row[colIndex])
|
|
378
|
+
);
|
|
321
379
|
}
|
|
322
380
|
|
|
381
|
+
/**
|
|
382
|
+
* @param {any} input
|
|
383
|
+
*/
|
|
323
384
|
function qrDecomposition(input) {
|
|
324
385
|
const A = asMatrixData(input).map((row) => [...row]);
|
|
325
386
|
if (!A.length || !A.every((row) => row.length === A[0].length)) {
|
|
326
|
-
throw new Error(
|
|
387
|
+
throw new Error('qr() expects a rectangular matrix');
|
|
327
388
|
}
|
|
328
389
|
|
|
329
390
|
const rowCount = A.length;
|
|
@@ -341,7 +402,7 @@ function qrDecomposition(input) {
|
|
|
341
402
|
|
|
342
403
|
const norm = vectorNorm(vector);
|
|
343
404
|
if (norm === 0) {
|
|
344
|
-
throw new Error(
|
|
405
|
+
throw new Error('qr() requires linearly independent columns');
|
|
345
406
|
}
|
|
346
407
|
|
|
347
408
|
qColumns.push(scaleVector(vector, 1 / norm));
|
|
@@ -374,22 +435,26 @@ function qrDecomposition(input) {
|
|
|
374
435
|
|
|
375
436
|
return {
|
|
376
437
|
Q: wrapDenseMatrix(Q),
|
|
377
|
-
R: wrapDenseMatrix(fullR)
|
|
438
|
+
R: wrapDenseMatrix(fullR),
|
|
378
439
|
};
|
|
379
440
|
}
|
|
380
441
|
|
|
442
|
+
/**
|
|
443
|
+
* @param {string} expression
|
|
444
|
+
*/
|
|
381
445
|
function splitTerms(expression) {
|
|
382
|
-
const normalized = expression.replace(/\s+/g,
|
|
446
|
+
const normalized = expression.replace(/\s+/g, '');
|
|
383
447
|
if (!normalized) {
|
|
384
448
|
return [];
|
|
385
449
|
}
|
|
386
450
|
|
|
387
|
-
return normalized
|
|
388
|
-
.replace(/-/g, "+-")
|
|
389
|
-
.split("+")
|
|
390
|
-
.filter(Boolean);
|
|
451
|
+
return normalized.replace(/-/g, '+-').split('+').filter(Boolean);
|
|
391
452
|
}
|
|
392
453
|
|
|
454
|
+
/**
|
|
455
|
+
* @param {string} expression
|
|
456
|
+
* @param {string} variable
|
|
457
|
+
*/
|
|
393
458
|
function parsePolynomial(expression, variable) {
|
|
394
459
|
const terms = splitTerms(expression);
|
|
395
460
|
const coefficients = new Map();
|
|
@@ -399,35 +464,37 @@ function parsePolynomial(expression, variable) {
|
|
|
399
464
|
const [rawCoeff, rawPower] = term.split(variable);
|
|
400
465
|
let coefficient;
|
|
401
466
|
|
|
402
|
-
if (rawCoeff ===
|
|
403
|
-
|
|
404
|
-
else {
|
|
405
|
-
|
|
467
|
+
if (rawCoeff === '' || rawCoeff === '+') {
|
|
468
|
+
coefficient = 1;
|
|
469
|
+
} else if (rawCoeff === '-') {
|
|
470
|
+
coefficient = -1;
|
|
471
|
+
} else {
|
|
472
|
+
const cleaned = rawCoeff.endsWith('*') ? rawCoeff.slice(0, -1) : rawCoeff;
|
|
406
473
|
coefficient = Number(cleaned);
|
|
407
474
|
}
|
|
408
475
|
|
|
409
476
|
if (!Number.isFinite(coefficient)) {
|
|
410
|
-
throw new Error(
|
|
477
|
+
throw new Error('Unsupported algebra term');
|
|
411
478
|
}
|
|
412
479
|
|
|
413
480
|
let power = 1;
|
|
414
481
|
if (rawPower) {
|
|
415
|
-
if (!rawPower.startsWith(
|
|
416
|
-
throw new Error(
|
|
482
|
+
if (!rawPower.startsWith('^')) {
|
|
483
|
+
throw new Error('Unsupported algebra term');
|
|
417
484
|
}
|
|
418
485
|
|
|
419
486
|
power = Number(rawPower.slice(1));
|
|
420
487
|
}
|
|
421
488
|
|
|
422
489
|
if (!Number.isInteger(power) || power < 0) {
|
|
423
|
-
throw new Error(
|
|
490
|
+
throw new Error('Only non-negative integer powers are supported');
|
|
424
491
|
}
|
|
425
492
|
|
|
426
493
|
coefficients.set(power, (coefficients.get(power) || 0) + coefficient);
|
|
427
494
|
} else {
|
|
428
495
|
const constant = Number(term);
|
|
429
496
|
if (!Number.isFinite(constant)) {
|
|
430
|
-
throw new Error(
|
|
497
|
+
throw new Error('Unsupported algebra term');
|
|
431
498
|
}
|
|
432
499
|
coefficients.set(0, (coefficients.get(0) || 0) + constant);
|
|
433
500
|
}
|
|
@@ -436,177 +503,1107 @@ function parsePolynomial(expression, variable) {
|
|
|
436
503
|
return coefficients;
|
|
437
504
|
}
|
|
438
505
|
|
|
506
|
+
/**
|
|
507
|
+
* @param {any[] | Map<any, any>} coefficients
|
|
508
|
+
* @param {string} variable
|
|
509
|
+
*/
|
|
439
510
|
function formatPolynomial(coefficients, variable) {
|
|
440
511
|
const ordered = [...coefficients.entries()]
|
|
441
512
|
.filter(([, coefficient]) => coefficient !== 0)
|
|
442
513
|
.sort((a, b) => b[0] - a[0]);
|
|
443
514
|
|
|
444
515
|
if (!ordered.length) {
|
|
445
|
-
return
|
|
516
|
+
return '0';
|
|
446
517
|
}
|
|
447
518
|
|
|
448
|
-
return ordered
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
? `${variable}^${power}`
|
|
460
|
-
|
|
461
|
-
}
|
|
519
|
+
return ordered
|
|
520
|
+
.map(([power, coefficient], index) => {
|
|
521
|
+
const negative = coefficient < 0;
|
|
522
|
+
const absCoeff = Math.abs(coefficient);
|
|
523
|
+
let body;
|
|
524
|
+
|
|
525
|
+
if (power === 0) {
|
|
526
|
+
body = `${absCoeff}`;
|
|
527
|
+
} else if (power === 1) {
|
|
528
|
+
body = absCoeff === 1 ? variable : `${absCoeff} * ${variable}`;
|
|
529
|
+
} else {
|
|
530
|
+
body = absCoeff === 1 ? `${variable}^${power}` : `${absCoeff} * ${variable}^${power}`;
|
|
531
|
+
}
|
|
462
532
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
533
|
+
if (index === 0) {
|
|
534
|
+
return negative ? `-${body}` : body;
|
|
535
|
+
}
|
|
466
536
|
|
|
467
|
-
|
|
468
|
-
|
|
537
|
+
return negative ? `- ${body}` : `+ ${body}`;
|
|
538
|
+
})
|
|
539
|
+
.join(' ');
|
|
469
540
|
}
|
|
470
541
|
|
|
542
|
+
/**
|
|
543
|
+
* @param {string} expression
|
|
544
|
+
*/
|
|
471
545
|
function simplifyExpression(expression) {
|
|
472
|
-
const compact = expression.replace(/\s+/g,
|
|
546
|
+
const compact = expression.replace(/\s+/g, '');
|
|
473
547
|
const variableMatch = compact.match(/[a-zA-Z]+/);
|
|
474
|
-
const variable = variableMatch?.[0] ||
|
|
548
|
+
const variable = variableMatch?.[0] || 'x';
|
|
475
549
|
const coefficients = parsePolynomial(expression, variable);
|
|
476
550
|
return formatPolynomial(coefficients, variable);
|
|
477
551
|
}
|
|
478
552
|
|
|
553
|
+
/**
|
|
554
|
+
* @param {string} expression
|
|
555
|
+
* @param {string} variable
|
|
556
|
+
*/
|
|
479
557
|
function derivativeExpression(expression, variable) {
|
|
480
558
|
const coefficients = parsePolynomial(expression, variable);
|
|
481
559
|
const derived = new Map();
|
|
482
560
|
|
|
483
561
|
for (const [power, coefficient] of coefficients.entries()) {
|
|
484
|
-
if (power === 0)
|
|
485
|
-
|
|
562
|
+
if (power === 0) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
derived.set(power - 1, (derived.get(power - 1) || 0) + coefficient * power);
|
|
486
566
|
}
|
|
487
567
|
|
|
488
568
|
return formatPolynomial(derived, variable);
|
|
489
569
|
}
|
|
490
570
|
|
|
571
|
+
/**
|
|
572
|
+
* @param {number} a
|
|
573
|
+
* @param {number} b
|
|
574
|
+
*/
|
|
575
|
+
function _gcd(a, b) {
|
|
576
|
+
a = Math.abs(a);
|
|
577
|
+
b = Math.abs(b);
|
|
578
|
+
while (b) {
|
|
579
|
+
[a, b] = [b, a % b];
|
|
580
|
+
}
|
|
581
|
+
return a;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* @param {any} n
|
|
586
|
+
*/
|
|
587
|
+
function _gamma(n) {
|
|
588
|
+
if (n === 0) {
|
|
589
|
+
throw new Error('gamma(0) is undefined');
|
|
590
|
+
}
|
|
591
|
+
if (Number.isInteger(n) && n < 0) {
|
|
592
|
+
throw new Error('gamma() undefined for negative integers');
|
|
593
|
+
}
|
|
594
|
+
if (Number.isInteger(n) && n > 0) {
|
|
595
|
+
let r = 1;
|
|
596
|
+
for (let i = 2; i < n; i++) {
|
|
597
|
+
r *= i;
|
|
598
|
+
}
|
|
599
|
+
return r;
|
|
600
|
+
}
|
|
601
|
+
const g = 7;
|
|
602
|
+
const c = [
|
|
603
|
+
0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313,
|
|
604
|
+
-176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6,
|
|
605
|
+
1.5056327351493116e-7,
|
|
606
|
+
];
|
|
607
|
+
// Euler's reflection formula: Gamma(z) = pi / (sin(pi*z) * Gamma(1-z))
|
|
608
|
+
if (n < 0.5) {
|
|
609
|
+
return Math.PI / (Math.sin(Math.PI * n) * _gamma(1 - n));
|
|
610
|
+
}
|
|
611
|
+
n -= 1;
|
|
612
|
+
let x = c[0];
|
|
613
|
+
for (let i = 1; i < g + 2; i++) {
|
|
614
|
+
x += c[i] / (n + i);
|
|
615
|
+
}
|
|
616
|
+
const t = n + g + 0.5;
|
|
617
|
+
return Math.sqrt(2 * Math.PI) * t ** (n + 0.5) * Math.exp(-t) * x;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* @param {any} n
|
|
622
|
+
*/
|
|
623
|
+
function _identity(n) {
|
|
624
|
+
return Array.from({ length: n }, (_, i) =>
|
|
625
|
+
Array.from({ length: n }, (_, j) => (i === j ? 1 : 0))
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/**
|
|
630
|
+
* @param {any[]} matrix
|
|
631
|
+
*/
|
|
632
|
+
function _inverse(matrix) {
|
|
633
|
+
const data = unwrapDenseMatrix(matrix);
|
|
634
|
+
validateSquareMatrix(matrix);
|
|
635
|
+
const n = data.length;
|
|
636
|
+
|
|
637
|
+
if (n === 2) {
|
|
638
|
+
const det = data[0][0] * data[1][1] - data[0][1] * data[1][0];
|
|
639
|
+
if (det === 0) {
|
|
640
|
+
throw new Error('Matrix is singular');
|
|
641
|
+
}
|
|
642
|
+
return wrapDenseMatrix([
|
|
643
|
+
[data[1][1] / det, -data[0][1] / det],
|
|
644
|
+
[-data[1][0] / det, data[0][0] / det],
|
|
645
|
+
]);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
const result = Array.from({ length: n }, () => Array(n).fill(0));
|
|
649
|
+
for (let col = 0; col < n; col++) {
|
|
650
|
+
const b = Array.from({ length: n }, (_, i) => (i === col ? 1 : 0));
|
|
651
|
+
const x = linearSolve(data, wrapDenseMatrix(b.map((v) => [v])));
|
|
652
|
+
const xData = unwrapDenseMatrix(x);
|
|
653
|
+
for (let row = 0; row < n; row++) {
|
|
654
|
+
result[row][col] = xData[row][0];
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return wrapDenseMatrix(result);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* @param {any} matrix
|
|
662
|
+
*/
|
|
663
|
+
function _rref(matrix) {
|
|
664
|
+
const data = unwrapDenseMatrix(matrix).map((/** @type {any} */ row) => [...row]);
|
|
665
|
+
let lead = 0;
|
|
666
|
+
const rowCount = data.length;
|
|
667
|
+
const colCount = data[0].length;
|
|
668
|
+
|
|
669
|
+
for (let r = 0; r < rowCount; r++) {
|
|
670
|
+
if (lead >= colCount) {
|
|
671
|
+
break;
|
|
672
|
+
}
|
|
673
|
+
let i = r;
|
|
674
|
+
while (Math.abs(data[i][lead]) < 1e-12) {
|
|
675
|
+
i++;
|
|
676
|
+
if (i === rowCount) {
|
|
677
|
+
i = r;
|
|
678
|
+
lead++;
|
|
679
|
+
if (lead >= colCount) {
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
if (lead >= colCount) {
|
|
685
|
+
break;
|
|
686
|
+
}
|
|
687
|
+
[data[r], data[i]] = [data[i], data[r]];
|
|
688
|
+
const pivot = data[r][lead];
|
|
689
|
+
for (let j = 0; j < colCount; j++) {
|
|
690
|
+
data[r][j] /= pivot;
|
|
691
|
+
}
|
|
692
|
+
for (let i = 0; i < rowCount; i++) {
|
|
693
|
+
if (i !== r) {
|
|
694
|
+
const factor = data[i][lead];
|
|
695
|
+
for (let j = 0; j < colCount; j++) {
|
|
696
|
+
data[i][j] -= factor * data[r][j];
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
lead++;
|
|
701
|
+
}
|
|
702
|
+
return wrapDenseMatrix(data);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* @param {any[]} a
|
|
707
|
+
* @param {any[]} b
|
|
708
|
+
*/
|
|
709
|
+
function _cross(a, b) {
|
|
710
|
+
return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* @param {any} matrix
|
|
715
|
+
*/
|
|
716
|
+
function _eig2x2(matrix) {
|
|
717
|
+
const data = unwrapDenseMatrix(matrix);
|
|
718
|
+
validateSquareMatrix(matrix);
|
|
719
|
+
const [[a, b], [c, d]] = data;
|
|
720
|
+
const trace = a + d;
|
|
721
|
+
const det = a * d - b * c;
|
|
722
|
+
const disc = trace * trace - 4 * det;
|
|
723
|
+
if (disc < 0) {
|
|
724
|
+
throw new Error('Complex eigenvalues not supported');
|
|
725
|
+
}
|
|
726
|
+
const sqrtDisc = Math.sqrt(disc);
|
|
727
|
+
const lambda1 = (trace + sqrtDisc) / 2;
|
|
728
|
+
const lambda2 = (trace - sqrtDisc) / 2;
|
|
729
|
+
|
|
730
|
+
// Solve (A - lambda*I)v = 0: pick non-zero row to solve for v1:v2 ratio
|
|
731
|
+
const eigenvec = (/** @type {number} */ lambda) => {
|
|
732
|
+
if (Math.abs(b) > 1e-12) {
|
|
733
|
+
return [1, (lambda - a) / b];
|
|
734
|
+
}
|
|
735
|
+
if (Math.abs(c) > 1e-12) {
|
|
736
|
+
return [(lambda - d) / c, 1];
|
|
737
|
+
}
|
|
738
|
+
return [1, 0];
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
const v1 = eigenvec(lambda1);
|
|
742
|
+
const norm1 = Math.sqrt(v1[0] * v1[0] + v1[1] * v1[1]);
|
|
743
|
+
const v2 = eigenvec(lambda2);
|
|
744
|
+
const norm2 = Math.sqrt(v2[0] * v2[0] + v2[1] * v2[1]);
|
|
745
|
+
|
|
746
|
+
return {
|
|
747
|
+
values: [lambda1, lambda2],
|
|
748
|
+
vectors: wrapDenseMatrix([
|
|
749
|
+
[v1[0] / norm1, v2[0] / norm2],
|
|
750
|
+
[v1[1] / norm1, v2[1] / norm2],
|
|
751
|
+
]),
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* @param {any[]} matrix
|
|
757
|
+
*/
|
|
758
|
+
function _cholesky(matrix) {
|
|
759
|
+
const data = unwrapDenseMatrix(matrix);
|
|
760
|
+
validateSquareMatrix(matrix);
|
|
761
|
+
const n = data.length;
|
|
762
|
+
const L = Array.from({ length: n }, () => Array(n).fill(0));
|
|
763
|
+
|
|
764
|
+
for (let j = 0; j < n; j++) {
|
|
765
|
+
let sum = 0;
|
|
766
|
+
for (let k = 0; k < j; k++) {
|
|
767
|
+
sum += L[j][k] * L[j][k];
|
|
768
|
+
}
|
|
769
|
+
const val = data[j][j] - sum;
|
|
770
|
+
if (val <= 0) {
|
|
771
|
+
throw new Error('Matrix is not positive definite');
|
|
772
|
+
}
|
|
773
|
+
L[j][j] = Math.sqrt(val);
|
|
774
|
+
for (let i = j + 1; i < n; i++) {
|
|
775
|
+
sum = 0;
|
|
776
|
+
for (let k = 0; k < j; k++) {
|
|
777
|
+
sum += L[i][k] * L[j][k];
|
|
778
|
+
}
|
|
779
|
+
L[i][j] = (data[i][j] - sum) / L[j][j];
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return wrapDenseMatrix(L);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* @param {any} matrix
|
|
787
|
+
*/
|
|
788
|
+
function _svd(matrix) {
|
|
789
|
+
const data = unwrapDenseMatrix(matrix);
|
|
790
|
+
const m = data.length;
|
|
791
|
+
const n = data[0].length;
|
|
792
|
+
|
|
793
|
+
if (m !== 2 || n !== 2) {
|
|
794
|
+
throw new Error('svd() currently supports 2x2 matrices only');
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const ata = [
|
|
798
|
+
[
|
|
799
|
+
data[0][0] * data[0][0] + data[1][0] * data[1][0],
|
|
800
|
+
data[0][0] * data[0][1] + data[1][0] * data[1][1],
|
|
801
|
+
],
|
|
802
|
+
[
|
|
803
|
+
data[0][1] * data[0][0] + data[1][1] * data[1][0],
|
|
804
|
+
data[0][1] * data[0][1] + data[1][1] * data[1][1],
|
|
805
|
+
],
|
|
806
|
+
];
|
|
807
|
+
|
|
808
|
+
const eigResult = _eig2x2(wrapDenseMatrix(ata));
|
|
809
|
+
const S = [
|
|
810
|
+
Math.sqrt(Math.max(0, eigResult.values[0])),
|
|
811
|
+
Math.sqrt(Math.max(0, eigResult.values[1])),
|
|
812
|
+
];
|
|
813
|
+
const vecData = unwrapDenseMatrix(eigResult.vectors);
|
|
814
|
+
const V = vecData;
|
|
815
|
+
|
|
816
|
+
const U = [
|
|
817
|
+
[
|
|
818
|
+
(data[0][0] * V[0][0] + data[0][1] * V[1][0]) / (S[0] || 1),
|
|
819
|
+
(data[0][0] * V[0][1] + data[0][1] * V[1][1]) / (S[1] || 1),
|
|
820
|
+
],
|
|
821
|
+
[
|
|
822
|
+
(data[1][0] * V[0][0] + data[1][1] * V[1][0]) / (S[0] || 1),
|
|
823
|
+
(data[1][0] * V[0][1] + data[1][1] * V[1][1]) / (S[1] || 1),
|
|
824
|
+
],
|
|
825
|
+
];
|
|
826
|
+
|
|
827
|
+
return {
|
|
828
|
+
U: wrapDenseMatrix(U),
|
|
829
|
+
S: wrapDenseMatrix([
|
|
830
|
+
[S[0], 0],
|
|
831
|
+
[0, S[1]],
|
|
832
|
+
]),
|
|
833
|
+
V: wrapDenseMatrix(V),
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
491
837
|
export const internalFunctions = {
|
|
492
|
-
|
|
493
|
-
|
|
838
|
+
fraction: (/** @type {number} */ n, /** @type {number} */ d) => makeFrac(n, d),
|
|
839
|
+
|
|
840
|
+
numer: (/** @type {any} */ v) => numer(v),
|
|
841
|
+
|
|
842
|
+
denom: (/** @type {any} */ v) => denom(v),
|
|
843
|
+
|
|
844
|
+
isFraction: (/** @type {any} */ v) => isFraction(v),
|
|
845
|
+
|
|
846
|
+
bignumber: (/** @type {any} */ n) => makeBN(n),
|
|
847
|
+
|
|
848
|
+
isBigNumber: (/** @type {any} */ v) => isBigNumber(v),
|
|
849
|
+
|
|
850
|
+
max: (/** @type {any[]} */ ...args) => {
|
|
851
|
+
if (!args.length) {
|
|
852
|
+
throw new Error('max() requires arguments');
|
|
853
|
+
}
|
|
494
854
|
return Math.max(...args);
|
|
495
855
|
},
|
|
496
856
|
|
|
497
|
-
min: (...args) => {
|
|
498
|
-
if (!args.length)
|
|
857
|
+
min: (/** @type {any[]} */ ...args) => {
|
|
858
|
+
if (!args.length) {
|
|
859
|
+
throw new Error('min() requires arguments');
|
|
860
|
+
}
|
|
499
861
|
return Math.min(...args);
|
|
500
862
|
},
|
|
501
863
|
|
|
502
|
-
abs: (x) => Math.abs(x),
|
|
864
|
+
abs: (/** @type {number} */ x) => Math.abs(x),
|
|
503
865
|
|
|
504
|
-
round: (x) => Math.round(x),
|
|
866
|
+
round: (/** @type {number} */ x) => Math.round(x),
|
|
505
867
|
|
|
506
|
-
floor: (x) => Math.floor(x),
|
|
868
|
+
floor: (/** @type {number} */ x) => Math.floor(x),
|
|
507
869
|
|
|
508
|
-
ceil: (x) => Math.ceil(x),
|
|
870
|
+
ceil: (/** @type {number} */ x) => Math.ceil(x),
|
|
509
871
|
|
|
510
|
-
sqrt: (x) => {
|
|
511
|
-
if (x < 0)
|
|
872
|
+
sqrt: (/** @type {number} */ x) => {
|
|
873
|
+
if (x < 0) {
|
|
874
|
+
throw new Error('sqrt() domain error');
|
|
875
|
+
}
|
|
512
876
|
return Math.sqrt(x);
|
|
513
877
|
},
|
|
514
878
|
|
|
515
|
-
pow: (a, b) => a ** b,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
879
|
+
pow: (/** @type {number} */ a, /** @type {number} */ b) => a ** b,
|
|
880
|
+
|
|
881
|
+
det: (/** @type {any[]} */ matrix) => determinant(matrix),
|
|
882
|
+
|
|
883
|
+
polynomialRoot: (/** @type {any} */ ...coefficients) => polynomialRoots(...coefficients),
|
|
884
|
+
|
|
885
|
+
lsolve: (
|
|
886
|
+
/** @type {any} */ a,
|
|
887
|
+
/** @type {{ exprify: string; data: any; size: number[]; }} */ b
|
|
888
|
+
) => linearSolve(a, b),
|
|
889
|
+
|
|
890
|
+
lup: (/** @type {any} */ matrix) => lupDecomposition(matrix),
|
|
891
|
+
|
|
892
|
+
lyap: (/** @type {any} */ a, /** @type {any} */ q) => solveLyapunov(a, q),
|
|
893
|
+
|
|
894
|
+
qr: (/** @type {any} */ matrix) => qrDecomposition(matrix),
|
|
895
|
+
|
|
896
|
+
transpose: (/** @type {any} */ matrix) => wrapDenseMatrix(transpose(unwrapDenseMatrix(matrix))),
|
|
897
|
+
|
|
898
|
+
inverse: (/** @type {any[]} */ matrix) => _inverse(matrix),
|
|
899
|
+
|
|
900
|
+
trace: (/** @type {any[]} */ matrix) => {
|
|
901
|
+
const data = unwrapDenseMatrix(matrix);
|
|
902
|
+
validateSquareMatrix(matrix);
|
|
903
|
+
return data.reduce(
|
|
904
|
+
(
|
|
905
|
+
/** @type {any} */ sum,
|
|
906
|
+
/** @type {{ [x: string]: any; }} */ row,
|
|
907
|
+
/** @type {string | number} */ i
|
|
908
|
+
) => sum + row[i],
|
|
909
|
+
0
|
|
910
|
+
);
|
|
911
|
+
},
|
|
912
|
+
|
|
913
|
+
rank: (/** @type {any} */ matrix) => {
|
|
914
|
+
const rrefData = unwrapDenseMatrix(_rref(matrix));
|
|
915
|
+
return rrefData.filter((/** @type {any[]} */ row) => row.some((v) => Math.abs(v) > 1e-10))
|
|
916
|
+
.length;
|
|
917
|
+
},
|
|
918
|
+
|
|
919
|
+
rref: (/** @type {any} */ matrix) => _rref(matrix),
|
|
920
|
+
|
|
921
|
+
minor: (/** @type {any[]} */ matrix, /** @type {any} */ i, /** @type {any} */ j) => {
|
|
922
|
+
const data = unwrapDenseMatrix(matrix);
|
|
923
|
+
validateSquareMatrix(matrix);
|
|
924
|
+
const sub = data
|
|
925
|
+
.filter((/** @type {any} */ _, /** @type {any} */ ri) => ri !== i)
|
|
926
|
+
.map((/** @type {any[]} */ row) => row.filter((_, cj) => cj !== j));
|
|
927
|
+
return determinant(sub);
|
|
928
|
+
},
|
|
929
|
+
|
|
930
|
+
cofactor: (/** @type {any} */ matrix, /** @type {any} */ i, /** @type {any} */ j) => {
|
|
931
|
+
const data = unwrapDenseMatrix(matrix);
|
|
932
|
+
const sub = data
|
|
933
|
+
.filter((/** @type {any} */ _, /** @type {any} */ ri) => ri !== i)
|
|
934
|
+
.map((/** @type {any[]} */ row) =>
|
|
935
|
+
row.filter((/** @type {any} */ _, /** @type {any} */ cj) => cj !== j)
|
|
936
|
+
);
|
|
937
|
+
return ((i + j) % 2 === 0 ? 1 : -1) * determinant(sub);
|
|
938
|
+
},
|
|
939
|
+
|
|
940
|
+
cross: (/** @type {any} */ a, /** @type {any} */ b) => {
|
|
941
|
+
const v1 = unwrapDenseMatrix(a);
|
|
942
|
+
const v2 = unwrapDenseMatrix(b);
|
|
943
|
+
if (!Array.isArray(v1) || !Array.isArray(v2) || v1.length !== 3 || v2.length !== 3) {
|
|
944
|
+
throw new Error('cross() requires two 3D vectors');
|
|
945
|
+
}
|
|
946
|
+
return _cross(v1, v2);
|
|
947
|
+
},
|
|
948
|
+
|
|
949
|
+
normalize: (/** @type {any} */ v) => {
|
|
950
|
+
const data = unwrapDenseMatrix(v);
|
|
951
|
+
if (!Array.isArray(data)) {
|
|
952
|
+
throw new Error('normalize() expects a vector');
|
|
953
|
+
}
|
|
954
|
+
const norm = vectorNorm(data);
|
|
955
|
+
if (norm === 0) {
|
|
956
|
+
throw new Error('Cannot normalize zero vector');
|
|
957
|
+
}
|
|
958
|
+
return scaleVector(data, 1 / norm);
|
|
959
|
+
},
|
|
960
|
+
|
|
961
|
+
angle: (/** @type {any} */ a, /** @type {any} */ b) => {
|
|
962
|
+
const v1 = unwrapDenseMatrix(a);
|
|
963
|
+
const v2 = unwrapDenseMatrix(b);
|
|
964
|
+
if (!Array.isArray(v1) || !Array.isArray(v2)) {
|
|
965
|
+
throw new Error('angle() expects vectors');
|
|
966
|
+
}
|
|
967
|
+
const dot = dotProduct(v1, v2);
|
|
968
|
+
const norms = vectorNorm(v1) * vectorNorm(v2);
|
|
969
|
+
if (norms === 0) {
|
|
970
|
+
throw new Error('Zero vector angle is undefined');
|
|
971
|
+
}
|
|
972
|
+
return Math.acos(Math.max(-1, Math.min(1, dot / norms)));
|
|
973
|
+
},
|
|
974
|
+
|
|
975
|
+
projection: (/** @type {any} */ a, /** @type {any} */ b) => {
|
|
976
|
+
const v1 = unwrapDenseMatrix(a);
|
|
977
|
+
const v2 = unwrapDenseMatrix(b);
|
|
978
|
+
if (!Array.isArray(v1) || !Array.isArray(v2)) {
|
|
979
|
+
throw new Error('projection() expects vectors');
|
|
980
|
+
}
|
|
981
|
+
const dot = dotProduct(v1, v2);
|
|
982
|
+
const normB = vectorNorm(v2);
|
|
983
|
+
if (normB === 0) {
|
|
984
|
+
throw new Error('Zero vector projection undefined');
|
|
985
|
+
}
|
|
986
|
+
return dot / normB;
|
|
987
|
+
},
|
|
988
|
+
|
|
989
|
+
identity: (/** @type {any} */ n) => wrapDenseMatrix(_identity(n)),
|
|
990
|
+
|
|
991
|
+
eye: (/** @type {any} */ n) => wrapDenseMatrix(_identity(n)),
|
|
992
|
+
|
|
993
|
+
zeros: (/** @type {any} */ n, /** @type {undefined} */ m) => {
|
|
994
|
+
if (m === undefined) {
|
|
995
|
+
m = n;
|
|
996
|
+
}
|
|
997
|
+
return wrapDenseMatrix(Array.from({ length: n }, () => Array(m).fill(0)));
|
|
998
|
+
},
|
|
999
|
+
|
|
1000
|
+
ones: (/** @type {any} */ n, /** @type {undefined} */ m) => {
|
|
1001
|
+
if (m === undefined) {
|
|
1002
|
+
m = n;
|
|
1003
|
+
}
|
|
1004
|
+
return wrapDenseMatrix(Array.from({ length: n }, () => Array(m).fill(1)));
|
|
1005
|
+
},
|
|
1006
|
+
|
|
1007
|
+
diag: (/** @type {any} */ x) => {
|
|
1008
|
+
const arr = unwrapDenseMatrix(x);
|
|
1009
|
+
if (!Array.isArray(arr)) {
|
|
1010
|
+
throw new Error('diag() expects an array');
|
|
1011
|
+
}
|
|
1012
|
+
return wrapDenseMatrix(
|
|
1013
|
+
Array.from({ length: arr.length }, (_, i) =>
|
|
1014
|
+
Array.from({ length: arr.length }, (_, j) => (i === j ? arr[i] : 0))
|
|
1015
|
+
)
|
|
1016
|
+
);
|
|
1017
|
+
},
|
|
1018
|
+
|
|
1019
|
+
cholesky: (/** @type {any[]} */ matrix) => _cholesky(matrix),
|
|
1020
|
+
|
|
1021
|
+
eig: (/** @type {any[]} */ matrix) => _eig2x2(matrix),
|
|
1022
|
+
|
|
1023
|
+
svd: (/** @type {any} */ matrix) => _svd(matrix),
|
|
1024
|
+
|
|
1025
|
+
simplify: (/** @type {string} */ expression) => {
|
|
1026
|
+
if (typeof expression !== 'string') {
|
|
1027
|
+
throw new Error('simplify() expects an expression string');
|
|
525
1028
|
}
|
|
526
1029
|
return simplifyExpression(expression);
|
|
527
1030
|
},
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1031
|
+
|
|
1032
|
+
derivative: (/** @type {string} */ expression, variable = 'x') => {
|
|
1033
|
+
if (typeof expression !== 'string' || typeof variable !== 'string') {
|
|
1034
|
+
throw new Error('derivative() expects expression and variable strings');
|
|
531
1035
|
}
|
|
532
1036
|
return derivativeExpression(expression, variable);
|
|
533
1037
|
},
|
|
534
1038
|
|
|
535
|
-
|
|
1039
|
+
sin: (/** @type {number} */ x) => Math.sin(x),
|
|
536
1040
|
|
|
537
|
-
|
|
538
|
-
cos: (x) => Math.cos(x),
|
|
539
|
-
tan: (x) => Math.tan(x),
|
|
1041
|
+
cos: (/** @type {number} */ x) => Math.cos(x),
|
|
540
1042
|
|
|
541
|
-
|
|
542
|
-
acos: (x) => Math.acos(x),
|
|
543
|
-
atan: (x) => Math.atan(x),
|
|
1043
|
+
tan: (/** @type {number} */ x) => Math.tan(x),
|
|
544
1044
|
|
|
545
|
-
|
|
1045
|
+
sind: (/** @type {number} */ x) => Math.sin((x * Math.PI) / 180),
|
|
546
1046
|
|
|
547
|
-
|
|
548
|
-
|
|
1047
|
+
cosd: (/** @type {number} */ x) => Math.cos((x * Math.PI) / 180),
|
|
1048
|
+
|
|
1049
|
+
tand: (/** @type {number} */ x) => Math.tan((x * Math.PI) / 180),
|
|
1050
|
+
|
|
1051
|
+
asind: (/** @type {number} */ x) => (Math.asin(x) * 180) / Math.PI,
|
|
1052
|
+
|
|
1053
|
+
acosd: (/** @type {number} */ x) => (Math.acos(x) * 180) / Math.PI,
|
|
1054
|
+
|
|
1055
|
+
atand: (/** @type {number} */ x) => (Math.atan(x) * 180) / Math.PI,
|
|
1056
|
+
|
|
1057
|
+
atand2: (/** @type {number} */ y, /** @type {number} */ x) => (Math.atan2(y, x) * 180) / Math.PI,
|
|
1058
|
+
|
|
1059
|
+
asin: (/** @type {number} */ x) => Math.asin(x),
|
|
1060
|
+
|
|
1061
|
+
acos: (/** @type {number} */ x) => Math.acos(x),
|
|
1062
|
+
|
|
1063
|
+
atan: (/** @type {number} */ x) => Math.atan(x),
|
|
1064
|
+
|
|
1065
|
+
log: (/** @type {number} */ x) => {
|
|
1066
|
+
if (x <= 0) {
|
|
1067
|
+
throw new Error('log() domain error');
|
|
1068
|
+
}
|
|
549
1069
|
return Math.log(x);
|
|
550
1070
|
},
|
|
551
1071
|
|
|
552
|
-
log10: (x) => {
|
|
553
|
-
if (x <= 0)
|
|
1072
|
+
log10: (/** @type {number} */ x) => {
|
|
1073
|
+
if (x <= 0) {
|
|
1074
|
+
throw new Error('log10() domain error');
|
|
1075
|
+
}
|
|
554
1076
|
return Math.log10(x);
|
|
555
1077
|
},
|
|
556
1078
|
|
|
557
|
-
exp: (x) => Math.exp(x),
|
|
558
|
-
|
|
559
|
-
/* ================= RANDOM ================= */
|
|
1079
|
+
exp: (/** @type {number} */ x) => Math.exp(x),
|
|
560
1080
|
|
|
561
1081
|
random: () => Math.random(),
|
|
562
1082
|
|
|
563
|
-
|
|
1083
|
+
and: (/** @type {any} */ a, /** @type {any} */ b) => Boolean(a && b),
|
|
564
1084
|
|
|
565
|
-
|
|
1085
|
+
or: (/** @type {any} */ a, /** @type {any} */ b) => Boolean(a || b),
|
|
566
1086
|
|
|
567
|
-
|
|
1087
|
+
not: (/** @type {any} */ a) => !a,
|
|
1088
|
+
'!': (/** @type {any} */ a) => !a,
|
|
568
1089
|
|
|
569
|
-
|
|
570
|
-
"!": (a) => !a,
|
|
1090
|
+
eq: (/** @type {any} */ a, /** @type {any} */ b) => a === b,
|
|
571
1091
|
|
|
572
|
-
|
|
1092
|
+
neq: (/** @type {any} */ a, /** @type {any} */ b) => a !== b,
|
|
1093
|
+
notEqual: (/** @type {any} */ a, /** @type {any} */ b) => a !== b,
|
|
573
1094
|
|
|
574
|
-
|
|
1095
|
+
gt: (/** @type {number} */ a, /** @type {number} */ b) => a > b,
|
|
1096
|
+
greaterThan: (/** @type {number} */ a, /** @type {number} */ b) => a > b,
|
|
575
1097
|
|
|
576
|
-
|
|
577
|
-
|
|
1098
|
+
lt: (/** @type {number} */ a, /** @type {number} */ b) => a < b,
|
|
1099
|
+
lessThan: (/** @type {number} */ a, /** @type {number} */ b) => a < b,
|
|
578
1100
|
|
|
579
|
-
|
|
580
|
-
|
|
1101
|
+
gte: (/** @type {number} */ a, /** @type {number} */ b) => a >= b,
|
|
1102
|
+
greaterThanOrEqual: (/** @type {number} */ a, /** @type {number} */ b) => a >= b,
|
|
581
1103
|
|
|
582
|
-
|
|
583
|
-
|
|
1104
|
+
lte: (/** @type {number} */ a, /** @type {number} */ b) => a <= b,
|
|
1105
|
+
lessThanOrEqual: (/** @type {number} */ a, /** @type {number} */ b) => a <= b,
|
|
584
1106
|
|
|
585
|
-
|
|
586
|
-
|
|
1107
|
+
clamp: (/** @type {number} */ x, /** @type {number} */ min, /** @type {number} */ max) => {
|
|
1108
|
+
if (min > max) {
|
|
1109
|
+
throw new Error('clamp(): min > max');
|
|
1110
|
+
}
|
|
1111
|
+
return Math.min(Math.max(x, min), max);
|
|
1112
|
+
},
|
|
587
1113
|
|
|
588
|
-
|
|
589
|
-
|
|
1114
|
+
if: (/** @type {any} */ condition, /** @type {any} */ a, /** @type {any} */ b) =>
|
|
1115
|
+
condition ? a : b,
|
|
590
1116
|
|
|
591
|
-
|
|
1117
|
+
typeof: (/** @type {any} */ x) => typeof x,
|
|
592
1118
|
|
|
593
|
-
|
|
594
|
-
if (
|
|
595
|
-
|
|
1119
|
+
length: (/** @type {string | any[]} */ x) => {
|
|
1120
|
+
if (typeof x === 'string' || Array.isArray(x)) {
|
|
1121
|
+
return x.length;
|
|
1122
|
+
}
|
|
1123
|
+
throw new Error('length() expects string or array');
|
|
1124
|
+
},
|
|
1125
|
+
|
|
1126
|
+
sum: (/** @type {any[]} */ ...args) => {
|
|
1127
|
+
if (!args.length) {
|
|
1128
|
+
throw new Error('sum() requires at least one argument');
|
|
1129
|
+
}
|
|
1130
|
+
return args.reduce((a, b) => a + b, 0);
|
|
596
1131
|
},
|
|
597
1132
|
|
|
598
|
-
|
|
1133
|
+
prod: (/** @type {any[]} */ ...args) => {
|
|
1134
|
+
if (!args.length) {
|
|
1135
|
+
throw new Error('prod() requires at least one argument');
|
|
1136
|
+
}
|
|
1137
|
+
return args.reduce((a, b) => a * b, 1);
|
|
1138
|
+
},
|
|
599
1139
|
|
|
600
|
-
|
|
1140
|
+
mean: (/** @type {any[]} */ ...args) => {
|
|
1141
|
+
if (!args.length) {
|
|
1142
|
+
throw new Error('mean() requires at least one argument');
|
|
1143
|
+
}
|
|
1144
|
+
return args.reduce((a, b) => a + b, 0) / args.length;
|
|
1145
|
+
},
|
|
601
1146
|
|
|
602
|
-
|
|
1147
|
+
median: (/** @type {any[]} */ ...args) => {
|
|
1148
|
+
if (!args.length) {
|
|
1149
|
+
throw new Error('median() requires at least one argument');
|
|
1150
|
+
}
|
|
1151
|
+
const sorted = [...args].sort((a, b) => a - b);
|
|
1152
|
+
const mid = Math.floor(sorted.length / 2);
|
|
1153
|
+
return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2;
|
|
1154
|
+
},
|
|
603
1155
|
|
|
604
|
-
|
|
1156
|
+
mode: (/** @type {any[]} */ ...args) => {
|
|
1157
|
+
if (!args.length) {
|
|
1158
|
+
throw new Error('mode() requires at least one argument');
|
|
1159
|
+
}
|
|
1160
|
+
const freq = new Map();
|
|
1161
|
+
args.forEach((v) => freq.set(v, (freq.get(v) || 0) + 1));
|
|
1162
|
+
let maxCount = 0;
|
|
1163
|
+
let result = args[0];
|
|
1164
|
+
for (const [val, count] of freq) {
|
|
1165
|
+
if (count > maxCount) {
|
|
1166
|
+
maxCount = count;
|
|
1167
|
+
result = val;
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
return result;
|
|
1171
|
+
},
|
|
605
1172
|
|
|
606
|
-
|
|
607
|
-
if (
|
|
608
|
-
|
|
1173
|
+
std: (/** @type {any[]} */ ...args) => {
|
|
1174
|
+
if (args.length < 2) {
|
|
1175
|
+
throw new Error('std() requires at least two values');
|
|
609
1176
|
}
|
|
610
|
-
|
|
611
|
-
|
|
1177
|
+
const m = args.reduce((a, b) => a + b, 0) / args.length;
|
|
1178
|
+
return Math.sqrt(args.reduce((sum, v) => sum + (v - m) ** 2, 0) / (args.length - 1));
|
|
1179
|
+
},
|
|
1180
|
+
|
|
1181
|
+
variance: (/** @type {any[]} */ ...args) => {
|
|
1182
|
+
if (args.length < 2) {
|
|
1183
|
+
throw new Error('variance() requires at least two values');
|
|
1184
|
+
}
|
|
1185
|
+
const m = args.reduce((a, b) => a + b, 0) / args.length;
|
|
1186
|
+
return args.reduce((sum, v) => sum + (v - m) ** 2, 0) / (args.length - 1);
|
|
1187
|
+
},
|
|
1188
|
+
|
|
1189
|
+
range: (/** @type {any[]} */ ...args) => {
|
|
1190
|
+
if (!args.length) {
|
|
1191
|
+
throw new Error('range() requires at least one argument');
|
|
1192
|
+
}
|
|
1193
|
+
return Math.max(...args) - Math.min(...args);
|
|
1194
|
+
},
|
|
1195
|
+
|
|
1196
|
+
gcd: (/** @type {number} */ a, /** @type {number} */ b) => _gcd(a, b),
|
|
1197
|
+
|
|
1198
|
+
lcm: (/** @type {number} */ a, /** @type {number} */ b) => {
|
|
1199
|
+
if (a === 0 || b === 0) {
|
|
1200
|
+
return 0;
|
|
1201
|
+
}
|
|
1202
|
+
return Math.abs((a / _gcd(a, b)) * b);
|
|
1203
|
+
},
|
|
1204
|
+
|
|
1205
|
+
factorial: (/** @type {any} */ n) => {
|
|
1206
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
1207
|
+
throw new Error('factorial() requires a non-negative integer');
|
|
1208
|
+
}
|
|
1209
|
+
if (n === 0 || n === 1) {
|
|
1210
|
+
return 1;
|
|
1211
|
+
}
|
|
1212
|
+
let r = 1;
|
|
1213
|
+
for (let i = 2; i <= n; i++) {
|
|
1214
|
+
r *= i;
|
|
1215
|
+
}
|
|
1216
|
+
return r;
|
|
1217
|
+
},
|
|
1218
|
+
|
|
1219
|
+
isPrime: (/** @type {any} */ n) => {
|
|
1220
|
+
if (!Number.isInteger(n) || n < 2) {
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
if (n === 2) {
|
|
1224
|
+
return true;
|
|
1225
|
+
}
|
|
1226
|
+
if (n % 2 === 0) {
|
|
1227
|
+
return false;
|
|
1228
|
+
}
|
|
1229
|
+
for (let i = 3; i * i <= n; i += 2) {
|
|
1230
|
+
if (n % i === 0) {
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
return true;
|
|
1235
|
+
},
|
|
1236
|
+
|
|
1237
|
+
primeFactors: (/** @type {any} */ n) => {
|
|
1238
|
+
if (!Number.isInteger(n) || n < 2) {
|
|
1239
|
+
throw new Error('primeFactors() requires an integer >= 2');
|
|
1240
|
+
}
|
|
1241
|
+
const factors = [];
|
|
1242
|
+
let m = n;
|
|
1243
|
+
for (let i = 2; i * i <= m; i++) {
|
|
1244
|
+
while (m % i === 0) {
|
|
1245
|
+
factors.push(i);
|
|
1246
|
+
m /= i;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
if (m > 1) {
|
|
1250
|
+
factors.push(m);
|
|
1251
|
+
}
|
|
1252
|
+
return factors;
|
|
1253
|
+
},
|
|
1254
|
+
|
|
1255
|
+
fibonacci: (/** @type {any} */ n) => {
|
|
1256
|
+
if (!Number.isInteger(n) || n < 0) {
|
|
1257
|
+
throw new Error('fibonacci() requires a non-negative integer');
|
|
1258
|
+
}
|
|
1259
|
+
if (n <= 1) {
|
|
1260
|
+
return n;
|
|
1261
|
+
}
|
|
1262
|
+
let a = 0;
|
|
1263
|
+
let b = 1;
|
|
1264
|
+
for (let i = 2; i <= n; i++) {
|
|
1265
|
+
const t = a + b;
|
|
1266
|
+
a = b;
|
|
1267
|
+
b = t;
|
|
1268
|
+
}
|
|
1269
|
+
return b;
|
|
1270
|
+
},
|
|
1271
|
+
|
|
1272
|
+
nCr: (/** @type {any} */ n, /** @type {any} */ r) => {
|
|
1273
|
+
if (!Number.isInteger(n) || !Number.isInteger(r) || n < 0 || r < 0) {
|
|
1274
|
+
throw new Error('nCr() requires non-negative integers');
|
|
1275
|
+
}
|
|
1276
|
+
if (r > n) {
|
|
1277
|
+
return 0;
|
|
1278
|
+
}
|
|
1279
|
+
if (r === 0 || r === n) {
|
|
1280
|
+
return 1;
|
|
1281
|
+
}
|
|
1282
|
+
r = Math.min(r, n - r);
|
|
1283
|
+
let result = 1;
|
|
1284
|
+
for (let i = 1; i <= r; i++) {
|
|
1285
|
+
result = (result * (n - r + i)) / i;
|
|
1286
|
+
}
|
|
1287
|
+
return result;
|
|
1288
|
+
},
|
|
1289
|
+
|
|
1290
|
+
nPr: (/** @type {any} */ n, /** @type {any} */ r) => {
|
|
1291
|
+
if (!Number.isInteger(n) || !Number.isInteger(r) || n < 0 || r < 0) {
|
|
1292
|
+
throw new Error('nPr() requires non-negative integers');
|
|
1293
|
+
}
|
|
1294
|
+
if (r > n) {
|
|
1295
|
+
return 0;
|
|
1296
|
+
}
|
|
1297
|
+
let result = 1;
|
|
1298
|
+
for (let i = 0; i < r; i++) {
|
|
1299
|
+
result *= n - i;
|
|
1300
|
+
}
|
|
1301
|
+
return result;
|
|
1302
|
+
},
|
|
1303
|
+
|
|
1304
|
+
gamma: (/** @type {any} */ n) => _gamma(n),
|
|
1305
|
+
|
|
1306
|
+
sinh: (/** @type {number} */ x) => Math.sinh(x),
|
|
1307
|
+
|
|
1308
|
+
cosh: (/** @type {number} */ x) => Math.cosh(x),
|
|
1309
|
+
|
|
1310
|
+
tanh: (/** @type {number} */ x) => Math.tanh(x),
|
|
1311
|
+
|
|
1312
|
+
asinh: (/** @type {number} */ x) => Math.asinh(x),
|
|
1313
|
+
|
|
1314
|
+
acosh: (/** @type {number} */ x) => Math.acosh(x),
|
|
1315
|
+
|
|
1316
|
+
atanh: (/** @type {number} */ x) => Math.atanh(x),
|
|
1317
|
+
|
|
1318
|
+
sec: (/** @type {number} */ x) => {
|
|
1319
|
+
const c = Math.cos(x);
|
|
1320
|
+
if (Math.abs(c) < 1e-15) {
|
|
1321
|
+
throw new Error('sec() undefined for this input');
|
|
1322
|
+
}
|
|
1323
|
+
return 1 / c;
|
|
1324
|
+
},
|
|
1325
|
+
|
|
1326
|
+
csc: (/** @type {number} */ x) => {
|
|
1327
|
+
const s = Math.sin(x);
|
|
1328
|
+
if (Math.abs(s) < 1e-15) {
|
|
1329
|
+
throw new Error('csc() undefined for this input');
|
|
1330
|
+
}
|
|
1331
|
+
return 1 / s;
|
|
1332
|
+
},
|
|
1333
|
+
|
|
1334
|
+
cot: (/** @type {number} */ x) => {
|
|
1335
|
+
const s = Math.sin(x);
|
|
1336
|
+
if (Math.abs(s) < 1e-15) {
|
|
1337
|
+
throw new Error('cot() undefined for this input');
|
|
1338
|
+
}
|
|
1339
|
+
return Math.cos(x) / s;
|
|
1340
|
+
},
|
|
1341
|
+
|
|
1342
|
+
trunc: (/** @type {number} */ x) => Math.trunc(x),
|
|
1343
|
+
|
|
1344
|
+
sign: (/** @type {number} */ x) => Math.sign(x),
|
|
1345
|
+
|
|
1346
|
+
frac: (/** @type {number} */ x) => x - Math.trunc(x),
|
|
1347
|
+
|
|
1348
|
+
split: (
|
|
1349
|
+
/** @type {string} */ str,
|
|
1350
|
+
/** @type {{ [Symbol.split](string: string, limit?: number): string[]; }} */ sep
|
|
1351
|
+
) => {
|
|
1352
|
+
if (typeof str !== 'string') {
|
|
1353
|
+
throw new Error('split() expects a string');
|
|
1354
|
+
}
|
|
1355
|
+
return str.split(sep);
|
|
1356
|
+
},
|
|
1357
|
+
|
|
1358
|
+
join: (/** @type {any[]} */ arr, /** @type {string | undefined} */ sep) => {
|
|
1359
|
+
if (!Array.isArray(arr)) {
|
|
1360
|
+
throw new Error('join() expects an array');
|
|
1361
|
+
}
|
|
1362
|
+
return arr.join(sep);
|
|
1363
|
+
},
|
|
1364
|
+
|
|
1365
|
+
upper: (/** @type {string} */ str) => {
|
|
1366
|
+
if (typeof str !== 'string') {
|
|
1367
|
+
throw new Error('upper() expects a string');
|
|
1368
|
+
}
|
|
1369
|
+
return str.toUpperCase();
|
|
1370
|
+
},
|
|
1371
|
+
|
|
1372
|
+
lower: (/** @type {string} */ str) => {
|
|
1373
|
+
if (typeof str !== 'string') {
|
|
1374
|
+
throw new Error('lower() expects a string');
|
|
1375
|
+
}
|
|
1376
|
+
return str.toLowerCase();
|
|
1377
|
+
},
|
|
1378
|
+
|
|
1379
|
+
trim: (/** @type {string} */ str) => {
|
|
1380
|
+
if (typeof str !== 'string') {
|
|
1381
|
+
throw new Error('trim() expects a string');
|
|
1382
|
+
}
|
|
1383
|
+
return str.trim();
|
|
1384
|
+
},
|
|
1385
|
+
|
|
1386
|
+
replace: (
|
|
1387
|
+
/** @type {string} */ str,
|
|
1388
|
+
/** @type {{ [Symbol.replace](string: string, replaceValue: string): string; }} */ pattern,
|
|
1389
|
+
/** @type {string} */ replacement
|
|
1390
|
+
) => {
|
|
1391
|
+
if (typeof str !== 'string') {
|
|
1392
|
+
throw new Error('replace() expects a string');
|
|
1393
|
+
}
|
|
1394
|
+
return str.replace(pattern, replacement);
|
|
1395
|
+
},
|
|
1396
|
+
|
|
1397
|
+
substring: (
|
|
1398
|
+
/** @type {string} */ str,
|
|
1399
|
+
/** @type {number} */ start,
|
|
1400
|
+
/** @type {number | undefined} */ end
|
|
1401
|
+
) => {
|
|
1402
|
+
if (typeof str !== 'string') {
|
|
1403
|
+
throw new Error('substring() expects a string');
|
|
1404
|
+
}
|
|
1405
|
+
return str.substring(start, end);
|
|
1406
|
+
},
|
|
1407
|
+
|
|
1408
|
+
// ---- Reciprocal trig ----
|
|
1409
|
+
acot: (/** @type {number} */ x) => {
|
|
1410
|
+
if (x === 0) {
|
|
1411
|
+
return Math.PI / 2;
|
|
1412
|
+
}
|
|
1413
|
+
return Math.atan(1 / x);
|
|
1414
|
+
},
|
|
1415
|
+
|
|
1416
|
+
asec: (/** @type {number} */ x) => {
|
|
1417
|
+
if (x < 1 && x > -1) {
|
|
1418
|
+
throw new Error('asec() domain error');
|
|
1419
|
+
}
|
|
1420
|
+
return Math.acos(1 / x);
|
|
1421
|
+
},
|
|
1422
|
+
|
|
1423
|
+
acsc: (/** @type {number} */ x) => {
|
|
1424
|
+
if (x < 1 && x > -1) {
|
|
1425
|
+
throw new Error('acsc() domain error');
|
|
1426
|
+
}
|
|
1427
|
+
return Math.asin(1 / x);
|
|
1428
|
+
},
|
|
1429
|
+
|
|
1430
|
+
acoth: (/** @type {number} */ x) => {
|
|
1431
|
+
if (Math.abs(x) <= 1) {
|
|
1432
|
+
throw new Error('acoth() domain error');
|
|
1433
|
+
}
|
|
1434
|
+
return Math.atanh(1 / x);
|
|
1435
|
+
},
|
|
1436
|
+
|
|
1437
|
+
asech: (/** @type {number} */ x) => {
|
|
1438
|
+
if (x <= 0 || x > 1) {
|
|
1439
|
+
throw new Error('asech() domain error');
|
|
1440
|
+
}
|
|
1441
|
+
return Math.acosh(1 / x);
|
|
1442
|
+
},
|
|
1443
|
+
|
|
1444
|
+
acsch: (/** @type {number} */ x) => {
|
|
1445
|
+
if (x === 0) {
|
|
1446
|
+
throw new Error('acsch() domain error');
|
|
1447
|
+
}
|
|
1448
|
+
return Math.asinh(1 / x);
|
|
1449
|
+
},
|
|
1450
|
+
|
|
1451
|
+
// ---- Stats ----
|
|
1452
|
+
quantile: (/** @type {any[]} */ arr, /** @type {number} */ p) => {
|
|
1453
|
+
if (!Array.isArray(arr) || arr.length === 0) {
|
|
1454
|
+
throw new Error('quantile() expects a non-empty array');
|
|
1455
|
+
}
|
|
1456
|
+
if (p < 0 || p > 1) {
|
|
1457
|
+
throw new Error('quantile() p must be between 0 and 1');
|
|
1458
|
+
}
|
|
1459
|
+
const sorted = [...arr].sort((a, b) => a - b);
|
|
1460
|
+
const idx = p * (sorted.length - 1);
|
|
1461
|
+
const lo = Math.floor(idx);
|
|
1462
|
+
const hi = Math.ceil(idx);
|
|
1463
|
+
return lo === hi ? sorted[lo] : sorted[lo] + (idx - lo) * (sorted[hi] - sorted[lo]);
|
|
1464
|
+
},
|
|
1465
|
+
|
|
1466
|
+
percentile: (/** @type {any[]} */ arr, /** @type {number} */ p) => {
|
|
1467
|
+
if (p < 0 || p > 100) {
|
|
1468
|
+
throw new Error('percentile() p must be between 0 and 100');
|
|
1469
|
+
}
|
|
1470
|
+
return internalFunctions.quantile(arr, p / 100);
|
|
1471
|
+
},
|
|
1472
|
+
|
|
1473
|
+
covariance: (/** @type {number[]} */ x, /** @type {number[]} */ y) => {
|
|
1474
|
+
if (!Array.isArray(x) || !Array.isArray(y) || x.length < 2 || x.length !== y.length) {
|
|
1475
|
+
throw new Error('covariance() expects two arrays of equal length >= 2');
|
|
1476
|
+
}
|
|
1477
|
+
const mx = x.reduce((s, v) => s + v, 0) / x.length;
|
|
1478
|
+
const my = y.reduce((s, v) => s + v, 0) / y.length;
|
|
1479
|
+
return x.reduce((s, v, i) => s + (v - mx) * (y[i] - my), 0) / (x.length - 1);
|
|
1480
|
+
},
|
|
1481
|
+
|
|
1482
|
+
corr: (/** @type {number[]} */ x, /** @type {number[]} */ y) => {
|
|
1483
|
+
const cov = internalFunctions.covariance(x, y);
|
|
1484
|
+
const sx = Math.sqrt(internalFunctions.covariance(x, x));
|
|
1485
|
+
const sy = Math.sqrt(internalFunctions.covariance(y, y));
|
|
1486
|
+
if (sx === 0 || sy === 0) {
|
|
1487
|
+
throw new Error('corr() zero variance');
|
|
1488
|
+
}
|
|
1489
|
+
return cov / (sx * sy);
|
|
1490
|
+
},
|
|
1491
|
+
|
|
1492
|
+
randomInt: (/** @type {number} */ min, /** @type {number} */ max) => {
|
|
1493
|
+
if (!Number.isInteger(min) || !Number.isInteger(max)) {
|
|
1494
|
+
throw new Error('randomInt() expects integers');
|
|
1495
|
+
}
|
|
1496
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
1497
|
+
},
|
|
1498
|
+
|
|
1499
|
+
randomNormal: (/** @type {number} */ mean, /** @type {number} */ std) => {
|
|
1500
|
+
if (std <= 0) {
|
|
1501
|
+
throw new Error('randomNormal() std must be > 0');
|
|
1502
|
+
}
|
|
1503
|
+
let u = 0;
|
|
1504
|
+
let v = 0;
|
|
1505
|
+
while (u === 0) {
|
|
1506
|
+
u = Math.random();
|
|
1507
|
+
}
|
|
1508
|
+
while (v === 0) {
|
|
1509
|
+
v = Math.random();
|
|
1510
|
+
}
|
|
1511
|
+
return mean + std * Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v);
|
|
1512
|
+
},
|
|
1513
|
+
|
|
1514
|
+
// ---- Special functions ----
|
|
1515
|
+
erf: (/** @type {number} */ x) => {
|
|
1516
|
+
if (x === 0) {
|
|
1517
|
+
return 0;
|
|
1518
|
+
}
|
|
1519
|
+
// Abramowitz & Stegun approximation (max error 1.5e-7)
|
|
1520
|
+
const t = 1 / (1 + 0.3275911 * Math.abs(x));
|
|
1521
|
+
const a = [0.254829592, -0.284496736, 1.421413741, -1.453152027, 1.061405429];
|
|
1522
|
+
let p = a[4] * t + a[3];
|
|
1523
|
+
p = p * t + a[2];
|
|
1524
|
+
p = p * t + a[1];
|
|
1525
|
+
p = p * t + a[0];
|
|
1526
|
+
p = p * t;
|
|
1527
|
+
const result = 1 - p * Math.exp(-x * x);
|
|
1528
|
+
return x >= 0 ? result : -result;
|
|
1529
|
+
},
|
|
1530
|
+
|
|
1531
|
+
lgamma: (/** @type {number} */ x) => {
|
|
1532
|
+
if (x <= 0) {
|
|
1533
|
+
throw new Error('lgamma() domain error (x > 0 required)');
|
|
1534
|
+
}
|
|
1535
|
+
// Stirling's approximation
|
|
1536
|
+
if (x < 12) {
|
|
1537
|
+
// Use recurrence: lgamma(x) = lgamma(x+1) - ln(x)
|
|
1538
|
+
let v = x;
|
|
1539
|
+
let r = 0;
|
|
1540
|
+
while (v < 12) {
|
|
1541
|
+
r -= Math.log(v);
|
|
1542
|
+
v += 1;
|
|
1543
|
+
}
|
|
1544
|
+
return r + internalFunctions.lgamma(v);
|
|
1545
|
+
}
|
|
1546
|
+
const inv = 1 / x;
|
|
1547
|
+
const s = (1 / 12 - (inv * inv) / 360 + (inv * inv * inv * inv) / 1260) * inv;
|
|
1548
|
+
return (x - 0.5) * Math.log(x) - x + 0.9189385332046727 + s;
|
|
1549
|
+
},
|
|
1550
|
+
|
|
1551
|
+
beta: (/** @type {number} */ a, /** @type {number} */ b) => {
|
|
1552
|
+
if (a <= 0 || b <= 0) {
|
|
1553
|
+
throw new Error('beta() domain error');
|
|
1554
|
+
}
|
|
1555
|
+
return Math.exp(
|
|
1556
|
+
internalFunctions.lgamma(a) + internalFunctions.lgamma(b) - internalFunctions.lgamma(a + b)
|
|
1557
|
+
);
|
|
1558
|
+
},
|
|
1559
|
+
|
|
1560
|
+
// ---- Numeric helpers ----
|
|
1561
|
+
hypot: (.../** @type {number[]} */ args) => Math.hypot(...args),
|
|
1562
|
+
|
|
1563
|
+
cbrt: (/** @type {number} */ x) => Math.cbrt(x),
|
|
1564
|
+
|
|
1565
|
+
log2: (/** @type {number} */ x) => {
|
|
1566
|
+
if (x <= 0) {
|
|
1567
|
+
throw new Error('log2() domain error');
|
|
1568
|
+
}
|
|
1569
|
+
return Math.log2(x);
|
|
1570
|
+
},
|
|
1571
|
+
|
|
1572
|
+
log1p: (/** @type {number} */ x) => {
|
|
1573
|
+
if (x <= -1) {
|
|
1574
|
+
throw new Error('log1p() domain error');
|
|
1575
|
+
}
|
|
1576
|
+
return Math.log1p(x);
|
|
1577
|
+
},
|
|
1578
|
+
|
|
1579
|
+
expm1: (/** @type {number} */ x) => Math.expm1(x),
|
|
1580
|
+
|
|
1581
|
+
// ---- Bitwise ----
|
|
1582
|
+
bitAnd: (/** @type {number} */ a, /** @type {number} */ b) => {
|
|
1583
|
+
if (!Number.isInteger(a) || !Number.isInteger(b)) {
|
|
1584
|
+
throw new Error('bitAnd() expects integers');
|
|
1585
|
+
}
|
|
1586
|
+
return a & b;
|
|
1587
|
+
},
|
|
1588
|
+
|
|
1589
|
+
bitOr: (/** @type {number} */ a, /** @type {number} */ b) => {
|
|
1590
|
+
if (!Number.isInteger(a) || !Number.isInteger(b)) {
|
|
1591
|
+
throw new Error('bitOr() expects integers');
|
|
1592
|
+
}
|
|
1593
|
+
return a | b;
|
|
1594
|
+
},
|
|
1595
|
+
|
|
1596
|
+
bitXor: (/** @type {number} */ a, /** @type {number} */ b) => {
|
|
1597
|
+
if (!Number.isInteger(a) || !Number.isInteger(b)) {
|
|
1598
|
+
throw new Error('bitXor() expects integers');
|
|
1599
|
+
}
|
|
1600
|
+
return a ^ b;
|
|
1601
|
+
},
|
|
1602
|
+
|
|
1603
|
+
bitNot: (/** @type {number} */ a) => {
|
|
1604
|
+
if (!Number.isInteger(a)) {
|
|
1605
|
+
throw new Error('bitNot() expects an integer');
|
|
1606
|
+
}
|
|
1607
|
+
return ~a;
|
|
1608
|
+
},
|
|
612
1609
|
};
|