def construct_Ccode(sympyexpr_list, list_of_deriv_vars, list_of_base_gridfunction_names_in_derivs, list_of_deriv_operators, fdcoeffs, fdstencl, read_from_memory_Ccode, FDparams, Coutput): """ C code is constructed in *up to* 3 parts: 5.a) Read gridfunctions from memory at needed pts for finite differencing; compute finite-differencing stencils. 5.b) Implement upwinding algorithm (if relevant) 5.c) Evaluate SymPy expressions and write to main memory """ # Failed Doctest. However, mathematically equivalent with Sympy 1.3 # :param sympyexpr_list: # :param list_of_deriv_vars: # :param list_of_base_gridfunction_names_in_derivs: # :param list_of_deriv_operators: # :param fdcoeffs: # :param fdstencl: # :param read_from_memory_Ccode: # :param FDparams: # :param Coutput: The start of the Coutput string; this function's output will be pasted to a copy of Coutput # :return: Returns a C code string # >>> from outputC import lhrh # >>> import indexedexp as ixp # >>> import NRPy_param_funcs as par # >>> from finite_difference_helpers import generate_list_of_deriv_vars_from_lhrh_sympyexpr_list,FDparams # >>> from finite_difference_helpers import extract_from_list_of_deriv_vars__base_gfs_and_deriv_ops_lists # >>> from finite_difference_helpers import read_gfs_from_memory, construct_Ccode # >>> from finite_difference import compute_fdcoeffs_fdstencl # >>> import grid as gri # >>> gri.glb_gridfcs_list = [] # >>> hDD = ixp.register_gridfunctions_for_single_rank2("EVOL","hDD","sym01") # >>> hDD_dD = ixp.declarerank3("hDD_dD","sym01") # >>> hDD_dupD = ixp.declarerank3("hDD_dupD","sym01") # >>> vU = ixp.register_gridfunctions_for_single_rank1("EVOL","vU") # >>> a0,a1,b,c = par.Cparameters("REAL",__name__,["a0","a1","b","c"],1) # >>> par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER",2) # >>> FDparams.DIM=3 # >>> FDparams.SIMD_enable="False" # >>> FDparams.FD_functions_enable=False # >>> FDparams.PRECISION="double" # >>> FDparams.MemAllocStyle="012" # >>> FDparams.upwindcontrolvec=vU # >>> FDparams.fullindent="" # >>> FDparams.outCparams="outCverbose=False" # >>> exprlist = [lhrh(lhs=a0,rhs=b*hDD[1][0] + c*hDD_dD[0][1][1]), \ # lhrh(lhs=a1,rhs=c*hDD_dupD[0][2][1]*vU[1])] # >>> list_of_deriv_vars = generate_list_of_deriv_vars_from_lhrh_sympyexpr_list(exprlist,FDparams) # >>> list_of_base_gridfunction_names_in_derivs, list_of_deriv_operators = extract_from_list_of_deriv_vars__base_gfs_and_deriv_ops_lists(list_of_deriv_vars) # >>> fdcoeffs = [[] for i in range(len(list_of_deriv_operators))] # >>> fdstencl = [[[] for i in range(4)] for j in range(len(list_of_deriv_operators))] # >>> for i in range(len(list_of_deriv_operators)): fdcoeffs[i], fdstencl[i] = compute_fdcoeffs_fdstencl(list_of_deriv_operators[i]) # >>> memread_Ccode = read_gfs_from_memory(list_of_base_gridfunction_names_in_derivs, fdstencl, exprlist, FDparams) # >>> print(construct_Ccode(exprlist, list_of_deriv_vars, \ # list_of_base_gridfunction_names_in_derivs, list_of_deriv_operators, \ # fdcoeffs, fdstencl, memread_Ccode, FDparams, "")) # /* # * NRPy+ Finite Difference Code Generation, Step 1 of 3: Read from main memory and compute finite difference stencils: # */ # const double hDD01_i0_i1m1_i2 = in_gfs[IDX4(HDD01GF, i0,i1-1,i2)]; # const double hDD01 = in_gfs[IDX4(HDD01GF, i0,i1,i2)]; # const double hDD01_i0_i1p1_i2 = in_gfs[IDX4(HDD01GF, i0,i1+1,i2)]; # const double hDD02_i0_i1m2_i2 = in_gfs[IDX4(HDD02GF, i0,i1-2,i2)]; # const double hDD02_i0_i1m1_i2 = in_gfs[IDX4(HDD02GF, i0,i1-1,i2)]; # const double hDD02 = in_gfs[IDX4(HDD02GF, i0,i1,i2)]; # const double hDD02_i0_i1p1_i2 = in_gfs[IDX4(HDD02GF, i0,i1+1,i2)]; # const double hDD02_i0_i1p2_i2 = in_gfs[IDX4(HDD02GF, i0,i1+2,i2)]; # const double vU1 = in_gfs[IDX4(VU1GF, i0,i1,i2)]; # const double FDPart1_Rational_1_2 = 1.0/2.0; # const double FDPart1_Integer_2 = 2.0; # const double FDPart1_Rational_3_2 = 3.0/2.0; # const double hDD_dD011 = FDPart1_Rational_1_2*invdx1*(-hDD01_i0_i1m1_i2 + hDD01_i0_i1p1_i2); # const double UpwindAlgInputhDD_ddnD021 = invdx1*(-FDPart1_Integer_2*hDD02_i0_i1m1_i2 + FDPart1_Rational_1_2*hDD02_i0_i1m2_i2 + FDPart1_Rational_3_2*hDD02); # const double UpwindAlgInputhDD_dupD021 = invdx1*(FDPart1_Integer_2*hDD02_i0_i1p1_i2 - FDPart1_Rational_1_2*hDD02_i0_i1p2_i2 - FDPart1_Rational_3_2*hDD02); # const double UpwindControlVectorU1 = vU1; # /* # * NRPy+ Finite Difference Code Generation, Step 2 of 3: Implement upwinding algorithm: # */ # const double UpWind1 = UPWIND_ALG(UpwindControlVectorU1); # const double hDD_dupD021 = UpWind1*(-UpwindAlgInputhDD_ddnD021 + UpwindAlgInputhDD_dupD021) + UpwindAlgInputhDD_ddnD021; # /* # * NRPy+ Finite Difference Code Generation, Step 3 of 3: Evaluate SymPy expressions and write to main memory: # */ # a0 = b*hDD01 + c*hDD_dD011; # a1 = c*hDD_dupD021*vU1; # <BLANKLINE> def indent_Ccode(Ccode): Ccodesplit = Ccode.splitlines() outstring = "" for i in range(len(Ccodesplit)): outstring += FDparams.fullindent + Ccodesplit[i] + '\n' return outstring # Step 5.a.i: Read gridfunctions from memory at needed pts. # *** No need to do anything here; already set in # string "read_from_memory_Ccode". *** # Step 5.a.ii: Perform arithmetic needed for finite differences # associated with input expressions provided in # sympyexpr_list[].rhs. # Note that FDexprs and FDlhsvarnames contain # A) Finite difference expressions (constructed # in steps above) and associated variable names, # and # B) Input expressions sympyexpr_list[], which # in general depend on finite difference # variables. FDexprs = [] FDlhsvarnames = [] if not FDparams.FD_functions_enable: FDexprs, FDlhsvarnames = \ construct_FD_exprs_as_SymPy_exprs(list_of_deriv_vars, list_of_base_gridfunction_names_in_derivs, list_of_deriv_operators, fdcoeffs, fdstencl) # Compute finite differences using function calls (instead of inlined calculations)? if FDparams.FD_functions_enable: # If so, add FD functions to outputC's outC_function_dict (C function dictionary), # AND return the full set of needed calls to these functions (to funccall_list) funccall_list = \ add_FD_func_to_outC_function_dict(list_of_deriv_vars, list_of_base_gridfunction_names_in_derivs, list_of_deriv_operators, fdcoeffs, fdstencl) # Step 5.b.i: (Upwinded derivatives algorithm, part 1): # If an upwinding control vector is specified, determine # which of the elements of the vector will be required. # This ensures that those elements are read from memory. # For example, if a symmetry axis is specified, # upwind derivatives with respect to only # two of the three dimensions are used. Here # we find all directions used for upwinding. upwind_directions = [] if FDparams.upwindcontrolvec != "": upwind_directions_unsorted_withdups = [] for deriv_op in list_of_deriv_operators: if "dupD" in deriv_op: if deriv_op[len(deriv_op) - 1].isdigit(): dirn = int(deriv_op[len(deriv_op) - 1]) upwind_directions_unsorted_withdups.append(dirn) else: print("Error: Derivative operator " + deriv_op + " does not contain a direction") sys.exit(1) if len(upwind_directions_unsorted_withdups) > 0: upwind_directions = superfast_uniq( upwind_directions_unsorted_withdups) upwind_directions = sorted(upwind_directions, key=sp.default_sort_key) # If upwind control vector is specified, # add upwind control vectors to the # derivative expression list, so its # needed elements are read from memory. for dirn in upwind_directions: FDexprs.append(FDparams.upwindcontrolvec[dirn]) FDlhsvarnames.append( type__var("UpwindControlVectorU" + str(dirn), FDparams)) # Step 5.x: Output useful code comment regarding # which step we are on. *At most* this # is a 3-step process: # 1. Read from memory & compute FD stencils, # 2. Perform upwinding, and # 3. Evaluate remaining expressions+write # results to main memory. NRPy_FD_StepNumber = 1 NRPy_FD__Number_of_Steps = 1 if len(read_from_memory_Ccode) > 0: NRPy_FD__Number_of_Steps += 1 if FDparams.upwindcontrolvec != "" and len(upwind_directions) > 0: NRPy_FD__Number_of_Steps += 1 if len(read_from_memory_Ccode) > 0: Coutput += indent_Ccode( "/*\n * NRPy+ Finite Difference Code Generation, Step " + str(NRPy_FD_StepNumber) + " of " + str(NRPy_FD__Number_of_Steps) + ": Read from main memory and compute finite difference stencils:\n */\n" ) NRPy_FD_StepNumber = NRPy_FD_StepNumber + 1 if FDparams.FD_functions_enable: # Compute finite differences using function calls (instead of inlined calculations) Coutput += indent_Ccode(read_from_memory_Ccode) for funccall in funccall_list: Coutput += indent_Ccode(funccall) if FDparams.upwindcontrolvec != "": # Compute finite differences using inlined calculations params = FDparams.outCparams # We choose the CSE temporary variable prefix "FDpart1" for the finite difference coefficients: params += ",CSE_varprefix=FDPart1,includebraces=False,CSE_preprocess=True,SIMD_find_more_subs=True" Coutput += indent_Ccode( outputC(FDexprs, FDlhsvarnames, "returnstring", params=params)) else: # Compute finite differences using inlined calculations params = FDparams.outCparams.replace( "preindent=1", "preindent=0") # Remove an unnecessary indentation # We choose the CSE temporary variable prefix "FDpart1" for the finite difference coefficients: params += ",CSE_varprefix=FDPart1,includebraces=False,CSE_preprocess=True,SIMD_find_more_subs=True" Coutput += indent_Ccode( outputC(FDexprs, FDlhsvarnames, "returnstring", params=params, prestring=read_from_memory_Ccode)) # Step 5.b.ii: Implement control-vector upwinding algorithm. if FDparams.upwindcontrolvec != "": if len(upwind_directions) > 0: Coutput += indent_Ccode( "/*\n * NRPy+ Finite Difference Code Generation, Step " + str(NRPy_FD_StepNumber) + " of " + str(NRPy_FD__Number_of_Steps) + ": Implement upwinding algorithm:\n */\n") NRPy_FD_StepNumber = NRPy_FD_StepNumber + 1 if FDparams.SIMD_enable == "True": for n in ["0", "1"]: Coutput += indent_Ccode( "const double tmp_upwind_Integer_" + n + " = " + n + ".000000000000000000000000000000000;\n") Coutput += indent_Ccode( "const REAL_SIMD_ARRAY upwind_Integer_" + n + " = ConstSIMD(tmp_upwind_Integer_" + n + ");\n") for dirn in upwind_directions: Coutput += indent_Ccode( type__var("UpWind" + str(dirn), FDparams) + " = UPWIND_ALG(UpwindControlVectorU" + str(dirn) + ");\n") upwindU = [sp.sympify(0) for i in range(FDparams.DIM)] for dirn in upwind_directions: upwindU[dirn] = sp.sympify("UpWind" + str(dirn)) upwind_expr_list, var_list = [], [] for i in range(len(list_of_deriv_vars)): if len(list_of_deriv_operators[i]) == 5 and ( "dupD" in list_of_deriv_operators[i]): var_dupD = sp.sympify("UpwindAlgInput" + str(list_of_deriv_vars[i])) var_ddnD = sp.sympify( "UpwindAlgInput" + str(list_of_deriv_vars[i]).replace("_dupD", "_ddnD")) upwind_dirn = int( list_of_deriv_operators[i][len(list_of_deriv_operators[i]) - 1]) upwind_expr = upwindU[upwind_dirn] * (var_dupD - var_ddnD) + var_ddnD upwind_expr_list.append(upwind_expr) var_list.append( type__var(str(list_of_deriv_vars[i]), FDparams, AddPrefix_for_UpDownWindVars=False)) # For convenience, we require type__var() above to # prefix up/downwinded variables with "UpwindAlgInput". # Here we do not wish to have this prefix. Coutput += indent_Ccode( outputC(upwind_expr_list, var_list, "returnstring", params=FDparams.outCparams + ",CSE_varprefix=FDPart2,includebraces=False")) # Step 5.c.i: Add input RHS & LHS expressions from # sympyexpr_list[] Coutput += indent_Ccode( "/*\n * NRPy+ Finite Difference Code Generation, Step " + str(NRPy_FD_StepNumber) + " of " + str(NRPy_FD__Number_of_Steps) + ": Evaluate SymPy expressions and write to main memory:\n */\n") exprs = [] lhsvarnames = [] for i in range(len(sympyexpr_list)): exprs.append(sympyexpr_list[i].rhs) if FDparams.SIMD_enable == "True": lhsvarnames.append("const REAL_SIMD_ARRAY __RHS_exp_" + str(i)) else: lhsvarnames.append(sympyexpr_list[i].lhs) # Step 5.c.ii: Write output to gridfunctions specified in # sympyexpr_list[].lhs. write_to_mem_string = "" if FDparams.SIMD_enable == "True": for i in range(len(sympyexpr_list)): write_to_mem_string += "WriteSIMD(&" + sympyexpr_list[ i].lhs + ", __RHS_exp_" + str(i) + ");\n" # outputC requires as its second argument a list of strings. # Sometimes when the lhs's are simple constants, but the inputs # contain gridfunctions, it is necessary to convert the lhs's # to strings: lhsvarnamestrings = [] for lhs in lhsvarnames: lhsvarnamestrings.append(str(lhs)) Coutput += indent_Ccode( outputC(exprs, lhsvarnamestrings, "returnstring", params=FDparams.outCparams + ",CSE_varprefix=FDPart3,includebraces=False,preindent=0", prestring="", poststring=write_to_mem_string)) return Coutput
def symbolic_parital_derivative(): # Step 2.a: Read in expressions as a (single) string with open(os.path.join(inputdir, 'Hamstring.txt'), 'r') as file: expressions_as_lines = file.readlines() # Step 2.b: Create and populate the "lr" array, which separates each line into left- and right-hand sides # Each entry is a string of the form lhrh(lhs='',rhs='') lr = [] for i in range(len(expressions_as_lines)): # Ignore lines with 2 or fewer characters and those starting with # if len(expressions_as_lines[i] ) > 2 and expressions_as_lines[i][0] != "#": # Split each line by its equals sign split_line = expressions_as_lines[i].split("=") # Append the line to "lr", removing spaces, "sp." prefixes, and replacing Lambda->Lamb # (Lambda is a protected keyword): lr.append( lhrh(lhs=split_line[0].replace(" ", "").replace("Lambda", "Lamb"), rhs=split_line[1].replace(" ", "").replace("sp.", "").replace( "Lambda", "Lamb"))) # Step 2.c: Separate and sympify right- and left-hand sides into separate arrays lhss = [] rhss = [] for i in range(len(lr)): lhss.append(custom_parse_expr(lr[i].lhs)) rhss.append(custom_parse_expr(lr[i].rhs)) # Step 3.a: Create `input_constants` array and populate with SymPy symbols m1, m2, tortoise, eta, KK, k0, k1, EMgamma, d1v2, dheffSSv2 = sp.symbols( 'm1 m2 tortoise eta KK k0 k1 EMgamma d1v2 dheffSSv2', real=True) input_constants = [ m1, m2, tortoise, eta, KK, k0, k1, EMgamma, d1v2, dheffSSv2 ] # Step 3.b: Create `dynamic_variables` array and populate with SymPy symbols x, y, z, px, py, pz, s1x, s1y, s1z, s2x, s2y, s2z = sp.symbols( 'x y z px py pz s1x s1y s1z s2x s2y s2z', real=True) dynamic_variables = [x, y, z, px, py, pz, s1x, s1y, s1z, s2x, s2y, s2z] # Step 4.a: Prepare array of "free symbols" in the right-hand side expressions full_symbol_list_with_dups = [] for i in range(len(lr)): for variable in rhss[i].free_symbols: full_symbol_list_with_dups.append(variable) # Step 4.b: Remove duplicate free symbols full_symbol_list = superfast_uniq(full_symbol_list_with_dups) # Step 4.c: Remove input constants from symbol list for inputconst in input_constants: for symbol in full_symbol_list: if str(symbol) == str(inputconst): full_symbol_list.remove(symbol) # Step 5.a: Convert each left-hand side to function notation # while separating and simplifying left- and right-hand sides xx = sp.Symbol('xx') func = [] for i in range(len(lr)): func.append(sp.sympify(sp.Function(lr[i].lhs)(xx))) # Step 5.b: Mark each free variable as a function with argument xx full_function_list = [] for symb in full_symbol_list: func = sp.sympify(sp.Function(str(symb))(xx)) full_function_list.append(func) for i in range(len(rhss)): for var in rhss[i].free_symbols: if str(var) == str(symb): rhss[i] = rhss[i].subs(var, func) # Step 6.a: Use SymPy's diff function to differentiate right-hand sides with respect to xx # and append "prm" notation to left-hand sides lhss_deriv = [] rhss_deriv = [] for i in range(len(rhss)): lhss_deriv.append(custom_parse_expr(str(lhss[i]) + "prm")) newrhs = custom_parse_expr( str(sp.diff(rhss[i], xx)).replace("(xx)", "").replace( ", xx", "prm").replace("Derivative", "")) rhss_deriv.append(newrhs) # Step 7.b: Call the simplication function and then copy results lhss_deriv_simp, rhss_deriv_simp = simplify_deriv(lhss_deriv, rhss_deriv) lhss_deriv = lhss_deriv_simp rhss_deriv = rhss_deriv_simp # Step 8.b: Call the derivative function and populate dictionaries with the result lhss_derivative = {} rhss_derivative = {} for index in range(len(dynamic_variables)): lhss_temp, rhss_temp = deriv_onevar(lhss_deriv, rhss_deriv, dynamic_variables, index) lhss_derivative[dynamic_variables[index]] = lhss_temp rhss_derivative[dynamic_variables[index]] = rhss_temp # Step 9: Output original expression and each partial derivative expression in SymPy snytax with open("partial_derivatives.txt", "w") as output: for i in range(len(lr)): right_side = lr[i].rhs right_side_in_sp = right_side.replace("sqrt(", "sp.sqrt(").replace( "log(", "sp.log(").replace("pi", "sp.pi").replace( "sign(", "sp.sign(").replace("Abs(", "sp.Abs(").replace( "Rational(", "sp.Rational(") output.write(str(lr[i].lhs) + " = " + right_side_in_sp) for var in dynamic_variables: for i in range(len(lhss_derivative[var])): right_side = str(rhss_derivative[var][i]) right_side_in_sp = right_side.replace( "sqrt(", "sp.sqrt(").replace("log(", "sp.log(").replace( "pi", "sp.pi").replace("sign(", "sp.sign(").replace( "Abs(", "sp.Abs(").replace("Rational(", "sp.Rational(").replace( "prm", "prm_" + str(var)) output.write( str(lhss_derivative[var][i]).replace( "prm", "prm_" + str(var)) + " = " + right_side_in_sp + "\n")
def read_gfs_from_memory(list_of_base_gridfunction_names_in_derivs, fdstencl, sympyexpr_list, FDparams): # with open(list_of_base_gridfunction_names_in_derivs[0]+".txt","w") as file: # file.write(str(list_of_base_gridfunction_names_in_derivs)) # file.write(str(fdstencl)) # file.write(str(sympyexpr_list)) # file.write(str(FDparams)) """ :param list_of_base_gridfunction_names_in_derivs: :param fdstencl: :param sympyexpr_list: :param FDparams: :return: >>> from outputC import lhrh >>> import indexedexp as ixp >>> import NRPy_param_funcs as par >>> from finite_difference_helpers import generate_list_of_deriv_vars_from_lhrh_sympyexpr_list,FDparams >>> from finite_difference_helpers import extract_from_list_of_deriv_vars__base_gfs_and_deriv_ops_lists >>> from finite_difference_helpers import read_gfs_from_memory >>> from finite_difference import compute_fdcoeffs_fdstencl >>> import grid as gri >>> gri.glb_gridfcs_list = [] >>> hDD = ixp.register_gridfunctions_for_single_rank2("EVOL","hDD","sym01") >>> hDD_dD = ixp.declarerank3("hDD_dD","sym01") >>> hDD_dupD = ixp.declarerank3("hDD_dupD","sym01") >>> vU = ixp.register_gridfunctions_for_single_rank1("EVOL","vU") >>> a0,a1,b,c = par.Cparameters("REAL",__name__,["a0","a1","b","c"],1) >>> par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER",2) >>> FDparams.DIM=3 >>> FDparams.SIMD_enable="False" >>> FDparams.PRECISION="double" >>> FDparams.MemAllocStyle="012" >>> FDparams.upwindcontrolvec=vU >>> exprlist = [lhrh(lhs=a0,rhs=b*hDD[1][0] + c*hDD_dD[0][1][1]), \ lhrh(lhs=a1,rhs=c*hDD_dupD[0][2][1]*vU[1])] >>> list_of_deriv_vars = generate_list_of_deriv_vars_from_lhrh_sympyexpr_list(exprlist,FDparams) >>> list_of_base_gridfunction_names_in_derivs, list_of_deriv_operators = extract_from_list_of_deriv_vars__base_gfs_and_deriv_ops_lists(list_of_deriv_vars) >>> fdcoeffs = [[] for i in range(len(list_of_deriv_operators))] >>> fdstencl = [[[] for i in range(4)] for j in range(len(list_of_deriv_operators))] >>> for i in range(len(list_of_deriv_operators)): fdcoeffs[i], fdstencl[i] = compute_fdcoeffs_fdstencl(list_of_deriv_operators[i]) >>> print(read_gfs_from_memory(list_of_base_gridfunction_names_in_derivs, fdstencl, exprlist, FDparams)) const double hDD01_i0_i1m1_i2 = in_gfs[IDX4(HDD01GF, i0,i1-1,i2)]; const double hDD01 = in_gfs[IDX4(HDD01GF, i0,i1,i2)]; const double hDD01_i0_i1p1_i2 = in_gfs[IDX4(HDD01GF, i0,i1+1,i2)]; const double hDD02_i0_i1m2_i2 = in_gfs[IDX4(HDD02GF, i0,i1-2,i2)]; const double hDD02_i0_i1m1_i2 = in_gfs[IDX4(HDD02GF, i0,i1-1,i2)]; const double hDD02 = in_gfs[IDX4(HDD02GF, i0,i1,i2)]; const double hDD02_i0_i1p1_i2 = in_gfs[IDX4(HDD02GF, i0,i1+1,i2)]; const double hDD02_i0_i1p2_i2 = in_gfs[IDX4(HDD02GF, i0,i1+2,i2)]; const double vU1 = in_gfs[IDX4(VU1GF, i0,i1,i2)]; <BLANKLINE> """ # Step 4a: Compile list of points to read from memory # for each gridfunction i, based on list # provided in fdstencil[i][]. list_of_points_read_from_memory_with_duplicates = [ [] for i in range(len(gri.glb_gridfcs_list)) ] for j in range(len(list_of_base_gridfunction_names_in_derivs)): derivgfname = list_of_base_gridfunction_names_in_derivs[j] # Next find the corresponding gridfunction index: for i in range(len(gri.glb_gridfcs_list)): gfname = gri.glb_gridfcs_list[i].name # If the gridfunction for the derivative matches, then # add to the list of points read from memory: if derivgfname == gfname: for k in range(len(fdstencl[j])): list_of_points_read_from_memory_with_duplicates[i].append( str(fdstencl[j][k][0]) + "," + str(fdstencl[j][k][1]) + "," + str(fdstencl[j][k][2]) + "," + str(fdstencl[j][k][3])) # Step 4b: "Zeroth derivative" case: # If gridfunction appears in expression not # as derivative (i.e., by itself), it must # be read from memory as well. for expr in range(len(sympyexpr_list)): for var in sympyexpr_list[expr].rhs.free_symbols: vartype = gri.variable_type(var) if vartype == "gridfunction": for i in range(len(gri.glb_gridfcs_list)): gfname = gri.glb_gridfcs_list[i].name if gfname == str(var): list_of_points_read_from_memory_with_duplicates[ i].append("0,0,0,0") # Step 4c: Remove duplicates when reading from memory; # do not needlessly read the same variable # from memory twice. list_of_points_read_from_memory = [ [] for i in range(len(gri.glb_gridfcs_list)) ] for i in range(len(gri.glb_gridfcs_list)): list_of_points_read_from_memory[i] = superfast_uniq( list_of_points_read_from_memory_with_duplicates[i]) # Step 4d: Minimize cache misses: # Sort the list of points read from # main memory by how they are stored # in memory. # Step 4d.i: Define a function that maps a gridpoint # index (i,j,k,l) to a unique memory "address", # which will correspond to the correct ordering # of actual memory addresses. # # Input: a list of 4 indices, e.g., (i,j,k,l) # corresponding to a gridpoint's *spatial* # index in memory (thus we support up to # 4D in space). If spatial dimension is # less than 4D, then just set latter # index/indices to zero. E.g., for 2D # spatial indexing, set (i,j,0,0). # Output: a single number, which when sorted # will yield a unique "address" in memory # such that consecutive addresses are # consecutive in memory. def unique_idx(idx4, FDparams): # 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 FDparams.MemAllocStyle == "210": return str( int(idx4[0]) + os + sz * ((int(idx4[1]) + os) + sz * ((int(idx4[2]) + os) + sz * (int(idx4[3]) + os)))) if FDparams.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 = " + FDparams.MemAllocStyle + " unsupported.") sys.exit(1) # Step 4d.ii: For each gridfunction and # point read from memory, call unique_idx, # then sort according to memory "address" # Input: list_of_points_read_from_memory[gridfunction][point], # gri.glb_gridfcs_list[gridfunction] # Output: 1) A list of points to be read from # memory, sorted according to memory # "address": # sorted_list_of_points_read_from_memory[gridfunction][point] # 2) A list containing the gridfunction # read at each point, with the number # of elements corresponding exactly # to the total number of points read # from memory for all gridfunctions: # read_from_memory_gf[] read_from_memory_gf = [] sorted_list_of_points_read_from_memory = [ [] for i in range(len(gri.glb_gridfcs_list)) ] for gfidx in range(len(gri.glb_gridfcs_list)): # Continue only if reading at least one point of gfidx from memory. # The sorting algorithm at the end of this code block is not # well-defined (will throw an error) if no points of gfidx are # read from memory. if len(list_of_points_read_from_memory[gfidx]) > 0: read_from_memory_index = [] for idx in list_of_points_read_from_memory[gfidx]: read_from_memory_gf.append(gri.glb_gridfcs_list[gfidx]) idxsplit = idx.split(',') idx4 = [ int(idxsplit[0]), int(idxsplit[1]), int(idxsplit[2]), int(idxsplit[3]) ] read_from_memory_index.append(unique_idx(idx4, FDparams)) # https://stackoverflow.com/questions/13668393/python-sorting-two-lists _unused_list, sorted_list_of_points_read_from_memory[gfidx] = \ [list(x) for x in zip(*sorted(zip(read_from_memory_index, list_of_points_read_from_memory[gfidx]), key=itemgetter(0)))] # Step 4e: Create the full C code string # for reading from memory: read_from_memory_Ccode = "" count = 0 for gfidx in range(len(gri.glb_gridfcs_list)): for pt in range(len(sorted_list_of_points_read_from_memory[gfidx])): read_from_memory_Ccode += read_from_memory_Ccode_onept( read_from_memory_gf[count].name, sorted_list_of_points_read_from_memory[gfidx][pt], FDparams) count += 1 return read_from_memory_Ccode
def add_FD_func_to_outC_function_dict( list_of_deriv_vars, list_of_base_gridfunction_names_in_derivs, list_of_deriv_operators, fdcoeffs, fdstencl): # Step 5.a.ii.A: First construct a list of all the unique finite difference functions list_of_uniq_deriv_operators = superfast_uniq(list_of_deriv_operators) Ctype = "REAL" if par.parval_from_str("grid::GridFuncMemAccess") == "ETK": Ctype = "CCTK_REAL" func_prefix = "order_" + str(FDparams.FD_CD_order) + "_" if FDparams.SIMD_enable == "True": Ctype = "REAL_SIMD_ARRAY" func_prefix = "SIMD_" + func_prefix # Stores the needed calls to the functions we're adding to outC_function_dict: FDfunccall_list = [] for op in list_of_uniq_deriv_operators: which_op_idx = find_which_op_idx(op, list_of_deriv_operators) rhs_expr = sp.sympify(0) for j in range(len(fdcoeffs[which_op_idx])): var = sp.sympify("f" + varsuffix(fdstencl[which_op_idx][j], FDparams)) rhs_expr += fdcoeffs[which_op_idx][j] * var # Multiply each expression by the appropriate power # of 1/dx[i] invdx = [] used_invdx = [False, False, False, False] for d in range(FDparams.DIM): invdx.append(sp.sympify("invdx" + str(d))) # First-order or Kreiss-Oliger derivatives: if ((len(op) == 5 and "dKOD" in op) or (len(op) == 3 and "dD" in op) or (len(op) == 5 and ("dupD" in op or "ddnD" in op))): dirn = int(op[len(op) - 1]) rhs_expr *= invdx[dirn] used_invdx[dirn] = True # Second-order derivs: elif len(op) == 5 and "dDD" in op: dirn1 = int(op[len(op) - 2]) dirn2 = int(op[len(op) - 1]) used_invdx[dirn1] = used_invdx[dirn2] = True rhs_expr *= invdx[dirn1] * invdx[dirn2] else: print("Error: was unable to parse derivative operator: ", op) sys.exit(1) outfunc_params = "" for d in range(FDparams.DIM): if used_invdx[d]: outfunc_params += "const " + Ctype + " invdx" + str(d) + "," for j in range(len(fdcoeffs[which_op_idx])): var = sp.sympify("f" + varsuffix(fdstencl[which_op_idx][j], FDparams)) outfunc_params += "const " + Ctype + " " + str(var) if j != len(fdcoeffs[which_op_idx]) - 1: outfunc_params += "," for i in range(len(list_of_deriv_operators)): # print("comparing ",list_of_deriv_operators[i],op) if list_of_deriv_operators[i] == op: funccall = type__var( list_of_deriv_vars[i], FDparams) + " = " + func_prefix + "f_" + str(op) + "(" for d in range(FDparams.DIM): if used_invdx[d]: funccall += "invdx" + str(d) + "," gfname = list_of_base_gridfunction_names_in_derivs[i] for j in range(len(fdcoeffs[which_op_idx])): funccall += gfname + varsuffix(fdstencl[which_op_idx][j], FDparams) if j != len(fdcoeffs[which_op_idx]) - 1: funccall += "," funccall += ");" FDfunccall_list.append(funccall) # If the function already exists in the outC_function_dict, then do not add it; move to the next op. if func_prefix + "f_" + str(op) not in outC_function_dict: p = "preindent=1,SIMD_enable=" + FDparams.SIMD_enable + ",outCverbose=False,CSE_preprocess=True,includebraces=False" outFDstr = outputC(rhs_expr, "retval", "returnstring", params=p) outFDstr = outFDstr.replace("retval = ", "return ") add_to_Cfunction_dict( desc=" * (__FD_OPERATOR_FUNC__) Finite difference operator for " + str(op).replace("dDD", "second derivative: ").replace( "dD", "first derivative: ").replace( "dKOD", "Kreiss-Oliger derivative: ").replace( "dupD", "upwinded derivative: ").replace( "ddnD", "downwinded derivative: "), type="static " + Ctype + " _NOINLINE _UNUSED", name=func_prefix + "f_" + str(op), opts="DisableCparameters", params=outfunc_params, preloop="", body=outFDstr) return FDfunccall_list
def output_H_and_derivs(): # Open and read the file of numerical expressions (written in SymPy syntax) computing the SEOBNRv3 Hamiltonian. f = open("SEOBNR/Hamstring.txt", 'r') Hamstring = str(f.read()) f.close() # Split Hamstring by carriage returns. Hamterms = Hamstring.splitlines() # Create 'lr' array to store each left-hand side and right-hand side of Hamstring as strings. lr = [] # Loop over each line in Hamstring to separate the left- and right-hand sides. for i in range(len(Hamterms)): # Ignore lines with 2 or fewer characters and those starting with # if len(Hamterms[i]) > 2 and Hamterms[i][0] != "#": # Split each line by its equals sign. splitHamterms = Hamterms[i].split("=") # Append terms to the 'lr' array, removing spaces, "sp." prefixes, and replacing Lambda->Lamb (Lambda is a # protected keyword) lr.append(lhrh(lhs=splitHamterms[0].replace(" ", "").replace("Lambda", "Lamb"), rhs=splitHamterms[1].replace(" ", "").replace("sp.", "").replace("Lambda", "Lamb"))) # Declare the symbol 'xx', which we use to denote each left-hand side as a function xx = sp.Symbol('xx') # Create arrays to store simplified left- and right-hand expressions, as well as left-hand sides designated as # functions. func = [] lhss = [] rhss = [] # Affix '(xx)' to each left-hand side as a function designation; separate and simplify left- and right-hand sides # of the numerical expressions. for i in range(len(lr)): func.append(sp.sympify(sp.Function(lr[i].lhs)(xx))) lhss.append(sp.sympify(lr[i].lhs)) rhss.append(sp.sympify(lr[i].rhs)) # Creat array for and generate a list of all the "free symbols" in the right-hand side expressions. full_symbol_list_with_dups = [] for i in range(len(lr)): for var in rhss[i].free_symbols: full_symbol_list_with_dups.append(var) # Remove all duplicated "free symbols" from the right-hand side expressions. full_symbol_list = superfast_uniq(full_symbol_list_with_dups) # Declare input constants. m1, m2, eta, KK, k0, k1, d1v2, dheffSSv2 = sp.symbols("m1 m2 eta KK k0 k1 d1v2 dheffSSv2", real=True) tortoise, EMgamma = sp.symbols("tortoise EMgamma", real=True) input_constants = [m1, m2, eta, KK, k0, k1, d1v2, dheffSSv2, tortoise, EMgamma] # Derivatives of input constants will always be zero, so remove them from the full_symbol_list. for inputconst in input_constants: for symbol in full_symbol_list: if str(symbol) == str(inputconst): full_symbol_list.remove(symbol) # Add symbols to the function list and replace right-hand side terms with their function equivalent. full_function_list = [] for symb in full_symbol_list: func = sp.sympify(sp.Function(str(symb))(xx)) full_function_list.append(func) for i in range(len(rhss)): for var in rhss[i].free_symbols: if str(var) == str(symb): rhss[i] = rhss[i].subs(var, func) # Create left- and right-hand side 'deriv' arrays lhss_deriv = [] rhss_deriv = [] # Differentiate with respect to xx, remove '(xx)', and replace xx with 'prm' notation. for i in range(len(rhss)): lhss_deriv.append(sp.sympify(str(lhss[i]) + "prm")) newrhs = sp.sympify( str(sp.diff(rhss[i], xx)).replace("(xx)", "").replace(", xx", "prm").replace("Derivative", "")) rhss_deriv.append(newrhs) # Simplify derivative expressions with simplify_deriv() lhss_deriv_simp, rhss_deriv_simp = simplify_deriv(lhss_deriv, rhss_deriv) lhss_deriv = lhss_deriv_simp rhss_deriv = rhss_deriv_simp # Generate partial derivatives with respect to each of the twelve input variables lhss_deriv_x, rhss_deriv_x = deriv_onevar(lhss_deriv, rhss_deriv, xprm=1, yprm=0, zprm=0, pxprm=0, pyprm=0, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_y, rhss_deriv_y = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=1, zprm=0, pxprm=0, pyprm=0, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_z, rhss_deriv_z = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=1, pxprm=0, pyprm=0, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_px, rhss_deriv_px = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=1, pyprm=0, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_py, rhss_deriv_py = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=0, pyprm=1, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_pz, rhss_deriv_pz = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=0, pyprm=0, pzprm=1, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_s1x, rhss_deriv_s1x = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=0, pyprm=0, pzprm=0, s1xprm=1, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_s1y, rhss_deriv_s1y = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=0, pyprm=0, pzprm=0, s1xprm=0, s1yprm=1, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_s1z, rhss_deriv_s1z = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=0, pyprm=0, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=1, s2xprm=0, s2yprm=0, s2zprm=0) lhss_deriv_s2x, rhss_deriv_s2x = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=0, pyprm=0, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=1, s2yprm=0, s2zprm=0) lhss_deriv_s2y, rhss_deriv_s2y = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=0, pyprm=0, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=1, s2zprm=0) lhss_deriv_s2z, rhss_deriv_s2z = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, pxprm=0, pyprm=0, pzprm=0, s1xprm=0, s1yprm=0, s1zprm=0, s2xprm=0, s2yprm=0, s2zprm=1) # Prepare to output derivative expressions in C syntax outstring = "/* SEOBNR Hamiltonian expression: */\n" outstringsp = "" outsplhs = [] outsprhs = [] for i in range(len(lr)): outstring += outputC(sp.sympify(lr[i].rhs), lr[i].lhs, "returnstring", "outCverbose=False,includebraces=False,CSE_enable=False") outstringsp += lr[i].lhs + " = " + lr[i].rhs + "\n" outsplhs.append(sp.sympify(lr[i].lhs)) outsprhs.append(sp.sympify(lr[i].rhs)) outstring += "\n\n\n/* SEOBNR \partial_x H expression: */\n" for i in range(len(lhss_deriv_x)): outstring += outputC(rhss_deriv_x[i], str(lhss_deriv_x[i]), "returnstring", "outCverbose=False,includebraces=False,CSE_enable=False") outstringsp += str(lhss_deriv_x[i]) + " = " + str(rhss_deriv_x[i]) + "\n" outsplhs.append(lhss_deriv_x[i]) outsprhs.append(rhss_deriv_x[i]) with open("SEOBNR_Playground_Pycodes/numpy_expressions.py", "w") as file: file.write("""from __future__ import division import numpy as np def compute_dHdq(m1, m2, eta, x, y, z, px, py, pz, s1x, s1y, s1z, s2x, s2y, s2z, KK, k0, k1, d1v2, dheffSSv2, tortoise, EMgamma): """) for i in range(len(lr) - 1): file.write(" " + lr[i].lhs + " = " + str(lr[i].rhs).replace("Rational(", "np.true_divide(").replace("sqrt(", "np.sqrt(").replace( "log(", "np.log(").replace("sign(", "np.sign(").replace("Abs(", "np.abs(").replace("pi", "np.pi") + "\n") file.write(""" return Hreal""") # Playground agrees to this point... now need to figure out how to do CSE and output to different files!TylerK # Open and write to a file that, when called, will perform CSE on Hreal and all derivative expressions. with open("SEOBNR_Playground_Pycodes/sympy_expression.py", "w") as file: file.write("""import sympy as sp from outputC import * def sympy_cse(): m1,m2,x,y,z,px,py,pz,s1x,s1y,s1z,s2x,s2y,s2z = sp.symbols("m1 m2 x y z px py pz s1x s1y s1z s2x s2y s2z",real=True) eta,KK,k0,k1,d1v2,dheffSSv2 = sp.symbols("eta KK k0 k1 d1v2 dheffSSv2",real=True) tortoise,EMgamma = sp.symbols("tortoise EMgamma",real=True) """) # Convert Numpy functions in expressions to SymPy expressions. for i in range(len(lr)): file.write(" " + lr[i].lhs + " = " + str(lr[i].rhs).replace("Abs(", "sp.Abs(").replace("sqrt(", "sp.sqrt(").replace("log(","sp.log(").replace("sign(", "sp.sign(").replace("Rational(", "sp.Rational(").replace("pi", "sp.pi") + "\n") # Call the CSE routine for Hreal file.write(""" CSE_results = sp.cse(Hreal, sp.numbered_symbols("Htmp"), order='canonical') with open("SEOBNR_Playground_Pycodes/numpy_expressions.py", "a") as file: for commonsubexpression in CSE_results[0]: file.write(" "+str(commonsubexpression[0])+" = "+str(commonsubexpression[1]).replace("sqrt(","np.sqrt(").replace("log(","np.log(").replace("sign(","np.sign(").replace("Abs(", "np.abs(").replace("pi", "np.pi")+"\\n") for i,result in enumerate(CSE_results[1]): file.write(" Hreal = "+str(result).replace("sqrt(","np.sqrt(")+"\\n") """) # Declare all Hamiltonian terms as symbols so they can be used in derivative computations. for i in range(len(lr)): file.write(" " + lr[i].lhs + " = " + "sp.symbols(\"" + lr[i].lhs + "\")\n") # Print all terms for each of the partial derivatives. #TylerK: make this into a loop so there's not a buch of repeated code for i in range(len(lhss_deriv_x)): file.write(" " + str(lhss_deriv_x[i]).replace("prm", "prm_x") + " = " + replace_numpy_funcs(rhss_deriv_x[i]).replace("prm", "prm_x") + "\n") for i in range(len(lhss_deriv_y)): file.write(" " + str(lhss_deriv_y[i]).replace("prm", "prm_y") + " = " + replace_numpy_funcs(rhss_deriv_y[i]).replace("prm", "prm_y") + "\n") for i in range(len(lhss_deriv_z)): file.write(" " + str(lhss_deriv_z[i]).replace("prm", "prm_z") + " = " + replace_numpy_funcs(rhss_deriv_z[i]).replace("prm", "prm_z") + "\n") for i in range(len(lhss_deriv_px)): file.write(" " + str(lhss_deriv_px[i]).replace("prm", "prm_px") + " = " + replace_numpy_funcs(rhss_deriv_px[i]).replace("prm", "prm_px") + "\n") for i in range(len(lhss_deriv_py)): file.write(" " + str(lhss_deriv_py[i]).replace("prm", "prm_py") + " = " + replace_numpy_funcs(rhss_deriv_py[i]).replace("prm", "prm_py") + "\n") for i in range(len(lhss_deriv_pz)): file.write(" " + str(lhss_deriv_pz[i]).replace("prm", "prm_pz") + " = " + replace_numpy_funcs(rhss_deriv_pz[i]).replace("prm", "prm_pz") + "\n") for i in range(len(lhss_deriv_s1x)): file.write(" " + str(lhss_deriv_s1x[i]).replace("prm", "prm_s1x") + " = " + replace_numpy_funcs(rhss_deriv_s1x[i]).replace("prm", "prm_s1x") + "\n") for i in range(len(lhss_deriv_s1y)): file.write(" " + str(lhss_deriv_s1y[i]).replace("prm", "prm_s1y") + " = " + replace_numpy_funcs(rhss_deriv_s1y[i]).replace("prm", "prm_s1y") + "\n") for i in range(len(lhss_deriv_s1z)): file.write(" " + str(lhss_deriv_s1z[i]).replace("prm", "prm_s1z") + " = " + replace_numpy_funcs(rhss_deriv_s1z[i]).replace("prm", "prm_s1z") + "\n") for i in range(len(lhss_deriv_s2x)): file.write(" " + str(lhss_deriv_s2x[i]).replace("prm", "prm_s2x") + " = " + replace_numpy_funcs(rhss_deriv_s2x[i]).replace("prm", "prm_s2x") + "\n") for i in range(len(lhss_deriv_s2y)): file.write(" " + str(lhss_deriv_s2y[i]).replace("prm", "prm_s2y") + " = " + replace_numpy_funcs(rhss_deriv_s2y[i]).replace("prm", "prm_s2y") + "\n") for i in range(len(lhss_deriv_s2z)): file.write(" " + str(lhss_deriv_s2z[i]).replace("prm", "prm_s2z") + " = " + replace_numpy_funcs(rhss_deriv_s2z[i]).replace("prm", "prm_s2z") + "\n") # Perform CSE file.write(""" output_list = ["Hrealprm_x","Hrealprm_y","Hrealprm_z","Hrealprm_px","Hrealprm_py","Hrealprm_pz", "Hrealprm_s1x","Hrealprm_s1y","Hrealprm_s1z","Hrealprm_s2x","Hrealprm_s2y","Hrealprm_s2z"] expression_list = [Hrealprm_x,Hrealprm_y,Hrealprm_z,Hrealprm_px,Hrealprm_py,Hrealprm_pz, Hrealprm_s1x,Hrealprm_s1y,Hrealprm_s1z,Hrealprm_s2x,Hrealprm_s2y,Hrealprm_s2z] CSE_results = sp.cse(expression_list, sp.numbered_symbols("tmp"), order='canonical') with open("SEOBNR_Playground_Pycodes/numpy_expressions.py", "a") as file: for commonsubexpression in CSE_results[0]: file.write(" "+str(commonsubexpression[0])+" = "+str(commonsubexpression[1]).replace("sqrt(","np.sqrt(").replace("log(","np.log(").replace("sign(","np.sign(").replace("Abs(", "np.abs(")+"\\n") for i,result in enumerate(CSE_results[1]): file.write(" "+str(output_list[i])+" = "+str(result).replace("sqrt(","np.sqrt(").replace("log(","np.log(").replace("sign(","np.sign(").replace("Abs(", "np.abs(")+"\\n") for i,result in enumerate(CSE_results[1]): if i > 0: file.write(","+str(output_list[i])) else: file.write(" return np.array([Hreal,"+str(output_list[i])) file.write("])") """)
def generate_list_of_deriv_vars_from_lhrh_sympyexpr_list( sympyexpr_list, FDparams): """ Generate from list of SymPy expressions in the form [lhrh(lhs=var, rhs=expr),lhrh(...),...] all derivative expressions. :param sympyexpr_list <- list of SymPy expressions in the form [lhrh(lhs=var, rhs=expr),lhrh(...),...]: :return list of derivative variables; creating _ddnD in case upwinding is enabled with control vector: >>> from outputC import lhrh >>> import indexedexp as ixp >>> import grid as gri >>> import NRPy_param_funcs as par >>> from finite_difference_helpers import generate_list_of_deriv_vars_from_lhrh_sympyexpr_list,FDparams >>> aDD = ixp.register_gridfunctions_for_single_rank2("EVOL","aDD","sym01") >>> aDD_dDD = ixp.declarerank4("aDD_dDD","sym01_sym23") >>> aDD_dupD = ixp.declarerank3("aDD_dupD","sym01") >>> betaU = ixp.register_gridfunctions_for_single_rank1("EVOL","betaU") >>> a0,a1,b,c = par.Cparameters("REAL",__name__,["a0","a1","b","c"],1) >>> FDparams.upwindcontrolvec=betaU >>> exprlist = [lhrh(lhs=a0,rhs=b*aDD[1][0] + b*aDD_dDD[2][1][2][1] + c*aDD_dDD[0][1][1][0]), \ lhrh(lhs=a1,rhs=aDD_dDD[1][0][0][1] + c*aDD_dupD[0][2][1]*betaU[1])] >>> generate_list_of_deriv_vars_from_lhrh_sympyexpr_list(exprlist,FDparams) [aDD_dDD0101, aDD_dDD1212, aDD_ddnD021, aDD_dupD021] """ # Step 1a: # Create a list of free symbols in the sympy expr list # that are registered neither as gridfunctions nor # as C parameters. These *must* be derivatives, # so we call the list "list_of_deriv_vars" list_of_deriv_vars_with_duplicates = [] for expr in sympyexpr_list: for var in expr.rhs.free_symbols: vartype = gri.variable_type(var) if vartype == "other": # vartype=="other" should ONLY refer to derivatives, so # if "_dD" or variants do not appear in a variable classified # neither as a gridfunction nor a Cparameter, then error out. if ("_dD" in str(var)) or \ ("_dKOD" in str(var)) or \ ("_dupD" in str(var)) or \ ("_ddnD" in str(var)): list_of_deriv_vars_with_duplicates.append(var) else: print("Error: Unregistered variable \"" + str(var) + "\" in SymPy expression for " + expr.lhs) print( "All variables in SymPy expressions passed to FD_outputC() must be registered" ) print( "in NRPy+ as either a gridfunction or Cparameter, by calling" ) print( str(var) + " = register_gridfunctions...() (in ixp/grid) if \"" + str(var) + "\" is a gridfunction, or") print( str(var) + " = Cparameters() (in par) otherwise (e.g., if it is a free parameter set at C runtime)." ) sys.exit(1) list_of_deriv_vars = superfast_uniq(list_of_deriv_vars_with_duplicates) # Upwinding with respect to a control vector: algorithm description. # To enable, set the FD_outputC()'s fourth function argument to the # desired control vector. In BSSN, the betaU vector controls the upwinding. # See https://arxiv.org/pdf/gr-qc/0206072.pdf for motivation and # https://arxiv.org/pdf/gr-qc/0109032.pdf for implementation details, # at second order. Note that the BSSN shift vector behaves like a *negative* # velocity. See http://www.damtp.cam.ac.uk/user/naweb/ii/advection/advection.php # for a very basic example motivating this choice. # Step 1b: For each variable with suffix _dupD, append to # the list_of_deriv_vars the corresponding _ddnD. # Both are required for control-vector upwinding. See # the above print() block for further documentation # on upwinding--both motivation and implementation # details. if FDparams.upwindcontrolvec != "": for var in list_of_deriv_vars: if "_dupD" in str(var): list_of_deriv_vars.append( sp.sympify(str(var).replace("_dupD", "_ddnD"))) # Finally, sort the list_of_deriv_vars. This ensures # consistency in the C code output, and might even be # tuned to reduce cache misses. # Thanks to Aaron Meurer for this nice one-liner! return sorted(list_of_deriv_vars, key=sp.default_sort_key)
def single_RK_substep_input_symbolic(commentblock, RHS_str, RHS_input_str, RHS_output_str, RK_lhss_list, RK_rhss_list, post_RHS_list, post_RHS_output_list, enable_SIMD=False, enable_griddata=False, gf_aliases="", post_post_RHS_string=""): return_str = commentblock + "\n" if not isinstance(RK_lhss_list, list): RK_lhss_list = [RK_lhss_list] if not isinstance(RK_rhss_list, list): RK_rhss_list = [RK_rhss_list] if not isinstance(post_RHS_list, list): post_RHS_list = [post_RHS_list] if not isinstance(post_RHS_output_list, list): post_RHS_output_list = [post_RHS_output_list] indent = "" if enable_griddata: return_str += "{\n" + indent_Ccode(gf_aliases, " ") indent = " " # Part 1: RHS evaluation: return_str += indent_Ccode(str(RHS_str).replace( "RK_INPUT_GFS", str(RHS_input_str).replace("gfsL", "gfs")).replace( "RK_OUTPUT_GFS", str(RHS_output_str).replace("gfsL", "gfs")) + "\n", indent=indent) # Part 2: RK update if enable_SIMD: return_str += "#pragma omp parallel for\n" return_str += indent + "for(int i=0;i<Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2*NUM_EVOL_GFS;i+=SIMD_width) {\n" else: return_str += indent + "LOOP_ALL_GFS_GPS(i) {\n" type = "REAL" if enable_SIMD: type = "REAL_SIMD_ARRAY" RK_lhss_str_list = [] for i, el in enumerate(RK_lhss_list): if enable_SIMD: RK_lhss_str_list.append(indent + "const REAL_SIMD_ARRAY __RHS_exp_" + str(i)) else: RK_lhss_str_list.append(indent + str(el).replace("gfsL", "gfs[i]")) read_list = [] for el in RK_rhss_list: for read in list(sp.ordered(el.free_symbols)): read_list.append(read) read_list_uniq = superfast_uniq(read_list) for el in read_list_uniq: if str(el) != "dt": if enable_SIMD: return_str += indent + " const " + type + " " + str( el) + " = ReadSIMD(&" + str(el).replace("gfsL", "gfs[i]") + ");\n" else: return_str += indent + " const " + type + " " + str( el) + " = " + str(el).replace("gfsL", "gfs[i]") + ";\n" if enable_SIMD: return_str += indent + " const REAL_SIMD_ARRAY DT = ConstSIMD(dt);\n" preindent = "1" if enable_griddata: preindent = "2" kernel = outputC(RK_rhss_list, RK_lhss_str_list, filename="returnstring", params="includebraces=False,preindent=" + preindent + ",outCverbose=False,enable_SIMD=" + str(enable_SIMD)) if enable_SIMD: return_str += kernel.replace("dt", "DT") for i, el in enumerate(RK_lhss_list): return_str += " WriteSIMD(&" + str(el).replace( "gfsL", "gfs[i]") + ", __RHS_exp_" + str(i) + ");\n" else: return_str += kernel return_str += indent + "}\n" # Part 3: Call post-RHS functions for post_RHS, post_RHS_output in zip(post_RHS_list, post_RHS_output_list): return_str += indent_Ccode( post_RHS.replace("RK_OUTPUT_GFS", str(post_RHS_output).replace("gfsL", "gfs"))) if enable_griddata: return_str += "}\n" for post_RHS, post_RHS_output in zip(post_RHS_list, post_RHS_output_list): return_str += indent_Ccode( post_post_RHS_string.replace( "RK_OUTPUT_GFS", str(post_RHS_output).replace("gfsL", "gfs")), "") return return_str
def add_HI_func_to_outC_function_dict( list_of_interp_vars, list_of_base_gridfunction_names_in_interps, list_of_interp_operators, hicoeffs, histencl): # Step 5.a.ii.A: First construct a list of all the unique Hermite interpolator functions list_of_uniq_interp_operators = superfast_uniq(list_of_interp_operators) c_type = "REAL" if par.parval_from_str("grid::GridFuncMemAccess") == "ETK": c_type = "CCTK_REAL" func_prefix = "order_" + str(HIparams.HI_DM_order) + "_" if HIparams.enable_SIMD == "True": c_type = "REAL_SIMD_ARRAY" func_prefix = "SIMD_" + func_prefix # Stores the needed calls to the functions we're adding to outC_function_dict: HIfunccall_list = [] for op in list_of_uniq_interp_operators: which_op_idx = find_which_op_idx(op, list_of_interp_operators) rhs_expr = sp.sympify(0) for j in range(len(hicoeffs[which_op_idx])): var = sp.sympify("f" + varsuffix(histencl[which_op_idx][j], HIparams)) rhs_expr += hicoeffs[which_op_idx][j] * var # Multiply each expression by the appropriate power # of 1/dx[i] invdx = [] used_invdx = [False, False, False, False] for d in range(HIparams.DIM): invdx.append(sp.sympify("invdx" + str(d))) # First-order or Kreiss-Oliger interpolators: if ((len(op) == 5 and "dKOD" in op) or (len(op) == 3 and "dD" in op) or (len(op) == 5 and ("dupD" in op or "ddnD" in op))): dirn = int(op[len(op) - 1]) rhs_expr *= invdx[dirn] used_invdx[dirn] = True # Second-order interps: elif len(op) == 5 and "dDD" in op: dirn1 = int(op[len(op) - 2]) dirn2 = int(op[len(op) - 1]) used_invdx[dirn1] = used_invdx[dirn2] = True rhs_expr *= invdx[dirn1] * invdx[dirn2] else: print("Error: was unable to parse interpolator operator: ", op) sys.exit(1) outfunc_params = "" for d in range(HIparams.DIM): if used_invdx[d]: outfunc_params += "const " + c_type + " invdx" + str(d) + "," for j in range(len(hicoeffs[which_op_idx])): var = sp.sympify("f" + varsuffix(histencl[which_op_idx][j], HIparams)) outfunc_params += "const " + c_type + " " + str(var) if j != len(hicoeffs[which_op_idx]) - 1: outfunc_params += "," for i in range(len(list_of_interp_operators)): # print("comparing ",list_of_interp_operators[i],op) if list_of_interp_operators[i] == op: funccall = type__var( list_of_interp_vars[i], HIparams) + " = " + func_prefix + "f_" + str(op) + "(" for d in range(HIparams.DIM): if used_invdx[d]: funccall += "invdx" + str(d) + "," gfname = list_of_base_gridfunction_names_in_interps[i] for j in range(len(hicoeffs[which_op_idx])): funccall += gfname + varsuffix(histencl[which_op_idx][j], HIparams) if j != len(hicoeffs[which_op_idx]) - 1: funccall += "," funccall += ");" HIfunccall_list.append(funccall) # If the function already exists in the outC_function_dict, then do not add it; move to the next op. if func_prefix + "f_" + str(op) not in outC_function_dict: p = "preindent=1,enable_SIMD=" + HIparams.enable_SIMD + ",outCverbose=False,CSE_preprocess=True,includebraces=False" outHIstr = outputC(rhs_expr, "retval", "returnstring", params=p) outHIstr = outHIstr.replace("retval = ", "return ") add_to_Cfunction_dict( desc= " * (__HI_OPERATOR_FUNC__) Hermite interpolator operator for " + str(op).replace("dDD", "second interpolator: ").replace( "dD", "first interpolator: ").replace( "dKOD", "Kreiss-Oliger interpolator: ").replace( "dupD", "upwinded interpolator: ").replace( "ddnD", "downwinded interpolator: ") + " direction. In Cartesian coordinates, directions 0,1,2 correspond to x,y,z directions, respectively.", c_type="static " + c_type + " _NOINLINE _UNUSED", name=func_prefix + "f_" + str(op), enableCparameters=False, params=outfunc_params, preloop="", body=outHIstr) return HIfunccall_list
def generate_list_of_interp_vars_from_lhrh_sympyexpr_list( sympyexpr_list, HIparams): """ Generate from list of SymPy expressions in the form [lhrh(lhs=var, rhs=expr),lhrh(...),...] all interpolator expressions. :param sympyexpr_list <- list of SymPy expressions in the form [lhrh(lhs=var, rhs=expr),lhrh(...),...]: :return list of interpolator variables; creating _ddnD in case upwinding is enabled with control vector: >>> from outputC import lhrh >>> import indexedexp as ixp >>> import grid as gri >>> import NRPy_param_funcs as par >>> from hermite_interpolator_helpers import generate_list_of_interp_vars_from_lhrh_sympyexpr_list,HIparams >>> aDD = ixp.register_gridfunctions_for_single_rank2("EVOL","aDD","sym01") >>> aDD_dDD = ixp.declarerank4("aDD_dDD","sym01_sym23") >>> aDD_dupD = ixp.declarerank3("aDD_dupD","sym01") >>> betaU = ixp.register_gridfunctions_for_single_rank1("EVOL","betaU") >>> a0,a1,b,c = par.Cparameters("REAL",__name__,["a0","a1","b","c"],1) >>> HIparams.upwindcontrolvec=betaU >>> exprlist = [lhrh(lhs=a0,rhs=b*aDD[1][0] + b*aDD_dDD[2][1][2][1] + c*aDD_dDD[0][1][1][0]), \ lhrh(lhs=a1,rhs=aDD_dDD[1][0][0][1] + c*aDD_dupD[0][2][1]*betaU[1])] >>> generate_list_of_interp_vars_from_lhrh_sympyexpr_list(exprlist,HIparams) [aDD_dDD0101, aDD_dDD1212, aDD_ddnD021, aDD_dupD021] """ # Step 1a: # Create a list of free symbols in the sympy expr list # that are registered neither as gridfunctions nor # as C parameters. These *must* be interpolators, # so we call the list "list_of_interp_vars" list_of_interp_vars_with_duplicates = [] for expr in sympyexpr_list: for var in expr.rhs.free_symbols: vartype = gri.variable_type(var) if vartype == "other": # vartype=="other" should ONLY refer to interpolators, so # if "_dD" or variants do not appear in a variable classified # neither as a gridfunction nor a Cparameter, then error out. if ("_dD" in str(var)) or \ ("_dKOD" in str(var)) or \ ("_dupD" in str(var)) or \ ("_ddnD" in str(var)): list_of_interp_vars_with_duplicates.append(var) else: print("Error: Unregistered variable \"" + str(var) + "\" in SymPy expression for " + expr.lhs) print( "All variables in SymPy expressions passed to HI_outputC() must be registered" ) print( "in NRPy+ as either a gridfunction or Cparameter, by calling" ) print( str(var) + " = register_gridfunctions...() (in ixp/grid) if \"" + str(var) + "\" is a gridfunction, or") print( str(var) + " = Cparameters() (in par) otherwise (e.g., if it is a free parameter set at C runtime)." ) sys.exit(1) list_of_interp_vars = superfast_uniq(list_of_interp_vars_with_duplicates) # Step 1b: For each variable with suffix _dupD, append to # the list_of_interp_vars the corresponding _ddnD. if HIparams.upwindcontrolvec != "": for var in list_of_interp_vars: if "_dupD" in str(var): list_of_interp_vars.append( sp.sympify(str(var).replace("_dupD", "_ddnD"))) # Finally, sort the list_of_interp_vars. This ensures # consistency in the C code output, and might even be # tuned to reduce cache misses. # Thanks to Aaron Meurer for this nice one-liner! return sorted(list_of_interp_vars, key=sp.default_sort_key)
def output_H_and_derivs(): # Open and read the file of numerical expressions (written in SymPy syntax) computing the SEOBNRv3 Hamiltonian. #f = open("SEOBNR/Hamstring.txt", 'r') f = open("SEOBNR/SymPy_Hreal_on_bottom.txt", 'r') Hamstring = str(f.read()) f.close() # Split Hamstring by carriage returns. Hamterms = Hamstring.splitlines() # Create 'lr' array to store each left-hand side and right-hand side of Hamstring as strings. lr = [] # Loop over each line in Hamstring to separate the left- and right-hand sides. for i in range(len(Hamterms)): # Ignore lines with 2 or fewer characters and those starting with # if len(Hamterms[i]) > 2 and Hamterms[i][0] != "#": # Split each line by its equals sign. splitHamterms = Hamterms[i].split("=") # Append terms to the 'lr' array, removing spaces, "sp." prefixes, and replacing Lambda->Lamb (Lambda is a # protected keyword) lr.append(lhrh(lhs=splitHamterms[0].replace(" ", "").replace("Lambda", "Lamb"), rhs=splitHamterms[1].replace(" ", "").replace("sp.", "").replace("Lambda", "Lamb"))) # Declare the symbol 'xx', which we use to denote each left-hand side as a function xx = sp.Symbol('xx') # Create arrays to store simplified left- and right-hand expressions, as well as left-hand sides designated as # functions. func = [] lhss = [] rhss = [] # Affix '(xx)' to each left-hand side as a function designation; separate and simplify left- and right-hand sides # of the numerical expressions. for i in range(len(lr)): func.append(sp.sympify(sp.Function(lr[i].lhs)(xx))) lhss.append(sp.sympify(lr[i].lhs)) rhss.append(sp.sympify(lr[i].rhs)) # Creat array for and generate a list of all the "free symbols" in the right-hand side expressions. full_symbol_list_with_dups = [] for i in range(len(lr)): for var in rhss[i].free_symbols: full_symbol_list_with_dups.append(var) # Remove all duplicated "free symbols" from the right-hand side expressions. full_symbol_list = superfast_uniq(full_symbol_list_with_dups) # Declare input constants. m1, m2, eta, KK, k0, k1, dSO, dSS = sp.symbols("m1 m2 eta KK k0 k1 dSO dSS", real=True) tortoise, EMgamma = sp.symbols("tortoise EMgamma", real=True) input_constants = [m1, m2, eta, KK, k0, k1, dSO, dSS, tortoise, EMgamma] # Derivatives of input constants will always be zero, so remove them from the full_symbol_list. for inputconst in input_constants: for symbol in full_symbol_list: if str(symbol) == str(inputconst): full_symbol_list.remove(symbol) # Add symbols to the function list and replace right-hand side terms with their function equivalent. full_function_list = [] for symb in full_symbol_list: func = sp.sympify(sp.Function(str(symb))(xx)) full_function_list.append(func) for i in range(len(rhss)): for var in rhss[i].free_symbols: if str(var) == str(symb): rhss[i] = rhss[i].subs(var, func) # Create left- and right-hand side 'deriv' arrays lhss_deriv = [] rhss_deriv = [] # Differentiate with respect to xx, remove '(xx)', and replace xx with 'prm' notation. for i in range(len(rhss)): lhss_deriv.append(sp.sympify(str(lhss[i]) + "prm")) newrhs = sp.sympify( str(sp.diff(rhss[i], xx)).replace("(xx)", "").replace(", xx", "prm").replace("Derivative", "")) rhss_deriv.append(newrhs) # Simplify derivative expressions with simplify_deriv() lhss_deriv_simp, rhss_deriv_simp = simplify_deriv(lhss_deriv, rhss_deriv) lhss_deriv = lhss_deriv_simp rhss_deriv = rhss_deriv_simp # Generate partial derivatives with respect to each of the twelve input variables lhss_deriv_x, rhss_deriv_x = deriv_onevar(lhss_deriv, rhss_deriv, xprm=1, yprm=0, zprm=0, p1prm=0, p2prm=0, p3prm=0, S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=0) #lhss_deriv_y, rhss_deriv_y = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=1, zprm=0, p1prm=0, p2prm=0, p3prm=0, #S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=0) #lhss_deriv_z, rhss_deriv_z = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=1, p1prm=0, p2prm=0, p3prm=0, #S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=0) #lhss_deriv_p1, rhss_deriv_p1 = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=1, p2prm=0, #p3prm=0, S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=0) lhss_deriv_p2, rhss_deriv_p2 = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=0, p2prm=1, p3prm=0, S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=0) lhss_deriv_p3, rhss_deriv_p3 = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=0, p2prm=0, p3prm=1, S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=0) #lhss_deriv_S1x, rhss_deriv_S1x = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=0, p2prm=0, #p3prm=0, S1xprm=1, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=0) #lhss_deriv_S1y, rhss_deriv_S1y = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=0, p2prm=0, #p3prm=0, S1xprm=0, S1yprm=1, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=0) #lhss_deriv_S1z, rhss_deriv_S1z = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=0, p2prm=0, #p3prm=0, S1xprm=0, S1yprm=0, S1zprm=1, S2xprm=0, S2yprm=0, S2zprm=0) #lhss_deriv_S2x, rhss_deriv_S2x = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=0, p2prm=0, #p3prm=0, S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=1, S2yprm=0, S2zprm=0) #lhss_deriv_S2y, rhss_deriv_S2y = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=0, p2prm=0, #p3prm=0, S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=1, S2zprm=0) #lhss_deriv_S2z, rhss_deriv_S2z = deriv_onevar(lhss_deriv, rhss_deriv, xprm=0, yprm=0, zprm=0, p1prm=0, p2prm=0, #p3prm=0, S1xprm=0, S1yprm=0, S1zprm=0, S2xprm=0, S2yprm=0, S2zprm=1) # Prepare to output derivative expressions in C syntax outstring = "/* SEOBNR Hamiltonian expression: */\n" outstringsp = "" outsplhs = [] outsprhs = [] for i in range(len(lr)): outstring += outputC(sp.sympify(lr[i].rhs), lr[i].lhs, "returnstring", "outCverbose=False,includebraces=False,CSE_enable=False") outstringsp += lr[i].lhs + " = " + lr[i].rhs + "\n" outsplhs.append(sp.sympify(lr[i].lhs)) outsprhs.append(sp.sympify(lr[i].rhs)) outstring += "\n\n\n/* SEOBNR \partial_x H expression: */\n" for i in range(len(lhss_deriv_x)): outstring += outputC(rhss_deriv_x[i], str(lhss_deriv_x[i]), "returnstring", "outCverbose=False,includebraces=False,CSE_enable=False") outstringsp += str(lhss_deriv_x[i]) + " = " + str(rhss_deriv_x[i]) + "\n" outsplhs.append(lhss_deriv_x[i]) outsprhs.append(rhss_deriv_x[i]) outstring += "\n\n\n/* SEOBNR \partial_p2 H expression: */\n" for i in range(len(lhss_deriv_p2)): outstring += outputC(rhss_deriv_p2[i], str(lhss_deriv_p2[i]), "returnstring", "outCverbose=False,includebraces=False,CSE_enable=False") outstringsp += str(lhss_deriv_p2[i]) + " = " + str(rhss_deriv_p2[i]) + "\n" outsplhs.append(lhss_deriv_p2[i]) outsprhs.append(rhss_deriv_p2[i]) outstring += "\n\n\n/* SEOBNR \partial_p3 H expression: */\n" for i in range(len(lhss_deriv_p3)): outstring += outputC(rhss_deriv_p3[i], str(lhss_deriv_p3[i]), "returnstring", "outCverbose=False,includebraces=False,CSE_enable=False") outstringsp += str(lhss_deriv_p3[i]) + " = " + str(rhss_deriv_p3[i]) + "\n" outsplhs.append(lhss_deriv_p3[i]) outsprhs.append(rhss_deriv_p3[i]) with open("SEOBNR_Playground_Pycodes/new_dHdx.py", "w") as file: file.write("""from __future__ import division import numpy as np def new_compute_dHdx(m1, m2, eta, x, y, z, p1, p2, p3, S1x, S1y, S1z, S2x, S2y, S2z, KK, k0, k1, dSO, dSS, tortoise, EMgamma): """) for i in range(len(lr) - 1): file.write(" " + lr[i].lhs + " = " + str(lr[i].rhs).replace("Rational(", "np.true_divide(").replace("sqrt(", "np.sqrt(").replace("log(", "np.log(").replace("sign(", "np.sign(").replace("Abs(", "np.abs(").replace("pi", "np.pi") + "\n") for i in range(len(lhss_deriv_x)): file.write(" " + str(lhss_deriv_x[i]).replace("prm", "prm_x") + " = " + replace_numpy_funcs(rhss_deriv_x[i]).replace("prm", "prm_x").replace("sp.sqrt(","np.sqrt(").replace("sp.log(","np.log(").replace("sp.sign(","np.sign(").replace("sp.Abs(", "np.abs(") + "\n") file.write(" return np.array([Hrealprm_x])") with open("SEOBNR_Playground_Pycodes/new_dHdp2.py", "w") as file: file.write("""from __future__ import division import numpy as np def new_compute_dHdp2(m1, m2, eta, x, y, z, p1, p2, p3, S1x, S1y, S1z, S2x, S2y, S2z, KK, k0, k1, dSO, dSS, tortoise, EMgamma): """) for i in range(len(lr) - 1): file.write(" " + lr[i].lhs + " = " + str(lr[i].rhs).replace("Rational(", "np.true_divide(").replace("sqrt(", "np.sqrt(").replace("log(", "np.log(").replace("sign(", "np.sign(").replace("Abs(", "np.abs(").replace("pi", "np.pi") + "\n") for i in range(len(lhss_deriv_p2)): file.write(" " + str(lhss_deriv_p2[i]).replace("prm", "prm_p2") + " = " + replace_numpy_funcs(rhss_deriv_p2[i]).replace("prm", "prm_p2").replace("sp.sqrt(","np.sqrt(").replace("sp.log(","np.log(").replace("sp.sign(","np.sign(").replace("sp.Abs(", "np.abs(") + "\n") file.write(" return np.array([Hrealprm_p2])") with open("SEOBNR_Playground_Pycodes/new_dHdp3.py", "w") as file: file.write("""from __future__ import division import numpy as np def new_compute_dHdp3(m1, m2, eta, x, y, z, p1, p2, p3, S1x, S1y, S1z, S2x, S2y, S2z, KK, k0, k1, dSO, dSS, tortoise, EMgamma): """) for i in range(len(lr) - 1): file.write(" " + lr[i].lhs + " = " + str(lr[i].rhs).replace("Rational(", "np.true_divide(").replace("sqrt(", "np.sqrt(").replace("log(", "np.log(").replace("sign(", "np.sign(").replace("Abs(", "np.abs(").replace("pi", "np.pi") + "\n") for i in range(len(lhss_deriv_p3)): file.write(" " + str(lhss_deriv_p3[i]).replace("prm", "prm_p3") + " = " + replace_numpy_funcs(rhss_deriv_p3[i]).replace("prm", "prm_p3").replace("sp.sqrt(","np.sqrt(").replace("sp.log(","np.log(").replace("sp.sign(","np.sign(").replace("sp.Abs(", "np.abs(") + "\n") file.write(" return np.array([Hrealprm_p3])") # TylerK: now create a text file listing only the terms so we can take a second derivative! with open("SEOBNR_Playground_Pycodes/dHdx.txt", "w") as file: for i in range(len(lr) - 1): file.write(lr[i].lhs + " = " + str(lr[i].rhs) + "\n") for i in range(len(lhss_deriv_x)): file.write(str(lhss_deriv_x[i]).replace("prm", "prm_x") + " = " + replace_numpy_funcs(rhss_deriv_x[i]).replace("prm", "prm_x") + "\n")
def FD_outputC(filename,sympyexpr_list, params="", upwindcontrolvec=""): outCparams = parse_outCparams_string(params) # Step 0.a: # In case sympyexpr_list is a single sympy expression, # convert it to a list with just one element: if type(sympyexpr_list) is not list: sympyexpr_list = [sympyexpr_list] # Step 0.b: # finite_difference.py takes control over outCparams.includebraces here, # which is necessary because outputC() is called twice: # first for the reads from main memory and finite difference # stencil expressions, and second for the SymPy expressions and # writes to main memory. # If outCparams.includebraces==True, then it will close off the braces # after the finite difference stencil expressions and start new ones # for the SymPy expressions and writes to main memory, resulting # in a non-functioning C code. # To get around this issue, we create braces around the entire # string of C output from this function, only if # outCparams.includebraces==True. # See Step 6 for corresponding end brace. if outCparams.includebraces == "True": Coutput = outCparams.preindent+"{\n" indent = " " else: Coutput = "" indent = "" # Step 1a: # Create a list of free symbols in the sympy expr list # that are registered neither as gridfunctions nor # as C parameters. These *must* be derivatives, # so we call the list "list_of_deriv_vars" list_of_deriv_vars_with_duplicates = [] for expr in sympyexpr_list: for var in expr.rhs.free_symbols: vartype = gri.variable_type(var) if vartype == "other": # vartype=="other" should ONLY refer to derivatives, so # if "_dD" or variants do not appear in a variable classified # neither as a gridfunction nor a Cparameter, then error out. if ("_dD" in str(var)) or \ ("_dKOD" in str(var)) or \ ("_dupD" in str(var)) or \ ("_ddnD" in str(var)): pass else: print("Error: Unregistered variable \""+str(var)+"\" in SymPy expression for "+expr.lhs) print("All variables in SymPy expressions passed to FD_outputC() must be registered") print("in NRPy+ as either a gridfunction or Cparameter, by calling") print(str(var)+" = register_gridfunctions...() (in ixp/grid) if \""+str(var)+"\" is a gridfunction, or") print(str(var)+" = Cparameters() (in par) otherwise (e.g., if it is a free parameter set at C runtime).") sys.exit(1) list_of_deriv_vars_with_duplicates.append(var) # elif vartype == "gridfunction": # list_of_deriv_vars_with_duplicates.append(var) list_of_deriv_vars = superfast_uniq(list_of_deriv_vars_with_duplicates) # Upwinding with respect to a control vector: algorithm description. # To enable, set the FD_outputC()'s fourth function argument to the # desired control vector. In BSSN, the betaU vector controls the upwinding. # See https://arxiv.org/pdf/gr-qc/0206072.pdf for motivation and # https://arxiv.org/pdf/gr-qc/0109032.pdf for implementation details, # at second order. Note that the BSSN shift vector behaves like a *negative* # velocity. See http://www.damtp.cam.ac.uk/user/naweb/ii/advection/advection.php # for a very basic example motivating this choice. # Step 1b: For each variable with suffix _dupD, append to # the list_of_deriv_vars the corresponding _ddnD. # Both are required for control-vector upwinding. See # the above print() block for further documentation # on upwinding--both motivation and implementation # details. if upwindcontrolvec != "": for var in list_of_deriv_vars: if "_dupD" in str(var): list_of_deriv_vars.append(sp.sympify(str(var).replace("_dupD","_ddnD"))) # Finally, sort the list_of_deriv_vars. This ensures # consistency in the C code output, and might even be # tuned to reduce cache misses. # Thanks to Aaron Meurer for this nice one-liner! list_of_deriv_vars = sorted(list_of_deriv_vars,key=sp.default_sort_key) # Step 2: # Process list_of_deriv_vars into a list of base gridfunctions # and a list of derivative operators. # Step 2a: # First determine the base gridfunction name from # "list_of_deriv_vars" deriv__base_gridfunction_name = [] deriv__operator = [] for var in list_of_deriv_vars: # Step 2a.1: Check that the number of juxtaposed integers # at the end of a variable name matches the # number of U's + D's in the variable name: varstr = str(var) num_UDs = 0 for i in range(len(varstr)): if varstr[i] == 'D' or varstr[i] == 'U': num_UDs += 1 num_digits = 0 i = len(varstr) - 1 while varstr[i].isdigit(): num_digits += 1 i-=1 if num_UDs != num_digits: print("Error: "+varstr+" has "+str(num_UDs)+" U's and D's, but ") print(str(num_digits)+" integers at the end. These must be equal.") print("Please rename your gridfunction.") sys.exit(1) # Step 2a.2: Based on the variable name, find the rank of # the underlying gridfunction of which we're # trying to take the derivative. rank = 0 # rank = "number of juxtaposed U's and D's before the underscore in a derivative expression" underscore_position = -1 for i in range(len(varstr)-1,-1,-1): if underscore_position > 0 and (varstr[i] == "U" or varstr[i] == "D"): rank += 1 if varstr[i] == "_": underscore_position = i # Step 2a.3: Based on the variable name, find the order # of the derivative we're trying to take. deriv_order = 0 # deriv_order = "number of D's after the underscore in a derivative expression" for i in range(underscore_position+1,len(varstr)): if (varstr[i] == "D"): deriv_order += 1 # Step 2a.4: Based on derivative order and rank, # store the base gridfunction name in # deriv__base_gridfunction_name[] deriv__base_gridfunction_name.append(varstr[0:underscore_position]+ varstr[len(varstr)-deriv_order-rank:len(varstr)-deriv_order]) deriv__operator.append(varstr[underscore_position+1:len(varstr)-deriv_order-rank]+ varstr[len(varstr)-deriv_order:len(varstr)]) # Step 2b: # Then check each base gridfunction to determine whether # it is indeed registered as a gridfunction. # If not, exit with error. for basegf in deriv__base_gridfunction_name: is_gf = False for gf in gri.glb_gridfcs_list: if basegf == str(gf.name): is_gf = True if not is_gf: print("Error: Attempting to take the derivative of "+basegf+", which is not a registered gridfunction.") print(" Make sure your gridfunction name does not have any underscores in it!") sys.exit(1) # Step 2c: # Check each derivative operator to make sure it is # supported. If not, error out. for i in range(len(deriv__operator)): found_derivID = False for derivID in ["dD","dupD","ddnD","dKOD"]: if derivID in deriv__operator[i]: found_derivID = True if not found_derivID: print("Error: Valid derivative operator in "+deriv__operator[i]+" not found.") sys.exit(1) # Step 2d (Upwinded derivatives algorithm, part 1): # If an upwinding control vector is specified, determine # which of the elements of the vector will be required. # This ensures that those elements are read from memory. # For example, if a symmetry axis is specified, # upwind derivatives with respect to only # two of the three dimensions are used. Here # we find all directions used for upwinding. if upwindcontrolvec != "": upwind_directions_unsorted_withdups = [] for deriv_op in deriv__operator: if "dupD" in deriv_op: if deriv_op[len(deriv_op)-1].isdigit(): dirn = int(deriv_op[len(deriv_op)-1]) upwind_directions_unsorted_withdups.append(dirn) else: print("Error: Derivative operator "+deriv_op+" does not contain a direction") sys.exit(1) upwind_directions = [] if len(upwind_directions_unsorted_withdups)>0: upwind_directions = superfast_uniq(upwind_directions_unsorted_withdups) upwind_directions = sorted(upwind_directions,key=sp.default_sort_key) # Step 3: # Evaluate the finite difference stencil for each # derivative operator, # TODO: being careful not to needlessly recompute. # Note: Each finite difference stencil consists # of two parts: # 1) The coefficient, and # 2) The index corresponding to the coefficient. # The former is stored as a rational number, and # the latter as a simple string, such that e.g., # in 3D, the empty string corresponds to (i,j,k), # the string "ip1" corresponds to (i+1,j,k), # the string "ip1kp1" corresponds to (i+1,j,k+1), # etc. fdcoeffs = [[] for i in range(len(deriv__operator))] fdstencl = [[[] for i in range(4)] for j in range(len(deriv__operator))] for i in range(len(deriv__operator)): fdcoeffs[i], fdstencl[i] = compute_fdcoeffs_fdstencl(deriv__operator[i]) # Step 4: # Create C code to read gridfunctions from memory # Step 4a: Compile list of points to read from memory # for each gridfunction i, based on list # provided in fdstencil[i][]. list_of_points_read_from_memory_with_duplicates = [[] for i in range(len(gri.glb_gridfcs_list))] for j in range(len(deriv__base_gridfunction_name)): derivgfname = deriv__base_gridfunction_name[j] # Next find the corresponding gridfunction index: for i in range(len(gri.glb_gridfcs_list)): gfname = gri.glb_gridfcs_list[i].name # If the gridfunction for the derivative matches, then # add to the list of points read from memory: if derivgfname == gfname: for k in range(len(fdstencl[j])): list_of_points_read_from_memory_with_duplicates[i].append(str(fdstencl[j][k][0]) + "," + \ str(fdstencl[j][k][1]) + "," + \ str(fdstencl[j][k][2]) + "," + \ str(fdstencl[j][k][3])) # Step 4b: "Zeroth derivative" case: # If gridfunction appears in expression not # as derivative (i.e., by itself), it must # be read from memory as well. for expr in range(len(sympyexpr_list)): for var in sympyexpr_list[expr].rhs.free_symbols: vartype = gri.variable_type(var) if vartype == "gridfunction": for i in range(len(gri.glb_gridfcs_list)): gfname = gri.glb_gridfcs_list[i].name if gfname == str(var): list_of_points_read_from_memory_with_duplicates[i].append("0,0,0,0") # Step 4c: Remove duplicates when reading from memory; # do not needlessly read the same variable # from memory twice. list_of_points_read_from_memory = [[] for i in range(len(gri.glb_gridfcs_list))] for i in range(len(gri.glb_gridfcs_list)): list_of_points_read_from_memory[i] = superfast_uniq(list_of_points_read_from_memory_with_duplicates[i]) # Step 4d: Minimize cache misses: # Sort the list of points read from # main memory by how they are stored # in memory. # Step 4d.i: Define a function that maps a gridpoint # index (i,j,k,l) to a unique memory "address", # which will correspond to the correct ordering # of actual memory addresses. # # Input: a list of 4 indices, e.g., (i,j,k,l) # corresponding to a gridpoint's *spatial* # index in memory (thus we support up to # 4D in space). If spatial dimension is # less than 4D, then just set latter # index/indices to zero. E.g., for 2D # spatial indexing, set (i,j,0,0). # Output: a single number, which when sorted # will yield a unique "address" in memory # such that consecutive addresses are # consecutive in memory. 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) # Step 4d.ii: For each gridfunction and # point read from memory, call unique_idx, # then sort according to memory "address" # Input: list_of_points_read_from_memory[gridfunction][point], # gri.glb_gridfcs_list[gridfunction] # Output: 1) A list of points to be read from # memory, sorted according to memory # "address": # sorted_list_of_points_read_from_memory[gridfunction][point] # 2) A list containing the gridfunction # read at each point, with the number # of elements corresponding exactly # to the total number of points read # from memory for all gridfunctions: # read_from_memory_gf[] read_from_memory_gf = [] sorted_list_of_points_read_from_memory = [[] for i in range(len(gri.glb_gridfcs_list))] for gfidx in range(len(gri.glb_gridfcs_list)): # Continue only if reading at least one point of gfidx from memory. # The sorting algorithm at the end of this code block is not # well-defined (will throw an error) if no points of gfidx are # read from memory. if len(list_of_points_read_from_memory[gfidx]) > 0: read_from_memory_index = [] for idx in list_of_points_read_from_memory[gfidx]: read_from_memory_gf.append(gri.glb_gridfcs_list[gfidx]) idxsplit = idx.split(',') idx4 = [int(idxsplit[0]),int(idxsplit[1]),int(idxsplit[2]),int(idxsplit[3])] read_from_memory_index.append(unique_idx(idx4)) # https://stackoverflow.com/questions/13668393/python-sorting-two-lists _UNUSEDlist, sorted_list_of_points_read_from_memory[gfidx] = \ [list(x) for x in zip(*sorted(zip(read_from_memory_index, list_of_points_read_from_memory[gfidx]), key=itemgetter(0)))] # Step 4e: Create the full C code string # for reading from memory: # if DIM==4: # input: [i,j,k,l] # output: "i0+i,i1+j,i2+k,i3+l" # if DIM==3: # input: [i,j,k,l] # output: "i0+i,i1+j,i2+k" # etc. 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 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 return "const "+ par.parval_from_str("PRECISION") + " " + varname def varsuffix(idx4): if idx4 == [0,0,0,0]: return "" return "_"+ijkl_string(idx4).replace(",","_").replace("+","p").replace("-","m") def read_from_memory_Ccode_onept(gfname,idx): idxsplit = idx.split(',') idx4 = [int(idxsplit[0]),int(idxsplit[1]),int(idxsplit[2]),int(idxsplit[3])] gf_array_name = "in_gfs" # Default array name. gfaccess_str = gri.gfaccess(gf_array_name,gfname,ijkl_string(idx4)) if outCparams.SIMD_enable == "True": retstring = out__type_var(gfname) + varsuffix(idx4) +" = ReadSIMD(&" + gfaccess_str + ");" else: retstring = out__type_var(gfname) + varsuffix(idx4) +" = " + gfaccess_str + ";" return retstring+"\n" read_from_memory_Ccode = "" count = 0 for gfidx in range(len(gri.glb_gridfcs_list)): for pt in range(len(sorted_list_of_points_read_from_memory[gfidx])): read_from_memory_Ccode += read_from_memory_Ccode_onept(read_from_memory_gf[count].name, sorted_list_of_points_read_from_memory[gfidx][pt]) count += 1 # Step 5: Output C code. C code consists of three parts # a) Read gridfunctions from memory at needed pts. # b) Perform arithmetic needed for input expressions # provided in sympyexpr_list[].rhs and associated # finite differences. # c) Write output to gridfunctions specified in # sympyexpr_list[].lhs. def indent_Ccode(Ccode): Ccodesplit = Ccode.splitlines() outstring = "" for i in range(len(Ccodesplit)): outstring += outCparams.preindent+indent+Ccodesplit[i]+'\n' return outstring # Step 5a: Read gridfunctions from memory at needed pts. # *** No need to do anything here; already set in # string "read_from_memory_Ccode". *** # FIXME: Update these code comments: # Step 5b: Perform arithmetic needed for finite differences # associated with input expressions provided in # sympyexpr_list[].rhs. # Note that exprs and lhsvarnames contain # i) finite difference expressions (constructed # in steps above) and associated variable names, # and # ii) Input expressions sympyexpr_list[], which # in general depend on finite difference # variables. exprs = [] lhsvarnames = [] # Step 5b.i: Output finite difference expressions to # Coutput string for i in range(len(list_of_deriv_vars)): exprs.append(sp.sympify(0)) # Append a new element to the list of derivative expressions. lhsvarnames.append(out__type_var(list_of_deriv_vars[i])) var = deriv__base_gridfunction_name[i] for j in range(len(fdcoeffs[i])): varname = str(var)+varsuffix(fdstencl[i][j]) exprs[i] += fdcoeffs[i][j]*sp.sympify(varname) # Multiply each expression by the appropriate power # of 1/dx[i] invdx = [] for d in range(par.parval_from_str("DIM")): invdx.append(sp.sympify("invdx"+str(d))) # First-order or Kreiss-Oliger derivatives: if (len(deriv__operator[i]) == 5 and "dKOD" in deriv__operator[i]) or \ (len(deriv__operator[i]) == 3 and "dD" in deriv__operator[i]) or \ (len(deriv__operator[i]) == 5 and ("dupD" in deriv__operator[i] or "ddnD" in deriv__operator[i])): dirn = int(deriv__operator[i][len(deriv__operator[i])-1]) exprs[i] *= invdx[dirn] # Second-order derivs: elif len(deriv__operator[i]) == 5 and "dDD" in deriv__operator[i]: dirn1 = int(deriv__operator[i][len(deriv__operator[i]) - 2]) dirn2 = int(deriv__operator[i][len(deriv__operator[i]) - 1]) exprs[i] *= invdx[dirn1]*invdx[dirn2] else: print("Error: was unable to parse derivative operator: ",deriv__operator[i]) sys.exit(1) # Step 5b.ii: If upwind control vector is specified, # add upwind control vectors to the # derivative expression list, so its # needed elements are read from memory. if upwindcontrolvec != "": for i in range(len(upwind_directions)): exprs.append(upwindcontrolvec[upwind_directions[i]]) lhsvarnames.append(out__type_var("UpwindControlVectorU"+str(upwind_directions[i]))) # Step 5b.iii: Output useful code comment regarding # which step we are on. *At most* this # is a 3-step process: # 1. Read from memory & compute FD stencils, # 2. Perform upwinding, and # 3. Evaluate remaining expressions+write # results to main memory. NRPy_FD_StepNumber = 1 NRPy_FD__Number_of_Steps = 1 if len(read_from_memory_Ccode) > 0: NRPy_FD__Number_of_Steps += 1 if upwindcontrolvec != "" and len(upwind_directions) > 0: NRPy_FD__Number_of_Steps += 1 if len(read_from_memory_Ccode) > 0: Coutput += indent_Ccode("/* \n * NRPy+ Finite Difference Code Generation, Step " + str(NRPy_FD_StepNumber) + " of " + str(NRPy_FD__Number_of_Steps)+ ": Read from main memory and compute finite difference stencils:\n */\n") NRPy_FD_StepNumber = NRPy_FD_StepNumber + 1 # Prefix chosen CSE variables with "FD", for the finite difference coefficients: Coutput += indent_Ccode(outputC(exprs,lhsvarnames,"returnstring",params=params + ",CSE_varprefix=FDPart1,includebraces=False,CSE_preprocess=True,SIMD_find_more_subs=True", prestring=read_from_memory_Ccode)) # Step 5b.iv: Implement control-vector upwinding algorithm. if upwindcontrolvec != "": if len(upwind_directions) > 0: Coutput += indent_Ccode("/* \n * NRPy+ Finite Difference Code Generation, Step " + str(NRPy_FD_StepNumber) + " of " + str(NRPy_FD__Number_of_Steps) + ": Implement upwinding algorithm:\n */\n") NRPy_FD_StepNumber = NRPy_FD_StepNumber + 1 if outCparams.SIMD_enable == "True": Coutput += """ const double tmp_upwind_Integer_1 = 1.000000000000000000000000000000000; const REAL_SIMD_ARRAY upwind_Integer_1 = ConstSIMD(tmp_upwind_Integer_1); const double tmp_upwind_Integer_0 = 0.000000000000000000000000000000000; const REAL_SIMD_ARRAY upwind_Integer_0 = ConstSIMD(tmp_upwind_Integer_0); """ for dirn in upwind_directions: Coutput += indent_Ccode(out__type_var("UpWind" + str(dirn)) + " = UPWIND_ALG(UpwindControlVectorU" + str(dirn) + ");\n") upwindU = [sp.sympify(0) for i in range(par.parval_from_str("DIM"))] for dirn in upwind_directions: upwindU[dirn] = sp.sympify("UpWind"+str(dirn)) upwind_expr_list, var_list = [], [] for i in range(len(list_of_deriv_vars)): if len(deriv__operator[i]) == 5 and ("dupD" in deriv__operator[i]): var_dupD = sp.sympify("UpwindAlgInput"+str(list_of_deriv_vars[i])) var_ddnD = sp.sympify("UpwindAlgInput"+str(list_of_deriv_vars[i]).replace("_dupD","_ddnD")) upwind_dirn = int(deriv__operator[i][len(deriv__operator[i])-1]) upwind_expr = upwindU[upwind_dirn]*(var_dupD - var_ddnD) + var_ddnD upwind_expr_list.append(upwind_expr) var_list.append(out__type_var(str(list_of_deriv_vars[i]),AddPrefix_for_UpDownWindVars=False)) # For convenience, we require out__type_var() above to # prefix up/downwinded variables with "UpwindAlgInput". # Here we do not wish to have this prefix. Coutput += indent_Ccode(outputC(upwind_expr_list,var_list, "returnstring",params=params + ",CSE_varprefix=FDPart2,includebraces=False")) # Step 5b.v: Add input RHS & LHS expressions from # sympyexpr_list[] Coutput += indent_Ccode("/* \n * NRPy+ Finite Difference Code Generation, Step " + str(NRPy_FD_StepNumber) + " of " + str(NRPy_FD__Number_of_Steps) + ": Evaluate SymPy expressions and write to main memory:\n */\n") exprs = [] lhsvarnames = [] for i in range(len(sympyexpr_list)): exprs.append(sympyexpr_list[i].rhs) if outCparams.SIMD_enable == "True": lhsvarnames.append("const REAL_SIMD_ARRAY __RHS_exp_"+str(i)) else: lhsvarnames.append(sympyexpr_list[i].lhs) # Step 5c: Write output to gridfunctions specified in # sympyexpr_list[].lhs. write_to_mem_string = "" if outCparams.SIMD_enable == "True": for i in range(len(sympyexpr_list)): write_to_mem_string += "WriteSIMD(&"+sympyexpr_list[i].lhs+", __RHS_exp_"+str(i)+");\n" Coutput += indent_Ccode(outputC(exprs,lhsvarnames,"returnstring", params = params+",CSE_varprefix=FDPart3,includebraces=False,preindent=0", prestring="",poststring=write_to_mem_string)) # Step 6: Add consistent indentation to the output end brace. # See Step 0.b for corresponding start brace. if outCparams.includebraces == "True": Coutput += outCparams.preindent+"}\n" # Step 7: Output the C code in desired format: stdout, string, or file. if filename == "stdout": print(Coutput) elif filename == "returnstring": return Coutput+'\n' else: # Output to the file specified by outCfilename with open(filename, outCparams.outCfileaccess) as file: file.write(Coutput) successstr = "" if outCparams.outCfileaccess == "a": successstr = "Appended " elif outCparams.outCfileaccess == "w": successstr = "Wrote " print(successstr + "to file \"" + filename + "\"")