```python
#Symbolic comptation
import sympy as sy
from sympy.functions.elementary.exponential import exp as sy_exp

#Library to deal with datetime objects sh*t
import datetime as dt

#Numerical computation
import numpy as np
from numpy.random import normal as N

#Data operation
import pandas as pd

#Statistical library
import statsmodels.api as sm
from statsmodels.tsa.stattools import adfuller

#plot library
import matplotlib.pyplot as plt

#
import requests
```

```python
def getDailyAdj(symbol:str, key:str) -> pd.DataFrame:
    url = 'https://www.alphavantage.co/query?function=TIME_SERIES_DAILY_ADJUSTED&symbol='+symbol+'&outputsize=full&apikey='+key
    r = requests.get(url)
    data = r.json()

    data = pd.DataFrame(data['Time Series (Daily)']).T
    data.rename(columns={'1. open':'Open', '2. high':'High', '3. low':'Low',
                       '4. close':'Close', '5. adjusted close':'Adj Close',
                       '6. volume':'Volume', '7. dividend amount': 'Dividends',
                       '8. split coefficient':'Split coefficient'}, inplace=True)
    data.index = pd.to_datetime(data.index)
    data.sort_index(inplace=True)
    for col in data.columns:
        data[col] = pd.to_numeric(data[col])
    return data
```

```python
key = '' #Insira aqui a API do Alpha vantage, caso não tenha é gratuita (https://www.alphavantage.co/)
ticker = 'BOVA11.SAO'
start_date = '2020-06-01'
S = getDailyAdj(ticker, key)
```

```python
global n
x = S.loc[(S.index>=pd.to_datetime(start_date))]
n = len(x)
```

# Parameter Estimation in Mean Reversion Processes with Deterministic Long-Term Trend

$$dX_t = \alpha(\mu(t) - X_t)dt + \sigma X_t^\gamma dB_t$$

#### Freddy H. Marín Sánchez and Verónica M. Gallego

by Euler-Maruyama method:
$$Y_t = \frac{X_t - X_{t-1} - \left[\alpha(m_{t-1} - X_{t-1}) + \dot{m}_{t-1}\right]}{X^{\gamma}_{t-1}}\Delta$$
where $Y_t \sim N(0, \sigma^2 \Delta)$, which $\Delta = \frac{1}{n}$

```python
Δ = 1
x = x['Adj Close']
x0 = x[0]
x = x/x0
```

## Hodrick-Prescott Filter
$$\min_{\{g_t\}_{t=1}^T} \big\{\sum_{t=1}^T c_t^2 + \lambda\sum_{t=1}^T[(g_t - g_{t-1})-(g_{t-1}-g_{t-2})]^2\big\}$$
where $c_t = x_t - g_t$

```python
def MA(x,n_rolling):
    m = x.rolling(n_rolling).mean().iloc[n_rolling-1:]
    x = x.iloc[n_rolling-1:]
    return x,m

cycle, m = sm.tsa.filters.hpfilter(x, 12000)
#x,m = MA(x,120)
m = m.rename('Adj Close')
n = len(x)
```

$$\dot{m}_i = \frac{2m_{i+1} - 3m_i + m_{i-1}}{\Delta}$$
for i = 1,2,...T-1. if i=0 or i=T we have:
$$\dot{m}_0 = \frac{m_1 - m_0}{\Delta}$$
$$\dot{m}_T = \frac{m_T - m_{T-1}}{\Delta}$$

```python
m_ = np.zeros(n)
T = n-1
m_[0] = (m[1] - m[0])/Δ
m_[n-1] = (m[T] - m[T-1])/Δ
for i in range(1, T):
    m_[i] = (2*m[i+1] - 3*m[i] + m[i-1])/Δ
```

Reversion Rate:
$$\hat{\alpha} = \frac{\sum_{i=1}^T \left((X_i - X_{i-1} - \dot{m}_{i-1}\Delta)(m_{i-1} - X_{i-1})/X_{i-1}^{2\gamma}\right)}{\sum_{i=1}^T\left[(m_{i-1} - X_{i-1})/X_{i-1}^\gamma\right]^2\Delta}$$

```python
γ = 1
α = sum(((x[i] - x[i-1] - m_[i-1]*Δ)*(m[i-1] - x[i-1]))/(x[i-1]**(2*γ)) for i in range(1,n))/(Δ*sum(((m[i-1] - x[i-1])/(x[i-1]**(γ)))**2 for i in range(1, n)))
```

Volatility:
$$\hat{\sigma} = \sqrt{\frac{1}{T\Delta}\sum_{i=1}^T \left(\frac{X_i - X_{i-1} -[\hat{\alpha}(m_{i-1} - X_{i-1}) + \dot{m}_{i-1}]\Delta}{X_{i-1}^\gamma}\right)^2}$$

```python
σ = ((1/n*Δ)*sum(((x[i] - x[i-1] - (α*(m[i-1] - x[i-1]) + m_[i-1])*Δ)/(x[i-1]**γ))**2 for i in range(1,n)))**(0.5)
```

Long mean-trend:
$$\mu(t) = m(t) + \frac{\dot{m}(t)}{\alpha}$$

```python
μ = np.zeros(n)
for t in range(n):
    μ[t] = m[t] + (m_[t]/α)
```

Euler-Maruyama numerical method:
$$X(\tau_t) = X(\tau_{t-1}) + \alpha(\mu_{t-1} - X(\tau_{t-1})\Delta + X^\gamma(\tau_{t-1}) \epsilon_{\tau_t}$$

where $\epsilon_{\tau_t} \sim N(0, \sigma^2 \Delta )$

```python
#Monte carlo do processo
X = np.zeros(len(μ))
X[0] = x[0]
for t in range(1, len(X)):
    ϵ = N(0, σ*Δ)
    X[t] =  X[t-1] + α*(μ[t-1] - X[t-1])*Δ + (X[t-1]**γ)*ϵ
```

$$Var(X_T) = \mathbb{E}[(X_T - \mathbb{E}[X_T])^2] = \mathbb{E}[(\sigma \int_0^T e^{-\alpha(T-t)} dt)^2] \\ = \quad \sigma^2 \int_0^T e^{-2\alpha (T-t)}dt =  \frac{\sigma^2}{2\alpha}(1 - e^{-2\alpha T})$$

# Notes

Now we'll plot our data and estimated parameters (such as our deterministic long term trend function)

```python
X = pd.DataFrame(X, index=x.index, columns=['Adj Close'])
μ = pd.DataFrame(μ, index=x.index, columns=['Adj Close'])
m = pd.DataFrame(m, index=x.index, columns=['Adj Close'])

fig, ax= plt.subplots(3, figsize=(16,11.25), facecolor=(0.75, 0.75, 0.75))

#ax1 plots
ax[0].plot(x, label=ticker, color='b', linewidth=1.5)
ax[0].plot(μ, label='μ[t]', color='r', linewidth=1.5)
ax[0].plot(m, label='E[X]', color='g', linewidth=1.5)
ax[0].set_ylabel('Returns')

#ax2 Plots
ax[1].plot(x, label=ticker, color='b', linewidth=1.5)
ax[1].plot(X['Adj Close'], label='Simulated', color='y', linewidth=1.5)
ax[1].set_ylabel('Returns')
#ax3 plots
m_positive = []
m_negative = []
for Ex in m_:
    if Ex>=0:
        m_positive.append(Ex)
        m_negative.append(float('nan'))
    else:
        m_negative.append(Ex)
        m_positive.append(float('nan'))

m_positive = pd.DataFrame(m_positive, columns=['positive_derivative'], index=x.index[-len(m_positive):])
m_negative = pd.DataFrame(m_negative, columns=['negative_derivative'], index=x.index[-len(m_negative):])

ax[2].stem(m_positive.index, m_positive['positive_derivative'], markerfmt='green', use_line_collection=True, label='E\'[X]', linefmt='green')
ax[2].stem(m_negative.index, m_negative['negative_derivative'], markerfmt='red', use_line_collection=True, label='E\'[X]', linefmt='red')
ax[2].axhline(y=0, color='w')
ax[2].set_ylabel('Derivative of mean')

for axes in ax:
    axes.set_facecolor((0.1, 0.1, 0.1))
    axes.grid(True)
    axes.set_xlabel('t')
    axes.legend()

#fig.autofmt_xdate()
plt.show()
```

## Phase 2: Re-estimation

We want to estimate the vector parameter $\Theta$ which minimizes an error function defined by:
$$Error(\Theta) = \sum_{t=1}^T [f(\Theta, t) - \hat{\mu}(t)]^2$$

$$Error(\Gamma) = \sum_{t=1}^T [g(\Gamma, t) - \hat{\mu}(t)]^2$$

Choose a function type:
* $f(\Theta, t) =  a_0 + a_1t + ... +  a_{n-1}t^{n-1} + a_nt^n, \Theta = [a_n, a_{n-1}, ..., a_1, a_0]$
* $g(\Theta, t) = a + b\sin{((2\pi/P)t)}, \Gamma = [a, b]$

```python
t = sy.Symbol('t')
```

```python
P = 60
Γ = (sy.Symbol('a'), sy.Symbol('b'))
g = Γ[0] + Γ[1]*sy.sin((2*sy.pi/P)*t)
g = sy.lambdify(t, g)
Erro_Γ = sum((g(t) - μ['Adj Close'][t])**2 for t in range(n))
del_g = [sy.diff(Erro_Γ, γ) for γ in Γ]
s_g = sy.solve(del_g)
g = s_g[Γ[0]] + s_g[Γ[1]]*sy.sin((2*sy.pi/P)*t)
```

```python
n_grau = 3 #Polynomial degree to be re estimated
a = sy.symbols('a0:'+str(n_grau+1))
Θ = a
f = sum(a[i]*(t**i) for i in range(n_grau+1))
f = sy.lambdify(t,f)
Error_Θ = sum((f(t) - μ['Adj Close'][t])**2 for t in range(n))
del_f = [sy.diff(Error_Θ, θ) for θ in Θ]
s_f = sy.solve(del_f)
f = sum(s_f[a[i]]*(t**i) for i in range(0,n_grau+1))
```

```python
μ_re_est_f = sy.lambdify(t, f)
μ_re_est_g = sy.lambdify(t, g)
```

$$\hat{\hat{\alpha}} = \frac{\sum_{i=1}^T \left((X_i - X_{i-1})(\hat{\hat{\mu}}_{i-1} - X_{i-1})\Delta/X_{i-1}^{2\gamma}\right)}{\sum_{i=1}^T\left[(\hat{\hat{\mu}}_{i-1} - X_{i-1})/X_{i-1}^\gamma)\right]^2\Delta}$$

```python
α_re_est_f = sum((x[i] - x[i-1])*(μ_re_est_f(i-1) - x[i-1])*(Δ/(x[i-1]**(2*γ))) for i in range(n))/(sum((((μ_re_est_f(i-1) - x[i-1])/(x[i-1]**γ))**2)*Δ for i in range(n)))
α_re_est_g = sum((x[i] - x[i-1])*(μ_re_est_g(i-1) - x[i-1])*(Δ/(x[i-1]**(2*γ))) for i in range(n))/(sum((((μ_re_est_g(i-1) - x[i-1])/(x[i-1]**γ))**2)*Δ for i in range(n)))
```

$$\hat{\hat{\sigma}} = \sqrt{\frac{1}{T\Delta} \sum_{i=1}^T \left(\frac{X_i - X_{i-1} - \hat{\hat{\alpha}}(\hat{\hat{\mu}}_{i-1} - X_{i-1})\Delta}{X_{i-1}^\gamma}\right)^2}$$

```python
σ_re_est_f = sy.sqrt((1/n*Δ)*sum((((x[i] - x[i-1]) - α_re_est_f*(μ_re_est_f(i-1) - x[i-1])*Δ)/(x[i-1]**γ))**2 for i in range(n)))
σ_re_est_g = sy.sqrt((1/n*Δ)*sum((((x[i] - x[i-1]) - α_re_est_g*(μ_re_est_g(i-1) - x[i-1])*Δ)/(x[i-1]**γ))**2 for i in range(n)))
```

$$X_t - X_0 = \alpha \int_0^t(\mu(s) - X_s)ds + \sigma \int_0^t X_s^{\gamma}dB_s$$

$$\mathop{\mathbb{E}}[X_t] - \mathop{\mathbb{E}}[X_0] = \alpha \int_0^t (\mu(s) - \mathop{\mathbb{E}}[X_s])$$

$$\dot{m}(t) = \alpha(\mu(t) - m(t))$$

$$m(t) = a + \left[m_0 - a + \frac{\alpha \beta b}{\alpha^2 + \beta^2} \right]e^{-\alpha t} + \frac{\alpha b}{\alpha^2 + \beta^2}\left[\alpha \sin{\beta t} - \beta \cos{\beta t}\right]$$

```python
t = sy.Symbol('t')
a = s_g[Γ[0]]
b = s_g[Γ[1]]
m0 = m['Adj Close'].iloc[0]
β = 2*sy.pi/P
m_re_estimated_g = a + (m0 - a + ((α_re_est_g*β*b)/(α_re_est_g**2 + β**2)))*sy_exp(-α_re_est_g*t) + ((α_re_est_g*b)/(α_re_est_g**2 + β**2))*(α_re_est_g*sy.sin(β*t) - β*sy.cos(β*t))
m_re_estimated_g_ = sy.diff(m_re_estimated_g, t)
m_re_estimated_g = sy.lambdify(t, m_re_estimated_g)
m_re_estimated_g_ = sy.lambdify(t, m_re_estimated_g_)
```

```python
m_re_estimated_f = sy.symbols('m', cls=sy.Function)
Equation = sy.Eq(m_re_estimated_f(t).diff(t), α_re_est_f*(f - m_re_estimated_f(t)))
m_re_estimated_f = sy.dsolve(Equation, ics={m_re_estimated_f(0):m['Adj Close'].iloc[0]}).rhs
m_re_estimated_f_ = sy.lambdify(t, m_re_estimated_f.diff(t))
m_re_estimated_f = sy.lambdify(t, m_re_estimated_f)
```

$$dX_t = dX_{t-1} + \alpha(\mu_{t-1} - dX_{t-1}) + \epsilon X_{t-1}^\gamma $$

where $\epsilon \sim (0, \sigma^2\Delta_t)$ and $\Delta_t = \tau_t - \tau_{t-1}$

```python
σt = sy.sqrt(σ_re_est_f/(2*α_re_est_f)*(1-sy_exp(-2*α_re_est_f*t)))
σt = sy.lambdify(t, σt)
```

```python
days_forecast = 5

nans = np.array([[float('NaN')] for k in range(1,n)])
forecast_index = [x.index[-1]]
for i in range(1, days_forecast):
    date = forecast_index[-1]
    if date.isoweekday()==5:
        forecast_index.append(forecast_index[-1]+dt.timedelta(days=3))
    else:
        forecast_index.append(forecast_index[-1]+dt.timedelta(days=1))
```

# Notes

You don't need to understand the code, just understand that we'll plot the data that was estimated and compare with the re-estimated parameters

```python
X = np.zeros(days_forecast)
X[0] = x[-1]
for t in range(1, days_forecast):
    ϵ = N(0, σ_re_est_f)
    X[t] =  X[t-1] + σ_re_est_f*(μ_re_est_f(t-1+n) - X[t-1])*Δ + (X[t-1]**γ)*ϵ

#No "Forecast" deveria ser aplicado um monte carlo, mas você ja tem o codigo
#O resto fica por sua conta
X = pd.DataFrame(X, index=pd.to_datetime(forecast_index), columns=['Adj Close'])

fig, ax = plt.subplots(3, figsize=(16, 12))
#ax.fill_between(t1, μ_(t1)+σ, μ_(t1)-σ, facecolor='yellow')

#ax1 plots
ax[0].plot(x, label=ticker)
ax[0].plot(X, label='Forecast')
ax[0].plot(μ, label='μ[t]', color='Red')
ax[0].plot(m, label='m[t]', color='Green')

#ax2 plots
t = np.arange(0, n+days_forecast, (1/n))
ax[1].plot(x.values, label=ticker)
ax[1].plot(t, μ_re_est_f(t), label='μ[t] - Re-Estimated', color='Red')
ax[1].plot(t, m_re_estimated_f(t), label='E[t] - Re-Estimated', color='Green')

#ax3 plots
t = sy.Symbol('t')
k = np.arange(0, n+days_forecast, 1.0)

m_positive = []
m_negative = []
for Ex in m_re_estimated_f_(k):
    if Ex>=0:
        m_positive.append(Ex)
        m_negative.append(float('nan'))
    else:
        m_negative.append(Ex)
        m_positive.append(float('nan'))
ax[2].stem(m_positive, markerfmt='green', use_line_collection=True, label='E\'[X]', linefmt='green')
ax[2].stem(m_negative, markerfmt='red', use_line_collection=True, label='E\'[X]', linefmt='red')
ax[2].axhline(y=0, color='w')

for axes in ax:
    axes.set_facecolor((0.1, 0.1, 0.1))
    axes.grid(True)

    axes.set_ylabel('x[t] - μ[t]')
    axes.set_xlabel('t')
    axes.legend()
```

```python
X = pd.DataFrame(X, index=pd.to_datetime(forecast_index), columns=['Adj Close'])

fig2, ax = plt.subplots(3, figsize=(16,12))

#ax1 plots
ax[0].plot(x, label=ticker)
ax[0].plot(X, label='Forecast')
ax[0].plot(μ, label='μ[t]', color='Red')
ax[0].plot(m, label='m[t]', color='Green')

#ax2 plots
t = np.arange(0,n+days_forecast, 0.01)
ax[1].plot(x.values, label=ticker)
ax[1].plot(t, μ_re_est_g(t), label='μ[t] - Re-Estimated', color='Red')
ax[1].plot(t, m_re_estimated_g(t), label='E[x] - Re-Estimated', color='Green')

#ax3 plots
k = np.arange(0, n+days_forecast, 1.0)
m_positive = []
m_negative = []
for Ex in m_re_estimated_g_(k):
    if Ex>=0:
        m_positive.append(Ex)
        m_negative.append(float('nan'))
    else:
        m_negative.append(Ex)
        m_positive.append(float('nan'))
ax[2].stem(m_positive, markerfmt='green', use_line_collection=True, label='E\'[X]', linefmt='green')
ax[2].stem(m_negative, markerfmt='red', use_line_collection=True, label='E\'[X]', linefmt='red')
ax[2].axhline(y=0, color='w')

for axes in ax:
    axes.set_facecolor((0.1, 0.1, 0.1))
    axes.grid(True)
    axes.set_ylabel('x[t] - μ[t]')
    axes.set_xlabel('t')
    axes.legend()

plt.show()
```


# Introduction to Deep Learning with PyTorch

In this notebook, you'll get introduced to [PyTorch](http://pytorch.org/), a framework for building and training neural networks. PyTorch in a lot of ways behaves like the arrays you love from Numpy. These Numpy arrays, after all, are just tensors. PyTorch takes these tensors and makes it simple to move them to GPUs for the faster processing needed when training neural networks. It also provides a module that automatically calculates gradients (for backpropagation!) and another module specifically for building neural networks. All together, PyTorch ends up being more coherent with Python and the Numpy/Scipy stack compared to TensorFlow and other frameworks.

## Neural Networks

Deep Learning is based on artificial neural networks which have been around in some form since the late 1950s. The networks are built from individual parts approximating neurons, typically called units or simply "neurons." Each unit has some number of weighted inputs. These weighted inputs are summed together (a linear combination) then passed through an activation function to get the unit's output.

Mathematically this looks like:

$$
\begin{align}
y &= f(w_1 x_1 + w_2 x_2 + b) \\
y &= f\left(\sum_i w_i x_i \right)
\end{align}
$$

With vectors this is the dot/inner product of two vectors:

$$
h = \begin{bmatrix}
x_1 \, x_2 \cdots  x_n
\end{bmatrix}
\cdot
\begin{bmatrix}
           w_1 \\
           w_2 \\
           \vdots \\
           w_n
\end{bmatrix}
$$

### Stack them up!

We can assemble these unit neurons into layers and stacks, into a network of neurons. The output of one layer of neurons becomes the input for the next layer. With multiple input units and output units, we now need to express the weights as a matrix.

We can express this mathematically with matrices again and use matrix multiplication to get linear combinations for each unit in one operation. For example, the hidden layer ($h_1$ and $h_2$ here) can be calculated

$$
\vec{h} = [h_1 \, h_2] =
\begin{bmatrix}
x_1 \, x_2 \cdots \, x_n
\end{bmatrix}
\cdot
\begin{bmatrix}
           w_{11} & w_{12} \\
           w_{21} &w_{22} \\
           \vdots &\vdots \\
           w_{n1} &w_{n2}
\end{bmatrix}
$$

The output for this small network is found by treating the hidden layer as inputs for the output unit. The network output is expressed simply

$$
y =  f_2 \! \left(\, f_1 \! \left(\vec{x} \, \mathbf{W_1}\right) \mathbf{W_2} \right)
$$

## Tensors

It turns out neural network computations are just a bunch of linear algebra operations on *tensors*, a generalization of matrices. A vector is a 1-dimensional tensor, a matrix is a 2-dimensional tensor, an array with three indices is a 3-dimensional tensor (RGB color images for example). The fundamental data structure for neural networks are tensors and PyTorch (as well as pretty much every other deep learning framework) is built around tensors.

With the basics covered, it's time to explore how we can use PyTorch to build a simple neural network.

```python
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

import numpy as np
import torch
import helper
```

First, let's see how we work with PyTorch tensors. These are the fundamental data structures of neural networks and PyTorch, so it's imporatant to understand how these work.

```python
x = torch.rand(3, 2)
x
```

```python
y = torch.ones(x.size())
y
```

```python
z = x + y
z
```

In general PyTorch tensors behave similar to Numpy arrays. They are zero indexed and support slicing.

```python
z[0]
```

```python
z[:, 1:]
```

Tensors typically have two forms of methods, one method that returns another tensor and another method that performs the operation in place. That is, the values in memory for that tensor are changed without creating a new tensor. In-place functions are always followed by an underscore, for example `z.add()` and `z.add_()`.

```python
# Return a new tensor z + 1
z.add(1)
```

```python
# z tensor is unchanged
z
```

```python
# Add 1 and update z tensor in-place
z.add_(1)
```

```python
# z has been updated
z
```

### Reshaping

Reshaping tensors is a really common operation. First to get the size and shape of a tensor use `.size()`. Then, to reshape a tensor, use `.resize_()`. Notice the underscore, reshaping is an in-place operation.

```python
z.size()
```

```python
z.resize_(2, 3)
```

```python
z
```

## Numpy to Torch and back

Converting between Numpy arrays and Torch tensors is super simple and useful. To create a tensor from a Numpy array, use `torch.from_numpy()`. To convert a tensor to a Numpy array, use the `.numpy()` method.

```python
a = np.random.rand(4,3)
a
```

```python
b = torch.from_numpy(a)
b
```

```python
b.numpy()
```

The memory is shared between the Numpy array and Torch tensor, so if you change the values in-place of one object, the other will change as well.

```python
# Multiply PyTorch Tensor by 2, in place
b.mul_(2)
```

```python
# Numpy array matches new values from Tensor
a
```

## Feedforward neural networks with PyTorch

Next I'll show you how to use this pattern to build a neural network with PyTorch. First up, we need to get our dataset. This is provided through the `torchvision` package. The code below will download the MNIST dataset, then create training and test datasets for us.

```python
import matplotlib.pyplot as plt
from torchvision import datasets, transforms
```

```python
# Define a transform to normalize the data
transform = transforms.Compose([transforms.ToTensor(),
                              transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
                             ])
# Download and load the training data
trainset = datasets.MNIST('MNIST_data/', download=True, train=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)

# Download and load the test data
testset = datasets.MNIST('MNIST_data/', download=True, train=False, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64, shuffle=True)
```

```python
dataiter = iter(trainloader)
images, labels = dataiter.next()
```

We have the training data loaded into `trainloader` and we make that an iterator with `iter(trainloader)`. We'd use this to loop through the dataset for training, but here I'm just grabbing the first batch so we can check out the data. We can see below that `images` is just a tensor with size (64, 1, 28, 28). So, 64 images per batch, 1 color channel, and 28x28 images.

```python
images.size()
```

```python
plt.imshow(images[1].numpy().squeeze(), cmap='Greys_r')
```

## Building networks with PyTorch

Here I'll use PyTorch to build a simple feedfoward network to classify the MNIST images. That is, the network will receive a digit image as input and predict the digit in the image.

To build a neural network with PyTorch, you use the `torch.nn` module. The network itself is a class inheriting from `torch.nn.Module`. You define each of the operations separately, like `nn.Linear(784, 128)` for a fully connected linear layer with 784 inputs and 128 units.

The class needs to include a `forward` method that implements the forward pass through the network. In this method, you pass some input tensor `x` through each of the operations you defined earlier. The `torch.nn` module also has functional equivalents for things like ReLUs in `torch.nn.functional`. This module is usually imported as `F`. Then to use a ReLU activation on some layer (which is just a tensor), you'd do `F.relu(x)`. Below are a few different commonly used activation functions.

So, for this network, I'll build it with three fully connected layers, then a softmax output for predicting classes. The softmax function is similar to the sigmoid in that it squashes inputs between 0 and 1, but it's also normalized so that all the values sum to one like a proper probability distribution.

```python
from torch import nn
from torch import optim
import torch.nn.functional as F
from torch.autograd import Variable
```

```python
class Network(nn.Module):
    def __init__(self):
        super().__init__()
        # Defining the layers, 128, 64, 10 units each
        self.fc1 = nn.Linear(784, 128)
        self.fc2 = nn.Linear(128, 64)
        # Output layer, 10 units - one for each digit
        self.fc3 = nn.Linear(64, 10)

    def forward(self, x):
        ''' Forward pass through the network, returns the output logits '''

        x = self.fc1(x)
        x = F.relu(x)
        x = self.fc2(x)
        x = F.relu(x)
        x = self.fc3(x)

        return x

    def predict(self, x):
        ''' This function for predicts classes by calculating the softmax '''
        logits = self.forward(x)
        return F.softmax(logits)

net = Network()
net
```

### Initializing weights and biases

The weights and such are automatically initialized for you, but it's possible to customize how they are initialized. The weights and biases are tensors attached to the layer you defined, you can get them with `net.fc1.weight` for instance.

```python
print(net.fc1.weight)
print(net.fc1.bias)
```

For custom initialization, we want to modify these tensors in place. These are actually autograd *Variables*, so we need to get back the actual tensors with `net.fc1.weight.data`. Once we have the tensors, we can fill them with zeros (for biases) or random normal values.

```python
# Set biases to all zeros
net.fc1.bias.data.fill_(0)
```

```python
# sample from random normal with standard dev = 0.01
net.fc1.weight.data.normal_(std=0.01)
```

### Forward pass

Now that we have a network, let's see what happens when we pass in an image.

```python
# Grab some data
dataiter = iter(trainloader)
images, labels = dataiter.next()
images.resize_(64, 1, 784)

# Need to wrap it in a Variable, will explain in next notebook
inputs = Variable(images)

# Forward pass through the network
img_idx = 0
logits = net.forward(inputs[img_idx,:])

# Predict the class from the network output
ps = F.softmax(logits)

img = images[img_idx]
helper.view_classify(img.resize_(1, 28, 28), ps)
```

As you can see above, our network has basically no idea what this digit is. It's because we haven't trained it yet, all the weights are random! Next up, we'll look at training this network so it learns how to properly classify these digits.

Part 1 - Neural Networks with PyTorch.ipynb


# Warm-starting quantum optimization

## Introduction

Optimization problems with integer variables or constraints are often hard to solve. For example, the Quadratic Unconstrained Binary Optimization (QUBO) problem, i.e.

\begin{align}
\min_{x\in\{0,1\}^n}x^T\Sigma x + \mu^Tx,
\end{align}

is NP-Hard. Here, $\Sigma$ is an $n\times n$ matrix and $x$ is a vector of $n$ binary variables. Note that we could have added the linear term $\mu$ to the diagonal as $x_i^2=x_i$ for $x_i\in\{0, 1\}$. While QUBOs are hard to solve there exists many ways to relax them to problems that are easier to solve.
For example, if $\Sigma$ is semi-definite positive the QUBO can be relaxed and results in a convex Quadratic Program

\begin{align}
\min_{x\in[0,1]^n}x^T\Sigma x,
\end{align}

which becomes easy to solve as $x$ now represents $n$ continuous variables bound to the range $[0, 1]$.
Such relaxations can be leveraged to warm-start quantum optimization algorithms as shown in [1].

## References

[1] [D. J. Egger, J Marecek, S. Woerner, *Warm-starting quantum optimization*, arXiv:2009.10095](http://arxiv.org/abs/2009.10095)

```python
import numpy as np
import copy

# Problem modelling imports
from docplex.mp.model import Model

# Qiskit imports
from qiskit import BasicAer
from qiskit.utils import QuantumInstance
from qiskit.algorithms import QAOA, NumPyMinimumEigensolver
from qiskit.utils.algorithm_globals import algorithm_globals
from qiskit_optimization.algorithms import MinimumEigenOptimizer, CplexOptimizer
from qiskit_optimization import QuadraticProgram
from qiskit_optimization.problems.variable import VarType
from qiskit_optimization.converters.quadratic_program_to_qubo import QuadraticProgramToQubo
from qiskit_optimization.translators import from_docplex_mp
```

## Preliminaries: relaxing QUBOs

First, we show how to relax a QUBO built with a semi-definite positive matrix to obtain an easy-to-solve QP.

```python
def create_problem(mu: np.array, sigma: np.array, total: int = 3) -> QuadraticProgram:
    """ Solve the quadratic program using docplex. """

    mdl = Model()
    x = [mdl.binary_var('x%s' % i) for i in range(len(sigma))]

    objective = mdl.sum([mu[i]*x[i] for i in range(len(mu))])
    objective -= 2*mdl.sum([sigma[i, j]*x[i]*x[j] for i in range(len(mu)) for j in range(len(mu))])
    mdl.maximize(objective)
    cost = mdl.sum(x)
    mdl.add_constraint(cost == total)

    qp = from_docplex_mp(mdl)
    return qp

def relax_problem(problem) -> QuadraticProgram:
    """Change all variables to continuous."""
    relaxed_problem = copy.deepcopy(problem)
    for variable in relaxed_problem.variables:
        variable.vartype = VarType.CONTINUOUS

    return relaxed_problem
```

For this example, we use a positive semi-definite matrix $\Sigma$ and a linear term $\mu$ as defined below.

```python
mu = np.array([3.418,  2.0913,  6.2415,  4.4436, 10.892 ,  3.4051])
sigma = np.array([[ 1.07978412,  0.00768914,  0.11227606, -0.06842969, -0.01016793, -0.00839765],
                  [ 0.00768914,  0.10922887, -0.03043424, -0.0020045 ,  0.00670929, 0.0147937 ],
                  [ 0.11227606, -0.03043424,  0.985353  ,  0.02307313, -0.05249785, 0.00904119],
                  [-0.06842969, -0.0020045 ,  0.02307313,  0.6043817 ,  0.03740115, -0.00945322],
                  [-0.01016793,  0.00670929, -0.05249785,  0.03740115,  0.79839634, 0.07616951],
                  [-0.00839765,  0.0147937 ,  0.00904119, -0.00945322,  0.07616951, 1.08464544]])
```

Using DOCPLEX we build a model with binary variables.

```python
qubo = create_problem(mu, sigma)
qubo
```

    \ This file has been generated by DOcplex
    \ ENCODING=ISO-8859-1
    \Problem name: docplex_model1

    Maximize
     obj: 3.418000000000 x0 + 2.091300000000 x1 + 6.241500000000 x2
          + 4.443600000000 x3 + 10.892000000000 x4 + 3.405100000000 x5 + [
          - 4.319136480000 x0^2 - 0.061513120000 x0*x1 - 0.898208480000 x0*x2
          + 0.547437520000 x0*x3 + 0.081343440000 x0*x4 + 0.067181200000 x0*x5
          - 0.436915480000 x1^2 + 0.243473920000 x1*x2 + 0.016036000000 x1*x3
          - 0.053674320000 x1*x4 - 0.118349600000 x1*x5 - 3.941412000000 x2^2
          - 0.184585040000 x2*x3 + 0.419982800000 x2*x4 - 0.072329520000 x2*x5
          - 2.417526800000 x3^2 - 0.299209200000 x3*x4 + 0.075625760000 x3*x5
          - 3.193585360000 x4^2 - 0.609356080000 x4*x5 - 4.338581760000 x5^2 ]/2
    Subject To
     c0: x0 + x1 + x2 + x3 + x4 + x5 = 3

    Bounds
     0 <= x0 <= 1
     0 <= x1 <= 1
     0 <= x2 <= 1
     0 <= x3 <= 1
     0 <= x4 <= 1
     0 <= x5 <= 1

    Binaries
     x0 x1 x2 x3 x4 x5
    End

Such binary problems are hard to deal with but can be solved if the problem instance is small enough. Our example above has as solution

```python
CplexOptimizer().solve(qubo)
```

    optimal function value: 16.7689322
    optimal value: [0. 0. 1. 1. 1. 0.]
    status: SUCCESS

We can create a relaxation of this problem in which the variables are no longer binary. Note that we use the `QuadraticProgramToQubo` converter to convert the constraint into a quadratic penalty term. We do this to remain consistent with the steps that the Qiskit optimization module applies internally.

```python
qp = relax_problem(QuadraticProgramToQubo().convert(qubo))
qp
```

    \ This file has been generated by DOcplex
    \ ENCODING=ISO-8859-1
    \Problem name: docplex_model1

    Maximize
     obj: 259.553391640000 x0 + 258.226691640000 x1 + 262.376891640000 x2
          + 260.578991640000 x3 + 267.027391640000 x4 + 259.540491640000 x5 + [
          - 89.697600360000 x0^2 - 170.818440880000 x0*x1 - 171.655136240000 x0*x2
          - 170.209490240000 x0*x3 - 170.675584320000 x0*x4 - 170.689746560000 x0*x5
          - 85.815379360000 x1^2 - 170.513453840000 x1*x2 - 170.740891760000 x1*x3
          - 170.810602080000 x1*x4 - 170.875277360000 x1*x5 - 89.319875880000 x2^2
          - 170.941512800000 x2*x3 - 170.336944960000 x2*x4 - 170.829257280000 x2*x5
          - 87.795990680000 x3^2 - 171.056136960000 x3*x4 - 170.681302000000 x3*x5
          - 88.572049240000 x4^2 - 171.366283840000 x4*x5 - 89.717045640000 x5^2 ]/2
          -384.203087460000
    Subject To

    Bounds
           x0 <= 1
           x1 <= 1
           x2 <= 1
           x3 <= 1
           x4 <= 1
           x5 <= 1
    End

The solution of this continuous relaxation is different from the solution to the binary problem but can be used to warm-start a solver when dealing with the binary problem.

```python
sol = CplexOptimizer().solve(qp)
sol
```

    optimal function value: 17.01205502568274
    optimal value: [1.75249958e-01 1.48038882e-07 9.70905326e-01 7.38416868e-01
     9.99999992e-01 1.44389045e-01]
    status: SUCCESS

```python
c_stars = sol.samples[0].x
c_stars
```

    [0.17524995761801201,
     1.4803888163984595e-07,
     0.9709053264087679,
     0.7384168677494151,
     0.9999999916475085,
     0.14438904470168346]

## QAOA

Here, we illustrate how to warm-start the quantum approximate optimization algorithm (QAOA) by leveraging the relaxed problem shown above.

### Standard QAOA

First, we use standard QAOA to solve the QUBO. To do this, we convert the QUBO to Qiskit's QuadraticProgram class (note that the resulting problem is still a binary problem).

```python
algorithm_globals.random_seed = 12345
quantum_instance = QuantumInstance(BasicAer.get_backend('statevector_simulator'),
                                   seed_simulator=algorithm_globals.random_seed,
                                   seed_transpiler=algorithm_globals.random_seed)
qaoa_mes = QAOA(quantum_instance=quantum_instance, initial_point=[0., 1.])
exact_mes = NumPyMinimumEigensolver()
```

```python
qaoa = MinimumEigenOptimizer(qaoa_mes)
```

```python
qaoa_result = qaoa.solve(qubo)
print(qaoa_result)
```

    optimal function value: 16.768932200000002
    optimal value: [0. 0. 1. 1. 1. 0.]
    status: SUCCESS

### Warm-start QAOA

Next, we compare this result to a warm-start QAOA in which we use the solution to the continuous relaxation of the problem. First, we create the initial state

\begin{align}
|\phi^*\rangle=\bigotimes_{i=0}^{n-1}R_y(\theta_i)|0\rangle_n .
\end{align}

which is given by applying $R_y$ rotations with an angle $\theta=2\arcsin(\sqrt{c^*_i})$ that depends on the solution to the relaxed problem.
Here, $c^*_i$ the value of variable $i$ of the relaxed problem.

```python
from qiskit import QuantumCircuit

thetas = [2*np.arcsin(np.sqrt(c_star)) for c_star in c_stars]

init_qc = QuantumCircuit(len(sigma))
for idx, theta in enumerate(thetas):
    init_qc.ry(theta, idx)

init_qc.draw(output='mpl')
```

Next, we create the mixer operator for QAOA. When warm-starting QAOA we must ensure that the mixer operator has the initial state as ground state. We therefore chose the Hamiltonian

\begin{align}
H_{M,i}^{(ws)}=
\begin{pmatrix}
2c_i^*-1 & -2\sqrt{c_i^*(1-c_i^*)} \\
-2\sqrt{c_i^*(1-c_i^*)} & 1-2c_i^*
\end{pmatrix}
\end{align}

as mixer operator for qubit $i$. Once multiplied by $-i\beta$ and exponentiated this mixer produces the following mixer circuit.

```python
from qiskit.circuit import Parameter

beta = Parameter('β')

ws_mixer = QuantumCircuit(len(sigma))
for idx, theta in enumerate(thetas):
    ws_mixer.ry(-theta, idx)
    ws_mixer.rz(-2*beta, idx)
    ws_mixer.ry(theta, idx)

ws_mixer.draw(output='mpl')
```

The initial state and mixer operator can then be passed to QAOA.

```python
ws_qaoa_mes = QAOA(quantum_instance=quantum_instance,
                   initial_state=init_qc,
                   mixer=ws_mixer,
                   initial_point=[0., 1.])
```

```python
ws_qaoa = MinimumEigenOptimizer(ws_qaoa_mes)
```

```python
ws_qaoa_result = ws_qaoa.solve(qubo)
print(ws_qaoa_result)
```

    optimal function value: 16.768932200000002
    optimal value: [0. 0. 1. 1. 1. 0.]
    status: SUCCESS

### Analysis

Both results appear to give the same result. However, when we look at the underlying probability distribution we observe that the warm-start QAOA has a much higher probability of sampling the optimal solution.

```python
def format_qaoa_samples(samples, max_len: int = 10):
    qaoa_res = []
    for s in samples:
        if sum(s.x) == 3:
            qaoa_res.append((''.join([str(int(_)) for _ in s.x]), s.fval, s.probability))

    res = sorted(qaoa_res, key=lambda x: -x[1])[0:max_len]

    return [(_[0] + f': value: {_[1]:.3f}, probability: {1e2*_[2]:.1f}%') for _ in res]

format_qaoa_samples(qaoa_result.samples)
```

    ['001110: value: 16.769, probability: 2.6%',
     '011010: value: 15.744, probability: 1.1%',
     '001011: value: 14.671, probability: 1.7%',
     '101010: value: 14.626, probability: 1.7%',
     '010110: value: 14.234, probability: 1.7%',
     '100110: value: 13.953, probability: 4.7%',
     '000111: value: 13.349, probability: 0.0%',
     '110010: value: 12.410, probability: 0.4%',
     '010011: value: 12.013, probability: 4.0%',
     '100011: value: 11.559, probability: 1.5%']

```python
format_qaoa_samples(ws_qaoa_result.samples)
```

    ['001110: value: 16.769, probability: 86.3%',
     '001011: value: 14.671, probability: 0.3%',
     '101010: value: 14.626, probability: 0.8%',
     '100110: value: 13.953, probability: 1.7%',
     '000111: value: 13.349, probability: 0.1%',
     '100011: value: 11.559, probability: 0.0%']

## Warm-start QAOA

The warm-start features above are available in the Qiskit optimization module as a single optimizer named `WarmStartQAOAOptimizer` which is illustrated below. This solver will solve a QUBO with a warm-start QAOA. It computes $c^*$ by relaxing the problem. This behavior is controlled by setting `relax_for_pre_solver` to `True`.

```python
from qiskit_optimization.algorithms import WarmStartQAOAOptimizer
```

```python
qaoa_mes = QAOA(quantum_instance=quantum_instance, initial_point=[0., 1.])
ws_qaoa = WarmStartQAOAOptimizer(pre_solver=CplexOptimizer(), relax_for_pre_solver=True,
                                 qaoa=qaoa_mes, epsilon=0.0)
```

```python
ws_result = ws_qaoa.solve(qubo)
ws_result
```

    optimal function value: 16.768932200000002
    optimal value: [0. 0. 1. 1. 1. 0.]
    status: SUCCESS

```python
format_qaoa_samples(ws_result.samples)
```

    ['001110: value: 16.769, probability: 86.3%',
     '001011: value: 14.671, probability: 0.3%',
     '101010: value: 14.626, probability: 0.8%',
     '100110: value: 13.953, probability: 1.7%',
     '000111: value: 13.349, probability: 0.1%',
     '100011: value: 11.559, probability: 0.0%']

```python
import qiskit.tools.jupyter
%qiskit_version_table
%qiskit_copyright
```

Version Information

Qiskit Software	Version

Qiskit	None

Terra	0.17.0.dev0+6d2ef86

Aer	0.7.4

Ignis	0.5.0.dev0+0e869b4

Aqua	0.9.0.dev0+3a600d8

IBM Q Provider	0.12.0.dev0+de9e1ef

System information

Python	3.8.5 (default, Sep  4 2020, 07:30:14)
[GCC 7.3.0]

OS	Linux

CPUs	4

Memory (Gb)	15.552055358886719

Fri Feb 26 16:55:50 2021 CET

This code is a part of Qiskit

© Copyright IBM 2017, 2021.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
 of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

```python

```


## Egyszerű példa átlag- és pillanatnyi sebesség numerikus közelítésére.

```python
import numpy as np # mindenféle számolós bővítmény
```

```python
# átlagsebesség-számoló függvény:

def atlseb(xt, t1, t2):
    deltax=xt(t2)-xt(t1)
    deltat=t2-t1
    return(deltax/deltat)
```

$$\overline{v}(t_1, t_2)=\frac{x(t_2)-x(t_1)}{t_2-t_1}$$

```python
# egy egyszerű x(t) függvény:

def xt_1(t):
    return(9.81/2*t*t)
```

$$x(t)=\frac{g}2 t^2, \qquad g=9.81$$

```python
# próbáljuk ki:

print(atlseb(xt_1, 1.0, 1.1))
```

    10.300500000000001

$$v(t)\approx \overline{v}(t, t+\Delta t)$$

```python
# csökkentsük deltat-t:
print(atlseb(xt_1, 1.0, 1.5))
print(atlseb(xt_1, 1.0, 1.1))
print(atlseb(xt_1, 1.0, 1.01))
```

    12.262499999999998
    10.300500000000001
    9.859050000000016

```python
# most csináljuk programmal:

def atlseb_tab(xt, t1, deltat0, szorzo, N):
    deltat=deltat0
    for i in range(N):
        print("%7.3e    %7.3f"%(deltat, atlseb(xt,t1, t1+deltat)))
        deltat*=szorzo
```

```python
# próbáljuk ki:
atlseb_tab(xt_1, 1.0, 1.0, 0.2, 10)
```

    1.000e+00     14.715
    2.000e-01     10.791
    4.000e-02     10.006
    8.000e-03      9.849
    1.600e-03      9.818
    3.200e-04      9.812
    6.400e-05      9.810
    1.280e-05      9.810
    2.560e-06      9.810
    5.120e-07      9.810

$$x_2(t)=5*\sin(3*t)$$

```python
# bonyolultabb eset: (nem ismerős ez a jegyzetből?)

def xt_2(t):
    return(5*np.sin(3*t))   # np.sin(): a szinusz függvény a np csomagból (alapból nem tudja a Python)

atlseb_tab(xt_2, 0.2, 1.0, 0.1, 8)
```

    1.000e+00     -5.036
    1.000e-01     10.934
    1.000e-02     12.251
    1.000e-03     12.367
    1.000e-04     12.379
    1.000e-05     12.380
    1.000e-06     12.380
    1.000e-07     12.380

```python
# kicsit pontosabb, ha szimmetrikusan fogjuk közre a pontot
# azaz [t-deltat, t+deltat] közt számolunk átlagsebességet
def atlseb_tab_centr(xt, t1, deltat0, szorzo, N):
    deltat=deltat0
    for i in range(N):
        print("%7.3e    %7.3f"%(deltat, atlseb(xt, t1-deltat, t1+deltat)))
        deltat*=szorzo
```

$$v(t)\approx \overline{v}(t+\Delta t, t-\Delta t)$$

```python
print("Centrális derivált közelítés:")
atlseb_tab_centr(xt_2, 0.2, 1.0, 0.1, 8)

print("\nJobb oldali derivált közelítés:")
atlseb_tab(xt_2, 0.2, 1.0, 0.1, 8)
```

    Centrális derivált közelítés:
    1.000e+00      0.582
    1.000e-01     12.195
    1.000e-02     12.378
    1.000e-03     12.380
    1.000e-04     12.380
    1.000e-05     12.380
    1.000e-06     12.380
    1.000e-07     12.380

    Jobb oldali derivált közelítés:
    1.000e+00     -5.036
    1.000e-01     10.934
    1.000e-02     12.251
    1.000e-03     12.367
    1.000e-04     12.379
    1.000e-05     12.380
    1.000e-06     12.380
    1.000e-07     12.380

Sokkal előbb elértük a kívánt pontosságot!

```python
# most már jöhet bármilyen bonyolult függvény:

def xt_3(t):
    return(4.0*np.sin(t*t)*np.exp(-0.6*t))

atlseb_tab_centr(xt_3, 4.125, 1.0, 0.1, 8)
```

    1.000e+00      0.186
    1.000e-01     -0.488
    1.000e-02     -0.527
    1.000e-03     -0.527
    1.000e-04     -0.527
    1.000e-05     -0.527
    1.000e-06     -0.527
    1.000e-07     -0.527

## És egy kis demo, mit tud a sympy modul: (nem kötelező anyag!)

```python
from sympy import *

t=symbols('t')                 # t nem konkrét érték, hanem szimbólum
xtfun=4*sin(t**2)*exp(-0.6*t)   # ez egy x(t) függvény
vtfun=diff(xtfun, t)           # deriváltja a v(t) függvény
print(vtfun)                   # hogy is néz ki?
```

    8*t*exp(-0.6*t)*cos(t**2) - 2.4*exp(-0.6*t)*sin(t**2)

```python
# behelyettesítünk (substitute) t helyére 5.5-öt
vtfun.subs(t, 4.125)
```

$\displaystyle -0.527381354323332$

```python
atfun=diff(vtfun,t)  # ez meg a gyorsulása
print(atfun)    # eredeti alakban
simplify(atfun)   # egyszerűsítve
```

    -16*t**2*exp(-0.6*t)*sin(t**2) - 9.6*t*exp(-0.6*t)*cos(t**2) + 1.44*exp(-0.6*t)*sin(t**2) + 8*exp(-0.6*t)*cos(t**2)

$\displaystyle \left(- 16 t^{2} \sin{\left(t^{2} \right)} - 9.6 t \cos{\left(t^{2} \right)} + 1.44 \sin{\left(t^{2} \right)} + 8 \cos{\left(t^{2} \right)}\right) e^{- 0.6 t}$

```python
atfun    # eredeti alakban
```

$\displaystyle - 16 t^{2} e^{- 0.6 t} \sin{\left(t^{2} \right)} - 9.6 t e^{- 0.6 t} \cos{\left(t^{2} \right)} + 1.44 e^{- 0.6 t} \sin{\left(t^{2} \right)} + 8 e^{- 0.6 t} \cos{\left(t^{2} \right)}$

```python
simplify(atfun)
```

$\displaystyle \left(- 16 t^{2} \sin{\left(t^{2} \right)} - 9.6 t \cos{\left(t^{2} \right)} + 1.44 \sin{\left(t^{2} \right)} + 8 \cos{\left(t^{2} \right)}\right) e^{- 0.6 t}$

```python

```


# GPE examples for a $^{87}Rb$ spinor in F=1,2

### Introduction
This Notebook gives brief examples on numerical GPE simulations of a mixture of spin-1 and spin-2 BECs under the Single Mode Approximation (SMA).

Publication avaliable under https://arxiv.org/abs/1904.07617

### Constants

```python
from __future__ import division
import numpy as np
from scipy.constants import physical_constants
import matplotlib.pyplot as plt
import  GPE_SMA_F1F2_Lib as GPE

h=physical_constants["Planck constant"][0]
hbar=physical_constants["Planck constant over 2 pi"][0]
aB=physical_constants["Bohr radius"][0]

m=1.443e-25 #mass of a Rb87 atom (see Rubidium 87 D Line Data, D.Steck)
q=0.89*h
##################################
# Spin matrices for f=1 and f=2
##################################

F1_x=1/np.sqrt(2)*np.array([[0,1,0],[1,0,1],[0,1,0]])
F1_y=1/np.sqrt(2)*np.array([[0,-1j,0],[1j,0,-1j],[0,1j,0]])
F1_z=np.array([[1,0,0],[0,0,0],[0,0,-1]])

F2_x=1/2*np.array([[0,2,0,0,0],[2,0,np.sqrt(6),0,0],[0,np.sqrt(6),0,np.sqrt(6),0],[0,0,np.sqrt(6),0,2],[0,0,0,2,0]])
F2_y=1/2*np.array([[0,-1j*2,0,0,0],[1j*2,0,-1j*np.sqrt(6),0,0],[0,1j*np.sqrt(6),0,-1j*np.sqrt(6),0],[0,0,1j*np.sqrt(6),0,-1j*2],[0,0,0,1j*2,0]])
F2_z=np.array([[2,0,0,0,0],[0,1,0,0,0],[0,0,0,0,0],[0,0,0,-1,0],[0,0,0,0,-2]])

```

### Scattering parameters

```python
#F=1
a1_0=101.8*aB
a1_20=-1.07*aB

g1_0=(4*np.pi*hbar**2/m)*(3*a1_0+2*a1_20)/3
g1_1=(4*np.pi*hbar**2/m)*(a1_20)/3

#F=2
a2_20=3.51*aB
a2_42=6.95*aB

g2_1=(4*np.pi*hbar**2/m)*a2_42/7
g2_2=(4*np.pi*hbar**2/m)*(3*a2_42-7*a2_20)/7

#F=1 <-> F=2
a12_12=-1.31*a1_20
a12_32=-1.27*a1_20

g12_1=(4*np.pi*hbar**2/m)*a12_32/3
g12_2=(4*np.pi*hbar**2/m)*(3*a12_12+2*a12_32)/3

```

### Atom number and effective volume

```python
atomNumber=100e3
omega=91*2*np.pi   #mean trapping frequency
RTF=(g1_0*atomNumber/(m*(omega**2))*15/(4*np.pi))**(1/5.)   #Thomas-Fermi radius
Veff=14./15.*np.pi*RTF**3    #effective volume
print("RTF: %.2f um"%(RTF*1e6))
```

    RTF: 6.66 um

### EXAMPLE (I): f=1

Alignment to orientation conversion for the the initial state:
\begin{equation}
\frac{\Psi_0^{(1)}}{\sqrt{N}}=
\begin{pmatrix}
1/2\\
i\sqrt{2}\\
1/2
\end{pmatrix}
\end{equation}

```python
wavefunction1=np.sqrt(atomNumber)*np.array([1/2,1j/np.sqrt(2),1/2])
wavefunction2=np.sqrt(atomNumber)*np.array([0,0,0,0,0])

t=np.linspace(0,300e-3,1000)
[w1Array,F1_xArray,F1_yArray,F1_zArray,angle1Array,w2Array,F2_xArray,F2_yArray,F2_zArray,angle2Array]=GPE.SMA_GPE_F12_simulation(t,wavefunction1,wavefunction2,Veff,q,g1_1, g2_1,g2_2,g12_1,g12_2)

F1_TArray=np.sqrt(F1_xArray**2+F1_yArray**2)

n1Array=np.abs(w1Array)**2

plt.figure()
plt.plot(t*1e3,F1_TArray/atomNumber)
plt.xlabel("t (ms)")
plt.ylabel(r"$F_T^{(1)}/N$")
plt.title("Transverse magnetization")
plt.show()

```

### Example  (II): f=1 & f=2 interaction, effective QZS

Effective quadratic Zeeman shift (QZS) due to the inter-hyperfine interaction between f=1 and f=2.
\begin{equation}
\frac{\Psi_0}{\sqrt{N}}=\frac{\Psi_0^{(1)}\oplus\Psi_0^{(2)}}{\sqrt{N}}=\frac{1}{\sqrt{2}}
\begin{pmatrix}
1/2\\
i/\sqrt{2}\\
1/2
\end{pmatrix}
\oplus
\begin{pmatrix}
0\\
0\\
0\\
0\\
1
\end{pmatrix}
\end{equation}

```python
wavefunction1=np.sqrt(atomNumber/2)*np.array([1/2.,1j/np.sqrt(2),1/2.])
wavefunction2=np.sqrt(atomNumber/2)*np.array([0,0,0,0,1])

t=np.linspace(0,300e-3,1000)
[w1Array,F1_xArray,F1_yArray,F1_zArray,angle1Array,w2Array,F2_xArray,F2_yArray,F2_zArray,angle2Array]=GPE.SMA_GPE_F12_simulation(t,wavefunction1,wavefunction2,Veff,q,g1_1, g2_1,g2_2,g12_1,g12_2)

F1_TArray=np.sqrt(F1_xArray**2+F1_yArray**2)

plt.figure()
plt.plot(t*1e3,F1_TArray,label=r"$f=1$")
plt.plot(t*1e3,np.abs(F2_zArray/2),label=r"$f=2$")
plt.xlabel("t (ms)")
plt.ylabel(r"$F_T^{(f)}/f$")
plt.title("Inter-hyperfine dynamics")
plt.legend()
plt.show()

```

### Example  (III): f=1 & f=2 interaction, effective LZS
Effective linear Zeeman splitting (LZS) due to the inter-hyperfine interaction between f=1 and f=2.

\begin{equation}
\frac{\Psi_{0,A}}{\sqrt{N}}=\hat{R}_{x,\frac{\pi}{6}}
\begin{pmatrix}
\frac{1}{\sqrt{2}}\\
0\\
0
\end{pmatrix}
\oplus
\begin{pmatrix}
\frac{1}{\sqrt{2}}\\
0\\
0\\
0\\
0
\end{pmatrix}
\;\;\;\;\;
\frac{\Psi_{0,B}}{\sqrt{N}}=\hat{R}_{x,\frac{\pi}{6}}
\begin{pmatrix}
0\\
0\\
\frac{1}{\sqrt{2}}
\end{pmatrix}
\oplus
\begin{pmatrix}
0\\
0\\
0\\
0\\
\frac{1}{\sqrt{2}}
\end{pmatrix}
\end{equation}

```python
from scipy.linalg import expm

rotAngle=np.pi/6.
R1=expm(-1j*rotAngle*F1_x)
R2=expm(-1j*rotAngle*F2_x)

t=np.linspace(0,200e-3,1000)

#EXPERIMENT A
wavefunction1A=np.sqrt(atomNumber/2)*np.array([1,0,0])
wavefunction2A=np.sqrt(atomNumber/2)*np.array([1,0,0,0,0])

wavefunction1A=np.dot(R1,wavefunction1A)
wavefunction2A=np.dot(R2,wavefunction2A)

#EXPERIMENT B
wavefunction1B=np.sqrt(atomNumber/2)*np.array([0,0,1])
wavefunction2B=np.sqrt(atomNumber/2)*np.array([0,0,0,0,1])

wavefunction1B=np.dot(R1,wavefunction1B)
wavefunction2B=np.dot(R2,wavefunction2B)

#Simulation

[_,_,_,_,angle1ArrayA,_,_,_,_,angle2ArrayA]=GPE.SMA_GPE_F12_simulation(t,wavefunction1A,wavefunction2A,Veff,q,g1_1, g2_1,g2_2,g12_1,g12_2)

[_,_,_,_,angle1ArrayB,_,_,_,_,angle2ArrayB]=GPE.SMA_GPE_F12_simulation(t,wavefunction1B,wavefunction2B,Veff,q,g1_1, g2_1,g2_2,g12_1,g12_2)

phi_A=angle1ArrayA+angle2ArrayA
phi_B=angle1ArrayB+angle2ArrayB

deltaPhi=np.unwrap(phi_A-phi_B)
deltaPhi-=deltaPhi[0]

plt.figure()
plt.plot(t*1e3,deltaPhi)
plt.xlabel("t (ms)")
plt.ylabel(r"$\phi_A^{(12)}-\phi_B^{(12)}$ (rad)")
plt.show()

```

```python

```


# !!! D . R . A . F . T !!!

# Luminance

The [Luminance](http://en.wikipedia.org/wiki/Luminance) $L_v$ is the quantity defined by the formula: [1]

$$
\begin{equation}
L_v=\cfrac{d\Phi_v}{dAcos\theta d\Omega}
\end{equation}
$$

where $d\Phi_v$ is the [luminous flux](http://en.wikipedia.org/wiki/Luminous_flux) transmitted by an elementary beam passing through the given point and propagating in the solid angle, $d\Omega$, containing the given direction. $dA$ is the area of a section of that beam containing the given point. $\theta$ is the angle between the normal to that section and the direction of the beam.

$L_v$ unit is candela per square metre (or nits) $cd\cdot m^{-2}=lm\cdot m^{-2}\cdot sr^{-1}$.

[Colour](https://github.com/colour-science/colour/) defines the following *luminance* computation methods:

```python
import colour

sorted(colour.LUMINANCE_METHODS.keys())
```

    ['ASTM D1535',
     'CIE 1976',
     'Fairchild 2010',
     'Fairchild 2011',
     'Newhall 1943',
     'astm2008',
     'cie1976']

> Note: `'astm2008'` and `'cie1976'` are convenient aliases for respectively `'ASTM D1535'` and `'CIE 1976'`.

## Newhall, Nickerson, and Judd (1943) Method

Newhall, Nickerson, and Judd (1943) fitted a quintic-parabola function to the adjusted *Munsell-Sloan-Godlove* reflectances, the resulting equation computing *luminance* $R_Y$ as function of *Munsell* value $V$ is expressed as follows: [2]

$$
\begin{equation}
R_Y=1.2219V-0.23111V^2+0.23951V^3-0.021009V^4+0.0008404V^5
\end{equation}
$$

> See Also: The [Munsell Renotation System](../notation/munsell.ipynb) notebook for in-depth information about the *Munsell Renotation System*.

The `colour.luminance_Newhall1943` definition is used to compute *luminance* $R_Y$:

```python
colour.colorimetry.luminance_Newhall1943(3.74629715382)
```

    10.408987457743208

> Note: Input *Munsell* value $V$ is in domain [0, 10], output *luminance* $R_Y$ is in domain [0, 100].

The `colour.luminance` definition is implemented as a wrapper for various luminance computation methods:

```python
colour.colorimetry.luminance(3.74629715382, method='Newhall 1943')
```

    0.43750976730517127

## ASTM D1535-08$^{\epsilon 1}$ (2008) Method

Since 1943, the reference white used for the *Munsell Renotation System* has changed.

As a result the quintic-parabola function from Newhall, Nickerson, and Judd (1943) has been adjusted: Each coefficient of the function has been multiplied by 0.975, the reflectance factor of magnesium oxide with respect to the perfect reflecting diffuser and then rounded to five digits.

The updated equation for computing *luminance* $Y$ as function of the *Munsell* value $V$ is expressed as follows: [3]

$$
\begin{equation}
Y=1.1914V-0.22533V^2+0.23352V^3-0.020484V^4+0.00081939V^5
\end{equation}
$$

> See Also: The [Munsell Renotation System](../notation/munsell.ipynb) notebook for in-depth information about the *Munsell Renotation System*.

The `colour.luminance_ASTMD153508` definition is used to compute *luminance* $Y$:

```python
colour.colorimetry.luminance_ASTMD153508(3.74629715382)
```

    /Users/kelsolaar/Documents/Development/colour-science/colour/colour/utilities/verbose.py:235: ColourUsageWarning: "colour.colorimetry.luminance_ASTMD153508" object has been renamed to "colour.colorimetry.luminance_ASTMD1535".
      warn(*args, **kwargs)

    10.148809678226682

> Note: Input *Munsell* value $V$ is in domain [0, 10], output *luminance* $Y$ is in domain [0, 100].

Using the `colour.luminance` wrapper definition:

```python
colour.luminance(3.74629715382, method='ASTM D1535')
```

    0.42659001108260142

```python
colour.luminance(3.74629715382, method='astm2008')
```

    0.42659001108260142

## CIE 1976 Method

The *CIE $L^*a^*b^*$* approximately uniform colourspace defined in 1976 computes the *luminance* $Y$ quantity as follows: [4]

$$
\begin{equation}
Y=\begin{cases}Y_n*\biggl(\cfrac{L^*+16}{116}\biggr)^3 & for\ L^*>\kappa*\epsilon\\
Y_n*\biggl(\cfrac{L^*}{\kappa}\biggr) & for\ L^*<=\kappa*\epsilon
\end{cases}
\end{equation}
$$
where $Y_n$ is the reference white *luminance*.
with
$$
\begin{equation}
\begin{aligned}
\epsilon&\ =\begin{cases}0.008856 & Actual\ CIE\ Standard\\
216\ /\ 24389 & Intent\ of\ the\ CIE\ Standard
\end{cases}\\
\kappa&\ =\begin{cases}903.3 & Actual\ CIE\ Standard\\
24389\ /\ 27 & Intent\ of\ the\ CIE\ Standard
\end{cases}
\end{aligned}
\end{equation}
$$

The original $\epsilon$ and $\kappa$ constants values have been shown to exhibit discontinuity at the junction point of the two functions grafted together to create the *Lightness* $L^*$ function. [5]

[Colour](https://github.com/colour-science/colour/) uses the rational values instead of the decimal values for these constants.

> See Also: The [CIE $L^*a^*b^*$ Colourspace](../models/cie_lab.ipynb) notebook for in-depth information about the *CIE $L^*a^*b^*$* colourspace.

The `colour.luminance_CIE1976` definition is used to compute *Luminance* $Y$:

```python
colour.colorimetry.luminance_CIE1976(37.9856290977)
```

    10.080000000026304

> Note: Input *Lightness* $L^*$ and and $Y_n$ are in domain [0, 100], output *luminance* $Y$ is in domain [0, 100].

Using the `colour.luminance` wrapper definition:

```python
colour.luminance(37.9856290977, method='CIE 1976')
```

    10.080000000026304

```python
colour.luminance(37.9856290977, method='cie1976')
```

    10.080000000026304

## Fairchild and Wyble (2010) Method

```python
colour.colorimetry.luminance_Fairchild2010(24.902290269546651, 1.836)
```

    0.10079999999999999

```python
colour.luminance(24.902290269546651, method='Fairchild 2010', epsilon=1.836)
```

    10.079999999999998

## Fairchild and Chen (2011) Method

```python
colour.colorimetry.luminance_Fairchild2011(26.459509817572265, 0.710)
```

    0.10079999999999999

```python
colour.luminance(26.459509817572265, method='Fairchild 2011', epsilon=0.710)
```

    10.079999999999998

## Bibliography

1. ^ CIE. (n.d.). 17-711 luminance (in a given direction, at a given point of a real or imaginary surface) [Lv; L]. Retrieved July 09, 2014, from http://eilv.cie.co.at/term/711
2. ^ Newhall, S. M., Nickerson, D., & Judd, D. B. (1943). Final report of the OSA subcommittee on the spacing of the munsell colors. JOSA, 33(7), 385. doi:10.1364/JOSA.33.000385
3. ^ ASTM International. (n.d.). ASTM D1535-08e1 Standard Practice for Specifying Color by the Munsell System. doi:10.1520/D1535-08E01
4. ^ Wyszecki, G., & Stiles, W. S. (2000). CIE 1976 (L*u*v*)-Space and Color-Difference Formula. In *Color Science: Concepts and Methods, Quantitative Data and Formulae* (p. 167). Wiley. ISBN:978-0471399186
5. ^ Lindbloom, B. (2003). A Continuity Study of the CIE L* Function. Retrieved February 24, 2014, from http://brucelindbloom.com/LContinuity.html


# C code generation of GR HD Equations

## Authors: Phil Chang & Zach Etienne
### Formatting improvements courtesy Brandon Clark

$\newcommand{\be}{\begin{equation}}$
$\newcommand{\ee}{\end{equation}}$
$\newcommand{\grad}{{\boldsymbol{\nabla}}}$
$\newcommand{\vel}{{\boldsymbol{v}}}$
$\newcommand{\mom}{{\boldsymbol{p}}}$
$\newcommand{\ddt}[1]{{\frac{\partial #1}{\partial t}}}$
$\newcommand{\ddx}[1]{{\frac{\partial #1}{\partial x}}}$
$\newcommand{\state}{{\boldsymbol{\mathcal{U}}}}$
$\newcommand{\charge}{{\boldsymbol{U}}}$
$\newcommand{\psicharge}{{\boldsymbol{\psi}}}$
$\newcommand{\lapse}{\alpha}$
$\newcommand{\shift}{\boldsymbol{\beta}}$
$\newcommand{\rhostar}{{\rho_*}}$
$\newcommand{\tautilde}{{\tilde{\tau}}}$
$\newcommand{\Svectilde}{{\tilde{\boldsymbol{S}}}}$
$\newcommand{\rtgamma}{{\sqrt{\gamma}}}$
$\newcommand{\T}[2]{{T^{#1 #2}}}$
$\newcommand{\uvec}{{\boldsymbol{u}}}$
$\newcommand{\Vvec}{{\boldsymbol{\mathcal{V}}}}$
$\newcommand{\vfluid}{{\boldsymbol{v}_{\rm f}}}$
$\newcommand{\vVal}{{\tilde{\boldsymbol{v}}}}$

$\newcommand{\flux}{{\boldsymbol{\mathcal{F}}}}$
$\newcommand{\fluxV}{{\boldsymbol{F}}}$
$\newcommand{\source}{{\boldsymbol{\mathcal{S}}}}$
$\newcommand{\sourceV}{{\boldsymbol{S}}}$

$\newcommand{\area}{{\boldsymbol{A}}}$
$\newcommand{\normal}{{\hat{\boldsymbol{n}}}}$
$\newcommand{\pt}{{\boldsymbol{p}}}$
$\newcommand{\nb}{{\boldsymbol{n}}}$
$\newcommand{\meshv}{{\boldsymbol{w}}}$
$\newcommand{\facev}{{\boldsymbol{\tilde{w}}_{ij}}}$
$\newcommand{\facer}{{\boldsymbol{\tilde{r}}_{ij}}}$
$\newcommand{\meshr}{{\boldsymbol{r}}}$
$\newcommand{\cmr}{{\boldsymbol{c}}}$

## Introduction:
We start out with the ** GRHD ** equations in conservative form with the state vector $\state=(\rhostar, \Svectilde, \tautilde)$:
\begin{equation}
\ddt{\state} + \grad\cdot\flux = \source,
\end{equation}
where $\rhostar = \lapse\rho\rtgamma u^0$, $\Svectilde = \rhostar h \uvec$, $\tautilde = \lapse^2\rtgamma \T00 - \rhostar$. The associated set of primitive variables are $(\rho, \vel, \epsilon)$, which are the rest mass density, fluid 3-velocity, and internal energy (measured in the rest frame).

The flux, $\flux$ is given by
\begin{equation}
 \flux=(\rhostar \vel, \lapse\rtgamma\T{j}{\beta}g_{\beta i}, \lapse^2\rtgamma\T0j - \rhostar\vel
\end{equation}
where $\vel$ is the 3-velocity, and $\source = (0, \frac 1 2 \lapse\rtgamma \T{\lapse}{\beta}g_{\lapse\beta,i}, s)$ is the source function, and
\begin{equation}
s = \lapse\rtgamma\left[\left(\T00\beta^i\beta^j + 2\T0i\beta^j\right)K_{ij} - \left(\T00\beta^i + \T0i\right)\partial_i\lapse\right]
\end{equation}
The stress energy tensor for a perfect fluid is written as
\begin{equation}
\T{\mu}{\nu} = \rho h u^{\mu} u^{\nu} + P g^{\mu\nu},
\end{equation}
where $h = 1 + \epsilon + P/\rho$ is the specific enthalpy and $u^{\mu}$ are the respective components of the four velocity.

Noting that the mass $\flux$ is defined in terms of $\rhostar$ and $\vel$, we need to first find a mapping between $\vel$ and $u$.

### Alternative formulation

The Athena++ folks have an alternative formulations that might be superior.
Begin with the continuity equation
\begin{equation}
\grad_{\mu}\rho u^{\mu} = 0,
\end{equation}
where $\grad$ is the covariant derivative.  This can be mapped directly to
\begin{equation}
\partial_{0} \sqrt{-g}\rho u^0 + \partial_i\sqrt{-g} \rho u^0 v^i = 0
\end{equation}
which we can identify with $\rhostar = \alpha\rtgamma \rho u^0$ because $\sqrt{-g} = \alpha\rtgamma$.

Now the second equation is conservation of energy-momentum which we write as
\begin{equation}
\grad_{\nu}T^{\nu}_{\mu} = 0
\end{equation}
writing this out we have
\begin{equation}
\partial_0 g_{\mu\alpha}T^{\alpha 0} + \partial_i g_{\mu\alpha}T^{\alpha i} - \Gamma_{\mu\alpha}^{\gamma} g_{\gamma\beta}T^{\alpha\beta} = 0
\end{equation}
Noting that
\begin{equation}
\Gamma^{\alpha}_{\beta\gamma} = \frac 1 2 g^{\alpha\delta}\left(\partial_{\gamma}g_{\beta\delta} + \partial_{\beta}g_{\gamma\delta} - \partial_{\delta}g_{\beta\gamma}\right)
\end{equation}
Writing this all out, we note the last term is
\begin{equation}
\Gamma_{\mu\alpha}^{\gamma} g_{\gamma\beta}T^{\alpha\beta} =
\frac 1 2 g^{\gamma\delta}\left(\partial_{\alpha}g_{\mu\delta} + \partial_{\mu}g_{\alpha \delta} - \partial_{\delta}g_{\mu \alpha}\right) T_{\gamma}^{\alpha} =
\frac 1 2 \left(\partial_{\alpha}g_{\mu\delta} + \partial_{\mu}g_{\alpha \delta} - \partial_{\delta}g_{\mu \alpha}\right)
T^{\alpha\delta}
\end{equation}
We sum over $\alpha$ and $\delta$, but noting that we are antisymmetric in first and last terms in $\alpha$ and $\delta$ in the () but symmetric in $T_{\alpha\delta}$ so we have
\begin{equation}
\Gamma_{\mu\alpha}^{\gamma} g_{\gamma\beta}T^{\alpha\beta} = \frac 1 2 \partial_{\mu}g_{\alpha \delta} T^{\alpha\delta}
\end{equation}

Thus we have
\begin{equation}
\partial_0 T^{0}_{\mu} + \partial_i T^{i}_{\mu} = \frac 1 2 \partial_{\mu}g_{\alpha \delta} T^{\alpha\delta}
\end{equation}
The $\mu = (1,2,3)$, we almost get back the equations in the standard formulation
\begin{equation}
\partial_0 \rho h u^0 u_i + \partial_j T^j_i = \frac 1 2 \partial_{i}g_{\alpha \delta} T^{\alpha\delta},
\end{equation}
which modulo a factors of $\lapse\rtgamma$ in front is the same as the "standard" equations.

The $T^0_0$ term is more interesting. Here we have
\begin{equation}
\partial_0 (\rho h u^0 u_0 + + \partial_j T^j_i = \frac 1 2 \partial_{0}g_{\alpha \delta} T^{\alpha\delta},
\end{equation}

However the disadvantage is that we need the time derivative of the metric.

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

This notebook is organized as follows

1. [Step 1](#mapping): Primitive to Conservative Mapping
1. [Step 2](#zach): Compute $u^0$ from the Valencia 3-velocity (Zach step)
1. [Step 3](#flux): Compute the flux
1. [Step 4](#source): Source Terms
1. [Step 5](#rotation): Rotation
1. [Step 6](#solver): Conservative to Primitive Solver
1. [Step 7](#lorentz): Lorentz Boosts
1. [Step 8](#TmunuSph): Compute $T^{\mu\nu}$ in Cartesian Coordinates
1. [Step 9](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file

# Step 1: Primitive to Conservative Mapping
$$\label{mapping}$$

We want to make a mapping from the primitives to conserved variables following Zach notebook:
\begin{equation}
(\rho, \vel, \epsilon) \rightarrow (\rhostar = \lapse\rho\rtgamma u^0, \Svectilde = \rhostar h \uvec, \tautilde = \lapse^2\rtgamma \T00 - \rhostar).
\end{equation}

```python
import GRHD.equations as Ge
import indexedexp as ixp
import sympy as sp
from outputC import *

# declare gammaDD
gammaDD = ixp.zerorank2()
components = ["xx", "xy", "xz", "yy", "yz", "zz"]
names = ""
for comp in components :
    names = names + "mi.gamDD{0} ".format(comp)

gxx, gxy, gxz, gyy, gyz, gzz = sp.symbols( names)

gammaDD[0][0] = gxx
gammaDD[0][1] = gxy
gammaDD[0][2] = gxz
gammaDD[1][0] = gxy
gammaDD[1][1] = gyy
gammaDD[1][2] = gyz
gammaDD[2][0] = gxz
gammaDD[2][1] = gyz
gammaDD[2][2] = gzz

#declare alpha
alpha = sp.symbols( "mi.alpha")

#declare beta
betaU = ixp.zerorank1()
for i, comp in enumerate(["X", "Y", "Z"]) :
    betaU[i] = sp.symbols( "mi.beta{0}".format(comp), real=True)

#now get the primitives
rho_b, epsilon, P = sp.symbols("rho ie p")

#get the 3-velocities
vU = ixp.zerorank1()
for i, comp in enumerate( ["vx", "vy", "vz"]) :
    vU[i] = sp.symbols("{0}".format(comp))

Ge.u4U_in_terms_of_vU__rescale_vU_by_applying_speed_limit(alpha,betaU,gammaDD, vU)

u4U = Ge.u4U_ito_vU
# Zach says: Probably want to adopt speed-limited vU[i], Ge.rescaledvU[i], here, a la:
# for i in range(3):
# ... vU[i] = Ge.rescaledvU[i]

# First compute stress-energy tensor T4UU and T4UD:
Ge.compute_T4UU(gammaDD,betaU,alpha, rho_b,P,epsilon,u4U)
Ge.compute_T4UD(gammaDD,betaU,alpha, Ge.T4UU)

# Next sqrt(gamma)
Ge.compute_sqrtgammaDET(gammaDD)

# Compute conservative variables in terms of primitive variables
Ge.compute_rho_star( alpha, Ge.sqrtgammaDET, rho_b, u4U)
Ge.compute_tau_tilde(alpha, Ge.sqrtgammaDET, Ge.T4UU,Ge.rho_star)
Ge.compute_S_tildeD( alpha, Ge.sqrtgammaDET, Ge.T4UD)

# Zach says: Why only output u^x? Debugging reasons?
outputC([u4U[1],     Ge.rho_star, Ge.S_tildeD[0], Ge.S_tildeD[1], Ge.S_tildeD[2], Ge.tau_tilde],
        ["u4U1", "con[iRhoStar]",     "con[iSx]",     "con[iSy]",     "con[iSz]",  "con[iTau]"],
        filename="NRPY+prim2Con.h", params="outCverbose=False")
!cat NRPY+prim2Con.h

outputC([Ge.sqrtgammaDET*alpha], ["detg"], filename="NRPY+detg.h")
!cat NRPY+detg.h

gammaUU, gammabarDet = ixp.symm_matrix_inverter3x3(gammaDD)
outputC([gammaUU[0][0],gammaUU[0][1],gammaUU[0][2],gammaUU[1][1],gammaUU[1][2],gammaUU[2][2]],
        [    "gamUUxx",    "gamUUxy",    "gamUUxz",    "gamUUyy",    "gamUUyz",    "gamUUzz"],
        filename="NRPY+gamUU.h")
!cat NRPY+gamUU.h
```

    Wrote to file "NRPY+prim2Con.h"
    {
       const double tmp_0 = (1.0/((GAMMA_SPEED_LIMIT)*(GAMMA_SPEED_LIMIT)));
       const double tmp_3 = (1.0/((mi.alpha)*(mi.alpha)));
       const double tmp_4 = mi.betaX + vx;
       const double tmp_5 = mi.gamDDxx*tmp_3*((tmp_4)*(tmp_4));
       const double tmp_7 = mi.betaY + vy;
       const double tmp_8 = mi.gamDDyy*tmp_3*((tmp_7)*(tmp_7));
       const double tmp_10 = mi.betaZ + vz;
       const double tmp_11 = mi.gamDDzz*((tmp_10)*(tmp_10))*tmp_3;
       const double tmp_14 = mi.gamDDxy*tmp_3*tmp_4*tmp_7;
       const double tmp_15 = mi.gamDDxz*tmp_10*tmp_3*tmp_4;
       const double tmp_16 = mi.gamDDyz*tmp_10*tmp_3*tmp_7;
       const double tmp_20 = (1.0/2.0)*fabs(-tmp_0 - tmp_11 - 2*tmp_14 - 2*tmp_15 - 2*tmp_16 - tmp_5 - tmp_8 + 1);
       const double tmp_21 = (1.0/2.0)*tmp_0 - 1.0/2.0*tmp_11 - tmp_14 - tmp_15 - tmp_16 + tmp_20 - 1.0/2.0*tmp_5 - 1.0/2.0*tmp_8 + 1.0/2.0;
       const double tmp_22 = (1.0/sqrt(tmp_21));
       const double tmp_23 = sqrt((-1.0/2.0*tmp_0 + (1.0/2.0)*tmp_11 + tmp_14 + tmp_15 + tmp_16 - tmp_20 + (1.0/2.0)*tmp_5 + (1.0/2.0)*tmp_8 + 1.0/2.0)/(TINYDOUBLE + tmp_11 + 2*tmp_14 + 2*tmp_15 + 2*tmp_16 + tmp_5 + tmp_8));
       const double tmp_24 = -mi.betaX + tmp_23*tmp_4;
       const double tmp_25 = sqrt(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*((mi.gamDDyz)*(mi.gamDDyz)) - ((mi.gamDDxy)*(mi.gamDDxy))*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - ((mi.gamDDxz)*(mi.gamDDxz))*mi.gamDDyy);
       const double tmp_26 = rho*tmp_22*tmp_25;
       const double tmp_27 = p*tmp_3;
       const double tmp_28 = rho*tmp_3*(ie + p/rho + 1)/tmp_21;
       const double tmp_29 = -tmp_27 + tmp_28;
       const double tmp_30 = mi.betaX*tmp_27 + tmp_24*tmp_28;
       const double tmp_31 = mi.betaY*tmp_27 + tmp_28*(-mi.betaY + tmp_23*tmp_7);
       const double tmp_32 = mi.betaZ*tmp_27 + tmp_28*(-mi.betaZ + tmp_10*tmp_23);
       const double tmp_33 = mi.alpha*tmp_25;
       u4U1 = tmp_22*tmp_24/mi.alpha;
       con[iRhoStar] = tmp_26;
       con[iSx] = tmp_33*(mi.gamDDxx*tmp_30 + mi.gamDDxy*tmp_31 + mi.gamDDxz*tmp_32 + tmp_29*(mi.betaX*mi.gamDDxx + mi.betaY*mi.gamDDxy + mi.betaZ*mi.gamDDxz));
       con[iSy] = tmp_33*(mi.gamDDxy*tmp_30 + mi.gamDDyy*tmp_31 + mi.gamDDyz*tmp_32 + tmp_29*(mi.betaX*mi.gamDDxy + mi.betaY*mi.gamDDyy + mi.betaZ*mi.gamDDyz));
       con[iSz] = tmp_33*(mi.gamDDxz*tmp_30 + mi.gamDDyz*tmp_31 + mi.gamDDzz*tmp_32 + tmp_29*(mi.betaX*mi.gamDDxz + mi.betaY*mi.gamDDyz + mi.betaZ*mi.gamDDzz));
       con[iTau] = ((mi.alpha)*(mi.alpha))*tmp_25*tmp_29 - tmp_26;
    }
    Wrote to file "NRPY+detg.h"
    /*
     *  Original SymPy expression:
     *  "detg = mi.alpha*sqrt(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy)"
     */
    {
       detg = mi.alpha*sqrt(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*((mi.gamDDyz)*(mi.gamDDyz)) - ((mi.gamDDxy)*(mi.gamDDxy))*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - ((mi.gamDDxz)*(mi.gamDDxz))*mi.gamDDyy);
    }
    Wrote to file "NRPY+gamUU.h"
    /*
     *  Original SymPy expressions:
     *  "[gamUUxx = (mi.gamDDyy*mi.gamDDzz - mi.gamDDyz**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy),
     *    gamUUxy = (-mi.gamDDxy*mi.gamDDzz + mi.gamDDxz*mi.gamDDyz)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy),
     *    gamUUxz = (mi.gamDDxy*mi.gamDDyz - mi.gamDDxz*mi.gamDDyy)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy),
     *    gamUUyy = (mi.gamDDxx*mi.gamDDzz - mi.gamDDxz**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy),
     *    gamUUyz = (-mi.gamDDxx*mi.gamDDyz + mi.gamDDxy*mi.gamDDxz)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy),
     *    gamUUzz = (mi.gamDDxx*mi.gamDDyy - mi.gamDDxy**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy)]"
     */
    {
       const double tmp_5 = (1.0/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*((mi.gamDDyz)*(mi.gamDDyz)) - ((mi.gamDDxy)*(mi.gamDDxy))*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - ((mi.gamDDxz)*(mi.gamDDxz))*mi.gamDDyy));
       gamUUxx = tmp_5*(mi.gamDDyy*mi.gamDDzz - ((mi.gamDDyz)*(mi.gamDDyz)));
       gamUUxy = tmp_5*(-mi.gamDDxy*mi.gamDDzz + mi.gamDDxz*mi.gamDDyz);
       gamUUxz = tmp_5*(mi.gamDDxy*mi.gamDDyz - mi.gamDDxz*mi.gamDDyy);
       gamUUyy = tmp_5*(mi.gamDDxx*mi.gamDDzz - ((mi.gamDDxz)*(mi.gamDDxz)));
       gamUUyz = tmp_5*(-mi.gamDDxx*mi.gamDDyz + mi.gamDDxy*mi.gamDDxz);
       gamUUzz = tmp_5*(mi.gamDDxx*mi.gamDDyy - ((mi.gamDDxy)*(mi.gamDDxy)));
    }

# Step 3: Compute the flux
$$\label{flux}$$

The fluxes are as follows
\begin{equation}
\frac{\partial}{\partial t}
\begin{pmatrix}
\rhostar\\
\Svectilde\\
\tautilde
\end{pmatrix} + \frac{\partial}{\partial x^j}\begin{pmatrix} \rhostar v^j\\
\lapse\rtgamma T^j_i\\ \lapse^2\rtgamma T^{0j} - \rhostar v^j
\end{pmatrix}  = \begin{pmatrix} 0 \\ \frac 1 2 \lapse\rtgamma T^{\alpha\beta}g_{\alpha\beta,i} \\ s \end{pmatrix}
\end{equation}
so the flux is
\begin{equation}
\mathcal{F} = \begin{pmatrix} \rhostar v^i \\ \lapse\rtgamma T^i_k \\ \lapse^2\rtgamma T^{0i} - \rhostar v^i
\end{pmatrix}
\end{equation}
In the moving-mesh formalism, the flux is just taken along the x directions so we have
\begin{equation}
\mathcal{F} = \begin{pmatrix} \rhostar v^1 \\ \lapse\rtgamma T^1_k \\ \lapse^2\rtgamma T^{01} - \rhostar v^1
\end{pmatrix}
\end{equation}
Note that we will need to rotate $T^{\mu\nu}$ and $g_{\mu\nu}$ to get the right orientation.
In order to do this, we must first compute the stress energy tensor:
\begin{equation}
T^{\mu\nu} = \rho h u^{\mu}u^{\nu} + Pg^{\mu\nu} = \rho h (u^0)^2v^iv^j + P g^{\mu\nu}
\end{equation}

```python
# Next compute fluxes of conservative variables
Ge.compute_rho_star_fluxU(                            vU,        Ge.rho_star)
Ge.compute_tau_tilde_fluxU(alpha, Ge.sqrtgammaDET,    vU,Ge.T4UU,Ge.rho_star)
Ge.compute_S_tilde_fluxUD( alpha, Ge.sqrtgammaDET,       Ge.T4UD)

normD = ixp.zerorank1()
normD[0], normD[1], normD[2] = sp.symbols("norm[0] norm[1] norm[2]", real=True)

faceVelU = ixp.zerorank1()
faceVelU[0], faceVelU[1], faceVelU[2] = sp.symbols("faceVelocity[0] faceVelocity[1] faceVelocity[2]", real=True)
# Zach says: don't forget to limit the velocities after they are computed!

faceVelNorm = sp.sympify(0)
for i in range(3) :
    faceVelNorm += normD[i]*faceVelU[i]

exprArray = []
nameArray = []
exprArray.append( Ge.rho_star)
nameArray.append( "temp_rho_star")
exprArray.append( Ge.T4UU[0][1])
nameArray.append( "temp_T4UU01")

rho_star_flux = sp.sympify(0)
for i in range(3) :
    rho_star_flux += Ge.rho_star_fluxU[i]*normD[i]
rho_star_flux -= Ge.rho_star*faceVelNorm

exprArray.append( rho_star_flux)
nameArray.append( "flux[iRhoStar]")

tau_tilde_flux = sp.sympify(0)
for i in range(3) :
    tau_tilde_flux += Ge.tau_tilde_fluxU[i]*normD[i]

tau_tilde_flux -= Ge.tau_tilde*faceVelNorm

S_tilde_fluxD = ixp.zerorank1()

for i in range(3) :
    S_tilde_fluxD[i] -= Ge.S_tildeD[i]*faceVelNorm
    for j in range(3) :
        S_tilde_fluxD[i] += Ge.S_tilde_fluxUD[j][i]*normD[j]

for j, comp in enumerate(["x","y", "z"]) :
    exprArray.append( S_tilde_fluxD[j])
    nameArray.append( "flux[iS{0}]".format(comp))

exprArray.append( tau_tilde_flux)
nameArray.append( "flux[iTau]")

#for expr, name in zip( exprArray, nameArray) :
#    print( name)
outputC(exprArray, nameArray, filename="NRPY+calFlux.h", params="outCverbose=False")

!cat NRPY+calFlux.h
```

    Wrote to file "NRPY+calFlux.h"
    {
       const double tmp_6 = mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*((mi.gamDDyz)*(mi.gamDDyz)) - ((mi.gamDDxy)*(mi.gamDDxy))*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - ((mi.gamDDxz)*(mi.gamDDxz))*mi.gamDDyy;
       const double tmp_7 = sqrt(tmp_6);
       const double tmp_8 = (1.0/((GAMMA_SPEED_LIMIT)*(GAMMA_SPEED_LIMIT)));
       const double tmp_11 = (1.0/((mi.alpha)*(mi.alpha)));
       const double tmp_12 = mi.betaX + vx;
       const double tmp_13 = mi.gamDDxx*tmp_11*((tmp_12)*(tmp_12));
       const double tmp_15 = mi.betaY + vy;
       const double tmp_16 = mi.gamDDyy*tmp_11*((tmp_15)*(tmp_15));
       const double tmp_18 = mi.betaZ + vz;
       const double tmp_19 = mi.gamDDzz*tmp_11*((tmp_18)*(tmp_18));
       const double tmp_22 = tmp_11*tmp_12*tmp_15;
       const double tmp_24 = mi.gamDDxz*tmp_11*tmp_12*tmp_18;
       const double tmp_25 = mi.gamDDyz*tmp_11*tmp_15*tmp_18;
       const double tmp_26 = 2*mi.gamDDxy*tmp_22;
       const double tmp_29 = (1.0/2.0)*fabs(-tmp_13 - tmp_16 - tmp_19 - 2*tmp_24 - 2*tmp_25 - tmp_26 - tmp_8 + 1);
       const double tmp_30 = -mi.gamDDxy*tmp_22 - 1.0/2.0*tmp_13 - 1.0/2.0*tmp_16 - 1.0/2.0*tmp_19 - tmp_24 - tmp_25 + tmp_29 + (1.0/2.0)*tmp_8 + 1.0/2.0;
       const double tmp_31 = rho*tmp_7/sqrt(tmp_30);
       const double tmp_32 = p*tmp_11;
       const double tmp_33 = sqrt((mi.gamDDxy*tmp_22 + (1.0/2.0)*tmp_13 + (1.0/2.0)*tmp_16 + (1.0/2.0)*tmp_19 + tmp_24 + tmp_25 - tmp_29 - 1.0/2.0*tmp_8 + 1.0/2.0)/(TINYDOUBLE + tmp_13 + tmp_16 + tmp_19 + 2*tmp_24 + 2*tmp_25 + tmp_26));
       const double tmp_34 = -mi.betaX + tmp_12*tmp_33;
       const double tmp_35 = rho*tmp_11*(ie + p/rho + 1)/tmp_30;
       const double tmp_36 = tmp_34*tmp_35;
       const double tmp_37 = mi.betaX*tmp_32 + tmp_36;
       const double tmp_41 = faceVelocity[0]*norm[0] + faceVelocity[1]*norm[1] + faceVelocity[2]*norm[2];
       const double tmp_42 = mi.betaX*mi.gamDDxx + mi.betaY*mi.gamDDxy + mi.betaZ*mi.gamDDxz;
       const double tmp_43 = -tmp_32 + tmp_35;
       const double tmp_44 = -mi.betaY + tmp_15*tmp_33;
       const double tmp_46 = mi.betaY*tmp_32 + tmp_35*tmp_44;
       const double tmp_47 = -mi.betaZ + tmp_18*tmp_33;
       const double tmp_48 = mi.betaZ*tmp_32 + tmp_35*tmp_47;
       const double tmp_49 = mi.alpha*tmp_7;
       const double tmp_50 = tmp_41*tmp_49;
       const double tmp_51 = (1.0/(tmp_6));
       const double tmp_52 = p*(-((mi.betaX)*(mi.betaX))*tmp_11 + tmp_51*(mi.gamDDyy*mi.gamDDzz - ((mi.gamDDyz)*(mi.gamDDyz)))) + ((tmp_34)*(tmp_34))*tmp_35;
       const double tmp_54 = p*(-mi.betaX*mi.betaY*tmp_11 + tmp_51*(-mi.gamDDxy*mi.gamDDzz + mi.gamDDxz*mi.gamDDyz)) + tmp_36*tmp_44;
       const double tmp_56 = p*(-mi.betaX*mi.betaZ*tmp_11 + tmp_51*(mi.gamDDxy*mi.gamDDyz - mi.gamDDxz*mi.gamDDyy)) + tmp_36*tmp_47;
       const double tmp_58 = norm[0]*tmp_49;
       const double tmp_59 = p*(-((mi.betaY)*(mi.betaY))*tmp_11 + tmp_51*(mi.gamDDxx*mi.gamDDzz - ((mi.gamDDxz)*(mi.gamDDxz)))) + tmp_35*((tmp_44)*(tmp_44));
       const double tmp_60 = p*(-mi.betaY*mi.betaZ*tmp_11 + tmp_51*(-mi.gamDDxx*mi.gamDDyz + mi.gamDDxy*mi.gamDDxz)) + tmp_35*tmp_44*tmp_47;
       const double tmp_61 = norm[1]*tmp_49;
       const double tmp_62 = p*(-((mi.betaZ)*(mi.betaZ))*tmp_11 + tmp_51*(mi.gamDDxx*mi.gamDDyy - ((mi.gamDDxy)*(mi.gamDDxy)))) + tmp_35*((tmp_47)*(tmp_47));
       const double tmp_63 = norm[2]*tmp_49;
       const double tmp_64 = mi.betaX*mi.gamDDxy + mi.betaY*mi.gamDDyy + mi.betaZ*mi.gamDDyz;
       const double tmp_66 = mi.betaX*mi.gamDDxz + mi.betaY*mi.gamDDyz + mi.betaZ*mi.gamDDzz;
       const double tmp_67 = ((mi.alpha)*(mi.alpha))*tmp_7;
       temp_rho_star = tmp_31;
       temp_T4UU01 = tmp_37;
       flux[iRhoStar] = norm[0]*tmp_31*vx + norm[1]*tmp_31*vy + norm[2]*tmp_31*vz - tmp_31*tmp_41;
       flux[iSx] = -tmp_50*(mi.gamDDxx*tmp_37 + mi.gamDDxy*tmp_46 + mi.gamDDxz*tmp_48 + tmp_42*tmp_43) + tmp_58*(mi.gamDDxx*tmp_52 + mi.gamDDxy*tmp_54 + mi.gamDDxz*tmp_56 + tmp_37*tmp_42) + tmp_61*(mi.gamDDxx*tmp_54 + mi.gamDDxy*tmp_59 + mi.gamDDxz*tmp_60 + tmp_42*tmp_46) + tmp_63*(mi.gamDDxx*tmp_56 + mi.gamDDxy*tmp_60 + mi.gamDDxz*tmp_62 + tmp_42*tmp_48);
       flux[iSy] = -tmp_50*(mi.gamDDxy*tmp_37 + mi.gamDDyy*tmp_46 + mi.gamDDyz*tmp_48 + tmp_43*tmp_64) + tmp_58*(mi.gamDDxy*tmp_52 + mi.gamDDyy*tmp_54 + mi.gamDDyz*tmp_56 + tmp_37*tmp_64) + tmp_61*(mi.gamDDxy*tmp_54 + mi.gamDDyy*tmp_59 + mi.gamDDyz*tmp_60 + tmp_46*tmp_64) + tmp_63*(mi.gamDDxy*tmp_56 + mi.gamDDyy*tmp_60 + mi.gamDDyz*tmp_62 + tmp_48*tmp_64);
       flux[iSz] = -tmp_50*(mi.gamDDxz*tmp_37 + mi.gamDDyz*tmp_46 + mi.gamDDzz*tmp_48 + tmp_43*tmp_66) + tmp_58*(mi.gamDDxz*tmp_52 + mi.gamDDyz*tmp_54 + mi.gamDDzz*tmp_56 + tmp_37*tmp_66) + tmp_61*(mi.gamDDxz*tmp_54 + mi.gamDDyz*tmp_59 + mi.gamDDzz*tmp_60 + tmp_46*tmp_66) + tmp_63*(mi.gamDDxz*tmp_56 + mi.gamDDyz*tmp_60 + mi.gamDDzz*tmp_62 + tmp_48*tmp_66);
       flux[iTau] = norm[0]*(-tmp_31*vx + tmp_37*tmp_67) + norm[1]*(-tmp_31*vy + tmp_46*tmp_67) + norm[2]*(-tmp_31*vz + tmp_48*tmp_67) - tmp_41*(-tmp_31 + tmp_43*tmp_67);
    }

# Step 4: Source Terms
$$\label{source}$$

The sources terms are for mass, momentum and energy are:
\begin{equation}
\source = (0, \frac 1 2 \lapse\rtgamma \T{\alpha}{\beta}g_{\alpha\beta,i}, s),
\end{equation}
For a time stationary metric $s\neq 0$, so we will ignore this until the next section.  As for the rest, we need to define derivatives of the metric.  Suppose I have done this already.  Then the code for the source terms is:

```python
# FIXME: Assume static spacetime with KDD = betaU = betaU_dD = 0
KDD = ixp.zerorank2()
betaU = ixp.zerorank1()
betaU_dD = ixp.zerorank2()

# Set+evaluate derivatives of alpha, performing 2nd-order finite difference
alpha_dD = ixp.zerorank1()
h = sp.symbols("h")
for i in range(3) :
    alpha_plus, alpha_minus = sp.symbols("mi_plus[{0}].alpha mi_minus[{0}].alpha".format(i))
    alpha_dD[i] = (alpha_plus - alpha_minus)/(2*h)

# Set+evaluate derivatives of gamma_{ij}, performing 2nd-order finite difference
gammaDD_dD = ixp.zerorank3()
components = ["xx", "xy", "xz", "yy", "yz", "zz"]
for i in range(3) :
    names_plus = ""
    names_minus = ""
    for comp in components :
        names_plus  = names_plus  +  "mi_plus[{0}].gamDD{1} ".format(i, comp)
        names_minus = names_minus + "mi_minus[{0}].gamDD{1} ".format(i, comp)

    gxx_plus, gxy_plus, gxz_plus, gyy_plus, gyz_plus, gzz_plus = sp.symbols( names_plus)
    gxx_minus, gxy_minus, gxz_minus, gyy_minus, gyz_minus, gzz_minus = sp.symbols( names_minus)

    gammaDD_dD[0][0][i] = (gxx_plus - gxx_minus)/(2*h)
    gammaDD_dD[0][1][i] = (gxy_plus - gxy_minus)/(2*h)
    gammaDD_dD[0][2][i] = (gxz_plus - gxz_minus)/(2*h)
    gammaDD_dD[1][0][i] = (gxy_plus - gxy_minus)/(2*h)
    gammaDD_dD[1][1][i] = (gyy_plus - gyy_minus)/(2*h)
    gammaDD_dD[1][2][i] = (gyz_plus - gyz_minus)/(2*h)
    gammaDD_dD[2][0][i] = (gxz_plus - gxz_minus)/(2*h)
    gammaDD_dD[2][1][i] = (gyz_plus - gyz_minus)/(2*h)
    gammaDD_dD[2][2][i] = (gzz_plus - gzz_minus)/(2*h)

# Compute g_{mu nu, i} based on ADM quantities & derivatives defined above
Ge.compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDD_dD,betaU_dD,alpha_dD)

# Compute source terms for tau tilde & S tilde:
Ge.compute_s_source_term(KDD,betaU,alpha, Ge.sqrtgammaDET,alpha_dD,  Ge.T4UU)
Ge.compute_S_tilde_source_termD(   alpha, Ge.sqrtgammaDET,Ge.g4DD_zerotimederiv_dD, Ge.T4UU)

exprArray = []
nameArray = []

#momentum terms
for i in range(3) :
    exprArray.append( Ge.S_tilde_source_termD[i])
    nameArray.append( "vSource[{0}]".format(i))

#tau term
exprArray.append( Ge.s_source_term)
nameArray.append( "eSource")
outputC( exprArray, nameArray, filename="NRPY+calSources.h", params="outCverbose=False")

!cat NRPY+calSources.h
```

    Wrote to file "NRPY+calSources.h"
    {
       const double tmp_0 = (1.0/(h));
       const double tmp_1 = (1.0/2.0)*tmp_0;
       const double tmp_2 = tmp_1*(-mi_minus[0].alpha + mi_plus[0].alpha);
       const double tmp_9 = mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*((mi.gamDDyz)*(mi.gamDDyz)) - ((mi.gamDDxy)*(mi.gamDDxy))*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - ((mi.gamDDxz)*(mi.gamDDxz))*mi.gamDDyy;
       const double tmp_10 = sqrt(tmp_9);
       const double tmp_12 = (1.0/((mi.alpha)*(mi.alpha)));
       const double tmp_13 = p*tmp_12;
       const double tmp_14 = (1.0/((GAMMA_SPEED_LIMIT)*(GAMMA_SPEED_LIMIT)));
       const double tmp_16 = mi.betaX + vx;
       const double tmp_17 = mi.gamDDxx*tmp_12*((tmp_16)*(tmp_16));
       const double tmp_19 = mi.betaY + vy;
       const double tmp_20 = mi.gamDDyy*tmp_12*((tmp_19)*(tmp_19));
       const double tmp_22 = mi.betaZ + vz;
       const double tmp_23 = mi.gamDDzz*tmp_12*((tmp_22)*(tmp_22));
       const double tmp_26 = tmp_12*tmp_16*tmp_19;
       const double tmp_28 = mi.gamDDxz*tmp_12*tmp_16*tmp_22;
       const double tmp_29 = mi.gamDDyz*tmp_12*tmp_19*tmp_22;
       const double tmp_30 = 2*mi.gamDDxy*tmp_26;
       const double tmp_33 = (1.0/2.0)*fabs(-tmp_14 - tmp_17 - tmp_20 - tmp_23 - 2*tmp_28 - 2*tmp_29 - tmp_30 + 1);
       const double tmp_34 = rho*tmp_12*(ie + p/rho + 1)/(-mi.gamDDxy*tmp_26 + (1.0/2.0)*tmp_14 - 1.0/2.0*tmp_17 - 1.0/2.0*tmp_20 - 1.0/2.0*tmp_23 - tmp_28 - tmp_29 + tmp_33 + 1.0/2.0);
       const double tmp_35 = ((mi.alpha)*(mi.alpha))*tmp_10*(-tmp_13 + tmp_34);
       const double tmp_36 = (1.0/(tmp_9));
       const double tmp_37 = sqrt((mi.gamDDxy*tmp_26 - 1.0/2.0*tmp_14 + (1.0/2.0)*tmp_17 + (1.0/2.0)*tmp_20 + (1.0/2.0)*tmp_23 + tmp_28 + tmp_29 - tmp_33 + 1.0/2.0)/(TINYDOUBLE + tmp_17 + tmp_20 + tmp_23 + 2*tmp_28 + 2*tmp_29 + tmp_30));
       const double tmp_38 = -mi.betaX + tmp_16*tmp_37;
       const double tmp_39 = mi.alpha*tmp_10;
       const double tmp_40 = (1.0/4.0)*tmp_0*tmp_39;
       const double tmp_41 = tmp_40*(p*(-((mi.betaX)*(mi.betaX))*tmp_12 + tmp_36*(mi.gamDDyy*mi.gamDDzz - ((mi.gamDDyz)*(mi.gamDDyz)))) + tmp_34*((tmp_38)*(tmp_38)));
       const double tmp_42 = -mi.betaY + tmp_19*tmp_37;
       const double tmp_43 = tmp_40*(p*(-((mi.betaY)*(mi.betaY))*tmp_12 + tmp_36*(mi.gamDDxx*mi.gamDDzz - ((mi.gamDDxz)*(mi.gamDDxz)))) + tmp_34*((tmp_42)*(tmp_42)));
       const double tmp_44 = -mi.betaZ + tmp_22*tmp_37;
       const double tmp_45 = tmp_40*(p*(-((mi.betaZ)*(mi.betaZ))*tmp_12 + tmp_36*(mi.gamDDxx*mi.gamDDyy - ((mi.gamDDxy)*(mi.gamDDxy)))) + tmp_34*((tmp_44)*(tmp_44)));
       const double tmp_47 = tmp_34*tmp_38;
       const double tmp_48 = tmp_1*tmp_39;
       const double tmp_49 = tmp_48*(p*(-mi.betaX*mi.betaY*tmp_12 + tmp_36*(-mi.gamDDxy*mi.gamDDzz + mi.gamDDxz*mi.gamDDyz)) + tmp_42*tmp_47);
       const double tmp_50 = tmp_48*(p*(-mi.betaX*mi.betaZ*tmp_12 + tmp_36*(mi.gamDDxy*mi.gamDDyz - mi.gamDDxz*mi.gamDDyy)) + tmp_44*tmp_47);
       const double tmp_52 = tmp_48*(p*(-mi.betaY*mi.betaZ*tmp_12 + tmp_36*(-mi.gamDDxx*mi.gamDDyz + mi.gamDDxy*mi.gamDDxz)) + tmp_34*tmp_42*tmp_44);
       const double tmp_53 = tmp_1*(-mi_minus[1].alpha + mi_plus[1].alpha);
       const double tmp_54 = tmp_1*(-mi_minus[2].alpha + mi_plus[2].alpha);
       vSource[0] = -tmp_2*tmp_35 + tmp_41*(-mi_minus[0].gamDDxx + mi_plus[0].gamDDxx) + tmp_43*(-mi_minus[0].gamDDyy + mi_plus[0].gamDDyy) + tmp_45*(-mi_minus[0].gamDDzz + mi_plus[0].gamDDzz) + tmp_49*(-mi_minus[0].gamDDxy + mi_plus[0].gamDDxy) + tmp_50*(-mi_minus[0].gamDDxz + mi_plus[0].gamDDxz) + tmp_52*(-mi_minus[0].gamDDyz + mi_plus[0].gamDDyz);
       vSource[1] = -tmp_35*tmp_53 + tmp_41*(-mi_minus[1].gamDDxx + mi_plus[1].gamDDxx) + tmp_43*(-mi_minus[1].gamDDyy + mi_plus[1].gamDDyy) + tmp_45*(-mi_minus[1].gamDDzz + mi_plus[1].gamDDzz) + tmp_49*(-mi_minus[1].gamDDxy + mi_plus[1].gamDDxy) + tmp_50*(-mi_minus[1].gamDDxz + mi_plus[1].gamDDxz) + tmp_52*(-mi_minus[1].gamDDyz + mi_plus[1].gamDDyz);
       vSource[2] = -tmp_35*tmp_54 + tmp_41*(-mi_minus[2].gamDDxx + mi_plus[2].gamDDxx) + tmp_43*(-mi_minus[2].gamDDyy + mi_plus[2].gamDDyy) + tmp_45*(-mi_minus[2].gamDDzz + mi_plus[2].gamDDzz) + tmp_49*(-mi_minus[2].gamDDxy + mi_plus[2].gamDDxy) + tmp_50*(-mi_minus[2].gamDDxz + mi_plus[2].gamDDxz) + tmp_52*(-mi_minus[2].gamDDyz + mi_plus[2].gamDDyz);
       eSource = tmp_39*(tmp_2*(-mi.betaX*tmp_13 - tmp_47) + tmp_53*(-mi.betaY*tmp_13 - tmp_34*tmp_42) + tmp_54*(-mi.betaZ*tmp_13 - tmp_34*tmp_44));
    }

# Step 6: Conservative to Primitive Solver
$$\label{solver}$$

We now discuss the reverse mapping from conservative to primitive variables.
Given the lapse, shift vector and $\rtgamma$, the mapping between primitive and conserved variable is straightforward.  However, the reverse is not as simple.  In GRMHD, the conservative to primitive solver is amplified by the inclusion of the magnetic field, leading to rather sophisticated root finding strategies.  The failure rates of these algorithms are low (??), but since this algorithm may be executed several times per timestep for every gridpoint, even a low failure can give unacceptable collective failure rates.  However, for purely polytropic equations of state, e.g., $P\propto\rho^{\Gamma_1}$, the convervative to primitive variable solver is greatly simplified.

To construct the conservative-to-primitive variable solver, we restrict ourselves to polytropic equations of states
\begin{equation}
P = P_0\left(\frac{\rho}{\rho_0}\right)^{\Gamma_1} \quad\textrm{and}\quad \epsilon = \epsilon_0\left(\frac{\rho}{\rho_0}\right)^{\Gamma_1-1},
\end{equation}
where $P_0$, $\rho_0$, and $\epsilon_0$ are the fiducial pressure, density, and internal energy, and we have used the relation $P = (\Gamma_1 - 1)\rho\epsilon$.

For such a polytropic equation of state, the energy equation is redundant and effectively we are only concerned with the continuity and momentum equations. The conservative variables of concern are $\rhostar$ and $\Svectilde$.  Noting that the shift, $\alpha$, and $\rtgamma$ are provided by the Einsteins field equation solver, we can write
\begin{equation}
u^0 = \frac{\rhostar}{\alpha\rtgamma\rho} = u^0(\rho)  \quad\textrm{and}\quad \uvec = \frac{\Svectilde}{\alpha\rtgamma\rho h} = \uvec(\rho).
\end{equation}
Noting that the four velocity $u^2 = g_{\mu\nu}u^{\mu}u^{\nu} = g^{00}u^0u^0 + 2g^{0i}u^0\uvec^i + g_{ij}\uvec^i\uvec^j = -1$, we have
\begin{equation}
 0 = f(\rho)\equiv \alpha^2\gamma\rho^2h^2 + \left(-\lapse^2 + \shift\cdot\shift\right)\rhostar^2h^2 + 2h\rhostar\shift\cdot\Svectilde + \Svectilde\cdot\Svectilde,
\end{equation}
which is an implicit equation of either $\rho$ or $u^0$, where $h(\rho = \rhostar/(\alpha\rtgamma u^0)) = 1 + \gamma_1 \epsilon$ which can be inverted by standard nonlinear root finding algorithms, e.g., Newton-raphson.

We put this all together to define a function, $f(\rho)$, whose root is zero that we will find via Newton-raphson.

Several checks must be performed:

1. $\rhostar > 0$ : This check is performed at the very beginning

2. $\rho > \rho_{\rm min}$ : This check is performed after the fact

3. $u_0 < \alpha^{-1}\Gamma_{\rm max}$ : This check is performed after the fact as well

```python
DIM = 3
# Declare rank-1 contravariant ("v") vector
vU = ixp.declarerank1("vU")
shiftU = ixp.zerorank1()
rho, gamma1 = sp.symbols("rho gamma")
Sx, Sy, Sz = sp.symbols("con[iSx] con[iSy] con[iSz]")
p0, rho0, rhostar = sp.symbols("p_0 rho_0 rhostar")
# Declare rank-2 covariant gmunu
#gammaDD = ixp.declarerank2("gammaDD","sym01")
StildeD = ixp.declarerank1("StildeD")
lapse, beta_x, beta_y, beta_z = sp.symbols( "mi.alpha mi.betaX mi.betaY mi.betaZ")
rtgamma = Ge.sqrtgammaDET

shiftU[0] = beta_x
shiftU[1] = beta_y
shiftU[2] = beta_z

StildeD[0] = Sx
StildeD[1] = Sy
StildeD[2] = Sz

# gamma = rtgamma*rtgamma <- unused
lapse2 = lapse*lapse
uU0 = rhostar/(lapse*rtgamma*rho)
epsilon = p0/rho0*(rho/rho0)**(gamma1 - 1)/(gamma1 - 1)
h = 1 + gamma1*epsilon

beta2 = 0.

for i in range(DIM) :
    for j in range(DIM) :
        beta2 += gammaDD[i][j] * shiftU[i]*shiftU[j]

betaDotStilde = 0
for i in range(DIM) :
    betaDotStilde += shiftU[i]*StildeD[i]

# Note that this is |Stilde|^2, where the absolute value denotes
#    that this is not a proper contraction of Stilde_i, as
#    Stilde^i is NOT equal to gamma^{ij} Stilde_j (to understand
#    why this is, notice that Stilde_i is proportional to the
#    *4D* stress-energy tensor.)
Stilde2 = 0
for i in range(DIM) :
    for j in range(DIM) :
        Stilde2 += gammaUU[i][j] * StildeD[i]*StildeD[j]

f = rhostar**2*h**2 + (-lapse2 + beta2)*rhostar**2.*h**2.*uU0**2 + 2.*h*rhostar*betaDotStilde*uU0 + Stilde2

outputC(f,"rootRho",filename="NRPY+rhoRoot.h")
outputC(Stilde2, "Stilde2", filename="NRPY+Stilde2.h")
!cat NRPY+rhoRoot.h
!cat NRPY+Stilde2.h
```

    Wrote to file "NRPY+rhoRoot.h"
    Wrote to file "NRPY+Stilde2.h"
    /*
     *  Original SymPy expression:
     *  "rootRho = con[iSx]**2*(mi.gamDDyy*mi.gamDDzz - mi.gamDDyz**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + 2*con[iSx]*con[iSy]*(-mi.gamDDxy*mi.gamDDzz + mi.gamDDxz*mi.gamDDyz)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + 2*con[iSx]*con[iSz]*(mi.gamDDxy*mi.gamDDyz - mi.gamDDxz*mi.gamDDyy)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + con[iSy]**2*(mi.gamDDxx*mi.gamDDzz - mi.gamDDxz**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + 2*con[iSy]*con[iSz]*(-mi.gamDDxx*mi.gamDDyz + mi.gamDDxy*mi.gamDDxz)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + con[iSz]**2*(mi.gamDDxx*mi.gamDDyy - mi.gamDDxy**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + rhostar**2*(gamma*p_0*(rho/rho_0)**(gamma - 1)/(rho_0*(gamma - 1)) + 1)**2 + rhostar**2*(2.0*gamma*p_0*(rho/rho_0)**(gamma - 1)/(rho_0*(gamma - 1)) + 2.0)*(con[iSx]*mi.betaX + con[iSy]*mi.betaY + con[iSz]*mi.betaZ)/(mi.alpha*rho*sqrt(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy)) + rhostar**4.0*(gamma*p_0*(rho/rho_0)**(gamma - 1)/(rho_0*(gamma - 1)) + 1)**2.0*(-mi.alpha**2 + mi.betaX**2*mi.gamDDxx + 2*mi.betaX*mi.betaY*mi.gamDDxy + 2*mi.betaX*mi.betaZ*mi.gamDDxz + mi.betaY**2*mi.gamDDyy + 2*mi.betaY*mi.betaZ*mi.gamDDyz + mi.betaZ**2*mi.gamDDzz)/(mi.alpha**2*rho**2*(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy))"
     */
    {
       const double tmp_1 = (1.0/(rho_0));
       const double tmp_3 = gamma*p_0*tmp_1*pow(rho*tmp_1, gamma - 1)/(gamma - 1);
       const double tmp_11 = mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*((mi.gamDDyz)*(mi.gamDDyz)) - ((mi.gamDDxy)*(mi.gamDDxy))*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - ((mi.gamDDxz)*(mi.gamDDxz))*mi.gamDDyy;
       const double tmp_12 = (1.0/(tmp_11));
       const double tmp_13 = 2*con[iSx]*tmp_12;
       rootRho = ((con[iSx])*(con[iSx]))*tmp_12*(mi.gamDDyy*mi.gamDDzz - ((mi.gamDDyz)*(mi.gamDDyz))) + ((con[iSy])*(con[iSy]))*tmp_12*(mi.gamDDxx*mi.gamDDzz - ((mi.gamDDxz)*(mi.gamDDxz))) + 2*con[iSy]*con[iSz]*tmp_12*(-mi.gamDDxx*mi.gamDDyz + mi.gamDDxy*mi.gamDDxz) + con[iSy]*tmp_13*(-mi.gamDDxy*mi.gamDDzz + mi.gamDDxz*mi.gamDDyz) + ((con[iSz])*(con[iSz]))*tmp_12*(mi.gamDDxx*mi.gamDDyy - ((mi.gamDDxy)*(mi.gamDDxy))) + con[iSz]*tmp_13*(mi.gamDDxy*mi.gamDDyz - mi.gamDDxz*mi.gamDDyy) + ((rhostar)*(rhostar))*((tmp_3 + 1)*(tmp_3 + 1)) + ((rhostar)*(rhostar))*(2.0*tmp_3 + 2.0)*(con[iSx]*mi.betaX + con[iSy]*mi.betaY + con[iSz]*mi.betaZ)/(mi.alpha*rho*sqrt(tmp_11)) + ((rhostar)*(rhostar)*(rhostar)*(rhostar))*tmp_12*((tmp_3 + 1)*(tmp_3 + 1))*(-((mi.alpha)*(mi.alpha)) + ((mi.betaX)*(mi.betaX))*mi.gamDDxx + 2*mi.betaX*mi.betaY*mi.gamDDxy + 2*mi.betaX*mi.betaZ*mi.gamDDxz + ((mi.betaY)*(mi.betaY))*mi.gamDDyy + 2*mi.betaY*mi.betaZ*mi.gamDDyz + ((mi.betaZ)*(mi.betaZ))*mi.gamDDzz)/(((mi.alpha)*(mi.alpha))*((rho)*(rho)));
    }
    /*
     *  Original SymPy expression:
     *  "Stilde2 = con[iSx]**2*(mi.gamDDyy*mi.gamDDzz - mi.gamDDyz**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + 2*con[iSx]*con[iSy]*(-mi.gamDDxy*mi.gamDDzz + mi.gamDDxz*mi.gamDDyz)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + 2*con[iSx]*con[iSz]*(mi.gamDDxy*mi.gamDDyz - mi.gamDDxz*mi.gamDDyy)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + con[iSy]**2*(mi.gamDDxx*mi.gamDDzz - mi.gamDDxz**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + 2*con[iSy]*con[iSz]*(-mi.gamDDxx*mi.gamDDyz + mi.gamDDxy*mi.gamDDxz)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy) + con[iSz]**2*(mi.gamDDxx*mi.gamDDyy - mi.gamDDxy**2)/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*mi.gamDDyz**2 - mi.gamDDxy**2*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - mi.gamDDxz**2*mi.gamDDyy)"
     */
    {
       const double tmp_5 = (1.0/(mi.gamDDxx*mi.gamDDyy*mi.gamDDzz - mi.gamDDxx*((mi.gamDDyz)*(mi.gamDDyz)) - ((mi.gamDDxy)*(mi.gamDDxy))*mi.gamDDzz + 2*mi.gamDDxy*mi.gamDDxz*mi.gamDDyz - ((mi.gamDDxz)*(mi.gamDDxz))*mi.gamDDyy));
       const double tmp_6 = 2*con[iSx]*tmp_5;
       Stilde2 = ((con[iSx])*(con[iSx]))*tmp_5*(mi.gamDDyy*mi.gamDDzz - ((mi.gamDDyz)*(mi.gamDDyz))) + ((con[iSy])*(con[iSy]))*tmp_5*(mi.gamDDxx*mi.gamDDzz - ((mi.gamDDxz)*(mi.gamDDxz))) + 2*con[iSy]*con[iSz]*tmp_5*(-mi.gamDDxx*mi.gamDDyz + mi.gamDDxy*mi.gamDDxz) + con[iSy]*tmp_6*(-mi.gamDDxy*mi.gamDDzz + mi.gamDDxz*mi.gamDDyz) + ((con[iSz])*(con[iSz]))*tmp_5*(mi.gamDDxx*mi.gamDDyy - ((mi.gamDDxy)*(mi.gamDDxy))) + con[iSz]*tmp_6*(mi.gamDDxy*mi.gamDDyz - mi.gamDDxz*mi.gamDDyy);
    }

The root solve above finds $\rho$, which then allows us to get
\begin{equation}
u^0 = \frac{\rhostar}{\alpha\rtgamma\rho}\quad\textrm{and}\quad \vel = \frac{\uvec}{u^0} = \frac{\Svectilde}{\rhostar h(\rho)}.
\end{equation}
and thus we can find the rest of the primitives.

```python
#rhostar = sp.symbols("rhostar")
#StildeU = ixp.declarerank1("StildeU")
velU = ixp.zerorank1()

#lapse, rtgamma, rho, gamma1, c = sp.symbols("lapse rtgamma rho gamma1 c")
rho, rhostar = sp.symbols("testPrim[iRho] con[iRhoStar]")

u0 = rhostar/(lapse*rtgamma*rho)
epsilon = p0/rho0*(rho/rho0)**(gamma1 - 1)/(gamma1 - 1)

h = 1. + gamma1*epsilon

for i in range(DIM) :
    for j in range(DIM) :
        velU[i] += gammaUU[i][j]*StildeD[j]/(rhostar * h)/u0

outputC([h,u0,velU[0],velU[1],velU[2]], ["h", "u0","testPrim[ivx]", "testPrim[ivy]", "testPrim[ivz]"],filename="NRPY+getv.h")
```

    Wrote to file "NRPY+getv.h"

# Step 7: Lorentz Boosts
$$\label{lorentz}$$

We need to boost to the frame of the moving face.  The boost is
\begin{equation}
B(\beta) =\begin{pmatrix}
\gamma & -\beta\gamma n_x & -\beta\gamma n_y & -\beta\gamma n_z  \\
-\beta\gamma n_x  & 1 + (\gamma-1)n_x^2 & (\gamma-1)n_x n_y & (\gamma-1)n_x n_z\\
-\beta\gamma n_x  & (\gamma-1)n_y n_x & 1 + (\gamma-1)n_y^2 & (\gamma-1)n_y n_z\\
-\beta\gamma n_x & (\gamma-1) n_z n_x & (\gamma-1)n_z n_x & 1 + (\gamma-1)n_z^2
\end{pmatrix}
\end{equation}
And the boost is $X' = B(\beta) X$, where $X'$ and $X$ are four vectors.

So the rest of this is straightforward.

# Step 8: Compute $T^{\mu\nu}$ in Cartesian Coordinates
$$\label{TmunuSph}$$

```python
# declare gammaDD
gammaDD = ixp.zerorank2()
components = ["xx", "xy", "xz", "yy", "yz", "zz"]
names = ""
for comp in components :
    names = names + "mi.gamDD{0} ".format(comp)

g11, g12, g13, g22, g23, g33 = sp.symbols( names)

gammaDD[0][0] = g11
gammaDD[0][1] = g12
gammaDD[0][2] = g13
gammaDD[1][0] = g12
gammaDD[1][1] = g22
gammaDD[1][2] = g23
gammaDD[2][0] = g13
gammaDD[2][1] = g23
gammaDD[2][2] = g33

#declare alpha
alpha = sp.symbols( "mi.alpha")

#declare beta
betaU = ixp.zerorank1()
for i, comp in enumerate(["X", "Y", "Z"]) :
    betaU[i] = 0 #NEED A BETTER WAY sp.symbols( "mi.beta{0}".format(comp), real=True)

#now get the primitives
rho_b, epsilon, P = sp.symbols("rho ie press")

#get the 3-velocities
vU = ixp.zerorank1()
for i, comp in enumerate( ["vx", "vy", "vz"]) :
    vU[i] = sp.symbols("{0}".format(comp))

Ge.u4U_in_terms_of_vU__rescale_vU_by_applying_speed_limit(alpha,betaU,gammaDD, vU)

u4U = Ge.u4U_ito_vU

# First compute stress-energy tensor T4UU and T4UD in Spherical Coordinates:
Ge.compute_T4UU(gammaDD,betaU,alpha, rho_b,P,epsilon,u4U)
Ge.compute_T4UD(gammaDD,betaU,alpha, Ge.T4UU)

outputC([Ge.T4UU[0][0],Ge.T4UU[1][1],Ge.T4UU[2][2],Ge.T4UU[3][3] ], ["T4UU_diag[0]", "T4UU_diag[1]","T4UU_diag[2]", "T4UU_diag[3]"],filename="NRPY+getT4UU.h")
```

    Wrote to file "NRPY+getT4UU.h"

# Step 9: Output this notebook 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-GRHD_Equations-Cartesian-c-code.pdf](Tutorial-GRHD_Equations-Cartesian-c-code.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 --log-level='WARN' Tutorial-GRHD_Equations-Cartesian-c-code.ipynb
!pdflatex -interaction=batchmode Tutorial-GRHD_Equations-Cartesian-c-code.tex
!pdflatex -interaction=batchmode Tutorial-GRHD_Equations-Cartesian-c-code.tex
!pdflatex -interaction=batchmode Tutorial-GRHD_Equations-Cartesian-c-code.tex
!rm -f Tut*.out Tut*.aux Tut*.log
```


## 8.1 公式和图表的索引

在第4、5、6、7章中，分别介绍了如何制作或插入公式及图表，在文档中，我们往往需要对公式及图表进行引用以辅助文档的叙述、描述结果以及佐证一些结论。而有时为了排版的美观，所插入的公式或图表不一定直接放在引用位置旁边，因此对公式及图表进行索引就显得尤为重要了。

### 8.1.1 公式的索引

LaTeX中，公式的索引分为主要分为两个部分，一部分是给公式添加标签，可以使用`\label{标签名}`命令。根据第4章，可以使用`equation`环境插入带标签的公式；另一部分是在文档中进行引用，可以使用`\ref{标签名}`命令。

【**例8-1**】使用`\label{标签名}`及`\ref{标签名}`在文中对公式进行索引。

```tex
\documentclass[12pt]{article}
\begin{document}

(\ref{eq1}) is a binary equation

(\ref{eq2}) is a binary quadratic equation.

\begin{equation}
x+y=2\label{eq1}
\end{equation}

\begin{equation}
x^{2}+y^{2}=2\label{eq2}
\end{equation}

\end{document}

```
编译上述代码，得到文档如图8.1.1所示。

图8.1.1 编译后的文档

### 8.1.2 图形的索引

根据第6章，插入图片需要使用`graphicx`宏包，图形的索引与公式的索引类似。同样分为两部分，一部分是使用`\label{标签名}`命令给添加图形标签，另一部分是使用`\ref{标签名}`在文档中进行引用。

【**例8-2**】使用`\label{标签名}`及`\ref{标签名}`在文中对图形进行索引。

```tex
\documentclass[12pt]{article}
\usepackage{graphicx}
\begin{document}

Figure \ref{butterfly} is a photo of butterfly.

\begin{figure}
\centering
\includegraphics[width = 0.8\textwidth]{graphics/butterfly.JPG}
\caption{There is a beautiful butterfly.}
\label{butterfly}
\end{figure}

\end{document}

```
编译上述代码，得到文档如图8.1.2所示。

图8.1.2 编译后的文档

### 8.1.3 表格的索引

表格的索引与公式及图形的索引类似，同样分为两部分，一部分是使用`\label{标签名}`命令给添加表格标签。根据第5章，可以使用`tabular`和`table`两种环境制作带标签的表格；另一部分是使用`\ref{标签名}`在文档中进行引用。

【**例8-3**】使用`\label{标签名}`及`\ref{标签名}`在文中对表格进行索引。

```tex
\documentclass[12pt]{article}
\begin{document}

Table~\ref{table1} shows the values of some basic functions.

\begin{table}
    \centering
    \caption{The values of some basic functions.}
    \begin{tabular}{l|cccc}
        \hline
        & $x=1$ & $x=2$ & $x=3$ & $x=4$ \\
        \hline
        $y=x$ & 1 & 2 & 3 & 4 \\
        $y=x^{2}$ & 1 & 4 & 9 & 16 \\
        $y=x^{3}$ & 1 & 8 & 27 & 64 \\
        \hline
    \end{tabular}
    \label{table1}% 索引标签
\end{table}

\end{document}
```
编译上述代码，得到文档如图8.1.3所示。

图8.1.3 编译后的文档

【回放】[**8.0 导言**](https://nbviewer.jupyter.org/github/xinychen/latex-cookbook/blob/main/chapter-8/section0.ipynb)

【继续】[**8.2 创建超链接**](https://nbviewer.jupyter.org/github/xinychen/latex-cookbook/blob/main/chapter-8/section2.ipynb)

### License

This work is released under the MIT license.