// Copyright (c) 2025 OPEN CASCADE SAS // // This file is part of Open CASCADE Technology software library. // // This library is free software; you can redistribute it and/or modify it under // the terms of the GNU Lesser General Public License version 2.1 as published // by the Free Software Foundation, with special exception defined in the file // OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT // distribution for complete text of the license and disclaimer of any warranty. // // Alternatively, this file may be used under the terms of Open CASCADE // commercial license or contractual agreement. #ifndef _MathInteg_DoubleExp_HeaderFile #define _MathInteg_DoubleExp_HeaderFile #include "MathUtils_Types.hxx" #include "MathUtils_Config.hxx" #include "MathUtils_Core.hxx" #include namespace MathInteg { using namespace MathUtils; //! Configuration for double exponential integration. struct DoubleExpConfig : IntegConfig { int NbLevels = 6; //!< Number of refinement levels (each doubles points) double StepFactor = 0.5; //!< Initial step size h = StepFactor / NbPoints //! Default constructor. DoubleExpConfig() = default; //! Constructor with tolerance. explicit DoubleExpConfig(double theTolerance, int theMaxIter = 100) : IntegConfig(theTolerance, theMaxIter) { } }; //! Tanh-Sinh (Double Exponential) quadrature for finite interval [a,b]. //! //! The double exponential transformation maps [a,b] to (-inf, +inf): //! x = (b+a)/2 + (b-a)/2 * tanh(pi/2 * sinh(t)) //! //! The integrand is multiplied by the Jacobian: //! dx/dt = (b-a)/2 * (pi/2) * cosh(t) / cosh^2(pi/2 * sinh(t)) //! //! Properties: //! - Excellent for functions with endpoint singularities //! - Exponential convergence for analytic functions //! - Self-adaptive through level refinement //! - Handles algebraic and logarithmic singularities //! //! @tparam Function type with Value(double theX, double& theF) method //! @param theFunc function to integrate //! @param theLower lower integration bound //! @param theUpper upper integration bound //! @param theConfig integration configuration //! @return integration result with error estimate template IntegResult TanhSinh(Function& theFunc, double theLower, double theUpper, const DoubleExpConfig& theConfig = DoubleExpConfig()) { IntegResult aResult; if (theLower >= theUpper) { aResult.Status = Status::InvalidInput; return aResult; } const double aHalfPi = M_PI / 2.0; const double aMid = 0.5 * (theUpper + theLower); const double aHalf = 0.5 * (theUpper - theLower); // Initial step size double aH = 1.0; // For convergence checking double aPrevSum = 0.0; size_t aTotalPoints = 0; // Level-by-level refinement for (int aLevel = 0; aLevel < theConfig.NbLevels; ++aLevel) { double aSum = 0.0; int aNbPoints = 0; // For level 0, evaluate at t = 0, +/-h, +/-2h, ... // For level > 0, evaluate only at new points: +/-h/2, +/-3h/2, ... int aStart = (aLevel == 0) ? 0 : 1; int aStep = (aLevel == 0) ? 1 : 2; // Positive t direction for (int k = aStart;; k += aStep) { double aT = k * aH; // Sinh and cosh of t double aSinhT = std::sinh(aT); double aCoshT = std::cosh(aT); // u = pi/2 * sinh(t) double aU = aHalfPi * aSinhT; // Check for overflow in exp if (std::abs(aU) > 700.0) { break; // tanh(u) ~= +/-1, contribution is negligible } // tanh(u) and sech^2(u) = 1/cosh^2(u) double aTanhU = std::tanh(aU); double aCoshU = std::cosh(aU); double aSech2U = 1.0 / (aCoshU * aCoshU); // x = mid + half * tanh(u) double aX = aMid + aHalf * aTanhU; // Check if x is within bounds (with small tolerance) if (aX <= theLower + MathUtils::THE_ZERO_TOL || aX >= theUpper - MathUtils::THE_ZERO_TOL) { break; } // Weight: h * (b-a)/2 * pi/2 * cosh(t) * sech^2(u) double aWeight = aHalf * aHalfPi * aCoshT * aSech2U; // Check if weight is negligible if (aWeight < MathUtils::THE_ZERO_TOL) { break; } // Evaluate function double aF = 0.0; if (!theFunc.Value(aX, aF)) { // Function evaluation failed, skip this point continue; } // Handle NaN or Inf if (!std::isfinite(aF)) { continue; } aSum += aWeight * aF; ++aNbPoints; // Limit number of points for safety if (aNbPoints > 10000) { break; } } // Negative t direction (skip t=0 which was already counted) int aNegStart = (aLevel == 0) ? 1 : aStart; // Start at 1 for level 0, aStart otherwise for (int k = aNegStart;; k += aStep) { double aT = -k * aH; double aSinhT = std::sinh(aT); double aCoshT = std::cosh(aT); double aU = aHalfPi * aSinhT; if (std::abs(aU) > 700.0) { break; } double aTanhU = std::tanh(aU); double aCoshU = std::cosh(aU); double aSech2U = 1.0 / (aCoshU * aCoshU); double aX = aMid + aHalf * aTanhU; if (aX <= theLower + MathUtils::THE_ZERO_TOL || aX >= theUpper - MathUtils::THE_ZERO_TOL) { break; } double aWeight = aHalf * aHalfPi * aCoshT * aSech2U; if (aWeight < MathUtils::THE_ZERO_TOL) { break; } double aF = 0.0; if (!theFunc.Value(aX, aF)) { continue; } if (!std::isfinite(aF)) { continue; } aSum += aWeight * aF; ++aNbPoints; if (aNbPoints > 10000) { break; } } // Scale by step size double aLevelSum = aH * aSum; aTotalPoints += static_cast(aNbPoints); // For level > 0, add to previous sum (trapezoidal refinement) // S_new = S_old/2 + h_new * new_points_sum double aNewSum = (aLevel == 0) ? aLevelSum : 0.5 * aPrevSum + aLevelSum; // Check for convergence if (aLevel > 0) { double aAbsError = std::abs(aNewSum - aPrevSum); double aRelError = aAbsError / std::max(std::abs(aNewSum), 1.0e-15); if (aRelError < theConfig.Tolerance) { aResult.Status = Status::OK; aResult.Value = aNewSum; aResult.AbsoluteError = aAbsError; aResult.RelativeError = aRelError; aResult.NbPoints = aTotalPoints; aResult.NbIterations = static_cast(aLevel + 1); return aResult; } } aPrevSum = aNewSum; aH *= 0.5; // Halve step size for next level } // Did not converge, return best estimate aResult.Status = Status::OK; // Still return a result aResult.Value = aPrevSum; aResult.NbPoints = aTotalPoints; aResult.NbIterations = static_cast(theConfig.NbLevels); return aResult; } //! Exp-Sinh quadrature for semi-infinite interval [a, +infinity). //! //! The transformation maps [0, +infinity) to (-infinity, +infinity): //! x = a + exp(pi/2 * sinh(t)) //! //! @tparam Function type with Value(double theX, double& theF) method //! @param theFunc function to integrate //! @param theLower lower bound a //! @param theConfig integration configuration //! @return integration result template IntegResult ExpSinh(Function& theFunc, double theLower, const DoubleExpConfig& theConfig = DoubleExpConfig()) { IntegResult aResult; const double aHalfPi = M_PI / 2.0; double aH = 1.0; double aPrevSum = 0.0; size_t aTotalPoints = 0; for (int aLevel = 0; aLevel < theConfig.NbLevels; ++aLevel) { double aSum = 0.0; int aNbPoints = 0; int aStart = (aLevel == 0) ? 0 : 1; int aStep = (aLevel == 0) ? 1 : 2; // Positive and negative t for (int aSign = -1; aSign <= 1; aSign += 2) { for (int k = (aSign < 0 && aLevel == 0) ? 1 : aStart;; k += aStep) { if (aSign < 0 && k == 0) { continue; } double aT = aSign * k * aH; double aSinhT = std::sinh(aT); double aCoshT = std::cosh(aT); double aU = aHalfPi * aSinhT; // exp(u) for x transformation if (aU > 700.0) { break; // Overflow } double aExpU = std::exp(aU); // x = a + exp(u) double aX = theLower + aExpU; // Weight: h * pi/2 * cosh(t) * exp(u) double aWeight = aHalfPi * aCoshT * aExpU; if (aWeight < MathUtils::THE_ZERO_TOL || !std::isfinite(aWeight)) { break; } // For negative t with large |u|, the contribution may be negligible if (aU < -30.0) { break; } double aF = 0.0; if (!theFunc.Value(aX, aF)) { continue; } if (!std::isfinite(aF)) { continue; } aSum += aWeight * aF; ++aNbPoints; if (aNbPoints > 10000) { break; } } } double aLevelSum = aH * aSum; aTotalPoints += static_cast(aNbPoints); // Trapezoidal refinement: S_new = S_old/2 + h_new * new_points_sum double aNewSum = (aLevel == 0) ? aLevelSum : 0.5 * aPrevSum + aLevelSum; if (aLevel > 0) { double aAbsError = std::abs(aNewSum - aPrevSum); double aRelError = aAbsError / std::max(std::abs(aNewSum), 1.0e-15); if (aRelError < theConfig.Tolerance) { aResult.Status = Status::OK; aResult.Value = aNewSum; aResult.AbsoluteError = aAbsError; aResult.RelativeError = aRelError; aResult.NbPoints = aTotalPoints; aResult.NbIterations = static_cast(aLevel + 1); return aResult; } } aPrevSum = aNewSum; aH *= 0.5; } aResult.Status = Status::OK; aResult.Value = aPrevSum; aResult.NbPoints = aTotalPoints; aResult.NbIterations = static_cast(theConfig.NbLevels); return aResult; } //! Sinh-Sinh quadrature for infinite interval (-infinity, +infinity). //! //! The transformation maps (-infinity, +infinity) to (-infinity, +infinity): //! x = sinh(pi/2 * sinh(t)) //! //! @tparam Function type with Value(double theX, double& theF) method //! @param theFunc function to integrate //! @param theConfig integration configuration //! @return integration result template IntegResult SinhSinh(Function& theFunc, const DoubleExpConfig& theConfig = DoubleExpConfig()) { IntegResult aResult; const double aHalfPi = M_PI / 2.0; double aH = 1.0; double aPrevSum = 0.0; size_t aTotalPoints = 0; for (int aLevel = 0; aLevel < theConfig.NbLevels; ++aLevel) { double aSum = 0.0; int aNbPoints = 0; int aStart = (aLevel == 0) ? 0 : 1; int aStep = (aLevel == 0) ? 1 : 2; // Positive and negative t for (int aSign = -1; aSign <= 1; aSign += 2) { for (int k = (aSign < 0 && aLevel == 0) ? 1 : aStart;; k += aStep) { if (aSign < 0 && k == 0) { continue; } double aT = aSign * k * aH; double aSinhT = std::sinh(aT); double aCoshT = std::cosh(aT); double aU = aHalfPi * aSinhT; // sinh(u) and cosh(u) for transformation if (std::abs(aU) > 700.0) { break; } double aSinhU = std::sinh(aU); double aCoshU = std::cosh(aU); // x = sinh(u) double aX = aSinhU; // Weight: h * pi/2 * cosh(t) * cosh(u) double aWeight = aHalfPi * aCoshT * aCoshU; if (aWeight < MathUtils::THE_ZERO_TOL || !std::isfinite(aWeight)) { break; } double aF = 0.0; if (!theFunc.Value(aX, aF)) { continue; } if (!std::isfinite(aF)) { continue; } aSum += aWeight * aF; ++aNbPoints; if (aNbPoints > 10000) { break; } } } double aLevelSum = aH * aSum; aTotalPoints += static_cast(aNbPoints); // Trapezoidal refinement: S_new = S_old/2 + h_new * new_points_sum double aNewSum = (aLevel == 0) ? aLevelSum : 0.5 * aPrevSum + aLevelSum; if (aLevel > 0) { double aAbsError = std::abs(aNewSum - aPrevSum); double aRelError = aAbsError / std::max(std::abs(aNewSum), 1.0e-15); if (aRelError < theConfig.Tolerance) { aResult.Status = Status::OK; aResult.Value = aNewSum; aResult.AbsoluteError = aAbsError; aResult.RelativeError = aRelError; aResult.NbPoints = aTotalPoints; aResult.NbIterations = static_cast(aLevel + 1); return aResult; } } aPrevSum = aNewSum; aH *= 0.5; } aResult.Status = Status::OK; aResult.Value = aPrevSum; aResult.NbPoints = aTotalPoints; aResult.NbIterations = static_cast(theConfig.NbLevels); return aResult; } //! Double exponential integration with automatic interval detection. //! //! Selects the appropriate DE quadrature based on the interval: //! - Finite [a,b]: TanhSinh //! - Semi-infinite [a, +infinity): ExpSinh //! - Infinite (-infinity, +infinity): SinhSinh //! //! @tparam Function type with Value(double theX, double& theF) method //! @param theFunc function to integrate //! @param theLower lower bound (use -HUGE_VAL for -infinity) //! @param theUpper upper bound (use HUGE_VAL for +infinity) //! @param theConfig integration configuration //! @return integration result template IntegResult DoubleExponential(Function& theFunc, double theLower, double theUpper, const DoubleExpConfig& theConfig = DoubleExpConfig()) { const double aHuge = 1.0e300; bool aIsLowerInf = (theLower < -aHuge); bool aIsUpperInf = (theUpper > aHuge); if (aIsLowerInf && aIsUpperInf) { // (-infinity, +infinity) return SinhSinh(theFunc, theConfig); } else if (aIsUpperInf) { // [a, +infinity) return ExpSinh(theFunc, theLower, theConfig); } else if (aIsLowerInf) { // (-infinity, b] - transform to [b, +infinity) with x -> -x class NegatedFunc { public: NegatedFunc(Function& theF) : myFunc(theF) { } bool Value(double theX, double& theF) { return myFunc.Value(-theX, theF); } private: Function& myFunc; }; NegatedFunc aNegated(theFunc); return ExpSinh(aNegated, -theUpper, theConfig); } else { // [a, b] return TanhSinh(theFunc, theLower, theUpper, theConfig); } } //! Tanh-Sinh quadrature optimized for endpoint singularities. //! //! This variant uses precomputed nodes and weights for better performance //! when integrating functions with algebraic singularities at the endpoints. //! //! @tparam Function type with Value(double theX, double& theF) method //! @param theFunc function to integrate //! @param theLower lower bound //! @param theUpper upper bound //! @param theTolerance relative tolerance //! @return integration result template IntegResult TanhSinhSingular(Function& theFunc, double theLower, double theUpper, double theTolerance = 1.0e-10) { DoubleExpConfig aConfig; aConfig.Tolerance = theTolerance; aConfig.NbLevels = 8; // More levels for singular functions return TanhSinh(theFunc, theLower, theUpper, aConfig); } //! Integrates a function with a known singularity at an interior point. //! //! Splits the interval at the singularity and integrates each part. //! //! @tparam Function type with Value(double theX, double& theF) method //! @param theFunc function to integrate //! @param theLower lower bound //! @param theUpper upper bound //! @param theSingularity location of interior singularity //! @param theConfig integration configuration //! @return combined integration result template IntegResult TanhSinhWithSingularity(Function& theFunc, double theLower, double theUpper, double theSingularity, const DoubleExpConfig& theConfig = DoubleExpConfig()) { IntegResult aResult; if (theSingularity <= theLower || theSingularity >= theUpper) { // Singularity is at or outside bounds, use regular integration return TanhSinh(theFunc, theLower, theUpper, theConfig); } // Integrate left part [lower, singularity] IntegResult aLeft = TanhSinh(theFunc, theLower, theSingularity, theConfig); if (!aLeft.IsDone()) { return aLeft; } // Integrate right part [singularity, upper] IntegResult aRight = TanhSinh(theFunc, theSingularity, theUpper, theConfig); if (!aRight.IsDone()) { return aRight; } // Combine results aResult.Status = Status::OK; aResult.Value = *aLeft.Value + *aRight.Value; aResult.NbPoints = aLeft.NbPoints + aRight.NbPoints; aResult.NbIterations = std::max(aLeft.NbIterations, aRight.NbIterations); if (aLeft.AbsoluteError && aRight.AbsoluteError) { aResult.AbsoluteError = *aLeft.AbsoluteError + *aRight.AbsoluteError; aResult.RelativeError = *aResult.AbsoluteError / std::max(std::abs(*aResult.Value), 1.0e-15); } return aResult; } } // namespace MathInteg #endif // _MathInteg_DoubleExp_HeaderFile