# Tutorial: Reading and validating [LORENE](https://lorene.obspm.fr/) binary neutron star initial data

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

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

This notebook is organized as follows

1. [Step 1](#initializenrpy): Initialize core Python/NRPy+ modules and parameters
1. [Step 2](#adding_cfuncs_to_dict): Adding C functions to the dictionary
    1. [Step 2.a](#cfunc__id_lorene_adm_quantities): `ID_Lorene_ADM_quantities_Cartesian`
    1. [Step 2.b](#cfunc__set_hydro_quantities): `set_hydro_quantities`
    1. [Step 2.c](#cfunc__initial_data): `initial_data`
    1. [Step 2.d](#cfunc__hamiltonian_constraint_source_term): `Hamiltonian_constraint_source_term`
        1. [Step 2.d.i](#hydro_quantities_eos): The equation of state & derived hydrodynamics quantities
        1. [Step 2.d.ii](#grmhd_rho): The energy density $\rho$
        1. [Step 2.d.iii](#adding_ham_constraint_source_term_to_dict): Adding the function to the dictionary
    1. [Step 2.e](#cfunc__hamiltonian_constraint_no_source_term): `Hamiltonian_constraint_no_source_term`
        1. [Step 2.e.i](#hamiltonian_constraint_no_source_symb): The Hamiltonian constraint without source terms
        1. [Step 2.e.ii](#adding_ham_constraint_no_source_term_to_dict): Adding the function to the dictionary
    1. [Step 2.f](#cfunc__main): `main`
1. [Step 3](#ccode_kernels_generation): C code kernels generation
    1. [Step 3.a](#cparams_rfm_and_domainsize): Set `free_parameters.h`; also output C codes needed for declaring and setting Cparameters
    1. [Step 3.b](#add_all): Add all NRPy+Lorene BNS initial data C codes to C function dictionary
    1. [Step 3.c](#generate_c_code): Generating C code for setting Lorene BNS initial data in NRPy+
1. [Step 4](#compiling_and_running): Compiling and running the code
1. [Step 5](#constraint_violations): Visualization: convergence of Hamiltonian constraint
1. [Step 6](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file

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

Let's start by importing all the needed modules from Python/NRPy+:

```python
# Step 1: Initialize core Python/NRPy+ modules and parameters
# Step 1.a: Add the NRPy+ base directory to the path
import os,sys,shutil            # Standard Python modules for multiplatform OS-level functions
nrpy_dir_path = os.path.join("..")
if nrpy_dir_path not in sys.path:
    sys.path.append(nrpy_dir_path)

# Step 1.b: Import core NRPy+ modules
import sympy as sp              # SymPy: The Python computer algebra package upon which NRPy+ depends
import outputC as outC          # NRPy+: Core C code output module
import finite_difference as fin # NRPy+: Finite difference C code generation module
import cmdline_helper as cmd    # NRPy+: Multi-platform Python command-line interface
import reference_metric as rfm  # NRPy+: Reference metric support
import NRPy_param_funcs as par  # NRPy+: Parameter interface
import indexedexp as ixp        # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
import grid as gri              # NRPy+: Functions having to do with numerical grids

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

# Step 1.d: Check if the initial data file exists
if not os.path.exists(os.path.join("lorene_standalone", "resu.d")):
    print("""# Error: resu.d not found.
# Be sure to go into nrpytutorial
# and run:
cd in_progress/lorene_standalone
# For "Lorene1": wget http://astro.phys.wvu.edu/zetienne/resu.d
# Lorene2 (latest):
wget --no-check-certificate https://ccrgpages.rit.edu/~jfaber/BNSID/Data/simple_polytrope/gam2.5/gam2.5_1.4_1.4/gam2.5_1.4_1.4_hr/gam2.5_1.4_1.4_hr_50/resu_5.000000e+01_1.520000e+00_1.520000e+00.d -O resu.d
""")
    sys.exit(1)

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

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

# Step P3: Set finite difference order
FD_order = 4
par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER",FD_order)

# Step P4: Enable rfm_precompute
enable_rfm_precompute = True

# Step P5: Disable FD_functions
enable_FD_functions = False

# Step P6: Disable SIMD
enable_SIMD = False

# Step P7: Parameter used to compute dt_min
thismodule = "Lorene_ID"
_wavespeed = par.Cparameters("REAL",thismodule,"wavespeed",1.0)
CFL_FACTOR = 0.5

# Step P8: Set grid parameters defaults
# Step P8.a: domain_size sets the default value for:
#   * Spherical's params.RMAX
#   * SinhSpherical*'s params.AMAX
#   * Cartesians*'s -params.{x,y,z}min & .{x,y,z}max
#   * Cylindrical's -params.ZMIN & .{Z,RHO}MAX
#   * SinhCylindrical's params.AMPL{RHO,Z}
#   * *SymTP's params.AMAX
domain_size     = 12.5 # Needed for all coordinate systems.

# Step P8.b: sinh_width sets the default value for:
#   * SinhSpherical's params.SINHW
#   * SinhCylindrical's params.SINHW{RHO,Z}
#   * SinhSymTP's params.SINHWAA
sinh_width      = 0.2 # If Sinh* coordinates chosen

# Step P8.c: sinhv2_const_dr sets the default value for:
#   * SinhSphericalv2's params.const_dr
#   * SinhCylindricalv2's params.const_d{rho,z}
sinhv2_const_dr = 0.05 # If Sinh*v2 coordinates chosen

# Step P8.d: SymTP_bScale sets the default value for:
#   * SinhSymTP's params.bScale
SymTP_bScale    = 1.0 # If SymTP chosen

# Step P9: Create rfm_files directory, if rfm_precompute is enabled
if enable_rfm_precompute:
    cmd.mkdir(os.path.join(Ccodesdir, "rfm_files/"))
    par.set_parval_from_str("reference_metric::rfm_precompute_to_Cfunctions_and_NRPy_basic_defines","True")
    par.set_parval_from_str("reference_metric::rfm_precompute_Ccode_outdir", os.path.join(Ccodesdir, "rfm_files/"))

# Step P10: Create SIMD directory and copy intrinsics, if SIMD is enabled
if enable_SIMD:
    cmd.mkdir(os.path.join(Ccodesdir,"SIMD"))
    shutil.copy(os.path.join("SIMD/")+"SIMD_intrinsics.h",os.path.join(Ccodesdir,"SIMD/"))
```

# Step 2: Adding C functions to the dictionary \[Back to [top](#toc)\]
$$\label{adding_cfuncs_to_dict}$$

We will now add all C functions that we will need to read in the [LORENE](https://lorene.obspm.fr/) initial data and interpolate it onto the NRPy+ grids.

## Step 2.a: `ID_Lorene_ADM_quantities_Cartesian` \[Back to [top](#toc)\]
$$\label{cfunc__id_lorene_adm_quantities}$$

The first function we will write is the one that sets up the ADM quantities from the [LORENE](https://lorene.obspm.fr/) initial data file, typically named `resu.d`. NRPy+ needs us to define the following ADM quantities:

$$
\left(\alpha,\beta^{i},\gamma_{ij},K_{ij}\right),
$$

where $\alpha$ is the lapse function, $\beta^{i}$ is the shift vector, $\gamma_{ij}$ is the physical spatial metric, and $K_{ij}$ is the extrinsic curvature.

We note here that these quantities will all be read from the [LORENE](https://lorene.obspm.fr/) initial data file and the interpolated onto the NRPy+ grid in the `main` function [below](#cfunc__main).

In this step, we write the function `ID_Lorene_ADM_quantities_Cartesian`, which is required by the C functions generated by the [`ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py`](ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py) \[[**edit**](../edit/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py)\] NRPy+ module. The goal of this function is to initialize local variables for the ADM quantities above from a user-defined struct of type `ID_inputs`. In our case this is a very simple task since our struct, which is defined in [Step 3.c](#ccode_kernels__defines_prototypes) below, contains pointers to the [LORENE](https://lorene.obspm.fr/) gridfunctions arrays, and therefore all we need to do is copy over the data to our local variables at the particular gridpoint we are interested in.

Note that we also initialize $B^{i}$, which is used by the second-order Gamma-driver shift condition, to zero.

```python
# Step 2: Adding C functions to the directionary
# Step 2.a: Adding ID_Lorene_ADM_quantities_Cartesian to the C functions dictionary
def add_to_Cfunction_dict_ID_Lorene_ADM_quantities_Cartesian():
    desc = """
(c) 2021 Leo Werneck
This function sets the initial data for all ADM quantities.
"""
    includes = ["NRPy_basic_defines.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "ID_Lorene_ADM_quantities_Cartesian"

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

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

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

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

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

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

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

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

## Step 2.b: `set_hydro_quantities` \[Back to [top](#toc)\]
$$\label{cfunc__set_hydro_quantities}$$

We now focus on the task of setting up the hydro quantities needed by our code, namely

$$
\left(\rho_{\rm b},u^{\mu},b^{\mu}\right),
$$

where $\rho_{\rm b}$ is the baryonic density, $u^{\mu}$ is the fluid four-velocity, and $b^{\mu}$ is related to the magnetic field $B^{i}$ via (see e.g. Eqs. 31, 23, and 24 of [Duez *et al*. (2005)](https://arxiv.org/pdf/astro-ph/0503420.pdf))

$$
\begin{align}
b^{\mu} &= \frac{B^{\mu}_{(u)}}{\sqrt{4\pi}},\\
B^{0}_{(u)} &= \frac{u_{i}B^{i}}{\alpha},\\
B^{i}_{(u)} &= \frac{B^{i}/\alpha + B^{0}_{(u)}u^{i}}{u^{0}}.
\end{align}
$$

We will assume that our initial data is unmagnetized and therefore will set $b^{\mu} = 0$. Note also that other hydro quantities, such as the pressure $P$ and the specific internal energy $\epsilon$, can be computed from the base quantities above and therefore we will not store them in gridfunctions.

The [LORENE](https://lorene.obspm.fr/) initial data file contains the base hydro quantities that we need in our code, namely the baryonic density $\rho_{\rm b}$ and the fluid 3-velocity in the Eulerian reference frame $v^{i}_{(n)}$. We are then left with the task of computing $u^{\mu}$ from the input data, a task which we now explain in detail.

After reading in the local value of $v^{i}_{(n)}$ must determine $u^{0}$. This is done by first remembering that the [Lorentz factor](https://en.wikipedia.org/wiki/Lorentz_factor), $W$, is given by

$$
W = \frac{1}{\sqrt{1 - \gamma_{ij}v^{i}_{(n)}v^{j}_{(n)}}}.
$$

Furthermore, remember that (see e.g. Eq. 15 in [Noble *et al*. (2006)](https://arxiv.org/pdf/astro-ph/0512420.pdf), noticing that they use the notation $W\to\gamma$)

$$
W = -n_{\mu}u^{\mu} = \alpha u^{0},
$$

where $n_{\mu}=\left(\alpha,0,0,0\right)$ is the unit vector normal to spatial hypersurfaces. We thus find the identity

$$
\frac{1}{W^{2}} = \frac{1}{\left(\alpha u^{0}\right)^{2}} = 1 - \gamma_{ij}v^{i}_{(n)}v^{j}_{(n)}
\implies
A \equiv 1 - \frac{1}{\left(\alpha u^{0}\right)^{2}} = \gamma_{ij}v^{i}_{(n)}v^{j}_{(n)}.
$$

After $A$ is computed we can determine $u^{0}$ trivially using

$$
\frac{1}{\left(\alpha u^{0}\right)^{2}} = 1 - A \implies u^{0} = \frac{1}{\alpha\sqrt{1-A}}.
$$

At this point we can compute the fluid 3-velocity $u^{i}$ using (see e.g. Eq. 10 [Etienne *et al.* (2015)](https://arxiv.org/pdf/1501.07276.pdf))

$$
u^{i} = u^{0}\left(\alpha v^{i}_{(n)} - \beta^{i}\right).
$$

```python
# Step 2.b: Adding set_hydro_quantities to the C functions dictionary
def add_to_Cfunction_dict_set_hydro_quantities():
    desc = """
(c) 2021 Leo Werneck
This function sets the initial data for all hydro quantities.
"""
    includes = ["NRPy_basic_defines.h","bin_ns.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "set_hydro_quantities"
    params   = """const paramstruct *restrict params, const ID_inputs other_inputs, REAL *restrict aux_gfs"""
    body     = r"""
    // Set the index
    const int idx = IDX3S(i0,i1,i2);

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

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

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

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

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

    // Now compute u^{mu}
    // Remember that: W = alpha u^{0} => u^{0} = W/alpha
    const REAL u4U0     = W/alpL;
    const REAL u4U1     = u4U0 * ( velx * alpL - betaxL );
    const REAL u4U2     = u4U0 * ( vely * alpL - betayL );
    const REAL u4U3     = u4U0 * ( velz * alpL - betazL );

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

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

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

## Step 2.c: `initial_data` \[Back to [top](#toc)\]
$$\label{cfunc__initial_data}$$

We now write the core initial data driver function, which is the only one that the user has to directly use to set up initial data. This function performs the following tasks:

1. Initializes all BSSN curvilinear quantities from the input ADM quantities. This is a two-step process, where the functions `ID_BSSN__ALL_BUT_LAMBDAs` and `ID_BSSN_lambdas`, defined by the [`ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py`](ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py) \[[**edit**] NRPy+ module, are called.
1. Initializes all hydro quantities. This is done by calling the `set_hydro_quantities` function that we have written in [Step 2.b](cfunc__set_hydro_quantities).

```python
# Step 2.c: initial_data
# Step 2.c.i: First add the core NRPy+ ADM_Cartesian to
#             BSSN_Curvilinear C functions to the dictionary
import ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear as atob
rfm.reference_metric()
atob.Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear("Cartesian","ID_Lorene_ADM_quantities_Cartesian",
                                                            Ccodesdir=Ccodesdir,loopopts="")

# Step 2.c.ii: Adding initial_data to the C functions dictionary
def add_to_Cfunction_dict_initial_data():
    desc     = """
(c) 2021 Leo Werneck
This is the initial data driver and is responsible for setting
all metric and matter field on the initial hypersurface.
"""
    includes = ["NRPy_basic_defines.h","NRPy_function_prototypes.h",
                "ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",
                "ID_BSSN__ALL_BUT_LAMBDAs.h",
                "ID_BSSN_lambdas.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "initial_data"

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

    params   = """const paramstruct *restrict params,REAL *restrict xx[3],ID_inputs other_inputs,
                  REAL *restrict in_gfs,REAL *restrict aux_gfs"""
    body     = r"""

    // Set up hydro quantities
    set_hydro_quantities(params,other_inputs,aux_gfs);

    // Set up BSSN quantities
    ID_BSSN__ALL_BUT_LAMBDAs(params,xx,other_inputs,in_gfs);
    ID_BSSN_lambdas(         params,xx,             in_gfs);
"""
    outC.add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body,enableCparameters=False)

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

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

## Step 2.d: `Hamiltonian_constraint_source_term` \[Back to [top](#toc)\]
$$\label{cfunc__hamiltonian_constraint_source_term}$$

We now focus on the computation of the energy density $\rho$ which appears on the right-hand side of the [Hamiltonian constriant](../Tutorial-BSSN_stress_energy_source_terms.ipynb). The definition of the energy density is (cf. Eq. 10a in [Baumgarte *et al.* (2013)](https://arxiv.org/pdf/1211.6632.pdf))

$$
\rho \equiv n_{\mu}n_{\nu}T^{\mu\nu}.
$$

Here, $T^{\mu\nu} = T^{\mu\nu}_{\rm GRMHD}$ is the energy-momentum tensor of general relativistic magnetohydrodynamics (GRMHD) and is given by (cf. Eq. 33 in [Duez *et al*. (2005)](https://arxiv.org/pdf/astro-ph/0503420.pdf))

$$
T^{\mu\nu}_{\rm GRMHD} = \left(\rho_{b}h + b^{2}\right)u^{\mu}u^{\nu} + \left(P^{2} + \frac{b^{2}}{2}\right)g^{\mu\nu} - b^{\mu}b^{\nu},
$$

where $h = 1 + \epsilon + P/\rho$ is the specific enthalpy, $P$ is the pressure, $b^{2}\equiv b_{\mu}b^{\mu}$, and $g^{\mu\nu}$ is the inverse of the spacetime metric $g_{\mu\nu}$.

### Step 2.d.i: The equation of state & derived hydrodynamics quantities \[Back to [top](#toc)\]
$$\label{hydro_quantities_eos}$$

We must now compute some of the derived hydrodynamics quantities defined above, such as the pressure and specific internal energy. We compute these quantities by employing an [equation of state (EOS)](https://en.wikipedia.org/wiki/Equation_of_state). We will assume here a very simple EOS which is known as a [simple polytrope](https://en.wikipedia.org/wiki/Polytrope). This EOS relates the pressure with the baryonic density via

$$
P(\rho_{\rm b}) = K \rho_{\rm b}^{\Gamma},
$$

where $K$ is a constant of proportinality and $\Gamma$ is the adiabatic index. From this we can determine the specific internal energy using

$$
\epsilon = \int d\rho_{\rm b} \frac{P}{\rho_{\rm b}^{2}} = K\int d\rho_{\rm b} \rho_{\rm b}^{\Gamma-2} = \frac{K\rho_{\rm b}^{\Gamma-1}}{\Gamma-1} = \frac{P}{\rho_{\rm b}\left(\Gamma-1\right)},
$$

where we have fixed the integration constant to zero by demanding that $\lim_{\rho_{\rm b}\to0}\epsilon=0$.

```python
# Step 2.d.i: Derived hydro quantities
# Step 2.d.i.A: Register rho_b as an auxiliary gridfunction
rho_b = gri.register_gridfunctions("AUX","rhob")

# Step 2.d.i.B: Define K and Gamma as symbols
K,Gamma = sp.symbols("K Gamma",real=True)

# Step 2.d.i.C: Compute the pressure: P = K rho_{b}^Gamma
P = K * rho_b**Gamma

# Step 2.d.i.D: Compute the specific internal energy: epsilon = P/( rho_{b}(Gamma-1) )
epsilon = P / ( rho_b*(Gamma-1) )
```

### Step 2.d.ii: The energy density $\rho$ \[Back to [top](#toc)\]
$$\label{grmhd_rho}$$

We now compute the symbolic expressions for the GRMHD energy density $\rho$. This requires a few steps, which we outline below:

1. Define symbolic expressions for the ADM variables used by the C code
1. Define symbolic expressions for $u^{\mu}$ and $b^{\mu}$ used by the C code
1. Use the function `compute_smallbsquared` from the [GRFFE/equations.py](../GRFFE/equations.py) \[[**edit**](../edit/GRFFE/equations.py), [**tutorial**](../Tutorial-GRFFE_Equations-Cartesian.ipynb)\] NRPy+ module to compute $b^{2}$.
1. Use the function `compute_GRMHD_T4UU` from the [GRMHD/equations.py](../GRMHD/equations.py) \[[**edit**](../edit/GRMHD/equations.py), [**tutorial**](../Tutorial-GRMHD_Equations-Cartesian.ipynb)\] NRPy+ module to compute $T^{\mu\nu}_{\rm GRMHD}$.
1. Declare symbolic expressions for $n_{\mu} = \left(-\alpha,0,0,0\right)$.
1. Compute $\rho = n_{\mu}n_{\nu}T^{\mu\nu}_{\rm GRMHD}$.

```python
# Step 2.d.ii: The energy density rho
# Step 2.d.ii.A: Import GRFFE/equations.py and GRMHD/equations.py NRPy+ modules
import GRFFE.equations as GRFFE
import GRMHD.equations as GRMHD
import BSSN.ADMBSSN_tofrom_4metric as AB4m

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

gammaUU,_ = ixp.symm_matrix_inverter3x3(gammaDD)

# Step 2.d.ii.C: Define symbolic expressions for hydro quantities
u4U      = ixp.register_gridfunctions_for_single_rank1("AUX","u4U",DIM=4)
smallb4U = ixp.register_gridfunctions_for_single_rank1("AUX","smallb4U",DIM=4)

# Step 2.d.ii.D: b^{2} = b_{\mu}b^{\mu}
GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, smallb4U)

# Step 2.d.ii.E: Compute the GRMHD energy-momentum tensor
GRMHD.compute_GRMHD_T4UU(gammaDD,betaU,alpha, rho_b,P,epsilon,u4U, smallb4U, GRFFE.smallbsquared)

# Step 2.d.ii.F: ADM 4-metric
AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD=gammaDD,betaU=betaU,alpha=alpha)

# Step 2.d.ii.G: Unit 4-vector
n4D    = ixp.zerorank1(DIM=4)
n4D[0] = -alpha

# Step 2.d.ii.H: Induced metric
gamma4DD = ixp.zerorank2(DIM=4)
for mu in range(4):
    for nu in range(4):
        gamma4DD[mu][nu] = AB4m.g4DD[mu][nu] + n4D[mu] * n4D[nu]

# Step 2.d.ii.I: Energy density
rhoADM = sp.sympify(0)
for mu in range(4):
    for nu in range(4):
        rhoADM += n4D[mu] * n4D[nu] * GRMHD.T4UU[mu][nu]

# Step 2.d.ii.J: Momentum density
SD = ixp.zerorank1()
for i in range(3):
    for mu in range(4):
        for nu in range(4):
            SD[i] += - gamma4DD[i+1][mu] * n4D[nu] * GRMHD.T4UU[mu][nu]

# Step 2.d.ii.L: S^{i} = gamma^{ij}S_{j}
SU = ixp.zerorank1()
for i in range(3):
    for j in range(3):
        SU[i] += gammaUU[i][j] * SD[j]

# Step 2.d.ii.K: Sources
M_PI     = par.Cparameters("REAL", thismodule, ["M_PI"], "3.14159265358979323846264338327950288")
sourceH  = -16 * M_PI * rhoADM
sourceMU = ixp.zerorank1()
for i in range(3):
    sourceMU[i] = -8 * M_PI * SU[i] / rfm.ReU[i]
```

### Step 2.d.iii: Adding the function to the dictionary \[Back to [top](#toc)\]
$$\label{adding_ham_constraint_source_term_to_dict}$$

Having defined everything we need, we now add the function `Hamiltonian_constraint_source_term` to our C function dictionary.

```python
# Step 2.e.i: The Hamiltonian constraint without source terms
# Step 2.e.i.A: Import the BSSN/BSSN_constraints.py NRPy+ module
import BSSN.BSSN_constraints as bssncon
import BSSN.BSSN_stress_energy_source_terms as Bsest

# Step 2.e.i.B: Adjust reference metric environment if rfm_precompute is enabled
if enable_rfm_precompute:
    par.set_parval_from_str("reference_metric::enable_rfm_precompute","True")
    rfm.reference_metric()

# Step 2.e.i.C: Now register the Hamiltonian constraint as an auxiliary gridfunction
H  = gri.register_gridfunctions("AUX","H")
MU = ixp.register_gridfunctions_for_single_rank1("AUX","MU")
_  = gri.register_gridfunctions("AUX","sourceH")
_  = ixp.register_gridfunctions_for_single_rank1("AUX","sourceMU")

# Step 2.e.i.D: Set symbolic expressions for the constraints
bssncon.BSSN_constraints(add_T4UUmunu_source_terms=False)

# Step 2.e.i.E: Reset the reference metric environment if rfm_precompute is enabled
if enable_rfm_precompute:
    par.set_parval_from_str("reference_metric::enable_rfm_precompute","False")
    rfm.ref_metric__hatted_quantities()
```

## Step 2.e: `Hamiltonian_constraint_no_source_term` \[Back to [top](#toc)\]
$$\label{cfunc__hamiltonian_constraint_no_source_term}$$

```python
# Step 2.d.iii: Adding Hamiltonian_constraint_source_term
#               to the C functions dictionary
def add_to_Cfunction_dict_Hamiltonian_and_momentum_constraints_source_terms():
    desc = """
(c) 2021 Leo Werneck
This function computes the energy density rho, which appears
in the source term of the Hamiltonian constraint.
"""
    includes = ["NRPy_basic_defines.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "Hamiltonian_and_momentum_constraints_source_terms"

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

    params   = """rfm_struct *restrict rfmstruct,const paramstruct *restrict params,
                  const REAL K, const REAL Gamma, ID_inputs other_inputs,
                  REAL *restrict aux_gfs"""
    body     = """
    const int idx        = IDX3S(i0,i1,i2);
    const REAL rhob      = aux_gfs[IDX4ptS(RHOBGF,idx)];
    const REAL u4U0      = aux_gfs[IDX4ptS(U4U0GF,idx)];
    const REAL u4U1      = aux_gfs[IDX4ptS(U4U1GF,idx)];
    const REAL u4U2      = aux_gfs[IDX4ptS(U4U2GF,idx)];
    const REAL u4U3      = aux_gfs[IDX4ptS(U4U3GF,idx)];
    const REAL smallb4U0 = aux_gfs[IDX4ptS(SMALLB4U0GF,idx)];
    const REAL smallb4U1 = aux_gfs[IDX4ptS(SMALLB4U1GF,idx)];
    const REAL smallb4U2 = aux_gfs[IDX4ptS(SMALLB4U2GF,idx)];
    const REAL smallb4U3 = aux_gfs[IDX4ptS(SMALLB4U3GF,idx)];

"""+outC.outputC([sourceH,
                  sourceMU[0],
                  sourceMU[1],
                  sourceMU[2]],
                 [gri.gfaccess("aux_gfs", "sourceH"),
                  gri.gfaccess("aux_gfs", "sourceMU0"),
                  gri.gfaccess("aux_gfs", "sourceMU1"),
                  gri.gfaccess("aux_gfs", "sourceMU2")], "returnstring",
                 params="outCverbose=False,includebraces=False")
    loopopts = "InteriorPoints"
    if enable_SIMD:
        loopopts +=",enable_SIMD"
    if enable_rfm_precompute:
        loopopts +=",enable_rfm_precompute"
    outC.add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body,loopopts=loopopts)

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

### Step 2.e.i: The Hamiltonian constraint without source terms \[Back to [top](#toc)\]
$$\label{hamiltonian_constraint_no_source_symb}$$

We now declare the symbolic expression for the Hamiltonian constraint by invoking the [`BSSN/BSSN_constraints.py`](../BSSN/BSSN_constraints.py) \[[**edit**](../edit/BSSN/BSSN_constraints.py), [**tutorial**](../Tutorial-BSSN_constraints.ipynb)\] NRPy+ module.

```python

```

### Step 2.e.ii: Adding the function to the dictionary \[Back to [top](#toc)\]
$$\label{adding_ham_constraint_no_source_term_to_dict}$$

Having defined everything we need, we now add the function `Hamiltonian_constraint_no_source_term` to our C function dictionary.

```python
# Step 2.e.ii: Adding Hamiltonian_constraint_no_source_term
#              to the C functions dictionary
def add_to_Cfunction_dict_Hamiltonian_and_momentum_constraints_no_source_terms():
    desc = """
(c) 2021 Leo Werneck
This function computes the metric terms of the Hamiltonian constraint.
"""
    includes = ["NRPy_basic_defines.h"]
    prefunc  = ""
    c_type   = "void"
    name     = "Hamiltonian_and_momentum_constraints_no_source_terms"

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

    params   = """rfm_struct *restrict rfmstruct,const paramstruct *restrict params,
                  REAL *restrict in_gfs, REAL *restrict aux_gfs"""
    body     = """
    const int idx        = IDX3S(i0,i1,i2);
    const REAL rhob      = aux_gfs[IDX4ptS(RHOBGF,idx)];
    const REAL u4U0      = aux_gfs[IDX4ptS(U4U0GF,idx)];
    const REAL u4U1      = aux_gfs[IDX4ptS(U4U1GF,idx)];
    const REAL u4U2      = aux_gfs[IDX4ptS(U4U2GF,idx)];
    const REAL u4U3      = aux_gfs[IDX4ptS(U4U3GF,idx)];
    const REAL smallb4U0 = aux_gfs[IDX4ptS(SMALLB4U0GF,idx)];
    const REAL smallb4U1 = aux_gfs[IDX4ptS(SMALLB4U1GF,idx)];
    const REAL smallb4U2 = aux_gfs[IDX4ptS(SMALLB4U2GF,idx)];
    const REAL smallb4U3 = aux_gfs[IDX4ptS(SMALLB4U3GF,idx)];
\n"""+fin.FD_outputC("returnstring",
                     [outC.lhrh(lhs=gri.gfaccess("aux_gfs", "H")  , rhs=bssncon.H),
                      outC.lhrh(lhs=gri.gfaccess("aux_gfs", "MU0"), rhs=bssncon.MU[0]),
                      outC.lhrh(lhs=gri.gfaccess("aux_gfs", "MU1"), rhs=bssncon.MU[1]),
                      outC.lhrh(lhs=gri.gfaccess("aux_gfs", "MU2"), rhs=bssncon.MU[2])],
                      params="outCverbose=False")
    loopopts = "InteriorPoints,enable_rfm_precompute"
    outC.add_to_Cfunction_dict(
        includes=includes,
        prefunc=prefunc,
        desc=desc,
        c_type=c_type, name=name, params=params,
        body=body,loopopts=loopopts)

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

## Step 2.f: `main` \[Back to [top](#toc)\]
$$\label{cfunc__main}$$

We now add the `main` function to the C functions dictionary. This function combines all the other functions that we have defined this far into a C program that is able to:

1. Set a NRPy+ grid.
1. Convert the NRPy+ grid to Cartesian coordinates, which is the coordinate system expected by [LORENE](https://lorene.obspm.fr/).
1. Read the initial data from the input `resu.d` file and interpolate the solution onto the NRPy+ grid.
1. Set up BSSN and GRMHD quantities from the input.
1. Compute the Hamiltonian constraint violations on the NRPy+ grid.
1. Output the results to file.

```python
# Step 2.f: Adding main to the C functions dictionary
def add_to_Cfunction_dict_main():
    print("Writing main function")
    desc = """
(c) 2009 Erik Schnetter
(c) 2010 Frank Loeffler
Edits by Z Etienne & L Werneck 2021
"""

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

    prefunc = r"""
using namespace std;

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

    c_type = "int"
    name = "main"

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

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

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

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

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

  printf("coord_unit = %.15e\n",coord_unit);

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

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

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

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

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

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

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

    // Step 0.e: Set up NRPy parameter struct
    paramstruct params;
    set_Cparameters_to_default(&params);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    assert(bin_ns.np == Nxx_plus_2NGHOSTS_tot);

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

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

  {
    FILE * fp = fopen("vx_vy_vz_NRPy.x.asc","w");
    const int j = Nxx_plus_2NGHOSTS1/2;
    const int k = Nxx_plus_2NGHOSTS2/2;
    for(int i=NGHOSTS;i<Nxx_plus_2NGHOSTS0-NGHOSTS;i++) {
      const int ijk = IDX3S(i,j,k);
      REAL xCart[3];
      xx_to_Cart(&params,xx,i,j,k,xCart);
      fprintf(fp,"%e %e %e %e\n",xCart[0],
              other_inputs.u_euler_x[ijk],
              other_inputs.u_euler_y[ijk],
              other_inputs.u_euler_z[ijk]);
    }
    fclose(fp);
  }

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

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

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

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

        const int idx = IDX3S(i0,i1_out,i2_out);
        const REAL H     = aux_gfs[IDX4ptS(HGF,idx)] + aux_gfs[IDX4ptS(SOURCEHGF,idx)];
        const REAL Mx    = aux_gfs[IDX4ptS(MU0GF,idx)] - aux_gfs[IDX4ptS(SOURCEMU0GF,idx)];
        const REAL My    = aux_gfs[IDX4ptS(MU1GF,idx)] - aux_gfs[IDX4ptS(SOURCEMU1GF,idx)];
        const REAL Mz    = aux_gfs[IDX4ptS(MU2GF,idx)] - aux_gfs[IDX4ptS(SOURCEMU2GF,idx)];
        const REAL rho_b = aux_gfs[IDX4ptS(RHOBGF,idx)];
        const REAL alp   = id_gfs[IDX4ptS(ALPHAGF,idx)];
        const REAL cf    = id_gfs[IDX4ptS(CFGF,idx)];
        const REAL cfm2  = 1.0/(cf*cf);
        const REAL gxx   = cfm2*(1.0 + id_gfs[IDX4ptS(HDD00GF,idx)]);
        const REAL gyy   = cfm2*(1.0 + id_gfs[IDX4ptS(HDD11GF,idx)]);
        const REAL gzz   = cfm2*(1.0 + id_gfs[IDX4ptS(HDD22GF,idx)]);
        const REAL u0    = aux_gfs[IDX4ptS(U4U0GF,idx)];
        const REAL ux    = aux_gfs[IDX4ptS(U4U1GF,idx)];
        const REAL uy    = aux_gfs[IDX4ptS(U4U2GF,idx)];
        const REAL uz    = aux_gfs[IDX4ptS(U4U3GF,idx)];
        const REAL vx    = ux/u0;
        const REAL vy    = uy/u0;
        const REAL vz    = uz/u0;
        fprintf(fp,"%.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e %.15e\n",
                xCart[0],xCart[1],xCart[2],H,Mx,My,Mz,rho_b,alp,gxx,gyy,gzz,vx,vy,vz);
      }
      /*
      i2_out = Nxx_plus_2NGHOSTS2/2;
      for(int i0=NGHOSTS;i0<Nxx_plus_2NGHOSTS0-NGHOSTS;i0++) {
        REAL xCart[3];
        xx_to_Cart(&params,xx,i0,i1_out,i2_out,xCart);

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

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

    printf ("Done.\n");
  }

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

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

# Step 3: C code kernels generation \[Back to [top](#toc)\]
$$\label{ccode_kernels_generation}$$

We now generate the C code kernels that are needed by our program. These include:

1. `free_parameters.h`: a file containing initialization values for all the free parameters in our program.
1. All the C functions that we have added to the C functions dictionary.
1. `NRPy_basic_defines.h`: a C header file that contains all the data structures and definitions which are used by NRPy+ programs.
1. `NRPy_function_prototypes.h`: a C header file that contains the prototypes of all the functions that have been added to the C functions dictionary.

## Step 3.a: Set `free_parameters.h`; also output C codes needed for declaring and setting Cparameters  \[Back to [top](#toc)\]
$$\label{cparams_rfm_and_domainsize}$$

First we output `free_parameters.h`, which sets initial data parameters, as well as grid domain & reference metric parameters, applying `domain_size` and `sinh_width`/`SymTP_bScale` (if applicable) as set above.

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

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

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

## Step 3.b: Add all NRPy+Lorene BNS initial data C codes to C function dictionary \[Back to [top](#toc)\]
$$\label{add_all}$$

```python
# Step 3.b: Add all NRPy+Lorene BNS C codes to C function to dictionary
def BNS_Initial_Data_NRPyLorene_register_C_functions():
    add_to_Cfunction_dict_ID_Lorene_ADM_quantities_Cartesian()
    add_to_Cfunction_dict_set_hydro_quantities()
    add_to_Cfunction_dict_initial_data()
    add_to_Cfunction_dict_Hamiltonian_and_momentum_constraints_source_terms()
    add_to_Cfunction_dict_Hamiltonian_and_momentum_constraints_no_source_terms()
    add_to_Cfunction_dict_main()
```

## Step 3.c: Generating C code for setting Lorene BNS initial data in NRPy+ \[Back to [top](#toc)\]
$$\label{generate_c_code}$$

```python
# Step 3.c: Generating C code for setting Lorene BNS initial data in NRPy+
# Step 3.c.i: Generate all the C codes for the C functions
BNS_Initial_Data_NRPyLorene_register_C_functions()

# Step 3.c.ii: Define a custom dictionary entry for NRPy_basic_defines.h
def generate_supplementary_dict():
    supplementary_dict = {}
    supplementary_dict["Lorene_ID"] = r"""
typedef struct of_ID_inputs {
    // This struct is used to store Lorene
    // initial data in Cartesian basis
    REAL *alp,*betax,*betay,*betaz;
    REAL *gxx,*gxy,*gxz,*gyy,*gyz,*gzz;
    REAL *kxx,*kxy,*kxz,*kyy,*kyz,*kzz;
    REAL *nbar, *u_euler_x,*u_euler_y,*u_euler_z;
    REAL rho_b_unit;
} ID_inputs;
"""
    return supplementary_dict

# Step 3.c.iii: Register all NRPy+ related C functions and
#               add entries to NRPy_basic_defines.h
outC.outputC_register_C_functions_and_NRPy_basic_defines()  # #define M_PI,  etc.
outC.NRPy_param_funcs_register_C_functions_and_NRPy_basic_defines(directory=Ccodesdir)
gri.register_C_functions_and_NRPy_basic_defines()  # #define IDX3S(),  etc.
rfm.register_C_functions_and_NRPy_basic_defines(enable_rfm_precompute=enable_rfm_precompute)
fin.register_C_functions_and_NRPy_basic_defines(NGHOSTS_account_for_onezone_upwind=False)

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

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

    Writing ID_Lorene_ADM_quantities_Cartesian function
    Finished writing ID_Lorene_ADM_quantities_Cartesian function
    Finished writing set_hydro_quantities function
    Writing initial_data function
    Finished writing initial_data function
    Writing Hamiltonian_and_momentum_constraints_source_terms function
    Finished writing Hamiltonian_and_momentum_constraints_source_terms function
    Writing Hamiltonian_and_momentum_constraints_no_source_terms function
    Finished writing Hamiltonian_and_momentum_constraints_no_source_terms function
    Writing main function
    Writing main function
    Finished writing main function

# Step 4: Compiling and running the code \[Back to [top](#toc)\]
$$\label{compiling_and_running}$$

```python
data_dir = os.path.join("lorene_standalone","interpolator")

outfile_1 = os.path.join(data_dir,"initial_data_grid_1.x.asc")
outfile_2 = os.path.join(data_dir,"initial_data_grid_2.x.asc")
outfile_3 = os.path.join(data_dir,"initial_data_grid_3.x.asc")

# outfile_1_lr = os.path.join(data_dir,"initial_data_grid_1_lr.x.asc")
# outfile_2_lr = os.path.join(data_dir,"initial_data_grid_2_lr.x.asc")
# outfile_3_lr = os.path.join(data_dir,"initial_data_grid_3_lr.x.asc")

# outfile_1_hr = os.path.join(data_dir,"initial_data_grid_1_hr.x.asc")
# outfile_2_hr = os.path.join(data_dir,"initial_data_grid_2_hr.x.asc")
# outfile_3_hr = os.path.join(data_dir,"initial_data_grid_3_hr.x.asc")

cmd.new_C_compile(Ccodesdir, "interpolator",
                  addl_CFLAGS=["-I../Lorene/Export/C++/Include",
                               "-I../Lorene/C++/Include"],
                  addl_libraries=["-L../Lorene/Lib/",
                                  "-llorene_export", "-llorene",
                                  "-llorenef77", "-lgfortran", "-lfftw3", "-lgsl",
                                  "-lgslcblas", "-llapack", "-lblas"],
                  CC="g++")

# shutil.copy(outfile_1,outfile_1_lr)
# shutil.copy(outfile_2,outfile_2_lr)
# shutil.copy(outfile_3,outfile_3_lr)

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

# shutil.copy(outfile_1,outfile_1_hr)
# shutil.copy(outfile_2,outfile_2_hr)
# shutil.copy(outfile_3,outfile_3_hr)
```

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

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

```python
os.chdir(Ccodesdir)
cmd.Execute("interpolator", "40 40 40 x 25 -25 2.5 ../resu_lowres.d","out.txt")
os.chdir(os.path.join("..",".."))
```

    (EXEC): Executing `./interpolator 40 40 40 x 25 -25 2.5 ../resu_lowres.d`...
    (BENCH): Finished executing in 1.0230820178985596 seconds.

# Step 5: Visualization: convergence of Hamiltonian constraint \[Back to [top](#toc)\]
$$\label{constraint_violations}$$

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

# outfile_1_etk  = os.path.join("ETK_data","ETK_data_lr_40.asc")
# outfile_2_etk  = os.path.join("ETK_data","ETK_data_mr_40.asc")
# outfile_3_etk  = os.path.join("ETK_data","ETK_data_hr_40.asc")
# outfile_1_nrpy = os.path.join(data_dir,"initial_data_grid_lr_40.x.asc")
# outfile_2_nrpy = os.path.join(data_dir,"initial_data_grid_mr_40.x.asc")
# outfile_3_nrpy = os.path.join(data_dir,"initial_data_grid_hr_40.x.asc")

# data_1_etk  = np.loadtxt(outfile_1_etk).T
# data_2_etk  = np.loadtxt(outfile_2_etk).T
# data_3_etk  = np.loadtxt(outfile_3_etk).T
# data_1_nrpy = np.loadtxt(outfile_1_nrpy).T
# data_2_nrpy = np.loadtxt(outfile_2_nrpy).T
# data_3_nrpy = np.loadtxt(outfile_3_nrpy).T

# fig = plt.figure()

# plt.grid()
# plt.ylabel(r"$\log_{10}\left|\mathcal{H}\right|$")
# plt.xlabel(r"$x$ [km]")
# plt.plot(data_1_etk[0],np.log10(np.maximum(np.abs(data_1_etk[4][:]),1e-15)),'blue',label=r"ETK, low spectral resolution")
# plt.plot(data_1_nrpy[0],np.log10(np.maximum(np.abs(data_1_nrpy[3][:]),1e-15)),'orange',ls='--',label="NRPy+, low spectral resolution")
# plt.plot(data_2_etk[0],np.log10(np.maximum(np.abs(data_2_etk[4][:]),1e-15)),'purple',label=r"ETK, med spectral resolution")
# plt.plot(data_2_nrpy[0],np.log10(np.maximum(np.abs(data_2_nrpy[3][:]),1e-15)),'cyan',ls='--',label="NRPy+, med spectral resolution")
# plt.plot(data_3_etk[0],np.log10(np.maximum(np.abs(data_3_etk[4][:]),1e-15)),'green',ls=":",label=r"ETK, high spectral resolution")
# plt.plot(data_3_nrpy[0],np.log10(np.maximum(np.abs(data_3_nrpy[3][:]),1e-15)),'red',ls='-.',label="NRPy+, high spectral resolution")
# plt.legend()

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

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

outfile_1_etk  = os.path.join("ETK_data","ETK_data_lr_40.asc")
# outfile_2_etk  = os.path.join("ETK_data","ETK_data_hr_80.asc")
outfile_1_nrpy = os.path.join(data_dir,"initial_data_grid_1.x.asc")
# outfile_2_nrpy = os.path.join(data_dir,"initial_data_grid_hr_80.x.asc")

data_1_etk  = np.loadtxt(outfile_1_etk).T
# data_2_etk  = np.loadtxt(outfile_2_etk).T
data_1_nrpy = np.loadtxt(outfile_1_nrpy).T
# data_2_nrpy = np.loadtxt(outfile_2_nrpy).T

fig = plt.figure()

plt.title("BNS Initial Data - High spectral resolution")
plt.grid()
plt.ylabel(r"$\log_{10}\left|\mathcal{H}\right|$")
plt.xlabel(r"$x$ [km]")
plt.plot(data_1_etk[0],np.log10(np.maximum(np.abs(data_1_etk[7][:]),1e-15)),'blue',label=r"ETK, $N=40^{3}$")
plt.plot(data_1_nrpy[0],np.log10(np.maximum(np.abs(data_1_nrpy[6][:]),1e-15)),'orange',ls='--',label="NRPy+, $N=40^{3}$")
# plt.plot(data_2_etk[0],np.log10(np.maximum(np.abs(data_2_etk[4][:]),1e-15)),'green',ls=":",label=r"ETK, $N=80^{3}$")
# plt.plot(data_2_nrpy[0],np.log10(np.maximum(np.abs(data_2_nrpy[3][:]),1e-15)),'red',ls='-.',label="NRPy+, $N=80^{3}$")
plt.legend()

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

```python
# fig,axs = plt.subplots(figsize=(9,3),ncols=3,nrows=1,sharex=True)
# for i in range(len(axs)):
#     axs[i].plot(grid_etk_v_data_lr[0],grid_etk_v_data_lr[idx],c="red",ls=":",label="vel ETK")
#     axs[i].plot(grid_etk_v_data_lr[0],grid_etk_v_data_lr[idx+3],c="magenta",ls=":",label="v ETK")
#     axs[i].plot(vels_nrpy[0],vels_nrpy[idx],c='black',ls="-.",label="vel NRPy")
#     axs[i].grid()
#     axs[i].legend()

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

```python
# outfile_1_etk      = os.path.join("ETK_data","all_data.asc")
# # outfile_1_etk_vel  = os.path.join("ETK_data","vx_vy_vz.x.asc")
# grid_etk_x_data_lr = np.loadtxt(outfile_1_etk).T
# grid_1_x_data_lr   = np.loadtxt(outfile_1).T
# # grid_etk_v_data_lr = np.loadtxt(outfile_1_etk_vel).T
# # vels_nrpy          = np.loadtxt(os.path.join(data_dir,"vx_vy_vz_NRPy.x.asc")).T

# X    = 0
# H    = 4
# ALP  = 9
# GXX  = 13
# GYY  = 16
# GZZ  = 18
# RHOB = 25
# VELX = 26
# VELY = VELX+1
# VELZ = VELY+1
# VX   = 29
# VY   = VX+1
# VZ   = VY+1

# fig,axs = plt.subplots(figsize=(9,4.5),ncols=3,nrows=3,sharex=True)
# axs = axs.flatten()
# ylabels = [r"$\log_{10}\left|\mathcal{H}\right|$",
#            r"$\alpha$",
#            r"$\rho_{b}$",
#            r"$\gamma_{xx}$",
#            r"$\gamma_{yy}$",
#            r"$\gamma_{zz}$",
#            r"$v^{x} = u^{x}/u^{0}$",
#            r"$v^{y} = u^{y}/u^{0}$",
#            r"$v^{z} = u^{z}/u^{0}$"]
# qETK = [np.log10(np.maximum(np.abs(grid_etk_x_data_lr[H][:]),1e-15)),
#         grid_etk_x_data_lr[ALP],grid_etk_x_data_lr[RHOB],
#         grid_etk_x_data_lr[GXX],grid_etk_x_data_lr[GYY],grid_etk_x_data_lr[GZZ],
#         grid_etk_x_data_lr[VX],grid_etk_x_data_lr[VY],grid_etk_x_data_lr[VZ]]

# # xCart[0],xCart[1],xCart[2],H,rho_b,alp,gxx,gyy,gzz,vx,vy,vz
# qNRPy =[np.log10(np.maximum(np.abs(grid_1_x_data_lr[3][:]),1e-15)),
#         grid_1_x_data_lr[5],grid_1_x_data_lr[4],
#         grid_1_x_data_lr[6],grid_1_x_data_lr[7],grid_1_x_data_lr[8],
#         grid_1_x_data_lr[9],grid_1_x_data_lr[10],grid_1_x_data_lr[11]]

# for i in range(len(axs)):
#     axs[i].grid()
#     axs[i].set_ylabel(ylabels[i])
#     axs[i].plot(grid_etk_x_data_lr[0],qETK[i],c='blue',ls='-',label="ETK")
#     axs[i].plot(grid_1_x_data_lr[0],qNRPy[i],c='orange',ls='--',label="NRPy+")
#     axs[i].legend()

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

```python
# import glob

# file_list = sorted(glob.glob(os.path.join("ETK_data","*.x.asc")))

# master_file = os.path.join("ETK_data","ETK_data_mr_40.asc")
# all_data   = []
# local_data = np.loadtxt(file_list[0]).T
# all_data.append(local_data[9])
# all_data.append(local_data[12])
# for i in range(1,len(file_list)):
#     local_data = np.loadtxt(file_list[i]).T
#     all_data.append(local_data[12])

# np.savetxt(master_file,list(zip(*all_data)))
```

```python
# string  = "# Column 1: x\n"
# counter = 2
# for i in file_list:
#     string  += "# Column "+str(counter)+": "+i.split(".")[0].split("/")[1]+"\n"
#     counter += 1
# print(string)
```

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

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

```python
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-ADM_Initial_Data-Lorene_BNS")
```

    Created Tutorial-ADM_Initial_Data-Lorene_BNS.tex, and compiled LaTeX file
        to PDF file Tutorial-ADM_Initial_Data-Lorene_BNS.pdf


## Tex Preamble
$$
\newcommand{\bm}[1]{{\bf #1}}
\newcommand{\eq}[1]{Eq.(\ref{eq:#1})}
\newcommand{\zu}[1]{図\ref{fig:#1}}
\newcommand{\shiki}[1]{式(\ref{eq:#1})}
\newcommand{\fig}[1]{Fig.\ \ref{fig:#1}}
\newcommand{\grp}[1]{Fig.\ \ref{grp:#1}}
\newcommand{\tab}[1]{Table\ \ref{tab:#1}}
\newcommand{\defeq}{:=}
\newcommand{\tvec}[1]{\bvec{#1}^{\mathsf{T}}}
\newcommand{\mr}[1]{\mathrm{#1}}%rm体
\newcommand{\ml}[1]{\mathcal{#1}}%frac体
\newcommand{\bmat}[1]{\begin{bmatrix} #1 \end{bmatrix}}%行列簡略化
\newcommand{\pmat}[1]{\begin{pmatrix} #1 \end{pmatrix}}%行列簡略化
\newcommand{\mat}[1]{\left( \begin{matrix} #1 \end{matrix} \right)}%行列簡略化
%\newcommand{\nom}[1]{ \ml{N} \{ 0,\sigma_{ #1 } \} }
\newcommand{\expo}[1]{ \mathrm{e}^{#1} }
$$

Texプリアンブル＋Yalmipの初期設定記述箇所。

addpathでSeDumiやSDpt3などのソルバをインストール。

```matlab
addpath(genpath('C:\Users\hkedw\Documents\MATLAB\SeDuMi_1_3'))
addpath(genpath('C:\Users\hkedw\Documents\MATLAB\SDpt3-4.0'))
addpath(genpath('C:\Users\hkedw\Documents\MATLAB\YALMIP-master'))
```

# LMIでの極配置テスト
システム

$$
\dot{x}=Ax
$$
に対して，固有値をどこにおくかという問題を考える。

アルファ領域（$-\alpha$より左半面），円領域，セクタ領域に極を配置するLMIを解く。

適当な行列の成分$\bm{\Psi}=\bmat{r & s\\s&q}$を用いて

$$
\bmat{\lambda & 1} \bm{\Psi} \bmat{\lambda \\1}\prec 0
$$
を満たす$\lambda$の領域へと極配置する。

例えば$-\alpha$の左側のアルファ領域は以下の通り，
$$
\bm{\Psi}_\alpha=\bmat{0&1\\1&2\alpha}
$$

中心$c$，半径$r$の円領域は次のようになる。
$$
\bm{\Psi}_C=\bmat{1&-c\\-c&c^2-r^2}
$$

従って以下の数式を満たす行列$P \succ0$があれば該当の位置に極配置がなされている。
$$
\bmat{A^\top&I} \bmat{rP&sP\\sP&qP}\bmat{A\\I}\prec 0
$$

展開すると，
$$
rA^\top P A+sPA+sA^\top P+qP\prec0
$$

また，$X:=P^{-1}$について書き直すと，

$$
\bmat{A&I} \bmat{rX&sX\\sX&qX}\bmat{A^\top\\I}\prec 0
$$

```matlab
%{
%% Parameters
m2 = 0.002;
l2 = 0.1;
g = 9.8;
L1 = 11.6e-2;
% tekito
I2 = L1/2;
c2 = 1.1;
b = 1.4;
a = 2.1;
%% Equation
A = [0 0 1 0;
    0 0 0 1;
    0 0 -a 0;
    0 m2*g*l2/I2 m2*L1*l2*a/I2 -c2/I2];

B = [0;0;b;-m2*L1*l2*b/I2];
%}

% ----------
J  = 0.0712;  M  = 0.390;  mu = 0.695;
l  = 0.204;   g  = 9.81;

A = [     0      1
      M*g*l/J  -mu/J ];
B = [  0
      1/J ];

rank(ctrb(A,B))
```

    ans =

         2

## アルファ領域（収束速度制約）

先程の式を展開すると以下のような制約をとけば良い。

$$
PA+A^\top P+2\alpha P \prec 0, P\succ0
$$

これは$X:=P^{-1}$を両辺からかけることで等価的に

$$
AX+XA^\top +2\alpha X \prec 0, X\succ0
$$

と表せる。

### BMIからLMIへの変換を用いた 設計問題
ここで以下のゲインKの状態FBシステムを安定化させることを考える。
$$
\dot{x}=Ax+Bu=(A+BK)x
$$

先ほどの式に代入して，（このままだとBMI：双線形行列不等式になるので）
$$
(A+BK)X+X(A+BK)^\top +2\alpha X \prec 0, X\succ0
$$

ここで，$KX=Y$と変換することで，LMIに再び変換することができる。

$$
(AX+BY)+(AX+BY)^\top +2\alpha X \prec 0, X\succ0
$$

```matlab
alpha = 5;

row=size(A,1);col=size(A,2);

X = sdpvar(row,col,'symmetric')
Y = sdpvar(1,col,'full')

LMI = [A*X+B*Y+(A*X+B*Y)'+ 2*alpha*X<0 ,X>0];

sol = solvesdp(LMI,Y*Y.');

X = double(X);
Y = double(Y);
K = Y/X;

% LMI1 result
pole1 = eig(A+B*K);

display(pole1);
```

    Linear matrix variable 2x2 (symmetric, real, 3 variables)
    Linear matrix variable 1x2 (full, real, 2 variables)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In < (line 12)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In > (line 12)
    SeDuMi 1.3 by AdvOL, 2005-2008 and Jos F. Sturm, 1998-2003.
    Alg = 2: xz-corrector, theta = 0.250, beta = 0.500
    eqs m = 6, order n = 7, dim = 13, blocks = 4
    nnz(A) = 16 + 0, nnz(ADA) = 30, nnz(L) = 18
     it :     b*y       gap    delta  rate   t/tP*  t/tD*   feas cg cg  prec
      0 :            1.51E+02 0.000
      1 :  -2.79E-01 1.40E+01 0.000 0.0928 0.9900 0.9900   1.54  1  1  4.9E+00
      2 :  -1.70E-02 3.10E+00 0.000 0.2213 0.9000 0.9000   1.36  1  1  1.6E+00
      3 :   1.07E-03 8.88E-01 0.000 0.2862 0.9000 0.9000   0.66  1  1  1.2E+00
      4 :  -2.91E-03 2.51E-01 0.000 0.2831 0.9000 0.9000   0.82  1  1  1.3E+00
      5 :  -5.27E-04 5.03E-02 0.000 0.2002 0.9000 0.9000   1.19  1  1  9.4E-01
      6 :  -1.26E-04 1.30E-02 0.000 0.2588 0.9000 0.9000   1.38  1  1  1.7E-01
      7 :  -2.82E-05 3.73E-03 0.000 0.2865 0.9000 0.9000   1.25  1  1  3.6E-02
      8 :  -7.36E-06 1.10E-03 0.000 0.2949 0.9000 0.9000   1.13  1  1  9.1E-03
      9 :  -1.94E-06 3.26E-04 0.000 0.2968 0.9000 0.9000   1.07  1  1  2.4E-03
     10 :  -6.07E-07 1.01E-04 0.000 0.3105 0.9000 0.9000   1.04  1  1  7.1E-04
     11 :  -1.51E-07 3.19E-05 0.000 0.3149 0.9000 0.9000   1.02  1  1  2.2E-04
     12 :  -7.32E-08 1.08E-05 0.000 0.3383 0.9000 0.9000   1.01  1  1  7.1E-05
     13 :  -1.34E-08 3.53E-06 0.000 0.3272 0.9000 0.9000   1.01  1  1  2.3E-05
     14 :  -7.19E-09 1.10E-06 0.000 0.3123 0.9000 0.9000   1.00  2  2  7.0E-06
     15 :  -1.41E-09 3.25E-07 0.000 0.2948 0.9000 0.9000   1.00  2  2  2.1E-06
     16 :  -5.66E-10 9.62E-08 0.000 0.2958 0.9000 0.9000   1.00  2  2  6.0E-07
     17 :  -1.28E-10 2.87E-08 0.000 0.2987 0.9000 0.9000   1.00  2  2  1.8E-07
     18 :  -5.44E-11 8.90E-09 0.000 0.3099 0.9000 0.9000   1.00  2  2  5.5E-08
     19 :  -1.14E-11 2.77E-09 0.000 0.3115 0.9000 0.9000   1.00  2  2  1.7E-08
     20 :  -5.45E-12 8.66E-10 0.000 0.3122 0.9000 0.9000   1.00  3  3  5.3E-09
     21 :  -1.09E-12 2.64E-10 0.000 0.3053 0.9000 0.9000   1.00  3  3  1.6E-09
     22 :  -4.86E-13 8.00E-11 0.000 0.3028 0.9000 0.9000   1.00  3  3  4.9E-10

    iter seconds digits       c*x               b*y
     22      0.3   7.6  6.5901333299e-13 -4.8648236002e-13
    |Ax-b| =   1.7e-11, [Ay-c]_+ =   2.3E-13, |x|=  7.1e-01, |y|=  1.3e-01

    Detailed timing (sec)
       Pre          IPM          Post
    7.001E-03    1.280E-01    4.003E-03
    Max-norms: ||b||=1, ||c|| = 1,
    Cholesky |add|=1, |skip| = 0, ||L.L|| = 3.03988.

    pole1 =

     -10.9597 + 0.2407i
     -10.9597 - 0.2407i

極配置はただの条件であり，実際の計算ではこれに更に制約条件を与える。
コマンドでは最小化する条件を入れている。

### コツとmemo

- ゲインKはY/XなのでYを最小化することで小さめのゲインを期待できる。（Xの値によっては其の限りではないが）
- 保険としてX>0ではなくX-I>0と制約をおいてゲインの発散を抑える
- 大きい行列で解いた方が楽なのか

#### X>0とX>eyeの違い

X>0とした時。

```
pole1 =

 -10.9597 + 0.2407i
 -10.9597 - 0.2407i
```

X>eyeとした時。
```
pole1 =

   -5.0001
  -10.7783
```
となる。Xを縛るのはなかなか意味がありそう。

```matlab
% Solve LMI 2

X = sdpvar(row,col,'symmetric')
Y = sdpvar(1,col,'full')
gamma = sdpvar(1);

% LMI
LMI = [A*X+B*Y+(A*X+B*Y).'+ 2*alpha*X<0,[gamma Y;Y.' eye(size(A))]>0,X>eye(size(A))];

sol = solvesdp(LMI,gamma);

X = double(X);
Y = double(Y);
K = Y/X;

% LMI1 result
pole2 = eig(A+B*K);

display(pole2);
```

    Linear matrix variable 2x2 (symmetric, real, 3 variables)
    Linear matrix variable 1x2 (full, real, 2 variables)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In < (line 12)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In > (line 12)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In > (line 12)
    SeDuMi 1.3 by AdvOL, 2005-2008 and Jos F. Sturm, 1998-2003.
    Alg = 2: xz-corrector, theta = 0.250, beta = 0.500
    eqs m = 6, order n = 8, dim = 18, blocks = 4
    nnz(A) = 15 + 0, nnz(ADA) = 30, nnz(L) = 18
     it :     b*y       gap    delta  rate   t/tP*  t/tD*   feas cg cg  prec
      0 :            1.25E+02 0.000
      1 :  -1.15E+00 4.02E+01 0.000 0.3220 0.9000 0.9000   0.84  1  1  2.8E+01
      2 :  -1.66E+01 7.76E+00 0.000 0.1932 0.9000 0.9000  -0.76  1  1  4.2E+01
      3 :  -8.40E+01 1.42E+00 0.000 0.1828 0.9000 0.9000  -0.77  1  1  3.4E+01
      4 :  -1.73E+02 4.14E-01 0.000 0.2921 0.9000 0.9000  -0.67  1  1  2.5E+01
      5 :  -1.28E+02 1.15E-01 0.000 0.2768 0.9000 0.9000   0.08  1  1  8.4E+00
      6 :  -7.55E+01 2.78E-02 0.000 0.2419 0.9000 0.9000   1.15  1  1  1.8E+00
      7 :  -4.79E+01 6.90E-03 0.000 0.2485 0.9000 0.9000   1.08  1  1  4.5E-01
      8 :  -3.54E+01 1.80E-03 0.000 0.2617 0.9000 0.9000   0.71  1  1  1.5E-01
      9 :  -2.89E+01 4.42E-04 0.000 0.2449 0.9000 0.9000   0.45  1  1  5.5E-02
     10 :  -2.58E+01 1.20E-04 0.000 0.2715 0.9000 0.9000   0.49  1  1  2.0E-02
     11 :  -2.43E+01 3.87E-05 0.000 0.3223 0.9000 0.9000   0.55  1  1  8.3E-03
     12 :  -2.34E+01 1.42E-05 0.000 0.3683 0.9000 0.9000   0.48  1  1  4.3E-03
     13 :  -2.28E+01 5.10E-06 0.000 0.3578 0.9000 0.9000   0.58  1  1  2.0E-03
     14 :  -2.25E+01 2.13E-06 0.000 0.4180 0.9000 0.9000   0.42  1  1  1.2E-03
     15 :  -2.22E+01 7.43E-07 0.000 0.3485 0.9000 0.9000   0.59  1  1  5.3E-04
     16 :  -2.20E+01 2.87E-07 0.000 0.3861 0.9000 0.9000   0.43  2  2  3.1E-04
     17 :  -2.18E+01 1.01E-07 0.000 0.3518 0.9000 0.9000   0.56  2  2  1.4E-04
     18 :  -2.17E+01 3.97E-08 0.000 0.3938 0.9000 0.9000   0.40  2  2  8.3E-05
     19 :  -2.17E+01 1.37E-08 0.000 0.3446 0.9000 0.9000   0.56  2  2  3.6E-05
     20 :  -2.16E+01 5.21E-09 0.000 0.3802 0.9000 0.9000   0.41  2  2  2.1E-05
     21 :  -2.16E+01 1.81E-09 0.000 0.3481 0.9000 0.9000   0.54  2  2  9.4E-06
     22 :  -2.16E+01 7.04E-10 0.000 0.3885 0.9000 0.9000   0.40  2  2  5.6E-06
     23 :  -2.15E+01 2.43E-10 0.000 0.3451 0.9000 0.9000   0.55  3  2  2.5E-06
     24 :  -2.15E+01 9.28E-11 0.000 0.3819 0.9000 0.9000   0.40  4  5  1.4E-06
     25 :  -2.15E+01 3.22E-11 0.000 0.3473 0.9000 0.9000   0.54  5  3  6.4E-07
     26 :  -2.15E+01 1.25E-11 0.000 0.3867 0.9000 0.9000   0.40  5  5  3.8E-07
     27 :  -2.15E+01 4.31E-12 0.000 0.3457 0.9000 0.9000   0.55  5  5  1.7E-07
     28 :  -2.15E+01 1.65E-12 0.000 0.3830 0.9000 0.9000   0.40  5  5  9.8E-08
     29 :  -2.15E+01 5.73E-13 0.000 0.3469 0.9000 0.9000   0.54  5  5  4.4E-08
     30 :  -2.15E+01 2.21E-13 0.000 0.3857 0.9000 0.9000   0.40  5  5  2.6E-08
     31 :  -2.15E+01 7.64E-14 0.000 0.3460 0.9000 0.9000   0.55  5  5  1.1E-08
     32 :  -2.15E+01 2.93E-14 0.000 0.3837 0.9000 0.9000   0.40  5  5  6.7E-09
     33 :  -2.15E+01 1.02E-14 0.000 0.3467 0.9000 0.9000   0.55  5  5  3.0E-09
     34 :  -2.15E+01 3.91E-15 0.000 0.3852 0.9000 0.9000   0.40  5  5  1.7E-09
     35 :  -2.15E+01 1.36E-15 0.000 0.3462 0.9000 0.9000   0.55  6  6  7.7E-10

    iter seconds digits       c*x               b*y
     35      0.6   5.5 -2.1506133405e+01 -2.1506059306e+01
    |Ax-b| =   1.0e-09, [Ay-c]_+ =   0.0E+00, |x|=  4.9e+01, |y|=  1.5e+06

    Detailed timing (sec)
       Pre          IPM          Post
    1.200E-02    2.710E-01    2.002E-03
    Max-norms: ||b||=1, ||c|| = 1,
    Cholesky |add|=0, |skip| = 1, ||L.L|| = 9.2353.

    pole2 =

       -5.0001
      -10.7783

## 円領域への極配置

解くべきLMIは

$$
cA^\top P A-cPA-cA^\top P+(c^2-r^2)P\prec0\\
cA X A^\top -cXA^\top-cA X+(c^2-r^2)X\prec0
$$
である。

このままだとLMIとして解けない。そこでSchurの補題を用いてこれをLMIの形式へと変換することを考える。

### Schurの補題の適用
Schur（シュール）の補題とは以下の補題である。
$$
\bmat{A&B\\C&D} \prec 0 \Leftrightarrow  A-BD^{-1}C \prec 0
$$
ただし，$D$は当然逆行列が無くてはならない。

この補題を用いることで次のような関係が導ける。

$$
\begin{align}
c(XA^\top+A X)+(r^2-c^2)X - cA X A^\top \succ 0 &\Leftrightarrow \\
&\bmat{c(XA^\top+A X)+(r^2-c^2)X & AX \\ XA^\top & X}\succ 0
\end{align}
$$

したがってLMIに変換できたのでこれを用いて変換を行う。

### FB系の設計

先程やったとおり，$A+BK$を代入し，$Y=KX$の変換を行う。
結論から言うと，以下の通りになる。

$$
M=AX+BY\\
\bmat{c(M^\top+M)+(r^2-c^2)X & M \\ M^\top & X}\succ 0
$$

```matlab
row=size(A,1);col=size(A,2);

X = sdpvar(row,col,'symmetric')
Y = sdpvar(1,col,'full')

M = A*X + B*Y;

qc = -5;  rc =  2;
M_D3 = [qc*(M+M')+(rc^2-qc^2)*X  M
                  M'             X ];

LMI = [ M_D3 > 0 , X>0];

options = sdpsettings('solver','sedumi');
solvesdp(LMI,[],options) ;

X_opt = double(X);
Y_opt = double(Y);
K_opt = Y_opt/X_opt;

% LMI3 result
pole3 = eig(A+B*K_opt);

display(pole3);
```

    Linear matrix variable 2x2 (symmetric, real, 3 variables)
    Linear matrix variable 1x2 (full, real, 2 variables)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In > (line 12)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In > (line 12)
    SeDuMi 1.3 by AdvOL, 2005-2008 and Jos F. Sturm, 1998-2003.
    Alg = 2: xz-corrector, theta = 0.250, beta = 0.500
    eqs m = 5, order n = 7, dim = 21, blocks = 3
    nnz(A) = 23 + 0, nnz(ADA) = 25, nnz(L) = 15
     it :     b*y       gap    delta  rate   t/tP*  t/tD*   feas cg cg  prec
      0 :            1.15E+04 0.000
      1 :   0.00E+00 9.45E+02 0.000 0.0821 0.9900 0.9900   1.00  1  0  2.3E+01
      2 :   0.00E+00 1.97E+02 0.000 0.2080 0.9000 0.9000   1.00  1  1  4.8E+00
      3 :   0.00E+00 1.61E+01 0.000 0.0817 0.9900 0.9900   1.00  1  1  3.9E-01
      4 :   0.00E+00 3.48E+00 0.000 0.2168 0.9000 0.9000   1.00  1  1  8.6E-02
      5 :   0.00E+00 1.99E-01 0.000 0.0570 0.9900 0.9900   1.00  1  1  4.9E-03
      6 :   0.00E+00 8.23E-05 0.000 0.0004 0.9999 0.9999   1.00  1  1  2.0E-06
      7 :   0.00E+00 3.15E-11 0.000 0.0000 1.0000 1.0000   1.00  1  1  2.8E-12

    iter seconds digits       c*x               b*y
      7      0.0   Inf  0.0000000000e+00  0.0000000000e+00
    |Ax-b| =   5.3e-13, [Ay-c]_+ =   0.0E+00, |x|=  1.0e-11, |y|=  4.3e-01

    Detailed timing (sec)
       Pre          IPM          Post
    8.006E-03    3.900E-02    4.003E-03
    Max-norms: ||b||=0, ||c|| = 0,
    Cholesky |add|=0, |skip| = 0, ||L.L|| = 3.77576.

    pole3 =

      -3.9254 + 0.5682i
      -3.9254 - 0.5682i

条件を簡単にしたらきちんと円の中に入っている。

## セクタ条件

極と原点の線分が実軸と交わる角度が$\theta$以内であるという制約を$k=\tan\theta$で表す。

LMI条件は
$$
\bmat{k(PA+A^\top P) & PA-A^\top P\\A^\top P-PA &k(PA+A^\top P)} \prec 0
$$
あるいは
$$
\bmat{k(AX+XA^\top ) & AX-XA^\top \\ XA^\top-AX &k(AX+XA^\top)} \prec0
$$
となる。

今までと同様にしてFBのための条件は，
$$
M=AX+BY\\
\bmat{k(M+M^\top ) & M-M^\top \\ M^\top-M &k(M+M^\top)} \prec0
$$

である。

```matlab
k=tand(15)
X = sdpvar(row,col,'symmetric')
Y = sdpvar(1,col,'full')

M = A*X + B*Y;

qc = -5;  rc =  2;
circle = [qc*(M+M')+(rc^2-qc^2)*X  M
                  M'             X ];
sector = [k*(M+M')  M-M.'; -M+M.' k*(M+M')];

LMI = [ circle > 0 , sector<0, X>0];

options = sdpsettings('solver','sedumi');
sol=solvesdp(LMI,[],options);

sol

X_opt = double(X);
Y_opt = double(Y);
K_opt = Y_opt/X_opt;

% LMI3 result
pole4 = eig(A+B*K_opt);

display(pole4);
```

    k =

        0.2679

    Linear matrix variable 2x2 (symmetric, real, 3 variables)
    Linear matrix variable 1x2 (full, real, 2 variables)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In > (line 12)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In < (line 12)
    警告: Strict inequalities are not supported. A non-strict has been added instead')
    > In > (line 12)
    SeDuMi 1.3 by AdvOL, 2005-2008 and Jos F. Sturm, 1998-2003.
    Alg = 2: xz-corrector, theta = 0.250, beta = 0.500
    eqs m = 5, order n = 11, dim = 37, blocks = 4
    nnz(A) = 47 + 0, nnz(ADA) = 25, nnz(L) = 15
     it :     b*y       gap    delta  rate   t/tP*  t/tD*   feas cg cg  prec
      0 :            8.96E+03 0.000
      1 :   0.00E+00 2.24E+03 0.000 0.2501 0.9000 0.9000   1.00  1  0  7.8E+01
      2 :   0.00E+00 4.68E+02 0.000 0.2088 0.9000 0.9000   1.00  1  1  1.6E+01
      3 :   0.00E+00 4.18E+01 0.000 0.0892 0.9900 0.9900   1.00  1  1  1.5E+00
      4 :   0.00E+00 1.17E+01 0.000 0.2811 0.9000 0.9000   1.00  1  1  4.1E-01
      5 :   0.00E+00 8.16E-01 0.000 0.0694 0.9900 0.9900   1.00  1  1  2.8E-02
      6 :   0.00E+00 3.48E-03 0.000 0.0043 0.9990 0.9990   1.00  1  1  1.2E-04
      7 :   0.00E+00 6.71E-10 0.000 0.0000 1.0000 1.0000   1.00  1  1  2.3E-11

    iter seconds digits       c*x               b*y
      7      0.0   Inf  0.0000000000e+00  0.0000000000e+00
    |Ax-b| =   1.7e-11, [Ay-c]_+ =   0.0E+00, |x|=  5.9e-10, |y|=  5.4e-01

    Detailed timing (sec)
       Pre          IPM          Post
    7.001E-03    2.900E-02    2.997E-03
    Max-norms: ||b||=0, ||c|| = 0,
    Cholesky |add|=0, |skip| = 0, ||L.L|| = 4.75102.

    sol =

      フィールドをもつ struct:

        yalmiptime: 0.0527
        solvertime: 0.0423
              info: 'Successfully solved (SeDuMi-1.3)'
           problem: 0

    pole4 =

       -3.3251
       -5.3127

```matlab
sol
```

    sol =

      フィールドをもつ struct:

        yalmiptime: 0.0527
        solvertime: 0.0423
              info: 'Successfully solved (SeDuMi-1.3)'
           problem: 0

先程の円の範囲と比較すると，複素成分を削ってこのように解の範囲を絞れる。

逆もできるのだろうか？と思ったが右半平面に行っちゃうので無しで。

```matlab

```


```python
from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)
```

Toggle cell visibility here.

```python
# Erasmus+ ICCT project (2018-1-SI01-KA203-047081)

%matplotlib notebook
import numpy as np
import math
import matplotlib.pyplot as plt
from scipy import signal
import ipywidgets as widgets
import control as c
import sympy as sym
from IPython.display import Latex, display, Markdown # For displaying Markdown and LaTeX code
from fractions import Fraction
import matplotlib.patches as patches
```

## Aproksimacija dominantnega pola

Pri analizi sistemov sisteme pogosto aproksimiramo z dominantnim polom ali parom kompleksnih dominantnih polov. V tem interaktivnem primeru je prikazan ta postopek.

Sisteme drugega reda lahko zapišemo z naslednjo prenosno funkcijo:

\begin{equation}
    G(s)=\frac{\alpha\beta}{(s+\alpha)(s+\beta)}=\frac{1}{(\frac{1}{\alpha}s+1)(\frac{1}{\beta}s+1)},
\end{equation}

kjer je $\beta=1$, $\alpha$ pa je iterabilen parameter.

Sisteme tretjega reda lahko zapišemo z naslednjo prenosno funkcijo:

\begin{equation}
    G(s)=\frac{\alpha{\omega_0}^2}{\big(s+\alpha\big)\big(s^2+2\zeta\omega_0s+\omega_0^2\big)}=\frac{1}{(\frac{1}{\alpha}s+1)(\frac{1}{\omega_0^2}s^2+\frac{2\zeta\alpha}{\omega_0}s+1)},
\end{equation}

kjer so $\beta=1$, $\omega_0=4.1$ in $\zeta=0.24$, $\alpha$ pa je iterabilen parameter.

---

### Kako upravljati s tem interaktivnim primerom?

Preklapljaj med sistemom drugega in tretjega reda ter z uporabo drsnika spreminjaj lego premakljivega pola $\alpha$.

Ta interaktivni primer temelji na [primeru](https://lpsa.swarthmore.edu/PZXferStepBode/DomPole.html "The Dominant Pole Approximation") Prof. Erika Cheeverja.

```python
# System selector buttons
style = {'description_width': 'initial'}
typeSelect = widgets.ToggleButtons(
    options=[('sistem drugega reda', 0), ('sistem tretjega reda', 1),],
    description='Izberi: ',style=style)
```

```python
display(typeSelect)
continuous_update=False

# set up plot

fig, ax = plt.subplots(2,1,figsize=[9.8,7],num='Aproksmiacija dominantnih polov')
plt.subplots_adjust(hspace=0.35)
ax[0].grid(True)
ax[1].grid(True)
# ax[2].grid(which='both', axis='both', color='lightgray')
ax[0].axhline(y=0,color='k',lw=.8)
ax[1].axhline(y=0,color='k',lw=.8)
ax[0].axvline(x=0,color='k',lw=.8)
ax[1].axvline(x=0,color='k',lw=.8)
ax[0].set_xlabel('Re')
ax[0].set_ylabel('Im')
ax[0].set_xlim([-10,0.5])
ax[1].set_xlim([-0.5,20])
ax[1].set_xlabel('$t$ [s]')
ax[1].set_ylabel('vhod, izhod')
ax[0].set_title('Diagram lege ničel in polov')
ax[1].set_title('Časovni odziv sistema')

plotzero, = ax[0].plot([], [])
response, = ax[1].plot([], [])
responseAdom, = ax[1].plot([], [])
responseBdom, = ax[1].plot([], [])

ax[1].step([0,50],[0,1],color='C0',label='vhod')

# generate x values

def response_func(a,index):

    global plotzero, response, responseAdom, responseBdom
#     global bodePlot, bodePlotAdom, bodePlotBdom

    t = np.linspace(0, 50, 1000)

    if index==0:
        b=1
        num=a*b
        den=([1,a+b,a*b])
        tf_sys=c.TransferFunction(num,den)
        poles_sys,zeros_sys=c.pzmap(tf_sys, Plot=False)
        tout, yout = c.step_response(tf_sys,t)
        den1=([1,a])
        tf_sys1=c.TransferFunction(a,den1)
        toutA, youtA = c.step_response(tf_sys1,t)
        den2=([1,b])
        tf_sys2=c.TransferFunction(b,den2)
        toutB, youtB = c.step_response(tf_sys2,t)
        mag, phase, omega = c.bode_plot(tf_sys, Plot=False)   # Bode-plot
        magA, phase, omegaA = c.bode_plot(tf_sys1, Plot=False)   # Bode-plot
        magB, phase, omegaB = c.bode_plot(tf_sys2, Plot=False)   # Bode-plot
        s=sym.Symbol('s')
        eq=(a*b/((s+a)*(s+b)))
        eq1=1/(((1/a)*s+1)*((1/b)*s+1))
        display(Markdown('Premakljivi pol (vijolična krivulja) $\\alpha$ je enak %.1f, fiksni pol (rdeča krivulja) $b$ je enak %i; Prenosna funkcija sistema je'%(a,1)))
        display(eq),display(Markdown('oz.')),display(eq1)

    elif index==1:
        omega0=4.1
        zeta=0.24
        num=a*omega0**2
        den=([1,2*zeta*omega0+a,omega0**2+2*zeta*omega0*a,a*omega0**2])
        tf_sys=c.TransferFunction(num,den)
        poles_sys,zeros_sys=c.pzmap(tf_sys, Plot=False)
        tout, yout = c.step_response(tf_sys,t)
        den1=([1,a])
        tf_sys1=c.TransferFunction(a,den1)
        toutA, youtA = c.step_response(tf_sys1,t)
        den2=([1,2*zeta*omega0,omega0**2])
        tf_sys2=c.TransferFunction(omega0**2,den2)
        toutB, youtB = c.step_response(tf_sys2,t)
        mag, phase, omega = c.bode_plot(tf_sys, Plot=False)   # Bode-plot
        magA, phase, omegaA = c.bode_plot(tf_sys1, Plot=False)   # Bode-plot
        magB, phase, omegaB = c.bode_plot(tf_sys2, Plot=False)   # Bode-plot
        s=sym.Symbol('s')
        eq=(a*omega0**2/((s+a)*(s**2+2*zeta*omega0*s+omega0*omega0)))
        eq1=1/(((1/a)*s+1)*((1/(omega0*omega0))*s*s+(2*zeta*a/omega0)*s+1))

        display(Markdown('Premakljivi pol (vijolična krivulja) $\\alpha$ je enak %.1f, fiksni pol (rdeča krivulja) $\\beta$ je enak $1\pm4j$ (vrednost $\omega_0$ je nastavljena na 4.1, vrednost $\zeta$ pa na 0.24). Prenosna funkcija sistema je enaka:'%(a)))
        display(eq),display(Markdown('oz.')),display(eq1)

    ax[0].lines.remove(plotzero)
    ax[1].lines.remove(response)
    ax[1].lines.remove(responseAdom)
    ax[1].lines.remove(responseBdom)

    plotzero, = ax[0].plot(np.real(poles_sys), np.imag(poles_sys), 'xg', markersize=10, label = 'pol')
    response, = ax[1].plot(tout,yout,color='C1',label='odziv sistema',lw=3)
    responseAdom, = ax[1].plot(toutA,youtA,color='C4',label='odziv zaradi premakljivega pola/para polov')
    responseBdom, = ax[1].plot(toutB,youtB,color='C3',label='odziv zaradi fiksnega pola')

    ax[0].legend()
    ax[1].legend()

a_slider=widgets.FloatSlider(value=0.1, min=0.1, max=10, step=.1,
                                        description='$\\alpha$:',disabled=False,continuous_update=False,
                                        orientation='horizontal',readout=True,readout_format='.2f',)

input_data=widgets.interactive_output(response_func,{'a':a_slider,'index':typeSelect})

def update_slider(index):
    global a_slider

    aval=[0.1,0.1]
    a_slider.value=aval[index]

input_data2=widgets.interactive_output(update_slider,{'index':typeSelect})

display(a_slider,input_data)

```

    ToggleButtons(description='Izberi: ', options=(('sistem drugega reda', 0), ('sistem tretjega reda', 1)), style…

    <IPython.core.display.Javascript object>

    FloatSlider(value=0.1, continuous_update=False, description='$\\alpha$:', max=10.0, min=0.1)

    Output()

```python

```