// Copyright (c) 2025 OPEN CASCADE SAS // // This file is part of Open CASCADE Technology software library. // // This library is free software; you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License version 2.1 as published // by the Free Software Foundation, with special exception defined in the file // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT // distribution for complete text of the license and disclaimer of any warranty. // // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. #ifndef _MathPoly_Cubic_HeaderFile #define _MathPoly_Cubic_HeaderFile #include "MathUtils_Types.hxx" #include "MathUtils_Core.hxx" #include "MathUtils_Poly.hxx" #include "MathPoly_Quadratic.hxx" #include //! Polynomial root finding algorithms. namespace MathPoly { using namespace MathUtils; //! Solve cubic equation: a*x^3 + b*x^2 + c*x + d = 0 //! Uses Cardano's method with Vieta substitution and trigonometric solution. //! //! Algorithm: //! 1. Handle lower degree cases (a = 0 -> quadratic) //! 2. Transform to depressed cubic: t^3 + pt + q = 0 via x = t - b/(3a) //! 3. Compute discriminant Delta = q^2/4 + p^3/27 //! 4. For Delta > 0: one real root (Cardano's formula) //! 5. For Delta < 0: three real roots (trigonometric method) //! 6. For Delta = 0: multiple roots //! 7. Apply Newton-Raphson refinement //! //! @param theA coefficient of x^3 //! @param theB coefficient of x^2 //! @param theC coefficient of x //! @param theD constant term //! @return result containing 1, 2, or 3 real roots (sorted in ascending order) inline MathUtils::PolyResult Cubic(double theA, double theB, double theC, double theD) { MathUtils::PolyResult aResult; // Reduce to quadratic if leading coefficient is zero if (MathUtils::IsZero(theA)) { return Quadratic(theB, theC, theD); } // Scale coefficients for numerical stability const double aScale = std::max({std::abs(theA), std::abs(theB), std::abs(theC), std::abs(theD)}); if (aScale < MathUtils::THE_ZERO_TOL) { aResult.Status = MathUtils::Status::InfiniteSolutions; return aResult; } // Normalize to monic cubic: x^3 + px^2 + qx + r = 0 const double aP = theB / theA; const double aQ = theC / theA; const double aR = theD / theA; // Substitute x = t - p/3 to get depressed cubic: t^3 + at + b = 0 const double aP3 = aP / 3.0; const double aP3_sq = aP3 * aP3; const double a = aQ - 3.0 * aP3_sq; const double b = aR - aP3 * aQ + 2.0 * aP3_sq * aP3; // Discriminant: Delta = (b/2)^2 + (a/3)^3 const double aHalfB = b / 2.0; const double aThirdA = a / 3.0; const double aDisc = aHalfB * aHalfB + aThirdA * aThirdA * aThirdA; // Tolerance for discriminant const double aDiscTol = MathUtils::THE_ZERO_TOL * std::max(aHalfB * aHalfB, std::abs(aThirdA * aThirdA * aThirdA)); // Store original coefficients for refinement const double aCoeffs[4] = {theD, theC, theB, theA}; if (aDisc > aDiscTol) { // One real root, two complex conjugate roots // Cardano's formula: t = cbrt(-b/2 + sqrt(disc)) + cbrt(-b/2 - sqrt(disc)) const double aSqrtDisc = std::sqrt(aDisc); const double aU = MathUtils::CubeRoot(-aHalfB + aSqrtDisc); const double aV = MathUtils::CubeRoot(-aHalfB - aSqrtDisc); aResult.Status = MathUtils::Status::OK; aResult.NbRoots = 1; aResult.Roots[0] = aU + aV - aP3; // Refine root aResult.Roots[0] = MathUtils::RefinePolyRoot(aCoeffs, 3, aResult.Roots[0]); } else if (aDisc < -aDiscTol) { // Three distinct real roots - use trigonometric method (casus irreducibilis) // t = 2*sqrt(-a/3) * cos(theta/3 + 2*k*pi/3), k = 0, 1, 2 // where cos(theta) = -b / (2 * sqrt((-a/3)^3)) const double aR_val = std::sqrt(-aThirdA * aThirdA * aThirdA); const double aCosArg = MathUtils::Clamp(-aHalfB / aR_val, -1.0, 1.0); const double aTheta = std::acos(aCosArg); const double aTwoSqrtNegA3 = 2.0 * std::sqrt(-aThirdA); aResult.Status = MathUtils::Status::OK; aResult.NbRoots = 3; aResult.Roots[0] = aTwoSqrtNegA3 * std::cos(aTheta / 3.0) - aP3; aResult.Roots[1] = aTwoSqrtNegA3 * std::cos((aTheta + 2.0 * MathUtils::THE_PI) / 3.0) - aP3; aResult.Roots[2] = aTwoSqrtNegA3 * std::cos((aTheta + 4.0 * MathUtils::THE_PI) / 3.0) - aP3; // Refine all roots for (int i = 0; i < 3; ++i) { aResult.Roots[i] = MathUtils::RefinePolyRoot(aCoeffs, 3, aResult.Roots[i]); } // Sort roots MathUtils::SortRoots(aResult.Roots.data(), 3); } else { // Discriminant is zero - multiple roots const double aU = MathUtils::CubeRoot(-aHalfB); aResult.Status = MathUtils::Status::OK; if (MathUtils::IsZero(aU)) { // Triple root at x = -p/3 aResult.NbRoots = 1; aResult.Roots[0] = -aP3; } else { // One single root and one double root aResult.NbRoots = 2; aResult.Roots[0] = 2.0 * aU - aP3; // Single root aResult.Roots[1] = -aU - aP3; // Double root // Refine roots aResult.Roots[0] = MathUtils::RefinePolyRoot(aCoeffs, 3, aResult.Roots[0]); aResult.Roots[1] = MathUtils::RefinePolyRoot(aCoeffs, 3, aResult.Roots[1]); // Sort roots if (aResult.Roots[0] > aResult.Roots[1]) { std::swap(aResult.Roots[0], aResult.Roots[1]); } } } return aResult; } } // namespace MathPoly #endif // _MathPoly_Cubic_HeaderFile