# PHY321: Two-body problems and Gravitational Forces
**[Morten Hjorth-Jensen](http://mhjgit.github.io/info/doc/web/)**, Department of Physics and Astronomy and Facility for Rare Ion Beams (FRIB), Michigan State University, USA and Department of Physics, University of Oslo, Norway

Date: **Mar 21, 2022**

Copyright 1999-2022, [Morten Hjorth-Jensen](http://mhjgit.github.io/info/doc/web/). Released under CC Attribution-NonCommercial 4.0 license

## Aims and Overarching Motivation

### Monday March 21

Definition of the two-body problem, rewriting the equations in relative and center-of-mass coordinates

**Reading suggestion**: Taylor sections 8.2-8.3

### Wednesday March 23

Preparing the ground for the  gravitational force and its solution in two dimensions.
Harmonic Oscillator example in two dimensions (if we get time).

**Reading suggestion**: Taylor chapter 8.4-8.5

### Friday  March 25

Discussion and work on homework 7

## Videos of possible interest

* [Video on solving differential equations numerically](https://youtu.be/7nYIfV0z1VM)

* [Video on Fourier aanalysis](https://youtu.be/neXZ4fb-4Rs)

* [Handwritten notes for Fourier analysis](https://github.com/mhjensen/Physics321/blob/master/doc/HandWrittenNotes/Spring2022/NotesFourierAnalysisMarch21.pdf)

# Two-body Problems

Two-body problems play a central role in physics. Some of these problems can, with appropriate transformations, be solved analytically. The gravitional force problem (as well as the Coulomb potential problem for two particles) is an example of this.

There are several small steps which we need to do in order to reach this solution. These are
1. Rewriting the equations in terms of the degrees of freedom of the relative motion and the center of mass motion

2. Making a transformation either to polar (two dimensions) or to spherical coordinates (three dimensions)

3. This gives us uncoupled differential equations for each coordinate that we can solve analytically

The advantage in doing so is that we can extract a lot of interesting insights about the motion and the physics of these important physics problems. These insights can be transferred to other physics problems where the potentials are given by expressions proportional with the inverse relative distance.

## The gravitational force

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

$$
\begin{eqnarray}
V_{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}
V=\sum_{a<b}U_{ab}=\frac{1}{2}\sum_{a\ne b}V_{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}
$$

## Relative and Center of Mass Motion, assumptions

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}
$$

## Trajectory as a one-body problem

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}
$$

## Kinetic energy

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

$$
\begin{eqnarray}
K&=&\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.

## Euqations of motion
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}
$$

## Reordering the equations
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}
$$

## Force depends on $r$ only

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}
$$

## Further manipulations

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$.

## Final manipulations, part 1

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}
$$

## Final manipulations, part 2

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)$.

## Final manipulations, part 3

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$.

## Ellipse reminder

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

```
%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&=&V(r)+\frac{1}{2}mv_\theta^2+\frac{1}{2}m\dot{r}^2\\
\nonumber
&=&V(r)+\frac{1}{2}mr^2\dot{\theta}^2+\frac{1}{2}m\dot{r}^2\\
\nonumber
&=&V(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 $V(r)$, it is sometimes referred
to as the "effective" potential,

$$
\begin{eqnarray}
V_{\rm eff}(r)&=&V(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}V(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.

## Code example

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$.

```
# 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).

```
# 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

$$
V=\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$.

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

Deltax = 0.01
#set up arrays
xinitial = 0.5
xfinal = 3.0
k = 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 = 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*}
V_{\rm eff}&=&\frac{1}{2}kr^2+\frac{L^2}{2mr^2}
\end{eqnarray*}
$$

## Harmonic oscillator in two dimensions and effective potential
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}V_{\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*}
$$

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*}
$$

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 5 and 6 and the midterm.  Note that this code is
tailored to run in Cartesian coordinates. There is thus no angular
momentum dependent term.

Here we have chose initial conditions that
correspond to the minimum of the effective potential
$r_{\mathrm{min}}$. We have chosen $x_0=r_{\mathrm{min}}$ and
$y_0=0$. Similarly, we use the centripetal acceleration to determine
the initial velocity so that we have a circular motion (see back to the
last question of the midterm). This means that we set the centripetal
acceleration $v^2/r$ equal to the force from the harmonic oscillator $-k\boldsymbol{r}$. Taking the
magnitude of $\boldsymbol{r}$ we have then
$v^2/r=k/mr$, which gives $v=\pm\omega_0r$.

Since the code here solves the equations of motion in cartesian
coordinates and the harmonic oscillator potential leads to forces in
the $x$- and $y$-directions that are decoupled, we have to select the initial velocities and positions so that we don't get that for example $y(t)=0$.

We set $x_0$ to be different from zero and $v_{y0}$ to be different from zero.

```

DeltaT = 0.001
#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 = 1.0   # spring constant
m = 1.0   # mass, you can change these
omega02 = k/m  # Frequency
AngMom = 1.0  #  The angular momentum
# Potential minimum
rmin = (AngMom*AngMom/k/m)**0.25
# Initial conditions as compact 2-dimensional arrays, x0=rmin and y0 = 0
x0 = rmin; y0= 0.0
r0 = np.array([x0,y0])
vy0 = sqrt(omega02)*rmin; vx0 = 0.0
v0 = np.array([vx0,vy0])
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()
```

We see that the radius (to within a given error), we obtain a constant radius.

The following code shows first how we can solve this problem using the radial degrees of freedom only.
Here we need to add the explicit centrifugal barrier.  Note that the variable $r$ depends only on time. There is no $x$ and $y$ directions
since we have transformed the equations to polar coordinates.

```
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 = 1.0
k = 1.0
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()
```

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.

## Stability of Orbits

The effective force can be extracted from the effective potential, $V_{\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_rV_{\rm eff},\\
\nonumber
F_{\rm eff}&=&-\partial_r\left[V(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_rV$ 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 $V(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.V_{\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.

```

# 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.

```
# 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)
```

Central forces are forces which are directed towards or away from a
reference point. A familiar force is the gravitional
force  with the motion of our Earth around the Sun as a classic. The Sun, being
approximately sixth order of magnitude heavier than the Earth serves
as our origin. A force like the gravitational force is a function of the
relative distance $\boldsymbol{r}=\boldsymbol{r}_1-\boldsymbol{r}_2$ only, where
$\boldsymbol{r}_1$ and $\boldsymbol{r}_2$ are the positions relative to a defined
origin for object one and object two, respectively.

These forces depend on the spatial degrees of freedom only (the
positions of the interacting objects/particles). As discussed earlier, from such forces we can infer
that  the total internal energy, the total linear momentum and total angular momentum are so-called constants of the motion, that is they stay constant  over time. We say that energy, linear and anuglar momentum are conserved.

With a scalar potential $V(\boldsymbol{r})$ we define the force as the gradient of the potential

$$
\boldsymbol{F}(\boldsymbol{r})=-\boldsymbol{\nabla}V(\boldsymbol{r}).
$$

In general these potentials depend only on the magnitude of the
relative position and we will write the potential as $V(r)$ where $r$
is defined as,

$$
r = |\boldsymbol{r}_1-\boldsymbol{r}_2|.
$$

In three dimensions our vectors are defined as (for a given object/particle $i$)

$$
\boldsymbol{r}_i = x_i\boldsymbol{e}_1+y_i\boldsymbol{e}_2+z_i\boldsymbol{e}_3,
$$

while in two dimensions we have

$$
\boldsymbol{r}_i = x_i\boldsymbol{e}_1+y_i\boldsymbol{e}_2.
$$

In two dimensions the radius $r$ is defined as

$$
r = |\boldsymbol{r}_1-\boldsymbol{r}_2|=\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}.
$$

If we consider the gravitational potential involving two masses $1$ and $2$, we have

$$
V_{12}(r)=V(r)=-\frac{Gm_1m_2}{|\boldsymbol{r}_1-\boldsymbol{r}_2|}=-\frac{Gm_1m_2}{r}.
$$

Calculating the gradient of this potential we obtain the force

$$
\boldsymbol{F}(\boldsymbol{r})=-\frac{Gm_1m_2}{|\boldsymbol{r}_1-\boldsymbol{r}_1|^2}\hat{\boldsymbol{r}}_{12}=-\frac{Gm_am_b}{r^2}\hat{\boldsymbol{r}},
$$

where we have the unit vector

$$
\hat{\boldsymbol{r}}=\hat{\boldsymbol{r}}_{12}=\frac{\boldsymbol{r}_2-\boldsymbol{r}_1}{|\boldsymbol{r}_1-\boldsymbol{r}_2|}.
$$

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

$$
V=\sum_{i<j}V_{ij}=\frac{1}{2}\sum_{i\ne j}V_{ij}.
$$

In general, the central forces that we will study can be written mathematically as

$$
\boldsymbol{F}(\boldsymbol{r})=f(r)\hat{r},
$$

where $f(r)$ is  a scalar function. For the above gravitational force this scalar term is
$-Gm_1m_2/r^2$.
In general we will simply write this scalar function $f(r)=\alpha/r^2$ where $\alpha$ is a constant that can be either negative or positive. We will also see examples of other types of potentials in the examples below.

Besides general expressions for the potentials/forces, we will discuss
in detail different types of motion that arise, from circular to
elliptical or hyperbolic or parabolic. By transforming to either polar
coordinates or spherical coordinates, we will be able to obtain
analytical solutions for the equations of motion and thereby obtain
new insights about the properties of a system. Where possible, we will
compare our analytical equations with numerical studies.

However, before we arrive at these lovely insights, we need to
introduce some mathematical manipulations and definitions. We conclude
this chapter with a discussion of two-body scattering.

## Center of Mass and Relative Coordinates

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}$ and the relative
coordinate $\boldsymbol{r}$. We define the center-of-mass (CoM) coordinate as

$$
\boldsymbol{R}\equiv\frac{m_1\boldsymbol{r}_1+m_2\boldsymbol{r}_2}{m_1+m_2},
$$

and the relative coordinate as

$$
\boldsymbol{r}\equiv\boldsymbol{r}_1-\boldsymbol{r_2}.
$$

We can then rewrite $\boldsymbol{r}_1$ and $\boldsymbol{r}_2$ in terms of the relative and CoM coordinates as

$$
\boldsymbol{r}_1=\boldsymbol{R}+\frac{m_2}{M}\boldsymbol{r},
$$

and

$$
\boldsymbol{r}_2=\boldsymbol{R}-\frac{m_1}{M}\boldsymbol{r}.
$$

### Conservation of total  Linear Momentum

In our discussions on conservative forces we defined
the total linear momentum as

$$
\boldsymbol{P}=\sum_{i=1}^Nm_i\frac{d\boldsymbol{r}_i}{dt},
$$

where $N=2$ in our case. With the above definition of the center of mass position, we see that we can rewrite the total linear momentum as (multiplying the CoM coordinate  with $M$)

$$
\boldsymbol{P}=M\frac{d\boldsymbol{R}}{dt}=M\dot{\boldsymbol{R}}.
$$

The net force acting on the system is given by the time derivative of the linear momentum (assuming mass is time independent)
and we have

$$
\boldsymbol{F}^{\mathrm{net}}=\dot{\boldsymbol{P}}=M\ddot{\boldsymbol{R}}.
$$

The net force acting on the system is given by the sum of the forces acting on the two bodies, that is we have

$$
\boldsymbol{F}^{\mathrm{net}}=\boldsymbol{F}_1+\boldsymbol{F}_2=\dot{\boldsymbol{P}}=M\ddot{\boldsymbol{R}}.
$$

In our case the forces are given by the internal forces only. The force acting on object $1$ is thus $\boldsymbol{F}_{12}$ and the one acting on object $2$ is $\boldsymbol{F}_{12}$. We have also defined that $\boldsymbol{F}_{12}=-\boldsymbol{F}_{21}$. This means thar we have

$$
\boldsymbol{F}_1+\boldsymbol{F}_2=\boldsymbol{F}_{12}+\boldsymbol{F}_{21}=0=\dot{\boldsymbol{P}}=M\ddot{\boldsymbol{R}}.
$$

We could alternatively had write this

$$
\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\}=\frac{1}{m_1+m_2}\left\{\boldsymbol{F}_{12}+\boldsymbol{F}_{21}\right\}=0.
$$

This has the important consequence that the CoM velocity is a constant
of the motion. And since the total linear momentum is given by the
time-derivative of the CoM coordinate times the total mass
$M=m_1+m_2$, it means that linear momentum is also conserved.
Stated differently,  the center-of-mass coordinate
$\boldsymbol{R}$ moves at a fixed velocity.

This has also another important consequence for our forces. If we
assume that our force depends only on the relative coordinate, it
means that the gradient of the potential with respect to the center of
mass position is zero, that is

$$
M\ddot{d\boldsymbol{R}}=-\boldsymbol{\nabla}_{\boldsymbol{R}}V =0!
$$

If we now switch to the equation of motion for the relative coordinate, we have

$$
\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)=\left(\frac{1}{m_1}+\frac{1}{m_2}\right)\boldsymbol{F}_{12},
$$

which we can rewrite in terms of the reduced mass

$$
\mu=\frac{m_1m_2}{m_1+m_2},
$$

as

$$
\mu \ddot{\boldsymbol{r}}=\boldsymbol{F}_{12}.
$$

This has a very important consequence for our coming analysis of the equations of motion for the two-body problem.
Since the acceleration for the CoM coordinate is zero, we can now
treat the trajectory as a one-body problem where the mass is given by
the reduced mass $\mu$ plus a second trivial problem for the center of
mass. The reduced mass is especially convenient when one is
considering forces that depend only on the relative coordinate (like the Gravitational force or the electrostatic force between two charges)  because then for say the gravitational force we have

$$
\mu \ddot{\boldsymbol{r}}=-\frac{Gm_1m_2}{r^2}\hat{\boldsymbol{r}}=-\frac{GM\mu}{r^2}\hat{\boldsymbol{r}},
$$

where we have defined $M= m_1+m_2$.  It means that the acceleration of the relative coordinate is

$$
\ddot{\boldsymbol{r}}=-\frac{GM}{r^2}\hat{\boldsymbol{r}},
$$

and we have that for the gravitational problem, the reduced mass then falls out and the
trajectory depends only on the total mass $M$.

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, see our discussion of scattering problems below, can also be
expressed in this frame.  Before we proceed to our definition of the CoM frame we need to set up the expression for the energy in terms of the relative and CoM coordinates.

### Kinetic and total Energy

The kinetic energy and momenta also have analogues in center-of-mass
coordinates.
We have defined the total linear momentum as

$$
\boldsymbol{P}=\sum_{i=1}^Nm_i\frac{d\boldsymbol{r}_i}{dt}=M\dot{\boldsymbol{R}}.
$$

For the relative momentum $\boldsymbol{q}$, we have that the time derivative of $\boldsymbol{r}$ is

$$
\dot{\boldsymbol{r}} =\dot{\boldsymbol{r}}_1-\dot{\boldsymbol{r}}_2,
$$

We know also that the momenta $\boldsymbol{p}_1=m_1\dot{\boldsymbol{r}}_1$ and
$\boldsymbol{p}_2=m_2\dot{\boldsymbol{r}}_2$. Using these expressions we can rewrite

$$
\dot{\boldsymbol{r}} =\frac{\boldsymbol{p}_1}{m_1}-\frac{\boldsymbol{p}_2}{m_2},
$$

which gives

$$
\dot{\boldsymbol{r}} =\frac{m_2\boldsymbol{p}_1-m_1\boldsymbol{p}_2}{m_1m_2},
$$

and dividing both sides with $M$ we have

$$
\frac{m_1m_2}{M}\dot{\boldsymbol{r}} =\frac{m_2\boldsymbol{p}_1-m_1\boldsymbol{p}_2}{M}.
$$

Introducing the reduced mass $\mu=m_1m_2/M$ we have finally

$$
\mu\dot{\boldsymbol{r}} =\frac{m_2\boldsymbol{p}_1-m_1\boldsymbol{p}_2}{M}.
$$

And $\mu\dot{\boldsymbol{r}}$ defines the relative momentum $\boldsymbol{q}=\mu\dot{\boldsymbol{r}}$.

With these definitions we can then calculate the kinetic energy in terms of the relative and CoM coordinates.

We have that

$$
K=\frac{p_1^2}{2m_1}+\frac{p_2^2}{2m_2},
$$

and with $\boldsymbol{p}_1=m_1\dot{\boldsymbol{r}}_1$ and $\boldsymbol{p}_2=m_2\dot{\boldsymbol{r}}_2$ and using

$$
\dot{\boldsymbol{r}}_1=\dot{\boldsymbol{R}}+\frac{m_2}{M}\dot{\boldsymbol{r}},
$$

and

$$
\dot{\boldsymbol{r}}_2=\dot{\boldsymbol{R}}-\frac{m_1}{M}\dot{\boldsymbol{r}},
$$

we obtain after squaring the expressions for $\dot{\boldsymbol{r}}_1$ and $\dot{\boldsymbol{r}}_2$

$$
K=\frac{(m_1+m_2)\dot{\boldsymbol{R}}^2}{2}+\frac{(m_1+m_2)m_1m_2\dot{\boldsymbol{r}}^2}{2M^2},
$$

which we simplify to

$$
K=\frac{\dot{\boldsymbol{P}}^2}{2M}+\frac{\mu\dot{\boldsymbol{q}}^2}{2}.
$$

Below we will define a reference frame, the so-called CoM-frame, where
$\boldsymbol{R}=0$. This is going to simplify our equations further.

### Conservation of Angular Momentum

The angular momentum (the total one) is the sum of the individual angular momenta. In our case we have two bodies only, meaning that our angular momentum is defined as

$$
\boldsymbol{L} = \boldsymbol{r}_1 \times \boldsymbol{p}_1+\boldsymbol{r}_2 \times \boldsymbol{p}_2,
$$

and using that $m_1\dot{\boldsymbol{r}}_1=\boldsymbol{p}_1$ and $m_2\dot{\boldsymbol{r}}_2=\boldsymbol{p}_2$ we have

$$
\boldsymbol{L} = m_1\boldsymbol{r}_1 \times \dot{\boldsymbol{r}}_1+m_2\boldsymbol{r}_2 \times \dot{\boldsymbol{r}}_2.
$$

We define now the CoM-Frame where we set $\boldsymbol{R}=0$. This means that the equations
for $\boldsymbol{r}_1$ and $\boldsymbol{r}_2$ in terms of the relative motion simplify and  we have

$$
\boldsymbol{r}_1=\frac{m_2}{M}\boldsymbol{r},
$$

and

$$
\boldsymbol{r}_2=-\frac{m_1}{M}\boldsymbol{r}.
$$

resulting in

$$
\boldsymbol{L} = m_1 \frac{m_2}{M}\boldsymbol{r}\times\frac{m_2}{M}\boldsymbol{r} +m_2 \frac{m_1}{M}\boldsymbol{r} \times \frac{m_1}{M}\dot{\boldsymbol{r}}.
$$

We see that can rewrite this equation as

$$
\boldsymbol{L}=\boldsymbol{r}\times \mu\dot{\boldsymbol{r}}=\mu\boldsymbol{r}\times \dot{\boldsymbol{r}}.
$$

If we now use a central force, we have that

$$
\mu\dot{\boldsymbol{r}}=\boldsymbol{F}(\boldsymbol{r})=f(r)\hat{\boldsymbol{r}},
$$

and inserting this in the equation for the angular momentum we have

$$
\boldsymbol{L}=\boldsymbol{r}\times f(r)\hat{\boldsymbol{r}},
$$

which equals zero since we are taking the cross product of the vector
$\boldsymbol{r}$ with itself.  Angular momentum is thus conserved and in
addition to the total linear momentum being conserved, we know that
energy is also conserved with forces that depend only on position and
the relative coordinate only.

Since angular momentum is conserved, we can idealize
the motion of our two objects as two bodies moving in a plane spanned by the
relative coordinate and the relative momentum. The angular
momentum is perpendicular to the plane spanned by these two vectors.

It means also, since $\boldsymbol{L}$ is conserved, that we can reduce our
problem to a motion in say the $xy$-plane.  What we have done then is to
reduce a two-body problem in three-dimensions with six degrees of
freedom (the six coordinates of the two objects) to a problem defined
entirely by the relative coordinate in two dimensions.  We have thus
moved from a problem with six degrees of freedom to one with two degrees of freedom only.

Since we deal with central forces that depend only on the
relative coordinate, we will show below that transforming to polar
coordinates, we cna find analytical solution to
the equation of motion

$$
\mu\dot{\boldsymbol{r}}=\boldsymbol{F}(\boldsymbol{r})=f(r)\hat{\boldsymbol{r}}.
$$

Note the boldfaced symbols for the relative position $\boldsymbol{r}$. Our vector $\boldsymbol{r}$ is defined as

$$
\boldsymbol{r}=x\boldsymbol{e}_1+y\boldsymbol{e}_2
$$

and introducing polar coordinates $r\in[0,\infty)$ and $\phi\in [0,2\pi]$ and the transformation

$$
r=\sqrt{x^2+y^2},
$$

and $x=r\cos\phi$ and $y=r\sin\phi$, we will rewrite our equation of motion by transforming from Cartesian coordinates to Polar coordinates. By so doing, we end up with two differential equations which can be solved analytically (it depends on the form of the potential).

What follows now is a rewrite of these equations and the introduction of Kepler's laws as well.


Functions

Named sequences of statements that performs some useful operations.

- [Overview](#Overview)
- [Function calls](#Function-calls)
- [Composition](#Composition)
- [Creating new functions](#Creating-new-functions)
- [Flow of execution](#Flow-of-execution)
- [Function arguments](#Function-arguments)
- [Function namespace](#Function-namespace)
- [Return objects](#Return-objects)
- [Incremental development](#Incremental-development)
- [Notebook namespace](#Notebook-namespace)
- [Why functions?](#Why-functions?)
- [Debugging](#Debugging)
- [Glossary](#Glossary)
- [Exercises](#Exercises)

## Overview

In the context of programming, a **function** is a named sequence of programming statements that performs a specific task or computation when called. When you define a function, you specify the name of the function and the sequence of statements that get executed when the function is called. Functions can be called, by name, from other programs or from other functions.

## Function calls

We have already seen **function calls** (the use of functions) in previous chapters, for example the ``radians`` function:

```python
from numpy import radians

radians(90)
```

The name of this function is ``radians`` and it was defined in the ``numpy`` module. This function is given a single argument (``90`` degrees) and returns the equivalent angle in radians. It is common to say that a function "takes" an argument and "returns" a result. The result is also called the return object.

 Take Note

It is the parenthese - () - after the function name that instructs Python to call the function. The expressions or objects inside the parenthese are the arguments to the function. Omitting the parenthese is interpreted by Python as "lookup the object bound to the variable name radians". Consider the output from running the following Code Cell:

```python
from numpy import radians

radians
```

In the example above we do not know how the ``radians`` function works or what statements are inside this function, we simply take for granted that it works and will correctly return the equivalent angle in radians for an input angle in degrees.

For the sake of interest we can verify that this ``radians`` function works correctly, by manually converting ``90`` degrees to radians and comparing the answer to that of the ``radians`` function:

```python
from numpy import pi

90 * pi / 180
```

## Composition

So far, we have looked at the elements of a program - variables, objects, expressions, and statements - in isolation, without talking much about how to combine them. One of the most useful features of programming languages is their ability to take small building blocks and **compose** them. For example, the arguments of a function can be any kind of expression, including arithmetic operators or even other function calls:

```python
import numpy as np

degrees = 90
x = np.sin(degrees / 360 * 2 * np.pi)
print(x)

x = np.exp(np.log(x + 1))
print(x)
```

 Take Note

The expression inside the function parentheses, for example (degrees / 360 \* 2 \* np.pi), is evaluated first and the result thereof is sent as an argument to the function np.sin. Almost anywhere you can put a value, you can put an arbitrary expression.

Keep in mind that everthing on the right hand side of the assignment (numbers, variables, objects, etc.) must be "known" (can be computed). Therefore, make sure that a name has been bound to an object before you use that name on the right of the assignment (equal sign).

Only a variable name is allowed on the left hand side of the equal sign. Any other expression on the left hand side, including function calls, gives a syntax error, for example:

```python
import numpy as np

degrees = 90
np.sin(degrees / 360 * 2 * np.pi) = x
print(x)
```

## Creating new functions

Programming would be a very difficult task if we could only make use of existing functions.
Not only would our programs become very long and difficult to keep track of, but it would also be difficult for a group of programmers to develop different components of a new program simultaneously.
So far, we have only been using the functions that come with *Python* or can be found in the ``numpy`` module.
Fortunately, almost all programming languages allow for the creating of new functions.

### Example - Tongue twister

As an example, lets create a function that prints a tongue twister to the screen:

```python
def print_tongue_twister():
    print('Fuzzy Wuzzy was a bear.')
    print('Fuzzy Wuzzy had no hair.')
    print("Fuzzy Wuzzy wasn't fuzzy, was he?")
```

The **function definition** specifies the name of the new function (``print_tongue_twister``) and the sequence of programming statements that run when the function is called.

The ``def`` keyword is used to define the new function with a specific function name (in this case ``print_tongue_twister``). The empty parentheses, after the function name, indicates that this function does not take any arguments. The first line of the function definition is called the **function header** and the rest is called the **function body**. The function body can contain any number of programming statements.

 Take Note

The rules for function names are the same as for variable names: Letters, numbers and underscores are legal, but the first character can't be a number. You should not use a keyword as either a function name or variable name, and you should avoid having a variable and a function with the same name.

The function header must end with a double colon (:). This double colon instructs Python where the statements inside the function body start. If the double colon is left out you will see an error message. Try it out - delete the double colon in the Code Cell above and run it to see the error message.

The function body must be indented. It is this indentation that instructs Python as to which statements belong to the function body. Indentation means the amount of white space placed in front of your code. *Python* uses 4 spaces as 1 indentation.

Executing the Code Cell above only defines the function and does not give any output. This is because the statements in the function body are not executed until the function is called. The function definition must be executed first, to define the function, before the function can be called, otherwise you get a NameError.

 More Information

Most editors (like *Jupyter Notebook*) will automatically add 4 spaces when you push the `tab` key. And `Shift + tab` will automatically remove 4 spaces. This can also be used if you have highlighted several lines of code. Try it !!

The strings in the print statements are enclosed in single and double quotes. Single quotes and double quotes do the same thing; most people use single quotes except in cases like this where a single quote (which is also an apostrophe) appears in the string.

All quotation marks (single and double) must be “straight quotes”, usually located next to Enter on the keyboard. “Curly quotes”, like the ones in this sentence, are not legal in Python.

For more information on valid names, see the PEP8 standard: http://www.python.org/dev/peps/pep-0008/

To see a list of keywords, that should not be used as function names, execute help('keywords') in a Code Cell.

Executing the *Code Cell* above (with the ``print_tongue_twister`` function) only defines the function (no output is given). Defining a function creates a **function object**, with a specified name, which has type ``function`` (note the output of the following two *Code Cell*):

```python
print(print_tongue_twister)
```

```python
type(print_tongue_twister)
```

The syntax for calling this new function is the same as for built-in functions (keeping in mind this function does not take any arguments):

```python
print_tongue_twister()
```

Once a function has been defined, it can be called (as shown in the *Code Cell* above) or it can be used inside another function. For example, to repeat the tongue twister, we could write a function called ``repeat_twister``:

```python
def repeat_twister():
    print_tongue_twister()
    print_tongue_twister()
```

And then call this ``repeat_twister`` function:

```python
repeat_twister()
```

Pulling together the code fragments above, the whole program looks like this:

```python
%reset -f

def print_tongue_twister():
    print("Fuzzy Wuzzy was a bear.")
    print("Fuzzy Wuzzy had no hair.")
    print("Fuzzy Wuzzy wasn't fuzzy, was he?")

def repeat_twister():
    print_tongue_twister()
    print_tongue_twister()

repeat_twister()
```

 Take Note

This program contains two function definitions: print_tongue_twister and repeat_twister. Function definitions get executed just like other statements, but the effect is to create function objects. The statements inside the function do not run until the function is called, and the function definition generates no output.

As you might expect, you have to create a function before you can run it. In other words, the function definition has to run before the function gets called.

As an exercise, move the last line (line 14) of this program to the top (just after the %reset -f line), so the function call appears before the definitions. Run the Code Cell and see what error message you get.

Now move the function call back to the bottom and move the definition of print_tongue_twister after the definition of repeat_twister. What happens when you run this program and why?

## Flow of execution

To ensure that a function is defined before its first use, you have to know the order statements run in, which is called the flow of execution. Execution always begins at the first statement of the program. Statements are run one at a time, in order from top to bottom.

Function definitions do not alter the flow of execution of the program, but remember that statements inside the function don’t run until the function is called.

A function call is like a detour in the flow of execution. Instead of going to the next statement, the flow jumps to the body of the function, runs the statements there, and then comes back to pick up where it left off.

That sounds simple enough, until you remember that one function can call another. While in the middle of one function, the program might have to run the statements in another function. Then, while running that new function, the program might have to run yet another function!

Fortunately, *Python* is good at keeping track of where it is, so each time a function completes, the program picks up where it left off in the function that called it. When it gets to the end of the program, it terminates.

In summary, when you read a program, you don’t always want to read from top to bottom. Sometimes it makes more sense if you follow the flow of execution as if you were *Python*.

There is also the added complexity of *Code Cells* and the order in which you execute these *Code Cells* that will be explained in the following sections. For now, execute the following two *Code Cells* and step through the line-by-line execution to ensure you understand the explaination given above:

```python
%load_ext nbtutor
```

```python
%%nbtutor -r -f --depth 3

def print_tongue_twister():
    print("Fuzzy Wuzzy was a bear.")
    print("Fuzzy Wuzzy had no hair.")
    print("Fuzzy Wuzzy wasn't fuzzy, was he?")

def repeat_twister():
    print_tongue_twister()
    print_tongue_twister()

repeat_twister()
```

## Function arguments

In the tongue twister example above, none on the functions we created required any arguments, but many of the functions we have seen before do require arguments. For example, when we call the ``numpy.radians`` function we passed a single number as an argument.

Inside the function, the arguments are assigned to variable names called parameters. Here is a definition for a function that takes a single argument:

```python
def print_twice(thing):
    print(thing)
    print(thing)
```

When this function is called, the single argument is assigned to the parameter named ``thing`` and the value of the object (whatever is is) is printed twice:

```python
print_twice("Hello")
```

```python
print_twice(42)
```

The same rules of composition that apply to built-in functions also apply to programmer-defined functions, so we can use any kind of expression as an argument for ``print_twice``:

```python
import numpy as np

degrees = 90
print_twice(np.sin(np.radians(degrees)))
```

 Take Note

The argument is evaluated before the function is called, so in the example above the expression np.sin(np.radians(degrees)) is only evaluated once and the result thereof is sent as input to the print_twice function and assigned to the parameter named thing.

The variable name we pass as an argument has nothing to do with the parameter name (thing). It does not matter what the object was called "back home" (in the caller); in print_twice, everybody is called thing. For example consider the following Code Cell:

```python
%%nbtutor -r -f
def print_twice(thing):
    print(thing)
    print(thing)

bird = "Robin"
print_twice(bird)
```

A function can take as many arguments as needed for the specific task or computation it is designed for. Here is a definition for a function that takes two arguments:

```python
def print_cat(part1, part2):
    print(part1 + part2)
```

This function prints the result of adding two parameters together. The order of the arguments passed in to the function is mapped to the order of the parameters, for example:

```python
one = "Here's"
two = " Johnny"

print_cat(one, two)
print_cat(two, one)
```

## Function namespace

Each function gets its own namespace where variables and parameters may exist / get created. For example, when you create a variable inside a function, it is local, which means that it only exists inside that function namespace. Function parameters are also local, which means they only exist inside the function namespace where they were created.

```python
%%nbtutor -r -f --depth 3
def print_twice(thing):
    print(thing)
    print(thing)

def cat_twice(part1, part2):
    cat = part1 + part2
    print_twice(cat)

this = "Bing tiddle "
that = "tiddle bang."
cat_twice(this, that)
```

Running the example above (using ``nbtutor`` to visualize the execution) will show how each function gets its own namespace. The ``Global frame`` is the namespace that is global to this notebook, I.e. global variables and function definitions get added to the notebook namespace. Function parameters and local function variables get created only in the function namespace where they were specifed.

Stepping through the example above you should notice that the function parameters ``part1`` and ``part2`` and the variable ``cat`` only exist inside the ``cat_twice`` function namespace. The function parameter ``thing`` only exists inside the ``print_twice`` function namespace.

Once the ``print_twice`` function finishes running (terminates), the ``thing`` parameter no longer exists (I.e. the ``print_twice`` namespace is deleted along with its variables and parameters). Once the ``cat_twice`` function terminates, the ``part`` and ``part2`` parameters and the ``cat`` variable no longer exist.

For example, if we tried to print the variable ``cat``, we would get an error:

```python
print(cat)
```

 Take Note

When a function is called, a new namespace is created where local variable names and parameters are created. Once the function terminates (finishes) this namespace is deleted along with the local variable names and parameters. Only the variable and paramater names are deleted, not the objects. The objects only get deleted if there are no names pointing to them anymore.

Objects send as arguments to functions are not copied! Consider the example above and take note of how parameters and/or variables in different namespaces point to the same objects.

## Return objects

When calling a function that generates a return object, we usually assign that object to a variable or use it as part of an expression:

```python
import numpy as np

radius = 1
e = np.exp(1.0)
radians = np.radians(90)
height = radius * np.sin(radians)
```

Many of the functions we have used, such as the ``numpy`` functions, return objects. Other functions, like ``print_twice``, perform an action but do not return any objects. The truth is, in *Python*, every function returns something to the caller. If we do not specify what the function should return, by default it will return ``None`` (a special object for "nothing"). For example, in the ``print_twice`` function we did not specify what this function should return:

```python
def print_twice(thing):
    print(thing)
    print(thing)
```

And if we use this function, expecting it to return something (similar to how the ``numpy.radians`` function returned an answer) we see that ``None`` is returned:

```python
something = print_twice("Hello")
print(something)
```

The object ``None`` is not the same as the string ``'None'`` . It is a special object that has its own type:

```python
type(None)
```

### Example - Circle area

As an example, lets create a function that computes and returns the area of a circle from a given radius:

```python
import numpy as np

def area(radius):
    ans = np.pi * radius**2
    return ans
```

The ``return`` statement means: “Return immediately from this function and use the following expression as a return object.”

 Take Note

The return statement tells Python what object/s must be returned from the function as output. The return statement exits the function immediately and no code (in the function body) after the return statement will be executed.

 The expression (following the ``return`` keyword) can be arbitrarily complicated, so we could have written this function more concisely:

```python
import numpy as np

def area(radius):
    return np.pi * radius**2
```

On the other hand, temporary variables like ``ans`` can make debugging easier.

## Incremental development

As you write larger functions, you might find yourself spending more time debugging. To deal with increasingly complex programs, you might want to try a process called incremental development. The goal of incremental development is to avoid long debugging sessions by adding and testing only a small amount of code at a time.

### Example - Distance

As an example, suppose you want to find the distance between two points, given by the coordinates $(x_1, y_1)$ and $(x_2, y_2)$. By the Pythagorean theorem, the distance is:
$$
\text{distance} = \sqrt{(x_2 − x_1)^2 + (y_2 − y_1)^2}
$$

The first step is to consider what a distance function should look like in *Python*. In other words, what are the arguments (parameters) and what is the output (return object)?

In this case, the arguments are the two points, which you can represent using four numbers. The return object is the distance represented by a floating-point number.

Immediately you can write an outline of the function:

```python
def distance(x1, y1, x2, y2):
    return 0.0
```

Obviously, this version doesn’t compute distances; it always returns zero. But it is syntactically correct, and it runs, which means that you can test it before you make it more complicated. To test the new function, call it with sample arguments:

```python
distance(1, 2, 4, 6)
```

I chose these values so that the horizontal distance is 3 and the vertical distance is 4; that way, the result is 5, the hypotenuse of a 3-4-5 triangle. When testing a function, it is useful to know the right answer.

At this point we have confirmed that the function is syntactically correct, and we can start adding code to the body. A reasonable next step is to find the differences $x_2 − x_1$ and $y_2 − y_1$.

The next version stores those values in temporary variables and prints them:

```python
def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    print('dx is', dx)
    print('dy is', dy)
    return 0.0
```

If the function is working, it should display ``dx`` is 3 and ``dy`` is 4 . If so, we know that the function is getting the right arguments and performing the first computation correctly. If not, there are only a few lines to check.

```python
distance(1, 2, 4, 6)
```

Next we compute the sum of squares of ``dx`` and ``dy``:

```python
def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    print('dsquared is: ', dsquared)
    return 0.0
```

Again, you would run the program at this stage and check the output (which should be
25).

```python
distance(1, 2, 4, 6)
```

 Finally, you can use ``np.sqrt`` to compute and return the result:

```python
import numpy as np

def distance(x1, y1, x2, y2):
    dx = x2 - x1
    dy = y2 - y1
    dsquared = dx**2 + dy**2
    result = np.sqrt(dsquared)
    return result
```

If that works correctly, you are done. Otherwise, you might want to print the value of result before the return statement.

```python
distance(1, 2, 4, 6)
```

 Take Note

The final version of the function doesn’t display anything when it runs; it only returns an object. The print statements we wrote are useful for debugging, but once you get the function working, you should remove them. Code like that is called scaffolding because it is helpful for building the program but is not part of the final product.

When you start out, you should add only a line or two of code at a time. As you gain more experience, you might find yourself writing and debugging bigger chunks. Either way, incremental development can save you a lot of debugging time.

The key aspects of the process are:
1. Start with a working program and make small incremental changes. At any point, if there is an error, you should have a good idea where it is.
2. Use variables to hold intermediate objects so you can display and check them.
3. Once the program is working, you might want to remove some of the scaffolding or consolidate multiple statements into compound expressions, but only if it does not make the program difficult to read.

As an exercise, use incremental development to write a function called ``hypotenuse`` that returns the length of the hypotenuse of a right triangle given the lengths of the other two legs as arguments. Record each stage of the development process as you go.

### Example - Circle area

Incremental development not only helps from a single function point of view, but also from a function structure point of view. As an example, lets write a function that takes two points, the center of a circle and a point on the perimeter, and computes the area of the circle.

Assume that the center point is stored in the variables ``xc`` and ``yc``, and the perimeter point is in ``xp`` and ``yp``. The first step is to find the radius of the circle, which is the distance between the two points. We just wrote a function, distance, that does that:
```python
radius = distance(xc, yc, xp, yp)
```

The next step is to find the area of a circle with that radius; we just wrote that, too:
```python
result = area(radius)
```

Encapsulating these steps in a function, we get:

```python
def circle_area(xc, yc, xp, yp):
    radius = distance(xc, yc, xp, yp)
    result = area(radius)
    return result
```

 Take Note

It is often useful, when you come to a function call, instead of following the flow of execution, you assume that the function works correctly and returns the right result.

In fact, you are already practicing this leap of faith when you use built-in functions. When you call ``numpy.cos`` or ``numpy.exp``, you don’t examine the bodies of those functions. You just assume that they work because the people who wrote the built-in functions were good programmers.

The same is true when you call one of your own functions. Once we have convinced ourselves that this function is correct — by examining the code and testing — we can use the function without looking at the body again.

The temporary variables ``radius`` and ``result`` are useful for development and debugging, but once the program is working, we can make it more concise by composing the function calls:

```python
def circle_area(xc, yc, xp, yp):
    radius = distance(xc, yc, xp, yp)
    return area(radius)
```

Putting the pieces together in one *Code Cell*, we have:

```python
import numpy as np

def area(radius):
    return np.pi * radius**2

def distance(x1, y1, x2, y2):
    return np.sqrt((x2 - x1)**2 + (y2 - y1)**2)

def circle_area(xc, yc, xp, yp):
    radius = distance(xc, yc, xp, yp)
    return area(radius)

result = circle_area(1, 2, 4, 6)
print(result)
```

## Notebook namespace

As mentioned previously, each function gets its own namespace where variables and parameters may exist / get created. For example, when you create a variable inside a function, it is local, which means that it only exists inside that function namespace. There is also a global (or Notebook) namespace. Global variables and function definitions get added to this Notebook namespace and are accessible from inside local function namespaces.

All variables and function definitions that exist in the Notebook namespace can be displayed by executing ``%whos`` in a *Code Cell*, for example:

```python
%whos
```

The Notebook namespace can be cleared (I.e. all variables and function definitions be deleted) by executing ``%reset`` in a *Code Cell*, for example:

```python
%reset
```

 Take Note

After executing the *Code Cell* above with the ``%reset`` statement, re-execute the *Code Cell* above with the ``%whos`` statement to see how the Notebook namespace was cleared.

### Example - Falling object

As an example, lets consider the example of an object falling from a height above the ground.
The equations describing the motion of this object, assuming constant acceleration, are:

$$
\begin{align}
    a(t) &= g \: \text{(constant)} \\
    v(t) &= \int a(t) \: \mathrm{d}t  =  v_0 + gt \\
    s(t) &= \int v(t) \: \mathrm{d}t = s_0 + v_0t + 0.5 gt^2
\end{align}
$$

where $g$ is the gravitationaly acceleration $[m/s^2]$, $v_0$ the initial object velocity $[m/s]$, $s_0$ the initial object height $[m]$ above the ground, and $t$ is the time $[s]$.

The following function (named ``height``) computes the objects height $s(t)$ above the ground for any initial height $s_0$, velocity $v_0$, and time $t$ values:

```python
def height(s0, v0, t):
    g = -9.81
    st = s0 + v0 * t + 0.5 * g * t**2
    return st
```

When creating new functions, you as the programmer will decide what inputs are needed for the function and what value/s the function returns. In this example, the equation we are using requires four variables in order to solve for the height $s(t)$, these variable being the initial height $s_0$, initial velocity $v_0$, gravitational acceleration $g$, and time $t$.

I decided that the ``height`` function should take the initial height $s_0$, initial velocity $v_0$, and the time $t$ values as input as these are likely to change. I also decided the gravitational acceleration $g$ would likely remain constant and as such defined $g$ as a local variable inside the ``height``function namespace.

I could have decided that the gravitational acceleration $g$ should be a global variable, for example:

```python
def height(s0, v0, t):
    st = s0 + v0 * t + 0.5 * g * t**2
    return st

g = -9.81
s = height(100, 0, 2)
print(s)
```

But wait... how is this code not crashing? The statements inside the function ``height`` rely on a variable ``g`` which is not only created outside the function but also created after the function, how is this code not crashing? Try and answer this question yourself before continuing to read further.

```python
%%nbtutor -rf

def height(s0, v0, t):
    st = s0 + v0 * t + 0.5 * g * t**2
    return st

g = -9.81
s = height(100, 0, 2)
print(s)
```

When a function is called, only then will the statements inside the function be executed and only when these statements are executed will *Python* start looking for what variables are. When line 4 gets executed *Python* will first look in the local function (``height``) namespace for the variables ``s0``, ``v0``, ``t``, and ``g``. The variables ``s0``, ``v0``, and ``t`` exist in the local namespace as parameters passed in as arguments, but ``g`` does not exist in the local namespace at all. If *Python* can't find a variable in the local namespace it will then look at the next namespace above, in this case the Notebook namespace.

Stepping through this example line-by-line should make it clearer. The function object and name gets created in the Notebook namespace (line 3), but the statements inside the function only get executed when the function is called (line 9) and this function call is after the definition of ``g`` (line 8), so this means that when the statements inside the function get executed *Python* knows what ``g`` is because it was defined before the function call in the Notebook namespace.

This is bad programming. All variables used in a function should either be passed as arguments or should be defined inside the function.

You may be thinking to yourself, but why? In this example it makes sense to have gravity as a "global" constant, it is not going to change. As a hypothetical lets say you have this exact example above and now you continue to work on another example, lets say computing the numerical gradient of a function:

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

dx = 0.0001
y1 = func(2)
y2 = func(2 + dx)
g = (y2 - y1) / dx
print(g)
```

Oh no... I've used ``g`` as a variable for the gradient, which now means that the ``height`` function will now not give me the correct result because ``g`` is no longer ``-9.81``. Thus all variables used in a function should either be passed as arguments or should be defined inside the function to avoid possible bugs like this.

```python
s = height(100, 0, 2)
print(g)
print(s)
```

## Why functions?

It may not be clear why it is worth the trouble to divide a program into functions. There are several reasons:
- Creating a new function gives you an opportunity to name a group of statements, which makes your program easier to read and debug.
- Functions can make a program smaller by eliminating repetitive code. Later, if you make a change, you only have to make it in one place.
- Dividing a long program into functions allows you to debug the parts one at a time and then assemble them into a working whole.
- Well-designed functions are often useful for many programs. Once you write and debug one, you can reuse it.

## Debugging

One of the most important skills you will acquire is debugging. Although it can be frustrating, debugging is one of the most intellectually rich, challenging, and interesting parts of programming.

In some ways debugging is like detective work. You are confronted with clues and you have to infer the processes and events that led to the results you see.

Debugging is also like an experimental science. Once you have an idea about what is going wrong, you modify your program and try again. If your hypothesis was correct, you can predict the result of the modification, and you take a step closer to a working program. If your hypothesis was wrong, you have to come up with a new one. As Sherlock Holmes pointed out, “When you have eliminated the impossible, whatever remains, however improbable, must be the truth.” (A. Conan Doyle, The Sign of Four)

For some people, programming and debugging are the same thing. That is, programming is the process of gradually debugging a program until it does what you want. The idea is that you should start with a working program and make small modifications, debugging them as you go.

Breaking a large program into smaller functions creates natural checkpoints for debugging. If a function is not working, there are three possibilities to consider:
- There is something wrong with the arguments the function is getting; a precondition is violated.
- There is something wrong with the function; a postcondition is violated.
- There is something wrong with the return object or the way it is being used.

To rule out the first possibility, you can add a print statement at the beginning of the function and display the values of the parameters (and maybe their types). Or you can write code that checks the preconditions explicitly.

If the parameters look good, add a print statement before each return statement and display the return object. If possible, check the result by hand. Consider calling the function with values that make it easy to check the result.

If the function seems to be working, look at the function call to make sure the return object is being used correctly (or used at all!).

Adding print statements at the beginning and end of a function can help make the flow of execution more visible. It takes some time to develop effective scaffolding, but a little bit of scaffolding can save a lot of debugging.

## Glossary

**argument**: An object provided to a function when the function is called. This object is assigned to the corresponding parameter in the function.

**composition**: Using an expression as part of a larger expression, or a statement as part of a larger statement.

**flow of execution**: The order statements run in.

**function**: A named sequence of statements that performs some useful operation. Functions may or may not take arguments and may or may not produce a result.

**function body**: The sequence of statements inside a function definition.

**function call**: A statement that runs a function. It consists of the function name followed by an argument list in parentheses.

**function definition**: A statement that creates a new function, specifying its name, parameters, and the statements it contains.

**function header**: The first line of a function definition.

**function object**: An object created by a function definition. The name of the function is a variable that refers to a function object.

**incremental development**: A program development plan intended to avoid debugging by adding and testing only a small amount of code at a time.

**local variable**: A variable defined inside a function. A local variable can only be used inside its function.

**namespace**: A mapping from names to objects.

**None**: A special object returned by functions when no ``return`` is specified.

**parameter**: A name used inside a function to refer to the object passed as an argument.

**return object**: The result of a function. If a function call is used as an expression, the return object is the object of the expression.

**scaffolding**: Code that is used during program development but is not part of the final version.

**temporary variable**: A variable used to store an intermediate object in a complex calculation.

## Exercises

1) Write a function (named ``area``) that takes a single argument, namely the radius $r$ of a sphere. This function must compute and return the surface area of a sphere given by:

$$
    S = 4 \pi r^2
$$

2) Write a function (named ``volume``) that takes a single argument, namely the radius $r$ of a sphere. This function must compute and return the volume of a sphere given by:

$$
    V = \frac{4}{3} \pi r^3
$$

3) Consider a rectangular section with a width $b$ (along the $x$-axis) and a height $h$ (along the $y$-axis). Write three functions that compute the moment of inertia around the centroidal $x$-axis, $y$-axis, and polar resectively:

$$
\begin{aligned}
    I_x &= \frac{1}{12}bh^3 \\
    I_y &= \frac{1}{12}hb^3 \\
    I_z &= \frac{bh}{12}\left(b^2 + h^2\right) \\
\end{aligned}
$$

Each function takes two arguments, namely the width $b$ and height $h$ of the rectangular section.

4) Redo number 3, however this time the function that computes $I_y$ must make use of the function that computes $I_x$ and must only contain a single statement in the function body.

5a) Write a function that computes the terminal velocity of a falling body in a non-dense medium given by:

$$
    v_t = \sqrt{\frac{2mg}{\rho A C_d}}
$$

where:

- $g=9.81 \; m/s^2$ is the gravitational acceleration
- $m$ is the body mass - $kg$
- $A$ is the body cross sectional area - $m^2$
- $\rho$ is the medium density - $kg / m^3$
- $C_d$ is the coefficient of drag

5b) Write a function that computes the velocity (of the falling object) at a given time $t$:

$$
    v(t) = \sqrt{\frac{2mg}{\rho A C_d}} \tanh \left(t\sqrt{\frac{g\rho C_d A}{2m}}\right)
$$

*Hint: Make use of the function you created in 5a*

6) A function object can be assigned to a variable or passed as an argument. For example, ``do_twice`` is a function that takes another function object as an argument and calls it twice:

```python
def do_twice(func):
    func()
    func()
```

Here’s an example that uses ``do_twice`` to call a function named ``print_spam`` twice.

```python
def print_spam():
    print('spam')

do_twice(print_spam)
```

6a) Modify ``do_twice`` so that it takes two arguments, namely a function object and a value, and calls the function twice, passing the value as an argument.

Recall the ``print_twice`` function from earlier in this chapter:

```python
def print_twice(thing):
    print(thing)
    print(thing)
```

6b) Use the modified version of ``do_twice`` to call ``print_twice`` twice, passing `spam` as an argument.

6c) Define a new function (called ``do_four``) that takes a function object and a value and calls the function four times, passing the value as an argument. There should be only two statements in the body of this function, not four.

```python

```