- Numerical Methods
- Numerical Integration with SciPy
- Tabulated Integrand
- Multiple Integration
- Symbolic & Arbitrary-Precision Integration
- Line Integrals
- Integral Transforms
- Main focus: numerical integration (aka "quadrature").
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib as mplimport numpy as np
from scipy import integrate
import sympy
sympy.init_printing()- Definite integrals - interpreted as area between integrand curve and x axis:
- Strategy for evaluating integral I(f): write it as discrete sum that approximates value of integral. ("n-point quadrature rule".)
- Quadrature rules derived from f(x) interpolations along interval [a,b].
- Midpoint rule: using 0th-order polynomial (constant value) of midpoint.
- Trapezoid rule: using 1st-order polynomial, evalated at endpoints.
- Simpson's rule: using 2nd-order polynomial, midpoint & endpoints.
a, b, X = sympy.symbols("a, b, x")
f = sympy.Function("f")x = a, (a+b)/2, b # simpson's rule# weight factors
w = [sympy.symbols("w_%d" % i)
for i in range(len(x))]
w# quadrature rule
q_rule = sum([w[i] * f(x[i])
for i in range(len(x))])
q_rule# using lambda to create symbolics for basis functions
phi = [sympy.Lambda(X, X**n)
for n in range(len(x))]
phieqs = [q_rule.subs(f, phi[n])
- sympy.integrate(phi[n](X), (X, a, b))
for n in range(len(phi))]
eqs# solves for weight factors
w_sol = sympy.solve(eqs, w); w_solq_rule.subs(w_sol).simplify()- Two types of quadrature routines:
-
accepts integrand as Python function using a Gaussian quadrature - quad(), quadrature(), fixed_quad())
-
Newton-Cotes methods: accepts arrays of integrand samples at given points - trapz(), simps(), romb())
def f(x):
return np.exp(-x**2)# integrate.quad(function, upper limit, lower limit)
# return estimated integral & abs error
# error tolerances can be set using epsabs, epsrel keywords
val, err = integrate.quad(f, -1, 1)
val, errdef f(x, a, b, c):
return a * np.exp(-((x-b)/c)**2)val, err = integrate.quad(f, -1, 1, args=(1, 2, 3))
val, errfrom scipy.special import jv # zeroth-order bessel functionval, err = integrate.quad(lambda x: jv(0, x), 0, 5)
val, errf = lambda x: np.exp(-x**2)val, err = integrate.quad(f, -np.inf, np.inf)
val, err- quadrature() & fixed_quad() functions only support finite integration limits.
f = lambda x: 1/np.sqrt(abs(x))a, b = -1, 1# this integral diverges at x=0. quad() can't handle it.
integrate.quad(f, a, b)/tmp/ipykernel_65767/525825373.py:1: RuntimeWarning: divide by zero encountered in scalar divide
f = lambda x: 1/np.sqrt(abs(x))
/tmp/ipykernel_65767/14331687.py:2: IntegrationWarning: The maximum number of subdivisions (50) has been achieved.
If increasing the limit yields no improvement it is advised to analyze
the integrand in order to determine the difficulties. If the position of a
local difficulty can be determined (singularity, discontinuity) one will
probably gain from splitting up the interval and calling the integrator
on the subranges. Perhaps a special-purpose integrator should be used.
integrate.quad(f, a, b)
# to avoid problem points, use points keyword
integrate.quad(f, a, b, points=[0])# visualize to see what's going on
fig, ax = plt.subplots(figsize=(8, 3))
x = np.linspace(a, b, 10000)
ax.plot(x, f(x), lw=2)
ax.fill_between(x, f(x), color='green', alpha=0.5)
ax.set_xlabel("$x$", fontsize=18)
ax.set_ylabel("$f(x)$", fontsize=18)
ax.set_ylim(0, 25)
fig.tight_layout()
fig.savefig("ch8-diverging-integrand.pdf")- use case: integrand that is specified only at predetermined points. (ie, not using a Python function that can be evaluated at any point.)
f = lambda x: np.sqrt(x)a, b = 0, 2x = np.linspace(a, b, 25)y = f(x)fig, ax = plt.subplots(figsize=(8, 3))
ax.plot(x, y, 'bo')
xx = np.linspace(a, b, 500)
ax.plot(xx, f(xx), 'b-')
ax.fill_between(xx, f(xx), color='green', alpha=0.5)
ax.set_xlabel(r"$x$", fontsize=18)
ax.set_ylabel(r"$f(x)$", fontsize=18)
fig.tight_layout()
fig.savefig("ch8-tabulated-integrand.pdf")# trapezoid method
val_trapz = integrate.trapz(y, x)
val_trapz# simpson's rule
val_simps = integrate.simps(y, x)
val_simps# find error estimates by computing integral & comparising to above estimate
val_exact = 2.0/3.0 * (b-a)**(3.0/2.0)
val_exact, (val_exact-val_trapz), (val_exact-val_simps)- We can't ask trapz or simps to find more accurate answers unless more samples are given, or a higher-order method is used.
- romb() function helps -- implementation of Romberg method
x = np.linspace(a, b, 1 + 2**6)
len(x)y = f(x)val_exact - integrate.romb(y, dx=(x[1]-x[0]))val_exact - integrate.simps(y, dx=x[1]-x[0])- dblquad and tplquad functions available in SciPy.
def f(x):
return np.exp(-x**2)%time integrate.quad(f, a, b)CPU times: user 37 µs, sys: 21 µs, total: 58 µs
Wall time: 60.1 µs
def f(x, y):
return np.exp(-x**2-y**2)a, b = 0, 1g = lambda x: 0
h = lambda x: 1integrate.dblquad(f, a, b, g, h)integrate.dblquad(
lambda x, y: np.exp(-x**2-y**2),
0, 1,
lambda x: 0,
lambda x: 1)fig, ax = plt.subplots(figsize=(6, 5))
x = y = np.linspace(-1.25, 1.25, 75)
X, Y = np.meshgrid(x, y)
c = ax.contour(X, Y, f(X, Y), 15, cmap=mpl.cm.RdBu, vmin=-1, vmax=1)
bound_rect = plt.Rectangle((0, 0), 1, 1,
facecolor="grey")
ax.add_patch(bound_rect)
ax.axis('tight')
ax.set_xlabel('$x$', fontsize=18)
ax.set_ylabel('$y$', fontsize=18)
fig.tight_layout()
fig.savefig("ch8-multi-dim-integrand.pdf")# because g&h are functions,
# we can find integrals with x-dependent integration limits along y.
integrate.dblquad(
f,
0, 1,
lambda x: -1+x,
lambda x: 1-x)def f(x, y, z):
return np.exp(-x**2 - y**2 - z**2)integrate.tplquad(f, 0, 1,
lambda x : 0,
lambda x : 1,
lambda x, y : 0,
lambda x, y : 1)integrate.nquad(f, [(0, 1), (0, 1), (0, 1)])- Computational complexity grows very quickly.
def f(*args):
return np.exp(-np.sum(np.array(args)**2))%time integrate.nquad(f, [(0,1)] * 1)CPU times: user 110 µs, sys: 60 µs, total: 170 µs
Wall time: 173 µs
%time integrate.nquad(f, [(0,1)] * 2)CPU times: user 1.55 ms, sys: 0 ns, total: 1.55 ms
Wall time: 1.55 ms
%time integrate.nquad(f, [(0,1)] * 3)CPU times: user 40.2 ms, sys: 134 µs, total: 40.3 ms
Wall time: 40.3 ms
%time integrate.nquad(f, [(0,1)] * 4)CPU times: user 593 ms, sys: 1.2 ms, total: 594 ms
Wall time: 593 ms
%time integrate.nquad(f, [(0,1)] * 5)CPU times: user 11.4 s, sys: 2.3 ms, total: 11.4 s
Wall time: 11.4 s
x = sympy.symbols("x")f = 2 * sympy.sqrt(1-x**2)a, b = -1, 1sympy.plot(f, (x, -2, 2));val_sym = sympy.integrate(f, (x, a, b))
val_sym- alternate method of numerical quadrature using arbitrary-precisin math
- much slower than floating-point math, but better precision than SciPy quadrature funcs.
import mpmath as mpmathmpmath.mp.dps = 75 # number of digits of precision# need mpmath-compatible python function as integrand.
f_mpmath = sympy.lambdify(x, f, 'mpmath')# compute integral & display result
val = mpmath.quad(f_mpmath, (a, b))
sympy.sympify(val)# compare result with known value. error sufficiently small?
sympy.N(val_sym, mpmath.mp.dps+1) - val%timeit mpmath.quad(f_mpmath, [a, b])2.27 ms ± 501 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
f_numpy = sympy.lambdify(x, f, 'numpy')%timeit integrate.quad(f_numpy, a, b)242 µs ± 54.3 µs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
- 1st argument = integrand (symPy expression)
- 2nd argument = symPy Curve object
- 3rd argument = list of integration variables
t, x, y = sympy.symbols("t, x, y")C = sympy.Curve([sympy.cos(t), sympy.sin(t)], (t, 0, 2 * sympy.pi))sympy.line_integrate(1, C, [x, y])sympy.line_integrate(x**2 * y**2, C, [x, y])- integral transform: accepts a function as input, returns another function.
- best when computed symbolically
- two examples: laplace (differential equation into algebraic equation) and fourier (time domain to frequency domain).
s = sympy.symbols("s")
a, t = sympy.symbols("a, t", positive=True)
f = sympy.sin(a*t)# compute transform
# returns tuple: result transform, value 'A' from convergence condition, any add'l conditions.
sympy.laplace_transform(f, t, s)# if you want just the result function:
F = sympy.laplace_transform(f, t, s, noconds=True); F# inverse transform (swap 's','t' arguments)
sympy.inverse_laplace_transform(F, s, t, noconds=True)# sympy can find transforms for many elementary functions automatically
# ex: laplace transforms of polynomials
[sympy.laplace_transform(f, t, s, noconds=True)
for f in [t, t**2, t**3, t**4]]# computing general results with arbitrary integer exponents
n = sympy.symbols(
"n", integer=True, positive=True)
sympy.laplace_transform(t**n, t, s, noconds=True)# laplace xform of composite expression
sympy.laplace_transform(
(1 - a*t) * sympy.exp(-a*t), t, s, noconds=True)- available functions: fourier_transform(), inverse_fourier_transform()
w = sympy.symbols("omega")f = sympy.exp(-a*t**2)F = sympy.fourier_transform(f, t, w); Fsympy.inverse_fourier_transform(F, w, t)



