Notebook Status: Validated
Validation Notes: All expressions generated in this here have been validated against the VacuumMaxwell_Flat_Evol_Curvilinear_rescaled module, as well as the Maxwell/VacuumMaxwell_Flat_Evol_Cartesian module when setting the coordinate system to Cartesian.
# Import needed Python modules
import NRPy_param_funcs as par # NRPy+: Parameter interface
import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
import reference_metric as rfm # NRPy+: Reference metric support
import grid as gri # NRPy+: Functions having to do with numerical grids
import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends
# Set the spatial dimension parameter to 3.
par.set_parval_from_str("grid::DIM", 3)
DIM = par.parval_from_str("grid::DIM")
# Set coordinate system
# Choices are: Spherical, SinhSpherical, SinhSphericalv2, Cylindrical, SinhCylindrical,
# SymTP, SinhSymTP
CoordSystem = "Spherical"
par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem)
# Set reference metric related quantities
rfm.reference_metric()
(Following discussion reproduced from Tutorial-VacuumMaxwell_formulation_Curvilinear)
Consider an arbitrary vector Λi, with smooth (continous) Cartesian components Λx, Λy, and Λz. Transforming Λi to, e.g. spherical coordinates, introduces terms that spoil the smoothness of Λi;
Λϕ=1rsinθ×[smooth part].Evolving Λϕ will introduce instabilities along the z-axis. To avoid this, we instead evolve the rescaled quantity λi, defined by
ˉΛi=λiscalefactor[i].where we use the Hadamard product of matrices, and no sums are implied by the repeated indices.
Thus, we evolve the smoothed variable λi, via
λi=ˉΛiscalefactor[i].Within Nrpy+, ReU[i] = 1/scalefactor[i], giving
λi=ˉΛiReU[i].We now define the rescaled quantities ai and ei and rewrite our formulation of Maxwell's equations in curvilinear coordinates;
ai=AiReU[i],ei=EiReU[i],Taking a time derivative on both sides,
∂tai=∂tAiReU[i]=−Ei−ˆgij∂jφReU[i]=−ei−ˆgij∂jφReU[i],∂tei=∂tEiReU[i]=ˆgij∂jΓ−ˆgjkˆ∇j(ˆ∇kAi)ReU[i]=ˆgij∂jΓReU[i]−ˆgjkˆ∇j(ˆ∇kaiReU[i])ReU[i].Given that
∂tEi=ˆgij∂jΓ⏟Term 1−ˆγjk(Ai,kj⏟Term 2+ˆΓimk,jAm+ˆΓimkAm,j+ˆΓidjAd,k−ˆΓdkjAi,d⏟Term 3+ˆΓidjˆΓdmkAm−ˆΓdkjˆΓimdAm⏟Term 4),we can make the following replacements within the above, in terms of NRPy+ code;
Ai=AU[i]→aU[i] * rfm.ReU[i]∂jAi=AUdD[i][j]→aU_dD[i][j] * rfm.ReU[i]+aU[i] * rfm.ReUdD[i][j]∂k∂jAi=AUdDD[i][j][k]→aU_dDD[i][j][k] * rfm.ReU[i]+aU_dDD[i][j] * rfm.ReUdD[i][k]+aU_dD[i][k] * rfm.ReUdD[i][j]+aU[i] * rfm.ReUdDD[i][j][k]The remainder of Maxwell's equations are unchanged;
∂tΓ=−ˆgij(∂i∂jφ−ˆΓkji∂kφ),subject to constraints
G≡Γ−∂iAi+ˆΓijiAj=0,C≡∂iEi+ˆΓijiEj=0.# Register gridfunctions that are needed as input.
# Declare the rank-1 indexed expressions e^{i}, e^{i},
# and \partial^{i} \psi, that are to be evolved in time.
# Derivative variables like these must have an underscore
# in them, so the finite difference module can parse
# the variable name properly.
# e^i
eU = ixp.register_gridfunctions_for_single_rank1("EVOL", "eU")
# \partial_k ( E^i ) --> rank two tensor
eU_dD = ixp.declarerank2("eU_dD", "nosym")
# a^i
aU = ixp.register_gridfunctions_for_single_rank1("EVOL", "aU")
# \partial_k ( a^i ) --> rank two tensor
aU_dD = ixp.declarerank2("aU_dD", "nosym")
# \partial_k partial_m ( a^i ) --> rank three tensor
aU_dDD = ixp.declarerank3("aU_dDD", "sym12")
# \psi is a scalar function that is time evolved
psi = gri.register_gridfunctions("EVOL", ["psi"])
# \Gamma is a scalar function that is time evolved
Gamma = gri.register_gridfunctions("EVOL", ["Gamma"])
# \partial_i \psi
psi_dD = ixp.declarerank1("psi_dD")
# \partial_i \Gamma
Gamma_dD = ixp.declarerank1("Gamma_dD")
# partial_i \partial_j \psi
psi_dDD = ixp.declarerank2("psi_dDD", "sym01")
ghatUU = rfm.ghatUU
GammahatUDD = rfm.GammahatUDD
GammahatUDDdD = rfm.GammahatUDDdD
ReU = rfm.ReU
ReUdD = rfm.ReUdD
ReUdDD = rfm.ReUdDD
# \partial_t a^i = -e^i - \frac{\hat{g}^{ij}\partial_j \varphi}{\text{ReU}[i]}
arhsU = ixp.zerorank1()
for i in range(DIM):
arhsU[i] -= eU[i]
for j in range(DIM):
arhsU[i] -= (ghatUU[i][j]*psi_dD[j])/ReU[i]
Given that
∂tEi=ˆgij∂jΓ⏟Term 1−ˆγjk(Ai,kj⏟Term 2+ˆΓimk,jAm+ˆΓimkAm,j+ˆΓidjAd,k−ˆΓdkjAi,d⏟Term 3+ˆΓidjˆΓdmkAm−ˆΓdkjˆΓimdAm⏟Term 4),we can make the following replacements within the above, in terms of NRPy+ code;
Ai=AU[i]→aU[i] * rfm.ReU[i]∂jAi=AUdD[i][j]→aU_dD[i][j] * rfm.ReU[i]+aU[i] * rfm.ReUdD[i][j]∂k∂jAi=AUdDD[i][j][k]→aU_dDD[i][j][k] * rfm.ReU[i]+aU_dD[i][j] * rfm.ReUdD[i][k]+aU_dD[i][k] * rfm.ReUdD[i][j]+aU[i] * rfm.ReUdDD[i][j][k]# A^i
AU = ixp.zerorank1()
# \partial_k ( A^i ) --> rank two tensor
AU_dD = ixp.zerorank2()
# \partial_k partial_m ( A^i ) --> rank three tensor
AU_dDD = ixp.zerorank3()
for i in range(DIM):
AU[i] = aU[i]*ReU[i]
for j in range(DIM):
AU_dD[i][j] = aU_dD[i][j]*ReU[i] + aU[i]*ReUdD[i][j]
for k in range(DIM):
AU_dDD[i][j][k] = aU_dDD[i][j][k]*ReU[i] + aU_dD[i][j]*ReUdD[i][k] +\
aU_dD[i][k]*ReUdD[i][j] + aU[i]*ReUdDD[i][j][k]
# Term 1 = \hat{g}^{ij}\partial_j \Gamma
Term1U = ixp.zerorank1()
for i in range(DIM):
for j in range(DIM):
Term1U[i] += ghatUU[i][j]*Gamma_dD[j]
# Term 2: A^i_{,kj}
Term2UDD = ixp.zerorank3()
for i in range(DIM):
for j in range(DIM):
for k in range(DIM):
Term2UDD[i][j][k] += AU_dDD[i][k][j]
# Term 3: \hat{\Gamma}^i_{mk,j} A^m + \hat{\Gamma}^i_{mk} A^m_{,j}
# + \hat{\Gamma}^i_{dj}A^d_{,k} - \hat{\Gamma}^d_{kj} A^i_{,d}
Term3UDD = ixp.zerorank3()
for i in range(DIM):
for j in range(DIM):
for k in range(DIM):
for m in range(DIM):
Term3UDD[i][j][k] += GammahatUDDdD[i][m][k][j]*AU[m] \
+ GammahatUDD[i][m][k]*AU_dD[m][j] \
+ GammahatUDD[i][m][j]*AU_dD[m][k] \
- GammahatUDD[m][k][j]*AU_dD[i][m]
# Term 4: \hat{\Gamma}^i_{dj}\hat{\Gamma}^d_{mk} A^m -
# \hat{\Gamma}^d_{kj} \hat{\Gamma}^i_{md} A^m
Term4UDD = ixp.zerorank3()
for i in range(DIM):
for j in range(DIM):
for k in range(DIM):
for m in range(DIM):
for d in range(DIM):
Term4UDD[i][j][k] += ( GammahatUDD[i][d][j]*GammahatUDD[d][m][k] \
-GammahatUDD[d][k][j]*GammahatUDD[i][m][d])*AU[m]
Finally, we build up the RHS of Ei,
∂tEi=ˆgij∂jΓ⏟Term 1−ˆγjk(Ai,kj⏟Term 2+ˆΓimk,jAm+ˆΓimkAm,j+ˆΓidjAd,k−ˆΓdkjAi,d⏟Term 3+ˆΓidjˆΓdmkAm−ˆΓdkjˆΓimdAm⏟Term 4),and divide through by ReU[i] to get ei.
# \partial_t E^i = \hat{g}^{ij}\partial_j \Gamma - \hat{\gamma}^{jk}*
# (A^i_{,kj}
# + \hat{\Gamma}^i_{mk,j} A^m + \hat{\Gamma}^i_{mk} A^m_{,j}
# + \hat{\Gamma}^i_{dj} A^d_{,k} - \hat{\Gamma}^d_{kj} A^i_{,d}
# + \hat{\Gamma}^i_{dj}\hat{\Gamma}^d_{mk} A^m
# - \hat{\Gamma}^d_{kj} \hat{\Gamma}^i_{md} A^m)
ErhsU = ixp.zerorank1()
for i in range(DIM):
ErhsU[i] += Term1U[i]
for j in range(DIM):
for k in range(DIM):
ErhsU[i] -= ghatUU[j][k]*(Term2UDD[i][j][k] + Term3UDD[i][j][k] + Term4UDD[i][j][k])
erhsU = ixp.zerorank1()
for i in range(DIM):
erhsU[i] = ErhsU[i]/ReU[i]
# \partial_t \Gamma = -\hat{g}^{ij} (\partial_i \partial_j \varphi -
# \hat{\Gamma}^k_{ji} \partial_k \varphi)
Gamma_rhs = sp.sympify(0)
for i in range(DIM):
for j in range(DIM):
Gamma_rhs -= ghatUU[i][j]*psi_dDD[i][j]
for k in range(DIM):
Gamma_rhs += ghatUU[i][j]*GammahatUDD[k][j][i]*psi_dD[k]
# \partial_t \varphi = -\Gamma
psi_rhs = -Gamma
Constraints:
G≡Γ−∂iAi+ˆΓijiAj,C≡∂iEi+ˆΓijiEj.# \mathcal{G} \equiv \Gamma - \partial_i A^i + \hat{\Gamma}^i_{ji} A^j
G = Gamma
for i in range(DIM):
G -= AU_dD[i][i]
for j in range(DIM):
G += GammahatUDD[i][j][i]*AU[j]
# E^i
EU = ixp.zerorank1()
# \partial_k ( A^i ) --> rank two tensor
EU_dD = ixp.zerorank2()
for i in range(DIM):
EU[i] = eU[i]*ReU[i]
for j in range(DIM):
EU_dD[i][j] = eU_dD[i][j]*ReU[i] + eU[i]*ReUdD[i][j]
C = sp.sympify(0)
for i in range(DIM):
C += EU_dD[i][i]
for j in range(DIM):
C += GammahatUDD[i][j][i]*EU[j]
Here we convert Ai and Ei to the Cartesian basis, to make convergence tests within Tutorial-Start_to_Finish-Solving_Maxwells_Equations_in_Vacuum-Curvilinear easier. Specifically, we will use the coordinate transformation definitions provided by reference_metric.py to build the Jacobian:
∂xiCart∂xjOrig,where xiCart∈{x,y,z}. We then apply it to Ai and Ei to transform into Cartesian coordinates, via
AiCart=∂xiCart∂xjOrigAjOrig.def Convert_to_Cartesian_basis(VU):
# Coordinate transformation from original basis to Cartesian
rfm.reference_metric()
VU_Cart = ixp.zerorank1()
Jac_dxCartU_dxOrigD = ixp.zerorank2()
for i in range(DIM):
for j in range(DIM):
Jac_dxCartU_dxOrigD[i][j] = sp.diff(rfm.xx_to_Cart[i], rfm.xx[j])
for i in range(DIM):
for j in range(DIM):
VU_Cart[i] += Jac_dxCartU_dxOrigD[i][j]*VU[j]
return VU_Cart
AU_Cart = Convert_to_Cartesian_basis(AU)
EU_Cart = Convert_to_Cartesian_basis(EU)
Here, as a code validation check, we verify agreement in the SymPy expressions for the RHSs of Maxwell's equations between
# Reset the list of gridfunctions, as registering a gridfunction
# twice will spawn an error.
gri.glb_gridfcs_list = []
# Call the VacuumMaxwellRHSs_rescaled() function from within the
# Maxwell/VacuumMaxwell_Flat_Evol_Curvilinear_rescaled.py module,
# which should do exactly the same as the above.
# Set which system to use, which are defined in Maxwell/InitialData.py
par.initialize_param(par.glb_param("char","Maxwell.InitialData","System_to_use","System_II"))
import Maxwell.VacuumMaxwell_Flat_Evol_Curvilinear_rescaled as mwevol
mwevol.VacuumMaxwellRHSs_rescaled()
print("Consistency check between Tutorial-VacuumMaxwell_Curvilinear_RHS-Rescaling tutorial and NRPy+ module: ALL SHOULD BE ZERO.")
print("C - mwevol.C = " + str(C - mwevol.C))
print("G - mwevol.G = " + str(G - mwevol.G))
print("psi_rhs - mwevol.psi_rhs = " + str(psi_rhs - mwevol.psi_rhs))
print("Gamma_rhs - mwevol.Gamma_rhs = " + str(Gamma_rhs - mwevol.Gamma_rhs))
for i in range(DIM):
print("arhsU["+str(i)+"] - mwevol.arhsU["+str(i)+"] = " + str(arhsU[i] - mwevol.arhsU[i]))
print("erhsU["+str(i)+"] - mwevol.erhsU["+str(i)+"] = " + str(erhsU[i] - mwevol.erhsU[i]))
print("AU_Cart["+str(i)+"] - mwevol.AU_Cart["+str(i)+"] = " + str(AU_Cart[i] - mwevol.AU_Cart[i]))
print("EU_Cart["+str(i)+"] - mwevol.EU_Cart["+str(i)+"] = " + str(EU_Cart[i] - mwevol.EU_Cart[i]))
Consistency check between Tutorial-VacuumMaxwell_Curvilinear_RHS-Rescaling tutorial and NRPy+ module: ALL SHOULD BE ZERO. C - mwevol.C = 0 G - mwevol.G = 0 psi_rhs - mwevol.psi_rhs = 0 Gamma_rhs - mwevol.Gamma_rhs = 0 arhsU[0] - mwevol.arhsU[0] = 0 erhsU[0] - mwevol.erhsU[0] = 0 AU_Cart[0] - mwevol.AU_Cart[0] = 0 EU_Cart[0] - mwevol.EU_Cart[0] = 0 arhsU[1] - mwevol.arhsU[1] = 0 erhsU[1] - mwevol.erhsU[1] = 0 AU_Cart[1] - mwevol.AU_Cart[1] = 0 EU_Cart[1] - mwevol.EU_Cart[1] = 0 arhsU[2] - mwevol.arhsU[2] = 0 erhsU[2] - mwevol.erhsU[2] = 0 AU_Cart[2] - mwevol.AU_Cart[2] = 0 EU_Cart[2] - mwevol.EU_Cart[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-VacuumMaxwell_Curvilinear_RHS-Rescaling.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-VacuumMaxwell_Curvilinear_RHS-Rescaling")
[NbConvertApp] WARNING | pattern 'Tutorial-VacuumMaxwell_Curvilinear_RHS-Rescaling.ipynb' matched no files Created Tutorial-VacuumMaxwell_Curvilinear_RHS-Rescaling.tex, and compiled LaTeX file to PDF file Tutorial-VacuumMaxwell_Curvilinear_RHS- Rescaling.pdf