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
Exemplo n.º 2
0
def BSSN_constraints(add_T4UUmunu_source_terms=False):
    # Step 1.a: Set spatial dimension (must be 3 for BSSN, as BSSN is
    #           a 3+1-dimensional decomposition of the general
    #           relativistic field equations)
    DIM = 3

    # Step 1.b: Given the chosen coordinate system, set up
    #           corresponding reference metric and needed
    #           reference metric quantities
    # The following function call sets up the reference metric
    #    and related quantities, including rescaling matrices ReDD,
    #    ReU, and hatted quantities.
    rfm.reference_metric()

    # Step 2: Hamiltonian constraint.
    # First declare all needed variables
    Bq.declare_BSSN_gridfunctions_if_not_declared_already()  # Sets trK
    Bq.BSSN_basic_tensors()  # Sets AbarDD
    Bq.gammabar__inverse_and_derivs()  # Sets gammabarUU
    Bq.AbarUU_AbarUD_trAbar_AbarDD_dD()  # Sets AbarUU and AbarDD_dD
    Bq.RicciBar__gammabarDD_dHatD__DGammaUDD__DGammaU()  # Sets RbarDD
    Bq.phi_and_derivs()  # Sets phi_dBarD & phi_dBarDD

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

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

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

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

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

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

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

    # FIXME: ADD T4UUmunu SOURCE TERMS TO MOMENTUM CONSTRAINT!

    # Step 3: M^i, the momentum constraint

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

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

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

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

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

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

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

    # Finally, we multiply by e^{-4 phi} and rescale the momentum constraint:
    for i in range(DIM):
        MU[i] *= Bq.exp_m4phi / rfm.ReU[i]
Exemplo n.º 3
0
def Psi4_tetrads():
    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!")
        sys.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 in terms of
    #           xx0,xx1,xx2.
    x = rfm.xxCart[0]
    y = rfm.xxCart[1]
    z = rfm.xxCart[2]

    # Step 2.b: Declare detgamma and gammaUU from
    #           BSSN.ADM_in_terms_of_BSSN;
    #           simplify detgamma & gammaUU expressions,
    #           which expedites Psi4 codegen.
    detgamma = sp.simplify(AB.detgamma)
    gammaUU = ixp.zerorank2()
    for i in range(DIM):
        for j in range(DIM):
            gammaUU[i][j] = sp.simplify(AB.gammaUU[i][j])

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

    # Step 2.d: 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.simplify(
                sp.diff(rfm.xxCart[i], rfm.xx[j]))

    # Step 2.e: 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.i: Simplify expressions for d xx^j / d x_Cart^i:
    for i in range(DIM):
        for j in range(DIM):
            Jac_dUrfm_dDCartUD[i][j] = sp.simplify(Jac_dUrfm_dDCartUD[i][j])

    # Step 2.f: Transform v1U and v2U from the Cartesian to the xx^i basis
    v1U = ixp.zerorank1()
    v2U = ixp.zerorank1()
    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.g: Define v3U
    v3U = ixp.zerorank1()
    LeviCivitaSymbolDDD = 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] * LeviCivitaSymbolDDD[d][b][c] * v1U[b] * v2U[c]

    # Step 2.g.i: Simplify expressions for v1U,v2U,v3U. This greatly expedites the C code generation (~10x faster)
    #             Drat. Simplification with certain versions of SymPy & coord systems results in a hang. Let's just
    #             evaluate the expressions so the most trivial optimizations can be performed.
    for a in range(DIM):
        v1U[a] = v1U[a].doit()  # sp.simplify(v1U[a])
        v2U[a] = v2U[a].doit()  # sp.simplify(v2U[a])
        v3U[a] = v3U[a].doit()  # sp.simplify(v3U[a])

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

    def v_vectorDU(v1U, v2U, v3U, i, a):
        if i == 0:
            return v1U[a]
        if i == 1:
            return v2U[a]
        if i == 2:
            return v3U[a]
        print("ERROR: unknown vector!")
        sys.exit(1)

    def update_omega(omegaDD, i, j, v1U, v2U, v3U, gammaDD):
        omegaDD[i][j] = sp.sympify(0)
        for a in range(DIM):
            for b in range(DIM):
                omegaDD[i][j] += v_vectorDU(v1U, v2U, v3U, i, a) * v_vectorDU(
                    v1U, v2U, v3U, j, b) * gammaDD[a][b]

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

    # Next e_2^a: First orthogonalize:
    e2U = ixp.zerorank1()
    update_omega(omegaDD, 0, 1, e1U, v2U, v3U, gammaDD)
    for a in range(DIM):
        e2U[a] = (v2U[a] - omegaDD[0][1] * e1U[a])
    # Then normalize:
    update_omega(omegaDD, 1, 1, e1U, e2U, v3U, gammaDD)
    for a in range(DIM):
        e2U[a] /= sp.sqrt(omegaDD[1][1])

    # Next e_3^a: First orthogonalize:
    e3U = ixp.zerorank1()
    update_omega(omegaDD, 0, 2, e1U, e2U, v3U, gammaDD)
    update_omega(omegaDD, 1, 2, e1U, e2U, v3U, gammaDD)
    for a in range(DIM):
        e3U[a] = (v3U[a] - omegaDD[0][2] * e1U[a] - omegaDD[1][2] * e2U[a])
    # Then normalize:
    update_omega(omegaDD, 2, 2, e1U, e2U, e3U, gammaDD)
    for a in range(DIM):
        e3U[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:
    r4U = ixp.zerorank1(DIM=4)
    u4U = ixp.zerorank1(DIM=4)
    theta4U = ixp.zerorank1(DIM=4)
    phi4U = ixp.zerorank1(DIM=4)

    for a in range(DIM):
        r4U[a + 1] = e2U[a]
        theta4U[a + 1] = e3U[a]
        phi4U[a + 1] = e1U[a]

    # FIXME? assumes alpha=1, beta^i = 0
    if par.parval_from_str(thismodule + "::UseCorrectUnitNormal") == "False":
        u4U[0] = 1
    else:
        # Eq. 2.116 in Baumgarte & Shapiro:
        #  n^mu = {1/alpha, -beta^i/alpha}. Note that n_mu = {alpha,0}, so n^mu n_mu = -1.
        import BSSN.BSSN_quantities as Bq
        Bq.declare_BSSN_gridfunctions_if_not_declared_already()
        Bq.BSSN_basic_tensors()
        u4U[0] = 1 / Bq.alpha
        for i in range(DIM):
            u4U[i + 1] = -Bq.betaU[i] / Bq.alpha

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

    # M_SQRT1_2 = 1 / sqrt(2) (defined in math.h on Linux)
    M_SQRT1_2 = par.Cparameters("#define", thismodule, "M_SQRT1_2", "")
    isqrt2 = M_SQRT1_2  #1/sp.sqrt(2) <- SymPy drops precision to 15 sig. digits in unit tests
    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]
Exemplo n.º 4
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"
    )
Exemplo n.º 5
0
def Psi4_tetrads():
    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 v1U and v2U from the Cartesian to the xx^i basis
    v1U = ixp.zerorank1()
    v2U = ixp.zerorank1()
    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(detgamma) * gammaUU[a][
                        d] * LeviCivitaSymbolDDD[d][b][c] * v1U[b] * v2U[c]

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

    def v_vectorDU(v1U, v2U, v3U, i, a):
        if i == 0:
            return v1U[a]
        elif i == 1:
            return v2U[a]
        elif i == 2:
            return v3U[a]
        else:
            print("ERROR: unknown vector!")
            exit(1)

    def update_omega(omegaDD, v1U, v2U, v3U, gammaDD):
        for i in range(DIM):
            for j in range(DIM):
                omegaDD[i][j] = sp.sympify(0)
        for i in range(DIM):
            for j in range(DIM):
                for a in range(DIM):
                    for b in range(DIM):
                        omegaDD[i][j] += v_vectorDU(
                            v1U, v2U, v3U, i, a) * v_vectorDU(
                                v1U, v2U, v3U, j, b) * gammaDD[a][b]

    # 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, gammaDD)
    for a in range(DIM):
        e1U[a] = v1U[a] / sp.sqrt(omegaDD[0][0])
    update_omega(omegaDD, e1U, v2U, v3U, gammaDD)
    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, gammaDD)
    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:
    r4U = ixp.zerorank1(DIM=4)
    u4U = ixp.zerorank1(DIM=4)
    theta4U = ixp.zerorank1(DIM=4)
    phi4U = ixp.zerorank1(DIM=4)

    for a in range(DIM):
        r4U[a + 1] = e2U[a]
        theta4U[a + 1] = e3U[a]
        phi4U[a + 1] = e1U[a]

    # FIXME? assumes alpha=1, beta^i = 0
    if par.parval_from_str(thismodule + "::UseCorrectUnitNormal") == "False":
        u4U[0] = 1
    else:
        # Eq. 2.116 in Baumgarte & Shapiro:
        #  n^mu = {1/alpha, -beta^i/alpha}. Note that n_mu = {alpha,0}, so n^mu n_mu = -1.
        import BSSN.BSSN_quantities as Bq
        Bq.declare_BSSN_gridfunctions_if_not_declared_already()
        Bq.BSSN_basic_tensors()
        u4U[0] = 1 / Bq.alpha
        for i in range(DIM):
            u4U[i + 1] = -Bq.betaU[i] / Bq.alpha

    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]