Notebook Status: Validated
Validation Notes: All expressions generated in this module have been validated against a trusted code (the original NRPy+/SENR code, which itself was validated against Baumgarte's code).
This tutorial notebook constructs SymPy expressions for the right-hand sides of the time-evolution equations for the gauge fields α (the lapse, governing how much proper time elapses at each point between one timestep in a 3+1 solution to Einstein's equations and the next) and βi (the shift, governing how much proper distance numerical grid points move from one timestep in a 3+1 solution to Einstein's equations and the next).
Though we are completely free to choose gauge conditions (i.e., free to choose the form of the right-hand sides of the gauge time evolution equations), very few have been found robust in the presence of (puncture) black holes. So we focus here only on a few of the most stable choices.
This notebook is organized as follows
BSSN.BSSN_gauge_RHSs
NRPy+ module# Step 1: Import all needed modules from NRPy+:
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 BSSN.BSSN_quantities as Bq # NRPy+: Computes useful BSSN quantities
import BSSN.BSSN_RHSs as Brhs # NRPy+: Constructs BSSN right-hand-side expressions
import sys # Standard Python modules for multiplatform OS-level functions
# Step 1.c: Declare/initialize parameters for this module
thismodule = "BSSN_gauge_RHSs"
par.initialize_param(par.glb_param("char", thismodule, "LapseEvolutionOption", "OnePlusLog"))
par.initialize_param(par.glb_param("char", thismodule, "ShiftEvolutionOption", "GammaDriving2ndOrder_Covariant"))
# Step 1.d: Set spatial dimension (must be 3 for BSSN, as BSSN is
# a 3+1-dimensional decomposition of the general
# relativistic field equations)
DIM = 3
# Step 1.e: Given the chosen coordinate system, set up
# corresponding reference metric and needed
# reference metric quantities
# The following function call sets up the reference metric
# and related quantities, including rescaling matrices ReDD,
# ReU, and hatted quantities.
rfm.reference_metric()
# Step 1.f: Define BSSN scalars & tensors (in terms of rescaled BSSN quantities)
import BSSN.BSSN_quantities as Bq
Bq.BSSN_basic_tensors()
Bq.betaU_derivs()
import BSSN.BSSN_RHSs as Brhs
Brhs.BSSN_RHSs()
The 1=log lapse condition is a member of the Bona-Masso family of lapse choices, which has the desirable property of singularity avoidance. As is common (e.g., see Campanelli et al (2005)), we make the replacement ∂t→∂0=∂t−βi∂i to ensure lapse characteristics advect with the shift. The bracketed term in the 1+log lapse condition below encodes the shift advection term:
∂0α=−2αK⟹∂tα=[βi∂iα]−2αK# Step 2.a: The 1+log lapse condition:
# \partial_t \alpha = \beta^i \alpha_{,i} - 2*\alpha*K
# First import expressions from BSSN_quantities
cf = Bq.cf
trK = Bq.trK
alpha = Bq.alpha
betaU = Bq.betaU
# Implement the 1+log lapse condition
if par.parval_from_str(thismodule+"::LapseEvolutionOption") == "OnePlusLog":
alpha_rhs = -2*alpha*trK
alpha_dupD = ixp.declarerank1("alpha_dupD")
for i in range(DIM):
alpha_rhs += betaU[i]*alpha_dupD[i]
As defined on Pg 2 of https://arxiv.org/pdf/gr-qc/9902024.pdf , this is given by
∂tα=∂te6ϕ=6e6ϕ∂tϕIf
cf=W=e−2ϕ,then
6e6ϕ∂tϕ=6W−3∂tϕ.However, ∂tϕ=−∂tcf/(2cf)
(as described above), so if cf
=W, then
∂tα=6e6ϕ∂tϕ=6W−3(−∂tW2W)=−3cf−4cf_rhs
Exercise to students: Implement Harmonic slicing for cf
=χ
# Step 2.b: Implement the harmonic slicing lapse condition
if par.parval_from_str(thismodule+"::LapseEvolutionOption") == "HarmonicSlicing":
if par.parval_from_str("BSSN.BSSN_quantities::EvolvedConformalFactor_cf") == "W":
alpha_rhs = -3*cf**(-4)*Brhs.cf_rhs
elif par.parval_from_str("BSSN.BSSN_quantities::EvolvedConformalFactor_cf") == "phi":
alpha_rhs = 6*sp.exp(6*cf)*Brhs.cf_rhs
else:
print("Error LapseEvolutionOption==HarmonicSlicing unsupported for EvolvedConformalFactor_cf!=(W or phi)")
sys.exit(1)
# Step 2.c: Frozen lapse
# \partial_t \alpha = 0
if par.parval_from_str(thismodule+"::LapseEvolutionOption") == "Frozen":
alpha_rhs = sp.sympify(0)
An alternative to the standard 1+log condition to be used with Static Trumpet initial data, the lapse is evolved according to a condition consistent with staticity, given by equation 67 in Ruchlin, Etienne, & Baumgarte (2018)
∂0α=−α(1−α)K⟹∂tα=[βi∂iα]−α(1−α)K# Step 2.d: Alternative 1+log lapse condition:
# \partial_t \alpha = \beta^i \alpha_{,i} -\alpha*(1 - \alpha)*K
# Implement the alternative 1+log lapse condition
if par.parval_from_str(thismodule+"::LapseEvolutionOption") == "OnePlusLogAlt":
alpha_rhs = -alpha*(1 - alpha)*trK
alpha_dupD = ixp.declarerank1("alpha_dupD")
for i in range(DIM):
alpha_rhs += betaU[i]*alpha_dupD[i]
The motivation behind Gamma-driving shift conditions are well documented in the book Numerical Relativity by Baumgarte & Shapiro.
Option 1: Non-Covariant, Second-Order Shift
We adopt the shifting (i.e., advecting) shift non-covariant, second-order shift condition: ∂0βi=Bi∂0Bi=34∂0ˉΛi−ηBi⟹∂tβi=[βj∂jβi]+Bi∂tBi=[βj∂jBi]+34∂0ˉΛi−ηBi,
# Step 3.a: Set \partial_t \beta^i
# First import expressions from BSSN_quantities
BU = Bq.BU
betU = Bq.betU
betaU_dupD = Bq.betaU_dupD
# Define needed quantities
beta_rhsU = ixp.zerorank1()
B_rhsU = ixp.zerorank1()
if par.parval_from_str(thismodule+"::ShiftEvolutionOption") == "GammaDriving2ndOrder_NoCovariant":
# Step 3.a.i: Compute right-hand side of beta^i
# * \partial_t \beta^i = \beta^j \beta^i_{,j} + B^i
for i in range(DIM):
beta_rhsU[i] += BU[i]
for j in range(DIM):
beta_rhsU[i] += betaU[j]*betaU_dupD[i][j]
# Compute right-hand side of B^i:
eta = par.Cparameters("REAL", thismodule, ["eta"],2.0)
# Step 3.a.ii: Compute right-hand side of B^i
# * \partial_t B^i = \beta^j B^i_{,j} + 3/4 * \partial_0 \Lambda^i - eta B^i
# Step 3.a.iii: Define BU_dupD, in terms of derivative of rescaled variable \bet^i
BU_dupD = ixp.zerorank2()
betU_dupD = ixp.declarerank2("betU_dupD","nosym")
for i in range(DIM):
for j in range(DIM):
BU_dupD[i][j] = betU_dupD[i][j]*rfm.ReU[i] + betU[i]*rfm.ReUdD[i][j]
# Step 3.a.iv: Compute \partial_0 \bar{\Lambda}^i = (\partial_t - \beta^i \partial_i) \bar{\Lambda}^j
Lambdabar_partial0 = ixp.zerorank1()
for i in range(DIM):
Lambdabar_partial0[i] = Brhs.Lambdabar_rhsU[i]
for i in range(DIM):
for j in range(DIM):
Lambdabar_partial0[j] += -betaU[i]*Brhs.LambdabarU_dupD[j][i]
# Step 3.a.v: Evaluate RHS of B^i:
for i in range(DIM):
B_rhsU[i] += sp.Rational(3,4)*Lambdabar_partial0[i] - eta*BU[i]
for j in range(DIM):
B_rhsU[i] += betaU[j]*BU_dupD[i][j]
This is Brown's suggested formulation (Eq. 20b; note that Eq. 20a is the same as our lapse condition, as ˉDjα=∂jα for scalar α): ∂tβi=[βjˉDjβi]+Bi
# Step 3.b: The right-hand side of the \partial_t \beta^i equation
if par.parval_from_str(thismodule+"::ShiftEvolutionOption") == "GammaDriving2ndOrder_Covariant":
# Step 3.b Option 2: \partial_t \beta^i = \left[\beta^j \bar{D}_j \beta^i\right] + B^{i}
# First we need GammabarUDD, defined in Bq.gammabar__inverse_and_derivs()
Bq.gammabar__inverse_and_derivs()
GammabarUDD = Bq.GammabarUDD
# Then compute right-hand side:
# Term 1: \beta^j \beta^i_{,j}
for i in range(DIM):
for j in range(DIM):
beta_rhsU[i] += betaU[j]*betaU_dupD[i][j]
# Term 2: \beta^j \bar{\Gamma}^i_{mj} \beta^m
for i in range(DIM):
for j in range(DIM):
for m in range(DIM):
beta_rhsU[i] += betaU[j]*GammabarUDD[i][m][j]*betaU[m]
# Term 3: B^i
for i in range(DIM):
beta_rhsU[i] += BU[i]
Based on the definition of covariant derivative, we have for vector Vi ˉDjVi=Vi,j+ˉΓimjVm,
if par.parval_from_str(thismodule+"::ShiftEvolutionOption") == "GammaDriving2ndOrder_Covariant":
# Step 3.c: Covariant option:
# \partial_t B^i = \beta^j \bar{D}_j B^i
# + \frac{3}{4} ( \partial_t \bar{\Lambda}^{i} - \beta^j \bar{D}_j \bar{\Lambda}^{i} )
# - \eta B^{i}
# = \beta^j B^i_{,j} + \beta^j \bar{\Gamma}^i_{mj} B^m
# + \frac{3}{4}[ \partial_t \bar{\Lambda}^{i}
# - \beta^j (\bar{\Lambda}^i_{,j} + \bar{\Gamma}^i_{mj} \bar{\Lambda}^m)]
# - \eta B^{i}
# Term 1, part a: First compute B^i_{,j} using upwinded derivative
BU_dupD = ixp.zerorank2()
betU_dupD = ixp.declarerank2("betU_dupD","nosym")
for i in range(DIM):
for j in range(DIM):
BU_dupD[i][j] = betU_dupD[i][j]*rfm.ReU[i] + betU[i]*rfm.ReUdD[i][j]
# Term 1: \beta^j B^i_{,j}
for i in range(DIM):
for j in range(DIM):
B_rhsU[i] += betaU[j]*BU_dupD[i][j]
# Term 2: \beta^j \bar{\Gamma}^i_{mj} B^m
for i in range(DIM):
for j in range(DIM):
for m in range(DIM):
B_rhsU[i] += betaU[j]*GammabarUDD[i][m][j]*BU[m]
# Term 3: \frac{3}{4}\partial_t \bar{\Lambda}^{i}
for i in range(DIM):
B_rhsU[i] += sp.Rational(3,4)*Brhs.Lambdabar_rhsU[i]
# Term 4: -\frac{3}{4}\beta^j \bar{\Lambda}^i_{,j}
for i in range(DIM):
for j in range(DIM):
B_rhsU[i] += -sp.Rational(3,4)*betaU[j]*Brhs.LambdabarU_dupD[i][j]
# Term 5: -\frac{3}{4}\beta^j \bar{\Gamma}^i_{mj} \bar{\Lambda}^m
for i in range(DIM):
for j in range(DIM):
for m in range(DIM):
B_rhsU[i] += -sp.Rational(3,4)*betaU[j]*GammabarUDD[i][m][j]*Bq.LambdabarU[m]
# Term 6: - \eta B^i
# eta is a free parameter; we declare it here:
eta = par.Cparameters("REAL", thismodule, ["eta"],2.0)
for i in range(DIM):
B_rhsU[i] += -eta*BU[i]
For the shift vector evolution equation, we desire only that the right-hand sides vanish analytically (although numerical error is expected to result in specious evolution). To this end, we adopt the nonadvecting Gamma-driver condition, given by equations 68a and 68b in Ruchlin, Etienne, & Baumgarte (2018)
∂tβi=Bi∂tBi=34∂tˉΛi−ηBi,# Step 3.c: Set \partial_t \beta^i
if par.parval_from_str(thismodule+"::ShiftEvolutionOption") == "NonAdvectingGammaDriving":
# Step 3.c.i: Compute right-hand side of beta^i
# * \partial_t \beta^i = B^i
for i in range(DIM):
beta_rhsU[i] += BU[i]
# Compute right-hand side of B^i:
eta = par.Cparameters("REAL", thismodule, ["eta"],2.0)
# Step 3.c.ii: Compute right-hand side of B^i
# * \partial_t B^i = 3/4 * \partial_t \Lambda^i - eta B^i
# Step 3.c.iii: Evaluate RHS of B^i:
for i in range(DIM):
B_rhsU[i] += sp.Rational(3,4)*Brhs.Lambdabar_rhsU[i] - eta*BU[i]
# Step 4: Rescale the BSSN gauge RHS quantities so that the evolved
# variables may remain smooth across coord singularities
vet_rhsU = ixp.zerorank1()
bet_rhsU = ixp.zerorank1()
for i in range(DIM):
vet_rhsU[i] = beta_rhsU[i] / rfm.ReU[i]
bet_rhsU[i] = B_rhsU[i] / rfm.ReU[i]
#print(str(Abar_rhsDD[2][2]).replace("**","^").replace("_","").replace("xx","x").replace("sin(x2)","Sin[x2]").replace("sin(2*x2)","Sin[2*x2]").replace("cos(x2)","Cos[x2]").replace("detgbaroverdetghat","detg"))
#print(str(Dbarbetacontraction).replace("**","^").replace("_","").replace("xx","x").replace("sin(x2)","Sin[x2]").replace("detgbaroverdetghat","detg"))
#print(betaU_dD)
#print(str(trK_rhs).replace("xx2","xx3").replace("xx1","xx2").replace("xx0","xx1").replace("**","^").replace("_","").replace("sin(xx2)","Sinx2").replace("xx","x").replace("sin(2*x2)","Sin2x2").replace("cos(x2)","Cosx2").replace("detgbaroverdetghat","detg"))
#print(str(bet_rhsU[0]).replace("xx2","xx3").replace("xx1","xx2").replace("xx0","xx1").replace("**","^").replace("_","").replace("sin(xx2)","Sinx2").replace("xx","x").replace("sin(2*x2)","Sin2x2").replace("cos(x2)","Cosx2").replace("detgbaroverdetghat","detg"))
BSSN.BSSN_gauge_RHSs
NRPy+ module [Back to top]¶Here, as a code validation check, we verify agreement in the SymPy expressions for the RHSs of the BSSN gauge equations between
By default, we analyze the RHSs in Spherical coordinates and with the covariant Gamma-driving second-order shift condition, though other coordinate systems & gauge conditions may be chosen.
# Step 5: We already have SymPy expressions for BSSN gauge RHS expressions
# in terms of other SymPy variables. Even if we reset the
# list of NRPy+ gridfunctions, these *SymPy* expressions for
# BSSN RHS variables *will remain unaffected*.
#
# Here, we will use the above-defined BSSN gauge RHS expressions
# to validate against the same expressions in the
# BSSN/BSSN_gauge_RHSs.py file, to ensure consistency between
# this tutorial and the module itself.
#
# Reset the list of gridfunctions, as registering a gridfunction
# twice will spawn an error.
gri.glb_gridfcs_list = []
# Step 5.a: Call the BSSN_gauge_RHSs() function from within the
# BSSN/BSSN_gauge_RHSs.py module,
# which should generate exactly the same expressions as above.
import BSSN.BSSN_gauge_RHSs as Bgrhs
par.set_parval_from_str("BSSN.BSSN_gauge_RHSs::ShiftEvolutionOption","GammaDriving2ndOrder_Covariant")
Bgrhs.BSSN_gauge_RHSs()
print("Consistency check between BSSN.BSSN_gauge_RHSs tutorial and NRPy+ module: ALL SHOULD BE ZERO.")
print("alpha_rhs - bssnrhs.alpha_rhs = " + str(alpha_rhs - Bgrhs.alpha_rhs))
for i in range(DIM):
print("vet_rhsU["+str(i)+"] - bssnrhs.vet_rhsU["+str(i)+"] = " + str(vet_rhsU[i] - Bgrhs.vet_rhsU[i]))
print("bet_rhsU["+str(i)+"] - bssnrhs.bet_rhsU["+str(i)+"] = " + str(bet_rhsU[i] - Bgrhs.bet_rhsU[i]))
Consistency check between BSSN.BSSN_gauge_RHSs tutorial and NRPy+ module: ALL SHOULD BE ZERO. alpha_rhs - bssnrhs.alpha_rhs = 0 vet_rhsU[0] - bssnrhs.vet_rhsU[0] = 0 bet_rhsU[0] - bssnrhs.bet_rhsU[0] = 0 vet_rhsU[1] - bssnrhs.vet_rhsU[1] = 0 bet_rhsU[1] - bssnrhs.bet_rhsU[1] = 0 vet_rhsU[2] - bssnrhs.vet_rhsU[2] = 0 bet_rhsU[2] - bssnrhs.bet_rhsU[2] = 0
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-BSSN_time_evolution-BSSN_gauge_RHSs.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-BSSN_time_evolution-BSSN_gauge_RHSs")
Created Tutorial-BSSN_time_evolution-BSSN_gauge_RHSs.tex, and compiled LaTeX file to PDF file Tutorial-BSSN_time_evolution-BSSN_gauge_RHSs.pdf