nerdamer 1.1.13 → 2.0.0-rc.1
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/LICENSE.md +184 -0
- package/README.md +293 -312
- package/dist/bundle.js +2 -0
- package/dist/bundle.js.LICENSE.txt +7 -0
- package/dist/parser.js +2 -0
- package/dist/parser.js.LICENSE.txt +7 -0
- package/docs-data/parser-functions.json +1960 -0
- package/index.d.ts +10 -426
- package/output/algebra/adapters.d.ts +33 -0
- package/output/algebra/adapters.js +109 -0
- package/output/algebra/algorithms/arith.d.ts +84 -0
- package/output/algebra/algorithms/arith.js +323 -0
- package/output/algebra/algorithms/factor.d.ts +3 -0
- package/output/algebra/algorithms/factor.js +5 -0
- package/output/algebra/algorithms/factorMultivariate.d.ts +23 -0
- package/output/algebra/algorithms/factorMultivariate.js +2393 -0
- package/output/algebra/algorithms/factorUnivariate.d.ts +72 -0
- package/output/algebra/algorithms/factorUnivariate.js +1072 -0
- package/output/algebra/algorithms/gcd.d.ts +22 -0
- package/output/algebra/algorithms/gcd.js +690 -0
- package/output/algebra/algorithms/groebnerBase.d.ts +157 -0
- package/output/algebra/algorithms/groebnerBase.js +1166 -0
- package/output/algebra/algorithms/multiPoly/MultiPoly.d.ts +137 -0
- package/output/algebra/algorithms/multiPoly/MultiPoly.js +346 -0
- package/output/algebra/algorithms/poly.d.ts +228 -0
- package/output/algebra/algorithms/poly.js +1299 -0
- package/output/algebra/algorithms/rational.d.ts +17 -0
- package/output/algebra/algorithms/rational.js +115 -0
- package/output/algebra/factor/factor.d.ts +62 -0
- package/output/algebra/factor/factor.js +158 -0
- package/output/algebra/gcd/gcd.d.ts +29 -0
- package/output/algebra/gcd/gcd.js +141 -0
- package/output/algebra/groebner.d.ts +21 -0
- package/output/algebra/groebner.js +42 -0
- package/output/algebra/partfrac.d.ts +19 -0
- package/output/algebra/partfrac.js +398 -0
- package/output/algebra/polynomialize.d.ts +20 -0
- package/output/algebra/polynomialize.js +24 -0
- package/output/algebra/simplify/complexsimp.d.ts +7 -0
- package/output/algebra/simplify/complexsimp.js +24 -0
- package/output/algebra/simplify/factorCommon.d.ts +13 -0
- package/output/algebra/simplify/factorCommon.js +136 -0
- package/output/algebra/simplify/funcsimp.d.ts +26 -0
- package/output/algebra/simplify/funcsimp.js +633 -0
- package/output/algebra/simplify/invtrigrewrite.d.ts +10 -0
- package/output/algebra/simplify/invtrigrewrite.js +96 -0
- package/output/algebra/simplify/ratsimp.d.ts +21 -0
- package/output/algebra/simplify/ratsimp.js +140 -0
- package/output/algebra/simplify/simplify.d.ts +25 -0
- package/output/algebra/simplify/simplify.js +228 -0
- package/output/algebra/simplify/trigreduce.d.ts +38 -0
- package/output/algebra/simplify/trigreduce.js +424 -0
- package/output/algebra/simplify/trigrewrite.d.ts +17 -0
- package/output/algebra/simplify/trigrewrite.js +156 -0
- package/output/algebra/simplify/trigsimp.d.ts +11 -0
- package/output/algebra/simplify/trigsimp.js +369 -0
- package/output/algebra/simplify/utils.d.ts +32 -0
- package/output/algebra/simplify/utils.js +56 -0
- package/output/algebra/utils.d.ts +32 -0
- package/output/algebra/utils.js +48 -0
- package/output/api/advanced.d.ts +7 -0
- package/output/api/advanced.js +18 -0
- package/output/api/algebra.d.ts +17 -0
- package/output/api/algebra.js +32 -0
- package/output/api/assumptions.d.ts +4 -0
- package/output/api/assumptions.js +11 -0
- package/output/api/calculus.d.ts +9 -0
- package/output/api/calculus.js +21 -0
- package/output/api/core.d.ts +17 -0
- package/output/api/core.js +41 -0
- package/output/api/debug.d.ts +16 -0
- package/output/api/debug.js +23 -0
- package/output/api/parser.d.ts +85 -0
- package/output/api/parser.js +16 -0
- package/output/api/solve.d.ts +10 -0
- package/output/api/solve.js +16 -0
- package/output/api/structures.d.ts +10 -0
- package/output/api/structures.js +22 -0
- package/output/calculus/derivative/diff.d.ts +30 -0
- package/output/calculus/derivative/diff.js +238 -0
- package/output/calculus/fresnel.d.ts +16 -0
- package/output/calculus/fresnel.js +39 -0
- package/output/calculus/integrate/byParts.d.ts +18 -0
- package/output/calculus/integrate/byParts.js +339 -0
- package/output/calculus/integrate/bySubstitution.d.ts +135 -0
- package/output/calculus/integrate/bySubstitution.js +411 -0
- package/output/calculus/integrate/integrate.d.ts +38 -0
- package/output/calculus/integrate/integrate.js +652 -0
- package/output/calculus/integrate/integrationTable.d.ts +2 -0
- package/output/calculus/integrate/integrationTable.js +914 -0
- package/output/calculus/integrate/utils.d.ts +28 -0
- package/output/calculus/integrate/utils.js +96 -0
- package/output/calculus/laplace/ilaplace.d.ts +31 -0
- package/output/calculus/laplace/ilaplace.js +192 -0
- package/output/calculus/laplace/ilaplaceTable.d.ts +2 -0
- package/output/calculus/laplace/ilaplaceTable.js +200 -0
- package/output/calculus/laplace/laplace.d.ts +22 -0
- package/output/calculus/laplace/laplace.js +109 -0
- package/output/calculus/laplace/laplaceTable.d.ts +2 -0
- package/output/calculus/laplace/laplaceTable.js +187 -0
- package/output/calculus/limit/limit.d.ts +35 -0
- package/output/calculus/limit/limit.js +1183 -0
- package/output/calculus/limit/limitsTable.d.ts +2 -0
- package/output/calculus/limit/limitsTable.js +7 -0
- package/output/core/Settings.d.ts +32 -0
- package/output/core/Settings.js +68 -0
- package/output/core/adapters.d.ts +18 -0
- package/output/core/adapters.js +40 -0
- package/output/core/classes/assumption/Assumption.d.ts +218 -0
- package/output/core/classes/assumption/Assumption.js +605 -0
- package/output/core/classes/assumption/assertiveFunctions.d.ts +6 -0
- package/output/core/classes/assumption/assertiveFunctions.js +64 -0
- package/output/core/classes/assumption/assume.d.ts +90 -0
- package/output/core/classes/assumption/assume.js +145 -0
- package/output/core/classes/collection/Collection.d.ts +107 -0
- package/output/core/classes/collection/Collection.js +231 -0
- package/output/core/classes/complex/Complex.d.ts +99 -0
- package/output/core/classes/complex/Complex.js +135 -0
- package/output/core/classes/decimalSet/DecimalSet.d.ts +226 -0
- package/output/core/classes/decimalSet/DecimalSet.js +453 -0
- package/output/core/classes/dictionary/Dictionary.d.ts +102 -0
- package/output/core/classes/dictionary/Dictionary.js +213 -0
- package/output/core/classes/equation/Equation.d.ts +256 -0
- package/output/core/classes/equation/Equation.js +350 -0
- package/output/core/classes/expression/CoeffObject.d.ts +100 -0
- package/output/core/classes/expression/CoeffObject.js +198 -0
- package/output/core/classes/expression/Expression.d.ts +1674 -0
- package/output/core/classes/expression/Expression.js +2332 -0
- package/output/core/classes/expression/analysis.d.ts +25 -0
- package/output/core/classes/expression/analysis.js +218 -0
- package/output/core/classes/expression/collect.d.ts +9 -0
- package/output/core/classes/expression/collect.js +49 -0
- package/output/core/classes/expression/format.d.ts +45 -0
- package/output/core/classes/expression/format.js +217 -0
- package/output/core/classes/expression/products.d.ts +23 -0
- package/output/core/classes/expression/products.js +115 -0
- package/output/core/classes/expression/shortcuts.d.ts +35 -0
- package/output/core/classes/expression/shortcuts.js +129 -0
- package/output/core/classes/expression/traversal.d.ts +31 -0
- package/output/core/classes/expression/traversal.js +216 -0
- package/output/core/classes/expression/trig.d.ts +25 -0
- package/output/core/classes/expression/trig.js +40 -0
- package/output/core/classes/expression/utils.d.ts +52 -0
- package/output/core/classes/expression/utils.js +189 -0
- package/output/core/classes/lookupTable/LookupTable.d.ts +12 -0
- package/output/core/classes/lookupTable/LookupTable.js +55 -0
- package/output/core/classes/matrix/Matrix.d.ts +227 -0
- package/output/core/classes/matrix/Matrix.js +889 -0
- package/output/core/classes/matrix/Sylvester.d.ts +11 -0
- package/output/core/classes/matrix/Sylvester.js +54 -0
- package/output/core/classes/matrix/functions.d.ts +26 -0
- package/output/core/classes/matrix/functions.js +52 -0
- package/output/core/classes/matrix/utils.d.ts +9 -0
- package/output/core/classes/matrix/utils.js +22 -0
- package/output/core/classes/parser/Parser.d.ts +480 -0
- package/output/core/classes/parser/Parser.js +1812 -0
- package/output/core/classes/parser/Token.d.ts +58 -0
- package/output/core/classes/parser/Token.js +137 -0
- package/output/core/classes/parser/constants.d.ts +160 -0
- package/output/core/classes/parser/constants.js +227 -0
- package/output/core/classes/parser/helpers.d.ts +21 -0
- package/output/core/classes/parser/helpers.js +42 -0
- package/output/core/classes/parser/operations/add.d.ts +15 -0
- package/output/core/classes/parser/operations/add.js +238 -0
- package/output/core/classes/parser/operations/comma.d.ts +7 -0
- package/output/core/classes/parser/operations/comma.js +11 -0
- package/output/core/classes/parser/operations/compare.d.ts +21 -0
- package/output/core/classes/parser/operations/compare.js +461 -0
- package/output/core/classes/parser/operations/divide.d.ts +2 -0
- package/output/core/classes/parser/operations/divide.js +72 -0
- package/output/core/classes/parser/operations/functions.d.ts +46 -0
- package/output/core/classes/parser/operations/functions.js +430 -0
- package/output/core/classes/parser/operations/multiply.d.ts +11 -0
- package/output/core/classes/parser/operations/multiply.js +321 -0
- package/output/core/classes/parser/operations/power.d.ts +48 -0
- package/output/core/classes/parser/operations/power.js +673 -0
- package/output/core/classes/parser/operations/subtract.d.ts +3 -0
- package/output/core/classes/parser/operations/subtract.js +35 -0
- package/output/core/classes/parser/preprocess.d.ts +14 -0
- package/output/core/classes/parser/preprocess.js +259 -0
- package/output/core/classes/parser/scripting/controlFlow.d.ts +121 -0
- package/output/core/classes/parser/scripting/controlFlow.js +355 -0
- package/output/core/classes/parser/scripting/evaluate.d.ts +10 -0
- package/output/core/classes/parser/scripting/evaluate.js +112 -0
- package/output/core/classes/parser/scripting/functions.d.ts +14 -0
- package/output/core/classes/parser/scripting/functions.js +59 -0
- package/output/core/classes/parser/scripting/scope.d.ts +35 -0
- package/output/core/classes/parser/scripting/scope.js +155 -0
- package/output/core/classes/parser/types.d.ts +125 -0
- package/output/core/classes/parser/types.js +2 -0
- package/output/core/classes/parser/wrappers/IndexedReference.d.ts +31 -0
- package/output/core/classes/parser/wrappers/IndexedReference.js +68 -0
- package/output/core/classes/parser/wrappers/KeyValuePair.d.ts +24 -0
- package/output/core/classes/parser/wrappers/KeyValuePair.js +51 -0
- package/output/core/classes/polynomial/Polynomial.d.ts +500 -0
- package/output/core/classes/polynomial/Polynomial.js +1149 -0
- package/output/core/classes/polynomial/Term.d.ts +222 -0
- package/output/core/classes/polynomial/Term.js +430 -0
- package/output/core/classes/polynomial/functions.d.ts +69 -0
- package/output/core/classes/polynomial/functions.js +126 -0
- package/output/core/classes/polynomial/utils.d.ts +62 -0
- package/output/core/classes/polynomial/utils.js +206 -0
- package/output/core/classes/rational/Rational.d.ts +504 -0
- package/output/core/classes/rational/Rational.js +779 -0
- package/output/core/classes/seq/SEQ.d.ts +13 -0
- package/output/core/classes/seq/SEQ.js +137 -0
- package/output/core/classes/valuesSet/ValuesSet.d.ts +179 -0
- package/output/core/classes/valuesSet/ValuesSet.js +381 -0
- package/output/core/classes/vector/Vector.d.ts +222 -0
- package/output/core/classes/vector/Vector.js +497 -0
- package/output/core/classes/vector/functions.d.ts +28 -0
- package/output/core/classes/vector/functions.js +47 -0
- package/output/core/common/classes/MathematicalAggregate.d.ts +53 -0
- package/output/core/common/classes/MathematicalAggregate.js +140 -0
- package/output/core/common/classes/Scope.d.ts +62 -0
- package/output/core/common/classes/Scope.js +122 -0
- package/output/core/common/classes/StructuredEntity.d.ts +55 -0
- package/output/core/common/classes/StructuredEntity.js +151 -0
- package/output/core/common/common.d.ts +63 -0
- package/output/core/common/common.js +279 -0
- package/output/core/common/functions/functions.d.ts +14 -0
- package/output/core/common/functions/functions.js +33 -0
- package/output/core/common/functions/structuredEntityUtils.d.ts +27 -0
- package/output/core/common/functions/structuredEntityUtils.js +40 -0
- package/output/core/converters/BaseConverter.d.ts +97 -0
- package/output/core/converters/BaseConverter.js +405 -0
- package/output/core/converters/Converter.d.ts +111 -0
- package/output/core/converters/Converter.js +802 -0
- package/output/core/converters/Pattern.d.ts +71 -0
- package/output/core/converters/Pattern.js +302 -0
- package/output/core/dispatch.d.ts +34 -0
- package/output/core/dispatch.js +304 -0
- package/output/core/errors.d.ts +531 -0
- package/output/core/errors.js +621 -0
- package/output/core/functions/bigint/bigint.d.ts +121 -0
- package/output/core/functions/bigint/bigint.js +392 -0
- package/output/core/functions/bigint/primeFactor.d.ts +49 -0
- package/output/core/functions/bigint/primeFactor.js +267 -0
- package/output/core/functions/bigint/primes.d.ts +1 -0
- package/output/core/functions/bigint/primes.js +11 -0
- package/output/core/functions/build/definitions.d.ts +12 -0
- package/output/core/functions/build/definitions.js +133 -0
- package/output/core/functions/build/index.d.ts +32 -0
- package/output/core/functions/build/index.js +136 -0
- package/output/core/functions/complex.d.ts +90 -0
- package/output/core/functions/complex.js +482 -0
- package/output/core/functions/decimal.d.ts +24 -0
- package/output/core/functions/decimal.js +664 -0
- package/output/core/functions/expand/expand.d.ts +42 -0
- package/output/core/functions/expand/expand.js +376 -0
- package/output/core/functions/fresnelNumeric.d.ts +12 -0
- package/output/core/functions/fresnelNumeric.js +123 -0
- package/output/core/functions/numeric.d.ts +140 -0
- package/output/core/functions/numeric.js +790 -0
- package/output/core/functions/rationalNormalization.d.ts +11 -0
- package/output/core/functions/rationalNormalization.js +117 -0
- package/output/core/functions/setFunction.d.ts +19 -0
- package/output/core/functions/setFunction.js +75 -0
- package/output/core/functions/string.d.ts +26 -0
- package/output/core/functions/string.js +101 -0
- package/output/core/functions/subst.d.ts +90 -0
- package/output/core/functions/subst.js +498 -0
- package/output/core/functions/utils.d.ts +24 -0
- package/output/core/functions/utils.js +51 -0
- package/output/core/types.d.ts +48 -0
- package/output/core/types.js +2 -0
- package/output/index.d.ts +339 -0
- package/output/index.js +715 -0
- package/output/math/defint/defintDecimal.d.ts +37 -0
- package/output/math/defint/defintDecimal.js +320 -0
- package/output/math/defint/defintNative.d.ts +57 -0
- package/output/math/defint/defintNative.js +292 -0
- package/output/math/geometry.d.ts +8 -0
- package/output/math/geometry.js +32 -0
- package/output/math/math.d.ts +655 -0
- package/output/math/math.js +1811 -0
- package/output/math/trig.d.ts +518 -0
- package/output/math/trig.js +1444 -0
- package/output/math/trunc.d.ts +19 -0
- package/output/math/trunc.js +36 -0
- package/output/math/utils.d.ts +60 -0
- package/output/math/utils.js +180 -0
- package/output/solve/classes/DecimalMatrix.d.ts +17 -0
- package/output/solve/classes/DecimalMatrix.js +86 -0
- package/output/solve/classes/FunctionSolver.d.ts +135 -0
- package/output/solve/classes/FunctionSolver.js +433 -0
- package/output/solve/classes/MultivariateSolver.d.ts +84 -0
- package/output/solve/classes/MultivariateSolver.js +240 -0
- package/output/solve/classes/PolynomialSolver.d.ts +85 -0
- package/output/solve/classes/PolynomialSolver.js +292 -0
- package/output/solve/classes/SolutionSet.d.ts +153 -0
- package/output/solve/classes/SolutionSet.js +357 -0
- package/output/solve/classes/Solver.d.ts +17 -0
- package/output/solve/classes/Solver.js +132 -0
- package/output/solve/classes/SymbolicSolver.d.ts +74 -0
- package/output/solve/classes/SymbolicSolver.js +601 -0
- package/output/solve/linsolve.d.ts +51 -0
- package/output/solve/linsolve.js +225 -0
- package/output/solve/solve.d.ts +34 -0
- package/output/solve/solve.js +260 -0
- package/output/solve/solveSystem.d.ts +31 -0
- package/output/solve/solveSystem.js +290 -0
- package/output/solve/utils.d.ts +9 -0
- package/output/solve/utils.js +62 -0
- package/output/utils/array.d.ts +42 -0
- package/output/utils/array.js +98 -0
- package/output/utils/debug.d.ts +131 -0
- package/output/utils/debug.js +295 -0
- package/output/utils/decimal.d.ts +3 -0
- package/output/utils/decimal.js +16 -0
- package/output/utils/numeric.d.ts +14 -0
- package/output/utils/numeric.js +51 -0
- package/output/utils/object.d.ts +26 -0
- package/output/utils/object.js +56 -0
- package/package.json +213 -57
- package/.travis.yml +0 -3
- package/Algebra.js +0 -4568
- package/BREAKING_CHANGES.md +0 -5
- package/CODE_OF_CONDUCT.md +0 -46
- package/CONTRIBUTING.md +0 -14
- package/Calculus.js +0 -2675
- package/Extra.js +0 -622
- package/Solve.js +0 -1783
- package/all.js +0 -16
- package/all.min.js +0 -1
- package/gulpfile.js +0 -16
- package/index.html +0 -158
- package/license.txt +0 -19
- package/nerdamer.core.js +0 -12510
- package/spec/LaTeX.spec.js +0 -302
- package/spec/TeXConvert.spec.js +0 -8
- package/spec/algebra.spec.js +0 -287
- package/spec/basic_parser.spec.js +0 -134
- package/spec/build.spec.js +0 -131
- package/spec/calculus.spec.js +0 -183
- package/spec/core.spec.js +0 -2970
- package/spec/extra.spec.js +0 -54
- package/spec/solve.spec.js +0 -126
- package/spec/support/jasmine.json +0 -11
- package/spec/support/utils.js +0 -42
- package/spec/text.spec.js +0 -132
|
@@ -0,0 +1,2393 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module factor_multivariate
|
|
4
|
+
*
|
|
5
|
+
* Multivariate polynomial factorization over Z[x1, x2, ..., xn].
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.factorPoly = factorPoly;
|
|
9
|
+
const arith_1 = require("./arith");
|
|
10
|
+
const factorUnivariate_1 = require("./factorUnivariate");
|
|
11
|
+
const MultiPoly_1 = require("./multiPoly/MultiPoly");
|
|
12
|
+
const poly_1 = require("./poly");
|
|
13
|
+
const rational_1 = require("./rational");
|
|
14
|
+
const MAX_CONSTANT_FACTOR_SPLIT_COUNT = 12;
|
|
15
|
+
const MIN_EVALUATION_ATTEMPTS = 20;
|
|
16
|
+
const MAX_EVALUATION_ATTEMPTS = 200;
|
|
17
|
+
const EVALUATION_ATTEMPT_DEGREE_DIVISOR = 2;
|
|
18
|
+
const EVALUATION_ATTEMPT_BIT_DIVISOR = 128;
|
|
19
|
+
const MIN_EVALUATION_SET_MULTIPLIER = 10;
|
|
20
|
+
const MAX_EVALUATION_SET_MULTIPLIER = 80;
|
|
21
|
+
const MIN_EVALUATION_SET_COUNT = 20;
|
|
22
|
+
const EVALUATION_SET_DEGREE_DIVISOR = 3;
|
|
23
|
+
const EVALUATION_SET_BIT_DIVISOR = 256;
|
|
24
|
+
/** Divide out coefficient content and normalize sign so the lex-leading coefficient is positive. */
|
|
25
|
+
function makePrimitiveZ(p) {
|
|
26
|
+
if (p.isZero()) {
|
|
27
|
+
return [1n, p];
|
|
28
|
+
}
|
|
29
|
+
const { content, primitive } = (0, poly_1.mvNormalizePrimitiveSignLex)(p);
|
|
30
|
+
return [content === 0n ? 1n : content, primitive];
|
|
31
|
+
}
|
|
32
|
+
/** True if p is a unit constant (±1). */
|
|
33
|
+
function isUnitConstant(p) {
|
|
34
|
+
return p.isConstant() && (p.constantTerm() === 1n || p.constantTerm() === -1n);
|
|
35
|
+
}
|
|
36
|
+
/** Remove unit constants (±1) from a factor list. */
|
|
37
|
+
function filterUnitConstants(factors) {
|
|
38
|
+
return factors.filter(f => !isUnitConstant(f));
|
|
39
|
+
}
|
|
40
|
+
/** Multiply factors (empty list -> 1). */
|
|
41
|
+
function multiplyFactors(factors) {
|
|
42
|
+
let acc = MultiPoly_1.MultiPoly.constant(1n);
|
|
43
|
+
for (const f of factors) {
|
|
44
|
+
acc = (0, poly_1.mulPoly)(acc, f);
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
}
|
|
48
|
+
/** Checks whether the given factors reconstruct the original polynomial (after normalization). */
|
|
49
|
+
function factorsReconstruct(original, factors) {
|
|
50
|
+
const prod = (0, poly_1.mvNormalize)(multiplyFactors(factors));
|
|
51
|
+
const orig = (0, poly_1.mvNormalize)(original);
|
|
52
|
+
return (0, poly_1.equalPoly)(prod, orig);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Check for special bivariate patterns
|
|
56
|
+
*/
|
|
57
|
+
function trySpecialBivariatePatterns(f, mainVar, liftVar) {
|
|
58
|
+
// Pattern: x^n - y^n (difference of powers)
|
|
59
|
+
const xDeg = f.degree(mainVar);
|
|
60
|
+
const yDeg = f.degree(liftVar);
|
|
61
|
+
if (xDeg === yDeg && xDeg >= 1) {
|
|
62
|
+
// Check if f = x^n - y^n
|
|
63
|
+
const xn = MultiPoly_1.MultiPoly.monomial(1n, mainVar === 0 ? [xDeg, 0] : [0, xDeg]);
|
|
64
|
+
const yn = MultiPoly_1.MultiPoly.monomial(1n, liftVar === 0 ? [yDeg, 0] : [0, yDeg]);
|
|
65
|
+
const diff = (0, poly_1.subPoly)(xn, yn);
|
|
66
|
+
if ((0, poly_1.equalPoly)(f, diff)) {
|
|
67
|
+
// x^n - y^n = (x - y)(x^(n-1) + x^(n-2)y + ... + y^(n-1))
|
|
68
|
+
const xMinusY = (0, poly_1.subPoly)(MultiPoly_1.MultiPoly.variable(mainVar), MultiPoly_1.MultiPoly.variable(liftVar));
|
|
69
|
+
// Build the sum
|
|
70
|
+
let sum = MultiPoly_1.MultiPoly.zero();
|
|
71
|
+
for (let i = 0; i < xDeg; i++) {
|
|
72
|
+
// x^(n-1-i) * y^i
|
|
73
|
+
const term = MultiPoly_1.MultiPoly.monomial(1n, mainVar === 0 ? [xDeg - 1 - i, i] : [i, xDeg - 1 - i]);
|
|
74
|
+
sum = (0, poly_1.addPoly)(sum, term);
|
|
75
|
+
}
|
|
76
|
+
return [xMinusY, sum];
|
|
77
|
+
}
|
|
78
|
+
// Check if f = x^n + y^n with n odd
|
|
79
|
+
const sumXY = (0, poly_1.addPoly)(xn, yn);
|
|
80
|
+
if ((0, poly_1.equalPoly)(f, sumXY) && xDeg % 2 === 1) {
|
|
81
|
+
// x^n + y^n = (x + y)(x^(n-1) - x^(n-2)y + ... + y^(n-1)) for odd n
|
|
82
|
+
const xPlusY = (0, poly_1.addPoly)(MultiPoly_1.MultiPoly.variable(mainVar), MultiPoly_1.MultiPoly.variable(liftVar));
|
|
83
|
+
let sum = MultiPoly_1.MultiPoly.zero();
|
|
84
|
+
for (let i = 0; i < xDeg; i++) {
|
|
85
|
+
const sign = i % 2 === 0 ? 1n : -1n;
|
|
86
|
+
const term = MultiPoly_1.MultiPoly.monomial(sign, mainVar === 0 ? [xDeg - 1 - i, i] : [i, xDeg - 1 - i]);
|
|
87
|
+
sum = (0, poly_1.addPoly)(sum, term);
|
|
88
|
+
}
|
|
89
|
+
return [xPlusY, sum];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Pattern: perfect square (x + y)^2 = x^2 + 2xy + y^2
|
|
93
|
+
if (xDeg === 2 && yDeg === 2) {
|
|
94
|
+
// Check if f = x^2 + 2xy + y^2
|
|
95
|
+
const x2 = f.getCoeff(mainVar === 0 ? [2, 0] : [0, 2]);
|
|
96
|
+
const y2 = f.getCoeff(liftVar === 0 ? [2, 0] : [0, 2]);
|
|
97
|
+
const xy = f.getCoeff([1, 1]);
|
|
98
|
+
if (x2 === 1n && y2 === 1n && xy === 2n && f.terms.size === 3) {
|
|
99
|
+
const xPlusY = (0, poly_1.addPoly)(MultiPoly_1.MultiPoly.variable(mainVar), MultiPoly_1.MultiPoly.variable(liftVar));
|
|
100
|
+
return [xPlusY, xPlusY];
|
|
101
|
+
}
|
|
102
|
+
// Check if f = x^2 - 2xy + y^2
|
|
103
|
+
if (x2 === 1n && y2 === 1n && xy === -2n && f.terms.size === 3) {
|
|
104
|
+
const xMinusY = (0, poly_1.subPoly)(MultiPoly_1.MultiPoly.variable(mainVar), MultiPoly_1.MultiPoly.variable(liftVar));
|
|
105
|
+
return [xMinusY, xMinusY];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Pattern: x^4 + x^2*y^2 + y^4 = (x^2 + xy + y^2)(x^2 - xy + y^2)
|
|
109
|
+
// More generally: x^(2m) + x^m*y^m + y^(2m) for m >= 2
|
|
110
|
+
// When m is even (m=2k): factors are (x^m + x^k*y^k + y^m)(x^m - x^k*y^k + y^m)
|
|
111
|
+
// When m is odd: factors are (x^m + x^(m-1)*y + ... + y^m)(x^m - x^(m-1)*y + ... + y^m) - alternating
|
|
112
|
+
if (xDeg === yDeg && xDeg % 2 === 0 && xDeg >= 4) {
|
|
113
|
+
const m = xDeg / 2; // We're looking at x^(2m) + x^m*y^m + y^(2m)
|
|
114
|
+
// Check if f = x^(2m) + x^m*y^m + y^(2m)
|
|
115
|
+
const x2m = MultiPoly_1.MultiPoly.monomial(1n, mainVar === 0 ? [2 * m, 0] : [0, 2 * m]);
|
|
116
|
+
const y2m = MultiPoly_1.MultiPoly.monomial(1n, liftVar === 0 ? [2 * m, 0] : [0, 2 * m]);
|
|
117
|
+
const xmym = MultiPoly_1.MultiPoly.monomial(1n, mainVar === 0 ? [m, m] : [m, m]);
|
|
118
|
+
const target = (0, poly_1.addPoly)((0, poly_1.addPoly)(x2m, xmym), y2m);
|
|
119
|
+
if ((0, poly_1.equalPoly)(f, target)) {
|
|
120
|
+
if (m % 2 === 0) {
|
|
121
|
+
// m is even: factors are (x^m + x^(m/2)*y^(m/2) + y^m)(x^m - x^(m/2)*y^(m/2) + y^m)
|
|
122
|
+
const k = m / 2;
|
|
123
|
+
const xm = MultiPoly_1.MultiPoly.monomial(1n, mainVar === 0 ? [m, 0] : [0, m]);
|
|
124
|
+
const ym = MultiPoly_1.MultiPoly.monomial(1n, liftVar === 0 ? [m, 0] : [0, m]);
|
|
125
|
+
const xkyk = MultiPoly_1.MultiPoly.monomial(1n, mainVar === 0 ? [k, k] : [k, k]);
|
|
126
|
+
const f1 = (0, poly_1.addPoly)((0, poly_1.addPoly)(xm, xkyk), ym); // x^m + x^k*y^k + y^m
|
|
127
|
+
const f2 = (0, poly_1.subPoly)((0, poly_1.addPoly)(xm, ym), xkyk); // x^m - x^k*y^k + y^m
|
|
128
|
+
// Verify
|
|
129
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
130
|
+
if ((0, poly_1.equalPoly)(prod, f)) {
|
|
131
|
+
return [f1, f2];
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
// m is odd: factors are (x^m + x^(m-1)*y + ... + y^m)(x^m - x^(m-1)*y + ... + y^m)
|
|
136
|
+
let f1 = MultiPoly_1.MultiPoly.zero();
|
|
137
|
+
let f2 = MultiPoly_1.MultiPoly.zero();
|
|
138
|
+
for (let i = 0; i <= m; i++) {
|
|
139
|
+
const term = MultiPoly_1.MultiPoly.monomial(1n, mainVar === 0 ? [m - i, i] : [i, m - i]);
|
|
140
|
+
f1 = (0, poly_1.addPoly)(f1, term);
|
|
141
|
+
const sign = i % 2 === 0 ? 1n : -1n;
|
|
142
|
+
f2 = (0, poly_1.addPoly)(f2, (0, poly_1.scalePoly)(term, sign));
|
|
143
|
+
}
|
|
144
|
+
// Verify
|
|
145
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
146
|
+
if ((0, poly_1.equalPoly)(prod, f)) {
|
|
147
|
+
return [f1, f2];
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Also check x^(2m) - x^m*y^m + y^(2m) pattern (for cases like x^8-x^4y^4+y^8)
|
|
152
|
+
const targetMinus = (0, poly_1.subPoly)((0, poly_1.addPoly)(x2m, y2m), xmym);
|
|
153
|
+
if ((0, poly_1.equalPoly)(f, targetMinus)) {
|
|
154
|
+
// This doesn't have a simple factorization over Z in general
|
|
155
|
+
// Skip for now
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Pattern: Check if (x+y) or (x-y) divides f by substitution
|
|
159
|
+
// If f(x, -x) = 0 (i.e., substituting y = -x), then (x+y) divides f
|
|
160
|
+
// If f(x, x) = 0 (i.e., substituting y = x), then (x-y) divides f
|
|
161
|
+
{
|
|
162
|
+
// Check (x+y) divisibility: evaluate at liftVar = -mainVar
|
|
163
|
+
// This means for each term c * x^a * y^b, we get c * x^a * (-x)^b = c * (-1)^b * x^(a+b)
|
|
164
|
+
let sumAtYeqNegX = 0n;
|
|
165
|
+
for (const [key, coeff] of f.terms) {
|
|
166
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
167
|
+
const b = exp.get(liftVar) ?? 0;
|
|
168
|
+
const sign = b % 2 === 0 ? 1n : -1n;
|
|
169
|
+
// All terms become multiples of x^(a+b), so we just check if they sum to 0
|
|
170
|
+
sumAtYeqNegX += coeff * sign;
|
|
171
|
+
}
|
|
172
|
+
if (sumAtYeqNegX === 0n && f.terms.size > 1) {
|
|
173
|
+
// (x+y) divides f - perform polynomial division
|
|
174
|
+
const xPlusY = (0, poly_1.addPoly)(MultiPoly_1.MultiPoly.variable(mainVar), MultiPoly_1.MultiPoly.variable(liftVar));
|
|
175
|
+
const quotient = (0, poly_1.mvExactDiv)(f, xPlusY);
|
|
176
|
+
if (quotient !== null && !quotient.isConstant()) {
|
|
177
|
+
return [xPlusY, quotient];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Check (x-y) divisibility: evaluate at liftVar = mainVar
|
|
181
|
+
let sumAtYeqX = 0n;
|
|
182
|
+
for (const coeff of f.terms.values()) {
|
|
183
|
+
sumAtYeqX += coeff;
|
|
184
|
+
}
|
|
185
|
+
if (sumAtYeqX === 0n && f.terms.size > 1) {
|
|
186
|
+
// (x-y) divides f
|
|
187
|
+
const xMinusY = (0, poly_1.subPoly)(MultiPoly_1.MultiPoly.variable(mainVar), MultiPoly_1.MultiPoly.variable(liftVar));
|
|
188
|
+
const quotient = (0, poly_1.mvExactDiv)(f, xMinusY);
|
|
189
|
+
if (quotient !== null && !quotient.isConstant()) {
|
|
190
|
+
return [xMinusY, quotient];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// Pattern: Try (x^2+y^2) as a factor for polynomials with only even powers
|
|
195
|
+
// This catches cases like x^6+x^4y^2+x^2y^4+y^6 = (x^2+y^2)(x^4+y^4)
|
|
196
|
+
{
|
|
197
|
+
// Check if all terms have even degrees in both variables
|
|
198
|
+
let allEven = true;
|
|
199
|
+
for (const key of f.terms.keys()) {
|
|
200
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
201
|
+
const a = exp.get(mainVar) ?? 0;
|
|
202
|
+
const b = exp.get(liftVar) ?? 0;
|
|
203
|
+
if (a % 2 !== 0 || b % 2 !== 0) {
|
|
204
|
+
allEven = false;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (allEven && xDeg >= 2 && yDeg >= 2) {
|
|
209
|
+
// Try dividing by x^2 + y^2
|
|
210
|
+
const x2 = MultiPoly_1.MultiPoly.monomial(1n, mainVar === 0 ? [2, 0] : [0, 2]);
|
|
211
|
+
const y2 = MultiPoly_1.MultiPoly.monomial(1n, liftVar === 0 ? [2, 0] : [0, 2]);
|
|
212
|
+
const x2PlusY2 = (0, poly_1.addPoly)(x2, y2);
|
|
213
|
+
const quotient = (0, poly_1.mvExactDiv)(f, x2PlusY2);
|
|
214
|
+
if (quotient !== null && !quotient.isConstant()) {
|
|
215
|
+
return [x2PlusY2, quotient];
|
|
216
|
+
}
|
|
217
|
+
// Also try x^2 - y^2 = (x-y)(x+y)
|
|
218
|
+
const x2MinusY2 = (0, poly_1.subPoly)(x2, y2);
|
|
219
|
+
const quotient2 = (0, poly_1.mvExactDiv)(f, x2MinusY2);
|
|
220
|
+
if (quotient2 !== null && !quotient2.isConstant()) {
|
|
221
|
+
return [x2MinusY2, quotient2];
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Pattern: Homogeneous polynomial factorization.
|
|
226
|
+
// If f is homogeneous of degree d (every term has total degree d), dehomogenize
|
|
227
|
+
// by setting liftVar=1 to get a univariate polynomial, factor it, then
|
|
228
|
+
// re-homogenize each factor by restoring powers of liftVar.
|
|
229
|
+
{
|
|
230
|
+
const totalDeg = xDeg; // candidate total degree
|
|
231
|
+
let isHomogeneous = true;
|
|
232
|
+
for (const [key] of f.terms) {
|
|
233
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
234
|
+
const a = exp.get(mainVar) ?? 0;
|
|
235
|
+
const b = exp.get(liftVar) ?? 0;
|
|
236
|
+
if (a + b !== totalDeg) {
|
|
237
|
+
isHomogeneous = false;
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (isHomogeneous && totalDeg >= 4) {
|
|
242
|
+
// Dehomogenize: set liftVar = 1, giving a univariate polynomial in mainVar
|
|
243
|
+
const uniCoeffs = new Array(totalDeg + 1).fill(0n);
|
|
244
|
+
for (const [key, coeff] of f.terms) {
|
|
245
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
246
|
+
const a = exp.get(mainVar) ?? 0;
|
|
247
|
+
uniCoeffs[a] = (uniCoeffs[a] ?? 0n) + coeff;
|
|
248
|
+
}
|
|
249
|
+
// Factor the univariate polynomial
|
|
250
|
+
const fr = (0, factorUnivariate_1.factorPolynomialBig)(uniCoeffs);
|
|
251
|
+
if (fr.factors && fr.factors.length >= 2) {
|
|
252
|
+
// Re-homogenize each factor: for coefficient at x^i in a degree-d factor,
|
|
253
|
+
// multiply by y^(d-i) to restore homogeneity.
|
|
254
|
+
const result = [];
|
|
255
|
+
for (const fac of fr.factors) {
|
|
256
|
+
const coeffs = fac.coefficients;
|
|
257
|
+
const facDeg = coeffs.length - 1;
|
|
258
|
+
let facPoly = MultiPoly_1.MultiPoly.zero();
|
|
259
|
+
for (let i = 0; i <= facDeg; i++) {
|
|
260
|
+
const c = coeffs[i] ?? 0n;
|
|
261
|
+
if (c !== 0n) {
|
|
262
|
+
const term = MultiPoly_1.MultiPoly.monomial(c, mainVar === 0 ? [i, facDeg - i] : [facDeg - i, i]);
|
|
263
|
+
facPoly = (0, poly_1.addPoly)(facPoly, term);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (let m = 0; m < (fac.multiplicity ?? 1); m++) {
|
|
267
|
+
result.push(facPoly);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Handle content
|
|
271
|
+
if (fr.content !== 1n && fr.content !== 0n) {
|
|
272
|
+
if (fr.content === -1n && result.length > 0) {
|
|
273
|
+
result[0] = (0, poly_1.scalePoly)(result[0], -1n);
|
|
274
|
+
}
|
|
275
|
+
else if (fr.content !== -1n) {
|
|
276
|
+
result.unshift(MultiPoly_1.MultiPoly.constant(fr.content));
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Verify the factorization
|
|
280
|
+
if (result.length >= 2) {
|
|
281
|
+
let prod = result[0];
|
|
282
|
+
for (let i = 1; i < result.length; i++) {
|
|
283
|
+
prod = (0, poly_1.mulPoly)(prod, result[i]);
|
|
284
|
+
}
|
|
285
|
+
if ((0, poly_1.equalPoly)(prod, f)) {
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Main factorization: factor a multivariate polynomial
|
|
296
|
+
*/
|
|
297
|
+
/**
|
|
298
|
+
* Updated factorMultivariate to handle complex evaluation points
|
|
299
|
+
* and leading coefficient distribution.
|
|
300
|
+
*/
|
|
301
|
+
/**
|
|
302
|
+
* Corrected multivariate factorization that won't hang on the user's polynomial.
|
|
303
|
+
*/
|
|
304
|
+
/**
|
|
305
|
+
* Full implementation to handle complex multivariate polynomials.
|
|
306
|
+
*/
|
|
307
|
+
/**
|
|
308
|
+
* Monomial GCD across all terms: gcd(coeffs) * Π v_i^{minExp_i}.
|
|
309
|
+
* Returns 1 if there is no non-trivial common monomial factor.
|
|
310
|
+
*/
|
|
311
|
+
function monomialGCD(p) {
|
|
312
|
+
if (p.isZero() || p.terms.size === 0) {
|
|
313
|
+
return MultiPoly_1.MultiPoly.constant(1n);
|
|
314
|
+
}
|
|
315
|
+
let gCoeff = null;
|
|
316
|
+
const minExp = new Map();
|
|
317
|
+
for (const [key, coeff] of p.terms) {
|
|
318
|
+
const c = coeff < 0n ? -coeff : coeff;
|
|
319
|
+
gCoeff = gCoeff === null ? c : (0, arith_1.gcd)(gCoeff, c);
|
|
320
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
321
|
+
const vars = new Set([...minExp.keys(), ...exp.keys()]);
|
|
322
|
+
for (const v of vars) {
|
|
323
|
+
const e = exp.get(v) ?? 0;
|
|
324
|
+
const prev = minExp.get(v);
|
|
325
|
+
if (prev === undefined) {
|
|
326
|
+
minExp.set(v, e);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
minExp.set(v, Math.min(prev, e));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (gCoeff === null || gCoeff === 0n) {
|
|
334
|
+
return MultiPoly_1.MultiPoly.constant(1n);
|
|
335
|
+
}
|
|
336
|
+
const g = new MultiPoly_1.MultiPoly();
|
|
337
|
+
const eMap = new Map();
|
|
338
|
+
for (const [v, e] of minExp) {
|
|
339
|
+
if (e > 0) {
|
|
340
|
+
eMap.set(v, e);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
g.terms.set((0, MultiPoly_1.expToKey)(eMap), gCoeff);
|
|
344
|
+
g.trim();
|
|
345
|
+
if (g.isConstant() && gCoeff === 1n) {
|
|
346
|
+
return MultiPoly_1.MultiPoly.constant(1n);
|
|
347
|
+
}
|
|
348
|
+
return g;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Try to factor a polynomial that is linear in variable v.
|
|
352
|
+
* p = A*v + B where A, B are polynomials not involving v.
|
|
353
|
+
* Factors as f * q if A and B share a non-trivial polynomial factor f.
|
|
354
|
+
*/
|
|
355
|
+
function tryFactorLinearInVar(p, v) {
|
|
356
|
+
const A = new MultiPoly_1.MultiPoly();
|
|
357
|
+
const B = new MultiPoly_1.MultiPoly();
|
|
358
|
+
for (const [key, coeff] of p.terms) {
|
|
359
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
360
|
+
const e = exp.get(v) ?? 0;
|
|
361
|
+
if (e > 1) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
exp.delete(v);
|
|
365
|
+
const k = (0, MultiPoly_1.expToKey)(exp);
|
|
366
|
+
if (e === 1) {
|
|
367
|
+
A.terms.set(k, (A.terms.get(k) ?? 0n) + coeff);
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
B.terms.set(k, (B.terms.get(k) ?? 0n) + coeff);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
A.trim();
|
|
374
|
+
B.trim();
|
|
375
|
+
if (A.isZero()) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
if (B.isZero()) {
|
|
379
|
+
return [MultiPoly_1.MultiPoly.variable(v), A];
|
|
380
|
+
}
|
|
381
|
+
const aVars = Array.from(A.variables());
|
|
382
|
+
const bVars = Array.from(B.variables());
|
|
383
|
+
const commonVars = aVars.filter(x => bVars.includes(x));
|
|
384
|
+
if (commonVars.length === 0 && A.terms.size === 1 && B.terms.size === 1) {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
const aFactors = factorMultivariate(A);
|
|
388
|
+
for (const f of aFactors) {
|
|
389
|
+
if (f.isConstant()) {
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
const bDivResult = (0, poly_1.mvExactDiv)(B, f);
|
|
393
|
+
if (bDivResult === null) {
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const aDivResult = (0, poly_1.mvExactDiv)(A, f);
|
|
397
|
+
if (aDivResult === null) {
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
const quotient = (0, poly_1.addPoly)((0, poly_1.mulPoly)(aDivResult, MultiPoly_1.MultiPoly.variable(v)), bDivResult);
|
|
401
|
+
if ((0, poly_1.equalPoly)((0, poly_1.mulPoly)(f, quotient), p)) {
|
|
402
|
+
return [f, quotient];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const bFactors = factorMultivariate(B);
|
|
406
|
+
for (const f of bFactors) {
|
|
407
|
+
if (f.isConstant()) {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
const aDivResult = (0, poly_1.mvExactDiv)(A, f);
|
|
411
|
+
if (aDivResult === null) {
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
const bDivResult = (0, poly_1.mvExactDiv)(B, f);
|
|
415
|
+
if (bDivResult === null) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const quotient = (0, poly_1.addPoly)((0, poly_1.mulPoly)(aDivResult, MultiPoly_1.MultiPoly.variable(v)), bDivResult);
|
|
419
|
+
if ((0, poly_1.equalPoly)((0, poly_1.mulPoly)(f, quotient), p)) {
|
|
420
|
+
return [f, quotient];
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Try to factor a polynomial that is binomial in variable v.
|
|
427
|
+
* p = A*v^k + B*v^m with exactly two distinct powers of v and k > m.
|
|
428
|
+
* Factors as f * q if A and B share a non-trivial polynomial factor f.
|
|
429
|
+
*/
|
|
430
|
+
function tryFactorBinomialInVar(p, v) {
|
|
431
|
+
const powerMap = new Map();
|
|
432
|
+
for (const [key, coeff] of p.terms) {
|
|
433
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
434
|
+
const e = exp.get(v) ?? 0;
|
|
435
|
+
exp.delete(v);
|
|
436
|
+
const k = (0, MultiPoly_1.expToKey)(exp);
|
|
437
|
+
let poly = powerMap.get(e);
|
|
438
|
+
if (poly === undefined) {
|
|
439
|
+
poly = new MultiPoly_1.MultiPoly();
|
|
440
|
+
powerMap.set(e, poly);
|
|
441
|
+
}
|
|
442
|
+
poly.terms.set(k, (poly.terms.get(k) ?? 0n) + coeff);
|
|
443
|
+
}
|
|
444
|
+
for (const [e, poly] of powerMap) {
|
|
445
|
+
poly.trim();
|
|
446
|
+
if (poly.isZero()) {
|
|
447
|
+
powerMap.delete(e);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (powerMap.size !== 2) {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
const powers = Array.from(powerMap.entries()).sort(([powerA], [powerB]) => powerA - powerB);
|
|
454
|
+
const [lowPower, B] = powers[0];
|
|
455
|
+
const [highPower, A] = powers[1];
|
|
456
|
+
if (highPower < 2) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
if (A.isZero() || B.isZero()) {
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
const buildVPower = (power) => {
|
|
463
|
+
if (power === 0) {
|
|
464
|
+
return MultiPoly_1.MultiPoly.constant(1n);
|
|
465
|
+
}
|
|
466
|
+
const expMap = new Map();
|
|
467
|
+
expMap.set(v, power);
|
|
468
|
+
const mp = new MultiPoly_1.MultiPoly();
|
|
469
|
+
mp.terms.set((0, MultiPoly_1.expToKey)(expMap), 1n);
|
|
470
|
+
return mp;
|
|
471
|
+
};
|
|
472
|
+
const aFactors = factorMultivariate(A);
|
|
473
|
+
for (const f of aFactors) {
|
|
474
|
+
if (f.isConstant()) {
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
const bDivResult = (0, poly_1.mvExactDiv)(B, f);
|
|
478
|
+
if (bDivResult === null) {
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const aDivResult = (0, poly_1.mvExactDiv)(A, f);
|
|
482
|
+
if (aDivResult === null) {
|
|
483
|
+
continue;
|
|
484
|
+
}
|
|
485
|
+
const quotient = (0, poly_1.addPoly)((0, poly_1.mulPoly)(aDivResult, buildVPower(highPower)), (0, poly_1.mulPoly)(bDivResult, buildVPower(lowPower)));
|
|
486
|
+
if ((0, poly_1.equalPoly)((0, poly_1.mulPoly)(f, quotient), p)) {
|
|
487
|
+
return [f, quotient];
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const bFactors = factorMultivariate(B);
|
|
491
|
+
for (const f of bFactors) {
|
|
492
|
+
if (f.isConstant()) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const aDivResult = (0, poly_1.mvExactDiv)(A, f);
|
|
496
|
+
if (aDivResult === null) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
const bDivResult = (0, poly_1.mvExactDiv)(B, f);
|
|
500
|
+
if (bDivResult === null) {
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
const quotient = (0, poly_1.addPoly)((0, poly_1.mulPoly)(aDivResult, buildVPower(highPower)), (0, poly_1.mulPoly)(bDivResult, buildVPower(lowPower)));
|
|
504
|
+
if ((0, poly_1.equalPoly)((0, poly_1.mulPoly)(f, quotient), p)) {
|
|
505
|
+
return [f, quotient];
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
return null;
|
|
509
|
+
}
|
|
510
|
+
function tryFactorQuadraticInVar(p, v) {
|
|
511
|
+
const { A, B, C } = (0, poly_1.quadraticCoeffsInVar)(p, v);
|
|
512
|
+
if (A.isZero() || A.terms.size === 0) {
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
// First, try the original simple candidate-based approach
|
|
516
|
+
if (A.terms.size === 1) {
|
|
517
|
+
const [[aKey, aCoeff]] = [...A.terms.entries()];
|
|
518
|
+
const aExp = (0, MultiPoly_1.keyToExp)(aKey);
|
|
519
|
+
const candidates = [];
|
|
520
|
+
for (const [bKey, bCoeff] of B.terms) {
|
|
521
|
+
if (aCoeff === 0n) {
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
if (bCoeff % aCoeff !== 0n) {
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const bExp = (0, MultiPoly_1.keyToExp)(bKey);
|
|
528
|
+
let ok = true;
|
|
529
|
+
for (const [vv, ee] of aExp) {
|
|
530
|
+
const be = bExp.get(vv) ?? 0;
|
|
531
|
+
if (be < ee) {
|
|
532
|
+
ok = false;
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
if (!ok) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
// quotient monomial exponent = bExp - aExp
|
|
540
|
+
const qExp = new Map(bExp);
|
|
541
|
+
for (const [vv, ee] of aExp) {
|
|
542
|
+
const be = qExp.get(vv) ?? 0;
|
|
543
|
+
const ne = be - ee;
|
|
544
|
+
if (ne === 0) {
|
|
545
|
+
qExp.delete(vv);
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
qExp.set(vv, ne);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
const q = new MultiPoly_1.MultiPoly();
|
|
552
|
+
q.terms.set((0, MultiPoly_1.expToKey)(qExp), -(bCoeff / aCoeff)); // r = -(term/A)
|
|
553
|
+
q.trim();
|
|
554
|
+
candidates.push(q);
|
|
555
|
+
}
|
|
556
|
+
// If B has no usable terms (e.g. a^2 - b^2), try the special case: B == 0 and -C/A is a perfect-square monomial (with unit coefficient).
|
|
557
|
+
if (candidates.length === 0 && (B.isZero() || B.terms.size === 0) && C.terms.size === 1) {
|
|
558
|
+
const [[cKey, cCoeff]] = [...C.terms.entries()];
|
|
559
|
+
if (aCoeff !== 0n && -cCoeff % aCoeff === 0n) {
|
|
560
|
+
const ratioCoeff = -cCoeff / aCoeff;
|
|
561
|
+
if (ratioCoeff === 1n) {
|
|
562
|
+
const cExp = (0, MultiPoly_1.keyToExp)(cKey);
|
|
563
|
+
let ok = true;
|
|
564
|
+
for (const [vv, ee] of aExp) {
|
|
565
|
+
const ce = cExp.get(vv) ?? 0;
|
|
566
|
+
if (ce < ee) {
|
|
567
|
+
ok = false;
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
if (ok) {
|
|
572
|
+
const qExp = new Map(cExp);
|
|
573
|
+
for (const [vv, ee] of aExp) {
|
|
574
|
+
const ce = qExp.get(vv) ?? 0;
|
|
575
|
+
const ne = ce - ee;
|
|
576
|
+
if (ne === 0) {
|
|
577
|
+
qExp.delete(vv);
|
|
578
|
+
}
|
|
579
|
+
else {
|
|
580
|
+
qExp.set(vv, ne);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
let allEven = true;
|
|
584
|
+
for (const ee of qExp.values()) {
|
|
585
|
+
if (ee % 2 !== 0) {
|
|
586
|
+
allEven = false;
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
if (allEven) {
|
|
591
|
+
const rootExp = new Map();
|
|
592
|
+
for (const [vv, ee] of qExp) {
|
|
593
|
+
rootExp.set(vv, ee / 2);
|
|
594
|
+
}
|
|
595
|
+
const r = new MultiPoly_1.MultiPoly();
|
|
596
|
+
r.terms.set((0, MultiPoly_1.expToKey)(rootExp), 1n);
|
|
597
|
+
r.trim();
|
|
598
|
+
candidates.push(r);
|
|
599
|
+
candidates.push((0, poly_1.scalePoly)(r, -1n));
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
for (const r of candidates) {
|
|
606
|
+
// Check root: A*r^2 + B*r + C == 0
|
|
607
|
+
const r2 = (0, poly_1.mulPoly)(r, r);
|
|
608
|
+
const Ar2 = (0, poly_1.mulPoly)(A, r2);
|
|
609
|
+
const Br = (0, poly_1.mulPoly)(B, r);
|
|
610
|
+
const sum = (0, poly_1.addPoly)((0, poly_1.addPoly)(Ar2, Br), C);
|
|
611
|
+
if (!sum.isZero()) {
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
// Build factors: (v - r) * (A*v + D), with D = B + A*r
|
|
615
|
+
const D = (0, poly_1.addPoly)(B, (0, poly_1.mulPoly)(A, r));
|
|
616
|
+
const vPoly = MultiPoly_1.MultiPoly.variable(v);
|
|
617
|
+
const f1 = (0, poly_1.subPoly)(vPoly, r);
|
|
618
|
+
const f2 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(A, vPoly), D);
|
|
619
|
+
// Verify exactness: f1*f2 == p
|
|
620
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
621
|
+
const diff = (0, poly_1.subPoly)(prod, p);
|
|
622
|
+
if (diff.isZero()) {
|
|
623
|
+
return [f1, f2];
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
// Fall back to discriminant-based approach for more general cases
|
|
628
|
+
// Compute discriminant D = B² - 4AC
|
|
629
|
+
const B2 = (0, poly_1.mulPoly)(B, B);
|
|
630
|
+
const AC = (0, poly_1.mulPoly)(A, C);
|
|
631
|
+
const AC4 = (0, poly_1.mulPoly)(AC, MultiPoly_1.MultiPoly.constant(4n));
|
|
632
|
+
const disc = (0, poly_1.subPoly)(B2, AC4);
|
|
633
|
+
if (disc.isZero()) {
|
|
634
|
+
// Perfect square case: D = 0, double root at r = -B/(2A).
|
|
635
|
+
// This path is intentionally skipped here; the square-root route below is the active implementation.
|
|
636
|
+
}
|
|
637
|
+
// Try to find if disc is a perfect square polynomial
|
|
638
|
+
const sqrtDisc = tryPolynomialSquareRoot(disc);
|
|
639
|
+
if (sqrtDisc !== null) {
|
|
640
|
+
// roots are: (-B ± sqrtDisc) / (2A)
|
|
641
|
+
// For integer polynomials, we need -B + sqrtDisc and -B - sqrtDisc to be divisible by 2A
|
|
642
|
+
const negB = (0, poly_1.negPoly)(B);
|
|
643
|
+
const num1 = (0, poly_1.addPoly)(negB, sqrtDisc);
|
|
644
|
+
const num2 = (0, poly_1.subPoly)(negB, sqrtDisc);
|
|
645
|
+
const twoA = (0, poly_1.mulPoly)(A, MultiPoly_1.MultiPoly.constant(2n));
|
|
646
|
+
// Try to divide num1 by 2A to get root1
|
|
647
|
+
const root1 = (0, poly_1.mvExactDiv)(num1, twoA);
|
|
648
|
+
const root2 = (0, poly_1.mvExactDiv)(num2, twoA);
|
|
649
|
+
// We only need ONE integer root to factor
|
|
650
|
+
// If root1 is integer, factor is (v - root1) * (A*v + B + A*root1)
|
|
651
|
+
// If root2 is integer, factor is (v - root2) * (A*v + B + A*root2)
|
|
652
|
+
for (const root of [root1, root2]) {
|
|
653
|
+
if (root === null) {
|
|
654
|
+
continue;
|
|
655
|
+
}
|
|
656
|
+
const vPoly = MultiPoly_1.MultiPoly.variable(v);
|
|
657
|
+
const f1 = (0, poly_1.subPoly)(vPoly, root);
|
|
658
|
+
// f2 = A*v + (B + A*root)
|
|
659
|
+
const D = (0, poly_1.addPoly)(B, (0, poly_1.mulPoly)(A, root));
|
|
660
|
+
const f2 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(A, vPoly), D);
|
|
661
|
+
// Verify: f1 * f2 should equal p
|
|
662
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
663
|
+
const diff = (0, poly_1.subPoly)(prod, p);
|
|
664
|
+
if (diff.isZero()) {
|
|
665
|
+
return [f1, f2];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
// Quadratic formula didn't give polynomial roots.
|
|
670
|
+
// Try factoring A and searching for factors of form (a*v + b) where a divides A.
|
|
671
|
+
// If p = A*v² + B*v + C and A = a1*a2, we look for:
|
|
672
|
+
// f1 = a1*v + b1, f2 = a2*v + b2
|
|
673
|
+
// such that f1*f2 = a1*a2*v² + (a1*b2 + a2*b1)*v + b1*b2 = A*v² + B*v + C
|
|
674
|
+
// So: a1*b2 + a2*b1 = B, b1*b2 = C
|
|
675
|
+
{
|
|
676
|
+
const aFactors = decomposeMonomialFactors(factorMultivariate(A));
|
|
677
|
+
if (aFactors.length >= 2) {
|
|
678
|
+
// Try all ways to split A into two factors
|
|
679
|
+
const n = aFactors.length;
|
|
680
|
+
for (let mask = 1; mask < (1 << n) - 1; mask++) {
|
|
681
|
+
let a1 = MultiPoly_1.MultiPoly.constant(1n);
|
|
682
|
+
let a2 = MultiPoly_1.MultiPoly.constant(1n);
|
|
683
|
+
for (let i = 0; i < n; i++) {
|
|
684
|
+
if (mask & (1 << i)) {
|
|
685
|
+
a1 = (0, poly_1.mulPoly)(a1, aFactors[i]);
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
a2 = (0, poly_1.mulPoly)(a2, aFactors[i]);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
// Now we need to find b1, b2 such that:
|
|
692
|
+
// a1*b2 + a2*b1 = B ... (1)
|
|
693
|
+
// b1*b2 = C ... (2)
|
|
694
|
+
// If C = 0, then b1 = 0 or b2 = 0
|
|
695
|
+
if (C.isZero()) {
|
|
696
|
+
// Try b1 = 0: then a1*b2 = B, so b2 = B/a1
|
|
697
|
+
const b2 = (0, poly_1.mvExactDiv)(B, a1);
|
|
698
|
+
if (b2 !== null) {
|
|
699
|
+
const f1 = (0, poly_1.mulPoly)(a1, MultiPoly_1.MultiPoly.variable(v)); // a1*v
|
|
700
|
+
const f2 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a2, MultiPoly_1.MultiPoly.variable(v)), b2);
|
|
701
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
702
|
+
if ((0, poly_1.equalPoly)(prod, p)) {
|
|
703
|
+
return [f1, f2];
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
// Try b2 = 0
|
|
707
|
+
const b1 = (0, poly_1.mvExactDiv)(B, a2);
|
|
708
|
+
if (b1 !== null) {
|
|
709
|
+
const f1 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, MultiPoly_1.MultiPoly.variable(v)), b1);
|
|
710
|
+
const f2 = (0, poly_1.mulPoly)(a2, MultiPoly_1.MultiPoly.variable(v));
|
|
711
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
712
|
+
if ((0, poly_1.equalPoly)(prod, p)) {
|
|
713
|
+
return [f1, f2];
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
// Factor C and try to match
|
|
719
|
+
// Need to decompose monomials into individual variable powers
|
|
720
|
+
const cFactors = decomposeMonomialFactors(factorMultivariate(C));
|
|
721
|
+
const cLen = cFactors.length;
|
|
722
|
+
// If cLen is too large, limit combinations to avoid exponential blowup
|
|
723
|
+
if (cLen > MAX_CONSTANT_FACTOR_SPLIT_COUNT) {
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
// Try all ways to split C factors into b1 and b2
|
|
727
|
+
for (let cMask = 0; cMask < 1 << cLen; cMask++) {
|
|
728
|
+
let b1 = MultiPoly_1.MultiPoly.constant(1n);
|
|
729
|
+
let b2 = MultiPoly_1.MultiPoly.constant(1n);
|
|
730
|
+
for (let i = 0; i < cLen; i++) {
|
|
731
|
+
if (cMask & (1 << i)) {
|
|
732
|
+
b1 = (0, poly_1.mulPoly)(b1, cFactors[i]);
|
|
733
|
+
}
|
|
734
|
+
else {
|
|
735
|
+
b2 = (0, poly_1.mulPoly)(b2, cFactors[i]);
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
// Check if a1*b2 + a2*b1 = B
|
|
739
|
+
const sum = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, b2), (0, poly_1.mulPoly)(a2, b1));
|
|
740
|
+
if ((0, poly_1.equalPoly)(sum, B)) {
|
|
741
|
+
const vPoly = MultiPoly_1.MultiPoly.variable(v);
|
|
742
|
+
const f1 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, vPoly), b1);
|
|
743
|
+
const f2 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a2, vPoly), b2);
|
|
744
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
745
|
+
if ((0, poly_1.equalPoly)(prod, p)) {
|
|
746
|
+
return [f1, f2];
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
// Also try with negated b1, b2 combinations
|
|
750
|
+
const negB1 = (0, poly_1.negPoly)(b1);
|
|
751
|
+
const negB2 = (0, poly_1.negPoly)(b2);
|
|
752
|
+
for (const [bb1, bb2] of [
|
|
753
|
+
[b1, negB2],
|
|
754
|
+
[negB1, b2],
|
|
755
|
+
[negB1, negB2],
|
|
756
|
+
]) {
|
|
757
|
+
const checkSum = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, bb2), (0, poly_1.mulPoly)(a2, bb1));
|
|
758
|
+
if ((0, poly_1.equalPoly)(checkSum, B)) {
|
|
759
|
+
const vPoly = MultiPoly_1.MultiPoly.variable(v);
|
|
760
|
+
const ff1 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, vPoly), bb1);
|
|
761
|
+
const ff2 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a2, vPoly), bb2);
|
|
762
|
+
const prodCheck = (0, poly_1.mulPoly)(ff1, ff2);
|
|
763
|
+
if ((0, poly_1.equalPoly)(prodCheck, p)) {
|
|
764
|
+
return [ff1, ff2];
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// Special case: try b1 = ±1, b2 = ±C (even if C doesn't factor)
|
|
770
|
+
// This handles cases like (ax - 1)(ax^3 + ...)
|
|
771
|
+
for (const b1Sign of [1n, -1n]) {
|
|
772
|
+
const b1 = MultiPoly_1.MultiPoly.constant(b1Sign);
|
|
773
|
+
const b2 = b1Sign === 1n ? C : (0, poly_1.negPoly)(C);
|
|
774
|
+
// Check if a1*b2 + a2*b1 = B
|
|
775
|
+
const sum = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, b2), (0, poly_1.mulPoly)(a2, b1));
|
|
776
|
+
if ((0, poly_1.equalPoly)(sum, B)) {
|
|
777
|
+
const vPoly = MultiPoly_1.MultiPoly.variable(v);
|
|
778
|
+
const f1 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, vPoly), b1);
|
|
779
|
+
const f2 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a2, vPoly), b2);
|
|
780
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
781
|
+
if ((0, poly_1.equalPoly)(prod, p)) {
|
|
782
|
+
return [f1, f2];
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
// Also try swapping: b1 = ±C, b2 = ±1
|
|
786
|
+
const b1Swap = b1Sign === 1n ? C : (0, poly_1.negPoly)(C);
|
|
787
|
+
const b2Swap = MultiPoly_1.MultiPoly.constant(b1Sign);
|
|
788
|
+
const sumSwap = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, b2Swap), (0, poly_1.mulPoly)(a2, b1Swap));
|
|
789
|
+
if ((0, poly_1.equalPoly)(sumSwap, B)) {
|
|
790
|
+
const vPoly = MultiPoly_1.MultiPoly.variable(v);
|
|
791
|
+
const f1 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a1, vPoly), b1Swap);
|
|
792
|
+
const f2 = (0, poly_1.addPoly)((0, poly_1.mulPoly)(a2, vPoly), b2Swap);
|
|
793
|
+
const prod = (0, poly_1.mulPoly)(f1, f2);
|
|
794
|
+
if ((0, poly_1.equalPoly)(prod, p)) {
|
|
795
|
+
return [f1, f2];
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return null;
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Decompose monomial factors into individual variable powers.
|
|
806
|
+
* E.g., [x^4, y^2] becomes [x, x, x, x, y, y]
|
|
807
|
+
*/
|
|
808
|
+
function decomposeMonomialFactors(factors) {
|
|
809
|
+
const result = [];
|
|
810
|
+
for (const f of factors) {
|
|
811
|
+
if (f.terms.size === 1) {
|
|
812
|
+
// It's a monomial - decompose it
|
|
813
|
+
const entry = f.terms.entries().next();
|
|
814
|
+
if (entry.done) {
|
|
815
|
+
continue;
|
|
816
|
+
}
|
|
817
|
+
const [key, coeff] = entry.value;
|
|
818
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
819
|
+
// Handle coefficient if it's not ±1
|
|
820
|
+
if (coeff !== 1n && coeff !== -1n) {
|
|
821
|
+
result.push(MultiPoly_1.MultiPoly.constant(coeff));
|
|
822
|
+
}
|
|
823
|
+
// Add individual variable powers
|
|
824
|
+
for (const [varIdx, power] of exp) {
|
|
825
|
+
for (let i = 0; i < power; i++) {
|
|
826
|
+
result.push(MultiPoly_1.MultiPoly.variable(varIdx));
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
// Not a monomial - keep as is
|
|
832
|
+
result.push(f);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return result;
|
|
836
|
+
}
|
|
837
|
+
function tryPolynomialSquareRoot(p) {
|
|
838
|
+
if (p.isZero()) {
|
|
839
|
+
return MultiPoly_1.MultiPoly.zero();
|
|
840
|
+
}
|
|
841
|
+
if (p.isConstant()) {
|
|
842
|
+
const c = p.constantTerm();
|
|
843
|
+
const sqrt = (0, arith_1.intNthRootExact)(c, 2);
|
|
844
|
+
return sqrt !== null ? MultiPoly_1.MultiPoly.constant(sqrt) : null;
|
|
845
|
+
}
|
|
846
|
+
// Get all terms sorted by total degree descending
|
|
847
|
+
const terms = [];
|
|
848
|
+
for (const [key, coeff] of p.terms) {
|
|
849
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
850
|
+
let totalDeg = 0;
|
|
851
|
+
for (const e of exp.values()) {
|
|
852
|
+
totalDeg += e;
|
|
853
|
+
}
|
|
854
|
+
terms.push({ key, exp, coeff, totalDeg });
|
|
855
|
+
}
|
|
856
|
+
terms.sort((a, b) => b.totalDeg - a.totalDeg);
|
|
857
|
+
// Leading term must have even exponents and perfect square coefficient
|
|
858
|
+
const lead = terms[0];
|
|
859
|
+
const leadSqrt = (0, arith_1.intNthRootExact)(lead.coeff, 2);
|
|
860
|
+
if (leadSqrt === null) {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
for (const [, e] of lead.exp) {
|
|
864
|
+
if (e % 2 !== 0) {
|
|
865
|
+
return null;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
// Build sqrt of leading term
|
|
869
|
+
const sqrtLeadExp = new Map();
|
|
870
|
+
for (const [v, e] of lead.exp) {
|
|
871
|
+
sqrtLeadExp.set(v, e / 2);
|
|
872
|
+
}
|
|
873
|
+
// Initialize sqrt polynomial with leading term
|
|
874
|
+
const sqrtP = new MultiPoly_1.MultiPoly();
|
|
875
|
+
sqrtP.terms.set((0, MultiPoly_1.expToKey)(sqrtLeadExp), leadSqrt);
|
|
876
|
+
// Use Hensel-style lifting to find remaining terms
|
|
877
|
+
// This is a heuristic approach: try to build sqrt term by term
|
|
878
|
+
let remaining = (0, poly_1.subPoly)(p, (0, poly_1.mulPoly)(sqrtP, sqrtP));
|
|
879
|
+
const maxIterations = terms.length * 2;
|
|
880
|
+
for (let iter = 0; iter < maxIterations && !remaining.isZero(); iter++) {
|
|
881
|
+
// Find leading term of remaining
|
|
882
|
+
let maxDeg = -1;
|
|
883
|
+
let maxKey = '';
|
|
884
|
+
let maxCoeff = 0n;
|
|
885
|
+
for (const [key, coeff] of remaining.terms) {
|
|
886
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
887
|
+
let deg = 0;
|
|
888
|
+
for (const e of exp.values()) {
|
|
889
|
+
deg += e;
|
|
890
|
+
}
|
|
891
|
+
if (deg > maxDeg) {
|
|
892
|
+
maxDeg = deg;
|
|
893
|
+
maxKey = key;
|
|
894
|
+
maxCoeff = coeff;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
if (maxDeg < 0) {
|
|
898
|
+
break;
|
|
899
|
+
} // No more terms
|
|
900
|
+
// The next term in sqrt should satisfy: 2 * leadSqrt * newTerm = maxCoeff * monomial
|
|
901
|
+
// newTerm = maxCoeff / (2 * leadSqrt) * monomial with adjusted exponents
|
|
902
|
+
if (2n * leadSqrt === 0n) {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
905
|
+
if (maxCoeff % (2n * leadSqrt) !== 0n) {
|
|
906
|
+
return null;
|
|
907
|
+
}
|
|
908
|
+
const newCoeff = maxCoeff / (2n * leadSqrt);
|
|
909
|
+
// New term exponents = maxExp - sqrtLeadExp
|
|
910
|
+
const maxExp = (0, MultiPoly_1.keyToExp)(maxKey);
|
|
911
|
+
const newExp = new Map();
|
|
912
|
+
for (const [v, e] of maxExp) {
|
|
913
|
+
const le = sqrtLeadExp.get(v) ?? 0;
|
|
914
|
+
if (e < le) {
|
|
915
|
+
return null;
|
|
916
|
+
} // Can't have negative exponents
|
|
917
|
+
const ne = e - le;
|
|
918
|
+
if (ne > 0) {
|
|
919
|
+
newExp.set(v, ne);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
sqrtP.terms.set((0, MultiPoly_1.expToKey)(newExp), newCoeff);
|
|
923
|
+
remaining = (0, poly_1.subPoly)(p, (0, poly_1.mulPoly)(sqrtP, sqrtP));
|
|
924
|
+
}
|
|
925
|
+
if (remaining.isZero()) {
|
|
926
|
+
return sqrtP;
|
|
927
|
+
}
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
930
|
+
/**
|
|
931
|
+
* Try to recognize a binomial power: (α*m1 + β*m2)^n where m1,m2 are monomials.
|
|
932
|
+
* Returns the base polynomial repeated n times, or null.
|
|
933
|
+
*
|
|
934
|
+
* This is designed to catch cases like (a^2 + b^2)^3.
|
|
935
|
+
*/
|
|
936
|
+
function tryBinomialPower(f) {
|
|
937
|
+
const termCount = f.terms.size;
|
|
938
|
+
if (termCount < 3) {
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
// Candidate n from termCount: binomial expansion has exactly n+1 terms (if no cancellation).
|
|
942
|
+
const n = termCount - 1;
|
|
943
|
+
if (n < 2) {
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
const terms = [];
|
|
947
|
+
let maxVar = -1;
|
|
948
|
+
for (const [key, coeff] of f.terms) {
|
|
949
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
950
|
+
for (const [vi] of exp) {
|
|
951
|
+
if (vi > maxVar) {
|
|
952
|
+
maxVar = vi;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
terms.push({ key, exp, coeff });
|
|
956
|
+
}
|
|
957
|
+
const len = Math.max(1, maxVar + 1);
|
|
958
|
+
// Define an ordering to pick "smallest" and "largest" exponent vectors deterministically.
|
|
959
|
+
function expToDense(e) {
|
|
960
|
+
const arr = new Array(len).fill(0);
|
|
961
|
+
for (const [i, v] of e) {
|
|
962
|
+
arr[i] = v;
|
|
963
|
+
}
|
|
964
|
+
return arr;
|
|
965
|
+
}
|
|
966
|
+
function cmpExp(a, b) {
|
|
967
|
+
for (let i = 0; i < a.length; i++) {
|
|
968
|
+
if (a[i] !== b[i]) {
|
|
969
|
+
return a[i] - b[i];
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return 0;
|
|
973
|
+
}
|
|
974
|
+
const dense = terms.map(t => ({ ...t, dense: expToDense(t.exp) }));
|
|
975
|
+
dense.sort((p, q) => cmpExp(p.dense, q.dense));
|
|
976
|
+
const t0 = dense[0];
|
|
977
|
+
const tn = dense[dense.length - 1];
|
|
978
|
+
// Endpoints must be divisible by n (since they are (β*m2)^n and (α*m1)^n).
|
|
979
|
+
function divisibleByN(arr) {
|
|
980
|
+
for (const v of arr) {
|
|
981
|
+
if (v % n !== 0) {
|
|
982
|
+
return false;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
return true;
|
|
986
|
+
}
|
|
987
|
+
if (!divisibleByN(t0.dense) || !divisibleByN(tn.dense)) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
const v = t0.dense.map(e => e / n); // monomial m2 exponents
|
|
991
|
+
const u = tn.dense.map(e => e / n); // monomial m1 exponents
|
|
992
|
+
const step = u.map((ui, i) => ui - v[i]);
|
|
993
|
+
// Build expected exponents for i=0..n and map them to coefficients
|
|
994
|
+
const expected = new Map();
|
|
995
|
+
for (let i = 0; i <= n; i++) {
|
|
996
|
+
const ei = v.map((vi, k) => n * vi + i * step[k]);
|
|
997
|
+
const expMap = new Map();
|
|
998
|
+
for (let k = 0; k < ei.length; k++) {
|
|
999
|
+
if (ei[k] !== 0) {
|
|
1000
|
+
expMap.set(k, ei[k]);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
expected.set((0, MultiPoly_1.expToKey)(expMap), 0n);
|
|
1004
|
+
}
|
|
1005
|
+
// f must have exactly these terms (no extras)
|
|
1006
|
+
for (const t of terms) {
|
|
1007
|
+
if (!expected.has(t.key)) {
|
|
1008
|
+
return null;
|
|
1009
|
+
}
|
|
1010
|
+
expected.set(t.key, t.coeff);
|
|
1011
|
+
}
|
|
1012
|
+
for (const [, c] of expected) {
|
|
1013
|
+
if (c === 0n) {
|
|
1014
|
+
return null;
|
|
1015
|
+
} // missing a required term
|
|
1016
|
+
}
|
|
1017
|
+
// Determine α and β from endpoint coefficients (must be exact nth powers)
|
|
1018
|
+
const beta = (0, arith_1.intNthRootExact)(t0.coeff, n);
|
|
1019
|
+
const alpha = (0, arith_1.intNthRootExact)(tn.coeff, n);
|
|
1020
|
+
if (alpha === null || beta === null) {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
// Verify all coefficients match binomial(n,i)*alpha^i*beta^(n-i)
|
|
1024
|
+
function binom(n_, k_) {
|
|
1025
|
+
let k = k_;
|
|
1026
|
+
if (k < 0 || k > n_) {
|
|
1027
|
+
return 0n;
|
|
1028
|
+
}
|
|
1029
|
+
k = Math.min(k, n_ - k);
|
|
1030
|
+
let num = 1n;
|
|
1031
|
+
let den = 1n;
|
|
1032
|
+
for (let i = 1; i <= k; i++) {
|
|
1033
|
+
num *= BigInt(n_ - (k - i));
|
|
1034
|
+
den *= BigInt(i);
|
|
1035
|
+
}
|
|
1036
|
+
return num / den;
|
|
1037
|
+
}
|
|
1038
|
+
for (let i = 0; i <= n; i++) {
|
|
1039
|
+
const expKey = (() => {
|
|
1040
|
+
const ei = v.map((vi, k) => n * vi + i * step[k]);
|
|
1041
|
+
const expMap = new Map();
|
|
1042
|
+
for (let k = 0; k < ei.length; k++) {
|
|
1043
|
+
if (ei[k] !== 0) {
|
|
1044
|
+
expMap.set(k, ei[k]);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return (0, MultiPoly_1.expToKey)(expMap);
|
|
1048
|
+
})();
|
|
1049
|
+
const ci = expected.get(expKey);
|
|
1050
|
+
const should = binom(n, i) * (0, arith_1.powN)(alpha, i) * (0, arith_1.powN)(beta, n - i);
|
|
1051
|
+
if (ci !== should) {
|
|
1052
|
+
return null;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
const m1 = MultiPoly_1.MultiPoly.monomial(alpha, u);
|
|
1056
|
+
const m2 = MultiPoly_1.MultiPoly.monomial(beta, v);
|
|
1057
|
+
const base = (0, poly_1.addPoly)(m1, m2);
|
|
1058
|
+
return { base, n };
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* Try to factor a univariate polynomial using the rational root theorem.
|
|
1062
|
+
* Returns array of factors if successful, or [p] if no factorization found.
|
|
1063
|
+
*/
|
|
1064
|
+
function tryRationalRootFactorization(p, varIndex) {
|
|
1065
|
+
const deg = p.degree(varIndex);
|
|
1066
|
+
if (deg <= 1) {
|
|
1067
|
+
return [p];
|
|
1068
|
+
}
|
|
1069
|
+
// Get coefficients
|
|
1070
|
+
const coeffs = new Array(deg + 1).fill(0n);
|
|
1071
|
+
for (const [key, coeff] of p.terms) {
|
|
1072
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
1073
|
+
const d = exp.get(varIndex) ?? 0;
|
|
1074
|
+
coeffs[d] = coeff;
|
|
1075
|
+
}
|
|
1076
|
+
// Rational root theorem: possible roots are ±(divisors of constant term)/(divisors of leading coeff)
|
|
1077
|
+
const constTerm = coeffs[0];
|
|
1078
|
+
const leadCoeff = coeffs[deg];
|
|
1079
|
+
if (constTerm === 0n) {
|
|
1080
|
+
// x is a factor
|
|
1081
|
+
const xPoly = MultiPoly_1.MultiPoly.variable(varIndex);
|
|
1082
|
+
const [quot, rem] = (0, poly_1.pseudoDivide)(p, xPoly, varIndex);
|
|
1083
|
+
if (rem.isZero()) {
|
|
1084
|
+
const quotFactors = tryRationalRootFactorization(quot, varIndex);
|
|
1085
|
+
return [xPoly, ...quotFactors];
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
// Get divisors of |constTerm| and |leadCoeff|
|
|
1089
|
+
const constDivisors = (0, arith_1.getDivisors)(constTerm < 0n ? -constTerm : constTerm);
|
|
1090
|
+
const leadDivisors = (0, arith_1.getDivisors)(leadCoeff < 0n ? -leadCoeff : leadCoeff);
|
|
1091
|
+
// Try all possible rational roots p/q
|
|
1092
|
+
for (const p_val of constDivisors) {
|
|
1093
|
+
for (const q_val of leadDivisors) {
|
|
1094
|
+
// Try +p/q and -p/q
|
|
1095
|
+
for (const sign of [1n, -1n]) {
|
|
1096
|
+
const numer = sign * p_val;
|
|
1097
|
+
// Check if numer/q_val is a root: evaluate polynomial at x = numer/q_val
|
|
1098
|
+
// To avoid fractions, compute q^deg * f(numer/q) which equals:
|
|
1099
|
+
// sum_{i=0}^{deg} coeffs[i] * numer^i * q^(deg-i)
|
|
1100
|
+
let evalResult = 0n;
|
|
1101
|
+
const powers = [1n];
|
|
1102
|
+
for (let i = 1; i <= deg; i++) {
|
|
1103
|
+
powers.push(powers[i - 1] * numer);
|
|
1104
|
+
}
|
|
1105
|
+
for (let i = 0; i <= deg; i++) {
|
|
1106
|
+
const qExp = deg - i;
|
|
1107
|
+
let qp = 1n;
|
|
1108
|
+
for (let j = 0; j < qExp; j++) {
|
|
1109
|
+
qp *= q_val;
|
|
1110
|
+
}
|
|
1111
|
+
evalResult += coeffs[i] * powers[i] * qp;
|
|
1112
|
+
}
|
|
1113
|
+
if (evalResult === 0n) {
|
|
1114
|
+
// Found a root! Build factor (q*x - numer) = q*x - p*sign
|
|
1115
|
+
// Actually the factor is (x - numer/q_val), but we want integer coefficients
|
|
1116
|
+
// So factor is (q_val * x - numer)
|
|
1117
|
+
const factor = (0, poly_1.addPoly)((0, poly_1.mulPoly)(MultiPoly_1.MultiPoly.constant(q_val), MultiPoly_1.MultiPoly.variable(varIndex)), MultiPoly_1.MultiPoly.constant(-numer));
|
|
1118
|
+
// Divide p by factor
|
|
1119
|
+
const [quot, rem] = (0, poly_1.pseudoDivide)(p, factor, varIndex);
|
|
1120
|
+
if (rem.isZero()) {
|
|
1121
|
+
// Make quot primitive
|
|
1122
|
+
const [_, primQuot] = makePrimitiveZ(quot);
|
|
1123
|
+
const quotFactors = tryRationalRootFactorization(primQuot, varIndex);
|
|
1124
|
+
return [factor, ...quotFactors];
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
return [p];
|
|
1131
|
+
}
|
|
1132
|
+
// NOTE: getDivisors/bigIntSqrt live in core.ts (shared number theory helpers).
|
|
1133
|
+
/**
|
|
1134
|
+
* Fast-path: extract a monomial gcd factor across all terms (e.g. x^2*y + x^2 => x^2*(y+1)).
|
|
1135
|
+
* Returns a factor list (already recursively factored) or null if not applicable.
|
|
1136
|
+
*/
|
|
1137
|
+
function tryFactorMonomialGcdFast(p) {
|
|
1138
|
+
const mg = monomialGCD(p);
|
|
1139
|
+
if ((0, poly_1.isOnePoly)(mg)) {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
const q = (0, poly_1.mvExactDivByMonomial)(p, mg);
|
|
1143
|
+
if (!q) {
|
|
1144
|
+
return null;
|
|
1145
|
+
}
|
|
1146
|
+
return [mg, ...factorMultivariate(q)];
|
|
1147
|
+
}
|
|
1148
|
+
/** Fast-path: detect if p is a perfect square polynomial and return duplicated factors. */
|
|
1149
|
+
function tryFactorPerfectSquareFast(p) {
|
|
1150
|
+
// Avoid trivial recursion
|
|
1151
|
+
if (p.isZero() || p.isConstant()) {
|
|
1152
|
+
return null;
|
|
1153
|
+
}
|
|
1154
|
+
const sqrt = tryPolynomialSquareRoot(p);
|
|
1155
|
+
if (sqrt === null) {
|
|
1156
|
+
return null;
|
|
1157
|
+
}
|
|
1158
|
+
// Verify exact square
|
|
1159
|
+
if (!(0, poly_1.equalPoly)((0, poly_1.mulPoly)(sqrt, sqrt), p)) {
|
|
1160
|
+
return null;
|
|
1161
|
+
}
|
|
1162
|
+
// Fully factor the square root, then duplicate factors
|
|
1163
|
+
const fs = factorMultivariate(sqrt);
|
|
1164
|
+
return [...fs, ...fs];
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Fast-path: detect binomial perfect powers like (a^2 + b^2)^3.
|
|
1168
|
+
* Returns fully expanded factors (base factors repeated n times) or null.
|
|
1169
|
+
*/
|
|
1170
|
+
function buildLinearInTwoVars(pX, pY, xVar, yVar) {
|
|
1171
|
+
const out = new MultiPoly_1.MultiPoly();
|
|
1172
|
+
if (pX !== 0n) {
|
|
1173
|
+
out.terms.set((0, MultiPoly_1.expToKey)(new Map([[xVar, 1]])), pX);
|
|
1174
|
+
}
|
|
1175
|
+
if (pY !== 0n) {
|
|
1176
|
+
out.terms.set((0, MultiPoly_1.expToKey)(new Map([[yVar, 1]])), (out.terms.get((0, MultiPoly_1.expToKey)(new Map([[yVar, 1]]))) ?? 0n) + pY);
|
|
1177
|
+
}
|
|
1178
|
+
out.trim();
|
|
1179
|
+
return out;
|
|
1180
|
+
}
|
|
1181
|
+
function tryFactorHomogeneousQuadratic2VarsFast(p) {
|
|
1182
|
+
const vars = (0, poly_1.mvVariablesSorted)(p);
|
|
1183
|
+
if (vars.length !== 2) {
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
const [xVar, yVar] = vars;
|
|
1187
|
+
// Require only terms x^2, x*y, y^2 (homogeneous degree 2 in these vars).
|
|
1188
|
+
let a = 0n, b = 0n, c = 0n;
|
|
1189
|
+
for (const [key, coeff] of p.terms) {
|
|
1190
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
1191
|
+
const ex = exp.get(xVar) ?? 0;
|
|
1192
|
+
const ey = exp.get(yVar) ?? 0;
|
|
1193
|
+
// no other vars
|
|
1194
|
+
exp.delete(xVar);
|
|
1195
|
+
exp.delete(yVar);
|
|
1196
|
+
if (exp.size !== 0) {
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
if (ex + ey !== 2) {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
if (ex === 2 && ey === 0) {
|
|
1203
|
+
a += coeff;
|
|
1204
|
+
}
|
|
1205
|
+
else if (ex === 1 && ey === 1) {
|
|
1206
|
+
b += coeff;
|
|
1207
|
+
}
|
|
1208
|
+
else if (ex === 0 && ey === 2) {
|
|
1209
|
+
c += coeff;
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
return null;
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
if (a === 0n || c === 0n) {
|
|
1216
|
+
return null;
|
|
1217
|
+
}
|
|
1218
|
+
const divA = (0, arith_1.getDivisors)((0, arith_1.abs)(a));
|
|
1219
|
+
const divC = (0, arith_1.getDivisors)((0, arith_1.abs)(c));
|
|
1220
|
+
function withSigns(d) {
|
|
1221
|
+
if (d === 0n) {
|
|
1222
|
+
return [0n];
|
|
1223
|
+
}
|
|
1224
|
+
return d === 0n ? [0n] : [d, -d];
|
|
1225
|
+
}
|
|
1226
|
+
for (const pAbs of divA) {
|
|
1227
|
+
for (const pX of withSigns(pAbs)) {
|
|
1228
|
+
if (pX === 0n) {
|
|
1229
|
+
continue;
|
|
1230
|
+
}
|
|
1231
|
+
if (a % pX !== 0n) {
|
|
1232
|
+
continue;
|
|
1233
|
+
}
|
|
1234
|
+
const rX = a / pX;
|
|
1235
|
+
for (const qAbs of divC) {
|
|
1236
|
+
for (const pY of withSigns(qAbs)) {
|
|
1237
|
+
if (pY === 0n) {
|
|
1238
|
+
continue;
|
|
1239
|
+
}
|
|
1240
|
+
if (c % pY !== 0n) {
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
const rY = c / pY;
|
|
1244
|
+
if (pX * rY + pY * rX !== b) {
|
|
1245
|
+
continue;
|
|
1246
|
+
}
|
|
1247
|
+
const f1 = buildLinearInTwoVars(pX, pY, xVar, yVar);
|
|
1248
|
+
const f2 = buildLinearInTwoVars(rX, rY, xVar, yVar);
|
|
1249
|
+
if (f1.isConstant() || f2.isConstant()) {
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
if ((0, poly_1.equalPoly)((0, poly_1.mulPoly)(f1, f2), p)) {
|
|
1253
|
+
return [f1, f2];
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
return null;
|
|
1260
|
+
}
|
|
1261
|
+
function tryFactorPolynomialInMonomialFast(p) {
|
|
1262
|
+
const vars = (0, poly_1.mvVariablesSorted)(p);
|
|
1263
|
+
if (vars.length < 2) {
|
|
1264
|
+
return null;
|
|
1265
|
+
}
|
|
1266
|
+
// Choose base exponent vector from the minimal positive exponents across non-constant terms.
|
|
1267
|
+
let base = null;
|
|
1268
|
+
for (const key of p.terms.keys()) {
|
|
1269
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
1270
|
+
if (exp.size === 0) {
|
|
1271
|
+
continue;
|
|
1272
|
+
}
|
|
1273
|
+
if (base === null) {
|
|
1274
|
+
base = new Map(exp);
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
for (const [v, e] of base) {
|
|
1278
|
+
const ee = exp.get(v) ?? 0;
|
|
1279
|
+
if (ee === 0) {
|
|
1280
|
+
base.delete(v);
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
base.set(v, Math.min(e, ee));
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
if (!base || base.size < 2) {
|
|
1287
|
+
return null;
|
|
1288
|
+
}
|
|
1289
|
+
for (const e of base.values()) {
|
|
1290
|
+
if (e <= 0) {
|
|
1291
|
+
return null;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
let maxK = 0;
|
|
1295
|
+
const coeffs = [];
|
|
1296
|
+
for (const [key, coeff] of p.terms) {
|
|
1297
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
1298
|
+
// constant term
|
|
1299
|
+
if (exp.size === 0) {
|
|
1300
|
+
coeffs[0] = (coeffs[0] ?? 0n) + coeff;
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
// Determine k from first base var.
|
|
1304
|
+
let k = null;
|
|
1305
|
+
for (const [v, be] of base) {
|
|
1306
|
+
const ee = exp.get(v) ?? 0;
|
|
1307
|
+
if (ee % be !== 0) {
|
|
1308
|
+
return null;
|
|
1309
|
+
}
|
|
1310
|
+
const kk = ee / be;
|
|
1311
|
+
if (k === null) {
|
|
1312
|
+
k = kk;
|
|
1313
|
+
}
|
|
1314
|
+
else if (k !== kk) {
|
|
1315
|
+
return null;
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
// No extra variables allowed.
|
|
1319
|
+
for (const [v, ee] of exp) {
|
|
1320
|
+
const be = base.get(v);
|
|
1321
|
+
if (be === undefined) {
|
|
1322
|
+
if (ee !== 0) {
|
|
1323
|
+
return null;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
// already checked proportionality
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (k === null) {
|
|
1331
|
+
return null;
|
|
1332
|
+
}
|
|
1333
|
+
maxK = Math.max(maxK, k);
|
|
1334
|
+
coeffs[k] = (coeffs[k] ?? 0n) + coeff;
|
|
1335
|
+
}
|
|
1336
|
+
if (maxK < 2) {
|
|
1337
|
+
return null;
|
|
1338
|
+
}
|
|
1339
|
+
// Factor univariate in t
|
|
1340
|
+
const fr = (0, factorUnivariate_1.factorPolynomialBig)(coeffs);
|
|
1341
|
+
if ((fr.factors?.length ?? 0) === 0) {
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
// Build monomial m
|
|
1345
|
+
const mPoly = new MultiPoly_1.MultiPoly();
|
|
1346
|
+
mPoly.terms.set((0, MultiPoly_1.expToKey)(base), 1n);
|
|
1347
|
+
mPoly.trim();
|
|
1348
|
+
function powMV(basePoly, e) {
|
|
1349
|
+
let r = MultiPoly_1.MultiPoly.constant(1n);
|
|
1350
|
+
let b = basePoly;
|
|
1351
|
+
let k = e;
|
|
1352
|
+
while (k > 0) {
|
|
1353
|
+
if (k & 1) {
|
|
1354
|
+
r = (0, poly_1.mulPoly)(r, b);
|
|
1355
|
+
}
|
|
1356
|
+
k >>= 1;
|
|
1357
|
+
if (k) {
|
|
1358
|
+
b = (0, poly_1.mulPoly)(b, b);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return r;
|
|
1362
|
+
}
|
|
1363
|
+
function fromTPolyToMV(g) {
|
|
1364
|
+
let acc = MultiPoly_1.MultiPoly.zero();
|
|
1365
|
+
for (let i = 0; i < g.length; i++) {
|
|
1366
|
+
const c = g[i] ?? 0n;
|
|
1367
|
+
if (c === 0n) {
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
const term = (0, poly_1.mulPoly)(MultiPoly_1.MultiPoly.constant(c), powMV(mPoly, i));
|
|
1371
|
+
acc = (0, poly_1.addPoly)(acc, term);
|
|
1372
|
+
}
|
|
1373
|
+
acc.trim();
|
|
1374
|
+
return acc;
|
|
1375
|
+
}
|
|
1376
|
+
const out = [];
|
|
1377
|
+
if (fr.content !== 1n && fr.content !== -1n) {
|
|
1378
|
+
out.push(MultiPoly_1.MultiPoly.constant(fr.content));
|
|
1379
|
+
}
|
|
1380
|
+
else if (fr.content === -1n) {
|
|
1381
|
+
out.push(MultiPoly_1.MultiPoly.constant(-1n));
|
|
1382
|
+
}
|
|
1383
|
+
for (const f of fr.factors) {
|
|
1384
|
+
for (let i = 0; i < (f.multiplicity ?? 1); i++) {
|
|
1385
|
+
out.push(fromTPolyToMV(f.coefficients));
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
return out.length >= 2 ? out : null;
|
|
1389
|
+
}
|
|
1390
|
+
function tryFactorEvenQuarticInVar(p, v) {
|
|
1391
|
+
// Only handle monic v^4 + A2*v^2 + A0 (no odd powers)
|
|
1392
|
+
let A4 = null;
|
|
1393
|
+
const A2 = new MultiPoly_1.MultiPoly();
|
|
1394
|
+
const A0 = new MultiPoly_1.MultiPoly();
|
|
1395
|
+
for (const [key, coeff] of p.terms) {
|
|
1396
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
1397
|
+
const e = exp.get(v) ?? 0;
|
|
1398
|
+
if (e === 1 || e === 3) {
|
|
1399
|
+
return null;
|
|
1400
|
+
}
|
|
1401
|
+
if (e !== 0 && e !== 2 && e !== 4) {
|
|
1402
|
+
return null;
|
|
1403
|
+
}
|
|
1404
|
+
exp.delete(v);
|
|
1405
|
+
const k = (0, MultiPoly_1.expToKey)(exp);
|
|
1406
|
+
if (e === 4) {
|
|
1407
|
+
if (A4 === null) {
|
|
1408
|
+
A4 = new MultiPoly_1.MultiPoly();
|
|
1409
|
+
}
|
|
1410
|
+
A4.terms.set(k, (A4.terms.get(k) ?? 0n) + coeff);
|
|
1411
|
+
}
|
|
1412
|
+
else if (e === 2) {
|
|
1413
|
+
A2.terms.set(k, (A2.terms.get(k) ?? 0n) + coeff);
|
|
1414
|
+
}
|
|
1415
|
+
else {
|
|
1416
|
+
A0.terms.set(k, (A0.terms.get(k) ?? 0n) + coeff);
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
if (A4 === null) {
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
A4.trim();
|
|
1423
|
+
A2.trim();
|
|
1424
|
+
A0.trim();
|
|
1425
|
+
// Require A4 == 1
|
|
1426
|
+
if (!(A4.isConstant() && A4.constantTerm() === 1n)) {
|
|
1427
|
+
return null;
|
|
1428
|
+
}
|
|
1429
|
+
// Need v = sqrt(A0)
|
|
1430
|
+
const v0 = tryPolynomialSquareRoot(A0);
|
|
1431
|
+
if (v0 === null) {
|
|
1432
|
+
return null;
|
|
1433
|
+
}
|
|
1434
|
+
// u^2 = 2*v0 - A2
|
|
1435
|
+
const u2 = (0, poly_1.subPoly)((0, poly_1.mulPoly)(v0, MultiPoly_1.MultiPoly.constant(2n)), A2);
|
|
1436
|
+
const u = tryPolynomialSquareRoot(u2);
|
|
1437
|
+
if (u === null) {
|
|
1438
|
+
return null;
|
|
1439
|
+
}
|
|
1440
|
+
const vPoly = MultiPoly_1.MultiPoly.variable(v);
|
|
1441
|
+
const v2 = (0, poly_1.mulPoly)(vPoly, vPoly);
|
|
1442
|
+
const fPlus = (0, poly_1.addPoly)((0, poly_1.addPoly)(v2, (0, poly_1.mulPoly)(u, vPoly)), v0);
|
|
1443
|
+
const fMinus = (0, poly_1.addPoly)((0, poly_1.addPoly)(v2, (0, poly_1.mulPoly)((0, poly_1.scalePoly)(u, -1n), vPoly)), v0);
|
|
1444
|
+
if ((0, poly_1.equalPoly)((0, poly_1.mulPoly)(fPlus, fMinus), p)) {
|
|
1445
|
+
return [fPlus, fMinus];
|
|
1446
|
+
}
|
|
1447
|
+
return null;
|
|
1448
|
+
}
|
|
1449
|
+
function tryFactorBinomialPowerFast(p) {
|
|
1450
|
+
const prim = makePrimitiveZ(p)[1];
|
|
1451
|
+
const pow = tryBinomialPower(prim);
|
|
1452
|
+
if (!pow) {
|
|
1453
|
+
return null;
|
|
1454
|
+
}
|
|
1455
|
+
const { base, n } = pow;
|
|
1456
|
+
const baseFacts = factorMultivariate(base);
|
|
1457
|
+
const out = [];
|
|
1458
|
+
for (let i = 0; i < n; i++) {
|
|
1459
|
+
out.push(...baseFacts);
|
|
1460
|
+
}
|
|
1461
|
+
return out;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Fast-path: special bivariate patterns like x^n ± y^n.
|
|
1465
|
+
* Returns recursively factored factors or null.
|
|
1466
|
+
*/
|
|
1467
|
+
function tryFactorSpecialBivariatePatternsFast(p) {
|
|
1468
|
+
const vars = (0, poly_1.mvVariablesSorted)(p);
|
|
1469
|
+
if (vars.length !== 2) {
|
|
1470
|
+
return null;
|
|
1471
|
+
}
|
|
1472
|
+
// Only check for patterns if the degrees are > 1 (to avoid infinite recursion on linear factors).
|
|
1473
|
+
const deg0 = p.degree(vars[0]);
|
|
1474
|
+
const deg1 = p.degree(vars[1]);
|
|
1475
|
+
if (!(deg0 > 1 && deg1 > 1)) {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
const special = trySpecialBivariatePatterns(p, vars[0], vars[1]);
|
|
1479
|
+
if (!special || special.length < 2) {
|
|
1480
|
+
return null;
|
|
1481
|
+
}
|
|
1482
|
+
const out = [];
|
|
1483
|
+
for (const f of special) {
|
|
1484
|
+
out.push(...factorMultivariate(f));
|
|
1485
|
+
}
|
|
1486
|
+
return out;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Fast-path: factor as polynomial in a single variable of degree 1 or 2.
|
|
1490
|
+
* Includes content extraction, linear/quadratic-in-var splits, and simple binomial-in-var splits.
|
|
1491
|
+
*/
|
|
1492
|
+
function tryFactorSmallDegreeInVarFast(p) {
|
|
1493
|
+
const vars = (0, poly_1.mvVariablesSorted)(p);
|
|
1494
|
+
// First try to extract content with respect to each variable.
|
|
1495
|
+
// Only accept if it leads to a proper factorization.
|
|
1496
|
+
for (const v of vars) {
|
|
1497
|
+
const cont = (0, poly_1.mvContentInVar)(p, v);
|
|
1498
|
+
if (!cont.isConstant() || cont.constantTerm() !== 1n) {
|
|
1499
|
+
const pp = (0, poly_1.primitivePart)(p, v);
|
|
1500
|
+
if (!(0, poly_1.equalPoly)(pp, p) && !pp.isConstant()) {
|
|
1501
|
+
if (factorsReconstruct(p, [cont, pp])) {
|
|
1502
|
+
const contFactors = factorMultivariate(cont);
|
|
1503
|
+
const ppFactors = factorMultivariate(pp);
|
|
1504
|
+
if (contFactors.length + ppFactors.length >= 2) {
|
|
1505
|
+
return [...contFactors, ...ppFactors];
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
// Linear factorization (polynomials linear in some variable).
|
|
1512
|
+
for (const v of vars) {
|
|
1513
|
+
const deg = p.degree(v);
|
|
1514
|
+
if (deg === 1) {
|
|
1515
|
+
const fact = tryFactorLinearInVar(p, v);
|
|
1516
|
+
if (fact) {
|
|
1517
|
+
const out = [];
|
|
1518
|
+
for (const f of fact) {
|
|
1519
|
+
out.push(...factorMultivariate(f));
|
|
1520
|
+
}
|
|
1521
|
+
return out;
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
// Binomial factorization: p = A*v^k + B where A,B share a common factor.
|
|
1526
|
+
for (const v of vars) {
|
|
1527
|
+
const fact = tryFactorBinomialInVar(p, v);
|
|
1528
|
+
if (fact) {
|
|
1529
|
+
const out = [];
|
|
1530
|
+
for (const f of fact) {
|
|
1531
|
+
out.push(...factorMultivariate(f));
|
|
1532
|
+
}
|
|
1533
|
+
return out;
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
// Quadratic factorization.
|
|
1537
|
+
for (const v of vars) {
|
|
1538
|
+
const deg = p.degree(v);
|
|
1539
|
+
if (deg === 2) {
|
|
1540
|
+
const fact = tryFactorQuadraticInVar(p, v);
|
|
1541
|
+
if (fact) {
|
|
1542
|
+
const out = [];
|
|
1543
|
+
for (const f of fact) {
|
|
1544
|
+
out.push(...factorMultivariate(f));
|
|
1545
|
+
}
|
|
1546
|
+
return out;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
// Even-quartic factorization (degree 4 with only even powers in some variable).
|
|
1551
|
+
for (const v of vars) {
|
|
1552
|
+
const deg = p.degree(v);
|
|
1553
|
+
if (deg === 4) {
|
|
1554
|
+
const fact = tryFactorEvenQuarticInVar(p, v);
|
|
1555
|
+
if (fact) {
|
|
1556
|
+
const out = [];
|
|
1557
|
+
for (const f of fact) {
|
|
1558
|
+
out.push(...factorMultivariate(f));
|
|
1559
|
+
}
|
|
1560
|
+
return out;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
return null;
|
|
1565
|
+
}
|
|
1566
|
+
/**
|
|
1567
|
+
* EEZ/Wang step: extract a monic linear factor in y by parameter specialization (fast path).
|
|
1568
|
+
* Returns [factor, ...factors(quotient)] or null.
|
|
1569
|
+
*
|
|
1570
|
+
* Note: this is intentionally kept as a single helper to keep factorMultivariate readable.
|
|
1571
|
+
*/
|
|
1572
|
+
function tryFactorEEZWangMonicLinearFast(p) {
|
|
1573
|
+
function varsPresent(poly) {
|
|
1574
|
+
return (0, poly_1.mvVariablesSorted)(poly);
|
|
1575
|
+
}
|
|
1576
|
+
function isMonicLinearInVar(f, varIndex) {
|
|
1577
|
+
if (f.degree(varIndex) !== 1) {
|
|
1578
|
+
return { ok: false };
|
|
1579
|
+
}
|
|
1580
|
+
let q = null;
|
|
1581
|
+
const r = new MultiPoly_1.MultiPoly();
|
|
1582
|
+
for (const [key, coeff] of f.terms) {
|
|
1583
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
1584
|
+
const e = exp.get(varIndex) ?? 0;
|
|
1585
|
+
if (e === 0) {
|
|
1586
|
+
r.terms.set(key, coeff);
|
|
1587
|
+
continue;
|
|
1588
|
+
}
|
|
1589
|
+
if (e !== 1) {
|
|
1590
|
+
return { ok: false };
|
|
1591
|
+
}
|
|
1592
|
+
exp.delete(varIndex);
|
|
1593
|
+
const k = (0, MultiPoly_1.expToKey)(exp);
|
|
1594
|
+
if (q === null) {
|
|
1595
|
+
q = new MultiPoly_1.MultiPoly();
|
|
1596
|
+
}
|
|
1597
|
+
q.terms.set(k, (q.terms.get(k) ?? 0n) + coeff);
|
|
1598
|
+
}
|
|
1599
|
+
if (q === null) {
|
|
1600
|
+
return { ok: false };
|
|
1601
|
+
}
|
|
1602
|
+
q.trim();
|
|
1603
|
+
r.trim();
|
|
1604
|
+
if (!(q.isConstant() && q.constantTerm() === 1n)) {
|
|
1605
|
+
return { ok: false };
|
|
1606
|
+
}
|
|
1607
|
+
return { ok: true, rest: r };
|
|
1608
|
+
}
|
|
1609
|
+
const ratZ = (z) => (0, rational_1.rat)(z);
|
|
1610
|
+
const toInt = (r) => (0, rational_1.ratToInt)(r);
|
|
1611
|
+
function solveDeg2(xs, ys) {
|
|
1612
|
+
// Solve for c0 + c1*x + c2*x^2 = y using 3 points via Gaussian elimination over rationals.
|
|
1613
|
+
const [x0, x1, x2] = xs;
|
|
1614
|
+
const [y0, y1, y2] = ys;
|
|
1615
|
+
const A = [
|
|
1616
|
+
[ratZ(1n), ratZ(x0), ratZ(x0 * x0), ratZ(y0)],
|
|
1617
|
+
[ratZ(1n), ratZ(x1), ratZ(x1 * x1), ratZ(y1)],
|
|
1618
|
+
[ratZ(1n), ratZ(x2), ratZ(x2 * x2), ratZ(y2)],
|
|
1619
|
+
];
|
|
1620
|
+
for (let col = 0; col < 3; col++) {
|
|
1621
|
+
let pivot = col;
|
|
1622
|
+
for (let r = col; r < 3; r++) {
|
|
1623
|
+
if (A[r][col].n !== 0n) {
|
|
1624
|
+
pivot = r;
|
|
1625
|
+
break;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (A[pivot][col].n === 0n) {
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1631
|
+
if (pivot !== col) {
|
|
1632
|
+
const tmp = A[col];
|
|
1633
|
+
A[col] = A[pivot];
|
|
1634
|
+
A[pivot] = tmp;
|
|
1635
|
+
}
|
|
1636
|
+
const inv = A[col][col];
|
|
1637
|
+
for (let c = col; c < 4; c++) {
|
|
1638
|
+
A[col][c] = (0, rational_1.ratDiv)(A[col][c], inv);
|
|
1639
|
+
}
|
|
1640
|
+
for (let r = 0; r < 3; r++) {
|
|
1641
|
+
if (r === col) {
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
const factor = A[r][col];
|
|
1645
|
+
if (factor.n === 0n) {
|
|
1646
|
+
continue;
|
|
1647
|
+
}
|
|
1648
|
+
for (let c = col; c < 4; c++) {
|
|
1649
|
+
A[r][c] = (0, rational_1.ratSub)(A[r][c], (0, rational_1.ratMul)(factor, A[col][c]));
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
const c0 = toInt(A[0][3]);
|
|
1654
|
+
const c1 = toInt(A[1][3]);
|
|
1655
|
+
const c2 = toInt(A[2][3]);
|
|
1656
|
+
if (c0 === null || c1 === null || c2 === null) {
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
return [c0, c1, c2];
|
|
1660
|
+
}
|
|
1661
|
+
function tryEEZMonicLinearFactorInY(poly, mainVar, evalVar) {
|
|
1662
|
+
const present = varsPresent(poly);
|
|
1663
|
+
if (!present.includes(mainVar) || !present.includes(evalVar)) {
|
|
1664
|
+
return null;
|
|
1665
|
+
}
|
|
1666
|
+
const params = present.filter(v => v !== mainVar && v !== evalVar);
|
|
1667
|
+
if (params.length === 0) {
|
|
1668
|
+
return null;
|
|
1669
|
+
}
|
|
1670
|
+
const degY = poly.degree(evalVar);
|
|
1671
|
+
if (degY < 1 || degY > 3) {
|
|
1672
|
+
return null;
|
|
1673
|
+
}
|
|
1674
|
+
const vary = params[0];
|
|
1675
|
+
const fixed = params.slice(1);
|
|
1676
|
+
const varyVals = [1n, 2n, 3n];
|
|
1677
|
+
const fixedVals = new Map();
|
|
1678
|
+
for (let i = 0; i < fixed.length; i++) {
|
|
1679
|
+
fixedVals.set(fixed[i], BigInt(i + 1));
|
|
1680
|
+
}
|
|
1681
|
+
// xExp -> coefficient values at varyVals
|
|
1682
|
+
const obs = new Map();
|
|
1683
|
+
for (let idx = 0; idx < varyVals.length; idx++) {
|
|
1684
|
+
let sp = poly;
|
|
1685
|
+
for (const [v, val] of fixedVals) {
|
|
1686
|
+
sp = (0, poly_1.substituteVarIndex)(sp, v, val);
|
|
1687
|
+
}
|
|
1688
|
+
sp = (0, poly_1.substituteVarIndex)(sp, vary, varyVals[idx]);
|
|
1689
|
+
// Factor the specialized (x,y) polynomial. This will use the existing bivariate path.
|
|
1690
|
+
const fs = filterUnitConstants(factorMultivariate(sp));
|
|
1691
|
+
let chosenRest = null;
|
|
1692
|
+
for (const f of fs) {
|
|
1693
|
+
const chk = isMonicLinearInVar(f, evalVar);
|
|
1694
|
+
if (chk.ok && chk.rest) {
|
|
1695
|
+
chosenRest = chk.rest;
|
|
1696
|
+
break;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
if (chosenRest === null) {
|
|
1700
|
+
return null;
|
|
1701
|
+
}
|
|
1702
|
+
for (const [key, coeff] of chosenRest.terms) {
|
|
1703
|
+
const exp = (0, MultiPoly_1.keyToExp)(key);
|
|
1704
|
+
const yExp = exp.get(evalVar) ?? 0;
|
|
1705
|
+
if (yExp !== 0) {
|
|
1706
|
+
return null;
|
|
1707
|
+
}
|
|
1708
|
+
const xExp = exp.get(mainVar) ?? 0;
|
|
1709
|
+
let arr = obs.get(xExp);
|
|
1710
|
+
if (!arr) {
|
|
1711
|
+
arr = Array(varyVals.length).fill(0n);
|
|
1712
|
+
obs.set(xExp, arr);
|
|
1713
|
+
}
|
|
1714
|
+
arr[idx] = coeff;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
// Build rest R(x, vary) and then factor = y + R
|
|
1718
|
+
const rest = new MultiPoly_1.MultiPoly();
|
|
1719
|
+
for (const [xExp, values] of obs) {
|
|
1720
|
+
const coeffs = solveDeg2(varyVals, values);
|
|
1721
|
+
if (coeffs === null) {
|
|
1722
|
+
return null;
|
|
1723
|
+
}
|
|
1724
|
+
const [c0, c1, c2] = coeffs;
|
|
1725
|
+
const addTerm = (pow, cc) => {
|
|
1726
|
+
if (cc === 0n) {
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
const exp = new Map();
|
|
1730
|
+
if (xExp !== 0) {
|
|
1731
|
+
exp.set(mainVar, xExp);
|
|
1732
|
+
}
|
|
1733
|
+
if (pow !== 0) {
|
|
1734
|
+
exp.set(vary, pow);
|
|
1735
|
+
}
|
|
1736
|
+
const k = (0, MultiPoly_1.expToKey)(exp);
|
|
1737
|
+
rest.terms.set(k, (rest.terms.get(k) ?? 0n) + cc);
|
|
1738
|
+
};
|
|
1739
|
+
addTerm(0, c0);
|
|
1740
|
+
addTerm(1, c1);
|
|
1741
|
+
addTerm(2, c2);
|
|
1742
|
+
}
|
|
1743
|
+
rest.trim();
|
|
1744
|
+
const factor = new MultiPoly_1.MultiPoly();
|
|
1745
|
+
factor.terms.set((0, MultiPoly_1.expToKey)(new Map([[evalVar, 1]])), 1n); // y
|
|
1746
|
+
for (const [k, c] of rest.terms) {
|
|
1747
|
+
factor.terms.set(k, (factor.terms.get(k) ?? 0n) + c);
|
|
1748
|
+
}
|
|
1749
|
+
factor.trim();
|
|
1750
|
+
const div = (0, poly_1.mvExactDiv)(poly, factor);
|
|
1751
|
+
if (!div) {
|
|
1752
|
+
return null;
|
|
1753
|
+
}
|
|
1754
|
+
return factor;
|
|
1755
|
+
}
|
|
1756
|
+
// Try EEZ with actual variables present
|
|
1757
|
+
const allVars = varsPresent(p);
|
|
1758
|
+
if (allVars.length >= 2) {
|
|
1759
|
+
// Choose first two variables as main and eval
|
|
1760
|
+
const eezMainVar = allVars[0];
|
|
1761
|
+
const eezEvalVar = allVars[1];
|
|
1762
|
+
const eezFactor = tryEEZMonicLinearFactorInY(p, eezMainVar, eezEvalVar);
|
|
1763
|
+
if (eezFactor !== null) {
|
|
1764
|
+
const q = (0, poly_1.mvExactDiv)(p, eezFactor);
|
|
1765
|
+
if (q) {
|
|
1766
|
+
const restFactors = factorMultivariate(q);
|
|
1767
|
+
return [eezFactor, ...restFactors];
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
return null;
|
|
1772
|
+
}
|
|
1773
|
+
function factorMultivariate(p) {
|
|
1774
|
+
if (p.isZero()) {
|
|
1775
|
+
return [];
|
|
1776
|
+
}
|
|
1777
|
+
if (p.isConstant()) {
|
|
1778
|
+
// Don't include ±1 in factor list
|
|
1779
|
+
const c = p.constantTerm();
|
|
1780
|
+
if (c === 1n || c === -1n) {
|
|
1781
|
+
return [];
|
|
1782
|
+
}
|
|
1783
|
+
return [p];
|
|
1784
|
+
}
|
|
1785
|
+
// ---- Phase 0: cheap structural fast paths ----
|
|
1786
|
+
const fast = tryFactorMonomialGcdFast(p) ??
|
|
1787
|
+
tryFactorPerfectSquareFast(p) ??
|
|
1788
|
+
tryFactorPolynomialInMonomialFast(p) ??
|
|
1789
|
+
tryFactorBinomialPowerFast(p) ??
|
|
1790
|
+
tryFactorHomogeneousQuadratic2VarsFast(p) ??
|
|
1791
|
+
tryFactorSpecialBivariatePatternsFast(p) ??
|
|
1792
|
+
tryFactorSmallDegreeInVarFast(p) ??
|
|
1793
|
+
tryFactorEEZWangMonicLinearFast(p);
|
|
1794
|
+
if (fast) {
|
|
1795
|
+
return fast;
|
|
1796
|
+
}
|
|
1797
|
+
// ---- Phase 1: preprocessing + univariate special case ----
|
|
1798
|
+
// 1. Pre-processing: (content is currently not used; keep computation for correctness hooks)
|
|
1799
|
+
const workingPoly = (0, poly_1.scalePoly)(p, 1n); // Simplified: assumes primitive for now
|
|
1800
|
+
const vars = Array.from(workingPoly.variables()).sort();
|
|
1801
|
+
if (vars.length === 1) {
|
|
1802
|
+
return factorUnivariateMultiPoly(workingPoly, vars[0]);
|
|
1803
|
+
}
|
|
1804
|
+
function evalBudgetForMV(poly, evalVar) {
|
|
1805
|
+
// Adaptive deterministic budgets to avoid hangs while not giving up too early.
|
|
1806
|
+
const d = Math.max(0, poly.degree(evalVar));
|
|
1807
|
+
let maxAbs = 0n;
|
|
1808
|
+
for (const c of poly.terms.values()) {
|
|
1809
|
+
const a = c < 0n ? -c : c;
|
|
1810
|
+
if (a > maxAbs) {
|
|
1811
|
+
maxAbs = a;
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
const bits = (0, arith_1.bigintBitLength)(maxAbs);
|
|
1815
|
+
let maxAttempts = MIN_EVALUATION_ATTEMPTS +
|
|
1816
|
+
Math.ceil(d / EVALUATION_ATTEMPT_DEGREE_DIVISOR) +
|
|
1817
|
+
Math.ceil(bits / EVALUATION_ATTEMPT_BIT_DIVISOR);
|
|
1818
|
+
let maxSetsMultiplier = MIN_EVALUATION_SET_MULTIPLIER +
|
|
1819
|
+
Math.ceil(d / EVALUATION_SET_DEGREE_DIVISOR) +
|
|
1820
|
+
Math.ceil(bits / EVALUATION_SET_BIT_DIVISOR);
|
|
1821
|
+
if (maxAttempts < MIN_EVALUATION_ATTEMPTS) {
|
|
1822
|
+
maxAttempts = MIN_EVALUATION_ATTEMPTS;
|
|
1823
|
+
}
|
|
1824
|
+
if (maxAttempts > MAX_EVALUATION_ATTEMPTS) {
|
|
1825
|
+
maxAttempts = MAX_EVALUATION_ATTEMPTS;
|
|
1826
|
+
}
|
|
1827
|
+
if (maxSetsMultiplier < MIN_EVALUATION_SET_MULTIPLIER) {
|
|
1828
|
+
maxSetsMultiplier = MIN_EVALUATION_SET_MULTIPLIER;
|
|
1829
|
+
}
|
|
1830
|
+
if (maxSetsMultiplier > MAX_EVALUATION_SET_MULTIPLIER) {
|
|
1831
|
+
maxSetsMultiplier = MAX_EVALUATION_SET_MULTIPLIER;
|
|
1832
|
+
}
|
|
1833
|
+
return { maxAttempts, maxSetsMultiplier };
|
|
1834
|
+
}
|
|
1835
|
+
// ---- Phase 2: bivariate/multivariate split via evaluation + interpolation ----
|
|
1836
|
+
const mainVar = vars[0];
|
|
1837
|
+
const evalVar = vars.length > 1 ? vars[1] : vars[0];
|
|
1838
|
+
const lcMultivariate = workingPoly.leadingCoeff(mainVar);
|
|
1839
|
+
const budgets = evalBudgetForMV(workingPoly, evalVar);
|
|
1840
|
+
const stats = undefined;
|
|
1841
|
+
const evalPoint = findGenericEvalPoint({
|
|
1842
|
+
workingPoly,
|
|
1843
|
+
mainVar,
|
|
1844
|
+
evalVar,
|
|
1845
|
+
lcMultivariate,
|
|
1846
|
+
maxAttempts: budgets.maxAttempts,
|
|
1847
|
+
stats,
|
|
1848
|
+
});
|
|
1849
|
+
if (evalPoint === null) {
|
|
1850
|
+
return [workingPoly];
|
|
1851
|
+
}
|
|
1852
|
+
const fingerprint = (poly) => fingerprintUnivariateInVar(poly, mainVar);
|
|
1853
|
+
const reconstruction = collectStableFactorSetsMajority({
|
|
1854
|
+
workingPoly,
|
|
1855
|
+
mainVar,
|
|
1856
|
+
evalVar,
|
|
1857
|
+
evalPoint,
|
|
1858
|
+
lcMultivariate,
|
|
1859
|
+
maxSetsMultiplier: budgets.maxSetsMultiplier,
|
|
1860
|
+
fingerprint,
|
|
1861
|
+
stats,
|
|
1862
|
+
});
|
|
1863
|
+
if (!reconstruction) {
|
|
1864
|
+
return [workingPoly];
|
|
1865
|
+
}
|
|
1866
|
+
const alignedFactors = alignFactorsAcrossPoints({
|
|
1867
|
+
stableSets: reconstruction.stableSets.slice(0, reconstruction.numPointsNeeded),
|
|
1868
|
+
referenceSet: reconstruction.referenceSet,
|
|
1869
|
+
referenceFingerprints: reconstruction.referenceFingerprints,
|
|
1870
|
+
numFactors: reconstruction.numFactors,
|
|
1871
|
+
fingerprint,
|
|
1872
|
+
});
|
|
1873
|
+
const candidateFactors = interpolateCandidateFactors({
|
|
1874
|
+
alignedFactors,
|
|
1875
|
+
stableSets: reconstruction.stableSets,
|
|
1876
|
+
mainVar,
|
|
1877
|
+
evalVar,
|
|
1878
|
+
});
|
|
1879
|
+
if (candidateFactors.length === 0) {
|
|
1880
|
+
return [workingPoly];
|
|
1881
|
+
}
|
|
1882
|
+
const divided = divideOutCandidates(workingPoly, candidateFactors);
|
|
1883
|
+
return divided.length > 0 ? divided : [workingPoly];
|
|
1884
|
+
}
|
|
1885
|
+
// ---- factorMultivariate phases: helpers ----
|
|
1886
|
+
function factorUnivariateMultiPoly(workingPoly, varIndex) {
|
|
1887
|
+
// Helper: unpack a FactorizationResult into MultiPoly factors.
|
|
1888
|
+
function unpackFactorResult(res) {
|
|
1889
|
+
const out = [];
|
|
1890
|
+
let content = BigInt(res.content);
|
|
1891
|
+
const factors = [];
|
|
1892
|
+
for (const f of res.factors) {
|
|
1893
|
+
for (let k = 0; k < (f.multiplicity ?? 1); k++) {
|
|
1894
|
+
factors.push((0, poly_1.fromUnivariate)(f.coefficients, varIndex));
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
if (content === -1n && factors.length > 0) {
|
|
1898
|
+
factors[0] = (0, poly_1.scalePoly)(factors[0], -1n);
|
|
1899
|
+
content = 1n;
|
|
1900
|
+
}
|
|
1901
|
+
if (content !== 1n) {
|
|
1902
|
+
out.push(MultiPoly_1.MultiPoly.constant(content));
|
|
1903
|
+
}
|
|
1904
|
+
out.push(...factors);
|
|
1905
|
+
return out;
|
|
1906
|
+
}
|
|
1907
|
+
// Count non-constant factors in a FactorizationResult.
|
|
1908
|
+
function nonConstFactorCount(res) {
|
|
1909
|
+
let count = 0;
|
|
1910
|
+
for (const f of res.factors) {
|
|
1911
|
+
const deg = (f.coefficients?.length ?? 0) - 1;
|
|
1912
|
+
if (deg > 0) {
|
|
1913
|
+
count += f.multiplicity ?? 1;
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
return count;
|
|
1917
|
+
}
|
|
1918
|
+
// Try standard univariate factorization (Berlekamp/Zassenhaus) first.
|
|
1919
|
+
const uni = (0, poly_1.toUnivariate)(workingPoly, varIndex);
|
|
1920
|
+
if (uni) {
|
|
1921
|
+
const res = (0, factorUnivariate_1.factorPolynomial)(uni);
|
|
1922
|
+
const fullCount = nonConstFactorCount(res);
|
|
1923
|
+
if (fullCount > 1) {
|
|
1924
|
+
// Berlekamp/Zassenhaus split it. But rational root extraction followed
|
|
1925
|
+
// by recursive factorPolynomial on remainders may find even more factors
|
|
1926
|
+
// (e.g. x^6-1 where Berlekamp misses the quartic split). Try both and
|
|
1927
|
+
// take whichever produces more factors.
|
|
1928
|
+
const fullFactors = unpackFactorResult(res);
|
|
1929
|
+
const univarFactors = tryRationalRootFactorization(workingPoly, varIndex);
|
|
1930
|
+
if (univarFactors.length > 1) {
|
|
1931
|
+
const refined = refineRationalRootFactors(univarFactors, varIndex);
|
|
1932
|
+
if (refined.length > fullFactors.length) {
|
|
1933
|
+
return refined;
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
return fullFactors;
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
// Berlekamp/Zassenhaus didn't split. Try rational root extraction + recursive
|
|
1940
|
+
// refinement as fallback (handles cases where rational roots exist but the
|
|
1941
|
+
// full polynomial appears irreducible to Berlekamp due to prime selection).
|
|
1942
|
+
const univarFactors = tryRationalRootFactorization(workingPoly, varIndex);
|
|
1943
|
+
if (univarFactors.length > 1) {
|
|
1944
|
+
return refineRationalRootFactors(univarFactors, varIndex);
|
|
1945
|
+
}
|
|
1946
|
+
return [workingPoly];
|
|
1947
|
+
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Given factors from tryRationalRootFactorization, run each degree >= 2 factor
|
|
1950
|
+
* through factorPolynomial to split further (e.g. x^4+x^2+1 → (x^2+x+1)(x^2-x+1)).
|
|
1951
|
+
*/
|
|
1952
|
+
function refineRationalRootFactors(univarFactors, varIndex) {
|
|
1953
|
+
const refined = [];
|
|
1954
|
+
for (const fac of univarFactors) {
|
|
1955
|
+
const d = fac.degree(varIndex);
|
|
1956
|
+
if (d >= 2) {
|
|
1957
|
+
const uni = (0, poly_1.toUnivariate)(fac, varIndex);
|
|
1958
|
+
if (uni) {
|
|
1959
|
+
const res = (0, factorUnivariate_1.factorPolynomial)(uni);
|
|
1960
|
+
if (res.factors.length > 1 ||
|
|
1961
|
+
(res.factors.length === 1 && (res.factors[0].multiplicity ?? 1) > 1)) {
|
|
1962
|
+
let content = BigInt(res.content);
|
|
1963
|
+
for (const f of res.factors) {
|
|
1964
|
+
for (let k = 0; k < (f.multiplicity ?? 1); k++) {
|
|
1965
|
+
refined.push((0, poly_1.fromUnivariate)(f.coefficients, varIndex));
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
if (content === -1n && refined.length > 0) {
|
|
1969
|
+
refined[refined.length - 1] = (0, poly_1.scalePoly)(refined[refined.length - 1], -1n);
|
|
1970
|
+
content = 1n;
|
|
1971
|
+
}
|
|
1972
|
+
if (content !== 1n && content !== 0n) {
|
|
1973
|
+
refined.push(MultiPoly_1.MultiPoly.constant(content));
|
|
1974
|
+
}
|
|
1975
|
+
continue;
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
refined.push(fac);
|
|
1980
|
+
}
|
|
1981
|
+
return refined;
|
|
1982
|
+
}
|
|
1983
|
+
// Avoid "special" evaluation points where the univariate specialization collapses to repeated/power
|
|
1984
|
+
// factorizations (e.g. x^4 at y=0 in a generic bivariate split).
|
|
1985
|
+
//
|
|
1986
|
+
// A point is considered "generic good" if:
|
|
1987
|
+
// - at least two non-constant factors
|
|
1988
|
+
// - all non-constant factors are squarefree (multiplicity == 1)
|
|
1989
|
+
function uniSplitSquarefree(fr) {
|
|
1990
|
+
if (!fr || !fr.factors) {
|
|
1991
|
+
return { ok: false, nonConst: 0 };
|
|
1992
|
+
}
|
|
1993
|
+
let nonConst = 0;
|
|
1994
|
+
for (const f of fr.factors) {
|
|
1995
|
+
const deg = (f.coefficients?.length ?? 0) - 1;
|
|
1996
|
+
if (deg <= 0) {
|
|
1997
|
+
continue;
|
|
1998
|
+
}
|
|
1999
|
+
nonConst += 1;
|
|
2000
|
+
const m = f.multiplicity ?? 1;
|
|
2001
|
+
if (m !== 1) {
|
|
2002
|
+
return { ok: false, nonConst };
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
return { ok: nonConst >= 2, nonConst };
|
|
2006
|
+
}
|
|
2007
|
+
function findGenericEvalPoint(args) {
|
|
2008
|
+
const { workingPoly, mainVar, evalVar, lcMultivariate, maxAttempts } = args;
|
|
2009
|
+
const stats = args.stats;
|
|
2010
|
+
if (stats) {
|
|
2011
|
+
stats.stage = 'find-generic-eval-point';
|
|
2012
|
+
stats.maxAttempts = maxAttempts;
|
|
2013
|
+
stats.attemptsUsed = 0;
|
|
2014
|
+
}
|
|
2015
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2016
|
+
if (stats) {
|
|
2017
|
+
stats.attemptsUsed = attempt + 1;
|
|
2018
|
+
}
|
|
2019
|
+
const a = attempt === 0
|
|
2020
|
+
? 0n
|
|
2021
|
+
: attempt % 2 === 1
|
|
2022
|
+
? BigInt(Math.floor(attempt / 2) + 1)
|
|
2023
|
+
: -BigInt(attempt / 2);
|
|
2024
|
+
const evaluatedLC = (0, poly_1.substituteVarIndex)(lcMultivariate, evalVar, a);
|
|
2025
|
+
if (evaluatedLC.isZero()) {
|
|
2026
|
+
continue;
|
|
2027
|
+
}
|
|
2028
|
+
const pEval = (0, poly_1.substituteVarIndex)(workingPoly, evalVar, a);
|
|
2029
|
+
const uniCoeffs = (0, poly_1.toUnivariate)(pEval, mainVar);
|
|
2030
|
+
if (!uniCoeffs) {
|
|
2031
|
+
continue;
|
|
2032
|
+
}
|
|
2033
|
+
const fr = (0, factorUnivariate_1.factorPolynomial)(uniCoeffs);
|
|
2034
|
+
if (uniSplitSquarefree(fr).ok) {
|
|
2035
|
+
return a;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
return null;
|
|
2039
|
+
}
|
|
2040
|
+
// ---- Matching / fingerprint helpers ----
|
|
2041
|
+
const FP_MOD = 1000003n;
|
|
2042
|
+
const FP_XS = [2n, 3n, 5n];
|
|
2043
|
+
function evalUnivariateMod(poly, x, mod, mainVar) {
|
|
2044
|
+
let acc = 0n;
|
|
2045
|
+
let pow = 1n;
|
|
2046
|
+
const deg = poly.degree(mainVar);
|
|
2047
|
+
for (let d = 0; d <= deg; d++) {
|
|
2048
|
+
const c = poly.getCoeff([d]);
|
|
2049
|
+
acc = (0, arith_1.modNorm)(acc + (0, arith_1.modNorm)(c, mod) * pow, mod);
|
|
2050
|
+
pow = (0, arith_1.modNorm)(pow * x, mod);
|
|
2051
|
+
}
|
|
2052
|
+
return acc;
|
|
2053
|
+
}
|
|
2054
|
+
function fingerprintUnivariateInVar(poly, mainVar) {
|
|
2055
|
+
const q = (0, poly_1.mvNormalizeSignUnivariateInVar)(poly, mainVar);
|
|
2056
|
+
const deg = q.degree(mainVar);
|
|
2057
|
+
const vals = FP_XS.map(x => evalUnivariateMod(q, x, FP_MOD, mainVar).toString());
|
|
2058
|
+
return `${deg}|${vals.join(',')}`;
|
|
2059
|
+
}
|
|
2060
|
+
function collectStableFactorSetsMajority(args) {
|
|
2061
|
+
const { workingPoly, mainVar, evalVar, evalPoint, lcMultivariate, maxSetsMultiplier, fingerprint, } = args;
|
|
2062
|
+
const stats = args.stats;
|
|
2063
|
+
if (stats) {
|
|
2064
|
+
stats.stage = 'collect-stable-factor-sets';
|
|
2065
|
+
stats.pointsTried = 0;
|
|
2066
|
+
stats.factorSetsFound = 0;
|
|
2067
|
+
}
|
|
2068
|
+
const degY = workingPoly.degree(evalVar);
|
|
2069
|
+
const numPointsNeeded = degY + 1;
|
|
2070
|
+
if (stats) {
|
|
2071
|
+
stats.numPointsNeeded = numPointsNeeded;
|
|
2072
|
+
}
|
|
2073
|
+
const factorSets = [];
|
|
2074
|
+
// Candidate evaluation points: include both positive and negative points.
|
|
2075
|
+
const candidatePoints = [];
|
|
2076
|
+
candidatePoints.push(evalPoint);
|
|
2077
|
+
candidatePoints.push(0n);
|
|
2078
|
+
for (let k = 1; k < numPointsNeeded * maxSetsMultiplier; k++) {
|
|
2079
|
+
candidatePoints.push(BigInt(k));
|
|
2080
|
+
candidatePoints.push(-BigInt(k));
|
|
2081
|
+
}
|
|
2082
|
+
// De-duplicate while preserving order.
|
|
2083
|
+
const seenPts = new Set();
|
|
2084
|
+
const uniqCandidatePoints = [];
|
|
2085
|
+
for (const pt of candidatePoints) {
|
|
2086
|
+
const key = pt.toString();
|
|
2087
|
+
if (seenPts.has(key)) {
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
seenPts.add(key);
|
|
2091
|
+
uniqCandidatePoints.push(pt);
|
|
2092
|
+
}
|
|
2093
|
+
const maxSets = Math.max(numPointsNeeded * maxSetsMultiplier, MIN_EVALUATION_SET_COUNT);
|
|
2094
|
+
if (stats) {
|
|
2095
|
+
stats.maxSets = maxSets;
|
|
2096
|
+
}
|
|
2097
|
+
for (const pt of uniqCandidatePoints) {
|
|
2098
|
+
if (stats) {
|
|
2099
|
+
stats.pointsTried = (stats.pointsTried ?? 0) + 1;
|
|
2100
|
+
}
|
|
2101
|
+
if (factorSets.length >= maxSets) {
|
|
2102
|
+
break;
|
|
2103
|
+
}
|
|
2104
|
+
const lcEval = (0, poly_1.substituteVarIndex)(lcMultivariate, evalVar, pt);
|
|
2105
|
+
if (lcEval.isZero()) {
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
2108
|
+
const pEval = (0, poly_1.substituteVarIndex)(workingPoly, evalVar, pt);
|
|
2109
|
+
const uniCoeffs = (0, poly_1.toUnivariate)(pEval, mainVar);
|
|
2110
|
+
if (!uniCoeffs) {
|
|
2111
|
+
continue;
|
|
2112
|
+
}
|
|
2113
|
+
const factResult = (0, factorUnivariate_1.factorPolynomial)(uniCoeffs);
|
|
2114
|
+
// Ensure the reduction splits in a generic (squarefree) way.
|
|
2115
|
+
if (!uniSplitSquarefree(factResult).ok) {
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
if (factResult.factors.length === 0) {
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
const factors = [];
|
|
2122
|
+
const degrees = [];
|
|
2123
|
+
// Use only non-constant factors for interpolation stability.
|
|
2124
|
+
for (const f of factResult.factors) {
|
|
2125
|
+
const degUni = (f.coefficients?.length ?? 0) - 1;
|
|
2126
|
+
if (degUni <= 0) {
|
|
2127
|
+
continue;
|
|
2128
|
+
}
|
|
2129
|
+
for (let m = 0; m < f.multiplicity; m++) {
|
|
2130
|
+
const fPoly = (0, poly_1.fromUnivariate)(f.coefficients, mainVar);
|
|
2131
|
+
factors.push(fPoly);
|
|
2132
|
+
degrees.push(fPoly.degree(mainVar));
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
if (factors.length < 2) {
|
|
2136
|
+
continue;
|
|
2137
|
+
}
|
|
2138
|
+
// Normalize only by sign/unit for matching across points.
|
|
2139
|
+
const normFactors = factors.map(f => (0, poly_1.mvNormalizeSignUnivariateInVar)(f, mainVar));
|
|
2140
|
+
const normDegrees = normFactors.map(f => f.degree(mainVar));
|
|
2141
|
+
factorSets.push({ point: pt, factors: normFactors, degrees: normDegrees });
|
|
2142
|
+
if (stats) {
|
|
2143
|
+
stats.factorSetsFound = factorSets.length;
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
if (factorSets.length === 0) {
|
|
2147
|
+
return null;
|
|
2148
|
+
}
|
|
2149
|
+
const sigKey = (fs) => [...fs.degrees].sort((a, b) => a - b).join(',');
|
|
2150
|
+
const sigMap = new Map();
|
|
2151
|
+
for (const fs of factorSets) {
|
|
2152
|
+
let nonConst = 0;
|
|
2153
|
+
for (const d of fs.degrees) {
|
|
2154
|
+
if (d > 0) {
|
|
2155
|
+
nonConst++;
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
const key = sigKey(fs);
|
|
2159
|
+
const cur = sigMap.get(key) ?? { count: 0, bestNonConst: -1, bestSet: null };
|
|
2160
|
+
cur.count += 1;
|
|
2161
|
+
if (nonConst > cur.bestNonConst) {
|
|
2162
|
+
cur.bestNonConst = nonConst;
|
|
2163
|
+
cur.bestSet = fs;
|
|
2164
|
+
}
|
|
2165
|
+
sigMap.set(key, cur);
|
|
2166
|
+
}
|
|
2167
|
+
// Pick the most frequent signature; tie-break by most non-constant factors.
|
|
2168
|
+
let bestKey = null;
|
|
2169
|
+
let bestCount = -1;
|
|
2170
|
+
let bestNonConst = -1;
|
|
2171
|
+
let referenceSet = null;
|
|
2172
|
+
for (const [key, info] of sigMap.entries()) {
|
|
2173
|
+
if (info.bestNonConst <= 1) {
|
|
2174
|
+
continue;
|
|
2175
|
+
} // not a split
|
|
2176
|
+
if (info.count > bestCount ||
|
|
2177
|
+
(info.count === bestCount && info.bestNonConst > bestNonConst)) {
|
|
2178
|
+
bestKey = key;
|
|
2179
|
+
bestCount = info.count;
|
|
2180
|
+
bestNonConst = info.bestNonConst;
|
|
2181
|
+
referenceSet = info.bestSet;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
if (!bestKey || !referenceSet) {
|
|
2185
|
+
return null;
|
|
2186
|
+
}
|
|
2187
|
+
const stableSets = factorSets.filter(fs => sigKey(fs) === bestKey);
|
|
2188
|
+
if (stats) {
|
|
2189
|
+
stats.numPointsNeeded = numPointsNeeded;
|
|
2190
|
+
stats.maxSets = maxSets;
|
|
2191
|
+
stats.stableSetsFound = stableSets.length;
|
|
2192
|
+
}
|
|
2193
|
+
if (stableSets.length < numPointsNeeded) {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
const numFactors = referenceSet.factors.length;
|
|
2197
|
+
const referenceFingerprints = referenceSet.factors.map(f => fingerprint(f));
|
|
2198
|
+
return { numPointsNeeded, numFactors, referenceSet, referenceFingerprints, stableSets };
|
|
2199
|
+
}
|
|
2200
|
+
function alignFactorsAcrossPoints(args) {
|
|
2201
|
+
const { stableSets, referenceSet, referenceFingerprints, numFactors, fingerprint } = args;
|
|
2202
|
+
const alignedFactors = Array.from({ length: numFactors }, () => []);
|
|
2203
|
+
for (const fs of stableSets) {
|
|
2204
|
+
const used = new Array(fs.factors.length).fill(false);
|
|
2205
|
+
for (let refIdx = 0; refIdx < numFactors; refIdx++) {
|
|
2206
|
+
const refDeg = referenceSet.degrees[refIdx];
|
|
2207
|
+
const refFp = referenceFingerprints[refIdx];
|
|
2208
|
+
let pick = -1;
|
|
2209
|
+
for (let j = 0; j < fs.factors.length; j++) {
|
|
2210
|
+
if (used[j]) {
|
|
2211
|
+
continue;
|
|
2212
|
+
}
|
|
2213
|
+
if (fs.degrees[j] !== refDeg) {
|
|
2214
|
+
continue;
|
|
2215
|
+
}
|
|
2216
|
+
if (fingerprint(fs.factors[j]) === refFp) {
|
|
2217
|
+
pick = j;
|
|
2218
|
+
break;
|
|
2219
|
+
}
|
|
2220
|
+
}
|
|
2221
|
+
if (pick === -1) {
|
|
2222
|
+
// fallback: degree-only match (still one-to-one)
|
|
2223
|
+
for (let j = 0; j < fs.factors.length; j++) {
|
|
2224
|
+
if (used[j]) {
|
|
2225
|
+
continue;
|
|
2226
|
+
}
|
|
2227
|
+
if (fs.degrees[j] === refDeg) {
|
|
2228
|
+
pick = j;
|
|
2229
|
+
break;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
if (pick === -1) {
|
|
2234
|
+
alignedFactors[refIdx].push(MultiPoly_1.MultiPoly.constant(1n));
|
|
2235
|
+
}
|
|
2236
|
+
else {
|
|
2237
|
+
used[pick] = true;
|
|
2238
|
+
alignedFactors[refIdx].push(fs.factors[pick]);
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
return alignedFactors;
|
|
2243
|
+
}
|
|
2244
|
+
function interpolateCandidateFactors(args) {
|
|
2245
|
+
const { alignedFactors, stableSets, mainVar } = args;
|
|
2246
|
+
const candidateFactors = [];
|
|
2247
|
+
const numFactors = alignedFactors.length;
|
|
2248
|
+
for (let factorIdx = 0; factorIdx < numFactors; factorIdx++) {
|
|
2249
|
+
const alignedSet = alignedFactors[factorIdx];
|
|
2250
|
+
const interpolationData = [];
|
|
2251
|
+
for (let i = 0; i < Math.min(alignedSet.length, stableSets.length); i++) {
|
|
2252
|
+
interpolationData.push({ y: stableSets[i].point, poly: alignedSet[i] });
|
|
2253
|
+
}
|
|
2254
|
+
// Find max degree in x
|
|
2255
|
+
let maxDegX = 0;
|
|
2256
|
+
for (const { poly } of interpolationData) {
|
|
2257
|
+
const deg = poly.degree(mainVar);
|
|
2258
|
+
if (deg > maxDegX) {
|
|
2259
|
+
maxDegX = deg;
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
const interpolatedFactor = new MultiPoly_1.MultiPoly();
|
|
2263
|
+
for (let xDeg = 0; xDeg <= maxDegX; xDeg++) {
|
|
2264
|
+
const points = [];
|
|
2265
|
+
for (const { y, poly } of interpolationData) {
|
|
2266
|
+
points.push({ y: Number(y), value: poly.getCoeff([xDeg]) });
|
|
2267
|
+
}
|
|
2268
|
+
const yPoly = lagrangeInterpolation(points);
|
|
2269
|
+
for (const [yDeg, coeff] of yPoly.entries()) {
|
|
2270
|
+
if (coeff !== 0n) {
|
|
2271
|
+
interpolatedFactor.setCoeff([xDeg, yDeg], coeff);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
}
|
|
2275
|
+
if (!interpolatedFactor.isZero() && !interpolatedFactor.isConstant()) {
|
|
2276
|
+
candidateFactors.push(interpolatedFactor);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
return candidateFactors;
|
|
2280
|
+
}
|
|
2281
|
+
function divideOutCandidates(workingPoly, candidateFactors) {
|
|
2282
|
+
const result = [];
|
|
2283
|
+
let remaining = workingPoly.clone();
|
|
2284
|
+
for (const candidate of candidateFactors) {
|
|
2285
|
+
const q = (0, poly_1.mvExactDiv)(remaining, candidate);
|
|
2286
|
+
if (q) {
|
|
2287
|
+
result.push(candidate);
|
|
2288
|
+
remaining = q;
|
|
2289
|
+
if (remaining.isConstant() || remaining.isZero()) {
|
|
2290
|
+
break;
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
if (!remaining.isConstant() && !remaining.isZero()) {
|
|
2295
|
+
result.push(remaining);
|
|
2296
|
+
}
|
|
2297
|
+
return result;
|
|
2298
|
+
}
|
|
2299
|
+
/**
|
|
2300
|
+
* Lagrange interpolation to find polynomial p(y) such that p(y_i) = value_i
|
|
2301
|
+
* Returns coefficients as Map from degree to coefficient
|
|
2302
|
+
* NOTE: Result may be scaled up by a constant factor to avoid fractional coefficients
|
|
2303
|
+
*/
|
|
2304
|
+
function lagrangeInterpolation(points) {
|
|
2305
|
+
const n = points.length;
|
|
2306
|
+
if (n === 0) {
|
|
2307
|
+
return new Map();
|
|
2308
|
+
}
|
|
2309
|
+
if (n === 1) {
|
|
2310
|
+
const result = new Map();
|
|
2311
|
+
result.set(0, points[0].value);
|
|
2312
|
+
return result;
|
|
2313
|
+
}
|
|
2314
|
+
// Compute the product of all denominators to clear fractions
|
|
2315
|
+
let commonDenominator = 1n;
|
|
2316
|
+
for (let i = 0; i < n; i++) {
|
|
2317
|
+
for (let j = 0; j < n; j++) {
|
|
2318
|
+
if (i !== j) {
|
|
2319
|
+
commonDenominator *= BigInt(Math.abs(points[i].y - points[j].y));
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
// Build the interpolating polynomial with cleared denominators
|
|
2324
|
+
const result = new Map();
|
|
2325
|
+
for (let i = 0; i < n; i++) {
|
|
2326
|
+
// Compute the Lagrange basis polynomial L_i(y)
|
|
2327
|
+
let numerator = new Map([[0, 1n]]);
|
|
2328
|
+
for (let j = 0; j < n; j++) {
|
|
2329
|
+
if (i === j) {
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
const yj = BigInt(points[j].y);
|
|
2333
|
+
// Multiply numerator by (y - yj)
|
|
2334
|
+
const newNumerator = new Map();
|
|
2335
|
+
for (const [deg, coeff] of numerator.entries()) {
|
|
2336
|
+
newNumerator.set(deg + 1, (newNumerator.get(deg + 1) || 0n) + coeff);
|
|
2337
|
+
newNumerator.set(deg, (newNumerator.get(deg) || 0n) - coeff * yj);
|
|
2338
|
+
}
|
|
2339
|
+
numerator = newNumerator;
|
|
2340
|
+
}
|
|
2341
|
+
// Compute the denominator for this basis polynomial
|
|
2342
|
+
let denominator = 1n;
|
|
2343
|
+
for (let j = 0; j < n; j++) {
|
|
2344
|
+
if (i !== j) {
|
|
2345
|
+
denominator *= BigInt(points[i].y - points[j].y);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
// Scale numerator by value_i and by (commonDenominator / denominator)
|
|
2349
|
+
const scale = points[i].value * (commonDenominator / denominator);
|
|
2350
|
+
// Add scaled numerator to result
|
|
2351
|
+
for (const [deg, coeff] of numerator.entries()) {
|
|
2352
|
+
result.set(deg, (result.get(deg) || 0n) + coeff * scale);
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
// Find GCD of all coefficients and the common denominator
|
|
2356
|
+
let g0 = 0n;
|
|
2357
|
+
for (const coeff of result.values()) {
|
|
2358
|
+
g0 = (0, arith_1.gcd)(g0, coeff);
|
|
2359
|
+
}
|
|
2360
|
+
g0 = (0, arith_1.gcd)(g0, commonDenominator);
|
|
2361
|
+
// Simplify by the GCD (this gives us the smallest integer polynomial)
|
|
2362
|
+
const simplified = new Map();
|
|
2363
|
+
for (const [deg, coeff] of result.entries()) {
|
|
2364
|
+
const simpl = coeff / g0;
|
|
2365
|
+
if (simpl !== 0n) {
|
|
2366
|
+
simplified.set(deg, simpl);
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
return simplified;
|
|
2370
|
+
}
|
|
2371
|
+
function factorPoly(f, vars) {
|
|
2372
|
+
if (f.isZero()) {
|
|
2373
|
+
return { content: 0n, factors: [] };
|
|
2374
|
+
}
|
|
2375
|
+
const [intContent, primitive] = makePrimitiveZ(f);
|
|
2376
|
+
const factorList = factorMultivariate(primitive);
|
|
2377
|
+
// Group by equality
|
|
2378
|
+
const factorMap = new Map();
|
|
2379
|
+
for (const factor of factorList) {
|
|
2380
|
+
const key = factor.text(vars);
|
|
2381
|
+
const existing = factorMap.get(key);
|
|
2382
|
+
if (existing) {
|
|
2383
|
+
existing.multiplicity++;
|
|
2384
|
+
}
|
|
2385
|
+
else {
|
|
2386
|
+
factorMap.set(key, { poly: factor, multiplicity: 1 });
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
return {
|
|
2390
|
+
content: intContent,
|
|
2391
|
+
factors: Array.from(factorMap.values()),
|
|
2392
|
+
};
|
|
2393
|
+
}
|