Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-09-28 09:20:51

0001 // Copyright (c) 2025 OPEN CASCADE SAS
0002 //
0003 // This file is part of Open CASCADE Technology software library.
0004 //
0005 // This library is free software; you can redistribute it and/or modify it under
0006 // the terms of the GNU Lesser General Public License version 2.1 as published
0007 // by the Free Software Foundation, with special exception defined in the file
0008 // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
0009 // distribution for complete text of the license and disclaimer of any warranty.
0010 //
0011 // Alternatively, this file may be used under the terms of Open CASCADE
0012 // commercial license or contractual agreement.
0013 
0014 #ifndef _MathPoly_Cubic_HeaderFile
0015 #define _MathPoly_Cubic_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Core.hxx>
0019 #include <MathUtils_Poly.hxx>
0020 #include <MathPoly_Quadratic.hxx>
0021 
0022 #include <cmath>
0023 
0024 //! Polynomial root finding algorithms.
0025 namespace MathPoly
0026 {
0027 using namespace MathUtils;
0028 
0029 //! Solve cubic equation: a*x^3 + b*x^2 + c*x + d = 0
0030 //! Uses Cardano's method with Vieta substitution and trigonometric solution.
0031 //!
0032 //! Algorithm:
0033 //! 1. Handle lower degree cases (a = 0 -> quadratic)
0034 //! 2. Transform to depressed cubic: t^3 + pt + q = 0 via x = t - b/(3a)
0035 //! 3. Compute discriminant Delta = q^2/4 + p^3/27
0036 //! 4. For Delta > 0: one real root (Cardano's formula)
0037 //! 5. For Delta < 0: three real roots (trigonometric method)
0038 //! 6. For Delta = 0: multiple roots
0039 //! 7. Apply Newton-Raphson refinement
0040 //!
0041 //! @param theA coefficient of x^3
0042 //! @param theB coefficient of x^2
0043 //! @param theC coefficient of x
0044 //! @param theD constant term
0045 //! @return result containing 1, 2, or 3 real roots (sorted in ascending order)
0046 inline MathUtils::PolyResult Cubic(double theA, double theB, double theC, double theD)
0047 {
0048   MathUtils::PolyResult aResult;
0049 
0050   // Reduce to quadratic if leading coefficient is zero
0051   if (MathUtils::IsZero(theA))
0052   {
0053     return Quadratic(theB, theC, theD);
0054   }
0055 
0056   // Scale coefficients for numerical stability
0057   const double aScale = std::max({std::abs(theA), std::abs(theB), std::abs(theC), std::abs(theD)});
0058   if (aScale < MathUtils::THE_ZERO_TOL)
0059   {
0060     aResult.Status = MathUtils::Status::InfiniteSolutions;
0061     return aResult;
0062   }
0063 
0064   // Normalize to monic cubic: x^3 + px^2 + qx + r = 0
0065   const double aP = theB / theA;
0066   const double aQ = theC / theA;
0067   const double aR = theD / theA;
0068 
0069   // Substitute x = t - p/3 to get depressed cubic: t^3 + at + b = 0
0070   const double aP3    = aP / 3.0;
0071   const double aP3_sq = aP3 * aP3;
0072   const double a      = aQ - 3.0 * aP3_sq;
0073   const double b      = aR - aP3 * aQ + 2.0 * aP3_sq * aP3;
0074 
0075   // Discriminant: Delta = (b/2)^2 + (a/3)^3
0076   const double aHalfB  = b / 2.0;
0077   const double aThirdA = a / 3.0;
0078   const double aDisc   = aHalfB * aHalfB + aThirdA * aThirdA * aThirdA;
0079 
0080   // Tolerance for discriminant
0081   const double aDiscTol =
0082     MathUtils::THE_ZERO_TOL * std::max(aHalfB * aHalfB, std::abs(aThirdA * aThirdA * aThirdA));
0083 
0084   // Store original coefficients for refinement
0085   const double aCoeffs[4] = {theD, theC, theB, theA};
0086 
0087   if (aDisc > aDiscTol)
0088   {
0089     // One real root, two complex conjugate roots
0090     // Cardano's formula: t = cbrt(-b/2 + sqrt(disc)) + cbrt(-b/2 - sqrt(disc))
0091     const double aSqrtDisc = std::sqrt(aDisc);
0092     const double aU        = MathUtils::CubeRoot(-aHalfB + aSqrtDisc);
0093     const double aV        = MathUtils::CubeRoot(-aHalfB - aSqrtDisc);
0094 
0095     aResult.Status   = MathUtils::Status::OK;
0096     aResult.NbRoots  = 1;
0097     aResult.Roots[0] = aU + aV - aP3;
0098 
0099     // Refine root
0100     aResult.Roots[0] = MathUtils::RefinePolyRoot(aCoeffs, 3, aResult.Roots[0]);
0101   }
0102   else if (aDisc < -aDiscTol)
0103   {
0104     // Three distinct real roots - use trigonometric method (casus irreducibilis)
0105     // t = 2*sqrt(-a/3) * cos(theta/3 + 2*k*pi/3), k = 0, 1, 2
0106     // where cos(theta) = -b / (2 * sqrt((-a/3)^3))
0107 
0108     const double aR_val        = std::sqrt(-aThirdA * aThirdA * aThirdA);
0109     const double aCosArg       = MathUtils::Clamp(-aHalfB / aR_val, -1.0, 1.0);
0110     const double aTheta        = std::acos(aCosArg);
0111     const double aTwoSqrtNegA3 = 2.0 * std::sqrt(-aThirdA);
0112 
0113     aResult.Status   = MathUtils::Status::OK;
0114     aResult.NbRoots  = 3;
0115     aResult.Roots[0] = aTwoSqrtNegA3 * std::cos(aTheta / 3.0) - aP3;
0116     aResult.Roots[1] = aTwoSqrtNegA3 * std::cos((aTheta + 2.0 * MathUtils::THE_PI) / 3.0) - aP3;
0117     aResult.Roots[2] = aTwoSqrtNegA3 * std::cos((aTheta + 4.0 * MathUtils::THE_PI) / 3.0) - aP3;
0118 
0119     // Refine all roots
0120     for (int i = 0; i < 3; ++i)
0121     {
0122       aResult.Roots[i] = MathUtils::RefinePolyRoot(aCoeffs, 3, aResult.Roots[i]);
0123     }
0124 
0125     // Sort roots
0126     MathUtils::SortRoots(aResult.Roots.data(), 3);
0127   }
0128   else
0129   {
0130     // Discriminant is zero - multiple roots
0131     const double aU = MathUtils::CubeRoot(-aHalfB);
0132 
0133     aResult.Status = MathUtils::Status::OK;
0134 
0135     if (MathUtils::IsZero(aU))
0136     {
0137       // Triple root at x = -p/3
0138       aResult.NbRoots  = 1;
0139       aResult.Roots[0] = -aP3;
0140     }
0141     else
0142     {
0143       // One single root and one double root
0144       aResult.NbRoots  = 2;
0145       aResult.Roots[0] = 2.0 * aU - aP3; // Single root
0146       aResult.Roots[1] = -aU - aP3;      // Double root
0147 
0148       // Refine roots
0149       aResult.Roots[0] = MathUtils::RefinePolyRoot(aCoeffs, 3, aResult.Roots[0]);
0150       aResult.Roots[1] = MathUtils::RefinePolyRoot(aCoeffs, 3, aResult.Roots[1]);
0151 
0152       // Sort roots
0153       if (aResult.Roots[0] > aResult.Roots[1])
0154       {
0155         std::swap(aResult.Roots[0], aResult.Roots[1]);
0156       }
0157     }
0158   }
0159 
0160   return aResult;
0161 }
0162 
0163 } // namespace MathPoly
0164 
0165 #endif // _MathPoly_Cubic_HeaderFile