```python
# Last amended: 5th Sep, 2021
# Ref: 1. https://github.com/cod3licious/autofeat
#      2. https://github.com/cod3licious/autofeat/blob/master/autofeat_examples.ipynb
```

```python
!pip install autofeat
```

    Collecting autofeat
      Downloading autofeat-2.0.9-py3-none-any.whl (24 kB)
    Requirement already satisfied: sympy>=1.7.1 in /usr/local/lib/python3.7/dist-packages (from autofeat) (1.7.1)
    Collecting pint
      Downloading Pint-0.17-py2.py3-none-any.whl (204 kB)
         |████████████████████████████████| 204 kB 3.6 MB/s
    Requirement already satisfied: numba in /usr/local/lib/python3.7/dist-packages (from autofeat) (0.51.2)
    Requirement already satisfied: scikit-learn in /usr/local/lib/python3.7/dist-packages (from autofeat) (0.22.2.post1)
    Requirement already satisfied: numpy in /usr/local/lib/python3.7/dist-packages (from autofeat) (1.19.5)
    Requirement already satisfied: pandas>=0.24.0 in /usr/local/lib/python3.7/dist-packages (from autofeat) (1.1.5)
    Requirement already satisfied: joblib in /usr/local/lib/python3.7/dist-packages (from autofeat) (1.0.1)
    Requirement already satisfied: future in /usr/local/lib/python3.7/dist-packages (from autofeat) (0.16.0)
    Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.7/dist-packages (from pandas>=0.24.0->autofeat) (2018.9)
    Requirement already satisfied: python-dateutil>=2.7.3 in /usr/local/lib/python3.7/dist-packages (from pandas>=0.24.0->autofeat) (2.8.2)
    Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.7/dist-packages (from python-dateutil>=2.7.3->pandas>=0.24.0->autofeat) (1.15.0)
    Requirement already satisfied: mpmath>=0.19 in /usr/local/lib/python3.7/dist-packages (from sympy>=1.7.1->autofeat) (1.2.1)
    Requirement already satisfied: setuptools in /usr/local/lib/python3.7/dist-packages (from numba->autofeat) (57.4.0)
    Requirement already satisfied: llvmlite<0.35,>=0.34.0.dev0 in /usr/local/lib/python3.7/dist-packages (from numba->autofeat) (0.34.0)
    Requirement already satisfied: importlib-metadata in /usr/local/lib/python3.7/dist-packages (from pint->autofeat) (4.6.4)
    Requirement already satisfied: packaging in /usr/local/lib/python3.7/dist-packages (from pint->autofeat) (21.0)
    Requirement already satisfied: zipp>=0.5 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->pint->autofeat) (3.5.0)
    Requirement already satisfied: typing-extensions>=3.6.4 in /usr/local/lib/python3.7/dist-packages (from importlib-metadata->pint->autofeat) (3.7.4.3)
    Requirement already satisfied: pyparsing>=2.0.2 in /usr/local/lib/python3.7/dist-packages (from packaging->pint->autofeat) (2.4.7)
    Requirement already satisfied: scipy>=0.17.0 in /usr/local/lib/python3.7/dist-packages (from scikit-learn->autofeat) (1.4.1)
    Installing collected packages: pint, autofeat
    Successfully installed autofeat-2.0.9 pint-0.17

```python
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

```

```python
from google.colab import drive
drive.mount('/gdrive')
```

    Drive already mounted at /gdrive; to attempt to forcibly remount, call drive.mount("/gdrive", force_remount=True).

```python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

from autofeat import FeatureSelector, AutoFeatRegressor, AutoFeatClassifier

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

    The autoreload extension is already loaded. To reload it, use:
      %reload_ext autoreload

# Experiments

```python
# generate some toy data
np.random.seed(10)
x1 = np.random.rand(1000)     # (1000,)
x2 = np.random.randn(1000)    # (1000,)
x3 = np.random.rand(1000)     # (1000,)
x4 = np.random.randn(1000)
x5 = np.random.rand(1000)

```

```python
# Stack seven (1000,) variables one upon another
#  to get (7,1000) dataset
X = np.vstack([x1, x2, x3, x4, x5, 1/(x2 - 1/x3), (x2 + np.log(x1))**3])
X.shape
```

    (7, 1000)

Transpose it to have our seven features as:

f1=x1      f2=x2          f3=x3           f4=x4          f5=x5         f6=1/(x2 - 1/x3)          f7=(x2 + np.log(x1))**3

```python
X = X.T
```

```python
# Data as a dataframe
df = pd.DataFrame(X, columns=["x1", "x2", "x3", "x4", "x5", "eng6", "eng7"])
```

```python
df.head()
```

      x1
      x2
      x3
      x4
      x5
      eng6
      eng7

      0
      0.771321
      1.097121
      0.222560
      0.029988
      0.720170
      -0.294459
      0.587364

      1
      0.020752
      -0.538577
      0.226277
      -0.975896
      0.723432
      -0.201697
      -85.981735

      2
      0.633648
      2.195597
      0.824412
      1.021897
      0.090843
      1.017697
      5.261990

      3
      0.748804
      -0.702644
      0.716858
      0.632287
      0.029328
      -0.476731
      -0.975961

      4
      0.498507
      -0.060600
      0.013533
      0.517100
      0.435986
      -0.013522
      -0.433346

```python
# Create target. Target, row-by-row has a non-linear relationship
# with independent features with target
# Note that x4 and x5 are absent here:

target = 2 + 15*x1 + 3/(x2 - 1/x3) + 5*(x2 + np.log(x1))**3
```

```python
help(FeatureSelector)
# FeatureSelector(
#                  problem_type='regression',  # "classification"
#                  featsel_runs=5,
#                  keep=None,
#                  n_jobs=1,
#                  verbose=0)
```

```python
# Instantiate feature selector
fsel = FeatureSelector(verbose=1)
```

```python
# Perform feature selection
new_X = fsel.fit_transform(df, target)
```

```python
# should contain ["x1", "eng6", "eng7"]
print(new_X.columns)
```

    Index(['x1', 'eng7', 'eng6'], dtype='object')

## autofeat

### Generate some toy data

```python
# Generate some toy data
np.random.seed(10)
x1 = np.random.rand(1000)
x2 = np.random.randn(1000)
x3 = np.random.rand(1000)
```

```python
# Stack and transpose to create (1000,3) shape dataset
X = np.vstack([x1, x2, x3]).T
```

```python
# Data frame
df_org = pd.DataFrame(X, columns=["x1", "x2", "x3"])
df_org.head()
```

```python
# Now the target
target = 2 + 15*x1 + 3/(x2 - 1/x3) + 5*(x2 + np.log(x1))**3
```

```python
# Add some noise to target
#  and create two more targets
target_noisy = target + 0.01*target.std()*np.random.randn(1000)
target_very_noisy = target + 0.1*target.std()*np.random.randn(1000)
```

### Autofeat with different numbers of FE steps

```python
help(AutoFeatRegressor)
```

```python
# Instaniate regressor
steps =1
afreg = AutoFeatRegressor(verbose=1, feateng_steps=steps)
```

```python
# Syntax of Regressor problem_type: regression
# AutoFeatRegressor(
#                    categorical_cols=None, # list of column names of cat features;
 |                                          # these will be transformed into 0/1 encoding
#                    feateng_cols=None,     # list of col names for feature engineering
#                    units=None,            # Let it be as it is
#                    feateng_steps=2, # no. of steps to perform in the FE (default: 2)
#                    featsel_runs=5,
#                    max_gb=None, # max gigabytes to use
#                                 # this is no guarantee!
#                                 # it will lead to subsampling of the
#                                 # data points if the new dataframe
#                                 # generated is n_rows * n_cols * 32bit > max_gb
#                    transformations=('1/', 'exp', 'log', 'abs', 'sqrt', '^2', '^3'),
#                    apply_pi_theorem=True,
#                    always_return_numpy=False,
#                    n_jobs=1,
#                    verbose=0)
 |
```

```python
df = afreg.fit_transform(df_org, target)
```

```python
df.head()
```

      x1
      x2
      x3
      1/x1
      x2**2
      x2**3
      log(x1)

      0
      0.771321
      1.097121
      0.222560
      1.296478
      1.203674
      1.320576
      -0.259651

      1
      0.020752
      -0.538577
      0.226277
      48.188244
      0.290066
      -0.156223
      -3.875115

      2
      0.633648
      2.195597
      0.824412
      1.578163
      4.820645
      10.584193
      -0.456261

      3
      0.748804
      -0.702644
      0.716858
      1.335463
      0.493708
      -0.346901
      -0.289278

      4
      0.498507
      -0.060600
      0.013533
      2.005990
      0.003672
      -0.000223
      -0.696138

```python
r2 = afreg.score(df_org, target)  # R^2/Accuracy returned by prediction_model
print("## Final R^2: %.4f" % r2)
```

```python
plt.figure()
plt.scatter(
             afreg.predict(df_org),
             target,
             s=2
            );
plt.title("%i FE steps (R^2: %.4f; %i new features)" % (steps, r2, len(afreg.new_feat_cols_)))
```

```python
steps = 2
afreg = AutoFeatRegressor(verbose=1, feateng_steps=steps)
df = afreg.fit_transform(df_org, target)
df.head()
```

```python
r2 = afreg.score(df_org, target)
print("## Final R^2: %.4f" % r2)
plt.figure()
plt.scatter(afreg.predict(df_org), target, s=2);
plt.title("%i FE steps (R^2: %.4f; %i new features)" % (steps, r2, len(afreg.new_feat_cols_)))
```

```python
steps = 3
afreg = AutoFeatRegressor(verbose=1, feateng_steps=steps)
df = afreg.fit_transform(df_org, target)
df.head()
```

```python
r2 = afreg.score(df_org, target)
print("## Final R^2: %.4f" % r2)
plt.figure()
plt.scatter(afreg.predict(df_org), target, s=2);
plt.title("%i FE steps (R^2: %.4f; %i new features)" % (steps, r2, len(afreg.new_feat_cols_)))
```

```python
help(AutoFeatClassifier)
```

# Problem

```python
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
from sklearn.metrics import accuracy_score, f1_score, roc_auc_score,cohen_kappa_score
import os, gc
```

```python
# Path to data
path = "/gdrive/MyDrive/Colab_data_files/autofeat/"
```

```python
# Read datafile
data = pd.read_csv(path + "Concrete_Data_Yeh.csv")
data.head()
```

      cement
      slag
      flyash
      water
      superplasticizer
      coarseaggregate
      fineaggregate
      age
      csMPa

      0
      540.0
      0.0
      0.0
      162.0
      2.5
      1040.0
      676.0
      28
      79.99

      1
      540.0
      0.0
      0.0
      162.0
      2.5
      1055.0
      676.0
      28
      61.89

      2
      332.5
      142.5
      0.0
      228.0
      0.0
      932.0
      594.0
      270
      40.27

      3
      332.5
      142.5
      0.0
      228.0
      0.0
      932.0
      594.0
      365
      41.05

      4
      198.6
      132.4
      0.0
      192.0
      0.0
      978.4
      825.5
      360
      44.30

```python
# Data should not have any nulls
data.isnull().sum().sum()
```

    0

```python
# Get predictors and target
X = data.iloc[:,:-1]
X.shape
y = data.iloc[:,-1]
y.shape
```

    (1030, 8)

    (1030,)

```python
# split dataset
X_train,X_test, y_train,y_test = train_test_split(X,y,
                                                  test_size = 0.3
                                                  )
```

```python
# Standardize
ss = StandardScaler()
X_train = ss.fit_transform(X_train)
X_test = ss.transform(X_test)
```

```python
X_train = pd.DataFrame(X_train, columns = data.columns[:-1])
X_test = pd.DataFrame(X_test, columns = data.columns[:-1])
X_train.head()
X_test.head()
```

      cement
      slag
      flyash
      water
      superplasticizer
      coarseaggregate
      fineaggregate
      age

      0
      -0.022239
      -0.864576
      -0.865997
      0.441054
      -1.033417
      -0.051765
      1.021137
      -0.509222

      1
      1.588466
      -0.583054
      0.367997
      -0.890834
      0.883435
      -0.064725
      -0.780808
      0.163770

      2
      -1.493795
      1.292588
      -0.865997
      1.015144
      -1.033417
      -0.178767
      0.321632
      0.708573

      3
      0.170283
      -0.864576
      0.969374
      -0.302967
      0.536419
      0.658406
      -0.261498
      -0.509222

      4
      -0.398706
      1.921320
      -0.865997
      2.140360
      -1.033417
      -0.518302
      -2.257402
      3.592823

      cement
      slag
      flyash
      water
      superplasticizer
      coarseaggregate
      fineaggregate
      age

      0
      1.588466
      -0.583054
      0.367997
      -0.890834
      0.883435
      -0.064725
      -0.780808
      -0.621387

      1
      -0.933384
      -0.864576
      1.742573
      -0.541788
      0.222452
      1.083473
      0.042581
      -0.284891

      2
      -0.756111
      0.695526
      -0.865997
      0.486981
      -1.033417
      -0.085460
      0.397964
      -0.621387

      3
      -1.204058
      0.331893
      -0.865997
      0.486981
      -1.033417
      -1.088513
      2.111064
      -0.284891

      4
      0.177907
      0.742446
      0.805362
      0.900326
      -0.041942
      -1.218106
      -1.494078
      -0.284891

```python
help(AutoFeatRegressor)
```

    Help on class AutoFeatRegressor in module autofeat.autofeat:

    class AutoFeatRegressor(AutoFeatModel, sklearn.base.BaseEstimator, sklearn.base.RegressorMixin)
     |  AutoFeatRegressor(categorical_cols=None, feateng_cols=None, units=None, feateng_steps=2, featsel_runs=5, max_gb=None, transformations=('1/', 'exp', 'log', 'abs', 'sqrt', '^2', '^3'), apply_pi_theorem=True, always_return_numpy=False, n_jobs=1, verbose=0)
     |
     |  Short-cut initialization for AutoFeatModel with problem_type: regression
     |
     |  Method resolution order:
     |      AutoFeatRegressor
     |      AutoFeatModel
     |      sklearn.base.BaseEstimator
     |      sklearn.base.RegressorMixin
     |      builtins.object
     |
     |  Methods defined here:
     |
     |  __init__(self, categorical_cols=None, feateng_cols=None, units=None, feateng_steps=2, featsel_runs=5, max_gb=None, transformations=('1/', 'exp', 'log', 'abs', 'sqrt', '^2', '^3'), apply_pi_theorem=True, always_return_numpy=False, n_jobs=1, verbose=0)
     |      multi-step feature engineering and cross-validated feature selection to generate promising additional
     |      features for your dataset and train a linear prediction model with them.
     |
     |      Inputs:
     |          - problem_type: str, either "regression" or "classification" (default: "regression")
     |          - categorical_cols: list of column names of categorical features; these will be transformed into
     |                              0/1 encoding (default: None)
     |          - feateng_cols: list of column names that should be used for the feature engineering part
     |                          (default None --> all, with categorical_cols in 0/1 encoding)
     |          - units: dictionary with {col_name: unit} where unit is a string that can be converted into a pint unit.
     |                   all columns without units are dimensionless and can be combined with any other column.
     |                   Note: it is assumed that all features are of comparable magnitude, i.e., not one variable is in
     |                         m and another in mm. If this needs to be accounted for, please scale your variables before
     |                         passing them to autofeat!
     |                   (default: None --> all columns are dimensionless).
     |          - feateng_steps: number of steps to perform in the feature engineering part (int; default: 2)
     |          - featsel_runs: number of times to perform in the feature selection part with a random fraction of data points (int; default: 5)
     |          - max_gb: if an int is given: maximum number of gigabytes to use in the process (i.e. mostly the
     |                    feature engineering part). this is no guarantee! it will lead to subsampling of the
     |                    data points if the new dataframe generated is n_rows * n_cols * 32bit > max_gb
     |                    Note: this is only an approximate estimate of the final matrix; intermediate representations could easily
     |                          take up at least 2 or 3 times that much space...If you can, subsample before, you know your data best.
     |          - transformations: list of transformations that should be applied; possible elements:
     |                             "1/", "exp", "log", "abs", "sqrt", "^2", "^3", "1+", "1-", "sin", "cos", "exp-", "2^"
     |                             (first 7, i.e., up to ^3, are applied by default)
     |          - apply_pi_theorem: whether or not to apply the pi theorem (if units are given; bool; default: True)
     |          - always_return_numpy: whether to always return a numpy array instead of a pd dataframe when calling (fit_)transform
     |                                 (default: False; mainly used for sklearn estimator checks)
     |          - n_jobs: how many jobs to run when selecting the features in parallel (int; default: 1)
     |          - verbose: verbosity level (int; default: 0)
     |
     |      Attributes:
     |          - original_columns_: original columns of X when calling fit
     |          - all_columns_: columns of X after calling fit
     |          - categorical_cols_map_: dict mapping from the original categorical columns to a list with new column names
     |          - feateng_cols_: actual columns used for the feature engineering
     |          - feature_formulas_: sympy formulas to generate new features
     |          - feature_functions_: compiled feature functions with columns
     |          - new_feat_cols_: list of good new features that should be generated when calling transform()
     |          - good_cols_: columns selected in the feature selection process, used with the final prediction model
     |          - prediction_model_: sklearn model instance used for the predictions
     |
     |      Note: when giving categorical_cols or feateng_cols, X later (i.e. when calling fit/fit_transform) has to be a DataFrame
     |
     |  ----------------------------------------------------------------------
     |  Methods inherited from AutoFeatModel:
     |
     |  __getstate__(self)
     |      get dict for pickling without feature_functions as they are not pickleable
     |
     |  fit(self, X, y)
     |
     |  fit_transform(self, X, y)
     |      Fits the regression model and returns a new dataframe with the additional features.
     |
     |      Inputs:
     |          - X: pandas dataframe or numpy array with original features (n_datapoints x n_features)
     |          - y: pandas dataframe or numpy array with targets for all n_datapoints
     |      Returns:
     |          - new_df: new pandas dataframe with all the original features (except categorical features transformed
     |                    into multiple 0/1 columns) and the most promising engineered features. This df can then be
     |                    used to train your final model.
     |
     |      Please ensure that X only contains valid feature columns (including possible categorical variables).
     |
     |      Note: we strongly encourage you to name your features X1 ...  Xn or something simple like this before passing
     |            a DataFrame to this model. This can help avoid potential problems with sympy later on.
     |            The data should only contain finite values (no NaNs etc.)
     |
     |  predict(self, X)
     |      Inputs:
     |          - X: pandas dataframe or numpy array with original features (n_datapoints x n_features)
     |      Returns:
     |          - y_pred: predicted targets return by prediction_model.predict()
     |
     |  score(self, X, y)
     |      Inputs:
     |          - X: pandas dataframe or numpy array with original features (n_datapoints x n_features)
     |          - y: pandas dataframe or numpy array with the targets for all n_datapoints
     |      Returns:
     |          - R^2/Accuracy returned by prediction_model.score()
     |
     |  transform(self, X)
     |      Inputs:
     |          - X: pandas dataframe or numpy array with original features (n_datapoints x n_features)
     |      Returns:
     |          - new_df: new pandas dataframe with all the original features (except categorical features transformed
     |                    into multiple 0/1 columns) and the most promising engineered features. This df can then be
     |                    used to train your final model.
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from AutoFeatModel:
     |
     |  n_features_in_
     |
     |  ----------------------------------------------------------------------
     |  Methods inherited from sklearn.base.BaseEstimator:
     |
     |  __repr__(self, N_CHAR_MAX=700)
     |      Return repr(self).
     |
     |  __setstate__(self, state)
     |
     |  get_params(self, deep=True)
     |      Get parameters for this estimator.
     |
     |      Parameters
     |      ----------
     |      deep : bool, default=True
     |          If True, will return the parameters for this estimator and
     |          contained subobjects that are estimators.
     |
     |      Returns
     |      -------
     |      params : mapping of string to any
     |          Parameter names mapped to their values.
     |
     |  set_params(self, **params)
     |      Set the parameters of this estimator.
     |
     |      The method works on simple estimators as well as on nested objects
     |      (such as pipelines). The latter have parameters of the form
     |      ``<component>__<parameter>`` so that it's possible to update each
     |      component of a nested object.
     |
     |      Parameters
     |      ----------
     |      **params : dict
     |          Estimator parameters.
     |
     |      Returns
     |      -------
     |      self : object
     |          Estimator instance.
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors inherited from sklearn.base.BaseEstimator:
     |
     |  __dict__
     |      dictionary for instance variables (if defined)
     |
     |  __weakref__
     |      list of weak references to the object (if defined)

```python
X.columns
```

    Index(['cement', 'slag', 'flyash', 'water', 'superplasticizer',
           'coarseaggregate', 'fineaggregate', 'age'],
          dtype='object')

```python
%%time
cols_fe = ['slag']
afreg = AutoFeatRegressor(verbose=1, feateng_steps=3, feateng_cols = cols_fe)
df = afreg.fit_transform(X_train, y_train)
```

    [AutoFeat] The 3 step feature engineering process could generate up to 532 features.
    [AutoFeat] With 721 data points this new feature matrix would use about 0.00 gb of space.
    [feateng] Step 1: transformation of original features
    [feateng] Generated 5 transformed features from 1 original features - done.
    [feateng] Step 2: first combination of features
    [feateng] Generated 56 feature combinations from 15 original feature tuples - done.
    [feateng] Step 3: transformation of new features
    [feateng] Generated 226 transformed features from 56 original features - done.
    [feateng] Generated altogether 293 new features in 3 steps
    [feateng] Removing correlated features, as well as additions at the highest level
    [feateng] Generated a total of 207 additional features
    [featsel] Scaling data...done.
    [featsel] Feature selection run 1/5
    [featsel] Feature selection run 2/5
    [featsel] Feature selection run 3/5
    [featsel] Feature selection run 4/5
    [featsel] Feature selection run 5/5
    [featsel] 15 features after 5 feature selection runs
    [featsel] 13 features after correlation filtering
    [featsel] 8 features after noise filtering
    [AutoFeat] Computing 3 new features.
    [AutoFeat]     3/    3 new features ...done.
    [AutoFeat] Final dataframe with 11 feature columns (3 new).
    [AutoFeat] Training final regression model.
    [AutoFeat] Trained model: largest coefficients:
    41.19155304505693
    -20.955573 * exp(slag - exp(slag))
    11.602106 * cement
    7.015662 * age
    6.839577 * Abs(slag)/slag
    -4.857598 * water
    4.798616 * flyash
    0.566009 * superplasticizer
    0.262678 * exp(slag - 1/slag)
    [AutoFeat] Final score: 0.6228
    CPU times: user 13 s, sys: 2.51 s, total: 15.5 s
    Wall time: 13 s

```python
df.head()
```

      cement
      slag
      flyash
      water
      superplasticizer
      coarseaggregate
      fineaggregate
      age
      Abs(slag)/slag
      exp(slag - 1/slag)
      exp(slag - exp(slag))

      0
      -0.022239
      -0.864576
      -0.865997
      0.441054
      -1.033417
      -0.051765
      1.021137
      -0.509222
      -1.0
      1.339184
      0.276428

      1
      1.588466
      -0.583054
      0.367997
      -0.890834
      0.883435
      -0.064725
      -0.780808
      0.163770
      -1.0
      3.102021
      0.319421

      2
      -1.493795
      1.292588
      -0.865997
      1.015144
      -1.033417
      -0.178767
      0.321632
      0.708573
      1.0
      1.680255
      0.095406

      3
      0.170283
      -0.864576
      0.969374
      -0.302967
      0.536419
      0.658406
      -0.261498
      -0.509222
      -1.0
      1.339184
      0.276428

      4
      -0.398706
      1.921320
      -0.865997
      2.140360
      -1.033417
      -0.518302
      -2.257402
      3.592823
      1.0
      4.058628
      0.007382

```python
df_test =  afreg.transform(X_test)
```

    [AutoFeat] Computing 31 new features.
    [AutoFeat]    31/   31 new features ...done.

```python
df_test.head()
```

```python
df.to_csv(path + "concrete_train.csv", index = False)
df_test.to_csv(path+"concrete_test.csv", index = False)
```

```python
clf_autofeat = RandomForestRegressor()
clf_autofeat.fit(df, y_train)
pred_autofeat = clf_autofeat.predict(df_test)
```

```python
clf = RandomForestRegressor()
clf.fit(X_train, y_train)
pred = clf.predict(X_test)
```

```python
y_test.shape
X_test.shape
pred.shape
```

    (309,)

    (309, 8)

    (309,)

```python
from sklearn.metrics import explained_variance_score, mean_squared_error
explained_variance_score(y_test, pred), explained_variance_score(y_test, pred_autofeat)
mean_squared_error(y_test, pred),mean_squared_error(y_test, pred_autofeat)
```

    (0.9077746541204639, 0.8894223984074386)

    (27.262571583041655, 32.60140986914953)

```python
# Path to data
path = "/gdrive/MyDrive/Colab_data_files/autofeat/"

# Read datafile
data = pd.read_csv(path + "heart.csv")
data.head()

# Get predictors and target
X = data.iloc[:,:-1]
X.shape
y = data.iloc[:,-1]
y.shape

# split dataset
X_train,X_test, y_train,y_test = train_test_split(X,y,
                                                  test_size = 0.3
                                                  )

# Standardize
ss = StandardScaler()
X_train = ss.fit_transform(X_train)
X_test = ss.transform(X_test)

X_train = pd.DataFrame(X_train, columns = data.columns[:-1])
X_test = pd.DataFrame(X_test, columns = data.columns[:-1])
X_train.head()
X_test.head()
```

```python
%%time
afreg = AutoFeatRegressor(verbose=1, feateng_steps=3)
df = afreg.fit_transform(X_train, y_train)
```

    [AutoFeat] The 3 step feature engineering process could generate up to 102466 features.
    [AutoFeat] With 212 data points this new feature matrix would use about 0.09 gb of space.
    [feateng] Step 1: transformation of original features
    [feateng] Generated 50 transformed features from 13 original features - done.
    [feateng] Step 2: first combination of features
    [feateng] Generated 7772 feature combinations from 1953 original feature tuples - done.
    [feateng] Step 3: transformation of new features
    [feateng] Generated 32061 transformed features from 7772 original features - done.
    [feateng] Generated altogether 39936 new features in 3 steps
    [feateng] Removing correlated features, as well as additions at the highest level
    [feateng] Generated a total of 31358 additional features
    [featsel] Scaling data...done.
    [featsel] Feature selection run 1/5
    [featsel] Feature selection run 2/5
    [featsel] Feature selection run 3/5
    [featsel] Feature selection run 4/5
    [featsel] Feature selection run 5/5
    [featsel] 75 features after 5 feature selection runs
    [featsel] 67 features after correlation filtering
    [featsel] 24 features after noise filtering
    [AutoFeat] Computing 24 new features.
    [AutoFeat]    24/   24 new features ...done.
    [AutoFeat] Final dataframe with 37 feature columns (24 new).
    [AutoFeat] Training final regression model.
    [AutoFeat] Trained model: largest coefficients:
    0.6989301087560164
    0.133336 * 1/(exp(oldpeak) + exp(serum_cholestrol))
    0.095188 * chest_painType*Abs(slope)
    -0.059953 * log(oldpeak**2 + thal**2)
    -0.053840 * exp(exang - exp(slope))
    -0.044278 * Abs(exp(thal) - Abs(oldpeak))
    -0.037656 * Abs(resting_blood_pressure**2 - exp(thal))
    -0.032874 * ca*exp(-restecg)
    0.032514 * 1/(thalach**3 - 1/oldpeak)
    -0.028151 * exp(sex - exp(thalach))
    -0.023340 * exp(-chest_painType)/ca
    -0.021413 * log(exp(oldpeak) + exp(resting_blood_pressure))
    -0.015946 * 1/(-resting_blood_pressure**2 + thal**3)
    0.009127 * log(exp(chest_painType)*exp(slope))
    0.008640 * Abs(sex/thal)
    -0.006761 * 1/(ca**2 - thalach)
    -0.006711 * Abs(serum_cholestrol + exp(thal))
    -0.003887 * exp(1/thal - 1/slope)
    -0.003627 * Abs(restecg - exp(thal))
    -0.001887 * exp(exang - 1/chest_painType)
    [AutoFeat] Final score: 0.6989
    CPU times: user 21min 1s, sys: 15min 24s, total: 36min 26s
    Wall time: 19min 7s

```python
df_test =  afreg.transform(X_test)
```

    [AutoFeat] Computing 24 new features.
    [AutoFeat]    24/   24 new features ...done.

```python
clf_autofeat = RandomForestClassifier(n_estimators=500)
clf_autofeat.fit(df, y_train)
pred_autofeat = clf_autofeat.predict_proba(df_test)
```

    RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None,
                           criterion='gini', max_depth=None, max_features='auto',
                           max_leaf_nodes=None, max_samples=None,
                           min_impurity_decrease=0.0, min_impurity_split=None,
                           min_samples_leaf=1, min_samples_split=2,
                           min_weight_fraction_leaf=0.0, n_estimators=500,
                           n_jobs=None, oob_score=False, random_state=None,
                           verbose=0, warm_start=False)

```python
clf = RandomForestClassifier(n_estimators=500)
clf.fit(X_train, y_train)
pred = clf.predict_proba(X_test)
```

    RandomForestClassifier(bootstrap=True, ccp_alpha=0.0, class_weight=None,
                           criterion='gini', max_depth=None, max_features='auto',
                           max_leaf_nodes=None, max_samples=None,
                           min_impurity_decrease=0.0, min_impurity_split=None,
                           min_samples_leaf=1, min_samples_split=2,
                           min_weight_fraction_leaf=0.0, n_estimators=500,
                           n_jobs=None, oob_score=False, random_state=None,
                           verbose=0, warm_start=False)

```python
pred[:5]
```

    array([[0.14 , 0.86 ],
           [0.298, 0.702],
           [0.058, 0.942],
           [0.102, 0.898],
           [0.204, 0.796]])

```python
pred_autofeat[:5]
```

    array([[0.198, 0.802],
           [0.456, 0.544],
           [0.07 , 0.93 ],
           [0.036, 0.964],
           [0.092, 0.908]])

```python
roc_auc_score(y_test, pred[:,1])
```

    0.8844476744186047

```python
roc_auc_score(y_test, pred_autofeat[:,1])
```

    0.8599806201550387

```python
accuracy_score(y_test,pred_autofeat)
```

    0.7802197802197802

```python
accuracy_score(y_test, pred)
```

    0.7802197802197802

```python
pred
```

    array([1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1,
           1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1,
           1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1,
           1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0,
           0, 0, 0])

```python
pred_autofeat
```

    array([1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1,
           1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1,
           1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0,
           1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0,
           0, 0, 0])

```python
f1_score(y_test,pred) , f1_score(y_test,pred_autofeat)
```

    (0.8076923076923077, 0.7959183673469388)


```python
%matplotlib inline
```

```python
import feather as ft
import pandas as pd
import numpy as np
import pymc3 as pm
import theano.tensor as T
```

    /home/paulc/anaconda3/envs/py36/lib/python3.6/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
      from ._conv import register_converters as _register_converters

First we have to get the bangladesh dataset. [Feather](https://github.com/wesm/feather) is perfect for this. Thanks to Hadley Wickham and Wes McKinney for producing this invaluable set of tools.

The code to export from R should look something like this, assuming you've installed rethinking and feather packages and their dependencies:
```R
library(rethinking)
library (feather)

data(bangladesh)
path <- "."
write_feather("bangladesh.feather", path)
```

That's all the R we'll need for now.

Next step is to read in the data

```python
# Read the bangledesh data from home directory
bangladesh = ft.read_dataframe("bangladesh.feather")
bangladesh.head()
```

      woman
      district
      use.contraception
      living.children
      age.centered
      urban

      0
      1
      1
      0
      4
      18.4400
      1

      1
      2
      1
      0
      1
      -5.5599
      1

      2
      3
      1
      0
      3
      1.4400
      1

      3
      4
      1
      0
      4
      8.4400
      1

      4
      5
      1
      0
      1
      -13.5590
      1

From here on we're following Richard McElreath's blog.

We set the random seed so we can reproduce the results as required and we sample 100 records from the bangladesh set.

```python
seed = np.random.RandomState(seed=1)

b = bangladesh.sample(100, random_state=seed)
b.head()
```

      woman
      district
      use.contraception
      living.children
      age.centered
      urban

      661
      662
      18
      0
      3
      -6.5599
      0

      1047
      1048
      31
      0
      4
      16.4400
      1

      643
      644
      18
      0
      3
      1.4400
      0

      1712
      1713
      52
      1
      4
      10.4400
      0

      1507
      1508
      46
      1
      4
      7.4400
      0

We only need contraception use tied to district. The data dataframe will hold this information.

```python
dat = pd.DataFrame()
dat['y'] = b['use.contraception']
dat['district_id'] = pd.Categorical(b['district']).codes
dat.head()
```

      y
      district_id

      661
      0
      15

      1047
      0
      24

      643
      0
      15

      1712
      1
      39

      1507
      1
      36

Our first model is the **centered parameterization**:
$$
\begin{align}
y_i & \sim \operatorname{Bernoulli}(p_i)\\
\operatorname{logit}(p_i) & = \alpha_{unit[i]}\\
\alpha_j & \sim \operatorname{Normal}(a, \tau)\\
a & \sim \operatorname{Normal}(0, 10)\\
\tau & \sim \operatorname{Half-Cauchy}(0, 1)\\
\end{align}
$$

```python
with pm.Model() as c_model:
    tau = pm.HalfCauchy('tau',1)
    a = pm.Normal('a', 0, 10)
    alpha = pm.Normal('alpha', a, tau, shape=len(b['district'].unique()))
    p = pm.math.invlogit(alpha[dat['district_id'].values])
    y = pm.Bernoulli('y', p, observed=dat['y'])

#     for RV in c_model.basic_RVs:
#         print(RV.name, RV.logp(c_model.test_point))
    trace_c = pm.sample(1000, tune=1000)
```

    Auto-assigning NUTS sampler...
    Initializing NUTS using jitter+adapt_diag...
    /home/paulc/anaconda3/envs/py36/lib/python3.6/site-packages/pymc3/model.py:384: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
      if not np.issubdtype(var.dtype, float):
    Multiprocess sampling (4 chains in 4 jobs)
    NUTS: [alpha, a, tau_log__]
     57%|█████▋    | 1135/2000 [00:05<00:03, 219.45it/s]INFO (theano.gof.compilelock): Waiting for existing lock by process '16081' (I am process '16082')
    INFO (theano.gof.compilelock): To manually release the lock, delete /home/paulc/.theano/compiledir_Linux-4.13--generic-x86_64-with-debian-stretch-sid-x86_64-3.6.3-64/lock_dir
     59%|█████▊    | 1172/2000 [00:05<00:03, 222.26it/s]INFO (theano.gof.compilelock): Waiting for existing lock by process '16081' (I am process '16083')
    INFO (theano.gof.compilelock): To manually release the lock, delete /home/paulc/.theano/compiledir_Linux-4.13--generic-x86_64-with-debian-stretch-sid-x86_64-3.6.3-64/lock_dir
    100%|██████████| 2000/2000 [00:08<00:00, 238.60it/s]
    INFO (theano.gof.compilelock): Waiting for existing lock by process '16082' (I am process '16083')
    INFO (theano.gof.compilelock): To manually release the lock, delete /home/paulc/.theano/compiledir_Linux-4.13--generic-x86_64-with-debian-stretch-sid-x86_64-3.6.3-64/lock_dir
    There were 17 divergences after tuning. Increase `target_accept` or reparameterize.
    The acceptance probability does not match the target. It is 0.6809466137372715, but should be close to 0.8. Try to increase the number of tuning steps.
    There were 2 divergences after tuning. Increase `target_accept` or reparameterize.
    There were 60 divergences after tuning. Increase `target_accept` or reparameterize.
    There were 6 divergences after tuning. Increase `target_accept` or reparameterize.
    The estimated number of effective samples is smaller than 200 for some parameters.

As with the R Stan version, there's a whole bunch of divergences in this log. This suggests that the results may not be reliable.

Our second model is the **non-centered parameterization**:
$$
\begin{align}
y_i & \sim \operatorname{Bernoulli}(p_i)\\
\operatorname{logit}(p_i) & = a + \tau z_{unit[i]}\\
z_j & \sim \operatorname{Normal}(0, 1)\\
a & \sim \operatorname{Normal}(0, 10)\\
\tau & \sim \operatorname{Half-Cauchy}(0, 1)\\
\end{align}
$$

```python
with pm.Model() as nc_model:
    # pymc3 HalfCauchy only uses the second (scale) parameter
    tau = pm.HalfCauchy('tau', beta=1)
    a = pm.Normal('a', 0, 10)
    z = pm.Normal('z', 0, 1, shape=len(b['district'].unique()))
    p = pm.math.invlogit(a + tau * z[dat['district_id'].values])
    y = pm.Bernoulli('y', p, observed=dat['y'])

#     for RV in c_model.basic_RVs:
#         print(RV.name, RV.logp(c_model.test_point))
    trace_nc = pm.sample(1000, tune=1000)
```

    Auto-assigning NUTS sampler...
    Initializing NUTS using jitter+adapt_diag...
    /home/paulc/anaconda3/envs/py36/lib/python3.6/site-packages/pymc3/model.py:384: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
      if not np.issubdtype(var.dtype, float):
    Multiprocess sampling (4 chains in 4 jobs)
    NUTS: [z, a, tau_log__]
    100%|██████████| 2000/2000 [00:06<00:00, 302.29it/s]
    There were 3 divergences after tuning. Increase `target_accept` or reparameterize.
    The acceptance probability does not match the target. It is 0.7201418506887862, but should be close to 0.8. Try to increase the number of tuning steps.
    There were 3 divergences after tuning. Increase `target_accept` or reparameterize.
    The acceptance probability does not match the target. It is 0.6577402127331399, but should be close to 0.8. Try to increase the number of tuning steps.

This log looks much better. That suggests already that we might see more reliable results from this approach.

Let's take a look at the traceplots:

```python
_ = pm.plots.traceplot(trace_c)
d = pm.diagnostics.effective_n(trace_c)
print(f"n_eff('a') = {d['a']}\nn_eff('tau') = {d['tau']}")
```

```python
_ = pm.plots.traceplot(trace_nc)
d = pm.diagnostics.effective_n(trace_nc)
print(f"n_eff('a') = {d['a']}\nn_eff('tau') = {d['tau']}")
```

These reflect the results from the blog.

The centered model has very low effective samples, the distributions look quite spread out.

The non-centered model has decent effective samples and the traceplots look clean with relatively tightly clustered distributions.

Centered vs Non Centered - McElreath.ipynb


# `GiRaFFE_NRPy` C code library: Conservative-to-Primitive and Primitive-to-Conservative Solvers

## Author: Patrick Nelson

**Notebook Status:**  Validated

**Validation Notes:** These functions have been validated to round-off precision against the coresponding functions in the original `GiRaFFE`.

### NRPy+ Source Code for this module:
* [GiRaFFE_NRPy/GiRaFFE_NRPy_C2P_P2C.py](../../edit/in_progress/GiRaFFE_NRPy/GiRaFFE_NRPy_C2P_P2C.py)

## Introduction:
This writes and documents the C code that `GiRaFFE_NRPy` uses in order to update the Valencia 3-velocity at each timestep. It also computes corrections to the densitized Poynting flux in order to keep the physical quantities from violating the GRFFE constraints.

These algorithms are adapted from the original `GiRaFFE` code (see [arxiv:1704.00599v2](https://arxiv.org/abs/1704.00599v2)), based on the description in [arXiv:1310.3274v2](https://arxiv.org/abs/1310.3274v2). They have been fully NRPyfied and modified to use the Valencia 3-velocity instead of the drift velocity.

The algorithm to do this is as follows:
1. Apply fixes to ${\tilde S}_i$
    1. Enforce the orthogonality of ${\tilde S}_i$ and $B^i$
        * ${\tilde S}_i \rightarrow {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2$
    1. Rescale ${\tilde S}_i$ to limit the Lorentz factor and enforce the velocity cap
        * $f = \sqrt{(1-\gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)}$
        * ${\tilde S}_i \rightarrow {\tilde S}_i \min(1,f)$
    1. Recompute the velocities at the new timestep
        * $v^i = 4 \pi \gamma^{ij} {\tilde S}_j \gamma^{-1/2} B^{-2}$
1. Enforce the Current Sheet prescription
    1. Zero the velocity normal to the sheet
        * ${\tilde n}_i v^i = 0$
    1. Recompute the Poynting flux to be consistent.

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

This notebook is organized as follows

1. [Step 1](#c2p): The conservative-to-primitive solver
    1. [Step 1.a](#ortho_s_b): Enforce the orthogonality of $\tilde{S}_i$ and $B^i$
    1. [Step 1.b](#vel_cap): Rescale ${\tilde S}_i$ to limit the Lorentz factor and enforce the velocity cap
    1. [Step 1.c](#update_vel): Recompute the velocities at the new timestep
    1. [Step 1.d](#current_sheet): Enforce the Current Sheet prescription
1. [Step 2](#p2c): The primitive-to-conservative solver
1. [Step 3](#code_validation): Code Validation against
1. [Step 4](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file

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

import os
import cmdline_helper as cmd
outdir = "GiRaFFE_NRPy/GiRaFFE_Ccode_validation/"
cmd.mkdir(outdir)
```

# Step 1: The conservative-to-primitive solver \[Back to [top](#toc)\]
$$\label{c2p}$$

We start with the Conservative-to-Primitive solver. This function is called after the vector potential and Poynting vector have been evolved at a timestep and updates the velocities. The algorithm will be as follows:

1. Apply fixes to ${\tilde S}_i$
    1. Enforce the orthogonality of ${\tilde S}_i$ and $B^i$
        * ${\tilde S}_i \rightarrow {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2$
    1. Rescale ${\tilde S}_i$ to limit the Lorentz factor and enforce the velocity cap
        * $f = \sqrt{(1-\gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)}$
        * ${\tilde S}_i \rightarrow {\tilde S}_i \min(1,f)$
    1. Recompute the velocities at the new timestep
        * $v^i = 4 \pi \gamma^{ij} {\tilde S}_j \gamma^{-1/2} B^{-2}$
1. Enforce the Current Sheet prescription
    * ${\tilde n}_i v^i = 0$

We will begin simply by creating the file. We will also `#include` the header file `<sys/time.h>` and define $\pi$.

```python
from outputC import *            # NRPy+: Core C code output module
import finite_difference as fin  # NRPy+: Finite difference C code generation module
import NRPy_param_funcs as par   # NRPy+: Parameter interface
import grid as gri               # NRPy+: Functions having to do with numerical grids
import loop as lp                # NRPy+: Generate C code loops
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

thismodule = "GiRaFFE_NRPy-C2P_P2C"

# There are several C parameters that we will need in this module:
M_PI  = par.Cparameters("#define",thismodule,["M_PI"], "")
GAMMA_SPEED_LIMIT = par.Cparameters("REAL",thismodule,"GAMMA_SPEED_LIMIT",10.0) # Default value based on
                                                                                # IllinoisGRMHD.
                                                                                # GiRaFFE default = 2000.0

# Register the relevant gridfunctions:
StildeD = ixp.register_gridfunctions_for_single_rank1("EVOL","StildeD")
BU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","BU")
ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","ValenciavU")
gammaDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gammaDD","sym01")
gammaUU = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gammaUU","sym01")
alpha,gammadet = gri.register_gridfunctions("AUXEVOL",["alpha","gammadet"])
betaU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","betaU")

import GiRaFFE_NRPy.GiRaFFE_NRPy_C2P_P2C as C2P_P2C

```

## Step 1.a: Enforce the orthogonality of $\tilde{S}_i$ and $B^i$ \[Back to [top](#toc)\]
$$\label{ortho_s_b}$$

Now, we will enforce the orthogonality of the magnetic field and densitized poynting flux using Eq. 22 of [arxiv:1704.00599v2](https://arxiv.org/abs/1704.00599v2):
$${\tilde S}_i \rightarrow {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2$$
First, we compute the inner products ${\tilde S}_j {\tilde B}^j$ and ${\tilde B}^2 = \gamma_{ij} {\tilde B}^i {\tilde B}^j,$ where $\tilde{B}^i = B^i \sqrt{\gamma}$. Then, we subtract $({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2$ from ${\tilde S}_i$. We thus guarantee that ${\tilde S}_i B^i=0$.

Having fixed ${\tilde S}_i$, we will also compute the related quantities ${\tilde S}^i = \gamma^{ij} {\tilde S}_j$ and ${\tilde S}^2 = {\tilde S}_i {\tilde S}^i$.

```python
# First, we need to obtain the index-lowered form of Btilde^i and (Btilde^i)^2
# Recall that Btilde^i = sqrtgamma*B^i
sqrtgammadet = sp.sqrt(gammadet)
BtildeU = ixp.zerorank1()
for i in range(3):
    # \tilde{B}^i = B^i \sqrt{\gamma}
    BtildeU[i] = sqrtgammadet*BU[i]

BtildeD = ixp.zerorank1()
for i in range(3):
    for j in range(3):
        BtildeD[j] += gammaDD[i][j]*BtildeU[i]

Btilde2 = sp.sympify(0)
for i in range(3):
    Btilde2 += BtildeU[i]*BtildeD[i]

StimesB = sp.sympify(0)
for i in range(3):
    StimesB += StildeD[i]*BtildeU[i]

# Then, enforce the orthogonality:
if par.parval_from_str("enforce_orthogonality_StildeD_BtildeU"):
    for i in range(3):
        # {\tilde S}_i = {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2
        StildeD[i] -= StimesB*BtildeD[i]/Btilde2

```

## Step 1.b: Rescale ${\tilde S}_i$ to limit the Lorentz factor and enforce the velocity cap \[Back to [top](#toc)\]
$$\label{vel_cap}$$

The next fix that we will apply limits the Lorentz factor using Eqs. 92 and 93 of [arXiv:1310.3274v2](https://arxiv.org/abs/1310.3274v2). That is, we define the factor $f$ as
$$f = \sqrt{(1-\Gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)}.$$

If $f<1$, we rescale the components of ${\tilde S}_i$ by $f$. That is, we must set
$${\tilde S}_i \rightarrow {\tilde S}_i \min(1,f).$$

Here, we will use a formulation of the `min()` function that does not use `if`:
\begin{equation}
\min(a,b) = \frac{1}{2} \left( a+b - \lvert a-b \rvert \right),
\end{equation}
or, in code,
```
min_noif(a,b) = sp.Rational(1,2)*(a+b-nrpyAbs(a-b))
```

```python
# Calculate \tilde{S}^2:
Stilde2 = sp.sympify(0)
for i in range(3):
    for j in range(3):
        Stilde2 += gammaUU[i][j]*StildeD[i]*StildeD[j]

# First we need to compute the factor f:
# f = \sqrt{(1-\Gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)}
speed_limit_factor = sp.sqrt((1.0-GAMMA_SPEED_LIMIT**(-2.0))\
                             *Btilde2*Btilde2*sp.Rational(1,16)/(M_PI*M_PI*gammadet*Stilde2))

def min_noif(a,b):
    # This returns the minimum of a and b
    # If a>b, then we get 0.5*(a+b-a+b) = b
    # If b>a, then we get 0.5*(a+b+a-b) = a
    return sp.Rational(1,2)*(a+b-nrpyAbs(a-b))

# Calculate B^2
B2 = sp.sympify(0)
for i in range(3):
    for j in range(3):
        B2 += gammaDD[i][j]*BU[i]*BU[j]

# Enforce the speed limit on StildeD:
if par.parval_from_str("enforce_speed_limit_StildeD"):
    for i in range(3):
        StildeD[i] *= min_noif(1.0,speed_limit_factor)

```

## Step 1.c: Recompute the velocities at the new timestep \[Back to [top](#toc)\]
$$\label{update_vel}$$

Finally, we can calculate the velocities. In [arxiv:1704.00599v2](https://arxiv.org/abs/1704.00599v2), Eq. 16 gives the drift velocity as
$$v^i = 4 \pi \alpha \gamma^{ij} {\tilde S}_j \gamma^{-1/2} B^{-2} - \beta^i.$$
However, we wish to use the Valencia velocity instead. Since the Valencia velocity $\bar{v}^i = \frac{1}{\alpha} \left( v^i + \beta^i \right)$, we will code
$$\bar{v}^i = 4 \pi \frac{\gamma^{ij} {\tilde S}_j}{\sqrt{\gamma} B^2}.$$

```python
ValenciavU = ixp.zerorank1()
if par.parval_from_str("enforce_orthogonality_StildeD_BtildeU") or par.parval_from_str("enforce_speed_limit_StildeD"):
    # Recompute 3-velocity:
    for i in range(3):
        for j in range(3):
            # \bar{v}^i = 4 \pi \gamma^{ij} {\tilde S}_j / (\sqrt{\gamma} B^2)
            ValenciavU[i] += sp.sympify(4.0)*M_PI*gammaUU[i][j]*StildeD[j]/(sqrtgammadet*B2)

```

## Step 1.d: Enforce the Current Sheet prescription \[Back to [top](#toc)\]
$$\label{current_sheet}$$

Now, we seek to handle any current sheets (a physically important phenomenon) that might form. This algorithm, given as Eq. 23 in [arxiv:1704.00599v2](https://arxiv.org/abs/1704.00599v2), will preserve current sheets that form in the xy-plane by preventing our numerical scheme from dissipating them. After fixing the z-component of the velocity, we recompute the conservative variables $\tilde{S}_i$ to be consistent with the new velocities.

Thus, if we are within four gridpoints of $z=0$, we set the component of the velocity perpendicular to the current sheet to zero by $n_i v^i = 0$, where $n_i = \gamma_{ij} n^j$ is a unit normal to the current sheet and $n^j = \delta^{jz} = (0\ 0\ 1)$. For drift velocity, this means we just set
$$
v^z = -\frac{\gamma_{xz} v^x + \gamma_{yz} v^y}{\gamma_{zz}}.
$$
This reduces to $v^z = 0$ in flat space, as one would expect. We then express the Valencia velocity in terms of the drift velocity as $\bar{v}^i = \frac{1}{\alpha} \left( v^i + \beta^i \right)$.

```python
# We will use once more the trick from above with min and max without if. However, we we'll need a function
# that returns either 0 or 1, so as to choose between two otherwise mathetmatically unrelated branches.
def max_normal0(a):
    # If a>0, return 1. Otherwise, return 0. This defines a 'greater than' branch.
    # WILL BREAK if a = 0.
    return (a+nrpyAbs(a))/(a+a)

def min_normal0(a):
    # If a<0, return 1. Otherwise, return 0. This defines a 'less than' branch.
    # WILL BREAK if a = 0.
    return (a-nrpyAbs(a))/(a+a)

# This number determines how far away (in grid points) we will apply the fix.
grid_points_from_z_plane = par.Cparameters("REAL",thismodule,"grid_points_from_z_plane",4.0)

if par.parval_from_str("enforce_current_sheet_prescription"):
    # Calculate the drift velocity
    driftvU = ixp.zerorank1()
    for i in range(3):
        driftvU[i] = alpha*ValenciavU[i] - betaU[i]

    # The direct approach, used by the original GiRaFFE:
    # v^z = -(\gamma_{xz} v^x + \gamma_{yz} v^y) / \gamma_{zz}
    newdriftvU2 = -(gammaDD[0][2]*driftvU[0] + gammaDD[1][2]*driftvU[1])/gammaDD[2][2]
    # Now that we have the z component, it's time to substitute its Valencia form in.
    # Remember, we only do this if abs(z) < (k+0.01)*dz. Note that we add 0.01; this helps
    # avoid floating point errors and division by zero. This is the same as abs(z) - (k+0.01)*dz<0
    boundary = nrpyAbs(rfm.xx[2]) - (grid_points_from_z_plane+sp.sympify(0.01))*gri.dxx[2]
    ValenciavU[2] = min_normal0(boundary)*(newdriftvU2+betaU[2])/alpha \
                  + max_normal0(boundary)*ValenciavU[2]

```

Below is an experiment in coding this more abstractly. While it works, it's a bit harder to follow than the direct approach, which is what is coded above
```python
# Set the Cartesian normal vector. This can be expanded later to arbitrary sheets and coordinate systems.
nU = ixp.zerorank1()
nU[2] = 1
# Lower the index, as usual:
nD = ixp.zerorank1()
for i in range(3):
    for j in range(3):
        nD[i] = gammaDD[i][j]*nU[j]

if par.parval_from_str("enforce_current_sheet_prescription"):
    # Calculate the drift velocity
    driftvU = ixp.declarerank1("driftvU")

    inner_product = sp.sympify(0)
    for i in range(3):
        inner_product += driftvU[i]*nD[i] # This is the portion of the drift velocity normal to the z plane
                                          # In flat space, this is just v^z
    # We'll use a sympy utility to solve for v^z. This should make it easier to generalize later
    newdriftvU2 = sp.solve(inner_product,driftvU[2]) # This outputs a list with a single element.
                                                     # Take the 0th element so .subs() works right.
    newdriftvU2 = newdriftvU2[0] # In flat space this reduces to v^z=0
    for i in range(3):
        # Now, we substitute drift velocity in terms of our preferred Valencia velocity
        newdriftvU2 = newdriftvU2.subs(driftvU[i],alpha*ValenciavU[i]-betaU[i])

    # Now that we have the z component, it's time to substitute its Valencia form in.
    # Remember, we only do this if abs(z) < (k+0.01)*dz. Note that we add 0.01; this helps
    # avoid floating point errors and division by zero. This is the same as abs(z) - (k+0.01)*dz<0
    boundary = nrpyAbs(rfm.xx[2]) - (grid_points_from_z_plane+sp.sympify(0.01))*gri.dxx[2]
    ValenciavU[2] = min_normal0(boundary)*(newdriftvU2+betaU[2])/alpha \
                  + max_normal0(boundary)*ValenciavU[2]
```

```python

```

# Step 2: The primitive-to-conservative solver \[Back to [top](#toc)\]
$$\label{p2c}$$

This function is used to recompute the conservatives $\tilde{S}_i$ after the 3-velocity is changed as part of the current sheet prescription using Eq. 21 of [arxiv:1704.00599v2](https://arxiv.org/abs/1704.00599v2). It implements the same equation used to compute the initial Poynting flux from the initial velocity: $$\tilde{S}_i = \gamma_{ij} \frac{\bar{v}^j \sqrt{\gamma}B^2}{4 \pi}$$ in terms of the Valencia 3-velocity. In the implementation here, we first calculate $B^2 = \gamma_{ij} B^i B^j$, then $v_i = \gamma_{ij} v^j$ before we calculate the equivalent expression $$\tilde{S}_i = \frac{\bar{v}_i \sqrt{\gamma}B^2}{4 \pi}.$$

Here, we will simply let the NRPy+ `GRFFE` module handle this part, since that is already validated.

```python
import GRFFE.equations as GRFFE
import GRHD.equations as GRHD

def GiRaFFE_NRPy_P2C(gammadet,gammaDD,betaU,alpha,  ValenciavU,BU, sqrt4pi):
    # After recalculating the 3-velocity, we need to update the poynting flux:
    # We'll reset the Valencia velocity, since this will be part of a second call to outCfunction.

    # First compute stress-energy tensor T4UU and T4UD:

    GRHD.u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha, betaU, gammaDD, ValenciavU)
    GRFFE.compute_smallb4U_with_driftvU_for_FFE(gammaDD, betaU, alpha, GRHD.u4U_ito_ValenciavU, BU, sqrt4pi)
    GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, GRFFE.smallb4_with_driftv_for_FFE_U)

    GRFFE.compute_TEM4UU(gammaDD, betaU, alpha, GRFFE.smallb4_with_driftv_for_FFE_U, GRFFE.smallbsquared, GRHD.u4U_ito_ValenciavU)
    GRFFE.compute_TEM4UD(gammaDD, betaU, alpha, GRFFE.TEM4UU)

    # Compute conservative variables in terms of primitive variables
    GRHD.compute_S_tildeD(alpha, sp.sqrt(gammadet), GRFFE.TEM4UD)

    global StildeD
    StildeD = GRHD.S_tildeD

```

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

As a code validation check, we will verify agreement in the SymPy expressions between
1. this tutorial and
2. the NRPy+ [GiRaFFE_NRPy/GiRaFFE_NRPy_C2P_P2C.py](../../edit/in_progress/GiRaFFE_NRPy/GiRaFFE_NRPy_C2P_P2C.py) module.

```python
all_passed=True
def comp_func(expr1,expr2,basename,prefixname2="C2P_P2C."):
    if str(expr1-expr2)!="0":
        print(basename+" - "+prefixname2+basename+" = "+ str(expr1-expr2))
        all_passed=False

def gfnm(basename,idx1,idx2=None,idx3=None):
    if idx2==None:
        return basename+"["+str(idx1)+"]"
    if idx3==None:
        return basename+"["+str(idx1)+"]["+str(idx2)+"]"
    return basename+"["+str(idx1)+"]["+str(idx2)+"]["+str(idx3)+"]"

# Still need to perform self-validation checks on C2P
# testValenciavU = ixp.declarerank1("testValenciavU",DIM=3)
# testStildeD = ixp.declarerank1("testStildeDU",DIM=3)
gammaDD = ixp.declarerank2("gammaDD","sym01")
betaU = ixp.declarerank1("betaU")
BU = ixp.declarerank1("BU")
alpha = sp.symbols('alpha',real=True)
sqrt4pi = sp.symbols('sqrt4pi', real=True)

notebook_StildeD = StildeD
StildeD = ixp.declarerank1("StildeD")

C2P_P2C.GiRaFFE_NRPy_C2P(StildeD,BU,gammaDD,gammaUU,gammadet,betaU,alpha)

expr_list = []
exprcheck_list = []
namecheck_list = []

for i in range(3):
    namecheck_list.extend([gfnm("StildeD",i),gfnm("ValenciavU",i)])
    exprcheck_list.extend([C2P_P2C.outStildeD[i],C2P_P2C.ValenciavU[i]])
    expr_list.extend([notebook_StildeD[i],ValenciavU[i]])

for i in range(len(expr_list)):
    comp_func(expr_list[i],exprcheck_list[i],namecheck_list[i])

import sys
if all_passed:
    print("ALL TESTS PASSED!")
else:
    print("ERROR: AT LEAST ONE TEST DID NOT PASS")
    sys.exit(1)
```

    ALL TESTS PASSED!

```python
all_passed=True

gammaDD = ixp.declarerank2("gammaDD","sym01")
betaU = ixp.declarerank1("betaU")
ValenciavU = ixp.declarerank1("ValenciavU")
BU = ixp.declarerank1("BU")
alpha = sp.symbols('alpha',real=True)
sqrt4pi = sp.symbols('sqrt4pi', real=True)
GiRaFFE_NRPy_P2C(gammadet,gammaDD,betaU,alpha,  ValenciavU,BU, sqrt4pi)

C2P_P2C.GiRaFFE_NRPy_P2C(gammadet,gammaDD,betaU,alpha,  ValenciavU,BU, sqrt4pi)

expr_list = []
exprcheck_list = []
namecheck_list = []

for i in range(3):
    namecheck_list.extend([gfnm("StildeD",i)])
    exprcheck_list.extend([C2P_P2C.StildeD[i]])
    expr_list.extend([StildeD[i]])

for i in range(len(expr_list)):
    comp_func(expr_list[i],exprcheck_list[i],namecheck_list[i])

import sys
if all_passed:
    print("ALL TESTS PASSED!")
else:
    print("ERROR: AT LEAST ONE TEST DID NOT PASS")
    sys.exit(1)

```

    ALL TESTS PASSED!

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

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

```python
!jupyter nbconvert --to latex --template latex_nrpy_style.tplx --log-level='WARN' Tutorial-GiRaFFE_NRPy-C2P_P2C.ipynb
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_NRPy-C2P_P2C.tex
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_NRPy-C2P_P2C.tex
!pdflatex -interaction=batchmode Tutorial-GiRaFFE_NRPy-C2P_P2C.tex
!rm -f Tut*.out Tut*.aux Tut*.log
```

    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode
    This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
     restricted \write18 enabled.
    entering extended mode


```
!pip install qiskit
import numpy as np
from qiskit import(
    QuantumCircuit,
    execute,
    Aer)
from qiskit.visualization import plot_histogram

simulator = Aer.get_backend('qasm_simulator')

circuit = QuantumCircuit(2,2)
circuit.h(0)
circuit.cx(0,1)
circuit.measure([0,1],[0,1])

job = execute(circuit,simulator,shots=1000)
result = job.result()

counts = result.get_counts(circuit)
print("\nTotal count for 00 and 11 are:",counts)

circuit.draw()
plot_histogram(counts)
```