###### Content under Creative Commons Attribution license CC-BY 4.0, code under MIT license (c)2014 L.A. Barba, G.F. Forsyth. Partly based on David Ketcheson's pendulum lesson, also under CC-BY.

# Phugoid Oscillation

Welcome back! This is the second IPython Notebook of the series _"The phugoid model of glider flight"_, the first learning module of the course [**"Practical Numerical Methods with Python."**](http://openedx.seas.gwu.edu/courses/GW/MAE6286/2014_fall/about)

In the first notebook, _"Phugoid Motion"_, we described the physics of an aircraft's oscillatory trajectory, seen as an exchange of kinetic and potential energy. This analysis goes back to Frederick Lanchester, who published his book _"Aerodonetics"_ on aircraft stability in 1909. We concluded that first exposure to our problem of interest by plotting the flight paths predicted by Lanchester's analysis, known as _phugoids_.

Here, we will look at the situation when an aircraft is initially moving on the straight-line phugoid (obtained with the parameters $C=2/3$, $\cos\theta=1$, and $z=z_t$ in the previous analysis), and experiences a small upset, a wind gust that slightly perturbs its path. It will then enter into a gentle oscillation around the previous straight-line path: a _phugoid oscillation_.

If the aircraft experiences an upward acceleration of $-d^2z/dt^2$, and we assume that the perturbation is small, then $\cos\theta=1$ is a good approximation and Newton's second law in the vertical direction is:

$$\begin{equation}
L - W = - \frac{W}{g}\frac{d^2 z}{dt^2}
\end{equation}$$

In the previous notebook, we saw that the following relation holds for the ratio of lift to weight, in terms of the trim velocity $v_t$:

$$\begin{equation}
\frac{L}{W}=\frac{v^2}{v_t^2}
\end{equation}$$

This will be useful: we can divide Equation (1) by the weight and use Equation (2) to replace $L/W$. Another useful relation from the previous notebook expressed the conservation of energy (per unit mass) as $v^2 = 2 gz$. With this, Equation (1) is rearranged as:

$$\begin{equation}
\frac{d^2z}{dt^2} + \frac{gz}{z_t} = g
\end{equation}$$

Look at Equation (3) for a moment. Does it ring a bell? Do you recognize it?

If you remember from your physics courses the equation for _simple harmonic motion_, you should see the similarity!

Take the case of a simple spring. Hooke's law is $F=-kx$, where $F$ is a restoring force, $x$ the displacement from a position of equilibrium and $k$ the spring constant. This results in the following ordinary differential equation for the displacement:

$$\begin{equation}
 \frac{d^2 x}{dt^2}= -\frac{k}{m}x
\end{equation}$$

which has the solution $x(t) = A \cos(\omega t- \phi)$, representing simple harmonic motion with an angular frequency $\omega=\sqrt{k/m}=2\pi f$ and phase angle $\phi$.

Now look back at Equation (3): it has the same form and it represents simple harmonic motion with angular frequency $\omega=\sqrt{g/z_t}$.

Think about this for a moment ... we can immediately say what the period of the oscillation is: exactly $2 \pi \sqrt{z_t/g}$ — or, in terms of the trim velocity, $\pi \sqrt{2} v_t/g$.

_This is a remarkable result!_ Think about it: we know nothing about the aircraft, or the flight altitude, yet we can obtain the period of the phugoid oscillation simply as a function of the trim velocity. For example, if trim velocity is 200 knots, we get a phugoid period of about 47 seconds—over that time, you really would not notice anything if you were flying in that aircraft.

Next, we want to be able to compute the trajectory of the aircraft for a given initial perturbance. We will do this by numerically integrating the equation of motion.

## Prepare to integrate

We want to integrate the differential equation and plot the trajectory of the aircraft. Are you ready?

The equation for the phugoid oscillation is a second-order, ordinary differential equation (ODE). Let's represent the time derivative with a prime, and write it like this:

$$\begin{equation}
z(t)'' + \frac{g \,z(t)}{z_t}=g
\end{equation}$$

There's a convenient trick when we work with ODEs: we can turn this 2nd-order equation into a system of two 1st-order equations. Like this:

$$\begin{eqnarray}
z'(t) &=& v(t)\\
v'(t) &=& g\left(1-\frac{z(t)}{z_t}\right)
\end{eqnarray}$$

Are you following? Make sure you are following the derivations, even if it means writing the equations down in your own notes! (Yes, the old-fashioned paper way.)

Another way to look at a system of two 1st-order ODEs is by using vectors. You can make a vector with your two independent variables,

$$\begin{equation}
\vec{u}  = \begin{pmatrix} z \\ v \end{pmatrix}
\end{equation}$$

and write the differential system as a single vector equation:

\begin{equation}
\vec{u}'(t)  = \begin{pmatrix} v \\ g-g\frac{z(t)}{z_t} \end{pmatrix}
\end{equation}

If you call the right-hand-side $\vec{f}(\vec{u})$, then the equation is very short: $\vec{u}'(t) = \vec{f}(\vec{u})$—but let's drop those arrows to denote vectors from now on, as they are a bit cumbersome: just remember that $u$ and $f$ are vectors in the phugoid equation of motion.

Next, we'll prepare to solve this problem numerically.

## Initial value problems

Let's step back for a moment. Suppose we have a first-order ODE $u'=f(u)$. You know that if we were to integrate this, there would be an arbitrary constant of integration. To find its value, we do need to know one point on the curve $(t, u)$. When the derivative in the ODE is with respect to time, we call that point the _initial value_ and write something like this:

$$u(t=0)=u_0$$

In the case of a second-order ODE, we already saw how to write it as a system of first-order ODEs, and we would need an initial value for each equation: two conditions are needed to determine our constants of integration. The same applies for higher-order ODEs: if it is of order $n$, we can write it as $n$ first-order equations, and we need $n$ known values. If we have that data, we call the problem an _initial value problem_.

Remember the definition of a derivative? The derivative represents the slope of the tangent at a point of the curve $u=u(t)$, and the definition of the derivative $u'$ for a function is:

$$u'(t) = \lim_{\Delta t\rightarrow 0} \frac{u(t+\Delta t)-u(t)}{\Delta t}$$

If the step $\Delta t$ is already very small, we can _approximate_ the derivative by dropping the limit. We can write:

$$\begin{equation}
u(t+\Delta t) \approx u(t) + u'(t) \Delta t
\end{equation}$$

With this equation, and because we know $u'(t)=f(u)$, if we have an initial value, we can step by $\Delta t$ and find the value of $u(t+\Delta t)$, then we can take this value, and find $u(t+2\Delta t)$, and so on: we say that we _step in time_, numerically finding the solution $u(t)$ for a range of values: $t_1, t_2, t_3 \cdots$, each separated by $\Delta t$. The numerical solution of the ODE is simply the table of values $t_i, u_i$ that results from this process.

## Discretization

In order to execute the process described above and find the numerical solution of the ODE, we start by choosing the values $t_1,t_2,t_3 \cdots t_n$—we call these values our *grid* in time. The first point of the grid is given by our _initial value_, and the small difference between two consecutive times is called the _time step_, denoted by $\Delta t$.  The solution value at time $t_n$ is denoted by $u_n$.

Let's build a time grid for our problem. We first choose a final time $T$ and the time step $\Delta t$. In code, we'll use readily identifiable variable names: `T` and `dt`, respectively. With those values set, we can calculate the number of time steps that will be needed to reach the final time; we call that variable `N`.

Let's write some code. The first thing we do in Python is load our favorite libraries: NumPy for array operations, and the Pyplot module in Matplotlib, to later on be able to plot the numerical solution. The line `%matplotlib inline` tells IPython Notebook to show the plots inline.

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

Now, initialize `T` and `dt`, calculate `N` and build a NumPy array with all the values of time that make up the grid.

```python
T = 100.0
dt = 0.01
N = int(T/dt)+1
t = numpy.linspace(0.0, T, N)
```

We have our grid! Now it's time to apply the numerical time stepping represented by Equation (10).

##### Challenge!

* Write the code above using the NumPy function `arange()` instead of `linspace()`. If you need to, read the documentation for these functions.

```python
# Challenge solution
T = 100.0
dt =  0.01
N = int(T/dt) + 1
t- numpy.arange(0.0, T + dt, dt)

```

##### Pro tip:

Enter a question mark followed by any function, e.g., `?numpy.linspace`, into a code cell and execute it, to get a help pane on the notebook.

## Euler's method

The approximate solution at time $t_n$ is $u_n$, and the numerical solution of the differential equation consists of computing a sequence of approximate solutions by the following formula, based on Equation (10):

$$u_{n+1} = u_n + \Delta t \,f(u_n).$$

This formula is called **Euler's method**.

For the equations of the phugoid oscillation, Euler's method gives the following algorithm that we need to implement in code:

\begin{align}
z_{n+1} & = z_n + \Delta t \, v_n \\
v_{n+1} & = v_n + \Delta t \left(g - \frac{g}{z_t} \, z_n \right).
\end{align}

### And solve!

To apply the numerical solution method, we need to set things up in code: define the parameter values needed in the model, initialize a NumPy array to hold the discrete solution values, and initialize another array for the angular values.

```python
# initial conditions
z0 = 100.  #altitude
v  = 10   #upward velocity resulting from gust
zt = 100.
g  = 9.81

u = numpy.array([z0, v])

# initialize an array to hold the changing angle values
z = numpy.zeros(N)
z[0] = z0
```

You should pay attention to a couple of things: (1) See how there is a dot after the numbers used to define our parameters? We just want to be explicit (as a good habit) that these variables are real numbers, called "floats." (2) We both _created_ and _initialized_ with zeros everywhere the solution vector `z`. Look up the documentation for the handy NumPy function `zeros()`, if you need to. (3) In the last line above, we assign the _initial value_ to the first element of the solution vector: `z[0]`.

Now we can step in time using Euler's method. Notice how we are time stepping the two independend variables at once in the time iterations.

```python
# time-loop using Euler's method
for n in range(1,N):
    u = u + dt*numpy.array([u[1], g*(1-u[0]/zt)])
    z[n] = u[0]
```

Make sure you understand what this code is doing. This is a basic pattern in numerical methods: iterations in a time variable that apply a numerical scheme at each step.

## Plot the solution

If the code is correct, we have stored in the array `z` the position of the glider at each time. Let's use Matplotlib to examine the flight path of the arcraft.

You should explore the [Matplotlib tutorial](http://matplotlib.org/users/pyplot_tutorial.html) (if you need to) and familiarize yourself with the command-style functions that control the size, labels, line style, and so on. Creating good plots is a useful skill: it is about communicating your results effectively.

Here, we set the figure size, the limits of the vertical axis, the format of tick-marks, and axis labels. The final line actually produces the plot, with our chosen line style (continuous black line).

```python
pyplot.figure(figsize=(10,4))   #set plot size
pyplot.ylim(40,160)             #y-axis plot limits
pyplot.tick_params(axis='both', labelsize=14) #increase font size for ticks
pyplot.xlabel('t', fontsize=14) #x label
pyplot.ylabel('z', fontsize=14) #y label
pyplot.plot(t,z, 'k-');
```

##### Explore and think

Try changing the value of `v` in the initial conditions.

* What happens when you have a larger gust?
* What about a smaller gust?
* What happens if there isn't a gust (`v = 0`)?

## Exact solution

The equation for phugoid oscillations is a 2nd-order, linear ODE and it has an exact solution of the following form:

$$
\begin{equation}
z(t) = A \sin \left(\sqrt{\frac{g}{z_t}} t \right) + B \cos \left(\sqrt{\frac{g}{z_t}} t \right) + z_t
\end{equation}
$$

where $A$ and $B$ are constants that we solve for using initial conditions.

Our numerical solution used the initial conditions:

$$\begin{eqnarray}
z(0) = z_0 \\
v(0) = v_0
\end{eqnarray}$$

Applying these to the exact solution and solving for $A$ and $B$, we get:

$$\begin{equation}
z(t) = v_0 \sqrt{\frac{z_t}{g}} \sin \left(\sqrt{\frac{g}{z_t}} t \right) + (z_0-z_t) \cos \left(\sqrt{\frac{g}{z_t}} t \right) + z_t
\end{equation}$$

We already defined all of these variables for our numerical solution, so we can immediately compute the exact solution.

##### Pro tip:

The expression is a  bit long —if you don't feel like scrolling left and right, you can insert a line break in the code using a backslash '\' _(without any trailing spaces)_ and Python will treat the next line as a continuation of the first.

```python
z_exact = v*(zt/g)**.5*numpy.sin((g/zt)**.5*t)+\
            (z0-zt)*numpy.cos((g/zt)**.5*t)+zt
```

## Plot the exact solution

Now we can plot our exact solution!  Even better, we can plot _both_ the numerical solution *and* the exact solution to see how well Euler's method approximated the phugoid oscillations.

To add another curve to a plot, simply type a second `pyplot.plot()` statement.  We also added a legend using the format

```Python
pyplot.legend(["line 1","line 2"])
```

```python
pyplot.figure(figsize=(10,4))
pyplot.ylim(40,160)             #y-axis plot limits
pyplot.tick_params(axis='both', labelsize=14) #increase font size for ticks
pyplot.xlabel('t', fontsize=14) #x label
pyplot.ylabel('z', fontsize=14) #y label
pyplot.plot(t,z)
pyplot.plot(t, z_exact)
pyplot.legend(['Numerical Solution','Analytical Solution']);
```

That looks like pretty good agreement, but what's happening towards the end? We'll come back to this.

Euler's method, like all numerical methods, introduces some errors.  If the method is *convergent*, the approximation will get closer and closer to the exact solution as we reduce the size of the step, $\Delta t$. The error in the numerical method should tend to zero, in fact, when $\Delta t\rightarrow 0$—when this happens, we call the method _consistent_. We'll define these terms more carefully in the theory components of this course.

## Convergence

To compare the two solutions, we need to use a **norm** of the difference, like the $L_1$ norm, for example.

$$ E = \Delta t \sum_{n=0}^N \left|z(t_n) - z_n\right|$$

The $L_1$ norm is the sum of the individual differences between the exact and the numerical solutions, at each mesh point.

We check for convergence by calculating the numerical solution using progressively smaller values of `dt`. We already have most of the code that we need.  We just need to add an extra loop and an array of different $\Delta t$ values to iterate through.

You should read the documentation for the function [enumerate](https://docs.python.org/2/library/functions.html#enumerate) and make sure you understand how we're using it below.

##### Warning

The cell below can take a little while to finish (the last $\Delta t$ value alone requires 1 million iterations!).  If the cell is still running, the input label will say `In [*]`.  When it finishes, the `*` will be replaced by a number.

```python
# time-increment array
dt_values = numpy.array([0.1, 0.05, 0.01, 0.005, 0.001, 0.0001])

# array that will contain solution of each grid
z_values = numpy.empty_like(dt_values, dtype=numpy.ndarray)

for i, dt in enumerate(dt_values):
    N = int(T/dt)+1    # number of time-steps
    ### discretize the time using numpy.linspace() ###
    t = numpy.linspace(0.0, T, N)

    # initial conditions
    u = numpy.array([z0, v])
    z = numpy.empty_like(t)
    z[0] = z0

    # time loop - Euler method
    for n in range(1,N):
        ### compute next solution using Euler method ###
        u = u + dt*numpy.array([u[1], g*(1-u[0]/zt)])
        z[n] = u[0]   # store the elevation at time-step n+1

    z_values[i] = z.copy()    # store the total elevation calculation grid i
```

### Calculate the error

We now have numerical solutions for each $\Delta t$ in the array `z_values`.  To calculate the error corresponding to each $\Delta t$, we can write a function!

```python
def get_error(z, dt):
    """Returns the error relative to analytical solution using L-1 norm.

    Parameters
    ----------
    z : array of float
        numerical solution.
    dt : float
        time increment.

    Returns
    -------
    err : float
        L_{1} norm of the error with respect to the exact solution.
    """
    N = len(z)
    t = numpy.linspace(0.0, T, N)

    z_exact = v*(zt/g)**.5*numpy.sin((g/zt)**.5*t)+\
                (z0-zt)*numpy.cos((g/zt)**.5*t)+zt

    return dt * numpy.sum(numpy.abs(z-z_exact))
```

**Note**: in the last line of the function, we perform an 'array operation':

`z - z_exact`

We are *not* subtracting one value from another.  Instead, we are taking the difference between elements at each corresponding index in both arrays.  Here is a quick example:

```python
a = numpy.array([1, 2, 3])
b = numpy.array([4, 4, 4])

b - a
```

    array([3, 2, 1])

Now, we iterate through each $\Delta t$ value and calculate the corresponding error.

```python
error_values = numpy.empty_like(dt_values)

for i, dt in enumerate(dt_values):
    ### call the function get_error() ###
    error_values[i] = get_error(z_values[i], dt)
```

Remember, *if* the method is convergent then the error should get smaller as  $\Delta t$ gets smaller.  To visualize this, let's plot $\Delta t$ vs. error.  If you use `pyplot.plot` you won't get a very useful result.  Instead, use `pyplot.loglog` to create the same plot with a log-log scale.  This is what we do almost always to assess the errors of a numerical scheme graphically.

```python
pyplot.figure(figsize=(10, 6))
pyplot.tick_params(axis='both', labelsize=14) #increase tick font size
pyplot.grid(True)                         #turn on grid lines
pyplot.xlabel('$\Delta t$', fontsize=16)  #x label
pyplot.ylabel('Error', fontsize=16)       #y label
pyplot.loglog(dt_values, error_values, 'ko-')  #log-log plot
pyplot.axis('equal')                      #make axes scale equally;
```

This is the kind of result we like to see!  As $\Delta t$ shrinks (towards the left), the error gets smaller and smaller, like it should.

##### Challenge!

We calculated the error for several different timestep sizes using two nested `for` loops.  That worked, but whenever possible, we like to re-use code (and not just copy and paste it!).

Create a function that implements Euler's method and re-write the error analysis cell to use your function.

---

######The cell below loads the style of this notebook.

```python
from IPython.core.display import HTML
css_file = '../../styles/numericalmoocstyle.css'
HTML(open(css_file, "r").read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Nixie+One' rel='stylesheet' type='text/css'>
<style>

@font-face {
    font-family: "Computer Modern";
    src: url('http://mirrors.ctan.org/fonts/cm-unicode/fonts/otf/cmunss.otf');
}

#notebook_panel { /* main background */
    background: rgb(245,245,245);
}

div.cell { /* set cell width */
    width: 750px;
}

div #notebook { /* centre the content */
    background: #fff; /* white background for content */
    width: 1000px;
    margin: auto;
    padding-left: 0em;
}

#notebook li { /* More space between bullet points */
margin-top:0.8em;
}

/* draw border around running cells */
div.cell.border-box-sizing.code_cell.running {
    border: 1px solid #111;
}

/* Put a solid color box around each cell and its output, visually linking them*/
div.cell.code_cell {
    background-color: rgb(256,256,256);
    border-radius: 0px;
    padding: 0.5em;
    margin-left:1em;
    margin-top: 1em;
}

div.text_cell_render{
    font-family: 'Alegreya Sans' sans-serif;
    line-height: 140%;
    font-size: 125%;
    font-weight: 400;
    width:600px;
    margin-left:auto;
    margin-right:auto;
}

/* Formatting for header cells */
.text_cell_render h1 {
    font-family: 'Nixie One', serif;
    font-style:regular;
    font-weight: 400;
    font-size: 45pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.5em;
    margin-top: 0.5em;
    display: block;
}
.text_cell_render h2 {
    font-family: 'Nixie One', serif;
    font-weight: 400;
    font-size: 30pt;
    line-height: 100%;
    color: rgb(0,51,102);
    margin-bottom: 0.1em;
    margin-top: 0.3em;
    display: block;
}

.text_cell_render h3 {
    font-family: 'Nixie One', serif;
    margin-top:16px;
	font-size: 22pt;
    font-weight: 600;
    margin-bottom: 3px;
    font-style: regular;
    color: rgb(102,102,0);
}

.text_cell_render h4 {    /*Use this for captions*/
    font-family: 'Nixie One', serif;
    font-size: 14pt;
    text-align: center;
    margin-top: 0em;
    margin-bottom: 2em;
    font-style: regular;
}

.text_cell_render h5 {  /*Use this for small titles*/
    font-family: 'Nixie One', sans-serif;
    font-weight: 400;
    font-size: 16pt;
    color: rgb(163,0,0);
    font-style: italic;
    margin-bottom: .1em;
    margin-top: 0.8em;
    display: block;
}

.text_cell_render h6 { /*use this for copyright note*/
    font-family: 'PT Mono', sans-serif;
    font-weight: 300;
    font-size: 9pt;
    line-height: 100%;
    color: grey;
    margin-bottom: 1px;
    margin-top: 1px;
}

.CodeMirror{
        font-family: "PT Mono";
        font-size: 90%;
}

</style>


# MAT 221 Calculus I

## April 9, 2020

Today's Agenda:
1. Continuous Function
2. Intermediate Value Theorem
3. Exercises

# Implicit Differentiation

An explicit function is of the form that should be the most familiar, such as:

$$ f(x) = x^2 + 3 $$
$$ y = \sin{x} $$

Whereas an *implicit function* defines an algebraic relationship between variables. These functions have a form similar to the following:

$$ x^2 + y^2 = 25 $$
$$ y^5 + xy = 3 $$

In these examples and the following exercises, it is assumed $y$ is implicitly defined and is a differentiable function of $x$.

Luckily, implicitly defined equations do not need to be solved for $y$ in terms of $x$ (or any other variable) to calculate the derivative of the function. We can employ *implicit differentiation* to find the deriatives of implicitly defined equations.

Implicit differentiation is performed by differentiating both sides of the equation with respect to $x$ and then solving for the resulting equation for the derivative of $y$.

As an example, consider the function $y^3 + x^3 = 1$. We can apply implicit differentiation to this equation to find its deriative. Begin by differentiating both sides of the equation with respect to $x$.

$$ \frac{d}{dx} (y^3 + x^3) = \frac{d}{dx} (1) $$

$$ \frac{d}{dx} (y^3) + \frac{d}{dx} (x^3) = 0 $$

Remembering that $y$ is implicitly defined as a function of $x$, we apply the Chain Rule for finding the derivative of $y^3$.

$$ 3y^2 \frac{dy}{dx} + 3x^2 = 0 $$

Then, solving for $\frac{dy}{dx}$:

$$ 3y^2 \frac{dy}{dx} = -3x^2 $$

$$ \frac{dy}{dx} = -\frac{3x^2}{3y^2} = -\frac{x^2}{y^2} $$

When dealing with more complex equations that feature more terms, it can be helpful to use Newton notation such as $y^\prime$ to help keep the intermediate calculations easier to read.

We can confirm our result by using the [SymPy package](http://docs.sympy.org/latest/index.html) as before. Import the needed functions and set $x$ and $y$ as variables to be recognized by SymPy.

```python
from sympy import symbols, idiff, sin, cos, tan, sqrt, init_printing, plot_implicit

init_printing()
x = symbols('x')
y = symbols('y')
```

As we are finding the derivatives of implicitly defined functions, we use the `idiff` function rather than `diff` as seen in previous examples.

```python
idiff(y ** 3 + x ** 3 - 1, y, x)
```

## Examples

### Example 1: Find the derivative of the implicit function $x^2 + xy - y^2 + 4$.

We begin by taking the derivative of both sides of the equation after subtracting $4$ from both sides.

$$ \frac{d}{dx} (x^2 + xy - y^2) = \frac{d}{dx} (-4) $$

Then taking each derivative in turn:

$$ \frac{d}{dx} (x^2) + \frac{d}{dx} (xy) - \frac{d}{dx} (y^2) = 0 $$

Remembering that $y$ is implicitly defined as a function of $x$, we apply the Product Rule when finding the derivative $\frac{d}{dx} (xy)$.

$$ y \frac{dx}{dy} x + x \frac{dy}{dx} = yx^\prime + y^\prime x $$

Finding the remaining derivatives of the function:

$$ 2x + yx^\prime + y^\prime x - 2yy^\prime = 0 $$

The derivative of $x$ is $1$. The derivative $\frac{d}{dx}$ is found using the Chain Rule:

$$ \frac{d}{dx} (y^2) = \frac{d}{dy} (y^2) \frac{dy}{dx} = 2y \frac{dy}{dx} = 2yy^\prime $$

Finish finding the derivative of the equation:

$$ 2x + y + y^\prime x - 2yy^\prime = 0 $$

Isolate $y^\prime$ to one side by subtracting $2x + y$ from both sides.

$$ y^\prime x - 2y y^\prime = -2x - y $$

Factor $y^\prime$ on the left side:

$$ y^\prime (2y - x) = -2x - y $$

Divide both sides by $x - 2y$.

$$ y^\prime = \frac{dy}{dx} = - \frac{2x - y}{x - 2y} $$

Check our answer using SymPy:

```python
idiff(x ** 2 + x * y - y ** 2 + 4, y, x)
```

For those interested, the implicit equations and their derivatives can be plotted easily using SymPy's [`plot_implicit()`](http://docs.sympy.org/latest/modules/plotting.html) function.

```python
plot_implicit(x ** 2 + x * y - y ** 2 + 4, title = 'graph of $x^2 + xy - y^2 + 4$')
plot_implicit(-(2 * x + y) / (x - 2 * y))
```

### Example 2: Find the derivative of the implicit equation $x^4 (x + y) = y^2 (3x - y)$

Focus on the left-hand side of the equation for now. Differentiate both sides of the equation and apply the Product Rule to the left-hand side.

$$ x^4 \frac{d}{dx} (x + y) + (x + y) \frac{d}{dx} x^4 = \frac{d}{dx} (y^2 (3x - y)) $$

$$ x^4 \frac{d}{dx} (x) + \frac{d}{dx} (y) + (x + y) \frac{d}{dx} x^4 = \frac{d}{dx} (y^2 (3x - y)) $$

Calculate the derivatives on the left-hand side of the equation:

$$ x^4(1 + y^\prime) + 4x^3(x + y) = y^2 \frac{d}{dx}(3x - y) + (3x - y) \frac{d}{dx} y^2 $$

Shift focus to the right-hand side of the equation:

$$ y^2 (\frac{d}{dx} 3x - \frac{d}{dx} y) + (3x - y) \frac{d}{dx} y^2 $$
$$ = y^2(3 - y^\prime) + 2yy^\prime (3x - y) $$

Thus, we now have:

$$ x^4(1 + y^\prime) + 4x^3 (x + y) = y^2(3 - y^\prime) + 2yy^\prime (3x - y) $$

Expand and clean up the terms:

$$ x^4 + x^4 y^\prime + 4x^4 + 4x^3 y = 3y^2 - y^2 y^\prime + 6xyy^\prime - 2y^2 y^\prime $$

$$ 5x^4 + x^4 y^\prime + 4x^3 y = 3y^2 + 6xyy^\prime - 3y^2 y^\prime $$

We then want to isolate $y^\prime$ to one side of the equation.

$$ 5x^4 + x^4 y^\prime + 4x^3 y - 6xyy^\prime + 3y^2 y^\prime = 3y^2 $$

$$ x^4 y^\prime - 6xyy^\prime + 3y^2 y^\prime = 3y^2 - 5x^4 - 4x^3 y $$

Finish calculating the derivative by factoring $y^\prime$ on the left-hand side of the equation and dividing:

$$ y^\prime (x^4 - 6xy + 3y^2) = 3y^2 - 5x^4 - 4x^3 y $$

$$ y^\prime = \frac{3y^2 - 5x^4 - 4x^3 y}{x^4 - 6xy + 3y^2} $$

Using SymPy as before, we can verify our answer is correct.

```python
idiff(x ** 4 * (x + y) - y ** 2 * (3 * x - y), y, x)
```

Also, for fun, we can plot the implicit function and its derivative as we did previously. Implicit functions, such as the one we just worked with, often make very appealing curves and graphs. For example, the previous implicit equation makes a unique bowtie like shape when plotted.

```python
plot_implicit(x ** 4 * (x + y) - y ** 2 * (3 * x - y))
```

The derivative of the equation can be plotted similarly.

```python
plot_implicit((-5 * x ** 5 - 4 * x ** 3 * y + 3 * y ** 2) / (x ** 4 - 6 * x * y + 3 * y ** 2))
```

### Example 3: Derivative of the equation $4 \cos{x} \space \sin{y} = 1$

Plotting the implicit equation $4 \cos{x} \space \sin{y} = 1$ reveals a rather unique and interesting graph.

```python
plot_implicit(4 * cos(x) * sin(y) - 1, (x, -10, 10), (y, -10, 10))
```

The derivative of the implicit equation can be found as in the previous examples. Start by differentiating both sides of the equation:

$$ 4 (\cos{x} \frac{d}{dx} \sin{y} + \sin{y} \frac{d}{dx} \cos{x}) = \frac{d}{dx} \space 1 $$

Then we calculate each derivative in turn, recalling that $y$ is implicitly defined as a function of $x$ and thus need to apply the Chain Rule to find the deriviative $\frac{d}{dx} \sin{y}$.

$$ 4(\cos{x} \space y^\prime \cos{y} - \sin{y} \space \sin{x}) = 0 $$

Expanding the terms:

$$ 4 \cos{x} \space y^\prime \cos{y} - 4 \sin{y} \space \sin{x} = 0 $$

Isolate $y^\prime$ to one side of the equation.

$$ 4 y^\prime \cos{x} \space \cos{y} = 4 \sin{y} \sin{x} $$

$$ \frac{dy}{dx} = \frac{4 \sin{y} \sin{x}}{4 \cos{x} \space \cos{y}} $$

The derivative can be simplified further knowing that $\tan{x} = \frac{\sin{x}}{\cos{x}}$

$$ \frac{dy}{dx} = \tan{x} \tan{y} $$

Confirm our derivative is correct by checking against SymPy.

```python
idiff(4 * cos(x) * sin(y) - 1, y, x)
```

The derivative of the implicit equation also makes for an interesting graph and creates a grid-like appearance.

```python
plot_implicit(tan(x) * tan(y), (x, -10, 10), (y, -10, 10))
```

## References

Stewart, J. (2007). Essential calculus: Early transcendentals. Belmont, CA: Thomson Higher Education.

Strang, G. (2010). Calculus. Wellesley, MA: Wellesley-Cambridge.

Weisstein, Eric W. "Implicit Function." From MathWorld--A Wolfram Web Resource. http://mathworld.wolfram.com/ImplicitFunction.html


# Balancer Simulations Math Challenge - Advanced

This notebook provides a collection of challenges for an advanced understanding of Balancer Math.

```python
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import math
```

# Challenge No. 1

For the following questions, there are two tokens $X$ and $Y$, with $x$ representing the number of Token $X$ in the pool, and $y$ representing the number of Token $Y$ in the pool.

The plots below represent the invariant curves for two Balancer Pools. Both curves are of the form $2 = x^ay^{1-a}$ for some value of $a$. Try as many of the exercises below as you want -- discuss them with others and have fun!

1. Explain how the equation $V = B_1^{W_1}B_2^{W_2}$ becomes $2 = x^ay^{1-a}$ in this context.

#### Solution

$V$ is the invariant and the specific value for this case is 2
$a$ and $1-a$ are the weights of token $x$ and token $y$ respectively

2. Give one possible value of $a$ and a pair of possible legal values for $x$ and $y$ in this context.

#### Solution
The only restriction for $a$ is it to be a positive value in range (0,1).
Let´s assume $x = 10$ and let´s see how $y$ changes with regard to $a$

```python
a_vals = pd.Series(range(1,10,1))

List = pd.DataFrame(a_vals/10, columns=['a'])

List['1-a'] = 1-List.a
List['token_A'] = 10
List['token_B'] = (2/(List.token_A**List.a))**(1/(1-List.a))

List
```

      a
      1-a
      token_A
      token_B

      0
      0.1
      0.9
      10
      1.672502

      1
      0.2
      0.8
      10
      1.337481

      2
      0.3
      0.7
      10
      1.003394

      3
      0.4
      0.6
      10
      0.683990

      4
      0.5
      0.5
      10
      0.400000

      5
      0.6
      0.4
      10
      0.178885

      6
      0.7
      0.3
      10
      0.046784

      7
      0.8
      0.2
      10
      0.003200

      8
      0.9
      0.1
      10
      0.000001

3. Rewrite the curve $2 = x^ay^{1-a}$ in the form "$y =$ (some expression involving x)" and plot it using the Python tool of your choice.

#### Solution

$$ y = (\frac{2}{x^a})^{1/(1-a)} $$

4. Generate 100 $(x,y)$ points on the curve $2 = x^{0.6}y^{0.4}$. Now take the **logarithm** of both $x$ and $y$ in your 100 points. Plot $\log(x)$ against $\log(y)$. What do you notice?

```python
x = pd.Series(range(10,0,-1))
a = .6
b = .4

List = pd.DataFrame(x, columns=['token_A'])
List['token_B'] = (2/(List.token_A**a))**(1/(b))
List['log_A'] = np.log(List.token_A)
List['log_B'] = np.log(List.token_B)

List
```

      token_A
      token_B
      log_A
      log_B

      0
      10
      0.178885
      2.302585
      -1.721010

      1
      9
      0.209513
      2.197225
      -1.562969

      2
      8
      0.250000
      2.079442
      -1.386294

      3
      7
      0.305441
      1.945910
      -1.185997

      4
      6
      0.384900
      1.791759
      -0.954771

      5
      5
      0.505964
      1.609438
      -0.681289

      6
      4
      0.707107
      1.386294
      -0.346574

      7
      3
      1.088662
      1.098612
      0.084950

      8
      2
      2.000000
      0.693147
      0.693147

      9
      1
      5.656854
      0.000000
      1.732868

```python
#plot curves with all

fig = px.line(List, x='log_B', y='log_A')

fig.update_xaxes(range=[-6, 6])
fig.update_yaxes(range=[-1, 6])
fig.update_layout(height=800, width=800, title_text='<b>AMM Curve</b>')

fig.show()
```

#### Solutions

The relationship between the variables is linear and the slope of the line is the ration between the weights of the tokens

5. Both pictures above represent AMM curves of the form $2 = x^{a}y^{1-a}$. One of the curves has $a =0.6$ and the other has $a = 0.8$. Which is which? How can you tell?

#### Solution - There are no pics

6. Both of the curves pictured above contain the point $(2,2)$. Explain why any curve of the form $2= x^{a}y^{1-a}$ will pass through $(2,2)$. Is this a special property of the number 2, or will any curve of the form $k = x^{a}y^{1-a}$ pass through $(k,k)$? (You can determine this with an algebraic proof, or by playing with Python graphs.)

#### Solution

Proof by contradiction. (Attempt 1)

Suppose there is no point $(k, k)$ in the curve $k = x^{a}y^{1-a}$.

$\begin{align}
& \text{Let $ y := x $} \quad (\textit{Is this sound?})\tag{1}. \\
& k = x^{a}y^{1-a} \quad \text{(given)} \tag{2} \\
& k = x^{a}x^{1-a} \quad \text{(substituting from (1))} \tag{3} \\
& k = x^{a+1-a} \quad \text{(product of exponents)} \tag{4} \\
& k = x^{1} \tag{5} \\
& k = x \tag{6}
\end{align} $

However, since $x = k$, per (1) this means $y = k$ also, meaning that point $(k, k)$ exists, contradicting the original assumption. $\square$

Proof by contradiction. (Attempt 2)

Suppose there is no point $(k, k)$ in the curve $k = x^{a}y^{1-a}$.
$
\begin{align*}
\text{Let}\ x &:= k  \tag{1}. \\
k &= x^{a}y^{1-a} \quad \text{(Given.)} \tag{2} \\
k &= k^{a}y^{1-a} \quad \text{(Substituting from (1).)} \tag{3} \\
\frac{k}{k^{a}} &= y^{1-a} \tag{4} \\
\log \frac{k}{k^{a}} &= \log y^{1-a} \tag{5} \\
\log {k} - \log {k^{a}} &= \log y^{1-a} \tag{6} \\
\log {k} - a \log {k} &= (1-a) \log y \tag{7} \\
\log {k} - a \log {k} &= \log y - a \log y\tag{8} \\
k &= y \tag{9} \quad \text{(Since a $\log$ function has only one $x$ mapping to every $\log x$.)}
\end{align*}
$

However, since $x = k$ per (1) and also $y=k$ per (9) this means point $(k, k)$ exists, contradicting the original assumption. $\square$

7. Suppose a curve $2 = x^ay^{1-a}$ passes through the point $(2,2)$ and $(p,q)$. Explain how you can use the values of $p$ and $q$ to determine the weights of Token $X$ and Token $Y$.

#### Solution

$
\begin{align*}
  2 &= x^a y^{(1-a)} \\
  2 &= p^a q^{(1-a)} \\
  2 &= x^a y^{(1-a)} \\
  p^a q^{(1-a)} &=  x^a y^{(1-a)}\\
  \log \left(p^a q^{(1-a)}\right) &=  \log \left( x^a y^{(1-a)} \right)\\
  \log (p^a) + \log (q^{(1-a)}) &=  \log (x^a) + \log (y^{(1-a)}) \\
  a \log p + (1-a) \log q &=  a \log x + (1-a) \log y  \\
  a \log p + \log q - a \log q &=  a \log x + \log y - a \log y  \\
  a (\log p - \log q) + \log q  &=  a (\log x - \log y) + \log y \\
  \log q  - \log y &=  a (\log x - \log y) - a (\log p - \log q)\\
  \log q  - \log y &=  a ((\log x - \log y) - (\log p - \log q))\\
  \log q  - \log y &=  a (\log x + \log q - \log y - \log p )\\
  \frac{\log q  - \log y}{\log x + \log q - \log y - \log p }&=  a \\
\end{align*}
$

$
\begin{align}
a = \frac{\log q  - \log y}{\log x + \log q - \log y - \log p } \tag{1}
\end{align}
$

Check:

```python
a = 0.8 # Let a = 0.8; we'll use this to check the Equation 1.
x = 2 # Per premise
y = 2 # Per premise
p = 5 # Arbitrary point
q = (2/(p**a))**(1/(1-a)) # Calculated from p via invariant function

"""Check equality in Equation 1."""
(math.log(q)- math.log(y))/(math.log(x) + math.log(q) - math.log(y) - math.log(p)) == a
```

8. In **Alice's Pool**, which point has the highest spot price $SP_X^{Y}$: **A**, **B** or **C**? (You may be able to answer this without calculation.)

9. Suppose Alice is actively managing her pool against Bob's pool and wishes to trade it against Bob's pool. Identify a pair of points -- one in her pool and one in Bob's pool -- that would represent an arbitrage opportunity for Alice.

```python
x = pd.Series(range(100,0,-1))
a = .6
b = .4
List = pd.DataFrame(x/10, columns=['token_A'])
List['token_B'] = (2/(List.token_A**a))**(1/(b))

a = .8
b = .2
List2 = pd.DataFrame(x/10, columns=['token_A2'])
List2['token_B2'] = (2/(List2.token_A2**a))**(1/(b))

L = pd.concat([List, List2], axis=1)
L.head(20)

```

      token_A
      token_B
      token_A2
      token_B2

      0
      10.0
      0.178885
      10.0
      0.003200

      1
      9.9
      0.181603
      9.9
      0.003331

      2
      9.8
      0.184389
      9.8
      0.003469

      3
      9.7
      0.187248
      9.7
      0.003615

      4
      9.6
      0.190181
      9.6
      0.003768

      5
      9.5
      0.193192
      9.5
      0.003929

      6
      9.4
      0.196283
      9.4
      0.004099

      7
      9.3
      0.199458
      9.3
      0.004278

      8
      9.2
      0.202718
      9.2
      0.004467

      9
      9.1
      0.206069
      9.1
      0.004666

      10
      9.0
      0.209513
      9.0
      0.004877

      11
      8.9
      0.213054
      8.9
      0.005100

      12
      8.8
      0.216696
      8.8
      0.005336

      13
      8.7
      0.220443
      8.7
      0.005586

      14
      8.6
      0.224299
      8.6
      0.005850

      15
      8.5
      0.228269
      8.5
      0.006130

      16
      8.4
      0.232357
      8.4
      0.006427

      17
      8.3
      0.236569
      8.3
      0.006743

      18
      8.2
      0.240910
      8.2
      0.007078

      19
      8.1
      0.245385
      8.1
      0.007434

```python
# In pool 1, to buy 0.1 tokens A $0.181603 - 0.178885 = 0.0027179$ tokens_B are required

# In pool 2, to buy 0.1 tokens A $0.003331 - 0.003200 = 0.0001310$ tokens_B are required

# Considering the point token_A = 9.9, if we sell 0.1 token_A in pool 1 we get 0.0027179 token_B but the price of buying 0.1 token_A in pool 2 is only $0.003469-0.003331=0.0001379$ so we end up with the same amount of token_A + $0.0027179 - 0.0001379 = 0.002580$ token_B.
# If we want to buy more token_A in pool 2, our next 0.1 token_A would cost $0.003615-0.003469=0.0001460$ so we could have now .2 token_A plus $0.002580-0.0001460=0.002434$ token_B

```

    0.002434

```python
cash = L.token_B.iloc[1]-L.token_B.iloc[0]

expense = 0
i = 1
count = 0.1
while expense < cash:
    expense_new = L.token_B2.iloc[i+1]-L.token_B2.iloc[i]
    i+=1
    expense += expense_new
    count += 0.1
count
```

    1.5000000000000002

```python
#plot curves with all
List = List[['token_A', 'token_B']]
List2 = List2[['token_B2']]

df = pd.concat([List, List2], axis=1, ignore_index=False)
fig = px.line(df, x="token_A", y=["token_B", 'token_B2'])
fig.update_xaxes(range=[0, 10])
fig.update_yaxes(range=[0, 10])
fig.update_layout(height=1000, width=1000, title_text='<b>AMM Curve</b>')

fig.show()
```

10. Create the invariant curves for Alice's pool and Bob's pool in Python using the graphing tool of your choice. Choose one point on Bob's curve and highlight it in Blue. Call this point **Z**. On your graph of Alice's curve, color in red **all of the points that represent arbitrage opportunities for Alice against Z**, i.e. color red all of the points on Alice's curve that represent where she could make a profit if she traded with Bob while he held the position represented by **Z**. (**Note**: This is the hardest one in terms of symbolic math, since it involves solving an inequality involving power functions.)

# Challenge No. 2

Alice wants to set up another pool.

She knows that the current price of 1 token C is 37 token D.
She owns 57000 token C, and 510000 token D. She plans to set a fee to 1%, and maximize her capital returns via this pool.

#### 1.
What is the optimal set up for Alice’s new pool? Think about how you’d approach it and explain why.

#### 2.
Due to a huge price dump, the price of token C on external markets drops to 28 token D. Now, Arbitrage traders get to work. Image Alice wanted to re-balance the pool herself, how much token C would she need in order to re-balance the pool in one trade? Keep in mind swap fees and slippage!

#### 3.
Imagine Alice’s pool would be twice as big. How much liquidity would she need for the same price change?

#### 4.
Explore the general relation between the size of the pool (liquidity m in token C and n in token D) and price changes with a series of experiments.
	• derive an expression
	• plot a graph

#### SOLUTION 1.

We understand that profit comes from trading and to be effective and maximize the return, the equilibrium of the pool should be around the trading price. In that sens, we need to calculate the weights of each token according to a Spot Price (SP) equal to the current price

$$ SP = \frac{\frac{B_c}{W_c}}{\frac{B_d}{W_d}} = \frac{1}{37} $$

with $$ W_c + W_d = 1 $$
$$ W_d = 1 - W_c $$

$$ SP = \frac{B_c*W_d}{B_d*W_c} = \frac{B_c}{B_d} * \frac{W_d}{1-W_d} $$
$$ SP * \frac{B_d}{B_c} * (1-W_d) = W_d $$

$$ SP * \frac{B_d}{B_c} = x $$

$$ x * (1-W_d) = x - x*W_d = W_d $$
$$ x = W_d (1+x)$$
$$ W_d = \frac{x}{1+x} $$

```python
SP = 1/37
Bc = 57000
Bd = 510000

x = SP*Bd/Bc

Wd = x/(1+x)
Wd
```

    0.19473081328751435

```python
Wc = 1-Wd
Wc
```

    0.8052691867124857

```python
# Checking:

SP_check = (Bc/Wc)/(Bd/Wd)
SP_check
```

    0.027027027027027032

```python
# What should be the same as:
1/37
```

    0.02702702702702703

#### SOLUTION 2.

Now the $ Price = 1/28 $ but the pool $ SP = 1/37$ so arbitrage is possible. To avoid it through one swap (including fees) we need to look for the trade that moves the price in the pool to $1/28$.

As a simplification we are going to consider:

$W_c = 0.805$

$W_b = 0.195$

```python
# FIRST WITHOUT FEES !

Bc = 57000 # initial balance
Bd = 510000 # initial balance
Wc = 0.805 #define weight
Wd = 0.195 #define weight
s_f = 0.01 #swap fee
inv = (Bc**Wc)*(Bd**Wd) #calculate invariant
a_vals = pd.Series(range(60160,60170,1))
#create dataframe with based on a_vals
List = pd.DataFrame(a_vals, columns=['token_C'])

#create values for plot, add Y_balances according to current invariant
List['invariant'] = inv #value required to calculate token B value
List['token_D'] = (List.invariant/(List.token_C**Wc))**(1/Wd)# calculate corresponding token_B value according to invariant
List['Price_C'] = 1/((List.token_C/Wc) / (List.token_D/Wd))

# List['In-Given-Out'] = List.token_D * (( List.token_C / (List.token_C-1) )**(Wc/Wd) -1)
List['Out-Given-Out'] = List.token_D * (( List.token_C / (List.token_C-1) )**(Wc/Wd) -1)

List
```

      token_C
      invariant
      token_D
      Price_C
      In-Given-Out

      0
      60160
      87388.733071
      408163.402192
      28.008349
      28.009542

      1
      60161
      87388.733071
      408135.395038
      28.005961
      28.007155

      2
      60162
      87388.733071
      408107.390270
      28.003574
      28.004768

      3
      60163
      87388.733071
      408079.387889
      28.001187
      28.002381

      4
      60164
      87388.733071
      408051.387896
      27.998800
      27.999994

      5
      60165
      87388.733071
      408023.390288
      27.996414
      27.997607

      6
      60166
      87388.733071
      407995.395067
      27.994028
      27.995221

      7
      60167
      87388.733071
      407967.402233
      27.991642
      27.992835

      8
      60168
      87388.733071
      407939.411783
      27.989256
      27.990449

      9
      60169
      87388.733071
      407911.423720
      27.986871
      27.988064

An approximation: Bc should be between 60100 and 60200 so the owner would need to bring ~3,1k token_C into the pool and would get 510k-408k = 102k token_B

The exact calculation can be done with the In-Given-Price formula:

$$ A_i = B_i ((\frac{SP_{new}}{SP})^{(\frac{w_0}{w_0+w_i})})-1)$$

```python
SP = 1/37
SP_new = 1/28

A = Bc * (((SP_new/SP)**(Wd/(Wd+Wc)))-1)
A
# Amount of token_C to bring to the pool (in exchange for token_B)
```

    3183.6295721159863

#### SOLUTION 2. with fees

In-Given-Out formula with fees:

a) paying the fee in the token I am getting

$$ A_i = B_i * ((\frac{B_o}{B_o + A_o(1-fee)})^{\frac{w_o}{w_i}}  -1) $$

or b) if I pay the fee in the token I am giving

$$ A_i*(1-fee) = B_i * ((\frac{B_o}{B_o + A_o})^{\frac{w_o}{w_i}}  -1) $$

```python
# WITH FEES

# First considerations: the amount of tokens to be paid should be higher as some of them goes directly to pay fees

Bc = 57000 # initial balance
Bd = 510000 # initial balance
Wc = 0.805 #define weight
Wd = 0.195 #define weight
s_f = 0 #swap fee

inv = (Bc**Wc)*(Bd**Wd) #calculate invariant

a_vals = pd.Series(range(0,5,1))
delta = 1-s_f

#create dataframe with based on a_vals
List = pd.DataFrame(a_vals, columns=['index'])
List['token_C'] = Bc + List.index*(delta)

#create values for plot, add Y_balances according to current invariant
List['invariant'] = inv #value required to calculate token B value
List['token_D'] = (List.invariant/(List.token_C**Wc))**(1/Wd)       # calculate corresponding token_B value according to invariant
List['Price_C'] = 1/((List.token_C/Wc) / (List.token_D/Wd))

# List['In-Given-Out'] = (List.token_C * (( List.token_D / (List.token_D-1) )**(Wd/Wc) -1)) # / (1-s_f)
# List['Out-Given-In'] = (List.token_D * (( List.token_C / (List.token_C + 1) )**(Wc/Wd) -1))

List['New_Own'] = List.token_D * (List.token_C/(List.token_C + delta))**(Wc/Wd) - List.token_D

List
```

      index
      token_C
      invariant
      token_D
      Price_C
      New_Own

      0
      0
      57000
      87388.733071
      510000.000000
      36.936572
      -36.934911

      1
      1
      57001
      87388.733071
      509963.065089
      36.933249
      -36.931588

      2
      2
      57002
      87388.733071
      509926.133501
      36.929927
      -36.928266

      3
      3
      57003
      87388.733071
      509889.205236
      36.926604
      -36.924943

      4
      4
      57004
      87388.733071
      509852.280292
      36.923283
      -36.921622

```python
List.token_D.iloc[2] - List.token_D.iloc[1]
```

    -36.93158792384202

```python
a_vals = pd.Series(range(0,5,1))
s_f = 0.01
delta = 1-s_f

#create dataframe with based on a_vals
List2 = pd.DataFrame(a_vals, columns=['index'])
List2['token_C'] = Bc + List2.index*(delta)

#create values for plot, add Y_balances according to current invariant
List2['invariant'] = inv #value required to calculate token B value
List2['token_D'] = (List2.invariant/(List2.token_C**Wc))**(1/Wd)       # calculate corresponding token_B value according to invariant
List2['Price_C'] = 1/((List2.token_C/Wc) / (List2.token_D/Wd))

# List['In-Given-Out'] = (List.token_C * (( List.token_D / (List.token_D-1) )**(Wd/Wc) -1)) # / (1-s_f)
# List['Out-Given-In'] = (List.token_D * (( List.token_C / (List.token_C + 1) )**(Wc/Wd) -1))

List2['New_Own'] = List2.token_D * (List2.token_C/(List2.token_C +(delta)))**(Wc/Wd) - List2.token_D

List2
```

      index
      token_C
      invariant
      token_D
      Price_C
      New_Own

      0
      0
      57000.00
      87388.733071
      510000.000000
      36.936572
      -36.565578

      1
      1
      57000.99
      87388.733071
      509963.434422
      36.933282
      -36.562321

      2
      2
      57001.98
      87388.733071
      509926.872101
      36.929993
      -36.559065

      3
      3
      57002.97
      87388.733071
      509890.313035
      36.926704
      -36.555809

      4
      4
      57003.96
      87388.733071
      509853.757226
      36.923415
      -36.552554

```python
List2.token_D.iloc[2] - List2.token_D.iloc[1]
```

    -36.56232138548512

```python
1/(List2.token_D.iloc[3]/List.token_D.iloc[3])
```

    0.9999978273765967

```python
# Easy solution:

# The effective tokens in the trade should be the same as before: 3183.6295721159863 but fees need to be paid
# As the effective tokens going into the pool are X-fee = 3183.63 and fee = 1%, .99*X = 3183.63 ->
# fee:
s_f = 0.01

A_new = A / (1-s_f)
A_new
```

    3215.7874465818045

```python
# Paid fees:

3215.7874465818045 - 3183.6295721159863
```

    32.15787446581817

#### SOLUTION 3.

```python
SP = 1/37
SP_new = 1/28
Bc = 2*Bc

A = Bc * (((SP_new/SP)**(Wd/(Wd+Wc)))-1)
A
# Amount of token_C to bring to the pool (in exchange for token_B)
```

    6367.259144231973

```python
# Ratio
6367.259144231973 / 3183.6295721159863
```

    2.0

```python
# Variables
# Size: S
# Change in price: delta
# Number of tokens: T

# T produces a delta in a pool with size S
# n*t produces a delta in a pool with size n*S
```

$$ A_i = B_i ((\frac{SP_{new}}{SP})^{(\frac{w_0}{w_0+w_i})})-1)$$

$$ A_i = B_i*(cte)$$

New pool with $n*B_i$

$$ m*A_i = n*B_i*(cte)$$

$$ \frac{m*A_i}{A_i} = \frac{n*B_i (cte)}{B_i*(cte)}$$

$$ m = n $$

If size is double: $B_i -> 2*B_i$ means that $A_i -> 2*A_i$

So we need more tokens in the sam factor as times the new pool is bigger than the old one

# Challenge No. 3

Bob runs a 50-50 Token X-Token Y pool with a 5% swap fee.

The following traders want to interact with this pool:
- Carlos wants to increase the amount of Token X by 10% by trading X for Y
- Diana wants to increase the amount of Token Y by 10% by trading Y for X
- Ellen wants to increase the spot price X:Y by 10%
- Fabricio wants to increase the spot price Y:X by 10%

#### 1.
For each action, specify the amount that the person will need to trade in to achieve the result.

#### 2.
These actions can not occur simultaneously and will be processed in some order. There are 24 different total orders -- choose a few different ones and simulate the effect on spot price and liquidity. Can you make a conclusion about how increasing liquidity or changing balances affects spot price? If not, try running additional simulations with varying numbers besides 10%.

#### 3.
Now add in two agents who want to increase the pool liquidity by making a deposit. With 6 actors, there are 720 different orderings of the actions. Analyze some. Which actions work "together" (reducing the amount needed of the second action to achieve a particular effect) and which work "against each other"?

#### SOLUTION 1.

```python
# Pool definition
Bc = 50000 # initial balance
Bd = 50000 # initial balance
Wc = 0.5 #define weight
Wd = 0.5 #define weight
s_f = 0.05 #swap fee

inv = (Bc**Wc)*(Bd**Wd) #calculate invariant
a_vals = pd.Series(range(50000,55000,1))
#create dataframe with based on a_vals
List = pd.DataFrame(a_vals, columns=['token_C'])

#create values for plot, add Y_balances according to current invariant
List['invariant'] = inv #value required to calculate token B value
List['token_D'] = (List.invariant/((List.token_C)**Wc))**(1/Wd)# calculate corresponding token_B value according to invariant

# List['In-Given-Out'] = List.token_D * (( List.token_C / (List.token_C-1) )**(Wc/Wd) -1)
# List['Out-Given-Out'] = List.token_D * (( List.token_C / (List.token_C-(1-s_f)) )**(Wc/Wd) -1)
List
```

      token_C
      invariant
      token_D

      0
      50000
      50000.0
      50000.000000

      1
      50001
      50000.0
      49999.000020

      2
      50002
      50000.0
      49998.000080

      3
      50003
      50000.0
      49997.000180

      4
      50004
      50000.0
      49996.000320

      ...
      ...
      ...
      ...

      4995
      54995
      50000.0
      45458.678062

      4996
      54996
      50000.0
      45457.851480

      4997
      54997
      50000.0
      45457.024929

      4998
      54998
      50000.0
      45456.198407

      4999
      54999
      50000.0
      45455.371916

5000 rows × 3 columns

```python
# Carlos wants to increase the amount of Token X by 10% by trading X for Y
# He needs to bring into the pool x*(1-s_f) = 0.1*B_x
# x = (0.1*B_x) / (1-s_f)

# Diana: same result changing X for Y

# Ellen & Fabricio:

```

$$ A_i = B_i ((\frac{SP_{new}}{SP})^{(\frac{w_0}{w_0+w_i})})-1)$$

$$ A_i = B_i ((1.1*SP)^{(\frac{w_0}{w_0+w_i})})-1)$$

```python
A_i = Bc*((1.1)**(0.5)-1)
A_i
```

    2440.4424085075816

```python
2440.4424085075816/Bc
```

    0.04880884817015163

#### BE CAREFUL: WEIGHTS HAVE CHANGED NOW

#### SOLUTION 2.

acting in the same direction:

- trading X for Y and increasing SP bringing X to the pool
- trading Y for X and increasing SP bringing Y to the pool

# Challenge No. 4

A user has $a$ **TKNA** and $b$ **TKNB**:
The TKNA-TKNB pool has 50:50 weights, and reserves of TKNA and TKNB of m and n, respectively.
The user wishes to perform a swap such that the ratio of TKNA:TKNB in their wallet is p:q

$$\frac{a + x}{b + y} = \frac{p}{q}$$

#### 1.
Assuming the user performs a swap between TKNA and TKNB on the TKNA-TKNB pool, derive an expression for x and y.

#### 2.
Using data or algebra, which is the best option to reduce slippage when trading X for Y:
    a) finding a pool with the same invariant but a different X balance value,
    b) finding a pool with a different weight on X,
    c) or finding a pool with a different invariant value?
    Justify your answer in any way you like,
    and explain whether "different" should be "smaller" or "larger" here.

#### SOLUTION 1.

$$ x = \frac{p}{q} * (b+y) - a $$

Out-Given-In

$$ A_o = B_o * (1-(\frac{B_i}{B_i + A_i})^{(\frac{w_i}{w_o}})) = B_o * (1- \frac{B_i}{B_i + A_i}) = n * (1- \frac{m}{m + \frac{p}{q} * (b+y) - a}) = b$$

where $A_i = x$ and $A_o = y$

#### SOLUTION 2.

a) if the pools have he same invariant, the depth (and resistance to slippage is the same). The exact value of slippage can be calculated according to the balance point and here we can add that, if the number of tokens X is bigger in the second pool, the slippage would be smaller and if the number of tokens X is smaller, the slippage would be bigger
b) if the weight of X is bigger, the pool is more resistant to slippage, however, if the weight is smaller, the impact of a swap is bigger
c) a bigger invariatn value (comming from a bigger depth of the pool, a bigger amount of tokens X and tokens Y) makes the pool more resitant to slippage


| |Pierre Proulx, ing, professeur|
|:---|:---|
|Département de génie chimique et de génie biotechnologique |** GCH200-Phénomènes d'échanges I **|

### Exemple 8.3-1, écoulement d'un fluide de type Power-Law dans une conduite cylindrique.

Le développement fait dans Transport Phenomena sera répété ici en développant les solutions avec le calculateur formel sympy et en traçant la solution avec sympy.plot.

La difficulté rencontrée dans cet exemple par rapport à la section 2.3 de Transport Phenomena est dans le type de solution qui est rencontrée. La valeur de n, le coefficient de consistance de la Power-Law, donne lieu à des solutions multiples si n peut prendre des valeurs non-positives. En utilisant positive=True dans la déclaration de la variable **n** on règle ceci...

```python
#
# Initialiser sympy et l'affichage des équationa
#
import sympy as sp
from IPython.display import *
sp.init_printing(use_latex=True)
%matplotlib inline
```

```python
#
# Définir les variables,paramètres et fonctions qui sont utilisées
#
r,L,rho,g,m,R,P_0,P_L=sp.symbols('r,L,rho,g,m,R,P_0,P_L')
n=sp.symbols('n',positive=True)
tau=sp.Function('tau_rz')(r)
v_z=sp.Function('v_z')(r)
```

```python
#
# Équation de Power-Law et bilan de force (voir la section 8-3.1 et la section 2.3 au besoin)
#
tau_rz=m*(-sp.Derivative(v_z,r))**n       # power-law
eq1=sp.Eq(tau_rz-(P_0-P_L)/(2*L)*r)       # bilan
display(eq1)
```

```python
#
# Solution
#
v_z=sp.dsolve(eq1,v_z).rhs
display(v_z)
```

```python
# La constante inconnue est obtenue avec la condition vz(r)=0 en r=R
#
C1=sp.symbols('C1')
constante=sp.solve((v_z.subs(r,R),0),C1)
display(constante)
```

```python
v_z0=v_z.subs(constante)
display(v_z0.simplify())
```

Maintenant voyons la forme de cette expression sous format graphique. On doit choisir des valeurs pour les paramètres. Il est intéressant de voir les variations de profils en fonction de ces paramètres rhéologiques m et n.

```python
# Je ne sais pas exactement pourquoi, mais la fonction subs de sympy ne semble
# pas permettre de remplacer avec un dictionnaire une variable symbol
#    qui a une contrainte positive=True. J'ai trouvé qu'on pouvait le faire directement
#    mais pas avec le dictionnaire. Pas élégant mais ça marche...
#
d1={'rho':1000, 'm':0.1, 'R':0.05,'L':100,'P_0':200,'P_L':0}
R=R.subs(d1)
v_z1=v_z0.subs(d1)
v_z1=v_z1.subs(n,0.9)
v_z1=v_z1/v_z1.subs(r,0).evalf()    # normaliser le profile entre 0 et 1
d2={'rho':1000, 'm':0.1, 'R':0.05,'L':100,'P_0':200,'P_L':0}
v_z2=v_z0.subs(d2)
v_z2=v_z2.subs(n,1.0)
v_z2=v_z2/v_z2.subs(r,0).evalf()
d3={'rho':1000, 'm':0.1, 'R':0.05,'L':100,'P_0':200,'P_L':0}
v_z3=v_z0.subs(d3)
v_z3=v_z3.subs(n,1.2)
v_z3=v_z3/v_z3.subs(r,0).evalf()
p = sp.plot((v_z1,(r,0,R)),(v_z2,(r,0,R)),(v_z3,(r,0,R))
            ,legend=True
            , title='pseudoplastique, Newtonien et dilatant', show=False)
p[0].line_color = 'blue'
p[0].label='pseudoplastique'
p[1].line_color = 'black'
p[1].label='Newtonien'
p[2].line_color = 'red'
p[2].label='dilatant'
p.show()
```

### Exercice

Calculez la viscosité à la paroi pour différentes valeurs de n et de m.


#12.850 - Assignment 5 | Corlett

The problems posed in Assignment 5 focus on the advection of a signal through 1D and 2D domains. For the initial problem, I focus on the propagation of a signal in 1D with a prescribed background velocity:
\begin{equation}
    \theta_{t} + u\theta_{x} = 0
\end{equation}

I've set the initial disturbance to be a sine wave, taking up $^{1}/_{5}$ of the domain space. Each of the three cases below demonstrates the effect of a different propagation scheme, moving from 1$^{st}$ order to 4$^{th}$ order.

```python
#Import toolboxes
from scipy import sparse #Allows me to create sparse matrices (i.e. not store all of the zeros in the 'A' matrix)
from scipy.sparse import linalg as linal
from numpy import * #To make matrices and do matrix manipulation
import matplotlib.pyplot as plt
import matplotlib.cm as cm #Load colormaps
plt.style.use('fivethirtyeight')

%matplotlib inline
```

##1.1 - 1$^{st}$order in space and time

The figures below demonstrate the effects of first order spatial and temporal schemes; I have used the explicit Forward Euler scheme in time, and the first order upwind scheme (as the background velocity is taken to be positive) in space. In this case, the discretization of equation (1) leads to the following scheme:
\begin{equation}
    \theta^{t+1}_{x} = \theta^{t}_{x} - \lambda \left[\theta^{t}_{x} - \theta^{t}_{x-1} \right]
\end{equation}
where $\lambda = u\frac{\Delta t}{\Delta x}$. Following the CFL condition, $\lambda \leq 1$ for stable results.

```python
c=1.
t_del = 0.1
x_del = 0.1
n=100;
T=matrix(zeros((n+1,1)))
for t in range(0,20):
    T[t,0]=sin((t/19.)*2*pi)

Y=(c*t_del)/x_del
Y=0.8 #Force value to be less than one to illustrate dissipation
for Tint in range(0,100):
    T=append(T,matrix(zeros((shape(T)[0],1))),axis=1)
    for int in range(0,shape(T)[0]-1):
        T[int+1,Tint+1]=T[int+1,Tint]-Y*((T[int+1,Tint]-T[int,Tint]))
```

```python
plt.figure(figsize=(12,5))
plt.subplot(121)
plt.plot(arange(0,n+1)*x_del,array(T),linewidth=0.5);plt.title('Propagation of a Sine wave')
plt.ylabel('$\eta$');plt.xlabel('x')
plt.subplot(122)
plt.contourf(arange(0,n+1)*x_del,arange(0,101)*t_del,array(T).T,cmap=cm.RdBu_r)
plt.colorbar();plt.title('Signal over Time');plt.xlabel('x');plt.ylabel('Time')
plt.show()
```

##1.2 - 1$^{st}$order in time and 4$^{th}$order in space

In this example, I've used the explicit Forward Euler scheme in time, and a fourth order centered-difference scheme in space. The discretization of equation (1) in this case takes the following form:
\begin{equation}
    \theta^{t+1}_{x} = \theta^{t}_{x} - \frac{\lambda}{12} \left[\theta^{t}_{x-2} - 8\theta^{t}_{x-1} + 8\theta^{t}_{x+1} - \theta^{t}_{x+2} \right]
\end{equation}

However, the fourth-order scheme requires two points spatially behind the point to be solved; in order to work around this limitation, I have applied the same first-order spatial scheme used above to the boundary cells and a second-order spatial scheme to the next immediate cell to provide a buffer for the interior iterative process.

```python
c=1.
t_del = 0.1
x_del = 0.1
n=100;
t_steps=100

T=matrix(zeros((n+1,1)))
for t in range(3,23):
    T[t,0]=sin(((t-3)/19.)*2*pi)

Y=(c*t_del)/x_del
Y=0.7 #Force value to be less than one to illustrate dissipation
for Tint in range(0,t_steps):
    T=append(T,matrix(zeros((shape(T)[0],1))),axis=1)
    for int in range(0,shape(T)[0]-1):
            if int==0 or int==shape(T)[0]-2:
                T[int+1,Tint+1]=T[int+1,Tint]-Y*((T[int+1,Tint]-T[int,Tint]))
            elif int==1 or int==shape(T)[0]-3:
                T[int+1,Tint+1]=T[int+1,Tint]-Y*((T[int+1,Tint]-T[int-1,Tint])/2)
            else:
                T[int+1,Tint+1]=T[int+1,Tint]-Y*((T[int-2,Tint]-8*T[int-1,Tint]+8*T[int+1,Tint]-T[int+2,Tint])/(12))
```

```python
plt.figure(figsize=(12,5))
plt.subplot(121)
plt.plot(arange(0,n+1)*x_del,array(T),linewidth=0.5);plt.title('Propagation of a Sine wave')
plt.ylabel('$\eta$');plt.xlabel('x')
plt.subplot(122)
plt.contourf(arange(0,n+1)*x_del,arange(0,t_steps+1)*t_del,array(T).T,cmap=cm.RdBu_r)
plt.colorbar();plt.title('Signal over Time');plt.xlabel('x');plt.ylabel('Time')
plt.show()
```

##1.3 - 2$^{nd}$order in time and 4$^{th}$order in space

In this scenario, I have increased the accuracy of the above scheme only in time, using a second-order Leap-Frog method, while retaining the fourth-order centered-difference scheme in space. Thus, the discrete form of equation (1) has become:
\begin{equation}
    \theta^{t+1}_{x} = \theta^{t-1}_{x} - \frac{2\lambda}{12} \left[\theta^{t}_{x-2} - 8\theta^{t}_{x-1} + 8\theta^{t}_{x+1} - \theta^{t}_{x+2} \right]
\end{equation}

```python
c=1.
t_del = 0.1
x_del = 0.1
n=100;
t_steps=600

T=matrix(zeros((n+1,1)))
for t in range(3,23):
    T[t,0]=sin(((t-3)/19.)*2*pi)

Y=(c*t_del)/x_del
Y=0.01 #Force value to be less than one to illustrate dissipation
for Tint in range(0,t_steps):
    T=append(T,matrix(zeros((shape(T)[0],1))),axis=1)
    for int in range(0,shape(T)[0]-1):
        if Tint==0:
            T[int+1,Tint+1]=T[int+1,Tint]-Y*((T[int+1,Tint]-T[int,Tint]))
        else:
            if int==0 or int==shape(T)[0]-2:
                T[int+1,Tint+1]=T[int+1,Tint-1]-2*Y*((T[int+1,Tint]-T[int,Tint]))
            elif int==1 or int==shape(T)[0]-3:
                T[int+1,Tint+1]=T[int+1,Tint-1]-2*Y*((T[int+1,Tint]-T[int-1,Tint])/2)
            else:
                T[int+1,Tint+1]=T[int+1,Tint-1]-2*Y*((T[int-2,Tint]-8*T[int-1,Tint]+8*T[int+1,Tint]-T[int+2,Tint])/(12))
```

```python
plt.figure(figsize=(12,5))
plt.subplot(121)
plt.plot(arange(0,n+1)*x_del,array(T),linewidth=0.05);plt.title('Propagation of a Sine wave')
plt.ylabel('$\eta$');plt.xlabel('x')
plt.subplot(122)
plt.contourf(arange(0,n+1)*x_del,arange(0,t_steps+1)*t_del,array(T).T,cmap=cm.RdBu_r)
plt.colorbar();plt.title('Signal over Time');plt.xlabel('x');plt.ylabel('Time')
plt.show()
```

By incrementally changing the accuracy (and thus order) of the schemes, it is apparent that the order of the time iteration scheme is much more important at driving dispersion and instabilities of the original signal. Increasing the spatial order serves to increase the dissipation of the signal, quickly spreading and muting the disturbance.

##1.4 - Evaluation of Amplification + Dispersion Relation

Amplitude error is evaluated from an entire scheme, while the dispersion (or phase error) is evaluated only from the spatial differencing scheme.

###Numerical Dissipation

For a Forward Euler method:
\begin{gather}
    \frac{\theta^{n+1} - \theta^{n}}{\Delta t} + c \left[ \frac{\theta^{n}_{i} - \theta^{n}_{i-1}}{\Delta x} \right] = 0 ~\text{for c > 0} \\
    \text{Take }\theta^{n} = \hat{\theta}^{n}e^{ikx} ~\text{assuming a wave form} \\
    \hat{\theta}^{n+1} = \hat{\theta}^{n} - \hat{\theta}^{n}c\frac{\Delta t}{\Delta x} \left[ e^{ikx} - e^{ik(x+\Delta x)} \right] \\
    \hat{\theta}^{n+1} = \hat{\theta}^{n} \left[ 1 - \lambda \left( 1 - e^{ik\Delta x} \right) \right] ~\text{where }\lambda = c\frac{\Delta t}{\Delta x} \\
    \frac{\hat{\theta}^{n+1}}{\hat{\theta}^{n}} = \left[ 1 - \lambda \left( 1 - \cos(k\Delta x) - i\sin(k\Delta x) \right) \right] \\
    \left[ \frac{\hat{\theta}^{n+1}}{\hat{\theta}^{n}} \right]^{2} = \left[ 1 - \lambda \left( 1 - \cos(k\Delta x) \right) \right]^{2} + \lambda^{2}\sin^{2}(k\Delta x) \\
    \text{Taking } k\Delta x = \frac{\pi}{2}: \\
    \left[ \frac{\hat{\theta}^{n+1}}{\hat{\theta}^{n}} \right]^{2} = \left[ 1 - \lambda \right]^{2} + \lambda^{2} \\
    \text{Therefore, for }\left[ \frac{\hat{\theta}^{n+1}}{\hat{\theta}^{n}} \right] \leq 1, \lambda = c\frac{\Delta t}{\Delta x} \leq 1
\end{gather}
Thus, we reach the CFL condition, required such that no waves outrun the explicit time-stepping method. Numerical dissipation occurs when $\lambda$ is less than one, as it effectively describes the change in amplitude of the signal from one time step to the next. Following a similar procedure for the leap-frog method:
\begin{gather}
    \frac{\theta^{n+1} - \theta^{n-1}}{2\Delta t} + c \left[ \frac{\theta^{n}_{i} - \theta^{n}_{i-1}}{\Delta x} \right] = 0 ~\text{for c > 0} \\
    \text{Thus, } \lambda = c\frac{2\Delta t}{\Delta x} \\
    \text{Jumping forward, we find} \\
    \left[ \frac{\hat{\theta}^{n+1} -\hat{\theta}^{n-1}}{\hat{\theta}^{n}} \right]^{2} = \lambda^{2} \left[1 - e^{ik\Delta x} + e^{2ik\Delta x} \right] \\
    \left[ \frac{\hat{\theta}^{n+1} -\hat{\theta}^{n-1}}{\hat{\theta}^{n}} \right]^{2} = \lambda^{2} \left[1 - (\cos(k\Delta x) + i\sin(k\Delta x)) + (\cos^{2}(k\Delta x) - \sin^{2}(k\Delta x)) \right] \\
    \text{If we now take } k\Delta x = \frac{\pi}{2}: \\
    \left[ \frac{\hat{\theta}^{n+1} -\hat{\theta}^{n-1}}{\hat{\theta}^{n}} \right]^{2} = \lambda^{2} \left[1 - 0 + (0 - 1) \right] = 0
\end{gather}
which we would hope, given that we wish the difference between signals to go to zero; thus, the leap-frog scheme is *not* dissipative, but can easily become unstable (as shown in the plot above). It's worth noting that these two examples utilize first order upwind spatial differencing schemes to look solely at the effects of different time-stepping methods.

###Numerical Dispersion

As numerical dispersion is primarily related to the spatial differencing scheme, the following examples examine the effects of the two schemes utilized above: the first order upwind scheme, and the fourth order centered-difference scheme. Beginning with the first order scheme:
\begin{gather}
    \frac{\delta \theta}{\delta t} + c \left[ \frac{\theta^{n}_{i} - \theta^{n}_{i-1}}{\Delta x} \right] = 0 ~\text{where we take } \theta^{n} = \hat{\theta}^{n}e^{i(kx-\omega t)} \\
    -i \omega + \frac{c}{\Delta x}\left[ 1 - e^{-ik\Delta x} \right] = 0 \\
    -i \omega = \frac{-c}{\Delta x}\left[ i\sin(k\Delta x) - \cos(k\Delta x) \right] \\
    \therefore \omega = \frac{c}{\Delta x}\sin(k\Delta x) \\
    \text{and } \frac{\delta \omega}{\delta k} = c_{g} = c \cos(k\Delta x)
\end{gather}
We will examine the dispersion relation below, but first I review the dispersion relation of the fourth-order centered-difference scheme:
\begin{gather}
    \frac{\delta \theta}{\delta t} + \frac{c}{12 \Delta x} \left[\theta^{n}_{x-2} - 8\theta^{n}_{x-1} + 8\theta^{n}_{x+1} - \theta^{n}_{x+2} \right] = 0 ~\text{where we take } \theta^{n} = \hat{\theta}^{n}e^{i(kx-\omega t)} \\
    -i \omega + \frac{c}{12\Delta x}\left[ e^{-2ik\Delta x} - 8e^{-ik\Delta x} +8e^{ik\Delta x} - e^{2ik\Delta x} \right] = 0 \\
    -i \omega = \frac{-c}{12\Delta x}\left[ -i\sin(2k\Delta x) + 8i\sin(k\Delta x) + 8i\sin(k\Delta x) - i\sin(2k\Delta x) \right] \\
    \therefore \omega = \frac{c}{6\Delta x} \left[8\sin(k\Delta x) - \sin(2k\Delta x) \right] \\
    \text{and } \frac{\delta \omega}{\delta k} = c_{g} = \frac{c}{3} \left[ 4\cos(k\Delta x) - \cos(2k\Delta x) \right]
\end{gather}

As inferred from the plot below, the fourth order scheme produces stable, nondispersive results for larger $\Delta x$ values than the first order scheme, which can roughly be seen in the close match between the fourth-order frequency and the linear ($\omega = ck$) frequency over larger wavenumbers.

```python
dx=1
c = 1.
k=arange(0,pi+0.1,pi/100.)

plt.figure(figsize=(6,6))
plt.plot(k,c*k)
plt.plot(k,(c/dx)*sin(k*dx))
plt.plot(k,(c/(6*dx))*(8*sin(k*dx) - sin(2*k*dx)))
plt.legend(['$\omega = ck$','1$^{st}$ Order Upwind Scheme','4$^{th}$ Order Centered-Difference'])
plt.xticks(arange(0,1.1,1/10.)*pi,['0','0.1$\pi$', '0.2$\pi$', '0.3$\pi$','0.4$\pi$','0.5$\pi$','0.6$\pi$','0.7$\pi$','0.8$\pi$','0.9$\pi$','$\pi$']);
plt.xlim((0,1*pi));plt.ylim((0,1*pi))
plt.ylabel('$\omega$ - Frequency');plt.xlabel('$k$ - Wave Number');plt.title('Numerical Dispersion Relation');
plt.show()

##Attempt to find points where C=C_g for each of the methods; did not look nice.
#k = pi/2.
#dx = arange(0,100+(1/50.),1/50.)+(1/50.)
#plt.figure(figsize=(6,6))
#plt.plot(dx,abs(((c/(dx*k))*(sin(k*dx)))/((c/(k))*(cos(k*dx)))))
#plt.plot(dx,abs(((c/(6*dx*k))*(8*sin(k*dx) - sin(2*k*dx)))/((c/(3*dx*k))*(4*cos(k*dx) - cos(2*k*dx)))))
#plt.legend(['1$^{st}$ Order Upwind','4$^{th}$ Order Centered']);plt.xlim((0,10));plt.ylim((0,10));
#plt.xlabel('$\delta x$');plt.ylabel('C/C$_{g}$')
#plt.show()

```

#2 - 2D Tracer Advection

We begin with a tracer advection equation of the form:
\begin{equation}
    \theta_{t} + {\bf u}\cdot\nabla\theta = S
\end{equation}
The tracer will be released within a 2D divergence-free velocity field, set up by creating a potential field, as shown below. As $\nabla^{2}\phi = 0$, we can solve for the velocity via $u = \phi_{x}$ and $v = \phi_{y}$, which will be divergence-free ($u_{x} + v_{y} =0$), and in this case produces cyclonic circulation about the center of the grid. I chose to provide an initial tracer distribution that is then advected through the grid, which has periodic boundaries in x and y. The periodicity should not create any issues, though, as the velocity distribution goes to zero at the boundaries.

To advect the tracer, I chose to implement a 1$^{st}$ order explicit Forward Euler scheme in time, and a 3$^{rd}$ order centered-difference scheme in space to calculate the fluxes of tracer into each grid cell; however, the differencing in space at the boundaries was adjusted to a 1$^{st}$ order scheme by necessity. Initially, I attempted to match a leap-frog scheme with the centered-difference spatial scheme, but found that the advection became unstable after $\sim$400 timesteps.

```python
#Provide user-defined inputs
dx = 0.2
dy = 0.2
n=100;
m=100;
t_steps = 1200;

x = arange(0,n+1)*dx
y = arange(0,m+1)*dy

#Create non-divergent velocity field
Phi = zeros((n+1,m+1))
for t in range(0,n+1):
    for t2 in range(0,m+1):
        Phi[t,t2]= 1 * exp(-(x[t] - median(x))**2  / (2*(max(x)/2))) * exp(-(y[t2] - median(y))**2  / (2*(max(y)/2)))
[v,u]=gradient(Phi,dx,dy); u= -u;

#Calculate Dt to avoid violating CFL condition
dt = (0.4*dx)/sqrt(nanmax(u)**2 + nanmax(v)**2)

#Initialize tracer distribution for advection
T=zeros((n+1,m+1))
for t in range(0,n+1):
    for t2 in range(0,m+1):
        T[t,t2]= 0.95 * exp(-(x[t] - 7)**2  / (2*2)) * exp(-(y[t2] - 7)**2  / (2*5))

#Plot Velocity field
plt.figure(figsize=(8,10));
plt.subplot(211)
plt.contourf(x,y,array(Phi).T,20,cmap=cm.RdBu_r)
plt.colorbar();plt.title('Streamfunction');plt.xlabel('x');plt.ylabel('y')
plt.subplot(223)
plt.contourf(x,y,array(u).T,20,cmap=cm.RdBu_r)
plt.colorbar();plt.title('U');plt.xlabel('x');plt.ylabel('y')
plt.subplot(224)
plt.contourf(x,y,array(v).T,20,cmap=cm.RdBu_r)
plt.colorbar();plt.title('V');plt.xlabel('x');plt.ylabel('y')
plt.show()
```

The above streamfunction, and corresponding velocity fields are now used to advect our tracer through the grid, following the specifications described above.

```python
#Application of Advection scheme: Finite Volume + Flux form of equations

upos=[]; uneg=[];
vpos=[]; vneg=[];

T_old=zeros(shape(T))
T_new=zeros(shape(T))
T_old[:]=T[:]

#Begin time-stepping
for t in range(0,t_steps):

    upos[:] = 0.5*(u[:]+abs(u[:])); upos = array(upos)
    vpos[:] = 0.5*(v[:]+abs(v[:])); vpos = array(vpos)
    uneg[:] = 0.5*(u[:]-abs(u[:])); uneg = array(uneg)
    vneg[:] = 0.5*(v[:]-abs(v[:])); vneg = array(vneg)

    Dx=zeros((n+1,m+1))
    for i in range(0,n-1):
        Dx[i,:]=(T[i+1,:]-T[i,:])
    Dx[n,:]=(T[0,:]-T[n,:]) #Provide cyclical boundary conditions

    Dy=zeros((n+1,m+1))
    for i in range(0,m-1):
        Dy[:,i]=(T[:,i+1]-T[:,i])
    Dy[:,m]=(T[:,0]-T[:,m]) #Provide cyclical boundary conditions

    #Calculate Fluxes (using QUICK)
    fx=zeros((n+1,m+1))
    for i in range(0,n):
        if i==0 or i==n-1:
            fx[i,:] = 0.5*(T[i,:]+ T[i+1,:])* u[i,:]
        else:
            fx[i,:] = u[i,:]*0.5*(T[i+1,:]+T[i,:]) -0.125*upos[i,:]*(Dx[i,:]- Dx[i-1,:]) -0.125*uneg[i,:]*(Dx[i+1,:]-Dx[i,:]); #3rd Order
    fy=zeros((n+1,m+1))
    for i in range(0,m):
        if i==0 or i==m-1:
            fy[:,i] = 0.5*(T[:,i]+ T[:,i+1])* v[:,i]
        else:
            fy[:,i] = v[:,i]*0.5*(T[:,i+1]+T[:,i]) -0.125*vpos[:,i]*(Dy[:,i]- Dy[:,i-1]) -0.125*vneg[:,i]*(Dy[:,i+1]-Dy[:,i]); #3rd Order

    #Adjust to be a leapfrog scheme after the first timestep
    if t==0:
        C=1
    else: C=1

    for i in range(1,n):
        for j in range(1,m):
            T_new[i,j] = T[i,j] - C*dt*(-fx[i-1,j]+fx[i,j]-fy[i,j-1]+fy[i,j])

    #Boundary
    T_new[n,:]=T_new[n-1,:]
    T_new[0,:]=T_new[1,:]
    T_new[:,m]=T_new[:,m-1]
    T_new[:,0]=T_new[:,1]

    T_old[:]=T[:]
    T[:]=T_new[:]

    if t%100 == 0:
        plt.figure(figsize=(10,4))
        plt.subplot(121);plt.pcolor(x,y,T.T,cmap=cm.RdBu_r)
        plt.clim([-1.2,1.2]);plt.plot(x,ones(shape(x))*(50*dy),linewidth=0.5,color='k')
        plt.colorbar();plt.title('Tracer Distribution at time '+str(round(t*dt)))
        plt.xlabel('x');plt.ylabel('y')
        plt.subplot(122);plt.plot(x,T[:,50].T)
        plt.title('Tracer Distribution across y = '+str(50*dy))
        plt.ylabel('Concentration');plt.xlabel('x')
        plt.show()
```

##Conclusions / Reflections

The advection of the tracer by the explicit Forward Euler scheme produces smooth, stable results. I believe the apparent advection of tracer toward the center of the grid is the result of the initial tracer distribution as opposed to an inward drift of tracer (which would imply that the initial velocity field is in fact divergent).

It was quite difficult to convert the one dimensional advection problem into two dimensions, especially when beginning to think about the equations in flux form, which took some time sorting out in the code. However, once the code was written properly, it is much easier to see and understand the physics at the basis of the equations. This assignment has provided a nice step into two-dimensional modelling, especially as the advection of a tracer and coupled equations are almost a necessity when modelling estuarine topics of interest.


```
%matplotlib inline
```

Given the coin-tossing experiment with unknown parameter $p$. The individual coin-flips are Bernoulli distributed. As we discussed previously, the estimator for $p$ is the following:

$ \hat{p}   = \frac{1}{n} \sum_{i=1}^n X_i$

We have to separate hypotheses: $H_0$ is the so-called null hypothesis. In our case this can be

$$ H_0 : p \lt \frac{1}{2}$$

and the alternative hypothesis is then

$$ H_1 : p \ge \frac{1}{2}$$

To choose between these, we need a statistical test that is a function, $G$, of the sample set
$\mathbf{X}_n=\left\{ X_i \right\}_n $. This test will have a threshold, something like

$$ G(\mathbf{X}_n) < c \implies H_0 $$

and otherwise choose $H_1$. To sum up at this point, we have the observed data $\mathbf{X}_n$ and a function $G$ that will somehow map that data onto the real line. Then, using the constant $c$ as a threshold, the inequality effectively divides the real line into two parts, one corresponding to each of the hypotheses.

Whatever this test $G$ is, it will make mistakes of two types -- false negatives and false positives. The false positives arise from the case where we declare $H_0$ when the test says we should declare $H_1$. Here are the false positives (i.e. false alarm):

$$ \mathbb{P}_{FA} = \mathbb{P}( G(\mathbf{X}_n) \gt c \vert p \le \frac{1}{2})$$

Or, alternatively,

$$ \mathbb{P}_{FA} = \mathbb{P}( G(\mathbf{X}_n) \gt c \vert H_0)$$

Likewise, the other error is a false negative, which we can write analogously as

$$ \mathbb{P}_{FN} = \mathbb{P}( G(\mathbf{X}_n) \lt c \vert H_1)$$

Thus, by choosing some acceptable values for either of these errors, we can solve for the other one. Later we will see that this is because these two quantities are co-dependent. The practice is usually to pick a value of $ \mathbb{P}_{FA}$ and then find the corresponding value of $ \mathbb{P}_{FN}$. Note that it is traditional to speak about *detection probability*, which is defined as

$$ \mathbb{P}_{D} = 1- \mathbb{P}_{FN} = \mathbb{P}( G(\mathbf{X}_n) \gt c \vert H_1)$$

In other words, this is the probability of declaring $H_1$ when the test exceeds the threshold. This is Otherwise known as the *probability of a true detection*.

## Back to the coin flipping example

In our previous problem, we wanted to derive an estimator for the underlying probability for the coin flipping experiment. For this example, we want to ask a softer question: is the underlying probability greater or less than 1/2. So, this leads to the two following hypotheses:

$$ H_0 : p \lt \frac{1}{2}$$

versus,

$$ H_1 : p \ge \frac{1}{2}$$
5
Let's suppose we want to determine is based upon five observations. Now the only ingredient we're missing is the $G$ function and a way to pick between the two hypotheses. Out of the clear blue sky, let's pick the number of heads observed in the sequence of five observations. Thus, we have

$$ G (\mathbf{X}_5) := \sum_{i=1}^5 X_i$$

and, suppose further that we pick $H_1$  only if exactly five out of five observations are heads. We'll call this the *all-heads* test.

Now, because all of the $X_i$ are random variables, so is $G$. Now, to find the corresponding probability mass function for this.  As usual, assuming independence, the probability of five heads is $p^5$. This means that the probability of rejecting the $H_0$ hypothesis (and choosing $H_1$, because there are only two choices here) based on the unknown underlying probability is $p^5$. In the parlance, this is known and the *power function* as in denoted by $\beta$ as in

$$ \beta(\theta) = \theta^5 $$

where I'm using the standard $\theta$ symbol to represent the underlying parameter ($p$ in this case). Let's get a quick plot this below.

```
px = linspace(0,1,50)
plot( px, px**5)
xlabel(r'$\theta$',fontsize=18)
ylabel(r'$\beta$',fontsize=18)
```

Now, to calculate the

$$ \mathbb{P}_{FA} = \mathbb{P}( G(\mathbf{X}_n)= 5 \vert H_0)$$

or, in other words,

$$ \mathbb{P}_{FA}(\theta) = \mathbb{P}( \theta^5 \vert H_0)$$

Notice that this is a function of $\theta$, which means there are many false alarm probability values that correspond to this test. To be on the conservative side, we'll pick the maximum (or, supremum if there are limits involved) of this function, which is known as the *size* of the test, traditionally denoted by $\alpha$.

$$ \alpha = \sup_{\theta \in \Theta_0} \beta(\theta) $$

which in our case is

$$ \alpha = \sup_{\theta < \frac{1}{2}} \theta^5 = (\frac{1}{2})^5 = 0.03125$$

which is a nice, low value. Likewise, for the detection probability,

$$ \mathbb{P}_{D}(\theta) = \mathbb{P}( \theta^5 \vert H_1)$$

which is again a function of the parameter $\theta$. The problem with this test is that the $\mathbb{P}_{D}$ is pretty low for most of the domain of $\theta$. Useful values for $\mathbb{P}_{D}$ are usually in the nineties, which only happens here when $\theta \gt 0.98$. Ideally, we want a test that is zero for the domain corresponding to $H_0$ (i.e. $\Theta_0$) and equal to one otherwise. Unfortunately, even if we increase the length of the observed sequence, we cannot escape this effect with this test. You can try plotting $\theta^n$ for larger and larger values of $n$ to see this.

### Majority Vote Test

Due to the problems with the detection probability we uncovered in the last example, maybe we can think of another test that will have the performance we want. Suppose we reject $H_0$ if the majority (i.e. more than half) of the observations are heads. Then, using the same reasoning as above, we have

$$ \beta(\theta) = \sum_{k=3}^5 \binom{5}{k} \theta^k(1-\theta)^{5-k} $$

Using some tools from `sympy`, we can plot this out and compare it to the previous case as in the cell below.

```
from sympy.abc import p,k # get some variable symbols
import sympy as S

expr=S.Sum(S.binomial(5,k)*p**(k)*(1-p)**(5-k),(k,3,5)).doit()
p0=S.plot(p**5,(p,0,1),xlabel='p',show=False,line_color='b')
p1=S.plot(expr,(p,0,1),xlabel='p',show=False,line_color='r',legend=True,ylim=(0,1.5))
p1.append(p0[0])
p1.show()
```

In this case, the new test has *size*

$$ \alpha = \sup_{\theta < \frac{1}{2}} \theta^{5} + 5 \theta^{4} \left(- \theta + 1\right) + 10 \theta^{3} \left(- \theta + 1\right)^{2}
 = (\frac{1}{2})^5 = 0.5 $$

which is a pretty bad false alarm probability. Values less that one percent are usually considered acceptable. As before we only get to upwards of 90% for detection probability only when the underlying parameter $\theta > 0.75$. As before, this is not so good. Let's run some simulations to see how this plays out.

### All-heads Test

```
# implement 1st test where all five must be heads to declare underlying parameter > 0.5
from scipy import stats
b = stats.bernoulli(0.8) # true parameter is 0.8 (i.e. hypothesis H_1)
samples = b.rvs(1000).reshape(-1,5) # -1 means let numpy figure out the other dimension i.e. (200,5)
print 'prob of detection = %0.3f'%mean(samples.sum(axis=1)==5) # approx 0.8**3
```

    prob of detection = 0.310

```
# here's the false alarm case
b = stats.bernoulli(0.3) # true parameter is 0.3 (i.e. hypothesis H_0)
samples = b.rvs(1000).reshape(-1,5)
print 'prob of false alarm = %0.3f'%mean(samples.sum(axis=1)==5)
```

    prob of false alarm = 0.000

The above two cells shows that the false alarm probability is great, but the detection probability is poor. Let's try the same simulation for the majority vote test.

### Majority Vote Test

```
# implement 1st test where all five must be heads to declare underlying parameter > 0.5
b = stats.bernoulli(0.8) # true paramater is 0.8 (i.e. hypothesis H_1)
samples = b.rvs(1000).reshape(-1,5)
print 'prob of detection = %0.3f'%mean(samples.sum(axis=1)>=3)
```

    prob of detection = 0.945

```
# here's the false alarm case
b = stats.bernoulli(0.3) # true parameter is 0.3 which means it's hypothesis H_0
samples = b.rvs(1000).reshape(-1,5)
print 'prob of false alarm = %0.3f'%mean(samples.sum(axis=1)>=3)
```

    prob of false alarm = 0.140

Both of the simulation results follow our earlier analysis. Try tweaking the underlying parameter (i.e. 0.8, 0.3) here and see how the simulation reacts. Not surprisingly, the majority vote test does better when the underlying parameter is much greater than 0.5

## Summary

Another way to think about how the test is working is to plot the so-called *Receiver Operating Characteristic* (ROC), which is a plot of the $\mathbb{P}_D$ versus the $\mathbb{P}_{FA}$. We can draft this quickly using the *size* of the test instead of the $\mathbb{P}_{FA}$.