Skip to content

A radical of a constant is a coefficient, and a polynomial with real coefficients has all its roots - #2326

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
a-radical-of-a-constant-is-a-coefficient
Nov 2, 2026
Merged

Rafael-SOWNet merged 1 commit into
masterfrom
a-radical-of-a-constant-is-a-coefficient

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

An equation with a radical of a constant among its coefficients could end the process with a stack overflow, and a polynomial with one missed its far roots.

The overflow. FractionedPolynoms raises an equation to a power to remove a radical, and it took any power with a fractional exponent for one, sqrt(2) among them. Raising x^5 + x - sqrt(2) = 0 gives (x^5 + x)^2 = 2, and the substitution that solves that gives back x^5 + x = sqrt(2), the equation it started from, so the solver recursed until the stack ran out. 2.6.0 and master both crash on it. Since #2323, which reads a whole power times a radical, the same loop is reached from sqrt(y) - x = sqrt(2) - 1, |x| + y = 3 with y eliminated, which is where a check of generated systems found it. Only a power whose base contains x is now a radical to raise; a radical of a constant is a coefficient.

The far roots. With the radical a coefficient, such a polynomial goes on to the numerical routes. #2324 answers a polynomial with rational coefficients by the Durand–Kerner iteration, all roots or none, and anything else went to Newton's method, which starts inside a bounded region and misses a root outside it. DurandKerner.RootsOf now reads a polynomial whose coefficients are real constants, sqrt(2) or pi, and runs the same iteration on their values. Those values are known to the working precision rather than exactly, so a repeated root cannot be told from a cluster. A root of multiplicity m splits by about the m-th root of that error, so approximations closer than 10^(-digits / (2n)) for degree n are taken not to be separated, and it answers nothing then. A coefficient smaller than the largest by more than half the digits may be an unsimplified zero, so it answers nothing for that too.

Generated polynomials of degree 5 to 8, one coefficient times sqrt(2) or sqrt(3), checked against mpmath's polyroots, 30 s per polynomial:

200 at seed 7 2.6.0 master this branch
polynomials timed out 95 154 0
polynomials answered in more than 3 s 64 37 0
roots missed 67 0 0
non-roots answered 12 6 0

At seeds 8 and 9 this branch has no findings either. The same check without the radical, at seeds 5 and 6, has none, the same as master.

Against master, everything else is unchanged:

master this branch
radical cross-check, powers-of-x mode, 1000 at seed 41 1 non-root answered (#2325's) the same
the same mode, 200 at seed 31 nothing nothing
default mode, 400 at seed 21 39 unsolved, nothing missed or wrong the same
generated systems, 1190 2 timeouts, 60 unsolved, 0 wrong rows 0 timeouts, 62 unsolved, 0 wrong rows

The two systems that time out on master are the ones above, sqrt(y) - x = sqrt(2) - 1, |x| + y = 3 and its sqrt(-2) twin. They are now unsolved, as they were on master before #2323.

BREAKING-CHANGES.md has an entry. Tests, in ARadicalTimesAPowerTest:

  • ARadicalOfAConstantIsNotRaised
  • APolynomialWithARadicalCoefficientIsAnswered
  • APolynomialWithRealCoefficientsHasAllItsRoots
  • ASystemThatEliminatesToOneFinishes

Each fails on master. APolynomialWithRealCoefficientsHasAllItsRoots also fails with the overflow fix alone.

🤖 Generated with Claude Code

…l with real coefficients by Durand-Kerner

FractionedPolynoms raises an equation to a power to remove a radical, and it
took every power with a fractional exponent for one, sqrt(2) among them.
Raising x^5 + x - sqrt(2) = 0 gives (x^5 + x)^2 = 2, whose substitution gives
back x^5 + x = sqrt(2), so the solver recursed until the stack ran out. Since
a whole power times a radical is read as one, the same loop is reached from
sqrt(y) - x = sqrt(2) - 1, |x| + y = 3 with y eliminated. Only a power whose
base contains x is a radical to raise now.

Such a polynomial then went to Newton's method, which starts inside a bounded
region and misses a root outside it. DurandKerner.RootsOf reads a polynomial
whose coefficients are real constants and runs the iteration on their values.
They are known to the working precision only, so a root of multiplicity m
splits by about the m-th root of that error; approximations closer than
10^(-digits / (2n)) are taken not to be separated, and it answers nothing.

Generated polynomials of degree 5 to 8 with one coefficient times sqrt(2) or
sqrt(3), 200 at seed 7 against mpmath: 2.6.0 timed out on 95, missed 67 roots
and answered 12 non-roots; master timed out on 154 and answered 6 non-roots;
this has no findings, nor at seeds 8 and 9. Integer polynomials at seeds 5
and 6, the radical cross-check at seeds 21, 31 and 41, and 1190 generated
systems are as on master, except that two systems that timed out are
unsolved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Rafael-SOWNet Rafael-SOWNet added this to the 2.7.0 milestone Nov 2, 2026
@Rafael-SOWNet Rafael-SOWNet moved this from Todo to In review in AngouriMath Nov 2, 2026
@codecov

ghost commented Nov 2, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.74359% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.74%. Comparing base (1e5318c) to head (c05ea20).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
...ontinuous/Solvers/NumericalSolving/DurandKerner.cs 88.57% 2 Missing and 2 partials ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2326      +/-   ##
==========================================
- Coverage   86.75%   86.74%   -0.02%     
==========================================
  Files         312      312              
  Lines       68388    68422      +34     
  Branches    19889    19899      +10     
==========================================
+ Hits        59331    59350      +19     
- Misses       4900     4915      +15     
  Partials     4157     4157              
Flag Coverage Δ
cpp 37.77% <ø> (ø)
csharp 87.28% <89.74%> (-0.02%) ⬇️
fsharp 32.97% <ø> (ø)
interactive 40.72% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...Solvers/EquationSolver/AnalyticalEquationSolver.cs 96.00% <100.00%> (+0.05%) ⬆️
...nuous/Solvers/EquationSolver/FractionedPolynoms.cs 81.81% <100.00%> (-9.10%) ⬇️
...ontinuous/Solvers/NumericalSolving/DurandKerner.cs 86.51% <88.57%> (+0.55%) ⬆️

... and 10 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Rafael-SOWNet
Rafael-SOWNet merged commit a2604a4 into master Nov 2, 2026
@github-project-automation github-project-automation Bot moved this from In review to Done in AngouriMath Nov 2, 2026
@Rafael-SOWNet
Rafael-SOWNet deleted the a-radical-of-a-constant-is-a-coefficient branch November 2, 2026 03:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant