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_Laguerre_HeaderFile
0015 #define _MathPoly_Laguerre_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Core.hxx>
0019 #include <MathPoly_Quartic.hxx>
0020 
0021 #include <array>
0022 #include <cmath>
0023 #include <complex>
0024 #include <algorithm>
0025 
0026 //! Polynomial root finding algorithms using Laguerre's method.
0027 namespace MathPoly
0028 {
0029 using namespace MathUtils;
0030 
0031 //! Maximum polynomial degree supported by Laguerre solver.
0032 constexpr int THE_MAX_POLY_DEGREE = 20;
0033 
0034 //! Result for general polynomial solver.
0035 struct GeneralPolyResult
0036 {
0037   MathUtils::Status                                     Status = MathUtils::Status::NotConverged;
0038   std::array<double, THE_MAX_POLY_DEGREE>               Roots  = {};
0039   std::array<std::complex<double>, THE_MAX_POLY_DEGREE> ComplexRoots   = {};
0040   size_t                                                NbRoots        = 0;
0041   size_t                                                NbComplexRoots = 0;
0042 
0043   bool IsDone() const { return Status == MathUtils::Status::OK; }
0044 
0045   explicit operator bool() const { return IsDone(); }
0046 };
0047 
0048 namespace detail
0049 {
0050 
0051 //! Evaluate polynomial and its first two derivatives at x (complex version).
0052 //! Polynomial: c[n]*x^n + c[n-1]*x^(n-1) + ... + c[1]*x + c[0]
0053 //! @param theCoeffs coefficients array (c[0] = constant term)
0054 //! @param theDegree polynomial degree
0055 //! @param theX evaluation point
0056 //! @param theP output: polynomial value P(x)
0057 //! @param theDP output: first derivative P'(x)
0058 //! @param theD2P output: second derivative P''(x)
0059 inline void EvaluatePolynomialWithDerivatives(const double*         theCoeffs,
0060                                               int                   theDegree,
0061                                               std::complex<double>  theX,
0062                                               std::complex<double>& theP,
0063                                               std::complex<double>& theDP,
0064                                               std::complex<double>& theD2P)
0065 {
0066   // Horner's method with derivative computation
0067   theP   = std::complex<double>(theCoeffs[theDegree], 0.0);
0068   theDP  = std::complex<double>(0.0, 0.0);
0069   theD2P = std::complex<double>(0.0, 0.0);
0070 
0071   for (int i = theDegree - 1; i >= 0; --i)
0072   {
0073     theD2P = theD2P * theX + theDP;
0074     theDP  = theDP * theX + theP;
0075     theP   = theP * theX + std::complex<double>(theCoeffs[i], 0.0);
0076   }
0077   theD2P *= 2.0;
0078 }
0079 
0080 //! Laguerre iteration to find one root of polynomial.
0081 //! @param theCoeffs coefficients array
0082 //! @param theDegree polynomial degree
0083 //! @param theX0 initial guess
0084 //! @param theTol convergence tolerance
0085 //! @param theMaxIter maximum iterations
0086 //! @return refined root estimate
0087 inline std::complex<double> LaguerreIteration(const double*        theCoeffs,
0088                                               int                  theDegree,
0089                                               std::complex<double> theX0,
0090                                               double               theTol,
0091                                               int                  theMaxIter)
0092 {
0093   std::complex<double> aX = theX0;
0094   const double         aN = static_cast<double>(theDegree);
0095 
0096   for (int anIter = 0; anIter < theMaxIter; ++anIter)
0097   {
0098     std::complex<double> aP, aDP, aD2P;
0099     EvaluatePolynomialWithDerivatives(theCoeffs, theDegree, aX, aP, aDP, aD2P);
0100 
0101     const double aAbsP = std::abs(aP);
0102     if (aAbsP < theTol)
0103     {
0104       return aX;
0105     }
0106 
0107     // Laguerre's formula: x_new = x - n*P / (P' +/- sqrt((n-1)*((n-1)*P'^2 - n*P*P'')))
0108     std::complex<double> aG  = aDP / aP;
0109     std::complex<double> aH  = aG * aG - aD2P / aP;
0110     std::complex<double> aSq = std::sqrt((aN - 1.0) * (aN * aH - aG * aG));
0111 
0112     // Choose denominator with larger magnitude for stability
0113     std::complex<double> aDenom1 = aG + aSq;
0114     std::complex<double> aDenom2 = aG - aSq;
0115     std::complex<double> aDenom  = (std::abs(aDenom1) > std::abs(aDenom2)) ? aDenom1 : aDenom2;
0116 
0117     std::complex<double> aDelta;
0118     if (std::abs(aDenom) < MathUtils::THE_ZERO_TOL)
0119     {
0120       // Fallback: simple Newton step
0121       if (std::abs(aDP) < MathUtils::THE_ZERO_TOL)
0122       {
0123         aDelta = std::complex<double>(1.0 + std::abs(aX), 0.0);
0124       }
0125       else
0126       {
0127         aDelta = aP / aDP;
0128       }
0129     }
0130     else
0131     {
0132       aDelta = std::complex<double>(aN, 0.0) / aDenom;
0133     }
0134 
0135     aX -= aDelta;
0136 
0137     // Check convergence
0138     if (std::abs(aDelta) < theTol * (1.0 + std::abs(aX)))
0139     {
0140       return aX;
0141     }
0142   }
0143 
0144   return aX;
0145 }
0146 
0147 //! Deflate polynomial by removing a real root.
0148 //! Divides p(x) by (x - root) to get q(x).
0149 //! @param theCoeffs input coefficients, output deflated coefficients
0150 //! @param theDegree polynomial degree (will be decremented)
0151 //! @param theRoot root to remove
0152 inline void DeflateReal(double* theCoeffs, int& theDegree, double theRoot)
0153 {
0154   // Synthetic division
0155   double aCarry = theCoeffs[theDegree];
0156   for (int i = theDegree - 1; i >= 0; --i)
0157   {
0158     double aTemp = theCoeffs[i];
0159     theCoeffs[i] = aCarry;
0160     aCarry       = aTemp + aCarry * theRoot;
0161   }
0162   --theDegree;
0163 }
0164 
0165 //! Deflate polynomial by removing a complex conjugate pair.
0166 //! Divides p(x) by (x^2 + b*x + c) where b = -2*Re(root), c = |root|^2.
0167 //! @param theCoeffs input coefficients, output deflated coefficients
0168 //! @param theDegree polynomial degree (will be decremented by 2)
0169 //! @param theRoot complex root (its conjugate is also removed)
0170 inline void DeflateComplex(double* theCoeffs, int& theDegree, std::complex<double> theRoot)
0171 {
0172   // Quadratic factor: x^2 + b*x + c where b = -2*re, c = re^2 + im^2
0173   const double aRe = theRoot.real();
0174   const double aIm = theRoot.imag();
0175   const double aB  = -2.0 * aRe;
0176   const double aC  = aRe * aRe + aIm * aIm;
0177 
0178   // Synthetic division by (x^2 + b*x + c)
0179   // If p(x) = a_n*x^n + ... + a_0
0180   // And p(x) = (x^2 + b*x + c) * q(x) + remainder
0181   // Where q(x) = q_{n-2}*x^{n-2} + ... + q_0
0182 
0183   std::array<double, THE_MAX_POLY_DEGREE + 1> aQuotient;
0184   aQuotient.fill(0.0);
0185 
0186   // Work from highest degree down
0187   aQuotient[theDegree - 2] = theCoeffs[theDegree];
0188   if (theDegree >= 3)
0189   {
0190     aQuotient[theDegree - 3] = theCoeffs[theDegree - 1] - aB * aQuotient[theDegree - 2];
0191   }
0192 
0193   for (int i = theDegree - 4; i >= 0; --i)
0194   {
0195     aQuotient[i] = theCoeffs[i + 2] - aB * aQuotient[i + 1] - aC * aQuotient[i + 2];
0196   }
0197 
0198   // Copy quotient back to coefficients
0199   for (int i = 0; i <= theDegree - 2; ++i)
0200   {
0201     theCoeffs[i] = aQuotient[i];
0202   }
0203   theDegree -= 2;
0204 }
0205 
0206 //! Refine a real root using Newton-Raphson.
0207 inline double RefineRealRoot(const double* theOrigCoeffs, int theOrigDegree, double theRoot)
0208 {
0209   constexpr int    THE_MAX_ITER = 10;
0210   constexpr double THE_TOL      = 1.0e-14;
0211 
0212   double aX = theRoot;
0213   for (int anIter = 0; anIter < THE_MAX_ITER; ++anIter)
0214   {
0215     // Evaluate P and P' using Horner
0216     double aP  = theOrigCoeffs[theOrigDegree];
0217     double aDP = 0.0;
0218     for (int i = theOrigDegree - 1; i >= 0; --i)
0219     {
0220       aDP = aDP * aX + aP;
0221       aP  = aP * aX + theOrigCoeffs[i];
0222     }
0223 
0224     if (std::abs(aDP) < MathUtils::THE_ZERO_TOL)
0225     {
0226       break;
0227     }
0228 
0229     const double aDelta = aP / aDP;
0230     aX -= aDelta;
0231 
0232     if (std::abs(aDelta) < THE_TOL * (1.0 + std::abs(aX)))
0233     {
0234       break;
0235     }
0236   }
0237   return aX;
0238 }
0239 
0240 } // namespace detail
0241 
0242 //! Solve polynomial equation using Laguerre's method with deflation.
0243 //! Works for any degree up to THE_MAX_POLY_DEGREE.
0244 //!
0245 //! Algorithm:
0246 //! 1. Use Laguerre iteration to find one root (complex)
0247 //! 2. If root is nearly real, treat it as real and deflate
0248 //! 3. If root is complex, deflate by quadratic factor (conjugate pair)
0249 //! 4. Repeat until all roots found
0250 //! 5. Refine real roots using Newton on original polynomial
0251 //!
0252 //! @param theCoeffs coefficients array [a0, a1, a2, ..., an] for a0 + a1*x + ... + an*x^n
0253 //! @param theDegree polynomial degree
0254 //! @param theTol tolerance for convergence and real/complex discrimination
0255 //! @return GeneralPolyResult containing real and complex roots
0256 inline GeneralPolyResult Laguerre(const double* theCoeffs, int theDegree, double theTol = 1.0e-12)
0257 {
0258   GeneralPolyResult aResult;
0259 
0260   // Validate input
0261   if (theDegree < 1 || theDegree > THE_MAX_POLY_DEGREE)
0262   {
0263     aResult.Status = MathUtils::Status::InvalidInput;
0264     return aResult;
0265   }
0266 
0267   // Check leading coefficient
0268   if (std::abs(theCoeffs[theDegree]) < MathUtils::THE_ZERO_TOL)
0269   {
0270     aResult.Status = MathUtils::Status::InvalidInput;
0271     return aResult;
0272   }
0273 
0274   // Copy coefficients for deflation
0275   std::array<double, THE_MAX_POLY_DEGREE + 1> aWorkCoeffs;
0276   for (int i = 0; i <= theDegree; ++i)
0277   {
0278     aWorkCoeffs[i] = theCoeffs[i];
0279   }
0280 
0281   // Store original for refinement
0282   std::array<double, THE_MAX_POLY_DEGREE + 1> aOrigCoeffs;
0283   for (int i = 0; i <= theDegree; ++i)
0284   {
0285     aOrigCoeffs[i] = theCoeffs[i];
0286   }
0287 
0288   int aDeg = theDegree;
0289 
0290   // Find all roots
0291   int aStartIdx = 0;
0292   while (aDeg > 0)
0293   {
0294     // Use different starting points to improve convergence
0295     // Rotate through starting positions
0296     std::array<std::complex<double>, 4> aStartPoints = {std::complex<double>(0.0, 0.1),
0297                                                         std::complex<double>(1.0, 0.5),
0298                                                         std::complex<double>(-0.5, 0.3),
0299                                                         std::complex<double>(0.5, -0.3)};
0300 
0301     std::complex<double> aX0 = aStartPoints[aStartIdx % 4];
0302     ++aStartIdx;
0303 
0304     // Use Laguerre iteration
0305     std::complex<double> aRoot =
0306       detail::LaguerreIteration(aWorkCoeffs.data(), aDeg, aX0, theTol, 100);
0307 
0308     // Determine if root is real or complex
0309     const double aImagPart = std::abs(aRoot.imag());
0310     const double aRealPart = std::abs(aRoot.real());
0311     const double aScale    = std::max(1.0, aRealPart);
0312 
0313     if (aImagPart < theTol * aScale)
0314     {
0315       // Real root
0316       double aRealRoot = aRoot.real();
0317 
0318       // Refine using Newton on original polynomial
0319       aRealRoot = detail::RefineRealRoot(aOrigCoeffs.data(), theDegree, aRealRoot);
0320 
0321       aResult.Roots[aResult.NbRoots++] = aRealRoot;
0322 
0323       // Deflate
0324       detail::DeflateReal(aWorkCoeffs.data(), aDeg, aRealRoot);
0325     }
0326     else
0327     {
0328       // Complex conjugate pair
0329       aResult.ComplexRoots[aResult.NbComplexRoots++] = aRoot;
0330       aResult.ComplexRoots[aResult.NbComplexRoots++] = std::conj(aRoot);
0331 
0332       // Deflate by quadratic
0333       detail::DeflateComplex(aWorkCoeffs.data(), aDeg, aRoot);
0334     }
0335   }
0336 
0337   // Sort real roots
0338   std::sort(aResult.Roots.begin(), aResult.Roots.begin() + aResult.NbRoots);
0339 
0340   // Remove duplicate real roots
0341   if (aResult.NbRoots > 1)
0342   {
0343     size_t aNewCount = 1;
0344     for (size_t i = 1; i < aResult.NbRoots; ++i)
0345     {
0346       if (std::abs(aResult.Roots[i] - aResult.Roots[aNewCount - 1]) > theTol)
0347       {
0348         aResult.Roots[aNewCount++] = aResult.Roots[i];
0349       }
0350     }
0351     aResult.NbRoots = aNewCount;
0352   }
0353 
0354   aResult.Status = MathUtils::Status::OK;
0355   return aResult;
0356 }
0357 
0358 //! Convenience function: solve polynomial given as array with specified size.
0359 //! @param theCoeffs coefficients [a0, a1, ..., an]
0360 //! @param theSize size of coefficient array (degree + 1)
0361 //! @param theTol tolerance
0362 //! @return GeneralPolyResult
0363 inline GeneralPolyResult LaguerreN(const double* theCoeffs, size_t theSize, double theTol = 1.0e-12)
0364 {
0365   if (theSize < 2)
0366   {
0367     GeneralPolyResult aResult;
0368     aResult.Status = MathUtils::Status::InvalidInput;
0369     return aResult;
0370   }
0371   return Laguerre(theCoeffs, static_cast<int>(theSize - 1), theTol);
0372 }
0373 
0374 //! Solve sextic (degree 6) polynomial: a*x^6 + b*x^5 + c*x^4 + d*x^3 + e*x^2 + f*x + g = 0
0375 //! @param theA coefficient of x^6
0376 //! @param theB coefficient of x^5
0377 //! @param theC coefficient of x^4
0378 //! @param theD coefficient of x^3
0379 //! @param theE coefficient of x^2
0380 //! @param theF coefficient of x
0381 //! @param theG constant term
0382 //! @return PolyResult containing real roots only
0383 inline MathUtils::PolyResult Sextic(double theA,
0384                                     double theB,
0385                                     double theC,
0386                                     double theD,
0387                                     double theE,
0388                                     double theF,
0389                                     double theG)
0390 {
0391   MathUtils::PolyResult aResult;
0392 
0393   // Handle leading zero coefficient
0394   if (MathUtils::IsZero(theA))
0395   {
0396     // Reduce to quintic, then use Laguerre
0397     double aCoeffs[6] = {theG, theF, theE, theD, theC, theB};
0398     auto   aGenResult = Laguerre(aCoeffs, 5);
0399     if (!aGenResult.IsDone())
0400     {
0401       aResult.Status = aGenResult.Status;
0402       return aResult;
0403     }
0404     aResult.Status  = MathUtils::Status::OK;
0405     aResult.NbRoots = std::min(aGenResult.NbRoots, size_t(4));
0406     for (size_t i = 0; i < aResult.NbRoots; ++i)
0407     {
0408       aResult.Roots[i] = aGenResult.Roots[i];
0409     }
0410     return aResult;
0411   }
0412 
0413   double aCoeffs[7] = {theG, theF, theE, theD, theC, theB, theA};
0414   auto   aGenResult = Laguerre(aCoeffs, 6);
0415 
0416   if (!aGenResult.IsDone())
0417   {
0418     aResult.Status = aGenResult.Status;
0419     return aResult;
0420   }
0421 
0422   aResult.Status  = MathUtils::Status::OK;
0423   aResult.NbRoots = std::min(aGenResult.NbRoots, size_t(4));
0424   for (size_t i = 0; i < aResult.NbRoots; ++i)
0425   {
0426     aResult.Roots[i] = aGenResult.Roots[i];
0427   }
0428 
0429   return aResult;
0430 }
0431 
0432 //! Solve quintic (degree 5) polynomial: a*x^5 + b*x^4 + c*x^3 + d*x^2 + e*x + f = 0
0433 //! @param theA coefficient of x^5
0434 //! @param theB coefficient of x^4
0435 //! @param theC coefficient of x^3
0436 //! @param theD coefficient of x^2
0437 //! @param theE coefficient of x
0438 //! @param theF constant term
0439 //! @return PolyResult containing real roots only
0440 inline MathUtils::PolyResult Quintic(double theA,
0441                                      double theB,
0442                                      double theC,
0443                                      double theD,
0444                                      double theE,
0445                                      double theF)
0446 {
0447   MathUtils::PolyResult aResult;
0448 
0449   if (MathUtils::IsZero(theA))
0450   {
0451     // Reduce to quartic - use existing solver
0452     return Quartic(theB, theC, theD, theE, theF);
0453   }
0454 
0455   double aCoeffs[6] = {theF, theE, theD, theC, theB, theA};
0456   auto   aGenResult = Laguerre(aCoeffs, 5);
0457 
0458   if (!aGenResult.IsDone())
0459   {
0460     aResult.Status = aGenResult.Status;
0461     return aResult;
0462   }
0463 
0464   aResult.Status  = MathUtils::Status::OK;
0465   aResult.NbRoots = std::min(aGenResult.NbRoots, size_t(4));
0466   for (size_t i = 0; i < aResult.NbRoots; ++i)
0467   {
0468     aResult.Roots[i] = aGenResult.Roots[i];
0469   }
0470 
0471   return aResult;
0472 }
0473 
0474 //! Solve octic (degree 8) polynomial using Laguerre's method.
0475 //! Useful for Circle-Ellipse extrema after Weierstrass substitution.
0476 //! @param theCoeffs coefficients [a0, a1, ..., a8] where polynomial is a0 + a1*x + ... + a8*x^8
0477 //! @return GeneralPolyResult containing all real roots
0478 inline GeneralPolyResult Octic(const double theCoeffs[9])
0479 {
0480   return Laguerre(theCoeffs, 8);
0481 }
0482 
0483 } // namespace MathPoly
0484 
0485 #endif // _MathPoly_Laguerre_HeaderFile