# Applied Stochastic Processes HW_09

**11510691 程远星$\DeclareMathOperator*{\argmin}{argmin}
\DeclareMathOperator*{\argmax}{argmax}
\DeclareMathOperator*{\plim}{plim}
\newcommand{\using}[1]{\stackrel{\mathrm{#1}}{=}}
\newcommand{\ffrac}{\displaystyle \frac}
\newcommand{\asim}{\overset{\text{a}}{\sim}}
\newcommand{\space}{\text{ }}
\newcommand{\bspace}{\;\;\;\;}
\newcommand{\QQQ}{\boxed{?\:}}
\newcommand{\void}{\left.\right.}
\newcommand{\Tran}[1]{{#1}^{\mathrm{T}}}
\newcommand{\d}[1]{\displaystyle{#1}}
\newcommand{\CB}[1]{\left\{ #1 \right\}}
\newcommand{\SB}[1]{\left[ #1 \right]}
\newcommand{\P}[1]{\left( #1 \right)}
\newcommand{\abs}[1]{\left| #1 \right|}
\newcommand{\norm}[1]{\left\| #1 \right\|}
\newcommand{\dd}{\mathrm{d}}
\newcommand{\Exp}{\mathrm{E}}
\newcommand{\RR}{\mathbb{R}}
\newcommand{\EE}{\mathbb{E}}
\newcommand{\II}{\mathbb{I}}
\newcommand{\NN}{\mathbb{N}}
\newcommand{\ZZ}{\mathbb{Z}}
\newcommand{\QQ}{\mathbb{Q}}
\newcommand{\PP}{\mathbb{P}}
\newcommand{\AcA}{\mathcal{A}}
\newcommand{\FcF}{\mathcal{F}}
\newcommand{\AsA}{\mathscr{A}}
\newcommand{\FsF}{\mathscr{F}}
\newcommand{\Var}[2][\,\!]{\mathrm{Var}_{#1}\left[#2\right]}
\newcommand{\Avar}[2][\,\!]{\mathrm{Avar}_{#1}\left[#2\right]}
\newcommand{\Cov}[2][\,\!]{\mathrm{Cov}_{#1}\left(#2\right)}
\newcommand{\Corr}[2][\,\!]{\mathrm{Corr}_{#1}\left(#2\right)}
\newcommand{\I}[1]{\mathrm{I}\left( #1 \right)}
\newcommand{\N}[1]{\mathcal{N} \left( #1 \right)}
\newcommand{\ow}{\text{otherwise}}
\void^\dagger$**

## Question 1

$\bspace$Necessity$\Rightarrow)$:

> Since it's time reversible, then the following equation set has a solution
>
>$$\begin{cases}
x_iP_{ij} = x_j P_{ji}\\
x_kP_{kj} = x_j P_{jk}\\
x_iP_{ik} = x_k P_{ki}
\end{cases} \Rightarrow \ffrac{x_i}{x_k} = \ffrac{P_{ki}}{P_{ik}} = \ffrac{P_{ji}P_{kj}}{P_{ij}P_{ji}} \Rightarrow P_{ik}P_{kj}P_{ji} = P_{ij}P_{jk}P_{ki}$$

$\bspace$Sufficiency$\Leftarrow)$:

>Given that $P_{ik}P_{kj}P_{ji} = P_{ij}P_{jk}P_{ki}$, we sum over all possible state $k$, we have
>
>$$P_{ij}^2P_{ji}=P_{ij}P_{ji}^2 \Rightarrow P_{ij} = P_{ji}$$
>
>So that by $Theorem4.2$ we have
>
>$$P_{i,i_1}P_{i_1,i_2} \cdots P_{i_k,i} = P_{i,i_k}P_{i_k,i_{{k-1}}} \cdots P_{i_1,i} $$
>
>for all states $i,i_1,\dots,i_k$, then the Markov Chain is time reversible

## Question 2

$\P{1}$

$$\begin{align}
P\CB{X_{n+1} = i+1 \mid X_n = i} &= \P{\ffrac{m-i}{m}}^2\\[0.6em]
P\CB{X_{n+1} = i \mid X_n = i} &= \ffrac{2\P{m-i}i}{m^2}\\[0.6em]
P\CB{X_{n+1} = i-1 \mid X_n = i} &= \P{\ffrac{i}{m}}^2
\end{align}$$

$\P{2}$

$$\pi_j = \lim_{n\to\infty}P_{ij}^{\P{n}} = \ffrac{\binom{m}{j}\binom{m}{m-j}}{\binom{2m}{m}}$$

$\bspace$And to show the time reversiblity, we can see the following two should be equal.

$$\pi_jP_{j,j+1} = \ffrac{\binom{m}{j}\binom{m}{m-j}}{\binom{2m}{m}}\P{\ffrac{m-j}{m}}^2 = \ffrac{\SB{m!}^2}{\SB{j!}^2\SB{\P{m-j-1}!}^2}$$

$$\pi_{j+1}P_{j+1,j} = \ffrac{\binom{m}{j+1}\binom{m}{m-j-1}}{\binom{2m}{m}}\P{\ffrac{j+1}{m}}^2 = \ffrac{\SB{m!}^2}{\SB{j!}^2\SB{\P{m-j-1}!}^2}$$

$\bspace$Done!

## Question 3

$\P{1}$

$\bspace$To prove this, we can prove $\sum_{i\in S}e_{ji}\pi_j = 1$

$$\begin{align}
\sum_{i\in S}e_{ji}\pi_j &= \sum_{i\in S}\sum_{n=1}^{\infty} e_{ji}^{\P{n}} \pi_j \\
&= \sum_{n=1}^{\infty}\sum_{i\in S} e_{ji}^{\P{n}} \pi_j \\
&= \sum_{i\in S}\sum_{n=1}^{\infty} \pi_i f_{ij}^{\P{n}} \\
&= \sum_{i\in S} \pi_i \\
&= 1
\end{align}$$

$\P{2}$

$\bspace$Consider the $i$th element of vector $\mu$, and the $i$th element after multiply the matrix $P$:

$$\begin{align}
\mu_i &= e_{ji}\\
&= \ffrac{\pi_i}{\pi_j}\\
&= \ffrac{1}{\pi_j} \sum_{k\in S} \pi_k P_{ik}\\
&= \sum_{k\in S} e_{jk} P_{ik}\\
&= \sum_{k\in S} \mu_k P_{ik}
\end{align}$$

$\bspace$And this is to say that $\vec \mu P = \vec \mu$

***

Probability and Statistics/Applied Random Process/HW/HW_09.ipynb


# 3D Snake -- Grid-independence study

---

```python
import pathlib
import numpy
from matplotlib import pyplot

from helper import read_forces, get_force_coefficients, get_time_averaged_values

%matplotlib inline
```

```python
pyplot.rc('font', family='serif', size=16)
```

We used our in-house research software PetIBM (version [0.2](https://github.com/barbagroup/PetIBM/releases/tag/v0.2)) to perform simulations of the three-dimensional flow around a snake model (a straight cylinder with an anatomically accurate cross-section of the gliding snake *Chrysopelea paradisi*).

PetIBM implements different immersed boundary methods, one of them being the decoupled immersed-boundary projection method from Li et al. (2016).
The method builds on top of the projection method of Perot (1993): an approximate block-LU decomposition of the fully discretized equations for the velocity fluxes, the pressure field, and the Lagrangian forces.

The computational domain is $30c \times 30c \times 3.2c$ ($c$ is the chord-length of the snake cross-section) with the body centered in the $x$/$y$ plane.
We use a fixed structured Cartesian to spatially discretize the domain.
The mesh is uniform in the sub-domain $\left[-0.52c, 3.48c\right]\times\left[-2c, 2c\right]\times\left[0, 3.2c\right]$, which contains the immersed boundary, and stretched to the external boundaries with a constant stretching ratio of $1.01$ in all directions.
The snake cylinder is discretized with the same resolution as the background Eulerian mesh (on which the Navier-Stokes equations are solved).

To assess grid-independence in the aerodynamic forces acting on the immersed body, we performed simulations on two different meshes:

* a "coarse" grid with about $46$ million cells ($1071\times1072\times40$) with a grid-spacing of $0.008c$ in the $x$ and $y$ directions and a grid-spacing of $0.08c$ in the $z$ direction,
* a "fine" grid, generated by halving the grid-spacing in all directions in the uniform sub-domain while maintaining the same stretching ratio outside it. That mesh contains about $233$ million cells ($1074\times1706\times80$).

(Note: a $x$/$z$ slice of the "fine" mesh has the same resolution than the mesh used for two-dimensional simulations of the same cross-section published in Krishnan et al. (2014) and Mesnard & Barba (2017).)

Spatial derivatives are approximated with second-order central differences.
The convective and diffusive terms are integrated in time with an explicit second-order Adams-Bashforth and an implicit second-order Crank-Nicolson schemes, respectively.

The time-step size was respectively set to $0.001$ and $0.0005$ for the "coarse" and "fine" meshes.

The inlet, bottom, and top boundary conditions are set with a uniform velocity of $1$ in the streamwise direction.
We use a linear convective boundary condition at the outlet of the computational domain.
We use periodic boundary conditions in the spanwise $z$-direction.

PetIBM was built with PETSc (version [3.7.4](http://ftp.mcs.anl.gov/pub/petsc/release-snapshots/petsc-lite-3.7.4.tar.gz)) to compute the numerical solution on distributed-memory architectures.
The linear systems for the velocity fluxes and for the Lagrangian boundary forces were solved using a stabilized bi-conjugate gradient method and a GMRES technique, respectively, with a Jacobi preconditioner from PETSc.
Both solvers were configured with an exit criterion based on an absolute tolerance of $10^{-6}$ for the residuals.
The Poisson system was solved on multiple GPU devices with the Nvidia AmgX library using a conjugate-gradient method preconditioned with a classical algebraic multigrid technique.
The exit criterion for the Poisson solver was also based on an absolute tolerance of $10^{-6}$ for the residuals.
To accomodate PETSc data structures with the NVIDIA AmgX library, we used AmgXWrapper.
AmgXWrapper (Chuang & Barba, 2017) is an open-source interface between PETSc and AmgX designed to handle data transfers between CPUs and GPU devices.

## Cloud environment

With the "coarse" grid, we computed $100$ non-dimensional time-units of flow simulation on a single NC24 node of Microsoft Azure ($24$ CPUs and $4$ GPU devices).
The computation was done with PetIBM-0.2, PETSc-3.7.4, OpenMPI-1.8.8, CUDA-6.5, and AmgX-1.2-build108.
At the time of running this simulation, AmgX was only available as a compiled library to Nvidia registered developers coming a free license for non-commercial use.
At the time of writing, AmgX is now an open-source project hosted on [GitHub](https://github.com/NVIDIA/AMGX).

The numerical solution after $100$ time units was then interpolated on the "fine" grid and we restarted the run (halving the time-step size).
The simulation on the "fine" grid ran on $6$ NC24r nodes ($24$ CPUs and $4$ GPU devices per node) using [Azure Batch](https://azure.microsoft.com/en-us/services/batch/) and [Batch Shipyard](https://azure.github.io/batch-shipyard/).
We used CentOS-7 base image with PetIBM-0.2, PETSc-3.7.4, Intel MPI, CUDA-8.0 ,and AmgX-2.0 (source code provided by Nvidia under NDA so we could build the library with Intel MPI).
(The NC24r instance of Microsoft Azure features RMDA connectivity to use InfiniBand network.)
The simulation on the "fine" mesh was restarted twice to maintain the runtime under $7$ days, which was the maximum time limit enforced by Azure for Batch tasks at that time.
(Since then, the maximum time limit has now been extended to $180$ days.)

<center>Table: Cost of the three-dimensional simulations.<\center>

| Run | Grid size | # Time steps | Nodes | Cost (USD) |
|:-:|:-:|:-:|:-:|:-:|
| 3D (coarse) | 46M | 100,000 | 1 NC24 | 586.3 |
| 3D (fine) | 233M | 98,614 | 6 NC24r | 7854.7 |
| 3D (fine, restart 1) | 233M | 55,200 | 6 NC24r | 3424.1 |
| 3D (fine) | 233M | 32,000 | 6 NC24r | 1923.2 |

## History of the force coefficients

The drag and Lift coefficients are defined as

$$
\begin{equation}
	C_D = \frac{F_x}{\frac{1}{2} \rho U_\infty^2 c L_z},\quad C_L = \frac{F_y}{\frac{1}{2} \rho U_\infty^2 c L_z}
\end{equation}
$$

where $F_x$ and $F_y$ are the total forces in the streamwise and cross-flow directions, respectively, $c$ is the chord length of the snake cross-section, and $L_z$ is the spanwise length.

```python
runs_dir = pathlib.Path('.').absolute() / 'runs'  # data directory
runs = {}  # dictionary to store data

# Parameters.
c = 1.0  # chord length
Lz = 3.2 * c  # spanwise length
rho = 1.0  # density
U_inf = 1.0  # freestream speed

# Read the forces from the 3D run (coarse) and get the force coefficients.
filepath = runs_dir / 'snake3d2k35-coarse' / 'forces.txt'
t, fx, fy, _ = read_forces(filepath)
cd, cl = get_force_coefficients(fx, fy, coeff=1 / (0.5 * rho * U_inf * c * Lz))
runs['3D (coarse)'] = dict(t=t, cd=cd, cl=cl)

# Read the forces from the 3D run (fine) and get the force coefficients.
filepaths = [runs_dir / ('snake3d2k35-fine' + ext) / 'forces.txt'
             for ext in ['', '-restart1', '-restart2']]
t, fx, fy, _ = read_forces(*filepaths)
t += 50.0  # apply time correction as time-step size was halved
cd, cl = get_force_coefficients(fx, fy, coeff=1 / (0.5 * rho * U_inf * c * Lz))
runs['3D (fine)'] = dict(t=t, cd=cd, cl=cl)
```

```python
# Set plotting parameters.
runs['3D (coarse)']['kwargs'] = dict(color='black')
runs['3D (fine)']['kwargs'] = dict(color='C0')
```

```python
# Plot the history of the drag and lift coefficients.
fig, (ax1, ax2) = pyplot.subplots(nrows=2, figsize=(8.0, 8.0))
ax1.set_xlabel('Non-dimensional time')
ax1.set_ylabel('Drag coefficient')
for label, data in runs.items():
    ax1.plot(data['t'], data['cd'], label=label, **data['kwargs'])
ax1.legend(ncol=3, frameon=False, loc='upper right')
ax1.set_xlim(0.0, 200.0)
ax1.set_ylim(0.6, 1.5)
ax2.set_xlabel('Non-dimensional time')
ax2.set_ylabel('Lift coefficient')
for label, data in runs.items():
    ax2.plot(data['t'], data['cl'], label=label, **data['kwargs'])
ax2.legend(ncol=3, frameon=False, loc='upper right')
ax2.set_xlim(0.0, 200.0)
ax2.set_ylim(1.0, 3.0)
fig.tight_layout()
```

## Time-averaged force coefficients

With the "coarse" grid, the simulation compute $100$ time units and we average the instantaneous force coefficients between $50$ and $100$ time units.

With the "fine" grid, the simulation started after $100$ time units; we average the aerodynamic coefficients between $100$ and $150$ time units.

```python
# Compute the time-averaged force coefficients for the coarse simulation.
t, cd, cl = (runs['3D (coarse)'][key] for key in ('t', 'cd', 'cl'))
cd_mean_ref, cl_mean_ref = get_time_averaged_values(t, cd, cl, limits=(50.0, 100.0))

# Compute the time-averaged force coefficients for the fine simulation.
t, cd, cl = (runs['3D (fine)'][key] for key in ('t', 'cd', 'cl'))
cd_mean, cl_mean = get_time_averaged_values(t, cd, cl, limits=(100.0, 150.0))

# Print the time-averaged force coefficients.
print('Case\t\t<CD>\t\t<CL>')
print(45 * '-')
# Coarse grid.
label = '3D (coarse)'
print('{}\t{:.4f}\t\t{:.4f}'.format(label, cd_mean_ref, cl_mean_ref))
# Fine grid.
label = '3D (fine)'
cd_rdiff = (cd_mean - cd_mean_ref) / cd_mean_ref * 100.0
cl_rdiff = (cl_mean - cl_mean_ref) / cl_mean_ref * 100.0
print('{}\t{:.4f} ({:.2f}%)\t{:.4f} ({:.2f}%)'
      .format(label, cd_mean, cd_rdiff, cl_mean, cl_rdiff))
```

    Case		<CD>		<CL>
    ---------------------------------------------
    3D (coarse)	0.7882		1.5236
    3D (fine)	0.8391 (6.45%)	1.5697 (3.02%)

## References

* Krishnan, A., Socha, J. J., Vlachos, P. P., & Barba, L. A. (2014). Lift and wakes of flying snakes. Physics of Fluids, 26(3), 031901.
* Li, R. Y., Xie, C. M., Huang, W. X., & Xu, C. X. (2016). An efficient immersed boundary projection method for flow over complex/moving boundaries. Computers & Fluids, 140, 122-135.
* Mesnard, O., & Barba, L. A. (2017). Reproducible and replicable computational fluid dynamics: it's harder than you think. Computing in Science & Engineering, 19(4), 44.
* Perot, J. B. (1993). An analysis of the fractional step method. Journal of Computational Physics, 108(1), 51-58.
* Pi-Yueh Chuang, & Lorena A. Barba (2017). AmgXWrapper: An interface between PETSc and the NVIDIA AmgX library. J. Open Source Software, 2(16):280, doi:10.21105/joss.00280

---


# 勾配消失問題
多層パーセプトロンでは、層の長さを長くすればするほど表現力は増します。一方で、学習が難しくなるという問題が知られています。

```python
# Tensorflowが使うCPUの数を制限します。(VMを使う場合)
%env OMP_NUM_THREADS=1
%env TF_NUM_INTEROP_THREADS=1
%env TF_NUM_INTRAOP_THREADS=1

from tensorflow.config import threading
num_threads = 1
threading.set_inter_op_parallelism_threads(num_threads)
threading.set_intra_op_parallelism_threads(num_threads)

#ライブラリのインポート
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
```

```python
# このセルはヘルパー関数です。

# i 番目のレイヤーの出力を取得する
def getActivation(model, i, x):
    from tensorflow.keras import Model
    activation = Model(model.input, model.layers[i].output)(x)
    return activation.numpy()

# iLayer 番目のレイヤーにつながる重み(wij)の勾配を取得する
def getGradientParameter(model, i, x, t):
    from tensorflow.nest import flatten
    from tensorflow import constant
    from tensorflow.python.eager import backprop

    weights = model.layers[i].weights[0]  # get only kernel (not bias)
    with backprop.GradientTape() as tape:
        pred = model(x)
        loss = model.compiled_loss(constant(t), pred)

    gradients = tape.gradient(loss, weights)
    return gradients.numpy()

# 左上: ウェイト(wij)の初期値をプロット
def plot_weights(model, x, t):
    iLayers = [0, 3, 6, 10]
    labels = [
        ' 0th layer',
        ' 3th layer',
        ' 6th layer',
        'Last layer',
    ]

    values = [model.weights[i * 2].numpy().flatten() for i in iLayers]  # get only kernel (not bias)
    plt.hist(values, bins=50, stacked=False, density=True, label=labels, histtype='step')
    plt.xlabel('weight')
    plt.ylabel('Probability density')
    plt.legend(loc='upper left', fontsize='x-small')
    plt.show()

# 左下: 各ノードの出力(sigma(ai))をプロット
def plot_nodes(model, x, t):
    iLayers = [0, 3, 6, 10]
    labels = [
        ' 0th layer',
        ' 3th layer',
        ' 6th layer',
        'Last layer',
    ]

    values = [getActivation(model, i, x).flatten() for i in iLayers]
    plt.hist(values, bins=50, stacked=False, density=True, label=labels, histtype='step')
    plt.xlabel('activation')
    plt.ylabel('Probability density')
    plt.legend(loc='upper center', fontsize='x-small')
    plt.show()

# 右上: ウェイト(wij)の微分(dE/dwij)をプロット
def plot_gradients(model, x, t):
    iLayers = [0, 3, 6, 10]
    labels = [
        ' 0th layer',
        ' 3th layer',
        ' 6th layer',
        'Last layer',
    ]

    grads = [np.abs(getGradientParameter(model, i, x, t).flatten()) for i in iLayers]
    grads = [np.log10(x[x > 0]) for x in grads]
    plt.hist(grads, bins=50, stacked=False, density=True, label=labels, histtype='step')
    plt.xlabel('log10(|gradient of weights|)')
    plt.ylabel('Probability density')
    plt.legend(loc='upper left', fontsize='x-small')
    plt.show()

```

中間層が10層という深い多層パーセプトロンを用いて、モデル中の重みパラメータの大きさ、勾配の大きさを調べてみます。

```python
from tensorflow.keras.models import Sequential
from tensorflow.keras.initializers import RandomNormal, RandomUniform
from tensorflow.keras.layers import Dense

# データセットの生成
nSamples = 1000
nFeatures = 50
x = np.random.randn(nSamples, nFeatures)  # 100個の入力変数を持つイベント1000個生成。それぞれの入力変数は正規分布に従う
t = np.random.randint(2, size=nSamples).reshape([nSamples, 1])  # 正解ラベルは0 or 1でランダムに生成

# モデルの定義
activation = 'sigmoid'  # 中間層の各ノードで使う活性化関数
initializer = RandomNormal(mean=0.0, stddev=1.0)  # weight(wij)の初期値。ここでは正規分布に従って初期化する
# initializer = RandomUniform(minval=-1, maxval=1)  # weight(wij)の初期値。ここでは一様分布に従って初期化する

# 中間層が10層の多層パーセプトロン。各レイヤーのノード数は全て50。
model = Sequential([
    Dense(50, activation=activation, kernel_initializer=initializer, input_dim=nFeatures),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(1, activation='sigmoid', kernel_initializer=initializer)
])
model.compile(loss='binary_crossentropy', optimizer='adam')

# ウェイト(wij)の初期値をプロット
plot_weights(model, x, t)

# 各ノードの出力(sigma(ai))をプロット
plot_nodes(model, x, t)

# ウェイト(wij)の微分(dE/dwij)をプロット
plot_gradients(model, x, t)

```

左上のプロットはパラメータ($w_{ij}$)の初期値を表しています。指定したとおり、各層で正規分布に従って初期化されています。
左下のプロットは活性化関数の出力($z_i$)を表しています。パラメータ($w_{ij}$)の初期値として正規分布を指定すると、シグモイド関数の出力はそのほとんどが0か1に非常に近い値となっています。シグモイド関数の微分は$\sigma^{'}(x)=\sigma(x)\cdot(1-\sigma(x))$なので、$\sigma(x)$が0や1に近いときは微分値も非常に小さな値となります。
誤差逆伝播の式は
$$
\begin{align}
\delta_{i}^{(k)} &= \sigma^{'}(a_i^{(k)}) \left( \sum_j w_{ij}^{(k+1)} \cdot \delta_{j}^{(k+1)} \right) \\
\frac{\partial E_n}{\partial w_{ij}^{(k)}}  &= \delta_{j}^{(k)} \cdot z_{i}^{(k)}
\end{align}
$$
でした。$\sigma^{'}(a_i^{(k)})$が小さいと後方の層から前方の層に誤差が伝わる際に、値が小さくなってしまいます。
中上、中下のプロットはそれぞれ各層での$\frac{\partial E_n}{\partial w_{ij}^{(k)}}$、$\delta_{i}^{(k)}$を表しています。
前方の層(0th layer)は後方の層と比較して分布の絶対値が小さくなっています。
右上と右下のプロットは各レイヤーでの$\frac{\partial E_n}{\partial w_{ij}^{(k)}}$と$\delta_{i}^{(k)}$の分布の分散を表しています。

このように誤差が前の層にいくにつれて小さくなるため、前の層が後ろの層と比較して学習が進まなくなります。
この問題は勾配消失の問題として知られています。

勾配消失はパラメータの初期値や、活性化関数を変更することによって解決・緩和することがわかっています。
Kerasの
- [初期化のページ](https://keras.io/ja/initializers/)
- [活性化関数のページ](https://keras.io/ja/activations/)

も参考にしながら、この問題の解決を試みてみましょう。

活性化関数・パラメータの初期化方法の変更はそれぞれコード中の"activation"、"initializer"を変更することによって行えます。

例えばパラメータの初期化を(-0.01, +0.01)の一様分布に変更するときは以下のコードのようにすれば良いです。

```python
from tensorflow.keras.models import Sequential
from tensorflow.keras.initializers import RandomNormal, RandomUniform
from tensorflow.keras.layers import Dense

# データセットの生成
nSamples = 1000
nFeatures = 50
x = np.random.randn(nSamples, nFeatures)  # 100個の入力変数を持つイベント1000個生成。それぞれの入力変数は正規分布に従う
t = np.random.randint(2, size=nSamples).reshape([nSamples, 1])  # 正解ラベルは0 or 1でランダムに生成

# モデルの定義
activation = 'sigmoid'  # 中間層の各ノードで使う活性化関数
# initializer = RandomNormal(mean=0.0, stddev=1.0)  # weight(wij)の初期値。ここでは正規分布に従って初期化する
initializer = RandomUniform(minval=-0.01, maxval=0.01)  # weight(wij)の初期値。ここでは一様分布に従って初期化する

# 中間層が10層の多層パーセプトロン。各レイヤーのノード数は全て50。
model = Sequential([
    Dense(50, activation=activation, kernel_initializer=initializer, input_dim=nFeatures),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(50, activation=activation, kernel_initializer=initializer),
    Dense(1, activation='sigmoid', kernel_initializer=initializer)
])

model.compile(loss='binary_crossentropy', optimizer='adam')

# ウェイト(wij)の初期値をプロット
plot_weights(model, x, t)

# 各ノードの出力(sigma(ai))をプロット
plot_nodes(model, x, t)

# ウェイト(wij)の微分(dE/dwij)をプロット
plot_gradients(model, x, t)

```

この例では活性化関数の出力が0.5付近に集中しています。
どのノードも同じ出力をしているということはノード数を増やした意味があまりなくなっており、多層パーセプトロンの表現力が十分に活かしきれていないことがわかります。
また、勾配消失も先程の例と比較して大きくなっています。


# Surfinpy

#### Tutorial 3 - Pressure

In the previous tutorials we went through the process of generating a simple phase diagram for bulk phases and introducing temperature dependence for gaseous species.  This useful however, sometimes it can be more beneficial to convert the chemical potenials (eVs) to partial presure (bar).

Chemical potential can be converted to pressure values using

\begin{align}
P & = \frac{\mu_O}{k_B T} ,
\end{align}

where P is the pressure, $\mu$ is the chemical potential of oxygen, $k_B$ is the Boltzmnann constant and T is the temperature.

```python
import matplotlib.pyplot as plt

from surfinpy import bulk_mu_vs_mu as bmvm
from surfinpy import utils as ut
from surfinpy import data

colors = ['#5B9BD5', '#4472C4', '#A5A5A5', '#772C24', '#ED7D31', '#FFC000', '#70AD47']
```

```python
bulk = data.ReferenceDataSet(cation = 1, anion = 1, energy = -92.0, funits = 10)
```

Additionally, SurfinPy has the functionality to allow you to choose which colours are used for each phase.  Specify within the DataSet class color.

```python
Bulk = data.DataSet(cation = 10, x = 0, y = 0, energy = -92.0, color=colors[0], label = "Bulk")
D = data.DataSet(cation = 10, x = 10, y = 0, energy = -310.0, color=colors[1], label = "D")
B = data.DataSet(cation = 10, x = 0, y = 10, energy = -228.0, color=colors[2], label = "B")
F = data.DataSet(cation = 10, x = 8, y = 10, energy = -398.0, color=colors[3], label = "F")
A = data.DataSet(cation = 10, x = 5, y = 20, energy = -470.0, color=colors[4], label = "A")
C = data.DataSet(cation = 10, x = 10, y = 30, energy = -706.0, color=colors[5], label = "C")
E = data.DataSet(cation = 10, x = 10, y = 50, energy = -972.0, color=colors[6], label = "E")

```

```python
data = [Bulk, A, B, C,  D, E, F]
```

```python
x_energy=-20.53412969
y_energy=-12.83725889
```

```python
CO2_exp = ut.fit_nist("ref_files/CO2.txt")[298]
Water_exp = ut.fit_nist("ref_files/H2O.txt")[298]

CO2_corrected = x_energy + CO2_exp
Water_corrected = y_energy + Water_exp

deltaX = {'Range': [ -1, 0.6],  'Label': 'CO_2'}
deltaY = {'Range': [ -1, 0.6], 'Label': 'H_2O'}
```

```python
system = bmvm.calculate(data, bulk, deltaX, deltaY, x_energy=CO2_corrected, y_energy=Water_corrected)

ax = system.plot_phase(figsize=(6, 4.5))

plt.show()
```

To convert chemical potential to pressure use the plot_pressure command and the temperature at which the pressure is calculated.  For this example we have used 298 K.

```python
import surfinpy
ax = system.plot_pressure(temperature=298, figsize=(5, 4), cbar_title="Bulk Phase System")
plt.show()
```

```python

```


# Jupyter R Notebook Demo

The notebook like an Rmd file, allows us to blend text, code and code outputs.

## Using Markdown

Just as in Rmd, we can write nicely styled text from vanilla markdown:

- yet
- another
- *styled* __list__

We can write `inline code` or language sensitively code styled blocks:

```R
# ggplot2 examples
library(ggplot2)

# create factors with value labels
mtcars$gear <- factor(mtcars$gear,levels=c(3,4,5),
   labels=c("3gears","4gears","5gears"))
```

We can write inline equations using latex — $e=mc^2$ as well as across multiple lines:

\begin{align}
\dot{x} & = \sigma(y-x) \\
\dot{y} & = \rho x - y - xz \\
\dot{z} & = -\beta z + xy
\end{align}

## Using Code

Code is entered and executed via code cells. The execution environment is determined by the notebook *kernel* attached to the notebook.

This notebook has been associated with an R kernel. Which means we can write R code in the cells:

```R
# ggplot2 examples
library(ggplot2)
```

As in Rmd documents, the state set or packages loaded by executing code in one cell is available to later executed cells.

So we can access one of the `ggplot2` loaded datasets:

```R
# create factors with value labels
mtcars$gear <- factor(mtcars$gear,levels=c(3,4,5),
   labels=c("3gears","4gears","5gears"))
mtcars$am <- factor(mtcars$am,levels=c(0,1),
   labels=c("Automatic","Manual"))
mtcars$cyl <- factor(mtcars$cyl,levels=c(4,6,8),
   labels=c("4cyl","6cyl","8cyl"))
```

As with Rmd, if the last line of a code cell returns an object, we can display it. This includes things like charts:

```R
# Kernel density plots for mpg
# grouped by number of gears (indicated by color)
g = qplot(mpg, data=mtcars, geom="density", fill=gear, alpha=I(.5),
   main="Distribution of Gas Mileage", xlab="Miles Per Gallon",
   ylab="Density")
g
```

As in any other R environment, we can change the aesthetics through the application of a particular theme.

For example, the `theme_commonslib()` theme is a theme used by the House of Commons Library; we can install it from it's Github repository:

```R
install.packages("remotes")
remotes::install_github("olihawkins/clplot")
```

And then simply add the theme to the chart:

```R
library(ggplot2)
library(clplot)

g + theme_commonslib()
```

It's some time since I looked properly at how we could embed interactive elements in a notebook.

For example, it's easy enough to generate a widget:

```R
#install.packages("leaflet")
library(leaflet)

m <- leaflet() %>%
  addTiles() %>%  # Add default OpenStreetMap map tiles
  addMarkers(lng=174.768, lat=-36.852, popup="The birthplace of R")
```

But I'm not sure if we can directly embed the result yet?

This is one of my old workarounds: save the widget HTML to a file and then load it back in via an IFrame...

```R
library(htmlwidgets)
library(IRdisplay)

saveWidget(m, 'demo.html', selfcontained = TRUE)
display_html('')
```

We can generate nicely styled tables with a bit of help...

```R
install.packages("kableExtra")
```

Generate an HTML table and display it as such, via a slight hack...

```R
library(knitr)
library(kableExtra)

kable(summary(cars)) %>%
  kable_styling(bootstrap_options = c("striped", "hover")) %>%
    as.character() %>% display_html()
```

## Loading Data From APIs

This example is cribbed in part from https://medium.com/@traffordDataLab/querying-apis-in-r-39029b73d5f1 and uses the UK Police API, which returns JSON data of the form: https://data.police.uk/api/crimes-street/burglary?lat=52.0406&lng=-0.7594&date=2018-05

```R
library(tidyverse)
library(httr)
library(jsonlite)
```

    ── Attaching packages ─────────────────────────────────────── tidyverse 1.2.1 ──

    ✔ tibble  2.1.1       ✔ purrr   0.3.2
    ✔ tidyr   0.8.3       ✔ dplyr   0.8.0.1
    ✔ readr   1.3.1       ✔ stringr 1.4.0
    ✔ tibble  2.1.1       ✔ forcats 0.4.0

    ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
    ✖ dplyr::filter()     masks stats::filter()
    ✖ dplyr::group_rows() masks kableExtra::group_rows()
    ✖ dplyr::lag()        masks stats::lag()

    Attaching package: ‘jsonlite’

    The following object is masked from ‘package:purrr’:

        flatten

We can make a query onto the API and get a JSON file back:

```R
path <- "https://data.police.uk/api/crimes-street/burglary?"

request <- GET(url = path,
               query = list(
                 lat = 52.0406,
                 lng = -0.7594,
                 date = "2018-05")
               )
```

Decoding the JSON returns everything as chars...:

```R
response <- content(request, as = "text", encoding = "UTF-8")
df <- fromJSON(response, flatten = TRUE) %>%
  data.frame()

head(df)
```

A data.frame: 6 × 13

	category	location_type	context	persistent_id	id	location_subtype	month	location.latitude	location.longitude	location.street.id	location.street.name	outcome_status.category	outcome_status.date

	<chr>	<chr>	<chr>	<chr>	<int>	<chr>	<chr>	<chr>	<chr>	<int>	<chr>	<chr>	<chr>

1	burglary	Force		fa6b515b3d9658626797bb6db06caefcab8d05e4ac387914c12d3077368b402d	65373360		2018-05	52.052319	-0.756575	1213725	On or near Bryony Place      	Investigation complete; no suspect identified	2018-08

2	burglary	Force		7480cb4911243cc2f48669e71fa2fb7320f2197b6ccb6390be06281c898b5074	65379707		2018-05	52.044662	-0.767213	1213735	On or near Wandsworth Place  	Investigation complete; no suspect identified	2018-08

3	burglary	Force		6ffc8736dc157b33cf0d9f8d3a858ad1d4d41fc92331e34145be0ef465cf8eab	65382710		2018-05	52.033215	-0.746623	1213362	On or near Trevone Court     	Unable to prosecute suspect                  	2018-08

4	burglary	Force		133441a73f4690a1d4e1873b7696fdd0240b1c330cc84337bf6243435d41159d	65370281		2018-05	52.027977	-0.760603	1212564	On or near Rashleigh Place   	Investigation complete; no suspect identified	2018-08

5	burglary	Force		1e38aec62bd62383218f27bbbe41567c28d39900a5bb965e00b0a4e5e6db4844	65371736		2018-05	52.044649	-0.758304	1213743	On or near North Tenth Street	Unable to prosecute suspect                  	2018-08

6	burglary	Force		04c25368c11760f02c45b701d21e1583fba2e31bb53ad4e1aa4c9dd55ba5c412	65377770		2018-05	52.030466	-0.743479	1213353	On or near Ashby             	Investigation complete; no suspect identified	2018-05

So I'm going to be lazy in how I cast the lat/long to a numeric and use a package to help:

```R
#install.packages("hablar")
library(hablar)
```

The cast is then just a `convert()` pipeline step:

```R
df <- select(df,
             month, category,
             location = location.street.name,
             long = location.longitude,
             lat = location.latitude) %>%
      convert(num(long, lat))

head(df)
```

A tibble: 6 × 5

month	category	location	long	lat

<chr>	<chr>	<chr>	<dbl>	<dbl>

2018-05	burglary	On or near Bryony Place      	-0.756575	52.05232

2018-05	burglary	On or near Wandsworth Place  	-0.767213	52.04466

2018-05	burglary	On or near Trevone Court     	-0.746623	52.03321

2018-05	burglary	On or near Rashleigh Place   	-0.760603	52.02798

2018-05	burglary	On or near North Tenth Street	-0.758304	52.04465

2018-05	burglary	On or near Ashby             	-0.743479	52.03047

```R
m  = leaflet(df) %>% addTiles() %>% addMarkers(
  clusterOptions = markerClusterOptions(), popup = ~as.character(location)
)

saveWidget(m, 'demo.html', selfcontained = TRUE)
display_html('')
```

    Assuming "long" and "lat" are longitude and latitude, respectively

```R

```


# Start-to-Finish Example: Gaussian pulse initial data for a massless scalar field, in Curvilinear coordinates

## Authors: Leonardo Werneck & Zach Etienne

# This tutorial notebook sets up initial data for a massless scalar field in *spherical-like coordinates*, using the *Numerical* ADM Spherical to BSSN Curvilinear initial data module (numerical = BSSN $\lambda^i$'s are computed using finite-difference derivatives instead of exact expressions).

**Notebook Status:**  Validated

**Validation Notes:** This module has been validated to exhibit convergence to zero of the Hamiltonian constraint violation at the expected order to the exact solution (see [plots](#convergence) at bottom).

## NRPy+ module to obtain the initial data used in this tutorial: **[ScalarFieldCollapse/ScalarField_InitialData.py](../edit/ScalarFieldCollapse/ScalarField_InitialData.py)**

## References

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

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

* [Step 1](#set_nrpy_core): Set core NRPy+ parameters for numerical grids and reference metric
* [Step 2](#adm_id_scalar_field): Set up ADM initial data for the scalar field
* [Step 3](#adm_id_spacetime): Convert ADM initial data to BSSN-in-curvilinear coordinates
* [Step 4](#validate): Validating that the scalar field initial data satisfies the Hamiltonian constraint
    * [Step 4.a](#ham_const_output): Output the Hamiltonian constraint
    * [Step 4.b](#bc_functs): Set up boundary condition functions for chosen singular, curvilinear coordinate system
    * [Step 4.c](#enforce3metric): Enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint
    * [Step 4.d](#cparams_rfm_and_domainsize): Output C codes needed for declaring and setting Cparameters; also set `free_parameters.h`
* [Step 5](#mainc): `ScalarField_Playground.c`: The Main C Code
* [Step 6](#convergence): Validation: Convergence of numerical errors of the Scalar Field Collapse initial data (Hamiltonian constraint violation) to zero
* [Step 7](#latex_pdf_output): Output this module as $\LaTeX$-formatted PDF file

# Step 1: Set core NRPy+ parameters for numerical grids and reference metric \[Back to [top](#toc)\]
$$\label{set_nrpy_core}$$

```python
# Step P1: Import needed NRPy+ core modules:
from outputC import lhrh,outCfunction,outputC # NRPy+: Core C code output module
import NRPy_param_funcs as par   # NRPy+: Parameter interface
import sympy as sp               # SymPy: The Python computer algebra package upon which NRPy+ depends
import finite_difference as fin  # NRPy+: Finite difference C code generation module
import grid as gri               # NRPy+: Functions having to do with numerical grids
import indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
import reference_metric as rfm   # NRPy+: Reference metric support
import cmdline_helper as cmd     # NRPy+: Multi-platform Python command-line interface
import shutil, os, sys           # Standard Python modules for multiplatform OS-level functions

# Step P2: Create C code output directory:
Ccodesdir = os.path.join("ScalarFieldID_Ccodes/")
# First remove C code output directory if it exists
# Courtesy https://stackoverflow.com/questions/303200/how-do-i-remove-delete-a-folder-that-is-not-empty
# !rm -r ScalarWaveCurvilinear_Playground_Ccodes
shutil.rmtree(Ccodesdir, ignore_errors=True)
# Then create a fresh directory
cmd.mkdir(Ccodesdir)

# Step P3: Create executable output directory:
outdir = os.path.join(Ccodesdir,"output/")
cmd.mkdir(outdir)

thismodule = "ScalarFieldCollapse_ID_setup"

# Step 1: Set the spatial dimension parameter
#         to three this time, and then read
#         the parameter as DIM.
par.set_parval_from_str("grid::DIM",3)
DIM = par.parval_from_str("grid::DIM")

# Then we set the coordinate system for the numerical grid
par.set_parval_from_str("reference_metric::CoordSystem","SinhSpherical")
rfm.reference_metric() # Create ReU, ReDD needed for rescaling B-L initial data, generating BSSN RHSs, etc.

# Step 2: Set some core parameters, including CoordSystem MoL timestepping algorithm,
#                                 FD order, floating point precision, and CFL factor:
# Choices are: Spherical, SinhSpherical, SinhSphericalv2, Cylindrical, SinhCylindrical,
#              SymTP, SinhSymTP
CoordSystem     = "SinhSpherical"

# Step 2.a: Set defaults for Coordinate system parameters.
#           These are perhaps the most commonly adjusted parameters,
#           so we enable modifications at this high level.
domain_size = 15

# sinh_width sets the default value for:
#   * SinhSpherical's params.SINHW
#   * SinhCylindrical's params.SINHW{RHO,Z}
#   * SinhSymTP's params.SINHWAA
sinh_width      = 0.2 # If Sinh* coordinates chosen

# sinhv2_const_dr sets the default value for:
#   * SinhSphericalv2's params.const_dr
#   * SinhCylindricalv2's params.const_d{rho,z}
sinhv2_const_dr = 0.05# If Sinh*v2 coordinates chosen

# SymTP_bScale sets the default value for:
#   * SinhSymTP's params.bScale
SymTP_bScale    = 0.5 # If SymTP chosen

# Step 2.b: Set the order of spatial finite difference derivatives;
#           and the core data type.
FD_order  = 4        # Finite difference order: even numbers only, starting with 2. 12 is generally unstable
REAL      = "double" # Best to use double here.

# Step 3: Set the coordinate system for the numerical grid
par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem)
rfm.reference_metric() # Create ReU, ReDD needed for rescaling B-L initial data, generating BSSN RHSs, etc.

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

# Step 5: Set the directions=1,2 (theta,phi) axis to be the symmetry axis;
#         This sets all spatial derivatives in the thetha and phi directions to zero.
par.set_parval_from_str("indexedexp::symmetry_axes","12")

# Step 6: The MoLtimestepping interface is only used for memory allocation/deallocation
import MoLtimestepping.C_Code_Generation as MoL
from MoLtimestepping.RK_Butcher_Table_Dictionary import Butcher_dict
RK_method = "Euler" # DOES NOT MATTER; Again MoL interface is only used for memory alloc/dealloc.
RK_order  = Butcher_dict[RK_method][1]
cmd.mkdir(os.path.join(Ccodesdir,"MoLtimestepping/"))
MoL.MoL_C_Code_Generation(RK_method, RHS_string      = "", post_RHS_string = "",
    outdir = os.path.join(Ccodesdir,"MoLtimestepping/"))
```

# Step 2: Set up ADM initial data for the scalar field \[Back to [top](#toc)\]
$$\label{adm_id_scalar_field}$$

As documented [in the time-symmetric Gaussian pulse scalar field initial data NRPy+ tutorial notebook](Tutorial-ScalarField-Gaussian_pulse_initial_data-time_symmetric.ipynb), we generate the scalar field initial data and output it to a file.

Our initial data solver requires space matrices methods which are implemented in SciPy, so we make sure that it is installed.

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

Next we call the [`ScalarField.ScalarField_time_symmetric_initial_data.py()` function](../edit/ScalarField/ScalarField_time_symmetric_initial_data.py) ([NRPy+ tutorial notebook](Tutorial-ScalarField-Gaussian_pulse_initial_data-time_symmetric.ipynb)) to set up the initial data, using the default parameters for initial data. This function outputs the solution to a file named "outputSFID.txt".

```python
# Step 2.a: Import necessary Python and NRPy+ modules
import ScalarField.ScalarField_InitialData as sfid

# Step 2.b: Set the initial data parameters
outputfilename      = os.path.join(outdir,"SFID.txt")
initial_data_family = "Gaussian_pulse"
pulse_amplitude     = 0.1
pulse_center        = 0
pulse_width         = 1
Nr                  = 100000
rmax                = domain_size*1.5

# Step 2.c: Generate the initial data
sfid.ScalarField_InitialData(outputfilename,Ccodesdir,initial_data_family,
                             pulse_amplitude,pulse_center,pulse_width,Nr,rmax)
```

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

    Type of initial condition: Scalar field: "Gaussian" Shell
                             ADM quantities: Time-symmetric
                            Lapse condition: Pre-collapsed
    Parameters: amplitude         = 0.1,
                center            = 0,
                width             = 1,
                domain size       = 22.5,
                number of points  = 100000,
                Initial data file = ScalarFieldID_Ccodes/output/SFID.txt.

    Wrote to file ScalarFieldID_Ccodes/ID_scalar_field_ADM_quantities.h
    Wrote to file ScalarFieldID_Ccodes/ID_scalar_field_spherical.h
    Appended to file "ScalarFieldID_Ccodes/ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2.h"
    Appended to file "ScalarFieldID_Ccodes/ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2.h"

# Step 3: Convert ADM initial data to BSSN-in-curvilinear coordinates \[Back to [top](#toc)\]
$$\label{adm_id_spacetime}$$

This is an automated process, taken care of by [`BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear`](../edit/BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py), and documented [in this tutorial notebook](Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb).

```python
import BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear as AtoBnum
AtoBnum.Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear("Spherical","ID_scalar_field_ADM_quantities",
                                                               Ccodesdir=Ccodesdir,loopopts="")
```

    Output C function ID_BSSN_lambdas() to file ScalarFieldID_Ccodes/ID_BSSN_lambdas.h
    Output C function ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs() to file ScalarFieldID_Ccodes/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h
    Output C function ID_BSSN__ALL_BUT_LAMBDAs() to file ScalarFieldID_Ccodes/ID_BSSN__ALL_BUT_LAMBDAs.h

# Step 4: Validating that the scalar field initial data satisfies the Hamiltonian constraint \[Back to [top](#toc)\]
$$\label{validate}$$

We will validate that the scalar field initial data satisfies the Hamiltonian constraint, modulo numerical finite differencing error.

## Step 4.a: Output the Hamiltonian constraint \[Back to [top](#toc)\]
$$\label{ham_const_output}$$

First output the Hamiltonian constraint [as documented in the corresponding NRPy+ tutorial notebook](Tutorial-BSSN_constraints.ipynb)

```python
# Enable rfm_precompute infrastructure, which results in
#   BSSN RHSs that are free of transcendental functions,
#   even in curvilinear coordinates, so long as
#   ConformalFactor is set to "W" (default).
cmd.mkdir(os.path.join(Ccodesdir,"rfm_files/"))
par.set_parval_from_str("reference_metric::enable_rfm_precompute","True")
par.set_parval_from_str("reference_metric::rfm_precompute_Ccode_outdir",os.path.join(Ccodesdir,"rfm_files/"))

import BSSN.Enforce_Detgammabar_Constraint as EGC
enforce_detg_constraint_symb_expressions = EGC.Enforce_Detgammabar_Constraint_symb_expressions()

# Now register the Hamiltonian as a gridfunction.
H = gri.register_gridfunctions("AUX","H")

# Declare scalar field gridfunctions
import ScalarField.ScalarField_declare_gridfunctions as sfgfs
sf,sfM = sfgfs.declare_scalar_field_gridfunctions_if_not_declared_already()

# Compute the scalar field energy-momentum tensor
import ScalarField.ScalarField_Tmunu as sfTmunu
sfTmunu.ScalarField_Tmunu()
T4UU = sfTmunu.T4UU

# Then define the Hamiltonian constraint and output the optimized C code.
import BSSN.BSSN_constraints as bssncon
import BSSN.BSSN_stress_energy_source_terms as Bsest
bssncon.BSSN_constraints(add_T4UUmunu_source_terms=False)
Bsest.BSSN_source_terms_for_BSSN_constraints(T4UU)
bssncon.H += Bsest.sourceterm_H

# Now that we are finished with all the rfm hatted
#           quantities in generic precomputed functional
#           form, let's restore them to their closed-
#           form expressions.
par.set_parval_from_str("reference_metric::enable_rfm_precompute","False") # Reset to False to disable rfm_precompute.
rfm.ref_metric__hatted_quantities()

desc="Evaluate the Hamiltonian constraint"
name="Hamiltonian_constraint"
outCfunction(
    outfile  = os.path.join(Ccodesdir,name+".h"), desc=desc, name=name,
    params   = """rfm_struct *restrict rfmstruct,const paramstruct *restrict params,
                  REAL *restrict in_gfs, REAL *restrict auxevol_gfs, REAL *restrict aux_gfs""",
    body     = fin.FD_outputC("returnstring",lhrh(lhs=gri.gfaccess("aux_gfs", "H"), rhs=bssncon.H),
                              params="outCverbose=False").replace("IDX4","IDX4S"),
    loopopts = "InteriorPoints,Enable_rfm_precompute")
```

    Output C function Hamiltonian_constraint() to file ScalarFieldID_Ccodes/Hamiltonian_constraint.h

## Step 4.b: Set up boundary condition functions for chosen singular, curvilinear coordinate system \[Back to [top](#toc)\]
$$\label{bc_functs}$$

Next apply singular, curvilinear coordinate boundary conditions [as documented in the corresponding NRPy+ tutorial notebook](Tutorial-Start_to_Finish-Curvilinear_BCs.ipynb).

```python
import CurviBoundaryConditions.CurviBoundaryConditions as cbcs
cbcs.Set_up_CurviBoundaryConditions(os.path.join(Ccodesdir,"boundary_conditions/"),Cparamspath=os.path.join("../"))
```

    Wrote to file "ScalarFieldID_Ccodes/boundary_conditions/parity_conditions_symbolic_dot_products.h"
    Evolved parity: ( aDD00:4, aDD01:5, aDD02:6, aDD11:7, aDD12:8, aDD22:9,
        alpha:0, betU0:1, betU1:2, betU2:3, cf:0, hDD00:4, hDD01:5, hDD02:6,
        hDD11:7, hDD12:8, hDD22:9, lambdaU0:1, lambdaU1:2, lambdaU2:3, sf:0,
        sfM:0, trK:0, vetU0:1, vetU1:2, vetU2:3 )
    Auxiliary parity: ( H:0 )

    Wrote to file "ScalarFieldID_Ccodes/boundary_conditions/EigenCoord_Cart_to_xx.h"

## Step 4.c: Enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint \[Back to [top](#toc)\]
$$\label{enforce3metric}$$

Then enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint (Eq. 53 of [Ruchlin, Etienne, and Baumgarte (2018)](https://arxiv.org/abs/1712.07658)), as [documented in the corresponding NRPy+ tutorial notebook](Tutorial-BSSN-Enforcing_Determinant_gammabar_equals_gammahat_Constraint.ipynb).

Applying curvilinear boundary conditions should affect the initial data at the outer boundary, and will in general cause the $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint to be violated there. Thus after we apply these boundary conditions, we must always call the routine for enforcing the $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint:

```python
# Set up the C function for the det(gammahat) = det(gammabar)
EGC.output_Enforce_Detgammabar_Constraint_Ccode(Ccodesdir,
                                                exprs=enforce_detg_constraint_symb_expressions)
```

    Output C function enforce_detgammabar_constraint() to file ScalarFieldID_Ccodes/enforce_detgammabar_constraint.h

## Step 4.d: Output C codes needed for declaring and setting Cparameters; also set `free_parameters.h` \[Back to [top](#toc)\]
$$\label{cparams_rfm_and_domainsize}$$

Based on declared NRPy+ Cparameters, first we generate `declare_Cparameters_struct.h`, `set_Cparameters_default.h`, and `set_Cparameters[-SIMD].h`.

Then we output `free_parameters.h`, which sets initial data parameters, as well as grid domain & reference metric parameters, applying `domain_size` and `sinh_width`/`SymTP_bScale` (if applicable) as set above

```python
# Step 3.d.i: Generate declare_Cparameters_struct.h, set_Cparameters_default.h, and set_Cparameters[-SIMD].h
par.generate_Cparameters_Ccodes(os.path.join(Ccodesdir))

# Step 3.d.ii: Set free_parameters.h
# Output to $Ccodesdir/free_parameters.h reference metric parameters based on generic
#    domain_size,sinh_width,sinhv2_const_dr,SymTP_bScale,
#    parameters set above.
rfm.out_default_free_parameters_for_rfm(os.path.join(Ccodesdir,"free_parameters.h"),
                                        domain_size,sinh_width,sinhv2_const_dr,SymTP_bScale)

# Step 3.d.iii: Generate set_Nxx_dxx_invdx_params__and__xx.h:
rfm.set_Nxx_dxx_invdx_params__and__xx_h(Ccodesdir)

# Step 3.d.iv: Generate xx_to_Cart.h, which contains xx_to_Cart() for
#               (the mapping from xx->Cartesian) for the chosen
#               CoordSystem:
rfm.xx_to_Cart_h("xx_to_Cart","./set_Cparameters.h",os.path.join(Ccodesdir,"xx_to_Cart.h"))

# Step 3.d.v: Generate declare_Cparameters_struct.h, set_Cparameters_default.h, and set_Cparameters[-SIMD].h
par.generate_Cparameters_Ccodes(os.path.join(Ccodesdir))
```

# Step 5: `ScalarField_Playground.c`: The Main C Code \[Back to [top](#toc)\]
$$\label{mainc}$$

```python
# Part P0: Define REAL, set the number of ghost cells NGHOSTS (from NRPy+'s FD_CENTDERIVS_ORDER)

with open(os.path.join(Ccodesdir,"ScalarField_Playground_REAL__NGHOSTS.h"), "w") as file:
    file.write("""
// Part P0.a: Set the number of ghost cells, from NRPy+'s FD_CENTDERIVS_ORDER
#define NGHOSTS """+str(int(FD_order/2)+1)+"""
// Part P0.b: Set the numerical precision (REAL) to double, ensuring all floating point
//            numbers are stored to at least ~16 significant digits
#define REAL """+REAL+"\n")
```

```python
%%writefile $Ccodesdir/ScalarField_Playground.c

// Step P0: Define REAL and NGHOSTS. This header is generated by NRPy+.
#include "ScalarField_Playground_REAL__NGHOSTS.h"

#include "rfm_files/rfm_struct__declare.h"

#include "declare_Cparameters_struct.h"

// Step P1: Import needed header files
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#ifndef M_PI
#define M_PI 3.141592653589793238462643383279502884L
#endif
#ifndef M_SQRT1_2
#define M_SQRT1_2 0.707106781186547524400844362104849039L
#endif

// Step P2: Declare the IDX4S(gf,i,j,k) macro, which enables us to store 4-dimensions of
//           data in a 1D array. In this case, consecutive values of "i"
//           (all other indices held to a fixed value) are consecutive in memory, where
//           consecutive values of "j" (fixing all other indices) are separated by
//           Nxx_plus_2NGHOSTS0 elements in memory. Similarly, consecutive values of
//           "k" are separated by Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1 in memory, etc.
#define IDX4S(g,i,j,k) \
( (i) + Nxx_plus_2NGHOSTS0 * ( (j) + Nxx_plus_2NGHOSTS1 * ( (k) + Nxx_plus_2NGHOSTS2 * (g) ) ) )
#define IDX4ptS(g,idx) ( (idx) + (Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2) * (g) )
#define IDX3S(i,j,k) ( (i) + Nxx_plus_2NGHOSTS0 * ( (j) + Nxx_plus_2NGHOSTS1 * ( (k) ) ) )
#define LOOP_REGION(i0min,i0max, i1min,i1max, i2min,i2max) \
  for(int i2=i2min;i2<i2max;i2++) for(int i1=i1min;i1<i1max;i1++) for(int i0=i0min;i0<i0max;i0++)
#define LOOP_ALL_GFS_GPS(ii) _Pragma("omp parallel for") \
  for(int (ii)=0;(ii)<Nxx_plus_2NGHOSTS_tot*NUM_EVOL_GFS;(ii)++)

// Step P3: Set gridfunctions macros
#include "boundary_conditions/gridfunction_defines.h"

// Step P4: Set xx_to_Cart(const paramstruct *restrict params,
//                     REAL *restrict xx[3],
//                     const int i0,const int i1,const int i2,
//                     REAL xCart[3]),
//           which maps xx->Cartesian via
//    {xx[0][i0],xx[1][i1],xx[2][i2]}->{xCart[0],xCart[1],xCart[2]}
#include "xx_to_Cart.h"

// Step P5: Defines set_Nxx_dxx_invdx_params__and__xx(const int EigenCoord, const int Nxx[3],
//                                       paramstruct *restrict params, REAL *restrict xx[3]),
//          which sets params Nxx,Nxx_plus_2NGHOSTS,dxx,invdx, and xx[] for
//          the chosen Eigen-CoordSystem if EigenCoord==1, or
//          CoordSystem if EigenCoord==0.
#include "set_Nxx_dxx_invdx_params__and__xx.h"

// Step P6: Include basic functions needed to impose curvilinear
//          parity and boundary conditions.
#include "boundary_conditions/CurviBC_include_Cfunctions.h"

// Step P7: Include function for enforcing detgammabar constraint.
#include "enforce_detgammabar_constraint.h"

// Step P8: Declare initial data input struct:
//          stores data from initial data solver,
//          so they can be put on the numerical grid.
typedef struct __ID_inputs {
    int interp_stencil_size;
    int numlines_in_file;
    REAL *r_arr,*sf_arr,*psi4_arr,*alpha_arr;
} ID_inputs;

// Part P9: Declare all functions for setting up ScalarField initial data.
/* Routines to interpolate the ScalarField solution and convert to ADM & T^{munu}: */
#include "../ScalarField/ScalarField_interp.h"
#include "ID_scalar_field_ADM_quantities.h"
#include "ID_scalar_field_spherical.h"
#include "ID_scalarfield_xx0xx1xx2_to_BSSN_xx0xx1xx2.h"
#include "ID_scalarfield.h"

/* Next perform the basis conversion and compute all needed BSSN quantities */
#include "ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h"
#include "ID_BSSN__ALL_BUT_LAMBDAs.h"
#include "ID_BSSN_lambdas.h"

// Step P10: Set the generic driver function for setting up BSSN initial data
void initial_data(const paramstruct *restrict params,const bc_struct *restrict bcstruct,
                  const rfm_struct *restrict rfmstruct,
                  REAL *restrict xx[3], REAL *restrict auxevol_gfs, REAL *restrict in_gfs) {
#include "set_Cparameters.h"

    // Step 1: Set up ScalarField initial data
    // Step 1.a: Read ScalarField initial data from data file
    // Open the data file:
    char filename[100];
    sprintf(filename,"./SFID.txt");
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        fprintf(stderr,"ERROR: could not open file %s\n",filename);
        exit(1);
    }
    // Count the number of lines in the data file:
    int numlines_in_file = count_num_lines_in_file(fp);
    // Allocate space for all data arrays:
    REAL *r_arr     = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *sf_arr    = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *psi4_arr  = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *alpha_arr = (REAL *)malloc(sizeof(REAL)*numlines_in_file);

    // Read from the data file, filling in arrays
    // read_datafile__set_arrays() may be found in ScalarField/ScalarField_interp.h
    if(read_datafile__set_arrays(fp,r_arr,sf_arr,psi4_arr,alpha_arr) == 1) {
        fprintf(stderr,"ERROR WHEN READING FILE %s!\n",filename);
        exit(1);
    }
    fclose(fp);

    const int interp_stencil_size = 12;
    ID_inputs SF_in;
    SF_in.interp_stencil_size = interp_stencil_size;
    SF_in.numlines_in_file    = numlines_in_file;
    SF_in.r_arr               = r_arr;
    SF_in.sf_arr              = sf_arr;
    SF_in.psi4_arr            = psi4_arr;
    SF_in.alpha_arr           = alpha_arr;

    // Step 1.b: Interpolate data from data file to set BSSN gridfunctions
    ID_scalarfield(params,xx,SF_in, in_gfs);
    ID_BSSN__ALL_BUT_LAMBDAs(params,xx,SF_in, in_gfs);
    apply_bcs_curvilinear(params, bcstruct, NUM_EVOL_GFS, evol_gf_parity, in_gfs);
    enforce_detgammabar_constraint(rfmstruct, params,                   in_gfs);
    ID_BSSN_lambdas(params, xx, in_gfs);
    apply_bcs_curvilinear(params, bcstruct, NUM_EVOL_GFS, evol_gf_parity, in_gfs);
    enforce_detgammabar_constraint(rfmstruct, params,                   in_gfs);

    free(r_arr);
    free(sf_arr);
    free(psi4_arr);
    free(alpha_arr);
}

// Step P11: Declare function for evaluating Hamiltonian constraint (diagnostic)
#include "Hamiltonian_constraint.h"

// main() function:
// Step 0: Read command-line input, set up grid structure, allocate memory for gridfunctions, set up coordinates
// Step 1: Set up initial data to an exact solution
// Step 2: Start the timer, for keeping track of how fast the simulation is progressing.
// Step 3: Integrate the initial data forward in time using the chosen RK-like Method of
//         Lines timestepping algorithm, and output periodic simulation diagnostics
// Step 3.a: Output 2D data file periodically, for visualization
// Step 3.b: Step forward one timestep (t -> t+dt) in time using
//           chosen RK-like MoL timestepping algorithm
// Step 3.c: If t=t_final, output conformal factor & Hamiltonian
//           constraint violation to 2D data file
// Step 3.d: Progress indicator printing to stderr
// Step 4: Free all allocated memory
int main(int argc, const char *argv[]) {
    paramstruct params;
#include "set_Cparameters_default.h"

    // Step 0a: Read command-line input, error out if nonconformant
    if((argc != 4) || atoi(argv[1]) < NGHOSTS || atoi(argv[2]) < 2 || atoi(argv[3]) < 2 /* FIXME; allow for axisymmetric sims */) {
        fprintf(stderr,"Error: Expected three command-line arguments: ./ScalarField_Playground Nx0 Nx1 Nx2,\n");
        fprintf(stderr,"where Nx[0,1,2] is the number of grid points in the 0, 1, and 2 directions.\n");
        fprintf(stderr,"Nx[] MUST BE larger than NGHOSTS (= %d)\n",NGHOSTS);
        exit(1);
    }
    // Step 0b: Set up numerical grid structure, first in space...
    const int Nxx[3] = { atoi(argv[1]), atoi(argv[2]), atoi(argv[3]) };
    if(Nxx[0]%2 != 0 || Nxx[1]%2 != 0 || Nxx[2]%2 != 0) {
        fprintf(stderr,"Error: Cannot guarantee a proper cell-centered grid if number of grid cells not set to even number.\n");
        fprintf(stderr,"       For example, in case of angular directions, proper symmetry zones will not exist.\n");
        exit(1);
    }

    // Step 0c: Set free parameters, overwriting Cparameters defaults
    //          by hand or with command-line input, as desired.
#include "free_parameters.h"

   // Step 0d: Uniform coordinate grids are stored to *xx[3]
    REAL *xx[3];
    // Step 0d.i: Set bcstruct
    bc_struct bcstruct;
    {
        int EigenCoord = 1;
        // Step 0d.ii: Call set_Nxx_dxx_invdx_params__and__xx(), which sets
        //             params Nxx,Nxx_plus_2NGHOSTS,dxx,invdx, and xx[] for the
        //             chosen Eigen-CoordSystem.
        set_Nxx_dxx_invdx_params__and__xx(EigenCoord, Nxx, &params, xx);
        // Step 0d.iii: Set Nxx_plus_2NGHOSTS_tot
#include "set_Cparameters-nopointer.h"
        const int Nxx_plus_2NGHOSTS_tot = Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2;
        // Step 0e: Find ghostzone mappings; set up bcstruct
#include "boundary_conditions/driver_bcstruct.h"
        // Step 0e.i: Free allocated space for xx[][] array
        for(int i=0;i<3;i++) free(xx[i]);
    }

    // Step 0f: Call set_Nxx_dxx_invdx_params__and__xx(), which sets
    //          params Nxx,Nxx_plus_2NGHOSTS,dxx,invdx, and xx[] for the
    //          chosen (non-Eigen) CoordSystem.
    int EigenCoord = 0;
    set_Nxx_dxx_invdx_params__and__xx(EigenCoord, Nxx, &params, xx);

    // Step 0g: Set all C parameters "blah" for params.blah, including
    //          Nxx_plus_2NGHOSTS0 = params.Nxx_plus_2NGHOSTS0, etc.
#include "set_Cparameters-nopointer.h"
    const int Nxx_plus_2NGHOSTS_tot = Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2;

    // Step 0j: Error out if the number of auxiliary gridfunctions outnumber evolved gridfunctions.
    //              This is a limitation of the RK method. You are always welcome to declare & allocate
    //              additional gridfunctions by hand.
    if(NUM_AUX_GFS > NUM_EVOL_GFS) {
        fprintf(stderr,"Error: NUM_AUX_GFS > NUM_EVOL_GFS. Either reduce the number of auxiliary gridfunctions,\n");
        fprintf(stderr,"       or allocate (malloc) by hand storage for *diagnostic_output_gfs. \n");
        exit(1);
    }

    // Step 0k: Allocate memory for gridfunctions
#include "MoLtimestepping/RK_Allocate_Memory.h"
    REAL *restrict auxevol_gfs = (REAL *)malloc(sizeof(REAL) * NUM_AUXEVOL_GFS * Nxx_plus_2NGHOSTS_tot);

    // Step 0l: Set up precomputed reference metric arrays
    // Step 0l.i: Allocate space for precomputed reference metric arrays.
#include "rfm_files/rfm_struct__malloc.h"

    // Step 0l.ii: Define precomputed reference metric arrays.
    {
    #include "set_Cparameters-nopointer.h"
    #include "rfm_files/rfm_struct__define.h"
    }

    // Step 1: Set up initial data
    initial_data(&params,&bcstruct, &rfmstruct, xx, auxevol_gfs, y_n_gfs);
    // Step 1a: Compute the energy-momentum tensor

    // Step 1b: Apply boundary conditions, as initial data
    //          are sometimes ill-defined in ghost zones.
    //          E.g., spherical initial data might not be
    //          properly defined at points where r=-1.
    apply_bcs_curvilinear(&params, &bcstruct, NUM_EVOL_GFS,evol_gf_parity, y_n_gfs);
    enforce_detgammabar_constraint(&rfmstruct, &params, y_n_gfs);

    // Evaluate Hamiltonian constraint violation
    Hamiltonian_constraint(&rfmstruct, &params, y_n_gfs,auxevol_gfs, diagnostic_output_gfs);
    char filename[100];
    sprintf(filename,"out%d.txt",Nxx[0]);
    FILE *out1D = fopen(filename, "w");
    const int i1mid = Nxx_plus_2NGHOSTS1/2;
    const int i2mid = Nxx_plus_2NGHOSTS2/2;
    for(int i0=NGHOSTS;i0<Nxx_plus_2NGHOSTS0-NGHOSTS;i0++) {
        REAL xCart[3];
        xx_to_Cart(&params,xx,i0,i1mid,i2mid,xCart);
        int idx = IDX3S(i0,i1mid,i2mid);
        REAL rr = sqrt( xCart[0]*xCart[0] + xCart[1]*xCart[1] + xCart[2]*xCart[2] );
        fprintf(out1D,"%e %e\n",rr,log10(fabs(diagnostic_output_gfs[IDX4ptS(HGF,idx)])));
    }
    fclose(out1D);

    // Step 4: Free all allocated memory
#include "rfm_files/rfm_struct__freemem.h"
#include "boundary_conditions/bcstruct_freemem.h"
#include "MoLtimestepping/RK_Free_Memory.h"
    free(auxevol_gfs);
    for(int i=0;i<3;i++) free(xx[i]);

    return 0;
}
```

    Writing ScalarFieldID_Ccodes//ScalarField_Playground.c

```python
import cmdline_helper as cmd
cmd.C_compile(os.path.join(Ccodesdir,"ScalarField_Playground.c"),
              os.path.join(outdir,"ScalarField_Playground"),compile_mode="optimized")

os.chdir(outdir)
cmd.delete_existing_files("out96.txt")
cmd.Execute("ScalarField_Playground", "96 2 2", "out96.txt")
cmd.delete_existing_files("out72.txt")
cmd.Execute("ScalarField_Playground", "72 2 2", "out72.txt")
cmd.delete_existing_files("out48.txt")
cmd.Execute("ScalarField_Playground", "48 2 2", "out48.txt")
os.chdir(os.path.join("..",".."))
```

    Compiling executable...
    (EXEC): Executing `gcc -std=gnu99 -Ofast -fopenmp -march=native -funroll-loops ScalarFieldID_Ccodes/ScalarField_Playground.c -o ScalarFieldID_Ccodes/output/ScalarField_Playground -lm`...
    (BENCH): Finished executing in 2.8425137996673584 seconds.
    Finished compilation.
    (EXEC): Executing `./ScalarField_Playground 96 2 2`...
    (BENCH): Finished executing in 1.4379079341888428 seconds.
    (EXEC): Executing `./ScalarField_Playground 72 2 2`...
    (BENCH): Finished executing in 0.2141411304473877 seconds.
    (EXEC): Executing `./ScalarField_Playground 48 2 2`...
    (BENCH): Finished executing in 0.2116990089416504 seconds.

# Step 6: Validation: Convergence of numerical errors (Hamiltonian constraint violation) to zero \[Back to [top](#toc)\]
$$\label{convergence}$$

The equations behind these initial data solve Einstein's equations exactly, at a single instant in time. One reflection of this solution is that the Hamiltonian constraint violation should be exactly zero in the initial data.

However, when evaluated on numerical grids, the Hamiltonian constraint violation will *not* generally evaluate to zero due to the associated numerical derivatives not being exact. However, these numerical derivatives (finite difference derivatives in this case) should *converge* to the exact derivatives as the density of numerical sampling points approaches infinity.

In this case, all of our finite difference derivatives agree with the exact solution, with an error term that drops with the uniform gridspacing to the fourth power: $\left(\Delta x^i\right)^4$.

Here, as in the [Start-to-Finish Scalar Wave (Cartesian grids) NRPy+ tutorial](Tutorial-Start_to_Finish-ScalarWave.ipynb) and the [Start-to-Finish Scalar Wave (curvilinear grids) NRPy+ tutorial](Tutorial-Start_to_Finish-ScalarWaveCurvilinear.ipynb) we confirm this convergence.

We demonstrate convergence along the radial direction in our 1D (spherically symmetric) problem.

```python
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import Image

r_96,log10H_96 = np.loadtxt(os.path.join(outdir,"out96.txt")).T
r_72,log10H_72 = np.loadtxt(os.path.join(outdir,"out72.txt")).T
r_48,log10H_48 = np.loadtxt(os.path.join(outdir,"out48.txt")).T

# Set 4 subplots
fig = plt.figure()

plt.title("Plot Demonstrating 4th-order Convergence")
plt.ylabel(r"$\log_{10}|H|$")
plt.xlabel(r"$r$")
plt.xlim(0,3)
plt.ylim(-10,-5)
plt.plot(r_96,log10H_96,color='black',ls='-',label=r"$N_{r}=96$")
plt.plot(r_72,log10H_72+np.log10((72.0/96.0)**4),color='green',ls='-.',label=r"$N_{r}=72$, times (72/96)$^4$")
plt.plot(r_48,log10H_48+np.log10((48.0/96.0)**4),color='red',ls='--',label=r"$N_{r}=60$, times (48/96)$^4$")
legend = plt.legend(shadow=True,loc=3)
legend.get_frame().set_facecolor('C1')
plt.tight_layout()

outfile = os.path.join(outdir,"HC_convergence_SFID.png")
plt.savefig(outfile,facecolor='white',dpi=120)
plt.close(fig)
Image(outfile)
```

# 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-Start_to_Finish-BSSNCurvilinear-Setting_up_ScalarField_initial_data.pdf](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_ScalarField_initial_data.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

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

    Created Tutorial-Start_to_Finish-BSSNCurvilinear-
        Setting_up_ScalarField_initial_data.tex, and compiled LaTeX file to PDF
        file Tutorial-Start_to_Finish-BSSNCurvilinear-
        Setting_up_ScalarField_initial_data.pdf


# Get $\omega_0$ and added mass from "B" models

```python
%matplotlib inline
%load_ext autoreload
%autoreload 2
```

```python
from jupyterthemes import jtplot
jtplot.style(theme='onedork', context='notebook', ticks=True, grid=False)
```

```python
import pandas as pd
pd.options.display.max_rows = 999
pd.options.display.max_columns = 999
pd.set_option("display.max_columns", None)
import numpy as np
import os
import matplotlib.pyplot as plt
from pylab import rcParams
rcParams['figure.figsize'] = 15, 5

import rolldecay.database as database
from mdldb.tables import Run, RollDecayExclude
from rolldecayestimators.transformers import CutTransformer, LowpassFilterDerivatorTransformer, ScaleFactorTransformer, OffsetTransformer
from rolldecayestimators.direct_estimator_cubic import EstimatorCubic, EstimatorQuadraticBandC, EstimatorLinear

from rolldecayestimators import equations
from rolldecayestimators import symbols
from rolldecayestimators.substitute_dynamic_symbols import lambdify,run
import sympy as sp

import data
import copy
from sklearn.pipeline import Pipeline
import seaborn as sns

from sympy.physics.vector.printing import vpprint, vlatex
from IPython.display import display, Math, Latex
```

## Quadratic model
Has quadratic damping and stiffness:

```python
Math(vlatex(EstimatorQuadraticBandC.roll_decay_equation))
```

```python
df_quadratic = database.load(rolldecay_table_name='rolldecay_quadratic_b_and_c', limit_score=0.95,
                             exclude_table_name=None)

mask = ((df_quadratic['gm'] > 0) & (df_quadratic['Volume'] > 0))
df_quadratic=df_quadratic.loc[mask].copy()

rho=1000
g=9.81
scale_factor=df_quadratic['scale_factor']

df_quadratic.rename(columns = {'KXX':'kxx', 'gm':'GM'}, inplace=True)

df_quadratic['kxx']/=scale_factor
df_quadratic['beam']/=scale_factor
df_quadratic['GM']/=scale_factor
df_quadratic['g'] = g
df_quadratic['rho'] = rho
df_quadratic['Volume']/=scale_factor**3
df_quadratic['m'] = df_quadratic['Volume']*rho
df_quadratic['Ixx'] = df_quadratic['kxx']**2*df_quadratic['m']
```

```python
meta_data = df_quadratic.iloc[0]
run_id = int(meta_data['id'])
db = database.get_db()
db_run = db.session.query(Run).get(run_id)
df = database.load_run(db_run, save_as_example=True)
```

```python
df.plot(y='phi')
```

```python
lowpass_filter = LowpassFilterDerivatorTransformer(cutoff=2, minimum_score=0.99)
#scaler = ScaleFactorTransformer(scale_factor=db_run.model.scale_factor)  # dummy value None for now
cutter = CutTransformer(phi_max=np.deg2rad(9), phi_min=np.deg2rad(0.25))
offset_transformer = OffsetTransformer()

steps = [
    ('filter',lowpass_filter),
#    ('scaler',scaler),  # Is froude scaling a good idea??
    ('cutter', cutter),
    ('offset_transformer',offset_transformer)
]

preprocessor = Pipeline(steps) # define the pipeline object.
preprocessor.fit(df)
X = preprocessor.transform(df)
```

```python
X.plot(y='phi')
```

```python
meta_data = df_quadratic.loc[run_id]
quadratic_estimator = EstimatorQuadraticBandC.load(data=meta_data, X=X)
quadratic_estimator.plot_fit()
```

```python
equations.C_equation_quadratic
```

```python
stiffness_lambda = lambdify(-sp.solve(equations.C_equation_quadratic,symbols.C)[0]*symbols.phi)
```

```python
X['restoring moment'] = stiffness_lambda(C_1=quadratic_estimator.parameters['C_1A'],
                 C_3=quadratic_estimator.parameters['C_3A'], phi=X['phi'])

X['restoring moment linear'] = stiffness_lambda(C_1=quadratic_estimator.parameters['C_1A'],
                 C_3=0, phi=X['phi'])
```

```python
X.plot(y=['restoring moment','restoring moment linear'])
```

```python
X.plot(x='phi',y=['restoring moment','restoring moment linear'])
```

```python

```

```python
Math(vlatex(equations.restoring_equation_linear))
```

```python
equations.normalize_equations[symbols.C_1]
```

```python
equations.C_equation_linear
```

```python
C_1_equation = equations.C_equation_linear.subs(symbols.C,symbols.C_1)
C_1_equation
```

```python
eqs = [
    C_1_equation,
    equations.normalize_equations[symbols.C_1]
]

C_1A = equations.new_symbols[symbols.C_1]

A44_equation = sp.Eq(symbols.A_44,sp.solve(eqs,symbols.C_1,symbols.A_44)[symbols.A_44])
A44_equation
```

```python
A44_lambda = lambdify(sp.solve(A44_equation,symbols.A_44)[0])
```

```python
A44_lambda
```

```python
def calculate_A44(meta_data):
    return run(function = A44_lambda, inputs=meta_data)
    return added_mass

def calculate_kxx_ratio(meta_data):
    A44 = calculate_A44(meta_data)
    kxx_tot = np.sqrt(A44/meta_data['m'])
    return (kxx_tot-meta_data['kxx'])/meta_data['kxx']

```

```python
equations.normalize_equations[symbols.B_1]
```

```python
equations.normalize_equations[symbols.C_1]
```

```python
meta_data = meta_data.copy()
calculate_kxx_ratio(meta_data)
```

```python
df_quadratic['kxx_ratio'] = df_quadratic.apply(func=calculate_kxx_ratio, axis=1)
mask = pd.notnull(df_quadratic['kxx_ratio'])
results = df_quadratic.loc[mask].copy()
```

```python
fig,ax=plt.subplots()
results['kxx_ratio'].hist(bins=60, ax=ax)
ax.set_xlabel(r'$\frac{k_{xxtot}-k_{xx}}{k_{xx}}$')
ax.set_title('Histogram of added roll inertia');
```

I have looked at the meta data from the reports for the outliers to the right and cannot find anything wrong. Strange...

```python
mask = results['ship_speed']==0
results_zero = results.loc[mask].copy()

fig,ax=plt.subplots()
results_zero['kxx_ratio'].hist(bins=60, ax=ax)
ax.set_xlabel(r'$\frac{k_{xxtot}-k_{xx}}{k_{xx}}$')
ax.set_title('Histogram of added roll inertia (zero speed)');
```

```python
equations.C_equation_linear
```

```python
equations.omega0_equation
```

```python
A44_equation
```

```python
eqs = [equations.C_equation_linear,
       equations.omega0_equation,
       A44_equation,
      ]
omgea0_equation = sp.Eq(symbols.omega0,sp.solve(eqs,symbols.A_44,symbols.C,symbols.omega0)[0][2])
omgea0_equation
```

```python
omega0_lambda = lambdify(sp.solve(omgea0_equation,symbols.omega0)[0])
```

```python
def calculate_omega0(meta_data):
    A44 = calculate_A44(meta_data)
    return omega0_lambda(C_1A=meta_data['C_1A'])
```

```python
meta_data['omega0'] = calculate_omega0(meta_data)
meta_data['omega0']
```

```python
quadratic_estimator.omega0
```

```python
quadratic_estimator.calculate_amplitudes_and_damping()
quadratic_estimator.X_amplitudes.plot(x='phi',y='omega0')
```

```python
results['omega0'] = results.apply(func=calculate_omega0, axis=1)
```

```python
fig,ax=plt.subplots()
results['omega0'].hist(bins=60, ax=ax)
ax.set_title(r'Histogram of $\omega_0$');
ax.set_xlabel('$\omega_0$');
```

```python
equations.omega_hat_equation
```

```python
omega_hat_equation = equations.omega_hat_equation.subs(symbols.omega,symbols.omega0)
omega_hat_equation
```

```python
omega_hat_lambda = lambdify(sp.solve(omega_hat_equation,symbols.omega_hat)[0])
```

```python
results['omega0_hat'] = run(function=omega_hat_lambda,inputs=results)
```

```python
fig,ax=plt.subplots()
results['omega0_hat'].hist(bins=60, ax=ax)
ax.set_title(r'Histogram of $\hat{\omega_0}$');
ax.set_xlabel('$\hat{\omega_0}$')
```

```python
mask = ((results['kxx_ratio'] < results['kxx_ratio'].quantile(0.005)) |
 (results['kxx_ratio'] > results['kxx_ratio'].quantile(0.97)))

df_good_runs = results.loc[~mask].copy()
outliers = results.loc[mask].copy()

```

```python
fig,ax=plt.subplots()
df_good_runs['kxx_ratio'].hist(bins=60, ax=ax)
ax.set_xlabel(r'$\frac{k_{xxtot}-k_{xx}}{k_{xx}}$')
ax.set_title('Histogram of "good runs" added roll inertia');
```

```python
df_good_runs.describe()
```

## Save outliers:

```python
roll_decay_exclude=RollDecayExclude(run_id=1)
db.session.merge(roll_decay_exclude)
db.session.commit()
RollDecayExclude.__table__.drop(db.engine)
RollDecayExclude.metadata.create_all(db.engine)

for run_id, _ in outliers.iterrows():
    roll_decay_exclude = RollDecayExclude(run_id=run_id)
    db.session.merge(roll_decay_exclude)

db.session.commit()
```

```python
outliers['A44']=run(function=A44_lambda,inputs=outliers)
outliers[['kxx_ratio','GM','m','omega0','omega0_hat','kxx','Ixx','A44','score','model_number','project_number']]
```

```python
meta_data = outliers.iloc[1]
run_id = int(meta_data['id'])
db = database.get_db()
db_run = db.session.query(Run).get(run_id)
df = database.load_run(db_run, save_as_example=True)
X = preprocessor.transform(df)
quadratic_estimator = EstimatorQuadraticBandC.load(data=meta_data, X=X)
quadratic_estimator.plot_fit()
```

```python
X['restoring moment'] = stiffness_lambda(C_1=quadratic_estimator.parameters['C_1A'],
                 C_3=quadratic_estimator.parameters['C_3A'], phi=X['phi'])

X['restoring moment linear'] = stiffness_lambda(C_1=quadratic_estimator.parameters['C_1A'],
                 C_3=0, phi=X['phi'])
```

```python
X.plot(x='phi',y=['restoring moment','restoring moment linear'])
```

```python
print(meta_data.project_path)
```

```python
meta_data.name
```

```python
temp = database.load(rolldecay_table_name='rolldecay_quadratic_b', limit_score=0.95)
temp.loc[meta_data.name]
```

## Correct wrong meta data

```python

db_run = db.session.query(Run).get(int(16501))
loading_condition = db_run.loading_condition
print(loading_condition.TA)
print(loading_condition.TF)

loading_condition.gm = 25.12-22.6
#loading_condition.lcg =
#loading_condition.kg =
loading_condition.KXX = 17.3
#loading_condition.KYY =
#loading_condition.KZZ =

db.session.merge(loading_condition)
db.session.commit()
```

```python

db_run = db.session.query(Run).get(int(18463))
loading_condition = db_run.loading_condition
print(loading_condition.TA)
print(loading_condition.TF)

#loading_condition.gm = 2.20
#loading_condition.lcg =
#loading_condition.kg =
loading_condition.KXX = 15.77
#loading_condition.KYY =
#loading_condition.KZZ =

db.session.merge(loading_condition)
db.session.commit()
```

```python
db_run = db.session.query(Run).get(int(16501))
loading_condition = db_run.loading_condition
print(loading_condition.TA)
print(loading_condition.TF)

#loading_condition.gm = 2.20
#loading_condition.lcg =
#loading_condition.kg =
loading_condition.KXX = 0.36*48
#loading_condition.KYY =
#loading_condition.KZZ =

db.session.merge(loading_condition)
db.session.commit()
```

```python
db_run = db.session.query(Run).get(int(5511))
loading_condition = db_run.loading_condition
print(loading_condition.TA)
print(loading_condition.TF)

loading_condition.gm = 2.20
#loading_condition.lcg =
#loading_condition.kg =
#loading_condition.KXX = 15.19
#loading_condition.KYY =
#loading_condition.KZZ =

db.session.merge(loading_condition)
db.session.commit()
```

```python
db_run = db.session.query(Run).get(int(18405))
loading_condition = db_run.loading_condition
print(loading_condition.TA)
print(loading_condition.TF)

loading_condition.gm = 8.02
#loading_condition.lcg =
#loading_condition.kg =
loading_condition.KXX = 15.19
#loading_condition.KYY =
#loading_condition.KZZ =

db.session.merge(loading_condition)
db.session.commit()
```

```python

```

## Correct wrong meta data

```python
db_run = db.session.query(Run).get(int(16490))
loading_condition = db_run.loading_condition
print(loading_condition.TA)
print(loading_condition.TF)

loading_condition.KXX = 0.36*48
db.session.merge(loading_condition)
db.session.commit()
```

## Correct wrong meta data

```python
db_run = db.session.query(Run).get(int(9453))
loading_condition = db_run.loading_condition
print(loading_condition.TA)
print(loading_condition.TF)

loading_condition.gm = 7.44
loading_condition.lcg = 3.80
loading_condition.kg = 8.55
loading_condition.KXX = 11.9
loading_condition.KYY = 46.6
loading_condition.KZZ = 46.7
db.session.merge(loading_condition)
db.session.commit()

```

```python

```

```python

```

```python

```

```python

```

```python

```

```python

```


# Recurrent neural network and dynamical system analysis

In this tutorial, we will use supervised learning to train a recurrent neural network on a parametric working memory task, and analyze the trained network using dynamical system analysis.

[](https://colab.research.google.com/github/gyyang/nn-brain/blob/master/ParametricWorkingMemory.ipynb)

## Install dependencies

```python
# # If on Google Colab, uncomment to install neurogym to use cognitive tasks
# ! git clone https://github.com/neurogym/neurogym.git
# %cd neurogym/
# ! pip install -e .
```

## Defining a cognitive task

```python
import numpy as np
import matplotlib.pyplot as plt
import neurogym as ngym
```

```python
# Environment
task = 'DelayComparison-v0'
timing = {'delay': ('choice', [200, 400, 800, 1600, 3200]),
          'response': ('constant', 500)
         }
kwargs = {'dt': 100, 'timing': timing}
seq_len = 100

# Make supervised dataset
dataset = ngym.Dataset(task, env_kwargs=kwargs, batch_size=16,
                       seq_len=seq_len)

# A sample environment from dataset
env = dataset.env
# Visualize the environment with 2 sample trials
_ = ngym.utils.plot_env(env, num_trials=2, def_act=0)

# Network input and output size
input_size = env.observation_space.shape[0]
output_size = env.action_space.n
```

```python
inputs, target = dataset()
mask = target > 0
print(inputs.shape)   # (N_time, batch_size, N_neuron)
print(target.shape)   # (N_time, batch_size)
```

    (100, 16, 2)
    (100, 16)

```python
plt.plot(target[:, 0])
```

## Define a vanilla continuous-time recurrent network

Here we will define a continuous-time neural network but discretize it in time using the Euler method.
\begin{align}
    \tau \frac{d\mathbf{r}}{dt} = -\mathbf{r}(t) + f(W_r \mathbf{r}(t) + W_x \mathbf{x}(t) + \mathbf{b}_r).
\end{align}

This continuous-time system can then be discretized using the Euler method with a time step of $\Delta t$,
\begin{align}
    \mathbf{r}(t+\Delta t) = \mathbf{r}(t) + \Delta \mathbf{r} = \mathbf{r}(t) + \frac{\Delta t}{\tau}[-\mathbf{r}(t) + f(W_r \mathbf{r}(t) + W_x \mathbf{x}(t) + \mathbf{b}_r)].
\end{align}

```python
# Define networks
import torch
import torch.nn as nn
from torch.nn import init
from torch.nn import functional as F
import math

class CTRNN(nn.Module):
    """Continuous-time RNN.

    Args:
        input_size: Number of input neurons
        hidden_size: Number of hidden neurons

    Inputs:
        input: (seq_len, batch, input_size), network input
        hidden: (batch, hidden_size), initial hidden activity
    """

    def __init__(self, input_size, hidden_size, dt=None, **kwargs):
        super().__init__()
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.tau = 100
        if dt is None:
            alpha = 1
        else:
            alpha = dt / self.tau
        self.alpha = alpha
        self.oneminusalpha = 1 - alpha

        self.input2h = nn.Linear(input_size, hidden_size)
        self.h2h = nn.Linear(hidden_size, hidden_size)

    def init_hidden(self, input_shape):
        batch_size = input_shape[1]
        return torch.zeros(batch_size, self.hidden_size)

    def recurrence(self, input, hidden):
        """Recurrence helper."""
        pre_activation = self.input2h(input) + self.h2h(hidden)
        h_new = torch.relu(hidden * self.oneminusalpha +
                           pre_activation * self.alpha)
        return h_new

    def forward(self, input, hidden=None):
        """Propogate input through the network."""
        if hidden is None:
            hidden = self.init_hidden(input.shape).to(input.device)

        output = []
        steps = range(input.size(0))
        for i in steps:
            hidden = self.recurrence(input[i], hidden)
            output.append(hidden)

        output = torch.stack(output, dim=0)
        return output, hidden

class RNNNet(nn.Module):
    """Recurrent network model.

    Args:
        input_size: int, input size
        hidden_size: int, hidden size
        output_size: int, output size
        rnn: str, type of RNN, lstm, rnn, ctrnn, or eirnn
    """
    def __init__(self, input_size, hidden_size, output_size, **kwargs):
        super().__init__()

        # Continuous time RNN
        self.rnn = CTRNN(input_size, hidden_size, **kwargs)
        self.fc = nn.Linear(hidden_size, output_size)

    def forward(self, x):
        rnn_activity, _ = self.rnn(x)
        out = self.fc(rnn_activity)
        return out, rnn_activity
```

## Train the recurrent network on the decision-making task

```python
import torch.optim as optim

# Instantiate the network and print information
hidden_size = 64
net = RNNNet(input_size=input_size, hidden_size=hidden_size,
             output_size=output_size, dt=env.dt)
print(net)

# Use Adam optimizer
optimizer = optim.Adam(net.parameters(), lr=0.001)
criterion = nn.CrossEntropyLoss()

running_loss = 0
running_acc = 0
for i in range(2000):
    inputs, labels_np = dataset()
    labels_np = labels_np.flatten()
    inputs = torch.from_numpy(inputs).type(torch.float)
    labels = torch.from_numpy(labels_np).type(torch.long)

    # in your training loop:
    optimizer.zero_grad()   # zero the gradient buffers
    output, _ = net(inputs)
    output = output.view(-1, output_size)
    loss = criterion(output, labels)
    loss.backward()
    optimizer.step()    # Does the update

    running_loss += loss.item()
    # Compute performance
    output_np = np.argmax(output.detach().numpy(), axis=-1)
    ind = labels_np > 0  # Only analyze time points when target is not fixation
    running_acc += np.mean(labels_np[ind] == output_np[ind])
    if i % 100 == 99:
        running_loss /= 100
        running_acc /= 100
        print('Step {}, Loss {:0.4f}, Acc {:0.3f}'.format(i+1, running_loss, running_acc))
        running_loss = 0
        running_acc = 0
```

    RNNNet(
      (rnn): CTRNN(
        (input2h): Linear(in_features=2, out_features=64, bias=True)
        (h2h): Linear(in_features=64, out_features=64, bias=True)
      )
      (fc): Linear(in_features=64, out_features=3, bias=True)
    )
    Step 100, Loss 0.2268, Acc 0.000
    Step 200, Loss 0.0690, Acc 0.219
    Step 300, Loss 0.0362, Acc 0.691
    Step 400, Loss 0.0253, Acc 0.794
    Step 500, Loss 0.0208, Acc 0.817
    Step 600, Loss 0.0177, Acc 0.836
    Step 700, Loss 0.0160, Acc 0.843
    Step 800, Loss 0.0138, Acc 0.871
    Step 900, Loss 0.0148, Acc 0.844
    Step 1000, Loss 0.0120, Acc 0.878
    Step 1100, Loss 0.0123, Acc 0.875
    Step 1200, Loss 0.0098, Acc 0.907
    Step 1300, Loss 0.0085, Acc 0.919
    Step 1400, Loss 0.0109, Acc 0.885
    Step 1500, Loss 0.0098, Acc 0.899
    Step 1600, Loss 0.0082, Acc 0.919
    Step 1700, Loss 0.0075, Acc 0.927
    Step 1800, Loss 0.0074, Acc 0.924
    Step 1900, Loss 0.0073, Acc 0.926
    Step 2000, Loss 0.0075, Acc 0.920

## Visualize neural activity for in sample trials

We will run the network for 100 sample trials, then visual the neural activity trajectories in a PCA space.

```python
import numpy as np
import gym

# Set delay to 3000ms for analysis
kwargs = {'timing': {'delay': ('constant', 3000)}}
env = gym.make(task, **kwargs)
env.reset(no_step=True)
env.timing

perf = 0
num_trial = 100
activity_dict = {}
trial_infos = {}
for i in range(num_trial):
    env.new_trial()
    ob, gt = env.ob, env.gt
    inputs = torch.from_numpy(ob[:, np.newaxis, :]).type(torch.float)
    action_pred, rnn_activity = net(inputs)
    rnn_activity = rnn_activity[:, 0, :].detach().numpy()
    activity_dict[i] = rnn_activity[env.start_ind['delay']:env.end_ind['delay']]
    trial_infos[i] = env.trial.copy()

# Concatenate activity for PCA
activity = np.concatenate(list(activity_dict[i] for i in range(num_trial)), axis=0)
print('Shape of the neural activity: (Time points, Neurons): ', activity.shape)

# Print trial informations
for i in range(5):
    print('Trial ', i, trial_infos[i])
```

    Shape of the neural activity: (Time points, Neurons):  (3000, 64)
    Trial  0 {'ground_truth': 1, 'vpair': (34, 26), 'v1': 34, 'v2': 26}
    Trial  1 {'ground_truth': 1, 'vpair': (22, 14), 'v1': 22, 'v2': 14}
    Trial  2 {'ground_truth': 1, 'vpair': (34, 26), 'v1': 34, 'v2': 26}
    Trial  3 {'ground_truth': 1, 'vpair': (30, 22), 'v1': 30, 'v2': 22}
    Trial  4 {'ground_truth': 1, 'vpair': (26, 18), 'v1': 26, 'v2': 18}

```python
# Compute PCA and visualize
from sklearn.decomposition import PCA

pca = PCA(n_components=2)
pca.fit(activity)
# print('Shape of the projected activity: (Time points, PCs): ', activity_pc.shape)
```

    PCA(n_components=2)

Transform individual trials and Visualize in PC space based on ground-truth color. We see that the neural activity is organized by stimulus ground-truth in PC1

```python
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2, sharey=True, sharex=True, figsize=(6, 3))
for i in range(num_trial):
    trial = trial_infos[i]
    activity_pc = pca.transform(activity_dict[i])

    color = 'red' if trial['ground_truth'] == 1 else 'blue'

    _ = ax1.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-', color=color)

    if i < 1:
        _ = ax2.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-', color=color)

ax1.set_xlabel('PC 1')
ax1.set_ylabel('PC 2')
```

## Dynamical system analysis

### Search for approximate fixed points
Here we search for approximate fixed points and visualize them in the same PC space. In a generic dynamical system,
\begin{align}
    \frac{d\mathbf{x}}{dt} = F(\mathbf{x}),
\end{align}
We can search for fixed points by doing the optimization
\begin{align}
    \mathrm{argmin}_{\mathbf{x}} |F(\mathbf{x})|^2.
\end{align}

```python
activity.shape
```

    (3000, 64)

```python
# Freeze for parameters in the recurrent network
for param in net.parameters():
    param.requires_grad = False

batch_size = 64

# Inputs should be the 0-coherence mean input during stimulus period
# This will be task-specific
input = np.tile([1, 0], (batch_size, 1))
input = torch.tensor(input, dtype=torch.float32)

# Here hidden activity is the variable to be optimized
# Initialized randomly for search in parallel (activity all positive)
# hidden_init = np.random.rand(batch_size, hidden_size)*3
hidden_init = activity[np.random.randint(activity.shape[0], size=(batch_size,))]
hidden_init = np.random.uniform(0.5, 1.5, size=hidden_init.shape) * hidden_init
hidden = torch.tensor(hidden_init, requires_grad=True, dtype=torch.float32)

# Use Adam optimizer
optimizer = optim.Adam([hidden], lr=0.01)
criterion = nn.MSELoss()

running_loss = 0
for i in range(10000):
    optimizer.zero_grad()   # zero the gradient buffers

    # Take the one-step recurrent function from the trained network
    new_h = net.rnn.recurrence(input, hidden)
    loss = criterion(new_h, hidden)
    loss.backward()
    optimizer.step()    # Does the update

    running_loss += loss.item()
    if i % 1000 == 999:
        running_loss /= 1000
        print('Step {}, Loss {:0.4f}'.format(i+1, running_loss))
        running_loss = 0
```

    Step 1000, Loss 0.0149
    Step 2000, Loss 0.0001
    Step 3000, Loss 0.0000
    Step 4000, Loss 0.0000
    Step 5000, Loss 0.0000
    Step 6000, Loss 0.0000
    Step 7000, Loss 0.0000
    Step 8000, Loss 0.0000
    Step 9000, Loss 0.0000
    Step 10000, Loss 0.0000

### Visualize the found approximate fixed points.

We see that they found an approximate line attrator, corresponding to our PC1, along which evidence is integrated during the stimulus period.

```python
fixedpoints = hidden.detach().numpy()
print(fixedpoints.shape)

# Plot in the same space as activity
plt.figure()
for i in range(5):
    activity_pc = pca.transform(activity_dict[i])
    trial = trial_infos[i]
    color = 'red' if trial['ground_truth'] == 0 else 'blue'
    plt.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-',
             color=color, alpha=0.1)

# Fixed points are shown in cross
fixedpoints_pc = pca.transform(fixedpoints)
plt.plot(fixedpoints_pc[:, 0], fixedpoints_pc[:, 1], 'x')

plt.xlabel('PC 1')
plt.ylabel('PC 2')
```

### Computing the Jacobian and finding the line attractor

First we will compute the Jacobian.

```python
# index of fixed point to focus on
# choose one close to center by sorting PC1
i_fp = np.argsort(fixedpoints[:, 0])[int(fixedpoints.shape[0]/2)]

fp = torch.from_numpy(fixedpoints[i_fp])
fp.requires_grad = True

# Inputs should be the 0-coherence mean input during stimulus period
# This will be task-specific
input = torch.tensor([1, 0], dtype=torch.float32)
deltah = net.rnn.recurrence(input, fp) - fp

jacT = torch.zeros(hidden_size, hidden_size)
for i in range(hidden_size):
    output = torch.zeros(hidden_size)
    output[i] = 1.
    jacT[:,i] = torch.autograd.grad(deltah, fp, grad_outputs=output, retain_graph=True)[0]

jac = jacT.detach().numpy().T
```

Here we plot the direction of the eigenvector corresponding to the highest eigenvalue

```python
eigval, eigvec = np.linalg.eig(jac)
vec = np.real(eigvec[:, np.argmax(eigval)])
end_pts = np.array([+vec, -vec]) * 10
end_pts = pca.transform(fp.detach().numpy() + end_pts)

# Plot in the same space as activity
plt.figure()
for i in range(5):
    activity_pc = pca.transform(activity_dict[i])
    trial = trial_infos[i]
    color = 'red' if trial['ground_truth'] == 0 else 'blue'
    plt.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-',
             color=color, alpha=0.1)

# Fixed points are shown in cross
fixedpoints_pc = pca.transform(fixedpoints)
plt.plot(fixedpoints_pc[:, 0], fixedpoints_pc[:, 1], 'x')

# Line attractor
plt.plot(end_pts[:, 0], end_pts[:, 1])

plt.xlabel('PC 1')
plt.ylabel('PC 2')
```

```python
# Plot distribution of eigenvalues in a 2-d real-imaginary plot
plt.figure()
plt.scatter(np.real(eigval), np.imag(eigval))
plt.plot([0, 0], [-1, 1], '--')
plt.xlabel('Real')
plt.ylabel('Imaginary')
```

# Supplementary Materials

Code for making publication quality figures as it appears in the paper.

```python
# Convert information into pandas dataframe
import pandas as pd
df = pd.DataFrame()
for i in range(len(trial_infos)):
    df = df.append(trial_infos[i], ignore_index=True)
# Example selection of conditions
# print(df[df['f1']==22])
```

```python
import matplotlib as mpl
plot_fp = True

# Plot in the same space as activity
# fig = plt.figure(figsize=(3, 3))
fig, axes = plt.subplots(1, 2, sharex=True, sharey=True, figsize=(4, 2))

for i in range(2):
    ax = axes[i]
    plot_fp = i == 1
#     ax = fig.add_axes([0.2, 0.2, 0.6, 0.6])

    colors = np.array([[27,158,119], [117,112,179], [217,95,2]])/255.

    # Search for two trials with similar conditions
    values = np.unique(df['f1'])

    color_intensity = [0.4, 0.7, 1.0, 1.3]
    cmap = mpl.cm.get_cmap('winter')
    if plot_fp:
        alpha = 0.2
    else:
        alpha = 1.0
    for i, val in enumerate(values):
        trials = df[df['f1']==val].index
        activity = np.mean(np.array([activity_dict[i] for i in trials]), axis=0)

        activity_pc = pca.transform(activity)
        label = '{:0.1f}'.format(val)
        color = cmap(i/len(values))
        ax.plot(activity_pc[:, 0], activity_pc[:, 1], 'o-',
                 color=color, ms=3, markeredgecolor='none',
                 lw=1, label=label, alpha=alpha)
        ax.plot(activity_pc[0, 0], activity_pc[0, 1], 'o-', alpha=alpha,
                marker='^', color=color, ms=5)

    if plot_fp:
        # Fixed points are shown in cross
        color = colors[2]
        fixedpoints_pc = pca.transform(fixedpoints)
        ax.plot(fixedpoints_pc[:, 0], fixedpoints_pc[:, 1], 'x', ms=3, color=color, alpha=0.3)

        # Line attractor
        ax.plot(fixedpoints_pc[i_fp, 0], fixedpoints_pc[i_fp, 1], 'x', ms=5, color=color, lw=1)
        ax.plot(end_pts[:, 0], end_pts[:, 1], color=color)
    else:
        ax.legend(title='Stimulus', loc='upper left', bbox_to_anchor=(1.0, 1.0), frameon=False)

    ax.set_xlabel('PC 1', fontsize=7)
    ax.set_ylabel('PC 2', fontsize=7)

    # plt.xlim([-5, 5])
    # plt.ylim([-1, 5])

    # Beautification
    ax.spines['top'].set_visible(False)
    ax.spines['right'].set_visible(False)
    # ax.spines['left'].set_position(('data', -5))
    # ax.spines['bottom'].set_position(('data', -1.5))
plt.tight_layout()
plt.locator_params(nbins=2)
from pathlib import Path
# if plot_fp:
#     fname = Path('figures/lineattractors_parametricWM')
# else:
#     fname = Path('figures/rnndynamics_parametricWM')
fname = Path('figures/rnndynamics_parametricWM')
fig.savefig(fname.with_suffix('.pdf'), transparent=True)
fig.savefig(fname.with_suffix('.png'), dpi=300)
```

```python

```