numerical integration

In first year calculus the definite integral of a function \(f(x)\) over the interval \([a, b]\) is defined to be the limit of a sequence of Riemann sums: \[ \int_a^b f(x)\, dx = \lim_{n \to \infty} \sum_{i=0}^{n-1} f(x_i) \, \Delta x \] where \(\Delta x = (b - a)/n\) and \(x_i = a + i \cdot \Delta x\) - So the definite integral can be approximated using Riemann sums for suitably large values of \(n\)

coding Riemann sums in numpy

import numpy as np
def num_int(f, a, b, n):
   dx = (b-a)/n
   x = np.arange(a, b, step = dx)
   y = f(x)
   return y.sum() * dx

Note this takes a function f as input.

example

Try approximating \(\int_0^1 x^2 \, dx\) with \(n=\{10,50,5000\}\)

def x_squared(x):
   return x ** 2
approx1 = num_int(x_squared, 0, 1, 10)
approx2 = num_int(x_squared, 0, 1, 50)
approx3 = num_int(x_squared, 0, 1, 5000)
print(approx1, approx2, approx3)
## 0.2850000000000001 0.3234 0.33323334

We could create an adaptive version of this that keeps trying larger values of n until the results are “close enough”

refinements

\[ \begin{split} \int_a^b f(x)\, dx & = \lim_{n \to \infty} \sum_{i=0}^{n-1} f(x_{i+1}) \, \Delta x \qquad \textrm{right endpoint} \\ \int_a^b f(x)\, dx & = \lim_{n \to \infty} \sum_{i=0}^{n-1} f\left((x_i+x_{i+1})/2\right) \, \Delta x \qquad \textrm{midpoint} \end{split} \]

other rules

We’ll write a function that implements any of these rules, and check it with \(\int_0^1 x^2 \, dx\) and \(\int_0^1 x^3 \, dx\).

symbolic integration

from sympy import *
x = Symbol('x')
integrate(x**2,x) ## no "plus a constant"
## x**3/3

Or definite integrals:

integrate(x**2,(x,0,1))
## 1/3

But:

integrate(log(x)**x,x)
## Integral(exp(x*log(log(x))), x)

(see Wikipedia for more information on symbolic integration)

area of a circle

integrate(sqrt(1-x**2),x)
## x*sqrt(-x**2 + 1)/2 + asin(x)/2
integrate(sqrt(1-x**2),(x,0,1))
## pi/4

Monte Carlo integration

import matplotlib.pyplot as plt
import numpy.random as npr
x = np.linspace(0,1,101)
y = np.sqrt(1-x**2)
fig, ax = plt.subplots()
ax.plot(x,y)
xr = npr.uniform(size=200)
yr = npr.uniform(size=200)
incirc = xr**2+yr**2<1
ax.plot(xr[incirc],yr[incirc],"b*")
ax.plot(xr[np.logical_not(incirc)],yr[np.logical_not(incirc)],"ro")

continuing

from math import pi
incirc.mean()*4/pi
## 0.884901483590938

another example

Overlap of circles at (1.5,2.5) (radius 1), (4,3) (radius 3), (1,2) (radius 2)

import numpy.random as npr
import numpy as np
c = ((2,2.5),(4,3),(1,2))
r = (1.5,3,2)
npr.seed(101)
x = npr.uniform(c[0][0]-r[0],c[0][0]+r[0],size=1000000)
y = npr.uniform(c[0][1]-r[0],c[0][1]+r[0],size=1000000)
tests = np.tile(True,10**6)  ## boolean vector
def incirc(x,y,ctr,radius):
    dsq = (x-ctr[0])**2+(y-ctr[1])**2
    return(dsq<radius**2)
for c0,r0 in zip(c,r):       ## zip() rearranges lists
    tests = tests & incirc(x,y,c0,r0)
    print(np.mean(tests))
## 0.78613
## 0.658081
## 0.48976
print(r[0]**2*np.mean(tests))
## 1.10196

More on random number generation

(Pseudo)random numbers

linear congruential generators

Simple example:

x = [5]         ## starting value
a,c,m = 2,3,10  ## constants
for i in range(9):
    newx = (a*x[-1]+c) % m
    x.append(newx)
print(x)
## [5, 3, 9, 1, 5, 3, 9, 1, 5, 3]

Park-Miller minimal standard generator

a,c,m = 16807,0,2147483647
x = [5]
for i in range(9):
    newx = (a*x[-1]+c) % m
    x.append(newx)
print(np.array(x)/m)
## [2.32830644e-09 3.91318463e-05 6.57688941e-01 7.78026611e-01
##  2.93250660e-01 6.63836187e-01 9.47959316e-02 2.35223081e-01
##  3.94323584e-01 3.96482029e-01]

run for longer

random values

import numpy.random as npr
a = npr.rand(1000)

Conway’s game of life

Rules:

pieces

examples

import os
import matplotlib.pyplot as plt
os.chdir("../code")
from life import *
os.chdir("../notes")
w = life_init()
fig, ax = plt.subplots()
life_show(w, ax)
fig.savefig("pix/life1.png")
nw = w.copy()
(nw, w) = life_step(w,nw)
life_show(w, ax)
fig.savefig("pix/life2.png")

fig, ax = plt.subplots(5,5)
for i in range(ax.shape[0]):
    for j in range(ax.shape[1]):
        nw, w = life_step(w,nw)
        life_show(w, ax[i][j])
fig.savefig("pix/life3.png")

fig, ax = plt.subplots(5,5)
for i in range(ax.shape[0]):
    for j in range(ax.shape[1]):
        for k in range(10):
            life_show(w, ax[i][j])
            nw, w = life_step(w,nw)
fig.savefig("pix/life4.png")