Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:40:40

0001 //  (C) Copyright Nick Thompson 2021.
0002 //  Use, modification and distribution are subject to the
0003 //  Boost Software License, Version 1.0. (See accompanying file
0004 //  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
0005 #ifndef BOOST_MATH_TOOLS_CUBIC_ROOTS_HPP
0006 #define BOOST_MATH_TOOLS_CUBIC_ROOTS_HPP
0007 #include <algorithm>
0008 #include <array>
0009 #include <boost/math/special_functions/sign.hpp>
0010 #include <boost/math/tools/roots.hpp>
0011 
0012 namespace boost::math::tools {
0013 
0014 // Solves ax^3 + bx^2 + cx + d = 0.
0015 // Only returns the real roots, as types get weird for real coefficients and
0016 // complex roots. Follows Numerical Recipes, Chapter 5, section 6. NB: A better
0017 // algorithm apparently exists: Algorithm 954: An Accurate and Efficient Cubic
0018 // and Quartic Equation Solver for Physical Applications However, I don't have
0019 // access to that paper!
0020 template <typename Real>
0021 std::array<Real, 3> cubic_roots(Real a, Real b, Real c, Real d) {
0022     using std::abs;
0023     using std::acos;
0024     using std::cbrt;
0025     using std::cos;
0026     using std::fma;
0027     using std::sqrt;
0028     std::array<Real, 3> roots = {std::numeric_limits<Real>::quiet_NaN(),
0029                                  std::numeric_limits<Real>::quiet_NaN(),
0030                                  std::numeric_limits<Real>::quiet_NaN()};
0031     if (a == 0) {
0032         // bx^2 + cx + d = 0:
0033         if (b == 0) {
0034             // cx + d = 0:
0035             if (c == 0) {
0036                 if (d != 0) {
0037                     // No solutions:
0038                     return roots;
0039                 }
0040                 roots[0] = 0;
0041                 roots[1] = 0;
0042                 roots[2] = 0;
0043                 return roots;
0044             }
0045             roots[0] = -d / c;
0046             return roots;
0047         }
0048         auto [x0, x1] = quadratic_roots(b, c, d);
0049         roots[0] = x0;
0050         roots[1] = x1;
0051         return roots;
0052     }
0053     if (d == 0) {
0054         auto [x0, x1] = quadratic_roots(a, b, c);
0055         roots[0] = x0;
0056         roots[1] = x1;
0057         roots[2] = 0;
0058         std::sort(roots.begin(), roots.end());
0059         return roots;
0060     }
0061     Real p = b / a;
0062     Real q = c / a;
0063     Real r = d / a;
0064     Real Q = (p * p - 3 * q) / 9;
0065     Real R = (2 * p * p * p - 9 * p * q + 27 * r) / 54;
0066     if (R * R < Q * Q * Q) {
0067         Real rtQ = sqrt(Q);
0068         Real theta = acos(R / (Q * rtQ)) / 3;
0069         Real st = sin(theta);
0070         Real ct = cos(theta);
0071         roots[0] = -2 * rtQ * ct - p / 3;
0072         roots[1] = -rtQ * (-ct + sqrt(Real(3)) * st) - p / 3;
0073         roots[2] = rtQ * (ct + sqrt(Real(3)) * st) - p / 3;
0074     } else {
0075         // In Numerical Recipes, Chapter 5, Section 6, it is claimed that we
0076         // only have one real root if R^2 >= Q^3. But this isn't true; we can
0077         // even see this from equation 5.6.18. The condition for having three
0078         // real roots is that A = B. It *is* the case that if we're in this
0079         // branch, and we have 3 real roots, two are a double root. Take
0080         // (x+1)^2(x-2) = x^3 - 3x -2 as an example. This clearly has a double
0081         // root at x = -1, and it gets sent into this branch.
0082         Real arg = R * R - Q * Q * Q;
0083         Real A = (R >= 0 ? -1 : 1) * cbrt(abs(R) + sqrt(arg));
0084         Real B = 0;
0085         if (A != 0) {
0086             B = Q / A;
0087         }
0088         roots[0] = A + B - p / 3;
0089         // Yes, we're comparing floats for equality:
0090         // Any perturbation pushes the roots into the complex plane; out of the
0091         // bailiwick of this routine.
0092         if (A == B || arg == 0) {
0093             roots[1] = -A - p / 3;
0094             roots[2] = -A - p / 3;
0095         }
0096     }
0097     // Root polishing:
0098     for (auto &r : roots) {
0099         // Horner's method.
0100         // Here I'll take John Gustaffson's opinion that the fma is a *distinct*
0101         // operation from a*x +b: Make sure to compile these fmas into a single
0102         // instruction and not a function call! (I'm looking at you Windows.)
0103         Real f = fma(a, r, b);
0104         f = fma(f, r, c);
0105         f = fma(f, r, d);
0106         Real df = fma(3 * a, r, 2 * b);
0107         df = fma(df, r, c);
0108         if (df != 0) {
0109             Real d2f = fma(6 * a, r, 2 * b);
0110             Real denom = 2 * df * df - f * d2f;
0111             if (denom != 0) {
0112                 r -= 2 * f * df / denom;
0113             } else {
0114                 r -= f / df;
0115             }
0116         }
0117     }
0118     std::sort(roots.begin(), roots.end());
0119     return roots;
0120 }
0121 
0122 // Computes the empirical residual p(r) (first element) and expected residual
0123 // eps*|rp'(r)| (second element) for a root. Recall that for a numerically
0124 // computed root r satisfying r = r_0(1+eps) of a function p, |p(r)| <=
0125 // eps|rp'(r)|.
0126 template <typename Real>
0127 std::array<Real, 2> cubic_root_residual(Real a, Real b, Real c, Real d,
0128                                         Real root) {
0129     using std::abs;
0130     using std::fma;
0131     std::array<Real, 2> out;
0132     Real residual = fma(a, root, b);
0133     residual = fma(residual, root, c);
0134     residual = fma(residual, root, d);
0135 
0136     out[0] = residual;
0137 
0138     // The expected residual is:
0139     // eps*[4|ar^3| + 3|br^2| + 2|cr| + |d|]
0140     // This can be demonstrated by assuming the coefficients and the root are
0141     // perturbed according to the rounding model of floating point arithmetic,
0142     // and then working through the inequalities.
0143     root = abs(root);
0144     Real expected_residual = fma(4 * abs(a), root, 3 * abs(b));
0145     expected_residual = fma(expected_residual, root, 2 * abs(c));
0146     expected_residual = fma(expected_residual, root, abs(d));
0147     out[1] = expected_residual * std::numeric_limits<Real>::epsilon();
0148     return out;
0149 }
0150 
0151 // Computes the condition number of rootfinding. This is defined in Corless, A
0152 // Graduate Introduction to Numerical Methods, Section 3.2.1.
0153 template <typename Real>
0154 Real cubic_root_condition_number(Real a, Real b, Real c, Real d, Real root) {
0155     using std::abs;
0156     using std::fma;
0157     // There are *absolute* condition numbers that can be defined when r = 0;
0158     // but they basically reduce to the residual computed above.
0159     if (root == static_cast<Real>(0)) {
0160         return std::numeric_limits<Real>::infinity();
0161     }
0162 
0163     Real numerator = fma(abs(a), abs(root), abs(b));
0164     numerator = fma(numerator, abs(root), abs(c));
0165     numerator = fma(numerator, abs(root), abs(d));
0166     Real denominator = fma(3 * a, root, 2 * b);
0167     denominator = fma(denominator, root, c);
0168     if (denominator == static_cast<Real>(0)) {
0169         return std::numeric_limits<Real>::infinity();
0170     }
0171     denominator *= root;
0172     return numerator / abs(denominator);
0173 }
0174 
0175 } // namespace boost::math::tools
0176 #endif