solve-engine 1.0.0-beta.7 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BytecodeBuilder-3egMyuDi.d.cts → BytecodeBuilder-B0xskcv5.d.cts} +2 -1
- package/dist/{BytecodeBuilder-3egMyuDi.d.ts → BytecodeBuilder-B0xskcv5.d.ts} +2 -1
- package/dist/{Configuration-fVpd-HC-.d.cts → Configuration-B-G5gTRn.d.cts} +107 -29
- package/dist/{Configuration-fVpd-HC-.d.ts → Configuration-B-G5gTRn.d.ts} +107 -29
- package/dist/{EngineError-DvPSnhZi.d.cts → EngineError-LU7W7AgI.d.cts} +96 -16
- package/dist/{EngineError-DvPSnhZi.d.ts → EngineError-LU7W7AgI.d.ts} +96 -16
- package/dist/{Lexer-W9MBOP0V.d.ts → Lexer-BN5mt30n.d.ts} +16 -0
- package/dist/{Lexer-Cfm79Dak.d.cts → Lexer-D78E2yDW.d.cts} +16 -0
- package/dist/{PackageRegistry-CjDt-Jy_.d.ts → PackageRegistry-DFeINv2O.d.ts} +54 -13
- package/dist/{PackageRegistry-pHtKythi.d.cts → PackageRegistry-u2zdfFaj.d.cts} +54 -13
- package/dist/{Parselet-DEdF9I7n.d.ts → Parselet-BaySkMV3.d.ts} +37 -5
- package/dist/{Parselet-Cu0bLSis.d.cts → Parselet-DuI1Pjiq.d.cts} +37 -5
- package/dist/{ScopeManager-CKfQtl7l.d.cts → ScopeManager-B6GzdhVG.d.cts} +75 -6
- package/dist/{ScopeManager-B5k0nk05.d.ts → ScopeManager-udv4Twwq.d.ts} +75 -6
- package/dist/{VMCheckpoints-BmDwIXgK.d.ts → VMCheckpoints-BiaIlOOY.d.ts} +4 -4
- package/dist/{VMCheckpoints-CqFuLwrT.d.cts → VMCheckpoints-DwLjivM7.d.cts} +4 -4
- package/dist/{Value-KtAQNHur.d.cts → Value-CXJqDH9J.d.cts} +5 -2
- package/dist/{Value-KtAQNHur.d.ts → Value-CXJqDH9J.d.ts} +5 -2
- package/dist/{chunk-CSG5XVNJ.cjs → chunk-3AFRJYP4.cjs} +556 -414
- package/dist/chunk-3AFRJYP4.cjs.map +1 -0
- package/dist/chunk-3BOK2LDK.js +17 -0
- package/dist/chunk-3BOK2LDK.js.map +1 -0
- package/dist/{chunk-6RDSQURE.js → chunk-3D7V24DG.js} +57 -6
- package/dist/chunk-3D7V24DG.js.map +1 -0
- package/dist/{chunk-MEOHSQEH.js → chunk-3LAEG75D.js} +47 -15
- package/dist/chunk-3LAEG75D.js.map +1 -0
- package/dist/{chunk-P75775WC.js → chunk-3VNGREMU.js} +118 -41
- package/dist/chunk-3VNGREMU.js.map +1 -0
- package/dist/{chunk-4AVD7NZW.cjs → chunk-4B2CNWQU.cjs} +13 -7
- package/dist/chunk-4B2CNWQU.cjs.map +1 -0
- package/dist/{chunk-6GCKCWLB.js → chunk-526PMQOA.js} +126 -69
- package/dist/chunk-526PMQOA.js.map +1 -0
- package/dist/{chunk-2CS6OMZK.js → chunk-5F4C26RU.js} +8 -5
- package/dist/chunk-5F4C26RU.js.map +1 -0
- package/dist/{chunk-M4N5GA5Z.cjs → chunk-5KMIY374.cjs} +57 -6
- package/dist/chunk-5KMIY374.cjs.map +1 -0
- package/dist/{chunk-R24DI24X.cjs → chunk-5LI5EPGJ.cjs} +6 -5
- package/dist/chunk-5LI5EPGJ.cjs.map +1 -0
- package/dist/{chunk-BAMZT3GL.js → chunk-5WVP4YHP.js} +939 -134
- package/dist/chunk-5WVP4YHP.js.map +1 -0
- package/dist/{chunk-MZOVP3WI.cjs → chunk-6KFYJ6TD.cjs} +3 -2
- package/dist/chunk-6KFYJ6TD.cjs.map +1 -0
- package/dist/{chunk-BXKJLM5R.cjs → chunk-6WFMPTGB.cjs} +972 -636
- package/dist/chunk-6WFMPTGB.cjs.map +1 -0
- package/dist/{chunk-2MV4HBKC.cjs → chunk-A2N2GFCG.cjs} +10 -10
- package/dist/{chunk-2MV4HBKC.cjs.map → chunk-A2N2GFCG.cjs.map} +1 -1
- package/dist/chunk-ADBA3XBE.cjs +20 -0
- package/dist/chunk-ADBA3XBE.cjs.map +1 -0
- package/dist/{chunk-MBNQVDVC.cjs → chunk-AMOJYLG2.cjs} +16 -10
- package/dist/chunk-AMOJYLG2.cjs.map +1 -0
- package/dist/{chunk-CLVQBF5C.cjs → chunk-AQNHHJMJ.cjs} +5 -5
- package/dist/{chunk-CLVQBF5C.cjs.map → chunk-AQNHHJMJ.cjs.map} +1 -1
- package/dist/{chunk-AHCLWAM5.cjs → chunk-BHTNFEGZ.cjs} +13 -13
- package/dist/chunk-BHTNFEGZ.cjs.map +1 -0
- package/dist/{chunk-35IWNZPJ.cjs → chunk-E4NUZXVK.cjs} +58 -27
- package/dist/chunk-E4NUZXVK.cjs.map +1 -0
- package/dist/{chunk-P33SBYGG.js → chunk-GLG5ZMX7.js} +54 -23
- package/dist/chunk-GLG5ZMX7.js.map +1 -0
- package/dist/{chunk-BVAR5J2F.js → chunk-GQCOSXMG.js} +117 -6
- package/dist/chunk-GQCOSXMG.js.map +1 -0
- package/dist/{chunk-QDMPOAI7.cjs → chunk-HDP7VK3C.cjs} +1118 -313
- package/dist/chunk-HDP7VK3C.cjs.map +1 -0
- package/dist/{chunk-GVWHCL2N.js → chunk-HMOISHXR.js} +217 -75
- package/dist/chunk-HMOISHXR.js.map +1 -0
- package/dist/{chunk-MTX2KVU7.cjs → chunk-HVQFNJKE.cjs} +122 -90
- package/dist/chunk-HVQFNJKE.cjs.map +1 -0
- package/dist/{chunk-OC6LTXYS.cjs → chunk-JMXUNXQS.cjs} +126 -7
- package/dist/chunk-JMXUNXQS.cjs.map +1 -0
- package/dist/{chunk-WWOLFXTX.cjs → chunk-KBSXGXPM.cjs} +10 -7
- package/dist/chunk-KBSXGXPM.cjs.map +1 -0
- package/dist/{chunk-YU2CUNFO.js → chunk-KV7UW6T6.js} +11 -5
- package/dist/chunk-KV7UW6T6.js.map +1 -0
- package/dist/{chunk-CCJPSHGI.cjs → chunk-N7FNVFXW.cjs} +4 -12
- package/dist/chunk-N7FNVFXW.cjs.map +1 -0
- package/dist/{chunk-CYFK5SY2.js → chunk-O3ANBHSA.js} +169 -39
- package/dist/chunk-O3ANBHSA.js.map +1 -0
- package/dist/{chunk-NZFKROS7.cjs → chunk-PA4VC73I.cjs} +130 -72
- package/dist/chunk-PA4VC73I.cjs.map +1 -0
- package/dist/{chunk-NJTXJ5AG.cjs → chunk-PFUESQTW.cjs} +18 -18
- package/dist/{chunk-NJTXJ5AG.cjs.map → chunk-PFUESQTW.cjs.map} +1 -1
- package/dist/{chunk-SFQWJMKT.js → chunk-QY25VWBF.js} +51 -12
- package/dist/chunk-QY25VWBF.js.map +1 -0
- package/dist/{chunk-WC5FFSHB.js → chunk-R3PY4G7J.js} +3 -3
- package/dist/{chunk-WC5FFSHB.js.map → chunk-R3PY4G7J.js.map} +1 -1
- package/dist/{chunk-M5E34VG5.js → chunk-SBUFNJVE.js} +3 -3
- package/dist/{chunk-M5E34VG5.js.map → chunk-SBUFNJVE.js.map} +1 -1
- package/dist/{chunk-UKPGSAZW.js → chunk-TMA4RCEN.js} +3 -3
- package/dist/{chunk-UKPGSAZW.js.map → chunk-TMA4RCEN.js.map} +1 -1
- package/dist/{chunk-V6HA6JJZ.cjs → chunk-U5VAY32K.cjs} +215 -138
- package/dist/chunk-U5VAY32K.cjs.map +1 -0
- package/dist/{chunk-DUEZFTK2.js → chunk-UKQHXPGW.js} +4 -12
- package/dist/chunk-UKQHXPGW.js.map +1 -0
- package/dist/{chunk-4CVLFLOB.cjs → chunk-UM6BVY2S.cjs} +201 -69
- package/dist/chunk-UM6BVY2S.cjs.map +1 -0
- package/dist/{chunk-75JP4BZX.js → chunk-UQ3UIZJC.js} +3 -2
- package/dist/chunk-UQ3UIZJC.js.map +1 -0
- package/dist/{chunk-PCPX42KL.js → chunk-V4XG5QF6.js} +16 -10
- package/dist/chunk-V4XG5QF6.js.map +1 -0
- package/dist/{chunk-GPPLSM2Z.js → chunk-VB37OC6I.js} +6 -5
- package/dist/chunk-VB37OC6I.js.map +1 -0
- package/dist/{chunk-MVBEL57Q.js → chunk-Y7FT4IQT.js} +500 -170
- package/dist/chunk-Y7FT4IQT.js.map +1 -0
- package/dist/{chunk-OFXOTECC.js → chunk-YPPPYLSR.js} +7 -7
- package/dist/chunk-YPPPYLSR.js.map +1 -0
- package/dist/{chunk-M4F66R4O.cjs → chunk-YWQ6V4ZN.cjs} +121 -82
- package/dist/chunk-YWQ6V4ZN.cjs.map +1 -0
- package/dist/constants.cjs +5 -5
- package/dist/constants.d.cts +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +2 -2
- package/dist/engine.cjs +31 -31
- package/dist/engine.d.cts +11 -11
- package/dist/engine.d.ts +11 -11
- package/dist/engine.js +21 -21
- package/dist/errors.cjs +19 -19
- package/dist/errors.d.cts +2 -2
- package/dist/errors.d.ts +2 -2
- package/dist/errors.js +1 -1
- package/dist/format.cjs +29 -22
- package/dist/format.cjs.map +1 -1
- package/dist/format.d.cts +1 -2
- package/dist/format.d.ts +1 -2
- package/dist/format.js +23 -16
- package/dist/format.js.map +1 -1
- package/dist/index.cjs +31 -31
- package/dist/index.d.cts +10 -10
- package/dist/index.d.ts +10 -10
- package/dist/index.js +22 -22
- package/dist/language.cjs +8 -8
- package/dist/language.cjs.map +1 -1
- package/dist/language.d.cts +10 -10
- package/dist/language.d.ts +10 -10
- package/dist/language.js +6 -6
- package/dist/language.js.map +1 -1
- package/dist/lexer.cjs +18 -18
- package/dist/lexer.d.cts +2 -2
- package/dist/lexer.d.ts +2 -2
- package/dist/lexer.js +6 -6
- package/dist/normalizer.cjs +12 -12
- package/dist/normalizer.js +6 -6
- package/dist/packages.cjs +39 -39
- package/dist/packages.d.cts +9 -9
- package/dist/packages.d.ts +9 -9
- package/dist/packages.js +16 -16
- package/dist/parser.cjs +15 -15
- package/dist/parser.d.cts +23 -9
- package/dist/parser.d.ts +23 -9
- package/dist/parser.js +5 -5
- package/dist/{pipeline-CrsRPmsv.d.ts → pipeline-B6k5lCB7.d.ts} +1 -1
- package/dist/{pipeline-DPUQt7yL.d.cts → pipeline-BEb3hujr.d.cts} +1 -1
- package/dist/resolvers.d.cts +2 -2
- package/dist/resolvers.d.ts +2 -2
- package/dist/uom.cjs +17 -17
- package/dist/uom.d.cts +2 -2
- package/dist/uom.d.ts +2 -2
- package/dist/uom.js +7 -7
- package/dist/utilities.cjs +8 -4
- package/dist/utilities.d.cts +19 -3
- package/dist/utilities.d.ts +19 -3
- package/dist/utilities.js +2 -2
- package/dist/vm.cjs +43 -31
- package/dist/vm.d.cts +105 -9
- package/dist/vm.d.ts +105 -9
- package/dist/vm.js +9 -9
- package/package.json +1 -1
- package/dist/chunk-2CS6OMZK.js.map +0 -1
- package/dist/chunk-35IWNZPJ.cjs.map +0 -1
- package/dist/chunk-4AVD7NZW.cjs.map +0 -1
- package/dist/chunk-4CVLFLOB.cjs.map +0 -1
- package/dist/chunk-6GCKCWLB.js.map +0 -1
- package/dist/chunk-6RDSQURE.js.map +0 -1
- package/dist/chunk-75JP4BZX.js.map +0 -1
- package/dist/chunk-AHCLWAM5.cjs.map +0 -1
- package/dist/chunk-BAMZT3GL.js.map +0 -1
- package/dist/chunk-BVAR5J2F.js.map +0 -1
- package/dist/chunk-BXKJLM5R.cjs.map +0 -1
- package/dist/chunk-CCJPSHGI.cjs.map +0 -1
- package/dist/chunk-CSG5XVNJ.cjs.map +0 -1
- package/dist/chunk-CYFK5SY2.js.map +0 -1
- package/dist/chunk-DUEZFTK2.js.map +0 -1
- package/dist/chunk-GPPLSM2Z.js.map +0 -1
- package/dist/chunk-GVWHCL2N.js.map +0 -1
- package/dist/chunk-M4F66R4O.cjs.map +0 -1
- package/dist/chunk-M4N5GA5Z.cjs.map +0 -1
- package/dist/chunk-MBNQVDVC.cjs.map +0 -1
- package/dist/chunk-MEOHSQEH.js.map +0 -1
- package/dist/chunk-MTX2KVU7.cjs.map +0 -1
- package/dist/chunk-MVBEL57Q.js.map +0 -1
- package/dist/chunk-MZOVP3WI.cjs.map +0 -1
- package/dist/chunk-N5YFR4UP.cjs +0 -10
- package/dist/chunk-N5YFR4UP.cjs.map +0 -1
- package/dist/chunk-NZFKROS7.cjs.map +0 -1
- package/dist/chunk-OC6LTXYS.cjs.map +0 -1
- package/dist/chunk-OFXOTECC.js.map +0 -1
- package/dist/chunk-P33SBYGG.js.map +0 -1
- package/dist/chunk-P75775WC.js.map +0 -1
- package/dist/chunk-PCPX42KL.js.map +0 -1
- package/dist/chunk-PFSDGGYL.js +0 -8
- package/dist/chunk-PFSDGGYL.js.map +0 -1
- package/dist/chunk-QDMPOAI7.cjs.map +0 -1
- package/dist/chunk-R24DI24X.cjs.map +0 -1
- package/dist/chunk-SFQWJMKT.js.map +0 -1
- package/dist/chunk-V6HA6JJZ.cjs.map +0 -1
- package/dist/chunk-WWOLFXTX.cjs.map +0 -1
- package/dist/chunk-YU2CUNFO.js.map +0 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { VariableResolver } from './chunk-GCM5JHMX.js';
|
|
2
|
-
import { sharedCurrencyExchange } from './chunk-
|
|
3
|
-
import { powNode, callNode, simplifySymbolic, sameShape, cancelSymbolic, complexNode, complex, RATIONAL_ZERO, rationalFromNumber, freeVariables, symbolicToEntry, rowMajorToColumnMajor, expandSymbolic, matrixMultiply, inverse, determinant, transpose, constNode, rationalToNumber, toPolynomial, polyDegree, polyCoefficients, rationalNeg, substitute, rationalDiv, isRationalZero, varNode, isRationalOne, RATIONAL_ONE, polyUnivariateVar, rationalMul, fromPolynomial,
|
|
4
|
-
import { errorValue, symbolicValue, numberValue, bigIntValue, uomValue, matrixValue, percentageValue, stringValue, hexValue } from './chunk-
|
|
5
|
-
import { getMeasure, convertUnit } from './chunk-
|
|
6
|
-
import { UNIT_TABLE } from './chunk-
|
|
7
|
-
import { ErrorFactory } from './chunk-
|
|
2
|
+
import { sharedCurrencyExchange } from './chunk-TMA4RCEN.js';
|
|
3
|
+
import { powNode, callNode, simplifySymbolic, sameShape, cancelSymbolic, complexNode, complex, RATIONAL_ZERO, rationalFromNumber, freeVariables, symbolicToEntry, rowMajorToColumnMajor, expandSymbolic, matrixMultiply, inverse, determinant, transpose, matrixPower, constNode, rationalToNumber, toPolynomial, polyDegree, polyCoefficients, rationalNeg, substitute, rationalDiv, isRationalZero, varNode, isRationalOne, RATIONAL_ONE, polyUnivariateVar, rationalMul, fromPolynomial, coefficientGcd, polynomialDivide, isZeroPolynomial, rationalCompare, rationalSub, exactIntegerSqrt, rationalAdd, surdNode, compareMonomials, COMPLEX_I, squareFreePart, exactRationalSqrt, symbolicKey, monomialFromKey, monomialToKey, signedSurdNode, exactIntegerCbrt } from './chunk-O3ANBHSA.js';
|
|
4
|
+
import { faultedOperand, errorValue, symbolicValue, numberValue, bigIntValue, uomValue, matrixValue, percentageValue, stringValue, hexValue } from './chunk-GQCOSXMG.js';
|
|
5
|
+
import { getMeasure, convertUnit } from './chunk-R3PY4G7J.js';
|
|
6
|
+
import { UNIT_TABLE } from './chunk-V4XG5QF6.js';
|
|
7
|
+
import { ErrorFactory } from './chunk-3D7V24DG.js';
|
|
8
8
|
|
|
9
9
|
// src/symbolic/MultivariateFactor.ts
|
|
10
10
|
function exactRoot(value, degree) {
|
|
@@ -92,9 +92,9 @@ function factorBinomial(first, second) {
|
|
|
92
92
|
return product([linear, termsToNode([squareOf(cubes.a), middle, squareOf(cubes.b)])]);
|
|
93
93
|
}
|
|
94
94
|
function asPowerPair(first, second, degree) {
|
|
95
|
-
const
|
|
95
|
+
const magnitude2 = second.coefficient.n < 0n ? rationalNeg(second.coefficient) : second.coefficient;
|
|
96
96
|
const firstRoot = exactRoot(first.coefficient, degree);
|
|
97
|
-
const secondRoot = exactRoot(
|
|
97
|
+
const secondRoot = exactRoot(magnitude2, degree);
|
|
98
98
|
if (firstRoot === null || secondRoot === null) return null;
|
|
99
99
|
const firstKey = monomialRoot(first.key, degree);
|
|
100
100
|
const secondKey = monomialRoot(second.key, degree);
|
|
@@ -119,8 +119,8 @@ function factorPerfectSquare(terms) {
|
|
|
119
119
|
if (cross.key !== middle.key) return null;
|
|
120
120
|
const twice = rationalMul(cross.coefficient, { n: 2n, d: 1n });
|
|
121
121
|
const negative = middle.coefficient.n < 0n;
|
|
122
|
-
const
|
|
123
|
-
if (!isRationalOne(rationalDiv(
|
|
122
|
+
const magnitude2 = negative ? rationalNeg(middle.coefficient) : middle.coefficient;
|
|
123
|
+
if (!isRationalOne(rationalDiv(magnitude2, twice))) return null;
|
|
124
124
|
const base = termsToNode([roots.a, negative ? negated(roots.b) : roots.b]);
|
|
125
125
|
return { kind: "pow", base, exponent: constNode(2) };
|
|
126
126
|
}
|
|
@@ -174,13 +174,13 @@ function lcm(a, b) {
|
|
|
174
174
|
return a / gcd(a, b) * b;
|
|
175
175
|
}
|
|
176
176
|
function divisors(value) {
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
177
|
+
const magnitude2 = value < 0n ? -value : value;
|
|
178
|
+
if (magnitude2 === 0n) return null;
|
|
179
179
|
const found = [];
|
|
180
|
-
for (let candidate = 1n; candidate * candidate <=
|
|
181
|
-
if (
|
|
180
|
+
for (let candidate = 1n; candidate * candidate <= magnitude2; candidate++) {
|
|
181
|
+
if (magnitude2 % candidate !== 0n) continue;
|
|
182
182
|
found.push(candidate);
|
|
183
|
-
const paired =
|
|
183
|
+
const paired = magnitude2 / candidate;
|
|
184
184
|
if (paired !== candidate) found.push(paired);
|
|
185
185
|
if (found.length > FACTOR_MAX_ROOT_CANDIDATES) return null;
|
|
186
186
|
}
|
|
@@ -246,8 +246,8 @@ function coefficientsToNode(descending, variable) {
|
|
|
246
246
|
const terms = /* @__PURE__ */ new Map();
|
|
247
247
|
descending.forEach((coeff, index) => {
|
|
248
248
|
if (isRationalZero(coeff)) return;
|
|
249
|
-
const
|
|
250
|
-
terms.set(
|
|
249
|
+
const power3 = degree - index;
|
|
250
|
+
terms.set(power3 === 0 ? "" : power3 === 1 ? variable : `${variable}^${power3}`, coeff);
|
|
251
251
|
});
|
|
252
252
|
return fromPolynomial({ terms, vars: terms.size > 0 ? [variable] : [] });
|
|
253
253
|
}
|
|
@@ -463,10 +463,10 @@ function trigonometricRoots(p, q, shift) {
|
|
|
463
463
|
const pNumber = rationalToNumber(p);
|
|
464
464
|
const qNumber = rationalToNumber(q);
|
|
465
465
|
const shiftNumber = rationalToNumber(shift);
|
|
466
|
-
const
|
|
466
|
+
const magnitude2 = 2 * Math.sqrt(-pNumber / 3);
|
|
467
467
|
const ratio = 3 * qNumber / (2 * pNumber) * Math.sqrt(-3 / pNumber);
|
|
468
468
|
const angle = Math.acos(Math.min(1, Math.max(-1, ratio))) / 3;
|
|
469
|
-
return [0, 1, 2].map((k) =>
|
|
469
|
+
return [0, 1, 2].map((k) => magnitude2 * Math.cos(angle - 2 * Math.PI * k / 3) + shiftNumber).sort((left, right) => left - right);
|
|
470
470
|
}
|
|
471
471
|
function depressQuartic(b, c, d, e) {
|
|
472
472
|
const shift = rationalNeg(rationalDiv(b, FOUR));
|
|
@@ -535,8 +535,8 @@ function nestedSquareRoots(p, r, discriminant) {
|
|
|
535
535
|
const roots = [];
|
|
536
536
|
for (const { node, positive } of solutions) {
|
|
537
537
|
const radicand = positive ? node : { kind: "neg", operand: node };
|
|
538
|
-
const
|
|
539
|
-
const square = positive ?
|
|
538
|
+
const magnitude2 = { kind: "call", name: "sqrt", args: [radicand] };
|
|
539
|
+
const square = positive ? magnitude2 : { kind: "mul", left: magnitude2, right: complexNode(COMPLEX_I) };
|
|
540
540
|
roots.push({ kind: "neg", operand: square }, square);
|
|
541
541
|
}
|
|
542
542
|
return roots;
|
|
@@ -545,13 +545,11 @@ function halved(node) {
|
|
|
545
545
|
return { kind: "div", left: node, right: constNode(TWO) };
|
|
546
546
|
}
|
|
547
547
|
function complexBiquadraticRoots(p, r) {
|
|
548
|
+
const centre = rationalDiv(rationalNeg(p), TWO);
|
|
548
549
|
const modulus = exactRationalSqrt(r);
|
|
549
|
-
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
if (rationalCompare(realPart, RATIONAL_ZERO) < 0 || rationalCompare(imaginaryPart, RATIONAL_ZERO) < 0) return null;
|
|
553
|
-
const re = surdNode(realPart);
|
|
554
|
-
const im = { kind: "mul", left: surdNode(imaginaryPart), right: complexNode(COMPLEX_I) };
|
|
550
|
+
const re = modulus === null ? nestedSurd(surdNode(r), centre) : surdNode(rationalDiv(rationalAdd(modulus, centre), TWO));
|
|
551
|
+
const imaginaryMagnitude = modulus === null ? nestedSurd(surdNode(r), rationalNeg(centre)) : surdNode(rationalDiv(rationalSub(modulus, centre), TWO));
|
|
552
|
+
const im = { kind: "mul", left: imaginaryMagnitude, right: complexNode(COMPLEX_I) };
|
|
555
553
|
const first = { kind: "add", left: re, right: im };
|
|
556
554
|
const second = { kind: "sub", left: re, right: im };
|
|
557
555
|
return [
|
|
@@ -561,6 +559,10 @@ function complexBiquadraticRoots(p, r) {
|
|
|
561
559
|
first
|
|
562
560
|
];
|
|
563
561
|
}
|
|
562
|
+
function nestedSurd(surd, offset) {
|
|
563
|
+
const shifted = offset.n >= 0n ? { kind: "add", left: surd, right: constNode(offset) } : { kind: "sub", left: surd, right: constNode(rationalNeg(offset)) };
|
|
564
|
+
return { kind: "call", name: "sqrt", args: [halved(shifted)] };
|
|
565
|
+
}
|
|
564
566
|
function splitQuarticRoots(p, q, r, solveQuadratic2) {
|
|
565
567
|
const resolvent = [
|
|
566
568
|
{ n: 1n, d: 1n },
|
|
@@ -585,21 +587,185 @@ function splitQuarticRoots(p, q, r, solveQuadratic2) {
|
|
|
585
587
|
return null;
|
|
586
588
|
}
|
|
587
589
|
|
|
590
|
+
// src/symbolic/NumericRoots.ts
|
|
591
|
+
var DURAND_KERNER_MAX_ITERATIONS = 200;
|
|
592
|
+
var DURAND_KERNER_TOLERANCE = 1e-14;
|
|
593
|
+
var ROOT_RESIDUAL_TOLERANCE = 1e-10;
|
|
594
|
+
var ROOT_SEPARATION_TOLERANCE = 1e-10;
|
|
595
|
+
var AXIS_SNAP_TOLERANCE = 1e-9;
|
|
596
|
+
var CONJUGATE_PAIR_TOLERANCE = 1e-8;
|
|
597
|
+
var NEWTON_POLISH_STEPS = 4;
|
|
598
|
+
function subtract(a, b) {
|
|
599
|
+
return { re: a.re - b.re, im: a.im - b.im };
|
|
600
|
+
}
|
|
601
|
+
function multiply(a, b) {
|
|
602
|
+
return { re: a.re * b.re - a.im * b.im, im: a.re * b.im + a.im * b.re };
|
|
603
|
+
}
|
|
604
|
+
function divide(a, b) {
|
|
605
|
+
if (Math.abs(b.re) >= Math.abs(b.im)) {
|
|
606
|
+
const ratio2 = b.im / b.re;
|
|
607
|
+
const denominator2 = b.re + b.im * ratio2;
|
|
608
|
+
return { re: (a.re + a.im * ratio2) / denominator2, im: (a.im - a.re * ratio2) / denominator2 };
|
|
609
|
+
}
|
|
610
|
+
const ratio = b.re / b.im;
|
|
611
|
+
const denominator = b.re * ratio + b.im;
|
|
612
|
+
return { re: (a.re * ratio + a.im) / denominator, im: (a.im * ratio - a.re) / denominator };
|
|
613
|
+
}
|
|
614
|
+
function magnitude(z) {
|
|
615
|
+
return Math.hypot(z.re, z.im);
|
|
616
|
+
}
|
|
617
|
+
function evaluateAt2(descending, z) {
|
|
618
|
+
let re = 0;
|
|
619
|
+
let im = 0;
|
|
620
|
+
for (const coefficient of descending) {
|
|
621
|
+
const nextRe = re * z.re - im * z.im + coefficient;
|
|
622
|
+
im = re * z.im + im * z.re;
|
|
623
|
+
re = nextRe;
|
|
624
|
+
}
|
|
625
|
+
return { re, im };
|
|
626
|
+
}
|
|
627
|
+
function evaluationScale(descending, z) {
|
|
628
|
+
const radius = magnitude(z);
|
|
629
|
+
let total = 0;
|
|
630
|
+
for (const coefficient of descending) total = total * radius + Math.abs(coefficient);
|
|
631
|
+
return total;
|
|
632
|
+
}
|
|
633
|
+
function differentiate(descending) {
|
|
634
|
+
const degree = descending.length - 1;
|
|
635
|
+
const derivative = [];
|
|
636
|
+
for (let i = 0; i < degree; i++) derivative.push(descending[i] * (degree - i));
|
|
637
|
+
return derivative;
|
|
638
|
+
}
|
|
639
|
+
function squareFreeDoubles(descending) {
|
|
640
|
+
const ascending = [...descending].reverse();
|
|
641
|
+
const squareFree = [...squareFreePart(ascending)].reverse();
|
|
642
|
+
const doubles = squareFree.map(rationalToNumber);
|
|
643
|
+
if (doubles.some((value) => !Number.isFinite(value))) return null;
|
|
644
|
+
return doubles;
|
|
645
|
+
}
|
|
646
|
+
function initialEstimates(monic) {
|
|
647
|
+
const degree = monic.length - 1;
|
|
648
|
+
let largest = 0;
|
|
649
|
+
for (let i = 1; i < monic.length; i++) largest = Math.max(largest, Math.abs(monic[i]));
|
|
650
|
+
const radius = 1 + largest;
|
|
651
|
+
const centre = -monic[1] / degree;
|
|
652
|
+
const estimates = [];
|
|
653
|
+
for (let k = 0; k < degree; k++) {
|
|
654
|
+
const angle = 2 * Math.PI * k / degree + Math.PI / (2 * degree);
|
|
655
|
+
estimates.push({ re: centre + radius * Math.cos(angle), im: radius * Math.sin(angle) });
|
|
656
|
+
}
|
|
657
|
+
return estimates;
|
|
658
|
+
}
|
|
659
|
+
function durandKerner(monic) {
|
|
660
|
+
const degree = monic.length - 1;
|
|
661
|
+
const estimates = initialEstimates(monic);
|
|
662
|
+
for (let iteration = 0; iteration < DURAND_KERNER_MAX_ITERATIONS; iteration++) {
|
|
663
|
+
let largestStep = 0;
|
|
664
|
+
let largestRoot = 1;
|
|
665
|
+
for (let i = 0; i < degree; i++) {
|
|
666
|
+
let denominator = { re: 1, im: 0 };
|
|
667
|
+
for (let j = 0; j < degree; j++) {
|
|
668
|
+
if (i !== j) denominator = multiply(denominator, subtract(estimates[i], estimates[j]));
|
|
669
|
+
}
|
|
670
|
+
if (denominator.re === 0 && denominator.im === 0) {
|
|
671
|
+
estimates[i] = { re: estimates[i].re + 1e-6 * (i + 1), im: estimates[i].im + 1e-6 };
|
|
672
|
+
continue;
|
|
673
|
+
}
|
|
674
|
+
const step = divide(evaluateAt2(monic, estimates[i]), denominator);
|
|
675
|
+
if (!Number.isFinite(step.re) || !Number.isFinite(step.im)) return null;
|
|
676
|
+
estimates[i] = subtract(estimates[i], step);
|
|
677
|
+
largestStep = Math.max(largestStep, magnitude(step));
|
|
678
|
+
largestRoot = Math.max(largestRoot, magnitude(estimates[i]));
|
|
679
|
+
}
|
|
680
|
+
if (largestStep <= DURAND_KERNER_TOLERANCE * largestRoot) return estimates;
|
|
681
|
+
}
|
|
682
|
+
return null;
|
|
683
|
+
}
|
|
684
|
+
function polish(monic, derivative, start) {
|
|
685
|
+
let candidate = start;
|
|
686
|
+
for (let step = 0; step < NEWTON_POLISH_STEPS; step++) {
|
|
687
|
+
const slope = evaluateAt2(derivative, candidate);
|
|
688
|
+
if (slope.re === 0 && slope.im === 0) break;
|
|
689
|
+
const next = subtract(candidate, divide(evaluateAt2(monic, candidate), slope));
|
|
690
|
+
if (!Number.isFinite(next.re) || !Number.isFinite(next.im)) break;
|
|
691
|
+
candidate = next;
|
|
692
|
+
}
|
|
693
|
+
return candidate;
|
|
694
|
+
}
|
|
695
|
+
function snapToAxes(monic, derivative, z) {
|
|
696
|
+
const scale2 = Math.max(1, magnitude(z));
|
|
697
|
+
if (z.im !== 0 && Math.abs(z.im) <= AXIS_SNAP_TOLERANCE * scale2) {
|
|
698
|
+
const candidate = polish(monic, derivative, { re: z.re, im: 0 });
|
|
699
|
+
if (isRootWithinTolerance(monic, candidate)) return { re: candidate.re, im: 0 };
|
|
700
|
+
}
|
|
701
|
+
if (z.re !== 0 && Math.abs(z.re) <= AXIS_SNAP_TOLERANCE * scale2) {
|
|
702
|
+
const candidate = polish(monic, derivative, { re: 0, im: z.im });
|
|
703
|
+
if (isRootWithinTolerance(monic, candidate)) return { re: 0, im: candidate.im };
|
|
704
|
+
}
|
|
705
|
+
return z;
|
|
706
|
+
}
|
|
707
|
+
function isRootWithinTolerance(monic, z) {
|
|
708
|
+
if (!Number.isFinite(z.re) || !Number.isFinite(z.im)) return false;
|
|
709
|
+
return magnitude(evaluateAt2(monic, z)) <= ROOT_RESIDUAL_TOLERANCE * evaluationScale(monic, z);
|
|
710
|
+
}
|
|
711
|
+
function pairConjugates(roots) {
|
|
712
|
+
const result = [...roots];
|
|
713
|
+
const paired = result.map((root) => root.im === 0);
|
|
714
|
+
for (let i = 0; i < result.length; i++) {
|
|
715
|
+
if (paired[i]) continue;
|
|
716
|
+
const wanted = { re: result[i].re, im: -result[i].im };
|
|
717
|
+
let partner = -1;
|
|
718
|
+
let closest = Number.POSITIVE_INFINITY;
|
|
719
|
+
for (let j = i + 1; j < result.length; j++) {
|
|
720
|
+
if (paired[j]) continue;
|
|
721
|
+
const distance = magnitude(subtract(result[j], wanted));
|
|
722
|
+
if (distance < closest) {
|
|
723
|
+
closest = distance;
|
|
724
|
+
partner = j;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
if (partner === -1 || closest > CONJUGATE_PAIR_TOLERANCE * Math.max(1, magnitude(result[i]))) continue;
|
|
728
|
+
const re = (result[i].re + result[partner].re) / 2;
|
|
729
|
+
const im = (Math.abs(result[i].im) + Math.abs(result[partner].im)) / 2;
|
|
730
|
+
const sign = result[i].im < 0 ? -1 : 1;
|
|
731
|
+
result[i] = { re, im: sign * im };
|
|
732
|
+
result[partner] = { re, im: -sign * im };
|
|
733
|
+
paired[i] = true;
|
|
734
|
+
paired[partner] = true;
|
|
735
|
+
}
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
function allSeparated(roots) {
|
|
739
|
+
for (let i = 0; i < roots.length; i++) {
|
|
740
|
+
for (let j = i + 1; j < roots.length; j++) {
|
|
741
|
+
const scale2 = Math.max(1, magnitude(roots[i]), magnitude(roots[j]));
|
|
742
|
+
if (magnitude(subtract(roots[i], roots[j])) <= ROOT_SEPARATION_TOLERANCE * scale2) return false;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
function approximateRoots(descending) {
|
|
748
|
+
const monic = squareFreeDoubles(descending);
|
|
749
|
+
if (monic === null) return null;
|
|
750
|
+
if (monic.length < 2) return [];
|
|
751
|
+
const converged = durandKerner(monic);
|
|
752
|
+
if (converged === null) return null;
|
|
753
|
+
const derivative = differentiate(monic);
|
|
754
|
+
const roots = pairConjugates(converged).map((root) => snapToAxes(monic, derivative, root));
|
|
755
|
+
for (const root of roots) {
|
|
756
|
+
if (!isRootWithinTolerance(monic, root)) return null;
|
|
757
|
+
}
|
|
758
|
+
if (!allSeparated(roots)) return null;
|
|
759
|
+
return [...roots].sort((a, b) => a.re - b.re || a.im - b.im);
|
|
760
|
+
}
|
|
761
|
+
|
|
588
762
|
// src/symbolic/Solve.ts
|
|
589
763
|
var SOLVE_MAX_DEGREE = 8;
|
|
590
|
-
var NEWTON_MAX_ITERATIONS = 100;
|
|
591
|
-
var NEWTON_TOLERANCE = 1e-12;
|
|
592
|
-
var NUMERIC_SCAN_STEPS = 2e3;
|
|
593
764
|
function evaluateRational(descending, x) {
|
|
594
765
|
let total = RATIONAL_ZERO;
|
|
595
766
|
for (const coeff of descending) total = rationalAdd(rationalMul(total, x), coeff);
|
|
596
767
|
return total;
|
|
597
768
|
}
|
|
598
|
-
function evaluateNumeric(descending, x) {
|
|
599
|
-
let total = 0;
|
|
600
|
-
for (const coeff of descending) total = total * x + rationalToNumber(coeff);
|
|
601
|
-
return total;
|
|
602
|
-
}
|
|
603
769
|
function divideByRoot2(descending, root) {
|
|
604
770
|
const quotient = [descending[0]];
|
|
605
771
|
for (let i = 1; i < descending.length - 1; i++) {
|
|
@@ -629,9 +795,9 @@ function solveQuadratic(a, b, c) {
|
|
|
629
795
|
return ascending ? [lower, upper] : [upper, lower];
|
|
630
796
|
}
|
|
631
797
|
function complexQuadraticRoots(negatedB, discriminant, twoA) {
|
|
632
|
-
const
|
|
633
|
-
const rootN = exactIntegerSqrt(
|
|
634
|
-
const rootD = exactIntegerSqrt(
|
|
798
|
+
const magnitude2 = rationalNeg(discriminant);
|
|
799
|
+
const rootN = exactIntegerSqrt(magnitude2.n);
|
|
800
|
+
const rootD = exactIntegerSqrt(magnitude2.d);
|
|
635
801
|
if (rootN !== null && rootD !== null) {
|
|
636
802
|
const imaginary2 = rationalDiv({ n: rootN, d: rootD }, twoA);
|
|
637
803
|
const real2 = rationalDiv(negatedB, twoA);
|
|
@@ -641,7 +807,7 @@ function complexQuadraticRoots(negatedB, discriminant, twoA) {
|
|
|
641
807
|
];
|
|
642
808
|
}
|
|
643
809
|
const real = rationalDiv(negatedB, twoA);
|
|
644
|
-
const scaledRadicand = rationalDiv(
|
|
810
|
+
const scaledRadicand = rationalDiv(magnitude2, rationalMul(twoA, twoA));
|
|
645
811
|
const imaginary = { kind: "mul", left: surdNode(scaledRadicand), right: complexNode(COMPLEX_I) };
|
|
646
812
|
if (isRationalZero(real)) {
|
|
647
813
|
return [{ kind: "neg", operand: imaginary }, imaginary];
|
|
@@ -657,44 +823,8 @@ function rational2() {
|
|
|
657
823
|
function rational4() {
|
|
658
824
|
return { n: 4n, d: 1n };
|
|
659
825
|
}
|
|
660
|
-
function
|
|
661
|
-
|
|
662
|
-
if (leading === 0) return [];
|
|
663
|
-
let bound = 0;
|
|
664
|
-
for (let i = 1; i < descending.length; i++) {
|
|
665
|
-
const ratio = Math.abs(rationalToNumber(descending[i]) / leading);
|
|
666
|
-
if (ratio > bound) bound = ratio;
|
|
667
|
-
}
|
|
668
|
-
bound += 1;
|
|
669
|
-
const roots = [];
|
|
670
|
-
const step = 2 * bound / NUMERIC_SCAN_STEPS;
|
|
671
|
-
let previousX = -bound;
|
|
672
|
-
let previousY = evaluateNumeric(descending, previousX);
|
|
673
|
-
for (let i = 1; i <= NUMERIC_SCAN_STEPS; i++) {
|
|
674
|
-
const x = -bound + i * step;
|
|
675
|
-
const y = evaluateNumeric(descending, x);
|
|
676
|
-
if (previousY === 0) roots.push(previousX);
|
|
677
|
-
else if (previousY < 0 !== y < 0) roots.push(bisect(descending, previousX, x));
|
|
678
|
-
previousX = x;
|
|
679
|
-
previousY = y;
|
|
680
|
-
}
|
|
681
|
-
return roots;
|
|
682
|
-
}
|
|
683
|
-
function bisect(descending, low, high) {
|
|
684
|
-
let left = low;
|
|
685
|
-
let right = high;
|
|
686
|
-
let leftValue = evaluateNumeric(descending, left);
|
|
687
|
-
for (let i = 0; i < NEWTON_MAX_ITERATIONS && right - left > NEWTON_TOLERANCE; i++) {
|
|
688
|
-
const middle = (left + right) / 2;
|
|
689
|
-
const middleValue = evaluateNumeric(descending, middle);
|
|
690
|
-
if (leftValue < 0 === middleValue < 0) {
|
|
691
|
-
left = middle;
|
|
692
|
-
leftValue = middleValue;
|
|
693
|
-
} else {
|
|
694
|
-
right = middle;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
return (left + right) / 2;
|
|
826
|
+
function realApproximation(value) {
|
|
827
|
+
return { re: value, im: 0 };
|
|
698
828
|
}
|
|
699
829
|
function solveForVariable(lhs, rhs, variable) {
|
|
700
830
|
const polynomial = toPolynomial({ kind: "sub", left: lhs, right: rhs });
|
|
@@ -763,38 +893,76 @@ function closedFormRoots(descending) {
|
|
|
763
893
|
}
|
|
764
894
|
return null;
|
|
765
895
|
}
|
|
766
|
-
function
|
|
767
|
-
const
|
|
896
|
+
function extractRationalRoots(descending) {
|
|
897
|
+
const roots = [];
|
|
768
898
|
let remaining = [...descending];
|
|
769
|
-
let
|
|
770
|
-
while (
|
|
771
|
-
|
|
899
|
+
let powersOfX = 0;
|
|
900
|
+
while (remaining.length > 1 && isRationalZero(remaining[remaining.length - 1])) {
|
|
901
|
+
remaining = remaining.slice(0, -1);
|
|
902
|
+
powersOfX++;
|
|
903
|
+
}
|
|
904
|
+
if (powersOfX > 0) roots.push(RATIONAL_ZERO);
|
|
905
|
+
while (remaining.length > 1) {
|
|
906
|
+
const found = rationalRoots(remaining);
|
|
907
|
+
if (found.length === 0) break;
|
|
908
|
+
for (const root of found) {
|
|
909
|
+
let multiplicity = 0;
|
|
772
910
|
while (remaining.length > 1 && isRationalZero(evaluateRational(remaining, root))) {
|
|
773
911
|
remaining = divideByRoot2(remaining, root);
|
|
912
|
+
multiplicity++;
|
|
774
913
|
}
|
|
775
|
-
|
|
914
|
+
if (multiplicity > 0) roots.push(root);
|
|
776
915
|
}
|
|
777
|
-
roots = remaining.length > 1 ? [...rationalRoots(remaining)].sort(rationalCompare) : [];
|
|
778
916
|
}
|
|
917
|
+
return { roots: roots.sort(rationalCompare), remaining };
|
|
918
|
+
}
|
|
919
|
+
function solveUnivariate(descending) {
|
|
920
|
+
const { roots, remaining } = extractRationalRoots(descending);
|
|
921
|
+
const exact = roots.map(constNode);
|
|
922
|
+
const approximate = [];
|
|
779
923
|
const leftoverDegree = remaining.length - 1;
|
|
924
|
+
const solvedDegree = descending.length - remaining.length;
|
|
925
|
+
if (leftoverDegree <= 0) return finish(exact, approximate, solvedDegree, 0, "");
|
|
780
926
|
if (leftoverDegree === 1) {
|
|
781
927
|
exact.push(constNode(rationalDiv(rationalNeg(remaining[1]), remaining[0])));
|
|
782
|
-
return
|
|
928
|
+
return finish(exact, approximate, solvedDegree + 1, 0, "");
|
|
783
929
|
}
|
|
784
930
|
if (leftoverDegree === 2) {
|
|
785
|
-
|
|
786
|
-
return
|
|
787
|
-
}
|
|
788
|
-
if (leftoverDegree <= 0) {
|
|
789
|
-
return exact.length > 0 ? { kind: "roots", exact, approximate: [] } : { kind: "no-real-solutions", reason: "there is no value that satisfies this equation" };
|
|
931
|
+
exact.push(...solveQuadratic(remaining[0], remaining[1], remaining[2]));
|
|
932
|
+
return finish(exact, approximate, solvedDegree + 2, 0, "");
|
|
790
933
|
}
|
|
791
934
|
const closedForm = closedFormRoots(remaining);
|
|
792
935
|
if (closedForm !== null) {
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
936
|
+
exact.push(...closedForm.exact);
|
|
937
|
+
approximate.push(...closedForm.approximate.map(realApproximation));
|
|
938
|
+
return finish(exact, approximate, solvedDegree + leftoverDegree, 0, "");
|
|
939
|
+
}
|
|
940
|
+
const numeric = approximateRoots(remaining);
|
|
941
|
+
if (numeric !== null) {
|
|
942
|
+
approximate.push(...numeric);
|
|
943
|
+
return finish(exact, approximate, solvedDegree + leftoverDegree, 0, "");
|
|
944
|
+
}
|
|
945
|
+
return finish(
|
|
946
|
+
exact,
|
|
947
|
+
approximate,
|
|
948
|
+
solvedDegree,
|
|
949
|
+
leftoverDegree,
|
|
950
|
+
`a degree-${leftoverDegree} factor of it has no exact solution here and the numerical method did not converge on that factor`
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
function finish(exact, approximate, solvedDegree, unsolvedDegree, reason) {
|
|
954
|
+
const found = exact.length + approximate.length;
|
|
955
|
+
const incoherent = solvedDegree >= 1 && (found < 1 || found > solvedDegree);
|
|
956
|
+
if (unsolvedDegree > 0 || incoherent) {
|
|
957
|
+
return {
|
|
958
|
+
kind: "incomplete",
|
|
959
|
+
exact,
|
|
960
|
+
approximate,
|
|
961
|
+
// A distinct root can stand for several equal ones, so the count found
|
|
962
|
+
// is a lower bound on what it covers. The degree left unsolved is not.
|
|
963
|
+
missing: incoherent ? Math.max(1, solvedDegree + unsolvedDegree - found) : unsolvedDegree,
|
|
964
|
+
reason: incoherent ? `${found} root${found === 1 ? "" : "s"} were produced for a degree-${solvedDegree + unsolvedDegree} equation, which does not add up` : reason
|
|
965
|
+
};
|
|
798
966
|
}
|
|
799
967
|
return { kind: "roots", exact, approximate };
|
|
800
968
|
}
|
|
@@ -871,7 +1039,8 @@ function symbolicNeg(v) {
|
|
|
871
1039
|
function symbolicBuiltin(index, args) {
|
|
872
1040
|
const nodes = [];
|
|
873
1041
|
for (const arg of args) {
|
|
874
|
-
|
|
1042
|
+
const faulted = faultedOperand(arg);
|
|
1043
|
+
if (faulted) return faulted;
|
|
875
1044
|
const node = valueToSymbolic(arg);
|
|
876
1045
|
if (node === null) return unsupported(SYMBOLIC_BUILTIN_NAMES[index] ?? `builtin ${index}`);
|
|
877
1046
|
nodes.push(node);
|
|
@@ -888,19 +1057,28 @@ function rootToValue(root) {
|
|
|
888
1057
|
if (value.type === 0 /* Number */ && !Number.isInteger(value.toNumber())) return symbolicValue(root);
|
|
889
1058
|
return value;
|
|
890
1059
|
}
|
|
1060
|
+
function approximateRootToValue(root) {
|
|
1061
|
+
if (root.im === 0) return numberValue(root.re);
|
|
1062
|
+
return symbolicValue(complexNode(complex(rationalFromNumber(root.re), rationalFromNumber(root.im))));
|
|
1063
|
+
}
|
|
891
1064
|
function solveOutcomeToValue(outcome) {
|
|
892
1065
|
switch (outcome.kind) {
|
|
893
1066
|
case "identity":
|
|
894
1067
|
return stringValue("true for every value");
|
|
895
1068
|
case "contradiction":
|
|
896
1069
|
return stringValue("no solution");
|
|
897
|
-
case "no-real-solutions":
|
|
898
|
-
return stringValue(`no real solutions (${outcome.reason})`);
|
|
899
1070
|
case "unsupported":
|
|
900
1071
|
return errorValue("SYMBOLIC_SOLVE_UNSUPPORTED", `Cannot solve this equation: ${outcome.reason}.`);
|
|
1072
|
+
case "incomplete": {
|
|
1073
|
+
const found = outcome.exact.length + outcome.approximate.length;
|
|
1074
|
+
return errorValue(
|
|
1075
|
+
"SYMBOLIC_SOLVE_INCOMPLETE",
|
|
1076
|
+
`Only ${found} of this equation's ${found + outcome.missing} roots could be found: ${outcome.reason}.`
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
901
1079
|
case "roots": {
|
|
902
1080
|
const values = outcome.exact.map(rootToValue);
|
|
903
|
-
for (const approximate of outcome.approximate) values.push(
|
|
1081
|
+
for (const approximate of outcome.approximate) values.push(approximateRootToValue(approximate));
|
|
904
1082
|
if (values.length === 0) return stringValue("no solution");
|
|
905
1083
|
if (values.length === 1) return values[0];
|
|
906
1084
|
return matrixValue(1, values.length, values.map((v) => v.type === 9 /* Symbolic */ ? v.value : v.toNumber()));
|
|
@@ -945,6 +1123,65 @@ function unifyUom(l, r) {
|
|
|
945
1123
|
}
|
|
946
1124
|
return { lv: l.toNumber(), rv: r.toNumber(), unit: void 0, sameMeasure: true };
|
|
947
1125
|
}
|
|
1126
|
+
var UNIFIED_COMPARISON_TOLERANCE = 1e-12;
|
|
1127
|
+
function largestFiniteMagnitude(a, b, c) {
|
|
1128
|
+
let scale2 = 0;
|
|
1129
|
+
if (Number.isFinite(a) && Math.abs(a) > scale2) scale2 = Math.abs(a);
|
|
1130
|
+
if (Number.isFinite(b) && Math.abs(b) > scale2) scale2 = Math.abs(b);
|
|
1131
|
+
if (Number.isFinite(c) && Math.abs(c) > scale2) scale2 = Math.abs(c);
|
|
1132
|
+
return scale2;
|
|
1133
|
+
}
|
|
1134
|
+
function compareUom(l, r) {
|
|
1135
|
+
const { lv, rv, sameMeasure } = unifyUom(l, r);
|
|
1136
|
+
if (!sameMeasure) return { lv, rv, equal: false, sameMeasure: false };
|
|
1137
|
+
if (lv === rv) return { lv, rv, equal: true, sameMeasure: true };
|
|
1138
|
+
if (!Number.isFinite(lv) || !Number.isFinite(rv)) return { lv, rv, equal: false, sameMeasure: true };
|
|
1139
|
+
const scale2 = largestFiniteMagnitude(lv, rv, r.toNumber());
|
|
1140
|
+
return { lv, rv, equal: Math.abs(lv - rv) <= scale2 * UNIFIED_COMPARISON_TOLERANCE, sameMeasure: true };
|
|
1141
|
+
}
|
|
1142
|
+
function incomparableUnitsError(l, r) {
|
|
1143
|
+
const lUnit = l.type === 6 /* Uom */ ? l.unit : void 0;
|
|
1144
|
+
const rUnit = r.type === 6 /* Uom */ ? r.unit : void 0;
|
|
1145
|
+
return errorValue("INCOMPATIBLE_UNITS", `Cannot compare incompatible units: ${lUnit ?? "?"} and ${rUnit ?? "?"}`);
|
|
1146
|
+
}
|
|
1147
|
+
function toBigIntOperand(v) {
|
|
1148
|
+
if (v.type === 2 /* BigInt */) return v.value;
|
|
1149
|
+
const n = v.toNumber();
|
|
1150
|
+
if (!Number.isInteger(n)) {
|
|
1151
|
+
const shown = Number.isNaN(n) ? "NaN" : String(n);
|
|
1152
|
+
throw ErrorFactory.execution(
|
|
1153
|
+
"BIGINT_INEXACT_OPERAND",
|
|
1154
|
+
`A whole-number (n) value can only be combined with another whole number, and ${shown} is not one`,
|
|
1155
|
+
{ operand: shown }
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
return BigInt(n);
|
|
1159
|
+
}
|
|
1160
|
+
function bigIntDivisionByZero() {
|
|
1161
|
+
return ErrorFactory.execution(
|
|
1162
|
+
"BIGINT_DIVISION_BY_ZERO",
|
|
1163
|
+
"Division by zero: a whole-number (n) division is exact integer division, which has no answer at zero, unlike 1 / 0 which is Infinity"
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
function compareBigIntOperands(l, r) {
|
|
1167
|
+
const lb = exactBigInt(l);
|
|
1168
|
+
const rb = exactBigInt(r);
|
|
1169
|
+
if (lb === null || rb === null) return null;
|
|
1170
|
+
if (lb < rb) return -1;
|
|
1171
|
+
if (lb > rb) return 1;
|
|
1172
|
+
return 0;
|
|
1173
|
+
}
|
|
1174
|
+
function exactBigInt(v) {
|
|
1175
|
+
if (v.type === 2 /* BigInt */) return v.value;
|
|
1176
|
+
const n = v.toNumber();
|
|
1177
|
+
return Number.isInteger(n) ? BigInt(n) : null;
|
|
1178
|
+
}
|
|
1179
|
+
function power(base, exponent) {
|
|
1180
|
+
if (Number.isFinite(exponent)) return Math.pow(base, exponent);
|
|
1181
|
+
if (base === 1) return 1;
|
|
1182
|
+
if (base === -1 && !Number.isNaN(exponent)) return 1;
|
|
1183
|
+
return Math.pow(base, exponent);
|
|
1184
|
+
}
|
|
948
1185
|
function binaryOp(l, r, op, bigOp, symbolicOp) {
|
|
949
1186
|
if (l.type === 13 /* Error */) return l;
|
|
950
1187
|
if (r.type === 13 /* Error */) return r;
|
|
@@ -966,14 +1203,13 @@ function binaryOp(l, r, op, bigOp, symbolicOp) {
|
|
|
966
1203
|
return numberValue(op(l.value, r.value));
|
|
967
1204
|
}
|
|
968
1205
|
if (l.type === 2 /* BigInt */ || r.type === 2 /* BigInt */) {
|
|
969
|
-
const lb =
|
|
970
|
-
const rb =
|
|
1206
|
+
const lb = toBigIntOperand(l);
|
|
1207
|
+
const rb = toBigIntOperand(r);
|
|
971
1208
|
if (bigOp) return bigIntValue(bigOp(lb, rb));
|
|
972
1209
|
return bigIntValue(lb + rb);
|
|
973
1210
|
}
|
|
974
1211
|
if (l.type === 6 /* Uom */ || r.type === 6 /* Uom */) {
|
|
975
1212
|
const { lv, rv, unit, sameMeasure } = unifyUom(l, r);
|
|
976
|
-
if (isNaN(lv) || isNaN(rv)) return numberValue(0);
|
|
977
1213
|
if (!sameMeasure) {
|
|
978
1214
|
const lUnit = l.type === 6 /* Uom */ ? l.unit : void 0;
|
|
979
1215
|
const rUnit = r.type === 6 /* Uom */ ? r.unit : void 0;
|
|
@@ -1003,10 +1239,7 @@ function binaryOp(l, r, op, bigOp, symbolicOp) {
|
|
|
1003
1239
|
const result = rm.data.map((v) => op(scalar, v));
|
|
1004
1240
|
return matrixValue(rm.rows, rm.cols, result);
|
|
1005
1241
|
}
|
|
1006
|
-
|
|
1007
|
-
const rNum = r.toNumber();
|
|
1008
|
-
if (isNaN(lNum) || isNaN(rNum)) return numberValue(0);
|
|
1009
|
-
return numberValue(op(lNum, rNum));
|
|
1242
|
+
return numberValue(op(l.toNumber(), r.toNumber()));
|
|
1010
1243
|
}
|
|
1011
1244
|
|
|
1012
1245
|
// src/vm/OpRegistry.ts
|
|
@@ -1057,7 +1290,7 @@ var sharedOpRegistry = new OpRegistry();
|
|
|
1057
1290
|
|
|
1058
1291
|
// src/symbolic/PartialFractions.ts
|
|
1059
1292
|
var APART_MAX_DEGREE = 12;
|
|
1060
|
-
function
|
|
1293
|
+
function multiply2(a, b) {
|
|
1061
1294
|
if (a.length === 0 || b.length === 0) return [];
|
|
1062
1295
|
const product2 = new Array(a.length + b.length - 1).fill(RATIONAL_ZERO);
|
|
1063
1296
|
a.forEach((left, i) => {
|
|
@@ -1067,9 +1300,9 @@ function multiply(a, b) {
|
|
|
1067
1300
|
});
|
|
1068
1301
|
return product2;
|
|
1069
1302
|
}
|
|
1070
|
-
function
|
|
1303
|
+
function power2(base, exponent) {
|
|
1071
1304
|
let result = [RATIONAL_ONE];
|
|
1072
|
-
for (let i = 0; i < exponent; i++) result =
|
|
1305
|
+
for (let i = 0; i < exponent; i++) result = multiply2(result, base);
|
|
1073
1306
|
return result;
|
|
1074
1307
|
}
|
|
1075
1308
|
function scale(coefficients, factor) {
|
|
@@ -1104,12 +1337,12 @@ function enumerateUnknowns(factors) {
|
|
|
1104
1337
|
return unknowns;
|
|
1105
1338
|
}
|
|
1106
1339
|
function unknownColumn(unknown, factors, denominator) {
|
|
1107
|
-
const divisor =
|
|
1340
|
+
const divisor = power2(factors[unknown.factor].base, unknown.power);
|
|
1108
1341
|
const { quotient, remainder } = polynomialDivide(denominator, divisor);
|
|
1109
1342
|
if (!isZeroPolynomial(remainder)) return null;
|
|
1110
1343
|
const shift = new Array(unknown.degree).fill(RATIONAL_ZERO);
|
|
1111
1344
|
shift.push(RATIONAL_ONE);
|
|
1112
|
-
return
|
|
1345
|
+
return multiply2(quotient, shift);
|
|
1113
1346
|
}
|
|
1114
1347
|
function solveLinearSystem(matrix, target) {
|
|
1115
1348
|
const size = target.length;
|
|
@@ -1289,7 +1522,7 @@ function oneMinusSquare(argument) {
|
|
|
1289
1522
|
function onePlusSquare(argument) {
|
|
1290
1523
|
return { kind: "add", left: one(), right: powNode(argument, constNode(2)) };
|
|
1291
1524
|
}
|
|
1292
|
-
function
|
|
1525
|
+
function differentiate2(node, variable, order = 1) {
|
|
1293
1526
|
if (order < 0 || !Number.isInteger(order)) {
|
|
1294
1527
|
throw ErrorFactory.execution("SYMBOLIC_DERIVATIVE_ORDER_LIMIT", "A derivative order must be a whole number, at least zero.");
|
|
1295
1528
|
}
|
|
@@ -1386,9 +1619,9 @@ function integratePolynomial(node, variable) {
|
|
|
1386
1619
|
}
|
|
1387
1620
|
const ascending = polyCoefficients(polynomial, variable);
|
|
1388
1621
|
let result = constNode(0);
|
|
1389
|
-
ascending.forEach((coeff,
|
|
1622
|
+
ascending.forEach((coeff, power3) => {
|
|
1390
1623
|
if (isRationalZero(coeff)) return;
|
|
1391
|
-
const nextPower =
|
|
1624
|
+
const nextPower = power3 + 1;
|
|
1392
1625
|
const scaled = rationalDiv(coeff, { n: BigInt(nextPower), d: 1n });
|
|
1393
1626
|
const term = {
|
|
1394
1627
|
kind: "mul",
|
|
@@ -1577,7 +1810,7 @@ function taylorSeries(node, variable, point, degree) {
|
|
|
1577
1810
|
let result = constNode(RATIONAL_ZERO);
|
|
1578
1811
|
for (let order = 0; order <= degree; order++) {
|
|
1579
1812
|
if (order > 0) factorial *= BigInt(order);
|
|
1580
|
-
const derivative =
|
|
1813
|
+
const derivative = differentiate2(node, variable, order);
|
|
1581
1814
|
const atPoint = simplifySymbolic(substitute(derivative, variable, pointNode));
|
|
1582
1815
|
if (atPoint.kind !== "const") {
|
|
1583
1816
|
throw ErrorFactory.execution(
|
|
@@ -1589,13 +1822,13 @@ function taylorSeries(node, variable, point, degree) {
|
|
|
1589
1822
|
const coefficient = rationalDiv(atPoint.value, { n: factorial, d: 1n });
|
|
1590
1823
|
if (isRationalZero(coefficient)) continue;
|
|
1591
1824
|
const shifted = isRationalZero(point) ? varNode(variable) : { kind: "sub", left: varNode(variable), right: pointNode };
|
|
1592
|
-
const
|
|
1593
|
-
result = { kind: "add", left: result, right: { kind: "mul", left: constNode(coefficient), right:
|
|
1825
|
+
const power3 = order === 0 ? constNode(1) : order === 1 ? shifted : powNode(shifted, constNode(order));
|
|
1826
|
+
result = { kind: "add", left: result, right: { kind: "mul", left: constNode(coefficient), right: power3 } };
|
|
1594
1827
|
}
|
|
1595
1828
|
return simplifySymbolic(result);
|
|
1596
1829
|
}
|
|
1597
1830
|
function jacobian(functions, variables) {
|
|
1598
|
-
return functions.map((fn) => variables.map((variable) =>
|
|
1831
|
+
return functions.map((fn) => variables.map((variable) => differentiate2(fn, variable)));
|
|
1599
1832
|
}
|
|
1600
1833
|
|
|
1601
1834
|
// src/engine/EngineContext.ts
|
|
@@ -1709,6 +1942,55 @@ function pluraliseUnit(unit, count) {
|
|
|
1709
1942
|
const plural = `${unit}s`;
|
|
1710
1943
|
return UNIT_TABLE[plural.toLowerCase()] === void 0 ? unit : plural;
|
|
1711
1944
|
}
|
|
1945
|
+
function extremum(args, wantLargest) {
|
|
1946
|
+
let best;
|
|
1947
|
+
let hasNaN = false;
|
|
1948
|
+
for (const a of args) {
|
|
1949
|
+
if (Number.isNaN(a.toNumber())) {
|
|
1950
|
+
hasNaN = true;
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
if (best === void 0) {
|
|
1954
|
+
best = a;
|
|
1955
|
+
continue;
|
|
1956
|
+
}
|
|
1957
|
+
const { lv, rv, sameMeasure } = unifyUom(best, a);
|
|
1958
|
+
if (!sameMeasure) {
|
|
1959
|
+
const bestUnit = best.type === 6 /* Uom */ ? best.unit : void 0;
|
|
1960
|
+
const otherUnit = a.type === 6 /* Uom */ ? a.unit : void 0;
|
|
1961
|
+
return errorValue(
|
|
1962
|
+
"INCOMPATIBLE_UNITS",
|
|
1963
|
+
`Cannot compare incompatible units: ${bestUnit ?? "?"} and ${otherUnit ?? "?"}`
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
if (wantLargest ? rv > lv : rv < lv) best = a;
|
|
1967
|
+
}
|
|
1968
|
+
if (hasNaN) return numberValue(NaN);
|
|
1969
|
+
if (best === void 0) return numberValue(wantLargest ? -Infinity : Infinity);
|
|
1970
|
+
return best.type === 6 /* Uom */ ? best : numberValue(best.toNumber());
|
|
1971
|
+
}
|
|
1972
|
+
function keepUnit(operand, magnitude2) {
|
|
1973
|
+
if (operand.type === 6 /* Uom */ && operand.unit !== void 0) return uomValue(magnitude2, operand.unit);
|
|
1974
|
+
return numberValue(magnitude2);
|
|
1975
|
+
}
|
|
1976
|
+
function nonFiniteEuclidOperand(a, b, name) {
|
|
1977
|
+
if (Number.isFinite(a) && Number.isFinite(b)) return null;
|
|
1978
|
+
const culprit = Number.isFinite(a) ? b : a;
|
|
1979
|
+
return errorValue(
|
|
1980
|
+
"INVALID_INTEGER_OPERAND",
|
|
1981
|
+
`${name} needs two whole numbers, and ${Number.isNaN(culprit) ? "one of them has no value" : "one of them is infinite"}`
|
|
1982
|
+
);
|
|
1983
|
+
}
|
|
1984
|
+
function euclidGcd(a, b) {
|
|
1985
|
+
let x = a;
|
|
1986
|
+
let y = b;
|
|
1987
|
+
while (y !== 0) {
|
|
1988
|
+
const t = y;
|
|
1989
|
+
y = x % y;
|
|
1990
|
+
x = t;
|
|
1991
|
+
}
|
|
1992
|
+
return x;
|
|
1993
|
+
}
|
|
1712
1994
|
var builtinFunctions = {
|
|
1713
1995
|
// ── Populated below ──
|
|
1714
1996
|
// sqrt: a negative argument has a complex answer now that there is a complex
|
|
@@ -1722,38 +2004,20 @@ var builtinFunctions = {
|
|
|
1722
2004
|
// abs(a): for a Matrix, "|a|" (Calca's determinant-pipe notation) is a
|
|
1723
2005
|
// valid alias for det(a) (index 64), reusing the SAME implementation
|
|
1724
2006
|
// not a separate one. Plain-number abs is unaffected.
|
|
1725
|
-
1: (args) => args[0].type === 7 /* Matrix */ ? determinant(args[0].value) :
|
|
2007
|
+
1: (args) => args[0].type === 7 /* Matrix */ ? determinant(args[0].value) : keepUnit(args[0], Math.abs(args[0].toNumber())),
|
|
1726
2008
|
// sin/cos/tan accept an angle with a unit; see angleInRadians().
|
|
1727
2009
|
2: (args) => numberValue(Math.sin(angleInRadians(args[0]))),
|
|
1728
2010
|
3: (args) => numberValue(Math.cos(angleInRadians(args[0]))),
|
|
1729
2011
|
4: (args) => numberValue(Math.tan(angleInRadians(args[0]))),
|
|
1730
2012
|
5: (args) => numberValue(Math.log(args[0].toNumber())),
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
// (
|
|
1736
|
-
//
|
|
1737
|
-
9: (args) =>
|
|
1738
|
-
|
|
1739
|
-
let hasNaN = false;
|
|
1740
|
-
for (const a of args) {
|
|
1741
|
-
const n = a.toNumber();
|
|
1742
|
-
if (Number.isNaN(n)) hasNaN = true;
|
|
1743
|
-
else if (n < result) result = n;
|
|
1744
|
-
}
|
|
1745
|
-
return numberValue(hasNaN ? NaN : result);
|
|
1746
|
-
},
|
|
1747
|
-
10: (args) => {
|
|
1748
|
-
let result = -Infinity;
|
|
1749
|
-
let hasNaN = false;
|
|
1750
|
-
for (const a of args) {
|
|
1751
|
-
const n = a.toNumber();
|
|
1752
|
-
if (Number.isNaN(n)) hasNaN = true;
|
|
1753
|
-
else if (n > result) result = n;
|
|
1754
|
-
}
|
|
1755
|
-
return numberValue(hasNaN ? NaN : result);
|
|
1756
|
-
},
|
|
2013
|
+
// round/ceil/floor keep a unit for the same reason abs does; see keepUnit().
|
|
2014
|
+
6: (args) => keepUnit(args[0], Math.ceil(args[0].toNumber())),
|
|
2015
|
+
7: (args) => keepUnit(args[0], Math.floor(args[0].toNumber())),
|
|
2016
|
+
8: (args) => keepUnit(args[0], Math.round(args[0].toNumber())),
|
|
2017
|
+
// min/max: see extremum() for why the winner is carried around as a Value
|
|
2018
|
+
// rather than as a running number.
|
|
2019
|
+
9: (args) => extremum(args, false),
|
|
2020
|
+
10: (args) => extremum(args, true),
|
|
1757
2021
|
11: (args) => numberValue(Math.asin(args[0].toNumber())),
|
|
1758
2022
|
12: (args) => numberValue(Math.acos(args[0].toNumber())),
|
|
1759
2023
|
13: (args) => numberValue(Math.atan(args[0].toNumber())),
|
|
@@ -1779,7 +2043,22 @@ var builtinFunctions = {
|
|
|
1779
2043
|
28: (args) => numberValue(Math.log10(args[0].toNumber())),
|
|
1780
2044
|
29: (args) => numberValue(Math.log1p(args[0].toNumber())),
|
|
1781
2045
|
30: (args) => numberValue(Math.log2(args[0].toNumber())),
|
|
1782
|
-
|
|
2046
|
+
// pow(): the spelled-out form of `^`, so a Matrix base means the same
|
|
2047
|
+
// repeated matrix multiplication `^` means. Without this branch
|
|
2048
|
+
// `pow([1,2;3,4], 2)` was Math.pow of a value whose toNumber() is 0, and
|
|
2049
|
+
// came back as the number 0. See vm/MatrixOps.ts's matrixPower().
|
|
2050
|
+
31: (args) => {
|
|
2051
|
+
if (args[0].type === 7 /* Matrix */) {
|
|
2052
|
+
if (args[1].type !== 0 /* Number */) {
|
|
2053
|
+
return errorValue("MATRIX_POWER_UNSUPPORTED", `pow: a matrix's exponent must be a whole number, as in "pow([1,2;3,4], 2)".`);
|
|
2054
|
+
}
|
|
2055
|
+
return matrixPower(args[0].value, args[1].value);
|
|
2056
|
+
}
|
|
2057
|
+
if (args[1].type === 7 /* Matrix */) {
|
|
2058
|
+
return errorValue("MATRIX_POWER_UNSUPPORTED", `pow: a matrix may only be the base, as in "pow([1,2;3,4], 2)".`);
|
|
2059
|
+
}
|
|
2060
|
+
return numberValue(power(args[0].toNumber(), args[1].toNumber()));
|
|
2061
|
+
},
|
|
1783
2062
|
32: () => numberValue(Math.random()),
|
|
1784
2063
|
// takes no arguments, unlike its neighbours
|
|
1785
2064
|
33: (args) => numberValue(Math.sign(args[0].toNumber())),
|
|
@@ -1802,32 +2081,37 @@ var builtinFunctions = {
|
|
|
1802
2081
|
// gcd(a, b), Euclidean algorithm. Negative inputs are treated by
|
|
1803
2082
|
// magnitude (gcd is conventionally defined over non-negative integers).
|
|
1804
2083
|
38: (args) => {
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
a = t;
|
|
1811
|
-
}
|
|
1812
|
-
return numberValue(a);
|
|
2084
|
+
const a = Math.trunc(Math.abs(args[0].toNumber()));
|
|
2085
|
+
const b = Math.trunc(Math.abs(args[1].toNumber()));
|
|
2086
|
+
const nonFinite = nonFiniteEuclidOperand(a, b, "gcd");
|
|
2087
|
+
if (nonFinite) return nonFinite;
|
|
2088
|
+
return numberValue(euclidGcd(a, b));
|
|
1813
2089
|
},
|
|
1814
2090
|
// lcm(a, b) = |a*b| / gcd(a, b), via the same Euclidean gcd inline
|
|
1815
2091
|
// (a standalone gcd() call site, no shared helper needed for two uses).
|
|
1816
2092
|
39: (args) => {
|
|
1817
2093
|
const a = Math.trunc(Math.abs(args[0].toNumber()));
|
|
1818
2094
|
const b = Math.trunc(Math.abs(args[1].toNumber()));
|
|
2095
|
+
const nonFinite = nonFiniteEuclidOperand(a, b, "lcm");
|
|
2096
|
+
if (nonFinite) return nonFinite;
|
|
1819
2097
|
if (a === 0 || b === 0) return numberValue(0);
|
|
1820
|
-
|
|
1821
|
-
while (y !== 0) {
|
|
1822
|
-
const t = y;
|
|
1823
|
-
y = x % y;
|
|
1824
|
-
x = t;
|
|
1825
|
-
}
|
|
1826
|
-
return numberValue(a / x * b);
|
|
2098
|
+
return numberValue(a / euclidGcd(a, b) * b);
|
|
1827
2099
|
},
|
|
1828
2100
|
// permutation(n, r) = n! / (n-r)!, computed as a running product
|
|
1829
2101
|
// rather than two full factorials, so it stays exact for n well
|
|
1830
2102
|
// beyond where n! itself would exceed Number.MAX_SAFE_INTEGER.
|
|
2103
|
+
//
|
|
2104
|
+
// The loop stops the moment the product stops being a representable
|
|
2105
|
+
// double, which is both the answer `fact` (index 62) already gives for
|
|
2106
|
+
// the same question and the thing that bounds the loop at all. `r` comes
|
|
2107
|
+
// straight off the stack: `permutation(1000000000000, 1000000000000)`
|
|
2108
|
+
// used to run a trillion iterations, of which the first ~170 computed
|
|
2109
|
+
// something and the rest multiplied Infinity by a number, and it had not
|
|
2110
|
+
// returned after thirty seconds. Every factor here is at least 1 and they
|
|
2111
|
+
// descend from `n`, so a product that is still finite after 170 steps
|
|
2112
|
+
// means the factors are small, which in turn means `r <= n` is small: the
|
|
2113
|
+
// early exit bounds the trip count without a separate estimate, and
|
|
2114
|
+
// without ever refusing a permutation that does have an answer.
|
|
1831
2115
|
40: (args) => {
|
|
1832
2116
|
const n = Math.trunc(args[0].toNumber());
|
|
1833
2117
|
const r = Math.trunc(args[1].toNumber());
|
|
@@ -1835,12 +2119,27 @@ var builtinFunctions = {
|
|
|
1835
2119
|
return errorValue("INVALID_RANGE", `permutation: invalid n=${n}, r=${r} (require 0 <= r <= n)`);
|
|
1836
2120
|
}
|
|
1837
2121
|
let result = 1;
|
|
1838
|
-
for (let i = 0; i < r; i++)
|
|
2122
|
+
for (let i = 0; i < r; i++) {
|
|
2123
|
+
result *= n - i;
|
|
2124
|
+
if (!Number.isFinite(result)) {
|
|
2125
|
+
return errorValue(
|
|
2126
|
+
"PERMUTATION_OVERFLOW",
|
|
2127
|
+
`permutation: P(${n}, ${r}) exceeds the maximum representable double (it passed it after ${i + 1} of ${r} factors)`
|
|
2128
|
+
);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
1839
2131
|
return numberValue(result);
|
|
1840
2132
|
},
|
|
1841
2133
|
// combination(n, r) = n! / (r! * (n-r)!), multiply-then-divide one
|
|
1842
2134
|
// step at a time (standard technique) keeps every intermediate result
|
|
1843
2135
|
// an integer, avoiding the overflow a naive factorial-ratio would hit.
|
|
2136
|
+
//
|
|
2137
|
+
// Same early exit, and needed for the same reason: `k = min(r, n-r)`
|
|
2138
|
+
// halves the trip count and bounds nothing, so `combination(1e12, 5e11)`
|
|
2139
|
+
// asked for five hundred billion iterations. The running value is C(n, i),
|
|
2140
|
+
// which grows monotonically to the middle, so once it is past what a
|
|
2141
|
+
// double can hold it never comes back, and the largest run this can now
|
|
2142
|
+
// take is the thousand-odd steps C(n, k) needs to cross that line.
|
|
1844
2143
|
41: (args) => {
|
|
1845
2144
|
const n = Math.trunc(args[0].toNumber());
|
|
1846
2145
|
const r = Math.trunc(args[1].toNumber());
|
|
@@ -1849,7 +2148,15 @@ var builtinFunctions = {
|
|
|
1849
2148
|
}
|
|
1850
2149
|
const k = Math.min(r, n - r);
|
|
1851
2150
|
let result = 1;
|
|
1852
|
-
for (let i = 0; i < k; i++)
|
|
2151
|
+
for (let i = 0; i < k; i++) {
|
|
2152
|
+
result = result * (n - i) / (i + 1);
|
|
2153
|
+
if (!Number.isFinite(result)) {
|
|
2154
|
+
return errorValue(
|
|
2155
|
+
"COMBINATION_OVERFLOW",
|
|
2156
|
+
`combination: C(${n}, ${r}) exceeds the maximum representable double (it passed it after ${i + 1} of ${k} steps)`
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
1853
2160
|
return numberValue(Math.round(result));
|
|
1854
2161
|
},
|
|
1855
2162
|
// average(...), arithmetic mean of any number of arguments. Backs the
|
|
@@ -2099,7 +2406,7 @@ var builtinFunctions = {
|
|
|
2099
2406
|
61: (args) => {
|
|
2100
2407
|
const n = args[0].toNumber();
|
|
2101
2408
|
const x = args[1].toNumber();
|
|
2102
|
-
return numberValue(
|
|
2409
|
+
return numberValue(power(x, 1 / n));
|
|
2103
2410
|
},
|
|
2104
2411
|
// fact(n) / factorial(n) -- integer factorial. Deliberately rejects
|
|
2105
2412
|
// negative or non-integer input (factorial isn't defined for either
|
|
@@ -2196,7 +2503,7 @@ var builtinFunctions = {
|
|
|
2196
2503
|
if (typeof variable !== "string") return variable;
|
|
2197
2504
|
const expression = valueToSymbolic(target);
|
|
2198
2505
|
if (expression === null) return errorValue("SYMBOLIC_NONFINITE_OPERAND", "der needs an expression with an exact value.");
|
|
2199
|
-
return symbolicToValue(
|
|
2506
|
+
return symbolicToValue(differentiate2(expression, variable, orderValue?.toNumber() ?? 1));
|
|
2200
2507
|
},
|
|
2201
2508
|
// integral(expr, variable), the indefinite integral without a constant of
|
|
2202
2509
|
// integration. Reports what it cannot do rather than approximating; see
|
|
@@ -2311,8 +2618,11 @@ var builtinFunctions = {
|
|
|
2311
2618
|
// so nothing is lost. A pair that is not ordered major-then-minor, or
|
|
2312
2619
|
// that spans two different measures, reports rather than guessing.
|
|
2313
2620
|
94: (args) => {
|
|
2314
|
-
const major = args[1].value;
|
|
2315
|
-
const minor = args[2].value;
|
|
2621
|
+
const major = typeof args[1]?.value === "string" ? args[1].value : void 0;
|
|
2622
|
+
const minor = typeof args[2]?.value === "string" ? args[2].value : void 0;
|
|
2623
|
+
if (major === void 0 || minor === void 0) {
|
|
2624
|
+
return errorValue("UNKNOWN_UNIT", 'both units have to be named, as in "12.5 minutes in minutes and seconds"');
|
|
2625
|
+
}
|
|
2316
2626
|
const majorEntry = UNIT_TABLE[major.toLowerCase()];
|
|
2317
2627
|
const minorEntry = UNIT_TABLE[minor.toLowerCase()];
|
|
2318
2628
|
if (majorEntry === void 0 || minorEntry === void 0) {
|
|
@@ -2462,6 +2772,26 @@ var builtinFunctions = {
|
|
|
2462
2772
|
return errorValue("INVALID_RATE", "annual return: nothing was returned, so there is no annual rate");
|
|
2463
2773
|
}
|
|
2464
2774
|
return percentageValue(Math.pow(returned / invested, 1 / years) - 1);
|
|
2775
|
+
},
|
|
2776
|
+
// roundToPlaces(value, places) -> value rounded to that many decimal
|
|
2777
|
+
// places, backing `<value> to <n> dp` (converters/parselets/
|
|
2778
|
+
// RoundingParselets.ts).
|
|
2779
|
+
//
|
|
2780
|
+
// It exists because the obvious bytecode for it, "multiply by 10^p, round,
|
|
2781
|
+
// divide by 10^p", is only safe for values small enough that the multiply
|
|
2782
|
+
// is exact. `1e21 to 2 dp` came back as 999999999999999900000: 1e21 has no
|
|
2783
|
+
// fractional part to round at all, and the round trip through 1e23 was
|
|
2784
|
+
// pure loss. Asking for fewer decimal places than a number has cannot
|
|
2785
|
+
// change it, so a value that is already whole is returned untouched.
|
|
2786
|
+
97: (args) => {
|
|
2787
|
+
const source = args[0];
|
|
2788
|
+
const value = source.toNumber();
|
|
2789
|
+
const places = args[1].toNumber();
|
|
2790
|
+
if (!Number.isFinite(value) || Number.isInteger(value)) return keepUnit(source, value);
|
|
2791
|
+
const scale2 = Math.pow(10, places);
|
|
2792
|
+
const scaled = value * scale2;
|
|
2793
|
+
if (!Number.isFinite(scaled) || Math.abs(scaled) > Number.MAX_SAFE_INTEGER) return keepUnit(source, value);
|
|
2794
|
+
return keepUnit(source, Math.round(scaled) / scale2);
|
|
2465
2795
|
}
|
|
2466
2796
|
};
|
|
2467
2797
|
function applyComplexAccessor(name, value) {
|
|
@@ -2612,6 +2942,6 @@ function globalDagKey(name) {
|
|
|
2612
2942
|
return `global:${name}`;
|
|
2613
2943
|
}
|
|
2614
2944
|
|
|
2615
|
-
export { CPI_MAX_YEAR, CPI_MIN_YEAR, OpRegistry, SYMBOLIC_NATIVE_BUILTINS, adjustForInflation, allocatePluginFunctionIndex, asConverterRegistry, binaryOp, builtinFunctions, createEngineContext, defaultEngineContext, globalDagKey, pluginFunctionRegistry, registerAsConverter, sharedGlobalVariableStore, sharedOpRegistry, solveEquationValues, symbolicBuiltin, symbolicNeg, symbolicPow, unifyUom, unregisterAsConverter };
|
|
2616
|
-
//# sourceMappingURL=chunk-
|
|
2617
|
-
//# sourceMappingURL=chunk-
|
|
2945
|
+
export { CPI_MAX_YEAR, CPI_MIN_YEAR, OpRegistry, SYMBOLIC_NATIVE_BUILTINS, adjustForInflation, allocatePluginFunctionIndex, asConverterRegistry, bigIntDivisionByZero, binaryOp, builtinFunctions, compareBigIntOperands, compareUom, createEngineContext, defaultEngineContext, globalDagKey, incomparableUnitsError, pluginFunctionRegistry, power, registerAsConverter, sharedGlobalVariableStore, sharedOpRegistry, solveEquationValues, symbolicBuiltin, symbolicNeg, symbolicPow, toBigIntOperand, unifyUom, unregisterAsConverter };
|
|
2946
|
+
//# sourceMappingURL=chunk-Y7FT4IQT.js.map
|
|
2947
|
+
//# sourceMappingURL=chunk-Y7FT4IQT.js.map
|