# Author: Zachariah B. Etienne # zachetie **at** gmail **dot* com # Step 1: Import all needed modules from NRPy+: import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends import NRPy_param_funcs as par # NRPy+: Parameter interface import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support import reference_metric as rfm # NRPy+: Reference metric support import BSSN.BSSN_quantities as Bq # NRPy+: Computes useful BSSN quantities import BSSN.BSSN_RHSs as Brhs # NRPy+: Constructs BSSN right-hand-side expressions import sys # Standard Python modules for multiplatform OS-level functions # Step 1.a: Declare/initialize parameters for this module thismodule = __name__ par.initialize_param(par.glb_param("char", thismodule, "LapseEvolutionOption", "OnePlusLog")) par.initialize_param(par.glb_param("char", thismodule, "ShiftEvolutionOption", "GammaDriving2ndOrder_Covariant")) def BSSN_gauge_RHSs(): # Step 1.d: Set spatial dimension (must be 3 for BSSN, as BSSN is # a 3+1-dimensional decomposition of the general # relativistic field equations) DIM = 3 # Step 1.e: Given the chosen coordinate system, set up # corresponding reference metric and needed # reference metric quantities # The following function call sets up the reference metric # and related quantities, including rescaling matrices ReDD, # ReU, and hatted quantities. rfm.reference_metric()
def UIUCBlackHole(ComputeADMGlobalsOnly=False): global Sph_r_th_ph, r, th, ph, gammaSphDD, KSphDD, alphaSph, betaSphU, BSphU # All gridfunctions will be written in terms of spherical coordinates (r, th, ph): r, th, ph = sp.symbols('r th ph', real=True) # Step 0: Set spatial dimension (must be 3 for BSSN) DIM = 3 par.set_parval_from_str("grid::DIM", DIM) # Step 1: Set psi, the conformal factor: # Spin per unit mass a = M * chi # Defined under equation 1 in Liu, Etienne, & Shapiro (2009) https://arxiv.org/pdf/1001.4077.pdf # Boyer - Lindquist outer horizon rp = M + sp.sqrt(M**2 - a**2) # Boyer - Lindquist inner horizon rm = M - sp.sqrt(M**2 - a**2) # Boyer - Lindquist radius in terms of UIUC radius # Eq. 11 # r_{BL} = r * ( 1 + r_+ / 4r )^2 rBL = r * (1 + rp / (4 * r))**2 # Expressions found below Eq. 2 # Sigma = r_{BL}^2 + a^2 cos^2 theta SIG = rBL**2 + a**2 * sp.cos(th)**2 # Delta = r_{BL}^2 - 2Mr_{BL} + a^2 DEL = rBL**2 - 2 * M * rBL + a**2 # A = (r_{BL}^2 + a^2)^2 - Delta a^2 sin^2 theta AA = (rBL**2 + a**2)**2 - DEL * a**2 * sp.sin(th)**2 # *** The ADM 3-metric in spherical basis *** gammaSphDD = ixp.zerorank2() # Declare the nonzero components of the 3-metric # (Eq. 13 of Liu, Etienne, & Shapiro, https://arxiv.org/pdf/1001.4077.pdf): # ds^2 = Sigma (r + r_+/4)^2 / ( r^3 (r_{BL} - r_- ) * dr^2 + # Sigma d theta^2 + (A sin^2 theta) / Sigma * d\phi^2 gammaSphDD[0][0] = ((SIG * (r + rp / 4)**2) / (r**3 * (rBL - rm))) gammaSphDD[1][1] = SIG gammaSphDD[2][2] = AA / SIG * sp.sin(th)**2 # *** The physical trace-free extrinsic curvature in spherical basis *** # Nonzero components of the extrinsic curvature K, given by # Eq. 14 of Liu, Etienne, & Shapiro, https://arxiv.org/pdf/1001.4077.pdf: KSphDD = ixp.zerorank2() # K_{r phi} = K_{phi r} = (Ma sin^2 theta) / (Sigma sqrt{A Sigma}) * # [3r^4_{BL} + 2a^2 r^2_{BL} - a^4 - a^2 (r^2_{BL} - a^2) sin^2 theta] * # (1 + r_+ / 4r) (1 / sqrt{r(r_{BL} - r_-)}) KSphDD[0][2] = KSphDD[2][0] = (M*a*sp.sin(th)**2)/(SIG*sp.sqrt(AA*SIG))*\ (3*rBL**4 + 2*a**2*rBL**2 - a**4- a**2*(rBL**2 - a**2)*\ sp.sin(th)**2)*(1 + rp/(4*r))*1/sp.sqrt(r*(rBL - rm)) # Components of the extrinsic curvature K, given by # Eq. 15 of Liu, Etienne, & Shapiro, https://arxiv.org/pdf/1001.4077.pdf: # K_{theta phi} = K_{phi theta} = -(2a^3 Mr_{BL} cos theta sin^3 theta) / # (Sigma sqrt{A Sigma}) x (r - r_+ / 4) sqrt{(r_{BL} - r_-) / r } KSphDD[1][2] = KSphDD[2][1] = -((2*a**3*M*rBL*sp.cos(th)*sp.sin(th)**3)/ \ (SIG*sp.sqrt(AA*SIG)))*(r - rp/4)*sp.sqrt((rBL - rm)/r) alphaSph = sp.sympify( 1 ) # We generally choose alpha = 1/psi**2 (psi = BSSN conformal factor) for these initial data betaSphU = ixp.zerorank1( ) # We generally choose \beta^i = 0 for these initial data BSphU = ixp.zerorank1( ) # We generally choose B^i = 0 for these initial data if ComputeADMGlobalsOnly == True: return # Validated against original SENR: KSphDD[0][2], KSphDD[1][2], gammaSphDD[2][2], gammaSphDD[0][0], gammaSphDD[1][1] #print(sp.mathematica_code(gammaSphDD[1][1])) Sph_r_th_ph = [r, th, ph] cf,hDD,lambdaU,aDD,trK,alpha,vetU,betU = \ AtoB.Convert_Spherical_or_Cartesian_ADM_to_BSSN_curvilinear("Spherical", Sph_r_th_ph, gammaSphDD,KSphDD,alphaSph,betaSphU,BSphU) # Let's choose alpha = 1/psi**2 (psi = BSSN conformal factor) for these initial data, # where psi = exp(phi); chi = 1/psi**4; W = 1/psi**2 if par.parval_from_str("EvolvedConformalFactor_cf") == "phi": alpha = sp.exp(-2 * cf) elif par.parval_from_str("EvolvedConformalFactor_cf") == "chi": alpha = sp.sqrt(cf) elif par.parval_from_str("EvolvedConformalFactor_cf") == "W": alpha = cf else: print("Error EvolvedConformalFactor_cf type = \"" + par.parval_from_str("EvolvedConformalFactor_cf") + "\" unknown.") sys.exit(1) import BSSN.BSSN_ID_function_string as bIDf # Generates initial_data() C function & stores to outC_function_dict["initial_data"] bIDf.BSSN_ID_function_string(cf, hDD, lambdaU, aDD, trK, alpha, vetU, betU)
from outputC import nrpyAbs # NRPy+: Core C code output module import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends import NRPy_param_funcs as par # NRPy+: Parameter interface import grid as gri # NRPy+: Functions having to do with numerical grids import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support import reference_metric as rfm # NRPy+: Reference metric support import GRHD.equations as GRHD # NRPy+: Generate general relativistic hydrodynamics equations import GRFFE.equations as GRFFE # NRPy+: Generate general relativisitic force-free electrodynamics equations thismodule = __name__ # There are several C parameters that we will need in this module: M_PI = par.Cparameters("#define", thismodule, ["M_PI"], "") GAMMA_SPEED_LIMIT = par.Cparameters("REAL", thismodule, "GAMMA_SPEED_LIMIT", 10.0) # Default value based on # IllinoisGRMHD. # GiRaFFE default = 2000.0 # There are three fixes in this module; we might not always want to do all (or even any!) of them. # So, we initialize some NRPy+ parameters to control this behavior par.initialize_param( par.glb_param(type="bool", module=thismodule, parname="enforce_orthogonality_StildeD_BtildeU", defaultval=True)) par.initialize_param( par.glb_param(type="bool", module=thismodule, parname="enforce_speed_limit_StildeD", defaultval=True)) par.initialize_param(
# $$\label{preliminaries}$$ # # \[Back to [top](#top)\] # # Here, we will import the NRPy+ core modules and set the reference metric to Cartesian, set commonly used NRPy+ parameters, and set C parameters that will be set from outside the code eventually generated from these expressions. We will also set up a parameter to determine what initial data is set up, although it won't do much yet. # Step 0: Import the NRPy+ core modules and set the reference metric to Cartesian import NRPy_param_funcs as par import indexedexp as ixp import grid as gri import finite_difference as fin from outputC import * import loop import reference_metric as rfm par.set_parval_from_str("reference_metric::CoordSystem", "Cartesian") rfm.reference_metric() # Step 1a: Set commonly used parameters. thismodule = "GiRaFFEfood_HO_Aligned_Rotator" # Set the spatial dimension parameter to 3. par.set_parval_from_str("grid::DIM", 3) DIM = par.parval_from_str("grid::DIM") B_p_aligned_rotator, R_NS_aligned_rotator = par.Cparameters( "REAL", thismodule, ["B_p_aligned_rotator", "R_NS_aligned_rotator"] ) # A constant defining the intensity of the magnetic field and the Neutron star Radius # <a id='step2'></a> # # ### Step 2: Set the vectors A in Spherical coordinates
def FishboneMoncriefID(CoordSystem="Cartesian"): par.set_parval_from_str("reference_metric::CoordSystem", CoordSystem) rfm.reference_metric() #Set the spatial dimension parameter to 3. par.set_parval_from_str("grid::DIM", 3) DIM = par.parval_from_str("grid::DIM") gPhys4UU = ixp.register_gridfunctions_for_single_rank2("AUX", "gPhys4UU", "sym01", DIM=4) KDD = ixp.register_gridfunctions_for_single_rank2("EVOL", "KDD", "sym01") # Variables needed for initial data given in spherical basis global r_in, r_at_max_density, a, M, r, th, ph r, th, ph = gri.register_gridfunctions("AUX", ["r", "th", "ph"]) r_in, r_at_max_density, a, M = par.Cparameters( "REAL", thismodule, ["r_in", "r_at_max_density", "a", "M"], [6.0, 12.0, 0.9375, 1.0]) kappa, gamma = par.Cparameters("REAL", thismodule, ["kappa", "gamma"], [1.0e-3, 4.0 / 3.0]) # The return value from gri.register_gridfunctions("AUX","LorentzFactor") is unused, so we ignore it here: gri.register_gridfunctions("AUX", "LorentzFactor") def calculate_l_at_r(r): l = sp.sqrt(M / r**3) * (r**4 + r**2 * a**2 - 2 * M * r * a**2 - a * sp.sqrt(M * r) * (r**2 - a**2)) l /= r**2 - 3 * M * r + 2 * a * sp.sqrt(M * r) return l # First compute angular momentum at r_at_max_density, TAKING POSITIVE ROOT. This way disk is co-rotating with black hole # Eq 3.8: l = calculate_l_at_r(r_at_max_density) # Eq 3.6: # First compute the radially-independent part of the log of the enthalpy, ln_h_const Delta = r**2 - 2 * M * r + a**2 Sigma = r**2 + a**2 * sp.cos(th)**2 A = (r**2 + a**2)**2 - Delta * a**2 * sp.sin(th)**2 # Next compute the radially-dependent part of log(enthalpy), ln_h tmp3 = sp.sqrt(1 + 4 * l**2 * Sigma**2 * Delta / (A * sp.sin(th))**2) # Term 1 of Eq 3.6 ln_h = sp.Rational(1, 2) * sp.log((1 + tmp3) / (Sigma * Delta / A)) # Term 2 of Eq 3.6 ln_h -= sp.Rational(1, 2) * tmp3 # Term 3 of Eq 3.6 ln_h -= 2 * a * M * r * l / A # Next compute the radially-INdependent part of log(enthalpy), ln_h # Note that there is some typo in the expression for these terms given in Eq 3.6, so we opt to just evaluate # negative of the first three terms at r=r_in and th=pi/2 (the integration constant), as described in # the text below Eq. 3.6, basically just copying the above lines of code. # Delin = Delta_in ; Sigin = Sigma_in ; Ain = A_in . Delin = r_in**2 - 2 * M * r_in + a**2 Sigin = r_in**2 + a**2 * sp.cos(sp.pi / 2)**2 Ain = (r_in**2 + a**2)**2 - Delin * a**2 * sp.sin(sp.pi / 2)**2 tmp3in = sp.sqrt(1 + 4 * l**2 * Sigin**2 * Delin / (Ain * sp.sin(sp.pi / 2))**2) # Term 4 of Eq 3.6 mln_h_in = -sp.Rational(1, 2) * sp.log( (1 + tmp3in) / (Sigin * Delin / Ain)) # Term 5 of Eq 3.6 mln_h_in += sp.Rational(1, 2) * tmp3in # Term 6 of Eq 3.6 mln_h_in += 2 * a * M * r_in * l / Ain global hm1 hm1 = sp.exp(ln_h + mln_h_in) - 1 global rho_initial rho_initial, Pressure_initial = gri.register_gridfunctions( "AUX", ["rho_initial", "Pressure_initial"]) # Python 3.4 + sympy 1.0.0 has a serious problem taking the power here, hangs forever. # so instead we use the identity x^{1/y} = exp( [1/y] * log(x) ) # Original expression (works with Python 2.7 + sympy 0.7.4.1): # rho_initial = ( hm1*(gamma-1)/(kappa*gamma) )**(1/(gamma - 1)) # New expression (workaround): rho_initial = sp.exp( (1 / (gamma - 1)) * sp.log(hm1 * (gamma - 1) / (kappa * gamma))) Pressure_initial = kappa * rho_initial**gamma # Eq 3.3: First compute exp(-2 chi), assuming Boyer-Lindquist coordinates # Eq 2.16: chi = psi - nu, so # Eq 3.5 -> exp(-2 chi) = exp(-2 (psi - nu)) = exp(2 nu)/exp(2 psi) exp2nu = Sigma * Delta / A exp2psi = A * sp.sin(th)**2 / Sigma expm2chi = exp2nu / exp2psi # Eq 3.3: Next compute u_(phi). u_pphip = sp.sqrt((-1 + sp.sqrt(1 + 4 * l**2 * expm2chi)) / 2) # Eq 2.13: Compute u_(t) u_ptp = -sp.sqrt(1 + u_pphip**2) # Next compute spatial components of 4-velocity in Boyer-Lindquist coordinates: global uBL4D uBL4D = ixp.zerorank1(DIM=4) # Components 1 and 2: u_r = u_theta = 0 # Eq 2.12 (typo): u_(phi) = e^(-psi) u_phi -> u_phi = e^(psi) u_(phi) uBL4D[3] = sp.sqrt(exp2psi) * u_pphip # Assumes Boyer-Lindquist coordinates: omega = 2 * a * M * r / A # Eq 2.13: u_(t) = 1/sqrt(exp2nu) * ( u_t + omega*u_phi ) # --> u_t = u_(t) * sqrt(exp2nu) - omega*u_phi # --> u_t = u_ptp * sqrt(exp2nu) - omega*uBL4D[3] uBL4D[0] = u_ptp * sp.sqrt(exp2nu) - omega * uBL4D[3] # Eq. 3.5: # w = 2*a*M*r/A; # Eqs. 3.5 & 2.1: # gtt = -Sig*Del/A + w^2*Sin[th]^2*A/Sig; # gtp = w*Sin[th]^2*A/Sig; # gpp = Sin[th]^2*A/Sig; # FullSimplify[Inverse[{{gtt,gtp},{gtp,gpp}}]] gPhys4BLUU = ixp.zerorank2(DIM=4) gPhys4BLUU[0][0] = -A / (Delta * Sigma) # DO NOT NEED TO SET gPhys4BLUU[1][1] or gPhys4BLUU[2][2]! gPhys4BLUU[0][3] = gPhys4BLUU[3][0] = -2 * a * M * r / (Delta * Sigma) gPhys4BLUU[3][3] = -4 * a**2 * M**2 * r**2 / ( Delta * A * Sigma) + Sigma**2 / (A * Sigma * sp.sin(th)**2) global uBL4U uBL4U = ixp.zerorank1(DIM=4) for i in range(4): for j in range(4): uBL4U[i] += gPhys4BLUU[i][j] * uBL4D[j] # https://github.com/atchekho/harmpi/blob/master/init.c # Next transform Boyer-Lindquist velocity to Kerr-Schild basis: transformBLtoKS = ixp.zerorank2(DIM=4) for i in range(4): transformBLtoKS[i][i] = 1 transformBLtoKS[0][1] = 2 * r / (r**2 - 2 * r + a * a) transformBLtoKS[3][1] = a / (r**2 - 2 * r + a * a) global uKS4U uKS4U = ixp.zerorank1(DIM=4) for i in range(4): for j in range(4): uKS4U[i] += transformBLtoKS[i][j] * uBL4U[j] # Adopt the Kerr-Schild metric for Fishbone-Moncrief disks # http://gravity.psu.edu/numrel/jclub/jc/Cook___LivRev_2000-5.pdf # Alternatively, Appendix of https://arxiv.org/pdf/1704.00599.pdf rhoKS2 = r**2 + a**2 * sp.cos( th )**2 # Eq 79 of Cook's Living Review article (note that definition above Eq A.1 in # https://arxiv.org/pdf/1704.00599.pdf should be rho^2, not rho) alphaKS = 1 / sp.sqrt(1 + 2 * M * r / rhoKS2) betaKSU = ixp.zerorank1() betaKSU[0] = alphaKS**2 * 2 * M * r / rhoKS2 gammaKSDD = ixp.zerorank2() gammaKSDD[0][0] = 1 + 2 * M * r / rhoKS2 gammaKSDD[0][2] = gammaKSDD[2][0] = -(1 + 2 * M * r / rhoKS2) * a * sp.sin( th)**2 gammaKSDD[1][1] = rhoKS2 gammaKSDD[2][2] = (r**2 + a**2 + 2 * M * r / rhoKS2 * a**2 * sp.sin(th)**2) * sp.sin(th)**2 AA = a**2 * sp.cos(2 * th) + a**2 + 2 * r**2 BB = AA + 4 * M * r DD = sp.sqrt(2 * M * r / (a**2 * sp.cos(th)**2 + r**2) + 1) KDD[0][0] = DD * (AA + 2 * M * r) / (AA**2 * BB) * ( 4 * M * (a**2 * sp.cos(2 * th) + a**2 - 2 * r**2)) KDD[0][1] = KDD[1][0] = DD / ( AA * BB) * 8 * a**2 * M * r * sp.sin(th) * sp.cos(th) KDD[0][2] = KDD[2][0] = DD / AA**2 * ( -2 * a * M * sp.sin(th)**2 * (a**2 * sp.cos(2 * th) + a**2 - 2 * r**2)) KDD[1][1] = DD / BB * 4 * M * r**2 KDD[1][2] = KDD[2][1] = DD / (AA * BB) * (-8 * a**3 * M * r * sp.sin(th)**3 * sp.cos(th)) KDD[2][2] = DD/(AA**2*BB) * \ (2*M*r*sp.sin(th)**2 * (a**4*(r-M)*sp.cos(4*th) + a**4*(M+3*r) + 4*a**2*r**2*(2*r-M) + 4*a**2*r*sp.cos(2*th)*(a**2 + r*(M+2*r)) + 8*r**5)) # For compatibility, we must compute gPhys4UU gammaKSUU, gammaKSDET = ixp.symm_matrix_inverter3x3(gammaKSDD) # See, e.g., Eq. 4.49 of https://arxiv.org/pdf/gr-qc/0703035.pdf , where N = alpha gPhys4UU[0][0] = -1 / alphaKS**2 for i in range(1, 4): if i > 0: # if the quantity does not have a "4", then it is assumed to be a 3D quantity. # E.g., betaKSU[] is a spatial vector, with indices ranging from 0 to 2: gPhys4UU[0][i] = gPhys4UU[i][0] = betaKSU[i - 1] / alphaKS**2 for i in range(1, 4): for j in range(1, 4): # if the quantity does not have a "4", then it is assumed to be a 3D quantity. # E.g., betaKSU[] is a spatial vector, with indices ranging from 0 to 2, # and gammaKSUU[][] is a spatial tensor, with indices again ranging from 0 to 2. gPhys4UU[i][j] = gPhys4UU[j][i] = gammaKSUU[i - 1][ j - 1] - betaKSU[i - 1] * betaKSU[j - 1] / alphaKS**2 A_b = par.Cparameters("REAL", thismodule, "A_b", 1.0) A_3vecpotentialD = ixp.zerorank1() # Set A_phi = A_b*rho_initial FIXME: why is there a sign error? A_3vecpotentialD[2] = -A_b * rho_initial BtildeU = ixp.register_gridfunctions_for_single_rank1("EVOL", "BtildeU") # Eq 15 of https://arxiv.org/pdf/1501.07276.pdf: # B = curl A -> B^r = d_th A_ph - d_ph A_th BtildeU[0] = sp.diff(A_3vecpotentialD[2], th) - sp.diff( A_3vecpotentialD[1], ph) # B = curl A -> B^th = d_ph A_r - d_r A_ph BtildeU[1] = sp.diff(A_3vecpotentialD[0], ph) - sp.diff( A_3vecpotentialD[2], r) # B = curl A -> B^ph = d_r A_th - d_th A_r BtildeU[2] = sp.diff(A_3vecpotentialD[1], r) - sp.diff( A_3vecpotentialD[0], th) # Construct spacetime metric in 3+1 form: # See, e.g., Eq. 4.49 of https://arxiv.org/pdf/gr-qc/0703035.pdf , where N = alpha # The return values from gri.register_gridfunctions() & ixp.register_gridfunctions_for_single_rank1() are # unused, so we ignore them below: gri.register_gridfunctions("EVOL", ["alpha"]) ixp.register_gridfunctions_for_single_rank1("EVOL", "betaU") alpha = sp.sqrt(1 / (-gPhys4UU[0][0])) betaU = ixp.zerorank1() for i in range(3): betaU[i] = alpha**2 * gPhys4UU[0][i + 1] gammaUU = ixp.zerorank2() for i in range(3): for j in range(3): gammaUU[i][j] = gPhys4UU[i + 1][j + 1] + betaU[i] * betaU[j] / alpha**2 # The return value from ixp.register_gridfunctions_for_single_rank2() is unused so we ignore it below: ixp.register_gridfunctions_for_single_rank2("EVOL", "gammaDD", "sym01") gammaDD, igammaDET = ixp.symm_matrix_inverter3x3(gammaUU) gammaDET = 1 / igammaDET ############### # Next compute g_{\alpha \beta} from lower 3-metric, using # Eq 4.47 of https://arxiv.org/pdf/gr-qc/0703035.pdf betaD = ixp.zerorank1() for i in range(3): for j in range(3): betaD[i] += gammaDD[i][j] * betaU[j] beta2 = sp.sympify(0) for i in range(3): beta2 += betaU[i] * betaD[i] global gPhys4DD gPhys4DD = ixp.zerorank2(DIM=4) gPhys4DD[0][0] = -alpha**2 + beta2 for i in range(3): gPhys4DD[0][i + 1] = gPhys4DD[i + 1][0] = betaD[i] for j in range(3): gPhys4DD[i + 1][j + 1] = gammaDD[i][j] ############### # Next compute b^{\mu} using Eqs 23 and 31 of https://arxiv.org/pdf/astro-ph/0503420.pdf uKS4D = ixp.zerorank1(DIM=4) for i in range(4): for j in range(4): uKS4D[i] += gPhys4DD[i][j] * uKS4U[j] # Eq 27 of https://arxiv.org/pdf/astro-ph/0503420.pdf BU = ixp.zerorank1() for i in range(3): BU[i] = BtildeU[i] / sp.sqrt(gammaDET) # Eq 23 of https://arxiv.org/pdf/astro-ph/0503420.pdf BU0_u = sp.sympify(0) for i in range(3): BU0_u += uKS4D[i + 1] * BU[i] / alpha smallbU = ixp.zerorank1(DIM=4) smallbU[0] = BU0_u / sp.sqrt(4 * sp.pi) # Eqs 24 and 31 of https://arxiv.org/pdf/astro-ph/0503420.pdf for i in range(3): smallbU[i + 1] = (BU[i] / alpha + BU0_u * uKS4U[i + 1]) / (sp.sqrt(4 * sp.pi) * uKS4U[0]) smallbD = ixp.zerorank1(DIM=4) for i in range(4): for j in range(4): smallbD[i] += gPhys4DD[i][j] * smallbU[j] smallb2 = sp.sympify(0) for i in range(4): smallb2 += smallbU[i] * smallbD[i] ############### LorentzFactor = alpha * uKS4U[0] # Define Valencia 3-velocity v^i_(n), which sets the 3-velocity as measured by normal observers to the spatial slice: # v^i_(n) = u^i/(u^0*alpha) + beta^i/alpha. See eq 11 of https://arxiv.org/pdf/1501.07276.pdf Valencia3velocityU = ixp.zerorank1() for i in range(3): Valencia3velocityU[i] = uKS4U[i + 1] / (alpha * uKS4U[0]) + betaU[i] / alpha # sqrtgamma4DET = sp.sqrt(gammaDET)*alpha alpha = alpha.subs(r, rfm.xxSph[0]).subs(th, rfm.xxSph[1]).subs(ph, rfm.xxSph[2]) for i in range(DIM): betaU[i] = betaU[i].subs(r, rfm.xxSph[0]).subs(th, rfm.xxSph[1]).subs( ph, rfm.xxSph[2]) for j in range(DIM): gammaDD[i][j] = gammaDD[i][j].subs(r, rfm.xxSph[0]).subs( th, rfm.xxSph[1]).subs(ph, rfm.xxSph[2]) KDD[i][j] = KDD[i][j].subs(r, rfm.xxSph[0]).subs( th, rfm.xxSph[1]).subs(ph, rfm.xxSph[2]) # GRMHD variables: # Density and pressure: hm1 = hm1.subs(r, rfm.xxSph[0]).subs(th, rfm.xxSph[1]).subs(ph, rfm.xxSph[2]) rho_initial = rho_initial.subs(r, rfm.xxSph[0]).subs(th, rfm.xxSph[1]).subs( ph, rfm.xxSph[2]) # "Valencia" three-velocity for i in range(DIM): BtildeU[i] = BtildeU[i].subs(r, rfm.xxSph[0]).subs(th, rfm.xxSph[1]).subs( ph, rfm.xxSph[2]) uKS4U[i + 1] = uKS4U[i + 1].subs(r, rfm.xxSph[0]).subs( th, rfm.xxSph[1]).subs(ph, rfm.xxSph[2]) uBL4U[i + 1] = uBL4U[i + 1].subs(r, rfm.xxSph[0]).subs( th, rfm.xxSph[1]).subs(ph, rfm.xxSph[2]) Valencia3velocityU[i] = Valencia3velocityU[i].subs( r, rfm.xxSph[0]).subs(th, rfm.xxSph[1]).subs(ph, rfm.xxSph[2]) # Transform initial data to our coordinate system: # First compute Jacobian and its inverse drrefmetric__dx_0UDmatrix = sp.Matrix([[ sp.diff(rfm.xxSph[0], rfm.xx[0]), sp.diff(rfm.xxSph[0], rfm.xx[1]), sp.diff(rfm.xxSph[0], rfm.xx[2]) ], [ sp.diff(rfm.xxSph[1], rfm.xx[0]), sp.diff(rfm.xxSph[1], rfm.xx[1]), sp.diff(rfm.xxSph[1], rfm.xx[2]) ], [ sp.diff(rfm.xxSph[2], rfm.xx[0]), sp.diff(rfm.xxSph[2], rfm.xx[1]), sp.diff(rfm.xxSph[2], rfm.xx[2]) ]]) dx__drrefmetric_0UDmatrix = drrefmetric__dx_0UDmatrix.inv() # Declare as gridfunctions the final quantities we will output for the initial data global IDalpha, IDgammaDD, IDKDD, IDbetaU, IDValencia3velocityU IDalpha = gri.register_gridfunctions("EVOL", "IDalpha") IDgammaDD = ixp.register_gridfunctions_for_single_rank2( "EVOL", "IDgammaDD", "sym01") IDKDD = ixp.register_gridfunctions_for_single_rank2( "EVOL", "IDKDD", "sym01") IDbetaU = ixp.register_gridfunctions_for_single_rank1("EVOL", "IDbetaU") IDValencia3velocityU = ixp.register_gridfunctions_for_single_rank1( "EVOL", "IDValencia3velocityU") IDalpha = alpha for i in range(3): IDbetaU[i] = 0 IDValencia3velocityU[i] = 0 for j in range(3): # Matrices are stored in row, column format, so (i,j) <-> (row,column) IDbetaU[i] += dx__drrefmetric_0UDmatrix[(i, j)] * betaU[j] IDValencia3velocityU[i] += dx__drrefmetric_0UDmatrix[ (i, j)] * Valencia3velocityU[j] IDgammaDD[i][j] = 0 IDKDD[i][j] = 0 for k in range(3): for l in range(3): IDgammaDD[i][j] += drrefmetric__dx_0UDmatrix[( k, i)] * drrefmetric__dx_0UDmatrix[(l, j)] * gammaDD[k][l] IDKDD[i][j] += drrefmetric__dx_0UDmatrix[ (k, i)] * drrefmetric__dx_0UDmatrix[(l, j)] * KDD[k][l]
(b, b, b, b)), (lambda b, e: e == 5, lambda b, e: '((%s)*(%s)*(%s)*(%s)*(%s))' % (b, b, b, b, b)), (lambda b, e: e == -1, lambda b, e: '(1.0/(%s))' % (b)), (lambda b, e: e == -2, lambda b, e: '(1.0/((%s)*(%s)))' % (b, b)), (lambda b, e: e == -3, lambda b, e: '(1.0/((%s)*(%s)*(%s)))' % (b, b, b)), (lambda b, e: e == -4, lambda b, e: '(1.0/((%s)*(%s)*(%s)*(%s)))' % (b, b, b, b)), (lambda b, e: e == -5, lambda b, e: '(1.0/((%s)*(%s)*(%s)*(%s)*(%s)))' % (b, b, b, b, b)), (lambda b, e: e != -5, 'pow')] ## (lambda b, e: e != 2, 'pow')] } # Parameter initialization is called once, within nrpy.py. par.initialize_param( par.glb_param("char", __name__, "PRECISION", "double")) # __name__ = "outputC", this module's name. # par.initialize_param(par.glb_param("bool", thismodule, "SIMD_enable", False)) # super fast 'uniq' function: # f8() function from https://www.peterbe.com/plog/uniqifiers-benchmark def superfast_uniq(seq): # Author: Dave Kirby # Order preserving seen = set() return [x for x in seq if x not in seen and not seen.add(x)] def check_if_string__error_if_not(allegedstring, stringdesc): if sys.version_info[0] == 3:
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) c_type = "REAL" if par.parval_from_str("grid::GridFuncMemAccess") == "ETK": c_type = "CCTK_REAL" func_prefix = "order_"+str(FDparams.FD_CD_order)+"_" if FDparams.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: 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 " + c_type + " 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 " + c_type + " " + 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,enable_SIMD="+FDparams.enable_SIMD+",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: ") + " 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=outFDstr) return FDfunccall_list
def GiRaFFE_HO_A2B(outdir): # Register the gridfunction gammadet. This determinant will be calculated separately gammadet = gri.register_gridfunctions("AUXEVOL", "gammadet") # Import the Levi-Civita symbol and build the corresponding tensor. # We already have a handy function to define the Levi-Civita symbol in WeylScalars import WeylScal4NRPy.WeylScalars_Cartesian as weyl LeviCivitaDDD = weyl.define_LeviCivitaSymbol_rank3() LeviCivitaUUU = ixp.zerorank3() for i in range(DIM): for j in range(DIM): for k in range(DIM): LCijk = LeviCivitaDDD[i][j][k] #LeviCivitaDDD[i][j][k] = LCijk * sp.sqrt(gho.gammadet) LeviCivitaUUU[i][j][k] = LCijk / sp.sqrt(gammadet) # We can use this function to compactly reset to expressions to print at each FD order. def set_BU_to_print(): return [lhrh(lhs=gri.gfaccess("out_gfs","BU0"),rhs=BU[0]),\ lhrh(lhs=gri.gfaccess("out_gfs","BU1"),rhs=BU[1]),\ lhrh(lhs=gri.gfaccess("out_gfs","BU2"),rhs=BU[2])] AD = ixp.register_gridfunctions_for_single_rank1("EVOL", "AD") BU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL", "BU") AD_dD = ixp.declarerank2("AD_dD", "nosym") BU = ixp.zerorank1( ) # BU is already registered as a gridfunction, but we need to zero its values and declare it in this scope. for i in range(DIM): for j in range(DIM): for k in range(DIM): BU[i] += LeviCivitaUUU[i][j][k] * AD_dD[k][j] par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 10) fin.FD_outputC(os.path.join(outdir, "B_from_A_order10.h"), set_BU_to_print(), params="outCverbose=False") par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 8) fin.FD_outputC(os.path.join(outdir, "B_from_A_order8.h"), set_BU_to_print(), params="outCverbose=False") par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 6) fin.FD_outputC(os.path.join(outdir, "B_from_A_order6.h"), set_BU_to_print(), params="outCverbose=False") par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 4) fin.FD_outputC(os.path.join(outdir, "B_from_A_order4.h"), set_BU_to_print(), params="outCverbose=False") par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER", 2) fin.FD_outputC(os.path.join(outdir, "B_from_A_order2.h"), set_BU_to_print(), params="outCverbose=False") # For the outermost points, we'll need a separate file for each face. # These will correspond to an upwinded and a downwinded file for each direction. AD_ddnD = ixp.declarerank2("AD_ddnD", "nosym") for i in range(DIM): BU[i] = 0 for j in range(DIM): for k in range(DIM): if j == 0: BU[i] += LeviCivitaUUU[i][j][k] * AD_ddnD[k][j] else: BU[i] += LeviCivitaUUU[i][j][k] * AD_dD[k][j] fin.FD_outputC(os.path.join(outdir, "B_from_A_order2_dirx0_dnwind.h"), set_BU_to_print(), params="outCverbose=False") AD_dupD = ixp.declarerank2("AD_dupD", "nosym") for i in range(DIM): BU[i] = 0 for j in range(DIM): for k in range(DIM): if j == 0: BU[i] += LeviCivitaUUU[i][j][k] * AD_dupD[k][j] else: BU[i] += LeviCivitaUUU[i][j][k] * AD_dD[k][j] fin.FD_outputC(os.path.join(outdir, "B_from_A_order2_dirx0_upwind.h"), set_BU_to_print(), params="outCverbose=False") for i in range(DIM): BU[i] = 0 for j in range(DIM): for k in range(DIM): if j == 1: BU[i] += LeviCivitaUUU[i][j][k] * AD_ddnD[k][j] else: BU[i] += LeviCivitaUUU[i][j][k] * AD_dD[k][j] fin.FD_outputC(os.path.join(outdir, "B_from_A_order2_dirx1_dnwind.h"), set_BU_to_print(), params="outCverbose=False") for i in range(DIM): BU[i] = 0 for j in range(DIM): for k in range(DIM): if j == 1: BU[i] += LeviCivitaUUU[i][j][k] * AD_dupD[k][j] else: BU[i] += LeviCivitaUUU[i][j][k] * AD_dD[k][j] fin.FD_outputC(os.path.join(outdir, "B_from_A_order2_dirx1_upwind.h"), set_BU_to_print(), params="outCverbose=False") for i in range(DIM): BU[i] = 0 for j in range(DIM): for k in range(DIM): if j == 2: BU[i] += LeviCivitaUUU[i][j][k] * AD_ddnD[k][j] else: BU[i] += LeviCivitaUUU[i][j][k] * AD_dD[k][j] fin.FD_outputC(os.path.join(outdir, "B_from_A_order2_dirx2_dnwind.h"), set_BU_to_print(), params="outCverbose=False") for i in range(DIM): BU[i] = 0 for j in range(DIM): for k in range(DIM): if j == 2: BU[i] += LeviCivitaUUU[i][j][k] * AD_dupD[k][j] else: BU[i] += LeviCivitaUUU[i][j][k] * AD_dD[k][j] fin.FD_outputC(os.path.join(outdir, "B_from_A_order2_dirx2_upwind.h"), set_BU_to_print(), params="outCverbose=False")
# Authors: Zachariah B. Etienne # (zachetie **at** gmail **dot* com), # and Patrick Nelson # Step 1.a: import all needed modules from NRPy+: import sympy as sp import NRPy_param_funcs as par import indexedexp as ixp import grid as gri import finite_difference as fin import reference_metric as rfm # Step 1.b: Initialize TetradChoice parameter thismodule = __name__ # Current option: QuasiKinnersley = choice made in Baker, Campanelli, and Lousto. PRD 65, 044001 (2002) par.initialize_param(par.glb_param("char", thismodule, "TetradChoice", "QuasiKinnersley")) par.initialize_param(par.glb_param("char", thismodule, "UseCorrectUnitNormal", "False")) # False = consistent with WeylScal4 ETK thorn. def Psi4_tetrads(): global l4U, n4U, mre4U, mim4U # Step 1.c: Check if tetrad choice is implemented: if par.parval_from_str(thismodule+"::TetradChoice") != "QuasiKinnersley": print("ERROR: "+thismodule+"::TetradChoice = "+par.parval_from_str("TetradChoice")+" currently unsupported!") exit(1) # Step 1.d: Given the chosen coordinate system, set up # corresponding reference metric and needed # reference metric quantities # The following function call sets up the reference metric # and related quantities, including rescaling matrices ReDD,
def GiRaFFE_NRPy_A2B(outdir,gammaDD,AD,BU): cmd.mkdir(outdir) # Set spatial dimension (must be 3 for BSSN) DIM = 3 par.set_parval_from_str("grid::DIM",DIM) # Compute the sqrt of the three metric determinant. import GRHD.equations as gh gh.compute_sqrtgammaDET(gammaDD) # Import the Levi-Civita symbol and build the corresponding tensor. # We already have a handy function to define the Levi-Civita symbol in indexedexp.py LeviCivitaUUU = ixp.LeviCivitaTensorUUU_dim3_rank3(gh.sqrtgammaDET) AD_dD = ixp.declarerank2("AD_dD","nosym") BU = ixp.zerorank1() for i in range(DIM): for j in range(DIM): for k in range(DIM): BU[i] += LeviCivitaUUU[i][j][k] * AD_dD[k][j] # Write the code to compute derivatives with shifted stencils as needed. with open(os.path.join(outdir,"driver_AtoB.h"),"w") as file: file.write("""void compute_A2B_in_ghostzones(const paramstruct *restrict params,REAL *restrict in_gfs,REAL *restrict auxevol_gfs, const int i0min,const int i0max, const int i1min,const int i1max, const int i2min,const int i2max) { #include "../set_Cparameters.h" for(int i2=i2min;i2<i2max;i2++) for(int i1=i1min;i1<i1max;i1++) for(int i0=i0min;i0<i0max;i0++) { REAL dx_Ay,dx_Az,dy_Ax,dy_Az,dz_Ax,dz_Ay; // Check to see if we're on the +x or -x face. If so, use a downwinded- or upwinded-stencil, respectively. // Otherwise, use a centered stencil. if (i0 > 0 && i0 < Nxx_plus_2NGHOSTS0-1) { dx_Ay = invdx0*(-1.0/2.0*in_gfs[IDX4S(AD1GF, i0-1,i1,i2)] + (1.0/2.0)*in_gfs[IDX4S(AD1GF, i0+1,i1,i2)]); dx_Az = invdx0*(-1.0/2.0*in_gfs[IDX4S(AD2GF, i0-1,i1,i2)] + (1.0/2.0)*in_gfs[IDX4S(AD2GF, i0+1,i1,i2)]); } else if (i0==0) { dx_Ay = invdx0*(-3.0/2.0*in_gfs[IDX4S(AD1GF, i0,i1,i2)] + 2*in_gfs[IDX4S(AD1GF, i0+1,i1,i2)] - 1.0/2.0*in_gfs[IDX4S(AD1GF, i0+2,i1,i2)]); dx_Az = invdx0*(-3.0/2.0*in_gfs[IDX4S(AD2GF, i0,i1,i2)] + 2*in_gfs[IDX4S(AD2GF, i0+1,i1,i2)] - 1.0/2.0*in_gfs[IDX4S(AD2GF, i0+2,i1,i2)]); } else { dx_Ay = invdx0*((3.0/2.0)*in_gfs[IDX4S(AD1GF, i0,i1,i2)] - 2*in_gfs[IDX4S(AD1GF, i0-1,i1,i2)] + (1.0/2.0)*in_gfs[IDX4S(AD1GF, i0-2,i1,i2)]); dx_Az = invdx0*((3.0/2.0)*in_gfs[IDX4S(AD2GF, i0,i1,i2)] - 2*in_gfs[IDX4S(AD2GF, i0-1,i1,i2)] + (1.0/2.0)*in_gfs[IDX4S(AD2GF, i0-2,i1,i2)]); } // As above, but in the y direction. if (i1 > 0 && i1 < Nxx_plus_2NGHOSTS1-1) { dy_Ax = invdx1*(-1.0/2.0*in_gfs[IDX4S(AD0GF, i0,i1-1,i2)] + (1.0/2.0)*in_gfs[IDX4S(AD0GF, i0,i1+1,i2)]); dy_Az = invdx1*(-1.0/2.0*in_gfs[IDX4S(AD2GF, i0,i1-1,i2)] + (1.0/2.0)*in_gfs[IDX4S(AD2GF, i0,i1+1,i2)]); } else if (i1==0) { dy_Ax = invdx1*(-3.0/2.0*in_gfs[IDX4S(AD0GF, i0,i1,i2)] + 2*in_gfs[IDX4S(AD0GF, i0,i1+1,i2)] - 1.0/2.0*in_gfs[IDX4S(AD0GF, i0,i1+2,i2)]); dy_Az = invdx1*(-3.0/2.0*in_gfs[IDX4S(AD2GF, i0,i1,i2)] + 2*in_gfs[IDX4S(AD2GF, i0,i1+1,i2)] - 1.0/2.0*in_gfs[IDX4S(AD2GF, i0,i1+2,i2)]); } else { dy_Ax = invdx1*((3.0/2.0)*in_gfs[IDX4S(AD0GF, i0,i1,i2)] - 2*in_gfs[IDX4S(AD0GF, i0,i1-1,i2)] + (1.0/2.0)*in_gfs[IDX4S(AD0GF, i0,i1-2,i2)]); dy_Az = invdx1*((3.0/2.0)*in_gfs[IDX4S(AD2GF, i0,i1,i2)] - 2*in_gfs[IDX4S(AD2GF, i0,i1-1,i2)] + (1.0/2.0)*in_gfs[IDX4S(AD2GF, i0,i1-2,i2)]); } // As above, but in the z direction. if (i2 > 0 && i2 < Nxx_plus_2NGHOSTS2-1) { dz_Ax = invdx2*(-1.0/2.0*in_gfs[IDX4S(AD0GF, i0,i1,i2-1)] + (1.0/2.0)*in_gfs[IDX4S(AD0GF, i0,i1,i2+1)]); dz_Ay = invdx2*(-1.0/2.0*in_gfs[IDX4S(AD1GF, i0,i1,i2-1)] + (1.0/2.0)*in_gfs[IDX4S(AD1GF, i0,i1,i2+1)]); } else if (i2==0) { dz_Ax = invdx2*(-3.0/2.0*in_gfs[IDX4S(AD0GF, i0,i1,i2)] + 2*in_gfs[IDX4S(AD0GF, i0,i1,i2+1)] - 1.0/2.0*in_gfs[IDX4S(AD0GF, i0,i1,i2+2)]); dz_Ay = invdx2*(-3.0/2.0*in_gfs[IDX4S(AD1GF, i0,i1,i2)] + 2*in_gfs[IDX4S(AD1GF, i0,i1,i2+1)] - 1.0/2.0*in_gfs[IDX4S(AD1GF, i0,i1,i2+2)]); } else { dz_Ax = invdx2*((3.0/2.0)*in_gfs[IDX4S(AD0GF, i0,i1,i2)] - 2*in_gfs[IDX4S(AD0GF, i0,i1,i2-1)] + (1.0/2.0)*in_gfs[IDX4S(AD0GF, i0,i1,i2-2)]); dz_Ay = invdx2*((3.0/2.0)*in_gfs[IDX4S(AD1GF, i0,i1,i2)] - 2*in_gfs[IDX4S(AD1GF, i0,i1,i2-1)] + (1.0/2.0)*in_gfs[IDX4S(AD1GF, i0,i1,i2-2)]); } // Compute the magnetic field in the normal way, using the previously calculated derivatives. const double gammaDD00 = auxevol_gfs[IDX4S(GAMMADD00GF, i0,i1,i2)]; const double gammaDD01 = auxevol_gfs[IDX4S(GAMMADD01GF, i0,i1,i2)]; const double gammaDD02 = auxevol_gfs[IDX4S(GAMMADD02GF, i0,i1,i2)]; const double gammaDD11 = auxevol_gfs[IDX4S(GAMMADD11GF, i0,i1,i2)]; const double gammaDD12 = auxevol_gfs[IDX4S(GAMMADD12GF, i0,i1,i2)]; const double gammaDD22 = auxevol_gfs[IDX4S(GAMMADD22GF, i0,i1,i2)]; /* * NRPy+ Finite Difference Code Generation, Step 2 of 1: Evaluate SymPy expressions and write to main memory: */ const double invsqrtg = 1.0/sqrt(gammaDD00*gammaDD11*gammaDD22 - gammaDD00*gammaDD12*gammaDD12 + 2*gammaDD01*gammaDD02*gammaDD12 - gammaDD11*gammaDD02*gammaDD02 - gammaDD22*gammaDD01*gammaDD01); auxevol_gfs[IDX4S(BU0GF, i0,i1,i2)] = (dy_Az-dz_Ay)*invsqrtg; auxevol_gfs[IDX4S(BU1GF, i0,i1,i2)] = (dz_Ax-dx_Az)*invsqrtg; auxevol_gfs[IDX4S(BU2GF, i0,i1,i2)] = (dx_Ay-dy_Ax)*invsqrtg; } } """) # Now, we'll also write some more auxiliary functions to handle the order-lowering method for A2B with open(os.path.join(outdir,"driver_AtoB.h"),"a") as file: file.write("""REAL relative_error(REAL a, REAL b) { if((a+b)!=0.0) { return 2.0*fabs(a-b)/fabs(a+b); } else { return 0.0; } } #define M2 0 #define M1 1 #define P0 2 #define P1 3 #define P2 4 #define CN4 0 #define CN2 1 #define UP2 2 #define DN2 3 #define UP1 4 #define DN1 5 void compute_Bx_pointwise(REAL *Bx, const REAL invdy, const REAL *Ay, const REAL invdz, const REAL *Az) { REAL dz_Ay,dy_Az; dz_Ay = invdz*((Ay[P1]-Ay[M1])*2.0/3.0 - (Ay[P2]-Ay[M2])/12.0); dy_Az = invdy*((Az[P1]-Az[M1])*2.0/3.0 - (Az[P2]-Az[M2])/12.0); Bx[CN4] = dy_Az - dz_Ay; dz_Ay = invdz*(Ay[P1]-Ay[M1])/2.0; dy_Az = invdy*(Az[P1]-Az[M1])/2.0; Bx[CN2] = dy_Az - dz_Ay; dz_Ay = invdz*(-1.5*Ay[P0]+2.0*Ay[P1]-0.5*Ay[P2]); dy_Az = invdy*(-1.5*Az[P0]+2.0*Az[P1]-0.5*Az[P2]); Bx[UP2] = dy_Az - dz_Ay; dz_Ay = invdz*(1.5*Ay[P0]-2.0*Ay[M1]+0.5*Ay[M2]); dy_Az = invdy*(1.5*Az[P0]-2.0*Az[M1]+0.5*Az[M2]); Bx[DN2] = dy_Az - dz_Ay; dz_Ay = invdz*(Ay[P1]-Ay[P0]); dy_Az = invdy*(Az[P1]-Az[P0]); Bx[UP1] = dy_Az - dz_Ay; dz_Ay = invdz*(Ay[P0]-Ay[M1]); dy_Az = invdy*(Az[P0]-Az[M1]); Bx[DN1] = dy_Az - dz_Ay; } #define TOLERANCE_A2B 1.0e-4 REAL find_accepted_Bx_order(REAL *Bx) { REAL accepted_val = Bx[CN4]; REAL Rel_error_o2_vs_o4 = relative_error(Bx[CN2],Bx[CN4]); REAL Rel_error_oCN2_vs_oDN2 = relative_error(Bx[CN2],Bx[DN2]); REAL Rel_error_oCN2_vs_oUP2 = relative_error(Bx[CN2],Bx[UP2]); if(Rel_error_o2_vs_o4 > TOLERANCE_A2B) { accepted_val = Bx[CN2]; if(Rel_error_o2_vs_o4 > Rel_error_oCN2_vs_oDN2 || Rel_error_o2_vs_o4 > Rel_error_oCN2_vs_oUP2) { // Should we use AND or OR in if statement? if(relative_error(Bx[UP2],Bx[UP1]) < relative_error(Bx[DN2],Bx[DN1])) { accepted_val = Bx[UP2]; } else { accepted_val = Bx[DN2]; } } } return accepted_val; } """) order_lowering_body = """REAL AD0_1[5],AD0_2[5],AD1_2[5],AD1_0[5],AD2_0[5],AD2_1[5]; const double gammaDD00 = auxevol_gfs[IDX4S(GAMMADD00GF, i0,i1,i2)]; const double gammaDD01 = auxevol_gfs[IDX4S(GAMMADD01GF, i0,i1,i2)]; const double gammaDD02 = auxevol_gfs[IDX4S(GAMMADD02GF, i0,i1,i2)]; const double gammaDD11 = auxevol_gfs[IDX4S(GAMMADD11GF, i0,i1,i2)]; const double gammaDD12 = auxevol_gfs[IDX4S(GAMMADD12GF, i0,i1,i2)]; const double gammaDD22 = auxevol_gfs[IDX4S(GAMMADD22GF, i0,i1,i2)]; AD0_2[M2] = in_gfs[IDX4S(AD0GF, i0,i1,i2-2)]; AD0_2[M1] = in_gfs[IDX4S(AD0GF, i0,i1,i2-1)]; AD0_1[M2] = in_gfs[IDX4S(AD0GF, i0,i1-2,i2)]; AD0_1[M1] = in_gfs[IDX4S(AD0GF, i0,i1-1,i2)]; AD0_1[P0] = AD0_2[P0] = in_gfs[IDX4S(AD0GF, i0,i1,i2)]; AD0_1[P1] = in_gfs[IDX4S(AD0GF, i0,i1+1,i2)]; AD0_1[P2] = in_gfs[IDX4S(AD0GF, i0,i1+2,i2)]; AD0_2[P1] = in_gfs[IDX4S(AD0GF, i0,i1,i2+1)]; AD0_2[P2] = in_gfs[IDX4S(AD0GF, i0,i1,i2+2)]; AD1_2[M2] = in_gfs[IDX4S(AD1GF, i0,i1,i2-2)]; AD1_2[M1] = in_gfs[IDX4S(AD1GF, i0,i1,i2-1)]; AD1_0[M2] = in_gfs[IDX4S(AD1GF, i0-2,i1,i2)]; AD1_0[M1] = in_gfs[IDX4S(AD1GF, i0-1,i1,i2)]; AD1_2[P0] = AD1_0[P0] = in_gfs[IDX4S(AD1GF, i0,i1,i2)]; AD1_0[P1] = in_gfs[IDX4S(AD1GF, i0+1,i1,i2)]; AD1_0[P2] = in_gfs[IDX4S(AD1GF, i0+2,i1,i2)]; AD1_2[P1] = in_gfs[IDX4S(AD1GF, i0,i1,i2+1)]; AD1_2[P2] = in_gfs[IDX4S(AD1GF, i0,i1,i2+2)]; AD2_1[M2] = in_gfs[IDX4S(AD2GF, i0,i1-2,i2)]; AD2_1[M1] = in_gfs[IDX4S(AD2GF, i0,i1-1,i2)]; AD2_0[M2] = in_gfs[IDX4S(AD2GF, i0-2,i1,i2)]; AD2_0[M1] = in_gfs[IDX4S(AD2GF, i0-1,i1,i2)]; AD2_0[P0] = AD2_1[P0] = in_gfs[IDX4S(AD2GF, i0,i1,i2)]; AD2_0[P1] = in_gfs[IDX4S(AD2GF, i0+1,i1,i2)]; AD2_0[P2] = in_gfs[IDX4S(AD2GF, i0+2,i1,i2)]; AD2_1[P1] = in_gfs[IDX4S(AD2GF, i0,i1+1,i2)]; AD2_1[P2] = in_gfs[IDX4S(AD2GF, i0,i1+2,i2)]; const double invsqrtg = 1.0/sqrt(gammaDD00*gammaDD11*gammaDD22 - gammaDD00*gammaDD12*gammaDD12 + 2*gammaDD01*gammaDD02*gammaDD12 - gammaDD11*gammaDD02*gammaDD02 - gammaDD22*gammaDD01*gammaDD01); REAL BU0[4],BU1[4],BU2[4]; compute_Bx_pointwise(BU0,invdx2,AD1_2,invdx1,AD2_1); compute_Bx_pointwise(BU1,invdx0,AD2_0,invdx2,AD0_2); compute_Bx_pointwise(BU2,invdx1,AD0_1,invdx0,AD1_0); auxevol_gfs[IDX4S(BU0GF, i0,i1,i2)] = find_accepted_Bx_order(BU0)*invsqrtg; auxevol_gfs[IDX4S(BU1GF, i0,i1,i2)] = find_accepted_Bx_order(BU1)*invsqrtg; auxevol_gfs[IDX4S(BU2GF, i0,i1,i2)] = find_accepted_Bx_order(BU2)*invsqrtg; """ # Here, we'll use the outCfunction() function to output a function that will compute the magnetic field # on the interior. Then, we'll add postloop code to handle the ghostzones. desc="Compute the magnetic field from the vector potential everywhere, including ghostzones" name="driver_A_to_B" driver_Ccode = outCfunction( outfile = "returnstring", desc=desc, name=name, params = "const paramstruct *restrict params,REAL *restrict in_gfs,REAL *restrict auxevol_gfs", body = fin.FD_outputC("returnstring",[lhrh(lhs=gri.gfaccess("out_gfs","BU0"),rhs=BU[0]),\ lhrh(lhs=gri.gfaccess("out_gfs","BU1"),rhs=BU[1]),\ lhrh(lhs=gri.gfaccess("out_gfs","BU2"),rhs=BU[2])]).replace("IDX4","IDX4S"), # body = order_lowering_body, postloop = """ int imin[3] = { NGHOSTS_A2B, NGHOSTS_A2B, NGHOSTS_A2B }; int imax[3] = { NGHOSTS+Nxx0, NGHOSTS+Nxx1, NGHOSTS+Nxx2 }; // Now, we loop over the ghostzones to calculate the magnetic field there. for(int which_gz = 0; which_gz < NGHOSTS_A2B; which_gz++) { // After updating each face, adjust imin[] and imax[] // to reflect the newly-updated face extents. compute_A2B_in_ghostzones(params,in_gfs,auxevol_gfs,imin[0]-1,imin[0], imin[1],imax[1], imin[2],imax[2]); imin[0]--; compute_A2B_in_ghostzones(params,in_gfs,auxevol_gfs,imax[0],imax[0]+1, imin[1],imax[1], imin[2],imax[2]); imax[0]++; compute_A2B_in_ghostzones(params,in_gfs,auxevol_gfs,imin[0],imax[0], imin[1]-1,imin[1], imin[2],imax[2]); imin[1]--; compute_A2B_in_ghostzones(params,in_gfs,auxevol_gfs,imin[0],imax[0], imax[1],imax[1]+1, imin[2],imax[2]); imax[1]++; compute_A2B_in_ghostzones(params,in_gfs,auxevol_gfs,imin[0],imax[0], imin[1],imax[1], imin[2]-1,imin[2]); imin[2]--; compute_A2B_in_ghostzones(params,in_gfs,auxevol_gfs,imin[0],imax[0], imin[1],imax[1], imax[2],imax[2]+1); imax[2]++; } """, loopopts="InteriorPoints", rel_path_for_Cparams=os.path.join("../")).replace("= NGHOSTS","= NGHOSTS_A2B").replace("NGHOSTS+Nxx0","Nxx_plus_2NGHOSTS0-NGHOSTS_A2B").replace("NGHOSTS+Nxx1","Nxx_plus_2NGHOSTS1-NGHOSTS_A2B").replace("NGHOSTS+Nxx2","Nxx_plus_2NGHOSTS2-NGHOSTS_A2B") with open(os.path.join(outdir,"driver_AtoB.h"),"a") as file: file.write(driver_Ccode)
# The A-to-B driver from outputC import * import os import grid as gri import indexedexp as ixp import NRPy_param_funcs as par import finite_difference as fin # Set spatial dimension (must be 3 for BSSN) DIM = 3 par.set_parval_from_str("grid::DIM", DIM) def GiRaFFE_HO_A2B(outdir): # Register the gridfunction gammadet. This determinant will be calculated separately gammadet = gri.register_gridfunctions("AUXEVOL", "gammadet") # Import the Levi-Civita symbol and build the corresponding tensor. # We already have a handy function to define the Levi-Civita symbol in WeylScalars import WeylScal4NRPy.WeylScalars_Cartesian as weyl LeviCivitaDDD = weyl.define_LeviCivitaSymbol_rank3() LeviCivitaUUU = ixp.zerorank3() for i in range(DIM): for j in range(DIM): for k in range(DIM): LCijk = LeviCivitaDDD[i][j][k] #LeviCivitaDDD[i][j][k] = LCijk * sp.sqrt(gho.gammadet) LeviCivitaUUU[i][j][k] = LCijk / sp.sqrt(gammadet) # We can use this function to compactly reset to expressions to print at each FD order. def set_BU_to_print(): return [lhrh(lhs=gri.gfaccess("out_gfs","BU0"),rhs=BU[0]),\
# # ### Steps 0-1: Preliminaries \[Back to [top](#top)\] # # Here, we will import the NRPy+ core modules and set the reference metric to Cartesian, set commonly used NRPy+ parameters, and set C parameters that will be set from outside the code eventually generated from these expressions. We will also set up a parameter to determine what initial data is set up, although it won't do much yet. # $$\label{preliminaries}$$ # Step 0: Import the NRPy+ core modules and set the reference metric to Cartesian import NRPy_param_funcs as par import indexedexp as ixp import grid as gri import finite_difference as fin from outputC import * import loop import reference_metric as rfm par.set_parval_from_str("reference_metric::CoordSystem", "Cartesian") rfm.reference_metric() # Step 1a: Set commonly used parameters. thismodule = "GiRaFFEfood_NRPy_1D" # <a id='step2'></a> # ### Set the vector $A_k$ # The vector potential is given as # \begin{align} # A_x &= 0 \\ # A_y &= \left \{ \begin{array}{lll}\gamma_\mu x - 0.015 & \mbox{if} & x \leq -0.1/\gamma_\mu \\ # 1.15 \gamma_\mu x - 0.03g(x) & \mbox{if} & -0.1/\gamma_\mu \leq x \leq 0.1/\gamma_\mu \\ # 1.3 \gamma_\mu x - 0.015 & \mbox{if} & x \geq 0.1/\gamma_\mu \end{array} \right. , \\ # A_z &= y - \gamma_\mu (1-\mu)x . # \end{align}
def outputC(sympyexpr, output_varname_str, filename="stdout", params="", prestring="", poststring=""): outCparams = parse_outCparams_string(params) preindent = outCparams.preindent TYPE = par.parval_from_str("PRECISION") if outCparams.enable_TYPE == "False": TYPE = "" # Step 0: Initialize # commentblock: comment block containing the input SymPy string, # set only if outCverbose==True # outstring: the output C code string commentblock = "" outstring = "" # Step 1: If SIMD_enable==True, then check if TYPE=="double". If not, error out. # Otherwise set TYPE="REAL_SIMD_ARRAY", which should be #define'd # within the C code. For example for AVX-256, the C code should have # #define REAL_SIMD_ARRAY __m256d if outCparams.SIMD_enable == "True": if not (TYPE == "double" or TYPE == ""): print( "SIMD output currently only supports double precision or typeless. Sorry!" ) sys.exit(1) if TYPE == "double": TYPE = "REAL_SIMD_ARRAY" else: TYPE = "" # Step 2a: Apply sanity checks when either sympyexpr or # output_varname_str is a list. if type(output_varname_str) is list and type(sympyexpr) is not list: print( "Error: Provided a list of output variable names, but only one SymPy expression." ) sys.exit(1) if type(sympyexpr) is list: if type(output_varname_str) is not list: print( "Error: Provided a list of SymPy expressions, but no corresponding list of output variable names" ) sys.exit(1) elif len(output_varname_str) != len(sympyexpr): print("Error: Length of SymPy expressions list (" + str(len(sympyexpr)) + ") != Length of corresponding output variable name list (" + str(len(output_varname_str)) + ")") sys.exit(1) # Step 2b: If sympyexpr and output_varname_str are not lists, # convert them to lists of one element each, to # simplify proceeding code. if type(output_varname_str) is not list and type(sympyexpr) is not list: output_varname_strtmp = [output_varname_str] output_varname_str = output_varname_strtmp sympyexprtmp = [sympyexpr] sympyexpr = sympyexprtmp # Step 3: If outCparams.verbose = True, then output the original SymPy # expression(s) in code comments prior to actual C code if outCparams.outCverbose == "True": commentblock += preindent + "/*\n" + preindent + " * Original SymPy expression" if len(output_varname_str) > 1: commentblock += "s" commentblock += ":\n" for i in range(len(output_varname_str)): if i == 0: if len(output_varname_str) != 1: commentblock += preindent + " * \"[" else: commentblock += preindent + " * \"" else: commentblock += preindent + " * " commentblock += output_varname_str[i] + " = " + str(sympyexpr[i]) if i == len(output_varname_str) - 1: if len(output_varname_str) != 1: commentblock += "]\"\n" else: commentblock += "\"\n" else: commentblock += ",\n" commentblock += preindent + " */\n" # Step 4: Add proper indentation of C code: if outCparams.includebraces == "True": indent = outCparams.preindent + " " else: indent = outCparams.preindent + "" # Step 5: Should the output variable, e.g., outvar, be declared? # If so, start output line with e.g., "double outvar " outtypestring = "" if outCparams.declareoutputvars == "True": outtypestring = outCparams.preindent + indent + TYPE + " " else: outtypestring = outCparams.preindent + indent # Step 6a: If common subexpression elimination (CSE) disabled, then # just output the SymPy string in the most boring way, # nearly consistent with SymPy's ccode() function, # though with support for float & long double types # as well. SIMD_decls = "" if outCparams.CSE_enable == "False": # If CSE is disabled: for i in range(len(sympyexpr)): outstring += outtypestring + ccode_postproc( sp.ccode( sympyexpr[i], output_varname_str[i], user_functions=custom_functions_for_SymPy_ccode)) + "\n" # Step 6b: If CSE enabled, then perform CSE using SymPy and then # resulting C code. else: # If CSE is enabled: SIMD_const_varnms = [] SIMD_const_values = [] CSE_results = sp.cse(sympyexpr, sp.numbered_symbols(outCparams.CSE_varprefix), order='canonical') for commonsubexpression in CSE_results[0]: FULLTYPESTRING = "const " + TYPE + " " if outCparams.enable_TYPE == "False": FULLTYPESTRING = "" if outCparams.SIMD_enable == "True": outstring += outCparams.preindent + indent + FULLTYPESTRING + str(commonsubexpression[0]) + " = " + \ str(expr_convert_to_SIMD_intrins(commonsubexpression[1],SIMD_const_varnms,SIMD_const_values,outCparams.SIMD_const_suffix,outCparams.SIMD_debug)) + ";\n" else: outstring += outCparams.preindent + indent + FULLTYPESTRING + ccode_postproc( sp.ccode(commonsubexpression[1], commonsubexpression[0], user_functions=custom_functions_for_SymPy_ccode) ) + "\n" for i, result in enumerate(CSE_results[1]): if outCparams.SIMD_enable == "True": outstring += outtypestring + output_varname_str[i] + " = " + \ str(expr_convert_to_SIMD_intrins(result,SIMD_const_varnms,SIMD_const_values,outCparams.SIMD_const_suffix,outCparams.SIMD_debug)) + ";\n" else: outstring += outtypestring + ccode_postproc( sp.ccode(result, output_varname_str[i], user_functions=custom_functions_for_SymPy_ccode) ) + "\n" # Step 6b.i: If SIMD_enable == True , and # there is at least one SIMD const variable, # then declare the SIMD_const_varnms and SIMD_const_values arrays if outCparams.SIMD_enable == "True" and len(SIMD_const_varnms) != 0: # Step 6a) Sort the list of definitions. Idea from: # https://stackoverflow.com/questions/9764298/is-it-possible-to-sort-two-listswhich-reference-each-other-in-the-exact-same-w SIMD_const_varnms, SIMD_const_values = \ (list(t) for t in zip(*sorted(zip(SIMD_const_varnms, SIMD_const_values)))) # Step 6b) Remove duplicates uniq_varnms = superfast_uniq(SIMD_const_varnms) uniq_values = superfast_uniq(SIMD_const_values) SIMD_const_varnms = uniq_varnms SIMD_const_values = uniq_values if len(SIMD_const_varnms) != len(SIMD_const_values): print( "Error: SIMD constant declaration arrays SIMD_const_varnms[] and SIMD_const_values[] have inconsistent sizes!" ) sys.exit(1) for i in range(len(SIMD_const_varnms)): if outCparams.enable_TYPE == "False": SIMD_decls += outCparams.preindent + indent + SIMD_const_varnms[ i] + " = " + SIMD_const_values[i] + ";" else: SIMD_decls += outCparams.preindent + indent + "const double " + outCparams.CSE_varprefix + SIMD_const_varnms[ i] + " = " + SIMD_const_values[i] + ";\n" SIMD_decls += outCparams.preindent + indent + "const REAL_SIMD_ARRAY " + SIMD_const_varnms[ i] + " = ConstSIMD(" + outCparams.CSE_varprefix + SIMD_const_varnms[ i] + ");\n" SIMD_decls += "\n" # Step 7: Construct final output string final_Ccode_output_str = commentblock # Step 7a: Output C code in indented curly brackets if # outCparams.includebraces = True if outCparams.includebraces == "True": final_Ccode_output_str += outCparams.preindent + "{\n" final_Ccode_output_str += prestring + SIMD_decls + outstring + poststring if outCparams.includebraces == "True": final_Ccode_output_str += outCparams.preindent + "}\n" # Step 8: If filename == "stdout", then output # C code to standard out (useful for copy-paste or interactive # mode). Otherwise output to file specified in variable name. if filename == "stdout": # Output to standard out (stdout; "the screen") print(final_Ccode_output_str) elif filename == "returnstring": return final_Ccode_output_str else: # Output to the file specified by the function input parameter string 'filename': with open(filename, outCparams.outCfileaccess) as file: file.write(final_Ccode_output_str) successstr = "" if outCparams.outCfileaccess == "a": successstr = "Appended " elif outCparams.outCfileaccess == "w": successstr = "Wrote " print(successstr + "to file \"" + filename + "\"")
def BSSN_gauge_RHSs(): # Step 1.d: Set spatial dimension (must be 3 for BSSN, as BSSN is # a 3+1-dimensional decomposition of the general # relativistic field equations) DIM = 3 # Step 1.e: Given the chosen coordinate system, set up # corresponding reference metric and needed # reference metric quantities # The following function call sets up the reference metric # and related quantities, including rescaling matrices ReDD, # ReU, and hatted quantities. rfm.reference_metric() # Step 1.f: Define needed BSSN quantities: # Declare scalars & tensors (in terms of rescaled BSSN quantities) Bq.BSSN_basic_tensors() Bq.betaU_derivs() # Declare BSSN_RHSs (excluding the time evolution equations for the gauge conditions), # if they haven't already been declared. if Brhs.have_already_called_BSSN_RHSs_function == False: print("BSSN_gauge_RHSs() Error: You must call BSSN_RHSs() before calling BSSN_gauge_RHSs().") sys.exit(1) # Step 2: Lapse conditions LapseEvolOption = par.parval_from_str(thismodule + "::LapseEvolutionOption") # Step 2.a: The 1+log lapse condition: # \partial_t \alpha = \beta^i \alpha_{,i} - 2*\alpha*K # First import expressions from BSSN_quantities cf = Bq.cf trK = Bq.trK alpha = Bq.alpha betaU = Bq.betaU # Implement the 1+log lapse condition global alpha_rhs alpha_rhs = sp.sympify(0) if LapseEvolOption == "OnePlusLog": alpha_rhs = -2 * alpha * trK alpha_dupD = ixp.declarerank1("alpha_dupD") for i in range(DIM): alpha_rhs += betaU[i] * alpha_dupD[i] # Step 2.b: Implement the harmonic slicing lapse condition elif LapseEvolOption == "HarmonicSlicing": if par.parval_from_str("BSSN.BSSN_quantities::EvolvedConformalFactor_cf") == "W": alpha_rhs = -3 * cf ** (-4) * Brhs.cf_rhs elif par.parval_from_str("BSSN.BSSN_quantities::EvolvedConformalFactor_cf") == "phi": alpha_rhs = 6 * sp.exp(6 * cf) * Brhs.cf_rhs else: print("Error LapseEvolutionOption==HarmonicSlicing unsupported for EvolvedConformalFactor_cf!=(W or phi)") sys.exit(1) # Step 2.c: Frozen lapse # \partial_t \alpha = 0 elif LapseEvolOption == "Frozen": alpha_rhs = sp.sympify(0) else: print("Error: LapseEvolutionOption == "+LapseEvolOption+" not supported!") sys.exit(1) # Step 3.a: Set \partial_t \beta^i # First check that ShiftEvolutionOption parameter choice is supported. ShiftEvolOption = par.parval_from_str(thismodule + "::ShiftEvolutionOption") if ShiftEvolOption not in ('Frozen', 'GammaDriving2ndOrder_NoCovariant', 'GammaDriving2ndOrder_Covariant', 'GammaDriving2ndOrder_Covariant__Hatted', 'GammaDriving1stOrder_Covariant', 'GammaDriving1stOrder_Covariant__Hatted'): print("Error: ShiftEvolutionOption == " + ShiftEvolOption + " unsupported!") sys.exit(1) # Next import expressions from BSSN_quantities BU = Bq.BU betU = Bq.betU betaU_dupD = Bq.betaU_dupD # Define needed quantities beta_rhsU = ixp.zerorank1() B_rhsU = ixp.zerorank1() # In the case of Frozen shift condition, we # explicitly set the betaU and BU RHS's to zero # instead of relying on the ixp.zerorank1()'s above, # for safety. if ShiftEvolOption == "Frozen": for i in range(DIM): beta_rhsU[i] = sp.sympify(0) BU[i] = sp.sympify(0) if ShiftEvolOption == "GammaDriving2ndOrder_NoCovariant": # Step 3.a.i: Compute right-hand side of beta^i # * \partial_t \beta^i = \beta^j \beta^i_{,j} + B^i for i in range(DIM): beta_rhsU[i] += BU[i] for j in range(DIM): beta_rhsU[i] += betaU[j] * betaU_dupD[i][j] # Compute right-hand side of B^i: eta = par.Cparameters("REAL", thismodule, ["eta"],2.0) # Step 3.a.ii: Compute right-hand side of B^i # * \partial_t B^i = \beta^j B^i_{,j} + 3/4 * \partial_0 \Lambda^i - eta B^i # Step 3.a.iii: Define BU_dupD, in terms of derivative of rescaled variable \bet^i BU_dupD = ixp.zerorank2() betU_dupD = ixp.declarerank2("betU_dupD", "nosym") for i in range(DIM): for j in range(DIM): BU_dupD[i][j] = betU_dupD[i][j] * rfm.ReU[i] + betU[i] * rfm.ReUdD[i][j] # Step 3.a.iv: Compute \partial_0 \bar{\Lambda}^i = (\partial_t - \beta^i \partial_i) \bar{\Lambda}^j Lambdabar_partial0 = ixp.zerorank1() for i in range(DIM): Lambdabar_partial0[i] = Brhs.Lambdabar_rhsU[i] for i in range(DIM): for j in range(DIM): Lambdabar_partial0[j] += -betaU[i] * Brhs.LambdabarU_dupD[j][i] # Step 3.a.v: Evaluate RHS of B^i: for i in range(DIM): B_rhsU[i] += sp.Rational(3, 4) * Lambdabar_partial0[i] - eta * BU[i] for j in range(DIM): B_rhsU[i] += betaU[j] * BU_dupD[i][j] # Step 3.b: The right-hand side of the \partial_t \beta^i equation if "GammaDriving2ndOrder_Covariant" in ShiftEvolOption: # Step 3.b Option 2: \partial_t \beta^i = \left[\beta^j \bar{D}_j \beta^i\right] + B^{i} # First we need GammabarUDD, defined in Bq.gammabar__inverse_and_derivs() Bq.gammabar__inverse_and_derivs() ConnectionUDD = Bq.GammabarUDD # If instead we wish to use the Hatted covariant derivative, we replace # ConnectionUDD with GammahatUDD: if ShiftEvolOption == "GammaDriving2ndOrder_Covariant__Hatted": ConnectionUDD = rfm.GammahatUDD # Then compute right-hand side: # Term 1: \beta^j \beta^i_{,j} for i in range(DIM): for j in range(DIM): beta_rhsU[i] += betaU[j] * betaU_dupD[i][j] # Term 2: \beta^j \bar{\Gamma}^i_{mj} \beta^m for i in range(DIM): for j in range(DIM): for m in range(DIM): beta_rhsU[i] += betaU[j] * ConnectionUDD[i][m][j] * betaU[m] # Term 3: B^i for i in range(DIM): beta_rhsU[i] += BU[i] if "GammaDriving2ndOrder_Covariant" in ShiftEvolOption: ConnectionUDD = Bq.GammabarUDD # If instead we wish to use the Hatted covariant derivative, we replace # ConnectionUDD with GammahatUDD: if ShiftEvolOption == "GammaDriving2ndOrder_Covariant__Hatted": ConnectionUDD = rfm.GammahatUDD # Step 3.c: Covariant option: # \partial_t B^i = \beta^j \bar{D}_j B^i # + \frac{3}{4} ( \partial_t \bar{\Lambda}^{i} - \beta^j \bar{D}_j \bar{\Lambda}^{i} ) # - \eta B^{i} # = \beta^j B^i_{,j} + \beta^j \bar{\Gamma}^i_{mj} B^m # + \frac{3}{4}[ \partial_t \bar{\Lambda}^{i} # - \beta^j (\bar{\Lambda}^i_{,j} + \bar{\Gamma}^i_{mj} \bar{\Lambda}^m)] # - \eta B^{i} # Term 1, part a: First compute B^i_{,j} using upwinded derivative BU_dupD = ixp.zerorank2() betU_dupD = ixp.declarerank2("betU_dupD", "nosym") for i in range(DIM): for j in range(DIM): BU_dupD[i][j] = betU_dupD[i][j] * rfm.ReU[i] + betU[i] * rfm.ReUdD[i][j] # Term 1: \beta^j B^i_{,j} for i in range(DIM): for j in range(DIM): B_rhsU[i] += betaU[j] * BU_dupD[i][j] # Term 2: \beta^j \bar{\Gamma}^i_{mj} B^m for i in range(DIM): for j in range(DIM): for m in range(DIM): B_rhsU[i] += betaU[j] * ConnectionUDD[i][m][j] * BU[m] # Term 3: \frac{3}{4}\partial_t \bar{\Lambda}^{i} for i in range(DIM): B_rhsU[i] += sp.Rational(3, 4) * Brhs.Lambdabar_rhsU[i] # Term 4: -\frac{3}{4}\beta^j \bar{\Lambda}^i_{,j} for i in range(DIM): for j in range(DIM): B_rhsU[i] += -sp.Rational(3, 4) * betaU[j] * Brhs.LambdabarU_dupD[i][j] # Term 5: -\frac{3}{4}\beta^j \bar{\Gamma}^i_{mj} \bar{\Lambda}^m for i in range(DIM): for j in range(DIM): for m in range(DIM): B_rhsU[i] += -sp.Rational(3, 4) * betaU[j] * ConnectionUDD[i][m][j] * Bq.LambdabarU[m] # Term 6: - \eta B^i # eta is a free parameter; we declare it here: eta = par.Cparameters("REAL", thismodule, ["eta"],2.0) for i in range(DIM): B_rhsU[i] += -eta * BU[i] if "GammaDriving1stOrder_Covariant" in ShiftEvolOption: # Step 3.c: \partial_t \beta^i = \left[\beta^j \bar{D}_j \beta^i\right] + 3/4 Lambdabar^i - eta*beta^i # First set \partial_t B^i = 0: B_rhsU = ixp.zerorank1() # \partial_t B^i = 0 # Second, set \partial_t beta^i RHS: # Compute covariant advection term: # We need GammabarUDD, defined in Bq.gammabar__inverse_and_derivs() Bq.gammabar__inverse_and_derivs() ConnectionUDD = Bq.GammabarUDD # If instead we wish to use the Hatted covariant derivative, we replace # ConnectionUDD with GammahatUDD: if ShiftEvolOption == "GammaDriving1stOrder_Covariant__Hatted": ConnectionUDD = rfm.GammahatUDD # Term 1: \beta^j \beta^i_{,j} for i in range(DIM): for j in range(DIM): beta_rhsU[i] += betaU[j] * betaU_dupD[i][j] # Term 2: \beta^j \bar{\Gamma}^i_{mj} \beta^m for i in range(DIM): for j in range(DIM): for m in range(DIM): beta_rhsU[i] += betaU[j] * ConnectionUDD[i][m][j] * betaU[m] # Term 3: 3/4 Lambdabar^i - eta*beta^i eta = par.Cparameters("REAL", thismodule, ["eta"], 2.0) for i in range(DIM): beta_rhsU[i] += sp.Rational(3, 4) * Bq.LambdabarU[i] - eta * betaU[i] # Step 4: Rescale the BSSN gauge RHS quantities so that the evolved # variables may remain smooth across coord singularities global vet_rhsU,bet_rhsU vet_rhsU = ixp.zerorank1() bet_rhsU = ixp.zerorank1() for i in range(DIM): vet_rhsU[i] = beta_rhsU[i] / rfm.ReU[i] bet_rhsU[i] = B_rhsU[i] / rfm.ReU[i]
import GiRaFFE_NRPy.GiRaFFE_NRPy_Metric_Face_Values as FCVAL import GiRaFFE_NRPy.GiRaFFE_NRPy_PPM as PPM import GiRaFFE_NRPy.GiRaFFE_NRPy_Afield_flux_handwritten as Af import GiRaFFE_NRPy.Stilde_flux as Sf import GiRaFFE_NRPy.GiRaFFE_NRPy_BCs as BC import GiRaFFE_NRPy.GiRaFFE_NRPy_A2B as A2B import GiRaFFE_NRPy.GiRaFFE_NRPy_C2P_P2C as C2P_P2C import GiRaFFE_NRPy.GiRaFFE_NRPy_Source_Terms as source thismodule = "GiRaFFE_NRPy_Main_Driver" CoordSystem = "Cartesian" outCparams = "outCverbose=False,CSE_sorting=none" par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem) rfm.reference_metric() # Create ReU, ReDD needed for rescaling B-L initial data, generating BSSN RHSs, etc. # Default Kreiss-Oliger dissipation strength default_KO_strength = 0.1 diss_strength = par.Cparameters("REAL", thismodule, "diss_strength", default_KO_strength) def GiRaFFE_NRPy_Main_Driver_generate_all(out_dir): cmd.mkdir(out_dir) gammaDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gammaDD","sym01",DIM=3) betaU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","betaU",DIM=3) alpha = gri.register_gridfunctions("AUXEVOL","alpha") AD = ixp.register_gridfunctions_for_single_rank1("EVOL","AD") BU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","BU") ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","ValenciavU")
def Psi4_tetrads(): global l4U, n4U, mre4U, mim4U # Step 1.c: Check if tetrad choice is implemented: if par.parval_from_str(thismodule+"::TetradChoice") != "QuasiKinnersley": print("ERROR: "+thismodule+"::TetradChoice = "+par.parval_from_str("TetradChoice")+" currently unsupported!") exit(1) # Step 1.d: Given the chosen coordinate system, set up # corresponding reference metric and needed # reference metric quantities # The following function call sets up the reference metric # and related quantities, including rescaling matrices ReDD, # ReU, and hatted quantities. rfm.reference_metric() # Step 1.e: Set spatial dimension (must be 3 for BSSN, as BSSN is # a 3+1-dimensional decomposition of the general # relativistic field equations) DIM = 3 # Step 1.f: Import all ADM quantities as written in terms of BSSN quantities import BSSN.ADM_in_terms_of_BSSN as AB AB.ADM_in_terms_of_BSSN() # Step 2.a: Declare the Cartesian x,y,z in terms of # xx0,xx1,xx2. x = rfm.xxCart[0] y = rfm.xxCart[1] z = rfm.xxCart[2] # Step 2.b: Declare v_1^a, v_2^a, and v_3^a tetrads, # as well as detgamma and gammaUU from # BSSN.ADM_in_terms_of_BSSN v1UCart = ixp.zerorank1() v2UCart = ixp.zerorank1() detgamma = AB.detgamma gammaUU = AB.gammaUU # Step 2.c: Define v1U and v2U v1UCart = [-y, x, sp.sympify(0)] v2UCart = [x, y, z] # Step 2.d: Construct the Jacobian d x_Cart^i / d xx^j Jac_dUCart_dDrfmUD = ixp.zerorank2() for i in range(DIM): for j in range(DIM): Jac_dUCart_dDrfmUD[i][j] = sp.simplify(sp.diff(rfm.xxCart[i], rfm.xx[j])) # Step 2.e: Invert above Jacobian to get needed d xx^j / d x_Cart^i Jac_dUrfm_dDCartUD, dummyDET = ixp.generic_matrix_inverter3x3(Jac_dUCart_dDrfmUD) # Step 2.e.i: Simplify expressions for d xx^j / d x_Cart^i: for i in range(DIM): for j in range(DIM): Jac_dUrfm_dDCartUD[i][j] = sp.simplify(Jac_dUrfm_dDCartUD[i][j]) # Step 2.f: Transform v1U and v2U from the Cartesian to the xx^i basis v1U = ixp.zerorank1() v2U = ixp.zerorank1() for i in range(DIM): for j in range(DIM): v1U[i] += Jac_dUrfm_dDCartUD[i][j] * v1UCart[j] v2U[i] += Jac_dUrfm_dDCartUD[i][j] * v2UCart[j] # Step 2.g: Define the rank-3 version of the Levi-Civita symbol. Amongst # other uses, this is needed for the construction of the approximate # quasi-Kinnersley tetrad. def define_LeviCivitaSymbol_rank3(DIM=-1): if DIM == -1: DIM = par.parval_from_str("DIM") LeviCivitaSymbol = ixp.zerorank3() for i in range(DIM): for j in range(DIM): for k in range(DIM): # From https://codegolf.stackexchange.com/questions/160359/levi-civita-symbol : LeviCivitaSymbol[i][j][k] = (i - j) * (j - k) * (k - i) / 2 return LeviCivitaSymbol # Step 2.h: Define v3U v3U = ixp.zerorank1() LeviCivitaSymbolDDD = define_LeviCivitaSymbol_rank3(DIM=3) for a in range(DIM): for b in range(DIM): for c in range(DIM): for d in range(DIM): v3U[a] += sp.sqrt(detgamma) * gammaUU[a][d] * LeviCivitaSymbolDDD[d][b][c] * v1U[b] * v2U[c] # Step 2.h.i: Simplify expressions for v1U,v2U,v3U. This greatly expedites the C code generation (~10x faster) for a in range(DIM): v1U[a] = sp.simplify(v1U[a]) v2U[a] = sp.simplify(v2U[a]) v3U[a] = sp.simplify(v3U[a]) # Step 2.i: Define omega_{ij} omegaDD = ixp.zerorank2() gammaDD = AB.gammaDD def v_vectorDU(v1U,v2U,v3U, i,a): if i==0: return v1U[a] elif i==1: return v2U[a] elif i==2: return v3U[a] else: print("ERROR: unknown vector!") exit(1) def update_omega(omegaDD, i,j, v1U,v2U,v3U,gammaDD): omegaDD[i][j] = sp.sympify(0) for a in range(DIM): for b in range(DIM): omegaDD[i][j] += v_vectorDU(v1U,v2U,v3U, i,a)*v_vectorDU(v1U,v2U,v3U, j,b)*gammaDD[a][b] # Step 2.j: Define e^a_i. Note that: # omegaDD[0][0] = \omega_{11} above; # omegaDD[1][1] = \omega_{22} above, etc. # First e_1^a: Orthogonalize & normalize: e1U = ixp.zerorank1() update_omega(omegaDD, 0,0, v1U,v2U,v3U,gammaDD) for a in range(DIM): e1U[a] = v1U[a]/sp.sqrt(omegaDD[0][0]) # Next e_2^a: First orthogonalize: e2U = ixp.zerorank1() update_omega(omegaDD, 0,1, e1U,v2U,v3U,gammaDD) for a in range(DIM): e2U[a] = (v2U[a] - omegaDD[0][1]*e1U[a]) # Then normalize: update_omega(omegaDD, 1,1, e1U,e2U,v3U,gammaDD) for a in range(DIM): e2U[a] /= sp.sqrt(omegaDD[1][1]) # Next e_3^a: First orthogonalize: e3U = ixp.zerorank1() update_omega(omegaDD, 0,2, e1U,e2U,v3U,gammaDD) update_omega(omegaDD, 1,2, e1U,e2U,v3U,gammaDD) for a in range(DIM): e3U[a] = (v3U[a] - omegaDD[0][2]*e1U[a] - omegaDD[1][2]*e2U[a]) # Then normalize: update_omega(omegaDD, 2,2, e1U,e2U,e3U,gammaDD) for a in range(DIM): e3U[a] /= sp.sqrt(omegaDD[2][2]) # Step 2.k: Construct l^mu, n^mu, and m^mu, based on r^mu, theta^mu, phi^mu, and u^mu: r4U = ixp.zerorank1(DIM=4) u4U = ixp.zerorank1(DIM=4) theta4U = ixp.zerorank1(DIM=4) phi4U = ixp.zerorank1(DIM=4) for a in range(DIM): r4U[ a+1] = e2U[a] theta4U[a+1] = e3U[a] phi4U[ a+1] = e1U[a] # FIXME? assumes alpha=1, beta^i = 0 if par.parval_from_str(thismodule+"::UseCorrectUnitNormal") == "False": u4U[0] = 1 else: # Eq. 2.116 in Baumgarte & Shapiro: # n^mu = {1/alpha, -beta^i/alpha}. Note that n_mu = {alpha,0}, so n^mu n_mu = -1. import BSSN.BSSN_quantities as Bq Bq.declare_BSSN_gridfunctions_if_not_declared_already() Bq.BSSN_basic_tensors() u4U[0] = 1/Bq.alpha for i in range(DIM): u4U[i+1] = -Bq.betaU[i]/Bq.alpha l4U = ixp.zerorank1(DIM=4) n4U = ixp.zerorank1(DIM=4) mre4U = ixp.zerorank1(DIM=4) mim4U = ixp.zerorank1(DIM=4) # M_SQRT1_2 = 1 / sqrt(2) (defined in math.h on Linux) M_SQRT1_2 = par.Cparameters("REAL",thismodule,"M_SQRT1_2") isqrt2 = M_SQRT1_2 #1/sp.sqrt(2) <- SymPy drops precision to 15 sig. digits in unit tests for mu in range(4): l4U[mu] = isqrt2*(u4U[mu] + r4U[mu]) n4U[mu] = isqrt2*(u4U[mu] - r4U[mu]) mre4U[mu] = isqrt2*theta4U[mu] mim4U[mu] = isqrt2* phi4U[mu]
def GiRaFFE_NRPy_Main_Driver_generate_all(out_dir): cmd.mkdir(out_dir) gammaDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gammaDD","sym01",DIM=3) betaU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","betaU",DIM=3) alpha = gri.register_gridfunctions("AUXEVOL","alpha") AD = ixp.register_gridfunctions_for_single_rank1("EVOL","AD") BU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","BU") ValenciavU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","ValenciavU") psi6Phi = gri.register_gridfunctions("EVOL","psi6Phi") StildeD = ixp.register_gridfunctions_for_single_rank1("EVOL","StildeD") ixp.register_gridfunctions_for_single_rank1("AUXEVOL","PhievolParenU",DIM=3) gri.register_gridfunctions("AUXEVOL","AevolParen") # Declare this symbol: sqrt4pi = par.Cparameters("REAL",thismodule,"sqrt4pi","sqrt(4.0*M_PI)") GRHD.compute_sqrtgammaDET(gammaDD) GRFFE.compute_AD_source_term_operand_for_FD(GRHD.sqrtgammaDET,betaU,alpha,psi6Phi,AD) GRFFE.compute_psi6Phi_rhs_flux_term_operand(gammaDD,GRHD.sqrtgammaDET,betaU,alpha,AD,psi6Phi) parens_to_print = [\ lhrh(lhs=gri.gfaccess("auxevol_gfs","AevolParen"),rhs=GRFFE.AevolParen),\ lhrh(lhs=gri.gfaccess("auxevol_gfs","PhievolParenU0"),rhs=GRFFE.PhievolParenU[0]),\ lhrh(lhs=gri.gfaccess("auxevol_gfs","PhievolParenU1"),rhs=GRFFE.PhievolParenU[1]),\ lhrh(lhs=gri.gfaccess("auxevol_gfs","PhievolParenU2"),rhs=GRFFE.PhievolParenU[2]),\ ] subdir = "RHSs" cmd.mkdir(os.path.join(out_dir, subdir)) desc = "Calculate quantities to be finite-differenced for the GRFFE RHSs" name = "calculate_AD_gauge_term_psi6Phi_flux_term_for_RHSs" outCfunction( outfile = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name, params ="const paramstruct *restrict params,const REAL *restrict in_gfs,REAL *restrict auxevol_gfs", body = fin.FD_outputC("returnstring",parens_to_print,params=outCparams), loopopts ="AllPoints", rel_path_to_Cparams=os.path.join("../")) xi_damping = par.Cparameters("REAL",thismodule,"xi_damping",0.1) GRFFE.compute_psi6Phi_rhs_damping_term(alpha,psi6Phi,xi_damping) AevolParen_dD = ixp.declarerank1("AevolParen_dD",DIM=3) PhievolParenU_dD = ixp.declarerank2("PhievolParenU_dD","nosym",DIM=3) A_rhsD = ixp.zerorank1() psi6Phi_rhs = GRFFE.psi6Phi_damping for i in range(3): A_rhsD[i] += -AevolParen_dD[i] psi6Phi_rhs += -PhievolParenU_dD[i][i] # Add Kreiss-Oliger dissipation to the GRFFE RHSs: # psi6Phi_dKOD = ixp.declarerank1("psi6Phi_dKOD") # AD_dKOD = ixp.declarerank2("AD_dKOD","nosym") # for i in range(3): # psi6Phi_rhs += diss_strength*psi6Phi_dKOD[i]*rfm.ReU[i] # ReU[i] = 1/scalefactor_orthog_funcform[i] # for j in range(3): # A_rhsD[j] += diss_strength*AD_dKOD[j][i]*rfm.ReU[i] # ReU[i] = 1/scalefactor_orthog_funcform[i] RHSs_to_print = [\ lhrh(lhs=gri.gfaccess("rhs_gfs","AD0"),rhs=A_rhsD[0]),\ lhrh(lhs=gri.gfaccess("rhs_gfs","AD1"),rhs=A_rhsD[1]),\ lhrh(lhs=gri.gfaccess("rhs_gfs","AD2"),rhs=A_rhsD[2]),\ lhrh(lhs=gri.gfaccess("rhs_gfs","psi6Phi"),rhs=psi6Phi_rhs),\ ] desc = "Calculate AD gauge term and psi6Phi RHSs" name = "calculate_AD_gauge_psi6Phi_RHSs" source_Ccode = outCfunction( outfile = "returnstring", desc=desc, name=name, params ="const paramstruct *params,const REAL *in_gfs,const REAL *auxevol_gfs,REAL *rhs_gfs", body = fin.FD_outputC("returnstring",RHSs_to_print,params=outCparams), loopopts ="InteriorPoints", rel_path_to_Cparams=os.path.join("../")).replace("= NGHOSTS","= NGHOSTS_A2B").replace("NGHOSTS+Nxx0","Nxx_plus_2NGHOSTS0-NGHOSTS_A2B").replace("NGHOSTS+Nxx1","Nxx_plus_2NGHOSTS1-NGHOSTS_A2B").replace("NGHOSTS+Nxx2","Nxx_plus_2NGHOSTS2-NGHOSTS_A2B") # Note the above .replace() functions. These serve to expand the loop range into the ghostzones, since # the second-order FD needs fewer than some other algorithms we use do. with open(os.path.join(out_dir,subdir,name+".h"),"w") as file: file.write(source_Ccode) source.write_out_functions_for_StildeD_source_term(os.path.join(out_dir,subdir),outCparams,gammaDD,betaU,alpha, ValenciavU,BU,sqrt4pi) subdir = "FCVAL" cmd.mkdir(os.path.join(out_dir, subdir)) FCVAL.GiRaFFE_NRPy_FCVAL(os.path.join(out_dir,subdir)) subdir = "PPM" cmd.mkdir(os.path.join(out_dir, subdir)) PPM.GiRaFFE_NRPy_PPM(os.path.join(out_dir,subdir)) # We will pass values of the gridfunction on the cell faces into the function. This requires us # to declare them as C parameters in NRPy+. We will denote this with the _face infix/suffix. alpha_face = gri.register_gridfunctions("AUXEVOL","alpha_face") gamma_faceDD = ixp.register_gridfunctions_for_single_rank2("AUXEVOL","gamma_faceDD","sym01") beta_faceU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","beta_faceU") # We'll need some more gridfunctions, now, to represent the reconstructions of BU and ValenciavU # on the right and left faces Valenciav_rU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","Valenciav_rU",DIM=3) B_rU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","B_rU",DIM=3) Valenciav_lU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","Valenciav_lU",DIM=3) B_lU = ixp.register_gridfunctions_for_single_rank1("AUXEVOL","B_lU",DIM=3) subdir = "RHSs" Af.GiRaFFE_NRPy_Afield_flux(os.path.join(out_dir,subdir)) ixp.register_gridfunctions_for_single_rank1("AUXEVOL","Stilde_flux_HLLED") Sf.generate_C_code_for_Stilde_flux(os.path.join(out_dir,subdir), True, alpha_face,gamma_faceDD,beta_faceU, Valenciav_rU,B_rU,Valenciav_lU,B_lU,sqrt4pi) subdir = "boundary_conditions" cmd.mkdir(os.path.join(out_dir,subdir)) BC.GiRaFFE_NRPy_BCs(os.path.join(out_dir,subdir)) subdir = "A2B" cmd.mkdir(os.path.join(out_dir,subdir)) A2B.GiRaFFE_NRPy_A2B(os.path.join(out_dir,subdir),gammaDD,AD,BU) C2P_P2C.GiRaFFE_NRPy_C2P(StildeD,BU,gammaDD,betaU,alpha) values_to_print = [ lhrh(lhs=gri.gfaccess("in_gfs","StildeD0"),rhs=C2P_P2C.outStildeD[0]), lhrh(lhs=gri.gfaccess("in_gfs","StildeD1"),rhs=C2P_P2C.outStildeD[1]), lhrh(lhs=gri.gfaccess("in_gfs","StildeD2"),rhs=C2P_P2C.outStildeD[2]), lhrh(lhs=gri.gfaccess("auxevol_gfs","ValenciavU0"),rhs=C2P_P2C.ValenciavU[0]), lhrh(lhs=gri.gfaccess("auxevol_gfs","ValenciavU1"),rhs=C2P_P2C.ValenciavU[1]), lhrh(lhs=gri.gfaccess("auxevol_gfs","ValenciavU2"),rhs=C2P_P2C.ValenciavU[2]) ] subdir = "C2P" cmd.mkdir(os.path.join(out_dir,subdir)) desc = "Apply fixes to \tilde{S}_i and recompute the velocity to match with current sheet prescription." name = "GiRaFFE_NRPy_cons_to_prims" outCfunction( outfile = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name, params ="const paramstruct *params,REAL *xx[3],REAL *auxevol_gfs,REAL *in_gfs", body = fin.FD_outputC("returnstring",values_to_print,params=outCparams), loopopts ="AllPoints,Read_xxs", rel_path_to_Cparams=os.path.join("../")) C2P_P2C.GiRaFFE_NRPy_P2C(gammaDD,betaU,alpha, ValenciavU,BU, sqrt4pi) values_to_print = [ lhrh(lhs=gri.gfaccess("in_gfs","StildeD0"),rhs=C2P_P2C.StildeD[0]), lhrh(lhs=gri.gfaccess("in_gfs","StildeD1"),rhs=C2P_P2C.StildeD[1]), lhrh(lhs=gri.gfaccess("in_gfs","StildeD2"),rhs=C2P_P2C.StildeD[2]), ] desc = "Recompute StildeD after current sheet fix to Valencia 3-velocity to ensure consistency between conservative & primitive variables." name = "GiRaFFE_NRPy_prims_to_cons" outCfunction( outfile = os.path.join(out_dir,subdir,name+".h"), desc=desc, name=name, params ="const paramstruct *params,REAL *auxevol_gfs,REAL *in_gfs", body = fin.FD_outputC("returnstring",values_to_print,params=outCparams), loopopts ="AllPoints", rel_path_to_Cparams=os.path.join("../")) # Write out the main driver itself: with open(os.path.join(out_dir,"GiRaFFE_NRPy_Main_Driver.h"),"w") as file: file.write(r"""// Structure to track ghostzones for PPM: typedef struct __gf_and_gz_struct__ { REAL *gf; int gz_lo[4],gz_hi[4]; } gf_and_gz_struct; // Some additional constants needed for PPM: const int VX=0,VY=1,VZ=2,BX=3,BY=4,BZ=5; const int NUM_RECONSTRUCT_GFS = 6; // Include ALL functions needed for evolution #include "RHSs/calculate_AD_gauge_term_psi6Phi_flux_term_for_RHSs.h" #include "RHSs/calculate_AD_gauge_psi6Phi_RHSs.h" #include "PPM/reconstruct_set_of_prims_PPM_GRFFE_NRPy.c" #include "FCVAL/interpolate_metric_gfs_to_cell_faces.h" #include "RHSs/calculate_StildeD0_source_term.h" #include "RHSs/calculate_StildeD1_source_term.h" #include "RHSs/calculate_StildeD2_source_term.h" #include "RHSs/calculate_E_field_flat_all_in_one.h" #include "RHSs/calculate_Stilde_flux_D0.h" #include "RHSs/calculate_Stilde_flux_D1.h" #include "RHSs/calculate_Stilde_flux_D2.h" #include "RHSs/calculate_Stilde_rhsD.h" #include "boundary_conditions/GiRaFFE_boundary_conditions.h" #include "A2B/driver_AtoB.h" #include "C2P/GiRaFFE_NRPy_cons_to_prims.h" #include "C2P/GiRaFFE_NRPy_prims_to_cons.h" void GiRaFFE_NRPy_RHSs(const paramstruct *restrict params,REAL *restrict auxevol_gfs,const REAL *restrict in_gfs,REAL *restrict rhs_gfs) { #include "set_Cparameters.h" // First thing's first: initialize the RHSs to zero! #pragma omp parallel for for(int ii=0;ii<Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2*NUM_EVOL_GFS;ii++) { rhs_gfs[ii] = 0.0; } // Next calculate the easier source terms that don't require flux directions // This will also reset the RHSs for each gf at each new timestep. calculate_AD_gauge_term_psi6Phi_flux_term_for_RHSs(params,in_gfs,auxevol_gfs); calculate_AD_gauge_psi6Phi_RHSs(params,in_gfs,auxevol_gfs,rhs_gfs); // Now, we set up a bunch of structs of pointers to properly guide the PPM algorithm. // They also count the number of ghostzones available. gf_and_gz_struct in_prims[NUM_RECONSTRUCT_GFS], out_prims_r[NUM_RECONSTRUCT_GFS], out_prims_l[NUM_RECONSTRUCT_GFS]; int which_prims_to_reconstruct[NUM_RECONSTRUCT_GFS],num_prims_to_reconstruct; const int Nxxp2NG012 = Nxx_plus_2NGHOSTS0*Nxx_plus_2NGHOSTS1*Nxx_plus_2NGHOSTS2; REAL *temporary = auxevol_gfs + Nxxp2NG012*AEVOLPARENGF; //We're not using this anymore // This sets pointers to the portion of auxevol_gfs containing the relevant gridfunction. int ww=0; in_prims[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAVU0GF; out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_RU0GF; out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_LU0GF; ww++; in_prims[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAVU1GF; out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_RU1GF; out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_LU1GF; ww++; in_prims[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAVU2GF; out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_RU2GF; out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*VALENCIAV_LU2GF; ww++; in_prims[ww].gf = auxevol_gfs + Nxxp2NG012*BU0GF; out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*B_RU0GF; out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*B_LU0GF; ww++; in_prims[ww].gf = auxevol_gfs + Nxxp2NG012*BU1GF; out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*B_RU1GF; out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*B_LU1GF; ww++; in_prims[ww].gf = auxevol_gfs + Nxxp2NG012*BU2GF; out_prims_r[ww].gf = auxevol_gfs + Nxxp2NG012*B_RU2GF; out_prims_l[ww].gf = auxevol_gfs + Nxxp2NG012*B_LU2GF; ww++; // Prims are defined AT ALL GRIDPOINTS, so we set the # of ghostzones to zero: for(int i=0;i<NUM_RECONSTRUCT_GFS;i++) for(int j=1;j<=3;j++) { in_prims[i].gz_lo[j]=0; in_prims[i].gz_hi[j]=0; } // Left/right variables are not yet defined, yet we set the # of gz's to zero by default: for(int i=0;i<NUM_RECONSTRUCT_GFS;i++) for(int j=1;j<=3;j++) { out_prims_r[i].gz_lo[j]=0; out_prims_r[i].gz_hi[j]=0; } for(int i=0;i<NUM_RECONSTRUCT_GFS;i++) for(int j=1;j<=3;j++) { out_prims_l[i].gz_lo[j]=0; out_prims_l[i].gz_hi[j]=0; } ww=0; which_prims_to_reconstruct[ww]=VX; ww++; which_prims_to_reconstruct[ww]=VY; ww++; which_prims_to_reconstruct[ww]=VZ; ww++; which_prims_to_reconstruct[ww]=BX; ww++; which_prims_to_reconstruct[ww]=BY; ww++; which_prims_to_reconstruct[ww]=BZ; ww++; num_prims_to_reconstruct=ww; // In each direction, perform the PPM reconstruction procedure. // Then, add the fluxes to the RHS as appropriate. for(int flux_dirn=0;flux_dirn<3;flux_dirn++) { // In each direction, interpolate the metric gfs (gamma,beta,alpha) to cell faces. interpolate_metric_gfs_to_cell_faces(params,auxevol_gfs,flux_dirn+1); // Then, reconstruct the primitive variables on the cell faces. // This function is housed in the file: "reconstruct_set_of_prims_PPM_GRFFE_NRPy.c" reconstruct_set_of_prims_PPM_GRFFE_NRPy(params, auxevol_gfs, flux_dirn+1, num_prims_to_reconstruct, which_prims_to_reconstruct, in_prims, out_prims_r, out_prims_l, temporary); // For example, if flux_dirn==0, then at gamma_faceDD00(i,j,k) represents gamma_{xx} // at (i-1/2,j,k), Valenciav_lU0(i,j,k) is the x-component of the velocity at (i-1/2-epsilon,j,k), // and Valenciav_rU0(i,j,k) is the x-component of the velocity at (i-1/2+epsilon,j,k). if(flux_dirn==0) { // Next, we calculate the source term for StildeD. Again, this also resets the rhs_gfs array at // each new timestep. calculate_StildeD0_source_term(params,auxevol_gfs,rhs_gfs); // Now, compute the electric field on each face of a cell and add it to the RHSs as appropriate //calculate_E_field_D0_right(params,auxevol_gfs,rhs_gfs); //calculate_E_field_D0_left(params,auxevol_gfs,rhs_gfs); // Finally, we calculate the flux of StildeD and add the appropriate finite-differences // to the RHSs. calculate_Stilde_flux_D0(params,auxevol_gfs,rhs_gfs); } else if(flux_dirn==1) { calculate_StildeD1_source_term(params,auxevol_gfs,rhs_gfs); //calculate_E_field_D1_right(params,auxevol_gfs,rhs_gfs); //calculate_E_field_D1_left(params,auxevol_gfs,rhs_gfs); calculate_Stilde_flux_D1(params,auxevol_gfs,rhs_gfs); } else { calculate_StildeD2_source_term(params,auxevol_gfs,rhs_gfs); //calculate_E_field_D2_right(params,auxevol_gfs,rhs_gfs); //calculate_E_field_D2_left(params,auxevol_gfs,rhs_gfs); calculate_Stilde_flux_D2(params,auxevol_gfs,rhs_gfs); } calculate_Stilde_rhsD(flux_dirn+1,params,auxevol_gfs,rhs_gfs); for(int count=0;count<=1;count++) { // This function is written to be general, using notation that matches the forward permutation added to AD2, // i.e., [F_HLL^x(B^y)]_z corresponding to flux_dirn=0, count=1. // The SIGN parameter is necessary because // -E_z(x_i,y_j,z_k) = 0.25 ( [F_HLL^x(B^y)]_z(i+1/2,j,k)+[F_HLL^x(B^y)]_z(i-1/2,j,k) // -[F_HLL^y(B^x)]_z(i,j+1/2,k)-[F_HLL^y(B^x)]_z(i,j-1/2,k) ) // Note the negative signs on the reversed permutation terms! // By cyclically permuting with flux_dirn, we // get contributions to the other components, and by incrementing count, we get the backward permutations: // Let's suppose flux_dirn = 0. Then we will need to update Ay (count=0) and Az (count=1): // flux_dirn=count=0 -> AD0GF+(flux_dirn+1+count)%3 = AD0GF + (0+1+0)%3=AD1GF <- Updating Ay! // (flux_dirn)%3 = (0)%3 = 0 Vx // (flux_dirn-count+2)%3 = (0-0+2)%3 = 2 Vz . Inputs Vx, Vz -> SIGN = -1 ; 2.0*((REAL)count)-1.0=-1 check! // flux_dirn=0,count=1 -> AD0GF+(flux_dirn+1+count)%3 = AD0GF + (0+1+1)%3=AD2GF <- Updating Az! // (flux_dirn)%3 = (0)%3 = 0 Vx // (flux_dirn-count+2)%3 = (0-1+2)%3 = 1 Vy . Inputs Vx, Vy -> SIGN = +1 ; 2.0*((REAL)count)-1.0=2-1=+1 check! // Let's suppose flux_dirn = 1. Then we will need to update Az (count=0) and Ax (count=1): // flux_dirn=1,count=0 -> AD0GF+(flux_dirn+1+count)%3 = AD0GF + (1+1+0)%3=AD2GF <- Updating Az! // (flux_dirn)%3 = (1)%3 = 1 Vy // (flux_dirn-count+2)%3 = (1-0+2)%3 = 0 Vx . Inputs Vy, Vx -> SIGN = -1 ; 2.0*((REAL)count)-1.0=-1 check! // flux_dirn=count=1 -> AD0GF+(flux_dirn+1+count)%3 = AD0GF + (1+1+1)%3=AD0GF <- Updating Ax! // (flux_dirn)%3 = (1)%3 = 1 Vy // (flux_dirn-count+2)%3 = (1-1+2)%3 = 2 Vz . Inputs Vy, Vz -> SIGN = +1 ; 2.0*((REAL)count)-1.0=2-1=+1 check! // Let's suppose flux_dirn = 2. Then we will need to update Ax (count=0) and Ay (count=1): // flux_dirn=2,count=0 -> AD0GF+(flux_dirn+1+count)%3 = AD0GF + (2+1+0)%3=AD0GF <- Updating Ax! // (flux_dirn)%3 = (2)%3 = 2 Vz // (flux_dirn-count+2)%3 = (2-0+2)%3 = 1 Vy . Inputs Vz, Vy -> SIGN = -1 ; 2.0*((REAL)count)-1.0=-1 check! // flux_dirn=2,count=1 -> AD0GF+(flux_dirn+1+count)%3 = AD0GF + (2+1+1)%3=AD1GF <- Updating Ay! // (flux_dirn)%3 = (2)%3 = 2 Vz // (flux_dirn-count+2)%3 = (2-1+2)%3 = 0 Vx . Inputs Vz, Vx -> SIGN = +1 ; 2.0*((REAL)count)-1.0=2-1=+1 check! calculate_E_field_flat_all_in_one(params, &auxevol_gfs[IDX4ptS(VALENCIAV_RU0GF+(flux_dirn)%3, 0)],&auxevol_gfs[IDX4ptS(VALENCIAV_RU0GF+(flux_dirn-count+2)%3, 0)], &auxevol_gfs[IDX4ptS(VALENCIAV_LU0GF+(flux_dirn)%3, 0)],&auxevol_gfs[IDX4ptS(VALENCIAV_LU0GF+(flux_dirn-count+2)%3, 0)], &auxevol_gfs[IDX4ptS(B_RU0GF +(flux_dirn)%3, 0)],&auxevol_gfs[IDX4ptS(B_RU0GF +(flux_dirn-count+2)%3, 0)], &auxevol_gfs[IDX4ptS(B_LU0GF +(flux_dirn)%3, 0)],&auxevol_gfs[IDX4ptS(B_LU0GF +(flux_dirn-count+2)%3, 0)], &auxevol_gfs[IDX4ptS(B_RU0GF +(flux_dirn-count+2)%3, 0)], &auxevol_gfs[IDX4ptS(B_LU0GF +(flux_dirn-count+2)%3, 0)], &auxevol_gfs[IDX4ptS(GAMMA_FACEDD00GF,0)],&auxevol_gfs[IDX4ptS(GAMMA_FACEDD01GF,0)],&auxevol_gfs[IDX4ptS(GAMMA_FACEDD02GF,0)], &auxevol_gfs[IDX4ptS(GAMMA_FACEDD11GF,0)],&auxevol_gfs[IDX4ptS(GAMMA_FACEDD12GF,0)],&auxevol_gfs[IDX4ptS(GAMMA_FACEDD22GF,0)], &auxevol_gfs[IDX4ptS(BETA_FACEU0GF+flux_dirn,0)],&auxevol_gfs[IDX4ptS(BETA_FACEU0GF+(flux_dirn-count+2)%3,0)],&auxevol_gfs[IDX4ptS(ALPHA_FACEGF,0)], &rhs_gfs[IDX4ptS(AD0GF+(flux_dirn+1+count)%3,0)], 2.0*((REAL)count)-1.0, flux_dirn); } } } void GiRaFFE_NRPy_post_step(const paramstruct *restrict params,REAL *xx[3],REAL *restrict auxevol_gfs,REAL *restrict evol_gfs,const int n) { // First, apply BCs to AD and psi6Phi. Then calculate BU from AD apply_bcs_potential(params,evol_gfs); driver_A_to_B(params,evol_gfs,auxevol_gfs); //override_BU_with_old_GiRaFFE(params,auxevol_gfs,n); // Apply fixes to StildeD, then recompute the velocity at the new timestep. // Apply the current sheet prescription to the velocities GiRaFFE_NRPy_cons_to_prims(params,xx,auxevol_gfs,evol_gfs); // Then, recompute StildeD to be consistent with the new velocities //GiRaFFE_NRPy_prims_to_cons(params,auxevol_gfs,evol_gfs); // Finally, apply outflow boundary conditions to the velocities. apply_bcs_velocity(params,auxevol_gfs); } """)
# This code calculates the Weyl scalars psi0, psi1, psi2, psi3, and psi4. It does so by following the paper # Baker, Campanelli, and Lousto. PRD 65, 044001 (2002), gr-qc/0104063 and the example set by the Kranc- # generated ETK thorn which can be found at https://bitbucket.org/einsteintoolkit/einsteinanalysis/src # Step 1: import all needed modules from NRPy+: import NRPy_param_funcs as par import indexedexp as ixp import grid as gri import finite_difference as fin from outputC import * import sympy as sp # Step 2: Initialize WeylScalars parameters thismodule = __name__ # Current option: Approx_QuasiKinnersley = choice made in Baker, Campanelli, and Lousto. PRD 65, 044001 (2002) par.initialize_param(par.glb_param("char *", thismodule, "TetradChoice", "Approx_QuasiKinnersley")) # This controls what gets output. Acceptable values are "psi4_only", "all_psis", and "all_psis_and_invariants" par.initialize_param(par.glb_param("char *", thismodule, "output_scalars", "all_psis_and_invariants")) # Step 3: Define the rank-3 version of the Levi-Civita symbol. Amongst # other possible uses, this is needed for the construction of the approximate # quasi-Kinnersley tetrad. def define_LeviCivitaSymbol_rank3(DIM=-1): if DIM == -1: DIM = par.parval_from_str("DIM") LeviCivitaSymbol = ixp.zerorank3() for i in range(DIM): for j in range(DIM): for k in range(DIM): # From https://codegolf.stackexchange.com/questions/160359/levi-civita-symbol :
def outputC(sympyexpr, output_varname_str, filename="stdout", params="", prestring="", poststring=""): outCparams = parse_outCparams_string(params) preindent = outCparams.preindent TYPE = par.parval_from_str("PRECISION") if outCparams.enable_TYPE == "False": TYPE = "" # Step 0: Initialize # commentblock: comment block containing the input SymPy string, # set only if outCverbose==True # outstring: the output C code string commentblock = "" outstring = "" # Step 1: If SIMD_enable==True, then check if TYPE=="double". If not, error out. # Otherwise set TYPE="REAL_SIMD_ARRAY", which should be #define'd # within the C code. For example for AVX-256, the C code should have # #define REAL_SIMD_ARRAY __m256d if outCparams.SIMD_enable == "True": if TYPE not in ('double', ''): print( "SIMD output currently only supports double precision or typeless. Sorry!" ) sys.exit(1) if TYPE == "double": TYPE = "REAL_SIMD_ARRAY" # Step 2a: Apply sanity checks when either sympyexpr or # output_varname_str is a list. if type(output_varname_str) is list and type(sympyexpr) is not list: print( "Error: Provided a list of output variable names, but only one SymPy expression." ) sys.exit(1) if type(sympyexpr) is list: if type(output_varname_str) is not list: print( "Error: Provided a list of SymPy expressions, but no corresponding list of output variable names" ) sys.exit(1) elif len(output_varname_str) != len(sympyexpr): print("Error: Length of SymPy expressions list (" + str(len(sympyexpr)) + ") != Length of corresponding output variable name list (" + str(len(output_varname_str)) + ")") sys.exit(1) # Step 2b: If sympyexpr and output_varname_str are not lists, # convert them to lists of one element each, to # simplify proceeding code. if type(output_varname_str) is not list and type(sympyexpr) is not list: output_varname_strtmp = [output_varname_str] output_varname_str = output_varname_strtmp sympyexprtmp = [sympyexpr] sympyexpr = sympyexprtmp sympyexpr = sympyexpr[:] # pass-by-value (copy list) # Step 3: If outCparams.verbose = True, then output the original SymPy # expression(s) in code comments prior to actual C code if outCparams.outCverbose == "True": commentblock += preindent + "/*\n" + preindent + " * Original SymPy expression" if len(output_varname_str) > 1: commentblock += "s" commentblock += ":\n" for i in range(len(output_varname_str)): if i == 0: if len(output_varname_str) != 1: commentblock += preindent + " * \"[" else: commentblock += preindent + " * \"" else: commentblock += preindent + " * " commentblock += output_varname_str[i] + " = " + str(sympyexpr[i]) if i == len(output_varname_str) - 1: if len(output_varname_str) != 1: commentblock += "]\"\n" else: commentblock += "\"\n" else: commentblock += ",\n" commentblock += preindent + " */\n" # Step 4: Add proper indentation of C code: if outCparams.includebraces == "True": indent = outCparams.preindent + " " else: indent = outCparams.preindent + "" # Step 5: Should the output variable, e.g., outvar, be declared? # If so, start output line with e.g., "double outvar " outtypestring = "" if outCparams.declareoutputvars == "True": outtypestring = outCparams.preindent + indent + TYPE + " " else: outtypestring = outCparams.preindent + indent # Step 6a: If common subexpression elimination (CSE) disabled, then # just output the SymPy string in the most boring way, # nearly consistent with SymPy's ccode() function, # though with support for float & long double types # as well. SIMD_RATIONAL_decls = RATIONAL_decls = "" if outCparams.CSE_enable == "False": # If CSE is disabled: for i in range(len(sympyexpr)): outstring += outtypestring + ccode_postproc( sp.ccode( sympyexpr[i], output_varname_str[i], user_functions=custom_functions_for_SymPy_ccode)) + "\n" # Step 6b: If CSE enabled, then perform CSE using SymPy and then # resulting C code. else: # If CSE is enabled: SIMD_const_varnms = [] SIMD_const_values = [] varprefix = '' if outCparams.CSE_varprefix == 'tmp' else outCparams.CSE_varprefix if outCparams.CSE_preprocess == "True" or outCparams.SIMD_enable == "True": # If CSE_preprocess == True, then perform partial factorization # If SIMD_enable == True, then declare _NegativeOne_ in preprocessing factor_negative = eval(outCparams.SIMD_enable) and eval( outCparams.SIMD_find_more_subs) sympyexpr, map_sym_to_rat = cse_preprocess(sympyexpr,prefix=varprefix,\ declare=eval(outCparams.SIMD_enable),negative=factor_negative,factor=eval(outCparams.CSE_preprocess)) for v in map_sym_to_rat: p, q = float(map_sym_to_rat[v].p), float(map_sym_to_rat[v].q) if outCparams.SIMD_enable == "False": RATIONAL_decls += outCparams.preindent + indent + 'const double ' + str( v) + ' = ' # Since Integer is a subclass of Rational in SymPy, we need only check whether # the denominator q = 1 to determine if a rational is an integer. if q != 1: RATIONAL_decls += str(p) + '/' + str(q) + ';\n' else: RATIONAL_decls += str(p) + ';\n' sympy_version = sp.__version__.replace('rc', '...').replace('b', '...') sympy_major_version = int(sympy_version.split(".")[0]) sympy_minor_version = int(sympy_version.split(".")[1]) if sympy_major_version < 1 or (sympy_major_version == 1 and sympy_minor_version < 3): print('Warning: SymPy version', sympy_version, 'does not support CSE postprocessing.') CSE_results = sp.cse(sympyexpr, sp.numbered_symbols(outCparams.CSE_varprefix + '_'), \ order=outCparams.CSE_sorting) else: CSE_results = cse_postprocess(sp.cse(sympyexpr, sp.numbered_symbols(outCparams.CSE_varprefix + '_'), \ order=outCparams.CSE_sorting)) for commonsubexpression in CSE_results[0]: FULLTYPESTRING = "const " + TYPE + " " if outCparams.enable_TYPE == "False": FULLTYPESTRING = "" if outCparams.SIMD_enable == "True": outstring += outCparams.preindent + indent + FULLTYPESTRING + str(commonsubexpression[0]) + " = " + \ str(expr_convert_to_SIMD_intrins(commonsubexpression[1],map_sym_to_rat,varprefix,outCparams.SIMD_find_more_FMAsFMSs)) + ";\n" else: outstring += outCparams.preindent + indent + FULLTYPESTRING + ccode_postproc( sp.ccode(commonsubexpression[1], commonsubexpression[0], user_functions=custom_functions_for_SymPy_ccode) ) + "\n" for i, result in enumerate(CSE_results[1]): if outCparams.SIMD_enable == "True": outstring += outtypestring + output_varname_str[i] + " = " + \ str(expr_convert_to_SIMD_intrins(result,map_sym_to_rat,varprefix,outCparams.SIMD_find_more_FMAsFMSs)) + ";\n" else: outstring += outtypestring + ccode_postproc( sp.ccode(result, output_varname_str[i], user_functions=custom_functions_for_SymPy_ccode) ) + "\n" # Complication: SIMD functions require numerical constants to be stored in SIMD arrays # Resolution: This function extends lists "SIMD_const_varnms" and "SIMD_const_values", # which store the name of each constant SIMD array (e.g., _Integer_1) and # the value of each variable (e.g., 1.0). if outCparams.SIMD_enable == "True": for v in map_sym_to_rat: p, q = float(map_sym_to_rat[v].p), float(map_sym_to_rat[v].q) SIMD_const_varnms.extend([str(v)]) if q != 1: SIMD_const_values.extend([str(p) + '/' + str(q)]) else: SIMD_const_values.extend([str(p)]) # Step 6b.i: If SIMD_enable == True , and # there is at least one SIMD const variable, # then declare the SIMD_const_varnms and SIMD_const_values arrays if outCparams.SIMD_enable == "True" and len(SIMD_const_varnms) != 0: # Step 6a) Sort the list of definitions. Idea from: # https://stackoverflow.com/questions/9764298/is-it-possible-to-sort-two-listswhich-reference-each-other-in-the-exact-same-w SIMD_const_varnms, SIMD_const_values = \ (list(t) for t in zip(*sorted(zip(SIMD_const_varnms, SIMD_const_values)))) # Step 6b) Remove duplicates uniq_varnms = superfast_uniq(SIMD_const_varnms) uniq_values = superfast_uniq(SIMD_const_values) SIMD_const_varnms = uniq_varnms SIMD_const_values = uniq_values if len(SIMD_const_varnms) != len(SIMD_const_values): print( "Error: SIMD constant declaration arrays SIMD_const_varnms[] and SIMD_const_values[] have inconsistent sizes!" ) sys.exit(1) for i in range(len(SIMD_const_varnms)): if outCparams.enable_TYPE == "False": SIMD_RATIONAL_decls += outCparams.preindent + indent + SIMD_const_varnms[ i] + " = " + SIMD_const_values[i] + ";" else: SIMD_RATIONAL_decls += outCparams.preindent + indent + "const double " + "tmp" + SIMD_const_varnms[ i] + " = " + SIMD_const_values[i] + ";\n" SIMD_RATIONAL_decls += outCparams.preindent + indent + "const REAL_SIMD_ARRAY " + SIMD_const_varnms[ i] + " = ConstSIMD(" + "tmp" + SIMD_const_varnms[ i] + ");\n" SIMD_RATIONAL_decls += "\n" # Step 7: Construct final output string final_Ccode_output_str = commentblock # Step 7a: Output C code in indented curly brackets if # outCparams.includebraces = True if outCparams.includebraces == "True": final_Ccode_output_str += outCparams.preindent + "{\n" final_Ccode_output_str += prestring + RATIONAL_decls + SIMD_RATIONAL_decls + outstring + poststring if outCparams.includebraces == "True": final_Ccode_output_str += outCparams.preindent + "}\n" # Step 8: If filename == "stdout", then output # C code to standard out (useful for copy-paste or interactive # mode). Otherwise output to file specified in variable name. if filename == "stdout": # Output to standard out (stdout; "the screen") print(final_Ccode_output_str) elif filename == "returnstring": return final_Ccode_output_str else: # Output to the file specified by the function input parameter string 'filename': with open(filename, outCparams.outCfileaccess) as file: file.write(final_Ccode_output_str) successstr = "" if outCparams.outCfileaccess == "a": successstr = "Appended " elif outCparams.outCfileaccess == "w": successstr = "Wrote " print(successstr + "to file \"" + filename + "\"")
def WeylScalars_Cartesian(): # We do not need the barred or hatted quantities calculated when using Cartesian coordinates. # Instead, we declare the PHYSICAL metric and extrinsic curvature as grid functions. gammaDD = ixp.register_gridfunctions_for_single_rank2("AUX","gammaDD", "sym01") kDD = ixp.register_gridfunctions_for_single_rank2("AUX","kDD", "sym01") gammaUU, detgamma = ixp.symm_matrix_inverter3x3(gammaDD) output_scalars = par.parval_from_str("output_scalars") global psi4r,psi4i,psi3r,psi3i,psi2r,psi2i,psi1r,psi1i,psi0r,psi0i # if output_scalars is "all_psis_and_invariants": # psi4r,psi4i,psi3r,psi3i,psi2r,psi2i,psi1r,psi1i,psi0r,psi0i = sp.symbols("psi4r psi4i\ # psi3r psi3i\ # psi2r psi2i\ # psi1r psi1i\ # psi0r psi0i") # elif output_scalars is "all_psis": psi4r,psi4i,psi3r,psi3i,psi2r,psi2i,psi1r,psi1i,psi0r,psi0i = gri.register_gridfunctions("AUX",["psi4r","psi4i",\ "psi3r","psi3i",\ "psi2r","psi2i",\ "psi1r","psi1i",\ "psi0r","psi0i"]) # Step 2a: Set spatial dimension (must be 3 for BSSN) DIM = 3 par.set_parval_from_str("grid::DIM",DIM) # Step 2b: Set the coordinate system to Cartesian x,y,z = gri.register_gridfunctions("AUX",["x","y","z"]) # Step 2c: Set which tetrad is used; at the moment, only one supported option if par.parval_from_str("WeylScal4NRPy.WeylScalars_Cartesian::TetradChoice") == "Approx_QuasiKinnersley": # Step 3a: Choose 3 orthogonal vectors. Here, we choose one in the azimuthal # direction, one in the radial direction, and the cross product of the two. # Eqs 5.6, 5.7 in https://arxiv.org/pdf/gr-qc/0104063.pdf: # v_1^a &= [-y,x,0] \\ # v_2^a &= [x,y,z] \\ # v_3^a &= {\rm det}(g)^{1/2} g^{ad} \epsilon_{dbc} v_1^b v_2^c, v1U = ixp.zerorank1() v2U = ixp.zerorank1() v3U = ixp.zerorank1() v1U[0] = -y v1U[1] = x v1U[2] = sp.sympify(0) v2U[0] = x v2U[1] = y v2U[2] = z LeviCivitaSymbol_rank3 = define_LeviCivitaSymbol_rank3() for a in range(DIM): for b in range(DIM): for c in range(DIM): for d in range(DIM): v3U[a] += sp.sqrt(detgamma) * gammaUU[a][d] * LeviCivitaSymbol_rank3[d][b][c] * v1U[b] *v2U[c] # Step 3b: Gram-Schmidt orthonormalization of the vectors. # The w_i^a vectors here are used to temporarily hold values on the way to the final vectors e_i^a # e_1^a &= \frac{v_1^a}{\omega_{11}} \\ # e_2^a &= \frac{v_2^a - \omega_{12} e_1^a}{\omega_{22}} \\ # e_3^a &= \frac{v_3^a - \omega_{13} e_1^a - \omega_{23} e_2^a}{\omega_{33}}, \\ # Normalize the first vector w1U = ixp.zerorank1() for a in range(DIM): w1U[a] = v1U[a] omega11 = sp.sympify(0) for a in range(DIM): for b in range(DIM): omega11 += w1U[a] * w1U[b] * gammaDD[a][b] e1U = ixp.zerorank1() for a in range(DIM): e1U[a] = w1U[a] / sp.sqrt(omega11) # Subtract off the portion of the first vector along the second, then normalize omega12 = sp.sympify(0) for a in range(DIM): for b in range(DIM): omega12 += e1U[a] * v2U[b] * gammaDD[a][b] w2U = ixp.zerorank1() for a in range(DIM): w2U[a] = v2U[a] - omega12*e1U[a] omega22 = sp.sympify(0) for a in range(DIM): for b in range(DIM): omega22 += w2U[a] * w2U[b] *gammaDD[a][b] e2U = ixp.zerorank1() for a in range(DIM): e2U[a] = w2U[a] / sp.sqrt(omega22) # Subtract off the portion of the first and second vectors along the third, then normalize omega13 = sp.sympify(0) for a in range(DIM): for b in range(DIM): omega13 += e1U[a] * v3U[b] * gammaDD[a][b] omega23 = sp.sympify(0) for a in range(DIM): for b in range(DIM): omega23 += e2U[a] * v3U[b] * gammaDD[a][b] w3U = ixp.zerorank1() for a in range(DIM): w3U[a] = v3U[a] - omega13*e1U[a] - omega23*e2U[a] omega33 = sp.sympify(0) for a in range(DIM): for b in range(DIM): omega33 += w3U[a] * w3U[b] * gammaDD[a][b] e3U = ixp.zerorank1() for a in range(DIM): e3U[a] = w3U[a] / sp.sqrt(omega33) # Step 3c: Construct the tetrad itself. # Eqs. 5.6: # l^a &= \frac{1}{\sqrt{2}} e_2^a \\ # n^a &= -\frac{1}{\sqrt{2}} e_2^a \\ # m^a &= \frac{1}{\sqrt{2}} (e_3^a + i e_1^a) \\ # \overset{*}{m}{}^a &= \frac{1}{\sqrt{2}} (e_3^a - i e_1^a) isqrt2 = 1/sp.sqrt(2) ltetU = ixp.zerorank1() ntetU = ixp.zerorank1() #mtetU = ixp.zerorank1() #mtetccU = ixp.zerorank1() remtetU = ixp.zerorank1() # SymPy does not like trying to take the real/imaginary parts of such a immtetU = ixp.zerorank1() # complicated expression as the Weyl scalars, so we will do it ourselves. for i in range(DIM): ltetU[i] = isqrt2 * e2U[i] ntetU[i] = -isqrt2 * e2U[i] remtetU[i] = isqrt2 * e3U[i] immtetU[i] = isqrt2 * e1U[i] nn = isqrt2 else: print("Error: TetradChoice == "+par.parval_from_str("TetradChoice")+" unsupported!") exit(1) gammaDD_dD = ixp.declarerank3("gammaDD_dD","sym01") # Define the Christoffel symbols GammaUDD = ixp.zerorank3(DIM) for i in range(DIM): for k in range(DIM): for l in range(DIM): for m in range(DIM): GammaUDD[i][k][l] += (sp.Rational(1,2))*gammaUU[i][m]*\ (gammaDD_dD[m][k][l] + gammaDD_dD[m][l][k] - gammaDD_dD[k][l][m]) # Step 4b: Declare and construct the Riemann curvature tensor: # R_{abcd} = \frac{1}{2} (\gamma_{ad,cb}+\gamma_{bc,da}-\gamma_{ac,bd}-\gamma_{bd,ac}) # + \gamma_{je} \Gamma^{j}_{bc}\Gamma^{e}_{ad} - \gamma_{je} \Gamma^{j}_{bd} \Gamma^{e}_{ac} gammaDD_dDD = ixp.declarerank4("gammaDD_dDD","sym01_sym23") RiemannDDDD = ixp.zerorank4() for a in range(DIM): for b in range(DIM): for c in range(DIM): for d in range(DIM): RiemannDDDD[a][b][c][d] = (gammaDD_dDD[a][d][c][b] + \ gammaDD_dDD[b][c][d][a] - \ gammaDD_dDD[a][c][b][d] - \ gammaDD_dDD[b][d][a][c]) / 2 for e in range(DIM): for j in range(DIM): RiemannDDDD[a][b][c][d] += gammaDD[j][e] * GammaUDD[j][b][c] * GammaUDD[e][a][d] - \ gammaDD[j][e] * GammaUDD[j][b][d] * GammaUDD[e][a][c] # Step 4c: We also need the extrinsic curvature tensor $K_{ij}$. # In Cartesian coordinates, we already made the components gridfunctions. # We will, however, need to calculate the trace of K seperately: trK = sp.sympify(0) for i in range(DIM): for j in range(DIM): trK += gammaUU[i][j] * kDD[i][j] # Step 5: Build the formula for \psi_4. # Gauss equation: involving the Riemann tensor and extrinsic curvature. # GaussDDDD[i][j][k][l] =& R_{ijkl} + 2K_{i[k}K_{l]j} GaussDDDD = ixp.zerorank4() for i in range(DIM): for j in range(DIM): for k in range(DIM): for l in range(DIM): GaussDDDD[i][j][k][l] = RiemannDDDD[i][j][k][l] + kDD[i][k]*kDD[l][j] - kDD[i][l]*kDD[k][j] # Codazzi equation: involving partial derivatives of the extrinsic curvature. # We will first need to declare derivatives of kDD # CodazziDDD[j][k][l] =& -2 (K_{j[k,l]} + \Gamma^p_{j[k} K_{l]p}) kDD_dD = ixp.declarerank3("kDD_dD","sym01") CodazziDDD = ixp.zerorank3() for j in range(DIM): for k in range(DIM): for l in range(DIM): CodazziDDD[j][k][l] = kDD_dD[j][l][k] - kDD_dD[j][k][l] for p in range(DIM): CodazziDDD[j][k][l] += GammaUDD[p][j][l]*kDD[k][p] - GammaUDD[p][j][k]*kDD[l][p] # Another piece. While not associated with any particular equation, # this is still useful for organizational purposes. # RojoDD[j][l]} = & R_{jl} - K_{jp} K^p_l + KK_{jl} \\ # = & \gamma^{pd} R_{jpld} - K_{jp} K^p_l + KK_{jl} RojoDD = ixp.zerorank2() for j in range(DIM): for l in range(DIM): RojoDD[j][l] = trK*kDD[j][l] for p in range(DIM): for d in range(DIM): RojoDD[j][l] += gammaUU[p][d]*RiemannDDDD[j][p][l][d] - kDD[j][p]*gammaUU[p][d]*kDD[d][l] # Now we can calculate $\psi_4$ itself! We assume l^0 = n^0 = \frac{1}{\sqrt{2}} # and m^0 = \overset{*}{m}{}^0 = 0 to simplify these equations. # We calculate the Weyl scalars as defined in https://arxiv.org/abs/gr-qc/0104063 # In terms of the above-defined quantites, the psis are defined as: # \psi_4 =&\ (\text{GaussDDDD[i][j][k][l]}) n^i \overset{*}{m}{}^j n^k \overset{*}{m}{}^l \\ # &+2 (\text{CodazziDDD[j][k][l]}) n^{0} \overset{*}{m}{}^{j} n^k \overset{*}{m}{}^l \\ # &+ (\text{RojoDD[j][l]}) n^{0} \overset{*}{m}{}^{j} n^{0} \overset{*}{m}{}^{l}. # \psi_3 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i n^j \overset{*}{m}{}^k n^l \\ # &+ (\text{CodazziDDD[j][k][l]}) (l^{0} n^{j} \overset{*}{m}{}^k n^l - l^{j} n^{0} \overset{*}{m}{}^k n^l - l^k n^j\overset{*}{m}{}^l n^0) \\ # &- (\text{RojoDD[j][l]}) l^{0} n^{j} \overset{*}{m}{}^l n^0 - l^{j} n^{0} \overset{*}{m}{}^l n^0 \\ # \psi_2 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i m^j \overset{*}{m}{}^k n^l \\ # &+ (\text{CodazziDDD[j][k][l]}) (l^{0} m^{j} \overset{*}{m}{}^k n^l - l^{j} m^{0} \overset{*}{m}{}^k n^l - l^k m^l \overset{*}{m}{}^l n^0) \\ # &- (\text{RojoDD[j][l]}) l^0 m^j \overset{*}{m}{}^l n^0 \\ # \psi_1 =&\ (\text{GaussDDDD[i][j][k][l]}) n^i l^j m^k l^l \\ # &+ (\text{CodazziDDD[j][k][l]}) (n^{0} l^{j} m^k l^l - n^{j} l^{0} m^k l^l - n^k l^l m^j l^0) \\ # &- (\text{RojoDD[j][l]}) (n^{0} l^{j} m^l l^0 - n^{j} l^{0} m^l l^0) \\ # \psi_0 =&\ (\text{GaussDDDD[i][j][k][l]}) l^i m^j l^k m^l \\ # &+2 (\text{CodazziDDD[j][k][l]}) (l^0 m^j l^k m^l + l^k m^l l^0 m^j) \\ # &+ (\text{RojoDD[j][l]}) l^0 m^j l^0 m^j. \\ psi4r = sp.sympify(0) psi4i = sp.sympify(0) psi3r = sp.sympify(0) psi3i = sp.sympify(0) psi2r = sp.sympify(0) psi2i = sp.sympify(0) psi1r = sp.sympify(0) psi1i = sp.sympify(0) psi0r = sp.sympify(0) psi0i = sp.sympify(0) for l in range(DIM): for j in range(DIM): psi4r += RojoDD[j][l] * nn * nn * (remtetU[j]*remtetU[l]-immtetU[j]*immtetU[l]) psi4i += RojoDD[j][l] * nn * nn * (-remtetU[j]*immtetU[l]-immtetU[j]*remtetU[l]) psi3r +=-RojoDD[j][l] * nn * nn * (ntetU[j]-ltetU[j]) * remtetU[l] psi3i += RojoDD[j][l] * nn * nn * (ntetU[j]-ltetU[j]) * immtetU[l] psi2r +=-RojoDD[j][l] * nn * nn * (remtetU[l]*remtetU[j]+immtetU[j]*immtetU[l]) psi2i +=-RojoDD[j][l] * nn * nn * (immtetU[l]*remtetU[j]-remtetU[j]*immtetU[l]) psi1r += RojoDD[j][l] * nn * nn * (ntetU[j]*remtetU[l]-ltetU[j]*remtetU[l]) psi1i += RojoDD[j][l] * nn * nn * (ntetU[j]*immtetU[l]-ltetU[j]*immtetU[l]) psi0r += RojoDD[j][l] * nn * nn * (remtetU[j]*remtetU[l]-immtetU[j]*immtetU[l]) psi0i += RojoDD[j][l] * nn * nn * (remtetU[j]*immtetU[l]+immtetU[j]*remtetU[l]) for l in range(DIM): for j in range(DIM): for k in range(DIM): psi4r += 2 * CodazziDDD[j][k][l] * ntetU[k] * nn * (remtetU[j]*remtetU[l]-immtetU[j]*immtetU[l]) psi4i += 2 * CodazziDDD[j][k][l] * ntetU[k] * nn * (-remtetU[j]*immtetU[l]-immtetU[j]*remtetU[l]) psi3r += 1 * CodazziDDD[j][k][l] * nn * ((ntetU[j]-ltetU[j])*remtetU[k]*ntetU[l]-remtetU[j]*ltetU[k]*ntetU[l]) psi3i +=-1 * CodazziDDD[j][k][l] * nn * ((ntetU[j]-ltetU[j])*immtetU[k]*ntetU[l]-immtetU[j]*ltetU[k]*ntetU[l]) psi2r += 1 * CodazziDDD[j][k][l] * nn * (ntetU[l]*(remtetU[j]*remtetU[k]+immtetU[j]*immtetU[k])-ltetU[k]*(remtetU[j]*remtetU[l]+immtetU[j]*immtetU[l])) psi2i += 1 * CodazziDDD[j][k][l] * nn * (ntetU[l]*(immtetU[j]*remtetU[k]-remtetU[j]*immtetU[k])-ltetU[k]*(remtetU[j]*immtetU[l]-immtetU[j]*remtetU[l])) psi1r += 1 * CodazziDDD[j][k][l] * nn * (ltetU[j]*remtetU[k]*ltetU[l]-remtetU[j]*ntetU[k]*ltetU[l]-ntetU[j]*remtetU[k]*ltetU[l]) psi1i += 1 * CodazziDDD[j][k][l] * nn * (ltetU[j]*immtetU[k]*ltetU[l]-immtetU[j]*ntetU[k]*ltetU[l]-ntetU[j]*immtetU[k]*ltetU[l]) psi0r += 2 * CodazziDDD[j][k][l] * nn * ltetU[k]*(remtetU[j]*remtetU[l]-immtetU[j]*immtetU[l]) psi0i += 2 * CodazziDDD[j][k][l] * nn * ltetU[k]*(remtetU[j]*immtetU[l]+immtetU[j]*remtetU[l]) for l in range(DIM): for j in range(DIM): for k in range(DIM): for i in range(DIM): psi4r += GaussDDDD[i][j][k][l] * ntetU[i] * ntetU[k] * (remtetU[j]*remtetU[l]-immtetU[j]*immtetU[l]) psi4i += GaussDDDD[i][j][k][l] * ntetU[i] * ntetU[k] * (-remtetU[j]*immtetU[l]-immtetU[j]*remtetU[l]) psi3r += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[j] * remtetU[k] * ntetU[l] psi3i +=-GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[j] * immtetU[k] * ntetU[l] psi2r += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[l] * (remtetU[j]*remtetU[k]+immtetU[j]*immtetU[k]) psi2i += GaussDDDD[i][j][k][l] * ltetU[i] * ntetU[l] * (immtetU[j]*remtetU[k]-remtetU[j]*immtetU[k]) psi1r += GaussDDDD[i][j][k][l] * ntetU[i] * ltetU[j] * remtetU[k] * ltetU[l] psi1i += GaussDDDD[i][j][k][l] * ntetU[i] * ltetU[j] * immtetU[k] * ltetU[l] psi0r += GaussDDDD[i][j][k][l] * ltetU[i] * ltetU[k] * (remtetU[j]*remtetU[l]-immtetU[j]*immtetU[l]) psi0i += GaussDDDD[i][j][k][l] * ltetU[i] * ltetU[k] * (remtetU[j]*immtetU[l]+immtetU[j]*remtetU[l])
def GiRaFFE_NRPy_C2P(StildeD, BU, gammaDD, gammaUU, gammadet, betaU, alpha): sqrtgammadet = sp.sqrt(gammadet) BtildeU = ixp.zerorank1() for i in range(3): # \tilde{B}^i = B^i \sqrt{\gamma} BtildeU[i] = sqrtgammadet * BU[i] BtildeD = ixp.zerorank1() for i in range(3): for j in range(3): BtildeD[j] += gammaDD[i][j] * BtildeU[i] Btilde2 = sp.sympify(0) for i in range(3): Btilde2 += BtildeU[i] * BtildeD[i] global outStildeD outStildeD = StildeD # Then, enforce the orthogonality: if par.parval_from_str("enforce_orthogonality_StildeD_BtildeU"): for i in range(3): for j in range(3): # {\tilde S}_i = {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2 outStildeD[i] -= (StildeD[j] * BtildeU[j]) * BtildeD[i] / Btilde2 # Calculate \tilde{S}^2: Stilde2 = sp.sympify(0) for i in range(3): for j in range(3): Stilde2 += gammaUU[i][j] * outStildeD[i] * outStildeD[j] # First we need to compute the factor f: # f = \sqrt{(1-\Gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)} speed_limit_factor = sp.sqrt((1-GAMMA_SPEED_LIMIT**(-2))\ *Btilde2*Btilde2/(16*M_PI*M_PI*sqrtgammadet*Stilde2*Stilde2)) def min_noif(a, b): # This returns the minimum of a and b # If a>b, then we get 0.5*(a+b-a+b) = b # If b>a, then we get 0.5*(a+b+a-b) = a return sp.Rational(1, 2) * (a + b - nrpyAbs(a - b)) # Calculate B^2 B2 = sp.sympify(0) for i in range(3): for j in range(3): B2 += gammaDD[i][j] * BU[i] * BU[j] # Enforce the speed limit on StildeD: if par.parval_from_str("enforce_speed_limit_StildeD"): for i in range(3): outStildeD[i] *= min_noif(1, speed_limit_factor) global ValenciavU ValenciavU = ixp.zerorank1() if par.parval_from_str( "enforce_orthogonality_StildeD_BtildeU") or par.parval_from_str( "enforce_speed_limit_StildeD"): # Recompute 3-velocity: for i in range(3): for j in range(3): # \bar{v}^i = 4 \pi \gamma^{ij} {\tilde S}_j / (\sqrt{\gamma} B^2) ValenciavU[i] = sp.sympify( 4.0) * M_PI * gammaUU[i][j] * StildeD[j] / (sqrtgammadet * B2) # We will use once more the trick from above with min and max without if. However, we we'll need a function # that returns either 0 or 1, so as to choose between two otherwise mathetmatically unrelated branches. def max_normal0(a): # If a>0, return 1. Otherwise, return 0. This defines a 'less than' branch. # WILL BREAK if a = 0. return (a + nrpyAbs(a)) / (2 * a) def min_normal0(a): # If a>0, return 1. Otherwise, return 0. This defines a 'greater than' branch. # WILL BREAK if a = 0. return (a - nrpyAbs(a)) / (2 * a) # This number determines how far away (in grid points) we will apply the fix. grid_points_from_z_plane = par.Cparameters("REAL", thismodule, "grid_points_from_z_plane", 4.0) # Set the Cartesian normal vector. This can be expanded later to arbitrary sheets and coordinate systems. nU = ixp.zerorank1() nU[2] = 1 # Lower the index, as usual: nD = ixp.zerorank1() for i in range(3): for j in range(3): nD[i] = gammaDD[i][j] * nU[j] if par.parval_from_str("enforce_current_sheet_prescription"): # Calculate the drift velocity driftvU = ixp.declarerank1("driftvU") inner_product = sp.sympify(0) for i in range(3): inner_product += driftvU[i] * nD[ i] # This is the portion of the drift velocity normal to the z plane # In flat space, this is just v^z # We'll use a sympy utility to solve for v^z. This should make it easier to generalize later newdriftvU2 = sp.solve(inner_product, driftvU[2]) newdriftvU2 = newdriftvU2[0] # In flat space this reduces to v^z=0 for i in range(3): # Now, we substitute drift velocity in terms of our preferred Valencia velocity newdriftvU2 = newdriftvU2.subs(driftvU[i], alpha * ValenciavU[i] - betaU[i]) # Now that we have the z component, it's time to substitute its Valencia form in. # Remember, we only do this if abs(z) < (k+0.01)*dz. Note that we add 0.01; this helps # avoid floating point errors and division by zero. This is the same as abs(z) - (k+0.01)*dz<0 boundary = nrpyAbs(rfm.xx[2]) - (grid_points_from_z_plane + sp.sympify(0.01)) * gri.dxx[2] ValenciavU[2] = max_normal0(boundary)*(newdriftvU2+betaU[2])/alpha \ + min_normal0(boundary)*ValenciavU[2]
def ADM_in_terms_of_BSSN(): global gammaDD, gammaDDdD, gammaDDdDD, gammaUU, detgamma, GammaUDD, KDD, KDDdD # Step 1.c: Given the chosen coordinate system, set up # corresponding reference metric and needed # reference metric quantities # The following function call sets up the reference metric # and related quantities, including rescaling matrices ReDD, # ReU, and hatted quantities. rfm.reference_metric() # Step 1.d: Set spatial dimension (must be 3 for BSSN, as BSSN is # a 3+1-dimensional decomposition of the general # relativistic field equations) DIM = 3 # Step 1.e: Import all basic (unrescaled) BSSN scalars & tensors import BSSN.BSSN_quantities as Bq Bq.BSSN_basic_tensors() gammabarDD = Bq.gammabarDD cf = Bq.cf AbarDD = Bq.AbarDD trK = Bq.trK Bq.gammabar__inverse_and_derivs() gammabarDD_dD = Bq.gammabarDD_dD gammabarDD_dDD = Bq.gammabarDD_dDD Bq.AbarUU_AbarUD_trAbar_AbarDD_dD() AbarDD_dD = Bq.AbarDD_dD # Step 2: The ADM three-metric gammaDD and its # derivatives in terms of BSSN quantities. gammaDD = ixp.zerorank2() exp4phi = sp.sympify(0) if par.parval_from_str("EvolvedConformalFactor_cf") == "phi": exp4phi = sp.exp(4 * cf) elif par.parval_from_str("EvolvedConformalFactor_cf") == "chi": exp4phi = (1 / cf) elif par.parval_from_str("EvolvedConformalFactor_cf") == "W": exp4phi = (1 / cf**2) else: print("Error EvolvedConformalFactor_cf type = \"" + par.parval_from_str("EvolvedConformalFactor_cf") + "\" unknown.") sys.exit(1) for i in range(DIM): for j in range(DIM): gammaDD[i][j] = exp4phi * gammabarDD[i][j] # Step 2.a: Derivatives of $e^{4\phi}$ phidD = ixp.zerorank1() phidDD = ixp.zerorank2() cf_dD = ixp.declarerank1("cf_dD") cf_dDD = ixp.declarerank2("cf_dDD", "sym01") if par.parval_from_str("EvolvedConformalFactor_cf") == "phi": for i in range(DIM): phidD[i] = cf_dD[i] for j in range(DIM): phidDD[i][j] = cf_dDD[i][j] elif par.parval_from_str("EvolvedConformalFactor_cf") == "chi": for i in range(DIM): phidD[i] = -sp.Rational(1, 4) * exp4phi * cf_dD[i] for j in range(DIM): phidDD[i][j] = sp.Rational(1, 4) * ( exp4phi**2 * cf_dD[i] * cf_dD[j] - exp4phi * cf_dDD[i][j]) elif par.parval_from_str("EvolvedConformalFactor_cf") == "W": exp2phi = (1 / cf) for i in range(DIM): phidD[i] = -sp.Rational(1, 2) * exp2phi * cf_dD[i] for j in range(DIM): phidDD[i][j] = sp.Rational(1, 2) * ( exp4phi * cf_dD[i] * cf_dD[j] - exp2phi * cf_dDD[i][j]) else: print("Error EvolvedConformalFactor_cf type = \"" + par.parval_from_str("EvolvedConformalFactor_cf") + "\" unknown.") sys.exit(1) exp4phidD = ixp.zerorank1() exp4phidDD = ixp.zerorank2() for i in range(DIM): exp4phidD[i] = 4 * exp4phi * phidD[i] for j in range(DIM): exp4phidDD[i][j] = 16 * exp4phi * phidD[i] * phidD[ j] + 4 * exp4phi * phidDD[i][j] # Step 2.b: Derivatives of gammaDD, the ADM three-metric gammaDDdD = ixp.zerorank3() gammaDDdDD = ixp.zerorank4() for i in range(DIM): for j in range(DIM): for k in range(DIM): gammaDDdD[i][j][k] = exp4phidD[k] * gammabarDD[i][ j] + exp4phi * gammabarDD_dD[i][j][k] for l in range(DIM): gammaDDdDD[i][j][k][l] = exp4phidDD[k][l] * gammabarDD[i][j] + \ exp4phidD[k] * gammabarDD_dD[i][j][l] + \ exp4phidD[l] * gammabarDD_dD[i][j][k] + \ exp4phi * gammabarDD_dDD[i][j][k][l] # Step 2.c: 3-Christoffel symbols associated with ADM 3-metric gammaDD # Step 2.c.i: First compute the inverse 3-metric gammaUU: gammaUU, detgamma = ixp.symm_matrix_inverter3x3(gammaDD) GammaUDD = ixp.zerorank3() for i in range(DIM): for j in range(DIM): for k in range(DIM): for l in range(DIM): GammaUDD[i][j][k] += sp.Rational(1,2)*gammaUU[i][l]* \ (gammaDDdD[l][j][k] + gammaDDdD[l][k][j] - gammaDDdD[j][k][l]) # Step 3: Define ADM extrinsic curvature KDD and # its first spatial derivatives KDDdD # in terms of BSSN quantities KDD = ixp.zerorank2() for i in range(DIM): for j in range(DIM): KDD[i][j] = exp4phi * AbarDD[i][j] + sp.Rational( 1, 3) * gammaDD[i][j] * trK KDDdD = ixp.zerorank3() trK_dD = ixp.declarerank1("trK_dD") for i in range(DIM): for j in range(DIM): for k in range(DIM): KDDdD[i][j][k] = exp4phidD[k] * AbarDD[i][j] + exp4phi * AbarDD_dD[i][j][k] + \ sp.Rational(1, 3) * (gammaDDdD[i][j][k] * trK + gammaDD[i][j] * trK_dD[k])
def GiRaFFEfood_HO_Aligned_Rotator(): r = rfm.xxSph[0] varpi = sp.sqrt(rfm.xxCart[0]**2 + rfm.xxCart[1]**2) mu = B_p_aligned_rotator * R_NS_aligned_rotator**3 / 2 ASphD = ixp.zerorank1() ASphD[2] = mu * varpi**2 / ( r**3) # The other components were already declared to be 0. # <a id='step3'></a> # # ### Step 3: Use the Jacobian matrix to transform the vectors to Cartesian coordinates. # $$\label{step3}$$ # # \[Back to [top](#top)\] # # Now, we will use the coordinate transformation definitions provided by reference_metric.py to build the Jacobian # $$ # \frac{\partial x_{\rm Sph}^j}{\partial x_{\rm Cart}^i}, # $$ # where $x_{\rm Sph}^j \in \{r,\theta,\phi\}$ and $x_{\rm Cart}^i \in \{x,y,z\}$. We would normally compute its inverse, but since none of the quantities we need to transform have upper indices, it is not necessary. Then, since $A_i$ and has one lower index, it will need to be multiplied by the Jacobian: # # $$ # A_i^{\rm Cart} = A_j^{\rm Sph} \frac{\partial x_{\rm Sph}^j}{\partial x_{\rm Cart}^i}, # $$ # Step 3: Use the Jacobian matrix to transform the vectors to Cartesian coordinates. drrefmetric__dx_0UDmatrix = sp.Matrix([[ sp.diff(rfm.xxSph[0], rfm.xx[0]), sp.diff(rfm.xxSph[0], rfm.xx[1]), sp.diff(rfm.xxSph[0], rfm.xx[2]) ], [ sp.diff(rfm.xxSph[1], rfm.xx[0]), sp.diff(rfm.xxSph[1], rfm.xx[1]), sp.diff(rfm.xxSph[1], rfm.xx[2]) ], [ sp.diff(rfm.xxSph[2], rfm.xx[0]), sp.diff(rfm.xxSph[2], rfm.xx[1]), sp.diff(rfm.xxSph[2], rfm.xx[2]) ]]) #dx__drrefmetric_0UDmatrix = drrefmetric__dx_0UDmatrix.inv() # We don't actually need this in this case. global AD AD = ixp.register_gridfunctions_for_single_rank1("EVOL", "AD") for i in range(DIM): for j in range(DIM): AD[i] = drrefmetric__dx_0UDmatrix[(j, i)] * ASphD[j] # <a id='step4'></a> # # ### Step 4: Calculate $v^i$ # $$\label{step4}$$ # # \[Back to [top](#top)\] # # Here, we will calculate the drift velocity $v^i = \Omega \textbf{e}_z \times \textbf{r} = [ijk] \Omega \textbf{e}^j_z x^k$, where $[ijk]$ is the Levi-Civita permutation symbol and $\textbf{e}^i_z = (0,0,1)$. Conveniently, in flat space, the drift velocity reduces to the Valencia velocity because $\alpha = 1$ and $\beta^i = 0$. # Step 4: Calculate v^i # Here, we build the Levi-Civita tensor from the Levi-Civita symbol. import WeylScal4NRPy.WeylScalars_Cartesian as weyl LeviCivitaSymbolDDD = weyl.define_LeviCivitaSymbol_rank3() Omega_aligned_rotator = par.Cparameters( "REAL", thismodule, "Omega_aligned_rotator") # The angular velocity of the NS unit_zU = ixp.zerorank1() unit_zU[2] = 1 global ValenciavU ValenciavU = ixp.zerorank1() for i in range(DIM): for j in range(DIM): for k in range(DIM): ValenciavU[i] += LeviCivitaSymbolDDD[i][j][ k] * Omega_aligned_rotator * unit_zU[j] * rfm.xx[k]
nrpy_dir_path = os.path.join("..") if nrpy_dir_path not in sys.path: sys.path.append(nrpy_dir_path) from outputC import outCfunction, lhrh, add_to_Cfunction_dict, outC_function_outdir_dict, outC_function_dict, outC_function_prototype_dict, outC_function_master_list, outC_function_element # NRPy+: Core C code output module import finite_difference as fin # NRPy+: Finite difference C code generation module import NRPy_param_funcs as par # NRPy+: Parameter interface import grid as gri # NRPy+: Functions having to do with numerical grids import loop as lp # NRPy+: Generate C code loops import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support import reference_metric as rfm # NRPy+: Reference metric support import cmdline_helper as cmd # NRPy+: Multi-platform Python command-line interface thismodule = __name__ par.set_parval_from_str("finite_difference::FD_CENTDERIVS_ORDER",2) out_dir = os.path.join("GiRaFFE_standalone_Ccodes") cmd.mkdir(out_dir) CoordSystem = "Cartesian" par.set_parval_from_str("reference_metric::CoordSystem",CoordSystem) rfm.reference_metric() # Create ReU, ReDD needed for rescaling B-L initial data, generating BSSN RHSs, etc. outCparams = "outCverbose=False,CSE_sorting=none" xi_damping = par.Cparameters("REAL",thismodule,"xi_damping",0.1) # Default Kreiss-Oliger dissipation strength default_KO_strength = 0.1
# * Desired coordinate system # * Desired initial lapse $\alpha$ and shift $\beta^i$. We will choose our gauge conditions as $\alpha=1$ and $\beta^i=B^i=0$. $\alpha = \psi^{-2}$ will yield much better behavior, but the conformal factor $\psi$ depends on the desired *destination* coordinate system (which may not be spherical coordinates). # Step P0: Load needed modules import sympy as sp # SymPy: The Python computer algebra package upon which NRPy+ depends import NRPy_param_funcs as par # NRPy+: Parameter interface import indexedexp as ixp # NRPy+: Symbolic indexed expression (e.g., tensors, vectors, etc.) support import sys # Standard Python module for multiplatform OS-level functions import BSSN.ADM_Exact_Spherical_or_Cartesian_to_BSSNCurvilinear as AtoB thismodule = __name__ # The UIUC initial data represent a Kerr black hole with mass M # and dimensionless spin chi in UIUC quasi-isotropic coordinates, # see https://arxiv.org/abs/1001.4077 # Input parameters: M, chi = par.Cparameters("REAL", thismodule, ["M", "chi"], [1.0, 0.99]) # ComputeADMGlobalsOnly == True will only set up the ADM global quantities. # == False will perform the full ADM SphorCart->BSSN Curvi conversion def UIUCBlackHole(ComputeADMGlobalsOnly=False): global Sph_r_th_ph, r, th, ph, gammaSphDD, KSphDD, alphaSph, betaSphU, BSphU # All gridfunctions will be written in terms of spherical coordinates (r, th, ph): r, th, ph = sp.symbols('r th ph', real=True) # Step 0: Set spatial dimension (must be 3 for BSSN) DIM = 3 par.set_parval_from_str("grid::DIM", DIM) # Step 1: Set psi, the conformal factor:
def dfdr_function(fd_order): # function to write c code to calculate dfdr term in Sommerfeld boundary condition # Read what # of dimensions being usded DIM = par.parval_from_str("grid::DIM") # Set up the chosen reference metric from chosen coordinate system, set within NRPy+ CoordSystem = par.parval_from_str("reference_metric::CoordSystem") rfm.reference_metric() # Simplifying the results make them easier to interpret. do_simplify = True if "Sinh" in CoordSystem: # Simplification takes too long on Sinh* coordinate systems do_simplify = False # Construct Jacobian matrix, output Jac_dUSph_dDrfmUD[i][j] = \partial x_{Sph}^i / \partial x^j: Jac_dUSph_dDrfmUD = ixp.zerorank2() for i in range(3): for j in range(3): Jac_dUSph_dDrfmUD[i][j] = sp.diff(rfm.xxSph[i],rfm.xx[j]) # Invert Jacobian matrix, output to Jac_dUrfm_dDSphUD. Jac_dUrfm_dDSphUD, dummyDET = ixp.generic_matrix_inverter3x3(Jac_dUSph_dDrfmUD) # Jac_dUrfm_dDSphUD[i][0] stores \partial x^i / \partial r if do_simplify: for i in range(3): Jac_dUrfm_dDSphUD[i][0] = sp.simplify(Jac_dUrfm_dDSphUD[i][0]) # Declare \partial_i f, which is actually computed later on fdD = ixp.declarerank1("fdD") # = [fdD0, fdD1, fdD2] contraction = sp.sympify(0) for i in range(3): contraction += fdD[i]*Jac_dUrfm_dDSphUD[i][0] if do_simplify: contraction = sp.simplify(contraction) r_str_and_contraction_str = outputC([rfm.xxSph[0],contraction], ["*_r","*_partial_i_f"],filename="returnstring",params="includebraces=False") def gen_central_2oFD_stencil_str(intdirn): if intdirn == 0: return "(gfs[IDX4S(which_gf,i0+1,i1,i2)]-gfs[IDX4S(which_gf,i0-1,i1,i2)])*0.5" # Does not include the 1/dx multiplication if intdirn == 1: return "(gfs[IDX4S(which_gf,i0,i1+1,i2)]-gfs[IDX4S(which_gf,i0,i1-1,i2)])*0.5" # Does not include the 1/dy multiplication return "(gfs[IDX4S(which_gf,i0,i1,i2+1)]-gfs[IDX4S(which_gf,i0,i1,i2-1)])*0.5" # Does not include the 1/dz multiplication def gen_central_4oFD_stencil_str(intdirn): if intdirn == 0: return """(-c2*gfs[IDX4S(which_gf,i0+2,i1,i2)] +c1*gfs[IDX4S(which_gf,i0+1,i1,i2)] -c1*gfs[IDX4S(which_gf,i0-1,i1,i2)] +c2*gfs[IDX4S(which_gf,i0-2,i1,i2)])""" # Does not include the 1/dx multiplication if intdirn == 1: return """(-c2*gfs[IDX4S(which_gf,i0,i1+2,i2)] +c1*gfs[IDX4S(which_gf,i0,i1+1,i2)] -c1*gfs[IDX4S(which_gf,i0,i1-1,i2)] +c2*gfs[IDX4S(which_gf,i0,i1-2,i2)])""" # Does not include the 1/dy multiplication return """(-c2*gfs[IDX4S(which_gf,i0,i1,i2+2)] +c1*gfs[IDX4S(which_gf,i0,i1,i2+1)] -c1*gfs[IDX4S(which_gf,i0,i1,i2-1)] +c2*gfs[IDX4S(which_gf,i0,i1,i2-2)])""" # Does not include the 1/dz multiplication def gen_central_6oFD_stencil_str(intdirn): if intdirn == 0: return """( c3*gfs[IDX4S(which_gf,i0+3,i1,i2)] -c2*gfs[IDX4S(which_gf,i0+2,i1,i2)] +c1*gfs[IDX4S(which_gf,i0+1,i1,i2)] -c1*gfs[IDX4S(which_gf,i0-1,i1,i2)] +c2*gfs[IDX4S(which_gf,i0-2,i1,i2)] -c3*gfs[IDX4S(which_gf,i0-3,i1,i2)])""" # Does not include the 1/dx multiplication if intdirn == 1: return """( c3*gfs[IDX4S(which_gf,i0,i1+3,i2)] -c2*gfs[IDX4S(which_gf,i0,i1+2,i2)] +c1*gfs[IDX4S(which_gf,i0,i1+1,i2)] -c1*gfs[IDX4S(which_gf,i0,i1-1,i2)] +c2*gfs[IDX4S(which_gf,i0,i1-2,i2)] -c3*gfs[IDX4S(which_gf,i0,i1-3,i2)])""" # Does not include the 1/dy multiplication return """( c3*gfs[IDX4S(which_gf,i0,i1,i2+3)] -c2*gfs[IDX4S(which_gf,i0,i1,i2+2)] +c1*gfs[IDX4S(which_gf,i0,i1,i2+1)] -c1*gfs[IDX4S(which_gf,i0,i1,i2-1)] +c2*gfs[IDX4S(which_gf,i0,i1,i2-2)] -c3*gfs[IDX4S(which_gf,i0,i1,i2-3)])""" # Does not include the 1/dz multiplication def gen_central_fd_stencil_str(intdirn, fd_order): if fd_order==2: return gen_central_2oFD_stencil_str(intdirn) if fd_order==4: return gen_central_4oFD_stencil_str(intdirn) return gen_central_6oFD_stencil_str(intdirn) def output_dfdx(intdirn, fd_order): dirn = str(intdirn) dirnp1 = str((intdirn+1)%3) # if dirn='0', then we want this to be '1'; '1' then '2'; and '2' then '0' dirnp2 = str((intdirn+2)%3) # if dirn='0', then we want this to be '2'; '1' then '0'; and '2' then '1' preface = """ // On a +x"""+dirn+""" or -x"""+dirn+""" face, do up/down winding as appropriate: if(abs(FACEXi["""+dirn+"""])==1 || i"""+dirn+"""+NGHOSTS >= Nxx_plus_2NGHOSTS"""+dirn+""" || i"""+dirn+"""-NGHOSTS <= 0) { int8_t SHIFTSTENCIL"""+dirn+""" = FACEXi["""+dirn+"""]; if(i"""+dirn+"""+NGHOSTS >= Nxx_plus_2NGHOSTS"""+dirn+""") SHIFTSTENCIL"""+dirn+""" = -1; if(i"""+dirn+"""-NGHOSTS <= 0) SHIFTSTENCIL"""+dirn+""" = +1; SHIFTSTENCIL"""+dirnp1+""" = 0; SHIFTSTENCIL"""+dirnp2+""" = 0; """ if fd_order == 2: return preface + """ fdD"""+dirn+""" = SHIFTSTENCIL"""+dirn+"""*(-1.5*gfs[IDX4S(which_gf,i0+0*SHIFTSTENCIL0,i1+0*SHIFTSTENCIL1,i2+0*SHIFTSTENCIL2)] +2.*gfs[IDX4S(which_gf,i0+1*SHIFTSTENCIL0,i1+1*SHIFTSTENCIL1,i2+1*SHIFTSTENCIL2)] -0.5*gfs[IDX4S(which_gf,i0+2*SHIFTSTENCIL0,i1+2*SHIFTSTENCIL1,i2+2*SHIFTSTENCIL2)] )*invdx"""+dirn+"""; // Not on a +x"""+dirn+""" or -x"""+dirn+""" face, using centered difference: } else { fdD"""+dirn+""" = """+gen_central_fd_stencil_str(intdirn, 2)+"""*invdx"""+dirn+"""; } """ if fd_order == 4: return preface + """ fdD"""+dirn+""" = SHIFTSTENCIL"""+dirn+"""*(u0*gfs[IDX4S(which_gf,i0+0*SHIFTSTENCIL0,i1+0*SHIFTSTENCIL1,i2+0*SHIFTSTENCIL2)] +u1*gfs[IDX4S(which_gf,i0+1*SHIFTSTENCIL0,i1+1*SHIFTSTENCIL1,i2+1*SHIFTSTENCIL2)] +u2*gfs[IDX4S(which_gf,i0+2*SHIFTSTENCIL0,i1+2*SHIFTSTENCIL1,i2+2*SHIFTSTENCIL2)] +u3*gfs[IDX4S(which_gf,i0+3*SHIFTSTENCIL0,i1+3*SHIFTSTENCIL1,i2+3*SHIFTSTENCIL2)] +u4*gfs[IDX4S(which_gf,i0+4*SHIFTSTENCIL0,i1+4*SHIFTSTENCIL1,i2+4*SHIFTSTENCIL2)] )*invdx"""+dirn+"""; // Not on a +x"""+dirn+""" or -x"""+dirn+""" face, using centered difference: } else { fdD"""+dirn+""" = """+gen_central_fd_stencil_str(intdirn, 4)+"""*invdx"""+dirn+"""; } """ if fd_order == 6: return preface + """ fdD"""+dirn+""" = SHIFTSTENCIL"""+dirn+"""*(u0*gfs[IDX4S(which_gf,i0+0*SHIFTSTENCIL0,i1+0*SHIFTSTENCIL1,i2+0*SHIFTSTENCIL2)] +u1*gfs[IDX4S(which_gf,i0+1*SHIFTSTENCIL0,i1+1*SHIFTSTENCIL1,i2+1*SHIFTSTENCIL2)] +u2*gfs[IDX4S(which_gf,i0+2*SHIFTSTENCIL0,i1+2*SHIFTSTENCIL1,i2+2*SHIFTSTENCIL2)] +u3*gfs[IDX4S(which_gf,i0+3*SHIFTSTENCIL0,i1+3*SHIFTSTENCIL1,i2+3*SHIFTSTENCIL2)] +u4*gfs[IDX4S(which_gf,i0+4*SHIFTSTENCIL0,i1+4*SHIFTSTENCIL1,i2+4*SHIFTSTENCIL2)] +u5*gfs[IDX4S(which_gf,i0+5*SHIFTSTENCIL0,i1+5*SHIFTSTENCIL1,i2+5*SHIFTSTENCIL2)] +u6*gfs[IDX4S(which_gf,i0+6*SHIFTSTENCIL0,i1+6*SHIFTSTENCIL1,i2+6*SHIFTSTENCIL2)] )*invdx"""+dirn+"""; // Not on a +x"""+dirn+""" or -x"""+dirn+""" face, using centered difference: } else { fdD"""+dirn+""" = """+gen_central_fd_stencil_str(intdirn, 6)+"""*invdx"""+dirn+"""; } """ print("Error: fd_order = "+str(fd_order)+" currently unsupported.") sys.exit(1) contraction_term_func = """ // Function to calculate the radial derivative of a grid function void contraction_term(const paramstruct *restrict params, const int which_gf, const REAL *restrict gfs, REAL *restrict xx[3], const int8_t FACEXi[3], const int i0, const int i1, const int i2, REAL *restrict _r, REAL *restrict _partial_i_f) { #include "RELATIVE_PATH__set_Cparameters.h" /* Header file containing correct #include for set_Cparameters.h; * accounting for the relative path */ // Initialize derivatives to crazy values, to ensure that // we will notice in case they aren't set properly. REAL fdD0=1e100; REAL fdD1=1e100; REAL fdD2=1e100; REAL xx0 = xx[0][i0]; REAL xx1 = xx[1][i1]; REAL xx2 = xx[2][i2]; int8_t SHIFTSTENCIL0; int8_t SHIFTSTENCIL1; int8_t SHIFTSTENCIL2; """ if fd_order == 4: contraction_term_func +=""" // foward/backward finite difference coefficients const REAL u0 =-25./12.; const REAL u1 = 4.; const REAL u2 = -3.; const REAL u3 = 4./3.; const REAL u4 = -1./4.; // central finite difference coefficients const REAL c1 = 2./3.; const REAL c2 = 1./12.; """ if fd_order == 6: contraction_term_func +=""" // foward/backward finite difference coefficients const REAL u0 = -49./20.; const REAL u1 = 6.; const REAL u2 = -15./2.; const REAL u3 = 20./3.; const REAL u4 = -15./4.; const REAL u5 = 6./5.; const REAL u6 = -1./6.; // central finite difference coefficients const REAL c1 = 3./4.; const REAL c2 = 3./20.; const REAL c3 = 1./60; """ for i in range(DIM): if "fdD"+str(i) in r_str_and_contraction_str: contraction_term_func += output_dfdx(i, fd_order) contraction_term_func += "\n" + r_str_and_contraction_str contraction_term_func +=""" } // END contraction_term function """ return contraction_term_func
def Set_up_CurviBoundaryConditions(Ccodesdir, verbose=True, Cparamspath=os.path.join("../"), enable_copy_of_static_Ccodes=True): # Step P0: Check that Ccodesdir is not the same as CurviBoundaryConditions/boundary_conditions, # to prevent trusted versions of these C codes from becoming contaminated. if os.path.join(Ccodesdir) == os.path.join("CurviBoundaryConditions", "boundary_conditions"): print( "Error: Tried to output boundary conditions C code into CurviBoundaryConditions/boundary_conditions," " which is not allowed, to prevent trusted versions of these C codes from becoming contaminated." ) sys.exit(1) # Step P1: Create the C codes output directory & copy static CurviBC files # from CurviBoundaryConditions/boundary_conditions to Ccodesdir/ if enable_copy_of_static_Ccodes: cmd.mkdir(os.path.join(Ccodesdir)) for file in [ "apply_bcs_curvilinear.h", "BCs_data_structs.h", "bcstruct_freemem.h", "CurviBC_include_Cfunctions.h", "driver_bcstruct.h", "set_bcstruct.h", "set_up__bc_gz_map_and_parity_condns.h" ]: shutil.copy( os.path.join("CurviBoundaryConditions", "boundary_conditions", file), os.path.join(Ccodesdir)) # Step P2: Output correct #include for set_Cparameters.h to # Ccodesdir/boundary_conditions/RELATIVE_PATH__set_Cparameters.h with open(os.path.join(Ccodesdir, "RELATIVE_PATH__set_Cparameters.h"), "w") as file: file.write( "#include \"" + Cparamspath + "/set_Cparameters.h\"\n" ) # #include's may include forward slashes for paths, even in Windows. # Step 0: Set up reference metric in case it hasn't already been set up. # (Doing it twice hurts nothing). rfm.reference_metric() # Step 1: Set unit-vector dot products (=parity) for each of the 10 parity condition types parity = ixp.zerorank1(DIM=10) UnitVectors_inner = ixp.zerorank2() xx0_inbounds, xx1_inbounds, xx2_inbounds = sp.symbols( "xx0_inbounds xx1_inbounds xx2_inbounds", real=True) for i in range(3): for j in range(3): UnitVectors_inner[i][j] = rfm.UnitVectors[i][j].subs( rfm.xx[0], xx0_inbounds).subs(rfm.xx[1], xx1_inbounds).subs(rfm.xx[2], xx2_inbounds) # Type 0: scalar parity[0] = sp.sympify(1) # Type 1: i0-direction vector or one-form # Type 2: i1-direction vector or one-form # Type 3: i2-direction vector or one-form for i in range(3): for Type in range(1, 4): parity[Type] += rfm.UnitVectors[Type - 1][i] * UnitVectors_inner[Type - 1][i] # Type 4: i0i0-direction rank-2 tensor # parity[4] = parity[1]*parity[1] # Type 5: i0i1-direction rank-2 tensor # Type 6: i0i2-direction rank-2 tensor # Type 7: i1i1-direction rank-2 tensor # Type 8: i1i2-direction rank-2 tensor # Type 9: i2i2-direction rank-2 tensor count = 4 for i in range(3): for j in range(i, 3): parity[count] = parity[i + 1] * parity[j + 1] count = count + 1 lhs_strings = [] for i in range(10): lhs_strings.append("parity[" + str(i) + "]") outputC( parity, lhs_strings, os.path.join(Ccodesdir, "parity_conditions_symbolic_dot_products.h")) # Step 2.a: Generate Ccodesdir/gridfunction_defines.h file, # containing human-readable gridfunction aliases evolved_variables_list, auxiliary_variables_list, auxevol_variables_list = gri.output__gridfunction_defines_h__return_gf_lists( Ccodesdir) # Step 2.b: set the parity conditions on all gridfunctions in gf_list, # based on how many digits are at the end of their names def set_parity_types(list_of_gf_names): parity_type = [] for name in list_of_gf_names: for gf in gri.glb_gridfcs_list: if gf.name == name: parity_type__orig_len = len(parity_type) if gf.DIM < 3 or gf.DIM > 4: print( "Error: Cannot currently specify parity conditions on gridfunctions with DIM<3 or >4." ) sys.exit(1) if gf.rank == 0: parity_type.append(0) elif gf.rank == 1: if gf.DIM == 3: parity_type.append( int(gf.name[-1]) + 1 ) # = 1 for e.g., beta^0; = 2 for e.g., beta^1, etc. elif gf.DIM == 4: parity_type.append( int(gf.name[-1]) ) # = 0 for e.g., b4^0; = 1 for e.g., beta^1, etc. elif gf.rank == 2: if gf.DIM == 3: # element of a list; a[-2] the # second-to-last element, etc. idx0 = gf.name[-2] idx1 = gf.name[-1] if idx0 == "0" and idx1 == "0": parity_type.append(4) elif (idx0 == "0" and idx1 == "1") or (idx0 == "1" and idx1 == "0"): parity_type.append(5) elif (idx0 == "0" and idx1 == "2") or (idx0 == "2" and idx1 == "0"): parity_type.append(6) elif idx0 == "1" and idx1 == "1": parity_type.append(7) elif (idx0 == "1" and idx1 == "2") or (idx0 == "2" and idx1 == "1"): parity_type.append(8) elif idx0 == "2" and idx1 == "2": parity_type.append(9) elif gf.DIM == 4: idx0 = gf.name[-2] idx1 = gf.name[-1] # g4DD00 = g_{tt} : parity type = 0 # g4DD01 = g_{tx} : parity type = 1 # g4DD02 = g_{ty} : parity type = 2 # g4DD0a = g_{ta} : parity type = a if idx0 == "0": parity_type.append(int(idx1)) elif idx1 == "0": parity_type.append(int(idx0)) if idx0 == "1" and idx1 == "1": parity_type.append(4) elif (idx0 == "1" and idx1 == "2") or (idx0 == "2" and idx1 == "1"): parity_type.append(5) elif (idx0 == "1" and idx1 == "3") or (idx0 == "3" and idx1 == "1"): parity_type.append(6) elif idx0 == "2" and idx1 == "2": parity_type.append(7) elif (idx0 == "2" and idx1 == "3") or (idx0 == "3" and idx1 == "2"): parity_type.append(8) elif idx0 == "3" and idx1 == "3": parity_type.append(9) if len(parity_type) == parity_type__orig_len: print( "Error: Could not figure out parity type for " + gf.gftype + " gridfunction: " + gf.name, gf.DIM, gf.name[-2], gf.name[-1], gf.rank) sys.exit(1) if len(parity_type) != len(list_of_gf_names): print( "Error: For some reason the length of the parity types list did not match the length of the gf list." ) sys.exit(1) return parity_type evol_parity_type = set_parity_types(evolved_variables_list) aux_parity_type = set_parity_types(auxiliary_variables_list) auxevol_parity_type = set_parity_types(auxevol_variables_list) # Step 2.c: Output all gridfunctions to Ccodesdir+"/gridfunction_defines.h" # ... then append to the file the parity type for each gridfunction. with open(os.path.join(Ccodesdir, "gridfunction_defines.h"), "a") as file: file.write("\n\n/* PARITY TYPES FOR ALL GRIDFUNCTIONS.\n") file.write( " SEE \"Tutorial-Start_to_Finish-Curvilinear_BCs.ipynb\" FOR DEFINITIONS. */\n" ) if len(evolved_variables_list) > 0: file.write("const int8_t evol_gf_parity[" + str(len(evolved_variables_list)) + "] = { ") for i in range(len(evolved_variables_list) - 1): file.write(str(evol_parity_type[i]) + ", ") file.write( str(evol_parity_type[len(evolved_variables_list) - 1]) + " };\n") if len(auxiliary_variables_list) > 0: file.write("const int8_t aux_gf_parity[" + str(len(auxiliary_variables_list)) + "] = { ") for i in range(len(auxiliary_variables_list) - 1): file.write(str(aux_parity_type[i]) + ", ") file.write( str(aux_parity_type[len(auxiliary_variables_list) - 1]) + " };\n") if len(auxevol_variables_list) > 0: file.write("const int8_t auxevol_gf_parity[" + str(len(auxevol_variables_list)) + "] = { ") for i in range(len(auxevol_variables_list) - 1): file.write(str(auxevol_parity_type[i]) + ", ") file.write( str(auxevol_parity_type[len(auxevol_variables_list) - 1]) + " };\n") if verbose == True: import textwrap wrapper = textwrap.TextWrapper(initial_indent="", subsequent_indent=" ", width=75) def print_parity_list(gf_type, variable_names, parity_types): outstr = "" if len(variable_names) != 0: outstr += gf_type + " parity: ( " for i in range(len(variable_names)): outstr += variable_names[i] + ":" + str(parity_types[i]) if i != len(variable_names) - 1: outstr += ", " outstr += " )" print(wrapper.fill(outstr)) print_parity_list("Evolved", evolved_variables_list, evol_parity_type) print_parity_list("Auxiliary", auxiliary_variables_list, aux_parity_type) print_parity_list("AuxEvol", auxevol_variables_list, auxevol_parity_type) # Step 3: Find the Eigen-Coordinate and set up the Eigen-Coordinate's reference metric: CoordSystem_orig = par.parval_from_str("reference_metric::CoordSystem") par.set_parval_from_str("reference_metric::CoordSystem", rfm.get_EigenCoord()) rfm.reference_metric() # Step 4: Output C code for the Eigen-Coordinate mapping from xx->Cartesian: rfm.xxCart_h("EigenCoord_xxCart", os.path.join(Cparamspath, "set_Cparameters.h"), os.path.join(Ccodesdir, "EigenCoord_xxCart.h")) # Step 5: Output the Eigen-Coordinate mapping from Cartesian->xx: # Step 5.a: Sanity check: First make sure that rfm.Cart_to_xx has been set. Error out if not! if rfm.Cart_to_xx[0] == 0 or rfm.Cart_to_xx[1] == 0 or rfm.Cart_to_xx[ 2] == 0: print( "ERROR: rfm.Cart_to_xx[], which maps Cartesian -> xx, has not been set for" ) print(" reference_metric::CoordSystem = " + par.parval_from_str("reference_metric::CoordSystem")) print( " Boundary conditions in curvilinear coordinates REQUIRE this be set." ) sys.exit(1) # Step 5.b: Output C code for the Eigen-Coordinate mapping from Cartesian->xx: outputC([rfm.Cart_to_xx[0], rfm.Cart_to_xx[1], rfm.Cart_to_xx[2]], [ "Cart_to_xx0_inbounds", "Cart_to_xx1_inbounds", "Cart_to_xx2_inbounds" ], os.path.join(Ccodesdir, "EigenCoord_Cart_to_xx.h")) # Step 6: Restore reference_metric::CoordSystem back to the original CoordSystem par.set_parval_from_str("reference_metric::CoordSystem", CoordSystem_orig) rfm.reference_metric()
# $$\label{preliminaries}$$ # # \[Back to [top](#top)\] # # Here, we will import the NRPy+ core modules and set the reference metric to Cartesian, set commonly used NRPy+ parameters, and set C parameters that will be set from outside the code eventually generated from these expressions. We will also set up a parameter to determine what initial data is set up, although it won't do much yet. # Step 0: Import the NRPy+ core modules and set the reference metric to Cartesian import NRPy_param_funcs as par import indexedexp as ixp import grid as gri import finite_difference as fin from outputC import * import loop import reference_metric as rfm par.set_parval_from_str("reference_metric::CoordSystem", "Cartesian") rfm.reference_metric() # Step 1a: Set commonly used parameters. thismodule = "GiRaFFEfood_HO_Aligned_Rotator" # Set the spatial dimension parameter to 3. par.set_parval_from_str("grid::DIM", 3) DIM = par.parval_from_str("grid::DIM") B_p_aligned_rotator, R_NS_aligned_rotator = par.Cparameters( "REAL", thismodule, # B_p_aligned_rotator = the intensity of the magnetic field and # R_NS_aligned_rotator= "Neutron star" radius ["B_p_aligned_rotator", "R_NS_aligned_rotator"], [1e-5, 1.0])
def GiRaFFE_NRPy_C2P(StildeD, BU, gammaDD, betaU, alpha): GRHD.compute_sqrtgammaDET(gammaDD) gammaUU, unusedgammadet = ixp.symm_matrix_inverter3x3(gammaDD) BtildeU = ixp.zerorank1() for i in range(3): # \tilde{B}^i = B^i \sqrt{\gamma} BtildeU[i] = GRHD.sqrtgammaDET * BU[i] BtildeD = ixp.zerorank1() for i in range(3): for j in range(3): BtildeD[j] += gammaDD[i][j] * BtildeU[i] Btilde2 = sp.sympify(0) for i in range(3): Btilde2 += BtildeU[i] * BtildeD[i] global outStildeD outStildeD = StildeD # Then, enforce the orthogonality: if par.parval_from_str("enforce_orthogonality_StildeD_BtildeU"): StimesB = sp.sympify(0) for i in range(3): StimesB += StildeD[i] * BtildeU[i] for i in range(3): # {\tilde S}_i = {\tilde S}_i - ({\tilde S}_j {\tilde B}^j) {\tilde B}_i/{\tilde B}^2 outStildeD[i] -= StimesB * BtildeD[i] / Btilde2 # Calculate \tilde{S}^2: Stilde2 = sp.sympify(0) for i in range(3): for j in range(3): Stilde2 += gammaUU[i][j] * outStildeD[i] * outStildeD[j] # First we need to compute the factor f: # f = \sqrt{(1-\Gamma_{\max}^{-2}){\tilde B}^4/(16 \pi^2 \gamma {\tilde S}^2)} speed_limit_factor = sp.sqrt((sp.sympify(1)-GAMMA_SPEED_LIMIT**(-2.0))*Btilde2*Btilde2*sp.Rational(1,16)/\ (M_PI*M_PI*GRHD.sqrtgammaDET*GRHD.sqrtgammaDET*Stilde2)) import Min_Max_and_Piecewise_Expressions as noif # Calculate B^2 B2 = sp.sympify(0) for i in range(3): for j in range(3): B2 += gammaDD[i][j] * BU[i] * BU[j] # Enforce the speed limit on StildeD: if par.parval_from_str("enforce_speed_limit_StildeD"): for i in range(3): outStildeD[i] *= noif.min_noif(sp.sympify(1), speed_limit_factor) global ValenciavU ValenciavU = ixp.zerorank1() # Recompute 3-velocity: for i in range(3): for j in range(3): # \bar{v}^i = 4 \pi \gamma^{ij} {\tilde S}_j / (\sqrt{\gamma} B^2) ValenciavU[i] += sp.sympify(4) * M_PI * gammaUU[i][j] * outStildeD[ j] / (GRHD.sqrtgammaDET * B2) # This number determines how far away (in grid points) we will apply the fix. grid_points_from_z_plane = par.Cparameters("REAL", thismodule, "grid_points_from_z_plane", 4.0) if par.parval_from_str("enforce_current_sheet_prescription"): # Calculate the drift velocity driftvU = ixp.zerorank1() for i in range(3): driftvU[i] = alpha * ValenciavU[i] - betaU[i] # The direct approach, used by the original GiRaFFE: # v^z = -(\gamma_{xz} v^x + \gamma_{yz} v^y) / \gamma_{zz} newdriftvU2 = -(gammaDD[0][2] * driftvU[0] + gammaDD[1][2] * driftvU[1]) / gammaDD[2][2] # Now that we have the z component, it's time to substitute its Valencia form in. # Remember, we only do this if abs(z) < (k+0.01)*dz. Note that we add 0.01; this helps # avoid floating point errors and division by zero. This is the same as abs(z) - (k+0.01)*dz<0 coord = nrpyAbs(rfm.xx[2]) bound = (grid_points_from_z_plane + sp.Rational(1, 100)) * gri.dxx[2] ValenciavU[2] = noif.coord_leq_bound(coord,bound)*(newdriftvU2+betaU[2])/alpha \ + noif.coord_greater_bound(coord,bound)*ValenciavU[2]
def BSSN_constraints(add_T4UUmunu_source_terms=False, output_H_only=False): # Step 1.a: Set spatial dimension (must be 3 for BSSN, as BSSN is # a 3+1-dimensional decomposition of the general # relativistic field equations) DIM = 3 # Step 1.b: Given the chosen coordinate system, set up # corresponding reference metric and needed # reference metric quantities # The following function call sets up the reference metric # and related quantities, including rescaling matrices ReDD, # ReU, and hatted quantities. rfm.reference_metric() # Step 1.c: Register H and MU gridfunctions for # Hamiltonian & momentum constraints, # respectively. registered_already = False for i in range(len(gri.glb_gridfcs_list)): if gri.glb_gridfcs_list[i].name == "H": registered_already = True if not registered_already: _H = gri.register_gridfunctions("AUX", "H") # _H unused. if not output_H_only: _MU = ixp.register_gridfunctions_for_single_rank1( "AUX", "MU") # _MU unused. # Step 2: Hamiltonian constraint. # First declare all needed variables Bq.declare_BSSN_gridfunctions_if_not_declared_already() # Sets trK Bq.BSSN_basic_tensors() # Sets AbarDD Bq.gammabar__inverse_and_derivs() # Sets gammabarUU Bq.AbarUU_AbarUD_trAbar_AbarDD_dD() # Sets AbarUU and AbarDD_dD Bq.RicciBar__gammabarDD_dHatD__DGammaUDD__DGammaU() # Sets RbarDD Bq.phi_and_derivs() # Sets phi_dBarD & phi_dBarDD ################################# # -={ HAMILTONIAN CONSTRAINT }=- ################################# # Term 1: 2/3 K^2 global H H = sp.Rational(2, 3) * Bq.trK**2 # Term 2: -A_{ij} A^{ij} for i in range(DIM): for j in range(DIM): H += -Bq.AbarDD[i][j] * Bq.AbarUU[i][j] # Term 3a: trace(Rbar) Rbartrace = sp.sympify(0) for i in range(DIM): for j in range(DIM): Rbartrace += Bq.gammabarUU[i][j] * Bq.RbarDD[i][j] # Term 3b: -8 \bar{\gamma}^{ij} \bar{D}_i \phi \bar{D}_j \phi = -8*phi_dBar_times_phi_dBar # Term 3c: -8 \bar{\gamma}^{ij} \bar{D}_i \bar{D}_j \phi = -8*phi_dBarDD_contraction phi_dBar_times_phi_dBar = sp.sympify(0) # Term 3b phi_dBarDD_contraction = sp.sympify(0) # Term 3c for i in range(DIM): for j in range(DIM): phi_dBar_times_phi_dBar += Bq.gammabarUU[i][j] * Bq.phi_dBarD[ i] * Bq.phi_dBarD[j] phi_dBarDD_contraction += Bq.gammabarUU[i][j] * Bq.phi_dBarDD[i][j] # Add Term 3: H += Bq.exp_m4phi * (Rbartrace - 8 * (phi_dBar_times_phi_dBar + phi_dBarDD_contraction)) if add_T4UUmunu_source_terms: M_PI = par.Cparameters("#define", thismodule, "M_PI", "") # M_PI is pi as defined in C BTmunu.define_BSSN_T4UUmunu_rescaled_source_terms() rho = BTmunu.rho H += -16 * M_PI * rho # FIXME: ADD T4UUmunu SOURCE TERMS TO MOMENTUM CONSTRAINT! # Step 3: M^i, the momentum constraint ############################## # -={ MOMENTUM CONSTRAINT }=- ############################## # SEE Tutorial-BSSN_constraints.ipynb for full documentation. global MU MU = ixp.zerorank1() # Term 2: 6 A^{ij} \partial_j \phi: for i in range(DIM): for j in range(DIM): MU[i] += 6 * Bq.AbarUU[i][j] * Bq.phi_dD[j] # Term 3: -2/3 \bar{\gamma}^{ij} K_{,j} trK_dD = ixp.declarerank1( "trK_dD") # Not defined in BSSN_RHSs; only trK_dupD is defined there. for i in range(DIM): for j in range(DIM): MU[i] += -sp.Rational(2, 3) * Bq.gammabarUU[i][j] * trK_dD[j] # Next evaluate the conformal covariant derivative \bar{D}_j \bar{A}_{lm} AbarDD_dBarD = ixp.zerorank3() for i in range(DIM): for j in range(DIM): for k in range(DIM): AbarDD_dBarD[i][j][k] = Bq.AbarDD_dD[i][j][k] for l in range(DIM): AbarDD_dBarD[i][j][ k] += -Bq.GammabarUDD[l][k][i] * Bq.AbarDD[l][j] AbarDD_dBarD[i][j][ k] += -Bq.GammabarUDD[l][k][j] * Bq.AbarDD[i][l] # Term 1: Contract twice with the metric to make \bar{D}_{j} \bar{A}^{ij} for i in range(DIM): for j in range(DIM): for k in range(DIM): for l in range(DIM): MU[i] += Bq.gammabarUU[i][k] * Bq.gammabarUU[j][ l] * AbarDD_dBarD[k][l][j] # Finally, we multiply by e^{-4 phi} and rescale the momentum constraint: for i in range(DIM): MU[i] *= Bq.exp_m4phi / rfm.ReU[i]