# Homework 3: Three-Body Problem (20 points)

Group Members: Julius Franke (el442, juliusttf@gmail.com), Erik Meister (kd400, erik.meister@me.com), Eugen Dizer (qo452, eugen9898@web.de)

Due on Friday, 15.05.2020.

```python
#Load standard libraries
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
```

## $4^{th}$ Order Runge-Kutta Method (RK4)

```python
def rk4_step(y0, x0, f, h):
    ''' Simple python implementation for one RK4 step.
        Inputs:
            y_0    - M x 1 numpy array specifying all variables of the ODE at the current time step
            x_0    - current time step
            f      - function that calculates the derivates of all variables of the ODE
            h      - time step size
        Output:
            yp1 - M x 1 numpy array of variables at time step x0 + h
            xp1 - time step x0+h
    '''
    k1 = h * f(y0)
    k2 = h * f(y0 + k1/2)
    k3 = h * f(y0 + k2/2)
    k4 = h * f(y0 + k3)

    xp1 = x0 + h
    yp1 = y0 + 1/6*(k1 + 2*k2 + 2*k3 + k4)

    return(yp1,xp1)

def rk4(y0, x0, f, h, n):
    ''' Simple implementation of RK4
        Inputs:
            y_0    - M x 1 numpy array specifying all variables of the ODE at the current time step
            x_0    - current time step
            f      - function that calculates the derivates of all variables of the ODE
            h      - time step size
            n      - number of steps
        Output:
            yn - N+1 x M numpy array with the results of the integration for every time step (includes y0)
            xn - N+1 x 1 numpy array with the time step value (includes start x0)
    '''
    yn = np.zeros((n+1, y0.shape[0]))
    xn = np.zeros(n+1)
    yn[0,:] = y0
    xn[0] = x0

    for n in np.arange(1,n+1,1):
        yn[n,:], xn[n] = rk4_step(y0 = yn[n-1,:], x0 = xn[n-1], f = f, h = h)

    return(yn, xn)

# Be advised that the integration can take a while for large values of n (e.g >=10^5).
```

## Introduction: The Three-Body Problem

The gravitational three-body problem is described by the Newtonian equations of motion:

\begin{align}
\ddot{\mathbf{r}}_1 &= - G m_2 \frac{\mathbf{r}_1 - \mathbf{r}_2}{\left\vert \mathbf{r}_1 - \mathbf{r}_2 \right\vert^3} - G m_3 \frac{\mathbf{r}_1 - \mathbf{r}_3}{\left\vert \mathbf{r}_1 - \mathbf{r}_3 \right\vert^3} , \\
\ddot{\mathbf{r}}_2 &= - G m_3 \frac{\mathbf{r}_2 - \mathbf{r}_3}{\left\vert \mathbf{r}_2 - \mathbf{r}_3 \right\vert^3} - G m_1 \frac{\mathbf{r}_2 - \mathbf{r}_1}{\left\vert \mathbf{r}_2 - \mathbf{r}_1 \right\vert^3} , \\
\ddot{\mathbf{r}}_3 &= - G m_1 \frac{\mathbf{r}_3 - \mathbf{r}_1}{\left\vert \mathbf{r}_3 - \mathbf{r}_1 \right\vert^3} - G m_2 \frac{\mathbf{r}_3 - \mathbf{r}_2}{\left\vert \mathbf{r}_3 - \mathbf{r}_2 \right\vert^3} ,
\end{align}

where $\mathbf{r}_i = (x_i, y_i)$ is the position vector of the masses $m_i$. To apply the Runge-Kutta Method to solve this system, we transform it to six first order ODEs.

### a) In a first step, set the masses of all three bodies to $m_1 = m_2 = m_3 = 1$ and select the following initial conditions for $y(0)$:

\begin{align}
(y_1, y_2) &= (-0.97000436, 0.24308753) \\
(y_3, y_4) &= (-0.46620368, -0.43236573) \\
(y_5, y_6) &= (0.97000436, -0.24308753) \\
(y_7, y_8) &= (-0.46620368, -0.43236573) \\
(y_9, y_{10}) &= (0.0, 0.0) \\
(y_{11}, y_{12}) &= (0.93240737, 0.86473146)
\end{align}

### Here, $y_{1+4i}$ and $y_{2+4i}$ are the initial coordinates and $y_{3+4i}$ and $y_{4+4i}$ the initial velocities for the objects $i = 0, 1$ and $2$. Try to integrate with a step size $h$ between 0.01 and 0.001 and plot the result.

```python
G = 1
m1 = 1
m2 = 1
m3 = 1

#Initial conditions
y1 = [-0.97000436, 0.24308753]
v1 = [-0.46620368, -0.43236573]
y2 = [0.97000436, -0.24308753]
v2 = [-0.46620368, -0.43236573]
y3 = [0.0, 0.0]
v3 = [0.93240737, 0.86473146]

y0 = np.array(y1 + v1 + y2 + v2 + y3 + v3)

#Define norm of a 2-vector
def norm_of_vector(x):
    return np.sqrt(x[0]**2 + x[1]**2)

#Define derivatives of all variables, when getting the M x 1 array of all variables y
def derivative_of_y1(y):
    v1 = y[2:4]
    return list(v1)

def derivative_of_v1(y):
    y1 = y[0:2]
    y2 = y[4:6]
    y3 = y[8:10]
    return list(-G*m2*(y1 - y2)/norm_of_vector(y1-y2)**3 - G*m3*(y1 - y3)/norm_of_vector(y1-y3)**3)

def derivative_of_y2(y):
    v2 = y[6:8]
    return list(v2)

def derivative_of_v2(y):
    y1 = y[0:2]
    y2 = y[4:6]
    y3 = y[8:10]
    return list(-G*m3*(y2 - y3)/norm_of_vector(y2-y3)**3 - G*m1*(y2 - y1)/norm_of_vector(y2-y1)**3)

def derivative_of_y3(y):
    v3 = y[10:12]
    return list(v3)

def derivative_of_v3(y):
    y1 = y[0:2]
    y2 = y[4:6]
    y3 = y[8:10]
    return list(-G*m1*(y3 - y1)/norm_of_vector(y3-y1)**3 - G*m2*(y3 - y2)/norm_of_vector(y3-y2)**3)

#Define function that calculates the derivates of all variables of the ODE
def f(y):
    return np.array(derivative_of_y1(y) + derivative_of_v1(y) + derivative_of_y2(y) + derivative_of_v2(y) + derivative_of_y3(y) + derivative_of_v3(y))
```

```python
#Choose steps size h
h = 0.005

y, x = rk4(y0, 0, f, h, 500)

pos1_x = []
pos1_y = []
for i in range(len(y)):
    pos1_x.append(y[i][0])
    pos1_y.append(y[i][1])

pos2_x = []
pos2_y = []
for i in range(len(y)):
    pos2_x.append(y[i][4])
    pos2_y.append(y[i][5])

pos3_x = []
pos3_y = []
for i in range(len(y)):
    pos3_x.append(y[i][8])
    pos3_y.append(y[i][9])

#Plot trajectory
plt.title('Three-Body-Solution with $h =$ ' + str(h))
plt.xlabel(r'x')
plt.ylabel(r'y')
plt.plot(pos1_x, pos1_y, linewidth=1.0, linestyle="-", label='m1 = 1')
plt.plot(pos2_x, pos2_y, linewidth=1.0, linestyle="-", label='m2 = 1')
plt.plot(pos3_x, pos3_y, linewidth=1.0, linestyle="-", label='m3 = 1')
plt.legend()
plt.show()
```

### b) Now consider a different problem. Choose the masses of the three bodies to be $m_1 = 3$, $m_2 = 4$ and $m_3 = 5$, and place them at the corners of a right triangle (one angle is $90^{\circ}$) with edge lengths of $l_1 = 3$, $l_2 = 4$ and $l_3 = 5$, such that $m_1$ is opposite to the edge $l_1$, $m_2$ opposite to $l_2$, and $m_3$ opposite to $l_3$. Set the initial velocities to zero. This is the Meissel-Burrau problem. We recommend to place the origin of your coordinate system into the center of mass of the system.

```python
#Initial conditions
m1 = 3
m2 = 4
m3 = 5

#Initial conditions, place the origin of your coordinate system into the center of mass of the system
y1 = [-1, 3]
v1 = [0.0, 0.0]
y2 = [2, -1]
v2 = [0.0, 0.0]
y3 = [-1, -1]
v3 = [0.0, 0.0]

y0 = np.array(y1 + v1 + y2 + v2 + y3 + v3)
```

### Use the Runge-Kutta-4 integrator to follow the time evolution of the system until it dissolves. Record the points in time when two bodies have minimum separation and store this data in a file. Investigate the behavior of the system for different integration steps $h$, starting with $h = 0.1$. How small does $h$ need to be in order to obtain reliable estimates for the time of the first five closest encounters.

```python
#Choose step size h
h = 0.0001

y, x = rk4(y0, 0, f, h, 80000)

pos1_x = []
pos1_y = []
for i in range(len(y)):
    pos1_x.append(y[i][0])
    pos1_y.append(y[i][1])

pos2_x = []
pos2_y = []
for i in range(len(y)):
    pos2_x.append(y[i][4])
    pos2_y.append(y[i][5])

pos3_x = []
pos3_y = []
for i in range(len(y)):
    pos3_x.append(y[i][8])
    pos3_y.append(y[i][9])
```

```python
plt.title('Three-Body-Solution with $h =$ ' + str(h))
plt.xlabel(r'x')
plt.ylabel(r'y')
plt.plot(pos1_x, pos1_y, linewidth=1.0, linestyle="-", label='m1 = 3')
plt.plot(pos2_x, pos2_y, linewidth=1.0, linestyle="-", label='m2 = 4')
plt.plot(pos3_x, pos3_y, linewidth=1.0, linestyle="-", label='m3 = 5')
plt.legend()
plt.show()
```

```python
################################################################################

distance23 = []

for t in range(len(pos1_x)):
    distance = np.sqrt((pos2_x[t]-pos3_x[t])**2 + (pos2_y[t]-pos3_y[t])**2)
    distance23.append(distance)

index = distance23.index(min(distance23))
t = x[index]

print(t)
print(min(distance23))

#################################################################################
```

    1.8793
    0.0097873762765

### Plot for different step sizes $h$:

```python
#We choose the time steps
h1 = 0.1
h2 = 0.01
h3 = 0.001
h4 = 0.0001
```

### (i) the trajectories of the three bodies in the orbital plane.

```python
#Solve for the coordinates of the masses for different step sizes and constant integration time T=8
def solve_for_position(h):
    y, x = rk4(y0, 0, f, h, int(8/h))

    pos1_x = []
    pos1_y = []
    for i in range(len(y)):
        pos1_x.append(y[i][0])
        pos1_y.append(y[i][1])

    pos2_x = []
    pos2_y = []
    for i in range(len(y)):
        pos2_x.append(y[i][4])
        pos2_y.append(y[i][5])

    pos3_x = []
    pos3_y = []
    for i in range(len(y)):
        pos3_x.append(y[i][8])
        pos3_y.append(y[i][9])

    return (pos1_x, pos1_y, pos2_x, pos2_y, pos3_x, pos3_y)

positions1 = solve_for_position(h1)
positions2 = solve_for_position(h2)
positions3 = solve_for_position(h3)
positions4 = solve_for_position(h4)
```

```python
#Plot trajectories

fig, axs = plt.subplots(2, 2, figsize=(12,10))
axs[0,0].plot(positions1[0], positions1[1], linewidth=1.0, linestyle="-", label='m1 = 3')
axs[0,0].plot(positions1[2], positions1[3], linewidth=1.0, linestyle="-", label='m2 = 4')
axs[0,0].plot(positions1[4], positions1[5], linewidth=1.0, linestyle="-", label='m3 = 5')
axs[0,0].set_title('Three-Body-Solution with $h =$ ' + str(h1))
axs[0,0].legend()
axs[1,0].plot(positions2[0], positions2[1], linewidth=1.0, linestyle="-", label='m1 = 3')
axs[1,0].plot(positions2[2], positions2[3], linewidth=1.0, linestyle="-", label='m2 = 4')
axs[1,0].plot(positions2[4], positions2[5], linewidth=1.0, linestyle="-", label='m3 = 5')
axs[1,0].set_title('Three-Body-Solution with $h =$ ' + str(h2))
axs[1,0].legend()
axs[0,1].plot(positions3[0], positions3[1], linewidth=1.0, linestyle="-", label='m1 = 3')
axs[0,1].plot(positions3[2], positions3[3], linewidth=1.0, linestyle="-", label='m2 = 4')
axs[0,1].plot(positions3[4], positions3[5], linewidth=1.0, linestyle="-", label='m3 = 5')
axs[0,1].set_title('Three-Body-Solution with $h =$ ' + str(h3))
axs[0,1].legend()
axs[1,1].plot(positions4[0], positions4[1], linewidth=1.0, linestyle="-", label='m1 = 3')
axs[1,1].plot(positions4[2], positions4[3], linewidth=1.0, linestyle="-", label='m2 = 4')
axs[1,1].plot(positions4[4], positions4[5], linewidth=1.0, linestyle="-", label='m3 = 5')
axs[1,1].set_title('Three-Body-Solution with $h =$ ' + str(h4))
axs[1,1].legend()

for ax in axs.flat:
    ax.set(xlabel='x', ylabel='y')
```

### (ii) the mutual distances of the three bodies in logarithmic scaling as function of time (linear).

```python
#Distance between m1 and m2
distance12 = []
for t in range(len(positions1[0])):
    distance = np.sqrt((positions1[0][t]-positions1[2][t])**2 + (positions1[1][t]-positions1[3][t])**2)
    distance12.append(distance)

#Distance between m1 and m3
distance13 = []
for t in range(len(positions1[0])):
    distance = np.sqrt((positions1[0][t]-positions1[4][t])**2 + (positions1[1][t]-positions1[5][t])**2)
    distance13.append(distance)

#Distance between m2 and m3
distance23 = []
for t in range(len(positions1[0])):
    distance = np.sqrt((positions1[2][t]-positions1[4][t])**2 + (positions1[3][t]-positions1[5][t])**2)
    distance23.append(distance)

x1 = rk4(y0, 0, f, h1, int(8/h1))[1]

plt.title('Distances with $h =$' + str(h1))
plt.xlabel(r'Time')
plt.ylabel(r'Distance')
plt.plot(x1, distance12, linewidth=1.0, linestyle="-", label='Distance 1 - 2')
plt.plot(x1, distance13, linewidth=1.0, linestyle="-", label='Distance 1 - 3')
plt.plot(x1, distance23, linewidth=1.0, linestyle="-", label='Distance 2 - 3')
plt.yscale('log')
plt.legend()
plt.show()
```

```python
#Distance between m1 and m2
distance12 = []
for t in range(len(positions2[0])):
    distance = np.sqrt((positions2[0][t]-positions2[2][t])**2 + (positions2[1][t]-positions2[3][t])**2)
    distance12.append(distance)

#Distance between m1 and m3
distance13 = []
for t in range(len(positions2[0])):
    distance = np.sqrt((positions2[0][t]-positions2[4][t])**2 + (positions2[1][t]-positions2[5][t])**2)
    distance13.append(distance)

#Distance between m2 and m3
distance23 = []
for t in range(len(positions2[0])):
    distance = np.sqrt((positions2[2][t]-positions2[4][t])**2 + (positions2[3][t]-positions2[5][t])**2)
    distance23.append(distance)

x2 = rk4(y0, 0, f, h2, int(8/h2))[1]

plt.title('Distances with $h =$' + str(h2))
plt.xlabel(r'Time')
plt.ylabel(r'Distance')
plt.plot(x2, distance12, linewidth=1.0, linestyle="-", label='Distance 1 - 2')
plt.plot(x2, distance13, linewidth=1.0, linestyle="-", label='Distance 1 - 3')
plt.plot(x2, distance23, linewidth=1.0, linestyle="-", label='Distance 2 - 3')
plt.yscale('log')
plt.legend()
plt.show()
```

```python
#Distance between m1 and m2
distance12 = []
for t in range(len(positions3[0])):
    distance = np.sqrt((positions3[0][t]-positions3[2][t])**2 + (positions3[1][t]-positions3[3][t])**2)
    distance12.append(distance)

#Distance between m1 and m3
distance13 = []
for t in range(len(positions3[0])):
    distance = np.sqrt((positions3[0][t]-positions3[4][t])**2 + (positions3[1][t]-positions3[5][t])**2)
    distance13.append(distance)

#Distance between m2 and m3
distance23 = []
for t in range(len(positions3[0])):
    distance = np.sqrt((positions3[2][t]-positions3[4][t])**2 + (positions3[3][t]-positions3[5][t])**2)
    distance23.append(distance)

x3 = rk4(y0, 0, f, h3, int(8/h3))[1]

plt.title('Distances with $h =$' + str(h3))
plt.xlabel(r'Time')
plt.ylabel(r'Distance')
plt.plot(x3, distance12, linewidth=1.0, linestyle="-", label='Distance 1 - 2')
plt.plot(x3, distance13, linewidth=1.0, linestyle="-", label='Distance 1 - 3')
plt.plot(x3, distance23, linewidth=1.0, linestyle="-", label='Distance 2 - 3')
plt.yscale('log')
plt.legend()
plt.show()
```

```python
#Distance between m1 and m2
distance12 = []
for t in range(len(positions4[0])):
    distance = np.sqrt((positions4[0][t]-positions4[2][t])**2 + (positions4[1][t]-positions4[3][t])**2)
    distance12.append(distance)

#Distance between m1 and m3
distance13 = []
for t in range(len(positions4[0])):
    distance = np.sqrt((positions4[0][t]-positions4[4][t])**2 + (positions4[1][t]-positions4[5][t])**2)
    distance13.append(distance)

#Distance between m2 and m3
distance23 = []
for t in range(len(positions4[0])):
    distance = np.sqrt((positions4[2][t]-positions4[4][t])**2 + (positions4[3][t]-positions4[5][t])**2)
    distance23.append(distance)

x4 = rk4(y0, 0, f, h4, int(8/h4))[1]

plt.title('Distances with $h =$' + str(h4))
plt.xlabel(r'Time')
plt.ylabel(r'Distance')
plt.plot(x4, distance12, linewidth=1.0, linestyle="-", label='Distance 1 - 2')
plt.plot(x4, distance13, linewidth=1.0, linestyle="-", label='Distance 1 - 3')
plt.plot(x4, distance23, linewidth=1.0, linestyle="-", label='Distance 2 - 3')
plt.yscale('log')
plt.legend()
plt.show()
```

### (iii) the error of the total energy of the system in logarithmic scaling as function of time (linear).

```python
#Didn't had so much time
```


_Latihan soal ini dari perkuliahan Analisis Sumber Daya Air (SI-6232) tahun 2018 Semester Genap diajarkan oleh Dr. Dhemi Harlan ST, MT, MSc._

_Kode dibawah ini dibuat oleh Taruma S.M._

# Metoda Secant
> Tentukan solusi positif dari $f(x) = x - 2\ \sin{x} = 0$ dengan metode secant, mulai dari $x_0 = 2, x_1 = 1.9$

Penyelesaian:
Solusi persamaan :
$$x_{n+1} = x_n - \frac{(x_n-2\sin{x_n})(x_n-x_{n-1})}{x_n-x_{n-1}+2(\sin{x_{n-1}}-\sin{x_n})}=x_n-\frac{N_n}{D_n}$$

```python
from math import sin

x0, x1 = 2, 1.9

lebar_text = 10*5+12+2*6+7
print('='*lebar_text)
print('|{:^{n:d}s}|'.format(
    'Solusi Numerik Menggunakan Metoda Secant', n=lebar_text-2))
print('='*(10*5+12+2*6+7))
print('| {:^10s} | {:^10s} | {:^10s} | {:^10s} | {:^10s} | {:^12s} |'.format(
    'n', 'x_{n-1}', 'x_n', 'N_n', 'D_n', 'x_{n+1}-x_n'))
print('='*(10*5+12+2*6+7))

iterasi = 5

xn = x1
xn_m1 = x0
xn_p1 = 0
for n in range(1, iterasi+1):
    Nn = (xn - 2*sin(xn))*(xn - xn_m1)
    Dn = (xn - xn_m1) + 2*(sin(xn_m1) - sin(xn))
    xn_p1 = xn - Nn / Dn
    dx = xn_p1-xn

    print('| {:^10d} | {:^+1.7f} | {:^+1.7f} | {:^+1.7f} | {:^+1.7f} | {:^+1.9f} |'.format(n, xn_m1, xn, Nn, Dn, dx))

    xn_m1 = xn
    xn = xn_p1

print('='*(10*5+12+2*6+7))
print('Maka diperoleh nilai akar-akarnya = {:10.6f}'.format(xn))
```

    =================================================================================
    |                   Solusi Numerik Menggunakan Metoda Secant                    |
    =================================================================================
    |     n      |  x_{n-1}   |    x_n     |    N_n     |    D_n     | x_{n+1}-x_n  |
    =================================================================================
    |     1      | +2.0000000 | +1.9000000 | -0.0007400 | -0.1740053 | -0.004252643 |
    |     2      | +1.9000000 | +1.8957474 | -0.0000018 | -0.0069852 | -0.000252433 |
    |     3      | +1.8957474 | +1.8954949 | -0.0000000 | -0.0004136 | -0.000000658 |
    |     4      | +1.8954949 | +1.8954943 | -0.0000000 | -0.0000011 | -0.000000000 |
    |     5      | +1.8954943 | +1.8954943 | -0.0000000 | -0.0000000 | +0.000000000 |
    =================================================================================
    Maka diperoleh nilai akar-akarnya =   1.895494

# Metode Newton-Rhapson

> Tentukan sebuah akar persamaan dari fungsi berikut: $f(x) = x^3 - 3.5x^2+2x-10$

Solusi:
Persamaan umumnya:
$$\large{x_{k+1} = x_k - \frac{f(x_k)}{f'(x_k)}}$$

```python
# Defining function
def f(x):  # Fungsi f(x)
    return x**3 - 3.5*x**2 + 2*x - 10

def f_d(x):  # Turunan Fungsi f(x) -> f'(x)
    return 3*x**2 - 7*x + 2

iterasi = 6  # Iteration
xk = 3  # x_k

lebar_text = 12*5 + 6
print('='*lebar_text)
print('|{:^{n:d}s}|'.format('Solusi Numerik Metoda Newton Rhapson', n=lebar_text-2))
print('='*lebar_text)
print('| {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} |'.format(
    'k', 'x_k', 'f(x_k)', 'f\'(x_k)', 'x_{k+1}', n=10))
print('='*lebar_text)

for i in range(1, iterasi+1):
    xk_p1 = xk - f(xk)/f_d(xk)
    print('| {:^{n:d}d} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | '.format(i,
                                                                                xk, f(xk), f_d(xk), xk_p1, n=10, f1=10, f2=5))
    xk = xk_p1
print('='*lebar_text)
print('Maka diperoleh nilai akar-akarnya = {:10.6f}'.format(xk))
```

    ==================================================================
    |              Solusi Numerik Metoda Newton Rhapson              |
    ==================================================================
    |     k      |    x_k     |   f(x_k)   |  f'(x_k)   |  x_{k+1}   |
    ==================================================================
    |     1      |   3.00000  |  -8.50000  |   8.00000  |   4.06250  |
    |     2      |   4.06250  |   7.40845  |  23.07422  |   3.74143  |
    |     3      |   3.74143  |   0.86246  |  17.80488  |   3.69299  |
    |     4      |   3.69299  |   0.01801  |  17.06359  |   3.69193  |
    |     5      |   3.69193  |   0.00001  |  17.04760  |   3.69193  |
    |     6      |   3.69193  |   0.00000  |  17.04759  |   3.69193  |
    ==================================================================
    Maka diperoleh nilai akar-akarnya =   3.691934

# Metode Interval-halving

> Gunakan metode interval-halving, fungsi berikut didapat mempunyai suatu akar real antara x = 1 dan x = 3; tentukan akar dari: $f(x) = x^3-5x^2-2x+10$

Solusi:

Evaluasi fungsi pada kedua nilai awal, $x_1 = 1 \text{ dan } x_2 = 3$
$$\begin{align} x_1 &=& 1 &\rightarrow& f(x_1) &=& 4 \\
x_2 &=& 3 &\rightarrow& f(x_2) &=& -14 \\ \end{align}$$

```python
def f(x):
    return x**3 - 5*x**2 - 2*x + 10

def newline(x=30):
    print('='*x)

x1, x2 = 1, 3
iterasi = 6
xa, xb = x1, x2

pad = 70
newline(pad)
print('Periksa nilai akarnya berada diantara xa dan xb')
if f(xa)*f(xb) < 0:
    print('f(x_a) x f(x_b) < 0 === OK \n\tdengan nilai f(x_a) = {fxa} dan f(x_b) = {fxb}'.format(
        fxa=f(xa), fxb=f(xb)))
else:
    print('f(x_a) x f(x_b) < 0 === NOT OK \n\ttidak akan ditemukan akarnya diantara {xa} dan {xb}'.format(
        xa=xa, xb=xb))
newline(pad)
print('\n')

lebar_text = 12*7 + 8
print('='*lebar_text)
print('|{:^{n:d}s}|'.format('Solusi Numerik Metoda Interval Halving', n=lebar_text-2))
print('='*lebar_text)
print('| {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} | {:^{n:d}s} |'.format(
    'n', 'x_a', 'x_b', 'f(x_a)', 'f(x_b)', 'x_h', 'f(x_h)', n=10))
print('='*lebar_text)

for i in range(1, iterasi+1):
    fxa, fxb = f(xa), f(xb)
    xh = (xa+xb)/2
    fxh = f(xh)

    print('| {:^{n:d}d} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} | {:^ {f1:d}.{f2:d}f} |'.format(
        i, xa, xb, f(xa), f(xb), xh, f(xh), n=10, f1=10, f2=5))
    if fxa*fxh < 0:
        xb = xh
    elif fxb*fxh < 0:
        xa = xh
print('='*lebar_text)
print('Maka diperoleh nilai akar-akarnya = {:10.6f}'.format(xh))
```

    ======================================================================
    Periksa nilai akarnya berada diantara xa dan xb
    f(x_a) x f(x_b) < 0 === OK
    	dengan nilai f(x_a) = 4 dan f(x_b) = -14
    ======================================================================

    ============================================================================================
    |                          Solusi Numerik Metoda Interval Halving                          |
    ============================================================================================
    |     n      |    x_a     |    x_b     |   f(x_a)   |   f(x_b)   |    x_h     |   f(x_h)   |
    ============================================================================================
    |     1      |   1.00000  |   3.00000  |   4.00000  | -14.00000  |   2.00000  |  -6.00000  |
    |     2      |   1.00000  |   2.00000  |   4.00000  |  -6.00000  |   1.50000  |  -0.87500  |
    |     3      |   1.00000  |   1.50000  |   4.00000  |  -0.87500  |   1.25000  |   1.64062  |
    |     4      |   1.25000  |   1.50000  |   1.64062  |  -0.87500  |   1.37500  |   0.39648  |
    |     5      |   1.37500  |   1.50000  |   0.39648  |  -0.87500  |   1.43750  |  -0.23657  |
    |     6      |   1.37500  |   1.43750  |   0.39648  |  -0.23657  |   1.40625  |   0.08072  |
    ============================================================================================
    Maka diperoleh nilai akar-akarnya =   1.406250

```python

```

ansis/Interval-Halving, Newton-Rhapson, Secant (Minggu 15).ipynb


# Computational Physics Lectures: Numerical integration, from Newton-Cotes quadrature to Gaussian quadrature

**Morten Hjorth-Jensen**, Department of Physics, University of Oslo and Department of Physics and Astronomy and National Superconducting Cyclotron Laboratory, Michigan State University

Date: **Aug 23, 2017**

Copyright 1999-2017, Morten Hjorth-Jensen. Released under CC Attribution-NonCommercial 4.0 license

## Numerical Integration
Here we will  discuss some of the classical methods for integrating a function. The methods we discuss are
1. Equal step methods like the trapezoidal, rectangular and Simpson's rule, parts of what are called Newton-Cotes quadrature methods.

2. Integration approaches   based on Gaussian quadrature.

The latter are more suitable
for the case where the abscissas are not equally spaced.
We emphasize methods for evaluating few-dimensional (typically up to four dimensions) integrals. Multi-dimensional integrals will be discussed in connection with Monte Carlo methods.

## Newton-Cotes Quadrature or equal-step methods
The integral

$$
\begin{equation}
   I=\int_a^bf(x) dx
\label{eq:integraldef} \tag{1}
\end{equation}
$$

has a very simple meaning. The integral is the
area enscribed by the function $f(x)$ starting from $x=a$ to  $x=b$. It is subdivided in several smaller areas whose evaluation is to  be approximated by different techniques. The areas under the curve can for example  be approximated by rectangular boxes or trapezoids.

## Basic philosophy of equal-step methods
In considering equal step  methods, our basic approach is that of approximating
a function $f(x)$ with a polynomial of at most
degree $N-1$, given $N$ integration points. If our polynomial is of degree $1$,
the function will be approximated with $f(x)\approx a_0+a_1x$.

## Simple algorithm for equal step methods
The algorithm for these integration methods  is rather simple, and the number of approximations perhaps  unlimited!

* Choose a step size $h=(b-a)/N$  where $N$ is the number of steps and $a$ and $b$ the lower and upper limits of integration.

* With a given step length we rewrite the integral as

$$
\int_a^bf(x) dx= \int_a^{a+h}f(x)dx + \int_{a+h}^{a+2h}f(x)dx+\dots \int_{b-h}^{b}f(x)dx.
$$

* The strategy then is to find a reliable polynomial approximation   for $f(x)$ in the various intervals.  Choosing a given approximation for  $f(x)$, we obtain a specific approximation to the  integral.

* With this approximation to $f(x)$ we perform the integration by computing the integrals over all subintervals.

## Simple algorithm for equal step methods
One possible strategy then is to find a reliable polynomial expansion for $f(x)$ in the smaller
subintervals. Consider for example evaluating

$$
\int_a^{a+2h}f(x)dx,
$$

which we rewrite as

$$
\begin{equation}
   \int_a^{a+2h}f(x)dx=
 \int_{x_0-h}^{x_0+h}f(x)dx.
\label{eq:hhint} \tag{2}
\end{equation}
$$

We have chosen a midpoint $x_0$ and have defined $x_0=a+h$.

## Lagrange's interpolation formula
Using Lagrange's interpolation formula

$$
P_N(x)=\sum_{i=0}^{N}\prod_{k\ne i} \frac{x-x_k}{x_i-x_k}y_i,
$$

we could attempt to approximate the function $f(x)$ with a first-order polynomial in $x$ in the two
sub-intervals $x\in[x_0-h,x_0]$ and $x\in[x_0,x_0+h]$. A first order polynomial means simply that
we have for say the interval  $x\in[x_0,x_0+h]$

$$
f(x)\approx P_1(x)=\frac{x-x_0}{(x_0+h)-x_0}f(x_0+h)+\frac{x-(x_0+h)}{x_0-(x_0+h)}f(x_0),
$$

and for the interval  $x\in[x_0-h,x_0]$

$$
f(x)\approx P_1(x)=\frac{x-(x_0-h)}{x_0-(x_0-h)}f(x_0)+\frac{x-x_0}{(x_0-h)-x_0}f(x_0-h).
$$

## Polynomial approximation
Having performed this subdivision and polynomial approximation,
one from $x_0-h$ to $x_0$ and the other from $x_0$ to $x_0+h$,

$$
\int_a^{a+2h}f(x)dx=\int_{x_0-h}^{x_0}f(x)dx+\int_{x_0}^{x_0+h}f(x)dx,
$$

we can easily calculate for example the second integral as

$$
\int_{x_0}^{x_0+h}f(x)dx\approx \int_{x_0}^{x_0+h}\left(\frac{x-x_0}{(x_0+h)-x_0}f(x_0+h)+\frac{x-(x_0+h)}{x_0-(x_0+h)}f(x_0)\right)dx.
$$

## Simplifying the integral
This integral can be simplified to

$$
\int_{x_0}^{x_0+h}f(x)dx\approx \int_{x_0}^{x_0+h}\left(\frac{x-x_0}{h}f(x_0+h)-\frac{x-(x_0+h)}{h}f(x_0)\right)dx,
$$

resulting in

$$
\int_{x_0}^{x_0+h}f(x)dx=\frac{h}{2}\left(f(x_0+h) + f(x_0)\right)+O(h^3).
$$

Here we added the error made in approximating our integral
with a polynomial of degree $1$.

## The trapezoidal rule
The other integral gives

$$
\int_{x_0-h}^{x_0}f(x)dx=\frac{h}{2}\left(f(x_0) + f(x_0-h)\right)+O(h^3),
$$

and adding up we obtain

$$
\begin{equation}
   \int_{x_0-h}^{x_0+h}f(x)dx=\frac{h}{2}\left(f(x_0+h) + 2f(x_0) + f(x_0-h)\right)+O(h^3),
\label{eq:trapez} \tag{3}
\end{equation}
$$

which is the well-known trapezoidal rule.  Concerning the error in the approximation made,
$O(h^3)=O((b-a)^3/N^3)$, you should  note
that this is the local error.  Since we are splitting the integral from
$a$ to $b$ in $N$ pieces, we will have to perform approximately $N$
such operations.

## Global error
This means that the *global error* goes like $\approx O(h^2)$.
The trapezoidal reads then

$$
\begin{equation}
   I=\int_a^bf(x) dx=h\left(f(a)/2 + f(a+h) +f(a+2h)+
                          \dots +f(b-h)+ f_{b}/2\right),
\label{eq:trapez1} \tag{4}
\end{equation}
$$

with a global error which goes like $O(h^2)$.

Hereafter we use the shorthand notations $f_{-h}=f(x_0-h)$, $f_{0}=f(x_0)$
and $f_{h}=f(x_0+h)$.

## Error in the trapezoidal rule
The correct mathematical expression for the local error for the trapezoidal rule is

$$
\int_a^bf(x)dx -\frac{b-a}{2}\left[f(a)+f(b)\right]=-\frac{h^3}{12}f^{(2)}(\xi),
$$

and the global error reads

$$
\int_a^bf(x)dx -T_h(f)=-\frac{b-a}{12}h^2f^{(2)}(\xi),
$$

where $T_h$ is the trapezoidal result and $\xi \in [a,b]$.

## Algorithm for the trapezoidal rule
The trapezoidal rule is easy to  implement numerically
through the following simple algorithm

  * Choose the number of mesh points and fix the step length.

  * calculate $f(a)$ and $f(b)$ and multiply with $h/2$.

  * Perform a loop over $n=1$ to $n-1$ ($f(a)$ and $f(b)$ are known) and sum up  the terms $f(a+h) +f(a+2h)+f(a+3h)+\dots +f(b-h)$. Each step in the loop  corresponds to a given value $a+nh$.

  * Multiply the final result by $h$ and add $hf(a)/2$ and $hf(b)/2$.

## Code example
A simple function which implements this algorithm is as follows

        double TrapezoidalRule(double a, double b, int n, double (*func)(double))
        {
              double TrapezSum;
              double fa, fb, x, step;
              int    j;
              step=(b-a)/((double) n);
              fa=(*func)(a)/2. ;
              fb=(*func)(b)/2. ;
              TrapezSum=0.;
              for (j=1; j <= n-1; j++){
                 x=j*step+a;
                 TrapezSum+=(*func)(x);
              }
              TrapezSum=(TrapezSum+fb+fa)*step;
              return TrapezSum;
        }  // end TrapezoidalRule

The function returns a new value for the specific integral through the variable **TrapezSum**.

## Transfer of  function names
There is one new feature to note here, namely
the transfer of a user defined function called **func** in the
definition

          void TrapezoidalRule(double a, double b, int n, double *TrapezSum, double (*func)(double) )

What happens here is that we are transferring a pointer to the name
of a user defined function, which has as input a double precision variable and returns
a double precision number. The function  **TrapezoidalRule** is called as

          TrapezoidalRule(a, b, n, &MyFunction )

in the calling function. We note that **a**, **b** and **n** are called by value,
while **TrapezSum** and the user defined function **MyFunction**
are called by reference.

## Going back to Python, why?
**Symbolic calculations and numerical calculations in one code!**

Python offers an  extremely versatile programming  environment, allowing for
the inclusion of analytical studies in a numerical program. Here we show an
example code with the **trapezoidal rule** using **SymPy** to evaluate an integral and compute the absolute error
with respect to the numerically evaluated one of the integral
$4\int_0^1 dx/(1+x^2) = \pi$:

```python
from math import *
from sympy import *
def Trapez(a,b,f,n):
   h = (b-a)/float(n)
   s = 0
   x = a
   for i in range(1,n,1):
       x = x+h
       s = s+ f(x)
   s = 0.5*(f(a)+f(b)) +s
   return h*s

#  function to compute pi
def function(x):
    return 4.0/(1+x*x)

a = 0.0;  b = 1.0; n = 100
result = Trapez(a,b,function,n)
print("Trapezoidal rule=", result)
# define x as a symbol to be used by sympy
x = Symbol('x')
exact = integrate(function(x), (x, 0.0, 1.0))
print("Sympy integration=", exact)
# Find relative error
print("Relative error", abs((exact-result)/exact))
```

## Error analysis
The following extended version of the trapezoidal rule allows you  to plot the relative error by comparing with the exact result. By increasing to $10^8$ points one arrives at a region where numerical errors start to accumulate.

```python
%matplotlib inline

from math import log10
import numpy as np
from sympy import Symbol, integrate
import matplotlib.pyplot as plt
# function for the trapezoidal rule
def Trapez(a,b,f,n):
   h = (b-a)/float(n)
   s = 0
   x = a
   for i in range(1,n,1):
       x = x+h
       s = s+ f(x)
   s = 0.5*(f(a)+f(b)) +s
   return h*s
#  function to compute pi
def function(x):
    return 4.0/(1+x*x)
# define integration limits
a = 0.0;  b = 1.0;
# find result from sympy
# define x as a symbol to be used by sympy
x = Symbol('x')
exact = integrate(function(x), (x, a, b))
# set up the arrays for plotting the relative error
n = np.zeros(9); y = np.zeros(9);
# find the relative error as function of integration points
for i in range(1, 8, 1):
    npts = 10**i
    result = Trapez(a,b,function,npts)
    RelativeError = abs((exact-result)/exact)
    n[i] = log10(npts); y[i] = log10(RelativeError);
plt.plot(n,y, 'ro')
plt.xlabel('n')
plt.ylabel('Relative error')
plt.show()
```

## Integrating numerical mathematics with calculus
The last example shows the potential of combining numerical algorithms with
symbolic calculations, allowing us thereby to

* Validate and verify  our  algorithms.

* Including concepts like unit testing, one has the possibility to test and  validate several or all parts of the code.

* Validation and verification are then included *naturally*.

* The above example allows you to test the mathematical error of the algorithm for the trapezoidal rule by changing the number of integration points. You get trained from day one to think error analysis.

## The rectangle method
Another very simple approach is the so-called midpoint or rectangle method.
In this case the integration area is split in a given number of rectangles with length $h$ and height given by the mid-point value of the function.  This gives the following simple rule for approximating an integral

$$
\begin{equation}
   I=\int_a^bf(x) dx \approx  h\sum_{i=1}^N f(x_{i-1/2}),
\label{eq:rectangle} \tag{5}
\end{equation}
$$

where $f(x_{i-1/2})$ is the midpoint value of $f$ for a given rectangle. We will discuss its truncation
error below.  It is easy to implement this algorithm,  as shown here

        double RectangleRule(double a, double b, int n, double (*func)(double))
        {
              double RectangleSum;
              double fa, fb, x, step;
              int    j;
              step=(b-a)/((double) n);
              RectangleSum=0.;
              for (j = 0; j <= n; j++){
                 x = (j+0.5)*step+;   // midpoint of a given rectangle
                 RectangleSum+=(*func)(x);   //  add value of function.
              }
              RectangleSum *= step;  //  multiply with step length.
              return RectangleSum;
        }  // end RectangleRule

## Truncation error for the rectangular rule
The correct mathematical expression for the local error for the rectangular rule $R_i(h)$ for element $i$ is

$$
\int_{-h}^hf(x)dx - R_i(h)=-\frac{h^3}{24}f^{(2)}(\xi),
$$

and the global error reads

$$
\int_a^bf(x)dx -R_h(f)=-\frac{b-a}{24}h^2f^{(2)}(\xi),
$$

where $R_h$ is the result obtained with rectangular rule and $\xi \in [a,b]$.

## Second-order polynomial
Instead of using the above first-order polynomials
approximations for $f$, we attempt at using a second-order polynomials.
In this case we need three points in order to define a second-order
polynomial approximation

$$
f(x) \approx P_2(x)=a_0+a_1x+a_2x^2.
$$

Using again Lagrange's interpolation formula we have

$$
P_2(x)=\frac{(x-x_0)(x-x_1)}{(x_2-x_0)(x_2-x_1)}y_2+
            \frac{(x-x_0)(x-x_2)}{(x_1-x_0)(x_1-x_2)}y_1+
            \frac{(x-x_1)(x-x_2)}{(x_0-x_1)(x_0-x_2)}y_0.
$$

Inserting this formula in the integral of Eq.  ([eq:hhint](#eq:hhint)) we obtain

$$
\int_{-h}^{+h}f(x)dx=\frac{h}{3}\left(f_h + 4f_0 + f_{-h}\right)+O(h^5),
$$

which is Simpson's rule.

## Simpson's rule
Note that the improved accuracy in the evaluation of
the derivatives gives a better error approximation, $O(h^5)$ vs.\ $O(h^3)$ .
But this is again the *local error approximation*.
Using Simpson's rule we can easily compute
the integral     of Eq.  ([eq:integraldef](#eq:integraldef)) to be

$$
\begin{equation}
   I=\int_a^bf(x) dx=\frac{h}{3}\left(f(a) + 4f(a+h) +2f(a+2h)+
                          \dots +4f(b-h)+ f_{b}\right),
\label{eq:simpson} \tag{6}
\end{equation}
$$

with a global error which goes like $O(h^4)$.

## Mathematical expressions for the truncation error
More formal expressions for the local and global errors are for the local error

$$
\int_a^bf(x)dx -\frac{b-a}{6}\left[f(a)+4f((a+b)/2)+f(b)\right]=-\frac{h^5}{90}f^{(4)}(\xi),
$$

and for the global error

$$
\int_a^bf(x)dx -S_h(f)=-\frac{b-a}{180}h^4f^{(4)}(\xi).
$$

with $\xi\in[a,b]$ and $S_h$ the results obtained with Simpson's method.

## Algorithm for Simpson's rule
The method
can easily be implemented numerically through the following simple algorithm

  * Choose the number of mesh points and fix the step.

  * calculate $f(a)$ and $f(b)$

  * Perform a loop over $n=1$ to $n-1$ ($f(a)$ and $f(b)$ are known) and sum up   the terms $4f(a+h) +2f(a+2h)+4f(a+3h)+\dots +4f(b-h)$. Each step in the loop  corresponds to a given value $a+nh$. Odd values of $n$ give $4$ as factor  while even values yield $2$ as factor.

  * Multiply the final result by $\frac{h}{3}$.

## Summary for equal-step methods
In more general terms, what we have done here is to approximate a given function $f(x)$ with a polynomial
of a certain degree. One can show that
given $n+1$ distinct points $x_0,\dots, x_n\in[a,b]$ and $n+1$ values $y_0,\dots,y_n$ there exists a
unique polynomial $P_n(x)$ with the property

$$
P_n(x_j) = y_j\hspace{0.5cm} j=0,\dots,n
$$

## Lagrange's polynomial
In the Lagrange representation the interpolating polynomial is given by

$$
P_n = \sum_{k=0}^nl_ky_k,
$$

with the Lagrange factors

$$
l_k(x) = \prod_{\begin{array}{c}i=0 \\ i\ne k\end{array}}^n\frac{x-x_i}{x_k-x_i}\hspace{0.2cm} k=0,\dots,n.
$$

## Polynomial approximation
If we for example set $n=1$, we obtain

$$
P_1(x) = y_0\frac{x-x_1}{x_0-x_1}+y_1\frac{x-x_0}{x_1-x_0}=\frac{y_1-y_0}{x_1-x_0}x-\frac{y_1x_0+y_0x_1}{x_1-x_0},
$$

which we recognize as the equation for a straight line.

The polynomial interpolatory quadrature of order $n$ with equidistant quadrature points $x_k=a+kh$
and step $h=(b-a)/n$ is called the Newton-Cotes quadrature formula of order $n$.

## Gaussian Quadrature
The methods we have presented hitherto are tailored to problems where the
mesh points $x_i$ are equidistantly spaced, $x_i$ differing from $x_{i+1}$ by the step $h$.

The basic idea behind all integration methods is to approximate the integral

$$
I=\int_a^bf(x)dx \approx \sum_{i=1}^N\omega_if(x_i),
$$

where $\omega$ and $x$ are the weights and the chosen mesh points, respectively.
In our previous discussion, these mesh points were fixed at the beginning, by choosing
a given number of points $N$. The weigths $\omega$ resulted then from the integration
method we applied. Simpson's rule, see Eq.  ([eq:simpson](#eq:simpson)) would give

$$
\omega : \left\{h/3,4h/3,2h/3,4h/3,\dots,4h/3,h/3\right\},
$$

for the weights, while the trapezoidal rule resulted in

$$
\omega : \left\{h/2,h,h,\dots,h,h/2\right\}.
$$

## Gaussian Quadrature, main idea
In general, an integration formula which is based on a Taylor series using $N$ points,
will integrate exactly a polynomial $P$ of degree $N-1$. That is, the $N$ weights
$\omega_n$ can be chosen to satisfy $N$ linear equations, see chapter 3 of Ref.\ [3].
A greater precision for a given amount of numerical work can  be achieved
if we are willing to give up the requirement of equally spaced integration points.
In Gaussian quadrature (hereafter GQ), both the mesh points and the weights are to
be determined. The points will not be equally spaced.

The theory behind GQ is to obtain an arbitrary weight $\omega$ through the use of
so-called orthogonal polynomials. These polynomials are orthogonal in some
interval say e.g., [-1,1]. Our points $x_i$ are chosen in some optimal sense subject
only to the constraint that they should lie in this interval. Together with the weights
we have then $2N$ ($N$ the number of points) parameters at our disposal.

## Gaussian Quadrature
Even though the integrand is not smooth, we could render it smooth by extracting
from it the weight function of an orthogonal polynomial, i.e.,
we are rewriting

$$
\begin{equation}
   I= \int_a^b f(x)dx =\int_a^b W(x)g(x)dx \approx \sum_{i=1}^N\omega_ig(x_i),
\label{eq:generalint} \tag{7}
\end{equation}
$$

where $g$ is smooth and $W$ is the weight function, which is to  be associated with a given
orthogonal polynomial. Note that with a given weight function we end up evaluating the integrand
for the function $g(x_i)$.

## Gaussian Quadrature, weight function
The weight function $W$ is non-negative in the integration interval
$x\in [a,b]$ such that
for any $n \ge 0$, the integral $\int_a^b |x|^n W(x) dx$ is integrable. The naming
weight function arises from the fact that it may be used to give more emphasis
to one part of the interval than another.
A quadrature formula

$$
\begin{equation}
   \int_a^b W(x)f(x)dx \approx \sum_{i=1}^N\omega_if(x_i),
\label{_auto1} \tag{8}
\end{equation}
$$

with $N$ distinct quadrature points (mesh points) is a called a Gaussian quadrature
formula if it integrates all polynomials $p\in P_{2N-1}$ exactly, that is

$$
\begin{equation}
\int_a^bW(x)p(x)dx =\sum_{i=1}^N\omega_ip(x_i),
\label{_auto2} \tag{9}
\end{equation}
$$

It is assumed that $W(x)$ is continuous and positive and that the integral

$$
\int_a^bW(x)dx
$$

exists. Note that the replacement of $f\rightarrow Wg$ is normally a better approximation
due to the fact that we may isolate possible singularities of $W$ and its
derivatives at the endpoints of the interval.

## Gaussian Quadrature weights and integration points
The quadrature weights or just weights (not to be confused with the weight function)
are positive and the sequence of Gaussian quadrature formulae is convergent
if the sequence $Q_N$ of quadrature formulae

$$
Q_N(f)\rightarrow Q(f)=\int_a^bf(x)dx,
$$

in the limit $N\rightarrow \infty$.

## Gaussian Quadrature
Then  we say that the sequence

$$
Q_N(f) = \sum_{i=1}^N\omega_i^{(N)}f(x_i^{(N)}),
$$

is convergent for all polynomials $p$, that is

$$
Q_N(p) = Q(p)
$$

if there exits a constant $C$ such that

$$
\sum_{i=1}^N|\omega_i^{(N)}| \le C,
$$

for all $N$ which are natural numbers.

## Error in Gaussian Quadrature
The error for the Gaussian quadrature formulae of order $N$ is given
by

$$
\int_a^bW(x)f(x)dx-\sum_{k=1}^Nw_kf(x_k)=\frac{f^{2N}(\xi)}{(2N)!}\int_a^bW(x)[q_{N}(x)]^2dx
$$

where $q_{N}$ is the chosen orthogonal polynomial and $\xi$ is a number in the interval $[a,b]$.
We have assumed that $f\in C^{2N}[a,b]$, viz. the space of all real or complex  $2N$ times continuously
differentiable functions.

## Important polynomials in Gaussian Quadrature
In science there are several important orthogonal polynomials which arise
from the solution of differential equations. Well-known examples are the
Legendre, Hermite, Laguerre and Chebyshev polynomials. They have the following weight functions

    Weight function    	          Interval         	 Polynomial

   $W(x)=1$                   	    $x\in [-1,1]$                 	    Legendre

   $W(x)=e^{-x^2}$            	    $-\infty \le x \le \infty$    	    Hermite

   $W(x)=x^{\alpha}e^{-x}$    	    $0 \le x \le \infty$          	    Laguerre

   $W(x)=1/(\sqrt{1-x^2})$    	    $-1 \le x \le 1$              	    Chebyshev

The importance of the use of orthogonal polynomials in the evaluation
of integrals can be summarized as follows.

## Gaussian Quadrature, win-win situation

Methods based on Taylor series using $N$ points will  integrate exactly a polynomial $P$ of degree $N-1$. If a function $f(x)$ can be approximated with a polynomial of degree $N-1$

$$
f(x)\approx P_{N-1}(x),
$$

with $N$ mesh points we should be able to integrate exactly the polynomial $P_{N-1}$.

Gaussian quadrature methods promise more than this. We can get a better          polynomial approximation with order greater than $N$  to $f(x)$ and still          get away with only $N$ mesh points. More precisely, we approximate

$$
f(x) \approx P_{2N-1}(x),
$$

and with only $N$ mesh points these methods promise that

$$
\int f(x)dx \approx \int P_{2N-1}(x)dx=\sum_{i=0}^{N-1} P_{2N-1}(x_i)\omega_i,
$$

## Gaussian Quadrature, determining mesh points and weights

The reason why we can represent a function $f(x)$ with a polynomial of degree
$2N-1$ is due to the fact that we have $2N$ equations, $N$ for the mesh points and $N$
for the weights.

*The mesh points are the zeros  of the chosen  orthogonal polynomial* of
order $N$, and the weights are determined from the inverse of a matrix.
An orthogonal polynomials of degree $N$ defined in an interval $[a,b]$
has precisely $N$ distinct zeros on the open interval $(a,b)$.

Before we detail how to obtain mesh points and weights with orthogonal
polynomials, let us revisit some features of orthogonal polynomials
by specializing to Legendre polynomials. In the text below, we reserve
hereafter the labelling
$L_N$ for a Legendre polynomial of order $N$, while $P_N$ is an arbitrary polynomial
of order $N$.
These polynomials form then the basis for the Gauss-Legendre method.

## Orthogonal polynomials, Legendre

The Legendre polynomials are the solutions of an important
differential equation in Science, namely

$$
C(1-x^2)P-m_l^2P+(1-x^2)\frac{d}{dx}\left((1-x^2)\frac{dP}{dx}\right)=0.
$$

Here $C$ is a constant. For $m_l=0$ we obtain the Legendre polynomials
as solutions, whereas $m_l \ne 0$ yields the so-called associated Legendre
polynomials. This differential equation arises in for example the solution
of the angular dependence of Schroedinger's
equation with spherically symmetric potentials such as
the Coulomb potential.

## Orthogonal polynomials, Legendre
The corresponding polynomials $P$ are

$$
L_k(x)=\frac{1}{2^kk!}\frac{d^k}{dx^k}(x^2-1)^k \hspace{1cm} k=0,1,2,\dots,
$$

which, up to a factor, are the Legendre polynomials $L_k$.
The latter fulfil the orthogonality relation

$$
\begin{equation}
  \int_{-1}^1L_i(x)L_j(x)dx=\frac{2}{2i+1}\delta_{ij},
\label{eq:ortholeg} \tag{10}
\end{equation}
$$

and the recursion relation

$$
\begin{equation}
  (j+1)L_{j+1}(x)+jL_{j-1}(x)-(2j+1)xL_j(x)=0.
\label{eq:legrecur} \tag{11}
\end{equation}
$$

## Orthogonal polynomials, Legendre
It is common to choose the normalization condition

$$
L_N(1)=1.
$$

With these equations we can determine a Legendre polynomial of arbitrary order
with input polynomials of order $N-1$ and $N-2$.

As an example, consider the determination of $L_0$, $L_1$ and $L_2$.
We have that

$$
L_0(x) = c,
$$

with $c$ a constant. Using the normalization equation $L_0(1)=1$
we get that

$$
L_0(x) = 1.
$$

## Orthogonal polynomials, Legendre
For $L_1(x)$ we have the general expression

$$
L_1(x) = a+bx,
$$

and using the orthogonality relation

$$
\int_{-1}^1L_0(x)L_1(x)dx=0,
$$

we obtain $a=0$ and with the condition $L_1(1)=1$, we obtain $b=1$, yielding

$$
L_1(x) = x.
$$

## Orthogonal polynomials, Legendre
We can proceed in a similar fashion in order to determine
the coefficients of $L_2$

$$
L_2(x) = a+bx+cx^2,
$$

using the orthogonality relations

$$
\int_{-1}^1L_0(x)L_2(x)dx=0,
$$

and

$$
\int_{-1}^1L_1(x)L_2(x)dx=0,
$$

and the condition
$L_2(1)=1$ we would get

$$
\begin{equation}
   L_2(x) = \frac{1}{2}\left(3x^2-1\right).
\label{eq:l2} \tag{12}
\end{equation}
$$

## Orthogonal polynomials, Legendre
We note that we have three equations to determine the three coefficients
$a$, $b$ and $c$.

Alternatively, we could have
employed the recursion relation of Eq. ([eq:legrecur](#eq:legrecur)), resulting in

$$
2L_2(x)=3xL_1(x)-L_0,
$$

which leads to Eq. ([eq:l2](#eq:l2)).

## Orthogonal polynomials, Legendre
The orthogonality relation above is important in our discussion
on how to obtain the weights and mesh points. Suppose we have an arbitrary
polynomial $Q_{N-1}$ of order $N-1$ and a Legendre polynomial $L_N(x)$ of
order $N$. We could represent $Q_{N-1}$
by the Legendre polynomials through

$$
\begin{equation}
   Q_{N-1}(x)=\sum_{k=0}^{N-1}\alpha_kL_{k}(x),
\label{eq:legexpansion} \tag{13}
\end{equation}
$$

where $\alpha_k$'s are constants.

Using the orthogonality relation of Eq. ([eq:ortholeg](#eq:ortholeg)) we see that

$$
\begin{equation}
  \int_{-1}^1L_N(x)Q_{N-1}(x)dx=\sum_{k=0}^{N-1} \int_{-1}^1L_N(x) \alpha_kL_{k}(x)dx=0.
\label{eq:ortholeg2} \tag{14}
\end{equation}
$$

We will use this result in our construction of mesh points and weights
in the next subsection.

## Orthogonal polynomials, Legendre
In summary, the first few Legendre polynomials are

6
1

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

6
2

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

6
3

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

$$
L_3(x) = (5x^3-3x)/2,
$$

and

$$
L_4(x) = (35x^4-30x^2+3)/8.
$$

## Orthogonal polynomials, simple code for Legendre polynomials
The following simple function implements the above recursion relation
of Eq. ([eq:legrecur](#eq:legrecur)).
for computing Legendre polynomials of order $N$.

        //  This function computes the Legendre polynomial of degree N

        double Legendre( int n, double x)
        {
               double r, s, t;
               int m;
               r = 0; s = 1.;
               //  Use recursion relation to generate p1 and p2
               for (m=0; m < n; m++ )
               {
                  t = r; r = s;
                  s = (2*m+1)*x*r - m*t;
                  s /= (m+1);
        	} // end of do loop
                return s;
        }   // end of function Legendre

The variable $s$ represents $L_{j+1}(x)$, while $r$ holds
$L_j(x)$ and $t$ the value $L_{j-1}(x)$.

## Integration points and weights with orthogonal polynomials

To understand how the weights and the mesh points are generated, we define first
a polynomial of degree $2N-1$ (since we have $2N$ variables at hand, the mesh points
and weights for $N$ points). This polynomial can be represented through polynomial
division by

$$
P_{2N-1}(x)=L_N(x)P_{N-1}(x)+Q_{N-1}(x),
$$

where $P_{N-1}(x)$ and $Q_{N-1}(x)$ are some polynomials of degree $N-1$ or less.
The function $L_N(x)$ is a Legendre polynomial of order $N$.

Recall that we wanted to approximate  an arbitrary function $f(x)$ with a
polynomial $P_{2N-1}$ in order to evaluate

$$
\int_{-1}^1f(x)dx\approx \int_{-1}^1P_{2N-1}(x)dx.
$$

## Integration points and weights with orthogonal polynomials
We can use Eq. ([eq:ortholeg2](#eq:ortholeg2))
to rewrite the above integral as

$$
\int_{-1}^1P_{2N-1}(x)dx=\int_{-1}^1(L_N(x)P_{N-1}(x)+Q_{N-1}(x))dx=\int_{-1}^1Q_{N-1}(x)dx,
$$

due to the orthogonality properties of the Legendre polynomials. We see that it suffices
to evaluate the integral over $\int_{-1}^1Q_{N-1}(x)dx$ in order to evaluate
$\int_{-1}^1P_{2N-1}(x)dx$. In addition, at the points $x_k$ where $L_N$ is zero, we have

$$
P_{2N-1}(x_k)=Q_{N-1}(x_k)\hspace{1cm} k=0,1,\dots, N-1,
$$

and we see that through these $N$ points we can fully define $Q_{N-1}(x)$  and thereby the
integral. Note that we have chosen to let the numbering of the points run from $0$ to $N-1$.
The reason for this choice is that we wish to have the same numbering as the order of a
polynomial of degree $N-1$.  This numbering will be useful below when  we introduce the matrix
elements  which define the integration weights $w_i$.

## Integration points and weights with orthogonal polynomials
We develope then $Q_{N-1}(x)$ in terms of Legendre polynomials,
as done in Eq. ([eq:legexpansion](#eq:legexpansion)),

$$
\begin{equation}
  Q_{N-1}(x)=\sum_{i=0}^{N-1}\alpha_iL_i(x).
\label{eq:lsum1} \tag{15}
\end{equation}
$$

Using the orthogonality property of the Legendre polynomials we have

$$
\int_{-1}^1Q_{N-1}(x)dx=\sum_{i=0}^{N-1}\alpha_i\int_{-1}^1L_0(x)L_i(x)dx=2\alpha_0,
$$

where we have just inserted $L_0(x)=1$!

## Integration points and weights with orthogonal polynomials
Instead of an integration problem we need now to define the coefficient $\alpha_0$.
Since we know the values of $Q_{N-1}$ at the zeros of $L_N$, we may rewrite
Eq.  ([eq:lsum1](#eq:lsum1)) as

$$
\begin{equation}
  Q_{N-1}(x_k)=\sum_{i=0}^{N-1}\alpha_iL_i(x_k)=\sum_{i=0}^{N-1}\alpha_iL_{ik} \hspace{1cm} k=0,1,\dots, N-1.
\label{eq:lsum2} \tag{16}
\end{equation}
$$

Since the Legendre polynomials are linearly independent of each other, none
of the columns in the matrix $L_{ik}$ are linear combinations of the others.

## Integration points and weights with orthogonal polynomials
This means that the matrix $L_{ik}$ has an inverse with the properties

$$
\hat{L}^{-1}\hat{L} = \hat{I}.
$$

Multiplying both sides of Eq. ([eq:lsum2](#eq:lsum2)) with $\sum_{j=0}^{N-1}L_{ji}^{-1}$ results in

$$
\begin{equation}
  \sum_{i=0}^{N-1}(L^{-1})_{ki}Q_{N-1}(x_i)=\alpha_k.
\label{eq:lsum3} \tag{17}
\end{equation}
$$

## Integration points and weights with orthogonal polynomials
We can derive this result in an alternative way by defining the vectors

$$
\hat{x}_k=\left(\begin{array} {c} x_0\\
                                x_1\\
                                .\\
                                .\\
                                x_{N-1}\end{array}\right) \hspace{0.5cm}
\hat{\alpha}=\left(\begin{array} {c} \alpha_0\\
                                \alpha_1\\
                                .\\
                                .\\
                                \alpha_{N-1}\end{array}\right),
$$

and the matrix

$$
\hat{L}=\left(\begin{array} {cccc} L_0(x_0)  & L_1(x_0) &\dots &L_{N-1}(x_0)\\
                                   L_0(x_1)  & L_1(x_1) &\dots &L_{N-1}(x_1)\\
                                   \dots  & \dots &\dots &\dots\\
L_0(x_{N-1})  & L_1(x_{N-1}) &\dots &L_{N-1}(x_{N-1})
\end{array}\right).
$$

## Integration points and weights with orthogonal polynomials
We have then

$$
Q_{N-1}(\hat{x}_k) = \hat{L}\hat{\alpha},
$$

yielding (if $\hat{L}$ has an inverse)

$$
\hat{L}^{-1}Q_{N-1}(\hat{x}_k) = \hat{\alpha},
$$

which is Eq. ([eq:lsum3](#eq:lsum3)).

## Integration points and weights with orthogonal polynomials
Using the above results and the fact that

$$
\int_{-1}^1P_{2N-1}(x)dx=\int_{-1}^1Q_{N-1}(x)dx,
$$

we get

$$
\int_{-1}^1P_{2N-1}(x)dx=\int_{-1}^1Q_{N-1}(x)dx=2\alpha_0=
   2\sum_{i=0}^{N-1}(L^{-1})_{0i}P_{2N-1}(x_i).
$$

## Integration points and weights with orthogonal polynomials
If we identify the weights with $2(L^{-1})_{0i}$, where the points $x_i$ are
the zeros of $L_N$, we have an integration formula of the type

$$
\int_{-1}^1P_{2N-1}(x)dx=\sum_{i=0}^{N-1}\omega_iP_{2N-1}(x_i)
$$

and if our function $f(x)$  can be approximated by a polynomial $P$ of degree
$2N-1$, we have finally that

$$
\int_{-1}^1f(x)dx\approx \int_{-1}^1P_{2N-1}(x)dx=\sum_{i=0}^{N-1}\omega_iP_{2N-1}(x_i)  .
$$

In summary, the mesh points $x_i$ are defined by the zeros of an orthogonal polynomial of degree $N$, that is
$L_N$, while the weights are
given by $2(L^{-1})_{0i}$.

## Application to the case $N=2$

Let us apply the above formal results to the case $N=2$.
This means that we can approximate a function $f(x)$ with a
polynomial $P_3(x)$ of order $2N-1=3$.

The mesh points are the zeros of $L_2(x)=1/2(3x^2-1)$.
These points are $x_0=-1/\sqrt{3}$ and $x_1=1/\sqrt{3}$.

Specializing Eq. ([eq:lsum2](#eq:lsum2))

$$
Q_{N-1}(x_k)=\sum_{i=0}^{N-1}\alpha_iL_i(x_k) \hspace{1cm} k=0,1,\dots, N-1.
$$

to $N=2$ yields

$$
Q_1(x_0)=\alpha_0-\alpha_1\frac{1}{\sqrt{3}},
$$

and

$$
Q_1(x_1)=\alpha_0+\alpha_1\frac{1}{\sqrt{3}},
$$

since $L_0(x=\pm 1/\sqrt{3})=1$ and $L_1(x=\pm 1/\sqrt{3})=\pm 1/\sqrt{3}$.

## Application to the case $N=2$
The matrix $L_{ik}$ defined in Eq. ([eq:lsum2](#eq:lsum2)) is then

$$
\hat{L}=\left(\begin{array} {cc} 1  & -\frac{1}{\sqrt{3}}\\
                                   1  & \frac{1}{\sqrt{3}}\end{array}\right),
$$

with an inverse given by

$$
\hat{L}^{-1}=\frac{\sqrt{3}}{2}\left(\begin{array} {cc} \frac{1}{\sqrt{3}}  & \frac{1}{\sqrt{3}}\\
                                   -1  & 1\end{array}\right).
$$

The weights are given by the matrix elements $2(L_{0k})^{-1}$. We have thence
$\omega_0=1$ and $\omega_1=1$.

## Application to the case $N=2$
Obviously, there is no problem in changing the numbering of the matrix elements $i,k=0,1,2,\dots,N-1$ to
$i,k=1,2,\dots,N$.  We have chosen to start from zero, since we deal with polynomials of degree $N-1$.

Summarizing, for Legendre polynomials with $N=2$ we have
weights

$$
\omega : \left\{1,1\right\},
$$

and mesh points

$$
x : \left\{-\frac{1}{\sqrt{3}},\frac{1}{\sqrt{3}}\right\}.
$$

## Application to the case $N=2$
If we wish to integrate

$$
\int_{-1}^1f(x)dx,
$$

with $f(x)=x^2$, we approximate

$$
I=\int_{-1}^1x^2dx \approx \sum_{i=0}^{N-1}\omega_ix_i^2.
$$

## Application to the case $N=2$
The exact answer is $2/3$. Using $N=2$ with the above two weights
and mesh points we get

$$
I=\int_{-1}^1x^2dx =\sum_{i=0}^{1}\omega_ix_i^2=\frac{1}{3}+\frac{1}{3}=\frac{2}{3},
$$

the exact answer!

If we were to emply the trapezoidal rule we would get

$$
I=\int_{-1}^1x^2dx =\frac{b-a}{2}\left((a)^2+(b)^2\right)/2=
                       \frac{1-(-1)}{2}\left((-1)^2+(1)^2\right)/2=1!
$$

With just two points we can calculate exactly the integral for a second-order
polynomial since our methods approximates the exact function with higher
order polynomial.
How many points do you need with the trapezoidal rule in order to achieve a
similar accuracy?

## General integration intervals for Gauss-Legendre

Note that the Gauss-Legendre method is not limited
to an interval [-1,1], since we can always through a change of variable

$$
t=\frac{b-a}{2}x+\frac{b+a}{2},
$$

rewrite  the integral for an interval  [a,b]

$$
\int_a^bf(t)dt=\frac{b-a}{2}\int_{-1}^1f\left(\frac{(b-a)x}{2}+\frac{b+a}{2}\right)dx.
$$

## Mapping integration points and weights
If we have an integral on the form

$$
\int_0^{\infty}f(t)dt,
$$

we can choose new mesh points and weights by using the mapping

$$
\tilde{x}_i=tan\left\{\frac{\pi}{4}(1+x_i)\right\},
$$

and

$$
\tilde{\omega}_i= \frac{\pi}{4}\frac{\omega_i}{cos^2\left(\frac{\pi}{4}(1+x_i)\right)},
$$

where $x_i$ and $\omega_i$ are the original mesh points and weights in the
interval $[-1,1]$, while $\tilde{x}_i$ and $\tilde{\omega}_i$ are the new
mesh points and weights for the interval $[0,\infty)$.

## Mapping integration points and weights
To see  that this is correct by inserting the
the value of $x_i=-1$ (the lower end of the interval $[-1,1]$)
into the expression for $\tilde{x}_i$. That gives $\tilde{x}_i=0$,
the lower end of the interval $[0,\infty)$. For
$x_i=1$, we obtain $\tilde{x}_i=\infty$. To check that the new
weights are correct, recall that the weights should correspond to the
derivative of the mesh points. Try to convince yourself that the
above expression fulfills this condition.

## Other orthogonal polynomials, Laguerre polynomials

If we are able to rewrite our integral of Eq.  ([eq:generalint](#eq:generalint)) with a
weight function $W(x)=x^{\alpha}e^{-x}$ with integration limits
$[0,\infty)$, we could then use the Laguerre polynomials.
The polynomials form then the basis for the Gauss-Laguerre method which can be applied
to integrals of the form

$$
I=\int_0^{\infty}f(x)dx =\int_0^{\infty}x^{\alpha}e^{-x}g(x)dx.
$$

## Other orthogonal polynomials, Laguerre polynomials
These polynomials arise from the solution of the differential
equation

$$
\left(\frac{d^2 }{dx^2}-\frac{d }{dx}+\frac{\lambda}{x}-\frac{l(l+1)}{x^2}\right){\cal L}(x)=0,
$$

where $l$ is an integer $l\ge 0$ and $\lambda$ a constant. This equation
arises for example from the solution of the radial Schr\"odinger equation with
a centrally symmetric potential such as the Coulomb potential.

## Other orthogonal polynomials, Laguerre polynomials
The first few polynomials are

1
0
1

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

1
0
2

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

1
0
3

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

$$
{\cal L}_3(x)=6-18x+9x^2-x^3,
$$

and

$$
{\cal L}_4(x)=x^4-16x^3+72x^2-96x+24.
$$

## Other orthogonal polynomials, Laguerre polynomials
They fulfil the orthogonality relation

$$
\int_{0}^{\infty}e^{-x}{\cal L}_n(x)^2dx=1,
$$

and the recursion relation

$$
(n+1){\cal L}_{n+1}(x)=(2n+1-x){\cal L}_{n}(x)-n{\cal L}_{n-1}(x).
$$

## Other orthogonal polynomials, Hermite polynomials

In a similar way, for an integral which goes like

$$
I=\int_{-\infty}^{\infty}f(x)dx =\int_{-\infty}^{\infty}e^{-x^2}g(x)dx.
$$

we could use the Hermite polynomials in order to extract weights and mesh points.
The Hermite polynomials are the solutions of the following differential
equation

$$
\begin{equation}
   \frac{d^2H(x)}{dx^2}-2x\frac{dH(x)}{dx}+
       (\lambda-1)H(x)=0.
\label{eq:hermite} \tag{18}
\end{equation}
$$

## Other orthogonal polynomials, Hermite polynomials
A typical example is again the solution of Schrodinger's
equation, but this time with a harmonic oscillator potential.
The first few polynomials are

1
1
0

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

1
1
1

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

1
1
2

<
<
<
!
!
M
A
T
H
_
B
L
O
C
K

$$
H_3(x)=8x^3-12,
$$

and

$$
H_4(x)=16x^4-48x^2+12.
$$

They fulfil the orthogonality relation

$$
\int_{-\infty}^{\infty}e^{-x^2}H_n(x)^2dx=2^nn!\sqrt{\pi},
$$

and the recursion relation

$$
H_{n+1}(x)=2xH_{n}(x)-2nH_{n-1}(x).
$$

## Demonstration of Gaussian Quadrature
Let us here compare three methods for integrating, namely the trapezoidal rule,
Simpson's method and the Gauss-Legendre approach.
We choose two functions to integrate:

$$
\int_1^{100}\frac{\exp{(-x)}}{x}dx,
$$

and

$$
\int_{0}^{3}\frac{1}{2+x^2}dx.
$$

## Demonstration of Gaussian Quadrature, simple program
A program example which uses the trapezoidal rule, Simpson's rule
and the Gauss-Legendre method is included here.

        #include <iostream>
        #include "lib.h"
        using namespace std;
        //     Here we define various functions called by the main program
        //     this function defines the function to integrate
        double int_function(double x);
        //   Main function begins here
        int main()
        {
             int n;
             double a, b;
             cout << "Read in the number of integration points" << endl;
             cin >> n;
             cout << "Read in integration limits" << endl;
             cin >> a >> b;
        //   reserve space in memory for vectors containing the mesh points
        //   weights and function values for the use of the gauss-legendre
        //   method
             double *x = new double [n];
             double *w = new double [n];
        //   set up the mesh points and weights
             gauss_legendre(a, b,x,w, n);
        //   evaluate the integral with the Gauss-Legendre method
        //   Note that we initialize the sum
             double int_gauss = 0.;
             for ( int i = 0;  i < n; i++){
                int_gauss+=w[i]*int_function(x[i]);
             }
        //    final output
              cout << "Trapez-rule = " << trapezoidal_rule(a, b,n, int_function)
                   << endl;
              cout << "Simpson's rule = " << simpson(a, b,n, int_function)
                   << endl;
              cout << "Gaussian quad = " << int_gauss << endl;
              delete [] x;
              delete [] w;
              return 0;
        }  // end of main program
        //  this function defines the function to integrate
        double int_function(double x)
        {
          double value = 4./(1.+x*x);
          return value;
        } // end of function to evaluate

## Demonstration of Gaussian Quadrature
To be noted in this program is that we can transfer the name of a given function to integrate.
In the table here we show the results for the first integral using various
mesh points,.

$N$ 	  Trapez 	 Simpson 	 Gauss-Legendre

   10      	    1.821020    	    1.214025    	    0.1460448

   20      	    0.912678    	    0.609897    	    0.2178091

   40      	    0.478456    	    0.333714    	    0.2193834

   100     	    0.273724    	    0.231290    	    0.2193839

   1000    	    0.219984    	    0.219387    	    0.2193839

We note here that, since the area over where we integrate is rather large and the integrand
goes slowly to zero for large values of $x$, both the trapezoidal rule and Simpson's method
need quite many points in order to approach the Gauss-Legendre method.
This integrand demonstrates clearly the strength of the Gauss-Legendre method
(and other GQ methods as well), viz., few points
are needed in order to achieve a very high precision.

## Demonstration of Gaussian Quadrature
The second table however shows that for smaller integration intervals, both the trapezoidal rule
and Simpson's method compare well with the results obtained with the Gauss-Legendre
approach.

$N$ 	  Trapez 	 Simpson 	 Gauss-Legendre

   10      	    0.798861    	    0.799231    	    0.799233

   20      	    0.799140    	    0.799233    	    0.799233

   40      	    0.799209    	    0.799233    	    0.799233

   100     	    0.799229    	    0.799233    	    0.799233

   1000    	    0.799233    	    0.799233    	    0.799233

## Comparing methods and using symbolic Python
The following python code allows you to run interactively either in a browser or using ipython notebook. It compares the trapezoidal rule and Gaussian quadrature with the exact result from symbolic python **SYMPY** up to 1000 integration points for the integral

$$
I = 2 = \int_0^{\infty} x^2 \exp{-x} dx.
$$

For the trapezoidal rule the results will vary strongly depending on how the infinity limit is approximated. Try to run the code below for different finite approximations to $\infty$.

```python
from math import exp
import numpy as np
from sympy import Symbol, integrate, exp, oo

# function for the trapezoidal rule
def TrapezoidalRule(a,b,f,n):
   h = (b-a)/float(n)
   s = 0
   x = a
   for i in range(1,n,1):
       x = x+h
       s = s+ f(x)
   s = 0.5*(f(a)+f(b)) +s
   return h*s

# function for the Gaussian quadrature with Laguerre polynomials
def GaussLaguerreRule(n):
   s = 0
   xgauleg, wgauleg = np.polynomial.laguerre.laggauss(n)
   for i in range(1,n,1):
       s = s+ xgauleg[i]*xgauleg[i]*wgauleg[i]
   return s

#  function to compute
def function(x):
    return x*x*exp(-x)

# Integration limits for the Trapezoidal rule
a = 0.0; b = 10000.0
# define x as a symbol to be used by sympy
x = Symbol('x')
# find result from sympy
exact = integrate(function(x), (x, a, oo))
# set up the arrays for plotting the relative error
n = np.zeros(40); Trapez = np.zeros(4); LagGauss = np.zeros(4);
# find the relative error as function of integration points
for i in range(1, 3, 1):
    npts = 10**i
    n[i] = npts
    Trapez[i] = abs((TrapezoidalRule(a,b,function,npts)-exact)/exact)
    LagGauss[i] = abs((GaussLaguerreRule(npts)-exact)/exact)
print "Integration points=", n[1], n[2]
print "Trapezoidal relative error=", Trapez[1], Trapez[2]
print "LagGuass relative error=", LagGauss[1], LagGauss[2]
```

## Treatment of Singular Integrals
So-called principal value (PV) integrals are often employed in physics,
from Green's functions for scattering to dispersion relations.
Dispersion relations are often related to measurable quantities
and provide important consistency checks in atomic, nuclear and
particle physics.
A PV integral is defined as

$$
I(x)={\cal P}\int_a^bdt\frac{f(t)}{t-x}=\lim_{\epsilon\rightarrow 0^+}
\left[\int_a^{x-\epsilon}dt\frac{f(t)}{t-x}+\int_{x+\epsilon}^bdt\frac{f(t)}{t-x}\right],
$$

and
arises in applications
of Cauchy's residue theorem when the pole $x$  lies
on the real axis within the interval of integration $[a,b]$. Here ${\cal P}$ stands for the principal value. *An important assumption is that the function $f(t)$ is continuous
on the interval of integration*.

## Treatment of Singular Integrals
In case $f(t)$ is a closed form expression or it has an analytic continuation
in the complex plane, it may be  possible to obtain an expression on closed
form for the above integral.

However, the situation which we are often confronted with is that
$f(t)$ is only known at some points $t_i$ with corresponding
values $f(t_i)$. In order to obtain $I(x)$ we need to resort to a
numerical evaluation.

To evaluate such an integral, let us first rewrite it as

$$
{\cal P}\int_a^bdt\frac{f(t)}{t-x}=
\int_a^{x-\Delta}dt\frac{f(t)}{t-x}+\int_{x+\Delta}^bdt\frac{f(t)}{t-x}+
{\cal P}\int_{x-\Delta}^{x+\Delta}dt\frac{f(t)}{t-x},
$$

where we have isolated the principal value part in the last integral.

## Treatment of Singular Integrals, change of variables
Defining a new variable $u=t-x$, we can rewrite the principal value
integral as

$$
\begin{equation}
I_{\Delta}(x)={\cal P}\int_{-\Delta}^{+\Delta}du\frac{f(u+x)}{u}.
\label{eq:deltaint} \tag{19}
\end{equation}
$$

One possibility is to Taylor expand $f(u+x)$ around $u=0$, and compute
derivatives to a certain order as we did for the Trapezoidal rule or
Simpson's rule.
Since all terms with even powers of $u$ in the Taylor expansion dissapear,
we have that

$$
I_{\Delta}(x)\approx \sum_{n=0}^{N_{max}}f^{(2n+1)}(x)
                     \frac{\Delta^{2n+1}}{(2n+1)(2n+1)!}.
$$

## Treatment of Singular Integrals, higher-order derivatives
To evaluate higher-order derivatives may be both time
consuming and delicate from a numerical point of view, since
there is always the risk of loosing precision when calculating
derivatives numerically. Unless we have an analytic expression
for $f(u+x)$ and can evaluate the derivatives in a closed form,
the above approach is not the preferred one.

Rather, we show here how to use the Gauss-Legendre method
to compute Eq. ([eq:deltaint](#eq:deltaint)).
Let us first introduce a new variable $s=u/\Delta$ and rewrite
Eq. ([eq:deltaint](#eq:deltaint)) as

$$
\begin{equation}
I_{\Delta}(x)={\cal P}\int_{-1}^{+1}ds\frac{f(\Delta s+x)}{s}.
\label{eq:deltaint2} \tag{20}
\end{equation}
$$

## Treatment of Singular Integrals
The integration limits are now from $-1$ to $1$, as for the Legendre
polynomials.
The principal value in Eq. ([eq:deltaint2](#eq:deltaint2)) is however rather tricky
to evaluate numerically, mainly since computers have limited
precision. We will here use a subtraction trick often used
when dealing with singular integrals in numerical calculations.
We introduce first the calculus relation

$$
\int_{-1}^{+1} \frac{ds}{s} =0.
$$

It means that the curve $1/(s)$ has equal and opposite
areas on both sides of the singular point $s=0$.

## Treatment of Singular Integrals
If we then note that $f(x)$ is just a constant, we have also

$$
f(x)\int_{-1}^{+1} \frac{ds}{s}=\int_{-1}^{+1}f(x) \frac{ds}{s} =0.
$$

Subtracting this equation from
Eq. ([eq:deltaint2](#eq:deltaint2)) yields

$$
\begin{equation}
I_{\Delta}(x)={\cal P}\int_{-1}^{+1}ds\frac{f(\Delta s+x)}{s}=\int_{-1}^{+1}ds\frac{f(\Delta s+x)-f(x)}{s},
\label{eq:deltaint3} \tag{21}
\end{equation}
$$

and the integrand is no longer singular since we have that
$\lim_{s \rightarrow 0} (f(s+x) -f(x))=0$ and for the particular case
$s=0$ the integrand
is now finite.

## Treatment of Singular Integrals
Eq. ([eq:deltaint3](#eq:deltaint3)) is now rewritten using the Gauss-Legendre
method resulting in

$$
\begin{equation}
\int_{-1}^{+1}ds\frac{f(\Delta s+x)-f(x)}{s}=\sum_{i=1}^{N}\omega_i\frac{f(\Delta s_i+x)-f(x)}{s_i},
\label{eq:deltaint4} \tag{22}
\end{equation}
$$

where $s_i$ are the mesh points ($N$ in total) and $\omega_i$ are the weights.

In the selection of mesh points for  a PV integral, it is important
to use an even number of points, since an odd number of mesh
points always picks $s_i=0$ as one of the mesh points. The sum in
Eq. ([eq:deltaint4](#eq:deltaint4)) will then diverge.

## Treatment of Singular Integrals
Let us apply this method to the integral

$$
\begin{equation}
I(x)={\cal P}\int_{-1}^{+1}dt\frac{e^t}{t}.
\label{eq:deltaint5} \tag{23}
\end{equation}
$$

The integrand diverges at $x=t=0$. We
rewrite it using Eq. ([eq:deltaint3](#eq:deltaint3)) as

$$
\begin{equation}
{\cal P}\int_{-1}^{+1}dt\frac{e^t}{t}=\int_{-1}^{+1}\frac{e^t-1}{t},
\label{eq:deltaint6} \tag{24}
\end{equation}
$$

since $e^x=e^0=1$. With Eq. ([eq:deltaint4](#eq:deltaint4)) we have then

$$
\begin{equation}
\int_{-1}^{+1}\frac{e^t-1}{t}\approx \sum_{i=1}^{N}\omega_i\frac{e^{t_i}-1}{t_i}.
\label{eq:deltaint7} \tag{25}
\end{equation}
$$

## Treatment of Singular Integrals
The exact results is $2.11450175075....$. With just two mesh points we recall
from the previous subsection that $\omega_1=\omega_2=1$ and that the mesh points are the zeros of $L_2(x)$, namely $x_1=-1/\sqrt{3}$ and
$x_2=1/\sqrt{3}$. Setting $N=2$ and inserting these values in the last
equation gives

$$
I_2(x=0)=\sqrt{3}\left(e^{1/\sqrt{3}}-e^{-1/\sqrt{3}}\right)=2.1129772845.
$$

With six mesh points we get even the exact result to the tenth digit

$$
I_6(x=0)=2.11450175075!
$$

## Treatment of Singular Integrals
We can repeat the above subtraction trick  for more complicated
integrands.
First we modify the integration limits to $\pm \infty$ and use the fact
that

$$
\int_{-\infty}^{\infty} \frac{dk}{k-k_0}=
  \int_{-\infty}^{0} \frac{dk}{k-k_0}+
  \int_{0}^{\infty} \frac{dk}{k-k_0} =0.
$$

A change of variable $u=-k$ in the integral with limits from $-\infty$ to $0$ gives

$$
\int_{-\infty}^{\infty} \frac{dk}{k-k_0}=
  \int_{\infty}^{0} \frac{-du}{-u-k_0}+
  \int_{0}^{\infty} \frac{dk}{k-k_0}=  \int_{0}^{\infty} \frac{dk}{-k-k_0}+
  \int_{0}^{\infty} \frac{dk}{k-k_0}=0.
$$

## Treatment of Singular Integrals
It means that the curve $1/(k-k_0)$ has equal and opposite
areas on both sides of the singular point $k_0$. If we break
the integral into one over positive $k$ and one over
negative $k$, a change of variable $k\rightarrow -k$
allows us to rewrite the last equation as

$$
\int_{0}^{\infty} \frac{dk}{k^2-k_0^2} =0.
$$

## Treatment of Singular Integrals
We can use this to express a principal values integral
as

$$
\begin{equation}
  {\cal P}\int_{0}^{\infty} \frac{f(k)dk}{k^2-k_0^2} =
  \int_{0}^{\infty} \frac{(f(k)-f(k_0))dk}{k^2-k_0^2},
\label{eq:trick_pintegral} \tag{26}
\end{equation}
$$

where the right-hand side is no longer singular at
$k=k_0$, it is proportional to the derivative $df/dk$,
and can be evaluated numerically as any other integral.

Such a trick is often used when evaluating integral  equations.

## Example of a multidimensional integral
Here we show an example of a multidimensional integral which appears in quantum mechanical calculations.

The ansatz for the wave function for two electrons is given by the product of two
$1s$ wave functions as

$$
\Psi({\bf r}_1,{\bf r}_2)  =   \exp{-(\alpha (r_1+r_2))}.
$$

The integral we need to solve is the quantum mechanical expectation value of the correlation
energy between two electrons, namely

$$
I = \int_{-\infty}^{\infty} d{\bf r}_1d{\bf r}_2  \exp{-2(\alpha (r_1+r_2))}\frac{1}{|{\bf r}_1-{\bf r}_2|}.
$$

The integral has an exact solution $5\pi^2/16 = 0.19277$.

## Parts of code and brute force Gauss-Legendre quadrature
If we use Gaussian quadrature with Legendre polynomials (without rewriting the integral), we have

             double *x = new double [N];
             double *w = new double [N];
        //   set up the mesh points and weights
             GaussLegendrePoints(a,b,x,w, N);

        //   evaluate the integral with the Gauss-Legendre method
        //   Note that we initialize the sum
             double int_gauss = 0.;
        //   six-double loops
             for (int i=0;i<N;i++){
        	     for (int j = 0;j<N;j++){
        	     for (int k = 0;k<N;k++){
        	     for (int l = 0;l<N;l++){
        	     for (int m = 0;m<N;m++){
        	     for (int n = 0;n<N;n++){
                int_gauss+=w[i]*w[j]*w[k]*w[l]*w[m]*w[n]
               *int_function(x[i],x[j],x[k],x[l],x[m],x[n]);
             		}}}}}
        	}

## The function to integrate, code example

        //  this function defines the function to integrate
        double int_function(double x1, double y1, double z1, double x2, double y2, double z2)
        {
           double alpha = 2.;
        // evaluate the different terms of the exponential
           double exp1=-2*alpha*sqrt(x1*x1+y1*y1+z1*z1);
           double exp2=-2*alpha*sqrt(x2*x2+y2*y2+z2*z2);
           double deno=sqrt(pow((x1-x2),2)+pow((y1-y2),2)+pow((z1-z2),2));
           return exp(exp1+exp2)/deno;
        } // end of function to evaluate

## Laguerre polynomials
Using Legendre polynomials for the Gaussian quadrature is not very efficient. There are several reasons for this:
* You can easily end up in situations where the integrand diverges

* The limits $\pm \infty$ have to be approximated with a finite number

It is very useful here to change to spherical coordinates

$$
d{\bf r}_1d{\bf r}_2  = r_1^2dr_1 r_2^2dr_2 dcos(\theta_1)dcos(\theta_2)d\phi_1d\phi_2,
$$

and

$$
\frac{1}{r_{12}}= \frac{1}{\sqrt{r_1^2+r_2^2-2r_1r_2cos(\beta)}}
$$

with

$$
\cos(\beta) = \cos(\theta_1)\cos(\theta_2)+\sin(\theta_1)\sin(\theta_2)\cos(\phi_1-\phi_2))
$$

## Laguerre polynomials, the new integrand
This means that our integral becomes

$$
I=\int_0^{\infty} r_1^2dr_1 \int_0^{\infty}r_2^2dr_2 \int_0^{\pi}dcos(\theta_1)\int_0^{\pi}dcos(\theta_2)\int_0^{2\pi}d\phi_1\int_0^{2\pi}d\phi_2  \frac{\exp{-2\alpha (r_1+r_2)}}{r_{12}}
$$

where we have defined

$$
\frac{1}{r_{12}}= \frac{1}{\sqrt{r_1^2+r_2^2-2r_1r_2cos(\beta)}}
$$

with

$$
\cos(\beta) = \cos(\theta_1)\cos(\theta_2)+\sin(\theta_1)\sin(\theta_2)\cos(\phi_1-\phi_2))
$$

## Laguerre polynomials, new integration rule: Gauss-Laguerre
Our integral is now given by

$$
I=\int_0^{\infty} r_1^2dr_1 \int_0^{\infty}r_2^2dr_2 \int_0^{\pi}dcos(\theta_1)\int_0^{\pi}dcos(\theta_2)\int_0^{2\pi}d\phi_1\int_0^{2\pi}d\phi_2  \frac{\exp{-2\alpha (r_1+r_2)}}{r_{12}}
$$

For the angles we need to perform the integrations over $\theta_i\in [0,\pi]$ and $\phi_i \in [0,2\pi]$. However, for the radial part we can now either use
* Gauss-Legendre wth an appropriate mapping or

* Gauss-Laguerre taking properly care of the integrands involving the $r_i^2 \exp{-(2\alpha r_i)}$ terms.

## Results with  $N=20$ with Gauss-Legendre

$r_{\mathrm{max}}$	   Integral 	    Error

   1.00                  	    0.161419805    	    0.0313459063

   1.50                  	    0.180468967    	    0.012296744

   2.00                  	    0.177065182    	    0.0157005292

   2.50                  	    0.167970694    	    0.0247950165

   3.00                  	    0.156139391    	    0.0366263199

## Results for  $r_{\mathrm{max}}=2$ with Gauss-Legendre

$N$	   Integral 	     Error

   10     	    0.129834248    	    0.0629314631

   16     	    0.167860437    	    0.0249052742

   20     	    0.177065182    	    0.0157005292

   26     	    0.183543237    	    0.00922247353

   30     	    0.185795624    	    0.00697008738

## Results  with Gauss-Laguerre

$N$	   Integral 	     Error

   10     	    0.186457345    	    0.00630836601

   16     	    0.190113364    	    0.00265234708

   20     	    0.19108178     	    0.00168393093

   26     	    0.191831828    	    0.000933882594

   30     	    0.192113712    	    0.000651999339

The code that was used to generate these results can be found under the [program link](https://github.com/CompPhysics/ComputationalPhysics/blob/master/doc/Programs/LecturePrograms/programs/NumericalIntegration/cpp/program2.cpp).


# 11 ODE Applications (Projectile with linear air resistance) – Students

Let's apply our ODE solvers to some problems involving balls and projectiles. We will start with projectile motion with linear air resistances.

The `integrators.py` file from the lesson on [ODE integrators](https://py4phy.github.io/PHY432/modules/ODEs/integrators/) is used here (and named [`ode.py`](https://github.com/Py4Phy/PHY432-resources/blob/main/11_ODE_applications/ode.py)).

*Note: Incomplete notebook for students to work on*

```python
import numpy as np
import ode
```

```python
%matplotlib inline
import matplotlib.pyplot as plt
```

## Theory
Linear drag force

$$
\mathbf{F}_1 = -b_1 \mathbf{v}
$$

Equations of motion with force due to gravity $\mathbf{g} = -g \hat{\mathbf{e}}_y$

\begin{align}
\frac{d\mathbf{r}}{dt} &= \mathbf{v}\\
\frac{d\mathbf{v}}{dt} &= - g \hat{\mathbf{e}}_y -\frac{b_1}{m} \mathbf{v}
\end{align}

Bring into standard ODE form for

$$
\frac{d\mathbf{y}}{dt} = \mathbf{f}(t, \mathbf{y})
$$

as

$$
\mathbf{y} = \begin{pmatrix}
x\\
y\\
v_x\\
v_y
\end{pmatrix}, \quad
\mathbf{f} = \begin{pmatrix}
v_x\\
v_y\\
-\frac{b_1}{m} v_x\\
-g -\frac{b_1}{m} v_y
\end{pmatrix}
$$

(Based on Wang 2016, Ch 3.3.1)

## Python implementation with ODE solver

- Formulate the function `f()` for the standard ODE form
- Set up the integration loop:
  - only integrate until the particle hits ground, i.e. while $y ≥ 0$.
  - choose an appropriate ODE solver from `ode.py`

```python
def simulate(v0, h=0.01, b1=0.2, g=9.81, m=0.5):

    def f(t, y):
        # y = [x, y, vx, vy]
        raise NotImplementedError

    vx, vy = v0
    t = 0
    positions = []
    y = np.array([0, 0, vx, vy], dtype=np.float64)

    # add integration loop here

    return np.array(positions)

def initial_v(v, theta):
    x = np.deg2rad(theta)
    return v * np.array([np.cos(x), np.sin(x)])
```

Run for an initial velocity of 200 m/s and an angle of $\theta = 30^\circ$:

```python
r = simulate(initial_v(200, 30), h=0.01, b1=1)
```

Plot the motion in the $x$-$y$ plane.

```python
# plot
```

Plot for different starting angles:

```python
for angle in (5, 7.5, 10, 20, 30, 45):
    # ... complete
```


# Modelos de Tasa Corta

Comenzamos con algunas definiciones preliminares.

## Definiciones Preliminares

- Bank Account
- Factor de Descuento
- Tasa Compuesta Contínua
- Tasa Lineal
- Curva Cero Cupón
- Curva de Bonos Cero Cupón
- Tasa Forward Lineal
- Tasa Forward Instantánea

### Bank Account

Sea $B(t)$ el valor en $t\geq0$ de una cuenta bancaria que acumula intereses de forma contínua. Se asume que $B(0)=1$ y que la evolución en el tiempo de la cuenta está regida por la siguiente ecuación diferencial:

$$
dB(t)=r_tB(t)dt
$$

donde $r_t\geq 0$ $\forall t$. La solución de (1) es:

$$
B(t)=\exp{\left({\int_0}^t r_s ds\right)}
$$

La cantidad $r_t$ corresponde a la tasa instantánea que se aplica en el tiempo $t$.

Es evidente que si $r_t$ es una función determinística, el valor $B(t)$ es totalmente conocido en $t=0$. Por otra parte si $r_t$ es un proceso estocástico (una variable aleatoria $\forall t$ ), entonces $B(t)$ también lo es. Esta observación motiva la siguiente definición:

### Factor de Descuento

El factor de descuento (que puede ser estocástico) $D(t,T)$ entre los dos instante de tiempo $t$ y $T$ está dado por:

$$
D(t,T)=\frac{B(t)}{B(T)}=\exp\left(-{\int_t}^T r_s ds\right)
$$

**Contrariamente al modelamiento usual para Equity o FX, para instrumentos de tasa de interés siempre se supondrá que $r_t$ es un proceso estocástico.**

### Bono Cero Cupón

Un bono cero cupón que vence en $T$ (o bono a descuento) es un contrato que garantiza a su tenedor el pago de una unidad de moneda en el instante $T$. El valor de este contrato en $t\lt T$ se denota con $P(t,T)$. Claramente, $P(T,T)=1$ $\forall\space T$.

### Tasa Compuesta Contínua

La tasa compuesta contínua en $t$ para un instante de tiempo $T\gt t$ se denota con $R(t,T$) y se define a partir de la siguiente ecuación:

$$
P(t,T)=\exp\left[-R(t,T)\cdot (T-t)\right]
$$

Sigue que:

$$
R(t,T)=-\frac{\log P(t,T)}{T-t}
$$

### Tasa Lineal

De forma análoga, la tasa lineal se define a través de la siguiente ecuación:

$$
P(t,T)=\frac{1}{1+L(t,T)\cdot (T-t)}
$$

Y por lo tanto:

$$
L(t,T)=\left(\frac{1}{P(t,T)}-1\right)\cdot\frac{1}{T-t}
$$

### Curva Cero Cupón

Una curva cero cupón en $t$ es una función del tipo:

$$T\rightarrow\rho(t,T)$$

donde $\rho(t,T)$ representa una tasa en convención compuesta contínua, lineal o cualquier otra.

De forma similar se puede definir:

### Curva Bonos Cero Cupón

Una curva de bonos cero cupón en $t$ es una función del tipo:

$$T\rightarrow P(t,T)$$

donde $P(t,T)$ es el valor de.l bono cero cupón en $t$ que vence en $T$.

### Tasa Forward Lineal

La tasa forward lineal en $t$ que expira en $T$ y vence en $S$ con $S>T$ se denota con $F(t;T,S)$ y se define como:

$$
F(t;T,S):=\left(\frac{P(t,T)}{P(t,S)}-1\right)\cdot\frac{1}{S-T}=\left(\frac{W(t,S)}{W(t,T)}-1\right)\cdot\frac{1}{S-T}
$$

### Tasa Forward Instantánea

La tasa forward instantánea en $t$ que expira y vence en $T$ se denota con $f(t,T)$ y se define como:

$$
f(t,T):=\lim_{S \to T^+}F(t;T,S)=-\frac{\partial\log P(t,T)}{\partial T}
$$

Para entender la última igualdad considerar lo siguiente:

$$\left(\frac{P\left(T\right)-P\left(S\right)}{P\left(S\right)}\right)\cdot\frac{1}{S-T}$$

$$-\left(\frac{P\left(S\right)-P\left(T\right)}{S-T}\right)\cdot\frac{1}{P\left(S\right)}$$

El término a la izquierda del producto tiende a la derivada de $P$ para $S$ que tiende a $T$.

Sigue que:

$$
P(t,T)=\exp\left(-{\int_t}^T f(t,u)\cdot du\right)
$$

## Ecuación Fundamental

- Aquí vemos la primera generación de modelos de tasa de interés en tiempo continuo. Los llamados modelos de la tasa corta.

- Se modela explícitamente la evolución de la tasa instantánea $r_t$ como única fuente de incertidumbre en tasa de interés.

- Esta tasa de interés se considera libre de riesgo financiero, es decir representa la tasa que se obtiene en un depósito a plazo (u otra fuente de financiamiento similar) entre $t$ y $t + dt$.

- De este modo, los valores de todos los instrumentos de tasa de interés quedan ligados a esta única variable estocástica.

Sea:

$$
dr_t=m\left(r_t,t\right)dt+\sigma\left(r_t,t\right)dX_t
$$

la ecuación diferencial estocástica en la medida histórica que gobierna la evolución de la tasa $r_t$. Aquí $X_t$ es un movimiento browniano en la medida histórica y las funciones $m\left(r_t,t\right)$ y $\sigma\left(r_t,t\right)$ son suficientemente regulares.

Queremos encontrar una ecuación a las derivadas parciales, al estilo de lo hecho para encontrar la fórmula de BSM, que nos permita valorizar instrumentos que dependen de la tasa de interés.

Sean $Z_1\left(r_t,t\right)$ y $Z_2\left(r_t,t\right)$ dos tales instrumentos y construyamos un portfolio con ambos que sea insensible a movimientos de tasa de interés, o sea:

$$
\Pi\left(r,t\right)=Z_1\left(r,t\right)-\Delta Z_2\left(r,t\right)
$$

$$
\frac{\partial\Pi\left(r,t\right)}{\partial r}=0
$$

$$
\Delta=\left(\frac{\partial Z_1}{\partial r}\right)\cdot\left(\frac{\partial Z_2}{\partial r}\right)^{-1}
$$

De ahora en adelante, por simplicidad, se omitirá la dependencia de $r$ de $t$.

Utilizando el lema de Itô tenemos que:

$$
d\Pi=\frac{\partial\Pi}{\partial t}dt+\frac{\partial\Pi}{\partial r}dr+\frac{1}{2}\frac{\partial^2\Pi}{\partial r^2}dr^2\\
=\left[\frac{\partial\Pi}{\partial t}+\frac{1}{2}\frac{\partial^2\Pi}{\partial r^2}\sigma^2\left(r,t\right)\right]dt+\frac{\partial\Pi}{\partial r}\left[m\left(r,t\right)dt+\sigma\left(r,t\right)dX_t\right]\\
=\left[\frac{\partial\Pi}{\partial t}+\frac{\partial\Pi}{\partial r}m\left(r,t\right)+\frac{1}{2}\frac{\partial^2\Pi}{\partial r^2}\sigma^2\left(r,t\right)\right]dt+\frac{\partial\Pi}{\partial r}\sigma\left(r,t\right)dX_t
$$

Dado que $\frac{\partial\Pi}{\partial r}=0$ tenemos que:

$$
d\Pi=\left(\frac{\partial\Pi}{\partial t}+\frac{1}{2}\frac{\partial^2\Pi}{\partial r^2}\sigma^2\left(r,t\right)\right)dt
$$

Luego, como el portfolio no es estocástico, por ausencia de arbitraje, sigue que:

$$
d\Pi=\Pi r dt
$$

y finalmente se obtiene que:

$$
\frac{\partial\Pi}{\partial t}+\frac{1}{2}\frac{\partial^2\Pi}{\partial r^2}\sigma^2\left(r,t\right)-r\Pi=0
$$

Recordando las definiciones de $\Pi$ y $\Delta$ llegamos a:

$$
\frac{\partial\Pi}{\partial t}=\frac{\partial Z_1}{\partial t}-\Delta\frac{\partial Z_2}{\partial t}\\
\frac{\partial^2\Pi}{\partial r^2}=\frac{\partial^2 Z_1}{\partial r^2}-\Delta\frac{\partial^2 Z_2}{\partial r^2}
$$

Sustituyendo y reordenando se llega a:

$$
\left(\frac{\partial Z_1}{\partial t}+\frac{1}{2}\frac{\partial^2 Z_1}{\partial r^2}\sigma^2\left(r,t\right)-r Z_1\right)\cdot\left(\frac{\partial Z_1}{\partial r}\right)^{-1}=\left(\frac{\partial Z_2}{\partial t}+\frac{1}{2}\frac{\partial^2 Z_2}{\partial r^2}\sigma^2\left(r,t\right)-r Z_2\right)\cdot\left(\frac{\partial Z_2}{\partial r}\right)^{-1}
$$

- Cambio de valor por paso del tiempo (*theta*):

$$
\frac{\partial Z}{\partial t}
$$

- Valor por convexidad o estocasticidad de las tasas de interés (*gamma*):

$$
\frac{1}{2}\frac{\partial^2 Z}{\partial r^2}
$$

- Costo de financiamiento de la posición (*carry*):

$$
rZ
$$

- Cambio de valor por cambio en las tasas de interés (*delta*):

$$
\frac{\partial Z}{\partial r}
$$

Se concluye que para todo instrumento $Z(r,t)$ vale la siguiente ecuación diferencial a las derivadas parciales:

$$
\left(\frac{\partial Z}{\partial t}+\frac{1}{2}\frac{\partial^2 Z}{\partial r^2}\sigma^2\left(r,t\right)-r Z\right)\cdot\left(\frac{\partial Z}{\partial r}\right)^{-1}=-m^*\left(r,t\right)\\
\frac{\partial Z}{\partial t}+\frac{\partial Z}{\partial r}m^*\left(r,t\right)+\frac{1}{2}\frac{\partial^2 Z}{\partial r^2}\sigma^2\left(r,t\right)-r Z=0
$$

Volviendo a hacer explícita la dependencia de $r$ de $t$, si el instrumento $Z(r,t)$ vence en $T$ podemos encontrar su valor $Z(r_t,t)$ en $t$ resolviendo la ecuación (27) junto con la oportuna condición terminal o de borde:

$$
Z(r_T, T)=g(r_T)
$$

Aquí la función $g(r_T)$ (que es una función del valor final $r_T$ de la tasa $r_t$) representa el payoff o valor final de $Z$.

Busquemos una interpretación para $m*$, definamos los siguientes dos objetos (partimos calculando la ecuación diferencial estocástica para un instrumento $Z$):

$$
dZ=\left(\frac{\partial Z}{\partial t}+\frac{1}{2}\frac{\partial^2 Z}{\partial r^2}\sigma^2\left(r,t\right)+\frac{\partial Z}{\partial r}m\left(r,t\right)\right)dt+\sigma\left(r,t\right)\frac{\partial Z}{\partial r}dX_t \\
\mu_Z\cdot Z=\frac{\partial Z}{\partial t}+\frac{1}{2}\frac{\partial^2 Z}{\partial r^2}\sigma^2\left(r,t\right)+\frac{\partial Z}{\partial r}m\left(r,t\right) \\
\sigma_Z\cdot Z=\sigma\left(r,t\right)\frac{\partial Z}{\partial r}
$$

Recordando (27) llegamos a:

$$
\frac{\mu_Z-r}{\sigma_Z}=\frac{m-m^*}{\sigma}:=\lambda
$$

Al cociente $\lambda$ se le llama *market price of interest rate risk*. Indica que la razón entre el exceso de retorno de un instrumento y su volatilidad es la misma para todos los instrumentos. Vemos además, el nexo que hay entre $m^*$ y $\lambda$.

En particular tenemos que $m*=m-\lambda\sigma$ y por lo tanto podemos escribir:

$$
\begin{equation}
\frac{\partial Z}{\partial t}+\frac{\partial Z}{\partial r}\left(m-\lambda\sigma\right)+\frac{1}{2}\frac{\partial^2 Z}{\partial r^2}\sigma^2\left(r,t\right)-r Z=0
\end{equation}
$$

## Fórmula de Feynman-Kac

Sea $V(r,t)$ el precio de un instrumento con payoff final igual a $V(r_T,T)=g(r_T,T)$ que satisfaga la siguiente ecuación diferencial parcial:

$$
\begin{equation}
\frac{\partial V}{\partial t}+\frac{\partial V}{\partial r}m^*\left(r_t,t\right)+\frac{1}{2}\frac{\partial^2 V}{\partial r^2}\sigma^2\left(r_t,t\right)=R\left(r_t\right)V
\end{equation}
$$

Donde $R(r)$ es una cierta función. Entonces $V(r_t,t)$ está dado por:

$$
\begin{equation}
V\left(r_t,t\right)=\mathbb{E}_t^*\left[e^{-\int_{t}^{T}R\left(r_u\right)du}g\left(r_T,T\right)\right] \\
\end{equation}
$$

Donde,

$$
\begin{equation}
dr_t=m^*\left(r_t,t\right)dt+\sigma\left(r_t,t\right)dX_t
\end{equation}
$$

## La Medida Forward

**Ref:** https://www.amazon.com/s?k=stochastic+finance+a+numeraire+approach&__mk_es_US=ÅMÅŽÕÑ&ref=nb_sb_noss

El cálculo de este valor esperado (fórmula de Feynman-Kac) es dificultoso porque la variable estocástica $r_t$ aparece en ambos términos del producto. Si recordamos que:

$$
\begin{equation}
\newcommand{\E}{\mathrm{E}}
\newcommand{\Var}{\mathrm{Var}}
\newcommand{\Cov}{\mathrm{Cov}}
\Cov\left(Z,Y\right)=\E\left(ZY\right)-\E\left(Z\right)\E\left(Y\right) \\
\E\left(ZY\right)=\E\left(Z\right)\E\left(Y\right)+\Cov\left(Z,Y\right)
\end{equation}
$$

Podemos escribir,

$$
\begin{equation}
\newcommand{\Cov}{\mathrm{Cov}}
\mathbb{E}_t^*\left[e^{-\int_{t}^{T}r_udu}g\left(r_T,T\right)\right]=\mathbb{E}_t^*\left[e^{-\int_{t}^{T}r_udu}\right]\mathbb{E}_t^*\left[g\left(r_T,T\right)\right]+\Cov\left(e^{-\int_{t}^{T}r_udu},g\left(r_T,T\right)\right)
\end{equation}
$$

Si pudiéramos eliminar el término con covarianza, quedaría algo mucho más sencillo, de hecho ya sabemos que:

$$
\begin{equation}
\newcommand{\Cov}{\mathrm{Cov}}
\mathbb{E}_t^*\left[e^{-\int_{t}^{T}r_udu}\right]=Z\left(t,T\right)
\end{equation}
$$

- Entendemos por numerario (del francés numeraire) la unidad en que expresamos los precios. Por ejemplo, en vez de usar USD, podríamos expresar precios de instrumentos financieros en términos del precio de un bono del tesoro prefijado.

- Veremos que un cambio de numerario lleva a una simplificación en las fórmulas de valoración de derivados.

- Sea $V(r_t,t,T)$ el valor en USD de un cierto instrumento y $Z(r_t,t,T)$ el valor del bono cupón cero que vence en T. Consideremos la siguiente renormalización o cambio de numerario:

$$
\begin{equation}
\overline{V}\left(r_t,t,T\right)=\frac{V\left(r_t,t,T\right)}{Z\left(r_t,t,T\right)}
\end{equation}
$$

El valor $\overline{V}\left(r_t,t,T\right)$ satisface la siguiente ecuación diferencial:

$$
\begin{equation}
\frac{\partial\overline{V}}{\partial t}+\frac{\partial\overline{V}}{\partial r}\left[m^*\left(r_t,t\right)+\frac{\partial Z}{\partial r}\frac{1}{Z}\sigma^2\left(r_t,t\right)\right]+\frac{1}{2}\frac{\partial^2\overline{V}}{\partial r^2}\sigma^2\left(r_t,t\right)=0
\end{equation}
$$

$$R(r_t)=0\quad \forall t$$

Si consideramos que:

$$
\begin{equation}
\frac{dZ}{Z}=\ldots+\sigma_{Z}dX_t
\end{equation}
$$

Tenemos que:

$$
\begin{equation}
\sigma_{Z}\left(r_t,t\right)=\frac{\partial Z}{\partial r}\frac{1}{Z}\sigma\left(r_t,t\right)
\end{equation}
$$

Y por lo tanto llegamos a:

$$
\begin{equation}
\frac{\partial\overline{V}}{\partial t}+\frac{\partial\overline{V}}{\partial r}\left[m^*\left(r_t,t\right)+\sigma_Z\left(r_t,t\right)\sigma\left(r_t,t\right)\right]+\frac{1}{2}\frac{\partial^2\overline{V}}{\partial r^2}\sigma^2\left(r_t,t\right)=0
\end{equation}
$$

Esta ecuación tiene dos diferencias importantes respecto a la ecuación fundamental: el término $rV$ desaparece y $m^*$ aparece modificado por el producto entre el coeficiente de difusión del bono cero cupón y el coeficiente de difusión de la tasa corta.

Podemos ahora aplicar la fórmula de Feynman-Kac para obtener que la solución a la ecuación diferencial anterior está dada por:

$$
\begin{equation}
\overline{V}\left(r_t,t,T\right)=\mathbb{E}_t^{Z_T}\left[g\left(r_T,T\right)\right]
\end{equation}
$$

Donde el valor esperado $\mathbb{E}_t^{Z_T}$ se calcula con la probabilidad tal que:

$$
\begin{equation}
dr_t=\left[m^*\left(r_t,t\right)+\sigma_Z\left(r_t,t\right)\sigma\left(r_t,t\right)\right]dt+\sigma\left(r_t,t\right)dX_t
\end{equation}
$$

El proceso anterior suele llamarse forward risk neutral o T-forward risk neutral cuando se desea enfatizar el plazo del instrumento a valorar. Finalmente tenemos que:

$$
\begin{equation}
V\left(r_t,t,T\right)=Z\left(r_t,t,T\right)\mathbb{E}_t^{Z_T}\left[g\left(r_T,T\right)\right]
\end{equation}
$$