Notebook Status: Validated
Validation Notes: This tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented below. In addition, if the same basis & grid are used for both source and destination, source and destination tensors are identical. Additional validation tests may have been performed, but are as yet, undocumented. (TODO)
Given the rescaled BSSN variables:
$$\left\{h_{i j},a_{i j},\phi, K, \lambda^{i}, \alpha, \mathcal{V}^i, \mathcal{B}^i\right\},$$we perform needed Jacobian transformations to all vectors and tensors to migrate to another basis. This is a four-step process:
xx0,xx1,xx2
)${}_{\rm dst}$This notebook is organized as follows
xx0,xx1,xx2
)${}_{\rm dst}$Here we declare BSSN quantities on the source grid $$\left\{h_{i j},a_{i j},\lambda^{i}, \mathcal{V}^i, \mathcal{B}^i\right\}$$
Unrescaling is documented in the BSSN quantities tutorial notebook.
# Step P1: Import needed NRPy+ core modules:
import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
import reference_metric as rfm # NRPy+: Reference metric support
import NRPy_param_funcs as par # NRPy+: Parameter interface
# Step P2: Declare inputs
# Input BSSN variables on source ("src") grid:
src_hDD = ixp.declarerank2("src_hDD","sym01")
src_aDD = ixp.declarerank2("src_aDD","sym01")
src_lambdaU = ixp.declarerank1("src_lambdaU")
src_vetU = ixp.declarerank1("src_vetU")
src_betU = ixp.declarerank1("src_betU")
# Source ("src") grid basis and coordinate point (xx0,xx1,xx2)_{src} = src_xx[i]
src_basis = "SinhCylindrical"
src_xx = ixp.declarerank1("src_xx")
# Destination ("dst") grid basis and coordinate point (xx0,xx1,xx2)_{dst} = dst_xx[i]
dst_basis = "SinhSpherical"
dst_xx = ixp.declarerank1("dst_xx")
# Step 1: Unrescale all BSSN variables
par.set_parval_from_str("reference_metric::CoordSystem",src_basis)
rfm.reference_metric()
# STOLEN FROM BSSN/BSSN_quantities.py:
# Step 1.a: gammabarDD and AbarDD:
src_gammabarDD = ixp.zerorank2()
src_AbarDD = ixp.zerorank2()
for i in range(3):
for j in range(3):
# gammabar_{ij} = h_{ij}*ReDD[i][j] + gammahat_{ij}
src_gammabarDD[i][j] = src_hDD[i][j] * rfm.ReDD[i][j] + rfm.ghatDD[i][j]
# Abar_{ij} = a_{ij}*ReDD[i][j]
src_AbarDD[i][j] = src_aDD[i][j] * rfm.ReDD[i][j]
# Step 1.b: LambdabarU, betaU, and BU:
src_LambdabarU = ixp.zerorank1()
src_betaU = ixp.zerorank1()
src_BU = ixp.zerorank1()
for i in range(3):
src_LambdabarU[i] = src_lambdaU[i] * rfm.ReU[i]
src_betaU[i] = src_vetU[i] * rfm.ReU[i]
src_BU[i] = src_betU[i] * rfm.ReU[i]
Within reference_metric.py
, the compute_Jacobian_and_inverseJacobian_tofrom_Cartesian()
function defines Jacobians relative to the center of the source (reference metric) grid, at a point $x^j_{\rm src}=$(xx0,xx1,xx2
)${}_{\rm src}$ on the source grid:
$$
{\rm Jac\_dUCart\_dDsrcUD[i][j]} = \frac{\partial x^i_{\rm Cart}}{\partial x^j_{\rm src}},
$$
via exact differentiation (courtesy SymPy), and the inverse Jacobian $$ {\rm Jac\_dUsrc\_dDCartUD[i][j]} = \frac{\partial x^i_{\rm src}}{\partial x^j_{\rm Cart}}, $$
using NRPy+'s generic_matrix_inverter3x3()
function.
In terms of these, the transformation of BSSN tensors from "reference_metric::CoordSystem"
coordinates to Cartesian may be written:
The transformation for vectors is provided by the reference_metric.py
function basis_transform_vectorU_from_rfmbasis_to_Cartesian(Jac_dUCart_dDrfmUD, src_vectorU)
, and the transformation for rank-2 covariant tensors is provided by basis_transform_tensorDD_from_rfmbasis_to_Cartesian(Jac_dUrfm_dDCartUD, src_tensorDD)
, also found within reference_metric.py
.
After performing the basis transformation to Cartesian, we relabel (xx0,xx1,xx2)
by (src_xx0,src_xx1,src_xx2)
to avoid ambiguity.
# Step 2: Transform source grid basis to Cartesian, using center of source grid as origin
# Step 2.a: Construct Jacobian & Inverse Jacobians:
Jac_dUCart_dDrfmUD,Jac_dUrfm_dDCartUD = rfm.compute_Jacobian_and_inverseJacobian_tofrom_Cartesian()
# Step 2.b: Convert basis of all BSSN *vectors* to Cartesian
CartLambdabarU = rfm.basis_transform_vectorU_from_rfmbasis_to_Cartesian(Jac_dUCart_dDrfmUD, src_LambdabarU)
CartbetaU = rfm.basis_transform_vectorU_from_rfmbasis_to_Cartesian(Jac_dUCart_dDrfmUD, src_betaU)
CartBU = rfm.basis_transform_vectorU_from_rfmbasis_to_Cartesian(Jac_dUCart_dDrfmUD, src_BU)
# Step 2.c: Convert basis of all BSSN *tensors* to Cartesian
CartgammabarDD = rfm.basis_transform_tensorDD_from_rfmbasis_to_Cartesian(Jac_dUrfm_dDCartUD, src_gammabarDD)
CartAbarDD = rfm.basis_transform_tensorDD_from_rfmbasis_to_Cartesian(Jac_dUrfm_dDCartUD, src_AbarDD)
# Step 2.d: All BSSN tensor/vector quantities are written in terms of
# rescaled quantities and (xx0,xx1,xx2) on the SOURCE grid.
# To avoid confusion with (xx0,xx1,xx2) on the DESTINATION grid,
# we replace (xx0,xx1,xx2) with (src_xx0,src_xx1,src_xx2) here:
for i in range(3):
for k in range(3):
CartLambdabarU[i] = CartLambdabarU[i].subs(rfm.xx[k],src_xx[k])
CartbetaU[i] = CartbetaU[i].subs(rfm.xx[k],src_xx[k])
CartBU[i] = CartBU[i].subs(rfm.xx[k],src_xx[k])
for i in range(3):
for j in range(3):
for k in range(3):
CartgammabarDD[i][j] = CartgammabarDD[i][j].subs(rfm.xx[k],src_xx[k])
CartAbarDD[i][j] = CartAbarDD[i][j].subs(rfm.xx[k],src_xx[k])
xx0,xx1,xx2
)${}_{\rm dst}$ [Back to top]¶We define Jacobians relative to the center of the destination grid, at a point $x^j_{\rm dst}=$(xx0,xx1,xx2
)${}_{\rm dst}$ on the destination grid:
$$
{\rm Jac\_dUCart\_dDdstUD[i][j]} = \frac{\partial x^i_{\rm Cart}}{\partial x^j_{\rm dst}},
$$
via exact differentiation (courtesy SymPy), and the inverse Jacobian $$ {\rm Jac\_dUdst\_dDCartUD[i][j]} = \frac{\partial x^i_{\rm dst}}{\partial x^j_{\rm Cart}}, $$
using NRPy+'s generic_matrix_inverter3x3()
function. In terms of these, the transformation of BSSN tensors from Cartesian to the destination grid's "reference_metric::CoordSystem"
coordinates may be written:
# Step 3: Transform BSSN tensors in Cartesian basis to destination grid basis, using center of dest. grid as origin
# Step 3.a: Set up destination grid coordinate system
par.set_parval_from_str("reference_metric::CoordSystem",dst_basis)
rfm.reference_metric()
# Step 3.b: Next construct Jacobian and inverse Jacobian matrices:
Jac_dUCart_dDrfmUD,Jac_dUrfm_dDCartUD = rfm.compute_Jacobian_and_inverseJacobian_tofrom_Cartesian()
# Step 3.c: Convert basis of all BSSN *vectors* from Cartesian to destination basis
dst_LambdabarU = rfm.basis_transform_vectorU_from_Cartesian_to_rfmbasis(Jac_dUrfm_dDCartUD, CartLambdabarU)
dst_betaU = rfm.basis_transform_vectorU_from_Cartesian_to_rfmbasis(Jac_dUrfm_dDCartUD, CartbetaU)
dst_BU = rfm.basis_transform_vectorU_from_Cartesian_to_rfmbasis(Jac_dUrfm_dDCartUD, CartBU)
# Step 3.d: Convert basis of all BSSN *tensors* from Cartesian to destination basis
dst_gammabarDD = rfm.basis_transform_tensorDD_from_Cartesian_to_rfmbasis(Jac_dUCart_dDrfmUD, CartgammabarDD)
dst_AbarDD = rfm.basis_transform_tensorDD_from_Cartesian_to_rfmbasis(Jac_dUCart_dDrfmUD, CartAbarDD)
Rescaling is documented in the BSSN quantities tutorial notebook.
# Step 4: Rescale all BSSN quantities
# BASED ON BSSN/BSSN_quantities.py:
# Step 4.a: hDD and aDD:
dst_hDD = ixp.zerorank2()
dst_aDD = ixp.zerorank2()
for i in range(3):
for j in range(3):
# gammabar_{ij} = h_{ij}*ReDD[i][j] + gammahat_{ij}
# ==> h_{ij} = (gammabar_{ij} - gammahat_{ij}) / ReDD[i][j]
dst_hDD[i][j] = (dst_gammabarDD[i][j] - rfm.ghatDD[i][j]) / rfm.ReDD[i][j]
# Abar_{ij} = a_{ij}*ReDD[i][j]
# ==> a_{ij} = Abar_{ij}/ReDD[i][j]
dst_aDD[i][j] = dst_AbarDD[i][j] / rfm.ReDD[i][j]
# Step 4.b: lambdaU, vetU, and betU:
dst_lambdaU = ixp.zerorank1()
dst_vetU = ixp.zerorank1()
dst_betU = ixp.zerorank1()
for i in range(3):
# Lambdabar^i = \lambda^i * ReU[i]
# ==> \lambda^i = Lambdabar^i / ReU[i]
dst_lambdaU[i] = dst_LambdabarU[i] / rfm.ReU[i]
dst_vetU[i] = dst_betaU[i] / rfm.ReU[i]
dst_betU[i] = dst_BU[i] / rfm.ReU[i]
# Step 4.c: All BSSN tensor/vector quantities are written in terms of
# rescaled quantities and (xx0,xx1,xx2) on the DESTINATION grid.
# To avoid confusion with (xx0,xx1,xx2) on the SOURCE grid,
# we replace (xx0,xx1,xx2) with (dst_xx0,dst_xx1,dst_xx2) here:
for i in range(3):
for k in range(3):
dst_lambdaU[i] = dst_lambdaU[i].subs(rfm.xx[k],dst_xx[k])
dst_vetU[i] = dst_vetU[i].subs(rfm.xx[k],dst_xx[k])
dst_betU[i] = dst_betU[i].subs(rfm.xx[k],dst_xx[k])
for i in range(3):
for j in range(3):
for k in range(3):
dst_hDD[i][j] = dst_hDD[i][j].subs(rfm.xx[k],dst_xx[k])
dst_aDD[i][j] = dst_aDD[i][j].subs(rfm.xx[k],dst_xx[k])
BSSN.BSSN_basis_transforms
NRPy+ module [Back to top]¶Here, as a code validation check, we verify agreement in the SymPy expressions for BrillLindquist initial data between
By default, we analyze these expressions in Spherical coordinates, though other coordinate systems may be chosen.
import BSSN.BSSN_basis_transforms as Bbt
# Set up expressions from separate BSSN.BSSN_basis_transforms Python module
Bbt.BSSN_basis_transform(src_basis,src_xx, dst_basis,dst_xx,
src_hDD,src_aDD,src_lambdaU,src_vetU,src_betU)
# Define functions for comparisons between this Jupyter notebook & associated Python module
def comp_func(expr1,expr2,basename,prefixname2="Bq."):
if str(expr1-expr2)!="0":
print(basename+" - "+prefixname2+basename+" = "+ str(expr1-expr2))
return 1
return 0
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 = []
# Set up expression lists for comparisons between this Jupyter notebook & Python module
for i in range(3):
namecheck_list.extend([gfnm("dst_lambdaU",i),gfnm("dst_vetU",i),gfnm("dst_betU",i)])
exprcheck_list.extend([Bbt.dst_lambdaU[i],Bbt.dst_vetU[i],Bbt.dst_betU[i]])
expr_list.extend([dst_lambdaU[i],dst_vetU[i],dst_betU[i]])
for j in range(3):
namecheck_list.extend([gfnm("dst_hDD",i,j),gfnm("dst_aDD",i,j)])
exprcheck_list.extend([Bbt.dst_hDD[i][j],Bbt.dst_aDD[i][j]])
expr_list.extend([dst_hDD[i][j],dst_aDD[i][j]])
# Compare all SymPy expressions
num_failures=0
for i in range(len(expr_list)):
num_failures += comp_func(expr_list[i],exprcheck_list[i],namecheck_list[i])
if num_failures == 0:
print("ALL TESTS PASSED!")
else:
print(str(num_failures) + " FAILURES.")
sys.exit(1)
ALL TESTS PASSED!
(xx0,xx1,xx2)
, the output is identical to the input [Back to top]¶Next we verify that if the same basis is chosen for input and output, at the same points (xx0,xx1,xx2)
, the results are identical.
import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends
Bbt.BSSN_basis_transform("Spherical",src_xx, "Spherical",src_xx,
src_hDD,src_aDD,src_lambdaU,src_vetU,src_betU)
all_passed = True
for i in range(3):
if sp.simplify(Bbt.dst_lambdaU[i])-src_lambdaU[i] != 0:
print("Error in lambdaU["+str(i)+"]: "+str(sp.simplify(Bbt.dst_lambdaU[i])-src_lambdaU[i])+" != 0")
all_passed = False
for j in range(3):
if sp.simplify(Bbt.dst_hDD[i][j])-src_hDD[i][j] != 0:
print("Error in hDD["+str(i)+"]["+str(j)+"]: "+sp.simplify(Bbt.dst_hDD[i][j])-src_hDD[i][j]+" != 0")
all_passed = False
if all_passed:
print("ALL TESTS PASSED!")
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-BSSN-basis_transforms.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-basis_transforms")
Created Tutorial-BSSN-basis_transforms.tex, and compiled LaTeX file to PDF file Tutorial-BSSN-basis_transforms.pdf