# Problema de selección de portafolio con preferencias media-varianza

En la clase pasada hablamos acerca de:
- preferencias,
- funciones de utilidad,
- la actitud de los inversionistas de cara al riesgo,
- la aversión al riesgo, entre otros.

Todas ellas son piezas que necesitamos para responder la pregunta de ¿cómo un inversionista toma la decisión óptima de selección de portafolio?

En esta clase al fin estamos listos para ensamblar estos conceptos y escribir el problema de selección de portafolios.

En el camino aprenderemos acerca del concepto de **utilidad esperada**, que nos permite trabajar con incertidumbre en el modelado económico (una de las ideas más importantes en economía). Esta idea tiene más de 60 años, y básicamente dice que los individuos, cuando están de cara a incertidumbre, maximizan el valor esperado de su utilidad (solo cierto si somos homo economicus).

Además del concepto de utilidad esperada, aprenderemos acerca de **preferencias media-varianza**. Es decir, supondremos que los inversionistas toman decisiones basados en un tipo particular de preferencias.

Con lo anterior, estableceremos el problema de selección de portafolios.

**Objetivos:**

- ¿Qué es utilidad esperada?
- ¿Qué son las preferencias media-varianza?
- Funciones de utilidad media-varianza.
- Enunciado y solución del problema básico de selección de portafolio.

*Referencia:*
- Notas del curso "Portfolio Selection and Risk Management", Rice University, disponible en Coursera.
___

## 1. Utilidad esperada
- Básicamente combina las probabilidades de los resultados con cómo los inversionistas se sienten con dichos resultados.
- En otras palabras, la utilidad esperada multiplica la probabilidad de suceso de un evento con la utilidad que genera dicho evento.

Recordemos que las *funciones de utilidad* permiten a los inversionistas expresar cómo se sienten con los resultados, especialmente en los malos ratos.

Entonces la *utilidad esperada* es una herramienta que nos permite cuantificar cómo nos sentimos en nuestros malos momentos económicos, capturando el riesgo con la probabilidad de ocurrencia de dichos malos momentos.

Dado este marco de trabajo, cualquier decisión se puede escribir como la maximización de la utilidad esperada:
\begin{align}
\max_{\theta} & \quad E[U(W)], \\
\end{align}
mediante la escogencia de cierta variable $\theta$ (gastos, planes de ahorro, compra de activos, planes de producción, etc.).

Para nuestros propósitos, la variable de decisión serán los pesos o ponderaciones del portafolio.

Adicionalmente, en el contexto de la decisión de distribución de la riqueza entre activos, el problema de maximización tendrá comúnmente las siguientes restricciones:
- universo de inversión,
- posición en los activos dados.

**Ejemplo.**

Supongamos que un inversionista debe determinar la composición óptima de su portafolio, que contiene activos y bonos. Supongamos que son los únicos instrumentos disponibles.

Sean:
- $w_s$: peso o ponderación de activos en el portafolio,
- $w_b$: peso o ponderación de bonos en el portafolio,
- $r_s$: rendimiento de los activos, y
- $r_b$: rendimiento de los bonos.

De manera que podemos escribir el problema de selección de portafolios como la maximización de la utilidad esperade de nuestra riqueza futura, la cual dependerá de nuestros rendimientos:

\begin{align}
\max_{w_s,w_b} &\quad E[U(W)]\\
\text{s. a.}   &\quad W=W_0(1+w_sr_s+w_br_b)\\
               &\quad w_s+w_b=1
\end{align}

Preguntas:
- ¿Qué significan las restricciones?
- Ya que tenemos planteado este problema básico, ¿qué haría falta para empezar a resolverlo?
___

## 2. Preferencias media-varianza

### 2.1. Utilidad media-varianza

Entonces, ¿qué funciones de utilidad deberíamos de usar en este problema de selección de portafolios?

- La respuesta es: **preferencias media-varianza**.
- Éstas serán representadas en términos de funciones de utilidad como: **utilidad media-varianza**.

Usamos la *utilidad media-varianza* en el problema de selección de portafolios dado que ésta decribe el "trade-off" entre riesgo y rendimiento que enfrentan los inversionistas. La *utilidad media-varianza* está dada por la siguiente expresión:

$$U=E[r_p]-\frac{1}{2}\gamma\sigma_p^2,$$

donde
- $E[r_p]$ es el rendimiento esperado del portafolio,
- $\sigma_p^2$ es la varianza del portafolio, y
- $\gamma$ es el coeficiente de aversión al riesgo.

#### Intuición acerca de la función de utilidad media-varianza:
- Sólo se preocupa por medias :) y varianzas :(.
- Incrementa con: rendimiento esperado del portafolio.
- Decrece con: varianza del portafolio.
- Malos tiempos: rendimientos son bajos y las volatilidades son altas.
- Conecta bastante bien con la teoría moderna de portafolios, la cual caracteriza los rendimientos con medias y varianzas únicamente.
- Criticada por su limitación: supone que los inversionistas sólo se preocupan por medias y varianzas.

### 2.2. Curvas de indiferencia

*¿Recuerdan las curvas de nivel que se ven en cálculo de varias variables?*
- Bien, acá nos servirán para representar la utilidad media-varianza gráficamente.
- En el contexto de utilidad media-varianza, las curvas de nivel se llaman **curvas de indiferencia**.

Dados ciertos niveles de utilidad $U_1>U_2>U_3$, las curvas de indiferencia relativas a estos niveles de utilidad, son los lugares geométricos en el espacio de rendimiento esperado vs. volatilidad representados por las siguientes expresiones

$$U_1=E[r_p]-\frac{1}{2}\gamma\sigma_p^2\Rightarrow E[r_p]=\frac{1}{2}\gamma\sigma_p^2+U_1,$$

$$U_2=E[r_p]-\frac{1}{2}\gamma\sigma_p^2\Rightarrow E[r_p]=\frac{1}{2}\gamma\sigma_p^2+U_2,$$

$$U_3=E[r_p]-\frac{1}{2}\gamma\sigma_p^2\Rightarrow E[r_p]=\frac{1}{2}\gamma\sigma_p^2+U_3.$$

**Gráficamente**

```python
# Importar numpy y pyplot
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
```

```python
# Coeficiente de aversión al riesgo (entre 1 y 10 comúnmente)
g = 0 #GAMMA
# Niveles de utilidad
U1,U2,U3 = 0.2,0.13,0.23
# Vector de volatilidades (sugerido 1%-60%)
sp = np.linspace(0.1,0.6,50)
# Curvas de indiferencia
Erp1 = .5 * g * sp**2 + U1
Erp2 = .5 * g * sp**2 + U2
Erp3 = .5 * g * sp**2 + U3
```

```python
# Gráfica
plt.figure(figsize=(6,4))
plt.plot(sp,Erp1,label = f'Nivel de Utilidad $U_1$={U1}')
plt.plot(sp,Erp2,label = f'Nivel de Utilidad $U_2$={U2}')
plt.plot(sp,Erp3,label = f'Nivel de Utilidad $U_3$={U3}')
plt.grid()
plt.xlabel('Volatilidad $\sigma$')
plt.ylabel('Rendimiento Esperado $E[r]$')
plt.legend()
```

Bueno, ¿y porqué se llaman curvas de indiferencia?, ¿qué representa una cuva de indiferencia?

- Porque sobre una misma curva el nivel de utilidad es el mismo (es indiferente).
- Son todas las combinaciones de riesgo y rendimiento que producen un mismo nivel de utilidad.

Volviendo al problema de selección de portafolios, queremos la utilidad más alta.
- ¿Cuál de las anteriores curvas de indiferencia corresponde a la utilidad más alta?
- Intuitivamente, ¿porqué?
- Curvas de indiferencia para niveles de utilidad más altos, estarán...

Notamos además que las anteriores curvas de indiferencia son *paralelas* una con otra. Claro, las dibujamos con el mismo coeficiente de aversión al riesgo.
$$\frac{d E[r_p]}{d\sigma_p} = \frac{d}{d\sigma_p}[\frac{1}{2}\gamma \sigma_p^2 + U]$$
¿Cómo cambian estas curvas para coeficientes de aversión al riesgo más altos?

```python
# Importar numpy y pyplot
import numpy as np
from matplotlib import pyplot as plt
%matplotlib inline
```

```python
# Coeficientes de aversión al riesgo (entre 1 y 10 comúnmente)
g1, g2, g3 = 10, 7, 4
# Nivel de utilidad
U = 0.07
# Vector de volatilidades (sugerido 1%-60%)
sp = np.linspace(0.01, 0.6, 50)
# Curvas de indiferencia
Erp1 = 0.5 * g1 * sp**2 + U
Erp2 = 0.5 * g2 * sp**2 + U
Erp3 = 0.5 * g3 * sp**2 + U
```

```python
# Gráfica
plt.figure(figsize=(6, 4))
plt.plot(sp, Erp1, label=f'Coeficiente de aversión al riesgo $\gamma_1$={g1}')
plt.plot(sp, Erp2, label=f'Coeficiente de aversión al riesgo $\gamma_2$={g2}')
plt.plot(sp, Erp3, label=f'Coeficiente de aversión al riesgo $\gamma_3$={g3}')
plt.grid()
plt.axvline(x=0.5, ls='--', color='grey')
plt.axhline(y=0.5, ls='--', color='grey')
plt.legend(loc='best')
plt.xlabel('Volatilidad $\sigma$')
plt.ylabel('Rendimiento esperado $E[r]$')
```

¿Cómo interpretamos las anteriores gráficas?, ¿qué pasa con las personas más aversas al riesgo?
- Se puede ver de dos maneras: para un mismo nivel de rendimiento esperado, una persona más aversa al riesgo soporta un nivel menor de riesgo; equivalentemente, para un mismo nivel de riesgo, una persona más aversa al riesgo requerirá un nivel de rendimiento esperado más alto.

Con todo lo anterior, el problema de selección de portafolios se puede plantear como *encontrar la curva de indeferencia más alta dado el conjunto de oportunidades de inversión y restricciones*.

## 3. Problema de selección de portafolios: una ilustración

Ahora ilustraremos el problema de selección de portafolios con algunos datos.
- Por ahora solo queremos ilustrar gráficamente cómo se resuelve este problema. Trabajar en la intuición.
- En las siguientes dos clases nos enfocaremos en cómo resolverlo analíticamente.

Acá tenemos el rendimiento medio anual y la volatilidad para dos instrumentos usando datos de EU: instrumentos de deuda (bonos) y acciones. Supondremos que el inversionista solo puede invertir en estas dos clases de instrumentos.

```python
# Importamos pandas
import pandas as pd
```

```python
# Datos
data = pd.DataFrame(index=['Stocks','Bonds', 'CorrSB'], columns=['Mean', 'Std'])
data['Mean'] = [0.119, 0.0591, 0.113]
data['Std'] = [0.1915, 0.0833, None]
data
```

      Mean
      Std

      Stocks
      0.1190
      0.1915

      Bonds
      0.0591
      0.0833

      CorrSB
      0.1130
      NaN

Entonces, ¿cuál es la distribución de riqueza óptima?, o más bien, ¿cuál es la composición óptima del portafolio para un inversionista dado su nivel de aversión al riesgo?

**Primero.** Recordamos que, para dos activos, podemos trazar la frontera de mínima varianza tomando todas las posibles combinaciones de los dos activos.

De nuevo, sean:
- $w_s=w$: peso o ponderación de activos en el portafolio,
- $w_b=1-w$: peso o ponderación de bonos en el portafolio,
- $r_s$: rendimiento de los activos, y
- $r_b$: rendimiento de los bonos.

Entonces

$$E[r_p]=wE[r_{s}]+(1-w)E[r_b]$$

$$\sigma_p^2=w^2\sigma_{s}^2+(1-w)^2\sigma_b^2+2w(1-w)\rho_{s,b}\sigma_s\sigma_b$$

```python
# Vector de w variando entre 0 y 1 con n pasos
N = 101
w = np.linspace(0,1,N)
# Rendimientos esperados individuales
Ers = data.loc['Stocks','Mean']
Erb = data.loc['Bonds','Mean']
# Volatilidades individuales
ss = data.loc['Stocks','Std']
sb = data.loc['Bonds','Std']
# Correlacion
rsb = data.loc['CorrSB','Mean']
Ers,Erb,ss,sb,rsb
```

    (0.119, 0.0591, 0.1915, 0.0833, 0.113)

```python
# Crear un DataFrame cuyas columnas sean rendimiento
# y volatilidad del portafolio para cada una de las w
# generadas
portafolios = pd.DataFrame({'Media':w*Ers + (1-w) * Erb,
                           'Vol':((w * ss)**2 + ((1-w)*sb)**2 + 2 * w* (1-w) *ss * sb *rsb)**.5},
                          index = w)
portafolios.index.name = 'w'
portafolios.head()
```

      Media
      Vol

      w

      0.00
      0.059100
      0.083300

      0.01
      0.059699
      0.082705

      0.02
      0.060298
      0.082155

      0.03
      0.060897
      0.081650

      0.04
      0.061496
      0.081191

```python
# Gráfica
plt.figure(figsize=(6,4))
plt.plot(portafolios['Vol'],portafolios['Media'], label = 'Portafolios')
plt.plot(ss,Ers,'ob', ms = 10, label = 'Stocks')
plt.plot(sb,Erb,'or', ms = 10, label = 'Bonds')
plt.xlabel('Volatilidad $\sigma$')
plt.ylabel('Rendimiento Esperado $E[r]$')
plt.legend()
plt.grid()
```

**Segundo.** Graficamos en la misma ventana, curvas de indiferencia.

```python
# Niveles de utilidad
U1,U2,U3 = 0.04,0.045,0.0512
# Coeficiente de aversión al riesgo
g = 7
# Curvas de indiferencia
sp = np.linspace(0.08,0.18,50)
Erp1 = .5 * g * sp**2 + U1
Erp2 = .5 * g * sp**2 + U2
Erp3 = .5 * g * sp**2 + U3
```

```python
# Gráfica
plt.figure(figsize=(6,4))
plt.plot(sp,Erp1,label = f'Nivel de Utilidad $U_1$={U1}')
plt.plot(sp,Erp2,label = f'Nivel de Utilidad $U_2$={U2}')
plt.plot(sp,Erp3,label = f'Nivel de Utilidad $U_3$={U3}')

plt.plot(portafolios['Vol'],portafolios['Media'], label = 'Portafolios')
plt.plot(ss,Ers,'ob', ms = 10, label = 'Stocks')
plt.plot(sb,Erb,'or', ms = 10, label = 'Bonds')
plt.xlabel('Volatilidad $\sigma$')
plt.ylabel('Rendimiento Esperado $E[r]$')
plt.legend()
plt.grid()
```

**Tercero.** La elección óptima está dada por la curva de indiferencia para el nivel de utilidad más alto que es tangente a la frontera media-varianza.
- Claramente, esta selección depende del coeficiente de aversión al riesgo.

```python
# Gráfica con zoom
plt.figure(figsize=(6,4))
plt.plot(sp,Erp1,label = f'Nivel de Utilidad $U_1$={U1}')
plt.plot(sp,Erp2,label = f'Nivel de Utilidad $U_2$={U2}')
plt.plot(sp,Erp3,label = f'Nivel de Utilidad $U_3$={U3}')

plt.plot(portafolios['Vol'],portafolios['Media'], label = 'Portafolios')
plt.plot(ss,Ers,'ob', ms = 10, label = 'Stocks')
plt.plot(sb,Erb,'or', ms = 10, label = 'Bonds')
plt.xlabel('Volatilidad $\sigma$')
plt.ylabel('Rendimiento Esperado $E[r]$')
plt.legend()
plt.grid()
plt.axis([0.08,0.1,.07,0.1])
```

# Anuncios parroquiales

## 1. Quiz la siguiente clase.
## 2. Un par de artículos del WSJ y el NYT que discuten herramientas disponibles para la medición de su propia tolerancia al riesgo:
- [Artículo 1](https://www.nytimes.com/2016/02/13/your-money/as-stocks-fall-its-time-to-measure-your-risk-tolerance.html)
- [Artículo 2](https://www.wsj.com/articles/check-your-tolerance-for-investment-risk-now-before-markets-sag-1405619939)

Created with Jupyter by Esteban Jiménez Rodríguez.


# Lambda School Data Science Module 132
## Sampling, Confidence Intervals, and Hypothesis Testing

## Prepare - examine other available hypothesis tests

If you had to pick a single hypothesis test in your toolbox, t-test would probably be the best choice - but the good news is you don't have to pick just one! Here's some of the others to be aware of:

```
import numpy as np
from scipy.stats import chisquare  # One-way chi square test

# Chi square can take any crosstab/table and test the independence of rows/cols
# The null hypothesis is that the rows/cols are independent -> low chi square
# The alternative is that there is a dependence -> high chi square
# Be aware! Chi square does *not* tell you direction/causation

ind_obs = np.array([[1, 1], [2, 2]]).T
print(ind_obs)
print(chisquare(ind_obs, axis=None))

dep_obs = np.array([[16, 18, 16, 14, 12, 12], [32, 24, 16, 28, 20, 24]]).T
print(dep_obs)
print(chisquare(dep_obs, axis=None))
```

    [[1 2]
     [1 2]]
    Power_divergenceResult(statistic=0.6666666666666666, pvalue=0.8810148425137847)
    [[16 32]
     [18 24]
     [16 16]
     [14 28]
     [12 20]
     [12 24]]
    Power_divergenceResult(statistic=23.31034482758621, pvalue=0.015975692534127565)

```
# Distribution tests:
# We often assume that something is normal, but it can be important to *check*

# For example, later on with predictive modeling, a typical assumption is that
# residuals (prediction errors) are normal - checking is a good diagnostic

from scipy.stats import normaltest
# Poisson models arrival times and is related to the binomial (coinflip)
sample = np.random.poisson(5, 1000)
print(normaltest(sample))  # Pretty clearly not normal
```

    NormaltestResult(statistic=28.094846668004493, pvalue=7.930152902256951e-07)

```
# Kruskal-Wallis H-test - compare the median rank between 2+ groups
# Can be applied to ranking decisions/outcomes/recommendations
# The underlying math comes from chi-square distribution, and is best for n>5
from scipy.stats import kruskal

x1 = [1, 3, 5, 7, 9]
y1 = [2, 4, 6, 8, 10]
print(kruskal(x1, y1))  # x1 is a little better, but not "significantly" so

x2 = [1, 1, 1]
y2 = [2, 2, 2]
z = [2, 2]  # Hey, a third group, and of different size!
print(kruskal(x2, y2, z))  # x clearly dominates
```

    KruskalResult(statistic=0.2727272727272734, pvalue=0.6015081344405895)
    KruskalResult(statistic=7.0, pvalue=0.0301973834223185)

And there's many more! `scipy.stats` is fairly comprehensive, though there are even more available if you delve into the extended world of statistics packages. As tests get increasingly obscure and specialized, the importance of knowing them by heart becomes small - but being able to look them up and figure them out when they *are* relevant is still important.

## T-test Assumptions

```
from scipy.stats import ttest_ind

?ttest_ind
```

<https://statistics.laerd.com/statistical-guides/independent-t-test-statistical-guide.php>

- Independence of means

Are the means of our voting data independent (do not affect the outcome of one another)?

The best way to increase thel likelihood of our means being independent is to randomly sample (which we did not do).

- "Homogeneity" of Variance?

Is the magnitude of the variance between the two roughly the same?

I think we're OK on this one for the voting data, although it probably could be better, one party was larger than the other.

If we suspect this to be a problem then we can use Welch's T-test

- "Dependent Variable" (sample means) are Distributed Normally

<https://stats.stackexchange.com/questions/9573/t-test-for-non-normal-when-n50>

Lots of statistical tests depend on normal distributions. We can test for normality using Scipy as was shown above.

This assumption is often assumed even if the assumption is a weak one. If you strongly suspect that things are not normally distributed, you can transform your data to get it looking more normal and then run your test. This problem typically goes away for large sample sizes (yay Central Limit Theorem) and is often why you don't hear it brought up. People declare the assumption to be satisfied either way.

## Central Limit Theorem

```
np.random.binomial(n=1, p=.5, size=30)
```

    array([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
           1, 1, 0, 1, 0, 0, 0, 0])

```
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

N = 1000
sample_means = []
for x in range(0,N):
  coinflips = np.random.binomial(n=1, p=.5, size=30)
  one_sample = coinflips
  sample_means.append(coinflips.mean())

print(len(sample_means))
print(sample_means)
```

    1000
    [0.36666666666666664, 0.4666666666666667, 0.5666666666666667, 0.5, 0.6666666666666666, 0.43333333333333335, 0.6, 0.36666666666666664, 0.6333333333333333, 0.5666666666666667, 0.5333333333333333, 0.43333333333333335, 0.7, 0.5, 0.36666666666666664, 0.6, 0.5333333333333333, 0.5666666666666667, 0.4, 0.36666666666666664, 0.26666666666666666, 0.5, 0.5666666666666667, 0.36666666666666664, 0.43333333333333335, 0.5, 0.4666666666666667, 0.6, 0.6, 0.6, 0.43333333333333335, 0.6333333333333333, 0.4666666666666667, 0.36666666666666664, 0.43333333333333335, 0.5, 0.5333333333333333, 0.5, 0.4, 0.6333333333333333, 0.4666666666666667, 0.5, 0.5666666666666667, 0.26666666666666666, 0.4666666666666667, 0.5333333333333333, 0.43333333333333335, 0.6, 0.43333333333333335, 0.5, 0.36666666666666664, 0.43333333333333335, 0.4666666666666667, 0.43333333333333335, 0.6666666666666666, 0.7666666666666667, 0.5, 0.43333333333333335, 0.43333333333333335, 0.3, 0.5333333333333333, 0.6, 0.5, 0.5, 0.5, 0.3333333333333333, 0.5333333333333333, 0.5666666666666667, 0.6, 0.5333333333333333, 0.6, 0.43333333333333335, 0.5666666666666667, 0.5, 0.3, 0.5666666666666667, 0.4, 0.43333333333333335, 0.3333333333333333, 0.6333333333333333, 0.5666666666666667, 0.7, 0.5333333333333333, 0.43333333333333335, 0.4666666666666667, 0.43333333333333335, 0.5, 0.5666666666666667, 0.5333333333333333, 0.2, 0.5, 0.3333333333333333, 0.5333333333333333, 0.36666666666666664, 0.6, 0.5333333333333333, 0.5666666666666667, 0.4666666666666667, 0.43333333333333335, 0.4666666666666667, 0.5333333333333333, 0.5333333333333333, 0.36666666666666664, 0.5, 0.5666666666666667, 0.4666666666666667, 0.43333333333333335, 0.6666666666666666, 0.6333333333333333, 0.5, 0.5, 0.3, 0.5, 0.5, 0.4, 0.4666666666666667, 0.43333333333333335, 0.5666666666666667, 0.6666666666666666, 0.5333333333333333, 0.3, 0.4, 0.4, 0.36666666666666664, 0.4666666666666667, 0.5666666666666667, 0.4, 0.4666666666666667, 0.43333333333333335, 0.6, 0.5666666666666667, 0.3333333333333333, 0.43333333333333335, 0.5333333333333333, 0.36666666666666664, 0.4, 0.5333333333333333, 0.3333333333333333, 0.4666666666666667, 0.5333333333333333, 0.5666666666666667, 0.5333333333333333, 0.6, 0.5333333333333333, 0.5333333333333333, 0.5666666666666667, 0.4, 0.5333333333333333, 0.43333333333333335, 0.4666666666666667, 0.5666666666666667, 0.5666666666666667, 0.5333333333333333, 0.5333333333333333, 0.43333333333333335, 0.4666666666666667, 0.36666666666666664, 0.4666666666666667, 0.6333333333333333, 0.5333333333333333, 0.5333333333333333, 0.5, 0.4666666666666667, 0.4666666666666667, 0.6666666666666666, 0.6333333333333333, 0.43333333333333335, 0.5, 0.5333333333333333, 0.5333333333333333, 0.6333333333333333, 0.3333333333333333, 0.43333333333333335, 0.36666666666666664, 0.6333333333333333, 0.3, 0.3, 0.6333333333333333, 0.5333333333333333, 0.5, 0.6, 0.4666666666666667, 0.5666666666666667, 0.5666666666666667, 0.4, 0.6333333333333333, 0.4666666666666667, 0.43333333333333335, 0.6, 0.36666666666666664, 0.5333333333333333, 0.6333333333333333, 0.5333333333333333, 0.5333333333333333, 0.6, 0.5666666666666667, 0.36666666666666664, 0.43333333333333335, 0.43333333333333335, 0.5, 0.4666666666666667, 0.36666666666666664, 0.6333333333333333, 0.5, 0.5333333333333333, 0.4666666666666667, 0.6333333333333333, 0.5333333333333333, 0.5666666666666667, 0.4666666666666667, 0.4, 0.6666666666666666, 0.4666666666666667, 0.6333333333333333, 0.5, 0.4666666666666667, 0.16666666666666666, 0.5333333333333333, 0.5, 0.4666666666666667, 0.7666666666666667, 0.36666666666666664, 0.5666666666666667, 0.43333333333333335, 0.4666666666666667, 0.4, 0.6333333333333333, 0.6, 0.4666666666666667, 0.6, 0.5, 0.5333333333333333, 0.4, 0.5666666666666667, 0.43333333333333335, 0.4666666666666667, 0.43333333333333335, 0.4666666666666667, 0.3333333333333333, 0.4666666666666667, 0.43333333333333335, 0.36666666666666664, 0.6, 0.5666666666666667, 0.4, 0.5, 0.5, 0.4666666666666667, 0.36666666666666664, 0.4666666666666667, 0.5, 0.3333333333333333, 0.6, 0.5, 0.36666666666666664, 0.5333333333333333, 0.5, 0.36666666666666664, 0.5, 0.5, 0.4666666666666667, 0.4666666666666667, 0.5, 0.4666666666666667, 0.26666666666666666, 0.4666666666666667, 0.6, 0.6333333333333333, 0.43333333333333335, 0.43333333333333335, 0.5333333333333333, 0.5666666666666667, 0.5333333333333333, 0.7, 0.4666666666666667, 0.36666666666666664, 0.36666666666666664, 0.6666666666666666, 0.43333333333333335, 0.43333333333333335, 0.5666666666666667, 0.4, 0.4666666666666667, 0.5, 0.5, 0.36666666666666664, 0.6, 0.5, 0.43333333333333335, 0.4666666666666667, 0.4, 0.4666666666666667, 0.5666666666666667, 0.5666666666666667, 0.3333333333333333, 0.5666666666666667, 0.5, 0.5333333333333333, 0.36666666666666664, 0.5, 0.5, 0.4666666666666667, 0.5666666666666667, 0.5666666666666667, 0.36666666666666664, 0.4666666666666667, 0.5333333333333333, 0.43333333333333335, 0.7666666666666667, 0.5666666666666667, 0.5, 0.43333333333333335, 0.4666666666666667, 0.36666666666666664, 0.5666666666666667, 0.4666666666666667, 0.4, 0.43333333333333335, 0.4, 0.6333333333333333, 0.5666666666666667, 0.36666666666666664, 0.43333333333333335, 0.36666666666666664, 0.4666666666666667, 0.6333333333333333, 0.5666666666666667, 0.6333333333333333, 0.5333333333333333, 0.43333333333333335, 0.5666666666666667, 0.6, 0.43333333333333335, 0.5, 0.6, 0.5, 0.5, 0.6, 0.5, 0.43333333333333335, 0.3, 0.43333333333333335, 0.4, 0.5666666666666667, 0.4666666666666667, 0.5, 0.5333333333333333, 0.5, 0.5666666666666667, 0.4666666666666667, 0.4666666666666667, 0.5333333333333333, 0.5, 0.7, 0.6, 0.5, 0.4, 0.5, 0.5333333333333333, 0.4666666666666667, 0.43333333333333335, 0.6666666666666666, 0.6, 0.5, 0.5, 0.43333333333333335, 0.43333333333333335, 0.3, 0.43333333333333335, 0.4666666666666667, 0.5666666666666667, 0.6, 0.4, 0.4666666666666667, 0.36666666666666664, 0.5333333333333333, 0.4666666666666667, 0.3, 0.5333333333333333, 0.5333333333333333, 0.6333333333333333, 0.43333333333333335, 0.5666666666666667, 0.6, 0.5, 0.6666666666666666, 0.6333333333333333, 0.6666666666666666, 0.43333333333333335, 0.6333333333333333, 0.26666666666666666, 0.6, 0.4666666666666667, 0.36666666666666664, 0.6333333333333333, 0.4666666666666667, 0.6, 0.4, 0.6, 0.23333333333333334, 0.5, 0.6, 0.5333333333333333, 0.5666666666666667, 0.6333333333333333, 0.4666666666666667, 0.36666666666666664, 0.4666666666666667, 0.5666666666666667, 0.5333333333333333, 0.5333333333333333, 0.5333333333333333, 0.43333333333333335, 0.4666666666666667, 0.43333333333333335, 0.5333333333333333, 0.4666666666666667, 0.6333333333333333, 0.4, 0.6333333333333333, 0.6, 0.6, 0.5, 0.5, 0.4, 0.4666666666666667, 0.5, 0.4666666666666667, 0.43333333333333335, 0.5, 0.4666666666666667, 0.4, 0.43333333333333335, 0.5666666666666667, 0.6666666666666666, 0.4666666666666667, 0.36666666666666664, 0.6, 0.4666666666666667, 0.6, 0.6, 0.3333333333333333, 0.36666666666666664, 0.5333333333333333, 0.36666666666666664, 0.5333333333333333, 0.4666666666666667, 0.5666666666666667, 0.5666666666666667, 0.5666666666666667, 0.4666666666666667, 0.6333333333333333, 0.4666666666666667, 0.5333333333333333, 0.4666666666666667, 0.5333333333333333, 0.5, 0.5666666666666667, 0.3, 0.7, 0.43333333333333335, 0.5, 0.4666666666666667, 0.4666666666666667, 0.5, 0.4, 0.4, 0.5333333333333333, 0.3, 0.5666666666666667, 0.43333333333333335, 0.5666666666666667, 0.6, 0.4666666666666667, 0.26666666666666666, 0.5, 0.5333333333333333, 0.5, 0.5333333333333333, 0.4, 0.5333333333333333, 0.5666666666666667, 0.5, 0.5666666666666667, 0.6, 0.7666666666666667, 0.43333333333333335, 0.4666666666666667, 0.5, 0.43333333333333335, 0.4, 0.36666666666666664, 0.7, 0.6, 0.4666666666666667, 0.43333333333333335, 0.3333333333333333, 0.6666666666666666, 0.6666666666666666, 0.43333333333333335, 0.7, 0.3333333333333333, 0.5, 0.5333333333333333, 0.43333333333333335, 0.4, 0.5, 0.5333333333333333, 0.5333333333333333, 0.5666666666666667, 0.4666666666666667, 0.4, 0.5333333333333333, 0.4666666666666667, 0.43333333333333335, 0.4666666666666667, 0.4666666666666667, 0.5666666666666667, 0.5333333333333333, 0.3, 0.6333333333333333, 0.4, 0.5333333333333333, 0.5, 0.5666666666666667, 0.6, 0.6333333333333333, 0.5333333333333333, 0.4666666666666667, 0.5333333333333333, 0.4666666666666667, 0.5666666666666667, 0.5333333333333333, 0.4666666666666667, 0.5, 0.43333333333333335, 0.4666666666666667, 0.4666666666666667, 0.5, 0.6, 0.43333333333333335, 0.5666666666666667, 0.6333333333333333, 0.4666666666666667, 0.5666666666666667, 0.6666666666666666, 0.5, 0.5333333333333333, 0.3, 0.43333333333333335, 0.43333333333333335, 0.5666666666666667, 0.36666666666666664, 0.4666666666666667, 0.4666666666666667, 0.5666666666666667, 0.6333333333333333, 0.4, 0.43333333333333335, 0.36666666666666664, 0.5333333333333333, 0.5, 0.36666666666666664, 0.6666666666666666, 0.4, 0.5, 0.5, 0.5, 0.5333333333333333, 0.3333333333333333, 0.6666666666666666, 0.5, 0.5333333333333333, 0.4, 0.4666666666666667, 0.4666666666666667, 0.6, 0.3, 0.36666666666666664, 0.5666666666666667, 0.36666666666666664, 0.5, 0.36666666666666664, 0.6, 0.3333333333333333, 0.4666666666666667, 0.43333333333333335, 0.4666666666666667, 0.5333333333333333, 0.43333333333333335, 0.5333333333333333, 0.36666666666666664, 0.4666666666666667, 0.5, 0.5333333333333333, 0.5, 0.5333333333333333, 0.5666666666666667, 0.43333333333333335, 0.36666666666666664, 0.43333333333333335, 0.5666666666666667, 0.6666666666666666, 0.5666666666666667, 0.5333333333333333, 0.4666666666666667, 0.5333333333333333, 0.6, 0.5666666666666667, 0.5333333333333333, 0.4, 0.43333333333333335, 0.5333333333333333, 0.6333333333333333, 0.5, 0.4, 0.43333333333333335, 0.6, 0.6, 0.5, 0.6333333333333333, 0.5, 0.5666666666666667, 0.5666666666666667, 0.5333333333333333, 0.4, 0.4666666666666667, 0.36666666666666664, 0.6333333333333333, 0.5333333333333333, 0.5666666666666667, 0.7, 0.4, 0.5, 0.5666666666666667, 0.5666666666666667, 0.3, 0.6666666666666666, 0.5666666666666667, 0.4666666666666667, 0.5, 0.5333333333333333, 0.26666666666666666, 0.5, 0.5333333333333333, 0.5333333333333333, 0.6333333333333333, 0.6333333333333333, 0.5, 0.5666666666666667, 0.5666666666666667, 0.43333333333333335, 0.5333333333333333, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.5666666666666667, 0.4666666666666667, 0.5333333333333333, 0.4666666666666667, 0.5333333333333333, 0.36666666666666664, 0.5666666666666667, 0.5333333333333333, 0.5666666666666667, 0.5333333333333333, 0.5333333333333333, 0.5, 0.3, 0.5666666666666667, 0.5, 0.4, 0.4, 0.4666666666666667, 0.6, 0.36666666666666664, 0.5333333333333333, 0.5333333333333333, 0.6333333333333333, 0.4666666666666667, 0.5, 0.5666666666666667, 0.7, 0.4666666666666667, 0.5333333333333333, 0.3333333333333333, 0.4, 0.5, 0.5333333333333333, 0.4, 0.5, 0.6, 0.4666666666666667, 0.7333333333333333, 0.4666666666666667, 0.7, 0.3, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.5666666666666667, 0.5666666666666667, 0.5666666666666667, 0.4, 0.4666666666666667, 0.5333333333333333, 0.6, 0.6, 0.5666666666666667, 0.6333333333333333, 0.4666666666666667, 0.36666666666666664, 0.43333333333333335, 0.4666666666666667, 0.43333333333333335, 0.5666666666666667, 0.5666666666666667, 0.5666666666666667, 0.5, 0.6333333333333333, 0.3333333333333333, 0.43333333333333335, 0.5666666666666667, 0.4666666666666667, 0.5, 0.4666666666666667, 0.5, 0.5666666666666667, 0.4666666666666667, 0.5666666666666667, 0.5333333333333333, 0.5333333333333333, 0.4666666666666667, 0.6, 0.6333333333333333, 0.36666666666666664, 0.43333333333333335, 0.5333333333333333, 0.36666666666666664, 0.5, 0.5, 0.5666666666666667, 0.6333333333333333, 0.3333333333333333, 0.5333333333333333, 0.5333333333333333, 0.6, 0.5666666666666667, 0.5666666666666667, 0.5, 0.4666666666666667, 0.43333333333333335, 0.5666666666666667, 0.4, 0.6, 0.36666666666666664, 0.36666666666666664, 0.6666666666666666, 0.4666666666666667, 0.5666666666666667, 0.4666666666666667, 0.43333333333333335, 0.5, 0.5666666666666667, 0.26666666666666666, 0.43333333333333335, 0.36666666666666664, 0.5333333333333333, 0.43333333333333335, 0.5, 0.4666666666666667, 0.36666666666666664, 0.36666666666666664, 0.4, 0.36666666666666664, 0.36666666666666664, 0.4666666666666667, 0.6, 0.5666666666666667, 0.4666666666666667, 0.5, 0.5333333333333333, 0.6666666666666666, 0.5666666666666667, 0.5, 0.43333333333333335, 0.5, 0.4, 0.43333333333333335, 0.6, 0.5333333333333333, 0.36666666666666664, 0.6666666666666666, 0.43333333333333335, 0.3, 0.6, 0.4666666666666667, 0.3333333333333333, 0.5333333333333333, 0.4666666666666667, 0.43333333333333335, 0.43333333333333335, 0.4666666666666667, 0.5666666666666667, 0.5, 0.4666666666666667, 0.5333333333333333, 0.5666666666666667, 0.5666666666666667, 0.5666666666666667, 0.5333333333333333, 0.43333333333333335, 0.5666666666666667, 0.4666666666666667, 0.6666666666666666, 0.5333333333333333, 0.36666666666666664, 0.6333333333333333, 0.43333333333333335, 0.43333333333333335, 0.4666666666666667, 0.5666666666666667, 0.5666666666666667, 0.5, 0.5, 0.43333333333333335, 0.36666666666666664, 0.4666666666666667, 0.5, 0.4666666666666667, 0.6666666666666666, 0.6, 0.36666666666666664, 0.5, 0.3333333333333333, 0.5666666666666667, 0.5, 0.7, 0.6333333333333333, 0.6, 0.43333333333333335, 0.4, 0.5, 0.36666666666666664, 0.5333333333333333, 0.7333333333333333, 0.5, 0.4, 0.6, 0.5666666666666667, 0.6333333333333333, 0.5333333333333333, 0.6, 0.5, 0.5, 0.4, 0.6, 0.5, 0.43333333333333335, 0.5, 0.5333333333333333, 0.6666666666666666, 0.5333333333333333, 0.5666666666666667, 0.43333333333333335, 0.6, 0.5666666666666667, 0.36666666666666664, 0.36666666666666664, 0.36666666666666664, 0.5333333333333333, 0.6, 0.5, 0.4, 0.6666666666666666, 0.36666666666666664, 0.36666666666666664, 0.5666666666666667, 0.4666666666666667, 0.5333333333333333, 0.5, 0.6666666666666666, 0.5666666666666667, 0.4, 0.43333333333333335, 0.4666666666666667, 0.43333333333333335, 0.4, 0.4, 0.5, 0.5, 0.36666666666666664, 0.36666666666666664, 0.6333333333333333, 0.43333333333333335, 0.5666666666666667, 0.6333333333333333, 0.5, 0.5333333333333333, 0.4666666666666667, 0.43333333333333335, 0.5, 0.26666666666666666, 0.5, 0.43333333333333335, 0.5, 0.4, 0.36666666666666664, 0.5333333333333333, 0.4666666666666667, 0.5333333333333333, 0.5333333333333333, 0.5666666666666667, 0.4666666666666667, 0.6, 0.5, 0.4, 0.6, 0.5666666666666667, 0.6, 0.5333333333333333, 0.43333333333333335, 0.5, 0.43333333333333335, 0.4, 0.5333333333333333, 0.4666666666666667, 0.4666666666666667, 0.5, 0.43333333333333335, 0.5333333333333333, 0.6, 0.5, 0.4, 0.3333333333333333, 0.3333333333333333, 0.5333333333333333, 0.5, 0.5666666666666667, 0.43333333333333335, 0.6, 0.5333333333333333, 0.5666666666666667, 0.5333333333333333, 0.3333333333333333, 0.5, 0.43333333333333335, 0.5, 0.5333333333333333, 0.4666666666666667, 0.4, 0.5333333333333333, 0.5, 0.43333333333333335, 0.6666666666666666, 0.4666666666666667, 0.6333333333333333, 0.4666666666666667, 0.4, 0.6333333333333333, 0.6, 0.6333333333333333, 0.4, 0.6666666666666666, 0.43333333333333335, 0.5, 0.6, 0.4, 0.6666666666666666, 0.4, 0.5333333333333333, 0.43333333333333335, 0.43333333333333335, 0.6333333333333333, 0.5666666666666667, 0.4666666666666667, 0.4, 0.4, 0.4666666666666667, 0.4, 0.43333333333333335, 0.36666666666666664, 0.4666666666666667, 0.5, 0.6, 0.4666666666666667, 0.6333333333333333, 0.5333333333333333, 0.4, 0.43333333333333335, 0.5333333333333333, 0.36666666666666664, 0.5, 0.5333333333333333, 0.4666666666666667, 0.4, 0.3, 0.5333333333333333, 0.6333333333333333]

```
# Create dataframe with single coin flip
df = pd.DataFrame({'one-samp': one_sample})
df.head()
```

      one-samp

      0
      1

      1
      0

      2
      1

      3
      1

      4
      1

```
# Plot histogram to look at distribution of a single coin flip
df.hist();
```

```
# Plot histogram to look at distribution of all coin flips
ax = plt.hist(sample_means)
plt.title(f'Distribution of {N} sample means \n (of 100 coinflips each)');
```

What does the Central Limit Theorem State? That no matter the initial distribution of the population, the distribution of sample means taken will approximate a normal distribution as $n \rightarrow \infty$.

This has very important implications for hypothesis testing and is precisely the reason why the t-distribution begins to approximate the normal distribution as our sample size increases.

## Standard Error of the Mean

What does it mean to "estimate"? the Population mean?

```
import numpy as np
import pandas as pd

# Average Height
mu = 70
sigma = 3

lambda_heights = np.random.normal(mu, sigma, 2000)
print(len(lambda_heights))
lambda_heights
```

    2000

    array([75.27298201, 73.53278172, 67.35668305, ..., 71.62847436,
           71.57331148, 71.59265235])

```
import seaborn as sns

sns.distplot(lambda_heights)
plt.title('Distribution of Heights (in inches)');
```

```
print("Population Mean:", lambda_heights.mean())
print("Population Standard Deviation:", lambda_heights.std())
```

    Population Mean: 70.01784335410584
    Population Standard Deviation: 2.960488297079555

```
population = pd.DataFrame({'heights': lambda_heights})
print(population.shape)
population.head()
```

    (2000, 1)

      heights

      0
      75.272982

      1
      73.532782

      2
      67.356683

      3
      68.993736

      4
      66.670617

```
# Take a random sample and print sample mean
sample1 = population.sample(100)
print(sample1.shape)
sample1.head()
```

    (100, 1)

      heights

      1642
      69.200564

      834
      70.208448

      1508
      68.242700

      875
      71.357699

      843
      69.299415

```
print('Sample Mean #1:', sample1['heights'].mean())
```

    Sample Mean #1: 69.69155993966513

```
# Take a different random sample and print sample mean
sample2 = population.sample(100)
print(sample2.shape)
sample2.head()
```

    (100, 1)

      heights

      484
      67.271530

      1912
      69.699096

      1824
      65.834826

      28
      69.167553

      1080
      68.418836

```
print('Sample Mean #2:', sample2['heights'].mean())
```

    Sample Mean #2: 69.65665910584407

## Build and Interpret a Confidence Interval

```
coinflips_10000 = np.random.binomial(n=1, p=0.5, size=10000)

sample_std = np.std(coinflips_10000)
print('Sample St Dev:', sample_std)
sample_size = len(coinflips_10000)
print('Sample Size:', sample_size)
```

    Sample St Dev: 0.49999963999987046
    Sample Size: 10000

```
standard_error = sample_std/np.sqrt(sample_size)
print(standard_error)
```

    0.004999996399998705

### What confidence level do we want our confidence interval to represent?

95% confidence Interval? 99% confidence interval?

```
import scipy.stats as stats
help(stats.t.ppf)
```

    Help on method ppf in module scipy.stats._distn_infrastructure:

    ppf(q, *args, **kwds) method of scipy.stats._continuous_distns.t_gen instance
        Percent point function (inverse of `cdf`) at q of the given RV.

        Parameters
        ----------
        q : array_like
            lower tail probability
        arg1, arg2, arg3,... : array_like
            The shape parameter(s) for the distribution (see docstring of the
            instance object for more information)
        loc : array_like, optional
            location parameter (default=0)
        scale : array_like, optional
            scale parameter (default=1)

        Returns
        -------
        x : array_like
            quantile corresponding to the lower tail probability q.

```
t = stats.t.ppf(0.975, sample_size-1)
t
```

    1.9602012636213575

```
sample_mean = coinflips_10000.mean()
confidence_interval = (sample_mean - t*standard_error, sample_mean + t*standard_error)
margin_of_error = t*standard_error

print("Sample Mean: ", sample_mean)
print("Margin of Error: ", margin_of_error)
print("Confidence Interval: ", confidence_interval)
```

    Sample Mean:  0.5006
    Margin of Error:  0.0098009992613797
    Confidence Interval:  (0.4907990007386204, 0.5104009992613797)

## Graphically Represent a Confidence Interval

```
import seaborn as sns

sns.kdeplot(coinflips_10000)
plt.axvline(x=sample_mean, color='k')
plt.axvline(x=confidence_interval[0], color='r')
plt.axvline(x=confidence_interval[1], color='r')
```

## Relationship between Confidence Intervals and T-tests

Confidence Interval == Bounds of statistical significance for our t-test

A sample mean that falls inside of our confidence interval will "FAIL TO REJECT" our null hypothesis

A sample mean that falls outside of our confidence interval will "REJECT" our null hypothesis

```
from scipy.stats import t, ttest_1samp
```

```
import numpy as np

coinflip_means = []
for x in range(0,100):
  coinflips = np.random.binomial(n=1, p=.5, size=30)
  coinflip_means.append(coinflips.mean())

print(coinflip_means)
```

    [0.5333333333333333, 0.5333333333333333, 0.4666666666666667, 0.5, 0.4666666666666667, 0.5333333333333333, 0.5333333333333333, 0.6333333333333333, 0.43333333333333335, 0.6666666666666666, 0.4666666666666667, 0.43333333333333335, 0.5333333333333333, 0.6666666666666666, 0.43333333333333335, 0.4666666666666667, 0.5333333333333333, 0.4, 0.4666666666666667, 0.43333333333333335, 0.6, 0.6333333333333333, 0.6333333333333333, 0.5333333333333333, 0.6333333333333333, 0.3333333333333333, 0.5666666666666667, 0.5, 0.5333333333333333, 0.5, 0.5333333333333333, 0.6333333333333333, 0.4666666666666667, 0.6333333333333333, 0.5, 0.6, 0.5333333333333333, 0.3, 0.5333333333333333, 0.6333333333333333, 0.6, 0.5, 0.5, 0.5666666666666667, 0.4666666666666667, 0.5666666666666667, 0.5, 0.4666666666666667, 0.36666666666666664, 0.6, 0.6666666666666666, 0.3333333333333333, 0.4, 0.4666666666666667, 0.4666666666666667, 0.43333333333333335, 0.5333333333333333, 0.5, 0.5333333333333333, 0.6666666666666666, 0.5333333333333333, 0.36666666666666664, 0.36666666666666664, 0.3, 0.4666666666666667, 0.43333333333333335, 0.5333333333333333, 0.36666666666666664, 0.43333333333333335, 0.43333333333333335, 0.4, 0.6666666666666666, 0.7, 0.5666666666666667, 0.5, 0.26666666666666666, 0.4666666666666667, 0.43333333333333335, 0.5, 0.4666666666666667, 0.5333333333333333, 0.5333333333333333, 0.4, 0.7, 0.5333333333333333, 0.4666666666666667, 0.43333333333333335, 0.43333333333333335, 0.5333333333333333, 0.43333333333333335, 0.4, 0.4666666666666667, 0.6, 0.36666666666666664, 0.4666666666666667, 0.5, 0.4666666666666667, 0.4666666666666667, 0.5, 0.4666666666666667]

```
np.mean(coinflip_means)
```

    0.5000000000000001

```
help(stats.t.interval)
```

    Help on method interval in module scipy.stats._distn_infrastructure:

    interval(alpha, *args, **kwds) method of scipy.stats._continuous_distns.t_gen instance
        Confidence interval with equal areas around the median.

        Parameters
        ----------
        alpha : array_like of float
            Probability that an rv will be drawn from the returned range.
            Each value should be in the range [0, 1].
        arg1, arg2, ... : array_like
            The shape parameter(s) for the distribution (see docstring of the
            instance object for more information).
        loc : array_like, optional
            location parameter, Default is 0.
        scale : array_like, optional
            scale parameter, Default is 1.

        Returns
        -------
        a, b : ndarray of float
            end-points of range that contain ``100 * alpha %`` of the rv's
            possible values.

```
# 95% confidence interval
t_stat = stats.t.ppf(0.975, 99)
print('T Statistic: ', t_stat)

std_sample = np.std(coinflip_means)
std_err = std_sample/np.sqrt(len(coinflip_means))

CI = stats.t.interval(0.95, 99, loc=np.mean(coinflip_means), scale=std_err)
print('95% confidence interval:', CI)
```

    T Statistic:  1.9842169515086827
    95% confidence interval: (0.4818625252375945, 0.5181374747624058)

A null hypothesis that's just inside of our confidence interval == fail to reject

```
ttest_1samp(coinflip_means, 0.48186)
```

    Ttest_1sampResult(statistic=1.9745458123056203, pvalue=0.051105424274298865)

A null hypothesis that's just outside of our confidence interval == reject

```
ttest_1samp(coinflip_means, 0.51813)
```

    Ttest_1sampResult(statistic=-1.9734573085501894, pvalue=0.051231132740212654)

```
def confidence_interval(data, confidence=0.95):
  """
  Calculate a confidence interval around a sample mean for given data.
  Using t-distribution and two-tailed test, default 95% confidence.

  Arguments:
    data - iterable (list or numpy array) of sample observations
    confidence - level of confidence for the interval

  Returns:
    tuple of (mean, lower bound, upper bound)
  """
  data = np.array(data)
  mean = np.mean(data)
  n = len(data)
  stderr = stats.sem(data)
  interval = stderr * stats.t.ppf((1 + confidence) / 2.0, n - 1)
  return (mean, mean - interval, mean + interval)
```

## Run a $\chi^{2}$ Test "by hand" (Using Numpy)

```
df = pd.read_csv('https://raw.githubusercontent.com/ryanleeallred/datasets/master/adult.csv', na_values=" ?")
print(df.shape)
df.head()
```

    (32561, 15)

      age
      workclass
      fnlwgt
      education
      education-num
      marital-status
      occupation
      relationship
      race
      sex
      capital-gain
      capital-loss
      hours-per-week
      country
      salary

      0
      39
      State-gov
      77516
      Bachelors
      13
      Never-married
      Adm-clerical
      Not-in-family
      White
      Male
      2174
      0
      40
      United-States
      <=50K

      1
      50
      Self-emp-not-inc
      83311
      Bachelors
      13
      Married-civ-spouse
      Exec-managerial
      Husband
      White
      Male
      0
      0
      13
      United-States
      <=50K

      2
      38
      Private
      215646
      HS-grad
      9
      Divorced
      Handlers-cleaners
      Not-in-family
      White
      Male
      0
      0
      40
      United-States
      <=50K

      3
      53
      Private
      234721
      11th
      7
      Married-civ-spouse
      Handlers-cleaners
      Husband
      Black
      Male
      0
      0
      40
      United-States
      <=50K

      4
      28
      Private
      338409
      Bachelors
      13
      Married-civ-spouse
      Prof-specialty
      Wife
      Black
      Female
      0
      0
      40
      Cuba
      <=50K

```
df['hours-per-week'].hist(bins=20);
```

```
df.describe(exclude='number')
```

      workclass
      education
      marital-status
      occupation
      relationship
      race
      sex
      country
      salary

      count
      30725
      32561
      32561
      30718
      32561
      32561
      32561
      31978
      32561

      unique
      8
      16
      7
      14
      6
      5
      2
      41
      2

      top
      Private
      HS-grad
      Married-civ-spouse
      Prof-specialty
      Husband
      White
      Male
      United-States
      <=50K

      freq
      22696
      10501
      14976
      4140
      13193
      27816
      21790
      29170
      24720

```
cut_points = [0, 9, 19, 29, 39, 49, 500]
label_names = ['0-9','10-19','20-29','30-39','40-49','50+']
df['hours_per_week_categories'] = pd.cut(df['hours-per-week'], cut_points, labels=label_names)
df.hours_per_week_categories.value_counts()
```

    40-49    18336
    50+       6462
    30-39     3667
    20-29     2392
    10-19     1246
    0-9        458
    Name: hours_per_week_categories, dtype: int64

```
df.sex.value_counts()
```

     Male      21790
     Female    10771
    Name: sex, dtype: int64

```
df = df.sort_values(by='hours_per_week_categories')
contingency_table = pd.crosstab(df['sex'], df['hours_per_week_categories'], margins=True)
contingency_table
```

      hours_per_week_categories
      0-9
      10-19
      20-29
      30-39
      40-49
      50+
      All

      sex

      Female
      235
      671
      1287
      1914
      5636
      1028
      10771

      Male
      223
      575
      1105
      1753
      12700
      5434
      21790

      All
      458
      1246
      2392
      3667
      18336
      6462
      32561

## Expected Value Calculation
\begin{align}
expected_{i,j} =\frac{(row_{i} \text{total})(column_{j} \text{total}) }{(\text{total observations})}
\end{align}

```
row_sums = contingency_table.iloc[0:2, 6].values
col_sums = contingency_table.iloc[2, 0:6].values

print(row_sums)
print(col_sums)
```

    [10771 21790]
    [  458  1246  2392  3667 18336  6462]

```
total = contingency_table.loc['All','All']
total
```

    32561

```
expected = []
for i in range(len(row_sums)):
  expected_row = []
  for column in col_sums:
    expected_val = column*row_sums[i]/total
    expected_row.append(expected_val)
  expected.append(expected_row)

expected = np.array(expected)
print(expected.shape)
print(expected)
```

    (2, 6)
    [[  151.50388502   412.16995793   791.26046497  1213.02346365
       6065.44811277  2137.59411566]
     [  306.49611498   833.83004207  1600.73953503  2453.97653635
      12270.55188723  4324.40588434]]

## Chi-Squared Statistic with Numpy

\begin{align}
\chi^2 = \sum \frac{(observed_{i}-expected_{i})^2}{(expected_{i})}
\end{align}

For the $observed$ values we will just use a version of our contingency table without the margins as a numpy array. In this way, if our observed values array and our expected values array are the same shape, then we can subtract them and divide them directly which makes the calculations a lot cleaner. No for loops!

```
observed = pd.crosstab(df['sex'], df['hours_per_week_categories']).values
print(observed.shape)
observed
```

    (2, 6)

    array([[  235,   671,  1287,  1914,  5636,  1028],
           [  223,   575,  1105,  1753, 12700,  5434]])

```
chi_square = ((observed - expected)**2/(expected)).sum()
chi_square
```

    2287.190943926107

## Run a $\chi^{2}$ Test using Scipy

```
help(stats.chi2_contingency)
```

    Help on function chi2_contingency in module scipy.stats.contingency:

    chi2_contingency(observed, correction=True, lambda_=None)
        Chi-square test of independence of variables in a contingency table.

        This function computes the chi-square statistic and p-value for the
        hypothesis test of independence of the observed frequencies in the
        contingency table [1]_ `observed`.  The expected frequencies are computed
        based on the marginal sums under the assumption of independence; see
        `scipy.stats.contingency.expected_freq`.  The number of degrees of
        freedom is (expressed using numpy functions and attributes)::

            dof = observed.size - sum(observed.shape) + observed.ndim - 1

        Parameters
        ----------
        observed : array_like
            The contingency table. The table contains the observed frequencies
            (i.e. number of occurrences) in each category.  In the two-dimensional
            case, the table is often described as an "R x C table".
        correction : bool, optional
            If True, *and* the degrees of freedom is 1, apply Yates' correction
            for continuity.  The effect of the correction is to adjust each
            observed value by 0.5 towards the corresponding expected value.
        lambda_ : float or str, optional.
            By default, the statistic computed in this test is Pearson's
            chi-squared statistic [2]_.  `lambda_` allows a statistic from the
            Cressie-Read power divergence family [3]_ to be used instead.  See
            `power_divergence` for details.

        Returns
        -------
        chi2 : float
            The test statistic.
        p : float
            The p-value of the test
        dof : int
            Degrees of freedom
        expected : ndarray, same shape as `observed`
            The expected frequencies, based on the marginal sums of the table.

        See Also
        --------
        contingency.expected_freq
        fisher_exact
        chisquare
        power_divergence

        Notes
        -----
        An often quoted guideline for the validity of this calculation is that
        the test should be used only if the observed and expected frequencies
        in each cell are at least 5.

        This is a test for the independence of different categories of a
        population. The test is only meaningful when the dimension of
        `observed` is two or more.  Applying the test to a one-dimensional
        table will always result in `expected` equal to `observed` and a
        chi-square statistic equal to 0.

        This function does not handle masked arrays, because the calculation
        does not make sense with missing values.

        Like stats.chisquare, this function computes a chi-square statistic;
        the convenience this function provides is to figure out the expected
        frequencies and degrees of freedom from the given contingency table.
        If these were already known, and if the Yates' correction was not
        required, one could use stats.chisquare.  That is, if one calls::

            chi2, p, dof, ex = chi2_contingency(obs, correction=False)

        then the following is true::

            (chi2, p) == stats.chisquare(obs.ravel(), f_exp=ex.ravel(),
                                         ddof=obs.size - 1 - dof)

        The `lambda_` argument was added in version 0.13.0 of scipy.

        References
        ----------
        .. [1] "Contingency table",
               https://en.wikipedia.org/wiki/Contingency_table
        .. [2] "Pearson's chi-squared test",
               https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test
        .. [3] Cressie, N. and Read, T. R. C., "Multinomial Goodness-of-Fit
               Tests", J. Royal Stat. Soc. Series B, Vol. 46, No. 3 (1984),
               pp. 440-464.

        Examples
        --------
        A two-way example (2 x 3):

        >>> from scipy.stats import chi2_contingency
        >>> obs = np.array([[10, 10, 20], [20, 20, 20]])
        >>> chi2_contingency(obs)
        (2.7777777777777777,
         0.24935220877729619,
         2,
         array([[ 12.,  12.,  16.],
                [ 18.,  18.,  24.]]))

        Perform the test using the log-likelihood ratio (i.e. the "G-test")
        instead of Pearson's chi-squared statistic.

        >>> g, p, dof, expctd = chi2_contingency(obs, lambda_="log-likelihood")
        >>> g, p
        (2.7688587616781319, 0.25046668010954165)

        A four-way example (2 x 2 x 2 x 2):

        >>> obs = np.array(
        ...     [[[[12, 17],
        ...        [11, 16]],
        ...       [[11, 12],
        ...        [15, 16]]],
        ...      [[[23, 15],
        ...        [30, 22]],
        ...       [[14, 17],
        ...        [15, 16]]]])
        >>> chi2_contingency(obs)
        (8.7584514426741897,
         0.64417725029295503,
         11,
         array([[[[ 14.15462386,  14.15462386],
                  [ 16.49423111,  16.49423111]],
                 [[ 11.2461395 ,  11.2461395 ],
                  [ 13.10500554,  13.10500554]]],
                [[[ 19.5591166 ,  19.5591166 ],
                  [ 22.79202844,  22.79202844]],
                 [[ 15.54012004,  15.54012004],
                  [ 18.10873492,  18.10873492]]]]))

```
chi_squared, p_value, dof, expected = stats.chi2_contingency(observed)
print(chi_squared, p_value, dof, expected)
```

    2287.190943926107 0.0 5 [[  151.50388502   412.16995793   791.26046497  1213.02346365
       6065.44811277  2137.59411566]
     [  306.49611498   833.83004207  1600.73953503  2453.97653635
      12270.55188723  4324.40588434]]

Null Hypothesis: Hours worked per week bins is **independent** of sex.

Due to a p-value of 0, we REJECT the null hypothesis that hours worked per week and sex are independent, and conclude that there is an association between hours worked per week and sex.

## Assignment - Build a confidence interval

A confidence interval refers to a neighborhood around some point estimate, the size of which is determined by the desired p-value. For instance, we might say that 52% of Americans prefer tacos to burritos, with a 95% confidence interval of +/- 5%.

52% (0.52) is the point estimate, and +/- 5% (the interval $[0.47, 0.57]$) is the confidence interval. "95% confidence" means a p-value $\leq 1 - 0.95 = 0.05$.

In this case, the confidence interval includes $0.5$ - which is the natural null hypothesis (that half of Americans prefer tacos and half burritos, thus there is no clear favorite). So in this case, we could use the confidence interval to report that we've failed to reject the null hypothesis.

But providing the full analysis with a confidence interval, including a graphical representation of it, can be a helpful and powerful way to tell your story. Done well, it is also more intuitive to a layperson than simply saying "fail to reject the null hypothesis" - it shows that in fact the data does *not* give a single clear result (the point estimate) but a whole range of possibilities.

How is a confidence interval built, and how should it be interpreted? It does *not* mean that 95% of the data lies in that interval - instead, the frequentist interpretation is "if we were to repeat this experiment 100 times, we would expect the average result to lie in this interval ~95 times."

For a 95% confidence interval and a normal(-ish) distribution, you can simply remember that +/-2 standard deviations contains 95% of the probability mass, and so the 95% confidence interval based on a given sample is centered at the mean (point estimate) and has a range of +/- 2 (or technically 1.96) standard deviations.

Different distributions/assumptions (90% confidence, 99% confidence) will require different math, but the overall process and interpretation (with a frequentist approach) will be the same.

Your assignment - using the data from the prior module ([congressional voting records](https://archive.ics.uci.edu/ml/datasets/Congressional+Voting+Records)):

### Confidence Intervals:
1. Generate and numerically represent a confidence interval
2. Graphically (with a plot) represent the confidence interval
3. Interpret the confidence interval - what does it tell you about the data and its distribution?

### Chi-squared tests:
4. Take a dataset that we have used in the past in class that has **categorical** variables. Pick two of those categorical variables and run a chi-squared tests on that data
  - By hand using Numpy
  - In a single line using Scipy

Stretch goals:

1. Write a summary of your findings, mixing prose and math/code/results. *Note* - yes, this is by definition a political topic. It is challenging but important to keep your writing voice *neutral* and stick to the facts of the data. Data science often involves considering controversial issues, so it's important to be sensitive about them (especially if you want to publish).
2. Apply the techniques you learned today to your project data or other data of your choice, and write/discuss your findings here.
3. Refactor your code so it is elegant, readable, and can be easily run for all issues.

```
# TODO - your code!
```

## Resources

- [Interactive visualize the Chi-Squared test](https://homepage.divms.uiowa.edu/~mbognar/applets/chisq.html)
- [Calculation of Chi-Squared test statistic](https://en.wikipedia.org/wiki/Pearson%27s_chi-squared_test)
- [Visualization of a confidence interval generated by R code](https://commons.wikimedia.org/wiki/File:Confidence-interval.svg)
- [Expected value of a squared standard normal](https://math.stackexchange.com/questions/264061/expected-value-calculation-for-squared-normal-distribution) (it's 1 - which is why the expected value of a Chi-Squared with $n$ degrees of freedom is $n$, as it's the sum of $n$ squared standard normals)


```python
%matplotlib inline
```

# Tutorial 05: Kernels and averages

Simulating swarming models requires expensive mean-field convolution operations of the form:

\begin{align}J^i = \frac{1}{N}\sum_{j=1}^N K(|X^j-X^i|) U^j,\end{align}
for $1\leq i\leq N$, where $(X^i)_{1\leq i \leq N}$ are the positions of the particles, $(U^j)_{1\leq j\leq N}$ are given vectors and $K$ is an **observation kernel**. Typically, $K(|X^i-X^j|)$ is equal to 1 if $X^i$ and $X^j$ are at distance smaller than a fixed interaction distance and 0 otherwise. Other kernels are defined in the module :mod:`sisyphe.kernels`. Below, we show a simple application case.

## Linear local averages

First, some standard imports...

```python
import time
import math
import torch
from matplotlib import pyplot as plt

use_cuda = torch.cuda.is_available()
dtype = torch.cuda.FloatTensor if use_cuda else torch.FloatTensor
```

Let the $N$ particles be uniformly scattered in a box of size $L$ with interaction radius  $R$.

```python
N = 100000
L = 1.
R = .15

pos = L*torch.rand((N,2)).type(dtype)
```

We can also assume that the particles have a bounded cone of vision around an axis (defined by a unit vector). The default behaviour is a full vision angle equal to $2\pi$ in which case the axis is a :data:`None` object. Here we take a cone of vision with angle $\pi/2$ around an axis which is sampled uniformly. For the :class:`sisyphe.particles.KineticParticles`, the default axis is the velocity.

```python
angle = math.pi/2
axis = torch.randn(N,2).type(dtype)
axis = axis/torch.norm(axis,dim=1).reshape((N,1))
```

Let us create an instance of a particle system with these parameters.

```python
from sisyphe.particles import Particles

particles = Particles(
    pos = pos,
    interaction_radius = R,
    box_size = L,
    vision_angle = angle,
    axis = axis)
```

Note

By default, the system and the operations below are defined with periodic boundary conditions.

As a simple application, we can compute the number of neighbours of each particle and print the number of neighbours of the first particle. This operation is already implemented in the method :func:`number_of_neighbours() <sisyphe.particles.Particles.number_of_neighbours>`. It simply corresponds to the average:

\begin{align}N^i_\mathrm{neigh} = \sum_{j=1}^N K(|X^j-X^i|).\end{align}

```python
Nneigh = particles.number_of_neighbours()

Nneigh0 = int(Nneigh[0].item())

print("The first particle sees " + str(Nneigh0) + " other particles.")
```

For custom objects, the mean-field average can be computed using the method :func:`linear_local_average() <sisyphe.particles.Particles.linear_local_average>`. As an example, let us compute the center of mass of the neighbours of each particle. First we define the quantity $U$ that we want to average. Here, since we are working on a torus, there are two: the sine and the cosine of the spatial coordinates.

```python
cos_pos = torch.cos((2*math.pi / L) * particles.pos)
sin_pos = torch.sin((2*math.pi / L) * particles.pos)
```

Then we compute the two mean field averages, i.e. the standard convolution over the $N$ particles. The center of mass along each dimension is the argument of the complex number whose coordinates are the average cosine and sine.

```python
average_cos, average_sin = particles.linear_local_average(cos_pos, sin_pos)
center_x = torch.atan2(average_sin[:,0], average_cos[:,0])
center_x = (L / (2*math.pi)) * torch.remainder(center_x, 2*math.pi)
center_y = torch.atan2(average_sin[:,1], average_cos[:,1])
center_y = (L / (2*math.pi)) * torch.remainder(center_y, 2*math.pi)

center_of_mass = torch.cat((center_x.reshape((N,1)), center_y.reshape((N,1))),
                            dim=1)
```

In the method :func:`linear_local_average() <sisyphe.particles.Particles.linear_local_average>`, the default observation kernel is a :class:`LazyTensor` of size $(N,N)$ whose $(i,j)$ component is equal to 1 when particle $j$ belongs to the cone of vision of particle $i$ and 0 otherwise. To retrieve the indexes of the particles which belong to the cone of vision of the first particle, we can use the `K-nearest-neighbours reduction <https://www.kernel-operations.io/keops/_auto_tutorials/knn/plot_knn_mnist.html#sphx-glr-auto-tutorials-knn-plot-knn-mnist-py>`_ provided by the `KeOps <https://www.kernel-operations.io/keops/index.html>`_ library.

```python
from sisyphe.kernels import lazy_interaction_kernel

interaction_kernel = lazy_interaction_kernel(
    particles.pos,
    particles.pos,
    particles.R,
    particles.L,
    boundary_conditions = particles.bc,
    vision_angle = particles.angle,
    axis = particles.axis)

K_ij = 1. - interaction_kernel

neigh0 = K_ij.argKmin(Nneigh0, dim=1)[0]

print("The indexes of the neighbours of the first particles are: ")
print(neigh0)
```

Finally, a fancy display of what we have computed. We plot the full particle system in black, the first particle in orange, its neighbours in blue and the center of mass of the neighbours in red.

```python
xall = particles.pos[:,0].cpu().numpy()
yall = particles.pos[:,1].cpu().numpy()

x = particles.pos[neigh0,0].cpu().numpy()
y = particles.pos[neigh0,1].cpu().numpy()

x0 = particles.pos[0,0].item()
y0 = particles.pos[0,1].item()

xc = center_of_mass[0,0].item()
yc = center_of_mass[0,1].item()

fig, ax = plt.subplots(figsize=(6,6))
ax.scatter(xall, yall, s=.003, c='black')
ax.scatter(x, y, s=.3)
ax.scatter(x0, y0, s=24)
ax.scatter(xc, yc, s=24, c='red')
ax.axis([0, L, 0, L])
ax.set_aspect("equal")
```

## Nonlinear averages

In some cases, we need to compute a **nonlinear average** of the form

\begin{align}J^i = \frac{1}{N}\sum_{j=1}^N K(|X^j-X^i|) b(U^i,V^j),\end{align}

where $(U^i)_{1\leq i \leq N}$ and $(V^j)_{1\leq j \leq N}$ are given vectors and $b$ is a given function. When the **binary formula** $b$ can be written as a :class:`LazyTensor`, this can be computed with the method :func:`nonlinear_local_average() <sisyphe.particles.Particles.nonlinear_local_average>`.

For instance, let us compute the local mean square distance:

\begin{align}J^i = \frac{\sum_{j=1}^N K(|X^j-X^i|) |X^j-X^i|^2}{\sum_{j=1}^N K(|X^j-X^i|)}.\end{align}

In this case, we can use the function :func:`sisyphe.kernels.lazy_xy_matrix` to define a custom binary formula. Given two vectors $X=(X^i)_{1\leq i\leq M}$ and $Y = (Y^j)_{1\leq j\leq N}$, respectively of sizes $(M,d)$ and $(N,d)$, the $XY$ matrix is a $(M,N,d)$ LazyTensor whose $(i,j,:)$ component is the vector $Y^j-X^i$.

```python
from sisyphe.kernels import lazy_xy_matrix

def b(x,y):
    K_ij = lazy_xy_matrix(x,y,particles.L)
    return (K_ij ** 2).sum(-1)

x = particles.pos
y = particles.pos
mean_square_dist = N/Nneigh.reshape((N,1)) * particles.nonlinear_local_average(b,x,y)
```

Since the particles are uniformly scattered in the box, the theoretical value is

\begin{align}MSD_0 = \frac{\int_0^R \int_0^{\pi/2} r^3 \mathrm{d}r\mathrm{d}\theta}{\int_0^R \int_0^{\pi/2} r \mathrm{d}r\mathrm{d}\theta} = \frac{R^2}{2}\end{align}

```python
print("Theoretical value: " + str(R**2/2))
print("Experimental value: " + str(mean_square_dist[0].item()))
```


# Converting *Numerical* ADM Initial Data in the Spherical or Cartesian Basis to BSSN Initial Data in the Desired Curvilinear Basis
## Author: Zach Etienne
### Formatting improvements courtesy Brandon Clark

### NRPy+ Source Code for this module: [BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinearID.py](../edit/BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinearID.py)

**This module is meant for use only with initial data that can be represented numerically in ADM form, either in the Spherical or Cartesian basis. I.e., the ADM variables are given $\left\{\gamma_{ij}, K_{ij}, \alpha, \beta^i\right\}$ *numerically* as functions of $(r,\theta,\phi)$ or $(x,y,z)$; e.g., through an initial data solver. If instead the ADM initial data are provided as exact (algebraic) functions of $(r,\theta,\phi)$ or $(x,y,z)$, then is is better to use [the Exact-ADM-Spherical/Cartesian-to-BSSNCurvilinear module](Tutorial-ADM_Initial_Data-Converting_Exact_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb) instead.**

## Introduction:
Given the ADM variables:

$$\left\{\gamma_{ij}, K_{ij}, \alpha, \beta^i, B^i\right\}$$

in the Spherical or Cartesian basis, and as functions of $(r,\theta,\phi)$ or $(x,y,z)$, respectively, this module documents their conversion to the BSSN variables

$$\left\{\bar{\gamma}_{i j},\bar{A}_{i j},\phi, K, \bar{\Lambda}^{i}, \alpha, \beta^i, B^i\right\},$$

in the desired curvilinear basis (given by reference_metric::CoordSystem). Then it rescales the resulting BSSNCurvilinear variables (as defined in [the BSSN Curvilinear tutorial](Tutorial-BSSNCurvilinear.ipynb)) into the form needed for BSSNCurvilinear evolutions:

$$\left\{h_{i j},a_{i j},\phi, K, \lambda^{i}, \alpha, \mathcal{V}^i, \mathcal{B}^i\right\}.$$

We will use as our core example in this module UIUC initial data, which are ([as documented in their NRPy+ initial data module](Tutorial-ADM_Initial_Data-UIUC_BlackHole.ipynb)) given in terms of ADM variables in Spherical coordinates.

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

The module is organized as follows:

1. [Step 1](#initializenrpy): Initialize core Python/NRPy+ modules
1. [Step 2](#cylindrical): Desired output BSSN Curvilinear coordinate system set to Cylindrical, as a proof-of-principle
1. [Step 3](#admfunc): Setting ADM variables through initial data solver C function
1. [Step 4](#adm_jacobian): Applying Jacobian transformations to get in the correct $xx0,xx1,xx2$ basis
1. [Step 5](#adm2bssn): Perform the ADM-to-BSSN conversion for 3-metric, extrinsic curvature, and gauge quantities
    1. [Step 5.a](#adm2bssn_gamma): Convert ADM $\gamma_{ij}$ to BSSN $\bar{\gamma}_{ij}$
    1. [Step 5.b](#admexcurv_convert): Convert the ADM extrinsic curvature $K_{ij}$
    1. [Step 5.c](#conformal): Define the conformal factor variable $\texttt{cf}$
1. [Step 6](#rescale): Rescale tensorial quantities
1. [Step 7](#adm2bssn_c): Output all ADM-to-BSSN expressions to a C function
    1. [Step 7.a](#driver): Output the driver function for the above C function
1. [Step 8](#lambda): Compute $\bar{\Lambda}^i$ from finite-difference derivatives of rescaled metric quantities
1. [Step 9](#code_validation): Code Validation against BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear NRPy+ module
1. [Step 10](#latex_pdf_output): Output this module to $\LaTeX$-formatted PDF

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

```python
# Step 1: Initialize core Python/NRPy+ modules
import sympy as sp
import NRPy_param_funcs as par
from outputC import *
import indexedexp as ixp
import reference_metric as rfm
import loop as lp
import grid as gri
import finite_difference as fin
import BSSN.BSSN_quantities as Bq # The EvolvedConformalFactor_cf parameter is used below
```

# Step 2: Desired output BSSN Curvilinear coordinate system set to Cylindrical, as a proof-of-principle \[Back to [top](#toc)\]
$$\label{cylindrical}$$

```python
# Step 2: Desired output BSSN Curvilinear coordinate system set to Cylindrical, as a proof-of-principle

# The ADM & BSSN formalisms only work in 3D; they are 3+1 decompositions of Einstein's equations.
#    To implement axisymmetry or spherical symmetry, simply set all spatial derivatives in
#    the relevant angular directions to zero; DO NOT SET DIM TO ANYTHING BUT 3.

# Step 0: Set spatial dimension (must be 3 for BSSN)
DIM = 3

# Set the desired *output* coordinate system to Cylindrical:
par.set_parval_from_str("reference_metric::CoordSystem","Cylindrical")
rfm.reference_metric()

# Set function input parameters to consistent defaults.
ADM_input_function_name = "ID_ADM_SphorCart"
pointer_to_ID_inputs = False
```

# Step 3: Setting ADM variables through initial data solver C function \[Back to [top](#toc)\]
$$\label{admfunc}$$

Given $(r,\theta,\phi)$ or $(x,y,z)$, we assume the initial data solver provides a C function ID_ADM_SphorCart({$(r,\theta,\phi)$ or $(x,y,z)$}, {ADM variables}) that sets all ADM variables in the spherical or Cartesian basis, respectively. Here, as a validation test, we set up this function for UIUC Black Hole initial data.

```python
# Step 3: Setting ADM variables through initial data solver C function

# Import UIUC Black Hole initial data
import BSSN.UIUCBlackHole as uibh
uibh.UIUCBlackHole(ComputeADMGlobalsOnly=True)

with open("BSSN/"+ADM_input_function_name+".h", "w") as file:
    file.write("""
// 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.
void """+ADM_input_function_name+"""(const REAL xyz_or_rthph[3],
                     REAL *gammaDD00,REAL *gammaDD01,REAL *gammaDD02,REAL *gammaDD11,REAL *gammaDD12,REAL *gammaDD22,
                     REAL *KDD00,REAL *KDD01,REAL *KDD02,REAL *KDD11,REAL *KDD12,REAL *KDD22,
                     REAL *alpha,
                     REAL *betaU0,REAL *betaU1,REAL *betaU2,
                     REAL *BU0,REAL *BU1,REAL *BU2) {
      const REAL r  = xyz_or_rthph[0];
      const REAL th = xyz_or_rthph[1];
      const REAL ph = xyz_or_rthph[2];\n""")
outCparams = "preindent=1,outCfileaccess=a,outCverbose=False,includebraces=False"
outputC([uibh.gammaSphDD[0][0],uibh.gammaSphDD[0][1],uibh.gammaSphDD[0][2],
           uibh.gammaSphDD[1][1],uibh.gammaSphDD[1][2],uibh.gammaSphDD[2][2],
           uibh.KSphDD[0][0],uibh.KSphDD[0][1],uibh.KSphDD[0][2],
           uibh.KSphDD[1][1],uibh.KSphDD[1][2],uibh.KSphDD[2][2],
           uibh.alphaSph, uibh.betaSphU[0],uibh.betaSphU[1],uibh.betaSphU[2],
           uibh.BSphU[0],uibh.BSphU[1],uibh.BSphU[2]],
        ["*gammaDD00","*gammaDD01","*gammaDD02","*gammaDD11","*gammaDD12","*gammaDD22",
         "*KDD00","*KDD01","*KDD02","*KDD11","*KDD12","*KDD22",
         "*alpha","*betaU0","*betaU1","*betaU2","*BU0","*BU1","*BU2"],
        "BSSN/"+ADM_input_function_name+".h",params=outCparams)
with open("BSSN/"+ADM_input_function_name+".h", "a") as file:
    file.write("}\n")
```

    Appended to file "BSSN/ID_ADM_SphorCart.h"

# Step 4:  Applying Jacobian transformations to get in the correct $xx0,xx1,xx2$ basis \[Back to [top](#toc)\]
$$\label{adm_jacobian}$$

The following discussion holds for either Spherical or Cartesian input data, so for simplicity let's just assume the data are given in Spherical coordinates.

All ADM tensors and vectors are in the Spherical coordinate basis $x^i_{\rm Sph} = (r,\theta,\phi)$, but we need them in the curvilinear coordinate basis $x^i_{\rm rfm}= ({\rm xx0},{\rm xx1},{\rm xx2})$ set by the "reference_metric::CoordSystem" variable. Empirically speaking, it is far easier to write $(x({\rm xx0},{\rm xx1},{\rm xx2}),y({\rm xx0},{\rm xx1},{\rm xx2}),z({\rm xx0},{\rm xx1},{\rm xx2}))$ than the inverse, so we will compute the Jacobian matrix

$$
{\rm Jac\_dUSph\_dDrfmUD[i][j]} = \frac{\partial x^i_{\rm Sph}}{\partial x^j_{\rm rfm}},
$$

via exact differentiation (courtesy SymPy), and the inverse Jacobian
$$
{\rm Jac\_dUrfm\_dDSphUD[i][j]} = \frac{\partial x^i_{\rm rfm}}{\partial x^j_{\rm Sph}},
$$

using NRPy+'s ${\rm generic\_matrix\_inverter3x3()}$ function. In terms of these, the transformation of BSSN tensors from Spherical to "reference_metric::CoordSystem" coordinates may be written:

\begin{align}
\beta^i_{\rm rfm} &= \frac{\partial x^i_{\rm rfm}}{\partial x^\ell_{\rm Sph}} \beta^\ell_{\rm Sph}\\
B^i_{\rm rfm} &= \frac{\partial x^i_{\rm rfm}}{\partial x^\ell_{\rm Sph}} B^\ell_{\rm Sph}\\
\gamma^{\rm rfm}_{ij} &=
\frac{\partial x^\ell_{\rm Sph}}{\partial x^i_{\rm rfm}}
\frac{\partial x^m_{\rm Sph}}{\partial x^j_{\rm rfm}} \gamma^{\rm Sph}_{\ell m}\\
K^{\rm rfm}_{ij} &=
\frac{\partial x^\ell_{\rm Sph}}{\partial x^i_{\rm rfm}}
\frac{\partial x^m_{\rm Sph}}{\partial x^j_{\rm rfm}} K^{\rm Sph}_{\ell m}
\end{align}

```python
# Step 4:  Applying Jacobian transformations to get in the correct $xx0,xx1,xx2$ basis

# Step 4 P0: All input quantities are in terms of r,th,ph or x,y,z. We want them in terms
#         of xx0,xx1,xx2, so here we call sympify_integers__replace_rthph() to replace
#         r,th,ph or x,y,z, respectively, with the appropriate functions of xx0,xx1,xx2
#         as defined for this particular reference metric in reference_metric.py's
#         xxSph[] or xxCart[], respectively:

# Define the input variables:
gammaSphorCartDD = ixp.declarerank2("gammaSphorCartDD","sym01")
KSphorCartDD     = ixp.declarerank2("KSphorCartDD","sym01")
alphaSphorCart = sp.symbols("alphaSphorCart")
betaSphorCartU = ixp.declarerank1("betaSphorCartU")
BSphorCartU    = ixp.declarerank1("BSphorCartU")

# UIUC Black Hole initial data are given in Spherical coordinates.
CoordType_in = "Spherical"

# Make sure that rfm.reference_metric() has been called.
#    We'll need the variables it defines throughout this module.
if rfm.have_already_called_reference_metric_function == False:
    print("Error. Called Convert_Spherical_ADM_to_BSSN_curvilinear() without")
    print("       first setting up reference metric, by calling rfm.reference_metric().")
    exit(1)

r_th_ph_or_Cart_xyz_oID_xx = []
if CoordType_in == "Spherical":
    r_th_ph_or_Cart_xyz_oID_xx = rfm.xxSph
elif CoordType_in == "Cartesian":
    r_th_ph_or_Cart_xyz_oID_xx = rfm.xxCart
else:
    print("Error: Can only convert ADM Cartesian or Spherical initial data to BSSN Curvilinear coords.")
    exit(1)

# Next apply Jacobian transformations to convert into the (xx0,xx1,xx2) basis

# alpha is a scalar, so no Jacobian transformation is necessary.
alpha = alphaSphorCart

Jac_dUSphorCart_dDrfmUD = ixp.zerorank2()
for i in range(DIM):
    for j in range(DIM):
        Jac_dUSphorCart_dDrfmUD[i][j] = sp.diff(r_th_ph_or_Cart_xyz_oID_xx[i],rfm.xx[j])

Jac_dUrfm_dDSphorCartUD, dummyDET = ixp.generic_matrix_inverter3x3(Jac_dUSphorCart_dDrfmUD)

betaU   = ixp.zerorank1()
BU      = ixp.zerorank1()
gammaDD = ixp.zerorank2()
KDD     = ixp.zerorank2()
for i in range(DIM):
    for j in range(DIM):
        betaU[i] += Jac_dUrfm_dDSphorCartUD[i][j] * betaSphorCartU[j]
        BU[i]    += Jac_dUrfm_dDSphorCartUD[i][j] * BSphorCartU[j]
        for k in range(DIM):
            for l in range(DIM):
                gammaDD[i][j] += Jac_dUSphorCart_dDrfmUD[k][i]*Jac_dUSphorCart_dDrfmUD[l][j] * gammaSphorCartDD[k][l]
                KDD[i][j]     += Jac_dUSphorCart_dDrfmUD[k][i]*Jac_dUSphorCart_dDrfmUD[l][j] *     KSphorCartDD[k][l]
```

# Step 5: Perform the ADM-to-BSSN conversion for 3-metric, extrinsic curvature, and gauge quantities \[Back to [top](#toc)\]
$$\label{adm2bssn}$$

All ADM quantities were input into this function in the Spherical or Cartesian basis, as functions of r,th,ph or x,y,z, respectively. In Steps 1 and 2 above, we converted them to the xx0,xx1,xx2 basis, and as functions of xx0,xx1,xx2. Here we convert ADM quantities to their BSSN Curvilinear counterparts.

## Step 5.a: Convert ADM $\gamma_{ij}$ to BSSN $\bar{\gamma}_{ij}$ \[Back to [top](#toc)\]
$$\label{adm2bssn_gamma}$$

We have (Eqs. 2 and 3 of [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf)):
$$
\bar{\gamma}_{i j} = \left(\frac{\bar{\gamma}}{\gamma}\right)^{1/3} \gamma_{ij},
$$
where we always make the choice $\bar{\gamma} = \hat{\gamma}$:

```python
# Step 5.a: Convert ADM $\gamma_{ij}$ to BSSN $\bar{\gamma}_{ij}$

gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)
gammabarDD = ixp.zerorank2()
for i in range(DIM):
    for j in range(DIM):
        gammabarDD[i][j] = (rfm.detgammahat/gammaDET)**(sp.Rational(1,3))*gammaDD[i][j]
```

## Step 5.b: Convert the ADM extrinsic curvature $K_{ij}$ \[Back to [top](#toc)\]
$$\label{admexcurv_convert}$$

Convert the ADM extrinsic curvature $K_{ij}$ to the trace-free extrinsic curvature $\bar{A}_{ij}$, plus the trace of the extrinsic curvature $K$, where (Eq. 3 of [Baumgarte *et al.*](https://arxiv.org/pdf/1211.6632.pdf)):
\begin{align}
K &= \gamma^{ij} K_{ij} \\
\bar{A}_{ij} &= \left(\frac{\bar{\gamma}}{\gamma}\right)^{1/3} \left(K_{ij} - \frac{1}{3} \gamma_{ij} K \right)
\end{align}

```python
# Step 5.b: Convert the ADM extrinsic curvature $K_{ij}$

trK = sp.sympify(0)
for i in range(DIM):
    for j in range(DIM):
        trK += gammaUU[i][j]*KDD[i][j]

AbarDD = ixp.zerorank2()
for i in range(DIM):
    for j in range(DIM):
        AbarDD[i][j] = (rfm.detgammahat/gammaDET)**(sp.Rational(1,3))*(KDD[i][j] - sp.Rational(1,3)*gammaDD[i][j]*trK)
```

## Step 5.c: Define the conformal factor variable $\texttt{cf}$ \[Back to [top](#toc)\]
$$\label{conformal}$$

We define the conformal factor variable $\texttt{cf}$ based on the setting of the "BSSN_quantities::EvolvedConformalFactor_cf" parameter.

For example if "EvolvedConformalFactor_cf" is set to "phi", we can use Eq. 3 of [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf), which in arbitrary coordinates is written:

$$
\phi = \frac{1}{12} \log\left(\frac{\gamma}{\bar{\gamma}}\right).
$$

Alternatively if "BSSN_RHSs::EvolvedConformalFactor_cf" is set to "chi", then
$$
\chi = e^{-4 \phi} = \exp\left(-4 \frac{1}{12} \left(\frac{\gamma}{\bar{\gamma}}\right)\right)
= \exp\left(-\frac{1}{3} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) = \left(\frac{\gamma}{\bar{\gamma}}\right)^{-1/3}.
$$

Finally if "BSSN_RHSs::EvolvedConformalFactor_cf" is set to "W", then
$$
W = e^{-2 \phi} = \exp\left(-2 \frac{1}{12} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) =
\exp\left(-\frac{1}{6} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) =
\left(\frac{\gamma}{\bar{\gamma}}\right)^{-1/6}.
$$

```python
# Step 5.c: Define the conformal factor variable $\texttt{cf}$

# First compute gammabarDET:
gammabarUU, gammabarDET = ixp.symm_matrix_inverter3x3(gammabarDD)

cf = sp.sympify(0)

if par.parval_from_str("EvolvedConformalFactor_cf") == "phi":
    cf = sp.Rational(1,12)*sp.log(gammaDET/gammabarDET)
elif par.parval_from_str("EvolvedConformalFactor_cf") == "chi":
    cf = (gammaDET/gammabarDET)**(-sp.Rational(1,3))
elif par.parval_from_str("EvolvedConformalFactor_cf") == "W":
    cf = (gammaDET/gammabarDET)**(-sp.Rational(1,6))
else:
    print("Error EvolvedConformalFactor_cf type = \""+par.parval_from_str("EvolvedConformalFactor_cf")+"\" unknown.")
    exit(1)
```

# Step 6: Rescale tensorial quantities \[Back to [top](#toc)\]
$$\label{rescale}$$

We rescale tensorial quantities according to the prescription described in the [BSSN in curvilinear coordinates tutorial module](Tutorial-BSSNCurvilinear.ipynb) (also [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf)):

\begin{align}
h_{ij} &= (\bar{\gamma}_{ij} - \hat{\gamma}_{ij})/\text{ReDD[i][j]}\\
a_{ij} &= \bar{A}_{ij}/\text{ReDD[i][j]}\\
\mathcal{V}^i &= \beta^i/\text{ReU[i]}\\
\mathcal{B}^i &= B^i/\text{ReU[i]}\\
\end{align}

```python
# Step 6: Rescale tensorial quantities

hDD     = ixp.zerorank2()
aDD     = ixp.zerorank2()
vetU    = ixp.zerorank1()
betU    = ixp.zerorank1()
for i in range(DIM):
    vetU[i]    =      betaU[i] / rfm.ReU[i]
    betU[i]    =         BU[i] / rfm.ReU[i]
    for j in range(DIM):
        hDD[i][j] = (gammabarDD[i][j] - rfm.ghatDD[i][j]) / rfm.ReDD[i][j]
        aDD[i][j] =                          AbarDD[i][j] / rfm.ReDD[i][j]
```

# Step 7: Output all ADM-to-BSSN expressions to a C function \[Back to [top](#toc)\]
$$\label{adm2bssn_c}$$

This function must first call the ID_ADM_SphorCart() defined above. Using these Spherical or Cartesian data, it sets up all quantities needed for BSSNCurvilinear initial data, *except* $\lambda^i$, which must be computed from numerical data using finite-difference derivatives.

```python
# Step 7: Output all ADM-to-BSSN expressions to a C function

with open("BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h", "w") as file:
    file.write("void ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs(const REAL xx0xx1xx2[3],")
    if pointer_to_ID_inputs == True:
        file.write("ID_inputs *other_inputs,")
    else:
        file.write("ID_inputs other_inputs,")
    file.write("""
                    REAL *hDD00,REAL *hDD01,REAL *hDD02,REAL *hDD11,REAL *hDD12,REAL *hDD22,
                    REAL *aDD00,REAL *aDD01,REAL *aDD02,REAL *aDD11,REAL *aDD12,REAL *aDD22,
                    REAL *trK,
                    REAL *vetU0,REAL *vetU1,REAL *vetU2,
                    REAL *betU0,REAL *betU1,REAL *betU2,
                    REAL *alpha,  REAL *cf) {
      REAL gammaSphorCartDD00,gammaSphorCartDD01,gammaSphorCartDD02,
           gammaSphorCartDD11,gammaSphorCartDD12,gammaSphorCartDD22;
      REAL KSphorCartDD00,KSphorCartDD01,KSphorCartDD02,
           KSphorCartDD11,KSphorCartDD12,KSphorCartDD22;
      REAL alphaSphorCart,betaSphorCartU0,betaSphorCartU1,betaSphorCartU2;
      REAL BSphorCartU0,BSphorCartU1,BSphorCartU2;
      const REAL xx0 = xx0xx1xx2[0];
      const REAL xx1 = xx0xx1xx2[1];
      const REAL xx2 = xx0xx1xx2[2];
      REAL xyz_or_rthph[3];\n""")
outCparams = "preindent=1,outCfileaccess=a,outCverbose=False,includebraces=False"
outputC(r_th_ph_or_Cart_xyz_oID_xx[0:3],["xyz_or_rthph[0]","xyz_or_rthph[1]","xyz_or_rthph[2]"],
        "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",outCparams+",CSE_enable=False")
with open("BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h", "a") as file:
    file.write("      "+ADM_input_function_name+"""(xyz_or_rthph, other_inputs,
                      &gammaSphorCartDD00,&gammaSphorCartDD01,&gammaSphorCartDD02,
                      &gammaSphorCartDD11,&gammaSphorCartDD12,&gammaSphorCartDD22,
                      &KSphorCartDD00,&KSphorCartDD01,&KSphorCartDD02,
                      &KSphorCartDD11,&KSphorCartDD12,&KSphorCartDD22,
                      &alphaSphorCart,&betaSphorCartU0,&betaSphorCartU1,&betaSphorCartU2,
                      &BSphorCartU0,&BSphorCartU1,&BSphorCartU2);
        // Next compute all rescaled BSSN curvilinear quantities:\n""")
outCparams = "preindent=1,outCfileaccess=a,outCverbose=False,includebraces=False"
outputC([hDD[0][0],hDD[0][1],hDD[0][2],hDD[1][1],hDD[1][2],hDD[2][2],
         aDD[0][0],aDD[0][1],aDD[0][2],aDD[1][1],aDD[1][2],aDD[2][2],
         trK,  vetU[0],vetU[1],vetU[2],  betU[0],betU[1],betU[2],
         alpha, cf],
        ["*hDD00","*hDD01","*hDD02","*hDD11","*hDD12","*hDD22",
         "*aDD00","*aDD01","*aDD02","*aDD11","*aDD12","*aDD22",
         "*trK",  "*vetU0","*vetU1","*vetU2",  "*betU0","*betU1","*betU2",
         "*alpha","*cf"],
        "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",params=outCparams)
with open("BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h", "a") as file:
    file.write("}\n")
```

    Appended to file "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h"
    Appended to file "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h"

## Step 7.a: Output the driver function for the above C function \[Back to [top](#toc)\]
$$\label{driver}$$

We output the driver function for the above C function:
`ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs()`

```python
# Step 7.a: Output the driver function for the above C function

# Next write the driver function for ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs():
with open("BSSN/ID_BSSN__ALL_BUT_LAMBDAs.h", "w") as file:
    file.write("void ID_BSSN__ALL_BUT_LAMBDAs(const int Nxx_plus_2NGHOSTS[3],REAL *xx[3],")
    if pointer_to_ID_inputs == True:
        file.write("ID_inputs *other_inputs,")
    else:
        file.write("ID_inputs other_inputs,")
    file.write("REAL *in_gfs) {\n")
    file.write(lp.loop(["i2", "i1", "i0"], ["0", "0", "0"],
                       ["Nxx_plus_2NGHOSTS[2]", "Nxx_plus_2NGHOSTS[1]", "Nxx_plus_2NGHOSTS[0]"],
                       ["1", "1", "1"], ["#pragma omp parallel for",
                                         "    const REAL xx2 = xx[2][i2];",
                                         "        const REAL xx1 = xx[1][i1];"], "",
                       """const REAL xx0 = xx[0][i0];
const int idx = IDX3(i0,i1,i2);
const REAL xx0xx1xx2[3] = {xx0,xx1,xx2};
ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs(xx0xx1xx2,other_inputs,
                    &in_gfs[IDX4pt(HDD00GF,idx)],&in_gfs[IDX4pt(HDD01GF,idx)],&in_gfs[IDX4pt(HDD02GF,idx)],
                    &in_gfs[IDX4pt(HDD11GF,idx)],&in_gfs[IDX4pt(HDD12GF,idx)],&in_gfs[IDX4pt(HDD22GF,idx)],
                    &in_gfs[IDX4pt(ADD00GF,idx)],&in_gfs[IDX4pt(ADD01GF,idx)],&in_gfs[IDX4pt(ADD02GF,idx)],
                    &in_gfs[IDX4pt(ADD11GF,idx)],&in_gfs[IDX4pt(ADD12GF,idx)],&in_gfs[IDX4pt(ADD22GF,idx)],
                    &in_gfs[IDX4pt(TRKGF,idx)],
                    &in_gfs[IDX4pt(VETU0GF,idx)],&in_gfs[IDX4pt(VETU1GF,idx)],&in_gfs[IDX4pt(VETU2GF,idx)],
                    &in_gfs[IDX4pt(BETU0GF,idx)],&in_gfs[IDX4pt(BETU1GF,idx)],&in_gfs[IDX4pt(BETU2GF,idx)],
                    &in_gfs[IDX4pt(ALPHAGF,idx)],&in_gfs[IDX4pt(CFGF,idx)]);
"""))
    file.write("}\n")
```

# Step 8: Compute $\bar{\Lambda}^i$ from finite-difference derivatives of rescaled metric quantities \[Back to [top](#toc)\]
$$\label{lambda}$$

We compute $\bar{\Lambda}^i$ (Eqs. 4 and 5 of [Baumgarte *et al.*](https://arxiv.org/pdf/1211.6632.pdf)), from finite-difference derivatives of rescaled metric quantities $h_{ij}$:

$$
\bar{\Lambda}^i = \bar{\gamma}^{jk}\left(\bar{\Gamma}^i_{jk} - \hat{\Gamma}^i_{jk}\right).
$$

The [reference_metric.py](../edit/reference_metric.py) module provides us with analytic expressions for $\hat{\Gamma}^i_{jk}$, so here we need only compute finite-difference expressions for $\bar{\Gamma}^i_{jk}$, based on the values for $h_{ij}$ provided in the initial data. Once $\bar{\Lambda}^i$ has been computed, we apply the usual rescaling procedure:
$$
\lambda^i = \bar{\Lambda}^i/\text{ReU[i]},
$$
and then output the result to a C file using the NRPy+ finite-difference C output routine.

```python
# Step 8: Compute $\bar{\Lambda}^i$ from finite-difference derivatives of rescaled metric quantities

# We will need all BSSN gridfunctions to be defined, as well as
#     expressions for gammabarDD_dD in terms of exact derivatives of
#     the rescaling matrix and finite-difference derivatives of
#     hDD's. This functionality is provided by BSSN.BSSN_unrescaled_and_barred_vars,
#     which we call here to overwrite above definitions of gammabarDD,gammabarUU, etc.
Bq.gammabar__inverse_and_derivs() # Provides gammabarUU and GammabarUDD
gammabarUU    = Bq.gammabarUU
GammabarUDD   = Bq.GammabarUDD

# Next evaluate \bar{\Lambda}^i, based on GammabarUDD above and GammahatUDD
#       (from the reference metric):
LambdabarU = ixp.zerorank1()
for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            LambdabarU[i] += gammabarUU[j][k] * (GammabarUDD[i][j][k] - rfm.GammahatUDD[i][j][k])

# Finally apply rescaling:
# lambda^i = Lambdabar^i/\text{ReU[i]}
lambdaU = ixp.zerorank1()
for i in range(DIM):
    lambdaU[i] = LambdabarU[i] / rfm.ReU[i]

outCparams = "preindent=1,outCfileaccess=a,outCverbose=False,includebraces=False"
lambdaU_expressions = [lhrh(lhs=gri.gfaccess("in_gfs","lambdaU0"),rhs=lambdaU[0]),
                       lhrh(lhs=gri.gfaccess("in_gfs","lambdaU1"),rhs=lambdaU[1]),
                       lhrh(lhs=gri.gfaccess("in_gfs","lambdaU2"),rhs=lambdaU[2])]
lambdaU_expressions_FDout = fin.FD_outputC("returnstring",lambdaU_expressions, outCparams)

with open("BSSN/ID_BSSN_lambdas.h", "w") as file:
    file.write("""
void ID_BSSN_lambdas(const int Nxx[3],const int Nxx_plus_2NGHOSTS[3],REAL *xx[3],const REAL dxx[3],REAL *in_gfs) {\n""")
    file.write(lp.loop(["i2","i1","i0"],["NGHOSTS","NGHOSTS","NGHOSTS"],
                       ["NGHOSTS+Nxx[2]","NGHOSTS+Nxx[1]","NGHOSTS+Nxx[0]"],
                       ["1","1","1"],["const REAL invdx0 = 1.0/dxx[0];\n"+
                                      "const REAL invdx1 = 1.0/dxx[1];\n"+
                                      "const REAL invdx2 = 1.0/dxx[2];\n"+
                                      "#pragma omp parallel for",
                                      "    const REAL xx2 = xx[2][i2];",
                                      "        const REAL xx1 = xx[1][i1];"],"",
                                     "const REAL xx0 = xx[0][i0];\n"+lambdaU_expressions_FDout))
    file.write("}\n")
```

# Step 9: Code Validation against  BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear NRPy+ module \[Back to [top](#toc)\]
$$\label{code_validation}$$

Here, as a code validation check, we verify agreement in the C codes for converting "numerical" UIUCBlackHole initial data (in Spherical coordinates/basis) to BSSN Curvilinear data in Cylindrical coordinates/basis between
1. this tutorial and
2. the NRPy+ [BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear](../edit/BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py) module.

By default, we analyze these expressions in Cylindrical coordinates, though other coordinate systems may be chosen.

```python

!mv BSSN/ID_BSSN_lambdas.h BSSN/ID_BSSN_lambdas.h-ADM_Num_ID_validation
!mv BSSN/ID_BSSN__ALL_BUT_LAMBDAs.h BSSN/ID_BSSN__ALL_BUT_LAMBDAs.h-ADM_Num_ID_validation
!mv BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h-ADM_Num_ID_validation

# Reset the gridfunctions list;
#  in Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear()
#  below, BSSN_RHSs is called
#          tutorial. This line of code enables us to run
#          Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear()
#          without resetting the running Python kernel.
gri.glb_gridfcs_list = []

import BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear as AtoBnum
AtoBnum.Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear("Spherical",ADM_input_function_name)

print("\n\n ### BEGIN VALIDATION TESTS")
import filecmp
for file in ["BSSN/ID_BSSN_lambdas.h","BSSN/ID_BSSN__ALL_BUT_LAMBDAs.h",
             "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h","BSSN/"+ADM_input_function_name+".h"]:
    if filecmp.cmp(file,file+"-ADM_Num_ID_validation") == False:
        print("VALIDATION TEST FAILED ON file: "+file+".")
        exit(1)
    else:
        print("Validation test PASSED on file: "+file)
```

    Appended to file "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h"
    Appended to file "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h"

     ### BEGIN VALIDATION TESTS
    Validation test PASSED on file: BSSN/ID_BSSN_lambdas.h
    Validation test PASSED on file: BSSN/ID_BSSN__ALL_BUT_LAMBDAs.h
    Validation test PASSED on file: BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h
    Validation test PASSED on file: BSSN/ID_ADM_SphorCart.h

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

Once the following code finishes running, the generated PDF may be found at the following location within the directory you have the NRPy+ tutorial saved:
[Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.pdf](Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.pdf)

```python
!jupyter nbconvert --to latex --template latex_nrpy_style.tplx Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb
!pdflatex -interaction=batchmode Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.tex
!pdflatex -interaction=batchmode Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.tex
!pdflatex -interaction=batchmode Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.tex
!rm -f Tut*.out Tut*.aux Tut*.log
```

    [NbConvertApp] Converting notebook Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb to latex
    [NbConvertApp] Writing 86695 bytes to Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.tex
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode