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.
Files changed (341) hide show
  1. package/LICENSE.md +184 -0
  2. package/README.md +293 -312
  3. package/dist/bundle.js +2 -0
  4. package/dist/bundle.js.LICENSE.txt +7 -0
  5. package/dist/parser.js +2 -0
  6. package/dist/parser.js.LICENSE.txt +7 -0
  7. package/docs-data/parser-functions.json +1960 -0
  8. package/index.d.ts +10 -426
  9. package/output/algebra/adapters.d.ts +33 -0
  10. package/output/algebra/adapters.js +109 -0
  11. package/output/algebra/algorithms/arith.d.ts +84 -0
  12. package/output/algebra/algorithms/arith.js +323 -0
  13. package/output/algebra/algorithms/factor.d.ts +3 -0
  14. package/output/algebra/algorithms/factor.js +5 -0
  15. package/output/algebra/algorithms/factorMultivariate.d.ts +23 -0
  16. package/output/algebra/algorithms/factorMultivariate.js +2393 -0
  17. package/output/algebra/algorithms/factorUnivariate.d.ts +72 -0
  18. package/output/algebra/algorithms/factorUnivariate.js +1072 -0
  19. package/output/algebra/algorithms/gcd.d.ts +22 -0
  20. package/output/algebra/algorithms/gcd.js +690 -0
  21. package/output/algebra/algorithms/groebnerBase.d.ts +157 -0
  22. package/output/algebra/algorithms/groebnerBase.js +1166 -0
  23. package/output/algebra/algorithms/multiPoly/MultiPoly.d.ts +137 -0
  24. package/output/algebra/algorithms/multiPoly/MultiPoly.js +346 -0
  25. package/output/algebra/algorithms/poly.d.ts +228 -0
  26. package/output/algebra/algorithms/poly.js +1299 -0
  27. package/output/algebra/algorithms/rational.d.ts +17 -0
  28. package/output/algebra/algorithms/rational.js +115 -0
  29. package/output/algebra/factor/factor.d.ts +62 -0
  30. package/output/algebra/factor/factor.js +158 -0
  31. package/output/algebra/gcd/gcd.d.ts +29 -0
  32. package/output/algebra/gcd/gcd.js +141 -0
  33. package/output/algebra/groebner.d.ts +21 -0
  34. package/output/algebra/groebner.js +42 -0
  35. package/output/algebra/partfrac.d.ts +19 -0
  36. package/output/algebra/partfrac.js +398 -0
  37. package/output/algebra/polynomialize.d.ts +20 -0
  38. package/output/algebra/polynomialize.js +24 -0
  39. package/output/algebra/simplify/complexsimp.d.ts +7 -0
  40. package/output/algebra/simplify/complexsimp.js +24 -0
  41. package/output/algebra/simplify/factorCommon.d.ts +13 -0
  42. package/output/algebra/simplify/factorCommon.js +136 -0
  43. package/output/algebra/simplify/funcsimp.d.ts +26 -0
  44. package/output/algebra/simplify/funcsimp.js +633 -0
  45. package/output/algebra/simplify/invtrigrewrite.d.ts +10 -0
  46. package/output/algebra/simplify/invtrigrewrite.js +96 -0
  47. package/output/algebra/simplify/ratsimp.d.ts +21 -0
  48. package/output/algebra/simplify/ratsimp.js +140 -0
  49. package/output/algebra/simplify/simplify.d.ts +25 -0
  50. package/output/algebra/simplify/simplify.js +228 -0
  51. package/output/algebra/simplify/trigreduce.d.ts +38 -0
  52. package/output/algebra/simplify/trigreduce.js +424 -0
  53. package/output/algebra/simplify/trigrewrite.d.ts +17 -0
  54. package/output/algebra/simplify/trigrewrite.js +156 -0
  55. package/output/algebra/simplify/trigsimp.d.ts +11 -0
  56. package/output/algebra/simplify/trigsimp.js +369 -0
  57. package/output/algebra/simplify/utils.d.ts +32 -0
  58. package/output/algebra/simplify/utils.js +56 -0
  59. package/output/algebra/utils.d.ts +32 -0
  60. package/output/algebra/utils.js +48 -0
  61. package/output/api/advanced.d.ts +7 -0
  62. package/output/api/advanced.js +18 -0
  63. package/output/api/algebra.d.ts +17 -0
  64. package/output/api/algebra.js +32 -0
  65. package/output/api/assumptions.d.ts +4 -0
  66. package/output/api/assumptions.js +11 -0
  67. package/output/api/calculus.d.ts +9 -0
  68. package/output/api/calculus.js +21 -0
  69. package/output/api/core.d.ts +17 -0
  70. package/output/api/core.js +41 -0
  71. package/output/api/debug.d.ts +16 -0
  72. package/output/api/debug.js +23 -0
  73. package/output/api/parser.d.ts +85 -0
  74. package/output/api/parser.js +16 -0
  75. package/output/api/solve.d.ts +10 -0
  76. package/output/api/solve.js +16 -0
  77. package/output/api/structures.d.ts +10 -0
  78. package/output/api/structures.js +22 -0
  79. package/output/calculus/derivative/diff.d.ts +30 -0
  80. package/output/calculus/derivative/diff.js +238 -0
  81. package/output/calculus/fresnel.d.ts +16 -0
  82. package/output/calculus/fresnel.js +39 -0
  83. package/output/calculus/integrate/byParts.d.ts +18 -0
  84. package/output/calculus/integrate/byParts.js +339 -0
  85. package/output/calculus/integrate/bySubstitution.d.ts +135 -0
  86. package/output/calculus/integrate/bySubstitution.js +411 -0
  87. package/output/calculus/integrate/integrate.d.ts +38 -0
  88. package/output/calculus/integrate/integrate.js +652 -0
  89. package/output/calculus/integrate/integrationTable.d.ts +2 -0
  90. package/output/calculus/integrate/integrationTable.js +914 -0
  91. package/output/calculus/integrate/utils.d.ts +28 -0
  92. package/output/calculus/integrate/utils.js +96 -0
  93. package/output/calculus/laplace/ilaplace.d.ts +31 -0
  94. package/output/calculus/laplace/ilaplace.js +192 -0
  95. package/output/calculus/laplace/ilaplaceTable.d.ts +2 -0
  96. package/output/calculus/laplace/ilaplaceTable.js +200 -0
  97. package/output/calculus/laplace/laplace.d.ts +22 -0
  98. package/output/calculus/laplace/laplace.js +109 -0
  99. package/output/calculus/laplace/laplaceTable.d.ts +2 -0
  100. package/output/calculus/laplace/laplaceTable.js +187 -0
  101. package/output/calculus/limit/limit.d.ts +35 -0
  102. package/output/calculus/limit/limit.js +1183 -0
  103. package/output/calculus/limit/limitsTable.d.ts +2 -0
  104. package/output/calculus/limit/limitsTable.js +7 -0
  105. package/output/core/Settings.d.ts +32 -0
  106. package/output/core/Settings.js +68 -0
  107. package/output/core/adapters.d.ts +18 -0
  108. package/output/core/adapters.js +40 -0
  109. package/output/core/classes/assumption/Assumption.d.ts +218 -0
  110. package/output/core/classes/assumption/Assumption.js +605 -0
  111. package/output/core/classes/assumption/assertiveFunctions.d.ts +6 -0
  112. package/output/core/classes/assumption/assertiveFunctions.js +64 -0
  113. package/output/core/classes/assumption/assume.d.ts +90 -0
  114. package/output/core/classes/assumption/assume.js +145 -0
  115. package/output/core/classes/collection/Collection.d.ts +107 -0
  116. package/output/core/classes/collection/Collection.js +231 -0
  117. package/output/core/classes/complex/Complex.d.ts +99 -0
  118. package/output/core/classes/complex/Complex.js +135 -0
  119. package/output/core/classes/decimalSet/DecimalSet.d.ts +226 -0
  120. package/output/core/classes/decimalSet/DecimalSet.js +453 -0
  121. package/output/core/classes/dictionary/Dictionary.d.ts +102 -0
  122. package/output/core/classes/dictionary/Dictionary.js +213 -0
  123. package/output/core/classes/equation/Equation.d.ts +256 -0
  124. package/output/core/classes/equation/Equation.js +350 -0
  125. package/output/core/classes/expression/CoeffObject.d.ts +100 -0
  126. package/output/core/classes/expression/CoeffObject.js +198 -0
  127. package/output/core/classes/expression/Expression.d.ts +1674 -0
  128. package/output/core/classes/expression/Expression.js +2332 -0
  129. package/output/core/classes/expression/analysis.d.ts +25 -0
  130. package/output/core/classes/expression/analysis.js +218 -0
  131. package/output/core/classes/expression/collect.d.ts +9 -0
  132. package/output/core/classes/expression/collect.js +49 -0
  133. package/output/core/classes/expression/format.d.ts +45 -0
  134. package/output/core/classes/expression/format.js +217 -0
  135. package/output/core/classes/expression/products.d.ts +23 -0
  136. package/output/core/classes/expression/products.js +115 -0
  137. package/output/core/classes/expression/shortcuts.d.ts +35 -0
  138. package/output/core/classes/expression/shortcuts.js +129 -0
  139. package/output/core/classes/expression/traversal.d.ts +31 -0
  140. package/output/core/classes/expression/traversal.js +216 -0
  141. package/output/core/classes/expression/trig.d.ts +25 -0
  142. package/output/core/classes/expression/trig.js +40 -0
  143. package/output/core/classes/expression/utils.d.ts +52 -0
  144. package/output/core/classes/expression/utils.js +189 -0
  145. package/output/core/classes/lookupTable/LookupTable.d.ts +12 -0
  146. package/output/core/classes/lookupTable/LookupTable.js +55 -0
  147. package/output/core/classes/matrix/Matrix.d.ts +227 -0
  148. package/output/core/classes/matrix/Matrix.js +889 -0
  149. package/output/core/classes/matrix/Sylvester.d.ts +11 -0
  150. package/output/core/classes/matrix/Sylvester.js +54 -0
  151. package/output/core/classes/matrix/functions.d.ts +26 -0
  152. package/output/core/classes/matrix/functions.js +52 -0
  153. package/output/core/classes/matrix/utils.d.ts +9 -0
  154. package/output/core/classes/matrix/utils.js +22 -0
  155. package/output/core/classes/parser/Parser.d.ts +480 -0
  156. package/output/core/classes/parser/Parser.js +1812 -0
  157. package/output/core/classes/parser/Token.d.ts +58 -0
  158. package/output/core/classes/parser/Token.js +137 -0
  159. package/output/core/classes/parser/constants.d.ts +160 -0
  160. package/output/core/classes/parser/constants.js +227 -0
  161. package/output/core/classes/parser/helpers.d.ts +21 -0
  162. package/output/core/classes/parser/helpers.js +42 -0
  163. package/output/core/classes/parser/operations/add.d.ts +15 -0
  164. package/output/core/classes/parser/operations/add.js +238 -0
  165. package/output/core/classes/parser/operations/comma.d.ts +7 -0
  166. package/output/core/classes/parser/operations/comma.js +11 -0
  167. package/output/core/classes/parser/operations/compare.d.ts +21 -0
  168. package/output/core/classes/parser/operations/compare.js +461 -0
  169. package/output/core/classes/parser/operations/divide.d.ts +2 -0
  170. package/output/core/classes/parser/operations/divide.js +72 -0
  171. package/output/core/classes/parser/operations/functions.d.ts +46 -0
  172. package/output/core/classes/parser/operations/functions.js +430 -0
  173. package/output/core/classes/parser/operations/multiply.d.ts +11 -0
  174. package/output/core/classes/parser/operations/multiply.js +321 -0
  175. package/output/core/classes/parser/operations/power.d.ts +48 -0
  176. package/output/core/classes/parser/operations/power.js +673 -0
  177. package/output/core/classes/parser/operations/subtract.d.ts +3 -0
  178. package/output/core/classes/parser/operations/subtract.js +35 -0
  179. package/output/core/classes/parser/preprocess.d.ts +14 -0
  180. package/output/core/classes/parser/preprocess.js +259 -0
  181. package/output/core/classes/parser/scripting/controlFlow.d.ts +121 -0
  182. package/output/core/classes/parser/scripting/controlFlow.js +355 -0
  183. package/output/core/classes/parser/scripting/evaluate.d.ts +10 -0
  184. package/output/core/classes/parser/scripting/evaluate.js +112 -0
  185. package/output/core/classes/parser/scripting/functions.d.ts +14 -0
  186. package/output/core/classes/parser/scripting/functions.js +59 -0
  187. package/output/core/classes/parser/scripting/scope.d.ts +35 -0
  188. package/output/core/classes/parser/scripting/scope.js +155 -0
  189. package/output/core/classes/parser/types.d.ts +125 -0
  190. package/output/core/classes/parser/types.js +2 -0
  191. package/output/core/classes/parser/wrappers/IndexedReference.d.ts +31 -0
  192. package/output/core/classes/parser/wrappers/IndexedReference.js +68 -0
  193. package/output/core/classes/parser/wrappers/KeyValuePair.d.ts +24 -0
  194. package/output/core/classes/parser/wrappers/KeyValuePair.js +51 -0
  195. package/output/core/classes/polynomial/Polynomial.d.ts +500 -0
  196. package/output/core/classes/polynomial/Polynomial.js +1149 -0
  197. package/output/core/classes/polynomial/Term.d.ts +222 -0
  198. package/output/core/classes/polynomial/Term.js +430 -0
  199. package/output/core/classes/polynomial/functions.d.ts +69 -0
  200. package/output/core/classes/polynomial/functions.js +126 -0
  201. package/output/core/classes/polynomial/utils.d.ts +62 -0
  202. package/output/core/classes/polynomial/utils.js +206 -0
  203. package/output/core/classes/rational/Rational.d.ts +504 -0
  204. package/output/core/classes/rational/Rational.js +779 -0
  205. package/output/core/classes/seq/SEQ.d.ts +13 -0
  206. package/output/core/classes/seq/SEQ.js +137 -0
  207. package/output/core/classes/valuesSet/ValuesSet.d.ts +179 -0
  208. package/output/core/classes/valuesSet/ValuesSet.js +381 -0
  209. package/output/core/classes/vector/Vector.d.ts +222 -0
  210. package/output/core/classes/vector/Vector.js +497 -0
  211. package/output/core/classes/vector/functions.d.ts +28 -0
  212. package/output/core/classes/vector/functions.js +47 -0
  213. package/output/core/common/classes/MathematicalAggregate.d.ts +53 -0
  214. package/output/core/common/classes/MathematicalAggregate.js +140 -0
  215. package/output/core/common/classes/Scope.d.ts +62 -0
  216. package/output/core/common/classes/Scope.js +122 -0
  217. package/output/core/common/classes/StructuredEntity.d.ts +55 -0
  218. package/output/core/common/classes/StructuredEntity.js +151 -0
  219. package/output/core/common/common.d.ts +63 -0
  220. package/output/core/common/common.js +279 -0
  221. package/output/core/common/functions/functions.d.ts +14 -0
  222. package/output/core/common/functions/functions.js +33 -0
  223. package/output/core/common/functions/structuredEntityUtils.d.ts +27 -0
  224. package/output/core/common/functions/structuredEntityUtils.js +40 -0
  225. package/output/core/converters/BaseConverter.d.ts +97 -0
  226. package/output/core/converters/BaseConverter.js +405 -0
  227. package/output/core/converters/Converter.d.ts +111 -0
  228. package/output/core/converters/Converter.js +802 -0
  229. package/output/core/converters/Pattern.d.ts +71 -0
  230. package/output/core/converters/Pattern.js +302 -0
  231. package/output/core/dispatch.d.ts +34 -0
  232. package/output/core/dispatch.js +304 -0
  233. package/output/core/errors.d.ts +531 -0
  234. package/output/core/errors.js +621 -0
  235. package/output/core/functions/bigint/bigint.d.ts +121 -0
  236. package/output/core/functions/bigint/bigint.js +392 -0
  237. package/output/core/functions/bigint/primeFactor.d.ts +49 -0
  238. package/output/core/functions/bigint/primeFactor.js +267 -0
  239. package/output/core/functions/bigint/primes.d.ts +1 -0
  240. package/output/core/functions/bigint/primes.js +11 -0
  241. package/output/core/functions/build/definitions.d.ts +12 -0
  242. package/output/core/functions/build/definitions.js +133 -0
  243. package/output/core/functions/build/index.d.ts +32 -0
  244. package/output/core/functions/build/index.js +136 -0
  245. package/output/core/functions/complex.d.ts +90 -0
  246. package/output/core/functions/complex.js +482 -0
  247. package/output/core/functions/decimal.d.ts +24 -0
  248. package/output/core/functions/decimal.js +664 -0
  249. package/output/core/functions/expand/expand.d.ts +42 -0
  250. package/output/core/functions/expand/expand.js +376 -0
  251. package/output/core/functions/fresnelNumeric.d.ts +12 -0
  252. package/output/core/functions/fresnelNumeric.js +123 -0
  253. package/output/core/functions/numeric.d.ts +140 -0
  254. package/output/core/functions/numeric.js +790 -0
  255. package/output/core/functions/rationalNormalization.d.ts +11 -0
  256. package/output/core/functions/rationalNormalization.js +117 -0
  257. package/output/core/functions/setFunction.d.ts +19 -0
  258. package/output/core/functions/setFunction.js +75 -0
  259. package/output/core/functions/string.d.ts +26 -0
  260. package/output/core/functions/string.js +101 -0
  261. package/output/core/functions/subst.d.ts +90 -0
  262. package/output/core/functions/subst.js +498 -0
  263. package/output/core/functions/utils.d.ts +24 -0
  264. package/output/core/functions/utils.js +51 -0
  265. package/output/core/types.d.ts +48 -0
  266. package/output/core/types.js +2 -0
  267. package/output/index.d.ts +339 -0
  268. package/output/index.js +715 -0
  269. package/output/math/defint/defintDecimal.d.ts +37 -0
  270. package/output/math/defint/defintDecimal.js +320 -0
  271. package/output/math/defint/defintNative.d.ts +57 -0
  272. package/output/math/defint/defintNative.js +292 -0
  273. package/output/math/geometry.d.ts +8 -0
  274. package/output/math/geometry.js +32 -0
  275. package/output/math/math.d.ts +655 -0
  276. package/output/math/math.js +1811 -0
  277. package/output/math/trig.d.ts +518 -0
  278. package/output/math/trig.js +1444 -0
  279. package/output/math/trunc.d.ts +19 -0
  280. package/output/math/trunc.js +36 -0
  281. package/output/math/utils.d.ts +60 -0
  282. package/output/math/utils.js +180 -0
  283. package/output/solve/classes/DecimalMatrix.d.ts +17 -0
  284. package/output/solve/classes/DecimalMatrix.js +86 -0
  285. package/output/solve/classes/FunctionSolver.d.ts +135 -0
  286. package/output/solve/classes/FunctionSolver.js +433 -0
  287. package/output/solve/classes/MultivariateSolver.d.ts +84 -0
  288. package/output/solve/classes/MultivariateSolver.js +240 -0
  289. package/output/solve/classes/PolynomialSolver.d.ts +85 -0
  290. package/output/solve/classes/PolynomialSolver.js +292 -0
  291. package/output/solve/classes/SolutionSet.d.ts +153 -0
  292. package/output/solve/classes/SolutionSet.js +357 -0
  293. package/output/solve/classes/Solver.d.ts +17 -0
  294. package/output/solve/classes/Solver.js +132 -0
  295. package/output/solve/classes/SymbolicSolver.d.ts +74 -0
  296. package/output/solve/classes/SymbolicSolver.js +601 -0
  297. package/output/solve/linsolve.d.ts +51 -0
  298. package/output/solve/linsolve.js +225 -0
  299. package/output/solve/solve.d.ts +34 -0
  300. package/output/solve/solve.js +260 -0
  301. package/output/solve/solveSystem.d.ts +31 -0
  302. package/output/solve/solveSystem.js +290 -0
  303. package/output/solve/utils.d.ts +9 -0
  304. package/output/solve/utils.js +62 -0
  305. package/output/utils/array.d.ts +42 -0
  306. package/output/utils/array.js +98 -0
  307. package/output/utils/debug.d.ts +131 -0
  308. package/output/utils/debug.js +295 -0
  309. package/output/utils/decimal.d.ts +3 -0
  310. package/output/utils/decimal.js +16 -0
  311. package/output/utils/numeric.d.ts +14 -0
  312. package/output/utils/numeric.js +51 -0
  313. package/output/utils/object.d.ts +26 -0
  314. package/output/utils/object.js +56 -0
  315. package/package.json +213 -57
  316. package/.travis.yml +0 -3
  317. package/Algebra.js +0 -4568
  318. package/BREAKING_CHANGES.md +0 -5
  319. package/CODE_OF_CONDUCT.md +0 -46
  320. package/CONTRIBUTING.md +0 -14
  321. package/Calculus.js +0 -2675
  322. package/Extra.js +0 -622
  323. package/Solve.js +0 -1783
  324. package/all.js +0 -16
  325. package/all.min.js +0 -1
  326. package/gulpfile.js +0 -16
  327. package/index.html +0 -158
  328. package/license.txt +0 -19
  329. package/nerdamer.core.js +0 -12510
  330. package/spec/LaTeX.spec.js +0 -302
  331. package/spec/TeXConvert.spec.js +0 -8
  332. package/spec/algebra.spec.js +0 -287
  333. package/spec/basic_parser.spec.js +0 -134
  334. package/spec/build.spec.js +0 -131
  335. package/spec/calculus.spec.js +0 -183
  336. package/spec/core.spec.js +0 -2970
  337. package/spec/extra.spec.js +0 -54
  338. package/spec/solve.spec.js +0 -126
  339. package/spec/support/jasmine.json +0 -11
  340. package/spec/support/utils.js +0 -42
  341. 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
+ }