You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
EquationSystem.Solve cannot solve coupled polynomial systems. It dies at four coupled variables:
system
current
2 coupled equations, 2 vars
2 ms
3 coupled equations, 3 vars
2 500 ms
4 coupled equations, 4 vars
no answer in 300 s
This is not a size problem. Four uncoupled quartics producing 256 solutions take 17 ms, and a degree-8 univariate takes 12 ms.
Root cause
EquationSolver.InSolveSystem eliminates one variable at a time by calling SolveEquation, which applies closed-form radical formulas (quadratic, Cardano, Ferrari). With numeric coefficients that is cheap; with symbolic ones it is not:
Each elimination turns the next step's coefficients into nested radicals, so size compounds multiplicatively, and it repeats per branch. Traced on the 4-variable case:
eq[0..3] (deg 1,2,3,4) 19 nodes each
eliminate x_3 via the linear equation → 25 nodes each
eliminate x_2 via the quadratic → 109 nodes (coefficients now radicals)
eliminate x_1 via the resulting cubic → 3698 nodes, 14.1 s for ONE branch
Gröbner bases avoid this: they triangularise so the last variable satisfies a univariate polynomial with rational coefficients, and back-substitution then works with numbers rather than symbols.
Measured with a prototype
A throwaway lex Buchberger over the same packed representation MultivariatePolynomial already uses (one byte per variable in a ulong, ERational coefficients — so lex monomial comparison is just ulong comparison, and LeadingMonomial() already exists):
system
current
prototype
4 coupled variables
>300 s
0 ms
5 coupled variables
>60 s
3 ms
6 coupled variables
>60 s
14 ms
The 4-variable case emits exactly x⁴ - 10x³ + 35x² - 50x + 24, which the existing PolynomialSolver already solves in 3 ms — so the end-to-end path is milliseconds.
Over-determined systems work natively, including inconsistency, which the current API cannot even express because it throws on the equation-count mismatch:
system
prototype
circle + line (2 eq, 2 var)
basis 2, correct
circle + line + a redundant consequence (3 eq, 2 var)
basis 2, same answer
circle + line + an inconsistent equation
basis {1} — the textbook "no solutions"
Sketch
GroebnerSystemSolver in front of EquationSolver.SolveSystem. If every equation passes MultivariatePolynomial.TryParse (polynomial, rational coefficients, ≤ 8 variables) take the Gröbner path; otherwise fall through to today's InSolveSystem unchanged, so nothing that works now changes.
Buchberger over lex, with the Gebauer–Möller pair criteria. Lex because the packed ulongis the lex comparison and lex directly yields the triangular form. The prototype's criteria skipped 90–96 % of pairs.
Back-substitution hands the univariate to the existing PolynomialSolver, inheriting its rational → radical → numeric convention rather than introducing a RootOf node.
A hard budget on pairs, basis size, intermediate terms and wall time, falling back rather than hanging. Buchberger is doubly exponential in the worst case.
Drop #eqs == #vars on the Gröbner path.
Two things the prototype disproved
Encoding dominates, and the implementation must pass the user's equations through unmodified rather than trying to be clever:
encoding
prototype
degree-4 membership generators + 2 power sums, 5 vars
gave up after 100 s
the same system as low-degree power sums
3 ms
sum-of-squares compression into fewer equations (degree 8)
gave up after 167 s
the same constraints uncompressed
0 ms
Known gaps
Back-substitution degrades to floating point once a root is not expressible in radicals, and deciding whether a residual is zero then becomes a tolerance question — the PrecisionErrorZeroRange trap from Precision is being lost somewhere #602. The real fix is algebraic-number arithmetic, which the library does not have; that is arguably the more foundational piece and worth its own issue.
The 8-variable cap of MultivariatePolynomial stays, so this covers systems users actually type, not large combinatorial ones.
Coefficient growth was not measured by the prototype.
EquationSystem.Solvecannot solve coupled polynomial systems. It dies at four coupled variables:This is not a size problem. Four uncoupled quartics producing 256 solutions take 17 ms, and a degree-8 univariate takes 12 ms.
Root cause
EquationSolver.InSolveSystemeliminates one variable at a time by callingSolveEquation, which applies closed-form radical formulas (quadratic, Cardano, Ferrari). With numeric coefficients that is cheap; with symbolic ones it is not:x⁴ - 10x³ + 35x² - 50x + 24(numeric coefficients)x⁴ + ax³ + bx² + cx + d(symbolic coefficients)Each elimination turns the next step's coefficients into nested radicals, so size compounds multiplicatively, and it repeats per branch. Traced on the 4-variable case:
Gröbner bases avoid this: they triangularise so the last variable satisfies a univariate polynomial with rational coefficients, and back-substitution then works with numbers rather than symbols.
Measured with a prototype
A throwaway lex Buchberger over the same packed representation
MultivariatePolynomialalready uses (one byte per variable in aulong,ERationalcoefficients — so lex monomial comparison is justulongcomparison, andLeadingMonomial()already exists):The 4-variable case emits exactly
x⁴ - 10x³ + 35x² - 50x + 24, which the existingPolynomialSolveralready solves in 3 ms — so the end-to-end path is milliseconds.Over-determined systems work natively, including inconsistency, which the current API cannot even express because it throws on the equation-count mismatch:
{1}— the textbook "no solutions"Sketch
GroebnerSystemSolverin front ofEquationSolver.SolveSystem. If every equation passesMultivariatePolynomial.TryParse(polynomial, rational coefficients, ≤ 8 variables) take the Gröbner path; otherwise fall through to today'sInSolveSystemunchanged, so nothing that works now changes.ulongis the lex comparison and lex directly yields the triangular form. The prototype's criteria skipped 90–96 % of pairs.PolynomialSolver, inheriting its rational → radical → numeric convention rather than introducing aRootOfnode.#eqs == #varson the Gröbner path.Two things the prototype disproved
Encoding dominates, and the implementation must pass the user's equations through unmodified rather than trying to be clever:
Known gaps
PrecisionErrorZeroRangetrap from Precision is being lost somewhere #602. The real fix is algebraic-number arithmetic, which the library does not have; that is arguably the more foundational piece and worth its own issue.MultivariatePolynomialstays, so this covers systems users actually type, not large combinatorial ones.Additive, so 2.1 rather than 2.0.