# Python for Engineers Assignment 2018
Answer all questions.

すべての質問に答えなさい。

Submission deadline 07/16 (July 16).

提出締め切り07/16 (7月16日）。

Please submit your answer by email.

どうぞ、メールによってあなたの答えを提出してください。

You may use the following file formats:

あなたは以下のファイル形式を使ってもよい:
- .py
- .ipynb
- .txt

You may use multiple files.

あなたは複数のファイルを使ってもよい。

You will be awarded points for:
あなたは以下のためのポイントを授与される：
- correct solution. 正しい答え。
- concise solution. 簡潔な答え。
- optimising your code by importing relevant library functions. 外部や基準のライブラリやパッケージを利用します。
- writing functions to avoid repetition. 重複を防ぐために関数（functions)を使いなさい。
- using comments to explain what your code does. あなたのコードを説明するために、コメントを用いる。

## Part A : Data Analysis　データ分析

#### Question 1
*(2 marks)*

Import the data from the url to a Pandas data frame:

`Pandas`を使って、以下urlからデータを読み込めよ

https://raw.githubusercontent.com/justmarkham/DAT8/master/data/u.user

```python
import pandas as pd
url = 'https://raw.githubusercontent.com/justmarkham/DAT8/master/data/u.user'
#Import the data
data = pd.read_csv(url, sep = '|')
#Check if I could import the data
print(data)
```

         user_id  age gender     occupation zip_code
    0          1   24      M     technician    85711
    1          2   53      F          other    94043
    2          3   23      M         writer    32067
    3          4   24      M     technician    43537
    4          5   33      F          other    15213
    5          6   42      M      executive    98101
    6          7   57      M  administrator    91344
    7          8   36      M  administrator    05201
    8          9   29      M        student    01002
    9         10   53      M         lawyer    90703
    10        11   39      F          other    30329
    11        12   28      F          other    06405
    12        13   47      M       educator    29206
    13        14   45      M      scientist    55106
    14        15   49      F       educator    97301
    15        16   21      M  entertainment    10309
    16        17   30      M     programmer    06355
    17        18   35      F          other    37212
    18        19   40      M      librarian    02138
    19        20   42      F      homemaker    95660
    20        21   26      M         writer    30068
    21        22   25      M         writer    40206
    22        23   30      F         artist    48197
    23        24   21      F         artist    94533
    24        25   39      M       engineer    55107
    25        26   49      M       engineer    21044
    26        27   40      F      librarian    30030
    27        28   32      M         writer    55369
    28        29   41      M     programmer    94043
    29        30    7      M        student    55436
    ..       ...  ...    ...            ...      ...
    913      914   44      F          other    08105
    914      915   50      M  entertainment    60614
    915      916   27      M       engineer    N2L5N
    916      917   22      F        student    20006
    917      918   40      M      scientist    70116
    918      919   25      M          other    14216
    919      920   30      F         artist    90008
    920      921   20      F        student    98801
    921      922   29      F  administrator    21114
    922      923   21      M        student    E2E3R
    923      924   29      M          other    11753
    924      925   18      F       salesman    49036
    925      926   49      M  entertainment    01701
    926      927   23      M     programmer    55428
    927      928   21      M        student    55408
    928      929   44      M      scientist    53711
    929      930   28      F      scientist    07310
    930      931   60      M       educator    33556
    931      932   58      M       educator    06437
    932      933   28      M        student    48105
    933      934   61      M       engineer    22902
    934      935   42      M         doctor    66221
    935      936   24      M          other    32789
    936      937   48      M       educator    98072
    937      938   38      F     technician    55038
    938      939   26      F        student    33319
    939      940   32      M  administrator    02215
    940      941   20      M        student    97229
    941      942   48      F      librarian    78209
    942      943   22      M        student    77841

    [943 rows x 5 columns]

#### Question 2
*(2 marks)*

Remove all entries with occupation `other` or `retired` from the data set.

職業`occupation`の中に`other`と`retired`に関連するデータを取り除く。

```python
A = data.index[(data.occupation == 'other')]
B = data.index[(data.occupation == 'retired')]
#remove all entries with occupation 'other'
data.drop(index = A.tolist(),inplace = True)
#remove all entries with occupation 'retired'
data.drop(index = B.tolist(),inplace =True)
print(data)
```

         user_id  age gender     occupation zip_code
    0          1   24      M     technician    85711
    2          3   23      M         writer    32067
    3          4   24      M     technician    43537
    5          6   42      M      executive    98101
    6          7   57      M  administrator    91344
    7          8   36      M  administrator    05201
    8          9   29      M        student    01002
    9         10   53      M         lawyer    90703
    12        13   47      M       educator    29206
    13        14   45      M      scientist    55106
    14        15   49      F       educator    97301
    15        16   21      M  entertainment    10309
    16        17   30      M     programmer    06355
    18        19   40      M      librarian    02138
    19        20   42      F      homemaker    95660
    20        21   26      M         writer    30068
    21        22   25      M         writer    40206
    22        23   30      F         artist    48197
    23        24   21      F         artist    94533
    24        25   39      M       engineer    55107
    25        26   49      M       engineer    21044
    26        27   40      F      librarian    30030
    27        28   32      M         writer    55369
    28        29   41      M     programmer    94043
    29        30    7      M        student    55436
    30        31   24      M         artist    10003
    31        32   28      F        student    78741
    32        33   23      M        student    27510
    33        34   38      F  administrator    42141
    34        35   20      F      homemaker    42459
    ..       ...  ...    ...            ...      ...
    908      909   50      F       educator    53171
    909      910   28      M     healthcare    29301
    910      911   37      F         writer    53210
    912      913   27      M        student    76201
    914      915   50      M  entertainment    60614
    915      916   27      M       engineer    N2L5N
    916      917   22      F        student    20006
    917      918   40      M      scientist    70116
    919      920   30      F         artist    90008
    920      921   20      F        student    98801
    921      922   29      F  administrator    21114
    922      923   21      M        student    E2E3R
    924      925   18      F       salesman    49036
    925      926   49      M  entertainment    01701
    926      927   23      M     programmer    55428
    927      928   21      M        student    55408
    928      929   44      M      scientist    53711
    929      930   28      F      scientist    07310
    930      931   60      M       educator    33556
    931      932   58      M       educator    06437
    932      933   28      M        student    48105
    933      934   61      M       engineer    22902
    934      935   42      M         doctor    66221
    936      937   48      M       educator    98072
    937      938   38      F     technician    55038
    938      939   26      F        student    33319
    939      940   32      M  administrator    02215
    940      941   20      M        student    97229
    941      942   48      F      librarian    78209
    942      943   22      M        student    77841

    [824 rows x 5 columns]

#### Question 3
*(6 marks)*

Plot a bar chart showing:

棒グラフをプロットしなさい：
- the number of people per occupation for the 10 occupations that occur most frequently in the data set.

取り除かない各職業の総人数を計算し、総人数最も多い10つの職業を取り出し、各職業の総人数を棒グラフで書きなさい。
- plot an additional bar, labelled other, showing the sum of all remaining entries in the data set.

その以外の職業の人数をまとめて、`other`という名前を付け、同じ棒グラフに書きなさい。

```python
import matplotlib.pyplot as plt
import numpy as np
#check what kinds of occupation there are in the data
A1 = data.drop_duplicates("occupation")
#make a list of the number of each occupation
new_list = []
for raw in A1["occupation"]:
    A = len(data[data['occupation'] == raw]['occupation'])
    new_list.append(A)

#make a list of all occupations
B = list(A1['occupation'])
#make an array of occupations and the number
B1 = np.array([B,new_list])
#rearrange the array in descending order
B2 = B1[:, np.argsort(new_list)[::-1]]
#make a list of 10 occupations and 'other'
C1 = B2[:,:10]
P = list(C1[0])
P.append('other')
#make a list of the number of 11 occupations
C2 = B2[:,10:]
D1 = list(map(int,C2[1]))
Q = list(map(int,C1[1]))
Q.append(sum(D1))

# Create an array with the position of each bar along the x-axis
x_pos = np.arange(len(P))
# Produce bar plot
plt.bar(x_pos, Q);

# Replace the x ticks with P
# Rotate labels 90 degrees
plt.xticks(x_pos, P, rotation=90);
# Add axis labels
plt.xlabel('occupation');
plt.ylabel('number of people');

```

#### Question 4
*(2 marks)*

Find the:
- mean
- standard deviaion

of the age of people with the occupation: `administrator`.

`administrator`という職業`occupation`の人達を見つけ出す。その人達の年齢の
- 平均値
- 標準偏差

を求めよ。

```python
#make a data of only administrator
for raw in data["occupation"]:
    P = data[data['occupation'] == "administrator"]
#find the mean
print(P.iloc[:, 1].mean())
#find the standard deviation
print(P.iloc[:, 1].std())
```

    38.74683544303797
    11.123396864533204

## Part B : Movement of a Particle 粒子の動き

A particle moves along a curve.

粒子がある軌跡を沿って移動する。

The cartesian coordinates of the position of the particle $(r_x, r_y)$ are:

粒子の位置$（r_x、r_y）$は時間$t$の変数である。その式は以下：

\begin{align}
r_x &= \exp \left( -\dfrac{t}{T_1} \right)
\\
r_y &= 2 \cos \left(\dfrac{t}{T_2}\right)
\end{align}

$t$ = time (s)

Constants:　その中に、$T$は定数

$T_1 = 1$ s

$T_2 = 1/3$ s.

#### Question 1
*(8 marks)*

Plot the trajectory of the particle in the $xy$-plane over the time interval $t = 0$ to $t = 2\pi$ s.

時間$t=0$から$t=2\pi$までの粒子の軌跡を書きなさい。軌跡を表す点の数は任意。

On the graph, show the particle’s locations at $t = 0$, $\pi/2$ and $2\pi$ using:

同じ図に、以下のものを使って、$t=0$、$t=\pi／2$と$t=2\pi$の粒子の位置を強調せよ：
- a marker
- a label

```python
import sympy as sp
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
#define the constants
e = np.e
T1 = 1
T2 = 1/3
# define the functions
def rx(t):
    return e**(-t/T1)
def ry(t) :
    return 2*np.cos(t/T2)
#define the time span
tspan = np.linspace(0,2*np.pi,100)
#plotting
plt.plot(rx(tspan),ry(tspan),label='the trajectory of the particle')
#plot the point of particle locations at t=0,pi/2,and 2*pi
plt.plot(rx(0),ry(0),'ro')
plt.plot(rx(np.pi/2),ry(np.pi/2),'ro')
plt.plot(rx(2*np.pi),ry(2*np.pi),'ro')
## Axes labels
plt.xlabel('rx')
plt.ylabel('ry')
```

#### Question 2

The $x$ and $y$ components of the particle's acceleration $(a_x, a_y)$:

粒子の加速度$（a_x、a_y）$はの粒子の位置$（r_x、r_y）$の二階微分である：

$$a_x=v_x'=r_x''$$
$$a_y=v_y'=r_y''$$

velocity $(v_x, v_y)$

速度$(v_x, v_y)$

__(a)__ Express the particles acceleration $(a_x、a_y)$ symbolically, i.e. show the equation.

粒子加速度$（a_x、a_y）$を計算せよ。方程式を示す。

*(5 marks)*

```python
from sympy import diff,symbols,Symbol
import sympy as sp
#define t
t = Symbol('t')
#difine the functions again
def ry(t) :
    return 2*sp.cos(t/T2)
def ax(t):
    return diff(rx(t), t, 2)
def ay(t):
    return diff(ry(t),t,2)

print('ax =',ax(t))
print('ay =',ay(t))
```

    ax = 1.0*2.71828182845905**(-t)
    ay = -18.0*cos(3.0*t)

__(b)__ Plot the *magnitude* of the acceleration of the particle against time over the time interval $t = 0$ to $t = 2\pi$ s.

時間$t=0$から$t=2\pi$までの粒子の加速度の絶対値（マグニチュード加速度）を計算し、時間$t$に対する図を書きなさい。つまり、X軸は時間$t$、Y軸は加速度の絶対値。絶対値の式は以下

*(5 marks)*

The magnitude of acceleration, $A$:
$$A = \sqrt{a_x^2 + a_y^2}$$

```python
#define the function
def A(t):
    return sp.sqrt(ax(t)**2 + ay(t)**2)
#plotting
sp.plot(A(t), (t, 0, 2*sp.pi))
```

##### Question 3
__(a)__
Find the magnitude velocity of the particle at time $ t = 1$s.

$t=1$時刻の粒子の速度の絶対値（マグニチュード速度）を見つけ出す。

*(5 marks)*

```python
print(A(t).subs(t,1))
```

    17.8236618493674

__(b)__
Estimate the time/times when $r_y=0$

$r_y=0$を満足する時間（時間達）を逆計算せよ。

*(5 marks)*

```python
from sympy import solve
solution = solve(ry(t),t)
print(solution)
```

    [0.523598775598299, 1.57079632679490]

#### Question 4
*(10 marks)*

Animate the trajectory of the particle and save your work as a .mp4 file.

粒子の移動軌跡をアニメーション化し、.mp4ファイルとして保存しなさい。

```python
try:
    import ipywidgets
except ImportError:
    try:
        !{sys.executable} -m pip -q install ipywidgets
        import ipywidgets
    except ImportError:
        !{sys.executable} -m pip -q --user install ipywidgets
    finally:
        !jupyter nbextension enable --py widgetsnbextension
        print("You will need to refresh your browser page")
from ipywidgets import interact

from matplotlib import animation, rc
from IPython.display import HTML
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np

# Creates a figure window.
fig = plt.figure()

# Creates axes within the window
ax = plt.axes(xlim=(0, 1), ylim=(-2,2))

# Object to animate
point, = ax.plot([1], [1], marker='o', ms=40)  # for points

e =np.e
T1 = 1
T2 = 1/3

# Position of mass as function of time
def fun(s):
    x = e**(-s/T1)
    y =2*np.cos(s/T2)
    return x, y

def animate(i):
    x, y = fun(i/10)
    point.set_data(x, y)
    return  (point,)

# Animates the data
anim = animation.FuncAnimation(fig, animate, frames=30, interval=50, blit=True)

anim
writer = animation.writers['ffmpeg'](fps=15,bitrate=1800)
anim.save('the_trajectory_of_the_particle.mp4',writer = writer)
```

```python

```


# $\texttt{GiRaFFE}$: General Relativistic Force-Free Electrodynamics

## Authors: Patrick Nelson, Zachariah Etienne, George Vopal & Maria Babiuc-Hamilton
### Formatting improvements courtesy Brandon Clark

## GiRaFFE_HO_v2 is an experiment that omits the analytic derivatives of $\partial_j T^j_{{\rm EM} i}$

**Notebook Status:**  In progress

**Validation Notes:** This module is under active development -- do ***not*** use the resulting C code output for doing science.

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

## Introduction:
The original $\texttt{GiRaFFE}$ code, as presented in [the original paper](https://arxiv.org/pdf/1704.00599.pdf), exists as a significant modification to $\texttt{IllinoisGRMHD}$. As such, it used a third-order reconstruction algorithm with a slope limiter (Colella et al's piecewise parabolic method, or PPM) to handle spatial derivatives in the general relativistic force-free electrodynamics (GRFFE) equations. However the GRFFE equations do not generally permit shocks, so a more optimal approach would involve finite differencing all derivatives in the GRFFE equations. As it happens, NRPy+ was designed to generate C codes involving complex tensorial expressions and finite difference spatial derivatives, with finite-differencing order a freely-specifiable parameter.

The purpose of this notebook is to rewrite the equations of GRFFE as used in the original $\texttt{GiRaFFE}$ code so that all derivatives that appear are represented numerically as finite difference derivatives.

The GRFFE evolution equations (from eq. 13 of the [original paper](https://arxiv.org/pdf/1704.00599.pdf)) we wish to encode in the NRPy+ version of $\texttt{GiRaFFE}$ are as follows:

* $\partial_t \tilde{S}_i = - \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}$: [Link to Step 3.0](#step7)
* $\partial_t A_i = \epsilon_{ijk} v^j B^k - \partial_i (\alpha \Phi - \beta^j A_j)$: [Link to Step 4.0](#step8)
* $\partial_t [\sqrt{\gamma} \Phi] = -\partial_j (\alpha\sqrt{\gamma}A^j - \beta^j [\sqrt{\gamma} \Phi]) - \xi \alpha [\sqrt{\gamma} \Phi]$: [Link to Step 4.0](#step8)

Here, the densitized spatial Poynting flux one-form $\tilde{S}_i = \sqrt{\gamma} S_i$ (and $S_i$ comes from $S_{\mu} -n_{\nu} T^{\nu}_{{\rm EM} \mu}$), and $(\Phi, A_i)$ is the vector potential. We will solve these PDEs using the method of lines, where the right-hand sides of these equations (involving no explicit time derivatives) will be constructed using NRPy+.

### A Note on Notation:

As is standard in NRPy+,

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

For instance, in calculating the first term of [$T_{\rm EM}^{\mu\nu}$](#em_tensor) (specifically, ${\rm Term\ 1} = b^2 u^\mu u^\nu$), we use Greek indices:

```
T4EMUU = ixp.zerorank2(DIM=4)
for mu in range(4):
    for nu in range(4):
        # Term 1: b^2 u^{\mu} u^{\nu}
        T4EMUU[mu][nu] = smallb2*u4U[mu]*u4U[nu]\
```

When we calculate [$\beta_i = \gamma_{ij} \beta^j$](#4metric), we use Latin indices:
```
betaD = ixp.zerorank1()
for i in range(DIM):
    for j in range(DIM):
        betaD[i] += gammaDD[i][j] * betaU[j]
```

As a corollary, any expressions involving mixed Greek and Latin indices will need to offset one set of indices by one: A Latin index in a four-vector will be incremented and a Greek index in a three-vector will be decremented (however, the latter case does not occur in this tutorial notebook). This can be seen when we handle the second term of [$\partial_t \tilde{S}_i$](#construct_si) (or, more specifically, the second term thereof: $\frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}$):
```
# The second term: \alpha \sqrt{\\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} / 2
for i in range(DIM):
    for mu in range(4):
        for nu in range(4):
            Stilde_rhsD[i] += alpsqrtgam * T4EMUU[mu][nu] * g4DDdD[mu][nu][i+1] / 2
```

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

This notebook is organized as follows

1. [Step 1](#initializenrpy): Set up the needed NRPy+ infrastructure and declare core gridfunctions used by $\texttt{GiRaFFE}$
1. [Step 2](#4metric): Build the four metric $g_{\mu\nu}$, its inverse $g^{\mu\nu}$ and spatial derivatives $g_{\mu\nu,i}$ from ADM 3+1 quantities $\gamma_{ij}$, $\beta^i$, and $\alpha$
1. [Step 3](#t_derivatives): $T^{\mu\nu}_{\rm EM}$ and its derivatives
    1. [Step 3.a](#uibi): $u^i$ and $b^i$ and related quantities
    1. [Step 3.b](#em_tensor): Construct all components of the electromagnetic stress-energy tensor $T^{\mu \nu}_{\rm EM}$
1. [Step 4](#construct_si): Construct the evolution equation for $\tilde{S}_i$
1. [Step 5](#construct_ai): Construct the evolution equations for $A_i$ and $[\sqrt{\gamma}\Phi]$
    1. [Step 5.a](#aux): Construct some useful auxiliary gridfunctions for the other evolution equations
    1. [Step 5.b](#complete_construct_ai): Complete the construction of the evolution equations for $A_i$ and $[\sqrt{\gamma}\Phi]$
1. [Step 6](#code_validation): Code Validation against `GiRaFFE_HO.GiRaFFE_Higher_Order_v2` NRPy+ Module
1. [Step 7](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file

# Step 1: Set up the needed NRPy+ infrastructure and declare core gridfunctions used by $\texttt{GiRaFFE}$ \[Back to [top](#toc)\]
$$\label{initializenrpy}$$

1. Set some basic NRPy+ parameters. E.g., set the spatial dimension parameter to 3 and the finite differencing order to 4.
1. Next, declare some gridfunctions that are provided as input to the equations:
    1. $\alpha$, $\beta^i$, and $\gamma_{ij}$: These ADM 3+1 metric quantities are declared in the ADMBase Einstein Toolkit thorn, and are assumed to be made available to $\texttt{GiRaFFE}$ at this stage.
    1. The Valencia 3-velocity $v^i_{(n)}$ and vector potential $A_i$: Declared by $\texttt{GiRaFFE}$, and will have their initial values set in the separate thorn **GiRaFFEfood_HO**.
    1. The magnetic field as measured by a normal observer $B^i$: The quantities evolved forward in time in $\texttt{GiRaFFE}$ do not include the Valencia 3-velocity, so this quantity is not automatically updated. Instead, we compute it based on the evolved quantity $\tilde{S}_i$ and $B^i = \epsilon^{ijk} \partial_j A_k$ (where $A_k$ is another evolved quantity and $\epsilon^{ijk}$ is the Levi-Civita tensor). $B^i$ is evaluated using finite differences of $A_k$ in a separate function, though it can only be evaluated consistently on the interior of the grid. In the ghost zones, we will have to use lower-order derivatives.

```python
# Step 0: Add NRPy's directory to the path
# https://stackoverflow.com/questions/16780014/import-file-from-parent-directory
import os,sys
nrpy_dir_path = os.path.join("..")
if nrpy_dir_path not in sys.path:
    sys.path.append(nrpy_dir_path)

import NRPy_param_funcs as par
import indexedexp as ixp
import grid as gri
import finite_difference as fin
from outputC import *

#Step 1.0: Set the spatial dimension parameter to 3.
par.set_parval_from_str("grid::DIM", 3)
DIM = par.parval_from_str("grid::DIM")

# Step 1.1: Set the finite differencing order to 4.
par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 4)

thismodule = "GiRaFFE_NRPy"

# M_PI will allow the C code to substitute the correct value
M_PI = par.Cparameters("#define",thismodule,"M_PI","")
# ADMBase defines the 4-metric in terms of the 3+1 spacetime metric quantities gamma_{ij}, beta^i, and alpha
gammaDD = ixp.register_gridfunctions_for_single_rank2("AUX","gammaDD", "sym01",DIM=3)
betaU   = ixp.register_gridfunctions_for_single_rank1("AUX","betaU",DIM=3)
alpha   = gri.register_gridfunctions("AUX","alpha")
# GiRaFFE uses the Valencia 3-velocity and A_i, which are defined in the initial data module(GiRaFFEfood)
ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUX","ValenciavU",DIM=3)
AD = ixp.register_gridfunctions_for_single_rank1("EVOL","AD",DIM=3)
# B^i must be computed at each timestep within GiRaFFE so that the Valencia 3-velocity can be evaluated
BU = ixp.register_gridfunctions_for_single_rank1("AUX","BU",DIM=3)
```

# Step 2: Build the four metric $g_{\mu\nu}$, its inverse $g^{\mu\nu}$ and spatial derivatives $g_{\mu\nu,i}$ from ADM 3+1 quantities $\gamma_{ij}$, $\beta^i$, and $\alpha$ \[Back to [top](#toc)\]
$$\label{4metric}$$

Notice that the time evolution equation for $\tilde{S}_i$
$$
\partial_t \tilde{S}_i = - \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}
$$
contains $\partial_i g_{\mu \nu} = g_{\mu\nu,i}$. We will now focus on evaluating this term.

The four-metric $g_{\mu\nu}$ is related to the three-metric $\gamma_{ij}$, index-lowered shift $\beta_i$, and lapse $\alpha$ by
$$
g_{\mu\nu} = \begin{pmatrix}
-\alpha^2 + \beta^k \beta_k & \beta_j \\
\beta_i & \gamma_{ij}
\end{pmatrix}.
$$
This tensor and its inverse have already been built by the u0_smallb_Poynting__Cartesian.py module ([documented here](Tutorial-u0_smallb_Poynting-Cartesian.ipynb)), so we can simply load the module and import the variables.

```python
# Step 1.2: import u0_smallb_Poynting__Cartesian.py to set
#           the four metric and its inverse. This module also sets b^2 and u^0.
import u0_smallb_Poynting__Cartesian.u0_smallb_Poynting__Cartesian as u0b
u0b.compute_u0_smallb_Poynting__Cartesian(gammaDD,betaU,alpha,ValenciavU,BU)

betaD = ixp.zerorank1()
for i in range(DIM):
    for j in range(DIM):
        betaD[i] += gammaDD[i][j] * betaU[j]
# Error check: fixed = to +=

# We will now pull in the four metric and its inverse.
import BSSN.ADMBSSN_tofrom_4metric as AB4m
gammaDD,betaU,alpha = AB4m.setup_ADM_quantities("ADM")
AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)
g4DD = AB4m.g4DD
AB4m.g4UU_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)
g4UU = AB4m.g4UU
```

Next we compute spatial derivatives of the metric, $\partial_i g_{\mu\nu} = g_{\mu\nu,i}$, written in terms of the three-metric, shift, and lapse. Simply taking the derivative of the expression for $g_{\mu\nu}$ above, we find
$$
g_{\mu\nu,l} = \begin{pmatrix}
-2\alpha \alpha_{,l} + \beta^k_{\ ,l} \beta_k + \beta^k \beta_{k,l} & \beta_{j,l} \\
\beta_{i,l} & \gamma_{ij,l}
\end{pmatrix}.
$$

Notice the derivatives of the shift vector with its indexed lowered, $\beta_{i,j} = \partial_j \beta_i$. This can be easily computed in terms of the given ADMBase quantities $\beta^i$ and $\gamma_{ij}$ via:
\begin{align}
\beta_{i,j} &= \partial_j \beta_i \\
            &= \partial_j (\gamma_{ik} \beta^k) \\
            &= \gamma_{ik} \partial_j\beta^k + \beta^k \partial_j \gamma_{ik} \\
\beta_{i,j} &= \gamma_{ik} \beta^k_{\ ,j} + \beta^k \gamma_{ik,j}.
\end{align}

Since this expression mixes Greek and Latin indices, we will declare this as a 4 dimensional quantity, but only set the three spatial components of its last index (that is, leaving $l=0$ unset).

So, we will first set
$$ g_{00,l} = \underbrace{-2\alpha \alpha_{,l}}_{\rm Term\ 1} + \underbrace{\beta^k_{\ ,l} \beta_k}_{\rm Term\ 2} + \underbrace{\beta^k \beta_{k,l}}_{\rm Term\ 3} $$

```python
# Step 1.2, cont'd: Build spatial derivatives of the four metric
# Step 1.2.a: Declare derivatives of grid functions. These will be handled by FD_outputC
alpha_dD   = ixp.declarerank1("alpha_dD")
betaU_dD   = ixp.declarerank2("betaU_dD","nosym")
gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01")

# Step 1.2.b: These derivatives will be constructed analytically.
betaDdD    = ixp.zerorank2()
g4DDdD     = ixp.zerorank3(DIM=4)

for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            # \gamma_{ik} \beta^k_{,j} + \beta^k \gamma_{ik,j}
            betaDdD[i][j] += gammaDD[i][k] * betaU_dD[k][j] + betaU[k] * gammaDD_dD[i][k][j]
#Error check: changed = to += above

# Step 1.2.c: Set the 00 components
# Step 1.2.c.i: Term 1: -2\alpha \alpha_{,l}
for l in range(DIM):
    g4DDdD[0][0][l+1] = -2*alpha*alpha_dD[l]

# Step 1.2.c.ii: Term 2: \beta^k_{\ ,l} \beta_k
for l in range(DIM):
    for k in range(DIM):
        g4DDdD[0][0][l+1] += betaU_dD[k][l] * betaD[k]

# Step 1.2.c.iii: Term 3: \beta^k \beta_{k,l}
for l in range(DIM):
    for k in range(DIM):
        g4DDdD[0][0][l+1] += betaU[k] * betaDdD[k][l]
```

Now we will contruct the other components of $g_{\mu\nu,l}$. We will first construct
$$ g_{i0,l} = g_{0i,l} = \beta_{i,l}, $$
then
$$ g_{ij,l} = \gamma_{ij,l} $$

```python
# Step 1.2.d: Set the i0 and 0j components
for l in range(DIM):
    for i in range(DIM):
        # \beta_{i,l}
        g4DDdD[i+1][0][l+1] = g4DDdD[0][i+1][l+1] = betaDdD[i][l]

#Step 1.2.e: Set the ij components
for l in range(DIM):
    for i in range(DIM):
        for j in range(DIM):
            # \gamma_{ij,l}
            g4DDdD[i+1][j+1][l+1] = gammaDD_dD[i][j][l]
```

# Step 3: $T^{\mu\nu}_{\rm EM}$ and its derivatives \[Back to [top](#toc)\]
$$\label{t_derivatives}$$

Now that the metric and its derivatives are out of the way, let's return to the evolution equation for $\tilde{S}_i$,
$$
\partial_t \tilde{S}_i = - \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}.
$$
We turn our focus now to $T^j_{{\rm EM} i}$ and its derivatives. To this end, we start by computing $T^{\mu \nu}_{\rm EM}$ (from eq. 27 of [Paschalidis & Shapiro's paper on their GRFFE code](https://arxiv.org/pdf/1310.3274.pdf)):

$$\boxed{T^{\mu \nu}_{\rm EM} = b^2 u^{\mu} u^{\nu} + \frac{b^2}{2} g^{\mu \nu} - b^{\mu} b^{\nu}.}$$

Notice that $T^{\mu\nu}_{\rm EM}$ is written in terms of

* $b^\mu$, the 4-component magnetic field vector, related to the comoving magnetic field vector $B^i_{(u)}$
* $u^\mu$, the 4-velocity
* $g^{\mu \nu}$, the inverse 4-metric

However, $\texttt{GiRaFFE}$ has access to only the following quantities, requiring in the following sections that we write the above quantities in terms of the following ones:

* $\gamma_{ij}$, the 3-metric
* $\alpha$, the lapse
* $\beta^i$, the shift
* $A_i$, the vector potential
* $B^i$, the magnetic field (we assume only in the grid interior, not the ghost zones)
* $\left[\sqrt{\gamma}\Phi\right]$, the zero-component of the vector potential $A_\mu$, times the square root of the determinant of the 3-metric
* $v_{(n)}^i$, the Valencia 3-velocity
* $u^0$, the zero-component of the 4-velocity

## Step 3.a: $u^i$ and $b^i$ and related quantities \[Back to [top](#toc)\]
$$\label{uibi}$$

We begin by importing what we can from [u0_smallb_Poynting__Cartesian.py](../edit/u0_smallb_Poynting__Cartesian/u0_smallb_Poynting__Cartesian.py). We will need the four-velocity $u^\mu$, which is related to the Valencia 3-velocity $v^i_{(n)}$ used directly by $\texttt{GiRaFFE}$ (see also [Duez, et al, eqs. 53 and 56](https://arxiv.org/pdf/astro-ph/0503420.pdf))
\begin{align}
u^i &= u^0 (\alpha v^i_{(n)} - \beta^i), \\
u_j &= \alpha u^0 \gamma_{ij} v^i_{(n)},
\end{align}
where $v^i_{(n)}$ is the Valencia three-velocity. These have already been constructed in terms of the Valencia 3-velocity and other 3+1 ADM quantities by the [u0_smallb_Poynting__Cartesian.py](../edit/u0_smallb_Poynting__Cartesian/u0_smallb_Poynting__Cartesian.py) module, so we can simply import these variables:

```python
# Step 2.0: u^i, b^i, and related quantities
# Step 2.0.a: import the four-velocity, as written in terms of the Valencia 3-velocity
uD = ixp.zerorank1()
uU = ixp.zerorank1()
u4upperZero = gri.register_gridfunctions("AUX","u4upperZero")

for i in range(DIM):
    uD[i] = u0b.uD[i].subs(u0b.u0,u4upperZero)
    uU[i] = u0b.uU[i].subs(u0b.u0,u4upperZero)

```

We also need the magnetic field 4-vector $b^{\mu}$, which is related to the magnetic field by [eqs. 23, 24, and 31 in Duez, et al](https://arxiv.org/pdf/astro-ph/0503420.pdf):
\begin{align}
b^0 &= \frac{1}{\sqrt{4\pi}} B^0_{\rm (u)} = \frac{u_j B^j}{\sqrt{4\pi}\alpha}, \\
b^i &= \frac{1}{\sqrt{4\pi}} B^i_{\rm (u)} = \frac{B^i + (u_j B^j) u^i}{\sqrt{4\pi}\alpha u^0}, \\
\end{align}
where $B^i$ is the variable tracked by the HydroBase thorn in the Einstein Toolkit. Again, these have already been built by the [u0_smallb_Poynting__Cartesian.py](../edit/u0_smallb_Poynting__Cartesian/u0_smallb_Poynting__Cartesian.py), so we can simply import the variables.

```python
# Step 2.0.b: import the small b terms
smallb4U = ixp.zerorank1(DIM=4)
smallb4D = ixp.zerorank1(DIM=4)
for mu in range(4):
    smallb4U[mu] = u0b.smallb4U[mu].subs(u0b.u0,u4upperZero)
    smallb4D[mu] = u0b.smallb4D[mu].subs(u0b.u0,u4upperZero)

smallb2 = u0b.smallb2etk.subs(u0b.u0,u4upperZero)

```

## Step 3.b: Construct all components of the electromagnetic stress-energy tensor $T^{\mu \nu}_{\rm EM}$ \[Back to [top](#toc)\]
$$\label{em_tensor}$$

We now have all the pieces to calculate the stress-energy tensor,
$$T^{\mu \nu}_{\rm EM} = \underbrace{b^2 u^{\mu} u^{\nu}}_{\rm Term\ 1} +
\underbrace{\frac{b^2}{2} g^{\mu \nu}}_{\rm Term\ 2}
- \underbrace{b^{\mu} b^{\nu}}_{\rm Term\ 3}.$$
Because $u^0$ is a separate variable, we will create a temporary variable $u^\mu=\left( u^0, u^i \right)$,

```python
# Step 2.1: Construct the electromagnetic stress-energy tensor
# Step 2.1.a: Set up the four-velocity vector
u4U = ixp.zerorank1(DIM=4)
u4U[0] = u4upperZero
for i in range(DIM):
    u4U[i+1] = uU[i]

# Step 2.1.b: Build T4EMUU itself
T4EMUU = ixp.zerorank2(DIM=4)
for mu in range(4):
    for nu in range(4):
        # Term 1: b^2 u^{\mu} u^{\nu}
        T4EMUU[mu][nu] = smallb2*u4U[mu]*u4U[nu]

for mu in range(4):
    for nu in range(4):
        # Term 2: b^2 / 2 g^{\mu \nu}
        T4EMUU[mu][nu] += smallb2*g4UU[mu][nu]/2

for mu in range(4):
    for nu in range(4):
        # Term 3: -b^{\mu} b^{\nu}
        T4EMUU[mu][nu] += -smallb4U[mu]*smallb4U[nu]
```

# Step 4: Construct the evolution equation for $\tilde{S}_i$ \[Back to [top](#top)\]
$$\label{construct_si}$$

Finally, we will return our attention to the time evolution equation (from eq. 13 of the [original paper](https://arxiv.org/pdf/1704.00599.pdf)),
\begin{align}
\partial_t \tilde{S}_i &= - \partial_j \underbrace{\left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right)}_{\rm SevolParenUD} + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} \\
                       &= - \partial_j{\rm SevolParenUD[j][i]} + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} .
\end{align}
We will first construct `SevolParenUD`, then use its derivatives to construct the evolution equation. Note that
\begin{align}
{\rm SevolParenUD[j][i]} &= \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \\
                             &= \alpha \sqrt{\gamma} g_{\mu i} T^{\mu j}_{\rm EM}.
\end{align}

```python
# Step 3.0: Construct the evolution equation for \tilde{S}_i
# Here, we set up the necessary machinery to take FD derivatives of alpha * sqrt(gamma)
gammaUU = ixp.register_gridfunctions_for_single_rank2("AUX","gammaUU","sym01")
gammadet = gri.register_gridfunctions("AUX","gammadet")
gammaUU, gammadet = ixp.symm_matrix_inverter3x3(gammaDD)

alpsqrtgam = alpha*sp.sqrt(gammadet)

SevolParenUD = ixp.register_gridfunctions_for_single_rank2("AUX","SevolParenUD","nosym")
SevolParenUD = ixp.zerorank2()

for i in range(DIM):
    for j in range (DIM):
        for mu in range(4):
            SevolParenUD[j][i] += alpsqrtgam * g4DD[mu][i+1] * T4EMUU[mu][j+1]

SevolParenUD_dD = ixp.declarerank3("SevolParenUD_dD","nosym")

Stilde_rhsD = ixp.zerorank1()
# The first term: \alpha \sqrt{\gamma} \partial_j T^j_{{\rm EM} i}
for i in range(DIM):
    for j in range(DIM):
        Stilde_rhsD[i] += -SevolParenUD_dD[j][i][j]

# The second term: \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} / 2
for i in range(DIM):
    for mu in range(4):
        for nu in range(4):
            Stilde_rhsD[i] += alpsqrtgam * T4EMUU[mu][nu] * g4DDdD[mu][nu][i+1] / 2

```

# Step 5: Construct the evolution equations for $A_i$ and $[\sqrt{\gamma}\Phi]$ \[Back to [top](#toc)\]
$$\label{construct_ai}$$

We will also need to evolve the vector potential $A_i$. This evolution is given as eq. 17 in the [$\texttt{GiRaFFE}$](https://arxiv.org/pdf/1704.00599.pdf) paper:
$$\boxed{\partial_t A_i = \epsilon_{ijk} v^j B^k - \partial_i (\underbrace{\alpha \Phi - \beta^j A_j}_{\rm AevolParen}),}$$
where $\epsilon_{ijk} = [ijk] \sqrt{\gamma}$ is the antisymmetric Levi-Civita tensor, the drift velocity $v^i = u^i/u^0$, $\gamma$ is the determinant of the three metric, $B^k$ is the magnetic field, $\alpha$ is the lapse, and $\beta$ is the shift.

We also need the scalar electric potential $\Phi$, which is evolved by eq. 19:
$$\boxed{\partial_t [\sqrt{\gamma} \Phi] = -\partial_j (\underbrace{\alpha\sqrt{\gamma} \gamma^{ij} A_i - \beta^j [\sqrt{\gamma} \Phi]}_{\rm PevolParenU[j]}) - \xi \alpha [\sqrt{\gamma} \Phi],}$$
with $\xi$ chosen as a damping factor.

## Step 5.a: Construct some useful auxiliary gridfunctions for the other evolution equations \[Back to [top](#toc)\]
$$\label{aux}$$

After declaring a  some needed quantities, we will also define the parenthetical terms (underbrace above) that we need to take derivatives of. That way, we can take finite-difference derivatives easily. Note that the above equations incorporate the fact that $\gamma^{ij}$ is the appropriate raising operator for $A_i$: $A^j = \gamma^{ij} A_i$. This is correct since $n_\mu A^\mu = 0$, where $n_\mu$ is a normal to the hypersurface, so $A^0=0$ (according to Sec. II, subsection C of [the "Improved EM gauge condition" paper of Etienne *et al*](https://arxiv.org/pdf/1110.4633.pdf)).

```python
# Step 4.0: Construct the evolution equations for A_i and sqrt(gamma)Phi
# Step 4.0.a: Construct some useful auxiliary gridfunctions for the other evolution equations
xi = par.Cparameters("REAL",thismodule,"xi",0.1) # The damping factor

# Define sqrt(gamma)Phi as psi6Phi
psi6Phi = gri.register_gridfunctions("EVOL","psi6Phi")
Phi = psi6Phi / sp.sqrt(gammadet)

# We'll define a few extra gridfunctions to avoid complicated derivatives
AevolParen  = gri.register_gridfunctions("AUX","AevolParen")
PevolParenU = ixp.register_gridfunctions_for_single_rank1("AUX","PevolParenU")

# {\rm AevolParen} = \alpha \Phi - \beta^j A_j
AevolParen = alpha*Phi
for j in range(DIM):
    AevolParen += -betaU[j] * AD[j]

# {\rm PevolParenU[j]} = \alpha\sqrt{\gamma} \gamma^{ij} A_i - \beta^j [\sqrt{\gamma} \Phi]
for j in range(DIM):
    PevolParenU[j] = -betaU[j] * psi6Phi
    for i in range(DIM):
        PevolParenU[j] += alpsqrtgam * gammaUU[i][j] * AD[i]

AevolParen_dD  = ixp.declarerank1("AevolParen_dD")
PevolParenU_dD = ixp.declarerank2("PevolParenU_dD","nosym")

```

## Step 5.b: Complete the construction of the evolution equations for $A_i$ and $[\sqrt{\gamma}\Phi]$ \[Back to [top](#toc)\]
$$\label{complete_construct_ai}$$

Now to set the evolution equations ([eqs. 17 and 19](https://arxiv.org/pdf/1704.00599.pdf)), recalling that the drift velocity $v^i = u^i/u^0$:
\begin{align}
\partial_t A_i &= \epsilon_{ijk} v^j B^k - \partial_i (\alpha \Phi - \beta^j A_j) \\
               &= \epsilon_{ijk} \frac{u^j}{u^0} B^k - {\rm AevolParen\_dD[i]} \\
\partial_t [\sqrt{\gamma} \Phi] &= -\partial_j \left(\left(\alpha\sqrt{\gamma}\right)A^j - \beta^j [\sqrt{\gamma} \Phi]\right) - \xi \alpha [\sqrt{\gamma} \Phi] \\
                                &= -{\rm PevolParenU\_dD[j][j]} - \xi \alpha [\sqrt{\gamma} \Phi]. \\
\end{align}

```python
# Step 4.0.b: Construct the actual evolution equations for A_i and sqrt(gamma)Phi
A_rhsD = ixp.zerorank1()
psi6Phi_rhs = sp.sympify(0)

# We already have a handy function to define the Levi-Civita symbol in WeylScalars
import WeylScal4NRPy.WeylScalars_Cartesian as weyl
# Initialize the Levi-Civita tensor by setting it equal to the Levi-Civita symbol
LeviCivitaSymbolDDD = weyl.define_LeviCivitaSymbol_rank3()
LeviCivitaTensorDDD = ixp.zerorank3()
#LeviCivitaTensorUUU = ixp.zerorank3()

for i in range(DIM):
    for j in range(DIM):
        for k in range(DIM):
            LeviCivitaTensorDDD[i][j][k] = LeviCivitaSymbolDDD[i][j][k] * sp.sqrt(gammadet)
            #LeviCivitaTensorUUU[i][j][k] = LeviCivitaSymbolDDD[i][j][k] / sp.sqrt(gammadet)

for i in range(DIM):
    A_rhsD[i] = -AevolParen_dD[i]
    for j in range(DIM):
        for k in range(DIM):
            A_rhsD[i] += LeviCivitaTensorDDD[i][j][k]*(uU[j]/u4upperZero)*BU[k]

psi6Phi_rhs = -xi*alpha*psi6Phi
for j in range(DIM):
    psi6Phi_rhs += -PevolParenU_dD[j][j]
```

# Step 6:  Code Validation against `GiRaFFE_HO.GiRaFFE_Higher_Order_v2` NRPy+ Module \[Back to [top](#toc)\]
$$\label{code_validation}$$

Here, as a code validation check, we verify agreement in the SymPy expressions for the $\texttt{GiRaFFE}$ evolution equations and auxiliary quantities we intend to use between
1. this tutorial and
2. the NRPy+ [GiRaFFE_HO.GiRaFFE_Higher_Order_v2](../edit/GiRaFFE_HO/GiRaFFE_Higher_Order_v2.py) module.

```python
# Reset the list of gridfunctions, as registering a gridfunction
#   twice will spawn an error.
gri.glb_gridfcs_list = []

import GiRaFFE_HO.GiRaFFE_Higher_Order_v2 as gho
gho.GiRaFFE_Higher_Order_v2()

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

print("AevolParen - gho.AevolParen = " + str(AevolParen - gho.AevolParen))
print("psi6Phi_rhs - gho.psi6Phi_rhs = " + str(psi6Phi_rhs - gho.psi6Phi_rhs))
for i in range(DIM):
    print("PevolParenU["+str(i)+"] - gho.PevolParenU["+str(i)+"] = " + str(PevolParenU[i] - gho.PevolParenU[i]))
    print("Stilde_rhsD["+str(i)+"] - gho.Stilde_rhsD["+str(i)+"] = " + str(Stilde_rhsD[i] - gho.Stilde_rhsD[i]))
    print("A_rhsD["+str(i)+"] - gho.A_rhsD["+str(i)+"] = " + str(A_rhsD[i] - gho.A_rhsD[i]))
    for j in range(DIM):
        print("SevolParenUD["+str(i)+"]["+str(j)+"] - gho.SevolParenUD["+str(i)+"]["+str(i)+"] = "\
              + str(SevolParenUD[i][j] - gho.SevolParenUD[i][j]))
```

    Consistency check between GiRaFFE_Higher_Order tutorial and NRPy+ module: ALL SHOULD BE ZERO.
    AevolParen - gho.AevolParen = 0
    psi6Phi_rhs - gho.psi6Phi_rhs = 0
    PevolParenU[0] - gho.PevolParenU[0] = 0
    Stilde_rhsD[0] - gho.Stilde_rhsD[0] = 0
    A_rhsD[0] - gho.A_rhsD[0] = 0
    SevolParenUD[0][0] - gho.SevolParenUD[0][0] = 0
    SevolParenUD[0][1] - gho.SevolParenUD[0][0] = 0
    SevolParenUD[0][2] - gho.SevolParenUD[0][0] = 0
    PevolParenU[1] - gho.PevolParenU[1] = 0
    Stilde_rhsD[1] - gho.Stilde_rhsD[1] = 0
    A_rhsD[1] - gho.A_rhsD[1] = 0
    SevolParenUD[1][0] - gho.SevolParenUD[1][1] = 0
    SevolParenUD[1][1] - gho.SevolParenUD[1][1] = 0
    SevolParenUD[1][2] - gho.SevolParenUD[1][1] = 0
    PevolParenU[2] - gho.PevolParenU[2] = 0
    Stilde_rhsD[2] - gho.Stilde_rhsD[2] = 0
    A_rhsD[2] - gho.A_rhsD[2] = 0
    SevolParenUD[2][0] - gho.SevolParenUD[2][2] = 0
    SevolParenUD[2][1] - gho.SevolParenUD[2][2] = 0
    SevolParenUD[2][2] - gho.SevolParenUD[2][2] = 0

# Step 7: 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-GiRaFFE_Higher_Order_v2.pdf](Tutorial-GiRaFFE_Higher_Order_v2.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-GiRaFFE_Higher_Order_v2.ipynb
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_Higher_Order_v2.tex
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_Higher_Order_v2.tex
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_Higher_Order_v2.tex
!rm -f Tut*.out Tut*.aux Tut*.log
```

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