]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/boost/boost/math/tools/cubic_roots.hpp
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / boost / boost / math / tools / cubic_roots.hpp
diff --git a/ceph/src/boost/boost/math/tools/cubic_roots.hpp b/ceph/src/boost/boost/math/tools/cubic_roots.hpp
new file mode 100644 (file)
index 0000000..451c5de
--- /dev/null
@@ -0,0 +1,176 @@
+//  (C) Copyright Nick Thompson 2021.
+//  Use, modification and distribution are subject to the
+//  Boost Software License, Version 1.0. (See accompanying file
+//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+#ifndef BOOST_MATH_TOOLS_CUBIC_ROOTS_HPP
+#define BOOST_MATH_TOOLS_CUBIC_ROOTS_HPP
+#include <algorithm>
+#include <array>
+#include <boost/math/special_functions/sign.hpp>
+#include <boost/math/tools/roots.hpp>
+
+namespace boost::math::tools {
+
+// Solves ax^3 + bx^2 + cx + d = 0.
+// Only returns the real roots, as types get weird for real coefficients and
+// complex roots. Follows Numerical Recipes, Chapter 5, section 6. NB: A better
+// algorithm apparently exists: Algorithm 954: An Accurate and Efficient Cubic
+// and Quartic Equation Solver for Physical Applications However, I don't have
+// access to that paper!
+template <typename Real>
+std::array<Real, 3> cubic_roots(Real a, Real b, Real c, Real d) {
+    using std::abs;
+    using std::acos;
+    using std::cbrt;
+    using std::cos;
+    using std::fma;
+    using std::sqrt;
+    std::array<Real, 3> roots = {std::numeric_limits<Real>::quiet_NaN(),
+                                 std::numeric_limits<Real>::quiet_NaN(),
+                                 std::numeric_limits<Real>::quiet_NaN()};
+    if (a == 0) {
+        // bx^2 + cx + d = 0:
+        if (b == 0) {
+            // cx + d = 0:
+            if (c == 0) {
+                if (d != 0) {
+                    // No solutions:
+                    return roots;
+                }
+                roots[0] = 0;
+                roots[1] = 0;
+                roots[2] = 0;
+                return roots;
+            }
+            roots[0] = -d / c;
+            return roots;
+        }
+        auto [x0, x1] = quadratic_roots(b, c, d);
+        roots[0] = x0;
+        roots[1] = x1;
+        return roots;
+    }
+    if (d == 0) {
+        auto [x0, x1] = quadratic_roots(a, b, c);
+        roots[0] = x0;
+        roots[1] = x1;
+        roots[2] = 0;
+        std::sort(roots.begin(), roots.end());
+        return roots;
+    }
+    Real p = b / a;
+    Real q = c / a;
+    Real r = d / a;
+    Real Q = (p * p - 3 * q) / 9;
+    Real R = (2 * p * p * p - 9 * p * q + 27 * r) / 54;
+    if (R * R < Q * Q * Q) {
+        Real rtQ = sqrt(Q);
+        Real theta = acos(R / (Q * rtQ)) / 3;
+        Real st = sin(theta);
+        Real ct = cos(theta);
+        roots[0] = -2 * rtQ * ct - p / 3;
+        roots[1] = -rtQ * (-ct + sqrt(Real(3)) * st) - p / 3;
+        roots[2] = rtQ * (ct + sqrt(Real(3)) * st) - p / 3;
+    } else {
+        // In Numerical Recipes, Chapter 5, Section 6, it is claimed that we
+        // only have one real root if R^2 >= Q^3. But this isn't true; we can
+        // even see this from equation 5.6.18. The condition for having three
+        // real roots is that A = B. It *is* the case that if we're in this
+        // branch, and we have 3 real roots, two are a double root. Take
+        // (x+1)^2(x-2) = x^3 - 3x -2 as an example. This clearly has a double
+        // root at x = -1, and it gets sent into this branch.
+        Real arg = R * R - Q * Q * Q;
+        Real A = (R >= 0 ? -1 : 1) * cbrt(abs(R) + sqrt(arg));
+        Real B = 0;
+        if (A != 0) {
+            B = Q / A;
+        }
+        roots[0] = A + B - p / 3;
+        // Yes, we're comparing floats for equality:
+        // Any perturbation pushes the roots into the complex plane; out of the
+        // bailiwick of this routine.
+        if (A == B || arg == 0) {
+            roots[1] = -A - p / 3;
+            roots[2] = -A - p / 3;
+        }
+    }
+    // Root polishing:
+    for (auto &r : roots) {
+        // Horner's method.
+        // Here I'll take John Gustaffson's opinion that the fma is a *distinct*
+        // operation from a*x +b: Make sure to compile these fmas into a single
+        // instruction and not a function call! (I'm looking at you Windows.)
+        Real f = fma(a, r, b);
+        f = fma(f, r, c);
+        f = fma(f, r, d);
+        Real df = fma(3 * a, r, 2 * b);
+        df = fma(df, r, c);
+        if (df != 0) {
+            Real d2f = fma(6 * a, r, 2 * b);
+            Real denom = 2 * df * df - f * d2f;
+            if (denom != 0) {
+                r -= 2 * f * df / denom;
+            } else {
+                r -= f / df;
+            }
+        }
+    }
+    std::sort(roots.begin(), roots.end());
+    return roots;
+}
+
+// Computes the empirical residual p(r) (first element) and expected residual
+// eps*|rp'(r)| (second element) for a root. Recall that for a numerically
+// computed root r satisfying r = r_0(1+eps) of a function p, |p(r)| <=
+// eps|rp'(r)|.
+template <typename Real>
+std::array<Real, 2> cubic_root_residual(Real a, Real b, Real c, Real d,
+                                        Real root) {
+    using std::abs;
+    using std::fma;
+    std::array<Real, 2> out;
+    Real residual = fma(a, root, b);
+    residual = fma(residual, root, c);
+    residual = fma(residual, root, d);
+
+    out[0] = residual;
+
+    // The expected residual is:
+    // eps*[4|ar^3| + 3|br^2| + 2|cr| + |d|]
+    // This can be demonstrated by assuming the coefficients and the root are
+    // perturbed according to the rounding model of floating point arithmetic,
+    // and then working through the inequalities.
+    root = abs(root);
+    Real expected_residual = fma(4 * abs(a), root, 3 * abs(b));
+    expected_residual = fma(expected_residual, root, 2 * abs(c));
+    expected_residual = fma(expected_residual, root, abs(d));
+    out[1] = expected_residual * std::numeric_limits<Real>::epsilon();
+    return out;
+}
+
+// Computes the condition number of rootfinding. This is defined in Corless, A
+// Graduate Introduction to Numerical Methods, Section 3.2.1.
+template <typename Real>
+Real cubic_root_condition_number(Real a, Real b, Real c, Real d, Real root) {
+    using std::abs;
+    using std::fma;
+    // There are *absolute* condition numbers that can be defined when r = 0;
+    // but they basically reduce to the residual computed above.
+    if (root == static_cast<Real>(0)) {
+        return std::numeric_limits<Real>::infinity();
+    }
+
+    Real numerator = fma(abs(a), abs(root), abs(b));
+    numerator = fma(numerator, abs(root), abs(c));
+    numerator = fma(numerator, abs(root), abs(d));
+    Real denominator = fma(3 * a, root, 2 * b);
+    denominator = fma(denominator, root, c);
+    if (denominator == static_cast<Real>(0)) {
+        return std::numeric_limits<Real>::infinity();
+    }
+    denominator *= root;
+    return numerator / abs(denominator);
+}
+
+} // namespace boost::math::tools
+#endif