Loading files
4f95ecaa3This file is pinned to the selected release. Download the original to open it in your design tool.
// 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 <cmath>
//! 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