nerdamer 2.0.0-rc.1 → 2.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/BREAKING_CHANGES.md +244 -0
- package/README.md +168 -229
- package/dist/bundle.js +1 -1
- package/dist/bundle.js.LICENSE.txt +6 -6
- package/output/algebra/adapters.d.ts +3 -33
- package/output/algebra/adapters.js +7 -105
- package/output/algebra/algorithms/arith.js +10 -9
- package/output/algebra/algorithms/groebnerBase.d.ts +24 -20
- package/output/algebra/algorithms/groebnerBase.js +486 -723
- package/output/algebra/dispatch.d.ts +1 -0
- package/output/algebra/dispatch.js +25 -0
- package/output/algebra/factor/factor.d.ts +15 -0
- package/output/algebra/factor/factor.js +99 -12
- package/output/algebra/gcd/gcd.d.ts +8 -7
- package/output/algebra/gcd/gcd.js +32 -78
- package/output/algebra/groebner.d.ts +2 -2
- package/output/algebra/groebner.js +6 -12
- package/output/algebra/partfrac.d.ts +3 -3
- package/output/algebra/partfrac.js +28 -61
- package/output/algebra/polynomial/ModularSparsePolynomial.d.ts +131 -0
- package/output/algebra/polynomial/ModularSparsePolynomial.js +378 -0
- package/output/algebra/polynomial/ModularSparsePolynomialFactor.d.ts +40 -0
- package/output/algebra/polynomial/ModularSparsePolynomialFactor.js +661 -0
- package/output/algebra/polynomial/SparsePolynomialFactor.d.ts +64 -0
- package/output/algebra/polynomial/SparsePolynomialFactor.js +492 -0
- package/output/algebra/polynomial/SparsePolynomialGcd.d.ts +29 -0
- package/output/algebra/polynomial/SparsePolynomialGcd.js +259 -0
- package/output/algebra/polynomial/SparsePolynomialMultivariateFactor.d.ts +82 -0
- package/output/algebra/polynomial/SparsePolynomialMultivariateFactor.js +604 -0
- package/output/algebra/polynomial/modularGcd.d.ts +40 -0
- package/output/algebra/polynomial/modularGcd.js +365 -0
- package/output/algebra/polynomialize.js +4 -2
- package/output/algebra/simplify/funcsimp.js +1 -1
- package/output/algebra/simplify/ratsimp.js +1 -1
- package/output/algebra/simplify/simplify.js +11 -2
- package/output/algebra/utils.d.ts +1 -1
- package/output/algebra/utils.js +3 -3
- package/output/api/advanced.d.ts +2 -1
- package/output/api/advanced.js +3 -3
- package/output/api/algebra.d.ts +21 -4
- package/output/api/algebra.js +22 -3
- package/output/api/assumptions.d.ts +1 -1
- package/output/api/assumptions.js +2 -1
- package/output/api/calculus.d.ts +2 -1
- package/output/api/calculus.js +2 -1
- package/output/api/core.d.ts +28 -7
- package/output/api/core.js +28 -2
- package/output/api/languages/deu.d.ts +3 -0
- package/output/api/languages/deu.js +266 -0
- package/output/api/languages/fra.d.ts +3 -0
- package/output/api/languages/fra.js +266 -0
- package/output/api/languages/ita.d.ts +3 -0
- package/output/api/languages/ita.js +266 -0
- package/output/api/languages/nld.d.ts +3 -0
- package/output/api/languages/nld.js +266 -0
- package/output/api/languages/por.d.ts +3 -0
- package/output/api/languages/por.js +266 -0
- package/output/api/languages/spa.d.ts +3 -0
- package/output/api/languages/spa.js +266 -0
- package/output/api/parser.d.ts +10 -2
- package/output/api/parser.js +2 -0
- package/output/api/solve.d.ts +1 -1
- package/output/api/solve.js +2 -1
- package/output/api/structures.d.ts +1 -1
- package/output/api/structures.js +2 -1
- package/output/calculus/adapters.d.ts +8 -0
- package/output/calculus/adapters.js +41 -0
- package/output/calculus/derivative/diff.js +6 -3
- package/output/calculus/dispatch.d.ts +1 -0
- package/output/calculus/dispatch.js +25 -0
- package/output/calculus/integrate/integrate.js +4 -4
- package/output/calculus/integrate/integrationTable.js +16 -16
- package/output/calculus/laplace/ilaplace.js +1 -1
- package/output/calculus/laplace/ilaplaceTable.js +18 -17
- package/output/calculus/laplace/laplaceTable.js +3 -2
- package/output/calculus/limit/limit.js +1 -1
- package/output/core/classes/assumption/Assumption.js +14 -10
- package/output/core/classes/assumption/assume.d.ts +6 -0
- package/output/core/classes/assumption/assume.js +9 -0
- package/output/core/classes/assumption/dispatch.d.ts +1 -0
- package/output/core/classes/assumption/dispatch.js +11 -0
- package/output/core/classes/collection/Collection.js +11 -2
- package/output/core/classes/complex/Complex.d.ts +10 -0
- package/output/core/classes/complex/Complex.js +19 -1
- package/output/core/classes/decimalSet/DecimalSet.js +5 -4
- package/output/core/classes/dictionary/Dictionary.js +4 -1
- package/output/core/classes/expression/CoeffObject.d.ts +1 -1
- package/output/core/classes/expression/CoeffObject.js +1 -2
- package/output/core/classes/expression/Expression.d.ts +35 -26
- package/output/core/classes/expression/Expression.js +77 -50
- package/output/core/classes/expression/analysis.d.ts +1 -1
- package/output/core/classes/expression/analysis.js +7 -7
- package/output/core/classes/expression/format.d.ts +9 -0
- package/output/core/classes/expression/format.js +130 -7
- package/output/core/classes/expression/utils.d.ts +9 -0
- package/output/core/classes/expression/utils.js +52 -6
- package/output/core/classes/matrix/Matrix.d.ts +2 -3
- package/output/core/classes/matrix/Matrix.js +26 -23
- package/output/core/classes/matrix/dispatch.d.ts +1 -0
- package/output/core/classes/matrix/dispatch.js +19 -0
- package/output/core/classes/matrix/functions.d.ts +2 -0
- package/output/core/classes/matrix/functions.js +5 -0
- package/output/core/classes/parser/Parser.d.ts +31 -5
- package/output/core/classes/parser/Parser.js +357 -444
- package/output/core/classes/parser/constants.d.ts +2 -0
- package/output/core/classes/parser/constants.js +4 -2
- package/output/core/classes/parser/controlFlowSignals.d.ts +38 -0
- package/output/core/classes/parser/controlFlowSignals.js +60 -0
- package/output/core/classes/parser/operations/add.js +14 -16
- package/output/core/classes/parser/operations/compare.js +13 -0
- package/output/core/classes/parser/operations/functions.d.ts +3 -4
- package/output/core/classes/parser/operations/functions.js +48 -102
- package/output/core/classes/parser/operations/multiply.js +22 -15
- package/output/core/classes/parser/operations/power.js +11 -11
- package/output/core/classes/parser/operations/subtract.js +2 -1
- package/output/core/classes/parser/preprocess.d.ts +0 -7
- package/output/core/classes/parser/preprocess.js +33 -39
- package/output/core/classes/parser/scripting/controlFlow.d.ts +4 -102
- package/output/core/classes/parser/scripting/controlFlow.js +134 -186
- package/output/core/classes/parser/scripting/deferred.d.ts +4 -0
- package/output/core/classes/parser/scripting/deferred.js +65 -0
- package/output/core/classes/parser/scripting/dispatch.d.ts +1 -0
- package/output/core/classes/parser/scripting/dispatch.js +62 -0
- package/output/core/classes/parser/scripting/evaluate.d.ts +13 -0
- package/output/core/classes/parser/scripting/evaluate.js +17 -1
- package/output/core/classes/parser/scripting/scope.d.ts +1 -28
- package/output/core/classes/parser/scripting/scope.js +36 -40
- package/output/core/classes/parser/types.d.ts +5 -5
- package/output/core/classes/parser/wrappers/IndexedReference.d.ts +4 -3
- package/output/core/classes/parser/wrappers/IndexedReference.js +8 -0
- package/output/core/classes/parser/wrappers/KeyValuePair.d.ts +2 -2
- package/output/core/classes/polynomial/Polynomial.d.ts +5 -1
- package/output/core/classes/polynomial/Polynomial.js +53 -7
- package/output/core/classes/polynomial/SparsePolynomial.d.ts +221 -0
- package/output/core/classes/polynomial/SparsePolynomial.js +824 -0
- package/output/core/classes/polynomial/SparsePolynomialAdapter.d.ts +56 -0
- package/output/core/classes/polynomial/SparsePolynomialAdapter.js +241 -0
- package/output/core/classes/polynomial/Term.js +2 -2
- package/output/core/{adapters.d.ts → classes/polynomial/adapters.d.ts} +2 -2
- package/output/core/classes/polynomial/adapters.js +33 -0
- package/output/core/classes/polynomial/dispatch.d.ts +1 -0
- package/output/core/classes/polynomial/dispatch.js +14 -0
- package/output/core/classes/polynomial/functions.d.ts +8 -0
- package/output/core/classes/polynomial/functions.js +71 -8
- package/output/core/classes/polynomial/utils.js +2 -1
- package/output/core/classes/rational/Rational.d.ts +8 -11
- package/output/core/classes/rational/Rational.js +70 -20
- package/output/core/classes/seq/SEQ.js +4 -4
- package/output/core/classes/valuesSet/ValuesSet.d.ts +9 -1
- package/output/core/classes/valuesSet/ValuesSet.js +22 -4
- package/output/core/classes/vector/Vector.d.ts +2 -3
- package/output/core/classes/vector/Vector.js +11 -15
- package/output/core/classes/vector/dispatch.d.ts +1 -0
- package/output/core/classes/vector/dispatch.js +11 -0
- package/output/core/common/classes/MathematicalAggregate.js +11 -2
- package/output/core/common/classes/StructuredEntity.d.ts +0 -17
- package/output/core/common/classes/StructuredEntity.js +3 -64
- package/output/core/common/common.d.ts +0 -1
- package/output/core/common/common.js +29 -234
- package/output/core/converters/BaseConverter.js +3 -2
- package/output/core/converters/Converter.js +9 -5
- package/output/core/converters/Pattern.js +1 -1
- package/output/core/dispatch.d.ts +18 -0
- package/output/core/dispatch.js +3 -281
- package/output/core/errors.d.ts +259 -474
- package/output/core/errors.js +270 -542
- package/output/core/fullFunctions.d.ts +7 -0
- package/output/core/fullFunctions.js +31 -0
- package/output/core/functions/bigint/bigint.d.ts +13 -1
- package/output/core/functions/bigint/bigint.js +56 -4
- package/output/core/functions/bigint/primeFactor.d.ts +2 -0
- package/output/core/functions/bigint/primeFactor.js +13 -13
- package/output/core/functions/build/definitions.js +6 -0
- package/output/core/functions/complex.d.ts +2 -2
- package/output/core/functions/complex.dispatch.d.ts +1 -0
- package/output/core/functions/complex.dispatch.js +16 -0
- package/output/core/functions/complex.js +7 -7
- package/output/core/functions/decimal.js +2 -1
- package/output/core/functions/expand/expand.js +2 -2
- package/output/core/functions/numeric.d.ts +1 -1
- package/output/core/functions/string.js +2 -1
- package/output/core/functions/subst.js +21 -13
- package/output/core/parserFunctions.d.ts +7 -0
- package/output/core/parserFunctions.js +25 -0
- package/output/index.d.ts +50 -30
- package/output/index.js +147 -19
- package/output/math/defint/defint.d.ts +18 -0
- package/output/math/defint/defint.js +60 -0
- package/output/math/defint/defintDecimal.js +6 -5
- package/output/math/defint/defintNative.js +7 -18
- package/output/math/dispatch.d.ts +1 -0
- package/output/math/dispatch.js +101 -0
- package/output/math/geometry.d.ts +4 -0
- package/output/math/geometry.js +23 -0
- package/output/math/math.d.ts +59 -19
- package/output/math/math.js +244 -100
- package/output/math/trig.js +4 -4
- package/output/math/utils.d.ts +6 -0
- package/output/math/utils.js +39 -0
- package/output/solve/classes/PolynomialSolver.d.ts +33 -14
- package/output/solve/classes/PolynomialSolver.js +267 -67
- package/output/solve/classes/SolutionSet.d.ts +20 -1
- package/output/solve/classes/SolutionSet.js +82 -5
- package/output/solve/dispatch.d.ts +1 -0
- package/output/solve/dispatch.js +13 -0
- package/output/solve/linsolve.d.ts +2 -0
- package/output/solve/linsolve.js +13 -5
- package/output/solve/solve.d.ts +10 -0
- package/output/solve/solve.js +128 -20
- package/output/solve/solveSystem.d.ts +1 -1
- package/output/solve/solveSystem.js +8 -32
- package/output/solve/utils.d.ts +2 -1
- package/output/solve/utils.js +18 -22
- package/output/utils/array.d.ts +1 -1
- package/output/utils/debug.js +2 -1
- package/package.json +22 -38
- package/dist/parser.js +0 -2
- package/dist/parser.js.LICENSE.txt +0 -7
- package/docs-data/parser-functions.json +0 -1960
- package/output/algebra/algorithms/factor.d.ts +0 -3
- package/output/algebra/algorithms/factor.js +0 -5
- package/output/algebra/algorithms/factorMultivariate.d.ts +0 -23
- package/output/algebra/algorithms/factorMultivariate.js +0 -2393
- package/output/algebra/algorithms/factorUnivariate.d.ts +0 -72
- package/output/algebra/algorithms/factorUnivariate.js +0 -1072
- package/output/algebra/algorithms/gcd.d.ts +0 -22
- package/output/algebra/algorithms/gcd.js +0 -690
- package/output/algebra/algorithms/multiPoly/MultiPoly.d.ts +0 -137
- package/output/algebra/algorithms/multiPoly/MultiPoly.js +0 -346
- package/output/algebra/algorithms/poly.d.ts +0 -228
- package/output/algebra/algorithms/poly.js +0 -1299
- package/output/algebra/algorithms/rational.d.ts +0 -17
- package/output/algebra/algorithms/rational.js +0 -115
- package/output/core/adapters.js +0 -40
- package/output/core/classes/parser/operations/comma.d.ts +0 -7
- package/output/core/classes/parser/operations/comma.js +0 -11
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
* - elimination ideals
|
|
13
13
|
* - ideal membership testing
|
|
14
14
|
* - triangular back-substitution solver
|
|
15
|
+
*
|
|
16
|
+
* External references consulted:
|
|
17
|
+
* - SymPy Gröbner basis implementation:
|
|
18
|
+
* https://github.com/sympy/sympy/blob/120ee85f346f6292b763cc195afda4f907260d4a/sympy/polys/groebnertools.py
|
|
19
|
+
* - Giovini, Mora, Niesi, Robbiano, Traverso, "One Sugar Cube, Please":
|
|
20
|
+
* https://doi.org/10.1145/120694.120701
|
|
21
|
+
* - Bigatti, Caboara, Robbiano, "Computing inhomogeneous Gröbner bases":
|
|
22
|
+
* https://doi.org/10.1016/j.jsc.2010.10.002
|
|
15
23
|
*/
|
|
16
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
25
|
exports.GroebnerBudgetExceeded = void 0;
|
|
@@ -21,9 +29,9 @@ exports.eliminate = eliminate;
|
|
|
21
29
|
exports.idealMembership = idealMembership;
|
|
22
30
|
exports.reduceByBasis = reduceByBasis;
|
|
23
31
|
exports.solve = solve;
|
|
32
|
+
const SparsePolynomial_1 = require("../../core/classes/polynomial/SparsePolynomial");
|
|
33
|
+
const errors_1 = require("../../core/errors");
|
|
24
34
|
const bigint_1 = require("../../core/functions/bigint/bigint");
|
|
25
|
-
const MultiPoly_1 = require("./multiPoly/MultiPoly");
|
|
26
|
-
const poly_1 = require("./poly");
|
|
27
35
|
/**
|
|
28
36
|
* Thrown when a Groebner pair-count or basis-size budget is exhausted.
|
|
29
37
|
*
|
|
@@ -44,146 +52,138 @@ class GroebnerBudgetExceeded extends Error {
|
|
|
44
52
|
}
|
|
45
53
|
}
|
|
46
54
|
exports.GroebnerBudgetExceeded = GroebnerBudgetExceeded;
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
function numVarsFromPolys(...polys) {
|
|
54
|
-
let maxIndex = -1;
|
|
55
|
-
for (const p of polys) {
|
|
56
|
-
for (const key of p.terms.keys()) {
|
|
57
|
-
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
58
|
-
for (const [i, e] of exp.entries()) {
|
|
59
|
-
if (e > 0 && i > maxIndex) {
|
|
60
|
-
maxIndex = i;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
55
|
+
function sparseOrder(order) {
|
|
56
|
+
if (order === 'LEX') {
|
|
57
|
+
return 'lex';
|
|
58
|
+
}
|
|
59
|
+
if (order === 'GRLEX') {
|
|
60
|
+
return 'grlex';
|
|
64
61
|
}
|
|
65
|
-
return
|
|
62
|
+
return 'grevlex';
|
|
63
|
+
}
|
|
64
|
+
function ltOrNull(p, order) {
|
|
65
|
+
const leading = p.leadingTerm(sparseOrder(order));
|
|
66
|
+
return leading === null ? null : { exp: leading.exponents, coeff: leading.coefficient };
|
|
66
67
|
}
|
|
67
68
|
function polyKey(p) {
|
|
68
|
-
const parts =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
parts.push(`${k}:${c.toString()}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
69
|
+
const parts = p
|
|
70
|
+
.terms()
|
|
71
|
+
.map(term => `${term.exponents.join(',')}:${term.coefficient.toString()}`);
|
|
74
72
|
parts.sort();
|
|
75
73
|
return parts.join('|');
|
|
76
74
|
}
|
|
77
|
-
function divScalarExact(p, d) {
|
|
78
|
-
if (d === 0n) {
|
|
79
|
-
throw new Error('divScalarExact: divide by 0');
|
|
80
|
-
}
|
|
81
|
-
if (d === 1n) {
|
|
82
|
-
return p;
|
|
83
|
-
}
|
|
84
|
-
const out = new MultiPoly_1.MultiPoly();
|
|
85
|
-
for (const [k, c] of p.terms.entries()) {
|
|
86
|
-
if (c % d !== 0n) {
|
|
87
|
-
throw new Error('divScalarExact: non-exact');
|
|
88
|
-
}
|
|
89
|
-
const q = c / d;
|
|
90
|
-
if (q !== 0n) {
|
|
91
|
-
out.terms.set(k, q);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
out.trim();
|
|
95
|
-
return out;
|
|
96
|
-
}
|
|
97
75
|
/**
|
|
98
76
|
* Primitive normalization under a given order:
|
|
99
|
-
* - normalize (combine like terms)
|
|
100
77
|
* - divide by coefficient content
|
|
101
|
-
* - ensure leading coefficient
|
|
102
|
-
* -
|
|
78
|
+
* - ensure leading coefficient under `order` is positive
|
|
79
|
+
* - make monic when exact integer division permits it
|
|
103
80
|
*/
|
|
104
|
-
function primitiveNormalizeOrder(p0, order
|
|
105
|
-
if (
|
|
106
|
-
return
|
|
107
|
-
}
|
|
108
|
-
let p = (
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
// Attempt monic (exact) if possible
|
|
117
|
-
const lt2 = ltOrNull(p, order, nVars);
|
|
118
|
-
if (lt2 && lt2.coeff !== 0n && lt2.coeff !== 1n) {
|
|
119
|
-
const lc = lt2.coeff;
|
|
120
|
-
let ok = true;
|
|
121
|
-
for (const c of p.terms.values()) {
|
|
122
|
-
if (c % lc !== 0n) {
|
|
123
|
-
ok = false;
|
|
81
|
+
function primitiveNormalizeOrder(p0, order) {
|
|
82
|
+
if (p0.isZero()) {
|
|
83
|
+
return SparsePolynomial_1.SparsePolynomial.zero(p0.variableCount);
|
|
84
|
+
}
|
|
85
|
+
let p = p0.primitivePart().normalizeLeadingSign(sparseOrder(order));
|
|
86
|
+
const lt = ltOrNull(p, order);
|
|
87
|
+
if (lt && lt.coeff !== 0n && lt.coeff !== 1n) {
|
|
88
|
+
let exact = true;
|
|
89
|
+
for (const term of p.terms()) {
|
|
90
|
+
if (term.coefficient % lt.coeff !== 0n) {
|
|
91
|
+
exact = false;
|
|
124
92
|
break;
|
|
125
93
|
}
|
|
126
94
|
}
|
|
127
|
-
if (
|
|
128
|
-
p = (
|
|
95
|
+
if (exact) {
|
|
96
|
+
p = p.divideByScalarExact(lt.coeff);
|
|
129
97
|
}
|
|
130
98
|
}
|
|
131
99
|
return p;
|
|
132
100
|
}
|
|
101
|
+
function expLcmDense(a, b) {
|
|
102
|
+
if (a.length !== b.length) {
|
|
103
|
+
throw new RangeError((0, errors_1.message)('multidegreeMismatch'));
|
|
104
|
+
}
|
|
105
|
+
const out = new Array(a.length);
|
|
106
|
+
for (let i = 0; i < a.length; i++) {
|
|
107
|
+
out[i] = a[i] > b[i] ? a[i] : b[i];
|
|
108
|
+
}
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
function monoDividesDense(a, b) {
|
|
112
|
+
if (a.length !== b.length) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
for (let i = 0; i < a.length; i++) {
|
|
116
|
+
if (a[i] > b[i]) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
function monoSubDense(b, a) {
|
|
123
|
+
if (a.length !== b.length) {
|
|
124
|
+
throw new RangeError((0, errors_1.message)('multidegreeMismatch'));
|
|
125
|
+
}
|
|
126
|
+
const out = new Array(a.length);
|
|
127
|
+
for (let i = 0; i < a.length; i++) {
|
|
128
|
+
out[i] = b[i] - a[i];
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function monomialTotalDegreeDense(monomial) {
|
|
133
|
+
let degree = 0n;
|
|
134
|
+
for (const exponent of monomial) {
|
|
135
|
+
degree += exponent;
|
|
136
|
+
}
|
|
137
|
+
return degree;
|
|
138
|
+
}
|
|
133
139
|
// ============================================================================
|
|
134
140
|
// S-polynomial (fraction-free)
|
|
135
141
|
// ============================================================================
|
|
136
|
-
function sPolynomialFF(
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
function sPolynomialFF(f, g, ltF, ltG) {
|
|
143
|
+
const lcm = expLcmDense(ltF.exp, ltG.exp);
|
|
144
|
+
const fMultiplier = monoSubDense(lcm, ltF.exp);
|
|
145
|
+
const gMultiplier = monoSubDense(lcm, ltG.exp);
|
|
146
|
+
return f.monomialScaleSubtract(ltG.coeff, fMultiplier, g, ltF.coeff, gMultiplier);
|
|
147
|
+
}
|
|
148
|
+
function reducerViews(basis, order, normalize) {
|
|
149
|
+
const reducers = [];
|
|
150
|
+
for (const source of basis) {
|
|
151
|
+
const poly = normalize ? primitiveNormalizeOrder(source, order) : source;
|
|
152
|
+
if (poly.isZero()) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const lt = ltOrNull(poly, order);
|
|
156
|
+
if (lt !== null) {
|
|
157
|
+
reducers.push({ poly, lt });
|
|
158
|
+
}
|
|
143
159
|
}
|
|
144
|
-
|
|
145
|
-
const a = (0, poly_1.monoSubDense)(l, ltF.exp);
|
|
146
|
-
const b = (0, poly_1.monoSubDense)(l, ltG.exp);
|
|
147
|
-
// fraction-free S: lc(g)*x^a*f - lc(f)*x^b*g
|
|
148
|
-
const m1 = MultiPoly_1.MultiPoly.monomial(ltG.coeff, a);
|
|
149
|
-
const m2 = MultiPoly_1.MultiPoly.monomial(ltF.coeff, b);
|
|
150
|
-
const term1 = (0, poly_1.mulPoly)(f, m1);
|
|
151
|
-
const term2 = (0, poly_1.mulPoly)(g, m2);
|
|
152
|
-
return (0, poly_1.mvNormalize)((0, poly_1.subPoly)(term1, term2));
|
|
160
|
+
return reducers;
|
|
153
161
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
function normalFormFractionFree(f0, G, order, nVars) {
|
|
159
|
-
let f = (0, poly_1.mvNormalize)(f0);
|
|
160
|
-
if (polyIsZero(f)) {
|
|
162
|
+
/** Head reduction using leading terms only and exact integer arithmetic. */
|
|
163
|
+
function normalFormFractionFree(f0, basis, order, reducers = reducerViews(basis, order, false), sugarState) {
|
|
164
|
+
let f = f0;
|
|
165
|
+
if (f.isZero()) {
|
|
161
166
|
return f;
|
|
162
167
|
}
|
|
163
168
|
while (true) {
|
|
164
|
-
const ltF = ltOrNull(f, order
|
|
169
|
+
const ltF = ltOrNull(f, order);
|
|
165
170
|
if (!ltF) {
|
|
166
171
|
break;
|
|
167
172
|
}
|
|
168
173
|
let reduced = false;
|
|
169
|
-
for (
|
|
170
|
-
const g =
|
|
171
|
-
if (
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
const ltG = ltOrNull(g, order, nVars);
|
|
175
|
-
if (!ltG) {
|
|
174
|
+
for (let reducerIndex = 0; reducerIndex < reducers.length; reducerIndex++) {
|
|
175
|
+
const { poly: g, lt: ltG } = reducers[reducerIndex];
|
|
176
|
+
if (!monoDividesDense(ltG.exp, ltF.exp)) {
|
|
176
177
|
continue;
|
|
177
178
|
}
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
const multExp = monoSubDense(ltF.exp, ltG.exp);
|
|
180
|
+
if (sugarState) {
|
|
181
|
+
const reductionSugar = sugarState.reducerSugars[reducerIndex] + monomialTotalDegreeDense(multExp);
|
|
182
|
+
if (reductionSugar > sugarState.value) {
|
|
183
|
+
sugarState.value = reductionSugar;
|
|
184
|
+
}
|
|
180
185
|
}
|
|
181
|
-
|
|
182
|
-
// fraction-free cancel the head term:
|
|
183
|
-
// f := lc(g)*f - lc(f)*x^(multExp)*g
|
|
184
|
-
const left = (0, poly_1.scalePoly)(f, ltG.coeff);
|
|
185
|
-
const right = (0, poly_1.mulPoly)(g, MultiPoly_1.MultiPoly.monomial(ltF.coeff, multExp));
|
|
186
|
-
f = (0, poly_1.mvNormalize)((0, poly_1.subPoly)(left, right));
|
|
186
|
+
f = f.scaleSubtractMonomial(ltG.coeff, g, ltF.coeff, multExp);
|
|
187
187
|
reduced = true;
|
|
188
188
|
break;
|
|
189
189
|
}
|
|
@@ -193,223 +193,153 @@ function normalFormFractionFree(f0, G, order, nVars) {
|
|
|
193
193
|
}
|
|
194
194
|
return f;
|
|
195
195
|
}
|
|
196
|
-
function collectDenseTermsDesc(p, order, nVars) {
|
|
197
|
-
const terms = [];
|
|
198
|
-
for (const [key, coeff] of p.terms.entries()) {
|
|
199
|
-
if (coeff === 0n) {
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
const expMap = (0, MultiPoly_1.keyToExp)(key);
|
|
203
|
-
const dense = new Array(nVars).fill(0);
|
|
204
|
-
for (const [i, e] of expMap.entries()) {
|
|
205
|
-
if (i >= 0 && i < nVars) {
|
|
206
|
-
dense[i] = e;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
terms.push({ exp: dense, coeff });
|
|
210
|
-
}
|
|
211
|
-
terms.sort((a, b) => -compareMonomialDense(a.exp, b.exp, order));
|
|
212
|
-
return terms;
|
|
213
|
-
}
|
|
214
196
|
/**
|
|
215
|
-
* Fraction-free reduction allowing reduction of
|
|
216
|
-
* without requiring coefficient division.
|
|
197
|
+
* Fraction-free reduction allowing reduction of any term.
|
|
217
198
|
*
|
|
218
|
-
* For a term t in f and a reducer g whose
|
|
199
|
+
* For a term t in f and a reducer g whose leading monomial divides t:
|
|
219
200
|
* f := lc(g) * f - coeff(t) * x^(tExp - LM(g)) * g
|
|
220
|
-
* and then normalize/primitive-reduce to control coefficient growth.
|
|
221
201
|
*/
|
|
222
|
-
function normalFormFractionFreeAllTerms(f0,
|
|
223
|
-
let f = primitiveNormalizeOrder(
|
|
224
|
-
if (
|
|
225
|
-
return f;
|
|
226
|
-
}
|
|
227
|
-
// Pre-normalize all reducers and cache their leading terms once.
|
|
228
|
-
// The reducers don't change during the loop, so this avoids O(|G| * iterations)
|
|
229
|
-
// redundant normalizations.
|
|
230
|
-
const normalizedG = [];
|
|
231
|
-
for (const g0 of G) {
|
|
232
|
-
const g = primitiveNormalizeOrder((0, poly_1.mvNormalize)(g0), order, nVars);
|
|
233
|
-
if (polyIsZero(g)) {
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
const lt = ltOrNull(g, order, nVars);
|
|
237
|
-
if (!lt || lt.coeff === 0n) {
|
|
238
|
-
continue;
|
|
239
|
-
}
|
|
240
|
-
normalizedG.push({ poly: g, lt });
|
|
241
|
-
}
|
|
242
|
-
if (normalizedG.length === 0) {
|
|
202
|
+
function normalFormFractionFreeAllTerms(f0, basis, order, normalizedBasis = reducerViews(basis, order, true), sugarState) {
|
|
203
|
+
let f = primitiveNormalizeOrder(f0, order);
|
|
204
|
+
if (f.isZero() || normalizedBasis.length === 0) {
|
|
243
205
|
return f;
|
|
244
206
|
}
|
|
207
|
+
const monomialOrder = sparseOrder(order);
|
|
245
208
|
while (true) {
|
|
246
|
-
let
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
for (
|
|
253
|
-
|
|
254
|
-
|
|
209
|
+
let selected = null;
|
|
210
|
+
let selectedReducer = null;
|
|
211
|
+
let selectedReducerIndex = -1;
|
|
212
|
+
for (const term of f.terms()) {
|
|
213
|
+
let reducer = null;
|
|
214
|
+
let reducerIndex = -1;
|
|
215
|
+
for (let i = 0; i < normalizedBasis.length; i++) {
|
|
216
|
+
const candidate = normalizedBasis[i];
|
|
217
|
+
if (monoDividesDense(candidate.lt.exp, term.exponents)) {
|
|
218
|
+
reducer = candidate;
|
|
219
|
+
reducerIndex = i;
|
|
220
|
+
break;
|
|
255
221
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
222
|
+
}
|
|
223
|
+
if (reducer !== null &&
|
|
224
|
+
(selected === null ||
|
|
225
|
+
SparsePolynomial_1.SparsePolynomial.compareMonomials(term.exponents, selected.exponents, monomialOrder) > 0)) {
|
|
226
|
+
selected = term;
|
|
227
|
+
selectedReducer = reducer;
|
|
228
|
+
selectedReducerIndex = reducerIndex;
|
|
262
229
|
}
|
|
263
230
|
}
|
|
264
|
-
if (
|
|
231
|
+
if (selected === null || selectedReducer === null) {
|
|
265
232
|
break;
|
|
266
233
|
}
|
|
234
|
+
const multExp = monoSubDense(selected.exponents, selectedReducer.lt.exp);
|
|
235
|
+
if (sugarState) {
|
|
236
|
+
const reductionSugar = sugarState.reducerSugars[selectedReducerIndex] +
|
|
237
|
+
monomialTotalDegreeDense(multExp);
|
|
238
|
+
if (reductionSugar > sugarState.value) {
|
|
239
|
+
sugarState.value = reductionSugar;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
f = primitiveNormalizeOrder(f.scaleSubtractMonomial(selectedReducer.lt.coeff, selectedReducer.poly, selected.coefficient, multExp), order);
|
|
267
243
|
}
|
|
268
244
|
return f;
|
|
269
245
|
}
|
|
270
246
|
// ============================================================================
|
|
271
247
|
// Canonicalization
|
|
272
248
|
// ============================================================================
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
const exp = (0, MultiPoly_1.keyToExp)(k);
|
|
278
|
-
for (const [i, e] of exp.entries()) {
|
|
279
|
-
if (e > 0) {
|
|
280
|
-
a[i] = true;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
249
|
+
function variablePresence(p) {
|
|
250
|
+
const presence = new Array(p.variableCount).fill(false);
|
|
251
|
+
for (const variableIndex of p.variables()) {
|
|
252
|
+
presence[variableIndex] = true;
|
|
283
253
|
}
|
|
284
|
-
return
|
|
254
|
+
return presence;
|
|
285
255
|
}
|
|
286
|
-
function canonicalizeBasis(
|
|
256
|
+
function canonicalizeBasis(inputBasis, order) {
|
|
287
257
|
const kept = [];
|
|
288
|
-
const
|
|
258
|
+
const keptHeadReducers = [];
|
|
259
|
+
const keptNormalizedReducers = [];
|
|
260
|
+
const input = inputBasis.filter(p => !p.isZero());
|
|
289
261
|
// 1) Reduce each element against what we've already kept, then normalize.
|
|
290
262
|
for (const p of input) {
|
|
291
|
-
const
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
263
|
+
const headReduced = normalFormFractionFree(p, kept, order, keptHeadReducers);
|
|
264
|
+
const normalized = normalFormFractionFreeAllTerms(headReduced, kept, order, keptNormalizedReducers);
|
|
265
|
+
if (!normalized.isZero()) {
|
|
266
|
+
kept.push(normalized);
|
|
267
|
+
keptHeadReducers.push(reducerViews([normalized], order, false)[0]);
|
|
268
|
+
keptNormalizedReducers.push(reducerViews([normalized], order, true)[0]);
|
|
296
269
|
}
|
|
297
270
|
}
|
|
298
271
|
// 2) Interreduce: each polynomial reduced by all others.
|
|
299
272
|
const out = [];
|
|
300
273
|
for (let i = 0; i < kept.length; i++) {
|
|
301
274
|
const others = kept.filter((_, j) => j !== i);
|
|
302
|
-
const
|
|
303
|
-
const
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
275
|
+
const otherHeadReducers = keptHeadReducers.filter((_, j) => j !== i);
|
|
276
|
+
const otherNormalizedReducers = keptNormalizedReducers.filter((_, j) => j !== i);
|
|
277
|
+
const headReduced = normalFormFractionFree(kept[i], others, order, otherHeadReducers);
|
|
278
|
+
const normalized = normalFormFractionFreeAllTerms(headReduced, others, order, otherNormalizedReducers);
|
|
279
|
+
if (!normalized.isZero()) {
|
|
280
|
+
out.push(normalized);
|
|
307
281
|
}
|
|
308
282
|
}
|
|
309
|
-
// 3)
|
|
310
|
-
const betterSameLM = (p, q) => {
|
|
311
|
-
const pp = variablePresence(p, nVars);
|
|
312
|
-
const qp = variablePresence(q, nVars);
|
|
313
|
-
let cp = 0;
|
|
314
|
-
let cq = 0;
|
|
315
|
-
for (let i = 0; i < nVars; i++) {
|
|
316
|
-
if (pp[i]) {
|
|
317
|
-
cp++;
|
|
318
|
-
}
|
|
319
|
-
if (qp[i]) {
|
|
320
|
-
cq++;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
if (cp !== cq) {
|
|
324
|
-
return cp < cq ? p : q;
|
|
325
|
-
}
|
|
326
|
-
if (p.terms.size !== q.terms.size) {
|
|
327
|
-
return p.terms.size < q.terms.size ? p : q;
|
|
328
|
-
}
|
|
329
|
-
const ltp = ltOrNull(p, order, nVars);
|
|
330
|
-
const ltq = ltOrNull(q, order, nVars);
|
|
331
|
-
if (ltp && ltq) {
|
|
332
|
-
const ap = (0, bigint_1.abs)(ltp.coeff);
|
|
333
|
-
const aq = (0, bigint_1.abs)(ltq.coeff);
|
|
334
|
-
if (ap !== aq) {
|
|
335
|
-
return ap < aq ? p : q;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
return p;
|
|
339
|
-
};
|
|
283
|
+
// 3) Remove exact duplicates produced by independent interreduction.
|
|
340
284
|
const byPoly = new Map();
|
|
341
285
|
for (const p of out) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
if (
|
|
364
|
-
const
|
|
365
|
-
if (
|
|
366
|
-
return
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
return p.
|
|
286
|
+
byPoly.set(polyKey(p), p);
|
|
287
|
+
}
|
|
288
|
+
const result = Array.from(byPoly.values());
|
|
289
|
+
const monomialOrder = sparseOrder(order);
|
|
290
|
+
const sortInfo = new Map();
|
|
291
|
+
for (const p of result) {
|
|
292
|
+
sortInfo.set(p, {
|
|
293
|
+
presence: variablePresence(p),
|
|
294
|
+
lt: ltOrNull(p, order),
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
result.sort((p, q) => {
|
|
298
|
+
const pInfo = sortInfo.get(p);
|
|
299
|
+
const qInfo = sortInfo.get(q);
|
|
300
|
+
for (let i = 0; i < p.variableCount; i++) {
|
|
301
|
+
const pv = pInfo.presence[i] ? 1 : 0;
|
|
302
|
+
const qv = qInfo.presence[i] ? 1 : 0;
|
|
303
|
+
if (pv !== qv) {
|
|
304
|
+
return pv - qv;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (pInfo.lt && qInfo.lt) {
|
|
308
|
+
const comparison = SparsePolynomial_1.SparsePolynomial.compareMonomials(pInfo.lt.exp, qInfo.lt.exp, monomialOrder);
|
|
309
|
+
if (comparison !== 0) {
|
|
310
|
+
return comparison;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return p.termCount - q.termCount;
|
|
370
314
|
});
|
|
371
|
-
return
|
|
315
|
+
return result;
|
|
372
316
|
}
|
|
373
317
|
// ============================================================================
|
|
374
318
|
// Sugar strategy
|
|
375
319
|
// ============================================================================
|
|
376
|
-
/**
|
|
377
|
-
* Sugar degree of a polynomial: the total degree of its leading monomial.
|
|
378
|
-
* This is the initial sugar value assigned when a polynomial enters the basis.
|
|
379
|
-
*/
|
|
380
|
-
function sugarDegree(p, order, nVars) {
|
|
381
|
-
const lt = ltOrNull(p, order, nVars);
|
|
382
|
-
if (!lt) {
|
|
383
|
-
return 0;
|
|
384
|
-
}
|
|
385
|
-
return monomialTotalDegreeDense(lt.exp);
|
|
386
|
-
}
|
|
387
320
|
/**
|
|
388
321
|
* Sugar degree of an S-polynomial S(f,g).
|
|
389
322
|
*
|
|
390
323
|
* sugar(S(f,g)) = max(sugar(f) + deg(lcm/LM(f)), sugar(g) + deg(lcm/LM(g)))
|
|
391
|
-
*
|
|
392
|
-
* where deg() is total degree difference between the lcm and respective LMs.
|
|
393
324
|
*/
|
|
394
|
-
function sPairSugar(sugarF, sugarG,
|
|
395
|
-
const
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
const degG = monomialTotalDegreeDense(ltG);
|
|
399
|
-
return Math.max(sugarF + (lcmDeg - degF), sugarG + (lcmDeg - degG));
|
|
325
|
+
function sPairSugar(sugarF, sugarG, ltFDegree, ltGDegree, lcmDegree) {
|
|
326
|
+
const candidateF = sugarF + lcmDegree - ltFDegree;
|
|
327
|
+
const candidateG = sugarG + lcmDegree - ltGDegree;
|
|
328
|
+
return candidateF > candidateG ? candidateF : candidateG;
|
|
400
329
|
}
|
|
401
330
|
/**
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
331
|
+
* Inserts a pair in reverse processing order so the next pair can be removed with pop().
|
|
332
|
+
* Lower sugar and lower lcmDeg are processed first. Exact ties retain the previous
|
|
333
|
+
* last-in-first-out behavior.
|
|
405
334
|
*/
|
|
406
335
|
function insertPairSorted(queue, pair) {
|
|
407
336
|
let lo = 0;
|
|
408
337
|
let hi = queue.length;
|
|
409
338
|
while (lo < hi) {
|
|
410
339
|
const mid = (lo + hi) >>> 1;
|
|
411
|
-
const
|
|
412
|
-
if (
|
|
340
|
+
const current = queue[mid];
|
|
341
|
+
if (current.sugar > pair.sugar ||
|
|
342
|
+
(current.sugar === pair.sugar && current.lcmDeg >= pair.lcmDeg)) {
|
|
413
343
|
lo = mid + 1;
|
|
414
344
|
}
|
|
415
345
|
else {
|
|
@@ -418,7 +348,10 @@ function insertPairSorted(queue, pair) {
|
|
|
418
348
|
}
|
|
419
349
|
queue.splice(lo, 0, pair);
|
|
420
350
|
}
|
|
421
|
-
|
|
351
|
+
// ============================================================================
|
|
352
|
+
// Core Buchberger algorithm
|
|
353
|
+
// ============================================================================
|
|
354
|
+
function groebnerBasisSparse(polys, orderOrOpts = 'LEX') {
|
|
422
355
|
const opts = typeof orderOrOpts === 'string' ? { order: orderOrOpts } : (orderOrOpts ?? {});
|
|
423
356
|
const order = opts.order ?? 'LEX';
|
|
424
357
|
const reduced = opts.reduced ?? true;
|
|
@@ -432,265 +365,240 @@ function groebnerBasis(polys, orderOrOpts = 'LEX') {
|
|
|
432
365
|
pairsReducedToZero: 0,
|
|
433
366
|
basisAppends: 0,
|
|
434
367
|
};
|
|
435
|
-
const
|
|
436
|
-
if (
|
|
368
|
+
const basisInput = polys.filter(p => !p.isZero());
|
|
369
|
+
if (basisInput.length === 0) {
|
|
437
370
|
return [];
|
|
438
371
|
}
|
|
439
|
-
const
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
return lmCache[idx];
|
|
455
|
-
}
|
|
456
|
-
const lt = ltOrNull(G[idx], order, nVars);
|
|
457
|
-
const exp = lt ? lt.exp : null;
|
|
458
|
-
lmCache[idx] = exp;
|
|
459
|
-
return exp;
|
|
372
|
+
const variableCount = basisInput[0].variableCount;
|
|
373
|
+
for (const polynomial of basisInput) {
|
|
374
|
+
if (polynomial.variableCount !== variableCount) {
|
|
375
|
+
throw new RangeError((0, errors_1.message)('multidegreeMismatch'));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const basis = basisInput.slice();
|
|
379
|
+
const headReducers = reducerViews(basis, order, false);
|
|
380
|
+
const normalizedReducers = reducerViews(basis, order, true);
|
|
381
|
+
const lmDegreeCache = headReducers.map(reducer => monomialTotalDegreeDense(reducer.lt.exp));
|
|
382
|
+
const sugarArr = basis.map(polynomial => polynomial.totalDegree() ?? 0n);
|
|
383
|
+
const zeroPairNeighbors = basis.map(() => new Set());
|
|
384
|
+
const markZeroPair = (i, j) => {
|
|
385
|
+
zeroPairNeighbors[i].add(j);
|
|
386
|
+
zeroPairNeighbors[j].add(i);
|
|
460
387
|
};
|
|
461
388
|
const areCoprimeLM = (a, b) => {
|
|
462
|
-
for (let i = 0; i <
|
|
463
|
-
if (a[i] >
|
|
389
|
+
for (let i = 0; i < variableCount; i++) {
|
|
390
|
+
if (a[i] > 0n && b[i] > 0n) {
|
|
464
391
|
return false;
|
|
465
392
|
}
|
|
466
393
|
}
|
|
467
394
|
return true;
|
|
468
395
|
};
|
|
469
396
|
const chainCriterionApplies = (i, j) => {
|
|
470
|
-
const li =
|
|
471
|
-
const lj =
|
|
472
|
-
if (!li || !lj) {
|
|
473
|
-
return false;
|
|
474
|
-
}
|
|
397
|
+
const li = headReducers[i].lt.exp;
|
|
398
|
+
const lj = headReducers[j].lt.exp;
|
|
475
399
|
const lcm = expLcmDense(li, lj);
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
if (
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
continue;
|
|
486
|
-
}
|
|
487
|
-
if (zeroPairs.has(pairKey(i, k)) && zeroPairs.has(pairKey(k, j))) {
|
|
400
|
+
const iNeighbors = zeroPairNeighbors[i];
|
|
401
|
+
const jNeighbors = zeroPairNeighbors[j];
|
|
402
|
+
const candidates = iNeighbors.size <= jNeighbors.size ? iNeighbors : jNeighbors;
|
|
403
|
+
const otherNeighbors = candidates === iNeighbors ? jNeighbors : iNeighbors;
|
|
404
|
+
for (const k of candidates) {
|
|
405
|
+
if (k !== i &&
|
|
406
|
+
k !== j &&
|
|
407
|
+
otherNeighbors.has(k) &&
|
|
408
|
+
monoDividesDense(headReducers[k].lt.exp, lcm)) {
|
|
488
409
|
return true;
|
|
489
410
|
}
|
|
490
411
|
}
|
|
491
412
|
return false;
|
|
492
413
|
};
|
|
493
|
-
|
|
414
|
+
const appendBasis = (h) => {
|
|
415
|
+
const index = basis.length;
|
|
416
|
+
basis.push(h);
|
|
417
|
+
const headReducer = reducerViews([h], order, false)[0];
|
|
418
|
+
const normalizedReducer = reducerViews([h], order, true)[0];
|
|
419
|
+
headReducers.push(headReducer);
|
|
420
|
+
normalizedReducers.push(normalizedReducer);
|
|
421
|
+
lmDegreeCache.push(monomialTotalDegreeDense(headReducer.lt.exp));
|
|
422
|
+
zeroPairNeighbors.push(new Set());
|
|
423
|
+
stats.basisAppends++;
|
|
424
|
+
if (maxBasisSize !== undefined && basis.length > maxBasisSize) {
|
|
425
|
+
throw new GroebnerBudgetExceeded((0, errors_1.message)('groebnerBasisSizeBudgetExceeded', { max: String(maxBasisSize) }), stats);
|
|
426
|
+
}
|
|
427
|
+
return index;
|
|
428
|
+
};
|
|
429
|
+
const reducePair = (i, j, initialSugar) => {
|
|
430
|
+
const sPolynomial = sPolynomialFF(basis[i], basis[j], headReducers[i].lt, headReducers[j].lt);
|
|
431
|
+
const sugarState = initialSugar === undefined
|
|
432
|
+
? undefined
|
|
433
|
+
: { value: initialSugar, reducerSugars: sugarArr };
|
|
434
|
+
const headReduced = normalFormFractionFree(sPolynomial, basis, order, headReducers, sugarState);
|
|
435
|
+
const polynomial = normalFormFractionFreeAllTerms(headReduced, basis, order, normalizedReducers, sugarState);
|
|
436
|
+
return { polynomial, sugar: sugarState?.value };
|
|
437
|
+
};
|
|
438
|
+
// Build initial pair queue.
|
|
494
439
|
if (strategy === 'sugar') {
|
|
495
440
|
const pairQueue = [];
|
|
496
441
|
const makePair = (i, j) => {
|
|
497
|
-
const li =
|
|
498
|
-
const lj =
|
|
499
|
-
if (!li || !lj) {
|
|
500
|
-
return null;
|
|
501
|
-
}
|
|
442
|
+
const li = headReducers[i].lt.exp;
|
|
443
|
+
const lj = headReducers[j].lt.exp;
|
|
502
444
|
const lcm = expLcmDense(li, lj);
|
|
445
|
+
const lcmDegree = monomialTotalDegreeDense(lcm);
|
|
503
446
|
return {
|
|
504
447
|
i,
|
|
505
448
|
j,
|
|
506
|
-
sugar: sPairSugar(sugarArr[i], sugarArr[j],
|
|
507
|
-
lcmDeg:
|
|
449
|
+
sugar: sPairSugar(sugarArr[i], sugarArr[j], lmDegreeCache[i], lmDegreeCache[j], lcmDegree),
|
|
450
|
+
lcmDeg: lcmDegree,
|
|
508
451
|
};
|
|
509
452
|
};
|
|
510
|
-
for (let i = 0; i <
|
|
511
|
-
for (let j = i + 1; j <
|
|
512
|
-
|
|
513
|
-
if (cp) {
|
|
514
|
-
insertPairSorted(pairQueue, cp);
|
|
515
|
-
}
|
|
453
|
+
for (let i = 0; i < basis.length; i++) {
|
|
454
|
+
for (let j = i + 1; j < basis.length; j++) {
|
|
455
|
+
insertPairSorted(pairQueue, makePair(i, j));
|
|
516
456
|
}
|
|
517
457
|
}
|
|
518
458
|
while (pairQueue.length > 0) {
|
|
519
|
-
const { i, j, sugar: pairSugar } = pairQueue
|
|
520
|
-
pairQueue.shift();
|
|
459
|
+
const { i, j, sugar: pairSugar } = pairQueue.pop();
|
|
521
460
|
stats.pairsPopped++;
|
|
522
461
|
if (maxPairsPopped !== undefined && stats.pairsPopped > maxPairsPopped) {
|
|
523
|
-
throw new GroebnerBudgetExceeded(
|
|
462
|
+
throw new GroebnerBudgetExceeded((0, errors_1.message)('groebnerPairsBudgetExceeded', { max: String(maxPairsPopped) }), stats);
|
|
524
463
|
}
|
|
525
|
-
|
|
526
|
-
const
|
|
527
|
-
|
|
528
|
-
if (lmi && lmj && areCoprimeLM(lmi, lmj)) {
|
|
464
|
+
const lmi = headReducers[i].lt.exp;
|
|
465
|
+
const lmj = headReducers[j].lt.exp;
|
|
466
|
+
if (areCoprimeLM(lmi, lmj)) {
|
|
529
467
|
stats.pairsSkippedProduct++;
|
|
530
|
-
|
|
468
|
+
markZeroPair(i, j);
|
|
531
469
|
continue;
|
|
532
470
|
}
|
|
533
471
|
if (chainCriterionApplies(i, j)) {
|
|
534
472
|
stats.pairsSkippedChain++;
|
|
535
|
-
|
|
473
|
+
markZeroPair(i, j);
|
|
536
474
|
continue;
|
|
537
475
|
}
|
|
538
|
-
const
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
const h = primitiveNormalizeOrder(h1, order, nVars);
|
|
542
|
-
if (polyIsZero(h)) {
|
|
476
|
+
const reduction = reducePair(i, j, pairSugar);
|
|
477
|
+
const h = reduction.polynomial;
|
|
478
|
+
if (h.isZero()) {
|
|
543
479
|
stats.pairsReducedToZero++;
|
|
544
|
-
|
|
480
|
+
markZeroPair(i, j);
|
|
481
|
+
continue;
|
|
545
482
|
}
|
|
546
|
-
|
|
547
|
-
return [
|
|
483
|
+
if (h.isConstant() && h.constantTerm() !== 0n) {
|
|
484
|
+
return [SparsePolynomial_1.SparsePolynomial.constant(variableCount, 1n)];
|
|
548
485
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// Sugar of the new element: max of the pair sugar and the actual degree
|
|
556
|
-
const hDeg = sugarDegree(h, order, nVars);
|
|
557
|
-
sugarArr.push(Math.max(pairSugar, hDeg));
|
|
558
|
-
stats.basisAppends++;
|
|
559
|
-
if (maxBasisSize !== undefined && G.length > maxBasisSize) {
|
|
560
|
-
throw new GroebnerBudgetExceeded(`Groebner budget exceeded: basis size > ${maxBasisSize}`, stats);
|
|
561
|
-
}
|
|
562
|
-
for (let t = 0; t < k; t++) {
|
|
563
|
-
const cp = makePair(t, k);
|
|
564
|
-
if (cp) {
|
|
565
|
-
insertPairSorted(pairQueue, cp);
|
|
566
|
-
}
|
|
567
|
-
}
|
|
486
|
+
const k = appendBasis(h);
|
|
487
|
+
const hTotalDegree = h.totalDegree() ?? 0n;
|
|
488
|
+
const reducedSugar = reduction.sugar ?? pairSugar;
|
|
489
|
+
sugarArr.push(reducedSugar > hTotalDegree ? reducedSugar : hTotalDegree);
|
|
490
|
+
for (let t = 0; t < k; t++) {
|
|
491
|
+
insertPairSorted(pairQueue, makePair(t, k));
|
|
568
492
|
}
|
|
569
493
|
}
|
|
570
494
|
}
|
|
571
495
|
else {
|
|
572
|
-
// FIFO strategy
|
|
496
|
+
// FIFO strategy.
|
|
573
497
|
const pairs = [];
|
|
574
|
-
for (let i = 0; i <
|
|
575
|
-
for (let j = i + 1; j <
|
|
498
|
+
for (let i = 0; i < basis.length; i++) {
|
|
499
|
+
for (let j = i + 1; j < basis.length; j++) {
|
|
576
500
|
pairs.push([i, j]);
|
|
577
501
|
}
|
|
578
502
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
pairs
|
|
503
|
+
let pairIndex = 0;
|
|
504
|
+
while (pairIndex < pairs.length) {
|
|
505
|
+
const [i, j] = pairs[pairIndex++];
|
|
582
506
|
stats.pairsPopped++;
|
|
583
507
|
if (maxPairsPopped !== undefined && stats.pairsPopped > maxPairsPopped) {
|
|
584
|
-
throw new GroebnerBudgetExceeded(
|
|
508
|
+
throw new GroebnerBudgetExceeded((0, errors_1.message)('groebnerPairsBudgetExceeded', { max: String(maxPairsPopped) }), stats);
|
|
585
509
|
}
|
|
586
|
-
const lmi =
|
|
587
|
-
const lmj =
|
|
588
|
-
if (
|
|
510
|
+
const lmi = headReducers[i].lt.exp;
|
|
511
|
+
const lmj = headReducers[j].lt.exp;
|
|
512
|
+
if (areCoprimeLM(lmi, lmj)) {
|
|
589
513
|
stats.pairsSkippedProduct++;
|
|
590
|
-
|
|
514
|
+
markZeroPair(i, j);
|
|
591
515
|
continue;
|
|
592
516
|
}
|
|
593
517
|
if (chainCriterionApplies(i, j)) {
|
|
594
518
|
stats.pairsSkippedChain++;
|
|
595
|
-
|
|
519
|
+
markZeroPair(i, j);
|
|
596
520
|
continue;
|
|
597
521
|
}
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
const h1 = normalFormFractionFreeAllTerms(h0, G, order, nVars);
|
|
601
|
-
const h = primitiveNormalizeOrder(h1, order, nVars);
|
|
602
|
-
if (polyIsZero(h)) {
|
|
522
|
+
const h = reducePair(i, j).polynomial;
|
|
523
|
+
if (h.isZero()) {
|
|
603
524
|
stats.pairsReducedToZero++;
|
|
604
|
-
|
|
525
|
+
markZeroPair(i, j);
|
|
526
|
+
continue;
|
|
605
527
|
}
|
|
606
|
-
|
|
607
|
-
return [
|
|
528
|
+
if (h.isConstant() && h.constantTerm() !== 0n) {
|
|
529
|
+
return [SparsePolynomial_1.SparsePolynomial.constant(variableCount, 1n)];
|
|
608
530
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
// Cache the LM of the new element
|
|
613
|
-
const ltH = ltOrNull(h, order, nVars);
|
|
614
|
-
lmCache.push(ltH ? ltH.exp : null);
|
|
615
|
-
stats.basisAppends++;
|
|
616
|
-
if (maxBasisSize !== undefined && G.length > maxBasisSize) {
|
|
617
|
-
throw new GroebnerBudgetExceeded(`Groebner budget exceeded: basis size > ${maxBasisSize}`, stats);
|
|
618
|
-
}
|
|
619
|
-
for (let t = 0; t < k; t++) {
|
|
620
|
-
pairs.push([t, k]);
|
|
621
|
-
}
|
|
531
|
+
const k = appendBasis(h);
|
|
532
|
+
for (let t = 0; t < k; t++) {
|
|
533
|
+
pairs.push([t, k]);
|
|
622
534
|
}
|
|
623
535
|
}
|
|
624
536
|
}
|
|
625
|
-
return reduced ? canonicalizeBasis(
|
|
537
|
+
return reduced ? canonicalizeBasis(basis, order) : basis;
|
|
538
|
+
}
|
|
539
|
+
function reduceSparseByBasis(f, basis, order) {
|
|
540
|
+
if (f.isZero() || basis.length === 0) {
|
|
541
|
+
return f;
|
|
542
|
+
}
|
|
543
|
+
const headReducers = reducerViews(basis, order, false);
|
|
544
|
+
const normalizedReducers = reducerViews(basis, order, true);
|
|
545
|
+
const headReduced = normalFormFractionFree(f, basis, order, headReducers);
|
|
546
|
+
return normalFormFractionFreeAllTerms(headReduced, basis, order, normalizedReducers);
|
|
626
547
|
}
|
|
627
548
|
// ============================================================================
|
|
628
549
|
// Public API: Groebner basis
|
|
629
550
|
// ============================================================================
|
|
630
551
|
/**
|
|
631
|
-
* Computes a Groebner basis for
|
|
552
|
+
* Computes a Groebner basis for exact sparse integer polynomials.
|
|
632
553
|
*
|
|
633
|
-
*
|
|
634
|
-
*
|
|
635
|
-
*
|
|
636
|
-
*
|
|
637
|
-
* when exact integer division permits it. The returned basis is sorted deterministically,
|
|
638
|
-
* and the routine works from normalized copies rather than editing the supplied generators.
|
|
554
|
+
* The input polynomials must belong to the same ring. With `reduced: true`, the
|
|
555
|
+
* basis is interreduced, coefficient content is removed, leading signs are normalized,
|
|
556
|
+
* and exact monic normalization is applied when possible. Results are sorted
|
|
557
|
+
* deterministically and the supplied polynomials are not modified.
|
|
639
558
|
*
|
|
640
|
-
* @param polys - Ideal generators
|
|
641
|
-
* @param vars - Variable names aligned with exponent indices.
|
|
559
|
+
* @param polys - Ideal generators in one sparse polynomial ring.
|
|
642
560
|
* @param order - Monomial order; defaults to `LEX`.
|
|
643
561
|
* @param reduced - Return the normalized/interreduced basis; defaults to `true`.
|
|
644
|
-
* @returns New basis polynomials in deterministic presentation order.
|
|
562
|
+
* @returns New sparse basis polynomials in deterministic presentation order.
|
|
645
563
|
*/
|
|
646
|
-
function Groebner(polys,
|
|
564
|
+
function Groebner(polys, order = 'LEX', reduced = true) {
|
|
647
565
|
if (polys.length === 0) {
|
|
648
566
|
return [];
|
|
649
567
|
}
|
|
650
|
-
const
|
|
651
|
-
const
|
|
652
|
-
// Present basis deterministically in a SymPy-like order:
|
|
653
|
-
// 1) Prefer polynomials that involve earlier variables (presence vector, lex-desc)
|
|
654
|
-
// 2) Then by leading monomial under the chosen order (ascending)
|
|
655
|
-
// 3) Then by term count (ascending)
|
|
568
|
+
const basis = groebnerBasisSparse(polys, { order, reduced });
|
|
569
|
+
const variableCount = basis[0]?.variableCount ?? polys[0].variableCount;
|
|
656
570
|
const cache = new Map();
|
|
657
571
|
const getInfo = (p) => {
|
|
658
572
|
let info = cache.get(p);
|
|
659
573
|
if (!info) {
|
|
660
|
-
info = { pv: variablePresence(p
|
|
574
|
+
info = { pv: variablePresence(p), lt: ltOrNull(p, order) };
|
|
661
575
|
cache.set(p, info);
|
|
662
576
|
}
|
|
663
577
|
return info;
|
|
664
578
|
};
|
|
665
|
-
|
|
666
|
-
const
|
|
667
|
-
const
|
|
668
|
-
for (let i = 0; i <
|
|
669
|
-
const
|
|
670
|
-
const
|
|
671
|
-
if (
|
|
672
|
-
return
|
|
579
|
+
return [...basis].sort((p, q) => {
|
|
580
|
+
const pInfo = getInfo(p);
|
|
581
|
+
const qInfo = getInfo(q);
|
|
582
|
+
for (let i = 0; i < variableCount; i++) {
|
|
583
|
+
const pv = pInfo.pv[i] ? 1 : 0;
|
|
584
|
+
const qv = qInfo.pv[i] ? 1 : 0;
|
|
585
|
+
if (pv !== qv) {
|
|
586
|
+
return pv > qv ? -1 : 1;
|
|
673
587
|
}
|
|
674
588
|
}
|
|
675
|
-
if (
|
|
676
|
-
const
|
|
677
|
-
if (
|
|
678
|
-
return
|
|
589
|
+
if (pInfo.lt && qInfo.lt) {
|
|
590
|
+
const comparison = SparsePolynomial_1.SparsePolynomial.compareMonomials(pInfo.lt.exp, qInfo.lt.exp, sparseOrder(order));
|
|
591
|
+
if (comparison !== 0) {
|
|
592
|
+
return comparison;
|
|
679
593
|
}
|
|
680
594
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if (tp !== tq) {
|
|
684
|
-
return tp < tq ? -1 : 1;
|
|
685
|
-
}
|
|
686
|
-
return 0;
|
|
687
|
-
};
|
|
688
|
-
return [...G].sort(cmp);
|
|
595
|
+
return p.termCount - q.termCount;
|
|
596
|
+
});
|
|
689
597
|
}
|
|
690
598
|
/**
|
|
691
599
|
* Computes a Groebner basis with ordering, strategy, and budget control.
|
|
692
600
|
*
|
|
693
|
-
* @param polys - Exact integer-coefficient generators
|
|
601
|
+
* @param polys - Exact sparse integer-coefficient generators in one ring.
|
|
694
602
|
* @param opts - Ordering, normalization, pair-selection, and deterministic budget options.
|
|
695
603
|
* @returns New basis polynomials. Work statistics are exposed only when a budget is
|
|
696
604
|
* exceeded; successful calls return the basis itself.
|
|
@@ -698,7 +606,7 @@ function Groebner(polys, vars, order = 'LEX', reduced = true) {
|
|
|
698
606
|
* basis-size limit is exceeded.
|
|
699
607
|
*/
|
|
700
608
|
function groebnerBasisWithOptions(polys, opts) {
|
|
701
|
-
return
|
|
609
|
+
return groebnerBasisSparse(polys, opts);
|
|
702
610
|
}
|
|
703
611
|
// ============================================================================
|
|
704
612
|
// Elimination
|
|
@@ -718,7 +626,7 @@ function eliminate(polys, vars, keepVars, opts) {
|
|
|
718
626
|
if (polys.length === 0) {
|
|
719
627
|
return [];
|
|
720
628
|
}
|
|
721
|
-
// Determine which variable indices to eliminate
|
|
629
|
+
// Determine which variable indices to eliminate.
|
|
722
630
|
const keepSet = new Set(keepVars);
|
|
723
631
|
const eliminateIndices = new Set();
|
|
724
632
|
for (let i = 0; i < vars.length; i++) {
|
|
@@ -726,24 +634,24 @@ function eliminate(polys, vars, keepVars, opts) {
|
|
|
726
634
|
eliminateIndices.add(i);
|
|
727
635
|
}
|
|
728
636
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
const result = [];
|
|
734
|
-
for (const g of G) {
|
|
735
|
-
const pv = variablePresence(g, nVars);
|
|
736
|
-
let ok = true;
|
|
737
|
-
for (const ei of eliminateIndices) {
|
|
738
|
-
if (pv[ei]) {
|
|
739
|
-
ok = false;
|
|
740
|
-
break;
|
|
637
|
+
for (const polynomial of polys) {
|
|
638
|
+
for (const variableIndex of polynomial.variables()) {
|
|
639
|
+
if (variableIndex >= vars.length) {
|
|
640
|
+
throw new RangeError((0, errors_1.message)('multidegreeMismatch'));
|
|
741
641
|
}
|
|
742
642
|
}
|
|
743
|
-
if (ok) {
|
|
744
|
-
result.push(g);
|
|
745
|
-
}
|
|
746
643
|
}
|
|
644
|
+
// Compute a LEX basis with variables to eliminate first in the supplied order.
|
|
645
|
+
const basis = groebnerBasisSparse(polys, { ...opts, order: 'LEX', reduced: true });
|
|
646
|
+
const result = basis.filter(poly => {
|
|
647
|
+
const presence = variablePresence(poly);
|
|
648
|
+
for (const variableIndex of eliminateIndices) {
|
|
649
|
+
if (presence[variableIndex]) {
|
|
650
|
+
return false;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return true;
|
|
654
|
+
});
|
|
747
655
|
return result;
|
|
748
656
|
}
|
|
749
657
|
// ============================================================================
|
|
@@ -760,22 +668,21 @@ function eliminate(polys, vars, keepVars, opts) {
|
|
|
760
668
|
* @throws {@link GroebnerBudgetExceeded} Thrown when basis computation exceeds a budget.
|
|
761
669
|
*/
|
|
762
670
|
function idealMembership(f, polys, order = 'LEX', opts) {
|
|
763
|
-
if (
|
|
671
|
+
if (f.isZero()) {
|
|
764
672
|
return true;
|
|
765
673
|
}
|
|
766
674
|
if (polys.length === 0) {
|
|
767
675
|
return false;
|
|
768
676
|
}
|
|
769
|
-
const
|
|
770
|
-
return
|
|
677
|
+
const basis = groebnerBasisSparse(polys, { ...opts, order, reduced: true });
|
|
678
|
+
return reduceSparseByBasis(f, basis, order).isZero();
|
|
771
679
|
}
|
|
772
680
|
/**
|
|
773
681
|
* Reduces a polynomial by a basis or arbitrary reducer set using fraction-free arithmetic.
|
|
774
682
|
*
|
|
775
683
|
* @remarks
|
|
776
684
|
* This does not compute or verify a Groebner basis. The result is a canonical ideal
|
|
777
|
-
* normal form only when `basis` is already a basis for the selected order.
|
|
778
|
-
* works from normalized polynomial copies, leaving the supplied objects alone.
|
|
685
|
+
* normal form only when `basis` is already a basis for the selected order.
|
|
779
686
|
*
|
|
780
687
|
* @param f - Polynomial to reduce.
|
|
781
688
|
* @param basis - Reducers, normally a Groebner basis.
|
|
@@ -783,13 +690,7 @@ function idealMembership(f, polys, order = 'LEX', opts) {
|
|
|
783
690
|
* @returns The primitive-normalized remainder.
|
|
784
691
|
*/
|
|
785
692
|
function reduceByBasis(f, basis, order = 'LEX') {
|
|
786
|
-
|
|
787
|
-
return (0, poly_1.mvNormalize)(f);
|
|
788
|
-
}
|
|
789
|
-
const nVars = numVarsFromPolys(f, ...basis);
|
|
790
|
-
const h0 = normalFormFractionFree(f, basis, order, nVars);
|
|
791
|
-
const h1 = normalFormFractionFreeAllTerms(h0, basis, order, nVars);
|
|
792
|
-
return primitiveNormalizeOrder(h1, order, nVars);
|
|
693
|
+
return f.isZero() || basis.length === 0 ? f : reduceSparseByBasis(f, basis, order);
|
|
793
694
|
}
|
|
794
695
|
/**
|
|
795
696
|
* Solves a zero-dimensional polynomial system for rational coordinates.
|
|
@@ -812,17 +713,23 @@ function solve(polys, vars, opts) {
|
|
|
812
713
|
if (polys.length === 0 || vars.length === 0) {
|
|
813
714
|
return [new Map()];
|
|
814
715
|
}
|
|
815
|
-
const
|
|
716
|
+
for (const polynomial of polys) {
|
|
717
|
+
for (const variableIndex of polynomial.variables()) {
|
|
718
|
+
if (variableIndex >= vars.length) {
|
|
719
|
+
throw new RangeError((0, errors_1.message)('multidegreeMismatch'));
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const basis = groebnerBasisSparse(polys, { ...opts, order: 'LEX', reduced: true });
|
|
816
724
|
// Trivial: if the basis is {1}, the system is inconsistent.
|
|
817
|
-
if (
|
|
725
|
+
if (basis.length === 1 && basis[0].isConstant() && basis[0].constantTerm() !== 0n) {
|
|
818
726
|
return [];
|
|
819
727
|
}
|
|
820
|
-
if (
|
|
728
|
+
if (basis.length === 0) {
|
|
821
729
|
// Ideal is {0}, meaning every point is a solution — infinite solutions.
|
|
822
730
|
return [];
|
|
823
731
|
}
|
|
824
|
-
|
|
825
|
-
return backSubstitute(G, vars, nVars);
|
|
732
|
+
return backSubstitute(basis, vars);
|
|
826
733
|
}
|
|
827
734
|
/**
|
|
828
735
|
* Triangular back-substitution on a LEX Gröbner basis.
|
|
@@ -834,46 +741,40 @@ function solve(polys, vars, opts) {
|
|
|
834
741
|
* so when we extend with a new variable binding we only need to substitute that
|
|
835
742
|
* one new value — not redo all prior substitutions.
|
|
836
743
|
*/
|
|
837
|
-
function backSubstitute(
|
|
838
|
-
// Start with a single empty partial assignment, basis = original
|
|
744
|
+
function backSubstitute(basis, vars) {
|
|
839
745
|
let partials = [
|
|
840
746
|
{
|
|
841
747
|
assignment: new Map(),
|
|
842
|
-
basis:
|
|
748
|
+
basis: [...basis],
|
|
843
749
|
},
|
|
844
750
|
];
|
|
845
|
-
|
|
846
|
-
for (let v = nVars - 1; v >= 0; v--) {
|
|
751
|
+
for (let variableIndex = vars.length - 1; variableIndex >= 0; variableIndex--) {
|
|
847
752
|
const nextPartials = [];
|
|
848
|
-
for (const { assignment, basis } of partials) {
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
if (polyIsZero(p)) {
|
|
753
|
+
for (const { assignment, basis: partialBasis } of partials) {
|
|
754
|
+
const univariates = partialBasis.filter(poly => {
|
|
755
|
+
if (poly.isZero()) {
|
|
852
756
|
return false;
|
|
853
757
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
if (e > 0 && i !== v) {
|
|
858
|
-
return false;
|
|
859
|
-
}
|
|
860
|
-
}
|
|
758
|
+
const variables = poly.variables();
|
|
759
|
+
if (variables.some(index => index !== variableIndex)) {
|
|
760
|
+
return false;
|
|
861
761
|
}
|
|
862
|
-
|
|
762
|
+
const degree = poly.degree(variableIndex);
|
|
763
|
+
return degree !== null && degree > 0n;
|
|
863
764
|
});
|
|
864
765
|
if (univariates.length === 0) {
|
|
865
766
|
// No constraint on this variable — push forward as-is.
|
|
866
|
-
nextPartials.push({
|
|
767
|
+
nextPartials.push({
|
|
768
|
+
assignment: new Map(assignment),
|
|
769
|
+
basis: partialBasis,
|
|
770
|
+
});
|
|
867
771
|
continue;
|
|
868
772
|
}
|
|
869
|
-
|
|
870
|
-
const poly = univariates[0];
|
|
871
|
-
const roots = rationalRootsUnivariate(poly, v);
|
|
872
|
-
// Filter roots by consistency with other univariates
|
|
773
|
+
const roots = rationalRootsUnivariate(univariates[0], variableIndex);
|
|
873
774
|
for (const root of roots) {
|
|
874
775
|
let consistent = true;
|
|
875
|
-
for (let
|
|
876
|
-
if (!evaluatesToZero(univariates[
|
|
776
|
+
for (let polynomialIndex = 1; polynomialIndex < univariates.length; polynomialIndex++) {
|
|
777
|
+
if (!evaluatesToZero(univariates[polynomialIndex], variableIndex, root)) {
|
|
877
778
|
consistent = false;
|
|
878
779
|
break;
|
|
879
780
|
}
|
|
@@ -881,171 +782,129 @@ function backSubstitute(G, vars, nVars) {
|
|
|
881
782
|
if (!consistent) {
|
|
882
783
|
continue;
|
|
883
784
|
}
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
return p;
|
|
889
|
-
}
|
|
890
|
-
let q = p;
|
|
891
|
-
if (root.d !== 1n) {
|
|
892
|
-
q = scaleByDenomPow(q, v, root.d);
|
|
785
|
+
const nextBasis = partialBasis.map(poly => {
|
|
786
|
+
const degree = poly.degree(variableIndex);
|
|
787
|
+
if (degree === null || degree === 0n) {
|
|
788
|
+
return poly;
|
|
893
789
|
}
|
|
894
|
-
|
|
790
|
+
const scaled = root.d === 1n ? poly : scaleByDenomPow(poly, variableIndex, root.d);
|
|
791
|
+
return scaled.evaluateVariable(variableIndex, root.n);
|
|
895
792
|
});
|
|
896
|
-
const
|
|
897
|
-
|
|
898
|
-
nextPartials.push({ assignment:
|
|
793
|
+
const extended = new Map(assignment);
|
|
794
|
+
extended.set(variableIndex, root);
|
|
795
|
+
nextPartials.push({ assignment: extended, basis: nextBasis });
|
|
899
796
|
}
|
|
900
797
|
}
|
|
901
798
|
partials = nextPartials;
|
|
902
799
|
}
|
|
903
|
-
// Convert variable indices back to names
|
|
904
800
|
return partials.map(({ assignment }) => {
|
|
905
801
|
const named = new Map();
|
|
906
|
-
for (const [
|
|
907
|
-
if (
|
|
908
|
-
named.set(vars[
|
|
802
|
+
for (const [variableIndex, value] of assignment) {
|
|
803
|
+
if (variableIndex < vars.length) {
|
|
804
|
+
named.set(vars[variableIndex], value);
|
|
909
805
|
}
|
|
910
806
|
}
|
|
911
807
|
return named;
|
|
912
808
|
});
|
|
913
809
|
}
|
|
914
810
|
/**
|
|
915
|
-
*
|
|
916
|
-
*
|
|
917
|
-
*
|
|
811
|
+
* Scales a polynomial before integer substitution of a rational value n/d.
|
|
812
|
+
*
|
|
813
|
+
* For each term containing x_v^k, the coefficient is multiplied by
|
|
814
|
+
* d^(maxDegree-k), so evaluating x_v at n gives p(n/d) * d^maxDegree.
|
|
918
815
|
*/
|
|
919
|
-
function scaleByDenomPow(
|
|
920
|
-
const
|
|
921
|
-
if (
|
|
922
|
-
return
|
|
923
|
-
}
|
|
924
|
-
// Precompute powers of d up to maxDeg
|
|
925
|
-
const dPowers = new Array(maxDeg + 1);
|
|
926
|
-
dPowers[0] = 1n;
|
|
927
|
-
for (let i = 1; i <= maxDeg; i++) {
|
|
928
|
-
dPowers[i] = dPowers[i - 1] * d;
|
|
816
|
+
function scaleByDenomPow(poly, variableIndex, denominator) {
|
|
817
|
+
const maxDegree = poly.degree(variableIndex);
|
|
818
|
+
if (maxDegree === null || maxDegree === 0n || denominator === 1n) {
|
|
819
|
+
return poly;
|
|
929
820
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
if (newCoeff !== 0n) {
|
|
936
|
-
out.terms.set(key, newCoeff);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
out.trim();
|
|
940
|
-
return out;
|
|
821
|
+
return new SparsePolynomial_1.SparsePolynomial(poly.variableCount, poly.terms().map(term => ({
|
|
822
|
+
coefficient: term.coefficient *
|
|
823
|
+
denominator ** (maxDegree - term.exponents[variableIndex]),
|
|
824
|
+
exponents: term.exponents,
|
|
825
|
+
})));
|
|
941
826
|
}
|
|
942
827
|
/**
|
|
943
|
-
*
|
|
944
|
-
*
|
|
828
|
+
* Finds all rational roots p/q of a univariate polynomial.
|
|
829
|
+
*
|
|
830
|
+
* Uses the Rational Root Theorem: p divides the constant term and q divides the
|
|
831
|
+
* leading coefficient. Polynomial degree and term exponents remain bigint values.
|
|
945
832
|
*/
|
|
946
|
-
function rationalRootsUnivariate(poly,
|
|
947
|
-
const
|
|
948
|
-
if (
|
|
949
|
-
return [];
|
|
950
|
-
}
|
|
951
|
-
// Extract coefficients as bigint array (index = power of varIndex)
|
|
952
|
-
const coeffs = new Array(deg + 1).fill(0n);
|
|
953
|
-
for (const [key, coeff] of poly.terms) {
|
|
954
|
-
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
955
|
-
const d = exp.get(varIndex) ?? 0;
|
|
956
|
-
if (d <= deg) {
|
|
957
|
-
coeffs[d] += coeff;
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
// Trim leading zeros
|
|
961
|
-
let actualDeg = deg;
|
|
962
|
-
while (actualDeg > 0 && coeffs[actualDeg] === 0n) {
|
|
963
|
-
actualDeg--;
|
|
964
|
-
}
|
|
965
|
-
if (actualDeg === 0) {
|
|
833
|
+
function rationalRootsUnivariate(poly, variableIndex) {
|
|
834
|
+
const degree = poly.degree(variableIndex);
|
|
835
|
+
if (degree === null || degree === 0n) {
|
|
966
836
|
return [];
|
|
967
837
|
}
|
|
968
|
-
const
|
|
969
|
-
const
|
|
970
|
-
if (
|
|
971
|
-
// x = 0 is a root; factor it out and recurse
|
|
838
|
+
const leadingCoefficient = poly.coefficientIn(variableIndex, degree).constantTerm();
|
|
839
|
+
const constantTerm = poly.constantTerm();
|
|
840
|
+
if (constantTerm === 0n) {
|
|
972
841
|
const roots = [{ n: 0n, d: 1n }];
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const exp = new Map();
|
|
978
|
-
if (i > 1) {
|
|
979
|
-
exp.set(varIndex, i - 1);
|
|
980
|
-
}
|
|
981
|
-
shiftedPoly.terms.set((0, MultiPoly_1.expToKey)(exp), coeffs[i]);
|
|
842
|
+
let commonPower = degree;
|
|
843
|
+
for (const term of poly.terms()) {
|
|
844
|
+
if (term.exponents[variableIndex] < commonPower) {
|
|
845
|
+
commonPower = term.exponents[variableIndex];
|
|
982
846
|
}
|
|
983
847
|
}
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
848
|
+
const shifted = new SparsePolynomial_1.SparsePolynomial(poly.variableCount, poly.terms().map(term => {
|
|
849
|
+
const exponents = [...term.exponents];
|
|
850
|
+
exponents[variableIndex] -= commonPower;
|
|
851
|
+
return { coefficient: term.coefficient, exponents };
|
|
852
|
+
}));
|
|
853
|
+
for (const root of rationalRootsUnivariate(shifted, variableIndex)) {
|
|
854
|
+
if (root.n !== 0n) {
|
|
855
|
+
roots.push(root);
|
|
988
856
|
}
|
|
989
857
|
}
|
|
990
858
|
return roots;
|
|
991
859
|
}
|
|
992
|
-
|
|
993
|
-
const
|
|
994
|
-
const qDivisors = positiveDivisors((0, bigint_1.abs)(lc));
|
|
860
|
+
const numeratorDivisors = positiveDivisors((0, bigint_1.abs)(constantTerm));
|
|
861
|
+
const denominatorDivisors = positiveDivisors((0, bigint_1.abs)(leadingCoefficient));
|
|
995
862
|
const roots = [];
|
|
996
863
|
const seen = new Set();
|
|
997
|
-
for (const
|
|
998
|
-
for (const
|
|
999
|
-
// Try ±p/q
|
|
864
|
+
for (const numeratorFactor of numeratorDivisors) {
|
|
865
|
+
for (const denominatorFactor of denominatorDivisors) {
|
|
1000
866
|
for (const sign of [1n, -1n]) {
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
const
|
|
1004
|
-
const
|
|
1005
|
-
const
|
|
1006
|
-
const key = `${rn}/${rd}`;
|
|
867
|
+
const numerator = sign * numeratorFactor;
|
|
868
|
+
const divisor = (0, bigint_1.GCD)((0, bigint_1.abs)(numerator), denominatorFactor);
|
|
869
|
+
const reducedNumerator = numerator / divisor;
|
|
870
|
+
const reducedDenominator = denominatorFactor / divisor;
|
|
871
|
+
const key = `${reducedNumerator}/${reducedDenominator}`;
|
|
1007
872
|
if (seen.has(key)) {
|
|
1008
873
|
continue;
|
|
1009
874
|
}
|
|
1010
875
|
seen.add(key);
|
|
1011
|
-
if (
|
|
1012
|
-
roots.push({ n:
|
|
876
|
+
if (evaluatesAtRationalToZero(poly, variableIndex, degree, reducedNumerator, reducedDenominator)) {
|
|
877
|
+
roots.push({ n: reducedNumerator, d: reducedDenominator });
|
|
1013
878
|
}
|
|
1014
879
|
}
|
|
1015
880
|
}
|
|
1016
881
|
}
|
|
1017
882
|
return roots;
|
|
1018
883
|
}
|
|
1019
|
-
/**
|
|
1020
|
-
|
|
884
|
+
/**
|
|
885
|
+
* Evaluates a univariate polynomial at n/d after clearing d^degree.
|
|
886
|
+
*/
|
|
887
|
+
function evaluatesAtRationalToZero(poly, variableIndex, degree, numerator, denominator) {
|
|
1021
888
|
let result = 0n;
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
for (let i = 0; i <= deg; i++) {
|
|
1029
|
-
result += coeffs[i] * nPow * dPow;
|
|
1030
|
-
nPow *= n;
|
|
1031
|
-
if (i < deg && d !== 0n) {
|
|
1032
|
-
dPow /= d;
|
|
1033
|
-
}
|
|
889
|
+
for (const term of poly.terms()) {
|
|
890
|
+
const exponent = term.exponents[variableIndex];
|
|
891
|
+
result +=
|
|
892
|
+
term.coefficient *
|
|
893
|
+
numerator ** exponent *
|
|
894
|
+
denominator ** (degree - exponent);
|
|
1034
895
|
}
|
|
1035
896
|
return result === 0n;
|
|
1036
897
|
}
|
|
1037
|
-
/**
|
|
1038
|
-
function evaluatesToZero(poly,
|
|
1039
|
-
const
|
|
1040
|
-
if (
|
|
1041
|
-
return
|
|
898
|
+
/** Checks whether substituting one rational value makes a univariate polynomial zero. */
|
|
899
|
+
function evaluatesToZero(poly, variableIndex, value) {
|
|
900
|
+
const degree = poly.degree(variableIndex);
|
|
901
|
+
if (degree === null) {
|
|
902
|
+
return true;
|
|
1042
903
|
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
p = scaleByDenomPow(p, varIndex, val.d);
|
|
904
|
+
if (degree === 0n) {
|
|
905
|
+
return poly.constantTerm() === 0n;
|
|
1046
906
|
}
|
|
1047
|
-
|
|
1048
|
-
return polyIsZero((0, poly_1.mvNormalize)(result));
|
|
907
|
+
return evaluatesAtRationalToZero(poly, variableIndex, degree, value.n, value.d);
|
|
1049
908
|
}
|
|
1050
909
|
/** Positive divisors of a positive bigint. */
|
|
1051
910
|
function positiveDivisors(n) {
|
|
@@ -1055,112 +914,16 @@ function positiveDivisors(n) {
|
|
|
1055
914
|
if (n < 0n) {
|
|
1056
915
|
n = -n;
|
|
1057
916
|
}
|
|
1058
|
-
const
|
|
1059
|
-
let
|
|
1060
|
-
while (
|
|
1061
|
-
if (n %
|
|
1062
|
-
|
|
1063
|
-
if (
|
|
1064
|
-
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
i++;
|
|
1068
|
-
}
|
|
1069
|
-
return divs;
|
|
1070
|
-
}
|
|
1071
|
-
// ============================================================================
|
|
1072
|
-
// Monomial order comparisons and utilities
|
|
1073
|
-
// ============================================================================
|
|
1074
|
-
/**
|
|
1075
|
-
* Leading term under a specified monomial order using dense exponent vectors.
|
|
1076
|
-
* Returns null for the zero polynomial.
|
|
1077
|
-
*/
|
|
1078
|
-
function mvLeadTermDense(p, order, nVars) {
|
|
1079
|
-
if (p.terms.size === 0) {
|
|
1080
|
-
return null;
|
|
1081
|
-
}
|
|
1082
|
-
let bestExp = null;
|
|
1083
|
-
let bestCoeff = 0n;
|
|
1084
|
-
for (const [key, coeff] of p.terms.entries()) {
|
|
1085
|
-
if (coeff === 0n) {
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
const expMap = (0, MultiPoly_1.keyToExp)(key);
|
|
1089
|
-
const dense = new Array(nVars).fill(0);
|
|
1090
|
-
for (const [i, e] of expMap.entries()) {
|
|
1091
|
-
if (i >= 0 && i < nVars) {
|
|
1092
|
-
dense[i] = e;
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
if (!bestExp || compareMonomialDense(dense, bestExp, order) > 0) {
|
|
1096
|
-
bestExp = dense;
|
|
1097
|
-
bestCoeff = coeff;
|
|
1098
|
-
}
|
|
1099
|
-
}
|
|
1100
|
-
return bestExp ? { exp: bestExp, coeff: bestCoeff } : null;
|
|
1101
|
-
}
|
|
1102
|
-
/** Componentwise lcm (max). */
|
|
1103
|
-
function expLcmDense(a, b) {
|
|
1104
|
-
const n = Math.max(a.length, b.length);
|
|
1105
|
-
const out = new Array(n).fill(0);
|
|
1106
|
-
for (let i = 0; i < n; i++) {
|
|
1107
|
-
out[i] = Math.max(a[i] ?? 0, b[i] ?? 0);
|
|
1108
|
-
}
|
|
1109
|
-
return out;
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Compare dense exponent vectors under a named monomial order.
|
|
1113
|
-
*
|
|
1114
|
-
* Returns:
|
|
1115
|
-
* - 1 if a > b
|
|
1116
|
-
* - -1 if a < b
|
|
1117
|
-
* - 0 if equal
|
|
1118
|
-
*
|
|
1119
|
-
* Convention: variable priority is index order: x0 > x1 > x2 > ...
|
|
1120
|
-
*/
|
|
1121
|
-
function compareMonomialDense(a, b, order) {
|
|
1122
|
-
const n = Math.max(a.length, b.length);
|
|
1123
|
-
if (order === 'LEX') {
|
|
1124
|
-
for (let i = 0; i < n; i++) {
|
|
1125
|
-
const ai = a[i] ?? 0;
|
|
1126
|
-
const bi = b[i] ?? 0;
|
|
1127
|
-
if (ai !== bi) {
|
|
1128
|
-
return ai > bi ? 1 : -1;
|
|
917
|
+
const divisors = [];
|
|
918
|
+
let candidate = 1n;
|
|
919
|
+
while (candidate * candidate <= n) {
|
|
920
|
+
if (n % candidate === 0n) {
|
|
921
|
+
divisors.push(candidate);
|
|
922
|
+
if (candidate !== n / candidate) {
|
|
923
|
+
divisors.push(n / candidate);
|
|
1129
924
|
}
|
|
1130
925
|
}
|
|
1131
|
-
|
|
1132
|
-
}
|
|
1133
|
-
const da = monomialTotalDegreeDense(a);
|
|
1134
|
-
const db = monomialTotalDegreeDense(b);
|
|
1135
|
-
if (da !== db) {
|
|
1136
|
-
return da > db ? 1 : -1;
|
|
1137
|
-
}
|
|
1138
|
-
if (order === 'GRLEX') {
|
|
1139
|
-
for (let i = 0; i < n; i++) {
|
|
1140
|
-
const ai = a[i] ?? 0;
|
|
1141
|
-
const bi = b[i] ?? 0;
|
|
1142
|
-
if (ai !== bi) {
|
|
1143
|
-
return ai > bi ? 1 : -1;
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
return 0;
|
|
1147
|
-
}
|
|
1148
|
-
// GREVLEX: same total degree; compare from last variable down.
|
|
1149
|
-
// At the last index where they differ, the monomial with the *smaller*
|
|
1150
|
-
// exponent is considered larger.
|
|
1151
|
-
for (let i = n - 1; i >= 0; i--) {
|
|
1152
|
-
const ai = a[i] ?? 0;
|
|
1153
|
-
const bi = b[i] ?? 0;
|
|
1154
|
-
if (ai !== bi) {
|
|
1155
|
-
return ai < bi ? 1 : -1;
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
return 0;
|
|
1159
|
-
}
|
|
1160
|
-
function monomialTotalDegreeDense(m) {
|
|
1161
|
-
let s = 0;
|
|
1162
|
-
for (let i = 0; i < m.length; i++) {
|
|
1163
|
-
s += m[i] ?? 0;
|
|
926
|
+
candidate++;
|
|
1164
927
|
}
|
|
1165
|
-
return
|
|
928
|
+
return divisors;
|
|
1166
929
|
}
|