```python
%matplotlib inline
```

# 592B, Class 5.1 (09/28).  The Fourier Transform in Action

```python
import numpy as np
import matplotlib.pyplot as plt
import librosa
from scipy import fftpack, signal #new

from ipywidgets import interactive
from IPython.display import Audio, display
```

We just ended up with the Fourier series defined as $T \rightarrow \infty$, expressed as a double integral:

\begin{equation}
g(t) = \int_{-\infty}^{\infty} \int_{-\infty}^{\infty}e^{-2\pi ift}g(t)dt e^{2\pi ift}df
\end{equation}

and then we said you could rewrite it as $G(f)$, where $G(f)$ is defined as the Fourier transform of a function $g(t)$:

\begin{equation}
G(f) = \int_{-\infty}^{\infty}e^{-2\pi ift} g(t)dt
\end{equation}

and the inverse Fourier transform $g(t)$ of a function $G(f)$ is:

\begin{equation}
g(t) = \int_{-\infty}^{\infty}e^{2\pi ift} G(f)df
\end{equation}

Show that $g(t)$ rewritten in terms of $G(f)$ is the same as the $g(t)$ written out as a double integral.

---

Today we'll be having some fun with Fourier transforms, after all the hard work of mathematically deriving Fourier series and transforms. We'll be getting acquainted with [`scipy.fftpack`](https://docs.scipy.org/doc/scipy/reference/tutorial/fftpack.html). A good portion of this notebook is inspired by [this fantastic chapter of **Elegant SciPy**](https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch04.html).

## The Fourier Transform in action: a first look

Remember this function?

$$y(t) = \cos \left(\frac{9\pi}{2}t\right ) $$

We worked with it when we were studying aliasing. Let's sample it at 20 Hz, for a duration of 20 seconds.

```python
fs = 20 # Sampling rate of 20 Hz
t_start = 0; t_stop = 20
ns = int((t_stop - t_start) * fs)
#t = np.linspace(t_start,t_stop,ns)
t = np.linspace(t_start,t_stop,ns, endpoint=False)
x = np.cos(9/2*np.pi*t)

fig, ax = plt.subplots()
ax.plot(t, x)
ax.set_xlabel('Time [s]')
ax.set_ylabel('Signal amplitude')
plt.xlim(0,10)
plt.title("A familiar signal")
```

Now let's take the Fourier Transform of the signal!

```python
X = fftpack.fft(x)
freqs = fftpack.fftfreq(len(x)) * fs

fig, ax = plt.subplots()

ax.stem(freqs, np.abs(X), use_line_collection = True)
ax.set_xlabel('Frequency in Hertz [Hz]')
ax.set_ylabel('Magnitude')
ax.set_xlim(-fs/2, fs/2)
#ax.set_xlim(-3.5, 3.5)
ax.set_ylim(-5, 220)
ax.set_ylabel('Fourier transform of signal')
```

We end up with non-zero values at only -2.25 and 2.25 Hz. At the moment, let's not worry about why we also get negative frequencies.

---
***Discussion: why set the x-axis limits as [-fs/2,fs/2]?***

***Discussion: does it make sense that we're getting a peak at 2.25 Hz?***

---

***In-class exercise: what happens to the computed spectrum if you delete `endpoint=False` in the np.linspace command? Why? ***

---

***In-class exercise: repeat what we did above for the signal sampled at 1 Hz. What happens and why?***

---

### Again, a bit more slowly...

Let's walk through this step-by-step. Now let's define a complex wave with two components.

```python
fs = 100
t_stop = 10
t = np.linspace(0,t_stop, t_stop*fs, endpoint=False)
f1 = 5; f2= 2
x = 0.1 * np.sin(2 * np.pi * f1 * t) + 1* np.sin(2 * np.pi * f2 * t) + 50
```

Let's take the Fourier Transform and inspect the values we get.

```python
X = fftpack.fft(x)

print(X[0:5])
print(len(X))

plt.stem(X, use_line_collection = True)
```

Aha! We are getting **complex** values!

What we actually plot in the spectrum turns out to be the size of a complex number, its **modulus**, defined as:

$$Mod(a+bi) = \sqrt{a^2+b^2}$$

In python, we can work with complex numbers as follows:

```python
c = complex(3,4) # syntax: complex(a,b) for defining a+bi
print(c)
print(c.real) # the real component, a
print(c.imag) # the imaginary component, b
print(np.abs(c)) # the modulus
```

    (3+4j)
    3.0
    4.0
    5.0

So let's try plotting the Fourier transform again, this time plotting the modulus.

```python
print(np.abs(X[0:5]))
plt.stem(np.abs(X), use_line_collection = True)
```

But there are still some problems. First the x-axis is in terms of points. We have 1000 points, so we get an x-axis going up to 1000. How do we get this in terms of frequency? We need to set the x-axis to go from [$-f_{Nyquist}, f_{Nyquist}$], $\Delta f_s$ apart. This is where `fftpack.fftfreq` comes in.

***Discussion: What do you notice about the array `freqs`? How long is it? What numbers does it range over? How far are the numbers apart?***

```python
freqs = fftpack.fftfreq(len(x)) * fs
print(freqs)

#print(fftpack.fftfreq(len(x)))

```

    [  0.    0.1   0.2   0.3   0.4   0.5   0.6   0.7   0.8   0.9   1.    1.1
       1.2   1.3   1.4   1.5   1.6   1.7   1.8   1.9   2.    2.1   2.2   2.3
       2.4   2.5   2.6   2.7   2.8   2.9   3.    3.1   3.2   3.3   3.4   3.5
       3.6   3.7   3.8   3.9   4.    4.1   4.2   4.3   4.4   4.5   4.6   4.7
       4.8   4.9   5.    5.1   5.2   5.3   5.4   5.5   5.6   5.7   5.8   5.9
       6.    6.1   6.2   6.3   6.4   6.5   6.6   6.7   6.8   6.9   7.    7.1
       7.2   7.3   7.4   7.5   7.6   7.7   7.8   7.9   8.    8.1   8.2   8.3
       8.4   8.5   8.6   8.7   8.8   8.9   9.    9.1   9.2   9.3   9.4   9.5
       9.6   9.7   9.8   9.9  10.   10.1  10.2  10.3  10.4  10.5  10.6  10.7
      10.8  10.9  11.   11.1  11.2  11.3  11.4  11.5  11.6  11.7  11.8  11.9
      12.   12.1  12.2  12.3  12.4  12.5  12.6  12.7  12.8  12.9  13.   13.1
      13.2  13.3  13.4  13.5  13.6  13.7  13.8  13.9  14.   14.1  14.2  14.3
      14.4  14.5  14.6  14.7  14.8  14.9  15.   15.1  15.2  15.3  15.4  15.5
      15.6  15.7  15.8  15.9  16.   16.1  16.2  16.3  16.4  16.5  16.6  16.7
      16.8  16.9  17.   17.1  17.2  17.3  17.4  17.5  17.6  17.7  17.8  17.9
      18.   18.1  18.2  18.3  18.4  18.5  18.6  18.7  18.8  18.9  19.   19.1
      19.2  19.3  19.4  19.5  19.6  19.7  19.8  19.9  20.   20.1  20.2  20.3
      20.4  20.5  20.6  20.7  20.8  20.9  21.   21.1  21.2  21.3  21.4  21.5
      21.6  21.7  21.8  21.9  22.   22.1  22.2  22.3  22.4  22.5  22.6  22.7
      22.8  22.9  23.   23.1  23.2  23.3  23.4  23.5  23.6  23.7  23.8  23.9
      24.   24.1  24.2  24.3  24.4  24.5  24.6  24.7  24.8  24.9  25.   25.1
      25.2  25.3  25.4  25.5  25.6  25.7  25.8  25.9  26.   26.1  26.2  26.3
      26.4  26.5  26.6  26.7  26.8  26.9  27.   27.1  27.2  27.3  27.4  27.5
      27.6  27.7  27.8  27.9  28.   28.1  28.2  28.3  28.4  28.5  28.6  28.7
      28.8  28.9  29.   29.1  29.2  29.3  29.4  29.5  29.6  29.7  29.8  29.9
      30.   30.1  30.2  30.3  30.4  30.5  30.6  30.7  30.8  30.9  31.   31.1
      31.2  31.3  31.4  31.5  31.6  31.7  31.8  31.9  32.   32.1  32.2  32.3
      32.4  32.5  32.6  32.7  32.8  32.9  33.   33.1  33.2  33.3  33.4  33.5
      33.6  33.7  33.8  33.9  34.   34.1  34.2  34.3  34.4  34.5  34.6  34.7
      34.8  34.9  35.   35.1  35.2  35.3  35.4  35.5  35.6  35.7  35.8  35.9
      36.   36.1  36.2  36.3  36.4  36.5  36.6  36.7  36.8  36.9  37.   37.1
      37.2  37.3  37.4  37.5  37.6  37.7  37.8  37.9  38.   38.1  38.2  38.3
      38.4  38.5  38.6  38.7  38.8  38.9  39.   39.1  39.2  39.3  39.4  39.5
      39.6  39.7  39.8  39.9  40.   40.1  40.2  40.3  40.4  40.5  40.6  40.7
      40.8  40.9  41.   41.1  41.2  41.3  41.4  41.5  41.6  41.7  41.8  41.9
      42.   42.1  42.2  42.3  42.4  42.5  42.6  42.7  42.8  42.9  43.   43.1
      43.2  43.3  43.4  43.5  43.6  43.7  43.8  43.9  44.   44.1  44.2  44.3
      44.4  44.5  44.6  44.7  44.8  44.9  45.   45.1  45.2  45.3  45.4  45.5
      45.6  45.7  45.8  45.9  46.   46.1  46.2  46.3  46.4  46.5  46.6  46.7
      46.8  46.9  47.   47.1  47.2  47.3  47.4  47.5  47.6  47.7  47.8  47.9
      48.   48.1  48.2  48.3  48.4  48.5  48.6  48.7  48.8  48.9  49.   49.1
      49.2  49.3  49.4  49.5  49.6  49.7  49.8  49.9 -50.  -49.9 -49.8 -49.7
     -49.6 -49.5 -49.4 -49.3 -49.2 -49.1 -49.  -48.9 -48.8 -48.7 -48.6 -48.5
     -48.4 -48.3 -48.2 -48.1 -48.  -47.9 -47.8 -47.7 -47.6 -47.5 -47.4 -47.3
     -47.2 -47.1 -47.  -46.9 -46.8 -46.7 -46.6 -46.5 -46.4 -46.3 -46.2 -46.1
     -46.  -45.9 -45.8 -45.7 -45.6 -45.5 -45.4 -45.3 -45.2 -45.1 -45.  -44.9
     -44.8 -44.7 -44.6 -44.5 -44.4 -44.3 -44.2 -44.1 -44.  -43.9 -43.8 -43.7
     -43.6 -43.5 -43.4 -43.3 -43.2 -43.1 -43.  -42.9 -42.8 -42.7 -42.6 -42.5
     -42.4 -42.3 -42.2 -42.1 -42.  -41.9 -41.8 -41.7 -41.6 -41.5 -41.4 -41.3
     -41.2 -41.1 -41.  -40.9 -40.8 -40.7 -40.6 -40.5 -40.4 -40.3 -40.2 -40.1
     -40.  -39.9 -39.8 -39.7 -39.6 -39.5 -39.4 -39.3 -39.2 -39.1 -39.  -38.9
     -38.8 -38.7 -38.6 -38.5 -38.4 -38.3 -38.2 -38.1 -38.  -37.9 -37.8 -37.7
     -37.6 -37.5 -37.4 -37.3 -37.2 -37.1 -37.  -36.9 -36.8 -36.7 -36.6 -36.5
     -36.4 -36.3 -36.2 -36.1 -36.  -35.9 -35.8 -35.7 -35.6 -35.5 -35.4 -35.3
     -35.2 -35.1 -35.  -34.9 -34.8 -34.7 -34.6 -34.5 -34.4 -34.3 -34.2 -34.1
     -34.  -33.9 -33.8 -33.7 -33.6 -33.5 -33.4 -33.3 -33.2 -33.1 -33.  -32.9
     -32.8 -32.7 -32.6 -32.5 -32.4 -32.3 -32.2 -32.1 -32.  -31.9 -31.8 -31.7
     -31.6 -31.5 -31.4 -31.3 -31.2 -31.1 -31.  -30.9 -30.8 -30.7 -30.6 -30.5
     -30.4 -30.3 -30.2 -30.1 -30.  -29.9 -29.8 -29.7 -29.6 -29.5 -29.4 -29.3
     -29.2 -29.1 -29.  -28.9 -28.8 -28.7 -28.6 -28.5 -28.4 -28.3 -28.2 -28.1
     -28.  -27.9 -27.8 -27.7 -27.6 -27.5 -27.4 -27.3 -27.2 -27.1 -27.  -26.9
     -26.8 -26.7 -26.6 -26.5 -26.4 -26.3 -26.2 -26.1 -26.  -25.9 -25.8 -25.7
     -25.6 -25.5 -25.4 -25.3 -25.2 -25.1 -25.  -24.9 -24.8 -24.7 -24.6 -24.5
     -24.4 -24.3 -24.2 -24.1 -24.  -23.9 -23.8 -23.7 -23.6 -23.5 -23.4 -23.3
     -23.2 -23.1 -23.  -22.9 -22.8 -22.7 -22.6 -22.5 -22.4 -22.3 -22.2 -22.1
     -22.  -21.9 -21.8 -21.7 -21.6 -21.5 -21.4 -21.3 -21.2 -21.1 -21.  -20.9
     -20.8 -20.7 -20.6 -20.5 -20.4 -20.3 -20.2 -20.1 -20.  -19.9 -19.8 -19.7
     -19.6 -19.5 -19.4 -19.3 -19.2 -19.1 -19.  -18.9 -18.8 -18.7 -18.6 -18.5
     -18.4 -18.3 -18.2 -18.1 -18.  -17.9 -17.8 -17.7 -17.6 -17.5 -17.4 -17.3
     -17.2 -17.1 -17.  -16.9 -16.8 -16.7 -16.6 -16.5 -16.4 -16.3 -16.2 -16.1
     -16.  -15.9 -15.8 -15.7 -15.6 -15.5 -15.4 -15.3 -15.2 -15.1 -15.  -14.9
     -14.8 -14.7 -14.6 -14.5 -14.4 -14.3 -14.2 -14.1 -14.  -13.9 -13.8 -13.7
     -13.6 -13.5 -13.4 -13.3 -13.2 -13.1 -13.  -12.9 -12.8 -12.7 -12.6 -12.5
     -12.4 -12.3 -12.2 -12.1 -12.  -11.9 -11.8 -11.7 -11.6 -11.5 -11.4 -11.3
     -11.2 -11.1 -11.  -10.9 -10.8 -10.7 -10.6 -10.5 -10.4 -10.3 -10.2 -10.1
     -10.   -9.9  -9.8  -9.7  -9.6  -9.5  -9.4  -9.3  -9.2  -9.1  -9.   -8.9
      -8.8  -8.7  -8.6  -8.5  -8.4  -8.3  -8.2  -8.1  -8.   -7.9  -7.8  -7.7
      -7.6  -7.5  -7.4  -7.3  -7.2  -7.1  -7.   -6.9  -6.8  -6.7  -6.6  -6.5
      -6.4  -6.3  -6.2  -6.1  -6.   -5.9  -5.8  -5.7  -5.6  -5.5  -5.4  -5.3
      -5.2  -5.1  -5.   -4.9  -4.8  -4.7  -4.6  -4.5  -4.4  -4.3  -4.2  -4.1
      -4.   -3.9  -3.8  -3.7  -3.6  -3.5  -3.4  -3.3  -3.2  -3.1  -3.   -2.9
      -2.8  -2.7  -2.6  -2.5  -2.4  -2.3  -2.2  -2.1  -2.   -1.9  -1.8  -1.7
      -1.6  -1.5  -1.4  -1.3  -1.2  -1.1  -1.   -0.9  -0.8  -0.7  -0.6  -0.5
      -0.4  -0.3  -0.2  -0.1]

OK, now we're set!

```python
fig, ax = plt.subplots()

ax.stem(freqs, np.abs(X), use_line_collection = True)
ax.set_xlabel('Frequency in Hertz [Hz]')
ax.set_ylabel('Magnitude')
#ax.set_xlim(-fs/2, fs/2)
ax.set_xlim(-10, 10)
ax.set_ylim(-5, 300)
ax.set_ylabel('Fourier transform of signal')

```

***In-class exercise: Fourier transform of a square wave. Compute a spectrum for the square wave you defined in the Review Questions.***

---

## From spectrum to spectrogram

Let's have some fun with [Japanese vowels](http://www.phonetics.ucla.edu/course/chapter9/japanese/japanese.html) from soundfiles collected by Peter Ladefoged. First we'll use `librosa` to load an audio recording of [ma]. And just for kicks, let's listen to it resampled at different sampling rates.

```python
y, sr = librosa.load("ma.aiff", sr = None)
print(type(y)) # Note that it is a numpy array
print("The sampling rate is: ", sr, "Hz")

y_8k = librosa.resample(y, sr, 8000)
y_2k = librosa.resample(y, sr, 2000)
y_500 = librosa.resample(y, sr, 500)

display(Audio(data=y,rate = sr))
display(Audio(data=y_8k,rate = 8000))
display(Audio(data=y_2k,rate = 2000))
display(Audio(data=y_500,rate = 500))

```

    <class 'numpy.ndarray'>
    The sampling rate is:  22050 Hz

<audio  controls="controls" >
    <source src="data:audio/wav;base64,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" type="audio/wav" />
    Your browser does not support the audio element.
</audio>

<audio  controls="controls" >
    <source src="data:audio/wav;base64,UklGRm4OAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YUoOAADQ/7X/sf+u/6//pf/v/woA+f8CAPz/9v/6//X//P/5//H/9P/y/+3/8//0/+//7f/p//L/8P/x//X/7P/r/+j/5P/t/+r/5f/g/97/4//t/+7/7v/1//P//v/6//L/AAAJAAcABgD6//b/9P/j/9b/xv++/7b/oP+W/6L/n/+W/4//mv+p/6H/q//H/87/3//r/+H/9v8IACMAOAAnAB4AIQAfACUAHgAXAAkA8f/d/8P/rf+d/4b/Yf9O/0L/Pv83/zn/Rf9Q/2b/cf+F/6L/wf/p/xoAQABnAH8AjgCpAK4AsgC2AI4AbgBeACEA/P/Z/5f/b/9E/w3/7v69/qv+p/6L/p7+of66/uT+D/9d/6D/8/9RAKcADAFvAc8BIAJZAnoCbAJIAggCtwFJAdQASwC8/zj/p/4//tL9ff0n/dD8rPyW/Kr87vxA/ZT9A/6S/kn/MgBKAWoCkwOkBHAFLAa4BuwG0gYTBgwFFwTuAtMBgwAJ/6/9UPwv+yf6KPlg+LL3afeb9wr4x/ic+Z/6Avyo/Zj/twERBBgG2QfACa4LYA3/DXQNDwxkCi8JKgghBlgC5v11+nv4DfdF9d7ysPDO74rwRPLh8zD1cfbo9+v5nvy4/9YCNwWHB4IKbQ1lEPcSVhTZE/QQdw45DgwLbAW2AZ/9HfpJ+A/2y/MF8QPvLe+V7nruR/Ae8W3xZvIt9Gf2aPhh+0P/ngIvBmIKtA51EtoVWxlCGzAZSRTwEaoOUQdqA8X/ivn39XXyNPAW767svO2u7gDu/O8N8SPxDPIF8w31Pvah9wj7Lv6RAm4HJwsZEHUUgxhRHZ4foR3EFm8RdQ4NCL0D+v7k9iTyQ+5Y7eHuze1+7xjx8vAO9OL1QvRl8RfxqPN782HzofS79Rj6w/8FBhsLZA5eFPsZMR4PIxslFCCaFiURngzdBa0A+/ju8MLr/ehy62Xtl+2T8B3y8PPO9+v3SPZO9bXz5vMh9NvzpfXs9hv6KABbBbIKBxBBFaQbZSHKJoYoSiD0Ff8Q0wumBlkApvXl7DLnueaF6/zsv+0B8N/wTfR++D355/ZS9OP06fRT86Pzi/Tu9pH73ACHBuoKtBDoF6odtyOdKeEqEiHIFMkQsQu2BZj/nfNx6rHkh+QQ60Ps2uyl7/3vz/P991z5GfhM9F/0h/SZ8+D08/Qs96P71ACiB14MHBKdGTAg+ie+LfUqjR7vE5sQNQwnBi/8s+6D5ZThFOXK6v/qdesE7ezuWPQi+Yf6wvel9Ab1evTS8yv0dvQR9776wv/iBQcLohHnGNMflCc2LoEtfiGFFagQYwxNBz79Ue/W5N/ew+EX6Djpwuny6lXtF/Or+Kb7b/gK9O70OvXC9P/z4/JJ9e742/3dA6wI7g8OGCwfjCZ/LXYv3CSUF2MSAw6DCOr+ePAR5frdjd9E5u3nJ+iw6f/rFfHm9Kn2kPcW9pv0ovOK8ivzyfSo9zv9xwJOB2YNzxQJHcUlAC6eM/oqjxhOE/wSKAwLBXH2nefw39DdGOZq6azl4uet6XTsE/Li8wr1RPQ28yL0APLK8dv0FviF/hEFsAmhD2oWUB8vKZAxfzWqKfkW3ROmEyQN9QOp8zvmo9893//nv+j44+3mQuge7f/wk/Ld8hn0uvHY60Tuw/B09zoACg1TFHMa7yJgLWpCKl3TWB4hROaDyCPS2OL37SjxN/PlBegb2iV6FDPtO859yY/PY9V41qDfufQbDDsVGQd28a3ufveXCfMZtiI9Ncs/WFKVWRliUiZAtV6HcKCs7hQieibuE2IQGx3sEafgrq4UmjvLrAQODjz+IOj69uoBo/Hk2IvTY/a/HVspzypWKDVBe042XrFgR2RKIbCimoS1mmwDH0wjOJ0SwQJRDKX3ULgFmlWrDu2fFerxzeoc9I4L5fpk0kHUie2UGkIi9ybcK+I1HEDOUPBcuGsWRfu3d4KwljcCP1RENEwES/YHDSP7NrpHoEW/DPKTDVHcctgv+tUT3vrAy4DVYfaHGfoWTyDJKRs15zapSiZgPnJtNden6YJDp0kn21ZHH3z1i/vaEYjqFa5GpWbdgQDGA+TR89Pj/OkLzPBAzWrjPgT3EVcTpB92Kpsyty1VTTBmMHdxFZ2SEIbuxqVJ3U6PDq7u2QS3DCzXOqjHuFjxsw7X6Ua9Sub9EsIPHdnvyFnt1hV6EVgQASWKLeEqaDOHVl9yPWTF06SCSJe+C0BojzSj9SbsyBGY+bS9Oa0d394E9wF7y3/NNwRdFC/yq8gZ3wsIpxWxCT8f3SwgJvge3EZCa7Z5Fxb1kM6HidPPVIJSeQob6LYD9wsi04G108zX91kGtN8by1nyOxAGAgrSWtCB9OsZBg9qFPwmniKBH9c1Ul4uetpMULUBgJOuNCrSZQgm+O9G8mYNWOmowBDFBunRBf3xCskh4+AIfQ1h5SrMyOQ3D54ZHhSgIrce8hs0K1NWMHU2Y7fXi4BNnb4Mj2WoMxH7Ye+XDMTyZsadx6fjYv8A8xfPneRgDjMRZuXNyA7iZA/1GzAUCiNEIswZAiLbTlRzuGu05pODsZvnCcFhwTS4+v3u/wyC+/vMhsR25Yb/K/TpzgPihw1TFAntn8hj3/gL/x2MHIclbBsSFbglWVJ+cXxeHN78gfCpGBI4Vu0scvlG8gEMB/sG0WzQ/d+M9DbuSNin8eAK0wbg40DUROfTDR8d6R+sJWEW4hOaMfdYW2o4RuO+S4P4w40qwFPpHnHtffLnDEvyQdHa01XktvFl5JTcHf15Dbz8pN2Z0qzwuRIfHUIlAiAyEvMWTzwSX/xuFBlRlcaRHvHhT6Q/Dv565z8ElQ2X4R7HPdtE7aT02t1m4q4FggXN8kjTwd08ADkYOyFnIp4YRQ5zJp9Oz2FZUC/W4IKKuVIlTVaZHhLnnOv1D3f+pdQDzMDksu5W7qDdhPLxB1T2p+RW0KrwVw4+HzAkBBevDssVQjubWlplrBiUmLuSTvPDTeRCDPiu4PwAxQ/B50nSVtaf4NzwGuGj6I4KgQUO6bHNzNe1BWMhhB8XGLcQVw6/KJNPl2SVRNzI24Gxxvc15FcRFEPc5OqSEyIABtmP2QbfaeSf4TrtLgb8ADHmS9lN3vb1kA1qF6ogSBb4BNoVxEPWXaJkdwkYj7WXawT4Xhs+/+to1foDzBXB6Q/XE9vB5tHtzN+t7WIDOQC75rfR3d0+ByIdqhxyFn4GlAvjLuxP8WfqQHK5cIE50r1GV11gC1nOW+ziG0cD69al2STlSui05tzsrAfCAp7guMkI328GchilEbwT0xE0CnYXtjuSYVNeDPswiZSgNhhIY5E1yt761HcLUBqW6wPUS+H73wjnq+sS/+MEnutq2XfbdveICScSDhfjHfQPSQ0kL+JWgWJfCIWQtJ0sGwxoSDPd2Y7QchDoIv/smdE24c/kbenB7wgF2wss60/ORMxQ9WUWTxUdBToOZBZsFb4r6EV8WlAyLrWrhLzmbVlBVhT7A8XG7ysmeArH25fYUOsd3g7lYgEMHlb25sIOy5HyRBvgCfv+/gqTIPMUjxhRPZ5bz0mc07CBMsj4TfRlVAuyw+nhwCa2HMLcGtJd6Yf0w+5w4CwN+xnW5ea5FcuRC1olgQyi6vgHoiOfH14vo0GvWZ4eSp9BjyUD3GnCSSzn78CAADgwTwK400fYnfLQCjLujtz7ArwSCOlXsrvQ1husNV8CZNrQAgou1DKzMfA9vE5j5a6FlL4SNUJrdBzvyz7WbCJFJKDpj8+53SoF7Qto78vyZAj8/UnH4LhO+CwuMRyy5Drdbg8kOboqCSqVNZk/eehmjSTJri3wWJEYB9Rr4NEdQCD16P3RdN6dA6QNFPnlANf9D+rh3n/WcenbC6sWk/7M7AnzIB3oNhszMilXKPgHc7Ewv7EMKkQ6MGLpGtvZAVof5/+r32ja2fAiDrAE4/5ABMD/z+JnymjkZwV7E2kCj+uJ64QHsC2vNjss9xhtFrbw3LXr3w0mJD1kExvZWebdFN0cA/ND2qHfA/7mEz4DRf5MANr5nO0845Lusge2AQ3qA+pk+cALtheyIaonpCWlFl0FpeGpxiz6oi4jJRP+/99w96YXbQss6VbhMvMCCIQMBvwT+/MAkvnF70PuvAA7FqIJP/Jo2gbjOv9ED/gSHwlCCzgQXiEkHLoNJvUu3tzfYgi/Kg4RB/Rk5br6Qw0jBSX02fLF/Ub/2f3e+MP+4QZe/KHzPfaqBbgQOATO8d/wwfumCosJDAhE+abnkP0dDB8PU/e672sECB6cHvYCMvmt9rv1YvVJ7kwZsidB+cblouUpD+gZm/7+4H/t7g0ZEbALzvIV9LoCRvvO/Fz+ggcqDFL3puxb8ZwGCwHb9gYAhAkjEskKdgpgB8cAwfeW7F73Dv1THOEfhvQ86Q3x/g1DD2D61+ur+TsNjQWwARr1nvlvBNn7L/3q/1kGfwaT9kPwL/kEC7EMNwUf/Fr5UACb/hL+G/4PAOoDmwGKAA3/KAHYA2sC9AGx/uf+kf8oAFn9j/pi/twCNgjZBe3/jftp+fr7vP0D/1AALALzAez+Cv6C/gYCrASPBN0Btfx++zj9Zv9L/+b+sgF6BXcG0AE6/Bf6yfsq/20AegGCAqwCxQAv/Tf8JP4HAbgByABkAKkArAAO/7j9W/53AHoCPgLvAOr/ZP80/9n+Wf97AL8BvQGVAHj/3P4y/3v/tv8RAJ0A6QA7AFv/zf78/pn/BQBrAJAAnABNAMX/fP+H/yIAjwCvAJYAUgA9AOT/rP+n/+X/UQByAFYA/v+x/43/gf+R/8b/DgAsACMA+f+x/5n/l/+5/+3/EgAuACkADwDr/9n/0//s/wsACAD//97/zP+p/wAA" type="audio/wav" />
    Your browser does not support the audio element.
</audio>

<audio  controls="controls" >
    <source src="data:audio/wav;base64,UklGRrgDAABXQVZFZm10IBAAAAABAAEA0AcAAKAPAAACABAAZGF0YZQDAADF/5j////2//H/6//o/+v/4P/a/+T/9v8CAPn/s/+A/3H/mv/a/yEAOAAVALn/Lv/3/iX/tf+AAPAAngC//77+Iv5O/n7/bQEgA8UCWwCp/cL71/si/i0DbAgmCEICQvul9Uj1wfoaBcMP6RByBmj2WuyV7tn4bAZBFlcY0wcB9Zfqsum/8O79NxQsIlUS0feV6OLoi+3F9aEIkCIWIRoDXOpO6U3wHe3R9MIMJCr2IkX9JeTJ6i/0Ke899TINjS1BJI76B+HP6r70ZO8q9dgOjzDkJGX4s94m6oL04+9V9bcQ4DOsJPz0uttI6fr0PvBZ9AgPWzQCJ0v1yNie5j31hPCm8gUMUTS4KRD3+dZF5Bvy7+/m9MkTgzk4I0PxrNVf5yntP/GS+K8lwzkhGBLkUtjw6SjpR/NxDwFm0PuV0TsgoNg5wUIYpeQvZEo1N6y3NuTMG9KhAQjdSDv2f6nI/AXsCISi0giM3tMP/3/yAxbgrx5Ar2vngvTG6dZyzibo2YgbwsfG0Hf7bN6TVhZGKNpzF1rdRsNJAPbZQUC0VcDjVRFW7XbAyP0+3Zotylwt6vYK9PgTwAD71d4iH2xh/vHrBPsBi8DY95fjDhWGYqr2OgEACFLBHPn75fkSzWDW+vkAzgn1xKL3C+luE89ekvdrALMJvsU9+IjobxmoWVjxNQWxAmPGDPo25aIhJlJE7a0Lcvm6yZH6E+N/LrlEo+c/EOPt/8+/983kHznpN9/lWBXg4ozVbPdZ5sJEdybx6ZMV8tp63rPx0+3FSZUaSu9PFLfVN+V27SD3Bk3lCov4PA6E0wvt/Ob7BOJJEACF/uQJfM9b81DjeRncQKTyJA+29l/a2fT23wYqbjS+7ZsTR+6D30PzR+StN4cjvvJRFXXlBurJ7lToej3PGPH0JRQP5Zzv7enz7jM+Swxw+i0Q2eUY92/mXvBWOV4I2/jsDp/nFvpq6pPrWjJSC0z5MQwe7Or6rvFo4/8nJBTj9ZcNE/BF+iX8PuaWBGQqEfThCPb73/RS/7/4/fb0+Jgn+/RXBZn8Y/md/F/9Jv/2/i3+4ACNEQL02wnf9lsBF/5P/nj91fWEFH35pQTKAN/7EwCF/V7+4f6eAuP8wAERAxr+2f8QAh78Lv+OAgoAiP0QBNL8swCy/3X/qgBn/4AB0v6ZAXX+/gCp/s4ArP+ZABgA7f8UAFj/UABF/4EAkf8rAAAA" type="audio/wav" />
    Your browser does not support the audio element.
</audio>

<audio  controls="controls" >
    <source src="data:audio/wav;base64,UklGRgoBAABXQVZFZm10IBAAAAABAAEA9AEAAOgDAAACABAAZGF0YeYAAAB77X4Rzu2VDJz1Bf1mCYPk4CMY0Do149DyJ8npw/+JGhrBq0VXm/JeJpwjOYzqteVjRueW234BgJ5xpKzjG/ANg65cW3iDLWEZqLUiYwLE4+soe+rtEUYT5C+r9aEwgOpgGZj32gmaDkcFpSCe/XIhjwTlHZsOvx+6ISsgLyI4G+8cCA/2GSELTAcFBjQBEwMF/j8BLARS/t0GWf09BssB2Ak2AxAE7grlBpwMowaIDHIAmgnz8jcLkPJJA+8AKPIeGGPb/yTg4g4ALRih2OklveWeBBoCaflNBs732Ahn+KIGQvoAAA==" type="audio/wav" />
    Your browser does not support the audio element.
</audio>

---
***In-class exercise: compute the spectrum of [a] in the recording.***

1. Plot the waveform, and use the waveform to determine what part of the recording has a steady-state [a].
2. Now that you've determined that, work with just that portion of the signal for computing the spectrum.
3. Try computing the spectrum for the [a] sampled at different sampling rates. How does this affect what you see in the resulting spectrum, if at all?
4. Do your computed spectra look as expected, given what you know about formants? You might try computing spectra for the other vowel samples at the webpage, too, e.g., [o], [i], etc.

---

Now let's work towards computing the spectrogram for the audio file. First let's plot the waveform of the audio file.

***Discussion: do you notice a technical problem with the recording revealed from plotting the waveform?***

```python
N = y.shape[0]

L = N / sr

print(f'Audio length: {L:.2f} seconds')

f, ax = plt.subplots()
ax.plot(np.arange(N) / sr, y)
ax.set_xlabel('Time [s]')
ax.set_ylabel('Amplitude [unknown]');
```

We can compute a spectrogram using [spectral analysis tools provided by `scipy.signal`](https://github.com/scipy/scipy/blob/master/scipy/signal/spectral.py), namely, `scipy.signal.spectrogram`.

```python
help(signal.spectrogram)
```

    Help on function spectrogram in module scipy.signal.spectral:

    spectrogram(x, fs=1.0, window=('tukey', 0.25), nperseg=None, noverlap=None, nfft=None, detrend='constant', return_onesided=True, scaling='density', axis=-1, mode='psd')
        Compute a spectrogram with consecutive Fourier transforms.

        Spectrograms can be used as a way of visualizing the change of a
        nonstationary signal's frequency content over time.

        Parameters
        ----------
        x : array_like
            Time series of measurement values
        fs : float, optional
            Sampling frequency of the `x` time series. Defaults to 1.0.
        window : str or tuple or array_like, optional
            Desired window to use. If `window` is a string or tuple, it is
            passed to `get_window` to generate the window values, which are
            DFT-even by default. See `get_window` for a list of windows and
            required parameters. If `window` is array_like it will be used
            directly as the window and its length must be nperseg.
            Defaults to a Tukey window with shape parameter of 0.25.
        nperseg : int, optional
            Length of each segment. Defaults to None, but if window is str or
            tuple, is set to 256, and if window is array_like, is set to the
            length of the window.
        noverlap : int, optional
            Number of points to overlap between segments. If `None`,
            ``noverlap = nperseg // 8``. Defaults to `None`.
        nfft : int, optional
            Length of the FFT used, if a zero padded FFT is desired. If
            `None`, the FFT length is `nperseg`. Defaults to `None`.
        detrend : str or function or `False`, optional
            Specifies how to detrend each segment. If `detrend` is a
            string, it is passed as the `type` argument to the `detrend`
            function. If it is a function, it takes a segment and returns a
            detrended segment. If `detrend` is `False`, no detrending is
            done. Defaults to 'constant'.
        return_onesided : bool, optional
            If `True`, return a one-sided spectrum for real data. If
            `False` return a two-sided spectrum. Defaults to `True`, but for
            complex data, a two-sided spectrum is always returned.
        scaling : { 'density', 'spectrum' }, optional
            Selects between computing the power spectral density ('density')
            where `Sxx` has units of V**2/Hz and computing the power
            spectrum ('spectrum') where `Sxx` has units of V**2, if `x`
            is measured in V and `fs` is measured in Hz. Defaults to
            'density'.
        axis : int, optional
            Axis along which the spectrogram is computed; the default is over
            the last axis (i.e. ``axis=-1``).
        mode : str, optional
            Defines what kind of return values are expected. Options are
            ['psd', 'complex', 'magnitude', 'angle', 'phase']. 'complex' is
            equivalent to the output of `stft` with no padding or boundary
            extension. 'magnitude' returns the absolute magnitude of the
            STFT. 'angle' and 'phase' return the complex angle of the STFT,
            with and without unwrapping, respectively.

        Returns
        -------
        f : ndarray
            Array of sample frequencies.
        t : ndarray
            Array of segment times.
        Sxx : ndarray
            Spectrogram of x. By default, the last axis of Sxx corresponds
            to the segment times.

        See Also
        --------
        periodogram: Simple, optionally modified periodogram
        lombscargle: Lomb-Scargle periodogram for unevenly sampled data
        welch: Power spectral density by Welch's method.
        csd: Cross spectral density by Welch's method.

        Notes
        -----
        An appropriate amount of overlap will depend on the choice of window
        and on your requirements. In contrast to welch's method, where the
        entire data stream is averaged over, one may wish to use a smaller
        overlap (or perhaps none at all) when computing a spectrogram, to
        maintain some statistical independence between individual segments.
        It is for this reason that the default window is a Tukey window with
        1/8th of a window's length overlap at each end.

        .. versionadded:: 0.16.0

        References
        ----------
        .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck
               "Discrete-Time Signal Processing", Prentice Hall, 1999.

        Examples
        --------
        >>> from scipy import signal
        >>> import matplotlib.pyplot as plt

        Generate a test signal, a 2 Vrms sine wave whose frequency is slowly
        modulated around 3kHz, corrupted by white noise of exponentially
        decreasing magnitude sampled at 10 kHz.

        >>> fs = 10e3
        >>> N = 1e5
        >>> amp = 2 * np.sqrt(2)
        >>> noise_power = 0.01 * fs / 2
        >>> time = np.arange(N) / float(fs)
        >>> mod = 500*np.cos(2*np.pi*0.25*time)
        >>> carrier = amp * np.sin(2*np.pi*3e3*time + mod)
        >>> noise = np.random.normal(scale=np.sqrt(noise_power), size=time.shape)
        >>> noise *= np.exp(-time/5)
        >>> x = carrier + noise

        Compute and plot the spectrogram.

        >>> f, t, Sxx = signal.spectrogram(x, fs)
        >>> plt.pcolormesh(t, f, Sxx)
        >>> plt.ylabel('Frequency [Hz]')
        >>> plt.xlabel('Time [sec]')
        >>> plt.show()

        Note, if using output that is not one sided, then use the following:

        >>> f, t, Sxx = signal.spectrogram(x, fs, return_onesided=False)
        >>> plt.pcolormesh(t, np.fft.fftshift(f), np.fft.fftshift(Sxx, axes=0))
        >>> plt.ylabel('Frequency [Hz]')
        >>> plt.xlabel('Time [sec]')
        >>> plt.show()

We can use the example code from the help file to try to compute the spectrogram for [ma].

***Discussion: Does the spectrogram look as you expect? Fiddle with the settings of the plot as suits you. Also try using the code from [Elegant SciPy](https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch04.html) used to produce figure 4-4. How does that look? Compare your computed spectrograms to the ones you get when you analyze the [ma] file in Praat.***

```python
f,t, Sxx = signal.spectrogram(y,sr)
plt.pcolormesh(t, f, Sxx)
plt.ylabel('Frequency [Hz]')
plt.xlabel('Time [sec]')
```

To better understand how scipy.signal.spectrogram works, let's look at how it is defined. We can do this [using the `inspect` module](https://stackoverflow.com/questions/427453/how-can-i-get-the-source-code-of-a-python-function#427533). We can also look at [the code on github](https://github.com/scipy/scipy/blob/master/scipy/signal/spectral.py), which is a little prettier.

```python
import inspect
lines = inspect.getsourcelines(signal.spectrogram)
print("".join(lines[0]))
```

    def spectrogram(x, fs=1.0, window=('tukey', .25), nperseg=None, noverlap=None,
                    nfft=None, detrend='constant', return_onesided=True,
                    scaling='density', axis=-1, mode='psd'):
        """
        Compute a spectrogram with consecutive Fourier transforms.

        Spectrograms can be used as a way of visualizing the change of a
        nonstationary signal's frequency content over time.

        Parameters
        ----------
        x : array_like
            Time series of measurement values
        fs : float, optional
            Sampling frequency of the `x` time series. Defaults to 1.0.
        window : str or tuple or array_like, optional
            Desired window to use. If `window` is a string or tuple, it is
            passed to `get_window` to generate the window values, which are
            DFT-even by default. See `get_window` for a list of windows and
            required parameters. If `window` is array_like it will be used
            directly as the window and its length must be nperseg.
            Defaults to a Tukey window with shape parameter of 0.25.
        nperseg : int, optional
            Length of each segment. Defaults to None, but if window is str or
            tuple, is set to 256, and if window is array_like, is set to the
            length of the window.
        noverlap : int, optional
            Number of points to overlap between segments. If `None`,
            ``noverlap = nperseg // 8``. Defaults to `None`.
        nfft : int, optional
            Length of the FFT used, if a zero padded FFT is desired. If
            `None`, the FFT length is `nperseg`. Defaults to `None`.
        detrend : str or function or `False`, optional
            Specifies how to detrend each segment. If `detrend` is a
            string, it is passed as the `type` argument to the `detrend`
            function. If it is a function, it takes a segment and returns a
            detrended segment. If `detrend` is `False`, no detrending is
            done. Defaults to 'constant'.
        return_onesided : bool, optional
            If `True`, return a one-sided spectrum for real data. If
            `False` return a two-sided spectrum. Defaults to `True`, but for
            complex data, a two-sided spectrum is always returned.
        scaling : { 'density', 'spectrum' }, optional
            Selects between computing the power spectral density ('density')
            where `Sxx` has units of V**2/Hz and computing the power
            spectrum ('spectrum') where `Sxx` has units of V**2, if `x`
            is measured in V and `fs` is measured in Hz. Defaults to
            'density'.
        axis : int, optional
            Axis along which the spectrogram is computed; the default is over
            the last axis (i.e. ``axis=-1``).
        mode : str, optional
            Defines what kind of return values are expected. Options are
            ['psd', 'complex', 'magnitude', 'angle', 'phase']. 'complex' is
            equivalent to the output of `stft` with no padding or boundary
            extension. 'magnitude' returns the absolute magnitude of the
            STFT. 'angle' and 'phase' return the complex angle of the STFT,
            with and without unwrapping, respectively.

        Returns
        -------
        f : ndarray
            Array of sample frequencies.
        t : ndarray
            Array of segment times.
        Sxx : ndarray
            Spectrogram of x. By default, the last axis of Sxx corresponds
            to the segment times.

        See Also
        --------
        periodogram: Simple, optionally modified periodogram
        lombscargle: Lomb-Scargle periodogram for unevenly sampled data
        welch: Power spectral density by Welch's method.
        csd: Cross spectral density by Welch's method.

        Notes
        -----
        An appropriate amount of overlap will depend on the choice of window
        and on your requirements. In contrast to welch's method, where the
        entire data stream is averaged over, one may wish to use a smaller
        overlap (or perhaps none at all) when computing a spectrogram, to
        maintain some statistical independence between individual segments.
        It is for this reason that the default window is a Tukey window with
        1/8th of a window's length overlap at each end.

        .. versionadded:: 0.16.0

        References
        ----------
        .. [1] Oppenheim, Alan V., Ronald W. Schafer, John R. Buck
               "Discrete-Time Signal Processing", Prentice Hall, 1999.

        Examples
        --------
        >>> from scipy import signal
        >>> from scipy.fft import fftshift
        >>> import matplotlib.pyplot as plt

        Generate a test signal, a 2 Vrms sine wave whose frequency is slowly
        modulated around 3kHz, corrupted by white noise of exponentially
        decreasing magnitude sampled at 10 kHz.

        >>> fs = 10e3
        >>> N = 1e5
        >>> amp = 2 * np.sqrt(2)
        >>> noise_power = 0.01 * fs / 2
        >>> time = np.arange(N) / float(fs)
        >>> mod = 500*np.cos(2*np.pi*0.25*time)
        >>> carrier = amp * np.sin(2*np.pi*3e3*time + mod)
        >>> noise = np.random.normal(scale=np.sqrt(noise_power), size=time.shape)
        >>> noise *= np.exp(-time/5)
        >>> x = carrier + noise

        Compute and plot the spectrogram.

        >>> f, t, Sxx = signal.spectrogram(x, fs)
        >>> plt.pcolormesh(t, f, Sxx, shading='gouraud')
        >>> plt.ylabel('Frequency [Hz]')
        >>> plt.xlabel('Time [sec]')
        >>> plt.show()

        Note, if using output that is not one sided, then use the following:

        >>> f, t, Sxx = signal.spectrogram(x, fs, return_onesided=False)
        >>> plt.pcolormesh(t, fftshift(f), fftshift(Sxx, axes=0), shading='gouraud')
        >>> plt.ylabel('Frequency [Hz]')
        >>> plt.xlabel('Time [sec]')
        >>> plt.show()
        """
        modelist = ['psd', 'complex', 'magnitude', 'angle', 'phase']
        if mode not in modelist:
            raise ValueError('unknown value for mode {}, must be one of {}'
                             .format(mode, modelist))

        # need to set default for nperseg before setting default for noverlap below
        window, nperseg = _triage_segments(window, nperseg,
                                           input_length=x.shape[axis])

        # Less overlap than welch, so samples are more statisically independent
        if noverlap is None:
            noverlap = nperseg // 8

        if mode == 'psd':
            freqs, time, Sxx = _spectral_helper(x, x, fs, window, nperseg,
                                                noverlap, nfft, detrend,
                                                return_onesided, scaling, axis,
                                                mode='psd')

        else:
            freqs, time, Sxx = _spectral_helper(x, x, fs, window, nperseg,
                                                noverlap, nfft, detrend,
                                                return_onesided, scaling, axis,
                                                mode='stft')

            if mode == 'magnitude':
                Sxx = np.abs(Sxx)
            elif mode in ['angle', 'phase']:
                Sxx = np.angle(Sxx)
                if mode == 'phase':
                    # Sxx has one additional dimension for time strides
                    if axis < 0:
                        axis -= 1
                    Sxx = np.unwrap(Sxx, axis=axis)

            # mode =='complex' is same as `stft`, doesn't need modification

        return freqs, time, Sxx

Well that's not that helpful. Looks like we need to take a look at `signal._spectral_helper`, which we can look at in the github code.

```python
# This doesn't work!
#lines = inspect.getsourcelines(signal.spectral_helper)
#print("".join(lines[0]))
```

Now let's use the code from [Elegant Scipy](https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch04.html) used to produce Figure 4.3, which breaks down spectrogram computation step by step.

```python
from skimage import util # See installation here: https://anaconda.org/anaconda/scikit-image

M = 1024

slices = util.view_as_windows(y, window_shape=(M,), step=100)
print(f'Audio shape: {y.shape}, Sliced audio shape: {slices.shape}')

win = np.hanning(M + 1)[:-1]
slices = slices * win

slices = slices.T
print('Shape of `slices`:', slices.shape)

spectrum = np.fft.fft(slices, axis=0)[:M // 2 + 1:-1]
spectrum = np.abs(spectrum)
```

    Audio shape: (5040,), Sliced audio shape: (41, 1024)
    Shape of `slices`: (1024, 41)

```python
f, ax = plt.subplots(figsize=(4.8, 2.4))

S = np.abs(spectrum)
S = 20 * np.log10(S / np.max(S))

ax.imshow(S, origin='lower', cmap='viridis',
          extent=(0, L, 0, sr / 2 / 1000))
ax.axis('tight')
ax.set_ylabel('Frequency [kHz]')
ax.set_xlabel('Time [s]');
```

## Working through DFT, FFT

Finally, let's work through the [Elegant Scipy](https://www.safaribooksonline.com/library/view/elegant-scipy/9781491922927/ch04.html) material on DFT and FFT together. This is the section that starts after Figure 4.4.


```python
from datascience import *
import matplotlib.pyplot as plt
%matplotlib inline
import numpy as np
from utils import *
plt.style.use('seaborn-muted')
import sympy
```

# Elasticity

Suppose you own a business but you find yourself struggling to make a profit lately. How would you resolve this? You might consider raising your prices. But surely if you were to raise your prices too much you might lose some of your customers. To relate to our discussions above regarding surplus, maybe some of your customers are already at their highest willingness to pay, and raising prices would push them away from your business. Depending on the situation, you might lose so many customers that the increased revenue from raising your prices are completely offset by a decline in customers. This is a problem. To answer the question of how much to raise prices without losing too many customers and therefore losing revenue, we would like a way to measure this concept of customers' responsiveness to prices. We call this concept *elasticity*.

```{admonition} Definition
**Elasticity** is a measure of how much the quantity of something changes in response to a price change. Typically, it is a measure of the percent change in the quantity demanded of a good in response to a percent change in its price.
```

How would we measure elasticity? In the above context, we want to see how much quantity demanded of your business's goods or services changes in response to a change in price. It seems like a good place to start would be to define elasticity as the percent change in quantity over percent change in price. Let's use the demand curve depicted below as an example.

```python
def plot_demand1():
    p = sympy.Symbol("p")
    demand_equation = 10 - p
    prices = [x for x in range(0,11)]
    demand_Q = [demand_equation.subs(p,x) for x in prices]
    plt.figure(figsize = [9,6])
    plt.plot(prices,demand_Q)
    plt.xlabel("Demand Quantity")
    plt.ylabel("Price")
    plt.title("Demand Curve")

plot_demand1()
plt.show()
```

Now let's say our current price is 5, and we would like to increase the price to 6. We can see from the curve that this causes demand to drop from 5 to 4.

```python
plot_demand1()
plt.plot(np.full(6,5), np.arange(0,6,1), 'r--')
plt.plot(np.full(7,4), np.arange(0,7,1), 'r--')
plt.plot(np.arange(0,6,1), np.full(6,5), 'r--')
plt.plot(np.arange(0,5,1), np.full(5,6), 'r--')
plt.plot(5,5,'ro')
plt.text(5.1,5.1,"(5,5)")
plt.plot(4,6,'ro')
plt.text(4.1,6.1,"(4,6)")
plt.arrow(4.9,5.1,-0.01,0.01,head_width=0.2)
plt.arrow(4.6,5.4,-0.01,0.01,head_width=0.2)
plt.arrow(4.3,5.7,-0.01,0.01,head_width=0.2)
plt.show()
```

Using our definition from above, we can calculate the elasticity.

$$
\begin{align}
\frac{\%\Delta Q}{\%\Delta P} &= \frac{\frac{4-5}{5}\times100\%}{\frac{6-5}{5}\times100\%} \\
&= \frac{-20\%}{20\%} \\
&= 1
\end{align}
$$

*Note that we take the absolute value for elasticity.*

This tells us that in this specific example, the percent change in price is met with an equal percent change in quantity. But what happens if we start at a price of 6 and go down to a price of 5? This movement is just the opposite of what we did above, so is the elasticity also 1? Let's see.

```python
plot_demand1()
plt.plot(np.full(6,5), np.arange(0,6,1), 'r--')
plt.plot(np.full(7,4), np.arange(0,7,1), 'r--')
plt.plot(np.arange(0,6,1), np.full(6,5), 'r--')
plt.plot(np.arange(0,5,1), np.full(5,6), 'r--')
plt.plot(5,5,'ro')
plt.text(5.1,5.1,"(5,5)")
plt.plot(4,6,'ro')
plt.text(4.1,6.1,"(4,6)")
plt.arrow(4.7,5.3,0.01,-0.01,head_width=0.2)
plt.arrow(4.4,5.6,0.01,-0.01,head_width=0.2)
plt.arrow(4.1,5.9,0.01,-0.01,head_width=0.2)
plt.show()
```

$$
\begin{align}
\frac{\%\Delta Q}{\%\Delta P} &= \frac{\frac{5-4}{4}\times100\%}{\frac{5-6}{6}\times100\%} \\
&= \frac{25\%}{-16.67\%} \\
&\approx 1.5
\end{align}
$$

We get a different number! This is of course a property of percentages. In both scenarios we moved price by 1, but we started at different prices, and increasing price by 1 starting from 5 is different percentage-wise from starting at 6 and decreasing by 1.

If we want to find a unique value for elasticity between the price points of 5 and 6, independent of price movement, we can use something called the **mid-point method**. It looks like this:

$$\frac{\frac{Q_2 - Q_1}{(Q_2 + Q_1)\times0.5}\times100\%}{\frac{P_2 - P_1}{(P_2 + P_1)\times0.5}\times100\%}$$

What this does is it essentially finds the elasticity of the point between the two given points.

It is also useful, however, to think of elasticity as a property of a single point. For example, we might want the price point of 5 in the example above to have an elasticity of 1 irrespective of what other point we are going to or coming from. For this, we can use the **point-slope formula**.

$$\frac{\Delta Q}{\Delta P}\frac{P}{Q}$$

or

$$\frac{dQ}{dP}\Bigr|_{P,Q}\times\frac{P}{Q}$$

where the left fraction is the inverse slope at that particular point.

It turns out that in our example above, we can think of the very first formula for elasticity (percent change in quantity over percent change in price) as the point-slope elasticity of the starting point. To verify this, find the elasticity from a price of 5 to 6, from a price of 5 to 4, and at the price of 5 using the point-slope formula. You should get the same answer all three times. This does not hold in general for all curves, but using the base formula that was first introduced should be fine for our examples and other situations.

## Other Elasticities

We can measure elasticity in different contexts as well. Naturally, if we have an elasticity of demand, it follows that we should have an elasticity of supply. Just like consumers make adjustments to their quantity demanded based on changes in the market price, producers respond to market prices as well. Elasticity of supply can be calculated similarly to elasticity of demand.

Imagine that there are two fast food locations next to each other on campus competing for students' business. Let's call them Railway and Bear Express. Initially, their food is priced such that they get equal amounts of business from the students, and students on average don't prefer one over the other; they just want a good and affordable place to eat. Now, imagine that Bear Express raises the prices of their food, while Railway holds their prices steady. In this scenario, one can imagine that students would begin eating more frequently at Railway as opposed to Bear Express due to it being relatively cheaper out of the two options. Now, if Railway in turn increases their prices, the flow of students would shift back to Bear Express by a certain amount. These two restaurants are said to offer goods that are *substitutes* to each other. That is, a consumer will want one or the other, but usually not both. Furthermore, it would seem that just like the price of a good affects its quantity demanded, for substitutes the price of one good can affect the quantity demanded of the other. This is called *cross-price elasticity of demand* (as opposed to *own-price elasticity* that we just learned earlier). It can be defined as follows:

$$\frac{\%\Delta Q_{Good A}}{\%\Delta P_{Good B}}$$

Notice that here there is a notion of positive and negative elasticity. Substitutes will have positive cross-price elasticity, because we expect that an increase in the price of one good will lead to an increase in the quantity demanded of its substitute. But what about when cross-price elasticity is negative? In other words, are there goods where an increase in the price of one leads to a decrease in the quantity demanded of the other? Of course; these are called *complements*. Examples of complementary goods could be cars and gasoline, hot dogs and hot dog buns, or peanut butter and jelly.

There is one more type of elasticity that we will briefly discuss, and that is *income elasticity of demand*. For most goods, the more disposable income consumers have the more they will demand these goods. Almost all goods follow this pattern, and thus there are countless examples. These goods are called *normal goods*. More interestingly, there are some goods, called *inferior goods*, for which we see quantity demanded decrease as income increases. An example of this might be frozen, microwaveable food. As consumers grow wealthier and their palates grow more discerning, their consumption of frozen food might decrease in place of more cooked meals. To calculate this elasticity we would use the following:

$$\frac{\%\Delta Q}{\%\Delta Income}$$

## Elasticity and Revenue

Let's take a closer look at own-price elasticity of demand. Below we've defined a function using our first definition. Let's see what happens as we increase price. Note that is this percent change in quantity over % change in price. While this is negative quantity usually, we still denote it as a positive number.

```python
def elasticity(P1,P2,Q1,Q2):
    return ((Q2-Q1)/Q1)/((P2-P1)/P1)

p = sympy.Symbol("p")
demand_equation = 10-p
prices = [i for i in range(1,10)]

print("Price: Elasticity")
print()

for x in prices:
    print(str(x)+":", abs(elasticity(x,x+1,demand_equation.subs(p,x),demand_equation.subs(p,x+1))))
```

    Price: Elasticity

    1: 0.111111111111111
    2: 0.250000000000000
    3: 0.428571428571429
    4: 0.666666666666667
    5: 1.00000000000000
    6: 1.50000000000000
    7: 2.33333333333333
    8: 4.00000000000000
    9: 9.00000000000000

Notice how a price increase will increase revenue until a certain point, at which point revenue decreases again. Intuitively this makes sense. A firm can only increase its prices so much until it starts losing too many of its customers for the increase to be worthwhile. Let's try and find when this happens in general. First let's plot revenue as a function of price.

```python
elast = [abs(elasticity(x,x+1,demand_equation.subs(p,x),demand_equation.subs(p,x+1))) for x in range(1,10)]
revenue = [demand_equation.subs(p,x)*x for x in range(1,10)]
prices = [x for x in range(1,10)]
plt.figure(figsize = [9,6])
plt.plot(prices,revenue)
plt.xlabel("Price")
plt.ylabel("Revenue")
plt.title("Revenue vs Price for Given Demand Curve")
plt.show()
```

We notice that revenue reaches a maximun at a price of 5. What elasticity does this price point have?

```python
plt.figure(figsize = [9,6])
plt.plot(prices,elast)
plt.plot(np.arange(1,6,1), np.full(5,1), 'r--')
plt.plot(np.full(2,5), np.arange(0,2,1), 'r--')
plt.plot(5,1,'ro')
plt.text(5,1.5,"(5,1)")
plt.xlabel("Price")
plt.ylabel("Elasticity")
plt.title("Elasticity vs Price for Given Demand Curve")
plt.show()
```

Revenue is maximized when elasticity is 1! To further reinforce this, let's plot revenue vs elasticity.

```python
plt.figure(figsize = [9,6])
plt.plot(elast,revenue)
plt.plot(np.full(17,1), np.arange(9,26,1), 'r--')
plt.plot(1,25,'ro')
plt.text(1.3,25,"(1,25)")
plt.xlabel("Elasticity")
plt.ylabel("Revenue")
plt.title("Revenue vs Elasticity for Given Demand Curve")
plt.show()
```

Before we continue to the final section let's make sure we have a solid intuitive grasp of the significance of our findings. Near low price points, a firm deciding how to raise prices will tend to face inelastic demand. You can imagine that if there were some market where let's say laptops cost only a few dollars, doubling the price of laptops would proabalby not decrease demand for laptops at all, since the price of laptops is still very low. Here, demand is inelastic, or in other words, it is less than 1, because a unit change in price is not met by the same change in quantity. Alternatively, if laptops were to cost as much as a house, and then the price for laptops doubled, the few people who were still in the market for laptops would probably be very discouraged from continuing to buy laptops. It wouldn't be unreasonable to assume that nearly all of the demand would be lost. Here, demand is elastic, or greater than 1, because a unit change in price is met by an even greater change in quantity. The point of the above exercise is to illustrate that there is some middle ground between these two extremes, where a firm with a good pulse on market demand should be pricing its products.

It's important to remember that while we have so far been discussing elasticity from a demand perspective, supply also has the property of elasticity.

## Relative Elasticity

Suppose that you are hiking in the mountains when a venomous snake bites you. Fortunately there is a hospital nearby. Unfortunately, it seems that their antivenom is very expensive. If the antivenom costs \$1000, would you pay? What if the price doubled to \$2000? What is your max willingness to pay for this antivenom? In the moment, you would likely be willing to pay anything and everything for the antivenom, as any money you refuse to pay won't be of much use to you if you're dead.

Contrast this with your demand for pizza. Imagine a pizza chain close to where you live doubles their prices from \$3 a slice to \$6 a slice. Most people would be disuaded from buying pizza by this price increase, assuming that there are many alternatives to pizza close to where you live. Since pizza is not a very essential good, and there are plenty of other places to eat close to where you live, most wouldn't see the point in getting pizza anymore.

We can easily see that any change in price is unlikely to change the demand for antivenom in our example, and thus we say the demand for antivenom is inelastic. But this is a different kind of elasticity. We are not measuring the marginal change in quantity demanded at a given price point or interval, we are saying that in general, on the demand curve for antivenom the elasticity at any price is more inelastic than some other typical demand curve. Similarly, in our example we can say that the demand for pizza is relatively elastic, as the pizza chain cannot change their prices too much without significantly altering the quantity demanded for their pizza. The keyword in this concept is *relative*. It doesn't really make much sense to say that the demand for antivenom is inelastic without there being at least one other kind of demand that is comparatively elastic.

More specifically, our example of antivenom likely exhibits *perfect inelasticity*, meaning that demand for antivenom is the same at absolutely any price; the hospital can charge any price it wants for antivenom. Contrast this with our nearby pizza chain. If we make some further assumptions and say that the market for food around your house or apartment is perfectly competitive, such that any marginal increase in price of one food chain will cause them to lose all of their customers to other food chains, then the demand for the specific food of that chain is *perfectly elastic*. The chain cannot increase its prices or else it will lose all demand. These two extreme scenarios are depicted below.

```python
plt.figure(figsize = [9,6])
plt.plot(np.full(20,1), np.arange(0,20,1))
plt.xlabel("Quantity Demanded")
plt.ylabel("Price")
plt.title("Perfectly Inelastic Demand Curve")
plt.xticks([], [])
plt.yticks([], [])
plt.show()
```

```python
plt.figure(figsize = [9,6])
plt.plot(np.arange(0,20,1), np.full(20,1))
plt.xlabel("Quantity Demanded")
plt.ylabel("Price")
plt.title("Perfectly Elastic Demand Curve")
plt.xticks([], [])
plt.yticks([], [])
plt.show()
```


```python
from pycalphad import Model, Database
from pycalphad.core.autograd_utils import build_functions as bf_interpreted
from pycalphad.core.custom_ufuncify import ufuncify
from pycalphad.core.sympydiff_utils import build_functions as bf_compiled
import pycalphad.variables as v
from sympy.printing.ccode import CCodePrinter
import numpy as np

dbf = Database('2016-04-09-AlNi.tdb')
mod = Model(dbf, ['AL', 'NI', 'VA'], 'FCC_L12')
```

```python
%lprun -f CCodePrinter._print_Piecewise obj, grad, hess = bf_compiled(mod.ast, tuple([v.T] + mod.site_fractions))
temps = np.linspace(300., 2000., 1000)
```

```python
%time res1 = hess(300, 0.4, 0.6, 0.4, 0.6, 0.4, 0.6, 0.4, 0.6, 1)
```

    CPU times: user 0 ns, sys: 0 ns, total: 0 ns
    Wall time: 220 µs

```python
%time ag_obj, ag_grad, ag_hess = bf_interpreted(mod.ast, [v.T] + mod.site_fractions)
```

    CPU times: user 303 ms, sys: 2 ms, total: 305 ms
    Wall time: 305 ms

```python
%time res2 = ag_hess(300, 0.4, 0.6, 0.4, 0.6, 0.4, 0.6, 0.4, 0.6, 1)
```

    CPU times: user 554 ms, sys: 1 ms, total: 555 ms
    Wall time: 554 ms

```python
np.testing.assert_allclose(np.squeeze(res1), np.squeeze(res2))
```

```python

```


# Quantum pipeline using JAX backend

```python
from jax import numpy as np

def read_data(filename):
    labels, sentences = [], []
    with open(filename) as f:
        for line in f:
            labels.append([1, 0] if line[0] == '1' else [0, 1])
            sentences.append(line[1:].strip())
    return np.array(labels), sentences

train_labels, train_data = read_data('datasets/mc_train_data.txt')
dev_labels, dev_data = read_data('datasets/mc_dev_data.txt')
test_labels, test_data = read_data('datasets/mc_test_data.txt')
```

### Create diagrams

```python
from lambeq.ccg2discocat import DepCCGParser

reader = DepCCGParser(possible_root_cats=['S[dcl]'])

train_diagrams = reader.sentences2diagrams(train_data)
dev_diagrams = reader.sentences2diagrams(dev_data)
test_diagrams = reader.sentences2diagrams(test_data)
```

### Create circuits

```python
from lambeq.circuit import IQPAnsatz
from lambeq.core.types import AtomicType

ansatz = IQPAnsatz({AtomicType.NOUN: 1, AtomicType.SENTENCE: 1},
                   n_layers=1, n_single_qubit_params=3)

train_circuits = [ansatz(diagram) for diagram in train_diagrams]
dev_circuits =  [ansatz(diagram) for diagram in dev_diagrams]
test_circuits = [ansatz(diagram) for diagram in test_diagrams]

train_circuits[0].draw(figsize=(8, 8))
```

### Parameterise

```python
from sympy import default_sort_key

all_circuits = train_circuits + dev_circuits + test_circuits

# sort the symbols since they are returned as a set
parameters = sorted(
    {s for circ in all_circuits for s in circ.free_symbols},
    key=default_sort_key)
```

```python
from discopy.tensor import Tensor
from jax import jit
from jax import numpy as np

def normalise(predictions):
    # apply smoothing to predictions
    predictions = np.abs(predictions + 1e-9)
    return predictions / predictions.sum()

def make_pred_fn(circuits):
    def predict(params):
        return np.array(
            [normalise(np.abs(c.lambdify(*parameters)(*params).eval().array))
             for c in circuits])
    return predict

Tensor.np = np
train_pred_fn = jit(make_pred_fn(train_circuits))
dev_pred_fn = jit(make_pred_fn(dev_circuits))
test_pred_fn = make_pred_fn(test_circuits)
```

### Train

```python
from noisyopt import minimizeSPSA
import numpy

def make_cost_fn(pred_fn, labels):
    def cost_fn(params, **kwargs):
        predictions = pred_fn(params)

        cost = -np.sum(labels * np.log2(predictions)) / len(labels)  # binary cross-entropy loss
        costs.append(cost)

        acc = np.sum(np.round(predictions) == labels) / len(labels) / 2  # half due to double-counting
        accuracies.append(acc)

        return cost

    costs, accuracies = [], []
    return cost_fn, costs, accuracies

numpy.random.seed(0)
rng = numpy.random.default_rng(0)
x0 = np.array(rng.random(len(parameters)))

train_cost_fn, train_costs, train_accs = make_cost_fn(train_pred_fn, train_labels)
dev_cost_fn, dev_costs, dev_accs = make_cost_fn(dev_pred_fn, dev_labels)

result = minimizeSPSA(train_cost_fn, x0=x0, a=0.13, c=0.1, niter=400, callback=dev_cost_fn)
```

### Show results

```python
import matplotlib.pyplot as plt
plt.rcParams['figure.figsize'] = (10, 6)

fig, ((ax_tl, ax_tr), (ax_bl, ax_br)) = plt.subplots(2, 2, sharex=True, sharey='row')
ax_tl.set_title('Training set')
ax_tr.set_title('Development set')
ax_bl.set_xlabel('Iterations')
ax_br.set_xlabel('Iterations')
ax_bl.set_ylabel('Accuracy')
ax_tl.set_ylabel('Loss')

colours = iter(plt.rcParams['axes.prop_cycle'].by_key()['color'])
ax_tl.plot(train_costs[1::2], color=next(colours))  # training evaluates twice per iteration
ax_bl.plot(train_accs[1::2], color=next(colours))   # so take every other entry
ax_tr.plot(dev_costs, color=next(colours))
ax_br.plot(dev_accs, color=next(colours))

# print test accuracy
test_cost_fn, _, test_accs = make_cost_fn(test_pred_fn, test_labels)
test_cost_fn(result.x)
print('Test accuracy:', test_accs[0])
```


```python
%matplotlib inline
%config InlineBackend.figure_format ='retina'

import torch
import socialforce

_ = torch.manual_seed(42)
```

# Diamond

## Parametric

This is an extension of the {ref}`pedped-2d` example to study the robustness
of the inference process to potentials with gradients that change orientation.
We use a modified $V(b)$ potential that could be described as a "diamond"
of height $V_0$ and with a half-width of $\sigma$:
\begin{align}
    V(b) &= V_0 \max\left(0, 1 - \frac{|x_1| + |x_2|}{2\sigma} \right)
\end{align}
with its two parameters $V_0$ and $\sigma$.

```python
socialforce.FieldOfView.out_of_view_factor = 0.0

V = socialforce.potentials.PedPedPotentialDiamond(sigma=0.5)
with socialforce.show.canvas(figsize=(12, 6), ncols=2) as (ax1, ax2):
    socialforce.show.potential_2d(V, ax1)
    socialforce.show.potential_2d_grad(V, ax2)
```

## Scenarios

Now we use the above parametric potential in a simulation to generate synthetic
scenarios according to this potential.
We generate {ref}`Circle and ParallelOvertake scenarios <scenarios>`.

```python
circle = socialforce.scenarios.Circle(ped_ped=V)
parallel_overtake = socialforce.scenarios.ParallelOvertake(ped_ped=V)
parallel_avoidance = socialforce.scenarios.ParallelAvoidance(ped_ped=V)
scenarios = circle.generate(50) + parallel_overtake.generate(50) + parallel_avoidance.generate(150)
true_experience = socialforce.Trainer.scenes_to_experience(scenarios)
```

All the scenes from a fixed observer:

```python
# HIDE CODE
with socialforce.show.track_canvas() as ax:
    socialforce.show.states(ax, scenarios[-1], zorder=10)
    for scene in scenarios[:-1]:
        socialforce.show.states(ax, scene, alpha=0.1)
```

Parallel Overtake and Avoidance scenes from the perspective of the primary pedestrian:

```python
# HIDE CODE
with socialforce.show.track_canvas() as ax:
    socialforce.show.experience(ax, true_experience)
```

From the perspective of the other pedestrian:

```python
# HIDE CELL
with socialforce.show.track_canvas() as ax:
    socialforce.show.experience(ax, true_experience, reference_ped=1)
```

## MLP Inference

We construct an coordinate-based MLP with Fourier
Features {cite}`tancik2020fourier,rahimi2007random`.

```python
V = socialforce.potentials.PedPedPotentialMLP2D(hidden_units=32, n_hidden_layers=3)
with socialforce.show.canvas(figsize=(12, 6), ncols=2) as (ax1, ax2):
    socialforce.show.potential_2d(V, ax1)
    socialforce.show.potential_2d_grad(V, ax2)
```

```python
simulator = socialforce.Simulator(ped_ped=V)
opt = torch.optim.SGD(V.parameters(), lr=0.03, momentum=0.9, nesterov=True)
# socialforce.Trainer(simulator, opt).loop(10, true_experience)  # does not work
```

```python
with socialforce.show.canvas(figsize=(12, 6), ncols=2) as (ax1, ax2):
    socialforce.show.potential_2d(V, ax1)
    socialforce.show.potential_2d_grad(V, ax2)
```

## Fourier Features

We construct an coordinate-based MLP with Fourier
Features {cite}`tancik2020fourier,rahimi2007random`.
This coordinate-based MLP with the Fourier Feature operator FF is:
\begin{align}
    \textrm{MLP}(b, d_{\perp}, d_{\parallel}) &= \textrm{Softplus} \;\; L_{1\times64} \;\; \big[ \textrm{Softplus} \;\; L_{64\times64} \big]^3 \;\;  \textrm{Softplus} \;\; L_{64\times192} \;\; \textrm{FF}_{192 \times 3} \;\; (b, d_{\perp}, d_{\parallel})
\end{align}

```python
V = socialforce.potentials.PedPedPotentialMLP2D(
    hidden_units=64, n_hidden_layers=2, n_fourier_features=64, tanh_range=3.0, fourier_scale=3.0)
with socialforce.show.canvas(figsize=(12, 6), ncols=2) as (ax1, ax2):
    socialforce.show.potential_2d(V, ax1)
    socialforce.show.potential_2d_grad(V, ax2)
```

```python
# HIDE OUTPUT
simulator = socialforce.Simulator(ped_ped=V)
opt = torch.optim.SGD(V.parameters(), lr=0.03, momentum=0.9, nesterov=True)
socialforce.Trainer(simulator, opt).loop(10, true_experience)
```

```python
with socialforce.show.canvas(figsize=(12, 6), ncols=2) as (ax1, ax2):
    socialforce.show.potential_2d(V, ax1)
    socialforce.show.potential_2d_grad(V, ax2)
```

## Asymmetric

We can redo the above steps with an asymmetric version of the diamond potential.
This refines the previous model by a short training on the scenarios
generated with the modified potential.

```python
V_asym = socialforce.potentials.PedPedPotentialDiamond(sigma=0.5, asymmetry_angle=-20.0)
with socialforce.show.canvas(figsize=(12, 6), ncols=2) as (ax1, ax2):
    socialforce.show.potential_2d(V_asym, ax1)
    socialforce.show.potential_2d_grad(V_asym, ax2)
```

```python
circle_asym = socialforce.scenarios.Circle(ped_ped=V_asym)
parallel_overtake_asym = socialforce.scenarios.ParallelOvertake(ped_ped=V_asym, b_center=0.25)
parallel_avoidance_asym = socialforce.scenarios.ParallelAvoidance(ped_ped=V_asym, b_center=-0.25)
scenarios_asym = circle_asym.generate(50) + parallel_overtake_asym.generate(50) + parallel_avoidance_asym.generate(150)
true_experience_asym = socialforce.Trainer.scenes_to_experience(scenarios_asym)
```

From the perspective of the primary pedestrian:

```python
# HIDE CODE
with socialforce.show.track_canvas() as ax:
    socialforce.show.experience(ax, true_experience_asym)
```

```python
# HIDE OUTPUT
socialforce.Trainer(simulator, opt).loop(10, true_experience_asym)
```

```python
with socialforce.show.canvas(figsize=(12, 6), ncols=2) as (ax1, ax2):
    socialforce.show.potential_2d(V, ax1)
    socialforce.show.potential_2d_grad(V, ax2)
```

```python

```


## Modifying initial and boundary conditions

In this file we demonstrate how to change
* Initial conditions
* Boundary conditions

### Initial and boundary conditions

Two porperties every well posed pde requires are initial and boundary conditions.
Let us consider a coupled heat diffusion equation
\begin{equation}
C_E\partial_t T_E = k_E\partial^2_x T_E + G(T_L-T_E)
\end{equation}
\begin{equation}
C_L\partial_t T_L = k_L\partial^2_x T_L + G(T_E-T_L)
\end{equation}
Initial conditions are of the form
\begin{align}
    T_E(x,t=0) &= f_E(x) \\ \nonumber
    T_L(x,t=0) &= f_L(x)
\end{align}
Boundary conditions can be of two different types, either \textit{Dirichlet} or \textit{Neumann} type.
That is fixing either the value of or the flux on the left/ right end of the material under consideration.

Dirichlet boundary conditions are of the form
\begin{align}
    T_E(x_0,t) &= d_E(t) \qquad\qquad &T_E(x=L,t) &= \tilde{d}_E(t) \\ \nonumber
    T_L(x,t=0) &= d_L(t) \qquad\qquad &T_L(x=L,t) &= \tilde{d}_L(t)
\end{align}
Neumann boundary conditions are of the form
\begin{align}
    \partial_x T_E(x_0,t) &= n_E(t) \qquad\qquad &\partial_x T_E(x=L,t) &= \tilde{n}_E(t) \\ \nonumber
    \partial_x T_L(x,t=0) &= n_L(t) \qquad\qquad &\partial_x T_L(x=L,t) &= \tilde{n}_L(t)
\end{align}

```python
from NTMpy import NTMpy as ntm
from matplotlib import pyplot as plt
import numpy as np
```

```python
# We define all the material parameters for a 2TM simulation
L  = 1e-5      # Length of the Material
Ce = 2e+4      # Specific Heat Electrons
Cl = 2.5e6     # Specific Heat Lattice
ke = 3.2e+1    # Conductivity Electrons
kl = 2.75      # Conductivity Lattica
G  = 3e+16     # Exchange constant
```

```python
# If no specific source is indicated the fluence will be 0
s = ntm.source()
#Create a simulation object with two systems.
sim = ntm.simulation(2,s)
```

In order to modify the initial and the boundary conditions of the equation use the command

`.changeInit(system,function)` where
* `system` is a string or a number: "electron" or "Electron" or 1, "lattice" or "Lattice" or 2 and "spin" or "Spin" or 3
* function is a function $f_E(x)$, $f_L(x)$ or a number.

`.changeBC_Type(system,side,type)` where
* `system` is a string or a number: "electron" or "Electron" or 1, "lattice" or "Lattice" or 2 and "spin" or "Spin" or 3
* `side` is a string: "left" $x = 0$ or "right" $x = L$
* `type` is a string: "dirichlet" or "neumann"

`.changeBC_Value(system,side,function)` where
* `system` is a string or a number: "electron" or "Electron" or 1, "lattice" or "Lattice" or 2 and "spin" or "Spin" or 3
* `side` is a string: "left" $x = 0$ or "right" $x = L$
* `function` is a function (see top).

```python
# Change initial and boundary conditions
system1 = 1
system2 = 2
sim.changeInit(system1,lambda x: 600 + 300*np.cos(2*np.pi*x/L))
sim.changeInit(system2,300)
# Define Boundary Conditions
sim.changeBC_Type(system1,"left","neumann")
sim.changeBC_Type(system1,"right","neumann")
sim.changeBC_Type(system2,"left","neumann")
sim.changeBC_Type(system2,"right","neumann")
# Set a value on boundaries
sim.changeBC_Value(system1,"left",0)
sim.changeBC_Value(system1,"right",0)
sim.changeBC_Value(system2,"left",0)
sim.changeBC_Value(system2,"right",0)
# Modify the timestep to see fast dynamics
sim.final_time = 2e-9
sim.time_step  = 1.5e-13
n_index = 1 #no refractive index needed, since no source is applied.
sim.addLayer(L, n_index, [ke, kl], [Ce, Cl], 1, [G])
# Run the simulation
[x, t, Temp_map] = sim.run()
```

    -----------------------------------------------------------
    The maunually chosen time step of 1.50e-13 is very small and will eventually cause a long simulation time.
    We suggest a timestep of 1.24e-12 s
    -----------------------------------------------------------
    -----------------------------------------------------------
    No source is applied.
    source.fluence = 0
    -----------------------------------------------------------

    100%|██████████████████████████████████████████████████████████████████████████| 13333/13333 [00:02<00:00, 4540.59it/s]

    -----------------------------------------------------------
    Heat diffusion in a coupled electron-lattice system has been simulated
    Eleapsed time in E.E.- loop: 2.9520208835601807
    -----------------------------------------------------------

```python
# Plot with the fast time dynamic
print("times of the first plot:\t" + str(t[0:4]))
plt.figure()
plt.title("Fast relaxation dynamics of the first time steps")
plt.xlabel("Surface (m)"); plt.ylabel("Temperature (K)")
plt.plot(x,Temp_map[0][0,:],"r",label = "$System 1$")
plt.plot(x,Temp_map[1][0,:],"k--",label = "$System 2$")
for i in range(1,4):
    plt.plot(x,Temp_map[0][i,:],"r")
    plt.plot(x,Temp_map[1][i,:],"k--")
plt.legend()
plt.grid()
```

We can see that the "neumann" boundary conditions have been correctly applied, since the value of the temperature changes but the flux stays constant at 0.

Now let us change the boundary conditions to "dirichlet type"

```python
sim         = ntm.simulation(2,s)

sim.changeInit(1,lambda x: 300 + np.sin(2*np.pi*x/L))
sim.changeInit(2,300)
# Define Boundary Conditions
sim.changeBC_Type(1,"left","dirichlet")
sim.changeBC_Type(1,"right","dirichlet")
sim.changeBC_Type(2,"left","dirichlet")
sim.changeBC_Type(2,"right","dirichlet")
# Set a value on boundaries
sim.changeBC_Value(1,"left",300)
sim.changeBC_Value(1,"right",300)
sim.changeBC_Value(2,"left",300)
sim.changeBC_Value(2,"right",300)
# Modify the timestep to see fast dynamics
sim.final_time = 2e-9
sim.time_step  = 1.5e-13
sim.addLayer(L,n_index, [ke, kl], [Ce, Cl], 1, [G])
# Run the simulation
[x, t, Temp_map] = sim.run()
```

    -----------------------------------------------------------
    The maunually chosen time step of 1.50e-13 is very small and will eventually cause a long simulation time.
    We suggest a timestep of 1.24e-12 s
    -----------------------------------------------------------
    -----------------------------------------------------------
    No source is applied.
    source.fluence = 0
    -----------------------------------------------------------

    100%|██████████████████████████████████████████████████████████████████████████| 13333/13333 [00:02<00:00, 4585.01it/s]

    -----------------------------------------------------------
    Heat diffusion in a coupled electron-lattice system has been simulated
    Eleapsed time in E.E.- loop: 2.939171075820923
    -----------------------------------------------------------

```python
# Plot with the fast time dynamic
print("times of the first plot:\t" + str(t[0:4]))
plt.figure()
plt.title("Fast relaxation dynamics of the first time steps")
plt.xlabel("Surface (m)"); plt.ylabel("Temperature (K)")
plt.plot(x,Temp_map[0][0,:],"r",label = "$T_e$")
plt.plot(x,Temp_map[1][0,:],"k--",label = "$T_l$")
for i in range(1,4):
    plt.plot(x,Temp_map[0][i,:],"r")
    plt.plot(x,Temp_map[1][i,:],"k--")
plt.legend()
plt.grid()
```

Now "dirichlet" boundary conditions are applied correctly and the value on the left and on the right end of the material are fixed.