# Read and Validate [LORENE](https://lorene.obspm.fr/) Initial Data

## Author(s): Zach Etienne & Leo Werneck

```python
import os, sys
nrpy_dir_path = os.path.join("..")
if nrpy_dir_path not in sys.path:
    sys.path.append(nrpy_dir_path)

if not os.path.exists(os.path.join("lorene_standalone", "Lorene", "Lib", "liblorenef77_g.a")):
    print("""# Error: Lorene hasn't been compiled yet. Please run the following from nrpytutorial/:
cd in_progress  # Make sure you're in the nrpytutorial root directory.
git clone https://bitbucket.org/zach_etienne/lorene_standalone.git
cd lorene_standalone/
# For "Lorene1": wget http://astro.phys.wvu.edu/zetienne/resu.d
# Lorene2 (latest):
wget --no-check-certificate https://ccrgpages.rit.edu/~jfaber/BNSID/Data/simple_polytrope/gam2.5/gam2.5_1.4_1.4/gam2.5_1.4_1.4_hr/gam2.5_1.4_1.4_hr_50/resu_5.000000e+01_1.520000e+00_1.520000e+00.d -O resu.d
cd Lorene/
HOME_LORENE=`pwd` make -j20
""")
    sys.exit(1)
if not os.path.exists(os.path.join("lorene_standalone", "resu.d")):
    print("""# Error: resu.d not found.
# Be sure to go into nrpytutorial
# and run:
cd in_progress/lorene_standalone
# For "Lorene1": wget http://astro.phys.wvu.edu/zetienne/resu.d
# Lorene2 (latest):
wget --no-check-certificate https://ccrgpages.rit.edu/~jfaber/BNSID/Data/simple_polytrope/gam2.5/gam2.5_1.4_1.4/gam2.5_1.4_1.4_hr/gam2.5_1.4_1.4_hr_50/resu_5.000000e+01_1.520000e+00_1.520000e+00.d -O resu.d
""")
    sys.exit(1)
```

```python
import shutil
import reference_metric as rfm
import NRPy_param_funcs as par
import cmdline_helper as cmd
from outputC import add_to_Cfunction_dict

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

# Step P2: Set basic NRPy+ parameters
CoordSystem = "Spherical"
par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem)

enable_rfm_precompute = True
par.set_parval_from_str("reference_metric::rfm_precompute_to_Cfunctions_and_NRPy_basic_defines", "True")

# Step P3: Set up reference metric quantities
rfm.reference_metric()

# Step P4: Disable FD_functions
enable_FD_functions = False

# STep P5: Disable SIMD
enable_SIMD = False

if enable_rfm_precompute:
    cmd.mkdir(os.path.join(Ccodesdir, "rfm_files/"))
    par.set_parval_from_str("reference_metric::enable_rfm_precompute", "True")
    par.set_parval_from_str("reference_metric::rfm_precompute_Ccode_outdir", os.path.join(Ccodesdir, "rfm_files/"))

if enable_SIMD:
    cmd.mkdir(os.path.join(Ccodesdir,"SIMD"))
    shutil.copy(os.path.join("SIMD/")+"SIMD_intrinsics.h",os.path.join(Ccodesdir,"SIMD/"))

# Dummy parameter
thismodule = "Lorene_ID"
wavespeed  = par.Cparameters("REAL",thismodule,"wavespeed",1.0)
```

```python
domain_size     = 64
sinh_width      = 0.2
sinhv2_const_dr = 0.05
SymTP_bScale    = 1.0
CFL_FACTOR      = 0.5

# Step 1.c.i: Set free_parameters.h
with open(os.path.join(Ccodesdir,"free_parameters.h"),"w") as file:
    file.write("""
// Free parameters related to physical system:
params.wavespeed = 1.0;

// Free parameters related to numerical timestep:
REAL CFL_FACTOR = """+str(CFL_FACTOR)+";\n")

# Append to $Ccodesrootdir/free_parameters.h reference metric parameters based on generic
#    domain_size,sinh_width,sinhv2_const_dr,SymTP_bScale,
#    parameters set above.
rfm.out_default_free_parameters_for_rfm(os.path.join(Ccodesdir,"free_parameters.h"),
                                        domain_size,sinh_width,sinhv2_const_dr,SymTP_bScale)
```

```python
# Step 1.c.iv: Generate declare_Cparameters_struct.h, set_Cparameters_default.h, and set_Cparameters[].h
par.generate_Cparameters_Ccodes(os.path.join(Ccodesdir))
```

```python
def add_to_Cfunction_dict_ID_Lorene_ADM_quantities_new_way():
    desc = """(c) 2021 Leo Werneck"""
    includes = ["NRPy_basic_defines.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "ID_Lorene_ADM_quantities"

    print("Writing "+name+" function")

    params   = """const paramstruct *restrict params,
                  const int i0i1i2[3],const REAL xyz_or_rthph[3],
                  const ID_inputs other_inputs,
                  REAL *gammaDD00,REAL *gammaDD01,REAL *gammaDD02,REAL *gammaDD11,REAL *gammaDD12,REAL *gammaDD22,
                  REAL *KDD00,REAL *KDD01,REAL *KDD02,REAL *KDD11,REAL *KDD12,REAL *KDD22,
                  REAL *alpha,
                  REAL *betaU0,REAL *betaU1,REAL *betaU2,
                  REAL *BU0,REAL *BU1,REAL *BU2"""
    body     = """
    // Set useful indices
    const int i0  = i0i1i2[0];
    const int i1  = i0i1i2[1];
    const int i2  = i0i1i2[2];
    const int idx = IDX3S(i0,i1,i2);

    // Lapse function alpha
    *alpha = other_inputs.alp[idx];

    // Shift vector beta^{i}
    *betaU0 = other_inputs.betax[idx];
    *betaU1 = other_inputs.betay[idx];
    *betaU2 = other_inputs.betaz[idx];

    // B^{i}
    *BU0 = 0.0;
    *BU1 = 0.0;
    *BU2 = 0.0;

    // Spatial metric gamma_{ij}
    *gammaDD00 = other_inputs.gxx[idx];
    *gammaDD01 = other_inputs.gxy[idx];
    *gammaDD02 = other_inputs.gxz[idx];
    *gammaDD11 = other_inputs.gyy[idx];
    *gammaDD12 = other_inputs.gyz[idx];
    *gammaDD22 = other_inputs.gzz[idx];

    // Extrinsic curvature K_{ij}
    *KDD00    = other_inputs.kxx[idx];
    *KDD01    = other_inputs.kxy[idx];
    *KDD02    = other_inputs.kxz[idx];
    *KDD11    = other_inputs.kyy[idx];
    *KDD12    = other_inputs.kyz[idx];
    *KDD22    = other_inputs.kzz[idx];
"""
    loopopts = "InteriorPoints"
    add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body,enableCparameters=True)

    print("Finished writing "+name+" function")
```

```python
import ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear as atob
atob.Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear("Cartesian","ID_Lorene_ADM_quantities",
                                                            Ccodesdir=Ccodesdir,loopopts="")

def add_to_Cfunction_dict_initial_data_new_way():
    desc     = """(c) 2021 Leo Werneck"""
    includes = ["NRPy_basic_defines.h","NRPy_function_prototypes.h",
                "ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",
                "ID_BSSN__ALL_BUT_LAMBDAs.h",
                "ID_BSSN_lambdas.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "initial_data"

    print("Writing "+name+" function")

    params   = "const paramstruct *restrict params,REAL *restrict xx[3],ID_inputs other_inputs,REAL *in_gfs"
    body     = r"""
    // Set up BSSN quantities
    ID_BSSN__ALL_BUT_LAMBDAs(params,xx,other_inputs,in_gfs);
    ID_BSSN_lambdas(         params,xx,             in_gfs);
"""
    add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body,enableCparameters=False)

    print("Finished writing "+name+" function")
```

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

```python
import sympy as sp
import indexedexp as ixp
import grid as gri
import GRFFE.equations as GRFFE

# Metric quantities
DIM     = 3
alpha   = sp.Symbol("other_inputs.alp[IDX3S(i0,i1,i2)]",real=True)
betaU   = ixp.zerorank1()
gammaDD = ixp.zerorank2()
for i in range(DIM):
    betaU[i] = sp.Symbol("other_inputs.beta"+chr(ord('x')+i)+"[IDX3S(i0,i1,i2)]",real=True)
    for j in range(i,DIM):
        gammaDD[i][j] = sp.Symbol("other_inputs.g"+chr(ord('x')+i)+chr(ord('x')+j)+"[IDX3S(i0,i1,i2)]",real=True)

# Hydro quantities
rho_b    = gri.register_gridfunctions("AUX","rhob")
u4U      = ixp.register_gridfunctions_for_single_rank1("AUX","u4U",DIM=4)
smallb4U = ixp.register_gridfunctions_for_single_rank1("AUX","smallb4U",DIM=4)
GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, smallb4U)

M_PI,K,Gamma = sp.symbols("M_PI K Gamma",real=True)

# Pressure
P = K * rho_b**Gamma

# Specific internal energy
epsilon = P/rho_b/(Gamma-1)

import GRMHD.equations as GRMHDeqs
GRMHDeqs.compute_GRMHD_T4UU(gammaDD,betaU,alpha, rho_b,P,epsilon,u4U, smallb4U, GRFFE.smallbsquared)

# Compute rho
n4D    = ixp.zerorank1(DIM=4)
n4D[0] = -alpha
rho    = sp.sympify(0)
for mu in range(4):
    for nu in range(4):
        rho += n4D[mu] * n4D[nu] * GRMHDeqs.T4UU[mu][nu]

rhoADM = gri.register_gridfunctions("AUX","rhoADM")
```

```python
def add_to_Cfunction_dict_set_hydro_quantities_new_way():
    desc = """(c) 2021 Leo Werneck"""
    includes = ["NRPy_basic_defines.h","bin_ns.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "set_hydro_quantities"
    params   = """const paramstruct *restrict params, const ID_inputs other_inputs, REAL *restrict aux_gfs"""
    body     = r"""
    // Set the index
    const int idx = IDX3S(i0,i1,i2);

    // Read in needed metric quantities
    const REAL alpL   = other_inputs.alp[idx];
    const REAL gxxL   = other_inputs.gxx[idx];
    const REAL gxyL   = other_inputs.gxy[idx];
    const REAL gxzL   = other_inputs.gxz[idx];
    const REAL gyyL   = other_inputs.gyy[idx];
    const REAL gyzL   = other_inputs.gyz[idx];
    const REAL gzzL   = other_inputs.gzz[idx];
    const REAL betaxL = other_inputs.betax[idx];
    const REAL betayL = other_inputs.betay[idx];
    const REAL betazL = other_inputs.betaz[idx];

    // rho_b (don't forget that we need to floor it!)
    const REAL rho_b = std::max(other_inputs.nbar[idx] / other_inputs.rho_b_unit,1e-12);

    // Velocities (remember that Lorene gives the Valencia velocity)
    REAL velx = other_inputs.u_euler_x[idx];
    REAL vely = other_inputs.u_euler_y[idx];
    REAL velz = other_inputs.u_euler_z[idx];

    // Adapted from IllinoisGRMHD
    REAL one_minus_one_over_alpha_u0_squared = gxxL * velx * velx +
                                           2.0*gxyL * velx * vely +
                                           2.0*gxzL * velx * velz +
                                               gyyL * vely * vely +
                                           2.0*gyzL * vely * velz +
                                               gzzL * velz * velz;

    // Apply velocity limit (W is the Lorentz factor)
    const REAL W_max = 10.0;
    const REAL ONE_MINUS_ONE_OVER_WMAX_SQUARED = 1.0 - 1.0/(W_max*W_max);
    if( one_minus_one_over_alpha_u0_squared > ONE_MINUS_ONE_OVER_WMAX_SQUARED ) {
        REAL correction_fac = sqrt(ONE_MINUS_ONE_OVER_WMAX_SQUARED/one_minus_one_over_alpha_u0_squared);
        velx *= correction_fac;
        vely *= correction_fac;
        velz *= correction_fac;
        one_minus_one_over_alpha_u0_squared = ONE_MINUS_ONE_OVER_WMAX_SQUARED;
        fprintf(stderr,"BAAAD: Initial data with very high velocities!\n");
    }

    // Now compute u^{mu}
    const REAL alpha_u0 = 1.0/sqrt(1.0-one_minus_one_over_alpha_u0_squared);
    const REAL u4U0     = alpha_u0/alpL;
    const REAL u4U1     = u4U0 * ( velx * alpL - betaxL );
    const REAL u4U2     = u4U0 * ( vely * alpL - betayL );
    const REAL u4U3     = u4U0 * ( velz * alpL - betazL );

    // Set the gridfunctions
    aux_gfs[IDX4ptS(RHOBGF,idx)] = rho_b;
    aux_gfs[IDX4ptS(U4U0GF,idx)] = u4U0;
    aux_gfs[IDX4ptS(U4U1GF,idx)] = u4U1;
    aux_gfs[IDX4ptS(U4U2GF,idx)] = u4U2;
    aux_gfs[IDX4ptS(U4U3GF,idx)] = u4U3;

    // TODO: extend to nonzero magnetic fields
    aux_gfs[IDX4ptS(SMALLB4U0GF,idx)] = 0.0;
    aux_gfs[IDX4ptS(SMALLB4U1GF,idx)] = 0.0;
    aux_gfs[IDX4ptS(SMALLB4U2GF,idx)] = 0.0;
    aux_gfs[IDX4ptS(SMALLB4U3GF,idx)] = 0.0;
"""
    loopopts = "AllPoints"
    add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body,loopopts=loopopts)

    print("Finished writing "+name+" function")
```

```python
def add_to_Cfunction_dict_Hamiltonian_constraint_source_term_new_way():
    desc = """(c) 2021 Leo Werneck"""
    includes = ["NRPy_basic_defines.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "Hamiltonian_constraint_source_term"

    print("Writing "+name+" function")

    params   = """rfm_struct *restrict rfmstruct,const paramstruct *restrict params,
                  const REAL K, const REAL Gamma, ID_inputs other_inputs,
                  REAL *restrict aux_gfs"""
    body     = """
    const int idx        = IDX3S(i0,i1,i2);
    const REAL rhob      = aux_gfs[IDX4ptS(RHOBGF,idx)];
    const REAL u4U0      = aux_gfs[IDX4ptS(U4U0GF,idx)];
    const REAL u4U1      = aux_gfs[IDX4ptS(U4U1GF,idx)];
    const REAL u4U2      = aux_gfs[IDX4ptS(U4U2GF,idx)];
    const REAL u4U3      = aux_gfs[IDX4ptS(U4U3GF,idx)];
    const REAL smallb4U0 = aux_gfs[IDX4ptS(SMALLB4U0GF,idx)];
    const REAL smallb4U1 = aux_gfs[IDX4ptS(SMALLB4U1GF,idx)];
    const REAL smallb4U2 = aux_gfs[IDX4ptS(SMALLB4U2GF,idx)];
    const REAL smallb4U3 = aux_gfs[IDX4ptS(SMALLB4U3GF,idx)];
    """+outputC(rho,gri.gfaccess("aux_gfs", "rhoADM"), "returnstring",
                params="outCverbose=False")
    loopopts = "InteriorPoints"
    add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body,loopopts=loopopts)

    print("Finished writing "+name+" function")
```

```python
cmd.mkdir(os.path.join(Ccodesdir,"rfm_files/"))
par.set_parval_from_str("reference_metric::enable_rfm_precompute","True")
par.set_parval_from_str("reference_metric::rfm_precompute_Ccode_outdir",os.path.join(Ccodesdir,"rfm_files/"))

# Now register the Hamiltonian as a gridfunction.
# H = gri.register_gridfunctions("AUX","H")
# Then define the Hamiltonian constraint and output the optimized C code
import BSSN.BSSN_constraints as bssncon
import BSSN.BSSN_stress_energy_source_terms as Bsest
bssncon.BSSN_constraints(add_T4UUmunu_source_terms=False)

# Reset to False to disable rfm_precompute
par.set_parval_from_str("reference_metric::enable_rfm_precompute","False")
rfm.ref_metric__hatted_quantities()

def add_to_Cfunction_dict_Hamiltonian_constraint_no_source_term_new_way():
    desc = """(c) 2021 Leo Werneck"""
    includes = ["NRPy_basic_defines.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "Hamiltonian_constraint_no_source_term"

    print("Writing "+name+" function")

    params   = """rfm_struct *restrict rfmstruct,const paramstruct *restrict params,
                  REAL *restrict in_gfs, REAL *restrict aux_gfs"""
    body     = fin.FD_outputC("returnstring",lhrh(lhs=gri.gfaccess("aux_gfs", "H"), rhs=bssncon.H),
                              params="outCverbose=False")
    loopopts = "InteriorPoints,enable_rfm_precompute"
    add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body,loopopts=loopopts)

    print("Finished writing "+name+" function")
```

```python
from outputC import outC_NRPy_basic_defines_h_dict
def generate_supplementary_dict():
    # First register C functions needed by grid

    # Then set up the dictionary entry for grid in NRPy_basic_defines
    supplementary_dict = {}
    supplementary_dict["Lorene_ID"] = r"""
typedef struct of_ID_inputs {
    // This struct is used to store Lorene
    // initial data in Cartesian basis
    REAL *alp,*betax,*betay,*betaz;
    REAL *gxx,*gxy,*gxz,*gyy,*gyz,*gzz;
    REAL *kxx,*kxy,*kxz,*kyy,*kyz,*kzz;
    REAL *nbar, *u_euler_x,*u_euler_y,*u_euler_z;
    REAL rho_b_unit;
} ID_inputs;
"""
    return supplementary_dict
```

```python
def add_to_Cfunction_dict_main__interpolator_new_way():
    print("Writing main function")
    desc = """
(c) 2009 Erik Schnetter
(c) 2010 Frank Loeffler
Edits by Z Etienne 2021
"""

    includes = ["<cstdio>", "<cstring>", "<vector>", "<ios>", "<iostream>",
                "assert.h", "stdlib.h", "bin_ns.h", "unites.h",
                "NRPy_basic_defines.h","NRPy_function_prototypes.h"]

    prefunc = r"""
using namespace std;

// define namespace here for old versions of Lorene that don't do so
namespace Lorene {}
using namespace Lorene;
"""

    c_type = "int"
    name = "main"

    print("Writing "+name+" function")

    params = "int argc, const char *argv[]"
    body = r"""
  // Step 0: Check correct usage
  if((argc < 9) || (argc > 10)) {
    fprintf(stderr,"Error, incorrect usage. Usage: ./standalone_interpolator [Nx0] [Nx1] [Nx2] [offset_axis] [offset_star_1] [offset_star_2] [Gamma] [filename (resu.d)] [(optional) initial shift (zero or lorene)]\n");
    exit(1);
  }

  // Step 0.a: Set up physical constants for converting quantities
  //           from SI units (Lorene) to Geometrized units (NRPy)
  // Be aware: these are the constants Lorene uses. They do differ from other
  // conventions, but they gave the best results in some tests.
  double const c_light  = Unites::c_si;      // speed of light [m/s]
  double const nuc_dens = Unites::rhonuc_si; // Nuclear density as used in Lorene units [kg/m^3]
  double const G_grav   = Unites::g_si;      // gravitational constant [m^3/kg/s^2]
  double const M_sun    = Unites::msol_si;   // solar mass [kg]

  // Step 0.b: Geometrized units in terms of SI units:
  // (These are derived from M = M_sun, c = G = 1,
  // and using 1/M_sun for the magnetic field)
  double const geomM = M_sun;
  double const geomL = geomM * G_grav / pow(c_light,2);
  double const geomT = geomL / c_light;

  // Step 0.c: Other quantities
  double const coord_unit = geomL / 1.0e+3;       // from km (~1.477)
  double const rho_b_unit = geomM / pow(geomL,3); // from kg/m^3

  // Step 0.d: Set initial shift from user input or default to zero
  char initial_shift[200];
  if( argc == 10 ) {
    sprintf(initial_shift,"%s",argv[9]);
  }
  else {
    sprintf(initial_shift,"%s","zero");
  }

  // Step 0.f: Set up numerical grid structure, first in space...
  const int Nxx[3] = { atoi(argv[1]), atoi(argv[2]), atoi(argv[3]) };
  if(Nxx[0]%2 != 0 || Nxx[1]%2 != 0 || Nxx[2]%2 != 0) {
    printf("Error: Cannot guarantee a proper cell-centered grid if number of grid cells not set to even number.\n");
    printf("       For example, in case of angular directions, proper symmetry zones will not exist.\n");
    exit(1);
  }

  int offset_index;
  REAL offset_star1_xyz[3] = {0,0,0};
  REAL offset_star2_xyz[3] = {0,0,0};
  char offset_axis[10];
  sprintf(offset_axis,"%s",argv[4]);

  if( !strcmp(offset_axis,"x") ) {
    offset_index = 0;
    printf("Found x\n");
  }
  else if( !strcmp(offset_axis,"y") ) {
    printf("Found y\n");
    offset_index = 1;
  }
  else if( !strcmp(offset_axis,"z") ) {
    printf("Found z\n");
    offset_index = 2;
  }
  else {
    printf("Found error\n");
    fprintf(stderr,"Error: unsupported offset axis: %s. Supported options are: x, y, and z\n");
    exit(1);
  }
  offset_star1_xyz[offset_index] = strtod(argv[5],NULL) / coord_unit;
  offset_star2_xyz[offset_index] = strtod(argv[6],NULL) / coord_unit;

  printf("Beginning analysis of Lorene initial data.\n");
  printf("Grid #1 will be centered at (x,y,z) = (%g,%g,%g)\n",offset_star1_xyz[0],offset_star1_xyz[1],offset_star1_xyz[2]);
  printf("Grid #2 will be centered at (x,y,z) = (%g,%g,%g)\n",offset_star2_xyz[0],offset_star2_xyz[1],offset_star2_xyz[2]);
  printf("Grid #3 will be centered at (x,y,z) = (0,0,0)\n");

  const int ngrids = 3;
  for(int n_grid=1;n_grid<=ngrids;n_grid++) {

    printf("Beginning analysis of Grid #%d\n",n_grid);

    // Step 0.e: Set up NRPy parameter struct
    paramstruct params;
#include "set_Cparameters_default.h"

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

    if(n_grid == 1) {
      params.Cart_originx = offset_star1_xyz[0];
      params.Cart_originy = offset_star1_xyz[1];
      params.Cart_originz = offset_star1_xyz[2];
      params.RMAX         = 24;
    }
    else if(n_grid == 2) {
      params.Cart_originx = offset_star2_xyz[0];
      params.Cart_originy = offset_star2_xyz[1];
      params.Cart_originz = offset_star2_xyz[2];
      params.RMAX         = 24;
    }

    // Step 0.h: Uniform coordinate grids are stored to *xx[3]
    REAL *xx[3];

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

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

    // Step 0.l: Allocate memory for initial data gridfunctions on NRPy grid
    REAL *id_gfs  = (REAL *)malloc(NUM_EVOL_GFS*Nxx_plus_2NGHOSTS_tot*sizeof(REAL));
    REAL *aux_gfs = (REAL *)malloc(NUM_AUX_GFS*Nxx_plus_2NGHOSTS_tot*sizeof(REAL));

    // Step 0.m: Set up precomputed reference metric arrays
    // Step 0.m.i: Allocate space for precomputed reference metric arrays.
    rfm_struct rfmstruct;
    rfm_precompute_rfmstruct_malloc(&params, &rfmstruct);

    // Step 0.m.ii: Define precomputed reference metric arrays.
    {
#include "set_Cparameters-nopointer.h"
      rfm_precompute_rfmstruct_define(&params, xx, &rfmstruct);
    }

    // LORENE COORDINATES, != NRPy COORDINATES
    vector<double> x_Lorene(Nxx_plus_2NGHOSTS_tot);
    vector<double> y_Lorene(Nxx_plus_2NGHOSTS_tot);
    vector<double> z_Lorene(Nxx_plus_2NGHOSTS_tot);

#pragma omp parallel for
    for(int i2=0;i2<Nxx_plus_2NGHOSTS2;i2++) {
      for(int i1=0;i1<Nxx_plus_2NGHOSTS1;i1++) {
        for(int i0=0;i0<Nxx_plus_2NGHOSTS0;i0++) {
          const int idx = IDX3S(i0,i1,i2);
          REAL xCart[3];
          xx_to_Cart(&params,xx,i0,i1,i2,xCart);
          x_Lorene[idx] = xCart[0] * coord_unit;
          y_Lorene[idx] = xCart[1] * coord_unit;
          z_Lorene[idx] = xCart[2] * coord_unit;
        }
      }
    }

    // --------------------------------------------------------------
    //   CHECKING FILE NAME EXISTENCE
    // --------------------------------------------------------------
    FILE *file;
    char filename[100];
    sprintf(filename,"%s",argv[8]);
    if ((file = fopen(filename, "r")) != NULL)
      fclose(file);
    else {
      fprintf(stderr,
              "File \"%s\" does not exist. ABORTING\n", filename);
      exit(1);
    }

    printf( "Reading from file \"%s\"\n", filename);

    Bin_NS bin_ns(Nxx_plus_2NGHOSTS_tot, &x_Lorene[0], &y_Lorene[0], &z_Lorene[0], filename);

    printf( "omega [rad/s]:       %g\n", bin_ns.omega);
    printf( "dist [km]:           %g\n", bin_ns.dist);
    printf( "dist_mass [km]:      %g\n", bin_ns.dist_mass);
    printf( "mass1_b [M_sun]:     %g\n", bin_ns.mass1_b);
    printf( "mass2_b [M_sun]:     %g\n", bin_ns.mass2_b);
    printf( "mass_ADM [M_sun]:    %g\n", bin_ns.mass_adm);
    printf( "L_tot [G M_sun^2/c]: %g\n", bin_ns.angu_mom);
    printf( "rad1_x_comp [km]:    %g\n", bin_ns.rad1_x_comp);
    printf( "rad1_y [km]:         %g\n", bin_ns.rad1_y);
    printf( "rad1_z [km]:         %g\n", bin_ns.rad1_z);
    printf( "rad1_x_opp [km]:     %g\n", bin_ns.rad1_x_opp);
    printf( "rad2_x_comp [km]:    %g\n", bin_ns.rad2_x_comp);
    printf( "rad2_y [km]:         %g\n", bin_ns.rad2_y);
    printf( "rad2_z [km]:         %g\n", bin_ns.rad2_z);
    printf( "rad2_x_opp [km]:     %g\n", bin_ns.rad2_x_opp);
    double K = bin_ns.kappa_poly1 * pow((pow(c_light, 6.0) /
                                         ( pow(G_grav, 3.0) * M_sun * M_sun *
                                           nuc_dens )),bin_ns.gamma_poly1-1.);
    printf( "K [ET unit]:         %.15g\n", K);
    double Gamma = strtod(argv[7],NULL);
    printf( "Gamma:               %g\n", Gamma);

    assert(bin_ns.np == Nxx_plus_2NGHOSTS_tot);

    printf("Filling in NRPy grid points\n");

    // Set up the initial data struct
    ID_inputs other_inputs;
    other_inputs.alp        = bin_ns.nnn;
    other_inputs.gxx        = bin_ns.g_xx;
    other_inputs.gxy        = bin_ns.g_xy;
    other_inputs.gxz        = bin_ns.g_xz;
    other_inputs.gyy        = bin_ns.g_yy;
    other_inputs.gyz        = bin_ns.g_yz;
    other_inputs.gzz        = bin_ns.g_zz;
    other_inputs.kxx        = bin_ns.k_xx;
    other_inputs.kxy        = bin_ns.k_xy;
    other_inputs.kxz        = bin_ns.k_xz;
    other_inputs.kyy        = bin_ns.k_yy;
    other_inputs.kyz        = bin_ns.k_yz;
    other_inputs.kzz        = bin_ns.k_zz;
    other_inputs.betax      = bin_ns.beta_x;
    other_inputs.betay      = bin_ns.beta_y;
    other_inputs.betaz      = bin_ns.beta_z;
    other_inputs.u_euler_x  = bin_ns.u_euler_x;
    other_inputs.u_euler_y  = bin_ns.u_euler_y;
    other_inputs.u_euler_z  = bin_ns.u_euler_z;
    other_inputs.nbar       = bin_ns.nbar;
    other_inputs.rho_b_unit = rho_b_unit;

    // Change units in K_{ij} and set beta^{i} to zero, if requested by the user
#pragma omp parallel for
    for(int i=0;i<Nxx_plus_2NGHOSTS_tot;i++) {
      other_inputs.kxx[i] *= coord_unit;
      other_inputs.kxy[i] *= coord_unit;
      other_inputs.kxz[i] *= coord_unit;
      other_inputs.kyy[i] *= coord_unit;
      other_inputs.kyz[i] *= coord_unit;
      other_inputs.kzz[i] *= coord_unit;
      if(strcmp(initial_shift,"zero")) {
        other_inputs.betax[i] = 0.0;
        other_inputs.betay[i] = 0.0;
        other_inputs.betaz[i] = 0.0;
      }
    }

    // Compute BSSN quantities from ADM Cartesian quantities
    printf("Computing BSSN quantities...\n");
    initial_data(&params,xx,other_inputs,id_gfs);

    // Set hydro quantities
    printf("Setting hydro quantities...\n");
    set_hydro_quantities(&params,other_inputs,aux_gfs);

    // Compute Hamiltonian constraint violation
    printf("Computing costraint violations...\n");
    Hamiltonian_constraint_no_source_term(&rfmstruct,&params,id_gfs,aux_gfs);
    Hamiltonian_constraint_source_term(&rfmstruct,&params,K,Gamma,other_inputs,aux_gfs);

    {
      printf("Outputting 1D data...\n");
      // 1D data
      char filename[256];
      sprintf(filename,"initial_data_grid_%d.x.asc",n_grid);
      FILE* fp = fopen(filename,"w");
      const int i1_out = Nxx_plus_2NGHOSTS1/2;
      int i2_out       = NGHOSTS;
      for(int i0=NGHOSTS;i0<Nxx_plus_2NGHOSTS0-NGHOSTS;i0++) {
        REAL xCart[3];
        xx_to_Cart(&params,xx,i0,i1_out,i2_out,xCart);

        const int idx = IDX3S(i0,i1_out,i2_out);
        const REAL H = aux_gfs[IDX4ptS(HGF,idx)] - 16.0*M_PI*aux_gfs[IDX4ptS(RHOADMGF,idx)];
        fprintf(fp,"%e %e\n",xCart[0],H);
      }
      i2_out = Nxx_plus_2NGHOSTS2/2;
      for(int i0=NGHOSTS;i0<Nxx_plus_2NGHOSTS0-NGHOSTS;i0++) {
        REAL xCart[3];
        xx_to_Cart(&params,xx,i0,i1_out,i2_out,xCart);

        const int idx = IDX3S(i0,i1_out,i2_out);
        const REAL H = aux_gfs[IDX4ptS(HGF,idx)] - 16.0*M_PI*aux_gfs[IDX4ptS(RHOADMGF,idx)];
        fprintf(fp,"%e %e\n",xCart[0],H);
      }
      fclose(fp);
    }
    {
      printf("Outputting 2D data...\n");
      // 2D data
      char filename[256];
      sprintf(filename,"initial_data_grid_%d.xy.asc",n_grid);
      FILE* fp = fopen(filename,"w");
      const int i1_out = Nxx_plus_2NGHOSTS1/2;
      for(int i2=0;i2<Nxx_plus_2NGHOSTS2;i2++) {
        for(int i0=0;i0<Nxx_plus_2NGHOSTS0;i0++) {
          REAL xCart[3];
          xx_to_Cart(&params,xx,i0,i1_out,i2,xCart);
          const int idx = IDX3S(i0,i1_out,i2);
          fprintf(fp,"%e %e %e\n",xCart[0],xCart[1],aux_gfs[IDX4ptS(RHOBGF,idx)]);
        }
        fprintf(fp,"\n");
      }
      fclose(fp);
    }

    for(int i=0;i<3;i++) free(xx[i]);
    free(id_gfs);
    free(aux_gfs);

    printf ("Done.\n");
  }

"""
    add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body, enableCparameters=False)

    print("Finished writing "+name+" function")
```

```python
# add all other functions to Cfunction_dict first!
import finite_difference as fin
from outputC import outputC,lhrh

par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER",4)

function_list = [add_to_Cfunction_dict_ID_Lorene_ADM_quantities_new_way,
                 add_to_Cfunction_dict_set_hydro_quantities_new_way,
                 add_to_Cfunction_dict_initial_data_new_way,
                 add_to_Cfunction_dict_Hamiltonian_constraint_no_source_term_new_way,
                 add_to_Cfunction_dict_Hamiltonian_constraint_source_term_new_way,
                 add_to_Cfunction_dict_main__interpolator_new_way]

def master_func(f):
    f()

# import multiprocess
# with multiprocess.Pool() as pool:
#     pool.map(master_func,function_list)
for function in function_list:
    function()
```

    Writing ID_Lorene_ADM_quantities function
    Finished writing ID_Lorene_ADM_quantities function
    Finished writing set_hydro_quantities function
    Writing initial_data function
    Finished writing initial_data function
    Writing Hamiltonian_constraint_no_source_term function
    Finished writing Hamiltonian_constraint_no_source_term function
    Writing Hamiltonian_constraint_source_term function
    Finished writing Hamiltonian_constraint_source_term function
    Writing main function
    Writing main function
    Finished writing main function

```python
import outputC as outC

outC.register_C_functions_and_NRPy_basic_defines()  # #define M_PI,  etc.
gri.register_C_functions_and_NRPy_basic_defines()  # #define IDX3S(),  etc.
rfm.register_C_functions_and_NRPy_basic_defines(enable_rfm_precompute=enable_rfm_precompute)
fin.register_C_functions_and_NRPy_basic_defines(NGHOSTS_account_for_onezone_upwind=False)

# Output functions for computing all finite-difference stencils.
#   Must be called after defining all functions depending on FD stencils.
if enable_FD_functions:
    fin.output_finite_difference_functions_h(path=Ccodesdir)

# Call this last: Set up NRPy_basic_defines.h and NRPy_function_prototypes.h.
outC.construct_NRPy_basic_defines_h(Ccodesdir, enable_SIMD=enable_SIMD,
                                    supplemental_dict=generate_supplementary_dict())
outC.construct_NRPy_function_prototypes_h(Ccodesdir)
```

```python
cmd.new_C_compile(Ccodesdir, "interpolator_new_way",
                  addl_CFLAGS=["-I../Lorene/Export/C++/Include",
#                                "-I/usr/local/Cellar/fftw/3.3.9_1/include",
#                                "-I/usr/local/Cellar/gsl/2.7/include",
                               "-I../Lorene/C++/Include"],
                  addl_libraries=["-L../Lorene/Lib/",
#                                   "-L/usr/local/Cellar/fftw/3.3.9_1/lib",
#                                   "-L/usr/local/Cellar/gsl/2.7/lib",
                                  "-llorene_export", "-llorene",
                                  "-llorenef77", "-lgfortran", "-lfftw3", "-lgsl",
                                  "-lgslcblas", "-llapack", "-lblas"],
                  CC="g++")
```

    (EXEC): Executing `make -j10`...
    ld: warning: ld: warning: dylib (/usr/local/Cellar/fftw/3.3.9_1/lib/libfftw3.dylib) was built for newer macOS version (11.2) than being linked (10.16)dylib (/usr/local/Cellar/gsl/2.7/lib/libgslcblas.dylib) was built for newer macOS version (11.0) than being linked (10.16)

    ld: warning: dylib (/usr/local/Cellar/gsl/2.7/lib/libgsl.dylib) was built for newer macOS version (11.0) than being linked (10.16)
    (BENCH): Finished executing in 4.677836894989014 seconds.
    Finished compilation.

```python
os.chdir(Ccodesdir)
cmd.Execute("interpolator_new_way", "128 32 32 x 25 -25 2.5 ../resu.d")
os.chdir(os.path.join("..",".."))
```

    (EXEC): Executing `./interpolator_new_way 128 32 32 x 25 -25 2.5 ../resu.d`...
    (BENCH): Finished executing in 277.48651218414307 seconds.

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

data_dir = os.path.join("lorene_standalone","interpolator_new_way")

grid_1_x,grid_1_H = np.loadtxt(os.path.join(data_dir,"initial_data_grid_1.x.asc")).T
grid_2_x,grid_2_H = np.loadtxt(os.path.join(data_dir,"initial_data_grid_2.x.asc")).T
grid_3_x,grid_3_H = np.loadtxt(os.path.join(data_dir,"initial_data_grid_3.x.asc")).T

grid_1_x,grid_1_H = [list(x) for x in zip(*sorted(zip(grid_1_x,grid_1_H), key=lambda pair: pair[0]))]
grid_2_x,grid_2_H = [list(x) for x in zip(*sorted(zip(grid_2_x,grid_2_H), key=lambda pair: pair[0]))]
grid_3_x,grid_3_H = [list(x) for x in zip(*sorted(zip(grid_3_x,grid_3_H), key=lambda pair: pair[0]))]

# Take absolute value
grid_1_H = np.abs(grid_1_H)
grid_2_H = np.abs(grid_2_H)
grid_3_H = np.abs(grid_3_H)

# Apply floor
grid_1_H[grid_1_H<1e-15] = 1e-15
grid_2_H[grid_2_H<1e-15] = 1e-15
grid_3_H[grid_3_H<1e-15] = 1e-15

fig = plt.figure()

plt.xlabel(r"$x$ [km]")
plt.ylabel(r"$\log_{10}\left|\mathcal{H}\right|$")

plt.plot()
plt.plot(grid_1_x,np.log10(grid_1_H),label="Grid 1")
plt.plot(grid_2_x,np.log10(grid_2_H),label="Grid 2")
plt.plot(grid_3_x,np.log10(grid_3_H)+2.75*np.log10(25.0/64.0),label="Grid 3, times $(25/64)^{2.75}$")
plt.legend()
plt.grid()

outfig = "constraint_violations.png"
plt.savefig(outfig,dpi=150,facecolor='white')
plt.close(fig)
Image(outfig)
```

```python
# from scipy.interpolate import griddata

# grid_1_x,grid_1_y,grid_1_rho = np.loadtxt(os.path.join(data_dir,"initial_data_grid_1.xy.asc")).T

# ncols=3
# fig = plt.figure()

# bound   = 25/1.476625
# pl_xmin = bound*0.5
# pl_xmax = bound*1.5
# pl_ymin = -bound*0.5
# pl_ymax = +bound*0.5
# grid_x, grid_y = np.mgrid[pl_xmin:pl_xmax:800j, pl_ymin:pl_ymax:800j]
# points = np.zeros((len(grid_1_x), 2))
# for i in range(len(grid_1_x)):
#     # Zach says: No idea why x and y get flipped...
#     points[i][0] = grid_1_y[i]
#     points[i][1] = grid_1_x[i]

# plot_data = grid_1_rho

# grid = griddata(points, plot_data, (grid_x, grid_y), method='cubic')

# fig,ax = plt.subplots(dpi=300)

# # cbmin = -7
# # cbmax = -1
# cblabel = r"$\rho_{\rm b}$"

# plt.title(r"Density")
# plt.xlabel(r"$x$")
# plt.ylabel(r"$y$")

# im = plt.imshow(grid, extent=(pl_xmin,pl_xmax,pl_ymin,pl_ymax),)
# # im.set_clim(cbmin,cbmax)
# cb = fig.colorbar(im, ax=ax,extend='both')
# cb.set_label(cblabel)

# outfig = "density.png"
# plt.savefig(outfig,dpi=150,facecolor='white')
# plt.close(fig)
# Image(outfig)
```


# Neuromatch Academy: Week 2, Day 3, Tutorial 1
# Sequential Probability Ratio Test

__Content creators:__ Yicheng Fei

__Content reviewers:__ John Butler, Matt Krause, Spiros Chavlis, Michael Waskom, Jesse Livezey, and Byron Galbraith

---
#Tutorial Objectives

In this tutorial, we will consider a simplified random dot motion task. On each trial $i$, we are shown a dot moving at velocity $v_i$, either in a leftward ($v<0$) or rightward ($v>0$) direction. Although the dots' velocity varies from trial to trial, the set of all $v_i$ are  generated by a fixed probability distribution, which we know to be either:
$$
\\
\begin{eqnarray}
p_L &=& \mathcal{N}\left(-1,\; \sigma^2\right) \\
&&\textrm{or} \\
p_R &=& \mathcal{N}\left(+1,\; \sigma^2\right) \\
\end{eqnarray}
\\
$$
We want to determine whether $p_L$ or $p_R$ is the true data generating distribution.

In W2D1, we learned how to combine the sensory evidence and our prior experience with Bayes' Theorem, producing a posterior probability distribution that would let us choose between the most probable of these *two* options: accepting hypothesis $H_L$, that the data comes from the $p_L$ distribution, or accepting $H_R$, that it comes from $p_R$.

Here, we add a *third* option: choosing to collect more evidence before making a decision.

---

In this notebook we will perform a *Sequential Probability Ratio Test* between two hypotheses $H_L$ and $H_R$ by running simulations of a *Drift Diffusion Model (DDM)*.

As independent and identically distributed (*i.i.d*) samples from the true data-generating distribution coming in, we accumulate our evidence linearly until a certain criterion is met before deciding which hypothesis to accept.  Two types of stopping criterion/stopping rule will be implemented: after seeing a fixed amount of data, and after the likelihood ratio passes a pre-defined threshold. Due to the noisy nature of observations, there will be a *drifting* term governed by expected mean output and a *diffusion* term governed by observation noise.

In this tutorial, you will

* Simulate Drift-Diffusion Model with different stopping rules.
* Observe the relation between accuracy and reaction time, get an intuition about the speed/accuracy tradeoff.

```python
#@title Video 1: Tutorial Objectives
# Insert the ID of the corresponding youtube video
from IPython.display import YouTubeVideo
video = YouTubeVideo(id="DGoPoLkDiUw", width=854, height=480, fs=1)
print("Video available at https://youtu.be/" + video.id)
video
```

    Video available at https://youtu.be/DGoPoLkDiUw

---
# Setup

```python
# Imports
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt
```

```python
#@title Figure settings
import ipywidgets as widgets       # interactive display
%config InlineBackend.figure_format = 'retina'
plt.style.use("https://raw.githubusercontent.com/NeuromatchAcademy/course-content/master/nma.mplstyle")
```

```python
#@title Helper functions

def log_likelihood_ratio(xvec, p0, p1):
  """Given a sequence(vector) of observed data, calculate the log of
  likelihood ratio of p1 and p0

  Args:
    xvec (numpy vector):           A vector of scalar measurements
    p0 (Gaussian random variable): A normal random variable with `logpdf'
                                    method
    p1 (Gaussian random variable): A normal random variable with `logpdf`
                                    method

  Returns:
    llvec: a vector of log likelihood ratios for each input data point
  """
  return p1.logpdf(xvec) - p0.logpdf(xvec)

def simulate_and_plot_SPRT_fixedtime(sigma, stop_time, num_sample,
                                     verbose=True):
  """Simulate and plot a SPRT for a fixed amount of time given a std.

  Args:
    sigma (float): Standard deviation of the observations.
    stop_time (int): Number of steps to run before stopping.
    num_sample (int): The number of samples to plot.
    """

  evidence_history_list = []
  if verbose:
    print("#Trial\tTotal_Evidence\tDecision")
  for i in range(num_sample):
    evidence_history, decision, data = simulate_SPRT_fixedtime(sigma, stop_time)
    if verbose:
      print("{}\t{:f}\t{}".format(i, evidence_history[-1], decision))
    evidence_history_list.append(evidence_history)

  fig, ax = plt.subplots()
  maxlen_evidence = np.max(list(map(len,evidence_history_list)))
  ax.plot(np.zeros(maxlen_evidence), '--', c='red', alpha=1.0)
  for evidences in evidence_history_list:
    ax.plot(np.arange(len(evidences)), evidences)
    ax.set_xlabel("Time")
    ax.set_ylabel("Cumulated log likelihood ratio")
    ax.set_title("Log likelihood ratio trajectories under the fixed-time " +
                  "stopping rule")

  plt.show(fig)

def simulate_and_plot_accuracy_vs_stoptime(sigma, stop_time_list, num_sample):
  """Simulate and plot a SPRT for a fixed amount of times given a std.

  Args:
    sigma (float): Standard deviation of the observations.
    stop_time_list (int): List of number of steps to run before stopping.
    num_sample (int): The number of samples to plot.
  """
  accuracy_list, _ = simulate_accuracy_vs_stoptime(sigma, stop_time_list,
                                                   num_sample)

  fig, ax = plt.subplots()
  ax.plot(stop_time_list, accuracy_list)
  ax.set_xlabel('Stop Time')
  ax.set_ylabel('Average Accuracy')

  plt.show(fig)

def simulate_and_plot_SPRT_fixedthreshold(sigma, num_sample, alpha,
                                          verbose=True):
  """Simulate and plot a SPRT for a fixed amount of times given a std.

  Args:
    sigma (float): Standard deviation of the observations.
    num_sample (int): The number of samples to plot.
    alpha (float): Threshold for making a decision.
  """
  # calculate evidence threshold from error rate
  threshold = threshold_from_errorrate(alpha)

  # run simulation
  evidence_history_list = []
  if verbose:
    print("#Trial\tTime\tCumulated Evidence\tDecision")
  for i in range(num_sample):
    evidence_history, decision, data = simulate_SPRT_threshold(sigma, threshold)
    if verbose:
      print("{}\t{}\t{:f}\t{}".format(i, len(data), evidence_history[-1],
                                      decision))
    evidence_history_list.append(evidence_history)

  fig, ax = plt.subplots()
  maxlen_evidence = np.max(list(map(len,evidence_history_list)))
  ax.plot(np.repeat(threshold,maxlen_evidence + 1), c="red")
  ax.plot(-np.repeat(threshold,maxlen_evidence + 1), c="red")
  ax.plot(np.zeros(maxlen_evidence + 1), '--', c='red', alpha=0.5)

  for evidences in evidence_history_list:
      ax.plot(np.arange(len(evidences) + 1), np.concatenate([[0], evidences]))

  ax.set_xlabel("Time")
  ax.set_ylabel("Cumulated log likelihood ratio")
  ax.set_title("Log likelihood ratio trajectories under the threshold rule")

  plt.show(fig)

def simulate_and_plot_accuracy_vs_threshold(sigma, threshold_list, num_sample):
  """Simulate and plot a SPRT for a set of thresholds given a std.

  Args:
    sigma (float): Standard deviation of the observations.
    alpha_list (float): List of thresholds for making a decision.
    num_sample (int): The number of samples to plot.
  """
  accuracies, decision_speeds = simulate_accuracy_vs_threshold(sigma,
                                                               threshold_list,
                                                               num_sample)

  # Plotting
  fig, ax = plt.subplots()
  ax.plot(decision_speeds, accuracies, linestyle="--", marker="o")
  ax.plot([np.amin(decision_speeds), np.amax(decision_speeds)],
          [0.5, 0.5], c='red')
  ax.set_xlabel("Average Decision speed")
  ax.set_ylabel('Average Accuracy')
  ax.set_title("Speed/Accuracy Tradeoff")
  ax.set_ylim(0.45, 1.05)

  plt.show(fig)

def threshold_from_errorrate(alpha):
  """Calculate log likelihood ratio threshold from desired error rate `alpha`

  Args:
    alpha (float): in (0,1), the desired error rate

  Return:
    threshold: corresponding evidence threshold
  """
  threshold = np.log((1. - alpha) / alpha)
  return threshold
```

---

# Section 1: Introduction to the SPRT

### Sequential Probability Ratio Test(SPRT)

Suppose we receive a sequence of independent samples from distribution $p$. We know that $p$ is from $\{p_0,p_1\}$ determined by a binary latent variable $x$ and need to test between the two hyptheses:

$H_L: p=p_L \text{ or } x=0$

$H_R: p=p_R \text{ or } x=1$

When we see $n$ samples $\{x_{1}...x_n\}$, we want to calculate the total log likelihood ratio as our evidence for decision:

$$S_n = \log \frac{\prod_{i=1}^n p_R(x_i)}{\prod_{i=1}^n p_L(x_i)} = \sum_{i=1}^n \log p_R(x_i) - \sum_{i=1}^n \log p_L(x_i) \tag{1}$$

Due to the independence of samples, this can be calculated in a incremental way when new data points come in sequentially:

$$ S_n =  S_{n-1} + \log \frac{p_R(x_n)}{p_L(x_n)} = S_{n-1} + \log \Lambda_n \tag{2}$$

The stopping rule can be implemented in two ways:

1. Fixed time

Make a decision based on $S_n$ immediately when we have collected $n$ samples. That is, accept $H_R$ if $S_n > 0$, accept $H_L$ if $S_n < 0$, and accept $H_R$ with probability $\frac{1}{2}$ if $S_n = 0$. The significance level or desired error rate $\alpha$ can then be determined as

$$\alpha = \frac{1}{1+\exp(|S_n|)} \tag{4}$$

2. Thresholding

We assume equal probability to make a false positive decision and to make a false negative decision, and denote it with $\alpha$. Then we accept hypothesis $H_R$ if $S_n \ge b$ or accept hypothesis $H_L$ if $S_n \le a$ where the thresholds are determined by

$$a=\log \frac{\alpha}{1-\alpha} \;\; \; b=\log \frac{1-\alpha}{\alpha}
\tag{3}$$
(Note that these are the same value with opposite signs: $a = -b).$

## Section 1.1: SPRT as a Drift Diffusion Model (DDM)

```python
#@title Video 2: SPRT and the Random Dot Motion Task
from IPython.display import YouTubeVideo
video = YouTubeVideo(id="7WBB4M_Vf58", width=854, height=480, fs=1)
print("Video available at https://youtu.be/" + video.id)
video
```

    Video available at https://youtu.be/7WBB4M_Vf58

SPRT as a Drift Diffusion Model (DDM)

Let's assume two different Gaussian observation models conditioned on discrete latent variable $z$

$$p_L(x|z=0) = \mathcal{N}\left(\mu_L,\sigma_L^2\right)$$

$$p_R(x|z=1) = \mathcal{N}\left(\mu_R,\sigma_R^2\right)$$

Then the log likelihood ratio for a single data point $x_i$ is

$$ \log \Lambda_i = \log \frac{\sigma_L}{\sigma_R} -0.5 \left[\frac{\left(x_i-\mu_1\right)^2}{\sigma_R^2} - \frac{(x_i-\mu_L)^2}{\sigma_L^2}\right] \tag{5}$$

Without loss of generality, let's further assume the true data generating distribution is $p_R$. In this case $x_i$ can be expressed as $x_i = \mu_R + \sigma_R \epsilon$ where $\epsilon$ comes from a standard Gaussian. The foregoing formula can then be rewritten as

$$
\log \Lambda_i = \left( \log \frac{\sigma_L}{\sigma_R} + 0.5 \frac{\left(\mu_R-\mu_L\right)^2}{\sigma_L^2} \right) + \left( \frac{\mu_R-\mu_L}{\sigma_L^2}\epsilon -0.5\left[1-\left(\frac{\sigma_R}{\sigma_L}\right)^2\right]\epsilon^2  \right) \tag{6}
$$

where the first two constant terms serve as the *drifting* part and the last terms are the *diffusion* part. If we further let $\sigma_L=\sigma_R$, we can get rid of the quadratic term and this reduces to the classical discrete drift-diffusion equation where we have analytical solutions for mean and expected auto-covariance:

$$
\log \Lambda_i = 0.5 \frac{(\mu_R-\mu_L)^2}{\sigma_L^2}  + \frac{\mu_R-\mu_L}{\sigma_L^2}\epsilon, \text{where } \epsilon \sim \mathcal{N}(0,1)
$$

## Section 1.2: Simulating DDM with fixed-time stopping rule

```python
#@title Video 3: Simulate the DDM with a fixed-time stopping rule
from IPython.display import YouTubeVideo
video = YouTubeVideo(id="9WNAZnEa64Y", width=854, height=480, fs=1)
print("Video available at https://youtu.be/" + video.id)
video
```

    Video available at https://youtu.be/9WNAZnEa64Y

Exercise 1: Simulating an SPRT model
---

Assume we are performing a random dot motion task and at each time we see a moving dot with velocity $x_t$. All data points are sampled from the same distribution $p$, which is either $p_L=\mathcal{N}\left(-\mu,\sigma^2\right)$ or $p_R=\mathcal{N}\left(\mu,\sigma^2\right)$. Let's now generate some simulated data under this setting and perform SPRT using the fixed time stopping rule.

In this exercise, without loss of generality, we assume the true data-generating model is $p_R$.

* Complete the code in function `simulate_SPRT_fixedtime` to create two Gaussian random variables to represent our observation models.
* Complete the function `log_likelihood_ratio` to calculate log likelihood ratio for a sequence of data points
* Complete the code in function `simulate_SPRT_fixedtime` to calculate cumulated evidence given a list of individual evidences
* Run 10 simulations and plot the DDM traces by commenting out our provided code

```python
def simulate_SPRT_fixedtime(sigma, stop_time, true_dist=1):
  """Simulate a Sequential Probability Ratio Test with fixed time stopping
  rule. Two observation models are 1D Gaussian distributions N(1,sigma^2) and
  N(-1,sigma^2).

  Args:
    sigma (float):      Standard deviation
    stop_time (int):    Number of samples to take before stopping
    true_dist (0 or 1): Which state is the true state.

  Returns:
    evidence_history (numpy vector): the history of cumulated evidence given
                                      generated data
    decision (int): 1 for pR, 0 for pL
    data (numpy vector): the generated sequences of data in this trial
  """
  muL = -1.0
  muR = 1.0
  ############################################################################
  # Insert your code here to:
  #      Create two Gaussian variables `pL` and `pR` with mean `muL` and
  #      `muR` respectively and same std. `sigma`
  #      Hint: using `stats.norm(loc=..., scale=...)` to construct an
  #      instance of 1D Gaussian distribution
  raise NotImplementedError("`simulate_SPRT_fixedtime` is incomplete")
  ############################################################################
  pL = stats.norm(loc=..., scale=...)
  pR = stats.norm(loc=..., scale=...)

  # Generate a random sequence of data
  if true_dist == 1:
    data = pR.rvs(size=stop_time)
  else:
    data = pL.rvs(size=stop_time)

  # Calculate cumulated evidence
  ll_ratio_vec = log_likelihood_ratio(data, pL, pR)

  ############################################################################
  # Insert your code here to:
  #      Calculate cumulated evidence given a vector of individual evidences
  #      Hint: use `np.cumsum`
  ############################################################################
  evidence_history = ...

  # Make decision
  if evidence_history[-1] > 0:
    decision = 1
  elif evidence_history[-1] < 0:
    decision = 0
  else:
    decision = np.random.randint(2)

  return evidence_history, decision, data

np.random.seed(100)
sigma = 3.5  # standard deviation for pL and pR
num_sample = 10  # number of simulations to run for each stopping time
stop_time = 150 # stopping time

################################################################################
# Un-comment the following code block after completing this exercise
################################################################################
# simulate_and_plot_SPRT_fixedtime(sigma, stop_time, num_sample)
```

[*Click for solution*](https://github.com/NeuromatchAcademy/course-content/tree/master//tutorials/W2D3_DecisionMaking/solutions/W2D3_Tutorial1_Solution_51fcdced.py)

*Example output:*

### Interactive Demo: Trajectories under the fixed-time stopping rule

Now let's look at how the dynamics change if you change the noise level and stopping time.

* Play with different noise levels and stopping times and observe the corresponding trajectories. Once you have completed exercise 1, check the box to enable the demo.

```python
#@title

#@markdown Make sure you execute this cell to enable the widget!
np.random.seed(100)
num_sample = 10

@widgets.interact
def plot(sigma=(0.05, 10.0, 0.05), stop_time=(5, 500, 1)):
  simulate_and_plot_SPRT_fixedtime(sigma, stop_time, num_sample, verbose=False)
```

---
# Section 2: Accuracy vs. Stopping time

```python
#@title Video 4: Speed/Accuracy Tradeoff
from IPython.display import YouTubeVideo
video = YouTubeVideo(id="E8lvgFeIGQM", width=854, height=480, fs=1)
print("Video available at https://youtu.be/" + video.id)
video
```

    Video available at https://youtu.be/E8lvgFeIGQM

### Exercise 2: The Speed/Accuracy Tradeoff

If you stop taking samples too early, (e.g., make a decision after only seeing 5 samples), or there's a huge amount of observation noise that buries the signal, you are likely to be driven by observation noise to a negative cumulated log likelihood ratio and thus make a wrong decision. You could get a sense of this by increasing noise level or decreasing stopping time in the last exercise.

Now let's look at how decision accuracy varies with the number of samples we see quantitatively. First we'll fix our observation noise level. In this exercise you will run several repeated simulations for a certain stopping time to calculate the average decision accuracy. Accuracy is simply defined as the proportion of correct trials across our repeated simulations: $\frac{\# \textrm{ correct decisions}}{\# \textrm{ total simulation runs}}$

Do this for a range of stopping times and plot the relation between average decision accuracy and stopping time. You should get a positive correlation between these two quantities.

* Choose a noise level. For example, $\sigma=3$
* Complete the function `simulate_accuracy_vs_stoptime` to simulate and compute corresponding average accuracies for a list of stopping times.
* Plot accuracy versus stopping time using the pre-written codes

```python
sigma = 4.65  # standard deviation for observation noise
num_sample = 200  # number of simulations to run for each stopping time
stop_time_list = np.arange(1, 150, 10) # Stopping times to play with

def simulate_accuracy_vs_stoptime(sigma, stop_time_list, num_sample):
  """Calculate the average decision accuracy vs. stopping time by running
  repeated SPRT simulations for each stop time.

  Args:
      sigma (float): standard deviation for observation model
      stop_list_list (list-like object): a list of stopping times to run over
      num_sample (int): number of simulations to run per stopping time

  Returns:
      accuracy_list: a list of average accuracies corresponding to input
                      `stop_time_list`
      decisions_list: a list of decisions made in all trials
  """
  accuracy_list = []
  decisions_list = []
  for stop_time in stop_time_list:
    decision_list = []
    ########################################################################
    # Insert your code here to:
    #      * Run `num_sample` repeated simulations, collect decision into
    #        `decision_list`
    #      * Calculate average decision accuracy as `accuracy`
    #      * Hint: use the function you wrote in the last exercise
    raise NotImplementedError("`simulate_accuracy_vs_stoptime` is incomplete")
    ########################################################################
    for i in range(num_sample):
      _, decision, _= ...
      decision_list.append(decision)

    # Calculate accuracy given the true decision is 1
    accuracy = ...
    accuracy_list.append(accuracy)
    decisions_list.append(decision_list)

  return accuracy_list, decisions_list

np.random.seed(100)
################################################################################
# Un-comment the following code after completing this exercise
################################################################################
# simulate_and_plot_accuracy_vs_stoptime(sigma, stop_time_list, num_sample)
```

[*Click for solution*](https://github.com/NeuromatchAcademy/course-content/tree/master//tutorials/W2D3_DecisionMaking/solutions/W2D3_Tutorial1_Solution_ce09121d.py)

*Example output:*

### Interactive Demo: Accuracy versus stop-time

**Suggestions**

* Play with difference values of  noise level `sigma` and observe how that affects the curve. What does that mean?

```python
#@title

#@markdown Make sure you execute this cell to enable the widget!
np.random.seed(100)
num_sample = 100
stop_time_list = np.arange(1, 150, 10)

@widgets.interact
def plot(sigma=(0.05, 10.0, 0.05)):
  simulate_and_plot_accuracy_vs_stoptime(sigma, stop_time_list, num_sample)
```

---
# Section 3: DDM with fixed thresholds

### Exercise 3: Simulating the DDM with fixed thresholds

In this exercise, we will use thresholding as our stopping rule and observe the behavior of the DDM.

With thresholding stopping rule, we define a desired error rate and will continue making measurements until that error rate is reached. Experimental evidence suggested that evidence accumulation and thresholding stopping strategy happens at neuronal level (see [this article](https://www.annualreviews.org/doi/full/10.1146/annurev.neuro.29.051605.113038) for further reading).

* Complete the function `threshold_from_errorrate` to calculate the evidence threshold from desired error rate $\alpha$ as described in the formulas below. The evidence thresholds $th_L$ and $th_R$ for $p_L$ and $p_R$ are opposite of each other as shown below, so you can just return the absolute value.
$$
\begin{align}
 th_{L} &= \log \frac{\alpha}{1-\alpha} &= -th_{R} \\
 th_{R} &= \log \frac{1-\alpha}{\alpha} &= -th{_L}\\
 \end{align}
 $$

* Complete the function `simulate_SPRT_threshold` to simulate an SPRT with thresholding stopping rule given noise level and desired threshold

* Run repeated simulations for a given noise level and a desired error rate visualize the DDM traces using our provided code

```python
def simulate_SPRT_threshold(sigma, threshold , true_dist=1):
  """Simulate a Sequential Probability Ratio Test with thresholding stopping
  rule. Two observation models are 1D Gaussian distributions N(1,sigma^2) and
  N(-1,sigma^2).

  Args:
    sigma (float): Standard deviation
    threshold (float): Desired log likelihood ratio threshold to achieve
                        before making decision

  Returns:
    evidence_history (numpy vector): the history of cumulated evidence given
                                      generated data
    decision (int): 1 for pR, 0 for pL
    data (numpy vector): the generated sequences of data in this trial
  """
  muL = -1.0
  muR = 1.0

  pL = stats.norm(muL, sigma)
  pR = stats.norm(muR, sigma)

  has_enough_data = False

  data_history = []
  evidence_history = []
  current_evidence = 0.0

  # Keep sampling data until threshold is crossed
  while not has_enough_data:
    if true_dist == 1:
      data = pR.rvs()
    else:
      data = pL.rvs()

    ########################################################################
    # Insert your code here to:
    #      * Calculate the log-likelihood ratio for the new sample
    #      * Update the accumulated evidence
    raise NotImplementedError("`simulate_SPRT_threshold` is incomplete")
    ########################################################################
    # individual log likelihood ratios
    ll_ratio = log_likelihood_ratio(...)
    # cumulated evidence for this chunk
    evidence_history.append(...)
    # update the collection of all data
    data_history.append(data)
    current_evidence = evidence_history[-1]

    # check if we've got enough data
    if abs(current_evidence) > threshold:
      has_enough_data = True

  data_history = np.array(data_history)
  evidence_history = np.array(evidence_history)

  # Make decision
  if evidence_history[-1] > 0:
    decision = 1
  elif evidence_history[-1] < 0:
    decision = 0
  else:
    decision = np.random.randint(2)

  return evidence_history, decision, data_history

np.random.seed(100)
sigma = 2.8
num_sample = 10
log10_alpha = -6.5 # log10(alpha)
alpha = np.power(10.0, log10_alpha)

################################################################################
# Un-comment the following code after completing this exercise
################################################################################
# simulate_and_plot_SPRT_fixedthreshold(sigma, num_sample, alpha)
```

[*Click for solution*](https://github.com/NeuromatchAcademy/course-content/tree/master//tutorials/W2D3_DecisionMaking/solutions/W2D3_Tutorial1_Solution_aed14feb.py)

*Example output:*

### Interactive Demo: DDM with fixed threshold

**Suggestion**

* Play with difference values of `alpha` and `sigma` and observe how that affects the dynamics of Drift-Diffusion Model.

```python
#@title

#@markdown Make sure you execute this cell to enable the widget!
np.random.seed(100)
num_sample = 10

@widgets.interact
def plot(sigma=(0.05, 10.0, 0.05), log10_alpha=(-8, -1, .1)):
  alpha = np.power(10.0, log10_alpha)
  simulate_and_plot_SPRT_fixedthreshold(sigma, num_sample, alpha, verbose=False)
```

---

# Section 4: Accuracy vs. Threshold

### Exercise 4: Speed/Accuracy Tradeoff Revisited

The faster you make a decision, the lower your accuracy often is. This phenomenon is known as the **speed/accuracy tradeoff**. Humans can make this tradeoff in a wide range of situations, and many animal species, including ants, bees, rodents, and monkeys also show similar effects.

To illustrate the speed/accuracy tradeoff under thresholding stopping rule, let's run some simulations under different thresholds and look at how average decision "speed" (1/length) changes with average decision accuracy. We use speed rather than accuracy because in real experiments, subjects can be incentivized to respond faster or slower; it's much harder to precisely control their decision time or error threshold.

* Complete the function `simulate_accuracy_vs_threshold` to simulate and compute average accuracies vs. average decision lengths for a list of error thresholds. You will need to supply code to calculate average decision 'speed' from the lengths of trials. You should also calculate the overall accuracy across these trials.

* We've set up a list of error thresholds. Run repeated simulations and collect average accuracy with average length for each error rate in this list, and use our provided code to visualize the speed/accuracy tradeoff. You should see a positive correlation between length and accuracy.

```python
def simulate_accuracy_vs_threshold(sigma, threshold_list, num_sample):
  """Calculate the average decision accuracy vs. average decision length by
  running repeated SPRT simulations with thresholding stopping rule for each
  threshold.

  Args:
      sigma (float): standard deviation for observation model
      threshold_list (list-like object): a list of evidence thresholds to run
                                          over
      num_sample (int): number of simulations to run per stopping time

  Returns:
      accuracy_list: a list of average accuracies corresponding to input
                      `threshold_list`
      decision_length_list: a list of average decision lengths
  """
  decision_speed_list = []
  accuracy_list = []
  for threshold in threshold_list:
    decision_time_list = []
    decision_list = []
    for i in range(num_sample):
      # run simulation and get decision of current simulation
      _, decision, data = simulate_SPRT_threshold(sigma, threshold)
      decision_time = len(data)
      decision_list.append(decision)
      decision_time_list.append(decision_time)

    ########################################################################
    # Insert your code here to:
    #      * Calculate mean decision speed given a list of decision times
    #      * Hint: Think about speed as being inversely proportional
    #        to decision_length. If it takes 10 seconds to make one decision,
    #        our "decision speed" is 0.1 decisions per second.
    #      * Calculate the decision accuracy
    raise NotImplementedError("`simulate_accuracy_vs_threshold` is incomplete")
    ########################################################################
    # Calculate and store average decision speed and accuracy
    decision_speed = ...
    decision_accuracy = ...
    decision_speed_list.append(decision_speed)
    accuracy_list.append(decision_accuracy)

  return accuracy_list, decision_speed_list

################################################################################
# Un-comment the following code block after completing this exercise
################################################################################
# np.random.seed(100)
# sigma = 3.75
# num_sample = 200
# alpha_list = np.logspace(-2, -0.1, 8)
# threshold_list = threshold_from_errorrate(alpha_list)
# simulate_and_plot_accuracy_vs_threshold(sigma, threshold_list, num_sample)
```

[*Click for solution*](https://github.com/NeuromatchAcademy/course-content/tree/master//tutorials/W2D3_DecisionMaking/solutions/W2D3_Tutorial1_Solution_9e816052.py)

*Example output:*

### Interactive Demo: Speed/Accuracy with a threshold rule

**Suggestions**

* Play with difference values of  noise level `sigma` and observe how that affects the speed/accuracy tradeoff.

```python
#@title

#@markdown Make sure you execute this cell to enable the widget!
np.random.seed(100)
num_sample = 100
alpha_list = np.logspace(-2, -0.1, 8)
threshold_list = threshold_from_errorrate(alpha_list)

@widgets.interact
def plot(sigma=(0.05, 10.0, 0.05)):
  alpha = np.power(10.0, log10_alpha)
  simulate_and_plot_accuracy_vs_threshold(sigma, threshold_list, num_sample)
```

---
# Summary

Good job! By simulating Drift Diffusion Models to perform decision making, you have learnt how to

1. Calculate individual sample evidence as the log likelihood ratio of two candidate models, accumulate evidence from new data points, and make decision based on current evidence in `Exercise 1`
2. Run repeated simulations to get an estimate of decision accuraries in `Exercise 2`
3. Implement the thresholding stopping rule where we can control our error rate by taking adequate amounts of data, and calculate the evidence threshold from desired error rate in `Exercise 3`
4. Explore and gain intuition about the speed/accuracy tradeoff for perceptual decision making in `Exercise 4`


```python
%matplotlib inline
```

#  Doing Math with Python

 Amit Saha

May 30, PyCon US 2016

Portland, Oregon

## About me

- Software Engineer at [Freelancer.com](https://www.freelancer.com) HQ in Sydney, Australia

- Author of "Doing Math with Python" (No Starch Press, 2015)

- Writes for Linux Voice, Linux Journal, and others.

- [Blog](http://echorand.me), [GitHub](http://github.com/amitsaha)

#### Contact

- [@echorand](http://twitter.com/echorand)

- [Email](mailto:amitsaha.in@gmail.com)

### Book: Doing Math with Python

*Use PYCONMATH code to get 30% off "Doing Math with Python" from [No Starch Press](https://www.nostarch.com/doingmathwithpython)*

(Valid from  May 26th - June 8th)

Book Signing - May 31st - 2.00 PM - No Starch Press booth

### What?

*Python can lead to a more enriching learning and teaching experience in the classroom*

How? *Next slides*

###  Tools (or, Giant shoulders we will stand on)

*Python 3*, *SymPy*, *matplotlib*

*Individual logos are copyright of the respective projects. [Source](http://www.orphancaremovement.org/standing-on-the-shoulder-of-giants/) of the "giant shoulders" image.

### Python - a scientific calculator

Whose calculator looks like this?

```python
>>> (131 + 21.5 + 100.2 + 88.7 + 99.5 + 100.5 + 200.5)/4
185.475
```

*Python 3 is my favorite calculator (not Python 2 because 1/2 = 0)*

Beyond basic operations:

- `fabs()`, `abs()`, `sin()`, `cos()`, `gcd()`, `log()` and more (See [math](https://docs.python.org/3/library/math.html))

- Descriptive statistics (See [statistics](https://docs.python.org/3/library/statistics.html#module-statistics))

### Python - a scientific calculator

- Develop your own functions: unit conversion, finding correlation, .., anything really

- Use PYTHONSTARTUP to extend the battery of readily available mathematical functions

```python
$ PYTHONSTARTUP=~/work/dmwp/pycon-us-2016/startup_math.py idle3 -s
```

### Unit conversion functions

```python

>>> unit_conversion()
1. Kilometers to Miles
2. Miles to Kilometers
3. Kilograms to Pounds
4. Pounds to Kilograms
5. Celsius to Fahrenheit
6. Fahrenheit to Celsius
Which conversion would you like to do? 6
Enter temperature in fahrenheit: 98
Temperature in celsius: 36.66666666666667
>>>

```

### Finding linear correlation

```python
>>>
>>> x = [1, 2, 3, 4]
>>> y = [2, 4, 6.1, 7.9]
>>> find_corr_x_y(x, y)
0.9995411791453812

```

### Python - a really fancy calculator

SymPy - a pure Python symbolic math library

*from sympy import awesomeness*  - don't try that :)

```python
# Create graphs from algebraic expressions

from sympy import Symbol, plot
x = Symbol('x')
p = plot(2*x**2 + 2*x + 2)

```

```python
# Solve equations

from sympy import solve, Symbol
x = Symbol('x')
solve(2*x + 1)

```

    [-1/2]

```python
# Limits

from sympy import Symbol, Limit, sin
x = Symbol('x')
Limit(sin(x)/x, x, 0).doit()

```

    1

```python
# Derivative

from sympy import Symbol, Derivative, sin, init_printing
x = Symbol('x')
init_printing()
Derivative(sin(x)**(2*x+1), x).doit()
```

```python
# Indefinite integral

from sympy import Symbol, Integral, sqrt, sin, init_printing
x = Symbol('x')
init_printing()
Integral(sqrt(x)).doit()
```

```python
# Definite integral

from sympy import Symbol, Integral, sqrt
x = Symbol('x')
Integral(sqrt(x), (x, 0, 2)).doit()
```

Can we do more than write smart calculators?

### Python - Making other subjects more lively

- matplotlib

- basemap

- Interactive Jupyter Notebooks

#### Bringing Science to life

*Animation of a Projectile motion* [(Python Source)](https://github.com/doingmathwithpython/pycon-us-2016/blob/master/py-files/projectile_animation.py)

```python
from IPython.display import YouTubeVideo
YouTubeVideo("8uWRVh58KdQ")
```

#### Exploring Fractals in Nature

*Interactively drawing a Barnsley Fern* [(Notebook)](https://github.com/doingmathwithpython/pycon-us-2016/blob/master/notebooks/Interactive%20Barnsley%20Fern.ipynb)

#### The world is your graph paper

*Showing places on a digital map* [(Notebook)](https://github.com/doingmathwithpython/pycon-us-2016/blob/master/notebooks/Maps%20using%20Basemap%20-%20demo.ipynb)

### Great base for the future

*Statistics and Graphing data* -> *Data Science*

*Differential Calculus* -> *Machine learning*


+ This notebook is part of lecture 30 *Linear transformations and their matrices* in the OCW MIT course 18.06 by Prof Gilbert Strang [1]
+ Created by me, Dr Juan H Klopper
    + Head of Acute Care Surgery
    + Groote Schuur Hospital
    + University Cape Town
    + Email me with your thoughts, comments, suggestions and corrections

Linear Algebra OCW MIT18.06 IPython notebook [2] study notes by Dr Juan H Klopper is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.

+ [1] OCW MIT 18.06
+ [2] Fernando Pérez, Brian E. Granger, IPython: A System for Interactive Scientific Computing, Computing in Science and Engineering, vol. 9, no. 3, pp. 21-29, May/June 2007, doi:10.1109/MCSE.2007.53. URL: http://ipython.org

```python
from IPython.core.display import HTML, Image
css_file = 'style.css'
HTML(open(css_file, 'r').read())
```

<link href='http://fonts.googleapis.com/css?family=Alegreya+Sans:100,300,400,500,700,800,900,100italic,300italic,400italic,500italic,700italic,800italic,900italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Arvo:400,700,400italic' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=PT+Mono' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Shadows+Into+Light' rel='stylesheet' type='text/css'>
<link href='http://fonts.googleapis.com/css?family=Philosopher:400,700,400italic,700italic' rel='stylesheet' type='text/css'>

```python
from sympy import init_printing, Matrix, symbols, sqrt, Rational
from warnings import filterwarnings
```

```python
init_printing(use_latex = 'mathjax')
filterwarnings('ignore')
```

# Linear transformations

## Mappings / transformations / projections

+ We'll begin the chapter with a familiar example: Projections

+ Let's look at a mapping / transformation / projection
    + T: ℝ2 → T: ℝ2
    + No axes are required for this mapping
+ Another example using matrices
    $$ T\left({v}\right)={A}{v} $$

+ For these to be a linear mapping, the following must apply
$$  {T}\left({v}+{w}\right)={T}\left({v}\right)+{T}\left({w}\right) \\ {T}\left({cv}\right)={c}{T}\left({v}\right) $$

+ Example that are not linear transformations
    + Shifting the axes
    + The transformation that turns a vector into its length
    + Transformation involving power or transcendental function

### Example

+ Let's create and example which accomplishes the following
    + T: ℝ3 → ℝ2

+ Without axes we could look at something like this
    $$ {f}\left({x},{y},{z}\right)=\left({x}+{y}+{z},\quad{3x}-{2y}+{z}\right) $$

+ With axes we naturally turn to matrices
    + We require **v** in 3-space
    $$ \begin{bmatrix}{v}_{1}\\{v}_{2}\\{v}_{3}\end{bmatrix} $$
    + To have a resultant vector in 2-space we require a matrix A of size 2×3
    $$ T\left( v \right) =Av\\ { \begin{bmatrix} { a }_{ 11 } & { a }_{ 12 } & { a }_{ 13 } \\ { a }_{ 21 } & { a }_{ 22 } & { a }_{ 23 } \end{bmatrix} }_{ 2\times 3 }{ \begin{bmatrix} { v }_{ 1 } \\ { v }_{ 2 } \\ { v }_{ 3 } \end{bmatrix} }_{ 3\times 1 }={ \begin{bmatrix} { a }_{ 11 }{ v }_{ 1 }+{ a }_{ 12 }{ v }_{ 2 }+{ a }_{ 13 }{ v }_{ 3 } \\ { a }_{ 21 }{ v }_{ 1 }+{ a }_{ 22 }{ v }_{ 2 }+{ a }_{ 23 }{ v }_{ 3 } \end{bmatrix} }_{ 2\times 1 } $$

+ Notice if we know what a linear transformation does to a single vector, we know what it does to constant multiples of that vector (from the property T(c**v**)=cT(**v**))
    + In 2- or 3-space this would represent a line
+ If we knew what T does to two (linearly independent or basis) vectors, we know what it does to the subspace created by those two vectors (the whole plane in ℝ2 or a plane in ℝ3), i.e. linear combinations of these two vectors

## Coordinates / basis

+ Coordinates originate from a basis
$$ {v}={c}_{1}{v}_{1}+{c}_{2}{v}_{2}+\dots+{c}_{n}{v}_{n} $$

+ This need not be the standard basis, though
$$ v=\begin{bmatrix} 2 \\ 3 \\ -1 \end{bmatrix}=2\begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}+3\begin{bmatrix} 0 \\ 1 \\ 0 \end{bmatrix}-\begin{bmatrix} 0 \\ 0 \\ 1 \end{bmatrix} $$

+ Let's look then at the following transformation: T: ℝn→ℝm
+ We need a basis for the input (*v*1,*v*2,...,*v*n in ℝn) and the output (*w*1,*w*2,...,*w*m in ℝm)

+ So what is the rule for finding A that will transform **v** into **w**?
+ Rule finding A given basis for **v** and **w**
    + The first column of A: apply T(*v*1)=*a*11*w*1+*a*21*w*2+...+*a*m1*w*m, with the first column entries of A being *a*11, *a*21, ..., *a*m1
    + The second column of A: apply T(*v*2)=*a*12*w*1+*a*22*w*2+...+*a*m2*w*m, with the second column entries of A being *a*12, *a*22, ..., *a*m2
    + ... and so on until *v*n

### Example

+ Let's look at polynomials with the following transformation
$$ T=\frac{d}{dx} $$

+ The input might be
$$ {c}_{1}+{c}_{2}{x}+{c}_{3}{x}^{2} $$
+ The output will be
$$ {c}_{2}+2{c}_{3}{x} $$
+ This gives use the following basis for each
$$ {1},{x},{x}^{2} \\ {1},{x} $$

+ We need the following
$$ {A}\begin{bmatrix}{c}_{1}\\{c}_{2}\\{c}_{3}\end{bmatrix}=\begin{bmatrix}{c}_{2}\\2{c}_{2}\end{bmatrix} $$

* Following the rule above we will have to have the following
$$ {A}=\begin{bmatrix}0&1&0\\0&0&2\end{bmatrix} $$

## Example problems

### Example problem 1

+ Consider the 2×2 matrix A and let T(A)=AT
    + Why is T linear and what is T-1
    + Write the matrix of T in:
    $$ { v }_{ 1 }=\begin{bmatrix} 1 & 0 \\ 0 & 0 \end{bmatrix},{ v }_{ 2 }=\begin{bmatrix} 0 & 1 \\ 0 & 0 \end{bmatrix},{ v }_{ 3 }=\begin{bmatrix} 0 & 0 \\ 1 & 0 \end{bmatrix},{ v }_{ 4 }=\begin{bmatrix} 0 & 0 \\ 0 & 1 \end{bmatrix}\\ { w }_{ 1 }=\begin{bmatrix} 1 & 0 \\ 1 & 0 \end{bmatrix},{ w }_{ 2 }=\begin{bmatrix} 0 & 0 \\ 0 & 1 \end{bmatrix},{ w }_{ 3 }=\begin{bmatrix} 0 & 1 \\ 1 & 0 \end{bmatrix},{ w }_{ 4 }=\begin{bmatrix} 0 & 1 \\ -1 & 0 \end{bmatrix} $$
    + What are the eigenvalues and eigenvectors of T?

#### Solution

+ From the properties of linear transformation we have the following
$$ {T}\left({A+B}\right)={\left({A+B}\right)}^{T}={A}^{T}+{B}^{T}={T}\left({A}\right)+{T}\left({B}\right) \\ {T}\left({cA}\right)={\left({cA}\right)}^{T}={c}{T}\left({A}\right) $$
    + A transpose turns a row into a columns and *vice versa*, from which we infer the following
    $$ {T}^{2}={I} \\ \therefore\quad{T}^{-1}={T} $$

+ For the next question we will have to see what T does to each basis matrix
$$ {T}\left({v}_{1}\right)={v}_{1} \\ {T}\left({v}_{2}\right)={v}_{3} \\ {T}\left({v}_{3}\right)={v}_{2} \\ {T}\left({v}_{4}\right)={v}_{4} $$
    + From this we have to form a matrix
        + Think of the columns each being T*v*1, T*v*2, T*v*3, and T*v*4
        + We see that transforming *v*1 takes 1 *v*1, and none of the rest
        $$ {M}_{T}=\begin{bmatrix}1&0&0&0\\0&0&1&0\\0&1&0&0\\0&0&0&1\end{bmatrix} $$

+ For the *w*i we will have the following matrix
$$ {M}_{T}=\begin{bmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\0&0&0&-1\end{bmatrix} $$

+ For the last question we will have the following
$$ {T}\left({v}\right)={\lambda}{v} $$

```python

```