# The BSSN Time-Evolution Equations

## Author: Zach Etienne
### Formatting improvements courtesy Brandon Clark

**All expressions generated in this module have been validated against a trusted code (the original NRPy+/SENR code, which itself was validated against [Baumgarte's code](https://arxiv.org/abs/1211.6632)).**

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

## Introduction:
This module documents and constructs the time evolution equations of the BSSN formulation of Einstein's equations, as defined  in [Ruchlin, Etienne, and Baumgarte (2018)](https://arxiv.org/abs/1712.07658) (see also [Baumgarte, Montero, Cordero-Carrión, and Müller (2012)](https://arxiv.org/abs/1211.6632)).

**This module is part of the following set of NRPy+ tutorial modules on the BSSN formulation of general relativity:**

* An overview of the BSSN formulation of Einstein's equations, as well as links for background reading/lectures, are provided in [the NRPy+ tutorial module on the BSSN formulation](Tutorial-BSSN_formulation.ipynb).
* Basic BSSN quantities are defined in the [BSSN quantities NRPy+ tutorial module](Tutorial-BSSN_quantities.ipynb).
* Other BSSN equation tutorial modules:
    * [Time-evolution equations the BSSN gauge quantities $\alpha$, $\beta^i$, and $B^i$](Tutorial-BSSN_time_evolution-BSSN_gauge_RHSs.ipynb).
    * [BSSN Hamiltonian and momentum constraints](Tutorial-BSSN_constraints.ipynb)
    * [Enforcing the $\bar{\gamma} = \hat{\gamma}$ constraint](Tutorial-BSSN-Enforcing_Determinant_gammabar_equals_gammahat_Constraint.ipynb)

### A Note on Notation

As is standard in NRPy+,

* Greek indices refer to four-dimensional quantities where the zeroth component indicates temporal (time) component.
* Latin indices refer to three-dimensional quantities. This is somewhat counterintuitive since Python always indexes its lists starting from 0. As a result, the zeroth component of three-dimensional quantities will necessarily indicate the first *spatial* direction.

As a corollary, any expressions involving mixed Greek and Latin indices will need to offset one set of indices by one: A Latin index in a four-vector will be incremented and a Greek index in a three-vector will be decremented (however, the latter case does not occur in this tutorial module).

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

The module is organized as follows

0. [Preliminaries](#bssntimeevolequations): BSSN time-evolution equations, as described in the [BSSN formulation NRPy+ tutorial module](Tutorial-BSSN_formulation.ipynb)
1. [Step 1](#initializenrpy): Initialize core Python/NRPy+ modules
1. [Step 2](#gammabar): Right-hand side of $\partial_t \bar{\gamma}_{ij}$
    1. [Step 2.a](#term1_partial_gamma): Term 1 of $\partial_t \bar{\gamma}_{i j}$
    1. [Step 2.b](#term2_partial_gamma): Term 2 of $\partial_t \bar{\gamma}_{i j}$
    1. [Step 2.c](#term3_partial_gamma): Term 3 of $\partial_t \bar{\gamma}_{i j}$
1. [Step 3](#abar): Right-hand side of $\partial_t \bar{A}_{ij}$
    1. [Step 3.a](#term1_partial_upper_a): Term 1 of $\partial_t \bar{A}_{i j}$
    1. [Step 3.c](#term2_partial_upper_a): Term 2 of $\partial_t \bar{A}_{i j}$
    1. [Step 3.c](#term3_partial_upper_a): Term 3 of $\partial_t \bar{A}_{i j}$
1. [Step 4](#cf): Right-hand side of $\partial_t \phi \to \partial_t (\text{cf})$
1. [Step 5](#trk): Right-hand side of $\partial_t \text{tr} K$
1. [Step 6](#lambdabar): Right-hand side of $\partial_t \bar{\Lambda}^i$
    1. [Step 6.a](#term1_partial_lambda): Term 1 of $\partial_t \bar{\Lambda}^i$
    1. [Step 6.b](#term2_partial_lambda): Term 2 of $\partial_t \bar{\Lambda}^i$
    1. [Step 6.c](#term3_partial_lambda): Term 3 of $\partial_t \bar{\Lambda}^i$
    1. [Step 6.d](#term4_partial_lambda): Term 4 of $\partial_t \bar{\Lambda}^i$
    1. [Step 6.e](#term5_partial_lambda): Term 5 of $\partial_t \bar{\Lambda}^i$
    1. [Step 6.f](#term6_partial_lambda): Term 6 of $\partial_t \bar{\Lambda}^i$
    1. [Step 6.g](#term7_partial_lambda): Term 7 of $\partial_t \bar{\Lambda}^i$
1. [Step 7](#rescalingrhss): Rescaling the BSSN right-hand sides; rewriting them in terms of the rescaled quantities $\left\{h_{i j},a_{i j},\text{cf}, K, \lambda^{i}, \alpha, \mathcal{V}^i, \mathcal{B}^i\right\}$
1. [Step 8](#code_validation): Code Validation against BSSN.BSSN_RHSs NRPy+ module
1. [Step 9](#latex_pdf_output): Output this module to $\LaTeX$-formatted PDF

# Preliminaries: BSSN time-evolution equations \[Back to [top](#toc)\]
$$\label{bssntimeevolequations}$$

As described in the [BSSN formulation NRPy+ tutorial module](Tutorial-BSSN_formulation.ipynb), the BSSN time-evolution equations are given by

\begin{align}
  \partial_t \bar{\gamma}_{i j} {} = {} & \left[\beta^k \partial_k \bar{\gamma}_{ij} + \partial_i \beta^k \bar{\gamma}_{kj} + \partial_j \beta^k \bar{\gamma}_{ik} \right] + \frac{2}{3} \bar{\gamma}_{i j} \left (\alpha \bar{A}_{k}^{k} - \bar{D}_{k} \beta^{k}\right ) - 2 \alpha \bar{A}_{i j} \; , \\
  \partial_t \bar{A}_{i j} {} = {} & \left[\beta^k \partial_k \bar{A}_{ij} + \partial_i \beta^k \bar{A}_{kj} + \partial_j \beta^k \bar{A}_{ik} \right] - \frac{2}{3} \bar{A}_{i j} \bar{D}_{k} \beta^{k} - 2 \alpha \bar{A}_{i k} {\bar{A}^{k}}_{j} + \alpha \bar{A}_{i j} K \nonumber \\
  & + e^{-4 \phi} \left \{-2 \alpha \bar{D}_{i} \bar{D}_{j} \phi + 4 \alpha \bar{D}_{i} \phi \bar{D}_{j} \phi  + 4 \bar{D}_{(i} \alpha \bar{D}_{j)} \phi - \bar{D}_{i} \bar{D}_{j} \alpha + \alpha \bar{R}_{i j} \right \}^{\text{TF}} \; , \\
  \partial_t \phi {} = {} & \left[\beta^k \partial_k \phi \right] + \frac{1}{6} \left (\bar{D}_{k} \beta^{k} - \alpha K \right ) \; , \\
  \partial_{t} K {} = {} & \left[\beta^k \partial_k K \right] + \frac{1}{3} \alpha K^{2} + \alpha \bar{A}_{i j} \bar{A}^{i j} - e^{-4 \phi} \left (\bar{D}_{i} \bar{D}^{i} \alpha + 2 \bar{D}^{i} \alpha \bar{D}_{i} \phi \right ) \; , \\
  \partial_t \bar{\Lambda}^{i} {} = {} & \left[\beta^k \partial_k \bar{\Lambda}^i - \partial_k \beta^i \bar{\Lambda}^k \right] + \bar{\gamma}^{j k} \hat{D}_{j} \hat{D}_{k} \beta^{i} + \frac{2}{3} \Delta^{i} \bar{D}_{j} \beta^{j} + \frac{1}{3} \bar{D}^{i} \bar{D}_{j} \beta^{j} \nonumber \\
  & - 2 \bar{A}^{i j} \left (\partial_{j} \alpha - 6 \partial_{j} \phi \right ) + 2 \alpha \bar{A}^{j k} \Delta_{j k}^{i}  -\frac{4}{3} \alpha \bar{\gamma}^{i j} \partial_{j} K
\end{align}

where the Lie derivative terms (often seen on the left-hand side of these equations) are enclosed in square braces.

Notice that the shift advection operator $\beta^k \partial_k \left\{\bar{\gamma}_{i j},\bar{A}_{i j},\phi, K, \bar{\Lambda}^{i}\right\}$ appears on the right-hand side of *every* expression. As the shift determines how the spatial coordinates $x^i$ move on the next 3D slice of our 4D manifold, we find that representing $\partial_k$ in these shift advection terms via an *upwinded* finite difference stencil results in far lower numerical errors. This trick is implemented below in all shift advection terms. Upwinded derivatives are indicated in NRPy+ by the "_dupD" variable suffix.

As discussed in the [NRPy+ tutorial module on BSSN quantities](Tutorial-BSSN_quantities.ipynb), tensorial expressions can diverge at coordinate singularities, so each tensor in the set of BSSN variables

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

is written in terms of the corresponding rescaled quantity in the set

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

respectively, as defined in the [BSSN quantities tutorial](Tutorial-BSSN_quantities.ipynb).

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

Let's start by importing all the needed modules from NRPy+:

```python
# Step 1.a: import all needed modules from Python/NRPy+:
import sympy as sp
import NRPy_param_funcs as par
import indexedexp as ixp
import grid as gri
import finite_difference as fin
import reference_metric as rfm

# Step 1.b: Set the coordinate system for the numerical grid
par.set_parval_from_str("reference_metric::CoordSystem","Spherical")

# Step 1.c: Given the chosen coordinate system, set up
#           corresponding reference metric and needed
#           reference metric quantities
# The following function call sets up the reference metric
#    and related quantities, including rescaling matrices ReDD,
#    ReU, and hatted quantities.
rfm.reference_metric()

# Step 1.d: Set spatial dimension (must be 3 for BSSN, as BSSN is
#           a 3+1-dimensional decomposition of the general
#           relativistic field equations)
DIM = 3

# Step 1.e: Import all basic (unrescaled) BSSN scalars & tensors
import BSSN.BSSN_quantities as Bq
Bq.BSSN_basic_tensors()
gammabarDD = Bq.gammabarDD
AbarDD     = Bq.AbarDD
LambdabarU = Bq.LambdabarU
trK   = Bq.trK
alpha = Bq.alpha
betaU = Bq.betaU

# Step 1.f: Import all neeeded rescaled BSSN tensors:
aDD = Bq.aDD
cf  = Bq.cf
lambdaU = Bq.lambdaU
```

    initialize_param() minor warning: Did nothing; already initialized parameter reference_metric::M_PI
    initialize_param() minor warning: Did nothing; already initialized parameter reference_metric::RMAX

# Step 2: Right-hand side of $\partial_t \bar{\gamma}_{ij}$ \[Back to [top](#toc)\]
$$\label{gammabar}$$

Let's start with

$$
\partial_t \bar{\gamma}_{i j} =
{\underbrace {\textstyle \left[\beta^k \partial_k \bar{\gamma}_{ij} + \partial_i \beta^k \bar{\gamma}_{kj} + \partial_j \beta^k \bar{\gamma}_{ik} \right]}_{\text{Term 1}}} +
{\underbrace {\textstyle \frac{2}{3} \bar{\gamma}_{i j} \left (\alpha \bar{A}_{k}^{k} - \bar{D}_{k} \beta^{k}\right )}_{\text{Term 2}}}
{\underbrace {\textstyle -2 \alpha \bar{A}_{i j}}_{\text{Term 3}}}.
$$

## Step 2.a: Term 1 of $\partial_t \bar{\gamma}_{i j}$  \[Back to [top](#toc)\]
$$\label{term1_partial_gamma}$$

Term 1 of $\partial_t \bar{\gamma}_{i j} =$ `gammabar_rhsDD[i][j]`: $\beta^k \bar{\gamma}_{ij,k} + \beta^k_{,i} \bar{\gamma}_{kj} + \beta^k_{,j} \bar{\gamma}_{ik}$

First we import derivative expressions for betaU defined in the [NRPy+ BSSN quantities tutorial module](Tutorial-BSSN_quantities.ipynb)

```python
# Step 2.a.i: Import derivative expressions for betaU defined in the BSSN.BSSN_quantities module:
Bq.betaU_derivs()
betaU_dD = Bq.betaU_dD
betaU_dupD = Bq.betaU_dupD
betaU_dDD = Bq.betaU_dDD
# Step 2.a.ii: Import derivative expression for gammabarDD
Bq.gammabar__inverse_and_derivs()
gammabarDD_dupD = Bq.gammabarDD_dupD

# Step 2.a.iii: First term of \partial_t \bar{\gamma}_{i j} right-hand side:
# \beta^k \bar{\gamma}_{ij,k} + \beta^k_{,i} \bar{\gamma}_{kj} + \beta^k_{,j} \bar{\gamma}_{ik}
gammabar_rhsDD = ixp.zerorank2()
for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            gammabar_rhsDD[i][j] += betaU[k]*gammabarDD_dupD[i][j][k] + betaU_dD[k][i]*gammabarDD[k][j] \
                                                                      + betaU_dD[k][j]*gammabarDD[i][k]
```

## Step 2.b: Term 2 of $\partial_t \bar{\gamma}_{i j}$ \[Back to [top](#toc)\]
$$\label{term2_partial_gamma}$$

Term 2 of $\partial_t \bar{\gamma}_{i j} =$ `gammabar_rhsDD[i][j]`: $\frac{2}{3} \bar{\gamma}_{i j} \left (\alpha \bar{A}_{k}^{k} - \bar{D}_{k} \beta^{k}\right )$

Let's first convert this expression to be in terms of the evolved variables $a_{ij}$ and $\mathcal{B}^i$, starting with $\bar{A}_{ij} = a_{ij} \text{ReDD[i][j]}$. Then $\bar{A}^k_{k} = \bar{\gamma}^{ij} \bar{A}_{ij}$, and we have already defined $\bar{\gamma}^{ij}$ in terms of the evolved quantity $h_{ij}$.

Next, we wish to compute

$$\bar{D}_{k} \beta^{k} = \beta^k_{,k} + \frac{\beta^k \bar{\gamma}_{,k}}{2 \bar{\gamma}},$$

where $\bar{\gamma}$ is the determinant of the conformal metric $\bar{\gamma}_{ij}$. ***Exercise to student: Prove the above relation.***
[Solution.](https://physics.stackexchange.com/questions/81453/general-relativity-christoffel-symbol-identity)

Usually (i.e., so long as we make the parameter choice `detgbarOverdetghat_equals_one = False` ) we will choose $\bar{\gamma}=\hat{\gamma}$, so $\bar{\gamma}$ will in general possess coordinate singularities. Thus we would prefer to rewrite derivatives of $\bar{\gamma}$ in terms of derivatives of $\bar{\gamma}/\hat{\gamma} = 1$.

```python
# Step 2.b.i: First import \bar{A}_{ij} = AbarDD[i][j], and its contraction trAbar = \bar{A}^k_k
#           from BSSN.BSSN_quantities
Bq.AbarUU_AbarUD_trAbar_AbarDD_dD()
trAbar = Bq.trAbar

# Step 2.b.ii: Import detgammabar quantities from BSSN.BSSN_quantities:
Bq.detgammabar_and_derivs()
detgammabar = Bq.detgammabar
detgammabar_dD = Bq.detgammabar_dD

# Step 2.b.ii: Compute the contraction \bar{D}_k \beta^k = \beta^k_{,k} + \frac{\beta^k \bar{\gamma}_{,k}}{2 \bar{\gamma}}
Dbarbetacontraction = sp.sympify(0)
for k in range(DIM):
    Dbarbetacontraction += betaU_dD[k][k] + betaU[k]*detgammabar_dD[k]/(2*detgammabar)

# Step 2.b.iii: Second term of \partial_t \bar{\gamma}_{i j} right-hand side:
# \frac{2}{3} \bar{\gamma}_{i j} \left (\alpha \bar{A}_{k}^{k} - \bar{D}_{k} \beta^{k}\right )
for i in range(DIM):
    for j in range(DIM):
        gammabar_rhsDD[i][j] += sp.Rational(2,3)*gammabarDD[i][j]*(alpha*trAbar - Dbarbetacontraction)
```

## Step 2.c: Term 3 of $\partial_t \bar{\gamma}_{i j}$ \[Back to [top](#toc)\]
$$\label{term3_partial_gamma}$$

Term 3 of $\partial_t \bar{\gamma}_{i j}$ = `gammabar_rhsDD[i][j]`: $-2 \alpha \bar{A}_{ij}$

```python
# Step 2.c: Third term of \partial_t \bar{\gamma}_{i j} right-hand side:
# -2 \alpha \bar{A}_{ij}
for i in range(DIM):
    for j in range(DIM):
        gammabar_rhsDD[i][j] += -2*alpha*AbarDD[i][j]
```

# Step 3: Right-hand side of $\partial_t \bar{A}_{ij}$ \[Back to [top](#toc)\]
$$\label{abar}$$

$$\partial_t \bar{A}_{i j} =
{\underbrace {\textstyle \left[\beta^k \partial_k \bar{A}_{ij} + \partial_i \beta^k \bar{A}_{kj} + \partial_j \beta^k \bar{A}_{ik} \right]}_{\text{Term 1}}}
{\underbrace {\textstyle - \frac{2}{3} \bar{A}_{i j} \bar{D}_{k} \beta^{k} - 2 \alpha \bar{A}_{i k} {\bar{A}^{k}}_{j} + \alpha \bar{A}_{i j} K}_{\text{Term 2}}} +
{\underbrace {\textstyle e^{-4 \phi} \left \{-2 \alpha \bar{D}_{i} \bar{D}_{j} \phi + 4 \alpha \bar{D}_{i} \phi \bar{D}_{j} \phi  + 4 \bar{D}_{(i} \alpha \bar{D}_{j)} \phi - \bar{D}_{i} \bar{D}_{j} \alpha + \alpha \bar{R}_{i j} \right \}^{\text{TF}}}_{\text{Term 3}}}$$

## Step 3.a: Term 1 of $\partial_t \bar{A}_{i j}$ \[Back to [top](#toc)\]
$$\label{term1_partial_upper_a}$$

Term 1 of $\partial_t \bar{A}_{i j}$ = `Abar_rhsDD[i][j]`: $\left[\beta^k \partial_k \bar{A}_{ij} + \partial_i \beta^k \bar{A}_{kj} + \partial_j \beta^k \bar{A}_{ik} \right]$

Notice the first subexpression has a $\beta^k \partial_k A_{ij}$ advection term, which will be upwinded.

```python
# Step 3.a: First term of \partial_t \bar{A}_{i j}:
# \beta^k \partial_k \bar{A}_{ij} + \partial_i \beta^k \bar{A}_{kj} + \partial_j \beta^k \bar{A}_{ik}

AbarDD_dupD = Bq.AbarDD_dupD # From Bq.AbarUU_AbarUD_trAbar_AbarDD_dD()

Abar_rhsDD = ixp.zerorank2()
for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            Abar_rhsDD[i][j] += betaU[k]*AbarDD_dupD[i][j][k] + betaU_dD[k][i]*AbarDD[k][j] \
                                                              + betaU_dD[k][j]*AbarDD[i][k]
```

## Step 3.b: Term 2 of $\partial_t \bar{A}_{i j}$ \[Back to [top](#toc)\]
$$\label{term2_partial_upper_a}$$

Term 2 of $\partial_t \bar{A}_{i j}$ = `Abar_rhsDD[i][j]`: $- \frac{2}{3} \bar{A}_{i j} \bar{D}_{k} \beta^{k} - 2 \alpha \bar{A}_{i k} \bar{A}^{k}_{j} + \alpha \bar{A}_{i j} K$

Note that $\bar{D}_{k} \beta^{k}$ was already defined as `Dbarbetacontraction`.

```python
# Step 3.b: Second term of \partial_t \bar{A}_{i j}:
# - (2/3) \bar{A}_{i j} \bar{D}_{k} \beta^{k} - 2 \alpha \bar{A}_{i k} {\bar{A}^{k}}_{j} + \alpha \bar{A}_{i j} K
gammabarUU = Bq.gammabarUU # From Bq.gammabar__inverse_and_derivs()
AbarUD     = Bq.AbarUD     # From Bq.AbarUU_AbarUD_trAbar()
for i in range(DIM):
    for j in range(DIM):
        Abar_rhsDD[i][j] += -sp.Rational(2,3)*AbarDD[i][j]*Dbarbetacontraction + alpha*AbarDD[i][j]*trK
        for k in range(DIM):
            Abar_rhsDD[i][j] += -2*alpha * AbarDD[i][k]*AbarUD[k][j]
```

## Step 3.c: Term 3 of $\partial_t \bar{A}_{i j}$ \[Back to [top](#toc)\]
$$\label{term3_partial_upper_a}$$

Term 3 of $\partial_t \bar{A}_{i j}$ = `Abar_rhsDD[i][j]`: $e^{-4 \phi} \left \{-2 \alpha \bar{D}_{i} \bar{D}_{j} \phi + 4 \alpha \bar{D}_{i} \phi \bar{D}_{j} \phi  + 4 \bar{D}_{(i} \alpha \bar{D}_{j)} \phi - \bar{D}_{i} \bar{D}_{j} \alpha + \alpha \bar{R}_{i j} \right \}^{\text{TF}}$
$$\label{step3p3}$$

The first covariant derivatives of $\phi$ and $\alpha$ are simply partial derivatives. However, $\phi$ is not a gridfunction; "cf" is. cf="W" (default value) denotes that the evolved variable is $W=e^{-2 \phi}$, which results in smoother spacetime fields around puncture black holes (desirable).

```python
# Step 3.c.i: Define partial derivatives of \phi in terms of evolved quantity "cf":
Bq.phi_and_derivs()
phi_dD   = Bq.phi_dD
phi_dupD = Bq.phi_dupD
phi_dDD  = Bq.phi_dDD
exp_m4phi  = Bq.exp_m4phi
phi_dBarD  = Bq.phi_dBarD  # phi_dBarD = Dbar_i phi = phi_dD (since phi is a scalar)
phi_dBarDD = Bq.phi_dBarDD # phi_dBarDD = Dbar_i Dbar_j phi (covariant derivative)

# Step 3.c.ii: Define RbarDD
Bq.RicciBar__gammabarDD_dHatD__DGammaUDD__DGammaU()
RbarDD = Bq.RbarDD

# Step 3.c.iii: Define first and second derivatives of \alpha, as well as
#         \bar{D}_i \bar{D}_j \alpha, which is defined just like phi
alpha_dD = ixp.declarerank1("alpha_dD")
alpha_dDD = ixp.declarerank2("alpha_dDD","sym01")
alpha_dBarD = alpha_dD
alpha_dBarDD = ixp.zerorank2()
GammabarUDD = Bq.GammabarUDD # Defined in Bq.gammabar__inverse_and_derivs()
for i in range(DIM):
    for j in range(DIM):
        alpha_dBarDD[i][j] = alpha_dDD[i][j]
        for k in range(DIM):
            alpha_dBarDD[i][j] += - GammabarUDD[k][i][j]*alpha_dD[k]

# Step 3.c.iv: Define the terms in curly braces:
curlybrackettermsDD = ixp.zerorank2()
for i in range(DIM):
    for j in range(DIM):
        curlybrackettermsDD[i][j] = -2*alpha*phi_dBarDD[i][j] + 4*alpha*phi_dBarD[i]*phi_dBarD[j] \
                                    +2*alpha_dBarD[i]*phi_dBarD[j] \
                                    +2*alpha_dBarD[j]*phi_dBarD[i] \
                                    -alpha_dBarDD[i][j] + alpha*RbarDD[i][j]

# Step 3.c.v: Compute the trace:
curlybracketterms_trace = sp.sympify(0)
for i in range(DIM):
    for j in range(DIM):
        curlybracketterms_trace += gammabarUU[i][j]*curlybrackettermsDD[i][j]

# Step 3.c.vi: Third and final term of Abar_rhsDD[i][j]:
for i in range(DIM):
    for j in range(DIM):
        Abar_rhsDD[i][j] += exp_m4phi*(curlybrackettermsDD[i][j] - \
                                       sp.Rational(1,3)*gammabarDD[i][j]*curlybracketterms_trace)
```

# Step 4: Right-hand side of $\partial_t \phi \to \partial_t (\text{cf})$ \[Back to [top](#toc)\]
$$\label{cf}$$

$$\partial_t \phi =
{\underbrace {\textstyle \left[\beta^k \partial_k \phi \right]}_{\text{Term 1}}} +
{\underbrace {\textstyle \frac{1}{6} \left (\bar{D}_{k} \beta^{k} - \alpha K \right)}_{\text{Term 2}}}$$

The right-hand side of $\partial_t \phi$ is trivial except for the fact that the actual evolved variable is "cf" (short for conformal factor), which could represent
* cf = $\phi$
* cf = $W = e^{-2 \phi}$ (default)
* cf = $\chi = e^{-4 \phi}$

Thus we are actually computing the right-hand side of the equation $\partial_t $cf, which is related to $\partial_t \phi$ via simple relations:
* cf = $\phi$: $\partial_t $cf$ = \partial_t \phi$ (unchanged)
* cf = $W$: $\partial_t $cf$ = \partial_t (e^{-2 \phi}) = -2 e^{-2\phi}\partial_t \phi = -2 W \partial_t \phi$. Thus we need to multiply the right-hand side by $-2 W = -2$cf when cf = $W$.
* cf = $\chi$: Same argument as for $W$, except the right-hand side must be multiplied by $-4 \chi=-4$cf.

```python
# Step 4: Right-hand side of conformal factor variable "cf". Supported
#          options include: cf=phi, cf=W=e^(-2*phi) (default), and cf=chi=e^(-4*phi)
# \partial_t phi = \left[\beta^k \partial_k \phi \right] <- TERM 1
#                  + \frac{1}{6} \left (\bar{D}_{k} \beta^{k} - \alpha K \right ) <- TERM 2

cf_rhs = sp.Rational(1,6) * (Dbarbetacontraction - alpha*trK) # Term 2
for k in range(DIM):
    cf_rhs += betaU[k]*phi_dupD[k] # Term 1

# Next multiply to convert phi_rhs to cf_rhs.
if par.parval_from_str("BSSN.BSSN_quantities::EvolvedConformalFactor_cf") == "phi":
    pass # do nothing; cf_rhs = phi_rhs
elif par.parval_from_str("BSSN.BSSN_quantities::EvolvedConformalFactor_cf") == "W":
    cf_rhs *= -2*cf # cf_rhs = -2*cf*phi_rhs
elif par.parval_from_str("BSSN.BSSN_quantities::EvolvedConformalFactor_cf") == "chi":
    cf_rhs *= -4*cf # cf_rhs = -4*cf*phi_rhs
else:
    print("Error: EvolvedConformalFactor_cf == "+
          par.parval_from_str("BSSN.BSSN_quantities::EvolvedConformalFactor_cf")+" unsupported!")
    exit(1)
```

# Step 5: Right-hand side of $\partial_t K$ \[Back to [top](#toc)\]
$$\label{trk}$$

$$
\partial_{t} K =
{\underbrace {\textstyle \left[\beta^i \partial_i K \right]}_{\text{Term 1}}} +
{\underbrace {\textstyle \frac{1}{3} \alpha K^{2}}_{\text{Term 2}}} +
{\underbrace {\textstyle \alpha \bar{A}_{i j} \bar{A}^{i j}}_{\text{Term 3}}}
{\underbrace {\textstyle - e^{-4 \phi} \left (\bar{D}_{i} \bar{D}^{i} \alpha + 2 \bar{D}^{i} \alpha \bar{D}_{i} \phi \right )}_{\text{Term 4}}}
$$

```python
# Step 5: right-hand side of trK (trace of extrinsic curvature):
# \partial_t K = \beta^k \partial_k K <- TERM 1
#           + \frac{1}{3} \alpha K^{2} <- TERM 2
#           + \alpha \bar{A}_{i j} \bar{A}^{i j} <- TERM 3
#           - - e^{-4 \phi} (\bar{D}_{i} \bar{D}^{i} \alpha + 2 \bar{D}^{i} \alpha \bar{D}_{i} \phi ) <- TERM 4
# TERM 2:
trK_rhs = sp.Rational(1,3)*alpha*trK*trK
trK_dupD = ixp.declarerank1("trK_dupD")
for i in range(DIM):
    # TERM 1:
    trK_rhs += betaU[i]*trK_dupD[i]
for i in range(DIM):
    for j in range(DIM):
        # TERM 4:
        trK_rhs += -exp_m4phi*gammabarUU[i][j]*(alpha_dBarDD[i][j] + 2*alpha_dBarD[j]*phi_dBarD[i])
AbarUU = Bq.AbarUU # From Bq.AbarUU_AbarUD_trAbar()
for i in range(DIM):
    for j in range(DIM):
        # TERM 3:
        trK_rhs += alpha*AbarDD[i][j]*AbarUU[i][j]
```

# Step 6: Right-hand side of $\partial_t \bar{\Lambda}^{i}$ \[Back to [top](#toc)\]
$$\label{lambdabar}$$

\begin{align}
\partial_t \bar{\Lambda}^{i} &=
{\underbrace {\textstyle \left[\beta^k \partial_k \bar{\Lambda}^i - \partial_k \beta^i \bar{\Lambda}^k \right]}_{\text{Term 1}}} +
{\underbrace {\textstyle \bar{\gamma}^{j k} \hat{D}_{j} \hat{D}_{k} \beta^{i}}_{\text{Term 2}}} +
{\underbrace {\textstyle \frac{2}{3} \Delta^{i} \bar{D}_{j} \beta^{j}}_{\text{Term 3}}} +
{\underbrace {\textstyle \frac{1}{3} \bar{D}^{i} \bar{D}_{j} \beta^{j}}_{\text{Term 4}}} \nonumber \\
  &
{\underbrace {\textstyle - 2 \bar{A}^{i j} \left (\partial_{j} \alpha - 6 \alpha \partial_{j} \phi \right )}_{\text{Term 5}}} +
{\underbrace {\textstyle 2 \alpha \bar{A}^{j k} \Delta_{j k}^{i}}_{\text{Term 6}}}
{\underbrace {\textstyle -\frac{4}{3} \alpha \bar{\gamma}^{i j} \partial_{j} K}_{\text{Term 7}}}
\end{align}

## Step 6.a: Term 1 of  $\partial_t \bar{\Lambda}^{i}$ \[Back to [top](#toc)\]
$$\label{term1_partial_lambda}$$

Term 1 of  $\partial_t \bar{\Lambda}^{i}$: $\beta^k \partial_k \bar{\Lambda}^i - \partial_k \beta^i \bar{\Lambda}^k$

Computing this term requires that we define $\bar{\Lambda}^i$ and $\bar{\Lambda}^i_{,j}$ in terms of the rescaled (i.e., actual evolved) variable $\lambda^i$ and derivatives:
\begin{align}
\bar{\Lambda}^i &= \lambda^i \text{ReU[i]} \\
\bar{\Lambda}^i_{,\ j} &= \lambda^i_{,\ j} \text{ReU[i]} + \lambda^i \text{ReUdD[i][j]}
\end{align}

```python
# Step 6: right-hand side of \partial_t \bar{\Lambda}^i:
# \partial_t \bar{\Lambda}^i = \beta^k \partial_k \bar{\Lambda}^i - \partial_k \beta^i \bar{\Lambda}^k <- TERM 1
#                            + \bar{\gamma}^{j k} \hat{D}_{j} \hat{D}_{k} \beta^{i} <- TERM 2
#                            + \frac{2}{3} \Delta^{i} \bar{D}_{j} \beta^{j} <- TERM 3
#                            + \frac{1}{3} \bar{D}^{i} \bar{D}_{j} \beta^{j} <- TERM 4
#                            - 2 \bar{A}^{i j} (\partial_{j} \alpha - 6 \partial_{j} \phi) <- TERM 5
#                            + 2 \alpha \bar{A}^{j k} \Delta_{j k}^{i} <- TERM 6
#                            - \frac{4}{3} \alpha \bar{\gamma}^{i j} \partial_{j} K <- TERM 7

# Step 6.a: Term 1 of \partial_t \bar{\Lambda}^i: \beta^k \partial_k \bar{\Lambda}^i - \partial_k \beta^i \bar{\Lambda}^k
# First we declare \bar{\Lambda}^i and \bar{\Lambda}^i_{,j} in terms of \lambda^i and \lambda^i_{,j}
LambdabarU_dupD = ixp.zerorank2()
lambdaU_dupD = ixp.declarerank2("lambdaU_dupD","nosym")
for i in range(DIM):
    for j in range(DIM):
        LambdabarU_dupD[i][j] = lambdaU_dupD[i][j]*rfm.ReU[i] + lambdaU[i]*rfm.ReUdD[i][j]

Lambdabar_rhsU = ixp.zerorank1()
for i in range(DIM):
    for k in range(DIM):
        Lambdabar_rhsU[i] += betaU[k]*LambdabarU_dupD[i][k] - betaU_dD[i][k]*LambdabarU[k] # Term 1
```

## Step 6.b: Term 2 of $\partial_t \bar{\Lambda}^{i}$ \[Back to [top](#toc)\]
$$\label{term2_partial_lambda}$$

Term 2 of $\partial_t \bar{\Lambda}^{i}$: $\bar{\gamma}^{j k} \hat{D}_{j} \hat{D}_{k} \beta^{i}$

This is a relatively difficult term to compute, as it requires we evaluate the second covariant derivative of the shift vector, with respect to the hatted (i.e., reference) metric.

Based on the definition of covariant derivative, we have
$$
\hat{D}_{k} \beta^{i} = \beta^i_{,k} + \hat{\Gamma}^i_{mk} \beta^m
$$

Since $\hat{D}_{k} \beta^{i}$ is a tensor, the covariant derivative of this will have the same indexing as a tensor $T_k^i$:

$$
\hat{D}_{j} T^i_k = T^i_{k,j} + \hat{\Gamma}^i_{dj} T^d_k - \hat{\Gamma}^d_{kj} T^i_d.
$$

Therefore,
\begin{align}
\hat{D}_{j} \left(\hat{D}_{k} \beta^{i}\right) &= \left(\beta^i_{,k} + \hat{\Gamma}^i_{mk} \beta^m\right)_{,j} + \hat{\Gamma}^i_{dj} \left(\beta^d_{,k} + \hat{\Gamma}^d_{mk} \beta^m\right) - \hat{\Gamma}^d_{kj} \left(\beta^i_{,d} + \hat{\Gamma}^i_{md} \beta^m\right) \\
&= \beta^i_{,kj} + \hat{\Gamma}^i_{mk,j} \beta^m + \hat{\Gamma}^i_{mk} \beta^m_{,j} + \hat{\Gamma}^i_{dj}\beta^d_{,k} + \hat{\Gamma}^i_{dj}\hat{\Gamma}^d_{mk} \beta^m - \hat{\Gamma}^d_{kj} \beta^i_{,d} - \hat{\Gamma}^d_{kj} \hat{\Gamma}^i_{md} \beta^m \\
&= {\underbrace {\textstyle \beta^i_{,kj}}_{\text{Term 2a}}}
{\underbrace {\textstyle \hat{\Gamma}^i_{mk,j} \beta^m + \hat{\Gamma}^i_{mk} \beta^m_{,j} + \hat{\Gamma}^i_{dj}\beta^d_{,k} - \hat{\Gamma}^d_{kj} \beta^i_{,d}}_{\text{Term 2b}}} +
{\underbrace {\textstyle \hat{\Gamma}^i_{dj}\hat{\Gamma}^d_{mk} \beta^m - \hat{\Gamma}^d_{kj} \hat{\Gamma}^i_{md} \beta^m}_{\text{Term 2c}}},
\end{align}

where
$$
\text{Term 2} = \bar{\gamma}^{jk} \left(\text{Term 2a} + \text{Term 2b} + \text{Term 2c}\right)
$$

```python
# Step 6.b: Term 2 of \partial_t \bar{\Lambda}^i = \bar{\gamma}^{jk} (Term 2a + Term 2b + Term 2c)
# Term 2a: \bar{\gamma}^{jk} \beta^i_{,kj}
Term2aUDD = ixp.zerorank3()
for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            Term2aUDD[i][j][k] += betaU_dDD[i][k][j]
# Term 2b: \hat{\Gamma}^i_{mk,j} \beta^m + \hat{\Gamma}^i_{mk} \beta^m_{,j}
#          + \hat{\Gamma}^i_{dj}\beta^d_{,k} - \hat{\Gamma}^d_{kj} \beta^i_{,d}
Term2bUDD = ixp.zerorank3()
for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            for m in range(DIM):
                Term2bUDD[i][j][k] += rfm.GammahatUDDdD[i][m][k][j]*betaU[m]    \
                                      + rfm.GammahatUDD[i][m][k]*betaU_dD[m][j] \
                                      + rfm.GammahatUDD[i][m][j]*betaU_dD[m][k] \
                                      - rfm.GammahatUDD[m][k][j]*betaU_dD[i][m]
# Term 2c: \hat{\Gamma}^i_{dj}\hat{\Gamma}^d_{mk} \beta^m - \hat{\Gamma}^d_{kj} \hat{\Gamma}^i_{md} \beta^m
Term2cUDD = ixp.zerorank3()
for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            for m in range(DIM):
                for d in range(DIM):
                    Term2cUDD[i][j][k] += ( rfm.GammahatUDD[i][d][j]*rfm.GammahatUDD[d][m][k] \
                                           -rfm.GammahatUDD[d][k][j]*rfm.GammahatUDD[i][m][d])*betaU[m]

Lambdabar_rhsUpieceU = ixp.zerorank1()

# Put it all together to get Term 2:
for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            Lambdabar_rhsU[i] += gammabarUU[j][k] * (Term2aUDD[i][j][k] + Term2bUDD[i][j][k] + Term2cUDD[i][j][k])
            Lambdabar_rhsUpieceU[i] += gammabarUU[j][k] * (Term2aUDD[i][j][k] + Term2bUDD[i][j][k] + Term2cUDD[i][j][k])
```

## Step 6.c: Term 3 of  $\partial_t \bar{\Lambda}^{i}$: $\frac{2}{3} \Delta^{i} \bar{D}_{j} \beta^{j}$ \[Back to [top](#toc)\]
$$\label{term3_partial_lambda}$$

Term 3 of $\partial_t \bar{\Lambda}^{i}$: $\frac{2}{3} \Delta^{i} \bar{D}_{j} \beta^{j}$

This term is the simplest to implement, as $\bar{D}_{j} \beta^{j}$ and $\Delta^i$ have already been defined, as `Dbarbetacontraction` and `DGammaU[i]`, respectively:

```python
# Step 6.c: Term 3 of \partial_t \bar{\Lambda}^i:
#    \frac{2}{3} \Delta^{i} \bar{D}_{j} \beta^{j}
DGammaU = Bq.DGammaU # From Bq.RicciBar__gammabarDD_dHatD__DGammaUDD__DGammaU()
for i in range(DIM):
    Lambdabar_rhsU[i] += sp.Rational(2,3)*DGammaU[i]*Dbarbetacontraction # Term 3
```

## Step 6.d: Term 4 of  $\partial_t \bar{\Lambda}^{i}$ \[Back to [top](#toc)\]
$$\label{term4_partial_lambda}$$

$\partial_t \bar{\Lambda}^{i}$: $\frac{1}{3} \bar{D}^{i} \bar{D}_{j} \beta^{j}$

Recall first that

$$\bar{D}_{k} \beta^{k} = \beta^k_{,\ k} + \frac{\beta^k \bar{\gamma}_{,k}}{2 \bar{\gamma}},$$
which is a scalar, so

\begin{align}
\bar{D}_m \bar{D}_{j} \beta^{j} &= \left(\beta^k_{,\ k} + \frac{\beta^k \bar{\gamma}_{,k}}{2 \bar{\gamma}}\right)_{,m} \\
&= \beta^k_{\ ,km} + \frac{\beta^k_{\ ,m} \bar{\gamma}_{,k} + \beta^k \bar{\gamma}_{\ ,km}}{2 \bar{\gamma}} - \frac{\beta^k \bar{\gamma}_{,k} \bar{\gamma}_{,m}}{2 \bar{\gamma}^2}
\end{align}

Thus,
\begin{align}
\bar{D}^i \bar{D}_{j} \beta^{j}
&= \bar{\gamma}^{im} \bar{D}_m \bar{D}_{j} \beta^{j} \\
&= \bar{\gamma}^{im} \left(\beta^k_{\ ,km} + \frac{\beta^k_{\ ,m} \bar{\gamma}_{,k} + \beta^k \bar{\gamma}_{\ ,km}}{2 \bar{\gamma}} - \frac{\beta^k \bar{\gamma}_{,k} \bar{\gamma}_{,m}}{2 \bar{\gamma}^2} \right)
\end{align}

```python
# Step 6.d: Term 4 of \partial_t \bar{\Lambda}^i:
#           \frac{1}{3} \bar{D}^{i} \bar{D}_{j} \beta^{j}
detgammabar_dDD = Bq.detgammabar_dDD # From Bq.detgammabar_and_derivs()
Dbarbetacontraction_dBarD = ixp.zerorank1()
for k in range(DIM):
    for m in range(DIM):
        Dbarbetacontraction_dBarD[m] += betaU_dDD[k][k][m] + \
                                        (betaU_dD[k][m]*detgammabar_dD[k] +
                                         betaU[k]*detgammabar_dDD[k][m])/(2*detgammabar) \
                                        -betaU[k]*detgammabar_dD[k]*detgammabar_dD[m]/(2*detgammabar*detgammabar)
for i in range(DIM):
    for m in range(DIM):
        Lambdabar_rhsU[i] += sp.Rational(1,3)*gammabarUU[i][m]*Dbarbetacontraction_dBarD[m]
```

## Step 6.e: Term 5 of  $\partial_t \bar{\Lambda}^{i}$ \[Back to [top](#toc)\]
$$\label{term5_partial_lambda}$$

Term 5 of  $\partial_t \bar{\Lambda}^{i}$: $- 2 \bar{A}^{i j} \left (\partial_{j} \alpha - 6\alpha \partial_{j} \phi\right)$

```python
# Step 6.e: Term 5 of \partial_t \bar{\Lambda}^i:
#           - 2 \bar{A}^{i j} (\partial_{j} \alpha - 6 \alpha \partial_{j} \phi)
for i in range(DIM):
    for j in range(DIM):
        Lambdabar_rhsU[i] += -2*AbarUU[i][j]*(alpha_dD[j] - 6*alpha*phi_dD[j])
```

## Step 6.f: Term 6 of $\partial_t \bar{\Lambda}^{i}$ \[Back to [top](#toc)\]
$$\label{term6_partial_lambda}$$

Term 6 of $\partial_t \bar{\Lambda}^{i}$: $2\alpha \bar{A}^{j k} \Delta_{j k}^{i}$

```python
# Step 6.f: Term 6 of \partial_t \bar{\Lambda}^i:
#           2 \alpha \bar{A}^{j k} \Delta^{i}_{j k}
DGammaUDD = Bq.DGammaUDD # From RicciBar__gammabarDD_dHatD__DGammaUDD__DGammaU()
for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            Lambdabar_rhsU[i] += 2*alpha*AbarUU[j][k]*DGammaUDD[i][j][k]
```

## Step 6.g: Term 7 of $\partial_t \bar{\Lambda}^{i}$ \[Back to [top](#toc)\]
$$\label{term7_partial_lambda}$$

$\partial_t \bar{\Lambda}^{i}$: $-\frac{4}{3} \alpha \bar{\gamma}^{i j} \partial_{j} K$

```python
# Step 6.g: Term 7 of \partial_t \bar{\Lambda}^i:
#           -\frac{4}{3} \alpha \bar{\gamma}^{i j} \partial_{j} K
trK_dD = ixp.declarerank1("trK_dD")
for i in range(DIM):
    for j in range(DIM):
        Lambdabar_rhsU[i] += -sp.Rational(4,3)*alpha*gammabarUU[i][j]*trK_dD[j]
```

# Step 7: Rescaling the BSSN right-hand sides; rewriting them in terms of the rescaled quantities $\left\{h_{i j},a_{i j},\text{cf}, K, \lambda^{i}, \alpha, \mathcal{V}^i, \mathcal{B}^i\right\}$ \[Back to [top](#toc)\]
$$\label{rescalingrhss}$$

Next we rescale the right-hand sides of the BSSN equations so that the evolved variables are $\left\{h_{i j},a_{i j},\text{cf}, K, \lambda^{i}\right\}$

```python
# Step 7: Rescale the RHS quantities so that the evolved
#          variables are smooth across coord singularities
h_rhsDD     = ixp.zerorank2()
a_rhsDD     = ixp.zerorank2()
lambda_rhsU = ixp.zerorank1()
for i in range(DIM):
    lambda_rhsU[i] = Lambdabar_rhsU[i] / rfm.ReU[i]
    for j in range(DIM):
        h_rhsDD[i][j] = gammabar_rhsDD[i][j] / rfm.ReDD[i][j]
        a_rhsDD[i][j] =     Abar_rhsDD[i][j] / rfm.ReDD[i][j]
#print(str(Abar_rhsDD[2][2]).replace("**","^").replace("_","").replace("xx","x").replace("sin(x2)","Sin[x2]").replace("sin(2*x2)","Sin[2*x2]").replace("cos(x2)","Cos[x2]").replace("detgbaroverdetghat","detg"))
#print(str(Dbarbetacontraction).replace("**","^").replace("_","").replace("xx","x").replace("sin(x2)","Sin[x2]").replace("detgbaroverdetghat","detg"))
#print(betaU_dD)
#print(str(trK_rhs).replace("xx2","xx3").replace("xx1","xx2").replace("xx0","xx1").replace("**","^").replace("_","").replace("sin(xx2)","Sinx2").replace("xx","x").replace("sin(2*x2)","Sin2x2").replace("cos(x2)","Cosx2").replace("detgbaroverdetghat","detg"))
#print(str(bet_rhsU[0]).replace("xx2","xx3").replace("xx1","xx2").replace("xx0","xx1").replace("**","^").replace("_","").replace("sin(xx2)","Sinx2").replace("xx","x").replace("sin(2*x2)","Sin2x2").replace("cos(x2)","Cosx2").replace("detgbaroverdetghat","detg"))
```

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

Here, as a code validation check, we verify agreement in the SymPy expressions for the RHSs of the BSSN equations between
1. this tutorial and
2. the NRPy+ [BSSN.BSSN_RHSs](../edit/BSSN/BSSN_RHSs.py) module.

By default, we analyze the RHSs in Spherical coordinates, though other coordinate systems may be chosen.

```python
# Step 8: We already have SymPy expressions for BSSN RHS expressions
#         in terms of other SymPy variables. Even if we reset the
#         list of NRPy+ gridfunctions, these *SymPy* expressions for
#         BSSN RHS variables *will remain unaffected*.
#
#         Here, we will use the above-defined BSSN RHS expressions
#         to validate against the same expressions in the
#         BSSN/BSSN_RHSs.py file, to ensure consistency between
#         this tutorial and the module itself.
#
# Reset the list of gridfunctions, as registering a gridfunction
#   twice will spawn an error.
gri.glb_gridfcs_list = []

# Step 9.a: Call the BSSN_RHSs() function from within the
#           BSSN/BSSN_RHSs.py module,
#           which should do exactly the same as in Steps 1-16 above.
print("vvv Ignore the minor warnings below. vvv")

# import BSSN.BSSN_RHSs as bssnrhs
# bssnrhs.BSSN_RHSs()
import BSSN.BSSN_RHSs as bssnrhs
bssnrhs.BSSN_RHSs()

print("^^^ Ignore the minor warnings above. ^^^\n")

print("Consistency check between BSSN_RHSs tutorial and NRPy+ module: ALL SHOULD BE ZERO.")

print("trK_rhs - bssnrhs.trK_rhs = " + str(trK_rhs - bssnrhs.trK_rhs))
print("cf_rhs - bssnrhs.cf_rhs = " + str(cf_rhs - bssnrhs.cf_rhs))

for i in range(DIM):
    print("lambda_rhsU["+str(i)+"] - bssnrhs.lambda_rhsU["+str(i)+"] = " +
          str(lambda_rhsU[i] - bssnrhs.lambda_rhsU[i]))
    for j in range(DIM):
        print("h_rhsDD["+str(i)+"]["+str(j)+"] - bssnrhs.h_rhsDD["+str(i)+"]["+str(j)+"] = "
              + str(h_rhsDD[i][j] - bssnrhs.h_rhsDD[i][j]))
        print("a_rhsDD["+str(i)+"]["+str(j)+"] - bssnrhs.a_rhsDD["+str(i)+"]["+str(j)+"] = "
              + str(a_rhsDD[i][j] - bssnrhs.a_rhsDD[i][j]))
```

    vvv Ignore the minor warnings below. vvv
    initialize_param() minor warning: Did nothing; already initialized parameter reference_metric::M_PI
    initialize_param() minor warning: Did nothing; already initialized parameter reference_metric::RMAX
    ^^^ Ignore the minor warnings above. ^^^

    Consistency check between BSSN_RHSs tutorial and NRPy+ module: ALL SHOULD BE ZERO.
    trK_rhs - bssnrhs.trK_rhs = 0
    cf_rhs - bssnrhs.cf_rhs = 0
    lambda_rhsU[0] - bssnrhs.lambda_rhsU[0] = 0
    h_rhsDD[0][0] - bssnrhs.h_rhsDD[0][0] = 0
    a_rhsDD[0][0] - bssnrhs.a_rhsDD[0][0] = 0
    h_rhsDD[0][1] - bssnrhs.h_rhsDD[0][1] = 0
    a_rhsDD[0][1] - bssnrhs.a_rhsDD[0][1] = 0
    h_rhsDD[0][2] - bssnrhs.h_rhsDD[0][2] = 0
    a_rhsDD[0][2] - bssnrhs.a_rhsDD[0][2] = 0
    lambda_rhsU[1] - bssnrhs.lambda_rhsU[1] = 0
    h_rhsDD[1][0] - bssnrhs.h_rhsDD[1][0] = 0
    a_rhsDD[1][0] - bssnrhs.a_rhsDD[1][0] = 0
    h_rhsDD[1][1] - bssnrhs.h_rhsDD[1][1] = 0
    a_rhsDD[1][1] - bssnrhs.a_rhsDD[1][1] = 0
    h_rhsDD[1][2] - bssnrhs.h_rhsDD[1][2] = 0
    a_rhsDD[1][2] - bssnrhs.a_rhsDD[1][2] = 0
    lambda_rhsU[2] - bssnrhs.lambda_rhsU[2] = 0
    h_rhsDD[2][0] - bssnrhs.h_rhsDD[2][0] = 0
    a_rhsDD[2][0] - bssnrhs.a_rhsDD[2][0] = 0
    h_rhsDD[2][1] - bssnrhs.h_rhsDD[2][1] = 0
    a_rhsDD[2][1] - bssnrhs.a_rhsDD[2][1] = 0
    h_rhsDD[2][2] - bssnrhs.h_rhsDD[2][2] = 0
    a_rhsDD[2][2] - bssnrhs.a_rhsDD[2][2] = 0

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

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

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

    [NbConvertApp] Converting notebook Tutorial-BSSN_time_evolution-BSSN_RHSs.ipynb to latex
    [NbConvertApp] Writing 103836 bytes to Tutorial-BSSN_time_evolution-BSSN_RHSs.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


```python
# ORIGINAL RGB FILE
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import random
import pystan

from IPython.display import display

from matplotlib import rc
rc("font", family="serif", size=11)
```

First we will read in a file that contains the overall list of all the available datasets, with corresponding star IDs, $\nu_{max}$ and $\sigma_{\nu_{max}}$ values. From this we will take a random selection for our analysis.

```python
#random.seed(121)
#output = pd.read_csv('../data/output_1000stars.csv', usecols=range(1,4))
output = pd.read_csv('../data/output_back_filesremoved.csv', usecols=['kic','numax', 'numax_err'])
output = output.loc[(np.abs(output['numax'] - 75) < 30.0)].reset_index(drop=True)
# Select a random number of stars from the file, recording their IDs
IDs = [random.choice(output['kic']) for i in range(6)]
IDs = [10802837,12003742,2283075,4760954,11358669,11521197]
# Create a list of the numax values corresponding to each ID
Numax = [(output.loc[(output['kic'] == IDs[i])]).iloc[0]['numax'] for i in range(len(IDs))]
Numax_err = [(output.loc[(output['kic'] == IDs[i])]).iloc[0]['numax_err'] for i in range(len(IDs))]
```

Using our list of selected stars, we can then pull the respective files containing the induvidual mode frequencies and errors. The different files will contain varying numbers of modes; in order to successfully parse the data through Stan, one must ensure that all the arrays are of the same length. To do this, for every dataset that contains less modes than the most complete dataset, we can produce additional modes based on the average frequency spacing $\left<\Delta\nu\right>$ for each star, ensuring to assign these points with a large uncertainty so that Stan does not use this as restrictive points during the fit (they are essentially ignored).

```python
modesID = [pd.read_csv('../data/rgbmodes/modes_'+str(IDs[i])+'.csv', usecols=['f0', 'f0_err', 'A0'])
           for i in np.arange(0,len(IDs),1)]
lenmodes = [len(modesID[i]) for i in range(len(IDs))]
maxmodes = max(lenmodes)
arr_n = np.zeros([len(IDs),maxmodes])
arr_freq = np.zeros([len(IDs),maxmodes])
arr_freqerr = np.zeros([len(IDs),maxmodes])

dnu_avgID = []
for i in np.arange(0,len(IDs),1):
    modesID[i] = modesID[i].sort_values(by=['f0'])
    modesID[i] = modesID[i].set_index(np.arange(0,len(modesID[i]),1))
    modesID[i]['dnu'] = (modesID[i]['f0'].diff(2).shift(-1))/2
    dnu_avg = (np.mean(modesID[i]['dnu']))
    dnu_avgID.append(dnu_avg)

    n_min = int(modesID[i]['f0'].min() / dnu_avg)
    n = np.arange(n_min, n_min+len(modesID[i]), 1)
    modesID[i].insert(loc=0, column='n', value=n)

    # Loop to ensure all arrays are the same length
    if lenmodes[i] < maxmodes:
        l = lenmodes[i]
        while l < maxmodes:
            newrow = {'n': int(np.max(modesID[i]['n'])+1),
                      'f0': np.max(modesID[i]['f0'])+dnu_avgID[i],
                      'f0_err': 100000}
            modesID[i] = modesID[i].append(newrow, ignore_index=True)
            l += 1

    arr_n[i,:] = modesID[i]['n']
    arr_freq[i,:] = modesID[i]['f0']
    arr_freqerr[i,:] = modesID[i]['f0_err']

    #display(modesID[i])

dnu_avgarr = np.asarray(dnu_avgID)

#Define start parameters
eps = []
for i in range(len(IDs)):
    epsilon1 = np.median((modesID[i].f0 % dnu_avg) / dnu_avg)
    eps.append(epsilon1)
epsilon = np.asarray(eps)

numax_obs = Numax
nmax = numax_obs/dnu_avgarr - epsilon
alpha = 0.015*dnu_avgarr**(-0.32)
A = 0.06*dnu_avgarr**(-0.88)
G = np.ones([len(IDs)])*3.08
tau = np.ones([len(IDs)])*200
phi = np.ones([len(IDs)])*1.71
```

The data for the different stars can be viewed on an Échelle Diagram.

```python
plt.figure(1)
for i in np.arange(0,len(IDs),1):
    plt.scatter(modesID[i]['f0'] % dnu_avgID[i], modesID[i]['f0'], label=r'ID = '+str(IDs[i]))

plt.ylabel('Frequency')
plt.xlabel(r'Mod. Freq. Spacing ('+ str(u"%.3f" % dnu_avg) +') $\mu$Hz')
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.5)
#plt.savefig('seminar/manyrgbs.pdf')

plt.figure(2)
for i in np.arange(0,len(IDs),1):
    plt.scatter(modesID[i]['n'], modesID[i]['f0'], label=r'ID = '+str(IDs[i]))
plt.xlabel('Radial Order ($n$)')
plt.ylabel('Frequency')
plt.show()
```

With all our required data, we can parse the data into a stan model constructed to perform a hierarchical Bayesian model. Hyperpriors have been introduced on a number of parameters based on values in the Vrard (2014) paper.

The model follows the form:
\begin{equation}
    \left(n + \epsilon + \frac{\alpha}{2}\left(n-n_{max}\right)^2 + \frac{\mathcal{AG}}{2\pi}\sin\left(\frac{2\pi(n-n_{max})}{\mathcal{G}} + \phi \right)\right) e^{\frac{n}{\tau}}
\end{equation}
where
\begin{equation}
    n_{max} = \frac{\nu_{max}}{\Delta\nu} - \epsilon
\end{equation}

$$ A = A_0 e^{-n_{max}/\tau} \longrightarrow A_0 = A_{n_{max}} e^{n_{max}/\tau}$$

```python
code = '''
functions {
    real glitch(real n, real dnu, real numax, real epsilon, real alpha, real A, real G, real phi, real tau){
        real nmax = numax/dnu - epsilon;
        return (n + epsilon + alpha/2 * (n-nmax)^2 +
                (A*G)/(2*pi()) * sin((2*pi()*(n-nmax))/G + phi)) * dnu * exp(-(n-nmax)/tau);
    }
}

data {
    int N;                  //number of stars
    int M;                  //number of modes
    real n[N, M];           //radial mode number
    real freq[N, M];        //l=0 frequencies
    real freq_err[N, M];
    real numax_obs[N];
    real numax_err[N];
    real dnu_guess[N];
}

parameters {
    // Normal Parameters
    real dnu[N];
    real numax[N];
    real<lower = -2.0*pi(), upper = 2.0*pi()> phi[N];
    //real<lower = 0> A[N];
    real G[N];
    real<lower = 0> tau[N];

    // Hierarchical Parameters
    real eps_std[N];
    real<lower=0> eps_sig;
    real epsA;
    real epsB;

    real al_std[N];
    real<lower=0> al_sig;
    real alA;
    real alB;

    real A_std[N];
    real<lower=0> A_sig;
    real AA;
    real AB;

    //real G_std[N];
    //real<lower=0> G_sig;
    //real GA;

}

transformed parameters {
    real epsilon[N];
    real alpha[N];
    real A[N];
    //real G[N];

    for (i in 1:N){
        epsilon[i] = eps_std[i] * eps_sig + (epsA + epsB * log(dnu[i]));
        alpha[i] = al_std[i] * al_sig + (alA * (dnu[i])^(-alB));
        A[i] = A_std[i] * A_sig + (AA * (dnu[i])^(-AB));
        //G[i] = G_std[i] * G_sig + GA;
    }
}

model {
    real mod[M];
    for (i in 1:N){
        for (j in 1:M){
            mod[j] = glitch(n[i,j], dnu[i], numax[i], epsilon[i], alpha[i], A[i], G[i], phi[i], tau[i]);
            // mod[j] ~ normal(freq[i], freq_err[i]);
        }
        freq[i,:] ~ normal(mod, freq_err[i,:]);
        dnu[i] ~ normal(dnu_guess[i], dnu_guess[i]*0.001);
        numax[i] ~ normal(numax_obs[i], numax_err[i]);
        //A[i] ~ lognormal(log(0.06*dnu[i]^(-0.88)), 0.4);
    }

    //epsilon ~ uniform(-1.0, 2.0);
    //nmax ~ normal(10, 4);
    //alpha ~ lognormal(log(0.015*dnu^(-0.32)), 0.3);

    G ~ normal(3.08, 0.65);
    //numax ~ normal(numax_obs, numax_err);
    tau ~ normal(1000, 100);

    // Hierarchical Parameters
    eps_std ~ normal(0, 1);
    eps_sig ~ normal(0, 0.5);
    epsA ~ normal(0.601, 0.25);
    epsB ~ normal(0.632, 0.25);

    al_std ~ normal(0, 1);
    al_sig ~ normal(0, 0.5);
    alA ~ normal(0.015, 0.005);
    alB ~ normal(0.32, 0.08);

    A_std ~ student_t(8, 0, 1);
    A_sig ~ normal(0, 0.5);
    AA ~ normal(0.06, 0.02);
    AB ~ normal(0.88, 0.05);

    //G_std ~ normal(0, 1);
    //G_sig ~ normal(0.65, 0.5);
    //GA ~ normal(3.08, 0.1);
}
'''
sm = pystan.StanModel(model_code=code)
```

    INFO:pystan:COMPILING THE C++ CODE FOR MODEL anon_model_f10a309571e22944b7a3242fcda96279 NOW.

```python
no_tau = pd.read_csv('no_tau_models.csv', names=['dnu', 'numax', 'epsilon', 'alpha', 'A', 'G', 'phi', 'tau'])

stan_data = {'N': len(IDs),
         'M': maxmodes,
         'n': arr_n,
         'freq': arr_freq,
         'freq_err': arr_freqerr,
         'numax_obs': Numax,
         'numax_err': Numax_err,
         'dnu_guess': dnu_avgarr
        }
start = {'dnu': dnu_avgarr,
         'numax': Numax,
         'eps_sig': 0.01,
         'al_sig': 0.01,
         'A_sig': 0.01,
         #'G_sig': 0.65,
         'epsilon': no_tau['epsilon'],
         'alpha': no_tau['alpha'],
         'A': no_tau['A'],
         'G': no_tau['G'],
         'phi': no_tau['phi'],
         'epsA': 0.601,
         'epsB': 0.632,
         'alA': 0.015,
         'alB': 0.32,
         'AA': 0.07,
         'AB': 0.87,
         #'GA': 3.08
         'tau': np.ones([len(IDs)])*1000
    }
nchains = 4

fit = sm.sampling(data=stan_data, iter=5000, chains=nchains, init=[start for n in range(nchains)])
```

    WARNING:pystan:n_eff / iter for parameter dnu[6] is 0.00037210839625705805!
    WARNING:pystan:n_eff / iter for parameter phi[6] is 0.00027130936772242056!
    WARNING:pystan:n_eff / iter for parameter G[6] is 0.0002685622857712131!
    WARNING:pystan:n_eff / iter for parameter tau[6] is 0.000689447707321841!
    WARNING:pystan:n_eff / iter for parameter al_std[6] is 0.0007727033231485766!
    WARNING:pystan:n_eff / iter for parameter epsilon[6] is 0.0005149043864723641!
    WARNING:pystan:n_eff / iter for parameter alpha[6] is 0.0003885890450781155!
    WARNING:pystan:n_eff / iter below 0.001 indicates that the effective sample size has likely been overestimated
    WARNING:pystan:Rhat for parameter dnu[6] is 1.6348027558722429!
    WARNING:pystan:Rhat for parameter numax[6] is 1.1266092952035156!
    WARNING:pystan:Rhat for parameter phi[6] is 2.5698536238107486!
    WARNING:pystan:Rhat for parameter G[6] is 2.7972375818038118!
    WARNING:pystan:Rhat for parameter tau[6] is 1.2844157954410929!
    WARNING:pystan:Rhat for parameter al_std[6] is 1.2511905994884165!
    WARNING:pystan:Rhat for parameter A_std[6] is 1.1786893744451195!
    WARNING:pystan:Rhat for parameter epsilon[6] is 1.362015599552383!
    WARNING:pystan:Rhat for parameter alpha[6] is 1.5635937851992252!
    WARNING:pystan:Rhat for parameter A[6] is 1.1265612412382644!
    WARNING:pystan:Rhat above 1.1 or below 0.9 indicates that the chains very likely have not mixed
    WARNING:pystan:10000 of 10000 iterations saturated the maximum tree depth of 10 (100.0%)
    WARNING:pystan:Run again with max_treedepth larger than 10 to avoid saturation

```python
print(fit)
```

    Inference for Stan model: anon_model_f10a309571e22944b7a3242fcda96279.
    4 chains, each with iter=5000; warmup=2500; thin=1;
    post-warmup draws per chain=2500, total post-warmup draws=10000.

                 mean se_mean     sd   2.5%    25%    50%    75%  97.5%  n_eff   Rhat
    dnu[1]       5.49  2.3e-4 5.4e-3   5.48   5.49   5.49    5.5    5.5    553   1.01
    dnu[2]        5.4  1.8e-4 5.0e-3   5.39    5.4    5.4   5.41   5.41    755   1.01
    dnu[3]       6.47  2.0e-4 5.0e-3   6.46   6.47   6.47   6.47   6.48    622   1.01
    dnu[4]       4.76  1.6e-4 4.6e-3   4.75   4.75   4.76   4.76   4.77    846    1.0
    dnu[5]       8.48  2.2e-4 5.4e-3   8.47   8.48   8.48   8.48   8.49    624   1.01
    dnu[6]       6.18  4.8e-3 9.3e-3   6.16   6.18   6.19   6.19    6.2      4   1.63
    numax[1]    56.13    0.01   0.33  55.48  55.91  56.14  56.37  56.77    580   1.01
    numax[2]     58.9    0.02    0.4   58.1  58.64  58.89  59.17   59.7    439   1.01
    numax[3]    66.25    0.02   0.34  65.59  66.02  66.25  66.47  66.93    465   1.02
    numax[4]    45.02    0.01   0.32  44.38  44.81  45.02  45.24  45.63    746   1.01
    numax[5]    97.52    0.01   0.27  96.98  97.33  97.52   97.7  98.04    561   1.01
    numax[6]    67.12    0.09   0.35  66.46  66.88  67.12  67.36  67.81     17   1.13
    phi[1]       0.51    0.01   0.23   0.08   0.36   0.51   0.67   0.96    493   1.01
    phi[2]      -0.47 10.0e-3   0.22  -0.89  -0.61  -0.47  -0.33  -0.04    464   1.01
    phi[3]       1.04  6.7e-3   0.15   0.76   0.94   1.04   1.14   1.34    494   1.01
    phi[4]      -0.41    0.01   0.29  -0.94  -0.61  -0.42  -0.22    0.2    654   1.01
    phi[5]       2.35  7.8e-3   0.18   1.98   2.23   2.35   2.47   2.67    506   1.01
    phi[6]       1.61    0.52   0.86    0.3    0.7    2.1   2.37   2.66      3   2.57
    G[1]         3.19    0.03   0.55   2.37   2.79   3.09   3.56   4.44    309   1.01
    G[2]         2.35  4.8e-3    0.1   2.17   2.28   2.34   2.42   2.57    460   1.01
    G[3]          2.7  2.7e-3   0.08   2.54   2.65    2.7   2.76   2.87    975    1.0
    G[4]         2.86  6.6e-3   0.17   2.58   2.75   2.85   2.96   3.26    684    1.0
    G[5]         2.73  3.4e-3   0.09   2.58   2.67   2.72   2.78   2.92    673   1.01
    G[6]          3.8    1.02   1.67   2.13   2.34   2.59    5.6    6.4      3    2.8
    tau[1]     1148.4    3.12   88.6 973.48 1088.7 1147.5 1206.4 1323.9    807    1.0
    tau[2]     1186.2    2.86  85.02 1028.3 1125.8 1183.9 1244.4 1356.3    882    1.0
    tau[3]     1381.9    2.46  76.72 1237.9 1328.3 1379.7 1434.1 1533.1    974    1.0
    tau[4]     1059.3    2.92  89.22  887.4 998.59 1059.1 1120.1 1236.8    934   1.01
    tau[5]     1393.9    2.81  74.14 1252.8 1342.9 1393.0 1442.8 1542.9    696   1.01
    tau[6]     1243.2   40.25 105.69 1025.9 1169.7 1251.7 1317.7 1437.1      7   1.28
    eps_std[1]  -0.86    0.03   0.38  -1.61  -1.11  -0.85  -0.61  -0.13    204   1.03
    eps_std[2]  -0.49    0.03   0.37  -1.22  -0.74  -0.49  -0.24   0.22    193   1.03
    eps_std[3]  -0.53    0.03   0.39  -1.31   -0.8  -0.53  -0.26   0.23    188   1.03
    eps_std[4]   1.47    0.04   0.78   0.09   0.91   1.43   1.99   3.14    369   1.01
    eps_std[5]  -0.58    0.03   0.45  -1.46   -0.9  -0.57  -0.27    0.3    186   1.03
    eps_std[6]   -0.7    0.03   0.39  -1.47  -0.96  -0.69  -0.44   0.04    181   1.04
    eps_sig      0.44  7.6e-3   0.17   0.21   0.31    0.4   0.52   0.89    516   1.01
    epsA         0.31  8.8e-3   0.22  -0.12   0.17   0.31   0.46   0.73    615    1.0
    epsB         0.04  8.3e-3   0.15  -0.26  -0.07   0.03   0.14   0.34    336    1.0
    al_std[1]    1.31    0.04   0.79   -0.3    0.8    1.3   1.83   2.88    405   1.03
    al_std[2]    0.35    0.03   0.68  -1.06  -0.06   0.37   0.77   1.68    397   1.02
    al_std[3]    0.41    0.03   0.53  -0.61   0.08   0.41   0.73   1.49    245   1.03
    al_std[4]    0.13    0.04   0.61  -1.13  -0.26   0.16   0.54   1.29    295   1.03
    al_std[5]    -0.5    0.12   0.64  -1.96  -0.89  -0.43  -0.03   0.49     29   1.08
    al_std[6]   -0.24     0.3   0.83  -1.83   -0.8  -0.26    0.3   1.46      8   1.25
    al_sig     4.1e-3  1.7e-4 3.1e-3 8.7e-4 2.1e-3 3.3e-3 5.1e-3   0.01    315   1.02
    alA          0.02  1.9e-4 3.5e-3   0.02   0.02   0.02   0.02   0.03    340   1.02
    alB          0.26  2.5e-3   0.07   0.13   0.22   0.26   0.31   0.39    700   1.02
    A_std[1]     0.37    0.02   0.29  -0.05   0.18   0.32   0.51   1.09    229   1.03
    A_std[2]      2.5    0.11   1.16   0.91   1.72    2.3   3.03   5.53    107   1.05
    A_std[3]     0.45    0.02   0.25   0.12   0.29   0.41   0.57    1.0    100   1.06
    A_std[4]     0.59    0.03   0.36   0.07   0.34   0.53   0.78   1.49    195   1.03
    A_std[5]     0.33    0.02   0.19   0.06   0.19   0.29   0.42   0.78    134   1.04
    A_std[6]     0.36    0.15   0.48   -0.2   0.07   0.22   0.51   1.57     10   1.18
    A_sig        0.04  1.6e-3   0.02   0.01   0.03   0.04   0.05    0.1    227   1.03
    AA           0.07  7.9e-4   0.02   0.03   0.06   0.07   0.08   0.11    626   1.01
    AB           0.87  1.7e-3   0.05   0.78   0.84   0.87   0.91   0.97    953   1.01
    epsilon[1] 7.6e-3  3.6e-4 9.6e-3  -0.01 1.1e-3 7.6e-3   0.01   0.03    701   1.01
    epsilon[2]   0.15  3.7e-4   0.01   0.13   0.14   0.15   0.16   0.17    845   1.01
    epsilon[3]   0.14  3.3e-4 8.1e-3   0.12   0.13   0.14   0.14   0.15    600   1.01
    epsilon[4]    0.9  3.5e-4 9.9e-3   0.88   0.89    0.9   0.91   0.92    819    1.0
    epsilon[5]   0.12  3.0e-4 7.4e-3   0.11   0.12   0.12   0.13   0.14    619   1.01
    epsilon[6]   0.07  5.8e-3   0.01   0.05   0.06   0.07   0.08    0.1      5   1.36
    alpha[1]     0.02  2.3e-4 3.9e-3   0.01   0.02   0.02   0.02   0.03    294   1.01
    alpha[2]     0.02  7.7e-5 1.8e-3   0.01   0.01   0.02   0.02   0.02    552   1.01
    alpha[3]     0.02  1.1e-5 6.5e-4   0.01   0.02   0.02   0.02   0.02   3401    1.0
    alpha[4]     0.02  3.8e-5 1.4e-3   0.01   0.01   0.02   0.02   0.02   1346   1.01
    alpha[5]     0.01  9.7e-6 5.3e-4   0.01   0.01   0.01   0.01   0.01   2937    1.0
    alpha[6]     0.01  1.4e-3 2.8e-3 9.5e-3   0.01   0.01   0.02   0.02      4   1.56
    A[1]         0.03  5.0e-4 9.9e-3   0.02   0.02   0.03   0.03   0.05    386   1.02
    A[2]         0.11  1.0e-3   0.02   0.07   0.09    0.1   0.12   0.16    564   1.01
    A[3]         0.03  4.7e-5 2.8e-3   0.02   0.03   0.03   0.03   0.04   3448    1.0
    A[4]         0.04  2.7e-4 7.9e-3   0.02   0.03   0.04   0.04   0.06    877    1.0
    A[5]         0.02  7.7e-5 2.7e-3   0.02   0.02   0.02   0.02   0.03   1259    1.0
    A[6]         0.03  5.9e-3   0.02   0.01   0.02   0.02   0.03   0.08     14   1.13
    lp__       -176.0    0.43   6.91 -190.9 -180.3 -175.6 -171.2 -163.5    262   1.02

    Samples were drawn using NUTS at Tue Feb 26 23:21:32 2019.
    For each parameter, n_eff is a crude measure of effective sample size,
    and Rhat is the potential scale reduction factor on split chains (at
    convergence, Rhat=1).

```python
fit.plot()
plt.show()
```

```python
#%matplotlib inline
def model(n,dnu,numax,eps,alpha,A,G,phi,tau):
    nmax = numax/dnu - eps
    s = (n + eps + ((alpha/2)*(nmax-n)**2) +
         (((A*G)/(2*np.pi)) * np.sin((2*np.pi*(n-nmax))/G + phi)))*dnu*np.exp(-(n-nmax)/(tau))
    return s

no_osc = np.loadtxt('/home/daniel/Documents/y4project/Dan_notebooks/no_osc_models.csv', delimiter=',')

fig = plt.figure(1, figsize=(12,4))
ax  = fig.add_subplot(111)
ax.set_position([0.1,0.1,0.6,0.85])

import itertools
colours = itertools.cycle(('r', 'orange', 'olive', 'teal', 'sienna', 'indigo'))

params = np.zeros([len(IDs), 8])

for i in np.arange(0,len(IDs),1):
    # Iterate point colour
    colour = next(colours)
    pts = modesID[i].loc[(modesID[i]['f0_err'] < 10e3)]
    # Plot all frequencies, and errorbars for the 'true' data
    ax.scatter(modesID[i]['f0'] % dnu_avgID[i], modesID[i]['f0'],
                label='', zorder=-1, facecolors='none', edgecolors=colour)
    ax.scatter(pts['f0'] % dnu_avgID[i], pts['f0'],
                label=r'ID = '+str(IDs[i]), c=colour)
    ax.errorbar(pts['f0'] % dnu_avgID[i], pts['f0'], xerr=pts['f0_err'],
                        zorder=0, fmt="none", label='', c='k',
                        capsize=2, markersize=4, elinewidth=1)

    params[i] = [np.mean(fit['dnu'], axis=0)[i],
           np.mean(fit['numax'], axis=0)[i],
           np.mean(fit['epsilon'], axis=0)[i],
           np.mean(fit['alpha'], axis=0)[i],
           np.mean(fit['A'], axis=0)[i],
           np.mean(fit['G'], axis=0)[i],
           np.mean(fit['phi'], axis=0)[i],
           np.mean(fit['tau'], axis=0)[i]]

    # Plot the modelled fit
    g2 = model(arr_n[i], params[i,0], params[i,1], params[i,2], params[i,3],
               params[i,4], params[i,5], params[i,6], params[i,7])
    ax.plot(g2 % dnu_avgID[i], g2, c=colour)
    ax.plot(no_osc[i] % dnu_avgID[i], no_osc[i], linestyle=':', c=colour)

np.savetxt('tau_models.csv', params, delimiter=',')

plt.legend()
plt.ylabel('Frequency')
plt.xlabel(r'Mod. Freq. Spacing ($\mu$Hz)')
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)
#plt.savefig('seminar/somergbseizure.pdf', bbox='tight_layout')
```

```python
%matplotlib inline
plt.figure(2)
for i in np.arange(0,len(IDs),1):
    plt.scatter(modesID[i]['n'], modesID[i]['f0'], label=r'ID = '+str(IDs[i]))
plt.xlabel('Radial Order ($n$)')
plt.ylabel('Frequency')
plt.legend()
plt.show()

fig, ax = plt.subplots(len(IDs))
for i in np.arange(0,len(IDs),1):
    ax[i].errorbar(modesID[i]['n'], modesID[i]['f0'] - g2, yerr=modesID[i]['f0_err']*10)
    #ax[i].set_xlim([6,20])
    #ax[i].set_ylim([-3.5, 3.5])
    ax[i].set_ylim([-15, 15])

plt.show()
```

```python

```

```python
g2 = model(arr_n[5],
           np.mean(fit['dnu'], axis=0)[5],
           np.mean(fit['numax'], axis=0)[5],
           np.mean(fit['epsilon'], axis=0)[5],
           np.mean(fit['alpha'], axis=0)[5],
           np.mean(fit['A'], axis=0)[5],
           np.mean(fit['G'], axis=0)[5],
           np.mean(fit['phi'], axis=0)[5],
           1000
              )

plt.plot(g2 % dnu_avgID[5], g2)

pts = modesID[5].loc[(modesID[5]['f0_err'] < 10e3)]
plt.scatter(pts['f0'] % dnu_avgID[5], pts['f0'],
                label=r'ID = '+str(IDs[5]))
plt.errorbar(pts['f0'] % dnu_avgID[5], pts['f0'], xerr=pts['f0_err'],
                        zorder=0, fmt="none", label='', c='k',
                        capsize=2, markersize=4, elinewidth=1)
```

```python

```


## 1. Module Import, Select Device, and Download MNIST Data

### 1-1. Setup & Module Import

```python
!pip install qiskit[visualization]
```

    Collecting qiskit[visualization]
      Downloading https://files.pythonhosted.org/packages/79/19/44f002f6633c64b4ab88d274dd036857624e4ba8b701cd90a408103a3791/qiskit-0.27.0.tar.gz
    Collecting qiskit-terra==0.17.4
      Downloading https://files.pythonhosted.org/packages/b3/0c/3c7a8dd451dae0907263e9de9e3e34909e15e18c88a589b44581972c8511/qiskit_terra-0.17.4-cp37-cp37m-manylinux2010_x86_64.whl (6.0MB)
         |████████████████████████████████| 6.0MB 9.5MB/s
    Collecting qiskit-aer==0.8.2
      Downloading https://files.pythonhosted.org/packages/c2/d2/6ff15c370b5465b32529b528bf3f4ce1e01f74498be16203aa1c04b67022/qiskit_aer-0.8.2-cp37-cp37m-manylinux2010_x86_64.whl (18.0MB)
         |████████████████████████████████| 18.0MB 130kB/s
    Collecting qiskit-ibmq-provider==0.14.0
      Downloading https://files.pythonhosted.org/packages/83/2f/6f3b53b633cc477dbe601728c8c3fc8674441cb9554d14517254a52d0c8a/qiskit_ibmq_provider-0.14.0-py3-none-any.whl (229kB)
         |████████████████████████████████| 235kB 54.5MB/s
    Collecting qiskit-ignis==0.6.0
      Downloading https://files.pythonhosted.org/packages/54/be/a13c828e457e09d979667a61bddbd8c7246aafa94e2501b6a9154429cbea/qiskit_ignis-0.6.0-py3-none-any.whl (207kB)
         |████████████████████████████████| 215kB 49.0MB/s
    Collecting qiskit-aqua==0.9.2
      Downloading https://files.pythonhosted.org/packages/81/00/1f71631bbc3fd24537d7ec1c52adfcf4fa0851c13fefc05d6babcb11a1f8/qiskit_aqua-0.9.2-py3-none-any.whl (2.1MB)
         |████████████████████████████████| 2.1MB 45.6MB/s
    Requirement already satisfied: matplotlib>=2.1 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (3.2.2)
    Requirement already satisfied: ipywidgets>=7.3.0 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (7.6.3)
    Requirement already satisfied: pydot in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (1.3.0)
    Requirement already satisfied: pillow>=4.2.1 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (7.1.2)
    Collecting pylatexenc>=1.4
      Downloading https://files.pythonhosted.org/packages/5d/ab/34ec41718af73c00119d0351b7a2531d2ebddb51833a36448fc7b862be60/pylatexenc-2.10.tar.gz (162kB)
         |████████████████████████████████| 163kB 51.4MB/s
    Requirement already satisfied: seaborn>=0.9.0 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (0.11.1)
    Requirement already satisfied: pygments>=2.4 in /usr/local/lib/python3.7/dist-packages (from qiskit[visualization]) (2.6.1)
    Requirement already satisfied: scipy>=1.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (1.4.1)
    Requirement already satisfied: sympy>=1.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (1.7.1)
    Collecting retworkx>=0.8.0
      Downloading https://files.pythonhosted.org/packages/9e/cd/70d436f170aa1ead2ac9e4c19c8838633355d48b530f09455eab0af2f98e/retworkx-0.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl (1.4MB)
         |████████████████████████████████| 1.5MB 44.3MB/s
    Requirement already satisfied: dill>=0.3 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (0.3.4)
    Collecting ply>=3.10
      Downloading https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl (49kB)
         |████████████████████████████████| 51kB 8.6MB/s
    Requirement already satisfied: python-dateutil>=2.8.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (2.8.1)
    Requirement already satisfied: psutil>=5 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (5.4.8)
    Collecting fastjsonschema>=2.10
      Downloading https://files.pythonhosted.org/packages/d1/fb/ea090e917b18320f79be31d754bbe496b715175e865603cfce1eaed2e774/fastjsonschema-2.15.1-py3-none-any.whl
    Requirement already satisfied: jsonschema>=2.6 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (2.6.0)
    Collecting python-constraint>=1.4
      Downloading https://files.pythonhosted.org/packages/37/8b/5f1bc2734ca611943e1d6733ee244238679f6410a10cd45ede55a61a8402/python-constraint-1.4.0.tar.bz2
    Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.7/dist-packages (from qiskit-terra==0.17.4->qiskit[visualization]) (1.19.5)
    Collecting pybind11>=2.6
      Downloading https://files.pythonhosted.org/packages/8d/43/7339dbabbc2793718d59703aace4166f53c29ee1c202f6ff5bf8a26c4d91/pybind11-2.6.2-py2.py3-none-any.whl (191kB)
         |████████████████████████████████| 194kB 53.3MB/s
    Collecting websocket-client>=1.0.1
      Downloading https://files.pythonhosted.org/packages/ca/5f/3c211d168b2e9f9342cfb53bcfc26aab0eac63b998015e7af7bcae66119d/websocket_client-1.1.0-py2.py3-none-any.whl (68kB)
         |████████████████████████████████| 71kB 10.8MB/s
    Requirement already satisfied: urllib3>=1.21.1 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (1.24.3)
    Collecting requests-ntlm>=1.1.0
      Downloading https://files.pythonhosted.org/packages/03/4b/8b9a1afde8072c4d5710d9fa91433d504325821b038e00237dc8d6d833dc/requests_ntlm-1.1.0-py2.py3-none-any.whl
    Requirement already satisfied: requests>=2.19 in /usr/local/lib/python3.7/dist-packages (from qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (2.23.0)
    Requirement already satisfied: setuptools>=40.1.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-ignis==0.6.0->qiskit[visualization]) (57.0.0)
    Collecting quandl
      Downloading https://files.pythonhosted.org/packages/8b/2b/feefb36015beaecc5c0f9f2533e815b409621d9fa7b50b2aac621796f828/Quandl-3.6.1-py2.py3-none-any.whl
    Requirement already satisfied: pandas in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (1.1.5)
    Requirement already satisfied: h5py in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (3.1.0)
    Requirement already satisfied: scikit-learn>=0.20.0 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (0.22.2.post1)
    Collecting dlx<=1.0.4
      Downloading https://files.pythonhosted.org/packages/54/c0/b8fb5bb727e983b6f5251433ef941b48f38c65bb0bd6ec509e9185bcd406/dlx-1.0.4.tar.gz
    Collecting docplex<=2.20.204; sys_platform != "darwin"
      Downloading https://files.pythonhosted.org/packages/87/99/6f7c219b39fd58c84688ad0713eb932bfcf6be81fc74519e43ea9c915b56/docplex-2.20.204.tar.gz (611kB)
         |████████████████████████████████| 614kB 36.9MB/s
    Requirement already satisfied: fastdtw<=0.3.4 in /usr/local/lib/python3.7/dist-packages (from qiskit-aqua==0.9.2->qiskit[visualization]) (0.3.4)
    Collecting yfinance
      Downloading https://files.pythonhosted.org/packages/a7/ee/315752b9ef281ba83c62aa7ec2e2074f85223da6e7e74efb4d3e11c0f510/yfinance-0.1.59.tar.gz
    Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1->qiskit[visualization]) (1.3.1)
    Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1->qiskit[visualization]) (0.10.0)
    Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.7/dist-packages (from matplotlib>=2.1->qiskit[visualization]) (2.4.7)
    Requirement already satisfied: nbformat>=4.2.0 in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0->qiskit[visualization]) (5.1.3)
    Requirement already satisfied: jupyterlab-widgets>=1.0.0; python_version >= "3.6" in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0->qiskit[visualization]) (1.0.0)
    Requirement already satisfied: ipykernel>=4.5.1 in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0->qiskit[visualization]) (4.10.1)
    Requirement already satisfied: ipython>=4.0.0; python_version >= "3.3" in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0->qiskit[visualization]) (5.5.0)
    Requirement already satisfied: traitlets>=4.3.1 in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0->qiskit[visualization]) (5.0.5)
    Requirement already satisfied: widgetsnbextension~=3.5.0 in /usr/local/lib/python3.7/dist-packages (from ipywidgets>=7.3.0->qiskit[visualization]) (3.5.1)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.3->qiskit-terra==0.17.4->qiskit[visualization]) (1.2.1)
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.8.0->qiskit-terra==0.17.4->qiskit[visualization]) (1.15.0)
    Collecting ntlm-auth>=1.0.2
      Downloading https://files.pythonhosted.org/packages/ff/84/97c550164b54942b0e908c31ef09d9469f3ba4cd7332a671e2125732f63b/ntlm_auth-1.5.0-py2.py3-none-any.whl
    Collecting cryptography>=1.3
      Downloading https://files.pythonhosted.org/packages/b2/26/7af637e6a7e87258b963f1731c5982fb31cd507f0d90d91836e446955d02/cryptography-3.4.7-cp36-abi3-manylinux2014_x86_64.whl (3.2MB)
         |████████████████████████████████| 3.2MB 42.3MB/s
    Requirement already satisfied: idna<3,>=2.5 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (2.10)
    Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (2021.5.30)
    Requirement already satisfied: chardet<4,>=3.0.2 in /usr/local/lib/python3.7/dist-packages (from requests>=2.19->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (3.0.4)
    Collecting inflection>=0.3.1
      Downloading https://files.pythonhosted.org/packages/59/91/aa6bde563e0085a02a435aa99b49ef75b0a4b062635e606dab23ce18d720/inflection-0.5.1-py2.py3-none-any.whl
    Requirement already satisfied: more-itertools in /usr/local/lib/python3.7/dist-packages (from quandl->qiskit-aqua==0.9.2->qiskit[visualization]) (8.8.0)
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas->qiskit-aqua==0.9.2->qiskit[visualization]) (2018.9)
    Requirement already satisfied: cached-property; python_version < "3.8" in /usr/local/lib/python3.7/dist-packages (from h5py->qiskit-aqua==0.9.2->qiskit[visualization]) (1.5.2)
    Requirement already satisfied: joblib>=0.11 in /usr/local/lib/python3.7/dist-packages (from scikit-learn>=0.20.0->qiskit-aqua==0.9.2->qiskit[visualization]) (1.0.1)
    Requirement already satisfied: multitasking>=0.0.7 in /usr/local/lib/python3.7/dist-packages (from yfinance->qiskit-aqua==0.9.2->qiskit[visualization]) (0.0.9)
    Collecting lxml>=4.5.1
      Downloading https://files.pythonhosted.org/packages/30/c0/d0526314971fc661b083ab135747dc68446a3022686da8c16d25fcf6ef07/lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl (6.3MB)
         |████████████████████████████████| 6.3MB 31.7MB/s
    Requirement already satisfied: jupyter-core in /usr/local/lib/python3.7/dist-packages (from nbformat>=4.2.0->ipywidgets>=7.3.0->qiskit[visualization]) (4.7.1)
    Requirement already satisfied: ipython-genutils in /usr/local/lib/python3.7/dist-packages (from nbformat>=4.2.0->ipywidgets>=7.3.0->qiskit[visualization]) (0.2.0)
    Requirement already satisfied: tornado>=4.0 in /usr/local/lib/python3.7/dist-packages (from ipykernel>=4.5.1->ipywidgets>=7.3.0->qiskit[visualization]) (5.1.1)
    Requirement already satisfied: jupyter-client in /usr/local/lib/python3.7/dist-packages (from ipykernel>=4.5.1->ipywidgets>=7.3.0->qiskit[visualization]) (5.3.5)
    Requirement already satisfied: simplegeneric>0.8 in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0->qiskit[visualization]) (0.8.1)
    Requirement already satisfied: decorator in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0->qiskit[visualization]) (4.4.2)
    Requirement already satisfied: pickleshare in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0->qiskit[visualization]) (0.7.5)
    Requirement already satisfied: pexpect; sys_platform != "win32" in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0->qiskit[visualization]) (4.8.0)
    Requirement already satisfied: prompt-toolkit<2.0.0,>=1.0.4 in /usr/local/lib/python3.7/dist-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0->qiskit[visualization]) (1.0.18)
    Requirement already satisfied: notebook>=4.4.1 in /usr/local/lib/python3.7/dist-packages (from widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (5.3.1)
    Requirement already satisfied: cffi>=1.12 in /usr/local/lib/python3.7/dist-packages (from cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (1.14.5)
    Requirement already satisfied: pyzmq>=13 in /usr/local/lib/python3.7/dist-packages (from jupyter-client->ipykernel>=4.5.1->ipywidgets>=7.3.0->qiskit[visualization]) (22.1.0)
    Requirement already satisfied: ptyprocess>=0.5 in /usr/local/lib/python3.7/dist-packages (from pexpect; sys_platform != "win32"->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0->qiskit[visualization]) (0.7.0)
    Requirement already satisfied: wcwidth in /usr/local/lib/python3.7/dist-packages (from prompt-toolkit<2.0.0,>=1.0.4->ipython>=4.0.0; python_version >= "3.3"->ipywidgets>=7.3.0->qiskit[visualization]) (0.2.5)
    Requirement already satisfied: Send2Trash in /usr/local/lib/python3.7/dist-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (1.5.0)
    Requirement already satisfied: terminado>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (0.10.1)
    Requirement already satisfied: jinja2 in /usr/local/lib/python3.7/dist-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (2.11.3)
    Requirement already satisfied: nbconvert in /usr/local/lib/python3.7/dist-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (5.6.1)
    Requirement already satisfied: pycparser in /usr/local/lib/python3.7/dist-packages (from cffi>=1.12->cryptography>=1.3->requests-ntlm>=1.1.0->qiskit-ibmq-provider==0.14.0->qiskit[visualization]) (2.20)
    Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from jinja2->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (2.0.1)
    Requirement already satisfied: entrypoints>=0.2.2 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (0.3)
    Requirement already satisfied: bleach in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (3.3.0)
    Requirement already satisfied: pandocfilters>=1.4.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (1.4.3)
    Requirement already satisfied: defusedxml in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (0.7.1)
    Requirement already satisfied: testpath in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (0.5.0)
    Requirement already satisfied: mistune<2,>=0.8.1 in /usr/local/lib/python3.7/dist-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (0.8.4)
    Requirement already satisfied: webencodings in /usr/local/lib/python3.7/dist-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (0.5.1)
    Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets>=7.3.0->qiskit[visualization]) (20.9)
    Building wheels for collected packages: qiskit, pylatexenc, python-constraint, dlx, docplex, yfinance
      Building wheel for qiskit (setup.py) ... done
      Created wheel for qiskit: filename=qiskit-0.27.0-cp37-none-any.whl size=10505 sha256=2571129c9ed236f0be19591d2c3dce736ec775a4fc1551bb1243b77754dbb1c4
      Stored in directory: /root/.cache/pip/wheels/c6/54/d9/f0f711dc20aff31947456951374132d0e23ec527da8c15295d
      Building wheel for pylatexenc (setup.py) ... done
      Created wheel for pylatexenc: filename=pylatexenc-2.10-cp37-none-any.whl size=136834 sha256=3153bf8a7989191cfb42fcfedb8d2459c38e43f8840f0ebd26a79770f4dffff5
      Stored in directory: /root/.cache/pip/wheels/3d/0a/64/533613e8165f1fc42f8f727cd12bb240940e2bd3bae7829021
      Building wheel for python-constraint (setup.py) ... done
      Created wheel for python-constraint: filename=python_constraint-1.4.0-py2.py3-none-any.whl size=24079 sha256=e1e0b72017e50a6a7163c46b69e419b8a5ee1554800b0c6cfbdb1909e9cccb13
      Stored in directory: /root/.cache/pip/wheels/34/31/15/7b070b25d0a549d20ce2e9fe6d727471c2c61ef904720fd40c
      Building wheel for dlx (setup.py) ... done
      Created wheel for dlx: filename=dlx-1.0.4-cp37-none-any.whl size=5720 sha256=570bfa3097e77873e4562e92b35b86965c8de3929cdc31b01f7b573ebde99a66
      Stored in directory: /root/.cache/pip/wheels/bb/ba/15/fdd0deb104df3254912998150ba9245668db06b00af5912d1a
      Building wheel for docplex (setup.py) ... done
      Created wheel for docplex: filename=docplex-2.20.204-cp37-none-any.whl size=675396 sha256=622ce861335d661a001454c3cf60297c9b9b35038fd6b33aa34276a70d5d384c
      Stored in directory: /root/.cache/pip/wheels/ae/2c/e2/a099ebb6fda8adeba9c5fc2e25659d195ad2f5c6cc5fb75fd4
      Building wheel for yfinance (setup.py) ... done
      Created wheel for yfinance: filename=yfinance-0.1.59-py2.py3-none-any.whl size=23455 sha256=c65b36a5c7a4629c59043386e15a7e9d79e74de7e509d39ed9047d1a064b6430
      Stored in directory: /root/.cache/pip/wheels/f8/2a/0f/4b5a86e1d52e451757eb6bc17fd899629f0925c777741b6d04
    Successfully built qiskit pylatexenc python-constraint dlx docplex yfinance
    Installing collected packages: retworkx, ply, fastjsonschema, python-constraint, qiskit-terra, pybind11, qiskit-aer, websocket-client, ntlm-auth, cryptography, requests-ntlm, qiskit-ibmq-provider, qiskit-ignis, inflection, quandl, dlx, docplex, lxml, yfinance, qiskit-aqua, pylatexenc, qiskit
      Found existing installation: lxml 4.2.6
        Uninstalling lxml-4.2.6:
          Successfully uninstalled lxml-4.2.6
    Successfully installed cryptography-3.4.7 dlx-1.0.4 docplex-2.20.204 fastjsonschema-2.15.1 inflection-0.5.1 lxml-4.6.3 ntlm-auth-1.5.0 ply-3.11 pybind11-2.6.2 pylatexenc-2.10 python-constraint-1.4.0 qiskit-0.27.0 qiskit-aer-0.8.2 qiskit-aqua-0.9.2 qiskit-ibmq-provider-0.14.0 qiskit-ignis-0.6.0 qiskit-terra-0.17.4 quandl-3.6.1 requests-ntlm-1.1.0 retworkx-0.9.0 websocket-client-1.1.0 yfinance-0.1.59

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

import torch
from torch.autograd import Function
from torchvision import datasets, transforms
import torch.optim as optim
import torch.nn as nn
import torch.nn.functional as F

# from torchsummary import summary

import qiskit
from qiskit import transpile, assemble
from qiskit.visualization import *
from qiskit.circuit.random import random_circuit

from itertools import combinations
```

### 1-2. Select Device

```python
if torch.cuda.is_available():
    DEVICE = torch.device('cuda')
else:
    DEVICE = torch.device('cpu')
print('Using PyTorch version:', torch.__version__, ' Device:', DEVICE)
print('cuda index:', torch.cuda.current_device())
print('GPU 이름:', torch.cuda.get_device_name())
```

    Using PyTorch version: 1.9.0+cu102  Device: cuda
    cuda index: 0
    GPU 이름: Tesla T4

### 1-3. Setting of the main hyper-parameters of the model

```python
BATCH_SIZE = 16
EPOCHS = 30     # Number of optimization epochs
n_layers = 1    # Number of random layers
n_train = 128    # Size of the train dataset
n_test = 32

SAVE_PATH = "quanvolution/" # Data saving folder
PREPROCESS = True           # If False, skip quantum processing and load data from SAVE_PATH
seed = 47
np.random.seed(seed)        # Seed for NumPy random number generator
torch.manual_seed(seed)     # Seed for TensorFlow random number generator

backend = qiskit.Aer.get_backend('aer_simulator')
shots = 10
shift = 0.01
```

### 1-4. Data Loading

```python
train_dataset = datasets.MNIST(root = "./data",
                               train = True,
                               download = True,
                               transform = transforms.ToTensor())

test_dataset = datasets.MNIST(root = "./data",
                              train = False,
                              transform = transforms.ToTensor())

idx_train = np.arange(n_train)
idx_test = np.arange(n_test)

train_dataset.data = train_dataset.data[idx_train]
train_dataset.targets = train_dataset.targets[idx_train]

test_dataset.data = train_dataset.data[idx_test]
test_dataset.targets = train_dataset.targets[idx_test]

train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
                                           batch_size = BATCH_SIZE,
                                           shuffle = True)

test_loader = torch.utils.data.DataLoader(dataset = test_dataset,
                                          batch_size = BATCH_SIZE,
                                          shuffle = False)
```

    Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
    Downloading http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz to ./data/MNIST/raw/train-images-idx3-ubyte.gz
    Failed to download (trying next):
    HTTP Error 503: Service Unavailable

    Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz
    Downloading https://ossci-datasets.s3.amazonaws.com/mnist/train-images-idx3-ubyte.gz to ./data/MNIST/raw/train-images-idx3-ubyte.gz

    HBox(children=(FloatProgress(value=0.0, max=9912422.0), HTML(value='')))

    Extracting ./data/MNIST/raw/train-images-idx3-ubyte.gz to ./data/MNIST/raw

    Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
    Downloading http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz to ./data/MNIST/raw/train-labels-idx1-ubyte.gz

    HBox(children=(FloatProgress(value=0.0, max=28881.0), HTML(value='')))

    Extracting ./data/MNIST/raw/train-labels-idx1-ubyte.gz to ./data/MNIST/raw

    Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
    Downloading http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw/t10k-images-idx3-ubyte.gz

    HBox(children=(FloatProgress(value=0.0, max=1648877.0), HTML(value='')))

    Extracting ./data/MNIST/raw/t10k-images-idx3-ubyte.gz to ./data/MNIST/raw

    Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz
    Downloading http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz

    HBox(children=(FloatProgress(value=0.0, max=4542.0), HTML(value='')))

    Extracting ./data/MNIST/raw/t10k-labels-idx1-ubyte.gz to ./data/MNIST/raw

    /usr/local/lib/python3.7/dist-packages/torchvision/datasets/mnist.py:498: UserWarning: The given NumPy array is not writeable, and PyTorch does not support non-writeable tensors. This means you can write to the underlying (supposedly non-writeable) NumPy array using the tensor. You may want to copy the array to protect its data or make it writeable before converting it to a tensor. This type of warning will be suppressed for the rest of this program. (Triggered internally at  /pytorch/torch/csrc/utils/tensor_numpy.cpp:180.)
      return torch.from_numpy(parsed.astype(m[2], copy=False)).view(*s)

```python
for (X_train, y_train) in train_loader:
    print('X_train:', X_train.size(), 'type:', X_train.type())
    print('y_train:', y_train.size(), 'type:', y_train.type())
    break
```

    X_train: torch.Size([16, 1, 28, 28]) type: torch.FloatTensor
    y_train: torch.Size([16]) type: torch.LongTensor

```python
pltsize = 1
plt.figure(figsize=(10 * pltsize, pltsize))
for i in range(1):
    plt.subplot(1, 10, i + 1)
    plt.axis('off')
    plt.imshow(X_train[i, :, :, :].numpy().reshape(28, 28), cmap = "gray_r")
    plt.title('Class: ' + str(y_train[i].item()))

```

## 2. Construct Quantum Circuit

### 2-1. Create a 'Quantum Class' with Qiskit

```python
class RandomCircuit:
    """
    This class defines random circuit of Quanvolution layer
    """

    def __init__(self, n_qubits, backend, shots):
        # --- Circuit definition ---
        self.n_qubits = n_qubits
        self._circuit = qiskit.QuantumCircuit(self.n_qubits)

        depth = np.random.randint(1,7,1)[0]

        #self.theta = qiskit.circuit.Parameter('theta')
        self.theta = [qiskit.circuit.Parameter('theta{}'.format(i)) for i in range(self.n_qubits)]

        for i in range(self.n_qubits):
            self._circuit.ry(self.theta[i], i)

        self._circuit.barrier()
        self._circuit += random_circuit(self.n_qubits, depth)
        self._circuit.measure_all()
        # ---------------------------

        self.backend = backend
        self.shots = shots

    def run(self, data):
        param_dict = dict()
        for i in range(self.n_qubits):
            param_dict[self.theta[i]] = data[i] * np.pi / 255

        t_qc = transpile(self._circuit,
                         self.backend)
        qobj = assemble(t_qc,
                        shots=self.shots,
                        parameter_binds = [param_dict])
        job = self.backend.run(qobj)
        result = job.result().get_counts()

        counts = np.array(list(result.values()))
        states = np.array(list(result.keys())).astype(float)

        # Compute probabilities for each state
        probabilities = counts / self.shots
        # Get state expectation
        expectation = np.sum(states * probabilities) / np.pi

        return expectation
```

Let's test the implementation.

```python
simulator = backend

circuit = RandomCircuit(4, simulator, shots)
#print('Expected value for rotation pi {}'.format(circuit.run([np.pi, np.pi*2])[0]))
print(circuit.run(np.random.randint(0, 255, size=4)))
circuit._circuit.draw('mpl')
```

### 2-2. Create a 'Quanvolution Class' with PyTorch

```python
import time
```

```python
class QuanvFunction(Function):
    """ Hybrid quantum - classical function definition """
    """
    Convolves the input image with many applications of the same quantum circuit.
    In the standard language of CNN, this would correspond to a convolution with a stride equal to 1.
    """

    @staticmethod
    def forward(ctx, input, quantum_circuits, in_channels, out_channels, kernel_size, backend, shots, shift):
        """ Forward pass computation """
        ctx.shift = shift
        ctx.quantum_circuits = quantum_circuits

        input_size = input.size()[-1]
        output_size = input_size - 2 * (kernel_size // 2)

        output = torch.zeros((BATCH_SIZE, out_channels, output_size, output_size))
        multiplier = out_channels // in_channels

        print(len(input))
        for i, image in enumerate(input):
            start = time.time()
            for m in range(multiplier):
                for c in range(in_channels):
                    for x in range(output_size):
                        for y in range(output_size):
                            window = image[c, x:x+kernel_size, y:y+kernel_size].squeeze()
                            window_flatten = window.reshape(kernel_size ** 2)
                            output[i, m*in_channels+c, x, y] = quantum_circuits[m].run(window_flatten.numpy())
                            #print(i, m*in_channels+c, x, y)
            print(i,"번 이미지 처리 시간: ", time.time()-start)

        result = output
        ctx.save_for_backward(input, result)

        return result

    @staticmethod
    def backward(ctx, grad_output):
        """ Backward pass computation """
        input, result = ctx.saved_tensors
        input_list = input.numpy()

        shift_right = input_list + np.ones(input_list.shape) * ctx.shift
        shift_left = input_list - np.ones(input_list.shape) * ctx.shift

        gradients = []
        for i in range(len(input_list)):
            expectation_right = ctx.quantum_circuit.run(shift_right[i])
            expectation_left  = ctx.quantum_circuit.run(shift_left[i])

            gradient = torch.tensor([expectation_right]) - torch.tensor([expectation_left])  # Actually (gradient * shift * 2), but that doesn't matter
            gradients.append(gradient)
        gradients = np.array([gradients]).T
        return torch.tensor([gradients]).float() * grad_output.float(), None, None
```

```python
class Quanv(nn.Module):
    """ Quanvolutional layer definition """

    def __init__(self, circuit_type, in_channels, out_channels, kernel_size, backend=backend, shots=shots, shift=shift):
        super(Quanv, self).__init__()
        self.in_channels = in_channels
        self.out_channels = out_channels
        multiplier = out_channels // in_channels

        if out_channels % in_channels != 0:
            print("out_channels / in_channels must be an integer")
            raise NotImplementedError

        if circuit_type == 'random':
            self.quantum_circuits = [RandomCircuit(n_qubits=kernel_size**2,
                                                   backend=backend,
                                                   shots=shots) for c in range(multiplier)]
        else:
            print("Invalid circuit_type")
            raise NotImplementedError

        self.kernel_size = kernel_size
        self.backend = backend
        self.shots = shots
        self.shift = shift

    def forward(self, input):
        return QuanvFunction.apply(input, self.quantum_circuits,
                                   self.in_channels, self.out_channels,
                                   self.kernel_size, self.backend,
                                   self.shots, self.shift)
```

```python
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.quanv = Quanv('random', 1, 1, kernel_size=3)
        self.conv1 = nn.Conv2d(1, 50, kernel_size=3)
        self.conv2 = nn.Conv2d(50, 64, kernel_size=3)
        self.dropout = nn.Dropout2d(0.4)
        self.fc1 = nn.Linear(64, 16)
        self.fc2 = nn.Linear(16, 10)

    def forward(self, x):
        x = F.max_pool2d(x, 2)
        x = F.relu(self.quanv(x))
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2)
        x = torch.flatten(x, start_dim=1)
        x = F.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return F.softmax(x)
```

```python
model = Net()
optimizer = optim.Adam(model.parameters(), lr=0.001)
loss_func = nn.CrossEntropyLoss()

epochs = EPOCHS
loss_list = []

model.train()
for epoch in range(epochs):
    total_loss = []
    for batch_idx, (data, target) in enumerate(train_loader):
        optimizer.zero_grad()

        # Forward pass
        output = model(data)

        # Calculating loss
        loss = loss_func(output, target)
        print(loss.item())

        # Backward pass
        loss.backward()

        # Optimize the weights
        optimizer.step()

        total_loss.append(loss.item())
    loss_list.append(sum(total_loss)/len(total_loss))
    print('Training [{:.0f}%]\tLoss: {:.4f}'.format(
        100. * (epoch + 1) / epochs, loss_list[-1]))

```

```python
model.eval()
with torch.no_grad():
    correct = 0
    for batch_idx, (data, target) in enumerate(test_loader):
        data = data
        target = target
        output = model(data)
        pred = output.argmax(dim=1, keepdim=True)
        correct += pred.eq(target.view_as(pred)).sum().item()
        loss = loss_func(output, target)
        total_loss.append(loss.item())
    print('Performance on test data:\n\tLoss: {:.4f}\n\tAccuracy: {:.1f}%'.format(
        sum(total_loss) / len(total_loss),
        correct / len(test_loader) * 100 / BATCH_SIZE)
        )
```