def compute_ValenciavU_from_ED_and_BU(ED, BU, gammaDD=None):
    # Now, we calculate v^i = ([ijk] E_j B_k) / B^2,
    # where [ijk] is the Levi-Civita symbol and B^2 = \gamma_{ij} B^i B^j$ is a trivial dot product in flat space.

    # In flat spacetime, use the Minkowski metric; otherwise, use the input metric.
    if gammaDD is None:
        gammaDD = ixp.zerorank2()
        for i in range(3):
            gammaDD[i][i] = sp.sympify(1)

    unused_gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)
    sqrtgammaDET = sp.sqrt(gammaDET)
    LeviCivitaTensorUUU = ixp.LeviCivitaTensorUUU_dim3_rank3(sqrtgammaDET)

    BD = ixp.zerorank1()
    for i in range(3):
        for j in range(3):
            BD[i] += gammaDD[i][j] * BU[j]
    B2 = sp.sympify(0)
    for i in range(3):
        B2 += BU[i] * BD[i]

    ValenciavU = ixp.zerorank1()
    for i in range(3):
        for j in range(3):
            for k in range(3):
                ValenciavU[
                    i] += LeviCivitaTensorUUU[i][j][k] * ED[j] * BD[k] / B2

    return ValenciavU
def LambdabarU_lambdaU__exact_gammaDD(gammaDD):
    global LambdabarU, lambdaU

    if gammaDD == None:
        gammaDD = ixp.declarerank2("gammaDD", "sym01")

    # \bar{Lambda}^i = \bar{gamma}^{jk}(\bar{Gamma}^i_{jk} - \hat{Gamma}^i_{jk}).
    gammabarDD_hDD(gammaDD)
    gammabarUU, gammabarDET = ixp.symm_matrix_inverter3x3(gammabarDD)

    # First compute Christoffel symbols \bar{Gamma}^i_{jk}, with respect to barred metric:
    GammabarUDD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    GammabarUDD[i][j][k] += sp.Rational(
                        1, 2) * gammabarUU[i][l] * (
                            sp.diff(gammabarDD[l][j], rfm.xx[k]) +
                            sp.diff(gammabarDD[l][k], rfm.xx[j]) -
                            sp.diff(gammabarDD[j][k], rfm.xx[l]))
    # Next evaluate \bar{Lambda}^i, based on GammabarUDD above and GammahatUDD
    #       (from the reference metric):
    LambdabarU = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                LambdabarU[i] += gammabarUU[j][k] * (GammabarUDD[i][j][k] -
                                                     rfm.GammahatUDD[i][j][k])
    lambdaU = ixp.zerorank1()
    for i in range(DIM):
        lambdaU[i] = LambdabarU[i] / rfm.ReU[i]
Beispiel #3
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])
def trK_AbarDD_aDD(gammaDD, KDD):
    global trK, AbarDD, aDD

    if gammaDD == None:
        gammaDD = ixp.declarerank2("gammaDD", "sym01")
    if KDD == None:
        KDD = ixp.declarerank2("KDD", "sym01")

    if rfm.have_already_called_reference_metric_function == False:
        print(
            "BSSN.BSSN_in_terms_of_ADM.trK_AbarDD(): Must call reference_metric() first!"
        )
        sys.exit(1)
    # \bar{gamma}_{ij} = (\frac{\bar{gamma}}{gamma})^{1/3}*gamma_{ij}.
    gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)
    # K = gamma^{ij} K_{ij}, and
    # \bar{A}_{ij} &= (\frac{\bar{gamma}}{gamma})^{1/3}*(K_{ij} - \frac{1}{3}*gamma_{ij}*K)
    trK = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            trK += gammaUU[i][j] * KDD[i][j]

    AbarDD = ixp.zerorank2()
    aDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            AbarDD[i][j] = (rfm.detgammahat / gammaDET)**(sp.Rational(
                1, 3)) * (KDD[i][j] - sp.Rational(1, 3) * gammaDD[i][j] * trK)
            aDD[i][j] = AbarDD[i][j] / rfm.ReDD[i][j]
def ADM_to_four_metric(gammaDD,
                       betaU,
                       alpha,
                       returng4DD=True,
                       returng4UU=False):
    # The ADM formulation decomposes Einstein's 4D equations into 3+1 dimensional form, with
    #     the 3 spatial dimensions separated from the 1 temporal dimension. DIM here refers to
    #     the spatial dimension.
    DIM = 3

    # Eq 4.47 in [Gourgoulhon](https://arxiv.org/pdf/gr-qc/0703035.pdf):

    # g_{tt} = -\alpha^2 + \beta^k \beta_k
    # g_{ti} = \beta_i
    # g_{ij} = \gamma_{ij}

    # Eq. 2.121 in B&S
    betaD = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            betaD[i] += gammaDD[i][j] * betaU[j]

    # Now compute the beta contraction.
    beta2 = sp.sympify(0)
    for i in range(DIM):
        beta2 += betaU[i] * betaD[i]

    g4DD = ixp.zerorank2(DIM=4)
    g4DD[0][0] = -alpha**2 + beta2
    for i in range(DIM):
        g4DD[i + 1][0] = g4DD[0][i + 1] = betaD[i]
        for j in range(DIM):
            g4DD[i + 1][j + 1] = gammaDD[i][j]

    if returng4DD == True and returng4UU == False:
        return g4DD

    gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)
    # Eq. 4.49 in [Gourgoulhon](https://arxiv.org/pdf/gr-qc/0703035.pdf):

    # g^{tt} = -1 / alpha^2
    # g^{ti} = beta^i / alpha^2
    # g^{ij} = gamma^{ij} - beta^i beta^j / alpha^2

    g4UU = ixp.zerorank2(DIM=4)
    g4UU[0][0] = -1 / alpha**2
    for i in range(DIM):
        g4UU[i + 1][0] = g4UU[0][i + 1] = betaU[i] / alpha**2
        for j in range(DIM):
            g4UU[i + 1][j + 1] = gammaUU[i][j] - betaU[i] * betaU[j] / alpha**2

    if returng4DD == True and returng4UU == True:
        return g4DD, g4UU
    if returng4DD == False and returng4UU == True:
        return g4UU
    print(
        "Error: ADM_to_four_metric() called without requesting anything being returned!"
    )
    exit(1)
def BSSN_or_ADM_ito_g4DD(inputvars,g4DD=None):
    # Step 0: Declare output variables as globals, to make interfacing with other modules/functions easier
    if inputvars == "ADM":
        global gammaDD, betaU, alpha
    elif inputvars == "BSSN":
        global hDD, cf, vetU, alpha
    else:
        print("inputvars = " + str(inputvars) + " not supported. Please choose ADM or BSSN.")
        sys.exit(1)

    # Step 1: declare g4DD as symmetric rank-4 tensor:
    if g4DD == None:
        g4DD = ixp.declarerank2("g4DD", "sym01", DIM=4)

    # Step 2: Compute gammaDD & betaD
    betaD = ixp.zerorank1()
    gammaDD = ixp.zerorank2()
    for i in range(3):
        betaD[i] = g4DD[0][i]
        for j in range(3):
            gammaDD[i][j] = g4DD[i + 1][j + 1]

    # Step 3: Compute betaU
    # Step 3.a: Compute gammaUU based on provided gammaDD
    gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)

    # Step 3.b: Use gammaUU to raise betaU
    betaU = ixp.zerorank1()
    for i in range(3):
        for j in range(3):
            betaU[i] += gammaUU[i][j] * betaD[j]

    # Step 4:   Compute alpha = sqrt(beta^2 - g_{00}):
    # Step 4.a: Compute beta^2 = beta^k beta_k:
    beta_squared = sp.sympify(0)
    for k in range(3):
        beta_squared += betaU[k] * betaD[k]

    # Step 4.b: alpha = sqrt(beta^2 - g_{00}):
    if g4DD==None:
        alpha = sp.sqrt(sp.simplify(beta_squared) - g4DD[0][0])
    else:
        alpha = sp.sqrt(beta_squared - g4DD[0][0])

    # Step 5: If inputvars == "ADM", we are finished. Return.
    if inputvars == "ADM":
        return

    # Step 6: If inputvars == "BSSN", convert ADM to BSSN
    import BSSN.BSSN_in_terms_of_ADM as BitoA
    dummyBU = ixp.zerorank1()
    BitoA.gammabarDD_hDD(gammaDD)
    BitoA.cf_from_gammaDD(gammaDD)
    BitoA.betU_vetU(betaU, dummyBU)
    hDD  = BitoA.hDD
    cf   = BitoA.cf
    vetU = BitoA.vetU
Beispiel #7
0
def BSSN_source_terms_for_BSSN_RHSs(custom_T4UU=None):
    global sourceterm_trK_rhs, sourceterm_a_rhsDD, sourceterm_lambda_rhsU, sourceterm_Lambdabar_rhsU

    # Step 3.a: Call BSSN_source_terms_ito_T4UU to get SDD, SD, S, & rho

    if custom_T4UU == "unrescaled BSSN source terms already given":
        SDD = ixp.declarerank2("SDD", "sym01")
        SD = ixp.declarerank1("SD")
        S = sp.symbols("S", real=True)
        rho = sp.symbols("rho", real=True)
    else:
        SDD, SD, S, rho = stress_energy_source_terms_ito_T4UU_and_ADM_or_BSSN_metricvars(
            "BSSN", custom_T4UU)
    PI = par.Cparameters("REAL", thismodule, ["PI"],
                         "3.14159265358979323846264338327950288")
    alpha = sp.symbols("alpha", real=True)

    # Step 3.b: trK_rhs
    sourceterm_trK_rhs = 4 * PI * alpha * (rho + S)

    # Step 3.c: Abar_rhsDD:
    # Step 3.c.i: Compute trace-free part of S_{ij}:
    import BSSN.BSSN_quantities as Bq
    Bq.BSSN_basic_tensors()  # Sets gammabarDD
    gammabarUU, dummydet = ixp.symm_matrix_inverter3x3(
        Bq.gammabarDD)  # Set gammabarUU
    tracefree_SDD = ixp.zerorank2()
    for i in range(3):
        for j in range(3):
            tracefree_SDD[i][j] = SDD[i][j]
    for i in range(3):
        for j in range(3):
            for k in range(3):
                for m in range(3):
                    tracefree_SDD[i][j] += -sp.Rational(1, 3) * Bq.gammabarDD[
                        i][j] * gammabarUU[k][m] * SDD[k][m]
    # Step 3.c.ii: Define exp_m4phi = e^{-4 phi}
    Bq.phi_and_derivs()
    # Step 3.c.iii: Evaluate stress-energy part of AbarDD's RHS
    sourceterm_a_rhsDD = ixp.zerorank2()
    for i in range(3):
        for j in range(3):
            Abar_rhsDDij = -8 * PI * alpha * Bq.exp_m4phi * tracefree_SDD[i][j]
            sourceterm_a_rhsDD[i][j] = Abar_rhsDDij / rfm.ReDD[i][j]

    # Step 3.d: Stress-energy part of Lambdabar_rhsU = stressenergy_Lambdabar_rhsU
    sourceterm_Lambdabar_rhsU = ixp.zerorank1()
    for i in range(3):
        for j in range(3):
            sourceterm_Lambdabar_rhsU[
                i] += -16 * PI * alpha * gammabarUU[i][j] * SD[j]
    sourceterm_lambda_rhsU = ixp.zerorank1()
    for i in range(3):
        sourceterm_lambda_rhsU[i] = sourceterm_Lambdabar_rhsU[i] / rfm.ReU[i]
def GiRaFFEfood_HO_ID_converter():
    gammaDD = ixp.register_gridfunctions_for_single_rank2("AUX",
                                                          "gammaDD",
                                                          "sym01",
                                                          DIM=3)
    gammaUU, gammadet = ixp.symm_matrix_inverter3x3(gammaDD)
    # Step 5: Build the expression for \tilde{S}_i
    global StildeD
    StildeD = ixp.zerorank1()
    BU = ixp.register_gridfunctions_for_single_rank1(
        "AUX", "BU")  # Reset so that NRPy will access stored values of BU
    ###########################################################
    #r     = rfm.xxSph[0] + KerrSchild_radial_shift # We are setting the data up in Shifted Kerr-Schild coordinates
    #theta = rfm.xxSph[1]
    #ASphD = ixp.zerorank1()
    #ASphD[2] = (r * r * sp.sin(theta)**2)/2
    #AD = ixp.zerorank1()
    #drrefmetric__dx_0UDmatrix = sp.Matrix([[sp.diff(rfm.xxSph[0],rfm.xx[0]), sp.diff(rfm.xxSph[0],rfm.xx[1]), sp.diff(rfm.xxSph[0],rfm.xx[2])],
    #                                       [sp.diff(rfm.xxSph[1],rfm.xx[0]), sp.diff(rfm.xxSph[1],rfm.xx[1]), sp.diff(rfm.xxSph[1],rfm.xx[2])],
    #                                       [sp.diff(rfm.xxSph[2],rfm.xx[0]), sp.diff(rfm.xxSph[2],rfm.xx[1]), sp.diff(rfm.xxSph[2],rfm.xx[2])]])
    #for i in range(DIM):
    #    for j in range(DIM):
    #        AD[i] = drrefmetric__dx_0UDmatrix[(j,i)]*ASphD[j]
    #import WeylScal4NRPy.WeylScalars_Cartesian as weyl
    #LeviCivitaSymbolDDD = weyl.define_LeviCivitaSymbol_rank3()
    #LeviCivitaTensorUUU = ixp.zerorank3()
    #for i in range(DIM):
    #    for j in range(DIM):
    #        for k in range(DIM):
    #            LeviCivitaTensorUUU[i][j][k] = LeviCivitaSymbolDDD[i][j][k] / sp.sqrt(gammadet)
    ## For the initial data, we can analytically take the derivatives of A_i
    #ADdD = ixp.zerorank2()
    #for i in range(DIM):
    #    for j in range(DIM):
    #        ADdD[i][j] = sp.simplify(sp.diff(AD[i],rfm.xxCart[j]))
    #BU = ixp.zerorank1()
    #for i in range(DIM):
    #    for j in range(DIM):
    #        for k in range(DIM):
    #            BU[i] += LeviCivitaTensorUUU[i][j][k] * ADdD[k][j]
    ###########################################################
    ValenciavU = ixp.register_gridfunctions_for_single_rank1(
        "AUX", "ValenciavU"
    )  # Reset so that NRPy will access stored values of ValenciavU
    B2 = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            B2 += gammaDD[i][j] * BU[i] * BU[j]
    for i in range(DIM):
        for j in range(DIM):
            StildeD[i] += gammaDD[i][j] * (
                ValenciavU[j]) * sp.sqrt(gammadet) * B2 / 4 / M_PI
def compute_psi6Phi_rhs_flux_term_operand(gammaDD,sqrtgammaDET,betaU,alpha,AD,psi6Phi):
    gammaUU,_gammaDET = ixp.symm_matrix_inverter3x3(gammaDD) # _gammaDET unused.
    AU = ixp.zerorank1()
    # Raise the index on A in the usual way:
    for i in range(3):
        for j in range(3):
            AU[i] += gammaUU[i][j] * AD[j]

    global PhievolParenU
    PhievolParenU = ixp.zerorank1(DIM=3)

    for j in range(3):
        # \alpha\sqrt{\gamma}A^j - \beta^j [\sqrt{\gamma} \Phi]
        PhievolParenU[j] += alpha*sqrtgammaDET*AU[j] - betaU[j]*psi6Phi
def Enforce_Detgammabar_Constraint_symb_expressions():
    # Set spatial dimension (must be 3 for BSSN)
    DIM = 3

    # Then we set the coordinate system for the numerical grid
    rfm.reference_metric(
    )  # Create ReU, ReDD needed for rescaling B-L initial data, generating BSSN RHSs, etc.

    # We will need the h_{ij} quantities defined within BSSN_RHSs
    #    below when we enforce the gammahat=gammabar constraint
    # Step 1: All barred quantities are defined in terms of BSSN rescaled gridfunctions,
    #         which we declare here in case they haven't yet been declared elsewhere.

    Bq.declare_BSSN_gridfunctions_if_not_declared_already()
    hDD = Bq.hDD
    Bq.BSSN_basic_tensors()
    gammabarDD = Bq.gammabarDD

    # First define the Kronecker delta:
    KroneckerDeltaDD = ixp.zerorank2()
    for i in range(DIM):
        KroneckerDeltaDD[i][i] = sp.sympify(1)

    # The detgammabar in BSSN_RHSs is set to detgammahat when BSSN_RHSs::detgbarOverdetghat_equals_one=True (default),
    #    so we manually compute it here:
    dummygammabarUU, detgammabar = ixp.symm_matrix_inverter3x3(gammabarDD)

    # Next apply the constraint enforcement equation above.
    hprimeDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            hprimeDD[i][j] = \
                (nrpyAbs(rfm.detgammahat) / detgammabar) ** (sp.Rational(1, 3)) * (KroneckerDeltaDD[i][j] + hDD[i][j]) \
                - KroneckerDeltaDD[i][j]

    enforce_detg_constraint_symb_expressions = [
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD00"), rhs=hprimeDD[0][0]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD01"), rhs=hprimeDD[0][1]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD02"), rhs=hprimeDD[0][2]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD11"), rhs=hprimeDD[1][1]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD12"), rhs=hprimeDD[1][2]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD22"), rhs=hprimeDD[2][2])
    ]

    return enforce_detg_constraint_symb_expressions
def gammabarDD_hDD(gammaDD):
    global gammabarDD, hDD

    if gammaDD == None:
        gammaDD = ixp.declarerank2("gammaDD", "sym01")

    if rfm.have_already_called_reference_metric_function == False:
        print(
            "BSSN.BSSN_in_terms_of_ADM.hDD_given_ADM(): Must call reference_metric() first!"
        )
        sys.exit(1)
    # \bar{gamma}_{ij} = (\frac{\bar{gamma}}{gamma})^{1/3}*gamma_{ij}.
    gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)
    gammabarDD = ixp.zerorank2()
    hDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            gammabarDD[i][j] = (rfm.detgammahat / gammaDET)**(sp.Rational(
                1, 3)) * gammaDD[i][j]
            hDD[i][j] = (gammabarDD[i][j] - rfm.ghatDD[i][j]) / rfm.ReDD[i][j]
def four_metric_to_ADM(g4DD):
    # The ADM formulation decomposes Einstein's 4D equations into 3+1 dimensional form, with
    #     the 3 spatial dimensions separated from the 1 temporal dimension. DIM here refers to
    #     the spatial dimension.
    DIM = 3

    # Eq 4.47 in [Gourgoulhon](https://arxiv.org/pdf/gr-qc/0703035.pdf):
    # g_{ij} = \gamma_{ij}

    gammaDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            gammaDD[i][j] = g4DD[i + 1][j + 1]

    # Eq 4.47 in [Gourgoulhon](https://arxiv.org/pdf/gr-qc/0703035.pdf):
    # g_{ti} = \beta_i
    betaD = ixp.zerorank1()
    for i in range(DIM):
        betaD[i] = g4DD[i + 1][0]

    #Eq. 2.121 in B&S:
    # beta^i = gamma^{ij} beta_j
    betaU = ixp.zerorank1()
    gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)
    for i in range(DIM):
        for j in range(DIM):
            betaU[i] += gammaUU[i][j] * betaD[j]

    # Eq 4.47 in [Gourgoulhon](https://arxiv.org/pdf/gr-qc/0703035.pdf):
    # g_{tt} = -\alpha^2 + \beta^k \beta_k
    # -> alpha = sqrt(beta^2 - g_tt)
    # Now compute the beta contraction.
    beta2 = sp.sympify(0)
    for i in range(DIM):
        beta2 += betaU[i] * betaD[i]

    alpha = sp.sqrt(beta2 - g4DD[0][0])

    return gammaDD, betaU, alpha
def LambdabarU_lambdaU__exact_gammaDD(gammaDD):
    global LambdabarU, lambdaU

    if gammaDD is None:  # Use "is None" instead of "==None", as the former is more correct.
        gammaDD = ixp.declarerank2("gammaDD", "sym01")

    # \bar{Lambda}^i = \bar{gamma}^{jk}(\bar{Gamma}^i_{jk} - \hat{Gamma}^i_{jk}).
    gammabarDD_hDD(gammaDD)
    gammabarUU, _gammabarDET = ixp.symm_matrix_inverter3x3(
        gammabarDD)  # _gammabarDET unused.

    # First compute Christoffel symbols \bar{Gamma}^i_{jk}, with respect to barred metric:
    GammabarUDD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    GammabarUDD[i][j][k] += sp.Rational(
                        1, 2) * gammabarUU[i][l] * (
                            sp.diff(gammabarDD[l][j], rfm.xx[k]) +
                            sp.diff(gammabarDD[l][k], rfm.xx[j]) -
                            sp.diff(gammabarDD[j][k], rfm.xx[l]))
    # Next evaluate \bar{Lambda}^i, based on GammabarUDD above and GammahatUDD
    #       (from the reference metric):
    LambdabarU = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                LambdabarU[i] += gammabarUU[j][k] * (GammabarUDD[i][j][k] -
                                                     rfm.GammahatUDD[i][j][k])
    for i in range(DIM):
        # We evaluate LambdabarU[i] here to ensure proper cancellations. If these cancellations
        #   are not applied, certain expressions (e.g., lambdaU[0] in StaticTrumpet) will
        #   cause SymPy's (v1.5+) CSE algorithm to hang
        LambdabarU[i] = LambdabarU[i].doit()
    lambdaU = ixp.zerorank1()
    for i in range(DIM):
        lambdaU[i] = LambdabarU[i] / rfm.ReU[i]
Beispiel #14
0
def GiRaFFEfood_HO_ID_converter():
    gammaDD = ixp.register_gridfunctions_for_single_rank2("AUX",
                                                          "gammaDD",
                                                          "sym01",
                                                          DIM=3)
    gammaUU, gammadet = ixp.symm_matrix_inverter3x3(gammaDD)
    # Step 5: Build the expression for \tilde{S}_i
    global StildeD
    StildeD = ixp.zerorank1()
    BU = ixp.register_gridfunctions_for_single_rank1(
        "AUX", "BU")  # Reset so that NRPy will access stored values of BU
    ValenciavU = ixp.register_gridfunctions_for_single_rank1(
        "AUX", "ValenciavU"
    )  # Reset so that NRPy will access stored values of ValenciavU
    B2 = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            B2 += gammaDD[i][j] * BU[i] * BU[j]
    for i in range(DIM):
        StildeD[i] = 0
        for j in range(DIM):
            StildeD[i] += gammaDD[i][j] * (
                ValenciavU[j]) * sp.sqrt(gammadet) * B2 / 4 / M_PI
Beispiel #15
0
def ADM_in_terms_of_BSSN():
    global gammaDD, gammaDDdD, gammaDDdDD, gammaUU, detgamma, GammaUDD, KDD, KDDdD
    # Step 1.c: 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 1.d: 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.e: Import all basic (unrescaled) BSSN scalars & tensors
    import BSSN.BSSN_quantities as Bq
    Bq.BSSN_basic_tensors()
    gammabarDD = Bq.gammabarDD
    cf         = Bq.cf
    AbarDD     = Bq.AbarDD
    trK        = Bq.trK

    Bq.gammabar__inverse_and_derivs()
    gammabarDD_dD  = Bq.gammabarDD_dD
    gammabarDD_dDD = Bq.gammabarDD_dDD

    Bq.AbarUU_AbarUD_trAbar_AbarDD_dD()
    AbarDD_dD = Bq.AbarDD_dD

    # Step 2: The ADM three-metric gammaDD and its
    #         derivatives in terms of BSSN quantities.
    gammaDD = ixp.zerorank2()

    exp4phi = sp.sympify(0)
    if par.parval_from_str("EvolvedConformalFactor_cf") == "phi":
        exp4phi = sp.exp(4 * cf)
    elif par.parval_from_str("EvolvedConformalFactor_cf") == "chi":
        exp4phi = (1 / cf)
    elif par.parval_from_str("EvolvedConformalFactor_cf") == "W":
        exp4phi = (1 / cf ** 2)
    else:
        print("Error EvolvedConformalFactor_cf type = \"" + par.parval_from_str("EvolvedConformalFactor_cf") + "\" unknown.")
        sys.exit(1)

    for i in range(DIM):
        for j in range(DIM):
            gammaDD[i][j] = exp4phi * gammabarDD[i][j]

    # Step 2.a: Derivatives of $e^{4\phi}$
    phidD = ixp.zerorank1()
    phidDD = ixp.zerorank2()
    cf_dD  = ixp.declarerank1("cf_dD")
    cf_dDD = ixp.declarerank2("cf_dDD","sym01")
    if par.parval_from_str("EvolvedConformalFactor_cf") == "phi":
        for i in range(DIM):
            phidD[i]  = cf_dD[i]
            for j in range(DIM):
                phidDD[i][j] = cf_dDD[i][j]
    elif par.parval_from_str("EvolvedConformalFactor_cf") == "chi":
        for i in range(DIM):
            phidD[i]  = -sp.Rational(1,4)*exp4phi*cf_dD[i]
            for j in range(DIM):
                phidDD[i][j] = sp.Rational(1,4)*( exp4phi**2*cf_dD[i]*cf_dD[j] - exp4phi*cf_dDD[i][j] )
    elif par.parval_from_str("EvolvedConformalFactor_cf") == "W":
        exp2phi = (1 / cf)
        for i in range(DIM):
            phidD[i]  = -sp.Rational(1,2)*exp2phi*cf_dD[i]
            for j in range(DIM):
                phidDD[i][j] = sp.Rational(1,2)*( exp4phi*cf_dD[i]*cf_dD[j] - exp2phi*cf_dDD[i][j] )
    else:
        print("Error EvolvedConformalFactor_cf type = \""+par.parval_from_str("EvolvedConformalFactor_cf")+"\" unknown.")
        sys.exit(1)

    exp4phidD  = ixp.zerorank1()
    exp4phidDD = ixp.zerorank2()
    for i in range(DIM):
        exp4phidD[i] = 4*exp4phi*phidD[i]
        for j in range(DIM):
            exp4phidDD[i][j] = 16*exp4phi*phidD[i]*phidD[j] + 4*exp4phi*phidDD[i][j]

    # Step 2.b: Derivatives of gammaDD, the ADM three-metric
    gammaDDdD = ixp.zerorank3()
    gammaDDdDD = ixp.zerorank4()

    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                gammaDDdD[i][j][k] = exp4phidD[k] * gammabarDD[i][j] + exp4phi * gammabarDD_dD[i][j][k]
                for l in range(DIM):
                    gammaDDdDD[i][j][k][l] = exp4phidDD[k][l] * gammabarDD[i][j] + \
                                             exp4phidD[k] * gammabarDD_dD[i][j][l] + \
                                             exp4phidD[l] * gammabarDD_dD[i][j][k] + \
                                             exp4phi * gammabarDD_dDD[i][j][k][l]

    # Step 2.c: 3-Christoffel symbols associated with ADM 3-metric gammaDD
    # Step 2.c.i: First compute the inverse 3-metric gammaUU:
    gammaUU, detgamma = ixp.symm_matrix_inverter3x3(gammaDD)

    GammaUDD = ixp.zerorank3()

    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    GammaUDD[i][j][k] += sp.Rational(1,2)*gammaUU[i][l]* \
                                    (gammaDDdD[l][j][k] + gammaDDdD[l][k][j] - gammaDDdD[j][k][l])
                    
    # Step 3: Define ADM extrinsic curvature KDD and
    #         its first spatial derivatives KDDdD
    #         in terms of BSSN quantities
    KDD = ixp.zerorank2()

    for i in range(DIM):
        for j in range(DIM):
            KDD[i][j] = exp4phi * AbarDD[i][j] + sp.Rational(1, 3) * gammaDD[i][j] * trK

    KDDdD = ixp.zerorank3()
    trK_dD = ixp.declarerank1("trK_dD")
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                KDDdD[i][j][k] = exp4phidD[k] * AbarDD[i][j] + exp4phi * AbarDD_dD[i][j][k] + \
                                 sp.Rational(1, 3) * (gammaDDdD[i][j][k] * trK + gammaDD[i][j] * trK_dD[k])
def GiRaFFEfood_HO_Exact_Wald():

    # <a id='step2'></a>
    #
    # ### Step 2: Set the vectors A and E in Spherical coordinates
    # $$\label{step2}$$
    #
    # \[Back to [top](#top)\]
    #
    # We will first build the fundamental vectors $A_i$ and $E_i$ in spherical coordinates (see [Table 3](https://arxiv.org/pdf/1704.00599.pdf)). Note that we use reference_metric.py to set $r$ and $\theta$ in terms of Cartesian coordinates; this will save us a step later when we convert to Cartesian coordinates. Since $C_0 = 1$,
    # \begin{align}
    # A_{\phi} &= \frac{1}{2} r^2 \sin^2 \theta \\
    # E_{\phi} &= 2 M \left( 1+ \frac {2M}{r} \right)^{-1/2} \sin^2 \theta. \\
    # \end{align}
    # While we have $E_i$ set as a variable in NRPy+, note that the final C code won't store these values.

    # Step 2: Set the vectors A and E in Spherical coordinates

    r = rfm.xxSph[
        0] + KerrSchild_radial_shift  # We are setting the data up in Shifted Kerr-Schild coordinates
    theta = rfm.xxSph[1]

    IDchoice = par.parval_from_str("IDchoice")

    # Initialize all components of A and E in the *spherical basis* to zero
    ASphD = ixp.zerorank1()
    ESphD = ixp.zerorank1()
    if IDchoice is "Exact_Wald":
        ASphD[2] = (r * r * sp.sin(theta)**2) / 2
        ESphD[2] = 2 * M * sp.sin(theta)**2 / sp.sqrt(1 + 2 * M / r)
    else:
        print("Error: IDchoice == " + par.parval_from_str("IDchoice") +
              " unsupported!")
        exit(1)

    # <a id='step3'></a>
    #
    # ### Step 3: Use the Jacobian matrix to transform the vectors to Cartesian coordinates.
    # $$\label{step3}$$
    #
    # \[Back to [top](#top)\]
    #
    # Now, we will use the coordinate transformation definitions provided by reference_metric.py to build the Jacobian
    # $$
    # \frac{\partial x_{\rm Sph}^j}{\partial x_{\rm Cart}^i},
    # $$
    # where $x_{\rm Sph}^j \in \{r,\theta,\phi\}$ and $x_{\rm Cart}^i \in \{x,y,z\}$. We would normally compute its inverse, but since none of the quantities we need to transform have upper indices, it is not necessary. Then, since both $A_i$ and $E_i$ have one lower index, both will need to be multiplied by the Jacobian:
    #
    # $$
    # A_i^{\rm Cart} = A_j^{\rm Sph} \frac{\partial x_{\rm Sph}^j}{\partial x_{\rm Cart}^i},
    # $$

    # Step 3: Use the Jacobian matrix to transform the vectors to Cartesian coordinates.
    drrefmetric__dx_0UDmatrix = sp.Matrix([[
        sp.diff(rfm.xxSph[0], rfm.xx[0]),
        sp.diff(rfm.xxSph[0], rfm.xx[1]),
        sp.diff(rfm.xxSph[0], rfm.xx[2])
    ],
                                           [
                                               sp.diff(rfm.xxSph[1],
                                                       rfm.xx[0]),
                                               sp.diff(rfm.xxSph[1],
                                                       rfm.xx[1]),
                                               sp.diff(rfm.xxSph[1], rfm.xx[2])
                                           ],
                                           [
                                               sp.diff(rfm.xxSph[2],
                                                       rfm.xx[0]),
                                               sp.diff(rfm.xxSph[2],
                                                       rfm.xx[1]),
                                               sp.diff(rfm.xxSph[2], rfm.xx[2])
                                           ]])
    #dx__drrefmetric_0UDmatrix = drrefmetric__dx_0UDmatrix.inv() # We don't actually need this in this case.

    global AD
    AD = ixp.register_gridfunctions_for_single_rank1("EVOL", "AD")
    ED = ixp.zerorank1()

    for i in range(DIM):
        for j in range(DIM):
            AD[i] = drrefmetric__dx_0UDmatrix[(j, i)] * ASphD[j]
            ED[i] = drrefmetric__dx_0UDmatrix[(j, i)] * ESphD[j]

    #Step 4: Register the basic spacetime quantities
    alpha = gri.register_gridfunctions("AUX", "alpha")
    betaU = ixp.register_gridfunctions_for_single_rank1("AUX", "betaU", DIM=3)
    gammaDD = ixp.register_gridfunctions_for_single_rank2("AUX",
                                                          "gammaDD",
                                                          "sym01",
                                                          DIM=3)
    gammaUU, gammadet = ixp.symm_matrix_inverter3x3(gammaDD)

    # <a id='step4'></a>
    #
    # ### Step 4: Calculate $v^i$ from $A_i$ and $E_i$
    # $$\label{step4}$$
    #
    # \[Back to [top](#top)\]
    #
    # We will now find the magnetic field using equation 18 in [the original paper](https://arxiv.org/pdf/1704.00599.pdf) $$B^i = \frac{[ijk]}{\sqrt{\gamma}} \partial_j A_k. $$ We will need the metric quantites: the lapse $\alpha$, the shift $\beta^i$, and the three-metric $\gamma_{ij}$. We will also need the Levi-Civita symbol, provided by $\text{WeylScal4NRPy}$.

    # Step 4: Calculate v^i from A_i and E_i
    # Step 4a: Calculate the magnetic field B^i
    # Here, we build the Levi-Civita tensor from the Levi-Civita symbol.
    # Here, we build the Levi-Civita tensor from the Levi-Civita symbol.
    import WeylScal4NRPy.WeylScalars_Cartesian as weyl
    LeviCivitaSymbolDDD = weyl.define_LeviCivitaSymbol_rank3()
    LeviCivitaTensorUUU = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                LeviCivitaTensorUUU[i][j][
                    k] = LeviCivitaSymbolDDD[i][j][k] / sp.sqrt(gammadet)

    # For the initial data, we can analytically take the derivatives of A_i
    ADdD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            ADdD[i][j] = sp.simplify(sp.diff(AD[i], rfm.xxCart[j]))

    #global BU
    BU = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                BU[i] += LeviCivitaTensorUUU[i][j][k] * ADdD[k][j]

    # We will now build the initial velocity using equation 152 in [this paper,](https://arxiv.org/pdf/1310.3274v2.pdf) cited in the original $\texttt{GiRaFFE}$ code: $$ v^i = \alpha \frac{\epsilon^{ijk} E_j B_k}{B^2} -\beta^i. $$
    # However, our code needs the Valencia 3-velocity while this expression is for the drift velocity. So, we will need to transform it to the Valencia 3-velocity using the rule $\bar{v}^i = \frac{1}{\alpha} \left(v^i +\beta^i \right)$.
    # Thus, $$\bar{v}^i = \frac{\epsilon^{ijk} E_j B_k}{B^2}$$

    # Step 4b: Calculate B^2 and B_i
    # B^2 is an inner product defined in the usual way:
    B2 = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            B2 += gammaDD[i][j] * BU[i] * BU[j]

    # Lower the index on B^i
    BD = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            BD[i] += gammaDD[i][j] * BU[j]

    # Step 4c: Calculate the Valencia 3-velocity
    global ValenciavU
    ValenciavU = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                ValenciavU[
                    i] += LeviCivitaTensorUUU[i][j][k] * ED[j] * BD[k] / B2
Beispiel #17
0
def ref_metric__hatted_quantities():
    # Step 0: Set dimension DIM
    DIM = par.parval_from_str("grid::DIM")

    global ReU,ReDD,ghatDD,ghatUU,detgammahat
    ReU    = ixp.zerorank1()
    ReDD   = ixp.zerorank2()
    ghatDD = ixp.zerorank2()

    # Step 1: Compute ghatDD (reference metric), ghatUU
    #         (inverse reference metric), as well as 
    #         rescaling vector ReU & rescaling matrix ReDD
    for i in range(DIM):
        scalefactor_orthog[i] = sp.sympify(scalefactor_orthog[i])
        ghatDD[i][i] = scalefactor_orthog[i]**2
        ReU[i] = 1/scalefactor_orthog[i]
        for j in range(DIM):
            ReDD[i][j] = scalefactor_orthog[i]*scalefactor_orthog[j]
    # Step 1b: Compute ghatUU
    ghatUU, detgammahat = ixp.symm_matrix_inverter3x3(ghatDD)

    # Step 1c: Sanity check: verify that ReDD, ghatDD, 
    #          and ghatUU are all symmetric rank-2: 
    for i in range(DIM):
        for j in range(DIM):
            if ReDD[i][j] != ReDD[j][i]:
                print("Error: ReDD["+ str(i) + "][" + str(j) + "] != ReDD["+ str(j) + "][" + str(i) + ": " + str(ReDD[i][j]) + "!=" + str(ReDD[j][i]))
                exit(1)
            if ghatDD[i][j] != ghatDD[j][i]:
                print("Error: ghatDD["+ str(i) + "][" + str(j) + "] != ghatDD["+ str(j) + "][" + str(i) + ": " + str(ghatDD[i][j]) + "!=" + str(ghatDD[j][i]))
                exit(1)
            if ghatUU[i][j] != ghatUU[j][i]:
                print("Error: ghatUU["+ str(i) + "][" + str(j) + "] != ghatUU["+ str(j) + "][" + str(i) + ": " + str(ghatUU[i][j]) + "!=" + str(ghatUU[j][i]))
                exit(1)

    # Step 2: Compute det(ghat) and its 1st & 2nd derivatives
    global detgammahatdD,detgammahatdDD
    detgammahatdD  = ixp.zerorank1(DIM)
    detgammahatdDD = ixp.zerorank2(DIM)
    for i in range(DIM):
        detgammahatdD[i] = (sp.diff(detgammahat, xx[i]))
        for j in range(DIM):
            detgammahatdDD[i][j] = sp.diff(detgammahatdD[i], xx[j])

    # Step 3a: Compute 1st & 2nd derivatives of rescaling vector.
    #          (E.g., needed in BSSN for betaUdDD computation)
    global ReUdD,ReUdDD
    ReUdD  = ixp.zerorank2(DIM)
    ReUdDD = ixp.zerorank3(DIM)
    for i in range(DIM):
        for j in range(DIM):
            ReUdD[i][j] = sp.diff(ReU[i], xx[j])
            for k in range(DIM):
                ReUdDD[i][j][k] = sp.diff(ReUdD[i][j], xx[k])

    # Step 3b: Compute 1st & 2nd derivatives of rescaling matrix.
    global ReDDdD,ReDDdDD
    ReDDdD = ixp.zerorank3(DIM)
    ReDDdDD = ixp.zerorank4(DIM)
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                ReDDdD[i][j][k] = (sp.diff(ReDD[i][j],xx[k]))
                for l in range(DIM):
                    # Simplifying this doesn't appear to help overall NRPy run time.
                    ReDDdDD[i][j][k][l] = sp.diff(ReDDdD[i][j][k],xx[l])

    # Step 3c: Compute 1st & 2nd derivatives of reference metric.
    global ghatDDdD,ghatDDdDD
    ghatDDdD = ixp.zerorank3(DIM)
    ghatDDdDD = ixp.zerorank4(DIM)
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                ghatDDdD[i][j][k] = sp.simplify(sp.diff(ghatDD[i][j],xx[k])) # FIXME: BAD: MUST BE SIMPLIFIED OR ANSWER IS INCORRECT! Must be some bug in sympy...
                for l in range(DIM):
                    ghatDDdDD[i][j][k][l] = (sp.diff(ghatDDdD[i][j][k],xx[l]))

    # Step 4a: Compute Christoffel symbols of reference metric.
    global GammahatUDD
    GammahatUDD = ixp.zerorank3(DIM)
    for i in range(DIM):
        for k in range(DIM):
            for l in range(DIM):
                for m in range(DIM):
                    GammahatUDD[i][k][l] += (sp.Rational(1,2))*ghatUU[i][m]*\
                                            (ghatDDdD[m][k][l] + ghatDDdD[m][l][k] - ghatDDdD[k][l][m])

    # Step 4b: Compute derivs of Christoffel symbols of reference metric.
    global GammahatUDDdD
    GammahatUDDdD = ixp.zerorank4(DIM)
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    GammahatUDDdD[i][j][k][l] = (sp.diff(GammahatUDD[i][j][k],xx[l]))
Beispiel #18
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])
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]
Beispiel #20
0
def output_Enforce_Detgammabar_Constraint_Ccode():
    # Set spatial dimension (must be 3 for BSSN)
    DIM = 3

    # Then we set the coordinate system for the numerical grid
    rfm.reference_metric(
    )  # Create ReU, ReDD needed for rescaling B-L initial data, generating BSSN RHSs, etc.

    # We will need the h_{ij} quantities defined within BSSN_RHSs
    #    below when we enforce the gammahat=gammabar constraint
    # Step 1: All barred quantities are defined in terms of BSSN rescaled gridfunctions,
    #         which we declare here in case they haven't yet been declared elsewhere.

    Bq.declare_BSSN_gridfunctions_if_not_declared_already()
    hDD = Bq.hDD
    Bq.BSSN_basic_tensors()
    gammabarDD = Bq.gammabarDD

    # First define the Kronecker delta:
    KroneckerDeltaDD = ixp.zerorank2()
    for i in range(DIM):
        KroneckerDeltaDD[i][i] = sp.sympify(1)

    # The detgammabar in BSSN_RHSs is set to detgammahat when BSSN_RHSs::detgbarOverdetghat_equals_one=True (default),
    #    so we manually compute it here:
    dummygammabarUU, detgammabar = ixp.symm_matrix_inverter3x3(gammabarDD)

    # Next apply the constraint enforcement equation above.
    hprimeDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            hprimeDD[i][j] = \
                (sp.Abs(rfm.detgammahat) / detgammabar) ** (sp.Rational(1, 3)) * (KroneckerDeltaDD[i][j] + hDD[i][j]) \
                - KroneckerDeltaDD[i][j]

    enforce_detg_constraint_vars = [ \
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD00"), rhs=hprimeDD[0][0]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD01"), rhs=hprimeDD[0][1]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD02"), rhs=hprimeDD[0][2]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD11"), rhs=hprimeDD[1][1]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD12"), rhs=hprimeDD[1][2]),
        lhrh(lhs=gri.gfaccess("in_gfs", "hDD22"), rhs=hprimeDD[2][2])]

    enforce_gammadet_string = fin.FD_outputC(
        "returnstring",
        enforce_detg_constraint_vars,
        params="outCverbose=False,preindent=0,includebraces=False")

    with open("BSSN/enforce_detgammabar_constraint.h", "w") as file:
        indent = "   "
        file.write(
            "void enforce_detgammabar_constraint(const int Nxx_plus_2NGHOSTS[3],REAL *xx[3], REAL *in_gfs) {\n\n"
        )
        file.write(
            lp.loop(["i2", "i1", "i0"], ["0", "0", "0"], [
                "Nxx_plus_2NGHOSTS[2]", "Nxx_plus_2NGHOSTS[1]",
                "Nxx_plus_2NGHOSTS[0]"
            ], ["1", "1", "1"], [
                "#pragma omp parallel for", "    const REAL xx2 = xx[2][i2];",
                "        const REAL xx1 = xx[1][i1];"
            ], "", "const REAL xx0 = xx[0][i0];\n" + enforce_gammadet_string))
        file.write("}\n")

    print(
        "Output C implementation of det(gammabar) constraint to file BSSN/enforce_detgammabar_constraint.h"
    )
Beispiel #21
0
def FishboneMoncriefID():
    par.set_parval_from_str("reference_metric::CoordSystem","Cartesian")
    rfm.reference_metric()
    #Set the spatial dimension parameter to 3.
    par.set_parval_from_str("grid::DIM", 3)
    DIM = par.parval_from_str("grid::DIM")

    gPhys4UU = ixp.register_gridfunctions_for_single_rank2("AUX","gPhys4UU", "sym01", DIM=4)
    KDD = ixp.register_gridfunctions_for_single_rank2("EVOL","KDD", "sym01")

    # Variables needed for initial data given in spherical basis
    r, th, ph = gri.register_gridfunctions("AUX",["r","th","ph"])

    r_in,r_at_max_density,a,M = par.Cparameters("REAL",thismodule,["r_in","r_at_max_density","a","M"])

    kappa,gamma = par.Cparameters("REAL",thismodule,["kappa","gamma"])

    LorentzFactor = gri.register_gridfunctions("AUX","LorentzFactor")

    def calculate_l_at_r(r):
        l  = sp.sqrt(M/r**3) * (r**4 + r**2*a**2 - 2*M*r*a**2 - a*sp.sqrt(M*r)*(r**2-a**2))
        l /= r**2 - 3*M*r + 2*a*sp.sqrt(M*r)
        return l
    # First compute angular momentum at r_at_max_density, TAKING POSITIVE ROOT. This way disk is co-rotating with black hole
    # Eq 3.8:
    l = calculate_l_at_r(r_at_max_density)

    # Eq 3.6:
    # First compute the radially-independent part of the log of the enthalpy, ln_h_const
    Delta = r**2 - 2*M*r + a**2
    Sigma = r**2 + a**2*sp.cos(th)**2
    A = (r**2 + a**2)**2 - Delta*a**2*sp.sin(th)**2

    # Next compute the radially-dependent part of log(enthalpy), ln_h
    tmp3 = sp.sqrt(1 + 4*l**2*Sigma**2*Delta/(A*sp.sin(th))**2)
    # Term 1 of Eq 3.6
    ln_h  = sp.Rational(1,2)*sp.log( ( 1 + tmp3) / (Sigma*Delta/A))
    # Term 2 of Eq 3.6
    ln_h -= sp.Rational(1,2)*tmp3
    # Term 3 of Eq 3.6
    ln_h -= 2*a*M*r*l/A

    # Next compute the radially-INdependent part of log(enthalpy), ln_h
    # Note that there is some typo in the expression for these terms given in Eq 3.6, so we opt to just evaluate
    #   negative of the first three terms at r=r_in and th=pi/2 (the integration constant), as described in
    #   the text below Eq. 3.6, basically just copying the above lines of code.
    # Delin = Delta_in ; Sigin = Sigma_in ; Ain = A_in .
    Delin = r_in**2 - 2*M*r_in + a**2
    Sigin = r_in**2 + a**2*sp.cos(sp.pi/2)**2
    Ain   = (r_in**2 + a**2)**2 - Delin*a**2*sp.sin(sp.pi/2)**2

    tmp3in = sp.sqrt(1 + 4*l**2*Sigin**2*Delin/(Ain*sp.sin(sp.pi/2))**2)
    # Term 4 of Eq 3.6
    mln_h_in  = -sp.Rational(1,2)*sp.log( ( 1 + tmp3in) / (Sigin*Delin/Ain))
    # Term 5 of Eq 3.6
    mln_h_in += sp.Rational(1,2)*tmp3in
    # Term 6 of Eq 3.6
    mln_h_in += 2*a*M*r_in*l/Ain

    global hm1
    hm1 = sp.exp(ln_h + mln_h_in) - 1

    global rho_initial
    rho_initial,Pressure_initial = gri.register_gridfunctions("AUX",["rho_initial","Pressure_initial"])

    # Python 3.4 + sympy 1.0.0 has a serious problem taking the power here, hangs forever.
    # so instead we use the identity x^{1/y} = exp( [1/y] * log(x) )
    # Original expression (works with Python 2.7 + sympy 0.7.4.1):
    # rho_initial = ( hm1*(gamma-1)/(kappa*gamma) )**(1/(gamma - 1))
    # New expression (workaround):
    rho_initial = sp.exp( (1/(gamma-1)) * sp.log( hm1*(gamma-1)/(kappa*gamma) ))
    Pressure_initial = kappa * rho_initial**gamma
    # Eq 3.3: First compute exp(-2 chi), assuming Boyer-Lindquist coordinates
    #    Eq 2.16: chi = psi - nu, so
    #    Eq 3.5 -> exp(-2 chi) = exp(-2 (psi - nu)) = exp(2 nu)/exp(2 psi)
    exp2nu  = Sigma*Delta / A
    exp2psi = A*sp.sin(th)**2 / Sigma
    expm2chi = exp2nu / exp2psi

    # Eq 3.3: Next compute u_(phi).
    u_pphip = sp.sqrt((-1 + sp.sqrt(1 + 4*l**2*expm2chi))/2)
    # Eq 2.13: Compute u_(t)
    u_ptp   = -sp.sqrt(1 + u_pphip**2)

    # Next compute spatial components of 4-velocity in Boyer-Lindquist coordinates:
    uBL4D = ixp.zerorank1(DIM=4) # Components 1 and 2: u_r = u_theta = 0
    # Eq 2.12 (typo): u_(phi) = e^(-psi) u_phi -> u_phi = e^(psi) u_(phi)
    uBL4D[3] = sp.sqrt(exp2psi)*u_pphip

    # Assumes Boyer-Lindquist coordinates:
    omega = 2*a*M*r/A
    # Eq 2.13: u_(t) = 1/sqrt(exp2nu) * ( u_t + omega*u_phi )
    #     -->  u_t = u_(t) * sqrt(exp2nu) - omega*u_phi
    #     -->  u_t = u_ptp * sqrt(exp2nu) - omega*uBL4D[3]
    uBL4D[0] = u_ptp*sp.sqrt(exp2nu) - omega*uBL4D[3]
    # Eq. 3.5:
    # w = 2*a*M*r/A;
    # Eqs. 3.5 & 2.1:
    # gtt = -Sig*Del/A + w^2*Sin[th]^2*A/Sig;
    # gtp = w*Sin[th]^2*A/Sig;
    # gpp = Sin[th]^2*A/Sig;
    # FullSimplify[Inverse[{{gtt,gtp},{gtp,gpp}}]]
    gPhys4BLUU = ixp.zerorank2(DIM=4)
    gPhys4BLUU[0][0] = -A/(Delta*Sigma)
    # DO NOT NEED TO SET gPhys4BLUU[1][1] or gPhys4BLUU[2][2]!
    gPhys4BLUU[0][3] = gPhys4BLUU[3][0] = -2*a*M*r/(Delta*Sigma)
    gPhys4BLUU[3][3] = -4*a**2*M**2*r**2/(Delta*A*Sigma) + Sigma**2/(A*Sigma*sp.sin(th)**2)

    uBL4U = ixp.zerorank1(DIM=4)
    for i in range(4):
        for j in range(4):
            uBL4U[i] += gPhys4BLUU[i][j]*uBL4D[j]

    # https://github.com/atchekho/harmpi/blob/master/init.c
    # Next transform Boyer-Lindquist velocity to Kerr-Schild basis:
    transformBLtoKS = ixp.zerorank2(DIM=4)
    for i in range(4):
        transformBLtoKS[i][i] = 1
    transformBLtoKS[0][1] = 2*r/(r**2 - 2*r + a*a)
    transformBLtoKS[3][1] =   a/(r**2 - 2*r + a*a)
    uKS4U = ixp.zerorank1(DIM=4)
    for i in range(4):
        for j in range(4):
            uKS4U[i] += transformBLtoKS[i][j]*uBL4U[j]

    # Adopt the Kerr-Schild metric for Fishbone-Moncrief disks
    # http://gravity.psu.edu/numrel/jclub/jc/Cook___LivRev_2000-5.pdf
    # Alternatively, Appendix of https://arxiv.org/pdf/1704.00599.pdf
    rhoKS2  = r**2 + a**2*sp.cos(th)**2 # Eq 79 of Cook's Living Review article
    DeltaKS = r**2 - 2*M*r + a**2    # Eq 79 of Cook's Living Review article
    alphaKS = 1/sp.sqrt(1 + 2*M*r/rhoKS2)
    betaKSU = ixp.zerorank1()
    betaKSU[0] = alphaKS**2*2*M*r/rhoKS2
    gammaKSDD = ixp.zerorank2()
    gammaKSDD[0][0] = 1 + 2*M*r/rhoKS2
    gammaKSDD[0][2] = gammaKSDD[2][0] = -(1 + 2*M*r/rhoKS2)*a*sp.sin(th)**2
    gammaKSDD[1][1] = rhoKS2
    gammaKSDD[2][2] = (r**2 + a**2 + 2*M*r/rhoKS2 * a**2*sp.sin(th)**2) * sp.sin(th)**2

    AA = a**2 * sp.cos(2*th) + a**2 + 2*r**2
    BB = AA + 4*M*r
    DD = sp.sqrt(2*M*r / (a**2 * sp.cos(th)**2 + r**2) + 1)
    KDD[0][0] = DD*(AA + 2*M*r)/(AA**2*BB) * (4*M*(a**2 * sp.cos(2*th) + a**2 - 2*r**2))
    KDD[0][1] = KDD[1][0] = DD/(AA*BB) * 8*a**2*M*r*sp.sin(th)*sp.cos(th)
    KDD[0][2] = KDD[2][0] = DD/AA**2 * (-2*a*M*sp.sin(th)**2 * (a**2 * sp.cos(2*th) + a**2 - 2*r**2))
    KDD[1][1] = DD/BB * 4*M*r**2
    KDD[1][2] = KDD[2][1] = DD/(AA*BB) * (-8*a**3*M*r*sp.sin(th)**3*sp.cos(th))
    KDD[2][2] = DD/(AA**2*BB) * \
                (2*M*r*sp.sin(th)**2 * (a**4*(r-M)*sp.cos(4*th) + a**4*(M+3*r) +
                 4*a**2*r**2*(2*r-M) + 4*a**2*r*sp.cos(2*th)*(a**2 + r*(M+2*r)) + 8*r**5))

    # For compatibility, we must compute gPhys4UU
    gammaKSUU,gammaKSDET = ixp.symm_matrix_inverter3x3(gammaKSDD)
    # See, e.g., Eq. 4.49 of https://arxiv.org/pdf/gr-qc/0703035.pdf , where N = alpha
    gPhys4UU[0][0] = -1 / alphaKS**2
    for i in range(1,4):
        if i>0:
            # if the quantity does not have a "4", then it is assumed to be a 3D quantity.
            #  E.g., betaKSU[] is a spatial vector, with indices ranging from 0 to 2:
            gPhys4UU[0][i] = gPhys4UU[i][0] = betaKSU[i-1]/alphaKS**2
    for i in range(1,4):
        for j in range(1,4):
            # if the quantity does not have a "4", then it is assumed to be a 3D quantity.
            #  E.g., betaKSU[] is a spatial vector, with indices ranging from 0 to 2,
            #    and gammaKSUU[][] is a spatial tensor, with indices again ranging from 0 to 2.
            gPhys4UU[i][j] = gPhys4UU[j][i] = gammaKSUU[i-1][j-1] - betaKSU[i-1]*betaKSU[j-1]/alphaKS**2

    A_b = par.Cparameters("REAL",thismodule,"A_b")

    A_3vecpotentialD = ixp.zerorank1()
    # Set A_phi = A_b*rho_initial FIXME: why is there a sign error?
    A_3vecpotentialD[2] = -A_b * rho_initial

    BtildeU = ixp.register_gridfunctions_for_single_rank1("EVOL","BtildeU")
    # Eq 15 of https://arxiv.org/pdf/1501.07276.pdf:
    # B = curl A -> B^r = d_th A_ph - d_ph A_th
    BtildeU[0] = sp.diff(A_3vecpotentialD[2],th) - sp.diff(A_3vecpotentialD[1],ph)
    # B = curl A -> B^th = d_ph A_r - d_r A_ph
    BtildeU[1] = sp.diff(A_3vecpotentialD[0],ph) - sp.diff(A_3vecpotentialD[2],r)
    # B = curl A -> B^ph = d_r A_th - d_th A_r
    BtildeU[2] = sp.diff(A_3vecpotentialD[1],r)  - sp.diff(A_3vecpotentialD[0],th)

    # Construct spacetime metric in 3+1 form:
    # See, e.g., Eq. 4.49 of https://arxiv.org/pdf/gr-qc/0703035.pdf , where N = alpha
    alpha = gri.register_gridfunctions("EVOL",["alpha"])
    betaU = ixp.register_gridfunctions_for_single_rank1("EVOL","betaU")

    alpha = sp.sqrt(1/(-gPhys4UU[0][0]))
    betaU = ixp.zerorank1()
    for i in range(3):
        betaU[i] = alpha**2 * gPhys4UU[0][i+1]
    gammaUU = ixp.zerorank2()
    for i in range(3):
        for j in range(3):
            gammaUU[i][j] = gPhys4UU[i+1][j+1] + betaU[i]*betaU[j]/alpha**2

    gammaDD = ixp.register_gridfunctions_for_single_rank2("EVOL","gammaDD","sym01")
    gammaDD,igammaDET = ixp.symm_matrix_inverter3x3(gammaUU)
    gammaDET = 1/igammaDET

    ###############
    # Next compute g_{\alpha \beta} from lower 3-metric, using
    # Eq 4.47 of https://arxiv.org/pdf/gr-qc/0703035.pdf
    betaD = ixp.zerorank1()
    for i in range(3):
        for j in range(3):
            betaD[i] += gammaDD[i][j]*betaU[j]

    beta2 = sp.sympify(0)
    for i in range(3):
        beta2 += betaU[i]*betaD[i]

    gPhys4DD = ixp.zerorank2(DIM=4)
    gPhys4DD[0][0] = -alpha**2 + beta2
    for i in range(3):
        gPhys4DD[0][i+1] = gPhys4DD[i+1][0] = betaD[i]
        for j in range(3):
            gPhys4DD[i+1][j+1] = gammaDD[i][j]

    ###############
    # Next compute b^{\mu} using Eqs 23 and 31 of https://arxiv.org/pdf/astro-ph/0503420.pdf
    uKS4D = ixp.zerorank1(DIM=4)
    for i in range(4):
        for j in range(4):
            uKS4D[i] += gPhys4DD[i][j] * uKS4U[j]

    # Eq 27 of https://arxiv.org/pdf/astro-ph/0503420.pdf
    BU = ixp.zerorank1()
    for i in range(3):
        BU[i] = BtildeU[i]/sp.sqrt(gammaDET)

    # Eq 23 of https://arxiv.org/pdf/astro-ph/0503420.pdf
    BU0_u = sp.sympify(0)
    for i in range(3):
        BU0_u += uKS4D[i+1]*BU[i]/alpha

    smallbU = ixp.zerorank1(DIM=4)
    smallbU[0] = BU0_u   / sp.sqrt(4 * sp.pi)
    # Eqs 24 and 31 of https://arxiv.org/pdf/astro-ph/0503420.pdf
    for i in range(3):
        smallbU[i+1] = (BU[i]/alpha + BU0_u*uKS4U[i+1])/(sp.sqrt(4*sp.pi)*uKS4U[0])

    smallbD = ixp.zerorank1(DIM=4)
    for i in range(4):
        for j in range(4):
            smallbD[i] += gPhys4DD[i][j]*smallbU[j]

    smallb2 = sp.sympify(0)
    for i in range(4):
        smallb2 += smallbU[i]*smallbD[i]

    ###############
    LorentzFactor = alpha * uKS4U[0]
    # Define Valencia 3-velocity v^i_(n), which sets the 3-velocity as measured by normal observers to the spatial slice:
    #  v^i_(n) = u^i/(u^0*alpha) + beta^i/alpha. See eq 11 of https://arxiv.org/pdf/1501.07276.pdf
    Valencia3velocityU = ixp.zerorank1()
    for i in range(3):
        Valencia3velocityU[i] = uKS4U[i + 1] / (alpha * uKS4U[0]) + betaU[i]

    sqrtgamma4DET = sp.symbols("sqrtgamma4DET")
    sqrtgamma4DET = sp.sqrt(gammaDET)*alpha

    alpha = alpha.subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])
    for i in range(DIM):
        betaU[i] = betaU[i].subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])
        for j in range(DIM):
            gammaDD[i][j] = gammaDD[i][j].subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])
            KDD[i][j]     = KDD[i][j].subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])

    # GRMHD variables:
    # Density and pressure:
    hm1           = hm1.subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])
    rho_initial          = rho_initial.subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])
    Pressure_initial     = Pressure_initial.subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])
    LorentzFactor = LorentzFactor.subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])

    # "Valencia" three-velocity
    for i in range(DIM):
        BtildeU[i] = BtildeU[i].subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])
        uKS4U[i+1] = uKS4U[i+1].subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])
        Valencia3velocityU[i] = Valencia3velocityU[i].subs(r,rfm.xxSph[0]).subs(th,rfm.xxSph[1]).subs(ph,rfm.xxSph[2])

    # Transform initial data to our coordinate system:
    # First compute Jacobian and its inverse
    drrefmetric__dx_0UDmatrix = sp.Matrix([[sp.diff( rfm.xxSph[0],rfm.xx[0]), sp.diff( rfm.xxSph[0],rfm.xx[1]), sp.diff( rfm.xxSph[0],rfm.xx[2])],
                                           [sp.diff(rfm.xxSph[1],rfm.xx[0]), sp.diff(rfm.xxSph[1],rfm.xx[1]), sp.diff(rfm.xxSph[1],rfm.xx[2])],
                                           [sp.diff(rfm.xxSph[2],rfm.xx[0]), sp.diff(rfm.xxSph[2],rfm.xx[1]), sp.diff(rfm.xxSph[2],rfm.xx[2])]])
    dx__drrefmetric_0UDmatrix = drrefmetric__dx_0UDmatrix.inv()

    # Declare as gridfunctions the final quantities we will output for the initial data
    global IDalpha,IDgammaDD,IDKDD,IDbetaU,IDValencia3velocityU
    IDalpha = gri.register_gridfunctions("EVOL","IDalpha")
    IDgammaDD = ixp.register_gridfunctions_for_single_rank2("EVOL","IDgammaDD","sym01")
    IDKDD = ixp.register_gridfunctions_for_single_rank2("EVOL","IDKDD","sym01")
    IDbetaU   = ixp.register_gridfunctions_for_single_rank1("EVOL","IDbetaU")
    IDValencia3velocityU = ixp.register_gridfunctions_for_single_rank1("EVOL","IDValencia3velocityU")

    IDalpha = alpha
    for i in range(3):
        IDbetaU[i] = 0
        IDValencia3velocityU[i] = 0
        for j in range(3):
            # Matrices are stored in row, column format, so (i,j) <-> (row,column)
            IDbetaU[i]   += dx__drrefmetric_0UDmatrix[(i,j)]*betaU[j]
            IDValencia3velocityU[i]   += dx__drrefmetric_0UDmatrix[(i,j)]*Valencia3velocityU[j]
            IDgammaDD[i][j] = 0
            IDKDD[i][j] = 0
            for k in range(3):
                for l in range(3):
                    IDgammaDD[i][j] += drrefmetric__dx_0UDmatrix[(k,i)]*drrefmetric__dx_0UDmatrix[(l,j)]*gammaDD[k][l]
                    IDKDD[i][j]     += drrefmetric__dx_0UDmatrix[(k,i)]*drrefmetric__dx_0UDmatrix[(l,j)]*    KDD[k][l]
Beispiel #22
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]
Beispiel #23
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]
def Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear(
        CoordType_in, Sph_r_th_ph_or_Cart_xyz, gammaDD_inSphorCart,
        KDD_inSphorCart, alpha_inSphorCart, betaU_inSphorCart, BU_inSphorCart):
    # This routine converts the ADM variables
    #    $$\left\{\gamma_{ij}, K_{ij}, \alpha, \beta^i\right\}$$
    #    in Spherical or Cartesian basis+coordinates, first to the BSSN variables
    #    in the chosen reference_metric::CoordSystem coordinate system+basis:
    # $$\left\{\bar{\gamma}_{i j},\bar{A}_{i j},\phi, K, \bar{\Lambda}^{i}, \alpha, \beta^i, B^i\right\},$$
    #    and then to the rescaled variables:
    # $$\left\{h_{i j},a_{i j},\phi, K, \lambda^{i}, \alpha, \mathcal{V}^i, \mathcal{B}^i\right\}.$$

    # The ADM & BSSN formalisms only work in 3D; they are 3+1 decompositions of Einstein's equations.
    #    To implement axisymmetry or spherical symmetry, simply set all spatial derivatives in
    #    the relevant angular directions to zero; DO NOT SET DIM TO ANYTHING BUT 3.
    # Step 0: Set spatial dimension (must be 3 for BSSN)
    DIM = 3

    # Step 0: Copy gammaSphDD_in to gammaSphDD, KSphDD_in to KSphDD, etc.
    #    This ensures that the input arrays are not modified below;
    #    modifying them would result in unexpected effects outside
    #    this function.
    alphaSphorCart = alpha_inSphorCart
    betaSphorCartU = ixp.zerorank1()
    BSphorCartU = ixp.zerorank1()
    gammaSphorCartDD = ixp.zerorank2()
    KSphorCartDD = ixp.zerorank2()
    for i in range(DIM):
        betaSphorCartU[i] = betaU_inSphorCart[i]
        BSphorCartU[i] = BU_inSphorCart[i]
        for j in range(DIM):
            gammaSphorCartDD[i][j] = gammaDD_inSphorCart[i][j]
            KSphorCartDD[i][j] = KDD_inSphorCart[i][j]

    # Make sure that rfm.reference_metric() has been called.
    #    We'll need the variables it defines throughout this module.
    if rfm.have_already_called_reference_metric_function == False:
        print(
            "Error. Called Convert_Spherical_ADM_to_BSSN_curvilinear() without"
        )
        print(
            "       first setting up reference metric, by calling rfm.reference_metric()."
        )
        exit(1)

    # Step 1: All input quantities are in terms of r,th,ph or x,y,z. We want them in terms
    #         of xx0,xx1,xx2, so here we call sympify_integers__replace_rthph() to replace
    #         r,th,ph or x,y,z, respectively, with the appropriate functions of xx0,xx1,xx2
    #         as defined for this particular reference metric in reference_metric.py's
    #         xxSph[] or xxCart[], respectively:
    # Note that substitution only works when the variable is not an integer. Hence the
    #         if isinstance(...,...) stuff:
    def sympify_integers__replace_rthph_or_Cartxyz(obj, rthph_or_xyz,
                                                   rthph_or_xyz_of_xx):
        if isinstance(obj, int):
            return sp.sympify(obj)
        else:
            return obj.subs(rthph_or_xyz[0], rthph_or_xyz_of_xx[0]).\
                subs(rthph_or_xyz[1], rthph_or_xyz_of_xx[1]).\
                subs(rthph_or_xyz[2], rthph_or_xyz_of_xx[2])

    r_th_ph_or_Cart_xyz_of_xx = []
    if CoordType_in == "Spherical":
        r_th_ph_or_Cart_xyz_of_xx = rfm.xxSph
    elif CoordType_in == "Cartesian":
        r_th_ph_or_Cart_xyz_of_xx = rfm.xxCart
    else:
        print(
            "Error: Can only convert ADM Cartesian or Spherical initial data to BSSN Curvilinear coords."
        )
        exit(1)

    alphaSphorCart = sympify_integers__replace_rthph_or_Cartxyz(
        alphaSphorCart, Sph_r_th_ph_or_Cart_xyz, r_th_ph_or_Cart_xyz_of_xx)
    for i in range(DIM):
        betaSphorCartU[i] = sympify_integers__replace_rthph_or_Cartxyz(
            betaSphorCartU[i], Sph_r_th_ph_or_Cart_xyz,
            r_th_ph_or_Cart_xyz_of_xx)
        BSphorCartU[i] = sympify_integers__replace_rthph_or_Cartxyz(
            BSphorCartU[i], Sph_r_th_ph_or_Cart_xyz, r_th_ph_or_Cart_xyz_of_xx)
        for j in range(DIM):
            gammaSphorCartDD[i][
                j] = sympify_integers__replace_rthph_or_Cartxyz(
                    gammaSphorCartDD[i][j], Sph_r_th_ph_or_Cart_xyz,
                    r_th_ph_or_Cart_xyz_of_xx)
            KSphorCartDD[i][j] = sympify_integers__replace_rthph_or_Cartxyz(
                KSphorCartDD[i][j], Sph_r_th_ph_or_Cart_xyz,
                r_th_ph_or_Cart_xyz_of_xx)

    # Step 2: All ADM initial data quantities are now functions of xx0,xx1,xx2, but
    #         they are still in the Spherical or Cartesian basis. We can now directly apply
    #         Jacobian transformations to get them in the correct xx0,xx1,xx2 basis:

    # alpha is a scalar, so no Jacobian transformation is necessary.
    alpha = alphaSphorCart

    Jac_dUSphorCart_dDrfmUD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            Jac_dUSphorCart_dDrfmUD[i][j] = sp.diff(
                r_th_ph_or_Cart_xyz_of_xx[i], rfm.xx[j])

    Jac_dUrfm_dDSphorCartUD, dummyDET = ixp.generic_matrix_inverter3x3(
        Jac_dUSphorCart_dDrfmUD)

    betaU = ixp.zerorank1()
    BU = ixp.zerorank1()
    gammaDD = ixp.zerorank2()
    KDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            betaU[i] += Jac_dUrfm_dDSphorCartUD[i][j] * betaSphorCartU[j]
            BU[i] += Jac_dUrfm_dDSphorCartUD[i][j] * BSphorCartU[j]
            for k in range(DIM):
                for l in range(DIM):
                    gammaDD[i][j] += Jac_dUSphorCart_dDrfmUD[k][
                        i] * Jac_dUSphorCart_dDrfmUD[l][j] * gammaSphorCartDD[
                            k][l]
                    KDD[i][j] += Jac_dUSphorCart_dDrfmUD[k][
                        i] * Jac_dUSphorCart_dDrfmUD[l][j] * KSphorCartDD[k][l]

    # Step 3: All ADM quantities were input into this function in the Spherical or Cartesian
    #         basis, as functions of r,th,ph or x,y,z, respectively. In Steps 1 and 2 above,
    #         we converted them to the xx0,xx1,xx2 basis, and as functions of xx0,xx1,xx2.
    #         Here we convert ADM quantities to their BSSN Curvilinear counterparts:

    # Step 3.1: Convert ADM $\gamma_{ij}$ to BSSN $\bar{\gamma}_{ij}$:
    #   We have (Eqs. 2 and 3 of [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf)):
    # \bar{\gamma}_{i j} = \left(\frac{\bar{\gamma}}{\gamma}\right)^{1/3} \gamma_{ij}.
    gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD)
    gammabarDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            gammabarDD[i][j] = (rfm.detgammahat / gammaDET)**(sp.Rational(
                1, 3)) * gammaDD[i][j]

    # Step 3.2: Convert the extrinsic curvature $K_{ij}$ to the trace-free extrinsic
    #           curvature $\bar{A}_{ij}$, plus the trace of the extrinsic curvature $K$,
    #           where (Eq. 3 of [Baumgarte *et al.*](https://arxiv.org/pdf/1211.6632.pdf)):

    # K = \gamma^{ij} K_{ij}, and
    # \bar{A}_{ij} &= \left(\frac{\bar{\gamma}}{\gamma}\right)^{1/3} \left(K_{ij} - \frac{1}{3} \gamma_{ij} K \right)
    trK = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            trK += gammaUU[i][j] * KDD[i][j]

    AbarDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            AbarDD[i][j] = (rfm.detgammahat / gammaDET)**(sp.Rational(
                1, 3)) * (KDD[i][j] - sp.Rational(1, 3) * gammaDD[i][j] * trK)

    # Step 3.3: Define $\bar{\Lambda}^i$ (Eqs. 4 and 5 of [Baumgarte *et al.*](https://arxiv.org/pdf/1211.6632.pdf)):

    # \bar{\Lambda}^i = \bar{\gamma}^{jk}\left(\bar{\Gamma}^i_{jk} - \hat{\Gamma}^i_{jk}\right).
    gammabarUU, gammabarDET = ixp.symm_matrix_inverter3x3(gammabarDD)

    # First compute \bar{\Gamma}^i_{jk}:
    GammabarUDD = ixp.zerorank3()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    GammabarUDD[i][j][k] += sp.Rational(
                        1, 2) * gammabarUU[i][l] * (
                            sp.diff(gammabarDD[l][j], rfm.xx[k]) +
                            sp.diff(gammabarDD[l][k], rfm.xx[j]) -
                            sp.diff(gammabarDD[j][k], rfm.xx[l]))
    # Next evaluate \bar{\Lambda}^i, based on GammabarUDD above and GammahatUDD
    #       (from the reference metric):
    LambdabarU = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                LambdabarU[i] += gammabarUU[j][k] * (GammabarUDD[i][j][k] -
                                                     rfm.GammahatUDD[i][j][k])

    # Step 3.4: Set the conformal factor variable $\texttt{cf}$, which is set
    #           by the "BSSN_quantities::ConformalFactor" parameter. For example if
    #           "ConformalFactor" is set to "phi", we can use Eq. 3 of
    #           [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf),
    #           which in arbitrary coordinates is written:

    # \phi = \frac{1}{12} \log\left(\frac{\gamma}{\bar{\gamma}}\right).

    # Alternatively if "BSSN_quantities::ConformalFactor" is set to "chi", then

    # \chi = e^{-4 \phi} = \exp\left(-4 \frac{1}{12} \left(\frac{\gamma}{\bar{\gamma}}\right)\right)
    #      = \exp\left(-\frac{1}{3} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) = \left(\frac{\gamma}{\bar{\gamma}}\right)^{-1/3}.
    #
    # Finally if "BSSN_quantities::ConformalFactor" is set to "W", then

    # W = e^{-2 \phi} = \exp\left(-2 \frac{1}{12} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) =
    # \exp\left(-\frac{1}{6} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) =
    # \left(\frac{\gamma}{\bar{\gamma}}\right)^{-1/6}.

    cf = sp.sympify(0)

    if par.parval_from_str("ConformalFactor") == "phi":
        cf = sp.Rational(1, 12) * sp.log(gammaDET / gammabarDET)
    elif par.parval_from_str("ConformalFactor") == "chi":
        cf = (gammaDET / gammabarDET)**(-sp.Rational(1, 3))
    elif par.parval_from_str("ConformalFactor") == "W":
        cf = (gammaDET / gammabarDET)**(-sp.Rational(1, 6))
    else:
        print("Error ConformalFactor type = \"" +
              par.parval_from_str("ConformalFactor") + "\" unknown.")
        exit(1)

    # Step 4: Rescale tensorial quantities according to the prescription described in
    #         the [BSSN in curvilinear coordinates tutorial module](Tutorial-BSSNCurvilinear.ipynb)
    #         (also [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf)):
    #
    # h_{ij} &= (\bar{\gamma}_{ij} - \hat{\gamma}_{ij})/\text{ReDD[i][j]}\\
    # a_{ij} &= \bar{A}_{ij}/\text{ReDD[i][j]}\\
    # \lambda^i &= \bar{\Lambda}^i/\text{ReU[i]}\\
    # \mathcal{V}^i &= \beta^i/\text{ReU[i]}\\
    # \mathcal{B}^i &= B^i/\text{ReU[i]}\\
    hDD = ixp.zerorank2()
    aDD = ixp.zerorank2()
    lambdaU = ixp.zerorank1()
    vetU = ixp.zerorank1()
    betU = ixp.zerorank1()
    for i in range(DIM):
        lambdaU[i] = LambdabarU[i] / rfm.ReU[i]
        vetU[i] = betaU[i] / rfm.ReU[i]
        betU[i] = BU[i] / rfm.ReU[i]
        for j in range(DIM):
            hDD[i][j] = (gammabarDD[i][j] - rfm.ghatDD[i][j]) / rfm.ReDD[i][j]
            aDD[i][j] = AbarDD[i][j] / rfm.ReDD[i][j]
    #print(sp.mathematica_code(hDD[0][0]))

    # Step 5: Return the BSSN Curvilinear variables in the desired xx0,xx1,xx2
    #         basis, and as functions of the consistent xx0,xx1,xx2 coordinates.
    return cf, hDD, lambdaU, aDD, trK, alpha, vetU, betU
Beispiel #25
0
def Psi4_tetradsv2():
    global l4U, n4U, mre4U, mim4U

    # Step 1.c: Check if tetrad choice is implemented:
    if par.parval_from_str(thismodule + "::TetradChoice") != "QuasiKinnersley":
        print("ERROR: " + thismodule + "::TetradChoice = " +
              par.parval_from_str("TetradChoice") + " currently unsupported!")
        exit(1)

    # Step 1.d: 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 1.e: 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.f: Import all ADM quantities as written in terms of BSSN quantities
    import BSSN.ADM_in_terms_of_BSSN as AB
    AB.ADM_in_terms_of_BSSN()

    # Step 2.a: Declare the Cartesian x,y,z as input parameters
    #           and v_1^a, v_2^a, and v_3^a tetrads,
    #           as well as detgamma and gammaUU from
    #           BSSN.ADM_in_terms_of_BSSN
    x, y, z = par.Cparameters("REAL", thismodule, ["x", "y", "z"])

    v1UCart = ixp.zerorank1()
    v2UCart = ixp.zerorank1()

    # detgamma = AB.detgamma
    # gammaUU  = AB.gammaUU

    # Step 2.b: Define v1U and v2U
    v1UCart = [-y, x, sp.sympify(0)]
    v2UCart = [x, y, z]

    # Step 2.c: Construct the Jacobian d x_Cart^i / d xx^j
    Jac_dUCart_dDrfmUD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            Jac_dUCart_dDrfmUD[i][j] = sp.diff(rfm.xxCart[i], rfm.xx[j])

    # Step 2.d: Invert above Jacobian to get needed d xx^j / d x_Cart^i
    Jac_dUrfm_dDCartUD, dummyDET = ixp.generic_matrix_inverter3x3(
        Jac_dUCart_dDrfmUD)

    # Step 2.e: Transform gammaDD to Cartesian basis:
    gammaCartDD = ixp.zerorank2()
    gammaDD = AB.gammaDD
    for i in range(DIM):
        for j in range(DIM):
            for k in range(DIM):
                for l in range(DIM):
                    gammaCartDD[i][j] += Jac_dUrfm_dDCartUD[k][
                        i] * Jac_dUrfm_dDCartUD[l][j] * gammaDD[k][l]

    gammaCartUU, detgammaCart = ixp.symm_matrix_inverter3x3(gammaCartDD)

    # Step 2.e: Transform v1U and v2U from the Cartesian to the xx^i basis
    v1U = ixp.zerorank1()
    v2U = ixp.zerorank1()
    for i in range(DIM):
        v1U[i] = v1UCart[i]
        v2U[i] = v2UCart[i]
    # for i in range(DIM):
    #     for j in range(DIM):
    #         v1U[i] += Jac_dUrfm_dDCartUD[i][j]*v1UCart[j]
    #         v2U[i] += Jac_dUrfm_dDCartUD[i][j]*v2UCart[j]

    # Step 2.f: Define the rank-3 version of the Levi-Civita symbol. Amongst
    #         other uses, this is needed for the construction of the approximate
    #         quasi-Kinnersley tetrad.
    def define_LeviCivitaSymbol_rank3(DIM=-1):
        if DIM == -1:
            DIM = par.parval_from_str("DIM")

        LeviCivitaSymbol = ixp.zerorank3()

        for i in range(DIM):
            for j in range(DIM):
                for k in range(DIM):
                    # From https://codegolf.stackexchange.com/questions/160359/levi-civita-symbol :
                    LeviCivitaSymbol[i][j][k] = (i - j) * (j - k) * (k - i) / 2
        return LeviCivitaSymbol

    # Step 2.g: Define v3U
    v3U = ixp.zerorank1()
    LeviCivitaSymbolDDD = define_LeviCivitaSymbol_rank3(DIM=3)
    for a in range(DIM):
        for b in range(DIM):
            for c in range(DIM):
                for d in range(DIM):
                    v3U[a] += sp.sqrt(detgammaCart) * gammaCartUU[a][
                        d] * LeviCivitaSymbolDDD[d][b][c] * v1U[b] * v2U[c]

    # Step 2.h: Define omega_{ij}
    omegaDD = ixp.zerorank2()

    # Step 2.i: Define e^a_i. Note that:
    #           omegaDD[0][0] = \omega_{11} above;
    #           omegaDD[1][1] = \omega_{22} above, etc.
    e1U = ixp.zerorank1()
    e2U = ixp.zerorank1()
    e3U = ixp.zerorank1()
    update_omega(omegaDD, v1U, v2U, v3U, gammaCartDD)
    for a in range(DIM):
        e1U[a] = v1U[a] / sp.sqrt(omegaDD[0][0])
    update_omega(omegaDD, e1U, v2U, v3U, gammaCartDD)
    for a in range(DIM):
        e2U[a] = (v2U[a] - omegaDD[0][1] * e1U[a]) / sp.sqrt(omegaDD[1][1])
    update_omega(omegaDD, e1U, e2U, v3U, gammaCartDD)
    for a in range(DIM):
        e3U[a] = (v3U[a] - omegaDD[0][2] * e1U[a] -
                  omegaDD[1][2] * e2U[a]) / sp.sqrt(omegaDD[2][2])

    # Step 2.j: Construct l^mu, n^mu, and m^mu, based on r^mu, theta^mu, phi^mu, and u^mu:
    rCart4U = ixp.zerorank1(DIM=4)
    thetaCart4U = ixp.zerorank1(DIM=4)
    phiCart4U = ixp.zerorank1(DIM=4)

    for a in range(DIM):
        rCart4U[a + 1] = e2U[a]
        thetaCart4U[a + 1] = e3U[a]
        phiCart4U[a + 1] = e1U[a]

    r4U = ixp.zerorank1(DIM=4)
    theta4U = ixp.zerorank1(DIM=4)
    phi4U = ixp.zerorank1(DIM=4)

    for a in range(DIM):
        for b in range(DIM):
            r4U[a + 1] += Jac_dUrfm_dDCartUD[a][b] * rCart4U[b + 1]
            theta4U[a + 1] += Jac_dUrfm_dDCartUD[a][b] * thetaCart4U[b + 1]
            phi4U[a + 1] += Jac_dUrfm_dDCartUD[a][b] * phiCart4U[b + 1]

    u4U = ixp.zerorank1(DIM=4)
    # FIXME? assumes alpha=1, beta^i = 0
    u4U[0] = 1

    l4U = ixp.zerorank1(DIM=4)
    n4U = ixp.zerorank1(DIM=4)
    mre4U = ixp.zerorank1(DIM=4)
    mim4U = ixp.zerorank1(DIM=4)

    isqrt2 = 1 / sp.sqrt(2)
    for mu in range(4):
        l4U[mu] = isqrt2 * (u4U[mu] + r4U[mu])
        n4U[mu] = isqrt2 * (u4U[mu] - r4U[mu])
        mre4U[mu] = isqrt2 * theta4U[mu]
        mim4U[mu] = isqrt2 * phi4U[mu]
def GiRaFFE_NRPy_C2P(StildeD,BU,gammaDD,betaU,alpha):
    GRHD.compute_sqrtgammaDET(gammaDD)
    gammaUU,unusedgammadet = ixp.symm_matrix_inverter3x3(gammaDD)
    BtildeU = ixp.zerorank1()
    for i in range(3):
        # \tilde{B}^i = B^i \sqrt{\gamma}
        BtildeU[i] = GRHD.sqrtgammaDET*BU[i]

    BtildeD = ixp.zerorank1()
    for i in range(3):
        for j in range(3):
            BtildeD[j] += gammaDD[i][j]*BtildeU[i]

    Btilde2 = sp.sympify(0)
    for i in range(3):
        Btilde2 += BtildeU[i]*BtildeD[i]

    global outStildeD
    outStildeD = StildeD
    # Then, enforce the orthogonality:
    if par.parval_from_str("enforce_orthogonality_StildeD_BtildeU"):
        StimesB = sp.sympify(0)
        for i in range(3):
            StimesB += StildeD[i]*BtildeU[i]

        for i in range(3):
            # {\tilde S}_i = {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2
            outStildeD[i] -= StimesB*BtildeD[i]/Btilde2

    # Calculate \tilde{S}^2:
    Stilde2 = sp.sympify(0)
    for i in range(3):
        for j in range(3):
            Stilde2 += gammaUU[i][j]*outStildeD[i]*outStildeD[j]

    # First we need to compute the factor f:
    # f = \sqrt{(1-\Gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)}
    speed_limit_factor = sp.sqrt((sp.sympify(1)-GAMMA_SPEED_LIMIT**(-2.0))*Btilde2*Btilde2*sp.Rational(1,16)/\
                                 (M_PI*M_PI*GRHD.sqrtgammaDET*GRHD.sqrtgammaDET*Stilde2))

    import Min_Max_and_Piecewise_Expressions as noif

    # Calculate B^2
    B2 = sp.sympify(0)
    for i in range(3):
        for j in range(3):
            B2 += gammaDD[i][j]*BU[i]*BU[j]

    # Enforce the speed limit on StildeD:
    if par.parval_from_str("enforce_speed_limit_StildeD"):
        for i in range(3):
            outStildeD[i] *= noif.min_noif(sp.sympify(1),speed_limit_factor)

    global ValenciavU
    ValenciavU = ixp.zerorank1()
    # Recompute 3-velocity:
    for i in range(3):
        for j in range(3):
            # \bar{v}^i = 4 \pi \gamma^{ij} {\tilde S}_j / (\sqrt{\gamma} B^2)
            ValenciavU[i] += sp.sympify(4)*M_PI*gammaUU[i][j]*outStildeD[j]/(GRHD.sqrtgammaDET*B2)

    # This number determines how far away (in grid points) we will apply the fix.
    grid_points_from_z_plane = par.Cparameters("REAL",thismodule,"grid_points_from_z_plane",4.0)

    if par.parval_from_str("enforce_current_sheet_prescription"):
        # Calculate the drift velocity
        driftvU = ixp.zerorank1()
        for i in range(3):
            driftvU[i] = alpha*ValenciavU[i] - betaU[i]

        # The direct approach, used by the original GiRaFFE:
        # v^z = -(\gamma_{xz} v^x + \gamma_{yz} v^y) / \gamma_{zz}
        newdriftvU2 = -(gammaDD[0][2]*driftvU[0] + gammaDD[1][2]*driftvU[1])/gammaDD[2][2]
        # Now that we have the z component, it's time to substitute its Valencia form in.
        # Remember, we only do this if abs(z) < (k+0.01)*dz. Note that we add 0.01; this helps
        # avoid floating point errors and division by zero. This is the same as abs(z) - (k+0.01)*dz<0
        coord = nrpyAbs(rfm.xx[2])
        bound =(grid_points_from_z_plane+sp.Rational(1,100))*gri.dxx[2]
        ValenciavU[2] = noif.coord_leq_bound(coord,bound)*(newdriftvU2+betaU[2])/alpha \
                      + noif.coord_greater_bound(coord,bound)*ValenciavU[2]
Beispiel #27
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])
Beispiel #28
0
def stress_energy_source_terms_ito_T4UU_and_ADM_or_BSSN_metricvars(
        inputvars, custom_T4UU=None):
    # Step 1: Check if rfm.reference_metric() already called. If not, BSSN
    #         quantities are not yet defined, so cannot proceed!
    if rfm.have_already_called_reference_metric_function == False:
        print(
            "BSSN_source_terms_ito_T4UU(): Must call reference_metric() first!"
        )
        sys.exit(1)

    # Step 2.a: Define gamma4DD[mu][nu] = g_{mu nu} + n_{mu} n_{nu}
    alpha = sp.symbols("alpha", real=True)
    zero = sp.sympify(0)
    n4D = [-alpha, zero, zero, zero]
    AB4m.g4DD_ito_BSSN_or_ADM(inputvars)

    gamma4DD = ixp.zerorank2(DIM=4)
    for mu in range(4):
        for nu in range(4):
            gamma4DD[mu][nu] = AB4m.g4DD[mu][nu] + n4D[mu] * n4D[nu]

    # Step 2.b: If expression for components of T4UU not given, declare T4UU here
    if custom_T4UU == None:
        T4UU = ixp.declarerank2("T4UU", "sym01", DIM=4)
    else:
        T4UU = custom_T4UU

    # Step 2.c: Define BSSN source terms
    global SDD, SD, S, rho
    # Step 2.c.i: S_{ij} = gamma_{i mu} gamma_{j nu} T^{mu nu}
    SDD = ixp.zerorank2()
    for i in range(3):
        for j in range(3):
            for mu in range(4):
                for nu in range(4):
                    SDD[i][j] += gamma4DD[i + 1][mu] * gamma4DD[
                        j + 1][nu] * T4UU[mu][nu]
    # Step 2.c.ii: S_{i} = -gamma_{i mu} n_{nu} T^{mu nu}
    SD = ixp.zerorank1()
    for i in range(3):
        for mu in range(4):
            for nu in range(4):
                SD[i] += -gamma4DD[i + 1][mu] * n4D[nu] * T4UU[mu][nu]
    # Step 2.c.iii: S = gamma^{ij} S_{ij}
    if inputvars == "ADM":
        gammaDD = ixp.declarerank2("gammaDD", "sym01")
        gammaUU, dummydet = ixp.symm_matrix_inverter3x3(gammaDD)  # Set gammaUU
    elif inputvars == "BSSN":
        import BSSN.ADM_in_terms_of_BSSN as AitoB  # NRPy+: ADM quantities in terms of BSSN quantities
        AitoB.ADM_in_terms_of_BSSN()
        gammaUU = AitoB.gammaUU

    S = zero
    for i in range(3):
        for j in range(3):
            S += gammaUU[i][j] * SDD[i][j]
    # Step 2.c.iv: rho = n_{mu} n_{nu} T^{mu nu}
    rho = zero
    for mu in range(4):
        for nu in range(4):
            rho += n4D[mu] * n4D[nu] * T4UU[mu][nu]
    return SDD, SD, S, rho
Beispiel #29
0
def compute_sqrtgammaDET(gammaDD):
    global sqrtgammaDET
    _gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(
        gammaDD)  # _gammaUU unused.
    sqrtgammaDET = sp.sqrt(gammaDET)
Beispiel #30
0
def Tmunu_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear(
        CoordType_in, Tmunu_input_function_name, pointer_to_ID_inputs=False):
    # The ADM & BSSN formalisms only work in 3D; they are 3+1 decompositions of Einstein's equations.
    #    To implement axisymmetry or spherical symmetry, simply set all spatial derivatives in
    #    the relevant angular directions to zero; DO NOT SET DIM TO ANYTHING BUT 3.

    # Step 0: Set spatial dimension (must be 3 for BSSN)
    DIM = 3

    # Step 1: Define the input variables: the 4D stress-energy tensor, and the ADM 3-metric, lapse, & shift:
    T4SphorCartUU = ixp.declarerank2("T4SphorCartUU", "sym01", DIM=4)
    gammaSphorCartDD = ixp.declarerank2("gammaSphorCartDD", "sym01")
    alphaSphorCart = sp.symbols("alphaSphorCart")
    betaSphorCartU = ixp.declarerank1("betaSphorCartU")

    # Step 2: All Tmunu initial data quantities are functions of xx0,xx1,xx2, but
    #         in the Spherical or Cartesian basis.
    #         We first define the BSSN stress-energy source terms in the Spherical
    #         or Cartesian basis, respectively.
    # To get \gamma_{\mu \nu} = gammabar4DD[mu][nu], we'll need to construct the 4-metric, using Eq. 2.122 in B&S:

    # S_{ij} = \gamma_{i \mu} \gamma_{j \nu} T^{\mu \nu}
    # S_{i}  = -\gamma_{i\mu} n_\nu T^{\mu\nu}
    # S      = \gamma^{ij} S_{ij}
    # rho    = n_\mu n_\nu T^{\mu\nu},

    # where

    # \gamma_{\mu\nu} = g_{\mu\nu} + n_\mu n_\nu

    # and

    # n_mu = {-\alpha,0,0,0},

    # Step 2.1: Construct the 4-metric based on the input ADM quantities.

    # This is provided by Eq 4.47 in [Gourgoulhon](https://arxiv.org/pdf/gr-qc/0703035.pdf):

    # g_{tt} = -\alpha^2 + \beta^k \beta_k
    # g_{ti} = \beta_i
    # g_{ij} = \gamma_{ij}

    # Eq. 2.121 in B&S
    betaSphorCartD = ixp.zerorank1()
    for i in range(DIM):
        for j in range(DIM):
            betaSphorCartD[i] += gammaSphorCartDD[i][j] * betaSphorCartU[j]

    # Now compute the beta contraction.
    beta2 = sp.sympify(0)
    for i in range(DIM):
        beta2 += betaSphorCartU[i] * betaSphorCartD[i]

    # Eq. 2.122 in B&S
    g4SphorCartDD = ixp.zerorank2(DIM=4)
    g4SphorCartDD[0][0] = -alphaSphorCart**2 + beta2
    for i in range(DIM):
        g4SphorCartDD[i + 1][0] = g4SphorCartDD[0][i + 1] = betaSphorCartD[i]
    for i in range(DIM):
        for j in range(DIM):
            g4SphorCartDD[i + 1][j + 1] = gammaSphorCartDD[i][j]

    # Step 2.2: Construct \gamma_{mu nu} = g_{mu nu} + n_mu n_nu:
    n4SphorCartD = ixp.zerorank1(DIM=4)
    n4SphorCartD[0] = -alphaSphorCart

    gamma4SphorCartDD = ixp.zerorank2(DIM=4)
    for mu in range(4):
        for nu in range(4):
            gamma4SphorCartDD[mu][nu] = g4SphorCartDD[mu][
                nu] + n4SphorCartD[mu] * n4SphorCartD[nu]

    # Step 2.3: We now have all we need to construct the BSSN source
    #           terms in the current basis (Spherical or Cartesian):

    # S_{ij} = \gamma_{i \mu} \gamma_{j \nu} T^{\mu \nu}
    # S_{i}  = -\gamma_{i\mu} n_\nu T^{\mu\nu}
    # S      = \gamma^{ij} S_{ij}
    # rho    = n_\mu n_\nu T^{\mu\nu},

    SSphorCartDD = ixp.zerorank2()
    SSphorCartD = ixp.zerorank1()
    SSphorCart = sp.sympify(0)
    rhoSphorCart = sp.sympify(0)

    for i in range(DIM):
        for j in range(DIM):
            for mu in range(4):
                for nu in range(4):
                    SSphorCartDD[i][j] += gamma4SphorCartDD[
                        i + 1][mu] * gamma4SphorCartDD[
                            j + 1][nu] * T4SphorCartUU[mu][nu]

    for i in range(DIM):
        for mu in range(4):
            for nu in range(4):
                SSphorCartD[i] += -gamma4SphorCartDD[
                    i + 1][mu] * n4SphorCartD[nu] * T4SphorCartUU[mu][nu]

    gammaSphorCartUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaSphorCartDD)
    for i in range(DIM):
        for j in range(DIM):
            SSphorCart += gammaSphorCartUU[i][j] * SSphorCartDD[i][j]

    for mu in range(4):
        for nu in range(4):
            rhoSphorCart += n4SphorCartD[mu] * n4SphorCartD[
                nu] * T4SphorCartUU[mu][nu]

    # Step 3: Perform basis conversion to

    # Make sure that rfm.reference_metric() has been called.
    #    We'll need the variables it defines throughout this module.
    if rfm.have_already_called_reference_metric_function == False:
        print(
            "Error. Called Tmunu_Numerical_Spherical_or_Cartesian_to_BSSNCurvilinear() without"
        )
        print(
            "       first setting up reference metric, by calling rfm.reference_metric()."
        )
        exit(1)

    # Step 1: All input quantities are in terms of r,th,ph or x,y,z. We want them in terms
    #         of xx0,xx1,xx2, so here we call sympify_integers__replace_rthph() to replace
    #         r,th,ph or x,y,z, respectively, with the appropriate functions of xx0,xx1,xx2
    #         as defined for this particular reference metric in reference_metric.py's
    #         xxSph[] or xxCart[], respectively:
    r_th_ph_or_Cart_xyz_oID_xx = []
    if CoordType_in == "Spherical":
        r_th_ph_or_Cart_xyz_oID_xx = rfm.xxSph
    elif CoordType_in == "Cartesian":
        r_th_ph_or_Cart_xyz_oID_xx = rfm.xxCart
    else:
        print(
            "Error: Can only convert ADM Cartesian or Spherical initial data to BSSN Curvilinear coords."
        )
        exit(1)

    # Next apply Jacobian transformations to convert into the (xx0,xx1,xx2) basis

    # alpha is a scalar, so no Jacobian transformation is necessary.
    alpha = alphaSphorCart

    Jac_dUSphorCart_dDrfmUD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            Jac_dUSphorCart_dDrfmUD[i][j] = sp.diff(
                r_th_ph_or_Cart_xyz_oID_xx[i], rfm.xx[j])

    Jac_dUrfm_dDSphorCartUD, dummyDET = ixp.generic_matrix_inverter3x3(
        Jac_dUSphorCart_dDrfmUD)

    betaU = ixp.zerorank1()
    BU = ixp.zerorank1()
    gammaSphorCartDD = ixp.zerorank2()
    KDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            betaU[i] += Jac_dUrfm_dDSphorCartUD[i][j] * betaSphorCartU[j]
            BU[i] += Jac_dUrfm_dDSphorCartUD[i][j] * BSphorCartU[j]
            for k in range(DIM):
                for l in range(DIM):
                    gammaSphorCartDD[i][j] += Jac_dUSphorCart_dDrfmUD[k][i] * Jac_dUSphorCart_dDrfmUD[l][j] * \
                                     gammaSphorCartDD[k][l]
                    KDD[i][j] += Jac_dUSphorCart_dDrfmUD[k][
                        i] * Jac_dUSphorCart_dDrfmUD[l][j] * KSphorCartDD[k][l]

    # Step 3: All ADM quantities were input into this function in the Spherical or Cartesian
    #         basis, as functions of r,th,ph or x,y,z, respectively. In Steps 1 and 2 above,
    #         we converted them to the xx0,xx1,xx2 basis, and as functions of xx0,xx1,xx2.
    #         Here we convert ADM quantities to their BSSN Curvilinear counterparts:

    # Step 3.1: Convert ADM $\gamma_{ij}$ to BSSN $\bar{\gamma}_{ij}$:
    #   We have (Eqs. 2 and 3 of [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf)):
    # \bar{\gamma}_{i j} = \left(\frac{\bar{\gamma}}{\gamma}\right)^{1/3} \gamma_{ij}.
    gammaSphorCartUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaSphorCartDD)
    gammabarDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            gammabarDD[i][j] = (rfm.detgammahat / gammaDET)**(sp.Rational(
                1, 3)) * gammaSphorCartDD[i][j]

    # Step 3.2: Convert the extrinsic curvature $K_{ij}$ to the trace-free extrinsic
    #           curvature $\bar{A}_{ij}$, plus the trace of the extrinsic curvature $K$,
    #           where (Eq. 3 of [Baumgarte *et al.*](https://arxiv.org/pdf/1211.6632.pdf)):

    # K = \gamma^{ij} K_{ij}, and
    # \bar{A}_{ij} &= \left(\frac{\bar{\gamma}}{\gamma}\right)^{1/3} \left(K_{ij} - \frac{1}{3} \gamma_{ij} K \right)
    trK = sp.sympify(0)
    for i in range(DIM):
        for j in range(DIM):
            trK += gammaSphorCartUU[i][j] * KDD[i][j]

    AbarDD = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            AbarDD[i][j] = (rfm.detgammahat / gammaDET)**(sp.Rational(
                1, 3)) * (KDD[i][j] -
                          sp.Rational(1, 3) * gammaSphorCartDD[i][j] * trK)

    # Step 3.3: Set the conformal factor variable $\texttt{cf}$, which is set
    #           by the "BSSN_RHSs::ConformalFactor" parameter. For example if
    #           "ConformalFactor" is set to "phi", we can use Eq. 3 of
    #           [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf),
    #           which in arbitrary coordinates is written:

    # \phi = \frac{1}{12} \log\left(\frac{\gamma}{\bar{\gamma}}\right).

    # Alternatively if "BSSN_RHSs::ConformalFactor" is set to "chi", then

    # \chi = e^{-4 \phi} = \exp\left(-4 \frac{1}{12} \left(\frac{\gamma}{\bar{\gamma}}\right)\right)
    #      = \exp\left(-\frac{1}{3} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) = \left(\frac{\gamma}{\bar{\gamma}}\right)^{-1/3}.
    #
    # Finally if "BSSN_RHSs::ConformalFactor" is set to "W", then

    # W = e^{-2 \phi} = \exp\left(-2 \frac{1}{12} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) =
    # \exp\left(-\frac{1}{6} \log\left(\frac{\gamma}{\bar{\gamma}}\right)\right) =
    # \left(\frac{\gamma}{\bar{\gamma}}\right)^{-1/6}.

    # First compute gammabarDET:
    gammabarUU, gammabarDET = ixp.symm_matrix_inverter3x3(gammabarDD)

    cf = sp.sympify(0)

    if par.parval_from_str("ConformalFactor") == "phi":
        cf = sp.Rational(1, 12) * sp.log(gammaDET / gammabarDET)
    elif par.parval_from_str("ConformalFactor") == "chi":
        cf = (gammaDET / gammabarDET)**(-sp.Rational(1, 3))
    elif par.parval_from_str("ConformalFactor") == "W":
        cf = (gammaDET / gammabarDET)**(-sp.Rational(1, 6))
    else:
        print("Error ConformalFactor type = \"" +
              par.parval_from_str("ConformalFactor") + "\" unknown.")
        exit(1)

    # Step 4: Rescale tensorial quantities according to the prescription described in
    #         the [BSSN in curvilinear coordinates tutorial module](Tutorial-BSSNCurvilinear.ipynb)
    #         (also [Ruchlin *et al.*](https://arxiv.org/pdf/1712.07658.pdf)):
    #
    # h_{ij} &= (\bar{\gamma}_{ij} - \hat{\gamma}_{ij})/\text{ReDD[i][j]}\\
    # a_{ij} &= \bar{A}_{ij}/\text{ReDD[i][j]}\\
    # \lambda^i &= \bar{\Lambda}^i/\text{ReU[i]}\\
    # \mathcal{V}^i &= \beta^i/\text{ReU[i]}\\
    # \mathcal{B}^i &= B^i/\text{ReU[i]}\\
    hDD = ixp.zerorank2()
    aDD = ixp.zerorank2()
    vetU = ixp.zerorank1()
    betU = ixp.zerorank1()
    for i in range(DIM):
        vetU[i] = betaU[i] / rfm.ReU[i]
        betU[i] = BU[i] / rfm.ReU[i]
        for j in range(DIM):
            hDD[i][j] = (gammabarDD[i][j] - rfm.ghatDD[i][j]) / rfm.ReDD[i][j]
            aDD[i][j] = AbarDD[i][j] / rfm.ReDD[i][j]

    # Step 5: Output all ADM-to-BSSN expressions to a C function. This function
    #         must first call the ID_ADM_SphorCart() defined above. Using these
    #         Spherical or Cartesian data, it sets up all quantities needed for
    #         BSSNCurvilinear initial data, *except* $\lambda^i$, which must be
    #         computed from numerical data using finite-difference derivatives.
    with open("BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",
              "w") as file:
        file.write(
            "void ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs(const REAL xx0xx1xx2[3],"
        )
        if pointer_to_ID_inputs == True:
            file.write("ID_inputs *other_inputs,")
        else:
            file.write("ID_inputs other_inputs,")
        file.write("""
                    REAL *hDD00,REAL *hDD01,REAL *hDD02,REAL *hDD11,REAL *hDD12,REAL *hDD22,
                    REAL *aDD00,REAL *aDD01,REAL *aDD02,REAL *aDD11,REAL *aDD12,REAL *aDD22,
                    REAL *trK, 
                    REAL *vetU0,REAL *vetU1,REAL *vetU2,
                    REAL *betU0,REAL *betU1,REAL *betU2,
                    REAL *alpha,  REAL *cf) {
      REAL gammaSphorCartDD00,gammaSphorCartDD01,gammaSphorCartDD02,
           gammaSphorCartDD11,gammaSphorCartDD12,gammaSphorCartDD22;
      REAL KSphorCartDD00,KSphorCartDD01,KSphorCartDD02,
           KSphorCartDD11,KSphorCartDD12,KSphorCartDD22;
      REAL alphaSphorCart,betaSphorCartU0,betaSphorCartU1,betaSphorCartU2;
      REAL BSphorCartU0,BSphorCartU1,BSphorCartU2;
      const REAL xx0 = xx0xx1xx2[0];
      const REAL xx1 = xx0xx1xx2[1];
      const REAL xx2 = xx0xx1xx2[2];
      REAL xyz_or_rthph[3];\n""")
    outCparams = "preindent=1,outCfileaccess=a,outCverbose=False,includebraces=False"
    outputC(r_th_ph_or_Cart_xyz_oID_xx[0:3],
            ["xyz_or_rthph[0]", "xyz_or_rthph[1]", "xyz_or_rthph[2]"],
            "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",
            outCparams + ",CSE_enable=False")
    with open("BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",
              "a") as file:
        file.write("      " + ADM_input_function_name +
                   """(xyz_or_rthph, other_inputs,
                      &gammaSphorCartDD00,&gammaSphorCartDD01,&gammaSphorCartDD02,
                      &gammaSphorCartDD11,&gammaSphorCartDD12,&gammaSphorCartDD22,
                      &KSphorCartDD00,&KSphorCartDD01,&KSphorCartDD02,
                      &KSphorCartDD11,&KSphorCartDD12,&KSphorCartDD22,
                      &alphaSphorCart,&betaSphorCartU0,&betaSphorCartU1,&betaSphorCartU2,
                      &BSphorCartU0,&BSphorCartU1,&BSphorCartU2);
        // Next compute all rescaled BSSN curvilinear quantities:\n""")
    outCparams = "preindent=1,outCfileaccess=a,outCverbose=False,includebraces=False"
    outputC([
        hDD[0][0], hDD[0][1], hDD[0][2], hDD[1][1], hDD[1][2], hDD[2][2],
        aDD[0][0], aDD[0][1], aDD[0][2], aDD[1][1], aDD[1][2], aDD[2][2], trK,
        vetU[0], vetU[1], vetU[2], betU[0], betU[1], betU[2], alpha, cf
    ], [
        "*hDD00", "*hDD01", "*hDD02", "*hDD11", "*hDD12", "*hDD22", "*aDD00",
        "*aDD01", "*aDD02", "*aDD11", "*aDD12", "*aDD22", "*trK", "*vetU0",
        "*vetU1", "*vetU2", "*betU0", "*betU1", "*betU2", "*alpha", "*cf"
    ],
            "BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",
            params=outCparams)
    with open("BSSN/ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs.h",
              "a") as file:
        file.write("}\n")

    # Step 5.A: Output the driver function for the above
    #           function ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs()
    # Next write the driver function for ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs():
    with open("BSSN/ID_BSSN__ALL_BUT_LAMBDAs.h", "w") as file:
        file.write(
            "void ID_BSSN__ALL_BUT_LAMBDAs(const int Nxx_plus_2NGHOSTS[3],REAL *xx[3],"
        )
        if pointer_to_ID_inputs == True:
            file.write("ID_inputs *other_inputs,")
        else:
            file.write("ID_inputs other_inputs,")
        file.write("REAL *in_gfs) {\n")
        file.write(
            lp.loop(["i2", "i1", "i0"], ["0", "0", "0"], [
                "Nxx_plus_2NGHOSTS[2]", "Nxx_plus_2NGHOSTS[1]",
                "Nxx_plus_2NGHOSTS[0]"
            ], ["1", "1", "1"], [
                "#pragma omp parallel for", "    const REAL xx2 = xx[2][i2];",
                "        const REAL xx1 = xx[1][i1];"
            ], "", """const REAL xx0 = xx[0][i0];
const int idx = IDX3(i0,i1,i2);
const REAL xx0xx1xx2[3] = {xx0,xx1,xx2};
ID_ADM_xx0xx1xx2_to_BSSN_xx0xx1xx2__ALL_BUT_LAMBDAs(xx0xx1xx2,other_inputs,
                    &in_gfs[IDX4pt(HDD00GF,idx)],&in_gfs[IDX4pt(HDD01GF,idx)],&in_gfs[IDX4pt(HDD02GF,idx)],
                    &in_gfs[IDX4pt(HDD11GF,idx)],&in_gfs[IDX4pt(HDD12GF,idx)],&in_gfs[IDX4pt(HDD22GF,idx)],
                    &in_gfs[IDX4pt(ADD00GF,idx)],&in_gfs[IDX4pt(ADD01GF,idx)],&in_gfs[IDX4pt(ADD02GF,idx)],
                    &in_gfs[IDX4pt(ADD11GF,idx)],&in_gfs[IDX4pt(ADD12GF,idx)],&in_gfs[IDX4pt(ADD22GF,idx)],
                    &in_gfs[IDX4pt(TRKGF,idx)],
                    &in_gfs[IDX4pt(VETU0GF,idx)],&in_gfs[IDX4pt(VETU1GF,idx)],&in_gfs[IDX4pt(VETU2GF,idx)],
                    &in_gfs[IDX4pt(BETU0GF,idx)],&in_gfs[IDX4pt(BETU1GF,idx)],&in_gfs[IDX4pt(BETU2GF,idx)],
                    &in_gfs[IDX4pt(ALPHAGF,idx)],&in_gfs[IDX4pt(CFGF,idx)]);
"""))
        file.write("}\n")

        # Step 6: Compute $\bar{\Lambda}^i$ (Eqs. 4 and 5 of
        #         [Baumgarte *et al.*](https://arxiv.org/pdf/1211.6632.pdf)),
        #         from finite-difference derivatives of rescaled metric
        #         quantities $h_{ij}$:

        # \bar{\Lambda}^i = \bar{\gamma}^{jk}\left(\bar{\Gamma}^i_{jk} - \hat{\Gamma}^i_{jk}\right).

        # The reference_metric.py module provides us with analytic expressions for
        #         $\hat{\Gamma}^i_{jk}$, so here we need only compute
        #         finite-difference expressions for $\bar{\Gamma}^i_{jk}$, based on
        #         the values for $h_{ij}$ provided in the initial data. Once
        #         $\bar{\Lambda}^i$ has been computed, we apply the usual rescaling
        #         procedure:

        # \lambda^i = \bar{\Lambda}^i/\text{ReU[i]},

        # and then output the result to a C file using the NRPy+
        #         finite-difference C output routine.
        # We will need all BSSN gridfunctions to be defined, as well as
        #     expressions for gammabarDD_dD in terms of exact derivatives of
        #     the rescaling matrix and finite-difference derivatives of
        #     hDD's.

        gammabarDD = bssnrhs.gammabarDD
        gammabarUU, gammabarDET = ixp.symm_matrix_inverter3x3(gammabarDD)

        gammabarDD_dD = bssnrhs.gammabarDD_dD

        # Next compute Christoffel symbols \bar{\Gamma}^i_{jk}:
        GammabarUDD = ixp.zerorank3()
        for i in range(DIM):
            for j in range(DIM):
                for k in range(DIM):
                    for l in range(DIM):
                        GammabarUDD[i][j][k] += sp.Rational(
                            1,
                            2) * gammabarUU[i][l] * (gammabarDD_dD[l][j][k] +
                                                     gammabarDD_dD[l][k][j] -
                                                     gammabarDD_dD[j][k][l])
        # Next evaluate \bar{\Lambda}^i, based on GammabarUDD above and GammahatUDD
        #       (from the reference metric):
        LambdabarU = ixp.zerorank1()
        for i in range(DIM):
            for j in range(DIM):
                for k in range(DIM):
                    LambdabarU[i] += gammabarUU[j][k] * (
                        GammabarUDD[i][j][k] - rfm.GammahatUDD[i][j][k])

        # Finally apply rescaling:
        # lambda^i = Lambdabar^i/\text{ReU[i]}
        lambdaU = ixp.zerorank1()
        for i in range(DIM):
            lambdaU[i] = LambdabarU[i] / rfm.ReU[i]

        outCparams = "preindent=1,outCfileaccess=a,outCverbose=False,includebraces=False"
        lambdaU_expressions = [
            lhrh(lhs=gri.gfaccess("in_gfs", "lambdaU0"), rhs=lambdaU[0]),
            lhrh(lhs=gri.gfaccess("in_gfs", "lambdaU1"), rhs=lambdaU[1]),
            lhrh(lhs=gri.gfaccess("in_gfs", "lambdaU2"), rhs=lambdaU[2])
        ]
        lambdaU_expressions_FDout = fin.FD_outputC("returnstring",
                                                   lambdaU_expressions,
                                                   outCparams)

        with open("BSSN/ID_BSSN_lambdas.h", "w") as file:
            file.write("""
void ID_BSSN_lambdas(const int Nxx[3],const int Nxx_plus_2NGHOSTS[3],REAL *xx[3],const REAL dxx[3],REAL *in_gfs) {\n"""
                       )
            file.write(
                lp.loop(["i2", "i1", "i0"], ["NGHOSTS", "NGHOSTS", "NGHOSTS"],
                        ["NGHOSTS+Nxx[2]", "NGHOSTS+Nxx[1]", "NGHOSTS+Nxx[0]"],
                        ["1", "1", "1"], [
                            "const REAL invdx0 = 1.0/dxx[0];\n" +
                            "const REAL invdx1 = 1.0/dxx[1];\n" +
                            "const REAL invdx2 = 1.0/dxx[2];\n" +
                            "#pragma omp parallel for",
                            "    const REAL xx2 = xx[2][i2];",
                            "        const REAL xx1 = xx[1][i1];"
                        ], "", "const REAL xx0 = xx[0][i0];\n" +
                        lambdaU_expressions_FDout))
            file.write("}\n")