Skip to content

Commit 4981897

Browse files
NAThompsonclaude
andcommitted
Polynomial roots
Imbach-Moroz piecewise root finder (arXiv:2302.06244), whose sector DFTs use tools::fft, and the Aurentz-Mach-Vandebril-Watkins companion QR: O(n^2), normwise backward stable. Python bindings for both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 0f983c7 commit 4981897

13 files changed

Lines changed: 1339 additions & 1 deletion

File tree

‎.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ build.ninja
3131
.ninja*
3232
a.out
3333
*.json
34+
*.patch
3435

3536
# Test executables built in place (extensionless), but not the sources or the test_* directories:
3637
/test/test_*

‎doc/roots/polynomial_roots.qbk‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
[section:polynomial_roots Complex Roots of a Polynomial]
2+
3+
``
4+
#include <boost/math/tools/polynomial_roots.hpp>
5+
#include <boost/math/tools/amvw_roots.hpp>
6+
``
7+
8+
template <class Real>
9+
std::vector<std::complex<Real> >
10+
polynomial_roots(std::vector<Real> coefficients,
11+
unsigned precision = std::numeric_limits<Real>::digits);
12+
13+
template <class Real>
14+
std::vector<std::complex<Real> >
15+
amvw_roots(std::vector<Real> coefficients);
16+
17+
`polynomial_roots` computes all finite complex roots of a polynomial with real,
18+
floating-point coefficients. Coefficients are supplied in ascending order, so
19+
`{a0, a1, ..., an}` represents
20+
[@https://en.wikipedia.org/wiki/Polynomial a0 + a1 z + ... + an z^n].
21+
The returned order is unspecified.
22+
23+
The implementation follows the piecewise approximation strategy of Imbach and
24+
Moroz: a Newton-polygon sweep divides the complex plane into radial rings, the
25+
rings are divided into angular sectors, and low-degree Taylor polynomials are
26+
factored locally. Candidate roots are polished and checked against the input
27+
polynomial. A global Aberth iteration is used as a safety net for unresolved or
28+
ill-conditioned clusters.
29+
30+
Exact leading zero coefficients are ignored. Exact trailing zero coefficients
31+
produce the corresponding number of roots at zero. The zero polynomial, a zero
32+
precision request, and non-finite coefficients cause `std::domain_error` to be
33+
thrown.
34+
35+
The optional `precision` argument is measured in bits and controls the local
36+
approximation order. Its default is appropriate for the coefficient type.
37+
38+
[heading The companion QR algorithm]
39+
40+
`amvw_roots` computes the same roots as the eigenvalues of the companion matrix,
41+
by the core-chasing QR algorithm of Aurentz, Mach, Vandebril and Watkins. It
42+
stores the companion matrix as O(n) 2x2 unitary rotations, so it takes O(n[super 2])
43+
time and O(n) memory, against O(n[super 3]) and O(n[super 2]) for a dense
44+
eigensolver. Before the iteration the variable is scaled so that the constant
45+
and leading coefficients have equal magnitude.
46+
47+
The two functions make different guarantees. `amvw_roots` is proven normwise
48+
backward stable: all of its roots are the exact roots of a single polynomial
49+
whose coefficient vector is within a modest multiple of n[epsilon] of the input,
50+
relative to the norm of the coefficients. `polynomial_roots` checks a stronger,
51+
coefficientwise, bound on each root: each one is an exact root of a polynomial
52+
whose coefficients each differ from the input in only the last few bits.
53+
It has no a priori proof of convergence, but it returns only roots that
54+
pass this check. The coefficientwise bound matters when the roots span many
55+
orders of magnitude, as in [prod](z - 10[super -k]): the small roots are
56+
determined by the small coefficients, which a normwise perturbation may change
57+
completely, so `amvw_roots` gets the small roots wrong while `polynomial_roots`
58+
gets them to nearly full precision. For roots of comparable magnitude the
59+
two agree.
60+
61+
Exact zero coefficients are handled as for `polynomial_roots`. The zero
62+
polynomial and non-finite coefficients cause `std::domain_error` to be
63+
thrown, and `std::runtime_error` is thrown in the unlikely event that the QR
64+
iteration fails to converge.
65+
66+
[heading Example]
67+
68+
std::vector<double> p{1, 0, 1}; // 1 + z^2
69+
auto roots = boost::math::tools::polynomial_roots(p);
70+
71+
The result contains approximations to `i` and `-i`.
72+
73+
[heading References]
74+
75+
J. Imbach and G. Moroz, "Fast evaluation and root finding for polynomials with
76+
floating-point coefficients", 2023.
77+
78+
J. L. Aurentz, T. Mach, R. Vandebril and D. S. Watkins, "Fast and backward
79+
stable computation of roots of polynomials", SIAM J. Matrix Anal. Appl. 36
80+
(2015), 942-973.
81+
82+
J. L. Aurentz, T. Mach, L. Robol, R. Vandebril and D. S. Watkins, "Fast and
83+
backward stable computation of roots of polynomials, Part II: backward error
84+
analysis; companion matrix and companion pencil", SIAM J. Matrix Anal. Appl.
85+
39 (2018), 1245-1269.
86+
87+
[endsect]

‎doc/roots/roots_overview.qbk‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ There are several fully-worked __root_finding_examples, including:
1919
[include roots.qbk]
2020
[include cubic_roots.qbk]
2121
[include quartic_roots.qbk]
22+
[include polynomial_roots.qbk]
2223
[include root_finding_examples.qbk]
2324
[include minima.qbk]
2425
[include root_comparison.qbk]

0 commit comments

Comments
 (0)