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.
Files changed (236) hide show
  1. package/BREAKING_CHANGES.md +244 -0
  2. package/README.md +168 -229
  3. package/dist/bundle.js +1 -1
  4. package/dist/bundle.js.LICENSE.txt +6 -6
  5. package/output/algebra/adapters.d.ts +3 -33
  6. package/output/algebra/adapters.js +7 -105
  7. package/output/algebra/algorithms/arith.js +10 -9
  8. package/output/algebra/algorithms/groebnerBase.d.ts +24 -20
  9. package/output/algebra/algorithms/groebnerBase.js +486 -723
  10. package/output/algebra/dispatch.d.ts +1 -0
  11. package/output/algebra/dispatch.js +25 -0
  12. package/output/algebra/factor/factor.d.ts +15 -0
  13. package/output/algebra/factor/factor.js +99 -12
  14. package/output/algebra/gcd/gcd.d.ts +8 -7
  15. package/output/algebra/gcd/gcd.js +32 -78
  16. package/output/algebra/groebner.d.ts +2 -2
  17. package/output/algebra/groebner.js +6 -12
  18. package/output/algebra/partfrac.d.ts +3 -3
  19. package/output/algebra/partfrac.js +28 -61
  20. package/output/algebra/polynomial/ModularSparsePolynomial.d.ts +131 -0
  21. package/output/algebra/polynomial/ModularSparsePolynomial.js +378 -0
  22. package/output/algebra/polynomial/ModularSparsePolynomialFactor.d.ts +40 -0
  23. package/output/algebra/polynomial/ModularSparsePolynomialFactor.js +661 -0
  24. package/output/algebra/polynomial/SparsePolynomialFactor.d.ts +64 -0
  25. package/output/algebra/polynomial/SparsePolynomialFactor.js +492 -0
  26. package/output/algebra/polynomial/SparsePolynomialGcd.d.ts +29 -0
  27. package/output/algebra/polynomial/SparsePolynomialGcd.js +259 -0
  28. package/output/algebra/polynomial/SparsePolynomialMultivariateFactor.d.ts +82 -0
  29. package/output/algebra/polynomial/SparsePolynomialMultivariateFactor.js +604 -0
  30. package/output/algebra/polynomial/modularGcd.d.ts +40 -0
  31. package/output/algebra/polynomial/modularGcd.js +365 -0
  32. package/output/algebra/polynomialize.js +4 -2
  33. package/output/algebra/simplify/funcsimp.js +1 -1
  34. package/output/algebra/simplify/ratsimp.js +1 -1
  35. package/output/algebra/simplify/simplify.js +11 -2
  36. package/output/algebra/utils.d.ts +1 -1
  37. package/output/algebra/utils.js +3 -3
  38. package/output/api/advanced.d.ts +2 -1
  39. package/output/api/advanced.js +3 -3
  40. package/output/api/algebra.d.ts +21 -4
  41. package/output/api/algebra.js +22 -3
  42. package/output/api/assumptions.d.ts +1 -1
  43. package/output/api/assumptions.js +2 -1
  44. package/output/api/calculus.d.ts +2 -1
  45. package/output/api/calculus.js +2 -1
  46. package/output/api/core.d.ts +28 -7
  47. package/output/api/core.js +28 -2
  48. package/output/api/languages/deu.d.ts +3 -0
  49. package/output/api/languages/deu.js +266 -0
  50. package/output/api/languages/fra.d.ts +3 -0
  51. package/output/api/languages/fra.js +266 -0
  52. package/output/api/languages/ita.d.ts +3 -0
  53. package/output/api/languages/ita.js +266 -0
  54. package/output/api/languages/nld.d.ts +3 -0
  55. package/output/api/languages/nld.js +266 -0
  56. package/output/api/languages/por.d.ts +3 -0
  57. package/output/api/languages/por.js +266 -0
  58. package/output/api/languages/spa.d.ts +3 -0
  59. package/output/api/languages/spa.js +266 -0
  60. package/output/api/parser.d.ts +10 -2
  61. package/output/api/parser.js +2 -0
  62. package/output/api/solve.d.ts +1 -1
  63. package/output/api/solve.js +2 -1
  64. package/output/api/structures.d.ts +1 -1
  65. package/output/api/structures.js +2 -1
  66. package/output/calculus/adapters.d.ts +8 -0
  67. package/output/calculus/adapters.js +41 -0
  68. package/output/calculus/derivative/diff.js +6 -3
  69. package/output/calculus/dispatch.d.ts +1 -0
  70. package/output/calculus/dispatch.js +25 -0
  71. package/output/calculus/integrate/integrate.js +4 -4
  72. package/output/calculus/integrate/integrationTable.js +16 -16
  73. package/output/calculus/laplace/ilaplace.js +1 -1
  74. package/output/calculus/laplace/ilaplaceTable.js +18 -17
  75. package/output/calculus/laplace/laplaceTable.js +3 -2
  76. package/output/calculus/limit/limit.js +1 -1
  77. package/output/core/classes/assumption/Assumption.js +14 -10
  78. package/output/core/classes/assumption/assume.d.ts +6 -0
  79. package/output/core/classes/assumption/assume.js +9 -0
  80. package/output/core/classes/assumption/dispatch.d.ts +1 -0
  81. package/output/core/classes/assumption/dispatch.js +11 -0
  82. package/output/core/classes/collection/Collection.js +11 -2
  83. package/output/core/classes/complex/Complex.d.ts +10 -0
  84. package/output/core/classes/complex/Complex.js +19 -1
  85. package/output/core/classes/decimalSet/DecimalSet.js +5 -4
  86. package/output/core/classes/dictionary/Dictionary.js +4 -1
  87. package/output/core/classes/expression/CoeffObject.d.ts +1 -1
  88. package/output/core/classes/expression/CoeffObject.js +1 -2
  89. package/output/core/classes/expression/Expression.d.ts +35 -26
  90. package/output/core/classes/expression/Expression.js +77 -50
  91. package/output/core/classes/expression/analysis.d.ts +1 -1
  92. package/output/core/classes/expression/analysis.js +7 -7
  93. package/output/core/classes/expression/format.d.ts +9 -0
  94. package/output/core/classes/expression/format.js +130 -7
  95. package/output/core/classes/expression/utils.d.ts +9 -0
  96. package/output/core/classes/expression/utils.js +52 -6
  97. package/output/core/classes/matrix/Matrix.d.ts +2 -3
  98. package/output/core/classes/matrix/Matrix.js +26 -23
  99. package/output/core/classes/matrix/dispatch.d.ts +1 -0
  100. package/output/core/classes/matrix/dispatch.js +19 -0
  101. package/output/core/classes/matrix/functions.d.ts +2 -0
  102. package/output/core/classes/matrix/functions.js +5 -0
  103. package/output/core/classes/parser/Parser.d.ts +31 -5
  104. package/output/core/classes/parser/Parser.js +357 -444
  105. package/output/core/classes/parser/constants.d.ts +2 -0
  106. package/output/core/classes/parser/constants.js +4 -2
  107. package/output/core/classes/parser/controlFlowSignals.d.ts +38 -0
  108. package/output/core/classes/parser/controlFlowSignals.js +60 -0
  109. package/output/core/classes/parser/operations/add.js +14 -16
  110. package/output/core/classes/parser/operations/compare.js +13 -0
  111. package/output/core/classes/parser/operations/functions.d.ts +3 -4
  112. package/output/core/classes/parser/operations/functions.js +48 -102
  113. package/output/core/classes/parser/operations/multiply.js +22 -15
  114. package/output/core/classes/parser/operations/power.js +11 -11
  115. package/output/core/classes/parser/operations/subtract.js +2 -1
  116. package/output/core/classes/parser/preprocess.d.ts +0 -7
  117. package/output/core/classes/parser/preprocess.js +33 -39
  118. package/output/core/classes/parser/scripting/controlFlow.d.ts +4 -102
  119. package/output/core/classes/parser/scripting/controlFlow.js +134 -186
  120. package/output/core/classes/parser/scripting/deferred.d.ts +4 -0
  121. package/output/core/classes/parser/scripting/deferred.js +65 -0
  122. package/output/core/classes/parser/scripting/dispatch.d.ts +1 -0
  123. package/output/core/classes/parser/scripting/dispatch.js +62 -0
  124. package/output/core/classes/parser/scripting/evaluate.d.ts +13 -0
  125. package/output/core/classes/parser/scripting/evaluate.js +17 -1
  126. package/output/core/classes/parser/scripting/scope.d.ts +1 -28
  127. package/output/core/classes/parser/scripting/scope.js +36 -40
  128. package/output/core/classes/parser/types.d.ts +5 -5
  129. package/output/core/classes/parser/wrappers/IndexedReference.d.ts +4 -3
  130. package/output/core/classes/parser/wrappers/IndexedReference.js +8 -0
  131. package/output/core/classes/parser/wrappers/KeyValuePair.d.ts +2 -2
  132. package/output/core/classes/polynomial/Polynomial.d.ts +5 -1
  133. package/output/core/classes/polynomial/Polynomial.js +53 -7
  134. package/output/core/classes/polynomial/SparsePolynomial.d.ts +221 -0
  135. package/output/core/classes/polynomial/SparsePolynomial.js +824 -0
  136. package/output/core/classes/polynomial/SparsePolynomialAdapter.d.ts +56 -0
  137. package/output/core/classes/polynomial/SparsePolynomialAdapter.js +241 -0
  138. package/output/core/classes/polynomial/Term.js +2 -2
  139. package/output/core/{adapters.d.ts → classes/polynomial/adapters.d.ts} +2 -2
  140. package/output/core/classes/polynomial/adapters.js +33 -0
  141. package/output/core/classes/polynomial/dispatch.d.ts +1 -0
  142. package/output/core/classes/polynomial/dispatch.js +14 -0
  143. package/output/core/classes/polynomial/functions.d.ts +8 -0
  144. package/output/core/classes/polynomial/functions.js +71 -8
  145. package/output/core/classes/polynomial/utils.js +2 -1
  146. package/output/core/classes/rational/Rational.d.ts +8 -11
  147. package/output/core/classes/rational/Rational.js +70 -20
  148. package/output/core/classes/seq/SEQ.js +4 -4
  149. package/output/core/classes/valuesSet/ValuesSet.d.ts +9 -1
  150. package/output/core/classes/valuesSet/ValuesSet.js +22 -4
  151. package/output/core/classes/vector/Vector.d.ts +2 -3
  152. package/output/core/classes/vector/Vector.js +11 -15
  153. package/output/core/classes/vector/dispatch.d.ts +1 -0
  154. package/output/core/classes/vector/dispatch.js +11 -0
  155. package/output/core/common/classes/MathematicalAggregate.js +11 -2
  156. package/output/core/common/classes/StructuredEntity.d.ts +0 -17
  157. package/output/core/common/classes/StructuredEntity.js +3 -64
  158. package/output/core/common/common.d.ts +0 -1
  159. package/output/core/common/common.js +29 -234
  160. package/output/core/converters/BaseConverter.js +3 -2
  161. package/output/core/converters/Converter.js +9 -5
  162. package/output/core/converters/Pattern.js +1 -1
  163. package/output/core/dispatch.d.ts +18 -0
  164. package/output/core/dispatch.js +3 -281
  165. package/output/core/errors.d.ts +259 -474
  166. package/output/core/errors.js +270 -542
  167. package/output/core/fullFunctions.d.ts +7 -0
  168. package/output/core/fullFunctions.js +31 -0
  169. package/output/core/functions/bigint/bigint.d.ts +13 -1
  170. package/output/core/functions/bigint/bigint.js +56 -4
  171. package/output/core/functions/bigint/primeFactor.d.ts +2 -0
  172. package/output/core/functions/bigint/primeFactor.js +13 -13
  173. package/output/core/functions/build/definitions.js +6 -0
  174. package/output/core/functions/complex.d.ts +2 -2
  175. package/output/core/functions/complex.dispatch.d.ts +1 -0
  176. package/output/core/functions/complex.dispatch.js +16 -0
  177. package/output/core/functions/complex.js +7 -7
  178. package/output/core/functions/decimal.js +2 -1
  179. package/output/core/functions/expand/expand.js +2 -2
  180. package/output/core/functions/numeric.d.ts +1 -1
  181. package/output/core/functions/string.js +2 -1
  182. package/output/core/functions/subst.js +21 -13
  183. package/output/core/parserFunctions.d.ts +7 -0
  184. package/output/core/parserFunctions.js +25 -0
  185. package/output/index.d.ts +50 -30
  186. package/output/index.js +147 -19
  187. package/output/math/defint/defint.d.ts +18 -0
  188. package/output/math/defint/defint.js +60 -0
  189. package/output/math/defint/defintDecimal.js +6 -5
  190. package/output/math/defint/defintNative.js +7 -18
  191. package/output/math/dispatch.d.ts +1 -0
  192. package/output/math/dispatch.js +101 -0
  193. package/output/math/geometry.d.ts +4 -0
  194. package/output/math/geometry.js +23 -0
  195. package/output/math/math.d.ts +59 -19
  196. package/output/math/math.js +244 -100
  197. package/output/math/trig.js +4 -4
  198. package/output/math/utils.d.ts +6 -0
  199. package/output/math/utils.js +39 -0
  200. package/output/solve/classes/PolynomialSolver.d.ts +33 -14
  201. package/output/solve/classes/PolynomialSolver.js +267 -67
  202. package/output/solve/classes/SolutionSet.d.ts +20 -1
  203. package/output/solve/classes/SolutionSet.js +82 -5
  204. package/output/solve/dispatch.d.ts +1 -0
  205. package/output/solve/dispatch.js +13 -0
  206. package/output/solve/linsolve.d.ts +2 -0
  207. package/output/solve/linsolve.js +13 -5
  208. package/output/solve/solve.d.ts +10 -0
  209. package/output/solve/solve.js +128 -20
  210. package/output/solve/solveSystem.d.ts +1 -1
  211. package/output/solve/solveSystem.js +8 -32
  212. package/output/solve/utils.d.ts +2 -1
  213. package/output/solve/utils.js +18 -22
  214. package/output/utils/array.d.ts +1 -1
  215. package/output/utils/debug.js +2 -1
  216. package/package.json +22 -38
  217. package/dist/parser.js +0 -2
  218. package/dist/parser.js.LICENSE.txt +0 -7
  219. package/docs-data/parser-functions.json +0 -1960
  220. package/output/algebra/algorithms/factor.d.ts +0 -3
  221. package/output/algebra/algorithms/factor.js +0 -5
  222. package/output/algebra/algorithms/factorMultivariate.d.ts +0 -23
  223. package/output/algebra/algorithms/factorMultivariate.js +0 -2393
  224. package/output/algebra/algorithms/factorUnivariate.d.ts +0 -72
  225. package/output/algebra/algorithms/factorUnivariate.js +0 -1072
  226. package/output/algebra/algorithms/gcd.d.ts +0 -22
  227. package/output/algebra/algorithms/gcd.js +0 -690
  228. package/output/algebra/algorithms/multiPoly/MultiPoly.d.ts +0 -137
  229. package/output/algebra/algorithms/multiPoly/MultiPoly.js +0 -346
  230. package/output/algebra/algorithms/poly.d.ts +0 -228
  231. package/output/algebra/algorithms/poly.js +0 -1299
  232. package/output/algebra/algorithms/rational.d.ts +0 -17
  233. package/output/algebra/algorithms/rational.js +0 -115
  234. package/output/core/adapters.js +0 -40
  235. package/output/core/classes/parser/operations/comma.d.ts +0 -7
  236. 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 ltOrNull(p, order, nVars) {
48
- return mvLeadTermDense(p, order, nVars);
49
- }
50
- function polyIsZero(p) {
51
- return p.terms.size === 0;
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 maxIndex + 1;
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
- for (const [k, c] of p.terms.entries()) {
70
- if (c !== 0n) {
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 (under `order`) is positive
102
- * - optionally make monic if exact
78
+ * - ensure leading coefficient under `order` is positive
79
+ * - make monic when exact integer division permits it
103
80
  */
104
- function primitiveNormalizeOrder(p0, order, nVars) {
105
- if (polyIsZero(p0)) {
106
- return MultiPoly_1.MultiPoly.zero();
107
- }
108
- let p = (0, poly_1.mvNormalize)(p0);
109
- const { primitive } = (0, poly_1.mvDivideByCoeffContent)(p);
110
- p = (0, poly_1.mvNormalize)(primitive);
111
- // Fix sign using the requested order
112
- const lt = ltOrNull(p, order, nVars);
113
- if (lt && lt.coeff < 0n) {
114
- p = (0, poly_1.negPoly)(p);
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 (ok) {
128
- p = (0, poly_1.mvNormalize)(divScalarExact(p, lc));
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(f0, g0, order, nVars) {
137
- const f = (0, poly_1.mvNormalize)(f0);
138
- const g = (0, poly_1.mvNormalize)(g0);
139
- const ltF = ltOrNull(f, order, nVars);
140
- const ltG = ltOrNull(g, order, nVars);
141
- if (!ltF || !ltG) {
142
- return MultiPoly_1.MultiPoly.zero();
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
- const l = expLcmDense(ltF.exp, ltG.exp);
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
- // Normal form (fraction-free)
156
- // ============================================================================
157
- /** Head reduction (fraction-free) using leading terms only. */
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, nVars);
169
+ const ltF = ltOrNull(f, order);
165
170
  if (!ltF) {
166
171
  break;
167
172
  }
168
173
  let reduced = false;
169
- for (const g0 of G) {
170
- const g = (0, poly_1.mvNormalize)(g0);
171
- if (polyIsZero(g)) {
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
- if (!(0, poly_1.monoDividesDense)(ltG.exp, ltF.exp)) {
179
- continue;
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
- const multExp = (0, poly_1.monoSubDense)(ltF.exp, ltG.exp);
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 ANY term (tail reduction),
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 LM divides t, we cancel t via:
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, G, order, nVars) {
223
- let f = primitiveNormalizeOrder((0, poly_1.mvNormalize)(f0), order, nVars);
224
- if (polyIsZero(f) || G.length === 0) {
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 changed = false;
247
- const fTerms = collectDenseTermsDesc(f, order, nVars);
248
- if (fTerms.length === 0) {
249
- break;
250
- }
251
- outer: for (const t of fTerms) {
252
- for (const { poly: g, lt: ltG } of normalizedG) {
253
- if (!(0, poly_1.monoDividesDense)(ltG.exp, t.exp)) {
254
- continue;
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
- const multExp = (0, poly_1.monoSubDense)(t.exp, ltG.exp);
257
- const left = (0, poly_1.scalePoly)(f, ltG.coeff);
258
- const right = (0, poly_1.mulPoly)(g, MultiPoly_1.MultiPoly.monomial(t.coeff, multExp));
259
- f = primitiveNormalizeOrder((0, poly_1.mvNormalize)((0, poly_1.subPoly)(left, right)), order, nVars);
260
- changed = true;
261
- break outer;
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 (!changed) {
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
- /** For each variable index 0..nVars-1, returns true if that variable appears in p. */
274
- function variablePresence(p, nVars) {
275
- const a = new Array(nVars).fill(false);
276
- for (const k of p.terms.keys()) {
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 a;
254
+ return presence;
285
255
  }
286
- function canonicalizeBasis(G0, order, nVars) {
256
+ function canonicalizeBasis(inputBasis, order) {
287
257
  const kept = [];
288
- const input = G0.filter(p => !polyIsZero(p)).map(p => (0, poly_1.mvNormalize)(p));
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 r0 = normalFormFractionFree(p, kept, order, nVars);
292
- const r1 = normalFormFractionFreeAllTerms(r0, kept, order, nVars);
293
- const rn = primitiveNormalizeOrder(r1, order, nVars);
294
- if (!polyIsZero(rn)) {
295
- kept.push(rn);
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 ri0 = normalFormFractionFree(kept[i], others, order, nVars);
303
- const ri1 = normalFormFractionFreeAllTerms(ri0, others, order, nVars);
304
- const rn = primitiveNormalizeOrder(ri1, order, nVars);
305
- if (!polyIsZero(rn)) {
306
- out.push(rn);
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) Dedupe by leading monomial.
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
- const key = polyKey(p);
343
- const prev = byPoly.get(key);
344
- if (!prev) {
345
- byPoly.set(key, p);
346
- continue;
347
- }
348
- byPoly.set(key, betterSameLM(prev, p));
349
- }
350
- const arr = Array.from(byPoly.values());
351
- arr.sort((p, q) => {
352
- const pp = variablePresence(p, nVars);
353
- const qp = variablePresence(q, nVars);
354
- for (let i = 0; i < nVars; i++) {
355
- const ap = pp[i] ? 1 : 0;
356
- const aq = qp[i] ? 1 : 0;
357
- if (ap !== aq) {
358
- return ap - aq;
359
- }
360
- }
361
- const ltp = ltOrNull(p, order, nVars);
362
- const ltq = ltOrNull(q, order, nVars);
363
- if (ltp && ltq) {
364
- const c = compareMonomialDense(ltp.exp, ltq.exp, order);
365
- if (c !== 0) {
366
- return c;
367
- }
368
- }
369
- return p.terms.size - q.terms.size;
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 arr;
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, ltF, ltG) {
395
- const lcm = expLcmDense(ltF, ltG);
396
- const lcmDeg = monomialTotalDegreeDense(lcm);
397
- const degF = monomialTotalDegreeDense(ltF);
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
- * Insert a pair into a sugar-sorted queue.
403
- * Primary sort: ascending sugar. Secondary: ascending lcmDeg.
404
- * Uses binary insertion to keep the queue sorted.
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 m = queue[mid];
412
- if (m.sugar < pair.sugar || (m.sugar === pair.sugar && m.lcmDeg < pair.lcmDeg)) {
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
- function groebnerBasis(polys, orderOrOpts = 'LEX') {
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 F = polys.filter(p => !polyIsZero(p)).map(p => (0, poly_1.mvNormalize)(p));
436
- if (F.length === 0) {
368
+ const basisInput = polys.filter(p => !p.isZero());
369
+ if (basisInput.length === 0) {
437
370
  return [];
438
371
  }
439
- const nVars = numVarsFromPolys(...F);
440
- const G = F.slice();
441
- // Sugar degrees tracked per basis element
442
- const sugarArr = G.map(p => sugarDegree(p, order, nVars));
443
- // Leading-monomial cache: index → dense exponent vector.
444
- // Basis elements are append-only and immutable once added, so cache entries never stale.
445
- const lmCache = G.map(p => {
446
- const lt = ltOrNull(p, order, nVars);
447
- return lt ? lt.exp : null;
448
- });
449
- // Buchberger criteria bookkeeping.
450
- const zeroPairs = new Set();
451
- const pairKey = (i, j) => (i < j ? `${i},${j}` : `${j},${i}`);
452
- const lmExpCached = (idx) => {
453
- if (idx < lmCache.length) {
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 < nVars; i++) {
463
- if (a[i] > 0 && b[i] > 0) {
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 = lmExpCached(i);
471
- const lj = lmExpCached(j);
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
- for (let k = 0; k < G.length; k++) {
477
- if (k === i || k === j) {
478
- continue;
479
- }
480
- const lk = lmExpCached(k);
481
- if (!lk) {
482
- continue;
483
- }
484
- if (!(0, poly_1.monoDividesDense)(lk, lcm)) {
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
- // Build initial pair queue
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 = lmExpCached(i);
498
- const lj = lmExpCached(j);
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], li, lj),
507
- lcmDeg: monomialTotalDegreeDense(lcm),
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 < G.length; i++) {
511
- for (let j = i + 1; j < G.length; j++) {
512
- const cp = makePair(i, j);
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[0];
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(`Groebner budget exceeded: pairsPopped > ${maxPairsPopped}`, stats);
462
+ throw new GroebnerBudgetExceeded((0, errors_1.message)('groebnerPairsBudgetExceeded', { max: String(maxPairsPopped) }), stats);
524
463
  }
525
- // Buchberger criteria
526
- const lmi = lmExpCached(i);
527
- const lmj = lmExpCached(j);
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
- zeroPairs.add(pairKey(i, j));
468
+ markZeroPair(i, j);
531
469
  continue;
532
470
  }
533
471
  if (chainCriterionApplies(i, j)) {
534
472
  stats.pairsSkippedChain++;
535
- zeroPairs.add(pairKey(i, j));
473
+ markZeroPair(i, j);
536
474
  continue;
537
475
  }
538
- const S = sPolynomialFF(G[i], G[j], order, nVars);
539
- const h0 = normalFormFractionFree(S, G, order, nVars);
540
- const h1 = normalFormFractionFreeAllTerms(h0, G, order, nVars);
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
- zeroPairs.add(pairKey(i, j));
480
+ markZeroPair(i, j);
481
+ continue;
545
482
  }
546
- else if (h.isConstant() && h.constantTerm() !== 0n) {
547
- return [MultiPoly_1.MultiPoly.constant(1n)];
483
+ if (h.isConstant() && h.constantTerm() !== 0n) {
484
+ return [SparsePolynomial_1.SparsePolynomial.constant(variableCount, 1n)];
548
485
  }
549
- else {
550
- const k = G.length;
551
- G.push(h);
552
- // Cache the LM of the new element
553
- const ltH = ltOrNull(h, order, nVars);
554
- lmCache.push(ltH ? ltH.exp : null);
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 (original behavior)
496
+ // FIFO strategy.
573
497
  const pairs = [];
574
- for (let i = 0; i < G.length; i++) {
575
- for (let j = i + 1; j < G.length; 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
- while (pairs.length > 0) {
580
- const [i, j] = pairs[0];
581
- pairs.shift();
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(`Groebner budget exceeded: pairsPopped > ${maxPairsPopped}`, stats);
508
+ throw new GroebnerBudgetExceeded((0, errors_1.message)('groebnerPairsBudgetExceeded', { max: String(maxPairsPopped) }), stats);
585
509
  }
586
- const lmi = lmExpCached(i);
587
- const lmj = lmExpCached(j);
588
- if (lmi && lmj && areCoprimeLM(lmi, lmj)) {
510
+ const lmi = headReducers[i].lt.exp;
511
+ const lmj = headReducers[j].lt.exp;
512
+ if (areCoprimeLM(lmi, lmj)) {
589
513
  stats.pairsSkippedProduct++;
590
- zeroPairs.add(pairKey(i, j));
514
+ markZeroPair(i, j);
591
515
  continue;
592
516
  }
593
517
  if (chainCriterionApplies(i, j)) {
594
518
  stats.pairsSkippedChain++;
595
- zeroPairs.add(pairKey(i, j));
519
+ markZeroPair(i, j);
596
520
  continue;
597
521
  }
598
- const S = sPolynomialFF(G[i], G[j], order, nVars);
599
- const h0 = normalFormFractionFree(S, G, order, nVars);
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
- zeroPairs.add(pairKey(i, j));
525
+ markZeroPair(i, j);
526
+ continue;
605
527
  }
606
- else if (h.isConstant() && h.constantTerm() !== 0n) {
607
- return [MultiPoly_1.MultiPoly.constant(1n)];
528
+ if (h.isConstant() && h.constantTerm() !== 0n) {
529
+ return [SparsePolynomial_1.SparsePolynomial.constant(variableCount, 1n)];
608
530
  }
609
- else {
610
- const k = G.length;
611
- G.push(h);
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(G, order, nVars) : G;
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 an exact integer-coefficient ideal.
552
+ * Computes a Groebner basis for exact sparse integer polynomials.
632
553
  *
633
- * @remarks
634
- * Variable indices in every {@link MultiPoly} must agree with `vars`. The Buchberger
635
- * engine uses fraction-free reduction. With `reduced: true`, it interreduces, removes
636
- * coefficient content, normalizes leading signs, and makes a polynomial monic only
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 over integer coefficients.
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, vars, order = 'LEX', reduced = true) {
564
+ function Groebner(polys, order = 'LEX', reduced = true) {
647
565
  if (polys.length === 0) {
648
566
  return [];
649
567
  }
650
- const nVars = vars.length;
651
- const G = groebnerBasis(polys, { order, reduced });
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, nVars), lt: ltOrNull(p, order, nVars) };
574
+ info = { pv: variablePresence(p), lt: ltOrNull(p, order) };
661
575
  cache.set(p, info);
662
576
  }
663
577
  return info;
664
578
  };
665
- const cmp = (p, q) => {
666
- const ip = getInfo(p);
667
- const iq = getInfo(q);
668
- for (let i = 0; i < nVars; i++) {
669
- const ap = ip.pv[i] ? 1 : 0;
670
- const aq = iq.pv[i] ? 1 : 0;
671
- if (ap !== aq) {
672
- return ap > aq ? -1 : 1;
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 (ip.lt && iq.lt) {
676
- const c = compareMonomialDense(ip.lt.exp, iq.lt.exp, order);
677
- if (c !== 0) {
678
- return c;
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
- const tp = p.terms.size;
682
- const tq = q.terms.size;
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 with consistent variable indices.
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 groebnerBasis(polys, opts);
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
- // Compute LEX basis (elimination order: variables to eliminate are first = most significant)
730
- const G = groebnerBasis(polys, { ...opts, order: 'LEX', reduced: true });
731
- // Extract polynomials that don't involve any eliminated variable
732
- const nVars = vars.length;
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 (polyIsZero(f)) {
671
+ if (f.isZero()) {
764
672
  return true;
765
673
  }
766
674
  if (polys.length === 0) {
767
675
  return false;
768
676
  }
769
- const G = groebnerBasis(polys, { ...opts, order, reduced: true });
770
- return polyIsZero(reduceByBasis(f, G, order));
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. Reduction
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
- if (polyIsZero(f) || basis.length === 0) {
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 G = groebnerBasis(polys, { ...opts, order: 'LEX', reduced: true });
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 (G.length === 1 && G[0].isConstant() && G[0].constantTerm() !== 0n) {
725
+ if (basis.length === 1 && basis[0].isConstant() && basis[0].constantTerm() !== 0n) {
818
726
  return [];
819
727
  }
820
- if (G.length === 0) {
728
+ if (basis.length === 0) {
821
729
  // Ideal is {0}, meaning every point is a solution — infinite solutions.
822
730
  return [];
823
731
  }
824
- const nVars = vars.length;
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(G, vars, nVars) {
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: G.map(g => (0, poly_1.mvNormalize)(g)),
748
+ basis: [...basis],
843
749
  },
844
750
  ];
845
- // Solve from last variable to first
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
- // Find polynomials that are now univariate in variable v
850
- const univariates = basis.filter(p => {
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
- for (const key of p.terms.keys()) {
855
- const exp = (0, MultiPoly_1.keyToExp)(key);
856
- for (const [i, e] of exp.entries()) {
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
- return p.degree(v) > 0;
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({ assignment: new Map(assignment), basis });
767
+ nextPartials.push({
768
+ assignment: new Map(assignment),
769
+ basis: partialBasis,
770
+ });
867
771
  continue;
868
772
  }
869
- // Extract rational roots of the first univariate polynomial.
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 k = 1; k < univariates.length; k++) {
876
- if (!evaluatesToZero(univariates[k], v, root)) {
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
- // Incrementally substitute only variable v into the basis
885
- const newBasis = basis.map(p => {
886
- const deg = p.degree(v);
887
- if (deg <= 0) {
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
- return (0, poly_1.mvNormalize)((0, poly_1.substituteVarIndex)(q, v, root.n));
790
+ const scaled = root.d === 1n ? poly : scaleByDenomPow(poly, variableIndex, root.d);
791
+ return scaled.evaluateVariable(variableIndex, root.n);
895
792
  });
896
- const ext = new Map(assignment);
897
- ext.set(v, root);
898
- nextPartials.push({ assignment: ext, basis: newBasis });
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 [vi, val] of assignment) {
907
- if (vi < vars.length) {
908
- named.set(vars[vi], val);
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
- * Scale polynomial so that substituting x_v = n (integer) correctly accounts
916
- * for a rational value n/d. For each term with x_v^k, multiply coefficient by d^(maxDeg - k).
917
- * This gives: p(n/d) * d^maxDeg = result evaluated at x_v = n.
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(p, varIndex, d) {
920
- const maxDeg = p.degree(varIndex);
921
- if (maxDeg <= 0 || d === 1n) {
922
- return p;
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
- const out = new MultiPoly_1.MultiPoly();
931
- for (const [key, coeff] of p.terms) {
932
- const exp = (0, MultiPoly_1.keyToExp)(key);
933
- const k = exp.get(varIndex) ?? 0;
934
- const newCoeff = coeff * dPowers[maxDeg - k];
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
- * Find all rational roots p/q of a univariate polynomial in variable `varIndex`.
944
- * Uses the Rational Root Theorem: p divides the constant term, q divides the leading coefficient.
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, varIndex) {
947
- const deg = poly.degree(varIndex);
948
- if (deg <= 0) {
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 lc = coeffs[actualDeg];
969
- const ct = coeffs[0]; // constant term
970
- if (ct === 0n) {
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
- // Shift down: divide by x
974
- const shiftedPoly = new MultiPoly_1.MultiPoly();
975
- for (let i = 1; i <= actualDeg; i++) {
976
- if (coeffs[i] !== 0n) {
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 moreRoots = rationalRootsUnivariate(shiftedPoly, varIndex);
985
- for (const r of moreRoots) {
986
- if (r.n !== 0n) {
987
- roots.push(r);
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
- // Divisors of |ct| and |lc|
993
- const pDivisors = positiveDivisors((0, bigint_1.abs)(ct));
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 p of pDivisors) {
998
- for (const q of qDivisors) {
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 num = sign * p;
1002
- // Reduce to lowest terms
1003
- const g = (0, bigint_1.GCD)((0, bigint_1.abs)(num), q);
1004
- const rn = num / g;
1005
- const rd = q / g;
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 (evaluatesCoeffsToZero(coeffs, actualDeg, rn, rd)) {
1012
- roots.push({ n: rn, d: rd });
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
- /** Evaluate polynomial coefficients at n/d: check if sum(c_i * n^i * d^(deg-i)) == 0 */
1020
- function evaluatesCoeffsToZero(coeffs, deg, n, d) {
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
- let nPow = 1n;
1023
- let dPow = 1n;
1024
- // Precompute d^deg
1025
- for (let i = 0; i < deg; i++) {
1026
- dPow *= d;
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
- /** Check if substituting varIndex = n/d into poly gives zero. */
1038
- function evaluatesToZero(poly, varIndex, val) {
1039
- const deg = poly.degree(varIndex);
1040
- if (deg <= 0) {
1041
- return polyIsZero(poly) || poly.constantTerm() === 0n;
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
- let p = poly;
1044
- if (val.d !== 1n) {
1045
- p = scaleByDenomPow(p, varIndex, val.d);
904
+ if (degree === 0n) {
905
+ return poly.constantTerm() === 0n;
1046
906
  }
1047
- const result = (0, poly_1.substituteVarIndex)(p, varIndex, val.n);
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 divs = [];
1059
- let i = 1n;
1060
- while (i * i <= n) {
1061
- if (n % i === 0n) {
1062
- divs.push(i);
1063
- if (i !== n / i) {
1064
- divs.push(n / i);
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
- return 0;
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 s;
928
+ return divisors;
1166
929
  }