Skip to content

adaptive_simpson.hpp

SECTIONOptimization INCLUDEnoya/optimization/adaptive_simpson.hpp

按误差估计自适应细分区间,数值计算平滑函数的定积分;达到深度上限或浮点分辨率时停止,误差估计不保证实际误差上界。

Complexity: Time: \(O(2^{d})\) function evaluations in the worst case, where \(d\) is lim. Space: \(O(d)\) recursion stack.

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

/// @complexity Time: O(2^d) function evaluations in the worst case, where d is lim.
/// Space: O(d) recursion stack.

#include <cassert>
#include <cmath>
#include <functional>
#include <numeric>
#include <utility>

namespace noya {

/// @brief Numerically integrate a smooth real function on [l, r] with
/// adaptive Simpson subdivision. eps targets the estimated absolute error;
/// recursion also stops at lim or floating-point resolution, without a certified bound.
template <class Function, class Real = long double>
Real adaptive_simpson(Function fun, Real l, Real r, Real eps = Real(1e-10),
                      int lim = 24) {
  assert(eps > Real{});
  assert(lim >= 0);
  if (l == r) {
    return Real{};
  }
  bool rev = r < l;
  if (rev) std::swap(l, r);

  // Keep the usual arithmetic when finite; split before adding on overflow.
  auto midpoint = [](Real a, Real b) {
    Real s = a + b;
    return std::isfinite(s) ? s / Real(2) : a / Real(2) + b / Real(2);
  };
  auto sim = [](Real a, Real b, Real fa, Real fm, Real fb) {
    Real wid = b - a;
    Real val = wid * (fa + Real(4) * fm + fb) / Real(6);
    if (std::isfinite(val)) return val;
    Real avg = std::lerp(std::midpoint(fa, fb), fm, Real(2) / Real(3));
    return std::isfinite(wid) ? wid * avg
                             : ((b / Real(2) - a / Real(2)) * avg) * Real(2);
  };
  Real mdd = midpoint(l, r);
  Real fl = std::invoke(fun, l);
  Real fm = std::invoke(fun, mdd);
  Real fr = std::invoke(fun, r);
  Real sum = sim(l, r, fl, fm, fr);

  auto sol = [&](auto &dfs, Real a, Real b, Real mid, Real fa, Real fc,
                 Real fb, Real est, Real tol, int dep) -> Real {
    if (mid == a || mid == b) return (b - a) * std::midpoint(fa, fb);
    Real lm = midpoint(a, mid);
    Real rm = midpoint(mid, b);
    Real flm = std::invoke(fun, lm);
    Real frm = std::invoke(fun, rm);
    Real sl = sim(a, mid, fa, flm, fc);
    Real sr = sim(mid, b, fc, frm, fb);
    Real dif = sl + sr - est;
    if (dep == 0 || std::abs(dif) <= Real(15) * tol) {
      return sl + sr + dif / Real(15);
    }
    return dfs(dfs, a, mid, lm, fa, flm, fc, sl, tol / Real(2), dep - 1) +
           dfs(dfs, mid, b, rm, fc, frm, fb, sr, tol / Real(2), dep - 1);
  };
  Real res = sol(sol, l, r, mdd, fl, fm, fr, sum, eps, lim);
  return rev ? -res : res;
}

} // namespace noya

 // NOYA_ADAPTIVE_SIMPSON_HPP
#ifndef NOYA_ADAPTIVE_SIMPSON_HPP
#define NOYA_ADAPTIVE_SIMPSON_HPP 1

/// @complexity Time: O(2^d) function evaluations in the worst case, where d is lim.
/// Space: O(d) recursion stack.

#include <cassert>
#include <cmath>
#include <functional>
#include <numeric>
#include <utility>

namespace noya {

/// @brief Numerically integrate a smooth real function on [l, r] with
/// adaptive Simpson subdivision. eps targets the estimated absolute error;
/// recursion also stops at lim or floating-point resolution, without a certified bound.
template <class Function, class Real = long double>
Real adaptive_simpson(Function fun, Real l, Real r, Real eps = Real(1e-10),
                      int lim = 24) {
  assert(eps > Real{});
  assert(lim >= 0);
  if (l == r) {
    return Real{};
  }
  bool rev = r < l;
  if (rev) std::swap(l, r);

  // Keep the usual arithmetic when finite; split before adding on overflow.
  auto midpoint = [](Real a, Real b) {
    Real s = a + b;
    return std::isfinite(s) ? s / Real(2) : a / Real(2) + b / Real(2);
  };
  auto sim = [](Real a, Real b, Real fa, Real fm, Real fb) {
    Real wid = b - a;
    Real val = wid * (fa + Real(4) * fm + fb) / Real(6);
    if (std::isfinite(val)) return val;
    Real avg = std::lerp(std::midpoint(fa, fb), fm, Real(2) / Real(3));
    return std::isfinite(wid) ? wid * avg
                             : ((b / Real(2) - a / Real(2)) * avg) * Real(2);
  };
  Real mdd = midpoint(l, r);
  Real fl = std::invoke(fun, l);
  Real fm = std::invoke(fun, mdd);
  Real fr = std::invoke(fun, r);
  Real sum = sim(l, r, fl, fm, fr);

  auto sol = [&](auto &dfs, Real a, Real b, Real mid, Real fa, Real fc,
                 Real fb, Real est, Real tol, int dep) -> Real {
    if (mid == a || mid == b) return (b - a) * std::midpoint(fa, fb);
    Real lm = midpoint(a, mid);
    Real rm = midpoint(mid, b);
    Real flm = std::invoke(fun, lm);
    Real frm = std::invoke(fun, rm);
    Real sl = sim(a, mid, fa, flm, fc);
    Real sr = sim(mid, b, fc, frm, fb);
    Real dif = sl + sr - est;
    if (dep == 0 || std::abs(dif) <= Real(15) * tol) {
      return sl + sr + dif / Real(15);
    }
    return dfs(dfs, a, mid, lm, fa, flm, fc, sl, tol / Real(2), dep - 1) +
           dfs(dfs, mid, b, rm, fc, frm, fb, sr, tol / Real(2), dep - 1);
  };
  Real res = sol(sol, l, r, mdd, fl, fm, fr, sum, eps, lim);
  return rev ? -res : res;
}

} // namespace noya

#endif // NOYA_ADAPTIVE_SIMPSON_HPP
/// @complexity Time: O(2^d) function evaluations in the worst case, where d is lim.
/// Space: O(d) recursion stack.

#include <cassert>
#include <cmath>
#include <functional>
#include <numeric>
#include <utility>

namespace noya {

/// @brief Numerically integrate a smooth real function on [l, r] with
/// adaptive Simpson subdivision. eps targets the estimated absolute error;
/// recursion also stops at lim or floating-point resolution, without a certified bound.
template <class Function, class Real = long double>
Real adaptive_simpson(Function fun, Real l, Real r, Real eps = Real(1e-10),
                      int lim = 24) {
  assert(eps > Real{});
  assert(lim >= 0);
  if (l == r) {
    return Real{};
  }
  bool rev = r < l;
  if (rev) std::swap(l, r);

  // Keep the usual arithmetic when finite; split before adding on overflow.
  auto midpoint = [](Real a, Real b) {
    Real s = a + b;
    return std::isfinite(s) ? s / Real(2) : a / Real(2) + b / Real(2);
  };
  auto sim = [](Real a, Real b, Real fa, Real fm, Real fb) {
    Real wid = b - a;
    Real val = wid * (fa + Real(4) * fm + fb) / Real(6);
    if (std::isfinite(val)) return val;
    Real avg = std::lerp(std::midpoint(fa, fb), fm, Real(2) / Real(3));
    return std::isfinite(wid) ? wid * avg
                             : ((b / Real(2) - a / Real(2)) * avg) * Real(2);
  };
  Real mdd = midpoint(l, r);
  Real fl = std::invoke(fun, l);
  Real fm = std::invoke(fun, mdd);
  Real fr = std::invoke(fun, r);
  Real sum = sim(l, r, fl, fm, fr);

  auto sol = [&](auto &dfs, Real a, Real b, Real mid, Real fa, Real fc,
                 Real fb, Real est, Real tol, int dep) -> Real {
    if (mid == a || mid == b) return (b - a) * std::midpoint(fa, fb);
    Real lm = midpoint(a, mid);
    Real rm = midpoint(mid, b);
    Real flm = std::invoke(fun, lm);
    Real frm = std::invoke(fun, rm);
    Real sl = sim(a, mid, fa, flm, fc);
    Real sr = sim(mid, b, fc, frm, fb);
    Real dif = sl + sr - est;
    if (dep == 0 || std::abs(dif) <= Real(15) * tol) {
      return sl + sr + dif / Real(15);
    }
    return dfs(dfs, a, mid, lm, fa, flm, fc, sl, tol / Real(2), dep - 1) +
           dfs(dfs, mid, b, rm, fc, frm, fb, sr, tol / Real(2), dep - 1);
  };
  Real res = sol(sol, l, r, mdd, fl, fm, fr, sum, eps, lim);
  return rev ? -res : res;
}

} // namespace noya

 // NOYA_ADAPTIVE_SIMPSON_HPP