# Random Forest + LIBRAS

This notebook covers a Python-based solution to be compared to the experiments 1 to 4 presented in .

The presented solution consists in reproducing the feature vectors for each of the experiments utilizing (x, y) points in , applying a RandomForest classifier, and lastly, comparing the resulting performance to the ones obtained by SVM and k-NN approaches.

## Context

\# TO DO

## Dataset

\# TO DO

## Experiments

\# TO DO

```python
import glob as gl
import numpy as np
from scipy.io import loadmat
from typing import List
import pandas as pd
```

```python
def labelname(file_name):  # Extract labels from filenames
    label = file_name.replace("data/fundamental/sample", "").lower()
    label = label.replace(".mat", "").lower()
    label = label.split('-', 1)[-1]
    return label

class Signal:  # Signal representation containing x and y coordinates and corresponding label
    def __init__(self, x, y, label):
        self.x = x
        self.y = y
        self.label = label
```

### Experiment 1

The first experiment utilizes each signal in its raw form.

```python
def sigvalues(sig):  # EX.1: raw data
    updsig = np.zeros((np.shape(sig.x)[0], np.shape(sig.x)[1] * 2))
    updsig[:, ::2] = sig.x
    updsig[:, 1::2] = sig.y

    return updsig  # Updated signal
```

### Experiment 2

The second experiment consists of applying z-normalization to each sample. The updated coordinates are:
\begin{align}
x_{\mathcal{N(0,1)}}=\frac{x-\bar{x}}{\sigma(x)} \\
y_{\mathcal{N(0,1)}}=\frac{y-\bar{y}}{\sigma(y)}
\end{align}

```python
def sigvalues(sig):  # EX.2: z-norm
    updsig = np.zeros((np.shape(sig.x)[0], np.shape(sig.x)[1] * 2))
    for idx, x in enumerate(sig.x):
        sig.x[idx] = np.divide((np.transpose(x) - np.mean(x)), np.std(x))
    for idx, y in enumerate(sig.y):
        sig.y[idx] = np.divide((np.transpose(y) - np.mean(y)), np.std(y))

    updsig[:, ::2] = sig.x
    updsig[:, 1::2] = sig.y

    return updsig  # Updated signal
```

### Experiment 3

The third experiment consists of normalizing each signal by its 1st frame centroid, as follows:
\begin{align}
\tilde{x}_{P,f}=x_{P,f}-\bar{x}_{1} \\
\tilde{y}_{P,f}=y_{P,f}-\bar{y}_{1}
\end{align}

```python
def sigvalues(sig): # EX.3: norm by 1st frame centroid
    frame_x = np.split(sig.x, 5, axis=1)  # x-coordinates by frame
    frame_y = np.split(sig.y, 5, axis=1)  # y-coordinates by frame
    cent_x, cent_y = (np.mean(frame_x[0], axis=1), np.mean(frame_y[0], axis=1))  # first frame centroid of each recording

    updsig = np.zeros((np.shape(sig.x)[0], np.shape(sig.x)[1] * 2))
    for idx, x in enumerate(sig.x):
        sig.x[idx] = x - cent_x[idx]
    for idx, y in enumerate(sig.y):
        sig.y[idx] = y - cent_y[idx]

    updsig[:, ::2] = sig.x
    updsig[:, 1::2] = sig.y

    return updsig  # Updated signal
```

### Experiment 4

The fourth experiment consists of normalizing each signal by its current frame centroid, as follows:
\begin{align}
\tilde{x}_{P,f}=x_{P,f}-\bar{x}_{f} \\
\tilde{y}_{P,f}=y_{P,f}-\bar{y}_{f}
\end{align}

```python
def sigvalues(sig):  # EX.4
    frame_x = np.split(sig.x, 5, axis=1)  # x-coordinates by frame
    frame_y = np.split(sig.y, 5, axis=1)  # y-coordinates by frame
    cent_x, cent_y = (np.mean(frame_x, axis=2), np.mean(frame_y, axis=2))  # centroids of each recording

    nframes, nrecs, idx = np.shape(frame_x)
    updsig = np.zeros((np.shape(sig.x)[0], np.shape(sig.x)[1] * 2))
    for fx in range(nframes):
        frame_x[fx] = np.transpose(np.transpose(frame_x[fx]) - cent_x[fx])
    for fy in range(nframes):
        frame_y[fy] = np.transpose(np.transpose(frame_y[fy]) - cent_y[fy])

    updsig[:, ::2] = np.hstack(frame_x)
    updsig[:, 1::2] = np.hstack(frame_y)

    return updsig  # Updated signal
```

## Classification

For means of comparison, this solution utilizes the Random Forest algorithm for the classification task. Tuning is done on every iteration utilizing GridSearchCV.

```python
files = gl.glob("data/fundamental/*.mat")  # type: list

signals = []  # type: List[Signal]

for f in files:
    data = loadmat(f).get('pontosSinal')
    signals.append(Signal(data[:, ::2], data[:, 1::2], labelname(f)))

n_signs = len(signals)
n_recs, n_x = np.shape(signals[0].x)  # Number of recordings and number of features

signals_feat = []  # Updated signals, according to each experiment
signals_labels = []
labels_dict = {'angry': 1, 'disgusted': 2, 'fearful': 3, 'happy': 4, 'sad': 5, 'surprised': 6,
               'neutral': 7, 'suckedcheeks': 8, 'inflatedcheeks': 9}  # Dictionary of signals' labels, for reference
for s in signals:
    signals_feat.append(sigvalues(s))
    signals_labels.append([labels_dict[s.label]] * n_recs)

sig_features = np.reshape(signals_feat, (n_signs * n_recs, n_x * 2))
sig_labels = np.reshape(signals_labels, (n_signs * n_recs,))
```

```python
from sklearn.model_selection import StratifiedShuffleSplit
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
from sklearn.metrics import confusion_matrix
from sklearn.model_selection import RandomizedSearchCV
# from sklearn.model_selection import GridSearchCV
from sklearn.metrics import classification_report
from pprint import pprint
```

\# TO DO: PARAMETER SELECTION

```python
# Number of trees in random forest
n_estimators = [int(x) for x in np.linspace(start=800, stop=2000, num=10)]
# Number of features to consider at every split
max_features = ['log2', 'sqrt', 0.3]
# Maximum number of levels in tree
max_depth = [int(x) for x in np.linspace(10, 80, num=10)]
max_depth.append(None)
# Minimum number of samples required to split a node
min_samples_split = [3, 5, 7]
# Minimum number of samples required at each leaf node
min_samples_leaf = [2, 3, 4]
# Method of selecting samples for training each tree
bootstrap = [True, False]
# Criterion
criterion = ['gini', 'entropy']
# Create the random grid
random_grid = {'n_estimators': n_estimators,
               'max_features': max_features,
               'max_depth': max_depth,
               'min_samples_split': min_samples_split,
               'min_samples_leaf': min_samples_leaf,
               'bootstrap': bootstrap,
               'criterion': criterion
              }
```

\# TO DO

```python
# cv = 5
niter = 30
results = []
train_acc = []
test_acc = []
train_report =[]
class_report = []
selected_params = []
cm = [] # confusion matrix
feature_importance = []
col_names = range(1,1211)

for i in range(niter):
    print("Iteration:: ", i)
    sss = StratifiedShuffleSplit(n_splits=3, test_size=0.2, random_state=42)
    sss.get_n_splits(sig_features, sig_labels)

    for train_index, test_index in sss.split(sig_features, sig_labels):
        train_x, test_x = sig_features[train_index], sig_features[test_index]
        train_y, test_y = sig_labels[train_index], sig_labels[test_index]

    rf = RandomForestClassifier()
    rf_random = RandomizedSearchCV(estimator=rf, param_distributions=random_grid, n_iter=500, cv=3, verbose=1, n_jobs=-1)

    rf_random.fit(train_x, train_y)
    predictions = rf_random.predict(test_x)

    selected_params.append(rf_random.best_params_)
    train_acc.append(accuracy_score(train_y, rf_random.predict(train_x)))
    test_acc.append(accuracy_score(test_y, predictions))
    cm.append(pd.crosstab(test_y, predictions, rownames=['True'], colnames=['Predicted'], margins=True))
    class_report.append(classification_report(test_y, predictions, target_names=list(labels_dict.keys())))
    feature_importance.append(pd.DataFrame(data=sorted(zip(map(lambda x: round(x, 4),
                                                               rf_random.best_estimator_.feature_importances_),
                                                           col_names), reverse=True)))
```

    Iteration::  0
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  2.5min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.7min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.2min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 30.4min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 48.4min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 60.2min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  1
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.3min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.1min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 15.1min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 27.5min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 45.2min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 53.3min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  2
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  2.1min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.2min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.4min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 28.4min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 46.6min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 57.6min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  3
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  2.1min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  8.0min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.5min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 30.1min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 47.7min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 58.7min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  4
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.6min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.9min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.4min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 30.2min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 49.0min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 60.7min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  5
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.4min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.8min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.2min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 29.8min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 49.1min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 60.6min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  6
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.3min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  5.8min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.3min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 29.4min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 47.1min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 56.6min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  7
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.9min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.2min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.2min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 30.0min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 45.7min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 55.0min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  8
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.1min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  6.3min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.4min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 29.2min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 47.3min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 56.4min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  9
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.0min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  5.8min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.3min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 30.6min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 48.8min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 57.8min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  10
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.2min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  8.3min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.8min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 31.7min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 46.9min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 57.9min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  11
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.6min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.6min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.6min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 31.1min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 48.0min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 58.4min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  12
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.8min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  6.4min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 15.6min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 31.2min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 47.5min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 56.0min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  13
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  2.1min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.7min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.1min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 23.0min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 32.3min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 37.1min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  14
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.4min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  4.7min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed:  8.6min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 14.8min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 23.6min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 29.3min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  15
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:   36.9s
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  3.7min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed:  9.5min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 16.0min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 23.8min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 29.4min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  16
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:   39.0s
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  4.3min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed:  8.7min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 14.5min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 22.2min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 26.3min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  17
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:   49.0s
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  3.6min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed:  8.5min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 14.7min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 21.4min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 26.2min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  18
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.1min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  4.3min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed:  9.4min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 16.6min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 25.8min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 30.9min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  19
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.2min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  4.3min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 12.8min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 25.3min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 44.9min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 54.8min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  20
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.3min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.1min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.6min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 27.2min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 45.0min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 55.6min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  21
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  2.0min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.6min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.4min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 32.2min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 49.2min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 58.5min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  22
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:   46.0s
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  6.9min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.0min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 29.7min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 47.0min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 57.7min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  23
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.7min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  8.0min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.7min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 30.4min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 50.0min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 59.3min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  24
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.7min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.3min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.7min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 29.1min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 45.3min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 55.0min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  25
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.8min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.8min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.4min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 29.9min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 45.9min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 57.8min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  26
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  2.0min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.8min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 20.1min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 32.0min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 45.2min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 54.0min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  27
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.5min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.2min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 17.0min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 31.2min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 47.8min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 57.0min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  28
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.4min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.2min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 15.1min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 27.4min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 46.1min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 57.2min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

    Iteration::  29
    Fitting 3 folds for each of 500 candidates, totalling 1500 fits

    [Parallel(n_jobs=-1)]: Done  42 tasks      | elapsed:  1.8min
    [Parallel(n_jobs=-1)]: Done 192 tasks      | elapsed:  7.9min
    [Parallel(n_jobs=-1)]: Done 442 tasks      | elapsed: 16.2min
    [Parallel(n_jobs=-1)]: Done 792 tasks      | elapsed: 30.1min
    [Parallel(n_jobs=-1)]: Done 1242 tasks      | elapsed: 45.5min
    [Parallel(n_jobs=-1)]: Done 1500 out of 1500 | elapsed: 55.2min finished
    C:\Users\rubiarg\Anaconda3\envs\tensorflow\lib\site-packages\sklearn\metrics\classification.py:1428: UserWarning: labels size, 6, does not match size of target_names, 9
      .format(len(labels), len(target_names))

## Results

```python
from functools import reduce

conf_matrix = reduce(lambda x, y: x.add(y, fill_value=0), cm)

np.savetxt("results/thesis/raw-trainacc.csv",train_acc,delimiter=",")
np.savetxt("results/thesis/raw-testacc.csv",test_acc,delimiter=",")
np.savetxt("results/thesis/raw-selected_params.txt",selected_params,fmt='%s',delimiter='\n')
np.savetxt("results/thesis/raw-classreport.txt",class_report,fmt='%s',delimiter='\n')
conf_matrix.to_csv("results/thesis/raw-cm.csv")

import pickle
with open('results/thesis/raw-featimportance.pickle', 'wb') as fp:
    for feat in feature_importance:
        pickle.dump(feat, fp)
```

```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib notebook
acc = np.transpose([train_acc, test_acc])
acc = pd.DataFrame(data=acc, columns=['train_acc', 'test_acc'])
boxplot = acc.boxplot(column=['train_acc', 'test_acc'])
plt.show()
```

    <IPython.core.display.Javascript object>

```python
acc.describe()
```

      train_acc
      test_acc

      count
      30.000000
      3.000000e+01

      mean
      0.999583
      8.000000e-01

      std
      0.002282
      2.258405e-16

      min
      0.987500
      8.000000e-01

      25%
      1.000000
      8.000000e-01

      50%
      1.000000
      8.000000e-01

      75%
      1.000000
      8.000000e-01

      max
      1.000000
      8.000000e-01

# References


# Tutorial-IllinoisGRMHD: apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

## Authors: Leo Werneck & Zach Etienne

**This module is currently under development**

## In this tutorial module we explain two major functions within IllinoisGRMHD that are used to ensure that the results obtained throughout the simulation are Physically sound.

### Required and recommended citations:

* **(Required)** Etienne, Z. B., Paschalidis, V., Haas R., Mösta P., and Shapiro, S. L. IllinoisGRMHD: an open-source, user-friendly GRMHD code for dynamical spacetimes. Class. Quantum Grav. 32 (2015) 175009. ([arxiv:1501.07276](http://arxiv.org/abs/1501.07276)).
* **(Required)** Noble, S. C., Gammie, C. F., McKinney, J. C., Del Zanna, L. Primitive Variable Solvers for Conservative General Relativistic Magnetohydrodynamics. Astrophysical Journal, 641, 626 (2006) ([astro-ph/0512420](https://arxiv.org/abs/astro-ph/0512420)).
* **(Recommended)** Del Zanna, L., Bucciantini N., Londrillo, P. An efficient shock-capturing central-type scheme for multidimensional relativistic flows - II. Magnetohydrodynamics. A&A 400 (2) 397-413 (2003). DOI: 10.1051/0004-6361:20021641 ([astro-ph/0210618](https://arxiv.org/abs/astro-ph/0210618)).

If using the version of `IllinoisGRMHD` with piecewise polytropic *or* tabulated (coming soon!) EOS support, then the following citation is also required:

* **(Required)** Etienne, Z. B., Werneck, L., Paschalidis, V., Haas R., Mösta P., and Shapiro, S. L., *IllinoisGRMHD github repository* (2019). Source Code URL: https://github.com/zachetienne/nrpytutorial/tree/master/IllinoisGRMHD/.

### Dependencies

The files generated in this tutorial notebook depend on the following file:

* `IllinoisGRMHD_EoS_lowlevel_functs.C` \[[**tutorial**](Tutorial-IllinoisGRMHD__EoS_lowlevel_functs.ipynb)\]

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

This module is organized as follows

0. [Step 0](#src_dir): **Source directory creation**
1. [Step 1](#introduction): **Introduction**
1. [Step 2](#apply_tau_floor): **The `apply_tau_floor` function**
    1. [Step 2.a](#positive_definiteness_of_the_metric): *Positive-definiteness of the metric*
    1. [Step 2.b](#barbi_barb_i_barb2_and_barb): *Computing $\bar{B}^{i}$, $\bar{B}_{i}$, $\bar{B}^{2}$, and $\bar{B}$*
    1. [Step 2.c](#barbdots_hatbarbdots_and_sdots): *Computing $\bar{B}\cdot\tilde{S}$, $\hat{\bar{B}}\cdot\tilde{S}$, and $\tilde{S}^{2}$*
    1. [Step 2.d](#modifying_tau): *Modifying $\tilde{\tau}$*
        1. [Step 2.d.i](#wm_sm_and_wmin): $W_{m}$, $\tilde{S}_{m}$, and $W_{\min}$
        1. [Step 2.d.ii](#tau_min): $\tau_{\min}$
    1. [Step 2.e](#modifying_tilde_s_i): *Modifying $\tilde{S}_{i}$*
1. [Step 3](#enforce_pressure_floor_ceiling) **The `enforce_pressure_floor_ceiling` function**
1. [Step 4](#enforce_limits_on_primitives_and_recompute_conservs): **The `IllinoisGRMHD_enforce_limits_on_primitives_and_recompute_conservs` function**
1. [Step 5](#code_validation): **Code validation**
1. [Step 6](#latex_pdf_output): **Output this notebook to $\LaTeX$-formatted PDF file**

# Step 0: Source directory creation \[Back to [top](#toc)\]
$$\label{src_dir}$$

We will now use the [cmdline_helper.py NRPy+ module](Tutorial-Tutorial-cmdline_helper.ipynb) to create the source directory within the `IllinoisGRMHD` NRPy+ directory, if it does not exist yet.

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

# Step 0b: Load up cmdline_helper and create the directory
import cmdline_helper as cmd
outdir = os.path.join("..","src")
cmd.mkdir(outdir)
```

# Step 1: Introduction \[Back to [top](#toc)\]
$$\label{introduction}$$

In this tutorial notebook we will discuss how we adjust our conservative variables given that our primitive variables are in the physical range.

For a given set of primitive variables $\left\{\rho_{b},P,v^i,B^i\right\}$ in the physical range (i.e. $\rho_{b}\geq0$, $P\geq0$ and $\epsilon\geq0$), the corresponding conservative variables $\left\{\rho_{\star},\tilde{\tau},\tilde{S}_{i},\tilde{B}^{i}\right\}$ must satisfy certain inequalities (see appendix A of [Etienne *et al.* (2012)](https://arxiv.org/pdf/1112.0568.pdf) for the full discussion). Here we provide a practical recipe to impose these inequalities approximately to reduce inversion failures, which occur mainly in regions with very low density in the artificial “atmosphere” or inside the BH horizon where high accuracy is difficult to maintain but not crucial.

# Step 2: The `apply_tau_floor` function \[Back to [top](#toc)\]
$$\label{apply_tau_floor}$$

Here we will start the `apply_tau_floor()` and declare a couple of function prototypes.

```python
%%writefile $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

void eigenvalues_3by3_real_sym_matrix(CCTK_REAL & lam1, CCTK_REAL & lam2, CCTK_REAL & lam3,
                                      CCTK_REAL M11, CCTK_REAL M12, CCTK_REAL M13, CCTK_REAL M22, CCTK_REAL M23, CCTK_REAL M33);

static inline void enforce_pressure_floor_ceiling(output_stats &stats,CCTK_REAL kpoly,CCTK_REAL P_cold,CCTK_REAL Psi6,const CCTK_REAL Psi6threshold,CCTK_REAL rho_b,const CCTK_REAL rhobatm,  CCTK_REAL &P);

static inline int apply_tau_floor(const CCTK_REAL tau_atm,const CCTK_REAL rho_b_atm,const CCTK_REAL Psi6threshold,CCTK_REAL *PRIMS,CCTK_REAL *ADM_3METRIC,output_stats &stats,eos_struct &eos,  CCTK_REAL *CONSERVS) {
```

    Overwriting ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

## Step 2.a: Positive-definiteness of the metric \[Back to [top](#toc)\]
$$\label{positive_definiteness_of_the_metric}$$

We start by verifying if the metrix $\gamma_{ij}$ is positive definite. Notice that although we expect this to always be true, the metric may lose its positive-definiteness due to numerical error during the evolution, especially in the region deep inside the BH, near the “puncture”.

To verify whether or not the [metric is positive definite, we analyse its eigenvectors](https://en.wikipedia.org/wiki/Definiteness_of_a_matrix#Eigenvalues). If the metrix is *not* positive definite, we reset $\gamma_{ij}\to\psi^{4}\tilde{\gamma}_{ij}$, where $\tilde{\gamma}_{ij}$ corresponds to the 3D flat metric tensor.

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  //First apply the rho_star floor:

  //rho_star = alpha u0 Psi6 rho_b, alpha u0 > 1, so if rho_star < Psi6 rho_b_atm, then we are GUARANTEED that we can reset to atmosphere.
  //if(CONSERVS[RHOSTAR] < 1e4*ADM_3METRIC[SQRTGAMMA]*rho_b_atm) {
  //if(CONSERVS[RHOSTAR] < 2*ADM_3METRIC[SQRTGAMMA]*rho_b_atm) {

    CCTK_REAL CONF_METRIC[6];
    CCTK_REAL Psi2               = cbrt(ADM_3METRIC[SQRTGAMMA]);
    CCTK_REAL Psi4               = Psi2*Psi2;
    CCTK_REAL Psim4              = 1.0 / Psi4;
    CONF_METRIC[CM_GAMMATILDEXX] = ADM_3METRIC[GAMMAUPXX]*Psim4;
    CONF_METRIC[CM_GAMMATILDEXY] = ADM_3METRIC[GAMMAUPXY]*Psim4;
    CONF_METRIC[CM_GAMMATILDEXZ] = ADM_3METRIC[GAMMAUPXZ]*Psim4;
    CONF_METRIC[CM_GAMMATILDEYY] = ADM_3METRIC[GAMMAUPYY]*Psim4;
    CONF_METRIC[CM_GAMMATILDEYZ] = ADM_3METRIC[GAMMAUPYZ]*Psim4;
    CONF_METRIC[CM_GAMMATILDEZZ] = ADM_3METRIC[GAMMAUPZZ]*Psim4;

  CCTK_REAL lam1,lam2,lam3;
  eigenvalues_3by3_real_sym_matrix(lam1, lam2, lam3,CONF_METRIC[CM_GAMMATILDEXX], CONF_METRIC[CM_GAMMATILDEXY], CONF_METRIC[CM_GAMMATILDEXZ], CONF_METRIC[CM_GAMMATILDEYY], CONF_METRIC[CM_GAMMATILDEYZ], CONF_METRIC[CM_GAMMATILDEZZ]);
  if (lam1 < 0.0 || lam2 < 0.0 || lam3 < 0.0) {
    // Metric is not positive-defitive, reset the metric to be conformally-flat.
    CCTK_REAL Psi4               = cbrt(ADM_3METRIC[SQRTGAMMA]*ADM_3METRIC[SQRTGAMMA]);
    CONF_METRIC[CM_GAMMATILDEXX] = 1.0;
    CONF_METRIC[CM_GAMMATILDEXY] = 0.0;
    CONF_METRIC[CM_GAMMATILDEXZ] = 0.0;
    CONF_METRIC[CM_GAMMATILDEYY] = 1.0;
    CONF_METRIC[CM_GAMMATILDEYZ] = 0.0;
    CONF_METRIC[CM_GAMMATILDEZZ] = 1.0;
    ADM_3METRIC[GAMMAUPXX]       = Psi4;
    ADM_3METRIC[GAMMAUPXY]       = 0.0;
    ADM_3METRIC[GAMMAUPXZ]       = 0.0;
    ADM_3METRIC[GAMMAUPYY]       = Psi4;
    ADM_3METRIC[GAMMAUPYZ]       = 0.0;
    ADM_3METRIC[GAMMAUPZZ]       = Psi4;
  }
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

## Step 2.b: Computing $\bar{B}^{i}$, $\bar{B}_{i}$, $\bar{B}^{2}$, and $\bar{B}$ \[Back to [top](#toc)\]
$$\label{barbi_barb_i_barb2_and_barb}$$

We then set

$$
\boxed{\bar{B}^{i} = \frac{B^{i}}{\sqrt{4\pi}}}\ ,
$$

and

$$
\bar{B}_{i} = \gamma_{ij}\bar{B}^{j} \implies
\boxed{
\left\{
\begin{matrix}
\bar{B}_{x} = \gamma_{xx}\bar{B}^{x} + \gamma_{xy}\bar{B}^{y} + \gamma_{xz}\bar{B}^{z}\\
\bar{B}_{y} = \gamma_{yx}\bar{B}^{x} + \gamma_{yy}\bar{B}^{y} + \gamma_{yz}\bar{B}^{z}\\
\bar{B}_{z} = \gamma_{zx}\bar{B}^{x} + \gamma_{zy}\bar{B}^{y} + \gamma_{zz}\bar{B}^{z}
\end{matrix}
\right.
}\ ,
$$

then

$$
\bar{B}^{2} \equiv B_{i}B^{i} \implies \boxed{\bar{B}^{2} = B_{x}B^{x} + B_{y}B^{y} + B_{z}B^{z}}\ ,
$$

and finally

$$
\boxed{\bar{B} \equiv \sqrt{\bar{B}^{2}}}\ .
$$

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  //Next, prepare for the tau and stilde fixes:

  CCTK_REAL Bxbar = PRIMS[BX_CENTER]*ONE_OVER_SQRT_4PI,Bybar = PRIMS[BY_CENTER]*ONE_OVER_SQRT_4PI,Bzbar = PRIMS[BZ_CENTER]*ONE_OVER_SQRT_4PI;

  CCTK_REAL Bbar_x = ADM_3METRIC[GAMMAXX]*Bxbar + ADM_3METRIC[GAMMAXY]*Bybar + ADM_3METRIC[GAMMAXZ]*Bzbar;
  CCTK_REAL Bbar_y = ADM_3METRIC[GAMMAXY]*Bxbar + ADM_3METRIC[GAMMAYY]*Bybar + ADM_3METRIC[GAMMAYZ]*Bzbar;
  CCTK_REAL Bbar_z = ADM_3METRIC[GAMMAXZ]*Bxbar + ADM_3METRIC[GAMMAYZ]*Bybar + ADM_3METRIC[GAMMAZZ]*Bzbar;
  CCTK_REAL Bbar2 = Bxbar*Bbar_x + Bybar*Bbar_y + Bzbar*Bbar_z;
  CCTK_REAL Bbar = sqrt(Bbar2);
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

The next part of the code is written to prevent [floating-point underflow](https://en.wikipedia.org/wiki/Arithmetic_underflow). We compute $\bar{B}$ in a different way. We start by evaluating

$$
\bar{B}_{\rm check} = \left|\bar{B}^{x}\right| + \left|\bar{B}^{y}\right| + \left|\bar{B}^{z}\right|\ ,
$$

and verifying whether that is a very small, positive number. Then, we determine the largest component of $\bar{B}_{\rm check}$:

$$
\bar{B}_{\max} = \max\left(\left|\bar{B}^{x}\right|,\left|\bar{B}^{y}\right|,\left|\bar{B}^{z}\right|\right)\ .
$$

Then, we rescale $\bar{B}_{i}$ and $\bar{B}^{i}$ using

$$
\left(\bar{B}^{i}\right)_{\rm tmp} \equiv \frac{\bar{B}^{i}}{\bar{B}_{\max}}\ ,\quad
\left(\bar{B}_{i}\right)_{\rm tmp} \equiv \frac{\bar{B}_{i}}{\bar{B}_{\max}}\ ,
$$

and finally recompute $\bar{B}$

$$
\bar{B} = \left[\left(\bar{B}_{i}\right)_{\rm tmp}\left(\bar{B}^{i}\right)_{\rm tmp}\right]\bar{B}_{\max}\ .
$$

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  CCTK_REAL check_B_small = fabs(Bxbar)+fabs(Bybar)+fabs(Bzbar);
  if (check_B_small>0 && check_B_small<1.e-150) {
    // need to compute Bbar specially to prevent floating-point underflow
    CCTK_REAL Bmax = fabs(Bxbar);
    if (Bmax < fabs(Bybar)) Bmax=fabs(Bybar);
    if (Bmax < fabs(Bzbar)) Bmax=fabs(Bzbar);
    CCTK_REAL Bxtmp=Bxbar/Bmax, Bytemp=Bybar/Bmax, Bztemp=Bzbar/Bmax;
    CCTK_REAL B_xtemp=Bbar_x/Bmax, B_ytemp=Bbar_y/Bmax, B_ztemp=Bbar_z/Bmax;
    Bbar = sqrt(Bxtmp*B_xtemp + Bytemp*B_ytemp + Bztemp*B_ztemp)*Bmax;
  }
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

## Step 2.c: Computing $\bar{B}\cdot\tilde{S}$, $\hat{\bar{B}}\cdot\tilde{S}$, and $\tilde{S}^{2}$ \[Back to [top](#toc)\]
$$\label{barbdots_hatbarbdots_and_sdots}$$

Then we compute

$$
\bar{B} \cdot \tilde{S} = \bar{B}_{i}\tilde{S}^{i} = \bar{B}_{x}\tilde{S}^{x} + \bar{B}_{y}\tilde{S}^{y} + \bar{B}_{z}\tilde{S}^{z}\ .
$$

and

$$
\hat{\bar{B}}\cdot\tilde{S} \equiv \frac{\bar{B} \cdot \tilde{S}}{\bar{B}}\ .
$$

However, if $\bar{B} \ll 1$, we set $\hat{\bar{B}}\cdot\tilde{S}=0$.

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  CCTK_REAL BbardotS = Bxbar*CONSERVS[STILDEX] + Bybar*CONSERVS[STILDEY] + Bzbar*CONSERVS[STILDEZ];
  CCTK_REAL hatBbardotS = BbardotS/Bbar;
  if (Bbar<1.e-300) hatBbardotS = 0.0;

  // Limit hatBbardotS
  //CCTK_REAL max_gammav = 100.0;
  //CCTK_REAL rhob_max = CONSERVS[RHOSTAR]/ADM_3METRIC[SQRTGAMMA];
  //CCTK_REAL hmax = 1.0 + 2.0*rhob_max;
  //CCTK_REAL abs_hatBbardotS_max = sqrt(SQR(max_gammav)-1.0)*CONSERVS[RHOSTAR]*hmax;
  //if (fabs(hatBbardotS) > abs_hatBbardotS_max) {
  //   CCTK_REAL fac_reduce = abs_hatBbardotS_max/fabs(hatBbardotS);
  //   CCTK_REAL hatBbardotS_max = hatBbardotS*fac_reduce;
  //   CCTK_REAL Bbar_inv = 1.0/Bbar;
  //   CCTK_REAL hat_Bbar_x = Bbar_x*Bbar_inv;
  //   CCTK_REAL hat_Bbar_y = Bbar_y*Bbar_inv;
  //   CCTK_REAL hat_Bbar_z = Bbar_z*Bbar_inv;
  //   CCTK_REAL sub_fact = hatBbardotS_max - hatBbardotS;
  //   CONSERVS[STILDEX] += sub_fact*hat_Bbar_x;
  //   CONSERVS[STILDEY] += sub_fact*hat_Bbar_y;
  //   CONSERVS[STILDEZ] += sub_fact*hat_Bbar_z;
  //   hatBbardotS = hatBbardotS_max;
  //   BbardotS *= fac_reduce;
  //   CONSERVS[STILDEX] = CONSERVS[STILDEX]; CONSERVS[STILDEY] = CONSERVS[STILDEY]; CONSERVS[STILDEZ] = CONSERVS[STILDEZ];
  //}
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

Next we compute

$$
\tilde{S}^{2} \equiv \tilde{S} \cdot \tilde{S} = \gamma^{ij}\tilde{S}_{i}\tilde{S}_{j}\ ,
$$

i.e.

$$
\boxed{
\begin{align}
\tilde{S}^{2} &= \gamma^{xx}\left(\tilde{S}_{x}\right)^{2}
                           + \gamma^{yy}\left(\tilde{S}_{y}\right)^{2}
                           + \gamma^{zz}\left(\tilde{S}_{z}\right)^{2}\\
                          &+2\left(
                             \gamma^{xy}\tilde{S}_{x}\tilde{S}_{y}
                            +\gamma^{xz}\tilde{S}_{x}\tilde{S}_{z}
                            +\gamma^{yz}\tilde{S}_{y}\tilde{S}_{z}
                           \right)
\end{align}
}\ .
$$

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  CCTK_REAL sdots= ADM_3METRIC[GAMMAUPXX]*SQR(CONSERVS[STILDEX])+ADM_3METRIC[GAMMAUPYY]*SQR(CONSERVS[STILDEY])+ADM_3METRIC[GAMMAUPZZ]*SQR(CONSERVS[STILDEZ])+2.0*
    (ADM_3METRIC[GAMMAUPXY]*CONSERVS[STILDEX]*CONSERVS[STILDEY]+ADM_3METRIC[GAMMAUPXZ]*CONSERVS[STILDEX]*CONSERVS[STILDEZ]+ADM_3METRIC[GAMMAUPYZ]*CONSERVS[STILDEY]*CONSERVS[STILDEZ]);
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

## Step 2.d: Modifying $\tilde{\tau}$ \[Back to [top](#toc)\]
$$\label{modifying_tau}$$

### Step 2.d.i: $W_{m}$, $\tilde{S}_{m}$, and $W_{\min}$ \[Back to [top](#toc)\]
$$\label{wm_sm_and_wmin}$$

Then we compute other useful quantities, which are eqs. (A52), (A53), and (A54) in appendix A of [Etienne *et al.* (2012)](https://arxiv.org/pdf/1112.0568.pdf)

$$
\begin{align}
W_{m} &= \psi^{-6}\left[\left(\hat{\bar{B}}\cdot\tilde{S}\right)^{2}+\rho_{\star}^{2}\right]^{1/2}\ ,\\
\tilde{S}_{m}^{2} &= \frac{W_{m}^{2}\tilde{S}^{2} + \left(\bar{B}\cdot\tilde{S}\right)^{2}\left(\bar{B}^{2}+2W_{m}\right)}{\left(W_{m}+\bar{B}^{2}\right)^{2}}\ ,\\
W_{\min} &= \psi^{-6}\left(S_{m}^{2}+\rho_{\star}^{2}\right)^{1/2}\ ,\\
\end{align}
$$

respectively (notice the slightly different notation between the equations above and the one used in the paper).

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  CCTK_REAL Wm = sqrt(SQR(hatBbardotS)+ SQR(CONSERVS[RHOSTAR]))/ADM_3METRIC[SQRTGAMMA];
  CCTK_REAL Sm2 = (SQR(Wm)*sdots + SQR(BbardotS)*(Bbar2+2.0*Wm))/SQR(Wm+Bbar2);
  CCTK_REAL Wmin = sqrt(Sm2 + SQR(CONSERVS[RHOSTAR]))/ADM_3METRIC[SQRTGAMMA];
  CCTK_REAL sdots_fluid_max = sdots;
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

### Step 2.d.ii: $\tilde{\tau}_{\min}$ \[Back to [top](#toc)\]
$$\label{tau_min}$$

Next we evaluate

$$
\tilde{\tau}_{\min} = \tilde{\tau} - \frac{\psi^{6}}{2}\bar{B}^{2} - \frac{\bar{B}^{2}\tilde{S}^{2} - \left(\bar{B}\cdot\tilde{S}\right)^{2}}{2\psi^{6}\left(W_{\min}+\bar{B}^{2}\right)^{2}}
$$

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  //tau fix, applicable when B==0 and B!=0:
  if(CONSERVS[TAUENERGY] < 0.5*ADM_3METRIC[SQRTGAMMA]*Bbar2) {
    CONSERVS[TAUENERGY] = tau_atm+0.5*ADM_3METRIC[SQRTGAMMA]*Bbar2;
    stats.failure_checker+=1000000;
  }

  CCTK_REAL tau_fluid_min = CONSERVS[TAUENERGY] - 0.5*ADM_3METRIC[SQRTGAMMA]*Bbar2 - (Bbar2*sdots - SQR(BbardotS))*0.5/(ADM_3METRIC[SQRTGAMMA]*SQR(Wmin+Bbar2));
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

Then we verify if $\tilde{\tau}_{\min} \geq \tilde{\tau}_{\rm atm}$. If $\tilde{\tau}_{\min} < \tilde{\tau}_{\rm atm}$, then reset $\tilde{\tau}$:

$$
\tilde{\tau} = \tilde{\tau}_{\min} + \frac{\psi^{6}}{2}\bar{B}^{2} + \frac{\bar{B}^{2}\tilde{S}^{2} - \left(\bar{B}\cdot\tilde{S}\right)^{2}}{2\psi^{6}\left(W_{\min}+\bar{B}^{2}\right)^{2}}\ .
$$

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  //Apply Stilde fix when B==0.
  //if(PRIMS[BX_CENTER]==0 && PRIMS[BY_CENTER]==0 && PRIMS[BZ_CENTER]==0 && (ADM_3METRIC[SQRTGAMMA]>30.0 || CONSERVS[RHOSTAR]/ADM_3METRIC[SQRTGAMMA]<100*rho_b_atm)) {
  //if(check_B_small < 1.e-300) {

  /**********************************
   * Piecewise Polytropic EOS Patch *
   *         Computing Patm         *
   **********************************/
  /* This modification of the code trades the variable
   * "gamma_equals2" by the already defined function
   * pow().
   *
   * Also, assuming that Patm < rho_ppoly_tab[0], we skip
   * the declaration of new variables to store the
   * values of K_ppoly_tab[0] and Gamma_ppoly_tab[0]. Thus:
   *  -----------------------------------------
   * | P_{atm} = K_{0} * rho_{atm}^{Gamma_{0}} |
   *  -----------------------------------------
   */
  int polytropic_index = find_polytropic_K_and_Gamma_index(eos,rho_b_atm);
  CCTK_REAL Patm = eos.K_ppoly_tab[polytropic_index]*pow(rho_b_atm,eos.Gamma_ppoly_tab[polytropic_index]);

  if(check_B_small*check_B_small < Patm*1e-32) {
    CCTK_REAL rhot=CONSERVS[TAUENERGY]*(CONSERVS[TAUENERGY]+2.0*CONSERVS[RHOSTAR]);
    CCTK_REAL safetyfactor = 0.999999;
    //if(ADM_3METRIC[SQRTGAMMA]>Psi6threshold) safetyfactor=0.99;

    if(sdots > safetyfactor*rhot) {
      CCTK_REAL rfactm1 = sqrt((safetyfactor*rhot)/sdots);
      CONSERVS[STILDEX]*=rfactm1;
      CONSERVS[STILDEY]*=rfactm1;
      CONSERVS[STILDEZ]*=rfactm1;
      stats.failure_checker+=10000000;
    }
  } else if(ADM_3METRIC[SQRTGAMMA]>Psi6threshold) {
    //Apply new Stilde fix.
    if (tau_fluid_min < tau_atm*1.001) {
      tau_fluid_min = tau_atm*1.001;
      CONSERVS[TAUENERGY] = tau_fluid_min + 0.5*ADM_3METRIC[SQRTGAMMA]*Bbar2 + (Bbar2*sdots - SQR(BbardotS))*0.5/(ADM_3METRIC[SQRTGAMMA]*SQR(Wmin+Bbar2));
    }
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

## Step 2.e: Modifying $\tilde{S}_{i}$ \[Back to [top](#toc)\]
$$\label{modifying_tilde_s_i}$$

Then we check if $\tilde{S}^{2} \leq \tilde{\tau}_{\min}\left(\tilde{\tau}_{\min}+2\rho_{\star}\right)$. If not, we reset $\tilde{S}_{i}$

$$
\tilde{S}_{i}\to \tilde{S}_{i}\sqrt{\frac{\tilde{\tau}_{\min}\left(\tilde{\tau}_{\min}+2\rho_{\star}\right)}{\tilde{S}^{2}}}
$$

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

    CCTK_REAL LHS = tau_fluid_min*(tau_fluid_min+2.0*CONSERVS[RHOSTAR]);
    CCTK_REAL RHS = sdots_fluid_max;

    CCTK_REAL safetyfactor = 0.999999;
    if(safetyfactor*LHS < RHS) {
      CCTK_REAL rfactm1 = sqrt((safetyfactor*LHS)/RHS);
      CONSERVS[STILDEX]*=rfactm1;
      CONSERVS[STILDEY]*=rfactm1;
      CONSERVS[STILDEZ]*=rfactm1;
      stats.failure_checker+=100000000;
    }
  }

  return 0;
}

/***********************************************************/
/***********************************************************/
/***********************************************************/
/***********************************************************/

```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

# Step 3: The `enforce_pressure_floor_ceiling` function \[Back to [top](#toc)\]
$$\label{enforce_pressure_floor_ceiling}$$

After the Newton-Raphson solver has successfully found a set of primitives, the primitives are checked for physicality, and if they are not in the physical range, they are minimally modified until they return to the physical range. First,if the velocity is found to be superluminal, the speed is reduced to `IllinoisGRMHD`’s default Lorentz factor limit, a procedure which we already explained above when we discussed the `impose_speed_limit_output_u0` function.

Next, `IllinoisGRMHD` does not include any cooling mechanism, which means that for evolutions adopting a $\Gamma$-law equation of state, the pressure should not physically drop below $P_{\rm cold}$. So a pressure floor of $0.9P_{\rm cold}$ is imposed. Increasing this floor to $P_{\rm cold}$ exactly results in large central density drifts in TOV star evolutions.

**NOTE**: Please keep in mind that the floor and ceiling values presented here were found ***empirically***.

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

static inline void enforce_pressure_floor_ceiling(output_stats &stats,CCTK_REAL kpoly,CCTK_REAL P_cold,CCTK_REAL Psi6,const CCTK_REAL Psi6threshold,CCTK_REAL rho_b,const CCTK_REAL rhobatm,  CCTK_REAL &P) {
  CCTK_REAL P_min=0.9*P_cold;
  if(P<P_min) {
    stats.failure_checker+=10;
    P=P_min;
  }
  //MAX(P,P_min);
  //if(P < P_min) P=1.0*P_cold;

  /* OLD: Discarded because lower limit is unphysical.
     if(P <= 0.5*kpoly*P_cold) {
     P=0.5*kpoly*P_cold;
     }
  */
```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

Simulations can crash in the other extreme, if $P/P_{\rm cold}$ becomes too large. This typically only happens in very low density regions or inside black holes.  So at densities $\rho_{b}<100\rho_{\rm atm}$ or deep inside black hole horizons, a ceiling on $P$ of $100P_{\rm cold}$ is enforced (see Appendix A of [Etienne *et al.* (2012)](https://arxiv.org/abs/1112.0568) for more details).

We also introduce a parameter, $\psi^{6}_{\rm threshold}$, which determines whether the region under consideration is deep inside the BH horizon or not. For regions deep inside the BH horizon, defined by $\sqrt{\gamma} = \psi^{6} > \psi^{6}_{\rm threshold}$, the primary goal is to keep the evolution stable and prevent inaccurate data from leaking out of the BH horizon. It was determined that in this situation, a better ceiling on $P$ is $10^{5}P_{\rm cold}$.

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

  //CCTK_REAL P_max = 10.0*P_cold;
  CCTK_REAL P_max = 100.0*P_cold;
  if(Psi6 > Psi6threshold) P_max = 1e5*P_cold; // <-- better than 10.

  if((rho_b < 100.0*rhobatm || Psi6 > Psi6threshold) && P>P_max) {
    P=P_max;
    stats.failure_checker+=100;
  }

  /*
    CCTK_REAL rho_horiz_cap = 1000.0*rhobatm;

    //New density damping mechanism inside the horizon
    if(Psi6 > Psi6threshold && rho_b>rho_horiz_cap) {
    CCTK_REAL six_phi=log(Psi6);
    CCTK_REAL six_phithreshold=log(Psi6threshold);
    CCTK_REAL Psi6max_approx=350000;
    rho_b = rho_horiz_cap+(rho_b-rho_horiz_cap)*exp(-200.0*SQR((six_phi-six_phithreshold)/log(Psi6max_approx)));
    }
  */
}

```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

# Step 4: `The IllinoisGRMHD_ enforce_limits_on_primitives_and_recompute_conservs` function \[Back to [top](#toc)\]
$$\label{enforce_limits_on_primitives_and_recompute_conservs}$$

We start by imposing physical limits on the primitive variables $\left\{\rho_{b},P,v^{i}\right\}$, using:

1. $\rho_{b} \to \min\left(\rho_{b},\rho_{b,{\rm atm}}\right)$
1. `enforce_pressure_floor_ceiling()`: documented [above](#enforce_pressure_floor_ceiling)

We then compute $g_{\mu\nu}$, $g^{\mu\nu}$, $T_{\mu\nu}$, $T^{\mu\nu}$, and, finally, recompute the conservative variables.

In this step, we will NRPy+ to generate expressions for:

1. The physical ADM 4-metric, $g_{\mu\nu}$: done using the [BSSN.ADMBSSN_tofrom_4metric NRPy+ module](/edit/NRPyIGM/BSSN/ADMBSSN_tofrom_4metric.py) \[[**tutorial**](/notebooks/NRPyIGM/Tutorial-ADMBSSN_tofrom_4metric.ipynb)\]
2. The physical ADM inverse 4-metric $g^{\mu\nu}$: done using the [BSSN.ADMBSSN_tofrom_4metric NRPy+ module](/edit/NRPyIGM/BSSN/ADMBSSN_tofrom_4metric.py) \[[**tutorial**](/notebooks/NRPyIGM/Tutorial-ADMBSSN_tofrom_4metric.ipynb)\]
3. The covariant GRMHD energy momentum tensor, $T_{\mu\nu}^{\rm GRMHD}$, which is done is three steps:
    1. Compute $T_{\mu\nu}^{\rm GRHD}$: done using the [GRHD.equations NRPy+ module](/edit/NRPyIGM/GRHD/equations.py) \[[**tutorial**](/notebooks/NRPyIGM/Tutorial-GRHD_Equations-Cartesian.ipynb)\]
    2. Compute $T_{\mu\nu}^{\rm GRFFE}$: done using the [GRFFE.equations NRPy+ module](/edit/NRPyIGM/GRFFE/equations.py) \[[**tutorial**](/notebooks/NRPyIGM/Tutorial-GRFFE_Equations-Cartesian.ipynb)\]
    3. Compute $T_{\mu\nu}^{\rm GRMHD} = T_{\mu\nu}^{\rm GRHD} + T_{\mu\nu}^{\rm GRFFE}$: done using the [GRMHD.equations NRPy+ module](/edit/NRPyIGM/GRMHD/equations.py) \[[**tutorial**](/notebooks/NRPyIGM/Tutorial-GRMHD_Equations-Cartesian.ipynb)\]
4. The contravariant GRMHD energy momentum tensor, $T^{\mu\nu}_{\rm GRMHD}$, which is done is three steps:
    1. Compute $T^{\mu\nu}_{\rm GRHD}$: done using the [GRHD.equations NRPy+ module](/edit/NRPyIGM/GRHD/equations.py) \[[**tutorial**](/notebooks/NRPyIGM/Tutorial-GRHD_Equations-Cartesian.ipynb)\]
    2. Compute $T^{\mu\nu}_{\rm GRFFE}$: done using the [GRFFE.equations NRPy+ module](/edit/NRPyIGM/GRFFE/equations.py) \[[**tutorial**](/notebooks/NRPyIGM/Tutorial-GRFFE_Equations-Cartesian.ipynb)\]
    3. Compute $T^{\mu\nu}_{\rm GRMHD} = T^{\mu\nu}_{\rm GRHD} + T^{\mu\nu}_{\rm GRFFE}$: done using the [GRMHD.equations NRPy+ module](/edit/NRPyIGM/GRMHD/equations.py) \[[**tutorial**](/notebooks/NRPyIGM/Tutorial-GRMHD_Equations-Cartesian.ipynb)\]

```python
from outputC import *
import indexedexp as ixp
import sympy as sp
import BSSN.ADMBSSN_tofrom_4metric as AB4m
import GRHD.equations as GRHD
import GRFFE.equations as GRFFE
import GRMHD.equations as GRMHD

NRPy_headers_dir_path = os.path.join(outdir,"NRPy_generated_headers")

ADMgammaDD = ixp.zerorank2()
for i in range(3):
    for j in range(i,3):
        ADMgammaDD[i][j] = ADMgammaDD[j][i] = sp.symbols("ADM_3METRIC[GAMMA"+chr(ord('X')+i)+chr(ord('X')+j)+"]",real=True)
ADMbetaU = ixp.zerorank1()
for i in range(3):
    ADMbetaU[i] = sp.symbols("ADM_3METRIC[BETA"+chr(ord('X')+i)+"]",real=True)
ADMalpha = sp.symbols("ADM_3METRIC[ALPHA]",real=True)
vU = ixp.zerorank1()
for i in range(3):
    vU[i] = sp.symbols("PRIMS[V"+chr(ord('X')+i)+"]",real=True)

GRHD.u4U_in_terms_of_vU__rescale_vU_by_applying_speed_limit(ADMalpha,ADMbetaU,ADMgammaDD, vU)
for i in range(3):
    vU[i] = GRHD.rescaledvU[i]
u4U = GRHD.u4U_ito_vU

# First compute smallb4U & smallbsquared from BtildeU, which are needed
#      for GRMHD stress-energy tensor T4UU and T4UD:
GRHD.compute_sqrtgammaDET(ADMgammaDD)
B_notildeU = ixp.zerorank1()
for i in range(3):
    B_notildeU[i] = sp.symbols("PRIMS[B"+chr(ord('X')+i)+"_CENTER]",real=True)

sqrt4pi = sp.symbols('sqrt4pi', real=True)
GRFFE.compute_smallb4U(     ADMgammaDD,ADMbetaU,ADMalpha, u4U,B_notildeU, sqrt4pi)
GRFFE.compute_smallbsquared(ADMgammaDD,ADMbetaU,ADMalpha, GRFFE.smallb4U)

rho_b,P,epsilon = sp.symbols("PRIMS[RHOB] PRIMS[PRESSURE] eps", real=True)

GRMHD.compute_GRMHD_T4UU(ADMgammaDD, ADMbetaU, ADMalpha, rho_b, P, epsilon, u4U,
                         GRFFE.smallb4U, GRFFE.smallbsquared)

GRMHD.compute_GRMHD_T4UD(ADMgammaDD, ADMbetaU, ADMalpha, GRMHD.GRHDT4UU,GRMHD.GRFFET4UU)
# Compute g_{\mu\nu}
AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD=ADMgammaDD,betaU=ADMbetaU,alpha=ADMalpha)
ADMgammaUU,dummy = ixp.symm_matrix_inverter3x3(ADMgammaDD)
AB4m.g4UU_ito_BSSN_or_ADM("ADM",betaU=ADMbetaU,alpha=ADMalpha,gammaUU=ADMgammaUU)

# Set up g4DD and T4UU in a way that uses the already computed
# gridfunctions instead of the complicated SymPy expressions
g4DD = ixp.zerorank2(DIM=4)
T4UU = ixp.zerorank2(DIM=4)
count = 0
for mu in range(4):
    for nu in range(mu,4):
        g4DD[mu][nu] = g4DD[nu][mu] = sp.Symbol("g4dn["+str(mu)+"]["+str(nu)+"]",real=True)
        T4UU[mu][nu] = T4UU[nu][mu] = sp.Symbol("TUPMUNU["+str(count)+"]",real=True)
        count += 1

# Compute T4DD using the simplified expressions
T4DD = ixp.zerorank2(DIM=4)
for mu in range(4):
    for nu in range(4):
        for rho in range(4):
            for sigma in range(4):
                T4DD[mu][nu] += g4DD[mu][rho] * g4DD[nu][sigma] * T4UU[rho][sigma]

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

varlist = []
exprlist = []

for mu in range(4):
    for nu in range(4):
        varlist.append("g4dn["+str(mu)+"]["+str(nu)+"]")
        exprlist.append(AB4m.g4DD[mu][nu])

for mu in range(4):
    for nu in range(4):
        varlist.append("g4up["+str(mu)+"]["+str(nu)+"]")
        exprlist.append(AB4m.g4UU[mu][nu])

count = 0
for mu in range(4):
    for nu in range(mu,4):
        varlist.append("TUPMUNU["+str(count)+"]")
        exprlist.append(GRMHD.T4UU[mu][nu])
        count += 1

import time
start = time.time()
outputC(exprlist,varlist,filename=os.path.join(NRPy_headers_dir_path,"compute_g4dn_g4up_T4UU.h"),
        params="outCverbose=False")
print("Time elapsed: %4.2lf seconds"%(time.time()-start))

varlist = []
exprlist = []

count = 0
for mu in range(4):
    for nu in range(mu,4):
        varlist.append("TDNMUNU["+str(count)+"]")
        exprlist.append(T4DD[mu][nu])
        count += 1

varlist.append("CONSERVS[RHOSTAR]")
exprlist.append(GRHD.rho_star)
for i in range(3):
    varlist.append("CONSERVS[STILDE"+chr(ord('X')+i)+"]")
    exprlist.append(GRHD.S_tildeD[i])
varlist.append("CONSERVS[TAUENERGY]")
exprlist.append(GRHD.tau_tilde)

# for mu in range(4):
#     varlist.append("smallb4U["+str(mu)+"]")
#     exprlist.append(GRFFE.smallb4U[mu])

# varlist.append("smallbsquared")
# exprlist.append(GRFFE.smallbsquared)

start = time.time()
outputC(exprlist,varlist,filename=os.path.join(NRPy_headers_dir_path,"compute_T4DD_CONSERVS.h"),
        params="outCverbose=False")
print("Time elapsed: %4.2lf seconds"%(time.time()-start))
```

    Wrote to file "../src/NRPy_generated_headers/compute_g4dn_g4up_T4UU.h"
    Time elapsed: 4.62 seconds
    Wrote to file "../src/NRPy_generated_headers/compute_T4DD_CONSERVS.h"
    Time elapsed: 11.80 seconds

```python
%%writefile -a $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

void IllinoisGRMHD_enforce_limits_on_primitives_and_recompute_conservs(const int already_computed_physical_metric_and_inverse,CCTK_REAL *PRIMS,
                                                                       struct output_stats &stats,eos_struct &eos,
                                                                       CCTK_REAL *ADM_3METRIC,CCTK_REAL g4dn[4][4],CCTK_REAL g4up[4][4],
                                                                       CCTK_REAL *TUPMUNU,CCTK_REAL *TDNMUNU,CCTK_REAL *CONSERVS) {
#ifndef ENABLE_STANDALONE_IGM_C2P_SOLVER
  DECLARE_CCTK_PARAMETERS;
#endif

  // Useful debugging tool, can be used to track fixes:
  //CCTK_REAL rho_b_orig=PRIMS[RHOB],P_orig=PRIMS[PRESSURE],vx_orig=PRIMS[VX],vy_orig=PRIMS[VY],vz_orig=PRIMS[VZ];

  /***********************************************************/
  // Enforce limits on pressure, density, and v^i
  /***********************************************************/
  // Density floor:
  //  printf("HEY222a %e %e %e\n",PRIMS[RHOB],rho_b_atm,PRIMS[PRESSURE]);
  PRIMS[RHOB] = MAX(PRIMS[RHOB],rho_b_atm);
  // Density ceiling:
  PRIMS[RHOB] = MIN(PRIMS[RHOB],rho_b_max);
  //   Next set h, the enthalpy:
  CCTK_REAL h_enthalpy,  P_cold,eps_cold,dPcold_drho,eps_th,Gamma_cold; /* <- Note that in setting h, we need to define several
                                                                         *    other variables. Though some will be unused later
                                                                         *    in this function, they may be useful in other
                                                                         *    functions */
  compute_P_cold__eps_cold__dPcold_drho__eps_th__h__Gamma_cold(PRIMS,eos,Gamma_th,P_cold,eps_cold,dPcold_drho,eps_th,h_enthalpy,Gamma_cold);

  // Pressure floor & ceiling:
  int polytropic_index = find_polytropic_K_and_Gamma_index(eos,PRIMS[RHOB]);
  enforce_pressure_floor_ceiling(stats,eos.K_ppoly_tab[polytropic_index],P_cold,ADM_3METRIC[SQRTGAMMA],Psi6threshold,PRIMS[RHOB],rho_b_atm,  PRIMS[PRESSURE]);
  //  printf("HEY222b %e %e %e\n",PRIMS[RHOB],rho_b_atm,PRIMS[PRESSURE]);

  // Possibly adjusted pressure, so recompute eps & h:
  CCTK_REAL eps = eps_cold + (PRIMS[PRESSURE]-P_cold)/(Gamma_th-1.0)/PRIMS[RHOB];
  const CCTK_REAL sqrt4pi = 1.0 * sqrt(4.0*M_PI);
  CCTK_REAL smallb4U[4];
  CCTK_REAL smallbsquared;
  //FIXME: Use already_computed_physical_metric_and_inverse to determine whether g4dn & g4up really need to be calculated.
#include "NRPy_generated_headers/compute_g4dn_g4up_T4UU.h"
#include "NRPy_generated_headers/compute_T4DD_CONSERVS.h"

  //printf("HEY?? %e %e %e %e %e\n",smallb4U[0],smallb4U[1],smallb4U[2],smallb4U[3],smallbsquared);
}

```

    Appending to ../src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

# Step 5: Code validation \[Back to [top](#toc)\]
$$\label{code_validation}$$

First we download the original `IllinoisGRMHD` source code and then compare it to the source code generated by this tutorial notebook.

```python
# # Verify if the code generated by this tutorial module
# # matches the original IllinoisGRMHD source code

# # First download the original IllinoisGRMHD source code
# import urllib
# from os import path

# original_IGM_file_url  = "https://bitbucket.org/zach_etienne/wvuthorns/raw/5611b2f0b17135538c9d9d17c7da062abe0401b6/IllinoisGRMHD/src/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C"
# original_IGM_file_name = "apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs-original.C"
# original_IGM_file_path = os.path.join(outdir,original_IGM_file_name)

# # Then download the original IllinoisGRMHD source code
# # We try it here in a couple of ways in an attempt to keep
# # the code more portable
# try:
#     original_IGM_file_code = urllib.request.urlopen(original_IGM_file_url).read().decode("utf-8")
#     # Write down the file the original IllinoisGRMHD source code
#     with open(original_IGM_file_path,"w") as file:
#         file.write(original_IGM_file_code)
# except:
#     try:
#         original_IGM_file_code = urllib.urlopen(original_IGM_file_url).read().decode("utf-8")
#         # Write down the file the original IllinoisGRMHD source code
#         with open(original_IGM_file_path,"w") as file:
#             file.write(original_IGM_file_code)
#     except:
#         # If all else fails, hope wget does the job
#         !wget -O $original_IGM_file_path $original_IGM_file_url

# # Perform validation
# Validation__tau_and_prims_limits__C  = !diff $original_IGM_file_path $outdir/apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C

# if Validation__tau_and_prims_limits__C == []:
#     # If the validation passes, we do not need to store the original IGM source code file
#     !rm $original_IGM_file_path
#     print("Validation test for apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C: PASSED!")
# else:
#     # If the validation fails, we keep the original IGM source code file
#     print("Validation test for apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C: FAILED!")
#     # We also print out the difference between the code generated
#     # in this tutorial module and the original IGM source code
#     print("Diff:")
#     for diff_line in Validation__tau_and_prims_limits__C:
#         print(diff_line)
```

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

```python
latex_nrpy_style_path = os.path.join(nrpy_dir_path,"latex_nrpy_style.tplx")
#!jupyter nbconvert --to latex --template $latex_nrpy_style_path --log-level='WARN' Tutorial-IllinoisGRMHD__apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.ipynb
#!pdflatex -interaction=batchmode Tutorial-IllinoisGRMHD__apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.tex
#!pdflatex -interaction=batchmode Tutorial-IllinoisGRMHD__apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.tex
#!pdflatex -interaction=batchmode Tutorial-IllinoisGRMHD__apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.tex
!rm -f Tut*.out Tut*.aux Tut*.log
```