#!/usr/bin/env python
# coding: utf-8
#
#
#
# # Transforming BSSN Variables between Two Bases
# ## Author: Zach Etienne
#
#
# ## This notebook demonstrates the use of NRPy+ for the transformation of BSSN variables from one basis to another. The tutorial provides the procedure of Jacobian transformations on vectors and tensors to migrate rescaled BSSN variables from a source grid to a destination grid. This process is outlined, including code validation against the `BSSN.BSSN_basis_transforms` NRPy+ module.
#
# **Notebook Status:** Validated
#
# **Validation Notes:** This tutorial notebook has been confirmed to be self-consistent with its corresponding NRPy+ module, as documented [below](#code_validation). 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)**
#
# ### NRPy+ Source Code for this module: [BSSN/BSSN_basis_transforms.py](../edit/BSSN/BSSN_basis_transforms.py)
#
#
#
# ## Introduction:
#
# 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:
#
# 1. Un-rescale all BSSN variables on source grid
# 1. Transform source grid basis to Cartesian, using center of source grid as origin
# 1. Basis transform from Cartesian to destination basis at point on destination grid (`xx0,xx1,xx2`)${}_{\rm dst}$
# 1. Compute rescaled BSSN quantities in destination basis
#
#
# # Table of Contents
# $$\label{toc}$$
#
# This notebook is organized as follows
#
# 1. [Step 1](#initializenrpy): Initialize core Python/NRPy+ modules and BSSN variables; Unrescale all BSSN variables on source grid
# 1. [Step 2](#srctocart): Transform source grid basis to Cartesian, using center of source grid as origin
# 1. [Step 3](#carttodst): Basis transform from Cartesian to destination grid basis at point on destination grid (`xx0,xx1,xx2`)${}_{\rm dst}$
# 1. [Step 4](#rescaleindstbasis): Compute rescaled BSSN quantities in destination basis
# 1. [Step 5](#code_validation): Code Validation Tests
# 1. [Step 5.a](#nrpy_module_validate): Confirm identical output to `BSSN.BSSN_basis_transforms` NRPy+ module
# 1. [Step 5.b](#confirm_same_basis_identity): Confirm that if same basis chosen for input and output, at the same points `(xx0,xx1,xx2)`, the output is identical to the input
# 1. [Step 6](#latex_pdf_output): Output this notebook to $\LaTeX$-formatted PDF file
#
#
# # Step 1: Initialize core Python/NRPy+ modules and BSSN variables; Unrescale all BSSN variables \[Back to [top](#toc)\]
# $$\label{initializenrpy}$$
#
# 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](Tutorial-BSSN_quantities.ipynb).
# In[1]:
# 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]
#
#
# # Step 2: Transform source grid basis to Cartesian, using center of source grid as origin \[Back to [top](#toc)\]
# $$\label{srctocart}$$
#
# Within [`reference_metric.py`](../edit/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:
#
# \begin{align}
# \bar{\Lambda}^i_{\rm Cart} &= \frac{\partial x^i_{\rm Cart}}{\partial x^\ell_{\rm src}} \bar{\Lambda}^\ell_{\rm src}\\
# \beta^i_{\rm Cart} &= \frac{\partial x^i_{\rm Cart}}{\partial x^\ell_{\rm src}} \beta^\ell_{\rm src}\\
# B^i_{\rm Cart} &= \frac{\partial x^i_{\rm Cart}}{\partial x^\ell_{\rm src}} B^\ell_{\rm src}\\
# \bar{\gamma}^{\rm Cart}_{ij} &=
# \frac{\partial x^\ell_{\rm src}}{\partial x^i_{\rm Cart}}
# \frac{\partial x^m_{\rm src}}{\partial x^j_{\rm Cart}} \bar{\gamma}^{\rm src}_{\ell m}\\
# \end{align}
#
# The transformation for vectors is provided by the [`reference_metric.py`](../edit/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.
# In[2]:
# 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])
#
#
# # Step 3: Basis transform from Cartesian to destination grid basis at point on destination grid (`xx0,xx1,xx2`)${}_{\rm dst}$ \[Back to [top](#toc)\]
# $$\label{carttodst}$$
#
# 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:
#
# \begin{align}
# \bar{\Lambda}^i_{\rm dst} &= \frac{\partial x^i_{\rm dst}}{\partial x^\ell_{\rm Cart}} \bar{\Lambda}^\ell_{\rm Cart}\\
# \beta^i_{\rm dst} &= \frac{\partial x^i_{\rm dst}}{\partial x^\ell_{\rm Cart}} \beta^\ell_{\rm Cart}\\
# B^i_{\rm dst} &= \frac{\partial x^i_{\rm dst}}{\partial x^\ell_{\rm Cart}} B^\ell_{\rm Cart}\\
# \bar{\gamma}^{\rm dst}_{ij} &=
# \frac{\partial x^\ell_{\rm Cart}}{\partial x^i_{\rm dst}}
# \frac{\partial x^m_{\rm Cart}}{\partial x^j_{\rm dst}} \bar{\gamma}^{\rm Cart}_{\ell m}\\
# \end{align}
# In[3]:
# 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)
#
#
# # Step 4: Compute rescaled BSSN quantities in destination basis \[Back to [top](#toc)\]
# $$\label{rescaleindstbasis}$$
#
# Rescaling is documented in the [BSSN quantities tutorial notebook](Tutorial-BSSN_quantities.ipynb).
# In[4]:
# 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])
#
#
# # Step 5: Code Validation Tests \[Back to [top](#toc)\]
# $$\label{code_validation}$$
#
#
#
# ## Step 5.a: Confirm identical output to `BSSN.BSSN_basis_transforms` NRPy+ module \[Back to [top](#toc)\]
# $$\label{nrpy_module_validate}$$
#
# Here, as a code validation check, we verify agreement in the SymPy expressions for BrillLindquist initial data between
# 1. this tutorial and
# 2. the NRPy+ [BSSN.BSSN_basis_transforms](../edit/BSSN/BSSN_basis_transforms.py) module.
#
# By default, we analyze these expressions in Spherical coordinates, though other coordinate systems may be chosen.
# In[5]:
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)
#
#
# ## Step 5.b: Confirm that if same basis chosen for input and output, at the same points `(xx0,xx1,xx2)`, the output is identical to the input \[Back to [top](#toc)\]
# $$\label{confirm_same_basis_identity}$$
#
# 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.
# In[6]:
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!")
#
#
# # Step 6: 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-BSSN-basis_transforms.pdf](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.)
# In[1]:
import cmdline_helper as cmd # NRPy+: Multi-platform Python command-line interface
cmd.output_Jupyter_notebook_to_LaTeXed_PDF("Tutorial-BSSN-basis_transforms")