# Realization of Recursive Filters

*This jupyter notebook is part of a [collection of notebooks](../index.ipynb) on various topics of Digital Signal Processing. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Quantization of Variables and Operations

As for [non-recursive filters](../nonrecursive_filters/quantization_effects.ipynb#Quantization-Effects), the practical realization of recursive filters may suffer from the quantization of variables and algebraic operations. The effects of [coefficient quantization](quantization_of_coefficients.ipynb) were already discussed. This section takes a look at the quantization of variables.  We limit the investigations to the recursive part of a second-order section (SOS), since any recursive filter of order $N \geq 2$ can be [decomposed into SOSs](cascaded_structures.ipynb).

The computation of the output signal $y[k] = \mathcal{H}\{ x[k] \}$ by a difference equation involves a number of multiplications and additions. As discussed already for [non-recursive filters](../nonrecursive_filters/quantization_effects.ipynb#Quantization-of-Signals-and-Operations), multiplying two numbers in a binary representation (e.g. [two's complement](https://en.wikipedia.org/wiki/Two's_complement) or [floating point](https://en.wikipedia.org/wiki/Floating_point)) requires requantization of the result to keep the word length constant. The addition of two numbers may fall outside the maximum/minimum values of the representation and may suffer from clipping.

The resulting round-off and clipping errors depend on the number and sequence of algebraic operations. These depend on the structure used for implementation of the SOSs. For ease of illustration we limit our discussion to the [direct form I and II](direct_forms.ipynb). Similar insights can be achieved in a similar manner for other structures.

### Analysis of Round-Off Errors

Round-off errors are a consequence of reducing the word length after a multiplication. In order to investigate the influence of these errors on a recursive filter, the statistical model for [round-off errors in multipliers](../nonrecursive_filters/quantization_effects.ipynb#Model-for-round-off-errors-in-multipliers) as introduced for non-recursive filters is used. We furthermore neglect clipping.

The difference equation for the recursive part of a SOS realized in direct form I or II is given as

\begin{equation}
y[k] =  x[k] - a_1 \, y[k-1] - a_2 \, y[k-2]
\end{equation}

where $a_0 = 1$, $a_1$ and $a_2$ denote the coefficients of the recursive part. Introducing the requantization after the multipliers into the difference equation yields the output signal $y_Q[k]$

\begin{equation}
y_Q[k] = x[k] - \mathcal{Q} \{ a_{1} \, y[k-1] \} - \mathcal{Q} \{ a_{2} \, y[k-2] \}
\end{equation}

where $\mathcal{Q} \{ \cdot \}$ denotes the requantizer. Requantization is a non-linear process which results in a requantization error. If the value to be requantized is much larger that the quantization step $Q$, the average statistical properties of this error can be modeled as additive uncorrelated white noise. Introducing the error into above difference equation gives

\begin{equation}
y_Q[k] = x[k] - a_1 \, y[k-1] - e_1[k] - a_2 \, y[k-2] - e_2[k]
\end{equation}

where the two white noise sources $e_1[k]$ and $e_2[k]$ are assumed to be uncorrelated to each other. This difference equation can be split into a set of two difference equations

\begin{align}
y_Q[k] &= y[k] + e[k] \\
y[k] &=  x[k] - a_1 \, y[k-1] - a_2 \, y[k-2] \\
e[k] &= - e_1[k] - e_2[k] - a_1 \, e[k-1] - a_2 \, e[k-2]
\end{align}

The first difference equation computes the desired output signal $y[k]$ as a result of the input signal $x[k]$. The second one the additive error $e[k]$ due to requantization as a result of the requantization error $- (e_1[k] + e_2[k])$ injected into the recursive filter.
The power spectral density (PSD) $\Phi_{ee}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ of the error $e[k]$ is then given as

\begin{equation}
\Phi_{ee}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = | H(\mathrm{e}^{\,\mathrm{j}\,\Omega})|^2 \cdot (\Phi_{e_1 e_1}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) + \Phi_{e_2 e_2}(\mathrm{e}^{\,\mathrm{j}\,\Omega}))
\end{equation}

According to the model for the requantization errors, their PSDs are given as $\Phi_{e_1 e_1}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = \Phi_{e_2 e_2}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = \frac{Q^2}{12}$. Introducing this together with the transfer function of the SOS yields

\begin{equation}
\Phi_{ee}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = \left| \frac{1}{1 + a_1 \, \mathrm{e}^{\,-\mathrm{j}\,\Omega} + a_2 \, \mathrm{e}^{\,-\mathrm{j}\,2\,\Omega}} \right|^2 \cdot \frac{Q^2}{6}
\end{equation}

#### Example

The following example evaluates the error $e[k] = y_Q[k] - y[k]$ for a SOS which only consists of a recursive part. The desired system response $y[k]$ is computed numerically by floating point operations with double precision, $y_Q[k]$ is computed by applying a uniform midtread quantizer after the multiplications. The system is excited by uniformly distributed white noise. Besides the PSD $\Phi_{ee}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$, the signal-to-noise ratio (SNR) $10 \cdot \log_{10} \left( \frac{\sigma_y^2}{\sigma_e^2} \right)$ in dB of the filter is evaluated.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal as sig

N = 8192  # length of signals
w = 8  # wordlength for requantization of multiplications

def uniform_midtread_quantizer(x):
    # linear uniform quantization
    xQ = Q * np.floor(x/Q + 1/2)

    return xQ

def no_quantizer(x):

    return x

def sos_df1(x, a, requantize=None):
    y = np.zeros(len(x)+2)  # initial value appended
    for k in range(len(x)):
        y[k] = x[k] - requantize(a[1]*y[k-1])  - requantize(a[2]*y[k-2])

    return y[0:-2]

# cofficients of SOS
p = 0.90*np.array([np.exp(1j*np.pi/3), np.exp(-1j*np.pi/3)])
a = np.poly(p)
# quantization step
Q = 1/(2**(w-1))

# compute input signal
x = np.random.uniform(low=-1, high=1, size=N)
# compute output signals w and w/o requantization
yQ = sos_df1(x, a, requantize=uniform_midtread_quantizer)
y = sos_df1(x, a, requantize=no_quantizer)
# compute requantization error
e = yQ-y
# Signal-to-noise ratio
SNR = 10*np.log10(np.var(y)/np.var(e))
print('SNR due to requantization: %f dB'%SNR)

# estimate PSD of requantization error
nf, Pxx = sig.welch(e, window='hamming', nperseg=256, noverlap=128)
Pxx = .5*Pxx  # due to normalization in scipy.signal
Om = 2*np.pi*nf
# compute frequency response of system
w, H = sig.freqz([1,0,0], a)

# plot results
plt.figure(figsize=(10,4))
plt.plot(Om, Pxx/Q**2 * 12, 'b', label=r'$|\hat{\Phi}_{ee}(e^{j \Omega})|$')
plt.plot(w, np.abs(H)**2 * 2, 'g', label=r'$|H(e^{j \Omega})|^2$')
plt.title('Estimated PSD and transfer function of requantization noise')
plt.xlabel(r'$\Omega$')
plt.ylabel(r'$Q^2/12$')
plt.axis([0, np.pi, 0, 100])
plt.legend()
plt.grid();
```

### Small Limit Cycles

Besides the requantization noise, recursive filters may be subject to periodic oscillations present at the output. These undesired oscillations are termed *limit cycles*. Small limit cycles emerge from the additive round-off noise due to requantization after a multiplication. The feedback in a recursive filter leads to a feedback of the requantization noise. This may lead to a periodic output signal with an amplitude range of some quantization steps $Q$, even after the input signal is zero. The presence, amplitude and frequency of small limit cycles depends on the location of poles and the structure of the filter. A detailed treatment of this phenomenon is beyond the scope of this notebook and can be found in the literature.

#### Example

The following example illustrates small limit cycles for the system investigated in the previous example. The input signal is uniformly distributed white noise till time-index $k=256$ and zero for the remainder.

```python
# compute input signal
x = np.random.uniform(low=-1, high=1, size=256)
x = np.concatenate((x, np.zeros(1024)))
# compute output signal
yQ = sos_df1(x, a, requantize=uniform_midtread_quantizer)

# plot results
plt.figure(figsize=(10, 3))
plt.plot(20*np.log10(np.abs(yQ)))
plt.title('Level of output signal')
plt.xlabel(r'$k$')
plt.ylabel(r'$|y_Q[k]|$ in dB')
plt.grid()

plt.figure(figsize=(10, 3))
k = np.arange(1000, 1050)
plt.stem(k, yQ[k]/Q)
plt.title('Output signal for zero input')
plt.xlabel(r'$k$')
plt.ylabel(r'$y_Q[k] / Q$ ')
plt.axis([k[0], k[-1], -3, 3])
plt.grid();
```

**Exercise**

* Estimate the period of the small limit cycles. How is it related to the poles of the system?
* What amplitude range is spanned?

### Large Limit Cycles

Large limit cycles are periodic oscillations of a recursive filter due to overflows in the multiplications/additions. As for small limit cycles, large limit cycles may be present even after the input signal is zero. Their level is typically in the range of the minimum/maximum value of the requantizer. Large limit cycles should therefore be avoided in a practical implementation. The presence of large limit cycles depends on the scaling of input signal and coefficients, as well as the strategy used to cope for clipping. Amongst others, they can be avoided by proper scaling of the coefficients to prevent overflow. Again, a detailed treatment of this phenomenon is beyond the scope of this notebook and can be found in the literature.

#### Example

The following example illustrates large limit cycles for the system investigated in the first example. In order to trigger large limit cycles, the coefficients of the filter have been doubled. The input signal is uniformly distributed white noise till time-index $k=256$ and zero for the remainder.

```python
def uniform_midtread_quantizer(x, xmin=1):
    # limiter
    x = np.copy(x)
    if x <= -xmin:
        x = -1
    if x > xmin - Q:
        x = 1 - Q
    # linear uniform quantization
    xQ = Q * np.floor(x/Q + 1/2)

    return xQ

# compute input signal
x = np.random.uniform(low=-1, high=1, size=256)
x = np.concatenate((x, np.zeros(1024)))
# compute output signal
yQ = sos_df1(x, 2*a, requantize=uniform_midtread_quantizer)

# plot results
plt.figure(figsize=(10, 3))
plt.plot(20*np.log10(np.abs(yQ)))
plt.title('Level of output signal')
plt.xlabel(r'$k$')
plt.ylabel(r'$|y_Q[k]|$ in dB')
plt.grid()

plt.figure(figsize=(10, 3))
k = np.arange(1000, 1050)
plt.stem(k, yQ[k])
plt.title('Output signal for zero input')
plt.xlabel(r'$k$')
plt.ylabel(r'$y_Q[k]$ ')
#plt.axis([k[0], k[-1], -1.1, 1.1])
plt.grid();
```

**Exercise**

* Determine the period of the large limit cycles. How is it related to the poles of the system?

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Digital Signal Processing - Lecture notes featuring computational examples, 2016-2017*.


### Example 1b: Linear convection in 2D, revisited

We will now revisit the fhe first example of this tutorial with an example that is better suited to the numerical scheme used in Devito. As a reminder, the governing equation is:

$$\frac{\partial u}{\partial t}+c\frac{\partial u}{\partial x} + c\frac{\partial u}{\partial y} = 0$$

We then discretized this using forward differences in time and backward differences in space:

$$u_{i,j}^{n+1} = u_{i,j}^n-c \frac{\Delta t}{\Delta x}(u_{i,j}^n-u_{i-1,j}^n)-c \frac{\Delta t}{\Delta y}(u_{i,j}^n-u_{i,j-1}^n)$$

In the previous example, the system was initialised with a hat function. As easy as this example seems, it actually highlights a few limitations of finite differences and related methods:
 - The governing equation above contains spatial derivatives ($\frac{\partial u}{\partial x}$ and $\frac{\partial u}{\partial y}$). The hat, with its sharp corners, is discontinuous and therefore non-smooth, meaning that the derivatives do not exist at the corners of the hat. This means that the governing equation has no solution in the strict sense for this problem. Mathematically, this problem can be overcome by introducing weak solutions, which still exist in the presence of discontinuities, as long as the problem is smooth almost everywhere. The Finite Volume (FV), Finite Element (FEM) and related schemes are based on this weak form.
 - The finite differences method only works well if finite differences are a good approximation of the derivatives. With the chosen discretisation above, this requires that $\frac{u_{i,j}^n-u_{i-1,j}^n}{\Delta x} \approx \frac{\partial u}{\partial x}$ and $\frac{u_{i,j}^n-u_{i,j-1}^n}{\Delta y} \approx \frac{\partial u}{\partial y}$. This is the case for systems with a smooth solution if $\Delta x$ and $\Delta y$ are sufficiently small. But if the solution is non-smooth, as in this example, then we can't expect much regardless of the grid size.
 - First-order methods, such as the backward differences that we have used in this exampe, are known to create artificial diffusion. Higher-order schemes, such as central differences, avoid this problem. However, in the presence of discontinuities these methods introduce so-called spurious oscillations. These oscillations may even build up (grow infinitely) and cause the computation to diverge.
 - Discontinuities can appear by themselves for some equations (such as the Burgers equation that we discuss next), even if the intial condition is smooth. In CFD, discontinuities appear for example as shocks in the simulation of transonic flow. For this reason, numerical schemes that behave well in the presence of discontinuities have been a research subject for a long time. A thorough discussion is beyond the scope of this tutorial, but can be found in [R. LeVeque (1992): Numerical Methods for Conservation Laws, 2nd ed., Birkhäuser Verlag, pp. 8-13].

In the remainder of this example, we will reproduce the results from the previous example, only this time with a smooth initial condition. This lets us observe Devito in a setting for which it is better equipped.

```python
from examples.cfd import plot_field, init_hat, init_smooth
import numpy as np
%matplotlib inline

# Some variable declarations
nx = 81
ny = 81
nt = 100
c = 1.
dx = 2. / (nx - 1)
dy = 2. / (ny - 1)
sigma = .2
dt = sigma * dx
```

Let us now initialise the field with an infinitely smooth bump, as given by [J.A. Krakos (2012): Unsteady Adjoint Analysis for Output Sensitivity
and Mesh Adaptation, PhD thesis, p. 68] as $$
f(r)=
\begin{cases}
\frac{1}{A}e^{-1/(r-r^2)} &\text{ for } 0 < r < 1,\\
0 &\text{ else.}
\end{cases}
$$
We use this with $A=100$, and define the initial condition in two dimensions as $$u^0(x,y)=1+f\left(\frac{2}{3}x\right)*f\left(\frac{2}{3}y\right).$$

```python
#NBVAL_IGNORE_OUTPUT

# Create field and assign initial conditions
u = np.empty((nx, ny))
init_smooth(field=u, dx=dx, dy=dy)

# Plot initial condition
plot_field(u,zmax=4)
```

Solving this will again move the bump.

```python
# Repeat initialisation, so we can re-run the cell
init_smooth(field=u, dx=dx, dy=dy)

for n in range(nt + 1):
    # Copy previous result into a new buffer
    un = u.copy()

    # Update the new result with a 3-point stencil
    u[1:, 1:] = (un[1:, 1:] - (c * dt / dx * (un[1:, 1:] - un[1:, :-1])) -
                              (c * dt / dy * (un[1:, 1:] - un[:-1, 1:])))

    # Apply boundary conditions
    u[0, :] = 1.
    u[-1, :] = 1.
    u[:, 0] = 1.
    u[:, -1] = 1.

```

```python
#NBVAL_IGNORE_OUTPUT

# A small sanity check for auto-testing
assert (u[45:55, 45:55] > 1.8).all()
u_ref = u.copy()

plot_field(u, zmax=4.)
```

Hooray, the wave moved! It looks like the solver works much better for this example: The wave has not noticeably changed its shape.

#### Devito implementation
Again, we can re-create this via a Devito operator. Let's fill the initial buffer with smooth data and look at it:

```python
#NBVAL_IGNORE_OUTPUT
from devito import Grid, TimeFunction

grid = Grid(shape=(nx, ny), extent=(2., 2.))
u = TimeFunction(name='u', grid=grid)
init_smooth(field=u.data[0], dx=dx, dy=dy)

plot_field(u.data[0])
```

We again create the discretised equation as shown below. Note that the equation is still the same, only the initial condition has changed.

```python
from sympy import Eq

eq = Eq(u.dt + c*u.dxl + c*u.dyl)

print(eq)
```

    Eq(1.0*u(t, x, y)/h_y - 1.0*u(t, x, y - h_y)/h_y + 1.0*u(t, x, y)/h_x - 1.0*u(t, x - h_x, y)/h_x - u(t, x, y)/dt + u(t + dt, x, y)/dt, 0)

SymPy can re-organise this equation just like in the previous example.

```python
from sympy import solve

stencil = solve(eq, u.forward, rational=False)[0]

print(stencil)
```

    (1.0*dt*h_x*(-u(t, x, y) + u(t, x, y - h_y)) + 1.0*dt*h_y*(-u(t, x, y) + u(t, x - h_x, y)) + h_x*h_y*u(t, x, y))/(h_x*h_y)

We can now use this stencil expression to create an operator to apply to our data object:

```python
#NBVAL_IGNORE_OUTPUT
from devito import Operator

# Reset our initial condition in both buffers.
# This is required to avoid 0s propagating into
# our solution, which has a background value of 1.
init_smooth(field=u.data[0], dx=dx, dy=dy)
init_smooth(field=u.data[1], dx=dx, dy=dy)

# Create an operator that updates the forward stencil point
op = Operator(Eq(u.forward, stencil))

# Apply the operator for a number of timesteps
op(time=nt+1, dt=dt)

plot_field(u.data[0, :, :], zmax=4)

# Some small sanity checks for the testing framework
assert (u.data[0, 45:55, 45:55] > 1.8).all()
assert np.allclose(u.data[0], u_ref, rtol=3.e-2)
```

Again, this looks just like the result from NumPy. Since this example is just like the one before, the low-level treatment of boundaries is also unchanged.

```python
#NBVAL_IGNORE_OUTPUT

# Reset our data field and ICs in both buffers
init_smooth(field=u.data[0], dx=dx, dy=dy)
init_smooth(field=u.data[1], dx=dx, dy=dy)

# For defining BCs, we want to explicitly set rows/columns in our field
# We can use Devito's "indexed" notation to do this:
x, y = grid.dimensions
t = grid.stepping_dim
bc_left = Eq(u.indexed[t + 1, 0, y], 1.)
bc_right = Eq(u.indexed[t + 1, nx-1, y], 1.)
bc_top = Eq(u.indexed[t + 1, x, ny-1], 1.)
bc_bottom = Eq(u.indexed[t + 1, x, 0], 1.)

# Now combine the BC expressions with the stencil to form operator
expressions = [Eq(u.forward, stencil)]
expressions += [bc_left, bc_right, bc_top, bc_bottom]
op = Operator(expressions=expressions, dle=None, dse=None)  # <-- Turn off performance optimisations
op(time=nt+1, dt=dt)

plot_field(u.data[0, :, :])

# Some small sanity checks for the testing framework
assert (u.data[0, 45:55, 45:55] > 1.8).all()
assert np.allclose(u.data[0], u_ref, rtol=3.e-2)
```

The C code of the Kernel is also still the same.

```python
print(op.ccode)
```

    #define _POSIX_C_SOURCE 200809L
    #include "stdlib.h"
    #include "math.h"
    #include "sys/time.h"

    struct profile
    {
      double section_0;
      double section_1;
      double section_2;
    } ;

    int Kernel(const float dt, const float h_x, const float h_y, float *restrict u_vec, const int t_size, const int t_s, const int t_e, const int time_size, const int time_s, const int time_e, const int x_size, const int x_s, const int x_e, const int y_size, const int y_s, const int y_e, void *_timings)
    {
      float (*restrict u)[x_size][y_size] __attribute__((aligned(64))) = (float (*)[x_size][y_size]) u_vec;
      struct profile *timings = (struct profile*) _timings;
      for (int time = t_s, t0 = (time)%(2), t1 = (time + 1)%(2); time < t_e - 1; time += 1, t0 = (time)%(2), t1 = (time + 1)%(2))
      {
        struct timeval start_section_0, end_section_0;
        gettimeofday(&start_section_0, NULL);
        for (int x = x_s + 1; x < x_e; x += 1)
        {
          for (int y = y_s + 1; y < y_e; y += 1)
          {
            u[t1][x][y] = (1.0F*dt*h_x*(-u[t0][x][y] + u[t0][x][y - 1]) + 1.0F*dt*h_y*(-u[t0][x][y] + u[t0][x - 1][y]) + h_x*h_y*u[t0][x][y])/(h_x*h_y);
          }
        }
        gettimeofday(&end_section_0, NULL);
        timings->section_0 += (double)(end_section_0.tv_sec-start_section_0.tv_sec)+(double)(end_section_0.tv_usec-start_section_0.tv_usec)/1000000;
        struct timeval start_section_1, end_section_1;
        gettimeofday(&start_section_1, NULL);
        for (int y = y_s + 1; y < y_e; y += 1)
        {
          u[t1][0][y] = 1.00000000000000F;
          u[t1][80][y] = 1.00000000000000F;
        }
        gettimeofday(&end_section_1, NULL);
        timings->section_1 += (double)(end_section_1.tv_sec-start_section_1.tv_sec)+(double)(end_section_1.tv_usec-start_section_1.tv_usec)/1000000;
        struct timeval start_section_2, end_section_2;
        gettimeofday(&start_section_2, NULL);
        for (int x = x_s + 1; x < x_e; x += 1)
        {
          u[t1][x][80] = 1.00000000000000F;
          u[t1][x][0] = 1.00000000000000F;
        }
        gettimeofday(&end_section_2, NULL);
        timings->section_2 += (double)(end_section_2.tv_sec-start_section_2.tv_sec)+(double)(end_section_2.tv_usec-start_section_2.tv_usec)/1000000;
      }
      return 0;
    }


```python
import numpy as np
import pandas as pd
import linearsolve as ls
import matplotlib.pyplot as plt
plt.style.use('classic')
%matplotlib inline
```

# Homework 6

**Instructions:** Complete the notebook below. Download the completed notebook in HTML format. Upload assignment using Canvas.

**Due:** Feb. 16 at **2pm.**

## Exercise: Four-Period Cake-Eating Problem

Now consider a the four-period cake-eating problem. A person has an initial quantity of cake $K_0>0$ and receives utility from consuming cake in period 0, period 1, period 2, and period 3:

\begin{align}
U(C_0,C_1,C_2,C_3) & = \log C_0 + \beta \log C_1+ \beta^2 \log C_2 + \beta^3 \log C_3.
\end{align}

The person chooses how much to consume in each period, $C_0$, $C_1$, $C_2$, $C_3$, and quantities of cake $K_1$, $K_2$, and $K_3$ to carry foward from period 0, period 1, and period 2 to maximize her utility subject to the following two budget constraints:

\begin{align}
C_0 + K_1 & = K_0\\
C_1 + K_2 & = K_1\\
C_2 + K_3 & = K_2\\
C_3 & = K_3,
\end{align}

where the boundary condition $K_4 = 0$ has been imposed.

### Part (a)

Find the first-order conditions with respect to $K_1$, $K_2$, and $K_3$ for the four-period cake-eating problem. Then solve by hand for:

1. $K_3$ as a function of $K_2$
1. $K_2$ as a function of $K_1$
2. $K_1$ as a function of $K_0$

Use what you derive in this part to do **Part (b)**, **Part (c)**, and **Part (d)**.

### Part (b)

Suppose that the initial quantity of cake is $K_0 = 100$. For $\beta = 0, 0.25, 0.5, 0.75, 0.99, 1, 2$, plot the optimal path for the quantity of cake for periods 0 through 4. Add labels to the plot command so you know which line corresponds to which value of $\beta$.

```python

```

### Part (c)

Suppose that the initial quantity of cake is $K_0 = 100$. For $\beta = 0, 0.25, 0.5, 0.75, 0.99, 1, 2$, plot the optimal path for cake consumption for periods 0 through 3. Add labels to the plot command so you know which line corresponds to which value of $\beta$.

```python

```

### Part (d)

Suppose that $\beta=0.95$. For $K_0 = 1, 10, 20, 50$, plot the optimal path for the quantity of cake for periods 0 through 4. Add labels to the plot command so you know which line corresponds to which value of $K_0$.

```python

```

## Exercise: The Stochastic Solow Growth Model

In class we simulated the stochastic Solow growth model with three endogenous variables: $K_t$, $Y_t$, and $A_t$. Now, reconsider the model with two additional variables, consumption $C_t$ and investment $I_t$, and two additional equations:

\begin{align}
Y_t & = A_t K_t^{\alpha}\\
C_{t} & = (1-s)Y_t\\
Y_{t} & = C_t + I_t\\
K_{t+1} & = sY_t + (1-\delta) K_t\\
\log A_{t+1} & = \rho \log A_t + \epsilon_{t+1}
\end{align}

where $\epsilon_{t+1} \sim \mathcal{N}(0,\sigma^2)$. In this exercise, you will construct a 401 period (10-year) simulation of the model.

For the other parameter, use the following values for the simulation:

| $$\rho$$ | $$\sigma$$ | $$s$$  | $$\alpha$$ | $$\delta $$ | $$T$$  |
|----------|------------|--------|------------|-------------|--------|
| 0.8      | 0.005      | 0.12   | 0.33       |  0.025      | 401    |

```python
# Create a variable called 'parameters' that is a Pandas Series containing the parameter values for the simulation

# Print the model's parameters

```

```python
# Define a function that evaluates the equilibrium conditions. PROVIDED
def equilibrium_equations(variables_forward,variables_current,parameters):

    # Parameters. PROVIDED
    p = parameters

    # Current variables. PROVIDED
    cur = variables_current

    # Forward variables. PROVIDED
    fwd = variables_forward

    # Production function

    # Capital evolution

    # Consumption function

    # Market clearing

    # Exogenous tfp

    # Stack equilibrium conditions into a numpy array

```

```python
# Initialize the model. Note that state variables must be named first in the var_names argument and shock for each
# state variable must be named.

```

```python
# Compute the steady state numerically using the .compute_ss() method.

```

```python
# Use the .approximate_and_solve() method to compute the log-linear approximation of the model around the
# non-stochastic steady state and solve endogenous variables in terms of state variables.

```

```python
# Use .stoch_sim() method to compute stochastic simulation with the following arguments:
#    1. seed = 126
#    2. T = 401
#    3. cov_mat= [[parameters['sigma_squared'],0],[0,0]]

# Print first 10 rows of model.simulated

```

```python
# Plot the computed stochastic simulation results. Creae two axes side-by-side.
#     1. Left axes: plot: a, k, i, y, c
#     1. Right axes: plot: shock to TFP

```

```python
# Compute standard deviations of simulated TFP, output, consumption, investment, and capital

```

```python
# Compute correlation coefficients of simulated TFP, output, consumption, investment, and capital

```

**Questions**

1. In the data simulated from the stochastic Solow growth model, which variables appear to be perfectly correlated?
2. Refer to the Notebook from Class 9. Compared with the actual correlations, does the stochastic Solow growth model under- or overpredict the correlation between output and consumption over the cycle? What about the correlation between output and investment?

**Answers**

1.

2.


# オイラー法による常微分方程式の数値解析

このノートブックでは，前回までに学んだPythonの基礎知識を用いて，
運動方程式でよく用いられる次の2階常微分方程式の解を数値的に求めるオイラー法について説明する．

$$
\frac{d^2 x(t)}{dt^2}=f\left(x(t), \frac{dx(t)}{dt}\right)
\tag{1}
$$

## オイラー法

まず，2階の微分方程式を1階化する．新しい変数$v(t) := \frac{dx(t)}{dt}$を導入することで，式(１)は

$$
\begin{cases}
 \frac{dx(t)}{dt} = v(t), \\
 \frac{dv(t)}{dt} = f(x(t), v(t))
\end{cases}
\tag{2}
$$

と書き直すことができる．

$h$を微小時間とし，$x(t + h)$をテイラー展開すると，

$$
x (t+h) = x(t) + \frac{dx(t)}{dt} h + \frac{1}{2!} \frac{d^2x(t)}{dt^2} h^2 + \cdots
$$

となる．1次の項のみで打ち切ると，

$$
x (t+h) \approx x(t) + \frac{dx(t)}{dt} h = x(t) + v(t) h
\tag{2}
$$

と近似できる．

この近似で得られるのは$h$ごとの解となるから，以降，これを$x_i := x(t_0 + i h)$, ただし$i=0, 1, 2, \ldots$, $t_0$は初期時刻，で表す．このとき，式(2)は次のようになる．

$$
x_{i+1} \approx x_i + v_i h
\tag{3}
$$

$v(t)$についても同様に，

$$
v(t+h) \approx v(t) + \frac{dv(t)}{dt} h = v(t) + f(x(t), v(t)) h
$$

より，$v_i := v(t_0 + i h)$を用いて，

$$
v_{i+1} \approx v_i + f(x_i, v_i) h
\tag{4}
$$

が得られる．この式（3,4）を逐次計算することで，微分方程式の解を求めるのがオイラー法である．

## オイラー法の利点

このオイラー法の利点は，式（1）が解析的に解けない場合でも，近似的な解（数値解）が得られる点にある．数値解を求めることを数値解析と呼ぶ．
複雑な運動方程式は解析的に解けないことが多いため，このような数値解析が重要となる．

## オイラー法の実装と実行

それでは，このオイラー法をPythonで実装してみよう．
ここでは，簡単な例として
$$
\begin{align}
  f = -x(t) \tag{5}\\
\end{align}
$$
とする．また，初期条件を$x(0)=1.0, y(0)=0$とする．

このとき，オイラー法を実装すると下記のようになる．

（TODO） 下記のコードを理解し，セルを実行せよ

```
import numpy as np
```

```
# 対象の関数定義
def f(x, v):
  return -x # f(x, y) = -x の実装
```

```
# オイラー法

# 計算条件を変数として定義
x = 1.0 # xの初期値
v = 0 # yの初期値
t_ini = 0 # 初期時刻
h = 0.2 # 積分刻み
t_max = 10 + h # 終了時刻

# 結果保存用の配列定義
ts = np.arange(start=t_ini, stop=t_max, step=h) # tの時系列
xs = np.zeros(len(ts)) # xの時系列
vs = np.zeros(len(ts)) # yの時系列

# 初期値の代入
xs[0] = x
vs[0] = v

for i in range(1, len(ts)): # 初期値から最終値までループ
  v += f(x, v) * h # 式(4)
  x += v * h # 式(3)
  # 結果を配列に代入
  xs[i] = x
  vs[i] = v
```

上のセルを実行ることで，オイラー法で計算された数値解が配列`xs`, `ys`に格納される．また，数値計算の各時刻は`ts`に格納される．得られた結果を確認していこう．

（TODO） 下記のコードを実行し，`ts`を横軸，`xs`を縦軸にプロットせよ．

```
from matplotlib import pyplot as plt
```

```
plt.plot(ts, xs, '.')
plt.xlabel('t')
plt.ylabel('x')
```

（TODO） 下記のコードを実行し，`ts`を横軸，`vs`を縦軸にプロットせよ．

```
plt.plot(ts, vs, '.')
plt.xlabel('t')
plt.ylabel('v')
```

## 厳密解との比較

さて，上で確認したオイラー法で得た数値解は妥当なものだろうか？これを検証するために，厳密解を導出して比較してみる．

式(5)のとき，$x(t) = \sin(t)$, $v(t) = \cos(t)$は式(1)の特解である．よって，任意定数$A, B$を用いた，それらの一次結合

$$
x(t) = A \sin(t) + B \cos(t)
\tag{6}
$$

は式（1）の一般解となる．また，$v(t)$についての一般解は，
$v(t) = \frac{dx}{dt} = A \cos(t) - B \sin(t)$
となる．

ここで，初期条件から，

$$
x(0) = A \sin(0) + B \cos(0) \Leftrightarrow x(0) = B = 1, \\
v(0) = A \cos(0) - B \sin(0) \Leftrightarrow v(0) = A = 0,
$$

であるから，式(1)の厳密解は

$$
x(t) = \cos(t)
\tag{7}
$$

となることがわかる．

（TODO） 下記のコードを実行し，厳密解を計算せよ．

```
# 厳密解の算出
xs_exact = np.cos(ts) # ts[i]をcos(ts[i])に変換して算出する
```

（TODO） 下記のコードを実行し，得られた厳密解とオイラー法の数値解を同時にplotせよ．

```
# 厳密解とオイラー法の数値解をplot
plt.plot(ts, xs_exact, '-r', label='Exact')
plt.plot(ts, xs, '.g', label='Euler') # 既にオイラー法で計算済み
plt.xlabel('t')
plt.ylabel('x')
plt.legend() # labelを表示する
```

出力されたグラフより，数値解（緑の点）と厳密解（赤の線）が，ある程度の精度で一致しており，数値解が妥当であったことがわかるはずだ．よって，オイラー法で微分方程式の数値解が求められることが確かめられた．

## オイラー法の問題点

このように手軽に実装できるオイラー法だが，その精度が問題となることがある．

確認のため，数値解と厳密解の平均絶対誤差（Mean Absolute Error）を計算してみよう．MAEの定義は以下のとおり．

$$
E = \frac{1}{N} \sum_{i=1}^{N} |x_{\text{s}}(i) - x_{\text{e}}(i)|
\tag{8}
$$

ここで，
$x_{\text{e}}(i)$は厳密解,
$x_{\text{s}}(i)$は数値解,
$N$はデータの総数を示す．

（TODO） 次のコードを実行し，オイラー法で得た数値解の配列`xs`と，厳密解の配列`xs_exact`のMAEを計算せよ．

```
e_abs = np.abs(xs_exact - xs) # 厳密解と数値解の絶対誤差の配列
np.mean(e_abs) # 上記の平均
```

結果からわかるように，今回のオイラー法では，$6.6\times 10^{-2}$ 程度のMAEとなるはずである．

このような誤差を減少させるには，次の2つの方法が考えられる．

1. 積分刻み$h$を小さくする．
2. より高精度な数値解析法を用いる．

1の場合，誤差が小さくなるものの，計算コストが増加する．そこで，次のnotebookでは，2の方法について学ぶ．

このノートブックの内容を一通り理解したら，[実験のTopページ](https://github.com/yyamnk/numerical-methods-py3/blob/master/uu_experiment.md) に戻り，次のノートブックに進むこと．


# Recurrent neural network and dynamical system analysis

In this tutorial, we will use supervised learning to train a recurrent neural network on a simple perceptual decision making task, and analyze the trained network using dynamical system analysis.

[](https://colab.research.google.com/github/gyyang/nn-brain/blob/master/RNN%2BDynamicalSystemAnalysis.ipynb)

## Install dependencies

```python
# # If on Google Colab, uncomment to install neurogym to use cognitive tasks
# ! git clone https://github.com/gyyang/neurogym.git
# %cd neurogym/
# ! pip install -e .
```

## Defining a cognitive task

```python
import neurogym as ngym

# Environment
task = 'PerceptualDecisionMaking-v0'
kwargs = {'dt': 100}
seq_len = 100

# Make supervised dataset
dataset = ngym.Dataset(task, env_kwargs=kwargs, batch_size=16,
                       seq_len=seq_len)

# A sample environment from dataset
env = dataset.env
# Visualize the environment with 2 sample trials
_ = ngym.utils.plot_env(env, num_trials=2)

# Network input and output size
input_size = env.observation_space.shape[0]
output_size = env.action_space.n
```

## Define a vanilla continuous-time recurrent network

Here we will define a continuous-time neural network but discretize it in time using the Euler method.
\begin{align}
    \tau \frac{d\mathbf{r}}{dt} = -\mathbf{r}(t) + f(W_r \mathbf{r}(t) + W_x \mathbf{x}(t) + \mathbf{b}_r).
\end{align}

This continuous-time system can then be discretized using the Euler method with a time step of $\Delta t$,
\begin{align}
    \mathbf{r}(t+\Delta t) = \mathbf{r}(t) + \Delta \mathbf{r} = \mathbf{r}(t) + \frac{\Delta t}{\tau}[-\mathbf{r}(t) + f(W_r \mathbf{r}(t) + W_x \mathbf{x}(t) + \mathbf{b}_r)].
\end{align}

```python
# Define networks
import torch
import torch.nn as nn
from torch.nn import init
from torch.nn import functional as F
import math

class CTRNN(nn.Module):
    """Continuous-time RNN.

    Args:
        input_size: Number of input neurons
        hidden_size: Number of hidden neurons

    Inputs:
        input: (seq_len, batch, input_size), network input
        hidden: (batch, hidden_size), initial hidden activity
    """

    def __init__(self, input_size, hidden_size, dt=None, **kwargs):
        super().__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.tau = 100
        if dt is None:
            alpha = 1
        else:
            alpha = dt / self.tau
        self.alpha = alpha
        self.oneminusalpha = 1 - alpha

        self.input2h = nn.Linear(input_size, hidden_size)
        self.h2h = nn.Linear(hidden_size, hidden_size)

    def init_hidden(self, input_shape):
        batch_size = input_shape[1]
        return torch.zeros(batch_size, self.hidden_size)

    def recurrence(self, input, hidden):
        """Recurrence helper."""
        pre_activation = self.input2h(input) + self.h2h(hidden)
        h_new = torch.relu(hidden * self.oneminusalpha +
                           pre_activation * self.alpha)
        return h_new

    def forward(self, input, hidden=None):
        """Propogate input through the network."""
        if hidden is None:
            hidden = self.init_hidden(input.shape).to(input.device)

        output = []
        steps = range(input.size(0))
        for i in steps:
            hidden = self.recurrence(input[i], hidden)
            output.append(hidden)

        output = torch.stack(output, dim=0)
        return output, hidden

class RNNNet(nn.Module):
    """Recurrent network model.

    Args:
        input_size: int, input size
        hidden_size: int, hidden size
        output_size: int, output size
        rnn: str, type of RNN, lstm, rnn, ctrnn, or eirnn
    """
    def __init__(self, input_size, hidden_size, output_size, **kwargs):
        super().__init__()

        # Continuous time RNN
        self.rnn = CTRNN(input_size, hidden_size, **kwargs)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        rnn_activity, _ = self.rnn(x)
        out = self.fc(rnn_activity)
        return out, rnn_activity
```

## Train the recurrent network on the decision-making task

```python
import torch.optim as optim

# Instantiate the network and print information
hidden_size = 64
net = RNNNet(input_size=input_size, hidden_size=hidden_size,
             output_size=output_size, dt=env.dt)
print(net)

# Use Adam optimizer
optimizer = optim.Adam(net.parameters(), lr=0.01)
criterion = nn.CrossEntropyLoss()

running_loss = 0
running_acc = 0
for i in range(1000):
    inputs, labels = dataset()
    inputs = torch.from_numpy(inputs).type(torch.float)
    labels = torch.from_numpy(labels.flatten()).type(torch.long)

    # in your training loop:
    optimizer.zero_grad()   # zero the gradient buffers
    output, _ = net(inputs)
    output = output.view(-1, output_size)
    loss = criterion(output, labels)
    loss.backward()
    optimizer.step()    # Does the update

    running_loss += loss.item()
    if i % 100 == 99:
        running_loss /= 100
        print('Step {}, Loss {:0.4f}'.format(i+1, running_loss))
        running_loss = 0
```

    RNNNet(
      (rnn): CTRNN(
        (input2h): Linear(in_features=3, out_features=64, bias=True)
        (h2h): Linear(in_features=64, out_features=64, bias=True)
      )
      (fc): Linear(in_features=64, out_features=3, bias=True)
    )
    Step 100, Loss 0.0712
    Step 200, Loss 0.0182
    Step 300, Loss 0.0139
    Step 400, Loss 0.0131
    Step 500, Loss 0.0119
    Step 600, Loss 0.0145
    Step 700, Loss 0.0127
    Step 800, Loss 0.0111
    Step 900, Loss 0.0116
    Step 1000, Loss 0.0115

## Visualize neural activity for in sample trials

We will run the network for 100 sample trials, then visual the neural activity trajectories in a PCA space.

```python
import numpy as np
env.reset(no_step=True)
perf = 0
num_trial = 100
activity_dict = {}
trial_infos = {}
for i in range(num_trial):
    env.new_trial()
    ob, gt = env.ob, env.gt
    inputs = torch.from_numpy(ob[:, np.newaxis, :]).type(torch.float)
    action_pred, rnn_activity = net(inputs)
    rnn_activity = rnn_activity[:, 0, :].detach().numpy()
    activity_dict[i] = rnn_activity
    trial_infos[i] = env.trial

# Concatenate activity for PCA
activity = np.concatenate(list(activity_dict[i] for i in range(num_trial)), axis=0)
print('Shape of the neural activity: (Time points, Neurons): ', activity.shape)

# Print trial informations
for i in range(5):
    print('Trial ', i, trial_infos[i])
```

    Shape of the neural activity: (Time points, Neurons):  (2200, 64)
    Trial  0 {'ground_truth': 1, 'coh': 12.8}
    Trial  1 {'ground_truth': 1, 'coh': 51.2}
    Trial  2 {'ground_truth': 1, 'coh': 0.0}
    Trial  3 {'ground_truth': 1, 'coh': 25.6}
    Trial  4 {'ground_truth': 0, 'coh': 51.2}

```python
# Compute PCA and visualize
from sklearn.decomposition import PCA

pca = PCA(n_components=2)
pca.fit(activity)
# print('Shape of the projected activity: (Time points, PCs): ', activity_pc.shape)
```

    PCA(copy=True, iterated_power='auto', n_components=2, random_state=None,
        svd_solver='auto', tol=0.0, whiten=False)

Transform individual trials and Visualize in PC space based on ground-truth color. We see that the neural activity is organized by stimulus ground-truth in PC1

```python
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True, sharex=True, figsize=(6, 3))
for i in range(num_trial):
    activity_pc = pca.transform(activity_dict[i])
    trial = trial_infos[i]
    color = 'red' if trial['ground_truth'] == 0 else 'blue'
    _ = ax1.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-', color=color)

    if i < 3:
        _ = ax2.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-', color=color)

ax1.set_xlabel('PC 1')
ax1.set_ylabel('PC 2')
```

## Dynamical system analysis

### Search for approximate fixed points
Here we search for approximate fixed points and visualize them in the same PC space. In a generic dynamical system,
\begin{align}
    \frac{d\mathbf{x}}{dt} = F(\mathbf{x}),
\end{align}
We can search for fixed points by doing the optimization
\begin{align}
    \mathrm{argmin}_{\mathbf{x}} |F(\mathbf{x})|^2.
\end{align}

```python
# Freeze for parameters in the recurrent network
for param in net.parameters():
    param.requires_grad = False

batch_size = 64

# Inputs should be the 0-coherence mean input during stimulus period
# This will be task-specific
input = np.tile([1, 0.5, 0.5], (batch_size, 1))
input = torch.tensor(input, dtype=torch.float32)

# Here hidden activity is the variable to be optimized
# Initialized randomly for search in parallel (activity all positive)
hidden = torch.tensor(np.random.rand(batch_size, hidden_size)*3,
                      requires_grad=True, dtype=torch.float32)

# Use Adam optimizer
optimizer = optim.Adam([hidden], lr=0.001)
criterion = nn.MSELoss()

running_loss = 0
for i in range(10000):
    optimizer.zero_grad()   # zero the gradient buffers

    # Take the one-step recurrent function from the trained network
    new_h = net.rnn.recurrence(input, hidden)
    loss = criterion(new_h, hidden)
    loss.backward()
    optimizer.step()    # Does the update

    running_loss += loss.item()
    if i % 1000 == 999:
        running_loss /= 1000
        print('Step {}, Loss {:0.4f}'.format(i+1, running_loss))
        running_loss = 0
```

    Step 1000, Loss 2.0559
    Step 2000, Loss 0.6365
    Step 3000, Loss 0.2054
    Step 4000, Loss 0.0579
    Step 5000, Loss 0.0139
    Step 6000, Loss 0.0030
    Step 7000, Loss 0.0006
    Step 8000, Loss 0.0001
    Step 9000, Loss 0.0001
    Step 10000, Loss 0.0001

### Visualize the found approximate fixed points.

We see that they found an approximate line attrator, corresponding to our PC1, along which evidence is integrated during the stimulus period.

```python
fixedpoints = hidden.detach().numpy()
print(fixedpoints.shape)

# Plot in the same space as activity
plt.figure()
for i in range(5):
    activity_pc = pca.transform(activity_dict[i])
    trial = trial_infos[i]
    color = 'red' if trial['ground_truth'] == 0 else 'blue'
    plt.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-',
             color=color, alpha=0.1)

# Fixed points are shown in cross
fixedpoints_pc = pca.transform(fixedpoints)
plt.plot(fixedpoints_pc[:, 0], fixedpoints_pc[:, 1], 'x')

plt.xlabel('PC 1')
plt.ylabel('PC 2')
```

### Computing the Jacobian and finding the line attractor

First we will compute the Jacobian.

```python
# index of fixed point to focus on
# choose one close to center by sorting PC1
i_fp = np.argsort(fixedpoints[:, 0])[int(fixedpoints.shape[0]/2)]

fp = torch.from_numpy(fixedpoints[i_fp])
fp.requires_grad = True

# Inputs should be the 0-coherence mean input during stimulus period
# This will be task-specific
input = torch.tensor([1, 0.5, 0.5], dtype=torch.float32)
deltah = net.rnn.recurrence(input, fp) - fp

# w1 = torch.randn((64,64), requires_grad = False)
# new_h = w1@fp

jacT = torch.zeros(hidden_size, hidden_size)
for i in range(hidden_size):
    output = torch.zeros(hidden_size)
    output[i] = 1.
    jacT[:,i] = torch.autograd.grad(deltah, fp, grad_outputs=output, retain_graph=True)[0]

jac = jacT.detach().numpy().T
```

Here we plot the direction of the eigenvector corresponding to the highest eigenvalue

```python
eigval, eigvec = np.linalg.eig(jac)
vec = np.real(eigvec[:, np.argmax(eigval)])
end_pts = np.array([+vec, -vec]) * 2
end_pts = pca.transform(fp.detach().numpy() + end_pts)

# Plot in the same space as activity
plt.figure()
for i in range(5):
    activity_pc = pca.transform(activity_dict[i])
    trial = trial_infos[i]
    color = 'red' if trial['ground_truth'] == 0 else 'blue'
    plt.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-',
             color=color, alpha=0.1)

# Fixed points are shown in cross
fixedpoints_pc = pca.transform(fixedpoints)
plt.plot(fixedpoints_pc[:, 0], fixedpoints_pc[:, 1], 'x')

# Line attractor
plt.plot(end_pts[:, 0], end_pts[:, 1])

plt.xlabel('PC 1')
plt.ylabel('PC 2')
```

```python
# Plot distribution of eigenvalues in a 2-d real-imaginary plot
plt.figure()
plt.scatter(np.real(eigval), np.imag(eigval))
plt.plot([0, 0], [-1, 1], '--')
plt.xlabel('Real')
plt.ylabel('Imaginary')
```

# Supplementary Materials

Code for making publication quality figures as it appears in the paper.

```python
# Convert information into pandas dataframe
import pandas as pd
df = pd.DataFrame()
for i in range(len(trial_infos)):
    df = df.append(trial_infos[i], ignore_index=True)

# Example selection of conditions
print(df[(df['coh']==6.4) & (df['ground_truth']==1.0)])
```

        coh  ground_truth
    7   6.4           1.0
    14  6.4           1.0
    18  6.4           1.0
    26  6.4           1.0
    29  6.4           1.0
    31  6.4           1.0
    48  6.4           1.0
    52  6.4           1.0
    55  6.4           1.0
    61  6.4           1.0
    66  6.4           1.0
    77  6.4           1.0

```python
plot_fp = False

# Plot in the same space as activity
fig = plt.figure(figsize=(3, 3))
ax = fig.add_axes([0.2, 0.2, 0.6, 0.6])

colors = np.array([[27,158,119], [117,112,179], [217,95,2]])/255.

# Search for two trials with similar conditions
cohs = np.unique(df['coh'])
cohs = [c for c in cohs if c > 0]  # remove 0

color_intensity = [0.4, 0.7, 1.0, 1.3]

for ground_truth in [0, 1]:
    if ground_truth == 0:
        cohs_ = cohs[::-1]
        color_intensity_ = color_intensity[::-1]
    else:
        cohs_ = cohs
        color_intensity_ = color_intensity
    for i_coh, coh in enumerate(cohs_):
        if plot_fp and coh != cohs[2]:
            continue
        trials = df[(df['coh']==coh) & (df['ground_truth']==ground_truth)].index
        activity = np.mean(np.array([activity_dict[i] for i in trials]), axis=0)

        activity_pc = pca.transform(activity)
        color = colors[0] if ground_truth == 0 else colors[1]
        color = color * color_intensity_[i_coh]
        signed_coh = coh * (2*ground_truth - 1)
        label = '{:0.1f}'.format(signed_coh)
        plt.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-',
                 color=color, ms=3, markeredgecolor='none',
                 lw=1, label=label)

if plot_fp:
    # Fixed points are shown in cross
    color = colors[2]
    fixedpoints_pc = pca.transform(fixedpoints)
    plt.plot(fixedpoints_pc[:, 0], fixedpoints_pc[:, 1], 'x', ms=3, color=color, alpha=0.3)

    # Line attractor
    plt.plot(fixedpoints_pc[i_fp, 0], fixedpoints_pc[i_fp, 1], 'x', ms=5, color=color, lw=1)
    plt.plot(end_pts[:, 0], end_pts[:, 1], color=color)
else:
    ax.legend(title='Coh', loc='upper left', bbox_to_anchor=(1.0, 1.0), frameon=False)

plt.xlabel('PC 1', fontsize=7)
plt.ylabel('PC 2', fontsize=7)

plt.xlim([-5, 5])
plt.ylim([-1, 5])

# Beautification
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['left'].set_position(('data', -5))
ax.spines['bottom'].set_position(('data', -1.5))

from pathlib import Path
if plot_fp:
    fname = Path('figures/lineattractors_dm')
else:
    fname = Path('figures/rnndynamics_dm')
fig.savefig(fname.with_suffix('.pdf'), transparent=True)
fig.savefig(fname.with_suffix('.png'), dpi=300)
```

```python

```


# Quantization of Signals

*This jupyter notebook is part of a [collection of notebooks](../index.ipynb) on various topics of Digital Signal Processing. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Oversampling

[Oversampling](https://en.wikipedia.org/wiki/Oversampling) is a technique which is applied in [analog-to-digital converters](https://en.wikipedia.org/wiki/Analog-to-digital_converter) to lower the average power of the quantization error. It requires a joint consideration of sampling and quantization.

### Ideal Analog-to-Digital Conversion

Let's consider the ideal sampling of a signal followed by its quantization, as given by the following block diagram

Ideal sampling is modeled by multiplying the continuous signal $x(t)$ with a series of equidistant Dirac impulse, resulting in the discrete signal $x[k] = x(k T)$ where $T$ denotes the sampling interval. The discrete signal $x[k]$ is then quantized. The output of the ideal analog-to-digital converter is the quantized discrete signal $x_\text{Q}[k]$.

### Nyquist Sampling

Sampling of the continuous signal $x(t)$ leads to repetitions of the spectrum $X(j \omega) = \mathcal{F} \{ x(t) \}$ at multiples of $\omega_\text{S} = \frac{2 \pi}{T}$. We limit ourselves to a continuous real-valued $x(t) \in \mathbb{R}$, band-limited signal $| X(j \omega) | = 0$ for $|\omega| > \omega_\text{C}$ where $\omega_\text{C}$ denotes its cut-off frequency. The spectral repetitions due to sampling do not overlap if the [sampling theorem](https://en.wikipedia.org/wiki/Nyquist%E2%80%93Shannon_sampling_theorem) $\omega_\text{S} \geq  2 \cdot \omega_\text{C}$ is fulfilled. In the case of Nyquist (critical) sampling, the sampling frequency is chosen as $\omega_\text{S} =  2 \cdot \omega_\text{C}$.

### Oversampling

The basic idea of oversampling is to sample the input signal at frequencies which are significantly higher than the Nyquist criterion dictates. After quantization, the signal is low-pass filtered by a discrete filter $H_\text{LP}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ and resampled back to the Nyquist rate. In order to avoid aliasing due to the resampling this filter has to be chosen as an ideal low-pass

\begin{equation}
H_\text{LP}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = \text{rect} \left( \frac{\Omega}{2 \, \Omega_\text{C}} \right)
\end{equation}

where $\Omega_\text{C} = \omega_\text{C} \cdot T$. For an oversampling of factor $L \in \mathbb{N}$ we have $\omega_\text{S} = L \cdot 2 \omega_\text{C}$. For this case, the resampling can be realized by keeping only every $L$-th sample which is known as decimation. The following block diagram illustrates the building blocks of oversampled digital-to-analog conversion, $\downarrow L$ denotes decimation by a factor of $L$

In order the conclude on the benefits of oversampling we have to derive the average power of the overall quantization error. According to our [model of the quantization error](linear_uniform_quantization_error.ipynb#Model-for-the-Quantization-Error), the quantization error $e[k]$ can be modeled as uniformly distributed white noise. Its power spectral density (PSD) is given as

\begin{equation}
\Phi_{ee}(\mathrm{e}^{\,\mathrm{j}\,\Omega}) = \frac{Q^2}{12}
\end{equation}

where $Q$ denotes the quantization step. Before the discrete low-pass filter $H_\text{LP}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$, the power of the quantization error is uniformly distributed over the entire frequency range $-\pi < \Omega \leq \pi$. However, after the ideal low-pass filter its frequency range is limited to $- \frac{\pi}{L} < \Omega \leq \frac{\pi}{L}$. The average power of the quantization error is then given as

\begin{equation}
\sigma_{e, \text{LP}}^2 = \frac{1}{2 \pi} \int\limits_{- \frac{\pi}{L}}^{\frac{\pi}{L}} \Phi_{ee}(\mathrm{e}^{\,\mathrm{j}\,\Omega})  \; \mathrm{d}\Omega = \frac{1}{L} \cdot \frac{Q^2}{12}
\end{equation}

The average power $\sigma_x^2$ of the sampled signal $x[k]$ is not affected, since the cutoff frequency of the low-pass filter has been chosen as the upper frequency limit $\omega_\text{C}$ of the input signal $x(t)$.

In order to calculate the SNR of the oversampled analog-to-digital converter we assume that the input signal is drawn from a wide-sense stationary (WSS) uniformly distributed zero-mean random process with $x_\text{min} \leq x[k] < x_\text{max}$. With the results from our discussion of [linear uniform quantization](linear_uniform_quantization_error.ipynb#Uniformly-Distributed-Signal) and $\sigma_{e, \text{LP}}^2$ from above we get

\begin{equation}
SNR = 10 \cdot \log_{10} \left( 2^{2 w} \right) + 10 \cdot \log_{10} \left( L \right) \approx 6.02 \, w + 10 \cdot \log_{10} \left( L \right) \quad \text{in dB}
\end{equation}

where $w$ denotes the number of bits used for a binary representation of the quantization index. Hence, oversampling by a factor of $L$ brings a plus of $10 \cdot \log_{10} \left( L \right)$ dB in terms of SNR. For instance, an oversampling by a factor of $L = 4$ results in a SNR which is approximately 6 dB higher. For equal SNR the quantization step $Q$ can be chosen larger. In terms of the wordlength of a quantizer this accounts to a reduction by one bit. Consequently, there is a trade-off between accuracy of the quantizer and its sampling frequency.

### Example

The following numerical simulation illustrates the benefit in terms of SNR for an oversampled linear uniform quantizer with $w = 16$ for the quantization of the harmonic signal $x[k] = \cos[\Omega_0 k]$.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
import scipy.signal as sig

w = 16  # wordlength of the quantized signal
L = 2**np.arange(1,10)  # oversampling factors

N = 8192  # length of signal
Om0 = 100*2*np.pi/N  # frequency of harmonic signal
Q = 1/(2**(w-1))  # quantization step

def uniform_midtread_quantizer(x, Q):
    # limiter
    x = np.copy(x)
    idx = np.where(x <= -1)
    x[idx] = -1
    idx = np.where(x > 1 - Q)
    x[idx] = 1 - Q
    # linear uniform quantization
    xQ = Q * np.floor(x/Q + 1/2)

    return xQ

def SNR_oversampled_ADC(L):
    x = (1-Q)*np.cos(Om0*np.arange(N))
    xu = (1-Q)*np.cos(Om0*np.arange(N*L)/L)
    # quantize signal
    xQu = uniform_midtread_quantizer(xu, Q)
    # low-pass filtering and decimation
    xQ = sig.resample(xQu, N)
    # estimate SNR
    e = xQ - x

    return 10*np.log10((np.var(x)/np.var(e)))

# compute SNR for oversampled ADC
SNR = [SNR_oversampled_ADC(l) for l in L]

# plot result
plt.figure(figsize=(10, 4))
plt.semilogx(L, SNR, label='SNR with oversampling')
plt.plot(L, (6.02*w+1.76)*np.ones(L.shape), label='SNR w/o oversampling' )
plt.xlabel(r'oversampling factor $L$')
plt.ylabel(r'SNR in dB')
plt.legend(loc='upper left')
plt.grid()
```

**Exercise**

* What SNR can be achieved for an oversampling factor of $L=16$?
* By how many bits could the word length $w$ be reduced in order to gain the same SNR as without oversampling?

Solution: The SNR for the quantization of a uniformly distributed input signal without oversampling is $\text{SNR} \approx 6.02 w \approx 96$ dB and with 16 times oversampling $\text{SNR}_{L} \approx 6.02 w + 10 \cdot \log_{10} (16)  \approx 96 + 12$ dB. Since the [quantization of a harmonic signal](linear_uniform_quantization_error.ipynb#Harmonic-Signal) is considered an offset of $1.76$ dB has to added to both. The wordlength could be reduced by 2 bits according to these numbers.

### Anti-Aliasing Filter

Besides an increased SNR, oversampling has also another benefit. In order to ensure that the input signal $x(t)$ is band-limited before sampling, a low-pass filter $H_\text{LP}(\mathrm{j}\,\omega)$ is applied in typical analog-to-digital converters. This is illustrated in the following

The filter $H_\text{LP}(\mathrm{j}\,\omega)$ is also known as [anti-aliasing filter](https://en.wikipedia.org/wiki/Anti-aliasing_filter). The ideal low-pass filter is given as $H_\text{LP}(\mathrm{j}\,\omega) = \text{rect}\left( \frac{\omega}{\omega_\text{S}}  \right)$. The ideal $H_\text{LP}(\mathrm{j}\,\omega)$ can only be approximated in the analog domain. Since the sampling rate is higher than the Nyquist rate, there is no need for a steep slope of the filter in order to avoid aliasing. However, the pass-band of the filter within $|\omega| < |\omega_\text{C}|$ has to be flat.

Before decimation, the discrete filter $H_\text{LP}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ has to remove the spectral contributions that may lead to aliasing. However, a discrete filter $H_\text{LP}(\mathrm{e}^{\,\mathrm{j}\,\Omega})$ with steep slope can be realized much easier than in the analog domain.

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Digital Signal Processing - Lecture notes featuring computational examples, 2016-2018*.


## Introducción

```python
import math
math.sin(math.pi/8)
```

```python
from math import pi,sin
sin(pi/8)
```

```python
import random as rnd
rnd.randint(0,10)
```

```python
from random import gauss as n, expovariate as exp
n(0,1) + exp(1)
```

##  Asignaciones y variables

```python
x = 9.80665
GravedadEstandar  = 9.80665
```

```python
NumComplejo = 2.5 + 1.2j
Alfabeto = 'alfabeto'
Aprobacion = True
Pele = 10
Messi = 10 + 0j
James = 10.0
```

```python
type(Alfabeto)
type(NumComplejo)
```

```python
print(Pele + Messi)
print(complex(Pele) + Messi)
```

```python
tasa = 0.05
print(1 + tasa)
tasa = 0.02
print(1 + tasa)
```

```python
tasa = 0.01
tasa = tasa + 0.005
tasa = tasa + 0.003
print(tasa)
```

```python
f"Tres elevado a la 2 es {3**2}"
f"La tasa de interés es {tasa}, pero esta aumentará 10%, es decir, la nueva tasa es {tasa*1.1}"

```

## Cálculos básicos

```python
850500*((1+0.02)**2)*45/100

```

```python
17/3
```

```python
-17/3
```

```python
17//3
```

```python
-17//3
```

```python
direccion = 'https://wiki.python.org/moin/'
inicio = 'FrontPage'
```

```python
'A' + 'C' + 'G' + 'T'
```

```python
print(direccion + inicio)
```

```python
'etc., ' * 3
```

```python
x = 3
```

```python
(5 > x) and (x>4)
```

```python
(5 > x) or (x>4)

```

```python
not (x==3)
```

## Cálculos simbólicos

```python
from sympy import *
V, R, T, a, b = symbols('V R T a b')
p = (R*T)/(V-b) - a/V**2
dpdV  = diff(p,V)
dpdV2 = diff(p,V,2)
```

```python
solve(dpdV,T)
solve(dpdV2,T)
solve(solve(dpdV,T)[0]-solve(dpdV2,T)[0],V)[1]
```

```python
limit((a**2 - b**2)/(a-b), a, b) # Calcula límites
integrate(sin(b), b)             # Integra sen(b)
simplify((a**2 - b**2)/(a+b))    # Simplifica la  expresión
```

```python
exp_f = a*exp(-a)
f = lambdify([a], exp_f)
f(-1)
```

## Contenedores básicos

### Listas

```python
Fechas = ['Oct 01, 2014', 'Oct 24, 1980', 'Feb 11, 2002']
Codigos = [201701092,201801875,201901902,202001802,202100002]
Constantes = [math.pi, math.e, 0.5]
DeTodoUnPoco = ['Casino', 65, 1.3, 2+3j]
SoloNumeros = [[1,2,3], [3.4, 4.5, 5.6], [2+8j,3+27j,4+64j]]
Aprobaron = [True,False,True,True,True,False]
```

```python
Fechas[0]
```

```python
SoloNumeros[2][1]

```

```python
DeTodoUnPoco[-1]
```

```python
# Primeros cuatro elementos
Codigos[0:4]
Codigos[:4]

```

```python
# Dos últimos elementos
Codigos[3:5]
Codigos[3:]
Codigos[-2:5]
Codigos[-2:]
```

```python
# Tercer y cuarto elemento
Codigos[2:4]
Codigos[-3:-1]
```

```python
Constantes[2]=0
```

```python
# Insertar el número aúreo en la segunda posición de la lista:
Constantes.insert(1,(1+math.sqrt(5))/2)
```

```python
# Adicionar la raíz cuadradda de 2 al final de la lista
# `Constantes`:
Constantes.append(math.sqrt(2))
```

```python
# Ordenar la lista
Constantes.sort()
```

```python
# Revertir el orden de la lista
Constantes.reverse()
```

```python
Constantes.__len__()
```

```python
0 in Constantes
```

```python
del Constantes[3]
```

```python
Constantes + [1, 10, 100]
```

```python
secuencia = 'PpRrOoGgRrAaMmCcIiOoNn'
secuencia[::2]
```

```python
a = [1,2]
b = a
print(a)
print(b)
a[1] = 4
print(a)
print(b)
```

### Tuplas

```python
NucleotidosADN = ('A', 'C', 'G', 'T')
# Número de unidades de valor tributario (UVT) exentas para
# pago de renta según el sistema de rentas cedulares:
UVTexemptas = (1090, 1700, 600, 600)
```

```python
# Lista de números en el plano cartesiano
z = [(2,3),(1,1), (4,7), (1.5,3.4), (1.34,3.41), (2.2,3.2), (0.2, -0.5)]

# Cálculo de |x| + |y| para el último elemento de z
abs(z[-1][0])+abs(z[-1][1])
```

### Conjuntos

```python
M = set("Marie Curie")
C = set("Carl Gauss")
print(M&C)
print(M|C)
print(M^C)
```

### Diccionarios

```python
codigoGenetico={'ATT':'I',   'ATC':'I',  'ATA':'I',  'CTT':'L',
'CTC':'L',  'CTA':'L',  'CTG':'L',  'TTA':'L',  'TTG':'L',
'GTT':'V',  'GTC':'V',  'GTA':'V',  'GTG':'V',  'TTT':'F',
'TTC':'F',  'ATG':'M',  'TGT':'C',  'TGC':'C',  'GCT':'A',
'GCC':'A',  'GCA':'A',  'GCG':'A',  'GGT':'G',  'GGC':'G',
'GGA':'G',  'GGG':'G',  'CCT':'P',  'CCC':'P',  'CCA':'P',
'CCG':'P',  'ACT':'T',  'ACC':'T',  'ACA':'T',  'ACG':'T',
'TCT':'S',  'TCC':'S',  'TCA':'S',  'TCG':'S',  'AGT':'S',
'AGC':'S',  'TAT':'Y',  'TAC':'Y',  'TGG':'W',  'CAA':'Q',
'CAG':'Q',  'AAT':'N',  'AAC':'N',  'CAT':'H',  'CAC':'H',
'GAA':'E',  'GAG':'E',  'GAT':'D',  'GAC':'D',  'AAA':'K',
'AAG':'K',  'CGT':'R',  'CGC':'R',  'CGA':'R',  'CGG':'R',
'AGA':'R',  'AGG':'R',  'TAA':'X',  'TAG':'X',  'TGA':'X'}
```

```python
list(codigoGenetico.values()).count('L')
```

```python
codigoGenetico['AAA']
```

```python
nucleotidosADN = 'ACGT'
codon = 'CAT'
print(codigoGenetico[codon[0]+nucleotidosADN[0]+codon[2]])
print(codigoGenetico[codon[0]+nucleotidosADN[1]+codon[2]])
print(codigoGenetico[codon[0]+nucleotidosADN[2]+codon[2]])
print(codigoGenetico[codon[0]+nucleotidosADN[3]+codon[2]])
```

```python
p1 = {6:4, 2:34, 0:-6}

```

## Funciones

```python
def g(z):
    return abs(z[0]) + abs(z[1])

a = g((1,-1))
b = g([0,-1])
```

```python
def hmsdec(h,m,s):
    return 15*(h + m/60 + s/3600)

def gmsdec(g,m,s,P):
    return P*(abs(g) + m/60 + s/3600)

gmsdec(-5,31,12,-1)
```

```python
def Vsm(m):
    return 1001.38-4.86*m+34.69*m**2

def Vcs(n):
    return 1003.0+16.4*n+2.1*n**(3/2)+0.003*n**(5/2)

def Vsp(m):
    return 17.963-0.1094*m**(3/2)-0.0002*m**2

Vsp(0.10)
```

```python
x, x0, gamma= symbols('x x0 gamma')
f = gamma/(pi*(gamma**2+(x-x0)**2))
dfdx = diff(f,x)
dfdx2 = diff(f,x,2)
```

```python
solve(dfdx,x)
```

```python
solve(f-(1/(2*pi*gamma)),x)
```

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

def Cauchy(x,x0,gamma):
    return gamma/(np.pi*(gamma**2+(x-x0)**2))

j = Cauchy(75,75,50)/2

xs = np.linspace(-75,225,200)
plt.plot(xs,Cauchy(xs,75,50),c='k')
plt.plot([25,74],[j,j],'k--')
plt.plot([76,125],[j,j],'k--')
plt.plot([25,25],[0,Cauchy(25,75,50)],'k:')
plt.plot([125,125],[0,Cauchy(125,75,50)],'k:')
plt.plot([75,75],[0,Cauchy(75,75,50)],'k:')
plt.ylim(0,0.0065)
plt.xlabel(r'$x$')
plt.ylabel(r'$f_X(x|50,75)$')
plt.text(65,j+.00005,r'$\Gamma$')
plt.text(62,.0001,r'$x_0$')
plt.text(12,.0001,r'$x_1$')
plt.text(112,.0001,r'$x_2$')

```

```python
def BreitWigner(e,Er,Gamma):
    return (Gamma/(2*math.pi))/((e-Er)**2+Gamma**2/4)

```

```python
def InterpolLinLagrange(x, xObs, yObs):
    """
    Interpolación lineal usando el método de Lagrange en
    el punto x.

    xObs: valores de los puntos en x
    yObs: valores de los puntos en y

    Ejemplo:
    >>> InterpolLinLagrange(0.5, (-1,1), (-1,1) )
    >>> InterpolLinLagrange(20, [18,25], [7,6] )
    """

    l0 = (x-xObs[1])/(xObs[0]-xObs[1])
    l1 = (x-xObs[0])/(xObs[1]-xObs[0])
    return yObs[0]*l0 + yObs[1]*l1
```

```python
def derivadas(f,x,h=0.0001):
    df  = (f(x+h) - f(x-h))/(2.0*h)
    ddf = (f(x+h) - 2.0*f(x) + f(x-h))/h**2
    return df,ddf
```

```python
DAtan = derivadas(math.atan, 0.6)
DAtan1,DAtan2 = derivadas(math.atan, 0.6, 0.000001)
```

```python
def f1(x):
    return 1/(x**3 - 2*x**2)
Df1 = derivadas(f1 ,0.4)
```

```python
Df1 = derivadas(lambda x: 1/(x**3 - 2*x**2) ,0.4)
Df2 = derivadas(lambda x: 1/(3*x**3 - 4*x**2) ,0.4)
```

```python
Norma = lambda x , y: math.sqrt(x**2 + y**2)
Norma(4,3)
```

```python
def ValorFuturo(V0, t=1, r=0.05):
  FactorCrecimiento = pow(1+r,t)
  return V0*FactorCrecimiento
```

```python
r0, t0 = 0.05, 2
vf1 = ValorFuturo(100, t0, r0)
```

```python
def f2(r1):
    V0 = 200
    print(ValorFuturo(100,t0,r1))
    print(ValorFuturo(100,t0,r0))
    print(V0)
    return None
```

```python
def parteEnteraL1(z):
    return abs(z.imag)+abs(z.real)
```

```python
def PC1(z):
    print(parteEnteraL1(z[0]), parteEnteraL1(z[1]),
          parteEnteraL1(z[2]))
    print(int(abs(z[-3])),int(abs(z[-2])),int(abs(z[-1])))
    return None
```

```python
from matplotlib import pyplot as plt
plt.rcParams['font.family'] = 'serif'
plt.rcParams['figure.figsize'] = (10.0, 8.0)
plt.rcParams['font.size'] = 18
```

```python
figura1 = plt.figure()
print(figura1)
```

```python
figura2 = plt.figure(figsize=(6, 4), facecolor='white')
print(figura2)

```

```python
x1 = [0,1,2,3,4,5]
x2 = [0.2, 0.8, 1.6, 2.5, 3.6, 4.8]
y1 = [10, 20, 25, 30, 25, 20]
y2 = [5, 15, 25, 25, 28,30]
figura3 = plt.figure()
ax = figura3.add_subplot(111)
ax.plot(x1, y1 , color='gray', linewidth=3, linestyle='dashed')
ax.scatter(x2, y2, color='black', marker='*')
ax.set_xlim(-0.5, 5.5)
plt.show()
```

```python
plt.plot(x1, y1, color='gray', linewidth=3, linestyle='dashed')
plt.scatter(x2, y2, color='black', marker='*')
plt.xlim(-0.5, 5.5)
plt.show()
```

```python
Edad = [0,1,2,3,4,5,6]
Fecundidad = [0,0,0,0,0.08,0.28,0.42]
Supervivencia = [0.657,0.93,0.93,0.93,0.935,0.935,0]
FocaGris = plt.figure()
ax = FocaGris.add_subplot(111)
ax.plot(Edad, Fecundidad, marker='+', markersize=12, lw=2,
        color = 'black', linestyle = 'dashed', mfc='black',
        mec='black', label='Fecundidad')
ax.plot(Edad, Supervivencia, marker='.', markersize=12, lw=2,
        color = 'gray', linestyle = 'dotted', mfc='gray',
        mec='gray',label ='Supervivencia')
ax.set_ylim([-.1,1])
ax.set_xlabel('Edad')
ax.set_ylabel('Tasa')
# Adiciona la legenda en el centro abajo
ax.legend(loc='upper center',
          bbox_to_anchor=(0.5, -.15), shadow=True, ncol=2)

```

```python
from matplotlib import style         # Importar `style`
style.use("seaborn-white")           # Escoger estilo
plt.plot(Edad, Fecundidad, 'k+:', label='Fecundidad' )
plt.plot(Edad, Supervivencia, 'k+--', label ='Supervivencia')
plt.xlabel('Edad')
plt.ylabel('Tasa')
plt.ylim([-.1,1])
plt.legend(loc='upper center',
          bbox_to_anchor=(0.5, -.15), shadow=True, ncol=2)
```

```python
Pais = ['E.E.U.U.', 'Italia', 'China','España','Alemania',
        'Francia','Iran', 'G.B.','Suiza','Holanda']
Casos = [123780,92472,81439,73235,58247,37575,35408,17089,
         14076,9762]
Recuperados = [3238,12384,75448,12285,8481,5700,11679,135,1595,3]

from matplotlib import style         # Importar `style`
style.use("fivethirtyeight")         # Escoger estilo
COVID = plt.figure(figsize=(6, 8))   # Nueva figura, tamaño 6X8
# Ajustar espacio entre subfiguras
COVID.subplots_adjust(hspace=0.4, wspace=0.4)
ax1 = COVID.add_subplot(211)         # Adicionar un subfigura
ax1.bar(Pais,Casos, color="gray")    # Adicionar barras
ax1.set_ylabel('Frecuencia')         # Adicionar etiqueta eje Y
ax1.tick_params(axis='x', labelrotation=45)
```

```python
posiciones = [1,2,3,4,5,6,7,8,9,10]
posiciones2 = [1.45,2.45,3.45,4.45,5.45,6.45,7.45,8.45,9.45,10.45]

style.use("seaborn-dark-palette")
ax2 = COVID.add_subplot(212)
ax2.bar(posiciones, Casos, 0.45, color ="dimgray",\
        label="Casos positivos")
ax2.bar(posiciones2, Recuperados,0.45, color="darkgray",\
        label="Recuperados")
ax2.set_xticks(posiciones2)
ax2.set_xticklabels(Pais)
ax2.set_xlabel('País')
ax2.set_ylabel('Frecuencia')
ax2.legend()
ax2.tick_params(axis='x', labelrotation=45)

```

```python
plt.style.available
```

```python
import math
from matplotlib import pyplot as plt
from matplotlib import style

def PC1(z):
    print(int(Norma(z[0][0],z[1][0])),
          int(Norma(z[0][1],z[1][1])),
          int(Norma(z[0][2],z[1][2])))
    print(g((z[0][-3],z[1][-3])),g((z[0][-2],z[1][-2])),
          g((z[0][-1],z[1][-1])))
    style.use("ggplot")
    plt.scatter(z[0],z[1],color="black")
    plt.xlabel("X")
    plt.ylabel("Y")
    plt.show()
```

```python
a = [(2,1,4,1.5,2,4,3,1,5,1.34,2.2,0.2),
     (3,1,7,3.4,7,-2,3,2.3,4.5,3.41,-3.2,-.5)]
PC1(a)
```

```python
PC1([(1,2,3),(1,3)])
PC1([(1,2),(1,3)])
PC1([(1,2),(1,3),(2,3)])
PC1([(1,2,4,5),(1,3,3)])
```

```python
import math
from matplotlib import pyplot as plt
from matplotlib import style

def PC1(z):
    if (len(z) != 2) or (len(z[1]) < 3) or (len(z[0]) < 3):
        print("Se necesita una lista con dos elementos y que\
                cada una de las listas tenga más de 3 elementos")
    else:
        print(int(Norma(z[0][0],z[1][0])),
              int(Norma(z[0][1],z[1][1])),
              int(Norma(z[0][2],z[1][2])))
        print(g((z[0][-3],z[1][-3])),g((z[0][-2],z[1][-2])),
              g((z[0][-1],z[1][-1])))
        if (len(z[1]) == len(z[0])):
            style.use("ggplot")
            plt.scatter(z[0],z[1],color="black")
            plt.xlabel("X")
            plt.ylabel("Y")
            plt.show()
        else:
            print("Tuplas de distinta longitud, no se pueden\
                   graficar los puntos")
    return None
```


Representations of second-quantized states by Stephane Virally is licensed under a Creative Commons Attribution 4.0 International License.

### Imports and definitions

```python
#
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
import scipy.constants as sc
import qutip as qu
import imageio
from matplotlib.image import imsave
from random import uniform
from matplotlib import colors as colors
from numpy.polynomial.hermite import hermval
from scipy.special import gamma, factorial
from numpy.lib.scimath import sqrt, log, power, arccos, arcsin, arctanh
from numpy import exp, sin, cos, tan, sinh, cosh, tanh, arctan, arcsinh, arccosh
sp.init_printing()
fS = 24;
fH = 6.0;
rcctxh = {"font.family": "STIXGeneral", "font.size": fS, "legend.fontsize": fS, "figure.titlesize": fS,
         "axes.titlesize": fS, "axes.labelsize": fS, "xtick.labelsize": fS, "ytick.labelsize": fS,
         "savefig.format": "pdf", "figure.figsize": [fH*1.618, fH]};
rcctxv = {"font.family": "STIXGeneral", "font.size": fS, "legend.fontsize": fS, "figure.titlesize": fS,
         "axes.titlesize": fS, "axes.labelsize": fS, "xtick.labelsize": fS, "ytick.labelsize": fS,
         "savefig.format": "pdf", "figure.figsize": [fH, fH*1.618]};
rcctxs = {"font.family": "STIXGeneral", "font.size": fS, "legend.fontsize": fS, "figure.titlesize": fS,
         "axes.titlesize": fS, "axes.labelsize": fS, "xtick.labelsize": fS, "ytick.labelsize": fS,
         "savefig.format": "pdf", "figure.figsize": [fH, fH]};
```

### Utility functions

```python
# RGB image from figure
def fig2img(canvas, colortype='RGB', croph=0, cropw=0):
    """
    Extracts RGB image from figure
    canvas: canvas of the figure
    colortype: RGB or alpha-RGB (ARGB)
    croph: crop height (figure croped around center, to height croph)
    cropw: crop width (figure croped around center, to width cropw)
    """
    canvas.draw()
    (width, height) = canvas.get_width_height()
    if colortype == 'ARGB':
        imgarray = np.frombuffer(canvas.tostring_argb(), dtype='uint8').reshape((height, width, 4))
    else:
        imgarray = np.frombuffer(canvas.tostring_rgb(), dtype='uint8').reshape((height, width, 3))
    if (croph>0) and (croph<height):
        crophT = (height-croph)//2
        crophB = height-croph-crophT
        imgarray = imgarray[crophT:-crophB,:,:]
    if (cropw>0) and (cropw<width):
        cropwL = (width-cropw)//2
        cropwR = width-cropw-cropwL
        imgarray = imgarray[:,cropwL:-cropwR,:]
    return imgarray

# Color normalization around definite midpoint
class mpNormalize(colors.Normalize):
    def __init__(self, vmin=None, vmax=None, vmid=0., clip=False):
        self.vmid = vmid
        colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        x, y = [self.vmin, self.vmid, self.vmax], [0, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y))

# Hermite function series from Hermite polynomial series hermval
def hermfval(x, cs):
    ns = np.arange(len(cs))
    return hermval(x, cs/sqrt((2**ns)*factorial(ns)))*exp(-x**2/2)/(sc.pi)**0.25

# Draw random data from given probability density function
def randomfromfun(data, frequency, numberofpoints):
    cumulativefrequency = np.zeros(len(frequency))
    cumulativefrequency[0] = frequency[0]
    for i in range(1, len(frequency)):
        cumulativefrequency[i] = cumulativefrequency[i-1]+frequency[i]+1e-12
    res = np.zeros(numberofpoints)
    for i in range(numberofpoints):
        y = uniform(0, cumulativefrequency[-1])
        res[i] = np.interp(y, cumulativefrequency, data)
    return res
```

### Second quantized states visualization functions

```python
# Sticks
def plotSticks(S, nmax, yzlim, titlename='', savename=''):
    ns = S.data.toarray().flatten()
    reals = np.real(ns)
    imags = np.imag(ns)
    if nmax > len(ns):
        nmax = len(ns)
    with plt.rc_context(rcctxv):
        fig = plt.figure()
        ax = fig.add_subplot(111, projection='3d')
        ax.set_facecolor('white')
        ax.plot((0, nmax-1), (0.0,0.0), (0.0,0.0), color='lightslategrey', lw=2)
        for n in range(nmax-1,-1,-1):
            ax.quiver(n, 0., 0., 0., reals[n], imags[n], color='midnightblue', lw=2, arrow_length_ratio=0.2, length=1.5)
            #ax.plot((n, n), (0.0, reals[n]), (0.0, imags[n]), color='steelblue', lw=2)
            #ax.plot((n, n), (reals[n], reals[n]), (imags[n] , imags[n]), marker='o', color='tomato')
        #ax.view_init(elev=-135, azim=5)
        ax.view_init(elev=-120, azim=10)
        ax.set_xlim(-0.5, nmax-0.5)
        ax.set_ylim(-yzlim, yzlim)
        ax.set_zlim(-yzlim, yzlim)
        ax.set_axis_off()
        #plt.tick_params(axis='both', which='both', bottom=False, top=False, labelbottom=False, right=False, left=False, labelleft=False)
        if titlename:
            plt.title(titlename)
        image = fig2img(fig.canvas)
        if savename:
            fig.set_dpi(144)
            #fig.canvas.print_png(savename + '.png', transparent=True, bbox='tight', pad=0)
            fig.canvas.print_png(savename + '.png')
        plt.close(fig)
    return image

# Wigner
def plotWigner(S, X, ticks, ticklabels, titlename='', savename='', cmap='bwr'):
    W = qu.wigner(S, X, X)
    with plt.rc_context(rcctxh):
        fig = plt.figure()
        ax = fig.add_subplot(111, aspect='equal')
        ax.set_facecolor('white')
        ctr = ax.contour(X, X, W, 1000, cmap=cmap, norm=mpNormalize())
        #cb = fig.colorbar(ctr, ax=ax)
        #cb.set_ticks(ticks)
        #cb.set_ticklabels(ticklabels)
        #ax.set_axis_off()
        plt.tick_params(axis='both', which='both', bottom=False, top=False, labelbottom=False, right=False, left=False, labelleft=False)
        if titlename:
            plt.title(titlename)
        image = fig2img(fig.canvas)
        if savename:
            fig.set_dpi(144)
            #fig.canvas.print_png(savename + '.png', transparent=True, bbox='tight', pad=0)
            fig.canvas.print_png(savename + '.png')
        plt.close(fig)
    return image

# Harmonic oscillator levels
def plotHarmonic(S, nmax, xlim, ylim, titlename='', savename=''):
    ns = S.data.toarray().flatten()
    creals = np.real(ns)
    cimags = np.imag(ns)
    if nmax > len(ns):
        nmax = len(ns)
    k = nmax/xlim**2
    xs = np.linspace(-xlim, xlim, int(100*xlim)+1)
    reals = np.zeros([nmax+1, len(xs)])
    imags = np.zeros([nmax+1, len(xs)])
    for i in range(nmax+1):
        zerosandoneone = np.zeros(i+1, dtype=int)
        zerosandoneone[-1] = 1
        reals[i] = hermfval(xs, creals[:i+1]*zerosandoneone)
        imags[i] = hermfval(xs, cimags[:i+1]*zerosandoneone)
    ws = hermfval(xs, (creals+1j*cimags))
    with plt.rc_context(rcctxs):
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.set_facecolor('white')
        for i in range(nmax+1):
            ax.plot(xs, reals[i]+i, color='midnightblue', lw=1)
            ax.plot(xs, imags[i]+i, color='darkred', lw=1)
        ax.plot(xs, 2*abs(ws)**2-2, color='darkgreen', lw=1)
        ax.plot(xs, k*xs**2, color='lightslategray', lw=1)
        ax.set_xlim(-xlim, xlim)
        ax.set_ylim(-ylim-2, ylim+nmax)
        #ax.set_axis_off()
        plt.tick_params(axis='both', which='both', bottom=False, top=False, labelbottom=False, right=False, left=False, labelleft=False)
        if titlename:
            plt.title(titlename)
        image = fig2img(fig.canvas)
        if savename:
            fig.set_dpi(144)
            #fig.canvas.print_png(savename + '.png', transparent=True, bbox='tight', pad=0)
            fig.canvas.print_png(savename + '.png')
        plt.close(fig)
    return image

# Time-like
def plotTime(evS, ts, xlim, titlename='', savename='', color='midnightblue'):
    rs = np.zeros([len(evS.states), 100])
    xs = np.linspace(-xlim, xlim, int(100*xlim)+1)
    for i in range(len(evS.states)):
        ns = evS.states[i].data.toarray().flatten()
        fs = abs(hermfval(xs, ns))**2
        rs[i] = randomfromfun(xs, fs, 100)
    with plt.rc_context(rcctxs):
        fig = plt.figure()
        ax = fig.add_subplot(111)
        ax.set_facecolor('white')
        ax.plot(ts, rs, color=color, marker='.', linestyle='None', markersize=1)
        ax.set_xlim(ts[0], ts[-1])
        ax.set_ylim(-xlim, xlim)
        #ax.set_axis_off()
        plt.tick_params(axis='both', which='both', bottom=False, top=False, labelbottom=False, right=False, left=False, labelleft=False)
        #plt.xlabel('$t/T$')
        #plt.ylabel('Electric field [arb. un.]')
        if titlename:
            plt.title(titlename)
        fig.set_dpi(144)
        image = fig2img(fig.canvas, croph=640, cropw=640)
        if savename:
            imsave(savename+'.png', image)
        plt.close(fig)
    return image
```

### Hilbert space and Hamiltonian for a single second quantized frequency mode

```python
# Fock space dimension
N = 40
# Hamiltonian
H = 2*sc.pi*qu.num(40)
```

### Coherent state

```python
# Creation
Sa = (qu.coherent(N, np.sqrt(3)*1j)).unit()
# Evolution
n_a = 40
max_a = 1
times_a = np.linspace(0.0, max_a*(n_a-1)/n_a, n_a)
evSa = qu.mesolve(H, Sa, times_a, [], [])
```

```python
# Wigner
X_a = np.linspace(-7,7,351)
W_a = qu.wigner(evSa.states[0], X_a, X_a)
ticks_a = [0., W_a.max()]
ticklabels_a = ['{:.2f}'.format(0.),'{:.2f}'.format(W_a.max())]
wignercoherentimages = [plotWigner(S, X_a, ticks_a, ticklabels_a, 'Coherent state', '', 'bwr') for S in evSa.states]
imageio.mimsave('./wignercoherent.gif', wignercoherentimages, fps=10);
#wignercoherentimages = [plotWigner(S, X_a, ticks_a, ticklabels_a, 'Coherent state', '', 'PRGn') for S in evSa.states]
#imageio.mimsave('./wignercoherentgreen.gif', wignercoherentimages, fps=15);
#wignercoherentimages = [plotWigner(S, X_a, ticks_a, ticklabels_a, 'Coherent state', '', 'RdBu') for S in evSa.states]
#imageio.mimsave('./wignercoherentblue.gif', wignercoherentimages, fps=20);
for i in range(n_a)[::n_a//8]:
    plotWigner(evSa.states[i], X_a, ticks_a, ticklabels_a, '', './wignercoherent_{}'.format(i), 'bwr')
#for i in range(n_a)[::n_a//8]:
#    plotWigner(evSa.states[i], X_a, ticks_a, ticklabels_a, '', './wignercoherentgreen_{}'.format(i), 'PRGn')
#for i in range(n_a)[::n_a//8]:
#    plotWigner(evSa.states[i], X_a, ticks_a, ticklabels_a, '', './wignercoherentblue_{}'.format(i), 'RdBu')
```

```python
# Sticks
#stickcoherentimages = [plotSticks(S, 15, 0.8, 'Coherent state') for S in evSa.states]
#imageio.mimsave('./stickcoherent.gif', stickcoherentimages, fps=5)
for i in range(n_a)[::n_a//8]:
    plotSticks(evSa.states[i], 15, 0.6, '', './stickcoherent_{}'.format(i))
```

```python
# Harmonic oscillator
#harmoniccoherentimages = [plotHarmonic(S, 8, 6.0, 1.0, 'Coherent state') for S in evSa.states]
#imageio.mimsave('./harmoniccoherent.gif', harmoniccoherentimages, fps=10)
for i in range(n_a)[::n_a//8]:
    plotHarmonic(evSa.states[i], 8, 6.0, 1.0, '', './harmoniccoherent_{}'.format(i))
```

```python
# Time-like
#doubletimes_a = np.linspace(0.0, 2.0, 801) # Blue
doubletimes_a = np.linspace(0.0, 1.5, 601) # Green
#doubletimes_a = np.linspace(0.0, 1.0, 401) # Red
doubleevSa = qu.mesolve(H, Sa, doubletimes_a, [], [])
#plotTime(doubleevSa, doubletimes_a, 6, '', './timecoherent', 'midnightblue');
plotTime(doubleevSa, doubletimes_a, 6, '', './timecoherentgreen', 'darkgreen');
#plotTime(doubleevSa, doubletimes_a, 6, '', './timecoherentred', 'red');
```

### Cat state

```python
# Creation
Sc = (qu.coherent(N, np.sqrt(5))+qu.coherent(N, -np.sqrt(5))).unit()
# Evolution
n_c = 40
max_c = 1
times_c = np.linspace(0.0, max_c*(n_c-1)/n_c, n_c)
evSc = qu.mesolve(H, Sc, times_c, [], [])
```

```python
# Wigner
X_c = np.linspace(-7,7,351)
W_c = qu.wigner(evSc.states[0], X_c, X_c)
ticks_c = [W_c.min(), 0., W_c.max()]
ticklabels_c = ['{:.2f}'.format(W_c.min()),'{:.2f}'.format(0.),'{:.2f}'.format(W_c.max())]
wignercatimages = [plotWigner(S, X_c, ticks_c, ticklabels_c, 'Cat state') for S in evSc.states]
imageio.mimsave('./wignercat.gif', wignercatimages, fps=10)
for i in range(n_c)[::n_c//8]:
    plotWigner(evSc.states[i], X_c, ticks_c, ticklabels_c, '', './wignercat_{}'.format(i))
```

```python
# Sticks
#stickcatimages = [plotSticks(S, 15, 0.8, 'Cat state') for S in evSc.states]
#imageio.mimsave('./stickcat.gif', stickcatimages, fps=5)
for i in range(n_c)[::n_c//8]:
    plotSticks(evSc.states[i], 15, 0.6, '', './stickcat_{}'.format(i))
```

```python
# Harmonic oscillator
#harmoniccatimages = [plotHarmonic(S, 8, 6.0, 1.0, 'Cat state') for S in evSc.states]
#imageio.mimsave('./harmoniccat.gif', harmoniccatimages, fps=10)
for i in range(n_c)[::n_c//8]:
    plotHarmonic(evSc.states[i], 8, 6.0, 1.0, '', './harmoniccat_{}'.format(i))
```

```python
# Time-like
#doubletimes_c = np.linspace(0.0, 2.0, 801)
doubletimes_c = np.linspace(0.0, 1.0, 401)
doubleevSc = qu.mesolve(H, Sc, doubletimes_c, [], [])
#plotTime(doubleevSc, doubletimes_c, 6, '', './timecat');
plotTime(doubleevSc, doubletimes_c, 6, '', './timecatred','red');
```

### Squeezed vacuum

```python
# Creation
Ssv = (qu.squeeze(N, 0.7)*qu.fock(N, 0)).unit()
# Evolution
n_sv = 40
max_sv = 1
times_sv = np.linspace(0.0, max_sv*(n_sv-1)/n_sv, n_sv)
evSsv = qu.mesolve(H, Ssv, times_sv, [], [])
```

```python
# Wigner
X_sv = np.linspace(-7,7,351)
W_sv = qu.wigner(evSsv.states[0], X_sv, X_sv)
ticks_sv = [0, W_sv.max()]
ticklabels_sv = ['{:.2f}'.format(0),'{:.2f}'.format(W_sv.max())]
wignersqueezedvacimages = [plotWigner(S, X_sv, ticks_sv, ticklabels_sv, 'Squeezed vacuum') for S in evSsv.states]
imageio.mimsave('./wignersqueezedvac.gif', wignersqueezedvacimages, fps=10)
for i in range(n_sv)[::n_sv//8]:
    plotWigner(evSsv.states[i], X_sv, ticks_sv, ticklabels_sv, '', './wignersqueezedvac_{}'.format(i))
```

```python
# Sticks
#sticksqueezedvacimages = [plotSticks(S, 15, 0.8, 'Squeezed vacuum') for S in evSsv.states]
#imageio.mimsave('./sticksqueezedvac.gif', sticksqueezedvacimages, fps=5)
for i in range(n_sv)[::n_sv//8]:
    plotSticks(evSsv.states[i], 15, 0.6, '', './sticksqueezedvac_{}'.format(i))
```

```python
# Harmonic oscillator
#harmonicsqueezedvacimages = [plotHarmonic(S, 8, 6.0, 1.0, 'Squeezed vacuum') for S in evSsv.states]
#imageio.mimsave('./harmonicsqueezedvac.gif', harmonicsqueezedvacimages, fps=10)
for i in range(n_sv)[::n_sv//8]:
    plotHarmonic(evSsv.states[i], 8, 6.0, 1.0, '', './harmonicsqueezedvac_{}'.format(i))
```

```python
# Time-like
#doubletimes_sv = np.linspace(0.0, 2.0, 801)
doubletimes_sv = np.linspace(0.0, 1.0, 401)
doubleevSsv = qu.mesolve(H, Ssv, doubletimes_sv, [], [])
#plotTime(doubleevSsv, doubletimes_sv, 6, '', './timesqueezedvac');
plotTime(doubleevSsv, doubletimes_sv, 6, '', './timesqueezedvacred', 'red');
```

### Amplitude squeezed state

```python
# Creation
Sas = (qu.displace(N, np.sqrt(3))*qu.squeeze(N, 0.7)*qu.fock(N, 0)).unit()
# Evolution
n_as = 40
max_as = 1
times_as = np.linspace(0.0, max_as*(n_as-1)/n_as, n_as)
evSas = qu.mesolve(H, Sas, times_as, [], [])
```

```python
# Wigner
X_as = np.linspace(-7,7,351)
W_as = qu.wigner(evSas.states[0], X_as, X_as)
ticks_as = [0, W_as.max()]
ticklabels_as = ['{:.2f}'.format(0),'{:.2f}'.format(W_as.max())]
wignerampsqueezedimages = [plotWigner(S, X_as, ticks_as, ticklabels_as, 'Amplitude squeezed') for S in evSas.states]
imageio.mimsave('./wignerampsqueezed.gif', wignerampsqueezedimages, fps=10)
for i in range(n_as)[::n_as//8]:
    plotWigner(evSas.states[i], X_as, ticks_as, ticklabels_as, '', './wignerampsqueezed_{}'.format(i))
```

```python
# Sticks
#stickampsqueezedimages = [plotSticks(S, 15, 0.8, 'Amplitude squeezed') for S in evSas.states]
#imageio.mimsave('./stickampsqueezed.gif', stickampsqueezedimages, fps=5)
for i in range(n_as)[::n_as//8]:
    plotSticks(evSas.states[i], 15, 0.6, '', './sticksampsqueezed_{}'.format(i))
```

```python
# Harmonic oscillator
#harmonicampsqueezedimages = [plotHarmonic(S, 8, 6.0, 1.0, 'Amplitude squeezed') for S in evSas.states]
#imageio.mimsave('./harmonicampsqueezed.gif', harmonicampsqueezedimages, fps=10)
for i in range(n_as)[::n_as//8]:
    plotHarmonic(evSas.states[i], 8, 6.0, 1.0, '', './harmonicampsqueezed_{}'.format(i))
```

```python
# Time-like
doubletimes_as = np.linspace(0.0, 2.0, 801)
doubleevSas = qu.mesolve(H, Sas, doubletimes_as, [], [])
plotTime(doubleevSas, doubletimes_as, 6, '', './timeampsqueezed');
```

### Phase squeezed state

```python
# Creation
Sps = (qu.displace(N, np.sqrt(3))*qu.squeeze(N, -0.7)*qu.fock(N, 0)).unit()
# Evolution
n_ps = 40
max_ps = 1
times_ps = np.linspace(0.0, max_ps*(n_ps-1)/n_ps, n_ps)
evSps = qu.mesolve(H, Sps, times_ps, [], [])
```

```python
# Wigner
X_ps = np.linspace(-7,7,351)
W_ps = qu.wigner(evSps.states[0], X_ps, X_ps)
ticks_ps = [0, W_ps.max()]
ticklabels_ps = ['{:.2f}'.format(0),'{:.2f}'.format(W_ps.max())]
wignerphasesqueezedimages = [plotWigner(S, X_ps, ticks_ps, ticklabels_ps, 'Phase squeezed') for S in evSps.states]
imageio.mimsave('./wignerphasesqueezed.gif', wignerphasesqueezedimages, fps=10)
for i in range(n_ps)[::n_ps//8]:
    plotWigner(evSps.states[i], X_ps, ticks_ps, ticklabels_ps, '', './wignerphasesqueezed_{}'.format(i))
```

```python
# Sticks
#stickphasesqueezedimages = [plotSticks(S, 15, 0.8, 'Phase squeezed') for S in evSps.states]
#imageio.mimsave('./stickphasesqueezed.gif', stickphasesqueezedimages, fps=5)
for i in range(n_ps)[::n_ps//8]:
    plotSticks(evSps.states[i], 15, 0.6, '', './sticksphasesqueezed_{}'.format(i))
```

```python
# Harmonic oscillator
#harmonicphasesqueezedimages = [plotHarmonic(S, 8, 6.0, 1.0, 'Phase squeezed') for S in evSps.states]
#imageio.mimsave('./harmonicphasesqueezed.gif', harmonicphasesqueezedimages, fps=10)
for i in range(n_ps)[::n_ps//8]:
    plotHarmonic(evSps.states[i], 8, 6.0, 1.0, '', './harmonicphasesqueezed_{}'.format(i))
```

```python
# Time-like
doubletimes_ps = np.linspace(0.0, 2.0, 801)
doubleevSps = qu.mesolve(H, Sps, doubletimes_ps, [], [])
plotTime(doubleevSps, doubletimes_ps, 6, '', './timephasesqueezed');
```

### Fock state

```python
# Creation (this is an eigenstate of the Hamiltonian)
Sn = (qu.fock(N, 3)).unit()
```

```python
# Wigner
X_n = np.linspace(-7,7,351)
W_n = qu.wigner(Sn, X_n, X_n)
ticks_n = [W_n.min(), 0, W_n.max()]
ticklabels_n = ['{:.2f}'.format(W_n.min()),'{:.2f}'.format(0),'{:.2f}'.format(W_n.max())]
wignerfockimage = [plotWigner(Sn, X_n, ticks_n, ticklabels_n, 'Fock N=3')]
imageio.mimsave('./wignerfock.gif', wignerfockimage)
plotWigner(Sn, X_n, ticks_n, ticklabels_n, '', './wignerfock');
```

```python
# Sticks
plotSticks(Sn, 15, 0.6, '', './stickfock');
```

```python
# Harmonic oscillator
plotHarmonic(Sn, 8, 6.0, 1.0, '', './harmonicfock');
```

```python
# Time-like
doubletimes_n = np.linspace(0.0, 2.0, 801)
doubleevSn = qu.mesolve(H, Sn, doubletimes_n, [], [])
plotTime(doubleevSn, doubletimes_n, 6, '', './timefock');
```

### Vacuum state

```python
# Creation (this is an eigenstate of the Hamiltonian)
Sv = (qu.fock(N, 0)).unit()
```

```python
# Wigner
X_v = np.linspace(-7,7,351)
W_v = qu.wigner(Sv, X_v, X_v)
ticks_v = [W_v.min(), 0, W_v.max()]
ticklabels_v = ['{:.2f}'.format(W_v.min()),'{:.2f}'.format(0),'{:.2f}'.format(W_v.max())]
wignervacuumimage = [plotWigner(Sv, X_v, ticks_v, ticklabels_v, 'Vacuum')]
imageio.mimsave('./wignervacuum.gif', wignervacuumimage)
plotWigner(Sv, X_v, ticks_v, ticklabels_v, '', './wignervacuum');
```

```python
# Sticks
plotSticks(Sv, 15, 1.1, '', './stickvacuum');
```

```python
# Harmonic oscillator
plotHarmonic(Sv, 8, 6.0, 1.0, '', './harmonicvacuum');
```

```python
# Time-like
doubletimes_v = np.linspace(0.0, 2.0, 801)
doubleevSv = qu.mesolve(H, Sv, doubletimes_n, [], [])
#plotTime(doubleevSv, doubletimes_v, 6, '', './timevacuum');
plotTime(doubleevSv, doubletimes_v, 6, '', './timevacuumred', 'red');
```

```python

```