コード例 #1
0
def Set_up_CurviBoundaryConditions():
    # Step 2.A.2. Output gridfunction #define aliases to file:
    evolved_variables_list, auxiliary_variables_list = \
        gri.output__gridfunction_defines_h__return_gf_lists("CurviBoundaryConditions")

    # Step 2.B.1: set the parity conditions on each gridfunction in gf_list,
    #       based on the digits at the end of its name
    # For example, if the gridfunction name ends with "01", then (based on the table
    #       in the Curvi. BCs tutorial module) the "set_parity_types()" function below
    #       will set the parity_type of that gridfunction to 5. We can be assured this
    #       is a robust algorithm, because gri.register_gridfunctions() in grid.py will
    #       throw an error if a gridfunction base name ends in an integer.
    #
    # After each parity type is found, we store the parity type of each gridfunction to
    #       const int8_t arrays evol_gf_parity and aux_gf_parity, appended to the end of
    #       "CurviBoundaryConditions/gridfunction_defines.h".
    def set_parity_types(gf_list):
        parity_type = []
        for i in range(len(gf_list)):
            varname = gf_list[i]
            parity_type__orig_len = len(parity_type)
            if len(varname) > 2:
                if varname[-2] == "0" and varname[-1] == "0":  # In Python, a[-1] points to the last
                    # element of a list; a[-2] the
                    # second-to-last element, etc.
                    parity_type.append(4)
                elif varname[-2] == "0" and varname[-1] == "1":
                    parity_type.append(5)
                elif varname[-2] == "0" and varname[-1] == "2":
                    parity_type.append(6)
                elif varname[-2] == "1" and varname[-1] == "1":
                    parity_type.append(7)
                elif varname[-2] == "1" and varname[-1] == "2":
                    parity_type.append(8)
                elif varname[-2] == "2" and varname[-1] == "2":
                    parity_type.append(9)
            if len(varname) > 1 and len(parity_type) == parity_type__orig_len:
                if varname[-1] == "0":
                    parity_type.append(1)
                elif varname[-1] == "1":
                    parity_type.append(2)
                elif varname[-1] == "2":
                    parity_type.append(3)
            if varname[len(varname) - 1].isdigit() == False:
                parity_type.append(0)

            if len(parity_type) == parity_type__orig_len:
                print("Error: Could not figure out parity type for evolved variable: " + varname)
                exit(1)
        return parity_type

    evol_parity_type = set_parity_types(evolved_variables_list)
    aux_parity_type = set_parity_types(auxiliary_variables_list)

    with open("CurviBoundaryConditions/gridfunction_defines.h", "a") as file:
        file.write("\n\n/* PARITY TYPES FOR ALL GRIDFUNCTIONS.\n")
        file.write("   SEE \"Tutorial-Start_to_Finish-BSSNCurvilinear-Two_BHs_Collide.ipynb\" FOR DEFINITIONS. */\n")
        if len(evolved_variables_list) > 0:
            file.write("const int8_t evol_gf_parity[" + str(len(evolved_variables_list)) + "] = { ")
            for i in range(len(evolved_variables_list) - 1):
                file.write(str(evol_parity_type[i]) + ", ")
            file.write(str(evol_parity_type[len(evolved_variables_list) - 1]) + " };\n")

        if len(auxiliary_variables_list) > 0:
            file.write("const int8_t aux_gf_parity[" + str(len(auxiliary_variables_list)) + "] = { ")
            for i in range(len(auxiliary_variables_list) - 1):
                file.write(str(aux_parity_type[i]) + ", ")
            file.write(str(aux_parity_type[len(auxiliary_variables_list) - 1]) + " };\n")
    print("Wrote to file \"CurviBoundaryConditions/gridfunction_defines.h\"")

    # Step 2.B.2: Set up unit-vector dot products (=parity) for each of the 10 parity condition types
    parity = ixp.zerorank1(DIM=10)
    UnitVectors_inner = ixp.zerorank2()
    xx0_inbounds, xx1_inbounds, xx2_inbounds = sp.symbols("xx0_inbounds xx1_inbounds xx2_inbounds", real=True)
    for i in range(3):
        for j in range(3):
            UnitVectors_inner[i][j] = rfm.UnitVectors[i][j].subs(rfm.xx[0], xx0_inbounds).subs(rfm.xx[1],
                                                                                               xx1_inbounds).subs(
                rfm.xx[2], xx2_inbounds)
    # Type 0: scalar
    parity[0] = sp.sympify(1)
    # Type 1: i0-direction vector or one-form
    # Type 2: i1-direction vector or one-form
    # Type 3: i2-direction vector or one-form
    for i in range(3):
        for Type in range(1, 4):
            parity[Type] += rfm.UnitVectors[Type - 1][i] * UnitVectors_inner[Type - 1][i]
    # Type 4: i0i0-direction rank-2 tensor
    # parity[4] = parity[1]*parity[1]
    # Type 5: i0i1-direction rank-2 tensor
    # Type 6: i0i2-direction rank-2 tensor
    # Type 7: i1i1-direction rank-2 tensor
    # Type 8: i1i2-direction rank-2 tensor
    # Type 9: i2i2-direction rank-2 tensor
    count = 4
    for i in range(3):
        for j in range(i, 3):
            parity[count] = parity[i + 1] * parity[j + 1]
            count = count + 1

    lhs_strings = []
    for i in range(10):
        lhs_strings.append("parity[" + str(i) + "]")
    outputC(parity, lhs_strings, "CurviBoundaryConditions/set_parity_conditions.h")

    # Step 2.C: Modified version of the scalar wave in curvilinear coordinates boundary
    #           condition ghost zone mapping routine, so that it also defines the parity
    #           conditions.

    # First output code needed for mapping from any given curvilinear coordinate gridpoint
    #  to the Cartesian coordinate in the grid interior (xxCart), and then find the
    #  corresponding gridpoint index in the grid interior (Cart_to_xx; xxminmax).
    # Generic coordinate NRPy+ file output, Part 1: output the conversion from (x0,x1,x2) to Cartesian (x,y,z)
    outputC([rfm.xxCart[0], rfm.xxCart[1], rfm.xxCart[2]], ["xCart[0]", "xCart[1]", "xCart[2]"],
            "CurviBoundaryConditions/xxCart.h")
    # Generic coordinate NRPy+ file output, Part 2: output the coordinate bounds xxmin[] and xxmax[]:
    with open("CurviBoundaryConditions/xxminmax.h", "w") as file:
        file.write(
            "const REAL xxmin[3] = {" + str(rfm.xxmin[0]) + "," + str(rfm.xxmin[1]) + "," + str(rfm.xxmin[2]) + "};\n")
        file.write(
            "const REAL xxmax[3] = {" + str(rfm.xxmax[0]) + "," + str(rfm.xxmax[1]) + "," + str(rfm.xxmax[2]) + "};\n")
    print("Wrote to file \"CurviBoundaryConditions/xxminmax.h\"")

    # Generic coordinate NRPy+ file output, Part 3: output the conversion from Cartesian (x,y,z) to interior/OB (x0,x1,x2)
    outputC([rfm.Cart_to_xx[0], rfm.Cart_to_xx[1], rfm.Cart_to_xx[2]],
            ["Cart_to_xx0_inbounds", "Cart_to_xx1_inbounds", "Cart_to_xx2_inbounds"],
            "CurviBoundaryConditions/Cart_to_xx.h")
コード例 #2
0
def Set_up_CurviBoundaryConditions(Ccodesdir,
                                   verbose=True,
                                   Cparamspath=os.path.join("../"),
                                   enable_copy_of_static_Ccodes=True,
                                   BoundaryCondition="QuadraticExtrapolation"):
    # Step P0: Check that Ccodesdir is not the same as CurviBoundaryConditions/boundary_conditions,
    #          to prevent trusted versions of these C codes from becoming contaminated.
    if os.path.join(Ccodesdir) == os.path.join("CurviBoundaryConditions",
                                               "boundary_conditions"):
        print(
            "Error: Tried to output boundary conditions C code into CurviBoundaryConditions/boundary_conditions,"
            "       which is not allowed, to prevent trusted versions of these C codes from becoming contaminated."
        )
        sys.exit(1)

    # Step P1: Create the C codes output directory & copy static CurviBC files
    #          from CurviBoundaryConditions/boundary_conditions to Ccodesdir/
    if enable_copy_of_static_Ccodes:
        cmd.mkdir(os.path.join(Ccodesdir))

        # Choosing boundary condition drivers with in NRPy+
        #  - current options are Quadratic Polynomial Extrapolation for any coordinate system,
        #    and the Sommerfeld boundary condition for only cartesian coordinates
        if str(BoundaryCondition) == "QuadraticExtrapolation":
            for file in [
                    "apply_bcs_curvilinear.h", "BCs_data_structs.h",
                    "bcstruct_freemem.h", "CurviBC_include_Cfunctions.h",
                    "driver_bcstruct.h", "set_bcstruct.h",
                    "set_up__bc_gz_map_and_parity_condns.h"
            ]:
                shutil.copy(
                    os.path.join("CurviBoundaryConditions",
                                 "boundary_conditions", file),
                    os.path.join(Ccodesdir))

            with open(os.path.join(Ccodesdir, "CurviBC_include_Cfunctions.h"),
                      "a") as file:
                file.write("\n#include \"apply_bcs_curvilinear.h\"")

        elif str(BoundaryCondition) == "Sommerfeld":
            for file in [
                    "apply_bcs_sommerfeld.h", "BCs_data_structs.h",
                    "bcstruct_freemem.h", "CurviBC_include_Cfunctions.h",
                    "driver_bcstruct.h", "set_bcstruct.h",
                    "set_up__bc_gz_map_and_parity_condns.h"
            ]:
                shutil.copy(
                    os.path.join("CurviBoundaryConditions",
                                 "boundary_conditions", file),
                    os.path.join(Ccodesdir))

            with open(os.path.join(Ccodesdir, "CurviBC_include_Cfunctions.h"),
                      "a") as file:
                file.write("\n#include \"apply_bcs_sommerfeld.h\"")

        elif str(BoundaryCondition) == "QuadraticExtrapolation&Sommerfeld":
            for file in [
                    "apply_bcs_curvilinear.h", "apply_bcs_sommerfeld.h",
                    "BCs_data_structs.h", "bcstruct_freemem.h",
                    "CurviBC_include_Cfunctions.h", "driver_bcstruct.h",
                    "set_bcstruct.h", "set_up__bc_gz_map_and_parity_condns.h"
            ]:
                shutil.copy(
                    os.path.join("CurviBoundaryConditions",
                                 "boundary_conditions", file),
                    os.path.join(Ccodesdir))

            with open(os.path.join(Ccodesdir, "CurviBC_include_Cfunctions.h"),
                      "a") as file:
                file.write("\n#include \"apply_bcs_sommerfeld.h\"" +
                           "\n#include \"apply_bcs_curvilinear.h\"")

        else:
            print(
                "ERROR: Only Quadratic Polynomial Extrapolation (QuadraticExtrapolation) and Sommerfeld boundary conditions are currently supported\n"
            )
            sys.exit(1)

    # Step P2: Output correct #include for set_Cparameters.h to
    #          Ccodesdir/boundary_conditions/RELATIVE_PATH__set_Cparameters.h
    with open(os.path.join(Ccodesdir, "RELATIVE_PATH__set_Cparameters.h"),
              "w") as file:
        file.write(
            "#include \"" + Cparamspath + "/set_Cparameters.h\"\n"
        )  # #include's may include forward slashes for paths, even in Windows.

    # Step 0: Set up reference metric in case it hasn't already been set up.
    #         (Doing it twice hurts nothing).
    rfm.reference_metric()

    # Step 1: Set unit-vector dot products (=parity) for each of the 10 parity condition types
    parity = ixp.zerorank1(DIM=10)
    UnitVectors_inner = ixp.zerorank2()
    xx0_inbounds, xx1_inbounds, xx2_inbounds = sp.symbols(
        "xx0_inbounds xx1_inbounds xx2_inbounds", real=True)
    for i in range(3):
        for j in range(3):
            UnitVectors_inner[i][j] = rfm.UnitVectors[i][j].subs(
                rfm.xx[0],
                xx0_inbounds).subs(rfm.xx[1],
                                   xx1_inbounds).subs(rfm.xx[2], xx2_inbounds)
    # Type 0: scalar
    parity[0] = sp.sympify(1)
    # Type 1: i0-direction vector or one-form
    # Type 2: i1-direction vector or one-form
    # Type 3: i2-direction vector or one-form
    for i in range(3):
        for Type in range(1, 4):
            parity[Type] += rfm.UnitVectors[Type -
                                            1][i] * UnitVectors_inner[Type -
                                                                      1][i]
    # Type 4: i0i0-direction rank-2 tensor
    # parity[4] = parity[1]*parity[1]
    # Type 5: i0i1-direction rank-2 tensor
    # Type 6: i0i2-direction rank-2 tensor
    # Type 7: i1i1-direction rank-2 tensor
    # Type 8: i1i2-direction rank-2 tensor
    # Type 9: i2i2-direction rank-2 tensor
    count = 4
    for i in range(3):
        for j in range(i, 3):
            parity[count] = parity[i + 1] * parity[j + 1]
            count = count + 1

    lhs_strings = []
    for i in range(10):
        lhs_strings.append("parity[" + str(i) + "]")
    outputC(
        parity, lhs_strings,
        os.path.join(Ccodesdir, "parity_conditions_symbolic_dot_products.h"))

    # Step 2.a: Generate Ccodesdir/gridfunction_defines.h file,
    #       containing human-readable gridfunction aliases
    evolved_variables_list, auxiliary_variables_list, auxevol_variables_list = gri.output__gridfunction_defines_h__return_gf_lists(
        Ccodesdir)

    # Step 2.b: set the parity conditions on all gridfunctions in gf_list,
    #       based on how many digits are at the end of their names
    def set_parity_types(list_of_gf_names):
        parity_type = []
        for name in list_of_gf_names:
            for gf in gri.glb_gridfcs_list:
                if gf.name == name:
                    parity_type__orig_len = len(parity_type)
                    if gf.DIM < 3 or gf.DIM > 4:
                        print(
                            "Error: Cannot currently specify parity conditions on gridfunctions with DIM<3 or >4."
                        )
                        sys.exit(1)
                    if gf.rank == 0:
                        parity_type.append(0)
                    elif gf.rank == 1:
                        if gf.DIM == 3:
                            parity_type.append(
                                int(gf.name[-1]) + 1
                            )  # = 1 for e.g., beta^0; = 2 for e.g., beta^1, etc.
                        elif gf.DIM == 4:
                            parity_type.append(
                                int(gf.name[-1])
                            )  # = 0 for e.g., b4^0; = 1 for e.g., beta^1, etc.
                    elif gf.rank == 2:
                        if gf.DIM == 3:
                            # element of a list; a[-2] the
                            # second-to-last element, etc.
                            idx0 = gf.name[-2]
                            idx1 = gf.name[-1]
                            if idx0 == "0" and idx1 == "0":
                                parity_type.append(4)
                            elif (idx0 == "0"
                                  and idx1 == "1") or (idx0 == "1"
                                                       and idx1 == "0"):
                                parity_type.append(5)
                            elif (idx0 == "0"
                                  and idx1 == "2") or (idx0 == "2"
                                                       and idx1 == "0"):
                                parity_type.append(6)
                            elif idx0 == "1" and idx1 == "1":
                                parity_type.append(7)
                            elif (idx0 == "1"
                                  and idx1 == "2") or (idx0 == "2"
                                                       and idx1 == "1"):
                                parity_type.append(8)
                            elif idx0 == "2" and idx1 == "2":
                                parity_type.append(9)
                        elif gf.DIM == 4:
                            idx0 = gf.name[-2]
                            idx1 = gf.name[-1]
                            # g4DD00 = g_{tt} : parity type = 0
                            # g4DD01 = g_{tx} : parity type = 1
                            # g4DD02 = g_{ty} : parity type = 2
                            # g4DD0a = g_{ta} : parity type = a
                            if idx0 == "0":
                                parity_type.append(int(idx1))
                            elif idx1 == "0":
                                parity_type.append(int(idx0))
                            if idx0 == "1" and idx1 == "1":
                                parity_type.append(4)
                            elif (idx0 == "1"
                                  and idx1 == "2") or (idx0 == "2"
                                                       and idx1 == "1"):
                                parity_type.append(5)
                            elif (idx0 == "1"
                                  and idx1 == "3") or (idx0 == "3"
                                                       and idx1 == "1"):
                                parity_type.append(6)
                            elif idx0 == "2" and idx1 == "2":
                                parity_type.append(7)
                            elif (idx0 == "2"
                                  and idx1 == "3") or (idx0 == "3"
                                                       and idx1 == "2"):
                                parity_type.append(8)
                            elif idx0 == "3" and idx1 == "3":
                                parity_type.append(9)
                    if len(parity_type) == parity_type__orig_len:
                        print(
                            "Error: Could not figure out parity type for " +
                            gf.gftype + " gridfunction: " + gf.name, gf.DIM,
                            gf.name[-2], gf.name[-1], gf.rank)
                        sys.exit(1)
        if len(parity_type) != len(list_of_gf_names):
            print(
                "Error: For some reason the length of the parity types list did not match the length of the gf list."
            )
            sys.exit(1)
        return parity_type

    evol_parity_type = set_parity_types(evolved_variables_list)
    aux_parity_type = set_parity_types(auxiliary_variables_list)
    auxevol_parity_type = set_parity_types(auxevol_variables_list)

    # Step 2.c: Output all gridfunctions to Ccodesdir+"/gridfunction_defines.h"
    # ... then append to the file the parity type for each gridfunction.
    with open(os.path.join(Ccodesdir, "gridfunction_defines.h"), "a") as file:
        file.write("\n\n/* PARITY TYPES FOR ALL GRIDFUNCTIONS.\n")
        file.write(
            "   SEE \"Tutorial-Start_to_Finish-Curvilinear_BCs.ipynb\" FOR DEFINITIONS. */\n"
        )
        if len(evolved_variables_list) > 0:
            file.write("const int8_t evol_gf_parity[" +
                       str(len(evolved_variables_list)) + "] = { ")
            for i in range(len(evolved_variables_list) - 1):
                file.write(str(evol_parity_type[i]) + ", ")
            file.write(
                str(evol_parity_type[len(evolved_variables_list) - 1]) +
                " };\n")

        if len(auxiliary_variables_list) > 0:
            file.write("const int8_t aux_gf_parity[" +
                       str(len(auxiliary_variables_list)) + "] = { ")
            for i in range(len(auxiliary_variables_list) - 1):
                file.write(str(aux_parity_type[i]) + ", ")
            file.write(
                str(aux_parity_type[len(auxiliary_variables_list) - 1]) +
                " };\n")

        if len(auxevol_variables_list) > 0:
            file.write("const int8_t auxevol_gf_parity[" +
                       str(len(auxevol_variables_list)) + "] = { ")
            for i in range(len(auxevol_variables_list) - 1):
                file.write(str(auxevol_parity_type[i]) + ", ")
            file.write(
                str(auxevol_parity_type[len(auxevol_variables_list) - 1]) +
                " };\n")

    if verbose == True:
        import textwrap
        wrapper = textwrap.TextWrapper(initial_indent="",
                                       subsequent_indent="    ",
                                       width=75)

        def print_parity_list(gf_type, variable_names, parity_types):
            outstr = ""
            if len(variable_names) != 0:
                outstr += gf_type + " parity: ( "
                for i in range(len(variable_names)):
                    outstr += variable_names[i] + ":" + str(parity_types[i])
                    if i != len(variable_names) - 1:
                        outstr += ", "
                outstr += " )"
            print(wrapper.fill(outstr))

        print_parity_list("Evolved", evolved_variables_list, evol_parity_type)
        print_parity_list("Auxiliary", auxiliary_variables_list,
                          aux_parity_type)
        print_parity_list("AuxEvol", auxevol_variables_list,
                          auxevol_parity_type)

    # Step 3: Find the Eigen-Coordinate and set up the Eigen-Coordinate's reference metric:
    CoordSystem_orig = par.parval_from_str("reference_metric::CoordSystem")
    par.set_parval_from_str("reference_metric::CoordSystem",
                            rfm.get_EigenCoord())
    rfm.reference_metric()

    # Step 4: Output C code for the Eigen-Coordinate mapping from xx->Cartesian:
    rfm.xxCart_h("EigenCoord_xxCart",
                 os.path.join(Cparamspath, "set_Cparameters.h"),
                 os.path.join(Ccodesdir, "EigenCoord_xxCart.h"))

    # Step 5: Output the Eigen-Coordinate mapping from Cartesian->xx:
    # Step 5.a: Sanity check: First make sure that rfm.Cart_to_xx has been set. Error out if not!
    if rfm.Cart_to_xx[0] == 0 or rfm.Cart_to_xx[1] == 0 or rfm.Cart_to_xx[
            2] == 0:
        print(
            "ERROR: rfm.Cart_to_xx[], which maps Cartesian -> xx, has not been set for"
        )
        print("       reference_metric::CoordSystem = " +
              par.parval_from_str("reference_metric::CoordSystem"))
        print(
            "       Boundary conditions in curvilinear coordinates REQUIRE this be set."
        )
        sys.exit(1)
    # Step 5.b: Output C code for the Eigen-Coordinate mapping from Cartesian->xx:
    outputC([rfm.Cart_to_xx[0], rfm.Cart_to_xx[1], rfm.Cart_to_xx[2]], [
        "Cart_to_xx0_inbounds", "Cart_to_xx1_inbounds", "Cart_to_xx2_inbounds"
    ], os.path.join(Ccodesdir, "EigenCoord_Cart_to_xx.h"))

    # Step 6: Restore reference_metric::CoordSystem back to the original CoordSystem
    par.set_parval_from_str("reference_metric::CoordSystem", CoordSystem_orig)
    rfm.reference_metric()
コード例 #3
0
def Set_up_CurviBoundaryConditions(outdir="CurviBoundaryConditions/",
                                   verbose=True):
    # Step 0: Set up reference metric in case it hasn't already been set up.
    #         (Doing it twice hurts nothing).
    rfm.reference_metric()

    # Step 1: Set unit-vector dot products (=parity) for each of the 10 parity condition types
    parity = ixp.zerorank1(DIM=10)
    UnitVectors_inner = ixp.zerorank2()
    xx0_inbounds, xx1_inbounds, xx2_inbounds = sp.symbols(
        "xx0_inbounds xx1_inbounds xx2_inbounds", real=True)
    for i in range(3):
        for j in range(3):
            UnitVectors_inner[i][j] = rfm.UnitVectors[i][j].subs(
                rfm.xx[0],
                xx0_inbounds).subs(rfm.xx[1],
                                   xx1_inbounds).subs(rfm.xx[2], xx2_inbounds)
    # Type 0: scalar
    parity[0] = sp.sympify(1)
    # Type 1: i0-direction vector or one-form
    # Type 2: i1-direction vector or one-form
    # Type 3: i2-direction vector or one-form
    for i in range(3):
        for Type in range(1, 4):
            parity[Type] += rfm.UnitVectors[Type -
                                            1][i] * UnitVectors_inner[Type -
                                                                      1][i]
    # Type 4: i0i0-direction rank-2 tensor
    # parity[4] = parity[1]*parity[1]
    # Type 5: i0i1-direction rank-2 tensor
    # Type 6: i0i2-direction rank-2 tensor
    # Type 7: i1i1-direction rank-2 tensor
    # Type 8: i1i2-direction rank-2 tensor
    # Type 9: i2i2-direction rank-2 tensor
    count = 4
    for i in range(3):
        for j in range(i, 3):
            parity[count] = parity[i + 1] * parity[j + 1]
            count = count + 1

    lhs_strings = []
    for i in range(10):
        lhs_strings.append("parity[" + str(i) + "]")
    outputC(parity, lhs_strings,
            outdir + "parity_conditions_symbolic_dot_products.h")

    # Step 2.a: Generate outdir+gridfunction_defines.h file,
    #       containing human-readable gridfunction aliases
    evolved_variables_list, auxiliary_variables_list, auxevol_variables_list = gri.output__gridfunction_defines_h__return_gf_lists(
        outdir)

    # Step 2.b: set the parity conditions on all gridfunctions in gf_list,
    #       based on how many digits are at the end of their names
    def set_parity_types(list_of_gf_names):
        parity_type = []
        for name in list_of_gf_names:
            for gf in gri.glb_gridfcs_list:
                if gf.name == name:
                    parity_type__orig_len = len(parity_type)
                    if gf.DIM < 3 or gf.DIM > 4:
                        print(
                            "Error: Cannot currently specify parity conditions on gridfunctions with DIM<3 or >4."
                        )
                        sys.exit(1)
                    if gf.rank == 0:
                        parity_type.append(0)
                    elif gf.rank == 1:
                        if gf.DIM == 3:
                            parity_type.append(
                                int(gf.name[-1]) + 1
                            )  # = 1 for e.g., beta^0; = 2 for e.g., beta^1, etc.
                        elif gf.DIM == 4:
                            parity_type.append(
                                int(gf.name[-1])
                            )  # = 0 for e.g., b4^0; = 1 for e.g., beta^1, etc.
                    elif gf.rank == 2:
                        if gf.DIM == 3:
                            # element of a list; a[-2] the
                            # second-to-last element, etc.
                            idx0 = gf.name[-2]
                            idx1 = gf.name[-1]
                            if idx0 == "0" and idx1 == "0":
                                parity_type.append(4)
                            elif (idx0 == "0"
                                  and idx1 == "1") or (idx0 == "1"
                                                       and idx1 == "0"):
                                parity_type.append(5)
                            elif (idx0 == "0"
                                  and idx1 == "2") or (idx0 == "2"
                                                       and idx1 == "0"):
                                parity_type.append(6)
                            elif idx0 == "1" and idx1 == "1":
                                parity_type.append(7)
                            elif (idx0 == "1"
                                  and idx1 == "2") or (idx0 == "2"
                                                       and idx1 == "1"):
                                parity_type.append(8)
                            elif idx0 == "2" and idx1 == "2":
                                parity_type.append(9)
                        elif gf.DIM == 4:
                            idx0 = gf.name[-2]
                            idx1 = gf.name[-1]
                            # g4DD00 = g_{tt} : parity type = 0
                            # g4DD01 = g_{tx} : parity type = 1
                            # g4DD02 = g_{ty} : parity type = 2
                            # g4DD0a = g_{ta} : parity type = a
                            if idx0 == "0":
                                parity_type.append(int(idx1))
                            elif idx1 == "0":
                                parity_type.append(int(idx0))
                            if idx0 == "1" and idx1 == "1":
                                parity_type.append(4)
                            elif (idx0 == "1"
                                  and idx1 == "2") or (idx0 == "2"
                                                       and idx1 == "1"):
                                parity_type.append(5)
                            elif (idx0 == "1"
                                  and idx1 == "3") or (idx0 == "3"
                                                       and idx1 == "1"):
                                parity_type.append(6)
                            elif idx0 == "2" and idx1 == "2":
                                parity_type.append(7)
                            elif (idx0 == "2"
                                  and idx1 == "3") or (idx0 == "3"
                                                       and idx1 == "2"):
                                parity_type.append(8)
                            elif idx0 == "3" and idx1 == "3":
                                parity_type.append(9)
                    if len(parity_type) == parity_type__orig_len:
                        print(
                            "Error: Could not figure out parity type for " +
                            gf.gftype + " gridfunction: " + gf.name, gf.DIM,
                            gf.name[-2], gf.name[-1], gf.rank)
                        sys.exit(1)
        if len(parity_type) != len(list_of_gf_names):
            print(
                "Error: For some reason the length of the parity types list did not match the length of the gf list."
            )
            sys.exit(1)
        return parity_type

    evol_parity_type = set_parity_types(evolved_variables_list)
    aux_parity_type = set_parity_types(auxiliary_variables_list)
    auxevol_parity_type = set_parity_types(auxevol_variables_list)

    # Step 2.c: Output all gridfunctions to outdir+"/gridfunction_defines.h"
    # ... then append to the file the parity type for each gridfunction.
    with open(outdir + "/gridfunction_defines.h", "a") as file:
        file.write("\n\n/* PARITY TYPES FOR ALL GRIDFUNCTIONS.\n")
        file.write(
            "   SEE \"Tutorial-Start_to_Finish-Curvilinear_BCs.ipynb\" FOR DEFINITIONS. */\n"
        )
        if len(evolved_variables_list) > 0:
            file.write("const int8_t evol_gf_parity[" +
                       str(len(evolved_variables_list)) + "] = { ")
            for i in range(len(evolved_variables_list) - 1):
                file.write(str(evol_parity_type[i]) + ", ")
            file.write(
                str(evol_parity_type[len(evolved_variables_list) - 1]) +
                " };\n")

        if len(auxiliary_variables_list) > 0:
            file.write("const int8_t aux_gf_parity[" +
                       str(len(auxiliary_variables_list)) + "] = { ")
            for i in range(len(auxiliary_variables_list) - 1):
                file.write(str(aux_parity_type[i]) + ", ")
            file.write(
                str(aux_parity_type[len(auxiliary_variables_list) - 1]) +
                " };\n")

        if len(auxevol_variables_list) > 0:
            file.write("const int8_t auxevol_gf_parity[" +
                       str(len(auxevol_variables_list)) + "] = { ")
            for i in range(len(auxevol_variables_list) - 1):
                file.write(str(auxevol_parity_type[i]) + ", ")
            file.write(
                str(auxevol_parity_type[len(auxevol_variables_list) - 1]) +
                " };\n")

    if verbose == True:
        for i in range(len(evolved_variables_list)):
            print("Evolved gridfunction \"" + evolved_variables_list[i] +
                  "\" has parity type " + str(evol_parity_type[i]) + ".")
        for i in range(len(auxiliary_variables_list)):
            print("Auxiliary gridfunction \"" + auxiliary_variables_list[i] +
                  "\" has parity type " + str(aux_parity_type[i]) + ".")
        for i in range(len(auxevol_variables_list)):
            print("AuxEvol gridfunction \"" + auxevol_variables_list[i] +
                  "\" has parity type " + str(auxevol_parity_type[i]) + ".")

    # Step 3: Find the Eigen-Coordinate and set up the Eigen-Coordinate's reference metric:
    CoordSystem_orig = par.parval_from_str("reference_metric::CoordSystem")
    par.set_parval_from_str("reference_metric::CoordSystem",
                            rfm.get_EigenCoord())
    rfm.reference_metric()

    # Step 4: Output C code for the Eigen-Coordinate mapping from xx->Cartesian:
    rfm.xxCart_h("EigenCoord_xxCart", "../set_Cparameters.h",
                 outdir + "EigenCoord_xxCart.h")

    # Step 5: Output the Eigen-Coordinate mapping from Cartesian->xx:
    # Step 5.a: Sanity check: First make sure that rfm.Cart_to_xx has been set. Error out if not!
    if rfm.Cart_to_xx[0] == 0 or rfm.Cart_to_xx[1] == 0 or rfm.Cart_to_xx[
            2] == 0:
        print(
            "ERROR: rfm.Cart_to_xx[], which maps Cartesian -> xx, has not been set for"
        )
        print("       reference_metric::CoordSystem = " +
              par.parval_from_str("reference_metric::CoordSystem"))
        print(
            "       Boundary conditions in curvilinear coordinates REQUIRE this be set."
        )
        sys.exit(1)
    # Step 5.b: Output C code for the Eigen-Coordinate mapping from Cartesian->xx:
    outputC([rfm.Cart_to_xx[0], rfm.Cart_to_xx[1], rfm.Cart_to_xx[2]], [
        "Cart_to_xx0_inbounds", "Cart_to_xx1_inbounds", "Cart_to_xx2_inbounds"
    ], outdir + "EigenCoord_Cart_to_xx.h")

    # Step 6: Restore reference_metric::CoordSystem back to the original CoordSystem
    par.set_parval_from_str("reference_metric::CoordSystem", CoordSystem_orig)
    rfm.reference_metric()