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_Quartic_HeaderFile
#define _MathPoly_Quartic_HeaderFile
#include <MathUtils_Types.hxx>
#include <MathUtils_Core.hxx>
#include <MathUtils_Poly.hxx>
#include <MathPoly_Quadratic.hxx>
#include <MathPoly_Cubic.hxx>
#include <cmath>
//! Polynomial root finding algorithms.
namespace MathPoly
{
using namespace MathUtils;
//! Solve quartic equation: a*x^4 + b*x^3 + c*x^2 + d*x + e = 0
//! Uses Ferrari's method with modern numerical enhancements.
//!
//! Algorithm:
//! 1. Handle lower degree cases (a = 0 -> cubic)
//! 2. Transform to depressed quartic: t^4 + pt^2 + qt + r = 0 via x = t - b/(4a)
//! 3. Solve Ferrari's resolvent cubic
//! 4. Factor quartic into two quadratics
//! 5. Solve both quadratics
//! 6. Apply Newton-Raphson refinement
//!
//! @param theA coefficient of x^4
//! @param theB coefficient of x^3
//! @param theC coefficient of x^2
//! @param theD coefficient of x
//! @param theE constant term
//! @return result containing 0, 1, 2, 3, or 4 real roots (sorted in ascending order)
inline MathUtils::PolyResult Quartic(double theA,
double theB,
double theC,
double theD,
double theE)
{
MathUtils::PolyResult aResult;
// Reduce to cubic if leading coefficient is zero
if (MathUtils::IsZero(theA))
{
return Cubic(theB, theC, theD, theE);
}
// Scale coefficients for numerical stability
const double aScale =
std::max({std::abs(theA), std::abs(theB), std::abs(theC), std::abs(theD), std::abs(theE)});
if (aScale < MathUtils::THE_ZERO_TOL)
{
aResult.Status = MathUtils::Status::InfiniteSolutions;
return aResult;
}
// Normalize to monic quartic: x^4 + ax^3 + bx^2 + cx + d = 0
const double a = theB / theA;
const double b = theC / theA;
const double c = theD / theA;
const double d = theE / theA;
// Substitute x = t - a/4 to get depressed quartic: t^4 + pt^2 + qt + r = 0
const double aShift = a / 4.0;
const double a2 = a * a;
const double a3 = a2 * a;
const double a4 = a2 * a2;
const double p = b - 3.0 * a2 / 8.0;
const double q = c - a * b / 2.0 + a3 / 8.0;
const double r = d - a * c / 4.0 + a2 * b / 16.0 - 3.0 * a4 / 256.0;
// Store original coefficients for refinement
const double aCoeffs[5] = {theE, theD, theC, theB, theA};
// Special case: biquadratic (q = 0) -> t^4 + pt^2 + r = 0
if (MathUtils::IsZero(q))
{
// Substitute u = t^2 to get u^2 + pu + r = 0
MathUtils::PolyResult aQuadResult = Quadratic(1.0, p, r);
if (!aQuadResult.IsDone())
{
aResult.Status = aQuadResult.Status;
return aResult;
}
aResult.Status = MathUtils::Status::OK;
aResult.NbRoots = 0;
for (size_t i = 0; i < aQuadResult.NbRoots; ++i)
{
const double u = aQuadResult.Roots[i];
if (u >= -MathUtils::THE_ZERO_TOL)
{
if (u <= MathUtils::THE_ZERO_TOL)
{
// u = 0 -> t = 0
aResult.Roots[aResult.NbRoots++] = -aShift;
}
else
{
// u > 0 -> t = +/-sqrt(u)
const double aSqrtU = std::sqrt(u);
aResult.Roots[aResult.NbRoots++] = aSqrtU - aShift;
aResult.Roots[aResult.NbRoots++] = -aSqrtU - aShift;
}
}
}
// Refine and sort roots
for (size_t i = 0; i < aResult.NbRoots; ++i)
{
aResult.Roots[i] = MathUtils::RefinePolyRoot(aCoeffs, 4, aResult.Roots[i]);
}
MathUtils::SortRoots(aResult.Roots.data(), aResult.NbRoots);
aResult.NbRoots = MathUtils::RemoveDuplicateRoots(aResult.Roots.data(), aResult.NbRoots);
return aResult;
}
// Use resolvent: z^3 + 2pz^2 + (p^2 - 4r)z - q^2 = 0
MathUtils::PolyResult aCubicResult = Cubic(1.0, 2.0 * p, p * p - 4.0 * r, -q * q);
if (!aCubicResult.IsDone() || aCubicResult.NbRoots == 0)
{
aResult.Status = MathUtils::Status::NumericalError;
return aResult;
}
// Find a positive root of the resolvent (there's always at least one)
double z = aCubicResult.Roots[aCubicResult.NbRoots - 1]; // Largest root
// Ensure z is positive (or at least non-negative)
if (z < -MathUtils::THE_ZERO_TOL)
{
// Try other roots
for (size_t i = 0; i < aCubicResult.NbRoots; ++i)
{
if (aCubicResult.Roots[i] >= -MathUtils::THE_ZERO_TOL)
{
z = std::max(0.0, aCubicResult.Roots[i]);
break;
}
}
}
z = std::max(0.0, z);
// Now factor: t^4 + pt^2 + qt + r = (t^2 + st + u)(t^2 - st + v)
// where s = sqrt(z), u = (p + z)/2 + q/(2s), v = (p + z)/2 - q/(2s)
const double s = std::sqrt(z);
double u, v;
if (MathUtils::IsZero(s))
{
// Special case: s = 0
// t^4 + pt^2 + r = (t^2 + u)(t^2 + v) where u + v = p, uv = r
MathUtils::PolyResult aUVResult = Quadratic(1.0, p, r);
if (!aUVResult.IsDone() || aUVResult.NbRoots < 2)
{
// Degenerate case
u = p / 2.0;
v = p / 2.0;
}
else
{
u = aUVResult.Roots[0];
v = aUVResult.Roots[1];
}
// Solve t^2 + u = 0 and t^2 + v = 0
aResult.Status = MathUtils::Status::OK;
aResult.NbRoots = 0;
if (u <= MathUtils::THE_ZERO_TOL)
{
const double aSqrt = std::sqrt(std::max(0.0, -u));
aResult.Roots[aResult.NbRoots++] = aSqrt - aShift;
if (aSqrt > MathUtils::THE_ZERO_TOL)
{
aResult.Roots[aResult.NbRoots++] = -aSqrt - aShift;
}
}
if (v <= MathUtils::THE_ZERO_TOL)
{
const double aSqrt = std::sqrt(std::max(0.0, -v));
aResult.Roots[aResult.NbRoots++] = aSqrt - aShift;
if (aSqrt > MathUtils::THE_ZERO_TOL)
{
aResult.Roots[aResult.NbRoots++] = -aSqrt - aShift;
}
}
}
else
{
// General case
const double aHalfPPlusZ = (p + z) / 2.0;
const double aQOver2S = q / (2.0 * s);
u = aHalfPPlusZ - aQOver2S;
v = aHalfPPlusZ + aQOver2S;
// Solve t^2 + st + u = 0
MathUtils::PolyResult aQuad1 = Quadratic(1.0, s, u);
// Solve t^2 - st + v = 0
MathUtils::PolyResult aQuad2 = Quadratic(1.0, -s, v);
aResult.Status = MathUtils::Status::OK;
aResult.NbRoots = 0;
if (aQuad1.IsDone())
{
for (size_t i = 0; i < aQuad1.NbRoots; ++i)
{
aResult.Roots[aResult.NbRoots++] = aQuad1.Roots[i] - aShift;
}
}
if (aQuad2.IsDone())
{
for (size_t i = 0; i < aQuad2.NbRoots; ++i)
{
aResult.Roots[aResult.NbRoots++] = aQuad2.Roots[i] - aShift;
}
}
}
// Refine all roots using Newton-Raphson
for (size_t i = 0; i < aResult.NbRoots; ++i)
{
aResult.Roots[i] = MathUtils::RefinePolyRoot(aCoeffs, 4, aResult.Roots[i]);
}
// Sort roots and remove duplicates
MathUtils::SortRoots(aResult.Roots.data(), aResult.NbRoots);
aResult.NbRoots = MathUtils::RemoveDuplicateRoots(aResult.Roots.data(), aResult.NbRoots);
return aResult;
}
} // namespace MathPoly
#endif // _MathPoly_Quartic_HeaderFile