# Tutorial-IllinoisGRMHD: The conservative to primitive algorithm

## Authors: Leo Werneck & Zach Etienne

**This module is currently under development**

## In this tutorial module we explain the algorithm used to get the primitive variables out of the conservative ones

### Required and recommended citations:

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

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

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

### Dependencies

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

* `IllinoisGRMHD_headers.h` \[[**tutorial**](Tutorial-IllinoisGRMHD__IllinoisGRMHD_headers.ipynb)\]
* `apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C` \[[**tutorial**](Tutorial-IllinoisGRMHD__apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.ipynb)\]
* `IllinoisGRMHD_convert_ADM_to_BSSN__enforce_detgtij_eq_1__and_compute_gtupij.C` \[[**tutorial**](Tutorial-IllinoisGRMHD__convert_ADM_to_BSSN__enforce_detgtij_eq_1__and_compute_gtupij.ipynb)\]
* `IllinoisGRMHD_EoS_lowlevel_functs.C` \[[**tutorial**](Tutorial-IllinoisGRMHD__EoS_lowlevel_functs.ipynb)\]
* `harm_utoprim_2d.c`  \[[**tutorial**](Tutorial-IllinoisGRMHD__harm_utoprim_2d.ipynb)\]
* `harm_u2p_util.c`  \[[**tutorial**](Tutorial-IllinoisGRMHD__harm_u2p_util.ipynb)\]

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

This module is organized as follows

0. [Step 0](#src_dir): **Source directory creation**
1. [Step 1](#introduction): **Introduction**
1. [Step 2](#driver_conserv_to_prims): **`driver_conserv_to_prims.C`**
    1. [Step 2.a](#equatorial_symmetry): *Applying equatorial symmetry*
    1. [Step 2.b](#c2p): *Conservative-to-primitives*
        1. [Step 2.b.i](#variable_setup__prims_conservs): Reading in primitives and conservatives
        1. [Step 2.b.ii](#variable_setup__adm_3metric): Reading in the physical metric
        1. [Step 2.b.iii](#variable_setup__temp_conservs): Temporary storage for current values of the conservative variables
        1. [Step 2.b.iv](#c2p_fct_call): Invoking the conservative-to-primitive algorithm
        1. [Step 2.b.v](#standalone__update_prims_conservs): Standalone routine: updating conservatives and primitives

    1. [Step 2.c](#updating_tdnmunu): *Updating $T_{\mu\nu}^{\rm GRMHD}$*
    1. [Step 2.d](#diagnostics_and_debugging_tools): *Diagnostics and debugging tools*
1. [Step 3](#noble2d_con2prim_onepoint): **The `IllinoisGRMHD_Noble2D_con2prim_onepoint.C` file**
    1. [Step 3.a](#compute_sqrtgamma_gammainv): *Computing $\sqrt{\gamma}$ and $\gamma^{ij}$ from ADM variables*
    1. [Step 3.b](#compute_g4dn): *Computing $g_{\mu\nu}$ from ADM variables*
    1. [Step 3.c](#compute_g4up): *Computing $g^{\mu\nu}$ from ADM variables*
    1. [Step 3.d](#c2p): *Compute primitives from conservatives*
1. [Step 4](#harm_primitives_lowlevel): **`harm_primitives_lowlevel.C`**
    1. [Step 4.a](#variables_needed_by_harm__bi_harm): *Computing $B^{i}_{\rm HARM}$*
    1. [Step 4.b](#variables_needed_by_harm__init_rhob_pressure_vi): *Initializing $\rho_{b}$, $P$, and $v^{i}$*
    1. [Step 4.c](#variables_needed_by_harm__original_conserv): *Storing the original values of the conservative variables*
    1. [Step 4.d](#variables_needed_by_harm__guessing_rhob_pressure_vi): *Guessing $\rho_{b}$, $P$, and $v^{i}$*
    1. [Step 4.e](#variables_needed_by_harm__conservs): *Writing $\boldsymbol{C}_{\rm HARM}$ in terms of $\boldsymbol{C}_{\rm IGM}$*
    1. [Step 4.f](#variables_needed_by_harm__prims): *Writing $\boldsymbol{P}_{\rm HARM}$ in terms of $\boldsymbol{P}_{\rm IGM}$*
    1. [Step 4.g](#calling_harm_conservs_to_prims_solver): *Calling the `HARM` conservative-to-primitive solver*
    1. [Step 4.h](#font_fix): *Applying the Font *et al.* fix, if the inversion fails*
    1. [Step 4.i](#compute_utconi): *Compute $\tilde{u}^{i}$*
    1. [Step 4.j](#limiting_velocities): *Limiting velocities*
    1. [Step 4.k](#primitives): *Setting the primitives*
1. [Step 5](#font_fix_gamma_law__c): **`font_fix_gamma_law.C`**
    1. [Step 5.a](#font_fix_gamma_law__basic_quantities): Computing the basic quantities needed by the algorithm
    1. [Step 5.b](#font_fix_gamma_law__initial_guesses): Initial guesses for $W$, $S_{{\rm fluid}}^{2}$, and $\rho$
    1. [Step 5.c](#font_fix_main_loop): Font fix main loop
    1. [Step 5.d](#font_fix_gamma_law__outputs): Output $\rho_{b}$ and $u_{i}$
    1. [Step 5.e](#font_fix__rhob_loop): The `font_fix__rhob_loop()` function
1. [Step 6](#harm_primitives_headers): **`harm_primitives_headers.h`**
1. [Step 7](#code_validation): **Code validation**
    1. [Step 7.a](#driver_conserv_to_prims_validation): *`driver_conserv_to_prims.C`*
    1. [Step 7.b](#harm_primitives_lowlevel_validation): *`harm_primitives_lowlevel.C`*
    1. [Step 7.c](#font_fix_gamma_law_validation): *`font_fix_gamma_law.C`*
    1. [Step 7.d](#harm_primitives_headers_validation): *`harm_primitives_headers.h`*
1. [Step 8](#latex_pdf_output): **Output this notebook to $\LaTeX$-formatted PDF file**

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

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

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

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

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

# Step 2: `driver_conserv_to_prims.C` \[Back to [top](#toc)\]
$$\label{driver_conserv_to_prims}$$

We start here by creating the `driver_conserv_to_prims.C` file and loading all files used by it. Note that of the files loaded, we have the following `IllinoisGRMHD` files:

1. `harm_primitives_headers.h`: we will discuss this file [in step 4 of this tutorial module](#harm_primitives_headers).
1. `apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C`: this file is discussed in the [apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs NRPy tutorial module](Tutorial-IllinoisGRMHD__apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.ipynb)

```python
%%writefile $outdir/driver_conserv_to_prims.C
/* We evolve forward in time a set of functions called the
 *  "conservative variables", and any time the conserv's
 *  are updated, we must solve for the primitive variables
 *  (rho, pressure, velocities) using a Newton-Raphson
 *  technique, before reconstructing & evaluating the RHSs
 *  of the MHD equations again.
 *
 * This file contains the driver routine for this Newton-
 *  Raphson solver. Truncation errors in conservative
 *  variables can lead to no physical solutions in
 *  primitive variables. We correct for these errors here
 *  through a number of tricks described in the appendices
 *  of http://arxiv.org/pdf/1112.0568.pdf.
 *
 * This is a wrapper for the 2d solver of Noble et al. See
 *  harm_utoprim_2d.c for references and copyright notice
 *  for that solver. This wrapper was primarily written by
 *  Zachariah Etienne & Yuk Tung Liu, in 2011-2013.
 *
 * For optimal compatibility, this wrapper is licensed under
 *  the GPL v2 or any later version.
 *
 * Note that this code assumes a simple gamma law for the
 *  moment, though it would be easy to extend to a piecewise
 *  polytrope. */

// Standard #include's
#include <iostream>
#include <iomanip>
#include <fstream>
#include <cmath>
#include <ctime>
#include <cstdlib>

#ifdef ENABLE_STANDALONE_IGM_C2P_SOLVER
#include "standalone_conserv_to_prims_main_function.h"
#else
#include "cctk.h"
#include "cctk_Arguments.h"
#include "cctk_Parameters.h"
#include "Symmetry.h"

#include "IllinoisGRMHD_headers.h"
#include "harm_primitives_headers.h"
#include "harm_u2p_util.c"
#include "apply_tau_floor__enforce_limits_on_primitives_and_recompute_conservs.C"
#include "IllinoisGRMHD_Noble2D_con2prim_onepoint.C"

extern "C" void IllinoisGRMHD_conserv_to_prims(CCTK_ARGUMENTS) {
  DECLARE_CCTK_ARGUMENTS;
  DECLARE_CCTK_PARAMETERS;

  // We use proper C++ here, for file I/O later.
  using namespace std;
#endif

  /**********************************
   * Piecewise Polytropic EOS Patch *
   *   Setting up the EOS struct    *
   **********************************/
  /*
   * The short piece of code below takes care of
   * initializing the EOS parameters. Please refer
   * to the "IllinoisGRMHD_EoS_lowlevel_functs.C"
   * source file for the documentation on the
   * function.
   */
  eos_struct eos;
  initialize_EOS_struct_from_input(eos);
```

    Overwriting ../src/driver_conserv_to_prims.C

## Step 2.a: Applying equatorial symmetry \[Back to [top](#toc)\]
$$\label{equatorial_symmetry}$$

We then use the [CardGrid3D ETK thorn](https://einsteintoolkit.org/thornguide/CactusBase/CartGrid3D/documentation.html) to apply equatorial symmetry to our problem.

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

#ifndef ENABLE_STANDALONE_IGM_C2P_SOLVER
  if(CCTK_EQUALS(Symmetry,"equatorial")) {
    // SET SYMMETRY GHOSTZONES ON ALL CONSERVATIVE VARIABLES!
    int ierr=0;
    ierr+=CartSymGN(cctkGH,"IllinoisGRMHD::grmhd_conservatives");
    // FIXME: UGLY. Filling metric ghostzones is needed for, e.g., Cowling runs.
    ierr+=CartSymGN(cctkGH,"lapse::lapse_vars");
    ierr+=CartSymGN(cctkGH,"bssn::BSSN_vars");
    ierr+=CartSymGN(cctkGH,"bssn::BSSN_AH");
    ierr+=CartSymGN(cctkGH,"shift::shift_vars");
    if(ierr!=0) CCTK_VError(VERR_DEF_PARAMS,"IllinoisGRMHD ERROR (grep for it, foo!)  :(");
  }
#endif
```

    Appending to ../src/driver_conserv_to_prims.C

## Step 2.b: Setting up the variables needed by `HARM` \[Back to [top](#toc)\]
$$\label{variable_setup}$$

We will now set up all the necessary variables to start the conservative to primitive algorithm. We begin by declaring useful debugging variables.

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

  //Start the timer, so we can benchmark the primitives solver during evolution.
  //  Slower solver -> harder to find roots -> things may be going crazy!
  //FIXME: Replace this timing benchmark with something more meaningful, like the avg # of Newton-Raphson iterations per gridpoint!
  /*
    struct timeval start, end;
    long mtime, seconds, useconds;
    gettimeofday(&start, NULL);
  */

  int failures=0,font_fixes=0,vel_limited_ptcount=0;
  int pointcount=0;
  int failures_inhoriz=0;
  int pointcount_inhoriz=0;

  int pressure_cap_hit=0;

  CCTK_REAL error_int_numer=0,error_int_denom=0;

  int imin=0,jmin=0,kmin=0;
  int imax=cctk_lsh[0],jmax=cctk_lsh[1],kmax=cctk_lsh[2];

  int rho_star_fix_applied=0;
  long n_iter=0;
```

    Appending to ../src/driver_conserv_to_prims.C

### Step 2.b.i: Reading in primitives and conservatives \[Back to [top](#toc)\]
$$\label{variable_setup__prims_conservs}$$

We load our current known values for the primitive variables, $\left(\rho_{b}, P, v^{i}, B^{i}\right)$, into a new array called $\rm PRIMS$, and the conservative variables, $\left(\rho_{\star}, \tilde{S}_{i},\tilde\tau\right)$, into a new array called $\rm CONSERVS$. Note that this is a ***pointwise*** operation.

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

#pragma omp parallel for reduction(+:failures,vel_limited_ptcount,font_fixes,pointcount,failures_inhoriz,pointcount_inhoriz,error_int_numer,error_int_denom,pressure_cap_hit,rho_star_fix_applied,n_iter) schedule(static)
  for(int k=kmin;k<kmax;k++)
    for(int j=jmin;j<jmax;j++)
      for(int i=imin;i<imax;i++) {
        int index = CCTK_GFINDEX3D(cctkGH,i,j,k);

        CCTK_REAL PRIMS[MAXNUMVARS];
        int ww=0;
        PRIMS[ww] = rho_b[index]; ww++;
        PRIMS[ww] = P[index];     ww++;
        PRIMS[ww] = vx[index];    ww++;
        PRIMS[ww] = vy[index];    ww++;
        PRIMS[ww] = vz[index];    ww++;
        PRIMS[ww] = Bx[index];    ww++;
        PRIMS[ww] = By[index];    ww++;
        PRIMS[ww] = Bz[index];    ww++;

        CCTK_REAL CONSERVS[NUM_CONSERVS] = {rho_star[index], mhd_st_x[index],mhd_st_y[index],mhd_st_z[index],tau[index]};
```

    Appending to ../src/driver_conserv_to_prims.C

### Step 2.b.ii: Reading in the physical metric \[Back to [top](#toc)\]
$$\label{variable_setup__adm_3metric}$$

Then we load our current known values for the ADM 3-metric variables, $\left(\alpha,\beta^{i},\gamma_{ij}\right)$. Note that this is a ***pointwise*** operation.

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

        CCTK_REAL ADM_3METRIC[NUMVARS_FOR_ADM_3METRIC];
        ADM_3METRIC[GAMMAXX] = gxx[index];
        ADM_3METRIC[GAMMAXY] = gxy[index];
        ADM_3METRIC[GAMMAXZ] = gxz[index];
        ADM_3METRIC[GAMMAYY] = gyy[index];
        ADM_3METRIC[GAMMAYZ] = gyz[index];
        ADM_3METRIC[GAMMAZZ] = gzz[index];

        ADM_3METRIC[ALPHA]   = alp[index];

        ADM_3METRIC[BETAX]   = betax[index];
        ADM_3METRIC[BETAY]   = betay[index];
        ADM_3METRIC[BETAZ]   = betaz[index];
```

    Appending to ../src/driver_conserv_to_prims.C

### Step 2.b.iii: Temporary storage for current values of the conservative variables \[Back to [top](#toc)\]
$$\label{variable_setup__temp_conservs}$$

Instead of declaring new variables to store the currently known values of the conservative variables $\left(\rho_{\star}, \tilde{S}_{i}, \tilde{\tau}\right)$, we will simply use the flux variables $\left(\rho_{\star}^{\rm flux}, \tilde{S}_{i}^{\rm flux}, \tilde{\tau}^{\rm flux}\right)$, which are used elsewhere in `IllinoisGRMHD`, as temporary storage. This is done for debugging purposes.

We also store the original values in the variables $\left(\rho_{\star}^{\rm orig}, \tilde{S}_{i}^{\rm orig}, \tilde{\tau}^{\rm orig}\right)$. Note that because we are now using the $\rm CONSERVS$ array, this is also a ***pointwise*** operation.

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

#ifndef ENABLE_STANDALONE_IGM_C2P_SOLVER
        // Here we use _flux variables as temp storage for original values of conservative variables.. This is used for debugging purposes only.
        rho_star_flux[index]    = CONSERVS[RHOSTAR];
        st_x_flux[index]        = CONSERVS[STILDEX];
        st_y_flux[index]        = CONSERVS[STILDEY];
        st_z_flux[index]        = CONSERVS[STILDEZ];
        tau_flux[index]         = CONSERVS[TAUENERGY];
#endif

        CCTK_REAL rho_star_orig = CONSERVS[RHOSTAR];
        CCTK_REAL mhd_st_x_orig = CONSERVS[STILDEX];
        CCTK_REAL mhd_st_y_orig = CONSERVS[STILDEY];
        CCTK_REAL mhd_st_z_orig = CONSERVS[STILDEZ];
        CCTK_REAL tau_orig      = CONSERVS[TAUENERGY];

        struct output_stats stats;
        stats.n_iter=0;
        stats.vel_limited=0;
        stats.failure_checker=0;
        stats.rho_star_fix_applied=0;

```

    Appending to ../src/driver_conserv_to_prims.C

### Step 2.b.iv: Invoking the conservative-to-primitive algorithm \[Back to [top](#toc)\]
$$\label{c2p_fct_call}$$

We now call the `IllinoisGRMHD_Noble2D_con2prim_onepoint()` function (documented [below](#noble2d_con2prim_onepoint)) to perform the conservative-to-primitive (C2P) operation.

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

        CCTK_REAL TUPMUNU[10],TDNMUNU[10];
        /*
        if(i==14 && j==14 && k==14) {
          printf("HEY1 %e %e %e %e %e\n",CONSERVS[RHOSTAR],CONSERVS[STILDEX],CONSERVS[STILDEY],CONSERVS[STILDEZ],CONSERVS[TAUENERGY]);
          printf("HEY2 %e %e %e %e %e %e\n",ADM_3METRIC[GAMMAXX],ADM_3METRIC[GAMMAXY],ADM_3METRIC[GAMMAXZ],ADM_3METRIC[GAMMAYY],ADM_3METRIC[GAMMAYZ],ADM_3METRIC[GAMMAZZ]);
        }
        */
        int check = IllinoisGRMHD_Noble2D_con2prim_onepoint(eos,stats, CONSERVS,ADM_3METRIC, TUPMUNU,TDNMUNU,PRIMS);
        /*
        if(i==14 && j==14 && k==14) {
          printf("HEY3 %e %e %e %e %e\n",PRIMS[RHOB],PRIMS[PRESSURE],PRIMS[VX],PRIMS[VY],PRIMS[VZ]);
          printf("HEY4 %e %e %e %e %e\n",CONSERVS[RHOSTAR],CONSERVS[STILDEX],CONSERVS[STILDEY],CONSERVS[STILDEZ],CONSERVS[TAUENERGY]);
        }
        */
        rho_star_fix_applied += stats.rho_star_fix_applied;
```

    Appending to ../src/driver_conserv_to_prims.C

### Step 2.b.v: Standalone routine: updating conservatives and primitives \[Back to [top](#toc)\]
$$\label{standalone__update_prims_conservs}$$

If the standalone flag is enabled, then we simply update the conservatives and primitives gridfunctions.

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

        // POST-C2P

#ifndef ENABLE_STANDALONE_IGM_C2P_SOLVER
        rho_star[index] = CONSERVS[RHOSTAR];
        mhd_st_x[index] = CONSERVS[STILDEX];
        mhd_st_y[index] = CONSERVS[STILDEY];
        mhd_st_z[index] = CONSERVS[STILDEZ];
        tau[index] = CONSERVS[TAUENERGY];

        // Set primitives, and/or provide a better guess.
        rho_b[index] = PRIMS[RHOB];
        P[index]     = PRIMS[PRESSURE];
        vx[index]    = PRIMS[VX];
        vy[index]    = PRIMS[VY];
        vz[index]    = PRIMS[VZ];
#endif
```

    Appending to ../src/driver_conserv_to_prims.C

## Step 2.c: Updating $T_{\mu\nu}^{\rm GRMHD}$ \[Back to [top](#toc)\]
$$\label{updating_tdnmunu}$$

The function `IllinoisGRMHD_Noble2D_con2prim_onepoint()` also computes $T_{\mu\nu}^{\rm GRMHD}$, so we update the global variables ${\rm eTmunu}$ (where ${\rm mu,nu=t,x,y,z}$, and ${\rm eTmunu}$ is a symmetric tensor). Note that this operation is only performed if the `update_Tmunu` variable is set to 1.

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

        if(update_Tmunu) {
          ww=0;
          eTtt[index] = TDNMUNU[ww]; ww++;
          eTtx[index] = TDNMUNU[ww]; ww++;
          eTty[index] = TDNMUNU[ww]; ww++;
          eTtz[index] = TDNMUNU[ww]; ww++;
          eTxx[index] = TDNMUNU[ww]; ww++;
          eTxy[index] = TDNMUNU[ww]; ww++;
          eTxz[index] = TDNMUNU[ww]; ww++;
          eTyy[index] = TDNMUNU[ww]; ww++;
          eTyz[index] = TDNMUNU[ww]; ww++;
          eTzz[index] = TDNMUNU[ww];
        }
```

    Appending to ../src/driver_conserv_to_prims.C

## Step 2.d: Diagnostics and debugging tools \[Back to [top](#toc)\]
$$\label{diagnostics_and_debugging_tools}$$

Now we simply append to the file useful diagnostics and debugging tools for our code.

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

        error_int_numer += fabs(CONSERVS[TAUENERGY] - tau_orig) + fabs(CONSERVS[RHOSTAR] - rho_star_orig) +
          fabs(CONSERVS[STILDEX] - mhd_st_x_orig) + fabs(CONSERVS[STILDEY] - mhd_st_y_orig) + fabs(CONSERVS[STILDEZ] - mhd_st_z_orig);
        error_int_denom += tau_orig + rho_star_orig + fabs(mhd_st_x_orig) + fabs(mhd_st_y_orig) + fabs(mhd_st_z_orig);

        if(stats.font_fixed==1) font_fixes++;
        vel_limited_ptcount+=stats.vel_limited;
        if(check!=0) {
          failures++;
          if(ADM_3METRIC[SQRTGAMMA]>Psi6threshold) {
            failures_inhoriz++;
            pointcount_inhoriz++;
          }
        }
        pointcount++;
        /***************************************************************************************************************************/
        failure_checker[index] = stats.failure_checker;
        n_iter += stats.n_iter;
      }

  /*
    gettimeofday(&end, NULL);

    seconds  = end.tv_sec  - start.tv_sec;
    useconds = end.tv_usec - start.tv_usec;

    mtime = ((seconds) * 1000 + useconds/1000.0) + 0.999;  // We add 0.999 since mtime is a long int; this rounds up the result before setting the value.  Here, rounding down is incorrect.
    solutions per second: cctk_lsh[0]*cctk_lsh[1]*cctk_lsh[2] / ((CCTK_REAL)mtime/1000.0),
  */
  if(CCTK_Equals(verbose, "essential") || CCTK_Equals(verbose, "essential+iteration output")) {
    CCTK_VInfo(CCTK_THORNSTRING,"C2P: Lev: %d NumPts= %d | Fixes: Font= %d VL= %d rho*= %d | Failures: %d InHoriz= %d / %d | Error: %.3e, ErrDenom: %.3e | %.2f iters/gridpt",
               (int)GetRefinementLevel(cctkGH),
               pointcount,font_fixes,vel_limited_ptcount,rho_star_fix_applied,
               failures,
               failures_inhoriz,pointcount_inhoriz,
               error_int_numer/error_int_denom,error_int_denom,
               (double)n_iter/( (double)(cctk_lsh[0]*cctk_lsh[1]*cctk_lsh[2]) ));
  }

  // Very useful con2prim debugger. E.g., if the primitives (con2prim) solver fails, this will output all data needed to
  //     debug where and why the solver failed. Strongly suggested for experimenting with new fixes.
  if(conserv_to_prims_debug==1 && error_int_numer/error_int_denom > 0.05) {

    ofstream myfile;
    char filename[100];
    srand(time(NULL));
    sprintf(filename,"primitives_debug-%e.dat",error_int_numer/error_int_denom);
    //Alternative, for debugging purposes as well:
    //srand(time(NULL));
    //sprintf(filename,"primitives_debug-%d.dat",rand());
    myfile.open (filename, ios::out | ios::binary);
    //myfile.open ("data.bin", ios::out | ios::binary);
    myfile.write((char*)cctk_lsh, 3*sizeof(int));

    myfile.write((char*)&GAMMA_SPEED_LIMIT, 1*sizeof(CCTK_REAL));

    myfile.write((char*)&rho_b_max, 1*sizeof(CCTK_REAL));
    myfile.write((char*)&rho_b_atm, 1*sizeof(CCTK_REAL));
    myfile.write((char*)&tau_atm, 1*sizeof(CCTK_REAL));

    myfile.write((char*)&Psi6threshold, 1*sizeof(CCTK_REAL));

    myfile.write((char*)&update_Tmunu, 1*sizeof(int));

    myfile.write((char*)&neos,                   1*sizeof(int));
    myfile.write((char*)&Gamma_th,               1*sizeof(CCTK_REAL));
    myfile.write((char*)&K_ppoly_tab0,            1*sizeof(CCTK_REAL));
    myfile.write((char*)Gamma_ppoly_tab_in,   neos*sizeof(CCTK_REAL));
    myfile.write((char*)rho_ppoly_tab_in, (neos-1)*sizeof(CCTK_REAL));

    int fullsize=cctk_lsh[0]*cctk_lsh[1]*cctk_lsh[2];
    myfile.write((char*)x,   (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)y,   (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)z,   (fullsize)*sizeof(CCTK_REAL));

    myfile.write((char *)failure_checker, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTtt, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTtx, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTty, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTtz, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTxx, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTxy, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTxz, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTyy, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTyz, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)eTzz, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)alp, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)gxx, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)gxy, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)gxz, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)gyy, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)gyz, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)gzz, fullsize*sizeof(CCTK_REAL));
    myfile.write((char *)psi_bssn, fullsize*sizeof(CCTK_REAL));

    myfile.write((char*)phi_bssn, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtxx, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtxy, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtxz, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtyy, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtyz, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtzz, (fullsize)*sizeof(CCTK_REAL));

    myfile.write((char*)gtupxx, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtupxy, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtupxz, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtupyy, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtupyz, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)gtupzz, (fullsize)*sizeof(CCTK_REAL));

    myfile.write((char*)betax, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)betay, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)betaz, (fullsize)*sizeof(CCTK_REAL));

    myfile.write((char*)lapm1, (fullsize)*sizeof(CCTK_REAL));

    // HERE WE USE _flux variables as temp storage for original values of conservative variables.. This is used for debugging purposes only.
    myfile.write((char*)tau_flux,      (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)st_x_flux, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)st_y_flux, (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)st_z_flux, (fullsize)*sizeof(CCTK_REAL));

    myfile.write((char*)rho_star_flux, (fullsize)*sizeof(CCTK_REAL));

    myfile.write((char*)Bx,   (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)By,   (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)Bz,   (fullsize)*sizeof(CCTK_REAL));

    myfile.write((char*)vx,   (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)vy,   (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)vz,   (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)P,    (fullsize)*sizeof(CCTK_REAL));
    myfile.write((char*)rho_b,(fullsize)*sizeof(CCTK_REAL));

    int checker=1063; myfile.write((char*)&checker,sizeof(int));

    myfile.close();
    CCTK_VInfo(CCTK_THORNSTRING,"Finished writing %s",filename);
  }

#ifdef ENABLE_STANDALONE_IGM_C2P_SOLVER
  return 0; // int main() requires an integer be returned
#endif

}

#include "harm_primitives_lowlevel.C"

```

    Appending to ../src/driver_conserv_to_prims.C

# Step 3: The `IllinoisGRMHD_Noble2D_con2prim_onepoint.C` file \[Back to [top](#toc)\]
$$\label{noble2d_con2prim_onepoint}$$

This is the main C2P loop, where we perform all tasks necessary for the algorithm. The general flow is the following:

* Before performing the C2P
    * Determine $\sqrt{\gamma}$ and $\gamma^{ij}$
    * Determine $g_{\mu\nu}$ from $\left(\alpha,\beta^{i},\gamma_{ij}\right)$
    * Determine $g^{\mu\nu}$ from $\left(\alpha,\beta^{i},\gamma^{ij}\right)$
    * Check whether or not we need to apply a floor to $\rho_{\star}$.
    * Check whether or not we need to apply a floor to $\tilde{\tau}$.
* Invoke the C2P main routine
    * This is also the step in which we apply the Font fix routine, if necessary
* After performing the C2P
    * Apply physical limits to the primitives and recompute the conservatives

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

int IllinoisGRMHD_Noble2D_con2prim_onepoint(struct eos_struct &eos, struct output_stats &stats, CCTK_REAL *CONSERVS,CCTK_REAL *ADM_3METRIC, CCTK_REAL *TUPMUNU,CCTK_REAL *TDNMUNU, CCTK_REAL *PRIMS) {

#ifndef ENABLE_STANDALONE_IGM_C2P_SOLVER
  DECLARE_CCTK_PARAMETERS;
#endif

  int check = 0;
```

    Overwriting ../src/IllinoisGRMHD_Noble2D_con2prim_onepoint.C

## Step 3.a: Computing $\sqrt{\gamma}$ and $\gamma^{ij}$ from ADM variables \[Back to [top](#toc)\]
$$\label{compute_sqrtgamma_gammainv}$$

We now write down the code that computes the determinant of the physical ADM 3-metric, $\sqrt{\gamma}$, and its inverse, $\gamma^{ij}$.

```python
# Import necessary Python system modules
import os,sys

# Register NRPy+ root directory to the path
nrpy_dir_path = os.path.join("..","..")
if nrpy_dir_path not in sys.path:
    sys.path.append(nrpy_dir_path)

# Import necessary Python/NRPy+ modules
import sympy as sp                              # Python module: used for symbolic expressions
from outputC import *                           # NRPy+ module: used for C code output
import cmdline_helper as cmd                    # NRPy+ module: used for command line features
import indexedexp as ixp                        # NRPy+ module: used to generate indexed expressions (e.g. g_{\mu\nu})
import IllinoisGRMHD_output_functions as IGMout # NRPy+ module: IllinoisGRMHD specific output functions

# Create the NRPy+ header file directory, if it doesn't already exist
IGM_src_dir_path = os.path.join("..","src")
cmd.mkdir(os.path.join(IGM_src_dir_path,"NRPy_generated_headers"))
NRPy_headers_dir_path = os.path.join(IGM_src_dir_path,"NRPy_generated_headers")

# Step 1.a: Set up gamma_{ij}
DIM = 3
gammaDD = ixp.zerorank2()
for i in range(DIM):
    for j in range(i,DIM):
        gammaDD[i][j] = gammaDD[j][i] = sp.Symbol('ADM_3METRIC[GAMMA'+chr(ord('X')+i)+chr(ord('X')+j)+"]",real=True)

# Step 1.b: The compute_ADM_gammaUU_and_sqrtgamma.h file
# Step 1.b.i: Compute eh inverse ADM 3-metric and the determinant of the ADM 3-metric
gammaINVUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)

# Step 1.b.ii: Populate ADM_3METRIC[GAMMAUPYZ] and ADM_3METRIC[SQRTGAMMA]
#              with the results of our inverter
exprlist = [sp.sqrt(gammaDET)]
varslist = ["ADM_3METRIC[SQRTGAMMA]"]
for i in range(DIM):
    for j in range(i,DIM):
        exprlist.append(gammaINVUU[i][j])
        varslist.append("ADM_3METRIC[GAMMAUP"+chr(ord('X')+i)+chr(ord('X')+j)+"]")

# Step 1.c.i: Output result to string
string = outputC(exprlist,varslist,filename="returnstring", params="outCverbose=False")

# Step 1.c.ii: Output result to file
filename = "compute__ADM_gammaUU_and_sqrtgamma__in_terms_of__ADM_3METRIC.h"
filepath = os.path.join(NRPy_headers_dir_path,filename)
IGMout.NRPy_IGM_write_to_file(filepath,filename,string)
```

    Just generated the file: ../src/NRPy_generated_headers/compute__ADM_gammaUU_and_sqrtgamma__in_terms_of__ADM_3METRIC.h

We then use the file we just generated to compute $\sqrt{\gamma}$ and $\gamma^{ij}$ in `IllinoisGRMHD`.

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

#include "NRPy_generated_headers/compute__ADM_gammaUU_and_sqrtgamma__in_terms_of__ADM_3METRIC.h"
```

    Appending to ../src/IllinoisGRMHD_Noble2D_con2prim_onepoint.C

## Step 3.b: Computing $g_{\mu\nu}$ from ADM variables \[Back to [top](#toc)\]
$$\label{compute_g4dn}$$

We now use the `g4DD_ito_BSSN_or_ADM()` function from the [BSSN/ADMBSSN_tofrom_4metric NRPy+ module](/edit/NRPyIGM/BSSN/ADMBSSN_tofrom_4metric.py) to compute $g_{\mu\nu}$ from the ADM variables.

```python
# Step 1.b: Compute the ADM 4-metric, g_{\mu\nu}
# Step 1.b.i: Load the BSSN.ADMBSSN_tofrom_4metric NRPy+ module
import BSSN.ADMBSSN_tofrom_4metric as AB4m

# Step 1.b.ii: Set up alpha
alpha  = sp.Symbol("ADM_3METRIC[ALPHA]",real=True)
gfslist = [["ALPHA"]]
# Step 1.b.iii: Set up beta^{i}
betaU   = ixp.zerorank1()
for i in range(DIM):
    betaU[i] = sp.Symbol('ADM_3METRIC[BETA'+chr(ord('X')+i)+"]",real=True)
    gfslist.append(["BETA"+chr(ord('X')+i)])

# Step 1.b.iv: Compute the g_{\mu\nu} in terms of our ADM variables
AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD=gammaDD,betaU=betaU,alpha=alpha)
g4DD = AB4m.g4DD

# Step 1.b.v: Set up lists to store the expressions and output variables
exprlist = []
namelist = []
for mu in range(4):
    for nu in range(4):
        exprlist.append(g4DD[mu][nu])
        namelist.append("g4dn["+str(mu)+"]["+str(nu)+"]")

# Step 1.b.vi: Convert our results to C output
string = outputC(exprlist,namelist,"returnstring", params="outCverbose=False")

# Step 1.b.vii: Output to file
filename = "compute__g4DD__in_terms_of__ADM_3METRIC.h"
filepath = os.path.join(NRPy_headers_dir_path,filename)
IGMout.NRPy_IGM_write_to_file(filepath,filename,string)
```

    Just generated the file: ../src/NRPy_generated_headers/compute__g4DD__in_terms_of__ADM_3METRIC.h

We then use the file we just generated to compute $g_{\mu\nu}$ in `IllinoisGRMHD`.

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

  CCTK_REAL g4dn[4][4];
#include "NRPy_generated_headers/compute__g4DD__in_terms_of__ADM_3METRIC.h"
```

    Appending to ../src/IllinoisGRMHD_Noble2D_con2prim_onepoint.C

## Step 3.c: Computing $g^{\mu\nu}$ from ADM variables \[Back to [top](#toc)\]
$$\label{compute_g4up}$$

We now use the `g4UU_ito_BSSN_or_ADM()` function from the [BSSN/ADMBSSN_tofrom_4metric NRPy+ module](/edit/NRPyIGM/BSSN/ADMBSSN_tofrom_4metric.py) to compute $g^{\mu\nu}$ from the ADM variables.

```python
# Step 1.c.i: Set up gamma^{ij}
gammaUU = ixp.zerorank2()
for i in range(3):
    for j in range(i,3):
        gammaUU[i][j] = gammaUU[j][i] = sp.Symbol('ADM_3METRIC[GAMMAUP'+chr(ord('X')+i)+chr(ord('X')+j)+"]",real=True)

# Step 1.c.ii: Compute the g_{\mu\nu} in terms of our ADM variables
AB4m.g4UU_ito_BSSN_or_ADM("ADM",betaU=betaU,alpha=alpha,gammaUU=gammaUU)
g4UU = AB4m.g4UU

# Step 1.c.iii: Set up lists to store the expressions and output variables
exprlist = []
namelist = []
for mu in range(4):
    for nu in range(4):
        exprlist.append(g4UU[mu][nu])
        namelist.append("g4up["+str(mu)+"]["+str(nu)+"]")

# Step 1.c.iv: Convert our results to C output
string = outputC(exprlist,namelist,"returnstring", params="outCverbose=False")

# Step 1.c.v: Output to file
filename = "compute__g4UU__in_terms_of__ADM_3METRIC.h"
filepath = os.path.join(NRPy_headers_dir_path,filename)
IGMout.NRPy_IGM_write_to_file(filepath,filename,string)
```

    Just generated the file: ../src/NRPy_generated_headers/compute__g4UU__in_terms_of__ADM_3METRIC.h

We then use the file we just generated to compute $g^{\mu\nu}$ in `IllinoisGRMHD`.

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

  CCTK_REAL g4up[4][4];
#include "NRPy_generated_headers/compute__g4UU__in_terms_of__ADM_3METRIC.h"
```

    Appending to ../src/IllinoisGRMHD_Noble2D_con2prim_onepoint.C

## Step 3.d: Compute primitives from conservatives \[Back to [top](#toc)\]
$$\label{c2p}$$

We now call upon the `harm_primitives_gammalaw_lowlevel()` function from `IllinoisGRMHD` to perform the C2P routine. Notice that to minimize the chance of failing in the C2P routine, we set up to call it 3 times. If at any point the algorithm converges, however, then we skip the extra calls.

Prior to each call of the `harm_primitives_gammalaw_lowlevel()` function, we apply a floor to $\tilde{\tau}$ by invoking the `apply_tau_floor()` function.

Notice that all of this is done *only* when $\rho_{\star}>0$. If we encounter a point where $\rho_{\star}\leq0$, then we instead set $\rho_{b}$ to its atmospheric value, compute $P=P_{\rm cold}(\rho_{b}^{\rm atm})$, and set the velocities as $v^{i} = -\beta^{i}$. This is the essence of the *$\rho_{\star}$ fix algorithm*.

Finally, after the steps above are completed, we call upon the `IllinoisGRMHD_enforce_limits_on_primitives_and_recompute_conservs()` function, which, as its name suggests, enforces physical limits on the primitive variables and then recompute the conservatives from them.

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

  /* Idea: Apply a rho_star floor. rho_* = alpha*sqrt(gamma)*rho_b*u^0, so minimum rho_star is simply alpha*sqrt(gamma)*rho_b_atm*1.0
     if(CONSERVS[RHOSTAR] < ADM_3METRIC[ALPHA]*ADM_3METRIC[SQRTGAMMA]*rho_b_atm*1.0) {
     stats.failure_checker+=1;
     CONSERVS[RHOSTAR] = ADM_3METRIC[ALPHA]*ADM_3METRIC[SQRTGAMMA]*rho_b_atm*1.0;
     stats.rho_star_fix_applied=1;
     }
  */
  if(CONSERVS[RHOSTAR]>0.0) {
    // Apply the tau floor
    apply_tau_floor(tau_atm,rho_b_atm,Psi6threshold,PRIMS,ADM_3METRIC,stats,eos,  CONSERVS);

    stats.font_fixed=0;
    for(int ii=0;ii<3;ii++) {
      check = harm_primitives_gammalaw_lowlevel(/*index,i,j,k,x,y,z,*/
                                                ADM_3METRIC,CONSERVS,PRIMS,  g4dn,g4up,   stats,eos);
      if(check==0) ii=4;
      else stats.failure_checker+=100000;
    }
  } else {
    stats.failure_checker+=1;
    PRIMS[RHOB]      =rho_b_atm;

    /* Set P = P_cold */
    int polytropic_index      = find_polytropic_K_and_Gamma_index(eos, rho_b_atm);
    CCTK_REAL K_ppoly_tab     = eos.K_ppoly_tab[polytropic_index];
    CCTK_REAL Gamma_ppoly_tab = eos.K_ppoly_tab[polytropic_index];
    PRIMS[PRESSURE]           = K_ppoly_tab*pow(rho_b_atm,Gamma_ppoly_tab);

    PRIMS[VX] = - ADM_3METRIC[BETAX];
    PRIMS[VY] = - ADM_3METRIC[BETAY];
    PRIMS[VZ] = - ADM_3METRIC[BETAZ];

    stats.rho_star_fix_applied=1;
  }
  // Enforce limits on primitive variables and recompute conservatives.
  const int already_computed_physical_metric_and_inverse=1;
  IllinoisGRMHD_enforce_limits_on_primitives_and_recompute_conservs(already_computed_physical_metric_and_inverse,PRIMS,stats,eos,ADM_3METRIC,g4dn,g4up, TUPMUNU,TDNMUNU,CONSERVS);

  return check;
}

```

    Appending to ../src/IllinoisGRMHD_Noble2D_con2prim_onepoint.C

# Step 4: `harm_primitives_lowlevel.C` \[Back to [top](#toc)\]
$$\label{harm_primitives_lowlevel}$$

We will now begin documenting the `harm_primitives_lowlevel.C` code. Notice that in the latest iterations of `IllinoisGRMHD`, we have adapted this function so that it now supports both single and piecewise polytropic equations of state.

```python
%%writefile $outdir/harm_primitives_lowlevel.C
inline int harm_primitives_gammalaw_lowlevel(/*const int index,const int i,const int j,const int k,CCTK_REAL *X,CCTK_REAL *Y,CCTK_REAL *Z,*/
                                             CCTK_REAL *ADM_3METRIC,
                                             CCTK_REAL *CONSERVS,CCTK_REAL *PRIMS,
                                             CCTK_REAL g4dn[NDIM][NDIM],CCTK_REAL g4up[NDIM][NDIM],
                                             struct output_stats &stats, eos_struct &eos) {
#ifndef ENABLE_STANDALONE_IGM_C2P_SOLVER
  DECLARE_CCTK_PARAMETERS;
#endif

  // declare some variables for HARM.
  CCTK_REAL U[NPR];
  CCTK_REAL prim[NPR];
  CCTK_REAL detg = ADM_3METRIC[ALPHA]*ADM_3METRIC[SQRTGAMMA]; // == alpha sqrt{gamma} = alpha Psi^6

  // Check to see if the metric is positive-definite.
  // Note that this will slow down the code, and if the metric doesn't obey this, the run is probably too far gone to save,
  //   though if it happens deep in the horizon, it might resurrect the run.
  /*
    CCTK_REAL lam1,lam2,lam3;
    CCTK_REAL M11 = CONF_METRIC[CM_GAMMATILDEXX], M12=CONF_METRIC[CM_GAMMATILDEXY], M13=CONF_METRIC[CM_GAMMATILDEXZ], M22=CONF_METRIC[CM_GAMMATILDEYY], M23=CONF_METRIC[CM_GAMMATILDEYZ], M33=CONF_METRIC[CM_GAMMATILDEZZ];
    eigenvalues_3by3_real_sym_matrix(lam1, lam2, lam3,M11, M12, M13, M22, M23, M33);
    if (lam1 < 0.0 || lam2 < 0.0 || lam3 < 0.0) {
    // Metric is not positive-defitive, reset the physical metric to be conformally-flat.
    CCTK_REAL psi4         = cbrt(ADM_3METRIC[SQRTGAMMA]*ADM_3METRIC[SQRTGAMMA]);
    CCTK_REAL psim4        = 1.0/psi4;
    ADM_3METRIC[GAMMAXX]   = psi4;
    ADM_3METRIC[GAMMAXY]   = 0.0;
    ADM_3METRIC[GAMMAXZ]   = 0.0;
    ADM_3METRIC[GAMMAYY]   = psi4;
    ADM_3METRIC[GAMMAYZ]   = 0.0;
    ADM_3METRIC[GAMMAZZ]   = psi4;
    ADM_3METRIC[GAMMAUPXX] = psim4;
    ADM_3METRIC[GAMMAUPXY] = 0.0;
    ADM_3METRIC[GAMMAUPXZ] = 0.0;
    ADM_3METRIC[GAMMAUPYY] = psim4;
    ADM_3METRIC[GAMMAUPYZ] = 0.0;
    ADM_3METRIC[GAMMAUPZZ] = psim4;
    }
  */
```

    Overwriting ../src/harm_primitives_lowlevel.C

## Step 4.a: Computing $B^{i}_{\rm HARM}$ \[Back to [top](#toc)\]
$$\label{variables_needed_by_harm__bi_harm}$$

Now we must relate `HARM`'s $B^{i}_{\rm HARM}$ with the variables used by `IllinoisGRMHD`. We can start by looking at eqs. (23), (24), and (31) in [Duez *et al.* (2005)](https://arxiv.org/pdf/astro-ph/0503420.pdf) to find the following useful relations

$$
{\rm IllinoisGRMHD:}\
\left\{
\begin{align}
\sqrt{4\pi}b^{0} &= \frac{u_{i}B^{i}}{\alpha}\ ,\\
\sqrt{4\pi}b^{i} &= \frac{B^{i}/\alpha + \sqrt{4\pi}b^{0}u^{i}}{u^{0}}\ .
\end{align}
\right.
$$

Now, if we look at eqs. (16) and (17) in [Gammie & McKinney (2003)](https://arxiv.org/pdf/astro-ph/0301509.pdf) we find the following relations

$$
{\rm HARM:}\
\left\{
\begin{align}
b^{0} &= u_{i}B^{i}_{\rm HARM}\ ,\\
b^{i} &= \frac{B^{i}_{\rm HARM} + b^{0}u^{i}}{u^{0}}\ ,
\end{align}
\right.
$$

from which we can then find the relation

$$
\boxed{B^{i}_{\rm HARM} = \frac{B^{i}}{\alpha\sqrt{4\pi}}}\ .
$$

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

  // Note that ONE_OVER_SQRT_4PI gets us to the object
  // referred to as B^i in the Noble et al paper (and
  // apparently also in the comments to their code).
  // This is NOT the \mathcal{B}^i, which differs by
  // a factor of the lapse.
  CCTK_REAL inv_lapse = 1.0/ADM_3METRIC[ALPHA];
  CCTK_REAL BxL_over_alpha_sqrt_fourpi = PRIMS[BX_CENTER]*inv_lapse*ONE_OVER_SQRT_4PI;
  CCTK_REAL ByL_over_alpha_sqrt_fourpi = PRIMS[BY_CENTER]*inv_lapse*ONE_OVER_SQRT_4PI;
  CCTK_REAL BzL_over_alpha_sqrt_fourpi = PRIMS[BZ_CENTER]*inv_lapse*ONE_OVER_SQRT_4PI;
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.b: Initializing $\rho_{b}$, $P$, and $v^{i}$ \[Back to [top](#toc)\]
$$\label{variables_needed_by_harm__init_rhob_pressure_vi}$$

Here we simply initialize $\rho_{b}$, $P$, and $v^{i}$.

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

  CCTK_REAL rho_b_oldL = PRIMS[RHOB];
  CCTK_REAL P_oldL     = PRIMS[PRESSURE];
  CCTK_REAL vxL        = PRIMS[VX];
  CCTK_REAL vyL        = PRIMS[VY];
  CCTK_REAL vzL        = PRIMS[VZ];
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.c: Storing the original values of the conservative variables \[Back to [top](#toc)\]
$$\label{variables_needed_by_harm__original_conserv}$$

Here we store the currently known values of $\left\{\rho_{\star},\tilde{\tau},\tilde{S}_{i}\right\}$.

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

  /*
    -- Driver for new prim. var. solver.  The driver just translates
    between the two sets of definitions for U and P.  The user may
    wish to alter the translation as they see fit.

    //        /     rho u^t     \                           //
    //    U = | T^t_t + rho u^t | * sqrt(-det(g_{\mu\nu}))  //
    //        |     T^t_i       |                           //
    //        \      B^i        /                           //
    //                                                      //
    //        /     rho     \                               //
    //    P = |     uu      |                               //
    //        | \tilde{u}^i |                               //
    //        \     B^i     /                               //

    (above equations have been fixed by Yuk Tung & Zach)
  */

  // U[NPR]    = conserved variables (current values on input/output);
  // g4dn[NDIM][NDIM] = covariant form of the 4-metric ;
  // g4up[NDIM][NDIM] = contravariant form of the 4-metric ;
  // gdet             = sqrt( - determinant of the 4-metric) ;
  // prim[NPR] = primitive variables (guess on input, calculated values on
  //                     output if there are no problems);

  // U[1]   =
  // U[2-4] =  stildei + rhostar

  CCTK_REAL rho_star_orig = CONSERVS[RHOSTAR];
  CCTK_REAL mhd_st_x_orig = CONSERVS[STILDEX];
  CCTK_REAL mhd_st_y_orig = CONSERVS[STILDEY];
  CCTK_REAL mhd_st_z_orig = CONSERVS[STILDEZ];
  CCTK_REAL tau_orig      = CONSERVS[TAUENERGY];
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.d: Guessing $\rho_{b}$, $P$, and $v^{i}$ \[Back to [top](#toc)\]
$$\label{variables_needed_by_harm__guessing_rhob_pressure_vi}$$

We will now start preparing the primitives for the conservative-to-primitive algorithm, which employs the [Newton-Raphson method](https://en.wikipedia.org/wiki/Newton%27s_method).

We offer the algorithm the initial guess

$$
\boxed{
{\rm Guess\ \#1:}\
\left\{
\begin{align}
\rho_{b}^{\rm guess} &= \frac{\rho_{\star}}{\psi^{6}}\\
P_{\rm guess} &= \kappa \left(\rho_{b}^{\rm guess}\right)^{\Gamma}\\
u0 &= \frac{1}{\alpha}\\
v^{i} &= -\beta^{i}
\end{align}
\right.
}\ .
$$

If this initial guess causes the Newton-Raphson method to fail, we try a second guess

$$
\boxed{
{\rm Guess\ \#2:}\
\left\{
\begin{align}
\rho_{b}^{\rm guess} &= 100\rho_{b}^{\rm atm}\\
P_{\rm guess} &= \kappa \left(\rho_{b}^{\rm guess}\right)^{\Gamma}\\
u0 &= \frac{1}{\alpha}\\
v^{i} &= -\beta^{i}
\end{align}
\right.
}\ .
$$

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

  // Other ideas for setting the gamma speed limit
  //CCTK_REAL GAMMA_SPEED_LIMIT = 100.0;
  //if(ADM_3METRIC[SQRTGAMMA]>Psi6threshold) GAMMA_SPEED_LIMIT=500.0;
  //if(ADM_3METRIC[SQRTGAMMA]>Psi6threshold) GAMMA_SPEED_LIMIT=100.0;

  //FIXME: Only works if poisoning is turned on. Otherwise will access unknown memory. This trick alone speeds up the whole code (Cowling) by 2%.
  //int startguess=0;
  //if(std::isnan(PRIMS[VX])) startguess=1;
  int startguess=1;

  CCTK_REAL u0L=1.0;
  CCTK_REAL K_ppoly_tab,Gamma_ppoly_tab;

  for(int which_guess=startguess;which_guess<3;which_guess++) {
    int check;

    if(which_guess==1) {
      //Use a different initial guess:
      rho_b_oldL = CONSERVS[RHOSTAR]/ADM_3METRIC[SQRTGAMMA];

      /*******************************************
       *     Piecewise Polytropic EOS Patch      *
       * Finding Gamma_ppoly_tab and K_ppoly_tab *
       *******************************************/
      /* Here we use our newly implemented
       * find_polytropic_K_and_Gamma() function
       * to determine the relevant polytropic
       * Gamma and K parameters to be used
       * within this function.
       */
      int polytropic_index = find_polytropic_K_and_Gamma_index(eos,rho_b_oldL);
      K_ppoly_tab     = eos.K_ppoly_tab[polytropic_index];
      Gamma_ppoly_tab = eos.Gamma_ppoly_tab[polytropic_index];

      // After that, we compute P_cold
      P_oldL = K_ppoly_tab*pow(rho_b_oldL,Gamma_ppoly_tab);

      u0L = 1.0/ADM_3METRIC[ALPHA];
      vxL = -ADM_3METRIC[BETAX];
      vyL = -ADM_3METRIC[BETAY];
      vzL = -ADM_3METRIC[BETAZ];
    }

    if(which_guess==2) {
      //Use atmosphere as initial guess:
      rho_b_oldL = 100.0*rho_b_atm;

      /*******************************************
       *     Piecewise Polytropic EOS Patch      *
       * Finding Gamma_ppoly_tab and K_ppoly_tab *
       *******************************************/
      /* Here we use our newly implemented
       * find_polytropic_K_and_Gamma() function
       * to determine the relevant polytropic
       * Gamma and K parameters to be used
       * within this function.
       */
      int polytropic_index = find_polytropic_K_and_Gamma_index(eos,rho_b_oldL);
      K_ppoly_tab     = eos.K_ppoly_tab[polytropic_index];
      Gamma_ppoly_tab = eos.Gamma_ppoly_tab[polytropic_index];

      // After that, we compute P_cold
      P_oldL = K_ppoly_tab*pow(rho_b_oldL,Gamma_ppoly_tab);

      u0L = 1.0/ADM_3METRIC[ALPHA];
      vxL = -ADM_3METRIC[BETAX];
      vyL = -ADM_3METRIC[BETAY];
      vzL = -ADM_3METRIC[BETAZ];
    }
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.e: Writing $\boldsymbol{C}_{\rm HARM}$ in terms of $\boldsymbol{C}_{\rm IGM}$ \[Back to [top](#toc)\]
$$\label{variables_needed_by_harm__conservs}$$

We will now relate $\boldsymbol{C}_{\rm HARM}$ to $\boldsymbol{C}_{\rm IGM}$. As previously mentioned, we have

$$
\begin{align}
\boldsymbol{C}_{\rm IGM} &= \left(\rho_{\star},\tilde{\tau},\tilde{S}_{i},\tilde{B}^{i}\right)\ ,\\
\boldsymbol{C}_{\rm HARM} &= \left(\sqrt{-g}\rho_{b}u^{0},\sqrt{-g}\left(T^{0}_{\ 0}+\rho_{b}u^{0}\right),\sqrt{-g}T^{0}_{\ i},\sqrt{-g}B^{i}_{\rm HARM}\right) \equiv \left(\rho_{\rm HARM},\tilde{\tau}_{\rm HARM},\tilde{S}_{i}^{\rm HARM},\tilde{B}^{i}_{\rm HARM}\right)\ .
\end{align}
$$

The following relations immediately hold

$$
\boxed{
\begin{align}
\rho_{\rm HARM} &= \rho_{\star}\\
\tilde{S}_{i}^{\rm HARM} &= \tilde{S}_{i}
\end{align}
}\ .
$$

As previously derived in [Step 4.b](#variables_needed_by_harm__bi_harm), we then have

$$
\boxed{\tilde{B}^{i}_{\rm HARM} = \underbrace{\left(\alpha\sqrt{\gamma}\right)}_{\rm detg}\underbrace{\left(\frac{B^{i}}{\alpha\sqrt{4\pi}}\right)}_{\rm BiL\_over\_alpha\_sqrt\_fourpi}}\ .
$$

Finally, we must relate $\tilde{\tau}_{\rm HARM} = \sqrt{-g}\left(T^{0}_{\ 0}+\rho_{b}u^{0}\right)$ to the `IllinoisGRMHD` variables. Let us start by looking at

$$
\begin{align}
S^{i} &= -\gamma^{i}_{\ \mu}n_{\nu}T^{\mu\nu}\\
      &= -\left(\delta^{i}_{\mu} + n^{i}n_{\mu}\right)n_{\nu}T^{\mu\nu}\\
      &= -\delta^{i}_{\mu}n_{\nu}T^{\mu\nu} - n^{i}n_{\mu}n_{\nu}T^{\mu\nu}\\
      &= -n_{0}T^{i0} - n^{i}n_{0}n_{0}T^{00}\\
      &= \alpha T^{i0} - \left(-\frac{\beta^{i}}{\alpha}\right)\alpha^{2}T^{00}\\
\implies S^{i}&= \alpha\left[\beta^{i}T^{00} + T^{i0}\right]\ .
\end{align}
$$

Now let us look remember that

$$
\tilde{\tau} = \alpha^{2}\sqrt{\gamma}T^{00} - \rho_{\star} \implies T^{00} = \frac{\left(\tilde\tau + \rho_{\star}\right)}{\alpha^{2}\sqrt{\gamma}}\ .
$$

Then

$$
\begin{align}
T^{0}_{0} &= g_{0\mu}T^{0\mu}\\
          &= g_{00}T^{00} + g_{0i}T^{0i}\\
          &= \left(-\alpha^{2}+\beta_{\ell}\beta^{\ell}\right)T^{00} + \beta_{i}T^{0i}\\
          &= -\alpha^{2}T^{00} + \beta_{i}\left[\beta^{i}T^{00} + T^{i0}\right]\\
          &= -\alpha^{2}\left[\frac{\left(\tilde\tau + \rho_{\star}\right)}{\alpha^{2}\sqrt{\gamma}}\right] + \frac{\beta^{i}S_{i}}{\alpha}\\
\implies T^{0}_{0} &= -\frac{\tilde\tau+\rho_{\star}}{\sqrt{\gamma}} + \frac{\beta^{i}S_{i}}{\alpha}\ .
\end{align}
$$

Finally, we can compute

$$
\begin{align}
\tilde\tau_{\rm HARM} &= \sqrt{-g}\left(T^{0}_{0} + \rho_{b}u^{0}\right)\\
                      &= \alpha\sqrt{\gamma}T^{0}_{0} + \alpha\sqrt{\gamma}\rho_{b}u^{0}\\
                      &= \alpha\sqrt{\gamma}\left[-\frac{\tilde\tau+\rho_{\star}}{\sqrt{\gamma}} + \frac{\beta^{i}S_{i}}{\alpha}\right] + \rho_{\star}\\
                      &= -\alpha\tilde{\tau} -\alpha\rho_{\star} + \beta^{i}\left(\sqrt{\gamma}S_{i}\right) + \rho_{\star}\\
\implies &\boxed{\tilde\tau_{\rm HARM} = -\alpha\tilde{\tau} - \left(\alpha-1\right)\rho_{\star} + \beta^{i}\tilde{S}_{i}}\ .
\end{align}
$$

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

    // Fill the array of conserved variables according to the wishes of Utoprim_2d.
    U[RHO]    = CONSERVS[RHOSTAR];
    U[UU]     = -CONSERVS[TAUENERGY]*ADM_3METRIC[ALPHA] - (ADM_3METRIC[ALPHA]-1.0)*CONSERVS[RHOSTAR] +
      ADM_3METRIC[BETAX]*CONSERVS[STILDEX] + ADM_3METRIC[BETAY]*CONSERVS[STILDEY]  + ADM_3METRIC[BETAZ]*CONSERVS[STILDEZ] ; // note the minus sign on tau
    U[UTCON1] = CONSERVS[STILDEX];
    U[UTCON2] = CONSERVS[STILDEY];
    U[UTCON3] = CONSERVS[STILDEZ];
    U[BCON1]  = detg*BxL_over_alpha_sqrt_fourpi;
    U[BCON2]  = detg*ByL_over_alpha_sqrt_fourpi;
    U[BCON3]  = detg*BzL_over_alpha_sqrt_fourpi;
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.f: Writing $\boldsymbol{P}_{\rm HARM}$ in terms of $\boldsymbol{P}_{\rm IGM}$ \[Back to [top](#toc)\]
$$\label{variables_needed_by_harm__prims}$$

Keep in mind that for the primitive variables, we are not intereted in providing exact relations. Instead, what we are interested in doing is providing an educated guess of what it should look like in hopes that the Newton-Raphson method then converges to the correct values. With that in mind, the primitive variables are then initialized to:

$$
\boxed{
\begin{align}
\rho_{\rm HARM} &= \rho_{b}\\
u &= \frac{P}{\Gamma_{\rm poly} - 1}\\
\tilde{u}^{i} &= u^{0}\left(v^{i}+\beta^{i}\right)\\
B^{i}_{\rm HARM} &= \frac{B^{i}}{\alpha\sqrt{4\pi}}
\end{align}\ ,
}
$$

where $\Gamma_{\rm poly}$ stands for the *local* polytropic $\Gamma$-factor.

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

    CCTK_REAL uL = P_oldL/(Gamma_ppoly_tab - 1.0);
    CCTK_REAL utxL = u0L*(vxL + ADM_3METRIC[BETAX]);
    CCTK_REAL utyL = u0L*(vyL + ADM_3METRIC[BETAY]);
    CCTK_REAL utzL = u0L*(vzL + ADM_3METRIC[BETAZ]);

    prim[RHO]    = rho_b_oldL;
    prim[UU]     = uL;
    prim[UTCON1] = utxL;
    prim[UTCON2] = utyL;
    prim[UTCON3] = utzL;
    prim[BCON1]  = BxL_over_alpha_sqrt_fourpi;
    prim[BCON2]  = ByL_over_alpha_sqrt_fourpi;
    prim[BCON3]  = BzL_over_alpha_sqrt_fourpi;
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.g: Calling the `HARM` conservative-to-primitive solver \[Back to [top](#toc)\]
$$\label{calling_harm_conservs_to_prims_solver}$$

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

    /*************************************************************/
    // CALL HARM PRIMITIVES SOLVER:
    check = Utoprim_2d(eos, U, g4dn, g4up, detg, prim,stats.n_iter);
    // Note that we have modified this solver, so that nearly 100%
    // of the time it yields either a good root, or a root with
    // negative epsilon (i.e., pressure).
    /*************************************************************/
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.h: Applying the Font *et al.* fix, if the inversion fails \[Back to [top](#toc)\]
$$\label{font_fix}$$

If the conservative-to-primitive solver fails to converge, we apply the procedure suggested by [Font *et al.* (1998)](https://arxiv.org/abs/gr-qc/9811015). The algorithm can be summarized as the requirement that

$$
P=P_{\rm cold} = \kappa \rho^{\Gamma_{\rm cold}}_{b}\ ,
$$

and then recomputing the velocities $u_{i}$. We will describe this procedure in detail in [Step 4](#font_fix_gamma_law__c) below.

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

    // Use the new Font fix subroutine
    int font_fix_applied=0;
    if(check!=0) {
      font_fix_applied=1;
      CCTK_REAL u_xl=1e100, u_yl=1e100, u_zl=1e100; // Set to insane values to ensure they are overwritten.
      /************************
       * New Font fix routine *
       ************************/
       check = font_fix__hybrid_EOS(u_xl,u_yl,u_zl, CONSERVS,PRIMS,ADM_3METRIC, eos);
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.i: Compute $\tilde{u}^{i}$ \[Back to [top](#toc)\]
$$\label{compute_utconi}$$

Now we evaluate

$$
\tilde{u}^{i} = \gamma^{ij}u_{i} \implies
\boxed{
\left\{
\begin{align}
\tilde{u}^{x} &= \gamma^{xx}u_{x} + \gamma^{xy}u_{y} + \gamma^{xz}u_{z}\\
\tilde{u}^{y} &= \gamma^{yx}u_{x} + \gamma^{yy}u_{y} + \gamma^{yz}u_{z}\\
\tilde{u}^{z} &= \gamma^{zx}u_{x} + \gamma^{zy}u_{y} + \gamma^{zz}u_{z}
\end{align}
\right.
}
$$

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

      //Translate to HARM primitive now:
      prim[UTCON1] = ADM_3METRIC[GAMMAUPXX]*u_xl + ADM_3METRIC[GAMMAUPXY]*u_yl + ADM_3METRIC[GAMMAUPXZ]*u_zl;
      prim[UTCON2] = ADM_3METRIC[GAMMAUPXY]*u_xl + ADM_3METRIC[GAMMAUPYY]*u_yl + ADM_3METRIC[GAMMAUPYZ]*u_zl;
      prim[UTCON3] = ADM_3METRIC[GAMMAUPXZ]*u_xl + ADM_3METRIC[GAMMAUPYZ]*u_yl + ADM_3METRIC[GAMMAUPZZ]*u_zl;
      if (check==1) {
        CCTK_VInfo(CCTK_THORNSTRING,"Font fix failed!");
        CCTK_VInfo(CCTK_THORNSTRING,"stats.failure_checker = %d , st_i = %e %e %e, rhostar = %e, Bi = %e %e %e, gij = %e %e %e %e %e %e, Psi6 = %e",stats.failure_checker,mhd_st_x_orig,mhd_st_y_orig,mhd_st_z_orig,rho_star_orig,PRIMS[BX_CENTER],PRIMS[BY_CENTER],PRIMS[BZ_CENTER],ADM_3METRIC[GAMMAXX],ADM_3METRIC[GAMMAXY],ADM_3METRIC[GAMMAXZ],ADM_3METRIC[GAMMAYY],ADM_3METRIC[GAMMAYZ],ADM_3METRIC[GAMMAZZ],ADM_3METRIC[SQRTGAMMA]);
      }
    }
    stats.failure_checker+=font_fix_applied*10000;
    stats.font_fixed=font_fix_applied;
    /*************************************************************/
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.j: Limiting velocities \[Back to [top](#toc)\]
$$\label{limiting_velocities}$$

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

    if(check==0) {
      //Now that we have found some solution, we first limit velocity:
      //FIXME: Probably want to use exactly the same velocity limiter function here as in mhdflux.C
      CCTK_REAL utx_new = prim[UTCON1];
      CCTK_REAL uty_new = prim[UTCON2];
      CCTK_REAL utz_new = prim[UTCON3];

      //Velocity limiter:
      CCTK_REAL gijuiuj = ADM_3METRIC[GAMMAXX]*SQR(utx_new ) +
        2.0*ADM_3METRIC[GAMMAXY]*utx_new*uty_new + 2.0*ADM_3METRIC[GAMMAXZ]*utx_new*utz_new +
        ADM_3METRIC[GAMMAYY]*SQR(uty_new) + 2.0*ADM_3METRIC[GAMMAYZ]*uty_new*utz_new +
        ADM_3METRIC[GAMMAZZ]*SQR(utz_new);
      CCTK_REAL au0m1 = gijuiuj/( 1.0+sqrt(1.0+gijuiuj) );
      u0L = (au0m1+1.0)/ADM_3METRIC[ALPHA];

      // *** Limit velocity
      stats.vel_limited=0;
      if (au0m1 > 0.9999999*(GAMMA_SPEED_LIMIT-1.0)) {
        CCTK_REAL fac = sqrt((SQR(GAMMA_SPEED_LIMIT)-1.0)/(SQR(1.0+au0m1) - 1.0));
        utx_new *= fac;
        uty_new *= fac;
        utz_new *= fac;
        gijuiuj = gijuiuj * SQR(fac);
        au0m1 = gijuiuj/( 1.0+sqrt(1.0+gijuiuj) );
        // Reset rho_b and u0
        u0L = (au0m1+1.0)/ADM_3METRIC[ALPHA];
        prim[RHO] =  rho_star_orig/(ADM_3METRIC[ALPHA]*u0L*ADM_3METRIC[SQRTGAMMA]);
        stats.vel_limited=1;
        stats.failure_checker+=1000;
      } //Finished limiting velocity
```

    Appending to ../src/harm_primitives_lowlevel.C

## Step 4.k: Setting the primitives \[Back to [top](#toc)\]
$$\label{primitives}$$

Finally, we update the primitives,

$$
\boxed{
\begin{align}
\rho_{b} &= \rho\\
P &= \left(\Gamma - 1\right)u = P_{\rm cold}\\
v^{i} &= \frac{\tilde{u}^{i}}{u^{0}} - \beta^{i}
\end{align}
}
$$

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

      //The Font fix only sets the velocities.  Here we set the pressure & density HARM primitives.
      if(font_fix_applied==1) {
        prim[RHO] = rho_star_orig/(ADM_3METRIC[ALPHA]*u0L*ADM_3METRIC[SQRTGAMMA]);
        //Next set P = P_cold:
        CCTK_REAL P_cold;

        /**********************************
         * Piecewise Polytropic EOS Patch *
         *  Finding Gamma_ppoly_tab and K_ppoly_tab *
         **********************************/
        /* Here we use our newly implemented
         * find_polytropic_K_and_Gamma() function
         * to determine the relevant polytropic
         * Gamma and K parameters to be used
         * within this function.
         */
        int polytropic_index = find_polytropic_K_and_Gamma_index(eos,prim[RHO]);
        K_ppoly_tab     = eos.K_ppoly_tab[polytropic_index];
        Gamma_ppoly_tab = eos.Gamma_ppoly_tab[polytropic_index];

        // After that, we compute P_cold
        P_cold = K_ppoly_tab*pow(prim[RHO],Gamma_ppoly_tab);

        prim[UU] = P_cold/(Gamma_ppoly_tab-1.0);
      } //Finished setting remaining primitives if there was a Font fix.

      /* Set rho_b */
      PRIMS[RHOB] = prim[RHO];

      /***************
       * PPEOS Patch *
       * Hybrid EOS  *
       ***************
       */
      /* We now compute the pressure as a function
       * of rhob, P_cold, eps_cold, and u = rhob*eps,
       * using the function pressure_rho0_u(), which
       * implements the equation:
       * .-------------------------------------------------------------.
       * | p(rho_b,u) = P_cold + (Gamma_th - 1)*(u - rho_b * eps_cold) |
       * .-------------------------------------------------------------.
       */
      PRIMS[PRESSURE] = pressure_rho0_u(eos, prim[RHO],prim[UU]);

      /* Already set u0L. */
      PRIMS[VX]       = utx_new/u0L - ADM_3METRIC[BETAX];
      PRIMS[VY]       = uty_new/u0L - ADM_3METRIC[BETAY];
      PRIMS[VZ]       = utz_new/u0L - ADM_3METRIC[BETAZ];

      return 0;
    } else {
      //If we didn't find a root, then try again with a different guess.
    }
  }
  CCTK_VInfo(CCTK_THORNSTRING,"Couldn't find root from: %e %e %e %e %e, rhob approx=%e, rho_b_atm=%e, Bx=%e, By=%e, Bz=%e, gij_phys=%e %e %e %e %e %e, alpha=%e",
	     tau_orig,rho_star_orig,mhd_st_x_orig,mhd_st_y_orig,mhd_st_z_orig,rho_star_orig/ADM_3METRIC[SQRTGAMMA],rho_b_atm,PRIMS[BX_CENTER],PRIMS[BY_CENTER],PRIMS[BZ_CENTER],ADM_3METRIC[GAMMAXX],ADM_3METRIC[GAMMAXY],ADM_3METRIC[GAMMAXZ],ADM_3METRIC[GAMMAYY],ADM_3METRIC[GAMMAYZ],ADM_3METRIC[GAMMAZZ],ADM_3METRIC[ALPHA]);
  return 1;
}

//#include "harm_u2p_util.c"
#include "harm_utoprim_2d.c"
#include "eigen.C"
#include "font_fix_gamma_law.C"

```

    Appending to ../src/harm_primitives_lowlevel.C

# Step 5: `font_fix_gamma_law.C` \[Back to [top](#toc)\]
$$\label{font_fix_gamma_law__c}$$

The [Font *et al.*](https://arxiv.org/pdf/gr-qc/9811015.pdf) algorithm (henceforth Font Fix algorithm) can be summarized by the following assumpition ssume:

$$
P = P_{\rm cold}\ ,
$$

where $P_{\rm cold}$ is computed assuming a simple or piecewise polytropic equation of state. It is also assumed that

$$
\epsilon = \epsilon_{\rm cold}\ ,
$$

hence

$$
h = 1 + \epsilon + \frac{P}{\rho_{b}}
$$

We then run an iterative process that updates $\rho$ based on the value of $h$, until convergence is obtained.

## Step 5.a: Computing the basic quantities needed by the algorithm \[Back to [top](#toc)\]
$$\label{font_fix_gamma_law__basic_quantities}$$

We start by computing all basic quantities needed by the Font Fix algorithm:

$$
\boxed{
\begin{align}
\bar{B}^{i}                 &= \frac{B^i}{\sqrt{4\pi}}\\
\bar{B}_{i}                 &= \gamma_{ij}\bar{B}^{j}\\
\bar{B}^{2}                 &= \bar{B}_{i}\bar{B}^{i}\\
\bar{B}                     &= \sqrt{\bar{B}^{2}}\\
\bar{B}\cdot\tilde{S}       &= \bar{B}^{i}\tilde{S}_{i}\\
                    (\bar{B}&\cdot\tilde{S})^{2}\\
\hat{\bar{B}}\cdot\tilde{S} &= \hat{\bar{B}}^{i}\tilde{S}_{i} \equiv \left(\frac{\bar{B}^{i}}{\bar{B}}\right)\tilde{S}_{i}\\
\tilde{S}\cdot\tilde{S} &= \gamma^{ij}\tilde{S}_{i}\tilde{S}_{j}
\end{align}
}\ .
$$

The algorithm then checks whether or not $\tilde{S}\cdot\tilde{S} = 0$, returning

$$
\begin{align}
\rho_{b} &= \psi^{-6}\rho_{\star}\ ,\\
u^{i} &= 0\ ,
\end{align}
$$

if that is the case and terminating the function call.

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

// Function prototype
static inline int font_fix__rhob_loop( int maxits, CCTK_REAL tol,
                                       CCTK_REAL W, CCTK_REAL Sf2, CCTK_REAL Psim6, CCTK_REAL sdots, CCTK_REAL BbardotS2, CCTK_REAL B2bar,
                                       CCTK_REAL *CONSERVS,
                                       eos_struct eos, CCTK_REAL rhob_in, CCTK_REAL &rhob_out );

/**********************************
 * Piecewise Polytropic EOS Patch *
 *    Font fix: function call     *
 **********************************/
inline int font_fix__hybrid_EOS(CCTK_REAL &u_x, CCTK_REAL &u_y, CCTK_REAL &u_z,CCTK_REAL *CONSERVS,CCTK_REAL *PRIMS,CCTK_REAL *ADM_3METRIC, eos_struct eos) {

  CCTK_REAL Bxbar  = PRIMS[BX_CENTER]*ONE_OVER_SQRT_4PI;
  CCTK_REAL Bybar  = PRIMS[BY_CENTER]*ONE_OVER_SQRT_4PI;
  CCTK_REAL Bzbar  = PRIMS[BZ_CENTER]*ONE_OVER_SQRT_4PI;
  CCTK_REAL Bbar_x = ADM_3METRIC[GAMMAXX]*Bxbar + ADM_3METRIC[GAMMAXY]*Bybar + ADM_3METRIC[GAMMAXZ]*Bzbar;
  CCTK_REAL Bbar_y = ADM_3METRIC[GAMMAXY]*Bxbar + ADM_3METRIC[GAMMAYY]*Bybar + ADM_3METRIC[GAMMAYZ]*Bzbar;
  CCTK_REAL Bbar_z = ADM_3METRIC[GAMMAXZ]*Bxbar + ADM_3METRIC[GAMMAYZ]*Bybar + ADM_3METRIC[GAMMAZZ]*Bzbar;
  CCTK_REAL B2bar  = Bxbar*Bbar_x + Bybar*Bbar_y + Bzbar*Bbar_z;
  CCTK_REAL Bbar   = sqrt(B2bar);

  CCTK_REAL check_B_small = fabs(Bxbar)+fabs(Bybar)+fabs(Bzbar);
  if (check_B_small>0 && check_B_small<1.e-150) {
    // need to compute B2bar specially to prevent floating-point underflow
    CCTK_REAL Bmax = fabs(Bxbar);
    if (Bmax < fabs(Bybar)) Bmax=fabs(Bybar);
    if (Bmax < fabs(Bzbar)) Bmax=fabs(Bzbar);
    CCTK_REAL Bxtmp=Bxbar/Bmax, Bytemp=Bybar/Bmax, Bztemp=Bzbar/Bmax;
    CCTK_REAL B_xtemp=Bbar_x/Bmax, B_ytemp=Bbar_y/Bmax, B_ztemp=Bbar_z/Bmax;
    Bbar = sqrt(Bxtmp*B_xtemp + Bytemp*B_ytemp + Bztemp*B_ztemp)*Bmax;
  }
  CCTK_REAL BbardotS = Bxbar*CONSERVS[STILDEX] + Bybar*CONSERVS[STILDEY] + Bzbar*CONSERVS[STILDEZ];
  CCTK_REAL BbardotS2 = BbardotS*BbardotS;
  CCTK_REAL hatBbardotS = BbardotS/Bbar;
  if (Bbar<1.e-300) hatBbardotS = 0.0;
  CCTK_REAL Psi6  = ADM_3METRIC[SQRTGAMMA];
  CCTK_REAL Psim6 = 1.0/Psi6;

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

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

  CCTK_REAL rhob;
  if (sdots<1.e-300) {
    rhob = CONSERVS[RHOSTAR]*Psim6;
    u_x=0.0; u_y=0.0; u_z=0.0;
    return 0;
  }
  /* This test has some problem.
     if (fabs(BbardotS2 - sdots*B2bar) > 1e-8) {
     CCTK_VInfo(CCTK_THORNSTRING,"(Bbar dot S)^2, Bbar^2 * sdotS, %e %e",SQR(BbardotS),sdots*B2bar);
     CCTK_VInfo(CCTK_THORNSTRING,"Cauchy-Schwartz inequality is violated!");
     }
  */
```

    Overwriting ../src/font_fix_gamma_law.C

## Step 5.b: Initial guesses for $W$, $S_{{\rm fluid}}^{2}$, and $\rho$ \[Back to [top](#toc)\]
$$\label{font_fix_gamma_law__initial_guesses}$$

We set the initial data based on eqs. (A52), (A53), and (A59) found in [Appendix A of Zachariah *et al.* (2012)](https://arxiv.org/pdf/1112.0568.pdf):

$$
\boxed{
\begin{align}
W_{0} &= \psi^{-6}\sqrt{\left(\hat{\bar{B}}\cdot\tilde{S}\right)^{2} + \rho_{\star}^{2}}\\
S_{{\rm fluid},0}^{2} &=\frac{W_{0}^{2}\left(\tilde{S}\cdot\tilde{S}\right)+\left(\bar{B}\cdot\tilde{S}\right)^{2}\left(\bar{B}^{2} + 2W_{0}\right)}{\left(W_{0} + \bar{B}^{2}\right)^{2}}\\
\rho_{0} &= \frac{\psi^{-6}\rho_{\star}}{\sqrt{1+\frac{S_{{\rm fluid},0}^{2}}{\rho_{\star}^{2}}}}
\end{align}
}\ .
$$

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

  // Initial guess for W, S_fluid and rhob
  CCTK_REAL W0    = sqrt( SQR(hatBbardotS) + SQR(CONSERVS[RHOSTAR]) ) * Psim6;
  CCTK_REAL Sf20  = (SQR(W0)*sdots + BbardotS2*(B2bar + 2.0*W0))/SQR(W0+B2bar);
  CCTK_REAL rhob0 = CONSERVS[RHOSTAR]*Psim6/sqrt(1.0+Sf20/SQR(CONSERVS[RHOSTAR]));

  //****************************************************************
  //                          FONT FIX
  // Impose Font fix when HARM primitives solver fails to find
  //   acceptable set of primitives.
  //****************************************************************

  /* Set the maximum number of iterations */
  int maxits = 500;

  /* Set the allowed tolerance */
  CCTK_REAL tol = 1.e-15;

  /* Declare basic variables */
  int font_fix_status;
```

    Appending to ../src/font_fix_gamma_law.C

## Step 5.c: Font fix main loop \[Back to [top](#toc)\]
$$\label{font_fix_main_loop}$$

We now perform the Font fix algorithm by calling the `font_fix__rhob_loop()` function. To see the details of what this function do, please refer to [step 5.e](#font_fix__rhob_loop) below.

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

  /**********************
   * FONT FIX MAIN LOOP *
   **********************
   * Perform the font fix routine until convergence
   * is obtained and the algorithm returns with no
   * error. Every time the Font fix fails, increase
   * the tolerance by a factor of 10.
   */
   int font_fix_attempts = 5;
   CCTK_REAL font_fix_tol_factor = 10.0;
   for(int n=0; n<font_fix_attempts; n++) {

    tol *= pow(font_fix_tol_factor,n);
    font_fix_status = font_fix__rhob_loop(maxits,tol, W0,Sf20,Psim6,sdots,BbardotS2,B2bar, CONSERVS,eos, rhob0,rhob);
    rhob0 = rhob;
    if(font_fix_status==0) break;

  }
  //**************************************************************************************************************
```

    Appending to ../src/font_fix_gamma_law.C

## Step 5.d: Output $\rho_{b}$ and $u_{i}$ \[Back to [top](#toc)\]
$$\label{font_fix_gamma_law__outputs}$$

Finally, we return $\rho_{b}$ and $u_{i}$. In the relations below, $N$ indicates the last computed value of $\rho$ obtained by our iterative process. The quantities evaluated here are

$$
\boxed{
\begin{align}
\rho_{b}   &= \rho_{N}\\
\gamma_{v} &= \frac{\psi^{-6}\rho_{\star}}{\rho_{b}}\\
h          &= 1 + \epsilon_{\rm cold} + \frac{P_{\rm cold}}{\rho_{b}}\\
f_{1}      &= \frac{\psi^{6}\left(\bar{B}\cdot\tilde{S}\right)}{\gamma_{v}\rho_{\star} h}\\
f_{2}      &= \left(\rho_{\star}h + \psi^{6}\frac{\bar{B}^{2}}{\gamma_{v}}\right)^{-1}\\
u_{i}      &= f_{2}\left(\tilde{S}_{i} + f_{1}\bar{B}_{i}\right)
\end{align}
}\ .
$$

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

  /* Font fix works! */
  /* First compute P_cold, eps_cold, then h = h_cold */
  CCTK_REAL P_cold, eps_cold;
  compute_P_cold__eps_cold(eos,rhob, P_cold,eps_cold);
  CCTK_REAL h = 1.0 + eps_cold + P_cold/rhob;

  /* Then compute gamma_v using equation (A19) in
   * Etienne et al. (2011) [https://arxiv.org/pdf/1112.0568.pdf]
   * .-----------------------------------------.
   * | gamma_v = psi^{-6} * (rho_star / rho_b) |
   * .-----------------------------------------.
   */
  CCTK_REAL gammav = CONSERVS[RHOSTAR]*Psim6/rhob;

  /* Finally, compute u_{i} */
  CCTK_REAL rhosh = CONSERVS[RHOSTAR]*h;
  CCTK_REAL fac1 = Psi6*BbardotS/(gammav*rhosh);
  CCTK_REAL fac2 = 1.0/(rhosh + Psi6*B2bar/gammav);
  u_x = fac2*(CONSERVS[STILDEX] + fac1*Bbar_x);
  u_y = fac2*(CONSERVS[STILDEY] + fac1*Bbar_y);
  u_z = fac2*(CONSERVS[STILDEZ] + fac1*Bbar_z);

  return 0;
}
```

    Appending to ../src/font_fix_gamma_law.C

## Step 5.e: The `font_fix__rhob_loop()` function \[Back to [top](#toc)\]
$$\label{font_fix__rhob_loop}$$

We now perform the following iterative process, which is again described in [Appendix A of Zachariah *et al.* (2012)](https://arxiv.org/pdf/1112.0568.pdf). We refer the reader to eqs. (A60), (A61), and (A62).

1. Store the previously computed values of $W_{n}$, $S_{{\rm fluid},n}^{2}$, and $\rho_{n}$
2. Compute $h = 1 + \epsilon + P/\rho_{n}$
3. Set
$$
\boxed{\rho_{n+1} = \psi^{-6}\rho_{\star}\left(1 + \frac{S_{{\rm fluid},n}^{2}}{\rho_{\star} h_{n}}\right)^{-1/2}}
$$

4. For a given value of $n$, perform steps 1 (for $\rho$), 2 and 3 until $\left|\rho_{n+1}-\rho_{n}\right| < \rho_{n+1}\epsilon$, where $\epsilon$ is a user given tolerance
5. After convergence is obtained, update:
$$
\boxed{
\begin{align}
h_{n+1} &= 1 + 2k_{\rm poly}\rho_{n+1}\\
W_{n+1} &= \psi^{-6}\sqrt{\tilde{S}^{2}_{{\rm fluid},n} + \rho_{\star}^{2} h_{n+1}^{2}}\\
S_{{\rm fluid},n+1}^{2} &= \frac{W^{2}_{n+1}\left(\tilde{S}\cdot\tilde{S}\right) + \left(\bar{B}\cdot\tilde{S}\right)^{2}\left(\bar{B}^{2} + 2W_{n+1}\right)}{\left(W_{n+1} + \bar{B}^{2}\right)^{2}}
\end{align}
}\ .
$$
6. Repeat steps 1 through 5 until $\left|W_{n+1}-W_{n}\right| < W_{n+1}\epsilon$ *and* $\left|S^{2}_{{\rm fluid},n+1}-S^{2}_{{\rm fluid},n}\right| < S^{2}_{{\rm fluid},n+1}\epsilon$ *or* we reach the maximum number of iterations

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

/* Function    : font_fix__rhob_loop()
 * Authors     : Leo Werneck
 * Description : Determines rhob using the font fix prescription
 * Dependencies: find_polytropic_K_and_Gamma_index()
 *             : compute_P_cold__eps_cold()
 * Reference   : Etienne et al. (2011) [https://arxiv.org/pdf/1112.0568.pdf]
 *
 * Inputs      : maxits          - maximum number of iterations allowed
 *             : tol             - font fix tolerance
 *             : W               - See eq. (A26)
 *             : Sf2             - S_{fluid}^{2}, see eq. (A24)
 *             : Psim6           - This is equal to sqrt(\gamma)
 *             : sdots           - \tilde{S}_{\mu}\tilde{S}^{\mu}
 *             : BbardotS2       - (\bar{B}^{\mu}S_{\mu})^{2},
 *             : B2bar           - \bar{B}^{2}, see eq. (A28)
 *             : CONSERVS        - Array of conservative variables
 *             : eos             - Struct of EOS parameters
 *             : rhob_in         - Initial value of rhob
 *             : rhob_out        - Output variable
 *
 * Outputs     : rhob_out        - Updated value of rhob
 *             : return value: 0 - Font fix worked
 *             : return value: 1 - Font fix failed
 */
inline int font_fix__rhob_loop( int maxits, CCTK_REAL tol,
                                CCTK_REAL W, CCTK_REAL Sf2, CCTK_REAL Psim6, CCTK_REAL sdots, CCTK_REAL BbardotS2, CCTK_REAL B2bar,
                                CCTK_REAL *CONSERVS,
                                eos_struct eos, CCTK_REAL rhob_in, CCTK_REAL &rhob_out ) {

  /* Declare basic variables */
  bool fontcheck=true;
  int itcount = 0, j0, j1;
  CCTK_REAL W0, Sf20, rhob0, rhob1, h, P_cold, eps_cold;

  //////////////////////
  // OUTER LOOP START //
  //////////////////////
  while(fontcheck && itcount < maxits) {

    /* Set variables to their input values */
    itcount++;
    W0    = W;
    Sf20  = Sf2;
    rhob1 = rhob_in;

    /* Based on rhob_in (i.e. rhob1), determine the
     * polytropic index j1
     */
    j1 = find_polytropic_K_and_Gamma_index(eos,rhob1);

    //////////////////////
    // INNER LOOP START //
    //////////////////////
    do {

      /* Set rhob0/j0 to be equal to the rhob/j used
       * in the previous iteration, i.e. rhob1/j1.
       */
      rhob0 = rhob1;
      j0    = j1;

      /* Compute h using h_cold and our polytropic EOS
       * .------------------------------------------.
       * | h = h_cold = 1 + eps_cold + P_cold/rhob. |
       * .------------------------------------------.
       */
      compute_P_cold__eps_cold(eos,rhob0, P_cold, eps_cold);
      h = 1.0 + eps_cold + P_cold/rhob0;

      /* Update rhob using eq. (A62) in Etienne et al. (2011)
       *          https://arxiv.org/pdf/1112.0568.pdf
       * .---------------------------------------------------------------------------.
       * | rhob = rho_star * Psi^{-6} / sqrt( 1 + S_fluid^{2}/( (rho_star*h)^{2} ) ) |
       * .---------------------------------------------------------------------------.
       */
      rhob1 = CONSERVS[RHOSTAR]*Psim6/sqrt(1.0+Sf20/SQR(CONSERVS[RHOSTAR]*h));

      /* Update j1 */
      j1 = find_polytropic_K_and_Gamma_index(eos,rhob1);

    }  while( fabs(rhob1-rhob0) > rhob1*tol || j1 != j0);
    //////////////////////
    //  INNER LOOP END  //
    //////////////////////

    /* Output the last value of rhob */
    rhob_out = rhob1;

    /* Perform physical checks on the variables
     * and output the last value of h obtained
     */
    compute_P_cold__eps_cold(eos,rhob_out, P_cold, eps_cold);
    h = 1.0 + eps_cold + P_cold/rhob_out;

    /* Set W based on eq. (A60) in Etienne et al. (2011)
     *       https://arxiv.org/pdf/1112.0568.pdf
     * .-------------------------------------------------------.
     * | W = psi^{-6} * sqrt( S_fluid^{2} + (rho_star*h)^{2} ) |
     * .-------------------------------------------------------.
     */
    W = sqrt( Sf20 + SQR(CONSERVS[RHOSTAR]*h))*Psim6;

    /* Then update S_{fluid}^{2} using eq. (A61) in Etienne et al. (2011)
     *           https://arxiv.org/pdf/1112.0568.pdf
     * .---------------------------------------------------------------------------.
     * | S_fluid^{2} = ( W^{2}*S^{2} + (B.S)^2*(B^{2} + 2W) )/( ( W + B^{2} )^{2} )|
     * .---------------------------------------------------------------------------.
     */
    Sf2 = (SQR(W)*sdots + BbardotS2*(B2bar + 2.0*W))/SQR(W+B2bar);

    if ( fabs(W-W0) < W*tol && fabs(Sf20-Sf2) < Sf2*tol) fontcheck=false;
  }
  //////////////////////
  //  OUTER LOOP END  //
  //////////////////////

  /* If the code converged before the max
   * number of iterations were exceeded,
   * return 0, otherwise return 1.
   */
  if(fontcheck || itcount >= maxits) {
    return 1;
  }
  else {
    return 0;
  }
}
```

    Appending to ../src/font_fix_gamma_law.C

# Step 6: `harm_primitives_headers.h` \[Back to [top](#toc)\]
$$\label{harm_primitives_headers}$$

Here we set all the function prototypes and constants we need to use the `HARM` files.

```python
%%writefile $outdir/harm_primitives_headers.h
/***********************************************************************************
    Copyright 2006 Charles F. Gammie, Jonathan C. McKinney, Scott C. Noble,
                   Gabor Toth, and Luca Del Zanna

                        HARM  version 1.0   (released May 1, 2006)

    This file is part of HARM.  HARM is a program that solves hyperbolic
    partial differential equations in conservative form using high-resolution
    shock-capturing techniques.  This version of HARM has been configured to
    solve the relativistic magnetohydrodynamic equations of motion on a
    stationary black hole spacetime in Kerr-Schild coordinates to evolve
    an accretion disk model.

    You are morally obligated to cite the following two papers in his/her
    scientific literature that results from use of any part of HARM:

    [1] Gammie, C. F., McKinney, J. C., \& Toth, G.\ 2003,
        Astrophysical Journal, 589, 444.

    [2] Noble, S. C., Gammie, C. F., McKinney, J. C., \& Del Zanna, L. \ 2006,
        Astrophysical Journal, 641, 626.

    Further, we strongly encourage you to obtain the latest version of
    HARM directly from our distribution website:
    http://rainman.astro.uiuc.edu/codelib/

    HARM is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    HARM is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with HARM; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

***********************************************************************************/
#ifndef HARM_PRIMITIVES_HEADERS_H_
#define HARM_PRIMITIVES_HEADERS_H_

static const int NPR =8;
static const int NDIM=4;

/* Adiabatic index used for the state equation */
//#define GAMMA    (2.0)

static const CCTK_REAL G_ISOTHERMAL = 1.0;

/* use K(s)=K(r)=const. (G_ATM = GAMMA) of time or  T = T(r) = const. of time (G_ATM = 1.) */
/*
  #define USE_ISENTROPIC 1

  #if( USE_ISENTROPIC )
  #define G_ATM GAMMA
  #else
  #define G_ATM G_ISOTHERMAL
  #endif
*/

static const int MAX_NEWT_ITER=30;     /* Max. # of Newton-Raphson iterations for find_root_2D(); */
//#define MAX_NEWT_ITER 300     /* Max. # of Newton-Raphson iterations for find_root_2D(); */
static const CCTK_REAL NEWT_TOL    =1.0e-10;    /* Min. of tolerance allowed for Newton-Raphson iterations */
static const CCTK_REAL MIN_NEWT_TOL=1.0e-10;    /* Max. of tolerance allowed for Newton-Raphson iterations */
static const int EXTRA_NEWT_ITER=0; /* ZACH SAYS: Original value = 2. But I don't think this parameter > 0 is warranted. Just slows the code for no reason, since our tolerances are fine. */

static const CCTK_REAL NEWT_TOL2    =1.0e-15;      /* TOL of new 1D^*_{v^2} gnr2 method */
static const CCTK_REAL MIN_NEWT_TOL2=1.0e-10;  /* TOL of new 1D^*_{v^2} gnr2 method */

static const CCTK_REAL W_TOO_BIG    =1.e20;    /* \gamma^2 (\rho_0 + u + p) is assumed
                                                  to always be smaller than this.  This
                                                  is used to detect solver failures */
static const CCTK_REAL UTSQ_TOO_BIG =1.e20;    /* \tilde{u}^2 is assumed to be smaller
                                                  than this.  Used to detect solver
                                                  failures */

static const CCTK_REAL FAIL_VAL     =1.e30;    /* Generic value to which we set variables when a problem arises */

static const CCTK_REAL NUMEPSILON=(2.2204460492503131e-16);

/* some mnemonics */
/* for primitive variables */
static const int RHO    =0;
static const int UU     =1;
static const int UTCON1 =2;
static const int UTCON2 =3;
static const int UTCON3 =4;
static const int BCON1  =5;
static const int BCON2  =6;
static const int BCON3  =7;

/* for conserved variables */
static const int QCOV0  =1;
static const int QCOV1  =2;
static const int QCOV2  =3;
static const int QCOV3  =4;

/********************************************************************************************/
// Function prototype declarations:
int Utoprim_2d(eos_struct eos, CCTK_REAL U[NPR], CCTK_REAL gcov[NDIM][NDIM], CCTK_REAL gcon[NDIM][NDIM],
               CCTK_REAL gdet, CCTK_REAL prim[NPR], long &n_iter);

inline int harm_primitives_gammalaw_lowlevel(/*const int index,const int i,const int j,const int k,CCTK_REAL *X,CCTK_REAL *Y,CCTK_REAL *Z,*/
                                             CCTK_REAL *ADM_3METRIC,
                                             CCTK_REAL *CONSERVS,CCTK_REAL *PRIMS,
                                             CCTK_REAL g4dn[NDIM][NDIM],CCTK_REAL g4up[NDIM][NDIM],
                                             output_stats &stats,eos_struct &eos);

inline int font_fix__hybrid_EOS(CCTK_REAL &u_x, CCTK_REAL &u_y, CCTK_REAL &u_z,CCTK_REAL *CONSERVS,CCTK_REAL *PRIMS,CCTK_REAL *ADM_3METRIC, eos_struct eos);
void eigenvalues_3by3_real_sym_matrix(CCTK_REAL & lam1, CCTK_REAL & lam2, CCTK_REAL & lam3,
                                      CCTK_REAL M11, CCTK_REAL M12, CCTK_REAL M13, CCTK_REAL M22, CCTK_REAL M23, CCTK_REAL M33);

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

#endif /* HARM_PRIMITIVES_HEADERS_H_ */

```

    Overwriting ../src/harm_primitives_headers.h

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

## Step 7.a: `driver_conserv_to_prims.C` \[Back to [top](#toc)\]
$$\label{driver_conserv_to_prims_validation}$$

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

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

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

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

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

# # Perform validation
# Validation__driver_conserv_to_prims__C  = !diff $original_IGM_file_path $outdir/driver_conserv_to_prims.C

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

## Step 7.b: `harm_primitives_lowlevel.C` \[Back to [top](#toc)\]
$$\label{harm_primitives_lowlevel_validation}$$

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

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

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

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

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

# # Perform validation
# Validation__harm_primitives_lowlevel__C  = !diff $original_IGM_file_path $outdir/harm_primitives_lowlevel.C

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

## Step 7.c: `font_fix_gamma_law.C` \[Back to [top](#toc)\]
$$\label{font_fix_gamma_law_validation}$$

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

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

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

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

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

# # Perform validation
# Validation__font_fix_gamma_law__C  = !diff $original_IGM_file_path $outdir/font_fix_gamma_law.C

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

## Step 7.d: `harm_primitives_headers.h` \[Back to [top](#toc)\]
$$\label{harm_primitives_headers_validation}$$

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

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

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

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

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

# # Perform validation
# Validation__harm_primitives_headers__h  = !diff $original_IGM_file_path $outdir/harm_primitives_headers.h

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

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

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

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