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.
@@ -1,29 +1,33 @@
1
- import { unwrapDenseMatrix, wrapDenseMatrix } from "../utils/matrix.js";
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("det() expects a non-empty matrix");
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("det() expects a 2D matrix");
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("det() expects a square matrix");
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 !== "number" && typeof value !== "bigint") {
21
- throw new Error("det() matrix values must be numeric");
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 (matrix[0][0] * matrix[1][1]) - (matrix[0][1] * matrix[1][0]);
40
+ return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
37
41
  }
38
42
 
39
- return matrix[0].reduce((sum, value, columnIndex) => {
40
- const minor = matrix.slice(1).map((row) =>
41
- row.filter((_, index) => index !== columnIndex)
42
- );
43
- const cofactor = columnIndex % 2 === 0 ? value : -value;
44
- return sum + (cofactor * determinant(minor));
45
- }, 0);
46
- }
47
-
48
- function toLinearArray(value) {
49
- const unwrapped = unwrapDenseMatrix(value);
50
- return Array.isArray(unwrapped) ? unwrapped : value;
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("Expected matrix data");
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("Linear system is singular");
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) continue;
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("Matrix is singular");
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) return 1;
141
- if (rowIndex > colIndex) return value;
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("Right-hand side dimension mismatch");
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("A and Q must have the same dimensions");
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 + (coefficient * (x ** index)), 0);
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] + (quotient[index - 1] * root));
269
+ quotient.push(descending[index] + quotient[index - 1] * root);
237
270
  }
238
271
 
239
- const remainder = descending[descending.length - 1] + (quotient[quotient.length - 1] * root);
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 = (b ** 2) - (4 * a * c);
284
+ const discriminant = b ** 2 - 4 * a * c;
249
285
  if (discriminant < 0) {
250
- throw new Error("Only real roots are supported");
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("polynomialRoot() expects at least a linear polynomial");
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("polynomialRoot() currently supports degree up to 3");
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 + (value * b[index]), 0);
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((value, index) => value - b[index]);
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((_, colIndex) => matrix.map((row) => row[colIndex]));
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("qr() expects a rectangular matrix");
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("qr() requires linearly independent columns");
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 === "" || rawCoeff === "+") coefficient = 1;
403
- else if (rawCoeff === "-") coefficient = -1;
404
- else {
405
- const cleaned = rawCoeff.endsWith("*") ? rawCoeff.slice(0, -1) : rawCoeff;
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("Unsupported algebra term");
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("Unsupported algebra term");
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("Only non-negative integer powers are supported");
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("Unsupported algebra term");
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 "0";
516
+ return '0';
446
517
  }
447
518
 
448
- return ordered.map(([power, coefficient], index) => {
449
- const negative = coefficient < 0;
450
- const absCoeff = Math.abs(coefficient);
451
- let body;
452
-
453
- if (power === 0) {
454
- body = `${absCoeff}`;
455
- } else if (power === 1) {
456
- body = absCoeff === 1 ? variable : `${absCoeff} * ${variable}`;
457
- } else {
458
- body = absCoeff === 1
459
- ? `${variable}^${power}`
460
- : `${absCoeff} * ${variable}^${power}`;
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
- if (index === 0) {
464
- return negative ? `-${body}` : body;
465
- }
533
+ if (index === 0) {
534
+ return negative ? `-${body}` : body;
535
+ }
466
536
 
467
- return negative ? `- ${body}` : `+ ${body}`;
468
- }).join(" ");
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] || "x";
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) continue;
485
- derived.set(power - 1, (derived.get(power - 1) || 0) + (coefficient * power));
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
- max: (...args) => {
493
- if (!args.length) throw new Error("max() requires arguments");
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) throw new Error("min() requires arguments");
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) throw new Error("sqrt() domain error");
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
- det: (matrix) => determinant(matrix),
517
- polynomialRoot: (...coefficients) => polynomialRoots(...coefficients),
518
- lsolve: (a, b) => linearSolve(a, b),
519
- lup: (matrix) => lupDecomposition(matrix),
520
- lyap: (a, q) => solveLyapunov(a, q),
521
- qr: (matrix) => qrDecomposition(matrix),
522
- simplify: (expression) => {
523
- if (typeof expression !== "string") {
524
- throw new Error("simplify() expects an expression string");
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
- derivative: (expression, variable = "x") => {
529
- if (typeof expression !== "string" || typeof variable !== "string") {
530
- throw new Error("derivative() expects expression and variable strings");
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
- /* ================= TRIGONOMETRY ================= */
1039
+ sin: (/** @type {number} */ x) => Math.sin(x),
536
1040
 
537
- sin: (x) => Math.sin(x),
538
- cos: (x) => Math.cos(x),
539
- tan: (x) => Math.tan(x),
1041
+ cos: (/** @type {number} */ x) => Math.cos(x),
540
1042
 
541
- asin: (x) => Math.asin(x),
542
- acos: (x) => Math.acos(x),
543
- atan: (x) => Math.atan(x),
1043
+ tan: (/** @type {number} */ x) => Math.tan(x),
544
1044
 
545
- /* ================= LOG / EXP ================= */
1045
+ sind: (/** @type {number} */ x) => Math.sin((x * Math.PI) / 180),
546
1046
 
547
- log: (x) => {
548
- if (x <= 0) throw new Error("log() domain error");
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) throw new Error("log10() domain error");
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
- /* ================= BOOLEAN / LOGIC ================= */
1083
+ and: (/** @type {any} */ a, /** @type {any} */ b) => Boolean(a && b),
564
1084
 
565
- and: (a, b) => Boolean(a && b),
1085
+ or: (/** @type {any} */ a, /** @type {any} */ b) => Boolean(a || b),
566
1086
 
567
- or: (a, b) => Boolean(a || b),
1087
+ not: (/** @type {any} */ a) => !a,
1088
+ '!': (/** @type {any} */ a) => !a,
568
1089
 
569
- not: (a) => !a,
570
- "!": (a) => !a,
1090
+ eq: (/** @type {any} */ a, /** @type {any} */ b) => a === b,
571
1091
 
572
- /* ================= COMPARISON ================= */
1092
+ neq: (/** @type {any} */ a, /** @type {any} */ b) => a !== b,
1093
+ notEqual: (/** @type {any} */ a, /** @type {any} */ b) => a !== b,
573
1094
 
574
- eq: (a, b) => a === b,
1095
+ gt: (/** @type {number} */ a, /** @type {number} */ b) => a > b,
1096
+ greaterThan: (/** @type {number} */ a, /** @type {number} */ b) => a > b,
575
1097
 
576
- neq: (a, b) => a !== b,
577
- "notEqual": (a, b) => a !== b,
1098
+ lt: (/** @type {number} */ a, /** @type {number} */ b) => a < b,
1099
+ lessThan: (/** @type {number} */ a, /** @type {number} */ b) => a < b,
578
1100
 
579
- gt: (a, b) => a > b,
580
- "greaterThan": (a, b) => a > b,
1101
+ gte: (/** @type {number} */ a, /** @type {number} */ b) => a >= b,
1102
+ greaterThanOrEqual: (/** @type {number} */ a, /** @type {number} */ b) => a >= b,
581
1103
 
582
- lt: (a, b) => a < b,
583
- "lessThan": (a, b) => a < b,
1104
+ lte: (/** @type {number} */ a, /** @type {number} */ b) => a <= b,
1105
+ lessThanOrEqual: (/** @type {number} */ a, /** @type {number} */ b) => a <= b,
584
1106
 
585
- gte: (a, b) => a >= b,
586
- "greaterThanOrEqual": (a, b) => a >= b,
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
- lte: (a, b) => a <= b,
589
- "lessThanOrEqual": (a, b) => a <= b,
1114
+ if: (/** @type {any} */ condition, /** @type {any} */ a, /** @type {any} */ b) =>
1115
+ condition ? a : b,
590
1116
 
591
- /* ================= UTILITY ================= */
1117
+ typeof: (/** @type {any} */ x) => typeof x,
592
1118
 
593
- clamp: (x, min, max) => {
594
- if (min > max) throw new Error("clamp(): min > max");
595
- return Math.min(Math.max(x, min), max);
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
- if: (condition, a, b) => (condition ? a : b),
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
- /* ================= TYPE ================= */
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
- typeof: (x) => typeof x,
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
- /* ================= STRING ================= */
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
- length: (x) => {
607
- if (typeof x === "string" || Array.isArray(x)) {
608
- return x.length;
1173
+ std: (/** @type {any[]} */ ...args) => {
1174
+ if (args.length < 2) {
1175
+ throw new Error('std() requires at least two values');
609
1176
  }
610
- throw new Error("length() expects string or array");
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
  };