# Cat vs coherent states in a Kerr resonator, and the role of measurement
$\newcommand{\ket}[1]{| #1 \rangle}$
$\newcommand{\bra}[1]{\langle #1 |}$
$\newcommand{\braket}[1]{\langle #1 \rangle}$
$\newcommand{\CC}{\mathcal{C}}$
Author: F. Minganti (minganti@riken.jp)

In this notebook we show how the same system can produce extremely different results according to the way an observer collects the emitted field of a resonator. This notebook closely follows the results obtained in Refs. [1-3].

```python
import matplotlib.pyplot as plt
import numpy as np
from qutip import *
from IPython.display import display, Math, Latex
```

## The two-photon Kerr Resontator

Let us consider a single nonlinear Kerr resonator subject
to a parametric two-photon driving.
In a frame rotating at the pump frequency, the Hamiltonian reads
\begin{equation}\label{Eq:Hamiltonian}
\hat{H}
=\frac{U}{2}\,\hat{a}^\dagger\hat{a}^\dagger\hat{a}\hat{a}
+\frac{G}{2}\left(\hat{a}^\dagger\hat{a}^\dagger+\hat{a}\hat{a}\right),
\end{equation}
where $U$ is the Kerr photon-photon interaction strength, $G$ is the two-photon driving amplitude, and $\hat{a}^\dagger$ ($\hat{a}$) is the bosonic creation (annihilation) operator.

The time dynamics  of the density matrix $\hat{\rho}$ of this sytem is given by a Lindblad master equation $i \partial_t \hat{\rho} = \mathcal{L} \hat{\rho}$, where $\mathcal{L}$ is the Liouvillian superoperator.
The superoperator $\mathcal{L}$ is made of an Hamiltonian part and
a non-hermitian contribution, which describe the dissipation of energy, particle and information into the environment,  as detailed
in e.g. [5].

Given the parametric drive, the dissipation processes include one- and two-photon dissipation, and the Lindblad superoperator become
\begin{equation}\label{Eq:Lindblad}
\mathcal{L} \hat{\rho} = - i \left[\hat{H},\hat{\rho}\right]
+\frac{\gamma}{2} \left(2\hat{a}\hat{\rho}\hat{a}^\dagger
-\hat{a}^\dagger\hat{a}\hat{\rho}
-\hat{\rho}\hat{a}^\dagger\hat{a}\right)
+ \, \frac{\eta}{2} \left(2\hat{a}\hat{a}\hat{\rho}\hat{a}^\dagger\hat{a}^\dagger
-\hat{a}^\dagger\hat{a}^\dagger\hat{a}\hat{a}\hat{\rho}
-\hat{\rho}\hat{a}^\dagger\hat{a}^\dagger\hat{a}\hat{a}\right),
\end{equation}
where $\gamma$ and $\eta$ are, respectively, the one- and two-photon dissipation rates.

We define the system parameters in the following cells.

```python
font_size=20
label_size=30
title_font=35
```

```python
a=destroy(20)
U=1
G=4
gamma=1
eta=1
H=U*a.dag()*a.dag()*a*a + G*(a*a + a.dag()*a.dag())
c_ops=[np.sqrt(gamma)*a,np.sqrt(eta)*a*a]

parity=1.j*np.pi*a.dag()*a
parity=parity.expm()

rho_ss=steadystate(H, c_ops)
```

This model can be solved exactly for its steady state [2,3].
The corresponding density matrix $\hat{\rho}_{\rm ss}$ is well approximated by the statistical mixture of two orthogonal states:
\begin{equation}\label{Eq:MixtureCats}
\hat{\rho}_{\rm ss}\simeq
p^+\,\ket{\CC^+_\alpha}\!\bra{\CC^+_\alpha}
+p^-\,\ket{\CC^-_\alpha}\!\bra{\CC^-_\alpha},
\end{equation}
where $\ket{\CC^\pm_\alpha}\propto\ket{\alpha}\pm\ket{-\alpha}$ are photonic Schr\"odinger cat states whose complex amplitude $\alpha$ is determined by the system parameters [2-4].
We recall that the coherent state $\ket{\alpha}$ is the eigenstate of the destruction operator: $\hat{a} \ket{\alpha}=\alpha \ket{\alpha}$.
The state $\ket{\CC^+_\alpha}$ is called the even cat, since it can be written as a superposition of solely even Fock states, while $\ket{\CC^-_\alpha}$ is the odd cat.
In the previous equation, the coefficients $p^\pm$ can be interpreted as the probabilities of the system of being found in the corresponding cat state.

Below, we demonstrate this feature by diagonalising the steady-state density matrix, and by plotting the photon-number probability for the two most probable states.

```python
vals, vecs = rho_ss.eigenstates(sort='high')
print("The mean number of photon is " + str(expect(a.dag()*a, rho_ss)))

plt.figure(figsize=(8, 6))
plt.rc('text', usetex=True)
plt.rc('font', family='serif', size=font_size)
plt.semilogy(range(1,7),vals[0:6], 'rx')
plt.xlabel('Eigenvalue', fontsize=label_size)
plt.ylabel('Probability', fontsize=label_size)
plt.title('Distribution of the eigenvalues',fontsize=title_font)
plt.show()
```

```python
state_zero=vecs[0].full()
state_one=vecs[1].full()

plt.figure(figsize=(8, 6))
plt.rc('text', usetex=True)
plt.rc('font', family='serif', size=font_size)
plt.plot(range(0,20), [abs(i)**2 for i in state_zero[0:20]], 'rx', label='First state')
plt.plot(range(0,20), [abs(i)**2 for i in state_one[0:20]], 'bo', label='Second state')
plt.legend()
plt.xlabel('Eigenvalue', fontsize=label_size)
plt.ylabel('Probability', fontsize=label_size)
plt.show()
```

Correctly, the two system have opposite parity. Indeed, for sufficiently intense pumping ($G> U,\gamma,\eta$ and $|\alpha|\gg1$), it was shown in [2] that  $p^+\simeq p^- \simeq 1/2$.
However, in this strong-pumping regime, the steady-state can be recast as
\begin{equation}\label{Eq:MixtureCoherent}
\hat{\rho}_{\rm ss}\simeq
\frac{1}{2}\ket{\alpha}\!\bra{\alpha}
+\frac{1}{2}\ket{-\alpha}\!\bra{-\alpha}.
\end{equation}
Hence, the steady state can be seen as well as a statistical mixture of two coherent states of opposite phase.
Since $\hat{\rho}_{\rm ss}$ is anyhow a mixture of two (quasi-)orthogonal states, the steady state is bimodal.
Such a bimodality can be visualised, for instance, through the Wigner function [2,3].
Now, the pivotal question is:  if one monitors the evolution of the system, in which states can it be observed?
The orthogonal cat states, the two coherent states with opposite phases, or none of them in particular?
As we will show in the following, the answer dramatically depends on the type of measurement scheme employed to monitor the trajectory of the system.

```python
xvec=np.linspace(-4,4, 500)
W_even=wigner(vecs[0], xvec, xvec, g=2)
W_odd=wigner(vecs[1], xvec, xvec, g=2)
```

```python
W_ss=wigner(rho_ss, xvec, xvec, g=2)
W_ss=np.around(W_ss, decimals=2)
plt.figure(figsize=(10, 8))

plt.contourf(xvec,xvec, W_ss, cmap='RdBu', levels=np.linspace(-1, 1, 20))
plt.colorbar()
plt.xlabel(r'Re$(\alpha)$', fontsize=label_size)
plt.ylabel(r'Im$(\alpha)$', fontsize=label_size)
plt.title("Steady state", fontsize=title_font)
plt.show()./images/
```

```python
W_even=np.around(W_even, decimals=2)
plt.figure(figsize=(10, 8))
plt.contourf(xvec,xvec, W_even, cmap='RdBu', levels=np.linspace(-1, 1, 20))
plt.colorbar()
plt.xlabel(r'Re$(\alpha)$', fontsize=label_size), width="300"
plt.ylabel(r'Im$(\alpha)$', fontsize=label_size)
plt.title("First state: even cat-like", fontsize=title_font)
plt.show()
```

```python
W_odd=np.around(W_odd, decimals=2)
plt.figure(figsize=(10, 8))

plt.contourf(xvec,xvec, W_odd, cmap='RdBu', levels=np.linspace(-1, 1, 20))
plt.colorbar()
plt.xlabel(r'Re$(\alpha)$', fontsize=label_size)
plt.ylabel(r'Im$(\alpha)$', fontsize=label_size)
plt.title("Second state: odd cat-like", fontsize=title_font)
plt.show()
```

## Quantum Trajectories
From a theoretical point of view, the Lindblad master equation describes the out-of-equilibrium dynamics of a system coupled to a Markovian (i.e., memoryless) environment.
Indeed, the density matrix $\hat{\rho}(t)$ solving the Lindblad equation encodes the average evolution of the system when no information is collected about environment state.
However, one can imagine to keep track of the system state by continuously probing the environment.
	Doing so, the time evolution of the system would change at each realisation.
	However, $\hat{\rho}(t)$ can be retrieved by averaging over an infinite number of such "monitored" realisations.
    The Monte Carlo wavefunction method has been developed relying exactly on this idea.
	It is based on the stochastic simulation of the system evolution when one continuously gathers information from the environment.
Each simulation of the stochastic evolution of the system gives a single quantum trajectory.
The results obtained by solving the master equation are recovered by averaging over many trajectories.
In order to simulate the quantum trajectories, it is necessary  to explicitly model how an observer measures the environment, thus affecting the system evolution itself (a detailed  discussion on this subject is given in [5].
Interestingly, several different measures can be associated with the same master equation.
Depending on the chosen measurement, contrasting results and interpretations can emerge.
Those incompatibilities are, however, harmonized once the mean value over many trajectories is taken.

$\newcommand{\ket}[1]{| #1 \rangle}$
$\newcommand{\bra}[1]{\langle #1 |}$
$\newcommand{\CC}{\mathcal{C}}$
### Photon counting

The most natural way to observe the exchanges between the Kerr resonator and the environment is to just detect every leaked photon (both individually and by couples).
This mechanism is described via the action of the one-photon jump operator $\hat{J}_1=\sqrt{\gamma}\, \hat{a}$ and the two-photon one $\hat{J}_2=\sqrt{\eta}\, \hat{a}^2$, which describe the absorption of one or two photons by an ideal photodetector (details in e.g. [6]).
Indeed, in typical realisations (e.g. [4]) the one- and two-photon dissipation channels are discernible.
Hence, we can assume that the photodetector is capable of distinguishing between one- and two-photon losses.
The photon-counting trajectory is then obtained by using the "mcsolve" function of qutip.
In conclusion, a photon-counting trajectory is characterised by abrupt jumps corresponding to the projective measure associated to the detection of one or two photons.

```python
tlist=np.linspace(0,20,2000)

sol_mc=mcsolve(H, fock(20,0), tlist, c_ops, [a.dag()*a, (a+a.dag())/2, -1.j*(a-a.dag())/2, parity], ntraj=1)
```

    100.0%. Run time:   0.38s. Est. time left: 00:00:00:00
    Total run time:   0.44s

```python
plt.figure(figsize=(18, 8))
plt.subplot(311)
plt.plot(tlist, sol_mc.expect[0])
plt.ylabel(r'$\langle \hat{a}^\dagger \hat{a} \rangle$', fontsize=label_size)
plt.xlim([0,20])
plt.subplot(312)
plt.plot(tlist, sol_mc.expect[3])
plt.ylabel(r'$\langle \hat{P} \rangle$', fontsize=label_size)
plt.xlim([0,20])
plt.subplot(313)
plt.plot(tlist, sol_mc.expect[1], label=r'$\langle \hat{x} \rangle$')
plt.plot(tlist, sol_mc.expect[2], label=r'$\langle \hat{p} \rangle$')
plt.xlabel(r'$\gamma t$', fontsize=label_size)
plt.xlim([0,20])
plt.ylim([-3,3])
plt.legend()
plt.show()
```

As shown in [2], the Hamiltonian $\hat{H}$ and the two-photon dissipation tend to stabilize photonic cat states.
On the other hand, the annihilation operator switches from the even (odd) cat to the odd (even) one:  $\hat{a}\ket{\CC^\pm_\alpha} \propto \alpha \ket{\CC^\mp_\alpha}$.
The operator $\hat{J}_1$ thus induces jumps between the two cat states at a rate proportional to $\gamma \braket{\hat{a}^\dagger \hat{a}}$.
This picture is very well captured in the framework of photon-counting trajectories, an example of which is given in the previous figure.
The cat states are, indeed, orthogonal eigenstates of the parity operator $\hat{\mathcal{P}}=e^{i \pi \hat{a}^\dagger \hat{a}}$ with eigenvalues $\pm1$.
As we can see, along a single trajectory the state intermittently and randomly switches between the two cat states.
We stress that, instead, the mean values of the field quadratures $\hat{x}=\left(\hat{a}^\dagger+\hat{a}\right)/2$ and $\hat{p}=i\left(\hat{a}^\dagger-\hat{a}\right)/2$ are practically zero along the trajectory, as expected for any cat state.
The parity, hence, appears to be the appropriate observable to detect a bimodal behaviour in a photon-counting environment.
Thus, we may interpret
$$\hat{\rho}_{\rm ss}\simeq
p^+\,\ket{\CC^+_\alpha}\!\bra{\CC^+_\alpha}
+p^-\,\ket{\CC^-_\alpha}\!\bra{\CC^-_\alpha}$$
as the steady-state probabilities to find the system in one of the two cat states.

The previous analysis seems to point in the direction of privileging the cat states over the coherent ones as the more truthful picture of the steady state.

### Homodyne

Another possible way to monitor a quantum-optical system is through homodyne detection, a widely-used experimental technique which allows to access the field quadratures [5-6].
To implement this kind of measurement, the cavity output field is mixed to the coherent field of a reference laser through a beam splitter (here assumed of perfect transmittance).
Then, the mixed fields are probed via (perfect) photodectors, whose measures are described by new jump operators.
We stress that both the coherent and the cavity fields are measured simultaneously.

In our case, we want to probe independently the two dissipation channels.
To distinguish between one- and two-photon losses, one can exploit a nonlinear element acting on the cavity output field.
Indeed, in experimental realisations such as [4], a nonlinear element is already part of the system and is the key ingredient to realise two-photon processes.
More specifically, one-photon losses are due to the finite quality factor of the resonator.
They can be probed by directly mixing the output field of the cavity with a coherent beam of amplitude $\beta_1$ acting as local oscillator.
Therefore, the homodyne jump operator for one-photon losses can be cast as $\hat{K}_1=\hat{J}_1 +\beta_1 \hat{1}$.
Two-photon losses are, instead, mediated by a nonlinear element (a Josephson junction in [4]), which converts two cavity photons of frequency $\omega_c$ into one photon of frequency $\omega_{nl}$. Hence, the field coming out of the nonlinear element can be probed by a second independent oscillator.
This whole process can be seen as the action of a nonlinear beam splitter which mixes couples of dissipated photons with a reference oscillator of amplitude $\beta_2$.
Therefore, the homodyne two-photon jump operator takes the form $\hat{K}_2=\hat{J}_2 +\beta_2 \hat{1}$.
Without loss of generality, in the following, we assume the amplitudes $\beta_{1,2}$ to be real [6].

In the ideal limit $\beta_{1,2}\to\infty$, the system evolves diffusively according to a homodyne stochastic Schr\"odinger equation.
Using the ssesolve function with option "method='homodyne'", one can simulate the trajectory.

```python
tlist=np.linspace(0,8000,800)

sol_hom=ssesolve(H, fock(20,0), tlist, c_ops, [a.dag()*a, (a+a.dag())/2, -1.j*(a-a.dag())/2, parity],ntraj=1,nsubsteps=9500, store_measurement=False, method='homodyne')
```

    Total run time: 636.04s

```python
plt.figure(figsize=(18, 8))
plt.subplot(311)
plt.plot(tlist, sol_hom.expect[0])
plt.ylabel(r'$\langle \hat{a}^\dagger \hat{a} \rangle$', fontsize=label_size)
plt.xlim([0,8000])
plt.subplot(312)
plt.plot(tlist, sol_hom.expect[3])
plt.ylabel(r'$\langle \hat{P} \rangle$', fontsize=label_size)
plt.xlim([0,8000])
plt.subplot(313)
plt.plot(tlist, sol_hom.expect[1], label=r'$\langle \hat{x} \rangle$')
plt.plot(tlist, sol_hom.expect[2], label=r'$\langle \hat{p} \rangle$')
plt.xlabel(r'$\gamma t$', fontsize=label_size)
plt.xlim([0,8000])
plt.ylim([-3,3])
plt.legend()
plt.show()
```

We see that the mean parity $\braket{\hat{\mathcal{P}}}$
is confined around zero along a single homodyne trajectory, in spite of the
"switching cat" picture.
These fluctuations are due to the diffusive nature of the homodyne trajectory, which rules
the stochastic time evolution of the system wave function under homodyne detection.

The bimodal behaviour, instead, is clear in the time evolution of $\braket{\hat{x}}$ and $\braket{\hat{p}}$.
This appears compatible with the picture given by
$\hat{\rho}_{\rm ss}\simeq
\frac{1}{2}\ket{\alpha}\!\bra{\alpha}
+\frac{1}{2}\ket{-\alpha}\!\bra{-\alpha}$: at the steady state the system switches between the coherent states $\ket{\pm\alpha}$.
We point out that the phase switches observed for homodyne trajectories have a much smaller rate than parity
switches in photon-counting trajectories. This is a consequence of the metastable nature of the
coherent states $\ket{\pm\alpha}$ [1-4].

# Conciling the two points of view

Summing up, we have shown that the behaviour of the system along a single quantum trajectory dramatically depends on the measurement protocol adopted.
For photon-counting measurements on the environment, the system switches between the parity-defined cat states, while under homodyne detection, the states explored along a single quantum trajectory are the coherent ones.

In other words, one may assign a physical meaning to the probabilities appearing in the mixed-state representation of $\hat{\rho}_{\rm ss}$ only upon specification of the single-trajectory protocol.
However, any possible controversy at the single-trajectory level is washed out by averaging over many of them.

```python
tlist=np.linspace(0,3,100)
sol_mc_mean=mcsolve(H, fock(20,0), tlist, c_ops, [a.dag()*a, (a+a.dag())/2, -1.j*(a-a.dag())/2, parity], ntraj=50)
sol_hom_mean=ssesolve(H, fock(20,0), tlist, c_ops, [a.dag()*a, (a+a.dag())/2, -1.j*(a-a.dag())/2, parity],ntraj=50,nsubsteps=350, store_measurement=False, method='homodyne')
```

    10.0%. Run time:   0.24s. Est. time left: 00:00:00:02
    20.0%. Run time:   0.32s. Est. time left: 00:00:00:01
    30.0%. Run time:   0.42s. Est. time left: 00:00:00:00
    40.0%. Run time:   0.50s. Est. time left: 00:00:00:00
    50.0%. Run time:   0.59s. Est. time left: 00:00:00:00
    60.0%. Run time:   0.64s. Est. time left: 00:00:00:00
    70.0%. Run time:   0.72s. Est. time left: 00:00:00:00
    80.0%. Run time:   0.81s. Est. time left: 00:00:00:00
    90.0%. Run time:   0.89s. Est. time left: 00:00:00:00
    100.0%. Run time:   0.96s. Est. time left: 00:00:00:00
    Total run time:   0.98s
    10.0%. Run time:  14.31s. Est. time left: 00:00:02:08
    20.0%. Run time:  28.11s. Est. time left: 00:00:01:52
    30.0%. Run time:  42.01s. Est. time left: 00:00:01:38
    40.0%. Run time:  57.06s. Est. time left: 00:00:01:25
    50.0%. Run time:  71.03s. Est. time left: 00:00:01:11
    60.0%. Run time:  85.84s. Est. time left: 00:00:00:57
    70.0%. Run time:  99.69s. Est. time left: 00:00:00:42
    80.0%. Run time: 113.37s. Est. time left: 00:00:00:28
    90.0%. Run time: 127.39s. Est. time left: 00:00:00:14
    Total run time: 141.30s

```python
plt.figure(figsize=(18, 8))
plt.subplot(311)
plt.plot(tlist, sol_mc_mean.expect[0], 'r', label='Conunting')
plt.plot(tlist, sol_hom_mean.expect[0], 'b', label='Homodyne')
plt.ylabel(r'$\langle \hat{a}^\dagger \hat{a} \rangle$', fontsize=label_size)
plt.xlim([0,3])
plt.legend()
plt.subplot(312)
plt.plot(tlist, sol_mc_mean.expect[3], 'r')
plt.plot(tlist, sol_hom_mean.expect[3], 'b')
plt.ylabel(r'$\langle \hat{P} \rangle$', fontsize=label_size)
plt.xlim([0,3])
plt.subplot(313)
plt.plot(tlist, sol_mc_mean.expect[2], 'r')
plt.plot(tlist, sol_hom_mean.expect[2], 'b')
plt.ylabel(r'$\langle \hat{p} \rangle$', fontsize=label_size)
plt.xlim([0,3])
plt.ylim([-2,2])
```

## References

[1] N. Bartolo, F. Minganti 1 , J. Lolli, and C. Ciuti,
Homodyne versus photon-counting quantum trajectories for dissipative Kerr resonators
with two-photon driving, The European Physical Journal Special Topics 226, 2705 (2017).
The European Physical Journal Special Topics 226, 2705 (2017).

[2] F. Minganti, N. Bartolo, J. Lolli, W. Casteels, and C. Ciuti,
Exact results for Schrödinger cats in driven-dissipative systems and their feedback control, Scientific Reports 6, 26987 (2016).

[3] N. Bartolo, F. Minganti, W. Casteels, and C. Ciuti,
Exact steady state of a Kerr resonator with one- and two-photon driving and dissipation: Controllable Wigner-function multimodality and dissipative phase transitions, Physical Review A 94, 033841 (2016).

[4] Z. Leghtas et al., Confining the state of light to a quantum manifold by
engineered two-photon loss, Science 347, 853 (2015).

[5] S. Haroche and J. M. Raimond, Exploring the Quantum: Atoms, Cavities, and Photons
(Oxford University Press, 2006).

[6] H. Wiseman and G. Milburn, Quantum Measurement and Control (Cambridge University Press, 2010).

```python
qutip.about()
```

    QuTiP: Quantum Toolbox in Python
    Copyright (c) 2011 and later.
    A. J. Pitchford, P. D. Nation, R. J. Johansson, A. Grimsmo, and C. Granade

    QuTiP Version:      4.3.1
    Numpy Version:      1.15.4
    Scipy Version:      1.1.0
    Cython Version:     0.29.2
    Matplotlib Version: 3.0.2
    Python Version:     3.7.1
    Number of CPUs:     6
    BLAS Info:          INTEL MKL
    OPENMP Installed:   False
    INTEL MKL Ext:      True
    Platform Info:      Linux (x86_64)
    Installation path:  /home/test/anaconda3/lib/python3.7/site-packages/qutip
    ==============================================================================
    Please cite QuTiP in your publication.
    ==============================================================================
    For your convenience a bibtex file can be easily generated using `qutip.cite()`

```python

```


```
import numpy as np
import scipy
import scipy.stats as st
import matplotlib.pyplot as plt
import math
```

```
def pert(xmin, xmode=math.nan, xmax=math.nan, conf=4):
    if math.isnan(xmode) or math.isnan(xmax):
        xmode = xmin
        xmax = xmin
    if xmin == xmax:
        xmin = xmode * 0.99999999
        xmax = xmode * 1.00000001
    if xmax - xmin == 0:
        result = scipy.stats.uniform(xmin, np.finfo(float).eps)
    else:
        a = conf * (xmode - xmin) / (xmax - xmin)
        b = conf * (xmax - xmode) / (xmax - xmin)
        result = scipy.stats.beta(a + 1, b + 1, xmin, (xmax - xmin))
    result.min, result.max, result.mode = xmin, xmax, xmode
    result.range = np.linspace(xmin, xmax, 100)
    result.rangespace = np.linspace(xmin - (xmax - xmin) * .01, xmax + (xmax - xmin) * .01, 100000)
    result.expected = np.mean(result.ppf(np.linspace(0, 1, 10000)))
    return result

a = pert(20, 30, 70, 4)
b = pert(20, 30, 70, 20)
c = pert(20, 30, 70, 2)
d = pert(20, 30, 70, 0)
e = pert(20, 55, 70, 10)
f = pert(20, 45, 70)

x = a.rangespace

fig, ax = plt.subplots(1, 1, figsize=(4, 2.5))
plt.plot(x, a.pdf(x), linestyle="-", label="Medium")
plt.plot(x, b.pdf(x), linestyle="--", label="High")
plt.plot(x, c.pdf(x), linestyle="-.", label="Low")
plt.plot(x, d.pdf(x), linestyle=":", label="Very Low", linewidth=2)
plt.plot(x, e.pdf(x), linestyle="-", label="Very Low", linewidth=2)
plt.plot(x, f.pdf(x), linestyle="--", label="Very Low")

plt.axis([None, None, 0, None])
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

ax.set_xticklabels([])
ax.set_yticklabels([])
ax.get_yaxis().set_visible(False)
ax.plot(1, 0, ">k", transform=ax.transAxes, zorder=1, clip_on=False, markersize=5)
ax.plot(0, 1, "^k", transform=ax.transAxes, zorder=1, clip_on=False, markersize=5)

plt.tight_layout(pad=0.1)
plt.savefig("pert-examples.pdf", transparent=True)
plt.show()

from google.colab import files
files.download('pert-examples.pdf')

```

```
# Import PyDrive and associated libraries.
# This only needs to be done once in a notebook.
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials

# Authenticate and create the PyDrive client.
# This only needs to be done once in a notebook.
auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

# Create & upload a text file.
uploaded = drive.CreateFile({'title': 'Sample file.txt'})
uploaded.SetContentString('Sample upload file content')
uploaded.Upload()
print('Uploaded file with ID {}'.format(uploaded.get('id')))
```

    Uploaded file with ID 1QSr6Ds_l2XBu6SFL_vgnv100Xa6iFtQr

```
from google.colab import files

with open('example.txt', 'w') as f:
  f.write('some content')

files.download('example.txt')
```

```
a=pert(20, 30, 70, 14)
b=pert(20, 30, 70, 4)
c=pert(20, 30, 70, 2)
d=pert(20, 30, 70, 0)

x = a.rangespace

fig, ax = plt.subplots(1, 1, figsize=(3, 2.4))
plt.plot(x, a.pdf(x), linestyle="-", color="C0", label="Very high conf. (λ=14)")
plt.plot(x, b.pdf(x), linestyle="--", color="C2", label="Normal conf. (λ=4)")
plt.plot(x, c.pdf(x), linestyle="-.", color="C3", label="Low conf. (λ=2)")
plt.plot(x, d.pdf(x), linestyle=":", color="C1", label="Very low conf. (λ=0)", linewidth=2)

plt.axis([None, None, 0, None])
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.get_yaxis().set_visible(False)
ax.plot(1, 0, ">k", transform=ax.transAxes, zorder=1, clip_on=False, markersize=5)
ax.plot(0, 1, "^k", transform=ax.transAxes, zorder=1, clip_on=False, markersize=5)
legend = plt.legend(title="PERT[20-30-70, λ]:")
legend._legend_box.align = "left"
#legend.get_title().set_fontsize("large")

plt.tight_layout(pad=0.2)
plt.savefig("confidence.pdf", transparent=True)
plt.show()
```

```
#@title Example form fields
#@markdown Forms support many types of fields.

no_type_checking = ''  #@param
string_type = 'example'  #@param {type: "string"}
slider_value = 185  #@param {type: "slider", min: 100, max: 200}
number = 102  #@param {type: "number"}
date = '2010-11-05'  #@param {type: "date"}
pick_me = "monday"  #@param ['monday', 'tuesday', 'wednesday', 'thursday']
select_or_input = "apples" #@param ["apples", "bananas", "oranges"] {allow-input: true}
#@markdown ---

import numpy as np
import scipy
import scipy.stats as st
import matplotlib.pyplot as plt
import math
x = np.linspace(0, 200, 1000)
plt.plot(x, st.norm(slider_value, 50).pdf(x))

```

```javascript
%%javascript
const listenerChannel = new BroadcastChannel('channel');
listenerChannel.onmessage = (msg) => {
  const div = document.createElement('div');
  div.textContent = msg.data;
  document.body.appendChild(div);
};
```

```
# Import PyDrive and associated libraries.
# This only needs to be done once per notebook.
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials

# Authenticate and create the PyDrive client.
# This only needs to be done once per notebook.
auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

# Download a file based on its file ID.
#
# A file ID looks like: laggVyWshwcyP6kEI-y_W3P8D26sz
file_id = 'REPLACE_WITH_YOUR_FILE_ID'
downloaded = drive.CreateFile({'id': file_id})
print('Downloaded content "{}"'.format(downloaded.GetContentString()))
```

```
import IPython
js_code = '''
document.querySelector("#output-area").appendChild(document.createTextNode("hello world!"));
'''
display(IPython.display.Javascript(js_code))
```

```
# Import PyDrive and associated libraries.
# This only needs to be done once per notebook.
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials

# Authenticate and create the PyDrive client.
# This only needs to be done once per notebook.
auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

# Download a file based on its file ID.
#
# A file ID looks like: laggVyWshwcyP6kEI-y_W3P8D26sz
file_id = 'REPLACE_WITH_YOUR_FILE_ID'
downloaded = drive.CreateFile({'id': file_id})
print('Downloaded content "{}"'.format(downloaded.GetContentString()))
```

```
from google.colab import drive
drive.mount('/content/drive')
```

```javascript
%%javascript
const senderChannel = new BroadcastChannel('channel');
senderChannel.postMessage('Hello world!');
```

```
from sympy import *
n, ub, lb, mu, i, j = symbols("n ub lb \mu i j")

x = [ub, lb]
x = IndexedBase("x")

n = 2
mu = Sum(x[i], (i, 1, n))/n
display(mu), display(mu.doit())

f1 = sqrt(1/n*Sum((mu - x[j])**2, (j, 1, n)))
display(f1)
f2 = f1.doit()

N(simplify(f2))
```

$$\frac{\sum_{i=1}^{2} x_{i}}{2}$$

$$\frac{x_{1}}{2} + \frac{x_{2}}{2}$$

$$0.707106781186548 \sqrt{\sum_{j=1}^{2} \left(- x_{j} + \frac{\sum_{i=1}^{2} x_{i}}{2}\right)^{2}}$$

$$0.5 \left(\left(x_{1} - x_{2}\right)^{2}\right)^{0.5}$$

```
import scipy
import scipy.stats as st

st.norm().ppf(0.95)-st.norm().ppf(0.05)

```

$$3.289707253902945$$

```
import requests  # Import the requests library

# Query URL
url = ('http://ec.europa.eu/eurostat/wdds/rest/data/v2.1/json/en'
       '/nama_10_gdp?precision=1'
       '&unit=CLV05_MEUR'  # Unit: CLV (2005) Million EUR
       '&geo=NL&geo=DE'  # Country: Netherlands, Germany
       '&time=2010&time=2011&time=2012'  # Years: 2010, 2011, 2012
       '&na_item=B1GQ&na_item=D21'  # GDP (market prices) & taxes on products
       )
# Some api's will have nicer syntax like:
# `&time=2010..2012` or `&na_item=B1GQ,D21`
print(url)

response = requests.get(url)  # Make a GET request to the URL

# Print status code (and associated text)
print(f"Request returned {response.status_code} : '{response.reason}'")

# Print data returned (parsing as JSON)
payload = response.json()  # Parse `response.text` into JSON

import pprint
pp = pprint.PrettyPrinter(indent=1)
pp.pprint(payload)
# NOTE: Could use print(response.json()) but this wouldn't be formatted nicely
```

    http://ec.europa.eu/eurostat/wdds/rest/data/v2.1/json/en/nama_10_gdp?precision=1&unit=CLV05_MEUR&geo=NL&geo=DE&time=2010&time=2011&time=2012&na_item=B1GQ&na_item=D21
    Request returned 200 : 'OK'
    {'class': 'dataset',
     'dimension': {'geo': {'category': {'index': {'DE': 0, 'NL': 1},
                                        'label': {'DE': 'Germany (until 1990 '
                                                        'former territory of the '
                                                        'FRG)',
                                                  'NL': 'Netherlands'}},
                           'label': 'geo'},
                   'na_item': {'category': {'index': {'B1GQ': 0, 'D21': 1},
                                            'label': {'B1GQ': 'Gross domestic '
                                                              'product at market '
                                                              'prices',
                                                      'D21': 'Taxes on products'}},
                               'label': 'na_item'},
                   'time': {'category': {'index': {'2010': 0, '2011': 1, '2012': 2},
                                         'label': {'2010': '2010',
                                                   '2011': '2011',
                                                   '2012': '2012'}},
                            'label': 'time'},
                   'unit': {'category': {'index': {'CLV05_MEUR': 0},
                                         'label': {'CLV05_MEUR': 'Chain linked '
                                                                 'volumes (2005), '
                                                                 'million euro'}},
                            'label': 'unit'}},
     'extension': {'datasetId': 'nama_10_gdp',
                   'description': None,
                   'lang': 'EN',
                   'subTitle': None},
     'href': 'http://ec.europa.eu/eurostat/wdds/rest/data/v2.1/json/en/nama_10_gdp?precision=1&unit=CLV05_MEUR&geo=NL&geo=DE&time=2010&time=2011&time=2012&na_item=B1GQ&na_item=D21',
     'id': ['unit', 'na_item', 'geo', 'time'],
     'label': 'GDP and main components (output, expenditure and income)',
     'size': [1, 2, 2, 3],
     'source': 'Eurostat',
     'updated': '2020-11-17',
     'value': {'0': 2426563.7,
               '1': 2521811.0,
               '10': 59852.5,
               '11': 57924.1,
               '2': 2532364.7,
               '3': 589946.6,
               '4': 599097.8,
               '5': 592925.0,
               '6': 228301.5,
               '7': 237271.4,
               '8': 236319.0,
               '9': 60631.6},
     'version': '2.0'}


# Term Project - Milestone #2

Simon Aytes

MAT 328 - Techniques in Data Science

Spring 2022

Professor Owen

----

# 0. Environment configuration

## 0.1 Import libraries

```python
# IMPORTANT: If you do not already have the libraries installed, run this cell to install them
!pip install -r requirements.txt
```

```python
# Data handling
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
import os

# Models
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression

# Model evaluation
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
from sklearn.metrics import accuracy_score

# Feature selection
from sklearn.feature_selection import RFE

# Visualization
from sklearn.tree import plot_tree
import seaborn as sns
import matplotlib.pyplot as plt

# Environment setup
%matplotlib inline
pd.set_option('display.max_columns', None)
```

## 0.2 Utility functions

The function below uses [RFE Feature Elimination](https://machinelearningmastery.com/rfe-feature-selection-in-python/) to determine the optimal features for our models. It is set up as a dynamic function that can be used by either model and, as such, is used for both to determine their inputs.

```python
def rfe_selection(model, data):
    # Assign data-related variables
    features = ["age", "sex", "is_smoking", "cigsPerDay", "BPMeds", "prevalentStroke", "prevalentHyp", "diabetes", "totChol", "sysBP", "diaBP", "BMI", "heartRate", "glucose", "TenYearCHD"]
    df_vals = data.values

    # Identify the column names of the features
    inputs = df_vals[:,0:len(features)-1]
    response_variable = df_vals[:,len(features)-1]

    # Feature extraction
    #dt_classifier = DecisionTreeClassifier(max_depth = 5)
    rfe = RFE(model, step = 3)
    fit = rfe.fit(inputs, response_variable)

    # Create list with the target feature names
    selected_features = list(np.array(features[:-1])[np.array(fit.support_)])

    # Return a list containing the column names of the most relevant features
    return selected_features
```

# 1. Load processed data
----

```python
# Get path to the repository's data folder
data_path = "/".join(os.getcwd().split("/")[0:-1]) + "/data/"
```

```python
chd = pd.read_csv(open(data_path + "processed/data_cleaned.csv"))
chd = chd.drop(columns=["Unnamed: 0"]) # Drop the index column
```

```python
chd.head()
```

      id
      age
      education
      sex
      is_smoking
      cigsPerDay
      BPMeds
      prevalentStroke
      prevalentHyp
      diabetes
      totChol
      sysBP
      diaBP
      BMI
      heartRate
      glucose
      TenYearCHD

      0
      1
      36
      4
      1
      0
      0.0
      0
      0
      1
      0
      212.0
      168.0
      98.0
      29.77
      72.0
      75.0
      0.0

      1
      2
      46
      1
      0
      1
      10.0
      0
      0
      0
      0
      250.0
      116.0
      71.0
      20.35
      88.0
      94.0
      0.0

      2
      3
      50
      1
      1
      1
      20.0
      0
      0
      1
      0
      233.0
      158.0
      88.0
      28.26
      68.0
      94.0
      1.0

      3
      4
      64
      1
      0
      1
      30.0
      0
      0
      0
      0
      241.0
      136.5
      85.0
      26.42
      70.0
      77.0
      0.0

      4
      5
      61
      3
      0
      0
      0.0
      0
      0
      1
      0
      272.0
      182.0
      121.0
      32.80
      85.0
      65.0
      1.0

```python
chd.TenYearCHD.value_counts()
```

    0.0    2483
    1.0     444
    Name: TenYearCHD, dtype: int64

# 2. Preparing the data

----

## 2.1 Outlier detection and removal

For our model data, we want to remove any outliers within the continuous columns of our data. To do this we will use Interquartile Ranges (IQR) and, more specifically, values that lie outside of the following boundary: $(Q_{1} - 1.5) * IQR ≤ x ≤ (Q_{3} + 1.5) * IQR$.

This boundary and the relationship of outliers to the dataset can be seen in the image below.

Image source

```python
# Identify numerical columns
numerical_columns = ['age', 'cigsPerDay', 'totChol', 'sysBP', 'diaBP', 'BMI', 'heartRate', 'glucose']

# Gather quantile information
Q1 = chd[numerical_columns].quantile(0.25)
Q3 = chd[numerical_columns].quantile(0.75)
IQR = Q3 - Q1

# Remove outliers by filtering to be within the range outlined above
chd_filtered = chd[~((chd[numerical_columns] < (Q1 - 1.5 * IQR)) | (chd[numerical_columns] > (Q3 + 1.5 * IQR))).any(axis=1)]
```

```python
num_outliers = chd.shape[0] - chd_filtered.shape[0]

print(f"A total of {num_outliers} were removed from the scaled datatset.")
```

    A total of 365 were removed from the scaled datatset.

## 2.2 Normalizing the data

The data is currently a mix of binary and continuous values. When using such a dataset to make classifications, the disparate scales of the data can increase model error.

To address this, we will use a min-max scaler to normalize all of the model inputs. The equation for min-max scaling is shown below, where $X_{scaled}$ is the scaled value of $X$ given a particular dataset.

$$X_{scaled} = \frac{X - X_{min}}{X_{max} - X_{min}}$$

```python
scaler = MinMaxScaler()
```

```python
# Scale values (All except the 'id' column, as it will not be used in analysis)
chd_scaled_filtered = scaler.fit_transform(chd_filtered)
chd_scaled_filtered = pd.DataFrame(chd_scaled_filtered)

# Re-assign column names
column_names = chd_filtered.columns.to_list() #Preserve old column name
chd_scaled_filtered.columns = column_names #Rename columns to old labels

# Drop the 'id' and 'education' columns
chd_scaled_filtered = chd_scaled_filtered.drop(columns=["id", "education"])
```

```python
chd_scaled_filtered.head()
```

      age
      sex
      is_smoking
      cigsPerDay
      BPMeds
      prevalentStroke
      prevalentHyp
      diabetes
      totChol
      sysBP
      diaBP
      BMI
      heartRate
      glucose
      TenYearCHD

      0
      0.105263
      1.0
      0.0
      0.0
      0.0
      0.0
      1.0
      0.0
      0.400862
      0.836634
      0.754098
      0.697638
      0.431034
      0.437500
      0.0

      1
      0.368421
      0.0
      1.0
      0.2
      0.0
      0.0
      0.0
      0.0
      0.564655
      0.321782
      0.311475
      0.203150
      0.706897
      0.734375
      0.0

      2
      0.473684
      1.0
      1.0
      0.4
      0.0
      0.0
      1.0
      0.0
      0.491379
      0.737624
      0.590164
      0.618373
      0.362069
      0.734375
      1.0

      3
      0.842105
      0.0
      1.0
      0.6
      0.0
      0.0
      0.0
      0.0
      0.525862
      0.524752
      0.540984
      0.521785
      0.396552
      0.468750
      0.0

      4
      0.105263
      1.0
      1.0
      0.7
      0.0
      0.0
      0.0
      0.0
      0.758621
      0.183168
      0.262295
      0.612598
      0.224138
      0.250000
      0.0

## 2.3 Save processed data

```python
chd_scaled_filtered.to_csv(f"{data_path}processed/model_data.csv", index=False)
```

# 3. Decision Tree Classifier

----

## 3.1 Determine ideal depth and model inputs

To determine the ideal model depth for the training data, we will loop through multiple depths ($2 ≤ d ≤ 10$) to see which one has the lowest error.

Within each loop we will utilize [RFE Feature Elimination](https://machinelearningmastery.com/rfe-feature-selection-in-python/) to determine our inputs and ensure that we are getting the best possible results.

```python
# Create empty lists
depth_list = []
model_error = []

# Split data before evaluation for consistency in results
dt_x = chd_scaled_filtered.drop(columns = ["TenYearCHD"]) #Use columns identified above
dt_y = chd_scaled_filtered[["TenYearCHD"]]

dt_x_train, dt_x_test, dt_y_train, dt_y_test = train_test_split(dt_x, dt_y, test_size=0.2)

# Loop through each depth
for d in range(2,11):
    # Append the depth to the list
    depth_list.append(d)

    # Create model input lists
    dt_selected_features = rfe_selection(model = DecisionTreeClassifier(max_depth = d), data = chd_scaled_filtered) #Find most relevant model inputs

    # Create and fit model
    dt_classifier = DecisionTreeClassifier(max_depth = d)
    dt_classifier = dt_classifier.fit(dt_x_train[chd_scaled_filtered.columns.intersection(dt_selected_features)], dt_y_train)

    # Test model and log error
    dt_y_pred = dt_classifier.predict(dt_x_test[chd_scaled_filtered.columns.intersection(dt_selected_features)])
    model_error.append(accuracy_score(dt_y_pred, dt_y_test))
```

```python
plt.plot(depth_list, model_error)
plt.xlabel("Depth")
plt.ylabel("Accuracy")
```

From the above plot, we can see the accuracy scores at each tested depth. The highest point on the graph corresponds to the ideal depth of the model.

We can access this value by using the following list comprehension: `depth_list[model_error.index(max(model_error))]`

This code will identify the index of the maximum value in the `model_error` list which stores the accuracy scores and locate the corresponding value in the `depth_list` list.

```python
# Identify most-relevant model features
dt_selected_features = rfe_selection(model = DecisionTreeClassifier(max_depth = depth_list[model_error.index(max(model_error))]), data = chd_scaled_filtered) #Find most relevant model inputs

# Split the data into x and y
dt_x = chd_scaled_filtered[chd_scaled_filtered.columns.intersection(dt_selected_features)] #Use columns identified above
dt_y = chd_scaled_filtered[["TenYearCHD"]]
```

```python
dt_selected_features
```

    ['age', 'sex', 'cigsPerDay', 'sysBP', 'BMI', 'heartRate', 'glucose']

```python
dt_x_train, dt_x_test, dt_y_train, dt_y_test = train_test_split(dt_x, dt_y, test_size=0.2)
```

## 3.2 Create decision tree classifier

```python
# Create classifier with a depth equal to that of the lowest error
dt_classifier = DecisionTreeClassifier(max_depth = depth_list[model_error.index(max(model_error))])

# Fit the classifier with the training data
dt_classifier = dt_classifier.fit(dt_x_train, dt_y_train)
```

```python
### OPTIONAL ###
# Visualize decision tree
fig, axes = plt.subplots(nrows = 1,ncols = 1,figsize = (5,5), dpi=300)
plot_tree(dt_classifier)
```

## 3.3 Evaluate model performance

```python
# Predict test data with classifier
dt_y_pred_test = dt_classifier.predict(dt_x_test)
```

```python
# Calculate the accuracy of the model
dt_classifier_accuracy = accuracy_score(dt_y_pred_test, dt_y_test)

# Output MSE
print(f"Decision Tree Regressor Accuracy (d={depth_list[model_error.index(max(model_error))]}): {dt_classifier_accuracy}")
```

    Decision Tree Regressor Accuracy (d=3): 0.8810916179337231

```python
# Create a confusion matrix of the results
dt_cm = confusion_matrix(dt_y_test, dt_y_pred_test)

print(f"""
CONFUSION MATRIX RESULTS
True Negatives: {dt_cm[0,0]}
True Positives: {dt_cm[1,1]}

False Negatives: {dt_cm[0,1]}
False Positives: {dt_cm[1,0]}
""")
```

    CONFUSION MATRIX RESULTS
    True Negatives: 443
    True Positives: 0

    False Negatives: 0
    False Positives: 70

# 4. Logistic Regression Classifier

----

## 4.1 Feature selection

First we have to determine the ideal inputs for the model. To determine this, we will use [RFE Feature Elimination](https://machinelearningmastery.com/rfe-feature-selection-in-python/).

```python
lr_selected_features = rfe_selection(model = LogisticRegression(), data = chd_scaled_filtered)
```

```python
lr_selected_features
```

    ['age', 'sex', 'cigsPerDay', 'prevalentStroke', 'totChol', 'sysBP', 'diaBP']

From the above RFE results, we can see that the seven most relevant variables are; `age`, `sex`, `cigsPerDay`, `prevalentStroke`, `totChol`, `sysBP`, and `glucose`.

These seven features will be our inputs for this model.

```python
# Split the data into x and y
lr_x = chd_scaled_filtered[chd_scaled_filtered.columns.intersection(lr_selected_features)] #Use columns identified above
lr_y = chd_scaled_filtered[["TenYearCHD"]]
```

```python
lr_x_train, lr_x_test, lr_y_train, lr_y_test = train_test_split(lr_x, lr_y, test_size=0.2)
```

## 4.2 Create logistic regressor

```python
lr_model = LogisticRegression()
lr_model = lr_model.fit(lr_x_train, lr_y_train.values.ravel())
```

```python
lr_model_predicted = lr_model.predict(lr_x_test)
```

## 4.3 Evaluate model performance

```python
# Create a confusion matrix of the results
lr_cm = confusion_matrix(lr_model_predicted, lr_y_test)

print(f"""
CONFUSION MATRIX RESULTS
True Negatives: {lr_cm[0,0]}
True Positives: {lr_cm[1,1]}

False Negatives: {lr_cm[0,1]}
False Positives: {lr_cm[1,0]}
""")
```

    CONFUSION MATRIX RESULTS
    True Negatives: 457
    True Positives: 0

    False Negatives: 56
    False Positives: 0

```python
# Calculate the accuracy of the model
lr_model_accuracy = accuracy_score(lr_model_predicted, lr_y_test)

# Output accuracy
print(f"Logistic Regressor Accuracy: {lr_model_accuracy}")
```

    Logistic Regressor Accuracy: 0.8908382066276803

# 5. Results

----

## 5.1 Confusion matrix

When evaluating a classification model, the context is very important. Even if the accuracy score is high, that only tells you how often a model correctly classifies something, but it does not tell you _what_ it is correctly classifying.

In the context of this project, we are looking at medical data. Given the nature of medical diagnoses, errors in classification (misdiagnoses) fall into two categories of errors:

> __Type I Error:__ Reject the null hypothesis when it's actually true (false negative)

> __Type II Error:__ Fail to reject the null hypothesis when it's actually false (false positive)

Type II errors are always worse than Type I errors. In this context, a false negative could mean leading a patient to think that they are healthy when, in fact, they have developed a fatal disease that is time sensitive and could otherwise be treated.

For our final evaluation of these models, we will look at the breakdown of Type I and Type II errors across the classifiers tested.

> _**NOTE:** The confusion matrix used for this project was created by using the [sklearn.metrics.confusion_matrix](https://scikit-learn.org/stable/modules/generated/sklearn.metrics.confusion_matrix.html) function. This package outputs the confusion matrix in an inverted fashion, so true positives are located at $C_{1,1}$ rather than at $C_{0,0}$._

```python
print(f"""
CORRECT CLASSIFICATIONS
\t\tLogistic Regression\tDecision Tree
NEGATIVES: \t{lr_cm[0,0]}\t\t\t{dt_cm[0,0]}
POSITIVES: \t{lr_cm[1,1]}\t\t\t{dt_cm[1,1]}

ERROR BREAKDOWN
\t\tLogistic Regression\tDecision Tree
TYPE I: \t{lr_cm[0,1]}\t\t\t{dt_cm[0,1]}
TYPE II: \t{lr_cm[1,0]}\t\t\t{dt_cm[1,0]}
""")
```

    CORRECT CLASSIFICATIONS
    		Logistic Regression	Decision Tree
    NEGATIVES: 	457			443
    POSITIVES: 	0			0

    ERROR BREAKDOWN
    		Logistic Regression	Decision Tree
    TYPE I: 	56			0
    TYPE II: 	0			70

```python
# Get the confusion matrix breakdown of each model
lr_true_pos = lr_cm[1,1]
lr_false_pos = lr_cm[1,0]
lr_false_neg = lr_cm[0,1]
lr_true_neg = lr_cm[0,0]

dt_true_pos = dt_cm[1,1]
dt_false_pos = dt_cm[1,0]
dt_false_neg = dt_cm[0,1]
dt_true_neg = dt_cm[0,0]

# Total data points
dt_num_classifications = dt_true_pos + dt_true_neg + dt_false_neg + dt_false_pos
lr_num_classifications = lr_true_pos + lr_true_neg + lr_false_neg + lr_false_pos
```

## 5.2 Accuracy

$\begin{align}
\text{accuracy} = \frac{TP + TN}{N_{total}}
\end{align}$

```python
dt_accuracy = (dt_true_pos + dt_true_neg)/dt_num_classifications
lr_accuracy = (lr_true_pos + lr_true_neg)/lr_num_classifications
print(f"DT: {dt_accuracy}\nLR: {lr_accuracy}")
```

    DT: 0.8635477582846004
    LR: 0.8908382066276803

## 5.3 Precision

$\begin{align}
\text{precision} &= \frac{TP}{TP + FP}
\end{align}$

```python
dt_precision = dt_true_pos/(dt_true_pos + dt_false_pos)
lr_precision = lr_true_pos/(lr_true_pos + lr_false_pos)
print(f"DT: {dt_precision}\nLR: {lr_precision}")
```

    DT: 0.0
    LR: nan

    <ipython-input-36-5d104cf73cc9>:2: RuntimeWarning: invalid value encountered in long_scalars
      lr_precision = lr_true_pos/(lr_true_pos + lr_false_pos)

## 5.4 Specificity

$\begin{align}
\text{specificity} &= \frac{TN}{TN + FP}
\end{align}$

```python
dt_specificity = dt_true_neg/(dt_true_neg + dt_false_pos)
lr_specificity = lr_true_neg/(lr_true_neg + lr_false_pos)
print(f"DT: {dt_specificity}\nLR: {lr_specificity}")
```

    DT: 0.8635477582846004
    LR: 1.0

## 5.5 Sensitivity

$\begin{align}
\text{sensitivity} &= \frac{TP}{TP + FN}
\end{align}$

```python
dt_sensitivity = dt_true_pos/(dt_true_pos + dt_false_neg)
lr_sensitivity = lr_true_pos/(lr_true_pos + lr_false_neg)
print(f"DT: {dt_sensitivity}\nLR: {lr_sensitivity}")
```

    DT: nan
    LR: 0.0

    <ipython-input-38-d71825124a6f>:1: RuntimeWarning: invalid value encountered in long_scalars
      dt_sensitivity = dt_true_pos/(dt_true_pos + dt_false_neg)

## 5.6 F1 Score

$\begin{align}
F_{1}=2*\frac{precision*sensitivity}{precision + sensitivity}
\end{align}$

**NOTE:** If $TP + FP == 0$ or $TP + FN == 0$, then the F1 score will be undefined.

```python
dt_f1 = 2 * ((dt_precision * dt_sensitivity) / (dt_precision + dt_sensitivity))
lr_f1 = 2 * ((lr_precision * lr_sensitivity) / (lr_precision + lr_sensitivity))
print(f"DT: {dt_f1}\nLR: {lr_f1}")
```

    DT: nan
    LR: nan


%matplotlib inline

from IPython.display import Image

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

sns.set_style('whitegrid')
sns.set_context('notebook', font_scale=1)

$$ f_V\left(V_{CE}^{norm}\right) = \begin{cases}
  \frac{V_{max} - V_{CE}^{norm}}{V_{max} + K V_{CE}^{norm}} & \text{if $V_{CE}^{norm} < 0$} \\
  N + (N - 1) \frac{V_{max} + V_{CE}^{norm}}{7.56 K V_{CE}^{norm} - V_{max}} & \text{if $V_{CE}^{norm} \geq 0$}
\end{cases} $$

```python
c = np.log(0.05)
L_CE_opt = 1.0
w = 0.5

def f_L(L_CE_norm):
    return np.exp(c * np.power(np.absolute((L_CE_norm - L_CE_opt) / (L_CE_opt * w)), 3.0))
f_L = np.vectorize(f_L)

L_CE_norm = np.linspace(0, 2, 100)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('Length')
ax.set_ylabel('Force')
ax.plot(L_CE_norm, f_L(L_CE_norm))
```

```python
V_max = -10.0
K = 5.0
N = 1.5

def f_V(V_CE_norm):
    if V_CE_norm < 0:
        return (V_max - V_CE_norm) / (V_max + (K * V_CE_norm))
    else:
        return N + (N - 1) * ((V_max + V_CE_norm) / ((7.56 * K * V_CE_norm) - V_max))
f_V = np.vectorize(f_V)

V_CE_norm = np.linspace(-10, 10, 100)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('Velocity')
ax.set_ylabel('Force')
ax.plot(V_CE_norm, f_V(V_CE_norm))
```

```python
def f_V_inv(y):
    if y < 1:
        return (V_max * (1 - y)) / (1 + (y * K))
    else:
        return (V_max * (y - 1)) / ((7.56 * K * (y - N)) - N + 1)
f_V_inv = np.vectorize(f_V_inv)

y = np.linspace(0, 1.5, 100)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('Force')
ax.set_ylabel('Velocity')
ax.plot(y, f_V_inv(y))
```

```python
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import cm

F_max = 22.0
a = 1.0

def f_CE(L_CE_norm, V_CE_norm):
    return a * F_max * f_L(L_CE_norm) * f_V(V_CE_norm)

L_CE_norm = np.linspace(0, 2, 100)
V_CE_norm = np.linspace(-10, 10, 100)
L_CE_norm, V_CE_norm = np.meshgrid(L_CE_norm, V_CE_norm)

f_CE = np.vectorize(f_CE)
F_CE = f_CE(L_CE_norm, V_CE_norm)

fig = plt.figure(figsize=(20, 8))
ax = fig.add_subplot(1, 2, 1, projection='3d')
ax.plot_surface(L_CE_norm, V_CE_norm, F_CE, rstride=4, cstride=4, alpha=0.8, linewidth=0.25, cmap=cm.coolwarm)

cset = ax.contour(L_CE_norm, V_CE_norm, F_CE, zdir='x', offset=0, cmap=cm.coolwarm)

ax.view_init(35, -45)

ax.set_xlabel('L_CE_norm')
ax.set_ylabel('V_CE_norm')
ax.set_zlabel('F_CE')
```

```python
L_SEE_rest = 0.4
epsilon_ref = 0.04 * L_SEE_rest

def f_SEE(L_SEE):
    epsilon = (L_SEE - L_SEE_rest) / L_SEE_rest
    if epsilon > 0.0:
        return (epsilon / epsilon_ref)**2.0
    else:
        return 0.0

L_SEE = np.linspace(0, 1, 100)

f_SEE = np.vectorize(f_SEE)
F_SEE = f_SEE(L_SEE)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('L_SEE')
ax.set_ylabel('F_SEE')
ax.plot(L_SEE, F_SEE)
```

```python
L_SEE_rest = 0.4
epsilon_ref = 0.04 * L_SEE_rest

def f_SEE(L_SEE):
    epsilon = (L_SEE - L_SEE_rest) / L_SEE_rest
    if epsilon > 0.0:
        return (epsilon / epsilon_ref)**2.0
    else:
        return 0.0

L_SEE = np.linspace(0, 1, 100)

f_SEE = np.vectorize(f_SEE)
F_SEE = f_SEE(L_SEE)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_xlabel('L_SEE')
ax.set_ylabel('F_SEE')
ax.plot(L_SEE, F_SEE)
```

\begin{equation}
  \frac{\partial a}{\partial t} = c_a (u - a)
\end{equation}

$$\begin{equation}
  \tau_{k} = F_M ||r_k|| \\
  r_k = (P_{i+1}^W - P_i^W) \times \frac{s_c}{||s_c||}
\end{equation}$$

\begin{equation}
  L_{CE} = \int V_{CE} dt
\end{equation}
\begin{equation} L_{CE} = L_{CE} + V_{CE} dt \end{equation}

```python
from mpl_toolkits.mplot3d.axes3d import Axes3D
from matplotlib import cm

w = 0.6
V_max = -12.0
K = 5.0
N = 1.5

def f_PEE(L_CE_norm, V_CE_norm):
    return F_max * ((L_CE_norm - L_CE_opt) / (L_CE_opt * w)) * f_V(V_CE_norm)

L_CE_norm = np.linspace(0, 2, 100)
V_CE_norm = np.linspace(-10, 10, 100)
L_CE_norm, V_CE_norm = np.meshgrid(L_CE_norm, V_CE_norm)

f_PEE = np.vectorize(f_PEE)
F_PEE = f_PEE(L_CE_norm, V_CE_norm)

fig = plt.figure(figsize=(20, 8))
ax = fig.add_subplot(1, 2, 1, projection='3d')
ax.plot_surface(L_CE_norm, V_CE_norm, F_PEE, rstride=8, cstride=8, alpha=0.8, linewidth=0.25, cmap=cm.coolwarm)

ax.view_init(35, 135)

ax.set_xlabel('L_CE_norm')
ax.set_ylabel('V_CE_norm')
ax.set_zlabel('F_PEE')
```

```python
c_a = 0.01

def da(u, a):
    return c_a * (u - a)

t_series = np.linspace(0, 100, 1000)
u_series = np.sin(t_series - (np.pi / 2)) * 100

a_series = []
a = 0.0

prev_t = 0
for (t, u) in zip(t_series, u_series):
    dt = t - prev_t
    a += da(u, a) * dt
    prev_t = t
    a_series.append(a)

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(t_series, a_series, "g") # neural input
ax.set_xlabel('t')
ax.set_ylabel('a / u')
```

```python

```


# Laplace transform

This notebook is a short tutorial of Laplace transform using SymPy.

The main functions to use are ``laplace_transform`` and ``inverse_laplace_transform``.

```python
from sympy import *
```

```python
init_session()
```

    IPython console for SymPy 1.0 (Python 2.7.13-64-bit) (ground types: python)

    These commands were executed:
    >>> from __future__ import division
    >>> from sympy import *
    >>> x, y, z, t = symbols('x y z t')
    >>> k, m, n = symbols('k m n', integer=True)
    >>> f, g, h = symbols('f g h', cls=Function)
    >>> init_printing()

    Documentation can be found at http://docs.sympy.org/1.0/

Let us compute the Laplace transform from variables $t$ to $s$, then, we have the condition that $t>0$ (and real).

```python
t = symbols("t", real=True, positive=True)
s = symbols("s")
```

To calculate the Laplace transform of the expression $t^4$, we enter

```python
laplace_transform(t**4, t, s)
```

This function returns ``(F, a, cond)`` where ``F`` is the Laplace transform of ``f``, $\mathcal{R}(s)>a$ is the half-plane of convergence, and ``cond`` are auxiliary convergence conditions.

If we are not interested in the conditions for the convergence of this transform, we can use ``noconds=True``

```python
laplace_transform(t**4, t, s, noconds=True)
```

```python
fun = 1/((s-2)*(s-1)**2)
fun
```

```python
inverse_laplace_transform(fun, s, t)
```

Right now, Sympy does not support the tranformation of derivatives.

If we do

```python
laplace_transform(f(t).diff(t), t, s, noconds=True)
```

we don't obtain, the expected

```python
s*LaplaceTransform(f(t), t, s) - f(0)

```

or,

$$\mathcal{L}\lbrace f^{(n)}(t)\rbrace = s^n F(s) - \sum_{k=1}^{n} s^{n - k} f^{(k - 1)}(0)\, ,$$

in general.

We can still, operate with the trasformation of a differential equation.

For example, let us consider the equation

$$\frac{d f(t)}{dt} = 3f(t) + e^{-t}\, ,$$

that has as Laplace transform

$$sF(s) - f(0) = 3F(s) + \frac{1}{s+1}\, .$$

```python
eq = Eq(s*LaplaceTransform(f(t), t, s) - f(0),
        3*LaplaceTransform(f(t), t, s) + 1/(s +1))
eq
```

We then solve for $F(s)$

```python
sol = solve(eq, LaplaceTransform(f(t), t, s))
sol
```

and compute the inverse Laplace transform

```python
inverse_laplace_transform(sol[0], s, t)
```

and we verify this using ``dsolve``

```python
factor(dsolve(f(t).diff(t) - 3*f(t) - exp(-t)))
```

that is equal if $4C_1 = 4f(0) + 1$.

It is common to use practial fraction decomposition when computing inverse
Laplace transforms. We can do this using ``apart``, as follows

```python
frac = 1/(x**2*(x**2 + 1))
frac
```

```python
apart(frac)
```

We can also compute the Laplace transform of Heaviside
and Dirac's Delta "functions"

```python
laplace_transform(Heaviside(t - 3), t, s, noconds=True)
```

```python
laplace_transform(DiracDelta(t - 2), t, s, noconds=True)
```

```python

```

The next cell change the format of the notebook.

```python
from IPython.core.display import HTML
def css_styling():
    styles = open('./styles/custom_barba.css', 'r').read()
    return HTML(styles)
css_styling()
```

<link href='http://fonts.googleapis.com/css?family=Fenix' rel='stylesheet' type='text/css'>
<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=Source+Code+Pro:300,400' rel='stylesheet' type='text/css'>
<style>
/* Based on Lorena Barba template available at: https://github.com/barbagroup/AeroPython/blob/master/styles/custom.css*/
@font-face {
font-family: "Computer Modern";
src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}
div.cell{
width:800px;
margin-left:16% !important;
margin-right:auto;
}
h1 {
font-family: 'Alegreya Sans', sans-serif;
}
h2 {
font-family: 'Fenix', serif;
}
h3{
font-family: 'Fenix', serif;
margin-top:12px;
margin-bottom: 3px;
}
h4{
font-family: 'Fenix', serif;
}
h5 {
font-family: 'Alegreya Sans', sans-serif;
}
div.text_cell_render{
font-family: 'Alegreya Sans',Computer Modern, "Helvetica Neue", Arial, Helvetica, Geneva, sans-serif;
line-height: 135%;
font-size: 120%;
width:600px;
margin-left:auto;
margin-right:auto;
}
.CodeMirror{
font-family: "Source Code Pro";
font-size: 90%;
}
/* .prompt{
display: None;
}*/
.text_cell_render h1 {
font-weight: 200;
font-size: 50pt;
line-height: 100%;
color:#CD2305;
margin-bottom: 0.5em;
margin-top: 0.5em;
display: block;
}
.text_cell_render h5 {
font-weight: 300;
font-size: 16pt;
color: #CD2305;
font-style: italic;
margin-bottom: .5em;
margin-top: 0.5em;
display: block;
}
.warning{
color: rgb( 240, 20, 20 )
}
</style>


# Nested Logit Model: Compute Market Shares

```python
import pandas as pd
import numpy as np
import biogeme.database as db
import biogeme.biogeme as bio
import biogeme.models as models
import biogeme.optimization as opt
import biogeme.results as res
from biogeme.expressions import Beta, DefineVariable
import seaborn as sns
import matplotlib.pyplot as plt
```

**Import Optima data**

```python
pandas = pd.read_csv("../../Data/6-Discrete Choice Models/optima.dat",sep='\t')
database = db.Database ("data/optima", pandas)
```

**Use collumn names as variables**

```python
globals().update(database.variables)
```

**Exclude some unwanted entries**

```python
exclude = (Choice == -1.)
database.remove(exclude)
```

**Define some dummy variables**

```python
male = (Gender == 1)
female = (Gender == 2)
unreportedGender = (Gender == -1)

fulltime = (OccupStat == 1)
notfulltime = (OccupStat != 1)
```

**Rescale some data**

```python
TimePT_scaled = TimePT / 200
TimeCar_scaled = TimeCar / 200
MarginalCostPT_scaled = MarginalCostPT / 10
CostCarCHF_scaled = CostCarCHF / 10
distance_km_scaled = distance_km / 5
```

**Create parameters to be estimated**

```python
ASC_CAR              = Beta('ASC_CAR',0,None,None,0)
ASC_PT               = Beta('ASC_PT',0,None,None,1)
ASC_SM               = Beta('ASC_SM',0,None,None,0)
BETA_TIME_FULLTIME   = Beta('BETA_TIME_FULLTIME',0,None,None,0)
BETA_TIME_OTHER      = Beta('BETA_TIME_OTHER',0,None,None,0)
BETA_DIST_MALE       = Beta('BETA_DIST_MALE',0,None,None,0)
BETA_DIST_FEMALE     = Beta('BETA_DIST_FEMALE',0,None,None,0)
BETA_DIST_UNREPORTED = Beta('BETA_DIST_UNREPORTED',0,None,None,0)
BETA_COST            = Beta('BETA_COST',0,None,None,0)
```

**Define the utility functions**

\begin{align}
V_{PT} & = \beta_{PT} + \beta_{time_{fulltime}} X_{time_{PT}} X_{fulltime} + \beta_{time_{other}} X_{time_{PT}} X_{not\_fulltime} + \beta_{cost} X_{cost_{PT}} \\
V_{car} & = \beta_{car} + \beta_{time_{fulltime}} X_{time_{car}} X_{fulltime} + \beta_{time_{other}} X_{time_{car}} X_{not\_fulltime} + \beta_{cost} X_{cost_{car}} \\
V_{SM} & = \beta_{SM} + \beta_{male} X_{distance} X_{male} + \beta_{female} X_{distance} X_{female} + \beta_{unreported} X_{distance} X_{unreported}
\end{align}

```python
V_PT = ASC_PT + BETA_TIME_FULLTIME * TimePT_scaled * fulltime + \
       BETA_TIME_OTHER * TimePT_scaled * notfulltime + \
       BETA_COST * MarginalCostPT_scaled
V_CAR = ASC_CAR + \
        BETA_TIME_FULLTIME * TimeCar_scaled * fulltime + \
        BETA_TIME_OTHER * TimeCar_scaled * notfulltime + \
        BETA_COST * CostCarCHF_scaled
V_SM = ASC_SM + \
       BETA_DIST_MALE * distance_km_scaled * male + \
       BETA_DIST_FEMALE * distance_km_scaled * female + \
       BETA_DIST_UNREPORTED * distance_km_scaled * unreportedGender
```

**Associate utility functions with alternatives and associate availability of alternatives**

In this example all alternatives are available for each individual

```python
V = {0: V_PT,
     1: V_CAR,
     2: V_SM}

av = {0: 1,
      1: 1,
      2: 1}
```

**Define the nests**

1. Define the nests paramenters
2. List alternatives in nests

```python
MU_NO_CAR = Beta('MU_NO_CAR', 1.,1.,None,0)

CAR_NEST = 1., [1]
NO_CAR_NEST = MU_NO_CAR, [0, 2]

nests = CAR_NEST, NO_CAR_NEST
```

**Define the choice probabilities**

```python
prob_pt  = models.nested(V, av , nests , 0)
prob_car = models.nested(V, av , nests , 1)
prob_sm  = models.nested(V, av , nests , 2)
```

**Compute normalizing weights for each alternative**

```python
sumWeight = database.data['Weight'].sum()
normalized_Weight = Weight * len(database.data['Weight']) / sumWeight
```

**Define what we want to simulate**

1. Normalized weights
2. Choice probabilities for each choice
3. Revenues for the Public Transportation alternative

```python
simulate ={'weight':  normalized_Weight ,
           'Prob. car':  prob_car ,
           'Prob. public transportation':  prob_pt ,
           'Prob. slow modes': prob_sm ,
           'Revenue public transportation': prob_pt * MarginalCostPT}
```

**Define the Biogeme object**

```python
biogeme = bio.BIOGEME(database, simulate)
biogeme.modelName = "optima_nested_logit_market"
```

**Retrieve the names of the variables we want to use. Then retrieve the results from the model that we estimated earlier**

```python
betas = biogeme.freeBetaNames

print('Extracting the following variables:')
for k in betas:
    print('\t',k)

results = res.bioResults(pickleFile='optima_nested_logit.pickle')
betaValues = results.getBetaValues ()
```

    Extracting the following variables:
    	 ASC_CAR
    	 ASC_SM
    	 BETA_COST
    	 BETA_DIST_FEMALE
    	 BETA_DIST_MALE
    	 BETA_DIST_UNREPORTED
    	 BETA_TIME_FULLTIME
    	 BETA_TIME_OTHER
    	 MU_NO_CAR

**Perform the simulation**

```python
simulatedValues = biogeme.simulate(betaValues)
```

**Compute confidente intervals using this simulation**

```python
b = results.getBetasForSensitivityAnalysis(betas , size=100)

left, right = biogeme.confidenceIntervals(b, .9)
```

**Computed the weighted probabilities**

```python
simulatedValues['Weighted prob. car'] =simulatedValues['weight'] * simulatedValues['Prob. car']
left['Weighted prob. car'] = left['weight'] * left['Prob. car']
right['Weighted prob. car'] = right['weight'] * right['Prob. car']

simulatedValues['Weighted prob. public transportation'] =simulatedValues['weight'] * simulatedValues['Prob. public transportation']
left['Weighted prob. public transportation'] = left['weight'] * left['Prob. public transportation']
right['Weighted prob. public transportation'] = right['weight'] * right['Prob. public transportation']

simulatedValues['Weighted prob. slow modes'] =simulatedValues['weight'] * simulatedValues['Prob. slow modes']
left['Weighted prob. slow modes'] = left['weight'] * left['Prob. slow modes']
right['Weighted prob. slow modes'] = right['weight'] * right['Prob. slow modes']
```

**Compute the market shares**

```python
marketShare_car = simulatedValues['Weighted prob. car'].mean()
marketShare_car_left = left['Weighted prob. car'].mean()
marketShare_car_right = right['Weighted prob. car'].mean()

marketShare_pt = simulatedValues['Weighted prob. public transportation'].mean()
marketShare_pt_left = left['Weighted prob. public transportation'].mean()
marketShare_pt_right = right['Weighted prob. public transportation'].mean()

marketShare_sm = simulatedValues['Weighted prob. slow modes'].mean()
marketShare_sm_left = left['Weighted prob. slow modes'].mean()
marketShare_sm_right = right['Weighted prob. slow modes'].mean()
```

**Display results**

```python
print(f"Market Share for car : {100*marketShare_car:.1f}%  [{100*marketShare_car_left:.1f} % , {100*marketShare_car_right:.1f} %]")
print(f"Market Share for PT :  {100*marketShare_pt:.1f}%  [{100*marketShare_pt_left:.1f} % , {100*marketShare_pt_right:.1f} %]")
print(f"Market Share for SM :  {100*marketShare_sm:.1f}%   [{100*marketShare_sm_left:.1f} % , {100*marketShare_sm_right:.1f} %]")
```

    Market Share for car : 65.3%  [60.6 % , 69.0 %]
    Market Share for PT :  28.1%  [23.6 % , 32.2 %]
    Market Share for SM :  6.6%   [4.6 % , 10.6 %]

**Compute revenues for Public Transportation alternative**

```python
revenues_pt = ( simulatedValues['Revenue public transportation']*simulatedValues['weight']).sum()
revenues_pt_left = (left['Revenue public transportation']*left['weight']).sum()
revenues_pt_right = ( right ['Revenue public transportation']*right['weight']).sum()

print( f"Revenues for PT : {revenues_pt:.3f} [{revenues_pt_left:.3f}, {revenues_pt_right:.3f}]")
```

    Revenues for PT : 3018.342 [2420.490, 3721.883]


# Propogate Errors

This notebook takes you through the steps of how to propogate errors for through the neural network model

* required packages: `numpy h5py keras`
* data files:
    - starnet_cnn.h5
    - mean_and_std.npy
    - test_data.h5
    - apStar_combined_main.h5

```python
import numpy as np
from keras.models import load_model
import h5py
import tensorflow as tf
import time
import keras.backend as K
import subprocess

datadir= ""
```

    Using TensorFlow backend.

Define path variables for your keras model, denormalization data, and test data

```python
model_path = datadir + 'starnet_cnn.h5'
denormalization_path = datadir + 'mean_and_std.npy'
test_data_path = datadir + 'test_data.h5'
```

**Define functions to:**

1. compute the jacobian matrix
2. compute the covariance
3. compute the variance

Note: these functions can be combined into one, but they are separated here to allow users to extract intermediate results for analysis

```python
def calc_jacobian(model,spectra,denormalize=None):

    if denormalize==None:
        y_list = tf.unstack(model.output)
    else:
        y_list = tf.unstack(denormalize(model.output[0]))

    J = [tf.gradients(y, model.input) for y in y_list]

    jacobian_func = [K.function([model.input, K.learning_phase()], j_) for j_ in J]

    jacobian = np.array([jf([spectra,False]) for jf in jacobian_func])[:,0,0,:]
    '''
    for i in range(len(spectra)):
        jacobian = np.array([jf([spectra,False]) for jf in jacobian_func])[:,:,0,:,0]
        np.save('temp/temp_jacobian_'+str(i)+'.npy',jacobian)
        if i%int(0.1*len(spectra))==0:
            print('Jacobians completed: '+str(i))

    for i in range(len(spectra)):
        if i==0:
            jacobian = np.load('temp/temp_jacobian_'+str(i)+'.npy')
        else:
            jacobian = np.concatenate((jacobian,np.load('temp/temp_jacobian_'+str(i)+'.npy')))
        subprocess.check_output(['rm','temp/temp_jacobian_'+str(i)+'.npy'])
    '''
    return jacobian

def calc_covariance(model,spectra,err_spectra,denormalize=None):
    jac_matrix = calc_jacobian(model,spectra,denormalize)
    err_spectra[err_spectra > 6] = 0
    jac_matrix = np.nan_to_num(jac_matrix)
    covariance = np.einsum('ij,jl->il',(jac_matrix*(err_spectra**2)),jac_matrix.T)
    return covariance

def calc_variance(model,spectra,err_spectra,denormalize=None):
    covariance = calc_covariance(model,spectra,err_spectra,denormalize)
    return np.diagonal(covariance, offset=0)
```

** Create a denormalization function **

```python
mean_and_std = np.load(denormalization_path)
mean_labels = mean_and_std[0]
std_labels = mean_and_std[1]
num_labels = mean_and_std.shape[1]

def denormalize(lb_norm):
    return ((lb_norm*std_labels)+mean_labels)
```

**Load the StarNet model**

```python
model = load_model(model_path)
```

** Load Test Data **

The error propagation technique takes some time, so for the purpose of example, we will only use the first 100 spectra in the test set

```python
num_test = 300

f = h5py.File(test_data_path, 'r')
test_spectra = f['spectrum']
test_err_spectra = f['error_spectrum']
test_ap_ids = f['Ap_ID'][0:num_test]
test_labels = np.column_stack((f['TEFF'][0:num_test],f['LOGG'][0:num_test],f['FE_H'][0:num_test]))

print('Test set contains '  + str(len(test_ap_ids))+' stars')
```

    Test set contains 300 stars

** Compute predictions and errors for the test set **

**Steps:**
1. compute predictions

    \begin{equation}
    h_(\textbf{x},\textbf{W}) =  h_{1}(\textbf{x},\textbf{W}),...,h_{j}(\textbf{x},\textbf{W}))
    \end{equation}

        j = 3

2. compute jacobian matrix

    \begin{equation}
    Jac = \frac{\partial h_{j}(\textbf{x},\textbf{W})}{\partial \textbf{x}} =  (\frac{\partial h_{j}(\textbf{x},\textbf{W})}{\partial x_{1}},...,\frac{\partial h_{j}(\textbf{x},\textbf{W})}{\partial x_{n}})
    \end{equation}

        j = 1,...,3

        n = 7214

3. compute covariance matrix

    \begin{equation}
    Cov = Jac \times \Delta \textbf{x}^2 \times Jac^T
    \end{equation}

4. obtain propagated variance due to error spectrum from the diagonal of the covariance matrix

    \begin{equation}
    \sigma_{\mathrm{prop}}^2 \approx diag(Cov)
    \end{equation}

5. determine which region of the label-space the labels are within to obtain the intrinsic scatter in the corresponding bin. These values have been predetermined from training StarNet on synthetic data and applying it to a test set of synthetic data

    \begin{equation}
    \sigma_{\mathrm{int}}
    \end{equation}

6. combine propagated error with the intrinsic scatter term

    \begin{equation}
    \Delta h_{j} = \sqrt{\sigma_{\mathrm{prop}}^2  + \sigma_{\mathrm{int}}^2}
    \end{equation}

```python
variance = np.zeros((len(test_labels),3))
predictions = np.zeros(test_labels.shape)
print('Making predictions and computing propagated variance for '+str(len(test_labels))+' spectra')
time_start = time.time()
for i in range(len(test_labels)):
    spectrum = test_spectra[i:i+1]
    err_spectrum = test_err_spectra[i:i+1]
    variance[i] = calc_variance(model,spectrum,err_spectrum,denormalize)
    predictions[i] = denormalize(model.predict(spectrum))
    if i%int(0.1*len(test_labels))==0:
        print('\n'+str(i+1)+' completed.\n'+str(time.time()-time_start)+' seconds elapsed.')
print('\nAll '+str(i+1)+' completed.\n'+str(time.time()-time_start)+' seconds elapsed.')
f.close()

```

    Making predictions and computing propagated variance for 300 spectra

    1 completed.
    0.43507194519 seconds elapsed.

    31 completed.
    17.3262429237 seconds elapsed.

    61 completed.
    47.8530139923 seconds elapsed.

    91 completed.
    93.3576600552 seconds elapsed.

    121 completed.
    153.251788855 seconds elapsed.

    151 completed.
    230.940063953 seconds elapsed.

    181 completed.
    327.530734062 seconds elapsed.

    211 completed.
    441.528791904 seconds elapsed.

    241 completed.
    572.318181038 seconds elapsed.

    271 completed.
    721.773078918 seconds elapsed.

    All 300 completed.
    884.853347063 seconds elapsed.

** Create intrinsic scatter arrays (predetermined) **

```python
scatter_terms = np.array([[  2.85209088e+01,   2.30193645e+01,   2.10676180e+01,
          1.91357425e+01,   1.72090644e+01,   1.58693655e+01,
          1.52684102e+01,   1.42387830e+01,   1.64239293e+01,
          2.18981017e+01],
       [  3.86073715e-02,   3.04916170e-02,   2.44161726e-02,
          2.25093310e-02,   2.35929675e-02,   2.36922221e-02,
          2.58764773e-02,   2.80946934e-02,   3.34534390e-02,
          3.56641714e-02],
       [  3.90793092e-02,   2.43149947e-02,   2.25292707e-02,
          1.81974298e-02,   1.58638867e-02,   1.46142515e-02,
          1.36038125e-02,   1.25392930e-02,   1.24740228e-02,
          1.53680421e-02]])
scatter_ranges = np.array([[  3.50000000e+03,   3.95000000e+03,   4.40000000e+03,
          4.85000000e+03,   5.30000000e+03,   5.75000000e+03,
          6.20000000e+03,   6.65000000e+03,   7.10000000e+03,
          7.55000000e+03,   8.00000000e+03],
       [  0.00000000e+00,   5.00000000e-01,   1.00000000e+00,
          1.50000000e+00,   2.00000000e+00,   2.50000000e+00,
          3.00000000e+00,   3.50000000e+00,   4.00000000e+00,
          4.50000000e+00,   5.00000000e+00],
       [ -2.50000000e+00,  -2.20000000e+00,  -1.90000000e+00,
         -1.60000000e+00,  -1.30000000e+00,  -1.00000000e+00,
         -7.00000000e-01,  -4.00000000e-01,  -1.00000000e-01,
          2.00000000e-01,   5.00000000e-01]])
```

** assign each spectrum an intrinsic scatter term depending on which region of the parameter-space the prediction lies **

```python
scatter_errs = np.empty(test_labels.shape)

for i in range(scatter_terms.shape[0]):
    for j in range(scatter_terms.shape[1]):
        current_min = scatter_ranges[i,j]
        current_max = scatter_ranges[i,j+1]
        current_scatter = scatter_terms[i,j]
        index = np.where((test_labels[:,i]>current_min)&(test_labels[:,i]<current_max))[0]
        scatter_errs[index,i]=current_scatter
```

** combine the propagated error (or the square root of the variance) and intrinsic error in quadrature **

```python
total_errors = np.sqrt(variance+np.square(scatter_errs))
```

```python
# label names
label_names = ['Teff  ','log(g)','[Fe/H]']
units = ['K','dex','dex']

mean_err_total = np.mean(total_errors, axis=0)
print('Mean total statistical errors: \n')
for i, err in enumerate(mean_err_total):
      print(label_names[i]+':  '+"{0:.3f}".format(err)+' '+units[i])
```

    Mean total statistical errors:

    Teff  :  33.947 K
    log(g):  0.076 dex
    [Fe/H]:  0.024 dex

```python

```


```python
import numpy as np
import sympy as sy
from sympy import I, exp
from sympy.utilities.codegen import codegen
```

```python
z = sy.symbols("z", real=False)
r1,r2,r3, s0,s1, s2,s3 = sy.symbols("r1,r2,r3, s0,s1,s2,s3", real=True)
a, h = sy.symbols("a, h", real=True)
```

```python
hh = 0.2
pcont = -4
pd1 = np.exp(hh*pcont)
#Ac = sy.poly(z**2*(z - pd1)*(z-pd2), z )
Ac = sy.poly((z - pd1)**2, z )
Ao = sy.poly((z-a), z )
Acl = Ac*Ao

# Assume anti-aliasing filter corresponding to a delay of two sample periods
A = sy.poly( ((z-1)*(z-exp(h))).subs(h,hh), z )
B = sy.poly( ((-1+exp(h)-h)*z + 1-(1-h)*exp(h)).subs(h,hh), z)
print pd1
print A
print B
print Ao
print Ac
```

    0.449328964117
    Poly(1.0*z**2 - 2.22140275816017*z + 1.22140275816017, z, domain='RR')
    Poly(0.0214027581601699*z + 0.022877793471864, z, domain='RR')
    Poly(z - a, z, domain='ZZ[a]')
    Poly(1.0*z**2 - 0.898657928234443*z + 0.201896517994655, z, domain='RR')

```python
R = sy.poly(z + r1, z)
S = sy.poly(s0*z + s1, z)
print A*R+B*S
print Acl

dioph=(A*R+B*S-Acl).all_coeffs()
```

    Poly(1.0*z**3 + (1.0*r1 + 0.0214027581601699*s0 - 2.22140275816017)*z**2 + (-2.22140275816017*r1 + 0.022877793471864*s0 + 0.0214027581601699*s1 + 1.22140275816017)*z + 1.22140275816017*r1 + 0.022877793471864*s1, z, domain='RR[r1,s0,s1]')
    Poly(1.0*z**3 + (-1.0*a - 0.898657928234443)*z**2 + (0.898657928234443*a + 0.201896517994655)*z - 0.201896517994655*a, z, domain='RR[a]')

```python
sol=sy.solve(dioph, (r1, s0,s1))
print sol
```

    {s1: 17.1460808200645*a - 29.3065846755898, s0: -23.9942021415893*a + 36.1547059971148, r1: -0.486457894317333*a + 0.548934401117437}

```python
len(dioph)
```

    4

```python
# Reorganize solution expression for matlab code generation
sol_expr = ('PS4_ship_heading_RST_example_noaliasing', [sol[r1], sol[s0], sol[s1], Ac.subs(z, 1)/B.subs(z,1), a])
```

```python
# Export to matlab code
[(m_name, m_code)] = codegen(sol_expr, 'octave')
```

```python
print m_code
with open("/home/kjartan/Dropbox/undervisning/tec/MR2007/matlab/PS4_ship_heading_RST_example_noaliasing.m", "w") as text_file:
    text_file.write(m_code)
```

    function [out1, out2, out3, out4, out5] = PS4_ship_heading_RST_example_noaliasing(a)
      %PS4_SHIP_HEADING_RST_EXAMPLE_NOALIASING  Autogenerated by sympy
      %   Code generated with sympy 0.7.6
      %
      %   See http://www.sympy.org/ for more information.
      %
      %   This file is part of 'project'

      out1 = -0.486457894317333*a + 0.548934401117437;
      out2 = -23.9942021415893*a + 36.1547059971148;
      out3 = 17.1460808200645*a - 29.3065846755898;
      out4 = 6.84812132152482;
      out5 = a;

    end

```python
Ac.subs(z, 1)
```

    0.303238589760212

```python
(1-0.45)**4
```

    0.09150625000000003

```python
B.subs(z,1)
```

    0.0442805516320339

```python
print dioph
```

    [1.0*a + 1.0*r1 + 0.0214027581601699*s0 - 1.32274482992573, -0.898657928234443*a - 2.22140275816017*r1 + 0.022877793471864*s0 + 0.0214027581601699*s1 + 1.01950624016551, 0.201896517994655*a + 1.22140275816017*r1 + 0.022877793471864*s1]

```python
0.55**2/(0.021+0.023)
```

    6.875000000000002

```python

```


```python
%matplotlib inline
```

# Implementing an anti-aliasing technique with a model

When performing nonlinear computations on discrete grids, it is important to
choose the time window and the number of sample points large enough to prevent
aliasing errors [B2001]_.  This example demonstrates how to
implement an anti-aliasing technique directly with a model.

For a nonlinearity of order three, as for the cubic Kerr nonlinearity, an easy
anti-aliasing procedure is to extend the spectrum by a factor of two and to
proceed by zero-padding [HCL2008]_ [FCGK2005]_.  Therefore, in each step, after
the nonlinear term is evaluated in the time domain and transformed to the
Fourier domain, the upper half of the spectrum is set to zero.

Here, the symmetric split-step Fourier method is used and the propagation of a
fourth-order soliton is considered.

.. codeauthor:: Oliver Melchert <melchert@iqo.uni-hannover.de>

We first import the functionality needed to perform the sequence of numerical
experiments:

```python
import sys; sys.path.append('../../')
import numpy as np
import numpy.fft as nfft
from fmas.models import ModelBaseClass
from fmas.config import FTFREQ, FT, IFT, C0
from fmas.solver import  SySSM
from fmas.grid import Grid
from fmas.tools import plot_evolution
```

Next, we implement a model for the nonlinear Schrödinger equation.  In
particular, we here consider the standard nonlinear Schrödinger equation,
given by

\begin{align}\partial_z u = -i \frac{\beta_2}{2}\partial_t^2 u + i\gamma |u|^2 u,\end{align}

wherein $u = u(z, t)$ represents the slowly varying pulse envelope,
$\beta_2=-1$ is the second order dispersion parameter, and
$\gamma=1$ is the nonlinear parameter.
As discussed above, we here implement a simple technique allowing
to compute the nonlinear term free of aliasing errors.

```python
class NSE(ModelBaseClass):

    def __init__(self, w, b2 = -1.0, gamma = 1.):
        super().__init__(w, 0.5*b2*w*w)
        self.gamma = gamma
        # ANTI-ALIASING FILTER SETTING UPPER HALF OF SPECTRUM TO ZERO
        self._de_alias = lambda uw: np.where(np.abs(w) < 0.5 * w.max(), uw, 0j)

    @property
    def Lw(self):
        return 1j*self.beta_w

    def Nw(self, uw):
        ut = IFT(uw)
        return self._de_alias(1j*self.gamma*FT(np.abs(ut)**2*ut))
```

Next, we initialize the computational domain and use a symmetric split-step
Fourier method to propagate a single third-order soliton for six soliton
periods.
For this numerical experiment, the extend of the time domain and the number
of sample points is chosen large enough to allow for a zero padding
anti-aliasing technique without cropping important parts of the spectrum.

```python
grid = Grid( t_max = 34., t_num = 2**12)
t, w = grid.t, grid.w
model = NSE(w, b2=-1., gamma=1.)
u_0t = 4./np.cosh(t)

solver = SySSM(model.Lw, model.Nw)
solver.set_initial_condition(w, FT(u_0t))
solver.propagate(z_range = 3*np.pi/2, n_steps = 10000, n_skip = 50)
z, utz = solver.z_, solver.utz

plot_evolution( solver.z, grid.t, solver.utz,
    t_lim = (-5,5), w_lim = (-60,60), DO_T_LOG=False)
```

**References:**

.. [B2001] J.P. Boyd, Chebychev and Fourier Spectral Methods, Dover, New York (2001)

.. [HCL2008] H. Holmas, D. Clamond, H.P. Langtangen, A pseudospectral Fourier
           method for a 1D incompressible two-fluid model, Int. J. Numer.
           Meth. Fluids 58 (2008) 639, https://doi.org/10.1002/fld.1772

.. [FCGK2005] D. Fuctus, D. Clamond, J. Grue, O. Kristiansen, An efficient
           model for three-dimensional surface wave simulations Part I: Free
           space problems, J. Comp. Phys. 205 (2005) 665,
           https://doi.org/10.1016/j.jcp.2004.11.027


## Lista de Exercício 01 - Fundamentos de Matemática para Computação.

# Universidade Federal do ABC - UFABC
## Centro de Matemática, Computação e Cognição - CMCC

## Disciplina:  Fundamentos de Matemática para a Computação -  CCM-007

Responsável: Prof. Dr. Saul Leite

Estudantes: Bruno Aristimunha.

Santo André, 20 de Fevereiro de 2019

### Primeira Parte do Curso

#### Objetivos

O objetivo dessa lista é explorar e aplicar os conceitos de sala de aula sobre **Noção de Cálculo**.

Para alcançar esse objetivo a resolução dos exercícios é necessária.

```
import sympy as sp
```

---

### Exercício 01.
Mostrar usando indução que a derivada da função $f_n(x) = \frac{1}{x^n}$ é dada por $f_n'(x) = \frac{-n}{x^{n+1}}, \forall x \neq 0$ e $n \geq1$.

---

**1. Caso Base:**

Para n = 1 temos:
> $ f_1(x) = \frac{1}{x^1} = x^{-1}$,

A derivada nesse caso:

> $f'_1(x) = -\frac{1}{x^{2}} $

Logo o passo base é verdade.

** Da hipótese de indução temos:**

Assumindo que $n - 1$ é verdade temos $f'_{n-1}(x) = -\frac{(n-1)}{x^{n}}$

** Passo: **

Queremos provar para $n$ do nosso enunciado, para dessa formar termos $n -1 \implies n$.  Da hipótese de indução temos que:
$f_{n-1}(x) =\frac{1}{x^{n-1}}, \text{e } f'_{n-1} = -\frac{(n-1)}{x^n}\\$.

$\\f_n (x) = \frac{1}{x} \cdot \frac{1}{x^{n-1}} = f_1(x)\cdot f_{n-1}(x)$.

Empregando regra do produto na derivada, temos:

> $f^{'}_n(x) = f^{'}_1(x)\cdot f_{n-1}(x) + f_1(x)\cdot f^{'}_{n-1}(x)\\$

>$\qquad \quad = -\frac{1}{x^2} \cdot \frac{1}{x^{n-1}} - \frac{1}{x} \cdot \frac{(n-1)}{x^n} $

> $ \qquad \quad = -\frac{1}{x^{n+1}} - \frac{(n-1)}{x^{n+1}} =$

>$ \qquad \quad = (-1 -n +1) \frac{1}{x^{n+1}} = - \frac{n}{x^{n+1}}$

c.q.d.

```
x, n = sp.symbols("x n")
print(sp.diff((1/x**n), x))
```

    -n*x**(-n)/x

---

## Exercício 02.

Mostrar que se $f$ é diferenciável no intervalo ($a,b$) e possui um mínimo local no ponto $x_0$ então $f'(x_0) = 0$.

---

Seja $x_0$ o mínimo local. Seja um $x < x_0$, tal que $f(x) \leq f(x_0)$. Dessa forma:

> $\frac{f(x) - f(x)}{x_0 -x} \geq 0$

Quando aplicamos limite de x $\to x_0$ temos que:

> $f'_{+} (x_0) \leq 0$

Analogamente, temos $f'_{-}(x_0) \geq 0$.

Da definição temos a diferenciabilidade de $f$. Logo, os limites laterais convergem para $0$.

> $f'(x_0) = f'_{-}(x_0) = f'_{+}(x_0)$, c.q.d.

---

## Exercício 03.
Encontre o valor de $ f(x) =  \int_{-4}^{-3} \frac{x}{x^2 -2} dx$ usando o Teorema 11.

---

Supondo que $f(x)$ é contínua no intervalo definido, isso é, $x^2 -2 \neq 0$ quando $x \in[-3,-4]$ . Seja $u = x^2 -2$, logo, $du = 2x\cdot dx$, manipulando temos que: $ \frac{du}{2}=dx \cdot x$.

Substindo na nossa $f(x)$ temos:

$\frac{1}{2}\int_{7}^{14} \frac{du}{u} = \frac{1}{2} [\ln(u)]_{14}^7 \\ \qquad \qquad = \frac{1}{2} [\ln(\frac{7}{14})] = \frac{1}{2} \ln (\frac{7}{7\cdot2}) \\ \qquad \qquad = -\frac{1}{2} \ln(2)$.

```
x = sp.symbols("x")
print(sp.integrate((x/(x**2 -2)),(x,-4,-3)))
```

    -log(14)/2 + log(7)/2

## Exercício 04.
Encontre o valor de $\int_{0}^{1}(t-2)\sin({t/5})dt$ usando o Teorema 10.

---

Manipulando a função temos essas duas integrais:

$\int_{0}^{1} t\sin(t/5) - 2 \int_{0}^{1} \sin(t/5)$.

Dessa forma:

> Seja, $u(t) = t  \qquad \quad  v'(t) = \sin(t/5)$.

> Logo, $u'(t) = 1$ $\qquad$ $v(t) = -5 \cos (\frac{t}{5})$.

---
Para resolução da primeira integral usamos o Teorema 10:

> $u\cdot dv = u \cdot v - \int v \cdot du,$

> $\int_{0}^{1} t \sin(t/5) = - t \cdot 5 \cos(t/5) \vert_{0}^{1}- \int_{0}^{1} -5 \cos(t/5) $

> $\int_{0}^{1} t \sin(t/5) = - t \cdot 5 \cos(t/5) \vert_{0}^{1}+5\int_{0}^{1}  \cos(t/5) $

> $\int_{0}^{1} t \sin(t/5) = - 5 \cdot \cos(1/5) + 5 \int_{0}^{1}  \cos(t/5) $

> $\int_{0}^{1} t \sin(t/5) = -5 \cdot \cos(1/5) + 5 \cdot 5 \sin(1/5) $

---

Para resolução da segunda integral, usando o Teorema 11 temos:

> $-2 \int_{0}^1 \sin(t/5), \text{seja } w = t/5 \implies dw = dt/5 \implies dw\cdot 5 = dt$.

> $-2 \cdot 5 \int_{0}^{\frac{1}{5}} \sin (w) dw =10 \cdot \cos(w)) \vert_{0}^{1/5} = 10 (\cos(1/5)  - \cos(0)) \\ \qquad \qquad  \qquad \qquad \qquad \qquad \qquad \qquad \quad = 10 (\cos(1/5)  - 1)$

Juntando as duas integrais temos:

> $ 5 \cdot 5 \sin(1/5) -5 \cdot \cos(1/5)  + 10 (\cos(1/5)  - 1)$

```
#\int_{0}^{1}(t-2)\sin({t/5})dt
from sympy import *
t = sp.symbols("t")

print(sp.integrate(((t-2)*sin(t/5))),(t,0,1))
```

    -5*t*cos(t/5) + 25*sin(t/5) + 10*cos(t/5) (t, 0, 1)

Nota ao professor: leva muito mais tempo para escreve em $\LaTeX$.