Back to home page

EIC code displayed by LXR

 
 

    


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

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 _MathInteg_Kronrod_HeaderFile
0015 #define _MathInteg_Kronrod_HeaderFile
0016 
0017 // Include wrapper for Gauss-Kronrod weights BEFORE namespace MathInteg is defined
0018 #include <MathUtils_GaussKronrodWeights.hxx>
0019 
0020 #include <MathUtils_Types.hxx>
0021 #include <MathUtils_Config.hxx>
0022 #include <MathUtils_Core.hxx>
0023 
0024 #include <NCollection_DynamicArray.hxx>
0025 
0026 #include <cmath>
0027 
0028 namespace MathInteg
0029 {
0030 using namespace MathUtils;
0031 
0032 //! Configuration for Gauss-Kronrod integration.
0033 struct KronrodConfig : IntegConfig
0034 {
0035   int  NbGaussPoints = 7;    //!< Number of Gauss points (n), Kronrod will use 2n+1 points
0036   bool Adaptive      = true; //!< Whether to use adaptive subdivision
0037 
0038   //! Default constructor.
0039   KronrodConfig() = default;
0040 
0041   //! Constructor with tolerance.
0042   explicit KronrodConfig(double theTolerance, int theMaxIter = 100)
0043       : IntegConfig(theTolerance, theMaxIter)
0044   {
0045   }
0046 };
0047 
0048 //! Apply Gauss-Kronrod rule to a single interval.
0049 //!
0050 //! The Gauss-Kronrod rule uses n Gauss points embedded in 2n+1 Kronrod points.
0051 //! The difference between the Gauss and Kronrod estimates provides
0052 //! an error estimate without additional function evaluations.
0053 //!
0054 //! @tparam Function type with Value(double theX, double& theF) method
0055 //! @param theFunc function to integrate
0056 //! @param theLower lower integration bound
0057 //! @param theUpper upper integration bound
0058 //! @param theNbGauss number of Gauss points (determines rule order)
0059 //! @return integration result with error estimate
0060 template <typename Function>
0061 IntegResult KronrodRule(Function& theFunc, double theLower, double theUpper, int theNbGauss = 7)
0062 {
0063   IntegResult aResult;
0064 
0065   // Number of Kronrod points = 2*n + 1
0066   const int aNbKronrod = 2 * theNbGauss + 1;
0067 
0068   // Get Gauss-Kronrod points and weights using global ::math class
0069   math_Vector aGaussP(1, theNbGauss);
0070   math_Vector aGaussW(1, theNbGauss);
0071   math_Vector aKronrodP(1, aNbKronrod);
0072   math_Vector aKronrodW(1, aNbKronrod);
0073 
0074   if (!GetKronrodPointsAndWeights(aNbKronrod, aKronrodP, aKronrodW)
0075       || !GetOrderedGaussPointsAndWeights(theNbGauss, aGaussP, aGaussW))
0076   {
0077     aResult.Status = Status::NumericalError;
0078     return aResult;
0079   }
0080 
0081   // Transform interval [theLower, theUpper] to [-1, 1]
0082   const double aHalfLen = 0.5 * (theUpper - theLower);
0083   const double aMid     = 0.5 * (theUpper + theLower);
0084 
0085   // Compute Gauss and Kronrod quadratures simultaneously
0086   const int aNPnt2 = (aNbKronrod + 1) / 2;
0087 
0088   double aGaussVal   = 0.0;
0089   double aKronrodVal = 0.0;
0090 
0091   // Function values at symmetric points
0092   math_Vector aF1(0, aNPnt2 - 1);
0093   math_Vector aF2(0, aNPnt2 - 1);
0094 
0095   // Even indices (Gauss points embedded in Kronrod)
0096   for (int i = 2; i < aNPnt2; i += 2)
0097   {
0098     const double aDx   = aHalfLen * aKronrodP(i);
0099     double       aVal1 = 0.0, aVal2 = 0.0;
0100 
0101     if (!theFunc.Value(aMid + aDx, aVal1) || !theFunc.Value(aMid - aDx, aVal2))
0102     {
0103       aResult.Status = Status::NumericalError;
0104       return aResult;
0105     }
0106 
0107     aF1(i) = aVal1;
0108     aF2(i) = aVal2;
0109     aGaussVal += (aVal1 + aVal2) * aGaussW(i / 2);
0110     aKronrodVal += (aVal1 + aVal2) * aKronrodW(i);
0111   }
0112 
0113   // Center point
0114   double aFc = 0.0;
0115   if (!theFunc.Value(aMid, aFc))
0116   {
0117     aResult.Status = Status::NumericalError;
0118     return aResult;
0119   }
0120 
0121   aKronrodVal += aFc * aKronrodW(aNPnt2);
0122 
0123   // Check if center is also a Gauss point
0124   if (aNPnt2 % 2 == 0)
0125   {
0126     aGaussVal += aFc * aGaussW(aNPnt2 / 2);
0127   }
0128 
0129   // Odd indices (Kronrod-only points)
0130   for (int i = 1; i < aNPnt2; i += 2)
0131   {
0132     const double aDx   = aHalfLen * aKronrodP(i);
0133     double       aVal1 = 0.0, aVal2 = 0.0;
0134 
0135     if (!theFunc.Value(aMid + aDx, aVal1) || !theFunc.Value(aMid - aDx, aVal2))
0136     {
0137       aResult.Status = Status::NumericalError;
0138       return aResult;
0139     }
0140 
0141     aF1(i) = aVal1;
0142     aF2(i) = aVal2;
0143     aKronrodVal += (aVal1 + aVal2) * aKronrodW(i);
0144   }
0145 
0146   // QUADPACK-style error estimation:
0147   // Compute asc = integral of |f(x) - mean|, which measures function variability
0148   const double aMean = 0.5 * aKronrodVal;
0149   double       aAsc  = std::abs(aFc - aMean) * aKronrodW(aNPnt2);
0150   for (int i = 1; i < aNPnt2; ++i)
0151   {
0152     aAsc += aKronrodW(i) * (std::abs(aF1(i) - aMean) + std::abs(aF2(i) - aMean));
0153   }
0154 
0155   // Scale by interval half-length
0156   aAsc *= aHalfLen;
0157   aKronrodVal *= aHalfLen;
0158   aGaussVal *= aHalfLen;
0159 
0160   // Basic error estimate
0161   double aAbsError = std::abs(aKronrodVal - aGaussVal);
0162 
0163   // QUADPACK scaling: when error is small relative to function variability,
0164   // the actual error may be even smaller
0165   if (aAsc != 0.0 && aAbsError != 0.0)
0166   {
0167     const double aScale = std::pow(200.0 * aAbsError / aAsc, 1.5);
0168     if (aScale < 1.0)
0169     {
0170       aAbsError = std::min(aAbsError, aAsc * aScale);
0171     }
0172   }
0173 
0174   aResult.Status        = Status::OK;
0175   aResult.Value         = aKronrodVal;
0176   aResult.AbsoluteError = aAbsError;
0177   aResult.RelativeError = aAbsError / std::max(std::abs(aKronrodVal), 1.0e-15);
0178   aResult.NbPoints      = static_cast<size_t>(aNbKronrod);
0179   aResult.NbIterations  = 1;
0180   return aResult;
0181 }
0182 
0183 //! Gauss-Kronrod adaptive integration.
0184 //!
0185 //! Uses adaptive bisection to achieve the requested tolerance.
0186 //! At each subdivision, the interval with the largest error estimate
0187 //! is bisected, and both halves are reintegrated.
0188 //!
0189 //! @tparam Function type with Value(double theX, double& theF) method
0190 //! @param theFunc function to integrate
0191 //! @param theLower lower integration bound
0192 //! @param theUpper upper integration bound
0193 //! @param theConfig integration configuration
0194 //! @return integration result with error estimate
0195 template <typename Function>
0196 IntegResult Kronrod(Function&            theFunc,
0197                     double               theLower,
0198                     double               theUpper,
0199                     const KronrodConfig& theConfig = KronrodConfig())
0200 {
0201   IntegResult aResult;
0202 
0203   if (!theConfig.Adaptive)
0204   {
0205     // Single application of Kronrod rule
0206     return KronrodRule(theFunc, theLower, theUpper, theConfig.NbGaussPoints);
0207   }
0208 
0209   // Adaptive integration using a heap of intervals
0210   struct Interval
0211   {
0212     double Lower;
0213     double Upper;
0214     double Value;
0215     double Error;
0216   };
0217 
0218   // Initialize with the whole interval
0219   IntegResult anInitResult = KronrodRule(theFunc, theLower, theUpper, theConfig.NbGaussPoints);
0220   if (!anInitResult.IsDone())
0221   {
0222     return anInitResult;
0223   }
0224 
0225   NCollection_DynamicArray<Interval> aHeap;
0226   aHeap.Append({theLower, theUpper, *anInitResult.Value, *anInitResult.AbsoluteError});
0227 
0228   double aTotalValue  = *anInitResult.Value;
0229   double aTotalError  = *anInitResult.AbsoluteError;
0230   size_t aTotalPoints = anInitResult.NbPoints;
0231   int    aIterations  = 1;
0232 
0233   // Adaptive refinement
0234   while (aIterations < theConfig.MaxIterations)
0235   {
0236     // Check convergence
0237     if (aTotalError < theConfig.Tolerance * std::max(std::abs(aTotalValue), 1.0e-15))
0238     {
0239       break;
0240     }
0241 
0242     // Find interval with largest error
0243     int    aMaxIdx   = 0;
0244     double aMaxError = 0.0;
0245     for (int i = 0; i < aHeap.Length(); ++i)
0246     {
0247       if (aHeap.Value(i).Error > aMaxError)
0248       {
0249         aMaxError = aHeap.Value(i).Error;
0250         aMaxIdx   = i;
0251       }
0252     }
0253 
0254     if (aMaxError < MathUtils::THE_ZERO_TOL)
0255     {
0256       break; // No more refinement needed
0257     }
0258 
0259     // Bisect the interval with largest error (copy to avoid reference invalidation)
0260     const Interval aWorst  = aHeap.Value(aMaxIdx);
0261     const double   aBisMid = 0.5 * (aWorst.Lower + aWorst.Upper);
0262 
0263     IntegResult aLeftResult  = KronrodRule(theFunc, aWorst.Lower, aBisMid, theConfig.NbGaussPoints);
0264     IntegResult aRightResult = KronrodRule(theFunc, aBisMid, aWorst.Upper, theConfig.NbGaussPoints);
0265 
0266     if (!aLeftResult.IsDone() || !aRightResult.IsDone())
0267     {
0268       aResult.Status        = Status::NumericalError;
0269       aResult.Value         = aTotalValue;
0270       aResult.AbsoluteError = aTotalError;
0271       aResult.NbPoints      = aTotalPoints;
0272       aResult.NbIterations  = static_cast<size_t>(aIterations);
0273       return aResult;
0274     }
0275 
0276     // Update totals
0277     aTotalValue -= aWorst.Value;
0278     aTotalError -= aWorst.Error;
0279     aTotalValue += *aLeftResult.Value + *aRightResult.Value;
0280     aTotalError += *aLeftResult.AbsoluteError + *aRightResult.AbsoluteError;
0281     aTotalPoints += aLeftResult.NbPoints + aRightResult.NbPoints;
0282     ++aIterations;
0283 
0284     // Replace the worst interval with the two new intervals
0285     aHeap.ChangeValue(
0286       aMaxIdx) = {aWorst.Lower, aBisMid, *aLeftResult.Value, *aLeftResult.AbsoluteError};
0287     aHeap.Append({aBisMid, aWorst.Upper, *aRightResult.Value, *aRightResult.AbsoluteError});
0288   }
0289 
0290   aResult.Status        = Status::OK;
0291   aResult.Value         = aTotalValue;
0292   aResult.AbsoluteError = aTotalError;
0293   aResult.RelativeError = aTotalError / std::max(std::abs(aTotalValue), 1.0e-15);
0294   aResult.NbPoints      = aTotalPoints;
0295   aResult.NbIterations  = static_cast<size_t>(aIterations);
0296   return aResult;
0297 }
0298 
0299 //! Gauss-Kronrod integration with automatic order selection.
0300 //!
0301 //! Starts with a low-order rule and increases the order until
0302 //! the tolerance is met or the maximum order is reached.
0303 //!
0304 //! @tparam Function type with Value(double theX, double& theF) method
0305 //! @param theFunc function to integrate
0306 //! @param theLower lower integration bound
0307 //! @param theUpper upper integration bound
0308 //! @param theTolerance relative tolerance
0309 //! @param theMaxOrder maximum Gauss order to try
0310 //! @return integration result
0311 template <typename Function>
0312 IntegResult KronrodAuto(Function& theFunc,
0313                         double    theLower,
0314                         double    theUpper,
0315                         double    theTolerance = 1.0e-10,
0316                         int       theMaxOrder  = 30)
0317 {
0318   IntegResult aBestResult;
0319   aBestResult.Status = Status::NotConverged;
0320 
0321   // Try increasing orders
0322   for (int aOrder = 7; aOrder <= theMaxOrder; aOrder += 4)
0323   {
0324     IntegResult aResult = KronrodRule(theFunc, theLower, theUpper, aOrder);
0325     if (!aResult.IsDone())
0326     {
0327       continue;
0328     }
0329 
0330     aBestResult = aResult;
0331 
0332     // Check if tolerance is met
0333     if (aResult.RelativeError && *aResult.RelativeError < theTolerance)
0334     {
0335       return aResult;
0336     }
0337   }
0338 
0339   // If fixed order didn't work, try adaptive
0340   KronrodConfig aConfig;
0341   aConfig.Tolerance     = theTolerance;
0342   aConfig.NbGaussPoints = 7;
0343   aConfig.Adaptive      = true;
0344   aConfig.MaxIterations = 50;
0345 
0346   return Kronrod(theFunc, theLower, theUpper, aConfig);
0347 }
0348 
0349 //! Gauss-Kronrod integration over semi-infinite interval [a, +infinity).
0350 //!
0351 //! Uses the substitution x = a + t / (1 - t) to map [a, +infinity) to [0, 1).
0352 //!
0353 //! @tparam Function type with Value(double theX, double& theF) method
0354 //! @param theFunc function to integrate
0355 //! @param theLower lower bound a
0356 //! @param theConfig integration configuration
0357 //! @return integration result
0358 template <typename Function>
0359 IntegResult KronrodSemiInfinite(Function&            theFunc,
0360                                 double               theLower,
0361                                 const KronrodConfig& theConfig = KronrodConfig())
0362 {
0363   class TransformedFunc
0364   {
0365   public:
0366     TransformedFunc(Function& theF, double theA)
0367         : myFunc(theF),
0368           myA(theA)
0369     {
0370     }
0371 
0372     bool Value(double theT, double& theF)
0373     {
0374       if (theT >= 1.0)
0375       {
0376         theF = 0.0;
0377         return true;
0378       }
0379 
0380       const double aX     = myA + theT / (1.0 - theT);
0381       const double aJacob = 1.0 / MathUtils::Sqr(1.0 - theT);
0382 
0383       double aFx = 0.0;
0384       if (!myFunc.Value(aX, aFx))
0385       {
0386         return false;
0387       }
0388 
0389       theF = aFx * aJacob;
0390       return true;
0391     }
0392 
0393   private:
0394     Function& myFunc;
0395     double    myA;
0396   };
0397 
0398   TransformedFunc aTransformed(theFunc, theLower);
0399   return Kronrod(aTransformed, 0.0, 1.0, theConfig);
0400 }
0401 
0402 //! Gauss-Kronrod integration over infinite interval (-infinity, +infinity).
0403 //!
0404 //! Uses the substitution x = t / (1 - t^2) to map (-infinity, +infinity) to (-1, 1).
0405 //! The function must decay sufficiently fast at infinity.
0406 //!
0407 //! @tparam Function type with Value(double theX, double& theF) method
0408 //! @param theFunc function to integrate
0409 //! @param theConfig integration configuration
0410 //! @return integration result
0411 template <typename Function>
0412 IntegResult KronrodInfinite(Function& theFunc, const KronrodConfig& theConfig = KronrodConfig())
0413 {
0414   class TransformedFunc
0415   {
0416   public:
0417     TransformedFunc(Function& theF)
0418         : myFunc(theF)
0419     {
0420     }
0421 
0422     bool Value(double theT, double& theF)
0423     {
0424       if (std::abs(theT) >= 1.0)
0425       {
0426         theF = 0.0;
0427         return true;
0428       }
0429 
0430       const double aT2    = theT * theT;
0431       const double aX     = theT / (1.0 - aT2);
0432       const double aJacob = (1.0 + aT2) / MathUtils::Sqr(1.0 - aT2);
0433 
0434       double aFx = 0.0;
0435       if (!myFunc.Value(aX, aFx))
0436       {
0437         return false;
0438       }
0439 
0440       theF = aFx * aJacob;
0441       return true;
0442     }
0443 
0444   private:
0445     Function& myFunc;
0446   };
0447 
0448   TransformedFunc aTransformed(theFunc);
0449   return Kronrod(aTransformed, -1.0, 1.0, theConfig);
0450 }
0451 
0452 } // namespace MathInteg
0453 
0454 #endif // _MathInteg_Kronrod_HeaderFile