#!/usr/bin/env python # coding: utf-8 # # # # # Equations of General Relativistic Hydrodynamics (GRHD) # # ## Authors: Zach Etienne & Patrick Nelson # # ## This notebook documents and constructs a number of quantities useful for building symbolic (SymPy) expressions for the equations of general relativistic hydrodynamics (GRHD), using the same (Valencia) formalism as `IllinoisGRMHD` # # **Notebook Status:** Self-Validated # # **Validation Notes:** This tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented [below](#code_validation). **Additional validation tests may have been performed, but are as yet, undocumented. (TODO)** # # ## Introduction # # We write the equations of general relativistic hydrodynamics in conservative form as follows (adapted from Eqs. 41-44 of [Duez et al](https://arxiv.org/pdf/astro-ph/0503420.pdf): # # \begin{eqnarray} # \ \partial_t \rho_* &+& \partial_j \left(\rho_* v^j\right) = 0 \\ # \partial_t \tilde{\tau} &+& \partial_j \left(\alpha^2 \sqrt{\gamma} T^{0j} - \rho_* v^j \right) = s \\ # \partial_t \tilde{S}_i &+& \partial_j \left(\alpha \sqrt{\gamma} T^j{}_i \right) = \frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu} g_{\mu\nu,i}, # \end{eqnarray} # where we assume $T^{\mu\nu}$ is the stress-energy tensor of a perfect fluid: # $$ # T^{\mu\nu} = \rho_0 h u^{\mu} u^{\nu} + P g^{\mu\nu}, # $$ # the $s$ source term is given in terms of ADM quantities via # # $$ # s = \alpha \sqrt{\gamma}\left[\left(T^{00}\beta^i\beta^j + 2 T^{0i}\beta^j + T^{ij} \right)K_{ij} # - \left(T^{00}\beta^i + T^{0i} \right)\partial_i\alpha \right], # $$ # # and # \begin{align} # v^j &= \frac{u^j}{u^0} \\ # \rho_* &= \alpha\sqrt{\gamma} \rho_0 u^0 \\ # h &= 1 + \epsilon + \frac{P}{\rho_0}. # \end{align} # # Also we will write the 4-metric in terms of the ADM 3-metric, lapse, and shift using standard equations. # # Thus the full set of input variables include: # * Spacetime quantities: # * ADM quantities $\alpha$, $\beta^i$, $\gamma_{ij}$, $K_{ij}$ # * Hydrodynamical quantities: # * Rest-mass density $\rho_0$ # * Pressure $P$ # * Internal energy $\epsilon$ # * 4-velocity $u^\mu$ # # For completeness, the rest of the conservative variables are given by # \begin{align} # \tilde{\tau} &= \alpha^2\sqrt{\gamma} T^{00} - \rho_* \\ # \tilde{S}_i &= \alpha \sqrt{\gamma} T^0{}_i # \end{align} # # ### A Note on Notation # # As is standard in NRPy+, # # * Greek indices refer to four-dimensional quantities where the zeroth component indicates temporal (time) component. # * Latin indices refer to three-dimensional quantities. This is somewhat counterintuitive since Python always indexes its lists starting from 0. As a result, the zeroth component of three-dimensional quantities will necessarily indicate the first *spatial* direction. # # For instance, in calculating the first term of $b^2 u^\mu u^\nu$, we use Greek indices: # # ```python # T4EMUU = ixp.zerorank2(DIM=4) # for mu in range(4): # for nu in range(4): # # Term 1: b^2 u^{\mu} u^{\nu} # T4EMUU[mu][nu] = smallb2*u4U[mu]*u4U[nu] # ``` # # When we calculate $\beta_i = \gamma_{ij} \beta^j$, we use Latin indices: # ```python # betaD = ixp.zerorank1(DIM=3) # for i in range(3): # for j in range(3): # betaD[i] += gammaDD[i][j] * betaU[j] # ``` # # As a corollary, any expressions involving mixed Greek and Latin indices will need to offset one set of indices by one: A Latin index in a four-vector will be incremented and a Greek index in a three-vector will be decremented (however, the latter case does not occur in this tutorial notebook). This can be seen when we handle $\frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}$: # ```python # # \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} / 2 # for i in range(3): # for mu in range(4): # for nu in range(4): # S_tilde_rhsD[i] += alpsqrtgam * T4EMUU[mu][nu] * g4DD_zerotimederiv_dD[mu][nu][i+1] / 2 # ``` # # # # Table of Contents # $$\label{toc}$$ # # Each family of quantities is constructed within a given function (**boldfaced** below). This notebook is organized as follows # # # 1. [Step 1](#importmodules): Import needed NRPy+ & Python modules # 1. [Step 2](#stressenergy): Define the stress-energy tensor $T^{\mu\nu}$ and $T^\mu{}_\nu$: # * **compute_enthalpy()**, **compute_T4UU()**, **compute_T4UD()**: # 1. [Step 3](#primtoconserv): Writing the conservative variables in terms of the primitive variables: # * **compute_sqrtgammaDET()**, **compute_rho_star()**, **compute_tau_tilde()**, **compute_S_tildeD()** # 1. [Step 4](#grhdfluxes): Define the fluxes for the GRHD equations # 1. [Step 4.a](#rhostarfluxterm): Define $\rho_*$ flux term for GRHD equations: # * **compute_vU_from_u4U__no_speed_limit()**, **compute_rho_star_fluxU()**: # 1. [Step 4.b](#taustildesourceterms) Define $\tilde{\tau}$ and $\tilde{S}_i$ flux terms for GRHD equations: # * **compute_tau_tilde_fluxU()**, **compute_S_tilde_fluxUD()** # 1. [Step 5](#grhdsourceterms): Define source terms on RHSs of GRHD equations # 1. [Step 5.a](#ssourceterm): Define $s$ source term on RHS of $\tilde{\tau}$ equation: # * **compute_s_source_term()** # 1. [Step 5.b](#stildeisourceterm): Define source term on RHS of $\tilde{S}_i$ equation # 1. [Step 5.b.i](#fourmetricderivs): Compute $g_{\mu\nu,i}$ in terms of ADM quantities and their derivatives: # * **compute_g4DD_zerotimederiv_dD()** # 1. [Step 5.b.ii](#stildeisource): Compute source term of the $\tilde{S}_i$ equation: $\frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu} g_{\mu\nu,i}$: # * **compute_S_tilde_source_termD()** # 1. [Step 6](#convertvtou): Conversion of $v^i$ to $u^\mu$ (Courtesy Patrick Nelson): # * **u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit()**, **u4U_in_terms_of_vU__rescale_vU_by_applying_speed_limit()** # 1. [Step 7](#declarevarsconstructgrhdeqs): Declare ADM and hydrodynamical input variables, and construct GRHD equations # 1. [Step 8](#code_validation): Code Validation against `GRHD.equations` NRPy+ module # 1. [Step 9](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file # # # # Step 1: Import needed NRPy+ & Python modules \[Back to [top](#toc)\] # $$\label{importmodules}$$ # In[1]: # Step 1: Import needed core NRPy+ modules from outputC import nrpyAbs # 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 indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support # # # # Step 2: Define the stress-energy tensor $T^{\mu\nu}$ and $T^\mu{}_\nu$ \[Back to [top](#toc)\] # $$\label{stressenergy}$$ # # Recall from above that # # $$ # T^{\mu\nu} = \rho_0 h u^{\mu} u^{\nu} + P g^{\mu\nu}, # $$ # where $h = 1 + \epsilon + \frac{P}{\rho_0}$. Also # # $$ # T^\mu{}_{\nu} = T^{\mu\delta} g_{\delta \nu} # $$ # In[2]: # Step 2.a: First define h, the enthalpy: def compute_enthalpy(rho_b,P,epsilon): global h h = 1 + epsilon + P/rho_b # Step 2.b: Define T^{mu nu} (a 4-dimensional tensor) def compute_T4UU(gammaDD,betaU,alpha, rho_b,P,epsilon,u4U): global T4UU compute_enthalpy(rho_b,P,epsilon) # Then define g^{mu nu} in terms of the ADM quantities: import BSSN.ADMBSSN_tofrom_4metric as AB4m AB4m.g4UU_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha) # Finally compute T^{mu nu} T4UU = ixp.zerorank2(DIM=4) for mu in range(4): for nu in range(4): T4UU[mu][nu] = rho_b * h * u4U[mu]*u4U[nu] + P*AB4m.g4UU[mu][nu] # Step 2.c: Define T^{mu}_{nu} (a 4-dimensional tensor) def compute_T4UD(gammaDD,betaU,alpha, T4UU): global T4UD # Next compute T^mu_nu = T^{mu delta} g_{delta nu}, needed for S_tilde flux. # First we'll need g_{alpha nu} in terms of ADM quantities: import BSSN.ADMBSSN_tofrom_4metric as AB4m AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha) T4UD = ixp.zerorank2(DIM=4) for mu in range(4): for nu in range(4): for delta in range(4): T4UD[mu][nu] += T4UU[mu][delta]*AB4m.g4DD[delta][nu] # # # # Step 3: Writing the conservative variables in terms of the primitive variables \[Back to [top](#toc)\] # $$\label{primtoconserv}$$ # # Recall from above that the conservative variables may be written as # \begin{align} # \rho_* &= \alpha\sqrt{\gamma} \rho_0 u^0 \\ # \tilde{\tau} &= \alpha^2\sqrt{\gamma} T^{00} - \rho_* \\ # \tilde{S}_i &= \alpha \sqrt{\gamma} T^0{}_i # \end{align} # # $T^{\mu\nu}$ and $T^\mu{}_\nu$ have already been defined $-$ all in terms of primitive variables. Thus we'll just need $\sqrt{\gamma}=$`gammaDET`, and all conservatives can then be written in terms of other defined quantities, which themselves are written in terms of primitive variables and the ADM metric. # In[3]: # Step 3: Writing the conservative variables in terms of the primitive variables def compute_sqrtgammaDET(gammaDD): global sqrtgammaDET gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD) sqrtgammaDET = sp.sqrt(gammaDET) def compute_rho_star(alpha, sqrtgammaDET, rho_b,u4U): global rho_star # Compute rho_star: rho_star = alpha*sqrtgammaDET*rho_b*u4U[0] def compute_tau_tilde(alpha, sqrtgammaDET, T4UU,rho_star): global tau_tilde tau_tilde = alpha**2*sqrtgammaDET*T4UU[0][0] - rho_star def compute_S_tildeD(alpha, sqrtgammaDET, T4UD): global S_tildeD S_tildeD = ixp.zerorank1(DIM=3) for i in range(3): S_tildeD[i] = alpha*sqrtgammaDET*T4UD[0][i+1] # # # # Step 4: Define the fluxes for the GRHD equations \[Back to [top](#toc)\] # $$\label{grhdfluxes}$$ # # # # ## Step 4.a: Define $\rho_*$ flux term for GRHD equations \[Back to [top](#toc)\] # $$\label{rhostarfluxterm}$$ # # Recall from above that # \begin{array} # \ \partial_t \rho_* &+ \partial_j \left(\rho_* v^j\right) = 0. # \end{array} # # Here we will define the $\rho_* v^j$ that constitutes the flux of $\rho_*$, first defining $v^j=u^j/u^0$: # In[4]: # Step 4: Define the fluxes for the GRHD equations # Step 4.a: vU from u4U may be needed for computing rho_star_flux from u4U def compute_vU_from_u4U__no_speed_limit(u4U): global vU # Now compute v^i = u^i/u^0: vU = ixp.zerorank1(DIM=3) for j in range(3): vU[j] = u4U[j+1]/u4U[0] # Step 4.b: rho_star flux def compute_rho_star_fluxU(vU, rho_star): global rho_star_fluxU rho_star_fluxU = ixp.zerorank1(DIM=3) for j in range(3): rho_star_fluxU[j] = rho_star*vU[j] # # # ## Step 4.b: Define $\tilde{\tau}$ and $\tilde{S}_i$ flux terms for GRHD equations \[Back to [top](#toc)\] # $$\label{taustildesourceterms}$$ # # Recall from above that # \begin{array} # \ \partial_t \tilde{\tau} &+ \partial_j \underbrace{\left(\alpha^2 \sqrt{\gamma} T^{0j} - \rho_* v^j \right)} &= s \\ # \partial_t \tilde{S}_i &+ \partial_j \underbrace{\left(\alpha \sqrt{\gamma} T^j{}_i \right)} &= \frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu} g_{\mu\nu,i}. # \end{array} # # Here we will define all terms that go inside the $\partial_j$'s on the left-hand side of the above equations (i.e., the underbraced expressions): # In[5]: # Step 4.c: tau_tilde flux def compute_tau_tilde_fluxU(alpha, sqrtgammaDET, vU,T4UU, rho_star): global tau_tilde_fluxU tau_tilde_fluxU = ixp.zerorank1(DIM=3) for j in range(3): tau_tilde_fluxU[j] = alpha**2*sqrtgammaDET*T4UU[0][j+1] - rho_star*vU[j] # Step 4.d: S_tilde flux def compute_S_tilde_fluxUD(alpha, sqrtgammaDET, T4UD): global S_tilde_fluxUD S_tilde_fluxUD = ixp.zerorank2(DIM=3) for j in range(3): for i in range(3): S_tilde_fluxUD[j][i] = alpha*sqrtgammaDET*T4UD[j+1][i+1] # # # # Step 5: Define source terms on RHSs of GRHD equations \[Back to [top](#toc)\] # $$\label{grhdsourceterms}$$ # # # # ## Step 5.a: Define $s$ source term on RHS of $\tilde{\tau}$ equation \[Back to [top](#toc)\] # $$\label{ssourceterm}$$ # # # Recall again from above the $s$ source term on the right-hand side of the $\tilde{\tau}$ evolution equation is given in terms of ADM quantities and the stress-energy tensor via # $$ # s = \underbrace{\alpha \sqrt{\gamma}}_{\text{Term 3}}\left[\underbrace{\left(T^{00}\beta^i\beta^j + 2 T^{0i}\beta^j + T^{ij} \right)K_{ij}}_{\text{Term 1}} # \underbrace{- \left(T^{00}\beta^i + T^{0i} \right)\partial_i\alpha}_{\text{Term 2}} \right], # $$ # In[6]: def compute_s_source_term(KDD,betaU,alpha, sqrtgammaDET,alpha_dD, T4UU): global s_source_term s_source_term = sp.sympify(0) # Term 1: for i in range(3): for j in range(3): s_source_term += (T4UU[0][0]*betaU[i]*betaU[j] + 2*T4UU[0][i+1]*betaU[j] + T4UU[i+1][j+1])*KDD[i][j] # Term 2: for i in range(3): s_source_term += -(T4UU[0][0]*betaU[i] + T4UU[0][i+1])*alpha_dD[i] # Term 3: s_source_term *= alpha*sqrtgammaDET # # # ## Step 5.b: Define source term on RHS of $\tilde{S}_i$ equation \[Back to [top](#toc)\] # $$\label{stildeisourceterm}$$ # # Recall from above # $$ # \partial_t \tilde{S}_i + \partial_j \left(\alpha \sqrt{\gamma} T^j{}_i \right) = \frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu} g_{\mu\nu,i}. # $$ # Our goal here will be to compute # $$ # \frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu} g_{\mu\nu,i}. # $$ # # # # ### Step 5.b.i: Compute $g_{\mu\nu,i}$ in terms of ADM quantities and their derivatives \[Back to [top](#toc)\] # $$\label{fourmetricderivs}$$ # # # To compute $g_{\mu\nu,i}$ we need to evaluate the first derivative of $g_{\mu\nu}$ in terms of ADM variables. # # We are given $\gamma_{ij}$, $\alpha$, and $\beta^i$, and the 4-metric is given in terms of these quantities via # $$ # g_{\mu\nu} = \begin{pmatrix} # -\alpha^2 + \beta^k \beta_k & \beta_i \\ # \beta_j & \gamma_{ij} # \end{pmatrix}. # $$ # # Thus # $$ # g_{\mu\nu,k} = \begin{pmatrix} # -2 \alpha\alpha_{,i} + \beta^j_{,k} \beta_j + \beta^j \beta_{j,k} & \beta_{i,k} \\ # \beta_{j,k} & \gamma_{ij,k} # \end{pmatrix}, # $$ # where $\beta_{i} = \gamma_{ij} \beta^j$, so # $$ # \beta_{i,k} = \gamma_{ij,k} \beta^j + \gamma_{ij} \beta^j_{,k} # $$ # In[7]: def compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDD_dD,betaU_dD,alpha_dD): global g4DD_zerotimederiv_dD # Eq. 2.121 in B&S betaD = ixp.zerorank1(DIM=3) for i in range(3): for j in range(3): betaD[i] += gammaDD[i][j]*betaU[j] betaDdD = ixp.zerorank2(DIM=3) for i in range(3): for j in range(3): for k in range(3): # Recall that betaD[i] = gammaDD[i][j]*betaU[j] (Eq. 2.121 in B&S) betaDdD[i][k] += gammaDD_dD[i][j][k]*betaU[j] + gammaDD[i][j]*betaU_dD[j][k] # Eq. 2.122 in B&S g4DD_zerotimederiv_dD = ixp.zerorank3(DIM=4) for k in range(3): # Recall that g4DD[0][0] = -alpha^2 + betaU[j]*betaD[j] g4DD_zerotimederiv_dD[0][0][k+1] += -2*alpha*alpha_dD[k] for j in range(3): g4DD_zerotimederiv_dD[0][0][k+1] += betaU_dD[j][k]*betaD[j] + betaU[j]*betaDdD[j][k] for i in range(3): for k in range(3): # Recall that g4DD[i][0] = g4DD[0][i] = betaD[i] g4DD_zerotimederiv_dD[i+1][0][k+1] = g4DD_zerotimederiv_dD[0][i+1][k+1] = betaDdD[i][k] for i in range(3): for j in range(3): for k in range(3): # Recall that g4DD[i][j] = gammaDD[i][j] g4DD_zerotimederiv_dD[i+1][j+1][k+1] = gammaDD_dD[i][j][k] # # # ### Step 5.b.ii: Compute source term of the $\tilde{S}_i$ equation: $\frac{1}{2} \alpha\sqrt{\gamma} T^{\mu\nu} g_{\mu\nu,i}$ \[Back to [top](#toc)\] # $$\label{stildeisource}$$ # # Now that we've computed `g4DD_zerotimederiv_dD`$=g_{\mu\nu,i}$, the $\tilde{S}_i$ evolution equation source term may be quickly constructed. # In[8]: # Step 5.b.ii: Compute S_tilde source term def compute_S_tilde_source_termD(alpha, sqrtgammaDET,g4DD_zerotimederiv_dD, T4UU): global S_tilde_source_termD S_tilde_source_termD = ixp.zerorank1(DIM=3) for i in range(3): for mu in range(4): for nu in range(4): S_tilde_source_termD[i] += sp.Rational(1,2)*alpha*sqrtgammaDET*T4UU[mu][nu]*g4DD_zerotimederiv_dD[mu][nu][i+1] # # # # Step 6: Conversion of $v^i$ to $u^\mu$ (Courtesy Patrick Nelson) \[Back to [top](#toc)\] # $$\label{convertvtou}$$ # # According to Eqs. 9-11 of [the IllinoisGRMHD paper](https://arxiv.org/pdf/1501.07276.pdf), the Valencia 3-velocity $v^i_{(n)}$ is related to the 4-velocity $u^\mu$ via # # \begin{align} # \alpha v^i_{(n)} &= \frac{u^i}{u^0} + \beta^i \\ # \implies u^i &= u^0 \left(\alpha v^i_{(n)} - \beta^i\right) # \end{align} # # Defining $v^i = \frac{u^i}{u^0}$, we get # # $$v^i = \alpha v^i_{(n)} - \beta^i,$$ # # and in terms of this variable we get # # \begin{align} # g_{00} \left(u^0\right)^2 + 2 g_{0i} u^0 u^i + g_{ij} u^i u^j &= \left(u^0\right)^2 \left(g_{00} + 2 g_{0i} v^i + g_{ij} v^i v^j\right)\\ # \implies u^0 &= \pm \sqrt{\frac{-1}{g_{00} + 2 g_{0i} v^i + g_{ij} v^i v^j}} \\ # &= \pm \sqrt{\frac{-1}{(-\alpha^2 + \beta^2) + 2 \beta_i v^i + \gamma_{ij} v^i v^j}} \\ # &= \pm \sqrt{\frac{1}{\alpha^2 - \gamma_{ij}\left(\beta^i + v^i\right)\left(\beta^j + v^j\right)}}\\ # &= \pm \sqrt{\frac{1}{\alpha^2 - \alpha^2 \gamma_{ij}v^i_{(n)}v^j_{(n)}}}\\ # &= \pm \frac{1}{\alpha}\sqrt{\frac{1}{1 - \gamma_{ij}v^i_{(n)}v^j_{(n)}}} # \end{align} # # Generally speaking, numerical errors will occasionally drive expressions under the radical to either negative values or potentially enormous values (corresponding to enormous Lorentz factors). Thus a reliable approach for computing $u^0$ requires that we first rewrite the above expression in terms of the Lorentz factor squared: $\Gamma^2=\left(\alpha u^0\right)^2$: # \begin{align} # u^0 &= \pm \frac{1}{\alpha}\sqrt{\frac{1}{1 - \gamma_{ij}v^i_{(n)}v^j_{(n)}}}\\ # \implies \left(\alpha u^0\right)^2 &= \frac{1}{1 - \gamma_{ij}v^i_{(n)}v^j_{(n)}} \\ # \implies \gamma_{ij}v^i_{(n)}v^j_{(n)} &= 1 - \frac{1}{\left(\alpha u^0\right)^2} \\ # &= 1 - \frac{1}{\Gamma^2} # \end{align} # # In order for the bottom expression to hold true, the left-hand side must be between 0 and 1. Again, this is not guaranteed due to the appearance of numerical errors. In fact, a robust algorithm will not allow $\Gamma^2$ to become too large (which might contribute greatly to the stress-energy of a given gridpoint), so let's define the largest allowed Lorentz factor as $\Gamma_{\rm max}$. # # Then our algorithm for computing $u^0$ is as follows: # # If # $$R=\gamma_{ij}v^i_{(n)}v^j_{(n)}>1 - \frac{1}{\Gamma_{\rm max}^2},$$ # then adjust the 3-velocity $v^i$ as follows: # # $$v^i_{(n)} \to \sqrt{\frac{1 - \frac{1}{\Gamma_{\rm max}^2}}{R}}v^i_{(n)}.$$ # # After this rescaling, we are then guaranteed that if $R$ is recomputed, it will be set to its ceiling value $R=R_{\rm max} = 1 - \frac{1}{\Gamma_{\rm max}^2}$. # # Then, regardless of whether the ceiling on $R$ was applied, $u^0$ can be safely computed via # # $$ # u^0 = \frac{1}{\alpha \sqrt{1-R}}, # $$ # and the remaining components $u^i$ via # $$ # u^i = u^0 v^i. # $$ # # In summary our algorithm for computing $u^{\mu}$ from $v^i = \frac{u^i}{u^0}$ is as follows: # # 1. Choose a maximum Lorentz factor $\Gamma_{\rm max}$=`GAMMA_SPEED_LIMIT`, and define $v^i_{(n)} = \frac{1}{\alpha}\left( \frac{u^i}{u^0} + \beta^i\right)$. # 1. Compute $R=\gamma_{ij}v^i_{(n)}v^j_{(n)}=1 - \frac{1}{\Gamma^2}$ # 1. If $R \le 1 - \frac{1}{\Gamma_{\rm max}^2}$, then skip the next step. # 1. Otherwise if $R > 1 - \frac{1}{\Gamma_{\rm max}^2}$ then adjust $v^i_{(n)}\to \sqrt{\frac{1 - \frac{1}{\Gamma_{\rm max}^2}}{R}}v^i_{(n)}$, which will force $R=R_{\rm max}$. # 1. Given the $R$ computed in the above step, $u^0 = \frac{1}{\alpha \sqrt{1-R}}$, and $u^i=u^0 v^i$. # # While the above algorithm is quite robust, its `if()` statement in the fourth step is not very friendly to NRPy+ or an optimizing C compiler, as it would require NRPy+ to generate separate C kernels for each branch of the `if()`. Let's instead try the following trick, which Roland Haas taught us. # # Define $R^*$ as # # $$ # R^* = \frac{1}{2} \left(R_{\rm max} + R - |R_{\rm max} - R| \right). # $$ # # If $R>R_{\rm max}$, then $|R_{\rm max} - R|=R - R_{\rm max}$, and we get: # # $$ # R^* = \frac{1}{2} \left(R_{\rm max} + R - (R - R_{\rm max}) \right) = \frac{1}{2} \left(2 R_{\rm max}\right) = R_{\rm max} # $$ # # If $R\le R_{\rm max}$, then $|R_{\rm max} - R|=R_{\rm max} - R$, and we get: # # $$ # R^* = \frac{1}{2} \left(R_{\rm max} + R - (R_{\rm max} - R) \right) = \frac{1}{2} \left(2 R\right) = R # $$ # # Then we can rescale *all* $v^i_{(n)}$ via # # $$ # v^i_{(n)} \to v^i_{(n)} \sqrt{\frac{R^*}{R}}, # $$ # # though we must be very careful to carefully handle the case in which $R=0$. To avoid any problems in this case, we simply adjust the above rescaling by adding a tiny number [`TINYDOUBLE`](https://en.wikipedia.org/wiki/Tiny_Bubbles) to $R$ in the denominator, typically `1e-100`: # # $$ # v^i_{(n)} \to v^i_{(n)} \sqrt{\frac{R^*}{R + {\rm TINYDOUBLE}}}. # $$ # # Finally, $u^0$ can be immediately and safely computed, via: # $$ # u^0 = \frac{1}{\alpha \sqrt{1-R^*}}, # $$ # and $u^i$ via # $$ # u^i = u^0 v^i = u^0 \left(\alpha v^i_{(n)} - \beta^i\right). # $$ # In[9]: # Step 6.a: Convert Valencia 3-velocity v_{(n)}^i into u^\mu, and apply a speed limiter # Speed-limited ValenciavU is output to rescaledValenciavU global. def u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha,betaU,gammaDD, ValenciavU): # Inputs: Metric lapse alpha, shift betaU, 3-metric gammaDD, Valencia 3-velocity ValenciavU # Outputs (as globals): u4U_ito_ValenciavU, rescaledValenciavU # R = gamma_{ij} v^i v^j R = sp.sympify(0) for i in range(3): for j in range(3): R += gammaDD[i][j]*ValenciavU[i]*ValenciavU[j] thismodule = "GRHD" # The default value isn't terribly important here, since we can overwrite in the main C code GAMMA_SPEED_LIMIT = par.Cparameters("REAL", thismodule, "GAMMA_SPEED_LIMIT", 10.0) # Default value based on # IllinoisGRMHD. # GiRaFFE default = 2000.0 Rmax = 1 - 1 / (GAMMA_SPEED_LIMIT * GAMMA_SPEED_LIMIT) # Now, we set Rstar = min(Rmax,R): # If R < Rmax, then Rstar = 0.5*(Rmax+R-Rmax+R) = R # If R >= Rmax, then Rstar = 0.5*(Rmax+R+Rmax-R) = Rmax Rstar = sp.Rational(1, 2) * (Rmax + R - nrpyAbs(Rmax - R)) # We add TINYDOUBLE to R below to avoid a 0/0, which occurs when # ValenciavU == 0 for all Valencia 3-velocity components. # "Those tiny *doubles* make me warm all over # with a feeling that I'm gonna love you till the end of time." # - Adapted from Connie Francis' "Tiny Bubbles" TINYDOUBLE = par.Cparameters("#define",thismodule,"TINYDOUBLE",1e-100) # The rescaled (speed-limited) Valencia 3-velocity # is given by, v_{(n)}^i = sqrt{Rstar/R} v^i global rescaledValenciavU rescaledValenciavU = ixp.zerorank1(DIM=3) for i in range(3): # If R == 0, then Rstar == 0, so sqrt( Rstar/(R+TINYDOUBLE) )=sqrt(0/1e-100) = 0 # If your velocities are of order 1e-100 and this is physically # meaningful, there must be something wrong with your unit conversion. rescaledValenciavU[i] = ValenciavU[i]*sp.sqrt(Rstar/(R + TINYDOUBLE)) # Finally compute u^mu in terms of Valenciav^i # u^0 = 1/(alpha-sqrt(1-R^*)) global u4U_ito_ValenciavU u4U_ito_ValenciavU = ixp.zerorank1(DIM=4) u4U_ito_ValenciavU[0] = 1/(alpha*sp.sqrt(1-Rstar)) # u^i = u^0 ( alpha v^i_{(n)} - beta^i ), where v^i_{(n)} is the Valencia 3-velocity for i in range(3): u4U_ito_ValenciavU[i+1] = u4U_ito_ValenciavU[0] * (alpha * rescaledValenciavU[i] - betaU[i]) # Step 6.b: Convert v^i into u^\mu, and apply a speed limiter. # Speed-limited vU is output to rescaledvU global. def u4U_in_terms_of_vU__rescale_vU_by_applying_speed_limit(alpha,betaU,gammaDD, vU): ValenciavU = ixp.zerorank1(DIM=3) for i in range(3): ValenciavU[i] = (vU[i] + betaU[i])/alpha u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha,betaU,gammaDD, ValenciavU) # Since ValenciavU is written in terms of vU, # u4U_ito_ValenciavU is actually u4U_ito_vU global u4U_ito_vU u4U_ito_vU = ixp.zerorank1(DIM=4) for mu in range(4): u4U_ito_vU[mu] = u4U_ito_ValenciavU[mu] # Finally compute the rescaled (speed-limited) vU global rescaledvU rescaledvU = ixp.zerorank1(DIM=3) for i in range(3): rescaledvU[i] = alpha * rescaledValenciavU[i] - betaU[i] # # # # Step 7: Declare ADM and hydrodynamical input variables, and construct GRHD equations \[Back to [top](#toc)\] # $$\label{declarevarsconstructgrhdeqs}$$ # In[10]: # First define hydrodynamical quantities u4U = ixp.declarerank1("u4U", DIM=4) rho_b,P,epsilon = sp.symbols('rho_b P epsilon',real=True) # Then ADM quantities gammaDD = ixp.declarerank2("gammaDD","sym01",DIM=3) KDD = ixp.declarerank2("KDD" ,"sym01",DIM=3) betaU = ixp.declarerank1("betaU", DIM=3) alpha = sp.symbols('alpha', real=True) # First compute stress-energy tensor T4UU and T4UD: compute_T4UU(gammaDD,betaU,alpha, rho_b,P,epsilon,u4U) compute_T4UD(gammaDD,betaU,alpha, T4UU) # Next sqrt(gamma) compute_sqrtgammaDET(gammaDD) # Compute conservative variables in terms of primitive variables compute_rho_star( alpha, sqrtgammaDET, rho_b,u4U) compute_tau_tilde(alpha, sqrtgammaDET, T4UU,rho_star) compute_S_tildeD( alpha, sqrtgammaDET, T4UD) # Then compute v^i from u^mu compute_vU_from_u4U__no_speed_limit(u4U) # Next compute fluxes of conservative variables compute_rho_star_fluxU( vU, rho_star) compute_tau_tilde_fluxU(alpha, sqrtgammaDET, vU,T4UU, rho_star) compute_S_tilde_fluxUD( alpha, sqrtgammaDET, T4UD) # Then declare derivatives & compute g4DD_zerotimederiv_dD gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01",DIM=3) betaU_dD = ixp.declarerank2("betaU_dD" ,"nosym",DIM=3) alpha_dD = ixp.declarerank1("alpha_dD" ,DIM=3) compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDD_dD,betaU_dD,alpha_dD) # Then compute source terms on tau_tilde and S_tilde equations compute_s_source_term(KDD,betaU,alpha, sqrtgammaDET,alpha_dD, T4UU) compute_S_tilde_source_termD( alpha, sqrtgammaDET,g4DD_zerotimederiv_dD, T4UU) # Then compute the 4-velocities in terms of an input Valencia 3-velocity testValenciavU[i] testValenciavU = ixp.declarerank1("testValenciavU",DIM=3) u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha,betaU,gammaDD, testValenciavU) # Finally compute the 4-velocities in terms of an input 3-velocity testvU[i] = u^i/u^0 testvU = ixp.declarerank1("testvU",DIM=3) u4U_in_terms_of_vU__rescale_vU_by_applying_speed_limit(alpha,betaU,gammaDD, testvU) # # # # Step 8: Code Validation against `GRHD.equations` NRPy+ module \[Back to [top](#toc)\] # $$\label{code_validation}$$ # # As a code validation check, we verify agreement in the SymPy expressions for the GRHD equations generated in # 1. this tutorial versus # 2. the NRPy+ [GRHD.equations](../edit/GRHD/equations.py) module. # In[11]: import GRHD.equations as Ge # First compute stress-energy tensor T4UU and T4UD: Ge.compute_T4UU(gammaDD,betaU,alpha, rho_b,P,epsilon,u4U) Ge.compute_T4UD(gammaDD,betaU,alpha, Ge.T4UU) # Next sqrt(gamma) Ge.compute_sqrtgammaDET(gammaDD) # Compute conservative variables in terms of primitive variables Ge.compute_rho_star( alpha, Ge.sqrtgammaDET, rho_b,u4U) Ge.compute_tau_tilde(alpha, Ge.sqrtgammaDET, Ge.T4UU,Ge.rho_star) Ge.compute_S_tildeD( alpha, Ge.sqrtgammaDET, Ge.T4UD) # Then compute v^i from u^mu Ge.compute_vU_from_u4U__no_speed_limit(u4U) # Next compute fluxes of conservative variables Ge.compute_rho_star_fluxU ( Ge.vU, Ge.rho_star) Ge.compute_tau_tilde_fluxU(alpha, Ge.sqrtgammaDET, Ge.vU,Ge.T4UU, Ge.rho_star) Ge.compute_S_tilde_fluxUD (alpha, Ge.sqrtgammaDET, Ge.T4UD) # Then declare derivatives & compute g4DD_zerotimederiv_dD # gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01",DIM=3) # betaU_dD = ixp.declarerank2("betaU_dD" ,"nosym",DIM=3) # alpha_dD = ixp.declarerank1("alpha_dD" ,DIM=3) Ge.compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDD_dD,betaU_dD,alpha_dD) # Finally compute source terms on tau_tilde and S_tilde equations Ge.compute_s_source_term(KDD,betaU,alpha, Ge.sqrtgammaDET,alpha_dD, Ge.T4UU) Ge.compute_S_tilde_source_termD( alpha, Ge.sqrtgammaDET,Ge.g4DD_zerotimederiv_dD,Ge.T4UU) GetestValenciavU = ixp.declarerank1("testValenciavU") Ge.u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha, betaU, gammaDD, GetestValenciavU) GetestvU = ixp.declarerank1("testvU") Ge.u4U_in_terms_of_vU__rescale_vU_by_applying_speed_limit( alpha, betaU, gammaDD, GetestvU) # In[12]: all_passed=True def comp_func(expr1,expr2,basename,prefixname2="Ge."): if str(expr1-expr2)!="0": print(basename+" - "+prefixname2+basename+" = "+ str(expr1-expr2)) all_passed=False def gfnm(basename,idx1,idx2=None,idx3=None): if idx2 is None: return basename+"["+str(idx1)+"]" if idx3 is None: return basename+"["+str(idx1)+"]["+str(idx2)+"]" return basename+"["+str(idx1)+"]["+str(idx2)+"]["+str(idx3)+"]" expr_list = [] exprcheck_list = [] namecheck_list = [] namecheck_list.extend(["sqrtgammaDET","rho_star","tau_tilde","s_source_term"]) exprcheck_list.extend([Ge.sqrtgammaDET,Ge.rho_star,Ge.tau_tilde,Ge.s_source_term]) expr_list.extend([sqrtgammaDET,rho_star,tau_tilde,s_source_term]) for mu in range(4): namecheck_list.extend([gfnm("u4_ito_ValenciavU",mu),gfnm("u4U_ito_vU",mu)]) exprcheck_list.extend([ Ge.u4U_ito_ValenciavU[mu], Ge.u4U_ito_vU[mu]]) expr_list.extend( [ u4U_ito_ValenciavU[mu], u4U_ito_vU[mu]]) for nu in range(4): namecheck_list.extend([gfnm("T4UU",mu,nu),gfnm("T4UD",mu,nu)]) exprcheck_list.extend([Ge.T4UU[mu][nu],Ge.T4UD[mu][nu]]) expr_list.extend([T4UU[mu][nu],T4UD[mu][nu]]) for delta in range(4): namecheck_list.extend([gfnm("g4DD_zerotimederiv_dD",mu,nu,delta)]) exprcheck_list.extend([Ge.g4DD_zerotimederiv_dD[mu][nu][delta]]) expr_list.extend([g4DD_zerotimederiv_dD[mu][nu][delta]]) for i in range(3): namecheck_list.extend([gfnm("S_tildeD",i),gfnm("vU",i),gfnm("rho_star_fluxU",i), gfnm("tau_tilde_fluxU",i),gfnm("S_tilde_source_termD",i), gfnm("rescaledValenciavU",i), gfnm("rescaledvU",i)]) exprcheck_list.extend([Ge.S_tildeD[i],Ge.vU[i],Ge.rho_star_fluxU[i], Ge.tau_tilde_fluxU[i],Ge.S_tilde_source_termD[i], Ge.rescaledValenciavU[i],Ge.rescaledvU[i]]) expr_list.extend([S_tildeD[i],vU[i],rho_star_fluxU[i], tau_tilde_fluxU[i],S_tilde_source_termD[i], rescaledValenciavU[i],rescaledvU[i]]) for j in range(3): namecheck_list.extend([gfnm("S_tilde_fluxUD",i,j)]) exprcheck_list.extend([Ge.S_tilde_fluxUD[i][j]]) expr_list.extend([S_tilde_fluxUD[i][j]]) for i in range(len(expr_list)): comp_func(expr_list[i],exprcheck_list[i],namecheck_list[i]) import sys if all_passed: print("ALL TESTS PASSED!") else: print("ERROR: AT LEAST ONE TEST DID NOT PASS") sys.exit(1) # # # # Step 9: 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-GRHD_Equations-Cartesian.pdf](Tutorial-GRHD_Equations-Cartesian.pdf) (Note that clicking on this link may not work; you may need to open the PDF file through another means.) # In[13]: import cmdline_helper as cmd # NRPy+: Multi-platform Python command-line interface cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-GRHD_Equations-Cartesian")