```python
import matplotlib.pyplot as plt
import scipy.stats as st
import seaborn as sns
import pandas as pd
from scipy.stats import norm, uniform, expon,poisson
from scipy.integrate import quad
from sympy.solvers import solve
from sympy import Symbol
import numpy as np
from pandas import Series, DataFrame
```

```python
plt.subplot(2, 2, 1)
x = st.norm.rvs(size = 10)
st.probplot(x, plot = plt)
plt.title("n=10")
plt.subplot(2, 2, 2)
x = st.norm.rvs(size = 20)
st.probplot(x, plot = plt)
plt.title("n=20")
plt.subplot(2, 2, 3)
x = st.norm.rvs(size = 50)
st.probplot(x, plot = plt)
plt.title("n=50")
plt.subplot(2, 2, 4)
x = st.norm.rvs(size = 100)
st.probplot(x, plot = plt)
plt.title("n=100")
plt.tight_layout()
plt.show()

```

```python
for i in range(1,4):
    plt.subplot(1,3,i)
    x = st.t.rvs(size=20, df=20)
    st.probplot(x,plot=plt)
plt.title("n=20, df=20")
plt.tight_layout()
plt.show()
for i in range(1,4):
    plt.subplot(1,3,i)
    x = st.t.rvs(size=100, df=20)
    st.probplot(x,plot=plt)
plt.title("n=100, df=20")
plt.tight_layout()
plt.show()

```

```python
for i in range(1,4):
    plt.subplot(1,3,i)
    x = st.t.rvs(size=20, df=7)
    st.probplot(x,plot=plt)
    plt.title("n=20, df=7")
plt.tight_layout()
plt.show()
for i in range(1,4):
    plt.subplot(1,3,i)
    x = st.t.rvs(size=100, df=7)
    st.probplot(x,plot=plt)
    plt.title("n=100, df=7")
plt.tight_layout()
plt.show()

```

# 5.2

```python
plt.tight_layout(False)
# moegliche Werte von X
werte = np.array([0,10,11])
# X simulieren
sim = Series(np.random.choice(werte, size=1000, replace=True))
# Mehrere Grafiken neben- und untereinander
plt.subplot(4,2,1)
# Histogramm erstellen
sim.hist(bins=[0,1,10,11,12], edgecolor="black")
plt.title("Original")
plt.subplot(4,2,2)
# Normalplot erstellen
st.probplot(sim,plot=plt)
plt.title("Normal Q-Q Plot")

```

```python
n = 5
# X_1,...,X_n simulieren und in einer n-spaltigen
# Matrix (mit 1000 Zeilen) anordnen
sim = np.random.choice(werte, size=n*1000, replace=True)
sim = DataFrame(np.reshape(sim,(n,1000)))
#In jeder Matrixzeile Mittelwert berechnen
sim_mean = sim.mean()
sim_mean.hist(edgecolor="black")
plt.title("Mittelwerte von 5 Beobachtungen")
plt.show()

```

```python
n = 10
# X_1,...,X_n simulieren und in einer n-spaltigen
# Matrix (mit 1000 Zeilen) anordnen
sim = np.random.choice(werte, size=n*1000, replace=True)
sim = DataFrame(np.reshape(sim,(n,1000)))
#In jeder Matrixzeile Mittelwert berechnen
sim_mean = sim.mean()
sim_mean.hist(edgecolor="black")
plt.title("Mittelwerte von 5 Beobachtungen")
plt.show()
```

```python
n = 200
# X_1,...,X_n simulieren und in einer n-spaltigen
# Matrix (mit 1000 Zeilen) anordnen
sim = np.random.choice(werte, size=n*1000, replace=True)
sim = DataFrame(np.reshape(sim,(n,1000)))
#In jeder Matrixzeile Mittelwert berechnen
sim_mean = sim.mean()
sim_mean.hist(edgecolor="black", bins=20)
plt.title("Mittelwerte von 5 Beobachtungen")
plt.show()
```

```python
Series([1,10,11]).std()
```

    5.507570547286102

```python
Series(sim[0]).std()
```

    4.917344444098822

# 5.3

```python
iron = pd.read_csv("ironF3.dat",sep=" ",index_col=False)
```

```python
iron.head()
```

      high
      medium
      low

      0
      0.71
      2.20
      2.25

      1
      1.66
      2.93
      3.93

      2
      2.01
      3.08
      5.08

      3
      2.16
      3.49
      5.82

      4
      2.42
      4.11
      5.84

```python
iron.plot(kind="box")
plt.title(f"Boxplot")
plt.show()
```

```python
iron_log = np.log(iron)
iron_log.plot(kind="box")
plt.title(f"Boxplot")
plt.show()
```

```python
plt.subplot(121)
st.probplot(iron['medium'], plot=plt)
plt.subplot(122)
st.probplot(iron_log['medium'], plot=plt)
```

```python
iron['medium'].plot(kind="hist")
```

```python
iron['medium'].mean()
```

    8.203888888888889

```python
iron['medium'].std()
```

    5.447385923421693

```python
iron['medium'].var()
```

    29.67401339869281

```python
1-norm.cdf(x=10, loc=iron['medium'].mean(), scale=iron['medium'].std())
```

    0.37080583780045573

```python
iron_log['medium'].mean()
```

    1.901224840075921

```python
iron_log['medium'].std()
```

    0.6585116257598326

```python
iron_log['medium'].var()
```

    0.43363756126085784

```python
1-norm.cdf(x=np.log(10), loc=iron_log['medium'].mean(), scale=iron_log['medium'].std())
```

    0.2710978359464026

```python
1-expon.cdf(x=12, loc=0, scale=(8))
```

    0.2231301601484298

```python

```

```python
poisson.pmf(k=2, loc=0, mu=1.5)
```

    0.25102143016698353

```python
serieAbstaende = np.array([16.9 ,4.20 ,6.70,8.83 ,10.7 ,22.4 ,1.37 ,3.00 ,4.82 ,4.53 ,6.77 ,4.81])
```

```python
sorted_values = np.sort(serieAbstaende)
```

```python
y = expon.ppf(q=sorted_values, loc=0, scale=8)
```

```python
plt.plot(y, sorted_values)
```

```python
y = np.sort(np.array([16.9, 4.20, 6.70, 8.83, 10.7 ,22.4, 1.37,3.00, 4.82, 4.53, 6.77, 4.81]))
alphak = (np.arange(1,13)-0.5)/12
x = -np.log(1-alphak)
plt.plot(x,y,'o')
plt.xlabel("Theoretische Quantile")
plt.ylabel("Empirische Quantile")
```

```python
print(x)
xn = x[:,np.newaxis]
```

    [0.04255961 0.13353139 0.23361485 0.34484049 0.47000363 0.61310447
     0.78015856 0.98082925 1.23214368 1.56861592 2.07944154 3.17805383]

```python
print(xn)
```

    [[0.04255961]
     [0.13353139]
     [0.23361485]
     [0.34484049]
     [0.47000363]
     [0.61310447]
     [0.78015856]
     [0.98082925]
     [1.23214368]
     [1.56861592]
     [2.07944154]
     [3.17805383]]

```python
a, _, _, _ = np.linalg.lstsq(xn,y,rcond=None)
```

```python
print(a)
```

    [7.42075136]

```python
a, _, _, _ = np.linalg.lstsq(xn,y,rcond=None)
```

# 5.5

```python
variables = (uniform.rvs(size=1000, loc=0, scale=1/2))
arr = -np.log(variables)/2
```

```python
plt.hist(arr)
```

```python
from scipy.stats import uniform, expon
import numpy as np
import matplotlib.pyplot as plt
from pandas import Series
v = uniform.rvs(size=1000, loc=0, scale=1)
x = Series(-np.log(v)/2)
plt.subplot(121)
x.plot(kind="hist", edgecolor="black")
plt.title("Histogram of x")
y = Series(expon.rvs(size=1000, scale=1/2))
plt.subplot(122)
y.plot(kind="hist", edgecolor="black")
plt.title("Histogram of y")
```

```python
v = uniform.rvs(size=1000, loc=0, scale=1)
x = -np.log(v)/2
x = np.sort(x)
n = 1000
theor_quantilen = expon.ppf((np.linspace(1,n,n)-0.5)/n, scale=1/2)
y = theor_quantilen
plt.plot(x,y,"o")
plt.xlabel("Theoretische Quantile")
plt.ylabel("Empirische Quantile")
```

```python

```

```python

```


# Traffic flow with an on-ramp

In this chapter we return to the LWR traffic model that we investigated in [two earlier](Traffic_flow.ipynb) [chapters](Traffic_variable_speed.ipynb).  The LWR model involves a single length of one-way road; in this chapter we will think of this road as a highway.  On a real highway, there are cars entering and leaving the highway from other roads.  In general, real traffic flow must be modeled on a network of roads. the development of continuum traffic models based on LWR and other simple models is an important and very active area of research; see for instance (Holden, 1995) for an investigation of the Riemann problem at a junction, and (Garavello, 2006) for an overview of the area.  Here we take a first step in that direction by considering the presence of a single on-ramp, where traffic enters the highway.

Let the flux of cars from the on-ramp be denoted by $D$; we assume that $D$ is constant in time but concentrated at a single point ($x=0$ in space).  Our model equation then becomes

\begin{align} \label{TFR:balance_law}
    \rho_t + \left(\rho(1-\rho)\right)_x & = D \delta(x),
\end{align}

where $\delta(x)$ is the Dirac delta function.  Equation \eqref{TFR:balance_law} is our first example of a *balance law*.  The term on the right hand side does not take the form of a flux, and the total mass of cars is not conserved.  We refer to the right-hand-side term as a *source term* -- quite appropriately in the present context, since it represents a source of cars entering the highway.  In a more realistic model, traffic on the on-ramp itself would also be modeled.  However, our goal here is primarily to illustrate the effect of a source term like that in \eqref{TFR:balance_law} on the solution of the Riemann problem.

Typically, source terms have only an infinitesimal effect on the Riemann solution over short times, since they are distributed in space.  The term considered here is an example of a *singular* source term; it has a non-negligible effect on the Riemann solution because it is concentrated at $x=0$.

Recall that the flux of cars in the LWR model is given by

$$f(\rho) = \rho(1-\rho)$$

where $0 \le \rho \le 1$.  Thus the maximum flux is $f_\text{max} = 1/4$, achieved when $\rho=1/2$.  We assume always that $D \le 1/4$, so that all the cars arriving from the on-ramp can enter the highway.

As discussed already in the [chapter on traffic with a varying speed limit](Traffic_variable_speed.ipynb), the flux of cars must be continuous everywhere, and in particular at $x=0$.  Let $\rho^-, \rho^+$ denote the density $\rho$ in the limit as $\xi \to 0$ from the left and right, respectively. Then this condition means that

\begin{align} \label{TFR:source_balance}
    f(\rho^-) + D = f(\rho^+).
\end{align}

For $D\ne0$, this condition implies that a stationary jump exists at $x=0$, similar to the stationary jump we found in the case of a varying speed limit.

One approach to solving the Riemann problem is to focus on finding $\rho^-$ and $\rho^+$; the wave structure on either side of $x=0$ can then be deduced in the same way we have done for problems without a source term -- connecting $\rho_l$ to $\rho_-$ and $\rho_r$ to $\rho_+$ by entropy-satisfying shock or rarefaction waves.  This approach was undertaken by Greenberg et. al. in (Greenberg, 1997) for Burgers' equation; the main results (Table 1 therein) can be transferred to the LWR model in a straightforward way.  As they noted, there is typically more than one choice of $(\rho^+, \rho^-)$ that leads to an entropy-satisfying weak solution; some additional admissibility condition is required in order to choose one.  Herein we will motivate the choice of $\rho^+, \rho^-$ based on physical considerations; the resulting values agree with those of Greenberg et. al. (see also (Isaacson, 1992) for yet another approach that yields the same admissibility conditions).

## Spatially-varying fluxes and source terms
The similarity between the existence of an on-ramp at $x=0$ and a change in the speed limit at $x=0$ can be seen mathematically as follows.  For the varying speed limit, we studied a conservation law of the form

$$\rho_t + f(\rho,x)_x =0.$$

Using the chain rule, this is equivalent to

$$\rho_t + f_\rho(\rho,x) \rho_x = - f_x(\rho,x).$$

Hence the variable-coefficient system can also be viewed as a balance law.  If $f$ is discontinuous at $x=0$, then
$f_x$ is a delta function.  Notice that the presence of an on-ramp (positive source term) corresponds to a decrease in the speed limit.  This makes sense -- both of these have the effect of reducing the rate at which cars from upstream ($x<0$) can proceed downstream ($x>0$).  Thus the Riemann solutions we find in this chapter will be similar to those found in the presence of a decrease in speed limit.

In the remainder of the chapter, we investigate the solution of the Riemann problem for this balance law.

## Conditions for existence of a solution
In our model, cars entering from the on-ramp are always given priority.  In a real-world scenario, traffic on the on-ramp could also back up and the flux $D$ from the ramp could be decreased.  However, a much more complicated model would be required in order to account for this; see (delle Monache, 1992) for an example of such a model.

The flux $D$ from the on-ramp cannot raise the density above $\rho_\text{max}=1$ (representing bumper-to-bumper traffic).  This leads to some restrictions on $D$ in order to guarantee existence of a solution to the Riemann problem:

1. $D \le 1/4$.  This condition is necessary since otherwise the flux from the on-ramp would exceed the maximum flux of the highway, even without any other oncoming traffic.
2. If $\rho_r > 1/2$, then  $D \le f(\rho_r)$.  The reason for this is as follows: if $\rho_r > 1/2$, then characteristics to the right of $x=0$ go to the left.  Hence there cannot be any right-going wave (a more detailed analysis shows that a right-going transonic shock is impossible), and it must be that $\rho^+ = \rho_r$.  Thus $D = f(\rho_r) - f(\rho^-) \le f(\rho_r)$.

It turns out that these two conditions are also sufficient for the existence of a solution to the Riemann problem.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from clawpack import pyclaw
from clawpack import riemann
from ipywidgets import interact, widgets
from exact_solvers import traffic_ramps
from utils import riemann_tools
```

```python
def c(rho, xi):
    return (1.-2*rho)

def make_plot_function(rho_l,rho_r,D):
    states, speeds, reval, wave_types = traffic_ramps.exact_riemann_solution(rho_l,rho_r,D)
    def plot_function(t):
        ax = riemann_tools.plot_riemann(states,speeds,reval,wave_types,t=t,t_pointer=0,
                                        extra_axes=True,variable_names=['Density']);
        # Characteristic plotting isn't working right for this problem
        riemann_tools.plot_characteristics(reval,c,None,ax[0])
        traffic_ramps.phase_plane_plot(rho_l,rho_r,D,axes=ax[2])
        ax[1].set_ylim(0,1)

        plt.show()
    return plot_function

def riemann_solution(rho_l, rho_r, D):
    plot_function = make_plot_function(rho_l,rho_r,D)
    interact(plot_function, t=widgets.FloatSlider(value=0.1,min=0,max=.9));
```

## Light traffic, little inflow

What happens when the on-ramp has a relatively small flux of cars, and the highway around the ramp is not congested?  There will be a region of somewhat higher density starting at the ramp and propagating downstream.  This is demonstrated in the example below.

```python
rho_l = 0.2
rho_r = 0.2
D = 0.05

riemann_solution(rho_l, rho_r, D)
```

```python
traffic_ramps.plot_car_trajectories(rho_l,rho_r,D)
```

In contrast to the LWR model without a ramp, here we see three constant states separated by two waves in the Riemann solution.  The first is a stationary wave where the traffic density abruptly increases due to the cars entering from the ramp, as predicted by \eqref{TFR:source_balance}.  Indeed that condition determines the middle state $\rho_m$ as the solution of

$$f(\rho_l) + D = f(\rho_m)$$

For given values of $\rho_l$ and $D$, this is a quadratic equation for $\rho_m$, with solution

\begin{align} \label{TFR:qm1}
    \rho_m = \frac{1 \pm \sqrt{1-4(f(\rho_l)+D)}}{2}.
\end{align}

As in the case of varying speed limit, we can choose the physically relevant solution by applying the condition that the characteristic speed not change sign at $x=0$.  This dictates that we choose the minus sign, so that $\rho_m<1/2$, since $\rho_l < 1/2$.

Downstream, there is a rarefaction as these cars accelerate and again spread out.

The solution just proposed will break down if either of the following occur:

2. If the downstream density $\rho_r$ is greater than $\rho_m$, then a shock wave will form rather than a rarefaction.
1. If the combined flux from upstream and from the ramp exceeds $f_\text{max}$, there will be a shock wave moving upstream due to congestion at the mouth of the on-ramp.  This happens if $f(\rho_l) + D > 1/4$; notice that this is precisely the condition for the value of $\rho_m$ in \eqref{TFR:qm1} to become complex.

We consider each of these scenarios in the following sections.

## Uncongested upstream, congested downstream: transonic shock

What if upstream traffic and flux from the on-ramp are light, but traffic is significantly heavier just after the on-ramp?  In this case a shock wave will form, since if $\rho_r > \rho_l$, characteristics from the left and right regions must cross.  The shock may move to the left or right, depending on how congested the downstream segment is.  In either case, there will again be a stationary jump at $x=0$ due to the cars entering from the on-ramp.

```python
rho_l = 0.2
rho_r = 0.8
D = 0.05

riemann_solution(rho_l, rho_r, D)
```

```python
traffic_ramps.plot_car_trajectories(rho_l,rho_r,D,xmax=0.2)
```

The influx of cars from the ramp here causes a traffic jam that moves upstream.  As we discuss further below, some real highways limit the influx in order to prevent this.

Experiment with the value of $\rho_r$ and in the example above.  Can you give a precise condition that determines whether the shock will move left or right?

## Light traffic, heavy inflow

Now we come to the most interesting case.  Since the maximum flux is $1/4$, it follows that if $f(\rho_l) + D = 1/4$, then the oncoming traffic from the highway and the on-ramp can just fit onto the road at $x=0$.  The smaller value of $\rho_l$ for which this equation holds is $\rho^* = 1/4 - \sqrt{D}$.  If $\rho_l$ exceeds this value, then not all the cars arriving at $x=0$ can  fit on the road there; since our model gives priority to the cars coming from the on-ramp, the road to the left of $x=0$ must suffer a traffic jam -- a shock wave moving to the left.

As long as $\rho_r < 1/2$, the value of $\rho^+$ will be exactly $1/2$, so as to maximize the flux through $x=0$.  Downstream, a rarefaction will form as cars accelerate into the less-congested highway.

```python
rho_l = 0.2
rho_r = 0.2
D = 0.25

riemann_solution(rho_l, rho_r, D)
```

```python
traffic_ramps.plot_car_trajectories(rho_l,rho_r,D)
```

Notice that in the extreme case that $D=1/4$, the cars from the on-ramp completely block the cars coming from the left; those cars come to a complete stop and never pass $x=0$.  This may seem surprising, since the density of cars to the right of $x=0$ is just $1/2$.  However, since the flux must increase by $1/4$ at $x=0$, it follows that the flux just to the left of $x=0$ must be zero.

Counterintuitively, when two roads merge, limiting the influx of traffic from one or both of them can significantly increase the overall rate of traffic flow.  Contrary to our model, the usual approach is to prioritize the cars already on the highway and restrict the influx of cars from an on-ramp.  This is done in practice nowadays on many highway on-ramps in congested areas.

## Congested upstream, uncongested downstream

```python
rho_l = 0.6
rho_r = 0.2
D = 0.12

riemann_solution(rho_l, rho_r, D)
```

```python
traffic_ramps.plot_car_trajectories(rho_l,rho_r,D)
```

## Congested on both sides
Next let us consider what happens ff the incoming traffic from the upstream highway and the on-ramp exceeds the maximum flux, but the road is also congested for $x>0$ (i.e., $\rho_r>1/2$).  Then no waves can travel to the right, and a left-going shock will form.  If downstream congestion is too high, then the traffic from the on-ramp will not all be able to enter the highway and no solution is possible in this model (see the second condition for existence, above).

```python
rho_l = 0.6
rho_r = 0.8
D = 0.12

riemann_solution(rho_l, rho_r, D)
```

```python
traffic_ramps.plot_car_trajectories(rho_l,rho_r,D)
```

## Further examples

```python
rho_l = 0.1
rho_r = 0.6
D = 0.08
riemann_solution(rho_l, rho_r, D)
```

```python
traffic_ramps.plot_car_trajectories(rho_l,rho_r,D)
```

```python
rho_l = 1.0
rho_r = 0.7
D = 0.1

riemann_solution(rho_l, rho_r, D)
```

```python
traffic_ramps.plot_car_trajectories(rho_l,rho_r,D)
```

## Interactive solver

```python
f = lambda q: q*(1-q)

def plot_all(rho_l,rho_r,D):
    states, speeds, reval, wave_types = traffic_ramps.exact_riemann_solution(rho_l,rho_r,D)
    ax = riemann_tools.plot_riemann(states,speeds,reval,wave_types,t=0.5,extra_axes=2);
    riemann_tools.plot_characteristics(reval,c,None,ax[0])
    traffic_ramps.phase_plane_plot(rho_l,rho_r,D,axes=ax[2],show=False)
    traffic_ramps.plot_car_trajectories(rho_l,rho_r,D,axes=ax[3])

    plt.show()

interact(plot_all,
            rho_l = widgets.FloatSlider(min=0.,max=1.,step=0.01,value=0.4),
            rho_r = widgets.FloatSlider(min=0.,max=1.,step=0.01,value=0.7),
            D = widgets.FloatSlider(min=0.,max=0.25,step=0.01,value=0.1),
        );
```


```python
import IPython as ipy
ipy.display.IFrame('https://en.wikipedia.org/wiki/Jacobian_matrix_and_determinant#Examples',width=800,height=400)

```

# Jacobian Matrix
> ## spherical coordinates $\big(\rho, \phi, \theta \big)$
>> ### $
F: \mathbb{R}^{+} \times [0, \pi) \times [0,2\pi) \to \mathbb{R}^3
\begin{cases}
x = \rho \sin \phi \cos \theta \\
y = \rho \sin \phi \sin\theta \\
z = \rho \cos \phi \\
\end{cases} \\
J =
\begin{bmatrix}
\frac{\partial x}{\partial \rho} & \frac{\partial x}{\partial \rho} & \frac{\partial x}{\partial \theta}
\\
\frac{\partial y}{\partial \rho} & \frac{\partial y}{\partial \phi} & \frac{\partial y}{\partial \theta}
\\\frac{\partial z}{\partial \rho} & \frac{\partial z}{\partial \phi} & \frac{\partial z}{\partial \theta}
\end{bmatrix}$

>> ### $J =
\begin{bmatrix}
\frac{\partial f}{\partial x_{1}} \cdots \frac{\partial f}{\partial x_{n}}
\end{bmatrix}
=
\begin{bmatrix}
\nabla^{T}f_{1} \\ \vdots \\ \nabla^{T}f_{m}
\end{bmatrix}
=
\begin{bmatrix}
\frac{\partial f_{1}}{\partial x_{1}} & \cdots & \frac{\partial f_{1}}{\partial x_{n}} \\
\vdots & \ddots & \vdots  \\
\frac{\partial f_{m}}{\partial x_{1}} & \cdots & \frac{\partial f_{m}}{\partial x_{n}}
\end{bmatrix}
$
>> ## gradient at f(p)
$
\nabla f(p) =
\begin{bmatrix}
\frac{\partial f}{\partial x_{1}}(p) \\ \vdots \\ \frac{\partial f}{\partial x_{n}}(p)
\end{bmatrix}
$

```python
# Define function utilization
import sympy as sm
x,y = sm.symbols('x y', real=True)
#y = x**2
#y.subs({x:5})
```

$$ f = \begin{bmatrix} f_{1}(x,y) \\ f_{2}(x,y) \end{bmatrix}$$
$$ f:\mathbb{R}^{2} \to \mathbb{R}^{2} $$

```python
f1 = x**2 + y**2
f2 = x*y
f=sm.Matrix([f1,f2])
f.subs({x:1,y:3})
```

$\displaystyle \left[\begin{matrix}10\\3\end{matrix}\right]$

```python
j = sm.Matrix([[f1.diff(x),f1.diff(y)],[f2.diff(x),f2.diff(y)]])
j
```

$\displaystyle \left[\begin{matrix}2 x & 2 y\\y & x\end{matrix}\right]$

```python
a = sm.Matrix([sm.Function('f')(x,y)])
#a.jacobian?
a.jacobian([x,y])
```

$\displaystyle \left[\begin{matrix}\frac{\partial}{\partial x} f{\left(x,y \right)} & \frac{\partial}{\partial y} f{\left(x,y \right)}\end{matrix}\right]$

```python

```

```python

```


###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, C.D. Cooper, G.F. Forsyth.

# Riding the wave

## Numerical schemes for hyperbolic PDEs

Welcome back! This is the second notebook of *Riding the wave: Convection problems*, the third module of  ["Practical Numerical Methods with Python"](http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about).

The first notebook of this module discussed conservation laws and developed the non-linear traffic equation. We learned about the effect of the wave speed on the stability of the numerical method, and on the CFL number. We also realized that the forward-time/backward-space difference scheme really has many limitations: it cannot deal with wave speeds that move in more than one direction. It is also first-order accurate in space and time, which often is just not good enough. This notebook will introduce some new numerical schemes for conservation laws, continuing with the traffic-flow problem as motivation.

## Red light!

Let's explore the behavior of different numerical schemes for a moving shock wave. In the context of the traffic-flow model of the previous notebook, imagine a very busy road and a red light at $x=4$. Cars accumulate quickly in the front, where we have the maximum allowed density of cars between $x=3$ and $x=4$, and there is an incoming traffic of 50% the maximum allowed density $(\rho = 0.5\rho_{\rm max})$.

Mathematically, this is:

\begin{equation}\rho(x,0) = \left\{ \begin{array}{cc}
0.5 \rho_{\rm max} & 0 \leq x < 3  \\
\rho_{\rm max} & 3 \leq x \leq 4 \\ \end{array} \right.\end{equation}

Let's find out what the initial condition looks like.

```python
%matplotlib inline
import numpy
from matplotlib import pyplot
from matplotlib import rcParams
rcParams['font.family'] = 'serif'
rcParams['font.size'] = 16
```

```python
def rho_red_light(nx, rho_max, rho_in):
    """Computes "red light" initial condition with shock

    Parameters
    ----------
    nx        : int
        Number of grid points in x
    rho_max   : float
        Maximum allowed car density
    rho_in    : float
        Density of incoming cars

    Returns
    -------
    rho: array of floats
        Array with initial values of density
    """
    rho = rho_max*numpy.ones(nx)
    rho[:int((nx-1)*3./4.)] = rho_in
    return rho
```

```python
#Basic initial condition parameters
#defining grid size, time steps, CFL condition, etc...
nx = 81
nt = 30
dx = 4.0/(nx-1)

rho_in = 5.
rho_max = 10.

u_max = 1.

x = numpy.linspace(0,4,nx)

rho = rho_red_light(nx, rho_max, rho_in)
```

```python
pyplot.plot(x, rho, color='#003366', ls='-', lw=3)
pyplot.ylabel('Traffic density')
pyplot.xlabel('Distance')
pyplot.ylim(-0.5,11.);
```

The question we would like to answer is: **How will cars accumulate at the red light?**

We will solve this problem using different numerical schemes, to see how they perform. These schemes are:

   * Lax-Friedrichs
   * Lax-Wendroff
   * MacCormack

Before we do any coding, let's think about the equation a little bit. The wave speed $u_{\rm wave}$ is $-1$ for $\rho = \rho_{\rm max}$ and $\rho \leq \rho_{\rm max}/2$, making all velocities negative. We should see a solution moving left, maintaining the shock geometry.

#### Figure 1. The exact solution is a shock wave moving left.

Now to some coding! First, let's define some useful functions and prepare to make some nice animations later.

```python
def computeF(u_max, rho_max, rho):
    """Computes flux F=V*rho

    Parameters
    ----------
    u_max  : float
        Maximum allowed velocity
    rho    : array of floats
        Array with density of cars at every point x
    rho_max: float
        Maximum allowed car density

    Returns
    -------
    F : array
        Array with flux at every point x
    """
    return u_max*rho*(1-rho/rho_max)
```

```python
from matplotlib import animation
from IPython.display import HTML
```

```python
def animate(data):
    x = numpy.linspace(0,4,nx)
    y = data
    line.set_data(x,y)
    return line,
```

## Lax-Friedrichs scheme

Recall the conservation law for vehicle traffic, resulting in the following equation for the traffic density:

\begin{equation} \frac{\partial \rho}{\partial t} + \frac{\partial F}{\partial x} = 0 \end{equation}

$F$ is the *traffic flux*, which in the linear traffic-speed model is given by:

\begin{equation} F = \rho u_{\rm max} \left(1-\frac{\rho}{\rho_{\rm max}}\right)
\end{equation}

In the time variable, the natural choice for discretization is always a forward-difference formula; time invariably moves forward!

\begin{equation}\frac{\partial \rho}{\partial t}\approx \frac{1}{\Delta t}( \rho_i^{n+1}-\rho_i^n )\end{equation}

As is usual, the discrete locations on the 1D spatial grid are denoted by indices $i$ and the discrete time instants are denoted by indices $n$.

In a convection problem, using first-order discretization in space leads to excessive numerical diffusion (as you probably observed in [Lesson 1 of Module 2](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/02_spacetime/02_01_1DConvection.ipynb)). The simplest approach to get second-order accuracy in space is to use a central difference:

\begin{equation}\frac{\partial F}{\partial x} \approx \frac{1}{2\Delta x}( F_{i+1}-F_{i-1})\end{equation}

But combining these two choices for time and space discretization in the convection equation has catastrophic results! The "forward-time, central scheme" (FTCS) is **unstable**. (Go on: try it; you know you want to!)

The Lax-Friedrichs scheme was proposed by Lax (1954) as a clever trick to stabilize the forward-time, central scheme. The idea was to replace the solution value at $\rho^n_i$ by the average of the values at the neighboring grid points. If we do that replacement, we get the following discretized equation:

\begin{equation}\frac{\rho_i^{n+1}-\frac{1}{2}(\rho^n_{i+1}+\rho^n_{i-1})}{\Delta t} = -\frac{F^n_{i+1}-F^n_{i-1}}{2 \Delta x}\end{equation}

Take a careful look: the difference formula no longer uses the value at $\rho^n_i$ to obtain $\rho^{n+1}_i$. The stencil of the Lax-Friedrichs scheme is slightly different than that for the forward-time, central scheme.

#### Figure 2. Stencil of the forward-time/central scheme.

#### Figure 3. Stencil of the Lax-Friedrichs scheme.

This numerical discretization is **stable**. Unfortunately, substituting  $\rho^n_i$ by the average of its neighbors introduces a first-order error. _Nice try, Lax!_

To implement the scheme in code, we need to isolate the value at the next time step, $\rho^{n+1}_i$, so we can write a time-stepping loop:

\begin{equation}\rho_i^{n+1} = \frac{1}{2}(\rho^n_{i+1}+\rho^n_{i-1}) - \frac{\Delta t}{2 \Delta x}(F^n_{i+1}-F^n_{i-1})\end{equation}

The function below implements Lax-Friedrichs for our traffic model.  All the schemes in this notebook are wrapped in their own functions to help with displaying animations of the results.  This is also good practice for developing modular, reusable code.

In order to display animations, we're going to hold the results of each time step in the variable `rho`, a 2D array.  The resulting array `rho_n` has `nt` rows and `nx` columns.

```python
def laxfriedrichs(rho, nt, dt, dx, rho_max, u_max):
    """ Computes the solution with Lax-Friedrichs scheme

    Parameters
    ----------
    rho    : array of floats
            Density at current time-step
    nt     : int
            Number of time steps
    dt     : float
            Time-step size
    dx     : float
            Mesh spacing
    rho_max: float
            Maximum allowed car density
    u_max  : float
            Speed limit

    Returns
    -------
    rho_n : array of floats
            Density after nt time steps at every point x
    """

    #initialize our results array with dimensions nt by nx
    rho_n = numpy.zeros((nt,len(rho)))
    #copy the initial u array into each row of our new array
    rho_n[:,:] = rho.copy()

    '''
    Now, for each timestep, we're going to calculate rho^n+1,
    then set the value of rho equal to rho^n+1 so we can calculate
    the next iteration.  For every timestep, the entire vector
    rho^n is saved in a single row of our results array rho_n.
    '''
    for t in range(1,nt):
        F = computeF(u_max, rho_max, rho)
        rho_n[t,1:-1] = .5*(rho[2:]+rho[:-2]) - dt/(2*dx)*(F[2:]-F[:-2])
        rho_n[t,0] = rho[0] # boundary condition at x=0
        rho_n[t,-1] = rho[-1] # boundary condition at x=4
        rho = rho_n[t].copy()

    return rho_n
```

### Lax-Friedrichs with $\frac{\Delta t}{\Delta x}=1$

We are now all set to run! First, let's try with CFL=1

```python
sigma = 1.0
dt = sigma*dx/u_max

rho = rho_red_light(nx, rho_max, rho_in)      #make sure that u is set to our expected initial conditions
rho_n = laxfriedrichs(rho, nt, dt, dx, rho_max, u_max)
```

We can ask NumPy to tell us the shape of our new array `rho_n` and see if we have the dimensions we're expecting.  We should have `nt` rows and `nx` columns.

```python
rho_n.shape
```

    (30, 81)

Looks good!  Let's plot this now

```python
fig = pyplot.figure();
ax = pyplot.axes(xlim=(0,4),ylim=(4.5,11),xlabel=('Distance'),ylabel=('Traffic density'));
line, = ax.plot([],[],color='#003366', lw=2);
```

```python
anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
HTML(anim.to_html5_video())
```

##### Think

* What do you see in the animation above? How does the numerical solution compare with the exact solution (a left-traveling shock wave)?
* What types of errors do you think we see?
* What do you think of the Lax-Friedrichs scheme, so far?

### Lax-Friedrichs with $\frac{\Delta t}{\Delta x} = 0.5$

Would the solution improve if we use smaller time steps? Let's check that!

```python
sigma = 0.5
dt = sigma*dx/u_max

rho = rho_red_light(nx, rho_max, rho_in)      #make sure that u is set to our expected initial conditions
rho_n = laxfriedrichs(rho, nt, dt, dx, rho_max, u_max)
```

Now that we have our new data set, we can set up the next animation.  We'll use all the same code from above, except now our `animate` function is already defined, so we can just set up a figure as before, then call `FuncAnimation`.

```python
anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
HTML(anim.to_html5_video())
```

##### Dig deeper

Notice the strange "staircase" behavior on the leading edge of the wave?  You may be interested to learn more about this: a feature typical of what is sometimes called "odd-even decoupling." Last year we published a collection of lessons in Computational Fluid Dynamics, called _CFD Python_, where we discuss [odd-even decoupling](http://nbviewer.ipython.org/github/barbagroup/CFDPython/blob/master/lessons/19_Odd_Even_Decoupling.ipynb?create=1) .

* How does this solution compare with the previous one, where the Courant number was $\frac{\Delta t}{\Delta x}=1$?

## Lax-Wendroff scheme

The Lax-Friedrichs method uses a clever trick to stabilize the central difference in space for convection, but loses an order of accuracy in doing so. First-order methods are just not good enough for convection problems, especially when you have sharp gradients (shocks).

The Lax-Wendroff (1960) method was the _first_ scheme ever to achieve second-order accuracy in both space and time. It is therefore a landmark in the history of computational fluid dynamics.

To develop the Lax-Wendroff scheme, we need to do a bit of work. Sit down, grab a notebook and grit your teeth. We want you to follow this derivation in your own hand. It's good for you! Start with the Taylor series expansion (in the time variable) about $\rho^{n+1}$:

\begin{equation}\rho^{n+1} = \rho^n + \frac{\partial\rho^n}{\partial t} \Delta t + \frac{(\Delta t)^2}{2}\frac{\partial^2\rho^n}{\partial t^2} + \ldots \end{equation}

For the conservation law with $F=F(\rho)$, and using our beloved chain rule, we can write:

\begin{equation}\frac{\partial \rho}{\partial t} = -\frac{\partial F}{\partial x} = -\frac{\partial F}{\partial \rho} \frac{\partial \rho}{\partial x} = -J \frac{\partial \rho}{\partial x}\end{equation}

where

\begin{equation}J = \frac{\partial F}{\partial \rho} = u _{\rm max} \left(1-2\frac{\rho}{\rho_{\rm max}} \right)\end{equation}

is the _Jacobian_ for the traffic model. Next, we can do a little trickery:

\begin{equation} \frac{\partial F}{\partial t} = \frac{\partial F}{\partial \rho} \frac{\partial \rho}{\partial t} = J \frac{\partial \rho}{\partial t} = -J \frac{\partial F}{\partial x}\end{equation}

In the last step above, we used the differential equation of the traffic model to replace the time derivative by a spatial derivative. These equivalences imply that

\begin{equation}\frac{\partial^2\rho}{\partial t^2} = \frac{\partial}{\partial x} \left( J \frac{\partial F}{\partial x} \right) \end{equation}

Let's use all this in the Taylor expansion:

\begin{equation}\rho^{n+1} = \rho^n - \frac{\partial F^n}{\partial x} \Delta t + \frac{(\Delta t)^2}{2} \frac{\partial}{\partial x} \left(J\frac{\partial F^n}{\partial x} \right)+ \ldots \end{equation}

We can now reorganize this and discretize the spatial derivatives with central differences to get the following discrete equation:

\begin{equation}\frac{\rho_i^{n+1} - \rho_i^n}{\Delta t} = -\frac{F^n_{i+1}-F^n_{i-1}}{2 \Delta x} + \frac{\Delta t}{2} \left(\frac{(J \frac{\partial F}{\partial x})^n_{i+\frac{1}{2}}-(J \frac{\partial F}{\partial x})^n_{i-\frac{1}{2}}}{\Delta x}\right)\end{equation}

Now, approximate the rightmost term (inside the parenthesis) in the above equation as follows:
\begin{equation} \frac{J^n_{i+\frac{1}{2}}\left(\frac{F^n_{i+1}-F^n_{i}}{\Delta x}\right)-J^n_{i-\frac{1}{2}}\left(\frac{F^n_i-F^n_{i-1}}{\Delta x}\right)}{\Delta x}\end{equation}

Then evaluate the Jacobian at the midpoints by using averages of the points on either side:

\begin{equation}\frac{\frac{1}{2 \Delta x}(J^n_{i+1}+J^n_i)(F^n_{i+1}-F^n_i)-\frac{1}{2 \Delta x}(J^n_i+J^n_{i-1})(F^n_i-F^n_{i-1})}{\Delta x}.\end{equation}

Our equation now reads:

\begin{align}
&\frac{\rho_i^{n+1} - \rho_i^n}{\Delta t} =
-\frac{F^n_{i+1}-F^n_{i-1}}{2 \Delta x} + \cdots \\ \nonumber
&+ \frac{\Delta t}{4 \Delta x^2} \left( (J^n_{i+1}+J^n_i)(F^n_{i+1}-F^n_i)-(J^n_i+J^n_{i-1})(F^n_i-F^n_{i-1})\right)
\end{align}

Solving for $\rho_i^{n+1}$:

\begin{align}
&\rho_i^{n+1} = \rho_i^n - \frac{\Delta t}{2 \Delta x} \left(F^n_{i+1}-F^n_{i-1}\right) + \cdots \\ \nonumber
&+ \frac{(\Delta t)^2}{4(\Delta x)^2} \left[ (J^n_{i+1}+J^n_i)(F^n_{i+1}-F^n_i)-(J^n_i+J^n_{i-1})(F^n_i-F^n_{i-1})\right]
\end{align}

with

\begin{equation}J^n_i = \frac{\partial F}{\partial \rho} = u_{\rm max} \left(1-2\frac{\rho^n_i}{\rho_{\rm max}} \right).\end{equation}

Lax-Wendroff is a little bit long.  Remember that you can use \ slashes to split up a statement across several lines.  This can help make code easier to parse (and also easier to debug!).

```python
def Jacobian(u_max, rho_max, rho):
    return u_max*(1-2*rho/rho_max)

def laxwendroff(rho, nt, dt, dx, rho_max, u_max):
    """ Computes the solution with Lax-Wendroff scheme

    Parameters
    ----------
    rho    : array of floats
            Density at current time-step
    nt     : int
            Number of time steps
    dt     : float
            Time-step size
    dx     : float
            Mesh spacing
    rho_max: float
            Maximum allowed car density
    u_max  : float
            Speed limit

    Returns
    -------
    rho_n : array of floats
            Density after nt time steps at every point x
    """

    #initialize our results array with dimensions nt by nx
    rho_n = numpy.zeros((nt,len(rho)))
    #copy the initial u array into each row of our new array
    rho_n[:,:] = rho.copy()

    for t in range(1,nt):
        F = computeF(u_max, rho_max, rho)
        J = Jacobian(u_max, rho_max, rho)

        rho_n[t,1:-1] = rho[1:-1] - dt/(2*dx)*(F[2:]-F[:-2]) \
                           + dt**2/(4*dx**2) * ( (J[2:]+J[1:-1])*(F[2:]-F[1:-1]) \
                           - (J[1:-1]+J[:-2])*(F[1:-1]-F[:-2]) )

        rho_n[t,0] = rho[0]
        rho_n[t,-1] = rho[-1]
        rho = rho_n[t].copy()

    return rho_n
```

Now that's we've defined a function for the Lax-Wendroff scheme, we can use the same procedure as above to animate and view our results.

### Lax-Wendroff with $\frac{\Delta t}{\Delta x}=1$

```python
rho = rho_red_light(nx, rho_max, rho_in)
sigma = 1
dt = sigma*dx/u_max
rho_n = laxwendroff(rho, nt, dt, dx, rho_max, u_max)
```

```python
fig = pyplot.figure();
ax = pyplot.axes(xlim=(0,4),ylim=(4.5,11.),xlabel=('Distance'),ylabel=('Traffic density'));
line, = ax.plot([],[],color='#003366', lw=2);
```

```python
anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
HTML(anim.to_html5_video())
```

Interesting! The Lax-Wendroff method captures the sharpness of the shock much better than the Lax-Friedrichs scheme, but there is a new problem: a strange wiggle appears right at the tail of the shock. This is typical of many second-order methods: they introduce _numerical oscillations_ where the solution is not smooth. Bummer.

### Lax-Wendroff with $\frac{\Delta t}{\Delta x} =0.5$

How do the oscillations at the shock front vary with changes to the CFL condition? You might think that the solution will improve if you make the time step smaller ... let's see.

```python
rho = rho_red_light(nx, rho_max, rho_in)
sigma = 0.5
dt = sigma*dx/u_max
rho_n = laxwendroff(rho, nt, dt, dx, rho_max, u_max)

anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
HTML(anim.to_html5_video())
```

Eek! The numerical oscillations got worse. Double bummer!

Why do we observe oscillations with second-order methods? This is a question of fundamental importance!

## MacCormack Scheme

The numerical oscillations that you observed with the Lax-Wendroff method on the traffic model can become severe in some problems. But actually the main drawback of the Lax-Wendroff method is having to calculate the Jacobian in every time step. With more complicated equations (like the Euler equations), calculating the Jacobian is a large computational expense.

Robert W. MacCormack introduced the first version of his now-famous method at the 1969 AIAA Hypervelocity Impact Conference, held in Cincinnati, Ohio, but the paper did not at first catch the attention of the aeronautics community. The next year, however, he presented at the 2nd International Conference on Numerical Methods in Fluid Dynamics at Berkeley. His paper there (MacCormack, 1971) was a landslide. MacCormack got a promotion and continued to work on applications of his method to the compressible Navier-Stokes equations. In 1973, NASA gave him the prestigious H. Julian Allen award for his work.

The MacCormack scheme is a two-step method, in which the first step is called a _predictor_ and the second step is called a _corrector_. It acheives second-order accuracy in both space and time. One version is as follows:

\begin{equation}\rho^*_i = \rho^n_i - \frac{\Delta t}{\Delta x} (F^n_{i+1}-F^n_{i}) \ \ \ \ \ \ \text{(predictor)}\end{equation}

\begin{equation}\rho^{n+1}_i = \frac{1}{2} (\rho^n_i + \rho^*_i - \frac{\Delta t}{\Delta x} (F^*_i - F^{*}_{i-1})) \ \ \ \ \ \ \text{(corrector)}\end{equation}

If you look closely, it appears like the first step is a forward-time/forward-space scheme, and the second step is like a forward-time/backward-space scheme (these can also be reversed), averaged with the first result. What is so cool about this? You can compute problems with left-running waves and right-running waves, and the MacCormack scheme gives you a stable method (subject to the CFL condition). Nice! Let's try it.

```python
def maccormack(rho, nt, dt, dx, u_max, rho_max):
    """ Computes the solution with MacCormack scheme

    Parameters
    ----------
    rho    : array of floats
            Density at current time-step
    nt     : int
            Number of time steps
    dt     : float
            Time-step size
    dx     : float
            Mesh spacing
    rho_max: float
            Maximum allowed car density
    u_max  : float
            Speed limit

    Returns
    -------
    rho_n : array of floats
            Density after nt time steps at every point x
    """

    rho_n = numpy.zeros((nt,len(rho)))
    rho_star = numpy.empty_like(rho)
    rho_n[:,:] = rho.copy()
    rho_star = rho.copy()

    for t in range(1,nt):
        F = computeF(u_max, rho_max, rho)
        rho_star[:-1] = rho[:-1] - dt/dx * (F[1:]-F[:-1])
        Fstar = computeF(u_max, rho_max, rho_star)
        rho_n[t,1:] = .5 * (rho[1:]+rho_star[1:] - dt/dx * (Fstar[1:] - Fstar[:-1]))
        rho = rho_n[t].copy()

    return rho_n
```

### MacCormack with $\frac{\Delta t}{\Delta x} = 1$

```python
rho = rho_red_light(nx, rho_max, rho_in)
sigma = 1.0
dt = sigma*dx/u_max

rho_n = maccormack(rho,nt,dt,dx,u_max,rho_max)

anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
HTML(anim.to_html5_video())
```

### MacCormack with $\frac{\Delta t}{\Delta x}= 0.5$

Once again, we ask: how does the CFL number affect the errors? Which one gives better results? You just have to try it.

```python
rho = rho_red_light(nx, rho_max, rho_in)
sigma = 0.5
dt = sigma*dx/u_max

rho_n = maccormack(rho,nt,dt,dx,u_max,rho_max)

anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
HTML(anim.to_html5_video())
```

##### Dig Deeper

You can also obtain a MacCormack scheme by reversing the predictor and corrector steps. For shocks, the best resolution will occur when the difference in the predictor step is in the direction of propagation. Try it out! Was our choice here the ideal one? In which case is the shock better resolved?

##### Challenge task

In the *red light* problem, $\rho \geq \rho_{\rm max}/2$, making the wave speed negative at all points . You might be wondering why we introduced these new methods; couldn't we have just used a forward-time/forward-space scheme? But, what if $\rho_{\rm in} < \rho_{\rm max}/2$? Now, a whole region has negative wave speeds and forward-time/backward-space is unstable.

* How do Lax-Friedrichs, Lax-Wendroff and MacCormack behave in this case? Try it out!

* As you decrease $\rho_{\rm in}$, what happens to the velocity of the shock? Why do you think that happens?

## References

* Peter D. Lax (1954), "Weak solutions of nonlinear hyperbolic equations and their numerical computation," _Commun. Pure and Appl. Math._, Vol. 7, pp. 159–193.

* Peter D. Lax and Burton Wendroff (1960), "Systems of conservation laws,"  _Commun. Pure and Appl. Math._, Vol. 13, pp. 217–237.

* R. W. MacCormack (1969), "The effect of viscosity in hypervelocity impact cratering," AIAA paper 69-354. Reprinted on _Journal of Spacecraft and Rockets_, Vol. 40, pp. 757–763 (2003). Also on _Frontiers of Computational Fluid Dynamics_, edited by D. A. Caughey, M. M. Hafez (2002), chapter 2: [read on Google Books](http://books.google.com/books?id=QBsnMOz_8qcC&lpg=PA27&ots=uqCeuH1U6S&lr&pg=PA27#v=onepage&q&f=false).

* R. W. MacCormack (1971), "Numerical solution of the interaction of a shock wave with a laminar boundary layer," _Proceedings of the 2nd Int. Conf. on Numerical Methods in Fluid Dynamics_, Lecture Notes in Physics, Vol. 8, Springer, Berlin, pp. 151–163.

---

###### The cell below loads the style of the notebook.

```python
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, "r").read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Source+Code+Pro' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
    margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}

.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
    font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
    font-family: "Source Code Pro";
    font-size: 90%;
}

.alert-box {
    padding:10px 10px 10px 36px;
    margin:5px;
}

.success {
    color:#666600;
    background:rgb(240,242,229);
}
</style>


# Nomoto model first order PIT

```python
# %load imports.py
"""
These is the standard setup for the notebooks.
"""

%matplotlib inline
%load_ext autoreload
%autoreload 2

from jupyterthemes import jtplot
jtplot.style(theme='onedork', context='notebook', ticks=True, grid=False)

import pandas as pd
pd.options.display.max_rows = 999
pd.options.display.max_columns = 999
pd.set_option("display.max_columns", None)
import numpy as np
import os
import matplotlib.pyplot as plt
#plt.style.use('paper')

import copy

import numpy as np
import os

from src.data import database
from mdldb import mdl_to_evaluation
from mdldb.tables import Run
import src.data
import os.path

from sklearn.pipeline import Pipeline

import sympy as sp
from sklearn.metrics import r2_score
import src.reporting.paper_writing as paper_writing

from src.equations import equations
from src.equations import symbols
from rolldecayestimators.substitute_dynamic_symbols import lambdify
```

```python
from sympy.physics.vector.printing import vpprint, vlatex
from IPython.display import display, Math, Latex
from pandas_profiling import ProfileReport
import evaluation.evaluation_helpers as evaluation_helpers
from scipy.optimize import least_squares
```

## Nomotos equation:

```python
Math(vlatex(equations.nomoto_first_order))
```

```python
db = database.get_db()
```

```python
sql="""
SELECT * from run
INNER JOIN projects
ON run.project_number==projects.project_number
    INNER JOIN loading_conditions
    ON (run.loading_condition_id == loading_conditions.id)
        INNER JOIN models
        ON run.model_number == models.model_number
            INNER JOIN ships
            ON models.ship_name == ships.name

WHERE run.test_type=="spiral"
"""
data = pd.read_sql_query(sql=sql, con=db.engine)
data = data.loc[:,~data.columns.duplicated()]
```

```python
data.describe()
```

```python
#profile = ProfileReport(statistics, title='Pandas Profiling Report')
#profile.to_widgets()
```

```python
loading_conditions = data.groupby(by=['loading_condition_id','ship_speed'])
```

```python
loading_conditions.describe()
```

```python
loading_condition = loading_conditions.get_group(name=(3,19))
#loading_condition = loading_conditions.get_group(name=(144,16))
```

```python
loading_condition.describe()
```

### Load all data for one loading condition

```python
df_all = pd.DataFrame()
interesting_columns = ['delta','x0','y0','z0','phi','theta','psi']
for index, run in loading_condition.iterrows():
    db_run = db.session.query(Run).get(int(run.id))
    df = database.load_run(db_run=db_run)
    df['t'] = df.index

    df_=evaluation_helpers.coord(df=df)  # add psi and position etc.

    df = pd.concat((df,df_), axis=1)

    df['run_id'] = run.id

    df_all = df_all.append(df[['t','run_id'] + interesting_columns], ignore_index=True)
```

```python
df_all.describe()
```

```python
fig,ax=plt.subplots()
runs = df_all.groupby(by='run_id')
for run_id, df in runs:
    df['x0']-=df.iloc[0]['x0']
    df['y0']-=df.iloc[0]['y0']

    df.plot(x='y0',y='x0', ax=ax)

ax.get_legend().remove()
ax.set_aspect('equal', 'box')
```

```python
def derivate(group):
    df = group.set_index('t')

    ddf = np.gradient(df, df.index, axis=0).mean(axis=0)
    s = pd.Series(ddf, index=df.columns)

    return s
```

```python
df = runs.mean()

ddf = runs.apply(func= derivate)

df['u']=ddf['x0']
df['v']=ddf['y0']
df['w']=ddf['z0']
df['p']=ddf['phi']
df['q']=ddf['theta']
df['r']=ddf['psi']
df.sort_values(by='r', inplace=True)
```

```python
fig,ax=plt.subplots()
df.plot(x='delta', y='r', ax=ax, style='o')
ax.grid(True)
ax.set_title('Reverse spiral plot')
```

```python
print(loading_condition.iloc[0]['project_path'])
```

```python
spiral_eq = sp.simplify(equations.nomoto_first_order.subs(symbols.r_1d,0))
Math(vlatex(spiral_eq))
```

```python
r_lambda=lambdify(sp.solve(spiral_eq,symbols.r)[0])
```

```python
def residual(parameters, X, ys):
    r = r_lambda(*parameters,delta=X['delta'])

    error = r - ys
    return error

initial_guess = [-1,]

kwargs={
    'X':df,
    'ys':df['r'],
}

result = least_squares(fun=residual, x0=initial_guess, kwargs=kwargs, method='lm')
parameters={
    'K':result.x,
}
```

```python
r_predict = r_lambda(**parameters,delta=df['delta'])

fig,ax=plt.subplots()
df.plot(x='delta', y='r', ax=ax, style='o')
ax.plot(df['delta'],r_predict, 'b-')
ax.grid(True)
ax.set_title('Reverse spiral plot');
```

```python
K_3 = sp.symbols('K_3')
delta_0 = sp.symbols('delta_0')

spiral_eq_3 = sp.Eq(symbols.delta,
      sp.solve(spiral_eq,symbols.delta)[0] + symbols.r**5/K_3 + delta_0)

Math(vlatex(spiral_eq_3))
```

```python
A, A_3 = sp.symbols('A A_3')
spiral_eq_3_A = spiral_eq_3.subs([(symbols.K,1/A),
                  (K_3,1/A_3),
                 ])
```

```python
delta_lambda_3=lambdify(sp.solve(spiral_eq_3_A,symbols.delta)[0])
```

```python
delta_lambda_3
```

```python
np.random.seed()

def residual_3(parameters, X, ys):
    delta = delta_lambda_3(*parameters,r=X['r'])
    error = (delta - ys)**2
    return error

initial_guess = [-0.1,-1000,0]

kwargs={
    'X':df,
    'ys':df['delta'],
}

bounds = ([-np.inf,-np.inf],
          [0,0,np.inf])

result = least_squares(fun=residual_3, x0=initial_guess, kwargs=kwargs, max_nfev=1000,
                       loss='linear', f_scale=0.1, method='lm')
parameters_3={
    'A':result.x[0],
    'A_3':result.x[1],
    'delta_0':result.x[2],
}
```

```python
result
```

```python
parameters_3
```

```python
N=100
r=np.linspace(df['r'].min(),df['r'].max(),N)
delta_predict = delta_lambda_3(**parameters_3,r=r)

fig,ax=plt.subplots()
df.plot(x='delta', y='r', ax=ax, style='o')
ax.plot(delta_predict,r, 'b-')
ax.grid(True)
ax.set_title('Reverse spiral plot');
```

```python
from scipy import polyval, polyfit
df['r**5'] = df['r']**5
X = df[['r','r**5']].copy()
X['1']=1.0
x, residuals, rank, s = np.linalg.lstsq(X, df['delta'], rcond=None)
parameters_4 = {
    'A':x[0],
    'A_3':x[1],
    'delta_0':x[2],
}

delta_predict = delta_lambda_3(**parameters_4,r=r)

fig,ax=plt.subplots()
df.plot(x='delta', y='r', ax=ax, style='o')
ax.plot(delta_predict,r, 'b-')
ax.grid(True)
ax.set_title('Reverse spiral plot');
```

```python

```