# Two-body Problems

The gravitational potential energy and forces involving two masses $a$ and $b$ are

$$
\begin{eqnarray}
U_{ab}&=&-\frac{Gm_am_b}{|\boldsymbol{r}_a-\boldsymbol{r}_b|},\\
\nonumber
F_{ba}&=&-\frac{Gm_am_b}{|\boldsymbol{r}_a-\boldsymbol{r}_b|^2}\hat{r}_{ab},\\
\nonumber
\hat{r}_{ab}&=&\frac{\boldsymbol{r}_b-\boldsymbol{r}_a}{|\boldsymbol{r}_a-\boldsymbol{r}_b|}.
\end{eqnarray}
$$

Here $G=6.67\times 10^{-11}$ Nm$^2$/kg$^2$, and $F_{ba}$ is the force
on $b$ due to $a$. By inspection, one can see that the force on $b$
due to $a$ and the force on $a$ due to $b$ are equal and opposite. The
net potential energy for a large number of masses would be

$$
\begin{equation}
U=\sum_{a<b}U_{ab}=\frac{1}{2}\sum_{a\ne b}U_{ab}.
\label{_auto1} \tag{1}
\end{equation}
$$

## Relative and Center of Mass Motion

Thus far, we have considered the trajectory as if the force is
centered around a fixed point. For two bodies interacting only with
one another, both masses circulate around the center of mass. One
might think that solutions would become more complex when both
particles move, but we will see here that the problem can be reduced
to one with a single body moving according to a fixed force by
expressing the trajectories for $\boldsymbol{r}_1$ and $\boldsymbol{r}_2$ into the
center-of-mass coordinate $\boldsymbol{R}_{\rm cm}$ and the relative
coordinate $\boldsymbol{r}$,

$$
\begin{eqnarray}
\boldsymbol{R}_{\rm cm}&\equiv&\frac{m_1\boldsymbol{r}_1+m_2\boldsymbol{r}_2}{m_1+m_2},\\
\nonumber
\boldsymbol{r}&\equiv&\boldsymbol{r}_1-\boldsymbol{r_2}.
\end{eqnarray}
$$

Here, we assume the two particles interact only with one another, so
$\boldsymbol{F}_{12}=-\boldsymbol{F}_{21}$ (where $\boldsymbol{F}_{ij}$ is the force on $i$
due to $j$. The equations of motion then become

$$
\begin{eqnarray}
\ddot{\boldsymbol{R}}_{\rm cm}&=&\frac{1}{m_1+m_2}\left\{m_1\ddot{\boldsymbol{r}}_1+m_2\ddot{\boldsymbol{r}}_2\right\}\\
\nonumber
&=&\frac{1}{m_1+m_2}\left\{\boldsymbol{F}_{12}+\boldsymbol{F}_{21}\right\}=0.\\
\ddot{\boldsymbol{r}}&=&\ddot{\boldsymbol{r}}_1-\ddot{\boldsymbol{r}}_2=\left(\frac{\boldsymbol{F}_{12}}{m_1}-\frac{\boldsymbol{F}_{21}}{m_2}\right)\\
\nonumber
&=&\left(\frac{1}{m_1}+\frac{1}{m_2}\right)\boldsymbol{F}_{12}.
\end{eqnarray}
$$

The first expression simply states that the center of mass coordinate
$\boldsymbol{R}_{\rm cm}$ moves at a fixed velocity. The second expression
can be rewritten in terms of the reduced mass $\mu$.

$$
\begin{eqnarray}
\mu \ddot{\boldsymbol{r}}&=&\boldsymbol{F}_{12},\\
\frac{1}{\mu}&=&\frac{1}{m_1}+\frac{1}{m_2},~~~~\mu=\frac{m_1m_2}{m_1+m_2}.
\end{eqnarray}
$$

Thus, one can treat the trajectory as a one-body problem where the
reduced mass is $\mu$, and a second trivial problem for the center of
mass. The reduced mass is especially convenient when one is
considering gravitational problems because then

$$
\begin{eqnarray}
\mu \ddot{r}&=&-\frac{Gm_1m_2}{r^2}\hat{r}\\
\nonumber
&=&-\frac{GM\mu}{r^2}\hat{r},~~~M\equiv m_1+m_2.
\end{eqnarray}
$$

For the gravitational problem, the reduced mass then falls out and the
trajectory depends only on the total mass $M$.

The kinetic energy and momenta also have analogues in center-of-mass
coordinates. The total and relative momenta are

$$
\begin{eqnarray}
\boldsymbol{P}&\equiv&\boldsymbol{p}_1+\boldsymbol{p}_2=M\dot{\boldsymbol{R}}_{\rm cm},\\
\nonumber
\boldsymbol{q}&\equiv&\mu\dot{\boldsymbol{r}}.
\end{eqnarray}
$$

With these definitions, a little algebra shows that the kinetic energy becomes

$$
\begin{eqnarray}
T&=&\frac{1}{2}m_1|\boldsymbol{v}_1|^2+\frac{1}{2}m_2|\boldsymbol{v}_2|^2\\
\nonumber
&=&\frac{1}{2}M|\dot{\boldsymbol{R}}_{\rm cm}|^2
+\frac{1}{2}\mu|\dot{\boldsymbol{r}}|^2\\
\nonumber
&=&\frac{P^2}{2M}+\frac{q^2}{2\mu}.
\end{eqnarray}
$$

The standard strategy is to transform into the center of mass frame,
then treat the problem as one of a single particle of mass $\mu$
undergoing a force $\boldsymbol{F}_{12}$. Scattering angles can also be
expressed in this frame, then transformed into the lab frame. In
practice, one sees examples in the literature where $d\sigma/d\Omega$
expressed in both the "center-of-mass" and in the "laboratory"
frame.

## Deriving Elliptical Orbits

Kepler's laws state that a gravitational orbit should be an ellipse
with the source of the gravitational field at one focus. Deriving this
is surprisingly messy. To do this, we first use angular momentum
conservation to transform the equations of motion so that it is in
terms of $r$ and $\theta$ instead of $r$ and $t$. The overall strategy
is to

1. Find equations of motion for $r$ and $t$ with no angle ($\theta$) mentioned, i.e. $d^2r/dt^2=\cdots$. Angular momentum conservation will be used, and the equation will involve the angular momentum $L$.

2. Use angular momentum conservation to find an expression for $\dot{\theta}$ in terms of $r$.

3. Use the chain rule to convert the equations of motions for $r$, an expression involving $r,\dot{r}$ and $\ddot{r}$, to one involving $r,dr/d\theta$ and $d^2r/d\theta^2$. This is quitecomplicated because the expressions will also involve a substitution $u=1/r$ so that one finds an expression in terms of $u$ and $\theta$.

4. Once $u(\theta)$ is found, you need to show that this can be converted to the familiar form for an ellipse.

The equations of motion give

$$
\begin{eqnarray}
\label{eq:radialeqofmotion} \tag{2}
\frac{d}{dt}r^2&=&\frac{d}{dt}(x^2+y^2)=2x\dot{x}+2y\dot{y}=2r\dot{r},\\
\nonumber
\dot{r}&=&\frac{x}{r}\dot{x}+\frac{y}{r}\dot{y},\\
\nonumber
\ddot{r}&=&\frac{x}{r}\ddot{x}+\frac{y}{r}\ddot{y}
+\frac{\dot{x}^2+\dot{y}^2}{r}
-\frac{\dot{r}^2}{r}.
\end{eqnarray}
$$

Recognizing that the numerator of the third term is the velocity squared, and that it can be written in polar coordinates,

$$
\begin{equation}
v^2=\dot{x}^2+\dot{y}^2=\dot{r}^2+r^2\dot{\theta}^2,
\label{_auto2} \tag{3}
\end{equation}
$$

one can write $\ddot{r}$ as

$$
\begin{eqnarray}
\label{eq:radialeqofmotion2} \tag{4}
\ddot{r}&=&\frac{F_x\cos\theta+F_y\sin\theta}{m}+\frac{\dot{r}^2+r^2\dot{\theta}^2}{r}-\frac{\dot{r}^2}{r}\\
\nonumber
&=&\frac{F}{m}+\frac{r^2\dot{\theta}^2}{r}\\
\nonumber
m\ddot{r}&=&F+\frac{L^2}{mr^3}.
\end{eqnarray}
$$

This derivation used the fact that the force was radial,
$F=F_r=F_x\cos\theta+F_y\sin\theta$, and that angular momentum is
$L=mrv_{\theta}=mr^2\dot{\theta}$. The term $L^2/mr^3=mv^2/r$ behaves
like an additional force. Sometimes this is referred to as a
centrifugal force, but it is not a force. Instead, it is the
consequence of considering the motion in a rotating (and therefore
accelerating) frame.

Now, we switch to the particular case of an attractive inverse square
force, $F=-\alpha/r^2$, and show that the trajectory, $r(\theta)$, is
an ellipse. To do this we transform derivatives w.r.t. time to
derivatives w.r.t. $\theta$ using the chain rule combined with angular
momentum conservation, $\dot{\theta}=L/mr^2$.

$$
\begin{eqnarray}
\label{eq:rtotheta} \tag{5}
\dot{r}&=&\frac{dr}{d\theta}\dot{\theta}=\frac{dr}{d\theta}\frac{L}{mr^2},\\
\nonumber
\ddot{r}&=&\frac{d^2r}{d\theta^2}\dot{\theta}^2
+\frac{dr}{d\theta}\left(\frac{d}{dr}\frac{L}{mr^2}\right)\dot{r}\\
\nonumber
&=&\frac{d^2r}{d\theta^2}\left(\frac{L}{mr^2}\right)^2
-2\frac{dr}{d\theta}\frac{L}{mr^3}\dot{r}\\
\nonumber
&=&\frac{d^2r}{d\theta^2}\left(\frac{L}{mr^2}\right)^2
-\frac{2}{r}\left(\frac{dr}{d\theta}\right)^2\left(\frac{L}{mr^2}\right)^2
\end{eqnarray}
$$

Equating the two expressions for $\ddot{r}$ in Eq.s ([4](#eq:radialeqofmotion2)) and ([5](#eq:rtotheta)) eliminates all the derivatives w.r.t. time, and provides a differential equation with only derivatives w.r.t. $\theta$,

$$
\begin{equation}
\label{eq:rdotdot} \tag{6}
\frac{d^2r}{d\theta^2}\left(\frac{L}{mr^2}\right)^2
-\frac{2}{r}\left(\frac{dr}{d\theta}\right)^2\left(\frac{L}{mr^2}\right)^2
=\frac{F}{m}+\frac{L^2}{m^2r^3},
\end{equation}
$$

that when solved yields the trajectory, i.e. $r(\theta)$. Up to this
point the expressions work for any radial force, not just forces that
fall as $1/r^2$.

The trick to simplifying this differential equation for the inverse
square problems is to make a substitution, $u\equiv 1/r$, and rewrite
the differential equation for $u(\theta)$.

$$
\begin{eqnarray}
r&=&1/u,\\
\nonumber
\frac{dr}{d\theta}&=&-\frac{1}{u^2}\frac{du}{d\theta},\\
\nonumber
\frac{d^2r}{d\theta^2}&=&\frac{2}{u^3}\left(\frac{du}{d\theta}\right)^2-\frac{1}{u^2}\frac{d^2u}{d\theta^2}.
\end{eqnarray}
$$

Plugging these expressions into Eq. ([6](#eq:rdotdot)) gives an
expression in terms of $u$, $du/d\theta$, and $d^2u/d\theta^2$. After
some tedious algebra,

$$
\begin{equation}
\frac{d^2u}{d\theta^2}=-u-\frac{F m}{L^2u^2}.
\label{_auto3} \tag{7}
\end{equation}
$$

For the attractive inverse square law force, $F=-\alpha u^2$,

$$
\begin{equation}
\frac{d^2u}{d\theta^2}=-u+\frac{m\alpha}{L^2}.
\label{_auto4} \tag{8}
\end{equation}
$$

The solution has two arbitrary constants, $A$ and $\theta_0$,

$$
\begin{eqnarray}
\label{eq:Ctrajectory} \tag{9}
u&=&\frac{m\alpha}{L^2}+A\cos(\theta-\theta_0),\\
\nonumber
r&=&\frac{1}{(m\alpha/L^2)+A\cos(\theta-\theta_0)}.
\end{eqnarray}
$$

The radius will be at a minimum when $\theta=\theta_0$ and at a
maximum when $\theta=\theta_0+\pi$. The constant $A$ is related to the
eccentricity of the orbit. When $A=0$ the radius is a constant
$r=L^2/(m\alpha)$, and the motion is circular. If one solved the
expression $mv^2/r=-\alpha/r^2$ for a circular orbit, using the
substitution $v=L/(mr)$, one would reproduce the expression
$r=L^2/(m\alpha)$.

The form describing the elliptical trajectory in
Eq. ([9](#eq:Ctrajectory)) can be identified as an ellipse with one
focus being the center of the ellipse by considering the definition of
an ellipse as being the points such that the sum of the two distances
between the two foci are a constant. Making that distance $2D$, the
distance between the two foci as $2a$, and putting one focus at the
origin,

$$
\begin{eqnarray}
2D&=&r+\sqrt{(r\cos\theta-2a)^2+r^2\sin^2\theta},\\
\nonumber
4D^2+r^2-4Dr&=&r^2+4a^2-4ar\cos\theta,\\
\nonumber
r&=&\frac{D^2-a^2}{D+a\cos\theta}=\frac{1}{D/(D^2-a^2)-a\cos\theta/(D^2-a^2)}.
\end{eqnarray}
$$

By inspection, this is the same form as Eq. ([9](#eq:Ctrajectory)) with $D/(D^2-a^2)=m\alpha/L^2$ and $a/(D^2-a^2)=A$.

Let us remind ourselves about what an ellipse is before we proceed.

```python
%matplotlib inline

import numpy as np
from matplotlib import pyplot as plt
from math import pi

u=1.     #x-position of the center
v=0.5    #y-position of the center
a=2.     #radius on the x-axis
b=1.5    #radius on the y-axis

t = np.linspace(0, 2*pi, 100)
plt.plot( u+a*np.cos(t) , v+b*np.sin(t) )
plt.grid(color='lightgray',linestyle='--')
plt.show()
```

## Effective or Centrifugal Potential

The total energy of a particle is

$$
\begin{eqnarray}
E&=&U(r)+\frac{1}{2}mv_\theta^2+\frac{1}{2}m\dot{r}^2\\
\nonumber
&=&U(r)+\frac{1}{2}mr^2\dot{\theta}^2+\frac{1}{2}m\dot{r}^2\\
\nonumber
&=&U(r)+\frac{L^2}{2mr^2}+\frac{1}{2}m\dot{r}^2.
\end{eqnarray}
$$

The second term then contributes to the energy like an additional
repulsive potential. The term is sometimes referred to as the
"centrifugal" potential, even though it is actually the kinetic energy
of the angular motion. Combined with $U(r)$, it is sometimes referred
to as the "effective" potential,

$$
\begin{eqnarray}
U_{\rm eff}(r)&=&U(r)+\frac{L^2}{2mr^2}.
\end{eqnarray}
$$

Note that if one treats the effective potential like a real potential, one would expect to be able to generate an effective force,

$$
\begin{eqnarray}
F_{\rm eff}&=&-\frac{d}{dr}U(r) -\frac{d}{dr}\frac{L^2}{2mr^2}\\
\nonumber
&=&F(r)+\frac{L^2}{mr^3}=F(r)+m\frac{v_\perp^2}{r},
\end{eqnarray}
$$

which is indeed matches the form for $m\ddot{r}$ in Eq. ([4](#eq:radialeqofmotion2)), which included the **centrifugal** force.

The following code plots this effective potential for a simple choice of parameters, with a standard gravitational potential $-\alpha/r$. Here we have chosen $L=m=\alpha=1$.

```python
# Common imports
import numpy as np
from math import *
import matplotlib.pyplot as plt

Deltax = 0.01
#set up arrays
xinitial = 0.3
xfinal = 5.0
alpha = 1.0   # spring constant
m = 1.0   # mass, you can change these
AngMom = 1.0  #  The angular momentum
n = ceil((xfinal-xinitial)/Deltax)
x = np.zeros(n)
for i in range(n):
    x[i] = xinitial+i*Deltax
V = np.zeros(n)
V = -alpha/x+0.5*AngMom*AngMom/(m*x*x)
# Plot potential
fig, ax = plt.subplots()
ax.set_xlabel('r[m]')
ax.set_ylabel('V[J]')
ax.plot(x, V)
fig.tight_layout()
plt.show()
```

### Gravitational force example

Using the above parameters, we can now study the evolution of the system using for example the velocity Verlet method.
This is done in the code here for an initial radius equal to the minimum of the potential well.  We seen then that the radius is always the same and corresponds to a circle (the radius is always constant).

```python
# Common imports
import numpy as np
import pandas as pd
from math import *
import matplotlib.pyplot as plt
import os

# Where to save the figures and data files
PROJECT_ROOT_DIR = "Results"
FIGURE_ID = "Results/FigureFiles"
DATA_ID = "DataFiles/"

if not os.path.exists(PROJECT_ROOT_DIR):
    os.mkdir(PROJECT_ROOT_DIR)

if not os.path.exists(FIGURE_ID):
    os.makedirs(FIGURE_ID)

if not os.path.exists(DATA_ID):
    os.makedirs(DATA_ID)

def image_path(fig_id):
    return os.path.join(FIGURE_ID, fig_id)

def data_path(dat_id):
    return os.path.join(DATA_ID, dat_id)

def save_fig(fig_id):
    plt.savefig(image_path(fig_id) + ".png", format='png')

# Simple Gravitational Force   -alpha/r

DeltaT = 0.01
#set up arrays
tfinal = 100.0
n = ceil(tfinal/DeltaT)
# set up arrays for t, v and r
t = np.zeros(n)
v = np.zeros(n)
r = np.zeros(n)
# Constants of the model, setting all variables to one for simplicity
alpha = 1.0
AngMom = 1.0  #  The angular momentum
m = 1.0  # scale mass to one
c1 = AngMom*AngMom/(m*m)
c2 = AngMom*AngMom/m
rmin = (AngMom*AngMom/m/alpha)
# Initial conditions
r0 = rmin
v0 = 0.0
r[0] = r0
v[0] = v0
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up acceleration
    a = -alpha/(r[i]**2)+c1/(r[i]**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    anew = -alpha/(r[i+1]**2)+c1/(r[i+1]**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
    # Plot position as function of time
fig, ax = plt.subplots(2,1)
ax[0].set_xlabel('time')
ax[0].set_ylabel('radius')
ax[0].plot(t,r)
ax[1].set_xlabel('time')
ax[1].set_ylabel('Velocity')
ax[1].plot(t,v)
save_fig("RadialGVV")
plt.show()
```

Changing the value of the initial position to a value where the energy is positive, leads to an increasing radius with time, a so-called unbound orbit. Choosing on the other hand an initial radius that corresponds to a negative energy and different from the minimum value leads to a radius that oscillates back and forth between two values.

### Harmonic Oscillator in two dimensions

Consider a particle of mass $m$ in a 2-dimensional harmonic oscillator with potential

$$
U=\frac{1}{2}kr^2=\frac{1}{2}k(x^2+y^2).
$$

If the orbit has angular momentum $L$, we can find the radius and angular velocity of the circular orbit as well as the b) the angular frequency of small radial perturbations.

We consider the effective potential. The radius of a circular orbit is at the minimum of the potential (where the effective force is zero).
The potential is plotted here with the parameters $k=m=0.1$ and $L=1.0$.

```python
# Common imports
import numpy as np
from math import *
import matplotlib.pyplot as plt

Deltax = 0.01
#set up arrays
xinitial = 1.0
xfinal = 5.0
k = 0.1   # spring constant
m = 0.1   # mass, you can change these
AngMom = 1.0  #  The angular momentum
n = ceil((xfinal-xinitial)/Deltax)
x = np.zeros(n)
for i in range(n):
    x[i] = xinitial+i*Deltax
V = np.zeros(n)
V = 0.5*k*x*x+0.5*AngMom*AngMom/(m*x*x)
# Plot potential
fig, ax = plt.subplots()
ax.set_xlabel('r[m]')
ax.set_ylabel('V[J]')
ax.plot(x, V)
fig.tight_layout()
plt.show()
```

$$
\begin{eqnarray*}
U_{\rm eff}&=&\frac{1}{2}kr^2+\frac{L^2}{2mr^2}
\end{eqnarray*}
$$

The effective potential looks like that of a harmonic oscillator for
large $r$, but for small $r$, the centrifugal potential repels the
particle from the origin. The combination of the two potentials has a
minimum for at some radius $r_{\rm min}$.

$$
\begin{eqnarray*}
0&=&kr_{\rm min}-\frac{L^2}{mr_{\rm min}^3},\\
r_{\rm min}&=&\left(\frac{L^2}{mk}\right)^{1/4},\\
\dot{\theta}&=&\frac{L}{mr_{\rm min}^2}=\sqrt{k/m}.
\end{eqnarray*}
$$

For particles at $r_{\rm min}$ with $\dot{r}=0$, the particle does not
accelerate and $r$ stays constant, i.e. a circular orbit. The radius
of the circular orbit can be adjusted by changing the angular momentum
$L$.

For the above parameters this minimum is at $r_{\rm min}=1$.

 Now consider small vibrations about $r_{\rm min}$. The effective spring constant is the curvature of the effective potential.

$$
\begin{eqnarray*}
k_{\rm eff}&=&\left.\frac{d^2}{dr^2}U_{\rm eff}(r)\right|_{r=r_{\rm min}}=k+\frac{3L^2}{mr_{\rm min}^4}\\
&=&4k,\\
\omega&=&\sqrt{k_{\rm eff}/m}=2\sqrt{k/m}=2\dot{\theta}.
\end{eqnarray*}
$$

Here, the second step used the result of the last step from part
(a). Because the radius oscillates with twice the angular frequency,
the orbit has two places where $r$ reaches a minimum in one
cycle. This differs from the inverse-square force where there is one
minimum in an orbit. One can show that the orbit for the harmonic
oscillator is also elliptical, but in this case the center of the
potential is at the center of the ellipse, not at one of the foci.

The solution is also simple to write down exactly in Cartesian coordinates. The $x$ and $y$ equations of motion separate,

$$
\begin{eqnarray*}
\ddot{x}&=&-kx,\\
\ddot{y}&=&-ky.
\end{eqnarray*}
$$

So the general solution can be expressed as

$$
\begin{eqnarray*}
x&=&A\cos\omega_0 t+B\sin\omega_0 t,\\
y&=&C\cos\omega_0 t+D\sin\omega_0 t.
\end{eqnarray*}
$$

The code here finds the solution for $x$ and $y$ using the code we developed in homework 4.

```python

DeltaT = 0.01
#set up arrays
tfinal = 10.0
n = ceil(tfinal/DeltaT)
# set up arrays
t = np.zeros(n)
v = np.zeros((n,2))
r = np.zeros((n,2))
radius = np.zeros(n)
# Constants of the model
k = 0.1   # spring constant
m = 0.1   # mass, you can change these
omega02 = sqrt(k/m)  # Frequency
AngMom = 1.0  #  The angular momentum
rmin = (AngMom*AngMom/k/m)**0.25
# Initial conditions as compact 2-dimensional arrays
#x0 =rmin*0.5; y0 = sqrt(rmin*rmin-x0*x0)
x0 = 1.0; y0= 1.0
r0 = np.array([x0,y0])
v0 = np.array([0.0,0.0])
r[0] = r0
v[0] = v0
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up the acceleration
    a =  -r[i]*omega02
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    anew = -r[i+1]*omega02
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
# Plot position as function of time
radius = np.sqrt(r[:,0]**2+r[:,1]**2)
fig, ax = plt.subplots(3,1)
ax[0].set_xlabel('time')
ax[0].set_ylabel('radius squared')
ax[0].plot(t,r[:,0]**2+r[:,1]**2)
ax[1].set_xlabel('time')
ax[1].set_ylabel('x position')
ax[1].plot(t,r[:,0])
ax[2].set_xlabel('time')
ax[2].set_ylabel('y position')
ax[2].plot(t,r[:,1])

fig.tight_layout()
save_fig("2DimHOVV")
plt.show()
```

With some work using double angle formulas, one can calculate

$$
\begin{eqnarray*}
r^2&=&x^2+y^2\\
\nonumber
&=&(A^2+C^2)\cos^2(\omega_0t)+(B^2+D^2)\sin^2\omega_0t+(AB+CD)\cos(\omega_0t)\sin(\omega_0t)\\
\nonumber
&=&\alpha+\beta\cos 2\omega_0 t+\gamma\sin 2\omega_0 t,\\
\alpha&=&\frac{A^2+B^2+C^2+D^2}{2},~~\beta=\frac{A^2-B^2+C^2-D^2}{2},~~\gamma=AB+CD,\\
r^2&=&\alpha+(\beta^2+\gamma^2)^{1/2}\cos(2\omega_0 t-\delta),~~~\delta=\arctan(\gamma/\beta),
\end{eqnarray*}
$$

and see that radius oscillates with frequency $2\omega_0$. The
factor of two comes because the oscillation $x=A\cos\omega_0t$ has two
maxima for $x^2$, one at $t=0$ and one a half period later.

The following code shows first how we can solve this problem using the radial degrees of freedom only.

```python
DeltaT = 0.01
#set up arrays
tfinal = 10.0
n = ceil(tfinal/DeltaT)
# set up arrays for t, v and r
t = np.zeros(n)
v = np.zeros(n)
r = np.zeros(n)
E = np.zeros(n)
# Constants of the model
AngMom = 1.0  #  The angular momentum
m = 0.1
k = 0.1
omega02 = k/m
c1 = AngMom*AngMom/(m*m)
c2 = AngMom*AngMom/m
rmin = (AngMom*AngMom/k/m)**0.25
# Initial conditions
r0 = rmin
v0 = 0.0
r[0] = r0
v[0] = v0
E[0] = 0.5*m*v0*v0+0.5*k*r0*r0+0.5*c2/(r0*r0)
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up acceleration
    a = -r[i]*omega02+c1/(r[i]**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    anew = -r[i+1]*omega02+c1/(r[i+1]**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
    E[i+1] = 0.5*m*v[i+1]*v[i+1]+0.5*k*r[i+1]*r[i+1]+0.5*c2/(r[i+1]*r[i+1])
    # Plot position as function of time
fig, ax = plt.subplots(2,1)
ax[0].set_xlabel('time')
ax[0].set_ylabel('radius')
ax[0].plot(t,r)
ax[1].set_xlabel('time')
ax[1].set_ylabel('Energy')
ax[1].plot(t,E)
save_fig("RadialHOVV")
plt.show()
```

## Stability of Orbits

The effective force can be extracted from the effective potential, $U_{\rm eff}$. Beginning from the equations of motion, Eq. ([2](#eq:radialeqofmotion)), for $r$,

$$
\begin{eqnarray}
m\ddot{r}&=&F+\frac{L^2}{mr^3}\\
\nonumber
&=&F_{\rm eff}\\
\nonumber
&=&-\partial_rU_{\rm eff},\\
\nonumber
F_{\rm eff}&=&-\partial_r\left[U(r)+(L^2/2mr^2)\right].
\end{eqnarray}
$$

For a circular orbit, the radius must be fixed as a function of time,
so one must be at a maximum or a minimum of the effective
potential. However, if one is at a maximum of the effective potential
the radius will be unstable. For the attractive Coulomb force the
effective potential will be dominated by the $-\alpha/r$ term for
large $r$ because the centrifugal part falls off more quickly, $\sim
1/r^2$. At low $r$ the centrifugal piece wins and the effective
potential is repulsive. Thus, the potential must have a minimum
somewhere with negative potential. The circular orbits are then stable
to perturbation.

The effective potential is sketched for two cases, a $1/r$ attractive
potential and a $1/r^3$ attractive potential. The $1/r$ case has a
stable minimum, whereas the circular orbit in the $1/r^3$ case is
unstable.

If one considers a potential that falls as $1/r^3$, the situation is
reversed and the point where $\partial_rU$ disappears will be a local
maximum rather than a local minimum. **Fig to come here with code**

The repulsive centrifugal piece dominates at large $r$ and the attractive
Coulomb piece wins out at small $r$. The circular orbit is then at a
maximum of the effective potential and the orbits are unstable. It is
the clear that for potentials that fall as $r^n$, that one must have
$n>-2$ for the orbits to be stable.

Consider a potential $U(r)=\beta r$. For a particle of mass $m$ with
angular momentum $L$, find the angular frequency of a circular
orbit. Then find the angular frequency for small radial perturbations.

For the circular orbit you search for the position $r_{\rm min}$ where the effective potential is minimized,

$$
\begin{eqnarray*}
\partial_r\left\{\beta r+\frac{L^2}{2mr^2}\right\}&=&0,\\
\beta&=&\frac{L^2}{mr_{\rm min}^3},\\
r_{\rm min}&=&\left(\frac{L^2}{\beta m}\right)^{1/3},\\
\dot{\theta}&=&\frac{L}{mr_{\rm min}^2}=\frac{\beta^{2/3}}{(mL)^{1/3}}
\end{eqnarray*}
$$

Now, we can find the angular frequency of small perturbations about the circular orbit. To do this we find the effective spring constant for the effective potential,

$$
\begin{eqnarray*}
k_{\rm eff}&=&\partial_r^2 \left.U_{\rm eff}\right|_{r_{\rm min}}\\
&=&\frac{3L^2}{mr_{\rm min}^4},\\
\omega&=&\sqrt{\frac{k_{\rm eff}}{m}}\\
&=&\frac{\beta^{2/3}}{(mL)^{1/3}}\sqrt{3}.
\end{eqnarray*}
$$

If the two frequencies, $\dot{\theta}$ and $\omega$, differ by an
integer factor, the orbit's trajectory will repeat itself each time
around. This is the case for the inverse-square force,
$\omega=\dot{\theta}$, and for the harmonic oscillator,
$\omega=2\dot{\theta}$. In this case, $\omega=\sqrt{3}\dot{\theta}$,
and the angles at which the maxima and minima occur change with each
orbit.

### Code example with gravitional force

The code example here is meant to illustrate how we can make a plot of the final orbit. We solve the equations in polar coordinates (the example here uses the minimum of the potential as initial value) and then we transform back to cartesian coordinates and plot $x$ versus $y$. We see that we get a perfect circle when we place ourselves at the minimum of the potential energy, as expected.

```python

# Simple Gravitational Force   -alpha/r

DeltaT = 0.01
#set up arrays
tfinal = 8.0
n = ceil(tfinal/DeltaT)
# set up arrays for t, v and r
t = np.zeros(n)
v = np.zeros(n)
r = np.zeros(n)
phi = np.zeros(n)
x = np.zeros(n)
y = np.zeros(n)
# Constants of the model, setting all variables to one for simplicity
alpha = 1.0
AngMom = 1.0  #  The angular momentum
m = 1.0  # scale mass to one
c1 = AngMom*AngMom/(m*m)
c2 = AngMom*AngMom/m
rmin = (AngMom*AngMom/m/alpha)
# Initial conditions, place yourself at the potential min
r0 = rmin
v0 = 0.0  # starts at rest
r[0] = r0
v[0] = v0
phi[0] = 0.0
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up acceleration
    a = -alpha/(r[i]**2)+c1/(r[i]**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    anew = -alpha/(r[i+1]**2)+c1/(r[i+1]**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    t[i+1] = t[i] + DeltaT
    phi[i+1] = t[i+1]*c2/(r0**2)
# Find cartesian coordinates for easy plot
x = r*np.cos(phi)
y = r*np.sin(phi)
fig, ax = plt.subplots(3,1)
ax[0].set_xlabel('time')
ax[0].set_ylabel('radius')
ax[0].plot(t,r)
ax[1].set_xlabel('time')
ax[1].set_ylabel('Angle $\cos{\phi}$')
ax[1].plot(t,np.cos(phi))
ax[2].set_ylabel('y')
ax[2].set_xlabel('x')
ax[2].plot(x,y)

save_fig("Phasespace")
plt.show()
```

Try to change the initial value for $r$ and see what kind of orbits you get.
In order to test different energies, it can be useful to look at the plot of the effective potential discussed above.

However, for orbits different from a circle the above code would need modifications in order to allow us to display say an ellipse. For the latter, it is much easier to run our code in cartesian coordinates, as done here. In this code we test also energy conservation and see that it is conserved to numerical precision. The code here is a simple extension of the code we developed for homework 4.

```python
# Common imports
import numpy as np
import pandas as pd
from math import *
import matplotlib.pyplot as plt

DeltaT = 0.01
#set up arrays
tfinal = 10.0
n = ceil(tfinal/DeltaT)
# set up arrays
t = np.zeros(n)
v = np.zeros((n,2))
r = np.zeros((n,2))
E = np.zeros(n)
# Constants of the model
m = 1.0   # mass, you can change these
alpha = 1.0
# Initial conditions as compact 2-dimensional arrays
x0 = 0.5; y0= 0.
r0 = np.array([x0,y0])
v0 = np.array([0.0,1.0])
r[0] = r0
v[0] = v0
rabs = sqrt(sum(r[0]*r[0]))
E[0] = 0.5*m*(v[0,0]**2+v[0,1]**2)-alpha/rabs
# Start integrating using the Velocity-Verlet  method
for i in range(n-1):
    # Set up the acceleration
    rabs = sqrt(sum(r[i]*r[i]))
    a =  -alpha*r[i]/(rabs**3)
    # update velocity, time and position using the Velocity-Verlet method
    r[i+1] = r[i] + DeltaT*v[i]+0.5*(DeltaT**2)*a
    rabs = sqrt(sum(r[i+1]*r[i+1]))
    anew = -alpha*r[i+1]/(rabs**3)
    v[i+1] = v[i] + 0.5*DeltaT*(a+anew)
    E[i+1] = 0.5*m*(v[i+1,0]**2+v[i+1,1]**2)-alpha/rabs
    t[i+1] = t[i] + DeltaT
# Plot position as function of time
fig, ax = plt.subplots(3,1)
ax[0].set_ylabel('y')
ax[0].set_xlabel('x')
ax[0].plot(r[:,0],r[:,1])
ax[1].set_xlabel('time')
ax[1].set_ylabel('y position')
ax[1].plot(t,r[:,0])
ax[2].set_xlabel('time')
ax[2].set_ylabel('y position')
ax[2].plot(t,r[:,1])

fig.tight_layout()
save_fig("2DimGravity")
plt.show()
print(E)
```

## Scattering and Cross Sections

Scattering experiments don't measure entire trajectories. For elastic
collisions, they measure the distribution of final scattering angles
at best. Most experiments use targets thin enough so that the number
of scatterings is typically zero or one. The cross section, $\sigma$,
describes the cross-sectional area for particles to scatter with an
individual target atom or nucleus. Cross section measurements form the
basis for MANY fields of physics. BThe cross section, and the
differential cross section, encapsulates everything measurable for a
collision where all that is measured is the final state, e.g. the
outgoing particle had momentum $\boldsymbol{p}_f$. y studying cross sections,
one can infer information about the potential interaction between the
two particles. Inferring, or constraining, the potential from the
cross section is a classic {\it inverse} problem. Collisions are
either elastic or inelastic. Elastic collisions are those for which
the two bodies are in the same internal state before and after the
collision. If the collision excites one of the participants into a
higher state, or transforms the particles into different species, or
creates additional particles, the collision is inelastic. Here, we
consider only elastic collisions.

For Coulomb forces, the cross section is infinite because the range of
the Coulomb force is infinite, but for interactions such as the strong
interaction in nuclear or particle physics, there is no long-range
force and cross-sections are finite. Even for Coulomb forces, the part
of the cross section that corresponds to a specific scattering angle,
$d\sigma/d\Omega$, which is a function of the scattering angle
$\theta_s$ is still finite.

If a particle travels through a thin target, the chance the particle
scatters is $P_{\rm scatt}=\sigma dN/dA$, where $dN/dA$ is the number
of scattering centers per area the particle encounters. If the density
of the target is $\rho$ particles per volume, and if the thickness of
the target is $t$, the areal density (number of target scatterers per
area) is $dN/dA=\rho t$. Because one wishes to quantify the collisions
independently of the target, experimentalists measure scattering
probabilities, then divide by the areal density to obtain
cross-sections,

$$
\begin{eqnarray}
\sigma=\frac{P_{\rm scatt}}{dN/dA}.
\end{eqnarray}
$$

Instead of merely stating that a particle collided, one can measure
the probability the particle scattered by a given angle. The
scattering angle $\theta_s$ is defined so that at zero the particle is
unscattered and at $\theta_s=\pi$ the particle is scattered directly
backward. Scattering angles are often described in the center-of-mass
frame, but that is a detail we will neglect for this first discussion,
where we will consider the scattering of particles moving classically
under the influence of fixed potentials $U(\boldsymbol{r})$. Because the
distribution of scattering angles can be measured, one expresses the
differential cross section,

$$
\begin{equation}
\frac{d^2\sigma}{d\cos\theta_s~d\phi}.
\label{_auto5} \tag{10}
\end{equation}
$$

Usually, the literature expresses differential cross sections as

$$
\begin{equation}
d\sigma/d\Omega=\frac{d\sigma}{d\cos\theta d\phi}=\frac{1}{2\pi}\frac{d\sigma}{d\cos\theta},
\label{_auto6} \tag{11}
\end{equation}
$$

where the last equivalency is true when the scattering does not depend
on the azimuthal angle $\phi$, as is the case for spherically
symmetric potentials.

The differential solid angle $d\Omega$ can be thought of as the area
subtended by a measurement, $dA_d$, divided by $r^2$, where $r$ is the
distance to the detector,

$$
\begin{eqnarray}
dA_d=r^2 d\Omega.
\end{eqnarray}
$$

With this definition $d\sigma/d\Omega$ is independent of the distance
from which one places the detector, or the size of the detector (as
long as it is small).

Differential scattering cross sections are calculated by assuming a
random distribution of impact parameters $b$. These represent the
distance in the $xy$ plane for particles moving in the $z$ direction
relative to the scattering center. An impact parameter $b=0$ refers to
being aimed directly at the target's center. The impact parameter
describes the transverse distance from the $z=0$ axis for the
trajectory when it is still far away from the scattering center and
has not yet passed it. The differential cross section can be expressed
in terms of the impact parameter,

$$
\begin{equation}
d\sigma=2\pi bdb,
\label{_auto7} \tag{12}
\end{equation}
$$

which is the area of a thin ring of radius $b$ and thickness $db$. In
classical physics, one can calculate the trajectory given the incoming
kinetic energy $E$ and the impact parameter if one knows the mass and
potential. From the trajectory, one then finds the scattering angle
$\theta_s(b)$. The differential cross section is then

$$
\begin{equation}
\frac{d\sigma}{d\Omega}=\frac{1}{2\pi}\frac{d\sigma}{d\cos\theta_s}=b\frac{db}{d\cos\theta_s}=\frac{b}{(d/db)\cos\theta_s(b)}.
\label{_auto8} \tag{13}
\end{equation}
$$

Typically, one would calculate $\cos\theta_s$ and $(d/db)\cos\theta_s$
as functions of $b$. This is sufficient to plot the differential cross
section as a function of $\theta_s$.

The total cross section is

$$
\begin{equation}
\sigma_{\rm tot}=\int d\Omega\frac{d\sigma}{d\Omega}=2\pi\int d\cos\theta_s~\frac{d\sigma}{d\Omega}.
\label{_auto9} \tag{14}
\end{equation}
$$

Even if the total cross section is infinite, e.g. Coulomb forces, one
can still have a finite differential cross section as we will see
later on.

An asteroid of mass $m$ and kinetic energy $E$ approaches a planet of
radius $R$ and mass $M$. What is the cross section for the asteroid to
impact the planet?

### Solution

Calculate the maximum impact parameter, $b_{\rm max}$, for which the asteroid will hit the planet. The total cross  section for impact is $\sigma_{\rm impact}=\pi b_{\rm max}^2$. The maximum cross-section can be found with the help of angular momentum conservation. The asteroid's incoming momentum is $p_0=\sqrt{2mE}$ and the angular momentum is $L=p_0b$. If the asteroid just grazes the planet, it is moving with zero radial kinetic energy at impact. Combining energy and angular momentum conservation and having $p_f$ refer to the momentum of the asteroid at a distance $R$,

$$
\begin{eqnarray*}
\frac{p_f^2}{2m}-\frac{GMm}{R}&=&E,\\
p_fR&=&p_0b_{\rm max},
\end{eqnarray*}
$$

allows one to solve for $b_{\rm max}$,

$$
\begin{eqnarray*}
b_{\rm max}&=&R\frac{p_f}{p_0}\\
&=&R\frac{\sqrt{2m(E+GMm/R)}}{\sqrt{2mE}}\\
\sigma_{\rm impact}&=&\pi R^2\frac{E+GMm/R}{E}.
\end{eqnarray*}
$$

## Rutherford Scattering

This refers to the calculation of $d\sigma/d\Omega$ due to an inverse
square force, $F_{12}=\pm\alpha/r^2$ for repulsive/attractive
interaction. Rutherford compared the scattering of $\alpha$ particles
($^4$He nuclei) off of a nucleus and found the scattering angle at
which the formula began to fail. This corresponded to the impact
parameter for which the trajectories would strike the nucleus. This
provided the first measure of the size of the atomic nucleus. At the
time, the distribution of the positive charge (the protons) was
considered to be just as spread out amongst the atomic volume as the
electrons. After Rutherford's experiment, it was clear that the radius
of the nucleus tended to be roughly 4 orders of magnitude smaller than
that of the atom, which is less than the size of a football relative
to Spartan Stadium.

The incoming and outgoing angles of the trajectory are at
$\pm\theta'$. They are related to the scattering angle by
$2\theta'=\pi+\theta_s$.

In order to calculate differential cross section, we must find how the
impact parameter is related to the scattering angle. This requires
analysis of the trajectory. We consider our previous expression for
the trajectory where we derived the elliptic form for the trajectory,
Eq. ([9](#eq:Ctrajectory)). For that case we considered an attractive
force with the particle's energy being negative, i.e. it was
bound. However, the same form will work for positive energy, and
repulsive forces can be considered by simple flipping the sign of
$\alpha$. For positive energies, the trajectories will be hyperbolas,
rather than ellipses, with the asymptotes of the trajectories
representing the directions of the incoming and outgoing
tracks. Rewriting Eq. ([9](#eq:Ctrajectory)),

$$
\begin{equation}\label{eq:ruthtraj} \tag{15}
r=\frac{1}{\frac{m\alpha}{L^2}+A\cos\theta}.
\end{equation}
$$

Once $A$ is large enough, which will happen when the energy is
positive, the denominator will become negative for a range of
$\theta$. This is because the scattered particle will never reach
certain angles. The asymptotic angles $\theta'$ are those for which
the denominator goes to zero,

$$
\begin{equation}
\cos\theta'=-\frac{m\alpha}{AL^2}.
\label{_auto10} \tag{16}
\end{equation}
$$

The trajectory's point of closest approach is at $\theta=0$ and the
two angles $\theta'$, which have this value of $\cos\theta'$, are the
angles of the incoming and outgoing particles. From
Fig (**to come**), one can see that the scattering angle
$\theta_s$ is given by,

$$
\begin{eqnarray}
\label{eq:sthetover2} \tag{17}
2\theta'-\pi&=&\theta_s,~~~\theta'=\frac{\pi}{2}+\frac{\theta_s}{2},\\
\nonumber
\sin(\theta_s/2)&=&-\cos\theta'\\
\nonumber
&=&\frac{m\alpha}{AL^2}.
\end{eqnarray}
$$

Now that we have $\theta_s$ in terms of $m,\alpha,L$ and $A$, we wish
to re-express $L$ and $A$ in terms of the impact parameter $b$ and the
energy $E$. This will set us up to calculate the differential cross
section, which requires knowing $db/d\theta_s$. It is easy to write
the angular momentum as

$$
\begin{equation}
L^2=p_0^2b^2=2mEb^2.
\label{_auto11} \tag{18}
\end{equation}
$$

Finding $A$ is more complicated. To accomplish this we realize that
the point of closest approach occurs at $\theta=0$, so from
Eq. ([15](#eq:ruthtraj))

$$
\begin{eqnarray}
\label{eq:rminofA} \tag{19}
\frac{1}{r_{\rm min}}&=&\frac{m\alpha}{L^2}+A,\\
\nonumber
A&=&\frac{1}{r_{\rm min}}-\frac{m\alpha}{L^2}.
\end{eqnarray}
$$

Next, $r_{\rm min}$ can be found in terms of the energy because at the
point of closest approach the kinetic energy is due purely to the
motion perpendicular to $\hat{r}$ and

$$
\begin{equation}
E=-\frac{\alpha}{r_{\rm min}}+\frac{L^2}{2mr_{\rm min}^2}.
\label{_auto12} \tag{20}
\end{equation}
$$

One can solve the quadratic equation for $1/r_{\rm min}$,

$$
\begin{equation}
\frac{1}{r_{\rm min}}=\frac{m\alpha}{L^2}+\sqrt{(m\alpha/L^2)^2+2mE/L^2}.
\label{_auto13} \tag{21}
\end{equation}
$$

We can plug the expression for $r_{\rm min}$ into the expression for $A$, Eq. ([19](#eq:rminofA)),

$$
\begin{equation}
A=\sqrt{(m\alpha/L^2)^2+2mE/L^2}=\sqrt{(\alpha^2/(4E^2b^4)+1/b^2}
\label{_auto14} \tag{22}
\end{equation}
$$

Finally, we insert the expression for $A$ into that for the scattering angle, Eq. ([17](#eq:sthetover2)),

$$
\begin{eqnarray}
\label{eq:scattangle} \tag{23}
\sin(\theta_s/2)&=&\frac{m\alpha}{AL^2}\\
\nonumber
&=&\frac{a}{\sqrt{a^2+b^2}}, ~~a\equiv \frac{\alpha}{2E}
\end{eqnarray}
$$

The differential cross section can now be found by differentiating the
expression for $\theta_s$ with $b$,

$$
\begin{eqnarray}
\label{eq:rutherford} \tag{24}
\frac{1}{2}\cos(\theta_s/2)d\theta_s&=&\frac{ab~db}{(a^2+b^2)^{3/2}}=\frac{bdb}{a^2}\sin^3(\theta_s/2),\\
\nonumber
d\sigma&=&2\pi bdb=\frac{\pi a^2}{\sin^3(\theta_s/2)}\cos(\theta_s/2)d\theta_s\\
\nonumber
&=&\frac{\pi a^2}{2\sin^4(\theta_s/2)}\sin\theta_s d\theta_s\\
\nonumber
\frac{d\sigma}{d\cos\theta_s}&=&\frac{\pi a^2}{2\sin^4(\theta_s/2)},\\
\nonumber
\frac{d\sigma}{d\Omega}&=&\frac{a^2}{4\sin^4(\theta_s/2)}.
\end{eqnarray}
$$

where $a= \alpha/2E$. This the Rutherford formula for the differential
cross section. It diverges as $\theta_s\rightarrow 0$ because
scatterings with arbitrarily large impact parameters still scatter to
arbitrarily small scattering angles. The expression for
$d\sigma/d\Omega$ is the same whether the interaction is positive or
negative.

Consider a particle of mass $m$ and charge $z$ with kinetic energy $E$
(Let it be the center-of-mass energy) incident on a heavy nucleus of
mass $M$ and charge $Z$ and radius $R$. Find the angle at which the
Rutherford scattering formula breaks down.

### Solution

Let $\alpha=Zze^2/(4\pi\epsilon_0)$. The scattering angle in Eq. ([23](#eq:scattangle)) is

$$
\sin(\theta_s/2)=\frac{a}{\sqrt{a^2+b^2}}, ~~a\equiv \frac{\alpha}{2E}.
$$

The impact parameter $b$ for which the point of closest approach
equals $R$ can be found by using angular momentum conservation,

$$
\begin{eqnarray*}
p_0b&=&b\sqrt{2mE}=Rp_f=R\sqrt{2m(E-\alpha/R)},\\
b&=&R\frac{\sqrt{2m(E-\alpha/R)}}{\sqrt{2mE}}\\
&=&R\sqrt{1-\frac{\alpha}{ER}}.
\end{eqnarray*}
$$

Putting these together

$$
\theta_s=2\sin^{-1}\left\{
\frac{a}{\sqrt{a^2+R^2(1-\alpha/(RE))}}
\right\},~~~a=\frac{\alpha}{2E}.
$$

It was from this departure of the experimentally measured
$d\sigma/d\Omega$ from the Rutherford formula that allowed Rutherford
to infer the radius of the gold nucleus, $R$.

Just like electrodynamics, one can define "fields", which for a small
additional mass $m$ are the force per mass and the additional
potential energy per mass. The {\it gravitational field} related to
the force has dimensions of force per mass, or acceleration, and can
be labeled $\boldsymbol{g}(\boldsymbol{r})$. The potential energy per mass has
dimensions of energy per mass. This is analogous to the
electromagnetic potential, which is the potential energy per charge,
and the electric field which is the force per charge.

Because the field $\boldsymbol{g}$ obeys the same inverse square law for a
point mass as the electric field does for a point charge, the
gravitational field also satisfies a version of Gauss's law,

$$
\begin{equation}
\label{eq:GravGauss} \tag{25}
\oint d\boldsymbol{A}\cdot\boldsymbol{g}=-4\pi GM_{\rm inside}.
\end{equation}
$$

Here, $M_{\rm inside}$ is the net mass inside a closed area.

Gauss's law can be understood by considering a nozzle that sprays
paint in all directions uniformly from a point source. Let $B$ be the
number of gallons per minute of paint leaving the nozzle. If the
nozzle is at the center of a sphere of radius $r$, the paint per
square meter per minute that is deposited on some part of the sphere
is

$$
\begin{eqnarray}
F(r)&=&\frac{B}{4\pi r^2}.
\end{eqnarray}
$$

Now, let $F$ also be assigned a direction, so that it becomes a vector
pointing along the direction of the flying paint. For any surface that
surrounds the nozzle, not necessarily a sphere, one can state that

$$
\begin{eqnarray}
\label{eq:paint} \tag{26}
\oint \boldsymbol{dA}\cdot\boldsymbol{F}&=&B,
\end{eqnarray}
$$

regardless of the shape of the surface. This follows because the rate
at which paint is deposited on the surface should equal the rate at
which it leaves the nozzle. The dot product ensures that only the
component of $\boldsymbol{F}$ into the surface contributes to the deposition
of paint. Similarly, if $\boldsymbol{F}$ is any radial inverse-square forces,
that falls as $B/(4\pi r^2)$, then one can apply
Eq. ([26](#eq:paint)). For gravitational fields, $B/(4\pi)$ is replaced
by $GM$, and one quickly "derives" Gauss's law for gravity,
Eq. ([25](#eq:GravGauss)).

Consider Earth to have its mass $M$ uniformly distributed in a sphere
of radius $R$. Find the magnitude of the gravitational acceleration as
a function of the radius $r$ in terms of the acceleration of gravity
at the surface $g(R)$. Assume $r<R$, i.e. you are inside the surface.

{\bf Solution}: Take the ratio of Eq. ([25](#eq:GravGauss)) for two radii, $R$ and $r<R$,

$$
\begin{eqnarray*}
\frac{4\pi r^2 g(r)}{4\pi R^2 g(R)}&=&\frac{4\pi GM_{\rm inside~r}}{4\pi GM_{\rm inside~R}}\\
\nonumber
&=&\frac{r^3}{R^3}\\
\nonumber
g(r)&=&g(R)\frac{r}{R}~.
\end{eqnarray*}
$$

The potential energy per mass is similar conceptually to the voltage, or electric potential energy per charge, that was studied in electromagnetism, if $V\equiv U/m$, $\boldsymbol{g}=-\nabla V$.

## Tidal Forces

Consider a spherical planet of radius $r$ a distance $D$ from another
body of mass $M$. The magnitude of the force due to $M$ on an small
object of mass $\delta m$ on surface of the planet can be calculated
by performing a Taylor expansion about the center of the spherical
planet.

$$
\begin{equation}
F=-\frac{GM\delta m}{D^2}+2\frac{GM\delta m}{D^3}\Delta D+\cdots
\label{_auto15} \tag{27}
\end{equation}
$$

If the $z$ direction points toward the large object, $\Delta D$ can be
referred to as $z$. In the accelerating frame of an observer at the
center of the planet,

$$
\begin{equation}
\delta m\frac{d^2 z}{dt^2}=F-\delta ma'+{\rm other~forces~acting~on~} \delta m,
\label{_auto16} \tag{28}
\end{equation}
$$

where $a'$ is the acceleration of the observer. Because $\delta ma'$
equals the gravitational force on $\delta m$ if it were located at the
planet's center, one can write

$$
\begin{equation}
m\frac{d^2z}{dt^2}=2\frac{GM\delta m}{D^3}z+{\rm other~forces~acting~on~}\delta m.
\label{_auto17} \tag{29}
\end{equation}
$$

Here the other forces could represent the forces acting on $\delta m$
from the spherical planet such as the gravitational force or the
contact force with the surface. If $\theta$ is the angle w.r.t. the
$z$ axis, the effective force acting on $\delta m$ is

$$
\begin{equation}
F_{\rm eff}\approx 2\frac{GM\delta m}{D^3}r\cos\theta\hat{z}+{\rm other~forces~acting~on~}\delta m.
\label{_auto18} \tag{30}
\end{equation}
$$

This first force is the "tidal" force. It pulls objects outward from the center of the object. If the object were covered with water, it would distort the objects shape so that the shape would be elliptical, stretched out along the axis pointing toward the large mass $M$. The force is always along (either parallel or antiparallel to) the $\hat{z}$ direction.

Consider the Earth to be a sphere of radius $R$ covered with water,
with the gravitational acceleration at the surface noted by $g$. Now
assume that a distant body provides an additional constant
gravitational acceleration $\boldsymbol{a}$ pointed along the $z$ axis. Find
the distortion of the radius as a function of $\theta$. Ignore
planetary rotation and assume $a<<g$.

{\bf Solution}: Because Earth would then accelerate with $a$, the
field $a$ would seem invisible in the accelerating frame. A tidal
force would only appear if $a$ depended on position, i.e. $\nabla
\boldsymbol{a}\ne 0$.

Now consider that the field is no longer constant, but that instead $a=-kz$ with $|kR|<<g$.

{\bf Solution}: The surface of the planet needs to be at constant
potential (if the planet is not accelerating). The force per mass,
$-kz$ is like a spring, and the potential per mass is
$kz^2/2$. Otherwise water would move to a point of lower
potential. Thus, the potential energy for a sample mass $\delta m$ is

$$
\begin{eqnarray*}
V(R)+\delta m gh(\theta)-\frac{\delta m}{2}kr^2\cos^2\theta={\rm Constant}\\
V(R)+\delta mgh(\theta)-\frac{\delta m}{2}kR^2\cos^2\theta-\delta m kRh(\theta)\cos^2\theta-\frac{\delta m}{2}kh^2(\theta)\cos^2\theta={\rm Constant}.
\end{eqnarray*}
$$

Here, the potential due to the external field is $(1/2)kz^2$ so that $-\nabla U=-kz$. One now needs to solve for $h(\theta)$. Absorbing all the constant terms from both sides of the equation into one constant $C$, and because both $h$ and $kR$ are small, we can through away terms of order $h^2$ or $kRh$. This gives

$$
\begin{eqnarray*}
gh(\theta)-\frac{1}{2}kR^2\cos^2\theta&=&C,\\
h(\theta)&=&\frac{C}{g}+\frac{1}{2g}kR^2\cos^2\theta,\\
h(\theta)&=&\frac{1}{2g}kR^2(\cos^2\theta-1/3).
\end{eqnarray*}
$$

The term with the factor of $1/3$ replaced the constant and was chosen so that the average height of the water would be zero.

The Sun's mass is $27\times 10^6$ the Moon's mass, but the Sun is 390 times further away from Earth as the Sun. What is ratio of the tidal force of the Sun to that of the Moon.

{\bf Solution}: The gravitational force due to an object $M$ a distance $D$ away goes as $M/D^2$, but the tidal force is only the difference of that force over a distance $R$,

$$
F_{\rm tidal}\propto \frac{M}{D^3}R.
$$

Therefore the ratio of force is

$$
\begin{eqnarray*}
\frac{F_{\rm Sun's~tidal~force}}{F_{\rm Moon's~tidal~force}}
&=&\frac{M_{\rm sun}/D_{\rm sun}^3}{M_{\rm moon}/D_{\rm moon}^3}\\
&=&\frac{27\times 10^6}{390^3}=0.46.
\end{eqnarray*}
$$

The Moon more strongly affects tides than the Sun.


# Hermite Polynomials

These are a set of polynomials $H_n$ on the real line, orthogonal over the interval $[-\infty, \infty]$.

Physicists tend to encounter them in the eigenfunctions of the quantum harmonic oscillator:

$$ \psi_n(x) = \frac{1}{\sqrt{2^n\,n!}} \cdot \left(\frac{m\omega}{\pi \hbar}\right)^{1/4} \cdot e^{
- \frac{m\omega x^2}{2 \hbar}} \cdot H_n\left(\sqrt{\frac{m\omega}{\hbar}} x \right), \qquad n = 0,1,2,\ldots $$

They are also important in probability and statistics. There is disagreement about scaling the functions: physicists use a leading term of $2^n$ and this seems to be what Python libraries deliver. In probability a leading term of 1 is preferred.

```python
import numpy as np
import matplotlib.pyplot as plt

plt.rcParams.update({'font.size': 16})
```

Python implements these functions in a variety of ways (see the Legendre function notebook for fuller details). The two simplest are:

- `scipy.special.hermite()` for numerical work and plotting
- `sympy.functions.special.polynomials.hermite()` for symbolic math

### scipy.special

Calling `hermite(n)` returns the nth-order polynomial as a function which can then itself be called with one or more x-values.

```python
import scipy.special as sp
H_3_sp = sp.hermite(3)
display(H_3_sp)
```

    poly1d([  8.,   0., -12.,   0.])

```python
x10 = np.linspace(-1, 1, 10)
display(H_3_sp(x10))
```

    array([ 4.        ,  5.56927298,  5.29492455,  3.7037037 ,  1.3223594 ,
           -1.3223594 , -3.7037037 , -5.29492455, -5.56927298, -4.        ])

## sympy.functions.special.polynomials

This is symbolic math, which will give you differentiation, integration, etc, as well as nice $LaTeX$ output. Not so convenient for plotting.

```python
from IPython.display import Math
from sympy import latex, hermite
from sympy.abc import x

for i in range(10):
    l_i = latex(hermite(i, x))
    display(Math('H_{} = {}'.format(i, l_i)))
```

$\displaystyle H_0 = 1$

$\displaystyle H_1 = 2 x$

$\displaystyle H_2 = 4 x^{2} - 2$

$\displaystyle H_3 = 8 x^{3} - 12 x$

$\displaystyle H_4 = 16 x^{4} - 48 x^{2} + 12$

$\displaystyle H_5 = 32 x^{5} - 160 x^{3} + 120 x$

$\displaystyle H_6 = 64 x^{6} - 480 x^{4} + 720 x^{2} - 120$

$\displaystyle H_7 = 128 x^{7} - 1344 x^{5} + 3360 x^{3} - 1680 x$

$\displaystyle H_8 = 256 x^{8} - 3584 x^{6} + 13440 x^{4} - 13440 x^{2} + 1680$

$\displaystyle H_9 = 512 x^{9} - 9216 x^{7} + 48384 x^{5} - 80640 x^{3} + 30240 x$

Note that the coefficients rapidly get large.

When __plotting__ the first few Hermite polymomials over a limited range it is clear that these are very different curves, so scaling the y-axis is a problem.

```python
import scipy.special as sp

xlims = (-1, 1)
x = np.linspace(xlims[0], xlims[1], 100)

plt.figure(figsize=(9, 9))
for v in range(0, 6):
    plt.plot(x, sp.hermite(v)(x))

plt.xlim(xlims)
# plt.ylim((-1.1, 1.1))
plt.legend(('$\mathcal{H}_0(x)$', '$\mathcal{H}_1(x)$', '$\mathcal{H}_2(x)$',
           '$\mathcal{H}_3(x)$', '$\mathcal{H}_4(x)$', '$\mathcal{H}_5(x)$'),
           loc = 0)
plt.xlabel('$x$')
plt.ylabel('$\mathcal{H}_n(x)$')
plt.title('Plots of the first six Hermite Polynomials')
plt.grid(True)
```

## References:

- Boas, "Mathematical methods in the physical sciences", 3rd ed, section 12.22
- MathWorld, http://mathworld.wolfram.com/HermitePolynomial.html
- Wikipedia, https://en.wikipedia.org/wiki/Hermite_polynomials
- Griffiths & Schroeter, "Introduction to Quantum Mechanics", 3rd ed, section 2.3.2

```python

```


# Pg. 332 #29, 41, 53, 71, 73, 83, 87, 89, 93, 101, 102
---
Eric Nguyen
20 Dec 2018

```
import numpy as np
import matplotlib.pyplot as plt
```

### *Find each logarithm*. *Round to six decimal places.*

#### 29. $\ln{5894}$

```
ans29 = round(np.log(5894), 6)
ans29
```

#### Answer 29:
> $8.68169$

### *Solve for $t$*.

#### 41. $e^{-0.02t} = 0.06$

$$
\begin{align}
e^{-0.02t} &= 0.06 \\
-0.02t &= \ln{0.06} \\
t &= \frac{\ln{0.06}}{-0.02}
\end{align}
$$

```
ans41 = np.log(0.06) / (-0.02)
ans41
```

```
# Verify
np.power(np.e, -0.02 * ans41)
```

#### Answer 41:
> $t = 140.670536$

### *Differentiate*.

#### 53. $y = \ln{\frac{x^2}{4}} \left(\text{Hint:} \ln{\frac{A}{B}} = \ln{A} - \ln{B}\right)$

$$
\begin{align}
y &= \ln{x^2} - \ln{4} \\
y' &= \frac{2x}{x^2} \\
&= \frac{2}{x}
\end{align}
$$

```
def pltfn(fn, xlim = (-10, 10), smoothness = 100, color = False):
    xp = np.linspace(xlim[0], xlim[1], smoothness)
    if (color):
      plt.plot(xp, fn(xp), color = color)
    else:
      plt.plot(xp, fn(xp))
qtn53 = lambda x: np.log(np.power(x, 2) / 4)
ans53 = lambda x: 2 / x
pltfn(qtn53)
pltfn(ans53)
```

#### Answer 53:
> $y' = \dfrac{2}{x}$

#### 59. $g(x) = e^x \ln{x^2}$

$$\begin{align}
g'(x) &= \frac{d}{dx} e^x \cdot \frac{d}{dx} \ln{x^2} \\
&= e^x \cdot \frac{2}{x} \\
&= \frac{2e^x}{x}
\end{align}$$

```
qtn59 = lambda x: np.power(np.e, x) * np.log(np.power(x, 2))
ans59 = lambda x: (2 * np.power(np.e, x)) / x
pltfn(qtn59, (5, 10))
pltfn(ans59, (5, 10))
```

#### Answer 59:
> $g'(x) = \dfrac{2e^x}{x}$

#### 71. Find the equation of the line tangent to the graph of $y = (\ln{x})^2$ at $x = 3$.

$$\begin{align}
y' &= 2 \cdot (\ln{x}) \cdot \frac{1}{x} \\
&= \frac{2\ln{x}}{x} \\
y &= f(x) \\
g(x) &= f'\left(a\right)\left(x-a\right)+f\left(a\right) \quad &\text{Tangent line equation.} \\
f'(3) &= \frac{2\ln{3}}{3} \quad &\text{Find the slope.} \\
&\approx 0.732408 \\
f(3) &\approx 1.206949 \\
g(x) &= 0.732408(x - 3) + 1.206949 \\
\end{align}$$

```
fn71 = lambda x: np.power(np.log(x), 2)
dfn71 = lambda x: (2 * np.log(x)) / x
```

```
slope71 = (2 * np.log(3)) / 3
slope71
```

```
fn71(3)
```

```
ans71 = lambda x: slope71 * (x - 3) + fn71(3)
pltfn(fn71, (0.5, 5))
pltfn(dfn71, (0.5, 5))
pltfn(ans71, (0.5, 5))
```

#### Answer 71:
> $g(x) = 0.732408(x - 3) + 1.206949$

#### 73. Advertising. A model for consumers' response to advertising is given by

$$ N(a) = 2000 + 500 \ln{a}, \quad a \geq 1,$$

#### where $N(a)$ is the number of units sold and $a$ is the amount spent on advertising, in thousands of dollars.

#### a) How many units were sold after spending $\$1000$ on advertising?

```
fn73 = lambda a: 2000 + 500 * np.log(a)
fn73(1000)
```

#### Answer 73a:
>  $N(1000) \approx 5453$

#### b) Find $N'(a)$ and $N'(10)$.

$$\begin{align}
N'(a) &= \frac{500}{a} \\
\end{align}$$

```
dfn73 = lambda a: 500 / a
dfn73(10)
```

```
pltfn(fn73, (0.25, 5))
pltfn(dfn73, (0.25, 5))
```

```
pltfn(dfn73, (8, 12), color="green")
plt.scatter(10, dfn73(10), color="green")
```

#### c) Find the maximum and minimum values, if they exist.

$$\begin{align}
N'(a) &= \frac{500}{a} \\
0 &= \frac{500}{a} & \text{Find the zeroes.} \\
0 &\neq 500
\end{align}$$

#### Answer 73c:
> There are no minimum or maximum values.

#### d) Find $\underset{a\to\infty}{\lim} N'(a)$. Discuss whether it makes sense to continue to spend more and more dollars on advertising.

$$\begin{align}
\underset{a\to\infty}{\lim} N'(a) &= \frac{500}{\infty} \\
\underset{a\to\infty}{\lim} N'(a) &= 0
\end{align}$$

#### Answer 73d:
> It does not make sense to spend more and more dollars on advertising, in thousands of dollars, as the effect of advertising rapidly diminishes for each thousand of dollars spent.

#### 83. Forgetting. Students in a zoology class took a final exam. They took equivalent forms of the exam at monthly intervals thereafter. After $t$ months, the average score $S(t)$, as a percentage was found to be given by

$$ S(t) = 78 - 15 \ln{\left(t + 1\right)}, \quad t \geq 0. $$

#### a) What was the average score when they initially took the test, $t = 0$?

```
fn83 = lambda t: 78 - 15 * np.log(t + 1)
pltfn(fn83, (0, 200))
ans83a = fn83(0)
ans83a
```

#### Answer 83a:
> $S(0) = 78.0$

#### b) What was the average score after 4 months?

```
ans83b = fn83(4)
ans83b
```

#### Answer 83b:
> $S(4) \approx 53.858$

#### c) What was the average score after 24 months?

```
ans83c = fn83(24)
ans83c
```

#### Answer 83c:
> $S(24) \approx 29.727$

#### d) What percentage of their original answers did the students retain after 2 years (24 months)?

```
ans83c / ans83a
```

    0.38098541829457677

#### Answer 83d:
> $38.1\%$

#### e) Find $S'(t)$.

$$\begin{align}
S(t) &= 78 - 15 \ln{\left(t + 1\right)}, & t \geq 0 \\
S'(t) &= -15 \cdot \frac{1}{t + 1}, & t \geq 0 \\
&= \frac{-15}{t + 1}, & t \geq 0
\end{align}$$

```
dfn83 = lambda t: -15 / (t + 1)
pltfn(fn83, (0, 200))
pltfn(dfn83, (0, 200))
```

#### f) Find the maximum and minimum values, if they exist.

$$\begin{align}
S'(t) &= \frac{-15}{t + 1} \\
0 &= \frac{-15}{t + 1} \\
0 &\neq -15
\end{align}$$

#### Answer 83f:
> There are no maximum or minimum values.

### *Solve for $t$*.
#### 87. $P = P_0e^{-kt}$

$$\begin{align}
\frac{P}{P_0} &= e^{-kt} \\
\ln{\left(\frac{P}{P_0}\right)} &= -kt \\
t &= \frac{\ln{\left(\frac{P}{P_0}\right)}}{-k}
\end{align}$$

### *Differentiate*.

#### 93. $f(t) = \ln{\dfrac{1 - t}{1 + t}}$

#### Answer 93:
$$\begin{align}
f(t) &= \ln{\left(1 - t \right)} - \ln{\left(1 + t\right)} \\
f'(t) &= \frac{-1}{1 - t} - \frac{1}{1 + t} \\
f'(t) &= \frac{-\left(1 + t\right)}{\left(1 - t\right)\left(1 + t\right)} -
\frac{\left(1 - t\right)}{\left(1 + t\right)\left(1 - t\right)} \\
f'(t) &= \frac{-1 - t - 1 + t}{\left(1 - t\right)\left(1 + t\right)} \\
f'(t) &= \frac{-2}{\left(1 - t\right)\left(1 + t\right)} \\
\end{align}$$

#### 101. $f(x) = \ln{\dfrac{1 + \sqrt{x}}{1 - \sqrt{x}}}$

#### Answer 101:
$$\begin{align}
f(x) &= \ln{\left(1 + \sqrt{x}\right)} - \ln{\left(1 - \sqrt{x}\right)} \\
\frac{d}{dx} \sqrt{x} &= \frac{1}{2\sqrt{x}} \\
f'(x) &= \frac{\frac{1}{2\sqrt{x}}}{1 + \sqrt{x}} - \frac{-\frac{1}{2\sqrt{x}}}{1 - \sqrt{x}} \\
f'(x) &= \frac{1}{\left(2\sqrt{x}\right)\left(1 + \sqrt{x}\right)} + \frac{1}{\left(2\sqrt{x}\right)\left(1 - \sqrt{x}\right)} \\
f'(x) &= \frac{\left(1 - \sqrt{x}\right)}{\left(2\sqrt{x}\right)\left(1 + \sqrt{x}\right)\left(1 - \sqrt{x}\right)} + \frac{\left(1 + \sqrt{x}\right)}{\left(2\sqrt{x}\right)\left(1 - \sqrt{x}\right)\left(1 + \sqrt{x}\right)} \\
f'(x) &= \frac{2}{\left(2\sqrt{x}\right)\left(1 + \sqrt{x}\right)\left(1 - \sqrt{x}\right)} \\
f'(x) &= \frac{1}{\sqrt{x}\left(1 - \sqrt{x}\right)\left(1 + \sqrt{x}\right)} \\
f'(x) &= \frac{1}{\sqrt{x}\left(1 - x\right)} \\
\end{align}$$

#### 102. $f(x) = \ln{\left(\ln{x}\right)}^3$

#### Answer 102:
$$\begin{align}
f(x) &= 3 \ln{\left(\ln{x}\right)} \\
f'(x) &= \frac{\frac{3}{x}}{\ln{x}} \\
f'(x) &= \frac{3}{x\ln{x}}
\end{align}$$


### Example 5: Laplace equation

In this tutorial we will look constructing the steady-state heat example using the Laplace equation. In contrast to the previous tutorials this example is entirely driven by the prescribed Dirichlet and Neumann boundary conditions, instead of an initial condition. We will also demonstrate how to use Devito to solve a steady-state problem without time derivatives and how to switch buffers explicitly without having to re-compile the kernel.

First, we again define our governing equation:
$$\frac{\partial ^2 p}{\partial x^2} + \frac{\partial ^2 p}{\partial y^2} = 0$$

We are again discretizing second-order derivatives using a central difference scheme to construct a diffusion problem (see tutorial 3). This time we have no time-dependent term in our equation though, since there is no term $p_{i,j}^{n+1}$. This means that we are simply updating our field variable $p$ over and over again, until we have reached an equilibrium state. In a discretised form, after rearranging to update the central point $p_{i,j}^n$ we have
$$p_{i,j}^n = \frac{\Delta y^2(p_{i+1,j}^n+p_{i-1,j}^n)+\Delta x^2(p_{i,j+1}^n + p_{i,j-1}^n)}{2(\Delta x^2 + \Delta y^2)}$$

And, as always, we first re-create the original implementation to see what we are aiming for. Here we initialise the field $p$ to $0$ and apply the following bounday conditions:

$p=0$ at $x=0$

$p=y$ at $x=2$

$\frac{\partial p}{\partial y}=0$ at $y=0, \ 1$

**Developer note:**
The original tutorial stores the field data in the layout `(ny, nx)`. Until now we have used `(x, y)` notation for creating our Devito examples, but for this one we will adopt the `(y, x)` layout for compatibility reasons.

```python
from examples.cfd import plot_field
import numpy as np
%matplotlib inline

# Some variable declarations
nx = 31
ny = 31
c = 1
dx = 2. / (nx - 1)
dy = 1. / (ny - 1)
```

```python
def laplace2d(p, bc_y, dx, dy, l1norm_target):
    l1norm = 1
    pn = np.empty_like(p)

    while l1norm > l1norm_target:
        pn = p.copy()
        p[1:-1, 1:-1] = ((dy**2 * (pn[1:-1, 2:] + pn[1:-1, 0:-2]) +
                         dx**2 * (pn[2:, 1:-1] + pn[0:-2, 1:-1])) /
                        (2 * (dx**2 + dy**2)))

        p[:, 0] = 0  # p = 0 @ x = 0
        p[:, -1] = bc_right  # p = y @ x = 2
        p[0, :] = p[1, :]  # dp/dy = 0 @ y = 0
        p[-1, :] = p[-2, :]  # dp/dy = 0 @ y = 1
        l1norm = (np.sum(np.abs(p[:]) - np.abs(pn[:])) /
                  np.sum(np.abs(pn[:])))

    return p
```

```python
#NBVAL_IGNORE_OUTPUT

# Out initial condition is 0 everywhere,except at the boundary
p = np.zeros((ny, nx))

# Boundary conditions
bc_right = np.linspace(0, 1, ny)
p[:, 0] = 0  # p = 0 @ x = 0
p[:, -1] = bc_right  # p = y @ x = 2
p[0, :] = p[1, :]  # dp/dy = 0 @ y = 0
p[-1, :] = p[-2, :]  # dp/dy = 0 @ y = 1

plot_field(p, ymax=1.0, view=(30, 225))
```

```python
#NBVAL_IGNORE_OUTPUT

p = laplace2d(p, bc_right, dx, dy, 1e-4)
plot_field(p, ymax=1.0, view=(30, 225))
```

Ok, nice. Now, to re-create this example in Devito we need to look a little bit further under the hood. There are two things that make this different to the examples we covered so far:
* We have no time dependence in the `p` field, but we still need to advance the state of p in between buffers. So, instead of using `TimeFunction` objects that provide multiple data buffers for timestepping schemes, we will use `Function` objects that have no time dimension and only allocate a single buffer according to the space dimensions. However, since we are still implementing a pseudo-timestepping loop, we will need to objects, say `p` and `pn`, to act as alternating buffers.
* If we're using two different symbols to denote our buffers, any operator we create will only perform a single timestep. This is desired though, since we need to check a convergence criteria outside of the main stencil update to determine when we stop iterating. As a result we will need to call the operator repeatedly after instantiating it outside the convergence loop.

So, how do we make sure our operator doesn't accidentally overwrite values in the same buffer? Well, we can again let SymPy reorganise our Laplace equation based on `pn` to generate the stencil, but when we create the update expression, we set the LHS to our second buffer variable `p`.

```python
from devito import Grid, Function, Eq, INTERIOR
from sympy import solve

# Create two explicit buffers for pseudo-timestepping
grid = Grid(shape=(nx, ny), extent=(1., 2.))
p = Function(name='p', grid=grid, space_order=2)
pn = Function(name='pn', grid=grid, space_order=2)

# Create Laplace equation base on `pn`
eqn = Eq(pn.laplace, region=INTERIOR)
# Let SymPy solve for the central stencil point
stencil = solve(eqn, pn)[0]
# Now we let our stencil populate our second buffer `p`
eq_stencil = Eq(p, stencil)

# In the resulting stencil `pn` is exclusively used on the RHS
# and `p` on the LHS is the grid the kernel will update
print("Update stencil:\n%s\n" % eq_stencil)
```

    Update stencil:
    Eq(p(x, y), (h_x**2*pn(x, y - h_y) + h_x**2*pn(x, y + h_y) + h_y**2*pn(x - h_x, y) + h_y**2*pn(x + h_x, y))/(2*(h_x**2 + h_y**2)))

Now we can add our boundary conditions. We have already seen how to prescribe constant Dirichlet BCs by simply setting values using the low-level notation. This time we will go a little further by setting a prescribed profile, which we create first as a custom 1D symbol and supply with the BC values. For this we need to create a `Function` object that has a different shape than our general `grid`, so instead of the grid we provide an explicit pair of dimension symbols and the according shape for the data.

```python
x, y = grid.dimensions
bc_right = Function(name='bc_right', shape=(nx, ), dimensions=(x, ))
bc_right.data[:] = np.linspace(0, 1, nx)
```

Now we can create a set of expressions for the BCs again, where we wet prescribed values on the right and left of our grid. For the Neuman BCs along the top and bottom boundaries we simply copy the second rwo from the outside into the outermost row, just as the original tutorial did. Using these expressions and our stencil update we can now create an operator.

```python
#NBVAL_IGNORE_OUTPUT
from devito import Operator

# Create boundary condition expressions
bc = [Eq(p.indexed[x, 0], 0.)]  # p = 0 @ x = 0
bc += [Eq(p.indexed[x, ny-1], bc_right.indexed[x])]  # p = y @ x = 2
bc += [Eq(p.indexed[0, y], p.indexed[1, y])]  # dp/dy = 0 @ y = 0
bc += [Eq(p.indexed[nx-1, y], p.indexed[nx-2, y])]  # dp/dy = 0 @ y = 1

# Now we can build the operator that we need
op = Operator(expressions=[eq_stencil] + bc)
```

We can now use this single-step operator repeatedly in a Python loop, where we can arbitrarily execute other code in between invocations. This allows us to update our L1 norm and check for convergence. Using our pre0compiled operator now comes down to a single function call that supplies the relevant data symbols. One thing to note is that we now do exactly the same thing as the original NumPy loop, in that we deep-copy the data between each iteration of the loop, which we will look at after this.

```python
#NBVAL_IGNORE_OUTPUT

# Silence the runtime performance logging
from devito import configuration
configuration['log_level'] = 'ERROR'

# Initialise the two buffer fields
p.data[:] = 0.
p.data[:, -1] = np.linspace(0, 1, ny)
pn.data[:] = 0.
pn.data[:, -1] = np.linspace(0, 1, ny)

# Visualize the initial condition
plot_field(p.data, ymax=1.0, view=(30, 225))

# Run the convergence loop with deep data copies
l1norm_target = 1.e-4
l1norm = 1
while l1norm > l1norm_target:
    # This call implies a deep data copy
    pn.data[:] = p.data[:]
    op(p=p, pn=pn)

    l1norm = (np.sum(np.abs(p.data[:]) - np.abs(pn.data[:])) /
              np.sum(np.abs(pn.data[:])))

# Visualize the converged steady-state
plot_field(p.data, ymax=1.0, view=(30, 225))
```

One crucial detail about the code above is that the deep data copy between iterations will really hurt performance if we were to run this on a large grid. However, we have already seen how we can match data symbols to symbolic names when calling the pre-compiled operator, which we can now use to actually switch the roles of `pn` and `p` between iterations, eg. `op(p=pn, pn=p)`. Thus, we can implement a simple buffer-switching scheme by simply testing for odd and even time-steps, without ever having to shuffle data around.

```python
#NBVAL_IGNORE_OUTPUT

# Initialise the two buffer fields
p.data[:] = 0.
p.data[:, -1] = np.linspace(0, 1, ny)
pn.data[:] = 0.
pn.data[:, -1] = np.linspace(0, 1, ny)

# Visualize the initial condition
plot_field(p.data, ymax=1.0, view=(30, 225))

# Run the convergence loop by explicitly flipping buffers
l1norm_target = 1.e-4
l1norm = 1
counter = 0
while l1norm > l1norm_target:
    # Determine buffer order
    if counter % 2 == 0:
        _p = p
        _pn = pn
    else:
        _p = pn
        _pn = p

    # Apply operator
    op(p=_p, pn=_pn)

    # Compute L1 norm
    l1norm = (np.sum(np.abs(_p.data[:]) - np.abs(_pn.data[:])) /
              np.sum(np.abs(_pn.data[:])))
    counter += 1

plot_field(p.data, ymax=1.0, view=(30, 225))
```


# Optimización de funciones escalares diferenciables con `SymPy`

- Mediante optimización se obtienen soluciones elegantes tanto en teoría como en ciertas aplicaciones.
- La teoría de optimización usa elementos comenzando con cálculo elemental y álgebra lineal básica, y luego se extiende con análisis funcional y convexo.
- Las aplicaciones en optimización involucran ciencia, ingeniería, economía, finanzas e industria.
- El amplio y creciente uso de la optimización lo hace escencial para estudiantes y profesionales de cualquier rama de la ciencia y la tecnología.

**Referencia:**
- http://www.math.uwaterloo.ca/~hwolkowi//henry/reports/talks.d/t06talks.d/06msribirs.d/optimportance.shtml

### Algunas aplicaciones son:

1. **Ingeniería:**
 - Encontrar la composición de equilibrio de una mezcla de diferentes átomos.
 - Planeación de ruta para un robot (o vehículo aéreo no tripulado).
 - Planeación de la mano de obra óptima en una construcción o planta de producción.
 - Análisis conformacional - Isomería conformacional en modelado molecular
 - Desempeño en fermentación de productos.
 - Estimación de parámetros en bioquímica e ingeniería metabólica.

2. **Distribución óptima de recursos:**
 - Distribución de rutas de vuelo.
 - Encontrar una dieta óptima.
 - Planeación de ruta óptima.

3. **Optimización financiera:**
 - Administración de riesgos.
 - Portafolios de inversión.
 - Problema de maximización de la utilidad.
 -

###
En esta clase veremos aspectos básicos de optimización. Específicamente, veremos cómo obtener máximos y mínimos de una función escalar de una variable (como en cálculo diferencial).

___
## 0. Librerías que usaremos

Como ya dijimos en la primer clase `python` es el lenguaje de programación (el cual es de alto nivel). Sin embargo, `python` solo tiene unos pocos comandos primitivos y para hacer más fácil su uso en nuestras actividades de simulación en ingeniería, otras personas ya han escrito ciertas librerías por nosotros.

### 0.1 `NumPy`

`NumPy` (Numerical Python) es la librería fundamental para computación científica (numérica) con `Python`. Contiene, entre otras cosas:
- un objeto tipo arreglo N-dimensional muy poderoso
- funciones sofisticadas
- funciones de álgebra lineal, transformada de Fourier y números aleatorios.

Por lo anterior, `NumPy` es de amplio uso entre la comunidad científica e ingenieril (por su manejo de cantidades vectoriales). De la misma manera, se usa para guardar datos. Para nuestros propósitos, se puede usar libremente.

**Referencia:**
- http://www.numpy.org/

`NumPy` ya viene incluido en la instalación estándar de Anaconda por defecto. Para comenzar a usarlo, solo debemos de importarlo:

```python
# Importar librería numpy
import numpy as np
```

### 0.2 `SymPy`

`SymPy` (Symbolic Python) es una librería de `Python` para matemáticas simbólicas. Su objetivo es convertirse en un sistema de álgebra computacional con las mejores características, manteniendo el código lo más simple posible para que sea comprensible.

**Referencia:**
- http://www.sympy.org/en/index.html

`SymPy` ya viene incluido en la instalación estándar de Anaconda por defecto. Para comenzar a usarlo, solo debemos de importarlo:

```python
#Importarmos la librería sympy
import sympy as sym

# Imprimir datos en formato Latex
sym.init_printing(use_latex = 'mathjax')
```

La funcionalidad de imprimir en formato LaTex que nos da `SymPy` mediante el proyecto `mathjax` hace de `SymPy` una herramienta muy atractiva...

Notar que en `SymPy` y en `NumPy` existen funciones con el mismo nombre, pero reciben tipos de datos diferentes...

```python
sym.var('x')
```

$$x$$

```python
sym.cos(x), np.cos(3.1416)
```

$$\left ( \cos{\left (x \right )}, \quad -0.9999999999730151\right )$$

Explicar el uso de la sintaxis `from numpy import *` y sus peligros (no recomendable).

```python
# importar con * y ver que pasa
#from numpy import *
#from sympy import *
# No recomendado
#b=array([3])
#b
#cos(x),
#cos(3.1416)
```

### 0.3 `PyPlot` de `matplotlib`

El módulo `PyPlot` de la librería `matplotlib` contiene funciones que nos permite generar una gran cantidad de gráficas rápidamente. Las funciones de este módulo están escritas con el mismo nombre que las funciones para graficar en `Matlab`.

**Referencia:**
- https://matplotlib.org/api/pyplot_summary.html

```python
# importar matplotlib.pyplot
import matplotlib.pyplot as plt
# comando para que las gráficas salgan en la misma ventana
%matplotlib inline
```

Ya que revisamos todas las librerías que usaremos, empecemos con la clase como tal...

___

Todos los resultados que veremos son basados en el siguiente teorema:

## 1. Teorema de Fermat (análisis)

Si una función $f(x)$ alcanza un máximo o mínimo local en $x=c$, y si la derivada $f'(c)$ existe en el punto $c$, entonces $f'(c) = 0$.

### Ejemplo

Sabemos que la función $f(x)=(x-2)^2$ tiene un mínimo global en $x=2$, pues

$$f(x)=(x-2)^2\geq0,\qquad\text{y}\qquad f(x)=(x-2)^2=0 \qquad\text{si y solo si}\qquad x=2.$$

```python
#Declarar variable x como real
sym.var('x', real=True)
```

$$x$$

```python
# Declarar función f y mostrar
f = (x-2)**2
f
```

$$\left(x - 2\right)^{2}$$

```python
# Encontrar la primera derivada de la función f
df = sym.diff(f,x)
df
```

$$2 x - 4$$

```python
#Resolver f'(x)= 0 y mostrar punto
xc = sym.solve(df,x)
xc
```

$$\left [ 2\right ]$$

Veamos la gráfica...

```python
# Convertir función simbólica f a una función numérica
fnum = sym.lambdify(x,f,modules='numpy')
```

```python
# Definir coordenas x
xnum = np.linspace(-4,8,200)
ynum = fnum(xnum)
```

```python
# Graficar
# Crear nueva ventana de gráficos y se da medida a la ventana
plt.figure(figsize = (8,6))

plt.plot(xnum,ynum,'k',label = '$y = (x-2)^2$')
plt.plot([2],[0], '*y', ms = 15, label = '$(2,0)$')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$')
plt.ylabel('$f(x) = (x-2)^2$')
plt.legend(loc = 'best')
plt.grid()
```

Ver diferencias entre f y f_num

```python

```

### **Otra manera de hacer lo anterior**

Revisemos el concepto de función en `Python`

```python
#Definimos una función en Python
def nuestra_funcion_f(x):
    return (x-2)**2
```

```python
x
```

$$x$$

```python
nuestra_funcion_f(x)
```

$$\left(x - 2\right)^{2}$$

```python
df = sym.diff(nuestra_funcion_f(x),x)
df
```

$$2 x - 4$$

```python
xc = sym.solve(df,x)
xc
```

$$\left [ 2\right ]$$

```python
# Graficar
# Crear nueva ventana de gráficos y se da medida a la ventana
plt.figure(figsize = (8,6))

plt.plot(xnum,nuestra_funcion_f(xnum),'k',label = '$y = (x-2)^2$')
plt.plot([2],[0], '*y', ms = 15, label = '$(2,0)$')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$')
plt.ylabel('$f(x) = (x-2)^2$')
plt.legend(loc = 'best')
plt.grid()
```

**El converso del teorema anterior no es cierto.**

### Actividad
Considere $g(x)=(x-2)^3$.
- Usando `sympy`, muestre que $g'(2)=0$.
- Sin embargo, descartar que $x=2$ es un extremo de $g(x)$ viendo su **gráfica**.

```python
def g(x):
    return (x-2)**3
```

```python
x
```

$$x$$

```python
df = sym.diff(g(x),x)
df
```

$$3 \left(x - 2\right)^{2}$$

```python
xc = sym.solve(df,x)
xc
```

$$\left [ 2\right ]$$

```python
# Graficar
# Crear nueva ventana de gráficos y se da medida a la ventana
plt.figure(figsize = (8,6))

plt.plot(xnum,g(xnum),'k',label = '$y = (x-2)^3$')
plt.plot([2],[0], '*y', ms = 15, label = '$(2,0)$')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$')
plt.ylabel('$g(x) = (x-2)^3$')
plt.legend(loc = 'best')
plt.grid()
```

## 2. Criterio de la segunda derivada

Sea $f(x)$ una función tal que $f’(c)=0$ y cuya segunda derivada existe en un intervalo abierto que contiene a $c$.
- Si $f’’(c)>0$, entonces $f(c)$ es un mínimo relativo.
- Si $f’’(c)<0$, entonces $f(c)$ es un máximo relativo.
- Si $f’’(c)=0$, entonces el criterio no decide.

### Ejemplo

Mostrar, usando `sympy`, que la función $f(x)=(x-2)^2$ tiene un mínimo relativo en $x=2$.

Ya vimos que $f'(2)=0$. Notemos que:

```python
def f(x):
    return (x-2)**2
```

```python
d2f = sym.diff(f(x),x,2)
d2f
```

$$2$$

```python
d2f>0
```

$$\mathrm{True}$$

Por tanto, por el criterio de la segunda derivada, $f(2)=0$ es un mínimo relativo (en efecto, el mínimo global).

### Ejemplo
¿Qué pasa con $g(x)=(x-2)^3$ al intentar utilizar el criterio de la segunda derivada? (usar `sympy`).

```python
d2g = sym.diff(g(x),x,2)
d2g
```

$$6 \left(x - 2\right)$$

```python
d2g.subs(x,2)
```

$$0$$

```python
# Graficar
#Se crea nuevo eje de las x
xnum1 = np.linspace(0,4,200)
# Crear nueva ventana de gráficos y se da medida a la ventana
plt.figure(figsize = (8,6))

plt.plot(xnum1,h(xnum1),'k',label = '$y = (x-2)^3$')
plt.plot([2],[0], '*y', ms = 15, label = '$(2,0)$')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$')
plt.ylabel('$g(x) = (x-2)^3$')
plt.legend(loc = 'best')
plt.grid()
```

Como $g''(2)=0$ entonces el criterio de la segunda derivada no concluye.

### Actividad

¿Qué pasa con $h(x)=(x-2)^4$ al intentar utilizar el criterio de la segunda derivada?.

```python
def h(x):
    return (x-2)**4
```

```python
x
```

$$x$$

```python
d2h = sym.diff(h(x),x,2)
d2h
```

$$12 \left(x - 2\right)^{2}$$

```python
d2h.subs(x,2)
```

$$0$$

```python
# Graficar
#Se crea nuevo eje de las x
xnum1 = np.linspace(0,4,200)
# Crear nueva ventana de gráficos y se da medida a la ventana
plt.figure(figsize = (8,6))

plt.plot(xnum1,h(xnum1),'g',label = '$y = (x-2)^3$')
plt.plot([2],[0], '*y', ms = 15, label = '$(2,0)$')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$')
plt.ylabel('$g(x) = (x-2)^3$')
plt.legend(loc = 'best')
plt.grid()
```

## 3. Método para determinar extremos absolutos de una función continua y=f(x) en [a,b]

- Determinar todos los valores críticos $c_1, c_2, c_3, \dots, c_n$ en $(a,b)$.
- Evaluar $f$ en todos los valores críticos y en los extremos $x=a$ y $x=b$.
- El más grande y el más pequeño de los valores de la lista $f(a), f(b), f(c_1), f(c_2), \dots, f(c_n)$ son el máximo absoluto y el mínimo absoluto, respectivamente, de $f$ en el intervalo $[a,b]$.

### Ejemplo

Determinar los extremos absolutos de $f(x)=x^2-2x+5$ en $\left[0,3\right]$.

Obtenemos los puntos críticos de $f$ en $\left[0,3\right]$:

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

```python
df = sym.diff(f(x),x)
df
```

$$2 x - 2$$

```python
xc = sym.solve(df,x)
xc
```

$$\left [ 1\right ]$$

Evaluamos $f$ en los extremos y en los puntos críticos:

```python
f(0), f(1), f(3)
```

$$\left ( 5, \quad 4, \quad 8\right )$$

Concluimos que el máximo absoluto de $f$ en $\left[0,3\right]$ es $8$ y se alcanza en $x=3$, y que el mínimo absoluto es $4$ y se alcanza en $x=1$.

```python
xnum = np.linspace(0,3,480)
#Grafica
# Crear nueva ventana de gráficos y se da medida a la ventana
plt.figure(figsize = (8,6))

plt.plot(xnum,f(xnum),'b',label = '$y = x^2-2x+5$')
plt.plot([3],[8], '*y', ms = 15, label = '$(3,8)$, máximo absoluto en el intervalo')
plt.plot([1],[4], '*y', ms = 15, label = '$(1,4)$, mínimo absoluto en el intervalo')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$')
plt.ylabel('$f(x) = x^2-2x+5$')
plt.legend(loc = 'best')
plt.grid()

```

### Actividad
Determinar los valores extremos absolutos de  $f(x) = (x-3)^2(x+3)^2$ en el intervalo $[1,\,4]$, usando `sympy`. Mostrar en una gráfica.

```python
def f1(x):
    return (x-3)**2*(x+3)**2
```

```python
df1 = sym.diff(f1(x),x)
df1
```

$$\left(x - 3\right)^{2} \left(2 x + 6\right) + \left(x + 3\right)^{2} \left(2 x - 6\right)$$

```python
xc1 = sym.solve(df1,x)
xc1
```

$$\left [ -3, \quad 0, \quad 3\right ]$$

```python
f1(1), f1(3), f1(4)
```

$$\left ( 64, \quad 0, \quad 49\right )$$

```python
xnum = np.linspace(1,4,480)
#Grafica
# Crear nueva ventana de gráficos y se da medida a la ventana
plt.figure(figsize = (8,6))

plt.plot(xnum,f1(xnum),'b',label = '$y = (x-3)^2(x+3)^2$')
#plt.plot([3],[8], '*y', ms = 15, label = '$(3,8)$, máximo absoluto en el intervalo')
#plt.plot([1],[4], '*y', ms = 15, label = '$(1,4)$, mínimo absoluto en el intervalo')

#Nombrar los ejes de la gráfica
plt.xlabel('$x$')
plt.ylabel('$f(x) = (x-3)^2(x+3)^2$')
plt.legend(loc = 'best')
plt.grid()
```

```python

```

```python

```

### En varias variables...

El procedimiento es análogo.

Si una función $f:\mathbb{R}^n\to\mathbb{R}$ alcanza un máximo o mínimo local en $\boldsymbol{x}=\boldsymbol{c}\in\mathbb{R}^n$, y $f$ es diferenciable en el punto $\boldsymbol{x}=\boldsymbol{c}$, entonces $\left.\frac{\partial f}{\partial \boldsymbol{x}}\right|_{\boldsymbol{x}=\boldsymbol{c}}=\boldsymbol{0}$ (todas las derivadas parciales en el punto $\boldsymbol{x}=\boldsymbol{c}$ son cero).

**Criterio de la segunda derivada:** para ver si es máximo o mínimo, se toma la segunda derivada (matriz jacobiana) y se verifica definición negativa o positiva, respectivamente.

Si se restringe a cierta región, hay ciertas técnicas. La más general, pero también la más compleja es la de **multiplicadores de Lagrange**.

**Ejemplo:** Determinar los valores extremo de $z(x,y)= (x-3)^2 + (y+5)^2$ usando el criterio de la segunda derivada y corroborarlo con la gráfica.

```python
sym.var('x y')
```

$$\left ( x, \quad y\right )$$

```python
def z(x,y):
    return (x-3)**2 + (y+5)**2
```

```python
dzx = sym.diff(z(x,y),x)
dzy = sym.diff(z(x,y),y)
dzx,dzy
```

$$\left ( 2 x - 6, \quad 2 y + 10\right )$$

```python
xy_c = sym.solve([dzx,dzy],[x,y])
xy_c
```

$$\left \{ x : 3, \quad y : -5\right \}$$

```python
d2zx = sym.diff(z(x,y),x,2)
d2zy = sym.diff(z(x,y),y,2)
dzxy = sym.diff(z(x,y),x,y)

Jz = sym.Matrix([[d2zx,dzxy],[dzxy,d2zy]])
Jz
```

$$\left[\begin{matrix}2 & 0\\0 & 2\end{matrix}\right]$$

```python
Jz.eigenvals()
Jz.det()
```

$$4$$

### Tarea.

1. Obtener máximos y mínimos absolutos, usando `sympy`, de las funciones dadas en los intervalos dados, y graficar la función en dicho intervalo señalando los puntos máximo y mínimo.

    + $f(x)=-3x^3+9x+2$ en $[-1.8,\,1.5]$
    + $g(x) = x^3-6x^2+9x-4$ en el intervalo $[0, \,2.5]$

Created with Jupyter by Cristian Camilo Zapata Zuluaga.


# Resolution of the elastic problem of a sphere in an infinite matrix

```julia
using TensND, LinearAlgebra, SymPy, Tensors, OMEinsum, Rotations
```

## Definition of the coordinate system, base vectors...

```julia
Spherical = coorsys_spherical()
θ, ϕ, r = getcoords(Spherical) # Note the order of coordinates not `r, θ, ϕ` but `θ, ϕ, r` so that the frame `(𝐞ᶿ, 𝐞ᵠ, 𝐞ʳ)` coincides with the canonical frame for null angles
𝐞ᶿ, 𝐞ᵠ, 𝐞ʳ = unitvec(Spherical) # "\bfe<TAB>" to write bold `𝐞` and "\^\theta<TAB><TAB>" to write superscript `ᶿ`
ℬ = getbasis(Spherical) ; # "\scrB<TAB>" to write `ℬ`
```

```julia
𝟏, 𝟙, 𝕀, 𝕁, 𝕂 = init_isotropic() # Defines usual isotropic Tensors
k, μ = symbols("k μ", positive = true)
ℂ = 3k * 𝕁 + 2μ * 𝕂 # Isotropic stiffness tensor "\bbC<TAB>" to write `ℂ`
display(ℂ)
```

    TensND.TensndCanonical{4, 3, Sym, SymmetricTensor{4, 3, Sym, 36}}

$\left[ \begin{array}{rrrrrr}k + \frac{4 μ}{3}&k - \frac{2 μ}{3}&k - \frac{2 μ}{3}&0&0&0\\k - \frac{2 μ}{3}&k + \frac{4 μ}{3}&k - \frac{2 μ}{3}&0&0&0\\k - \frac{2 μ}{3}&k - \frac{2 μ}{3}&k + \frac{4 μ}{3}&0&0&0\\0&0&0&2 μ&0&0\\0&0&0&0&2 μ&0\\0&0&0&0&0&2 μ\end{array}\right]$

    # data:

$\left[ \begin{array}{rrr}1&0&0\\0&1&0\\0&0&1\end{array}\right]$

    (:cont, :cont, :cont, :cont)

    # basis: # var:

## General resolution

### Hydrostatic loading
\begin{equation}
\mathbf{u}=𝐄
\end{equation}

```julia
u = SymFunction("u", real = true)(r)
𝐮 = u * 𝐞ʳ
```


## Resource Allocation Example

Assume we have to assign resources of $m$ classes to $n$ kinds of jobs. This resource allocation is encoded in $X \in \mathbb{R}^{n \times m}$, with $X_{i,j}$ denoting the amount of resource $j$ allocated to job $i$. Given the utility matrix $W \in \mathbb{R}^{n \times m}$, we want to solve the optimization problem

\begin{equation}
\begin{array}{ll}
\text{maximize} \quad &\mathrm{tr} \left( \min \left( X W^T, S\right) \right)\\
\text{subject to} \quad &X^\mathrm{min} \leq X \leq X^\mathrm{max} \\
&X^T \mathbb{1} \leq r,
\end{array}
\end{equation}

with variable $X \in \mathbb{R}^{n \times m}$. The utility for some job $i$ cannot be increased beyond the saturation value $S_{ii}$, with $S \in \mathbb{S}_+^{n}$ being diagonal. The minimum and maximum amounts of resources to be allocated are denoted by $X^\mathrm{min} \geq 0$ and $X^\mathrm{max} \geq X^\mathrm{min}$, respectively, while $r$ is the vector of available resources. The problem is feasible if $\left(X^\mathrm{min}\right)^T \mathbb{1} \leq r$ and $X^\mathrm{min} \leq X^\mathrm{max}$.

Let's define the corresponding CVXPY problem.

```python
import cvxpy as cp
import numpy as np

# define dimensions
n, m = 30, 10

# define variable
X = cp.Variable((n, m), name='X')

# define parameters
W = cp.Parameter((n, m), name='W')
S = cp.Parameter((n, n), diag=True, name='S')
X_min = cp.Parameter((n, m), name='X_min')
X_max = cp.Parameter((n, m), name='X_max')
r = cp.Parameter(m, name='r')

# define objective
objective = cp.Maximize(cp.trace(cp.minimum(X@W.T, S)))

# define constraints
constraints = [X_min <= X, X<= X_max,
               X.T@np.ones(n) <= r]

# define problem
problem = cp.Problem(objective, constraints)
```

Assign parameter values and solve the problem.

```python
np.random.seed(0)

W.value = np.ones((n, m)) + 0.1*np.random.rand(n, m)
S.value = 100*np.eye(n)
X_min.value = np.random.rand(n, m)
X_max.value = 10 + np.random.rand(n, m)
r.value = np.matmul(X_min.value.T, np.ones(n)) + 10*np.random.rand(m)

val = problem.solve()
```

Generating C source for the problem is as easy as:

```python
from cvxpygen import cpg

cpg.generate_code(problem, code_dir='resource_code')
```

Now, you can use a python wrapper around the generated code as a custom CVXPY solve method.

```python
from resource_code.cpg_solver import cpg_solve
import numpy as np
import pickle
import time

# load the serialized problem formulation
with open('resource_code/problem.pickle', 'rb') as f:
    prob = pickle.load(f)

# assign parameter values
np.random.seed(0)
prob.param_dict['S'].value = 100*np.eye(n)
prob.param_dict['W'].value = 0.8*np.ones((n, m)) + 0.2*np.random.rand(n, m)
prob.param_dict['X_min'].value = np.zeros((n, m))
prob.param_dict['X_max'].value = np.ones((n, m))
prob.param_dict['r'].value = np.matmul(prob.param_dict['X_min'].value.T, np.ones(n)) + np.random.rand(m)

# solve problem conventionally
t0 = time.time()
# CVXPY chooses eps_abs=eps_rel=1e-5, max_iter=10000, polish=True by default,
# however, we choose the OSQP default values here, as they are used for code generation as well
val = prob.solve()
t1 = time.time()
print('\nCVXPY\nSolve time: %.3f ms' % (1000 * (t1 - t0)))
print('Objective function value: %.6f\n' % val)

# solve problem with C code via python wrapper
prob.register_solve('CPG', cpg_solve)
t0 = time.time()
val = prob.solve(method='CPG')
t1 = time.time()
print('\nCVXPYgen\nSolve time: %.3f ms' % (1000 * (t1 - t0)))
print('Objective function value: %.6f\n' % val)
```

```python
from visualization.resource import create_animation
from IPython.display import Image

create_animation(prob, 'resource_animation')

with open('resource_animation.gif', 'rb') as f:
    display(Image(f.read()))
```


 Data Transformation

## Logistic Regression - on [Titanic Dataset](https://www.kaggle.com/c/titanic)

- Models the probability an object belongs to a class
- Values ranges from 0 to 1
- Can use threshold to classify into which classes a class belongs
- An S-shaped curve

$
\begin{align}
\sigma(t) = \frac{1}{1 + e^{-t}}
\end{align}
$

#### Read the data

```python
import pandas as pd
df_train = pd.read_csv('../data/titanic_train.csv')
```

```python
df_train.head(8)
```

      PassengerId
      Survived
      Pclass
      Name
      Sex
      Age
      SibSp
      Parch
      Ticket
      Fare
      Cabin
      Embarked

      0
      1
      0
      3
      Braund, Mr. Owen Harris
      male
      22.0
      1
      0
      A/5 21171
      7.2500
      NaN
      S

      1
      2
      1
      1
      Cumings, Mrs. John Bradley (Florence Briggs Th...
      female
      38.0
      1
      0
      PC 17599
      71.2833
      C85
      C

      2
      3
      1
      3
      Heikkinen, Miss. Laina
      female
      26.0
      0
      0
      STON/O2. 3101282
      7.9250
      NaN
      S

      3
      4
      1
      1
      Futrelle, Mrs. Jacques Heath (Lily May Peel)
      female
      35.0
      1
      0
      113803
      53.1000
      C123
      S

      4
      5
      0
      3
      Allen, Mr. William Henry
      male
      35.0
      0
      0
      373450
      8.0500
      NaN
      S

      5
      6
      0
      3
      Moran, Mr. James
      male
      NaN
      0
      0
      330877
      8.4583
      NaN
      Q

      6
      7
      0
      1
      McCarthy, Mr. Timothy J
      male
      54.0
      0
      0
      17463
      51.8625
      E46
      S

      7
      8
      0
      3
      Palsson, Master. Gosta Leonard
      male
      2.0
      3
      1
      349909
      21.0750
      NaN
      S

## Data Statistics

#### Describing the statistics for numerical features

```python
df_train.describe()
```

      PassengerId
      Survived
      Pclass
      Age
      SibSp
      Parch
      Fare

      count
      891.000000
      891.000000
      891.000000
      714.000000
      891.000000
      891.000000
      891.000000

      mean
      446.000000
      0.383838
      2.308642
      29.699118
      0.523008
      0.381594
      32.204208

      std
      257.353842
      0.486592
      0.836071
      14.526497
      1.102743
      0.806057
      49.693429

      min
      1.000000
      0.000000
      1.000000
      0.420000
      0.000000
      0.000000
      0.000000

      25%
      223.500000
      0.000000
      2.000000
      20.125000
      0.000000
      0.000000
      7.910400

      50%
      446.000000
      0.000000
      3.000000
      28.000000
      0.000000
      0.000000
      14.454200

      75%
      668.500000
      1.000000
      3.000000
      38.000000
      1.000000
      0.000000
      31.000000

      max
      891.000000
      1.000000
      3.000000
      80.000000
      8.000000
      6.000000
      512.329200

#### Find the count of the non-NaN values per feature

```python
df_train.count()
```

    PassengerId    891
    Survived       891
    Pclass         891
    Name           891
    Sex            891
    Age            714
    SibSp          891
    Parch          891
    Ticket         891
    Fare           891
    Cabin          204
    Embarked       889
    dtype: int64

## What features can be removed?

### Remove features that are not related to your outcome

```python
df_train.drop(['Name', 'Ticket'], axis=1, inplace=True)
```

### Remove column with missing data

```python
df_train.drop(['Cabin'], axis=1, inplace=True)
```

## Data Imputation - Filling in missing values
- Select a percentage threshold that you would want to accomodate
- Around 1/5th to 1/3rd of the data (20% to 33.3%)
- if more than 50% of the data is missing, you will be generating data for the majority of your dataset - Not a good thing to do

```python
from matplotlib import pyplot as plt
import seaborn as sns
plt.figure(figsize=(7,5))
sns.boxplot(x='Pclass',y='Age',data=df_train)
```

```python
from matplotlib import pyplot as plt
import seaborn as sns
plt.figure(figsize=(7,5))
sns.boxplot(x='Sex',y='Age',data=df_train)
```

```python
from matplotlib import pyplot as plt
import seaborn as sns
plt.figure(figsize=(7,5))
sns.boxplot(x='Embarked',y='Age',data=df_train)
```

```python
def add_age(cols):
    Age = cols[0]
    Pclass = cols[1]
    if pd.isnull(Age):
        return int(df_train[df_train["Pclass"] == Pclass]["Age"].mean())
    else:
        return Age
```

```python
df_train['Age'] = df_train[['Age', 'Pclass']].apply(add_age,axis=1)
```

```python
df_train.count()
```

    PassengerId    891
    Survived       891
    Pclass         891
    Sex            891
    Age            891
    SibSp          891
    Parch          891
    Fare           891
    Embarked       889
    dtype: int64

### Drop Rows

```python
df_train.dropna(inplace=True)
```

```python
df_train.count()
```

    PassengerId    889
    Survived       889
    Pclass         889
    Sex            889
    Age            889
    SibSp          889
    Parch          889
    Fare           889
    Embarked       889
    dtype: int64

## Categorical to Numerical

#### Convert the categorical values to numeric
- Find the columns that are explicitly categorical - like male, female
- Find the columns that are although numerical, represent categorical features

### One-Hot Encoding
- A technique to create multiple feature for each corrsponding value

```python
import numpy as np
col = 'Sex'
print(np.unique(df_train[col]))
```

    ['female' 'male']

```python
import numpy as np
col = 'Embarked'
print(np.unique(df_train[col]))
```

    ['C' 'Q' 'S']

```python
import numpy as np
col = 'Pclass'
print(np.unique(df_train[col]))
```

    [1 2 3]

#### pd.get_dummies()

```python
# sex = pd.get_dummies(df_train["Sex"],drop_first=True)
# embarked = pd.get_dummies(df_train["Embarked"],drop_first=True)
# pclass = pd.get_dummies(df_train["Pclass"],drop_first=True)

sex = pd.get_dummies(df_train["Sex"])
embarked = pd.get_dummies(df_train["Embarked"])
pclass = pd.get_dummies(df_train["Pclass"])
```

### Drop the columns that were used for transformation

```python
df_train.drop(['Sex', 'Embarked', 'Pclass', 'PassengerId'], axis=1, inplace=True)
```

```python
df_train.head()
```

      Survived
      Age
      SibSp
      Parch
      Fare

      0
      0
      22.0
      1
      0
      7.2500

      1
      1
      38.0
      1
      0
      71.2833

      2
      1
      26.0
      0
      0
      7.9250

      3
      1
      35.0
      1
      0
      53.1000

      4
      0
      35.0
      0
      0
      8.0500

### Add encoded columns to the training dataset

```python
df_train = pd.concat([df_train,pclass,sex,embarked],axis=1)
```

```python
df_train.head()
```

      Survived
      Age
      SibSp
      Parch
      Fare
      1
      2
      3
      female
      male
      C
      Q
      S

      0
      0
      22.0
      1
      0
      7.2500
      0
      0
      1
      0
      1
      0
      0
      1

      1
      1
      38.0
      1
      0
      71.2833
      1
      0
      0
      1
      0
      1
      0
      0

      2
      1
      26.0
      0
      0
      7.9250
      0
      0
      1
      1
      0
      0
      0
      1

      3
      1
      35.0
      1
      0
      53.1000
      1
      0
      0
      1
      0
      0
      0
      1

      4
      0
      35.0
      0
      0
      8.0500
      0
      0
      1
      0
      1
      0
      0
      1

# Save the transformed file as a pickle file

```python
df_train.shape
```

    (889, 13)

```python
import pickle as pkl
df_train.to_pickle('../data/titanic_tansformed.pkl')
```

## Logistic Regression

```python
data = df_train.drop("Survived",axis=1)
label = df_train["Survived"]
```

```python
data.head()
```

      Age
      SibSp
      Parch
      Fare
      1
      2
      3
      female
      male
      C
      Q
      S

      0
      22.0
      1
      0
      7.2500
      0
      0
      1
      0
      1
      0
      0
      1

      1
      38.0
      1
      0
      71.2833
      1
      0
      0
      1
      0
      1
      0
      0

      2
      26.0
      0
      0
      7.9250
      0
      0
      1
      1
      0
      0
      0
      1

      3
      35.0
      1
      0
      53.1000
      1
      0
      0
      1
      0
      0
      0
      1

      4
      35.0
      0
      0
      8.0500
      0
      0
      1
      0
      1
      0
      0
      1

```python
from sklearn.cross_validation import train_test_split
data_train, data_test, label_train, label_test = train_test_split(data, label, test_size = 0.3, random_state = 101)
```

    /Users/talat/anaconda3/lib/python3.6/site-packages/sklearn/cross_validation.py:41: DeprecationWarning: This module was deprecated in version 0.18 in favor of the model_selection module into which all the refactored classes and functions are moved. Also note that the interface of the new CV iterators are different from that of this module. This module will be removed in 0.20.
      "This module will be removed in 0.20.", DeprecationWarning)

```python
from sklearn.linear_model import LogisticRegression

# Run Logistic Regression
log_regr = LogisticRegression()
log_regr.fit(data_train, label_train)
predictions = log_regr.predict(data_test)
```

### Accuracy

```python
print('Accuracy', log_regr.score(data_test, label_test))
print('Coefficients', log_regr.coef_)
print('Intercept', log_regr.intercept_)
```

    Accuracy 0.8277153558052435
    Coefficients [[-0.03855005 -0.24328718 -0.10076536  0.00231142  1.18429317  0.2997331
      -0.8678696   1.56887514 -0.95271847  0.39520621  0.20701836  0.0139321 ]]
    Intercept [0.61615666]

### Precision Recall

```python
from sklearn.metrics import classification_report
print(classification_report(label_test, predictions))
```

                 precision    recall  f1-score   support

              0       0.82      0.92      0.87       163
              1       0.85      0.68      0.76       104

    avg / total       0.83      0.83      0.82       267

## Cross Validation

```python
from sklearn.model_selection import StratifiedKFold
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
# skf = StratifiedKFold(n_splits=5)

log_regr = LogisticRegression()
log_regr.fit(data_train, label_train)
score = log_regr.score(data_train, label_train)
print('Train accuracy score', score)

score_cv = cross_val_score(log_regr, data_train, label_train, cv=10, scoring='accuracy')
print('Cross Val Accuracy for each run', score_cv)
print('CrossVal Accuracy', score_cv.mean())

```

    Train accuracy score 0.8086816720257235
    Cross Val Accuracy for each run [0.76190476 0.6984127  0.79365079 0.87301587 0.80952381 0.77777778
     0.78688525 0.81967213 0.91803279 0.7704918 ]
    CrossVal Accuracy 0.8009367681498828

## AUC - Receiver Operating Characteristics
- How much a model is capable of distinguishing between classes
- Higher the AUC, better the model is

$
\begin{align}
True Positive Rate = \frac{TP}{TP + FN}
\end{align}
$

$
\begin{align}
\ False Positive Rate = 1 - \frac{TN}{TN + FP} = \frac{FP}{TN + FP}
\end{align}
$

```python
from sklearn import metrics

fpr, tpr, threshold = metrics.roc_curve(label_test, log_regr.predict(data_test))
roc_auc = metrics.auc(fpr, tpr)
print('AUCROC Stage1 vs Healthy: ' , roc_auc)
```

    AUCROC Stage1 vs Healthy:  0.8014688532326569

```python
import matplotlib.pyplot as plt

plt.title('Receiver Operating Characteristic')
plt.plot(fpr, tpr, 'b', label = 'AUC = %0.2f' % roc_auc)
plt.legend(loc = 'lower right')
plt.plot([0, 1], [0, 1],'r--')
plt.xlim([0, 1])
plt.ylim([0, 1])
plt.ylabel('True Positive Rate')
plt.xlabel('False Positive Rate')
plt.show()
```

```python

```

classification/notebooks/04 - Data Transformation.ipynb