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 _MathRoot_Trig_HeaderFile
0015 #define _MathRoot_Trig_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Config.hxx>
0019 #include <MathUtils_Core.hxx>
0020 #include <MathPoly_Quadratic.hxx>
0021 #include <MathPoly_Quartic.hxx>
0022 
0023 #include <cmath>
0024 #include <algorithm>
0025 
0026 namespace MathRoot
0027 {
0028 using namespace MathUtils;
0029 
0030 //! Result for trigonometric equation solver.
0031 struct TrigResult
0032 {
0033   MathUtils::Status     Status        = MathUtils::Status::NotConverged;
0034   std::array<double, 4> Roots         = {0.0, 0.0, 0.0, 0.0};
0035   int                   NbRoots       = 0;
0036   bool                  InfiniteRoots = false;
0037 
0038   bool IsDone() const { return Status == MathUtils::Status::OK; }
0039 
0040   explicit operator bool() const { return IsDone(); }
0041 };
0042 
0043 //! Solve trigonometric equation: a*cos^2(x) + 2*b*cos(x)*sin(x) + c*cos(x) + d*sin(x) + e = 0.
0044 //!
0045 //! Uses half-angle substitution t = tan(x/2) to convert to polynomial:
0046 //! - cos(x) = (1-t^2)/(1+t^2)
0047 //! - sin(x) = 2t/(1+t^2)
0048 //!
0049 //! Resulting polynomial is of degree 4, 3, or 2 depending on coefficients.
0050 //! Roots are filtered to lie within [theInfBound, theSupBound].
0051 //!
0052 //! @param theA coefficient of cos^2(x)
0053 //! @param theB coefficient of cos(x)*sin(x) (equation uses 2*b)
0054 //! @param theC coefficient of cos(x)
0055 //! @param theD coefficient of sin(x)
0056 //! @param theE constant term
0057 //! @param theInfBound lower bound for roots (default 0)
0058 //! @param theSupBound upper bound for roots (default 2*PI)
0059 //! @param theEps tolerance for coefficient comparison
0060 //! @return TrigResult containing roots in specified interval
0061 inline TrigResult Trigonometric(double theA,
0062                                 double theB,
0063                                 double theC,
0064                                 double theD,
0065                                 double theE,
0066                                 double theInfBound = 0.0,
0067                                 double theSupBound = THE_2PI,
0068                                 double theEps      = 1.5e-12)
0069 {
0070   TrigResult aResult;
0071   aResult.Status = MathUtils::Status::OK;
0072 
0073   // Compute working interval
0074   double aMyBorneInf, aDelta, aMod;
0075   if (theInfBound <= std::numeric_limits<double>::lowest() / 2.0
0076       && theSupBound >= std::numeric_limits<double>::max() / 2.0)
0077   {
0078     aMyBorneInf = 0.0;
0079     aDelta      = THE_2PI;
0080     aMod        = 0.0;
0081   }
0082   else if (theSupBound >= std::numeric_limits<double>::max() / 2.0)
0083   {
0084     aMyBorneInf = theInfBound;
0085     aDelta      = THE_2PI;
0086     aMod        = aMyBorneInf / THE_2PI;
0087   }
0088   else if (theInfBound <= std::numeric_limits<double>::lowest() / 2.0)
0089   {
0090     aMyBorneInf = theSupBound - THE_2PI;
0091     aDelta      = THE_2PI;
0092     aMod        = aMyBorneInf / THE_2PI;
0093   }
0094   else
0095   {
0096     aMyBorneInf = theInfBound;
0097     aDelta      = theSupBound - theInfBound;
0098     aMod        = theInfBound / THE_2PI;
0099     if (aDelta > THE_2PI)
0100     {
0101       aDelta = THE_2PI;
0102     }
0103   }
0104 
0105   std::array<double, 4> aZer       = {0.0, 0.0, 0.0, 0.0};
0106   size_t                aNZer      = 0;
0107   const double          aDelta_Eps = std::numeric_limits<double>::epsilon() * std::abs(aDelta);
0108 
0109   // Case: A = B = 0 (degree <= 2 in cos/sin)
0110   if (std::abs(theA) <= theEps && std::abs(theB) <= theEps)
0111   {
0112     if (std::abs(theC) <= theEps)
0113     {
0114       if (std::abs(theD) <= theEps)
0115       {
0116         if (std::abs(theE) <= theEps)
0117         {
0118           aResult.InfiniteRoots = true;
0119           return aResult;
0120         }
0121         else
0122         {
0123           aResult.NbRoots = 0;
0124           return aResult;
0125         }
0126       }
0127       else
0128       {
0129         // d*sin(x) + e = 0  =>  sin(x) = -e/d
0130         double aVal = -theE / theD;
0131         if (std::abs(aVal) > 1.0)
0132         {
0133           aResult.NbRoots = 0;
0134           return aResult;
0135         }
0136 
0137         aZer[0] = std::asin(aVal);
0138         aZer[1] = THE_PI - aZer[0];
0139         aNZer   = 2;
0140 
0141         // Adjust to positive range
0142         for (size_t i = 0; i < aNZer; ++i)
0143         {
0144           if (aZer[i] <= -theEps)
0145           {
0146             aZer[i] = THE_2PI - std::abs(aZer[i]);
0147           }
0148           aZer[i] += std::trunc(aMod) * THE_2PI;
0149           double aX = aZer[i] - aMyBorneInf;
0150           if (aX >= -aDelta_Eps && aX <= aDelta + aDelta_Eps)
0151           {
0152             aResult.Roots[aResult.NbRoots++] = aZer[i];
0153           }
0154         }
0155         return aResult;
0156       }
0157     }
0158     else if (std::abs(theD) <= theEps)
0159     {
0160       // c*cos(x) + e = 0  =>  cos(x) = -e/c
0161       double aVal = -theE / theC;
0162       if (std::abs(aVal) > 1.0)
0163       {
0164         aResult.NbRoots = 0;
0165         return aResult;
0166       }
0167 
0168       double aPrincipal = std::acos(aVal);
0169       aZer[0]           = aPrincipal;  // acos gives [0, PI]
0170       aZer[1]           = -aPrincipal; // Negative angle
0171       aNZer             = 2;
0172 
0173       // For each solution, find the representative in or near the given bounds
0174       for (size_t i = 0; i < aNZer; ++i)
0175       {
0176         double aAngle = aZer[i];
0177         // Shift angle to be near the lower bound
0178         double aK = std::floor((theInfBound - aAngle) / THE_2PI);
0179         aAngle += (aK + 1) * THE_2PI; // Start from a value >= theInfBound - 2*PI
0180 
0181         // Check both this value and the next period
0182         for (int aPeriod = 0; aPeriod < 2; ++aPeriod)
0183         {
0184           double aTestAngle = aAngle + aPeriod * THE_2PI;
0185           if (aTestAngle >= theInfBound - aDelta_Eps && aTestAngle <= theSupBound + aDelta_Eps)
0186           {
0187             // Avoid duplicates
0188             bool aDup = false;
0189             for (int k = 0; k < aResult.NbRoots; ++k)
0190             {
0191               if (std::abs(aTestAngle - aResult.Roots[k]) < theEps)
0192               {
0193                 aDup = true;
0194                 break;
0195               }
0196             }
0197             if (!aDup && aResult.NbRoots < 4)
0198             {
0199               aResult.Roots[aResult.NbRoots++] = aTestAngle;
0200             }
0201           }
0202         }
0203       }
0204       return aResult;
0205     }
0206     else
0207     {
0208       // c*cos(x) + d*sin(x) + e = 0
0209       // Using t = tan(x/2): (e-c)*t^2 + 2d*t + (e+c) = 0
0210       double aAA = theE - theC;
0211       double aBB = 2.0 * theD;
0212       double aCC = theE + theC;
0213 
0214       MathPoly::PolyResult aPoly = MathPoly::Quadratic(aAA, aBB, aCC);
0215       if (!aPoly.IsDone())
0216       {
0217         aResult.Status = aPoly.Status;
0218         return aResult;
0219       }
0220       if (aPoly.Status == MathUtils::Status::InfiniteSolutions)
0221       {
0222         aResult.InfiniteRoots = true;
0223         return aResult;
0224       }
0225 
0226       aNZer = aPoly.NbRoots;
0227       for (size_t i = 0; i < aNZer; ++i)
0228       {
0229         aZer[i] = aPoly.Roots[i];
0230       }
0231     }
0232   }
0233   else
0234   {
0235     // Special case: A = E = 0
0236     if (std::abs(theA) <= theEps && std::abs(theE) <= theEps)
0237     {
0238       if (std::abs(theC) <= theEps)
0239       {
0240         // 2*B*sin*cos + D*sin = 0  =>  sin(x)*(2*B*cos(x) + D) = 0
0241         aZer[0] = 0.0;
0242         aZer[1] = THE_PI;
0243         aNZer   = 2;
0244 
0245         double aVal = -theD / (theB * 2.0);
0246         if (std::abs(aVal) <= 1.0 + 1.0e-10)
0247         {
0248           if (aVal >= 1.0)
0249           {
0250             aZer[2] = 0.0;
0251             aZer[3] = 0.0;
0252           }
0253           else if (aVal <= -1.0)
0254           {
0255             aZer[2] = THE_PI;
0256             aZer[3] = THE_PI;
0257           }
0258           else
0259           {
0260             aZer[2] = std::acos(aVal);
0261             aZer[3] = THE_2PI - aZer[2];
0262           }
0263           aNZer = 4;
0264         }
0265 
0266         for (size_t i = 0; i < aNZer; ++i)
0267         {
0268           if (aZer[i] <= aMyBorneInf - theEps)
0269           {
0270             aZer[i] += THE_2PI;
0271           }
0272           aZer[i] += std::trunc(aMod) * THE_2PI;
0273           double aX = aZer[i] - aMyBorneInf;
0274           if (aX >= -1.0e-10 && aX <= aDelta + 1.0e-10)
0275           {
0276             aZer[i] = std::max(theInfBound, std::min(theSupBound, aZer[i]));
0277             aResult.Roots[aResult.NbRoots++] = aZer[i];
0278           }
0279         }
0280         return aResult;
0281       }
0282       if (std::abs(theD) <= theEps)
0283       {
0284         // 2*B*sin*cos + C*cos = 0  =>  cos(x)*(2*B*sin(x) + C) = 0
0285         aZer[0] = THE_PI / 2.0;
0286         aZer[1] = THE_PI * 3.0 / 2.0;
0287         aNZer   = 2;
0288 
0289         double aVal = -theC / (theB * 2.0);
0290         if (std::abs(aVal) <= 1.0 + 1.0e-10)
0291         {
0292           if (aVal >= 1.0)
0293           {
0294             aZer[2] = THE_PI / 2.0;
0295             aZer[3] = THE_PI / 2.0;
0296           }
0297           else if (aVal <= -1.0)
0298           {
0299             aZer[2] = THE_PI * 3.0 / 2.0;
0300             aZer[3] = THE_PI * 3.0 / 2.0;
0301           }
0302           else
0303           {
0304             aZer[2] = std::asin(aVal);
0305             aZer[3] = THE_PI - aZer[2];
0306           }
0307           aNZer = 4;
0308         }
0309 
0310         for (size_t i = 0; i < aNZer; ++i)
0311         {
0312           if (aZer[i] <= aMyBorneInf - theEps)
0313           {
0314             aZer[i] += THE_2PI;
0315           }
0316           aZer[i] += std::trunc(aMod) * THE_2PI;
0317           double aX = aZer[i] - aMyBorneInf;
0318           if (aX >= -1.0e-10 && aX <= aDelta + 1.0e-10)
0319           {
0320             aZer[i] = std::max(theInfBound, std::min(theSupBound, aZer[i]));
0321             aResult.Roots[aResult.NbRoots++] = aZer[i];
0322           }
0323         }
0324         return aResult;
0325       }
0326     }
0327 
0328     // General case: degree 4 polynomial
0329     // t = tan(x/2), then:
0330     // ko[0]*t^4 + ko[1]*t^3 + ko[2]*t^2 + ko[3]*t + ko[4] = 0
0331     double ko0 = theA - theC + theE;
0332     double ko1 = 2.0 * theD - 4.0 * theB;
0333     double ko2 = 2.0 * theE - 2.0 * theA;
0334     double ko3 = 4.0 * theB + 2.0 * theD;
0335     double ko4 = theA + theC + theE;
0336 
0337     MathPoly::PolyResult aPoly = MathPoly::Quartic(ko0, ko1, ko2, ko3, ko4);
0338     if (!aPoly.IsDone())
0339     {
0340       if (aPoly.Status == MathUtils::Status::InfiniteSolutions)
0341       {
0342         aResult.InfiniteRoots = true;
0343       }
0344       else
0345       {
0346         aResult.Status = aPoly.Status;
0347       }
0348       return aResult;
0349     }
0350 
0351     // NbRoots is bounded by the fixed storage capacity; clamp defensively.
0352     aNZer = std::min<size_t>(aPoly.NbRoots, aZer.size());
0353     for (size_t i = 0; i < aNZer; ++i)
0354     {
0355       aZer[i] = aPoly.Roots[i];
0356     }
0357 
0358     // Sort roots
0359     std::sort(aZer.begin(), aZer.begin() + aNZer);
0360   }
0361 
0362   // Convert t values to angles and filter by bounds
0363   for (size_t i = 0; i < aNZer; ++i)
0364   {
0365     double aTeta = 2.0 * std::atan(aZer[i]);
0366     if (aZer[i] <= -theEps)
0367     {
0368       aTeta = THE_2PI - std::abs(aTeta);
0369     }
0370     aTeta += std::trunc(aMod) * THE_2PI;
0371     if (aTeta - aMyBorneInf < 0.0)
0372     {
0373       aTeta += THE_2PI;
0374     }
0375 
0376     double aX = aTeta - aMyBorneInf;
0377     if (aX >= -aDelta_Eps && aX <= aDelta + aDelta_Eps)
0378     {
0379       // Newton refinement with Halley's method fallback for double roots
0380       auto aRefineRoot = [&](double theX) -> double {
0381         constexpr int    THE_MAX_ITER = 20;
0382         constexpr double THE_TOL      = 1.0e-14;
0383 
0384         for (int anIter = 0; anIter < THE_MAX_ITER; ++anIter)
0385         {
0386           double aCos  = std::cos(theX);
0387           double aSin  = std::sin(theX);
0388           double aCos2 = aCos * aCos;
0389           double aSin2 = aSin * aSin;
0390           double aCS   = aCos * aSin;
0391 
0392           double aF  = theA * aCos2 + 2.0 * theB * aCS + theC * aCos + theD * aSin + theE;
0393           double aDF = -2.0 * theA * aCS + 2.0 * theB * (aCos2 - aSin2) - theC * aSin + theD * aCos;
0394 
0395           // Check if already converged
0396           if (std::abs(aF) < 1.0e-15)
0397           {
0398             break;
0399           }
0400 
0401           double aDelta;
0402           if (std::abs(aDF) < 1.0e-10 * (std::abs(aF) + 1.0))
0403           {
0404             // Near double root: use Halley's method for better convergence
0405             // F'' = -2*a*(cos^2-sin^2) - 4*b*cos*sin - c*cos - d*sin
0406             double aD2F =
0407               -2.0 * theA * (aCos2 - aSin2) - 4.0 * theB * aCS - theC * aCos - theD * aSin;
0408             double aDenom = 2.0 * aDF * aDF - aF * aD2F;
0409             if (std::abs(aDenom) < 1.0e-30)
0410             {
0411               // Can't improve further
0412               break;
0413             }
0414             aDelta = 2.0 * aF * aDF / aDenom;
0415           }
0416           else
0417           {
0418             // Standard Newton step
0419             aDelta = aF / aDF;
0420           }
0421 
0422           // Limit step size to avoid overshooting
0423           constexpr double THE_MAX_STEP = 0.5;
0424           if (std::abs(aDelta) > THE_MAX_STEP)
0425           {
0426             aDelta = (aDelta > 0) ? THE_MAX_STEP : -THE_MAX_STEP;
0427           }
0428 
0429           theX -= aDelta;
0430 
0431           if (std::abs(aDelta) < THE_TOL)
0432           {
0433             break;
0434           }
0435         }
0436         return theX;
0437       };
0438 
0439       double aTetaRefined = aRefineRoot(aTeta);
0440 
0441       // Check if Newton didn't diverge too far
0442       double aDeltaNewton = std::abs(aTetaRefined - aTeta);
0443       double aSupmInfs100 = (theSupBound - theInfBound) * 0.01;
0444       if (aDeltaNewton <= aSupmInfs100)
0445       {
0446         aTeta = aTetaRefined;
0447       }
0448 
0449       // Insert in sorted order, avoiding duplicates
0450       bool aFound = false;
0451       for (int k = 0; k < aResult.NbRoots; ++k)
0452       {
0453         if (std::abs(aTeta - aResult.Roots[k]) < theEps)
0454         {
0455           aFound = true;
0456           break;
0457         }
0458       }
0459 
0460       if (!aFound && aResult.NbRoots < 4)
0461       {
0462         // Insert sorted
0463         int aPos = aResult.NbRoots;
0464         for (int k = 0; k < aResult.NbRoots; ++k)
0465         {
0466           if (aTeta < aResult.Roots[k])
0467           {
0468             aPos = k;
0469             break;
0470           }
0471         }
0472         for (int k = aResult.NbRoots; k > aPos; --k)
0473         {
0474           aResult.Roots[k] = aResult.Roots[k - 1];
0475         }
0476         aResult.Roots[aPos] = aTeta;
0477         aResult.NbRoots++;
0478       }
0479     }
0480   }
0481 
0482   // Special case: check if PI is a root (when A - C + E = 0)
0483   if (aResult.NbRoots < 4 && std::abs(theA - theC + theE) <= theEps)
0484   {
0485     double aTeta = THE_PI + std::trunc(aMod) * THE_2PI;
0486     double aX    = aTeta - aMyBorneInf;
0487     if (aX >= -aDelta_Eps && aX <= aDelta + aDelta_Eps)
0488     {
0489       bool aFound = false;
0490       for (int k = 0; k < aResult.NbRoots; ++k)
0491       {
0492         if (std::abs(aTeta - aResult.Roots[k]) <= theEps)
0493         {
0494           aFound = true;
0495           break;
0496         }
0497       }
0498       if (!aFound)
0499       {
0500         int aPos = aResult.NbRoots;
0501         for (int k = 0; k < aResult.NbRoots; ++k)
0502         {
0503           if (aTeta < aResult.Roots[k])
0504           {
0505             aPos = k;
0506             break;
0507           }
0508         }
0509         for (int k = aResult.NbRoots; k > aPos; --k)
0510         {
0511           aResult.Roots[k] = aResult.Roots[k - 1];
0512         }
0513         aResult.Roots[aPos] = aTeta;
0514         aResult.NbRoots++;
0515       }
0516     }
0517   }
0518 
0519   return aResult;
0520 }
0521 
0522 //! Solve linear trigonometric equation: d*sin(x) + e = 0.
0523 //!
0524 //! @param theD coefficient of sin(x)
0525 //! @param theE constant term
0526 //! @param theInfBound lower bound for roots
0527 //! @param theSupBound upper bound for roots
0528 //! @return TrigResult containing roots
0529 inline TrigResult TrigonometricLinear(double theD,
0530                                       double theE,
0531                                       double theInfBound = 0.0,
0532                                       double theSupBound = THE_2PI)
0533 {
0534   return Trigonometric(0.0, 0.0, 0.0, theD, theE, theInfBound, theSupBound);
0535 }
0536 
0537 //! Solve trigonometric equation: c*cos(x) + d*sin(x) + e = 0.
0538 //!
0539 //! @param theC coefficient of cos(x)
0540 //! @param theD coefficient of sin(x)
0541 //! @param theE constant term
0542 //! @param theInfBound lower bound for roots
0543 //! @param theSupBound upper bound for roots
0544 //! @return TrigResult containing roots
0545 inline TrigResult TrigonometricCDE(double theC,
0546                                    double theD,
0547                                    double theE,
0548                                    double theInfBound = 0.0,
0549                                    double theSupBound = THE_2PI)
0550 {
0551   return Trigonometric(0.0, 0.0, theC, theD, theE, theInfBound, theSupBound);
0552 }
0553 
0554 } // namespace MathRoot
0555 
0556 #endif // _MathRoot_Trig_HeaderFile