* Line integrals in Python with autograd
:PROPERTIES:
:categories: autograd,python,integration
:date: 2018/11/16 08:39:44
:updated: 2018/11/16 08:39:44
:org-url: http://kitchingroup.cheme.cmu.edu/org/2018/11/16/Line-integrals-in-Python-with-autograd.org
:permalink: http://kitchingroup.cheme.cmu.edu/blog/2018/11/16/Line-integrals-in-Python-with-autograd/index.html
:END:
A line integral is an integral of a function along a curve in space. We usually represent the curve by a parametric equation, e.g. $\mathbf{r}(t) = [x(t), y(t), z(t)] = x(t)\mathbf{i} + y(t)\mathbf{j} + z(t)\mathbf{k}$. So, in general the curve will be a vector function, and the function we want to integrate will also be a vector function.
Then, we can write the line integral definition as:
$\int_C \mathbf{F(r)}\cdot d\mathbf{r} = \int_a^b \mathbf{F}({\mathbf{r}(t)) \cdot \mathbf{r'}(t) dt$ where $\mathbf{r'}(t) = \frac{d\mathbf{r}}{dt}$. This integrand is a scalar function, because of the dot product.
The following examples are adapted from Chapter 10 in Advanced Engineering Mathematics by Kreysig.
The first example is the evaluation of a line integral in the plane. We want to evaluate the integral of $\mathbf{F(r)}=[-y, -xy]$ on the curve $\mathbf{r(t)}=[-sin(t), cos(t)]$ from t=0 to t = \pi/2. The answer in the book is given as 0.4521. Here we evaluate this numerically, using autograd for the relevant derivative. Since the curve has multiple outputs, we have to use the jacobian function to get the derivatives. After that, it is a simple bit of matrix multiplication, and a call to the quad function.
#+BEGIN_SRC ipython
import autograd.numpy as np
from autograd import jacobian
from scipy.integrate import quad
def F(X):
x, y = X
return -y, -x * y
def r(t):
return np.array([-np.sin(t), np.cos(t)])
drdt = jacobian(r)
def integrand(t):
return F(r(t)) @ drdt(t)
I, e = quad(integrand, 0.0, np.pi / 2)
print(f'The integral is {I:1.4f}.')
#+END_SRC
#+RESULTS:
:RESULTS:
# Out[27]:
# output
: The integral is 0.4521.
:
:END:
We get the same result as the analytical solution.
The next example is in three dimensions. Find the line integral along $\mathbf{r}(t)=[cos(t), sin(t), 3t]$ of the function $\mathbf{F(r)}=[z, x, y]$ from t=0 to t=2 \pi. The solution is given as 21.99.
#+BEGIN_SRC ipython
import autograd.numpy as np
from autograd import elementwise_grad, grad, jacobian
def F(X):
x, y, z = X
return [z, x, y]
def C(t):
return np.array([np.cos(t), np.sin(t), 3 * t])
dCdt = jacobian(C, 0)
def integrand(t):
return F(C(t)) @ dCdt(t)
I, e = quad(integrand, 0, 2 * np.pi)
print(f'The integral is {I:1.2f}.')
#+END_SRC
#+RESULTS:
:RESULTS:
# Out[29]:
# output
: The integral is 21.99.
:
:END:
That is also the same as the analytical solution. Note the real analytical solution was 7 \pi, which is nearly equivalent to our answer.
#+BEGIN_SRC ipython
7 * np.pi - I
#+END_SRC
#+RESULTS:
:RESULTS:
# Out[30]:
# text/plain
: 3.552713678800501e-15
:END:
As a final example, we consider an alternate form of the line integral. In this form we do not use a dot product, so the integral results in a vector. This doesn't require anything from autograd, but does require us to be somewhat clever in how to do the integrals since quad can only integrate scalar functions. We need to integrate each component of the integrand independently. Here is one approach where we use lambda functions for each component. You could also manually separate the components.
#+BEGIN_SRC ipython
def F(r):
x, y, z = r
return x * y, y * z, z
def r(t):
return np.array([np.cos(t), np.sin(t), 3 * t])
def integrand(t):
return F(r(t))
[quad(lambda t: integrand(t)[i], 0, 2 * np.pi)[0] for i in [0, 1, 2]]
#+END_SRC
#+RESULTS:
:RESULTS:
# Out[37]:
# text/plain
: [-6.9054847581172525e-18, -18.849555921538755, 59.21762640653615]
:END:
The analytical solution in this case was given as:
#+BEGIN_SRC ipython
[0, -6 * np.pi, 6 * np.pi**2]
#+END_SRC
#+RESULTS:
:RESULTS:
# Out[34]:
# text/plain
: [0, -18.84955592153876, 59.21762640653615]
:END:
which is evidently the same as our numerical solution.
Maybe an alternative, but more verbose is this vectorized integrate function. We still make temporary functions for integrating, and the vectorization is essentially like the list comprehension above, but we avoid the lambda functions.
#+BEGIN_SRC ipython
@np.vectorize
def integrate(i):
def integrand(t):
return F(r(t))[i]
I, e = quad(integrand, 0, 2 * np.pi)
return I
integrate([0, 1, 2])
#+END_SRC
#+RESULTS:
:RESULTS:
# Out[38]:
# text/plain
: array([ -6.90548476e-18, -1.88495559e+01, 5.92176264e+01])
:END:
** Summary
Once again, autograd provides a convenient way to compute function jacobians which make it easy to evaluate line integrals in Python.