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_MultipleUtils_HeaderFile
0015 #define _MathRoot_MultipleUtils_HeaderFile
0016 
0017 #include <MathUtils_Types.hxx>
0018 #include <MathUtils_Config.hxx>
0019 #include <MathRoot_Brent.hxx>
0020 #include <math_Vector.hxx>
0021 
0022 #include <NCollection_DynamicArray.hxx>
0023 
0024 #include <algorithm>
0025 #include <cmath>
0026 
0027 //! @file MathRoot_MultipleUtils.hxx
0028 //! @brief Internal utilities for FindAllRoots / FindAllRootsWithDerivative.
0029 //!
0030 //! Contains result/config types, helper functions, functor adapters and the
0031 //! shared core implementation used by MathRoot_Multiple.hxx.
0032 
0033 namespace MathRoot
0034 {
0035 using namespace MathUtils;
0036 
0037 // ============================================================================
0038 //  Result and configuration types
0039 // ============================================================================
0040 
0041 //! Result for multiple root finding.
0042 //! Contains all found roots sorted in ascending order.
0043 struct MultipleResult
0044 {
0045   MathUtils::Status                Status = MathUtils::Status::NotConverged; //!< Computation status
0046   size_t                           NbIterations = 0; //!< Total iterations across all roots
0047   NCollection_DynamicArray<double> Roots;            //!< Found roots (sorted)
0048   NCollection_DynamicArray<double> Values;           //!< Function values at roots
0049   bool IsAllNull = false; //!< True if function is essentially zero in range
0050 
0051   //! Returns true if computation succeeded.
0052   bool IsDone() const { return Status == MathUtils::Status::OK; }
0053 
0054   //! Conversion to bool for convenient checking.
0055   explicit operator bool() const { return IsDone(); }
0056 
0057   //! Returns the number of roots found.
0058   int NbRoots() const { return Roots.Length(); }
0059 
0060   //! Access root by index (0-based).
0061   double operator[](int theIndex) const { return Roots.Value(theIndex); }
0062 };
0063 
0064 //! Configuration for multiple root finding.
0065 struct MultipleConfig
0066 {
0067   int    NbSamples     = 100;   //!< Number of sample points for initial search
0068   double XTolerance    = 1e-10; //!< Tolerance on X for convergence
0069   double FTolerance    = 1e-10; //!< Tolerance on F(X) for convergence
0070   double NullTolerance = 1e-12; //!< Tolerance to consider function as null
0071   int    MaxIterations = 100;   //!< Max iterations per root refinement
0072   double Offset        = 0.0;   //!< Find roots of f(x) - Offset = 0
0073 };
0074 
0075 // ============================================================================
0076 //  Helper functions
0077 // ============================================================================
0078 
0079 //! In-place insertion sort of roots and corresponding values by ascending root value.
0080 inline void SortRoots(MultipleResult& theResult)
0081 {
0082   for (int i = 1; i < theResult.Roots.Length(); ++i)
0083   {
0084     const double aKeyRoot = theResult.Roots[i];
0085     const double aKeyVal  = theResult.Values[i];
0086     int          j        = i - 1;
0087     while (j >= 0 && theResult.Roots[j] > aKeyRoot)
0088     {
0089       theResult.Roots[j + 1]  = theResult.Roots[j];
0090       theResult.Values[j + 1] = theResult.Values[j];
0091       --j;
0092     }
0093     theResult.Roots[j + 1]  = aKeyRoot;
0094     theResult.Values[j + 1] = aKeyVal;
0095   }
0096 }
0097 
0098 //! Helper to add a root if it is not a duplicate of an already found root.
0099 inline void AddRoot(MultipleResult& theResult, double theEpsX, double theRoot, double theValue)
0100 {
0101   for (int k = 0; k < theResult.Roots.Length(); ++k)
0102   {
0103     if (std::abs(theRoot - theResult.Roots.Value(k)) < theEpsX)
0104     {
0105       return;
0106     }
0107   }
0108   theResult.Roots.Append(theRoot);
0109   theResult.Values.Append(theValue);
0110 }
0111 
0112 //! Compute the minimal X tolerance compatible with the established multi-root behavior.
0113 inline double EffectiveXTolerance(double theLower, double theUpper, double theXTolerance)
0114 {
0115   const double aMinEpsX = 1.0e-10 * (std::abs(theLower) + std::abs(theUpper));
0116   return std::max(theXTolerance, aMinEpsX);
0117 }
0118 
0119 // ============================================================================
0120 //  Functor adapters for Value-only interface
0121 // ============================================================================
0122 
0123 //! Samples a Value-only function and stores f(x)-offset into a math_Vector.
0124 //! @tparam Function type with Value(double theX, double& theF) method
0125 template <typename Function>
0126 struct MultipleSampleValueFn
0127 {
0128   Function&    myFunc;
0129   math_Vector& mySamples;
0130   const double myOffset;
0131 
0132   bool operator()(int theIndex, double theX) const
0133   {
0134     double aF = 0.0;
0135     if (!myFunc.Value(theX, aF))
0136       return false;
0137     mySamples(theIndex) = aF - myOffset;
0138     return true;
0139   }
0140 };
0141 
0142 //! Returns the sampled value at a given index from a math_Vector.
0143 struct MultipleGetValueFn
0144 {
0145   const math_Vector& mySamples;
0146 
0147   double operator()(int theIndex) const { return mySamples(theIndex); }
0148 };
0149 
0150 //! Brent wrapper that adapts a Value-only function for offset root finding.
0151 //! @tparam Function type with Value(double theX, double& theF) method
0152 template <typename Function>
0153 struct MultipleBrentValueWrapper
0154 {
0155   Function& myFunc;
0156   double    myOffset;
0157 
0158   bool Value(double theX, double& theY) const
0159   {
0160     if (!myFunc.Value(theX, theY))
0161       return false;
0162     theY -= myOffset;
0163     return true;
0164   }
0165 };
0166 
0167 //! Evaluates original (non-offset) function value at a root point via Value interface.
0168 //! @tparam Function type with Value(double theX, double& theF) method
0169 template <typename Function>
0170 struct MultipleGetRootValueFn
0171 {
0172   Function& myFunc;
0173 
0174   double operator()(double theX) const
0175   {
0176     double aF = 0.0;
0177     myFunc.Value(theX, aF);
0178     return aF;
0179   }
0180 };
0181 
0182 // ============================================================================
0183 //  Functor adapters for Values (with derivative) interface
0184 // ============================================================================
0185 
0186 //! Wrapper exposing a function derivative through the Value() contract required by Brent.
0187 //! @tparam Function type with Values(double theX, double& theF, double& theDF) method
0188 template <typename Function>
0189 struct MultipleDerivativeValueWrapper
0190 {
0191   Function& myFunc;
0192 
0193   bool Value(double theX, double& theY) const
0194   {
0195     double aF = 0.0;
0196     return myFunc.Values(theX, aF, theY);
0197   }
0198 };
0199 
0200 //! Evaluate the original function value.
0201 //! @tparam Function type with Value(double theX, double& theF) method
0202 template <typename Function>
0203 bool EvaluateValue(Function& theFunc, double theX, double& theValue)
0204 {
0205   return theFunc.Value(theX, theValue);
0206 }
0207 
0208 //! Evaluate the function value shifted by the requested offset.
0209 //! @tparam Function type with Value(double theX, double& theF) method
0210 template <typename Function>
0211 bool EvaluateShiftedValue(Function& theFunc, double theX, double theOffset, double& theValue)
0212 {
0213   if (!theFunc.Value(theX, theValue))
0214   {
0215     return false;
0216   }
0217 
0218   theValue -= theOffset;
0219   return true;
0220 }
0221 
0222 //! Evaluate the function value and derivative, then shift the value by the requested offset.
0223 //! @tparam Function type with Values(double theX, double& theF, double& theDF) method
0224 template <typename Function>
0225 bool EvaluateShiftedValues(Function& theFunc,
0226                            double    theX,
0227                            double    theOffset,
0228                            double&   theValue,
0229                            double&   theDerivative)
0230 {
0231   if (!theFunc.Values(theX, theValue, theDerivative))
0232   {
0233     return false;
0234   }
0235 
0236   theValue -= theOffset;
0237   return true;
0238 }
0239 
0240 //! Refine a bracketed sign change using the same Brent/Newton sequence as math_FunctionRoots.
0241 //! @tparam Function type with Value() and Values() methods
0242 template <typename Function>
0243 bool RefineBracketedRoot(Function&       theFunc,
0244                          double          theOffset,
0245                          double          theX1,
0246                          double          theY1,
0247                          double          theX2,
0248                          double          theY2,
0249                          double          theTolerance,
0250                          double          theEpsX,
0251                          MultipleResult& theResult)
0252 {
0253   constexpr int    THE_MAX_ITERATIONS = 100;
0254   constexpr double THE_EPS2           = 2.0e-14;
0255   constexpr double THE_DERIV_EPS      = 1.0e-10;
0256 
0257   int    anIter = 0;
0258   double aTol2  = 0.5 * theTolerance;
0259   double aA     = theX1;
0260   double aB     = theX2;
0261   double aC     = theX2;
0262   double aD     = 0.0;
0263   double anE    = 0.0;
0264   double aFa    = theY1;
0265   double aFb    = theY2;
0266   double aFc    = theY2;
0267 
0268   for (anIter = 1; anIter <= THE_MAX_ITERATIONS; ++anIter)
0269   {
0270     if ((aFb > 0.0 && aFc > 0.0) || (aFb < 0.0 && aFc < 0.0))
0271     {
0272       aC  = aA;
0273       aFc = aFa;
0274       anE = aD = aB - aA;
0275     }
0276 
0277     if (std::abs(aFc) < std::abs(aFb))
0278     {
0279       const double aPrevA  = aA;
0280       const double aPrevFa = aFa;
0281       aA                   = aB;
0282       aB                   = aC;
0283       aC                   = aPrevA;
0284       aFa                  = aFb;
0285       aFb                  = aFc;
0286       aFc                  = aPrevFa;
0287     }
0288 
0289     const double aTol1 = THE_EPS2 * std::abs(aB) + aTol2;
0290     const double aXm   = 0.5 * (aC - aB);
0291     if (std::abs(aXm) < aTol1 || aFb == 0.0)
0292     {
0293       double aNewtonX = aB;
0294       for (int aNewtonIter = 0; aNewtonIter < 5; ++aNewtonIter)
0295       {
0296         double aY    = 0.0;
0297         double aDfdx = 0.0;
0298         if (!EvaluateShiftedValues(theFunc, aNewtonX, theOffset, aY, aDfdx))
0299         {
0300           return false;
0301         }
0302 
0303         if (std::abs(aDfdx) <= THE_DERIV_EPS)
0304         {
0305           break;
0306         }
0307 
0308         aNewtonX -= aY / aDfdx;
0309         if (aNewtonX < theX1 || aNewtonX > theX2)
0310         {
0311           break;
0312         }
0313 
0314         if (!EvaluateShiftedValue(theFunc, aNewtonX, theOffset, aY))
0315         {
0316           return false;
0317         }
0318 
0319         if (std::abs(aY) < std::abs(aFb))
0320         {
0321           aB  = aNewtonX;
0322           aFb = aY;
0323         }
0324       }
0325 
0326       double aRootValue = 0.0;
0327       if (!EvaluateValue(theFunc, aB, aRootValue))
0328       {
0329         return false;
0330       }
0331 
0332       theResult.NbIterations += static_cast<size_t>(anIter);
0333       AddRoot(theResult, theEpsX, aB, aRootValue);
0334       return true;
0335     }
0336 
0337     if (std::abs(anE) >= aTol1 && std::abs(aFa) > std::abs(aFb))
0338     {
0339       double       aP = 0.0;
0340       double       aQ = 0.0;
0341       const double aS = aFb / aFa;
0342       if (aA == aC)
0343       {
0344         aP = 2.0 * aXm * aS;
0345         aQ = 1.0 - aS;
0346       }
0347       else
0348       {
0349         aQ              = aFa / aFc;
0350         const double aR = aFb / aFc;
0351         aP              = aS * (2.0 * aXm * aQ * (aQ - aR) - (aB - aA) * (aR - 1.0));
0352         aQ              = (aQ - 1.0) * (aR - 1.0) * (aS - 1.0);
0353       }
0354 
0355       if (aP > 0.0)
0356       {
0357         aQ = -aQ;
0358       }
0359 
0360       aP                 = std::abs(aP);
0361       const double aMin1 = 3.0 * aXm * aQ - std::abs(aTol1 * aQ);
0362       const double aMin2 = std::abs(anE * aQ);
0363       if (2.0 * aP < std::min(aMin1, aMin2))
0364       {
0365         anE = aD;
0366         aD  = aP / aQ;
0367       }
0368       else
0369       {
0370         aD  = aXm;
0371         anE = aD;
0372       }
0373     }
0374     else
0375     {
0376       aD  = aXm;
0377       anE = aD;
0378     }
0379 
0380     aA  = aB;
0381     aFa = aFb;
0382     if (std::abs(aD) > aTol1)
0383     {
0384       aB += aD;
0385     }
0386     else
0387     {
0388       aB += (aXm >= 0.0) ? std::abs(aTol1) : -std::abs(aTol1);
0389     }
0390 
0391     if (!EvaluateShiftedValue(theFunc, aB, theOffset, aFb))
0392     {
0393       return false;
0394     }
0395   }
0396 
0397   theResult.NbIterations += THE_MAX_ITERATIONS;
0398   return true;
0399 }
0400 
0401 //! Derivative-aware implementation mirroring the proven math_FunctionRoots heuristics
0402 //! while operating on the modern callable-based MathRoot API.
0403 //! @tparam Function type with Value() and Values() methods
0404 template <typename Function>
0405 MultipleResult FindAllRootsWithDerivativeImpl(Function&             theFunc,
0406                                               double                theLower,
0407                                               double                theUpper,
0408                                               const MultipleConfig& theConfig)
0409 {
0410   MultipleResult aResult;
0411   aResult.Status = MathUtils::Status::OK;
0412 
0413   const double aLower         = std::min(theLower, theUpper);
0414   const double aUpper         = std::max(theLower, theUpper);
0415   const int    aNbSamples     = std::max(2 * theConfig.NbSamples, 20);
0416   const double aDx            = (aUpper - aLower) / aNbSamples;
0417   const double aEpsX          = EffectiveXTolerance(aLower, aUpper, theConfig.XTolerance);
0418   const double aRawXTolerance = theConfig.XTolerance;
0419   const double aMajorDx       = 5.0 * aDx;
0420 
0421   math_Vector aValues(0, aNbSamples);
0422   double      aX = aLower;
0423   for (int i = 0; i <= aNbSamples; ++i, aX += aDx)
0424   {
0425     if (aX > aUpper)
0426     {
0427       aX = aUpper;
0428     }
0429 
0430     if (!EvaluateShiftedValue(theFunc, aX, theConfig.Offset, aValues(i)))
0431     {
0432       aResult.Status = MathUtils::Status::NumericalError;
0433       return aResult;
0434     }
0435   }
0436 
0437   aResult.IsAllNull = true;
0438   for (int i = 0; i <= aNbSamples; ++i)
0439   {
0440     if (aValues(i) > theConfig.NullTolerance || aValues(i) < -theConfig.NullTolerance)
0441     {
0442       aResult.IsAllNull = false;
0443       break;
0444     }
0445   }
0446   if (aResult.IsAllNull)
0447   {
0448     return aResult;
0449   }
0450 
0451   const double aTolerance = aEpsX;
0452   double       aX1        = aLower;
0453   for (int i = 0, anIp1 = 1; i < aNbSamples; ++i, ++anIp1, aX1 += aDx)
0454   {
0455     double aX2 = aX1 + aDx;
0456     if (aX2 > aUpper)
0457     {
0458       aX2 = aUpper;
0459     }
0460 
0461     if ((aValues(i) < 0.0 && aValues(anIp1) > 0.0) || (aValues(i) > 0.0 && aValues(anIp1) < 0.0))
0462     {
0463       if (!RefineBracketedRoot(theFunc,
0464                                theConfig.Offset,
0465                                aX1,
0466                                aValues(i),
0467                                aX2,
0468                                aValues(anIp1),
0469                                aTolerance,
0470                                aEpsX,
0471                                aResult))
0472       {
0473         aResult.Status = MathUtils::Status::NumericalError;
0474         return aResult;
0475       }
0476     }
0477   }
0478 
0479   for (int i = 0; i <= aNbSamples; ++i)
0480   {
0481     if (aValues(i) != 0.0)
0482     {
0483       continue;
0484     }
0485 
0486     const double aZeroX  = std::min(aLower + i * aDx, aUpper);
0487     double       aLeftX  = aZeroX - 0.5 * aDx;
0488     double       aRightX = aZeroX + 0.5 * aDx;
0489     if (aLeftX < aLower)
0490     {
0491       aLeftX = aLower;
0492     }
0493     if (aLeftX > aUpper)
0494     {
0495       aLeftX = aUpper;
0496     }
0497     if (aRightX < aLower)
0498     {
0499       aRightX = aLower;
0500     }
0501     if (aRightX > aUpper)
0502     {
0503       aRightX = aUpper;
0504     }
0505 
0506     double aLeftY  = 0.0;
0507     double aRightY = 0.0;
0508     if (!EvaluateShiftedValue(theFunc, aLeftX, theConfig.Offset, aLeftY)
0509         || !EvaluateShiftedValue(theFunc, aRightX, theConfig.Offset, aRightY))
0510     {
0511       aResult.Status = MathUtils::Status::NumericalError;
0512       return aResult;
0513     }
0514 
0515     if (aLeftY * aRightY < 0.0)
0516     {
0517       if (!RefineBracketedRoot(theFunc,
0518                                theConfig.Offset,
0519                                aLeftX,
0520                                aLeftY,
0521                                aRightX,
0522                                aRightY,
0523                                aTolerance,
0524                                aEpsX,
0525                                aResult))
0526       {
0527         aResult.Status = MathUtils::Status::NumericalError;
0528         return aResult;
0529       }
0530     }
0531     else if (aLeftY != 0.0 || aRightY != 0.0)
0532     {
0533       double aRootValue = 0.0;
0534       if (!EvaluateValue(theFunc, aZeroX, aRootValue))
0535       {
0536         aResult.Status = MathUtils::Status::NumericalError;
0537         return aResult;
0538       }
0539       AddRoot(aResult, aEpsX, aZeroX, aRootValue);
0540     }
0541   }
0542 
0543   if (aValues(0) <= theConfig.FTolerance && aValues(0) >= -theConfig.FTolerance)
0544   {
0545     double aRootValue = 0.0;
0546     if (!EvaluateValue(theFunc, aLower, aRootValue))
0547     {
0548       aResult.Status = MathUtils::Status::NumericalError;
0549       return aResult;
0550     }
0551     AddRoot(aResult, aEpsX, aLower, aRootValue);
0552   }
0553 
0554   if (aValues(aNbSamples) <= theConfig.FTolerance && aValues(aNbSamples) >= -theConfig.FTolerance)
0555   {
0556     double aRootValue = 0.0;
0557     if (!EvaluateValue(theFunc, aUpper, aRootValue))
0558     {
0559       aResult.Status = MathUtils::Status::NumericalError;
0560       return aResult;
0561     }
0562     AddRoot(aResult, aEpsX, aUpper, aRootValue);
0563   }
0564 
0565   int    anIm1 = 0;
0566   int    anIp1 = 2;
0567   double aMidX = aLower + aDx;
0568   for (int i = 1; i < aNbSamples; ++i, ++anIm1, ++anIp1, aMidX += aDx)
0569   {
0570     if (aMidX > aUpper)
0571     {
0572       aMidX = aUpper;
0573     }
0574 
0575     bool isRediscretize = false;
0576     if (aValues(i) > 0.0)
0577     {
0578       if (aValues(anIm1) > aValues(i) && aValues(anIp1) > aValues(i))
0579       {
0580         double aProbeX  = std::max(aLower, aMidX - aDx);
0581         double aProbeY  = 0.0;
0582         double aProbeDy = 0.0;
0583         if (!EvaluateShiftedValues(theFunc, aProbeX, theConfig.Offset, aProbeY, aProbeDy))
0584         {
0585           aResult.Status = MathUtils::Status::NumericalError;
0586           return aResult;
0587         }
0588 
0589         if (std::abs(aProbeDy) > 1.0e-10)
0590         {
0591           const double aStep = aProbeY / aProbeDy;
0592           if (aStep < aMajorDx && aStep > -aMajorDx)
0593           {
0594             isRediscretize = true;
0595           }
0596         }
0597 
0598         if (!isRediscretize)
0599         {
0600           aProbeX = std::min(aUpper, aMidX + aDx);
0601           if (!EvaluateShiftedValues(theFunc, aProbeX, theConfig.Offset, aProbeY, aProbeDy))
0602           {
0603             aResult.Status = MathUtils::Status::NumericalError;
0604             return aResult;
0605           }
0606 
0607           if (std::abs(aProbeDy) > 1.0e-10)
0608           {
0609             const double aStep = aProbeY / aProbeDy;
0610             if (aStep < aMajorDx && aStep > -aMajorDx)
0611             {
0612               isRediscretize = true;
0613             }
0614           }
0615         }
0616       }
0617     }
0618     else if (aValues(i) < 0.0)
0619     {
0620       if (aValues(anIm1) < aValues(i) && aValues(anIp1) < aValues(i))
0621       {
0622         double aProbeX  = std::max(aLower, aMidX - aDx);
0623         double aProbeY  = 0.0;
0624         double aProbeDy = 0.0;
0625         if (!EvaluateShiftedValues(theFunc, aProbeX, theConfig.Offset, aProbeY, aProbeDy))
0626         {
0627           aResult.Status = MathUtils::Status::NumericalError;
0628           return aResult;
0629         }
0630 
0631         if (std::abs(aProbeDy) > 1.0e-10)
0632         {
0633           const double aStep = aProbeY / aProbeDy;
0634           if (aStep < aMajorDx && aStep > -aMajorDx)
0635           {
0636             isRediscretize = true;
0637           }
0638         }
0639 
0640         if (!isRediscretize)
0641         {
0642           aProbeX = std::min(aUpper, aMidX + aDx);
0643           if (!EvaluateShiftedValues(theFunc, aProbeX, theConfig.Offset, aProbeY, aProbeDy))
0644           {
0645             aResult.Status = MathUtils::Status::NumericalError;
0646             return aResult;
0647           }
0648 
0649           if (std::abs(aProbeDy) > 1.0e-10)
0650           {
0651             const double aStep = aProbeY / aProbeDy;
0652             if (aStep < aMajorDx && aStep > -aMajorDx)
0653             {
0654               isRediscretize = true;
0655             }
0656           }
0657         }
0658       }
0659     }
0660 
0661     if (!isRediscretize)
0662     {
0663       continue;
0664     }
0665 
0666     double aX0      = std::max(aLower, aMidX - aDx);
0667     double aX3      = std::min(aUpper, aMidX + aDx);
0668     double aRoot1   = 0.0;
0669     double aRoot2   = 0.0;
0670     double aVal1    = 0.0;
0671     double aVal2    = 0.0;
0672     double aDer1    = 0.0;
0673     double aDer2    = 0.0;
0674     bool   hasRoot1 = false;
0675     bool   hasRoot2 = false;
0676 
0677     MultipleDerivativeValueWrapper<Function> aDerivativeWrapper{theFunc};
0678     MathUtils::Config                        aDerivativeConfig;
0679     aDerivativeConfig.XTolerance    = aRawXTolerance;
0680     aDerivativeConfig.FTolerance    = 0.0;
0681     aDerivativeConfig.MaxIterations = theConfig.MaxIterations;
0682 
0683     MathUtils::ScalarResult aDerivativeRoot =
0684       Brent(aDerivativeWrapper, aX0, aX3, aDerivativeConfig);
0685     aResult.NbIterations += aDerivativeRoot.NbIterations;
0686     if (aDerivativeRoot.IsDone() && aDerivativeRoot.Root.has_value())
0687     {
0688       aRoot1                = *aDerivativeRoot.Root;
0689       double aOriginalValue = 0.0;
0690       if (!EvaluateValue(theFunc, aRoot1, aOriginalValue))
0691       {
0692         aResult.Status = MathUtils::Status::NumericalError;
0693         return aResult;
0694       }
0695       aVal1 = std::abs(aOriginalValue - theConfig.Offset);
0696       if (aVal1 < theConfig.FTolerance)
0697       {
0698         hasRoot1 = true;
0699         if (!EvaluateShiftedValues(theFunc, aRoot1, theConfig.Offset, aOriginalValue, aDer1))
0700         {
0701           aResult.Status = MathUtils::Status::NumericalError;
0702           return aResult;
0703         }
0704       }
0705     }
0706 
0707     double           aXProbe1             = 0.0;
0708     double           aXProbe2             = 0.0;
0709     constexpr double THE_GOLDEN_INV_RATIO = 1.0 / MathUtils::THE_GOLDEN_RATIO;
0710     constexpr double THE_GOLDEN_INV_COMP  = 1.0 - THE_GOLDEN_INV_RATIO;
0711     const double     aTolCR               = aEpsX * 10.0;
0712     const double     aLocalTolX           = 0.001 * aEpsX;
0713     double           aF0                  = aValues(anIm1);
0714     double           aF3                  = aValues(anIp1);
0715     const bool       isSearchMinimum      = (aF0 > 0.0);
0716 
0717     if (std::abs(aX3 - aMidX) > std::abs(aX0 - aMidX))
0718     {
0719       aXProbe1 = aMidX;
0720       aXProbe2 = aMidX + THE_GOLDEN_INV_COMP * (aX3 - aMidX);
0721     }
0722     else
0723     {
0724       aXProbe2 = aMidX;
0725       aXProbe1 = aMidX - THE_GOLDEN_INV_COMP * (aMidX - aX0);
0726     }
0727 
0728     double aF1 = 0.0;
0729     double aF2 = 0.0;
0730     if (!EvaluateShiftedValue(theFunc, aXProbe1, theConfig.Offset, aF1)
0731         || !EvaluateShiftedValue(theFunc, aXProbe2, theConfig.Offset, aF2))
0732     {
0733       aResult.Status = MathUtils::Status::NumericalError;
0734       return aResult;
0735     }
0736 
0737     while (std::abs(aX3 - aX0) > aTolCR * (std::abs(aXProbe1) + std::abs(aXProbe2))
0738            && std::abs(aXProbe1 - aXProbe2) > aLocalTolX)
0739     {
0740       if (isSearchMinimum)
0741       {
0742         if (aF2 < aF1)
0743         {
0744           aX0      = aXProbe1;
0745           aXProbe1 = aXProbe2;
0746           aXProbe2 = THE_GOLDEN_INV_RATIO * aXProbe1 + THE_GOLDEN_INV_COMP * aX3;
0747           aF0      = aF1;
0748           aF1      = aF2;
0749           if (!EvaluateShiftedValue(theFunc, aXProbe2, theConfig.Offset, aF2))
0750           {
0751             aResult.Status = MathUtils::Status::NumericalError;
0752             return aResult;
0753           }
0754         }
0755         else
0756         {
0757           aX3      = aXProbe2;
0758           aXProbe2 = aXProbe1;
0759           aXProbe1 = THE_GOLDEN_INV_RATIO * aXProbe2 + THE_GOLDEN_INV_COMP * aX0;
0760           aF3      = aF2;
0761           aF2      = aF1;
0762           if (!EvaluateShiftedValue(theFunc, aXProbe1, theConfig.Offset, aF1))
0763           {
0764             aResult.Status = MathUtils::Status::NumericalError;
0765             return aResult;
0766           }
0767         }
0768       }
0769       else
0770       {
0771         if (aF2 > aF1)
0772         {
0773           aX0      = aXProbe1;
0774           aXProbe1 = aXProbe2;
0775           aXProbe2 = THE_GOLDEN_INV_RATIO * aXProbe1 + THE_GOLDEN_INV_COMP * aX3;
0776           aF0      = aF1;
0777           aF1      = aF2;
0778           if (!EvaluateShiftedValue(theFunc, aXProbe2, theConfig.Offset, aF2))
0779           {
0780             aResult.Status = MathUtils::Status::NumericalError;
0781             return aResult;
0782           }
0783         }
0784         else
0785         {
0786           aX3      = aXProbe2;
0787           aXProbe2 = aXProbe1;
0788           aXProbe1 = THE_GOLDEN_INV_RATIO * aXProbe2 + THE_GOLDEN_INV_COMP * aX0;
0789           aF3      = aF2;
0790           aF2      = aF1;
0791           if (!EvaluateShiftedValue(theFunc, aXProbe1, theConfig.Offset, aF1))
0792           {
0793             aResult.Status = MathUtils::Status::NumericalError;
0794             return aResult;
0795           }
0796         }
0797       }
0798 
0799       if (aF1 * aF0 < 0.0)
0800       {
0801         if (!RefineBracketedRoot(theFunc,
0802                                  theConfig.Offset,
0803                                  aX0,
0804                                  aF0,
0805                                  aXProbe1,
0806                                  aF1,
0807                                  aTolerance,
0808                                  aEpsX,
0809                                  aResult))
0810         {
0811           aResult.Status = MathUtils::Status::NumericalError;
0812           return aResult;
0813         }
0814       }
0815 
0816       if (aF2 * aF3 < 0.0)
0817       {
0818         if (!RefineBracketedRoot(theFunc,
0819                                  theConfig.Offset,
0820                                  aXProbe2,
0821                                  aF2,
0822                                  aX3,
0823                                  aF3,
0824                                  aTolerance,
0825                                  aEpsX,
0826                                  aResult))
0827         {
0828           aResult.Status = MathUtils::Status::NumericalError;
0829           return aResult;
0830         }
0831       }
0832     }
0833 
0834     if ((isSearchMinimum && aF1 < aF2) || (!isSearchMinimum && aF1 > aF2))
0835     {
0836       if (std::abs(aF1) < theConfig.FTolerance)
0837       {
0838         hasRoot2 = true;
0839         aRoot2   = aXProbe1;
0840         aVal2    = std::abs(aF1);
0841       }
0842     }
0843     else if (std::abs(aF2) < theConfig.FTolerance)
0844     {
0845       hasRoot2 = true;
0846       aRoot2   = aXProbe2;
0847       aVal2    = std::abs(aF2);
0848     }
0849 
0850     if (hasRoot1 && hasRoot2)
0851     {
0852       if (aVal2 - aVal1 > theConfig.FTolerance)
0853       {
0854         double aRootValue = 0.0;
0855         if (!EvaluateValue(theFunc, aRoot1, aRootValue))
0856         {
0857           aResult.Status = MathUtils::Status::NumericalError;
0858           return aResult;
0859         }
0860         AddRoot(aResult, aEpsX, aRoot1, aRootValue);
0861       }
0862       else if (aVal1 - aVal2 > theConfig.FTolerance)
0863       {
0864         double aRootValue = 0.0;
0865         if (!EvaluateValue(theFunc, aRoot2, aRootValue))
0866         {
0867           aResult.Status = MathUtils::Status::NumericalError;
0868           return aResult;
0869         }
0870         AddRoot(aResult, aEpsX, aRoot2, aRootValue);
0871       }
0872       else
0873       {
0874         double aShiftedValue = 0.0;
0875         if (!EvaluateShiftedValues(theFunc, aRoot2, theConfig.Offset, aShiftedValue, aDer2))
0876         {
0877           aResult.Status = MathUtils::Status::NumericalError;
0878           return aResult;
0879         }
0880 
0881         const double aChosenRoot = (std::abs(aDer1) < std::abs(aDer2)) ? aRoot1 : aRoot2;
0882         double       aRootValue  = 0.0;
0883         if (!EvaluateValue(theFunc, aChosenRoot, aRootValue))
0884         {
0885           aResult.Status = MathUtils::Status::NumericalError;
0886           return aResult;
0887         }
0888         AddRoot(aResult, aEpsX, aChosenRoot, aRootValue);
0889       }
0890     }
0891     else if (hasRoot1)
0892     {
0893       double aRootValue = 0.0;
0894       if (!EvaluateValue(theFunc, aRoot1, aRootValue))
0895       {
0896         aResult.Status = MathUtils::Status::NumericalError;
0897         return aResult;
0898       }
0899       AddRoot(aResult, aEpsX, aRoot1, aRootValue);
0900     }
0901     else if (hasRoot2)
0902     {
0903       double aRootValue = 0.0;
0904       if (!EvaluateValue(theFunc, aRoot2, aRootValue))
0905       {
0906         aResult.Status = MathUtils::Status::NumericalError;
0907         return aResult;
0908       }
0909       AddRoot(aResult, aEpsX, aRoot2, aRootValue);
0910     }
0911   }
0912 
0913   SortRoots(aResult);
0914   return aResult;
0915 }
0916 
0917 // ============================================================================
0918 //  Interval handlers
0919 // ============================================================================
0920 
0921 //! No-op interval handler for functions without derivative.
0922 struct MultipleNoExtraHandler
0923 {
0924   void operator()(int, double, double, double, double, MultipleResult&, double) const {}
0925 };
0926 
0927 // ============================================================================
0928 //  Core implementation
0929 // ============================================================================
0930 
0931 //! Core implementation for finding all roots in an interval.
0932 //! Shared logic for both Value-only and Values (with derivative) interfaces.
0933 //!
0934 //! @tparam SampleFn callable (int theIndex, double theX) -> bool
0935 //! @tparam GetValueFn callable (int theIndex) -> double, returns f(x)-offset at sample
0936 //! @tparam BrentWrapperT type with Value(double, double&) for Brent root finding
0937 //! @tparam GetRootValueFn callable (double theX) -> double, returns original f(x)
0938 //! @tparam IntervalExtraFn callable (int, x0, x1, f0, f1, result, epsX) -> void
0939 template <typename SampleFn,
0940           typename GetValueFn,
0941           typename BrentWrapperT,
0942           typename GetRootValueFn,
0943           typename IntervalExtraFn>
0944 MultipleResult FindAllRootsImpl(double                theLower,
0945                                 double                theUpper,
0946                                 const MultipleConfig& theConfig,
0947                                 SampleFn              theSampleFn,
0948                                 GetValueFn            theGetValue,
0949                                 BrentWrapperT&        theBrentWrapper,
0950                                 GetRootValueFn        theGetRootValue,
0951                                 IntervalExtraFn       theIntervalExtra)
0952 {
0953   MultipleResult aResult;
0954   aResult.Status = MathUtils::Status::OK;
0955 
0956   // Ensure proper ordering
0957   const double aLower = std::min(theLower, theUpper);
0958   const double aUpper = std::max(theLower, theUpper);
0959 
0960   // Minimum samples
0961   const int    aNbSamples = std::max(2 * theConfig.NbSamples, 20);
0962   const double aDx        = (aUpper - aLower) / aNbSamples;
0963 
0964   // Ensure EpsX is not too small relative to interval
0965   const double aEpsX = EffectiveXTolerance(aLower, aUpper, theConfig.XTolerance);
0966 
0967   // Sample function values
0968   math_Vector aXValues(0, aNbSamples);
0969   for (int i = 0; i <= aNbSamples; ++i)
0970   {
0971     double aX = aLower + i * aDx;
0972     if (aX > aUpper)
0973       aX = aUpper;
0974     aXValues(i) = aX;
0975 
0976     if (!theSampleFn(i, aX))
0977     {
0978       aResult.Status = MathUtils::Status::NumericalError;
0979       return aResult;
0980     }
0981   }
0982 
0983   // Check if function is essentially null everywhere
0984   aResult.IsAllNull = true;
0985   for (int i = 0; i <= aNbSamples; ++i)
0986   {
0987     if (std::abs(theGetValue(i)) > theConfig.NullTolerance)
0988     {
0989       aResult.IsAllNull = false;
0990       break;
0991     }
0992   }
0993 
0994   if (aResult.IsAllNull)
0995   {
0996     return aResult;
0997   }
0998 
0999   // Find sign changes
1000   for (int i = 0; i < aNbSamples; ++i)
1001   {
1002     const double aF0 = theGetValue(i);
1003     const double aF1 = theGetValue(i + 1);
1004     const double aX0 = aXValues(i);
1005     const double aX1 = aXValues(i + 1);
1006 
1007     // Exact zero at sample point
1008     if (std::abs(aF0) < theConfig.FTolerance)
1009     {
1010       AddRoot(aResult, aEpsX, aX0, aF0 + theConfig.Offset);
1011       continue;
1012     }
1013 
1014     // Sign change detected
1015     if (aF0 * aF1 < 0.0)
1016     {
1017       MathUtils::Config aBrentConfig;
1018       aBrentConfig.XTolerance    = aEpsX;
1019       aBrentConfig.FTolerance    = theConfig.FTolerance;
1020       aBrentConfig.MaxIterations = theConfig.MaxIterations;
1021 
1022       MathUtils::ScalarResult aBrentResult = Brent(theBrentWrapper, aX0, aX1, aBrentConfig);
1023       aResult.NbIterations += aBrentResult.NbIterations;
1024 
1025       if (aBrentResult.IsDone() && aBrentResult.Root.has_value())
1026       {
1027         AddRoot(aResult, aEpsX, *aBrentResult.Root, theGetRootValue(*aBrentResult.Root));
1028       }
1029     }
1030     else
1031     {
1032       // Additional per-interval processing (e.g., tangential root detection)
1033       theIntervalExtra(i, aX0, aX1, aF0, aF1, aResult, aEpsX);
1034     }
1035   }
1036 
1037   // Check last sample point
1038   if (std::abs(theGetValue(aNbSamples)) < theConfig.FTolerance)
1039   {
1040     AddRoot(aResult, aEpsX, aXValues(aNbSamples), theGetValue(aNbSamples) + theConfig.Offset);
1041   }
1042 
1043   SortRoots(aResult);
1044   return aResult;
1045 }
1046 
1047 } // namespace MathRoot
1048 
1049 #endif // _MathRoot_MultipleUtils_HeaderFile