blob: 34d4148b438f3ccfd59405241edae52bf7ccbfe5 [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2010 Manuel Yguel <manuel.yguel@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
#ifndef EIGEN_POLYNOMIAL_SOLVER_H
#define EIGEN_POLYNOMIAL_SOLVER_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
namespace Eigen {
/** \ingroup Polynomials_Module
* \class PolynomialSolverBase.
*
* \brief Defined to be inherited by polynomial solvers: it provides
* convenient methods such as
* - real roots,
* - greatest, smallest complex roots,
* - real roots with greatest, smallest absolute real value,
* - greatest, smallest real roots.
*
* It stores the set of roots as a vector of complexes.
*
*/
template <typename Scalar_, int Deg_>
class PolynomialSolverBase {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF_VECTORIZABLE_FIXED_SIZE(Scalar_, Deg_ == Dynamic ? Dynamic : Deg_)
typedef Scalar_ Scalar;
typedef typename NumTraits<Scalar>::Real RealScalar;
typedef internal::make_complex_t<Scalar> RootType;
typedef Matrix<RootType, Deg_, 1> RootsType;
typedef DenseIndex Index;
protected:
template <typename OtherPolynomial>
inline void setPolynomial(const OtherPolynomial& poly) {
m_roots.resize(poly.size() - 1);
}
public:
template <typename OtherPolynomial>
inline PolynomialSolverBase(const OtherPolynomial& poly) {
setPolynomial(poly());
}
inline PolynomialSolverBase() {}
public:
/** \returns the complex roots of the polynomial */
inline const RootsType& roots() const { return m_roots; }
public:
/** Clear and fills the back insertion sequence with the real roots of the polynomial
* i.e. the real part of the complex roots that have an imaginary part which
* absolute value is smaller than absImaginaryThreshold.
* absImaginaryThreshold takes the dummy_precision associated
* with the Scalar_ template parameter of the PolynomialSolver class as the default value.
*
* \param[out] bi_seq : the back insertion sequence (stl concept)
* \param[in] absImaginaryThreshold : the maximum bound of the imaginary part of a complex
* number that is considered as real.
* */
template <typename Stl_back_insertion_sequence>
inline void realRoots(Stl_back_insertion_sequence& bi_seq,
const RealScalar& absImaginaryThreshold = NumTraits<Scalar>::dummy_precision()) const {
using std::abs;
bi_seq.clear();
for (Index i = 0; i < m_roots.size(); ++i) {
if (abs(m_roots[i].imag()) < absImaginaryThreshold) {
bi_seq.push_back(m_roots[i].real());
}
}
}
protected:
template <typename Predicate>
inline const RootType& selectComplexRoot_withRespectToNorm(Predicate& pred) const {
Index res = 0;
RealScalar norm2 = numext::abs2(m_roots[0]);
for (Index i = 1; i < m_roots.size(); ++i) {
const RealScalar currNorm2 = numext::abs2(m_roots[i]);
if (pred(currNorm2, norm2)) {
res = i;
norm2 = currNorm2;
}
}
return m_roots[res];
}
public:
/**
* \returns the complex root with greatest norm.
*/
inline const RootType& greatestRoot() const {
std::greater<RealScalar> greater;
return selectComplexRoot_withRespectToNorm(greater);
}
/**
* \returns the complex root with smallest norm.
*/
inline const RootType& smallestRoot() const {
std::less<RealScalar> less;
return selectComplexRoot_withRespectToNorm(less);
}
protected:
template <typename Predicate>
inline const RealScalar& selectRealRoot_withRespectToAbsRealPart(
Predicate& pred, bool& hasArealRoot,
const RealScalar& absImaginaryThreshold = NumTraits<Scalar>::dummy_precision()) const {
using std::abs;
hasArealRoot = false;
Index res = 0;
RealScalar val(0);
for (Index i = 0; i < m_roots.size(); ++i) {
if (abs(m_roots[i].imag()) <= absImaginaryThreshold) {
if (!hasArealRoot) {
hasArealRoot = true;
res = i;
val = abs(m_roots[i].real());
} else {
const RealScalar curr = abs(m_roots[i].real());
if (pred(curr, val)) {
val = curr;
res = i;
}
}
} else if (!hasArealRoot) {
if (abs(m_roots[i].imag()) < abs(m_roots[res].imag())) {
res = i;
}
}
}
return numext::real_ref(m_roots[res]);
}
template <typename Predicate>
inline const RealScalar& selectRealRoot_withRespectToRealPart(
Predicate& pred, bool& hasArealRoot,
const RealScalar& absImaginaryThreshold = NumTraits<Scalar>::dummy_precision()) const {
using std::abs;
hasArealRoot = false;
Index res = 0;
RealScalar val(0);
for (Index i = 0; i < m_roots.size(); ++i) {
if (abs(m_roots[i].imag()) <= absImaginaryThreshold) {
if (!hasArealRoot) {
hasArealRoot = true;
res = i;
val = m_roots[i].real();
} else {
const RealScalar curr = m_roots[i].real();
if (pred(curr, val)) {
val = curr;
res = i;
}
}
} else {
if (abs(m_roots[i].imag()) < abs(m_roots[res].imag())) {
res = i;
}
}
}
return numext::real_ref(m_roots[res]);
}
public:
/**
* \returns a real root with greatest absolute magnitude.
* A real root is defined as the real part of a complex root with absolute imaginary
* part smaller than absImaginaryThreshold.
* absImaginaryThreshold takes the dummy_precision associated
* with the Scalar_ template parameter of the PolynomialSolver class as the default value.
* If no real root is found the boolean hasArealRoot is set to false and the real part of
* the root with smallest absolute imaginary part is returned instead.
*
* \param[out] hasArealRoot : boolean true if a real root is found according to the
* absImaginaryThreshold criterion, false otherwise.
* \param[in] absImaginaryThreshold : threshold on the absolute imaginary part to decide
* whether or not a root is real.
*/
inline const RealScalar& absGreatestRealRoot(
bool& hasArealRoot, const RealScalar& absImaginaryThreshold = NumTraits<Scalar>::dummy_precision()) const {
std::greater<RealScalar> greater;
return selectRealRoot_withRespectToAbsRealPart(greater, hasArealRoot, absImaginaryThreshold);
}
/**
* \returns a real root with smallest absolute magnitude.
* A real root is defined as the real part of a complex root with absolute imaginary
* part smaller than absImaginaryThreshold.
* absImaginaryThreshold takes the dummy_precision associated
* with the Scalar_ template parameter of the PolynomialSolver class as the default value.
* If no real root is found the boolean hasArealRoot is set to false and the real part of
* the root with smallest absolute imaginary part is returned instead.
*
* \param[out] hasArealRoot : boolean true if a real root is found according to the
* absImaginaryThreshold criterion, false otherwise.
* \param[in] absImaginaryThreshold : threshold on the absolute imaginary part to decide
* whether or not a root is real.
*/
inline const RealScalar& absSmallestRealRoot(
bool& hasArealRoot, const RealScalar& absImaginaryThreshold = NumTraits<Scalar>::dummy_precision()) const {
std::less<RealScalar> less;
return selectRealRoot_withRespectToAbsRealPart(less, hasArealRoot, absImaginaryThreshold);
}
/**
* \returns the real root with greatest value.
* A real root is defined as the real part of a complex root with absolute imaginary
* part smaller than absImaginaryThreshold.
* absImaginaryThreshold takes the dummy_precision associated
* with the Scalar_ template parameter of the PolynomialSolver class as the default value.
* If no real root is found the boolean hasArealRoot is set to false and the real part of
* the root with smallest absolute imaginary part is returned instead.
*
* \param[out] hasArealRoot : boolean true if a real root is found according to the
* absImaginaryThreshold criterion, false otherwise.
* \param[in] absImaginaryThreshold : threshold on the absolute imaginary part to decide
* whether or not a root is real.
*/
inline const RealScalar& greatestRealRoot(
bool& hasArealRoot, const RealScalar& absImaginaryThreshold = NumTraits<Scalar>::dummy_precision()) const {
std::greater<RealScalar> greater;
return selectRealRoot_withRespectToRealPart(greater, hasArealRoot, absImaginaryThreshold);
}
/**
* \returns the real root with smallest value.
* A real root is defined as the real part of a complex root with absolute imaginary
* part smaller than absImaginaryThreshold.
* absImaginaryThreshold takes the dummy_precision associated
* with the Scalar_ template parameter of the PolynomialSolver class as the default value.
* If no real root is found the boolean hasArealRoot is set to false and the real part of
* the root with smallest absolute imaginary part is returned instead.
*
* \param[out] hasArealRoot : boolean true if a real root is found according to the
* absImaginaryThreshold criterion, false otherwise.
* \param[in] absImaginaryThreshold : threshold on the absolute imaginary part to decide
* whether or not a root is real.
*/
inline const RealScalar& smallestRealRoot(
bool& hasArealRoot, const RealScalar& absImaginaryThreshold = NumTraits<Scalar>::dummy_precision()) const {
std::less<RealScalar> less;
return selectRealRoot_withRespectToRealPart(less, hasArealRoot, absImaginaryThreshold);
}
protected:
RootsType m_roots;
};
#define EIGEN_POLYNOMIAL_SOLVER_BASE_INHERITED_TYPES(BASE) \
typedef typename BASE::Scalar Scalar; \
typedef typename BASE::RealScalar RealScalar; \
typedef typename BASE::RootType RootType; \
typedef typename BASE::RootsType RootsType;
/** \ingroup Polynomials_Module
*
* \class PolynomialSolver
*
* \brief A polynomial solver
*
* Computes the complex roots of a real polynomial.
*
* \param Scalar_ the scalar type, i.e., the type of the polynomial coefficients
* \param Deg_ the degree of the polynomial, can be a compile time value or Dynamic.
* Notice that the number of polynomial coefficients is Deg_+1.
*
* This class implements a polynomial solver and provides convenient methods such as
* - real roots,
* - greatest, smallest complex roots,
* - real roots with greatest, smallest absolute real value.
* - greatest, smallest real roots.
*
* WARNING: this polynomial solver is experimental, part of the contrib Eigen modules.
*
*
* The eigenvalues of the balanced companion matrix of the polynomial give first approximations of
* the roots. Ehrlich-Aberth iterations on the polynomial itself target a residual within the rounding
* error of evaluation, with a finite sweep limit and an initial-estimate fallback for unfinished
* iterates whose residual increased. The roots of a real polynomial are returned as real numbers or exact
* conjugate pairs. A root of multiplicity \f$ m \f$ can only be located to about
* \f$ \varepsilon^{1/m} \f$.
*/
template <typename Scalar_, int Deg_>
class PolynomialSolver : public PolynomialSolverBase<Scalar_, Deg_> {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW_IF_VECTORIZABLE_FIXED_SIZE(Scalar_, Deg_ == Dynamic ? Dynamic : Deg_)
typedef PolynomialSolverBase<Scalar_, Deg_> PS_Base;
EIGEN_POLYNOMIAL_SOLVER_BASE_INHERITED_TYPES(PS_Base)
typedef Matrix<Scalar, Deg_, Deg_> CompanionMatrixType;
typedef std::conditional_t<NumTraits<Scalar>::IsComplex, ComplexEigenSolver<CompanionMatrixType>,
EigenSolver<CompanionMatrixType> >
EigenSolverType;
typedef internal::make_complex_t<Scalar_> ComplexScalar;
public:
/** Computes the complex roots of a new polynomial. */
template <typename OtherPolynomial>
void compute(const OtherPolynomial& poly) {
eigen_assert(Scalar(0) != poly[poly.size() - 1]);
eigen_assert(poly.size() > 1);
if (poly.size() > 2) {
internal::companion<Scalar, Deg_> companion(poly);
companion.balance();
m_eigenSolver.compute(companion.denseMatrix());
eigen_assert(m_eigenSolver.info() == Eigen::Success);
m_roots = m_eigenSolver.eigenvalues();
// Uniform coefficient scaling must not overflow or underflow the refinement's Horner recurrences.
const RealScalar scale = poly.realView().cwiseAbs().maxCoeff();
const auto scaledPoly = (poly / scale).eval();
refineRoots(scaledPoly);
cleanUpRoots(scaledPoly);
} else if (poly.size() == 2) {
m_roots.resize(1);
m_roots[0] = -poly[0] / poly[1];
}
}
public:
template <typename OtherPolynomial>
inline PolynomialSolver(const OtherPolynomial& poly) {
compute(poly);
}
inline PolynomialSolver() {}
protected:
// Evaluates p(z) and p'(z) by Horner's rule. With withBound, returns Higham's running bound on the rounding error
// of the value (Higham 2002, Algorithm 5.1), otherwise zero: the moduli it needs cost more than the recurrence.
// The real bound is u (2 mu - |p(z)|), u = eps / 2; a complex product rounds by at most 2 sqrt(2) u |z| |y| and a
// complex sum by u |y|, so the complex bound is sqrt(2) eps (2 mu - |p(z)|).
template <typename OtherPolynomial>
static RealScalar evaluate(const OtherPolynomial& poly, const RootType& z, RootType& value, RootType& derivative,
bool withBound) {
const Index degree = poly.size() - 1;
value = RootType(poly[degree]);
derivative = RootType(0);
if (!withBound) {
for (Index k = degree - 1; k >= 0; --k) {
derivative = derivative * z + value;
value = value * z + RootType(poly[k]);
}
return RealScalar(0);
}
const RealScalar absz = numext::abs(z);
RealScalar mu = numext::abs(value) / RealScalar(2);
for (Index k = degree - 1; k >= 0; --k) {
derivative = derivative * z + value;
value = value * z + RootType(poly[k]);
mu = mu * absz + numext::abs(value);
}
const RealScalar errorScale = numext::sqrt(RealScalar(2)) * NumTraits<RealScalar>::epsilon();
return (RealScalar(2) * errorScale) * mu - errorScale * numext::abs(value);
}
/** Refines the eigenvalue estimates in m_roots by Ehrlich-Aberth iterations (Ehrlich 1967; Aberth 1973),
* \f$ z_i \leftarrow z_i - w_i / (1 - w_i \sum_{j \ne i} (z_i - z_j)^{-1}) \f$ with \f$ w_i = p(z_i) / p'(z_i) \f$.
* The iteration is cubic for simple roots, and the sum keeps the iterates apart, so every root is refined at
* once without deflation. Updates use the latest estimates; real starting values receive an epsilon-sized imaginary
* perturbation so they can reach nonreal roots. At the sweep limit, unfinished estimates are compared with
* their initial polynomial residuals. A root is final once \f$ |p(z_i)| \f$ is
* within the rounding bound of its evaluation, so that \f$ z_i \f$ is an exact root of a polynomial that close to
* \f$ p \f$ (the stopping rule of Bini 1996), or once its Newton correction is below one ulp. */
template <typename OtherPolynomial>
void refineRoots(const OtherPolynomial& poly) {
const Index n = m_roots.size();
const RealScalar eps = NumTraits<RealScalar>::epsilon();
// Convergence is not guaranteed for clustered roots; retain the eigenvalue estimates as a fallback.
const int maxSweeps = NumTraits<RealScalar>::digits();
EIGEN_IF_CONSTEXPR (!NumTraits<Scalar>::IsComplex) {
for (Index i = 0; i < n; ++i) {
// Real iterates cannot reach a nonreal root; perturb by +/- i eps |z|.
if (numext::imag(m_roots[i]) == RealScalar(0)) {
const RealScalar perturbation = eps * numext::abs(m_roots[i]);
m_roots[i] += RootType(0, i % 2 == 0 ? perturbation : -perturbation);
}
}
}
Array<bool, Deg_, 1> active = Array<bool, Deg_, 1>::Constant(n, true);
RootType value, derivative;
for (int sweep = 0; sweep < maxSweeps; ++sweep) {
bool moving = false;
for (Index i = 0; i < n; ++i) {
if (!active[i]) continue;
const RootType z = m_roots[i];
// The eigenvalues can pass the rounding test with a backward error of ~10 eps, while one step from them
// lands near eps, so the test applies from the second sweep on.
const bool testResidual = sweep > 0;
const RealScalar bound = evaluate(poly, z, value, derivative, testResidual);
if ((testResidual && !(numext::abs(value) > bound)) || !(numext::isfinite)(bound) ||
derivative == RootType(0)) {
active[i] = false;
continue;
}
const RootType newton = value / derivative;
if (!(numext::abs(newton) > eps * numext::abs(z))) {
active[i] = false;
continue;
}
RootType repulsion(0);
for (Index j = 0; j < n; ++j) {
const RootType gap = z - m_roots[j];
if (j != i && gap != RootType(0)) repulsion += RootType(1) / gap;
}
const RootType denominator = RootType(1) - newton * repulsion;
const RootType refined = z - (denominator == RootType(0) ? newton : RootType(newton / denominator));
if ((numext::isfinite)(numext::abs(refined))) {
m_roots[i] = refined;
moving = true;
} else {
active[i] = false;
}
}
if (!moving) return;
}
// Keep improvements to slowly converging multiple roots, but reject a larger residual at the sweep cap.
for (Index i = 0; i < n; ++i) {
if (!active[i]) continue;
RootType initialValue;
evaluate(poly, m_roots[i], value, derivative, false);
evaluate(poly, m_eigenSolver.eigenvalues()[i], initialValue, derivative, false);
if (!(numext::abs(value) <= numext::abs(initialValue))) m_roots[i] = m_eigenSolver.eigenvalues()[i];
}
}
/** Restores the structure the in-place iteration keeps only to within rounding. The roots of a real polynomial
* are real or conjugate pairs: two iterates closer to each other's conjugate than to the real axis become exactly
* conjugate, and an iterate left without a partner becomes real. Residual-based snapping of the remaining
* roots requires an imaginary part at most sqrt(eps) times the real part and a finite rounding bound. */
template <typename OtherPolynomial>
void cleanUpRoots(const OtherPolynomial& poly) {
const Index n = m_roots.size();
constexpr bool realPolynomial = !NumTraits<Scalar>::IsComplex;
Array<bool, Deg_, 1> paired = Array<bool, Deg_, 1>::Constant(n, false);
EIGEN_IF_CONSTEXPR (realPolynomial) {
for (Index i = 0; i < n; ++i) {
if (!(numext::imag(m_roots[i]) > RealScalar(0))) continue;
Index partner = n;
RealScalar distance = numext::imag(m_roots[i]);
for (Index j = 0; j < n; ++j) {
if (paired[j] || !(numext::imag(m_roots[j]) < RealScalar(0))) continue;
const RealScalar d = numext::abs(m_roots[j] - numext::conj(m_roots[i]));
if (d < distance) {
distance = d;
partner = j;
}
}
if (partner < n) {
paired[i] = paired[partner] = true;
const RootType mean = (m_roots[i] + numext::conj(m_roots[partner])) / RealScalar(2);
m_roots[i] = mean;
m_roots[partner] = numext::conj(mean);
}
}
}
RootType value, derivative;
for (Index i = 0; i < n; ++i) {
if (numext::imag(m_roots[i]) == RealScalar(0)) continue;
const RootType realPart(numext::real(m_roots[i]));
if (realPolynomial && !paired[i]) {
m_roots[i] = realPart;
continue;
}
// A small residual at realPart can belong to a different root.
if (!(numext::abs(numext::imag(m_roots[i])) <=
numext::sqrt(NumTraits<RealScalar>::epsilon()) * numext::abs(realPart)))
continue;
const RealScalar bound = evaluate(poly, realPart, value, derivative, true);
if ((numext::isfinite)(bound) && numext::abs(value) <= bound) m_roots[i] = realPart;
}
}
using PS_Base::m_roots;
EigenSolverType m_eigenSolver;
};
template <typename Scalar_>
class PolynomialSolver<Scalar_, 1> : public PolynomialSolverBase<Scalar_, 1> {
public:
typedef PolynomialSolverBase<Scalar_, 1> PS_Base;
EIGEN_POLYNOMIAL_SOLVER_BASE_INHERITED_TYPES(PS_Base)
public:
/** Computes the complex roots of a new polynomial. */
template <typename OtherPolynomial>
void compute(const OtherPolynomial& poly) {
eigen_assert(poly.size() == 2);
eigen_assert(Scalar(0) != poly[1]);
m_roots[0] = -poly[0] / poly[1];
}
public:
template <typename OtherPolynomial>
inline PolynomialSolver(const OtherPolynomial& poly) {
compute(poly);
}
inline PolynomialSolver() {}
protected:
using PS_Base::m_roots;
};
} // end namespace Eigen
#endif // EIGEN_POLYNOMIAL_SOLVER_H