GiRaFFE_NRPy
C code library: Conservative-to-Primitive and Primitive-to-Conservative Solvers¶GiRaFFE_NRPy
uses in order to update the Valencia 3-velocity at each timestep. It also computes corrections to the densitized Poynting flux in order to keep the physical quantities from violating the GRFFE constraints.¶Notebook Status: Validated
Validation Notes: These functions have been validated to round-off precision against the coresponding functions in the original GiRaFFE
.
These algorithms are adapted from the original GiRaFFE
code (see arxiv:1704.00599v2), based on the description in arXiv:1310.3274v2. They have been fully NRPyfied and modified to use the Valencia 3-velocity instead of the drift velocity.
The algorithm to do this is as follows:
Each of these steps can be toggled on/off by changing the following NRPy+ parameters, specified in the python module:
par.initialize_param(par.glb_param(type="bool", module=thismodule, parname="enforce_orthogonality_StildeD_BtildeU", defaultval=True))
par.initialize_param(par.glb_param(type="bool", module=thismodule, parname="enforce_speed_limit_StildeD", defaultval=True))
par.initialize_param(par.glb_param(type="bool", module=thismodule, parname="enforce_current_sheet_prescription", defaultval=True))
# Step 0: Add NRPy's directory to the path
# https://stackoverflow.com/questions/16780014/import-file-from-parent-directory
import os,sys
nrpy_dir_path = os.path.join("..")
if nrpy_dir_path not in sys.path:
sys.path.append(nrpy_dir_path)
import os
import cmdline_helper as cmd
outdir = "GiRaFFE_NRPy/GiRaFFE_Ccode_validation/"
cmd.mkdir(outdir)
We start with the Conservative-to-Primitive solver. This function is called after the vector potential and Poynting vector have been evolved at a timestep and updates the velocities. The algorithm will be as follows:
from outputC import nrpyAbs # NRPy+: Core C code output module
import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends
import NRPy_param_funcs as par # NRPy+: Parameter interface
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 GRHD.equations as GRHD # NRPy+: Generate general relativistic hydrodynamics equations
import GRFFE.equations as GRFFE # NRPy+: Generate general relativisitic force-free electrodynamics equations
import GiRaFFE_NRPy.GiRaFFE_NRPy_C2P_P2C as C2P_P2C
thismodule = "GiRaFFE_NRPy-C2P_P2C"
# There are several C parameters that we will need in this module:
M_PI = par.Cparameters("#define",thismodule,["M_PI"], "")
GAMMA_SPEED_LIMIT = par.Cparameters("REAL",thismodule,"GAMMA_SPEED_LIMIT",10.0) # Default value based on
# IllinoisGRMHD.
# GiRaFFE default = 2000.0
gammaDD = ixp.declarerank2("gammaDD","sym01")
betaU = ixp.declarerank1("betaU")
StildeD = ixp.declarerank1("StildeD")
BU = ixp.declarerank1("BU")
alpha = sp.symbols('alpha',real=True)
sqrt4pi = sp.symbols('sqrt4pi', real=True)
ValenciavU = ixp.declarerank1("ValenciavU")
GRHD.compute_sqrtgammaDET(gammaDD)
gammaUU,unusedgammadet = ixp.symm_matrix_inverter3x3(gammaDD)
Now, we will enforce the orthogonality of the magnetic field and densitized poynting flux using Eq. 22 of arxiv:1704.00599v2: $${\tilde S}_i \rightarrow {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2$$ First, we compute the inner products ${\tilde S}_j {\tilde B}^j$ and ${\tilde B}^2 = \gamma_{ij} {\tilde B}^i {\tilde B}^j,$ where $\tilde{B}^i = B^i \sqrt{\gamma}$. Then, we subtract $({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2$ from ${\tilde S}_i$. We thus guarantee that ${\tilde S}_i B^i=0$.
Having fixed ${\tilde S}_i$, we will also compute the related quantities ${\tilde S}^i = \gamma^{ij} {\tilde S}_j$ and ${\tilde S}^2 = {\tilde S}_i {\tilde S}^i$.
# First, we need to obtain the index-lowered form of Btilde^i and (Btilde^i)^2
# Recall that Btilde^i = sqrtgamma*B^i
BtildeU = ixp.zerorank1()
for i in range(3):
# \tilde{B}^i = B^i \sqrt{\gamma}
BtildeU[i] = GRHD.sqrtgammaDET*BU[i]
BtildeD = ixp.zerorank1()
for i in range(3):
for j in range(3):
BtildeD[j] += gammaDD[i][j]*BtildeU[i]
Btilde2 = sp.sympify(0)
for i in range(3):
Btilde2 += BtildeU[i]*BtildeD[i]
# Then, enforce the orthogonality:
if par.parval_from_str("enforce_orthogonality_StildeD_BtildeU"):
StimesB = sp.sympify(0)
for i in range(3):
StimesB += StildeD[i]*BtildeU[i]
for i in range(3):
# {\tilde S}_i = {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2
StildeD[i] -= StimesB*BtildeD[i]/Btilde2
The next fix that we will apply limits the Lorentz factor using Eqs. 92 and 93 of arXiv:1310.3274v2. That is, we define the factor $f$ as $$f = \sqrt{(1-\Gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)}.$$
If $f<1$, we rescale the components of ${\tilde S}_i$ by $f$. That is, we must set $${\tilde S}_i \rightarrow {\tilde S}_i \min(1,f).$$
Here, we will use a formulation of the min()
function that does not use if
:
\begin{equation}
\min(a,b) = \frac{1}{2} \left( a+b - \lvert a-b \rvert \right),
\end{equation}
or, in code,
min_noif(a,b) = sp.Rational(1,2)*(a+b-nrpyAbs(a-b))
# Calculate \tilde{S}^2:
Stilde2 = sp.sympify(0)
for i in range(3):
for j in range(3):
Stilde2 += gammaUU[i][j]*StildeD[i]*StildeD[j]
# First we need to compute the factor f:
# f = \sqrt{(1-\Gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)}
speed_limit_factor = sp.sqrt((sp.sympify(1)-GAMMA_SPEED_LIMIT**(-2.0))*Btilde2*Btilde2*sp.Rational(1,16)/\
(M_PI*M_PI*GRHD.sqrtgammaDET*GRHD.sqrtgammaDET*Stilde2))
import Min_Max_and_Piecewise_Expressions as noif
# Calculate B^2
B2 = sp.sympify(0)
for i in range(3):
for j in range(3):
B2 += gammaDD[i][j]*BU[i]*BU[j]
# Enforce the speed limit on StildeD:
if par.parval_from_str("enforce_speed_limit_StildeD"):
for i in range(3):
StildeD[i] *= noif.min_noif(sp.sympify(1),speed_limit_factor)
Finally, we can calculate the velocities. In arxiv:1704.00599v2, Eq. 16 gives the drift velocity as $$v^i = 4 \pi \alpha \gamma^{ij} {\tilde S}_j \gamma^{-1/2} B^{-2} - \beta^i.$$ However, we wish to use the Valencia velocity instead. Since the Valencia velocity $\bar{v}^i = \frac{1}{\alpha} \left( v^i + \beta^i \right)$, we will code $$\bar{v}^i = 4 \pi \frac{\gamma^{ij} {\tilde S}_j}{\sqrt{\gamma} B^2}.$$
ValenciavU = ixp.zerorank1()
# Recompute 3-velocity:
for i in range(3):
for j in range(3):
# \bar{v}^i = 4 \pi \gamma^{ij} {\tilde S}_j / (\sqrt{\gamma} B^2)
ValenciavU[i] += sp.sympify(4)*M_PI*gammaUU[i][j]*StildeD[j]/(GRHD.sqrtgammaDET*B2)
Now, we seek to handle any current sheets (a physically important phenomenon) that might form. This algorithm, given as Eq. 23 in arxiv:1704.00599v2, will preserve current sheets that form in the xy-plane by preventing our numerical scheme from dissipating them. After fixing the z-component of the velocity, we recompute the conservative variables $\tilde{S}_i$ to be consistent with the new velocities.
Thus, if we are within four gridpoints of $z=0$, we set the component of the velocity perpendicular to the current sheet to zero by $n_i v^i = 0$, where $n_i = \gamma_{ij} n^j$ is a unit normal to the current sheet and $n^j = \delta^{jz} = (0\ 0\ 1)$. For drift velocity, this means we just set $$ v^z = -\frac{\gamma_{xz} v^x + \gamma_{yz} v^y}{\gamma_{zz}}. $$ This reduces to $v^z = 0$ in flat space, as one would expect. We then express the Valencia velocity in terms of the drift velocity as $\bar{v}^i = \frac{1}{\alpha} \left( v^i + \beta^i \right)$.
# This number determines how far away (in grid points) we will apply the fix.
grid_points_from_z_plane = par.Cparameters("REAL",thismodule,"grid_points_from_z_plane",4.0)
if par.parval_from_str("enforce_current_sheet_prescription"):
# Calculate the drift velocity
driftvU = ixp.zerorank1()
for i in range(3):
driftvU[i] = alpha*ValenciavU[i] - betaU[i]
# The direct approach, used by the original GiRaFFE:
# v^z = -(\gamma_{xz} v^x + \gamma_{yz} v^y) / \gamma_{zz}
newdriftvU2 = -(gammaDD[0][2]*driftvU[0] + gammaDD[1][2]*driftvU[1])/gammaDD[2][2]
# Now that we have the z component, it's time to substitute its Valencia form in.
# Remember, we only do this if abs(z) < (k+0.01)*dz. Note that we add 0.01; this helps
# avoid floating point errors and division by zero. This is the same as abs(z) - (k+0.01)*dz<0
coord = nrpyAbs(rfm.xx[2])
bound =(grid_points_from_z_plane+sp.Rational(1,100))*gri.dxx[2]
ValenciavU[2] = noif.coord_leq_bound(coord,bound)*(newdriftvU2+betaU[2])/alpha \
+ noif.coord_greater_bound(coord,bound)*ValenciavU[2]
Below is an experiment in coding this more abstractly. While it works, it's a bit harder to follow than the direct approach, which is what is coded above
# Set the Cartesian normal vector. This can be expanded later to arbitrary sheets and coordinate systems.
nU = ixp.zerorank1()
nU[2] = 1
# Lower the index, as usual:
nD = ixp.zerorank1()
for i in range(3):
for j in range(3):
nD[i] = gammaDD[i][j]*nU[j]
if par.parval_from_str("enforce_current_sheet_prescription"):
# Calculate the drift velocity
driftvU = ixp.declarerank1("driftvU")
inner_product = sp.sympify(0)
for i in range(3):
inner_product += driftvU[i]*nD[i] # This is the portion of the drift velocity normal to the z plane
# In flat space, this is just v^z
# We'll use a sympy utility to solve for v^z. This should make it easier to generalize later
newdriftvU2 = sp.solve(inner_product,driftvU[2]) # This outputs a list with a single element.
# Take the 0th element so .subs() works right.
newdriftvU2 = newdriftvU2[0] # In flat space this reduces to v^z=0
for i in range(3):
# Now, we substitute drift velocity in terms of our preferred Valencia velocity
newdriftvU2 = newdriftvU2.subs(driftvU[i],alpha*ValenciavU[i]-betaU[i])
# Now that we have the z component, it's time to substitute its Valencia form in.
# Remember, we only do this if abs(z) < (k+0.01)*dz. Note that we add 0.01; this helps
# avoid floating point errors and division by zero. This is the same as abs(z) - (k+0.01)*dz<0
boundary = nrpyAbs(rfm.xx[2]) - (grid_points_from_z_plane+sp.Rational(1,100))*gri.dxx[2]
ValenciavU[2] = min_normal0(boundary)*(newdriftvU2+betaU[2])/alpha \
+ max_normal0(boundary)*ValenciavU[2]
This function is used to recompute the conservatives $\tilde{S}_i$ after the 3-velocity is changed as part of the current sheet prescription using Eq. 21 of arxiv:1704.00599v2. It implements the same equation used to compute the initial Poynting flux from the initial velocity: $$\tilde{S}_i = \gamma_{ij} \frac{\bar{v}^j \sqrt{\gamma}B^2}{4 \pi}$$ in terms of the Valencia 3-velocity. In the implementation here, we first calculate $B^2 = \gamma_{ij} B^i B^j$, then $v_i = \gamma_{ij} v^j$ before we calculate the equivalent expression $$\tilde{S}_i = \frac{\bar{v}_i \sqrt{\gamma}B^2}{4 \pi}.$$
Here, we will simply let the NRPy+ GRFFE
module handle this part, since that is already validated.
def GiRaFFE_NRPy_P2C(gammaDD,betaU,alpha, ValenciavU,BU, sqrt4pi):
# After recalculating the 3-velocity, we need to update the poynting flux:
# We'll reset the Valencia velocity, since this will be part of a second call to outCfunction.
# First compute stress-energy tensor T4UU and T4UD:
GRHD.compute_sqrtgammaDET(gammaDD)
GRHD.u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha, betaU, gammaDD, ValenciavU)
GRFFE.compute_smallb4U_with_driftvU_for_FFE(gammaDD, betaU, alpha, GRHD.u4U_ito_ValenciavU, BU, sqrt4pi)
GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, GRFFE.smallb4_with_driftv_for_FFE_U)
GRFFE.compute_TEM4UU(gammaDD, betaU, alpha, GRFFE.smallb4_with_driftv_for_FFE_U, GRFFE.smallbsquared, GRHD.u4U_ito_ValenciavU)
GRFFE.compute_TEM4UD(gammaDD, betaU, alpha, GRFFE.TEM4UU)
# Compute conservative variables in terms of primitive variables
GRHD.compute_S_tildeD(alpha, GRHD.sqrtgammaDET, GRFFE.TEM4UD)
global StildeD
StildeD = GRHD.S_tildeD
As a code validation check, we will verify agreement in the SymPy expressions between
We will test the C2P algorithms first.
all_passed=True
def comp_func(expr1,expr2,basename,prefixname2="C2P_P2C."):
global all_passed
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)+"]"
notebook_StildeD = StildeD
StildeD = ixp.declarerank1("StildeD")
C2P_P2C.GiRaFFE_NRPy_C2P(StildeD,BU,gammaDD,betaU,alpha)
expr_list = []
exprcheck_list = []
namecheck_list = []
for i in range(3):
namecheck_list.extend([gfnm("StildeD",i),gfnm("ValenciavU",i)])
exprcheck_list.extend([C2P_P2C.outStildeD[i],C2P_P2C.ValenciavU[i]])
expr_list.extend([notebook_StildeD[i],ValenciavU[i]])
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)
ALL TESTS PASSED!
Now, we will reset the variables and test the P2C function.
all_passed=True
gammaDD = ixp.declarerank2("gammaDD","sym01")
betaU = ixp.declarerank1("betaU")
ValenciavU = ixp.declarerank1("ValenciavU")
BU = ixp.declarerank1("BU")
alpha = sp.symbols('alpha',real=True)
sqrt4pi = sp.symbols('sqrt4pi', real=True)
GiRaFFE_NRPy_P2C(gammaDD,betaU,alpha, ValenciavU,BU, sqrt4pi)
C2P_P2C.GiRaFFE_NRPy_P2C(gammaDD,betaU,alpha, ValenciavU,BU, sqrt4pi)
expr_list = []
exprcheck_list = []
namecheck_list = []
for i in range(3):
namecheck_list.extend([gfnm("StildeD",i)])
exprcheck_list.extend([C2P_P2C.StildeD[i]])
expr_list.extend([StildeD[i]])
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)
ALL TESTS PASSED!
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-GiRaFFE_NRPy-C2P_P2C.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-GiRaFFE_NRPy-C2P_P2C")
Created Tutorial-GiRaFFE_NRPy-C2P_P2C.tex, and compiled LaTeX file to PDF file Tutorial-GiRaFFE_NRPy-C2P_P2C.pdf