###### Material desenvolvido para o minicurso: Introdução à solução numérica de EDP's, ministrado no ERMAC/2018 de 5 a 6 de abril de 2018 na Universidade Federal de Lavras, Lavras/MG, Brasil. Autor: [Jonas Laerte Ansoni](http://jonasansoni.blogspot.com.br/).

#  Minicurso: Introdução à solução numérica de EDP's

##  Equação parabólica

### Equação transiente de difusão de calor
######  Olhar Fortuna página 113

A equação clássica para problemas parabólicos é a equação transiente de difusão de calor:

\begin{equation}
\frac{\partial T}{\partial t} = \alpha \frac{\partial^2 T}{\partial x^2}
\tag{1}
\end{equation}

onde, $\alpha$ é o ceficiente de difusividade térmica (propriedade do material), e $T$ é a temperatura.

Na lição anterior, vimos a solução numérica da equação de difusão 1D com um esquema progressivo no temo e centrado no espaço: um esquema _explícito_. O que isso significa?

A solução para $T$ em timestep $t^{n + 1}$ foi calculada usando diferentes combinações de valores de $T$ do timestep *anterior* $t^n$. Temos conhecimento completo das partes que alimentam a atualização da solução em cada ponto espacial.

*Os métodos implícitos* funcionam de maneira diferente: usaremos mais dados do "futuro" na atualização, incluindo vários valores de $T$ em $t^{n + 1}$. Isso tornará o esquema mais difícil de se aplicar, mas existem várias razões pelas quais pode valer a pena o esforço.

Na lição anterior, discutimos duas desvantagens dos métodos explícitos: (1) efeitos de fronteira arrastados para trás por um passo de tempo; (2) os requisitos de estabilidade restringem o intervalo de tempo a valores muito pequenos. Ambas as questões são resolvidas por esquemas implícitos.

#### 3.2.3 Esquemas implícitos

Ao contrário das discretizações explícitas, nos métodos implícitos as derivadas espaciais são discretizadas no nível de tempo $n+1$. Como exemplo, ao discretizarmos a derivada espacial por diferenças centrais de segunda ordem e a derivada temporal por diferenças atrasadas de primeira ordem temos:

\begin{equation}
\label{eq1}
\frac{T_{i}^{n+1}-T_{i}^{n}}{\Delta t}=\alpha\frac{T_{i+1}^{n+1}-2T_{i}^{n+1}+T_{i-1}^{n+1}}{\Delta x^2}+{\mathcal O}[(\Delta t),(\Delta x)^2]
\tag{1}
\end{equation}

O estêncil para esta discretização não se parece em nada com os outros estênceis que usamos até agora. Confira.

####  Figure 1. Estêncil para o esquema implícito da Eq. \ref{eq1}.

Do passo no tempo anterior, conhecemos apenas $T_i^{n}$, mas os outros termos $T_i^{n+1}$, $T_{i-1}^{n+1}$ and $T_{i+1}^{n+1}$?  Como podemos calcular?

Não precisa entrar em pânico! Vamos começar colocando o que *conhecemos* no lado direito da equação e o que *não conhecemos* à esquerda. Nós temos então:

\begin{equation} -T_{i-1}^{n+1} + \left( 2 + \frac{\Delta x^2}{\alpha\Delta t}\right) T_{i}^{n+1} - T_{i+1}^{n+1} = T_{i}^{n}\frac{\Delta x^2}{\alpha\Delta t} \end{equation}

Parece que há muitas incógnitas e apenas uma equação!

Com o que se parece com $i = 1$?

\begin{equation}-T_{0}^{n+1} + \left( 2 + \frac{\Delta x^2}{\alpha\Delta t}\right) T_{1}^{n+1} - T_{2}^{n+1} = T_{1}^{n}\frac{\Delta x^2}{\alpha\Delta t} \end{equation}

e $i=2$?

\begin{equation}-T_{1}^{n+1} + \left( 2 + \frac{\Delta x^2}{\alpha\Delta t}\right) T_{2}^{n+1} - T_{3}^{n+1} = T_{2}^{n}\frac{\Delta x^2}{\alpha\Delta t} \end{equation}

e sobre $i=3$?

\begin{equation}-T_{2}^{n+1} + \left( 2 + \frac{\Delta x^2}{\alpha\Delta t}\right) T_{3}^{n+1} - T_{4}^{n+1} = T_{3}^{n}\frac{\Delta x^2}{\alpha\Delta t} \end{equation}

Você consegue ver o elemento comum entre as equações? Aqui está uma pequena ajuda:

$T_{i}^{n + 1}$ também aparece na equação de $T_{i-1}^{n + 1}$ e $T_{i + 1}^{n + 1}$. Podemos ter equações suficientes se aplicarmos isso para todos os valores $i$ ao mesmo tempo, você não acha? Na verdade, este é um sistema linear de equações para os valores desconhecidos $T_{i}^{n + 1}$ na malha espacial.

#### E as condições de contorno?

Vejamos os pontos nas fronteiras do exemplo da lição anterior com Dirichlet em $x = 0$ e Neumann em $x = 1$, discretizando com $N$ pontos de malha.

O valor $T_0^{n + 1}$ é conhecido a cada passo de tempo do BC, então colocando todos os termos desconhecidos no lado esquerdo da equação e os valores conhecidos no lado direito geram o seguinte para $i = 1$:

\begin{equation}-T_{2}^{n+1} + \left( 2 + \frac{\Delta x^2}{\alpha\Delta t}\right) T_{1}^{n+1} = T_{1}^{n}\frac{\Delta x^2}{\alpha\Delta t} + T_{0}^{n+1} \end{equation}

Essa foi fácil!

Por outro lado, para $i = N-2$, a equação diz

\begin{equation} -T_{N-3}^{n+1} + \left( 2 + \frac{\Delta x^2}{\alpha\Delta t}\right) T_{N-2}^{n+1} - T_{N-1}^{n+1} = T_{N-2}^{n}\frac{\Delta x^2}{\alpha\Delta t} \end{equation}

A condição de fronteira de Neumann discretizada no lado direito da haste é

\begin{equation}\frac{T^{n}_{N-1} - T^{n}_{N-2}}{\Delta x} = q.\end{equation}

Mas podemos facilmente escrever que no passo de tempo $n + 1$ (as condições de contorno se aplicam a cada passo de tempo):

\begin{equation}\frac{T^{n+1}_{N-1} - T^{n+1}_{N-2}}{\Delta x} = q.\end{equation}

Inserindo a condição de fronteira de Neumann na equação para $i = N-2$

\begin{equation} - T_{N-3}^{n+1} + \left( 1 + \frac{\Delta x^2}{\alpha\Delta t}\right) T_{N-2}^{n+1} = T_{N-2}^{n}\frac{\Delta x^2}{\alpha\Delta t} + \Delta x q \end{equation}

Certifique-se de trabalhar com caneta e papel: é importante reconhecer de onde vêm esses termos!

Esse conjunto de equações pode ser escrito no forma matricial

$$[A][x] = [b]+[b]_{b.c.}$$

onde a matriz de coeficientes $[A]$ é uma matriz esparsa - a maioria dos elementos da matriz é zero - com três diagonais diferentes de zero. Nós escrevemos abaixo o sistema expandido, assim você pode ver a estrutura da matriz, com $\sigma = \frac{\alpha \Delta t}{\Delta x^2}$:

\begin{align}\left[ \begin{array}{cccccc}
 \left(2 + \frac{1}{\sigma}\right) & -1 & 0 & \cdots & & 0 \\
 -1 & \left(2 + \frac{1}{\sigma}\right) & -1 & 0 & \cdots & 0 \\
 0 & & \ddots& & & \vdots \\
 \vdots & & & & \left(2 + \frac{1}{\sigma}\right)& \\
 0 & \cdots & & & -1 & \left(1 + \frac{1}{\sigma}\right) \end{array} \right]
 \cdot
 \left[ \begin{array}{c}
 T_1^{n+1} \\ T_2^{n+1} \\ \vdots \\ \\ T_{N-2}^{n+1} \end{array} \right]
 =
 \left[ \begin{array}{c}
T_1^n \frac{1}{\sigma} \\ T_2^{n}\frac{1}{\sigma} \\ \vdots \\ \\ T_{N-2}^{n}\frac{1}{\sigma} \end{array} \right]
 +
 \begin{bmatrix}
  T_0^{n+1}\\
  0\\\\
  \vdots\\\\
  0\\
  q\Delta x
 \end{bmatrix}
 \end{align}

O sistema é formado por equações linearmente independentes e possui o mesmo número de incógnitas, admitindo, portanto, uma única solução.

Aqui podemos notar uma grande diferença entre métodos explícitos e implícitos. No primeiro, as equações resultados são independentes, permitindo a solução direta. Já no método implícito, elas são acopladas, exigindo a resolução de um sistema de equações a cada passo no tempo.

#### Problema de aplicação

Vamos reutilizar o problema anterior: temos uma haste de grafite com [difusividade térmica](http://en.wikipedia.org/wiki/Thermal_diffusivity) $\alpha = 1,22\times10^{- 3}{\rm m}^2/{\rm s}$, comprimento $L = 1{\rm m}$ e temperatura mantida em $T = 100{\rm C}$ no lado esquerdo, $x = 0$, e $0{\rm C}$ em qualquer outro lugar inicialmente. Nós calcularemos a evolução da temperatura no comprimento da haste.

Vamos começar como fizemos na lição anterior: importe suas bibliotecas e configure a discretização.

```python
import numpy
from matplotlib import pyplot
%matplotlib inline
from matplotlib import rcParams
rcParams['font.family'] = 'serif'
rcParams['font.size'] = 16
```

```python
L = 1.
nt = 100
nx = 51
alpha = 1.22e-3

q = 0.

dx = L/(nx-1)

qdx = q*dx

Ti = numpy.zeros(nx)
Ti[0] = 100
```

### Resolvendo o sistema linear

Precisamos resolver o sistema linear de equações escritas acima para avançar a solução no tempo. Felizmente, podemos confiar em nossos amigos da SciPy que desenvolveram bons solucionadores lineares, portanto não precisamos escrever os nossos.

A partir do `scipy.linalg`, vamos importar um `solve`: uma função para resolver sistemas lineares. Certifique-se de explorar a documentação do [`scipy.linalg`](http://docs.scipy.org/doc/scipy-0.14.0/reference/linalg.html). Precisamos definir nossas próprias funções personalizadas para gerar a matriz de coeficientes e o lado direito do sistema linear. Você deve estudar cuidadosamente o código abaixo.

```python
from scipy.linalg import solve
```

```python
def generateMatrix(N, sigma):
    """ Computes the matrix for the diffusion equation with backward Euler
        Dirichlet condition at i=0, Neumann at i=-1

    Parameters:
    ----------
    N: int
        number of spatial grids
    sigma: float
        alpha*dt/dx^2

    Returns:
    -------
    A: 2D numpy array of float
        Matrix for diffusion equation
    """

    # Setup the diagonal
    d = numpy.diag(numpy.ones(N-2)*(2+1./sigma))

    # Consider Neumann BC
    d[-1,-1] = 1+1./sigma

    # Setup upper diagonal
    ud = numpy.diag(numpy.ones(N-3)*-1, 1)

    # Setup lower diagonal
    ld = numpy.diag(numpy.ones(N-3)*-1, -1)

    A = d + ud + ld

    return A
```

```python
def generateRHS(T, sigma, qdx):
    """ Computes right-hand side of linear system for diffusion equation
        with backward Euler

    Parameters:
    ----------
    T: array of float
        Temperature at current time step
    sigma: float
        alpha*dt/dx^2
    qdx: float
        flux at right boundary * dx

    Returns:
    -------
    b: array of float
        Right-hand side of diffusion equation with backward Euler
    """

    b = T[1:-1]*1./sigma
    # Consider Dirichlet BC
    b[0] += T[0]
    # Consider Neumann BC
    b[-1] += qdx

    return b
```

Em seguida, vamos definir uma função de passos no tempo o esquema implícito desenvolvido acima. Lembre-se de que, para um método implícito, é executado um passo no tempo resolvendo todo o sistema linear. Esta é uma diferença fundamental entre métodos implícitos e explícitos, e implica um custo computacional considerável.

Estamos todos prontos para executar! Primeiro, vamos usar um passo de tempo `dt` que satisfaz a restrição de estabilidade.

```python
def implicit_btcs(T, A, nt, sigma, qdx):
    """ Advances diffusion equation in time with implicit central scheme

    Parameters:
    ----------
    T: array of float
        initial temperature profile
    A: 2D array of float
        Matrix with discretized diffusion equation
    nt: int
        number of time steps
    sigma: float
        alpha*dt/dx^2

    qdx: float
        flux at right boundary * dx
    Returns:
    -------
    T: array of floats
        temperature profile after nt time steps
    """

    for t in range(nt):
        Tn = T.copy()
        b = generateRHS(Tn, sigma, qdx)
        # Use scipy.linalg.solve
        T_interior = solve(A,b)
        T[1:-1] = T_interior
        # Enforce Neumann BC (Dirichlet is enforced automatically)
        T[-1] = T[-2] + qdx

    return T
```

Nós resolvemos o sistema linear para cada etapa de tempo, mas a matriz $A$ não muda. Assim, você pode gerá-lo apenas uma vez e usá-lo como um parâmetro de entrada para a função de escalonamento de tempo. Vamos tentar isso!

```python
sigma = 0.5
dt = sigma * dx*dx/alpha
nt = 1000

A = generateMatrix(nx, sigma)
```

É sempre uma boa ideia checar sua matriz ...

```python
print(A)
```

    [[ 4. -1.  0. ...  0.  0.  0.]
     [-1.  4. -1. ...  0.  0.  0.]
     [ 0. -1.  4. ...  0.  0.  0.]
     ...
     [ 0.  0.  0. ...  4. -1.  0.]
     [ 0.  0.  0. ... -1.  4. -1.]
     [ 0.  0.  0. ...  0. -1.  3.]]

Parece bom!

Agora, aplicamos a iteração de tempo com nossa própria função `implicit_btcs ()`.

```python
T = implicit_btcs(Ti.copy(), A, nt, sigma, qdx)
```

Agora plote a solução!!

```python
pyplot.plot(numpy.linspace(0,1,nx), T, color='#003366', ls='-', lw=3);
```

Não muito impressionante, isso se parece com o resultado do caso *explícito* progressivo no tempo, centralizado no espaço para $\alpha\frac{\Delta t}{\Delta x^2}=\frac{1}{2}$.

Mas tente $\alpha\frac{\Delta t}{\Delta x^2}=5$, o que viola a condição de estabilidade do esquema *explícito*:

```python
sigma = 5.0

A = generateMatrix(nx, sigma)

T = implicit_btcs(Ti.copy(), A, nt, sigma, qdx)

pyplot.plot(numpy.linspace(0,1,nx), T, color='#003366', ls='-', lw=3);
```

**Não explodiu!**

Nós não fomos capazes de usar um passo de tempo tão grande com o esquema explícito. Você pode experimentar outros valores de `sigma` e obterá uma solução estável. Na verdade, esse é um esquema *incondicionalmente estável* - o recurso mais valioso dos métodos implícitos é que eles fornecem soluções estáveis sem uma restrição na escolha do passo de tempo.

Usando o esquema implícito, sempre podemos avançar no tempo usando passos de tempo maiores. Mas cada etapa de tempo requer a solução de um sistema linear, que é computacionalmente caro. Esse é o __trade-off__ entre métodos explícitos e implícitos.

  __Atenção!!__

  Métodos implícitos permitem que você use passos de tempo significativamente maiores, porque eles não estão sujeitos a restrições de estabilidade. Mas isso não significa que você pode usar apenas um grande intervalo de tempo! Lembre-se de que o método de Euler é um método de primeira ordem, então a _precisão_ fica pior à medida que você aumenta o intervalo de tempo, em proporção direta. Na verdade, você pode perder a capacidade de capturar a física correta se seu intervalo de tempo for muito grande. Estabilidade numérica não implica precisão!

#### Dig deeper

Você vê como a matriz 'A' está quase cheia de zeros? Chamamos essa matriz de *esparsa* e há muitas maneiras de fazer cálculos mais eficientes aproveitando sua estrutura específica. Primeiro de tudo, você pode otimizar o uso da memória. Confira os [formatos de armazenamento de matriz esparsa] do SciPy (http://docs.scipy.org/doc/scipy-0.14.0/reference/sparse.html): você não precisa de muito armazenamento $(N-2)^2$ elementos! Por exemplo, um formato `coo_matrix` armazena apenas $3*N_\text{nonzero}$, em que $N_\text{nonzero}$ é o número de elementos diferentes de zero em `A`. Certifique-se de explorar este tópico um pouco mais. É um tópico importante em PDEs numéricos.

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


# Chempy example notebook
##### Credit goes to CHEMPY authors MOST OF THIS NOTEBOOK IS ADAPTED FROM THEIR EXAMPLES

First we are going to import the chempy package, see the install instructions on the chempy github, and use the "chempy._ _version_" as a sanity check for installing the correct version. Make sure to install the optional dependencies for the chempy package because other useful packages such as thermo,sympy, etc are useful for ode solving, thermodynamic constants, etc.
__This is to remind myself that I am in the virtual env kittens where I installed chempy__

#### MOST OF THE RATE CONSTANTS VALUES ARE ARBITRARY. will need to ask elizabeth for more clarification, but this notebook is a good start to figuring out the kinetic modelling

```python
import chempy
import numpy as np
import sympy as sp
sp.init_printing()
import matplotlib.pyplot as plt
%matplotlib inline
sp.__version__, chempy.__version__
```

    ('1.4', '0.7.8')

We will first go through an example of balancing stoichiometry and mass fraction calculations that comes with the chempy package

```python
# import classes
from chempy import balance_stoichiometry
from pprint import pprint
from chempy import mass_fractions
```

```python
reactants, products = balance_stoichiometry({'HCOOH'},{'H2','CO2'})#,'CO','H2O'})
```

```python
# Creates an ordered dictionary object with the reactants
reactants
```

    OrderedDict([('HCOOH', 1)])

```python
products
```

    OrderedDict([('CO2', 1), ('H2', 1)])

```python
# Creates a dictionary object with the stoichiometric constants as the items and the reactants as the keys
pprint(dict(reactants))

```

    {'HCOOH': 1}

```python
pprint(dict(products))
```

    {'CO2': 1, 'H2': 1}

```python
mass_fractions
```

    <function chempy.chemistry.mass_fractions(stoichiometries, substances=None, substance_factory=<bound method Substance.from_formula of <class 'chempy.chemistry.Substance'>>)>

```python
# Calculates the weight percents based on the mass
for fractions in map(mass_fractions, [reactants, products]):
    pprint({k: '{0:.3g} wt%'.format(v*100) for k, v in fractions.items()})
    # .3g is for the number of sig figs
```

    {'HCOOH': '100 wt%'}
    {'CO2': '95.6 wt%', 'H2': '4.38 wt%'}

```python
reaction, product = balance_stoichiometry({'HCOOH'},{'CO','H2O'})
for fractions in map(mass_fractions, [reaction, product]):
    pprint({k: '{0:.3g} wt%'.format(v*100) for k, v in fractions.items()})
    # .4g is for the number of sig figs
```

    {'HCOOH': '100 wt%'}
    {'CO': '60.9 wt%', 'H2O': '39.1 wt%'}

## Reaction Chemical Kinetics

```python
# Import packages
from chempy import ReactionSystem
from chempy.kinetics.ode import get_odesys
from collections import defaultdict
import numpy as np
import matplotlib.pyplot as plt
```

```python
# Reaction System from string
# The rate constants below are arbitrary
rsys = ReactionSystem.from_string("""
HCOOH -> H2 + CO2; 42
HCOOH -> H2O + CO; 17
""")  # "[H2O]" = 1.0 (actually 55.4 at RT)

# Ordinary Differential Systems of Equations
odesys, extra = get_odesys(rsys)
# Time axis
tout = sorted(np.concatenate((np.linspace(0, 23), np.logspace(-8, 1))))
# Initial Concentrations
c0 = defaultdict(float, {'HCOOH': 1.00, 'H2': 0.00, 'CO2': 0.000, 'H2O': 0.00, 'CO': 0.00})
# Integrated ode system
result = odesys.integrate(tout, c0, atol=1e-12, rtol=1e-14)
# Plotting the results
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
for ax in axes:
    result.plot(names=[k for k in rsys.substances if k != 'H2O'], ax=ax)
    ax.legend(loc='best', prop={'size': 9})
    ax.set_xlabel('Time')
    ax.set_ylabel('Concentration')
axes[1].set_ylim([1e-13, 1e-1])
axes[1].set_xscale('log')
axes[1].set_yscale('log')
fig.tight_layout()
#fig.savefig('examples/kinetics.png', dpi=72)
```

```python
rsys
```

HCOOH	→	CO2 + H2	 42

HCOOH	→	CO + H2O	 17

```python
odesys
```

    <pyodesys.symbolic.SymbolicSys at 0x21f64e9ad30>

```python
extra
```

    {'param_keys': [],
     'unique': OrderedDict(),
     'p_units': None,
     'max_euler_step_cb': <function chempy.kinetics.ode.get_odesys.<locals>.max_euler_step_cb(x, y, p=())>,
     'linear_dependencies': <function chempy.kinetics.ode.get_odesys.<locals>.linear_dependencies(preferred=None)>,
     'rate_exprs_cb': <pyodesys.util._Callback at 0x21f64e8bc88>,
     'cstr_fr_fc': False,
     'unit_registry': None}

# Example kinetic model with 2 sets of products

```python
 # EXAMPLE KINETIC MODEL
from __future__ import absolute_import, division, print_function
from collections import defaultdict
from ipywidgets import interact
import matplotlib.pyplot as plt
from chempy import Reaction, Substance, ReactionSystem
from chempy.kinetics.ode import get_odesys
from chempy.kinetics.analysis import plot_reaction_contributions
from chempy.printing.tables import UnimolecularTable, BimolecularTable
from chempy.util.graph import rsys2graph
import sympy
sympy.init_printing()
%matplotlib inline
```

```python
# Substance ??
A, B, C, D, E, F= map(Substance, 'ABCDEF')
# Sympy ??
One = sympy.S.One
# Reactions object
reactions = r0, r1 = [
    Reaction({'A'}, {'B', 'C'}, 4*One/100, name='R1: A cons.'),
    Reaction({'A'}, {'D', 'E'}, 3*One/100, name='R2: A cons.'),
]
# Reaction system
rsys = ReactionSystem(reactions, (A, B, C, D, E))
# UnimolecularTable and BimolecularTable from Reaction system
uni, not_uni = UnimolecularTable.from_ReactionSystem(rsys)
bi, not_bi = BimolecularTable.from_ReactionSystem(rsys)
assert not (not_uni & not_bi), "There are only uni- & bi-molecular reactions in this set"
# Ordinary Differential Systems of Equations
odesys, extra = get_odesys(rsys, include_params=True)
odesys.exprs
```

```python
# Jacobian matrix of Ordinary Differential Systems of Equations
odesys.get_jac()
```

$\displaystyle \left[\begin{matrix}- \frac{7}{100} & 0 & 0 & 0 & 0\\\frac{1}{25} & 0 & 0 & 0 & 0\\\frac{1}{25} & 0 & 0 & 0 & 0\\\frac{3}{100} & 0 & 0 & 0 & 0\\\frac{3}{100} & 0 & 0 & 0 & 0\end{matrix}\right]$

```python
reactions
```

    [<chempy.chemistry.Reaction at 0x21f6892f7b8>,
     <chempy.chemistry.Reaction at 0x21f6892f8d0>]

```python
rsys
```

A	→	B + C	 0.04	 R1: A cons.

A	→	D + E	 0.03	 R2: A cons.

```python
uni
```

A	R1: A cons., R2: A cons.

B

C

D

E

```python
bi
```

	A	B	C	D	E

A

B	-

C	-	-

D	-	-	-

E	-	-	-	-

```python
not_uni
```

```python
not_bi
```

```python
# Initial Concentration
c0 = defaultdict(float, {'A': 1})
# Integrated ode system ??
result = odesys.integrate(1e10, c0, integrator='cvode', nsteps=2000)
# Dictionary object ??
{k: v for k, v in result.info.items() if not k.startswith('internal')}
```

    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '#include <math.h>':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed to include math.h:
      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning:
      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '#include <sundials/sundials_config.h>':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: sundials not in include path, set e.g. $CPLUS_INCLUDE_PATH ():

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '
        #include <sundials/sundials_config.h>
        #if SUNDIALS_VERSION_MAJOR >= 3
        #include <stdio.h>
        #include <sunmatrix/sunmatrix_dense.h>
        #else
        #error "Sundials 2?"
        #endif
        ':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '
                #include <sundials/sundials_config.h>
                #if defined(SUNDIALS_PACKAGE_VERSION)   /* == 2.7.0 */
                #include <cvodes/cvodes_spgmr.h>
                #else
                #error "Unkown sundials version"
                #endif
                ':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Unknown sundials version:

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '
            #include <sundials/sundials_config.h>
            #if !defined(SUNDIALS_KLU)
            #error "INFO: KLU was not enabled for this sundials build"
            #endif
            ':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '#include <sundials/sundials_config.h>
                        #ifndef SUNDIALS_SINGLE_PRECISION
                            #error "INFO: SUNDIALS_SINGLE not defined in sundials/sundials_config.h"
                        #endif
                     ':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '#include <sundials/sundials_config.h>
                        #ifndef SUNDIALS_DOUBLE_PRECISION
                            #error "INFO: SUNDIALS_DOUBLE not defined in sundials/sundials_config.h"
                        #endif
                     ':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '#include <sundials/sundials_config.h>
                        #ifndef SUNDIALS_EXTENDED_PRECISION
                            #error "INFO: SUNDIALS_EXTENDED not defined in sundials/sundials_config.h"
                        #endif
                     ':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Couldn't determine sundials precision from sundials/sundials_config.h
      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '#include <sundials/sundials_types.h>
                        #ifndef SUNDIALS_INT32_T
                            #error INFO: SUNDIALS_INT32_T is not defined
                        #endif
                     ':

      warnings.warn(msg)
    c:\users\user1\anaconda3\envs\kittens\lib\site-packages\pycvodes\_config.py:44: UserWarning: Failed test compilation of '#include <sundials/sundials_types.h>
                        #ifndef SUNDIALS_INT64_T
                            #error INFO: SUNDIALS_INT64_T is not defined
                        #endif
                     ':

      warnings.warn(msg)

    {'n_nonlin_solv_iters': 233,
     'n_steps': 186,
     'n_dls_jac_evals': 4,
     'n_nonlin_solv_conv_fails': 0,
     'n_root_evals': 0,
     'n_rhs_evals': 234,
     'n_lin_solv_setups': 48,
     'n_err_test_fails': 8,
     'n_dls_rhs_evals': 0,
     'njvev': 0,
     'nfev': 234,
     'njev': 4,
     'time_cpu': 0.011,
     'time_jtimes': 0.0,
     'time_jac': 0.000191574,
     'time_wall': 0.010793836,
     'time_rhs': 0.010093234000000013,
     'time_prec': 0.0,
     'time_quads': 0.0,
     'time_roots': 0.0,
     'steps': array([], dtype=float64),
     'orders': array([], dtype=int32),
     'fpes': array([], dtype=int32),
     'success': True,
     'atol': [1e-08],
     'rtol': 1e-08,
     'mode': 'adaptive'}

```python
odesys
```

    <pyodesys.symbolic.SymbolicSys at 0x21f688d5198>

```python
extra
```

    {'param_keys': [],
     'unique': OrderedDict(),
     'p_units': None,
     'max_euler_step_cb': None,
     'linear_dependencies': None,
     'rate_exprs_cb': <pyodesys.util._Callback at 0x21f6892fa90>,
     'cstr_fr_fc': False,
     'unit_registry': None}

```python
# Experimental rates ??
extra['rate_exprs_cb'](result.xout, result.yout)
```

    array([[ 4.00000000e-02,  3.00000000e-02],
           [ 3.99999791e-02,  2.99999844e-02],
           [ 3.99967271e-02,  2.99975453e-02],
           [ 3.99865668e-02,  2.99899251e-02],
           [ 3.99764087e-02,  2.99823065e-02],
           [ 3.99662532e-02,  2.99746899e-02],
           [ 3.99561001e-02,  2.99670751e-02],
           [ 3.99346578e-02,  2.99509933e-02],
           [ 3.98952558e-02,  2.99214418e-02],
           [ 3.98203854e-02,  2.98652891e-02],
           [ 3.96304666e-02,  2.97228500e-02],
           [ 3.95263476e-02,  2.96447607e-02],
           [ 3.94225021e-02,  2.95668765e-02],
           [ 3.93189294e-02,  2.94891970e-02],
           [ 3.91159337e-02,  2.93369503e-02],
           [ 3.89139863e-02,  2.91854897e-02],
           [ 3.87130818e-02,  2.90348113e-02],
           [ 3.85132147e-02,  2.88849110e-02],
           [ 3.83143795e-02,  2.87357846e-02],
           [ 3.79337531e-02,  2.84503148e-02],
           [ 3.73264129e-02,  2.79948096e-02],
           [ 3.67287969e-02,  2.75465977e-02],
           [ 3.61407485e-02,  2.71055614e-02],
           [ 3.55621145e-02,  2.66715859e-02],
           [ 3.49927444e-02,  2.62445583e-02],
           [ 3.44324903e-02,  2.58243678e-02],
           [ 3.38812063e-02,  2.54109048e-02],
           [ 3.33387487e-02,  2.50040616e-02],
           [ 3.24162116e-02,  2.43121587e-02],
           [ 3.15192025e-02,  2.36394019e-02],
           [ 3.06470150e-02,  2.29852613e-02],
           [ 2.97989624e-02,  2.23492218e-02],
           [ 2.89743767e-02,  2.17307825e-02],
           [ 2.76893824e-02,  2.07670368e-02],
           [ 2.64613765e-02,  1.98460324e-02],
           [ 2.52878319e-02,  1.89658739e-02],
           [ 2.41663336e-02,  1.81247502e-02],
           [ 2.30945733e-02,  1.73209300e-02],
           [ 2.20703449e-02,  1.65527586e-02],
           [ 2.10915401e-02,  1.58186551e-02],
           [ 2.01561447e-02,  1.51171085e-02],
           [ 1.92622334e-02,  1.44466751e-02],
           [ 1.74302608e-02,  1.30726956e-02],
           [ 1.63209099e-02,  1.22406825e-02],
           [ 1.52821632e-02,  1.14616224e-02],
           [ 1.43095274e-02,  1.07321455e-02],
           [ 1.33987953e-02,  1.00490965e-02],
           [ 1.19767624e-02,  8.98257176e-03],
           [ 1.11752112e-02,  8.38140841e-03],
           [ 1.04273033e-02,  7.82047751e-03],
           [ 9.72944951e-03,  7.29708713e-03],
           [ 9.07830037e-03,  6.80872528e-03],
           [ 8.34266157e-03,  6.25699618e-03],
           [ 7.66663380e-03,  5.74997535e-03],
           [ 7.04538610e-03,  5.28403958e-03],
           [ 6.47447948e-03,  4.85585961e-03],
           [ 5.94983492e-03,  4.46237619e-03],
           [ 5.46770384e-03,  4.10077788e-03],
           [ 5.02464118e-03,  3.76848089e-03],
           [ 4.61748104e-03,  3.46311078e-03],
           [ 4.24331413e-03,  3.18248559e-03],
           [ 3.89946697e-03,  2.92460023e-03],
           [ 3.58348269e-03,  2.68761202e-03],
           [ 3.29310348e-03,  2.46982761e-03],
           [ 3.02625447e-03,  2.26969085e-03],
           [ 2.78102894e-03,  2.08577170e-03],
           [ 2.55567469e-03,  1.91675602e-03],
           [ 2.34858151e-03,  1.76143613e-03],
           [ 2.15826965e-03,  1.61870224e-03],
           [ 1.98337926e-03,  1.48753445e-03],
           [ 1.82266072e-03,  1.36699554e-03],
           [ 1.67496562e-03,  1.25622422e-03],
           [ 1.53923866e-03,  1.15442900e-03],
           [ 1.41451002e-03,  1.06088252e-03],
           [ 1.29988848e-03,  9.74916357e-04],
           [ 1.19455502e-03,  8.95916267e-04],
           [ 1.09775702e-03,  8.23317766e-04],
           [ 1.00880282e-03,  7.56602117e-04],
           [ 9.27056822e-04,  6.95292616e-04],
           [ 8.51934918e-04,  6.38951189e-04],
           [ 7.82900344e-04,  5.87175258e-04],
           [ 7.19459827e-04,  5.39594871e-04],
           [ 6.61160066e-04,  4.95870049e-04],
           [ 6.07584491e-04,  4.55688368e-04],
           [ 5.58350288e-04,  4.18762716e-04],
           [ 5.13105665e-04,  3.84829249e-04],
           [ 4.71527335e-04,  3.53645501e-04],
           [ 4.33318209e-04,  3.24988657e-04],
           [ 3.98205271e-04,  2.98653954e-04],
           [ 3.65937629e-04,  2.74453222e-04],
           [ 3.36284720e-04,  2.52213540e-04],
           [ 3.09034667e-04,  2.31776000e-04],
           [ 2.72061796e-04,  2.04046347e-04],
           [ 2.39512522e-04,  1.79634391e-04],
           [ 2.10857507e-04,  1.58143130e-04],
           [ 1.85630703e-04,  1.39223028e-04],
           [ 1.63421939e-04,  1.22566454e-04],
           [ 1.43870204e-04,  1.07902653e-04],
           [ 1.26657658e-04,  9.49932435e-05],
           [ 1.11504427e-04,  8.36283205e-05],
           [ 9.81641102e-05,  7.36230827e-05],
           [ 8.64198077e-05,  6.48148558e-05],
           [ 7.60805864e-05,  5.70604398e-05],
           [ 6.69783502e-05,  5.02337627e-05],
           [ 5.89651031e-05,  4.42238273e-05],
           [ 5.19105539e-05,  3.89329154e-05],
           [ 4.57000044e-05,  3.42750033e-05],
           [ 4.02324815e-05,  3.01743612e-05],
           [ 3.54190913e-05,  2.65643185e-05],
           [ 3.11815727e-05,  2.33861795e-05],
           [ 2.74510278e-05,  2.05882708e-05],
           [ 2.41668026e-05,  1.81251020e-05],
           [ 2.12755003e-05,  1.59566253e-05],
           [ 1.75056313e-05,  1.31292235e-05],
           [ 1.44039148e-05,  1.08029361e-05],
           [ 1.18518599e-05,  8.88889491e-06],
           [ 9.75193522e-06,  7.31395142e-06],
           [ 8.02401013e-06,  6.01800760e-06],
           [ 6.60223839e-06,  4.95167880e-06],
           [ 5.43241565e-06,  4.07431174e-06],
           [ 4.46988461e-06,  3.35241346e-06],
           [ 3.67788945e-06,  2.75841709e-06],
           [ 3.02621177e-06,  2.26965883e-06],
           [ 2.49000293e-06,  1.86750220e-06],
           [ 2.04881003e-06,  1.53660752e-06],
           [ 1.68579297e-06,  1.26434473e-06],
           [ 1.38709477e-06,  1.04032108e-06],
           [ 1.14131926e-06,  8.55989445e-07],
           [ 8.38052171e-07,  6.28539129e-07],
           [ 6.15511979e-07,  4.61633984e-07],
           [ 4.52156444e-07,  3.39117333e-07],
           [ 3.32130784e-07,  2.49098088e-07],
           [ 2.43907039e-07,  1.82930280e-07],
           [ 1.79104383e-07,  1.34328288e-07],
           [ 1.31542625e-07,  9.86569685e-08],
           [ 9.66260770e-08,  7.24695577e-08],
           [ 7.09698669e-08,  5.32274002e-08],
           [ 5.21143367e-08,  3.90857525e-08],
           [ 3.14617924e-08,  2.35963443e-08],
           [ 1.91091814e-08,  1.43318861e-08],
           [ 1.16984682e-08,  8.77385118e-09],
           [ 7.15785297e-09,  5.36838972e-09],
           [ 4.33438087e-09,  3.25078565e-09],
           [ 2.61121267e-09,  1.95840950e-09],
           [ 1.21334427e-09,  9.10008201e-10],
           [ 1.61967179e-09,  1.21475385e-09],
           [ 1.06285474e-09,  7.97141055e-10],
           [ 2.07729306e-10,  1.55796979e-10],
           [-9.59293320e-11, -7.19469990e-11],
           [ 8.96962950e-11,  6.72722212e-11],
           [ 1.04470567e-09,  7.83529255e-10],
           [ 7.78316673e-10,  5.83737505e-10],
           [ 6.93022615e-11,  5.19766962e-11],
           [-1.88991908e-10, -1.41743931e-10],
           [ 2.67524332e-11,  2.00643249e-11],
           [ 1.72278821e-10,  1.29209116e-10],
           [ 5.26186486e-11,  3.94639864e-11],
           [-8.17638928e-11, -6.13229196e-11],
           [-2.90894793e-10, -2.18171095e-10],
           [-3.18967988e-10, -2.39225991e-10],
           [-1.63582207e-10, -1.22686655e-10],
           [-1.81596404e-11, -1.36197303e-11],
           [ 1.83549750e-11,  1.37662312e-11],
           [ 1.32137566e-11,  9.91031742e-12],
           [-2.76716376e-12, -2.07537282e-12],
           [-3.04536327e-11, -2.28402245e-11],
           [-1.62952245e-11, -1.22214184e-11],
           [-4.61533631e-11, -3.46150223e-11],
           [-5.60159158e-11, -4.20119368e-11],
           [-1.28556083e-11, -9.64170621e-12],
           [ 3.98511958e-12,  2.98883968e-12],
           [-5.82143618e-13, -4.36607714e-13],
           [-3.26420773e-12, -2.44815580e-12],
           [-4.22232971e-12, -3.16674728e-12],
           [-7.46289015e-13, -5.59716761e-13],
           [ 8.12130098e-14,  6.09097574e-14],
           [ 1.82886619e-13,  1.37164964e-13],
           [ 7.96723760e-14,  5.97542820e-14],
           [ 2.13431203e-14,  1.60073402e-14],
           [ 1.34327057e-17,  1.00745293e-17],
           [ 3.12507252e-21,  2.34380439e-21],
           [ 7.27189653e-26,  5.45392240e-26],
           [ 1.69212205e-31,  1.26909154e-31],
           [ 3.93790608e-38,  2.95342956e-38],
           [ 8.96831017e-46,  6.72623263e-46],
           [ 5.34552942e-53,  4.00914707e-53],
           [ 2.98704733e-61,  2.24028550e-61]])

```python
result.plot(xscale='log', yscale='log')
```

```python
# PLOTTING THE REACTION CONTRIBUTIONS DIRECTLY FROM CHEMPY ROBERTSON
fig, axes = plt.subplots(2, 2, figsize=(14, 6))
# normal y scale
plot_reaction_contributions(result, rsys, extra['rate_exprs_cb'], 'AB', axes=axes[0, :])
plot_reaction_contributions(result, rsys, extra['rate_exprs_cb'], 'AD', axes=axes[1, :])

plt.tight_layout()

```

```python
fig, axes = plt.subplots(2, 2, figsize=(14, 6))
# Linear y scale
plot_reaction_contributions(result, rsys, extra['rate_exprs_cb'], 'AC', axes=axes[0, :],
                            relative=True, yscale='linear')
plot_reaction_contributions(result, rsys, extra['rate_exprs_cb'], 'AE', axes=axes[1, :],
                            relative=True, yscale='linear')

plt.tight_layout()

```

### Now we will incorporate mass action into the kinetic model

```python
str_massaction = """
A -> B + C; 'k1'
A -> D + E; 'k2'
"""
```

```python
rsys3 = ReactionSystem.from_string(str_massaction, substance_factory=lambda formula: Substance(formula))
```

```python
str_massaction
```

    "\nA -> B + C; 'k1'\nA -> D + E; 'k2'\n"

```python
rsys3
```

A	→	B + C	 MassAction((), ('k1',))

A	→	D + E	 MassAction((), ('k2',))

```python
odesys3, extra3 = get_odesys(rsys3, include_params=False, lower_bounds=[0, 0, 0,0,0])
extra3['param_keys'], extra3['unique']
```

    ([], OrderedDict([('k1', None), ('k2', None)]))

```python
odesys3.exprs, odesys3.params, odesys3.names, odesys3.param_names
```

    ((-p_0*y_0 - p_1*y_0, p_0*y_0, p_0*y_0, p_1*y_0, p_1*y_0),
     (p_0, p_1),
     ('A', 'B', 'C', 'D', 'E'),
     ('k1', 'k2'))

```python
def integrate_and_plot(A0=1.0, B0=0.0, C0=0.0, D0=0.0, E0=0.0, lg_k1=-2, lg_k2=4, lg_tend=9):
    plt.figure(figsize=(14, 4))
    tout, yout, info = odesys3.integrate(
        10**lg_tend, {'A': A0, 'B': B0, 'C': C0,'D': D0, 'E': E0},
        {'k1': 10**lg_k1, 'k2': 10**lg_k2},
        integrator='cvode', nsteps=3000)
    plt.subplot(1, 2, 1)
    odesys3.plot_result(xscale='log', yscale='log')
    plt.legend(loc='best')
    plt.subplot(1, 2, 2)
    plt.plot(tout[tout<.05], yout[tout<.05, odesys3.names.index('B')])
    _ = plt.legend('best')
interact(integrate_and_plot) #, **kw)
# kw for additional optional arguments
```

    interactive(children=(FloatSlider(value=1.0, description='A0', max=3.0, min=-1.0), FloatSlider(value=0.0, desc…

    <function __main__.integrate_and_plot(A0=1.0, B0=0.0, C0=0.0, D0=0.0, E0=0.0, lg_k1=-2, lg_k2=4, lg_tend=9)>

```python
# We could also have used SymPy to construct symbolic rates:
import sympy
rsys_sym = ReactionSystem.from_string("""
A -> B + C; sp.Symbol('k1')
A -> D + E; sp.Symbol('k2')
""", rxn_parse_kwargs=dict(globals_={'sp': sympy}), substance_factory=lambda formula: Substance(formula))
odesys_sym, _ = get_odesys(rsys_sym, params=True)
for attr in 'exprs params names param_names'.split():
    print(getattr(odesys_sym, attr))
```

    (-k1*y_0 - k2*y_0, k1*y_0, k1*y_0, k2*y_0, k2*y_0)
    (k1, k2)
    ('A', 'B', 'C', 'D', 'E')
    ()

For larger systems it is easy to loose track of what substances are actually playing a part, here the html tables can help (note the yellow background color):

```python
rsys.substances['F'] = F
uni, not_uni = UnimolecularTable.from_ReactionSystem(rsys)
uni
```

A	R1: A cons., R2: A cons.

B

C

D

E

F

```python
bi, not_bi = BimolecularTable.from_ReactionSystem(rsys)
bi
```

	A	B	C	D	E	F

A

B	-

C	-	-

D	-	-	-

E	-	-	-	-

F	-	-	-	-	-

```python
# # Saving a Picture
# rsys2graph(rsys, 'pciture.png', save='.')
# from IPython.display import Image; Image('picture.png')
```

# Arrhenius Kinetic model with 2 sets of products

```python
from collections import defaultdict
from itertools import chain
import numpy as np
import sympy as sp
import matplotlib.pyplot as plt
from ipywidgets import interact
from chempy import Substance, Reaction, ReactionSystem
from chempy.kinetics.rates import Arrhenius, MassAction
from chempy.kinetics.ode import get_odesys
from chempy.printing.numbers import number_to_scientific_latex
from chempy.units import SI_base_registry, default_units as u
sp.init_printing()
%matplotlib inline
```

```python
A, B, C, D, E, F= map(Substance, 'ABCDEF')
r1 = Reaction({'A'}, {'B','C'}, MassAction(Arrhenius(unique_keys=('A1', 'Ea_R_1'))))
r2 = Reaction({'A'}, {'D','E'}, MassAction(Arrhenius(unique_keys=('A2', 'Ea_R_2'))))
rsys = ReactionSystem([r1, r2])
rsys
```

A	→	B + C	 MassAction((Arrhenius((), ('A1', 'Ea_R_1')),))

A	→	D + E	 MassAction((Arrhenius((), ('A2', 'Ea_R_2')),))

```python
# Temperature is at 15 C just like the calibration
# Activation energies and rates are arbitrary
params = {'A1': 1e11/u.s, 'A2': 2e11/u.s, 'Ea_R_1': 8e3*u.K, 'Ea_R_2': 8.5e3*u.K, 'temperature': 288*u.K}
c0 = defaultdict(lambda: 0*u.molar, {'A': 1*u.molar})
variables = c0.copy()
variables.update(params)
rsys.rates(variables)
```

    {'C': array(0.08635041) * M/s,
     'A': array(-0.11678104) * M/s,
     'B': array(0.08635041) * M/s,
     'E': array(0.03043063) * M/s,
     'D': array(0.03043063) * M/s}

```python
odesys, extra = get_odesys(rsys, include_params=False, lower_bounds=0)
print(dict(zip(odesys.dep, odesys.names)))
print(dict(zip(odesys.params, odesys.param_names)))
odesys.exprs
```

```python
params2 = params.copy()
pk = 'Ea_R_2'
params2[pk] = [(320+273), (340+273), (360+273)]*u.K
```

```python
params2
```

    {'A1': array(1.e+11) * 1/s,
     'A2': array(2.e+11) * 1/s,
     'Ea_R_1': array(8000.) * K,
     'Ea_R_2': array([593., 613., 633.]) * K,
     'temperature': array(288.) * K}

```python
params2[pk]
```

    array([593., 613., 633.]) * K

# Run time errors happen for 1 second or over for arrhenius model

# Will need to fix

```python
res2 = odesys.integrate(.001*u.s, c0, params2, integrator='cvode')
# plotting at the 3 temperatures
fig, axes = plt.subplots(1, len(res2), figsize=(14, 4))
for r, ax in zip(res2, axes):
    r.plot(ax=ax)
    ax.set_title('$%s = %s$' % (pk.replace('_', '\\_'), number_to_scientific_latex(r.named_param('Ea_R_2'))))

```

Using ipywidgets to get interactive controls:

```python
def integrate_and_plot(T_C=25):
    res = odesys.integrate(7*u.s, c0, dict(params, temperature=(T_C+273.15)*u.K), integrator='cvode')
    res.plot()
interact(integrate_and_plot)
```

    interactive(children=(IntSlider(value=25, description='T_C', max=75, min=-25), Output()), _dom_classes=('widge…

    <function __main__.integrate_and_plot(T_C=25)>

```python

```


# PS2, part 2: Costly state verification and optimality of debt

Import packages, classes and settings:

```python
import sys
import numpy as np
import math
import itertools
from scipy import optimize
import scipy.stats as stats
import PS2 as func
# For plots:
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib inline
plt.style.use('seaborn-whitegrid')
mpl.style.use('seaborn')
prop_cycle = plt.rcParams["axes.prop_cycle"]
colors = prop_cycle.by_key()["color"]
import ipywidgets as widgets
from ipywidgets import interact, interact_manual
```

The core of the model setup:

* **Utility** is linear: $U_E=c_0+c_1$.
* Entrepreneur invests at time $0$, cost $I$. Return at time $t=1$ is random, distributed according to $g(s)$:

$$\begin{align}
    Return = Rs, && s\sim g(s), && \text{Support on }S\equiv \left[s^{min}, s^{max}\right]
\end{align}$$
Also assumed that
$$\begin{align}
    \mathbb{E}[s]=1, && R>I.
\end{align}$$

* **Asymmetric information:** State $s$ is unknown at time $t=0$. At time $t=1$ the entrepreneur (E) observes the true value of $s$ costlessly. The financier (F) can choose to audit and observe it, but at the cost $\kappa>0$.
* **Assumption:** The financier chooses a *deterministic* audit rule. I.e. no *random/stochastic* audit rule. By the *revelation principle:* Only focus on contracts in which E has no incentive to lie.
* **Contracts:** Specified by the triplet

$$\begin{align}
    Contract = \left( R^E(s), \mbox{ }R^F(s), \mbox{ }S_A\right), && S_A\subset S
\end{align}$$
where $R^E(s)$ and $R^F(s)$ denotes the payoffs for E,F (net of audit cost). $S_A$ is the set of states $S$, where $F$ chooses to audit.

* **Resource constraint:**

$$\begin{align}
    R^E(s)+R^F(s)+\kappa \mathbf{1}_{s\in S_A} = Rs, && R^E(s)\geq 0.
\end{align}$$

where $\mathbf{1}_{s\in S_A}$ indicates auditing, and the inequality captures *limited liability* of the entrepreneur.

* **Participation constraint:** (for F)

$$\begin{align}
    \int_S R^F(s)g(s)\mbox{ }ds\geq I.
\end{align}$$

States that expected revenue covers investment cost.

### **Q1: Write down the IC constraint for E. What does it imply for F's net payoff?**

**IC constraint** with a continuous set of possible outcomes: *In any given state $s$ it must not be profitable to deviate to *any* state $s'$.*

Another useful definition is **the revelation principle**. The principle roughly states that every potential equilibrium outcome, can be achieved by an incentive-compatible strategy.

The IC constraint then states that

$$\begin{align}
    Rs-R^F(s)-\kappa \mathbf{1}_{s\in S_A}\geq Rs-R^F(s'), && \forall s'\in\left(S\backslash S_A\right)
\end{align}$$

The left-hand side (LHS) is the profit from E telling the true $s$. The RHS is the outcome if E is in state $s$, but signal to F that he is in state $s'$. Note that we only consider deviations to $s'\in (S\backslash S_A)$. There is no incentive to lie about a state $s'$, where E knows he will be audited.

**What does it imply for F's net payoff?**

* Consider two states, $s,s'$, both in the no-audit region. The IC constraint for state $s$ states $R^F(s)\geq R^F(s')$. The IC constraint for state $s$ states $R^F(s')\geq R^F(s)$.

* For them both to hold simultaneously, there must be exactly one payoff level for all $s\notin S_A$. Denote this $R^F(\bar{s})$. If there was a state with higher payoffs in the region $S\backslash S_A$, where E is not audited, he would always report this.

* Now consider the case where $s\in S_A$ (recall that alternative $s'\notin S_A$): The $(IC)$ then states that

$$\begin{align}
    Rs-R^F(s)-\kappa \geq Rs-R^F(\bar{s}) && \Rightarrow && R^F(s)\leq R^F(\bar{s}), && \forall s\in S_A.
\end{align}$$

* This states that in the audit region, the financier (F) has to accept lower returns $R^F(s)$ than in the non-audit region. If this was not the case, E would always report a state in the no-audit region, and thus get higher profits.

### **Q2: Define a standard debt contract:**

$$\begin{align}
    R^F(s) = \left\lbrace \begin{array}{ll} R\bar{s}, & s\geq \bar{s} \\ Rs-\kappa, & s\leq \bar{s} \end{array}\right., && S_A=\left[s^{min},\mbox{ }\bar{s}\right].
\end{align}$$

### **Check that any level $\bar{s}$ satisfies both the resource constraint and the (IC).**

* **(IC) constraint:**
    * When $s\geq \bar{s}:$ The entrepreneur receives $R(s-\bar{s})$ by telling the truth. The same if he over-reports. If he under-reports in the audit-region he receives 0.
    * When $s<\bar{s}:$ If E over-reports (in the no-audit region) he receives $R(s-\bar{s})<0$. By true reporting he receives 0.

* **Resource constraint:**
    * If $s\geq \bar{s}$ the resource constraint reads:
    $$\begin{align}
        \overbrace{R(s-\bar{s})}^{R^E}+\overbrace{R\bar{s}}^{R^F} + \overbrace{0}^{audit}= Rs
    \end{align}$$
    * If $s<\bar{s}$ the resource constraint reads:

$$\begin{align}
    \overbrace{0}^{R^E} +\overbrace{Rs-\kappa}^{R^F} + \overbrace{\kappa}^{audit} = Rs
\end{align}$$

### **Q3: Prove that the standard contract is optimal.**

Consider a non-standard contract:

$$\begin{align}
    (\tilde{R}^E, \tilde{R}^F, \tilde{S}_A)
\end{align}$$

* The (IC) constraint still needs to hold. Thus for $s\notin \tilde{S}_A$ the non-standard contract still has to offer the same return. Denote this constant $\tilde{R}^F$.
* Note furthermore that $\tilde{S}_A$ has to be a closed interval on the form $\tilde{S}_A = [s^{min},\tilde{s}]$: Assume there is a level $s$ that is not audited, but $s'>s$ is audited. If E is in state s', he can report $s$ without being audited and profit from it.
* This implies that $\tilde{R}^F=R\tilde{s}$.

Consider now the various regions of $s$:

* For $s\in\left(\tilde{S}_A \cap S_A\right):$ F receives $Rs-\kappa$ in both cases.
* For $s\in\left(\tilde{S}_A \cap (S\backslash S_A)\right):$ F receives higher payoff in standard debt contract, where he does not pay the audit cost.
* For $s$ in non-audit region: Let the standard contract offer the level $\bar{R}^F= \tilde{R}^F$. Thus F is indifferent in these cases.

For entrepreneurs: The standard debt contract minimizes audit-costs (given IC and participation constraints). Given that they receive the residual income F's payment, E prefers a standard contract as well.

### **Q4: Assume that $s$ is uniform. Characterizes the riskiness of the optimal debt contract.**

### **Q4.1: Characterize the expected profit, and the participation constraint**

Assume that $s$ is uniform on $[1-\Delta, 1+\Delta]$. In other words the pdf (f) and cdf (F) are given by:

$$\begin{align}
f(s) = \dfrac{1}{2\Delta}, && F(s) = \dfrac{s-(1-\Delta)}{2\Delta}, && s\in [1-\Delta, 1+\Delta]
\end{align}$$

The entrepreneur then solves:

$$\begin{align}
    \max_{\bar{s}} \dfrac{1}{2\Delta} \int_{\bar{s}}^{1+\Delta}(Rs-R\bar{s})\mbox{ }ds
\end{align}$$

subject to F's participation constraint

$$\begin{align}
    \dfrac{1}{2\Delta} \int_{\bar{s}}^{1+\Delta} R\bar{s}\mbox{ }ds+\dfrac{1}{2\Delta}\int_{1-\Delta}^{\bar{s}}(Rs-\kappa)\mbox{ }ds\geq I
\end{align}$$

**Note: The expected profits (given that F participates) are always positive. However, a certain level of $\bar{s}$ is needed for F to participate**

NB: The participation constraint is written with $-I$ on the LHS: Thus when this is positive, F participates

```python
Model = func.OptDebt()
Model.plt_profit_participationconstr()
# you can check out default parameters (and everything else in the class 'OptDebt') used here by 'uncommenting' the following line:
# Model.__dict__
```

Integrating out the expression we write this as:

$$\begin{align}
       \left[1+\Delta-\bar{s}\right]R\bar{s}+\left[\dfrac{R}{2}\bar{s}^2-\bar{s}\kappa\right]-\left[\dfrac{R}{2}(1-\Delta)^2-(1-\Delta)\kappa\right]\geq 2\Delta I,
\end{align}$$

or rewritten as

$$\begin{align}
    -\dfrac{R}{2}\bar{s}^2+\left[R(1+\Delta)-\kappa\right]\bar{s}+(1-\Delta)\kappa-\dfrac{R}{2}(1-\Delta)^2\geq 2\Delta I. \tag{PC}
\end{align}$$

We note here that:
* For $s<\bar{s}:$ the entrepreneur receives 0. Thus the object function integrates from $\bar{s}$ to $1+\Delta$.
* For $\bar{s}\leq s \leq 1+\Delta$, the financier receives $R\bar{s}$. In the non-audit region he receives $Rs-\kappa$.

It is straightforward to see that for the entrepreneur, the lowest possible $\bar{s}$ is preferred, where the participation constraint still holds. The question then comes down to **what is the minimum level of $\bar{s}$ the entrepreneur can set, maintaining the participation of F?**

### **4.2: Characterize the optimal $\bar{s}$ that ensures participation of F**

To do this proceed as follows:

* If $I$ is sufficiently small, the obvious solution is to choose $\bar{s}=1-\Delta$. This is feasible (participation constraint holds) if:

$$\begin{align}
        R(1-\Delta)\geq I
\end{align}$$

* There are two options now: Either there is **no** solution within $[1-\Delta, 1+\Delta]$. Otherwise the solution is given in the case where $(PC)$ holds with equality.

Solving for $(PC)$ with equality, we have:

$$\begin{align}
 \bar{s}^* = 1+\Delta-\dfrac{\kappa}{R}-\dfrac{1}{R}\sqrt{(R(1+\Delta)-\kappa)^2+2R*\left[(1-\Delta)\kappa-\dfrac{R}{2}(1-\Delta)^2-2\Delta I\right]}, \tag{$s^*$}
\end{align}$$

Note that there might not be any solution at all, if the term in the $\sqrt{\cdot}$ is negative (this is a solution to a second-order polynomial equation).

```python
Model.plot_interactive_pc()
```

    interactive(children=(SelectionSlider(description='Investment cost, $I$', options=(0.0, 0.03, 0.06, 0.09, 0.12…

### **Q5: Find the threshold cost $I$, where the project is profitable**

Using the result from Q4 the threshold $I^{max}$, where the project is financed as long as $I\leq I^{\max}$ is defined as follows:

* If $R(1-\Delta)\geq I$ then $s^*=1-\Delta$. In this case the largest cost $I$ that is financed, is given by $R(1-\Delta)$.
* If $R(1-\Delta)<I$ then there exists a solution (recall that there exists a solution as long as the term $\sqrt{\cdot}$ in $s^*$ is non-negative).
* Note furthermore, that $I$ enters negatively in that expression; thus the higher $I$, the closer we get to a scenario, where there is no $\bar{s}^*$.
* In other words, the largest $I$ that allows a feasible solution is defined by the condition:

$$\begin{align}
(R(1+\Delta)-\kappa)^2+2R*\left[(1-\Delta)\kappa-\dfrac{R}{2}(1-\Delta)^2-2\Delta I\right] = 0 && \Rightarrow && I = R-\kappa+\dfrac{\kappa^2}{4\Delta R}.
\end{align}$$

Combining the two potential scenarios the highest possible $I$ that can be financed is thus given by

$$\begin{align}
     I^{max} = \max \Bigg\lbrace \underbrace{R-\kappa+\dfrac{\kappa^2}{4\Delta R}}_{\text{Scenario 1}},\mbox{ }\mbox{ }\underbrace{R(1-\Delta)}_{\text{Scenario 2}}\Bigg\rbrace.
\end{align}$$

A few comments on this solution:

* **When $\kappa<2R\Delta$:** The max-operator returns *scenario 1*. In this case $I^{max}$ is decreasing in both $\kappa$ and $\Delta$. Why?

    * When $\kappa$ increases the cost of auditing goes up. When $\kappa<2\Delta$ the $(PC)$ constraint is binding implying that $s^*>1-\Delta$; in other words, the optimal debt contract involves some costly auditing. In this scenario a larger audit-cost makes investment projects less profitable over all $\Rightarrow I^{max}$ decreases.

    * An increase in $\Delta$ increases the uncertainty of the outcome. You may verify (e.g. from graphs below) that $s^*$ in this case is increasing in $\Delta$. In other words, the increase in $\Delta$ increases the region of $s$, where the financier has to audit, in order to ensure zero expected profits. This lowers the profitability of the project for F, which lowers $I^{max}$.

* **When $\kappa\geq 2R\Delta$:** The max operator returns *scenario 2*.

    * In this case $I^{max}$ is simply given by $R(1-\Delta)$: When there is no auditing at all, the project is still profitable for F. Thus any change in auditing-costs $(\kappa)$ does not affect $I^{max}$.

    * An increase in $\Delta$ clearly lowers $s^*$ proportionally. As the financier in this case always receive the simple payoff $R^F=Rs^* = R(1-\Delta)$, an increase in $\Delta$ lowers the profits from the project, thus $I$ decreases.

**Participation constraint with different values of $\Delta$:**

With default values $(\Delta=1)$:

```python
Model.plt_profit_participationconstr()
```

Now decrease $\Delta$ (be aware that the x-axis/y-axis both change to fit the graphs)

```python
par = {'sDelta': 0.5} # New parameter-values. There are more parameters that you can change; this is only a selection of them.
Model.upd_par(par)
Model.plt_profit_participationconstr()
```

**Participation constraint with higher auditing cost:**

```python
par = {'kappa': 0.5} #default value is 0.1
Model.upd_par(par)
Model.plt_profit_participationconstr()
```

**Participation constraint with prohibitively large audit costs:**

```python
par = {'kappa': 2} #default value is 0.1
Model.upd_par(par)
Model.plt_profit_participationconstr()
```


```python
import sys
sys.path.append('../')
from TMM_functions import PQ_matrices as pq
from TMM_functions import scatter_matrices as sm
from TMM_functions import redheffer_star as rs
from TMM_functions import generate_initial_conditions as ic
import numpy as np
import matplotlib.pyplot as plt
from numpy.linalg import cond
import cmath;
from scipy import linalg as LA
from numpy.linalg import solve as bslash
import time
from convolution_matrices.convmat1D import *
# from RCWA_1D_functions.grating_fft.grating_conv import *

# Moharam et. al Formulation for stable and efficient implementation for RCWA
plt.close("all")
'''
'''
L0 = 1e-6;
e0 = 8.854e-12;
mu0 = 4*np.pi*1e-8;

n_ridge = 3.48;              # ridge
n_groove = 3.48;                # groove (unit-less)
lattice_constant = 0.7;  # SI units

## simulation parameters
theta = (0)*np.pi/180;
phi = (0)*np.pi/180;
e_r = 1;
m_r = 1;
e_t = 1;
m_t = 1;
n_i = np.sqrt(e_r);

normal_vector= [0,0,-1]
e_r = 12; m_r = 1;
exy = 14; eyx = 14;
I = np.matrix(np.eye(2))

## specify the initial kx and ky
k0=1
kx = k0 *np.sin(theta)*np.cos(phi);
ky = k0 *np.sin(theta)*np.sin(phi);
print(kx, ky)
```

    0.0 0.0

```python
## redefined functions
def Pa(kx, ky, e_r, m_r, exy, eyx):
        return (1/e_r)*np.matrix([[kx * ky - exy,  m_r * e_r - kx ** 2],
                                  [ky ** 2 - m_r * e_r,  -kx * ky -exy]]);

def Qa(kx, ky, e_r, m_r):
        Q = (1/m_r)*np.matrix([[kx * ky, (m_r * e_r) - kx ** 2],
                               [ky ** 2 - (m_r * e_r), -kx * ky]]);
        return Q;

def Pa_Qa_kz(kx, ky, e_r, mu_r, exy, eyx):
    '''
    r is for relative so do not put epsilon_0 or mu_0 here
    :param kx:
    :param ky:
    :param e_r:
    :param mu_r:
    :return:
    '''
    # eigensolution required in xy anisotropic medium, yea because the k-axes are now different.
    kz = np.sqrt(mu_r * e_r - kx ** 2 - ky ** 2);
    q = Qa(kx, ky, e_r, mu_r)
    p = Pa(kx, ky, e_r, mu_r, exy, eyx)

    return p, q, kz;
```

## Troubleshooting and Observations
In the isotropic case, the E modes can be assumed to be the identity matrix because PQ in that case would actually come out to be the identity matrix.

\begin{equation}
\frac{1}{\mu \epsilon}
\begin{bmatrix}
    k_x k_y & \mu \epsilon - k_x^2 \\
    k_y^2 - \mu\epsilon & -k_x k_y
\end{bmatrix} \begin{bmatrix}
    k_x k_y & \mu \epsilon - k_x^2 \\
    k_y^2 - \mu\epsilon & -k_x k_y
\end{bmatrix} = \begin{bmatrix}
    (-k_x^2 -k_y^2) - \mu \epsilon & 0 \\
    0 & (-k_x^2 -k_y^2) - \mu \epsilon
\end{bmatrix}
\end{equation}

In the anisotropic case, it is quite clear that the P and Q matrices do not have symmetry with resepct to each other anymore.
\begin{equation}
\frac{1}{\mu \epsilon}
\begin{bmatrix}
    k_x k_y - e_{xy} & \mu \epsilon - k_x^2 \\
    k_y^2 - \mu\epsilon & -k_x k_y + e_{xy}
\end{bmatrix} \begin{bmatrix}
    k_x k_y & \mu \epsilon - k_x^2 \\
    k_y^2 - \mu\epsilon & -k_x k_y
\end{bmatrix} = \begin{bmatrix}
    (-k_x^2 -k_y^2) - \mu \epsilon & 0 \\
    0 & (-k_x^2 -k_y^2) - \mu \epsilon
\end{bmatrix}
\end{equation}

```python
ref = [];
trans = [];
I = np.matrix(np.eye(2, 2));  # unit 2x2 matrix
wavelengths = np.linspace(0.5, 2, 100)
[e_r, m_r] = [1,1];
[e_t, m_t] = [1,1];
ER = [12];
UR = [1]
layer_thicknesses = [0.5]
n_i = np.sqrt(e_r*m_r);
kx = n_i * np.sin(theta) * np.cos(phi);  # constant in ALL LAYERS; kx = 0 for normal incidence
ky = n_i * np.sin(theta) * np.sin(phi);  # constant in ALL LAYERS; ky = 0 for normal incidence
normal_vector = np.array([0, 0, -1])  # positive z points down;
ate_vector = np.matrix([0, 1, 0]);  # vector for the out of plane E-field

## =================  specify gap media ========================##
e_h = 1; m_h = 1;
Pg, Qg, kzg = pq.P_Q_kz(kx, ky, e_h, m_h)
Wg = I;  # Wg should be the eigenmodes of the E field, which paparently is the identity, yes for a homogeneous medium
sqrt_lambda = cmath.sqrt(-1) * Wg;
# remember Vg is really Qg*(Omg)^-1; Vg is the eigenmodes of the H fields
Vg = Qg * Wg * (sqrt_lambda) ** -1;

## ========================================== ##

[pte, ptm] = [1,0];

for i in range(len(wavelengths)):  # in SI units
    ## initialize global scattering matrix: should be a 4x4 identity so when we start the redheffer star, we get I*SR

    Sg11 = np.matrix(np.zeros((2, 2)));
    Sg12 = np.matrix(np.eye(2, 2));
    Sg21 = np.matrix(np.eye(2, 2));
    Sg22 = np.matrix(np.zeros((2, 2)));  # matrices
    Sg = np.block(
        [[Sg11, Sg12], [Sg21, Sg22]]);  # initialization is equivelant as that for S_reflection side matrix

    ### ================= Working on the Reflection Side =========== ##
    Pr, Qr, kzr = pq.P_Q_kz(kx, ky, e_r, m_r)

    ## ============== values to keep track of =======================##
    S_matrices = list();
    kz_storage = [kzr];
    X_storage = list();
    ## ==============================================================##

    # define vacuum wavevector k0
    lam0 = wavelengths[i];  # k0 and lam0 are related by 2*pi/lam0 = k0
    k0 = 2*np.pi/lam0;
    ## modes of the layer
    Om_r = np.matrix(cmath.sqrt(-1) * kzr * I);
    X_storage.append(Om_r);
    W_ref = I;
    V_ref = Qr * Om_r.I;  # can't play games with V like with W because matrices for V are complex

    ## calculating A and B matrices for scattering matrix
    Ar, Br = sm.A_B_matrices(Wg, W_ref, Vg, V_ref);

    S_ref, Sr_dict = sm.S_R(Ar, Br);  # scatter matrix for the reflection region
    S_matrices.append(S_ref);
    Sg, D_r, F_r = rs.RedhefferStar(Sg, S_ref);

    ## go through the (ANISOTROPIC) layers
    for i in range(len(ER)):
        # ith layer material parameters
        e = ER[i];
        m = UR[i];

        # longitudinal k_vector
        P, Q, kzl = Pa_Qa_kz(kx, ky, e, m, exy, eyx)

        ## E-field modes that can propagate in the medium
        W_i = I; #should not be identity for xy anisotorpy
        ## corresponding H-field modes.
        Om = cmath.sqrt(-1) * kzl * I;
        X_storage.append(Om)
        V_i = Q * np.linalg.inv(Om);

        # now defIne A and B
        A, B = sm.A_B_matrices(Wg, W_i, Vg, V_i);

        # calculate scattering matrix
        S_layer, Sl_dict = sm.S_layer(A, B, layer_thicknesses[i], k0, Om)
        S_matrices.append(S_layer);

        ## update global scattering matrix using redheffer star
        Sg, D_i, F_i = rs.RedhefferStar(Sg, S_layer);

    ##========= Working on the Transmission Side==============##
    Pt, Qt, kz_trans = pq.P_Q_kz(kx, ky, e_t, m_t);
    kz_storage.append(kz_trans);

    Om = cmath.sqrt(-1) * kz_trans * I;
    Vt = Qt * np.linalg.inv(Om);

    # get At, Bt
    At, Bt = sm.A_B_matrices(Wg, I, Vg, Vt)

    ST, ST_dict = sm.S_T(At, Bt)
    S_matrices.append(ST);
    # update global scattering matrix
    Sg, D_t, F_t = rs.RedhefferStar(Sg, ST);

    K_inc_vector = n_i * k0 * np.matrix([np.sin(theta) * np.cos(phi), \
                                         np.sin(theta) * np.sin(phi), np.cos(theta)]);

    # cinc is the c1+
    E_inc, cinc, Polarization = ic.initial_conditions(K_inc_vector, theta, normal_vector, pte, ptm)

    ## COMPUTE FIELDS
    Er = Sg[0:2, 0:2] * cinc;  # S11; #(cinc = initial mode amplitudes), cout = Sg*cinc; #2d because Ex, Ey...
    Et = Sg[2:, 0:2] * cinc;  # S21

    Er = np.squeeze(np.asarray(Er));
    Et = np.squeeze(np.asarray(Et));

    Erx = Er[0];
    Ery = Er[1];
    Etx = Et[0];
    Ety = Et[1];

    # apply the grad(E) = 0 equation to get z components
    Erz = -(kx * Erx + ky * Ery) / kzr;
    Etz = -(kx * Etx + ky * Ety) / kz_trans;  ## using divergence of E equation here

    # add in the Erz component to vectors
    Er = np.matrix([Erx, Ery, Erz]);  # a vector
    Et = np.matrix([Etx, Ety, Etz]);

    R = np.linalg.norm(Er) ** 2;
    T = np.linalg.norm(Et) ** 2;
    ref.append(R);
    trans.append(T);
```

```python
ref = np.array(ref);
trans = np.array(trans);
plt.plot(ref)
plt.plot(trans)
plt.show()
```

```python

```

notebooks/Transfer Matrices/TMM transverse(xy) anisotropy study.ipynb


# Projectile motion

> Marcos Duarte
> [Laboratory of Biomechanics and Motor Control](http://pesquisa.ufabc.edu.br/bmclab)
> Federal University of ABC, Brazil

Projectile or ballistic motion is the movement of a body in the air near Earth's surface where the main force acting on the body is the gravitational force. If we neglect the air resistance and approximate the gravity force (which acts only at the vertical direction) on the body as constant, the body will have constant velocity at the horizontal direction and constant acceleration (the gravitational acceleration) at the vertical direction.

Here is an interactive animation of a projectile motion where you can change the initial angle and speed of the body (or to start, simply press the button *Fire* in the rectangular menu):

```python
from IPython.display import IFrame
IFrame('https://phet.colorado.edu/sims/html/projectile-motion/latest/projectile-motion_en.html',
       width='100%', height=500)
```

## Equations of motion

Let's deduce the equations for a projectile motion and investigate their characteristics. For simplicity, let's consider for now a planar movement, neglect the air resistance, and the gravitational acceleration, $g=9.8 m/s^2$. Consider a body (which will treat it as a particle) launched to the air with initial angle $\theta$ with respect to the horizontal (the $\mathbf{x}$ direction) and initial velocity $\mathbf{v_0}$, a vector quantity with components $v_{0x}=v_0\cos(\theta)$ and $v_{0y}=v_0\sin(\theta)$.
The equations of motion for position ($x, y$), velocity $v_x, v_y$, and acceleration $a_x, a_y$ are:

\begin{align}
& x(t) = x_0 + v_0\cos(\theta)\:t \\
& y(t) = y_0 + v_0\sin(\theta)\:t - \frac{g\:t^2}{2} \\
& v_x(t) = v_0\cos(\theta) \\
& v_y(t) = v_0\sin(\theta) - g\:t \\
& a_x(t) = 0 \\
& a_y(t) = -g
\label{eq_1}
\end{align}

**Time of flight**
The time of flight can be calculated from the equation for the vertical velocity and using the properties that at the maximum height, the vertical velocity is zero and the time of rising is equal to the time of falling:

\begin{equation}
t_{flight} = \frac{2v_0\sin(\theta)}{g}
\label{eq_2}
\end{equation}

**Maximum height**
Using the value for time of flight we just calculated and once again the fact the at the maximum height the body has zero vertical velocity, the maximum height, $h$, can be obtained from the equation for the vertical position:

\begin{equation}
h = \frac{v_0^2\sin^2(\theta)}{2g}
\label{eq_3}
\end{equation}

**Range, the maximum horizontal distance**
The range, $R$, is the maximum horizontal distance reached with respect to the point of release:

\begin{array}{l l}
R = v_0\cos(\theta)\:t_{flight} \\
\\
R = \frac{v_0^2\sin(2\theta)}{g}
\label{eq_4}
\end{array}

**Parabolic trajectory**
The equation for the horizontal position versus time is the equation of a straight line and for the vertical position is the equation of a parabola. We can prove that the spatial trajectory (the vertical position versus the horizontal position) is also a parabola if we use the two equations for position and eliminate the variable time:

\begin{equation}
y = y_0 + \tan(\theta)(x-x_0) - \frac{g}{2v_0^2cos^2(\theta)}(x-x_0)^2
\label{eq_5}
\end{equation}

Since $y_0,\: x_0,\: \theta,\: v_0,\: g$ are constants, this is an equation for a parabola.

**Release angle for maximum horizontal distance**
From the expression for the range, we note that the maximum value is reached when $\sin(2\theta)=1$ which gives $\theta=45^o$.

**Release angle for maximum height**
From the expression for the maximum height, we note that the maximum value is reached when $\sin^2(\theta)=1$ which gives $\theta=90^o$.

## Kinematics of the long jump

We can study the kinematics of jumps by humans and other animals as projectile motions with the equations of motion we derived if we consider the body as a particle. There is a particular point in the body (a virtual point) that will strictly follow the equations of motion we derived. This point is called center of mass or center of gravity and represents an average position of all the masses in the body. We will study this later; for now just consider that when applying the equations for projectile motion to a body with many particles, we are in fact describing the motion of its center of mass.

The track and field event long jump has some particularities we must consider to analyze it as a projectile motion; the figure below shows the kinematic characteristics relevant for this analysis.

Figure. Diagram of a long jump and its kinematic characteristics. The red circle represents the center of mass of the jumper. Adapted from Linthorne (2007).

The distance officially considered as the jump distance, $d_{jump}$, can be described as the sum of three distances as indicated in the previous figure:

\begin{equation}
d_{jump} = d_{take-off} + d_{flight} + d_{landing}
\label{eq_6}
\end{equation}

The jump in the air is the part of the jump that we can describe as a projectile motion and its distance, $d_{flight}$, is typically 90% of the jump distance for professional jumpers.

Let's derive an expression for $d_{flight}$, which we called range before, as a function of the velocity and angle at the take-off. Looking at the long jump diagram, we see that the jumper takes off from a higher height than when he or she lands. The equation for the vertical displacement of the jumper's center of mass during the flight phase will be:

\begin{equation}
h_{landing} = h_{take-off} + v_0\sin(\theta)\:t - \frac{g\:t^2_{flight}}{2}
\label{eq_7}
\end{equation}

For simplicity let's adopt $h_0=h_{take-off}-h_{landing}$:

\begin{equation}
0 = h_0 + v_0\sin(\theta)\:t - \frac{g\:t^2_{flight}}{2}
\label{eq_8}
\end{equation}

Solving this second-order equation for $t_{flight}$:

\begin{equation}
t_{flight} = \frac{v_0 \sin(\theta) \pm \sqrt{v^2_0 \sin^2(\theta) + 2 h_0 g}}{g}
\label{eq_9}
\end{equation}

This expression is different than the one we derived before because the initial and final heights are different in the long jump. However, the above expression reduces to the former expression when this difference is zero, $h_0=0$.

We are only interested in the positive solution of the equation (the negative solution results in negative time) and substituting this result in the equation for the horizontal distance, we have the an expression for $d_{flight}$:

\begin{equation}
d_{flight} = \frac{v_0\cos(\theta)\left[v_0 \sin(\theta) + \sqrt{v^2_0\sin^2(\theta) + 2 h_0 g}\:\right]}{g} \label{eq_10}
\end{equation}

Once again, when $h_0=0$, we have the same expression we derived before.

We now can find the angle that maximizes the range given that the projectile is released (the body jumps) from an initial height. In order to find such angle, we differentiate the previous equation with respect to the variable angle, set this derivative equal to zero, and solve it (read about how to find maxima points in a function [here](http://en.wikipedia.org/wiki/Maxima_and_minima)). It can be shown that the solution is (for a proof, see [here](http://math.stackexchange.com/questions/127300/maximum-range-of-a-projectile-launched-from-an-elevation) or [here](http://www.themcclungs.net/physics/download/H/2_D_Motion/Projectile%20Cliff.pdf)):

\begin{equation}
\theta = \arctan\left(\frac{v_0}{\sqrt{v_0^2 + 2gh_0}}\right)
\label{eq_11}
\end{equation}

For $h_0=0$, the angle will be $\arctan(1)$, which is equal to $45^o$, as obtained before. However, higher the height, smaller than one the fraction and smaller will be the optimum angle. Let's write a function to calculate the optimum angle given the initial height and velocity:

```python
def angle(height, velocity):
    '''Opitmum angle for projectile motion with initial `height` and `velocity`.
    '''
    import math

    g = 9.8  # gravitational acceleration, m/s2

    ang = 0
    if velocity != 0:
        ang = math.atan(velocity/math.sqrt(velocity**2+2*g*height))*180/math.pi

    return ang

```

Let's determine the optimum angle for an initial height of 0.6 m and an initial velocity of 9 m/s (actual values for athletes of long jump):

```python
height=.6
velocity=9
ang = angle(height, velocity)
print('The optimum angle for a jump with h=%.1f m and v=%.1f m/s is %.1f degrees'
          %(height, velocity, ang))
```

    The optimum angle for a jump with h=0.6 m and v=9.0 m/s is 43.1 degrees

We can even plot the optimum angle for different values of initial height and velocity (e.g., heights from 0 to 1 m and velocities from 5 to 10 m/s):

```python
# Import the necessary libraries
import numpy as np
%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import seaborn as sns
sns.set(rc={'axes.facecolor': 'white', 'figure.facecolor': 'white'})
sns.set_context("notebook", font_scale=1.2, rc={"lines.linewidth": 2, "lines.markersize": 10})
```

```python
angles = np.zeros((21, 21))
for h in range(0, 21):
    for v in range(0, 21):
        angles[h, v] = angle(height=h/20, velocity=v/4+5)

v, h = np.meshgrid(np.arange(5, 10.25, .25), np.arange(0, 1.05, 0.05))

fig = plt.figure(figsize=(8, 5))
ax = fig.gca(projection='3d')
surf = ax.plot_surface(h, v, angles, rstride=1, cstride=1, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)
ax.set_xlim(0, 1)
ax.set_ylim(5, 10)
ax.set_zlim(np.min(angles), 45)
ax.set_xlabel('Initial height (m)', fontsize=12)
ax.set_ylabel('Initial velocity (m/s)', fontsize=12)
ax.set_zlabel('Optimum angle ( $^o$)', fontsize=12)
ax.set_title('Projectile motion: optimum angle for maximum range', fontsize=16, y=1.04)
plt.show()
```

    <IPython.core.display.Javascript object>

However, we have a problem if we want to apply these results to understand the long jump of athletes: the typical angles of take-off among the athletes vary between 15$^o$ and 27$^o$ (Linthorne, 2007), much lower than we predicted. Neither the Physics or the simplifications we adopted are wrong.

It happens that humans are unable to run at the horizontal direction with a speed of 10 m/s and suddenly change their direction to 45$^o$ without losing a large amount of that speed. The contact time of the foot with the ground just before jumping for an athlete of long jump is about 10 ms. Our muscles don't have the capacity to act fast enough and generate the necessary amount of force in such short time.

But don't think then the physics of projectile motion is useless for understanding the jumps by humans, quite the contrary. It is possible to model the mechanical and physiological properties of the human body and simulate a long jump (using the equations of projectile motion) which will reproduce most of the long jump characteristics, see for example, Alexander (1990) and Seyfarth et al. (2000). We can even use this kind of modeling to simulate what would happen if the athlete changes some of his or her technique.

See experimental values for the kinematic properties of long jumps measured during competitions in the websites [Research Projects](http://www.iaaf.org/development/research) (from the International Association of Athletics Federations) and [Análise Biomecânica do Salto em Distância](http://demotu.org/x/salto/) (in Portuguese).

## Problems

1. An athlete during a long jump had at the moment of takeoff a velocity of (8.10, 3.58, 0) [in m/s] where $\mathbf{x, y, z}$ are the horizontal (anterior-posterior), vertical, and medio-lateral directions, respectively.
 a. Calculate the magnitude of the velocity at the takeoff.
 b. Calculate the angle of the jump at the takeoff.
 c. Calculate the jump distance.
 d. The actual jump distance was 6.75 m. Comment about the difference between this value and the distance found in (c).

2. A person throws a ball upward (vertically) into the air with an initial speed of 10 m/s and initial height of release of 2 m. Determine the following quantities for the ball:
 a. Maximum height.
 b. Total time in the air.
 c. Velocity just before impact with the ground.
 d. Plots for position, velocity, and acceleration.

3. An athlete of diving jumps from a 10-m height platform and reaches the water at a horizontal distance of 5 m after 2.5 s. Calculate the following quantities for the diver's center of mass:
 a. Initial speed and angle at the takeoff.
 b. Maximum height reached.

4. Investigate and propose at least three different methods to measure these kinematic quantities during training and sporting events. Discuss the limitations and advantages of each method considering their application in different sports.

## References

- Alexander RM (1990) [Optimum take-off techniques for high and long jumps](http://rstb.royalsocietypublishing.org/cgi/pmidlookup?view=long&pmid=1976267). Philosophical transactions of the Royal Society of London. Series B, Biological sciences. 329(1252), 3-10.
- [Análise Biomecânica do Salto em Distância](http://demotu.org/x/salto/)
- Linthorne NP (2007) Biomechanics of the long jump. In Routledge Handbook of Biomechanics and Human Movement Science, Y. Hong and R. Bartlett (Editors), Routledge, London. pp. 340–353.
- [Research Projects](http://www.iaaf.org/development/research) from the International Association of Athletics Federations.
- Seyfarth A, Blickhan R, Van Leeuwen JL (2000) [Optimum take-off techniques and muscle design for long jump](http://jeb.biologists.org/cgi/pmidlookup?view=long&pmid=10648215). Journal of Experimental Biology, 203(Pt 4), 741-50.


```python
#Import packages
from scipy import optimize,arange
from numpy import array
import numpy as np
import matplotlib.pyplot as plt
import random
import sympy as sm
from math import *
%matplotlib inline
from IPython.display import Markdown, display
import pandas as pd

a = sm.symbols('a')
c_vec = sm.symbols('c_vec')
b = sm.symbols('b')
q_vec = sm.symbols('q_vec')
```

```python
N = 1000 # number of firms
np.random.seed(1986)
#c_low = 1
#c_high = 5
#c_vec = np.random.uniform(c_low,c_high,size=N)
```

```python
#first we define the profit function for firm i:
#sumq = np.sum(q_vec)
#Pi_i = q_vec*((a-b*sumq)-c_vec)
Pi_i = q_vec*((a-b*np.sum(q_vec))-c_vec)

```

```python
focs = sm.diff(Pi_i,q_vec)
focs
```

$\displaystyle a - 2 b q_{vec} - c_{vec}$

```python
def focs(a,b,q_vec,c_vec):
    # Using analytical FOCs from pdf-link -> kom frem til denne ved sympy!!
    return -b*q_vec+a-b*np.sum(q_vec)-c_vec
```

```python
def solve(a,b,c_vec):
    obj = lambda q_vec : focs(a,b,q_vec,c_vec)

    res = optimize.root(obj, x0 = [90 for n in range(N)])

    q_vec_star = res.x
    Q = np.sum(q_vec_star)

    # Use inverse demand to find price
    p = a- b*Q
    return Q, p
```

```python
Q,p = solve(a,b,c_vec)
print (Q,p)

```

    978.8015079161239 3.119849208387606

```python
n_vec = list(range(100,1000))

output_vec1 = array([solve(a,b,c_vec)for n in n_vec])

fig,ax = plt.subplots()
ax.plot(n_vec,output_vec1[:,0], label = '$X$')
plt.legend()
plt.show()
```

```python

```


# Characterization of Discrete Systems in the Spectral Domain

*This Jupyter notebook is part of a [collection of notebooks](../index.ipynb) in the bachelors module Signals and Systems, Communications Engineering, Universität Rostock. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Phase and Group Delay

The [phase and group delay](https://en.wikipedia.org/wiki/Group_delay_and_phase_delay) characterize the phase and delay properties of an linear time-invariant (LTI) system. Both quantify the frequency dependent delay that is imprinted on a signal when passing through a system. In many applications the delay introduced by a system should be as small as possible or within reasonable limits.

### Phase Delay

For an LTI system with transfer function $H(e^{j \Omega})$ the phase delay in samples is defined as follows

\begin{equation}
t_p(\Omega) = - \frac{\varphi(e^{j \Omega})}{\Omega}
\end{equation}

where $\varphi(e^{j \Omega}) = \arg \{ H(e^{j \Omega}) \}$ denotes the phase of the transfer function. The phase delay quantifies the delay of a single harmonic exponential signal $e^{j \Omega k}$ with normalized frequency $\Omega$ when passing through the system. The negative sign in the definition of the phase delay results in a positive phase delay $t_p(\Omega) > 0$ when a signal is delayed by a system. Note, the phase delay may not be defined for $\Omega = 0$.

**Example - Phase delay of second-order recursive system**

The phase delay $t_p(\Omega)$ for the before introduced [second-order recursive LTI system](difference_equation.ipynb#Second-Order-System) with transfer function

\begin{equation}
H(z) = \frac{1}{1 - z^{-1} + \frac{1}{2} z^{-2}}
\end{equation}

is computed. First the transfer function is defined

```python
import sympy as sym
sym.init_printing()
%matplotlib inline

z = sym.symbols('z', complex=True)
W = sym.symbols('Omega', real=True)
H = 1 / (1 - z**(-1) + sym.Rational(1, 2)*z**(-2))
H
```

Now the phase delay $t_p(\Omega)$ is computed and plotted for illustration

```python
phi = sym.arg(H.subs(z, sym.exp(sym.I*W)))
tp = -phi/W

sym.plot(tp, (W, -sym.pi, sym.pi), xlabel='$\Omega$', ylabel='$t_p(\Omega)$ in samples')
```

    <sympy.plotting.plot.Plot at 0x116750d68>

### Group Delay

The group delay is defined as the derivative of the phase with respect to the normalized frequency

\begin{equation}
t_g(\Omega) = - \frac{\mathrm{d} \varphi(e^{j \Omega})}{\mathrm{d} \Omega}
\end{equation}

given in samples.

The group delay quantifies the delay the amplitude envelope of a group of exponential signals observes when passing through a system. The negative sign in above definition results in a positive group delay for a system imposing a delay onto the input signal. Note that the [phase](https://en.wikipedia.org/wiki/Instantaneous_phase) $\varphi(e^{j \Omega})$ is in general only unique for $- \pi < \varphi(e^{j \Omega}) \leq \pi$. If the phase exceeds this range it is wrapped back. For meaningful results it is required to unwrap the phase before computing the group delay.

**Example - Phase delay of second-order recursive system**

The group delay $t_g(\Omega)$ of above second-order recursive system is computed and plotted

```python
tg = - sym.diff(phi, W)
sym.plot(tg, (W, -sym.pi, sym.pi), xlabel='$\Omega$', ylabel='$t_g(\Omega)$ in samples')
```

    <sympy.plotting.plot.Plot at 0x1167c3e80>

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Continuous- and Discrete-Time Signals and Systems - Theory and Computational Examples*.


```python
from devito import *
from examples.seismic.source import WaveletSource, TimeAxis
from examples.seismic import plot_image
import numpy as np

from sympy import init_printing, latex
init_printing(use_latex=True)
```

```python
# Initial grid: 1km x 1km, with spacing 100m
extent = (2000., 2000.)
shape = (81, 81)
x = SpaceDimension(name='x', spacing=Constant(name='h_x', value=extent[0]/(shape[0]-1)))
z = SpaceDimension(name='z', spacing=Constant(name='h_z', value=extent[1]/(shape[1]-1)))
grid = Grid(extent=extent, shape=shape, dimensions=(x, z))
```

```python
class DGaussSource(WaveletSource):

    def wavelet(self, f0, t):
        a = 0.004
        return -2.*a*(t - 1./f0) * np.exp(-a * (t - 1./f0)**2)

# Timestep size from Eq. 7 with V_p=6000. and dx=100
t0, tn = 0., 200.
dt = 1e2*(1. / np.sqrt(2.)) / 60.
time_range = TimeAxis(start=t0, stop=tn, step=dt)

src = DGaussSource(name='src', grid=grid, f0=0.01, time_range=time_range)
src.coordinates.data[:] = [1000., 1000.]
src.show()
```

```python
# Now we create the velocity and pressure fields
p = TimeFunction(name='p', grid=grid, staggered=NODE, space_order=2, time_order=1)
vx= TimeFunction(name='vx', grid=grid, staggered=x, space_order=2, time_order=1)
vz = TimeFunction(name='vz', grid=grid, staggered=z, space_order=2, time_order=1)
```

    p(t, x, z) node (0, 0, 0)
    p(t, x, z) {t: left, x: left, z: left} <function staggered_diff at 0x7f7faacf1510>
    vx(t, x, z) x (0, 1, 0)
    vx(t, x, z) {t: left, x: right, z: left} <function staggered_diff at 0x7f7faacf1510>
    vz(t, x, z) z (0, 0, 1)
    vz(t, x, z) {t: left, x: left, z: right} <function staggered_diff at 0x7f7faacf1510>

```python
t = grid.stepping_dim
time = grid.time_dim

# We need some initial conditions
V_p = 4.0
#V_s = 1.0
density = 3.

dx = 100.
rox = 1/density * dt
roz = 1/density * dt
l2m = V_p*V_p*density * dt

c1 = 9.0/8.0;
c2 = -1.0/24.0;

# The source injection term
src_p = src.inject(field=p.forward, expr=src)

# 2nd order acoustic according to fdelmoc
u_vx_2 = Eq(vx.forward, vx + rox * p.dx)
u_vz_2 = Eq(vz.forward, vz + roz * p.dz)
u_p_2 = Eq(p.forward, p + l2m * (vx.forward.dx + vz.forward.dz))

op_2 = Operator([u_vx_2, u_vz_2, u_p_2] + src_p)

# 4th order acoustic according to fdelmoc
# Now we create the velocity and pressure fields
p4 = TimeFunction(name='p', grid=grid, staggered=NODE,space_order=4, time_order=1)
vx4= TimeFunction(name='vx', grid=grid, staggered=x, space_order=4, time_order=1)
vz4 = TimeFunction(name='vz', grid=grid, staggered=z, space_order=4, time_order=1)
u_vx_2 = Eq(vx4.forward, vx4 + rox * p4.dx)
u_vz_2 = Eq(vz4.forward, vz4 + roz * p4.dz)
u_p_2 = Eq(p4.forward, p4 + l2m * (vx4.forward.dx + vz4.forward.dz))

op_4 = Operator([u_vx_2, u_vz_2, u_p_2] + src_p)
```

    p(t, x, z) node (0, 0, 0)
    p(t, x, z) {t: left, x: left, z: left} <function staggered_diff at 0x7f7faacf1510>
    vx(t, x, z) x (0, 1, 0)
    vx(t, x, z) {t: left, x: right, z: left} <function staggered_diff at 0x7f7faacf1510>
    vz(t, x, z) z (0, 0, 1)
    vz(t, x, z) {t: left, x: left, z: right} <function staggered_diff at 0x7f7faacf1510>

```python
### 2nd order acoustic

# Reset the fields
vx.data[:] = 0.
vz.data[:] = 0.
p.data[:] = 0.

op_2(time=src.time_range.num-1)

# Let's see what we got....
plot_image(vx.data[0])
plot_image(vz.data[0])
plot_image(p.data[0])
```

```python
### 4th order acoustic

op_4(time=src.time_range.num-1)

# Let's see what we got....
plot_image(vx4.data[0])
plot_image(vz4.data[0])
plot_image(p4.data[0])
```

```python

```


```python
import numpy as np
from mpmath import hyp2f1
from mpmath import ln as arbprec_ln
from scipy.special import gammaln
from scipy.special import gamma as gammafn
import pandas as pd

import matplotlib.pyplot as plt
```

```python
# first make mpmath's hypgeom vectorizable for convenience
np_2f1 = np.frompyfunc(
    lambda a, b, c, z: hyp2f1(a, b, c, z), 4, 1
)
```

#### Let's try one of the easy cases
(00+) has an easy minimal solution for $n\rightarrow\infty$: it's just $_2F_1(a,b,c+n;z)$ for $Re(z)<1/2$. The TTRR is
\begin{align}
(c+n)(c+n-1)(z-1)y_{n-1} + (c+n)[c+n-1-(2c+2n-a-b-1)z]y_n + (c+n-a)(c+n-b)zy_{n+1} = 0
\end{align}
So if we start with $n$ large and run backwards to $n$ small, the claim is that we should maintain numerical stability. Let's see. Rearrange TTRR to facilitate this:
\begin{align}
y_{n-1} = -\frac{1}{(c+n)(c+n-1)(z-1)} [(c+n)[c+n-1-(2(c+n)-a-b-1)z]y_n + (c+n-a)(c+n-b)zy_{n+1}]
\end{align}

```python
# rev recursion
n = np.arange(0,100)
a = 4.5
b = 0.2
c = 3.8
z = 0.2
mp_math2F1 = np.asfarray(np_2f1(a, b, c+n, z))
min2F1 = np.zeros_like(mp_math2F1)
min2F1[-2:] = mp_math2F1[-2:]
# python indexing rules make the indexing here horribly confusing,
# haven't yet figured out a better/more transparent way
for i, k in enumerate(np.arange(n[-1]-1, n[0], -1)):
    ck = c+k
    prefac_n = ck * (ck - 1 - (2 * ck - a - b - 1)*z)
    prefac_np1 = (ck - a) * (ck - b) * z
    denom = ck * (ck - 1) * (z - 1)
    min2F1[-(3+i)] = - (prefac_n * min2F1[-(2+i)] + prefac_np1 * min2F1[-(1+i)]) / denom
plt.semilogy(n, mp_math2F1)
plt.semilogy(n, min2F1);
```

Ok, so that's pretty magical. Play with different a,b,c and see many different behaviors, but the minimal solution tracks perfectly.

#### Harder case of actual interest
Now, how do we translate things when the minimal solution is not so simple? Let's try and run (+00) backwards. From 15.2.10 in Abramowitz & Stegun, the TTRR is
\begin{align}
(c-a)F(a-1,b,c;z) + (2a-c-az+bz)F(a,b,c;z) + a(z-1)F(a+1,b,c;z)=0.
\end{align}
To run backwards, rearrange to
\begin{align}
F(a-1,b,c;z) = - \frac{1}{(c-a)}[ (2a-c-az+bz)F(a,b,c;z) + a(z-1)F(a+1,b,c;z) ].
\end{align}
I think what Pearson et al failed to explain was the indexing, though in hindsight it's "obvious." When you relabel $a\rightarrow a+k$, you have to do that everywhere in the above equation, giving
\begin{align}
y_{k-1} = - \frac{1}{(c-a-k)}[ (2(a+k)-c-(a+k)z+bz)y_k + (a+k)(z-1)y_{k+1} ].
\end{align}
The solutions of the TTRR are not just plain ${_2F_1(a,b,c,z)}$, there is a set of $y_k$ that satisfy the TTRR with extra prefactors and different parameters. Because of all the symmetry transformations that ${_2F_1}$ satisfies, any single TTRR has many related solutions.

Gil et al claim that the minimal solution as $k\rightarrow\infty$ for this, on the real line for $0<z<2$, is (eq 4.3)
\begin{align}
y_{4,k} = \frac{\Gamma(1+a-c+k)}{\Gamma(1+a+b-c+k)} {_2F_1}(a+k,b,1+a+b-c+k;1-z).
\end{align}
The confusing thing that I finally get: the recursion relations are labeled by (+00) or (+0-) or whatever, but those aren't the increments you actually use them to generate. Each recursion relation has a set of six solutions, the $y_k$ in Gil et al notation. You literally just plug in the $y_k$ into their TTRR: the $a,b,c$ in the complicated expressions in the $y_k$ correspond to the $a,b,c$ in the TTRR. In the example here, the TTRR increments $a$ only, but the solution $y_{4,k}$ increments its 1st _and_ 3rd arguments, so it generates all $_2F_1$ of the form ${_2F_1}(a+k,b,1+a+b-c+k;1-z)$. So if you want to know ${_2F_1}(a^\prime+k,b^\prime,c^\prime+k;z^\prime)$ for some values of $a^\prime+k,b^\prime,c^\prime+k;z^\prime$, you're in luck, this is the TTRR and the solution that'll do that, and you can get it for a whole bunch of $k$ with ease, if you start at the max $k$ and recurse down to smaller $k$. Set $b=b^\prime, a=a^\prime, c=1+a+b-c^\prime$, and $z=1-z^\prime$ and you'll get what you want.

```python
# rev recursion
k_burst = 5.2
mean_n = 3.6
kR_on = 4.5
kR_off = 3
rate_sum = k_burst + kR_off + kR_on
sqrt_discrim = np.sqrt((rate_sum)**2 - 4*k_burst*kR_off)
alpha = (rate_sum + sqrt_discrim) / 2.0
beta = (rate_sum - sqrt_discrim) / 2.0
gamma = kR_on + kR_off

n = np.arange(10,20)
a = alpha
b = gamma - beta
c = beta - alpha - 1
z = (1 + mean_n)**(-1)
gamma_numer = gammafn(1+a-c+n)
gamma_denom = gammafn(1+a+b-c+n)
mp_math2F1 = np.asfarray(np_2f1(a+n, b, 1+a+b-c+n, 1-z))
min2F1 = np.zeros_like(np.asfarray(n))
dum2F1 = np.zeros_like(min2F1)
dum2F1[-2:] = mp_math2F1[-2:]
min2F1[-1] = (np.exp(gammaln(1+a-c+n[-1]) - gammaln(1+a+b-c+n[-1]))
              * np_2f1(a+n[-1],b,1+a+b-c+n[-1],1-z))
min2F1[-2] = (np.exp(gammaln(1+a-c+n[-2]) - gammaln(1+a+b-c+n[-2]))
              * np_2f1(a+n[-2],b,1+a+b-c+n[-2],1-z))
# python indexing rules make the indexing here horribly confusing,
# haven't yet figured out a better/more transparent way
for i, k in enumerate(np.arange(n[-1]-1, n[0], -1)):
    apk = a+k
    prefac_k = 2*apk - c + (b - apk)*z
    prefac_kplus1 = apk*(z-1)
    denom = c - apk
    min2F1[-(3+i)] = - (prefac_k * min2F1[-(2+i)] + prefac_kplus1 * min2F1[-(1+i)]) / denom
    dum2F1[-(3+i)] = - (prefac_k * dum2F1[-(2+i)] + prefac_kplus1 * dum2F1[-(1+i)]) / denom
min2F1 *= (gamma_denom/gamma_numer)
plt.semilogy(n, mp_math2F1, label='arb precision calculation')
plt.plot(n, min2F1, '--', label='minimal solution recursion');
plt.plot(n, dum2F1, label='naive recursion')
plt.legend();
```

The naive recursion is so bad, I pretty sure I'm doing something dumb there. I added it as an afterthought and didn't think carefully about what I was calculating. It should gradually accumulate error, but it appears to diverge immediately. Not worth the time right now, maybe another time.

Recursion with the minimal solution seems robust (fractional error below $10^{-8}$, often down to $\sim10^{-13}$ for $a,b,c\lesssim5$) for $n$ up to 100 or so, even if $a,b,c$ are as large as 20 or 30 and even with funny signs. MUST have $0<z<2$ but that's fine. Use the expression for $P_m$ in the form
\begin{align}
P(m) = (prefac)\times {_2F_1}(\alpha+m, k_R^+ + k_R^- - \beta, k_R^+ + k_R^- + m;\frac{b}{1+b}).
\end{align}
With that we don't even have to restrict $b>1$ like I feared we might have had to with some of the other forms.

I think it's breaking for very large $n$ because of the gamma fcns, it could probably handle even higher values if I implemented Euler's reflection formula and wrapped `scipy.gammaln` rather than working with linear scale gamma. May be unnecessary to handle even larger $n\gtrsim100$ though. _Realized later that, since the rates that go into parameters are strictly positive, it follows from some algebra that the gamma fcn args are also strictly positive. So don't bother._