# The NRPy_param_funcs module sets up global structures that manage free parameters within NRPy+
import NRPy_param_funcs as par # NRPy+: Parameter interface
# The indexedexp module defines various functions for defining and managing indexed quantities like tensors and pseudotensors
import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support
# The grid module defines various parameters related to a numerical grid or the dimensionality of indexed expressions
# For example, it declares the parameter DIM, which specifies the dimensionality of the indexed expression
import grid as gri # NRPy+: Functions having to do with numerical grids
from outputC import outputC # NRPy+: Basic C code output functionality
Indexed expressions of rank 1 are stored as Python lists.
There are two standard ways to declare indexed expressions:
par.parval_from_str("grid::DIM")
. Otherwise the rank-1 indexed expression will have dimension DIM.zerorank1()
, DIM is an optional parameter that, if set to -1, will default to the dimension as set in the grid module: par.parval_from_str("grid::DIM")
. Otherwise the rank-1 indexed expression will have dimension DIM.zerorank1()
and declarerank1()
are both wrapper functions for the more general function declare_indexedexp()
.
declare_indexedexp()
will initialize an indexed expression to zero. If symmetry is not specified or has value "nosym", then an indexed expression will not be symmetrized, which has no relevance for an indexed expression of rank 1. If dimension is not specified or has value -1, then dimension will default to the dimension as set in the grid module: par.parval_from_str("grid::DIM")
.For example, the 3-vector βi (upper index denotes contravariant) can be initialized to zero as follows:
# Declare rank-1 contravariant ("U") vector
betaU = ixp.zerorank1()
# Print the result. It's a list of zeros!
print(betaU)
[0, 0, 0]
Next set βi=∑ij=0j={0,1,3}
# Get the dimension we just set, so we know how many indices to loop over
DIM = par.parval_from_str("grid::DIM")
for i in range(DIM): # sum i from 0 to DIM-1, inclusive
for j in range(i+1): # sum j from 0 to i, inclusive
betaU[i] += j
print("The 3-vector betaU is now set to: "+str(betaU))
The 3-vector betaU is now set to: [0, 1, 3]
Alternatively, the 3-vector βi can be initialized symbolically as follows:
# Set the dimension to 3
par.set_parval_from_str("grid::DIM",3)
# Declare rank-1 contravariant ("U") vector
betaU = ixp.declarerank1("betaU")
# Print the result. It's a list!
print(betaU)
[betaU0, betaU1, betaU2]
Declaring βi symbolically is standard in case betaU0
, betaU1
, and betaU2
are defined elsewhere (e.g., read in from main memory as a gridfunction).
As can be seen, NRPy+'s standard naming convention for indexed rank-1 expressions is
Caution: after declaring the vector, betaU0
, betaU1
, and betaU2
can only be accessed or manipulated through list access; i.e., via betaU[0]
, betaU[1]
, and betaU[2]
, respectively. Attempts to access betaU0
directly will fail.
Knowing this, let's multiply betaU1
by 2:
betaU[1] *= 2
print("The 3-vector betaU is now set to "+str(betaU))
print("The component betaU[1] is now set to "+str(betaU[1]))
The 3-vector betaU is now set to [betaU0, 2*betaU1, betaU2] The component betaU[1] is now set to 2*betaU1
# First set betaU back to its initial value
betaU = ixp.declarerank1("betaU")
# Declare beta_j:
betaD = ixp.declarerank1("betaD")
# Get the dimension we just set, so we know how many indices to loop over
DIM = par.parval_from_str("grid::DIM")
# Initialize dot product to zero
dotprod = 0
# Perform dot product beta^i beta_i
for i in range(DIM):
dotprod += betaU[i]*betaD[i]
# Print result!
print(dotprod)
betaD0*betaU0 + betaD1*betaU1 + betaD2*betaU2
Moving to higher ranks, rank-2 indexed expressions are stored as lists of lists, rank-3 indexed expressions as lists of lists of lists, etc. For example:
gDD[i][j]
in NRPy+, so that e.g., gDD[0][2]
is stored with name gDD02
andTUD[m][n]
in NRPy+ (index names are of course arbitrary).Caveat: note that it is currently up to the user to determine whether the combination of indexed expressions makes sense; NRPy+ does not track whether up and down indices are written consistently.
NRPy+ supports symmetries in indexed expressions (above rank 1), so that if hij=hji, then declaring hDD[i][j]
to be symmetric in NRPy+ will result in both hDD[0][2]
and hDD[2][0]
mapping to the single SymPy variable hDD02
.
To see how this works in NRPy+, let's define in NRPy+ a symmetric, rank-2 tensor hij in three dimensions, and then compute the contraction, which should be given by con=hijhij=h00h00+h11h11+h22h22+2(h01h01+h02h02+h12h12).
# Get the dimension we just set (should be set to 3).
DIM = par.parval_from_str("grid::DIM")
# Declare h_{ij}=hDD[i][j] and h^{ij}=hUU[i][j]
hUU = ixp.declarerank2("hUU","sym01")
hDD = ixp.declarerank2("hDD","sym01")
# Perform sum h^{ij} h_{ij}, initializing contraction result to zero
con = 0
for i in range(DIM):
for j in range(DIM):
con += hUU[i][j]*hDD[i][j]
# Print result
print(con)
hDD00*hUU00 + 2*hDD01*hUU01 + 2*hDD02*hUU02 + hDD11*hUU11 + 2*hDD12*hUU12 + hDD22*hUU22
outputC(con,"con")
/* * Original SymPy expression: * "con = hDD00*hUU00 + 2*hDD01*hUU01 + 2*hDD02*hUU02 + hDD11*hUU11 + 2*hDD12*hUU12 + hDD22*hUU22" */ { con = hDD00*hUU00 + 2*hDD01*hUU01 + 2*hDD02*hUU02 + hDD11*hUU11 + 2*hDD12*hUU12 + hDD22*hUU22; }
outputC(con,"con",params="enable_SIMD=True")
/* * Original SymPy expression: * "con = hDD00*hUU00 + 2*hDD01*hUU01 + 2*hDD02*hUU02 + hDD11*hUU11 + 2*hDD12*hUU12 + hDD22*hUU22" */ { const double tmp_Integer_2 = 2.0; const REAL_SIMD_ARRAY _Integer_2 = ConstSIMD(tmp_Integer_2); con = FusedMulAddSIMD(hDD22, hUU22, FusedMulAddSIMD(_Integer_2, MulSIMD(hDD01, hUU01), FusedMulAddSIMD(_Integer_2, MulSIMD(hDD02, hUU02), FusedMulAddSIMD(_Integer_2, MulSIMD(hDD12, hUU12), FusedMulAddSIMD(hDD00, hUU00, MulSIMD(hDD11, hUU11)))))); }
To complete this exercise, you must first reset all variables in the notebook:
# *Uncomment* the below %reset command and then press <Shift>+<Enter>.
# Respond with "y" in the dialog box to reset all variables.
# %reset
Write your solution below:
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-Indexed_Expressions.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-Indexed_Expressions")
Created Tutorial-Indexed_Expressions.tex, and compiled LaTeX file to PDF file Tutorial-Indexed_Expressions.pdf