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

import models
import payoffs
import plotting
import selection_functions
import symbolics
```

```python
%matplotlib inline
plt.style.use("ggplot")
```

# One-locus model

Start from full model. Set $x_3=x_4=0, x_1, x_2=1 - x1$. Monomorphic with $\gamma=G$ only.

```python
x1 = sym.symbols('x1', real=True, positive=True)
T, R, P, S = sym.symbols('T, R, P, S', real=True, positive=True)
M = sym.symbols("M", real=True, positive=True)
epsilon = sym.symbols("epsilon", real=True, positive=True)

UGA = symbolics.UGA
UgA = symbolics.UgA
```

# Total offspring (fitness)

```python
x = np.array([[x1], [1-x1], [0], [0]])
payoff_kernel = np.array([[R, S], [T, P]])
W = models.generalized_sexual_selection(x, UGA, UgA, payoff_kernel, M=M, m=0, mutation_rate=epsilon)
N, = models.total_offspring(W, x)
```

```python
N = sym.simplify(N)
```

```python
sym.factor(N, UGA(x1))
```

$\displaystyle 4.0 \left(- 0.5 M + 0.5 P + \left(- 1.0 P + 0.5 S + 0.5 T\right) \operatorname{UGA}{\left(x_{1} \right)} + \left(0.5 P + 0.5 R - 0.5 S - 0.5 T\right) \operatorname{UGA}^{2}{\left(x_{1} \right)}\right)$

After a bit of algebra we arrive at the following expression for total offspring.

\begin{align}
   % N(x_1) =& 2.0*P*UGA(x1)**2 - 4.0*P*UGA(x1) + 2.0*P + 2.0*R*UGA(x1)**2 - 2.0*S*UGA(x1)**2 + 2.0*S*UGA(x1) - 2.0*T*UGA(x1)**2 + 2.0*T*UGA(x1)\\
   % N(x_1) =& 2\bigg(PU_{GA}(x_1)^2 - 2PU_{GA}(x_1) + P + RU_{GA}(x_1)^2 - SU_{GA}(x_1)^2 + SU_{GA}(x_1) - TU_{GA}(x_1)^2 + TU_{GA}(x_1)\bigg) \\
   % N(x_1) =& 2\bigg(RU_{GA}(x_1)^2 + PU_{GA}(x_1)^2 - TU_{GA}(x_1)^2 - SU_{GA}(x_1)^2 + SU_{GA}(x_1) + TU_{GA}(x_1) - 2PU_{GA}(x_1) + P\bigg) \\
    N(x_1) =& 2\bigg(\big((R + P) - (T + S)\big)U_{GA}(x_1)^2 + \big((T + S) - 2P\big)U_{GA}(x_1) + P\bigg) \\
\end{align}

# The locus of potential equilibria (LPE)

```python
(f,), _, _, _ = models.offspring_genotypes_evolution(W, x)
```

```python
# solving for the LPE using Python
x1_star, = sym.solve(f, x1, implicit=True)
x1_star =  sym.cancel(x1_star)
```

$$ \frac{2\big(U_G(S - M) + (R - S)U_G^2\big)}{F(U_G)} $$

```python
x1_star
```

$\displaystyle - \frac{1.0 \left(4.0 M \epsilon \operatorname{UGA}{\left(x_{1} \right)} - 4.0 M \epsilon - 2.0 M \operatorname{UGA}{\left(x_{1} \right)} + 4.0 P \epsilon \operatorname{UGA}^{2}{\left(x_{1} \right)} - 8.0 P \epsilon \operatorname{UGA}{\left(x_{1} \right)} + 4.0 P \epsilon + 2.0 R \operatorname{UGA}^{2}{\left(x_{1} \right)} - 2.0 S \operatorname{UGA}^{2}{\left(x_{1} \right)} + 2.0 S \operatorname{UGA}{\left(x_{1} \right)} - 4.0 T \epsilon \operatorname{UGA}^{2}{\left(x_{1} \right)} + 4.0 T \epsilon \operatorname{UGA}{\left(x_{1} \right)}\right)}{4.0 M \epsilon + 2.0 M - 4.0 P \epsilon \operatorname{UGA}^{2}{\left(x_{1} \right)} + 8.0 P \epsilon \operatorname{UGA}{\left(x_{1} \right)} - 4.0 P \epsilon - 2.0 P \operatorname{UGA}^{2}{\left(x_{1} \right)} + 4.0 P \operatorname{UGA}{\left(x_{1} \right)} - 2.0 P - 4.0 R \epsilon \operatorname{UGA}^{2}{\left(x_{1} \right)} - 2.0 R \operatorname{UGA}^{2}{\left(x_{1} \right)} + 4.0 S \epsilon \operatorname{UGA}^{2}{\left(x_{1} \right)} - 4.0 S \epsilon \operatorname{UGA}{\left(x_{1} \right)} + 2.0 S \operatorname{UGA}^{2}{\left(x_{1} \right)} - 2.0 S \operatorname{UGA}{\left(x_{1} \right)} + 4.0 T \epsilon \operatorname{UGA}^{2}{\left(x_{1} \right)} - 4.0 T \epsilon \operatorname{UGA}{\left(x_{1} \right)} + 2.0 T \operatorname{UGA}^{2}{\left(x_{1} \right)} - 2.0 T \operatorname{UGA}{\left(x_{1} \right)}}$

```python
x1_star = sym.factor(x1_star.subs({UGA(x1): 1}))
```

```python
x1_star.subs({UGA(x1): 1, M: 0})
```

$\displaystyle \frac{0.5}{1.0 \epsilon + 0.5}$

# Invadability

Restrict the full model to obtain a monomorphic $\gamma$ equilibrium, add a small amount of individuals carrying the $g$ allele of the $\gamma$ gene and simulate.

```python
x1, x2, x3 = sym.symbols("x1, x2, x3", real=True, positive=True)
x4 = 1 - x1 - x2 - x3
xA, xG = sym.symbols("x_A, x_G", real=True, positive=True)
x = np.array([[x1], [x2], [x3], [x4]])
W = models.generalized_sexual_selection(x, UGA, UgA, payoff_kernel, M=M, mutation_rate=epsilon)
f1, f2, f3, _ = models.offspring_genotypes_evolution(W, x)
```

```python
F = sym.Matrix([f1, f2, f3])
```

```python
F_jac = F.jacobian([x1, x2, x3])
```

```python
sym.simplify(x1_star.subs({UGA(x1): UG}))
```

$\displaystyle \frac{4.0 M UG \epsilon - 2.0 M UG - 4.0 M \epsilon + 4.0 P UG^{2} \epsilon - 8.0 P UG \epsilon + 4.0 P \epsilon + 2.0 R UG^{2} - 2.0 S UG^{2} + 2.0 S UG - 4.0 T UG^{2} \epsilon + 4.0 T UG \epsilon}{- 4.0 M \epsilon - 2.0 M + 4.0 P UG^{2} \epsilon + 2.0 P UG^{2} - 8.0 P UG \epsilon - 4.0 P UG + 4.0 P \epsilon + 2.0 P + 4.0 R UG^{2} \epsilon + 2.0 R UG^{2} - 4.0 S UG^{2} \epsilon - 2.0 S UG^{2} + 4.0 S UG \epsilon + 2.0 S UG - 4.0 T UG^{2} \epsilon - 2.0 T UG^{2} + 4.0 T UG \epsilon + 2.0 T UG}$

```python
UG = sym.symbols("UG", positive=True, real=True)
Ug = sym.symbols("Ug", positive=True, real=True)
UG_prime = sym.symbols("UG_prime", positive=True, real=True)

evaluated_F_jac = sym.simplify(F_jac.subs({x2: 1 - x1, x3:0})
                                    .doit()
                                    .subs({UGA(x1): UG, UgA(x1): Ug, sym.Derivative(UGA(x1), x1): UG_prime}))

```

```python
eigenvals = evaluated_F_jac.eigenvals()
```

```python
e1, e2, e3 = eigenvals.keys()
```

```python
x1_star.subs({UGA(x1): UG})
```

$\displaystyle \frac{0.5 \left(M - R\right)}{\left(1.0 M - 1.0 R\right) \left(1.0 \epsilon + 0.5\right)}$

```python
UG = sym.symbols("UG", positive=True, real=True)
Ug = sym.symbols("Ug", positive=True, real=True)
UG_prime = sym.symbols("UG_prime", positive=True, real=True)

evaluated_F_jac = sym.simplify(F_jac.subs({x2: 1 - x1, x3:0})
           .doit()
           .subs({UGA(x1): UG, UgA(x1): Ug, sym.Derivative(UGA(x1), x1): UG_prime})
           .subs({x1: x1_star.subs({UGA(x1): UG})})
           .subs({Ug: x1_star.subs({UGA(x1): UG})})
           .subs({UG: 1}))
```

```python
eigenvals = evaluated_F_jac.eigenvals()
```

```python
e1, e2, e3 = eigenvals.keys()
```

```python
e1
```

$\displaystyle - \frac{2 M UG_{prime} \epsilon - M UG_{prime} + 2 M \epsilon + M - 2 R \epsilon - R - 2 T UG_{prime} \epsilon + T UG_{prime}}{2 M - 2 R}$

```python
e2.subs({M: 0})
```

$\displaystyle - \frac{- 8 P \epsilon^{3} + 12 P \epsilon^{2} - 24 R \epsilon^{2} - 26 R \epsilon - 3 R - 4 S \epsilon^{2} + 6 S \epsilon - 4 T \epsilon^{2} + 6 T \epsilon + 2 \epsilon \sqrt{16 P^{2} \epsilon^{4} + 128 P R \epsilon^{4} + 32 P R \epsilon^{3} - 8 P R \epsilon^{2} + 16 P S \epsilon^{3} + 16 P T \epsilon^{3} + 16 R^{2} \epsilon^{2} + 24 R^{2} \epsilon + 9 R^{2} + 16 R S \epsilon^{2} + 12 R S \epsilon + 64 R T \epsilon^{3} + 16 R T \epsilon^{2} - 4 R T \epsilon + 4 S^{2} \epsilon^{2} + 8 S T \epsilon^{2} + 4 T^{2} \epsilon^{2}} + \sqrt{16 P^{2} \epsilon^{4} + 128 P R \epsilon^{4} + 32 P R \epsilon^{3} - 8 P R \epsilon^{2} + 16 P S \epsilon^{3} + 16 P T \epsilon^{3} + 16 R^{2} \epsilon^{2} + 24 R^{2} \epsilon + 9 R^{2} + 16 R S \epsilon^{2} + 12 R S \epsilon + 64 R T \epsilon^{3} + 16 R T \epsilon^{2} - 4 R T \epsilon + 4 S^{2} \epsilon^{2} + 8 S T \epsilon^{2} + 4 T^{2} \epsilon^{2}}}{- 32 R \epsilon^{2} - 32 R \epsilon - 8 R}$

```python
str(e3.subs({M: 0}))
```

    '-(-8*P*epsilon**3 + 12*P*epsilon**2 - 24*R*epsilon**2 - 26*R*epsilon - 3*R - 4*S*epsilon**2 + 6*S*epsilon - 4*T*epsilon**2 + 6*T*epsilon - 2*epsilon*sqrt(16*P**2*epsilon**4 + 128*P*R*epsilon**4 + 32*P*R*epsilon**3 - 8*P*R*epsilon**2 + 16*P*S*epsilon**3 + 16*P*T*epsilon**3 + 16*R**2*epsilon**2 + 24*R**2*epsilon + 9*R**2 + 16*R*S*epsilon**2 + 12*R*S*epsilon + 64*R*T*epsilon**3 + 16*R*T*epsilon**2 - 4*R*T*epsilon + 4*S**2*epsilon**2 + 8*S*T*epsilon**2 + 4*T**2*epsilon**2) - sqrt(16*P**2*epsilon**4 + 128*P*R*epsilon**4 + 32*P*R*epsilon**3 - 8*P*R*epsilon**2 + 16*P*S*epsilon**3 + 16*P*T*epsilon**3 + 16*R**2*epsilon**2 + 24*R**2*epsilon + 9*R**2 + 16*R*S*epsilon**2 + 12*R*S*epsilon + 64*R*T*epsilon**3 + 16*R*T*epsilon**2 - 4*R*T*epsilon + 4*S**2*epsilon**2 + 8*S*T*epsilon**2 + 4*T**2*epsilon**2))/(-32*R*epsilon**2 - 32*R*epsilon - 8*R)'

```python
delta = (64*P**2*epsilon**4 +
         32*P*R*epsilon**2 +
         64*P*S*epsilon**3 +
         64*P*T*epsilon**3 +
         4*R**2 +
         16*R*S*epsilon +
         16*R*T*epsilon +
         16*S**2*epsilon**2 +
         32*S*T*epsilon**2 +
         16*T**2*epsilon**2 +

         128*P*R*epsilon**4 +
         32*P*R*epsilon**3 -
         16*R**2*epsilon**2 +
         24*R**2*epsilon +
         8*R**2 +
         16*R*S*epsilon**2 +
         64*R*T*epsilon**3 +
         16*R*T*epsilon**2 +
         8*R*S*epsilon -
         48*P**2*epsilon**4 -
         24*P*R*epsilon**2 -
         48*P*S*epsilon**3 -
         48*P*T*epsilon**3 -
         3*R**2 -
         12*R*S*epsilon -
         12*R*T*epsilon -
         12*S**2*epsilon**2 -
         24*S*T*epsilon**2 -
         12*T**2*epsilon**2)

```

```python
A = -8*P*epsilon**3 + 12*P*epsilon**2 - 24*R*epsilon**2 - 26*R*epsilon - 3*R - 4*S*epsilon**2 + 6*S*epsilon - 4*T*epsilon**2 + 6*T*epsilon
```

```python
A / (2*epsilon + 1)**2
```

$\displaystyle \frac{- 8 P \epsilon^{3} + 12 P \epsilon^{2} - 24 R \epsilon^{2} - 26 R \epsilon - 3 R - 4 S \epsilon^{2} + 6 S \epsilon - 4 T \epsilon^{2} + 6 T \epsilon}{\left(2 \epsilon + 1\right)^{2}}$

```python
n = (128*P*R*epsilon**4 +
         32*P*R*epsilon**3 -
         #16*R**2*epsilon**2 +
         8*R**2*epsilon +
         4*R**2 +
         16*R*S*epsilon**2 +
         64*R*T*epsilon**3 +
         16*R*T*epsilon**2 +
         8*R*S*epsilon)
d = (2*epsilon + 1)**4
sym.simplify(n / d)
```

$\displaystyle \frac{4 R \left(32 P \epsilon^{4} + 8 P \epsilon^{3} - 2 R \epsilon + R + 4 S \epsilon^{2} + 2 S \epsilon + 16 T \epsilon^{3} + 4 T \epsilon^{2}\right)}{\left(2 \epsilon + 1\right)^{4}}$

```python
K = (128*P*R*epsilon**4 +
     32*P*R*epsilon**3 -
     8*R**2*epsilon +
     4*R**2 +
     16*R*S*epsilon**2 +
     64*R*T*epsilon**3 +
     16*R*T*epsilon**2 +
     8*R*S*epsilon)
```

```python
sym.simplify(-K - A)
```

$\displaystyle - 128 P R \epsilon^{4} - 32 P R \epsilon^{3} + 8 P \epsilon^{3} - 12 P \epsilon^{2} + 8 R^{2} \epsilon - 4 R^{2} - 16 R S \epsilon^{2} - 8 R S \epsilon - 64 R T \epsilon^{3} - 16 R T \epsilon^{2} + 24 R \epsilon^{2} + 26 R \epsilon + 3 R + 4 S \epsilon^{2} - 6 S \epsilon + 4 T \epsilon^{2} - 6 T \epsilon$

```python
_epsilon = 4e-1
e2.subs({T: 4, R: 2, P: 1, S: 0.5, M: 0, epsilon: _epsilon}) < 0
```

$\displaystyle \text{True}$

```python
e3.subs({T: 4, R: 2, P: 1, S: 0.5, M: 0, epsilon: _epsilon}) < 0
```

$\displaystyle \text{True}$

```python
N, = models.total_offspring(W, x)
```

```python
sym.expand(sym.simplify(N.subs({x2: 1 - x1, x3: 0}).subs({UGA(x1): x1_star.subs({UGA(x1): 1, M: 0})}).subs({M: 0}))**2)
```

$\displaystyle \frac{4.0 P^{2} \epsilon^{4}}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{2.0 P R \epsilon^{2}}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{4.0 P S \epsilon^{3}}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{4.0 P T \epsilon^{3}}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{0.25 R^{2}}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{1.0 R S \epsilon}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{1.0 R T \epsilon}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{1.0 S^{2} \epsilon^{2}}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{2.0 S T \epsilon^{2}}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625} + \frac{1.0 T^{2} \epsilon^{2}}{1.0 \epsilon^{4} + 2.0 \epsilon^{3} + 1.5 \epsilon^{2} + 0.5 \epsilon + 0.0625}$

```python
sym.simplify(N.subs({x2: 1 - x1, x3: 0}).subs({UGA(x1): x1})).subs({x1: x1_star}).subs({M: 0, P: 1, S: 1 / R, T: 2 * R}).subs({R: 2}).subs({epsilon: 4e-1})
```

$\displaystyle 3.85185185185185$

```python
N.subs({T: 4, R: 2, P: 1, S: 0.5, M: 0, epsilon: 1e-2, UGA(x1): x1_star.subs({UGA(x1): 1})})
```

$\displaystyle x_{1} \left(\frac{0.005 x_{3} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.25 \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{1} \left(\frac{0.495 x_{3} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.25 \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{1} \left(\frac{0.01 x_{1} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.5 x_{2} \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1} + \frac{0.005 x_{3} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.25 \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{1} \left(\frac{0.99 x_{1} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.5 x_{2} \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1} + \frac{0.495 x_{3} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.25 \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{2} \left(\frac{0.25 x_{3} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.005 \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{2} \left(\frac{0.25 x_{3} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.495 \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{2} \left(\frac{0.5 x_{1} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.01 x_{2} \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1} + \frac{0.25 x_{3} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.005 \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{2} \left(\frac{0.5 x_{1} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.99 x_{2} \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1} + \frac{0.25 x_{3} \left(3.0 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UGA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.495 \left(1 - \operatorname{UGA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UGA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{3} \left(\frac{0.005 x_{1} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.25 x_{2} \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1}\right) + x_{3} \left(\frac{0.495 x_{1} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.25 x_{2} \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1}\right) + x_{3} \left(\frac{0.005 x_{1} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.25 x_{2} \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1} + \frac{0.01 x_{3} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.5 \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + x_{3} \left(\frac{0.495 x_{1} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.25 x_{2} \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1} + \frac{0.99 x_{3} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.5 \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + \left(\frac{0.25 x_{1} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.005 x_{2} \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1}\right) \left(- x_{1} - x_{2} - x_{3} + 1\right) + \left(\frac{0.25 x_{1} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.495 x_{2} \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1}\right) \left(- x_{1} - x_{2} - x_{3} + 1\right) + \left(- x_{1} - x_{2} - x_{3} + 1\right) \left(\frac{0.25 x_{1} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.005 x_{2} \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1} + \frac{0.5 x_{3} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.01 \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right) + \left(- x_{1} - x_{2} - x_{3} + 1\right) \left(\frac{0.25 x_{1} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.495 x_{2} \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right)}{- x_{1} - x_{3} + 1} + \frac{0.5 x_{3} \left(3.0 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 1.0\right) \operatorname{UgA}{\left(x_{1} + x_{3} \right)}}{x_{1} + x_{3}} + \frac{0.99 \left(1 - \operatorname{UgA}{\left(x_{1} + x_{3} \right)}\right) \left(6 \operatorname{UgA}{\left(x_{1} + x_{3} \right)} + 2\right) \left(- x_{1} - x_{2} - x_{3} + 1\right)}{- x_{1} - x_{3} + 1}\right)$

```python

```

```python
import pickle

with open('non-invadable-eigenvalues.pickle', 'wb') as handle:
    pickle.dump(eigenvals, handle, protocol=pickle.HIGHEST_PROTOCOL)
```

```python
e1, e2, e3 = eigenvals.keys()
```

```python
numerator, denominator = sym.fraction(sym.factor(e1))
```

```python
denominator
```

    2*(P*UGA_star**2 - 2*P*UGA_star + P + R*UGA_star**2 - S*UGA_star**2 + S*UGA_star - T*UGA_star**2 + T*UGA_star)**2

```python
upper_bound, = sym.solve(numerator, UGA_prime_star)
```

```python
sym.factor(upper_bound)
```

    (2*epsilon + 1)*(P*UGA_star**2 - 2*P*UGA_star + P + R*UGA_star**2 - S*UGA_star**2 + S*UGA_star - T*UGA_star**2 + T*UGA_star)**2/((2*epsilon - 1)*(2*P*R*UGA_star**2 - 2*P*R*UGA_star - P*S*UGA_star**2 + 2*P*S*UGA_star - P*S - R*T*UGA_star**2))

```python
e1.subs({UGA_star: 1, UGA_prime_star: 0})
```

    -(2*R**2*epsilon + R**2)/(2*R**2)

```python
# vectorized numeric repr for the eigenvalue
_numeric_e2 = sym.lambdify((UGA_star, UgA_star, T, R, P, S, epsilon),
                           e2.subs({x1: x1_star}),
                           modules="numpy")
```

```python
def plot_second_eigenvalue(T, R, P, S, epsilon):

    fig, ax = plt.subplots(1, 1, figsize=(10, 10))
    ax.set_ylabel(r"$U_{GA}^*$", fontsize=20, rotation="horizontal")
    ax.set_xlabel(r"$U_{gA}^{*}$", fontsize=20)
    ax.set_title(r"Eigenvalue, $e_2$", fontsize=25)
    ax.grid("off")

    equilibrium_selection_probs = np.linspace(0, 1, 100)
    UgAs, UGAs = np.meshgrid(equilibrium_selection_probs, equilibrium_selection_probs)
    Z = _numeric_e2(UGAs, UgAs, T, R, P, S, epsilon)
    cax = ax.imshow(Z, origin="lower")
    contours = ax.contour(Z, colors='w', origin='lower')
    ax.clabel(contours, contours.levels, inline=True, fontsize=10)

    # adjust the tick labels
    locs, _ = plt.xticks()
    plt.xticks(locs[1:], np.linspace(0, 1, locs.size-1))
    locs, _ = plt.yticks()
    plt.yticks(locs[1:], np.linspace(0, 1, locs.size-1))

    plt.show()
```

```python
mpld3.disable_notebook()  # don't need interactive plotting for this!

# sliders used to control the Prisoner's Dilemma Payoffs
T_slider = widgets.FloatSlider(value=10, min=0, max=100, step=0.1, description=r"$T$")
R_slider = widgets.FloatSlider(value=8, min=0, max=100, step=0.1, description=r"$R$")
P_slider = widgets.FloatSlider(value=6, min=0, max=100, step=0.1, description=r"$P$")
S_slider = widgets.FloatSlider(value=4, min=0, max=100, step=0.1, description=r"$S$")

# slider used to control the mutation rate
e_slider = widgets.FloatSlider(value=0.0, min=0.0, max=1.0, step=1e-3, description=r"$\epsilon$", readout_format=".3f")

w = widgets.interactive(plot_second_eigenvalue, T=T_slider, R=R_slider, P=P_slider, S=S_slider, epsilon=e_slider)
display(w)
```

    A Jupyter Widget

```python
evaluated = (e2.subs({x1: x1_star, UgA_star: x1_star})
               .subs({UGA_star: 1, UGA_prime_star: 0}))
together = sym.together(evaluated)
```

```python
expanded = sym.expand(together)
```

```python
expanded
```

    -4.0*P*epsilon**4/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) + 6.0*P*epsilon**3/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) - 12.0*R*epsilon**3/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) - 13.0*R*epsilon**2/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) - 1.5*R*epsilon/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) - 2.0*S*epsilon**3/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) + 3.0*S*epsilon**2/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) - 2.0*T*epsilon**3/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) + 3.0*T*epsilon**2/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon) + 0.015625*sqrt(65536.0*P**2*epsilon**8 + 65536.0*P**2*epsilon**7 + 16384.0*P**2*epsilon**6 + 524288.0*P*R*epsilon**8 + 655360.0*P*R*epsilon**7 + 229376.0*P*R*epsilon**6 - 8192.0*P*R*epsilon**4 + 65536.0*P*S*epsilon**7 + 65536.0*P*S*epsilon**6 + 16384.0*P*S*epsilon**5 + 65536.0*P*T*epsilon**7 + 65536.0*P*T*epsilon**6 + 16384.0*P*T*epsilon**5 + 65536.0*R**2*epsilon**6 + 163840.0*R**2*epsilon**5 + 151552.0*R**2*epsilon**4 + 61440.0*R**2*epsilon**3 + 9216.0*R**2*epsilon**2 + 65536.0*R*S*epsilon**6 + 114688.0*R*S*epsilon**5 + 65536.0*R*S*epsilon**4 + 12288.0*R*S*epsilon**3 + 262144.0*R*T*epsilon**7 + 327680.0*R*T*epsilon**6 + 114688.0*R*T*epsilon**5 - 4096.0*R*T*epsilon**3 + 16384.0*S**2*epsilon**6 + 16384.0*S**2*epsilon**5 + 4096.0*S**2*epsilon**4 + 32768.0*S*T*epsilon**6 + 32768.0*S*T*epsilon**5 + 8192.0*S*T*epsilon**4 + 16384.0*T**2*epsilon**6 + 16384.0*T**2*epsilon**5 + 4096.0*T**2*epsilon**4)/(16.0*R*epsilon**3 + 16.0*R*epsilon**2 + 4.0*R*epsilon)

```python
tmp_expr = sym.together(expanded)
```

```python
tmp_expr
```

    (-4.0*P*epsilon**3 + 6.0*P*epsilon**2 - 12.0*R*epsilon**2 - 13.0*R*epsilon - 1.5*R - 2.0*S*epsilon**2 + 3.0*S*epsilon - 2.0*T*epsilon**2 + 3.0*T*epsilon + 0.015625*sqrt(65536.0*P**2*epsilon**6 + 65536.0*P**2*epsilon**5 + 16384.0*P**2*epsilon**4 + 524288.0*P*R*epsilon**6 + 655360.0*P*R*epsilon**5 + 229376.0*P*R*epsilon**4 - 8192.0*P*R*epsilon**2 + 65536.0*P*S*epsilon**5 + 65536.0*P*S*epsilon**4 + 16384.0*P*S*epsilon**3 + 65536.0*P*T*epsilon**5 + 65536.0*P*T*epsilon**4 + 16384.0*P*T*epsilon**3 + 65536.0*R**2*epsilon**4 + 163840.0*R**2*epsilon**3 + 151552.0*R**2*epsilon**2 + 61440.0*R**2*epsilon + 9216.0*R**2 + 65536.0*R*S*epsilon**4 + 114688.0*R*S*epsilon**3 + 65536.0*R*S*epsilon**2 + 12288.0*R*S*epsilon + 262144.0*R*T*epsilon**5 + 327680.0*R*T*epsilon**4 + 114688.0*R*T*epsilon**3 - 4096.0*R*T*epsilon + 16384.0*S**2*epsilon**4 + 16384.0*S**2*epsilon**3 + 4096.0*S**2*epsilon**2 + 32768.0*S*T*epsilon**4 + 32768.0*S*T*epsilon**3 + 8192.0*S*T*epsilon**2 + 16384.0*T**2*epsilon**4 + 16384.0*T**2*epsilon**3 + 4096.0*T**2*epsilon**2))/(R*(16.0*epsilon**2 + 16.0*epsilon + 4.0))

```python
tmp_expr.subs({epsilon**2: 0})
```

    (-13.0*R*epsilon - 1.5*R + 3.0*S*epsilon + 3.0*T*epsilon + 0.015625*sqrt(61440.0*R**2*epsilon + 9216.0*R**2 + 12288.0*R*S*epsilon - 4096.0*R*T*epsilon))/(R*(16.0*epsilon + 4.0))

\begin{align}
   % (-13.0*R*epsilon - 1.5*R + 3.0*S*epsilon + 3.0*T*epsilon + 0.015625*sqrt(61440.0*R**2*epsilon + 9216.0*R**2 + 12288.0*R*S*epsilon - 4096.0*R*T*epsilon))/(R*(16.0*epsilon + 4.0)) < 0 \\
    \frac{(-13R\epsilon - 1.5R + 3S\epsilon + 3T\epsilon + 0.015625\sqrt{61440R^2\epsilon + 9216R^2 + 12288RS\epsilon - 4096RT\epsilon}}{R(16\epsilon + 4.0)} < 0 \\
    \frac{(-13R\epsilon - \frac{3}{2}R + 3S\epsilon + 3T\epsilon + \frac{1}{64}\sqrt{61440R^2\epsilon + 9216R^2 + 12288RS\epsilon - 4096RT\epsilon}}{R(16\epsilon + 4.0)} < 0 \\
    \frac{(-13R\epsilon - \frac{3}{2}R + 3S\epsilon + 3T\epsilon + \frac{1}{2}\sqrt{60R^2\epsilon + 9R^2 + 12RS\epsilon - 4RT\epsilon}}{R(16\epsilon + 4)} < 0
\end{align}

```python
numerator, denominator = sym.fraction(sym.factor(e2))
```

```python
denominator
```

    8*x1*(x1 - 1)*(P*UGA_star**2 - 2*P*UGA_star + P + R*UGA_star**2 - S*UGA_star**2 + S*UGA_star - T*UGA_star**2 + T*UGA_star)

```python
# vectorized numeric repr for the eigenvalue
_numeric_e3 = sym.lambdify((UGA_star, UgA_star, T, R, P, S, epsilon),
                           e3.subs({x1: x1_star}),
                           modules="numpy")
```

```python
def plot_third_eigenvalue(T, R, P, S, epsilon):

    fig, ax = plt.subplots(1, 1, figsize=(10, 10))
    ax.set_ylabel(r"$U_{GA}^*$", fontsize=20, rotation="horizontal")
    ax.set_xlabel(r"$U_{gA}^{*}$", fontsize=20)
    ax.set_title(r"Eigenvalue, $e_3$", fontsize=25)
    ax.grid("off")

    equilibrium_selection_probs = np.linspace(0, 1, 100)
    UgAs, UGAs = np.meshgrid(equilibrium_selection_probs, equilibrium_selection_probs)
    Z = _numeric_e3(UGAs, UgAs, T, R, P, S, epsilon)
    cax = ax.imshow(Z, origin="lower")
    contours = ax.contour(Z, colors='w', origin='lower')
    ax.clabel(contours, contours.levels, inline=True, fontsize=10)

    # adjust the tick labels
    locs, _ = plt.xticks()
    plt.xticks(locs[1:], np.linspace(0, 1, locs.size-1))
    locs, _ = plt.yticks()
    plt.yticks(locs[1:], np.linspace(0, 1, locs.size-1))

    plt.show()
```

```python
mpld3.disable_notebook()  # don't need interactive plotting for this!

# sliders used to control the Prisoner's Dilemma Payoffs
T_slider = widgets.FloatSlider(value=25, min=0, max=100, step=0.1, description=r"$T$")
R_slider = widgets.FloatSlider(value=3, min=0, max=100, step=0.1, description=r"$R$")
P_slider = widgets.FloatSlider(value=2, min=0, max=100, step=0.1, description=r"$P$")
S_slider = widgets.FloatSlider(value=1, min=0, max=100, step=0.1, description=r"$S$")

# slider used to control the mutation rate
e_slider = widgets.FloatSlider(value=0.0, min=0.0, max=1.0, step=1e-3, description=r"$\epsilon$", readout_format=".3f")

w = widgets.interactive(plot_third_eigenvalue, T=T_slider, R=R_slider, P=P_slider, S=S_slider, epsilon=e_slider)
display(w)
```

    A Jupyter Widget

```python
e2 == e3
```

    False

```python
sym.simplify(e2.subs({x1: x1_star, UgA_star: x1_star}).subs({UGA_star: 1, UGA_prime_star: 0})).subs({epsilon**2: 0})
```

    (-13.0*R*epsilon - 1.5*R + 3.0*S*epsilon + 3.0*T*epsilon + 0.015625*sqrt(61440.0*R**2*epsilon + 9216.0*R**2 + 12288.0*R*S*epsilon - 4096.0*R*T*epsilon))/(R*(16.0*epsilon + 4.0))

```python
sym.simplify(e3.subs({x1: x1_star, UgA_star: x1_star}).subs({UGA_star: 1, UGA_prime_star: 0})).subs({epsilon**2: 0})
```

    (-13.0*R*epsilon - 1.5*R + 3.0*S*epsilon + 3.0*T*epsilon - 0.015625*sqrt(61440.0*R**2*epsilon + 9216.0*R**2 + 12288.0*R*S*epsilon - 4096.0*R*T*epsilon))/(R*(16.0*epsilon + 4.0))

```python
evaluated = (e3.subs({x1: x1_star, UgA_star: x1_star})
               .subs({UGA_star: 1, UGA_prime_star: 0}))
together = sym.together(evaluated)
```

```python
expanded = sym.expand(together)
```

```python
tmp_expr = sym.together(expanded)
```

```python
tmp_expr.subs({epsilon**2: 0})
```

    (-13.0*R*epsilon - 1.5*R + 3.0*S*epsilon + 3.0*T*epsilon + 0.015625*sqrt(61440.0*R**2*epsilon + 9216.0*R**2 + 12288.0*R*S*epsilon - 4096.0*R*T*epsilon))/(R*(16.0*epsilon + 4.0))

```python
(-13.0*R*epsilon - 1.5*R + 3.0*S*epsilon + 3.0*T*epsilon + 0.015625*sqrt(61440.0*R**2*epsilon + 9216.0*R**2 + 12288.0*R*S*epsilon - 4096.0*R*T*epsilon))/(R*(16.0*epsilon + 4.0))
```

```python

```

First eigenvalue can be written as a hyperbola in $U_{gA}^*,U_{GA}^*$ plane.

$$ e_1 = \left(\frac{\big((R + P) - (T + S)\big)U_{gA}^{*2} + \big((T + S) - 2P\big)U_{gA}^*}{N^*}\right) - \left(\frac{\big((R + P) - (T + S)\big)U_{GA}^{*2} + \big((T + S) - 2P\big)U_{GA}^*}{N^*}\right) $$

### Completing the square

To write this hyperbola in standard form we need to [complete the square](https://en.wikipedia.org/wiki/Completing_the_square). Completing the square for the quadratic polynomial in $U_{GA}(x^*)$ yields the following.

\begin{align}
    \big((R + P) - (T + S)\big)\left(U_{GA}^* - \bar{U}_{GA}^*\right)^2 - \frac{1}{2}\big(\bar{N}^* - 2P\big) \\
\end{align}

where $\bar{U}_{GA}^*$ is the value of $U_{GA}^*$ that maximizes total offspring $N$ derived above. Completing the square for the quadratic polynomial in $U_{gA}(x^*)$ yields a similar expression.

\begin{align}
    \big((R + P) - (T + S)\big)\left(U_{gA}^* - \bar{U}_{GA}^*\right)^2 - \frac{1}{2}\big(\bar{N}^* - 2P\big) \\
\end{align}

Substituting these results into the expression for the eigenvalue yields the following.

$$ e_1 = \left(\frac{\big(U_{gA}^* - \bar{U}_{GA}^*\big)^{2}}{\frac{N^*}{\big((R + P) - (T + S)\big)}}\right) - \left(\frac{\big(U_{GA}^* - \bar{U}_{GA}^*\big)^{2}}{\frac{N^*}{\big((R + P) - (T + S)\big)}}\right) $$

### Conditions for negative eigenvalue

Non-invadability requires that this eigenvalue is strictly negative.

$$ \left(\frac{\big(U_{gA}^* - \bar{U}_{GA}^*\big)^{2}}{\frac{N^*}{\big((R + P) - (T + S)\big)}}\right) - \left(\frac{\big(U_{GA}^* - \bar{U}_{GA}^*\big)^{2}}{\frac{N^*}{\big((R + P) - (T + S)\big)}}\right) < 0$$

#### Case: (R + P) < (T + S)
In this case the inequality simplifies to the following.

$$ \big(U_{gA}^* - \bar{U}_{GA}^*\big)^{2} - \big(U_{GA}^* - \bar{U}_{GA}^*\big)^{2} > 0 $$

This implies that we have four sub-cases to consider.

\begin{align}
    \big(U_{gA}^* - \bar{U}_{GA}^*\big) >& \big(U_{GA}^* - \bar{U}_{GA}^*\big) \\
    \big(U_{gA}^* - \bar{U}_{GA}^*\big) >& -\big(U_{GA}^* - \bar{U}_{GA}^*\big) \\
    -\big(U_{gA}^* - \bar{U}_{GA}^*\big) >& \big(U_{GA}^* - \bar{U}_{GA}^*\big) \\
    -\big(U_{gA}^* - \bar{U}_{GA}^*\big) >& -\big(U_{GA}^* - \bar{U}_{GA}^*\big)
\end{align}

These four sub-cases define the following two regions of the $U_{gA}^*,U_{GA}^*$ plane where the eigenvalue is negative.

\begin{align}
   & 2\big(\bar{U}_{GA}^* - U_{gA}^*\big) < U_{GA}^* - U_{gA}^* < 0\\
   & 0 < U_{GA}^* - U_{gA}^* < 2\big(\bar{U}_{GA}^* - U_{gA}\big)
\end{align}

#### Case: (R + P) > (T + S)
In this case the inequality simplifies to the following.

$$ \big(U_{gA}^* - \bar{U}_{GA}^*\big)^{2} - \big(U_{GA}^* - \bar{U}_{GA}^*\big)^{2} < 0 $$

This implies that we again have four sub-cases to consider.

\begin{align}
    \big(U_{gA}^* - \bar{U}_{GA}^*\big) <& \big(U_{GA}^* - \bar{U}_{GA}^*\big) \\
    \big(U_{gA}^* - \bar{U}_{GA}^*\big) <& -\big(U_{GA}^* - \bar{U}_{GA}^*\big) \\
    -\big(U_{gA}^* - \bar{U}_{GA}^*\big) <& \big(U_{GA}^* - \bar{U}_{GA}^*\big) \\
    -\big(U_{gA}^* - \bar{U}_{GA}^*\big) <& -\big(U_{GA}^* - \bar{U}_{GA}^*\big)
\end{align}

These four sub-cases define the following two regions of the $U_{gA}^*,U_{GA}^*$ plane where the eigenvalue is negative.

\begin{align}
   & 2\big(\bar{U}_{GA}^* - U_{gA}^*\big) < U_{GA}^* - U_{gA}^* < 0\\
   & 0 < U_{GA}^* - U_{gA}^* < 2\big(\bar{U}_{GA}^* - U_{gA}\big)
\end{align}

### Asymptotes

Asymptotes of this hyperbola can be defined as follows.

$$ U_{GA}^* - \bar{U}_{GA}^* = \pm\big(U_{gA}^* - \bar{U}_{GA}^*\big) $$

The above can be written as linear equations in $U_{gA}^*,U_{GA}^*$ plane.

\begin{align}
    U_{GA}^* =& U_{gA}^* \\
    U_{GA}^* =& 2\bar{U}_{GA}^* - U_{gA}^*
\end{align}

Note that the asymptotes of this hyperbola correspond to the locus of points for which the value of the first eigenvalue is exactly zero.

#### Discussion

Suppose $U_{GA}^* = \bar{U}_{GA}^*$, then both of the sets of inequalities will hold for any value of $U_{gA}^*$. This implies that the eigenvalue will be negative for any value of $U_{gA}^*$.

```python
# vectorized numeric repr for the eigenvalue
_numeric_e1 = sym.lambdify((UGA_star, UGA_prime_star, T, R, P, S, M), e1, modules="numpy")
```

```python
def plot_first_eigenvalue(T, R, P, S, M):

    fig, ax = plt.subplots(1, 1, figsize=(10, 10))
    ax.set_ylabel(r"$U_{GA}^*$", fontsize=20, rotation="horizontal")
    ax.set_xlabel(r"$U_{GA}^{'*}$", fontsize=20)
    ax.set_title(r"Eigenvalue, $e_1$", fontsize=25)
    ax.grid("off")

    equilibrium_selection_probs = np.linspace(0, 1, 100).reshape(-1, 1)
    equilibrium_selection_derivs = np.linspace(0, 10, 1000).reshape(1, -1) # sensible UGA'(0) >= 1
    Z = _numeric_e1(equilibrium_selection_probs, equilibrium_selection_derivs, T, R, P, S, M)
    cax = ax.imshow(Z, origin="lower", aspect="auto", vmin=-0.5, vmax=1.0)

    levels = np.arange(-0.25, 1.25, 0.25)
    contours = ax.contour(Z, levels=levels, colors='w', origin='lower')
    ax.clabel(contours, contours.levels, inline=True, fontsize=10)

    # adjust the tick labels
    locs, _ = plt.xticks()
    plt.xticks(locs[1:], np.linspace(0, 10, locs.size-1))
    locs, _ = plt.yticks()
    plt.yticks(locs[1:], np.linspace(0, 1, locs.size-1))

    plt.show()
```

```python
mpld3.disable_notebook()  # don't need interactive plotting for this!

# sliders used to control the Prisoner's Dilemma Payoffs
T_slider = widgets.FloatSlider(value=25, min=0, max=100, step=0.1, description=r"$T$")
R_slider = widgets.FloatSlider(value=3, min=0, max=100, step=0.1, description=r"$R$")
P_slider = widgets.FloatSlider(value=2, min=0, max=100, step=0.1, description=r"$P$")
S_slider = widgets.FloatSlider(value=1, min=0, max=100, step=0.1, description=r"$S$")

M_slider = widgets.FloatSlider(value=1, min=0, max=100, step=0.1, description=r"$M$")

w = widgets.interactive(plot_first_eigenvalue, T=T_slider, R=R_slider, P=P_slider, S=S_slider, M=M_slider)
display(w)
```

    A Jupyter Widget

```python
UGA_prime_star, = sym.solve(numerator, UGA_prime_star)
```

```python
sym.factor(UGA_prime_star)
```

    -(P*UGA_star**2 - 2*P*UGA_star + P + R*UGA_star**2 - S*UGA_star**2 + S*UGA_star - T*UGA_star**2 + T*UGA_star)**2/(2*P*R*UGA_star**2 - 2*P*R*UGA_star - P*S*UGA_star**2 + 2*P*S*UGA_star - P*S - R*T*UGA_star**2)

After a bit of fiddling, we arrive at the same stability condition derived above.

\begin{align}
   % U'_{GA}(x^*) < -\frac{(PU_{GA}(x^*)^2 - 2PU_{GA}(x^*) + P + RU_{GA}(x^*)^2 - SU_{GA}(x^*)^2 + SU_{GA}(x^*) - TU_{GA}(x^*)^2 + TU_{GA}(x^*))^2}{(2PRU_{GA}(x^*)^2 - 2PRU_{GA}(x^*) - PSU_{GA}(x^*)^2 + 2PSU_{GA}(x^*) - PS - RTU_{GA}(x^*)^2)} \\
    U'_{GA}(x^*) < \frac{\bigg(\big((R + P) - (T + S)\big)U_{GA}(x^*)^2 + \big((T + S) - 2P\big)U_{GA}(x^*) + P\bigg)^2}{\big(R(T-2P) + PS\big)U_{GA}(x^*)^2 + 2P(R - S)U_{GA}(x^*) + PS}
\end{align}

```python
e2
```

    (-3*M + 3*P*UGA_star**2 - 6*P*UGA_star - P*UgA_star**2 + 2*P*UgA_star + 2*P + 3*R*UGA_star**2 - R*UgA_star**2 - 3*S*UGA_star**2 + 3*S*UGA_star + S*UgA_star**2 - S*UgA_star - 3*T*UGA_star**2 + 3*T*UGA_star + T*UgA_star**2 - T*UgA_star + m)/(4*M - 4*P*UGA_star**2 + 8*P*UGA_star - 4*P - 4*R*UGA_star**2 + 4*S*UGA_star**2 - 4*S*UGA_star + 4*T*UGA_star**2 - 4*T*UGA_star)

$$ e_2 = \left(\frac{\big((R + P) - (T + S)\big)U_{gA}^{*2} + \big((T + S) - 2P\big)U_{gA}^* + P}{4N^*}\right) - \frac{3}{4} $$

### Completing the square

Completing the square for the quadratic polynomial in $U_{gA}(x^*)$ yields the following expression.

\begin{align}
    \big((R + P) - (T + S)\big)\left(U_{gA}^* - \bar{U}_{GA}^*\right)^2 + \frac{1}{2}\bar{N}^* \\
\end{align}

Finally we need to find conditions under which the third eigenvalue is strictly negative.

```python
# vectorized numeric repr for the eigenvalue
_numeric_e2 = sym.lambdify((UGA_star, UgA_star, T, R, P, S, M, m), e2, modules="numpy")
```

```python
def plot_second_eigenvalue(T, R, P, S, M, m):

    fig, ax = plt.subplots(1, 1, figsize=(10, 10))
    ax.set_ylabel(r"$U_{GA}^*$", fontsize=20, rotation="horizontal")
    ax.set_xlabel(r"$U_{gA}^{*}$", fontsize=20)
    ax.set_title(r"Eigenvalue, $e_2$", fontsize=25)
    ax.grid("off")

    equilibrium_selection_probs = np.linspace(0, 1, 100)
    UgAs, UGAs = np.meshgrid(equilibrium_selection_probs, equilibrium_selection_probs)
    Z = _numeric_e2(UGAs, UgAs, T, R, P, S, M, m)
    cax = ax.imshow(Z, origin="lower")
    contours = ax.contour(Z, colors='w', origin='lower')
    ax.clabel(contours, contours.levels, inline=True, fontsize=10)

    # adjust the tick labels
    locs, _ = plt.xticks()
    plt.xticks(locs[1:], np.linspace(0, 1, locs.size-1))
    locs, _ = plt.yticks()
    plt.yticks(locs[1:], np.linspace(0, 1, locs.size-1))

    plt.show()
```

```python
mpld3.disable_notebook()  # don't need interactive plotting for this!

# sliders used to control the Prisoner's Dilemma Payoffs
T_slider = widgets.FloatSlider(value=25, min=0, max=100, step=0.1, description=r"$T$")
R_slider = widgets.FloatSlider(value=3, min=0, max=100, step=0.1, description=r"$R$")
P_slider = widgets.FloatSlider(value=2, min=0, max=100, step=0.1, description=r"$P$")
S_slider = widgets.FloatSlider(value=1, min=0, max=100, step=0.1, description=r"$S$")

M_slider = widgets.FloatSlider(value=1, min=0, max=100, step=0.1, description=r"$M$")
m_slider = widgets.FloatSlider(value=0, min=0, max=100, step=0.1, description=r"$m$")

w = widgets.interactive(plot_second_eigenvalue, T=T_slider, R=R_slider, P=P_slider, S=S_slider, M=M_slider, m=m_slider)
display(w)
```

    A Jupyter Widget

```python
e3
```

    (-M + P*UGA_star**2 - 2*P*UGA_star - P*UgA_star**2 + 2*P*UgA_star + R*UGA_star**2 - R*UgA_star**2 - S*UGA_star**2 + S*UGA_star + S*UgA_star**2 - S*UgA_star - T*UGA_star**2 + T*UGA_star + T*UgA_star**2 - T*UgA_star + m)/(2*M - 2*P*UGA_star**2 + 4*P*UGA_star - 2*P - 2*R*UGA_star**2 + 2*S*UGA_star**2 - 2*S*UGA_star + 2*T*UGA_star**2 - 2*T*UGA_star)

```python
# vectorized numeric repr for the eigenvalue
_numeric_e3 = sym.lambdify((UGA_star, UgA_star, T, R, P, S, M, m), e3, modules="numpy")
```

```python
def plot_third_eigenvalue(T, R, P, S, M, m):

    fig, ax = plt.subplots(1, 1, figsize=(10, 10))
    ax.set_ylabel(r"$U_{GA}^*$", fontsize=20, rotation="horizontal")
    ax.set_xlabel(r"$U_{gA}^{*}$", fontsize=20)
    ax.set_title(r"Eigenvalue, $e_3$", fontsize=25)
    ax.grid("off")

    equilibrium_selection_probs = np.linspace(0, 1, 100)
    UgAs, UGAs = np.meshgrid(equilibrium_selection_probs, equilibrium_selection_probs)
    Z = _numeric_e3(UGAs, UgAs, T, R, P, S, M, m)
    cax = ax.imshow(Z, origin="lower")
    contours = ax.contour(Z, colors='w', origin='lower')
    ax.clabel(contours, contours.levels, inline=True, fontsize=10)

    # adjust the tick labels
    locs, _ = plt.xticks()
    plt.xticks(locs[1:], np.linspace(0, 1, locs.size-1))
    locs, _ = plt.yticks()
    plt.yticks(locs[1:], np.linspace(0, 1, locs.size-1))

    plt.show()
```

```python
mpld3.disable_notebook()  # don't need interactive plotting for this!

# sliders used to control the Prisoner's Dilemma Payoffs
T_slider = widgets.FloatSlider(value=25, min=0, max=100, step=0.1, description=r"$T$")
R_slider = widgets.FloatSlider(value=3, min=0, max=100, step=0.1, description=r"$R$")
P_slider = widgets.FloatSlider(value=2, min=0, max=100, step=0.1, description=r"$P$")
S_slider = widgets.FloatSlider(value=1, min=0, max=100, step=0.1, description=r"$S$")

M_slider = widgets.FloatSlider(value=1, min=0, max=100, step=0.1, description=r"$M$")
m_slider = widgets.FloatSlider(value=0, min=0, max=100, step=0.1, description=r"$m$")

w = widgets.interactive(plot_third_eigenvalue, T=T_slider, R=R_slider, P=P_slider, S=S_slider, M=M_slider, m=m_slider)
display(w)
```

    A Jupyter Widget

## When is the fitness-maximizing interior equilibrium un-invadable by randomista?

```python
evaluated_e3 = e3.subs({UGA_star: optimal_UGA_star, UgA_star: optimal_x1_star, m: 0})
simplified_e3 = sym.factor(sym.cancel(sym.together(evaluated_e3)))
```

```python
numerator, denominator = sym.fraction(simplified_e3)
```

```python
numerator
```

    -4.0*M**3*P**3 - 12.0*M**3*P**2*R + 12.0*M**3*P**2*S + 12.0*M**3*P**2*T - 12.0*M**3*P*R**2 + 24.0*M**3*P*R*S + 24.0*M**3*P*R*T - 12.0*M**3*P*S**2 - 24.0*M**3*P*S*T - 12.0*M**3*P*T**2 - 4.0*M**3*R**3 + 12.0*M**3*R**2*S + 12.0*M**3*R**2*T - 12.0*M**3*R*S**2 - 24.0*M**3*R*S*T - 12.0*M**3*R*T**2 + 4.0*M**3*S**3 + 12.0*M**3*S**2*T + 12.0*M**3*S*T**2 + 4.0*M**3*T**3 + 8.0*M**2*P**3*R + 16.0*M**2*P**2*R**2 - 16.0*M**2*P**2*R*S - 16.0*M**2*P**2*R*T - 2.0*M**2*P**2*S**2 - 4.0*M**2*P**2*S*T - 2.0*M**2*P**2*T**2 + 8.0*M**2*P*R**3 - 16.0*M**2*P*R**2*S - 16.0*M**2*P*R**2*T + 4.0*M**2*P*R*S**2 + 8.0*M**2*P*R*S*T + 4.0*M**2*P*R*T**2 + 4.0*M**2*P*S**3 + 12.0*M**2*P*S**2*T + 12.0*M**2*P*S*T**2 + 4.0*M**2*P*T**3 - 2.0*M**2*R**2*S**2 - 4.0*M**2*R**2*S*T - 2.0*M**2*R**2*T**2 + 4.0*M**2*R*S**3 + 12.0*M**2*R*S**2*T + 12.0*M**2*R*S*T**2 + 4.0*M**2*R*T**3 - 2.0*M**2*S**4 - 8.0*M**2*S**3*T - 12.0*M**2*S**2*T**2 - 8.0*M**2*S*T**3 - 2.0*M**2*T**4 - 4.0*M*P**3*R**2 - 4.0*M*P**2*R**3 + 4.0*M*P**2*R**2*S + 4.0*M*P**2*R**2*T + 2.0*M*P**2*R*S**2 + 4.0*M*P**2*R*S*T + 2.0*M*P**2*R*T**2 + 2.0*M*P*R**2*S**2 + 4.0*M*P*R**2*S*T + 2.0*M*P*R**2*T**2 - 2.0*M*P*R*S**3 - 6.0*M*P*R*S**2*T - 6.0*M*P*R*S*T**2 - 2.0*M*P*R*T**3 - 0.25*M*P*S**4 - 1.0*M*P*S**3*T - 1.5*M*P*S**2*T**2 - 1.0*M*P*S*T**3 - 0.25*M*P*T**4 - 0.25*M*R*S**4 - 1.0*M*R*S**3*T - 1.5*M*R*S**2*T**2 - 1.0*M*R*S*T**3 - 0.25*M*R*T**4 + 0.25*M*S**5 + 1.25*M*S**4*T + 2.5*M*S**3*T**2 + 2.5*M*S**2*T**3 + 1.25*M*S*T**4 + 0.25*M*T**5 - 1.0*P**2*R**2*S**2 + 2.0*P**2*R**2*S*T - 1.0*P**2*R**2*T**2 + 1.0*P**2*R*S**3 - 1.0*P**2*R*S**2*T - 1.0*P**2*R*S*T**2 + 1.0*P**2*R*T**3 - 0.25*P**2*S**4 + 0.5*P**2*S**2*T**2 - 0.25*P**2*T**4 + 1.0*P*R**2*S**3 - 1.0*P*R**2*S**2*T - 1.0*P*R**2*S*T**2 + 1.0*P*R**2*T**3 - 1.0*P*R*S**4 + 2.0*P*R*S**2*T**2 - 1.0*P*R*T**4 + 0.25*P*S**5 + 0.25*P*S**4*T - 0.5*P*S**3*T**2 - 0.5*P*S**2*T**3 + 0.25*P*S*T**4 + 0.25*P*T**5 - 0.25*R**2*S**4 + 0.5*R**2*S**2*T**2 - 0.25*R**2*T**4 + 0.25*R*S**5 + 0.25*R*S**4*T - 0.5*R*S**3*T**2 - 0.5*R*S**2*T**3 + 0.25*R*S*T**4 + 0.25*R*T**5 - 0.0625*S**6 - 0.125*S**5*T + 0.0625*S**4*T**2 + 0.25*S**3*T**3 + 0.0625*S**2*T**4 - 0.125*S*T**5 - 0.0625*T**6

\begin{align}
    4\bigg(-\big(P^3 + 3P^2R + 3PR^2 + R^3\big) - \big(P^3 - 3P^2S + 3PS^2 + S^3\big) + \big(-P^3 + 3P^2T - 3PT^2 + T^3\big) - \big(R^3 - 3R^2S + 3RS^2 - S^3\big) + \big(T^3 - 3RT^2 + 3R^2T - R^3\big) + 2P^3 + 2R^3 + 8PRS + 8PRT - 8PST - 8RST - 2T^3 + \big(S^3 + 3S^2T + 3ST^2 + T^3\big)\bigg)M^3 + 2\big(4P^3R + 8P^2R^2 - 8P^2RS - 8P^2RT - P^2S^2 - 2P^2ST - P^2T^2 + 4PR^3 - 8PR^2S - 8PR^2T + 2PRS^2 + 4PRST + 2PRT^2 + 2PS^3 + 6PS^2T + 6PST^2 + 2PT^3 - R^2S^2 - 2R^2ST - R^2T^2 + 2RS^3 + 6RS^2T + 6RST^2 + 2RT^3 - S^4 - 4S^3T - 6S^2T^2 - 4ST^3 - T^4\big)M^2 + \big(- 4P^3R^2 - 4P^2R^3 + 4P^2R^2S + 4P^2R^2T + 2P^2RS^2 + 4P^2RST + 2P^2RT^2 + 2PR^2S^2 + 4PR^2ST + 2PR^2T^2 - 2PRS^3 - 6PRS^2T - 6PRST^2 - 2PRT^3 - 0.25PS^4 -PS^3T - 1.5PS^2T^2 - PST^3 - 0.25PT^4 - 0.25RS^4 - RS^3T - 1.5RS^2T^2 - RST^3 - 0.25RT^4 + 0.25S^5 + 1.25S^4T + 2.5S^3T^2 + 2.5S^2T^3 + 1.25ST^4 + 0.25T^5\big)M + \big(- P^2R^2S^2 + 2P^2R^2ST - P^2R^2T^2 + P^2RS^3 - P^2RS^2T - P^2RST^2 + P^2RT^3 - 0.25P^2S^4 + 0.5P^2S^2T^2 - 0.25P^2T^4 + PR^2S^3 - PR^2S^2T - PR^2ST^2 + PR^2T^3 - PRS^4 + 2PRS^2T^2 - PRT^4 + 0.25PS^5 + 0.25PS^4T - 0.5PS^3T^2 - 0.5PS^2T^3 + 0.25PST^4 + 0.25PT^5 - 0.25R^2S^4 + 0.5R^2S^2T^2 - 0.25R^2T^4 + 0.25RS^5 + 0.25RS^4T - 0.5RS^3T^2 - 0.5RS^2T^3 + 0.25RST^4 + 0.25RT^5 - 0.0625S^6 - 0.125S^5T + 0.0625S^4T^2 + 0.25S^3T^3 + 0.0625S^2T^4 - 0.125ST^5 - 0.0625T^6\big) \\
    4\bigg(-\big(R + P\big)^3 - \big(P - S\big)^3 + \big(T - P\big)^3 - \big(R - S\big)^3 + \big(T - R\big)^3 + 2P^3 + 2R^3 + 8PRS + 8PRT - 8PST - 8RST - 2T^3 + \big(T + S\big)^3\bigg)M^3 + 2\big(4P^3R + 8P^2R^2 - 8P^2RS - 8P^2RT - P^2S^2 - 2P^2ST - P^2T^2 + 4PR^3 - 8PR^2S - 8PR^2T + 2PRS^2 + 4PRST + 2PRT^2 + 2PS^3 + 6PS^2T + 6PST^2 + 2PT^3 - R^2S^2 - 2R^2ST - R^2T^2 + 2RS^3 + 6RS^2T + 6RST^2 + 2RT^3 - S^4 - 4S^3T - 6S^2T^2 - 4ST^3 - T^4\big)M^2 + \big(- 4P^3R^2 - 4P^2R^3 + 4P^2R^2S + 4P^2R^2T + 2P^2RS^2 + 4P^2RST + 2P^2RT^2 + 2PR^2S^2 + 4PR^2ST + 2PR^2T^2 - 2PRS^3 - 6PRS^2T - 6PRST^2 - 2PRT^3 - 0.25PS^4 -PS^3T - 1.5PS^2T^2 - PST^3 - 0.25PT^4 - 0.25RS^4 - RS^3T - 1.5RS^2T^2 - RST^3 - 0.25RT^4 + 0.25S^5 + 1.25S^4T + 2.5S^3T^2 + 2.5S^2T^3 + 1.25ST^4 + 0.25T^5\big)M + \big(- P^2R^2S^2 + 2P^2R^2ST - P^2R^2T^2 + P^2RS^3 - P^2RS^2T - P^2RST^2 + P^2RT^3 - 0.25P^2S^4 + 0.5P^2S^2T^2 - 0.25P^2T^4 + PR^2S^3 - PR^2S^2T - PR^2ST^2 + PR^2T^3 - PRS^4 + 2PRS^2T^2 - PRT^4 + 0.25PS^5 + 0.25PS^4T - 0.5PS^3T^2 - 0.5PS^2T^3 + 0.25PST^4 + 0.25PT^5 - 0.25R^2S^4 + 0.5R^2S^2T^2 - 0.25R^2T^4 + 0.25RS^5 + 0.25RS^4T - 0.5RS^3T^2 - 0.5RS^2T^3 + 0.25RST^4 + 0.25RT^5 - 0.0625S^6 - 0.125S^5T + 0.0625S^4T^2 + 0.25S^3T^3 + 0.0625S^2T^4 - 0.125ST^5 - 0.0625T^6\big) \\
\end{align}

```python
(S - R)(S^2 - 2SR + R^2) = S^3 - 3RS^2 + 3SR^2 - R^3
```

```python
sym.factor(-P**3 - 3*P**2*R + 3*P**2*S + 3*P**2*T - 3*P*R**2 + 8*P*R*S + 8*P*R*T - 3*P*S**2 - 8*P*S*T - 3*P*T**2 - R**3 + 3*R**2*S + 3*R**2*T - 3*R*S**2 - 8*R*S*T - 3*R*T**2 + S**3 + 3*S**2*T + 3*S*T**2 + T**3)
```

    -P**3 - 3*P**2*R + 3*P**2*S + 3*P**2*T - 3*P*R**2 + 8*P*R*S + 8*P*R*T - 3*P*S**2 - 8*P*S*T - 3*P*T**2 - R**3 + 3*R**2*S + 3*R**2*T - 3*R*S**2 - 8*R*S*T - 3*R*T**2 + S**3 + 3*S**2*T + 3*S*T**2 + T**3

```python
r1, r2, r3 = sym.solve(numerator, M)
```

```python
sym.cancel(sym.together(r1))
```

```python
denominator
```

    (0.5*M*P + 0.5*M*R - 0.5*M*S - 0.5*M*T - 0.5*P*R + 0.125*S**2 + 0.25*S*T + 0.125*T**2)**3

Denominator will be negative if and only if...

\begin{align}
    % 4MP + 4MR - 4MS - 4MT - 4PR + S^2 + 2ST + T^2 < 0 \\
    % 4M\big((R + P) - (T + S)\big) - 4PR + (T + S)^2< 0 \\
    % 4M\big((R + P) - (T + S)\big) < 4RP - (T + S)^2 \\
    M < \frac{1}{4}\frac{4RP - (T + S)^2}{(R + P) - (T + S)} \\
    M < \frac{RP - \left(\frac{T + S}{2}\right)^2}{(R + P) - (T + S)} \\
\end{align}

...since we are looking at interior equilibrium the RHS of the above inequality will be positive.

```python
_f = sym.lambdify((T, R, P, S, M), evaluated_e3, modules = "numpy")

def plot_non_invadable_by_randomista(T, R, P, S):
    fig, ax = plt.subplots(1, 1, figsize=(10,6))
    Ms = np.linspace(0, min(0.5 * (T + S), P), 100)
    ax.plot(Ms, _f(T, R, P, S, Ms))
    ax.set_xlabel(r"$M$", fontsize=15)
    ax.set_ylabel(r"$e_3$", rotation="horizontal", fontsize=15)
    plt.show()
```

```python
# sliders used to control the Prisoner's Dilemma Payoffs
T_slider = widgets.FloatSlider(value=10, min=0, max=100, step=0.1, description=r"$T$")
R_slider = widgets.FloatSlider(value=8, min=0, max=100, step=0.1, description=r"$R$")
P_slider = widgets.FloatSlider(value=6, min=0, max=100, step=0.1, description=r"$P$")
S_slider = widgets.FloatSlider(value=4, min=0, max=100, step=0.1, description=r"$S$")

w = widgets.interactive(plot_non_invadable_by_randomista, T=T_slider, R=R_slider, P=P_slider, S=S_slider)
display(w)
```

    A Jupyter Widget

```python
solutions = sym.solve(simplified_e3, M)
```

```python

```


# Tutorial: Gaussian pulse initial data for a massless scalar field in spherical-like coordinates

## Authors: Leonardo Werneck and Zach Etienne

# This tutorial notebook explains how to obtain time-symmetric initial data for the problem of gravitational collapse of a massless scalar field. We will be following the approaches of [Akbarian & Choptuik (2015)](https://arxiv.org/pdf/1508.01614.pdf) and [Baumgarte (2018)](https://arxiv.org/pdf/1807.10342.pdf).

**Notebook Status**:  Validated

**Validation Notes**: The initial data generated by the NRPy+ module corresponding to this tutorial notebook are used shown to satisfy Einstein's equations as expected [in this tutorial notebook](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_ScalarField_initial_data.ipynb).

## Python module which performs the procedure described in this tutorial: [ScalarField/ScalarField_InitialData.py](../edit/ScalarField/ScalarField_InitialData.py)

## References

* [Akbarian & Choptuik (2015)](https://arxiv.org/pdf/1508.01614.pdf) (Useful to understand the theoretical framework)
* [Baumgarte (2018)](https://arxiv.org/pdf/1807.10342.pdf) (Useful to understand the theoretical framework)
* [Baumgarte & Shapiro's Numerical Relativity](https://books.google.com.br/books/about/Numerical_Relativity.html?id=dxU1OEinvRUC&redir_esc=y): Section 6.2.2 (Useful to understand how to solve the Hamiltonian constraint)

# Table of Contents
$$\label{toc}$$

1. [Step 1](#initial_data) Setting up time-symmetric initial data
    1. [Step 1.a](#id_time_symmetry) Time symmetry: $\tilde{K}_{ij}$, $\tilde K$, $\tilde\beta^{i}$, and $\tilde B^{i}$
    1. [Step 1.b](#id_sf_ic) The scalar field initial condition: $\tilde{\varphi}$, $\tilde{\Phi}$, $\tilde{\Pi}$
    1. [Step 1.c](#id_metric) The physical metric: $\tilde{\gamma}_{ij}$
        1. [Step 1.c.i](#id_conformal_metric) The conformal metric $\bar\gamma_{ij}$
        1. [Step 1.c.ii](#id_hamiltonian_constraint) Solving the Hamiltonian constraint
            1. [Step 1.c.ii.1](#id_tridiagonal_matrix) The tridiagonal matrix: $A$
            1. [Step 1.c.ii.2](#id_tridiagonal_rhs) The right-hand side of the linear system: $\vec{s}$
            1. [Step 1.c.ii.3](#id_conformal_factor) The conformal factor: $\psi$
    1. [Step 1.d](#id_lapse_function) The lapse function: $\tilde{\alpha}$
    1. [Step 1.e](#id_output) Outputting the initial data to file
1. [Step 2](#id_interpolation_files) Interpolating the initial data file as needed
1. [Step 3](#id_sph_to_curvilinear) Converting Spherical initial data to Curvilinear initial data
1. [Step 4](#validation) Validation of this tutorial against the [ScalarField/ScalarField_InitialData.py](../edit/ScalarField/ScalarField_InitialData.py) module
1. [Step 5](#output_to_pdf) Output this module as $\LaTeX$-formatted PDF file

# Step 0: Initialize Python/NRPy+ modules \[Back to [top](#toc)\]
$$\label{initialize_nrpy}$$

```python
# Step 0: Load all needed Python/NRPy+ modules
import os,sys,shutil                    # Standard Python modules for multiplatform OS-level functions
import sympy as sp                      # SymPy: The Python computer algebra package upon which NRPy+ depends
import numpy as np                      # NumPy: A large collection of mathematical functions for Python
from scipy.sparse import spdiags        # SciPy: Sparse, tri-diagonal matrix setup function
from scipy.sparse import csc_matrix     # SciPy: Sparse matrix optimization function
from scipy.sparse.linalg import spsolve # SciPy: Solver of linear systems involving sparse matrices
import outputC as outC                  # NRPy+: Core C code output module
import reference_metric as rfm          # NRPy+: Reference metric support
import cmdline_helper as cmd            # NRPy+: Multi-platform Python command-line interface

# Step 0.a: Create the output directory
Ccodesdir = "ScalarFieldID_validation"
shutil.rmtree(Ccodesdir,ignore_errors=True)
cmd.mkdir(Ccodesdir)
```

# Step 1: Setting up time-symmetric initial data \[Back to [top](#toc)\]
$$\label{initial_data}$$

In this section we will set up time symmetric initial data for the gravitational collapse of a massless scalar field, in spherical coordinates. Our discussion will follow closely section III.A of [Akbarian & Choptuik (2015)](https://arxiv.org/pdf/1508.01614.pdf) (henceforth A&C). We will be using a *uniform* radial sampling. All initial data quantities will be written with tildes over them, meaning that, for example, $\tilde{\alpha} \equiv \alpha(0,r)$.

## Step 1.a: Time symmetry: $\tilde{K}_{ij}$, $\tilde K$, $\tilde\beta^{i}$, and $\tilde B^{i}$ \[Back to [top](#toc)\]
$$\label{id_time_symmetry}$$

We are here considering a spherically symmetric problem, so that $f=f(t,r)$, for every function discussed in this tutorial. The demand for time-symmetric initial data then imples that

\begin{align}
\tilde K_{ij} &= 0\ ,\\
\tilde K &= 0\ ,\\
\tilde \beta^{i} &= 0\ ,\\
\tilde B^{i} &= 0\ .
\end{align}

For the scalar field, $\varphi$, it also demands

$$
\partial_{t}\varphi(0,r) = 0\ ,
$$

which we discuss below.

## Step 1.b: The scalar field initial condition: $\tilde{\varphi}$, $\tilde{\Phi}$, $\tilde{\Pi}$ \[Back to [top](#toc)\]
$$\label{id_sf_ic}$$

We will be implementing the following options for the initial profile of the scalar field

$$
\begin{aligned}
\tilde{\varphi}_{\rm I}   &= \varphi_{0}\exp\left(-\frac{r^{2}}{\sigma^{2}}\right)\ ,\\
\tilde{\varphi}_{\rm II}  &= \varphi_{0}r^{3}\exp\left[-\left(\frac{r-r_{0}}{\sigma}\right)^{2}\right]\ ,\\
\tilde{\varphi}_{\rm III} &= \varphi_{0}\left\{1 - \tanh\left[\left(\frac{r-r_{0}}{\sigma}\right)^{2}\right]\right\}.
\end{aligned}
$$

We introduce the two auxiliary fields

$$
\tilde\Phi\equiv\partial_{r}\tilde\varphi\quad \text{and}\quad \Pi\equiv-\frac{1}{\alpha}\left(\partial_{t}\varphi - \beta^{i}\partial_{i}\varphi\right)\ ,
$$

of which $\tilde\Phi$ will only be used as an auxiliary variable for setting the initial data, but $\Pi$ is a dynamical variable which will be evolved in time. Because we are setting time-symmetric initial data, $\partial_{t}\sf = 0 = \beta^{i}$, and thus $\tilde\Pi=0$.

```python
# Step 1: Setting up time-symmetric initial data
# Step 1.a: Define basic parameters
# Step 1.a.i: Domain size
RMAX = 50

# Step 1.a.ii: Number of gridpoints in the radial direction
NR = 30000

# Step 1.a.iii: Initial data family. Available options are:
#               Gaussian_pulse, Gaussian_pulsev2, and Tanh_pulse
ID_Family = "Gaussian_pulsev2"

# Step 1.a.iv: Coordinate system. Available options are:
#              Spherical and SinhSpherical
CoordSystem = "Spherical"

# Step 1.a.v: SinhSpherical parameters
sinhA = RMAX
sinhW = 0.1

# Step 1.b: Set the radial array
if CoordSystem == "Spherical":
    r  = np.linspace(0,RMAX,NR+1) # Set the r array
    dr = np.zeros(NR)
    for i in range(NR):
        dr[i] = r[1]-r[0]
    r  = np.delete(r-dr[0]/2,0)      # Shift the vector by -dr/2 and remove the negative entry
elif CoordSystem == "SinhSpherical":
    if sinhA is None or sinhW is None:
        print("Error: SinhSpherical coordinates require initialization of both sinhA and sinhW")
        sys.exit(1)
    else:
        x  = np.linspace(0,1.0,NR+1)
        dx = 1.0/(NR+1)
        x  = np.delete(x-dx/2,0)      # Shift the vector by -dx/2 and remove the negative entry
        r  = sinhA * np.sinh( x/sinhW ) / np.sinh( 1.0/sinhW )
        dr = sinhA * np.cosh( x/sinhW ) / np.sinh( 1.0/sinhW ) * dx
else:
    print("Error: Unknown coordinate system")
    sys.exit(1)

# Step 1.c: Step size squared
dr2   = dr**2

# Step 1.d: Set SymPy variables for the initial condition
phi0,rr,rr0,sigma = sp.symbols("phi0 rr rr0 sigma",real=True)

# Step 1.e: Now set the initial profile of the scalar field
if ID_Family == "Gaussian_pulse":
    phiID = phi0 * sp.exp( -r**2/sigma**2 )
elif ID_Family == "Gaussian_pulsev2":
    phiID = phi0 * rr**3 * sp.exp( -(rr-rr0)**2/sigma**2 )
elif ID_Family == "Tanh_pulse":
    phiID = phi0 * ( 1 - sp.tanh( (rr-rr0)**2/sigma**2 ) )
else:
    print("Unkown initial data family: ",ID_Family)
    print("Available options are: Gaussian_pulse, Gaussian_pulsev2, and Tanh_pulse")
    sys.exit(1)

# Step 1.f: Compute Phi := \partial_{r}phi
PhiID = sp.diff(phiID,rr)

# Step 1.g: Generate NumPy functions for phi
#           and Phi from the SymPy variables.
phi = sp.lambdify((phi0,rr,rr0,sigma),phiID)
Phi = sp.lambdify((phi0,rr,rr0,sigma),PhiID)

# Step 1.h: populating the varphi(0,r) array
phi0  = 0.1
r0    = 0
sigma = 1
ID_sf = phi(phi0,r,r0,sigma)
```

## Step 1.c: The physical metric: $\tilde{\gamma}_{ij}$ \[Back to [top](#toc)\]
$$\label{id_metric}$$

### Step 1.c.i: The conformal metric $\bar\gamma_{ij}$ \[Back to [top](#toc)\]
$$\label{id_conformal_metric}$$

To set up the physical metric initial data, $\tilde\gamma_{ij}$, we will start by considering the conformal transformation

$$
\gamma_{ij} = e^{4\phi}\bar\gamma_{ij}\ ,
$$

where $\bar\gamma_{ij}$ is the conformal metric and $e^{\phi}$ is the conformal factor. We then fix the initial value of $\bar\gamma_{ij}$ according to eqs. (32) and (43) of [A&C](https://arxiv.org/pdf/1508.01614.pdf)

$$
\bar\gamma_{ij} = \hat\gamma_{ij}\ ,
$$

where $\hat\gamma_{ij}$ is the *reference metric*, which is the flat metric in spherical symmetry

$$
\hat\gamma_{ij}
=
\begin{pmatrix}
1 & 0 & 0\\
0 & r^{2} & 0\\
0 & 0 & r^{2}\sin^{2}\theta
\end{pmatrix}\ .
$$

To determine the physical metric, we must then determine the conformal factor $e^{\phi}$. This is done by solving the Hamiltonian constraint (cf. eq. (12) of [Baumgarte](https://arxiv.org/pdf/1807.10342.pdf))

$$
\hat\gamma^{ij}\hat D_{i}\hat D_{j}\psi = -2\pi\psi^{5}\rho\ ,
$$

where $\psi\equiv e^{\tilde\phi}$. For a massless scalar field, we know that

$$
T^{\mu\nu} = \partial^{\mu}\varphi\partial^{\nu}\varphi - \frac{1}{2}g^{\mu\nu}\left(\partial^{\lambda}\varphi\partial_{\lambda}\varphi\right)\ .
$$

where $g^{\mu\nu}$ is the inverse of the ADM 4-metric given by eq. (2.119) of [Baumgarte & Shapiro's Numerical Relativity](https://books.google.com.br/books/about/Numerical_Relativity.html?id=dxU1OEinvRUC&redir_esc=y),

$$
g^{\mu\nu}=\begin{pmatrix}
-\alpha^{-2} & \alpha^{-2}\beta^{i}\\
\alpha^{-2}\beta^{j} & \gamma^{ij} - \alpha^{-2}\beta^{i}\beta^{j}
\end{pmatrix}\ .
$$

We know that (see Step 2 in [this tutorial module](Tutorial-ADM_Setting_up_massless_scalarfield_Tmunu.ipynb) for the details)

\begin{align}
\partial^{t}\varphi &= \alpha^{-1}\Pi\ ,\\
\partial^{\lambda}\varphi\partial_{\lambda}\varphi &= -\Pi^{2} + \gamma^{ij}\partial_{i}\varphi\partial_{j}\varphi\ .
\end{align}

The tt-component of the energy-momentum tensor at the initial time is then given by (we will ommit the "tildes" below to avoid cluttering the equation, but keep in mind that all quantities are considered at $t=0$)

\begin{align}
 T^{tt} &= \left(\partial^{t}\varphi\right)^{2} - \frac{1}{2} g^{tt}\left(\partial^{\lambda}\varphi\partial_{\lambda}\varphi\right)\nonumber\\
&= \left(\frac{\Pi}{\alpha}\right)^{2} - \frac{1}{2}\left(-\frac{1}{\alpha^{2}}\right)\left(-\Pi^{2} + \gamma^{ij}\partial_{i}\varphi\partial_{j}\varphi\right)\nonumber\\
&= \frac{\gamma^{ij}\partial_{i}\varphi\partial_{j}\varphi}{2\alpha^{2}}\nonumber\\
&= \frac{e^{-4\phi}\bar\gamma^{ij}\partial_{i}\varphi\partial_{j}\varphi}{2\alpha^{2}}\nonumber\\
&= \frac{e^{-4\phi}\hat\gamma^{ij}\partial_{i}\varphi\partial_{j}\varphi}{2\alpha^{2}}\nonumber\\
&= \frac{e^{-4\phi}\hat\gamma^{rr}\partial_{r}\varphi\partial_{r}\varphi}{2\alpha^{2}}\nonumber\\
&= \frac{e^{-4\phi}\Phi^{2}}{2\alpha^{2}}\nonumber\\
\end{align}

By remembering the definition of the normal vector $n_{\mu} = (-\alpha,0,0,0)$ (eq. (2.117) of [B&S](https://books.google.com.br/books/about/Numerical_Relativity.html?id=dxU1OEinvRUC&redir_esc=y)), we can then evaluate the energy density $\rho$ given by eq. (24) of [A&C](https://arxiv.org/pdf/1508.01614.pdf)

$$
\tilde\rho = \tilde n_{\mu}\tilde n_{\nu}\tilde T^{\mu\nu} = \frac{e^{-4\tilde\phi}}{2}\tilde\Phi^{2}\ .
$$

Plugging this result in the Hamiltonian constraint, remembering that $\psi\equiv e^{\tilde\phi}$, we have

$$
\partial^{2}_{r}\psi + \frac{2}{r}\partial_{r}\psi + \pi\psi\Phi^{2} = 0\ .
$$

This is a linear elliptic equation which will solve using the procedure described in detail in section 6.2.2 of [B&S](https://books.google.com.br/books/about/Numerical_Relativity.html?id=dxU1OEinvRUC&redir_esc=y).

### Step 1.c.ii: Solving the Hamiltonian constraint \[Back to [top](#toc)\]
$$\label{id_hamiltonian_constraint}$$

We will discretize the Hamiltonian constraint using [second-order accurate finite differences](https://en.wikipedia.org/wiki/Finite_difference_coefficient). We get

$$
\frac{\psi_{i+1} - 2\psi_{i} + \psi_{i-1}}{\Delta r^{2}} + \frac{2}{r_{i}}\left(\frac{\psi_{i+1}-\psi_{i-1}}{2\Delta r}\right) + \pi\psi_{i}\Phi^{2}_{i} = 0\ ,
$$

or, by multiplying the entire equation by $\Delta r^{2}$ and then grouping the coefficients of each $\psi_{j}$:

$$
\boxed{\left(1-\frac{\Delta r}{r_{i}}\right)\psi_{i-1}+\left(\pi\Delta r^{2}\Phi_{i}^{2}-2\right)\psi_{i} + \left(1+\frac{\Delta r}{r_{i}}\right)\psi_{i+1} = 0}\ .
$$

We choose to set up a grid that is cell-centered, with:

$$
r_{i} = \left(i-\frac{1}{2}\right)\Delta r\ ,
$$

so that $r_{0} = - \frac{\Delta r}{2}$. This is a two-point boundary value problem, which we solve using the same strategy as [A&C](https://arxiv.org/pdf/1508.01614.pdf), described in eqs. (48)-(50):

\begin{align}
\left.\partial_{r}\psi\right|_{r=0} &= 0\ ,\\
\lim_{r\to\infty}\psi &= 1\ .
\end{align}

In terms of our grid structure, the first boundary condition (regularity at the origin) is written to second-order in $\Delta r$ as:

$$
\left.\partial_{r}\psi\right|_{r=0} = \frac{\psi_{1} - \psi_{0}}{\Delta r} = 0 \Rightarrow \psi_{0} = \psi_{1}\ .
$$

The second boundary condition (asymptotic flatness) can be interpreted as

$$
\psi_{N} = 1 + \frac{C}{r_{N}}\ (r_{N}\gg1)\ ,
$$

which then implies

$$
\partial_{r}\psi_{N} = -\frac{C}{r_{N}^{2}} = -\frac{1}{r_{N}}\left(\frac{C}{r_{N}}\right) = -\frac{1}{r_{N}}\left(\psi_{N} - 1\right) = \frac{1-\psi_{N}}{r_{N}}\ ,
$$

which can then be written as

$$
\frac{\psi_{N+1}-\psi_{N-1}}{2\Delta r} = \frac{1-\psi_{N}}{r_{N}}\Rightarrow \psi_{N+1} = \psi_{N-1} - \frac{2\Delta r}{r_{N}}\psi_{N} + \frac{2\Delta r}{r_{N}}\ .
$$

Substituting the boundary conditions at the boxed equations above, we end up with

\begin{align}
\left(\pi\Delta r^{2}\Phi^{2}_{1} - 1 - \frac{\Delta r}{r_{1}}\right)\psi_{1} + \left(1+\frac{\Delta r}{r_{1}}\right)\psi_{2} = 0\quad &(i=1)\ ,\\
\left(1-\frac{\Delta r}{r_{i}}\right)\psi_{i-1}+\left(\pi\Delta r^{2}\Phi_{i}^{2}-2\right)\psi_{i} + \left(1+\frac{\Delta r}{r_{i}}\right)\psi_{i+1} = 0\quad &(1<i<N)\ ,\\
2\psi_{N-1} + \left[\pi\Delta r^{2}\Phi^{2}_{N} - 2 - \frac{2\Delta r}{r_{N}}\left(1+\frac{\Delta r}{r_{N}}\right)\right]\psi_{N} = - \frac{2\Delta r}{r_{N}}\left(1+\frac{\Delta r}{r_{N}}\right)\quad &(i=N)\ .
\end{align}

This results in the following tridiagonal system of linear equations

$$
A \cdot \vec{\psi} = \vec{s}\Rightarrow \vec{\psi} = A^{-1}\cdot\vec{s}\ ,
$$

where

$$
A=\begin{pmatrix}
\left(\pi\Delta r^{2}\Phi^{2}_{1} - 1 - \frac{\Delta r}{r_{1}}\right) & \left(1+\frac{\Delta r}{r_{1}}\right) & 0 & 0 & 0 & 0 & 0\\
\left(1-\frac{\Delta r}{r_{2}}\right) & \left(\pi\Delta r^{2}\Phi_{2}^{2}-2\right) & \left(1+\frac{\Delta r}{r_{2}}\right) & 0 & 0 & 0 & 0\\
0 & \ddots & \ddots & \ddots & 0 & 0 & 0\\
0 & 0 & \left(1-\frac{\Delta r}{r_{i}}\right) & \left(\pi\Delta r^{2}\Phi_{i}^{2}-2\right) & \left(1+\frac{\Delta r}{r_{i}}\right) & 0 & 0\\
0 & 0 & 0 & \ddots & \ddots & \ddots & 0\\
0 & 0 & 0 & 0 & \left(1-\frac{\Delta r}{r_{N-1}}\right) & \left(\pi\Delta r^{2}\Phi_{N-1}^{2}-2\right) & \left(1+\frac{\Delta r}{r_{N-1}}\right)\\
0 & 0 & 0 & 0 & 0 & 2 & \left[\pi\Delta r^{2}\Phi^{2}_{N} - 2 - \frac{2\Delta r}{r_{N}}\left(1+\frac{\Delta r}{r_{N}}\right)\right]
\end{pmatrix}\ ,
$$

$$
\vec{\psi} =
\begin{pmatrix}
\psi_{1}\\
\psi_{2}\\
\vdots\\
\psi_{i}\\
\vdots\\
\psi_{N-1}\\
\psi_{N}
\end{pmatrix}\ ,
$$

and

$$
\vec{s} =
\begin{pmatrix}
0\\
0\\
\vdots\\
0\\
\vdots\\
0\\
-\frac{2\Delta r}{r_{N}}\left(1+\frac{\Delta r}{r_{N}}\right)
\end{pmatrix}
$$

#### Step 1.c.ii.1: The tridiagonal matrix: $A$ \[Back to [top](#toc)\]
$$\label{id_tridiagonal_matrix}$$

We now start solving the tridiagonal linear system. We start by implementing the tridiagonal matrix $A$ defined above. We break down it down by implementing each diagonal into an array. We start by looking at the main diagonal:

$$
{\rm diag}_{\rm main}
=
\begin{pmatrix}
\left(\pi\Delta r^{2}\Phi^{2}_{1} - 1 - \frac{\Delta r}{r_{1}}\right)\\
\left(\pi\Delta r^{2}\Phi_{2}^{2}-2\right)\\
\vdots\\
\left(\pi\Delta r^{2}\Phi_{i}^{2}-2\right)\\
\vdots\\
\left(\pi\Delta r^{2}\Phi_{N-1}^{2}-2\right)\\
\left[\pi\Delta r^{2}\Phi^{2}_{N} - 2 - \frac{2\Delta r}{r_{N}}\left(1+\frac{\Delta r}{r_{N}}\right)\right]\\
\end{pmatrix}
=
\begin{pmatrix}
\left(\pi\Delta r^{2}\Phi^{2}_{1} - 2\right)\\
\left(\pi\Delta r^{2}\Phi_{2}^{2} - 2\right)\\
\vdots\\
\left(\pi\Delta r^{2}\Phi_{i}^{2} - 2\right)\\
\vdots\\
\left(\pi\Delta r^{2}\Phi_{N-1}^{2}-2\right)\\
\left(\pi\Delta r^{2}\Phi^{2}_{N} - 2\right)\\
\end{pmatrix}
+
\left.\begin{pmatrix}
1 - \frac{\Delta r}{r_{1}}\\
0\\
\vdots\\
0\\
\vdots\\
0\\
- \frac{2\Delta r}{r_{N}}\left(1+\frac{\Delta r}{r_{N}}\right)
\end{pmatrix}\quad \right\}\text{N elements}
$$

```python
# Set the main diagonal
main_diag = np.pi * dr2 * Phi(phi0,r,r0,sigma)**2 - 2

# Update the first element of the main diagonal
main_diag[0] += 1 - dr[0]/r[0]

# Update the last element of the main diagonal
main_diag[NR-1] += - (2 * dr[NR-1] / r[NR-1])*(1 + dr[NR-1] / r[NR-1])
```

Then we look at the upper diagonal of the A matrix:

$$
{\rm diag}_{\rm upper}
=
\left.\begin{pmatrix}
1+\frac{\Delta r}{r_{1}}\\
1+\frac{\Delta r}{r_{2}}\\
\vdots\\
1+\frac{\Delta r}{r_{i}}\\
\vdots\\
1+\frac{\Delta r}{r_{N-2}}\\
1+\frac{\Delta r}{r_{N-1}}
\end{pmatrix}\quad\right\}\text{N-1 elements}
$$

```python
# Set the upper diagonal, ignoring the last point in the r array
upper_diag = np.zeros(NR)
upper_diag[1:] = 1 + dr[:-1]/r[:-1]
```

Finally, we look at the lower diagonal of the A matrix:

$$
{\rm diag}_{\rm lower}
=
\left.\begin{pmatrix}
1-\frac{\Delta r}{r_{2}}\\
1-\frac{\Delta r}{r_{3}}\\
\vdots\\
1-\frac{\Delta r}{r_{i+1}}\\
\vdots\\
1-\frac{\Delta r}{r_{N-1}}\\
2
\end{pmatrix}\quad\right\}\text{N-1 elements}
$$

```python
# Set the lower diagonal, start counting the r array at the second element
lower_diag = np.zeros(NR)
lower_diag[:-1] = 1 - dr[1:]/r[1:]

# Change the last term in the lower diagonal to its correct value
lower_diag[NR-2] = 2
```

Finally, we construct the tridiagonal matrix by adding the three diagonals, while shifting the upper and lower diagonals to the right and left, respectively. Because A is a sparse matrix, we will also use scipy to solve the linear system faster.

```python
!pip install scipy >/dev/null
```

```python
# Set the sparse matrix A by adding up the three diagonals
A = spdiags([main_diag,upper_diag,lower_diag],[0,1,-1],NR,NR)

# Then compress the sparse matrix A column wise, so that SciPy can invert it later
A = csc_matrix(A)
```

#### Step 1.c.ii.2 The right-hand side of the linear system: $\vec{s}$ \[Back to [top](#toc)\]
$$\label{id_tridiagonal_rhs}$$

We now focus our attention to the implementation of the $\vec{s}$ vector:

$$
\vec{s} =
\begin{pmatrix}
0\\
0\\
\vdots\\
0\\
\vdots\\
0\\
-\frac{2\Delta r}{r_{N}}\left(1+\frac{\Delta r}{r_{N}}\right)
\end{pmatrix}
$$

```python
# Set up the right-hand side of the linear system: s
s = np.zeros(NR)

# Update the last entry of the vector s
s[NR-1] = - (2 * dr[NR-1] / r[NR-1])*(1 + dr[NR-1] / r[NR-1])

# Compress the vector s column-wise
s = csc_matrix(s)
```

#### Step 1.c.ii.3 The conformal factor: $\psi$ \[Back to [top](#toc)\]
$$\label{id_conformal_factor}$$

We now use scipy to solve the sparse linear system of equations and determine the conformal factor $\psi$.

```python
# Solve the sparse linear system using scipy
# https://docs.scipy.org/doc/scipy-0.14.0/reference/generated/scipy.sparse.linalg.spsolve.html
psi = spsolve(A, s.T)
```

We then show useful plots of the conformal factor $\psi$ and of the *evolved conformal factors*

\begin{align}
\phi &= \log\psi\ ,\\
W &= \psi^{-2}\ ,\\
\chi &= \psi^{-4}\ .
\end{align}

```python
import matplotlib.pyplot as plt

# Compute phi
phi = np.log(psi)

# Compute W
W = psi**(-2)

# Compute chi
chi = psi**(-4)

f = plt.figure(figsize=(12,8),dpi=100)

ax  = f.add_subplot(221)
ax.set_title(r"Conformal factor $\psi(0,r)$")
ax.set_ylabel(r"$\psi(0,r)$")
ax.plot(r,psi,'k-')
ax.grid()

ax2 = f.add_subplot(222)
ax2.set_title(r"Evolved conformal factor $\phi(0,r)$")
ax2.set_ylabel(r"$\phi(0,r)$")
ax2.plot(r,phi,'r-')
ax2.grid()

ax3 = f.add_subplot(223)
ax3.set_title(r"Evolved conformal factor $W(0,r)$")
ax3.set_xlabel(r"$r$")
ax3.set_ylabel(r"$W(0,r)$")
ax3.plot(r,W,'b-')
ax3.grid()

ax4 = f.add_subplot(224)
ax4.set_title(r"Evolved conformal factor $\chi(0,r)$")
ax4.set_xlabel(r"$r$")
ax4.set_ylabel(r"$\chi(0,r)$")
ax4.plot(r,chi,'c-')
ax4.grid()

outfile = os.path.join(Ccodesdir,"cfs_scalarfield_id.png")
plt.savefig(outfile)
plt.close(f)

# Display the figure
from IPython.display import Image
Image(outfile)
```

## Step 1.d The lapse function: $\tilde\alpha$ \[Back to [top](#toc)\]
$$\label{id_lapse_function}$$

There are two common initial conditions for $\tilde\alpha$. The first one is eq. (44) of [A&C](https://arxiv.org/pdf/1508.01614.pdf), namely setting the lapse to unity

$$
\tilde\alpha = 1\ .
$$

```python
# Set the unity lapse initial condition
alpha_unity = np.ones(NR)
```

The second one is discussed in the last paragraph of section II.B in [Baumgarte](https://arxiv.org/pdf/1807.10342.pdf), which is to set the "pre-collapsed lapse"

$$
\tilde\alpha = \psi^{-2}\ .
$$

```python
# Set the "pre-collapsed lapse" initial condition
alpha_precollapsed = psi**(-2)
```

## Step 1.e Outputting the initial data to file \[Back to [top](#toc)\]
$$\label{id_output}$$

```python
# Check to see which version of Python is being used
# For a machine running the final release of Python 3.7.1,
# sys.version_info should return the tuple [3,7,1,'final',0]
if sys.version_info[0] == 3:
    np.savetxt(os.path.join(Ccodesdir,"outputSFID_unity_lapse.txt"), list(zip( r, ID_sf, psi**4, alpha_unity )),
               fmt="%.15e")
    np.savetxt(os.path.join(Ccodesdir,"outputSFID_precollapsed_lapse.txt"), list(zip( r, ID_sf, psi**4, alpha_precollapsed )),
               fmt="%.15e")
elif sys.version_info[0] == 2:
    np.savetxt(os.path.join(Ccodesdir,"outputSFID_unity_lapse.txt"), zip( r, ID_sf, psi**4, alpha_unity ),
               fmt="%.15e")
    np.savetxt(os.path.join(Ccodesdir,"outputSFID_precollapsed_lapse.txt"), zip( r, ID_sf, psi**4, alpha_precollapsed ),
               fmt="%.15e")
```

# Step 2: Interpolating the initial data file as needed \[Back to [top](#toc)\]
$$\label{id_interpolation_files}$$

In order to use the initial data file properly, we must tell the program how to interpolate the values we just computed to the values of $r$ in our numerical grid. We do this by creating two C functions: one that interpolates the ADM quantities, $\left\{\gamma_{ij},K_{ij},\alpha,\beta^{i},B^{i}\right\}$, and one that interpolates the scalar field quantities, $\left\{\varphi,\Pi\right\}$. The two files written below use the scalarfield_interpolate_1D(  ) function, which is defined in the [ScalarField/ScalarField_interp.h](../edit/ScalarField/ScalarField_interp.h) file. This function performs a Lagrange polynomial interpolation between the initial data file and the numerical grid used during the simulation.

```python
def ID_scalarfield_ADM_quantities(Ccodesdir=".",new_way=False):
    includes = ["NRPy_basic_defines.h", "NRPy_function_prototypes.h"]
    desc = """(c) 2021 Leo Werneck
This function takes as input either (x,y,z) or (r,th,ph) and outputs
all ADM quantities in the Cartesian or Spherical basis, respectively.
"""
    c_type = "void"
    name   = "ID_scalarfield_ADM_quantities"
    params = """const REAL xyz_or_rthph[3],const ID_inputs other_inputs,
                REAL *restrict gammaDD00,REAL *restrict gammaDD01,REAL *restrict gammaDD02,
                REAL *restrict gammaDD11,REAL *restrict gammaDD12,REAL *restrict gammaDD22,
                REAL *restrict KDD00,REAL *restrict KDD01,REAL *restrict KDD02,
                REAL *restrict KDD11,REAL *restrict KDD12,REAL *restrict KDD22,
                REAL *restrict alpha,
                REAL *restrict betaU0,REAL *restrict betaU1,REAL *restrict betaU2,
                REAL *restrict BU0,REAL *restrict BU1,REAL *restrict BU2"""
    body   = """
  const REAL r  = xyz_or_rthph[0];
  const REAL th = xyz_or_rthph[1];
  const REAL ph = xyz_or_rthph[2];

  REAL sf_star,psi4_star,alpha_star;

  scalarfield_interpolate_1D(r,
                             other_inputs.interp_stencil_size,
                             other_inputs.numlines_in_file,
                             other_inputs.r_arr,
                             other_inputs.sf_arr,
                             other_inputs.psi4_arr,
                             other_inputs.alpha_arr,
                             &sf_star,&psi4_star,&alpha_star);

  // Update alpha
  *alpha = alpha_star;
  // gamma_{rr} = psi^4
  *gammaDD00 = psi4_star;
  // gamma_{thth} = psi^4 r^2
  *gammaDD11 = psi4_star*r*r;
  // gamma_{phph} = psi^4 r^2 sin^2(th)
  *gammaDD22 = psi4_star*r*r*sin(th)*sin(th);

  // All other quantities ARE ZERO:
  *gammaDD01 = 0.0; *gammaDD02 = 0.0;
  /**/              *gammaDD12 = 0.0;

  *KDD00 = 0.0; *KDD01 = 0.0; *KDD02 = 0.0;
  /**/          *KDD11 = 0.0; *KDD12 = 0.0;
  /**/                        *KDD22 = 0.0;

  *betaU0 = 0.0; *betaU1 = 0.0; *betaU2 = 0.0;

  *BU0 = 0.0; *BU1 = 0.0; *BU2 = 0.0;
"""
    if new_way == True:
        outC.add_to_Cfunction_dict(includes=includes,desc=desc,c_type=c_type,name=name,
                                   params=params,body=body,enableCparameters=False)
    else:
        outfile = os.path.join(Ccodesdir,"ID_scalarfield_ADM_quantities-validation.h")
        outC.outCfunction(outfile=outfile,
                          includes=None,desc=desc,c_type=c_type,name=name,
                          params=params,body=body,enableCparameters=False)
```

```python
def ID_scalarfield_spherical(Ccodesdir=".",new_way=False):
    includes = ["NRPy_basic_defines.h", "NRPy_function_prototypes.h"]
    desc = """(c) 2021 Leo Werneck
This function takes as input either (x,y,z) or (r,th,ph) and outputs all
scalar field quantities in the Cartesian or Spherical basis, respectively.
"""
    c_type = "void"
    name   = "ID_scalarfield_spherical"
    params = "const REAL xyz_or_rthph[3],const ID_inputs other_inputs,REAL *restrict sf,REAL *restrict sfM"
    body   = """
  const REAL r  = xyz_or_rthph[0];
  const REAL th = xyz_or_rthph[1];
  const REAL ph = xyz_or_rthph[2];

  REAL sf_star,psi4_star,alpha_star;

  scalarfield_interpolate_1D(r,
                             other_inputs.interp_stencil_size,
                             other_inputs.numlines_in_file,
                             other_inputs.r_arr,
                             other_inputs.sf_arr,
                             other_inputs.psi4_arr,
                             other_inputs.alpha_arr,
                             &sf_star,&psi4_star,&alpha_star);

  // Update varphi
  *sf  = sf_star;
  // Update Pi
  *sfM = 0;
"""
    if new_way == True:
        outC.add_to_Cfunction_dict(includes=includes,desc=desc,c_type=c_type,name=name,
                                   params=params,body=body,enableCparameters=False)
    else:
        outfile = os.path.join(Ccodesdir,"ID_scalarfield_spherical-validation.h")
        outC.outCfunction(outfile=outfile,
                          includes=None,desc=desc,c_type=c_type,name=name,
                          params=params,body=body,enableCparameters=False)
```

# Step 3: Converting Spherical initial data to Curvilinear initial data \[Back to [top](#toc)\]
$$\label{id_sph_to_curvilinear}$$

In this tutorial module we have explained how to obtain spherically symmetric, time-symmetric initial data for the collapse of a massless scalar field in Spherical coordinates (see [Step 1](#initial_data)). We have also explained how to interpolate the initial data file to the numerical grid we will use during the simulation (see [Step 2](#id_interpolation_files)).

NRPy+ is capable of generating the BSSN evolution equations in many different Curvilinear coordinates (for example SinhSpherical coordinates, which are of particular interest for this problem). Therefore, it is essential that we convert the Spherical initial data generated here to any Curvilinear system supported by NRPy+.

We start by calling the reference_metric() function within the [reference_metric.py](../edit/reference_metric.py) NRPy+ module. This will set up a variety of useful quantities for us.

Then the code below interpolate the values of the Spherical grid $\left\{r,\theta,\phi\right\}$ to the Curvilinear grid $\left\{{\rm xx0,xx1,xx2}\right\}$.

```python
def ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2(Ccodesdir=".",pointer_to_ID_inputs=False,new_way=False):

    rfm.reference_metric()

    rthph = outC.outputC(rfm.xxSph[0:3],["rthph[0]", "rthph[1]", "rthph[2]"],
                         "returnstring", "includebraces=False,outCverbose=False,preindent=1")

    includes = ["NRPy_basic_defines.h", "NRPy_function_prototypes.h"]
    desc = """(c) 2021 Leo Werneck
This function takes as input either (x,y,z) or (r,th,ph) and outputs all
scalar field quantities in the Cartesian or Spherical basis, respectively.
"""
    c_type = "void"
    name   = "ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2"
    params = "const paramstruct *restrict params,const REAL xx0xx1xx2[3],\n"
    if pointer_to_ID_inputs == True:
        params += "ID_inputs *other_inputs,\n"
    else:
        params += "ID_inputs other_inputs,\n"
    params += "REAL *restrict sf, REAL *restrict sfM"
    body   = """
  const REAL xx0 = xx0xx1xx2[0];
  const REAL xx1 = xx0xx1xx2[1];
  const REAL xx2 = xx0xx1xx2[2];
  REAL rthph[3];
"""+rthph+"""
  ID_scalarfield_spherical(rthph,other_inputs,sf,sfM);
"""

    if new_way == True:
        outC.add_to_Cfunction_dict(includes=includes,desc=desc,c_type=c_type,name=name,
                                   params=params,body=body)
    else:
        outfile = os.path.join(Ccodesdir,"ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2-validation.h")
        outC.outCfunction(outfile=outfile,
                          includes=None,desc=desc,c_type=c_type,name=name,
                          params=params,body=body)
```

Finally, we create the driver function which puts everything together using OpenMP.

```python
def ID_scalarfield(Ccodesdir=".",new_way=False):
    includes = ["NRPy_basic_defines.h", "NRPy_function_prototypes.h"]
    desc = """(c) 2021 Leo Werneck
This is the scalar field initial data driver functiono.
"""
    c_type = "void"
    name   = "ID_scalarfield"
    params = """const paramstruct *restrict params,REAL *restrict xx[3],
                ID_inputs other_inputs,REAL *restrict in_gfs"""
    body   = """
  const int idx = IDX3S(i0,i1,i2);
  const REAL xx0xx1xx2[3] = {xx0,xx1,xx2};
  ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2(params,xx0xx1xx2,other_inputs,
                                             &in_gfs[IDX4ptS(SFGF,idx)],
                                             &in_gfs[IDX4ptS(SFMGF,idx)]);
"""
    loopopts = "AllPoints,Read_xxs"
    if new_way == True:
        outC.add_to_Cfunction_dict(includes=includes,desc=desc,c_type=c_type,name=name,
                                   params=params,body=body,loopopts=loopopts)
    else:
        outfile = os.path.join(Ccodesdir,"ID_scalarfield-validation.h")
        outC.outCfunction(outfile=outfile,
                          includes=None,desc=desc,c_type=c_type,name=name,
                          params=params,body=body,loopopts=loopopts)
```

```python
def NRPy_param_funcs_register_C_functions_and_NRPy_basic_defines(Ccodesdir=".",pointer_to_ID_inputs=False,new_way=False):
    ID_scalarfield_ADM_quantities(Ccodesdir=Ccodesdir,new_way=new_way)
    ID_scalarfield_spherical(Ccodesdir=Ccodesdir,new_way=new_way)
    ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2(Ccodesdir=Ccodesdir,pointer_to_ID_inputs=pointer_to_ID_inputs,new_way=new_way)
    ID_scalarfield(Ccodesdir=Ccodesdir,new_way=new_way)
```

```python
NRPy_param_funcs_register_C_functions_and_NRPy_basic_defines(Ccodesdir=Ccodesdir)
```

    Output C function ID_scalarfield_ADM_quantities() to file ScalarFieldID_validation/ID_scalarfield_ADM_quantities-validation.h
    Output C function ID_scalarfield_spherical() to file ScalarFieldID_validation/ID_scalarfield_spherical-validation.h
    Output C function ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2() to file ScalarFieldID_validation/ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2-validation.h
    Output C function ID_scalarfield() to file ScalarFieldID_validation/ID_scalarfield-validation.h

# Step 4: Validation of this tutorial against the [ScalarField/ScalarField_InitialData.py](../edit/ScalarField/ScalarField_InitialData.py) module \[Back to [top](#toc)\]
$$\label{validation}$$

First we load the [ScalarField/ScalarField_InitialData.py](../edit/ScalarField/ScalarField_InitialData.py) module and compute everything by using the scalarfield_initial_data(  ) function, which should do exactly the same as we have done in this tutorial.

```python
# Import the ScalarField.ScalarField_InitialData NRPy module
import ScalarField.ScalarField_InitialData as sfid

# Output the unity lapse initial data file
outputname = os.path.join(Ccodesdir,"outputSFID_unity_lapse-validation.txt")
sfid.ScalarField_InitialData(outputname,ID_Family,
                             phi0,r0,sigma,NR,RMAX,CoordSystem=CoordSystem,
                             sinhA=sinhA,sinhW=sinhW,lapse_condition="Unity")

# Output the "pre-collapsed" lapse initial data file
outputname = os.path.join(Ccodesdir,"outputSFID_precollapsed_lapse-validation.txt")
sfid.ScalarField_InitialData(outputname,ID_Family,
                             phi0,r0,sigma,NR,RMAX,CoordSystem=CoordSystem,
                             sinhA=sinhA,sinhW=sinhW,lapse_condition="Pre-collapsed")

# Output C codes
sfid.NRPy_param_funcs_register_C_functions_and_NRPy_basic_defines(Ccodesdir=Ccodesdir)
```

    Generated the ADM initial data for the gravitational collapse
    of a massless scalar field in Spherical coordinates.

    Type of initial condition: Scalar field: "Gaussian" Shell
                             ADM quantities: Time-symmetric
                            Lapse condition: Unity
    Parameters: amplitude         = 0.1,
                center            = 0,
                width             = 1,
                domain size       = 50,
                number of points  = 30000,
                Initial data file = ScalarFieldID_validation/outputSFID_unity_lapse-validation.txt.

    Generated the ADM initial data for the gravitational collapse
    of a massless scalar field in Spherical coordinates.

    Type of initial condition: Scalar field: "Gaussian" Shell
                             ADM quantities: Time-symmetric
                            Lapse condition: Pre-collapsed
    Parameters: amplitude         = 0.1,
                center            = 0,
                width             = 1,
                domain size       = 50,
                number of points  = 30000,
                Initial data file = ScalarFieldID_validation/outputSFID_precollapsed_lapse-validation.txt.

    Output C function ID_scalarfield_ADM_quantities() to file ScalarFieldID_validation/ID_scalarfield_ADM_quantities.h
    Output C function ID_scalarfield_spherical() to file ScalarFieldID_validation/ID_scalarfield_spherical.h
    Output C function ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2() to file ScalarFieldID_validation/ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2.h
    Output C function ID_scalarfield() to file ScalarFieldID_validation/ID_scalarfield.h

```python
import filecmp

if filecmp.cmp(os.path.join(Ccodesdir,'outputSFID_unity_lapse.txt'),
               os.path.join(Ccodesdir,'outputSFID_unity_lapse-validation.txt')) == False:
    print("ERROR: Unity lapse initial data test FAILED!")
    sys.exit(1)
else:
    print("             Unity lapse initial data test: PASSED!")

if filecmp.cmp(os.path.join(Ccodesdir,'outputSFID_precollapsed_lapse.txt'),
               os.path.join(Ccodesdir,'outputSFID_precollapsed_lapse-validation.txt')) == False:
    print("ERROR: \"Pre-collapsed\" lapse initial data test FAILED!")
    sys.exit(1)
else:
    print("   \"Pre-collapsed\" lapse initial data test: PASSED!")

if filecmp.cmp(os.path.join(Ccodesdir,'ID_scalarfield_ADM_quantities.h'),
               os.path.join(Ccodesdir,'ID_scalarfield_ADM_quantities-validation.h')) == False:
    print("ERROR: ADM quantities interpolation file test FAILED!")
    sys.exit(1)
else:
    print("    ADM quantities interpolation file test: PASSED!")

if filecmp.cmp(os.path.join(Ccodesdir,'ID_scalarfield_spherical.h'),
               os.path.join(Ccodesdir,'ID_scalarfield_spherical-validation.h')) == False:
    print("ERROR: Scalar field interpolation file test FAILED!")
    sys.exit(1)
else:
    print("      Scalar field interpolation file test: PASSED!")

if filecmp.cmp(os.path.join(Ccodesdir,'ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2.h'),
               os.path.join(Ccodesdir,'ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2-validation.h')) == False:
    print("ERROR: Scalar field Spherical to Curvilinear test FAILED!")
    sys.exit(1)
else:
    print("Scalar field Spherical to Curvilinear test: PASSED!")

if filecmp.cmp(os.path.join(Ccodesdir,'ID_scalarfield.h'),
               os.path.join(Ccodesdir,'ID_scalarfield-validation.h')) == False:
    print("ERROR: Scalar field driver test: FAILED!")
    sys.exit(1)
else:
    print("                  Scalar field driver test: PASSED!")
```

                 Unity lapse initial data test: PASSED!
       "Pre-collapsed" lapse initial data test: PASSED!
        ADM quantities interpolation file test: PASSED!
          Scalar field interpolation file test: PASSED!
    Scalar field Spherical to Curvilinear test: PASSED!
                      Scalar field driver test: PASSED!

# Step 5: Output this module as $\LaTeX$-formatted PDF file \[Back to [top](#toc)\]
$$\label{output_to_pdf}$$

The following code cell converts this Jupyter notebook into a proper, clickable $\LaTeX$-formatted PDF file. After the cell is successfully run, the generated PDF may be found in the root NRPy+ tutorial directory, with filename
[Tutorial-ADM_Initial_Data-ScalarField.pdf](Tutorial-ADM_Initial_Data-ScalarField.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

```python
import cmdline_helper as cmd    # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-ADM_Initial_Data-ScalarField")
```

    Created Tutorial-ADM_Initial_Data-ScalarField.tex, and compiled LaTeX file
        to PDF file Tutorial-ADM_Initial_Data-ScalarField.pdf