```python
import tensorflow as tf
#import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np
import seaborn as sns
import collections

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit
from PIL import Image
```

```python

```

```python

```

```python
def convert_to_circuit(image):
    values = np.ndarray.flatten(image)
    qubits = cirq.GridQubit.rect(4, 4)
    circuit = cirq.Circuit()
    for i, value in enumerate(values):
        if value:
            circuit.append(cirq.X(qubits[i]))
    return circuit
```

```python
image=np.random.randint(255,size=(2,2))
#image=np.ones(255,size=(2,2))
plt.imshow(image,cmap='gray',vmin=0,vmax=255)
plt.show()
plt.imsave('grayimg.jpeg',image,cmap='gray')
```

```python
FILENAME='grayimg.jpeg'
image2 = Image.open(FILENAME)
pixels=np.asarray(image2)
pixels=pixels.astype('float32')
pixels2=pixels/255.0
print(pixels2)
THRESHOLD = 0.5
image_new_bin=np.array(pixels2 > THRESHOLD, dtype=np.float32)
image_new_bin
image_circuit=convert_to_circuit(image_new_bin)
```

```python
SVGCircuit(image_circuit)
```


```
# Alexander Hebert
# ECE 6390
# Computer Project #3
```

```
# Tested using Python v3.4 and IPython v2
```

```
# Import libraries
```

```
import numpy as np
```

```
import scipy
```

```
import sympy
```

```
import itertools
```

```
from MatrixSignFunction import msf
```

```
from IPython.display import display
```

```
from sympy.interactive import printing
```

```
np.set_printoptions(linewidth=200,
                    formatter={'all':lambda x: format(x,'10.5f')})
```

```
#np.set_printoptions(suppress=True)
```

```
# Original system:
```

```
A = np.loadtxt('A.txt')
print(A)
```

    [[   0.00000    1.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000]
     [   0.00000   -0.11323   -0.98109  -11.84700  -11.84700  -63.08000  -34.33900  -34.33900  -27.64500    0.00000]
     [ 324.12100   -1.17550  -29.10100    0.12722    2.83448 -967.73000 -678.14000 -678.14000    0.00000 -129.29000]
     [-127.30000    0.46167   11.42940   -1.03790   13.12370  380.07900  266.34100  266.34100    0.00000 1054.85000]
     [-186.05000    0.67475   16.70450    0.86092  -17.06800  555.50200  389.26800  389.26800    0.00000 -874.92000]
     [ 341.91700    1.09173 1052.75000  756.46500  756.46500  -29.77400    0.16507    3.27626    0.00000    0.00000]
     [ -30.74800   -0.09817  -94.67400  -68.02900  -68.02900    2.67753   -2.65580    4.88497    0.00000    0.00000]
     [-302.36000   -0.96543 -930.96000 -668.95000 -668.95000   26.32920    2.42028   -9.56030    0.00000    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000   -1.66670    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000  -10.00000]]

```
n,nc = A.shape
```

```
B = np.loadtxt('B.txt')
print(B)
```

    [[   0.00000    0.00000]
     [   0.00000    0.00000]
     [   0.00000    0.00000]
     [   0.00000    0.00000]
     [   0.00000    0.00000]
     [   0.00000    0.00000]
     [   0.00000    0.00000]
     [   0.00000    0.00000]
     [   1.66667    0.00000]
     [   0.00000   10.00000]]

```
nr,m = B.shape
```

```
C = np.loadtxt('C.txt')
print(C)
```

    [[   1.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000]
     [  -0.49134    0.00000   -0.63203    0.00000    0.00000   -0.20743    0.00000    0.00000    0.00000    0.00000]]

```
D = np.zeros((2,2))
print(D)
```

    [[   0.00000    0.00000]
     [   0.00000    0.00000]]

```
# Compute eigenvalues/poles of A to determine system stability:
```

```
A_eigvals, M = np.linalg.eig(A)
```

```
# Sort eigenvalues in descending order
idx = A_eigvals.argsort()[::-1]
A_eigvals = A_eigvals[idx]
print(A_eigvals)
```

    [-0.23448+0.00000j -0.34915+6.34401j -0.34915-6.34401j -1.04205+0.00000j -1.66670+0.00000j -10.00000+0.00000j -10.74614+0.00000j -17.66419+0.00000j -29.46253+313.93671j -29.46253-313.93671j]

```
# All poles are stable.
```

```
# Sort eigenvectors
M = M[:,idx]
```

```
# Mean of eigenvalues
gamma = sum(np.real(A_eigvals)) / n
print(gamma)
```

    -10.097693

```
A_hat = A - gamma * np.eye(n)
print(A_hat)
```

    [[  10.09769    1.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000]
     [   0.00000    9.98446   -0.98109  -11.84700  -11.84700  -63.08000  -34.33900  -34.33900  -27.64500    0.00000]
     [ 324.12100   -1.17550  -19.00331    0.12722    2.83448 -967.73000 -678.14000 -678.14000    0.00000 -129.29000]
     [-127.30000    0.46167   11.42940    9.05979   13.12370  380.07900  266.34100  266.34100    0.00000 1054.85000]
     [-186.05000    0.67475   16.70450    0.86092   -6.97031  555.50200  389.26800  389.26800    0.00000 -874.92000]
     [ 341.91700    1.09173 1052.75000  756.46500  756.46500  -19.67631    0.16507    3.27626    0.00000    0.00000]
     [ -30.74800   -0.09817  -94.67400  -68.02900  -68.02900    2.67753    7.44189    4.88497    0.00000    0.00000]
     [-302.36000   -0.96543 -930.96000 -668.95000 -668.95000   26.32920    2.42028    0.53739    0.00000    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    8.43099    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.09769]]

```
A_hat_eigvals, M_hat = np.linalg.eig(A_hat)
```

```
idx2 = A_hat_eigvals.argsort()[::-1]
A_hat_eigvals = A_hat_eigvals[idx2]
print(A_hat_eigvals)
```

    [9.86321+0.00000j 9.74855+6.34401j 9.74855-6.34401j 9.05564+0.00000j 8.43099+0.00000j 0.09769+0.00000j -0.64845+0.00000j -7.56650+0.00000j -19.36484+313.93671j -19.36484-313.93671j]

```
# There are six dominant (unstable) eigenvalues of A_hat.
# Therefore, the top six eigenvalues are selected
# for the reduced order model.
```

```
# Compute sign(A_hat)
eps = 1.0e-7
maxiter = 100
sign_A_hat, jp1, flag = msf(A_hat,eps,maxiter)
print('j = %d' %jp1)
print('flag = %d' %flag)
```

    j = 17
    flag = 1

```
print(sign_A_hat)
```

    [[   1.03870   -0.00336    0.02278    0.01414    0.03112   -0.11007   -0.06049   -0.11721   -0.00972    2.85405]
     [  -0.54195    1.03876   -1.01065   -0.68724   -0.98331    0.85785    0.40622    1.01189    0.10912  -43.80039]
     [  -1.84661   -0.00187   -5.01676   -4.33066   -4.03548   -0.13851   -0.24001   -0.13494    0.00303   32.96137]
     [   1.52237   -0.04730    5.23084    4.63410    5.25086    0.17573    0.29052    0.17014   -0.09360  189.82583]
     [   0.20288    0.05275    0.36461    0.40344   -0.63261   -0.05607   -0.07666   -0.05317    0.09759 -237.34365]
     [   1.95411    0.03022    0.40569    0.43978    0.41876   -4.12085   -3.74582   -3.14521    0.09592  -11.81757]
     [  -0.96414    0.07184    0.00472    0.01879   -0.00164    3.21693    2.86791    3.24639    0.21814  -23.72170]
     [  -0.77019   -0.11709   -0.39984   -0.45331   -0.40735    1.18904    1.45797    0.19074   -0.35967   40.52374]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    1.00000    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    1.00000]]

```
sign_pos_A_hat = 0.5*(sign_A_hat + np.identity(n))
print(sign_pos_A_hat)
```

    [[   1.01935   -0.00168    0.01139    0.00707    0.01556   -0.05504   -0.03024   -0.05861   -0.00486    1.42703]
     [  -0.27098    1.01938   -0.50533   -0.34362   -0.49165    0.42893    0.20311    0.50595    0.05456  -21.90020]
     [  -0.92330   -0.00094   -2.00838   -2.16533   -2.01774   -0.06925   -0.12000   -0.06747    0.00152   16.48068]
     [   0.76119   -0.02365    2.61542    2.81705    2.62543    0.08787    0.14526    0.08507   -0.04680   94.91292]
     [   0.10144    0.02637    0.18230    0.20172    0.18369   -0.02803   -0.03833   -0.02658    0.04879 -118.67183]
     [   0.97705    0.01511    0.20284    0.21989    0.20938   -1.56042   -1.87291   -1.57261    0.04796   -5.90878]
     [  -0.48207    0.03592    0.00236    0.00940   -0.00082    1.60846    1.93396    1.62320    0.10907  -11.86085]
     [  -0.38509   -0.05854   -0.19992   -0.22665   -0.20368    0.59452    0.72898    0.59537   -0.17983   20.26187]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    1.00000    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    1.00000]]

```
np.trace(sign_pos_A_hat)
```

    6.0

```
# Select eigenvectors from sign_pos_A_hat
m_hat_1 = np.concatenate((sign_pos_A_hat[:,0:4],
                          sign_pos_A_hat[:,8:]), 1)
```

```
sign_neg_A_hat = 0.5*(np.identity(n) - sign_A_hat)
print(sign_neg_A_hat)
```

    [[  -0.01935    0.00168   -0.01139   -0.00707   -0.01556    0.05504    0.03024    0.05861    0.00486   -1.42703]
     [   0.27098   -0.01938    0.50533    0.34362    0.49165   -0.42893   -0.20311   -0.50595   -0.05456   21.90020]
     [   0.92330    0.00094    3.00838    2.16533    2.01774    0.06925    0.12000    0.06747   -0.00152  -16.48068]
     [  -0.76119    0.02365   -2.61542   -1.81705   -2.62543   -0.08787   -0.14526   -0.08507    0.04680  -94.91292]
     [  -0.10144   -0.02637   -0.18230   -0.20172    0.81631    0.02803    0.03833    0.02658   -0.04879  118.67183]
     [  -0.97705   -0.01511   -0.20284   -0.21989   -0.20938    2.56042    1.87291    1.57261   -0.04796    5.90878]
     [   0.48207   -0.03592   -0.00236   -0.00940    0.00082   -1.60846   -0.93396   -1.62320   -0.10907   11.86085]
     [   0.38509    0.05854    0.19992    0.22665    0.20368   -0.59452   -0.72898    0.40463    0.17983  -20.26187]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000]]

```
np.trace(sign_neg_A_hat)
```

    4.0

```
# Select eigenvectors from sign_neg_A_hat
m_hat_2 = sign_neg_A_hat[:,0:4]
```

```
M_msf = np.concatenate((m_hat_1, m_hat_2), 1)
print(M_msf)
```

    [[   1.01935   -0.00168    0.01139    0.00707   -0.00486    1.42703   -0.01935    0.00168   -0.01139   -0.00707]
     [  -0.27098    1.01938   -0.50533   -0.34362    0.05456  -21.90020    0.27098   -0.01938    0.50533    0.34362]
     [  -0.92330   -0.00094   -2.00838   -2.16533    0.00152   16.48068    0.92330    0.00094    3.00838    2.16533]
     [   0.76119   -0.02365    2.61542    2.81705   -0.04680   94.91292   -0.76119    0.02365   -2.61542   -1.81705]
     [   0.10144    0.02637    0.18230    0.20172    0.04879 -118.67183   -0.10144   -0.02637   -0.18230   -0.20172]
     [   0.97705    0.01511    0.20284    0.21989    0.04796   -5.90878   -0.97705   -0.01511   -0.20284   -0.21989]
     [  -0.48207    0.03592    0.00236    0.00940    0.10907  -11.86085    0.48207   -0.03592   -0.00236   -0.00940]
     [  -0.38509   -0.05854   -0.19992   -0.22665   -0.17983   20.26187    0.38509    0.05854    0.19992    0.22665]
     [   0.00000    0.00000    0.00000    0.00000    1.00000    0.00000    0.00000    0.00000    0.00000    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    1.00000    0.00000    0.00000    0.00000    0.00000]]

```
# Check rank of M_hat
np.linalg.matrix_rank(M_msf)
```

    10

```
M_msf_inv = np.linalg.inv(M_msf)
print(M_msf_inv)
```

    [[   1.00000   -0.00000    0.00000    0.00000    0.00745   -3.00473   -3.62961   -3.02771   -0.00000    0.00000]
     [  -0.00000    1.00000   -0.00000   -0.00000   -0.12208  121.36333  147.73967  122.24146    0.00000   -0.00000]
     [  -0.00000    0.00000    1.00000   -0.00000    0.24684  662.69532  808.38183  667.09694    0.00000   -0.00000]
     [   0.00000   -0.00000    0.00000    1.00000    0.69977 -613.40105 -748.24942 -617.47503   -0.00000    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    0.00000    1.00000    0.00000]
     [  -0.00000   -0.00000   -0.00000   -0.00000    0.00000   -0.00000   -0.00000    0.00000    0.00000    1.00000]
     [   1.00000   -0.00000    0.00000    0.00000    1.60527   -2.81461   -2.16547   -1.36052    0.05304  174.32474]
     [   0.00000    1.00000   -0.00000   -0.00000   26.72206    7.23488   -3.55224   29.13826    3.92205 2603.27644]
     [   0.00000   -0.00000    1.00000   -0.00000   17.67588    1.85031   -0.78878    7.94046    0.56126 1921.83776]
     [  -0.00000    0.00000    0.00000    1.00000  -24.32204   -1.34169    2.07621  -10.43331   -0.80479 -2753.15778]]

```
# Diagonalize A using modal matrix from above
Ad = M_msf_inv.dot(A).dot(M_msf)
print(Ad)
```

    [[  -1.69221    1.00404   -0.79535   -0.65912   -0.00000   -6.52054   -0.00000    0.00000   -0.00000   -0.00000]
     [  15.27267   -0.21879  -26.78675  -28.84512  -27.64500  106.81115    0.00000   -0.00000    0.00000    0.00000]
     [ 305.43843   -0.91883   54.21425   58.25123    0.00000 -345.25166    0.00000   -0.00000    0.00000    0.00000]
     [-282.81502    0.85008  -50.51501  -54.27808    0.00000  442.60752   -0.00000   -0.00000   -0.00000   -0.00000]
     [   0.00000    0.00000    0.00000    0.00000   -1.66670    0.00000    0.00000    0.00000    0.00000    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000  -10.00000   -0.00000    0.00000   -0.00000   -0.00000]
     [  -0.00000    0.00000    0.00000    0.00000    0.00000   -0.00000 -783.07297    0.53643 -1464.66219 -1070.33745]
     [  -0.00000    0.00000    0.00000    0.00000    0.00000   -0.00000 -11198.93019   -1.96622 -18728.33307 -13766.29216]
     [   0.00000    0.00000   -0.00000   -0.00000    0.00000   -0.00000 -4708.44985    5.18282 -5103.49654 -3843.07312]
     [  -0.00000   -0.00000    0.00000    0.00000   -0.00000    0.00000 7029.84495   -7.54558 7709.10727 5801.20032]]

```
Bd = M_msf_inv.dot(B)
print(Bd)
```

    [[  -0.00000    0.00000]
     [   0.00000   -0.00000]
     [   0.00000   -0.00000]
     [  -0.00000    0.00000]
     [   1.66667    0.00000]
     [   0.00000   10.00000]
     [   0.08841 1743.24737]
     [   6.53676 26032.76440]
     [   0.93543 19218.37757]
     [  -1.34132 -27531.57780]]

```
Cd = C.dot(M_msf)
print(Cd)
```

    [[   1.01935   -0.00168    0.01139    0.00707   -0.00486    1.42703   -0.01935    0.00168   -0.01139   -0.00707]
     [  -0.11996   -0.00172    1.22169    1.31947   -0.00852   -9.89178   -0.37138    0.00172   -1.85372   -1.31947]]

```
# Block decoupling
```

```
Lambda1 = Ad[0:6,0:6]
Lambda2 = Ad[6:,6:]

print(Lambda1)
```

    [[  -1.69221    1.00404   -0.79535   -0.65912   -0.00000   -6.52054]
     [  15.27267   -0.21879  -26.78675  -28.84512  -27.64500  106.81115]
     [ 305.43843   -0.91883   54.21425   58.25123    0.00000 -345.25166]
     [-282.81502    0.85008  -50.51501  -54.27808    0.00000  442.60752]
     [   0.00000    0.00000    0.00000    0.00000   -1.66670    0.00000]
     [   0.00000    0.00000    0.00000    0.00000    0.00000  -10.00000]]

```
Bd1 = Bd[0:6,:]
print(Bd1)
```

    [[  -0.00000    0.00000]
     [   0.00000   -0.00000]
     [   0.00000   -0.00000]
     [  -0.00000    0.00000]
     [   1.66667    0.00000]
     [   0.00000   10.00000]]

```
Bd2 = Bd[6:,:]
```

```
Cd1 = Cd[:,0:6]
print(Cd1)
```

    [[   1.01935   -0.00168    0.01139    0.00707   -0.00486    1.42703]
     [  -0.11996   -0.00172    1.22169    1.31947   -0.00852   -9.89178]]

```
Cd2 = Cd[:,6:]
```

```
Dstar = (C.dot(np.linalg.inv(-1*A)).dot(B) + D
              - Cd1.dot(np.linalg.inv(-1*Lambda1)).dot(Bd1))
```

```
print(Dstar)
```

    [[   0.00073   -0.98193]
     [   0.00155    5.11005]]

```
np.savetxt("Lambda1.txt",Lambda1,fmt=list(itertools.repeat('%.18e',6)))
np.savetxt("Bd1.txt",Bd1,fmt=list(itertools.repeat('%.18e',2)))
np.savetxt("Cd1.txt",Cd1,fmt=list(itertools.repeat('%.18e',6)))
np.savetxt("Dstar.txt",Dstar,fmt=list(itertools.repeat('%.18e',2)))
```

```
L1_eigvals, M_L1 = np.linalg.eig(Lambda1)
np.savetxt("Lambda1_eigvals.txt",L1_eigvals.reshape((6,1)),
           fmt=list(itertools.repeat('%.18e%+.18ej',1)))
print(L1_eigvals)
```

    [-0.34915+6.34401j -0.34915-6.34401j -1.04205+0.00000j -0.23448+0.00000j -10.00000+0.00000j -1.66670+0.00000j]

```

```


 INF285 - Computación Científica

 Roots of 1D equations

  [S]cientific [C]omputing [T]eam

 Version: 1.34

## Table of Contents
* [Introduction](#intro)
* [Bisection Method](#bisection)
* [Fixed Point Iteration and Cobweb diagram](#fpi)
* [FPI - example from etxtbook](#fpi-textbook-example)
* [Newton Method](#nm)
* [Wilkinson Polynomial](#wilkinson)
* [Acknowledgements](#acknowledgements)
* [Extra Examples](#extraexamples)

```python
import numpy as np
import matplotlib.pyplot as plt
import sympy as sym
%matplotlib inline
from ipywidgets import interact
from ipywidgets import widgets
sym.init_printing()
from scipy import optimize
```

# Introduction
[Back to TOC](#toc)

Hello again! In this document we're going to learn how to find a 1D equation's solution using numerical methods. First, let's start with the definition of a root:

Definition: The function $f(x)$ has a root in $x = r$ if $f(r) = 0$.

An example: Let's say we want to solve the equation $x + \log(x) = 3$. We can rearrange the equation: $x + \log(x) - 3 = 0$. That way, to find its solution we can find the root of $f(x) = x + \log(x) - 3$. Now let's study some numerical methods to solve these kinds of problems.

Defining a function $f(x)$

```python
f = lambda x: x+np.log(x)-3
```

Finding $r$ using sympy

```python
y = sym.Symbol('y')
fsym = lambda y: y+sym.log(y)-3
r_all=sym.solve(sym.Eq(fsym(y), 0), y)
r=r_all[0].evalf()
print(r)
print(r_all)
```

    2.20794003156932
    [LambertW(exp(3))]

```python
def find_root_manually(r=2.0):
    x = np.linspace(1,3,1000)
    plt.figure(figsize=(8,8))
    plt.plot(x,f(x),'b-')
    plt.grid()
    plt.ylabel('$f(x)$',fontsize=16)
    plt.xlabel('$x$',fontsize=16)
    plt.title('What is r such that $f(r)='+str(f(r))+'$? $r='+str(r)+'$',fontsize=16)
    plt.plot(r,f(r),'k.',markersize=20)
    plt.show()
interact(find_root_manually,r=(1e-5,3,1e-3))
```

    interactive(children=(FloatSlider(value=2.0, description='r', max=3.0, min=1e-05, step=0.001), Output()), _dom…

    <function __main__.find_root_manually(r=2.0)>

# Bisection Method
[Back to TOC](#toc)

The bisection method finds the root of a function $f$, where $f$ is a **continuous** function.
If we want to know if this has a root, we have to check if there is an interval $[a,b]$ for which
$f(a)\cdot f(b) < 0$. When these 2 conditions are satisfied, it means that there is a value $r$, between $a$ and $b$, for which $f(r) = 0$. To summarize how this method works, start with the aforementioned interval (checking that there's a root in it), and split it into two smaller intervals $[a,c]$ and $[c,b]$. Then, check which of the two intervals contains a root. Keep splitting each "eligible" interval until the algorithm converges or the tolerance is surpassed.

```python
def bisect(f, a, b, tol=1e-5, maxNumberIterations=100):
    fa = f(a)
    fb = f(b)
    i = 0
    # Just checking if the sign is not negative => not root  necessarily
    if np.sign(f(a)*f(b)) >= 0:
        print('f(a)f(b)<0 not satisfied!')
        return None

    #Printing the evolution of the computation of the root
    print(' i |     a     |     c     |     b     |     fa    |     fc     |     fb     |   b-a')
    print('----------------------------------------------------------------------------------------')

    while ((b-a)/2 > tol) and i<=maxNumberIterations:
        c = (a+b)/2.
        fc = f(c)
        print('%2d | %.7f | %.7f | %.7f | %.7f | %.7f | %.7f | %.7f' %
              (i+1, a, c, b, fa, fc, fb, b-a))
        # Did we find the root?
        if fc == 0:
            print('f(c)==0')
            break
        elif np.sign(fa*fc) < 0:
            b = c
            fb = fc
        else:
            a = c
            fa = fc
        i += 1

    xc = (a+b)/2.
    return xc
```

```python
# Initial example
f1 = lambda x: x+np.log(x)-3
# A different function, notice that x is multiplied to teh exponential now and not added, as before.
f2 = lambda x: x*np.exp(x)-3
# This is the introductory example about Fixed Point Iteration
f3 = lambda x: np.cos(x)-x
bisect(f1,0.5,3)
```

```python
# Initial example
f1 = lambda x: x+np.log(x)-3
# A different function, notice that x is multiplied to teh exponential now and not added, as before.
f2 = lambda x: x*np.exp(x)-3
# This is the introductory example about Fixed Point Iteration
f3 = lambda x: np.cos(x)-x
bisect(f2,0.5,3,tol=1e-5)
```

It's very important to define a concept called **convergence rate**.
This rate shows how fast the convergence of a method is at a specified point.

The convergence rate for the bisection is always 0.5 because this method uses the half of the interval for each iteration.

# Fixed Point Iteration and Cobweb diagram
[Back to TOC](#toc)

To learn about the Fixed-Point Iteration we will first learn about the concept of Fixed Point.

A Fixed Point of a function $g$ is a real number $r$, where $g(r) = r$

The Fixed-Point Iteration is based in the Fixed Point concept and works like this to find the root of a function:

\begin{align*}
    x_{0} &= initial\_guess \\
    x_{i+1} &= g(x_{i})
\end{align*}

To find an equation's solution using this method you'll have to move around some things to rearrange the equation in the form $x = g(x)$. That way, you'll be iterating over the funcion $g(x)$, but you will **not** find $g$'s root, but $f(x) = g(x) - x$ (or $f(x) = x - g(x)$)'s root. In our following example, we'll find the solution to $f(x) = x - \cos(x)$ by iterating over the funcion $g(x) = \cos(x)$.

```python
def cobweb(x,g=None):
    min_x = np.amin(x)
    max_x = np.amax(x)

    plt.figure(figsize=(10,10))
    ax = plt.axes()
    plt.plot(np.array([min_x,max_x]),np.array([min_x,max_x]),'b-')
    for i in np.arange(x.size-1):
        delta_x = x[i+1]-x[i]
        head_length =  np.abs(delta_x)*0.04
        arrow_length = delta_x-np.sign(delta_x)*head_length
        ax.arrow(x[i], x[i], 0, arrow_length, head_width=1.5*head_length, head_length=head_length, fc='k', ec='k')
        ax.arrow(x[i], x[i+1], arrow_length, 0, head_width=1.5*head_length, head_length=head_length, fc='k', ec='k')

    if g!=None:
        y = np.linspace(min_x,max_x,1000)
        plt.plot(y,g(y),'r')

    plt.title('Cobweb diagram')
    plt.grid(True)
    plt.show()

def fpi(g, x0, k, flag_cobweb=False):
    x = np.empty(k+1)
    x[0] = x0
    error_i = np.inf

    print(' i |     x_i      |    x_{i+1}   ||x_{i+1}-x_i| | e_{i+1}/e_i | e_{i+1}/e_i^2')
    print('-----------------------------------------------------------------------------')

    for i in range(k):
        x[i+1] = g(x[i])
        error_iminus1 = error_i
        error_i = abs(x[i+1]-x[i])
        print('%2d | %.10f | %.10f | %.10f | %.10f | %.10f' %
              (i,x[i],x[i+1],error_i,error_i/error_iminus1,error_i/(error_iminus1**2)))
    if flag_cobweb:
        cobweb(x,g)
    return x[-1]
```

```python
g = lambda x: np.cos(x)
fpi(g, 2, 20, True)
```

Let's quickly explain the Cobweb Diagram we have here. The blue line is the function $x$ and the red is the function $g(x)$. The point in which they meet is $g$'s fixed point. In this particular example, we start at $y = x = 1.5$ (the top right corner) and then we "jump" **vertically** to $y = \cos(1.5) \approx 0.07$. After this, we jump **horizontally** to $x = \cos(1.5) \approx 0.07$. Then, we jump again **vertically** to $y = \cos\left(\cos(1.5)\right) \approx 0.997$ and so on. See the pattern here? We're just iterating over $x = \cos(x)$, getting closer to the center of the diagram where the fixed point resides, in $x \approx 0.739$.

It's very important to mention that the algorithm will converge only if the rate of convergence $S < 1$, where $S = \left| g'(r) \right|$. If you want to use this method, you'll have to construct $g(x)$ starting from $f(x)$ accordingly. In this example, $g(x) = \cos(x) \Rightarrow g'(x) = -\sin(x)$ and $|-\sin(0.739)| \approx 0.67$.

### Another example. Look at this web page to undertand the context: https://divisbyzero.com/2008/12/18/sharkovskys-theorem/amp/?__twitter_impression=true

```python
# Consider this funtion
g = lambda x: -(3/2)*x**2+(11/2)*x-2
# Here we compute the derivative of it.
gp = lambda x: -3*x+11/2

# We plot now the funcion itself (red), its derivative (magenta) and the function y=x (blue).
# We also plot the values -1 and 1 with green dashed curves.
# This analyis shows that the fixed point, which is the intersection between teh red and blue curves,
# does not generate a convergent fix-point-iteration since the derivative (magenta curve) has a value
# lower then -1 about the fized point.
x=np.linspace(2,3,100)
plt.figure(figsize=(8,8))
plt.plot(x,g(x),'r-',label=r'$g(x)$')
plt.plot(x,x,'b-')
plt.plot(x,gp(x),'m-')
plt.plot(x,gp(x)*0+1,'g--')
plt.plot(x,gp(x)*0-1,'g--')
plt.grid(True)
plt.show()
```

What it is interesting about the previous example is that it generates an interesting limit cicle! In the next cell we evaluate the fixed point with initial guess equal to 1. The iteration oscilates generating the following sequence: 1, 2, 3, 1, 2, 3, .... Which is nice!

```python
fpi(g, 1, 12, True)
```

However, we prefer convergent fixed-point-iterations! Here is interesting way to make a non-convergent FPI into a convergent one.

```python
# This is an "avocado" hidden in the code!
a=-1/2.7
g2 = lambda x: x+a*(x-g(x))
fpi(g2, 1, 14, True)
```

# FPI - example from textbook
[Back to TOC](#toc)

This example is from the textbook. We are trying to find a root of $f(x)=x^3+x-1$.

```python
# These are the three functions proposed.
g1 = lambda x: 1-x**3
g2 = lambda x: (1-x)**(1/3)
g3 = lambda x: (1+2*x**3)/(1+3*x**2)
# Change the input function to evaluate different functions.
# Are the three functions convergent fixed point iterations?
fpi(g3, 0.75, 10, True)
```

```python
# This is a "hack" to improve the convergence of g2!
a  = -0.6
g4 = lambda x: x+a*(x-g2(x))
fpi(g4, 0.75, 10, True)
# Why does this hack works?
```

Now that we found the root, let's compute the derivative of each $g(x)$ used previously and understand what exactly was going on.

```python
g1p = lambda x: -3*x**2
g2p = lambda x: -(1/3)*(1-x)**(-2/3)
g3p = lambda x: ((1+3*x**2)*(6*x**2)-(1+2*x**3)*6*x)/((1+3*x**2)**2)
g4p = lambda x: 1+a*(1-g2p(x))
r=0.6823278038280194
print('What is the conclusion then?')
print([g1p(r), g2p(r), g3p(r), g4p(r)])
```

    What is the conclusion then?
    [-1.3967136956303043, -0.7159663452349291, 0.0, -0.029579807140957426]

```python
# Or it may be better to apply the absolute value.
print(np.abs([g1p(r), g2p(r), g3p(r), g4p(r)]))
```

    [1.3967137  0.71596635 0.         0.02957981]

# Newton's Method
[Back to TOC](#toc)

The Newton's method also finds a root of a function $f(x)$ but it requires its derivative, i.e. $f'(x)$.
The algorithm is as follows:
\begin{align*}
    x_0 &= \text{initial guess},\\
    x_{i+1} &= x_i - \dfrac{f(x_i)}{f'(x_i)}.
\end{align*}
For roots with multiplicity equal to 1, Newton's method convergens quadratically. However, when the multiplicity is larger that 1, it will show linear convergence. Fortunately, we can modify Newton's method if we know the multiplicity of the root, say $m$, this is as follows:
\begin{align*}
    x_0 &= \text{initial guess},\\
    x_{i+1} &= x_i - m\,\dfrac{f(x_i)}{f'(x_i)}.
\end{align*}
This modified version will also show quadratic convergence!

```python
def newton_method(f, fp, x0, rel_error=1e-8, m=1, maxNumberIterations=100):
    #Initialization of hybrid error and absolute
    hybrid_error = 100
    error_i = np.inf

    print('i |     x_i      |     x_{i+1}  |     |x_{i+1}-x_i|      |  e_{i+1}/e_i | e_{i+1}/e_i^2')
    print('----------------------------------------------------------------------------------------')

    #Iteration counter
    i = 1
    while (hybrid_error > rel_error and hybrid_error < 1e12 and i<=maxNumberIterations):
        #Newton's iteration
        x1 = x0-m*f(x0)/fp(x0)

        #Checking if root was found
        if f(x1) == 0.0:
            hybrid_error = 0.0
            break

        #Computation of hybrid error
        hybrid_error = abs(x1-x0)/np.max([abs(x1),1e-12])

        #Computation of absolute error
        error_iminus1 = error_i
        error_i = abs(x1-x0)

        #Increasing counter
        i += 1

        #Showing some info
        print("%d | %.10f | %.10f | %.20f | %.10f | %.10f" %
              (i, x0, x1, error_i, error_i/error_iminus1, error_i/(error_iminus1**2)))
        #Updating solution
        x0 = x1

    #Checking if solution was obtained
    if hybrid_error < rel_error:
        return x1
    elif i>=maxNumberIterations:
        print('Newton''s Method did not converged. Too many iterations!!')
        return None
    else:
        print('Newton''s Method did not converged!')
        return None
```

First example, let's compute a root of $\sin(x)$, near $x_0=3.1$.

```python
# Example funtion
f = lambda x: np.sin(x)
# The derivative of f
fp = lambda x: np.cos(x)
newton_method(f, fp, 3.1,rel_error=1e-15)
```

Now, we will look at the example when Newton's method shows linear convergence.

```python
f = lambda x: x**2
fp = lambda x: 2*x # the derivative of f
newton_method(f, fp, 3.1, rel_error=1e-1, m=3, maxNumberIterations=10)
```

    i |     x_i      |     x_{i+1}  |     |x_{i+1}-x_i|      |  e_{i+1}/e_i | e_{i+1}/e_i^2
    ----------------------------------------------------------------------------------------
    2 | 3.1000000000 | -1.5500000000 | 4.65000000000000035527 | 0.0000000000 | 0.0000000000
    3 | -1.5500000000 | 0.7750000000 | 2.32500000000000017764 | 0.5000000000 | 0.1075268817
    4 | 0.7750000000 | -0.3875000000 | 1.16249999999999986677 | 0.5000000000 | 0.2150537634
    5 | -0.3875000000 | 0.1937500000 | 0.58124999999999993339 | 0.5000000000 | 0.4301075269
    6 | 0.1937500000 | -0.0968750000 | 0.29062499999999996669 | 0.5000000000 | 0.8602150538
    7 | -0.0968750000 | 0.0484375000 | 0.14531249999999998335 | 0.5000000000 | 1.7204301075
    8 | 0.0484375000 | -0.0242187500 | 0.07265624999999999167 | 0.5000000000 | 3.4408602151
    9 | -0.0242187500 | 0.0121093750 | 0.03632812499999999584 | 0.5000000000 | 6.8817204301
    10 | 0.0121093750 | -0.0060546875 | 0.01816406249999999792 | 0.5000000000 | 13.7634408602
    11 | -0.0060546875 | 0.0030273437 | 0.00908203124999999896 | 0.5000000000 | 27.5268817204
    Newtons Method did not converged. Too many iterations!!

e_{i+1}/e_i=S=(m-1)/m -> e_{i+1}/e_i=(m-1)/m -> 0.5=(m-1)/m -> m=2

```python
newton_method(f, fp, 3.1, rel_error=1e-1, m=2, maxNumberIterations=10)
```

# Wilkinson Polynomial
[Back to TOC](#toc)

https://en.wikipedia.org/wiki/Wilkinson%27s_polynomial

**Final question: Why is the root far far away from $16$?**

```python
x = sym.symbols('x', reals=True)
W=1
for i in np.arange(1,21):
        W*=(x-i)
W # Printing W nicely
```

```python
# Expanding the Wilkinson polynomial
We=sym.expand(W)
We
```

```python
# Just computiong the derivative
Wep=sym.diff(We,x)
Wep
```

```python
# Lamdifying the polynomial to be used with sympy
P=sym.lambdify(x,We)
Pp=sym.lambdify(x,Wep)
```

```python
# Using scipy function to compute a root
root = optimize.newton(P,16)
print(root)
```

```python
newton_method(P, Pp, 16.01, rel_error=1e-10, maxNumberIterations=10)
```

    i |     x_i      |     x_{i+1}  |     |x_{i+1}-x_i|      |  e_{i+1}/e_i | e_{i+1}/e_i^2
    ----------------------------------------------------------------------------------------
    2 | 16.0100000000 | 16.0425006915 | 0.03250069153185108917 | 0.0000000000 | 0.0000000000
    3 | 16.0425006915 | 16.0050204647 | 0.03748022678450269041 | 1.1532132093 | 35.4827283637
    4 | 16.0050204647 | 16.0078597186 | 0.00283925384213290499 | 0.0757533795 | 2.0211558458
    5 | 16.0078597186 | 15.9851271041 | 0.02273261449981944793 | 8.0065452981 | 2819.9469801818
    6 | 15.9851271041 | 16.0029892675 | 0.01786216337692181355 | 0.7857505074 | 34.5648982597
    7 | 16.0029892675 | 16.0136315293 | 0.01064226179553884322 | 0.5957991522 | 33.3553746866
    8 | 16.0136315293 | 16.0001980846 | 0.01343344470737761753 | 1.2622734683 | 118.6095110764
    9 | 16.0001980846 | 16.0178167527 | 0.01761866814365831146 | 1.3115525115 | 97.6333725295
    10 | 16.0178167527 | 16.0287977529 | 0.01098100018750258755 | 0.6232593802 | 35.3749429376
    11 | 16.0287977529 | 16.0248062796 | 0.00399147331818383577 | 0.3634890493 | 33.1016340099
    Newtons Method did not converged. Too many iterations!!

# Acknowledgements
[Back to TOC](#toc)

* _Material created by professor Claudio Torres_ (`ctorres@inf.utfsm.cl`) _and assistants: Laura Bermeo, Alvaro Salinas, Axel Simonsen and Martín Villanueva. DI UTFSM. March 2016._ v1.1.
* _Update April 2020 - v1.32 - C.Torres_ : Re-ordering the notebook.
* _Update April 2021 - v1.33 - C.Torres_ : Updating format and re-re-ordering the notebook. Adding 'maxNumberIterations' to bisection, fpi and Newton's method. Adding more explanations.
* _Update April 2021 - v1.33 - C.Torres_ : Updating description and solution of 'Proposed classwork'.

# Extra examples
[Back to TOC](#toc)

## Propose Classwork
1. Build a FPI such that given $a$ computes $\displaystyle \frac{1}{a}$. The constraint is that you can't use a division in the 'final' FPI. Write down your solution below or go and see the [solution](#sol1).

_Hint: I strongly suggest to use Newton's method._

```python
print('Please try to solve it before you see the solution!!!')
```

    Please try to solve it before you see the solution!!!

2. Build an algorithm that computes $\log(x_i)$ for $x_i=0.1*i+0.5$, for $i\in{0,1,2,\dots,10}$. The only special function available is $\exp(x)$, in particular use _np.exp(x)_. You can also use $*$, $÷$, $+$, and $-$. It would be nice to use the result from previous example to replace $÷$.

# In class

Which function shows quadratic convergence? Why?

```python
g1 = lambda x: (4./5.)*x+1./x
g2 = lambda x: x/2.+5./(2*x)
g3 = lambda x: (x+5.)/(x+1)
fpi(g1, 3.0, 10, True)
```

### Building a FPI to compute the cubic root of 7

```python
# What is 'a'? Can we find another 'a'?
a = -3*(1.7**2)
print(a)
```

    -8.669999999999998

```python
f = lambda x: x**3-7
g =  lambda x: f(x)/a+x
r=fpi(g, 1.7, 14, True)
print(f(r))
```

### Playing with some roots

The following example proposed a particular function $f(x)$.
The idea here is first obtain an initial guess for applying the Newton's method from the plot in semilogy scale.
The plot of $f(x)$ (blue) shows that there seems to be 2 roots in the interval plotted.
Now, the plot of $f'(x)$ (magenta) indicates that the derivative may also have a 0 together with a root, this means that the multiplicity of that root may be higher than 1. How can we find out that?

```python
f = lambda x: 8*x**4-12*x**3+6*x**2-x
fp = lambda x: 32*x**3-36*x**2+12*x-1

x = np.linspace(-1,1,10000)
plt.figure(figsize=(10,10))

plt.title('What are we seeing with the semiloigy plot? Is this function differentiable?')
plt.semilogy(x,np.abs(f(x)),'b-',label=r'$|f(x)|$')
plt.semilogy(x,np.abs(fp(x)),'m-',label=r'$|fp(x)|$')
plt.grid()
plt.legend()
plt.xlabel(r'$x$',fontsize=16)
plt.show()
```

```python
r=newton_method(f, fp, 0.4, rel_error=1e-8, m=1)
print([r,f(r)])
# Is this showing quadratic convergence? If not, can you fix it?
```

    i |     x_i      |     x_{i+1}  |     |x_{i+1}-x_i|      |  e_{i+1}/e_i | e_{i+1}/e_i^2
    ----------------------------------------------------------------------------------------
    2 | 0.4000000000 | 0.4363636364 | 0.03636363636363626473 | 0.0000000000 | 0.0000000000
    3 | 0.4363636364 | 0.4586595886 | 0.02229595222295999157 | 0.6131386861 | 16.8613138686
    4 | 0.4586595886 | 0.4728665654 | 0.01420697676575599644 | 0.6371998210 | 28.5791705436
    5 | 0.4728665654 | 0.4820874099 | 0.00922084451765831092 | 0.6490363622 | 45.6843403691
    6 | 0.4820874099 | 0.4881331524 | 0.00604574254164202962 | 0.6556603932 | 71.1063278311
    7 | 0.4881331524 | 0.4921210847 | 0.00398793230801725018 | 0.6596265522 | 109.1059613639
    8 | 0.4921210847 | 0.4947614808 | 0.00264039610890853815 | 0.6620965214 | 166.0250150478
    9 | 0.4947614808 | 0.4965138385 | 0.00175235769376258510 | 0.6636722755 | 251.3533000745
    10 | 0.4965138385 | 0.4976786184 | 0.00116477990795793573 | 0.6646930088 | 379.3135449254
    11 | 0.4976786184 | 0.4984536173 | 0.00077499883014725546 | 0.6653607474 | 571.2330225492
    12 | 0.4984536173 | 0.4989696118 | 0.00051599450909012301 | 0.6658003716 | 859.0985504567
    13 | 0.4989696118 | 0.4993133111 | 0.00034369932075445364 | 0.6660910430 | 1290.8878511100
    14 | 0.4993133111 | 0.4995423124 | 0.00022900128606345715 | 0.6662837900 | 1938.5659202606
    15 | 0.4995423124 | 0.4996949214 | 0.00015260904304309486 | 0.6664112926 | 2910.0766376211
    16 | 0.4996949214 | 0.4997966348 | 0.00010171339535092194 | 0.6664965150 | 4367.3461398087
    17 | 0.4997966348 | 0.4998644327 | 0.00006779791883326780 | 0.6665584076 | 6553.3001358038
    18 | 0.4998644327 | 0.4999096253 | 0.00004519259103091811 | 0.6665778509 | 9831.8335188516
    19 | 0.4999096253 | 0.4999397542 | 0.00003012888079562126 | 0.6666774378 | 14751.9189004836
    20 | 0.4999397542 | 0.4999598336 | 0.00002007941249576595 | 0.6664506601 | 22119.9939244004
    21 | 0.4999598336 | 0.4999732282 | 0.00001339460597526987 | 0.6670815682 | 33222.1656531949
    22 | 0.4999732282 | 0.4999821551 | 0.00000892684249254039 | 0.6664505480 | 49755.1439166074
    23 | 0.4999821551 | 0.4999881259 | 0.00000597078794178918 | 0.6688577677 | 74926.5788229439
    24 | 0.4999881259 | 0.4999921287 | 0.00000400283269313961 | 0.6704027562 | 112280.4498807474
    25 | 0.4999921287 | 0.4999947419 | 0.00000261325394618206 | 0.6528511548 | 163097.2875577784
    26 | 0.4999947419 | 0.4999967498 | 0.00000200786547838172 | 0.7683392122 | 294016.2831700209
    [0.49999718771149326, 0.0]

## Solutions
Problem: Build a FPI such that given $a$ computes $\displaystyle \frac{1}{a}$

```python
# We are finding the 1/a
# Solution code:
a = 2.1
g = lambda x: 2*x-a*x**2
gp = lambda x: 2-2*a*x
r=fpi(g, 0.7, 7, flag_cobweb=False)
print('Reciprocal found :',r)
print('Reciprocal computed explicitly: ', 1/a)
# Are we seeing quadratic convergence?
```

     i |     x_i      |    x_{i+1}   ||x_{i+1}-x_i| | e_{i+1}/e_i | e_{i+1}/e_i^2
    -----------------------------------------------------------------------------
     0 | 0.7000000000 | 0.3710000000 | 0.3290000000 | 0.0000000000 | 0.0000000000
     1 | 0.3710000000 | 0.4529539000 | 0.0819539000 | 0.2491000000 | 0.7571428571
     2 | 0.4529539000 | 0.4750566054 | 0.0221027054 | 0.2696968100 | 3.2908355795
     3 | 0.4750566054 | 0.4761877763 | 0.0011311709 | 0.0511779387 | 2.3154603813
     4 | 0.4761877763 | 0.4761904762 | 0.0000026999 | 0.0023867984 | 2.1100246103
     5 | 0.4761904762 | 0.4761904762 | 0.0000000000 | 0.0000056698 | 2.1000206476
     6 | 0.4761904762 | 0.4761904762 | 0.0000000000 | 0.0000000000 | 0.0000000000
    Reciprocal found : 0.47619047619047616
    Reciprocal computed explicitly:  0.47619047619047616

### What is this plot telling us?

This plots shows that, even if we don't know the exact value of $g'(r)$, we can determine if the FPI will convergan by looking at the plot.
In this plot we observe that when plotting $g'(x)$ (magenta), we can determine that the value of $|g'(r)|$ will be less than 1 since it is between the black lines, that are located at $y=-1$ and $y=1$.

```python
xx=np.linspace(0.2,0.8,1000)
plt.figure(figsize=(10,10))
plt.plot(xx,g(xx),'r-',label=r'$g(x)$')
plt.plot(xx,gp(xx),'m-',label=r'$gp(x)$')
plt.plot(xx,xx,'b-',label=r'$x$')
plt.plot(xx,0*xx+1,'k--')
plt.plot(xx,0*xx-1,'k--')
plt.legend(loc='best')
plt.grid()
plt.show()
```

```python

```


```python
from sympy.matrices import Matrix
import sympy as sp
import numpy as np
from Exercise import Exercise, MarkdownBlock

from helpers import *

import math
import matplotlib.pyplot as plt
import matplotlib as mpl
plt.rcParams.update({'font.size': 10})

from process_latex import process_sympy

try:
    from config import URL, TOKEN
except:
    None

# TODO: replace with supplied strings
Exercise.URL = URL
Exercise.TOKEN = TOKEN
```

```python
import sympy as sp

# matrix object
m1 = sp.Matrix([[0,0,0],
                [1,1,1]])

# LaTeX representation
l = sp.latex(m)

# parse LaTeX back to object
m2 = process_sympy(l)

assert m1 == m2
```

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

We've reached the third lesson of the module _Riding the wave: Convection problems_, where we explore the numerical solution of conservation laws. We learned in the [first lesson](https://nbviewer.jupyter.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb) how to think about conservation laws, starting with the most intuitive case: that of conservation of mass. But there are many physical quantities that can be conserved: energy, for example. Or cars on a road.

Developing a conservation law for traffic in a one-lane roadway is fun, because we can relate to the insights it gives. We've all experienced traffic slowing down to a crawl when the density of cars gets very high, and stepping on the accelerator pedal with glee when there are no cars on the road!

In the previous two lessons, we developed a traffic-flow model and explored [different choices of numerical scheme](https://nbviewer.jupyter.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_02_convectionSchemes.ipynb).  Not everything worked as you expected, and by now you might be realizing how some restrictions come about from the numerical methods themselves, while others still are imposed by the models we use. This lesson will develop a better traffic model, and also show you some impressive SymPy kung-fu.

## Traffic flow, revisited

### A better flux model

Like you saw in the first lesson, cars obey a typical conservation law,

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

where $F$ is the flux, $F=\rho u$—flux equals density times velocity. From our experience on the road, we know that the traffic speed is a function of traffic density, and we proposed as a first approximation a linear relation between the two. Thus,

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

This flux model meets the two requirements, based on a qualitative view of traffic flow, that:
1. $u \rightarrow u_{max}$ and $F\rightarrow 0$ when $\rho \rightarrow 0$.
2. $u \rightarrow 0$ as $\rho \rightarrow \rho_{max}$

However, it leads to some unrealistic or at least improbable results.  For example, note that if the traffic speed is a linear function of density, the flux function will be quadratic (see Figure 1). In this case, the maximum flux will occur when $\rho^{\star} = \rho_{max}/2$, corresponding to a traffic speed $u_{max}/2$.

#### Figure 1. Traffic speed (left) and flux (right) vs. density.

A good question to ask here is:  should the maximum flux on a given stretch of road have a strict dependence on the maximum speed that the roadway allows, be it by physical restrictions or posted speed limits? In other words, do we really expect the maximum flux to increase if we allow arbitrarily high speeds?

Probably not.  But there *should* be some ideal traffic speed, $u^{\star}$, corresponding to an ideal traffic density, $\rho^{\star}$, resulting in the maximum traffic flux:

$$
\begin{equation}
F_{\rm max} = \rho^{\star}u^{\star}
\end{equation}
$$

Let us improve the initial flux model by taking this observation into account. One thing that we can try is to introduce a flux model that is cubic in $\rho$ instead of quadratic:

$$
\begin{equation}
F(\rho) = u_{\rm max}\rho (1 - A\rho - B \rho^2)
\end{equation}
$$

This new model still meets the first criterion listed above: $F\rightarrow 0$ when $\rho \rightarrow 0$. Moreover, we impose the following conditions:

* When $\rho = \rho_{\rm max}$ traffic flux goes to zero:

$$
\begin{equation}
F(\rho_{\rm max}) = 0 = u_{\rm max}\, \rho_{\rm max}(1 - A \rho_{\rm max} - B \rho_{\rm max}^2)
\end{equation}
$$

* Based on eq. (3), maximum flux occurs when $\rho = \rho^{\star}$ and $F'(\rho^{\star}) = 0$:

$$
\begin{equation}
F'(\rho^{\star}) = 0  = u_{\rm max}(1 - 2A\rho^{\star} - 3B(\rho^{\star})^2)
\end{equation}
$$

* $u^{\star}$ is obtained when $\rho = \rho^{\star}$:

$$
\begin{equation}
u^{\star} = u_{\rm max}(1 - A \rho^{\star} - B(\rho^{\star})^2)
\end{equation}
$$

We have three equations and four unknowns $A,B,\rho^{\star}, u^{\star}$. However, in practice, the ideal traffic speed could be obtained for a given road by observations. Similarly to $u_{max}$ and $\rho_{max}$ it will therefore be taken as a parameter.

### Solving the new flux equation

Equations $(5)$, $(6)$, and $(7)$ are not incredibly difficult to solve with pen and paper. Instead of following that route, we can use [SymPy](http://sympy.org/en/index.html), the symbolic mathematics library of Python.  You used SymPy already in the [Burgers' equation lesson](https://nbviewer.jupyter.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/02_spacetime/02_04_1DBurgers.ipynb) of Module 2, _"Space & Time"_, and you will learn some new functionalities here.

We begin by importing SymPy, initializing $\LaTeX$ printing and defining a set of symbolic variables that we'll use in the calculations. Remember: variables are not defined automatically, you have to tell SymPy that a name will correspond to a symbolic variable by using the keyword `symbols`. This behavior is different from many other symbolic math systems that implicitly construct symbols for you, so you may be perplexed if you've used these other sytems before. The reason for this behavior in SymPy is that the system is fully built on Python, a general-purpose language that needs you to define all objects before using them.

```python
import sympy
sympy.init_printing()

(u_max, u_star, rho_max,
 rho_star, A, B) = sympy.symbols('u_max u_star rho_max rho_star A B')
```

Notice that we used the special character `_` (the under-dash) to create a symbol with a subscript. Here, for example, we've created the symbols $u_{max}$ and $u_{star}$, representing $u_{\rm max}$ and $u^{\star}$ ($u$-star) in the equations, and assigned them to the variable names `u_max` and `u_star`. SymPy also allows you to create symbols with a superscript by means of the special character `^`. Be careful, though: SymPy is built on Python, so you denote an exponent in an expression by `**` (this may also be different from other symbolic math systems that you have used in the past).

Next, use `sympy.Eq()` to define the three equations—corresponding to Equations $(5)$, $(6)$ and $(7)$, above—in terms of symbolic variables. The function `sympy.Eq()` creates an equality between two SymPy objects, passed as arguments separated by a comma. We need to remember that the equal sign in Python is used for variable assignment, and for that reason it cannot be used to build a symbolic equation with SymPy. That is why we need `sympy.Eq()` to create symbolic equalities. But the equal sign _is_ used to _assign_ an equation to a name: here, `eq1`, `eq2`, `eq3` are names for the symbolic equalities we create with `sympy.Eq()`.

```python
eq1 = sympy.Eq(0, u_max * rho_max * (1 - A * rho_max - B * rho_max**2))
eq2 = sympy.Eq(0, u_max * (1 - 2 * A * rho_star - 3 * B * rho_star**2))
eq3 = sympy.Eq(u_star, u_max * (1 - A * rho_star - B * rho_star**2))
```

Check this out: you can display these equations in pretty typeset mathematics just by executing their name in a code cell:

```python
eq1
```

```python
eq2
```

```python
eq3
```

As stressed above, we have three equations with three unknowns (assuming we have some observed value of $u_{star}$, corresponding to the ideal traffic speed)—there must be a solution!

To eliminate the term with $B$ in `eq2`, leaving it only in terms of $A$ and $\rho^{\star}$, we might subtract `3*eq3`.  But this will not work in SymPy if you attempt equation subtraction using the equation names. Just like we couldn't use the equal sign (which in Python means assignment) to create a symbolic equality (needing `sympy.Eq` instead), we can't use mathematical operators directly on the SymPy equations, because they are really _equalities_. What does it mean to subtract two _equalities_? It doesn't make sense. Try it ...

```python
eq2 - 3 * eq3
```

See?  SymPy just printed out what you suggested but it did not manipulate algebraically the left and right sides of `eq2` like we were aiming for. What we _can_ do is create a _new_ equation, perform the left-hand side (LHS) and right-hand side (RHS) operations separately and then recombine them into our desired result. For this, it is helpful to know that there are built-in properties of a SymPy equality for the left-hand side and the right-hand side of the equality. Check it out:

```python
eq4 = sympy.Eq(eq2.lhs - 3 * eq3.lhs, eq2.rhs - 3 * eq3.rhs)
eq4
```

That still needs a little work. SymPy offers several methods to help reduce expressions. You can use [`simplify()`](http://docs.sympy.org/latest/modules/simplify/simplify.html?highlight=simplify#sympy.simplify.simplify.simplify) to attempt to make the expression "simpler," but you can imagine that the quality of an expression being simple is not well defined. One may expect the simpler expression to be shorter, maybe; but not always. The SymPy `simplify()` function applies several strategies, heuristically, to give you an equivalent reduced expression. But some expressions are uncooperative and `simplify()` gives up, returning the argument unchanged. Let's see what it can do with our expression `eq4`.

```python
eq4.simplify()
```

That is actually a useful result. We see that `eq4` allows solving for $\rho^{\star}$ in terms of $A$, for example (remember that $u_{max}$ is known: the road's maximum velocity). Then we could try to manipulate `eq1` to solve for $B$ in terms of $A$, as well, so that we can substitute back in `eq2` leaving everything in terms of $A$. We can do all that without the help of `simplify()`, but using it interactively to reason about the long expression helped us to decide on a course of action.

Another SymPy function that can help us examine complicated expressions is [`expand()`](http://docs.sympy.org/latest/modules/core.html?highlight=expand#sympy.core.function.expand). Its purpose is to expand bracketed factors in expressions and group powers of symbols. Let's see what that does here. Notice first that `eq4` hasn't changed; we just printed the result of applying `simplify()` to it.

```python
eq4
```

And now we print the result of applying `expand()` to it.

```python
eq4.expand()
```

That's very similar to our previous result, except without the bracketed factor. Simplifying an expression can be accomplished by expanding or factoring terms, or a combination of the two. Whether to `simplify()` or `expand()` depends on the situation and you just have to experiment.

We now have an idea of what to do with our three equations. We'll get expressions in terms of $A$ from `eq4` and `eq1` and substitute them back into `eq2`. For that, we can use the SymPy functions [`solve()`](http://docs.sympy.org/dev/modules/solvers/solvers.html#sympy.solvers.solvers.solve) and `subs()`, respectively. The arguments to `solve()` are the equality that needs to be solved, and the symbol to solve for.

**Note**: `sympy.solve()` always returns results in a *list*, since you often end up with multiple solutions for a given variable.  These linear equations will only return one solution, so we can skip a step and ask right away for the `[0]`-th element of the list.

```python
rho_sol = sympy.solve(eq4, rho_star)[0]
rho_sol
```

```python
B_sol = sympy.solve(eq1, B)[0]
B_sol
```

The arguments to the SymPy function [`subs()`](http://docs.sympy.org/dev/modules/core.html#sympy.core.basic.Basic.subs) are given as (old, new) pairs, where the "new" expression substitutes the "old" one. So here, in `eq2`, we substitute `rho_sol` in place of `rho_star` and we substitute `B_sol` in place of `B`.

```python
quadA = eq2.subs([(rho_star, rho_sol), (B, B_sol)])
quadA
```

```python
quadA.simplify()
```

It's a little bit ugly, but that is quadratic in $A$, and that means we can solve for the roots of the equation. SymPy's `solve()` function in this case will return a list with the two roots. They are long expressions, so let's print each root separately.

```python
A_sol = sympy.solve(quadA, A)
```

```python
A_sol[0]
```

```python
A_sol[1]
```

### Evaluating the new flux equation

Quadratic equations, of course, have two solutions. Here we have to select the positive root, otherwise our model would have an inconsistency. Are you able to see why?  To determine its value, we need to fill in some actual numbers into the model.

Let's start with the same numerical values that we used in [lesson 1](https://nbviewer.jupyter.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb) for $\rho_{\rm max}$ and $u_{\rm max}$. But we also have to supply a value for $u^{\star}$, a quantity that should be experimentally observed in a given road. We propose $u^{\star} = 0.7\, u_{\rm max}$ for this exercise.  This would correspond to 84 km/h for a highway with a 120-km/h speed limit, for example.

Let's numerically evaluate the solutions for $A$ using the following values:

$$
\begin{align}
\rho_{\rm max} &=10.0 \nonumber\\ u_{\rm max} &=1.0 \nonumber\\ u^{\star} &=0.7 \nonumber
\end{align}
$$

Now evaluate the numeric result for each root of $A$ using the `evalf()` function, where you pass the numeric substitution as an argument. It's very cool.  Let's evaluate both roots and we will pick the positive one.

Let's try the `[0]`-th solution for $A$, first:

```python
A_val_0 = A_sol[0].evalf(subs={u_star: 0.7, u_max: 1.0, rho_max: 10.0})
A_val_0
```

Now let's try the `[1]`-th solution for $A$:

```python
A_val_1 = A_sol[1].evalf(subs={u_star: 0.7, u_max: 1.0, rho_max: 10.0})
A_val_1
```

Because Sympy seems not to return the roots in a deterministic order and  we want to use the positive root, we will automatically pick this one by using the Python built-in `max` function.

```python
A_val = max(A_val_0, A_val_1)
A_val
```

Now, numerically evaluate $B$ in the same way using the positive root `A_val`:

```python
B_val = B_sol.evalf(subs={rho_max: 10.0, A: A_val})
B_val
```

### Turn off $\LaTeX$

$\LaTeX$ output is great when we're looking at algebraic expressions, but it's a little too much for simple numeric output.  Let's turn it off for the rest of the exercise:

```python
sympy.init_printing(use_latex=False)
```

## Green light: take 2

Let's re-examine the green-light problem from [lesson 1](https://nbviewer.jupyter.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb) but using our newly computed traffic-flux equation.  We shouldn't have to change much—in fact, we can simply create a new `flux` function and leave the rest of the code unchanged.

There's one last bit of housekeeping to do so we don't run into trouble -- we used the variables `rho_max` and `u_max` in our SymPy code and we defined them as SymPy `symbols`.  You can check on the status of a variable using `type()`.

```python
print(type(rho_max), type(u_max))
```

    <class 'sympy.core.symbol.Symbol'> <class 'sympy.core.symbol.Symbol'>

If we try to use SymPy variables with NumPy arrays, Python is going to be very unhappy.  We can re-define them as floats and avoid that messy situation.

```python
rho_max = 10.0
u_max = 1.0
```

```python
def flux(rho, u_max, A, B):
    """
    Computes the traffic flux for the better model.

    Parameters
    ----------
    rho : numpy.ndarray
       Traffic density along the road as a 1D array of floats.
    u_max : float
        Maximum speed allowed on the road.
    A : float
        Scaling coefficient for rho.
    B : float
        Scaling coefficient for rho squared.

    Returns
    -------
    F : numpy.ndarray
        The traffic flux along the road as a 1D array of floats.
    """
    F = rho * u_max * (1.0 - A * rho - B * rho**2)
    return F
```

```python
import numpy
from matplotlib import pyplot
%matplotlib inline
```

```python
# Set the font family and size to use for Matplotlib figures.
pyplot.rcParams['font.family'] = 'serif'
pyplot.rcParams['font.size'] = 16
```

```python
def rho_green_light(x, rho_light):
    """
    Computes the "green light" initial condition.
    It consists of a shock with a linear distribution behind it.

    Parameters
    ----------
    x : numpy.ndarray
        Locations on the road as a 1D array of floats.
    rho_light : float
        Car density at the stoplight.

    Returns
    -------
    rho : numpy.ndarray
        The initial car density along the road
        as a 1D array of floats.
    """
    rho = numpy.zeros_like(x)
    mask = numpy.where(x < 2.0)
    rho[mask] = rho_light * x[mask] / 2.0
    return rho
```

```python
# Set parameters.
nx = 81  # number of locations on the road
L = 4.0  # length of the road
dx = L / (nx - 1)  # distance between two consecutive locations
nt = 30  # number of time steps to compute
rho_light = 5.0  # car density at the traffic light.

# Define the locations on the road.
x = numpy.linspace(0.0, L, num=nx)

# Compute the initial traffic density.
rho0 = rho_green_light(x, rho_light)
```

```python
# Plot the initial car density on the road.
fig = pyplot.figure(figsize=(6.0, 4.0))
pyplot.xlabel(r'$x$')
pyplot.ylabel(r'$\rho$')
pyplot.grid()
line = pyplot.plot(x, rho0,
                   color='C0', linestyle='-', linewidth=2)[0]
pyplot.xlim(0.0, L)
pyplot.ylim(-0.5, 6.0)
pyplot.tight_layout();
```

```python
def ftbs(rho0, nt, dt, dx, bc_value, *args):
    """
    Computes the traffic density on the road
    at a certain time given the initial traffic density.

    Parameters
    ----------
    rho0 : numpy.ndarray
        The initial car density along the road
        as a 1D array of floats.
    nt : integer
        The number of time steps to compute.
    dt : float
        The time-step size to integrate.
    dx : float
        The distance between two consecutive locations.
    bc_value : float
        The constant density at the first station.
    args : list or tuple
        Positional arguments to be passed to the flux function.

    Returns
    -------
    rho_hist : list of numpy.ndarray objects
        The history of the car density along the road.
    """
    rho_hist = [rho0.copy()]
    rho = rho0.copy()
    for n in range(nt):
        # Compute the flux.
        F = flux(rho, *args)
        # Advance in time.
        rho[1:] = rho[1:] - dt / dx * (F[1:] - F[:-1])
        # Set the left boundary condition.
        rho[0] = bc_value
        # Record the time-step solution.
        rho_hist.append(rho.copy())
    return rho_hist
```

```python
# Set time-step size based on CFL limit.
sigma = 1.0
dt = sigma * dx / u_max  # time-step size

# Compute the traffic density at all time steps.
rho_hist = ftbs(rho0, nt, dt, dx, rho0[0], u_max, A_val, B_val)
```

Now that we have computed the history of the car density along the road, let's create an animation to visualize the results.

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

```python
def update_plot(n, rho_hist):
    """
    Update the line y-data of the Matplotlib figure.

    Parameters
    ----------
    n : integer
        The time-step index.
    rho_hist : list of numpy.ndarray objects
        The history of the numerical solution.
    """
    fig.suptitle('Time step {:0>2}'.format(n))
    line.set_ydata(rho_hist[n])
```

```python
# Create an animation of the traffic density.
anim = animation.FuncAnimation(fig, update_plot,
                               frames=nt, fargs=(rho_hist,),
                               interval=100)
# Display the video.
HTML(anim.to_html5_video())
```

That definitely looks different!  Do you think this is more or less accurate than our previous model?

##### Dig Deeper

The new traffic-flux model that we developed here changes the way that traffic patterns evolve. In this lesson, we only experimented with the most basic scheme: forward-time, backward-space.  Try to implement the green-light problem using one of the second-order schemes from [Lesson 2](https://nbviewer.jupyter.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_02_convectionSchemes.ipynb).

## References

* Neville D. Fowkes and John J. Mahony, *"An Introduction to Mathematical Modelling,"* Wiley & Sons, 1994. Chapter 14: Traffic Flow.

---

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


# Inaugural Project

> **Note the following:**
> 1. This is an example of how to structure your **inaugural project**.
> 1. Remember the general advice on structuring and commenting your code from [lecture 5](https://numeconcopenhagen.netlify.com/lectures/Workflow_and_debugging).
> 1. Remember this [guide](https://www.markdownguide.org/basic-syntax/) on markdown and (a bit of) latex.
> 1. Turn on automatic numbering by clicking on the small icon on top of the table of contents in the left sidebar.
> 1. The `inauguralproject.py` file includes a function which can be used multiple times in this notebook.

Imports and set magics:

```python
import numpy as np

# autoreload modules when code is run. Otherwise, python will not see recent changes.
%load_ext autoreload
%autoreload 2

# Now we use the library scipy to do the heavy lifting
from scipy import optimize

from sympy import *

```

# Question 1

**Explain how you solve the model**

```python
# Parameters are defined

y = 1
p = 0.2
theta = -2
```

```python
import Insurancefunctions as IFC
import numpy as np
from scipy import optimize
```

```python
import numpy as np
from scipy import optimize

y = 1
p = 0.2
theta = -2

N = 1_000
x_vec = np.linspace(0.01,0.9,N)
q_vec = np.zeros(N)

def premium_policy(q):
    ''' Calculates premium policy for the insurance company

    Args:
    q (float): The coverage ammount.
    p (float): The proability that the loss is incurred. Meaning the probability that the coverage ammount has to be paid.

    Returns:

        (float): The premium policy the agent has to pay for her preferred coverage

    '''
    return p*q

def utility_of_assets(z):
    ''' Calculates the utility for the assets the agent is receiving after paying for insurance

    Args:
    z (float): The assets the agent is receiving.
    theta (float): The relative risk aversion.

    Return:
    (float): the utility for the assets the agent is receiving after paying for insurance '''

    return (z**(1+theta))/(1+theta)

def uninsured_expected_utility():
    ''' Calculates the expected utility of the assets, if the agents is uninsured.

    Args:
    p (float): The proability that the loss is incurred.
    y (float): The assets the agent is holdning initially.
    x (float): The maximum coverage the agent can buy of insurance from loss.

    Returns:
    (float): The expected utility of the assets, if the agents is uninsured.'''
    return p*utility_of_assets(z=y-x) + (1-p)*utility_of_assets(z=y)

def insured_expected_utility(q):
    ''' Calculates the expected utility of the assets, if the agents is insured for the coverage ammount q.

    Args:
    p (float): The proability that the loss is incurred.
    y (float): The assets the agent is holdning initially.
    x (float): The maximum coverage the agent can buy of insurance from loss.
    q (float): The coverage ammount.

    Returns:
    (float): The expected utility of the assets, if the agents is insured.'''
    return p*utility_of_assets(z=y-x+q-premium_policy(q)) + (1-p)*utility_of_assets(z=y-premium_policy(q))

def find_optimal_coverage_ammount(x):
    ''' Finds optimal coverage ammount

    Args:
    p (float): The proability that the loss is incurred.
    y (float): The assets the agent is holdning initially.
    x (float): The maximum coverage the agent can buy of insurance from loss.
    q (float): The coverage ammount.

    Returns:
    (float): Expected utility at optimal coverage ammount'''

    obj = lambda q: -insured_expected_utility(q)
    res = optimize.minimize_scalar(obj,bounds=(1e-8,0.9),method='bounded')
    return res.x

N = 90
x_vec = np.linspace(0.01,0.9,N)
q_vec = np.zeros(N)
for i,x in enumerate(x_vec):
    q_vec[i] = find_optimal_coverage_ammount(x)
    print(f'x={x:.2f} --> q = {q_vec[i]:12.0f}')

```

    x=0.01 --> q =            0
    x=0.02 --> q =            0
    x=0.03 --> q =            0
    x=0.04 --> q =            0
    x=0.05 --> q =            0
    x=0.06 --> q =            0
    x=0.07 --> q =            0
    x=0.08 --> q =            0
    x=0.09 --> q =            0
    x=0.10 --> q =            0
    x=0.11 --> q =            0
    x=0.12 --> q =            0
    x=0.13 --> q =            0
    x=0.14 --> q =            0
    x=0.15 --> q =            0
    x=0.16 --> q =            0
    x=0.17 --> q =            0
    x=0.18 --> q =            0
    x=0.19 --> q =            0
    x=0.20 --> q =            0
    x=0.21 --> q =            0
    x=0.22 --> q =            0
    x=0.23 --> q =            0
    x=0.24 --> q =            0
    x=0.25 --> q =            0
    x=0.26 --> q =            0
    x=0.27 --> q =            0
    x=0.28 --> q =            0
    x=0.29 --> q =            0
    x=0.30 --> q =            0
    x=0.31 --> q =            0
    x=0.32 --> q =            0
    x=0.33 --> q =            0
    x=0.34 --> q =            0
    x=0.35 --> q =            0
    x=0.36 --> q =            0
    x=0.37 --> q =            0
    x=0.38 --> q =            0
    x=0.39 --> q =            0
    x=0.40 --> q =            0
    x=0.41 --> q =            0
    x=0.42 --> q =            0
    x=0.43 --> q =            0
    x=0.44 --> q =            0
    x=0.45 --> q =            0
    x=0.46 --> q =            0
    x=0.47 --> q =            0
    x=0.48 --> q =            0
    x=0.49 --> q =            0
    x=0.50 --> q =            1
    x=0.51 --> q =            1
    x=0.52 --> q =            1
    x=0.53 --> q =            1
    x=0.54 --> q =            1
    x=0.55 --> q =            1
    x=0.56 --> q =            1
    x=0.57 --> q =            1
    x=0.58 --> q =            1
    x=0.59 --> q =            1
    x=0.60 --> q =            1
    x=0.61 --> q =            1
    x=0.62 --> q =            1
    x=0.63 --> q =            1
    x=0.64 --> q =            1
    x=0.65 --> q =            1
    x=0.66 --> q =            1
    x=0.67 --> q =            1
    x=0.68 --> q =            1
    x=0.69 --> q =            1
    x=0.70 --> q =            1
    x=0.71 --> q =            1
    x=0.72 --> q =            1
    x=0.73 --> q =            1
    x=0.74 --> q =            1
    x=0.75 --> q =            1
    x=0.76 --> q =            1
    x=0.77 --> q =            1
    x=0.78 --> q =            1
    x=0.79 --> q =            1
    x=0.80 --> q =            1
    x=0.81 --> q =            1
    x=0.82 --> q =            1
    x=0.83 --> q =            1
    x=0.84 --> q =            1
    x=0.85 --> q =            1
    x=0.86 --> q =            1
    x=0.87 --> q =            1
    x=0.88 --> q =            1
    x=0.89 --> q =            1
    x=0.90 --> q =            1

# Question 2

Explain your code and procedure

```python
# code
```

# Question 3

Explain your code and procedure

```python
# code
```

ADD CONCISE CONLUSION.


```python
f=(x**3-4)**2-x**6+2*x**5
sp.simplify(f)
```

```python
import sympy as sp
sp.init_printing()
```

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

We've reached the third lesson of the module _Riding the wave: Convection problems_, where we explore the numerical solution of conservation laws. We learned in the [first lesson](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb) how to think about conservation laws, starting with the most intuitive case: that of conservation of mass. But there are many physical quantities that can be conserved: energy, for example. Or cars on a road.

Developing a conservation law for traffic in a one-lane roadway is fun, because we can relate to the insights it gives. We've all experienced traffic slowing down to a crawl when the density of cars gets very high, and stepping on the accelerator pedal with glee when there are no cars on the road!

In the previous two lessons, we developed a traffic-flow model and explored [different choices of numerical scheme](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_02_convectionSchemes.ipynb).  Not everything worked as you expected, and by now you might be realizing how some restrictions come about from the numerical methods themselves, while others still are imposed by the models we use. This lesson will develop a better traffic model, and also show you some impressive SymPy kung fu.

## Traffic flow, revisited

### A better flux model

Like you saw in the first lesson, cars obey a typical conservation law,

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

where $F$ is the flux, $F=\rho u$—flux equals density times velocity. From our experience on the road, we know that the traffic speed is a function of traffic density, and we proposed as a first approximation a linear relation between the two. Thus,

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

This flux model meets the two requirements, based on a qualitative view of traffic flow, that:
1.  $u \rightarrow u_{max}$ and $F\rightarrow 0$ when $\rho \rightarrow 0$.
2.  $u \rightarrow 0$ as $\rho \rightarrow \rho_{max}$

However, it leads to some unrealistic or at least improbable results.  Note that if the traffic speed is a linear function of density, the flux function will be quadratic (see Figure 1). In this case, the maximum flux will occur when $\rho^{\star} = \rho_{max}/2$, corresponding to a traffic speed $u_{max}/2$.

####Figure 1. Traffic speed (left) and flux (right) vs. density.

A good question to ask here is:  should the maximum flux on a given stretch of road have a strict dependence on the maximum speed that the roadway allows, be it by physical restrictions or posted speed limits?

Probably not.  But there *should* be some ideal traffic speed, $u^{\star}$, corresponding to an ideal traffic density, $\rho^{\star}$, resulting in the maximum traffic flux:

\begin{equation}F_{\rm max} = \rho^{\star}u^{\star}\end{equation}

Any flux model would still satisfy that the slope of the curve $F(\rho)$ is equal to zero at $F_{\rm max}$. One possible flux model that can satisfy all the conditions is cubic in $\rho$ instead of quadratic:

\begin{equation}F(\rho) = u_{\rm max}\rho (1 - A\rho - B \rho^2)\end{equation}

This new model still meets the first criterion listed above: $F\rightarrow 0$ when $\rho \rightarrow 0$.  To find the unknowns $A$ and $B$ we apply the other conditions on the flux, based now on the quantities, $u^{\star}$ and $\rho^{\star}$, as follows:

*  When $\rho = \rho_{\rm max}$ traffic flux goes to zero:

\begin{equation}F(\rho_{\rm max}) = 0 = u_{\rm max}\, \rho_{\rm max}(1 - A \rho_{\rm max} - B \rho_{\rm max}^2)\end{equation}

*  Maximum flux occurs when $\rho = \rho^{\star}$ and $F'(\rho^{\star}) = 0$:

\begin{equation}
F'(\rho^{\star}) = 0  = u_{\rm max}(1 - 2A\rho^{\star} - 3B(\rho^{\star})^2)\end{equation}

*  $u^{\star}$ is obtained when $\rho = \rho^{\star}$:

\begin{equation}
u^{\star} = u_{\rm max}(1 - A \rho^{\star} - B(\rho^{\star})^2)\end{equation}

### Solving the new flux equation

Equations $(5)$, $(6)$, and $(7)$ might seem straightforward at first glance, but notice that the latter two are quadratic in $\rho^{\star}$ and involve unknown coefficients.  In practice, the quantity $u^{\star}$ could be obtained for a given road by observations, and $u_{\rm max}$ is also known, but we need to find the coefficients $A$ and $B$. For this, we can use [SymPy](http://sympy.org/en/index.html), the symbolic mathematics library of Python.  You used SymPy already in the [Burgers' equation lesson](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/02_spacetime/02_04_1DBurgers.ipynb) of Module 2, _"Space & Time"_.

We begin by importing SymPy, initializing $\LaTeX$ printing and defining a set of symbolic variables that we'll use in the calculations. Remember: variables are not defined automatically, you have to tell SymPy that a name will correspond to a symbolic variable by using the keyword `symbols`. This behavior is different from many other symbolic math systems that implicitly construct symbols for you, so you may be perplexed if you've used these other sytems before. The reason for this behavior in SymPy is that the system is fully built on Python, a general-purpose language that needs you to define all objects before using them.

```python
import sympy
sympy.init_printing()

u_max, u_star, rho_max, rho_star, A, B = sympy.symbols('u_max u_star rho_max rho_star A B')
```

Notice that we used the special character `_` (the under-dash) to create a symbol with a subscript. Here, for example, we've created the symbols $u_{max}$ and $u_{star}$, representing $u_{\rm max}$ and $u^{\star}$ ($u$-star) in the equations, and assigned them to the variable names `u_max` and `u_star`. SymPy also allows you to create symbols with a superscript by means of the special character `^`. Be careful, though: SymPy is built on Python, so you denote an exponent in an expression by `**` (this may also be different from other symbolic math systems that you have used in the past).

Next, use `sympy.Eq()` to define the three equations—corresponding to Equations $(5)$, $(6)$ and $(7)$, above—in terms of symbolic variables. The function `sympy.Eq()` creates an equality between two SymPy objects, passed as arguments separated by a comma. We need to remember that the equal sign in Python is used for variable assignment, and for that reason it cannot be used to build a symbolic equation with SymPy. That is why we need `sympy.Eq()` to create symbolic equalities. But the equal sign _is_ used to _assign_ an equation to a name: here, `eq1`, `eq2`, `eq3` are names for the symbolic equalities we create with `sympy.Eq()`.

```python
eq1 = sympy.Eq( 0, u_max*rho_max*(1 - A*rho_max-B*rho_max**2) )
eq2 = sympy.Eq( 0, u_max*(1 - 2*A*rho_star-3*B*rho_star**2) )
eq3 = sympy.Eq( u_star, u_max*(1 - A*rho_star - B*rho_star**2) )
```

Check this out: you can display these equations in pretty typeset mathematics just by executing their name in a code cell:

```python
eq1
```

```python
eq2
```

```python
eq3
```

We have three equations with three unknowns (assuming we have some observed value of $u_{star}$, corresponding to the ideal traffic speed)—there must be a solution!

To eliminate the term with $B$ in `eq2`, leaving it only in terms of $A$ and $\rho^{\star}$, we might subtract `3*eq3`.  But this will not work in SymPy if you attempt equation subtraction using the equation names. Just like we couldn't use the equal sign (which in Python means assignment) to create a symbolic equality (needing `sympy.Eq` instead), we can't use mathematical operators directly on the SymPy equations, because they are really _equalities_. What does it mean to subtract two _equalities_? It doesn't make sense. Try it ...

```python
eq2 - 3*eq3
```

See?  SymPy just printed out what you suggested but it did not manipulate algebraically the left and right sides of `eq2` like we were aiming for. What we _can_ do is create a _new_ equation, perform the left-hand side (LHS) and right-hand side (RHS) operations separately and then recombine them into our desired result. For this, it is helpful to know that there are built-in properties of a SymPy equality for the left-hand side and the right-hand side of the equality. Check it out:

```python
eq4 = sympy.Eq(eq2.lhs - 3*eq3.lhs, eq2.rhs - 3*eq3.rhs)
eq4
```

That still needs a little work. SymPy offers several methods to help reduce expressions. You can use [`simplify()`](http://docs.sympy.org/latest/modules/simplify/simplify.html?highlight=simplify#sympy.simplify.simplify.simplify) to attempt to make the expression "simpler," but you can imagine that the quality of an expression being simple is not well defined. One may expect the simpler expression to be shorter, maybe; but not always. The SymPy `simplify()` function applies several strategies, heuristically, to give you an equivalent reduced expression. But some expressions are uncooperative and `simplify()` gives up, returning the argument unchanged. Let's see what it can do with our expression `eq4`.

```python
eq4.simplify()
```

That is actually a useful result. We see that `eq4` allows solving for $\rho^{\star}$ in terms of $A$, for example (remember that $u_{max}$ is known: the road's maximum velocity). Then we could try to manipulate `eq1` to solve for $B$ in terms of $A$, as well, so that we can substitute back in `eq2` leaving everything in terms of $A$. We can do all that without the help of `simplify()`, but using it interactively to reason about the long expression helped us to decide on a course of action.

Another SymPy function that can help us examine complicated expressions is [`expand()`](http://docs.sympy.org/latest/modules/core.html?highlight=expand#sympy.core.function.expand). Its purpose is to expand bracketed factors in expressions and group powers of symbols. Let's see what that does here. Notice first that `eq4` hasn't changed; we just printed the result of applying `simplify()` to it.

```python
eq4
```

And now we print the result of applying `expand()` to it.

```python
eq4.expand()
```

That's very similar to our previous result, except without the bracketed factor. Simplifying an expression can be accomplished by expanding or factoring terms, or a combination of the two. Whether to `simplify()` or `expand()` depends on the situation and you just have to experiment.

We now have an idea of what to do with our three equations. We'll get expressions in terms of $A$ from `eq4` and `eq1` and substitute them back into `eq2`. For that, we can use the SymPy functions [`solve()`](http://docs.sympy.org/dev/modules/solvers/solvers.html#sympy.solvers.solvers.solve) and `subs()`, respectively. The arguments to `solve()` are the equality that needs to be solved, and the symbol to solve for.

**Note**: `sympy.solve()` always returns results in a *list*, since you often end up with multiple solutions for a given variable.  These linear equations will only return one solution, so we can skip a step and ask right away for the `[0]`-th element of the list.

```python
rho_sol = sympy.solve(eq4,rho_star)[0]
rho_sol
```

```python
B_sol = sympy.solve(eq1,B)[0]
B_sol
```

The arguments to the SymPy function [`subs()`](http://docs.sympy.org/dev/modules/core.html#sympy.core.basic.Basic.subs) are given as (old, new) pairs, where the "new" expression substitutes the "old" one. So here, in `eq2`, we substitute `rho_sol` in place of `rho_s` and we substitute `B_sol` in place of `B`.

```python
quadA = eq2.subs([(rho_star, rho_sol), (B,B_sol)])
quadA
```

```python
quadA.simplify()
```

It's a little bit ugly, but that is quadratic in $A$, and that means we can solve for the roots of the equation. SymPy's `solve()` function in this case will return a list with the two roots. They are long expressions, so let's print each root separately.

```python
A_sol = sympy.solve(quadA, A)
```

```python
A_sol[0]
```

```python
A_sol[1]
```

### Evaluating the new flux equation

Quadratic equations, of course, have two solutions. Which should we choose?  It's natural to select the positive root, but in order to determine *which* root is positive, we need to fill in some actual numbers into the model.

Let's start with the same numerical values that we used in [lesson 1](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb) for $\rho_{\rm max}$ and $u_{\rm max}$. But we also have to supply a value for $u^{\star}$, a quantity that should be experimentally observed in a given road. We propose $u^{\star} = 0.7\, u_{\rm max}$ for this exercise.  This would correspond to 84 km/h for a highway with a 120-km/h speed limit, for example.

Let's numerically evaluate the solutions for $A$ using the following values:

\begin{align}
\rho_{\rm max} &=10.0 \nonumber\\ u_{\rm max} &=1.0 \nonumber\\ u^{\star} &=0.7 \nonumber
\end{align}

Now evaluate the numeric result for each root of $A$ using the `evalf()` function, where you pass the numeric substitution as an argument. It's very cool.  Let's try the `[0]`-th solution for $A$, first:

```python
aval = A_sol[0].evalf(subs={u_star: 0.7, u_max:1.0, rho_max:10.0} )
aval
```

That's the positive root.  Let's evaluate the negative root (but we're not going to save this result, because we don't need it!):

```python
A_sol[1].evalf(subs={u_star: 0.7, u_max:1.0, rho_max:10.0} )
```

Now, numerically evaluate $B$ in the same way:

```python
bval = B_sol.evalf(subs={rho_max:10.0, A:aval} )
bval
```

### Turn off $\LaTeX$

$\LaTeX$ output is great when we're looking at algebraic expressions, but it's a little too much for simple numeric output.  Let's turn it off for the rest of the exercise:

```python
sympy.init_printing(use_latex=False)
```

## Green light: take 2

Let's re-examine the green-light problem from [lesson 1](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_01_conservationLaw.ipynb) but using our newly computed traffic-flux equation.  We shouldn't have to change much—in fact, we can simply create a new `computeF` function and leave the rest of the code unchanged.

There's one last bit of housekeeping to do so we don't run into trouble -- we used the variables `rho_max` and `u_max` in our SymPy code and we defined them as SymPy `symbols`.  You can check on the status of a variable using `type()`.

```python
print (type(rho_max), type(u_max))
```

    <class 'sympy.core.symbol.Symbol'> <class 'sympy.core.symbol.Symbol'>

If we try to use SymPy variables with NumPy arrays, Python is going to be very unhappy.  We can re-define them as floats and avoid that messy situation.

```python
rho_max = 10.
u_max = 1.
```

```python
def computeF(u_max, rho, aval, bval):
    return u_max*rho*(1 - aval*rho-bval*rho**2)
```

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

```python
def rho_green_light(nx, rho_light):
    """Computes "green light" initial condition with shock, and linear distribution behind

    Parameters
    ----------
    nx        : int
        Number of grid points in x
    rho_light : float
        Density of cars at stoplight

    Returns
    -------
    rho_initial: array of floats
        Array with initial values of density
    """
    rho_initial = numpy.arange(nx)*2./nx*rho_light  # Before stoplight
    rho_initial[(nx-1)/2:] = 0

    return rho_initial
```

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

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

rho_light = 5.5

```

```python
rho_initial = rho_green_light(nx, rho_light)
```

    /home/randy/anaconda3/lib/python3.4/site-packages/ipykernel/__main__.py:17: DeprecationWarning: using a non-integer number instead of an integer will result in an error in the future

```python
pyplot.plot(x, rho_initial, color='#003366', ls='-', lw=3)
pyplot.ylim(-0.5,11.);
```

```python
def ftbs(rho, nt, dt, dx, rho_max, u_max):
    """ Computes the solution with forward in time, backward in space

    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[0,:] = rho.copy()

    for t in range(1,nt):
        F = computeF(u_max, rho, aval, bval)
        rho_n[t,1:] = rho[1:] - dt/dx*(F[1:]-F[:-1])
        rho_n[t,0] = rho[0]
        rho_n[t,-1] = rho[-1]
        rho = rho_n[t].copy()

    return rho_n
```

```python
sigma = 1.
dt = sigma*dx/u_max

rho_n = ftbs(rho_initial, nt, dt, dx, rho_max, u_max)
```

```python
from matplotlib import animation
from JSAnimation.IPython_display import display_animation
```

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

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

anim = animation.FuncAnimation(fig, animate, frames=rho_n, interval=50)
display_animation(anim, default_mode='once')
```

    <input id="_anim_sliderBVUDRKZTJYQTEAYO" type="range" style="width:350px" name="points" min="0" max="1" step="1" value="0" onchange="animBVUDRKZTJYQTEAYO.set_frame(parseInt(this.value));"></input>

    <button onclick="animBVUDRKZTJYQTEAYO.slower()">–</button>
    <button onclick="animBVUDRKZTJYQTEAYO.first_frame()"></button>
    <button onclick="animBVUDRKZTJYQTEAYO.previous_frame()"></button>
    <button onclick="animBVUDRKZTJYQTEAYO.reverse_animation()"></button>
    <button onclick="animBVUDRKZTJYQTEAYO.pause_animation()"></button>
    <button onclick="animBVUDRKZTJYQTEAYO.play_animation()"></button>
    <button onclick="animBVUDRKZTJYQTEAYO.next_frame()"></button>
    <button onclick="animBVUDRKZTJYQTEAYO.last_frame()"></button>
    <button onclick="animBVUDRKZTJYQTEAYO.faster()">+</button>
  <form action="#n" name="_anim_loop_selectBVUDRKZTJYQTEAYO" class="anim_control">
    <input type="radio" name="state" value="once" checked> Once </input>
    <input type="radio" name="state" value="loop" > Loop </input>
    <input type="radio" name="state" value="reflect" > Reflect </input>
  </form>

That definitely looks different!  Do you think this is more or less accurate than our previous model?

##### Dig Deeper

The new traffic-flux model that we developed here changes the way that traffic patterns evolve. In this lesson, we only experimented with the most basic scheme: forward-time, backward-space.  Try to implement the green-light problem using one of the second-order schemes from [Lesson 2](http://nbviewer.ipython.org/github/numerical-mooc/numerical-mooc/blob/master/lessons/03_wave/03_02_convectionSchemes.ipynb).

## References

* Neville D. Fowkes and John J. Mahony, *"An Introduction to Mathematical Modelling,"* Wiley & Sons, 1994. Chapter 14: Traffic Flow.

---

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


Markdown macros

$\newcommand{\I}{\mathrm{i}}
\newcommand{\E}{\mathrm{e}}
\newcommand{\D}{\mathop{}\!\mathrm{d}}
\newcommand{\bra}[1]{\langle{#1}|}
\newcommand{\ket}[1]{|{#1}\rangle}
\newcommand{\braket}[1]{\langle{#1}\rangle}
\newcommand{\bm}[1]{\boldsymbol{#1}}$

```javascript
%%javascript
MathJax.Hub.Config({
    TeX: { equationNumbers: { autoNumber: "AMS" } }
});
```

    <IPython.core.display.Javascript object>

# Imports

```python
# numpy
import numpy as np
```

```python
# matplotlib
import matplotlib
import matplotlib.pylab as plt
```

```python
%matplotlib inline
from IPython.display import Image
```

```python
# sympy
import sympy as sy
sy.init_printing(use_unicode = True)
```

# Forces de firction

### Géométrie:
* charriot, longueur $2l$, hauteur $2h$, moment d'inertie $I_z= m(l^2+h^2)/3$
* disque, (coupe 2D du cylindre) rayon $a$, moment d'inertie $I_z=ma^2/2$, $I_x=I_y=ma^2/4$; les disques sont séparés d'une distance $2d$

* référenciel fixe: $(O,X,Y,Z)$
* référenciel fixe cylindre 1: $(O_1,X,Y,Z)$
* référenciel fixe cylindre 2: $(O_2,X,Y,Z)$

### Centre de masse $G$:

* position $\bm G = (X(t), a + h, 0)$
* vitesse $\bm V = \dot{X} \hat{\bm X}$
* accellération $\dot{V} = \ddot{X} \hat{\bm X}$

### Vitesses

Vitesse angulaire des disques: $\bm \omega_1 = \omega_1 \hat{\bm Z}$, et $\bm \omega_2 = \omega_1 \hat{\bm Z}$

Vitesse des disques aux points de contact  $C_1$ et $C_2$:

* $\bm v_1 = a \bm \omega_1 \times \hat{\bm Y} = -a\omega_1 \hat{\bm X}$ ($a$ est dans la verticale, au point $C_1$)
* $\bm v_2 = -a\omega_2 \hat{\bm X}$

Notez que $v_1>0$ initialement, puisque $\omega_1(0) < 0 $, et $v_2$ est toujours négative ($\omega_2>0$)

Vitesse de glissement aux points de contact:

* le disque 1 roule sans glisser, $0 = a\omega_1 \hat{\bm X} + \dot{X} \hat{\bm X}$, la vitesse de glissement est nulle:
    $$\dot{X}(t) = - a \omega_1(t) > 0$$
* le disque 2 peut glisser, la vitesse en $C_2$ est la différence de la vitesse du charriot et de la vitesse du disque: $\bm v_2 = \dot{X} \hat{\bm X} - (-a\omega_2) \hat{\bm X}=(\dot{X} + a\omega_2) \hat{\bm X}$

### Moment cinétique:

Seuls les diques on un moment cinétique. Pour 1 en $O_1$:
$$\bm L_1 = I \bm \omega_1 = \frac{ma^2}{2}\omega_1 \hat{\bm Z}$$
De même pour 2 en $O_2$:
$$\bm L_2 = \frac{ma^2}{2}\omega_2 \hat{\bm Z}$$
suele la composante $I_z$ du moment d'inertie du disque compte.

### Forces:

* réaction en $C_1$, $\bm R_1 = T_1 \hat{\bm X} + N_1 \hat{\bm Z}$
* réaction en $C_2$, $\bm R_2 = T_2 \hat{\bm X} + N_2 \hat{\bm Z}$. Loi de frottement $T_2=-\mu N_2$, le signe moins indique la direction opposée au mouvement (selon $\hat{\bm X}$)
* poids, applqué en $G$: $-mg \hat{\bm Z}$

### Roulement

La condition de non glissement en $C_1$ determine la contrainte $T_1$. En effet, le {\D t} de $\bm T_1$ satisfait l'équation d'Euler:
$$\frac{\D \bm L}{\D t} = a \hat{\bm Y} \times T_1 \hat{\bm X}$$
ce qui donne:
\begin{equation}
\frac{ma^2}{2} \dot\omega_1 = -aT_1
\end{equation}

### Moments de forces au centre de masse $G$:

Les réactions $\bm R_{1,2}$ contribuent au moment des forces; le poids non.

Au point $C_1$ on a:
\begin{align*}
\bm M_1 &= (l - 2d + X(t))\hat{\bm X} \times N_1 \hat{\bm Y}  + h \hat{\bm Y} \times T_1 \hat{\bm X} \\
&= [(l - 2d + X(t)) N_1 + h T_1] \hat{\bm Z}
\end{align*}
et en $C_2$,
$$\bm M_2 = [(l- X(t)) N_2 + h T_2]\hat{\bm Z}$$
Le moment total en $G$ s'annule:
\begin{equation}
h(T_1 + T_2) + (l - 2d - X(t)) N_1 + (l- X(t)) N_2 = 0
\end{equation}

### Newton:

\begin{equation}
m\ddot{X} = T_1 + T_2
\end{equation}
et
\begin{equation}
0 = -mg + N_1 + N_2
\end{equation}

### Euler:

On a quatre équation pour $T_1,N_1$ et $T_2,N_2$.

En utilisant l'équation de Newton on exprime dans un premier temps toutes les grandeurs en fonction de $N_2$:
\begin{align*}
N_1 & = mg - N_2 \\
T_1 & = m \ddot{X} + \mu N_2 \\
T_2 &= - \mu N_2 \\
\end{align*}
on remplace $T_1 = -ma\ddot{X}/2$,
$$N_2 = -\frac{3m}{2\mu} \ddot{X}$$
(l'accelération est négative, donc $N_2>0$). La substituion dans l'équilibre de moments donne:
$$-\frac{mh}{2}(1+3\mu) \ddot{X} + (l - 2d - X)\bigg(mg + \frac{3m}{2\mu} \ddot{X}\bigg) - (l- X)\frac{3m}{2\mu} \ddot{X} = 0 $$
Après regroupement on a:
$$\ddot X(t) + \Omega^2 X(t) = K$$
avec
$$\Omega^2 = \frac{\mu g}{3d + \mu h}$$
et la constante
$$K = (l - 2d)\Omega^2$$

Observez que $\Omega \sim\mu$: sans frottement on n'a pas de freinage.

## Graphe du mouvement

On crée d'abord un style pour les figures en utilisan $\LaTeX$.

ATTENTION il vous faut latex

```python
# figure style
matplotlib.rcParams['text.usetex'] = True
matplotlib.rcParams['font.family'] = 'serif'
matplotlib.rcParams['font.weight'] = 'normal'
matplotlib.rcParams['font.size'] = 14
matplotlib.rcParams['axes.labelsize'] = 18
matplotlib.rcParams['figure.figsize'] = (6.0,4.5)
matplotlib.rcParams['lines.linewidth'] = 0.5
matplotlib.rcParams['xtick.top'] = True
matplotlib.rcParams['ytick.right'] = True
```

```python
# Example de figure:
x = np.linspace(-3,3,129)
y = np.exp(-np.abs(x/3))*np.cos(x**2)

fig, ax = plt.subplots()
ax.plot(x, y, '-', color = '#800')
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$f(x)$");
```

## Solution $X = X(t)$

On pose $\Omega = 1$, ce qui fixe l'unité de temps. On pose $d = 1$, ce qui fixe l'unité de longueur. Danc ces unités l'équation devient
$$\ddot x + x = 0, \quad x = X - K, \; K = l - 2$$
Rappelons nous que $\dot{x} >0$, donc le mouvement s'arrête quad la vitesse devient nulle.

La trajectoire est
$$x = x(t) = \sin(t), \quad t < \pi/2$$
et la vitesse du centre de masse:
$$\dot x(t) = \cos(t), \quad t < \pi/2$$
où on a supposé que $v = 1$ est la vitesse initiale.

Notez que
$$X = l - 2d$$
semble être une solution aussi, sauf qu'on cherche des solutions avec vitesse positive (et $X>0$).

On fait le graphe de la trajectoire.

```python
# Example de figure:
t = np.linspace(0,np.pi/2,129)
x = np.sin(t)
v = np.cos(t)

fig, ax = plt.subplots()
ax.plot(t, x, '-', color = '#800', label = r"$(X-K)/d$")
ax.plot(t, v, '-', color = '#008', label = r"$v/\Omega d$")
ax.set_xlabel(r"$\Omega t$")
ax.legend(loc=6, fontsize = 16);
```

```python

```