Back to home page

EIC code displayed by LXR

 
 

    


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

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_Quartic_HeaderFile
0015 #define _MathPoly_Quartic_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Core.hxx>
0019 #include <MathUtils_Poly.hxx>
0020 #include <MathPoly_Quadratic.hxx>
0021 #include <MathPoly_Cubic.hxx>
0022 
0023 #include <cmath>
0024 
0025 //! Polynomial root finding algorithms.
0026 namespace MathPoly
0027 {
0028 using namespace MathUtils;
0029 
0030 //! Solve quartic equation: a*x^4 + b*x^3 + c*x^2 + d*x + e = 0
0031 //! Uses Ferrari's method with modern numerical enhancements.
0032 //!
0033 //! Algorithm:
0034 //! 1. Handle lower degree cases (a = 0 -> cubic)
0035 //! 2. Transform to depressed quartic: t^4 + pt^2 + qt + r = 0 via x = t - b/(4a)
0036 //! 3. Solve Ferrari's resolvent cubic
0037 //! 4. Factor quartic into two quadratics
0038 //! 5. Solve both quadratics
0039 //! 6. Apply Newton-Raphson refinement
0040 //!
0041 //! @param theA coefficient of x^4
0042 //! @param theB coefficient of x^3
0043 //! @param theC coefficient of x^2
0044 //! @param theD coefficient of x
0045 //! @param theE constant term
0046 //! @return result containing 0, 1, 2, 3, or 4 real roots (sorted in ascending order)
0047 inline MathUtils::PolyResult Quartic(double theA,
0048                                      double theB,
0049                                      double theC,
0050                                      double theD,
0051                                      double theE)
0052 {
0053   MathUtils::PolyResult aResult;
0054 
0055   // Reduce to cubic if leading coefficient is zero
0056   if (MathUtils::IsZero(theA))
0057   {
0058     return Cubic(theB, theC, theD, theE);
0059   }
0060 
0061   // Scale coefficients for numerical stability
0062   const double aScale =
0063     std::max({std::abs(theA), std::abs(theB), std::abs(theC), std::abs(theD), std::abs(theE)});
0064   if (aScale < MathUtils::THE_ZERO_TOL)
0065   {
0066     aResult.Status = MathUtils::Status::InfiniteSolutions;
0067     return aResult;
0068   }
0069 
0070   // Normalize to monic quartic: x^4 + ax^3 + bx^2 + cx + d = 0
0071   const double a = theB / theA;
0072   const double b = theC / theA;
0073   const double c = theD / theA;
0074   const double d = theE / theA;
0075 
0076   // Substitute x = t - a/4 to get depressed quartic: t^4 + pt^2 + qt + r = 0
0077   const double aShift = a / 4.0;
0078   const double a2     = a * a;
0079   const double a3     = a2 * a;
0080   const double a4     = a2 * a2;
0081 
0082   const double p = b - 3.0 * a2 / 8.0;
0083   const double q = c - a * b / 2.0 + a3 / 8.0;
0084   const double r = d - a * c / 4.0 + a2 * b / 16.0 - 3.0 * a4 / 256.0;
0085 
0086   // Store original coefficients for refinement
0087   const double aCoeffs[5] = {theE, theD, theC, theB, theA};
0088 
0089   // Special case: biquadratic (q = 0) -> t^4 + pt^2 + r = 0
0090   if (MathUtils::IsZero(q))
0091   {
0092     // Substitute u = t^2 to get u^2 + pu + r = 0
0093     MathUtils::PolyResult aQuadResult = Quadratic(1.0, p, r);
0094 
0095     if (!aQuadResult.IsDone())
0096     {
0097       aResult.Status = aQuadResult.Status;
0098       return aResult;
0099     }
0100 
0101     aResult.Status  = MathUtils::Status::OK;
0102     aResult.NbRoots = 0;
0103 
0104     for (size_t i = 0; i < aQuadResult.NbRoots; ++i)
0105     {
0106       const double u = aQuadResult.Roots[i];
0107       if (u >= -MathUtils::THE_ZERO_TOL)
0108       {
0109         if (u <= MathUtils::THE_ZERO_TOL)
0110         {
0111           // u = 0 -> t = 0
0112           aResult.Roots[aResult.NbRoots++] = -aShift;
0113         }
0114         else
0115         {
0116           // u > 0 -> t = +/-sqrt(u)
0117           const double aSqrtU              = std::sqrt(u);
0118           aResult.Roots[aResult.NbRoots++] = aSqrtU - aShift;
0119           aResult.Roots[aResult.NbRoots++] = -aSqrtU - aShift;
0120         }
0121       }
0122     }
0123 
0124     // Refine and sort roots
0125     for (size_t i = 0; i < aResult.NbRoots; ++i)
0126     {
0127       aResult.Roots[i] = MathUtils::RefinePolyRoot(aCoeffs, 4, aResult.Roots[i]);
0128     }
0129     MathUtils::SortRoots(aResult.Roots.data(), aResult.NbRoots);
0130     aResult.NbRoots = MathUtils::RemoveDuplicateRoots(aResult.Roots.data(), aResult.NbRoots);
0131 
0132     return aResult;
0133   }
0134 
0135   // Use resolvent: z^3 + 2pz^2 + (p^2 - 4r)z - q^2 = 0
0136   MathUtils::PolyResult aCubicResult = Cubic(1.0, 2.0 * p, p * p - 4.0 * r, -q * q);
0137 
0138   if (!aCubicResult.IsDone() || aCubicResult.NbRoots == 0)
0139   {
0140     aResult.Status = MathUtils::Status::NumericalError;
0141     return aResult;
0142   }
0143 
0144   // Find a positive root of the resolvent (there's always at least one)
0145   double z = aCubicResult.Roots[aCubicResult.NbRoots - 1]; // Largest root
0146 
0147   // Ensure z is positive (or at least non-negative)
0148   if (z < -MathUtils::THE_ZERO_TOL)
0149   {
0150     // Try other roots
0151     for (size_t i = 0; i < aCubicResult.NbRoots; ++i)
0152     {
0153       if (aCubicResult.Roots[i] >= -MathUtils::THE_ZERO_TOL)
0154       {
0155         z = std::max(0.0, aCubicResult.Roots[i]);
0156         break;
0157       }
0158     }
0159   }
0160   z = std::max(0.0, z);
0161 
0162   // Now factor: t^4 + pt^2 + qt + r = (t^2 + st + u)(t^2 - st + v)
0163   // where s = sqrt(z), u = (p + z)/2 + q/(2s), v = (p + z)/2 - q/(2s)
0164 
0165   const double s = std::sqrt(z);
0166   double       u, v;
0167 
0168   if (MathUtils::IsZero(s))
0169   {
0170     // Special case: s = 0
0171     // t^4 + pt^2 + r = (t^2 + u)(t^2 + v) where u + v = p, uv = r
0172     MathUtils::PolyResult aUVResult = Quadratic(1.0, p, r);
0173     if (!aUVResult.IsDone() || aUVResult.NbRoots < 2)
0174     {
0175       // Degenerate case
0176       u = p / 2.0;
0177       v = p / 2.0;
0178     }
0179     else
0180     {
0181       u = aUVResult.Roots[0];
0182       v = aUVResult.Roots[1];
0183     }
0184 
0185     // Solve t^2 + u = 0 and t^2 + v = 0
0186     aResult.Status  = MathUtils::Status::OK;
0187     aResult.NbRoots = 0;
0188 
0189     if (u <= MathUtils::THE_ZERO_TOL)
0190     {
0191       const double aSqrt               = std::sqrt(std::max(0.0, -u));
0192       aResult.Roots[aResult.NbRoots++] = aSqrt - aShift;
0193       if (aSqrt > MathUtils::THE_ZERO_TOL)
0194       {
0195         aResult.Roots[aResult.NbRoots++] = -aSqrt - aShift;
0196       }
0197     }
0198 
0199     if (v <= MathUtils::THE_ZERO_TOL)
0200     {
0201       const double aSqrt               = std::sqrt(std::max(0.0, -v));
0202       aResult.Roots[aResult.NbRoots++] = aSqrt - aShift;
0203       if (aSqrt > MathUtils::THE_ZERO_TOL)
0204       {
0205         aResult.Roots[aResult.NbRoots++] = -aSqrt - aShift;
0206       }
0207     }
0208   }
0209   else
0210   {
0211     // General case
0212     const double aHalfPPlusZ = (p + z) / 2.0;
0213     const double aQOver2S    = q / (2.0 * s);
0214 
0215     u = aHalfPPlusZ - aQOver2S;
0216     v = aHalfPPlusZ + aQOver2S;
0217 
0218     // Solve t^2 + st + u = 0
0219     MathUtils::PolyResult aQuad1 = Quadratic(1.0, s, u);
0220 
0221     // Solve t^2 - st + v = 0
0222     MathUtils::PolyResult aQuad2 = Quadratic(1.0, -s, v);
0223 
0224     aResult.Status  = MathUtils::Status::OK;
0225     aResult.NbRoots = 0;
0226 
0227     if (aQuad1.IsDone())
0228     {
0229       for (size_t i = 0; i < aQuad1.NbRoots; ++i)
0230       {
0231         aResult.Roots[aResult.NbRoots++] = aQuad1.Roots[i] - aShift;
0232       }
0233     }
0234 
0235     if (aQuad2.IsDone())
0236     {
0237       for (size_t i = 0; i < aQuad2.NbRoots; ++i)
0238       {
0239         aResult.Roots[aResult.NbRoots++] = aQuad2.Roots[i] - aShift;
0240       }
0241     }
0242   }
0243 
0244   // Refine all roots using Newton-Raphson
0245   for (size_t i = 0; i < aResult.NbRoots; ++i)
0246   {
0247     aResult.Roots[i] = MathUtils::RefinePolyRoot(aCoeffs, 4, aResult.Roots[i]);
0248   }
0249 
0250   // Sort roots and remove duplicates
0251   MathUtils::SortRoots(aResult.Roots.data(), aResult.NbRoots);
0252   aResult.NbRoots = MathUtils::RemoveDuplicateRoots(aResult.Roots.data(), aResult.NbRoots);
0253 
0254   return aResult;
0255 }
0256 
0257 } // namespace MathPoly
0258 
0259 #endif // _MathPoly_Quartic_HeaderFile