def Ricci__generate_Ccode(Ricci_exprs_list): Ricci_SymbExpressions = Ricci_exprs_list[0] print("Generating C code for Ricci tensor (FD_order="+str(FD_order)+") in "+par.parval_from_str("reference_metric::CoordSystem")+" coordinates.") start = time.time() # Store original finite-differencing order: FD_order_orig = par.parval_from_str("finite_difference::FD_CENTDERIVS_ORDER") # Set new finite-differencing order: par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", FD_order) Ricci_string = fin.FD_outputC("returnstring", Ricci_SymbExpressions, params="outCverbose=False,SIMD_enable=True,GoldenKernelsEnable=True") filename = "BSSN_Ricci_FD_order_"+str(FD_order)+".h" with open(os.path.join(outdir, filename), "w") as file: file.write(lp.loop(["i2","i1","i0"],["cctk_nghostzones[2]","cctk_nghostzones[1]","cctk_nghostzones[0]"], ["cctk_lsh[2]-cctk_nghostzones[2]","cctk_lsh[1]-cctk_nghostzones[1]","cctk_lsh[0]-cctk_nghostzones[0]"], ["1","1","SIMD_width"], ["#pragma omp parallel for", "#include \"rfm_files/rfm_struct__SIMD_outer_read2.h\"", r""" #include "rfm_files/rfm_struct__SIMD_outer_read1.h" #if (defined __INTEL_COMPILER && __INTEL_COMPILER_BUILD_DATE >= 20180804) #pragma ivdep // Forces Intel compiler (if Intel compiler used) to ignore certain SIMD vector dependencies #pragma vector always // Forces Intel compiler (if Intel compiler used) to vectorize #endif"""],"", "#include \"rfm_files/rfm_struct__SIMD_inner_read0.h\"\n"+Ricci_string)) # Restore original finite-differencing order: par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", FD_order_orig) end = time.time() print("(BENCH) Finished Ricci C codegen (FD_order="+str(FD_order)+") in " + str(end - start) + " seconds.")
def cf_from_gammaDD(gammaDD): global cf 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) gammaUU, gammaDET = ixp.symm_matrix_inverter3x3(gammaDD) cf = sp.sympify(0) if par.parval_from_str("EvolvedConformalFactor_cf") == "phi": # phi = \frac{1}{12} log(\frac{gamma}{\bar{gamma}}). cf = sp.Rational(1, 12) * sp.log(gammaDET / gammabarDET) elif par.parval_from_str("EvolvedConformalFactor_cf") == "chi": # chi = exp(-4*phi) = exp(-4*\frac{1}{12}*(\frac{gamma}{\bar{gamma}})) # = exp(-\frac{1}{3}*log(\frac{gamma}{\bar{gamma}})) = (\frac{gamma}{\bar{gamma}})^{-1/3}. # cf = (gammaDET / gammabarDET)**(-sp.Rational(1, 3)) elif par.parval_from_str("EvolvedConformalFactor_cf") == "W": # W = exp(-2*phi) = exp(-2*\frac{1}{12}*log(\frac{gamma}{\bar{gamma}})) # = exp(-\frac{1}{6}*log(\frac{gamma}{\bar{gamma}})) = (\frac{gamma}{bar{gamma}})^{-1/6}. cf = (gammaDET / gammabarDET)**(-sp.Rational(1, 6)) else: print("Error EvolvedConformalFactor_cf type = \"" + par.parval_from_str("EvolvedConformalFactor_cf") + "\" unknown.") sys.exit(1)
def EigenCoord_Cart_to_xx(): # Step 1: 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 2: Output the Eigen-Coordinate mapping from Cartesian->xx: # Step 2.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 2.b: Output C code for the Eigen-Coordinate mapping from Cartesian->xx: outstr = """ // Cart_to_xx for EigenCoordinate """+rfm.get_EigenCoord()+r""" (orig coord = """+CoordSystem_orig+");\n" outstr += 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"], filename="returnstring", params="preindent=2") # Step 3: Restore reference_metric::CoordSystem back to the original CoordSystem par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem_orig) rfm.reference_metric() # Step 4: Return EigenCoord Cart_to_xx C code return outstr
def MaxwellCartesian_ID(): DIM = par.parval_from_str("grid::DIM") x, y, z = gri.register_gridfunctions("AUX", ["x", "y", "z"]) gammaDD = ixp.register_gridfunctions_for_single_rank2("AUX","gammaDD", "sym01") # The AUX or EVOL designation is *not* # used in diagnostic modules. # Step 1: Declare free parameters intrinsic to these initial data amp,lam = par.Cparameters("REAL",__name__,["amp","lam"], [1.0,1.0]) # __name__ = "MaxwellCartesian_ID", this module's name # Step 2: Set the initial data system = par.parval_from_str("System_to_use") if system == "System_I" or system == "System_II": global AidD,EidD,psi_ID AidD = ixp.zerorank1() EidD = ixp.zerorank1() EidU = ixp.zerorank1() # Set the coordinate transformations: radial = sp.sqrt(x*x + y*y + z*z) polar = sp.atan2(sp.sqrt(x*x + y*y),z) EU_phi = 8*amp*radial*sp.sin(polar)*lam*lam*sp.exp(-lam*radial*radial) EidU[0] = -(y * EU_phi)/sp.sqrt(x*x + y*y) EidU[1] = (x * EU_phi)/sp.sqrt(x*x + y*y) # The z component (2)is zero. for i in range(DIM): for j in range(DIM): EidD[i] += gammaDD[i][j] * EidU[j] psi_ID = sp.sympify(0) if system == "System_II": global Gamma_ID Gamma_ID = sp.sympify(0) else: print("Invalid choice of system: System_to_use must be either System_I or System_II")
def gfaccess(gfarrayname="", varname="", ijklstring=""): found_registered_gf = False for gf in glb_gridfcs_list: if gf.name == varname: if found_registered_gf: print("Error: found duplicate gridfunction name: " + gf.name) sys.exit(1) found_registered_gf = True if not found_registered_gf: print("Error: gridfunction \"" + varname + "\" is not registered!") sys.exit(1) gftype = find_gftype(varname) DIM = par.parval_from_str("DIM") retstring = "" if par.parval_from_str("GridFuncMemAccess") == "SENRlike": if gfarrayname == "": print( "Error: GridFuncMemAccess = SENRlike requires gfarrayname be passed to gfaccess()" ) sys.exit(1) # FIXME: if gftype == "AUX" then override gfarrayname to aux_gfs[]. # This enables expressions containing a mixture of AUX and EVOL # gridfunctions, though in a slightly hacky way. if gftype == "AUX": gfarrayname = "aux_gfs" elif gftype == "AUXEVOL": gfarrayname = "auxevol_gfs" # Return gfarrayname[IDX3(varname,i0)] for DIM=1, gfarrayname[IDX3(varname,i0,i1)] for DIM=2, etc. retstring += gfarrayname + "[IDX" + str( DIM + 1) + "S(" + varname.upper() + "GF" + ", " elif par.parval_from_str("GridFuncMemAccess") == "ETK": # Return varname[CCTK_GFINDEX3D(i0,i1,i2)] for DIM=3. Error otherwise if DIM != 3: print( "Error: GridFuncMemAccess = ETK currently requires that gridfunctions be 3D. Can be easily extended." ) sys.exit(1) if gfarrayname == "rhs_gfs": retstring += varname + "_rhsGF" + "[CCTK_GFINDEX" + str( DIM) + "D(cctkGH, " else: retstring += varname + "GF" + "[CCTK_GFINDEX" + str( DIM) + "D(cctkGH, " else: print("grid::GridFuncMemAccess = " + par.parval_from_str("GridFuncMemAccess") + " not supported") sys.exit(1) if ijklstring == "": for i in range(DIM): retstring += "i" + str(i) if i != DIM - 1: retstring += ', ' else: retstring += ijklstring return retstring + ")]"
def unique_idx(idx4): # os and sz are set *just for the purposes of ensuring indices are ordered in memory* # Do not modify the values of os and sz. os = 50 # offset sz = 100 # assumed size in each direction if par.parval_from_str("MemAllocStyle") == "210": return str(int(idx4[0])+os + sz*( (int(idx4[1])+os) + sz*( (int(idx4[2])+os) + sz*( int(idx4[3])+os ) ) )) if par.parval_from_str("MemAllocStyle") == "012": return str(int(idx4[3])+os + sz*( (int(idx4[2])+os) + sz*( (int(idx4[1])+os) + sz*( int(idx4[0])+os ) ) )) print("Error: MemAllocStyle = "+par.parval_from_str("MemAllocStyle")+" unsupported.") sys.exit(1)
def detgammabar_and_derivs(): # Step 5.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 detgammabar,detgammabar_dD,detgammabar_dDD hDD, aDD, lambdaU, vetU, betU, trK, cf, alpha = declare_BSSN_gridfunctions_if_not_declared_already() DIM = 3 detgbarOverdetghat = sp.sympify(1) detgbarOverdetghat_dD = ixp.zerorank1() detgbarOverdetghat_dDD = ixp.zerorank2() if par.parval_from_str(thismodule + "::detgbarOverdetghat_equals_one") == "False": print("Error: detgbarOverdetghat_equals_one=\"False\" is not fully implemented yet.") exit(1) ## Approach for implementing detgbarOverdetghat_equals_one=False: # detgbarOverdetghat = gri.register_gridfunctions("AUX", ["detgbarOverdetghat"]) # detgbarOverdetghatInitial = gri.register_gridfunctions("AUX", ["detgbarOverdetghatInitial"]) # detgbarOverdetghat_dD = ixp.declarerank1("detgbarOverdetghat_dD") # detgbarOverdetghat_dDD = ixp.declarerank2("detgbarOverdetghat_dDD", "sym01") # Step 8d: Define detgammabar, detgammabar_dD, and detgammabar_dDD (needed for \partial_t \bar{\Lambda}^i below) detgammabar = detgbarOverdetghat * rfm.detgammahat detgammabar_dD = ixp.zerorank1() for i in range(DIM): detgammabar_dD[i] = detgbarOverdetghat_dD[i] * rfm.detgammahat + detgbarOverdetghat * rfm.detgammahatdD[i] detgammabar_dDD = ixp.zerorank2() for i in range(DIM): for j in range(DIM): detgammabar_dDD[i][j] = detgbarOverdetghat_dDD[i][j] * rfm.detgammahat + \ detgbarOverdetghat_dD[i] * rfm.detgammahatdD[j] + \ detgbarOverdetghat_dD[j] * rfm.detgammahatdD[i] + \ detgbarOverdetghat * rfm.detgammahatdDD[i][j]
def register_gridfunctions_for_single_rank2(gf_type, gf_basename, symmetry_option, DIM=-1): # Step 0: Verify the gridfunction basename is valid: gri.verify_gridfunction_basename_is_valid(gf_basename) # Step 1: Declare a list of lists of SymPy variables, # where IDX_OBJ_TMP[i][j] = gf_basename+str(i)+str(j) IDX_OBJ_TMP = declarerank2(gf_basename, symmetry_option, DIM) # Step 2: register each gridfunction, being careful not # not to store duplicates due to rank-2 symmetries. if DIM == -1: DIM = par.parval_from_str("DIM") # Register only unique gridfunctions. Otherwise # rank-2 symmetries might result in duplicates gf_list = [] for i in range(DIM): for j in range(DIM): save = True for l in range(len(gf_list)): if gf_list[l] == str(IDX_OBJ_TMP[i][j]): save = False if save == True: gf_list.append(str(IDX_OBJ_TMP[i][j])) gri.register_gridfunctions(gf_type, gf_list, rank=2, is_indexed=True, DIM=DIM) # Step 3: Return array of SymPy variables return IDX_OBJ_TMP
def out__type_var(in_var, AddPrefix_for_UpDownWindVars=True): varname = str(in_var) # Disable prefixing upwinded and downwinded variables # if the upwind control vector algorithm is disabled. if upwindcontrolvec == "": AddPrefix_for_UpDownWindVars = False if AddPrefix_for_UpDownWindVars: if "_dupD" in varname: # Variables suffixed with "_dupD" are set # to be the "pure" upwinded derivative, # before the upwinding algorithm has been # applied. However, when they are used # in the RHS expressions, it is assumed # that the up. algorithm has been applied. # To ensure consistency we rename all # _dupD suffixed variables as # _dupDPUREUPWIND, and use them as input # into the upwinding algorithm. The output # will be the original _dupD variable. varname = "UpwindAlgInput" + varname if "_ddnD" in varname: # For consistency with _dupD varname = "UpwindAlgInput" + varname if outCparams.SIMD_enable == "True": return "const REAL_SIMD_ARRAY " + varname else: TYPE = par.parval_from_str("PRECISION") return "const " + TYPE + " " + varname
def __init__(self, fd_order=2, vars_at_inf_default = 0., vars_radial_falloff_power_default = 3., vars_speed_default = 1.): evolved_variables_list, _, _ = gri.gridfunction_lists() # set class finite differencing order self.fd_order = fd_order NRPy_FD_order = par.parval_from_str("finite_difference::FD_CENTDERIVS_ORDER") if NRPy_FD_order < fd_order: print("ERROR: The global central finite differencing order within NRPy+ must be greater than or equal to the Sommerfeld boundary condition's finite differencing order") sys.exit(1) # Define class dictionaries to store sommerfeld parameters for each EVOL gridfunction # EVOL gridfunction asymptotic value at infinity self.vars_at_infinity = {} # EVOL gridfunction wave speed at outer boundaries self.vars_speed = {} # EVOL gridfunction radial falloff power self.vars_radial_falloff_power = {} # Set default values for each specific EVOL gridfunction for gf in evolved_variables_list: self.vars_at_infinity[gf.upper() + 'GF'] = vars_at_inf_default self.vars_radial_falloff_power[gf.upper() + 'GF'] = vars_radial_falloff_power_default self.vars_speed[gf.upper() + 'GF'] = vars_speed_default
def ccode_postproc(string): PRECISION = par.parval_from_str("PRECISION") # In the C math library, e.g., pow(x,y) assumes x and y are doubles, and returns a double. # If x and y are floats, then for consistency should use powf(x,y) instead. # Similarly, in the case of x and y being long doubles, should use powl(x,y) for consistency. # First we find the appropriate suffix depending on the desired precision: cmathsuffix = "" if PRECISION == "double": pass elif PRECISION == "long double": cmathsuffix = "l" elif PRECISION == "float": cmathsuffix = "f" else: print("Error: "+__name__+"::PRECISION = \""+ PRECISION +"\" not supported") sys.exit(1) # ... then we append the above suffix to standard C math library functions: for func in ['pow', 'sqrt', 'sin', 'cos', 'tan', 'sinh', 'cosh', 'tanh', 'exp', 'log', 'fabs']: string2 = re.sub(func+r'\(', func + cmathsuffix+"(", string); string = string2 # Finally, SymPy prefers to output Rationals as long-double fractions. # E.g., Rational(1,3) is output as 1.0L/3.0L. # The Intel compiler vectorizer complains miserably about this, # and strictly speaking it is useless when we're in double precision. # So here we get rid of the "L" suffix on floating point numbers: if PRECISION!="long double": string2 = re.sub(r'([0-9.]+)L/([0-9.]+)L', '(\\1 / \\2)', string); string = string2 return string
def ScalarWave_RHSs(): # Step 1: Get the spatial dimension, defined in the # NRPy+ "grid" module. DIM = par.parval_from_str("DIM") # Step 2: Register gridfunctions that are needed as input # to the scalar wave RHS expressions. uu, vv = gri.register_gridfunctions("EVOL", ["uu", "vv"]) # Step 3: Declare the rank-2 indexed expression \partial_{ij} u, # which is symmetric about interchange of indices i and j # Derivative variables like these must have an underscore # in them, so the finite difference module can parse the # variable name properly. uu_dDD = ixp.declarerank2("uu_dDD", "sym01") # Step 4: Specify RHSs as global variables, # to enable access outside this # function (e.g., for C code output) global uu_rhs, vv_rhs # Step 5: Define right-hand sides for the evolution. uu_rhs = vv vv_rhs = 0 for i in range(DIM): vv_rhs += wavespeed * wavespeed * uu_dDD[i][i]
def declarerank4(objname, symmetry_option, DIM=-1): if DIM == -1: DIM = par.parval_from_str("DIM") IDX_OBJ_TMP = [[[[ sp.sympify(objname + str(i) + str(j) + str(k) + str(l)) for l in range(DIM) ] for k in range(DIM)] for j in range(DIM)] for i in range(DIM)] for i in range(DIM): for j in range(DIM): for k in range(DIM): for l in range(DIM): IDX_OBJ_TMP[i][j][k][l] = sp.sympify(objname + str(i) + str(j) + str(k) + str(l)) if symmetry_option in ('sym01', 'sym01_sym23'): if (j < i): IDX_OBJ_TMP[i][j][k][l] = IDX_OBJ_TMP[j][i][k][l] if symmetry_option == "sym12": if (k < j): IDX_OBJ_TMP[i][j][k][l] = IDX_OBJ_TMP[i][k][j][l] if symmetry_option in ('sym23', 'sym01_sym23'): if (l < k): IDX_OBJ_TMP[i][j][k][l] = IDX_OBJ_TMP[i][j][l][k] if symmetry_option not in ('sym01', 'sym23', 'sym01_sym23', 'nosym'): print("Error: symmetry option " + symmetry_option + " unsupported.") sys.exit(1) return apply_symmetry_condition_to_derivatives(IDX_OBJ_TMP)
def outputC_register_C_functions_and_NRPy_basic_defines(): # First register C functions needed by outputC # Then set up the dictionary entry for outputC in NRPy_basic_defines Nbd_str = r""" #include "stdio.h" #include "stdlib.h" #include "math.h" #include "string.h" // "string.h Needed for strncmp, etc. #include "stdint.h" // "stdint.h" Needed for Windows GCC 6.x compatibility, and for int8_t #ifndef M_PI #define M_PI 3.141592653589793238462643383279502884L #endif #ifndef M_SQRT1_2 #define M_SQRT1_2 0.707106781186547524400844362104849039L #endif #ifndef MIN #define MIN(A, B) ( ((A) < (B)) ? (A) : (B) ) #endif #ifndef MAX #define MAX(A, B) ( ((A) > (B)) ? (A) : (B) ) #endif #ifdef __cplusplus #define restrict __restrict__ #endif """ Nbd_str += "#define REAL " + par.parval_from_str("outputC::PRECISION") + "\n" outC_NRPy_basic_defines_h_dict["outputC"] = Nbd_str
def SphericalGaussian(CoordSystem="Cartesian", default_time=0, default_sigma=3): # Step 1: Set parameters for the wave DIM = par.parval_from_str("grid::DIM") # Step 2: Set up Cartesian coordinates in terms of the native CoordSystem we have chosen. # E.g., if CoordSystem="Cartesian", then xxCart = [xx[0],xx[1],xx[2]] # or if CoordSystem="Spherical", then xxCart = [xx[0]*sp.sin(xx[1])*sp.cos(xx[2]), # xx[0]*sp.sin(xx[1])*sp.sin(xx[2]), # xx[0]*sp.cos(xx[1])] par.set_parval_from_str("reference_metric::CoordSystem", CoordSystem) rfm.reference_metric() # Must call this function to specify rfm.xxCart xxCart = rfm.xxCart # Step 3: Declare free parameters intrinsic to these initial data time = par.Cparameters("REAL", thismodule, "time", default_time) sigma = par.Cparameters("REAL", thismodule, "sigma", default_sigma) # Step 4: Compute r r = sp.sympify(0) for i in range(DIM): r += xxCart[i]**2 r = sp.sqrt(r) # Step 5: Set initial data for uu and vv, where vv_ID = \partial_t uu_ID. global uu_ID, vv_ID # uu_ID = (r - wavespeed*time)/r * sp.exp(- (r - wavespeed*time)**2 / (2*sigma**2) ) uu_ID = (+(r - wavespeed * time) / r * sp.exp(-(r - wavespeed * time)**2 / (2 * sigma**2)) + (r + wavespeed * time) / r * sp.exp(-(r + wavespeed * time)**2 / (2 * sigma**2))) vv_ID = sp.diff(uu_ID, time)
def PlaneWave(CoordSystem="Cartesian", default_time=0, default_k0=1, default_k1=1, default_k2=1): # Step 1: Set parameters defined in other modules DIM = par.parval_from_str("grid::DIM") # Step 2: Set up Cartesian coordinates in terms of the native CoordSystem we have chosen. # E.g., if CoordSystem="Cartesian", then xxCart = [xx[0],xx[1],xx[2]] # or if CoordSystem="Spherical", then xxCart = [xx[0]*sp.sin(xx[1])*sp.cos(xx[2]), # xx[0]*sp.sin(xx[1])*sp.sin(xx[2]), # xx[0]*sp.cos(xx[1])] par.set_parval_from_str("reference_metric::CoordSystem", CoordSystem) rfm.reference_metric() xxCart = rfm.xxCart # Step 3: Declare free parameters intrinsic to these initial data time = par.Cparameters("REAL", thismodule, "time", default_time) kk = par.Cparameters("REAL", thismodule, ["kk0", "kk1", "kk2"], [default_k0, default_k1, default_k2]) # Step 4: Normalize the k vector kk_norm_factor = sp.sqrt(kk[0]**2 + kk[1]**2 + kk[2]**2) # Step 5: Compute k_norm.x dot_product = sp.sympify(0) for i in range(DIM): dot_product += kk[i] * xxCart[i] dot_product /= kk_norm_factor # Step 6: Set initial data for uu and vv, where vv_ID = \partial_t uu_ID. global uu_ID, vv_ID uu_ID = sp.sin(dot_product - wavespeed * time) + 2 vv_ID = sp.diff(uu_ID, time)
def ijkl_string(idx4): DIM = par.parval_from_str("DIM") retstring = "" for i in range(DIM): if i > 0: # Add a comma retstring += "," retstring += "i" + str(i) + "+" + str(idx4[i]) return retstring.replace("+-", "-").replace("+0", "")
def ScalarWaveCurvilinear_RHSs(): # Step 1: Get the spatial dimension, defined in the # NRPy+ "grid" module. With reference metrics, # this must be set to 3 or fewer. DIM = par.parval_from_str("DIM") # Step 2: Set up the reference metric and # quantities derived from the # reference metric. rfm.reference_metric() # Step 3: Compute the contracted Christoffel symbols: contractedGammahatU = ixp.zerorank1() for k in range(DIM): for i in range(DIM): for j in range(DIM): contractedGammahatU[k] += rfm.ghatUU[i][j] * rfm.GammahatUDD[k][i][j] # Step 4: Register gridfunctions that are needed as input # to the scalar wave RHS expressions. uu, vv = gri.register_gridfunctions("EVOL",["uu","vv"]) # Step 5a: Declare the rank-1 indexed expression \partial_{i} u, # Derivative variables like these must have an underscore # in them, so the finite difference module can parse the # variable name properly. uu_dD = ixp.declarerank1("uu_dD") # Step 5b: Declare the rank-2 indexed expression \partial_{ij} u, # which is symmetric about interchange of indices i and j # Derivative variables like these must have an underscore # in them, so the finite difference module can parse the # variable name properly. uu_dDD = ixp.declarerank2("uu_dDD","sym01") # Step 7: Specify RHSs as global variables, # to enable access outside this # function (e.g., for C code output) global uu_rhs,vv_rhs # Step 6: Define right-hand sides for the evolution. # Step 6a: uu_rhs = vv: uu_rhs = vv # Step 6b: The right-hand side of the \partial_t v equation # is given by: # \hat{g}^{ij} \partial_i \partial_j u - \hat{\Gamma}^i \partial_i u. # ^^^^^^^^^^^^ PART 1 ^^^^^^^^^^^^^^^^ ^^^^^^^^^^ PART 2 ^^^^^^^^^^^ vv_rhs = 0 for i in range(DIM): # PART 2: vv_rhs -= contractedGammahatU[i]*uu_dD[i] for j in range(DIM): # PART 1: vv_rhs += rfm.ghatUU[i][j]*uu_dDD[i][j] vv_rhs *= wavespeed*wavespeed
def f_of_r(r, M): if par.parval_from_str("drop_fr"): return sp.sympify(0) x = sp.sympify(2) * M / r L = sp.sympify(0) + \ noif.coord_greater_bound(x,sp.sympify(0))*noif.coord_less_bound(x,sp.sympify(1))*nrpyDilog(x)\ +sp.Rational(1,2)*sp.log(noif.coord_greater_bound(x,sp.sympify(0))*x + noif.coord_leq_bound(x,sp.sympify(1)))\ *sp.log(noif.coord_less_bound(x,sp.sympify(1))*(sp.sympify(1)-x) + noif.coord_geq_bound(x,sp.sympify(1))) f = r*r*(sp.sympify(2)*r-sp.sympify(3)*M)*sp.Rational(1,8)/(M**3)*L\ +(M*M+sp.sympify(3)*M*r-sp.sympify(6)*r*r)*sp.Rational(1,12)/(M*M)*sp.log(r*sp.Rational(1,2)/M)\ +sp.Rational(11,72) + M*sp.Rational(1,3)/r + r*sp.Rational(1,2)/M - r*r*sp.Rational(1,2)/(M*M) return f
def define_LeviCivitaSymbol(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
def fp_of_r(r, M): if par.parval_from_str("drop_fr"): return sp.sympify(0) x = sp.sympify(2) * M / r L = sp.sympify(0) + \ noif.coord_greater_bound(x,sp.sympify(0))*noif.coord_less_bound(x,sp.sympify(1))*nrpyDilog(x)\ +sp.Rational(1,2)*sp.log(noif.coord_greater_bound(x,sp.sympify(0))*x + noif.coord_leq_bound(x,sp.sympify(1)))\ *sp.log(noif.coord_less_bound(x,sp.sympify(1))*(sp.sympify(1)-x) + noif.coord_geq_bound(x,sp.sympify(1))) Lp = sp.sympify(0) + noif.coord_greater_bound(x,sp.sympify(0))*noif.coord_less_bound(x,sp.sympify(1)) * -sp.Rational(1,2) *\ (sp.log(noif.coord_less_bound(x,sp.sympify(1))*(sp.sympify(1)-x) + noif.coord_geq_bound(x,sp.sympify(1)))/(x+sp.sympify(1e-100))\ +sp.log(noif.coord_greater_bound(x,sp.sympify(0))*x + noif.coord_leq_bound(x,sp.sympify(1)))/(sp.sympify(1)-x+sp.sympify(1e-100))) fp = sp.sympify(3)*r*(r-M)*sp.Rational(1,4)/(M**3)*L + (sp.sympify(2)*r-sp.sympify(3)*M)*sp.Rational(1,4)/(M*M)*Lp\ +(sp.sympify(3)*M-12*r)*sp.Rational(1,12)/(M*M)*sp.log(r*sp.Rational(1,2)/M) + (M*M+sp.sympify(3)*M*r-sp.sympify(6)*r*r)*sp.Rational(1,12)/(r*M*M)\ -M*sp.Rational(1,3)/(r*r) + sp.Rational(1,2)/M - r/(M*M) return fp
def enforce_detgammabar_eq_detgammahat__generate_symbolic_expressions_and_C_code(): ###################################### # START: GENERATE SYMBOLIC EXPRESSIONS # Store original finite-differencing order: FD_order_orig = par.parval_from_str("finite_difference::FD_CENTDERIVS_ORDER") # Set new finite-differencing order: par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", FD_order) # Enable rfm_precompute infrastructure, which results in # BSSN RHSs that are free of transcendental functions, # even in curvilinear coordinates, so long as # ConformalFactor is set to "W" (default). cmd.mkdir(os.path.join(outdir,"rfm_files/")) par.set_parval_from_str("reference_metric::enable_rfm_precompute","True") par.set_parval_from_str("reference_metric::rfm_precompute_Ccode_outdir",os.path.join(outdir,"rfm_files/")) import BSSN.Enforce_Detgammabar_Constraint as EGC enforce_detg_constraint_symb_expressions = EGC.Enforce_Detgammabar_Constraint_symb_expressions() # Now that we are finished with all the rfm hatted # quantities in generic precomputed functional # form, let's restore them to their closed- # form expressions. par.set_parval_from_str("reference_metric::enable_rfm_precompute","False") # Reset to False to disable rfm_precompute. rfm.ref_metric__hatted_quantities() # Restore original finite-differencing order: par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", FD_order) # END: GENERATE SYMBOLIC EXPRESSIONS ###################################### start = time.time() print("Generating optimized C code (FD_order="+str(FD_order)+") for gamma constraint. May take a while, depending on CoordSystem.") enforce_gammadet_string = fin.FD_outputC("returnstring", enforce_detg_constraint_symb_expressions, params="outCverbose=False,preindent=0,includebraces=False") with open(os.path.join(outdir,"enforcedetgammabar_constraint_FD_order_"+str(FD_order)+".h"), "w") as file: file.write(lp.loop(["i2","i1","i0"],["0", "0", "0"], ["cctk_lsh[2]","cctk_lsh[1]","cctk_lsh[0]"], ["1","1","1"], ["#pragma omp parallel for", "#include \"rfm_files/rfm_struct__read2.h\"", "#include \"rfm_files/rfm_struct__read1.h\""],"", "#include \"rfm_files/rfm_struct__read0.h\"\n"+enforce_gammadet_string)) end = time.time() print("Finished gamma constraint C codegen (FD_order="+str(FD_order)+") in " + str(end - start) + " seconds.")
def declarerank3(objname, symmetry_option, DIM=-1): if DIM==-1: DIM = par.parval_from_str("DIM") IDX_OBJ_TMP = [[[sp.sympify(objname + str(i) + str(j) + str(k)) for k in range(DIM)] for j in range(DIM)] for i in range(DIM)] for i in range(DIM): for j in range(DIM): for k in range(DIM): if symmetry_option == "sym01": if j < i: IDX_OBJ_TMP[i][j][k] = IDX_OBJ_TMP[j][i][k] if symmetry_option == "sym12": if k < j: IDX_OBJ_TMP[i][j][k] = IDX_OBJ_TMP[i][k][j] if not (symmetry_option == "sym01" or symmetry_option == "sym12" or symmetry_option == "nosym"): print("Error: symmetry option " + symmetry_option + " unsupported.") sys.exit(1) return apply_symmetry_condition_to_derivatives(IDX_OBJ_TMP)
def register_C_functions_and_NRPy_basic_defines(NGHOSTS_account_for_onezone_upwind=False, enable_SIMD=True): # First register C functions needed by finite_difference # Then set up the dictionary entry for finite_difference in NRPy_basic_defines NGHOSTS = int(par.parval_from_str("finite_difference::FD_CENTDERIVS_ORDER")/2) if NGHOSTS_account_for_onezone_upwind: NGHOSTS += 1 Nbd_str = """ // Set the number of ghost zones // Note that upwinding in e.g., BSSN requires that NGHOSTS = FD_CENTDERIVS_ORDER/2 + 1 <- Notice the +1. """ Nbd_str += "#define NGHOSTS " + str(NGHOSTS)+"\n" if not enable_SIMD: Nbd_str += """ // When enable_SIMD = False, this is the UPWIND_ALG() macro: #define UPWIND_ALG(UpwindVecU) UpwindVecU > 0.0 ? 1.0 : 0.0\n""" outC_NRPy_basic_defines_h_dict["finite_difference"] = Nbd_str
def declarerank2(objname, symmetry_option, DIM=-1): if DIM==-1: DIM = par.parval_from_str("DIM") IDX_OBJ_TMP = [[sp.sympify(objname + str(i) + str(j)) for j in range(DIM)] for i in range(DIM)] for i in range(DIM): for j in range(DIM): if symmetry_option == "sym01": if (j < i): # j<i in g_{ij} would indicate, e.g., g_{21}. # By this convention, we must set # g_{21} = g_{12}: IDX_OBJ_TMP[i][j] = IDX_OBJ_TMP[j][i] elif symmetry_option == "nosym": pass else: print("Error: symmetry option " + symmetry_option + " unsupported.") sys.exit(1) return apply_symmetry_condition_to_derivatives(IDX_OBJ_TMP)
def register_gridfunctions_for_single_rank1(gf_type,gf_basename, DIM=-1, f_infinity=0.0, wavespeed=1.0): # Step 0: Verify the gridfunction basename is valid: gri.verify_gridfunction_basename_is_valid(gf_basename) # Step 1: Declare a list of SymPy variables, # where IDX_OBJ_TMP[i] = gf_basename+str(i) IDX_OBJ_TMP = declarerank1(gf_basename, DIM) # Step 2: Register each gridfunction if DIM==-1: DIM = par.parval_from_str("DIM") gf_list = [] for i in range(DIM): gf_list.append(str(IDX_OBJ_TMP[i])) gri.register_gridfunctions(gf_type, gf_list, rank=1, is_indexed=True, DIM=DIM, f_infinity=f_infinity, wavespeed=wavespeed) # Step 3: Return array of SymPy variables return IDX_OBJ_TMP
def output_hermite_interpolator_functions_h(path=os.path.join(".")): with open(os.path.join(path, "hermite_interpolator_functions.h"), "w") as file: file.write(""" #ifndef __HI_FUNCTIONS_H__ #define __HI_FUNCTIONS_H__ #include "math.h" #include "stdio.h" #include "stdlib.h" """) UNUSED = "__attribute__((unused))" NOINLINE = "__attribute__((noinline))" if par.parval_from_str("grid::GridFuncMemAccess") == "ETK": UNUSED = "CCTK_ATTRIBUTE_UNUSED" NOINLINE = "CCTK_ATTRIBUTE_NOINLINE" file.write("#define _UNUSED " + UNUSED + "\n") file.write("#define _NOINLINE " + NOINLINE + "\n") for key, item in outC_function_dict.items(): if "__HI_OPERATOR_FUNC__" in item: file.write( item.replace( "const REAL_SIMD_ARRAY _NegativeOne_ =", "const REAL_SIMD_ARRAY " + UNUSED + " _NegativeOne_ =") ) # Many of the NegativeOne's get optimized away in the SIMD postprocessing step. No need for all the warnings # Clear all HI functions from outC_function_dict after outputting to hermite_interpolator_functions.h. # Otherwise outputC will be outputting these as separate individual C codes & attempting to build them in Makefile. key_list_del = [] element_del = [] for i, func in enumerate(outC_function_master_list): if "__HI_OPERATOR_FUNC__" in func.desc: if func.name not in key_list_del: key_list_del += [func.name] if func not in element_del: element_del += [func] for func in element_del: outC_function_master_list.remove(func) for key in key_list_del: outC_function_dict.pop(key) if key in outC_function_prototype_dict: outC_function_prototype_dict.pop(key) file.write("#endif // #ifndef __HI_FUNCTIONS_H__\n")
def BSSN_constraints__generate_symbolic_expressions_and_C_code(): ###################################### # START: GENERATE SYMBOLIC EXPRESSIONS # Define the Hamiltonian constraint and output the optimized C code. import BSSN.BSSN_constraints as bssncon bssncon.BSSN_constraints(add_T4UUmunu_source_terms=False) if T4UU != None: import BSSN.BSSN_stress_energy_source_terms as Bsest Bsest.BSSN_source_terms_for_BSSN_RHSs(T4UU) Bsest.BSSN_source_terms_for_BSSN_constraints(T4UU) bssncon.H += Bsest.sourceterm_H for i in range(3): bssncon.MU[i] += Bsest.sourceterm_MU[i] # END: GENERATE SYMBOLIC EXPRESSIONS ###################################### # Store original finite-differencing order: FD_order_orig = par.parval_from_str("finite_difference::FD_CENTDERIVS_ORDER") # Set new finite-differencing order: par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", FD_order) start = time.time() print("Generating optimized C code for Ham. & mom. constraints. May take a while, depending on CoordSystem.") Ham_mom_string = fin.FD_outputC("returnstring", [lhrh(lhs=gri.gfaccess("aux_gfs", "H"), rhs=bssncon.H), lhrh(lhs=gri.gfaccess("aux_gfs", "MU0"), rhs=bssncon.MU[0]), lhrh(lhs=gri.gfaccess("aux_gfs", "MU1"), rhs=bssncon.MU[1]), lhrh(lhs=gri.gfaccess("aux_gfs", "MU2"), rhs=bssncon.MU[2])], params="outCverbose=False") with open(os.path.join(outdir,"BSSN_constraints_enable_Tmunu_"+str(enable_stress_energy_source_terms)+"_FD_order_"+str(FD_order)+".h"), "w") as file: file.write(lp.loop(["i2","i1","i0"],["cctk_nghostzones[2]","cctk_nghostzones[1]","cctk_nghostzones[0]"], ["cctk_lsh[2]-cctk_nghostzones[2]","cctk_lsh[1]-cctk_nghostzones[1]","cctk_lsh[0]-cctk_nghostzones[0]"], ["1","1","1"],["#pragma omp parallel for","",""], "", Ham_mom_string)) # Restore original finite-differencing order: par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", FD_order_orig) end = time.time() print("(BENCH) Finished Hamiltonian & momentum constraint C codegen (FD_order="+str(FD_order)+",Tmunu="+str(enable_stress_energy_source_terms)+") in " + str(end - start) + " seconds.")
def EigenCoord_xx_to_Cart(i012suffix = ""): # Step 1: 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 2: Output C code for the Eigen-Coordinate mapping from xx->Cartesian: outstr = """ { // xx_to_Cart for EigenCoordinate """+rfm.get_EigenCoord()+r""" (orig coord = """+CoordSystem_orig+r"""): REAL xx0 = xx[0][i0"""+i012suffix+"""]; REAL xx1 = xx[1][i1"""+i012suffix+"""]; REAL xx2 = xx[2][i2"""+i012suffix+"""];\n"""+ \ outputC([rfm.xx_to_Cart[0],rfm.xx_to_Cart[1],rfm.xx_to_Cart[2]], ["xCart[0]","xCart[1]","xCart[2]"], "returnstring", params="preindent=3")+" }\n" # Step 3: Restore reference_metric::CoordSystem back to the original CoordSystem par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem_orig) rfm.reference_metric() # Step 4: Return EigenCoord xx_to_Cart C code return outstr
def print_msg_with_timing(desc, msg="Symbolic", startstop="start", starttime=0.0): CoordSystem = par.parval_from_str("reference_metric::CoordSystem") elapsed = time.time() - starttime if msg == "Symbolic": if startstop == "start": print("Generating symbolic expressions for " + desc + " (%s coords)..." % CoordSystem) return time.time() else: print("Finished generating symbolic expressions for " + desc + " (%s coords) in %.1f seconds. Next up: C codegen..." % (CoordSystem, elapsed)) elif msg == "Ccodegen": if startstop == "start": print("Generating C code for " + desc + " (%s coords)..." % CoordSystem) return time.time() else: print("Finished generating C code for " + desc + " (%s coords) in %.1f seconds." % (CoordSystem, elapsed))