using BenchmarkTools
adaptive Simpson's method¶
basic form¶
The integral $I = \int_a^b dx f(x)$
is approximated as
$$ \begin{align} I &= S + E \\ S &= \frac{b-a}{6} \, \left( f(a) + 4 f(m) + f(b) \right) \\ E &= -\frac{1}{90} \left( \frac{b-a}{2} \right)^5 \, f^{(4)}(\xi). \end{align} $$
for $m = \frac{a+b}{2}$ and some $\xi$ in within a:b.
adaptive scheme¶
Given the basic form, we can consider two levels of approximations:
$ \begin{aligned} S_1(a, b) &= \frac{b-a}{6} \, \left( f(a) + 4 f(m) + f(b) \right) \\ S_2(a, b) &= S_1(a, m) + S_1(m, b). \end{aligned} $
and the respective errors work out to be
$ \begin{aligned} E_1(a, b) &= -\frac{1}{90} \left( \frac{b-a}{2} \right)^5 \, f^{(4)}(\xi) \\ E_2(a, b) &= -\frac{1}{90} \, 2\, \left( \frac{b-a}{4} \right)^5 \, f^{(4)}(\xi) \\ &\approx \frac{1}{16} \, E_1(a, b). \end{aligned} $
This is expected for a 4-th order scheme. With $ S_1 + E_1 = S_2 + E_2 $ we have a way to construct the error via
$ E_2(a, b) \approx \frac{1}{15} \, \left( S_2(a,b)-S_1(a,b) \right) $
function adapt_simp(f, a, b, tol=1E-12, max_depth=80)
rule(a1, a2) = (a2-a1) * (f(a1)+f(a2)+4*f((a1+a2)/2))/6
function adapt(a1, a2, tol, depth)
m = (a1+a2)/2
s1 = rule(a1, a2)
s2 = rule(a1, m) + rule(m, a2)
err = abs(s2-s1)
if err < tol && depth < max_depth
return s2
elseif depth >= max_depth
@warn "max. depth $max_depth reached."
return s2
else
tol = max(eps(), tol/2)
return adapt(a1, m, tol, depth+1) + adapt(m, a2, tol, depth+1)
end
end
return adapt(a, b, tol, 0)
end
adapt_simp (generic function with 3 methods)
println([adapt_simp(x->tan(x), 0.0, 1.0), -log(cos(1))])
println([adapt_simp(x->tanh(x), 0.0, 1.0), log(cosh(1))])
[0.6156264703860375, 0.6156264703860141] [0.433780830483044, 0.4337808304830271]
@benchmark adapt_simp(x->tanh(x), 0.0, 1.0)
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample. Range (min … max): 24.708 μs … 863.000 μs ┊ GC (min … max): 0.00% … 95.57% Time (median): 25.708 μs ┊ GC (median): 0.00% Time (mean ± σ): 26.983 μs ± 11.804 μs ┊ GC (mean ± σ): 1.02% ± 2.97% ▃▅███▅▄▃▁▁ ▂▂▃▂▃▂▂▂ ▂▁▁ ▁ ▂▃▄▆▆▇▅▄▂▁ ▂ ▂▇█████████████████████████████████████▇▇▇▇█▆█▇▇▇▇▅▅▅▂▅▄▅▄▆▆ █ 24.7 μs Histogram: log(frequency) by time 31.1 μs < Memory estimate: 30.61 KiB, allocs estimate: 1958.
function fast_simp(f, a, b, tol=1e-12, max_depth=80)
# not that much faster
function rule(a1, a2, a3, f1, f2, f3)
return (a3-a1)*(f1+f3+4*f2)/6
end
function adapt(a1, a2, a3, f1, f2, f3, tol, depth)
m12 = (a1+a2)/2; fm12 = f(m12)
m23 = (a2+a3)/2; fm23 = f(m23)
s1 = rule(a1, a2, a3, f1, f2, f3)
sl = rule(a1, m12, a2, f1, fm12, f2)
sr = rule(a2, m23, a3, f2, fm23, f3)
s2 = sl + sr
err = abs(s2 - s1)
# ret = (32*s2-s1)/31
ret = s2
if err < tol || depth >= max_depth
depth >= max_depth && @warn "max depth $max_depth reached"
return ret
else
tol = max(tol/2, eps())
return adapt(a1, m12, a2, f1, fm12, f2, tol, depth+1) +
adapt(a2, m23, a3, f2, fm23, f3, tol, depth+1)
end
end
mid = (a+b)/2
fa = f(a)
fb = f(b)
fm = f(mid)
return adapt(a, mid, b, fa, fm, fb, tol, 0)
end
fast_simp (generic function with 3 methods)
function integrate(f, a, b, tol=1e-12, max_depth=80)
function rule(a1, a2, a3, f1, f2, f3)
# richardson-improved simpson's rule
ret1 = (a3-a1)*(f1+f3+4*f2)/6
ret2 = (a2-a1)*(f1+f2+4*f(a1/2+a2/2))/6
ret2 += (a3-a2)*(f2+f3+4*f(a2/2+a3/2))/6
return (16*ret2 - ret1)/15
end
function adapt(a1, a2, a3, f1, f2, f3, tol, depth)
m12 = (a1+a2)/2; fm12 = f(m12)
m23 = (a2+a3)/2; fm23 = f(m23)
s1 = rule(a1, a2, a3, f1, f2, f3)
sl = rule(a1, m12, a2, f1, fm12, f2)
sr = rule(a2, m23, a3, f2, fm23, f3)
s2 = sl + sr
err = abs(s2 - s1)
ret = (32*s2-s1)/31
if err < tol || depth >= max_depth
depth >= max_depth && @warn "max depth $max_depth reached"
return ret
else
tol = max(tol/2, eps())
return adapt(a1, m12, a2, f1, fm12, f2, tol, depth+1) +
adapt(a2, m23, a3, f2, fm23, f3, tol, depth+1)
end
end
mid = (a+b)/2
fa = f(a)
fb = f(b)
fm = f(mid)
return adapt(a, mid, b, fa, fm, fb, tol, 0)
end
integrate (generic function with 3 methods)
println([fast_simp(x->tanh(x), 0.0, 1.0), integrate(x->tanh(x), 0.0, 1.0), log(cosh(1))])
println([fast_simp(x->tan(x), 0.0, 1.0), integrate(x->tan(x), 0.0, 1.0), -log(cos(1))])
[0.433780830483044, 0.4337808304830294, 0.4337808304830271] [0.6156264703860375, 0.6156264703860105, 0.6156264703860141]
@benchmark fast_simp(x->tanh(x), 0.0, 1.0)
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample. Range (min … max): 21.042 μs … 1.635 ms ┊ GC (min … max): 0.00% … 98.18% Time (median): 23.792 μs ┊ GC (median): 0.00% Time (mean ± σ): 24.184 μs ± 24.908 μs ┊ GC (mean ± σ): 2.98% ± 4.16% ▃▄▄▄ ▁█▃▁ ▁▁▄████▇▅▆▄▂▂▂▂▂▂▂▂▄▆████▇▆▃▂▂▁▁▁▁▁▁▂▂▂▂▂▂▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁ ▃ 21 μs Histogram: frequency by time 29.2 μs < Memory estimate: 61.17 KiB, allocs estimate: 3914.
@benchmark integrate(x->tanh(x), 0.0, 1.0)
BenchmarkTools.Trial: 10000 samples with 9 evaluations per sample. Range (min … max): 2.852 μs … 263.602 μs ┊ GC (min … max): 0.00% … 98.12% Time (median): 3.074 μs ┊ GC (median): 0.00% Time (mean ± σ): 3.172 μs ± 3.379 μs ┊ GC (mean ± σ): 2.22% ± 3.11% ▁▁▇▃█▅ ▁ ▁ ▁▂▂██████▆▅▃▂▂▂▁▂▁▁▂▂▃▃▄▃▅▄▃▄▃▃▄██▆█▆▇▃▃▂▂▁▁▁▁▁▁▁▁▂▂▂▂▂▂▂▂▁ ▃ 2.85 μs Histogram: frequency by time 3.55 μs < Memory estimate: 5.42 KiB, allocs estimate: 346.
compare to Romberg Integration¶
function romberg(f, a, b; Max=25, tol=1E-12)
Rmat = Matrix{Float64}(undef, Max, Max) # Avoids zero-initialization
Rmat[1, 1] = 0.5 * (b - a) * (f(a) + f(b))
estim = Rmat[1, 1]
for mm in 2:Max
# Compute next trapezoidal estimate without redundant calculations
dx = (b - a) / 2^(mm - 1)
inside = dx * sum(f(a + (2 * kk - 1) * dx) for kk in 1:2^(mm - 2))
Rmat[mm, 1] = 0.5 * Rmat[mm - 1, 1] + inside
# Perform Richardson extrapolation
for nn in 2:mm
Rmat[mm, nn] = (4^(nn - 1) * Rmat[mm, nn - 1] - Rmat[mm - 1, nn - 1]) / (4^(nn - 1) - 1)
end
# Convergence check
if abs(Rmat[mm, mm] - Rmat[mm - 1, mm - 1]) < tol * abs(Rmat[mm, mm]+1)
return Rmat[mm, mm]
end
end
@warn "Warning: Maximum iterations ($Max) reached. Result may not be accurate."
return Rmat[Max, Max]
end
romberg (generic function with 1 method)
println([romberg(x->x*log(1+x), 0.0, 1.0), 0.25])
@benchmark ret1 = romberg(x->x*log(1+x), 0.0, 1.0), 0.25
[0.25000000000000006, 0.25]
BenchmarkTools.Trial: 10000 samples with 201 evaluations per sample. Range (min … max): 430.348 ns … 21.269 μs ┊ GC (min … max): 0.00% … 97.58% Time (median): 512.025 ns ┊ GC (median): 0.00% Time (mean ± σ): 776.775 ns ± 969.840 ns ┊ GC (mean ± σ): 31.41% ± 22.11% ██▆▅▂ ▂▁▁ ▂ ██████▆▆▅▅▅▄▄▄▁▃▃▄▄▃▃▁▃▁▁▁▁▁▁▁▁▁▃▁▆▅▆▆▆▇█████▇█▇▆▇▇▇▆▅▇▆▆▆▆▆▆ █ 430 ns Histogram: log(frequency) by time 4.7 μs < Memory estimate: 5.08 KiB, allocs estimate: 3.