| // 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 | |
| #include "main.h" | |
| #include <contrib/Eigen/Polynomials> | |
| #include <iostream> | |
| #include <algorithm> | |
| using namespace std; | |
| namespace Eigen { | |
| namespace internal { | |
| template <int Size> | |
| struct increment_if_fixed_size : std::integral_constant<int, (Size == Dynamic) ? Dynamic : Size + 1> {}; | |
| } // namespace internal | |
| } // namespace Eigen | |
| template <typename PolynomialType> | |
| PolynomialType polyder(const PolynomialType& p) { | |
| typedef typename PolynomialType::Scalar Scalar; | |
| PolynomialType res(p.size()); | |
| for (Index i = 1; i < p.size(); ++i) res[i - 1] = p[i] * Scalar(i); | |
| res[p.size() - 1] = 0.; | |
| return res; | |
| } | |
| template <int Deg, typename POLYNOMIAL, typename SOLVER> | |
| bool aux_evalSolver(const POLYNOMIAL& pols, SOLVER& psolve) { | |
| typedef typename POLYNOMIAL::Scalar Scalar; | |
| typedef typename POLYNOMIAL::RealScalar RealScalar; | |
| typedef typename SOLVER::RootsType RootsType; | |
| typedef Matrix<RealScalar, Deg, 1> EvalRootsType; | |
| const Index deg = pols.size() - 1; | |
| // Test template constructor from coefficient vector | |
| SOLVER solve_constr(pols); | |
| psolve.compute(pols); | |
| const RootsType& roots(psolve.roots()); | |
| EvalRootsType evr(deg); | |
| POLYNOMIAL pols_der = polyder(pols); | |
| EvalRootsType der(deg); | |
| for (int i = 0; i < roots.size(); ++i) { | |
| evr[i] = std::abs(poly_eval(pols, roots[i])); | |
| der[i] = numext::maxi(RealScalar(1.), std::abs(poly_eval(pols_der, roots[i]))); | |
| } | |
| // we need to divide by the magnitude of the derivative because | |
| // with a high derivative is very small error in the value of the root | |
| // yiels a very large error in the polynomial evaluation. | |
| bool evalToZero = (evr.cwiseQuotient(der)).isZero(test_precision<Scalar>()); | |
| if (!evalToZero) { | |
| cerr << "WRONG root: " << endl; | |
| cerr << "Polynomial: " << pols.transpose() << endl; | |
| cerr << "Roots found: " << roots.transpose() << endl; | |
| cerr << "Abs value of the polynomial at the roots: " << evr.transpose() << endl; | |
| cerr << endl; | |
| } | |
| std::vector<RealScalar> rootModuli(roots.size()); | |
| Map<EvalRootsType> aux(&rootModuli[0], roots.size()); | |
| aux = roots.array().abs(); | |
| std::sort(rootModuli.begin(), rootModuli.end()); | |
| bool distinctModuli = true; | |
| for (size_t i = 1; i < rootModuli.size() && distinctModuli; ++i) { | |
| if (internal::isApprox(rootModuli[i], rootModuli[i - 1])) { | |
| distinctModuli = false; | |
| } | |
| } | |
| VERIFY(evalToZero || !distinctModuli); | |
| return distinctModuli; | |
| } | |
| template <int Deg, typename POLYNOMIAL> | |
| void evalSolver(const POLYNOMIAL& pols) { | |
| typedef typename POLYNOMIAL::Scalar Scalar; | |
| typedef PolynomialSolver<Scalar, Deg> PolynomialSolverType; | |
| PolynomialSolverType psolve; | |
| aux_evalSolver<Deg, POLYNOMIAL, PolynomialSolverType>(pols, psolve); | |
| } | |
| template <typename Solver> | |
| void verify_polynomialsolver_sugar(const Solver& solver, typename Solver::RealScalar threshold) { | |
| using Real = typename Solver::RealScalar; | |
| const auto& computed = solver.roots(); | |
| Index greatest, smallest; | |
| computed.cwiseAbs2().maxCoeff(&greatest); | |
| computed.cwiseAbs2().minCoeff(&smallest); | |
| VERIFY_IS_EQUAL(solver.greatestRoot(), computed[greatest]); | |
| VERIFY_IS_EQUAL(solver.smallestRoot(), computed[smallest]); | |
| std::vector<Real> expectedRealRoots, expectedRealExtrema; | |
| for (Index i = 0; i < computed.size(); ++i) { | |
| // realRoots uses a strict threshold; the extremal queries include its boundary. | |
| if (numext::abs(computed[i].imag()) < threshold) expectedRealRoots.push_back(computed[i].real()); | |
| if (numext::abs(computed[i].imag()) <= threshold) expectedRealExtrema.push_back(computed[i].real()); | |
| } | |
| std::vector<Real> actualRealRoots; | |
| solver.realRoots(actualRealRoots, threshold); | |
| VERIFY_IS_EQUAL(actualRealRoots.size(), expectedRealRoots.size()); | |
| for (size_t i = 0; i < expectedRealRoots.size(); ++i) VERIFY_IS_EQUAL(actualRealRoots[i], expectedRealRoots[i]); | |
| const bool expectedHasRealRoot = !expectedRealExtrema.empty(); | |
| const auto absLess = [](Real a, Real b) { return numext::abs(a) < numext::abs(b); }; | |
| bool hasRealRoot; | |
| Real result = solver.absGreatestRealRoot(hasRealRoot, threshold); | |
| VERIFY_IS_EQUAL(hasRealRoot, expectedHasRealRoot); | |
| if (hasRealRoot) | |
| VERIFY_IS_EQUAL(result, *std::max_element(expectedRealExtrema.begin(), expectedRealExtrema.end(), absLess)); | |
| result = solver.absSmallestRealRoot(hasRealRoot, threshold); | |
| VERIFY_IS_EQUAL(hasRealRoot, expectedHasRealRoot); | |
| if (hasRealRoot) | |
| VERIFY_IS_EQUAL(result, *std::min_element(expectedRealExtrema.begin(), expectedRealExtrema.end(), absLess)); | |
| result = solver.greatestRealRoot(hasRealRoot, threshold); | |
| VERIFY_IS_EQUAL(hasRealRoot, expectedHasRealRoot); | |
| if (hasRealRoot) VERIFY_IS_EQUAL(result, *std::max_element(expectedRealExtrema.begin(), expectedRealExtrema.end())); | |
| result = solver.smallestRealRoot(hasRealRoot, threshold); | |
| VERIFY_IS_EQUAL(hasRealRoot, expectedHasRealRoot); | |
| if (hasRealRoot) VERIFY_IS_EQUAL(result, *std::min_element(expectedRealExtrema.begin(), expectedRealExtrema.end())); | |
| } | |
| template <int Deg, typename POLYNOMIAL, typename REAL_ROOTS> | |
| void evalSolverSugarFunction(const POLYNOMIAL& pols, const REAL_ROOTS& real_roots) { | |
| using Scalar = typename POLYNOMIAL::Scalar; | |
| using RealScalar = typename POLYNOMIAL::RealScalar; | |
| using PolynomialSolverType = PolynomialSolver<Scalar, Deg>; | |
| PolynomialSolverType psolve; | |
| if (aux_evalSolver<Deg, POLYNOMIAL, PolynomialSolverType>(pols, psolve)) { | |
| // First-order root displacement estimate: delta * sum_k |r_j|^k / |p'(r_j)| for a monic polynomial, | |
| // p'(r_j) = prod_{k != j} (r_j - r_k). Use delta = 32 eps max_k |a_k| for the companion eigenvalue error. | |
| const RealScalar coefficientError = RealScalar(32) * NumTraits<RealScalar>::epsilon() * pols.cwiseAbs().maxCoeff(); | |
| Matrix<RealScalar, Dynamic, 1> tolerance(real_roots.size()); | |
| for (Index j = 0; j < real_roots.size(); ++j) { | |
| const RealScalar root = real_roots[j]; | |
| RealScalar powerSum = RealScalar(0), power = RealScalar(1), derivative = RealScalar(1); | |
| for (Index k = 0; k < pols.size(); ++k) { | |
| powerSum += power; | |
| power *= numext::abs(root); | |
| } | |
| for (Index k = 0; k < real_roots.size(); ++k) { | |
| if (k != j) derivative *= numext::abs(root - real_roots[k]); | |
| } | |
| tolerance[j] = coefficientError * powerSum / derivative; | |
| } | |
| // A broad cluster tolerance must not hide a missing, well-conditioned root. | |
| for (Index j = 0; j < real_roots.size(); ++j) { | |
| VERIFY((numext::isfinite)(tolerance[j])); | |
| VERIFY((psolve.roots().array() - real_roots[j]).abs().minCoeff() <= tolerance[j]); | |
| } | |
| for (Index i = 0; i < psolve.roots().size(); ++i) { | |
| bool found = false; | |
| for (Index j = 0; j < real_roots.size() && !found; ++j) { | |
| VERIFY((numext::isfinite)(tolerance[j])); | |
| if (numext::abs(psolve.roots()[i] - real_roots[j]) <= tolerance[j]) found = true; | |
| } | |
| VERIFY(found); | |
| } | |
| } | |
| verify_polynomialsolver_sugar(psolve, numext::sqrt(test_precision<RealScalar>())); | |
| } | |
| void polynomialsolver_sugar_cluster() { | |
| Matrix<float, 7, 1> roots; | |
| roots << -0.8f, 0.2f, 0.2001f, 0.5f, 0.7f, 0.9f, 1.0f; | |
| Matrix<float, 8, 1> poly; | |
| roots_to_monicPolynomial(roots, poly); | |
| evalSolverSugarFunction<7>(poly, roots); | |
| } | |
| void polynomialsolver_sugar_filtering() { | |
| Vector4d poly; | |
| poly << 0, 1, 0, 1; | |
| PolynomialSolver<double, 3> solver(poly); | |
| verify_polynomialsolver_sugar(solver, 0.0); | |
| verify_polynomialsolver_sugar(solver, 0.5); | |
| verify_polynomialsolver_sugar(solver, 1.0); | |
| verify_polynomialsolver_sugar(solver, 2.0); | |
| Vector3d noRealRoots; | |
| noRealRoots << 1, 0, 1; | |
| PolynomialSolver<double, 2> complexSolver(noRealRoots); | |
| verify_polynomialsolver_sugar(complexSolver, 0.5); | |
| } | |
| template <typename Scalar_, int Deg_> | |
| void polynomialsolver(int deg) { | |
| typedef typename NumTraits<Scalar_>::Real RealScalar; | |
| typedef internal::increment_if_fixed_size<Deg_> Dim; | |
| typedef Matrix<Scalar_, Dim::value, 1> PolynomialType; | |
| typedef Matrix<Scalar_, Deg_, 1> EvalRootsType; | |
| typedef Matrix<RealScalar, Deg_, 1> RealRootsType; | |
| cout << "Standard cases" << endl; | |
| PolynomialType pols = PolynomialType::Random(deg + 1); | |
| evalSolver<Deg_, PolynomialType>(pols); | |
| cout << "Hard cases" << endl; | |
| Scalar_ multipleRoot = internal::random<Scalar_>(); | |
| EvalRootsType allRoots = EvalRootsType::Constant(deg, multipleRoot); | |
| roots_to_monicPolynomial(allRoots, pols); | |
| evalSolver<Deg_, PolynomialType>(pols); | |
| // The companion matrix eigenvalue approach has limited accuracy for float at | |
| // high degrees. The PolynomialSolver documentation itself warns: "With 32bit | |
| // (float) floating types this problem shows up frequently." Skip the sugar | |
| // function test (which requires exact root matching) for float beyond degree 8. | |
| if (deg <= 8 || sizeof(RealScalar) > sizeof(float)) { | |
| cout << "Test sugar" << endl; | |
| RealRootsType realRoots = RealRootsType::Random(deg); | |
| // sort by ascending absolute value to mitigate precision lost during polynomial expansion | |
| std::sort(realRoots.begin(), realRoots.end(), | |
| [](RealScalar a, RealScalar b) { return numext::abs(a) < numext::abs(b); }); | |
| roots_to_monicPolynomial(realRoots, pols); | |
| evalSolverSugarFunction<Deg_>(pols, realRoots); | |
| } | |
| } | |
| EIGEN_DECLARE_TEST(polynomialsolver) { | |
| CALL_SUBTEST_7(polynomialsolver_sugar_cluster()); | |
| CALL_SUBTEST_13(polynomialsolver_sugar_filtering()); | |
| for (int i = 0; i < g_repeat; i++) { | |
| CALL_SUBTEST_1((polynomialsolver<float, 1>(1))); | |
| CALL_SUBTEST_2((polynomialsolver<double, 2>(2))); | |
| CALL_SUBTEST_3((polynomialsolver<double, 3>(3))); | |
| CALL_SUBTEST_4((polynomialsolver<float, 4>(4))); | |
| CALL_SUBTEST_5((polynomialsolver<double, 5>(5))); | |
| CALL_SUBTEST_6((polynomialsolver<float, 6>(6))); | |
| CALL_SUBTEST_7((polynomialsolver<float, 7>(7))); | |
| CALL_SUBTEST_8((polynomialsolver<double, 8>(8))); | |
| CALL_SUBTEST_9((polynomialsolver<float, Dynamic>(internal::random<int>(9, 13)))); | |
| CALL_SUBTEST_10((polynomialsolver<double, Dynamic>(internal::random<int>(9, 13)))); | |
| CALL_SUBTEST_11((polynomialsolver<float, Dynamic>(1))); | |
| CALL_SUBTEST_12((polynomialsolver<std::complex<double>, Dynamic>(internal::random<int>(2, 13)))); | |
| } | |
| } |