Download or view NumericalIntegrationArbitrary.frink in plain text format
use ArbitraryPrecision.frink
/** Routines for numerical integration.
See:
https://en.wikipedia.org/wiki/Simpson%27s_rule
https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method
https://web.mit.edu/10.001/Web/Course_Notes/Integration_Notes/numerical_integration.pdf
See examples and tests in NumericalIntegrationTest.frink
*/
/** Numerically integrate function f from a to b. */
NIntegrate[f, a, b, eps=1e-14] :=
{
return EngelenIntegrator.NIntegrate[f, a, b, eps, 6]
//NIntegrateAdaptiveSimpson[f, a, b, eps, 30, undef]
}
/** Numerically integrate function f[x, data] from a to b. data is auxiliary
data (e.g. constant parameters) that will be passed to the function. */
NIntegrateData[f, a, b, data, eps=1e-14, steps=6] :=
{
return EngelenIntegrator.NIntegrateData[f, a, b, data, eps, steps]
//return NIntegrateAdaptiveSimpson[f, a, b, eps, 30, data]
}
/** Integrate the function from a to b with the specified number of steps. */
NIntegrateCompositeTrapezoid[f, a, b, steps, data] :=
{
if data == undef
{
fa = f[a]
fb = f[b]
} else
{
fa = f[a, data]
fb = f[b, data]
}
h = (b-a)/steps
integral = (fa + fb) / 2 // TODO: Make this work with dates
xi = a
for i = 1 to steps-1
{
xi = xi + h
if data == undef
fxi = f[xi]
else
fxi = f[xi, data]
integral = integral + fxi
}
return integral * h
}
/** Integrate the function from a to b with the specified number of steps using
the composite Simpson's 1/3 rule. Simpson's rule is exact when the
integrand is a polynomial of degree 3 or lower.
See:
https://en.wikipedia.org/wiki/Simpson%27s_rule#Composite_Simpson's_1/3_rule
The error is proportional to
-1/180 h^4 (b-a) D[f, 4]
that is, proportional to the 4th derivative of f. h = (b-a)/steps
*/
NIntegrateCompositeSimpson13[f, a, b, steps, data] :=
{
// Make sure steps is even
if steps mod 2 == 1
steps = steps + 1
h = (b-a)/steps
if data == undef
{
fa = f[a]
fb = f[b]
} else
{
fa = f[a, data]
fb = f[b, data]
}
integral = fa + fb
xi = a
integral1 = 0 integral
for i = 1 to steps div 2
{
x2iminus1 = a + (2i-1) h
if data == undef
ff = f[x2iminus1]
else
ff = f[x2iminus1, data]
integral1 = integral1 + ff
}
integral2 = 0 integral
for i = 1 to steps div 2 - 1
{
x2i = a + (2 i) h
if data == undef
ff = f[x2i]
else
ff = f[x2i, data]
integral2 = integral2 + ff
}
integral = integral + 4 integral1 + 2 integral2
return 1/3 h integral
}
/** Adaptive Simpson's method.
see https://en.wikipedia.org/wiki/Adaptive_Simpson%27s_method
f: function to integrate
a, b: limits of integration
epsilon: maximum error
maxRecDepth: maximum recursion depth
*/
NIntegrateAdaptiveSimpson[f, a, b, epsilon, maxRecDepth, data] :=
{
h = b - a
if h == 0 h
return 0 h
if data == undef
{
fa = f[a]
fb = f[b]
fm = f[(a + b)/2]
} else
{
fa = f[a, data]
fb = f[b, data]
fm = f[(a + b)/2, data]
}
S = (h/6)*(fa + 4*fm + fb)
eps = epsilon factorUnits[b]@1 factorUnits[fb]@1 // Make units come out right
return adaptiveSimpsonRec[f, a, b, eps, S, fa, fb, fm, maxRecDepth, data]
}
/** Adaptive Simpson's Rule, private recursive function */
adaptiveSimpsonRec[f, a, b, eps, whole, fa, fb, fm, rec, data] :=
{
m = (a + b)/2
h = (b - a)/2
lm = (a + m)/2
rm = (m + b)/2
// serious numerical trouble: it won't converge
if ((eps/2 == eps) or (a == lm))
{
println["Serious numerical trouble in integration. eps is $eps"]
return whole;
}
if data == undef
{
flm = f[lm]
frm = f[rm]
} else
{
flm = f[lm, data]
frm = f[rm, data]
}
left = (h/6) * (fa + 4*flm + fm)
right = (h/6) * (fm + 4*frm + fb)
delta = left + right - whole
if (rec <= 0)
{
println["Recursive integration limit too shallow between $a and $b."]
}
// println["Got here. delta is $delta and eps is $eps"]
// Lyness 1969 + Richardson extrapolation; see article
if rec <= 0 or abs[delta] <= 15*eps
return left + right + delta/15
return adaptiveSimpsonRec[f, a, m, eps/2, left, fa, fm, flm, rec-1, data] +
adaptiveSimpsonRec[f, m, b, eps/2, right, fm, fb, frm, rec-1, data]
}
/** Convenience method to call the class below. */
NIntegrateTanh[f, a, b, eps=1e-14, debug=false] :=
{
TanhSinhIntegrator.NIntegrate[f, a, b, eps, debug]
}
/** Numerical integrator using the tanh-sinh quadrature.
See:
Takahasi, Hidetosi; Mori, Masatake (1974),
"Double Exponential Formulas for Numerical Integration",
Publications of the Research Institute for Mathematical Sciences,
9 (3): 721–741
https://www.ems-ph.org/journals/show_pdf.php?issn=0034-5318&vol=9&iss=3&rank=12
The algorithm has been additionally improved by Michashki & Mosig (2016):
https://www.ingentaconnect.com/content/tandf/jew/2016/00000030/00000003/art00001;jsessionid=55mdimh9e060a.x-ic-live-03
and Van Engelen (2022):
https://www.genivia.com/files/qthsh.pdf
(see especially page 24 if you're going to implement it with all the
improvements or, better yet, page 33 if you want to implement infinite
endpoints.)
This implementation is based on:
https://github.com/Proektsoftbg/Numerical/blob/main/Numerical/Integrator/TanhSinh.cs
*/
class TanhSinhIntegrator
{
// Precalculated abcissae and weights from the Van Engelen implementation
class var n = 11
class var m = [ 6, 7, 13, 26, 53, 106, 212, 423, 846, 1693, 3385 ]
class var r = new array[n]
class var w = new array[n]
class var initialized = TanhSinhIntegrator.initialize[]
/** Numerically integrate function f from x1 to x2 and the specified
precision. */
class NIntegrate[f, x1, x2, precision = 1e-14, debug=false] :=
{
// Friendly correction to limits of integration
signCorrection = 1
if x1 > x2
[x1,x2,signCorrection] = [x2, x1, -1] // Flip sign
ic = 1 // Iteration count
c = (x1 + x2) / 2
d = (x2 - x1) / 2 // Half-width of bounds of integration
s = f[c]
err = undef
i = 0
eps = clamp[precision * 0.1, 1e-15, 1e-8]
tol = 10 precision
do
{
q = 0
p = 0
fp = 0
fm = 0
j = 0
do
{
x = r@i@j * d
if (x1 + x > x1)
{
y = f[x1 + x]
ic = ic + 1
fp = y
}
if (x2 - x < x2)
{
y = f[x2 - x]
ic = ic + 1
fm = y
}
q = w@i@j * (fp + fm)
p = p + q
j = j + 1
} while (abs[q] > eps * abs[p] and j < m@i)
err = 2 s
s = s + p
err = abs[err - s]
i = i + 1
} while (err > tol * abs[s] and i < n)
// When the integral is smaller than precision, the absolute error is
// evaluated
if abs[s] > 1
err = err / abs[s]
if debug
println["err is $err, iteration count is $ic"]
return d * s * 2^(1-i) signCorrection
}
/** Initialize the abcissae and weights */
class initialize[] :=
{
h = 2
for i = 0 to n-1
{
h = h / 2
eh = exp[h]
t = eh
r@i = new array[m@i]
w@i = new array[m@i]
if i > 0
eh = eh * eh
for j= 0 to m@i - 1
{
t1 = 1 / t
u = exp[t1 - t]
u1 = 1 / (1 + u)
d = 2 * u * u1
if d == 0
break
r@i@j = d
w@i@j = (t1 + t) * d * u1
t = t * eh
}
}
return true
}
}
/** This class integrates numerically using the advanced algorithm in
Improving the Double Exponential Quadrature Tanh-Sinh, Sinh-Sinh and
Exp-Sinh Formulas, Dr. Robert A. van Engelen, Genivia Labs,
https://www.genivia.com/files/qthsh.pdf
*/
class EngelenIntegrator
{
class var debug = false
// integrate function f, range a..b, max steps, error tolerance eps
class NIntegrate[f, a, b, eps=1e-14, steps=6, data=undef] :=
{
tol = 10 eps
c = 0
d = 1
h = 2
sign = 1
v = undef
k = 0
mode = 0 // Tanh-Sinh = 0, Exp-Sinh = 1, Sinh-Sinh = 2
if isfinite[a] and isfinite[b] and b < a // Friendly swap of bounds
{
v = b
[a,b,sign] = [b,a,-1]
}
if isfinite[a] and isfinite[b]
{
// Both a and b are finite, mode 0
c = (a+b)/2
d = (b-a)/2
v = c
} else
if isfinite[a]
{
// a is finite, b is +infinity
mode = 1 // Exp-Sinh
// alternatively d = exp_sinh_opt_d(f, a, eps, d)
d = exp_sinh_opt_d[f, a, eps, d, data]
c = a
v = a+d
} else
if isfinite[b]
{
// a is -infinity, b is finite
mode = 1 // Exp-Sinh
//d = -d // alternatively d = exp_sinh_opt_d(f, b, eps, -d)
d = exp_sinh_opt_d[f, b, eps, -d, data]
sign = -sign
c = b
v = b+d
} else
{
// Both a and b are infinite
mode = 2 // Sinh-Sinh
v = 0
}
// println["got here, mode=$mode, v=$v"]
if data == undef
s = applySafe[f, v]
else
s = applySafe[f, [v,data]]
// println["and here, s=$s"]
if ! isfinite[s]
{
println["Error: this integral cannot be peformed because its center point $v is infinite."]
// Try to do it as the sum of two one-sided integrals.
return EngelenIntegrator.NIntegrate[f, a, v, eps, steps, data] + EngelenIntegrator.NIntegrate[f, v, b, eps, steps, data]
}
do
{
p = 0
fp = 0
fm = 0
h = h/2
t = eh = arbitraryExp[h]
if k > 0
eh = eh * eh
if mode == 0
{
// Tanh-Sinh
do
{
u = arbitraryExp[1/t-t] // = exp(-2*sinh(j*h)) = 1/exp(sinh(j*h))^2
r = 2*u/(1+u) // = 1 - tanh(sinh(j*h))
w = (t+1/t)*r/(1+u) // = cosh(j*h)/cosh(sinh(j*h))^2
x = d*r
if (a+x > a)
{
// if too close to a then reuse previous fp
arg = a+x
if data == undef
y = applySafe[f, arg]
else
y = applySafe[f, [arg, data]]
if isfinite[y]
fp = y // if f(x) is finite, add to local sum
}
if b-x < b
{
// if too close to a then reuse previous fp
arg = b-x
if data == undef
y = applySafe[f, arg]
else
y = applySafe[f, [arg, data]]
if isfinite[y]
fm = y // if f(x) is finite, add to local sum
}
//println["w=$w, fp=$fp, fm=$fm"]
q = w*(fp+fm)
p = p+q
t = t * eh
//println["First loop, q=$q, p=$p"]
} while abs[q] > eps*abs[p]
} else
{
t = t / 2
do
{
r = arbitraryExp[t-(1/4)/t] // = exp(sinh(j*h))
w = r
q = 0
if mode == 1
{
// Exp-Sinh
x = c + d/r
if (x == c) // if x hit the finite endpoint then break
break
if data == undef
y = applySafe[f, x]
else
y = applySafe[f, [x, data]]
if isfinite[y] // if f(x) is finite, add to local sum
q = q + y/w
} else
{
// Sinh-Sinh
r = (r-1/r)/2 // = sinh(sinh(j*h))
w = (w+1/w)/2 // = cosh(sinh(j*h))
x = c - d*r
if data == undef
y = applySafe[f, x]
else
y = applySafe[f, [x, data]]
if isfinite[y] // if f(x) is finite, add to local sum
q = q + y*w
}
x = c + d*r
if data == undef
y = applySafe[f, x]
else
y = applySafe[f, [x, data]]
if isfinite[y] // if f(x) is finite, add to local sum
q = q + y*w
q = q * (t+(1/4)/t) // q *= cosh(j*h)
p = p + q
t = t * eh
//println["q=$q, p=$p"]
} while abs[q] > eps*abs[p]
}
v = s - p
s = s + p
k = k + 1
//println["v=$v, s=$s"]
} while abs[v] > tol*abs[s] and k <= steps
err = abs[v]/(abs[s]+eps) // This is estimated error
if debug
{
println["err is $err"]
if err > eps
println["Warning: integration error $err is greater than epsilon $eps. Integration may not have converged. Consider reducing epsilon or taking more steps?"]
}
return sign d s h // result with estimated relative error e
}
class NIntegrateData[f, a, b, data, eps=1e-14, steps=6] :=
{
return NIntegrate[f, a, b, eps, steps, data]
}
// Returns optimized Exp-Sinh integral split point d
class exp_sinh_opt_d[f, a, eps, d, data] :=
{
if data == undef
{
fa = applySafe[f, a + d/2]
fb = applySafe[f, a + d*2]
} else
{
fa = applySafe[f, [a + d/2, data]]
fb = applySafe[f, [a + d*2, data]]
}
h2 = fa - fb * 4
i = 1
j = 32 // j=32 is optimal to find r
if isfinite[h2] and abs[h2] > 1e-5 // if |h2| > 2^-16
{
s = 0
lr = 2
do
{
// find max j such that fl and fr are finite
j = j / 2
r = shiftLeft[1, i + j]
if data == undef
{
fl = applySafe[f, a + d/r]
fr = applySafe[f, a + d*r] r^2
} else
{
fl = applySafe[f, [a + d/r, data]]
fr = applySafe[f, [a + d*r, data]] r^2
}
h = fl - fr
} while j > 1 and !isfinite[h]
if j > 1 and isfinite[h] and realSignum[h] != realSignum[h2]
{
lfl = fl // last fl=f(a+d/r)
lfr = fr // last fr=f(a+d*r)*r*r
do
{
// bisect in 4 iterations
j = j / 2
r = shiftLeft[1, i + j]
if data == undef
{
fl = applySafe[f, a + d/r]
fr = applySafe[f, a + d*r] r^2
} else
{
fl = applySafe[f, [a + d/r, data]]
fr = applySafe[f, [a + d*r, data]] r^2
}
h = fl - fr
if isfinite[h]
{
s = s + abs[h] // sum |h| to remove noisy cases
if realSignum[h] == realSignum[h2]
i = i + j // search right half
else
{
// search left half
lfl = fl // record last fl=f(a+d/r)
lfr = fr // record last fl=f(a+d*r)*r*r
lr = r // record last r
}
}
} while j > 1
if s > eps
{
// if sum of |h| > eps
h = lfl - lfr // use last fl and fr before the sign change
r = lr // use last r before the sign change
if (h != 0) // if last diff != 0, back up r by one step
r = r / 2
if abs[lfl] < abs[lfr]
d = d / r // move d closer to the finite endpoint
else
d = d * r // move d closer to the infinite endpoint
}
}
}
// println["optimized d is $d"]
return d
}
// Determines if a result is infinite for the purposes of integration.
class isfinite[expr] :=
{
if isUnit[expr]
return true
else
return false
}
}
Download or view NumericalIntegrationArbitrary.frink in plain text format
This is a program written in the programming language Frink.
For more information, view the Frink
Documentation or see More Sample Frink Programs.
Alan Eliasen, eliasen@mindspring.com