# Notebook from dinatraykova/nrpytutorial
Path: Tutorial-ADM_and_T4UUmunu_Initial_Data-TOV.ipynb

# Setting up Polytropic [TOV](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) Initial Data

## Authors: Phil Chang, Zachariah B. Etienne

## This module sets up initial data for a [TOV](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) star in *spherical, isotropic coordinates*

**This module has been validated to exhibit convergence to zero of the Hamiltonian constraint violation at the expected order to the exact solution (see [start-to-finish TOV module](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb) for full test). Note that convergence at the surface of the star is lower order due to the sharp drop to zero in $T^{\mu\nu}$.**

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

$$\label{top}$$

## Introduction

[Tolman-Oppenheimer-Volkoff (TOV)](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) initial data are constructed from the solution of a system of [ODEs](https://en.wikipedia.org/wiki/Ordinary_differential_equation). The solution is spherically symmetric.

This module first constructs the system of ODEs and uses [NumPy](http://www.numpy.org/) to numerically solve the system and output the result to a file.

Then, a small C code is written to read in the file, interpolate the data to any desired radius, and construct the ADM spacetime quantities and corresponding $T^{\mu\nu}$.

## Table of Contents

1. [Step 1](#step1): Overview of the [Tolman-Oppenheimer-Volkoff (TOV)](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) solution to Einstein's equations
1. [Step 2](#step2): Python code for solving the TOV equations using NumPy's ODE solver, outputting the data to a file
    1. [Step 2.1](#step2p1): Validation of the TOV output file against a trusted version
1. [Step 3](#step3): C code for reading in data file and interpolating data to any desired radius
    1. [Step 3.1](#step3p1): Reading in the data file, generated in [Step 2](#step2).
    1. [Step 3.2](#step3p2): Interpolating the data to any desired radius
1. [Step 4](#step4): Construct ADM spacetime quantities and $T^{\mu\nu}$ from TOV solution
    1. [Step 4.1](#step4p1): Load NRPy+ modules and set basic parameters
    1. [Step 4.2](#step4p2): Constructing ADM spacetime quantities from the TOV data
    1. [Step 4.3](#step4p3): Constructing $T^{\mu\nu}$ from the TOV data
1. [Step 5](#step5): NRPy+ code validation: Confirm that ADM spacetime quantities and $T^{\mu\nu}$_____no_output_____

## Step 1: Overview of the [Tolman-Oppenheimer-Volkoff (TOV)](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) solution to Einstein's equations \[Back to [top](#top)\]
$$\label{step1}$$

The [TOV line element](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) in terms of the *Schwarzschild coordinate* $r$ is written (in the $-+++$ form):
$$
ds^2 = - c^2 e^\nu dt^2 + \left(1 - \frac{2Gm}{rc^2}\right)^{-1} dr^2 + r^2 d\Omega^2,
$$
where $m(r)$ is the mass-energy enclosed at a given $r$, and is equal to the total star's mass outside the stellar radius $r=R$.

In terms of the *isotropic coordinate* $\bar{r}$ with $G=c=1$ (i.e., the coordinate system and units we'd prefer to use), the ($-+++$ form) line element is written:
$$
ds^2 = - e^{\nu} dt^2 + e^{4\phi} \left(d\bar{r}^2 + \bar{r}^2 d\Omega^2\right),
$$
where $\phi$ here is the *conformal factor*.

Setting components of the above line element equal to one another, we get (in $G=c=1$ units):

\begin{align}
r^2 &= e^{4\phi} \bar{r}^2 \implies e^{4\phi} = \frac{r^2}{\bar{r}^2} \\
\left(1 - \frac{2m}{r}\right)^{-1} dr^2 &= e^{4\phi} d\bar{r}^2 \\
\implies \frac{d\bar{r}(r)}{dr} &= \left(1 - \frac{2m}{r} \right)^{-1/2} \frac{\bar{r}(r)}{r}.
\end{align}

The TOV equations provide radial ODEs for the pressure and $\nu$ (from [the Wikipedia article on the TOV solution](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation)):

\begin{align}
\frac{dP}{dr} &= - \frac{1}{r} \left( \frac{\rho + P}{2} \right) \left(\frac{2 m}{r} + 8 \pi r^2 P\right) \left(1 - \frac{2 m}{r}\right)^{-1} \\
\frac{d \nu}{d r} &= \frac{1}{r}\left(1 - \frac{2 m}{r}\right)^{-1} \left(\frac{2 m}{r} + 8 \pi r^2 P\right) \\
\end{align}

Assuming a polytropic equation of state, which relates the pressure $P$ to the baryonic rest-mass density $\rho_B$,

$$
P(\rho_B) = K \rho_B^\Gamma,
$$
the specific internal energy will be given by
$$
\epsilon = \frac{P}{\rho_B (\Gamma - 1)},
$$

so the total mass-energy density $\rho$ is given by
$$
\rho = \rho_B (1 + \epsilon).
$$

Given this, the mass-energy $m(r)$ density is the solution to the ODE:
$$
\frac{m(r)}{dr} = 4\pi r^2 \rho(r)
$$

Thus the full set of ODEs that need to be solved is given by

\begin{align}
\frac{dP}{dr} &= - \frac{1}{r} \left( \frac{\rho + P}{2} \right) \left(\frac{2 m}{r} + 8 \pi r^2 P\right) \left(1 - \frac{2 m}{r}\right)^{-1} \\
\frac{d \nu}{d r} &= \frac{1}{r}\left(1 - \frac{2 m}{r}\right)^{-1} \left(\frac{2 m}{r} + 8 \pi r^2 P\right) \\
\frac{m(r)}{dr} &= 4\pi r^2 \rho(r) \\
\frac{d\bar{r}(r)}{dr} &= \left(1 - \frac{2m}{r} \right)^{-1/2} \frac{\bar{r}(r)}{r}.
\end{align}_____no_output_____

## Step 2: Python code for solving the TOV equations using NumPy's ODE solver, outputting the data to a file \[Back to [top](#top)\]

$$\label{step2}$$

The following code solves the above equations, and was largely written by **Phil Chang**._____no_output_____

## TOV SOLVER FOR SIMPLE POLYTROPES.
## Authors: Phil Chang, Zachariah B. Etienne

# Full documentation for this module may be found in the NRPy+ tutorial Jupyter notebook:
#  Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb

# Inputs:
# * Output data file name
# * rho_baryon_central, the central density of the TOV star.
# * n, the polytropic equation of state index. n=1 models cold, degenerate neutron star matter.
# * K_Polytrope, the polytropic constant.
# * Verbose output toggle (default = True)

# Output: An initial data file (default file name = "outputTOVpolytrope.txt") that well
#         samples the (spherically symmetric) solution both inside and outside the star.
#         It is up to the initial data module to perform the 1D interpolation to generate
#         the solution at arbitrary radius. The file has the following columns:
# Column 1: Schwarzschild radius
# Column 2: rho(r), *total* mass-energy density (as opposed to baryonic rest-mass density)
# Column 3: P(r), Pressure
# Column 4: m(r), mass enclosed
# Column 5: e^{nu(r)}, g_{tt}(r)
# Column 6: e^{4 phi(r)}, conformal factor g_{rr}(r)
# Column 7: rbar(r), Isotropic radius

# rbar refers to the isotropic radius, and
# R_Schw refers to the Schwarzschild radius

import numpy as np
import scipy.integrate as si
import math

n = 1.
rho_baryon_central = 0.129285

P0 = 1. # ZACH NOTES: CHANGED FROM 100.
gamma = 1. + 1./n
gam1 = gamma - 1.

def TOV_pressure( rho_baryon) :
    return P0*rho_baryon**gamma

def TOV_rhs(r_Schw, y) :
# In \tilde units
#
    P    = y[0]
    m    = y[1]
    nu   = y[2]
    rbar = y[3]

    dPdrSchw    = 0.
    drbardrSchw = 0.

    rho_baryon = (P/P0)**(1./gamma)
    rho = rho_baryon + P/gam1 # rho is the *total* mass-energy density!
    if( r_Schw < 1e-4 or m <= 0.):
        m = 4*math.pi/3. * rho*r_Schw**3
        dPdrSchw = -(rho + P)*(4.*math.pi/3.*r_Schw*rho + 4.*math.pi*r_Schw*P)/(1.-8.*math.pi*rho*r_Schw*r_Schw)
        drbardrSchw = 1./(1. - 8.*math.pi*rho*r_Schw*r_Schw)**0.5
    else:
        dPdrSchw = -(rho + P)*(m + 4.*math.pi*r_Schw**3*P)/(r_Schw*r_Schw*(1.-2.*m/r_Schw))
        drbardrSchw = 1./(1. - 2.*m/r_Schw)**0.5*rbar/r_Schw

    dmdrSchw  =  4.*math.pi*r_Schw*r_Schw*rho
    dnudrSchw = -2./(P + rho)*dPdrSchw
    return [dPdrSchw, dmdrSchw, dnudrSchw, drbardrSchw]

def integrateStar( P, dumpData = False):
    integrator = si.ode(TOV_rhs).set_integrator('dop853')
    y0 = [P, 0., 0., 0.]
    integrator.set_initial_value(y0,0.)
    dr_Schw = 1e-5
    P = y0[0]

    PArr      = []
    r_SchwArr = []
    mArr      = []
    nuArr     = []
    rbarArr   = []

    r_Schw = 0.

    while integrator.successful() and P > 1e-9*y0[0] :
        P, m, nu, rbar = integrator.integrate(r_Schw + dr_Schw)
        r_Schw = integrator.t

        dPdrSchw, dmdrSchw, dnudrSchw, drbardrSchw = TOV_rhs( r_Schw+dr_Schw, [P,m,nu,rbar])
        dr_Schw = 0.1*min(abs(P/dPdrSchw), abs(m/dmdrSchw))
        dr_Schw = min(dr_Schw, 1e-2)
        PArr.append(P)
        r_SchwArr.append(r_Schw)
        mArr.append(m)
        nuArr.append(nu)
        rbarArr.append(rbar)

    M = mArr[-1]
    R_Schw = r_SchwArr[-1]

    # Apply integration constant to ensure rbar is continuous across TOV surface
    for ii in range(len(rbarArr)):
        rbarArr[ii] *= 0.5*(np.sqrt(R_Schw*(R_Schw - 2.0*M)) + R_Schw - M) / rbarArr[-1]

    nuArr_np = np.array(nuArr)
    # Rescale solution to nu so that it satisfies BC: exp(nu(R))=exp(nutilde-nu(r=R)) * (1 - 2m(R)/R)
    #   Thus, nu(R) = (nutilde - nu(r=R)) + log(1 - 2*m(R)/R)
    nuArr_np = nuArr_np - nuArr_np[-1] + math.log(1.-2.*mArr[-1]/r_SchwArr[-1])

    r_SchwArrExtend_np = 10.**(np.arange(0.01,5.0,0.01))*r_SchwArr[-1]

    r_SchwArr.extend(r_SchwArrExtend_np)
    mArr.extend(r_SchwArrExtend_np*0. + M)
    PArr.extend(r_SchwArrExtend_np*0.)
    exp2phiArr_np = np.append( np.exp(nuArr_np), 1. - 2.*M/r_SchwArrExtend_np)
    nuArr.extend(np.log(1. - 2.*M/r_SchwArrExtend_np))
    rbarArr.extend( 0.5*(np.sqrt(r_SchwArrExtend_np**2 - 2.*M*r_SchwArrExtend_np) + r_SchwArrExtend_np - M) )

    # Appending to a Python array does what one would reasonably expect.
    #   Appending to a numpy array allocates space for a new array with size+1,
    #   then copies the data over... over and over... super inefficient.
    r_SchwArr_np          = np.array(r_SchwArr)
    PArr_np               = np.array(PArr)
    rho_baryonArr_np      = (PArr_np/P0)**(1./gamma)
    mArr_np               = np.array(mArr)
    rbarArr_np            = np.array(rbarArr)
    confFactor_exp4phi_np = (r_SchwArr_np/rbarArr_np)**2

    # Compute the *total* mass-energy density (as opposed to the *baryonic* mass density)
    rhoArr_np = []
    for i in range(len(rho_baryonArr_np)):
        rhoArr_np.append(rho_baryonArr_np[i] + PArr_np[i]/(gamma - 1.))

    print(len(r_SchwArr_np),len(rhoArr_np),len(PArr_np),len(mArr_np),len(exp2phiArr_np))
    np.savetxt("outputTOVpolytrope.txt", zip(r_SchwArr_np,rhoArr_np,PArr_np,mArr_np,exp2phiArr_np,confFactor_exp4phi_np,rbarArr_np),
               fmt="%.15e")

    return R_Schw, M

R_Schw_TOV,M_TOV = integrateStar(TOV_pressure(rho_baryon_central), True)
print("Just generated a TOV star with R_Schw = "+str(R_Schw_TOV)+" , M = "+str(M_TOV)+" , M/R_Schw = "+str(M_TOV/R_Schw_TOV)+" .")(1051, 1051, 1051, 1051, 1051)
Just generated a TOV star with R_Schw = 0.956568142523 , M = 0.14050303285288188 , M/R_Schw = 0.1468824086931645 .

## Step 2.1: Validation of the TOV output file against a trusted version
$$\label{step2p1}$$
\[Back to [top](#top)\]

Here, as a code validation check, we verify agreement in the SymPy expressions for these TOV initial data between

1. this tutorial and
2. the NRPy+ [TOV.TOV_Solver](../edit/TOV/TOV_Solver.py) module._____no_output_____

import filecmp

import TOV.TOV_Solver as TOV
TOV.TOV_Solver("outputTOVpolytrope-validation.txt",rho_baryon_central=0.129285, n_Polytrope=1.0, K_Polytrope=1.0,
               verbose = False)

if filecmp.cmp('outputTOVpolytrope.txt',
               'outputTOVpolytrope-validation.txt') == False:
    print("ERROR: TOV initial data test FAILED!")
    exit(1)
else:
    print("TOV initial data test PASSED.")TOV initial data test PASSED.

## Step 3: C code for reading TOV star initial data and interpolating data to numerical grids \[Back to [top](#top)\]

$$\label{step3}$$

### Step 3.1: C code for reading TOV star initial data file \[Back to [top](#top)\]
$$\label{step3p1}$$
_____no_output_____

%%writefile TOV/read_datafile__set_arrays.h

int read_datafile__set_arrays(FILE *in1Dpolytrope, REAL *r_Schw_arr,REAL *rho_arr,REAL *P_arr,REAL *M_arr,REAL *expnu_arr,REAL *exp4phi_arr,REAL *rbar_arr) {
  char * line = NULL;

  size_t len = 0;
  ssize_t read;

  int which_line = 0;
  while ((read = getline(&line, &len, in1Dpolytrope)) != -1) {
    // Define the line delimiters (i.e., the stuff that goes between the data on a given
    //     line of data.  Here, we define both spaces " " and tabs "\t" as data delimiters.
    const char delimiters[] = " \t";

    //Now we define "token", a pointer to the first column of data
    char *token;

    //Each successive time we call strtok(NULL,blah), we read in a new column of data from
    //     the originally defined character array, as pointed to by token.

    token=strtok(line, delimiters); if(token==NULL) { printf("BADDDD\n"); return 1; }
    r_Schw_arr[which_line]  = strtod(token, NULL); token = strtok( NULL, delimiters );
    rho_arr[which_line]     = strtod(token, NULL); token = strtok( NULL, delimiters );
    P_arr[which_line]       = strtod(token, NULL); token = strtok( NULL, delimiters );
    M_arr[which_line]       = strtod(token, NULL); token = strtok( NULL, delimiters );
    expnu_arr[which_line]   = strtod(token, NULL); token = strtok( NULL, delimiters );
    exp4phi_arr[which_line] = strtod(token, NULL); token = strtok( NULL, delimiters );
    rbar_arr[which_line]    = strtod(token, NULL);

    which_line++;
  }
  free(line);
  return 0;
}Overwriting TOV/read_datafile__set_arrays.h

### Step 3.2: C code for interpolating TOV data to desired radial coordinate $\bar{r}$ \[Back to [top](#top)\]
$$\label{step3p2}$$

The TOV data file written in [Step 2](#step2) above stored $\left(r,\rho(r),P(r),M(r),e^{\nu(r),\bar{r}}\right)$, where $\rho(r)$ is the total mass-energy density (cf. $\rho_{\text{baryonic}}$) at a fixed number of radii. This C code implements [Lagrange polynomial interpolation](https://en.wikipedia.org/wiki/Polynomial_interpolation) to interpolate $\left(r,\rho(r),P(r),M(r),e^{\nu(r)}\right)$ to any given input radius $\bar{r}$._____no_output_____

%%writefile TOV/TOV_interpolate_1D.h

void TOV_interpolate_1D(REAL rrbar,const REAL Rbar,const int Rbar_idx,const int interp_stencil_size,
                        const int numlines_in_file,const REAL *r_Schw_arr,const REAL *rho_arr,const REAL *P_arr,const REAL *M_arr,const REAL *expnu_arr,const REAL *exp4phi_arr,const REAL *rbar_arr,
                        REAL *rho,REAL *P,REAL *M,REAL *expnu,REAL *exp4phi) {
  // Find interpolation index using Bisection root-finding algorithm:
  int bisection_idx_finder(const REAL rr, const int numlines_in_file, const REAL *rbar_arr) {
    int x1 = 0;
    int x2 = numlines_in_file-1;
    REAL y1 = rrbar-rbar_arr[x1];
    REAL y2 = rrbar-rbar_arr[x2];
    if(y1*y2 >= 0) {
      fprintf(stderr,"INTERPOLATION BRACKETING ERROR %e | %e %e\n",rr,y1,y2);
      exit(1);
    }
    for(int i=0;i<numlines_in_file;i++) {
      int x_midpoint = (x1+x2)/2;
      REAL y_midpoint = rrbar-rbar_arr[x_midpoint];
      if(y_midpoint*y1 < 0) {
        x2 = x_midpoint;
        y2 = y_midpoint;
      } else {
        x1 = x_midpoint;
        y1 = y_midpoint;
      }
      if( abs(x2-x1) == 1 ) {
        // If rbar_arr[x1] is closer to rrbar than rbar_arr[x2] then return x1:
        if(fabs(rrbar-rbar_arr[x1]) < fabs(rrbar-rbar_arr[x2])) return x1;
        // Otherwiser return x2:
        return x2;
      }
    }
    fprintf(stderr,"INTERPOLATION BRACKETING ERROR: DID NOT CONVERGE.\n");
    exit(1);
  }

  // For this case, we know that for all functions, f(r) = f(-r)
  if(rrbar < 0) rrbar = -rrbar;

  // First find the central interpolation stencil index:
  int idx = bisection_idx_finder(rrbar,numlines_in_file,rbar_arr);

#ifdef MAX
#undef MAX
#endif
#define MAX(A, B) ( ((A) > (B)) ? (A) : (B) )

  int idxmin = MAX(0,idx-interp_stencil_size/2-1);

#ifdef MIN
#undef MIN
#endif
#define MIN(A, B) ( ((A) < (B)) ? (A) : (B) )

  // -= Do not allow the interpolation stencil to cross the star's surface =-
  // max index is when idxmin + (interp_stencil_size-1) = Rbar_idx
  //  -> idxmin at most can be Rbar_idx - interp_stencil_size + 1
  if(rrbar < Rbar) {
    idxmin = MIN(idxmin,Rbar_idx - interp_stencil_size + 1);
  } else {
    idxmin = MAX(idxmin,Rbar_idx+1);
    idxmin = MIN(idxmin,numlines_in_file - interp_stencil_size + 1);
  }

  // Now perform Lagrange polynomial interpolation:

  // First set the interpolation coefficients:
  REAL rbar_sample[interp_stencil_size];
  for(int i=idxmin;i<idxmin+interp_stencil_size;i++) {
    rbar_sample[i-idxmin] = rbar_arr[i];
  }
  REAL l_i_of_r[interp_stencil_size];
  for(int i=0;i<interp_stencil_size;i++) {
    REAL numer = 1.0;
    REAL denom = 1.0;
    for(int j=0;j<i;j++) {
      numer *= rrbar - rbar_sample[j];
      denom *= rbar_sample[i] - rbar_sample[j];
    }
    for(int j=i+1;j<interp_stencil_size;j++) {
      numer *= rrbar - rbar_sample[j];
      denom *= rbar_sample[i] - rbar_sample[j];
    }
    l_i_of_r[i] = numer/denom;
  }

  // Then perform the interpolation:
  *rho = 0.0;
  *P = 0.0;
  *M = 0.0;
  *expnu = 0.0;
  *exp4phi = 0.0;

  REAL r_Schw = 0.0;
  for(int i=idxmin;i<idxmin+interp_stencil_size;i++) {
    r_Schw   += l_i_of_r[i-idxmin] * r_Schw_arr[i];
    *rho     += l_i_of_r[i-idxmin] * rho_arr[i];
    *P       += l_i_of_r[i-idxmin] * P_arr[i];
    *M       += l_i_of_r[i-idxmin] * M_arr[i];
    *expnu   += l_i_of_r[i-idxmin] * expnu_arr[i];
    *exp4phi += l_i_of_r[i-idxmin] * exp4phi_arr[i];
  }

  if(rrbar > Rbar) {
    *rho   = 0;
    *P     = 0;
    *M     = M_arr[Rbar_idx+1];
    *expnu = 1. - 2.*(*M) / r_Schw;
    *exp4phi = pow(r_Schw / rrbar,2.0);
  }
}Overwriting TOV/TOV_interpolate_1D.h

## Step 4: Construct ADM spacetime quantities and $T^{\mu\nu}$  from TOV solution \[Back to [top](#top)\]
$$\label{step4}$$

## Step 4.1: Load NRPy+ modules and set basic parameters \[Back to [top](#top)\]
$$\label{step4p1}$$_____no_output_____

# Step 4.1: Load NRPy+ modules and set basic parameters

import sympy as sp
import NRPy_param_funcs as par
import indexedexp as ixp
import grid as gri
from outputC import *
import reference_metric as rfm

# All gridfunctions will be written in terms of spherical coordinates (r, th, ph):
r,th,ph = sp.symbols('r th ph', real=True)

thismodule = "TOV"

DIM = 3
par.set_parval_from_str("grid::DIM",DIM)

# Input parameters read in from the TOV data file:
rbar,expnu,exp4phi,P,rho = par.Cparameters("REAL", thismodule,
                                           ["rbar","expnu","exp4phi","P", "rho"],
                                           [ 1e300,  1e300,    1e300,1e300,1e300]) # Must be read from TOV data
                                                                      # file; set to crazy values to ensure this_____no_output_____

## Step 4.2: Constructing ADM spacetime quantities from the TOV data \[Back to [top](#top)\]

$$\label{step4p2}$$

The [TOV line element](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) in *Schwarzschild coordinates* is written (in the $-+++$ form):
$$
ds^2 = - c^2 e^\nu dt^2 + \left(1 - \frac{2GM}{rc^2}\right)^{-1} dr^2 + r^2 d\Omega^2.
$$

In *isotropic coordinates* with $G=c=1$ (i.e., the coordinate system and units we'd prefer to use), the ($-+++$ form) line element is written:
$$
ds^2 = - e^{\nu} dt^2 + e^{4\phi} \left(d\bar{r}^2 + \bar{r}^2 d\Omega^2\right),
$$
where $\phi$ here is the *conformal factor*.

The ADM 3+1 line element for this diagonal metric in isotropic spherical coordinates is given by:
$$
ds^2 = (-\alpha^2 + \beta_k \beta^k) dt^2 + \gamma_{\bar{r}\bar{r}} d\bar{r}^2 + \gamma_{\theta\theta} d\theta^2+ \gamma_{\phi\phi} d\phi^2,
$$

from which we can immediately read off the ADM quantities:
\begin{align}
\gamma_{\bar{r}\bar{r}} &= e^{4\phi}\\
\gamma_{\theta\theta} &= e^{4\phi} \bar{r}^2 \\
\gamma_{\phi\phi} &= e^{4\phi} \bar{r}^2 \sin^2 \theta \\
K_{ij} &= 0 \\
\alpha &= e^{\nu(\bar{r})/2} \\
\beta^i = B^i &= 0 \\
\end{align}_____no_output_____

# Step 4.2: Construct ADM quantities:

# *** The physical spatial metric in spherical basis ***
# In isotropic coordinates,
#  gamma_{ij} = e^{4 phi} eta_{ij},
# where eta is the flat-space 3-metric in spherical coordinates
gammaSphDD = ixp.zerorank2()
gammaSphDD[0][0] = exp4phi
gammaSphDD[1][1] = exp4phi * rbar**2
gammaSphDD[2][2] = exp4phi * rbar**2*sp.sin(th)**2

# *** The extrinsic curvature in spherical basis ***
# K_{ij} = 0 for the TOV solution
KSphDD = ixp.zerorank2()

# *** The lapse and shift in spherical basis ***
# alpha = exp^{nu/2} for the TOV solution
# \beta^i = 0 for the TOV solution
alphaSph = sp.sqrt(expnu)
betaSphU = ixp.zerorank1()
BSphSphU = ixp.zerorank1()_____no_output_____

## Step 4.3: Constructing $T^{\mu\nu}$ from the TOV data \[Back to [top](#top)\]
$$\label{step4p3}$$

We will also need the stress-energy tensor $T^{\mu\nu}$. [As discussed here](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation), the stress-energy tensor is diagonal:

\begin{align}
T^t_t &= -\rho \\
T^i_j &= P \delta^i_j \\
\text{All other components of }T^\mu_\nu &= 0.
\end{align}

Since $\beta^i=0$ the inverse metric expression simplifies to (Eq. 4.49 in [Gourgoulhon](https://arxiv.org/pdf/gr-qc/0703035.pdf)):
$$
g^{\mu\nu} = \begin{pmatrix}
-\frac{1}{\alpha^2} & \frac{\beta^i}{\alpha^2} \\
\frac{\beta^i}{\alpha^2} & \gamma^{ij} - \frac{\beta^i\beta^j}{\alpha^2}
\end{pmatrix} =
\begin{pmatrix}
-\frac{1}{\alpha^2} & 0 \\
0 & \gamma^{ij}
\end{pmatrix},
$$

and since the 4-metric is diagonal we get

\begin{align}
\gamma^{\bar{r}\bar{r}} &= e^{-4\phi}\\
\gamma^{\theta\theta} &= e^{-4\phi}\frac{1}{\bar{r}^2} \\
\gamma^{\phi\phi} &= e^{-4\phi}\frac{1}{\bar{r}^2 \sin^2 \theta}.
\end{align}

Thus raising $T^\mu_\nu$ yields a diagonal $T^{\mu\nu}$

\begin{align}
T^{tt} &= -g^{tt} \rho = \frac{1}{\alpha^2} \rho = e^{-\nu(\bar{r})} \rho \\
T^{\bar{r}\bar{r}} &= g^{\bar{r}\bar{r}} P = \frac{1}{e^{4 \phi}} P \\
T^{\theta\theta} &= g^{\theta\theta} P = \frac{1}{e^{4 \phi}\bar{r}^2} P\\
T^{\phi\phi} &= g^{\phi\phi} P = \frac{1}{e^{4\phi}\bar{r}^2 \sin^2 \theta} P
\end{align}_____no_output_____

# Step 4.3: Construct T^{mu nu}:
T4UU = ixp.zerorank2(DIM=4)

# T^tt = e^(-nu) * rho
T4UU[0][0] = rho / expnu
# T^{ii} = P / gamma_{ii}
for i in range(3):
    T4UU[i+1][i+1] = P / gammaSphDD[i][i]_____no_output_____

## Step 5: NRPy+ Module Code Validation \[Back to [top](#top)\]
$$\label{step5}$$

Here, as a code validation check, we verify agreement in the SymPy expressions for TOV initial data between

1. this tutorial and
2. the NRPy+ [TOV.TOV_ADM_T4UUmunu](../edit/TOV/TOV_ADM_T4UUmunu.py) module._____no_output_____As all input quantities are functions of $r$, we will simply read the solution from file and interpolate it to the values of $r$ needed by the initial data.

1. First we define functions ID_TOV_ADM_quantities() and ID_TOV_TUPMUNU() that call the [1D TOV interpolator function](../edit/TOV/tov_interp.h) to evaluate the ADM spacetime quantities and $T^{\mu\nu}$, respectively, at any given point $(r,\theta,\phi)$ in the Spherical basis. All quantities are defined as above.
1. Next we will construct the BSSN/ADM source terms $\{S_{ij},S_{i},S,\rho\}$ in the Spherical basis
1. Then we will perform the Jacobian transformation on $\{S_{ij},S_{i},S,\rho\}$ to the desired (xx0,xx1,xx2) basis
1. Next we call the *Numerical* Spherical ADM$\to$Curvilinear BSSN converter function to convert the above ADM quantities to the rescaled BSSN quantities in the desired curvilinear coordinate system: [BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py](../edit/BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py); [\[**tutorial**\]](Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb)._____no_output_____### Output to $\LaTeX$, then PDF_____no_output_____

!jupyter nbconvert --to latex --template latex_nrpy_style.tplx Tutorial-ADM_and_T4UUmunu_Initial_Data-TOV.ipynb
!pdflatex -interaction=batchmode Tutorial-ADM_and_T4UUmunu_Initial_Data-TOV.tex
!pdflatex -interaction=batchmode Tutorial-ADM_and_T4UUmunu_Initial_Data-TOV.tex
!pdflatex -interaction=batchmode Tutorial-ADM_and_T4UUmunu_Initial_Data-TOV.tex
!rm -f Tut*.out Tut*.aux Tut*.log[NbConvertApp] Converting notebook Tutorial-ADM_and_T4UUmunu_Initial_Data-TOV.ipynb to latex
[NbConvertApp] Writing 67639 bytes to Tutorial-ADM_and_T4UUmunu_Initial_Data-TOV.tex
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode
This is pdfTeX, Version 3.14159265-2.6-1.40.18 (TeX Live 2017/Debian) (preloaded format=pdflatex)
 restricted \write18 enabled.
entering extended mode


# Notebook from azubiolo/lectures
Path: it_step/ml_from_scratch/4_least-squares_continued/least-squares.ipynb

# Ordinary Least Squares -- Part II
## Course recap
This lab consists in implementing the **Ordinary Least Squares** (OLS) algorithm, which is **a linear regression with a least-squares penalty**. Given a training set $ D = \left\{ \left(x^{(i)}, y^{(i)}\right), x^{(i)} \in \mathcal{X}, y^{(i)} \in \mathcal{Y}, i \in \{1, \dots, n \}  \right\}$, recall (from lectures 1 and 2) OLS aims at minimizing the following cost function $J$:
$$J(\theta) = \dfrac{1}{2} \sum_{i = 1}^{n} \left( h\left(x^{(i)}\right) - y^{(i)} \right)^2$$
where
$$h(x) = \sum_{j = 0}^{d} \theta_j x_j = \theta^T x.$$

For the sake of simplicity, we will be working on a small training set (the one we used in lectures 1 and 2):

| living area (m$^2$) | price (1000's BGN)|
|--------------------:|------------------:|
|                 50  |         30        |
|                 76  |         48        |
|                 26  |         12        |
|                102  |         90        |_____no_output_____## Defining the training set
**Exercise 1**: Define variables `X` and `Y` that will contain the features $\mathcal{X}$ and labels $\mathcal{Y}$ of the training set.

**Hint**: Do not forget the intercept!_____no_output_____

X = [[1., 50.], [1., 76.], [1., 26.], [1., 102.]]
Y = [30., 48., 12., 90.]

# Y[3] = 300 # Outlier. Uncomment this line if you want to introduce an outlier._____no_output_____

In this simple example, the dimensionality is $d = 1$ (which means 2 features: don't forget the intercept!) and the number of samples is $n = 4$.

**Remark**: `1.` is written instead of `1` in order to avoid *integers operations*. For example, in some languages (including Python 2), the result of `1/2` is `0` and not `0.5`:_____no_output_____

1/2_____no_output_____

Instead, writing `1./2` forces a *float operation* and gives `0.5` as a result, which is what we want:_____no_output_____

1./2_____no_output_____

## Prediction function
**Exercise**: Define a function `predict` that takes as parameter *the feature vector* $x$ and *the model* $\theta$ and returns the predicted label:
$$ \hat{y} = h(x) = \theta^T x = \sum_{j = 0}^d \theta_j x_j$$_____no_output_____

def predict(x, theta):
    y_hat = x[0] * theta[0] + x[1] * theta[1] # compute the dot product between 'theta' and 'x'
    return y_hat_____no_output_____

## Defining the cost function
### Cost function on a single sample
**Exercise**: Define a function `cost_function` that takes as parameter *the predicted label* $y$ and *the actual label* $\hat{y}$ of a single sample and returns the value of the cost function for this pair. Recall from lectures 1 and 2 that it is given by:
$$ \ell \left( y, \hat{y} \right) = \dfrac{1}{2}\left( y - \hat{y} \right)^2$$_____no_output_____

def cost_function(y, y_hat):
    loss = (y - y_hat) ** 2 / 2
    return loss_____no_output_____

### Cost function on the whole training set
We are now able to compute the cost function for a single sample. We can easily compute the cost function for the whole training set by summing the cost function values for all the samples in the training set. Recall that the total cost function is given by:
$$J(\theta) = \dfrac{1}{2} \sum_{i = 1}^{n} \left( h\left(x^{(i)}\right) - y^{(i)} \right)^2$$
where, for all $i \in \{ 1, \dots, n \}$
$$h\left(x^{(i)}\right) = \sum_{j = 0}^{d} \theta_j x^{(i)}_j = \theta^T x^{(i)}$$
is the prediction of $x$ given the model $\theta$._____no_output_____

def cost_function_total(X, Y, theta):
    cost = 0 # initialize the cost with 0
    n = len(Y)
    for i in range(n): # iterate over the training set (n = 4 in our case)
        x = X[i] # get the ith feature vector
        y = Y[i] # get the ith label
        y_hat = predict(x, theta) # predict the ith label
        cost += cost_function(y, y_hat) # add the cost of the current sample to the total cost
    return cost_____no_output_____

Let's now test the code written above and check the total cost function we would have when $\theta = [0, 0]$._____no_output_____

theta_0 = [0, 0]
cost_function_total(X, Y, theta_0)_____no_output_____

Note that this error is big, which is expectable because having $\theta = [0, 0]$ means always predicting $\hat{y} = 0$._____no_output_____## Defining the gradient of the cost function
### Gradient on a single sample
**Exercise**: Define a function `gradient` that implements the gradient of the cost function for a given sample $(x, y)$. Recall from the lectures 1 and 2 that the gradient is given by:
$$\nabla J(\theta) = \left[ \dfrac{\partial}{\partial \theta_1} J(\theta), \dots, \dfrac{\partial}{\partial \theta_d} J(\theta) \right]^T$$
where, for all $j \in \{0, \dots, d \}$:
$$ \dfrac{\partial}{\partial \theta_j} J(\theta) = \left( h\left(x\right) - y \right) x_j. $$
**Hint**: Recall that $d = 1$, hence the gradient is of size $2$ (one value for $j = 0$, and another one for $j = 1$). Its two values are given by:
$$ \dfrac{\partial}{\partial \theta_0} J(\theta) = \left( h\left(x\right) - y \right) x_0 \quad \text{and} \quad
\dfrac{\partial}{\partial \theta_1} J(\theta) = \left( h\left(x\right) - y \right) x_1. $$_____no_output_____

def gradient(x, y, theta):
    grad = [0, 0]
    grad[0] = (predict(x, theta) - y) * x[0] # first value of the gradient
    grad[1] = (predict(x, theta) - y) * x[1] # second value of the gradient
    return grad_____no_output_____

Let's try the `gradient` function on a simple example ($\theta = [0, 0]$ on the first sample of the training set, *i.e.* $\left(x^{(0)}, y^{(0)}\right)$)._____no_output_____

gradient(X[0], Y[0], theta_0)_____no_output_____

### Gradient on the whole training set
Now we are able to compute the gradient of the cost function on a single sample, we can easily compute `gradient_total`, the gradient of the cost function on the whole training set by summing the gradients for all the samples in the training set._____no_output_____

def gradient_total(X, Y, theta):
    grad_total = [0, 0] # initialize the gradient with zeros
    n = len(Y)
    for i in range(n): # iterate over the training set
        x = X[i] # get the ith feature vector
        y = Y[i] # get the ith label
        grad = gradient(x, y, theta) # predict the ith label given 'theta'
        grad_total[0] += grad[0] # add the gradient corresponding to theta[0]
        grad_total[1] += grad[1] # add the gradient corresponding to theta[1]
    return grad_total_____no_output_____

Let's now test the code written above and check the total gradient we would have when $\theta = [0, 0]$_____no_output_____

gradient_total(X, Y, theta_0)_____no_output_____

**Question**: What is the sign of the gradient values? What would it mean if we had such a gradient when applying a gradient descent?

**Hint**: Recall the gradient descent update:
$$\theta_j := \theta_j - \alpha \dfrac{\partial}{\partial \theta_j} J(\theta) \quad \text{for all } j \in \{0, \dots, d \}$$

**Answer**: Both values are negative, which means this gradient step would increase the value of $\theta$ due to fact we **substract** the gradient. This makes sense, because:
- we start with $\theta = [0, 0]$,
- we expect $\theta_0 > 0$ and $\theta_1 > 0$ because otherwise we could predict a negative price.

## Applying a gradient descent
### Gradient descent step implementation
We now have all the building blocs needed for the gradient descent algorithm, that is:
- The loss function
- The gradient
Indeed, the iterative update scheme of this algorithm is given by the following formula:
$$\theta_j := \theta_j - \alpha \dfrac{\partial}{\partial \theta_j} J(\theta)$$
for all $j \in \{0, \dots, d \}$. Recall that $\alpha$ is a parameter called the *learning rate* (or *step size*).

**Exercise**: Define a function called `gradient_descent_step` that performs an update on theta by applying the formula above._____no_output_____

def gradient_descent_step(X, Y, theta, alpha):
    theta_updated = [0, 0]
    grad = gradient_total(X, Y, theta)
    theta_updated[0] = theta[0] - alpha * grad[0]
    theta_updated[1] = theta[1] - alpha * grad[1]
    return theta_updated_____no_output_____

Try to run a few iterations manually. Play with the value of $\alpha$ to see how it impacts the algorithm._____no_output_____

alpha = 0.0001
theta_1 = gradient_descent_step(X, Y, theta_0, alpha)
theta_2 = gradient_descent_step(X, Y, theta_1, alpha)
theta_2_____no_output_____

### Iterating gradient descent steps
The `gradient_descent_step` implements a single gradient step of the gradient descent algorithm. Implement a function called `gradient_descent` that starts from a given $\theta$ (exaple $\theta = [0, 0]$) and applies 100 gradient descent iterations. Display the total cost function $J(\theta)$ at each iteration._____no_output_____

def gradient_descent(X, Y, alpha):
    theta = [0, 0] # initializing theta with zeros (it can be initialized in another manner)
    n_iteration_max = 100
    loss_history = []
    for i_iteration in range(n_iteration_max):
        loss = cost_function_total(X, Y, theta)
        loss_history.append(loss)
        print("Iteration {:>2}. Current loss = {}".format(i_iteration, loss))
        theta = gradient_descent_step(X, Y, theta, alpha)
    loss = cost_function_total(X, Y, theta)
    loss_history.append(loss)
    print("Optimization complete. Final loss = {}".format(loss))
    return theta, loss_history_____no_output_____

Play with the code you've just run. Try different values of $\alpha$ and see the impact it has.

Note: $\alpha = 0.0001$ works well in this case._____no_output_____

theta_trained_gd, loss_history = gradient_descent(X, Y, alpha)Iteration  0. Current loss = 5724.0
Iteration  1. Current loss = 5037.312744000001
Iteration  2. Current loss = 4435.792673300704
Iteration  3. Current loss = 3908.876603267039
Iteration  4. Current loss = 3447.311488513006
Iteration  5. Current loss = 3042.9919278860875
Iteration  6. Current loss = 2688.8178235154955
Iteration  7. Current loss = 2378.569694239707
Iteration  8. Current loss = 2106.799453761523
Iteration  9. Current loss = 1868.7347354588892
Iteration 10. Current loss = 1660.1950836758394
Iteration 11. Current loss = 1477.5185397081189
Iteration 12. Current loss = 1317.497333241855
Iteration 13. Current loss = 1177.3215499067294
Iteration 14. Current loss = 1054.5297856756622
Iteration 15. Current loss = 946.9659215407085
Iteration 16. Current loss = 852.7412593745793
Iteration 17. Current loss = 770.2013540363196
Iteration 18. Current loss = 697.8969592515267
Iteration 19. Current loss = 634.5585770405551
Iteration 20. Current loss = 579.0741637509263
Iteration 21. Current loss = 530.469601184068
Iteration 22. Current loss = 487.89158986501747
Iteration 23. Current loss = 450.5926640395761
Iteration 24. Current loss = 417.9180652435737
Iteration 25. Current loss = 389.2942439277548
Iteration 26. Current loss = 364.21878721247606
Iteration 27. Current loss = 342.2515958910323
Iteration 28. Current loss = 323.0071557387844
Iteration 29. Current loss = 306.1477674026388
Iteration 30. Current loss = 291.3776159792998
Iteration 31. Current loss = 278.43757613668663
Iteration 32. Current loss = 267.1006615499776
Iteration 33. Current loss = 257.1680387386946
Iteration 34. Current loss = 248.46553530284635
Iteration 35. Current loss = 240.84058123839284
Iteration 36. Current loss = 234.159529617723
Iteration 37. Current loss = 228.30530958295333
Iteration 38. Current loss = 223.17537043567705
Iteration 39. Current loss = 218.67988071882633
Iteration 40. Current loss = 214.74015066426756
Iteration 41. Current loss = 211.28725030235205
Iteration 42. Current loss = 208.26079896569684
Iteration 43. Current loss = 205.6079049293753
Iteration 44. Current loss = 203.28223656627523
Iteration 45. Current loss = 201.24320870595525
Iteration 46. Current loss = 199.45526990844746
Iteration 47. Current loss = 197.8872781366419
Iteration 48. Current loss = 196.51195386328519
Iteration 49. Current loss = 195.30540100847043
Iteration 50. Current loss = 194.24668729467783
Iteration 51. Current loss = 193.31747664988472
Iteration 52. Current loss = 192.5017072032791
Iteration 53. Current loss = 191.7853092187849
Iteration 54. Current loss = 191.15595801295416
Iteration 55. Current loss = 190.60285751816338
Iteration 56. Current loss = 190.11655069020554
Iteration 57. Current loss = 189.68875343080742
Iteration 58. Current loss = 189.3122091085363
Iteration 59. Current loss = 188.98056112330914
Iteration 60. Current loss = 188.68824127657257
Iteration 61. Current loss = 188.4303719867952
Iteration 62. Current loss = 188.20268063306162
Iteration 63. Current loss = 188.0014245225231
Iteration 64. Current loss = 187.82332516404898
Iteration 65. Current loss = 187.66551069383956
Iteration 66. Current loss = 187.52546544192012
Iteration 67. Current loss = 187.4009857538448
Iteration 68. Current loss = 187.29014129177935
Iteration 69. Current loss = 187.19124113536765
Iteration 70. Current loss = 187.1028040870657
Iteration 71. Current loss = 187.0235326604702
Iteration 72. Current loss = 186.9522902948442
Iteration 73. Current loss = 186.88808139569693
Iteration 74. Current loss = 186.83003385090893
Iteration 75. Current loss = 186.77738371535878
Iteration 76. Current loss = 186.7294617950999
Iteration 77. Current loss = 186.6856818954842
Iteration 78. Current loss = 186.64553052685784
Iteration 79. Current loss = 186.60855788704697
Iteration 80. Current loss = 186.57436996227526
Iteration 81. Current loss = 186.54262160779513
Iteration 82. Current loss = 186.51301048672116
Iteration 83. Current loss = 186.4852717606192
Iteration 84. Current loss = 186.45917343862135
Iteration 85. Current loss = 186.43451230337817
Iteration 86. Current loss = 186.4111103423159
Iteration 87. Current loss = 186.38881162151648
Iteration 88. Current loss = 186.3674795473284
Iteration 89. Current loss = 186.34699446761687
Iteration 90. Current loss = 186.32725157052732
Iteration 91. Current loss = 186.3081590438632
Iteration 92. Current loss = 186.28963646275298
Iteration 93. Current loss = 186.27161337729555
Iteration 94. Current loss = 186.25402807537478
Iteration 95. Current loss = 186.2368264989228
Iteration 96. Current loss = 186.2199612945987
Iteration 97. Current loss = 186.2033909822071
Iteration 98. Current loss = 186.1870792262614
Iteration 99. Current loss = 186.17099419788883
Optimization complete. Final loss = 186.15510801588317

Let's have a more visual interpretation by plotting the loss over iterations._____no_output_____

import matplotlib.pyplot as plt
%matplotlib inline
plt.plot(loss_history)
plt.ylabel('loss')
plt.show()_____no_output_____theta_trained_gd_____no_output_____

Let us see what the trained regression looks like. _____no_output_____

houses_sizes = range(150)
estimated_prices = [theta_trained_gd[0] + house_size * theta_trained_gd[1] for house_size in houses_sizes]
x1s = [x[1] for x in X]
plt.plot(houses_sizes, estimated_prices)
plt.ylabel("price")
plt.xlabel("house size")
plt.scatter(x1s, Y, color="red")
plt.show()_____no_output_____

**Question**: Looks at the evolution of the cost function over time. What comment can you make?

**Answer**: The loss function constantly drops over time with this initial value of $\theta$ and this $\alpha$. It ends up reaching a plateau at around 186. It seems like the algorithm has converged to the optimal value of the cost function.

**Question**: What does the value `theta_trained` represent?

**Answer**: Recall that the model $\theta$ has to values, $\theta_0$ and $\theta_1$. Hence, with the model `theta_trained` we've learnt, price prediction would be:
$$ \text{price} = \theta_0 + \theta_1 \times \text{area}.$$

## Batch gradient descent vs. stochastic gradient descent
As we have seen during the lecture 1, the gradient descent methods are often split into 2 different subfamilies:
- **Batch methods** update $\theta$ after having computed the gradient on the whole training set
- **Stochastic methods** update $\theta$ after having computed the gradient on a single sample
The gradient descent we have implemented above (`gradient_descent_step` and `gradient_descent`) corresponds to the batch version because it sums the gradient of all the samples in the training set.

**Exercise**: Try to implement the stochastic version of the gradient descent algorithm. You will need to define a function `stochastic_gradient_descent_step` that compute a stochastic gradient step (on a single $(x, y)$ sample) and a function `stochastic_gradient_descent` iterates 100 stochastic gradient descent steps and returns the trained model $\theta$.

** Solution**: We first define the function `stochastic_gradient_descent_step` that implements a stochastic gradient step._____no_output_____

def stochastic_gradient_descent_step(x, y, theta, alpha):
    theta_updated = [0, 0]
    grad = gradient(x, y, theta)
    theta_updated[0] = theta[0] - alpha * grad[0]
    theta_updated[1] = theta[1] - alpha * grad[1]
    return theta_updated_____no_output_____

Then the `stochastic_gradient_descent` function that will do the iterations._____no_output_____

def stochastic_gradient_descent(X, Y, alpha):
    theta = [0, 0] # initializing theta with zeros (it can be initialized in another manner)
    n_iteration_max = 100
    loss_history = []
    n_samples = len(Y)
    for i_iteration in range(n_iteration_max):
        i_sample = i_iteration % n_samples
        loss = cost_function_total(X, Y, theta)
        loss_history.append(loss)
        print("Iteration {:>2}. Current loss = {}".format(i_iteration, loss))
        theta = stochastic_gradient_descent_step(X[i_sample], Y[i_sample], theta, alpha) # run the gradient update on a single sample
    loss = cost_function_total(X, Y, theta)
    loss_history.append(loss)
    print("Optimization complete. Final loss = {}".format(loss))
    return theta, loss_history_____no_output_____

Let's now apply the algorithm with the same parameters._____no_output_____

theta_trained_sgd, loss_history_sgd = stochastic_gradient_descent(X, Y, alpha)Iteration  0. Current loss = 5724.0
Iteration  1. Current loss = 3745.329318
Iteration  2. Current loss = 1229.5885055588851
Iteration  3. Current loss = 1215.4002506952156
Iteration  4. Current loss = 389.2488988697621
Iteration  5. Current loss = 233.84872707672636
Iteration  6. Current loss = 205.38990003797537
Iteration  7. Current loss = 222.35389730665707
Iteration  8. Current loss = 360.2446248124806
Iteration  9. Current loss = 223.65082333311133
Iteration 10. Current loss = 208.31517423690133
Iteration 11. Current loss = 226.11952840036483
Iteration 12. Current loss = 360.5442624543617
Iteration 13. Current loss = 223.74419111203997
Iteration 14. Current loss = 208.25999331497226
Iteration 15. Current loss = 226.05126940341967
Iteration 16. Current loss = 360.50675808520907
Iteration 17. Current loss = 223.721958030496
Iteration 18. Current loss = 208.24178739067673
Iteration 19. Current loss = 226.03001538194087
Iteration 20. Current loss = 360.4732920259364
Iteration 21. Current loss = 223.7011117481283
Iteration 22. Current loss = 208.22314266007874
Iteration 23. Current loss = 226.0082029487174
Iteration 24. Current loss = 360.4397826157614
Iteration 25. Current loss = 223.68025225863477
Iteration 26. Current loss = 208.20450634314338
Iteration 27. Current loss = 225.9864006950348
Iteration 28. Current loss = 360.40627866132445
Iteration 29. Current loss = 223.65939630261482
Iteration 30. Current loss = 208.18587309127957
Iteration 31. Current loss = 225.96460182117386
Iteration 32. Current loss = 360.37277957826257
Iteration 33. Current loss = 223.63854367935122
Iteration 34. Current loss = 208.16724296793694
Iteration 35. Current loss = 225.94280640789367
Iteration 36. Current loss = 360.3392853728212
Iteration 37. Current loss = 223.61769439071062
Iteration 38. Current loss = 208.14861597184492
Iteration 39. Current loss = 225.92101445367686
Iteration 40. Current loss = 360.3057960441828
Iteration 41. Current loss = 223.59684843613806
Iteration 42. Current loss = 208.12999210250692
Iteration 43. Current loss = 225.89922595799038
Iteration 44. Current loss = 360.27231159161306
Iteration 45. Current loss = 223.57600581510619
Iteration 46. Current loss = 208.111371359418
Iteration 47. Current loss = 225.87744092029027
Iteration 48. Current loss = 360.23883201437906
Iteration 49. Current loss = 223.55516652708909
Iteration 50. Current loss = 208.09275374207272
Iteration 51. Current loss = 225.85565934003165
Iteration 52. Current loss = 360.205357311747
Iteration 53. Current loss = 223.53433057155982
Iteration 54. Current loss = 208.07413924996584
Iteration 55. Current loss = 225.8338812166708
Iteration 56. Current loss = 360.1718874829827
Iteration 57. Current loss = 223.5134979479919
Iteration 58. Current loss = 208.05552788259214
Iteration 59. Current loss = 225.81210654966304
Iteration 60. Current loss = 360.13842252735407
Iteration 61. Current loss = 223.49266865585912
Iteration 62. Current loss = 208.03691963944726
Iteration 63. Current loss = 225.79033533846427
Iteration 64. Current loss = 360.10496244412576
Iteration 65. Current loss = 223.4718426946345
Iteration 66. Current loss = 208.0183145200253
Iteration 67. Current loss = 225.76856758253032
Iteration 68. Current loss = 360.0715072325656
Iteration 69. Current loss = 223.45102006379193
Iteration 70. Current loss = 207.99971252382204
Iteration 71. Current loss = 225.74680328131802
Iteration 72. Current loss = 360.03805689193996
Iteration 73. Current loss = 223.43020076280555
Iteration 74. Current loss = 207.9811136503327
Iteration 75. Current loss = 225.725042434283
Iteration 76. Current loss = 360.0046114215164
Iteration 77. Current loss = 223.4093847911493
Iteration 78. Current loss = 207.96251789905267
Iteration 79. Current loss = 225.7032850408812
Iteration 80. Current loss = 359.9711708205613
Iteration 81. Current loss = 223.38857214829648
Iteration 82. Current loss = 207.94392526947695
Iteration 83. Current loss = 225.68153110056898
Iteration 84. Current loss = 359.93773508834147
Iteration 85. Current loss = 223.36776283372149
Iteration 86. Current loss = 207.92533576110145
Iteration 87. Current loss = 225.65978061280353
Iteration 88. Current loss = 359.90430422412567
Iteration 89. Current loss = 223.3469568468986
Iteration 90. Current loss = 207.906749373421
Iteration 91. Current loss = 225.63803357704077
Iteration 92. Current loss = 359.8708782271795
Iteration 93. Current loss = 223.3261541873017
Iteration 94. Current loss = 207.88816610593256
Iteration 95. Current loss = 225.61628999273688
Iteration 96. Current loss = 359.8374570967709
Iteration 97. Current loss = 223.30535485440527
Iteration 98. Current loss = 207.86958595813053
Iteration 99. Current loss = 225.5945498593494
Optimization complete. Final loss = 359.80404083216865

Here again, let's plot the loss history over iterations._____no_output_____

plt.plot(loss_history_sgd)
plt.ylabel('loss')
plt.show()_____no_output_____

Again, let's look at the regression we obtain._____no_output_____

houses_sizes = range(150)
estimated_prices = [theta_trained_sgd[0] + house_size * theta_trained_sgd[1] for house_size in houses_sizes]
x1s = [x[1] for x in X]
plt.plot(houses_sizes, estimated_prices)
plt.ylabel("price")
plt.xlabel("house size")
plt.scatter(x1s, Y, color="red")
plt.show()_____no_output_____

**Question**: Compare the results obtained when solving the OLS problem with stochastic gradient descent and batch gradient descent. Are the results the same? Why?

**Hint**: Plot the loss function.

**Answer**: The results are **not the same**, the loss function value differs **a lot**: More than 200 for the stochastic gradient descent, and about 186 for the batch gradient descent version.

To understand what happens, let's look at what the function looks like. It is a function of 2 variables ($\theta_0$ and $\theta_1$), so we can use either a *3D plot* or a *contour plot* for visualize it.

The following code shows the *contour plot*. First, we need to define a grid of $\theta$ values on which we will evaluate the loss function. `theta0_vector` and `theta1_vector` define the values of $\theta_0$ and $\theta_1$ on which the function will be evaluated._____no_output_____

import numpy as np
dtheta = 0.001
theta0_vector = np.arange(-1,+1, dtheta)
theta1_vector = np.arange(-1,+1, dtheta)_____no_output_____

Let's compute the value of the loss function for each pair $(\theta_0, \theta_1)$._____no_output_____

n0 = len(theta0_vector)
n1 = len(theta1_vector)
loss = np.zeros([n0, n1])
for i0 in range(n0):
    for i1 in range(n1):
        loss[i0, i1] = cost_function_total(X, Y, [theta0_vector[i0], theta1_vector[i1]])_____no_output_____fig = plt.figure()
plt.contourf(theta0_vector, theta1_vector, loss)
plt.colorbar()
plt.show()_____no_output_____

We can look at what the function from "further away":_____no_output_____

dtheta = 1.
# from -100 to +100 on both axis
theta0_vector = np.arange(-100,100, dtheta)
theta1_vector = np.arange(-100,100, dtheta)
n0 = len(theta0_vector)
n1 = len(theta1_vector)
loss = np.zeros([n0, n1])
for i0 in range(n0):
    for i1 in range(n1):
        loss[i0, i1] = cost_function_total(X, Y, [theta0_vector[i0], theta1_vector[i1]])
fig = plt.figure()
plt.contourf(theta0_vector, theta1_vector, loss)
plt.colorbar()
plt.show()_____no_output_____

It appears that the function behaves very differently on the two different axis:
- It varies very fast on the horizontal axis
- It varies very slowly on the vertical axis

**Question**: What could this be due to? How could we solve this issue?

**Answer**: This is due to the fact that the two features (the size and the intercept) are on very different scales: the intercept is constantly equal to 1, while the size of the house varies on a much wider range (up to 102). In our algorithm, we make no difference between $\theta_0$ and $\theta_1$: the same step-size is
A way to address this issue is to normalize (or rescale) the features. There are several ways to do it:
1. The first way consists in, for each feature, dividing all the feature values by its maximal value. In the case of the house size, it would consist in dividing all the house sizes by 102, so that the biggest house would have a size of 1.
2. Another way consists in computing the $z$-score of the feature:
$$ z = \dfrac{x - x_{\min}}{x_{\max} - x_{\min}} $$
so that the feature respectively has 0 and 1 as minimum and maximum value. Note that this does not apply to the intercept $x_0$, because it is constantly equal to $1$._____no_output_____**Homeworks**:
1. Implement the feature normalization of your choice. Run the OLS algorithm on it, and compare the result with the non-normalized regression.
2. The gradient descent we have implemented seems to lower the loss smoothly. The impelmentation we proposed did 100 iterations, and we can see that the loss function values is not changing much after 40-50 iterations. Could you think of a way to stop the algorithm to avoid having too many iterations giving a very marginal gain in the loss function?
3. Add an *outlier* to the training set (for example, a house with a normal size but a very small or very big price), and run the OLS algorithm on it. What impact does the outlier have on the quality of the regression? How to correct this issue?

In the next session, we will talk about regularization and how to define a more complex model when the data is not linearly separable._____no_output_____Let's apply the following transformation:
$$ z = \dfrac{x - x_{\min}}{x_{\max} - x_{\min}} $$
To do so, we need to compute the min and max values of $X$. Let's also define a function that computes $z$ given $x$_____no_output_____

x_min = X[0][1]
x_max = X[0][1]
for i in range(len(X)):
    x_min = min(x_min, X[i][1])
    x_max = max(x_max, X[i][1])

def rescale(x):
    return (x - x_min)/(x_max - x_min)_____no_output_____

Let's now apply this transformation to all the element of $X$.

**Remark**: We keep the intercept as it is, without applying the transformation to it._____no_output_____

Z = [[x[0], rescale(x[1])] for x in X]
print(Z)[[1.0, 0.3157894736842105], [1.0, 0.6578947368421053], [1.0, 0.0], [1.0, 1.0]]

The intercept remains unchanged, and the second value of each instance (which corresponds to the area of the house) is between 0 and 1._____no_output_____

dtheta = 1.
# from -100 to +100 on both axis
theta0_vector = np.arange(-100,100, dtheta)
theta1_vector = np.arange(-100,100, dtheta)
n0 = len(theta0_vector)
n1 = len(theta1_vector)
loss = np.zeros([n0, n1])
for i0 in range(n0):
    for i1 in range(n1):
        loss[i0, i1] = cost_function_total(Z, Y, [theta0_vector[i0], theta1_vector[i1]])
fig = plt.figure()
plt.contourf(theta0_vector, theta1_vector, loss)
plt.colorbar()
plt.show()_____no_output_____

We can now apply the gradient algorithm we've defined previously._____no_output_____

def gradient_descent(X, Y, alpha):
    theta = [0, 0] # initializing theta with zeros (it can be initialized in another manner)
    n_iteration_max = 100
    loss_history = []
    theta_history = [theta]
    for i_iteration in range(n_iteration_max):
        loss = cost_function_total(X, Y, theta)
        loss_history.append(loss)
        # print("Iteration {:>2}. Current loss = {}".format(i_iteration, loss))
        theta = gradient_descent_step(X, Y, theta, alpha)
        theta_history.append(theta)
    loss = cost_function_total(X, Y, theta)
    loss_history.append(loss)
    print("Optimization complete. Final loss = {}".format(loss))
    plt.plot(loss_history)
    plt.ylabel('loss')
    plt.xlabel('iterations')
    plt.show()
    return theta_history, loss_history

alpha = 0.0001
theta_history_gd, loss_history_gd = gradient_descent(Z, Y, alpha)Optimization complete. Final loss = 5252.219720169256

The loss is constantly going down, and does not seem to have reached its minimal value. This is because we chose $\alpha = 0.0001$, which isn't appropriate in this case. $\alpha$ is way too small, hence we are doing tiny steps and it would take a long time for the algorithm to converge. _____no_output_____**Note**: As previously, the chosen value for $\alpha$ is still important, if we take $\alpha = 1$, the algorithm would diverge:_____no_output_____

alpha = 1.
theta_history_gd, loss_history_gd = gradient_descent(Z, Y, alpha)Optimization complete. Final loss = 1.2779108922343823e+126

Instead, let us try with a higher value, say $\alpha = 0.1$. _____no_output_____

alpha = 0.1
theta_history_gd, loss_history = gradient_descent(Z, Y, alpha)Optimization complete. Final loss = 77.56352172981322

$\alpha = 0.1$ seems to work much better, the loss function is decreasing fast at the beginning and seems to reach a loss function value of about 77.

**Note**: This loss function value is different than the one we used to have without rescaling the $x$ value (77 vs. 186). This is completely normal, because rescaling $X$ into $Z$ changes the loss function value, which becomes
$$J(\theta) = \dfrac{1}{2} \sum_{i = 1}^{n} \left( h\left(z^{(i)}\right) - y^{(i)} \right)^2$$
instead of:
$$J(\theta) = \dfrac{1}{2} \sum_{i = 1}^{n} \left( h\left(x^{(i)}\right) - y^{(i)} \right)^2$$
($x^{(i)}$ is replaced by $z^{(i)}$ in the formula). As a consequence, having 77 rather than 186 doesn't mean we're doing a better job than before!_____no_output_____

dtheta = 1.
# from -100 to +100 on both axis
theta0_vector = np.arange(0,100, dtheta)
theta1_vector = np.arange(0,100, dtheta)
n0 = len(theta0_vector)
n1 = len(theta1_vector)
loss = np.zeros([n0, n1])
for i0 in range(n0):
    for i1 in range(n1):
        loss[i0, i1] = cost_function_total(Z, Y, [theta0_vector[i0], theta1_vector[i1]])
fig = plt.figure()
plt.contourf(theta0_vector, theta1_vector, loss)
plt.colorbar()
for theta in theta_history_gd:
    plt.scatter(theta[1], theta[0], color="red")
plt.show()_____no_output_____

Let's do the same for stochastic gradient descent:_____no_output_____

def stochastic_gradient_descent(X, Y, alpha):
    theta = [0, 0] # initializing theta with zeros (it can be initialized in another manner)
    n_iteration_max = 100
    loss_history = []
    n_samples = len(Y)
    theta_history = [theta]
    for i_iteration in range(n_iteration_max):
        i_sample = i_iteration % n_samples
        loss = cost_function_total(X, Y, theta)
        loss_history.append(loss)
        print("Iteration {:>2}. Current loss = {}".format(i_iteration, loss))
        theta = stochastic_gradient_descent_step(X[i_sample], Y[i_sample], theta, alpha) # run the gradient update on a single sample
        theta_history.append(theta)
    loss = cost_function_total(X, Y, theta)
    loss_history.append(loss)
    print("Optimization complete. Final loss = {}".format(loss))
    return theta_history, loss_history_____no_output_____alpha = .5
theta_history_sgd, loss_history_sgd = stochastic_gradient_descent(Z, Y, alpha)
plt.plot(loss_history_sgd)
plt.ylabel('loss')
plt.show()Iteration  0. Current loss = 5724.0
Iteration  1. Current loss = 3010.6532446804435
Iteration  2. Current loss = 1241.9025020501365
Iteration  3. Current loss = 1685.279818913434
Iteration  4. Current loss = 1531.9766999537965
Iteration  5. Current loss = 561.6195267010057
Iteration  6. Current loss = 561.6950873394483
Iteration  7. Current loss = 695.3182321565688
Iteration  8. Current loss = 931.8346044543953
Iteration  9. Current loss = 330.2952436540648
Iteration 10. Current loss = 345.2981694177922
Iteration 11. Current loss = 417.82247243762936
Iteration 12. Current loss = 602.6180880809652
Iteration 13. Current loss = 211.2669451858648
Iteration 14. Current loss = 238.35061763263207
Iteration 15. Current loss = 269.80037270918945
Iteration 16. Current loss = 418.37493939016014
Iteration 17. Current loss = 149.91153342330858
Iteration 18. Current loss = 186.35699699370403
Iteration 19. Current loss = 189.76918378198243
Iteration 20. Current loss = 312.8279513004007
Iteration 21. Current loss = 118.20691670069019
Iteration 22. Current loss = 161.71909972666782
Iteration 23. Current loss = 145.76124061473206
Iteration 24. Current loss = 250.76723445227606
Iteration 25. Current loss = 101.76863999861726
Iteration 26. Current loss = 150.52615505433388
Iteration 27. Current loss = 121.06195124820532
Iteration 28. Current loss = 213.254642288688
Iteration 29. Current loss = 93.20643928610741
Iteration 30. Current loss = 145.814144689401
Iteration 31. Current loss = 106.86625787677015
Iteration 32. Current loss = 189.94336293986785
Iteration 33. Current loss = 88.71889884214102
Iteration 34. Current loss = 144.13139376144792
Iteration 35. Current loss = 98.48970924226285
Iteration 36. Current loss = 175.07094578163267
Iteration 37. Current loss = 86.34740181551697
Iteration 38. Current loss = 143.79247658929688
Iteration 39. Current loss = 93.40804945660534
Iteration 40. Current loss = 165.3546426031773
Iteration 41. Current loss = 85.08049511257698
Iteration 42. Current loss = 143.99318164746717
Iteration 43. Current loss = 90.23898381794234
Iteration 44. Current loss = 158.87598385797403
Iteration 45. Current loss = 84.39421019660546
Iteration 46. Current loss = 144.3638645076205
Iteration 47. Current loss = 88.2105416041328
Iteration 48. Current loss = 154.48268130141545
Iteration 49. Current loss = 84.01594715100458
Iteration 50. Current loss = 144.7454481849383
Iteration 51. Current loss = 86.88154567000558
Iteration 52. Current loss = 151.4631193322166
Iteration 53. Current loss = 83.80305827048062
Iteration 54. Current loss = 145.07810690060668
Iteration 55. Current loss = 85.99326603006479
Iteration 56. Current loss = 149.36591513064735
Iteration 57. Current loss = 83.68031682422452
Iteration 58. Current loss = 145.34669518675065
Iteration 59. Current loss = 85.38973629688371
Iteration 60. Current loss = 147.8976824704998
Iteration 61. Current loss = 83.60764408252136
Iteration 62. Current loss = 145.5545468411729
Iteration 63. Current loss = 84.97429147320415
Iteration 64. Current loss = 146.86364210078062
Iteration 65. Current loss = 83.56340419140346
Iteration 66. Current loss = 145.71130737950176
Iteration 67. Current loss = 84.685411332905
Iteration 68. Current loss = 146.13217530376218
Iteration 69. Current loss = 83.5357226578277
Iteration 70. Current loss = 145.82759833410626
Iteration 71. Current loss = 84.48299101275114
Iteration 72. Current loss = 145.6130704561316
Iteration 73. Current loss = 83.51795012529058
Iteration 74. Current loss = 145.91292594711132
Iteration 75. Current loss = 84.34033807981587
Iteration 76. Current loss = 145.24380606439095
Iteration 77. Current loss = 83.50627483612044
Iteration 78. Current loss = 145.97506922228146
Iteration 79. Current loss = 84.23937870145936
Iteration 80. Current loss = 144.98068216103007
Iteration 81. Current loss = 83.49845387500828
Iteration 82. Current loss = 146.0200951455565
Iteration 83. Current loss = 84.16770504373368
Iteration 84. Current loss = 144.79295896443543
Iteration 85. Current loss = 83.4931304929281
Iteration 86. Current loss = 146.0526017003134
Iteration 87. Current loss = 84.11670711128062
Iteration 88. Current loss = 144.65891082822105
Iteration 89. Current loss = 83.48946095342595
Iteration 90. Current loss = 146.07601068372122
Iteration 91. Current loss = 84.0803612093535
Iteration 92. Current loss = 144.56312956025602
Iteration 93. Current loss = 83.48690659451836
Iteration 94. Current loss = 146.09283816901842
Iteration 95. Current loss = 84.05442713380813
Iteration 96. Current loss = 144.49465972833522
Iteration 97. Current loss = 83.48511529329402
Iteration 98. Current loss = 146.10491926351492
Iteration 99. Current loss = 84.03590653123382
Optimization complete. Final loss = 144.44569756315704

As it was the case previously, stochastic gradient descend seems to osciliate around the optimal position. Let us see how the weight vector $\theta$ evolves._____no_output_____

dtheta = 1.
# from -100 to +100 on both axis
theta0_vector = np.arange(0,100, dtheta)
theta1_vector = np.arange(0,100, dtheta)
n0 = len(theta0_vector)
n1 = len(theta1_vector)
loss = np.zeros([n0, n1])
for i0 in range(n0):
    for i1 in range(n1):
        loss[i0, i1] = cost_function_total(Z, Y, [theta0_vector[i0], theta1_vector[i1]])
fig = plt.figure()
plt.contourf(theta0_vector, theta1_vector, loss)
plt.colorbar()
for theta in theta_history_sgd:
    plt.scatter(theta[1], theta[0], color="red")
plt.show()_____no_output_____

Let's have the learning rate decrease over time and put everything together:_____no_output_____

from math import sqrt
def stochastic_gradient_descent(X, Y, alpha):
    theta = [0, 0] # initializing theta with zeros (it can be initialized in another manner)
    n_iteration_max = 100
    loss_history = []
    n_samples = len(Y)
    theta_history = [theta]
    for i_iteration in range(n_iteration_max):
        i_sample = i_iteration % n_samples
        loss = cost_function_total(X, Y, theta)
        loss_history.append(loss)
        # change the learning rate
        alpha_sgd = alpha / sqrt(i_iteration + 1)
        theta = stochastic_gradient_descent_step(X[i_sample], Y[i_sample], theta, alpha_sgd) # run the gradient update on a single sample
        theta_history.append(theta)
    loss = cost_function_total(X, Y, theta)
    loss_history.append(loss)
    print("Optimization complete. Final loss = {}".format(loss))
    return theta_history, loss_history

alpha = 1.
theta_history_sgd, loss_history_sgd = stochastic_gradient_descent(Z, Y, alpha)
plt.plot(loss_history_sgd)
plt.ylabel('loss')
plt.show()

dtheta = 1.
# from -100 to +100 on both axis
theta0_vector = np.arange(0,100, dtheta)
theta1_vector = np.arange(0,100, dtheta)
n0 = len(theta0_vector)
n1 = len(theta1_vector)
loss = np.zeros([n0, n1])
for i0 in range(n0):
    for i1 in range(n1):
        loss[i0, i1] = cost_function_total(Z, Y, [theta0_vector[i0], theta1_vector[i1]])
fig = plt.figure()
plt.contourf(theta0_vector, theta1_vector, loss)
plt.colorbar()
for theta in theta_history_sgd:
    plt.scatter(theta[1], theta[0], color="red")
plt.show()Optimization complete. Final loss = 89.96861903899075


# Notebook from alvin-hsu/ColabFold
Path: AlphaFold2.ipynb

_____no_output_____

##ColabFold: AlphaFold2 using MMseqs2

Easy to use protein structure and complex prediction using [AlphaFold2](https://www.nature.com/articles/s41586-021-03819-2) and [Alphafold2-multimer](https://www.biorxiv.org/content/10.1101/2021.10.04.463034v1). Sequence alignments/templates are generated through [MMseqs2](mmseqs.com) and [HHsearch](https://github.com/soedinglab/hh-suite). For more details, see bottom of the notebook, checkout the [ColabFold GitHub](https://github.com/sokrypton/ColabFold) and read our manuscript. Old versions: [v1.0](https://colab.research.google.com/github/sokrypton/ColabFold/blob/v1.0-alpha/AlphaFold2.ipynb), [v1.1](https://colab.research.google.com/github/sokrypton/ColabFold/blob/v1.1-premultimer/AlphaFold2.ipynb), [v1.2](https://colab.research.google.com/github/sokrypton/ColabFold/blob/v1.2.0/AlphaFold2.ipynb), [v1.3](https://colab.research.google.com/github/sokrypton/ColabFold/blob/v1.3.0/AlphaFold2.ipynb)

[Mirdita M, Schütze K, Moriwaki Y, Heo L, Ovchinnikov S, Steinegger M. ColabFold - Making protein folding accessible to all.
*bioRxiv*, 2021](https://www.biorxiv.org/content/10.1101/2021.08.15.456425v2) _____no_output_____

#@title Input protein sequence(s), then hit `Runtime` -> `Run all`
from google.colab import files
import os.path
import re
import hashlib
import random

def add_hash(x,y):
  return x+"_"+hashlib.sha1(y.encode()).hexdigest()[:5]

query_sequence = 'PIAQIHILEGRSDEQKETLIREVSEAISRSLDAPLTSVRVIITEMAKGHFGIGGELASK' #@param {type:"string"}
#@markdown  - Use `:` to specify inter-protein chainbreaks for **modeling complexes** (supports homo- and hetro-oligomers). For example **PI...SK:PI...SK** for a homodimer

# remove whitespaces
query_sequence = "".join(query_sequence.split())

jobname = 'test' #@param {type:"string"}
# remove whitespaces
basejobname = "".join(jobname.split())
basejobname = re.sub(r'\W+', '', basejobname)
jobname = add_hash(basejobname, query_sequence)
while os.path.isfile(f"{jobname}.csv"):
  jobname = add_hash(basejobname, ''.join(random.sample(query_sequence,len(query_sequence))))

with open(f"{jobname}.csv", "w") as text_file:
    text_file.write(f"id,sequence\n{jobname},{query_sequence}")

queries_path=f"{jobname}.csv"

# number of models to use
use_amber = False #@param {type:"boolean"}
template_mode = "none" #@param ["none", "pdb70","custom"]
#@markdown - "none" = no template information is used, "pdb70" = detect templates in pdb70, "custom" - upload and search own templates (mmCIF format)

if template_mode == "pdb70":
  use_templates = True
  custom_template_path = None
elif template_mode == "custom":
  custom_template_path = f"{jobname}_template"
  os.mkdir(custom_template_path)
  uploaded = files.upload()
  use_templates = True
  for fn in uploaded.keys():
    os.rename(fn, f"{jobname}_template/{fn}")
else:
  custom_template_path = None
  use_templates = False
_____no_output_____#@markdown ### MSA options (custom MSA upload, single sequence, pairing mode)
msa_mode = "MMseqs2 (UniRef+Environmental)" #@param ["MMseqs2 (UniRef+Environmental)", "MMseqs2 (UniRef only)","single_sequence","custom"]
pair_mode = "unpaired+paired" #@param ["unpaired+paired","paired","unpaired"] {type:"string"}
#@markdown - "unpaired+paired" = pair sequences from same species + unpaired MSA, "unpaired" = seperate MSA for each chain, "paired" - only use paired sequences.

# decide which a3m to use
if msa_mode.startswith("MMseqs2"):
  a3m_file = f"{jobname}.a3m"
elif msa_mode == "custom":
  a3m_file = f"{jobname}.custom.a3m"
  if not os.path.isfile(a3m_file):
    custom_msa_dict = files.upload()
    custom_msa = list(custom_msa_dict.keys())[0]
    header = 0
    import fileinput
    for line in fileinput.FileInput(custom_msa,inplace=1):
      if line.startswith(">"):
         header = header + 1
      if not line.rstrip():
        continue
      if line.startswith(">") == False and header == 1:
         query_sequence = line.rstrip()
      print(line, end='')

    os.rename(custom_msa, a3m_file)
    queries_path=a3m_file
    print(f"moving {custom_msa} to {a3m_file}")
else:
  a3m_file = f"{jobname}.single_sequence.a3m"
  with open(a3m_file, "w") as text_file:
    text_file.write(">1\n%s" % query_sequence)_____no_output_____#@markdown ### Advanced settings
model_type = "auto" #@param ["auto", "AlphaFold2-ptm", "AlphaFold2-multimer-v1", "AlphaFold2-multimer-v2"]
num_recycles = 3 #@param [1,3,6,12,24,48] {type:"raw"}
#@markdown  - "auto" = protein structure prediction using "AlphaFold2-ptm" and complex prediction "AlphaFold-multimer-v2". For complexes "AlphaFold-multimer-v[1,2]" and "AlphaFold-ptm" can be used.
save_to_google_drive = False #@param {type:"boolean"}
#@markdown -  if the save_to_google_drive option was selected, the result zip will be uploaded to your Google Drive

#@markdown Don't forget to hit `Runtime` -> `Run all` after updating the form.

if save_to_google_drive:
  from pydrive.drive import GoogleDrive
  from pydrive.auth import GoogleAuth
  from google.colab import auth
  from oauth2client.client import GoogleCredentials
  auth.authenticate_user()
  gauth = GoogleAuth()
  gauth.credentials = GoogleCredentials.get_application_default()
  drive = GoogleDrive(gauth)
  print("You are logged into Google Drive and are good to go!")_____no_output_____#@title Install dependencies
%%bash -s $use_amber $use_templates

set -e

USE_AMBER=$1
USE_TEMPLATES=$2

if [ ! -f COLABFOLD_READY ]; then
  # install dependencies
  # We have to use "--no-warn-conflicts" because colab already has a lot preinstalled with requirements different to ours
  pip install --no-warn-conflicts -q "colabfold[alphafold] @ git+https://github.com/sokrypton/ColabFold"
  touch COLABFOLD_READY
fi

# setup conda
if [ ${USE_AMBER} == "True" ] || [ ${USE_TEMPLATES} == "True" ]; then
  if [ ! -f CONDA_READY ]; then
    wget -qnc https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
    bash Miniconda3-latest-Linux-x86_64.sh -bfp /usr/local 2>&1 1>/dev/null
    rm Miniconda3-latest-Linux-x86_64.sh
    touch CONDA_READY
  fi
fi
# setup template search
if [ ${USE_TEMPLATES} == "True" ] && [ ! -f HH_READY ]; then
  conda install -y -q -c conda-forge -c bioconda kalign2=2.04 hhsuite=3.3.0 python=3.7 2>&1 1>/dev/null
  touch HH_READY
fi
# setup openmm for amber refinement
if [ ${USE_AMBER} == "True" ] && [ ! -f AMBER_READY ]; then
  conda install -y -q -c conda-forge openmm=7.5.1 python=3.7 pdbfixer 2>&1 1>/dev/null
  wget -qnc https://raw.githubusercontent.com/deepmind/alphafold/main/docker/openmm.patch
  (cd /usr/local/lib/python3.7/site-packages; patch -s -p0 < /content/openmm.patch)
  wget -qnc https://git.scicore.unibas.ch/schwede/openstructure/-/raw/7102c63615b64735c4941278d92b554ec94415f8/modules/mol/alg/src/stereo_chemical_props.txt
  touch AMBER_READY
fi_____no_output_____#@title Run Prediction

import sys

from colabfold.download import download_alphafold_params, default_data_dir
from colabfold.utils import setup_logging
from colabfold.batch import get_queries, run, set_model_type
K80_chk = !nvidia-smi | grep "Tesla K80" | wc -l
if "1" in K80_chk:
  print("WARNING: found GPU Tesla K80: limited to total length < 1000")
  if "TF_FORCE_UNIFIED_MEMORY" in os.environ:
    del os.environ["TF_FORCE_UNIFIED_MEMORY"]
  if "XLA_PYTHON_CLIENT_MEM_FRACTION" in os.environ:
    del os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"]

from colabfold.colabfold import plot_protein
from pathlib import Path
import matplotlib.pyplot as plt

# For some reason we need that to get pdbfixer to import
if use_amber and '/usr/local/lib/python3.7/site-packages/' not in sys.path:
    sys.path.insert(0, '/usr/local/lib/python3.7/site-packages/')

def prediction_callback(unrelaxed_protein, length, prediction_result, input_features):
  fig = plot_protein(unrelaxed_protein, Ls=length, dpi=100)
  plt.show()
  plt.close()

result_dir="."
setup_logging(Path(".").joinpath("log.txt"))
queries, is_complex = get_queries(queries_path)
model_type = set_model_type(is_complex, model_type)
download_alphafold_params(model_type, Path("."))
run(
    queries=queries,
    result_dir=result_dir,
    use_templates=use_templates,
    custom_template_path=custom_template_path,
    use_amber=use_amber,
    msa_mode=msa_mode,
    model_type=model_type,
    num_models=5,
    num_recycles=num_recycles,
    model_order=[1, 2, 3, 4, 5],
    is_complex=is_complex,
    data_dir=Path("."),
    keep_existing_results=False,
    recompile_padding=1.0,
    rank_by="auto",
    pair_mode=pair_mode,
    stop_at_score=float(100),
    prediction_callback=prediction_callback,
)_____no_output_____#@title Display 3D structure {run: "auto"}
import py3Dmol
import glob
import matplotlib.pyplot as plt
rank_num = 1 #@param ["1", "2", "3", "4", "5"] {type:"raw"}
color = "lDDT" #@param ["chain", "lDDT", "rainbow"]
show_sidechains = False #@param {type:"boolean"}
show_mainchains = False #@param {type:"boolean"}

jobname_prefix = ".custom" if msa_mode == "custom" else ""
if use_amber:
  pdb_filename = f"{jobname}{jobname_prefix}_relaxed_rank_{rank_num}_model_*.pdb"
else:
  pdb_filename = f"{jobname}{jobname_prefix}_unrelaxed_rank_{rank_num}_model_*.pdb"

pdb_file = glob.glob(pdb_filename)

def show_pdb(rank_num=1, show_sidechains=False, show_mainchains=False, color="lDDT"):
  model_name = f"rank_{rank_num}"
  view = py3Dmol.view(js='https://3dmol.org/build/3Dmol.js',)
  view.addModel(open(pdb_file[0],'r').read(),'pdb')

  if color == "lDDT":
    view.setStyle({'cartoon': {'colorscheme': {'prop':'b','gradient': 'roygb','min':50,'max':90}}})
  elif color == "rainbow":
    view.setStyle({'cartoon': {'color':'spectrum'}})
  elif color == "chain":
    chains = len(queries[0][1]) + 1 if is_complex else 1
    for n,chain,color in zip(range(chains),list("ABCDEFGH"),
                     ["lime","cyan","magenta","yellow","salmon","white","blue","orange"]):
      view.setStyle({'chain':chain},{'cartoon': {'color':color}})
  if show_sidechains:
    BB = ['C','O','N']
    view.addStyle({'and':[{'resn':["GLY","PRO"],'invert':True},{'atom':BB,'invert':True}]},
                        {'stick':{'colorscheme':f"WhiteCarbon",'radius':0.3}})
    view.addStyle({'and':[{'resn':"GLY"},{'atom':'CA'}]},
                        {'sphere':{'colorscheme':f"WhiteCarbon",'radius':0.3}})
    view.addStyle({'and':[{'resn':"PRO"},{'atom':['C','O'],'invert':True}]},
                        {'stick':{'colorscheme':f"WhiteCarbon",'radius':0.3}})
  if show_mainchains:
    BB = ['C','O','N','CA']
    view.addStyle({'atom':BB},{'stick':{'colorscheme':f"WhiteCarbon",'radius':0.3}})

  view.zoomTo()
  return view

show_pdb(rank_num,show_sidechains, show_mainchains, color).show()

_____no_output_____#@title Plots {run: "auto"}
from IPython.display import display, HTML
import base64
from html import escape

# see: https://stackoverflow.com/a/53688522
def image_to_data_url(filename):
  ext = filename.split('.')[-1]
  prefix = f'data:image/{ext};base64,'
  with open(filename, 'rb') as f:
    img = f.read()
  return prefix + base64.b64encode(img).decode('utf-8')

pae = image_to_data_url(f"{jobname}{jobname_prefix}_PAE.png")
cov = image_to_data_url(f"{jobname}{jobname_prefix}_coverage.png")
plddt = image_to_data_url(f"{jobname}{jobname_prefix}_plddt.png")
display(HTML(f"""

Plots for {escape(jobname)}

"""))
_____no_output_____#@title Package and download results
#@markdown If you are having issues downloading the result archive, try disabling your adblocker and run this cell again. If that fails click on the little folder icon to the left, navigate to file: `jobname.result.zip`, right-click and select \"Download\" (see [screenshot](https://pbs.twimg.com/media/E6wRW2lWUAEOuoe?format=jpg&name=small)).

if msa_mode == "custom":
  print("Don't forget to cite your custom MSA generation method.")

!zip -FSr $jobname".result.zip" config.json $jobname*".json" $jobname*".a3m" $jobname*"relaxed_rank_"*".pdb" "cite.bibtex" $jobname*".png"
files.download(f"{jobname}.result.zip")

if save_to_google_drive == True and drive:
  uploaded = drive.CreateFile({'title': f"{jobname}.result.zip"})
  uploaded.SetContentFile(f"{jobname}.result.zip")
  uploaded.Upload()
  print(f"Uploaded {jobname}.result.zip to Google Drive with ID {uploaded.get('id')}")_____no_output_____

# Instructions
**Quick start**
1. Paste your protein sequence(s) in the input field.
2. Press "Runtime" -> "Run all".
3. The pipeline consists of 5 steps. The currently running steps is indicated by a circle with a stop sign next to it.

**Result zip file contents**

1. PDB formatted structures sorted by avg. pLDDT and complexes are sorted by pTMscore. (unrelaxed and relaxed if `use_amber` is enabled).
2. Plots of the model quality.
3. Plots of the MSA coverage.
4. Parameter log file.
5. A3M formatted input MSA.
6. A `predicted_aligned_error_v1.json` using [AlphaFold-DB's format](https://alphafold.ebi.ac.uk/faq#faq-7) and a `scores.json` for each model which contains an array (list of lists) for PAE, a list with the averagge pLDDT and the pTMscore.
7. BibTeX file with citations for all used tools and databases.

At the end of the job a download modal box will pop up with a `jobname.result.zip` file. Additionally, if the `save_to_google_drive` option was selected, the `jobname.result.zip` will be uploaded to your Google Drive.

**MSA generation for complexes**

For the complex prediction we use unpaired and paired MSAs. Unpaired MSA is generated the same way as for the protein structures prediction by searching the UniRef100 and environmental sequences three iterations each.

The paired MSA is generated by searching the UniRef100 database and pairing the best hits sharing the same NCBI taxonomical identifier (=species or sub-species). We only pair sequences if all of the query sequences are present for the respective taxonomical identifier.

**Using a custom MSA as input**

To predict the structure with a custom MSA (A3M formatted): (1) Change the `msa_mode`: to "custom", (2) Wait for an upload box to appear at the end of the "MSA options ..." box. Upload your A3M. The first fasta entry of the A3M must be the query sequence without gaps.

As an alternative for MSA generation the [HHblits Toolkit server](https://toolkit.tuebingen.mpg.de/tools/hhblits) can be used. After submitting your query, click "Query Template MSA" -> "Download Full A3M". Download the A3M file and upload it in this notebook.

**Using custom templates**

To predict the structure with a custom templates (mmCIF formatted): (1) change the `template_mode` to "custom" execture cell and (2) wait for an upload box to appear at the end of the "Input Protein" box. Select and upload your templates (multiple choices are possible). Templates need to be in mmCIF format, its names must follow the PDB naming (four letters) and must contain `_entity_poly_seq` and `_pdbx_audit_revision_history.revision_date`. To convert a PDB file into a ColabFold compatible mmCIF see this [issue](https://github.com/sokrypton/ColabFold/issues/177).

**Comparision to the full AlphaFold2 and Alphafold2 colab**

This notebook replaces the homology detection and MSA pairing of AlphaFold2 with MMseqs2. For a comparision against the [AlphaFold2 Colab](https://colab.research.google.com/github/deepmind/alphafold/blob/main/notebooks/AlphaFold.ipynb) and the full [AlphaFold2](https://github.com/deepmind/alphafold) system read our [preprint](https://www.biorxiv.org/content/10.1101/2021.08.15.456425v1).

**Troubleshooting**
* Check that the runtime type is set to GPU at "Runtime" -> "Change runtime type".
* Try to restart the session "Runtime" -> "Factory reset runtime".
* Check your input sequence.

**Known issues**
* Google Colab assigns different types of GPUs with varying amount of memory. Some might not have enough memory to predict the structure for a long sequence.
* Your browser can block the pop-up for downloading the result file. You can choose the `save_to_google_drive` option to upload to Google Drive instead or manually download the result file: Click on the little folder icon to the left, navigate to file: `jobname.result.zip`, right-click and select \"Download\" (see [screenshot](https://pbs.twimg.com/media/E6wRW2lWUAEOuoe?format=jpg&name=small)).

**Limitations**
* Computing resources: Our MMseqs2 API can handle ~20-50k requests per day.
* MSAs: MMseqs2 is very precise and sensitive but might find less hits compared to HHblits/HMMer searched against BFD or Mgnify.
* We recommend to additionally use the full [AlphaFold2 pipeline](https://github.com/deepmind/alphafold).

**Description of the plots**
*   **Number of sequences per position** - We want to see at least 30 sequences per position, for best performance, ideally 100 sequences.
*   **Predicted lDDT per position** - model confidence (out of 100) at each position. The higher the better.
*   **Predicted Alignment Error** - For homooligomers, this could be a useful metric to assess how confident the model is about the interface. The lower the better.

**Bugs**
- If you encounter any bugs, please report the issue to https://github.com/sokrypton/ColabFold/issues

**License**

The source code of ColabFold is licensed under [MIT](https://raw.githubusercontent.com/sokrypton/ColabFold/main/LICENSE). Additionally, this notebook uses the AlphaFold2 source code and its parameters licensed under [Apache 2.0](https://raw.githubusercontent.com/deepmind/alphafold/main/LICENSE) and [CC BY 4.0](https://creativecommons.org/licenses/by-sa/4.0/) respectively. Read more about the AlphaFold license [here](https://github.com/deepmind/alphafold).

**Acknowledgments**
- We thank the AlphaFold team for developing an excellent model and open sourcing the software.

- [Söding Lab](https://www.mpibpc.mpg.de/soeding) for providing the computational resources for the MMseqs2 server

- Richard Evans for helping to bechmark the ColabFold's Alphafold-multimer support

- [David Koes](https://github.com/dkoes) for his awesome [py3Dmol](https://3dmol.csb.pitt.edu/) plugin, without whom these notebooks would be quite boring!

- Do-Yoon Kim for creating the ColabFold logo.

- A colab by Sergey Ovchinnikov ([@sokrypton](https://twitter.com/sokrypton)), Milot Mirdita ([@milot_mirdita](https://twitter.com/milot_mirdita)) and Martin Steinegger ([@thesteinegger](https://twitter.com/thesteinegger)).
_____no_output_____


# Notebook from Steve-Hawk/nrpytutorial
Path: in_progress/Tutorial-GiRaFFEfood_NRPy_Aligned_Rotator.ipynb

# `GiRaFFEfood`: Initial data for `GiRaFFE` Aligned Rotator

## Author: Zach Etienne & Patrick Nelson
### Formatting improvements courtesy Brandon Clark

[comment]: <> (Abstract: TODO)

**Notebook Status:**  Self-Validated

**Validation Notes:** This tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented [below](#code_validation). **Additional validation tests may have been performed, but are as yet, undocumented. (TODO)**

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

## Introduction:

This module provides another initial data option for $\texttt{GiRaFFE}$. This is a flat-spacetime test with initial data $$A_{\phi} = \frac{\mu \varpi^2}{r^3},$$ where  $\mu = B_p R^3_{\rm NS} / 2$, $R_{\rm NS}$ is the neutron star radius, and $\varpi = \sqrt{x^2+y^2}$ is the cylindrical radius. We let $A_r = A_\theta = 0$.

Additionally, the drift velocity $v^i = \Omega \textbf{e}_z \times \textbf{r} = [ijk] \Omega \textbf{e}^j_z x^k$, where $[ijk]$ is the Levi-Civita permutation symbol and $\textbf{e}^i_z = (0,0,1)$. See the [Tutorial-GiRaFFEfood_NRPy](Tutorial-GiRaFFEfood_NRPy.ipynb) tutorial notebook for details on how this is used._____no_output_____

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

This notebook is organized as follows

1. [Step 1](#initializenrpy): Import core NRPy+ modules and set NRPy+ parameters
1. [Step 2](#set_aphi): Set the vector $A_{\phi}$ Spherical coordinates
1. [Step 3](#jacobian): Use the Jacobian matrix to transform the vectors to Cartesian coordinates
1. [Step 4](#vi): Calculate $v^i$
1. [Step 5](#code_validation): Code Validation against `GiRaFFEfood_NRPy.GiRaFFEfood_NRPy_Aligned_Rotator` NRPy+ Module
1. [Step 6](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file_____no_output_____

# Step 1: Import core NRPy+ modules and set NRPy+ parameters \[Back to [top](#toc)\]
$$\label{initializenrpy}$$

Here, we will import the NRPy+ core modules and set the reference metric to Cartesian, set commonly used NRPy+ parameters, and set C parameters that will be set from outside the code eventually generated from these expressions. We will also set up a parameter to determine what initial data is set up, although it won't do much yet._____no_output_____

# 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)

# Step 0.a: Import the NRPy+ core modules and set the reference metric to Cartesian
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 indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support

import reference_metric as rfm
par.set_parval_from_str("reference_metric::CoordSystem","Cartesian")
rfm.reference_metric()

# Step 1a: Set commonly used parameters.
thismodule = "GiRaFFEfood_NRPy_Aligned_Rotator"

# The angular velocity of the "neutron star"
Omega_aligned_rotator = par.Cparameters("REAL",thismodule,"Omega_aligned_rotator",1e3)
B_p_aligned_rotator,R_NS_aligned_rotator = par.Cparameters("REAL",thismodule,
                                                           # B_p_aligned_rotator = the intensity of the magnetic field and
                                                           # R_NS_aligned_rotator= "Neutron star" radius
                                                           ["B_p_aligned_rotator","R_NS_aligned_rotator"],
                                                           [1e-5, 1.0])_____no_output_____

# Step 2: Set the vector $A_{\phi}$ in Spherical coordinates \[Back to [top](#toc)\]
$$\label{set_aphi}$$

We will first build the fundamental vector $A_i$ in spherical coordinates (see [Table 3](https://arxiv.org/pdf/1704.00599.pdf)). Note that we use [reference_metric.py](../edit/reference_metric.py) to set $r$ and $\theta$ in terms of Cartesian coordinates; this will save us a step later when we convert to Cartesian coordinates. So, we set
\begin{align}
A_{\phi} &= \frac{\mu \varpi^2}{r^3}, \\
\end{align}
with $\mu = B_p R^3_{\rm NS} / 2$, $R_{\rm NS}$ is the neutron star radius, and $\varpi = \sqrt{x^2+y^2}$
_____no_output_____

r     = rfm.xxSph[0]
varpi = sp.sqrt(rfm.xxCart[0]**2 + rfm.xxCart[1]**2)

mu = B_p_aligned_rotator * R_NS_aligned_rotator**3 / 2

ASphD = ixp.zerorank1()

ASphD[2] = mu * varpi**2 / (r**3) # The other components were already declared to be 0.
_____no_output_____

# Step 3: Use the Jacobian matrix to transform the vector to Cartesian coordinates \[Back to [top](#toc)\]
$$\label{jacobian}$$

Now, we will use the coordinate transformation definitions provided by [reference_metric.py](../edit/reference_metric.py) to build the Jacobian
$$
\frac{\partial x_{\rm Sph}^j}{\partial x_{\rm Cart}^i},
$$
where $x_{\rm Sph}^j \in \{r,\theta,\phi\}$ and $x_{\rm Cart}^i \in \{x,y,z\}$. We would normally compute its inverse, but since none of the quantities we need to transform have upper indices, it is not necessary. Then, since $A_i$ and has one lower index, it will need to be multiplied by the Jacobian:

$$
A_i^{\rm Cart} = A_j^{\rm Sph} \frac{\partial x_{\rm Sph}^j}{\partial x_{\rm Cart}^i},
$$_____no_output_____

# Step 3: Use the Jacobian matrix to transform the vectors to Cartesian coordinates.
drrefmetric__dx_0UDmatrix = sp.Matrix([[sp.diff(rfm.xxSph[0],rfm.xx[0]), sp.diff(rfm.xxSph[0],rfm.xx[1]), sp.diff(rfm.xxSph[0],rfm.xx[2])],
                                       [sp.diff(rfm.xxSph[1],rfm.xx[0]), sp.diff(rfm.xxSph[1],rfm.xx[1]), sp.diff(rfm.xxSph[1],rfm.xx[2])],
                                       [sp.diff(rfm.xxSph[2],rfm.xx[0]), sp.diff(rfm.xxSph[2],rfm.xx[1]), sp.diff(rfm.xxSph[2],rfm.xx[2])]])
#dx__drrefmetric_0UDmatrix = drrefmetric__dx_0UDmatrix.inv() # We don't actually need this in this case.

AD = ixp.zerorank1(DIM=3)
for i in range(3):
    for j in range(3):
        AD[i] = drrefmetric__dx_0UDmatrix[(j,i)]*ASphD[j]
_____no_output_____

# Step 4: Calculate $v^i$ \[Back to [top](#toc)\]
$$\label{vi}$$

Here, we will calculate the drift velocity $v^i = \Omega \textbf{e}_z \times \textbf{r} = [ijk] \Omega \textbf{e}^j_z x^k$, where $[ijk]$ is the Levi-Civita permutation symbol and $\textbf{e}^i_z = (0,0,1)$. Conveniently, in flat space, the drift velocity reduces to the Valencia velocity because $\alpha = 1$ and $\beta^i = 0$.

However, we need to set the velocity to zero outside the neutron star; this is equivalent to only setting the non-zero velocity inside the star, $r \leq R_{\rm NS}$_____no_output_____

# Step 4: Calculate v^i
# Here, we build the Levi-Civita tensor from the Levi-Civita symbol.
import WeylScal4NRPy.WeylScalars_Cartesian as weyl
LeviCivitaSymbolDDD = weyl.define_LeviCivitaSymbol_rank3()

import Min_Max_and_Piecewise_Expressions as noif

unit_zU = ixp.zerorank1()
unit_zU[2] = sp.sympify(1)

global ValenciavU
ValenciavU = ixp.zerorank1()
for i in range(3):
    for j in range(3):
        for k in range(3):
            ValenciavU[i] += noif.coord_leq_bound(r,R_NS_aligned_rotator)*LeviCivitaSymbolDDD[i][j][k] * Omega_aligned_rotator * unit_zU[j] * rfm.xx[k]
_____no_output_____

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

Here, as a code validation check, we verify agreement in the SymPy expressions for the $\texttt{GiRaFFE}$ Aligned Rotator initial data equations we intend to use between

1. this tutorial and
2. the NRPy+ [GiRaFFEfood_NRPy.GiRaFFEfood_NRPy_Aligned_Rotator](../edit/GiRaFFEfood_NRPy/GiRaFFEfood_NRPy_Aligned_Rotator.py) module._____no_output_____

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

import GiRaFFEfood_NRPy.GiRaFFEfood_NRPy_Aligned_Rotator as gfho
gfho.GiRaFFEfood_NRPy_Aligned_Rotator()

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

for i in range(3):

    print("ValenciavU["+str(i)+"] - gfho.ValenciavU["+str(i)+"] = \t" + str(ValenciavU[i] - gfho.ValenciavU[i]))
    print("AD["+str(i)+"] - gfho.AD["+str(i)+"] = \t\t\t" + str(AD[i] - gfho.AD[i]))Consistency check between GiRaFFEfood_NRPy tutorial and NRPy+ module: ALL SHOULD BE ZERO.
ValenciavU[0] - gfho.ValenciavU[0] = 	0
AD[0] - gfho.AD[0] = 			0
ValenciavU[1] - gfho.ValenciavU[1] = 	0
AD[1] - gfho.AD[1] = 			0
ValenciavU[2] - gfho.ValenciavU[2] = 	0
AD[2] - gfho.AD[2] = 			0

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

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

!jupyter nbconvert --to latex --template latex_nrpy_style.tplx --log-level='WARN' Tutorial-GiRaFFEfood_NRPy_Aligned_Rotator.ipynb
!pdflatex -interaction=batchmode Tutorial-GiRaFFEfood_NRPy_Aligned_Rotator.tex
!pdflatex -interaction=batchmode Tutorial-GiRaFFEfood_NRPy_Aligned_Rotator.tex
!pdflatex -interaction=batchmode Tutorial-GiRaFFEfood_NRPy_Aligned_Rotator.tex
!rm -f Tut*.out Tut*.aux Tut*.logThis 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