```python
from mayavi import mlab
mlab.init_notebook()
mlab.test_plot3d()
```

    Notebook initialized with ipy backend.

    Image(value=b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x01\x90\x00\x00\x01^\x08\x02\x00\x00\x00$?\xde_\x00\…

# Applications of vibration models

The following text derives some of the most well-known physical
problems that lead to second-order ODE models of the type addressed in
this ${DOCUMENT}.  We consider a simple spring-mass system; thereafter
extended with nonlinear spring, damping, and external excitation; a
spring-mass system with sliding friction; a simple and a physical
(classical) pendulum; and an elastic pendulum.

## Oscillating mass attached to a spring

Simple oscillating mass.

The most fundamental mechanical vibration system is depicted in [Figure](#vib:app:mass_spring:fig). A body with mass $m$ is attached to a
spring and can move horizontally without friction (in the wheels). The
position of the body is given by the vector $\rpos(t) = u(t)\ii$, where
$\ii$ is a unit vector in $x$ direction.
There is
only one force acting on the body: a spring force $\F_s =-ku\ii$, where
$k$ is a constant. The point $x=0$, where $u=0$, must therefore
correspond to the body's position
where the spring is neither extended nor compressed, so the force
vanishes.

The basic physical principle that governs the motion of the body is
Newton's second law of motion: $\F=m\acc$, where
$\F$ is the sum of forces on the body, $m$ is its mass, and $\acc=\ddot\rpos$
is the acceleration. We use the dot for differentiation with respect
to time, which is
usual in mechanics. Newton's second law simplifies here
to $-\F_s=m\ddot u\ii$, which translates to

$$
-ku = m\ddot u\thinspace .
$$

Two initial conditions are needed: $u(0)=I$, $\dot u(0)=V$.
The ODE problem is normally written as

$$
\begin{equation}
m\ddot u + ku = 0,\quad u(0)=I,\ \dot u(0)=V\thinspace .
\label{vib:app:mass_spring:eqx} \tag{1}
\end{equation}
$$

mathcal{I}_t is
not uncommon to divide by $m$
and introduce the frequency $\omega = \sqrt{k/m}$:

$$
\begin{equation}
\ddot u + \omega^2 u = 0,\quad u(0)=I,\  \dot u(0)=V\thinspace .
\label{vib:app:mass_spring:equ} \tag{2}
\end{equation}
$$

This is the model problem in the first part of this chapter, with the
small difference that we write the time derivative of $u$ with a dot
above, while we used $u^{\prime}$ and $u^{\prime\prime}$ in previous
parts of the ${DOCUMENT}.

Since only one scalar mathematical quantity, $u(t)$, describes the
complete motion, we say that the mechanical system has one degree of freedom
(DOF).

### Scaling

For numerical simulations it is very convenient to scale
([2](#vib:app:mass_spring:equ)) and thereby get rid of the problem of
finding relevant values for all the parameters $m$, $k$, $I$, and $V$.
Since the amplitude of the oscillations are dictated by $I$ and $V$
(or more precisely, $V/\omega$), we scale $u$ by $I$ (or $V/\omega$ if
$I=0$):

$$
\bar u = \frac{u}{I},\quad \bar t = \frac{t}{t_c}\thinspace .
$$

The time scale $t_c$ is normally chosen as the inverse period $2\pi/\omega$ or
angular frequency $1/\omega$, most often as $t_c=1/\omega$.
Inserting the dimensionless quantities $\bar u$ and $\bar t$ in
([2](#vib:app:mass_spring:equ)) results in the scaled problem

$$
\frac{d^2\bar u}{d\bar t^2} + \bar u = 0,\quad \bar u(0)=1,\ \frac{\bar u}{\bar t}(0)=\beta = \frac{V}{I\omega},
$$

where $\beta$ is a dimensionless number. Any motion that starts from rest
($V=0$) is free of parameters in the scaled model!

### The physics

The typical physics of the system in [Figure](#vib:app:mass_spring:fig) can be described as follows.  Initially,
we displace the body to some position $I$, say at rest ($V=0$). After
releasing the body, the spring, which is extended, will act with a
force $-kI\ii$ and pull the body to the left. This force causes an
acceleration and therefore increases velocity. The body passes the
point $x=0$, where $u=0$, and the spring will then be compressed and
act with a force $kx\ii$ against the motion and cause retardation. At
some point, the motion stops and the velocity is zero, before the
spring force $kx\ii$ has worked long enough to push the body in
positive direction. The result is that the body accelerates back and
forth. As long as there is no friction forces to damp the motion, the
oscillations will continue forever.

## General mechanical vibrating system

General oscillating system.

The mechanical system in [Figure](#vib:app:mass_spring:fig) can easily be
extended to the more general system in [Figure](#vib:app:mass_gen:fig),
where the body is attached to a spring and a dashpot, and also subject
to an environmental force $F(t)\ii$. The system has still only one
degree of freedom since the body can only move back and forth parallel to
the $x$ axis. The spring force was linear, $\F_s=-ku\ii$,
in the section [Oscillating mass attached to a spring](#vib:app:mass_spring), but in more general cases it can
depend nonlinearly on the position. We therefore set $\F_s=s(u)\ii$.
The dashpot, which acts
as a damper, results in a force $\F_d$ that depends on the body's
velocity $\dot u$ and that always acts against the motion.
The mathematical model of the force is written $\F_d =f(\dot u)\ii$.
A positive $\dot u$ must result in a force acting in the positive $x$
direction.
Finally, we have the external environmental force $\F_e = F(t)\ii$.

Newton's second law of motion now involves three forces:

$$
F(t)\ii - f(\dot u)\ii - s(u)\ii = m\ddot u \ii\thinspace .
$$

The common mathematical form of the ODE problem is

$$
\begin{equation}
m\ddot u + f(\dot u) + s(u) = F(t),\quad u(0)=I,\ \dot u(0)=V\thinspace .
\label{vib:app:mass_gen:equ} \tag{3}
\end{equation}
$$

This is the generalized problem treated in the last part of the
present chapter, but with prime denoting the derivative instead of the dot.

The most common models for the spring and dashpot are linear: $f(\dot u)
=b\dot u$ with a constant $b\geq 0$, and $s(u)=ku$ for a constant $k$.

### Scaling

A specific scaling requires specific choices of $f$, $s$, and $F$.
Suppose we have

$$
f(\dot u) = b|\dot u|\dot u,\quad s(u)=ku,\quad F(t)=A\sin(\phi t)\thinspace .
$$

We introduce dimensionless variables as usual, $\bar u = u/u_c$ and
$\bar t = t/t_c$. The scale $u_c$ depends both on the initial conditions
and $F$, but as time grows, the effect of the initial conditions die out
and $F$ will drive the motion. Inserting $\bar u$ and $\bar t$ in the
ODE gives

$$
m\frac{u_c}{t_c^2}\frac{d^2\bar u}{d\bar t^2}
+ b\frac{u_c^2}{t_c^2}\left\vert\frac{d\bar u}{d\bar t}\right\vert
\frac{d\bar u}{d\bar t} + ku_c\bar u = A\sin(\phi t_c\bar t)\thinspace .
$$

We divide by $u_c/t_c^2$ and demand the coefficients of the
$\bar u$ and the forcing term from $F(t)$ to have unit coefficients.
This leads to the scales

$$
t_c = \sqrt{\frac{m}{k}},\quad u_c = \frac{A}{k}\thinspace .
$$

The scaled ODE becomes

$$
\begin{equation}
\frac{d^2\bar u}{d\bar t^2}
+ 2\beta\left\vert\frac{d\bar u}{d\bar t}\right\vert
\frac{d\bar u}{d\bar t} + \bar u = \sin(\gamma\bar t),
\label{vib:app:mass_gen:scaled} \tag{4}
\end{equation}
$$

where there are two dimensionless numbers:

$$
\beta = \frac{Ab}{2mk},\quad\gamma =\phi\sqrt{\frac{m}{k}}\thinspace .
$$

The $\beta$ number measures the size of the damping term (relative to unity)
and is assumed to be small, basically because $b$ is small. The $\phi$
number is the ratio of the time scale of free vibrations and the time scale
of the forcing.
The scaled initial conditions have two other dimensionless numbers
as values:

$$
\bar u(0) = \frac{Ik}{A},\quad \frac{d\bar u}{d\bar t}=\frac{t_c}{u_c}V = \frac{V}{A}\sqrt{mk}\thinspace .
$$

## A sliding mass attached to a spring

Consider a variant of the oscillating body in the section [Oscillating mass attached to a spring](#vib:app:mass_spring)
and [Figure](#vib:app:mass_spring:fig): the body rests on a flat
surface, and there is sliding friction between the body and the surface.
[Figure](#vib:app:mass_sliding:fig) depicts the problem.

Sketch of a body sliding on a surface.

The body is attached to a spring with spring force $-s(u)\ii$.
The friction force is proportional to the normal force on the surface,
$-mg\jj$, and given by $-f(\dot u)\ii$, where

$$
f(\dot u) = \left\lbrace\begin{array}{ll}
-\mu mg,& \dot u < 0,\\
\mu mg, & \dot u > 0,\\
0,      & \dot u=0
\end{array}\right.
$$

Here, $\mu$ is a friction coefficient. With the signum function

$$
\mbox{sign(x)} = \left\lbrace\begin{array}{ll}
-1,& x < 0,\\
1, & x > 0,\\
0, & x=0
\end{array}\right.
$$

we can simply write $f(\dot u) = \mu mg\,\hbox{sign}(\dot u)$
(the sign function is implemented by `numpy.sign`).

The equation of motion becomes

$$
\begin{equation}
m\ddot u + \mu mg\hbox{sign}(\dot u) + s(u) = 0,\quad u(0)=I,\ \dot u(0)=V\thinspace .
\label{vib:app:mass_sliding:equ} \tag{5}
\end{equation}
$$

## A jumping washing machine

A washing machine is placed on four springs with efficient dampers.
If the machine contains just a few clothes, the circular motion of
the machine induces a sinusoidal external force from the floor and the machine will
jump up and down if the frequency of the external force is close to
the natural frequency of the machine and its spring-damper system.

[hpl 1: Not finished. This is a good example on resonance.]

## Motion of a pendulum

### Simple pendulum

A classical problem in mechanics is the motion of a pendulum. We first
consider a [simplified pendulum](https://en.wikipedia.org/wiki/Pendulum) (sometimes also called a
mathematical pendulum): a small body of mass $m$ is
attached to a massless wire and can oscillate back and forth in the
gravity field. [Figure](#vib:app:pendulum:fig_problem) shows a sketch
of the problem.

Sketch of a simple pendulum.

The motion is governed by Newton's 2nd law, so we need to find
expressions for the forces and the acceleration. Three forces on the
body are considered: an unknown force $S$ from the wire, the gravity
force $mg$, and an air resistance force, $\frac{1}{2}C_D\varrho A|v|v$,
hereafter called the drag force, directed against the velocity
of the body. Here, $C_D$ is a drag coefficient, $\varrho$ is the
density of air, $A$ is the cross section area of the body, and $v$ is
the magnitude of the velocity.

We introduce a coordinate system with polar coordinates and unit
vectors $\ir$ and $\ith$ as shown in [Figure](#vib:app:pendulum:fig_forces).  The position of the center of mass
of the body is

$$
\rpos(t) = x_0\ii + y_0\jj + L\ir,
$$

where $\ii$ and $\jj$ are unit vectors in the corresponding Cartesian
coordinate system in the $x$ and $y$ directions, respectively. We have
that $\ir = \cos\theta\ii +\sin\theta\jj$.

Forces acting on a simple pendulum.

The forces are now expressed as follows.

 * Wire force: $-S\ir$

 * Gravity force: $-mg\jj = mg(-\sin\theta\,\ith + \cos\theta\,\ir)$

 * Drag force: $-\frac{1}{2}C_D\varrho A |v|v\,\ith$

Since a positive velocity means movement in the direction of $\ith$,
the drag force must be directed along $-\ith$ so it works against the
motion. We assume motion in air so that the added mass effect can
be neglected (for a spherical body, the added mass is $\frac{1}{2}\varrho V$,
where $V$ is the volume of the body). Also the buoyancy effect
can be neglected for motion in the air when the density difference
between the fluid and the body is so significant.

The velocity of the body is found from $\rpos$:

$$
\v(t) = \dot\rpos (t) = \frac{d}{d\theta}(x_0\ii + y_0\jj + L\ir)\frac{d\theta}{dt} = L\dot\theta\ith,
$$

since $\frac{d}{d\theta}\ir = \ith$. mathcal{I}_t follows that $v=|\v|=L\dot\theta$.
The acceleration is

$$
\acc(t) = \dot\v(r) = \frac{d}{dt}(L\dot\theta\ith)
= L\ddot\theta\ith + L\dot\theta\frac{d\ith}{d\theta}\dot\theta =
= L\ddot\theta\ith - L\dot\theta^2\ir,
$$

since $\frac{d}{d\theta}\ith = -\ir$.

Newton's 2nd law of motion becomes

$$
-S\ir + mg(-\sin\theta\,\ith + \cos\theta\,\ir) -
\frac{1}{2}C_D\varrho AL^2|\dot\theta|\dot\theta\,\ith
= mL\ddot\theta\dot\theta\,\ith - L\dot\theta^2\ir,
$$

leading to two component equations

$$
\begin{equation}
-S + mg\cos\theta = -L\dot\theta^2,
\label{vib:app:pendulum:ir} \tag{6}
\end{equation}
$$

$$
\begin{equation}
-mg\sin\theta - \frac{1}{2}C_D\varrho AL^2|\dot\theta|\dot\theta
= mL\ddot\theta\thinspace .
\label{vib:app:pendulum:ith} \tag{7}
\end{equation}
$$

From ([6](#vib:app:pendulum:ir)) we get an expression for
$S=mg\cos\theta + L\dot\theta^2$, and from ([7](#vib:app:pendulum:ith))
we get a differential equation for the angle $\theta(t)$. This latter
equation is ordered as

$$
\begin{equation}
m\ddot\theta + \frac{1}{2}C_D\varrho AL|\dot\theta|\dot\theta
+ \frac{mg}{L}\sin\theta = 0\thinspace .
\label{vib:app:pendulum:thetaeq} \tag{8}
\end{equation}
$$

Two initial conditions are needed: $\theta=\Theta$ and $\dot\theta = \Omega$.
Normally, the pendulum motion is started from rest, which means $\Omega =0$.

Equation ([8](#vib:app:pendulum:thetaeq)) fits the general model
used in ([vib:ode2](#vib:ode2)) in the section [vib:model2](#vib:model2) if we define
$u=\theta$, $f(u^{\prime}) = \frac{1}{2}C_D\varrho AL|\dot u|\dot u$,
$s(u) = L^{-1}mg\sin u$, and $F=0$.
If the body is a sphere with radius $R$, we can take $C_D=0.4$ and $A=\pi R^2$.
[Exercise 4: Simulate a simple pendulum](#vib:exer:pendulum_simple) asks you to scale the equations
and carry out specific simulations with this model.

### Physical pendulum

The motion of a compound or physical pendulum where the wire is a rod with
mass, can be modeled very similarly. The governing equation is
$I\acc = \boldsymbol{T}$ where $I$ is the moment of inertia of the entire body about
the point $(x_0,y_0)$, and $\boldsymbol{T}$ is the sum of moments of the forces
with respect to $(x_0,y_0)$. The vector equation reads

$$
\rpos\times(-S\ir + mg(-\sin\theta\ith + \cos\theta\ir) -
\frac{1}{2}C_D\varrho AL^2|\dot\theta|\dot\theta\ith)
= I(L\ddot\theta\dot\theta\ith - L\dot\theta^2\ir)\thinspace .
$$

The component equation in $\ith$ direction gives the equation of motion
for $\theta(t)$:

$$
\begin{equation}
I\ddot\theta + \frac{1}{2}C_D\varrho AL^3|\dot\theta|\dot\theta
+ mgL\sin\theta = 0\thinspace .
\label{vib:app:pendulum:thetaeq_physical} \tag{9}
\end{equation}
$$

## Dynamic free body diagram during pendulum motion

Usually one plots the mathematical quantities as functions of time to
visualize the solution of ODE models.  [Exercise 4: Simulate a simple pendulum](#vib:exer:pendulum_simple) asks you to do this for the motion of a
pendulum in the previous section.  However, sometimes it is more
instructive to look at other types of visualizations. For example, we
have the pendulum and the free body diagram in Figures
[vib:app:pendulum:fig_problem](#vib:app:pendulum:fig_problem) and
[vib:app:pendulum:fig_forces](#vib:app:pendulum:fig_forces). We may think of these figures as
animations in time instead. Especially the free body diagram will show both the
motion of the pendulum *and* the size of the forces during the motion.
The present section exemplifies how to make such a dynamic body
diagram.
% if FORMAT == 'pdflatex':
Two typical snapshots of free body diagrams are displayed below
(the drag force is magnified 5 times to become more visual!).

% else:

```python
from IPython.display import HTML
_s = """
<div>

</div>
<p><em>The drag force is magnified 5 times! % endif</em></p>

<!-- Issue warning if in a Safari browser -->

"""
HTML(_s)
```

Dynamic physical sketches, coupled to the numerical solution of
differential equations, requires a program to produce a sketch for
the situation at each time level.
[Pysketcher](https://github.com/hplgit/pysketcher) is such a tool.
In fact (and not surprising!) Figures [vib:app:pendulum:fig_problem](#vib:app:pendulum:fig_problem) and
[vib:app:pendulum:fig_forces](#vib:app:pendulum:fig_forces) were drawn using Pysketcher.
The details of the drawings are explained in the
[Pysketcher tutorial](http://hplgit.github.io/pysketcher/doc/web/index.html).
Here, we outline how this type of sketch can be used to create an animated
free body diagram during the motion of a pendulum.

Pysketcher is actually a layer of useful abstractions on top of
standard plotting packages. This means that we in fact apply Matplotlib
to make the animated free body diagram, but instead of dealing with a wealth
of detailed Matplotlib commands, we can express the drawing in terms of
more high-level objects, e.g., objects for the wire, angle $\theta$,
body with mass $m$, arrows for forces, etc. When the position of these
objects are given through variables, we can just couple those variables
to the dynamic solution of our ODE and thereby make a unique drawing
for each $\theta$ value in a simulation.

### Writing the solver

Let us start with the most familiar part of the current problem:
writing the solver function. We use Odespy for this purpose.
We also work with dimensionless equations. Since $\theta$ can be
viewed as dimensionless, we only need to introduce a dimensionless time,
here taken as $\bar t = t/\sqrt{L/g}$.
The resulting dimensionless mathematical model for $\theta$,
the dimensionless angular velocity $\omega$, the
dimensionless wire force $\bar S$, and the dimensionless
drag force $\bar D$ is then

$$
\begin{equation}
\frac{d\omega}{d\bar t} = - \alpha|\omega|\omega - \sin\theta,
\label{vib:app:pendulum_bodydia:eqth} \tag{10}
\end{equation}
$$

$$
\begin{equation}
\frac{d\theta}{d\bar t} = \omega,
\label{vib:app:pendulum_bodydia:eqomega} \tag{11}
\end{equation}
$$

$$
\begin{equation}
\bar S = \omega^2 + \cos\theta,
\label{vib:app:pendulum_bodydia:eqS} \tag{12}
\end{equation}
$$

$$
\begin{equation}
\bar D = -\alpha |\omega|\omega,
\label{vib:app:pendulum_bodydia:eqD} \tag{13}
\end{equation}
$$

with

$$
\alpha = \frac{C_D\varrho\pi R^2L}{2m}\thinspace .
$$

as a dimensionless parameter expressing the ratio of the drag force and
the gravity force. The dimensionless $\omega$ is made non-dimensional
by the time, so $\omega\sqrt{L/g}$ is the corresponding angular
frequency with dimensions.

A suitable function for computing
([10](#vib:app:pendulum_bodydia:eqth))-([13](#vib:app:pendulum_bodydia:eqD))
is listed below.

```python
def simulate(alpha, Theta, dt, T):
    import odespy

    def f(u, t, alpha):
        omega, theta = u
        return [-alpha*omega*abs(omega) - sin(theta),
                omega]

    import numpy as np
    Nt = int(round(T/float(dt)))
    t = np.linspace(0, Nt*dt, Nt+1)
    solver = odespy.RK4(f, f_args=[alpha])
    solver.set_initial_condition([0, Theta])
    u, t = solver.solve(
        t, terminate=lambda u, t, n: abs(u[n,1]) < 1E-3)
    omega = u[:,0]
    theta = u[:,1]
    S = omega**2 + np.cos(theta)
    drag = -alpha*np.abs(omega)*omega
    return t, theta, omega, S, drag
```

### Drawing the free body diagram

The `sketch` function below applies Pysketcher objects to build
a diagram like that in [Figure](#vib:app:pendulum:fig_forces),
except that we have removed the rotation point $(x_0,y_0)$ and
the unit vectors in polar coordinates as these objects are not
important for an animated free body diagram.

```python
import sys
try:
    from pysketcher import *
except ImportError:
    print 'Pysketcher must be installed from'
    print 'https://github.com/hplgit/pysketcher'
    sys.exit(1)

# Overall dimensions of sketch
H = 15.
W = 17.

drawing_tool.set_coordinate_system(
    xmin=0, xmax=W, ymin=0, ymax=H,
    axis=False)

def sketch(theta, S, mg, drag, t, time_level):
    """
    Draw pendulum sketch with body forces at a time level
    corresponding to time t. The drag force is in
    drag[time_level], the force in the wire is S[time_level],
    the angle is theta[time_level].
    """
    import math
    a = math.degrees(theta[time_level])  # angle in degrees
    L = 0.4*H         # Length of pendulum
    P = (W/2, 0.8*H)  # Fixed rotation point

    mass_pt = path.geometric_features()['end']
    rod = Line(P, mass_pt)

    mass = Circle(center=mass_pt, radius=L/20.)
    mass.set_filled_curves(color='blue')
    rod_vec = rod.geometric_features()['end'] - \
              rod.geometric_features()['start']
    unit_rod_vec = unit_vec(rod_vec)
    mass_symbol = Text('$m$', mass_pt + L/10*unit_rod_vec)

    rod_start = rod.geometric_features()['start']  # Point P
    vertical = Line(rod_start, rod_start + point(0,-L/3))

    def set_dashed_thin_blackline(*objects):
        """Set linestyle of objects to dashed, black, width=1."""
        for obj in objects:
            obj.set_linestyle('dashed')
            obj.set_linecolor('black')
            obj.set_linewidth(1)

    set_dashed_thin_blackline(vertical)
    set_dashed_thin_blackline(rod)
    angle = Arc_wText(r'$\theta$', rod_start, L/6, -90, a,
                      text_spacing=1/30.)

    magnitude = 1.2*L/2   # length of a unit force in figure
    force = mg[time_level]  # constant (scaled eq: about 1)
    force *= magnitude
    mg_force  = Force(mass_pt, mass_pt + force*point(0,-1),
                      '', text_pos='end')
    force = S[time_level]
    force *= magnitude
    rod_force = Force(mass_pt, mass_pt - force*unit_vec(rod_vec),
                      '', text_pos='end',
                      text_spacing=(0.03, 0.01))
    force = drag[time_level]
    force *= magnitude
    air_force = Force(mass_pt, mass_pt -
                      force*unit_vec((rod_vec[1], -rod_vec[0])),
                      '', text_pos='end',
                      text_spacing=(0.04,0.005))

    body_diagram = Composition(
        {'mg': mg_force, 'S': rod_force, 'air': air_force,
         'rod': rod, 'body': mass
         'vertical': vertical, 'theta': angle,})

    body_diagram.draw(verbose=0)
    drawing_tool.savefig('tmp_%04d.png' % time_level, crop=False)
    # (No cropping: otherwise movies will be very strange!)
```

### Making the animated free body diagram

mathcal{I}_t now remains to couple the `simulate` and `sketch` functions.
We first run `simulate`:

```python
from math import pi, radians, degrees
import numpy as np
alpha = 0.4
period = 2*pi   # Use small theta approximation
T = 12*period   # Simulate for 12 periods
dt = period/40  # 40 time steps per period
a = 70          # Initial amplitude in degrees
Theta = radians(a)

t, theta, omega, S, drag = simulate(alpha, Theta, dt, T)
```

The next step is to run through the time levels in the simulation and
make a sketch at each level:

```python
for time_level, t_ in enumerate(t):
    sketch(theta, S, mg, drag, t_, time_level)
```

The individual sketches are (by the `sketch` function) saved in files
with names `tmp_%04d.png`. These can be combined to videos using
(e.g.) `ffmpeg`. A complete function `animate` for running the
simulation and creating video files is
listed below.

```python
def animate():
    # Clean up old plot files
    import os, glob
    for filename in glob.glob('tmp_*.png') + glob.glob('movie.*'):
        os.remove(filename)
    # Solve problem
    from math import pi, radians, degrees
    import numpy as np
    alpha = 0.4
    period = 2*pi   # Use small theta approximation
    T = 12*period   # Simulate for 12 periods
    dt = period/40  # 40 time steps per period
    a = 70          # Initial amplitude in degrees
    Theta = radians(a)

    t, theta, omega, S, drag = simulate(alpha, Theta, dt, T)

    # Visualize drag force 5 times as large
    drag *= 5
    mg = np.ones(S.size)  # Gravity force (needed in sketch)

    # Draw animation
    import time
    for time_level, t_ in enumerate(t):
        sketch(theta, S, mg, drag, t_, time_level)
        time.sleep(0.2)  # Pause between each frame on the screen

    # Make videos
    prog = 'ffmpeg'
    filename = 'tmp_%04d.png'
    fps = 6
    codecs = {'flv': 'flv', 'mp4': 'libx264',
              'webm': 'libvpx', 'ogg': 'libtheora'}
    for ext in codecs:
        lib = codecs[ext]
        cmd = '%(prog)s -i %(filename)s -r %(fps)s ' % vars()
        cmd += '-vcodec %(lib)s movie.%(ext)s' % vars()
        print(cmd)
        os.system(cmd)
```

## Motion of an elastic pendulum

Consider a pendulum as in [Figure](#vib:app:pendulum:fig_problem), but
this time the wire is elastic. The length of the wire when it is not
stretched is $L_0$, while $L(t)$ is the stretched
length at time $t$ during the motion.

Stretching the elastic wire a distance $\Delta L$ gives rise to a
spring force $k\Delta L$ in the opposite direction of the
stretching. Let $\boldsymbol{n}$ be a unit normal vector along the wire
from the point $\rpos_0=(x_0,y_0)$ and in the direction of $\ith$, see
[Figure](#vib:app:pendulum:fig_forces) for definition of $(x_0,y_0)$
and $\ith$. Obviously, we have $\boldsymbol{n}=\ith$, but in this modeling
of an elastic pendulum we do not need polar coordinates.  Instead, it
is more straightforward to develop the equation in Cartesian
coordinates.

A mathematical expression for $\boldsymbol{n}$ is

$$
\boldsymbol{n} = \frac{\rpos-\rpos_0}{L(t)},
$$

where $L(t)=||\rpos-\rpos_0||$ is the current length of the elastic wire.
The position vector $\rpos$ in Cartesian coordinates reads
$\rpos(t) = x(t)\ii + y(t)\jj$, where $\ii$ and $\jj$ are unit vectors
in the $x$ and $y$ directions, respectively.
mathcal{I}_t is convenient to introduce the Cartesian components $n_x$ and $n_y$
of the normal vector:

$$
\boldsymbol{n} = \frac{\rpos-\rpos_0}{L(t)} = \frac{x(t)-x_0}{L(t)}\ii + \frac{y(t)-y_0}{L(t)}\jj = n_x\ii + n_y\jj\thinspace .
$$

The stretch $\Delta L$ in the wire is

$$
\Delta t = L(t) - L_0\thinspace .
$$

The force in the wire is then $-S\boldsymbol{n}=-k\Delta L\boldsymbol{n}$.

The other forces are the gravity and the air resistance, just as in
[Figure](#vib:app:pendulum:fig_forces). For motion in air we can
neglect the added mass and buoyancy effects.  The main difference is
that we have a *model* for $S$ in terms of the motion (as soon as we
have expressed $\Delta L$ by $\rpos$). For simplicity, we drop the air
resistance term (but [Exercise 6: Simulate an elastic pendulum with air resistance](#vib:exer:pendulum_elastic_drag) asks
you to include it).

Newton's second law of motion applied to the body now results in

$$
\begin{equation}
m\ddot\rpos = -k(L-L_0)\boldsymbol{n} - mg\jj
\label{vib:app:pendulum_elastic:eq1} \tag{14}
\end{equation}
$$

The two components of
([14](#vib:app:pendulum_elastic:eq1)) are

$$
\begin{equation}
\ddot x = -\frac{k}{m}(L-L_0)n_x,
\label{_auto1} \tag{15}
\end{equation}
$$

$$
\begin{equation}
\label{vib:app:pendulum_elastic:eq2a} \tag{16}
\end{equation}
$$

$$
\begin{equation}
\ddot y = - \frac{k}{m}(L-L_0)n_y - g
\label{vib:app:pendulum_elastic:eq2b} \tag{17}\thinspace .
\end{equation}
$$

### Remarks about an elastic vs a non-elastic pendulum

Note that the derivation of the ODEs for an elastic pendulum is more
straightforward than for a classical, non-elastic pendulum,
since we avoid the details
with polar coordinates, but instead work with Newton's second law
directly in Cartesian coordinates. The reason why we can do this is that
the elastic pendulum undergoes a general two-dimensional motion where
all the forces are known or expressed as functions of $x(t)$ and $y(t)$,
such that we get two ordinary differential equations.
The motion of the non-elastic pendulum, on the other hand, is constrained:
the body has to move along a circular path, and the force $S$ in the
wire is unknown.

The non-elastic pendulum therefore leads to
a *differential-algebraic* equation, i.e., ODEs for $x(t)$ and $y(t)$
combined with an extra constraint $(x-x_0)^2 + (y-y_0)^2 = L^2$
ensuring that the motion takes place along a circular path.
The extra constraint (equation) is compensated by an extra unknown force
$-S\boldsymbol{n}$. Differential-algebraic equations are normally hard
to solve, especially with pen and paper.
Fortunately, for the non-elastic pendulum we can do a
trick: in polar coordinates the unknown force $S$ appears only in the
radial component of Newton's second law, while the unknown
degree of freedom for describing the motion, the angle $\theta(t)$,
is completely governed by the asimuthal component. This allows us to
decouple the unknowns $S$ and $\theta$. But this is a kind of trick and
not a widely applicable method. With an elastic pendulum we use straightforward
reasoning with Newton's 2nd law and arrive at a standard ODE problem that
(after scaling) is easy to solve on a computer.

### Initial conditions

What is the initial position of the body? We imagine that first the
pendulum hangs in equilibrium in its vertical position, and then it is
displaced an angle $\Theta$. The equilibrium position is governed
by the ODEs with the accelerations set to zero.
The $x$ component leads to $x(t)=x_0$, while the $y$ component gives

$$
0 = - \frac{k}{m}(L-L_0)n_y - g = \frac{k}{m}(L(0)-L_0) - g\quad\Rightarrow\quad
L(0) = L_0 + mg/k,
$$

since $n_y=-11$ in this position. The corresponding $y$ value is then
from $n_y=-1$:

$$
y(t) = y_0 - L(0) = y_0 - (L_0 + mg/k)\thinspace .
$$

Let us now choose $(x_0,y_0)$ such that the body is at the origin
in the equilibrium position:

$$
x_0 =0,\quad y_0 = L_0 + mg/k\thinspace .
$$

Displacing the body an angle $\Theta$ to the right leads to the
initial position

$$
x(0)=(L_0+mg/k)\sin\Theta,\quad y(0)=(L_0+mg/k)(1-\cos\Theta)\thinspace .
$$

The initial velocities can be set to zero: $x'(0)=y'(0)=0$.

### The complete ODE problem

We can summarize all the equations as follows:

$$
\begin{align*}
\ddot x &= -\frac{k}{m}(L-L_0)n_x,
\\
\ddot y &= -\frac{k}{m}(L-L_0)n_y - g,
\\
L &= \sqrt{(x-x_0)^2 + (y-y_0)^2},
\\
n_x &= \frac{x-x_0}{L},
\\
n_y &= \frac{y-y_0}{L},
\\
x(0) &= (L_0+mg/k)\sin\Theta,
\\
x'(0) &= 0,
\\
y(0) & =(L_0+mg/k)(1-\cos\Theta),
\\
y'(0) &= 0\thinspace .
\end{align*}
$$

We insert $n_x$ and $n_y$  in the ODEs:

$$
\begin{equation}
\ddot x = -\frac{k}{m}\left(1 -\frac{L_0}{L}\right)(x-x_0),
\label{vib:app:pendulum_elastic:x} \tag{18}
\end{equation}
$$

$$
\begin{equation}
\ddot y = -\frac{k}{m}\left(1 -\frac{L_0}{L}\right)(y-y_0) - g,
\label{vib:app:pendulum_elastic:y} \tag{19}
\end{equation}
$$

$$
\begin{equation}
L = \sqrt{(x-x_0)^2 + (y-y_0)^2},
\label{vib:app:pendulum_elastic:L} \tag{20}
\end{equation}
$$

$$
\begin{equation}
x(0) = (L_0+mg/k)\sin\Theta,
\label{vib:app:pendulum_elastic:x0} \tag{21}
\end{equation}
$$

$$
\begin{equation}
x'(0) = 0,
\label{vib:app:pendulum_elastic:vx0} \tag{22}
\end{equation}
$$

$$
\begin{equation}
y(0)  =(L_0+mg/k)(1-\cos\Theta),
\label{vib:app:pendulum_elastic:y0} \tag{23}
\end{equation}
$$

$$
\begin{equation}
y'(0) = 0\thinspace .
\label{vib:app:pendulum_elastic:vy0} \tag{24}
\end{equation}
$$

### Scaling

The elastic pendulum model can be used to study both an elastic pendulum
and a classic, non-elastic pendulum. The latter problem is obtained
by letting $k\rightarrow\infty$. Unfortunately,
a serious problem with the ODEs
([18](#vib:app:pendulum_elastic:x))-([19](#vib:app:pendulum_elastic:y)) is that for large $k$, we have a very large factor $k/m$ multiplied by a
very small number $1-L_0/L$, since for large $k$, $L\approx L_0$ (very
small deformations of the wire). The product is subject to
significant round-off errors for many relevant physical values of
the parameters. To circumvent the problem, we introduce a scaling. This
will also remove physical parameters from the problem such that we end
up with only one dimensionless parameter,
closely related to the elasticity of the wire. Simulations can then be
done by setting just this dimensionless parameter.

The characteristic length can be taken such that in equilibrium, the
scaled length is unity, i.e., the characteristic length is $L_0+mg/k$:

$$
\bar x = \frac{x}{L_0+mg/k},\quad \bar y = \frac{y}{L_0+mg/k}\thinspace .
$$

We must then also work with the scaled length $\bar L = L/(L_0+mg/k)$.

Introducing $\bar t=t/t_c$, where $t_c$ is a characteristic time we
have to decide upon later, one gets

$$
\begin{align*}
\frac{d^2\bar x}{d\bar t^2} &=
-t_c^2\frac{k}{m}\left(1 -\frac{L_0}{L_0+mg/k}\frac{1}{\bar L}\right)\bar x,\\
\frac{d^2\bar y}{d\bar t^2} &=
-t_c^2\frac{k}{m}\left(1 -\frac{L_0}{L_0+mg/k}\frac{1}{\bar L}\right)(\bar y-1)
-t_c^2\frac{g}{L_0 + mg/k},\\
\bar L &= \sqrt{\bar x^2 + (\bar y-1)^2},\\
\bar x(0) &= \sin\Theta,\\
\bar x'(0) &= 0,\\
\bar y(0) & = 1 - \cos\Theta,\\
\bar y'(0) &= 0\thinspace .
\end{align*}
$$

For a non-elastic pendulum with small angles, we know that the
frequency of the oscillations are $\omega = \sqrt{L/g}$. mathcal{I}_t is therefore
natural to choose a similar expression here, either the length in
the equilibrium position,

$$
t_c^2 = \frac{L_0+mg/k}{g}\thinspace .
$$

or simply the unstretched length,

$$
t_c^2 = \frac{L_0}{g}\thinspace .
$$

These quantities are not very different (since the elastic model
is valid only for quite small elongations), so we take the latter as it is
the simplest one.

The ODEs become

$$
\begin{align*}
\frac{d^2\bar x}{d\bar t^2} &=
-\frac{L_0k}{mg}\left(1 -\frac{L_0}{L_0+mg/k}\frac{1}{\bar L}\right)\bar x,\\
\frac{d^2\bar y}{d\bar t^2} &=
-\frac{L_0k}{mg}\left(1 -\frac{L_0}{L_0+mg/k}\frac{1}{\bar L}\right)(\bar y-1)
-\frac{L_0}{L_0 + mg/k},\\
\bar L &= \sqrt{\bar x^2 + (\bar y-1)^2}\thinspace .
\end{align*}
$$

We can now identify a dimensionless number

$$
\beta = \frac{L_0}{L_0 + mg/k} = \frac{1}{1+\frac{mg}{L_0k}},
$$

which is the ratio of the unstretched length and the
stretched length in equilibrium. The non-elastic pendulum will have
$\beta =1$ ($k\rightarrow\infty$).
With $\beta$ the ODEs read

$$
\begin{equation}
\frac{d^2\bar x}{d\bar t^2} =
-\frac{\beta}{1-\beta}\left(1- \frac{\beta}{\bar L}\right)\bar x,
\label{vib:app:pendulum_elastic:x:s} \tag{25}
\end{equation}
$$

$$
\begin{equation}
\frac{d^2\bar y}{d\bar t^2} =
-\frac{\beta}{1-\beta}\left(1- \frac{\beta}{\bar L}\right)(\bar y-1)
-\beta,
\label{vib:app:pendulum_elastic:y:s} \tag{26}
\end{equation}
$$

$$
\begin{equation}
\bar L = \sqrt{\bar x^2 + (\bar y-1)^2},
\label{vib:app:pendulum_elastic:L:s} \tag{27}
\end{equation}
$$

$$
\begin{equation}
\bar x(0) = (1+\epsilon)\sin\Theta,
\label{vib:app:pendulum_elastic:x0:s} \tag{28}
\end{equation}
$$

$$
\begin{equation}
\frac{d\bar x}{d\bar t}(0) = 0,
\label{vib:app:pendulum_elastic:vx0:s} \tag{29}
\end{equation}
$$

$$
\begin{equation}
\bar y(0) = 1 - (1+\epsilon)\cos\Theta,
\label{vib:app:pendulum_elastic:y0:s} \tag{30}
\end{equation}
$$

$$
\begin{equation}
\frac{d\bar y}{d\bar t}(0) = 0,
\label{vib:app:pendulum_elastic:vy0:s} \tag{31}
\end{equation}
$$

We have here added a parameter $\epsilon$, which is an additional
downward stretch of the wire at $t=0$. This parameter makes it possible
to do a desired test: vertical oscillations of the pendulum. Without
$\epsilon$, starting the motion from $(0,0)$ with zero velocity will
result in $x=y=0$ for all times (also a good test!), but with
an initial stretch so the body's position is $(0,\epsilon)$, we
will have oscillatory vertical motion with amplitude $\epsilon$ (see
[Exercise 5: Simulate an elastic pendulum](#vib:exer:pendulum_elastic)).

### Remark on the non-elastic limit

We immediately see that as $k\rightarrow\infty$ (i.e., we obtain a non-elastic
pendulum), $\beta\rightarrow 1$, $\bar L\rightarrow 1$, and we have
very small values $1-\beta\bar L^{-1}$ divided by very small values
$1-\beta$ in the ODEs. However, it turns out that we can set $\beta$
very close to one and obtain a path of the body that within the visual
accuracy of a plot does not show any elastic oscillations.
(Should the division of very small values become a problem, one can
study the limit by L'Hospital's rule:

$$
\lim_{\beta\rightarrow 1}\frac{1 - \beta \bar L^{-1}}{1-\beta}
= \frac{1}{\bar L},
$$

and use the limit $\bar L^{-1}$ in the ODEs for $\beta$ values very
close to 1.)

## Vehicle on a bumpy road

Sketch of one-wheel vehicle on a bumpy road.

We consider a very simplistic vehicle, on one wheel, rolling along a
bumpy road. The oscillatory nature of the road will induce an external
forcing on the spring system in the vehicle and cause vibrations.
[Figure](#vib:app:bumpy:fig:sketch) outlines the situation.

To derive the equation that governs the motion, we must first establish
the position vector of the black mass at the top of the spring.
Suppose the spring has length $L$ without any elongation or compression,
suppose the radius of the wheel is $R$, and suppose the height of the
black mass at the top is $H$. With the aid of the $\rpos_0$ vector
in [Figure](#vib:app:bumpy:fig:sketch), the position $\rpos$ of
the center point of the mass is

$$
\begin{equation}
\rpos = \rpos_0 + 2R\jj + L\jj + u\jj + \frac{1}{2} H\jj,\
\label{_auto2} \tag{32}
\end{equation}
$$

where $u$ is the elongation or compression in the spring according to
the (unknown and to be computed) vertical displacement $u$ relative to the
road. If the vehicle travels
with constant horizontal velocity $v$ and $h(x)$ is the shape of the
road, then the vector $\rpos_0$ is

$$
\rpos_0 = vt\ii + h(vt)\jj,
$$

if the motion starts from $x=0$ at time $t=0$.

The forces on the mass is the gravity, the spring force, and an optional
damping force that is proportional to the vertical velocity $\dot u$. Newton's
second law of motion then tells that

$$
m\ddot\rpos = -mg\jj - s(u) - b\dot u\jj\thinspace .
$$

This leads to

$$
m\ddot u = - s(u) - b\dot u - mg -mh''(vt)v^2
$$

To simplify a little bit, we omit the gravity force $mg$ in comparison with
the other terms. Introducing $u'$ for $\dot u$ then gives a standard
damped, vibration equation with external forcing:

$$
\begin{equation}
mu'' + bu' + s(u) =  -mh''(vt)v^2\thinspace .
\label{_auto3} \tag{33}
\end{equation}
$$

Since the road is normally known just as a set of array values, $h''$ must
be computed by finite differences. Let $\Delta x$ be the spacing between
measured values $h_i= h(i\Delta x)$ on the road. The discrete second-order
derivative $h''$ reads

$$
q_i = \frac{h_{i-1} - 2h_i + h_{i+1}}{\Delta x^2}, \quad i=1,\ldots,N_x-1\thinspace .
$$

We may for maximum simplicity set
the end points as $q_0=q_1$ and $q_{N_x}=q_{N_x-1}$.
The term $-mh''(vt)v^2$ corresponds to a force with discrete time values

$$
F^n = -mq_n v^2,\quad \Delta t = v^{-1}\Delta x\thinspace .
$$

This force can be directly used in a numerical model

$$
[mD_tD_t u + bD_{2t} u + s(u) = F]^n\thinspace .
$$

Software for computing $u$ and also making an animated sketch of
the motion like we did in the section [Dynamic free body diagram during pendulum motion](#vib:app:pendulum_bodydia)
is found in a separate project on the web:
<https://github.com/hplgit/bumpy>. You may start looking at the
"tutorial":
% if FORMAT == 'pdflatex':
"http://hplgit.github.io/bumpy/doc/pub/bumpy.pdf".
% else:
"http://hplgit.github.io/bumpy/doc/pub/bumpy.html".
% endif

## Bouncing ball

A bouncing ball is a ball in free vertically fall until it impacts the
ground, but during the impact, some kinetic energy is lost, and a new
motion upwards with reduced velocity starts. After the motion is retarded,
a new free fall starts, and the process is repeated. At some point the
velocity close to the ground is so small that the ball is considered
to be finally at rest.

The motion of the ball falling in air is governed by Newton's second
law $F=ma$, where $a$ is the acceleration of the body, $m$ is the mass,
and $F$ is the sum of all forces. Here, we neglect the air resistance
so that gravity $-mg$ is the only force. The height of the ball is
denoted by $h$ and $v$ is the velocity. The relations between $h$, $v$, and
$a$,

$$
h'(t)= v(t),\quad v'(t) = a(t),
$$

combined with Newton's second law gives the ODE model

$$
\begin{equation}
h^{\prime\prime}(t) = -g,
\label{vib:app:bouncing:ball:h2eq} \tag{34}
\end{equation}
$$

or expressed alternatively as a system of first-order equations:

$$
\begin{equation}
v'(t) = -g,
\label{vib:app:bouncing:ball:veq} \tag{35}
\end{equation}
$$

$$
\begin{equation}
h'(t) = v(t)\thinspace .
\label{vib:app:bouncing:ball:heq} \tag{36}
\end{equation}
$$

These equations govern the motion as long as the ball is away from
the ground by a small distance $\epsilon_h > 0$. When $h<\epsilon_h$,
we have two cases.

1. The ball impacts the ground, recognized by a sufficiently large negative
   velocity ($v<-\epsilon_v$). The velocity then changes sign and is
   reduced by a factor $C_R$, known as the [coefficient of restitution](http://en.wikipedia.org/wiki/Coefficient_of_restitution).
   For plotting purposes, one may set $h=0$.

2. The motion stops, recognized by a sufficiently small velocity
   ($|v|<\epsilon_v$) close to the ground.

## Two-body gravitational problem

Consider two astronomical objects $A$ and $B$ that attract each other
by gravitational forces. $A$ and $B$ could be two stars in a binary
system, a planet orbiting a star, or a moon orbiting a planet.
Each object is acted upon by the
gravitational force due to the other object. Consider motion in a plane
(for simplicity) and let $(x_A,y_A)$ and $(x_B,y_B)$ be the
positions of object $A$ and $B$, respectively.

### The governing equations

Newton's second law of motion applied to each object is all we need
to set up a mathematical model for this physical problem:

$$
\begin{equation}
m_A\ddot\x_A = \F,
\label{_auto4} \tag{37}
\end{equation}
$$

$$
\begin{equation}
m_B\ddot\x_B = -\F,
\label{_auto5} \tag{38}
\end{equation}
$$

where $F$ is the gravitational force

$$
\F = \frac{Gm_Am_B}{||\rpos||^3}\rpos,
$$

where

$$
\rpos(t) = \x_B(t) - \x_A(t),
$$

and $G$ is the gravitational constant:
$G=6.674\cdot 10^{-11}\hbox{ Nm}^2/\hbox{kg}^2$.

### Scaling

A problem with these equations is that the parameters are very large
($m_A$, $m_B$, $||\rpos||$) or very small ($G$). The rotation time
for binary stars can be very small and large as well. mathcal{I}_t is therefore
advantageous to scale the equations.
A natural length scale could be the initial distance between the objects:
$L=\rpos(0)$. We write the dimensionless quantities as

$$
\bar\x_A = \frac{\x_A}{L},\quad\bar\x_B = \frac{\x_B}{L},\quad
\bar t = \frac{t}{t_c}\thinspace .
$$

The gravity force is transformed to

$$
\F = \frac{Gm_Am_B}{L^2||\bar\rpos||^3}\bar\rpos,\quad \bar\rpos = \bar\x_B - \bar\x_A,
$$

so the first ODE for $\x_A$ becomes

$$
\frac{d^2 \bar\x_A}{d\bar t^2} =
\frac{Gm_Bt_c^2}{L^3}\frac{\bar\rpos}{||\bar\rpos||^3}\thinspace .
$$

Assuming that quantities with a bar and their derivatives are around unity
in size, it is natural to choose $t_c$ such that the fraction $Gm_Bt_c/L^2=1$:

$$
t_c = \sqrt{\frac{L^3}{Gm_B}}\thinspace .
$$

From the other equation for $\x_B$ we get another candidate for $t_c$ with
$m_A$ instead of $m_B$. Which mass we choose play a role if $m_A\ll m_B$ or
$m_B\ll m_A$. One solution is to use the sum of the masses:

$$
t_c = \sqrt{\frac{L^3}{G(m_A+m_B)}}\thinspace .
$$

Taking a look at [Kepler's laws](https://en.wikipedia.org/wiki/Kepler%27s_laws_of_planetary_motion) of planetary motion, the orbital period for a planet around the star is given by the $t_c$ above, except for a missing factor of $2\pi$,
but that means that $t_c^{-1}$ is just the angular frequency of the motion.
Our characteristic time $t_c$ is therefore highly relevant.
Introducing the dimensionless number

$$
\alpha = \frac{m_A}{m_B},
$$

we can write the dimensionless ODE as

$$
\begin{equation}
\frac{d^2 \bar\x_A}{d\bar t^2} =
\frac{1}{1+\alpha}\frac{\bar\rpos}{||\bar\rpos||^3},
\label{_auto6} \tag{39}
\end{equation}
$$

$$
\begin{equation}
\frac{d^2 \bar\x_B}{d\bar t^2} =
\frac{1}{1+\alpha^{-1}}\frac{\bar\rpos}{||\bar\rpos||^3}\thinspace .
\label{_auto7} \tag{40}
\end{equation}
$$

In the limit $m_A\ll m_B$, i.e., $\alpha\ll 1$,
object B stands still, say $\bar\x_B=0$, and object
A orbits according to

$$
\frac{d^2 \bar\x_A}{d\bar t^2} = -\frac{\bar\x_A}{||\bar \x_A||^3}\thinspace .
$$

### Solution in a special case: planet orbiting a star

To better see the motion, and that our scaling is reasonable,
we introduce polar coordinates $r$ and $\theta$:

$$
\bar\x_A = r\cos\theta\ii + r\sin\theta\jj,
$$

which means $\bar\x_A$ can be written as $\bar\x_A =r\ir$. Since

$$
\frac{d}{dt}\ir = \dot\theta\ith,\quad \frac{d}{dt}\ith = -\dot\theta\ir,
$$

we have

$$
\frac{d^2 \bar\x_A}{d\bar t^2} =
(\ddot r - r\dot\theta^2)\ir + (r\ddot\theta + 2\dot r\dot\theta)\ith\thinspace .
$$

The equation of motion for mass A is then

$$
\begin{align*}
\ddot r - r\dot\theta^2 &= -\frac{1}{r^2},\\
r\ddot\theta + 2\dot r\dot\theta &= 0\thinspace .
\end{align*}
$$

The special case of circular motion, $r=1$, fulfills the equations, since
the latter equation then gives $\dot\theta =\hbox{const}$ and
the former then gives $\dot\theta = 1$, i.e., the motion is
$r(t)=1$, $\theta(t)=t$, with unit angular frequency as expected and
period $2\pi$ as expected.

## Electric circuits

Although the term "mechanical vibrations" is used in the present
book, we must mention that the same type of equations arise
when modeling electric circuits.
The current $I(t)$ in a
circuit with an inductor with inductance $L$, a capacitor with
capacitance $C$, and overall resistance $R$, is governed by

$$
\begin{equation}
\ddot I + \frac{R}{L}\dot I + \frac{1}{LC}I = \dot V(t),
\label{_auto8} \tag{41}
\end{equation}
$$

where $V(t)$ is the voltage source powering the circuit.
This equation has the same form as the general model considered in
the section [vib:model2](#vib:model2) if we set $u=I$, $f(u^{\prime})=bu^{\prime}$
and define $b=R/L$, $s(u) = L^{-1}C^{-1}u$, and $F(t)=\dot V(t)$.

# Exercises

## Exercise 1: Simulate resonance

We consider the scaled ODE model
([4](#vib:app:mass_gen:scaled)) from the section [General mechanical vibrating system](#vib:app:mass_gen).
After scaling, the amplitude of $u$ will have a size about unity
as time grows and the effect of the initial conditions die out due
to damping. However, as $\gamma\rightarrow 1$, the amplitude of $u$
increases, especially if $\beta$ is small. This effect is called
*resonance*. The purpose of this exercise is to explore resonance.

**a)**
Figure out how the `solver` function in `vib.py` can be called
for the scaled ODE ([4](#vib:app:mass_gen:scaled)).

**Solution.**
Comparing the scaled ODE ([4](#vib:app:mass_gen:scaled))
with the ODE ([3](#vib:app:mass_gen:equ)) with dimensions, we
realize that the parameters in the latter must be set as

 * $m=1$

 * $f(\dot u) = 2\beta |\dot u|\dot u$

 * $s(u)=ku$

 * $F(t)=\sin(\gamma t)$

 * $I=Ik/A$

 * $V=\sqrt{mk}V/A$

The expected period is $2\pi$, so simulating for $N$ periods means
$T=2\pi N$. Having $m$ time steps per period means $\Delta t = 2\pi/m$.

Suppose we just choose $I=1$ and $V=0$. Simulating for 20 periods with
60 time steps per period, implies the following
`solver` call to run the scaled model:

```python
u, t = solver(I=1, V=0, m=1, b=2*beta, s=lambda u: u,
              F=lambda t: sin(gamma*t), dt=2*pi/60,
              T=2*pi*20, damping='quadratic')
```

**b)**
Run $\gamma =5, 1.5, 1.1, 1$ for $\beta=0.005, 0.05, 0.2$.
For each $\beta$ value, present an image with plots of $u(t)$ for
the four $\gamma$ values.

**Solution.**
An appropriate program is

```python
from vib import solver, visualize, plt
from math import pi, sin
import numpy as np

beta_values = [0.005, 0.05, 0.2]
beta_values = [0.00005]
gamma_values = [5, 1.5, 1.1, 1]
for i, beta in enumerate(beta_values):
    for gamma in gamma_values:
        u, t = solver(I=1, V=0, m=1, b=2*beta, s=lambda u: u,
                      F=lambda t: sin(gamma*t), dt=2*pi/60,
                      T=2*pi*20, damping='quadratic')
        visualize(u, t, title='gamma=%g' %
                  gamma, filename='tmp_%s' % gamma)
        print gamma, 'max u amplitude:', np.abs(u).max()
    for ext in 'png', 'pdf':
        cmd = 'doconce combine_images '
        cmd += ' '.join(['tmp_%s.' % gamma + ext
                         for gamma in gamma_values])
        cmd += ' resonance%d.' % (i+1) + ext
        os.system(cmd)
raw_input()
```

For $\beta = 0.2$ we see that the amplitude is not far from unity:

For $\beta =0.05$ we see that as $\gamma\rightarrow 1$, the amplitude grows:

Finally, a small damping ($\beta = 0.005$) amplifies the amplitude significantly (by a factor of 10) for $\gamma=1$:

For a very small $\beta=0.00005$, the amplitude grows linearly up to
about 60 for $\bar t\in [0,120]$.

Filename: `resonance`.

## Exercise 2: Simulate oscillations of a sliding box

Consider a sliding box on a flat surface as modeled in the section [A sliding mass attached to a spring](#vib:app:mass_sliding). As spring force we choose the nonlinear
formula

$$
s(u) = \frac{k}{\alpha}\tanh(\alpha u) = ku + \frac{1}{3}\alpha^2 ku^3 + \frac{2}{15}\alpha^4 k u^5 + \Oof{u^6}\thinspace .
$$

**a)**
Plot $g(u)=\alpha^{-1}\tanh(\alpha u)$ for various values of $\alpha$.
Assume $u\in [-1,1]$.

**Solution.**
Here is a function that does the plotting:

```python
%matplotlib inline

import scitools.std as plt
import numpy as np

def plot_spring():
    alpha_values = [1, 2, 3, 10]
    s = lambda u: 1.0/alpha*np.tanh(alpha*u)
    u = np.linspace(-1, 1, 1001)
    for alpha in alpha_values:
        print alpha, s(u)
        plt.plot(u, s(u))
        plt.hold('on')
    plt.legend([r'$\alpha=%g$' % alpha for alpha in alpha_values])
    plt.xlabel('u');  plt.ylabel('Spring response $s(u)$')
    plt.savefig('tmp_s.png'); plt.savefig('tmp_s.pdf')
```

**b)**
Scale the equations using $I$ as scale for $u$ and $\sqrt{m/k}$ as
time scale.

**Solution.**
Inserting the dimensionless dependent and independent variables,

$$
\bar u = \frac{u}{I},\quad \bar t = \frac{t}{\sqrt{m/k}},
$$

in the problem

$$
m\ddot u + \mu mg\hbox{sign}(\dot u) + s(u) = 0,\quad u(0)=I,\ \dot u(0)=V,
$$

gives

$$
\frac{d^2\bar u}{d\bar t^2} + \frac{\mu mg}{kI}\hbox{sign}\left(
\frac{d\bar u}{d\bar t}\right) + \frac{1}{\alpha I}\tanh(\alpha I\bar u)
= 0,\quad \bar u(0)=1,\ \frac{d\bar u}{d\bar t}(0)=\frac{V\sqrt{mk}}{kI}\thinspace .
$$

We can now identify three dimensionless parameters,

$$
\beta = \frac{\mu mg}{kI},\quad
\gamma = \alpha I,\quad \delta = \frac{V\sqrt{mk}}{kI}\thinspace .
$$

The scaled problem can then be written

$$
\frac{d^2\bar u}{d\bar t^2} + \beta\hbox{sign}\left(
\frac{d\bar u}{d\bar t}\right) + \gamma^{-1}\tanh(\gamma \bar u)
= 0,\quad \bar u(0)=1,\ \frac{d\bar u}{d\bar t}(0)=\delta\thinspace .
$$

The initial set of 7 parameters $(\mu, m, g, k, \alpha, I, V)$ are
reduced to 3 dimensionless combinations.

**c)**
Implement the scaled model in b). Run it for some values of
the dimensionless parameters.

**Solution.**
We use Odespy to solve the ODE, which requires rewriting the ODE as a
system of two first-order ODEs:

$$
\begin{align*}
v' &= - \beta\hbox{sign}(v) - \gamma^{-1}\tanh(\gamma \bar u),\\
u' &= v,
\end{align*}
$$

with initial conditions $v(0)=\delta$ and $u(0)=1$. Here, $u(t)$ corresponds
to the previous $\bar u(\bar t)$, while $v(t)$ corresponds to
$d\bar u/d\bar t (\bar t)$. The code can be like this:

```python
def simulate(beta, gamma, delta=0,
             num_periods=8, time_steps_per_period=60):
    # Use oscillations without friction to set dt and T
    P = 2*np.pi
    dt = P/time_steps_per_period
    T = num_periods*P
    t = np.linspace(0, T, time_steps_per_period*num_periods+1)
    import odespy
    def f(u, t, beta, gamma):
        # Note the sequence of unknowns: v, u (v=du/dt)
        v, u = u
        return [-beta*np.sign(v) - 1.0/gamma*np.tanh(gamma*u), v]
        #return [-beta*np.sign(v) - u, v]

    solver = odespy.RK4(f, f_args=(beta, gamma))
    solver.set_initial_condition([delta, 1]) # sequence must match f
    uv, t = solver.solve(t)
    u = uv[:,1]  # recall sequence in f: v, u
    v = uv[:,0]
    return u, t
```

We simulate for an almost linear spring in the regime of $\bar u$ (recall
that $\bar u\in [0,1]$ since $u$ is scaled with $I$), which corresponds
to $\alpha = 1$ in a) and therefore $\gamma =1$. Then we can try a
spring whose force quickly flattens out like $\alpha=5$ in a), which
corresponds to $\gamma = 5$ in the scaled model. A third option is
to have a truly linear spring, e.g., $\gamma =0.1$. After some
experimentation we realize that $\beta=0,0.05, 0.1$ are relevant values.

Filename: `sliding_box`.

## Exercise 3: Simulate a bouncing ball

The section [Bouncing ball](#vib:app:bouncing_ball) presents a model for a bouncing
ball.
Choose one of the two ODE formulation, ([34](#vib:app:bouncing:ball:h2eq)) or
([35](#vib:app:bouncing:ball:veq))-([36](#vib:app:bouncing:ball:heq)),
and simulate the motion of a bouncing ball. Plot $h(t)$. Think about how to
plot $v(t)$.

**Hint.**
A naive implementation may get stuck in repeated impacts for large time
step sizes. To avoid this situation, one can introduce a state
variable that holds the mode of the motion: free fall, impact, or rest.
Two consecutive impacts imply that the motion has stopped.

**Solution.**
A tailored `solver` function and some plotting statements go like

```python
import numpy as np

def solver(H, C_R, dt, T, eps_v=0.01, eps_h=0.01):
    """
    Simulate bouncing ball until it comes to rest. Time step dt.
    h(0)=H (initial height). T: maximum simulation time.
    Method: Euler-Cromer.
    """
    dt = float(dt)
    Nt = int(round(T/dt))
    h = np.zeros(Nt+1)
    v = np.zeros(Nt+1)
    t = np.linspace(0, Nt*dt, Nt+1)
    g = 0.81

    v[0] = 0
    h[0] = H
    mode = 'free fall'
    for n in range(Nt):
        v[n+1] = v[n] - dt*g
        h[n+1] = h[n] + dt*v[n+1]

        if h[n+1] < eps_h:
            #if abs(v[n+1]) > eps_v:  # handles large dt, but is wrong
            if v[n+1] < -eps_v:
                # Impact
                v[n+1] = -C_R*v[n+1]
                h[n+1] = 0
                if mode == 'impact':
                    # impact twice
                    return h[:n+2], v[:n+2], t[:n+2]
                mode = 'impact'
            elif abs(v[n+1]) < eps_v:
                mode = 'rest'
                v[n+1] = 0
                h[n+1] = 0
                return h[:n+2], v[:n+2], t[:n+2]
            else:
                mode = 'free fall'
        else:
            mode = 'free fall'
        print '%4d v=%8.5f h=%8.5f %s' % (n, v[n+1], h[n+1], mode)
    raise ValueError('T=%g is too short simulation time' % T)

import matplotlib.pyplot as plt
h, v, t = solver(
    H=1, C_R=0.8, T=100, dt=0.0001, eps_v=0.01, eps_h=0.01)
plt.plot(t, h)
plt.legend('h')
plt.savefig('tmp_h.png'); plt.savefig('tmp_h.pdf')
plt.figure()
plt.plot(t, v)
plt.legend('v')
plt.savefig('tmp_v.png'); plt.savefig('tmp_v.pdf')
plt.show()
```

Filename: `bouncing_ball`.

## Exercise 4: Simulate a simple pendulum

Simulation of simple pendulum can be carried out by using
the mathematical model derived in the section [Motion of a pendulum](#vib:app:pendulum)
and calling up functionality in the [`vib.py`](${src_vib}/vib.py)
file (i.e., solve the second-order ODE by centered finite differences).

**a)**
Scale the model. Set up the dimensionless governing equation for $\theta$
and expressions for dimensionless drag and wire forces.

**Solution.**
The angle is measured in radians so we may think of this quantity as
dimensionless, or we may scale it by the initial condition to obtain
a primary unknown that lies in $[-1,1]$. We go for the former strategy here.

Dimensionless time $\bar t$ is introduced as $t/t_c$ for some suitable
time scale $t_c$.

Inserted in the two governing equations
([8](#vib:app:pendulum:thetaeq)) and ([6](#vib:app:pendulum:ir)),
for the
two unknowns $\theta$ and $S$, respectively, we achieve

$$
\begin{align*}
-S + mg\cos\theta &= -\frac{1}{t_c}L\frac{d\theta}{d\bar t},\\
\frac{1}{t_c^2}m\frac{d^2\theta}{d\bar t^2} +
\frac{1}{2}C_D\varrho AL \frac{1}{t_c^2}\left\vert
\frac{d\theta}{d\bar t}\right\vert
\frac{d\theta}{d\bar t}
+ \frac{mg}{L}\sin\theta &= 0\thinspace .
\end{align*}
$$

We multiply the latter equation by $t_c^2/m$ to make each term
dimensionless:

$$
\frac{d^2\theta}{d\bar t^2} +
\frac{1}{2m}C_D\varrho AL \left\vert
\frac{d\theta}{d\bar t}\right\vert
\frac{d\theta}{d\bar t}
+ \frac{t_c^2g}{L}\sin\theta = 0\thinspace .
$$

Assuming that the acceleration term and the
gravity term to be the dominating terms, these should balance, so
$t_c^2g/L=1$, giving $t_c = \sqrt{g/L}$. With $A=\pi R^2$ we get the
dimensionless ODEs

$$
\begin{equation}
\frac{d^2\theta}{d\bar t^2} +
\alpha\left\vert\frac{d\theta}{d\bar t}\right\vert\frac{d\theta}{d\bar t} +
\sin\theta = 0,
\label{vib:exer:pendulum_simple:eq:ith:s} \tag{42}
\end{equation}
$$

$$
\begin{equation}
\frac{S}{mg} = \left(\frac{d\theta}{d\bar t}\right)^2 + \cos\theta,
\label{vib:exer:pendulum_simple:eq:ir:s} \tag{43}
\end{equation}
$$

where $\alpha$ is a dimensionless drag coefficient

$$
\alpha = \frac{C_D\varrho\pi R^2L}{2m}\thinspace .
$$

Note that in ([43](#vib:exer:pendulum_simple:eq:ir:s)) we have divided by
$mg$, which is in fact a force scale, making the gravity force unity
and also $S/mg=1$ in the equilibrium position $\theta=0$. We may
introduce

$$
\bar S = S/mg
$$

as a dimensionless drag force.

The parameter $\alpha$ is about
the ratio of the drag force and the gravity force:

$$
\frac{|\frac{1}{2} C_D\varrho \pi R^2 |v|v|}{|mg|}\sim
\frac{C_D\varrho \pi R^2 L^2 t_c^{-2}}{mg}
\left|\frac{d\bar\theta}{d\bar t}\right|\frac{d\bar\theta}{d\bar t}
\sim \frac{C_D\varrho \pi R^2 L}{2m}\Theta^2 = \alpha \Theta^2\thinspace .
$$

(We have that $\theta(t)/d\Theta$ is in $[-1,1]$, so we expect
since $\Theta^{-1}d\bar\theta/d\bar t$ to be around unity. Here,
$\Theta=\theta(0)$.)

Let us introduce $\omega$ for the dimensionless angular velocity,

$$
\omega = \frac{d\theta}{d\bar t}\thinspace .
$$

When $\theta$ is computed, the dimensionless wire and drag forces
are computed by

$$
\begin{align*}
\bar S &= \omega^2 + \cos\theta,\\
\bar D &= -\alpha |\omega|\omega\thinspace .
\end{align*}
$$

**b)**
Write a function for computing
$\theta$ and the dimensionless drag force and the force in the wire,
using the `solver` function in
the `vib.py` file. Plot these three quantities
below each other (in subplots) so the graphs can be compared.
Run two cases, first one in the limit of $\Theta$ small and
no drag, and then a second one with $\Theta=40$ degrees and $\alpha=0.8$.

**Solution.**
The first step is to realize how to utilize the `solver` function for
our dimensionless model. Introducing `Theta` for $\Theta$, the
arguments to `solver` must be set as

```python
I = Theta
V = 0
m = 1
b = alpha
s = lambda u: sin(u)
F = lambda t: 0
damping = 'quadratic'
```

After computing $\theta$, we need to find $\omega$ by finite differences:

$$
\omega^n = \frac{\theta^{n+1}-\theta^{n-1}}{2\Delta t},
\ n=1,\ldots,N_t-1,\quad \omega^0=\frac{\theta^1-\theta^0}{\Delta t},
\ \omega^{N_t}=\frac{\theta^{N_t}-\theta^{N_t-1}}{\Delta t}\thinspace .
$$

The duration of the simulation and the time step can be computed on
basis of the analytical insight we have for small $\theta$
($\theta\approx \Theta\cos(t)$). A complete function then reads

```python
def simulate(Theta, alpha, num_periods=10):
    # Dimensionless model requires the following parameters:
    from math import sin, pi

    I = Theta
    V = 0
    m = 1
    b = alpha
    s = lambda u: sin(u)
    F = lambda t: 0
    damping = 'quadratic'

    # Estimate T and dt from the small angle solution
    P = 2*pi   # One period (theta small, no drag)
    dt = P/40  # 40 intervals per period
    T = num_periods*P

    theta, t =  solver(I, V, m, b, s, F, dt, T, damping)
    omega = np.zeros(theta.size)
    omega[1:-1] = (theta[2:] - theta[:-2])/(2*dt)
    omega[0] = (theta[1] - theta[0])/dt
    omega[-1] = (theta[-1] - theta[-2])/dt

    S = omega**2 + np.cos(theta)
    D = alpha*np.abs(omega)*omega
    return t, theta, S, D
```

Assuming imports like

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

the following function visualizes $\theta$, $\bar S$, and $\bar D$
with three subplots:

```python
def visualize(t, theta, S, D, filename='tmp'):
    f, (ax1, ax2, ax3) = plt.subplots(3, sharex=True, sharey=False)
    ax1.plot(t, theta)
    ax1.set_title(r'$\theta(t)$')
    ax2.plot(t, S)
    ax2.set_title(r'Dimensonless force in the wire')
    ax3.plot(t, D)
    ax3.set_title(r'Dimensionless drag force')
    plt.savefig('%s.png' % filename)
    plt.savefig('%s.pdf' % filename)
```

A suitable main program is

```python
import math
# Rough verification that small theta and no drag gives cos(t)
Theta = 1.0
alpha = 0
t, theta, S, D = simulate(Theta, alpha, num_periods=4)
# Scale theta by Theta (easier to compare with cos(t))
theta /= Theta
visualize(t, theta, S, D, filename='pendulum_verify')

Theta = math.radians(40)
alpha = 0.8
t, theta, S, D = simulate(Theta, alpha)
visualize(t, theta, S, D, filename='pendulum_alpha0.8_Theta40')
plt.show()
```

The "verification" case looks good (at least when the `solver` function
has been thoroughly verified in other circumstances):

The "real case" shows how quickly the drag force is reduced, even when
we set $\alpha$ to a significant value (0.8):

Filename: `simple_pendulum`.

## Exercise 5: Simulate an elastic pendulum

The section [Motion of an elastic pendulum](#vib:app:pendulum_elastic) describes a model for an elastic
pendulum, resulting in a system of two ODEs. The purpose of this
exercise is to implement the scaled model, test the software, and
generalize the model.

**a)**
Write a function `simulate`
that can simulate an elastic pendulum using the scaled model.
The function should have the following arguments:

```python
def simulate(
    beta=0.9,                 # dimensionless parameter
    Theta=30,                 # initial angle in degrees
    epsilon=0,                # initial stretch of wire
    num_periods=6,            # simulate for num_periods
    time_steps_per_period=60, # time step resolution
    plot=True,                # make plots or not
    ):
```

To set the total simulation time and the time step, we
use our knowledge of the scaled, classical, non-elastic pendulum:
$u^{\prime\prime} + u = 0$, with solution
$u = \Theta\cos \bar t$.
The period of these oscillations is $P=2\pi$
and the frequency is unity. The time
for simulation is taken as `num_periods` times $P$. The time step
is set as $P$ divided by `time_steps_per_period`.

The `simulate` function should return the arrays of
$x$, $y$, $\theta$, and $t$, where $\theta = \tan^{-1}(x/(1-y))$ is
the angular displacement of the elastic pendulum corresponding to the
position $(x,y)$.

If `plot` is `True`, make a plot of $\bar y(\bar t)$
versus $\bar x(\bar t)$, i.e., the physical motion
of the mass at $(\bar x,\bar y)$. Use the equal aspect ratio on the axis
such that we get a physically correct picture of the motion. Also
make a plot of $\theta(\bar t)$, where $\theta$ is measured in degrees.
If $\Theta < 10$ degrees, add a plot that compares the solutions of
the scaled, classical, non-elastic pendulum and the elastic pendulum
($\theta(t)$).

Although the mathematics here employs a bar over scaled quantities, the
code should feature plain names `x` for $\bar x$, `y` for $\bar y$, and
`t` for $\bar t$ (rather than `x_bar`, etc.). These variable names make
the code easier to read and compare with the mathematics.

**Hint 1.**
Equal aspect ratio is set by `plt.gca().set_aspect('equal')` in
Matplotlib (`import matplotlib.pyplot as plt`)
and in SciTools by the command
`plt.plot(..., daspect=[1,1,1], daspectmode='equal')`
(provided you have done `import scitools.std as plt`).

**Hint 2.**
If you want to use Odespy to solve the equations, order the ODEs
like $\dot \bar x, \bar x, \dot\bar y,\bar y$ such that
`odespy.EulerCromer` can be applied.

**Solution.**
Here is a suggested `simulate` function:

```python
import odespy
import numpy as np
import scitools.std as plt

def simulate(
    beta=0.9,                 # dimensionless parameter
    Theta=30,                 # initial angle in degrees
    epsilon=0,                # initial stretch of wire
    num_periods=6,            # simulate for num_periods
    time_steps_per_period=60, # time step resolution
    plot=True,                # make plots or not
    ):
    from math import sin, cos, pi
    Theta = Theta*np.pi/180  # convert to radians
    # Initial position and velocity
    # (we order the equations such that Euler-Cromer in odespy
    # can be used, i.e., vx, x, vy, y)
    ic = [0,                              # x'=vx
          (1 + epsilon)*sin(Theta),       # x
          0,                              # y'=vy
          1 - (1 + epsilon)*cos(Theta),   # y
          ]

    def f(u, t, beta):
        vx, x, vy, y = u
        L = np.sqrt(x**2 + (y-1)**2)
        h = beta/(1-beta)*(1 - beta/L)  # help factor
        return [-h*x, vx, -h*(y-1) - beta, vy]

    # Non-elastic pendulum (scaled similarly in the limit beta=1)
    # solution Theta*cos(t)
    P = 2*pi
    dt = P/time_steps_per_period
    T = num_periods*P
    omega = 2*pi/P

    time_points = np.linspace(
        0, T, num_periods*time_steps_per_period+1)

    solver = odespy.EulerCromer(f, f_args=(beta,))
    solver.set_initial_condition(ic)
    u, t = solver.solve(time_points)
    x = u[:,1]
    y = u[:,3]
    theta = np.arctan(x/(1-y))

    if plot:
        plt.figure()
        plt.plot(x, y, 'b-', title='Pendulum motion',
                 daspect=[1,1,1], daspectmode='equal',
                 axis=[x.min(), x.max(), 1.3*y.min(), 1])
        plt.savefig('tmp_xy.png')
        plt.savefig('tmp_xy.pdf')
        # Plot theta in degrees
        plt.figure()
        plt.plot(t, theta*180/np.pi, 'b-',
                 title='Angular displacement in degrees')
        plt.savefig('tmp_theta.png')
        plt.savefig('tmp_theta.pdf')
        if abs(Theta) < 10*pi/180:
            # Compare theta and theta_e for small angles (<10 degrees)
            theta_e = Theta*np.cos(omega*t)  # non-elastic scaled sol.
            plt.figure()
            plt.plot(t, theta, t, theta_e,
                     legend=['theta elastic', 'theta non-elastic'],
                     title='Elastic vs non-elastic pendulum, '\
                            'beta=%g' % beta)
            plt.savefig('tmp_compare.png')
            plt.savefig('tmp_compare.pdf')
        # Plot y vs x (the real physical motion)
    return x, y, theta, t
```

**b)**
Write a test function for testing that $\Theta=0$ and $\epsilon=0$
gives $x=y=0$ for all times.

**Solution.**
Here is the code:

```python
def test_equilibrium():
    """Test that starting from rest makes x=y=theta=0."""
    x, y, theta, t = simulate(
        beta=0.9, Theta=0, epsilon=0,
        num_periods=6, time_steps_per_period=10, plot=False)
    tol = 1E-14
    assert np.abs(x.max()) < tol
    assert np.abs(y.max()) < tol
    assert np.abs(theta.max()) < tol
```

**c)**
Write another test function for checking that the pure vertical
motion of the elastic pendulum is correct.
Start with simplifying the ODEs for pure vertical motion and show that
$\bar y(\bar t)$ fulfills a vibration equation with
frequency $\sqrt{\beta/(1-\beta)}$. Set up the exact solution.

Write a test function that
uses this special case to verify the `simulate` function. There will
be numerical approximation errors present in the results from
`simulate` so you have to believe in correct results and set a
(low) tolerance that corresponds to the computed maximum error.
Use a small $\Delta t$ to obtain a small numerical approximation error.

**Solution.**
For purely vertical motion, the ODEs reduce to $\ddot x = 0$ and

$$
\frac{d^2\bar y}{d\bar t^2} = -\frac{\beta}{1-\beta}(1-\beta\frac{1}{\sqrt{(\bar y - 1)^2}})(\bar y-1) - \beta = -\frac{\beta}{1-\beta}(\bar y-1 + \beta) - \beta\thinspace .
$$

We have here used that $(\bar y -1)/\sqrt{(\bar y -1)^2}=-1$ since
$\bar y$ cannot exceed 1 (the pendulum's wire is fixed at the scaled
point $(0,1)$). In fact, $\bar y$ will be around zero.
(As a consistency check, we realize that in equilibrium, $\ddot\bar y =0$,
and multiplying by $(1-\beta)/\beta$ leads to the expected $\bar y=0$.)
Further calculations easily lead to

$$
\frac{d^2\bar y}{d\bar t^2} = -\frac{\beta}{1-\beta}\bar y = -\omega^2\bar y,
$$

where we have introduced the frequency
$\omega = \sqrt{\beta/(1-\beta)}$.
Solving this standard ODE, with an initial stretching $\bar y(0)=\epsilon$
and no velocity, results in

$$
\bar y(\bar t) = \epsilon\cos(\omega\bar t)\thinspace .
$$

Note that the oscillations we describe here are very different from
the oscillations used to set the period and time step in function
`simulate`. The latter type of oscillations are due to gravity when
a classical, non-elastic pendulum oscillates back and forth, while
$\bar y(\bar t)$ above refers to vertical *elastic* oscillations in the wire
around the equilibrium point in the gravity field. The angular frequency
of the vertical oscillations are given by $\omega$ and the corresponding
period is $\hat P = 2\pi/\omega$. Suppose we want to simulate for
$T=N\hat P = N2\pi/\omega$ and use $n$ time steps per period,
$\Delta\bar t = \hat P/n$. The `simulate` function operates with
a simulation time of `num_periods` times $2\pi$. This means that we must set
`num_periods=N/omega` if we want to simulate to time $T=N\hat P$.
The parameter `time_steps_per_period` must be set to $\omega n$
since `simulate` has $\Delta t$ as $2\pi$ divided by `time_steps_per_period`
and we want $\Delta t = 2\pi\omega^{-1}n^{-1}$.

The corresponding test function can be written as follows.

```python
def test_vertical_motion():
    beta = 0.9
    omega = np.sqrt(beta/(1-beta))
    # Find num_periods. Recall that P=2*pi for scaled pendulum
    # oscillations, while here we don't have gravity driven
    # oscillations, but elastic oscillations with frequency omega.
    period = 2*np.pi/omega
    # We want T = N*period
    N = 5
    # simulate function has T = 2*pi*num_periods
    num_periods = 5/omega
    n = 600
    time_steps_per_period = omega*n

    y_exact = lambda t: -0.1*np.cos(omega*t)
    x, y, theta, t = simulate(
        beta=beta, Theta=0, epsilon=0.1,
        num_periods=num_periods,
        time_steps_per_period=time_steps_per_period,
        plot=False)

    tol = 0.00055 # ok tolerance for the above resolution
    # No motion in x direction is epxected
    assert np.abs(x.max()) < tol
    # Check motion in y direction
    y_e = y_exact(t)
    diff = np.abs(y_e - y).max()
    if diff > tol: # plot
        plt.plot(t, y, t, y_e, legend=['y', 'exact'])
        raw_input('Error in test_vertical_motion; type CR:')
    assert diff < tol, 'diff=%g' % diff
```

**d)**
Make a function `demo(beta, Theta)` for simulating an elastic pendulum with a
given $\beta$ parameter and initial angle $\Theta$. Use 600 time steps
per period to get every accurate results, and simulate for 3 periods.

**Solution.**
The `demo` function is just

```python
def demo(beta=0.999, Theta=40, num_periods=3):
    x, y, theta, t = simulate(
        beta=beta, Theta=Theta, epsilon=0,
        num_periods=num_periods, time_steps_per_period=600,
        plot=True)
```

Below are plots corresponding to $\beta = 0.999$ (3 periods) and
$\beta = 0.93$ (one period):

Filename: `elastic_pendulum`.

## Exercise 6: Simulate an elastic pendulum with air resistance

This is a continuation [Exercise 5: Simulate an elastic pendulum](#vib:exer:pendulum_elastic).
Air resistance on the body with mass $m$ can be modeled by the
force $-\frac{1}{2}\varrho C_D A|\v|\v$,
where $C_D$ is a drag coefficient (0.2 for a sphere), $\varrho$
is the density of air (1.2 $\hbox{kg }\,{\hbox{m}}^{-3}$), $A$ is the
cross section area ($A=\pi R^2$ for a sphere, where $R$ is the radius),
and $\v$ is the velocity of the body.
Include air resistance in the original model, scale the model,
write a function `simulate_drag` that is a copy of the `simulate`
function from [Exercise 5: Simulate an elastic pendulum](#vib:exer:pendulum_elastic), but with the
new ODEs included, and show plots of how air resistance
influences the motion.

**Solution.**
We start with the model
([18](#vib:app:pendulum_elastic:x))-([24](#vib:app:pendulum_elastic:vy0)).
Since $\v = \dot x\ii + \dot y\jj$, the air resistance term
can be written

$$
-q(\dot x\ii + \dot y\jj),\quad q=\frac{1}{2}\varrho C_D A\sqrt{\dot x^2 + \dot y^2}\thinspace .
$$

Note that for positive velocities, the pendulum is moving to the right
and the air resistance works against the motion, i.e., in direction of
$-\v = -\dot x\ii - \dot y\jj$.

We can easily include the terms in the ODEs:

$$
\begin{equation}
\ddot x = -\frac{q}{m}\dot x -\frac{k}{m}\left(1 -\frac{L_0}{L}\right)(x-x_0),
\label{vib:app:pendulum_elastic_drag:x} \tag{44}
\end{equation}
$$

$$
\begin{equation}
\ddot y = -\frac{q}{m}\dot y -\frac{k}{m}\left(1 -\frac{L_0}{L}\right)(y-y_0) - g,
\label{vib:app:pendulum_elastic_drag:y} \tag{45}
\end{equation}
$$

$$
\begin{equation}
L = \sqrt{(x-x_0)^2 + (y-y_0)^2},
\label{vib:app:pendulum_elastic_drag:L} \tag{46}
\end{equation}
$$

$$
\begin{equation}
\label{_auto9} \tag{47}
\end{equation}
$$

The initial conditions are not affected.

The next step is to scale the model. We use the same scales as in
[Exercise 5: Simulate an elastic pendulum](#vib:exer:pendulum_elastic), introduce $\beta$, and $A=\pi R^2$
to simplify the $-q\dot x/m$ term to

$$
\frac{L_0}{2m}\varrho C_D R^2\beta^{-1}
\sqrt{\left(\frac{d\bar x}{d\bar t}\right)^2 +
\left(\frac{d\bar y}{d\bar t}\right)^2}
= \gamma \beta^{-1}
\sqrt{\left(\frac{d\bar x}{d\bar t}\right)^2 +
\left(\frac{d\bar y}{d\bar t}\right)^2},
$$

where $\gamma$ is a second dimensionless parameter:

$$
\gamma = \frac{L_0}{2m}\varrho C_D R^2\thinspace .
$$

The final set of scaled equations is then

$$
\begin{equation}
\frac{d^2\bar x}{d\bar t^2} = -\gamma\beta^{-1}
\sqrt{\left(\frac{d\bar x}{d\bar t}\right)^2 +
\left(\frac{d\bar y}{d\bar t}\right)^2}\frac{d\bar x}{d\bar t}
-\frac{\beta}{1-\beta}\left(1- \frac{\beta}{\bar L}\right)\bar x,
\label{vib:app:pendulum_elastic_drag:x:s} \tag{48}
\end{equation}
$$

$$
\begin{equation}
\frac{d^2\bar y}{d\bar t^2} =
-\gamma\beta^{-1}
\sqrt{\left(\frac{d\bar x}{d\bar t}\right)^2 +
\left(\frac{d\bar y}{d\bar t}\right)^2}\frac{d\bar y}{d\bar t}
-\frac{\beta}{1-\beta}\left(1- \frac{\beta}{\bar L}\right)(\bar y-1)
-\beta,
\label{vib:app:pendulum_elastic_drag:y:s} \tag{49}
\end{equation}
$$

$$
\begin{equation}
\bar L = \sqrt{\bar x^2 + (\bar y-1)^2},
\label{vib:app:pendulum_elastic_drag:L:s} \tag{50}
\end{equation}
$$

$$
\begin{equation}
\bar x(0) = (1+\epsilon)\sin\Theta,
\label{vib:app:pendulum_elastic_drag:x0:s} \tag{51}
\end{equation}
$$

$$
\begin{equation}
\frac{d\bar x}{d\bar t}(0) = 0,
\label{vib:app:pendulum_elastic_drag:vx0:s} \tag{52}
\end{equation}
$$

$$
\begin{equation}
\bar y(0) = 1 - (1+\epsilon)\cos\Theta,
\label{vib:app:pendulum_elastic_drag:y0:s} \tag{53}
\end{equation}
$$

$$
\begin{equation}
\frac{d\bar y}{d\bar t}(0) = 0,
\label{vib:app:pendulum_elastic_drag:vy0:s} \tag{54}
\end{equation}
$$

The new `simulate_drag` function is implemented below.

```python
def simulate_drag(
    beta=0.9,                 # dimensionless elasticity parameter
    gamma=0,                  # dimensionless drag parameter
    Theta=30,                 # initial angle in degrees
    epsilon=0,                # initial stretch of wire
    num_periods=6,            # simulate for num_periods
    time_steps_per_period=60, # time step resolution
    plot=True,                # make plots or not
    ):
    from math import sin, cos, pi
    Theta = Theta*np.pi/180  # convert to radians
    # Initial position and velocity
    # (we order the equations such that Euler-Cromer in odespy
    # can be used, i.e., vx, x, vy, y)
    ic = [0,                              # x'=vx
          (1 + epsilon)*sin(Theta),       # x
          0,                              # y'=vy
          1 - (1 + epsilon)*cos(Theta),   # y
          ]

    def f(u, t, beta, gamma):
        vx, x, vy, y = u
        L = np.sqrt(x**2 + (y-1)**2)
        v = np.sqrt(vx**2 + vy**2)
        h1 = beta/(1-beta)*(1 - beta/L)  # help factor
        h2 = gamma/beta*v
        return [-h2*vx - h1*x, vx, -h2*vy - h1*(y-1) - beta, vy]

    # Non-elastic pendulum (scaled similarly in the limit beta=1)
    # solution Theta*cos(t)
    P = 2*pi
    dt = P/time_steps_per_period
    T = num_periods*P
    omega = 2*pi/P

    time_points = np.linspace(
        0, T, num_periods*time_steps_per_period+1)

    solver = odespy.EulerCromer(f, f_args=(beta, gamma))
    solver.set_initial_condition(ic)
    u, t = solver.solve(time_points)
    x = u[:,1]
    y = u[:,3]
    theta = np.arctan(x/(1-y))

    if plot:
        plt.figure()
        plt.plot(x, y, 'b-', title='Pendulum motion',
                 daspect=[1,1,1], daspectmode='equal',
                 axis=[x.min(), x.max(), 1.3*y.min(), 1])
        plt.savefig('tmp_xy.png')
        plt.savefig('tmp_xy.pdf')
        # Plot theta in degrees
        plt.figure()
        plt.plot(t, theta*180/np.pi, 'b-',
                 title='Angular displacement in degrees')
        plt.savefig('tmp_theta.png')
        plt.savefig('tmp_theta.pdf')
        if abs(Theta) < 10*pi/180:
            # Compare theta and theta_e for small angles (<10 degrees)
            theta_e = Theta*np.cos(omega*t)  # non-elastic scaled sol.
            plt.figure()
            plt.plot(t, theta, t, theta_e,
                     legend=['theta elastic', 'theta non-elastic'],
                     title='Elastic vs non-elastic pendulum, '\
                            'beta=%g' % beta)
            plt.savefig('tmp_compare.png')
            plt.savefig('tmp_compare.pdf')
        # Plot y vs x (the real physical motion)
    return x, y, theta, t
```

The plot of $\theta$ shows the damping ($\beta = 0.999$):

Test functions for equilibrium and vertical motion are also included. These
are as in [Exercise 6: Simulate an elastic pendulum with air resistance](#vib:exer:pendulum_elastic_drag), except that
they call `simulate_drag` instead of `simulate`.

Filename: `elastic_pendulum_drag`.

### Remarks

Test functions are challenging to construct for the problem with
air resistance. You can reuse the tests from
[Exercise 6: Simulate an elastic pendulum with air resistance](#vib:exer:pendulum_elastic_drag) for `simulate_drag`,
but these tests does not verify the new terms arising from air
resistance.

## Exercise 7: Implement the PEFRL algorithm

We consider the motion of a planet around a star (the section [Two-body gravitational problem](#vib:app:gravitation)).
The simplified case where one
mass is very much bigger than the other and one object is at rest,
results in the scaled ODE model

$$
\begin{align*}
\ddot x + (x^2 + y^2)^{-3/2}x & = 0,\\
\ddot y + (x^2 + y^2)^{-3/2}y & = 0\thinspace .
\end{align*}
$$

**a)**
mathcal{I}_t is easy to show that $x(t)$ and $y(t)$ go like sine and cosine
functions. Use this idea to derive the exact solution.

**Solution.**
We may assume $x=C_x\cos(\omega t)$ and $y=C_y\sin(\omega t)$ for
constants $C_x,$, $C_y$, and $\omega$. Inserted in the equations, we
see that $\omega =1$. The initial conditions determine the other
constants, which we may choose as $C_x=C_y=1$ (the object starts
at $(1,0)$ with a velocity $(0,1)$). The motion is a perfect circle,
which should last forever.

**b)**
One believes that a planet may orbit a star for billions of years.
We are now interested
in how accurate methods we actually need for such calculations.
A first task is to determine what the time interval of interest is in
scaled units. Take the earth and sun as typical objects and find
the characteristic time used in the scaling of the equations
($t_c = \sqrt{L^3/(mG)}$), where $m$ is the mass of the sun, $L$ is the
distance between the sun and the earth, and $G$ is the gravitational
constant. Find the scaled time interval corresponding to one billion years.

**Solution.**
According to [Wikipedia](https://en.wikipedia.org/wiki/Solar_mass),
the mass of the sun is approximately $2\cdot 10^{30}$ kg. This
is 332946 times the mass of the earth, implying that the
dimensionless constant $\alpha \approx 3\cdot 10^{-6}$. With
$G=6.674\cdot 10^{-11}\hbox{ Nm}^2/\hbox{kg}^2$, and the
[sun-earth distance](https://en.wikipedia.org/wiki/Astronomical_unit)
as (approximately) 150 million km, we have $t_c \approx 5 028 388$ s.
This is about 58 days, which is the characteristic time, chosen as the
angular frequency of the oscillations. To get the period of one orbit we therefore must multiply by $2\pi$. This gives about 1 year (and demonstrates the
fact mentioned about the scaling: the natural time scale is consistent with
Kepler's law about the period).

Thus, one billion years correspond to 62,715,924,070 time units (dividing
one billion years by $t_c$), which corresponds to about 2000
"time unit years".

**c)**
Solve the equations using 4th-order Runge-Kutta and the Euler-Cromer
methods. You may benefit from applying Odespy for this purpose. With
each solver, simulate 10000 orbits and print the maximum position
error and CPU time as a function of time step. Note that the maximum
position error does not necessarily occur at the end of the
simulation. The position error achieved with each solver will depend
heavily on the size of the time step. Let the time step correspond to
200, 400, 800 and 1600 steps per orbit, respectively.  Are the results
as expected?  Explain briefly. When you develop your program, have in
mind that it will be extended with an implementation of the other
algorithms (as requested in d) and e) later) and experiments with this
algorithm as well.

**Solution.**
The first task is to implement the right-hand side function for the
system of ODEs such that we can call up Odespy solvers (or make use of
other types of ODE software, e.g., from SciPy). The $2\times 2$ system of
second-order ODEs must be expressed as a $4\times 4$ system of first-order
ODEs. We have three different cases of right-hand sides:

1. Common numbering of unknowns: $x$, $v_x$, $y$, $y_x$

2. Numbering required by Euler-Cromer: $v_x$, $x$, $v_y$, $y$

3. Numbering required by the PEFRL method: same as Euler-Cromer

Most Odespy solvers can handle any convention for numbering of the unknowns.
The important point is that initial conditions and new values at the end of
the time step are filled in the right positions of a one-dimensional array
containing the unknowns.
Using Odespy to solve the system by the Euler-Cromer method, however, requires
the unknowns to appear as velocity 1st degree-of-freedom, displacement
1st degree-of-freedom, velocity 2nd degree-of-freedom, displacement
2nd degree-of-freedom, and so forth. Two alternative right-hand side
functions `f(u, t)` for Odespy solvers is then

```python
def f_EC(u, t):
    '''
    Return derivatives for the 1st order system as
    required by Euler-Cromer.
    '''
    vx, x, vy, y = u  # u: array holding vx, x, vy, y
    d = -(x**2 + y**2)**(-3.0/2)
    return [d*x, vx, d*y, vy ]

def f_RK4(u, t):
    '''
    Return derivatives for the 1st order system as
    required by ordinary solvers in Odespy.
    '''
    x, vx, y, vy = u  # u: array holding x, vx, y, vy
    d = -(x**2 + y**2)**(-3.0/2)
    return [vx, d*x, vy, d*y ]
```

In addition, we shall later in d) implement the PEFRL method and just
give the $g$ function as input to a system of the form $dv_x = g_x$,
$dv_y = g_y$, and $g$ becomes the vector $(g_x,g_y)$:

Some prefer to number the unknowns differently, and with the RK4 method we
are free to use any numbering, including this one:

```python
def g(u, v):
    return np.array([-u])
def u_exact(t):
    return np.array([3*np.cos(t)]).transpose()
I = u_exact(0)
V = np.array([0])
print 'V:', V, 'I:', I

# Numerical parameters
w = 1
P = 2*np.pi/w
dt_values = [P/20, P/40, P/80, P/160, P/320]
T = 8*P
error_vs_dt = []
for n, dt in enumerate(dt_values):
    u, v, t = solver_PEFRL(I, V, g, dt, T)
    error = np.abs(u - u_exact(t)).max()
    print 'error:', error
    if n > 0:
        error_vs_dt.append(error/dt**4)
for i in range(1, len(error_vs_dt)):
    #print abs(error_vs_dt[i]- error_vs_dt[0])
    assert abs(error_vs_dt[i]-
               error_vs_dt[0]) < 0.1

s PEFRL(odespy.Solver):
"""Class wrapper for Odespy."""  # Not used!
quick_desctiption = "Explicit 4th-order method for v'=-f, u=v."""

def advance(self):
    u, f, n, t = self.u, self.f, self.n, self.t
    dt = t[n+1] - t[n]
    I = np.array([u[1], u[3]])
    V = np.array([u[0], u[2]])
    u, v, t = solver_PFFRL(I, V, f, dt, t+dt)
    return np.array([v[-1], u[-1]])

compute_orbit_and_error(
f,
solver_ID,
timesteps_per_period=20,
N_orbit_groups=1000,
orbit_group_size=10):
'''
For one particular solver:
Calculte the orbits for a multiple of grouped orbits, i.e.
number of orbits = orbit_group_size*N_orbit_groups.
Returns: time step dt, and, for each N_orbit_groups cycle,
the 2D position error and cpu time (as lists).
'''
def u_exact(t):
    return np.array([np.cos(t), np.sin(t)])

w = 1
P = 2*np.pi/w       # scaled period (1 year becomes 2*pi)
dt = P/timesteps_per_period
Nt = orbit_group_size*N_orbit_groups*timesteps_per_period
T = Nt*dt
t_mesh = np.linspace(0, T, Nt+1)
E_orbit = []

#print '        dt:', dt
T_interval = P*orbit_group_size
N = int(round(T_interval/dt))

# set initial conditions
if solver_ID == 'EC':
    A = [0,1,1,0]
elif solver_ID == 'PEFRL':
    I = np.array([1, 0])
    V = np.array([0, 1])
else:
    A = [1,0,0,1]

t1 = time.clock()
for i in range(N_orbit_groups):
    time_points = np.linspace(i*T_interval, (i+1)*T_interval,N+1)
    u_e = u_exact(time_points).transpose()
    if solver_ID == 'EC':
        solver = odespy.EulerCromer(f)
        solver.set_initial_condition(A)
        ui, ti = solver.solve(time_points)
        # Find error (correct final pos:  x=1, y=0)
        orbit_error = np.sqrt(
            (ui[:,1]-u_e[:,0])**2 + (ui[:,3]-u_e[:,1])**2).max()
    elif solver_ID == 'PEFRL':
        # Note: every T_inverval is here counted from time 0
        ui, vi, ti = solver_PEFRL(I, V, f, dt, T_interval)
        # Find error (correct final pos:  x=1, y=0)
        orbit_error = np.sqrt(
            (ui[:,0]-u_e[:,0])**2 + (ui[:,1]-u_e[:,1])**2).max()
    else:
        solver = eval('odespy.' + solver_ID(f)
        solver.set_initial_condition(A)
        ui, ti = solver.solve(time_points)
        # Find error (correct final pos:  x=1, y=0)
        orbit_error = np.sqrt(
            (ui[:,0]-u_e[:,0])**2 + (ui[:,2]-u_e[:,1])**2).max()

    print '      Orbit no. %d,   max error (per cent): %g' % \
                       ((i+1)*orbit_group_size, orbit_error)

    E_orbit.append(orbit_error)

    # set init. cond. for next time interval
    if solver_ID == 'EC':
        A = [ui[-1,0], ui[-1,1], ui[-1,2], ui[-1,3]]
    elif solver_ID == 'PEFRL':
        I = [ui[-1,0], ui[-1,1]]
        V = [vi[-1,0], vi[-1,1]]
    else:  # RK4, adaptive rules, etc.
        A = [ui[-1,0], ui[-1,1], ui[-1,2], ui[-1,3]]

t2 = time.clock()
CPU_time = (t2 - t1)/(60.0*60.0)    # in hours
return dt, E_orbit, CPU_time

orbit_error_vs_dt(
f_EC, f_RK4, g, solvers,
N_orbit_groups=1000,
orbit_group_size=10):
'''
With each solver in list "solvers": Simulate
orbit_group_size*N_orbit_groups orbits with different dt values.
Collect final 2D position error for each dt and plot all errors.
'''

for solver_ID in solvers:
    print 'Computing orbit with solver:', solver_ID
    E_values = []
    dt_values = []
    cpu_values = []
    for timesteps_per_period in 200, 400, 800, 1600:
        print '.......time steps per period: ', \
                                  timesteps_per_period
        if solver_ID == 'EC':
            dt, E, cpu_time = compute_orbit_and_error(
                f_EC,
                solver_ID,
                timesteps_per_period,
                N_orbit_groups,
                orbit_group_size)
        elif solver_ID == 'PEFRL':
            dt, E, cpu_time = compute_orbit_and_error(
                g,
                solver_ID,
                timesteps_per_period,
                N_orbit_groups,
                orbit_group_size)
        else:
            dt, E, cpu_time = compute_orbit_and_error(
                f_RK4,
                solver_ID,
                timesteps_per_period,
                N_orbit_groups,
                orbit_group_size)

        dt_values.append(dt)
        E_values.append(np.array(E).max())
        cpu_values.append(cpu_time)
    print 'dt_values:', dt_values
    print 'E max with dt...:', E_values
    print 'cpu_values with dt...:', cpu_values

orbit_error_vs_years(
f_EC, f_RK4, g, solvers,
N_orbit_groups=1000,
orbit_group_size=100,
N_time_steps = 1000):
'''
For each solver in the list solvers:
simulate orbit_group_size*N_orbit_groups orbits with a fixed
dt corresponding to N_time_steps steps per year.
Collect max 2D position errors for each N_time_steps'th run,
plot these errors and CPU. Finally, make an empirical
formula for error and CPU as functions of a number
of cycles.
'''
timesteps_per_period = N_time_steps     # fixed for all runs

for solver_ID in solvers:
    print 'Computing orbit with solver:', solver_ID
    if solver_ID == 'EC':
        dt, E, cpu_time = compute_orbit_and_error(
            f_EC,
            solver_ID,
            timesteps_per_period,
            N_orbit_groups,
            orbit_group_size)
    elif solver_ID == 'PEFRL':
        dt, E, cpu_time = compute_orbit_and_error(
            g,
            solver_ID,
            timesteps_per_period,
            N_orbit_groups,
            orbit_group_size)
    else:
        dt, E, cpu_time = compute_orbit_and_error(
            f_RK4,
            solver_ID,
            timesteps_per_period,
            N_orbit_groups,
            orbit_group_size)

    # E and cpu_time are for every N_orbit_groups cycle
    print 'E_values (fixed dt, changing no of years):', E
    print 'CPU (hours):', cpu_time
    years = np.arange(
        0,
        N_orbit_groups*orbit_group_size,
        orbit_group_size)

    # Now make empirical formula

    def E_of_years(x, *coeff):
        return sum(coeff[i]*x**float((len(coeff)-1)-i) \
                   for i in range(len(coeff)))
    E =  np.array(E)
    degree = 4
    # note index: polyfit finds p[0]*x**4 + p[1]*x**3 ...etc.
    p = np.polyfit(years, E, degree)
    p_str = map(str, p)
    formula = ' + '.join([p_str[i] + '*x**' + \
                    str(degree-i) for i in range(degree+1)])

    print 'Empirical formula (error with years):  ', formula
    plt.figure()
    plt.plot(years,
             E, 'b-',
             years,
             E_of_years(years, *p), 'r--')
    plt.xlabel('Number of years')
    plt.ylabel('Orbit error')
    plt.title(solver_ID)
    filename = solver_ID + 'tmp_E_with_years'
    plt.savefig(filename + '.png')
    plt.savefig(filename + '.pdf')
    plt.show()

    print 'Predicted CPU time in hours (1 billion years):', \
                                        cpu_time*10000
    print 'Predicted max error (1 billion years):', \
                                    E_of_years(1E9, *p)

compute_orbit_error_and_CPU():
'''
Orbit error and associated CPU times are computed with
solvers: RK4, Euler-Cromer, PEFRL.'''

def f_EC(u, t):
    '''
    Return derivatives for the 1st order system as
    required by Euler-Cromer.
    '''
    vx, x, vy, y = u  # u: array holding vx, x, vy, y
    d = -(x**2 + y**2)**(-3.0/2)
    return [d*x, vx, d*y, vy ]

def f_RK4(u, t):
    '''
    Return derivatives for the 1st order system as
    required by ordinary solvers in Odespy.
    '''
    x, vx, y, vy = u  # u: array holding x, vx, y, vy
    d = -(x**2 + y**2)**(-3.0/2)
    return [vx, d*x, vy, d*y ]

def g(u, v):
    '''
    Return derivatives for the 1st order system as
    required by PEFRL.
    '''
    d = -(u[0]**2 + u[1]**2)**(-3.0/2)
    return np.array([d*u[0], d*u[1]])
```

The standard way of solving the ODE by Odespy is then

```python
def u_exact(t):
    """Return exact solution at time t."""
    return np.array([np.cos(t), np.sin(t)])

u_e = u_exact(time_points).transpose()

solver = odespy.RK4(f_RK4)
solver.set_initial_condition(A)
ui, ti = solver.solve(time_points)

# Find error (correct final pos:  x=1, y=0)
orbit_error = np.sqrt(
         (ui[:,0]-u_e[:,0])**2 + (ui[:,2]-u_e[:,1])**2).max()
```

We develop functions for computing errors and plotting results where we
can compare different methods. These functions are shown in the solution to
item d).

Running the code, the time step sizes become

        dt_values: [0.031415926535897934, 0.015707963267948967,
                    0.007853981633974483, 0.003926990816987242]

Corresponding maximum errors (per cent) and CPU values (hours) are for the 4th-order Runge-Kutta given in the table below.

 Quantity 	 $\Delta t_1$	 $\Delta t_2$	 $\Delta t_3$	 $\Delta t_4$

   $\Delta t$    	    0.03            	    0.02            	    0.008           	    0.004

   Error         	    1.9039          	    0.0787          	    0.0025          	    7.7e-05

   CPU (h)       	    0.03            	    0.06            	    0.12            	    0.23

For Euler-Cromer we these results:

 Quantity 	 $\Delta t_1$	 $\Delta t_2$	 $\Delta t_3$	 $\Delta t_4$

   $\Delta t$    	    0.03            	    0.02            	    0.008           	    0.004

   Error         	    2.0162          	    2.0078          	    1.9634          	    0.6730

   CPU (h)       	    0.01            	    0.02            	    0.05            	    0.09

These results are as expected. The Runge-Kutta implementation is much more accurate than Euler-Cromer, but since it requires more computations, more CPU time is needed. For both methods, accuracy and CPU time both increase as
the step size is reduced, but the increase is much more pronounced for
the Euler-Cromer method.

**d)**
Implement a solver based on the PEFRL method from
the section [vib:ode2:PEFRL](#vib:ode2:PEFRL). Verify its 4th-order convergence
using an equation $u'' + u = 0$.

**Solution.**
Here is a solver function:

```python
import numpy as np
import time

def solver_PEFRL(I, V, g, dt, T):
    """
    Solve v' = - g(u,v), u'=v for t in (0,T], u(0)=I and v(0)=V,
    by the PEFRL method.
    """
    dt = float(dt)
    Nt = int(round(T/dt))
    u = np.zeros((Nt+1, len(I)))
    v = np.zeros((Nt+1, len(I)))
    t = np.linspace(0, Nt*dt, Nt+1)

    # these values are from eq (20), ref to paper below
    xi = 0.1786178958448091
    lambda_ = -0.2123418310626054
    chi = -0.06626458266981849

    v[0] = V
    u[0] = I
    # Compare with eq 22 in http://arxiv.org/pdf/cond-mat/0110585.pdf
    for n in range(0, Nt):
        u_ = u[n] + xi*dt*v[n]
        v_ = v[n] + 0.5*(1-2*lambda_)*dt*g(u_, v[n])
        u_ = u_ + chi*dt*v_
        v_ = v_ + lambda_*dt*g(u_, v_)
        u_ = u_ + (1-2*(chi+xi))*dt*v_
        v_ = v_ + lambda_*dt*g(u_, v_)
        u_ = u_ + chi*dt*v_
        v[n+1] = v_ + 0.5*(1-2*lambda_)*dt*g(u_, v_)
        u[n+1] = u_ + xi*dt*v[n+1]
        #print 'v[%d]=%g, u[%d]=%g' % (n+1,v[n+1],n+1,u[n+1])
    return u, v, t
```

A proper test function for verification reads

```python
def test_solver_PEFRL():
    """Check 4th order convergence rate, using u'' + u = 0,
    I = 3.0, V = 0, which has the exact solution u_e = 3*cos(t)"""
    def g(u, v):
        return np.array([-u])
    def u_exact(t):
        return np.array([3*np.cos(t)]).transpose()
    I = u_exact(0)
    V = np.array([0])
    print 'V:', V, 'I:', I

    # Numerical parameters
    w = 1
    P = 2*np.pi/w
    dt_values = [P/20, P/40, P/80, P/160, P/320]
    T = 8*P
    error_vs_dt = []
    for n, dt in enumerate(dt_values):
        u, v, t = solver_PEFRL(I, V, g, dt, T)
        error = np.abs(u - u_exact(t)).max()
        print 'error:', error
        if n > 0:
            error_vs_dt.append(error/dt**4)
    for i in range(1, len(error_vs_dt)):
        #print abs(error_vs_dt[i]- error_vs_dt[0])
        assert abs(error_vs_dt[i]-
                   error_vs_dt[0]) < 0.1
```

**e)**
The simulations done previously with the 4th-order Runge-Kutta and
Euler-Cromer are now to be repeated with the PEFRL solver, so the
code must be extended accordingly. Then run the simulations and comment
on the performance of PEFRL compared to the other two.

**Solution.**
With the PEFRL algorithm, we get

        E max with dt...: [0.0010452575786173163, 6.5310955829464402e-05,
                           4.0475768394248492e-06, 2.9391302503251016e-07]
        cpu_values with dt...: [0.01873611111111106, 0.037422222222222294,
                                0.07511666666666655, 0.14985]

 Qantity  	 $\Delta t_1$	 $\Delta t_2$	 $\Delta t_3$	 $\Delta t_4$

   $\Delta t$    	    0.03            	    0.02            	    0.008           	    0.004

   Error         	    1.04E-3         	    6.53E-05        	    4.05E-6         	    2.94E-7

   CPU (h)       	    0.02            	    0.04            	    0.08            	    0.15

The accuracy is now dramatically improved compared to 4th-order Runge-Kutta (and Euler-Cromer).
With 1600 steps per orbit, the PEFRL maximum error is just below $3.0e-07$ per cent, while
the corresponding error with Runge-Kutta was about $7.7e-05$ per cent! This is striking,
considering the fact that the 4th-order Runge-Kutta and the PEFRL schemes are both 4th-order accurate.

**f)**
Use the PEFRL solver to simulate 100000 orbits with a fixed time step
corresponding to 1600 steps per period. Record the maximum error
within each subsequent group of 1000 orbits. Plot these errors and fit
(least squares) a mathematical function to the data. Print also the
total CPU time spent for all 100000 orbits.

Now, predict the error and required CPU time for a simulation of 1
billion years (orbits).  Is it feasible on today's computers to
simulate the planetary motion for one billion years?

**Solution.**
The complete code (which also produces the printouts given previously) reads:

```python
import scitools.std as plt
import sys
import odespy
import numpy as np
import time

def solver_PEFRL(I, V, g, dt, T):
    """
    Solve v' = - g(u,v), u'=v for t in (0,T], u(0)=I and v(0)=V,
    by the PEFRL method.
    """
    dt = float(dt)
    Nt = int(round(T/dt))
    u = np.zeros((Nt+1, len(I)))
    v = np.zeros((Nt+1, len(I)))
    t = np.linspace(0, Nt*dt, Nt+1)

    # these values are from eq (20), ref to paper below
    xi = 0.1786178958448091
    lambda_ = -0.2123418310626054
    chi = -0.06626458266981849

    v[0] = V
    u[0] = I
    # Compare with eq 22 in http://arxiv.org/pdf/cond-mat/0110585.pdf
    for n in range(0, Nt):
        u_ = u[n] + xi*dt*v[n]
        v_ = v[n] + 0.5*(1-2*lambda_)*dt*g(u_, v[n])
        u_ = u_ + chi*dt*v_
        v_ = v_ + lambda_*dt*g(u_, v_)
        u_ = u_ + (1-2*(chi+xi))*dt*v_
        v_ = v_ + lambda_*dt*g(u_, v_)
        u_ = u_ + chi*dt*v_
        v[n+1] = v_ + 0.5*(1-2*lambda_)*dt*g(u_, v_)
        u[n+1] = u_ + xi*dt*v[n+1]
        #print 'v[%d]=%g, u[%d]=%g' % (n+1,v[n+1],n+1,u[n+1])
    return u, v, t

def test_solver_PEFRL():
    """Check 4th order convergence rate, using u'' + u = 0,
    I = 3.0, V = 0, which has the exact solution u_e = 3*cos(t)"""
    def g(u, v):
        return np.array([-u])
    def u_exact(t):
        return np.array([3*np.cos(t)]).transpose()
    I = u_exact(0)
    V = np.array([0])
    print 'V:', V, 'I:', I

    # Numerical parameters
    w = 1
    P = 2*np.pi/w
    dt_values = [P/20, P/40, P/80, P/160, P/320]
    T = 8*P
    error_vs_dt = []
    for n, dt in enumerate(dt_values):
        u, v, t = solver_PEFRL(I, V, g, dt, T)
        error = np.abs(u - u_exact(t)).max()
        print 'error:', error
        if n > 0:
            error_vs_dt.append(error/dt**4)
    for i in range(1, len(error_vs_dt)):
        #print abs(error_vs_dt[i]- error_vs_dt[0])
        assert abs(error_vs_dt[i]-
                   error_vs_dt[0]) < 0.1

class PEFRL(odespy.Solver):
    """Class wrapper for Odespy."""  # Not used!
    quick_desctiption = "Explicit 4th-order method for v'=-f, u=v."""

    def advance(self):
        u, f, n, t = self.u, self.f, self.n, self.t
        dt = t[n+1] - t[n]
        I = np.array([u[1], u[3]])
        V = np.array([u[0], u[2]])
        u, v, t = solver_PFFRL(I, V, f, dt, t+dt)
        return np.array([v[-1], u[-1]])

def compute_orbit_and_error(
    f,
    solver_ID,
    timesteps_per_period=20,
    N_orbit_groups=1000,
    orbit_group_size=10):
    '''
    For one particular solver:
    Calculte the orbits for a multiple of grouped orbits, i.e.
    number of orbits = orbit_group_size*N_orbit_groups.
    Returns: time step dt, and, for each N_orbit_groups cycle,
    the 2D position error and cpu time (as lists).
    '''
    def u_exact(t):
        return np.array([np.cos(t), np.sin(t)])

    w = 1
    P = 2*np.pi/w       # scaled period (1 year becomes 2*pi)
    dt = P/timesteps_per_period
    Nt = orbit_group_size*N_orbit_groups*timesteps_per_period
    T = Nt*dt
    t_mesh = np.linspace(0, T, Nt+1)
    E_orbit = []

    #print '        dt:', dt
    T_interval = P*orbit_group_size
    N = int(round(T_interval/dt))

    # set initial conditions
    if solver_ID == 'EC':
        A = [0,1,1,0]
    elif solver_ID == 'PEFRL':
        I = np.array([1, 0])
        V = np.array([0, 1])
    else:
        A = [1,0,0,1]

    t1 = time.clock()
    for i in range(N_orbit_groups):
        time_points = np.linspace(i*T_interval, (i+1)*T_interval,N+1)
        u_e = u_exact(time_points).transpose()
        if solver_ID == 'EC':
            solver = odespy.EulerCromer(f)
            solver.set_initial_condition(A)
            ui, ti = solver.solve(time_points)
            # Find error (correct final pos:  x=1, y=0)
            orbit_error = np.sqrt(
                (ui[:,1]-u_e[:,0])**2 + (ui[:,3]-u_e[:,1])**2).max()
        elif solver_ID == 'PEFRL':
            # Note: every T_inverval is here counted from time 0
            ui, vi, ti = solver_PEFRL(I, V, f, dt, T_interval)
            # Find error (correct final pos:  x=1, y=0)
            orbit_error = np.sqrt(
                (ui[:,0]-u_e[:,0])**2 + (ui[:,1]-u_e[:,1])**2).max()
        else:
            solver = eval('odespy.' + solver_ID(f)
            solver.set_initial_condition(A)
            ui, ti = solver.solve(time_points)
            # Find error (correct final pos:  x=1, y=0)
            orbit_error = np.sqrt(
                (ui[:,0]-u_e[:,0])**2 + (ui[:,2]-u_e[:,1])**2).max()

        print '      Orbit no. %d,   max error (per cent): %g' % \
                           ((i+1)*orbit_group_size, orbit_error)

        E_orbit.append(orbit_error)

        # set init. cond. for next time interval
        if solver_ID == 'EC':
            A = [ui[-1,0], ui[-1,1], ui[-1,2], ui[-1,3]]
        elif solver_ID == 'PEFRL':
            I = [ui[-1,0], ui[-1,1]]
            V = [vi[-1,0], vi[-1,1]]
        else:  # RK4, adaptive rules, etc.
            A = [ui[-1,0], ui[-1,1], ui[-1,2], ui[-1,3]]

    t2 = time.clock()
    CPU_time = (t2 - t1)/(60.0*60.0)    # in hours
    return dt, E_orbit, CPU_time

def orbit_error_vs_dt(
    f_EC, f_RK4, g, solvers,
    N_orbit_groups=1000,
    orbit_group_size=10):
    '''
    With each solver in list "solvers": Simulate
    orbit_group_size*N_orbit_groups orbits with different dt values.
    Collect final 2D position error for each dt and plot all errors.
    '''

    for solver_ID in solvers:
        print 'Computing orbit with solver:', solver_ID
        E_values = []
        dt_values = []
        cpu_values = []
        for timesteps_per_period in 200, 400, 800, 1600:
            print '.......time steps per period: ', \
                                      timesteps_per_period
            if solver_ID == 'EC':
                dt, E, cpu_time = compute_orbit_and_error(
                    f_EC,
                    solver_ID,
                    timesteps_per_period,
                    N_orbit_groups,
                    orbit_group_size)
            elif solver_ID == 'PEFRL':
                dt, E, cpu_time = compute_orbit_and_error(
                    g,
                    solver_ID,
                    timesteps_per_period,
                    N_orbit_groups,
                    orbit_group_size)
            else:
                dt, E, cpu_time = compute_orbit_and_error(
                    f_RK4,
                    solver_ID,
                    timesteps_per_period,
                    N_orbit_groups,
                    orbit_group_size)

            dt_values.append(dt)
            E_values.append(np.array(E).max())
            cpu_values.append(cpu_time)
        print 'dt_values:', dt_values
        print 'E max with dt...:', E_values
        print 'cpu_values with dt...:', cpu_values

def orbit_error_vs_years(
    f_EC, f_RK4, g, solvers,
    N_orbit_groups=1000,
    orbit_group_size=100,
    N_time_steps = 1000):
    '''
    For each solver in the list solvers:
    simulate orbit_group_size*N_orbit_groups orbits with a fixed
    dt corresponding to N_time_steps steps per year.
    Collect max 2D position errors for each N_time_steps'th run,
    plot these errors and CPU. Finally, make an empirical
    formula for error and CPU as functions of a number
    of cycles.
    '''
    timesteps_per_period = N_time_steps     # fixed for all runs

    for solver_ID in solvers:
        print 'Computing orbit with solver:', solver_ID
        if solver_ID == 'EC':
            dt, E, cpu_time = compute_orbit_and_error(
                f_EC,
                solver_ID,
                timesteps_per_period,
                N_orbit_groups,
                orbit_group_size)
        elif solver_ID == 'PEFRL':
            dt, E, cpu_time = compute_orbit_and_error(
                g,
                solver_ID,
                timesteps_per_period,
                N_orbit_groups,
                orbit_group_size)
        else:
            dt, E, cpu_time = compute_orbit_and_error(
                f_RK4,
                solver_ID,
                timesteps_per_period,
                N_orbit_groups,
                orbit_group_size)

        # E and cpu_time are for every N_orbit_groups cycle
        print 'E_values (fixed dt, changing no of years):', E
        print 'CPU (hours):', cpu_time
        years = np.arange(
            0,
            N_orbit_groups*orbit_group_size,
            orbit_group_size)

        # Now make empirical formula

        def E_of_years(x, *coeff):
            return sum(coeff[i]*x**float((len(coeff)-1)-i) \
                       for i in range(len(coeff)))
        E =  np.array(E)
        degree = 4
        # note index: polyfit finds p[0]*x**4 + p[1]*x**3 ...etc.
        p = np.polyfit(years, E, degree)
        p_str = map(str, p)
        formula = ' + '.join([p_str[i] + '*x**' + \
                        str(degree-i) for i in range(degree+1)])

        print 'Empirical formula (error with years):  ', formula
        plt.figure()
        plt.plot(years,
                 E, 'b-',
                 years,
                 E_of_years(years, *p), 'r--')
        plt.xlabel('Number of years')
        plt.ylabel('Orbit error')
        plt.title(solver_ID)
        filename = solver_ID + 'tmp_E_with_years'
        plt.savefig(filename + '.png')
        plt.savefig(filename + '.pdf')
        plt.show()

        print 'Predicted CPU time in hours (1 billion years):', \
                                            cpu_time*10000
        print 'Predicted max error (1 billion years):', \
                                        E_of_years(1E9, *p)

def compute_orbit_error_and_CPU():
    '''
    Orbit error and associated CPU times are computed with
    solvers: RK4, Euler-Cromer, PEFRL.'''

    def f_EC(u, t):
        '''
        Return derivatives for the 1st order system as
        required by Euler-Cromer.
        '''
        vx, x, vy, y = u  # u: array holding vx, x, vy, y
        d = -(x**2 + y**2)**(-3.0/2)
        return [d*x, vx, d*y, vy ]

    def f_RK4(u, t):
        '''
        Return derivatives for the 1st order system as
        required by ordinary solvers in Odespy.
        '''
        x, vx, y, vy = u  # u: array holding x, vx, y, vy
        d = -(x**2 + y**2)**(-3.0/2)
        return [vx, d*x, vy, d*y ]

    def g(u, v):
        '''
        Return derivatives for the 1st order system as
        required by PEFRL.
        '''
        d = -(u[0]**2 + u[1]**2)**(-3.0/2)
        return np.array([d*u[0], d*u[1]])

    print 'Find orbit error as fu. of dt...(10000 orbits)'
    solvers = ['RK4', 'EC', 'PEFRL']
    N_orbit_groups=1
    orbit_group_size=10000
    orbit_error_vs_dt(
        f_EC, f_RK4, g, solvers,
        N_orbit_groups=N_orbit_groups,
        orbit_group_size=orbit_group_size)

    print 'Compute orbit error as fu. of no of years (fixed dt)...'
    solvers = ['PEFRL']
    N_orbit_groups=100
    orbit_group_size=1000
    N_time_steps = 1600    # no of steps per orbit cycle
    orbit_error_vs_years(
        f_EC, f_RK4, g, solvers,
        N_orbit_groups=N_orbit_groups,
        orbit_group_size=orbit_group_size,
        N_time_steps = N_time_steps)

if __name__ == '__main__':
    test_solver_PEFRL()
    compute_orbit_error_and_CPU()
```

The maximum error develops with number of orbits as seen in the following plot,
where the red dashed curve is from the mathematical model:

We note that the maximum error achieved during the first 100000 orbits is only
about $1.2e-06$ per cent. Not bad!

For the printed CPU and empirical formula, we get:

        CPU (hours): 1.51591388889
        Empirical formula (E with years):
        3.15992325978e-26*x**4 + -6.1772567063e-21*x**3 +
        1.87983349496e-16*x**2 + 2.32924158693e-11*x**1 +
        5.46989368301e-08*x**0

Since the CPU develops linearly, the CPU time for 100000 orbits can just be multiplied by 10000 to get the
estimated CPU time required for 1 billion years. This gives 15159 CPU hours (631 days), which is also printed.

With the derived empirical formula, the estimated orbit error after 1 billion years becomes 31593055529 per cent.

[sl 2: Can we really use the plot and the function to predict max E during 1 billion years? Seems hard.]

Filename: `vib_PEFRL`.

### Remarks

This exercise investigates whether it is feasible to predict
planetary motion for the life time of a solar system.
[hpl 3: Is it???]