Example #1
0
def gammabar__inverse_and_derivs():
    # Step 4.a: Declare as globals all expressions that may be used
    #           outside this function, declare BSSN gridfunctions
    #           if not defined already, and set DIM=3.
    global gammabarUU, gammabarDD_dD, gammabarDD_dupD, gammabarDD_dDD, GammabarUDD
    hDD, aDD, lambdaU, vetU, betU, trK, cf, alpha = declare_BSSN_gridfunctions_if_not_declared_already(
    )
    DIM = 3
    # This function needs gammabarDD, defined in BSSN_basic_tensors()
    BSSN_basic_tensors()

    # Step 4.a.i: gammabarUU:
    gammabarUU, dummydet = ixp.symm_matrix_inverter3x3(gammabarDD)

    # Step 4.b.i: gammabarDDdD[i][j][k]
    #               = \hat{\gamma}_{ij,k} + h_{ij,k} \text{ReDD[i][j]} + h_{ij} \text{ReDDdD[i][j][k]}.
    gammabarDD_dD = ixp.zerorank3()
    gammabarDD_dupD = ixp.zerorank3()
    hDD_dD = ixp.declarerank3("hDD_dD", "sym01")
    hDD_dupD = ixp.declarerank3("hDD_dupD", "sym01")
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                gammabarDD_dD[i][j][k] = rfm.ghatDDdD[i][j][k] + \
                                         hDD_dD[i][j][k] * rfm.ReDD[i][j] + hDD[i][j] * rfm.ReDDdD[i][j][k]

                # Compute associated upwinded derivative, needed for the \bar{\gamma}_{ij} RHS
                gammabarDD_dupD[i][j][k] = rfm.ghatDDdD[i][j][k] + \
                                           hDD_dupD[i][j][k] * rfm.ReDD[i][j] + hDD[i][j] * rfm.ReDDdD[i][j][k]

    # Step 4.b.ii: Compute gammabarDD_dDD in terms of the rescaled BSSN quantity hDD
    #      and its derivatives, as well as the reference metric and rescaling
    #      matrix, and its derivatives (expression given below):
    hDD_dDD = ixp.declarerank4("hDD_dDD", "sym01_sym23")
    gammabarDD_dDD = ixp.zerorank4()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    # gammabar_{ij,kl} = gammahat_{ij,kl}
                    #                  + h_{ij,kl} ReDD[i][j]
                    #                  + h_{ij,k} ReDDdD[i][j][l] + h_{ij,l} ReDDdD[i][j][k]
                    #                  + h_{ij} ReDDdDD[i][j][k][l]
                    gammabarDD_dDD[i][j][k][l] = rfm.ghatDDdDD[i][j][k][l]
                    gammabarDD_dDD[i][j][k][
                        l] += hDD_dDD[i][j][k][l] * rfm.ReDD[i][j]
                    gammabarDD_dDD[i][j][k][l] += hDD_dD[i][j][k] * rfm.ReDDdD[i][j][l] + \
                                                  hDD_dD[i][j][l] * rfm.ReDDdD[i][j][k]
                    gammabarDD_dDD[i][j][k][
                        l] += hDD[i][j] * rfm.ReDDdDD[i][j][k][l]

    # Step 4.b.iii: Define barred Christoffel symbol \bar{\Gamma}^{i}_{kl} = GammabarUDD[i][k][l] (see expression below)
    GammabarUDD = ixp.zerorank3()
    for i in range(DIM):
        for k in range(DIM):
            for l in range(DIM):
                for m in range(DIM):
                    # Gammabar^i_{kl} = 1/2 * gammabar^{im} ( gammabar_{mk,l} + gammabar_{ml,k} - gammabar_{kl,m}):
                    GammabarUDD[i][k][l] += sp.Rational(1, 2) * gammabarUU[i][m] * \
                                            (gammabarDD_dD[m][k][l] + gammabarDD_dD[m][l][k] - gammabarDD_dD[k][l][m])
Example #2
0
def AbarUU_AbarUD_trAbar_AbarDD_dD():
    # Step 6.a: Declare as globals all expressions that may be used
    #           outside this function, declare BSSN gridfunctions
    #           if not defined already, and set DIM=3.
    global AbarUU, AbarUD, trAbar, AbarDD_dD, AbarDD_dupD
    hDD, aDD, lambdaU, vetU, betU, trK, cf, alpha = declare_BSSN_gridfunctions_if_not_declared_already(
    )
    DIM = 3

    # Define AbarDD and gammabarDD in terms of BSSN gridfunctions
    BSSN_basic_tensors()
    # Define gammabarUU in terms of BSSN gridfunctions
    gammabar__inverse_and_derivs()

    # Step 6.a.i: Compute Abar^{ij} in terms of Abar_{ij} and gammabar^{ij}
    AbarUU = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    # Abar^{ij} = gammabar^{ik} gammabar^{jl} Abar_{kl}
                    AbarUU[i][j] += gammabarUU[i][k] * gammabarUU[j][
                        l] * AbarDD[k][l]

    # Step 6.a.ii: Compute Abar^i_j in terms of Abar_{ij} and gammabar^{ij}
    AbarUD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                # Abar^i_j = gammabar^{ik} Abar_{kj}
                AbarUD[i][j] += gammabarUU[i][k] * AbarDD[k][j]

    # Step 6.a.iii: Compute Abar^k_k = trace of Abar:
    trAbar = sp.sympify(0)
    for k in range(DIM):
        for j in range(DIM):
            # Abar^k_k = gammabar^{kj} Abar_{jk}
            trAbar += gammabarUU[k][j] * AbarDD[j][k]

    # Step 6.a.iv: Compute Abar_{ij,k}
    AbarDD_dD = ixp.zerorank3()
    AbarDD_dupD = ixp.zerorank3()
    aDD_dD = ixp.declarerank3("aDD_dD", "sym01")
    aDD_dupD = ixp.declarerank3("aDD_dupD", "sym01")
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                AbarDD_dupD[i][j][k] = rfm.ReDDdD[i][j][k] * aDD[i][
                    j] + rfm.ReDD[i][j] * aDD_dupD[i][j][k]
                AbarDD_dD[i][j][k] = rfm.ReDDdD[i][j][k] * aDD[i][
                    j] + rfm.ReDD[i][j] * aDD_dD[i][j][k]
Example #3
0
def betaU_derivs():
    # Step 8.i: Declare as globals all expressions that may be used
    #           outside this function, declare BSSN gridfunctions
    #           if not defined already, and set DIM=3.
    global betaU_dD, betaU_dupD, betaU_dDD
    hDD, aDD, lambdaU, vetU, betU, trK, cf, alpha = declare_BSSN_gridfunctions_if_not_declared_already(
    )
    DIM = 3

    # Step 8.ii: Compute the unrescaled shift vector beta^i = ReU[i]*vet^i
    vetU_dD = ixp.declarerank2("vetU_dD", "nosym")
    vetU_dupD = ixp.declarerank2("vetU_dupD",
                                 "nosym")  # Needed for upwinded \beta^i_{,j}
    vetU_dDD = ixp.declarerank3("vetU_dDD", "sym12")  # Needed for \beta^i_{,j}
    betaU_dD = ixp.zerorank2()
    betaU_dupD = ixp.zerorank2()  # Needed for, e.g., \beta^i RHS
    betaU_dDD = ixp.zerorank3()  # Needed for, e.g., \bar{\Lambda}^i RHS
    for i in range(DIM):
        for j in range(DIM):
            betaU_dD[i][
                j] = vetU_dD[i][j] * rfm.ReU[i] + vetU[i] * rfm.ReUdD[i][j]
            betaU_dupD[i][j] = vetU_dupD[i][j] * rfm.ReU[i] + vetU[
                i] * rfm.ReUdD[i][j]  # Needed for \beta^i RHS
            for k in range(DIM):
                # Needed for, e.g., \bar{\Lambda}^i RHS:
                betaU_dDD[i][j][k] = vetU_dDD[i][j][k] * rfm.ReU[i] + vetU_dD[i][j] * rfm.ReUdD[i][k] + \
                                     vetU_dD[i][k] * rfm.ReUdD[i][j] + vetU[i] * rfm.ReUdDD[i][j][k]
def write_out_functions_for_StildeD_source_term(outdir,outCparams,gammaDD,betaU,alpha,ValenciavU,BU,sqrt4pi):
    generate_memory_access_code()
    # First, we declare some dummy tensors that we will use for the codegen.
    gammaDDdD  = ixp.declarerank3("gammaDDdD","sym01",DIM=3)
    betaUdD = ixp.declarerank2("betaUdD","nosym",DIM=3)
    alphadD = ixp.declarerank1("alphadD",DIM=3)

    # We need to rerun a few of these functions with the reset lists to make sure these functions
    # don't cheat by using analytic expressions
    GRHD.compute_sqrtgammaDET(gammaDD)
    GRHD.u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha, betaU, gammaDD, ValenciavU)
    GRFFE.compute_smallb4U(gammaDD, betaU, alpha, GRHD.u4U_ito_ValenciavU, BU, sqrt4pi)
    GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, GRFFE.smallb4U)
    GRFFE.compute_TEM4UU(gammaDD,betaU,alpha, GRFFE.smallb4U, GRFFE.smallbsquared,GRHD.u4U_ito_ValenciavU)
    GRHD.compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, gammaDDdD,betaUdD,alphadD)
    GRHD.compute_S_tilde_source_termD(alpha, GRHD.sqrtgammaDET,GRHD.g4DD_zerotimederiv_dD, GRFFE.TEM4UU)
    for i in range(3):
        desc = "Adds the source term to StildeD"+str(i)+"."
        name = "calculate_StildeD"+str(i)+"_source_term"
        outCfunction(
            outfile  = os.path.join(outdir,name+".h"), desc=desc, name=name,
            params   ="const paramstruct *params,const REAL *auxevol_gfs, REAL *rhs_gfs",
            body     = general_access \
                      +metric_deriv_access[i]\
                      +outputC(GRHD.S_tilde_source_termD[i],"Stilde_rhsD"+str(i),"returnstring",params=outCparams).replace("IDX4","IDX4S")\
                      +write_final_quantity[i],
            loopopts ="InteriorPoints",
            rel_path_for_Cparams=os.path.join("../"))
Example #5
0
def generate_everything_for_UnitTesting():
    # First define hydrodynamical quantities
    u4U = ixp.declarerank1("u4U", DIM=4)
    rho_b, P, epsilon = sp.symbols('rho_b P epsilon', real=True)

    # Then ADM quantities
    gammaDD = ixp.declarerank2("gammaDD", "sym01", DIM=3)
    KDD = ixp.declarerank2("KDD", "sym01", DIM=3)
    betaU = ixp.declarerank1("betaU", DIM=3)
    alpha = sp.symbols('alpha', real=True)

    # First compute stress-energy tensor T4UU and T4UD:
    compute_T4UU(gammaDD, betaU, alpha, rho_b, P, epsilon, u4U)
    compute_T4UD(gammaDD, betaU, alpha, T4UU)

    # Next sqrt(gamma)
    compute_sqrtgammaDET(gammaDD)

    # Compute conservative variables in terms of primitive variables
    compute_rho_star(alpha, sqrtgammaDET, rho_b, u4U)
    compute_tau_tilde(alpha, sqrtgammaDET, T4UU, rho_star)
    compute_S_tildeD(alpha, sqrtgammaDET, T4UD)

    # Then compute v^i from u^mu
    compute_vU_from_u4U__no_speed_limit(u4U)

    # Next compute fluxes of conservative variables
    compute_rho_star_fluxU(vU, rho_star)
    compute_tau_tilde_fluxU(alpha, sqrtgammaDET, vU, T4UU, rho_star)
    compute_S_tilde_fluxUD(alpha, sqrtgammaDET, T4UD)

    # Then declare derivatives & compute g4DD_zerotimederiv_dD
    gammaDD_dD = ixp.declarerank3("gammaDD_dD", "sym01", DIM=3)
    betaU_dD = ixp.declarerank2("betaU_dD", "nosym", DIM=3)
    alpha_dD = ixp.declarerank1("alpha_dD", DIM=3)
    compute_g4DD_zerotimederiv_dD(gammaDD, betaU, alpha, gammaDD_dD, betaU_dD,
                                  alpha_dD)

    # Then compute source terms on tau_tilde and S_tilde equations
    compute_s_source_term(KDD, betaU, alpha, sqrtgammaDET, alpha_dD, T4UU)
    compute_S_tilde_source_termD(alpha, sqrtgammaDET, g4DD_zerotimederiv_dD,
                                 T4UU)

    # Then compute the 4-velocities in terms of an input Valencia 3-velocity testValenciavU[i]
    testValenciavU = ixp.declarerank1("testValenciavU", DIM=3)
    u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(
        alpha, betaU, gammaDD, testValenciavU)

    # Finally compute the 4-velocities in terms of an input 3-velocity testvU[i] = u^i/u^0
    testvU = ixp.declarerank1("testvU", DIM=3)
    u4U_in_terms_of_vU__rescale_vU_by_applying_speed_limit(
        alpha, betaU, gammaDD, testvU)
Example #6
0
def generate_everything_for_UnitTesting():
    # First define hydrodynamical quantities
    u4U = ixp.declarerank1("u4U", DIM=4)
    B_tildeU = ixp.declarerank1("B_tildeU", DIM=3)

    # Then ADM quantities
    gammaDD = ixp.declarerank2("gammaDD", "sym01", DIM=3)
    betaU = ixp.declarerank1("betaU", DIM=3)
    alpha = sp.symbols('alpha', real=True)

    # Then numerical constant
    sqrt4pi = sp.symbols('sqrt4pi', real=True)

    # First compute stress-energy tensor T4UU and T4UD:
    import GRHD.equations as GHeq
    GHeq.compute_sqrtgammaDET(gammaDD)
    compute_B_notildeU(GHeq.sqrtgammaDET, B_tildeU)
    compute_smallb4U(gammaDD, betaU, alpha, u4U, B_notildeU, sqrt4pi)
    compute_smallb4U_with_driftvU_for_FFE(gammaDD, betaU, alpha, u4U,
                                          B_notildeU, sqrt4pi)
    compute_smallbsquared(gammaDD, betaU, alpha, smallb4U)

    compute_TEM4UU(gammaDD, betaU, alpha, smallb4U, smallbsquared, u4U)
    compute_TEM4UD(gammaDD, betaU, alpha, TEM4UU)

    # Compute conservative variables in terms of primitive variables
    GHeq.compute_S_tildeD(alpha, GHeq.sqrtgammaDET, TEM4UD)
    global S_tildeD
    S_tildeD = GHeq.S_tildeD

    # Next compute fluxes of conservative variables
    GHeq.compute_S_tilde_fluxUD(alpha, GHeq.sqrtgammaDET, TEM4UD)
    global S_tilde_fluxUD
    S_tilde_fluxUD = GHeq.S_tilde_fluxUD

    # Then declare derivatives & compute g4DDdD
    gammaDD_dD = ixp.declarerank3("gammaDD_dD", "sym01", DIM=3)
    betaU_dD = ixp.declarerank2("betaU_dD", "nosym", DIM=3)
    alpha_dD = ixp.declarerank1("alpha_dD", DIM=3)
    GHeq.compute_g4DD_zerotimederiv_dD(gammaDD, betaU, alpha, gammaDD_dD,
                                       betaU_dD, alpha_dD)

    # Finally compute source terms on tau_tilde and S_tilde equations
    GHeq.compute_S_tilde_source_termD(alpha, GHeq.sqrtgammaDET,
                                      GHeq.g4DD_zerotimederiv_dD, TEM4UU)
    global S_tilde_source_termD
    S_tilde_source_termD = GHeq.S_tilde_source_termD
Example #7
0
def WeylScalars_Cartesian():
    # We do not need the barred or hatted quantities calculated when using Cartesian coordinates.
    # Instead, we declare the PHYSICAL metric and extrinsic curvature as grid functions.
    gammaDD = ixp.register_gridfunctions_for_single_rank2(
        "EVOL", "gammaDD", "sym01")
    kDD = ixp.register_gridfunctions_for_single_rank2("EVOL", "kDD", "sym01")
    gammaUU, detgamma = ixp.symm_matrix_inverter3x3(gammaDD)

    output_scalars = par.parval_from_str("output_scalars")
    global psi4r, psi4i, psi3r, psi3i, psi2r, psi2i, psi1r, psi1i, psi0r, psi0i
    #    if output_scalars is "all_psis_and_invariants":
    #        psi4r,psi4i,psi3r,psi3i,psi2r,psi2i,psi1r,psi1i,psi0r,psi0i = sp.symbols("psi4r psi4i\
    #                                                                                  psi3r psi3i\
    #                                                                                  psi2r psi2i\
    #                                                                                  psi1r psi1i\
    #                                                                                  psi0r psi0i")
    #   elif output_scalars is "all_psis":
    psi4r,psi4i,psi3r,psi3i,psi2r,psi2i,psi1r,psi1i,psi0r,psi0i = gri.register_gridfunctions("AUX",["psi4r","psi4i",\
                                                                                                    "psi3r","psi3i",\
                                                                                                    "psi2r","psi2i",\
                                                                                                    "psi1r","psi1i",\
                                                                                                    "psi0r","psi0i"])

    # Step 2a: Set spatial dimension (must be 3 for BSSN)
    DIM = 3
    par.set_parval_from_str("grid::DIM", DIM)

    # Step 2b: Set the coordinate system to Cartesian
    x, y, z = gri.register_gridfunctions("AUX", ["x", "y", "z"])

    # Step 2c: Set which tetrad is used; at the moment, only one supported option
    if par.parval_from_str("WeylScal4NRPy.WeylScalars_Cartesian::TetradChoice"
                           ) == "Approx_QuasiKinnersley":
        # Step 3a: Choose 3 orthogonal vectors. Here, we choose one in the azimuthal
        #          direction, one in the radial direction, and the cross product of the two.
        # Eqs 5.6, 5.7 in https://arxiv.org/pdf/gr-qc/0104063.pdf:
        # v_1^a &= [-y,x,0] \\
        # v_2^a &= [x,y,z] \\
        # v_3^a &= {\rm det}(g)^{1/2} g^{ad} \epsilon_{dbc} v_1^b v_2^c,
        v1U = ixp.zerorank1()
        v2U = ixp.zerorank1()
        v3U = ixp.zerorank1()
        v1U[0] = -y
        v1U[1] = x
        v1U[2] = sp.sympify(0)
        v2U[0] = x
        v2U[1] = y
        v2U[2] = z
        LeviCivitaSymbol_rank3 = define_LeviCivitaSymbol_rank3()
        for a in range(DIM):
            for b in range(DIM):
                for c in range(DIM):
                    for d in range(DIM):
                        v3U[a] += sp.sqrt(
                            detgamma) * gammaUU[a][d] * LeviCivitaSymbol_rank3[
                                d][b][c] * v1U[b] * v2U[c]

        # Step 3b: Gram-Schmidt orthonormalization of the vectors.
        # The w_i^a vectors here are used to temporarily hold values on the way to the final vectors e_i^a
        # e_1^a &= \frac{v_1^a}{\omega_{11}} \\
        # e_2^a &= \frac{v_2^a - \omega_{12} e_1^a}{\omega_{22}} \\
        # e_3^a &= \frac{v_3^a - \omega_{13} e_1^a - \omega_{23} e_2^a}{\omega_{33}}, \\

        # Normalize the first vector
        w1U = ixp.zerorank1()
        for a in range(DIM):
            w1U[a] = v1U[a]
        omega11 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega11 += w1U[a] * w1U[b] * gammaDD[a][b]
        e1U = ixp.zerorank1()
        for a in range(DIM):
            e1U[a] = w1U[a] / sp.sqrt(omega11)

        # Subtract off the portion of the first vector along the second, then normalize
        omega12 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega12 += e1U[a] * v2U[b] * gammaDD[a][b]
        w2U = ixp.zerorank1()
        for a in range(DIM):
            w2U[a] = v2U[a] - omega12 * e1U[a]
        omega22 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega22 += w2U[a] * w2U[b] * gammaDD[a][b]
        e2U = ixp.zerorank1()
        for a in range(DIM):
            e2U[a] = w2U[a] / sp.sqrt(omega22)

        # Subtract off the portion of the first and second vectors along the third, then normalize
        omega13 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega13 += e1U[a] * v3U[b] * gammaDD[a][b]
        omega23 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega23 += e2U[a] * v3U[b] * gammaDD[a][b]
        w3U = ixp.zerorank1()
        for a in range(DIM):
            w3U[a] = v3U[a] - omega13 * e1U[a] - omega23 * e2U[a]
        omega33 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega33 += w3U[a] * w3U[b] * gammaDD[a][b]
        e3U = ixp.zerorank1()
        for a in range(DIM):
            e3U[a] = w3U[a] / sp.sqrt(omega33)

        # Step 3c: Construct the tetrad itself.
        # Eqs. 5.6:
        # l^a &= \frac{1}{\sqrt{2}} e_2^a \\
        # n^a &= -\frac{1}{\sqrt{2}} e_2^a \\
        # m^a &= \frac{1}{\sqrt{2}} (e_3^a + i e_1^a) \\
        # \overset{*}{m}{}^a &= \frac{1}{\sqrt{2}} (e_3^a - i e_1^a)
        isqrt2 = 1 / sp.sqrt(2)
        ltetU = ixp.zerorank1()
        ntetU = ixp.zerorank1()
        #mtetU = ixp.zerorank1()
        #mtetccU = ixp.zerorank1()
        remtetU = ixp.zerorank1(
        )  # SymPy does not like trying to take the real/imaginary parts of such a
        immtetU = ixp.zerorank1(
        )  # complicated expression as the Weyl scalars, so we will do it ourselves.
        for i in range(DIM):
            ltetU[i] = isqrt2 * e2U[i]
            ntetU[i] = -isqrt2 * e2U[i]
            remtetU[i] = isqrt2 * e3U[i]
            immtetU[i] = isqrt2 * e1U[i]
        nn = isqrt2

    else:
        print("Error: TetradChoice == " + par.parval_from_str("TetradChoice") +
              " unsupported!")
        exit(1)

    gammaDD_dD = ixp.declarerank3("gammaDD_dD", "sym01")

    # Define the Christoffel symbols
    GammaUDD = ixp.zerorank3(DIM)
    for i in range(DIM):
        for k in range(DIM):
            for l in range(DIM):
                for m in range(DIM):
                    GammaUDD[i][k][l] += (sp.Rational(1,2))*gammaUU[i][m]*\
                                         (gammaDD_dD[m][k][l] + gammaDD_dD[m][l][k] - gammaDD_dD[k][l][m])

    # Step 4b: Declare and construct the Riemann curvature tensor:
    # R_{abcd} = \frac{1}{2} (\gamma_{ad,cb}+\gamma_{bc,da}-\gamma_{ac,bd}-\gamma_{bd,ac})
    #            + \gamma_{je} \Gamma^{j}_{bc}\Gamma^{e}_{ad} - \gamma_{je} \Gamma^{j}_{bd} \Gamma^{e}_{ac}
    gammaDD_dDD = ixp.declarerank4("gammaDD_dDD", "sym01_sym23")
    RiemannDDDD = ixp.zerorank4()
    for a in range(DIM):
        for b in range(DIM):
            for c in range(DIM):
                for d in range(DIM):
                    RiemannDDDD[a][b][c][d] = (gammaDD_dDD[a][d][c][b] + \
                                               gammaDD_dDD[b][c][d][a] - \
                                               gammaDD_dDD[a][c][b][d] - \
                                               gammaDD_dDD[b][d][a][c]) / 2
                    for e in range(DIM):
                        for j in range(DIM):
                            RiemannDDDD[a][b][c][d] +=  gammaDD[j][e] * GammaUDD[j][b][c] * GammaUDD[e][a][d] - \
                                                        gammaDD[j][e] * GammaUDD[j][b][d] * GammaUDD[e][a][c]

    # Step 4c: We also need the extrinsic curvature tensor $K_{ij}$.
    # In Cartesian coordinates, we already made the components gridfunctions.
    # We will, however, need to calculate the trace of K seperately:
    trK = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            trK += gammaUU[i][j] * kDD[i][j]

    # Step 5: Build the formula for \psi_4.
    # Gauss equation: involving the Riemann tensor and extrinsic curvature.
    # GaussDDDD[i][j][k][l] =& R_{ijkl} + 2K_{i[k}K_{l]j}
    GaussDDDD = ixp.zerorank4()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    GaussDDDD[i][j][k][l] = RiemannDDDD[i][j][k][
                        l] + kDD[i][k] * kDD[l][j] - kDD[i][l] * kDD[k][j]

    # Codazzi equation: involving partial derivatives of the extrinsic curvature.
    # We will first need to declare derivatives of kDD
    # CodazziDDD[j][k][l] =& -2 (K_{j[k,l]} + \Gamma^p_{j[k} K_{l]p})
    kDD_dD = ixp.declarerank3("kDD_dD", "sym01")
    CodazziDDD = ixp.zerorank3()
    for j in range(DIM):
        for k in range(DIM):
            for l in range(DIM):
                CodazziDDD[j][k][l] = kDD_dD[j][l][k] - kDD_dD[j][k][l]
                for p in range(DIM):
                    CodazziDDD[j][k][l] += GammaUDD[p][j][l] * kDD[k][
                        p] - GammaUDD[p][j][k] * kDD[l][p]

    # Another piece. While not associated with any particular equation,
    # this is still useful for organizational purposes.
    # RojoDD[j][l]} = & R_{jl} - K_{jp} K^p_l + KK_{jl} \\
    #               = & \gamma^{pd} R_{jpld} - K_{jp} K^p_l + KK_{jl}
    RojoDD = ixp.zerorank2()
    for j in range(DIM):
        for l in range(DIM):
            RojoDD[j][l] = trK * kDD[j][l]
            for p in range(DIM):
                for d in range(DIM):
                    RojoDD[j][l] += gammaUU[p][d] * RiemannDDDD[j][p][l][
                        d] - kDD[j][p] * gammaUU[p][d] * kDD[d][l]

    # Now we can calculate $\psi_4$ itself! We assume l^0 = n^0 = \frac{1}{\sqrt{2}}
    # and m^0 = \overset{*}{m}{}^0 = 0 to simplify these equations.
    # We calculate the Weyl scalars as defined in https://arxiv.org/abs/gr-qc/0104063
    # In terms of the above-defined quantites, the psis are defined as:
    # \psi_4 =&\ (\text{GaussDDDD[i][j][k][l]}) n^i \overset{*}{m}{}^j n^k \overset{*}{m}{}^l \\
    #         &+2 (\text{CodazziDDD[j][k][l]}) n^{0} \overset{*}{m}{}^{j} n^k \overset{*}{m}{}^l \\
    #         &+ (\text{RojoDD[j][l]}) n^{0} \overset{*}{m}{}^{j} n^{0} \overset{*}{m}{}^{l}.
    # \psi_3 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i n^j \overset{*}{m}{}^k n^l \\
    #         &+ (\text{CodazziDDD[j][k][l]}) (l^{0} n^{j} \overset{*}{m}{}^k n^l - l^{j} n^{0} \overset{*}{m}{}^k n^l - l^k n^j\overset{*}{m}{}^l n^0) \\
    #         &- (\text{RojoDD[j][l]}) l^{0} n^{j} \overset{*}{m}{}^l n^0 - l^{j} n^{0} \overset{*}{m}{}^l n^0 \\
    # \psi_2 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i m^j \overset{*}{m}{}^k n^l \\
    #         &+ (\text{CodazziDDD[j][k][l]}) (l^{0} m^{j} \overset{*}{m}{}^k n^l - l^{j} m^{0} \overset{*}{m}{}^k n^l - l^k m^l \overset{*}{m}{}^l n^0) \\
    #         &- (\text{RojoDD[j][l]}) l^0 m^j \overset{*}{m}{}^l n^0 \\
    # \psi_1 =&\ (\text{GaussDDDD[i][j][k][l]}) n^i l^j m^k l^l \\
    #         &+ (\text{CodazziDDD[j][k][l]}) (n^{0} l^{j} m^k l^l - n^{j} l^{0} m^k l^l - n^k l^l m^j l^0) \\
    #         &- (\text{RojoDD[j][l]}) (n^{0} l^{j} m^l l^0 - n^{j} l^{0} m^l l^0) \\
    # \psi_0 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i m^j l^k m^l \\
    #         &+2 (\text{CodazziDDD[j][k][l]}) (l^0 m^j l^k m^l + l^k m^l l^0 m^j) \\
    #         &+ (\text{RojoDD[j][l]}) l^0 m^j l^0 m^j. \\

    psi4r = sp.sympify(0)
    psi4i = sp.sympify(0)
    psi3r = sp.sympify(0)
    psi3i = sp.sympify(0)
    psi2r = sp.sympify(0)
    psi2i = sp.sympify(0)
    psi1r = sp.sympify(0)
    psi1i = sp.sympify(0)
    psi0r = sp.sympify(0)
    psi0i = sp.sympify(0)
    for l in range(DIM):
        for j in range(DIM):
            psi4r += RojoDD[j][l] * nn * nn * (remtetU[j] * remtetU[l] -
                                               immtetU[j] * immtetU[l])
            psi4i += RojoDD[j][l] * nn * nn * (-remtetU[j] * immtetU[l] -
                                               immtetU[j] * remtetU[l])
            psi3r += -RojoDD[j][l] * nn * nn * (ntetU[j] -
                                                ltetU[j]) * remtetU[l]
            psi3i += RojoDD[j][l] * nn * nn * (ntetU[j] -
                                               ltetU[j]) * immtetU[l]
            psi2r += -RojoDD[j][l] * nn * nn * (remtetU[l] * remtetU[j] +
                                                immtetU[j] * immtetU[l])
            psi2i += -RojoDD[j][l] * nn * nn * (immtetU[l] * remtetU[j] -
                                                remtetU[j] * immtetU[l])
            psi1r += RojoDD[j][l] * nn * nn * (ntetU[j] * remtetU[l] -
                                               ltetU[j] * remtetU[l])
            psi1i += RojoDD[j][l] * nn * nn * (ntetU[j] * immtetU[l] -
                                               ltetU[j] * immtetU[l])
            psi0r += RojoDD[j][l] * nn * nn * (remtetU[j] * remtetU[l] -
                                               immtetU[j] * immtetU[l])
            psi0i += RojoDD[j][l] * nn * nn * (remtetU[j] * immtetU[l] +
                                               immtetU[j] * remtetU[l])

    for l in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                psi4r += 2 * CodazziDDD[j][k][l] * ntetU[k] * nn * (
                    remtetU[j] * remtetU[l] - immtetU[j] * immtetU[l])
                psi4i += 2 * CodazziDDD[j][k][l] * ntetU[k] * nn * (
                    -remtetU[j] * immtetU[l] - immtetU[j] * remtetU[l])
                psi3r += 1 * CodazziDDD[j][k][l] * nn * (
                    (ntetU[j] - ltetU[j]) * remtetU[k] * ntetU[l] -
                    remtetU[j] * ltetU[k] * ntetU[l])
                psi3i += -1 * CodazziDDD[j][k][l] * nn * (
                    (ntetU[j] - ltetU[j]) * immtetU[k] * ntetU[l] -
                    immtetU[j] * ltetU[k] * ntetU[l])
                psi2r += 1 * CodazziDDD[j][k][l] * nn * (
                    ntetU[l] *
                    (remtetU[j] * remtetU[k] + immtetU[j] * immtetU[k]) -
                    ltetU[k] *
                    (remtetU[j] * remtetU[l] + immtetU[j] * immtetU[l]))
                psi2i += 1 * CodazziDDD[j][k][l] * nn * (
                    ntetU[l] *
                    (immtetU[j] * remtetU[k] - remtetU[j] * immtetU[k]) -
                    ltetU[k] *
                    (remtetU[j] * immtetU[l] - immtetU[j] * remtetU[l]))
                psi1r += 1 * CodazziDDD[j][k][l] * nn * (
                    ltetU[j] * remtetU[k] * ltetU[l] - remtetU[j] * ntetU[k] *
                    ltetU[l] - ntetU[j] * remtetU[k] * ltetU[l])
                psi1i += 1 * CodazziDDD[j][k][l] * nn * (
                    ltetU[j] * immtetU[k] * ltetU[l] - immtetU[j] * ntetU[k] *
                    ltetU[l] - ntetU[j] * immtetU[k] * ltetU[l])
                psi0r += 2 * CodazziDDD[j][k][l] * nn * ltetU[k] * (
                    remtetU[j] * remtetU[l] - immtetU[j] * immtetU[l])
                psi0i += 2 * CodazziDDD[j][k][l] * nn * ltetU[k] * (
                    remtetU[j] * immtetU[l] + immtetU[j] * remtetU[l])

    for l in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for i in range(DIM):
                    psi4r += GaussDDDD[i][j][k][l] * ntetU[i] * ntetU[k] * (
                        remtetU[j] * remtetU[l] - immtetU[j] * immtetU[l])
                    psi4i += GaussDDDD[i][j][k][l] * ntetU[i] * ntetU[k] * (
                        -remtetU[j] * immtetU[l] - immtetU[j] * remtetU[l])
                    psi3r += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[
                        j] * remtetU[k] * ntetU[l]
                    psi3i += -GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[
                        j] * immtetU[k] * ntetU[l]
                    psi2r += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[l] * (
                        remtetU[j] * remtetU[k] + immtetU[j] * immtetU[k])
                    psi2i += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[l] * (
                        immtetU[j] * remtetU[k] - remtetU[j] * immtetU[k])
                    psi1r += GaussDDDD[i][j][k][l] * ntetU[i] * ltetU[
                        j] * remtetU[k] * ltetU[l]
                    psi1i += GaussDDDD[i][j][k][l] * ntetU[i] * ltetU[
                        j] * immtetU[k] * ltetU[l]
                    psi0r += GaussDDDD[i][j][k][l] * ltetU[i] * ltetU[k] * (
                        remtetU[j] * remtetU[l] - immtetU[j] * immtetU[l])
                    psi0i += GaussDDDD[i][j][k][l] * ltetU[i] * ltetU[k] * (
                        remtetU[j] * immtetU[l] + immtetU[j] * remtetU[l])
Example #8
0
def RicciBar__gammabarDD_dHatD__DGammaUDD__DGammaU():
    # Step 7.a: Declare as globals all expressions that may be used
    #           outside this function, declare BSSN gridfunctions
    #           if not defined already, and set DIM=3.
    global RbarDD, DGammaUDD, gammabarDD_dHatD, DGammaU
    hDD, aDD, lambdaU, vetU, betU, trK, cf, alpha = declare_BSSN_gridfunctions_if_not_declared_already(
    )
    DIM = 3
    # GammabarUDD is used below, defined in
    #    gammabar__inverse_and_derivs()
    gammabar__inverse_and_derivs()

    # Step 7.a.i: Define \varepsilon_{ij} = epsDD[i][j]
    epsDD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            epsDD[i][j] = hDD[i][j] * rfm.ReDD[i][j]

    # Step 7.a.ii: Define epsDD_dD[i][j][k]
    hDD_dD = ixp.declarerank3("hDD_dD", "sym01")
    epsDD_dD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                epsDD_dD[i][j][k] = hDD_dD[i][j][k] * rfm.ReDD[i][j] + hDD[i][
                    j] * rfm.ReDDdD[i][j][k]

    # Step 7.a.iii: Define epsDD_dDD[i][j][k][l]
    hDD_dDD = ixp.declarerank4("hDD_dDD", "sym01_sym23")
    epsDD_dDD = ixp.zerorank4()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    epsDD_dDD[i][j][k][l] = hDD_dDD[i][j][k][l] * rfm.ReDD[i][j] + \
                                            hDD_dD[i][j][k] * rfm.ReDDdD[i][j][l] + \
                                            hDD_dD[i][j][l] * rfm.ReDDdD[i][j][k] + \
                                            hDD[i][j] * rfm.ReDDdDD[i][j][k][l]

    # Step 7.a.iv: DhatgammabarDDdD[i][j][l] = \bar{\gamma}_{ij;\hat{l}}
    # \bar{\gamma}_{ij;\hat{l}} = \varepsilon_{i j,l}
    #                           - \hat{\Gamma}^m_{i l} \varepsilon_{m j}
    #                           - \hat{\Gamma}^m_{j l} \varepsilon_{i m}
    gammabarDD_dHatD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for l in range(DIM):
                gammabarDD_dHatD[i][j][l] = epsDD_dD[i][j][l]
                for m in range(DIM):
                    gammabarDD_dHatD[i][j][l] += - rfm.GammahatUDD[m][i][l] * epsDD[m][j] \
                                                 - rfm.GammahatUDD[m][j][l] * epsDD[i][m]

    # Step 7.a.v: \bar{\gamma}_{ij;\hat{l},k} = DhatgammabarDD_dHatD_dD[i][j][l][k]:
    #        \bar{\gamma}_{ij;\hat{l},k} = \varepsilon_{ij,lk}
    #                                      - \hat{\Gamma}^m_{i l,k} \varepsilon_{m j}
    #                                      - \hat{\Gamma}^m_{i l} \varepsilon_{m j,k}
    #                                      - \hat{\Gamma}^m_{j l,k} \varepsilon_{i m}
    #                                      - \hat{\Gamma}^m_{j l} \varepsilon_{i m,k}
    gammabarDD_dHatD_dD = ixp.zerorank4()
    for i in range(DIM):
        for j in range(DIM):
            for l in range(DIM):
                for k in range(DIM):
                    gammabarDD_dHatD_dD[i][j][l][k] = epsDD_dDD[i][j][l][k]
                    for m in range(DIM):
                        gammabarDD_dHatD_dD[i][j][l][k] += -rfm.GammahatUDDdD[m][i][l][k] * epsDD[m][j] \
                                                           - rfm.GammahatUDD[m][i][l] * epsDD_dD[m][j][k] \
                                                           - rfm.GammahatUDDdD[m][j][l][k] * epsDD[i][m] \
                                                           - rfm.GammahatUDD[m][j][l] * epsDD_dD[i][m][k]

    # Step 7.a.vi: \bar{\gamma}_{ij;\hat{l}\hat{k}} = DhatgammabarDD_dHatDD[i][j][l][k]
    #          \bar{\gamma}_{ij;\hat{l}\hat{k}} = \partial_k \hat{D}_{l} \varepsilon_{i j}
    #                                           - \hat{\Gamma}^m_{lk} \left(\hat{D}_{m} \varepsilon_{i j}\right)
    #                                           - \hat{\Gamma}^m_{ik} \left(\hat{D}_{l} \varepsilon_{m j}\right)
    #                                           - \hat{\Gamma}^m_{jk} \left(\hat{D}_{l} \varepsilon_{i m}\right)
    gammabarDD_dHatDD = ixp.zerorank4()
    for i in range(DIM):
        for j in range(DIM):
            for l in range(DIM):
                for k in range(DIM):
                    gammabarDD_dHatDD[i][j][l][k] = gammabarDD_dHatD_dD[i][j][
                        l][k]
                    for m in range(DIM):
                        gammabarDD_dHatDD[i][j][l][k] += - rfm.GammahatUDD[m][l][k] * gammabarDD_dHatD[i][j][m] \
                                                         - rfm.GammahatUDD[m][i][k] * gammabarDD_dHatD[m][j][l] \
                                                         - rfm.GammahatUDD[m][j][k] * gammabarDD_dHatD[i][m][l]

    # Step 7.b: Second term of RhatDD: compute \hat{D}_{j} \bar{\Lambda}^{k} = LambarU_dHatD[k][j]
    lambdaU_dD = ixp.declarerank2("lambdaU_dD", "nosym")
    LambarU_dHatD = ixp.zerorank2()
    for j in range(DIM):
        for k in range(DIM):
            LambarU_dHatD[k][j] = lambdaU_dD[k][j] * rfm.ReU[k] + lambdaU[
                k] * rfm.ReUdD[k][j]
            for m in range(DIM):
                LambarU_dHatD[k][
                    j] += rfm.GammahatUDD[k][m][j] * lambdaU[m] * rfm.ReU[m]

    # Step 7.c: Conformal Ricci tensor, part 3: The \Delta^{k} \Delta_{(i j) k}
    #           + \bar{\gamma}^{k l}*(2 \Delta_{k(i}^{m} \Delta_{j) m l}
    #           + \Delta_{i k}^{m} \Delta_{m j l}) terms

    # Step 7.c.i: Define \Delta^i_{jk} = \bar{\Gamma}^i_{jk} - \hat{\Gamma}^i_{jk} = DGammaUDD[i][j][k]
    DGammaUDD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                DGammaUDD[i][j][
                    k] = GammabarUDD[i][j][k] - rfm.GammahatUDD[i][j][k]

    # Step 7.c.ii: Define \Delta^i = \bar{\gamma}^{jk} \Delta^i_{jk}
    DGammaU = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                DGammaU[i] += gammabarUU[j][k] * DGammaUDD[i][j][k]

    # Step 7.c.iii: Define \Delta_{ijk} = \bar{\gamma}_{im} \Delta^m_{jk}
    DGammaDDD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for m in range(DIM):
                    DGammaDDD[i][j][k] += gammabarDD[i][m] * DGammaUDD[m][j][k]

    if par.parval_from_str(thismodule + "::LeaveRicciSymbolic") == "True":
        for i in range(len(gri.glb_gridfcs_list)):
            if "RbarDD00" in gri.glb_gridfcs_list[i].name:
                return

        RbarDD = ixp.register_gridfunctions_for_single_rank2(
            "AUXEVOL", "RbarDD", "sym01")
        return

    # Step 7.d: Summing the terms and defining \bar{R}_{ij}
    # Step 7.d.i: Add the first term to RbarDD:
    #         Rbar_{ij} += - \frac{1}{2} \bar{\gamma}^{k l} \hat{D}_{k} \hat{D}_{l} \bar{\gamma}_{i j}
    RbarDD = ixp.zerorank2()
    RbarDDpiece = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    RbarDD[i][j] += -sp.Rational(1, 2) * gammabarUU[k][
                        l] * gammabarDD_dHatDD[i][j][l][k]
                    RbarDDpiece[i][j] += -sp.Rational(1, 2) * gammabarUU[k][
                        l] * gammabarDD_dHatDD[i][j][l][k]

    # Step 7.d.ii: Add the second term to RbarDD:
    #         Rbar_{ij} += (1/2) * (gammabar_{ki} Lambar^k_{;\hat{j}} + gammabar_{kj} Lambar^k_{;\hat{i}})
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                RbarDD[i][j] += sp.Rational(
                    1, 2) * (gammabarDD[k][i] * LambarU_dHatD[k][j] +
                             gammabarDD[k][j] * LambarU_dHatD[k][i])

    # Step 7.d.iii: Add the remaining term to RbarDD:
    #      Rbar_{ij} += \Delta^{k} \Delta_{(i j) k} = 1/2 \Delta^{k} (\Delta_{i j k} + \Delta_{j i k})
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                RbarDD[i][j] += sp.Rational(1, 2) * DGammaU[k] * (
                    DGammaDDD[i][j][k] + DGammaDDD[j][i][k])

    # Step 7.d.iv: Add the final term to RbarDD:
    #      Rbar_{ij} += \bar{\gamma}^{k l} (\Delta^{m}_{k i} \Delta_{j m l}
    #                   + \Delta^{m}_{k j} \Delta_{i m l}
    #                   + \Delta^{m}_{i k} \Delta_{m j l})
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    for m in range(DIM):
                        RbarDD[i][j] += gammabarUU[k][l] * (
                            DGammaUDD[m][k][i] * DGammaDDD[j][m][l] +
                            DGammaUDD[m][k][j] * DGammaDDD[i][m][l] +
                            DGammaUDD[m][i][k] * DGammaDDD[m][j][l])
def GiRaFFE_Higher_Order_v2():
    #Step 1.0: Set the spatial dimension parameter to 3.
    par.set_parval_from_str("grid::DIM", 3)
    DIM = par.parval_from_str("grid::DIM")

    # Step 1.1: Set the finite differencing order to 4.
    #par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 4)

    thismodule = "GiRaFFE_NRPy"

    # M_PI will allow the C code to substitute the correct value
    M_PI = par.Cparameters("#define",thismodule,"M_PI","")
    # ADMBase defines the 4-metric in terms of the 3+1 spacetime metric quantities gamma_{ij}, beta^i, and alpha
    gammaDD = ixp.register_gridfunctions_for_single_rank2("AUX","gammaDD", "sym01",DIM=3)
    betaU   = ixp.register_gridfunctions_for_single_rank1("AUX","betaU",DIM=3)
    alpha   = gri.register_gridfunctions("AUX","alpha")
    # GiRaFFE uses the Valencia 3-velocity and A_i, which are defined in the initial data module(GiRaFFEfood)
    ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUX","ValenciavU",DIM=3)
    AD = ixp.register_gridfunctions_for_single_rank1("EVOL","AD",DIM=3)
    # B^i must be computed at each timestep within GiRaFFE so that the Valencia 3-velocity can be evaluated
    BU = ixp.register_gridfunctions_for_single_rank1("AUX","BU",DIM=3)


    # <a id='step3'></a>
    # 
    # ## Step 1.2: Build the four metric $g_{\mu\nu}$, its inverse $g^{\mu\nu}$ and spatial derivatives $g_{\mu\nu,i}$ from ADM 3+1 quantities $\gamma_{ij}$, $\beta^i$, and $\alpha$ \[Back to [top](#top)\]
    # 
    # Notice that the time evolution equation for $\tilde{S}_i$
    # $$
    # \partial_t \tilde{S}_i = - \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}
    # $$
    # contains $\partial_i g_{\mu \nu} = g_{\mu\nu,i}$. We will now focus on evaluating this term.
    # 
    # The four-metric $g_{\mu\nu}$ is related to the three-metric $\gamma_{ij}$, index-lowered shift $\beta_i$, and lapse $\alpha$ by  
    # $$
    # g_{\mu\nu} = \begin{pmatrix} 
    # -\alpha^2 + \beta^k \beta_k & \beta_j \\
    # \beta_i & \gamma_{ij}
    # \end{pmatrix}.
    # $$
    # This tensor and its inverse have already been built by the u0_smallb_Poynting__Cartesian.py module ([documented here](Tutorial-u0_smallb_Poynting-Cartesian.ipynb)), so we can simply load the module and import the variables.
    # $$\label{step3}$$


    # Step 1.2: import u0_smallb_Poynting__Cartesian.py to set 
    #           the four metric and its inverse. This module also sets b^2 and u^0.
    import u0_smallb_Poynting__Cartesian.u0_smallb_Poynting__Cartesian as u0b
    u0b.compute_u0_smallb_Poynting__Cartesian(gammaDD,betaU,alpha,ValenciavU,BU)

    betaD = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            betaD[i] += gammaDD[i][j] * betaU[j]
    # Error check: fixed = to +=

    # We will now pull in the four metric and its inverse.
    import BSSN.ADMBSSN_tofrom_4metric as AB4m # NRPy+: ADM/BSSN <-> 4-metric conversions
    AB4m.g4DD_ito_BSSN_or_ADM("ADM")
    g4DD = AB4m.g4DD
    AB4m.g4UU_ito_BSSN_or_ADM("ADM")
    g4UU = AB4m.g4UU


    # Next we compute spatial derivatives of the metric, $\partial_i g_{\mu\nu} = g_{\mu\nu,i}$, written in terms of the three-metric, shift, and lapse. Simply taking the derivative of the expression for $g_{\mu\nu}$ above, we find
    # $$
    # g_{\mu\nu,l} = \begin{pmatrix} 
    # -2\alpha \alpha_{,l} + \beta^k_{\ ,l} \beta_k + \beta^k \beta_{k,l} & \beta_{j,l} \\
    # \beta_{i,l} & \gamma_{ij,l}
    # \end{pmatrix}.
    # $$
    # 
    # Notice the derivatives of the shift vector with its indexed lowered, $\beta_{i,j} = \partial_j \beta_i$. This can be easily computed in terms of the given ADMBase quantities $\beta^i$ and $\gamma_{ij}$ via:
    # \begin{align}
    # \beta_{i,j} &= \partial_j \beta_i \\
    #             &= \partial_j (\gamma_{ik} \beta^k) \\
    #             &= \gamma_{ik} \partial_j\beta^k + \beta^k \partial_j \gamma_{ik} \\
    # \beta_{i,j} &= \gamma_{ik} \beta^k_{\ ,j} + \beta^k \gamma_{ik,j}.
    # \end{align}
    # 
    # Since this expression mixes Greek and Latin indices, we will declare this as a 4 dimensional quantity, but only set the three spatial components of its last index (that is, leaving $l=0$ unset).
    # 
    # So, we will first set 
    # $$ g_{00,l} = \underbrace{-2\alpha \alpha_{,l}}_{\rm Term\ 1} + \underbrace{\beta^k_{\ ,l} \beta_k}_{\rm Term\ 2} + \underbrace{\beta^k \beta_{k,l}}_{\rm Term\ 3} $$


    # Step 1.2, cont'd: Build spatial derivatives of the four metric
    # Step 1.2.a: Declare derivatives of grid functions. These will be handled by FD_outputC
    alpha_dD   = ixp.declarerank1("alpha_dD")
    betaU_dD   = ixp.declarerank2("betaU_dD","nosym")
    gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01")

    # Step 1.2.b: These derivatives will be constructed analytically.
    betaDdD    = ixp.zerorank2()
    g4DDdD     = ixp.zerorank3(DIM=4)

    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                # \gamma_{ik} \beta^k_{,j} + \beta^k \gamma_{ik,j}
                betaDdD[i][j] += gammaDD[i][k] * betaU_dD[k][j] + betaU[k] * gammaDD_dD[i][k][j]
    #Error check: changed = to += above

    # Step 1.2.c: Set the 00 components
    # Step 1.2.c.i: Term 1: -2\alpha \alpha_{,l}
    for l in range(DIM):
        g4DDdD[0][0][l+1] = -2*alpha*alpha_dD[l]

    # Step 1.2.c.ii: Term 2: \beta^k_{\ ,l} \beta_k
    for l in range(DIM):
        for k in range(DIM):
            g4DDdD[0][0][l+1] += betaU_dD[k][l] * betaD[k]

    # Step 1.2.c.iii: Term 3: \beta^k \beta_{k,l}
    for l in range(DIM):
        for k in range(DIM):
            g4DDdD[0][0][l+1] += betaU[k] * betaDdD[k][l]


    # Now we will contruct the other components of $g_{\mu\nu,l}$. We will first construct 
    # $$ g_{i0,l} = g_{0i,l} = \beta_{i,l}, $$
    # then
    # $$ g_{ij,l} = \gamma_{ij,l} $$


    # Step 1.2.d: Set the i0 and 0j components
    for l in range(DIM):
        for i in range(DIM):
            # \beta_{i,l}
            g4DDdD[i+1][0][l+1] = g4DDdD[0][i+1][l+1] = betaDdD[i][l]

    #Step 1.2.e: Set the ij components
    for l in range(DIM):
        for i in range(DIM):
            for j in range(DIM):
                # \gamma_{ij,l}
                g4DDdD[i+1][j+1][l+1] = gammaDD_dD[i][j][l]


    # <a id='step4'></a>
    # 
    # # $T^{\mu\nu}_{\rm EM}$ and its derivatives
    # 
    # Now that the metric and its derivatives are out of the way, let's return to the evolution equation for $\tilde{S}_i$,
    # $$
    # \partial_t \tilde{S}_i = - \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}.
    # $$ 
    # We turn our focus now to $T^j_{{\rm EM} i}$ and its derivatives. To this end, we start by computing $T^{\mu \nu}_{\rm EM}$ (from eq. 27 of [Paschalidis & Shapiro's paper on their GRFFE code](https://arxiv.org/pdf/1310.3274.pdf)):
    # 
    # $$\boxed{T^{\mu \nu}_{\rm EM} = b^2 u^{\mu} u^{\nu} + \frac{b^2}{2} g^{\mu \nu} - b^{\mu} b^{\nu}.}$$
    # 
    # Notice that $T^{\mu\nu}_{\rm EM}$ is written in terms of
    # 
    # * $b^\mu$, the 4-component magnetic field vector, related to the comoving magnetic field vector $B^i_{(u)}$
    # * $u^\mu$, the 4-velocity
    # * $g^{\mu \nu}$, the inverse 4-metric
    # 
    # However, $\texttt{GiRaFFE}$ has access to only the following quantities, requiring in the following sections that we write the above quantities in terms of the following ones:
    # 
    # * $\gamma_{ij}$, the 3-metric
    # * $\alpha$, the lapse
    # * $\beta^i$, the shift
    # * $A_i$, the vector potential
    # * $B^i$, the magnetic field (we assume only in the grid interior, not the ghost zones)
    # * $\left[\sqrt{\gamma}\Phi\right]$, the zero-component of the vector potential $A_\mu$, times the square root of the determinant of the 3-metric
    # * $v_{(n)}^i$, the Valencia 3-velocity
    # * $u^0$, the zero-component of the 4-velocity
    # 
    # ## Step 2.0: $u^i$ and $b^i$ and related quantities \[Back to [top](#top)\]
    # 
    # We begin by importing what we can from u0_smallb_Poynting__Cartesian.py. We will need the four-velocity $u^\mu$, which is related to the Valencia 3-velocity $v^i_{(n)}$ used directly by $\texttt{GiRaFFE}$ (see also [Duez, et al, eqs. 53 and 56](https://arxiv.org/pdf/astro-ph/0503420.pdf))
    # \begin{align}
    # u^i &= u^0 (\alpha v^i_{(n)} - \beta^i), \\
    # u_j &= \alpha u^0 \gamma_{ij} v^i_{(n)},
    # \end{align}
    # where $v^i_{(n)}$ is the Valencia three-velocity. These have already been constructed in terms of the Valencia 3-velocity and other 3+1 ADM quantities by the u0_smallb_Poynting__Cartesian.py module, so we can simply import these variables:
    # $$\label{step4}$$


    # Step 2.0: u^i, b^i, and related quantities
    # Step 2.0.a: import the four-velocity, as written in terms of the Valencia 3-velocity
    uD = ixp.zerorank1()
    uU = ixp.zerorank1()
    u4upperZero = gri.register_gridfunctions("AUX","u4upperZero")

    for i in range(DIM):
        uD[i] = u0b.uD[i].subs(u0b.u0,u4upperZero)
        uU[i] = u0b.uU[i].subs(u0b.u0,u4upperZero)


    # We also need the magnetic field 4-vector $b^{\mu}$, which is related to the magnetic field by [eqs. 23, 24, and 31 in Duez, et al](https://arxiv.org/pdf/astro-ph/0503420.pdf):
    # \begin{align}
    # b^0 &= \frac{1}{\sqrt{4\pi}} B^0_{\rm (u)} = \frac{u_j B^j}{\sqrt{4\pi}\alpha}, \\
    # b^i &= \frac{1}{\sqrt{4\pi}} B^i_{\rm (u)} = \frac{B^i + (u_j B^j) u^i}{\sqrt{4\pi}\alpha u^0}, \\
    # \end{align}
    # where $B^i$ is the variable tracked by the HydroBase thorn in the Einstein Toolkit. Again, these have already been built by the u0_smallb_Poynting__Cartesian.py module, so we can simply import the variables.


    # Step 2.0.b: import the small b terms
    smallb4U = ixp.zerorank1(DIM=4)
    smallb4D = ixp.zerorank1(DIM=4)
    for mu in range(4):
        smallb4U[mu] = u0b.smallb4U[mu].subs(u0b.u0,u4upperZero)
        smallb4D[mu] = u0b.smallb4D[mu].subs(u0b.u0,u4upperZero)

    smallb2 = u0b.smallb2etk.subs(u0b.u0,u4upperZero)


    # <a id='step5'></a>
    # 
    # ## Step 2.1: Construct all components of the electromagnetic stress-energy tensor $T^{\mu \nu}_{\rm EM}$ \[Back to [top](#top)\]
    # 
    # We now have all the pieces to calculate the stress-energy tensor,
    # $$T^{\mu \nu}_{\rm EM} = \underbrace{b^2 u^{\mu} u^{\nu}}_{\rm Term\ 1} + 
    # \underbrace{\frac{b^2}{2} g^{\mu \nu}}_{\rm Term\ 2}
    # - \underbrace{b^{\mu} b^{\nu}}_{\rm Term\ 3}.$$
    # Because $u^0$ is a separate variable, we could build the $00$ component separately, then the $\mu0$ and $0\nu$ components, and finally the $\mu\nu$ components. Alternatively, for clarity, we could create a temporary variable $u^\mu=\left( u^0, u^i \right)$
    # $$\label{step5}$$


    # Step 2.1: Construct the electromagnetic stress-energy tensor
    # Step 2.1.a: Set up the four-velocity vector
    u4U = ixp.zerorank1(DIM=4)
    u4U[0] = u4upperZero
    for i in range(DIM):
        u4U[i+1] = uU[i]

    # Step 2.1.b: Build T4EMUU itself
    T4EMUU = ixp.zerorank2(DIM=4)
    for mu in range(4):
        for nu in range(4):
            # Term 1: b^2 u^{\mu} u^{\nu}
            T4EMUU[mu][nu] = smallb2*u4U[mu]*u4U[nu]

    for mu in range(4):
        for nu in range(4):
            # Term 2: b^2 / 2 g^{\mu \nu}
            T4EMUU[mu][nu] += smallb2*g4UU[mu][nu]/2

    for mu in range(4):
        for nu in range(4):
            # Term 3: -b^{\mu} b^{\nu}
            T4EMUU[mu][nu] += -smallb4U[mu]*smallb4U[nu]


    # 
    # # Evolution equation for $\tilde{S}_i$
    # <a id='step7'></a>
    # 
    # ## Step 3.0: Construct the evolution equation for $\tilde{S}_i$ \[Back to [top](#top)\]
    # 
    # Finally, we will return our attention to the time evolution equation (from eq. 13 of the [original paper](https://arxiv.org/pdf/1704.00599.pdf)),
    # \begin{align}
    # \partial_t \tilde{S}_i &= - \partial_j \underbrace{\left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right)}_{\rm SevolParenUD} + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} \\
    #                        &= - \partial_j{\rm SevolParenUD[i][j]} + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} .
    # \end{align}
    # We will first take construct ${\rm SevolParenUD}$, then use its derivatives to construct the evolution equation. Note that 
    # \begin{align}
    # {\rm SevolParenUD[i][j]} &= \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \\
    #                              &= \alpha \sqrt{\gamma} g_{\mu i} T^{\mu j}_{\rm EM}.
    # \end{align}
    # $$\label{step7}$$


    # Step 3.0: Construct the evolution equation for \tilde{S}_i
    # Here, we set up the necessary machinery to take FD derivatives of alpha * sqrt(gamma)
    global gammaUU,gammadet
    gammaUU = ixp.register_gridfunctions_for_single_rank2("AUX","gammaUU","sym01")
    gammadet = gri.register_gridfunctions("AUX","gammadet")
    gammaUU, gammadet = ixp.symm_matrix_inverter3x3(gammaDD)

    alpsqrtgam = alpha*sp.sqrt(gammadet)

    global SevolParenUD
    SevolParenUD = ixp.register_gridfunctions_for_single_rank2("AUX","SevolParenUD","nosym")
    SevolParenUD = ixp.zerorank2()

    for i in range(DIM):
        for j in range (DIM):
            for mu in range(4):
                SevolParenUD[j][i] += alpsqrtgam * g4DD[mu][i+1] * T4EMUU[mu][j+1]

    SevolParenUD_dD = ixp.declarerank3("SevolParenUD_dD","nosym")

    global Stilde_rhsD
    Stilde_rhsD = ixp.zerorank1()
    # The first term: \alpha \sqrt{\gamma} \partial_j T^j_{{\rm EM} i}
    for i in range(DIM):
        for j in range(DIM):
            Stilde_rhsD[i] += -SevolParenUD_dD[j][i][j]

    # The second term: \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} / 2
    for i in range(DIM):
        for mu in range(4):
            for nu in range(4):
                Stilde_rhsD[i] += alpsqrtgam * T4EMUU[mu][nu] * g4DDdD[mu][nu][i+1] / 2


    # # Evolution equations for $A_i$ and $\Phi$
    # <a id='step8'></a>
    # 
    # ## Step 4.0: Construct the evolution equations for $A_i$ and $[\sqrt{\gamma}\Phi]$ \[Back to [top](#top)\]
    # 
    # We will also need to evolve the vector potential $A_i$. This evolution is given as eq. 17 in the [$\texttt{GiRaFFE}$](https://arxiv.org/pdf/1704.00599.pdf) paper:
    # $$\boxed{\partial_t A_i = \epsilon_{ijk} v^j B^k - \partial_i (\underbrace{\alpha \Phi - \beta^j A_j}_{\rm AevolParen}),}$$
    # where $\epsilon_{ijk} = [ijk] \sqrt{\gamma}$ is the antisymmetric Levi-Civita tensor, the drift velocity $v^i = u^i/u^0$, $\gamma$ is the determinant of the three metric, $B^k$ is the magnetic field, $\alpha$ is the lapse, and $\beta$ is the shift.
    # The scalar electric potential $\Phi$ is also evolved by eq. 19:
    # $$\boxed{\partial_t [\sqrt{\gamma} \Phi] = -\partial_j (\underbrace{\alpha\sqrt{\gamma} \gamma^{ij} A_i - \beta^j [\sqrt{\gamma} \Phi]}_{\rm PevolParenU[j]}) - \xi \alpha [\sqrt{\gamma} \Phi],}$$
    # with $\xi$ chosen as a damping factor. 
    # 
    # ### Step 4.0.a: Construct some useful auxiliary gridfunctions for the other evolution equations
    # 
    # After declaring a  some needed quantities, we will also define the parenthetical terms (underbrace above) that we need to take derivatives of. That way, we can take finite-difference derivatives easily. Note that the above equations incorporate the fact that $\gamma^{ij}$ is the appropriate raising operator for $A_i$: $A^j = \gamma^{ij} A_i$. This is correct since $n_\mu A^\mu = 0$, where $n_\mu$ is a normal to the hypersurface, so $A^0=0$ (according to Sec. II, subsection C of [the "Improved EM gauge condition" paper of Etienne *et al*](https://arxiv.org/pdf/1110.4633.pdf)).
    # $$\label{step8}$$


    # Step 4.0: Construct the evolution equations for A_i and sqrt(gamma)Phi
    # Step 4.0.a: Construct some useful auxiliary gridfunctions for the other evolution equations
    xi = par.Cparameters("REAL",thismodule,"xi", 0.1) # The (dimensionful) Lorenz damping factor. Recommendation: set to ~1.5/max(delta t).

    # Define sqrt(gamma)Phi as psi6Phi
    psi6Phi = gri.register_gridfunctions("EVOL","psi6Phi")
    Phi = psi6Phi / sp.sqrt(gammadet)

    # We'll define a few extra gridfunctions to avoid complicated derivatives
    global AevolParen,PevolParenU
    AevolParen  = gri.register_gridfunctions("AUX","AevolParen")
    PevolParenU = ixp.register_gridfunctions_for_single_rank1("AUX","PevolParenU")

    # {\rm AevolParen} = \alpha \Phi - \beta^j A_j
    AevolParen = alpha*Phi
    for j in range(DIM):
        AevolParen    += -betaU[j] * AD[j]

    # {\rm PevolParenU[j]} = \alpha\sqrt{\gamma} \gamma^{ij} A_i - \beta^j [\sqrt{\gamma} \Phi]
    for j in range(DIM):
        PevolParenU[j] = -betaU[j] * psi6Phi
        for i in range(DIM):
            PevolParenU[j] += alpsqrtgam * gammaUU[i][j] * AD[i]

    AevolParen_dD  = ixp.declarerank1("AevolParen_dD")
    PevolParenU_dD = ixp.declarerank2("PevolParenU_dD","nosym")


    # ### Step 4.0.b: Complete the construction of the evolution equations for $A_i$ and $[\sqrt{\gamma}\Phi]$
    # 
    # Now to set the evolution equations ([eqs. 17 and 19](https://arxiv.org/pdf/1704.00599.pdf)), recalling that the drift velocity $v^i = u^i/u^0$:
    # \begin{align}
    # \partial_t A_i &= \epsilon_{ijk} v^j B^k - \partial_i (\alpha \Phi - \beta^j A_j) \\
    #                &= \epsilon_{ijk} \frac{u^j}{u^0} B^k - {\rm AevolParen\_dD[i]} \\
    # \partial_t [\sqrt{\gamma} \Phi] &= -\partial_j \left(\left(\alpha\sqrt{\gamma}\right)A^j - \beta^j [\sqrt{\gamma} \Phi]\right) - \xi \alpha [\sqrt{\gamma} \Phi] \\
    #                                 &= -{\rm PevolParenU\_dD[j][j]} - \xi \alpha [\sqrt{\gamma} \Phi]. \\
    # \end{align}


    # Step 4.0.b: Construct the actual evolution equations for A_i and sqrt(gamma)Phi
    global A_rhsD,psi6Phi_rhs
    A_rhsD = ixp.zerorank1()
    psi6Phi_rhs = sp.sympify(0)

    # We already have a handy function to define the Levi-Civita symbol in WeylScalars
    import WeylScal4NRPy.WeylScalars_Cartesian as weyl
    # Initialize the Levi-Civita tensor by setting it equal to the Levi-Civita symbol
    LeviCivitaSymbolDDD = weyl.define_LeviCivitaSymbol_rank3()
    LeviCivitaTensorDDD = ixp.zerorank3()
    #LeviCivitaTensorUUU = ixp.zerorank3()

    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                LeviCivitaTensorDDD[i][j][k] = LeviCivitaSymbolDDD[i][j][k] * sp.sqrt(gammadet)
                #LeviCivitaTensorUUU[i][j][k] = LeviCivitaSymbolDDD[i][j][k] / sp.sqrt(gammadet)

    for i in range(DIM):
        A_rhsD[i] = -AevolParen_dD[i]
        for j in range(DIM):
            for k in range(DIM):
                A_rhsD[i] += LeviCivitaTensorDDD[i][j][k]*(uU[j]/u4upperZero)*BU[k]

    psi6Phi_rhs = -xi*alpha*psi6Phi
    for j in range(DIM):
        psi6Phi_rhs += -PevolParenU_dD[j][j]
Example #10
0
def GiRaFFE_Higher_Order():
    #Step 1.0: Set the spatial dimension parameter to 3.
    par.set_parval_from_str("grid::DIM", 3)
    DIM = par.parval_from_str("grid::DIM")

    # Step 1.1: Set the finite differencing order to 4.
    #par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 4)

    thismodule = "GiRaFFE_NRPy"

    # M_PI will allow the C code to substitute the correct value
    M_PI = par.Cparameters("#define", thismodule, "M_PI", "")
    # ADMBase defines the 4-metric in terms of the 3+1 spacetime metric quantities gamma_{ij}, beta^i, and alpha
    gammaDD = ixp.register_gridfunctions_for_single_rank2("AUX",
                                                          "gammaDD",
                                                          "sym01",
                                                          DIM=3)
    betaU = ixp.register_gridfunctions_for_single_rank1("AUX", "betaU", DIM=3)
    alpha = gri.register_gridfunctions("AUX", "alpha")
    # GiRaFFE uses the Valencia 3-velocity and A_i, which are defined in the initial data module(GiRaFFEfood)
    ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUX",
                                                             "ValenciavU",
                                                             DIM=3)
    AD = ixp.register_gridfunctions_for_single_rank1("EVOL", "AD", DIM=3)
    # B^i must be computed at each timestep within GiRaFFE so that the Valencia 3-velocity can be evaluated
    BU = ixp.register_gridfunctions_for_single_rank1("AUX", "BU", DIM=3)

    # <a id='step3'></a>
    #
    # ## Step 1.2: Build the four metric $g_{\mu\nu}$, its inverse $g^{\mu\nu}$ and spatial derivatives $g_{\mu\nu,i}$ from ADM 3+1 quantities $\gamma_{ij}$, $\beta^i$, and $\alpha$
    #
    # $$\label{step3}$$
    # \[Back to [top](#top)\]
    #
    # Notice that the time evolution equation for $\tilde{S}_i$
    # $$
    # \partial_t \tilde{S}_i = - \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}
    # $$
    # contains $\partial_i g_{\mu \nu} = g_{\mu\nu,i}$. We will now focus on evaluating this term.
    #
    # The four-metric $g_{\mu\nu}$ is related to the three-metric $\gamma_{ij}$, index-lowered shift $\beta_i$, and lapse $\alpha$ by
    # $$
    # g_{\mu\nu} = \begin{pmatrix}
    # -\alpha^2 + \beta^k \beta_k & \beta_j \\
    # \beta_i & \gamma_{ij}
    # \end{pmatrix}.
    # $$
    # This tensor and its inverse have already been built by the u0_smallb_Poynting__Cartesian.py module ([documented here](Tutorial-u0_smallb_Poynting-Cartesian.ipynb)), so we can simply load the module and import the variables.

    # Step 1.2: import u0_smallb_Poynting__Cartesian.py to set
    #           the four metric and its inverse. This module also sets b^2 and u^0.
    import u0_smallb_Poynting__Cartesian.u0_smallb_Poynting__Cartesian as u0b
    u0b.compute_u0_smallb_Poynting__Cartesian(gammaDD, betaU, alpha,
                                              ValenciavU, BU)

    betaD = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            betaD[i] += gammaDD[i][j] * betaU[j]

    # We will now pull in the four metric and its inverse.
    import BSSN.ADMBSSN_tofrom_4metric as AB4m  # NRPy+: ADM/BSSN <-> 4-metric conversions
    AB4m.g4DD_ito_BSSN_or_ADM("ADM")
    g4DD = AB4m.g4DD
    AB4m.g4UU_ito_BSSN_or_ADM("ADM")
    g4UU = AB4m.g4UU

    # Next we compute spatial derivatives of the metric, $\partial_i g_{\mu\nu} = g_{\mu\nu,i}$, written in terms of the three-metric, shift, and lapse. Simply taking the derivative of the expression for $g_{\mu\nu}$ above, we find
    # $$
    # g_{\mu\nu,l} = \begin{pmatrix}
    # -2\alpha \alpha_{,l} + \beta^k_{\ ,l} \beta_k + \beta^k \beta_{k,l} & \beta_{i,l} \\
    # \beta_{j,l} & \gamma_{ij,l}
    # \end{pmatrix}.
    # $$
    #
    # Notice the derivatives of the shift vector with its indexed lowered, $\beta_{i,j} = \partial_j \beta_i$. This can be easily computed in terms of the given ADMBase quantities $\beta^i$ and $\gamma_{ij}$ via:
    # \begin{align}
    # \beta_{i,j} &= \partial_j \beta_i \\
    #             &= \partial_j (\gamma_{ik} \beta^k) \\
    #             &= \gamma_{ik} \partial_j\beta^k + \beta^k \partial_j \gamma_{ik} \\
    # \beta_{i,j} &= \gamma_{ik} \beta^k_{\ ,j} + \beta^k \gamma_{ik,j}.
    # \end{align}
    #
    # Since this expression mixes Greek and Latin indices, we will need to store the expressions for each of the three spatial derivatives as separate variables.
    #
    # So, we will first set
    # $$ g_{00,l} = \underbrace{-2\alpha \alpha_{,l}}_{\rm Term\ 1} + \underbrace{\beta^k_{\ ,l} \beta_k}_{\rm Term\ 2} + \underbrace{\beta^k \beta_{k,l}}_{\rm Term\ 3} $$

    # Step 1.2, cont'd: Build spatial derivatives of the four metric
    # Step 1.2.a: Declare derivatives of grid functions. These will be handled by FD_outputC
    alpha_dD = ixp.declarerank1("alpha_dD")
    betaU_dD = ixp.declarerank2("betaU_dD", "nosym")
    gammaDD_dD = ixp.declarerank3("gammaDD_dD", "sym01")

    # Step 1.2.b: These derivatives will be constructed analytically.
    betaDdD = ixp.zerorank2()
    g4DDdD = ixp.zerorank3(DIM=4)

    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                # \gamma_{ik} \beta^k_{,j} + \beta^k \gamma_{ik,j}
                betaDdD[i][j] += gammaDD[i][k] * betaU_dD[k][j] + betaU[
                    k] * gammaDD_dD[i][k][j]

    # Step 1.2.c: Set the 00 components
    # Step 1.2.c.i: Term 1: -2\alpha \alpha_{,l}
    for l in range(DIM):
        g4DDdD[0][0][l + 1] = -2 * alpha * alpha_dD[l]

    # Step 1.2.c.ii: Term 2: \beta^k_{\ ,l} \beta_k
    for l in range(DIM):
        for k in range(DIM):
            g4DDdD[0][0][l + 1] += betaU_dD[k][l] * betaD[k]

    # Step 1.2.c.iii: Term 3: \beta^k \beta_{k,l}
    for l in range(DIM):
        for k in range(DIM):
            g4DDdD[0][0][l + 1] += betaU[k] * betaDdD[k][l]

    # Now we will contruct the other components of $g_{\mu\nu,l}$. We will first construct
    # $$ g_{i0,l} = g_{0i,l} = \beta_{i,l}, $$
    # then
    # $$ g_{ij,l} = \gamma_{ij,l} $$

    # Step 1.2.d: Set the i0 and 0j components
    for l in range(DIM):
        for i in range(DIM):
            # \beta_{i,l}
            g4DDdD[i + 1][0][l + 1] = g4DDdD[0][i + 1][l + 1] = betaDdD[i][l]

    #Step 1.2.e: Set the ij components
    for l in range(DIM):
        for i in range(DIM):
            for j in range(DIM):
                # \gamma_{ij,l}
                g4DDdD[i + 1][j + 1][l + 1] = gammaDD_dD[i][j][l]

    # <a id='step4'></a>
    #
    # # $T^{\mu\nu}_{\rm EM}$ and its derivatives
    #
    # Now that the metric and its derivatives are out of the way, let's return to the evolution equation for $\tilde{S}_i$,
    # $$
    # \partial_t \tilde{S}_i = - \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}.
    # $$
    # We turn our focus now to $T^j_{{\rm EM} i}$ and its derivatives. To this end, we start by computing $T^{\mu \nu}_{\rm EM}$ (from eq. 27 of [Paschalidis & Shapiro's paper on their GRFFE code](https://arxiv.org/pdf/1310.3274.pdf)):
    #
    # $$\boxed{T^{\mu \nu}_{\rm EM} = b^2 u^{\mu} u^{\nu} + \frac{b^2}{2} g^{\mu \nu} - b^{\mu} b^{\nu}.}$$
    #
    # Notice that $T^{\mu\nu}_{\rm EM}$ is written in terms of
    #
    # * $b^\mu$, the 4-component magnetic field vector, related to the comoving magnetic field vector $B^i_{(u)}$
    # * $u^\mu$, the 4-velocity
    # * $g^{\mu \nu}$, the inverse 4-metric
    #
    # However, $\texttt{GiRaFFE}$ has access to only the following quantities, requiring in the following sections that we write the above quantities in terms of the following ones:
    #
    # * $\gamma_{ij}$, the 3-metric
    # * $\alpha$, the lapse
    # * $\beta^i$, the shift
    # * $A_i$, the vector potential
    # * $B^i$, the magnetic field (we assume only in the grid interior, not the ghost zones)
    # * $\left[\sqrt{\gamma}\Phi\right]$, the zero-component of the vector potential $A_\mu$, times the square root of the determinant of the 3-metric
    # * $v_{(n)}^i$, the Valencia 3-velocity
    # * $u^0$, the zero-component of the 4-velocity
    #
    # ## Step 2.0: $u^i$ and $b^i$ and related quantities
    # $$\label{step4}$$
    # \[Back to [top](#top)\]
    #
    # We begin by importing what we can from u0_smallb_Poynting__Cartesian.py. We will need the four-velocity $u^\mu$, which is related to the Valencia 3-velocity $v^i_{(n)}$ used directly by $\texttt{GiRaFFE}$ (see also [Duez, et al, eqs. 53 and 56](https://arxiv.org/pdf/astro-ph/0503420.pdf))
    # \begin{align}
    # u^i &= u^0 (\alpha v^i_{(n)} - \beta^i), \\
    # u_j &= \alpha u^0 \gamma_{ij} v^i_{(n)},
    # \end{align}
    # and $v^i_{(n)}$ is the Valencia three-velocity. These have already been constructed in terms of the Valencia 3-velocity and other 3+1 ADM quantities by the u0_smallb_Poynting__Cartesian.py module, so we can simply import these variables:

    # Step 2.0: u^i, b^i, and related quantities
    # Step 2.0.a: import the four-velocity, as written in terms of the Valencia 3-velocity
    global uD, uU
    uD = ixp.register_gridfunctions_for_single_rank1("AUX", "uD")
    uU = ixp.register_gridfunctions_for_single_rank1("AUX", "uU")
    u4upperZero = gri.register_gridfunctions("AUX", "u4upperZero")

    for i in range(DIM):
        uD[i] = u0b.uD[i].subs(u0b.u0, u4upperZero)
        uU[i] = u0b.uU[i].subs(u0b.u0, u4upperZero)

    # We also need the magnetic field 4-vector $b^{\mu}$, which is related to the magnetic field by [eqs. 23, 24, and 31 in Duez, et al](https://arxiv.org/pdf/astro-ph/0503420.pdf):
    # \begin{align}
    # b^0 &= \frac{1}{\sqrt{4\pi}} B^0_{\rm (u)} = \frac{u_j B^j}{\sqrt{4\pi}\alpha}, \\
    # b^i &= \frac{1}{\sqrt{4\pi}} B^i_{\rm (u)} = \frac{B^i + (u_j B^j) u^i}{\sqrt{4\pi}\alpha u^0}, \\
    # \end{align}
    # where $B^i$ is the variable tracked by the HydroBase thorn in the Einstein Toolkit. Again, these have already been built by the u0_smallb_Poynting__Cartesian.py module, so we can simply import the variables.

    # Step 2.0.b: import the small b terms
    smallb4U = ixp.zerorank1(DIM=4)
    smallb4D = ixp.zerorank1(DIM=4)
    for mu in range(4):
        smallb4U[mu] = u0b.smallb4U[mu].subs(u0b.u0, u4upperZero)
        smallb4D[mu] = u0b.smallb4D[mu].subs(u0b.u0, u4upperZero)

    smallb2 = u0b.smallb2etk.subs(u0b.u0, u4upperZero)

    # <a id='step5'></a>
    #
    # ## Step 2.1: Construct all components of the electromagnetic stress-energy tensor $T^{\mu \nu}_{\rm EM}$
    # $$\label{step5}$$
    #
    # \[Back to [top](#top)\]
    #
    # We now have all the pieces to calculate the stress-energy tensor,
    # $$T^{\mu \nu}_{\rm EM} = \underbrace{b^2 u^{\mu} u^{\nu}}_{\rm Term\ 1} +
    # \underbrace{\frac{b^2}{2} g^{\mu \nu}}_{\rm Term\ 2}
    # - \underbrace{b^{\mu} b^{\nu}}_{\rm Term\ 3}.$$
    # Because $u^0$ is a separate variable, we could build the $00$ component separately, then the $\mu0$ and $0\nu$ components, and finally the $\mu\nu$ components. Alternatively, for clarity, we could create a temporary variable $u^\mu=\left( u^0, u^i \right)$

    # Step 2.1: Construct the electromagnetic stress-energy tensor
    # Step 2.1.a: Set up the four-velocity vector
    u4U = ixp.zerorank1(DIM=4)
    u4U[0] = u4upperZero
    for i in range(DIM):
        u4U[i + 1] = uU[i]

    # Step 2.1.b: Build T4EMUU itself
    T4EMUU = ixp.zerorank2(DIM=4)
    for mu in range(4):
        for nu in range(4):
            # Term 1: b^2 u^{\mu} u^{\nu}
            T4EMUU[mu][nu] = smallb2 * u4U[mu] * u4U[nu]

    for mu in range(4):
        for nu in range(4):
            # Term 2: b^2 / 2 g^{\mu \nu}
            T4EMUU[mu][nu] += smallb2 * g4UU[mu][nu] / 2

    for mu in range(4):
        for nu in range(4):
            # Term 3: -b^{\mu} b^{\nu}
            T4EMUU[mu][nu] += -smallb4U[mu] * smallb4U[nu]

    # <a id='step6'></a>
    #
    # # Step 2.2: Derivatives of the electromagnetic stress-energy tensor
    # $$\label{step6}$$
    #
    # \[Back to [top](#top)\]
    #
    # If we look at the evolution equation, we see that we will need spatial  derivatives of $T^{\mu\nu}_{\rm EM}$. When confronted with derivatives of complicated expressions, it is generally convenient to declare those expressions as gridfunctions themselves, allowing NRPy+ to take finite-difference derivatives of the expressions. This can even reduce the truncation error associated with the finite differences, because the alternative is to use a function of several finite-difference derivatives, allowing more error to accumulate than the extra gridfunction will introduce. While we will use that technique for some of the subexpressions of $T^{\mu\nu}_{\rm EM}$, we don't want to rely on it for the whole expression; doing so would require us to take the derivative of the magnetic field $B^i$, which is itself found by finite-differencing the vector potential $A_i$. Thus $B^i$ cannot be *consistently* defined in ghost zones. To potentially reduce numerical errors induced by inconsistent finite differencing, we will differentiate $T^{\mu\nu}_{\rm EM}$ term-by-term so that finite-difference derivatives of $A_i$ appear.
    #
    # We will now now take these spatial derivatives of $T^{\mu\nu}_{\rm EM}$, applying the chain rule until it is only in terms of basic gridfunctions and their derivatives: $\alpha$, $\beta^i$, $\gamma_{ij}$, $A_i$, and the four-velocity $u^i$. Along the way, we will also set up useful temporary variables representing the steps of the chain rule. (Notably, *all* of these quantities will be written in terms of $A_i$ and its derivatives):
    #
    # * $B^i$ (already computed in terms of $A_k$, via $B^i = \epsilon^{ijk} \partial_j A_k$),
    # * $B^i_{,l}$,
    # * $b^i$ and $b_i$ (already computed),
    # * $b^i_{,k}$,
    # * $b^2$ (already computed),
    # * and $\left(b^2\right)_{,j}$.
    #
    # (The variables not already computed will not be seen by the ETK, as they are written in terms of $A_i$ and its derivatives; they simply help to organize the NRPy+ code.)
    #
    # So then,
    # \begin{align}
    # \partial_j T^{j}_{{\rm EM} i} &= \partial_j (g_{\mu i} T^{\mu j}_{\rm EM}) \\
    # &= \partial_j \left[g_{\mu i} \left(b^2 u^j u^\mu + \frac{b^2}{2} g^{j\mu} - b^j b^\mu\right)\right] \\
    # &= \underbrace{g_{\mu i,j} T^{\mu j}_{\rm EM}}_{\rm Term\ A} + g_{\mu i} \left( \underbrace{\partial_j \left(b^2 u^j u^\mu \right)}_{\rm Term\ B} + \underbrace{\partial_j \left(\frac{b^2}{2} g^{j\mu}\right)}_{\rm Term\ C} - \underbrace{\partial_j \left(b^j b^k\right)}_{\rm Term\ D} \right) \\
    # \end{align}
    # Following the product and chain rules for each term, we find that
    # \begin{align}
    # {\rm Term\ B} &= \partial_j (b^2 u^j u^\mu) \\
    #               &= \partial_j b^2 u^j u^\mu + b^2 \partial_j u^j u^\mu + b^2 u^j \partial_j u^\mu \\
    #               &= \underbrace{\left(b^2\right)_{,j} u^j u^\mu + b^2 u^j_{,j} u^\mu + b^2 u^j u^{\mu}_{,j}}_{\rm To\ Term\ 2\ below} \\
    # {\rm Term\ C} &= \partial_j \left(\frac{b^2}{2} g^{j\mu}\right) \\
    #               &= \frac{1}{2} \left( \partial_j b^2 g^{j\mu} + b^2 \partial_j g^{j\mu} \right) \\
    #               &= \underbrace{\frac{1}{2} \left(b^2\right)_{,j} g^{j\mu} + \frac{b^2}{2} g^{j\mu}_{\ ,j}}_{\rm To\ Term\ 3\ below} \\
    # {\rm Term\ D} &= \partial_j (b^j b^\mu) \\
    #               &= \underbrace{b^j_{,j} b^\mu + b^j b^\mu_{,j}}_{\rm To\ Term\ 2\ below}\\
    # \end{align}
    #
    # So,
    # \begin{align}
    # \partial_j T^{j}_{{\rm EM} i} &= g_{\mu i,j} T^{\mu j}_{\rm EM} \\
    # &+ g_{\mu i} \left(\left(b^2\right)_{,j} u^j u^\mu +b^2 u^j_{,j} u^\mu + b^2 u^j u^{\mu}_{,j} + \frac{1}{2}\left(b^2\right)_{,j} g^{j\mu} + \frac{b^2}{2} g^{j\mu}_{\ ,j} + b^j_{,j} b^\mu + b^j b^\mu_{,j}\right);
    # \end{align}
    # We will rearrange this once more, collecting the $b^2$ terms together, noting that Term A will become Term 1:
    # \begin{align}
    # \partial_j  T^{j}_{{\rm EM} i} =& \
    # \underbrace{g_{\mu i,j} T^{\mu j}_{\rm EM}}_{\rm Term\ 1} \\
    # & + \underbrace{g_{\mu i} \left( b^2 u^j_{,j} u^\mu + b^2 u^j u^\mu_{,j} + \frac{b^2}{2} g^{j\mu}_{\ ,j} + b^j_{,j} b^\mu + b^j b^\mu_{,j} \right)}_{\rm Term\ 2} \\
    # & + \underbrace{g_{\mu i} \left( \left(b^2\right)_{,j} u^j u^\mu + \frac{1}{2} \left(b^2\right)_{,j} g^{j\mu} \right).}_{\rm Term\ 3} \\
    # \end{align}
    #
    # <a id='table2'></a>
    #
    # **List of Derivatives**
    # $$\label{table2}$$
    #
    # Note that this is in terms of the derivatives of several other quantities:
    #
    # * [Step 2.2.a](#capitalBideriv): $B^i_{,l}$: Since $b^i$ is itself a function of $B^i$, we will first need the derivatives $B^i_{,l}$ in terms of the evolved quantity $A_i$ (the vector potential).
    # * [Step 2.2.b](#bideriv): $b^i_{,k}$: Once we have $B^i_{,l}$ we can evaluate derivatives of $b^i$, $b^i_{,k}$
    # * [Step 2.2.c](#b2deriv): The derivative of $b^2 = g_{\mu\nu} b^\mu b^\nu$, $\left(b^2\right)_{,j}$
    # * [Step 2.2.d](#gupijderiv): Derivatives of $g^{\mu\nu}$, $g^{\mu\nu}_{\ ,k}$
    # * [Step 2.2.e](#alltogether): Putting it together: $\partial_j T^{j}_{{\rm EM} i}$
    #     * [Step 2.2.e.i](#alltogether1): Putting it together: Term 1
    #     * [Step 2.2.e.ii](#alltogether2): Putting it together: Term 2
    #     * [Step 2.2.e.iii](#alltogether3): Putting it together: Term 3

    # <a id='capitalBideriv'></a>
    #
    # ## Step 2.2.a: Derivatives of $B^i$
    #
    # $$\label{capitalbideriv}$$
    #
    # \[Back to [List of Derivatives](#table2)\]
    #
    # First, we will build the derivatives of the magnetic field. Since $b^i$ is a function of $B^i$, we will start from the definition of $B^i$ in terms of $A_i$, $B^i = \frac{[ijk]}{\sqrt{\gamma}} \partial_j A_k$. We will first apply the product rule, noting that the symbol $[ijk]$ consists purely of the integers $-1, 0, 1$ and thus can be treated as a constant in this process.
    # \begin{align}
    # B^i_{,l} &= \partial_l \left( \frac{[ijk]}{\sqrt{\gamma}} \partial_j A_k \right)  \\
    #          &= [ijk] \partial_l \left( \frac{1}{\sqrt{\gamma}}\right) \partial_j A_k + \frac{[ijk]}{\sqrt{\gamma}} \partial_l \partial_j A_k \\
    #          &= [ijk]\left(-\frac{\gamma_{,l}}{2\gamma^{3/2}}\right) \partial_j A_k + \frac{[ijk]}{\sqrt{\gamma}} \partial_l \partial_j A_k \\
    # \end{align}
    # Now, we will substitute back in for the definition of the Levi-Civita tensor: $\epsilon^{ijk} = [ijk] / \sqrt{\gamma}$. Then we will substitute the magnetic field $B^i$ back in.
    # \begin{align}
    # B^i_{,l} &= -\frac{\gamma_{,l}}{2\gamma} \epsilon^{ijk} \partial_j A_k + \epsilon^{ijk} \partial_l \partial_j A_k \\
    #          &= -\frac{\gamma_{,l}}{2\gamma} B^i + \epsilon^{ijk} A_{k,jl}, \\
    # \end{align}
    #
    # Thus, the expression we are left with for the derivatives of the magnetic field is:
    # \begin{align}
    # B^i_{,l} &= \underbrace{-\frac{\gamma_{,l}}{2\gamma} B^i}_{\rm Term\ 1} + \underbrace{\epsilon^{ijk} A_{k,jl}}_{\rm Term\ 2}, \\
    # \end{align}
    # where $\epsilon^{ijk} = [ijk] / \sqrt{\gamma}$ is the antisymmetric Levi-Civita tensor and $\gamma$ is the determinant of the three-metric.
    #

    # Step 2.2: Derivatives of the electromagnetic stress-energy tensor

    # We already have a handy function to define the Levi-Civita symbol in WeylScalars
    import WeylScal4NRPy.WeylScalars_Cartesian as weyl
    # Initialize the Levi-Civita tensor by setting it equal to the Levi-Civita symbol
    LeviCivitaSymbolDDD = weyl.define_LeviCivitaSymbol_rank3()
    LeviCivitaTensorDDD = ixp.zerorank3()
    LeviCivitaTensorUUU = ixp.zerorank3()

    global gammaUU, gammadet
    gammaUU = ixp.register_gridfunctions_for_single_rank2(
        "AUX", "gammaUU", "sym01")
    gammadet = gri.register_gridfunctions("AUX", "gammadet")
    gammaUU, gammadet = ixp.symm_matrix_inverter3x3(gammaDD)

    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                LeviCivitaTensorDDD[i][j][
                    k] = LeviCivitaSymbolDDD[i][j][k] * sp.sqrt(gammadet)
                LeviCivitaTensorUUU[i][j][
                    k] = LeviCivitaSymbolDDD[i][j][k] / sp.sqrt(gammadet)

    AD_dD = ixp.declarerank2("AD_dD", "nosym")

    # Step 2.2.a: Construct the derivatives of the magnetic field.
    gammadet_dD = ixp.declarerank1("gammadet_dD")

    AD_dDD = ixp.declarerank3("AD_dDD", "sym12")
    # The other partial derivatives of B^i
    BUdD = ixp.zerorank2()
    for i in range(DIM):
        for l in range(DIM):
            # Term 1: -\gamma_{,l} / (2\gamma) B^i
            BUdD[i][l] = -gammadet_dD[l] * BU[i] / (2 * gammadet)

    for i in range(DIM):
        for l in range(DIM):
            for j in range(DIM):
                for k in range(DIM):
                    # Term 2: \epsilon^{ijk} A_{k,jl}
                    BUdD[i][
                        l] += LeviCivitaTensorUUU[i][j][k] * AD_dDD[k][j][l]

    # <a id='bideriv'></a>
    #
    # ## Step 2.2.b: Derivatives of $b^i$
    # $$\label{bideriv}$$
    #
    # \[Back to [List of Derivatives](#table2)\]
    #
    # Now, we will code the derivatives of the spatial components of $b^{\mu}$, $b^i$:
    # $$
    # b^i_{,k} = \frac{1}{\sqrt{4 \pi}} \frac{\left(\alpha u^0\right)  \left(B^i_{,k} + u_{j,k} B^j u^i + u_j B^j_{,k} u^i + u_j B^j u^i_{,k}\right) - \left(B^i + (u_j B^j) u^i\right) \partial_k \left(\alpha u^0\right)}{\left(\alpha u^0\right)^2}.
    # $$
    #
    # We should note that while $b^\mu$ is a four-vector (and the code reflects this: $\text{smallb4U}$ and $\text{smallb4U}$ have $\text{DIM=4}$), we only need the spatial components. We will only focus on the spatial components for the moment.
    #
    #
    # Let's go into a little more detail on where this comes from. We start from the definition $$b^i = \frac{B^i + (u_j B^j) u^i}{\sqrt{4\pi}\alpha u^0};$$ we then apply the quotient rule:
    # \begin{align}
    # b^i_{,k} &= \frac{\left(\sqrt{4\pi}\alpha u^0\right) \partial_k \left(B^i + (u_j B^j) u^i\right) - \left(B^i + (u_j B^j) u^i\right) \partial_k \left(\sqrt{4\pi}\alpha u^0\right)}{\left(\sqrt{4\pi}\alpha u^0\right)^2} \\
    # &= \frac{1}{\sqrt{4 \pi}} \frac{\left(\alpha u^0\right) \partial_k \left(B^i + (u_j B^j) u^i\right) - \left(B^i + (u_j B^j) u^i\right) \partial_k \left(\alpha u^0\right)}{\left(\alpha u^0\right)^2} \\
    # \end{align}
    # Note that $\left( \alpha u^0 \right)$ is being used as its own gridfunction, so $\partial_k \left(a u^0\right)$ will be finite-differenced by NRPy+ directly. We will also apply the product rule to the term $\partial_k \left(B^i + (u_j B^j) u^i\right) = B^i_{,k} + u_{j,k} B^j u^i + u_j B^j_{,k} u^i + u_j B^j u^i_{,k}$. So,
    # $$ b^i_{,k} = \frac{1}{\sqrt{4 \pi}} \frac{\left(\alpha u^0\right)  \left(B^i_{,k} + u_{j,k} B^j u^i + u_j B^j_{,k} u^i + u_j B^j u^i_{,k}\right) - \left(B^i + (u_j B^j) u^i\right) \partial_k \left(\alpha u^0\right)}{\left(\alpha u^0\right)^2}. $$
    #
    # It will be easier to code this up if we rearrange these terms to group together the terms that involve contractions over $j$. Doing that, we find
    # $$
    # b^i_{,k} = \frac{\overbrace{\alpha u^0 B^i_{,k} - B^i \partial_k (\alpha u^0)}^{\rm Term\ Num1} + \overbrace{\left( \alpha u^0 \right) \left( u_{j,k} B^j u^i + u_j B^j_{,k} u^i + u_j B^j u^i_{,k} \right)}^{\rm Term\ Num2.a} - \overbrace{\left( u_j B^j u^i \right) \partial_k \left( \alpha u^0 \right) }^{\rm Term\ Num2.b}}{\underbrace{\sqrt{4 \pi} \left( \alpha u^0 \right)^2}_{\rm Term\ Denom}}.
    # $$

    global u0alpha
    u0alpha = gri.register_gridfunctions("AUX", "u0alpha")
    u0alpha = alpha * u4upperZero
    u0alpha_dD = ixp.declarerank1("u0alpha_dD")
    uU_dD = ixp.declarerank2("uU_dD", "nosym")
    uD_dD = ixp.declarerank2("uD_dD", "nosym")

    # Step 2.2.b: Construct derivatives of the small b vector
    # smallbUdD represents the derivative of smallb4U
    smallbUdD = ixp.zerorank2()
    for i in range(DIM):
        for k in range(DIM):
            # Term Num1: \alpha u^0 B^i_{,k} - B^i \partial_k (\alpha u^0)
            smallbUdD[i][k] += u0alpha * BUdD[i][k] - BU[i] * u0alpha_dD[k]

    for i in range(DIM):
        for k in range(DIM):
            for j in range(DIM):
                # Term Num2.a: terms that require contractions over k, and thus an extra loop.
                # ( \alpha u^0 ) (  u_{j,k} B^j u^i
                #                 + u_j B^j_{,k} u^i
                #                 + u_j B^j u^i_{,k} )
                smallbUdD[i][k] += u0alpha * (uD_dD[j][k] * BU[j] * uU[i] +
                                              uD[j] * BUdD[j][k] * uU[i] +
                                              uD[j] * BU[j] * uU_dD[i][k])

    for i in range(DIM):
        for k in range(DIM):
            for j in range(DIM):
                #Term 2.b (More contractions over k): ( u_j B^j u^i ) ( \alpha u^0 ),k
                smallbUdD[i][k] += -(uD[j] * BU[j] * uU[i]) * u0alpha_dD[k]

    for i in range(DIM):
        for k in range(DIM):
            # Term Denom: Divide the numerator by sqrt(4 pi) * (alpha u^0)^2
            smallbUdD[i][k] /= sp.sqrt(4 * M_PI) * u0alpha * u0alpha

    # <a id='b2deriv'></a>
    #
    # ## Step 2.2.c: Derivative of $b^2$
    # $$\label{b2deriv}$$
    #
    # \[Back to [List of Derivatives](#table2)\]
    #
    # Here, we will take the derivative of $b^2 = g_{\mu\nu} b^\mu b^\nu$. Using the product rule,
    # \begin{align}
    # \left(b^2\right)_{,j} &= \partial_j \left( g_{\mu\nu} b^\mu b^\nu \right) \\
    #                       &= g_{\mu\nu,j} b^\mu b^\nu + g_{\mu\nu} b^\mu_{,j} b^\nu + g_{\mu\nu} b^\mu b^\nu_{,j} \\
    #                       &= g_{\mu\nu,j} b^\mu b^\nu + 2 g_{\mu\nu} b^\mu_{,j} b^\nu.
    # \end{align}
    # We have already defined the spatial derivatives of the four-metric $g_{\mu\nu,j}$ in [this section](#step3); we have also defined the spatial derivatives of spatial components of $b^\mu$, $b^i_{,k}$ in [this section](#bideriv). Notice the above expression requires spatial derivatives of the *zeroth* component of $b^\mu$ as well, $b^0_{,j}$, which we will now compute. Starting with the definition, and applying the quotient rule:
    # \begin{align}
    # b^0 &= \frac{u_k B^k}{\sqrt{4\pi}\alpha}, \\
    # \rightarrow b^0_{,j} &= \frac{1}{\sqrt{4\pi}} \frac{\alpha \left( u_{k,j} B^k + u_k B^k_{,j} \right) - u_k B^k \alpha_{,j}}{\alpha^2} \\
    #     &= \frac{\alpha u_{k,j} B^k + \alpha u_k B^k_{,j} - \alpha_{,j} u_k B^k}{\sqrt{4\pi} \alpha^2}.
    # \end{align}
    # We will first code the numerator, and then divide through by the denominator.

    # Step 2.2.c: Construct the derivative of b^2
    # First construct the derivative b^0_{,j}
    # This four-vector will make b^2 simpler:
    smallb4UdD = ixp.zerorank2(DIM=4)
    # Fill in the zeroth component
    for j in range(DIM):
        for k in range(DIM):
            # The numerator:  \alpha u_{k,j} B^k
            #               + \alpha u_k B^k_{,j}
            #               - \alpha_{,j} u_k B^k
            smallb4UdD[0][j + 1] += alpha * uD_dD[k][j] * BU[k] + alpha * uD[
                k] * BUdD[k][j] - alpha_dD[j] * uD[k] * BU[k]
    for j in range(DIM):
        # Divide through by the denominator: \sqrt{4\pi} \alpha^2
        smallb4UdD[0][j + 1] /= sp.sqrt(4 * M_PI) * alpha * alpha

    # At this point, both $b^0_{\ ,j}$ and $b^i_{\ ,j}$ have been computed, but one exists inconveniently in the $4\times 4$ component $\verb|smallb4UdD[][]|$ and the other in the $3\times 3$ component $\verb|smallbUdD[][]|$. So that we can perform full implied sums over $g_{\mu\nu} b^\mu_{,j} b^\nu$ more conveniently, we will now store all information from $\verb|smallbUdD[i][j]|$ into $\verb|smallb4UdD[i+1][j+1]|$:

    # Now, we'll fill out the rest of the four-vector with b^i_{,j} that we derived above.
    for i in range(DIM):
        for j in range(DIM):
            smallb4UdD[i + 1][j + 1] = smallbUdD[i][j]

    # Using 4-component (Greek-indexed) quantities, we can now complete our construction of
    # $$\left(b^2\right)_{,j} = g_{\mu\nu,j} b^\mu b^\nu + 2 g_{\mu\nu} b^\mu_{,j} b^\nu:$$

    smallb2_dD = ixp.zerorank1()
    for j in range(DIM):
        for mu in range(4):
            for nu in range(4):
                #   g_{\mu\nu,j} b^\mu b^\nu
                # + 2 g_{\mu\nu} b^\mu_{,j} b^\nu
                smallb2_dD[j] += g4DDdD[mu][nu][j + 1] * smallb4U[
                    mu] * smallb4U[nu] + 2 * g4DD[mu][nu] * smallb4UdD[mu][
                        j + 1] * smallb4U[nu]

    # <a id='gupijderiv'></a>
    #
    # ## Step 2.2.d: Derivatives of $g^{\mu\nu}$
    # $$\label{gupijderiv}$$
    #
    # \[Back to [List of Derivatives](#table2)\]
    #
    # We will need derivatives of the inverse four-metric, as well. Let us begin with $g^{00}$: since $g^{00} = -1/\alpha^2$ ([Gourgoulhon, eq. 4.49](https://arxiv.org/pdf/gr-qc/0703035.pdf)), $$g^{00}_{\ ,k} = \frac{2 \alpha_{,k}}{\alpha^3}$$
    #

    # Step 2.2.d: Construct derivatives of the components of g^{\mu\nu}
    g4UUdD = ixp.zerorank3(DIM=4)

    for k in range(DIM):
        # 2 \alpha_{,k} / \alpha^3
        g4UUdD[0][0][k + 1] = 2 * alpha_dD[k] / alpha**3

    # Now, we will code the $g^{i0}_{\ ,k}$ and $g^{0j}_{\ ,k}$ components. According to [Gourgoulhon, eq. 4.49](https://arxiv.org/pdf/gr-qc/0703035.pdf), $g^{i0} = g^{0i} = \beta^i/\alpha^2$, so $$g^{i0}_{\ ,k} = g^{0i}_{\ ,k} = \frac{\alpha^2 \beta^i_{,k} - 2 \beta^i \alpha \alpha_{,k}}{\alpha^4}$$ by the quotient rule. So, we'll code
    # $$
    # g^{i0} = g^{0i} =
    # \underbrace{\frac{\beta^i_{,k}}{\alpha^2}}_{\rm Term\ 1}
    # - \underbrace{\frac{2 \beta^i \alpha_{,k}}{\alpha^3}}_{\rm Term\ 2}
    # $$

    for k in range(DIM):
        for i in range(DIM):
            # Term 1:                                    \beta^i_{,k} / \alpha^2
            g4UUdD[i + 1][0][k +
                             1] = g4UUdD[0][i +
                                            1][k +
                                               1] = betaU_dD[i][k] / alpha**2

    for k in range(DIM):
        for i in range(DIM):
            # Term 2:              -2 \beta^i \alpha_{,k} / \alpha^3
            g4UUdD[i + 1][0][k + 1] += -2 * betaU[i] * alpha_dD[k] / alpha**3
            g4UUdD[0][i + 1][k + 1] += -2 * betaU[i] * alpha_dD[k] / alpha**3

    # We will also need derivatives of the spatial part of the inverse four-metric: since $g^{ij} = \gamma^{ij} - \frac{\beta^i \beta^j}{\alpha^2}$ ([Gourgoulhon, eq. 4.49](https://arxiv.org/pdf/gr-qc/0703035.pdf)),
    # \begin{align}
    # g^{ij}_{\ ,k} &= \gamma^{ij}_{\ ,k} - \frac{\alpha^2 \partial_k (\beta^i \beta^j) - \beta^i \beta^j \partial_k \alpha^2}{(\alpha^2)^2} \\
    # &= \gamma^{ij}_{\ ,k} - \frac{\alpha^2\beta^i \beta^j_{,k}+\alpha^2\beta^i_{,k} \beta^j-2\beta^i \beta^j \alpha \alpha_{,k}}{\alpha^4}. \\
    # &= \gamma^{ij}_{\ ,k} - \frac{\alpha\beta^i \beta^j_{,k}+\alpha\beta^i_{,k} \beta^j-2\beta^i \beta^j \alpha_{,k}}{\alpha^3} \\
    # g^{ij}_{\ ,k} &= \underbrace{\gamma^{ij}_{\ ,k}}_{\rm Term\ 1} - \underbrace{\frac{\beta^i \beta^j_{,k}}{\alpha^2}}_{\rm Term\ 2} - \underbrace{\frac{\beta^i_{,k} \beta^j}{\alpha^2}}_{\rm Term\ 3} + \underbrace{\frac{2\beta^i \beta^j \alpha_{,k}}{\alpha^3}}_{\rm Term\ 4}. \\
    # \end{align}
    #

    gammaUU_dD = ixp.declarerank3("gammaUU_dD", "sym01")

    # The spatial derivatives of the spatial components of the four metric:
    # Term 1: \gamma^{ij}_{\ ,k}
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                g4UUdD[i + 1][j + 1][k + 1] = gammaUU_dD[i][j][k]

    # Term 2: - \beta^i \beta^j_{,k} / \alpha^2
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                g4UUdD[i + 1][j +
                              1][k +
                                 1] += -betaU[i] * betaU_dD[j][k] / alpha**2

    # Term 3: - \beta^i_{,k} \beta^j / \alpha^2
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                g4UUdD[i + 1][j +
                              1][k +
                                 1] += -betaU_dD[i][k] * betaU[j] / alpha**2

    # Term 4: 2\beta^i \beta^j \alpha_{,k}\alpha^3
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                g4UUdD[i + 1][j + 1][
                    k + 1] += 2 * betaU[i] * betaU[j] * alpha_dD[k] / alpha**3

    # <a id='alltogether'></a>
    #
    # ## Step 2.2.e: Putting it together:
    # $$\label{alltogether}$$
    #
    # \[Back to [List of Derivatives](#table2)\]
    #
    # So, we can now put it all together, starting from the expression we derived above in [Step 2.2](#step6):
    # \begin{align}
    # \partial_j  T^{j}_{{\rm EM} i} =& \
    # \underbrace{g_{\mu i,j} T^{\mu j}_{\rm EM}}_{\rm Term\ 1} \\
    # & + \underbrace{g_{\mu i} \left( b^2 u^j_{,j} u^\mu + b^2 u^j u^\mu_{,j} + \frac{b^2}{2} g^{j\mu}_{\ ,j} + b^j_{,j} b^\mu + b^j b^\mu_{,j} \right)}_{\rm Term\ 2} \\
    # & + \underbrace{g_{\mu i} \left( \left(b^2\right)_{,j} u^j u^\mu + \frac{1}{2} \left(b^2\right)_{,j} g^{j\mu} \right).}_{\rm Term\ 3} \\
    # \end{align}
    #
    # <a id='alltogether1'></a>
    #
    # ### Step 2.2.e.i: Putting it together: Term 1
    # $$\label{alltogether1}$$
    #
    # \[Back to [List of Derivatives](#table2)\]
    #
    # We will now construct this term by term. Term 1 is straightforward: $${\rm Term\ 1} = \gamma_{\mu i,j} T^{\mu j}_{\rm EM}.$$

    # Step 2.2.e: Construct TEMUDdD_contracted itself
    # Step 2.2.e.i
    TEMUDdD_contracted = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                # Term 1:                g_{\mu i,j} T^{\mu j}_{\rm EM}
                TEMUDdD_contracted[i] += g4DDdD[mu][i + 1][j +
                                                           1] * T4EMUU[mu][j +
                                                                           1]

    # We'll need derivatives of u4U for the next part:
    u4UdD = ixp.zerorank2(DIM=4)
    u4upperZero_dD = ixp.declarerank1(
        "u4upperZero_dD"
    )  # Note that derivatives can't be done in 4-D with the current version of NRPy
    for i in range(DIM):
        u4UdD[0][i + 1] = u4upperZero_dD[i]
    for i in range(DIM):
        for j in range(DIM):
            u4UdD[i + 1][j + 1] = uU_dD[i][j]

    # <a id='alltogether2'></a>
    #
    # ### Step 2.2.e.ii: Putting it together: Term 2
    # $$\label{alltogether2}$$
    #
    # \[Back to [List of Derivatives](#table2)\]
    #
    # We will now add $${\rm Term\ 2} = g_{\mu i} \left( \underbrace{b^2 u^j_{,j} u^\mu}_{\rm Term\ 2a} + \underbrace{b^2 u^j u^\mu_{,j}}_{\rm Term\ 2b} + \underbrace{\frac{b^2}{2} g^{j\mu}_{\ ,j}}_{\rm Term\ 2c} + \underbrace{b^j_{,j} b^\mu}_{\rm Term\ 2d} + \underbrace{b^j b^\mu_{,j}}_{\rm Term\ 2e} \right)$$ to $\partial_j  T^{j}_{{\rm EM} i}$. These are the terms that involve contractions over $k$ (but no metric derivatives like Term 1 had).
    #

    # Step 2.2.e.ii
    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                # Term 2a: g_{\mu i} b^2 u^j_{,j} u^\mu
                TEMUDdD_contracted[i] += g4DD[mu][
                    i + 1] * smallb2 * uU_dD[j][j] * u4U[mu]

    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                # Term 2b: g_{\mu i} b^2 u^j u^\mu_{,j}
                TEMUDdD_contracted[i] += g4DD[mu][
                    i + 1] * smallb2 * uU[j] * u4UdD[mu][j + 1]

    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                # Term 2c: g_{\mu i} b^2 g^{j \mu}_{,j} / 2
                TEMUDdD_contracted[i] += g4DD[mu][i + 1] * smallb2 * g4UUdD[
                    j + 1][mu][j + 1] / 2

    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                # Term 2d: g_{\mu i} b^j_{,j} b^\mu
                TEMUDdD_contracted[i] += g4DD[mu][
                    i + 1] * smallbUdD[j][j] * smallb4U[mu]

    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                # Term 2e: g_{\mu i} b^j b^\mu_{,j}
                TEMUDdD_contracted[i] += g4DD[mu][i + 1] * smallb4U[
                    j + 1] * smallb4UdD[mu][j + 1]

    # <a id='alltogether3'></a>
    #
    # ### Step 2.2.e.iii: Putting it together: Term 3
    # $$\label{alltogether3}$$
    #
    # \[Back to [List of Derivatives](#table2)\]
    #
    # Now, we will add $${\rm Term\ 3} = g_{\mu i} \left( \underbrace{\left(b^2\right)_{,j} u^j u^\mu}_{\rm Term\ 3a} + \underbrace{\frac{1}{2} \left(b^2\right)_{,j} g^{j\mu}}_{\rm Term\ 3b} \right).$$

    # Step 2.2.e.iii
    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                # Term 3a: g_{\mu i} ( b^2 )_{,j} u^j u^\mu
                TEMUDdD_contracted[i] += g4DD[mu][
                    i + 1] * smallb2_dD[j] * uU[j] * u4U[mu]

    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                # Term 3b: g_{mu i} ( b^2 )_{,j} g^{j\mu} / 2
                TEMUDdD_contracted[i] += g4DD[mu][
                    i + 1] * smallb2_dD[j] * g4UU[j + 1][mu] / 2

    #
    # # Evolution equation for $\tilde{S}_i$
    # <a id='step7'></a>
    #
    # ## Step 3.0: Construct the evolution equation for $\tilde{S}_i$
    # $$\label{step7}$$
    #
    # \[Back to [top](#top)\]
    #
    # Finally, we will return our attention to the time evolution equation (from eq. 13 of the [original paper](https://arxiv.org/pdf/1704.00599.pdf)),
    # \begin{align}
    # \partial_t \tilde{S}_i &= - \partial_j \left( \alpha \sqrt{\gamma} T^j_{{\rm EM} i} \right) + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} \\
    #                        &= -T^j_{{\rm EM} i} \partial_j (\alpha \sqrt{\gamma}) - \alpha \sqrt{\gamma} \partial_j T^j_{{\rm EM} i} + \frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} \\
    #                        &= \underbrace{-g_{i\mu} T^{\mu j}_{\rm EM} \partial_j (\alpha \sqrt{\gamma})
    # }_{\rm Term\ 1} - \underbrace{\alpha \sqrt{\gamma} \partial_j T^j_{{\rm EM} i}}_{\rm Term\ 2} + \underbrace{\frac{1}{2} \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu}}_{\rm Term\ 3} .
    # \end{align}
    # We will first take derivatives of $\alpha \sqrt{\gamma}$, then construct each term in turn.

    # Step 3.0: Construct the evolution equation for \tilde{S}_i
    # Here, we set up the necessary machinery to take FD derivatives of alpha * sqrt(gamma)
    global alpsqrtgam
    alpsqrtgam = gri.register_gridfunctions("AUX", "alpsqrtgam")
    alpsqrtgam = alpha * sp.sqrt(gammadet)
    alpsqrtgam_dD = ixp.declarerank1("alpsqrtgam_dD")

    global Stilde_rhsD
    Stilde_rhsD = ixp.zerorank1()
    # The first term: g_{i\mu} T^{\mu j}_{\rm EM} \partial_j (\alpha \sqrt{\gamma})
    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                Stilde_rhsD[i] += -g4DD[i + 1][mu] * T4EMUU[mu][
                    j + 1] * alpsqrtgam_dD[j]

    # The second term: \alpha \sqrt{\gamma} \partial_j T^j_{{\rm EM} i}
    for i in range(DIM):
        Stilde_rhsD[i] += -alpsqrtgam * TEMUDdD_contracted[i]

    # The third term: \alpha \sqrt{\gamma} T^{\mu \nu}_{\rm EM} \partial_i g_{\mu \nu} / 2
    for i in range(DIM):
        for mu in range(4):
            for nu in range(4):
                Stilde_rhsD[i] += alpsqrtgam * T4EMUU[mu][nu] * g4DDdD[mu][nu][
                    i + 1] / 2

    # # Evolution equations for $A_i$ and $\Phi$
    # <a id='step8'></a>
    #
    # ## Step 4.0: Construct the evolution equations for $A_i$ and $[\sqrt{\gamma}\Phi]$
    # $$\label{step8}$$
    #
    # \[Back to [top](#top)\]
    #
    # We will also need to evolve the vector potential $A_i$. This evolution is given as eq. 17 in the [$\texttt{GiRaFFE}$](https://arxiv.org/pdf/1704.00599.pdf) paper:
    # $$\boxed{\partial_t A_i = \epsilon_{ijk} v^j B^k - \partial_i (\underbrace{\alpha \Phi - \beta^j A_j}_{\rm AevolParen}),}$$
    # where $\epsilon_{ijk} = [ijk] \sqrt{\gamma}$ is the antisymmetric Levi-Civita tensor, the drift velocity $v^i = u^i/u^0$, $\gamma$ is the determinant of the three metric, $B^k$ is the magnetic field, $\alpha$ is the lapse, and $\beta$ is the shift.
    # The scalar electric potential $\Phi$ is also evolved by eq. 19:
    # $$\boxed{\partial_t [\sqrt{\gamma} \Phi] = -\partial_j (\underbrace{\alpha\sqrt{\gamma}A^j - \beta^j [\sqrt{\gamma} \Phi]}_{\rm PevolParenU[j]}) - \xi \alpha [\sqrt{\gamma} \Phi],}$$
    # with $\xi$ chosen as a damping factor.
    #
    # ### Step 4.0.a: Construct some useful auxiliary gridfunctions for the other evolution equations
    #
    # After declaring a  some needed quantities, we will also define the parenthetical terms (underbrace above) that we need to take derivatives of. That way, we can take finite-difference derivatives easily. Note that we use $A^j = \gamma^{ij} A_i$, while $A_i$ (with $\Phi$) is technically a four-vector; this is justified, however, since $n_\mu A^\mu = 0$, where $n_\mu$ is a normal to the hypersurface, $A^0=0$ (according to Sec. II, subsection C of [this paper](https://arxiv.org/pdf/1110.4633.pdf)).

    # Step 4.0: Construct the evolution equations for A_i and sqrt(gamma)Phi
    # Step 4.0.a: Construct some useful auxiliary gridfunctions for the other evolution equations
    xi = par.Cparameters(
        "REAL", thismodule, "xi", 0.1
    )  # The (dimensionful) Lorenz damping factor. Recommendation: set to ~1.5/max(delta t).

    # Define sqrt(gamma)Phi as psi6Phi
    psi6Phi = gri.register_gridfunctions("EVOL", "psi6Phi")
    Phi = psi6Phi / sp.sqrt(gammadet)

    # We'll define a few extra gridfunctions to avoid complicated derivatives
    global AevolParen, PevolParenU
    AevolParen = gri.register_gridfunctions("AUX", "AevolParen")
    PevolParenU = ixp.register_gridfunctions_for_single_rank1(
        "AUX", "PevolParenU")

    # {\rm AevolParen} = \alpha \Phi - \beta^j A_j
    AevolParen = alpha * Phi
    for j in range(DIM):
        AevolParen += -betaU[j] * AD[j]

    # {\rm PevolParenU[j]} = \alpha\sqrt{\gamma} \gamma^{ij} A_i - \beta^j [\sqrt{\gamma} \Phi]
    for j in range(DIM):
        PevolParenU[j] = -betaU[j] * psi6Phi
        for i in range(DIM):
            PevolParenU[j] += alpha * sp.sqrt(gammadet) * gammaUU[i][j] * AD[i]

    AevolParen_dD = ixp.declarerank1("AevolParen_dD")
    PevolParenU_dD = ixp.declarerank2("PevolParenU_dD", "nosym")

    # ### Step 4.0.b: Construct the actual evolution equations for $A_i$ and $[\sqrt{\gamma}\Phi]$
    #
    # Now to set the evolution equations ([eqs. 17 and 19](https://arxiv.org/pdf/1704.00599.pdf)), recalling that the drift velocity $v^i = u^i/u^0$:
    # \begin{align}
    # \partial_t A_i &= \epsilon_{ijk} v^j B^k - \partial_i (\alpha \Phi - \beta^j A_j) \\
    #                &= \epsilon_{ijk} \frac{u^j}{u^0} B^k - {\rm AevolParen\_dD[i]} \\
    # \partial_t [\sqrt{\gamma} \Phi] &= -\partial_j \left(\left(\alpha\sqrt{\gamma}\right)A^j - \beta^j [\sqrt{\gamma} \Phi]\right) - \xi \alpha [\sqrt{\gamma} \Phi] \\
    #                                 &= -{\rm PevolParenU\_dD[j][j]} - \xi \alpha [\sqrt{\gamma} \Phi]. \\
    # \end{align}

    # Step 4.0.b: Construct the actual evolution equations for A_i and sqrt(gamma)Phi
    global A_rhsD, psi6Phi_rhs
    A_rhsD = ixp.zerorank1()
    psi6Phi_rhs = sp.sympify(0)

    for i in range(DIM):
        A_rhsD[i] = -AevolParen_dD[i]
        for j in range(DIM):
            for k in range(DIM):
                A_rhsD[i] += LeviCivitaTensorDDD[i][j][k] * (
                    uU[j] / u4upperZero) * BU[k]

    psi6Phi_rhs = -xi * alpha * psi6Phi
    for j in range(DIM):
        psi6Phi_rhs += -PevolParenU_dD[j][j]
Example #11
0
def MaxwellCartesian_Evol():
    #Step 0: Set the spatial dimension parameter to 3.
    par.set_parval_from_str("grid::DIM", 3)
    DIM = par.parval_from_str("grid::DIM")

    # Step 1: Set the finite differencing order to 4.
    # (not needed here)
    # par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 4)

    # Step 2: Register gridfunctions that are needed as input.
    _psi = gri.register_gridfunctions(
        "EVOL", ["psi"])  # lgtm [py/unused-local-variable]

    # Step 3a: Declare the rank-1 indexed expressions E_{i}, A_{i},
    #          and \partial_{i} \psi. Derivative variables like these
    #          must have an underscore in them, so the finite
    #          difference module can parse the variable name properly.
    ED = ixp.register_gridfunctions_for_single_rank1("EVOL", "ED")
    AD = ixp.register_gridfunctions_for_single_rank1("EVOL", "AD")
    psi_dD = ixp.declarerank1("psi_dD")

    ## Step 3b: Declare the conformal metric tensor and its first
    #           derivative. These are needed to find the Christoffel
    #           symbols, which we need for covariant derivatives.
    gammaDD = ixp.register_gridfunctions_for_single_rank2(
        "AUX", "gammaDD", "sym01")  # The AUX or EVOL designation is *not*
    # used in diagnostic modules.
    gammaDD_dD = ixp.declarerank3("gammaDD_dD", "sym01")
    gammaDD_dDD = ixp.declarerank4("gammaDD_dDD", "sym01_sym23")

    gammaUU, detgamma = ixp.symm_matrix_inverter3x3(gammaDD)
    gammaUU_dD = ixp.declarerank3("gammaDD_dD", "sym01")

    # Define the Christoffel symbols
    GammaUDD = ixp.zerorank3(DIM)
    for i in range(DIM):
        for k in range(DIM):
            for l in range(DIM):
                for m in range(DIM):
                    GammaUDD[i][k][l] += (sp.Rational(1,2))*gammaUU[i][m]*\
                                         (gammaDD_dD[m][k][l] + gammaDD_dD[m][l][k] - gammaDD_dD[k][l][m])

    # Step 3b: Declare the rank-2 indexed expression \partial_{j} A_{i},
    #          which is not symmetric in its indices.
    #          Derivative variables like these must have an underscore
    #          in them, so the finite difference module can parse the
    #          variable name properly.
    AD_dD = ixp.declarerank2("AD_dD", "nosym")

    # Step 3c: Declare the rank-3 indexed expression \partial_{jk} A_{i},
    #          which is symmetric in the two {jk} indices.
    AD_dDD = ixp.declarerank3("AD_dDD", "sym12")

    # Step 4: Calculate first and second covariant derivatives, and the
    #         necessary contractions.
    # First covariant derivative
    # D_{j} A_{i} = A_{i,j} - \Gamma^{k}_{ij} A_{k}
    AD_dcovD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            AD_dcovD[i][j] = AD_dD[i][j]
            for k in range(DIM):
                AD_dcovD[i][j] -= GammaUDD[k][i][j] * AD[k]

    # First, we must construct the lowered Christoffel symbols:
    # \Gamma_{ijk} = \gamma_{il} \Gamma^l_{jk}
    # And raise the index on A:
    # A^j = \gamma^{ij} A_i
    GammaDDD = ixp.zerorank3()
    AU = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            AU[j] += gammaUU[i][j] * AD[i]
            for k in range(DIM):
                for l in range(DIM):
                    GammaDDD[i][j][k] += gammaDD[i][l] * GammaUDD[l][j][k]

    # Covariant second derivative (the bracketed terms):
    # D_j D^j A_i = \gamma^{jk} [A_{i,jk} - A^l (\gamma_{li,kj} + \gamma_{kl,ij} - \gamma_{ik,lj})
    #               + \Gamma_{lik} (\gamma^{lm} A_{m,j} + A_m \gamma^{lm}{}_{,j})
    #               - (\Gamma^l_{ij} A_{l,k} + \Gamma^l_{jk} A_{i,l})
    #               + (\Gamma^m_{ij} \Gamma^l_{mk} A_l + \Gamma ^m_{jk} \Gamma^l_{im} A_l)
    AD_dcovDD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                AD_dcovDD[i][j][k] = AD_dDD[i][j][k]
                for l in range(DIM):
                    # Terms 1 and 3
                    AD_dcovDD[i][j][k] -= AU[l] * (gammaDD_dDD[l][i][k][j] + gammaDD_dDD[k][l][i][j] - \
                                                   gammaDD_dDD[i][k][l][j]) \
                                        + GammaUDD[l][i][j] * AD_dD[l][k] + GammaUDD[l][j][k] * AD_dD[i][l]
                    for m in range(DIM):
                        # Terms 2 and 4
                        AD_dcovDD[i][j][k] += GammaDDD[l][i][k] * (gammaUU[l][m] * AD_dD[m][j] + AD[m] * gammaUU_dD[l][m][j]) \
                                            + GammaUDD[m][i][j] * GammaUDD[l][m][k] * AD[l] \
                                            + GammaUDD[m][j][k] * GammaUDD[l][i][m] * AD[l]

    # Covariant divergence
    # D_{i} A^{i} = \gamma^{ij} D_{j} A_{i}
    DivA = 0
    # Gradient of covariant divergence
    # DivA_dD_{i} = \gamma^{jk} A_{k;\hat{j}\hat{i}}
    DivA_dD = ixp.zerorank1()
    # Covariant Laplacian
    # LapAD_{i} = \gamma^{jk} A_{i;\hat{j}\hat{k}}
    LapAD = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            DivA += gammaUU[i][j] * AD_dcovD[i][j]
            for k in range(DIM):
                DivA_dD[i] += gammaUU[j][k] * AD_dcovDD[k][j][i]
                LapAD[i] += gammaUU[j][k] * AD_dcovDD[i][j][k]

    global ArhsD, ErhsD, psi_rhs
    system = par.parval_from_str("System_to_use")
    if system == "System_I":
        # Step 5: Define right-hand sides for the evolution.
        print("Warning: System I is less stable!")
        ArhsD = ixp.zerorank1()
        ErhsD = ixp.zerorank1()
        for i in range(DIM):
            ArhsD[i] = -ED[i] - psi_dD[i]
            ErhsD[i] = -LapAD[i] + DivA_dD[i]
        psi_rhs = -DivA

    elif system == "System_II":
        # We inherit here all of the definitions from System I, above

        # Step 7a: Register the scalar auxiliary variable \Gamma
        Gamma = gri.register_gridfunctions("EVOL", ["Gamma"])

        # Step 7b: Declare the ordinary gradient \partial_{i} \Gamma
        Gamma_dD = ixp.declarerank1("Gamma_dD")

        # Step 8a: Construct the second covariant derivative of the scalar \psi
        # \psi_{;\hat{i}\hat{j}} = \psi_{,i;\hat{j}}
        #                        = \psi_{,ij} - \Gamma^{k}_{ij} \psi_{,k}
        psi_dDD = ixp.declarerank2("psi_dDD", "sym01")
        psi_dcovDD = ixp.zerorank2()
        for i in range(DIM):
            for j in range(DIM):
                psi_dcovDD[i][j] = psi_dDD[i][j]
                for k in range(DIM):
                    psi_dcovDD[i][j] += -GammaUDD[k][i][j] * psi_dD[k]

        # Step 8b: Construct the covariant Laplacian of \psi
        # Lappsi = ghat^{ij} D_{j} D_{i} \psi
        Lappsi = 0
        for i in range(DIM):
            for j in range(DIM):
                Lappsi += gammaUU[i][j] * psi_dcovDD[i][j]

        # Step 9: Define right-hand sides for the evolution.
        global Gamma_rhs
        ArhsD = ixp.zerorank1()
        ErhsD = ixp.zerorank1()
        for i in range(DIM):
            ArhsD[i] = -ED[i] - psi_dD[i]
            ErhsD[i] = -LapAD[i] + Gamma_dD[i]
        psi_rhs = -Gamma
        Gamma_rhs = -Lappsi

    else:
        print(
            "Invalid choice of system: System_to_use must be either System_I or System_II"
        )

    ED_dD = ixp.declarerank2("ED_dD", "nosym")
    global Cviolation
    Cviolation = gri.register_gridfunctions("AUX", ["Cviolation"])
    Cviolation = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            Cviolation += gammaUU[i][j] * ED_dD[j][i]
            for b in range(DIM):
                Cviolation -= gammaUU[i][j] * GammaUDD[b][i][j] * ED[b]
Example #12
0
def VacuumMaxwellRHSs():
    system = par.parval_from_str("Maxwell.InitialData::System_to_use")
    global ArhsU, ErhsU, C, psi_rhs

    #Step 0: Set the spatial dimension parameter to 3.
    DIM = par.parval_from_str("grid::DIM")

    rfm.reference_metric()

    # Register gridfunctions that are needed as input.

    #          Declare the rank-1 indexed expressions E_{i}, A_{i},
    #          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")

    # A^i, _AU is unused
    _AU = ixp.register_gridfunctions_for_single_rank1("EVOL", "AU")

    # \psi is a scalar function that is time evolved
    # _psi is unused
    _psi = gri.register_gridfunctions("EVOL", ["psi"])

    # \partial_i \psi
    psi_dD = ixp.declarerank1("psi_dD")

    # \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")

    EU_dD = ixp.declarerank2("EU_dD", "nosym")

    C = gri.register_gridfunctions("AUX", "DivE")

    # Equation 12 of https://arxiv.org/abs/gr-qc/0201051
    C = EU_dD[0][0] + EU_dD[1][1] + EU_dD[2][2]

    if system == "System_I":
        print('Currently using ' + system + ' RHSs \n')

        # Define right-hand sides for the evolution.
        # Equations 10 and 11 from https://arxiv.org/abs/gr-qc/0201051

        # \partial_t A^i = E^i - \partial_i \psi
        ArhsU = ixp.zerorank1()

        # \partial_t E^i = -\partial_j^2 A^i + \partial_j \partial_i A^j
        ErhsU = ixp.zerorank1()

        # Lorenz gauge condition
        # \partial_t \psi = -\partial_i A^i
        psi_rhs = sp.sympify(0)

        for i in range(DIM):
            ArhsU[i] = -EU[i] - psi_dD[i]
            psi_rhs -= AU_dD[i][i]
            for j in range(DIM):
                ErhsU[i] += -AU_dDD[i][j][j] + AU_dDD[j][j][i]

    elif system == "System_II":
        global Gamma_rhs, G
        print('Currently using ' + system + ' RHSs \n')
        # We inherit here all of the definitions from System I, above

        # Register the scalar auxiliary variable \Gamma
        Gamma = gri.register_gridfunctions("EVOL", ["Gamma"])

        # Declare the ordinary gradient \partial_{i} \Gamma
        Gamma_dD = ixp.declarerank1("Gamma_dD")

        # partial_i \partial_j \psi
        psi_dDD = ixp.declarerank2("psi_dDD", "sym01")

        # Lorenz gauge condition
        psi_rhs = -Gamma

        # Define right-hand sides for the evolution.
        # Equations 10 and 14 https://arxiv.org/abs/gr-qc/0201051
        ArhsU = ixp.zerorank1()
        ErhsU = ixp.zerorank1()

        # Equation 13 of https://arxiv.org/abs/gr-qc/0201051
        # G = \Gamma - \partial_i A^i
        G = gri.register_gridfunctions("AUX", ["G"])
        G = Gamma - AU_dD[0][0] - AU_dD[1][1] - AU_dD[2][2]

        # Equation 15 https://arxiv.org/abs/gr-qc/0201051
        # Gamma_rhs = -DivE
        Gamma_rhs = sp.sympify(0)

        for i in range(DIM):
            ArhsU[i] = -EU[i] - psi_dD[i]
            ErhsU[i] = Gamma_dD[i]
            Gamma_rhs -= psi_dDD[i][i]
            for j in range(DIM):
                ErhsU[i] -= AU_dDD[i][j][j]

    else:
        print(
            "Invalid choice of system: System_to_use must be either System_I or System_II"
        )
Example #13
0
def GiRaFFE_NRPy_Main_Driver_generate_all(out_dir):
    cmd.mkdir(out_dir)
    
    gammaDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gammaDD","sym01",DIM=3)
    betaU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","betaU",DIM=3)
    alpha = gri.register_gridfunctions("AUXEVOL","alpha")
    AD = ixp.register_gridfunctions_for_single_rank1("EVOL","AD")
    BU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","BU")
    ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","ValenciavU")
    psi6Phi = gri.register_gridfunctions("EVOL","psi6Phi")
    StildeD = ixp.register_gridfunctions_for_single_rank1("EVOL","StildeD")

    PhievolParenU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","PhievolParenU",DIM=3)
    AevolParen = gri.register_gridfunctions("AUXEVOL","AevolParen")

    GRHD.compute_sqrtgammaDET(gammaDD)
    GRFFE.compute_AD_source_term_parenthetical_for_FD(GRHD.sqrtgammaDET,betaU,alpha,psi6Phi,AD)
    GRFFE.compute_psi6Phi_rhs_parenthetical(gammaDD,GRHD.sqrtgammaDET,betaU,alpha,AD,psi6Phi)

    parens_to_print = [\
                       lhrh(lhs=gri.gfaccess("auxevol_gfs","AevolParen"),rhs=GRFFE.AevolParen),\
                       lhrh(lhs=gri.gfaccess("auxevol_gfs","PhievolParenU0"),rhs=GRFFE.PhievolParenU[0]),\
                       lhrh(lhs=gri.gfaccess("auxevol_gfs","PhievolParenU1"),rhs=GRFFE.PhievolParenU[1]),\
                       lhrh(lhs=gri.gfaccess("auxevol_gfs","PhievolParenU2"),rhs=GRFFE.PhievolParenU[2]),\
                      ]

    subdir = "RHSs"
    cmd.mkdir(os.path.join(out_dir, subdir))
    desc = "Calculate quantities to be finite-differenced for the GRFFE RHSs"
    name = "calculate_parentheticals_for_RHSs"
    outCfunction(
        outfile  = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name,
        params   ="const paramstruct *restrict params,const REAL *restrict in_gfs,REAL *restrict auxevol_gfs",
        body     = fin.FD_outputC("returnstring",parens_to_print,params="outCverbose=False").replace("IDX4","IDX4S"),
        loopopts ="AllPoints",
        rel_path_for_Cparams=os.path.join("../"))

    xi_damping = par.Cparameters("REAL",thismodule,"xi_damping",0.1)
    GRFFE.compute_psi6Phi_rhs_damping_term(alpha,psi6Phi,xi_damping)

    AevolParen_dD = ixp.declarerank1("AevolParen_dD",DIM=3)
    PhievolParenU_dD = ixp.declarerank2("PhievolParenU_dD","nosym",DIM=3)

    A_rhsD = ixp.zerorank1()
    psi6Phi_rhs = GRFFE.psi6Phi_damping

    for i in range(3):
        A_rhsD[i] += -AevolParen_dD[i]
        psi6Phi_rhs += -PhievolParenU_dD[i][i]

    # Add Kreiss-Oliger dissipation to the GRFFE RHSs:
    psi6Phi_dKOD = ixp.declarerank1("psi6Phi_dKOD")
    AD_dKOD    = ixp.declarerank2("AD_dKOD","nosym")
    for i in range(3):
        psi6Phi_rhs += diss_strength*psi6Phi_dKOD[i]*rfm.ReU[i] # ReU[i] = 1/scalefactor_orthog_funcform[i]
        for j in range(3):
            A_rhsD[j] += diss_strength*AD_dKOD[j][i]*rfm.ReU[i] # ReU[i] = 1/scalefactor_orthog_funcform[i]

    RHSs_to_print = [\
                     lhrh(lhs=gri.gfaccess("rhs_gfs","AD0"),rhs=A_rhsD[0]),\
                     lhrh(lhs=gri.gfaccess("rhs_gfs","AD1"),rhs=A_rhsD[1]),\
                     lhrh(lhs=gri.gfaccess("rhs_gfs","AD2"),rhs=A_rhsD[2]),\
                     lhrh(lhs=gri.gfaccess("rhs_gfs","psi6Phi"),rhs=psi6Phi_rhs),\
                    ]

    desc = "Calculate AD gauge term and psi6Phi RHSs"
    name = "calculate_AD_gauge_psi6Phi_RHSs"
    source_Ccode = outCfunction(
        outfile  = "returnstring", desc=desc, name=name,
        params   ="const paramstruct *params,const REAL *in_gfs,const REAL *auxevol_gfs,REAL *rhs_gfs",
        body     = fin.FD_outputC("returnstring",RHSs_to_print,params="outCverbose=False").replace("IDX4","IDX4S"),
        loopopts ="InteriorPoints",
        rel_path_for_Cparams=os.path.join("../")).replace("=NGHOSTS","=NGHOSTS_A2B").replace("NGHOSTS+Nxx0","Nxx_plus_2NGHOSTS0-NGHOSTS_A2B").replace("NGHOSTS+Nxx1","Nxx_plus_2NGHOSTS1-NGHOSTS_A2B").replace("NGHOSTS+Nxx2","Nxx_plus_2NGHOSTS2-NGHOSTS_A2B")
    # Note the above .replace() functions. These serve to expand the loop range into the ghostzones, since 
    # the second-order FD needs fewer than some other algorithms we use do. 
    with open(os.path.join(out_dir,subdir,name+".h"),"w") as file:
        file.write(source_Ccode)
    
    # Declare all the Cparameters we will need
    metricderivDDD = ixp.declarerank3("metricderivDDD","sym01",DIM=3)
    shiftderivUD = ixp.declarerank2("shiftderivUD","nosym",DIM=3)
    lapsederivD = ixp.declarerank1("lapsederivD",DIM=3)

    general_access = """const REAL gammaDD00 = auxevol_gfs[IDX4S(GAMMADD00GF,i0,i1,i2)];
const REAL gammaDD01 = auxevol_gfs[IDX4S(GAMMADD01GF,i0,i1,i2)];
const REAL gammaDD02 = auxevol_gfs[IDX4S(GAMMADD02GF,i0,i1,i2)];
const REAL gammaDD11 = auxevol_gfs[IDX4S(GAMMADD11GF,i0,i1,i2)];
const REAL gammaDD12 = auxevol_gfs[IDX4S(GAMMADD12GF,i0,i1,i2)];
const REAL gammaDD22 = auxevol_gfs[IDX4S(GAMMADD22GF,i0,i1,i2)];
const REAL betaU0 = auxevol_gfs[IDX4S(BETAU0GF,i0,i1,i2)];
const REAL betaU1 = auxevol_gfs[IDX4S(BETAU1GF,i0,i1,i2)];
const REAL betaU2 = auxevol_gfs[IDX4S(BETAU2GF,i0,i1,i2)];
const REAL alpha = auxevol_gfs[IDX4S(ALPHAGF,i0,i1,i2)];
const REAL ValenciavU0 = auxevol_gfs[IDX4S(VALENCIAVU0GF,i0,i1,i2)];
const REAL ValenciavU1 = auxevol_gfs[IDX4S(VALENCIAVU1GF,i0,i1,i2)];
const REAL ValenciavU2 = auxevol_gfs[IDX4S(VALENCIAVU2GF,i0,i1,i2)];
const REAL BU0 = auxevol_gfs[IDX4S(BU0GF,i0,i1,i2)];
const REAL BU1 = auxevol_gfs[IDX4S(BU1GF,i0,i1,i2)];
const REAL BU2 = auxevol_gfs[IDX4S(BU2GF,i0,i1,i2)];
"""
    metric_deriv_access = ixp.zerorank1(DIM=3)
    metric_deriv_access[0] = """const REAL metricderivDDD000 = (auxevol_gfs[IDX4S(GAMMA_FACEDD00GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD00GF,i0,i1,i2)])/dxx0;
const REAL metricderivDDD010 = (auxevol_gfs[IDX4S(GAMMA_FACEDD01GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD01GF,i0,i1,i2)])/dxx0;
const REAL metricderivDDD020 = (auxevol_gfs[IDX4S(GAMMA_FACEDD02GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD02GF,i0,i1,i2)])/dxx0;
const REAL metricderivDDD110 = (auxevol_gfs[IDX4S(GAMMA_FACEDD11GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD11GF,i0,i1,i2)])/dxx0;
const REAL metricderivDDD120 = (auxevol_gfs[IDX4S(GAMMA_FACEDD12GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD12GF,i0,i1,i2)])/dxx0;
const REAL metricderivDDD220 = (auxevol_gfs[IDX4S(GAMMA_FACEDD22GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD22GF,i0,i1,i2)])/dxx0;
const REAL shiftderivUD00 = (auxevol_gfs[IDX4S(BETA_FACEU0GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(BETA_FACEU0GF,i0,i1,i2)])/dxx0;
const REAL shiftderivUD10 = (auxevol_gfs[IDX4S(BETA_FACEU1GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(BETA_FACEU1GF,i0,i1,i2)])/dxx0;
const REAL shiftderivUD20 = (auxevol_gfs[IDX4S(BETA_FACEU2GF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(BETA_FACEU2GF,i0,i1,i2)])/dxx0;
const REAL lapsederivD0 = (auxevol_gfs[IDX4S(ALPHA_FACEGF,i0+1,i1,i2)]-auxevol_gfs[IDX4S(ALPHA_FACEGF,i0,i1,i2)])/dxx0;
REAL Stilde_rhsD0;
"""
    metric_deriv_access[1] = """const REAL metricderivDDD001 = (auxevol_gfs[IDX4S(GAMMA_FACEDD00GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD00GF,i0,i1,i2)])/dxx1;
const REAL metricderivDDD011 = (auxevol_gfs[IDX4S(GAMMA_FACEDD01GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD01GF,i0,i1,i2)])/dxx1;
const REAL metricderivDDD021 = (auxevol_gfs[IDX4S(GAMMA_FACEDD02GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD02GF,i0,i1,i2)])/dxx1;
const REAL metricderivDDD111 = (auxevol_gfs[IDX4S(GAMMA_FACEDD11GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD11GF,i0,i1,i2)])/dxx1;
const REAL metricderivDDD121 = (auxevol_gfs[IDX4S(GAMMA_FACEDD12GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD12GF,i0,i1,i2)])/dxx1;
const REAL metricderivDDD221 = (auxevol_gfs[IDX4S(GAMMA_FACEDD22GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(GAMMA_FACEDD22GF,i0,i1,i2)])/dxx1;
const REAL shiftderivUD01 = (auxevol_gfs[IDX4S(BETA_FACEU0GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(BETA_FACEU0GF,i0,i1,i2)])/dxx1;
const REAL shiftderivUD11 = (auxevol_gfs[IDX4S(BETA_FACEU1GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(BETA_FACEU1GF,i0,i1,i2)])/dxx1;
const REAL shiftderivUD21 = (auxevol_gfs[IDX4S(BETA_FACEU2GF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(BETA_FACEU2GF,i0,i1,i2)])/dxx1;
const REAL lapsederivD1 = (auxevol_gfs[IDX4S(ALPHA_FACEGF,i0,i1+1,i2)]-auxevol_gfs[IDX4S(ALPHA_FACEGF,i0,i1,i2)])/dxx1;
REAL Stilde_rhsD1;
"""
    metric_deriv_access[2] = """const REAL metricderivDDD002 = (auxevol_gfs[IDX4S(GAMMA_FACEDD00GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(GAMMA_FACEDD00GF,i0,i1,i2)])/dxx2;
const REAL metricderivDDD012 = (auxevol_gfs[IDX4S(GAMMA_FACEDD01GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(GAMMA_FACEDD01GF,i0,i1,i2)])/dxx2;
const REAL metricderivDDD022 = (auxevol_gfs[IDX4S(GAMMA_FACEDD02GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(GAMMA_FACEDD02GF,i0,i1,i2)])/dxx2;
const REAL metricderivDDD112 = (auxevol_gfs[IDX4S(GAMMA_FACEDD11GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(GAMMA_FACEDD11GF,i0,i1,i2)])/dxx2;
const REAL metricderivDDD122 = (auxevol_gfs[IDX4S(GAMMA_FACEDD12GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(GAMMA_FACEDD12GF,i0,i1,i2)])/dxx2;
const REAL metricderivDDD222 = (auxevol_gfs[IDX4S(GAMMA_FACEDD22GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(GAMMA_FACEDD22GF,i0,i1,i2)])/dxx2;
const REAL shiftderivUD02 = (auxevol_gfs[IDX4S(BETA_FACEU0GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(BETA_FACEU0GF,i0,i1,i2)])/dxx2;
const REAL shiftderivUD12 = (auxevol_gfs[IDX4S(BETA_FACEU1GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(BETA_FACEU1GF,i0,i1,i2)])/dxx2;
const REAL shiftderivUD22 = (auxevol_gfs[IDX4S(BETA_FACEU2GF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(BETA_FACEU2GF,i0,i1,i2)])/dxx2;
const REAL lapsederivD2 = (auxevol_gfs[IDX4S(ALPHA_FACEGF,i0,i1,i2+1)]-auxevol_gfs[IDX4S(ALPHA_FACEGF,i0,i1,i2)])/dxx2;
REAL Stilde_rhsD2;
"""
    write_final_quantity = ixp.zerorank1(DIM=3)
    write_final_quantity[0] = """rhs_gfs[IDX4S(STILDED0GF,i0,i1,i2)] += Stilde_rhsD0;
"""
    write_final_quantity[1] = """rhs_gfs[IDX4S(STILDED1GF,i0,i1,i2)] += Stilde_rhsD1;
"""
    write_final_quantity[2] = """rhs_gfs[IDX4S(STILDED2GF,i0,i1,i2)] += Stilde_rhsD2;
"""

    # Declare this symbol:
    sqrt4pi = par.Cparameters("REAL",thismodule,"sqrt4pi","sqrt(4.0*M_PI)")

    # We need to rerun a few of these functions with the reset lists to make sure these functions
    # don't cheat by using analytic expressions
    GRHD.u4U_in_terms_of_ValenciavU__rescale_ValenciavU_by_applying_speed_limit(alpha, betaU, gammaDD, ValenciavU)
    GRFFE.compute_smallb4U(gammaDD, betaU, alpha, GRHD.u4U_ito_ValenciavU, BU, sqrt4pi)
    GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, GRFFE.smallb4U)
    GRFFE.compute_TEM4UU(gammaDD,betaU,alpha, GRFFE.smallb4U, GRFFE.smallbsquared,GRHD.u4U_ito_ValenciavU)
    GRHD.compute_g4DD_zerotimederiv_dD(gammaDD,betaU,alpha, metricderivDDD,shiftderivUD,lapsederivD)
    GRHD.compute_S_tilde_source_termD(alpha, GRHD.sqrtgammaDET,GRHD.g4DD_zerotimederiv_dD, GRFFE.TEM4UU)
    for i in range(3):
        desc = "Adds the source term to StildeD"+str(i)+"."
        name = "calculate_StildeD"+str(i)+"_source_term"
        outCfunction(
            outfile  = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name,
            params   ="const paramstruct *params,const REAL *auxevol_gfs, REAL *rhs_gfs",
            body     = general_access \
                      +metric_deriv_access[i]\
                      +outputC(GRHD.S_tilde_source_termD[i],"Stilde_rhsD"+str(i),"returnstring",params="outCverbose=False").replace("IDX4","IDX4S")\
                      +write_final_quantity[i],
            loopopts ="InteriorPoints",
            rel_path_for_Cparams=os.path.join("../"))

    subdir = "FCVAL"
    cmd.mkdir(os.path.join(out_dir, subdir))
    FCVAL.GiRaFFE_NRPy_FCVAL(os.path.join(out_dir,subdir))
    
    subdir = "PPM"
    cmd.mkdir(os.path.join(out_dir, subdir))
    PPM.GiRaFFE_NRPy_PPM(os.path.join(out_dir,subdir))
    

    # We will pass values of the gridfunction on the cell faces into the function. This requires us
    # to declare them as C parameters in NRPy+. We will denote this with the _face infix/suffix.
    alpha_face = gri.register_gridfunctions("AUXEVOL","alpha_face")
    gamma_faceDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gamma_faceDD","sym01")
    beta_faceU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","beta_faceU")

    # We'll need some more gridfunctions, now, to represent the reconstructions of BU and ValenciavU
    # on the right and left faces
    Valenciav_rU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","Valenciav_rU",DIM=3)
    B_rU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","B_rU",DIM=3)
    Valenciav_lU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","Valenciav_lU",DIM=3)
    B_lU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","B_lU",DIM=3)

    Memory_Read = """const double alpha_face = auxevol_gfs[IDX4S(ALPHA_FACEGF, i0,i1,i2)];
const double gamma_faceDD00 = auxevol_gfs[IDX4S(GAMMA_FACEDD00GF, i0,i1,i2)];
const double gamma_faceDD01 = auxevol_gfs[IDX4S(GAMMA_FACEDD01GF, i0,i1,i2)];
const double gamma_faceDD02 = auxevol_gfs[IDX4S(GAMMA_FACEDD02GF, i0,i1,i2)];
const double gamma_faceDD11 = auxevol_gfs[IDX4S(GAMMA_FACEDD11GF, i0,i1,i2)];
const double gamma_faceDD12 = auxevol_gfs[IDX4S(GAMMA_FACEDD12GF, i0,i1,i2)];
const double gamma_faceDD22 = auxevol_gfs[IDX4S(GAMMA_FACEDD22GF, i0,i1,i2)];
const double beta_faceU0 = auxevol_gfs[IDX4S(BETA_FACEU0GF, i0,i1,i2)];
const double beta_faceU1 = auxevol_gfs[IDX4S(BETA_FACEU1GF, i0,i1,i2)];
const double beta_faceU2 = auxevol_gfs[IDX4S(BETA_FACEU2GF, i0,i1,i2)];
const double Valenciav_rU0 = auxevol_gfs[IDX4S(VALENCIAV_RU0GF, i0,i1,i2)];
const double Valenciav_rU1 = auxevol_gfs[IDX4S(VALENCIAV_RU1GF, i0,i1,i2)];
const double Valenciav_rU2 = auxevol_gfs[IDX4S(VALENCIAV_RU2GF, i0,i1,i2)];
const double B_rU0 = auxevol_gfs[IDX4S(B_RU0GF, i0,i1,i2)];
const double B_rU1 = auxevol_gfs[IDX4S(B_RU1GF, i0,i1,i2)];
const double B_rU2 = auxevol_gfs[IDX4S(B_RU2GF, i0,i1,i2)];
const double Valenciav_lU0 = auxevol_gfs[IDX4S(VALENCIAV_LU0GF, i0,i1,i2)];
const double Valenciav_lU1 = auxevol_gfs[IDX4S(VALENCIAV_LU1GF, i0,i1,i2)];
const double Valenciav_lU2 = auxevol_gfs[IDX4S(VALENCIAV_LU2GF, i0,i1,i2)];
const double B_lU0 = auxevol_gfs[IDX4S(B_LU0GF, i0,i1,i2)];
const double B_lU1 = auxevol_gfs[IDX4S(B_LU1GF, i0,i1,i2)];
const double B_lU2 = auxevol_gfs[IDX4S(B_LU2GF, i0,i1,i2)];
REAL A_rhsD0 = 0; REAL A_rhsD1 = 0; REAL A_rhsD2 = 0;
"""
    Memory_Write = """rhs_gfs[IDX4S(AD0GF,i0,i1,i2)] += A_rhsD0;
rhs_gfs[IDX4S(AD1GF,i0,i1,i2)] += A_rhsD1;
rhs_gfs[IDX4S(AD2GF,i0,i1,i2)] += A_rhsD2;
"""

    indices = ["i0","i1","i2"]
    indicesp1 = ["i0+1","i1+1","i2+1"]

    subdir = "RHSs"
    for flux_dirn in range(3):
        Af.calculate_E_i_flux(flux_dirn,True,alpha_face,gamma_faceDD,beta_faceU,\
                              Valenciav_rU,B_rU,Valenciav_lU,B_lU)

        E_field_to_print = [\
                            sp.Rational(1,4)*Af.E_fluxD[(flux_dirn+1)%3],
                            sp.Rational(1,4)*Af.E_fluxD[(flux_dirn+2)%3],
                           ]
        E_field_names = [\
                         "A_rhsD"+str((flux_dirn+1)%3),
                         "A_rhsD"+str((flux_dirn+2)%3),
                        ]

        desc = "Calculate the electric flux on the left face in direction " + str(flux_dirn) + "."
        name = "calculate_E_field_D" + str(flux_dirn) + "_right"
        outCfunction(
            outfile  = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name,
            params   ="const paramstruct *params,const REAL *auxevol_gfs,REAL *rhs_gfs",
            body     =  Memory_Read \
                       +outputC(E_field_to_print,E_field_names,"returnstring",params="outCverbose=False").replace("IDX4","IDX4S")\
                       +Memory_Write,
            loopopts ="InteriorPoints",
            rel_path_for_Cparams=os.path.join("../"))

        desc = "Calculate the electric flux on the left face in direction " + str(flux_dirn) + "."
        name = "calculate_E_field_D" + str(flux_dirn) + "_left"
        outCfunction(
            outfile  = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name,
            params   ="const paramstruct *params,const REAL *auxevol_gfs,REAL *rhs_gfs",
            body     =  Memory_Read.replace(indices[flux_dirn],indicesp1[flux_dirn]) \
                       +outputC(E_field_to_print,E_field_names,"returnstring",params="outCverbose=False").replace("IDX4","IDX4S")\
                       +Memory_Write,
            loopopts ="InteriorPoints",
            rel_path_for_Cparams=os.path.join("../"))


    Memory_Read = """const double alpha_face = auxevol_gfs[IDX4S(ALPHA_FACEGF, i0,i1,i2)];
const double gamma_faceDD00 = auxevol_gfs[IDX4S(GAMMA_FACEDD00GF, i0,i1,i2)];
const double gamma_faceDD01 = auxevol_gfs[IDX4S(GAMMA_FACEDD01GF, i0,i1,i2)];
const double gamma_faceDD02 = auxevol_gfs[IDX4S(GAMMA_FACEDD02GF, i0,i1,i2)];
const double gamma_faceDD11 = auxevol_gfs[IDX4S(GAMMA_FACEDD11GF, i0,i1,i2)];
const double gamma_faceDD12 = auxevol_gfs[IDX4S(GAMMA_FACEDD12GF, i0,i1,i2)];
const double gamma_faceDD22 = auxevol_gfs[IDX4S(GAMMA_FACEDD22GF, i0,i1,i2)];
const double beta_faceU0 = auxevol_gfs[IDX4S(BETA_FACEU0GF, i0,i1,i2)];
const double beta_faceU1 = auxevol_gfs[IDX4S(BETA_FACEU1GF, i0,i1,i2)];
const double beta_faceU2 = auxevol_gfs[IDX4S(BETA_FACEU2GF, i0,i1,i2)];
const double Valenciav_rU0 = auxevol_gfs[IDX4S(VALENCIAV_RU0GF, i0,i1,i2)];
const double Valenciav_rU1 = auxevol_gfs[IDX4S(VALENCIAV_RU1GF, i0,i1,i2)];
const double Valenciav_rU2 = auxevol_gfs[IDX4S(VALENCIAV_RU2GF, i0,i1,i2)];
const double B_rU0 = auxevol_gfs[IDX4S(B_RU0GF, i0,i1,i2)];
const double B_rU1 = auxevol_gfs[IDX4S(B_RU1GF, i0,i1,i2)];
const double B_rU2 = auxevol_gfs[IDX4S(B_RU2GF, i0,i1,i2)];
const double Valenciav_lU0 = auxevol_gfs[IDX4S(VALENCIAV_LU0GF, i0,i1,i2)];
const double Valenciav_lU1 = auxevol_gfs[IDX4S(VALENCIAV_LU1GF, i0,i1,i2)];
const double Valenciav_lU2 = auxevol_gfs[IDX4S(VALENCIAV_LU2GF, i0,i1,i2)];
const double B_lU0 = auxevol_gfs[IDX4S(B_LU0GF, i0,i1,i2)];
const double B_lU1 = auxevol_gfs[IDX4S(B_LU1GF, i0,i1,i2)];
const double B_lU2 = auxevol_gfs[IDX4S(B_LU2GF, i0,i1,i2)];
REAL Stilde_fluxD0 = 0; REAL Stilde_fluxD1 = 0; REAL Stilde_fluxD2 = 0;
"""
    Memory_Write = """rhs_gfs[IDX4S(STILDED0GF, i0, i1, i2)] += invdx0*Stilde_fluxD0;
rhs_gfs[IDX4S(STILDED1GF, i0, i1, i2)] += invdx0*Stilde_fluxD1;
rhs_gfs[IDX4S(STILDED2GF, i0, i1, i2)] += invdx0*Stilde_fluxD2;
"""

    indices = ["i0","i1","i2"]
    indicesp1 = ["i0+1","i1+1","i2+1"]
    assignment = "+="
    assignmentp1 = "-="
    invdx = ["invdx0","invdx1","invdx2"]

    for flux_dirn in range(3):
        Sf.calculate_Stilde_flux(flux_dirn,True,alpha_face,gamma_faceDD,beta_faceU,\
                                 Valenciav_rU,B_rU,Valenciav_lU,B_lU,sqrt4pi)
        Stilde_flux_to_print = [\
                                Sf.Stilde_fluxD[0],\
                                Sf.Stilde_fluxD[1],\
                                Sf.Stilde_fluxD[2],\
                               ]
        Stilde_flux_names = [\
                             "Stilde_fluxD0",\
                             "Stilde_fluxD1",\
                             "Stilde_fluxD2",\
                            ]

        desc = "Compute the flux of all 3 components of tilde{S}_i on the right face in the " + str(flux_dirn) + "."
        name = "calculate_Stilde_flux_D" + str(flux_dirn) + "_right"
        outCfunction(
            outfile  = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name,
            params   ="const paramstruct *params,const REAL *auxevol_gfs,REAL *rhs_gfs",
            body     =  Memory_Read \
                   +outputC(Stilde_flux_to_print,Stilde_flux_names,"returnstring",params="outCverbose=False").replace("IDX4","IDX4S")\
                       +Memory_Write.replace(invdx[0],invdx[flux_dirn]),
            loopopts ="InteriorPoints",
            rel_path_for_Cparams=os.path.join("../"))

        desc = "Compute the flux of all 3 components of tilde{S}_i on the left face in the " + str(flux_dirn) + "."
        name = "calculate_Stilde_flux_D" + str(flux_dirn) + "_left"
        outCfunction(
            outfile  = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name,
            params   ="const paramstruct *params,const REAL *auxevol_gfs,REAL *rhs_gfs",
            body     =  Memory_Read.replace(indices[flux_dirn],indicesp1[flux_dirn]) \
                   +outputC(Stilde_flux_to_print,Stilde_flux_names,"returnstring",params="outCverbose=False").replace("IDX4","IDX4S")\
                       +Memory_Write.replace(invdx[0],invdx[flux_dirn]).replace(assignment,assignmentp1),
            loopopts ="InteriorPoints",
            rel_path_for_Cparams=os.path.join("../"))

    subdir = "boundary_conditions"
    cmd.mkdir(os.path.join(out_dir,subdir))
    BC.GiRaFFE_NRPy_BCs(os.path.join(out_dir,subdir))
    
    subdir = "A2B"
    cmd.mkdir(os.path.join(out_dir,subdir))
    A2B.GiRaFFE_NRPy_A2B(os.path.join(out_dir,subdir),gammaDD,AD,BU)
    
    C2P_P2C.GiRaFFE_NRPy_C2P(StildeD,BU,gammaDD,betaU,alpha)

    values_to_print = [\
                       lhrh(lhs=gri.gfaccess("in_gfs","StildeD0"),rhs=C2P_P2C.outStildeD[0]),\
                       lhrh(lhs=gri.gfaccess("in_gfs","StildeD1"),rhs=C2P_P2C.outStildeD[1]),\
                       lhrh(lhs=gri.gfaccess("in_gfs","StildeD2"),rhs=C2P_P2C.outStildeD[2]),\
                       lhrh(lhs=gri.gfaccess("auxevol_gfs","ValenciavU0"),rhs=C2P_P2C.ValenciavU[0]),\
                       lhrh(lhs=gri.gfaccess("auxevol_gfs","ValenciavU1"),rhs=C2P_P2C.ValenciavU[1]),\
                       lhrh(lhs=gri.gfaccess("auxevol_gfs","ValenciavU2"),rhs=C2P_P2C.ValenciavU[2])\
                      ]

    subdir = "C2P"
    cmd.mkdir(os.path.join(out_dir,subdir))
    desc = "Apply fixes to \tilde{S}_i and recompute the velocity to match with current sheet prescription."
    name = "GiRaFFE_NRPy_cons_to_prims"
    outCfunction(
        outfile  = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name,
        params   ="const paramstruct *params,REAL *xx[3],REAL *auxevol_gfs,REAL *in_gfs",
        body     = fin.FD_outputC("returnstring",values_to_print,params="outCverbose=False").replace("IDX4","IDX4S"),
        loopopts ="AllPoints,Read_xxs",
        rel_path_for_Cparams=os.path.join("../"))

    C2P_P2C.GiRaFFE_NRPy_P2C(gammaDD,betaU,alpha,  ValenciavU,BU, sqrt4pi)

    values_to_print = [\
                       lhrh(lhs=gri.gfaccess("in_gfs","StildeD0"),rhs=C2P_P2C.StildeD[0]),\
                       lhrh(lhs=gri.gfaccess("in_gfs","StildeD1"),rhs=C2P_P2C.StildeD[1]),\
                       lhrh(lhs=gri.gfaccess("in_gfs","StildeD2"),rhs=C2P_P2C.StildeD[2]),\
                      ]

    desc = "Recompute StildeD after current sheet fix to Valencia 3-velocity to ensure consistency between conservative & primitive variables."
    name = "GiRaFFE_NRPy_prims_to_cons"
    outCfunction(
        outfile  = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name,
        params   ="const paramstruct *params,REAL *auxevol_gfs,REAL *in_gfs",
        body     = fin.FD_outputC("returnstring",values_to_print,params="outCverbose=False").replace("IDX4","IDX4S"),
        loopopts ="AllPoints",
        rel_path_for_Cparams=os.path.join("../"))

    # Write out the main driver itself:
    with open(os.path.join(out_dir,"GiRaFFE_NRPy_Main_Driver.h"),"w") as file:
        file.write("""// Structure to track ghostzones for PPM:
typedef struct __gf_and_gz_struct__ {
  REAL *gf;
  int gz_lo[4],gz_hi[4];
} gf_and_gz_struct;
// Some additional constants needed for PPM:
const int VX=0,VY=1,VZ=2,BX=3,BY=4,BZ=5;
const int NUM_RECONSTRUCT_GFS = 6;

// Include ALL functions needed for evolution
#include "RHSs/calculate_parentheticals_for_RHSs.h"
#include "RHSs/calculate_AD_gauge_psi6Phi_RHSs.h"
#include "PPM/reconstruct_set_of_prims_PPM_GRFFE_NRPy.c"
#include "FCVAL/interpolate_metric_gfs_to_cell_faces.h"
#include "RHSs/calculate_StildeD0_source_term.h"
#include "RHSs/calculate_StildeD1_source_term.h"
#include "RHSs/calculate_StildeD2_source_term.h"
// #include "RHSs/calculate_E_field_D0_right.h"
// #include "RHSs/calculate_E_field_D0_left.h"
// #include "RHSs/calculate_E_field_D1_right.h"
// #include "RHSs/calculate_E_field_D1_left.h"
// #include "RHSs/calculate_E_field_D2_right.h"
// #include "RHSs/calculate_E_field_D2_left.h"
#include "../calculate_E_field_flat_all_in_one.h"
#include "RHSs/calculate_Stilde_flux_D0_right.h"
#include "RHSs/calculate_Stilde_flux_D0_left.h"
#include "RHSs/calculate_Stilde_flux_D1_right.h"
#include "RHSs/calculate_Stilde_flux_D1_left.h"
#include "RHSs/calculate_Stilde_flux_D2_right.h"
#include "RHSs/calculate_Stilde_flux_D2_left.h"
#include "boundary_conditions/GiRaFFE_boundary_conditions.h"
#include "A2B/driver_AtoB.h"
#include "C2P/GiRaFFE_NRPy_cons_to_prims.h"
#include "C2P/GiRaFFE_NRPy_prims_to_cons.h"

void override_BU_with_old_GiRaFFE(const paramstruct *restrict params,REAL *restrict auxevol_gfs,const int n) {
#include "set_Cparameters.h"
    char filename[100];
    sprintf(filename,"BU0_override-%08d.bin",n);
    FILE *out2D = fopen(filename, "rb");
    fread(auxevol_gfs+BU0GF*Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2,
          sizeof(double),Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2,out2D);
    fclose(out2D);
    sprintf(filename,"BU1_override-%08d.bin",n);
    out2D = fopen(filename, "rb");
    fread(auxevol_gfs+BU1GF*Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2,
          sizeof(double),Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2,out2D);
    fclose(out2D);
    sprintf(filename,"BU2_override-%08d.bin",n);
    out2D = fopen(filename, "rb");
    fread(auxevol_gfs+BU2GF*Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2,
          sizeof(double),Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2,out2D);
    fclose(out2D);
}

void GiRaFFE_NRPy_RHSs(const paramstruct *restrict params,REAL *restrict auxevol_gfs,const REAL *restrict in_gfs,REAL *restrict rhs_gfs) {
#include "set_Cparameters.h"
    // First thing's first: initialize the RHSs to zero!
#pragma omp parallel for
    for(int ii=0;ii<Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2*NUM_EVOL_GFS;ii++) {
        rhs_gfs[ii] = 0.0;
    }
    // Next calculate the easier source terms that don't require flux directions
    // This will also reset the RHSs for each gf at each new timestep.
    calculate_parentheticals_for_RHSs(params,in_gfs,auxevol_gfs);
    calculate_AD_gauge_psi6Phi_RHSs(params,in_gfs,auxevol_gfs,rhs_gfs);
    
    // Now, we set up a bunch of structs of pointers to properly guide the PPM algorithm.
    // They also count the number of ghostzones available.
    gf_and_gz_struct in_prims[NUM_RECONSTRUCT_GFS], out_prims_r[NUM_RECONSTRUCT_GFS], out_prims_l[NUM_RECONSTRUCT_GFS];
    int which_prims_to_reconstruct[NUM_RECONSTRUCT_GFS],num_prims_to_reconstruct;
    const int Nxxp2NG012 = Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2;
    
    REAL *temporary = auxevol_gfs + Nxxp2NG012*AEVOLPARENGF; //We're not using this anymore
    // This sets pointers to the portion of auxevol_gfs containing the relevant gridfunction.
    int ww=0;
    in_prims[ww].gf      = auxevol_gfs + Nxxp2NG012*VALENCIAVU0GF; 
      out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_RU0GF; 
      out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_LU0GF; 
    ww++;
    in_prims[ww].gf      = auxevol_gfs + Nxxp2NG012*VALENCIAVU1GF; 
      out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_RU1GF; 
      out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_LU1GF; 
    ww++;
    in_prims[ww].gf      = auxevol_gfs + Nxxp2NG012*VALENCIAVU2GF; 
      out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_RU2GF; 
      out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_LU2GF; 
    ww++;
    in_prims[ww].gf      = auxevol_gfs + Nxxp2NG012*BU0GF; 
      out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*B_RU0GF; 
      out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*B_LU0GF; 
    ww++;
    in_prims[ww].gf      = auxevol_gfs + Nxxp2NG012*BU1GF; 
      out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*B_RU1GF; 
      out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*B_LU1GF; 
    ww++;
    in_prims[ww].gf      = auxevol_gfs + Nxxp2NG012*BU2GF; 
      out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*B_RU2GF; 
      out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*B_LU2GF; 
    ww++;

    // Prims are defined AT ALL GRIDPOINTS, so we set the # of ghostzones to zero:
    for(int i=0;i<NUM_RECONSTRUCT_GFS;i++) for(int j=1;j<=3;j++) { in_prims[i].gz_lo[j]=0; in_prims[i].gz_hi[j]=0; }
    // Left/right variables are not yet defined, yet we set the # of gz's to zero by default:
    for(int i=0;i<NUM_RECONSTRUCT_GFS;i++) for(int j=1;j<=3;j++) { out_prims_r[i].gz_lo[j]=0; out_prims_r[i].gz_hi[j]=0; }
    for(int i=0;i<NUM_RECONSTRUCT_GFS;i++) for(int j=1;j<=3;j++) { out_prims_l[i].gz_lo[j]=0; out_prims_l[i].gz_hi[j]=0; }

    ww=0;
    which_prims_to_reconstruct[ww]=VX; ww++;
    which_prims_to_reconstruct[ww]=VY; ww++;
    which_prims_to_reconstruct[ww]=VZ; ww++;
    which_prims_to_reconstruct[ww]=BX; ww++;
    which_prims_to_reconstruct[ww]=BY; ww++;
    which_prims_to_reconstruct[ww]=BZ; ww++;
    num_prims_to_reconstruct=ww;

    // In each direction, perform the PPM reconstruction procedure.
    // Then, add the fluxes to the RHS as appropriate.
    for(int flux_dirn=0;flux_dirn<3;flux_dirn++) {
        // In each direction, interpolate the metric gfs (gamma,beta,alpha) to cell faces.
        interpolate_metric_gfs_to_cell_faces(params,auxevol_gfs,flux_dirn+1);
        // Then, reconstruct the primitive variables on the cell faces.
        // This function is housed in the file: "reconstruct_set_of_prims_PPM_GRFFE_NRPy.c"
        reconstruct_set_of_prims_PPM_GRFFE_NRPy(params, auxevol_gfs, flux_dirn+1, num_prims_to_reconstruct,                                                          
                                                which_prims_to_reconstruct, in_prims, out_prims_r, out_prims_l, temporary);
        // For example, if flux_dirn==0, then at gamma_faceDD00(i,j,k) represents gamma_{xx}
        // at (i-1/2,j,k), Valenciav_lU0(i,j,k) is the x-component of the velocity at (i-1/2-epsilon,j,k),
        // and Valenciav_rU0(i,j,k) is the x-component of the velocity at (i-1/2+epsilon,j,k).
        
        if(flux_dirn==0) {
            // Next, we calculate the source term for StildeD. Again, this also resets the rhs_gfs array at
            // each new timestep.
            calculate_StildeD0_source_term(params,auxevol_gfs,rhs_gfs);
            // Now, compute the electric field on each face of a cell and add it to the RHSs as appropriate
            //calculate_E_field_D0_right(params,auxevol_gfs,rhs_gfs);
            //calculate_E_field_D0_left(params,auxevol_gfs,rhs_gfs);
            calculate_E_field_flat_all_in_one(params,auxevol_gfs,rhs_gfs,flux_dirn);
            // Finally, we calculate the flux of StildeD and add the appropriate finite-differences 
            // to the RHSs.
            calculate_Stilde_flux_D0_right(params,auxevol_gfs,rhs_gfs);
            calculate_Stilde_flux_D0_left(params,auxevol_gfs,rhs_gfs);
        }
        else if(flux_dirn==1) {
            calculate_StildeD1_source_term(params,auxevol_gfs,rhs_gfs);
            //calculate_E_field_D1_right(params,auxevol_gfs,rhs_gfs);
            //calculate_E_field_D1_left(params,auxevol_gfs,rhs_gfs);
            calculate_E_field_flat_all_in_one(params,auxevol_gfs,rhs_gfs,flux_dirn);
            calculate_Stilde_flux_D1_right(params,auxevol_gfs,rhs_gfs);
            calculate_Stilde_flux_D1_left(params,auxevol_gfs,rhs_gfs);
        }
        else {
            calculate_StildeD2_source_term(params,auxevol_gfs,rhs_gfs);
            //calculate_E_field_D2_right(params,auxevol_gfs,rhs_gfs);
            //calculate_E_field_D2_left(params,auxevol_gfs,rhs_gfs);
            calculate_E_field_flat_all_in_one(params,auxevol_gfs,rhs_gfs,flux_dirn);
            calculate_Stilde_flux_D2_right(params,auxevol_gfs,rhs_gfs);
            calculate_Stilde_flux_D2_left(params,auxevol_gfs,rhs_gfs);
        }
    }
}

void GiRaFFE_NRPy_post_step(const paramstruct *restrict params,REAL *xx[3],REAL *restrict auxevol_gfs,REAL *restrict evol_gfs,const int n) {
    // First, apply BCs to AD and psi6Phi. Then calculate BU from AD
    apply_bcs_potential(params,evol_gfs);
    driver_A_to_B(params,evol_gfs,auxevol_gfs);
    //override_BU_with_old_GiRaFFE(params,auxevol_gfs,n);
    // Apply fixes to StildeD, then recompute the velocity at the new timestep. 
    // Apply the current sheet prescription to the velocities
    GiRaFFE_NRPy_cons_to_prims(params,xx,auxevol_gfs,evol_gfs);
    // Then, recompute StildeD to be consistent with the new velocities
    //GiRaFFE_NRPy_prims_to_cons(params,auxevol_gfs,evol_gfs);
    // Finally, apply outflow boundary conditions to the velocities.
    apply_bcs_velocity(params,auxevol_gfs);
}
""")
Example #14
0
def generate_everything_for_UnitTesting():
    # First define hydrodynamical quantities
    u4U = ixp.declarerank1("u4U", DIM=4)
    rho_b, P, epsilon = sp.symbols('rho_b P epsilon', real=True)
    B_tildeU = ixp.declarerank1("B_tildeU", DIM=3)

    # Then ADM quantities
    gammaDD = ixp.declarerank2("gammaDD", "sym01", DIM=3)
    KDD = ixp.declarerank2("KDD", "sym01", DIM=3)
    betaU = ixp.declarerank1("betaU", DIM=3)
    alpha = sp.symbols('alpha', real=True)

    # Then numerical constant
    sqrt4pi = sp.symbols('sqrt4pi', real=True)

    # First compute smallb4U & smallbsquared from BtildeU, which are needed
    #      for GRMHD stress-energy tensor T4UU and T4UD:
    GRHD.compute_sqrtgammaDET(gammaDD)
    GRFFE.compute_B_notildeU(GRHD.sqrtgammaDET, B_tildeU)
    GRFFE.compute_smallb4U(gammaDD, betaU, alpha, u4U, GRFFE.B_notildeU,
                           sqrt4pi)
    GRFFE.compute_smallbsquared(gammaDD, betaU, alpha, GRFFE.smallb4U)

    # Then compute the GRMHD stress-energy tensor:
    compute_GRMHD_T4UU(gammaDD, betaU, alpha, rho_b, P, epsilon, u4U,
                       GRFFE.smallb4U, GRFFE.smallbsquared)
    compute_GRMHD_T4UD(gammaDD, betaU, alpha, GRHDT4UU, GRFFET4UU)

    # Compute conservative variables in terms of primitive variables
    global rho_star, tau_tilde, S_tildeD
    GRHD.compute_rho_star(alpha, GRHD.sqrtgammaDET, rho_b, u4U)
    GRHD.compute_tau_tilde(alpha, GRHD.sqrtgammaDET, T4UU, GRHD.rho_star)
    GRHD.compute_S_tildeD(alpha, GRHD.sqrtgammaDET, T4UD)
    rho_star = GRHD.rho_star
    tau_tilde = GRHD.tau_tilde
    S_tildeD = GRHD.S_tildeD

    # Then compute v^i from u^mu
    GRHD.compute_vU_from_u4U__no_speed_limit(u4U)

    # Next compute fluxes of conservative variables
    global rho_star_fluxU, tau_tilde_fluxU, S_tilde_fluxUD
    GRHD.compute_rho_star_fluxU(GRHD.vU, GRHD.rho_star)
    GRHD.compute_tau_tilde_fluxU(alpha, GRHD.sqrtgammaDET, GRHD.vU, T4UU,
                                 GRHD.rho_star)
    GRHD.compute_S_tilde_fluxUD(alpha, GRHD.sqrtgammaDET, T4UD)
    rho_star_fluxU = GRHD.rho_star_fluxU
    tau_tilde_fluxU = GRHD.tau_tilde_fluxU
    S_tilde_fluxUD = GRHD.S_tilde_fluxUD

    # Then declare derivatives & compute g4DD_zerotimederiv_dD
    gammaDD_dD = ixp.declarerank3("gammaDD_dD", "sym01", DIM=3)
    betaU_dD = ixp.declarerank2("betaU_dD", "nosym", DIM=3)
    alpha_dD = ixp.declarerank1("alpha_dD", DIM=3)
    GRHD.compute_g4DD_zerotimederiv_dD(gammaDD, betaU, alpha, gammaDD_dD,
                                       betaU_dD, alpha_dD)

    # Then compute source terms on tau_tilde and S_tilde equations
    global s_source_term, S_tilde_source_termD
    GRHD.compute_s_source_term(KDD, betaU, alpha, GRHD.sqrtgammaDET, alpha_dD,
                               T4UU)
    GRHD.compute_S_tilde_source_termD(alpha, GRHD.sqrtgammaDET,
                                      GRHD.g4DD_zerotimederiv_dD, T4UU)
    s_source_term = GRHD.s_source_term
    S_tilde_source_termD = GRHD.S_tilde_source_termD
Example #15
0
def WeylScalars_Cartesian():
    # Step 3.a: Set spatial dimension (must be 3 for BSSN)
    DIM = 3
    par.set_parval_from_str("grid::DIM", DIM)

    # Step 3.b: declare the additional gridfunctions (i.e., functions whose values are declared
    #          at every grid point, either inside or outside of our SymPy expressions) needed
    #         for this thorn:
    #           * the physical metric $\gamma_{ij}$,
    #           * the extrinsic curvature $K_{ij}$,
    #           * the real and imaginary components of $\psi_4$, and
    #           * the Weyl curvature invariants:
    gammaDD = ixp.register_gridfunctions_for_single_rank2(
        "AUX", "gammaDD", "sym01")  # The AUX or EVOL designation is *not*
    # used in diagnostic modules.
    kDD = ixp.register_gridfunctions_for_single_rank2("AUX", "kDD", "sym01")
    x, y, z = gri.register_gridfunctions("AUX", ["x", "y", "z"])
    global psi4r, psi4i, psi3r, psi3i, psi2r, psi2i, psi1r, psi1i, psi0r, psi0i
    psi4r, psi4i, psi3r, psi3i, psi2r, psi2i, psi1r, psi1i, psi0r, psi0i = gri.register_gridfunctions(
        "AUX", [
            "psi4r", "psi4i", "psi3r", "psi3i", "psi2r", "psi2i", "psi1r",
            "psi1i", "psi0r", "psi0i"
        ])

    # Step 4: Set which tetrad is used; at the moment, only one supported option

    # The tetrad depends in general on the inverse 3-metric gammaUU[i][j]=\gamma^{ij}
    #          and the determinant of the 3-metric (detgamma), which are defined in
    #          the following line of code from gammaDD[i][j]=\gamma_{ij}.
    tmpgammaUU, detgamma = ixp.symm_matrix_inverter3x3(gammaDD)
    detgamma = sp.simplify(detgamma)
    gammaUU = ixp.zerorank2()
    for i in range(3):
        for j in range(3):
            gammaUU[i][j] = sp.simplify(tmpgammaUU[i][j])

    if par.parval_from_str("WeylScal4NRPy.WeylScalars_Cartesian::TetradChoice"
                           ) == "Approx_QuasiKinnersley":
        # Eqs 5.6 in https://arxiv.org/pdf/gr-qc/0104063.pdf
        xmoved = x  # - xorig
        ymoved = y  # - yorig
        zmoved = z  # - zorig

        # Step 5.a: Choose 3 orthogonal vectors. Here, we choose one in the azimuthal
        #          direction, one in the radial direction, and the cross product of the two.
        # Eqs 5.7
        v1U = ixp.zerorank1()
        v2U = ixp.zerorank1()
        v3U = ixp.zerorank1()
        v1U[0] = -ymoved
        v1U[1] = xmoved  # + offset
        v1U[2] = sp.sympify(0)
        v2U[0] = xmoved  # + offset
        v2U[1] = ymoved
        v2U[2] = zmoved
        LeviCivitaSymbol_rank3 = ixp.LeviCivitaSymbol_dim3_rank3()
        for a in range(DIM):
            for b in range(DIM):
                for c in range(DIM):
                    for d in range(DIM):
                        v3U[a] += sp.sqrt(
                            detgamma) * gammaUU[a][d] * LeviCivitaSymbol_rank3[
                                d][b][c] * v1U[b] * v2U[c]

        for a in range(DIM):
            v3U[a] = sp.simplify(v3U[a])

        # Step 5.b: Gram-Schmidt orthonormalization of the vectors.
        # The w_i^a vectors here are used to temporarily hold values on the way to the final vectors e_i^a

        # e_1^a &= \frac{v_1^a}{\omega_{11}}
        # e_2^a &= \frac{v_2^a - \omega_{12} e_1^a}{\omega_{22}}
        # e_3^a &= \frac{v_3^a - \omega_{13} e_1^a - \omega_{23} e_2^a}{\omega_{33}},

        # Normalize the first vector
        w1U = ixp.zerorank1()
        for a in range(DIM):
            w1U[a] = v1U[a]
        omega11 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega11 += w1U[a] * w1U[b] * gammaDD[a][b]
        e1U = ixp.zerorank1()
        for a in range(DIM):
            e1U[a] = w1U[a] / sp.sqrt(omega11)

        # Subtract off the portion of the first vector along the second, then normalize
        omega12 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega12 += e1U[a] * v2U[b] * gammaDD[a][b]
        w2U = ixp.zerorank1()
        for a in range(DIM):
            w2U[a] = v2U[a] - omega12 * e1U[a]
        omega22 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega22 += w2U[a] * w2U[b] * gammaDD[a][b]
        e2U = ixp.zerorank1()
        for a in range(DIM):
            e2U[a] = w2U[a] / sp.sqrt(omega22)

        # Subtract off the portion of the first and second vectors along the third, then normalize
        omega13 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega13 += e1U[a] * v3U[b] * gammaDD[a][b]
        omega23 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega23 += e2U[a] * v3U[b] * gammaDD[a][b]
        w3U = ixp.zerorank1()
        for a in range(DIM):
            w3U[a] = v3U[a] - omega13 * e1U[a] - omega23 * e2U[a]
        omega33 = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omega33 += w3U[a] * w3U[b] * gammaDD[a][b]
        e3U = ixp.zerorank1()
        for a in range(DIM):
            e3U[a] = w3U[a] / sp.sqrt(omega33)

        # Step 5.c: Construct the tetrad itself.
        # Eqs. 5.6:
        # l^a &= \frac{1}{\sqrt{2}} e_2^a \\
        # n^a &= -\frac{1}{\sqrt{2}} e_2^a \\
        # m^a &= \frac{1}{\sqrt{2}} (e_3^a + i e_1^a) \\
        # \overset{*}{m}{}^a &= \frac{1}{\sqrt{2}} (e_3^a - i e_1^a)
        isqrt2 = 1 / sp.sqrt(2)
        ltetU = ixp.zerorank1()
        ntetU = ixp.zerorank1()
        # mtetU = ixp.zerorank1()
        # mtetccU = ixp.zerorank1()
        remtetU = ixp.zerorank1(
        )  # SymPy did not like trying to take the real/imaginary parts of such a
        immtetU = ixp.zerorank1(
        )  # complicated expression, so we do it ourselves.
        for i in range(DIM):
            ltetU[i] = isqrt2 * e2U[i]
            ntetU[i] = -isqrt2 * e2U[i]
            remtetU[i] = isqrt2 * e3U[i]
            immtetU[i] = isqrt2 * e1U[i]
        nn = isqrt2

    else:
        print("Error: TetradChoice == " + par.parval_from_str("TetradChoice") +
              " unsupported!")
        sys.exit(1)

    # Step 5: Declare and construct the second derivative of the metric.
    gammaDD_dD = ixp.declarerank3("gammaDD_dD", "sym01")

    # Define the Christoffel symbols
    GammaUDD = ixp.zerorank3(DIM)
    for i in range(DIM):
        for k in range(DIM):
            for l in range(DIM):
                for m in range(DIM):
                    GammaUDD[i][k][l] += (sp.Rational(1, 2)) * gammaUU[i][m] * \
                                         (gammaDD_dD[m][k][l] + gammaDD_dD[m][l][k] - gammaDD_dD[k][l][m])

    # Step 6.a: Declare and construct the Riemann curvature tensor:
    # R_{abcd} = \frac{1}{2} (\gamma_{ad,cb}+\gamma_{bc,da}-\gamma_{ac,bd}-\gamma_{bd,ac})
    #            + \gamma_{je} \Gamma^{j}_{bc}\Gamma^{e}_{ad} - \gamma_{je} \Gamma^{j}_{bd} \Gamma^{e}_{ac}
    gammaDD_dDD = ixp.declarerank4("gammaDD_dDD", "sym01_sym23")
    RiemannDDDD = ixp.zerorank4()
    for a in range(DIM):
        for b in range(DIM):
            for c in range(DIM):
                for d in range(DIM):
                    RiemannDDDD[a][b][c][d] = (gammaDD_dDD[a][d][c][b] + \
                                               gammaDD_dDD[b][c][d][a] - \
                                               gammaDD_dDD[a][c][b][d] - \
                                               gammaDD_dDD[b][d][a][c]) / 2
                    for e in range(DIM):
                        for j in range(DIM):
                            RiemannDDDD[a][b][c][d] +=  gammaDD[j][e] * GammaUDD[j][b][c] * GammaUDD[e][a][d] - \
                                                        gammaDD[j][e] * GammaUDD[j][b][d] * GammaUDD[e][a][c]

    # Step 6.b: We also need the extrinsic curvature tensor $K_{ij}$.
    # In Cartesian coordinates, we already made the components gridfunctions.
    # We will, however, need to calculate the trace of K seperately:
    trK = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            trK += gammaUU[i][j] * kDD[i][j]

    # Step 7: Build the formula for \psi_4.
    # Gauss equation: involving the Riemann tensor and extrinsic curvature.
    # GaussDDDD[i][j][k][l] =& R_{ijkl} + 2K_{i[k}K_{l]j}
    GaussDDDD = ixp.zerorank4()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    GaussDDDD[i][j][k][l] = RiemannDDDD[i][j][k][
                        l] + kDD[i][k] * kDD[l][j] - kDD[i][l] * kDD[k][j]

    # Codazzi equation: involving partial derivatives of the extrinsic curvature.
    # We will first need to declare derivatives of kDD
    # CodazziDDD[j][k][l] =& -2 (K_{j[k,l]} + \Gamma^p_{j[k} K_{l]p})
    kDD_dD = ixp.declarerank3("kDD_dD", "sym01")
    CodazziDDD = ixp.zerorank3()
    for j in range(DIM):
        for k in range(DIM):
            for l in range(DIM):
                CodazziDDD[j][k][l] = kDD_dD[j][l][k] - kDD_dD[j][k][l]
                for p in range(DIM):
                    CodazziDDD[j][k][l] += GammaUDD[p][j][l] * kDD[k][
                        p] - GammaUDD[p][j][k] * kDD[l][p]

    # Another piece. While not associated with any particular equation,
    # this is still useful for organizational purposes.
    # RojoDD[j][l]} = & R_{jl} - K_{jp} K^p_l + KK_{jl} \\
    #               = & \gamma^{pd} R_{jpld} - K_{jp} K^p_l + KK_{jl}
    RojoDD = ixp.zerorank2()
    for j in range(DIM):
        for l in range(DIM):
            RojoDD[j][l] = trK * kDD[j][l]
            for p in range(DIM):
                for d in range(DIM):
                    RojoDD[j][l] += gammaUU[p][d] * RiemannDDDD[j][p][l][
                        d] - kDD[j][p] * gammaUU[p][d] * kDD[d][l]

    # Now we can calculate $\psi_4$ itself! We assume l^0 = n^0 = \frac{1}{\sqrt{2}}
    # and m^0 = \overset{*}{m}{}^0 = 0 to simplify these equations.
    # We calculate the Weyl scalars as defined in https://arxiv.org/abs/gr-qc/0104063
    # In terms of the above-defined quantites, the psis are defined as:
    # \psi_4 =&\ (\text{GaussDDDD[i][j][k][l]}) n^i \overset{*}{m}{}^j n^k \overset{*}{m}{}^l \\
    #         &+2 (\text{CodazziDDD[j][k][l]}) n^{0} \overset{*}{m}{}^{j} n^k \overset{*}{m}{}^l \\
    #         &+ (\text{RojoDD[j][l]}) n^{0} \overset{*}{m}{}^{j} n^{0} \overset{*}{m}{}^{l}.
    # \psi_3 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i n^j \overset{*}{m}{}^k n^l \\
    #         &+ (\text{CodazziDDD[j][k][l]}) (l^{0} n^{j} \overset{*}{m}{}^k n^l - l^{j} n^{0} \overset{*}{m}{}^k n^l - l^k n^j\overset{*}{m}{}^l n^0) \\
    #         &- (\text{RojoDD[j][l]}) l^{0} n^{j} \overset{*}{m}{}^l n^0 - l^{j} n^{0} \overset{*}{m}{}^l n^0 \\
    # \psi_2 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i m^j \overset{*}{m}{}^k n^l \\
    #         &+ (\text{CodazziDDD[j][k][l]}) (l^{0} m^{j} \overset{*}{m}{}^k n^l - l^{j} m^{0} \overset{*}{m}{}^k n^l - l^k m^l \overset{*}{m}{}^l n^0) \\
    #         &- (\text{RojoDD[j][l]}) l^0 m^j \overset{*}{m}{}^l n^0 \\
    # \psi_1 =&\ (\text{GaussDDDD[i][j][k][l]}) n^i l^j m^k l^l \\
    #         &+ (\text{CodazziDDD[j][k][l]}) (n^{0} l^{j} m^k l^l - n^{j} l^{0} m^k l^l - n^k l^l m^j l^0) \\
    #         &- (\text{RojoDD[j][l]}) (n^{0} l^{j} m^l l^0 - n^{j} l^{0} m^l l^0) \\
    # \psi_0 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i m^j l^k m^l \\
    #         &+2 (\text{CodazziDDD[j][k][l]}) (l^0 m^j l^k m^l + l^k m^l l^0 m^j) \\
    #         &+ (\text{RojoDD[j][l]}) l^0 m^j l^0 m^j. \\

    psi4r = sp.sympify(0)
    psi4i = sp.sympify(0)
    psi3r = sp.sympify(0)
    psi3i = sp.sympify(0)
    psi2r = sp.sympify(0)
    psi2i = sp.sympify(0)
    psi1r = sp.sympify(0)
    psi1i = sp.sympify(0)
    psi0r = sp.sympify(0)
    psi0i = sp.sympify(0)
    for l in range(DIM):
        for j in range(DIM):
            psi4r += RojoDD[j][l] * nn * nn * (remtetU[j] * remtetU[l] -
                                               immtetU[j] * immtetU[l])
            psi4i += RojoDD[j][l] * nn * nn * (-remtetU[j] * immtetU[l] -
                                               immtetU[j] * remtetU[l])
            psi3r += -RojoDD[j][l] * nn * nn * (ntetU[j] -
                                                ltetU[j]) * remtetU[l]
            psi3i += RojoDD[j][l] * nn * nn * (ntetU[j] -
                                               ltetU[j]) * immtetU[l]
            psi2r += -RojoDD[j][l] * nn * nn * (remtetU[l] * remtetU[j] +
                                                immtetU[j] * immtetU[l])
            psi2i += -RojoDD[j][l] * nn * nn * (immtetU[l] * remtetU[j] -
                                                remtetU[j] * immtetU[l])
            psi1r += RojoDD[j][l] * nn * nn * (ntetU[j] * remtetU[l] -
                                               ltetU[j] * remtetU[l])
            psi1i += RojoDD[j][l] * nn * nn * (ntetU[j] * immtetU[l] -
                                               ltetU[j] * immtetU[l])
            psi0r += RojoDD[j][l] * nn * nn * (remtetU[j] * remtetU[l] -
                                               immtetU[j] * immtetU[l])
            psi0i += RojoDD[j][l] * nn * nn * (remtetU[j] * immtetU[l] +
                                               immtetU[j] * remtetU[l])

    for l in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                psi4r += 2 * CodazziDDD[j][k][l] * ntetU[k] * nn * (
                    remtetU[j] * remtetU[l] - immtetU[j] * immtetU[l])
                psi4i += 2 * CodazziDDD[j][k][l] * ntetU[k] * nn * (
                    -remtetU[j] * immtetU[l] - immtetU[j] * remtetU[l])
                psi3r += 1 * CodazziDDD[j][k][l] * nn * (
                    (ntetU[j] - ltetU[j]) * remtetU[k] * ntetU[l] -
                    remtetU[j] * ltetU[k] * ntetU[l])
                psi3i += -1 * CodazziDDD[j][k][l] * nn * (
                    (ntetU[j] - ltetU[j]) * immtetU[k] * ntetU[l] -
                    immtetU[j] * ltetU[k] * ntetU[l])
                psi2r += 1 * CodazziDDD[j][k][l] * nn * (
                    ntetU[l] *
                    (remtetU[j] * remtetU[k] + immtetU[j] * immtetU[k]) -
                    ltetU[k] *
                    (remtetU[j] * remtetU[l] + immtetU[j] * immtetU[l]))
                psi2i += 1 * CodazziDDD[j][k][l] * nn * (
                    ntetU[l] *
                    (immtetU[j] * remtetU[k] - remtetU[j] * immtetU[k]) -
                    ltetU[k] *
                    (remtetU[j] * immtetU[l] - immtetU[j] * remtetU[l]))
                psi1r += 1 * CodazziDDD[j][k][l] * nn * (
                    ltetU[j] * remtetU[k] * ltetU[l] - remtetU[j] * ntetU[k] *
                    ltetU[l] - ntetU[j] * remtetU[k] * ltetU[l])
                psi1i += 1 * CodazziDDD[j][k][l] * nn * (
                    ltetU[j] * immtetU[k] * ltetU[l] - immtetU[j] * ntetU[k] *
                    ltetU[l] - ntetU[j] * immtetU[k] * ltetU[l])
                psi0r += 2 * CodazziDDD[j][k][l] * nn * ltetU[k] * (
                    remtetU[j] * remtetU[l] - immtetU[j] * immtetU[l])
                psi0i += 2 * CodazziDDD[j][k][l] * nn * ltetU[k] * (
                    remtetU[j] * immtetU[l] + immtetU[j] * remtetU[l])

    for l in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for i in range(DIM):
                    psi4r += GaussDDDD[i][j][k][l] * ntetU[i] * ntetU[k] * (
                        remtetU[j] * remtetU[l] - immtetU[j] * immtetU[l])
                    psi4i += GaussDDDD[i][j][k][l] * ntetU[i] * ntetU[k] * (
                        -remtetU[j] * immtetU[l] - immtetU[j] * remtetU[l])
                    psi3r += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[
                        j] * remtetU[k] * ntetU[l]
                    psi3i += -GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[
                        j] * immtetU[k] * ntetU[l]
                    psi2r += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[l] * (
                        remtetU[j] * remtetU[k] + immtetU[j] * immtetU[k])
                    psi2i += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[l] * (
                        immtetU[j] * remtetU[k] - remtetU[j] * immtetU[k])
                    psi1r += GaussDDDD[i][j][k][l] * ntetU[i] * ltetU[
                        j] * remtetU[k] * ltetU[l]
                    psi1i += GaussDDDD[i][j][k][l] * ntetU[i] * ltetU[
                        j] * immtetU[k] * ltetU[l]
                    psi0r += GaussDDDD[i][j][k][l] * ltetU[i] * ltetU[k] * (
                        remtetU[j] * remtetU[l] - immtetU[j] * immtetU[l])
                    psi0i += GaussDDDD[i][j][k][l] * ltetU[i] * ltetU[k] * (
                        remtetU[j] * immtetU[l] + immtetU[j] * remtetU[l])
Example #16
0
    def BSSN_RHSs__generate_symbolic_expressions():
        ######################################
        # START: GENERATE SYMBOLIC EXPRESSIONS
        print("Generating symbolic expressions for BSSN RHSs...")
        start = time.time()
        # Enable rfm_precompute infrastructure, which results in
        #   BSSN RHSs that are free of transcendental functions,
        #   even in curvilinear coordinates, so long as
        #   ConformalFactor is set to "W" (default).
        par.set_parval_from_str("reference_metric::enable_rfm_precompute","True")
        par.set_parval_from_str("reference_metric::rfm_precompute_Ccode_outdir",os.path.join(outdir,"rfm_files/"))

        # Evaluate BSSN + BSSN gauge RHSs with rfm_precompute enabled:
        import BSSN.BSSN_quantities as Bq
        par.set_parval_from_str("BSSN.BSSN_quantities::LeaveRicciSymbolic","True")

        rhs.BSSN_RHSs()

        if T4UU != None:
            import BSSN.BSSN_stress_energy_source_terms as Bsest
            Bsest.BSSN_source_terms_for_BSSN_RHSs(T4UU)
            rhs.trK_rhs += Bsest.sourceterm_trK_rhs
            for i in range(3):
                # Needed for Gamma-driving shift RHSs:
                rhs.Lambdabar_rhsU[i] += Bsest.sourceterm_Lambdabar_rhsU[i]
                # Needed for BSSN RHSs:
                rhs.lambda_rhsU[i]    += Bsest.sourceterm_lambda_rhsU[i]
                for j in range(3):
                    rhs.a_rhsDD[i][j] += Bsest.sourceterm_a_rhsDD[i][j]

        gaugerhs.BSSN_gauge_RHSs()

        # Add Kreiss-Oliger dissipation to the BSSN RHSs:
        thismodule = "KO_Dissipation"
        diss_strength = par.Cparameters("REAL", thismodule, "diss_strength", default_KO_strength)

        alpha_dKOD = ixp.declarerank1("alpha_dKOD")
        cf_dKOD    = ixp.declarerank1("cf_dKOD")
        trK_dKOD   = ixp.declarerank1("trK_dKOD")
        betU_dKOD    = ixp.declarerank2("betU_dKOD","nosym")
        vetU_dKOD    = ixp.declarerank2("vetU_dKOD","nosym")
        lambdaU_dKOD = ixp.declarerank2("lambdaU_dKOD","nosym")
        aDD_dKOD = ixp.declarerank3("aDD_dKOD","sym01")
        hDD_dKOD = ixp.declarerank3("hDD_dKOD","sym01")
        for k in range(3):
            gaugerhs.alpha_rhs += diss_strength*alpha_dKOD[k]*rfm.ReU[k] # ReU[k] = 1/scalefactor_orthog_funcform[k]
            rhs.cf_rhs         += diss_strength*   cf_dKOD[k]*rfm.ReU[k] # ReU[k] = 1/scalefactor_orthog_funcform[k]
            rhs.trK_rhs        += diss_strength*  trK_dKOD[k]*rfm.ReU[k] # ReU[k] = 1/scalefactor_orthog_funcform[k]
            for i in range(3):
                if "2ndOrder" in ShiftCondition:
                    gaugerhs.bet_rhsU[i] += diss_strength*   betU_dKOD[i][k]*rfm.ReU[k] # ReU[k] = 1/scalefactor_orthog_funcform[k]
                gaugerhs.vet_rhsU[i]     += diss_strength*   vetU_dKOD[i][k]*rfm.ReU[k] # ReU[k] = 1/scalefactor_orthog_funcform[k]
                rhs.lambda_rhsU[i]       += diss_strength*lambdaU_dKOD[i][k]*rfm.ReU[k] # ReU[k] = 1/scalefactor_orthog_funcform[k]
                for j in range(3):
                    rhs.a_rhsDD[i][j] += diss_strength*aDD_dKOD[i][j][k]*rfm.ReU[k] # ReU[k] = 1/scalefactor_orthog_funcform[k]
                    rhs.h_rhsDD[i][j] += diss_strength*hDD_dKOD[i][j][k]*rfm.ReU[k] # ReU[k] = 1/scalefactor_orthog_funcform[k]

        # We use betaU as our upwinding control vector:
        Bq.BSSN_basic_tensors()
        betaU = Bq.betaU

        # Now that we are finished with all the rfm hatted
        #           quantities in generic precomputed functional
        #           form, let's restore them to their closed-
        #           form expressions.
        par.set_parval_from_str("reference_metric::enable_rfm_precompute","False") # Reset to False to disable rfm_precompute.
        rfm.ref_metric__hatted_quantities()
        par.set_parval_from_str("BSSN.BSSN_quantities::LeaveRicciSymbolic","False")
        end = time.time()
        print("(BENCH) Finished BSSN RHS symbolic expressions in "+str(end-start)+" seconds.")
        # END: GENERATE SYMBOLIC EXPRESSIONS
        ######################################

        BSSN_RHSs_SymbExpressions = [lhrh(lhs=gri.gfaccess("rhs_gfs","aDD00"),   rhs=rhs.a_rhsDD[0][0]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","aDD01"),   rhs=rhs.a_rhsDD[0][1]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","aDD02"),   rhs=rhs.a_rhsDD[0][2]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","aDD11"),   rhs=rhs.a_rhsDD[1][1]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","aDD12"),   rhs=rhs.a_rhsDD[1][2]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","aDD22"),   rhs=rhs.a_rhsDD[2][2]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","alpha"),   rhs=gaugerhs.alpha_rhs),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","betU0"),   rhs=gaugerhs.bet_rhsU[0]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","betU1"),   rhs=gaugerhs.bet_rhsU[1]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","betU2"),   rhs=gaugerhs.bet_rhsU[2]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","cf"),      rhs=rhs.cf_rhs),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","hDD00"),   rhs=rhs.h_rhsDD[0][0]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","hDD01")   ,rhs=rhs.h_rhsDD[0][1]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","hDD02"),   rhs=rhs.h_rhsDD[0][2]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","hDD11"),   rhs=rhs.h_rhsDD[1][1]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","hDD12"),   rhs=rhs.h_rhsDD[1][2]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","hDD22"),   rhs=rhs.h_rhsDD[2][2]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","lambdaU0"),rhs=rhs.lambda_rhsU[0]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","lambdaU1"),rhs=rhs.lambda_rhsU[1]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","lambdaU2"),rhs=rhs.lambda_rhsU[2]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","trK"),     rhs=rhs.trK_rhs),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","vetU0"),   rhs=gaugerhs.vet_rhsU[0]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","vetU1"),   rhs=gaugerhs.vet_rhsU[1]),
                                     lhrh(lhs=gri.gfaccess("rhs_gfs","vetU2"),   rhs=gaugerhs.vet_rhsU[2]) ]

        return [betaU,BSSN_RHSs_SymbExpressions]
def BSSN_constraints(add_T4UUmunu_source_terms=False):
    # Step 1.a: 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.b: 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 2: Hamiltonian constraint.
    # First declare all needed variables
    Bq.declare_BSSN_gridfunctions_if_not_declared_already()  # Sets trK
    Bq.BSSN_basic_tensors()  # Sets AbarDD
    Bq.gammabar__inverse_and_derivs()  # Sets gammabarUU
    Bq.AbarUU_AbarUD_trAbar_AbarDD_dD()  # Sets AbarUU and AbarDD_dD
    Bq.RicciBar__gammabarDD_dHatD__DGammaUDD__DGammaU()  # Sets RbarDD
    Bq.phi_and_derivs()  # Sets phi_dBarD & phi_dBarDD

    ###############################
    ###############################
    #  HAMILTONIAN CONSTRAINT
    ###############################
    ###############################

    # Term 1: 2/3 K^2
    global H
    H = sp.Rational(2, 3) * Bq.trK**2

    # Term 2: -A_{ij} A^{ij}
    for i in range(DIM):
        for j in range(DIM):
            H += -Bq.AbarDD[i][j] * Bq.AbarUU[i][j]

    # Term 3a: trace(Rbar)
    Rbartrace = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            Rbartrace += Bq.gammabarUU[i][j] * Bq.RbarDD[i][j]

    # Term 3b: -8 \bar{\gamma}^{ij} \bar{D}_i \phi \bar{D}_j \phi = -8*phi_dBar_times_phi_dBar
    # Term 3c: -8 \bar{\gamma}^{ij} \bar{D}_i \bar{D}_j \phi      = -8*phi_dBarDD_contraction
    phi_dBar_times_phi_dBar = sp.sympify(0)  # Term 3b
    phi_dBarDD_contraction = sp.sympify(0)  # Term 3c
    for i in range(DIM):
        for j in range(DIM):
            phi_dBar_times_phi_dBar += Bq.gammabarUU[i][j] * Bq.phi_dBarD[
                i] * Bq.phi_dBarD[j]
            phi_dBarDD_contraction += Bq.gammabarUU[i][j] * Bq.phi_dBarDD[i][j]

    # Add Term 3:
    H += Bq.exp_m4phi * (Rbartrace - 8 *
                         (phi_dBar_times_phi_dBar + phi_dBarDD_contraction))

    if add_T4UUmunu_source_terms:
        M_PI = par.Cparameters("#define", thismodule, "M_PI",
                               "")  # M_PI is pi as defined in C
        BTmunu.define_BSSN_T4UUmunu_rescaled_source_terms()
        rho = BTmunu.rho
        H += -16 * M_PI * rho

    # FIXME: ADD T4UUmunu SOURCE TERMS TO MOMENTUM CONSTRAINT!

    # Step 3: M^i, the momentum constraint

    ###############################
    ###############################
    #  MOMENTUM CONSTRAINT
    ###############################
    ###############################

    # SEE Tutorial-BSSN_constraints.ipynb for full documentation.
    global MU
    MU = ixp.zerorank1()

    # Term 2: 6 A^{ij} \partial_j \phi:
    for i in range(DIM):
        for j in range(DIM):
            MU[i] += 6 * Bq.AbarUU[i][j] * Bq.phi_dD[j]

    # Term 3: -2/3 \bar{\gamma}^{ij} K_{,j}
    trK_dD = ixp.declarerank1(
        "trK_dD")  # Not defined in BSSN_RHSs; only trK_dupD is defined there.
    for i in range(DIM):
        for j in range(DIM):
            MU[i] += -sp.Rational(2, 3) * Bq.gammabarUU[i][j] * trK_dD[j]

    # First define aDD_dD:
    aDD_dD = ixp.declarerank3("aDD_dD", "sym01")

    # Then evaluate the conformal covariant derivative \bar{D}_j \bar{A}_{lm}
    AbarDD_dBarD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                AbarDD_dBarD[i][j][k] = Bq.AbarDD_dD[i][j][k]
                for l in range(DIM):
                    AbarDD_dBarD[i][j][
                        k] += -Bq.GammabarUDD[l][k][i] * Bq.AbarDD[l][j]
                    AbarDD_dBarD[i][j][
                        k] += -Bq.GammabarUDD[l][k][j] * Bq.AbarDD[i][l]

    # Term 1: Contract twice with the metric to make \bar{D}_{j} \bar{A}^{ij}
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    MU[i] += Bq.gammabarUU[i][k] * Bq.gammabarUU[j][
                        l] * AbarDD_dBarD[k][l][j]

    # Finally, we multiply by e^{-4 phi} and rescale the momentum constraint:
    for i in range(DIM):
        MU[i] *= Bq.exp_m4phi / rfm.ReU[i]
def VacuumMaxwellRHSs_rescaled():
    global erhsU, arhsU, psi_rhs, Gamma_rhs, C, G, EU_Cart, AU_Cart

    #Step 0: Set the spatial dimension parameter to 3.
    par.set_parval_from_str("grid::DIM", 3)
    DIM = par.parval_from_str("grid::DIM")

    # Set reference metric related quantities
    rfm.reference_metric()

    # Register gridfunctions that are needed as input.

    #  Declare the rank-1 indexed expressions E^{i}, A^{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 is unused here
    _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]

    # 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]

    # \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

    # \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()

    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]

    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.xxCart[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)
Example #19
0
def BSSN_RHSs__generate_symbolic_expressions(
        LapseCondition="OnePlusLog",
        ShiftCondition="GammaDriving2ndOrder_Covariant",
        enable_KreissOliger_dissipation=True,
        enable_stress_energy_source_terms=False,
        leave_Ricci_symbolic=True):
    ######################################
    # START: GENERATE SYMBOLIC EXPRESSIONS
    starttime = print_msg_with_timing("BSSN_RHSs",
                                      msg="Symbolic",
                                      startstop="start")

    # Returns None if enable_stress_energy_source_terms==False; otherwise returns symb expressions for T4UU
    T4UU = register_stress_energy_source_terms_return_T4UU(
        enable_stress_energy_source_terms)

    # Evaluate BSSN RHSs:
    import BSSN.BSSN_quantities as Bq
    par.set_parval_from_str("BSSN.BSSN_quantities::LeaveRicciSymbolic",
                            str(leave_Ricci_symbolic))
    rhs.BSSN_RHSs()

    if enable_stress_energy_source_terms:
        import BSSN.BSSN_stress_energy_source_terms as Bsest
        Bsest.BSSN_source_terms_for_BSSN_RHSs(T4UU)
        rhs.trK_rhs += Bsest.sourceterm_trK_rhs
        for i in range(3):
            # Needed for Gamma-driving shift RHSs:
            rhs.Lambdabar_rhsU[i] += Bsest.sourceterm_Lambdabar_rhsU[i]
            # Needed for BSSN RHSs:
            rhs.lambda_rhsU[i] += Bsest.sourceterm_lambda_rhsU[i]
            for j in range(3):
                rhs.a_rhsDD[i][j] += Bsest.sourceterm_a_rhsDD[i][j]

    par.set_parval_from_str("BSSN.BSSN_gauge_RHSs::LapseEvolutionOption",
                            LapseCondition)
    par.set_parval_from_str("BSSN.BSSN_gauge_RHSs::ShiftEvolutionOption",
                            ShiftCondition)
    gaugerhs.BSSN_gauge_RHSs()  # Can depend on above RHSs
    # Restore BSSN.BSSN_quantities::LeaveRicciSymbolic to False
    par.set_parval_from_str("BSSN.BSSN_quantities::LeaveRicciSymbolic",
                            "False")

    # Add Kreiss-Oliger dissipation to the BSSN RHSs:
    if enable_KreissOliger_dissipation:
        thismodule = "KO_Dissipation"
        diss_strength = par.Cparameters(
            "REAL", thismodule, "diss_strength", 0.1
        )  # *Bq.cf # *Bq.cf*Bq.cf*Bq.cf # cf**1 is found better than cf**4 over the long term.

        alpha_dKOD = ixp.declarerank1("alpha_dKOD")
        cf_dKOD = ixp.declarerank1("cf_dKOD")
        trK_dKOD = ixp.declarerank1("trK_dKOD")
        betU_dKOD = ixp.declarerank2("betU_dKOD", "nosym")
        vetU_dKOD = ixp.declarerank2("vetU_dKOD", "nosym")
        lambdaU_dKOD = ixp.declarerank2("lambdaU_dKOD", "nosym")
        aDD_dKOD = ixp.declarerank3("aDD_dKOD", "sym01")
        hDD_dKOD = ixp.declarerank3("hDD_dKOD", "sym01")
        for k in range(3):
            gaugerhs.alpha_rhs += diss_strength * alpha_dKOD[k] * rfm.ReU[
                k]  # ReU[k] = 1/scalefactor_orthog_funcform[k]
            rhs.cf_rhs += diss_strength * cf_dKOD[k] * rfm.ReU[
                k]  # ReU[k] = 1/scalefactor_orthog_funcform[k]
            rhs.trK_rhs += diss_strength * trK_dKOD[k] * rfm.ReU[
                k]  # ReU[k] = 1/scalefactor_orthog_funcform[k]
            for i in range(3):
                if "2ndOrder" in ShiftCondition:
                    gaugerhs.bet_rhsU[
                        i] += diss_strength * betU_dKOD[i][k] * rfm.ReU[
                            k]  # ReU[k] = 1/scalefactor_orthog_funcform[k]
                gaugerhs.vet_rhsU[
                    i] += diss_strength * vetU_dKOD[i][k] * rfm.ReU[
                        k]  # ReU[k] = 1/scalefactor_orthog_funcform[k]
                rhs.lambda_rhsU[
                    i] += diss_strength * lambdaU_dKOD[i][k] * rfm.ReU[
                        k]  # ReU[k] = 1/scalefactor_orthog_funcform[k]
                for j in range(3):
                    rhs.a_rhsDD[i][
                        j] += diss_strength * aDD_dKOD[i][j][k] * rfm.ReU[
                            k]  # ReU[k] = 1/scalefactor_orthog_funcform[k]
                    rhs.h_rhsDD[i][
                        j] += diss_strength * hDD_dKOD[i][j][k] * rfm.ReU[
                            k]  # ReU[k] = 1/scalefactor_orthog_funcform[k]

    # We use betaU as our upwinding control vector:
    Bq.BSSN_basic_tensors()
    betaU = Bq.betaU

    # END: GENERATE SYMBOLIC EXPRESSIONS
    ######################################

    lhs_names = ["alpha", "cf", "trK"]
    rhs_exprs = [gaugerhs.alpha_rhs, rhs.cf_rhs, rhs.trK_rhs]
    for i in range(3):
        lhs_names.append("betU" + str(i))
        rhs_exprs.append(gaugerhs.bet_rhsU[i])
        lhs_names.append("lambdaU" + str(i))
        rhs_exprs.append(rhs.lambda_rhsU[i])
        lhs_names.append("vetU" + str(i))
        rhs_exprs.append(gaugerhs.vet_rhsU[i])
        for j in range(i, 3):
            lhs_names.append("aDD" + str(i) + str(j))
            rhs_exprs.append(rhs.a_rhsDD[i][j])
            lhs_names.append("hDD" + str(i) + str(j))
            rhs_exprs.append(rhs.h_rhsDD[i][j])

    # Sort the lhss list alphabetically, and rhss to match.
    #   This ensures the RHSs are evaluated in the same order
    #   they're allocated in memory:
    lhs_names, rhs_exprs = [
        list(x) for x in zip(
            *sorted(zip(lhs_names, rhs_exprs), key=lambda pair: pair[0]))
    ]

    # Declare the list of lhrh's
    BSSN_RHSs_SymbExpressions = []
    for var in range(len(lhs_names)):
        BSSN_RHSs_SymbExpressions.append(
            lhrh(lhs=gri.gfaccess("rhs_gfs", lhs_names[var]),
                 rhs=rhs_exprs[var]))

    print_msg_with_timing("BSSN_RHSs",
                          msg="Symbolic",
                          startstop="stop",
                          starttime=starttime)
    return [betaU, BSSN_RHSs_SymbExpressions]