# FFM232, Klassisk fysik och vektorfält - Föreläsningsanteckningar

 **[Christian Forssén](http://fy.chalmers.se/subatom/tsp/), Institutionen för fysik, Chalmers, Göteborg, Sverige**

Date: **Sep 28, 2016**

# Repetition: Singulära fält

### Punktkälla i origo

Fältet i punkten $\vec{r}$

$$
\begin{equation}
  \vec{F}(\vec{r}) = \frac{q}{4 \pi r^2} \hat{e}_r,
\end{equation}
$$

vilket fås av potentialen

$$
\begin{equation}
  \phi(\vec{r}) = \frac{q}{4 \pi r},
\end{equation}
$$

eftersom $\vec{F} = - \nabla \phi$.

Källtäthet $\rho (\vec{r}) = \nabla \cdot \vec{F} = - \Delta \phi$.

För detta fält insåg vi att det blir problem med Gauss sats

$$
\begin{equation}
  \int_V \nabla \cdot \vec{F} \mbox{d}V = \oint_{\partial V} \vec{F} \cdot \mbox{d} \vec{S},
\end{equation}
$$

eftersom $\nabla \cdot \vec{F} = 0$ skulle ge $\mathrm{VL} = 0$, men $\mathrm{HL} = q$ om den inneslutna volymen $V$ innehåller origo. Problemet är att

$$
\begin{equation}
\nabla \cdot \vec{F} =
\left\{
\begin{array}{ll}
0 & r \neq 0 \\
\infty & r=0
\end{array}
\right.
\end{equation}
$$

En bättre beskrivning av en sådan källtäthet kommer härnäst...

## 7. Deltafunktioner

Kan vi approximera $\nabla \cdot \vec{F} = \rho (\vec{r})$ på något sätt? T.ex.

$$
\begin{equation}
\rho =
\left\{
\begin{array}{ll}
c & r < \varepsilon \\
0 & r > \varepsilon
\end{array}
\right.
\end{equation}
$$

Dvs, en "utsmetad" punktladdning där vi väljer $c$ så att den totala laddningen är $q$, dvs

$$
\begin{equation}
\rho =
\left\{
\begin{array}{ll}
\frac{q}{\frac{4\pi}{3} \varepsilon^3} & r < \varepsilon \\
0 & r > \varepsilon
\end{array}
\right.
\end{equation}
$$

* Vad blir gränsen då $\varepsilon \to 0^+$?

* Det kan vi tyvärr inte definiera.

* $\rho(\vec{r}) = \lim_{\varepsilon \to 0^+} \rho_\varepsilon(\vec{r})$ är inte en funktion; sekvensen av funktioner som erhålls genom att variera $\varepsilon$ kallas för en distribution.

## Deltafunktioner i en dimension

### Punktkälla i $D=1$

I en dimension kan vi definiera en punktkälla från potentialen

$$
\begin{equation}
\phi(x) = -\frac{q}{2} \left| x \right|
\end{equation}
$$

vilket ger fältet

$$
\begin{equation}
\vec{F}(x) = -\hat{x} \frac{\mbox{d}\phi}{\mbox{d}x} =
\left\{
\begin{array}{ll}
\frac{q}{2} \hat{x} & x > 0 \\
-\frac{q}{2} \hat{x} & x < 0 \\
\end{array}
\right.
\end{equation}
$$

Vi kallar den enda komponenten av detta vektorfält för $F(x)$. Motsvarigheten till Gauss sats för detta endimensionella fält är

$$
\begin{equation}
\int_a^b \frac{\mbox{d}F}{\mbox{d}x} \mbox{d}x  = F(b) - F(a) =
\left\{
\begin{array}{ll}
q, & \mathrm{om~} a < 0 < b \\
0, & \mathrm{annars} \\
\end{array}
\right.
\end{equation}
$$

medan en naiv insättning av $\mbox{d}F / \mbox{d}x = 0$ i VL hade gett noll.

Problemet är ju att $\frac{\mbox{d}F}{\mbox{d}x} = 0$ för $x \neq 0$, men $\frac{\mbox{d}F}{\mbox{d}x} =  \infty$ för $x = 0$. Vi kan uttrycka detta som en "funktion", $\frac{\mbox{d}F}{\mbox{d}x} = q \delta(x)$, som är noll då $x \neq 0$ men som också uppfyller

$$
\begin{equation}
\int_{a<0}^{b>0} \delta(x) \mbox{d}x = 1.
\end{equation}
$$

### Distributioner

Vi konstruerar denna "funktion" som en gräns $\varepsilon \to 0^+$ för

$$
\begin{equation}
h_\varepsilon(x)
=
\left\{
\begin{array}{ll}
0 & |x| > \frac{\varepsilon}{2} \\
\frac{1}{\varepsilon} & |x| < \frac{\varepsilon}{2} \\
\end{array}
\right.
\end{equation}
$$

### Kontrollera

$$
\lim_{\varepsilon \to 0} h_\varepsilon(x) = 0,
$$
för $x \neq 0$. Dessutom har vi
$$
\lim_{\varepsilon \to 0} \int_{a<0}^{b>0} h_\varepsilon(x) \mbox{d}x
= \lim_{\varepsilon \to 0} \int_{-\varepsilon/2}^{\varepsilon/2} \frac{1}{\varepsilon} \mbox{d}x
= \lim_{\varepsilon \to 0} 1 = 1.
$$
---------------------------------------

Men det finns också andra möjligheter

$$
\begin{equation}
h_\varepsilon(x) = \frac{\exp(-x^2 / \varepsilon^2)}{\sqrt{\pi} \varepsilon},
\end{equation}
$$

$$
\begin{equation}
h_\varepsilon(x) = \frac{\varepsilon}{\pi (x^2 + \varepsilon^2)},
\end{equation}
$$

$$
\begin{equation}
h_\varepsilon(x) = \frac{\sin(x/\varepsilon)}{\pi x} \label{eq:sinxdelta} \tag{1}.
\end{equation}
$$

```
import numpy as np
import pylab as p

# Delta function approximation 1 - 4
def h(eps,x,case):
    if case==0:
        h = np.zeros(len(x))
        h [abs(x) < eps] = 1/eps
        return h
    elif case==1:
        return np.exp(-x**2/eps**2) / (np.sqrt(np.pi)*eps)
    elif case==2:
        return eps / (np.pi * (x**2 + eps**2))
    elif case==3:
        return np.sin(x/eps) / (np.sqrt(np.pi)*x)

# End initialization

# Start plot
x=np.linspace(-1,1,200)
for case in range(4):
    p.figure(figsize=(6,6))
    for eps in np.linspace(0.5,0.1,5):
        p.plot(x,h(eps,x,case))
    p.xlabel(r'$x$')
    p.ylabel(r'$h_{%i,\epsilon}(x)$' %case)
```

Samtliga dessa utgör en *sekvens av funktioner* (en *distribution*) från vilka vi kan definiera *Diracs deltafunktion*

$$
\begin{equation}
\delta(x) = \lim_{\varepsilon \to 0^+} h_\varepsilon(x)
\end{equation}
$$

med de definierande egenskaperna

$$
\begin{equation}
\delta(x) = 0, \quad x \neq 0
\end{equation}
$$

$$
\begin{equation}
f(0) = \int_a^b f(x) \delta(x) \mbox{d}x,
\end{equation}
$$

där $f(x)$ är en välbeteende funktion och $\left[ a,b \right]$ inkluderar 0. Ett specialfall av ovanstående är

$$
\begin{equation}
\int_{-\infty}^{+\infty} \delta(x) \mbox{d}x = 1
\end{equation}
$$

------------------------------

### Exempel

-----------------------

Kontrollera att vi erhåller Diracs deltafunktion från sekvensen $h_\varepsilon(x) = \frac{\exp(-x^2 / \varepsilon^2)}{\sqrt{\pi} \varepsilon}$.

*Lösning*:
För $x \neq 0$ gäller

$$
\begin{equation}
h_\varepsilon(x)
= \frac{1}{\sqrt{\pi} \varepsilon \exp(x^2 / \varepsilon^2)}
= \frac{1}{\sqrt{\pi} \varepsilon \left[ 1 + \frac{x^2}{\varepsilon^2} + \frac{1}{2}\left(\frac{x^2}{\varepsilon^2}\right)^2 + \ldots \right] } \nonumber
\end{equation}
$$

$$
\begin{equation}
= \frac{\varepsilon}{\sqrt{\pi}} \frac{1}{\left( x^2 + \varepsilon^2 + \frac{x^4}{2\varepsilon^2} + \ldots \right)} \to 0 \quad \mathrm{då~} \varepsilon \to 0^+
\label{eq:heps0} \tag{2}
\end{equation}
$$

Vidare har vi integralen $\int_{-\infty}^\infty e^{-x^2 / \varepsilon^2} \mbox{d}x = \sqrt{\pi \varepsilon^2}$ (se tabell över definita integraler, eventuellt Beta 7.5-41). Detta ger

$$
\begin{equation}
\lim_{\varepsilon \to 0^+}
\int_{-\infty}^{+\infty} \frac{\exp(-x^2 / \varepsilon^2)}{\sqrt{\pi} \varepsilon} \mbox{d}x = \lim_{\varepsilon \to 0} \frac{\sqrt{\pi \varepsilon^2}}{\sqrt{\pi} \varepsilon} = 1, \quad \mathrm{för~} \varepsilon>0.
\label{eq:heps1} \tag{3}
\end{equation}
$$

För att vara helt korrekta skall vi egentligen visa den mer allmänna egenskapen $\int_a^b f(x) \delta(x) \mbox{d}x = f(0)$ för en väl beteende funktion $f(x)$. Eftersom ekv. [(2)](#eq:heps0) gäller, och $f(x)$ inte utgör något problem, kan vi utöka integrationsintervallet och istället studera

$$
\begin{equation}
\int_{-\infty}^{+\infty} f(x) \delta(x) \mbox{d}x = f(0).
\end{equation}
$$

Vi Taylorutvecklar, $f(x) = f(0) + f'(0)x + f''(0)x^2/2+\ldots$, och konstaterar att

$$
\begin{equation}
\lim_{\varepsilon \to 0} \int_{-\infty}^{+\infty} f(0) h_\varepsilon(x) \mbox{d}x = f(0) \lim_{\varepsilon \to 0} \int_{-\infty}^{+\infty} h_\varepsilon(x) \mbox{d}x = f(0),
\end{equation}
$$

enligt vad vi visat ovan [(3)](#eq:heps1). Det återstår att visa att

$$
\begin{equation}
\lim_{\varepsilon \to 0} \int_{-\infty}^{+\infty} x^n h_\varepsilon(x) \mbox{d}x = 0,
\label{eq:xnheps0} \tag{4}
\end{equation}
$$

för alla heltal $n>0$. I vårt fall har vi en jämn funktion $h_\varepsilon(x)$ vilket gör att ekv. [(4)](#eq:xnheps0) är trivialt uppfyllt för udda $n$ då integranden blir udda. För jämna $n=2k$ finner vi (se t.ex. Beta 7.5-42)

$$
\begin{equation}
\lim_{\varepsilon \to 0^+}
\int_{-\infty}^{+\infty} x^{2k} \frac{\exp(-x^2 / \varepsilon^2)}{\sqrt{\pi} \varepsilon} \mbox{d}x = \lim_{\varepsilon \to 0} \frac{2}{\sqrt{\pi} \varepsilon} \frac{(2k-1)!!}{2^{k+1}} \sqrt{\pi} \varepsilon \varepsilon^{2k} = 0, \quad \mathrm{för~} \varepsilon>0.
\end{equation}
$$

Alltså har vi visat att

$$
\begin{equation}
\lim_{\varepsilon \to 0^+}
\int_{a<0}^{b>0} f(x) \frac{\exp(-x^2 / \varepsilon^2)}{\sqrt{\pi} \varepsilon} \mbox{d}x = f(0), \quad \mathrm{för~} \varepsilon>0.
\end{equation}
$$

-----------------------------------------------

## Egenskaper hos Diracs deltafunktion

* Jämn: $$\delta(-x) = \delta(x)$$

* Skalning:

$$
\delta(ax) = \frac{1}{|a|} \delta(x).
$$

[Comment 1: Visas enklast genom att göra substitutionen $y=x a$ i uttrycket $\int_{-\infty}^{+\infty} f(x) \delta(ax) \mbox{d}x$. Var noga med tecknet på integrationsgränserna.]
* Translation:

$$
\int_{-\infty}^{+\infty} f(x) \delta(x-x_0) \mbox{d}x = f(x_0).
$$
[Comment 2: visas genom substitutionen $y=x-x_0$.]
* Derivata

$$
\int_{-\infty}^{+\infty} f(x) \delta'(x-x_0) \mbox{d}x = -\int_{-\infty}^{+\infty} f'(x) \delta(x-x_0) \mbox{d}x = -f'(x_0),
$$
vilket kan betraktas som definitionen av derivatan $\delta'(x)$.

[Comment 3: Visas genom partiell integration med någon av funktionssekvenserna som definierar deltafunktionen.]
* Kan generaliseras till andra dimensioner. Vi skriver generellt $\delta^{(D)}(\vec{r})$, där vi skall tolka superskriptet som antalet dimensioner. T.ex. har vi för $D=3$

$$
\delta^{(3)}(\vec{r}) = \delta(x) \delta(y) \delta(z).
$$
I sfäriska koordinater blir detta
$$
\iiint f(\vec{r}) \delta^{(3)}(\vec{r} - \vec{r}_0) r^2 \sin\theta \mbox{d}r \mbox{d}\theta \mbox{d}\phi = f(\vec{r}_0).
$$
Med vissa förbehåll kan deltafunktionen i kroklinjiga koordinater skrivas
$$
\delta^{(3)}(\vec{r} - \vec{r}_0) = \frac{1}{h_1(\vec{r}_0) h_2(\vec{r}_0) h_3(\vec{r}_0)} \delta(u_1-u_{1,0}) \delta(u_2-u_{2,0}) \delta(u_3-u_{3,0}).
$$

## Deltafunktioner i högre dimensioner

Vi startar med punktkällan i origo: $\vec{F} = \frac{q}{4 \pi r^2} \hat{e}_r$, och den problematiska volymsintegralen
$$
\int_V \nabla \cdot \vec{F} \mbox{d}V,
$$
som borde bli lika med $q$ om $V$ omfattar origo. Detta kan vi åstadkomma genom att införa $\nabla \cdot \vec{F} = q \delta^3(x) = q \delta(x)\delta(y)\delta(z)$ eftersom
$$
\iiint \mbox{d}x \mbox{d}y \mbox{d}z \delta(x)\delta(y)\delta(z) = 1.
$$
Ännu bättre blir det om vi jobbar konsekvent med sfäriska koordinater. Inför ett *regulariserat* fält

$$
\begin{equation}
\vec{F}_\varepsilon(\vec{r}) = \frac{q}{4 \pi (r^2 + \varepsilon^2)} \hat{e}_r
\end{equation}
$$

som uppenbarligen går mot $\vec{F}$ då $\varepsilon \to 0^+$.

Divergensen blir ($\nabla \cdot \vec{F} = \frac{1}{r^2} \frac{\partial}{\partial r} (r^2 F_r) + \ldots$)

$$
\begin{equation}
\nabla \cdot \vec{F}_\varepsilon(\vec{r}) = \frac{q}{4 \pi r^2} \underbrace{\frac{\partial}{\partial r} \left( \frac{r^2}{r^2 + \varepsilon^2} \right)}_{=\frac{2r}{r^2 + \varepsilon^2} - \frac{2 r r^2}{(r^2 + \varepsilon^2)^2} = \frac{2 r \varepsilon^2}{(r^2 + \varepsilon^2)^2}}
\end{equation}
$$

$$
\begin{equation}
= \frac{q \varepsilon^2}{2 \pi} \frac{1}{r(r^2 + \varepsilon^2)^2} \to 0 \quad \mathrm{då} \; \varepsilon \to 0.
\end{equation}
$$

Utan styrkan $q$ kallar vi denna sekvens av funktioner för $h_\varepsilon(\vec{r})$ och påstår att $\lim_{\varepsilon \to 0} h_\varepsilon(\vec{r}) = \delta^3(\vec{r})$. Utför integralen

$$
\begin{equation}
\int_V \nabla \cdot \vec{F}_\varepsilon \mbox{d} V = \int_V q h_\varepsilon(\vec{r}) \mbox{d} V = \frac{ q \varepsilon^2}{2 \pi} 4\pi \int_0^\infty r^2 \mbox{d} r \frac{1}{r(r^2 + \varepsilon^2)^2}
\end{equation}
$$

$$
\begin{equation}
= 2 q \varepsilon^2 \left[ -\frac{1}{2} \frac{1}{r^2 + \varepsilon^2} \right]_0^\infty = 2 q \varepsilon^2 \frac{1}{2 \varepsilon^2} = q
\end{equation}
$$

Alltså har vi visat att
* $\lim_{\varepsilon \to 0} h_\varepsilon(\vec{r}) = 0$ för $r \neq 0$.

* $\int_{\mathbf{R}^3} h_\varepsilon(\vec{r}) \mbox{d}V = 1$

Alltså skriver vi källtätheten
$$
\rho(\vec{r}) = \nabla \cdot \vec{F} = -\Delta \phi = q \delta^3(\vec{r}).
$$

### Linjekälla

Linjekällan $\vec{F} = \frac{k}{2 \pi \rho} \hat{e}_\rho$ (motsvarar en punktkälla i $D=2$).
Källtätheten kan skrivas
$$
\nabla \cdot \vec{F} = k \delta^2(\vec{\rho}) \left( = k \delta(x) \delta(y) \right).
$$
Studera t.ex. normalytintegralen genom en cylinder med höjden $L$ runt linjekällan
$$
\int_S \vec{F} \cdot \mbox{d}\vec{S} = \int_{S+S_0+S_L} \vec{F} \cdot \mbox{d}\vec{S} = \int_V \nabla \cdot \vec{F} \mbox{d} V = \int_0^L \mbox{d} z \int \mbox{d}x \mbox{d}y k \delta(x)\delta(y) = \int_0^L \mbox{d} z k = k L.
$$
där vi först har slutit ytan genom att införa ytorna $S_0$ och $S_L$ som är cirkelskivor vid botten och toppen och som har normalytintegralen noll eftersom fältet är vinkelrät mot normalen.

### Virveltråd

Vi kan resonera på liknande sätt för en virveltråd $\vec{F} = \frac{J}{2 \pi \rho} \hat{e}_\phi$. Stokes sats säger att
$$
\int_{\partial S} \vec{F} \cdot \mbox{d} \vec{r} = \int_S (\nabla \times \vec{F}) \cdot \mbox{d} \vec{S},
$$
där vi kan räkna ut $\mathrm{VL} = J$ (t.ex. för en cirkel runt virveltråden). För detta fält är det rotationen som är problematisk. Notera att detta är en vektor.
$$
\nabla \times \vec{F} = J \delta^2(\vec{\rho}) \hat{z} = \vec{J} \delta(x)\delta(y).
$$

### Exempel: tillämpning av deltafunktionen; Fouriertransform och ortogonalitet.

-----------------------

Givet en funktion $f(x)$ definieras dess Fouriertransform som
$$
\tilde f(k)=\frac{1}{\sqrt{2\pi}}\int_{-\infty}^\infty dx\,e^{-ikx}f(x)
$$
Den inversa transformen ger frekvenssönderläggningen av $f(x)$, i detta fall motsvaras "frekvensen" av vågtalet $k$:
$$
f(x)=\frac{1}{\sqrt{2\pi}}\int_{-\infty}^\infty dk\,e^{ikx}\tilde f(k)
$$
(normeringen har valts för att åstadkomma symmetri mellan de två uttrycken).

Genom att sätta in det första uttrycket i det andra får man, under förutättning att man kan byta integrationsordning,
$$
f(x)=\frac{1}{\pi}\int_{-\infty}^\infty dk\,e^{ikx}
\int_{-\infty}^\infty dx'\,e^{-ikx'}f(x')=
\frac{1}{2\pi}\int_{-\infty}^\infty dx'
\left(\,\int_{-\infty}^\infty dk\,e^{ik(x-x')}\right)f(x')
$$

Uttrycket inom parenteser i det sista ledet beror bara på $x-x'$, och om resultatet skall bli $f(x)$ måste det vara en deltafunktion lika med $2\pi\delta(x-x')$. Dvs

$$
\begin{equation}
\delta(x-x') = \frac{1}{2\pi} \int_{-\infty}^\infty dk\,e^{ik(x-x')}.
\label{eq:expdelta} \tag{5}
\end{equation}
$$

Genom att byta vågtalet $k$ och koordinaten $x$ får man också $2\pi \delta(k-k') = \int_{-\infty}^\infty dx\,e^{i(k-k')x}$. Detta sätt att skriva deltafunktionen kunde vi också ha anat från ekv. [(1)](#eq:sinxdelta) genom att byta
$$
\lim_{\varepsilon \to 0} \frac{\sin(x/\varepsilon)}{\pi x} \Rightarrow \lim_{n \to \infty} \frac{\sin(n x)}{\pi x}.
$$
Här kan man nämligen göra omskrivningen

$$
\begin{equation}
\int_{-n}^n e^{i x t} dt = \left[ \frac{e^{ixt}}{ix} \right]_{-n}^n
= \left[ \frac{\cos(xt)+i\sin(xt)}{ix} \right]_{-n}^n
= 2 \frac{\sin(nx)}{x},
\end{equation}
$$

så att vi får

$$
\begin{equation}
\delta(x) = \lim_{n \to \infty} \frac{1}{2\pi} \int_{-n}^n e^{i x t} dt,
\end{equation}
$$

vilket är analogt med ekv. [(5)](#eq:expdelta)

Funktionerna $e_k(x)=\frac{1}{\sqrt{2\pi}}e^{ikx}$ kan alltså ses som ortogonala och "deltafunktionsnormerade" med ortogonalitetsrelationen
$$
\int_{-\infty}^\infty dx\,e^{\mathstrut}_k(x)e^*_{k'}(x)=\delta(k-k')
$$

Man kan bekräfta resultatet genom att göra beräkningen explicit
för de regulariserade funktionerna $e_{k,\varepsilon}(x)={1\over\sqrt{2\pi}}e^{ikx-\varepsilon^2x^2}$ och låta $\varepsilon\rightarrow0$ (se uppgift 7.15).

----------------------------------------------


# Sistemas de Recomendacion avanzados

Diego Galeano, Ph.D.

Material basado en: https://developers.google.com/machine-learning/recommendation/labs/movie-rec-programming-exercise

```
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd
import numpy as np
import copy
import random
import scipy.io
from scipy.optimize import minimize
from scipy.optimize import differential_evolution

# If you want to have direct access to the datasets and codes you can clone the following github repository
! git clone https://github.com/saminehbagheri/Recommender-System.git
%cd Recommender-System
```

    /usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
      import pandas.util.testing as tm

    Cloning into 'Recommender-System'...
    remote: Enumerating objects: 66, done.
    remote: Total 66 (delta 0), reused 0 (delta 0), pack-reused 66
    Unpacking objects: 100% (66/66), done.
    /content/Recommender-System

### Leemos el Movielens dataset

```
mat = scipy.io.loadmat('ex8_movies.mat')
movie_names = pd.read_csv('movie_ids.txt',delimiter=';',header=None)[1]
Y=mat['Y']
R=mat['R']
num_user=Y.shape[1]
num_movie=Y.shape[0]
density= 100*np.sum(np.where(Y > 0,1,0))/(num_user*num_movie)
print("numero de usuarios:"+str(num_user))
print("numero de peliculas:"+str(num_movie))
print("densidad del rating matrix: "+str(density) + '%')
```

    numero de usuarios:943
    numero de peliculas:1682
    densidad del rating matrix: 6.304669364224532%

## Matrix factorizacion: collaborative filtering

Recordando el sistema de recomendación basado en contenido, la idea era describir cada item y cada usuario con un vector de características importantes. En nuestro ejemplo de la clase anterior, el vector de características que usamos tenía solo tres elementos, pero somos conscientes de que hay criterios mucho más importantes que esos tres para capturar nuestro interés por una película y, a veces, estas características pueden ser más complicadas que simplemente el género de la película.

 Matrix Factorization supone que podemos describir una película y un usuario en forma de un vector de características. La idea principal de la factorización matricial es encontrar el vector de característica de usuario adecuado $ \vec {x}_i $ y el vector de característica de elemento $ \vec{\theta}_j $ para todos los usuarios $ i = 1 \cdots n_u $ y películas $ j = 1 \cdots n_m $ que sus productos punto dan una buena estimación de la calificación que el $ i $ -ésimo usuario daría a la $ j $ -ésima película $ y_ {ij} $.

 **Porque este metodo se llama decomposicion de matrices?** Porque el rating que un usuario da a una pelicula se modela como el producto escalar entre el vector de caracteristicas del usuario y el vector de caracteristicas de la pelicula, es decir: $\vec{x}_i\cdot\vec{\theta}_j=y_{ij}$. Supongamos que apilamos todos los vectores de características del usuario en la matriz de características del usuario $\mathbf{X}=\begin{bmatrix}-\vec{x}_1^T-\\ -\vec{x}_2^T- \\ \vdots \\-\vec{x}_{n_u}^T- \end{bmatrix}_{n_u \times n_f}$  y todos los vectores de características de la película juntos en la matriz de características de la película $\mathbf{\Theta}=\begin{bmatrix}-\vec{\theta}_1^T-\\ -\vec{\theta}_2^T- \\ \vdots \\-\vec{\theta}_{n_m}^T- \end{bmatrix}_{n_m \times n_f}$. Entonces, la matriz de calificación se puede determinar de la siguiente manera:

\begin{equation}\mathbf{\Theta} \cdot \mathbf{X}^T=\mathbf{R}\end{equation}
\begin{equation}\begin{bmatrix}- \vec{\theta}_1^T-\\ - \vec{\theta}_2^T- \\ \vdots \\- \vec{\theta}_{n_m}^T- \end{bmatrix} \cdot \begin{bmatrix}|&|&\cdots&|\\
               \vec{x_1}&\vec{x_2}&\cdots&\vec{x}_{n_u}\\
               |&|&\cdots&|\end{bmatrix}=\mathbf{R}\end{equation}

Sería perfecto si tuviéramos las matrices de características adecuadas, pero no las tenemos. Lo que en realidad tenemos es la matriz de calificación $ \mathbf{Y}$ e intentamos *aprender* las matrices de características de $ \mathbf{Y}$. El nombre **factorización de matrices** proviene de este punto que este algoritmo tiende a encontrar matrices de características razonablemente óptimas al factorizar la matriz de calificación $ \mathbf{Y}$.

**¿Cómo funciona la factorización de matrices?** Supongamos que iniciamos las matrices de características con valores completamente aleatorios. El producto escalar del vector de características de usuario $ i$ -ésimo y el vector de características de película $ j $ -ésimo dará $ p_{ij} $ que probablemente sea muy diferente a la calificación real dada $ y_{ij} $. La idea es encontrar las matrices de características de manera que el error $ | p_{ij} -y_{ij} | $ sea lo más mínimo posible para todas las calificaciones dadas. En otras palabras, la factorización matricial se convirtió en un problema de optimización de minimizar una función de costo que es la suma de todos los errores $ \color {green}{\text{al cuadrado}} $ para todas las celdas con una calificación. La función de costo a minimizar se define de la siguiente manera:

\begin{equation}J(\mathbf{\Theta},\mathbf{X})=\frac{1}{2} \sum\limits_{(i,j):r(i,j)=\{1\}} (\vec{\theta}_{j} \vec{x}_{i}-y_{i,j})^2\end{equation}

**¿Cómo resolver un problema de optimización de este tipo?** Podemos pasar la función de costo con un punto de partida aleatorio a un optimizador y esperar hasta que el optimizador encuentre una solución óptima, pero esto llevará un tiempo insoportablemente largo ya que el problema de optimización es dimensional muy alto. ¿Puede adivinar cuál es la dimensión de entrada de este problema de optimización? $ (n_u + n_m) * n_f $, donde $ n_f $ es el número de caracteristicas.

Es más lógico calcular los gradientes hacia la solución óptima de manera iterativa mediante un descenso de gradiente o métodos de gradiente conjugado. Es muy sencillo calcular los gradientes ya que nuestra función de costo es una función cuadrática.

## The Gradients
 \begin{equation}\frac{\partial J}{\partial x_i^{(k)}}=\sum\limits_{j:r(i,j)=1}(\vec{\theta}_{j} \vec{x}_{i}-y_{i,j})\theta^{(k)}_j,\end{equation}
 \begin{equation}\frac{\partial J}{\partial \theta_j^{(k)}}=\sum\limits_{i:r(i,j)=1}(\vec{\theta}_{j} \vec{x}_{i}-y_{i,j})x^{(k)}_i,\end{equation}

donde $x^{(k)}_j$ es el elemento $k$-esimo del $i$-esimo vector de caracteristicas del usuario $\vec{x}_i$.

Los pasos principales del algoritmo de factorización matricial se pueden resumir a continuación:

1. Inicialice $ \mathbf {\Theta} $ y $ \mathbf{X} $ con números pequeños aleatorios
2. Minimice la función de costo $ J (\mathbf{\Theta}, \mathbf{X}) $
3. Utilice las matrices de características optimizadas para predecir

<a id='MFPy'> </a>
# Implementación de factorización matricial

## Función de inicialización de parámetros
Esta función simplemente obtiene el número de usuarios, el número de elementos y el número de características como entrada y devuelve la matriz de características del usuario iniciada aleatoriamente y la matriz de características del elemento.

```
def initilizeFeat(nu,ni,nf,seed=42):
    '''
    Inicializacion de las matrices de caracteristicas.
    '''
    # semilla de randomizacion
    np.random.seed(seed)
    # inicializacion aleatoria de matrices de caracteristicas
    Theta = np.random.rand(nu,nf)*0.05
    X = np.random.rand(ni,nf)*0.05
    return X, Theta
```

## Funciones auxiliares
Tenemos dos funciones auxiliares. Una para aplanar las matrices de características en una matriz 1-D, y otra hace lo inverso.

```
def flatterRev(x,nu,ni,nf):
    '''
    Convierte un vector 1-D a las matrices de caracteristicas X y Theta.
      x: vector 1-D.
      nu: numero de usuarios.
      ni: numero de items.
      nf: numero de caracteristicas.
    '''
    X=x[0:ni*nf].reshape((ni,nf),order='F')
    Theta=x[ni*nf:].reshape((nu,nf),order='F')
    return X,Theta

def flatter(X, Theta):
    '''
    Convierte las matrices de caracteristicas a un vector 1-D.
      X: matriz de caracteristicas de usuarios.
      Theta: matriz de caracteristicas de peliculas.
    '''
    x=np.concatenate([X.reshape(X.shape[0]*X.shape[1],order='F'),Theta.reshape(Theta.shape[0]*Theta.shape[1],order='F')])
    return(x)

```

# Función de costo
La función de costo es simplemente la implementación de la fórmula de la función de costo que hemos discutido anteriormente en una forma vectorizada para evitar bucles for anidados para las sumas. La única diferencia es un término adicional vinculado con el parámetro $ \lambda $, el parámetro de regularización.

```
def costFunc(X,Theta,R,M,la=0):
    '''
    Funcion auxiliar de la funcion de costo.

    '''
    R=np.ma.array(R, mask=M)
    e=0.5*np.sum(np.power((np.dot(Theta,X.T)-R),2))+la*0.5*np.sum(np.power(Theta, 2))+la*0.5*np.sum(np.power(X, 2))
    return(e/np.sum(M==False))

def CF(x,R,M,nu,ni,nf,la=0):
    '''
    Funcion de costo con termino de regularizacion.
      x: 1-D vector que contiene X y Theta.
      R: matriz de ratings.
      M: matriz de enmascaramiento para solo optimizar en los ratings observados.
      nu: numero de usuarios.
      ni: numero de items.
      nf: numero de caracteristicas.
      la: lambda, termino de regularizacion L2.
    '''
    X, Theta=flatterRev(x,nu,ni,nf)
    error=costFunc(X,Theta,R,M,la=la)
    return error
```

# Gradiente

```
def gradFunc(x,R,M,nu,ni,nf,la=0 ):
    '''
    Retorna los gradientes para el optimizador.

    '''
    X, Theta=flatterRev(x,nu,ni,nf)
    R=np.ma.array(R, mask=M)
    e=np.dot(Theta,X.T)-R
    TG=np.dot(e,X)+la*Theta
    XG=np.dot(e.T,Theta)+la*X
    grads=np.concatenate([XG.reshape(XG.shape[0]*XG.shape[1],order='F'),TG.reshape(TG.shape[0]*TG.shape[1],order='F')])
    return grads/np.sum(M==False)
```

## Función de entrenamiento

La función de entrenamiento en realidad es solo aplicar un algoritmo de optimización en la función de costo. Usamos un optimizador de gradiente conjugado incorporado de la biblioteca scipy. Es posible que desee probar diferentes métodos.

```
def trainMF(R,M,nf,la=0,seed=42):
    nu=R.shape[0]
    ni=R.shape[1]
    R=np.ma.array(R, mask=M)
    X, Theta=initilizeFeat(nu,ni,nf,seed=seed)
    x=flatter(X, Theta)

    res = minimize(CF, x, args=(R,M,nu,ni,nf,la), method='CG',jac=gradFunc,options={ 'disp': True,'gtol':1e-5})
    MSE=CF(res.x,R,M,nu,ni,nf,la)
    return(MSE, res,nu,ni,nf)
```

# Funcion de prediccion

```
def Predict(res,nu,ni,nf,la=0):
    X, Theta=flatterRev(res.x,nu,ni,nf)
    predict=np.dot(Theta,X.T)
    return(predict)
```

# Construyendo el modelo del sistema de recomendación

```
runEXAMPLE=True
def buildRSModel(R,M,mu=None, nf=10,la=0,seed=42, movie_names=None):
    trainR=copy.copy(R)
    trainM=copy.copy(M)

    trainR=np.ma.array(trainR, mask=trainM)
    if mu is None:
        mu=np.average(trainR,axis=1)
    trainR=trainR-mu[:,None]
    trainingError, res,nu,ni,nf=trainMF(trainR,M,nf=nf,la=la,seed=seed)
    model={'trainingError': trainingError, 'res':res,'nu':nu,'ni':ni,'nf':nf, 'la':la, 'movie_names':movie_names, 'mu':mu,
          'R':R,'M':M}
    return model

#Ejemplo
if runEXAMPLE:
    R=mat['Y']
    M=mat['R']
    trainR=copy.copy(R)
    trainM= (M==0)
    mymodel=buildRSModel(R=trainR,M=trainM,mu=None, nf=100,la=0,seed=42, movie_names=movie_names)
    #print(mymodel)
```

    Warning: Desired error not necessarily achieved due to precision loss.
             Current function value: 0.158532
             Iterations: 16
             Function evaluations: 335
             Gradient evaluations: 324

# Predicción para el usuario X

```
runEXAMPLE=True
def predictForUserX(user_Id,model,movie_Id=None):
    trainingError=model['trainingError']
    res=model['res']
    nu=model['nu']
    ni=model['ni']
    nf=model['nf']
    la=model['la']
    movie_names=model['movie_names']
    mu=model['mu']
    R=model['R']
    M=model['M']
    mypredict=Predict(res,nu,ni,nf,la=0)
    mydata=pd.DataFrame()
    Pred=mypredict[:,user_Id]+mu[user_Id]
    mydata['names']=movie_names
    mydata['predictedRating']=Pred
    mydata['originalrating']=R[:,user_Id]
    mydata=mydata.sort_values(by=['predictedRating'], ascending=False)
    output=mydata[mydata['originalrating'] == 0]
    return(output)

#Example
if runEXAMPLE:
    user_id = 125
    print(predictForUserX(user_id,mymodel,movie_Id=None).head())
```

                                  names  predictedRating  originalrating
    471               Dragonheart(1996)         4.268084               0
    679        Kull the Conqueror(1997)         4.233533               0
    320                    Mother(1996)         4.195186               0
    627                  Sleepers(1996)         4.182644               0
    221  Star Trek: First Contact(1996)         4.162360               0

# Ingrese su propia calificación

Puede usar esta función para ingresar su propia calificación y ver lo que sugiere el sistema. Si no configura el parámetro model, se utilizarán los valores predeterminados de nf = 100, la = 0.1.

```
def weRecommend(myratings,modelparam=None):
    movie_names = pd.read_csv('movie_ids.txt',delimiter=';',header=None)[1]
    mat = scipy.io.loadmat('ex8_movies.mat')
    print("Reading the data")
    R=mat['Y']
    M=mat['R']
    trainR=copy.copy(R)
    trainM= (M==0)
    num_user=R.shape[1]
    num_movie=R.shape[0]

    myratings=myratings.sort_values(by=['names'], ascending=False)
    movies=copy.copy(movie_names)
    movies=movies.sort_values( ascending=False)
    indices=movies[movies.isin( myratings['names'])].index
    newuserratingR=np.zeros(num_movie)
    newuserratingM=np.zeros(num_movie)
    newuserratingR[indices]=myratings['rating']
    newuserratingM[indices]=1

    newuserratingM= (newuserratingM==0)
    trainR=np.concatenate((newuserratingR[:,None],trainR),axis=1)
    trainM=np.concatenate((newuserratingM[:,None],trainM),axis=1)

    print("Training the Recommender System...")
    if modelparam is None:
        mymodel=buildRSModel(R=trainR,M=trainM,mu=None, nf=100,la=0.1, movie_names=movie_names)
    else:
        nf=modelparam['nf']
        la=modelparam['la']
        mymodel=buildRSModel(R=trainR,M=trainM,mu=None, nf=nf,la=la, movie_names=movie_names)
    print("Training is successfully finished")
    bests=predictForUserX(0,mymodel,movie_Id=None).head(15)
    worsts=predictForUserX(0,mymodel,movie_Id=None).tail(15)
    print("Predicting you're ratings:")
    bests=bests.iloc[:, :-1]
    worsts=worsts.iloc[:, :-1]
    output={'bests':bests,'worsts':worsts}
    return output

```

```
 movie_names = pd.read_csv('movie_ids.txt',delimiter=';',header=None)[1]
```

```
df = pd.DataFrame()
df['rating'] = [5,4,5,5,5]
df['names'] = ['Toy Story(1995)', 'Batman Forever(1995)', 'Ace Ventura: Pet Detective(1994)', 'Lion King  The(1994)', 'Mask  The(1994)' ]
```

```
 weRecommend(df)
```

    Reading the data
    Training the Recommender System...
    Warning: Desired error not necessarily achieved due to precision loss.
             Current function value: 0.164909
             Iterations: 16
             Function evaluations: 274
             Gradient evaluations: 263
    Training is successfully finished
    Predicting you're ratings:

    {'bests':                                    names  predictedRating
     94                         Aladdin(1992)         4.137037
     587           Beauty and the Beast(1991)         4.103507
     741                         Ransom(1996)         4.080346
     236                  Jerry Maguire(1996)         4.039222
     185            Blues Brothers  The(1980)         4.032696
     392                 Mrs. Doubtfire(1993)         4.028297
     293                      Liar Liar(1997)         4.027884
     158                 Basic Instinct(1992)         4.027414
     767                         Casper(1995)         4.020870
     229  Star Trek IV: The Voyage Home(1986)         4.020013
     635           Escape from New York(1981)         4.018811
     595   Hunchback of Notre Dame  The(1996)         4.017570
     6                   Twelve Monkeys(1995)         4.015794
     68                    Forrest Gump(1994)         4.013471
     95      Terminator 2: Judgment Day(1991)         4.012567,
     'worsts':                              names  predictedRating
     273                  Sabrina(1995)         3.787733
     215  When Harry Met Sally...(1989)         3.784047
     184                   Psycho(1960)         3.782446
     270        Starship Troopers(1997)         3.779023
     746             Benny & Joon(1993)         3.778370
     495     Its a Wonderful Life(1946)         3.776042
     339            Boogie Nights(1997)         3.768823
     244          Devils Own  The(1997)         3.767892
     474            Trainspotting(1996)         3.763347
     214          Field of Dreams(1989)         3.761476
     684       Executive Decision(1996)         3.748189
     172      Princess Bride  The(1987)         3.732079
     143                 Die Hard(1988)         3.730593
     149                 Swingers(1996)         3.724997
     116                Rock  The(1996)         3.685122}

# Optimizar los parametros del sistema de recomendacion

## Función de error de prueba

Existen diferentes enfoques para evaluar el desempeño de un sistema de recomendación en los datos de prueba. Un método consiste en calcular el error cuadrático medio de los datos de prueba. Otro enfoque es medir algún tipo de precisión de predicción. En la siguiente celda, definimos la precisión como el porcentaje de las calificaciones pronosticadas que tienen un error de 1 o menos.

```
def testMF(tR,tM,predict):
    tR=np.ma.array(tR, mask=tM)
    e=np.abs(tR-predict)
    testMSE=np.sum(np.power(e,2))/np.sum(tM==False)
    return(testMSE)
```

```
def splitMatrix(R,M,testPer):
    trainPer=1-testPer
    num_user=R.shape[1]
    num_movie=R.shape[0]
    overallRating=np.sum(M)
    testsize=testPer*overallRating
    testsize=testsize.astype(int)

    #split tarining and test dataset
    random.seed( 9273482 )
    ind1, ind2=np.where(M==1)
    testSamples=random.sample(range(ind1.shape[0]), testsize)
    testInd1=ind1[testSamples]
    testInd2=ind2[testSamples]

    trainR=copy.copy(R)
    trainM=copy.copy(M)
    trainR[testInd1,testInd2]=0
    trainM[testInd1,testInd2]=0

    M= (trainM==0)
    trainR=np.ma.array(trainR, mask=M)
    mu=np.average(trainR,axis=1)

    testR=copy.copy(R)
    testM=np.zeros(shape = (testR.shape[0],testR.shape[1]))
    testM[testInd1,testInd2]=1
    tM=(testM==0)
    testR=testR*testM
    testR=np.ma.array(testR, mask=tM)
    return trainR, M, testR, tM, mu
```

```
DORUN=True

trainR, M, testR, tM, mu=splitMatrix(Y,R,0.1)
NF=[1,5,10,20]
myseed=5623
if DORUN:
    trainR=trainR-mu[:,None]
    testR=testR-mu[:,None]

    for nf in NF:
        trainingError, res,nu,ni,nf=trainMF(trainR,M,nf=nf,la=0.1,seed=myseed)
        mypredict=Predict(res,nu,ni,nf,la=0)
        zeropredict=np.zeros(shape = (mypredict.shape[0],mypredict.shape[1]))
        testError=testMF(testR,tM,mypredict)

        print('[nf='+str(nf)+']'+'Training Error:'+str(trainingError))
        print('[nf='+str(nf)+']'+'Test Error:'+str(testError))

```

    Warning: Desired error not necessarily achieved due to precision loss.
             Current function value: 0.467095
             Iterations: 4
             Function evaluations: 74
             Gradient evaluations: 63
    [nf=1]Training Error:0.46709498777841846
    [nf=1]Test Error:0.995016231120181
    Warning: Desired error not necessarily achieved due to precision loss.
             Current function value: 0.466449
             Iterations: 3
             Function evaluations: 103
             Gradient evaluations: 91
    [nf=5]Training Error:0.46644884839687906
    [nf=5]Test Error:0.9962897332608236
    Warning: Desired error not necessarily achieved due to precision loss.
             Current function value: 0.463523
             Iterations: 2
             Function evaluations: 59
             Gradient evaluations: 48
    [nf=10]Training Error:0.4635231472060253
    [nf=10]Test Error:0.9924336179545064
    Warning: Desired error not necessarily achieved due to precision loss.
             Current function value: 0.466339
             Iterations: 3
             Function evaluations: 92
             Gradient evaluations: 80
    [nf=20]Training Error:0.4663389769881152
    [nf=20]Test Error:0.9980363109345022

```

```


# Start-to-Finish Example: Setting up Polytropic [TOV](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation) Initial Data, in Curvilinear Coordinates

## Authors: Zach Etienne, Phil Chang, and Leo Werneck
### Formatting improvements courtesy Brandon Clark

## This module sets up initial data for a TOV star in *spherical, isotropic coordinates*, using the *Numerical* ADM Spherical to BSSN Curvilinear initial data module (numerical = BSSN $\lambda^i$'s are computed using finite-difference derivatives instead of exact expressions).

**Notebook Status:**  Validated

**Validation Notes:** This module has been validated to exhibit convergence to zero of the Hamiltonian constraint violation at the expected order to the exact solution (see [plots](#convergence) at bottom). Note that convergence at the surface of the star will be lower order due to the sharp drop to zero in $T^{\mu\nu}$.

### NRPy+ Source Code for this module:

* [TOV/TOV_Solver.py](../edit/TOV/TOV_Solver.py); ([**NRPy+ Tutorial module reviewing mathematical formulation and equations solved**](Tutorial-ADM_Initial_Data-TOV.ipynb)); ([**start-to-finish NRPy+ Tutorial module demonstrating that initial data satisfy Hamiltonian constraint**](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb)): Tolman-Oppenheimer-Volkoff (TOV) initial data; defines all ADM variables and nonzero $T^{\mu\nu}$ components in Spherical basis.
* [BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py](../edit/BSSN/ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py); [\[**tutorial**\]](Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb): *Numerical* Spherical ADM$\to$Curvilinear BSSN converter function
* [BSSN/BSSN_constraints.py](../edit/BSSN/BSSN_constraints.py); [\[**tutorial**\]](Tutorial-BSSN_constraints.ipynb): Hamiltonian constraint in BSSN curvilinear basis/coordinates

## Introduction:
Here we use NRPy+ to set up initial data for a [simple polytrope TOV star](https://en.wikipedia.org/wiki/Tolman%E2%80%93Oppenheimer%E2%80%93Volkoff_equation).

The entire algorithm is outlined as follows, with links to the relevant NRPy+ tutorial notebooks listed at each step:

1. Allocate memory for gridfunctions, including temporary storage for the Method of Lines time integration [(**NRPy+ tutorial on NRPy+ Method of Lines algorithm**)](Tutorial-Method_of_Lines-C_Code_Generation.ipynb).
1. Set gridfunction values to initial data
    * [**NRPy+ tutorial on TOV initial data**](Tutorial-ADM_Initial_Data-TOV.ipynb)
    * [**NRPy+ tutorial on validating TOV initial data**](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.ipynb).
1. Evaluate the Hamiltonian constraint violation
    * [**NRPy+ tutorial on BSSN constraints**](Tutorial-BSSN_constraints.ipynb)
1. Repeat above steps at two numerical resolutions to confirm convergence of Hamiltonian constraint violation to zero.

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

This notebook is organized as follows

1. [Step 1](#initializenrpy): Set core NRPy+ parameters for numerical grids and reference metric
1. [Step 2](#adm_id_tov): Set up ADM initial data for polytropic TOV Star
    1. [Step 2.a](#tov_interp): Interpolating the TOV data file as needed
    1. [Step 2.b](#source): Compute source terms $S_{ij}$, $S_{i}$, $S$, and $\rho$
    1. [Step 2.c](#jacobian): Jacobian transformation on the ADM/BSSN source terms
    1. [Step 2.d](#tensor): Rescale tensorial quantities
1. [Step 3](#adm_id_spacetime): Convert ADM spacetime quantity initial data from Spherical to BSSN Curvilinear coordinates
1. [Step 4](#validate): Validating that the TOV initial data satisfy the Hamiltonian constraint
    1. [Step 4.a](#ham_const_output): Output the Hamiltonian Constraint
    1. [Step 4.b](#apply_bcs): Apply singular, curvilinear coordinate boundary conditions
    1. [Step 4.c](#enforce3metric): Enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$
1. [Step 5](#mainc): `TOV_Playground.c`: The Main C Code
1. [Step 6](#plot): Plotting the single-neutron-star initial data
1. [Step 7](#convergence): Validation: Convergence of numerical errors (Hamiltonian constraint violation) to zero
1. [Step 8](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file

# Step 1: Set core NRPy+ parameters for numerical grids and reference metric \[Back to [top](#toc)\]
$$\label{initializenrpy}$$

```python
# Step P1: Import needed NRPy+ core modules:
from outputC import lhrh,outCfunction,outputC  # NRPy+: Core C code output module
import NRPy_param_funcs as par   # NRPy+: Parameter interface
import sympy as sp               # SymPy: The Python computer algebra package upon which NRPy+ depends
import finite_difference as fin  # NRPy+: Finite difference C code generation module
import grid as gri               # NRPy+: Functions having to do with numerical grids
import indexedexp as ixp         # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
import reference_metric as rfm   # NRPy+: Reference metric support
import cmdline_helper as cmd     # NRPy+: Multi-platform Python command-line interface
import shutil, os, sys           # Standard Python modules for multiplatform OS-level functions

# Step P2: Create C code output directory:
Ccodesdir = os.path.join("TOVID_Ccodes/")
# 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
# !rm -r ScalarWaveCurvilinear_Playground_Ccodes
shutil.rmtree(Ccodesdir, ignore_errors=True)
# Then create a fresh directory
cmd.mkdir(Ccodesdir)

# Step P3: Create executable output directory:
outdir = os.path.join(Ccodesdir,"output/")
cmd.mkdir(outdir)

# Step 1: Set the spatial dimension parameter
#         to three this time, and then read
#         the parameter as DIM.
par.set_parval_from_str("grid::DIM",3)
DIM = par.parval_from_str("grid::DIM")

# Step 2: Set some core parameters, including CoordSystem MoL timestepping algorithm,
#                                 FD order, floating point precision, and CFL factor:
# Choices are: Spherical, SinhSpherical, SinhSphericalv2, Cylindrical, SinhCylindrical,
#              SymTP, SinhSymTP
CoordSystem     = "Spherical"

# Step 2.a: Set defaults for Coordinate system parameters.
#           These are perhaps the most commonly adjusted parameters,
#           so we enable modifications at this high level.

# domain_size     = 7.5 # SET BELOW BASED ON TOV STELLAR RADIUS

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

# 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

# SymTP_bScale sets the default value for:
#   * SinhSymTP's params.bScale
SymTP_bScale    = 0.5 # If SymTP chosen

# Step 2.b: Set the order of spatial finite difference derivatives;
#           and the core data type.
FD_order  = 4        # Finite difference order: even numbers only, starting with 2. 12 is generally unstable
REAL      = "double" # Best to use double here.

# Step 3: Set the coordinate system for the numerical grid
par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem)
rfm.reference_metric() # Create ReU, ReDD needed for rescaling B-L initial data, generating BSSN RHSs, etc.

# Step 4: Set the finite differencing order to FD_order (set above).
par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", FD_order)

# Step 5: Set the direction=2 (phi) axis to be the symmetry axis; i.e.,
#         axis "2", corresponding to the i2 direction.
#         This sets all spatial derivatives in the phi direction to zero.
par.set_parval_from_str("indexedexp::symmetry_axes","2")

# Step 6: The MoLtimestepping interface is only used for memory allocation/deallocation
import MoLtimestepping.C_Code_Generation as MoL
from MoLtimestepping.RK_Butcher_Table_Dictionary import Butcher_dict
RK_method = "Euler" # DOES NOT MATTER; Again MoL interface is only used for memory alloc/dealloc.
RK_order  = Butcher_dict[RK_method][1]
cmd.mkdir(os.path.join(Ccodesdir,"MoLtimestepping/"))
MoL.MoL_C_Code_Generation(RK_method, RHS_string      = "", post_RHS_string = "",
    outdir = os.path.join(Ccodesdir,"MoLtimestepping/"))

# Step 7: Polytropic EOS setup
# For EOS_type, choose either "SimplePolytrope" or "PiecewisePolytrope"
EOS_type = "SimplePolytrope"
# If "PiecewisePolytrope" is chosen as EOS_type, you
#   must also choose the name of the EOS, which can
#   be any of the following:
# 'PAL6', 'SLy', 'APR1', 'APR2', 'APR3', 'APR4',
# 'FPS', 'WFF1', 'WFF2', 'WFF3', 'BBB2', 'BPAL12',
# 'ENG', 'MPA1', 'MS1', 'MS2', 'MS1b', 'PS', 'GS1',
# 'GS2', 'BGN1H1', 'GNH3', 'H1', 'H2', 'H3', 'H4',
# 'H5', 'H6', 'H7', 'PCL2', 'ALF1', 'ALF2', 'ALF3',
# 'ALF4'
EOS_name = 'SLy' # <-- IGNORED IF EOS_type is not PiecewisePolytrope.
```

# Step 2: Set up ADM initial data for polytropic TOV Star \[Back to [top](#toc)\]
$$\label{adm_id_tov}$$

As documented [in the TOV Initial Data NRPy+ Tutorial Module](Tutorial-TOV_Initial_Data.ipynb) ([older version here](Tutorial-GRMHD_UnitConversion.ipynb)), we will now set up TOV initial data, storing the densely-sampled result to file (***Courtesy Phil Chang***).

The TOV solver uses an ODE integration routine provided by scipy, so we first make sure that scipy is installed:

```python
!pip install scipy > /dev/null
```

Next we call the [`TOV.TOV_Solver()` function](../edit/TOV/TOV_Solver.py) ([NRPy+ Tutorial module](Tutorial-ADM_Initial_Data-TOV.ipynb)) to set up the initial data, using the default parameters for initial data. This function outputs the solution to a file named "outputTOVpolytrope.txt".

```python
##########################
# Polytropic EOS example #
##########################
import TOV.Polytropic_EOSs as ppeos

if EOS_type == "SimplePolytrope":
    # Set neos = 1 (single polytrope)
    neos = 1

    # Set rho_poly_tab (not needed for a single polytrope)
    rho_poly_tab = []

    # Set Gamma_poly_tab
    Gamma_poly_tab = [2.0]

    # Set K_poly_tab0
    K_poly_tab0 = 1. # ZACH NOTES: CHANGED FROM 100.

    # Set the eos quantities
    eos = ppeos.set_up_EOS_parameters__complete_set_of_input_variables(neos,rho_poly_tab,Gamma_poly_tab,K_poly_tab0)
    rho_baryon_central = 0.129285
elif EOS_type == "PiecewisePolytrope":
    eos = ppeos.set_up_EOS_parameters__Read_et_al_input_variables(EOS_name)
    rho_baryon_central=2.0
else:
    print("""Error: unknown EOS_type. Valid types are 'SimplePolytrope' and 'PiecewisePolytrope' """)
    sys.exit(1)

import TOV.TOV_Solver as TOV
M_TOV, R_Schw_TOV, R_iso_TOV = TOV.TOV_Solver(eos,
                                              outfile="outputTOVpolytrope.txt",
                                              rho_baryon_central=rho_baryon_central,
                                              return_M_RSchw_and_Riso = True,
                                              verbose = True)

# 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 = 2.0 * R_iso_TOV
```

    1256 1256 1256 1256 1256 1256
    Just generated a TOV star with
    * M        = 1.405030336771405e-01 ,
    * R_Schw   = 9.566044579232513e-01 ,
    * R_iso    = 8.100085557410308e-01 ,
    * M/R_Schw = 1.468768334847266e-01

## Step 2.a: Interpolate the TOV data file as needed to set up ADM spacetime quantities in spherical basis (for input into the `Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear` module) and $T^{\mu\nu}$ in the chosen reference metric basis \[Back to [top](#toc)\]
$$\label{tov_interp}$$

The TOV data file just written stored $\left(r,\rho(r),P(r),M(r),e^{\nu(r)}\right)$, where $\rho(r)$ is the total mass-energy density (cf. $\rho_{\text{baryonic}}$).

**METRIC DATA IN TERMS OF ADM QUANTITIES**

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

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

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

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

**STRESS-ENERGY TENSOR $T^{\mu\nu}$**

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

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

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

and since the 3-metric is diagonal we get

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

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

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

```python
thismodule = "TOVID"
rbar,theta,rho,P,expnu,exp4phi = par.Cparameters("REAL",thismodule,
                                                 ["rbar","theta","rho","P","expnu","exp4phi"],1e300)
IDalpha = sp.sqrt(expnu)
gammaSphDD = ixp.zerorank2(DIM=3)
gammaSphDD[0][0] = exp4phi
gammaSphDD[1][1] = exp4phi*rbar**2
gammaSphDD[2][2] = exp4phi*rbar**2*sp.sin(theta)**2

T4SphUU = ixp.zerorank2(DIM=4)
T4SphUU[0][0] = rho/expnu
T4SphUU[1][1] = P/exp4phi
T4SphUU[2][2] = P/(exp4phi*rbar**2)
T4SphUU[3][3] = P/(exp4phi*rbar**2*sp.sin(theta)**2)
```

```python
expr_list = [IDalpha]
name_list = ["*alpha"]
for i in range(3):
    for j in range(i,3):
        expr_list.append(gammaSphDD[i][j])
        name_list.append("*gammaDD"+str(i)+str(j))

desc = """This function takes as input either (x,y,z) or (r,th,ph) and outputs
all ADM quantities in the Cartesian or Spherical basis, respectively."""
name = "ID_TOV_ADM_quantities"
outCparams = "preindent=1,outCverbose=False,includebraces=False"

outCfunction(
    outfile=os.path.join(Ccodesdir, name + ".h"), desc=desc, name=name,
    params="""  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 trivial metric quantities:
      *KDD00 = *KDD01 = *KDD02 = 0.0;
      /**/     *KDD11 = *KDD12 = 0.0;
      /**/              *KDD22 = 0.0;
      *betaU0 = *betaU1 = *betaU2 = 0.0;
      *BU0 = *BU1 = *BU2 = 0.0;

      // Next set gamma_{ij} in spherical basis
      const REAL rbar  = xyz_or_rthph[0];
      const REAL theta = xyz_or_rthph[1];
      const REAL phi   = xyz_or_rthph[2];

      REAL rho,rho_baryon,P,M,expnu,exp4phi;
      TOV_interpolate_1D(rbar,other_inputs.Rbar,other_inputs.Rbar_idx,other_inputs.interp_stencil_size,
                         other_inputs.numlines_in_file,
                         other_inputs.r_Schw_arr,other_inputs.rho_arr,other_inputs.rho_baryon_arr,other_inputs.P_arr,other_inputs.M_arr,
                         other_inputs.expnu_arr,other_inputs.exp4phi_arr,other_inputs.rbar_arr,
                         &rho,&rho_baryon,&P,&M,&expnu,&exp4phi);\n"""+
    outputC(expr_list,name_list, "returnstring",outCparams),
    opts="DisableCparameters")
```

    Output C function ID_TOV_ADM_quantities() to file TOVID_Ccodes/ID_TOV_ADM_quantities.h

As all input quantities are functions of $r$, we will simply read the solution from file and interpolate it to the values of $r$ needed by the initial data.

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

$$
{\rm Jac\_dUSph\_dDrfmUD[mu][nu]} = \frac{\partial x^\mu_{\rm Sph}}{\partial x^\nu_{\rm rfm}},
$$

via exact differentiation (courtesy SymPy), and the inverse Jacobian
$$
{\rm Jac\_dUrfm\_dDSphUD[mu][nu]} = \frac{\partial x^\mu_{\rm rfm}}{\partial x^\nu_{\rm Sph}},
$$

using NRPy+'s `generic_matrix_inverter3x3()` function. In terms of these, the transformation of BSSN tensors from Spherical to `"reference_metric::CoordSystem"` coordinates may be written:

$$
T^{\mu\nu}_{\rm rfm} =
\frac{\partial x^\mu_{\rm rfm}}{\partial x^\delta_{\rm Sph}}
\frac{\partial x^\nu_{\rm rfm}}{\partial x^\sigma_{\rm Sph}} T^{\delta\sigma}_{\rm Sph}
$$

```python
r_th_ph_or_Cart_xyz_oID_xx = []
CoordType_in = "Spherical"
if CoordType_in == "Spherical":
    r_th_ph_or_Cart_xyz_oID_xx = rfm.xxSph
elif CoordType_in == "Cartesian":
    r_th_ph_or_Cart_xyz_oID_xx = rfm.xxCart
else:
    print("Error: Can only convert ADM Cartesian or Spherical initial data to BSSN Curvilinear coords.")
    exit(1)

# Next apply Jacobian transformations to convert into the (xx0,xx1,xx2) basis

# rho and S are scalar, so no Jacobian transformations are necessary.

Jac4_dUSphorCart_dDrfmUD = ixp.zerorank2(DIM=4)
Jac4_dUSphorCart_dDrfmUD[0][0] = sp.sympify(1)
for i in range(DIM):
    for j in range(DIM):
        Jac4_dUSphorCart_dDrfmUD[i+1][j+1] = sp.diff(r_th_ph_or_Cart_xyz_oID_xx[i],rfm.xx[j])

Jac4_dUrfm_dDSphorCartUD, dummyDET = ixp.generic_matrix_inverter4x4(Jac4_dUSphorCart_dDrfmUD)

# Perform Jacobian operations on T^{mu nu} and gamma_{ij}
T4UU = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","T4UU","sym01",DIM=4)

IDT4UU = ixp.zerorank2(DIM=4)
for mu in range(4):
    for nu in range(4):
        for delta in range(4):
            for sigma in range(4):
                IDT4UU[mu][nu] += \
                     Jac4_dUrfm_dDSphorCartUD[mu][delta]*Jac4_dUrfm_dDSphorCartUD[nu][sigma]*T4SphUU[delta][sigma]

lhrh_list = []
for mu in range(4):
    for nu in range(mu,4):
        lhrh_list.append(lhrh(lhs=gri.gfaccess("auxevol_gfs","T4UU"+str(mu)+str(nu)),rhs=IDT4UU[mu][nu]))

desc = """This function takes as input either (x,y,z) or (r,th,ph) and outputs
all ADM quantities in the Cartesian or Spherical basis, respectively."""
name = "ID_TOV_TUPMUNU_xx0xx1xx2"
outCparams = "preindent=1,outCverbose=False,includebraces=False"
outCfunction(
    outfile=os.path.join(Ccodesdir, name + ".h"), desc=desc, name=name,
    params="""const paramstruct *restrict params,REAL *restrict xx[3],
              const ID_inputs other_inputs,REAL *restrict auxevol_gfs""",
    body=outputC([rfm.xxSph[0],rfm.xxSph[1],rfm.xxSph[2]],
                 ["const REAL rbar","const REAL theta","const REAL ph"],"returnstring",
                 "CSE_enable=False,includebraces=False")+"""
      REAL rho,rho_baryon,P,M,expnu,exp4phi;
      TOV_interpolate_1D(rbar,other_inputs.Rbar,other_inputs.Rbar_idx,other_inputs.interp_stencil_size,
                         other_inputs.numlines_in_file,
                         other_inputs.r_Schw_arr,other_inputs.rho_arr,other_inputs.rho_baryon_arr,other_inputs.P_arr,other_inputs.M_arr,
                         other_inputs.expnu_arr,other_inputs.exp4phi_arr,other_inputs.rbar_arr,
                         &rho,&rho_baryon,&P,&M,&expnu,&exp4phi);\n"""+
    fin.FD_outputC("returnstring",lhrh_list,params="outCverbose=False,includebraces=False").replace("IDX4","IDX4S"),
    loopopts="AllPoints,Read_xxs")
```

    Output C function ID_TOV_TUPMUNU_xx0xx1xx2() to file TOVID_Ccodes/ID_TOV_TUPMUNU_xx0xx1xx2.h

# Step 3: Convert ADM initial data to BSSN-in-curvilinear coordinates \[Back to [top](#toc)\]
$$\label{adm_id_spacetime}$$

This is an automated process, taken care of by [`BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear`](../edit/BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear.py), and documented [in this tutorial notebook](Tutorial-ADM_Initial_Data-Converting_Numerical_ADM_Spherical_or_Cartesian_to_BSSNCurvilinear.ipynb).

```python
import BSSN.ADM_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear as AtoBnum
AtoBnum.Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear("Spherical","ID_TOV_ADM_quantities",
                                                               Ccodesdir=Ccodesdir,loopopts="")
```

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

# Step 4: Validating that the TOV initial data satisfy the Hamiltonian constraint \[Back to [top](#toc)\]
$$\label{validate}$$

We will validate that the TOV initial data satisfy the Hamiltonian constraint, modulo numerical finite differencing error

## Step 4.a: Output the Hamiltonian constraint \[Back to [top](#toc)\]
$$\label{ham_const_output}$$

First output the Hamiltonian constraint [as documented in the corresponding NRPy+ tutorial notebook](Tutorial-BSSN_constraints.ipynb)

```python
# Enable rfm_precompute infrastructure, which results in
#   BSSN RHSs that are free of transcendental functions,
#   even in curvilinear coordinates, so long as
#   ConformalFactor is set to "W" (default).
cmd.mkdir(os.path.join(Ccodesdir,"rfm_files/"))
par.set_parval_from_str("reference_metric::enable_rfm_precompute","True")
par.set_parval_from_str("reference_metric::rfm_precompute_Ccode_outdir",os.path.join(Ccodesdir,"rfm_files/"))

import BSSN.Enforce_Detgammabar_Constraint as EGC
enforce_detg_constraint_symb_expressions = EGC.Enforce_Detgammabar_Constraint_symb_expressions()

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

# Now that we are finished with all the rfm hatted
#           quantities in generic precomputed functional
#           form, let's restore them to their closed-
#           form expressions.
par.set_parval_from_str("reference_metric::enable_rfm_precompute","False") # Reset to False to disable rfm_precompute.
rfm.ref_metric__hatted_quantities()

desc="Evaluate the Hamiltonian constraint"
name="Hamiltonian_constraint"
outCfunction(
    outfile  = os.path.join(Ccodesdir,name+".h"), desc=desc, name=name,
    params   = """rfm_struct *restrict rfmstruct,const paramstruct *restrict params,
                  REAL *restrict in_gfs, REAL *restrict auxevol_gfs, REAL *restrict aux_gfs""",
    body     = fin.FD_outputC("returnstring",lhrh(lhs=gri.gfaccess("aux_gfs", "H"), rhs=bssncon.H),
                              params="outCverbose=False").replace("IDX4","IDX4S"),
    loopopts = "InteriorPoints,Enable_rfm_precompute")
```

    Output C function Hamiltonian_constraint() to file TOVID_Ccodes/Hamiltonian_constraint.h

## Step 4.b: Set up boundary condition functions for chosen singular, curvilinear coordinate system \[Back to [top](#toc)\]
$$\label{bc_functs}$$

Next apply singular, curvilinear coordinate boundary conditions [as documented in the corresponding NRPy+ tutorial notebook](Tutorial-Start_to_Finish-Curvilinear_BCs.ipynb)

```python
import CurviBoundaryConditions.CurviBoundaryConditions as cbcs
cbcs.Set_up_CurviBoundaryConditions(os.path.join(Ccodesdir,"boundary_conditions/"),Cparamspath=os.path.join("../"))
```

    Wrote to file "TOVID_Ccodes/boundary_conditions/parity_conditions_symbolic_dot_products.h"
    Evolved parity: ( aDD00:4, aDD01:5, aDD02:6, aDD11:7, aDD12:8, aDD22:9,
        alpha:0, betU0:1, betU1:2, betU2:3, cf:0, hDD00:4, hDD01:5, hDD02:6,
        hDD11:7, hDD12:8, hDD22:9, lambdaU0:1, lambdaU1:2, lambdaU2:3, trK:0,
        vetU0:1, vetU1:2, vetU2:3 )
    Auxiliary parity: ( H:0 )
    AuxEvol parity: ( T4UU00:0, T4UU01:1, T4UU02:2, T4UU03:3, T4UU11:4,
        T4UU12:5, T4UU13:6, T4UU22:7, T4UU23:8, T4UU33:9 )
    Wrote to file "TOVID_Ccodes/boundary_conditions/EigenCoord_Cart_to_xx.h"

## Step 4.c: Enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint \[Back to [top](#toc)\]
$$\label{enforce3metric}$$

Then enforce conformal 3-metric $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint (Eq. 53 of [Ruchlin, Etienne, and Baumgarte (2018)](https://arxiv.org/abs/1712.07658)), as [documented in the corresponding NRPy+ tutorial notebook](Tutorial-BSSN-Enforcing_Determinant_gammabar_equals_gammahat_Constraint.ipynb)

Applying curvilinear boundary conditions should affect the initial data at the outer boundary, and will in general cause the $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint to be violated there. Thus after we apply these boundary conditions, we must always call the routine for enforcing the $\det{\bar{\gamma}_{ij}}=\det{\hat{\gamma}_{ij}}$ constraint:

```python
# Set up the C function for the det(gammahat) = det(gammabar)
EGC.output_Enforce_Detgammabar_Constraint_Ccode(Ccodesdir,
                                                exprs=enforce_detg_constraint_symb_expressions)
```

    Output C function enforce_detgammabar_constraint() to file TOVID_Ccodes/enforce_detgammabar_constraint.h

## Step 4.d: Output C codes needed for declaring and setting Cparameters; also set `free_parameters.h` \[Back to [top](#toc)\]
$$\label{cparams_rfm_and_domainsize}$$

Based on declared NRPy+ Cparameters, first we generate `declare_Cparameters_struct.h`, `set_Cparameters_default.h`, and `set_Cparameters[-SIMD].h`.

Then 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.d.i: Generate declare_Cparameters_struct.h, set_Cparameters_default.h, and set_Cparameters[-SIMD].h
par.generate_Cparameters_Ccodes(os.path.join(Ccodesdir))

# Step 3.d.ii: Set free_parameters.h
# Output to $Ccodesdir/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.d.iii: Generate set_Nxx_dxx_invdx_params__and__xx.h:
rfm.set_Nxx_dxx_invdx_params__and__xx_h(Ccodesdir)

# Step 3.d.iv: Generate xxCart.h, which contains xxCart() for
#               (the mapping from xx->Cartesian) for the chosen
#               CoordSystem:
rfm.xxCart_h("xxCart","./set_Cparameters.h",os.path.join(Ccodesdir,"xxCart.h"))

# Step 3.d.v: Generate declare_Cparameters_struct.h, set_Cparameters_default.h, and set_Cparameters[-SIMD].h
par.generate_Cparameters_Ccodes(os.path.join(Ccodesdir))
```

# Step 5: `TOV_Playground.c`: The Main C Code \[Back to [top](#toc)\]
$$\label{mainc}$$

```python
# Part P0: Define REAL, set the number of ghost cells NGHOSTS (from NRPy+'s FD_CENTDERIVS_ORDER)

with open(os.path.join(Ccodesdir,"TOV_Playground_REAL__NGHOSTS.h"), "w") as file:
    file.write("""
// Part P0.a: Set the number of ghost cells, from NRPy+'s FD_CENTDERIVS_ORDER
#define NGHOSTS """+str(int(FD_order/2)+1)+"""
// Part P0.b: Set the numerical precision (REAL) to double, ensuring all floating point
//            numbers are stored to at least ~16 significant digits
#define REAL """+REAL+"""
// Part P0.c: Set TOV stellar parameters
#define TOV_Mass  """+str(M_TOV)+"""
#define TOV_Riso  """+str(R_iso_TOV)+"\n")
```

```python
%%writefile $Ccodesdir/TOV_Playground.c

// Step P0: Define REAL and NGHOSTS. This header is generated by NRPy+.
#include "TOV_Playground_REAL__NGHOSTS.h"

#include "rfm_files/rfm_struct__declare.h"

#include "declare_Cparameters_struct.h"

// Step P1: Import needed header files
#include "stdio.h"
#include "stdlib.h"
#include "math.h"
#ifndef M_PI
#define M_PI 3.141592653589793238462643383279502884L
#endif
#ifndef M_SQRT1_2
#define M_SQRT1_2 0.707106781186547524400844362104849039L
#endif

// Step P2: Declare the IDX4S(gf,i,j,k) macro, which enables us to store 4-dimensions of
//           data in a 1D array. In this case, consecutive values of "i"
//           (all other indices held to a fixed value) are consecutive in memory, where
//           consecutive values of "j" (fixing all other indices) are separated by
//           Nxx_plus_2NGHOSTS0 elements in memory. Similarly, consecutive values of
//           "k" are separated by Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1 in memory, etc.
#define IDX4S(g,i,j,k) \
( (i) + Nxx_plus_2NGHOSTS0 * ( (j) + Nxx_plus_2NGHOSTS1 * ( (k) + Nxx_plus_2NGHOSTS2 * (g) ) ) )
#define IDX4ptS(g,idx) ( (idx) + (Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2) * (g) )
#define IDX3S(i,j,k) ( (i) + Nxx_plus_2NGHOSTS0 * ( (j) + Nxx_plus_2NGHOSTS1 * ( (k) ) ) )
#define LOOP_REGION(i0min,i0max, i1min,i1max, i2min,i2max) \
  for(int i2=i2min;i2<i2max;i2++) for(int i1=i1min;i1<i1max;i1++) for(int i0=i0min;i0<i0max;i0++)
#define LOOP_ALL_GFS_GPS(ii) _Pragma("omp parallel for") \
  for(int (ii)=0;(ii)<Nxx_plus_2NGHOSTS_tot*NUM_EVOL_GFS;(ii)++)

// Step P3: Set UUGF and VVGF macros, as well as xxCart()
#include "boundary_conditions/gridfunction_defines.h"

// Step P4: Set xxCart(const paramstruct *restrict params,
//                     REAL *restrict xx[3],
//                     const int i0,const int i1,const int i2,
//                     REAL xCart[3]),
//           which maps xx->Cartesian via
//    {xx[0][i0],xx[1][i1],xx[2][i2]}->{xCart[0],xCart[1],xCart[2]}
#include "xxCart.h"

// Step P5: Defines set_Nxx_dxx_invdx_params__and__xx(const int EigenCoord, const int Nxx[3],
//                                       paramstruct *restrict params, REAL *restrict xx[3]),
//          which sets params Nxx,Nxx_plus_2NGHOSTS,dxx,invdx, and xx[] for
//          the chosen Eigen-CoordSystem if EigenCoord==1, or
//          CoordSystem if EigenCoord==0.
#include "set_Nxx_dxx_invdx_params__and__xx.h"

// Step P6: Include basic functions needed to impose curvilinear
//          parity and boundary conditions.
#include "boundary_conditions/CurviBC_include_Cfunctions.h"

// Step P8: Include function for enforcing detgammabar constraint.
#include "enforce_detgammabar_constraint.h"

// Step P4: Declare initial data input struct:
//          stores data from initial data solver,
//          so they can be put on the numerical grid.
typedef struct __ID_inputs {
    REAL Rbar;
    int Rbar_idx;
    int interp_stencil_size;
    int numlines_in_file;
    REAL *r_Schw_arr,*rho_arr,*rho_baryon_arr,*P_arr,*M_arr,*expnu_arr,*exp4phi_arr,*rbar_arr;
} ID_inputs;

// Part P11: Declare all functions for setting up TOV initial data.
/* Routines to interpolate the TOV solution and convert to ADM & T^{munu}: */
#include "../TOV/tov_interp.h"
#include "ID_TOV_ADM_quantities.h"
#include "ID_TOV_TUPMUNU_xx0xx1xx2.h"

/* Next perform the basis conversion and compute all needed BSSN quantities */
#include "ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h"
#include "ID_BSSN__ALL_BUT_LAMBDAs.h"
#include "ID_BSSN_lambdas.h"

// Step P10: Declare function necessary for setting up the initial data.
// Step P10.a: Define BSSN_ID() for BrillLindquist initial data

// Step P10.b: Set the generic driver function for setting up BSSN initial data
void initial_data(const paramstruct *restrict params,const bc_struct *restrict bcstruct,
                  const rfm_struct *restrict rfmstruct,
                  REAL *restrict xx[3], REAL *restrict auxevol_gfs, REAL *restrict in_gfs) {
#include "set_Cparameters.h"
        // Step 1: Set up TOV initial data
    // Step 1.a: Read TOV initial data from data file
    // Open the data file:
    char filename[100];
    sprintf(filename,"./outputTOVpolytrope.txt");
    FILE *in1Dpolytrope = fopen(filename, "r");
    if (in1Dpolytrope == NULL) {
        fprintf(stderr,"ERROR: could not open file %s\n",filename);
        exit(1);
    }
    // Count the number of lines in the data file:
    int numlines_in_file = count_num_lines_in_file(in1Dpolytrope);
    // Allocate space for all data arrays:
    REAL *r_Schw_arr     = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *rho_arr        = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *rho_baryon_arr = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *P_arr          = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *M_arr          = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *expnu_arr      = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *exp4phi_arr    = (REAL *)malloc(sizeof(REAL)*numlines_in_file);
    REAL *rbar_arr       = (REAL *)malloc(sizeof(REAL)*numlines_in_file);

    // Read from the data file, filling in arrays
    // read_datafile__set_arrays() may be found in TOV/tov_interp.h
    if(read_datafile__set_arrays(in1Dpolytrope, r_Schw_arr,rho_arr,rho_baryon_arr,P_arr,M_arr,expnu_arr,exp4phi_arr,rbar_arr) == 1) {
        fprintf(stderr,"ERROR WHEN READING FILE %s!\n",filename);
        exit(1);
    }
    fclose(in1Dpolytrope);
    REAL Rbar = -100;
    int Rbar_idx = -100;
    for(int i=1;i<numlines_in_file;i++) {
        if(rho_arr[i-1]>0 && rho_arr[i]==0) { Rbar = rbar_arr[i-1]; Rbar_idx = i-1; }
    }
    if(Rbar<0) {
        fprintf(stderr,"Error: could not find rbar=Rbar from data file.\n");
        exit(1);
    }

    ID_inputs TOV_in;
    TOV_in.Rbar = Rbar;
    TOV_in.Rbar_idx = Rbar_idx;

    const int interp_stencil_size = 12;
    TOV_in.interp_stencil_size = interp_stencil_size;
    TOV_in.numlines_in_file = numlines_in_file;

    TOV_in.r_Schw_arr     = r_Schw_arr;
    TOV_in.rho_arr        = rho_arr;
    TOV_in.rho_baryon_arr = rho_baryon_arr;
    TOV_in.P_arr          = P_arr;
    TOV_in.M_arr          = M_arr;
    TOV_in.expnu_arr      = expnu_arr;
    TOV_in.exp4phi_arr    = exp4phi_arr;
    TOV_in.rbar_arr       = rbar_arr;
    /* END TOV INPUT ROUTINE */

    // Step 1.b: Interpolate data from data file to set BSSN gridfunctions
    ID_BSSN__ALL_BUT_LAMBDAs(params,xx,TOV_in, in_gfs);
    apply_bcs_curvilinear(params, bcstruct, NUM_EVOL_GFS, evol_gf_parity, in_gfs);
    enforce_detgammabar_constraint(rfmstruct, params,                   in_gfs);
    ID_BSSN_lambdas(params, xx, in_gfs);
    apply_bcs_curvilinear(params, bcstruct, NUM_EVOL_GFS, evol_gf_parity, in_gfs);
    enforce_detgammabar_constraint(rfmstruct, params,                   in_gfs);

    ID_TOV_TUPMUNU_xx0xx1xx2(params,xx,TOV_in,auxevol_gfs);

    free(rbar_arr);
    free(rho_arr);
    free(rho_baryon_arr);
    free(P_arr);
    free(M_arr);
    free(expnu_arr);
}

// Step P11: Declare function for evaluating Hamiltonian constraint (diagnostic)
#include "Hamiltonian_constraint.h"

// main() function:
// Step 0: Read command-line input, set up grid structure, allocate memory for gridfunctions, set up coordinates
// Step 1: Set up initial data to an exact solution
// Step 2: Start the timer, for keeping track of how fast the simulation is progressing.
// Step 3: Integrate the initial data forward in time using the chosen RK-like Method of
//         Lines timestepping algorithm, and output periodic simulation diagnostics
// Step 3.a: Output 2D data file periodically, for visualization
// Step 3.b: Step forward one timestep (t -> t+dt) in time using
//           chosen RK-like MoL timestepping algorithm
// Step 3.c: If t=t_final, output conformal factor & Hamiltonian
//           constraint violation to 2D data file
// Step 3.d: Progress indicator printing to stderr
// Step 4: Free all allocated memory
int main(int argc, const char *argv[]) {
    paramstruct params;
#include "set_Cparameters_default.h"

    // Step 0a: Read command-line input, error out if nonconformant
    if((argc != 4) || atoi(argv[1]) < NGHOSTS || atoi(argv[2]) < NGHOSTS || atoi(argv[3]) < 2 /* FIXME; allow for axisymmetric sims */) {
        fprintf(stderr,"Error: Expected three command-line arguments: ./BrillLindquist_Playground Nx0 Nx1 Nx2,\n");
        fprintf(stderr,"where Nx[0,1,2] is the number of grid points in the 0, 1, and 2 directions.\n");
        fprintf(stderr,"Nx[] MUST BE larger than NGHOSTS (= %d)\n",NGHOSTS);
        exit(1);
    }
    // Step 0b: 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) {
        fprintf(stderr,"Error: Cannot guarantee a proper cell-centered grid if number of grid cells not set to even number.\n");
        fprintf(stderr,"       For example, in case of angular directions, proper symmetry zones will not exist.\n");
        exit(1);
    }

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

   // Step 0d: Uniform coordinate grids are stored to *xx[3]
    REAL *xx[3];
    // Step 0d.i: Set bcstruct
    bc_struct bcstruct;
    {
        int EigenCoord = 1;
        // Step 0d.ii: Call set_Nxx_dxx_invdx_params__and__xx(), which sets
        //             params Nxx,Nxx_plus_2NGHOSTS,dxx,invdx, and xx[] for the
        //             chosen Eigen-CoordSystem.
        set_Nxx_dxx_invdx_params__and__xx(EigenCoord, Nxx, &params, xx);
        // Step 0d.iii: Set Nxx_plus_2NGHOSTS_tot
#include "set_Cparameters-nopointer.h"
        const int Nxx_plus_2NGHOSTS_tot = Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2;
        // Step 0e: Find ghostzone mappings; set up bcstruct
#include "boundary_conditions/driver_bcstruct.h"
        // Step 0e.i: Free allocated space for xx[][] array
        for(int i=0;i<3;i++) free(xx[i]);
    }

    // Step 0f: 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 = 0;
    set_Nxx_dxx_invdx_params__and__xx(EigenCoord, Nxx, &params, xx);

    // Step 0g: 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 0j: Error out if the number of auxiliary gridfunctions outnumber evolved gridfunctions.
    //              This is a limitation of the RK method. You are always welcome to declare & allocate
    //              additional gridfunctions by hand.
    if(NUM_AUX_GFS > NUM_EVOL_GFS) {
        fprintf(stderr,"Error: NUM_AUX_GFS > NUM_EVOL_GFS. Either reduce the number of auxiliary gridfunctions,\n");
        fprintf(stderr,"       or allocate (malloc) by hand storage for *diagnostic_output_gfs. \n");
        exit(1);
    }

    // Step 0k: Allocate memory for gridfunctions
#include "MoLtimestepping/RK_Allocate_Memory.h"
    REAL *restrict auxevol_gfs = (REAL *)malloc(sizeof(REAL) * NUM_AUXEVOL_GFS * Nxx_plus_2NGHOSTS_tot);

    // Step 0l: Set up precomputed reference metric arrays
    // Step 0l.i: Allocate space for precomputed reference metric arrays.
#include "rfm_files/rfm_struct__malloc.h"

    // Step 0l.ii: Define precomputed reference metric arrays.
    {
    #include "set_Cparameters-nopointer.h"
    #include "rfm_files/rfm_struct__define.h"
    }

    // Step 1: Set up initial data to an exact solution
    initial_data(&params,&bcstruct, &rfmstruct, xx, auxevol_gfs, y_n_gfs);

    // Step 1b: Apply boundary conditions, as initial data
    //          are sometimes ill-defined in ghost zones.
    //          E.g., spherical initial data might not be
    //          properly defined at points where r=-1.
    apply_bcs_curvilinear(&params, &bcstruct, NUM_EVOL_GFS,evol_gf_parity, y_n_gfs);
    enforce_detgammabar_constraint(&rfmstruct, &params, y_n_gfs);

    // Evaluate Hamiltonian constraint violation
    Hamiltonian_constraint(&rfmstruct, &params, y_n_gfs,auxevol_gfs, diagnostic_output_gfs);
    char filename[100];
    sprintf(filename,"out%d.txt",Nxx[0]);
    FILE *out2D = fopen(filename, "w");
    LOOP_REGION(NGHOSTS,Nxx_plus_2NGHOSTS0-NGHOSTS,
                NGHOSTS,Nxx_plus_2NGHOSTS1-NGHOSTS,
                NGHOSTS,Nxx_plus_2NGHOSTS2-NGHOSTS) {
        REAL xx0 = xx[0][i0];
        REAL xx1 = xx[1][i1];
        REAL xx2 = xx[2][i2];
        REAL xCart[3];
        xxCart(&params,xx,i0,i1,i2,xCart);
        int idx = IDX3S(i0,i1,i2);
        fprintf(out2D,"%e %e %e %e\n",xCart[1]/TOV_Mass,xCart[2]/TOV_Mass, y_n_gfs[IDX4ptS(CFGF,idx)],
                log10(fabs(diagnostic_output_gfs[IDX4ptS(HGF,idx)])));
    }
    fclose(out2D);

    // Step 4: Free all allocated memory
#include "rfm_files/rfm_struct__freemem.h"
#include "boundary_conditions/bcstruct_freemem.h"
#include "MoLtimestepping/RK_Free_Memory.h"
    free(auxevol_gfs);
    for(int i=0;i<3;i++) free(xx[i]);

    return 0;
}
```

    Writing TOVID_Ccodes//TOV_Playground.c

```python
import cmdline_helper as cmd
cmd.C_compile(os.path.join(Ccodesdir,"TOV_Playground.c"), "TOV_Playground")
cmd.delete_existing_files("out96.txt")
cmd.Execute("TOV_Playground", "96 96 2", "out96.txt")
```

    Compiling executable...
    (EXEC): Executing `gcc -Ofast -fopenmp -march=native -funroll-loops TOVID_Ccodes/TOV_Playground.c -o TOV_Playground -lm`...
    (BENCH): Finished executing in 1.8139622211456299 seconds.
    Finished compilation.
    (EXEC): Executing `taskset -c 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 ./TOV_Playground 96 96 2`...
    (BENCH): Finished executing in 0.21292877197265625 seconds.

# Step 6: Plotting the single-neutron-star initial data \[Back to [top](#toc)\]
$$\label{plot}$$

Here we plot the conformal factor of these initial data on a 2D grid, such that darker colors imply stronger gravitational fields. Hence, we see the single neutron star centered at the origin: $x/M=y/M=z/M=0$, where $M$ is an arbitrary mass scale (conventionally the [ADM mass](https://en.wikipedia.org/w/index.php?title=ADM_formalism&oldid=846335453) is chosen), and our formulation of Einstein's equations adopt $G=c=1$ [geometrized units](https://en.wikipedia.org/w/index.php?title=Geometrized_unit_system&oldid=861682626).

```python
import numpy as np
from scipy.interpolate import griddata
from pylab import savefig
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from IPython.display import Image

x96,y96,valuesCF96,valuesHam96 = np.loadtxt('out96.txt').T #Transposed for easier unpacking

bounds = 7.5
pl_xmin = -bounds
pl_xmax = +bounds
pl_ymin = -bounds
pl_ymax = +bounds

grid_x, grid_y = np.mgrid[pl_xmin:pl_xmax:100j, pl_ymin:pl_ymax:100j]
points96 = np.zeros((len(x96), 2))
for i in range(len(x96)):
    points96[i][0] = x96[i]
    points96[i][1] = y96[i]

grid96 = griddata(points96, valuesCF96, (grid_x, grid_y), method='nearest')
grid96cub = griddata(points96, valuesCF96, (grid_x, grid_y), method='cubic')

plt.clf()
plt.title("Neutron Star: log10( max(1e-6,Energy Density) )")
plt.xlabel("x/M")
plt.ylabel("y/M")

# fig, ax = plt.subplots()
# ax.plot(grid96cub.T, extent=(pl_xmin,pl_xmax, pl_ymin,pl_ymax))
# plt.close(fig)
fig96cf = plt.imshow(grid96.T, extent=(pl_xmin,pl_xmax, pl_ymin,pl_ymax))
cb = plt.colorbar(fig96cf)
savefig("BHB.png")
from IPython.display import Image
Image("BHB.png")
# #           interpolation='nearest', cmap=cm.gist_rainbow)
```

# Step 7: Validation: Convergence of numerical errors (Hamiltonian constraint violation) to zero \[Back to [top](#toc)\]
$$\label{convergence}$$

The equations behind these initial data solve Einstein's equations exactly, at a single instant in time. One reflection of this solution is that the Hamiltonian constraint violation should be exactly zero in the initial data.

However, when evaluated on numerical grids, the Hamiltonian constraint violation will *not* generally evaluate to zero due to the associated numerical derivatives not being exact. However, these numerical derivatives (finite difference derivatives in this case) should *converge* to the exact derivatives as the density of numerical sampling points approaches infinity.

In this case, all of our finite difference derivatives agree with the exact solution, with an error term that drops with the uniform gridspacing to the fourth power: $\left(\Delta x^i\right)^4$.

Here, as in the [Start-to-Finish Scalar Wave (Cartesian grids) NRPy+ tutorial](Tutorial-Start_to_Finish-ScalarWave.ipynb) and the [Start-to-Finish Scalar Wave (curvilinear grids) NRPy+ tutorial](Tutorial-Start_to_Finish-ScalarWaveCurvilinear.ipynb) we confirm this convergence.

First, let's take a look at what the numerical error looks like on the x-y plane at a given numerical resolution, plotting $\log_{10}|H|$, where $H$ is the Hamiltonian constraint violation:

```python
grid96 = griddata(points96, valuesHam96, (grid_x, grid_y), method='nearest')
grid96cub = griddata(points96, valuesHam96, (grid_x, grid_y), method='cubic')

# fig, ax = plt.subplots()

plt.clf()
plt.title("96^3 Numerical Err.: log_{10}|Ham|")
plt.xlabel("x/M")
plt.ylabel("y/M")

fig96cub = plt.imshow(grid96cub.T, extent=(pl_xmin,pl_xmax, pl_ymin,pl_ymax))
cb = plt.colorbar(fig96cub)
```

Next, we set up the same initial data but on a lower-resolution, $48\times 8\times 2$ grid (axisymmetric in the $\phi$ direction). Since the constraint violation (numerical error associated with the fourth-order-accurate, finite-difference derivatives) should converge to zero with the uniform gridspacing to the fourth power: $\left(\Delta x^i\right)^4$, we expect the constraint violation will increase (relative to the $96\times 16\times 2$ grid) by a factor of $\left(96/48\right)^4$. Here we demonstrate that indeed this order of convergence is observed as expected, *except* at the star's surface where the stress-energy tensor $T^{\mu\nu}$ sharply drops to zero.

```python
# Now rerun TOV_Playground with twice lower resolution.
cmd.delete_existing_files("out48.txt")
cmd.Execute("TOV_Playground", "48 48 2", "out48.txt")

x48,y48,valuesCF48,valuesHam48 = np.loadtxt('out48.txt').T #Transposed for easier unpacking
points48 = np.zeros((len(x48), 2))
for i in range(len(x48)):
    points48[i][0] = x48[i]
    points48[i][1] = y48[i]

grid48 = griddata(points48, valuesHam48, (grid_x, grid_y), method='cubic')

griddiff_48_minus_96 = np.zeros((100,100))
griddiff_48_minus_96_1darray = np.zeros(100*100)
gridx_1darray_yeq0 = np.zeros(100)
grid48_1darray_yeq0 = np.zeros(100)
grid96_1darray_yeq0 = np.zeros(100)
count = 0
outarray = []
for i in range(100):
    for j in range(100):
        griddiff_48_minus_96[i][j] = grid48[i][j] - grid96[i][j]
        griddiff_48_minus_96_1darray[count] = griddiff_48_minus_96[i][j]
        if j==49:
            gridx_1darray_yeq0[i] = grid_x[i][j]
            grid48_1darray_yeq0[i] = grid48[i][j] + np.log10((48./96.)**4)
            grid96_1darray_yeq0[i] = grid96[i][j]
        count = count + 1

plt.clf()
fig, ax = plt.subplots()
plt.title("Plot Demonstrating 4th-order Convergence")
plt.xlabel("x/M")
plt.ylabel("log10(Relative error)")

ax.plot(gridx_1darray_yeq0, grid96_1darray_yeq0, 'k-', label='Nr=96')
ax.plot(gridx_1darray_yeq0, grid48_1darray_yeq0, 'k--', label='Nr=48, mult by (48/96)^4')
ax.set_ylim([-12.5,1.5])

legend = ax.legend(loc='lower right', shadow=True, fontsize='x-large')
legend.get_frame().set_facecolor('C1')
plt.show()
```

# 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-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.pdf](Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.)

```python
import cmdline_helper as cmd    # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-Start_to_Finish-BSSNCurvilinear-Setting_up_TOV_initial_data")
```

    Created Tutorial-Start_to_Finish-BSSNCurvilinear-
        Setting_up_TOV_initial_data.tex, and compiled LaTeX file to PDF file
        Tutorial-Start_to_Finish-BSSNCurvilinear-
        Setting_up_TOV_initial_data.pdf