Notebook Status: Validated
Validation Notes: This module has been validated against a trusted code (the hand-written smallbPoynET in WVUThorns_diagnostics, which itself is based on expressions in IllinoisGRMHD... which was validated against the original GRMHD code of the Illinois NR group)
This notebook is organized as follows
BSSN.ADMBSSN_tofrom_4metric
(tutorial) NRPy+ modulegammaDET
from the ADM 3+1 variablesu0_smallb_Poynting__Cartesian
NRPy+ moduleFirst some definitions. The spatial components of bμ are simply the magnetic field as measured by an observer comoving with the plasma Bμ(u), divided by √4π. In addition, in the ideal MHD limit, Bμ(u) is orthogonal to the plasma 4-velocity uμ, which sets the μ=0 component.
Note also that Bμ(u) is related to the magnetic field as measured by a normal observer Bi via a simple projection (Eq 21 in Duez et al (2005)), which results in the expressions (Eqs 23 and 24 in Duez et al (2005)):
√4πb0=B0(u)=ujBjα√4πbi=Bi(u)=Bi+(ujBj)uiαu0Bi is related to the actual magnetic field evaluated in IllinoisGRMHD, ˜Bi via
Bi=˜Biγ,where γ is the determinant of the spatial 3-metric.
The above expressions will require that we compute
BSSN.ADMBSSN_tofrom_4metric
(tutorial) NRPy+ module [Back to top]¶We are given γij, α, and βi from ADMBase, so let's first compute
gμν=(−α2+βkβkβiβjγij).# Step 1: Initialize needed Python/NRPy+ modules
import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends
import NRPy_param_funcs as par # NRPy+: Parameter interface
import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
from outputC import outputC # NRPy+: Basic C code output functionality
import BSSN.ADMBSSN_tofrom_4metric as AB4m # NRPy+: ADM/BSSN <-> 4-metric conversions
# Set spatial dimension = 3
DIM=3
thismodule = "smallbPoynET"
# Step 1.a: Compute the 4-metric $g_{\mu\nu}$ and its inverse
# $g^{\mu\nu}$ from the ADM 3+1 variables, using the
# BSSN.ADMBSSN_tofrom_4metric NRPy+ module
import BSSN.ADMBSSN_tofrom_4metric as AB4m
gammaDD,betaU,alpha = AB4m.setup_ADM_quantities("ADM")
AB4m.g4DD_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)
g4DD = AB4m.g4DD
AB4m.g4UU_ito_BSSN_or_ADM("ADM",gammaDD,betaU,alpha)
g4UU = AB4m.g4UU
According to Eqs. 9-11 of the IllinoisGRMHD paper, the Valencia 3-velocity vi(n) is related to the 4-velocity uμ via
αvi(n)=uiu0+βi⟹ui=u0(αvi(n)−βi)Defining vi=uiu0, we get
vi=αvi(n)−βi,and in terms of this variable we get
g00(u0)2+2g0iu0ui+gijuiuj=(u0)2(g00+2g0ivi+gijvivj)⟹u0=±√−1g00+2g0ivi+gijvivj=±√−1(−α2+β2)+2βivi+γijvivj=±√1α2−γij(βi+vi)(βj+vj)=±√1α2−α2γijvi(n)vj(n)=±1α√11−γijvi(n)vj(n)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 u0 requires that we first rewrite the above expression in terms of the Lorentz factor squared: Γ2=(αu0)2: u0=±1α√11−γijvi(n)vj(n)⟹(αu0)2=11−γijvi(n)vj(n)⟹γijvi(n)vj(n)=1−1(αu0)2=1−1Γ2
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 Γ2 to become too large (which might contribute greatly to the stress-energy of a given gridpoint), so let's define Γmax, the largest allowed Lorentz factor.
Then our algorithm for computing u0 is as follows:
If R=γijvi(n)vj(n)>1−1Γ2max,
After this rescaling, we are then guaranteed that if R is recomputed, it will be set to its ceiling value R=Rmax=1−1Γ2max.
Then, regardless of whether the ceiling on R was applied, u0 can be safely computed via
u0=1α√1−R.ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUX","ValenciavU",DIM=3)
# Step 1: Compute R = 1 - 1/max(Gamma)
R = sp.sympify(0)
for i in range(DIM):
for j in range(DIM):
R += gammaDD[i][j]*ValenciavU[i]*ValenciavU[j]
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)
rescaledValenciavU = ixp.zerorank1()
for i in range(DIM):
rescaledValenciavU[i] = ValenciavU[i]*sp.sqrt(Rmax/R)
rescaledu0 = 1/(alpha*sp.sqrt(1-Rmax))
regularu0 = 1/(alpha*sp.sqrt(1-R))
computeu0_Cfunction = """
/* Function for computing u^0 from Valencia 3-velocity. */
/* Inputs: ValenciavU[], alpha, gammaDD[][], GAMMA_SPEED_LIMIT (C parameter) */
/* Output: u0=u^0 and velocity-limited ValenciavU[] */\n\n"""
computeu0_Cfunction += outputC([R,Rmax],["const double R","const double Rmax"],"returnstring",
params="includebraces=False,CSE_varprefix=tmpR,outCverbose=False")
computeu0_Cfunction += "if(R <= Rmax) "
computeu0_Cfunction += outputC(regularu0,"u0","returnstring",
params="includebraces=True,CSE_varprefix=tmpnorescale,outCverbose=False")
computeu0_Cfunction += " else "
computeu0_Cfunction += outputC([rescaledValenciavU[0],rescaledValenciavU[1],rescaledValenciavU[2],rescaledu0],
["ValenciavU0","ValenciavU1","ValenciavU2","u0"],"returnstring",
params="includebraces=True,CSE_varprefix=tmprescale,outCverbose=False")
print(computeu0_Cfunction)
/* Function for computing u^0 from Valencia 3-velocity. */ /* Inputs: ValenciavU[], alpha, gammaDD[][], GAMMA_SPEED_LIMIT (C parameter) */ /* Output: u0=u^0 and velocity-limited ValenciavU[] */ const double R = ((ValenciavU0)*(ValenciavU0))*gammaDD00 + 2*ValenciavU0*ValenciavU1*gammaDD01 + 2*ValenciavU0*ValenciavU2*gammaDD02 + ((ValenciavU1)*(ValenciavU1))*gammaDD11 + 2*ValenciavU1*ValenciavU2*gammaDD12 + ((ValenciavU2)*(ValenciavU2))*gammaDD22; const double Rmax = 1 - 1/((GAMMA_SPEED_LIMIT)*(GAMMA_SPEED_LIMIT)); if(R <= Rmax) { u0 = 1/(alpha*sqrt(-((ValenciavU0)*(ValenciavU0))*gammaDD00 - 2*ValenciavU0*ValenciavU1*gammaDD01 - 2*ValenciavU0*ValenciavU2*gammaDD02 - ((ValenciavU1)*(ValenciavU1))*gammaDD11 - 2*ValenciavU1*ValenciavU2*gammaDD12 - ((ValenciavU2)*(ValenciavU2))*gammaDD22 + 1)); } else { const double tmprescale_1 = sqrt((1 - 1/((GAMMA_SPEED_LIMIT)*(GAMMA_SPEED_LIMIT)))/(((ValenciavU0)*(ValenciavU0))*gammaDD00 + 2*ValenciavU0*ValenciavU1*gammaDD01 + 2*ValenciavU0*ValenciavU2*gammaDD02 + ((ValenciavU1)*(ValenciavU1))*gammaDD11 + 2*ValenciavU1*ValenciavU2*gammaDD12 + ((ValenciavU2)*(ValenciavU2))*gammaDD22)); ValenciavU0 = ValenciavU0*tmprescale_1; ValenciavU1 = ValenciavU1*tmprescale_1; ValenciavU2 = ValenciavU2*tmprescale_1; u0 = fabs(GAMMA_SPEED_LIMIT)/alpha; }
u0 = par.Cparameters("REAL",thismodule,"u0",1e300) # Will be overwritten in C code. Set to crazy value to ensure this.
uD = ixp.zerorank1()
for i in range(DIM):
for j in range(DIM):
uD[j] += alpha*u0*gammaDD[i][j]*ValenciavU[i]
We compute bμ from the above expressions:
√4πb0=B0(u)=ujBjα√4πbi=Bi(u)=Bi+(ujBj)uiαu0Bi is exactly equal to the Bi evaluated in IllinoisGRMHD/GiRaFFE.
Pulling this together, we currently have available as input:
with the goal of outputting now bμ and b2:
M_PI = par.Cparameters("#define",thismodule,"M_PI","")
BU = ixp.register_gridfunctions_for_single_rank1("AUX","BU",DIM=3)
# uBcontraction = u_i B^i
uBcontraction = sp.sympify(0)
for i in range(DIM):
uBcontraction += uD[i]*BU[i]
# uU = 3-vector representing u^i = u^0 \left(\alpha v^i_{(n)} - \beta^i\right)
uU = ixp.zerorank1()
for i in range(DIM):
uU[i] = u0*(alpha*ValenciavU[i] - betaU[i])
smallb4U = ixp.zerorank1(DIM=4)
smallb4U[0] = uBcontraction/(alpha*sp.sqrt(4*M_PI))
for i in range(DIM):
smallb4U[1+i] = (BU[i] + uBcontraction*uU[i])/(alpha*u0*sp.sqrt(4*M_PI))
The Poynting flux is defined in Eq. 11 of Kelly et al. (note that we choose the minus sign convention so that the Poynting luminosity across a spherical shell is LEM=∫(−αTiEM 0)√γdΩ=∫Sr√γdΩ, as in Farris et al.:
Si=−αTiEM 0=−α(b2uiu0+12b2gi0−bib0)# Step 2.a.i: compute g^\mu_\delta:
g4UD = ixp.zerorank2(DIM=4)
for mu in range(4):
for delta in range(4):
for nu in range(4):
g4UD[mu][delta] += g4UU[mu][nu]*g4DD[nu][delta]
# Step 2.a.ii: compute b_{\mu}
smallb4D = ixp.zerorank1(DIM=4)
for mu in range(4):
for nu in range(4):
smallb4D[mu] += g4DD[mu][nu]*smallb4U[nu]
# Step 2.a.iii: compute u_0 = g_{mu 0} u^{mu} = g4DD[0][0]*u0 + g4DD[i][0]*uU[i]
u_0 = g4DD[0][0]*u0
for i in range(DIM):
u_0 += g4DD[i+1][0]*uU[i]
# Step 2.a.iv: compute b^2, setting b^2 = smallb2etk, as gridfunctions with base names ending in a digit
# are forbidden in NRPy+.
smallb2etk = sp.sympify(0)
for mu in range(4):
smallb2etk += smallb4U[mu]*smallb4D[mu]
# Step 2.a.v: compute S^i
PoynSU = ixp.zerorank1()
for i in range(DIM):
PoynSU[i] = -alpha * (smallb2etk*uU[i]*u_0 + sp.Rational(1,2)*smallb2etk*g4UD[i+1][0] - smallb4U[i+1]*smallb4D[0])
u0_smallb_Poynting__Cartesian
NRPy+ module [Back to top]¶Here, as a code validation check, we verify agreement in the SymPy expressions for u0, smallbU, smallb2etk, and PoynSU between
import sys
import u0_smallb_Poynting__Cartesian.u0_smallb_Poynting__Cartesian as u0etc
u0etc.compute_u0_smallb_Poynting__Cartesian(gammaDD,betaU,alpha,ValenciavU,BU)
if u0etc.computeu0_Cfunction != computeu0_Cfunction:
print("FAILURE: u0 C code has changed!")
sys.exit(1)
else:
print("PASSED: u0 C code matches!")
for i in range(4):
print("u0etc.smallb4U["+str(i)+"] - smallb4U["+str(i)+"] = "
+ str(u0etc.smallb4U[i]-smallb4U[i]))
print("u0etc.smallb2etk - smallb2etk = " + str(u0etc.smallb2etk-smallb2etk))
for i in range(DIM):
print("u0etc.PoynSU["+str(i)+"] - PoynSU["+str(i)+"] = "
+ str(u0etc.PoynSU[i]-PoynSU[i]))
PASSED: u0 C code matches! u0etc.smallb4U[0] - smallb4U[0] = 0 u0etc.smallb4U[1] - smallb4U[1] = 0 u0etc.smallb4U[2] - smallb4U[2] = 0 u0etc.smallb4U[3] - smallb4U[3] = 0 u0etc.smallb2etk - smallb2etk = 0 u0etc.PoynSU[0] - PoynSU[0] = 0 u0etc.PoynSU[1] - PoynSU[1] = 0 u0etc.PoynSU[2] - PoynSU[2] = 0
uμuμ=−1 implies
gμνuμuν=g00(u0)2+2g0iu0ui+gijuiuj=−1⟹g00(u0)2+2g0iu0ui+gijuiuj+1=0ax2+bx+c=0Thus we have a quadratic equation for u0, with solution given by
u0=−b±√b2−4ac2a=−2g0iui±√(2g0iui)2−4g00(gijuiuj+1)2g00=−g0iui±√(g0iui)2−g00(gijuiuj+1)g00Notice that (Eq. 4.49 in Gourgoulhon) gμν=(−1α2βiα2βiα2γij−βiβjα2),
Now, since
u0=gα0uα=−1α2u0+βiuiα2,we get
u0=1α2(u0+βiui)=±1α2√α2(γijuiuj+1)=±1α√γijuiuj+1By convention, the relativistic Gamma factor is positive and given by αu0, so we choose the positive root. Thus we have derived Eq. 53 in Duez et al (2005):
u0=1α√γijuiuj+1.Next we evaluate
ui=uμgμi=u0g0i+ujgij=u0βiα2+uj(γij−βiβjα2)=γijuj+u0βiα2−ujβiβjα2=γijuj+βiα2(u0−ujβj)=γijuj−βiu0,⟹vi=γijuju0−βiwhich is equivalent to Eq. 56 in Duez et al (2005). Notice in the last step, we used the above definition of u0.
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-u0_smallb_Poynting-Cartesian.pdf (Note that clicking on this link may not work; you may need to open the PDF file through another means.)
import cmdline_helper as cmd # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-u0_smallb_Poynting-Cartesian")
[pandoc warning] Duplicate link reference `[comment]' "source" (line 22, column 1) Created Tutorial-u0_smallb_Poynting-Cartesian.tex, and compiled LaTeX file to PDF file Tutorial-u0_smallb_Poynting-Cartesian.pdf