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

# Class 18: Optimal Monetary Policy in the New-Keynesian Model

In this notebook, we will derive and compute the optimal monetary policy response to exogenous shocks in the new-Keynesian model.

## The New-Keynesian Model

Consider the New-Keynesian model without a specification for monetary policy:

\begin{align}
y_t & = E_t y_{t+1}  - \left( r_{t} - \bar{r}\right) + g_t\\
i_{t} & = r_{t} + E_t \pi_{t+1}\\
\pi_t & = \beta E_t\pi_{t+1}  + \kappa y_t+ u_t,
\end{align}

where $\bar{y}$ and $\pi^T$ have been set to zero. We can interpret $y$ as the output gap (log difference of output from steady state). The system above has three equations, but four endogenous variables: $y_t$, $\pi_t$, $i_t$, and $r_t$ ($u_t$ and $g_t$ are exoenous to the system). Previously, we closed the model by assuming a *rule* for monetary that specified the nominal interest rate as a linear function of inflation and output. Here we will derive optimal monetary policy.

## The Optimization Problem

Suppose that the central bank has preferences over inflation and output in period $t$ is represented by the *loss function*:

\begin{align}
L\left(\pi_t, y_t\right) & = \frac{1}{2}\bigg[\pi_t^2 + \lambda y_t^2\bigg].
\end{align}

The loss function reflects the degree of disutility that the central bank inscurs when inflation and the output gap are different from zero. $\lambda$ denotes the weight that the central bank places on output fluctuations. If $\lambda =0$, then central bank cares only about inflation stabilization. A value for $\lambda>0$ means that the central bank wants to stabilize output and inflation. In the US, the Fed is required by law to promote stability in inflation *and* employment and so $\lambda>0$ would make sense for a model of the Fed's decisionmaking.

The unconstrained minimum of $L$ is obtained at $\pi_t= 0$ and $y_t = 0$. But often this is not feasible given the constraints of the model. To see why, note that the new-Keynesian Phillips curve imposes a constraint on the feasible combinations of output and inflation. Therefore, the central bank's optimization problem is to solve:

\begin{align}
\min_{\pi, y} \frac{1}{2}\bigg[\pi^2 + \lambda y^2\bigg]
\end{align}

subject to:

\begin{align}
\pi & = \beta \pi' + \kappa y + u
\end{align}

where time subscripts have been dropped. $\pi'$ means expected inflation next period.

The problem can be recast as an unconstrained optimization problem by substituting the Phillips curve into the loss function to eliminate inflation:

\begin{align}
\min_{y} \frac{1}{2}\bigg[\left(\beta \pi' + \kappa y + u\right)^2 + \lambda y^2\bigg]
\end{align}

The first-order condition with respect to $y$ is:

\begin{align}
\kappa \underbrace{\left(\beta \pi' + \kappa y + u\right)}_{\pi} + \lambda y & = 0
\end{align}

or:

\begin{align}
\pi & = \frac{\lambda}{\kappa}y
\end{align}

Since the central bank solves the same problem in each period, we have the final equation for the system given in the first section:

\begin{align}
\boxed{\pi_t  = \frac{\lambda}{\kappa}y_t}
\end{align}

This equation expresses the optimal outcome of monetary policy. It should maintain a fixed proportional relationship between output and inflation that depends only on $\lambda$ and $\kappa$. Given this equation, the optimal setting of the nominal interest rate is determined by the IS and Fisher equations.

## Exogenous Shocks and Parameters

As usual, he variables $g_t$, $u_t$, and $v_t$ represent exogenous shocks to aggregate demand and inflation. They follow AR(1) processes:

\begin{align}
g_{t+1} & = \rho_g g_{t} + \epsilon^g_{t+1}\\
u_{t+1} & = \rho_u u_{t} + \epsilon^u_{t+1}.
\end{align}

We will use the following parameterization in the simulations below:

| $\beta$ | $\bar{r}$    | $\kappa$ | $\rho_g$ | $\rho_u$ | $\lambda$ |
|---------|--------------|----------|----------|----------|-----------|
| 0.995   | $-\log\beta$ | 0.1      | 0.5      | 0.5      | 0.1       |

I have assumed a value of 0.1 for $\lambda$, the weight on ouput in the loss function. This value is in the range of estimates for the Fed's loss function.

```python
# Create a variable called 'parameters' that stores the model parameter values (except lambda) in a Pandas Series. PROVIDED
parameters = pd.Series({'beta':0.995,'r_bar':-np.log(0.995),'kappa':0.1,'rho_g':0.5,'rho_u':0.5})

# Add the value for lambda as 'lam' to 'parameters'

# Create variable called 'var_names' that stores the variable names in a list with state variables ordered first. PROVIDED
var_names = ['g','u','y','pi','i','r']

# Create variable called 'shock_names' that stores an exogenous shock name for each state variable. PROVIDED
shock_names = ['e_g','e_u']

# Define a function that evaluates the equilibrium conditions of the model solved for zero. 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

    # IS equation. PROVIDED
    is_equation = fwd.y - (cur.r -p.r_bar) + cur.g - cur.y

    # Fisher_equation. PROVIDED
    fisher_equation = cur.r + fwd.pi - cur.i

    # Monetary policy. Variable name: 'monetary_policy'

    # Phillips curve. PROVIDED
    phillips_curve = p.beta*fwd.pi + p.kappa*cur.y + cur.u - cur.pi

    # Demand process. PROVIDED
    demand_process = p.rho_g*cur.g - fwd.g

    # Inflation process. PROVIDED
    inflation_process = p.rho_u*cur.u - fwd.u

    # Stack equilibrium conditions into a numpy array. PROVIDED
    return np.array([is_equation,fisher_equation,monetary_policy,phillips_curve,demand_process,inflation_process])

# Initialize the model into a variable named 'model'. PROVIDED
nk_model = ls.model(equations = equilibrium_equations,n_states=2,var_names=var_names,shock_names=shock_names,parameters = parameters)

# Compute the steady state numerically using .compute_ss() method of nk_model. PROVIDED
nk_model.compute_ss([0,0,0,0.01,0.01,0.01])

# Find the log-linear approximation around the non-stochastic steady state and solve using .approximate_and_solve() method of nk_model
# set argumement 'log_linear' to False because the model is already log-linear.

```

## Optimal Responses to Shocks

Compute a 21 period impulse responses of the model's variables to a 0.01 unit shock to the exogenous component of  demand ($g_t$) and a 0.01/4 unit shock to the exogenous component of inflation ($u_t$) in period 5.

```python
# Compute the desired impulse responses

```

### Demand Shocks

Plot the impulse responses of output and inflation to the demand shock. Multiply output by 100 and multiply inflation by 400 (to annualize the values).

```python
# Create figure. PROVIDED
fig = plt.figure(figsize=(12,4))

# Create left subplot. PROVIDED
ax1 = fig.add_subplot(1,2,1)
# Create right subplot. PROVIDED
ax2 = fig.add_subplot(1,2,2)

# Left subplot title. PROVIDED
ax1.set_title('Output $y_t$')
# Right subplot title. PROVIDED
ax2.set_title('Inflation $\pi_t$')

# Left subplot y-axis label. PROVIDED
ax1.set_ylabel('Percent')
# Right subplot y-axis label. PROVIDED
ax2.set_ylabel('Percent')

# Plot output on left axis

# Plot inflation on right axis

```

Apparently optimal policy entails perfectly offsetting demand shocks so that output and inflation are perfectly stabilized. This is because there is no tradeoff in stabilizing output versus inflation in the face of a demand shock.

### Inflation Shocks

Plot the impulse responses of output and inflation to the demand shock. Multiply output by 100 and multiply inflation by 400.

```python
# Create figure. PROVIDED
fig = plt.figure(figsize=(12,4))

# Create left subplot. PROVIDED
ax1 = fig.add_subplot(1,2,1)
# Create right subplot. PROVIDED
ax2 = fig.add_subplot(1,2,2)

# Left subplot title. PROVIDED
ax1.set_title('Output $y_t$')
# Right subplot title. PROVIDED
ax2.set_title('Inflation $\pi_t$')

# Left subplot y-axis label. PROVIDED
ax1.set_ylabel('Percent')
# Right subplot y-axis label. PROVIDED
ax2.set_ylabel('Percent')

# Plot output on left axis

# Plot inflation on right axis

```

The Phillips curve implies that there *is* a tradeoff in setabilizing output versus inflation in the face of an inflation shock. The optimal policy is to allow inflation to rise and to allow output to fall so that the full force of the shock does not fall on inflation.

## Stochastic Simulation with Optimal Monetary Policy

Now we simulate how would output and inflation evolve if monetary policy were set optimally. Specifically, construct a 201 period stochastic simulation with the seed for the random number generator set to 126. Note that the shock covariance matrix for the model is:

\begin{align}
\text{Covariance matrix} & = \left[\begin{array}{cc}\sigma_g^2 & 0\\ 0 & \sigma_u^2\end{array}\right]
\end{align}

Use the following values for $\sigma_g^2$ and $\sigma_u^2$ in the simulation:

| $\sigma_g^2$      | $\sigma_u^2$      |
|-------------------|-------------------|
| 0.0022 | 0.0012 |

```python
# Define variable 'sig_g' equal to the the standard deviation of sigma_g

# Define variable 'sig_u' equal to the the standard deviation of sigma_u

# Compute simulation

```

Plot the simulated values of output and inflation.

```python
# Create figure. PROVIDED
fig = plt.figure(figsize=(12,4))
# Create subplot. PROVIDED
ax = fig.add_subplot(1,1,1)
# Add plot title. PROVIDED
ax.set_title('Output and inflation under optimal monetary policy')
# y-axis label. PROVIDED
ax.set_ylabel('Percent')

# Plot output

# Plot inflation

# Construct legend. PROVIDED
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))
```

Compute the correlations of output and inflation under optimal monetary policy.

```python
# Correlations of output and inflation

```

By requiring that output be proportional to inflation, the optimal monetary policy implies perfect negative correlation between output and inflation. All demand shocks are neutralized.


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

    <IPython.core.display.Javascript object>

# BMWFLC - band-limited multiple-WFLC

In this section, I will propose a new method for tracking multiple frequencies in a pathological tremor signal. The proposed filter is a combination of the E-BMFLC filter and the WFLC filter. Like the band-limited multiple-FLC (BMFLC) filter that consists of multiple-FLC, my idea is to extend this to a band-limited multiple-WFLC (BMWFLC) filter. In int he first figure we can see the new filter architecture, we see that $n$-WFLC are combined to form the BMWFLC. The novelty here is that each frequency in the band now can adjust to the signal.  To accomplish this we measure the magnitude of the Fourier coefficients of each WFLC in the BMWFLC, the WFLC with the largest magnitude contains the dominant frequency in the estimated signal. It is now possible to measure all the frequencies present in the estimated signal, and their strength with respect to each other.

## Core Equations

Each $n$-WFLC in the BMWFLC has its own frequency and learning rate, enabling each WFLC to adapt to the signal individually with its own speed. The learning rates are

\begin{equation}\label{eq:BMWFLClearningrates}
\boldsymbol{\mu}_k= \begin{bmatrix}
\mu_{1k}&\mu_{2_k}&\cdots&\mu_{n_k}
\end{bmatrix}
\end{equation}

and the angular frequencies for the WFLC in the filter are

\begin{equation}\label{eq:BMWFLCfrequencies}
\boldsymbol{\omega}_k= \begin{bmatrix}
\omega_{1_k}&\omega_{2_k}&\cdots&\omega_{n_k}
\end{bmatrix}
\end{equation}

where $k$ is the sampling instance. When $k=0$ we get the size frequency window, where the frequencies have the distance $\Delta w$  between them

\begin{equation}\label{eq:BWMFLCwindow}
[\omega_{1_0},\omega_{n_0}]
\end{equation}

The frequency window is divided into a number of divisions

\begin{equation}\label{eq:divisions}
n=\frac{\omega_{n_0}-\omega_{1_0}}{\Delta \omega}
\end{equation}

and $n$ is then the number of harmonics in for the Fourier combiner model of $y_k$.

\begin{equation}
y_k=\sum_{r=1}^{n}a_{rk}\sin(\omega_{r_k}k )+b_{rk}\cos(\omega_{r_k}k)
\end{equation}

$y_k$ will not be used as an output for this filter, since it will not be tuned to have an accurate estimate of the amplitude, only the frequencies  \eqref{eq:BWMFLCwindow} are of interest. Then $\textbf{x}_k$ and $\textbf{w}_k$ are defined

\begin{align}
\textbf{x}_k&=
\begin{bmatrix}
[\sin(\omega_{1_k}k)&\sin(\omega_{2_k}k)&\cdots&\sin(\omega_{n_k}k)]^T \\ \label{eq:BMWLFCx}
[\cos(\omega_{1_k}k)&\cos(\omega_{2_k}k)&\cdots&\cos(\omega_{n_k}k)]^T
\end{bmatrix}\\ \label{eq:BMWLFCw}
\textbf{w}_k&=\begin{bmatrix}
[a_{1k}&a_{2k}&\cdots & a_{nk}]^T\\
[b_{1k}&b_{2k}&\cdots & b_{nk}]^T
\end{bmatrix}
\end{align}

Using \eqref{eq:BMWLFCx} and \eqref{eq:BMWLFCw} the linear combiner can be written as

\begin{equation}\label{eq:error_sy}
y_k=\textbf{w}_k^T\textbf{x}_k
\end{equation}

and the error beween the signal of the complete motion of the hand and the estimated signal $y_k$ is

\begin{equation}\label{key}
\varepsilon_k=s_k-y_k
\end{equation}

The recursive [least mean squares (LMS) algorithm](https://en.wikipedia.org/wiki/Least_mean_squares_filter) used to update the weights of $\textbf{w}_k$

\begin{align}\label{}
\textbf{w}_{k+1}&=\rho \textbf{w}_{k}+2\mu_k\textbf{x}_{k}\varepsilon_k \label{eq:LMS}\\
\rho&=\sqrt[\delta]{\alpha}\\
\delta&=\frac{1}{\Delta T}T_p\label{}
\end{align}

This is the *memory manipulation* from the E-BMFLC algorithm in the [E-BMFLC notebook](5.%20E-BMFLC.ipynb). It makes the filter forget past information, making it adapt faster when sudden changes occur in the signal $s_k$, E.g., a sudden change of frequency.
Decreasing $\rho$ increases the rate of forgetting past information. $\Delta T$ is the sampling time (in seconds), $T_p$ is the width of the considered memory window (in seconds), and $\alpha$ is the minimum amplification gain considered within the window.

The fundamental angular frequencies $\boldsymbol{\mu}_k$, are updated using multiple modified LSM algorithms, one for each WFLC, each with its own adaptive gain $\boldsymbol{\omega}_k$

\begin{equation}\label{eq:wrk}
\omega_{r_{k+1}}=\omega_{r_k}+2\varepsilon_k\mu_{rk}\left[a_{rk}\cos(r\omega_{r_k}k)-b_{rk}\sin(\omega_{r_k}k) \right], \hspace{16pt} r=1,2,\ldots, n
\end{equation}

Equation \eqref{eq:wrk} is a special case of Equation (7) with one harmonic from the [WFLC notebook](3.%20WFLC.ipynb). Equation  \eqref{eq:wrk} uses the [LMS algorithm](https://en.wikipedia.org/wiki/Least_mean_squares_filter) to descend the performance surface to minimize the [mean squared error (MSE)](https://en.wikipedia.org/wiki/Mean_squared_error). If the learning rates $\boldsymbol{\mu}_k$, are too large all the frequencies will try to reach the frequencies with the largest magnitude in the magnitude spectrum, the global minimum on the performance surface. This is because the error $\varepsilon_{k}$ looks at the error between the input signal $s_k$ and the estimate of the entire signal $y_k$, so each WFLC in the BMWFLC wants to go for the dominant frequency in the signal. If the learning rates $\boldsymbol{\mu}_k$, is set small enough, each WFLC will get stuck in its local minimum, this is what we want, enabling each WFLC only converge towards the real frequency closest to it. When tracking multiple frequencies, the learning rate needs to be small, resulting in slow convergence, if only the frequency with the largest magnitude is being tracked, the learning rate can be set much higher since only the global minimum is of interest, resulting in faster convergence.

## Adaptive Learning Rate

One of the drawbacks with the FLC, WFLC and BMFLC algorithms is that the learning rate parameters $\mu$ and $\mu_0$ needs fine tuning, and are sensitive to change in the amplitude of the input signal \cite{popovic2010adaptive}. The two parameters also need to be tuned separately. Our primary goal is to dampen the tremor for people with PD and ET, but when the tremor is suppressed the amplitude of the signal will be decreased, and the sensitivity of the estimation will decrease. To solve this problem, we can use the amplitude of the input signal to adapt the learning rate. I have come up with a simple but elegant solution to this problem. We will use a queue data structure $Q$, which uses the first-in, first-out, or FIFO, policy. The queue will have a fixed length $L$, a good value for its length is to set it equal to the sampling rate of the signal, $f_s$.

Algorithm 1 checks the $L$ last samples from the tremor signal, and return the peak amplitude. This value can now be used to adapt the learning rates. If the length $L$ is set to $100$ and our sample rate of the signal is $100$ Hz, the window will contain the tremor values for the past 1 second so that the learning rates can adapt relatively fast to any changes in the amplitude of the tremor. The following equations adapt the learning rates.

\begin{equation}\label{eq:Adaptive learningrate}
\mu_k = \dfrac{\kappa}{A_{peak}}
\end{equation}

The value for the scaling parameter $\kappa$ can be found by tuning, I found that a good starting value is $0.01$. The equation to make $\mu_{nk}$  adaptive is

\begin{equation}\label{eq:adaptlearningrate}
\mu_{rk} = \mu_k h + \mu_{rk}\beta e^{-\lambda t}, \hspace{16pt} r=1,2,\ldots, n
\end{equation}

For the first term $h$ scales the learning rate $u_{nk}$ with respect to the value of $\mu_k$. The second term is adds exponential decay, making $mu_{r_k}$  an exponential decaying learning rate. At time $t=0$, $e^0=1$, and $\mu_{r_k} = \mu_k h + \mu_{r_k}\beta $. When $\lim\limits_{t\to \infty}$ the term becomes $0$, and $\mu_{nk} = \mu_k h$ . What this does is to make the learning rate larger in the start and exponentially decaying to the desired and stable $\mu_{r_k} = \mu_k h
$ form.
$\mu_{r_k}$ should have a value that is $\mu_{r_k}<<\mu_k$ for it to be stable ([Adaptive band-pass filter (ABPF) for tremor extraction from inertial sensor data](https://www.sciencedirect.com/science/article/pii/S0169260710000702)),  so for the scaling parameter $h$ a good starting value is $0.00001$. The parameter $\beta$ is the value we want scale up the learnngrate at the start, a good value to start with is 50. $\lambda$ (known as the decay constant) makes the learning rate decay faster when it increases i size. A good starting value for this constant is 0.2. The reason to use a exponential decaying learning rate is to make the estimation of the frequency go faster in the start, but for such high values, the filter would become unstable over time, so it decays to a stable value.

## Magnitude Spectrum - Extracting Frequencies
To find the frequencies in the input signal, we will look at the magnitudes of the Fourier components in the BMFLC, the magnitude of each FLC; the FLC with the highest magnitude should contain the dominant frequency in the estimated signal, which then is the estimate of the dominant frequency for the real signal. This is based on the concept of magnitude spectrum for a Fourier series, presented in the [Fourier Series notebook](1.%20Fourier%20Series.ipynb).

\begin{equation}\label{eq:magnitude_BMWFLC}
M_{rk}=\sqrt{a_{rk}^2 + b_{rk}^2},  \hspace{16pt} r=1,2,\ldots, n
\end{equation}
\begin{equation}\label{eq:magnitude_array}
\textbf{M}=\begin{bmatrix}
M_{1k}&M_{2k}&\cdots&M_{nk}
\end{bmatrix}
\end{equation}

The cool thing about this method is that we can use it to estimate multiple frequencies in the signal. To accomplish this, we look for the magnitudes that are peaks in the magnitude spectrum.   By peak magnitude I mean that the magnitude of interest should be larger than the magnitude to its immediate left and right in the magnitude spectrum, $M_{r-1} < M_r > M_{r+1}$, an example can be seen in Figure \ref{fig:BMWFLCmagnitudeSepctrum}, where the two red markers are peaks in the spectrum. We  want to be able to tell our algorithm how many peaks it should track in the spectrum, each peak it tracks is an estimate of a frequency, so we define a variable called $ftt$ (frequencies to track). Instead of making all the FLC in the truncated Fourier series into WFLC, we can say that the $ftt$ number of FLC with the largest magnitude of Fourier coefficients are allowed to become WFLC. When the learning rate is set to zero, $\mu_{rk}=0$, it becomes an FLC, and its respective frequency $\omega_{r_k}$ lose its capability to adapt to the signal. When the BWMFLC starts to adapt to the signal, all the magnitudes of the FLC are tracked, and the $fft$ number of FLC with the largest peak magnitudes in the magnitude spectrum become WFLC, by setting $\mu_{rk} > 0$. We define an array to store the peak magnitudes and their position in the spectrum.  $M_{peak_1}$ is largest, $M_{peak_2}$ second largest peaks and $Pos_{p_1}$ and $Pos_{p_2}$ are their position in the spectrum respectively. The magnitude peaks can now be used to compare how much power the different dominant frequencies have in respect to each other, this is not something I have implemented here, so this should be looked further at in future iterations of the BMWFLC filter.

\begin{equation}\label{eq:MagPeak}
\textbf{M}_{peak}=\begin{bmatrix}
[M_{p_1},Pos_{p_1}]&[M_{p_2},Pos_{p_2}]&\cdots&[M_{p_r}, Pos_{p_r}]
\end{bmatrix}
\end{equation}

Algorithm 2 shows how we can find the magnitude peaks, and return an array with all the peaks, it should be sorted the same way as \eqref{eq:MagPeak}.

It is now easy to get the estimate for the frequencies; we use the positions from \eqref{eq:MagPeak}, and the estimates will be the frequencies in \eqref{eq:BWMFLCwindow} that corresponds to them. E.g if $Pos_{p_1} = 8$, the estimate for the frequency with the most power in the input signal will be $\omega_{8_k}$. In Figure \ref{fig:BMWFLC_band} we see the frequency window of the BMWFLC, the two arrows with red arrowheads represent the two dominant frequencies in the signal when $ftt=2$, they can adapt to the signal by setting the learning rates, $\mu_{4k} > 0$ and $\mu_{7k} > 0$.

\label{alg:maxAmp}

Since the frequencies in $\boldsymbol{\omega}_k$ will move change over time, there should be a mechanism to reset the frequencies to their original values $\boldsymbol{\omega}_0$, if they no longer are present in the signal. An easy way to do this is to make a threshold variable, $\eta$, and if any of the magnitudes in $\textbf{M}_k$ gets under this threshold, its respective frequency in $\boldsymbol{\omega}_k$  should reset to its original value $\boldsymbol{\omega}_0$.

\begin{equation}\label{eq:threshold}
\omega_{r_k} =
\begin{cases}
\omega_{r_0},& \text{if } M_{rk} <  \eta\\
\omega_{r_k} ,              & \text{otherwise}
\end{cases} \hspace{16pt} r=1,2,\ldots, n
\end{equation}

\begin{exmp}
	\hspace{1px}\\
	To get a better understanding of how finding frequencies in the signal using the magnitude spectrum in the BMWFLC works, we will use the signal $s_k=\underbrace{4}_{A_1}\sin(2\pi \underbrace{4.2 }_{f_1}t) + \underbrace{3}_{A_2}\cos(2\pi \underbrace{5}_{f_2} t)$ as input to our algorithm. We chose the band of [3Hz, 7Hz], with $\Delta f=0.1$. In Figure \ref{fig:BMWFLCmagnitudeSepctrum}, each stem plotted is the magnitude of the Fourier coefficients of its respective FLC (WFLC for the peaks), after running the algorithm for 10 seconds. The two dominant frequencies have red markers, and it's now easy to see which frequencies are dominant.


# Kinematics: the geometry of motion

## Galileo's Physics: Kinematics

[Galileo Galilei](https://en.wikipedia.org/wiki/Galileo_Galilei)
1564-1642, created the foundations for classical engineering physics by
defining __kinematics__. __Kinematics__ is the study of the
geometry of motion. He observed the motion of planets, stars,
falling objects, etc. Galileo defined used the relationships between
position, velocity, and acceleration to make informed predictions.

One of the first kinematic experiments, was Galileo's inclined ramp.
Galileo's hypthosis: objects all fall at the same rate of change in
speed. Now, engineers take this for granted and use a gravitational
constant to describe acceleration, $g=9.81~\frac{m}{s^2}$.

### Inclined ramp experiment

Placing two round objects, of the same shape, on an inclined plane, you
can release them and see they both
- move the same distance in the same amount of time
- move slowly at first, then fastest at the end of the ramp
- the distance travelled is $d \propto t^2$ time-squared

The last obeservation is harder to _see_, but if you use a stopwatch and
meter stick you can create a table for distance and time.

Classical physics describes the position of an object using three
independent coordinates e.g.

$$\mathbf{r}_{P/O} = x\hat{i} + y\hat{j} + z\hat{k}$$

where $\mathbf{r}_{P/O}$ is the position of point $P$ _with respect to the point
of origin_ $O$, $x,~y,~z$ are magnitudes of distance along a
Cartesian coordinate system and $\hat{i},~\hat{j}$ and $\hat{k}$ are
unit vectors that describe three

```python
import numpy as np
import matplotlib.pyplot as plt
plt.style.use('fivethirtyeight')
```

## Velocity

The velocity of an object is the change in [position](#position) per length of time.

\begin{equation}
\mathbf{v}_{P/O} = \frac{d\mathbf{r}_{P/O}}{dt} = \dot{x}\hat{i} + \dot{y}\hat{j} +
\dot{z}\hat{k}
\end{equation}

> __Note:__ The notation $\dot{x}$ and $\ddot{x}$ is short-hand for writing out
> $\frac{dx}{dt}$ and $\frac{d^2x}{dt^2}$, respectively.

The definition of velocity in equation {eq}`velocity` depends upon the change in position of all
three independent coordinates, where
$\frac{d}{dt}(x\hat{i})=\dot{x}\hat{i}$.

## Example - GPS vs speedometer
You can find velocity based upon postion, but you can only find changes
in position with velocity. Consider tracking the motion of a car driving
down a road using GPS. You determine its motion and create the position,
$\mathbf{r} = x\hat{i} +y\hat{j}$, where

$x(t) = 4t + 3$ and $y(t) = 3t - 1$

To get the velocity, calculate $\mathbf{v} = \dot{\mathbf{r}}$

$\mathbf{v} = 4\hat{i} +3 \hat{j}$

```python
t = np.arange(0,5)
x = 4*t + 3
y = 3*t -1
plt.plot(x,y,'o')
plt.quiver(x,y,4,3)
plt.title('Position of car on road every 1 second'+
    '\nvelocity shown as arrow')
plt.xlabel('x-position (m)')
plt.ylabel('y-position (m)');
```

## Speed

The speed of an object is the
[magnitude](https://www.mathsisfun.com/algebra/vectors.html) of the
velocity,

$|\mathbf{v}_{P/O}| = \sqrt{\mathbf{v}\cdot\mathbf{v}} =
\sqrt{\dot{x}^2 + \dot{y}^2 + \dot{z}^2}$

### Remember the chain rule
In Dynamics, there are many moving parts, so it is usually safer to
assume that a variable is a function of time. Consider the derivative of
position,
\begin{equation}
\frac{d}{dt}(x\hat{i}+y\hat{j}) = \dot{x}\hat{i} + \dot{y}\hat{j} +
x\dot{\hat{i}} + y\dot{\hat{j}},
\end{equation}

in a fixed coordinate system (like the ground)
$\dot{\hat{i}}=\dot{\hat{j}}=0$ because these unit vectors do not change direction.

### Chain rule in dynamics

In calculus, many times the [chain rule](https://en.wikipedia.org/wiki/Chain_rule) is shown as an example

- $$\frac{d}{dx}(\sin x^2) = \frac{d}{dx}(x^2)\cdot \cos x^2$$
- $$\frac{d}{dx}(\sin x^2)= 2x\cos x^2$$

When you do this calculation, you have used the chain rule, but there is
a hidden step _multiplying by a chain rule operator_
$$\frac{dx^2}{dx^2}$$:
- $$\frac{d}{dx}(\sin x^2) = \frac{dx^2}{dx}\frac{d}{dx^2}\cdot \cos
x^2$$
-  $$\frac{d}{dx}(\sin x^2) =
\frac{d}{dx}{x^2}\cdot\frac{d}{dx^2}(\cos x^2)$$
- $$\frac{d}{dx}(\sin x^2) = \frac{d}{dx}(x^2)\cdot \cos x^2$$
- $$\frac{d}{dx}(\sin x^2)= 2x\cos x^2$$

The chain rule in our case is used for functions of time, $$t$$
e.g. $$x(t),~y(t),~\theta(t)$$

So, when you take a derivative of a function:

- $$\frac{d}{dt}\sin\theta =
\frac{d\theta}{dt}\frac{d}{d\theta}\sin\theta$$
- $$\frac{d}{dt}\sin\theta =\frac{d\theta}{dt}\cos\theta$$
- $$\frac{d}{dt}\sin\theta =\dot{\theta}\cos\theta$$

## Acceleration

The acceleration of an object is the change in velocity per length of
time.

$\mathbf{a}_{P/O} = \frac{d \mathbf{v}_{P/O} }{dt} = \ddot{x}\hat{i} +
\ddot{y}\hat{j} + \ddot{z}\hat{k}$

where $\ddot{x}=\frac{d^2 x}{dt^2}$ and $\mathbf{a}_{P/O}$ is the
acceleration of point $P$ _with respect to the point of origin_ $O$.

## Rotation and Orientation

The definitions of position, velocity, and acceleration all describe a
single point, but dynamic engineering systems are composed of rigid
bodies is needed to describe the position of an object.

```python
from IPython.core.display import SVG

SVG(filename='./images/position_angle.svg')
```

_In the figure above, the center of the block is located at
$r_{P/O}=x\hat{i}+y\hat{j}$ in both the left and right images, but the
two locations are not the same. The orientation of the block is
important for determining the position of all the material points._

In general, a rigid body has a _pitch_, _yaw_, and _roll_ that describes
its rotational orientation, as seen in the animation below. We will
revisit 3D motion in Module_05

```python
from IPython.display import YouTubeVideo
vid = YouTubeVideo("li7t--8UZms?loop=1")
display(vid)
```

## Rotation in planar motion

Our initial focus is planar rotations e.g. yaw and roll are fixed. For a
body constrained to planar motion, you need 3 independent measurements
to describe its position e.g. $x$, $y$, and $\theta$


# Pendulum with moving support point.

Consider a pendulum with mass $m_2$ hanging from a rod of length $l$. The support point has a mass $m_1$, and can move without friction along a  curve given by formula $y=f(x)$

We will need some helpers for the algebra:

```python
var('t')
var('l g m1 m2')
xy_wsp = [('x1','x_1'),('y1','y_1'),('x2','x_2'),('y2','y_2')]

uv_wsp = [('phi','\phi'),('x','x')]

for v,lv in uv_wsp+xy_wsp:
    var("%s"%v,latex_name=r'%s'%lv)
    vars()[v.capitalize()] = function(v.capitalize())(t)
    var("%sdd"%v,latex_name=r'\ddot %s'%lv)
    var("%sd"%v,latex_name=r'\dot %s'%lv)
    var("d%s"%v,latex_name=r'\delta %s'%lv)

uv = [vars()[v] for v,lv in uv_wsp]

xy = [vars()[v] for v,lv in xy_wsp]
to_fun=dict()
for v,lv in xy_wsp+uv_wsp:
    to_fun[vars()[v]]=vars()[v.capitalize()]
    to_fun[vars()[v+"d"]]=vars()[v.capitalize()].diff()
    to_fun[vars()[v+"dd"]]=vars()[v.capitalize()].diff(2)
to_var = dict((v,k) for k,v in to_fun.items())
```

We introduce coordinates compliant with contraints: $\varphi$, and $x$.

```python
f(x) = sin(x)
x2u = {x1:x,x2:x+l*sin(phi),y2:-l*cos(phi)+sin(x),y1:f(x)}

show(x2u)
```

We express virtual displacement:$\delta x_1,...$ as function of virtual displacements of new coordinates: $\delta x,\delta \phi.$

```python
for w in xy:
    vars()['d'+repr(w)+'_polar']=sum([w.subs(x2u).diff(w2)*vars()['d'+repr(w2)] for w2 in uv])
    show([vars()['d'+repr(w)],vars()['d'+repr(w)+'_polar']])
```

Now we can write d'Alembert principle:

$$\sum_{i} ( \mathbf {F}_{i} - m_i \mathbf{a}_i )\cdot \delta \mathbf r_i = 0,$$

```python
dAlemb = (m1*x1.subs(x2u).subs(to_fun).diff(t,2)  )*dx1_polar + \
         (m1*y1.subs(x2u).subs(to_fun).diff(t,2)+m1*g)*dy1_polar + \
         (m2*x2.subs(x2u).subs(to_fun).diff(t,2)  )*dx2_polar + \
         (m2*y2.subs(x2u).subs(to_fun).diff(t,2)+m2*g)*dy2_polar
dAlemb = dAlemb.subs(to_var)
show(dAlemb.collect(dx).collect(dphi))
```

and derive  equations of motion in new coordintes:

```python
r1 = dAlemb.coefficient(dx)
r2 = dAlemb.coefficient(dphi)
w1,w2 =  solve([r1,r2],[xdd,phidd])[0]
show(w1.trig_simplify())
show(w2)
```

#### Special case

$m_1\to\infty$, $x=-\frac{\pi}{2}$

```python
limit(w1.rhs(),m1=oo).subs({xdd:0,xd:0,x:-pi/2}).show() #.subs({xdd:0,xd:0})
```

```python
limit(w2.rhs(),m1=oo).subs({xdd:0,xd:0,x:-pi/2}).trig_reduce().show()
```

We obtain mathematical pendulum if

```python
limit(w1.rhs(),m1=0).trig_reduce().show() #.subs({xdd:0,xd:0})
```

```python
limit(w2.rhs(),m1=0).trig_reduce().show() #.subs({xdd:0,xd:0})
```

## Numerical analysis of the system
Initial conditions are four numbers: $x,\phi,\dot x,\dot \phi$.

```python
import numpy as np
```

```python
%%time
pars = {l:1,g:9.81,m1:1.,m2:1}
ode=[xd,phid,w1.rhs().subs(pars),w2.rhs().subs(pars)]
times=srange(0,10.25,0.01)
ics=[1,0,0,1]
sol=desolve_odeint(ode,ics,times,[x,phi,xd,phid])
```

    CPU times: user 68 ms, sys: 12 ms, total: 80 ms
    Wall time: 74.1 ms

```python
line( zip(times,sol[::1,0]),figsize=(8,2) )+\
 line( zip(times,sol[::1,1]),color='red')
```

```python
line( zip(times,sol[:,0]),figsize=4 )
line( zip(np.sin(sol[:,1])+sol[:,0],-np.cos(sol[:,1])),figsize=4 )+\
 line( zip(np.sin(sol[:,1])+sol[:,0],-np.cos(sol[:,1])),figsize=4 )
```

### Visualization

It is helpfull to write simple function displaying configuration of the system for given set of variables.

```python
def draw_system(ith=0,l=1):
    x,phi = sol[ith,:2]
    x1,y1,x2,y2 =x, f(x),  l*sin(phi) + x,f(x)-l*cos(phi)

    p = point( (x1,y1), size=40) +\
     point( (x2,y2), size=40,color='red',figsize=3) +\
     line( [(x1,y1),(x2,y2)],aspect_ratio=1)
    n=40
    i0 = max(0,ith-n)

    trace = sum([point((l*sin(phi) + x,f(x)-l*cos(phi)),hue=(0,1-(i)/n,1)) for i,(x,phi) in enumerate(sol[ith:i0:-1,:2])])
    trace2 = sum([point((x,f(x)),hue=(.51,(i)/n,1)) for i,(x,phi) in enumerate(sol[i0:ith,:2])])

    p += trace+trace2
    var('x_')
    p += plot(f(x_),(x_,-4.5,1),figsize=6 )
    p.set_axes_range(-4.5,1,-2,1)
    p.set_aspect_ratio(1)
    return p
```

Let's try:

```python
draw_system(120)
```

We can animate:

```python
from IPython.display import clear_output
import time
for ith in range(0,len(sol),20):
    plt = draw_system(ith=ith,l=1)
    clear_output(wait=True)
    plt.show(figsize=3)
    time.sleep(0.021)

```

Alternatively one can use slider:

```python
@interact
def _(ith=slider(range(len(sol)))):
    plt = draw_system(ith=ith,l=2)
    plt.show(figsize=6)

```

Failed to display Jupyter Widget of type sage_interactive.

  If you're reading this message in the Jupyter Notebook or JupyterLab Notebook, it may mean
  that the widgets JavaScript is still loading. If this message persists, it
  likely means that the widgets JavaScript library is either not installed or
  not enabled. See the Jupyter
  Widgets Documentation for setup instructions.

  If you're reading this message in another frontend (for example, a static
  rendering on GitHub or NBViewer),
  it may mean that your frontend doesn't currently support widgets.

### Chaotic properties of the solution

#### Spectrum

The solution looks chaotic, but it can be caused by mixing of few frequencies. We can, however, calculate the Fourier transform of one of the system variables and see.

```python
%%time
pars = {l:1,g:9.81,m1:1.1,m2:1}
ode = [xd,phid,w1.rhs().subs(pars),w2.rhs().subs(pars)]
times = srange(0,5000.25,0.2)
ics = [1,0,0,0]
sol = desolve_odeint(ode,ics,times,[x,phi,xd,phid])
xfft = np.fft.fft(sol[:,0])
n1 = xfft.shape[0]
```

    CPU times: user 11.2 s, sys: 352 ms, total: 11.6 s
    Wall time: 11.3 s

```python
plt = line(enumerate(np.abs(np.fft.fftshift(xfft))),ymax=2500,thickness=0.1)
plt.show(figsize=(8,2))
```

Let us check for comparison that sum of two signals with different frequencies would not cause such effect

```python
expr = sin(1.2*t)+sin(sqrt(1.2)*t)
```

```python
import numpy as np
import sympy
expr_np = np.vectorize( sympy.lambdify(t, sympy.sympify( expr ) ) )
```

```python
nonchaotic = expr_np(np.linspace(0,5000,10000))
```

```python
%time nonchaotic_fft = np.fft.fft(nonchaotic)
```

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

```python
line(enumerate(nonchaotic[:234])).show(figsize=(7,2))
```

```python
plt2 = line(enumerate(np.abs(np.fft.fftshift(nonchaotic_fft))),alpha=1)
plt2.show(figsize=(8,2))
```

#### Sensitivity to initial conditions

Let us compare two solution which differ by $\frac{1}{1000000}$ in initial velocity.

```python
%%time
pars = {l:1,g:9.81,m1:1.1,m2:1}
ode = [xd,phid,w1.rhs().subs(pars),w2.rhs().subs(pars)]
times = srange(0,35.,0.1)
ics = [1,0,0,0]
sol = desolve_odeint(ode,ics,times,[x,phi,xd,phid])
ics2 = [1+1e-6,0,0,0]
sol2 = desolve_odeint(ode,ics2,times,[x,phi,xd,phid])

```

    CPU times: user 152 ms, sys: 12 ms, total: 164 ms
    Wall time: 145 ms

```python
line(zip(times,sol[:,0]))+line(zip(times,sol2[:,0]),color='red',figsize=(8,3))
```

We can have a look how the error propagetes in log-scale:

```python
line(zip(times[1:],log(abs(sol[1:,0]-sol2[1:,0]))),figsize=(8,3))
```

```python

```

```python

```


```python
import warnings
```

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

```python
# Makes it so any changes in pymedphys is automatically
# propagated into the notebook without needing a kernel reset.
from IPython.lib.deepreload import reload
%load_ext autoreload
%autoreload 2
```

```python
from pymedphys._mocks import profiles
```

```python
field_centre = [0, 0]
side_lengths = [20, 24]
penumbra_width = 2
rotation = 20

field = profiles.create_rectangular_field_function(
    field_centre, side_lengths, penumbra_width, rotation)
```

```python
def show_field_with_profiles(field):
    x = np.arange(-20, 20.1, 0.1)
    xx, yy = np.meshgrid(x, x)

    plt.figure()
    plt.pcolormesh(xx, yy, field(xx, yy))
    plt.axis('equal')

    plt.figure()
    plt.plot(x, field(x, 0))

    plt.figure()
    plt.plot(x, field(0, x))

show_field_with_profiles(field)
```

```python
t = np.linspace(0, 2*np.pi)
plt.plot(np.sin(t), np.cos(t))
plt.axis('equal')
```

```python
x = np.linspace(-1, 1, 100)
y = np.sqrt(1 - x**2)

plt.plot(x, y)
plt.axis('equal')
```

```python
bb_diameter = 8

x = np.linspace(0, bb_diameter + 4, 1000)
y = np.sqrt(bb_diameter**2 - x**2) / bb_diameter

y[np.isnan(y)] = 0

plt.plot(x, y)
plt.plot(x + penumbra_width/3, y)
```

```python
import sympy
```

```python
# From https://codereview.stackexchange.com/a/202614
def convolve(f, g, t, lower_limit=-sympy.oo, upper_limit=sympy.oo):
    tau = sympy.Symbol('__very_unlikely_name__', real=True)
    return sympy.integrate(f.subs(t, tau) * g.subs(t, t - tau),
                     (tau, lower_limit, upper_limit))
```

```python
x = sympy.Symbol('x')
r = sympy.Symbol('r')
circle = sympy.sqrt(r - x**2)

y = sympy.Symbol('y')
sig = sympy.Symbol('\sigma')
mu = sympy.Symbol('\mu')

gauss = 1/(sig * sympy.sqrt(2*sympy.pi)) * sympy.exp(-1/2 * ((y - mu) / sig)**2)

gauss
```

```python
circle
```

```python
# convolve(circle, gauss, 0.5, 0, 1)
```

```python
import scipy.ndimage
```

```python
def create_bb_attenuation_func(diameter, penumbra, max_attenuation):
    dx = diameter / 100
    radius = diameter / 2
    image_half_width = penumbra * 2 + radius

    x = np.arange(-image_half_width, image_half_width + dx, dx)
    xx, yy = np.meshgrid(x, x)

    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        z = np.sqrt(bb_radius**2 - xx**2 - yy**2) / bb_radius

    z[np.isnan(z)] = 0

    sig = profiles.scaled_penumbra_sig() * penumbra
    sig_pixel = sig / dx

    filtered = scipy.ndimage.gaussian_filter(z, sig_pixel)
    interp = scipy.interpolate.RegularGridInterpolator((x, x), filtered, bounds_error=False, fill_value=None)

    def bb_attenuation(x, y):
        return 1 - bb_interp((x, y)) * max_attenuation

    return bb_attenuation
```

```python
dx = 0.1

bb_radius = bb_diameter / 2
bb_penumbra = penumbra_width / 3
image_half_width = bb_penumbra * 2 + bb_radius

x = np.arange(-image_half_width, image_half_width + dx, dx)
xx, yy = np.meshgrid(x, x)

z = np.sqrt(bb_radius**2 - xx**2 - yy**2) / bb_radius
z[np.isnan(z)] = 0

plt.pcolormesh(xx, yy, z)
plt.axis('equal')
plt.colorbar()
```

```python
sig = profiles.scaled_penumbra_sig() * bb_penumbra
sig_pixel = sig / dx
```

```python
filtered = scipy.ndimage.gaussian_filter(z, sig_pixel)
plt.pcolormesh(xx, yy, filtered)
plt.axis('equal')
```

```python
bb_interp = scipy.interpolate.RegularGridInterpolator((x, x), filtered, bounds_error=False, fill_value=None)
```

```python
result = bb_interp((xx, yy))
plt.pcolormesh(xx, yy, result)
plt.colorbar()
plt.axis('equal')
```

```python
bb_attenuation = 0.3
result = 1 - bb_interp((xx, yy)) * bb_attenuation

plt.pcolormesh(xx, yy, result)
plt.colorbar()
plt.axis('equal')
```

```python
bb_attenuation_map = create_bb_attenuation_func(bb_diameter, penumbra_width/3, bb_attenuation)

result = bb_attenuation_map(xx, yy)

plt.pcolormesh(xx, yy, result)
plt.colorbar()
plt.axis('equal')
```

```python
field_centre = [0, 0]
side_lengths = [20, 24]
penumbra_width = 2
rotation = 20

bb_centre = [2, 2]
bb_diameter = 8
bb_attenuation = 0.3

def create_field_with_bb_func(field_centre, bb_centre, side_lengths, bb_diameter, penumbra_width, rotation, bb_attenuation):
    field = profiles.create_rectangular_field_function(
        field_centre, side_lengths, penumbra_width, rotation)

    bb_penumbra = penumbra_width / 3
    bb_attenuation_map = create_bb_attenuation_func(bb_diameter, bb_penumbra, bb_attenuation)

    def field_with_bb(x, y):
        return field(x, y) * bb_attenuation_map(x - bb_centre[0], y - bb_centre[1])

    return field_with_bb

field_with_bb = create_field_with_bb_func(field_centre, bb_centre, side_lengths, bb_diameter, penumbra_width, rotation, bb_attenuation)
```

```python
show_field_with_profiles(field_with_bb)
```