From a3672d89f64c3ca2678072eef949a08dbcaca965 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Fri, 17 Sep 2021 12:54:30 -0400 Subject: [PATCH 01/95] Added magnetic tower, agn triggering --- inputs/cluster/agn_triggering.in | 114 +++ inputs/cluster/cluster.in | 192 +++++ inputs/cluster/cooling.in | 38 +- inputs/cluster/hse.in | 37 +- inputs/cluster/hydro_agn_feedback.in | 114 +++ inputs/cluster/magnetic_tower.in | 116 +++ inputs/cluster/sutherland_dopita.cooling | 782 ++++++++++++++++++ src/eos/adiabatic_glmmhd.hpp | 92 +++ src/eos/adiabatic_hydro.hpp | 65 ++ src/eos/eos.hpp | 2 +- src/hydro/hydro_driver.cpp | 85 +- src/hydro/srcterms/gravitational_field.hpp | 5 +- src/hydro/srcterms/tabular_cooling.hpp | 9 +- src/main.cpp | 1 + src/main.hpp | 2 + src/pgen/CMakeLists.txt | 3 + src/pgen/cluster.cpp | 175 +++- src/pgen/cluster/agn_feedback.cpp | 193 +++++ src/pgen/cluster/agn_feedback.hpp | 60 ++ src/pgen/cluster/agn_triggering.cpp | 561 +++++++++++++ src/pgen/cluster/agn_triggering.hpp | 129 +++ src/pgen/cluster/cluster_gravity.hpp | 67 +- src/pgen/cluster/cluster_utils.hpp | 88 ++ src/pgen/cluster/entropy_profiles.hpp | 16 +- .../hydrostatic_equilibrium_sphere.cpp | 262 +++--- .../hydrostatic_equilibrium_sphere.hpp | 124 ++- src/pgen/cluster/jet_coords.hpp | 115 +++ src/pgen/cluster/magnetic_tower.cpp | 312 +++++++ src/pgen/cluster/magnetic_tower.hpp | 194 +++++ src/pgen/pgen.hpp | 3 +- src/reduction_utils.hpp | 65 ++ src/units.hpp | 2 +- tst/regression/CMakeLists.txt | 9 + .../cluster_agn_triggering/__init__.py | 0 .../cluster_agn_triggering.py | 343 ++++++++ .../test_suites/cluster_hse/cluster_hse.py | 54 +- .../cluster_hydro_agn_feedback/__init__.py | 0 .../cluster_hydro_agn_feedback.py | 355 ++++++++ .../cluster_magnetic_tower/__init__.py | 0 .../cluster_magnetic_tower.py | 523 ++++++++++++ .../cluster_tabular_cooling.py | 12 +- 41 files changed, 4958 insertions(+), 361 deletions(-) create mode 100644 inputs/cluster/agn_triggering.in create mode 100644 inputs/cluster/cluster.in create mode 100644 inputs/cluster/hydro_agn_feedback.in create mode 100644 inputs/cluster/magnetic_tower.in create mode 100644 inputs/cluster/sutherland_dopita.cooling create mode 100644 src/pgen/cluster/agn_feedback.cpp create mode 100644 src/pgen/cluster/agn_feedback.hpp create mode 100644 src/pgen/cluster/agn_triggering.cpp create mode 100644 src/pgen/cluster/agn_triggering.hpp create mode 100644 src/pgen/cluster/cluster_utils.hpp create mode 100644 src/pgen/cluster/jet_coords.hpp create mode 100644 src/pgen/cluster/magnetic_tower.cpp create mode 100644 src/pgen/cluster/magnetic_tower.hpp create mode 100644 src/reduction_utils.hpp create mode 100644 tst/regression/test_suites/cluster_agn_triggering/__init__.py create mode 100644 tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py create mode 100644 tst/regression/test_suites/cluster_hydro_agn_feedback/__init__.py create mode 100644 tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py create mode 100644 tst/regression/test_suites/cluster_magnetic_tower/__init__.py create mode 100644 tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py diff --git a/inputs/cluster/agn_triggering.in b/inputs/cluster/agn_triggering.in new file mode 100644 index 00000000..b7631a75 --- /dev/null +++ b/inputs/cluster/agn_triggering.in @@ -0,0 +1,114 @@ + + +problem = AGN Triggering Test + + +problem_id = cluster # problem ID: basename of output filenames + + +file_type = hst # History data dump +dt = 1e-4 # time increment between outputs + + +file_type = hdf5 # HDF5 data dump +variables = cons,prim # Variables to be output +dt = 0.1 # Time increment between outputs +id = vars # Name to append to output + + +cfl_number = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number +nlim = -1 # cycle limit +tlim = 0.1 # time limit +integrator = vl2 # time integration algorithm +perf_cycle_offset = 10 # interval for stdout summary info + + + +refinement = static +nghost = 2 + +nx1 = 64 # Number of zones in X1-direction +x1min =-0.1 # minimum value of X1 +x1max = 0.1 # maximum value of X1 +ix1_bc = outflow # inner-X1 boundary flag +ox1_bc = outflow # outer-X1 boundary flag + +nx2 = 64 # Number of zones in X2-direction +x2min =-0.1 # minimum value of X2 +x2max = 0.1 # maximum value of X2 +ix2_bc = outflow # inner-X2 boundary flag +ox2_bc = outflow # outer-X2 boundary flag + +nx3 = 64 # Number of zones in X3-direction +x3min =-0.1 # minimum value of X3 +x3max = 0.1 # maximum value of X3 +ix3_bc = outflow # inner-X3 boundary flag +ox3_bc = outflow # outer-X3 boundary flag + + +x1min = -0.025 +x1max = 0.025 +x2min = -0.025 +x2max = 0.025 +x3min = -0.025 +x3max = 0.025 +level = 2 + + + +nx1 = 8 # Number of zones in X1-direction +nx2 = 8 # Number of zones in X2-direction +nx3 = 8 # Number of zones in X3-direction + + +fluid = euler +gamma = 1.6666666666666667 # gamma = C_p/C_v +eos = adiabatic +riemann = hlle +reconstruction = plm +use_scratch = false +scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM +integrate_flux_div = false + +He_mass_fraction = 0.25 + + +#Units parameters +#Note: All other parameters for the cluster are in terms of these units +code_length_cgs = 3.085677580962325e+24 # 1 Mpc in cm +code_mass_cgs = 1.98841586e+47 # 1e14 Msun in g +code_time_cgs = 3.15576e+16 # 1 Gyr in s + + + + +#Define SMBH for Bondi accretion +m_smbh = 1.0000000000000002e-06 + +#Disable gravity as a source term +gravity_srcterm = false + + +#Initialize with a uniform gas +init_uniform_gas = true +rho = 14775.575892787232 +ux = 0.0006136272991326239 +uy = 0.0004090848660884159 +uz =-0.0005113560826105199 +pres = 1.5454368403867562 + + +triggering_mode = COLD_GAS +accretion_radius = 0.02 +cold_temp_thresh = 7198.523584993224 +cold_t_acc = 0.1 +bondi_alpha = 100 +bondi_beta = 2 +bondi_n0 = 1.4928506511614283e+74 +write_to_file=false +triggering_filename= agn_triggering.dat + + +#Don't do any feedback with the triggering +disabled = true + diff --git a/inputs/cluster/cluster.in b/inputs/cluster/cluster.in new file mode 100644 index 00000000..3f587ff4 --- /dev/null +++ b/inputs/cluster/cluster.in @@ -0,0 +1,192 @@ + + +problem = Isolated galaxy cluster + + +problem_id = cluster # problem ID: basename of output filenames + + +file_type = hst # History data dump +dt = 1e-3 # time increment between outputs (1 Myr) + + +file_type = hdf5 # HDF5 data dump +variables = prim # Variables to be output +dt = 1.e-2 # Time increment between outputs (10 Myr) +id = prim # Name to append to output + + +cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number +nlim = -1 # cycle limit +tlim = 1e-1 # time limit (100 Myr) +integrator = vl2 # time integration algorithm +perf_cycle_offset = 10 # interval for stdout summary info + + + +refinement = static +nghost = 2 + +nx1 = 128 # Number of zones in X1-direction +x1min =-1.6 # minimum value of X1 +x1max = 1.6 # maximum value of X1 +ix1_bc = outflow # inner-X1 boundary flag +ox1_bc = outflow # outer-X1 boundary flag + +nx2 = 128 # Number of zones in X2-direction +x2min =-1.6 # minimum value of X2 +x2max = 1.6 # maximum value of X2 +ix2_bc = outflow # inner-X2 boundary flag +ox2_bc = outflow # outer-X2 boundary flag + +nx3 = 128 # Number of zones in X3-direction +x3min =-1.6 # minimum value of X3 +x3max = 1.6 # maximum value of X3 +ix3_bc = outflow # inner-X3 boundary flag +ox3_bc = outflow # outer-X3 boundary flag + + +x1min = -0.4 +x1max = 0.4 +x2min = -0.4 +x2max = 0.4 +x3min = -0.4 +x3max = 0.4 +level = 1 + + +x1min = -0.025 +x1max = 0.025 +x2min = -0.025 +x2max = 0.025 +x3min = -0.025 +x3max = 0.025 +level = 3 + + +nx1 = 32 # Number of zones in X1-direction +nx2 = 32 # Number of zones in X2-direction +nx3 = 32 # Number of zones in X3-direction + + +fluid = glmmhd +gamma = 1.6666666666666667 # gamma = C_p/C_v +eos = adiabatic +riemann = hlld +reconstruction = plm +use_scratch = false +scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM + +He_mass_fraction = 0.25 + + +#Units parameters +#Note: All other parameters for the cluster are in terms of these units +code_length_cgs = 3.085677580962325e+24 # 1 Mpc in cm +code_mass_cgs = 1.98841586e+47 # 1e14 Msun in g +code_time_cgs = 3.15576e+16 # 1 Gyr in s + + +enable_cooling=tabular +table_filename=schure.cooling +log_temp_col=0 +log_lambda_col=1 +lambda_units_cgs=1.0 + +integrator=rk45 +cfl=0.1 +max_iter=100 +d_e_tol=1e-08 +d_log_temp_tol=1e-08 + + +hubble_parameter = 0.0715898515654728 + + +#Include gravity as a source term +gravity_srcterm = true +#NOTE: Use this line instead to disable gravity source term +#gravity_srcterm = false + +#Which gravitational fields to include +include_nfw_g = True +which_bcg_g = HERNQUIST +include_smbh_g = True + +#NFW parameters +c_nfw = 6.0 +m_nfw_200 = 10.000000000000002 + +#BCG parameters +m_bcg_s = 0.0010000000000000002 +r_bcg_s = 0.004 + +#SMBH parameters +m_smbh = 1.0000000000000002e-06 + +#Smooth gravity at origin, for numerical reasons +g_smoothing_radius = 0.0 + +#NOTE: Uncomment these lines to use a uniform initial gas instead of hydrostatic equilbrium +# +##Initialize with a uniform gas +#init_uniform_gas = true +#rho = 147.7557589278723 +#ux = 0 +#uy = 0 +#uz = 0 +#pres = 1.5454368403867562 + + +#Entropy profile parameters +k_0 = 8.851337676479303e-121 +k_100 = 1.3277006514718954e-119 +r_k = 0.1 +alpha_k = 1.1 + + +#Fix density at radius to close system of equations +r_fix = 2.0 +rho_fix = 0.01477557589278723 + +#Building the radii at which to sample initial rho,P +r_sampling = 4.0 +max_dr = 0.001 + + +triggering_mode = COLD_GAS +#NOTE: Change to this line to disable AGN triggering +#triggering_mode = NONE +accretion_radius = 0.0005 +cold_temp_thresh= 10000.0 +cold_t_acc= 0.1 +bondi_alpha= 100.0 +bondi_beta= 2.0 +bondi_n0= 2.9379989445851786e+72 + + +jet_phi= 0.15 +jet_theta_dot= 628.3185307179587 + + +efficiency = 0.001 +magnetic_fraction = 0.4 +thermal_fraction = 0.3 +kinetic_fraction = 0.3 +#NOTE: Change to these lines to disable magnetic AGN feedback +#magnetic_fraction = 0.0 +#thermal_fraction = 0.5 +#kinetic_fraction = 0.5 + +thermal_radius = 0.0005 +kinetic_jet_radius = 0.0005 +kinetic_jet_height = 0.0005 + + + +alpha = 20 +l_scale = 0.001 +initial_field = 0.12431560000204142 +#NOTE: Change to this line to disable initial magnetic tower +#initial_field = 0. +l_mass_scale = 0.001 diff --git a/inputs/cluster/cooling.in b/inputs/cluster/cooling.in index c56a2e8b..2979860f 100644 --- a/inputs/cluster/cooling.in +++ b/inputs/cluster/cooling.in @@ -1,6 +1,6 @@ -problem = Isolated galaxy cluster +problem = Galaxy Cluster Cooling Test problem_id = cluster # problem ID: basename of output filenames @@ -63,29 +63,17 @@ He_mass_fraction = 0.25 #Units parameters -code_length_cgs = 3.085677580962325e+24 -code_mass_cgs = 1.98841586e+47 -code_time_cgs = 3.15576e+16 - - - -#Disable gravity as a source term -gravity_srcterm = false - -#Initialize with a uniform gas -init_uniform_gas = true -uniform_gas_rho = 147.7557589278723 -uniform_gas_ux = 0 -uniform_gas_uy = 0 -uniform_gas_uz = 0 -uniform_gas_pres = 1.5454368403867562 +#Note: All other parameters for the cluster are in terms of these units +code_length_cgs = 3.085677580962325e+24 # 1 Mpc in cm +code_mass_cgs = 1.98841586e+47 # 1e14 Msun in g +code_time_cgs = 3.15576e+16 # 1 Gyr in s enable_cooling = tabular table_filename = schure.cooling log_temp_col = 0 log_lambda_col = 1 -lambda_units_cgs = 1 +lambda_units_cgs = 1 #erg cm^3/s in cgs, as used in schure.cooling integrator = rk12 max_iter = 100 @@ -93,3 +81,17 @@ cfl = 0.1 d_log_temp_tol = 1e-8 d_e_tol = 1e-8 + + + +#Disable gravity as a source term +gravity_srcterm = false + + +#Initialize with a uniform gas +init_uniform_gas = true +rho = 147.7557589278723 +ux = 0 +uy = 0 +uz = 0 +pres = 1.5454368403867562 diff --git a/inputs/cluster/hse.in b/inputs/cluster/hse.in index 5d99d003..7b24c397 100644 --- a/inputs/cluster/hse.in +++ b/inputs/cluster/hse.in @@ -1,6 +1,6 @@ -problem = Isolated galaxy cluster +problem = Galaxy Cluster Hydrostatic Equilibrium Test problem_id = cluster # problem ID: basename of output filenames @@ -73,13 +73,16 @@ He_mass_fraction = 0.25 #Units parameters -code_length_cgs = 3.085677580962325e+24 -code_mass_cgs = 1.98841586e+47 -code_time_cgs = 3.15576e+16 +#Note: All other parameters for the cluster are in terms of these units +code_length_cgs = 3.085677580962325e+24 # 1 Mpc in cm +code_mass_cgs = 1.98841586e+47 # 1e14 Msun in g +code_time_cgs = 3.15576e+16 # 1 Gyr in s hubble_parameter = 0.0715898515654728 + + #Which gravitational fields to include include_nfw_g = True which_bcg_g = HERNQUIST @@ -87,14 +90,14 @@ include_smbh_g = True #NFW parameters c_nfw = 6.0 -M_nfw_200 = 10.000000000000002 +m_nfw_200 = 10.000000000000002 #BCG parameters -M_bcg_s = 0.0010000000000000002 -R_bcg_s = 0.004 +m_bcg_s = 0.0010000000000000002 +r_bcg_s = 0.004 #SMBH parameters -M_smbh = 1.0000000000000002e-06 +m_smbh = 1.0000000000000002e-06 #Smooth gravity at origin, for numerical reasons g_smoothing_radius = 1e-6 @@ -102,18 +105,22 @@ g_smoothing_radius = 1e-6 #Include gravity as a source term gravity_srcterm = true + + #Entropy profile parameters -K_0 = 8.851337676479303e-121 -K_100 = 1.3277006514718954e-119 -R_K = 0.1 -alpha_K = 1.1 +k_0 = 8.851337676479303e-121 +k_100 = 1.3277006514718954e-119 +r_k = 0.1 +alpha_k = 1.1 + + #Fix density at radius to close system of equations -R_fix = 2.0 +r_fix = 2.0 rho_fix = 0.01477557589278723 #Building the radii at which to sample initial rho,P -R_sampling = 4.0 -max_dR = 0.001 +r_sampling = 4.0 +max_dr = 0.001 test_he_sphere = true diff --git a/inputs/cluster/hydro_agn_feedback.in b/inputs/cluster/hydro_agn_feedback.in new file mode 100644 index 00000000..c8886ef7 --- /dev/null +++ b/inputs/cluster/hydro_agn_feedback.in @@ -0,0 +1,114 @@ + + +problem = Hydro AGN Feedback Test + + +problem_id = cluster # problem ID: basename of output filenames + + +file_type = hst # History data dump +dt = 1e-4 # time increment between outputs + + +file_type = hdf5 # HDF5 data dump +variables = cons,prim # Variables to be output +dt = 0.01 # Time increment between outputs +id = vars # Name to append to output + + +cfl_number = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number +nlim = -1 # cycle limit +tlim = 0.01 # time limit +integrator = vl2 # time integration algorithm +perf_cycle_offset = 10 # interval for stdout summary info + + + +refinement = static +nghost = 2 + +nx1 = 64 # Number of zones in X1-direction +x1min =-0.1 # minimum value of X1 +x1max = 0.1 # maximum value of X1 +ix1_bc = outflow # inner-X1 boundary flag +ox1_bc = outflow # outer-X1 boundary flag + +nx2 = 64 # Number of zones in X2-direction +x2min =-0.1 # minimum value of X2 +x2max = 0.1 # maximum value of X2 +ix2_bc = outflow # inner-X2 boundary flag +ox2_bc = outflow # outer-X2 boundary flag + +nx3 = 64 # Number of zones in X3-direction +x3min =-0.1 # minimum value of X3 +x3max = 0.1 # maximum value of X3 +ix3_bc = outflow # inner-X3 boundary flag +ox3_bc = outflow # outer-X3 boundary flag + + +x1min = -0.025 +x1max = 0.025 +x2min = -0.025 +x2max = 0.025 +x3min = -0.025 +x3max = 0.025 +level = 2 + + + +nx1 = 8 # Number of zones in X1-direction +nx2 = 8 # Number of zones in X2-direction +nx3 = 8 # Number of zones in X3-direction + + +fluid = euler +gamma = 1.6666666666666667 # gamma = C_p/C_v +eos = adiabatic +riemann = hlle +reconstruction = plm +use_scratch = false +scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM +integrate_flux_div = false + +He_mass_fraction = 0.25 + + +#Units parameters +#Note: All other parameters for the cluster are in terms of these units +code_length_cgs = 3.085677580962325e+24 # 1 Mpc in cm +code_mass_cgs = 1.98841586e+47 # 1e14 Msun in g +code_time_cgs = 3.15576e+16 # 1 Gyr in s + + + + +#Disable gravity as a source term +gravity_srcterm = false + + +#Initialize with a uniform gas +init_uniform_gas = true +rho = 147.7557589278723 +ux = 0 +uy = 0 +uz = 0 +pres = 1.5454368403867562 + + +#Define a precessing jet +jet_phi = 0.2 +jet_theta_dot = 1 +jet_theta0 = 2 + + +fixed_power = 1.65998282e-04 +efficiency = 1e-3 + +magnetic_fraction = = 0.0 +thermal_fraction = 0.5 +kinetic_fraction = 0.5 + +thermal_radius = 0.0125 + +kinetic_jet_radius = 0.01 +kinetic_jet_height = 0.02 diff --git a/inputs/cluster/magnetic_tower.in b/inputs/cluster/magnetic_tower.in new file mode 100644 index 00000000..368ce580 --- /dev/null +++ b/inputs/cluster/magnetic_tower.in @@ -0,0 +1,116 @@ + + +problem = Magnetic Tower Test + + +problem_id = cluster # problem ID: basename of output filenames + + +file_type = hst # History data dump +dt = 1e-4 # time increment between outputs + + +file_type = hdf5 # HDF5 data dump +variables = cons,prim # Variables to be output +dt = 0.01 # Time increment between outputs +id = vars # Name to append to output + + +cfl_number = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number +nlim = -1 # cycle limit +tlim = 0.01 # time limit +integrator = vl2 # time integration algorithm +perf_cycle_offset = 10 # interval for stdout summary info + + + +refinement = static +nghost = 2 + +nx1 = 64 # Number of zones in X1-direction +x1min =-0.1 # minimum value of X1 +x1max = 0.1 # maximum value of X1 +ix1_bc = outflow # inner-X1 boundary flag +ox1_bc = outflow # outer-X1 boundary flag + +nx2 = 64 # Number of zones in X2-direction +x2min =-0.1 # minimum value of X2 +x2max = 0.1 # maximum value of X2 +ix2_bc = outflow # inner-X2 boundary flag +ox2_bc = outflow # outer-X2 boundary flag + +nx3 = 64 # Number of zones in X3-direction +x3min =-0.1 # minimum value of X3 +x3max = 0.1 # maximum value of X3 +ix3_bc = outflow # inner-X3 boundary flag +ox3_bc = outflow # outer-X3 boundary flag + + +x1min = -0.025 +x1max = 0.025 +x2min = -0.025 +x2max = 0.025 +x3min = -0.025 +x3max = 0.025 +level = 2 + + + +nx1 = 8 # Number of zones in X1-direction +nx2 = 8 # Number of zones in X2-direction +nx3 = 8 # Number of zones in X3-direction + + +fluid = glmmhd +gamma = 1.6666666666666667 # gamma = C_p/C_v +eos = adiabatic +riemann = hlle +reconstruction = plm +use_scratch = false +scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM +integrate_flux_div = false + +He_mass_fraction = 0.25 + + +#Units parameters +#Note: All other parameters for the cluster are in terms of these units +code_length_cgs = 3.085677580962325e+24 # 1 Mpc in cm +code_mass_cgs = 1.98841586e+47 # 1e14 Msun in g +code_time_cgs = 3.15576e+16 # 1 Gyr in s + + + + +#Disable gravity as a source term +gravity_srcterm = false + + +#Initialize with a uniform gas +init_uniform_gas = true +rho = 147.7557589278723 +ux = 0 +uy = 0 +uz = 0 +pres = 1.5454368403867562 + + +#Define a precessing jet +jet_theta = 0.2 +jet_phi_dot = 0 +jet_phi0 = 1 + + +fixed_power = 0.0016599828166743962 +efficiency = 1e-3 +magnetic_fraction = 1 +kinetic_fraction = 0 +thermal_fraction = 0 + + +alpha = 20 +l_scale = 0.01 +initial_field = 0.12431560000204142 +fixed_field_rate = 12.431560000204144 +fixed_mass_rate = 1.7658562333594375e-05 +l_mass_scale = 0.005 diff --git a/inputs/cluster/sutherland_dopita.cooling b/inputs/cluster/sutherland_dopita.cooling new file mode 100644 index 00000000..5e329103 --- /dev/null +++ b/inputs/cluster/sutherland_dopita.cooling @@ -0,0 +1,782 @@ +# Cooling table for solar metallicity, 1/3 solar metallicity +# (adapted from PLUTO code by Deovrat Prasad) +# temperature log(K), cooling rate/ne^3 (erg cm^3/s) +# This is the cooling table based on Sutherland & Dopita (1993), ApJ, 88, 253 +# logT 1/3 solar 1 solar +3.0064233e+00 -2.4478679e+01 -2.4024876e+01 +3.0154017e+00 -2.4475474e+01 -2.4022199e+01 +3.0244036e+00 -2.4472293e+01 -2.4019538e+01 +3.0333835e+00 -2.4469109e+01 -2.4016884e+01 +3.0424180e+00 -2.4465936e+01 -2.4014241e+01 +3.0513841e+00 -2.4462760e+01 -2.4011606e+01 +3.0603956e+00 -2.4459595e+01 -2.4008978e+01 +3.0694091e+00 -2.4456416e+01 -2.4006361e+01 +3.0783843e+00 -2.4453248e+01 -2.4003747e+01 +3.0873909e+00 -2.4450078e+01 -2.4001139e+01 +3.0963885e+00 -2.4446918e+01 -2.3998526e+01 +3.1053739e+00 -2.4443746e+01 -2.3995937e+01 +3.1143774e+00 -2.4440572e+01 -2.3993363e+01 +3.1233616e+00 -2.4437398e+01 -2.3990762e+01 +3.1323878e+00 -2.4434223e+01 -2.3988176e+01 +3.1413557e+00 -2.4431036e+01 -2.3985563e+01 +3.1503573e+00 -2.4427861e+01 -2.3983008e+01 +3.1593566e+00 -2.4424662e+01 -2.3980427e+01 +3.1683501e+00 -2.4421464e+01 -2.3977819e+01 +3.1773633e+00 -2.4418266e+01 -2.3975227e+01 +3.1863629e+00 -2.4415036e+01 -2.3972610e+01 +3.1953461e+00 -2.4411818e+01 -2.3970008e+01 +3.2043642e+00 -2.4408568e+01 -2.3967381e+01 +3.2133584e+00 -2.4405320e+01 -2.3964770e+01 +3.2223522e+00 -2.4402053e+01 -2.3962135e+01 +3.2313421e+00 -2.4398766e+01 -2.3959516e+01 +3.2403495e+00 -2.4395472e+01 -2.3956834e+01 +3.2493451e+00 -2.4392395e+01 -2.3954403e+01 +3.2583259e+00 -2.4388999e+01 -2.3951636e+01 +3.2673360e+00 -2.4385662e+01 -2.3948963e+01 +3.2763239e+00 -2.4382266e+01 -2.3946192e+01 +3.2853322e+00 -2.4378876e+01 -2.3943476e+01 +3.2943339e+00 -2.4375439e+01 -2.3940664e+01 +3.3033257e+00 -2.4371989e+01 -2.3937869e+01 +3.3123255e+00 -2.4368516e+01 -2.3935056e+01 +3.3213084e+00 -2.4365009e+01 -2.3932185e+01 +3.3303123e+00 -2.4361481e+01 -2.3929297e+01 +3.3393123e+00 -2.4357921e+01 -2.3926355e+01 +3.3483049e+00 -2.4354332e+01 -2.3923396e+01 +3.3573058e+00 -2.4350714e+01 -2.3920385e+01 +3.3663109e+00 -2.4347038e+01 -2.3917286e+01 +3.3752977e+00 -2.4343308e+01 -2.3914174e+01 +3.3842996e+00 -2.4339533e+01 -2.3910978e+01 +3.3932944e+00 -2.4335697e+01 -2.3907771e+01 +3.4022958e+00 -2.4331810e+01 -2.3904447e+01 +3.4112998e+00 -2.4327856e+01 -2.3901114e+01 +3.4202859e+00 -2.4323846e+01 -2.3897669e+01 +3.4292838e+00 -2.4319764e+01 -2.3894183e+01 +3.4382891e+00 -2.4315621e+01 -2.3890590e+01 +3.4472821e+00 -2.4311402e+01 -2.3886926e+01 +3.4562749e+00 -2.4307100e+01 -2.3883193e+01 +3.4652787e+00 -2.4302727e+01 -2.3879327e+01 +3.4742746e+00 -2.4298268e+01 -2.3875398e+01 +3.4832734e+00 -2.4293726e+01 -2.3871375e+01 +3.4922714e+00 -2.4289088e+01 -2.3867228e+01 +3.5012647e+00 -2.4284364e+01 -2.3862963e+01 +3.5102634e+00 -2.4279535e+01 -2.3858582e+01 +3.5192634e+00 -2.4274595e+01 -2.3854089e+01 +3.5282609e+00 -2.4269557e+01 -2.3849458e+01 +3.5372523e+00 -2.4264401e+01 -2.3844694e+01 +3.5462465e+00 -2.4259124e+01 -2.3839802e+01 +3.5552517e+00 -2.4253732e+01 -2.3834756e+01 +3.5642517e+00 -2.4248221e+01 -2.3829533e+01 +3.5732430e+00 -2.4242574e+01 -2.3824169e+01 +3.5822452e+00 -2.4236774e+01 -2.3818642e+01 +3.5912427e+00 -2.4230837e+01 -2.3812902e+01 +3.6002321e+00 -2.4224746e+01 -2.3807015e+01 +3.6092315e+00 -2.4218503e+01 -2.3800931e+01 +3.6182260e+00 -2.4212100e+01 -2.3794633e+01 +3.6272327e+00 -2.4205533e+01 -2.3788132e+01 +3.6362270e+00 -2.4198795e+01 -2.3781438e+01 +3.6452257e+00 -2.4190723e+01 -2.3773117e+01 +3.6542247e+00 -2.4183447e+01 -2.3765787e+01 +3.6632202e+00 -2.4175972e+01 -2.3758180e+01 +3.6722180e+00 -2.4168277e+01 -2.3750313e+01 +3.6812141e+00 -2.4160359e+01 -2.3742153e+01 +3.6902049e+00 -2.4152205e+01 -2.3733745e+01 +3.6992045e+00 -2.4143815e+01 -2.3725034e+01 +3.7081999e+00 -2.4135175e+01 -2.3716021e+01 +3.7172043e+00 -2.4126273e+01 -2.3706682e+01 +3.7261973e+00 -2.4117100e+01 -2.3697042e+01 +3.7351995e+00 -2.4107638e+01 -2.3687040e+01 +3.7441912e+00 -2.4097872e+01 -2.3676686e+01 +3.7531922e+00 -2.4087778e+01 -2.3665949e+01 +3.7621907e+00 -2.4077342e+01 -2.3654803e+01 +3.7711831e+00 -2.4066538e+01 -2.3643248e+01 +3.7801804e+00 -2.4055340e+01 -2.3631248e+01 +3.7891787e+00 -2.4043711e+01 -2.3618777e+01 +3.7981809e+00 -2.4031629e+01 -2.3605794e+01 +3.8071764e+00 -2.4019047e+01 -2.3592286e+01 +3.8161750e+00 -2.4005943e+01 -2.3578248e+01 +3.8251729e+00 -2.3992252e+01 -2.3563631e+01 +3.8341663e+00 -2.3977984e+01 -2.3548428e+01 +3.8431642e+00 -2.3963012e+01 -2.3532614e+01 +3.8521627e+00 -2.3947421e+01 -2.3516284e+01 +3.8611579e+00 -2.3930924e+01 -2.3499160e+01 +3.8701580e+00 -2.3913533e+01 -2.3481302e+01 +3.8791532e+00 -2.3895103e+01 -2.3462697e+01 +3.8881514e+00 -2.3875561e+01 -2.3443275e+01 +3.8971486e+00 -2.3854773e+01 -2.3423014e+01 +3.9061464e+00 -2.3832535e+01 -2.3401844e+01 +3.9151412e+00 -2.3808773e+01 -2.3379729e+01 +3.9241397e+00 -2.3783254e+01 -2.3356616e+01 +3.9331379e+00 -2.3755896e+01 -2.3332482e+01 +3.9421371e+00 -2.3726466e+01 -2.3307285e+01 +3.9511334e+00 -2.3694864e+01 -2.3280984e+01 +3.9601282e+00 -2.3660946e+01 -2.3253560e+01 +3.9691269e+00 -2.3626886e+01 -2.3227869e+01 +3.9781257e+00 -2.3587976e+01 -2.3198123e+01 +3.9871208e+00 -2.3546269e+01 -2.3166898e+01 +3.9961175e+00 -2.3501855e+01 -2.3134280e+01 +4.0051376e+00 -2.3455015e+01 -2.3100163e+01 +4.0141003e+00 -2.3405099e+01 -2.3064503e+01 +4.0231289e+00 -2.3352686e+01 -2.3027274e+01 +4.0320947e+00 -2.3297880e+01 -2.2988430e+01 +4.0411162e+00 -2.3240884e+01 -2.2947999e+01 +4.0501090e+00 -2.3181926e+01 -2.2905878e+01 +4.0591088e+00 -2.3121266e+01 -2.2862203e+01 +4.0681116e+00 -2.3059200e+01 -2.2816987e+01 +4.0771134e+00 -2.2996066e+01 -2.2770446e+01 +4.0861106e+00 -2.2932111e+01 -2.2722437e+01 +4.0950996e+00 -2.2867676e+01 -2.2673234e+01 +4.1040772e+00 -2.2803078e+01 -2.2623022e+01 +4.1130739e+00 -2.2738642e+01 -2.2572076e+01 +4.1220848e+00 -2.2674649e+01 -2.2520338e+01 +4.1310730e+00 -2.2611686e+01 -2.2468726e+01 +4.1400679e+00 -2.2550013e+01 -2.2417221e+01 +4.1490651e+00 -2.2490233e+01 -2.2366521e+01 +4.1580608e+00 -2.2432797e+01 -2.2317025e+01 +4.1670809e+00 -2.2378325e+01 -2.2269347e+01 +4.1760623e+00 -2.2327505e+01 -2.2224026e+01 +4.1850603e+00 -2.2281067e+01 -2.2182091e+01 +4.1940701e+00 -2.2239615e+01 -2.2144202e+01 +4.2030601e+00 -2.2167274e+01 -2.2083372e+01 +4.2120544e+00 -2.2127989e+01 -2.2046956e+01 +4.2210489e+00 -2.2094112e+01 -2.2015099e+01 +4.2300400e+00 -2.2065896e+01 -2.1988134e+01 +4.2390491e+00 -2.2043452e+01 -2.1966134e+01 +4.2480469e+00 -2.2026484e+01 -2.1948616e+01 +4.2570543e+00 -2.2015104e+01 -2.1936291e+01 +4.2660434e+00 -2.2008663e+01 -2.1928228e+01 +4.2750348e+00 -2.2006595e+01 -2.1923906e+01 +4.2840245e+00 -2.2008278e+01 -2.1922741e+01 +4.2930309e+00 -2.2013094e+01 -2.1924088e+01 +4.3020277e+00 -2.2020433e+01 -2.1927383e+01 +4.3110330e+00 -2.2029751e+01 -2.1932111e+01 +4.3200216e+00 -2.2040544e+01 -2.1937719e+01 +4.3290316e+00 -2.2052380e+01 -2.1943820e+01 +4.3380180e+00 -2.2064886e+01 -2.1950085e+01 +4.3470176e+00 -2.2077737e+01 -2.1956206e+01 +4.3560067e+00 -2.2090658e+01 -2.1961976e+01 +4.3650197e+00 -2.2103413e+01 -2.1967140e+01 +4.3740147e+00 -2.2115805e+01 -2.1971591e+01 +4.3830070e+00 -2.2127646e+01 -2.1975145e+01 +4.3920107e+00 -2.2138812e+01 -2.1977819e+01 +4.4010040e+00 -2.2149188e+01 -2.1979473e+01 +4.4100007e+00 -2.2158641e+01 -2.1980053e+01 +4.4189969e+00 -2.2167095e+01 -2.1979556e+01 +4.4279889e+00 -2.2174515e+01 -2.1977984e+01 +4.4369891e+00 -2.2180693e+01 -2.1974981e+01 +4.4459932e+00 -2.2185926e+01 -2.1971225e+01 +4.4549820e+00 -2.2190265e+01 -2.1966616e+01 +4.4639825e+00 -2.2193298e+01 -2.1961141e+01 +4.4729757e+00 -2.2194723e+01 -2.1954325e+01 +4.4819726e+00 -2.2195200e+01 -2.1946614e+01 +4.4909693e+00 -2.2195186e+01 -2.1938246e+01 +4.4999756e+00 -2.2194812e+01 -2.1929408e+01 +4.5089739e+00 -2.2193698e+01 -2.1919987e+01 +4.5179608e+00 -2.2192025e+01 -2.1910024e+01 +4.5269593e+00 -2.2190427e+01 -2.1899871e+01 +4.5359647e+00 -2.2188217e+01 -2.1889208e+01 +4.5449605e+00 -2.2185593e+01 -2.1878210e+01 +4.5539558e+00 -2.2182494e+01 -2.1866749e+01 +4.5629587e+00 -2.2179615e+01 -2.1855332e+01 +4.5719533e+00 -2.2175451e+01 -2.1843178e+01 +4.5809478e+00 -2.2171585e+01 -2.1831120e+01 +4.5899496e+00 -2.2166929e+01 -2.1818614e+01 +4.5989436e+00 -2.2161132e+01 -2.1805541e+01 +4.6079373e+00 -2.2155510e+01 -2.1792527e+01 +4.6169374e+00 -2.2148925e+01 -2.1778977e+01 +4.6259295e+00 -2.2141120e+01 -2.1764876e+01 +4.6349305e+00 -2.2133217e+01 -2.1750655e+01 +4.6439262e+00 -2.2123996e+01 -2.1735914e+01 +4.6529229e+00 -2.2114147e+01 -2.1720858e+01 +4.6619262e+00 -2.2103518e+01 -2.1705468e+01 +4.6709228e+00 -2.2092030e+01 -2.1689732e+01 +4.6799182e+00 -2.2079835e+01 -2.1673849e+01 +4.6889179e+00 -2.2066619e+01 -2.1657459e+01 +4.6979090e+00 -2.2052522e+01 -2.1640753e+01 +4.7069140e+00 -2.2034963e+01 -2.1622767e+01 +4.7159115e+00 -2.2018172e+01 -2.1605128e+01 +4.7249064e+00 -2.2001183e+01 -2.1587472e+01 +4.7339031e+00 -2.1983301e+01 -2.1569538e+01 +4.7428979e+00 -2.1964530e+01 -2.1551340e+01 +4.7518947e+00 -2.1944966e+01 -2.1532940e+01 +4.7608972e+00 -2.1924599e+01 -2.1514293e+01 +4.7698940e+00 -2.1903368e+01 -2.1495407e+01 +4.7788889e+00 -2.1881636e+01 -2.1476423e+01 +4.7878854e+00 -2.1859461e+01 -2.1457336e+01 +4.7968864e+00 -2.1836928e+01 -2.1438231e+01 +4.8058813e+00 -2.1814005e+01 -2.1419440e+01 +4.8148799e+00 -2.1791021e+01 -2.1400455e+01 +4.8238783e+00 -2.1768097e+01 -2.1381638e+01 +4.8328728e+00 -2.1745404e+01 -2.1363071e+01 +4.8418723e+00 -2.1723492e+01 -2.1345025e+01 +4.8508667e+00 -2.1702480e+01 -2.1327560e+01 +4.8598645e+00 -2.1682564e+01 -2.1310789e+01 +4.8688618e+00 -2.1664301e+01 -2.1294975e+01 +4.8778607e+00 -2.1646892e+01 -2.1279708e+01 +4.8868572e+00 -2.1630933e+01 -2.1265296e+01 +4.8958533e+00 -2.1616400e+01 -2.1251711e+01 +4.9048507e+00 -2.1603260e+01 -2.1238922e+01 +4.9138509e+00 -2.1591573e+01 -2.1226923e+01 +4.9228448e+00 -2.1581053e+01 -2.1215682e+01 +4.9318442e+00 -2.1571703e+01 -2.1205205e+01 +4.9408401e+00 -2.1563456e+01 -2.1195499e+01 +4.9498387e+00 -2.1556236e+01 -2.1186613e+01 +4.9588361e+00 -2.1550013e+01 -2.1178585e+01 +4.9678334e+00 -2.1544759e+01 -2.1171495e+01 +4.9768312e+00 -2.1540487e+01 -2.1165427e+01 +4.9858305e+00 -2.1537153e+01 -2.1160434e+01 +4.9948273e+00 -2.1534781e+01 -2.1156593e+01 +5.0038051e+00 -2.1533607e+01 -2.1154226e+01 +5.0128372e+00 -2.1533088e+01 -2.1152717e+01 +5.0218093e+00 -2.1534558e+01 -2.1152748e+01 +5.0308020e+00 -2.1535898e+01 -2.1153484e+01 +5.0398106e+00 -2.1537452e+01 -2.1154747e+01 +5.0487913e+00 -2.1539343e+01 -2.1156512e+01 +5.0578182e+00 -2.1541241e+01 -2.1158515e+01 +5.0668103e+00 -2.1543163e+01 -2.1160491e+01 +5.0758024e+00 -2.1544820e+01 -2.1162191e+01 +5.0847907e+00 -2.1546070e+01 -2.1163423e+01 +5.0938068e+00 -2.1546789e+01 -2.1164031e+01 +5.1028109e+00 -2.1546911e+01 -2.1163929e+01 +5.1118000e+00 -2.1546422e+01 -2.1163075e+01 +5.1208042e+00 -2.1545308e+01 -2.1161472e+01 +5.1297865e+00 -2.1543619e+01 -2.1159167e+01 +5.1387762e+00 -2.1541407e+01 -2.1156232e+01 +5.1477690e+00 -2.1538757e+01 -2.1152761e+01 +5.1567914e+00 -2.1535734e+01 -2.1148840e+01 +5.1657783e+00 -2.1532436e+01 -2.1144596e+01 +5.1747864e+00 -2.1528958e+01 -2.1140123e+01 +5.1837822e+00 -2.1525361e+01 -2.1135530e+01 +5.1927625e+00 -2.1521765e+01 -2.1130933e+01 +5.2017521e+00 -2.1518214e+01 -2.1126412e+01 +5.2107732e+00 -2.1514804e+01 -2.1122059e+01 +5.2197678e+00 -2.1511576e+01 -2.1117937e+01 +5.2287596e+00 -2.1508582e+01 -2.1114091e+01 +5.2377448e+00 -2.1505818e+01 -2.1110536e+01 +5.2467447e+00 -2.1503306e+01 -2.1107266e+01 +5.2557548e+00 -2.1501001e+01 -2.1104241e+01 +5.2647470e+00 -2.1498872e+01 -2.1101412e+01 +5.2737418e+00 -2.1496863e+01 -2.1098705e+01 +5.2827354e+00 -2.1498982e+01 -2.1097671e+01 +5.2917461e+00 -2.1496809e+01 -2.1094911e+01 +5.3007259e+00 -2.1494606e+01 -2.1092116e+01 +5.3097366e+00 -2.1492414e+01 -2.1089296e+01 +5.3187310e+00 -2.1490273e+01 -2.1086483e+01 +5.3277267e+00 -2.1488237e+01 -2.1083751e+01 +5.3367198e+00 -2.1486370e+01 -2.1081205e+01 +5.3457265e+00 -2.1484802e+01 -2.1078969e+01 +5.3547229e+00 -2.1483663e+01 -2.1077223e+01 +5.3637248e+00 -2.1483108e+01 -2.1076160e+01 +5.3727095e+00 -2.1483332e+01 -2.1076016e+01 +5.3817106e+00 -2.1484550e+01 -2.1077057e+01 +5.3907055e+00 -2.1486982e+01 -2.1079558e+01 +5.3997083e+00 -2.1490878e+01 -2.1083825e+01 +5.4086978e+00 -2.1496482e+01 -2.1090145e+01 +5.4177041e+00 -2.1503970e+01 -2.1098787e+01 +5.4267064e+00 -2.1513499e+01 -2.1109954e+01 +5.4357011e+00 -2.1525158e+01 -2.1123759e+01 +5.4447004e+00 -2.1538907e+01 -2.1140219e+01 +5.4536852e+00 -2.1554614e+01 -2.1159198e+01 +5.4626824e+00 -2.1572011e+01 -2.1180423e+01 +5.4716877e+00 -2.1590743e+01 -2.1203502e+01 +5.4806823e+00 -2.1610409e+01 -2.1227935e+01 +5.4896773e+00 -2.1630580e+01 -2.1253187e+01 +5.4986826e+00 -2.1650781e+01 -2.1278692e+01 +5.5076805e+00 -2.1670643e+01 -2.1303932e+01 +5.5166676e+00 -2.1689838e+01 -2.1328438e+01 +5.5256666e+00 -2.1708121e+01 -2.1351845e+01 +5.5346733e+00 -2.1725311e+01 -2.1373855e+01 +5.5436708e+00 -2.1741315e+01 -2.1394286e+01 +5.5526682e+00 -2.1756218e+01 -2.1413087e+01 +5.5616618e+00 -2.1769628e+01 -2.1429994e+01 +5.5706597e+00 -2.1782095e+01 -2.1445293e+01 +5.5796579e+00 -2.1793147e+01 -2.1458745e+01 +5.5886526e+00 -2.1803382e+01 -2.1470685e+01 +5.5976513e+00 -2.1812479e+01 -2.1481013e+01 +5.6066500e+00 -2.1820879e+01 -2.1490018e+01 +5.6156450e+00 -2.1828303e+01 -2.1497641e+01 +5.6246430e+00 -2.1835053e+01 -2.1504151e+01 +5.6336401e+00 -2.1841065e+01 -2.1509592e+01 +5.6426327e+00 -2.1846551e+01 -2.1514165e+01 +5.6516365e+00 -2.1851613e+01 -2.1518013e+01 +5.6606284e+00 -2.1856298e+01 -2.1521289e+01 +5.6696237e+00 -2.1860751e+01 -2.1524155e+01 +5.6786276e+00 -2.1865090e+01 -2.1526747e+01 +5.6876181e+00 -2.1869377e+01 -2.1529251e+01 +5.6966185e+00 -2.1873739e+01 -2.1531830e+01 +5.7056157e+00 -2.1879294e+01 -2.1535108e+01 +5.7146147e+00 -2.1885389e+01 -2.1538862e+01 +5.7236116e+00 -2.1891841e+01 -2.1543179e+01 +5.7326109e+00 -2.1898769e+01 -2.1548214e+01 +5.7416085e+00 -2.1906333e+01 -2.1554085e+01 +5.7506010e+00 -2.1914531e+01 -2.1560888e+01 +5.7595999e+00 -2.1923396e+01 -2.1568652e+01 +5.7686011e+00 -2.1932929e+01 -2.1577312e+01 +5.7775935e+00 -2.1943019e+01 -2.1586751e+01 +5.7865953e+00 -2.1953544e+01 -2.1596742e+01 +5.7955881e+00 -2.1964250e+01 -2.1607004e+01 +5.8045892e+00 -2.1974899e+01 -2.1617191e+01 +5.8135877e+00 -2.1985311e+01 -2.1627033e+01 +5.8225799e+00 -2.1995249e+01 -2.1636256e+01 +5.8315819e+00 -2.2004571e+01 -2.1644625e+01 +5.8405765e+00 -2.2013197e+01 -2.1652007e+01 +5.8495730e+00 -2.2021062e+01 -2.1658368e+01 +5.8585733e+00 -2.2028172e+01 -2.1663700e+01 +5.8675677e+00 -2.2034573e+01 -2.1668107e+01 +5.8765642e+00 -2.2040324e+01 -2.1671661e+01 +5.8855647e+00 -2.2045526e+01 -2.1674525e+01 +5.8945597e+00 -2.2050278e+01 -2.1676851e+01 +5.9035566e+00 -2.2054689e+01 -2.1678754e+01 +5.9125568e+00 -2.2058861e+01 -2.1680436e+01 +5.9215512e+00 -2.2062909e+01 -2.1681999e+01 +5.9305517e+00 -2.2066923e+01 -2.1683589e+01 +5.9395492e+00 -2.2071005e+01 -2.1685332e+01 +5.9485450e+00 -2.2075230e+01 -2.1687336e+01 +5.9575450e+00 -2.2079673e+01 -2.1689689e+01 +5.9665406e+00 -2.2084379e+01 -2.1692440e+01 +5.9755375e+00 -2.2089386e+01 -2.1695617e+01 +5.9845363e+00 -2.2094701e+01 -2.1699252e+01 +5.9935332e+00 -2.2100305e+01 -2.1703313e+01 +6.0025116e+00 -2.2106177e+01 -2.1707744e+01 +6.0115282e+00 -2.2112265e+01 -2.1712489e+01 +6.0205270e+00 -2.2118518e+01 -2.1717469e+01 +6.0295055e+00 -2.2124158e+01 -2.1722368e+01 +6.0385009e+00 -2.2131009e+01 -2.1727787e+01 +6.0475085e+00 -2.2137773e+01 -2.1733228e+01 +6.0565237e+00 -2.2144463e+01 -2.1738666e+01 +6.0655050e+00 -2.2151054e+01 -2.1744028e+01 +6.0745239e+00 -2.2157541e+01 -2.1749336e+01 +6.0835026e+00 -2.2163936e+01 -2.1754586e+01 +6.0925101e+00 -2.2170253e+01 -2.1759825e+01 +6.1015065e+00 -2.2176513e+01 -2.1765027e+01 +6.1104887e+00 -2.2182745e+01 -2.1770267e+01 +6.1194868e+00 -2.2188995e+01 -2.1775571e+01 +6.1284962e+00 -2.2195309e+01 -2.1781018e+01 +6.1374807e+00 -2.2201736e+01 -2.1786668e+01 +6.1465001e+00 -2.2208344e+01 -2.1792554e+01 +6.1554879e+00 -2.2215190e+01 -2.1798794e+01 +6.1644718e+00 -2.2222334e+01 -2.1805458e+01 +6.1734776e+00 -2.2229848e+01 -2.1812592e+01 +6.1824717e+00 -2.2237802e+01 -2.1820305e+01 +6.1914790e+00 -2.2246241e+01 -2.1828654e+01 +6.2004674e+00 -2.2255230e+01 -2.1837704e+01 +6.2094614e+00 -2.2264824e+01 -2.1847498e+01 +6.2184567e+00 -2.2275061e+01 -2.1858143e+01 +6.2274753e+00 -2.2285972e+01 -2.1869666e+01 +6.2364617e+00 -2.2297595e+01 -2.1882099e+01 +6.2454633e+00 -2.2309955e+01 -2.1895479e+01 +6.2544514e+00 -2.2323050e+01 -2.1909777e+01 +6.2634467e+00 -2.2336884e+01 -2.1925110e+01 +6.2724450e+00 -2.2351425e+01 -2.1941346e+01 +6.2814425e+00 -2.2366622e+01 -2.1958489e+01 +6.2904353e+00 -2.2382413e+01 -2.1976459e+01 +6.2994419e+00 -2.2398712e+01 -2.1995163e+01 +6.3084363e+00 -2.2415409e+01 -2.2014520e+01 +6.3174365e+00 -2.2432409e+01 -2.2034361e+01 +6.3264383e+00 -2.2449576e+01 -2.2054571e+01 +6.3354378e+00 -2.2466813e+01 -2.2075023e+01 +6.3444316e+00 -2.2484020e+01 -2.2095609e+01 +6.3534353e+00 -2.2501097e+01 -2.2116219e+01 +6.3624259e+00 -2.2518070e+01 -2.2136820e+01 +6.3714189e+00 -2.2534573e+01 -2.2157197e+01 +6.3804102e+00 -2.2551186e+01 -2.2177551e+01 +6.3894142e+00 -2.2567223e+01 -2.2197548e+01 +6.3984088e+00 -2.2582944e+01 -2.2217226e+01 +6.4074079e+00 -2.2598117e+01 -2.2236467e+01 +6.4164075e+00 -2.2612806e+01 -2.2255191e+01 +6.4254038e+00 -2.2626922e+01 -2.2273322e+01 +6.4344092e+00 -2.2640430e+01 -2.2290798e+01 +6.4434038e+00 -2.2653315e+01 -2.2307567e+01 +6.4523998e+00 -2.2665566e+01 -2.2323608e+01 +6.4613935e+00 -2.2677161e+01 -2.2338898e+01 +6.4703958e+00 -2.2688077e+01 -2.2353390e+01 +6.4793881e+00 -2.2698406e+01 -2.2367168e+01 +6.4883815e+00 -2.2708121e+01 -2.2380218e+01 +6.4973858e+00 -2.2717242e+01 -2.2392524e+01 +6.5063833e+00 -2.2725796e+01 -2.2404129e+01 +6.5153837e+00 -2.2733792e+01 -2.2415047e+01 +6.5243831e+00 -2.2741291e+01 -2.2425309e+01 +6.5333780e+00 -2.2748240e+01 -2.2434884e+01 +6.5423772e+00 -2.2754685e+01 -2.2443806e+01 +6.5513646e+00 -2.2760625e+01 -2.2452102e+01 +6.5603610e+00 -2.2766091e+01 -2.2459733e+01 +6.5693622e+00 -2.2770958e+01 -2.2466686e+01 +6.5783641e+00 -2.2775622e+01 -2.2473170e+01 +6.5873629e+00 -2.2779604e+01 -2.2478940e+01 +6.5963551e+00 -2.2783412e+01 -2.2484232e+01 +6.6053482e+00 -2.2786535e+01 -2.2488839e+01 +6.6143487e+00 -2.2789521e+01 -2.2493049e+01 +6.6233527e+00 -2.2791908e+01 -2.2496632e+01 +6.6323459e+00 -2.2794173e+01 -2.2499805e+01 +6.6413452e+00 -2.2795826e+01 -2.2502448e+01 +6.6503367e+00 -2.2797430e+01 -2.2504789e+01 +6.6593361e+00 -2.2798521e+01 -2.2506626e+01 +6.6683300e+00 -2.2799560e+01 -2.2508190e+01 +6.6773332e+00 -2.2800135e+01 -2.2509311e+01 +6.6863323e+00 -2.2800656e+01 -2.2510224e+01 +6.6953240e+00 -2.2800739e+01 -2.2510717e+01 +6.7043221e+00 -2.2800848e+01 -2.2511055e+01 +6.7133225e+00 -2.2800547e+01 -2.2511055e+01 +6.7223212e+00 -2.2800245e+01 -2.2510900e+01 +6.7313147e+00 -2.2799587e+01 -2.2510463e+01 +6.7403153e+00 -2.2798958e+01 -2.2509901e+01 +6.7493111e+00 -2.2798057e+01 -2.2509143e+01 +6.7583062e+00 -2.2797212e+01 -2.2508372e+01 +6.7673043e+00 -2.2796097e+01 -2.2507407e+01 +6.7763016e+00 -2.2795039e+01 -2.2506430e+01 +6.7853014e+00 -2.2793795e+01 -2.2505345e+01 +6.7942998e+00 -2.2792635e+01 -2.2504289e+01 +6.8032932e+00 -2.2791290e+01 -2.2503167e+01 +6.8122915e+00 -2.2790083e+01 -2.2502158e+01 +6.8212908e+00 -2.2788719e+01 -2.2501097e+01 +6.8302871e+00 -2.2787546e+01 -2.2500230e+01 +6.8392832e+00 -2.2786217e+01 -2.2499297e+01 +6.8482815e+00 -2.2785024e+01 -2.2498544e+01 +6.8572782e+00 -2.2783755e+01 -2.2497819e+01 +6.8662755e+00 -2.2782648e+01 -2.2497259e+01 +6.8752755e+00 -2.2781464e+01 -2.2496754e+01 +6.8842685e+00 -2.2780442e+01 -2.2496454e+01 +6.8932678e+00 -2.2779343e+01 -2.2496209e+01 +6.9022640e+00 -2.2778429e+01 -2.2496168e+01 +6.9112642e+00 -2.2777466e+01 -2.2496209e+01 +6.9202590e+00 -2.2776660e+01 -2.2496468e+01 +6.9292554e+00 -2.2775830e+01 -2.2496795e+01 +6.9382545e+00 -2.2775105e+01 -2.2497341e+01 +6.9472523e+00 -2.2774536e+01 -2.2498092e+01 +6.9562501e+00 -2.2773942e+01 -2.2498982e+01 +6.9652488e+00 -2.2773503e+01 -2.2500107e+01 +6.9742445e+00 -2.2773220e+01 -2.2501496e+01 +6.9832428e+00 -2.2772936e+01 -2.2503070e+01 +6.9922397e+00 -2.2772833e+01 -2.2504955e+01 +7.0012576e+00 -2.2772911e+01 -2.2507128e+01 +7.0102151e+00 -2.2773065e+01 -2.2509578e+01 +7.0192410e+00 -2.2773400e+01 -2.2512381e+01 +7.0282458e+00 -2.2773942e+01 -2.2515586e+01 +7.0372272e+00 -2.2774613e+01 -2.2519117e+01 +7.0462219e+00 -2.2775467e+01 -2.2523024e+01 +7.0552254e+00 -2.2776504e+01 -2.2527331e+01 +7.0642333e+00 -2.2777700e+01 -2.2532022e+01 +7.0732050e+00 -2.2779056e+01 -2.2537093e+01 +7.0822107e+00 -2.2780546e+01 -2.2542527e+01 +7.0912096e+00 -2.2782200e+01 -2.2548352e+01 +7.1001982e+00 -2.2783940e+01 -2.2554458e+01 +7.1092072e+00 -2.2785739e+01 -2.2560825e+01 +7.1181986e+00 -2.2787653e+01 -2.2567480e+01 +7.1272020e+00 -2.2789521e+01 -2.2574254e+01 +7.1361813e+00 -2.2791397e+01 -2.2581169e+01 +7.1451964e+00 -2.2793255e+01 -2.2588162e+01 +7.1541804e+00 -2.2795012e+01 -2.2595115e+01 +7.1631912e+00 -2.2796695e+01 -2.2602043e+01 +7.1721941e+00 -2.2798193e+01 -2.2608835e+01 +7.1811859e+00 -2.2799587e+01 -2.2615503e+01 +7.1901916e+00 -2.2800821e+01 -2.2621929e+01 +7.1991790e+00 -2.2801838e+01 -2.2628120e+01 +7.2081725e+00 -2.2802664e+01 -2.2634044e+01 +7.2171680e+00 -2.2803299e+01 -2.2639653e+01 +7.2261615e+00 -2.2803713e+01 -2.2644932e+01 +7.2351748e+00 -2.2803934e+01 -2.2649868e+01 +7.2441534e+00 -2.2803934e+01 -2.2654430e+01 +7.2531683e+00 -2.2803741e+01 -2.2658684e+01 +7.2621662e+00 -2.2803354e+01 -2.2662581e+01 +7.2711676e+00 -2.2802775e+01 -2.2666150e+01 +7.2801457e+00 -2.2802031e+01 -2.2669404e+01 +7.2891428e+00 -2.2800986e+01 -2.2672008e+01 +7.2981542e+00 -2.2799861e+01 -2.2674566e+01 +7.3071536e+00 -2.2798603e+01 -2.2676830e+01 +7.3161382e+00 -2.2796885e+01 -2.2678029e+01 +7.3251461e+00 -2.2795283e+01 -2.2679604e+01 +7.3341319e+00 -2.2793552e+01 -2.2680915e+01 +7.3431328e+00 -2.2791693e+01 -2.2681958e+01 +7.3521246e+00 -2.2789708e+01 -2.2682752e+01 +7.3611232e+00 -2.2787626e+01 -2.2683317e+01 +7.3701243e+00 -2.2785421e+01 -2.2683631e+01 +7.3791241e+00 -2.2783096e+01 -2.2683757e+01 +7.3881190e+00 -2.2780704e+01 -2.2683652e+01 +7.3971228e+00 -2.2778221e+01 -2.2683380e+01 +7.4061142e+00 -2.2775622e+01 -2.2682898e+01 +7.4151070e+00 -2.2772936e+01 -2.2682166e+01 +7.4241136e+00 -2.2770190e+01 -2.2681332e+01 +7.4331135e+00 -2.2767385e+01 -2.2680353e+01 +7.4421033e+00 -2.2764497e+01 -2.2679210e+01 +7.4511107e+00 -2.2761552e+01 -2.2677925e+01 +7.4601007e+00 -2.2758553e+01 -2.2676500e+01 +7.4691000e+00 -2.2755451e+01 -2.2674957e+01 +7.4780901e+00 -2.2752346e+01 -2.2673275e+01 +7.4870959e+00 -2.2749165e+01 -2.2671478e+01 +7.4960851e+00 -2.2745960e+01 -2.2669566e+01 +7.5050821e+00 -2.2742681e+01 -2.2667562e+01 +7.5140827e+00 -2.2739356e+01 -2.2665446e+01 +7.5230828e+00 -2.2736009e+01 -2.2663220e+01 +7.5320789e+00 -2.2732594e+01 -2.2660926e+01 +7.5410798e+00 -2.2729181e+01 -2.2658526e+01 +7.5500815e+00 -2.2725680e+01 -2.2656060e+01 +7.5590683e+00 -2.2722207e+01 -2.2653510e+01 +7.5680726e+00 -2.2718648e+01 -2.2650878e+01 +7.5770665e+00 -2.2715096e+01 -2.2648165e+01 +7.5860694e+00 -2.2711482e+01 -2.2645411e+01 +7.5950661e+00 -2.2707855e+01 -2.2642580e+01 +7.6040640e+00 -2.2704191e+01 -2.2639691e+01 +7.6130592e+00 -2.2700536e+01 -2.2636745e+01 +7.6220585e+00 -2.2696804e+01 -2.2633745e+01 +7.6310479e+00 -2.2693103e+01 -2.2630691e+01 +7.6400539e+00 -2.2689349e+01 -2.2627604e+01 +7.6490426e+00 -2.2685585e+01 -2.2624464e+01 +7.6580400e+00 -2.2681791e+01 -2.2621275e+01 +7.6670418e+00 -2.2678008e+01 -2.2618037e+01 +7.6760348e+00 -2.2674156e+01 -2.2614769e+01 +7.6850338e+00 -2.2670338e+01 -2.2611473e+01 +7.6940345e+00 -2.2666472e+01 -2.2608130e+01 +7.7030333e+00 -2.2662621e+01 -2.2604778e+01 +7.7120264e+00 -2.2658724e+01 -2.2601366e+01 +7.7210270e+00 -2.2654842e+01 -2.2597962e+01 +7.7300228e+00 -2.2650936e+01 -2.2594500e+01 +7.7390182e+00 -2.2647027e+01 -2.2591048e+01 +7.7480174e+00 -2.2643095e+01 -2.2587556e+01 +7.7570162e+00 -2.2639179e+01 -2.2584059e+01 +7.7660111e+00 -2.2635224e+01 -2.2580523e+01 +7.7750130e+00 -2.2631267e+01 -2.2576984e+01 +7.7840036e+00 -2.2627309e+01 -2.2573424e+01 +7.7930007e+00 -2.2623350e+01 -2.2569877e+01 +7.8020002e+00 -2.2619373e+01 -2.2566294e+01 +7.8109982e+00 -2.2615396e+01 -2.2562709e+01 +7.8199977e+00 -2.2611402e+01 -2.2559107e+01 +7.8289948e+00 -2.2607426e+01 -2.2555502e+01 +7.8379922e+00 -2.2603417e+01 -2.2551881e+01 +7.8469862e+00 -2.2599427e+01 -2.2548275e+01 +7.8559853e+00 -2.2595423e+01 -2.2544637e+01 +7.8649855e+00 -2.2591421e+01 -2.2541015e+01 +7.8739829e+00 -2.2587388e+01 -2.2537362e+01 +7.8829797e+00 -2.2583376e+01 -2.2533726e+01 +7.8919777e+00 -2.2579351e+01 -2.2530075e+01 +7.9009731e+00 -2.2575347e+01 -2.2526425e+01 +7.9099677e+00 -2.2571315e+01 -2.2522777e+01 +7.9189682e+00 -2.2567287e+01 -2.2519117e+01 +7.9279654e+00 -2.2563249e+01 -2.2515444e+01 +7.9369609e+00 -2.2559217e+01 -2.2511788e+01 +7.9459607e+00 -2.2555159e+01 -2.2508120e+01 +7.9549561e+00 -2.2551124e+01 -2.2504456e+01 +7.9639530e+00 -2.2547079e+01 -2.2500780e+01 +7.9729523e+00 -2.2543027e+01 -2.2497109e+01 +7.9819499e+00 -2.2538967e+01 -2.2493427e+01 +7.9909468e+00 -2.2534930e+01 -2.2489737e+01 +7.9999435e+00 -2.2530856e+01 -2.2486050e+01 +8.0089407e+00 -2.2526805e+01 -2.2482356e+01 +8.0179511e+00 -2.2522734e+01 -2.2478653e+01 +8.0269416e+00 -2.2518658e+01 -2.2474955e+01 +8.0359498e+00 -2.2514577e+01 -2.2471238e+01 +8.0449315e+00 -2.2510506e+01 -2.2467526e+01 +8.0539232e+00 -2.2506416e+01 -2.2463795e+01 +8.0629203e+00 -2.2502324e+01 -2.2460071e+01 +8.0719188e+00 -2.2498243e+01 -2.2456329e+01 +8.0809150e+00 -2.2494145e+01 -2.2452582e+01 +8.0899051e+00 -2.2490045e+01 -2.2448831e+01 +8.0989205e+00 -2.2485931e+01 -2.2445063e+01 +8.1079219e+00 -2.2481828e+01 -2.2441291e+01 +8.1169065e+00 -2.2477712e+01 -2.2437517e+01 +8.1259040e+00 -2.2473583e+01 -2.2433716e+01 +8.1349099e+00 -2.2469467e+01 -2.2429924e+01 +8.1438888e+00 -2.2465327e+01 -2.2426108e+01 +8.1528996e+00 -2.2461188e+01 -2.2422290e+01 +8.1619068e+00 -2.2457050e+01 -2.2418460e+01 +8.1709068e+00 -2.2452902e+01 -2.2414618e+01 +8.1798963e+00 -2.2448745e+01 -2.2410766e+01 +8.1889004e+00 -2.2444579e+01 -2.2406902e+01 +8.1978868e+00 -2.2440417e+01 -2.2403029e+01 +8.2068798e+00 -2.2436246e+01 -2.2399136e+01 +8.2158754e+00 -2.2432068e+01 -2.2395245e+01 +8.2248696e+00 -2.2427884e+01 -2.2391335e+01 +8.2338841e+00 -2.2423693e+01 -2.2387407e+01 +8.2428643e+00 -2.2419497e+01 -2.2383482e+01 +8.2518571e+00 -2.2415296e+01 -2.2379531e+01 +8.2608581e+00 -2.2411090e+01 -2.2375574e+01 +8.2698630e+00 -2.2406869e+01 -2.2371611e+01 +8.2788679e+00 -2.2402645e+01 -2.2367624e+01 +8.2878689e+00 -2.2398418e+01 -2.2363632e+01 +8.2968626e+00 -2.2394189e+01 -2.2359628e+01 +8.3058456e+00 -2.2389947e+01 -2.2355621e+01 +8.3148570e+00 -2.2385704e+01 -2.2351591e+01 +8.3238500e+00 -2.2381450e+01 -2.2347551e+01 +8.3328423e+00 -2.2377196e+01 -2.2343499e+01 +8.3418498e+00 -2.2372931e+01 -2.2339438e+01 +8.3508486e+00 -2.2368658e+01 -2.2335367e+01 +8.3598355e+00 -2.2364386e+01 -2.2331288e+01 +8.3688259e+00 -2.2360105e+01 -2.2327191e+01 +8.3778342e+00 -2.2355808e+01 -2.2323078e+01 +8.3868377e+00 -2.2351513e+01 -2.2318967e+01 +8.3958329e+00 -2.2347212e+01 -2.2314832e+01 +8.4048166e+00 -2.2342906e+01 -2.2310700e+01 +8.4138193e+00 -2.2338585e+01 -2.2306546e+01 +8.4228196e+00 -2.2334269e+01 -2.2302387e+01 +8.4318139e+00 -2.2329940e+01 -2.2298208e+01 +8.4408147e+00 -2.2325607e+01 -2.2294025e+01 +8.4498177e+00 -2.2321263e+01 -2.2289832e+01 +8.4588040e+00 -2.2316917e+01 -2.2285628e+01 +8.4678004e+00 -2.2312560e+01 -2.2281415e+01 +8.4768027e+00 -2.2308203e+01 -2.2277185e+01 +8.4858066e+00 -2.2303836e+01 -2.2272955e+01 +8.4947944e+00 -2.2299461e+01 -2.2268709e+01 +8.5037907e+00 -2.2295078e+01 -2.2264449e+01 +8.5127911e+00 -2.2290688e+01 -2.2260182e+01 +8.5217916e+00 -2.2286291e+01 -2.2255903e+01 +8.5307886e+00 -2.2281889e+01 -2.2251618e+01 +8.5397784e+00 -2.2277481e+01 -2.2247314e+01 +8.5487824e+00 -2.2273061e+01 -2.2243007e+01 +8.5577838e+00 -2.2268637e+01 -2.2238689e+01 +8.5667791e+00 -2.2264202e+01 -2.2234361e+01 +8.5757765e+00 -2.2259764e+01 -2.2230017e+01 +8.5847721e+00 -2.2255316e+01 -2.2225673e+01 +8.5937733e+00 -2.2250859e+01 -2.2221313e+01 +8.6027651e+00 -2.2246394e+01 -2.2216933e+01 +8.6117658e+00 -2.2241929e+01 -2.2212554e+01 +8.6207605e+00 -2.2237449e+01 -2.2208169e+01 +8.6297561e+00 -2.2232963e+01 -2.2203766e+01 +8.6387587e+00 -2.2228464e+01 -2.2199359e+01 +8.6477545e+00 -2.2223960e+01 -2.2194934e+01 +8.6567496e+00 -2.2219445e+01 -2.2190508e+01 +8.6657498e+00 -2.2214927e+01 -2.2186059e+01 +8.6747418e+00 -2.2210391e+01 -2.2181609e+01 +8.6837403e+00 -2.2205854e+01 -2.2177139e+01 +8.6927412e+00 -2.2201301e+01 -2.2172663e+01 +8.7017406e+00 -2.2196741e+01 -2.2168175e+01 +8.7107349e+00 -2.2192167e+01 -2.2163670e+01 +8.7197289e+00 -2.2187588e+01 -2.2159160e+01 +8.7287270e+00 -2.2182997e+01 -2.2154635e+01 +8.7377252e+00 -2.2178395e+01 -2.2150096e+01 +8.7467276e+00 -2.2173783e+01 -2.2145548e+01 +8.7557224e+00 -2.2169155e+01 -2.2140988e+01 +8.7647215e+00 -2.2164519e+01 -2.2136415e+01 +8.7737133e+00 -2.2159875e+01 -2.2131826e+01 +8.7827162e+00 -2.2155212e+01 -2.2127226e+01 +8.7917117e+00 -2.2150544e+01 -2.2122617e+01 +8.8007102e+00 -2.2145858e+01 -2.2117988e+01 +8.8097078e+00 -2.2141162e+01 -2.2113351e+01 +8.8187008e+00 -2.2136451e+01 -2.2108697e+01 +8.8276987e+00 -2.2131732e+01 -2.2104025e+01 +8.8366975e+00 -2.2126993e+01 -2.2099343e+01 +8.8456932e+00 -2.2122243e+01 -2.2094647e+01 +8.8546946e+00 -2.2117475e+01 -2.2089936e+01 +8.8636916e+00 -2.2112698e+01 -2.2085207e+01 +8.8726864e+00 -2.2107900e+01 -2.2080462e+01 +8.8816870e+00 -2.2103088e+01 -2.2075705e+01 +8.8906836e+00 -2.2098258e+01 -2.2070929e+01 +8.8996783e+00 -2.2093417e+01 -2.2066133e+01 +8.9086780e+00 -2.2088555e+01 -2.2061325e+01 +8.9176735e+00 -2.2083678e+01 -2.2056496e+01 +8.9266716e+00 -2.2078782e+01 -2.2051651e+01 +8.9356685e+00 -2.2073869e+01 -2.2046787e+01 +8.9446652e+00 -2.2068934e+01 -2.2041905e+01 +8.9536631e+00 -2.2063979e+01 -2.2037006e+01 +8.9626581e+00 -2.2059011e+01 -2.2032082e+01 +8.9716562e+00 -2.2054015e+01 -2.2027140e+01 +8.9806532e+00 -2.2049003e+01 -2.2022180e+01 +8.9896545e+00 -2.2043966e+01 -2.2017195e+01 +8.9986516e+00 -2.2038911e+01 -2.2012191e+01 +9.0076624e+00 -2.2033835e+01 -2.2007159e+01 +9.0166573e+00 -2.2028729e+01 -2.2002111e+01 +9.0256335e+00 -2.2023604e+01 -2.1997057e+01 +9.0346285e+00 -2.2018453e+01 -2.1991954e+01 +9.0436373e+00 -2.2013278e+01 -2.1986826e+01 +9.0526170e+00 -2.2008079e+01 -2.1981674e+01 +9.0616409e+00 -2.2002850e+01 -2.1976501e+01 +9.0706288e+00 -2.1997575e+01 -2.1971307e+01 +9.0796153e+00 -2.1992295e+01 -2.1966054e+01 +9.0886321e+00 -2.1986994e+01 -2.1960824e+01 +9.0976043e+00 -2.1981674e+01 -2.1955539e+01 +9.1066328e+00 -2.1976295e+01 -2.1950201e+01 +9.1156105e+00 -2.1970900e+01 -2.1944889e+01 +9.1246020e+00 -2.1965452e+01 -2.1939491e+01 +9.1336028e+00 -2.1959991e+01 -2.1934084e+01 +9.1426084e+00 -2.1954481e+01 -2.1928670e+01 +9.1516150e+00 -2.1948963e+01 -2.1923178e+01 +9.1605886e+00 -2.1943400e+01 -2.1917681e+01 +9.1695863e+00 -2.1937794e+01 -2.1912148e+01 +9.1786029e+00 -2.1932148e+01 -2.1906578e+01 +9.1876053e+00 -2.1926465e+01 -2.1900976e+01 +9.1965907e+00 -2.1920746e+01 -2.1895308e+01 +9.2055833e+00 -2.1914995e+01 -2.1889612e+01 +9.2145790e+00 -2.1909213e+01 -2.1883891e+01 +9.2235738e+00 -2.1903368e+01 -2.1878145e+01 +9.2325896e+00 -2.1897498e+01 -2.1872312e+01 +9.2415714e+00 -2.1891570e+01 -2.1866493e+01 +9.2505664e+00 -2.1885589e+01 -2.1860593e+01 +9.2595700e+00 -2.1879591e+01 -2.1854648e+01 +9.2685780e+00 -2.1873544e+01 -2.1848661e+01 +9.2775634e+00 -2.1867420e+01 -2.1842634e+01 +9.2865687e+00 -2.1861255e+01 -2.1836570e+01 +9.2955671e+00 -2.1855052e+01 -2.1830443e+01 +9.3045552e+00 -2.1848814e+01 -2.1824256e+01 +9.3135509e+00 -2.1842483e+01 -2.1818042e+01 +9.3225501e+00 -2.1836123e+01 -2.1811775e+01 +9.3315488e+00 -2.1829709e+01 -2.1805430e+01 +9.3405433e+00 -2.1823243e+01 -2.1799040e+01 +9.3495495e+00 -2.1816702e+01 -2.1792608e+01 +9.3585440e+00 -2.1810117e+01 -2.1786111e+01 +9.3675423e+00 -2.1803465e+01 -2.1779578e+01 +9.3765405e+00 -2.1796777e+01 -2.1772962e+01 +9.3855348e+00 -2.1790003e+01 -2.1766293e+01 +9.3945392e+00 -2.1783148e+01 -2.1759551e+01 +9.4035323e+00 -2.1776270e+01 -2.1752763e+01 +9.4125277e+00 -2.1769296e+01 -2.1745887e+01 +9.4215217e+00 -2.1762255e+01 -2.1738975e+01 +9.4305265e+00 -2.1755179e+01 -2.1731984e+01 +9.4395222e+00 -2.1747997e+01 -2.1724919e+01 +9.4485208e+00 -2.1740765e+01 -2.1717809e+01 +9.4575186e+00 -2.1733439e+01 -2.1710612e+01 +9.4665117e+00 -2.1726073e+01 -2.1703335e+01 +9.4755114e+00 -2.1718603e+01 -2.1695984e+01 +9.4845134e+00 -2.1711080e+01 -2.1688585e+01 +9.4934999e+00 -2.1703466e+01 -2.1681081e+01 +9.5024954e+00 -2.1695768e+01 -2.1673521e+01 +9.5114957e+00 -2.1687992e+01 -2.1665868e+01 +9.5204966e+00 -2.1680145e+01 -2.1658150e+01 +9.5294945e+00 -2.1672212e+01 -2.1650353e+01 +9.5384858e+00 -2.1664201e+01 -2.1642465e+01 +9.5474916e+00 -2.1656099e+01 -2.1634493e+01 +9.5564834e+00 -2.1647914e+01 -2.1626444e+01 +9.5654818e+00 -2.1639634e+01 -2.1618307e+01 +9.5744827e+00 -2.1631267e+01 -2.1610090e+01 +9.5834822e+00 -2.1622821e+01 -2.1601782e+01 +9.5924766e+00 -2.1614287e+01 -2.1593375e+01 +9.6014733e+00 -2.1605653e+01 -2.1584893e+01 +9.6104685e+00 -2.1596931e+01 -2.1576312e+01 +9.6194690e+00 -2.1588111e+01 -2.1567640e+01 +9.6284605e+00 -2.1579186e+01 -2.1558871e+01 +9.6374597e+00 -2.1570183e+01 -2.1550013e+01 +9.6464625e+00 -2.1561078e+01 -2.1541060e+01 +9.6554554e+00 -2.1551881e+01 -2.1532007e+01 +9.6644539e+00 -2.1542572e+01 -2.1522864e+01 +9.6734541e+00 -2.1533177e+01 -2.1513612e+01 +9.6824520e+00 -2.1523676e+01 -2.1504275e+01 +9.6914440e+00 -2.1514066e+01 -2.1494823e+01 +9.7004441e+00 -2.1504372e+01 -2.1485280e+01 +9.7094396e+00 -2.1494565e+01 -2.1475643e+01 +9.7184353e+00 -2.1484656e+01 -2.1465898e+01 +9.7274355e+00 -2.1474644e+01 -2.1456044e+01 +9.7364363e+00 -2.1464529e+01 -2.1446093e+01 +9.7454340e+00 -2.1454309e+01 -2.1436045e+01 +9.7544248e+00 -2.1443987e+01 -2.1425876e+01 +9.7634280e+00 -2.1433563e+01 -2.1415612e+01 +9.7724244e+00 -2.1423026e+01 -2.1405254e+01 +9.7814179e+00 -2.1412390e+01 -2.1394770e+01 +9.7904189e+00 -2.1401636e+01 -2.1384197e+01 +9.7994164e+00 -2.1390790e+01 -2.1373506e+01 +9.8084136e+00 -2.1379822e+01 -2.1362720e+01 +9.8174067e+00 -2.1368759e+01 -2.1351816e+01 +9.8264053e+00 -2.1357585e+01 -2.1340807e+01 +9.8354052e+00 -2.1346305e+01 -2.1329698e+01 +9.8444026e+00 -2.1334916e+01 -2.1318478e+01 +9.8534001e+00 -2.1323425e+01 -2.1307153e+01 +9.8623938e+00 -2.1311829e+01 -2.1295721e+01 +9.8713919e+00 -2.1300119e+01 -2.1284180e+01 +9.8803905e+00 -2.1288311e+01 -2.1272540e+01 +9.8893858e+00 -2.1276388e+01 -2.1260784e+01 +9.8983851e+00 -2.1264369e+01 -2.1248929e+01 +9.9073845e+00 -2.1252239e+01 -2.1236969e+01 +9.9163802e+00 -2.1240007e+01 -2.1224900e+01 +9.9253791e+00 -2.1227671e+01 -2.1212724e+01 +9.9343772e+00 -2.1215233e+01 -2.1200453e+01 +9.9433708e+00 -2.1202691e+01 -2.1188070e+01 +9.9523710e+00 -2.1190050e+01 -2.1175588e+01 +9.9613689e+00 -2.1177302e+01 -2.1162999e+01 +9.9703655e+00 -2.1164455e+01 -2.1150310e+01 +9.9793617e+00 -2.1151509e+01 -2.1137523e+01 +9.9883583e+00 -2.1138466e+01 -2.1124632e+01 diff --git a/src/eos/adiabatic_glmmhd.hpp b/src/eos/adiabatic_glmmhd.hpp index 9add7160..a0d5d0d4 100644 --- a/src/eos/adiabatic_glmmhd.hpp +++ b/src/eos/adiabatic_glmmhd.hpp @@ -52,6 +52,98 @@ class AdiabaticGLMMHDEOS : public EquationOfState { } // + //---------------------------------------------------------------------------------------- + // \!fn Real EquationOfState::ConsToPrim(View4D cons, View4D prim, const int& k, const + // int& j, const int& i) \brief Fills an array of primitives given an array of + // conserveds, potentially updating the conserved with floors + template + KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &k, + const int &j, const int &i) const { + Real &u_d = cons(IDN, k, j, i); + Real &u_m1 = cons(IM1, k, j, i); + Real &u_m2 = cons(IM2, k, j, i); + Real &u_m3 = cons(IM3, k, j, i); + Real &u_e = cons(IEN, k, j, i); + Real &u_b1 = cons(IB1, k, j, i); + Real &u_b2 = cons(IB2, k, j, i); + Real &u_b3 = cons(IB3, k, j, i); + Real &u_psi = cons(IPS, k, j, i); + + Real &w_d = prim(IDN, k, j, i); + Real &w_vx = prim(IV1, k, j, i); + Real &w_vy = prim(IV2, k, j, i); + Real &w_vz = prim(IV3, k, j, i); + Real &w_p = prim(IPR, k, j, i); + Real &w_Bx = prim(IB1, k, j, i); + Real &w_By = prim(IB2, k, j, i); + Real &w_Bz = prim(IB3, k, j, i); + Real &w_psi = prim(IPS, k, j, i); + + // apply density floor, without changing momentum or energy + u_d = (u_d > density_floor_) ? u_d : density_floor_; + w_d = u_d; + + Real di = 1.0 / u_d; + w_vx = u_m1 * di; + w_vy = u_m2 * di; + w_vz = u_m3 * di; + + w_Bx = u_b1; + w_By = u_b2; + w_Bz = u_b3; + w_psi = u_psi; + + Real e_k = 0.5 * di * (SQR(u_m1) + SQR(u_m2) + SQR(u_m3)); + Real e_B = 0.5 * (SQR(u_b1) + SQR(u_b2) + SQR(u_b3)); + Real gm1 = gamma_ - 1.0; + w_p = gm1 * (u_e - e_k - e_B); + + // apply pressure floor, correct total energy + u_e = (w_p > pressure_floor_) ? u_e : ((pressure_floor_ / gm1) + e_k + e_B); + w_p = (w_p > pressure_floor_) ? w_p : pressure_floor_; + } + + //---------------------------------------------------------------------------------------- + // \!fn Real EquationOfState::PrimToCons(View4D prim, View4D cons, const int& k, const + // int& j, const int& i) \brief Fills an array of conserveds given an array of + // primitives, + template + KOKKOS_INLINE_FUNCTION void PrimToCons(View4D prim, View4D cons, const int &k, + const int &j, const int &i) const { + Real &u_d = cons(IDN, k, j, i); + Real &u_m1 = cons(IM1, k, j, i); + Real &u_m2 = cons(IM2, k, j, i); + Real &u_m3 = cons(IM3, k, j, i); + Real &u_e = cons(IEN, k, j, i); + Real &u_b1 = cons(IB1, k, j, i); + Real &u_b2 = cons(IB2, k, j, i); + Real &u_b3 = cons(IB3, k, j, i); + Real &u_psi = cons(IPS, k, j, i); + + const Real &w_d = prim(IDN, k, j, i); + const Real &w_vx = prim(IV1, k, j, i); + const Real &w_vy = prim(IV2, k, j, i); + const Real &w_vz = prim(IV3, k, j, i); + const Real &w_p = prim(IPR, k, j, i); + const Real &w_Bx = prim(IB1, k, j, i); + const Real &w_By = prim(IB2, k, j, i); + const Real &w_Bz = prim(IB3, k, j, i); + const Real &w_psi = prim(IPS, k, j, i); + + const Real igm1 = 1 / (gamma_ - 1.0); + u_d = w_d; + u_m1 = w_vx * w_d; + u_m2 = w_vy * w_d; + u_m3 = w_vz * w_d; + u_e = w_p * igm1 + 0.5 * w_d * (SQR(w_vx) + SQR(w_vy) + SQR(w_vz)) + + 0.5 * (SQR(w_Bx) + SQR(w_By) + SQR(w_Bz)); + + u_b1 = w_Bx; + u_b2 = w_By; + u_b3 = w_Bz; + u_psi = w_psi; + } + private: Real gamma_; // ratio of specific heats }; diff --git a/src/eos/adiabatic_hydro.hpp b/src/eos/adiabatic_hydro.hpp index 75c2744f..f4189738 100644 --- a/src/eos/adiabatic_hydro.hpp +++ b/src/eos/adiabatic_hydro.hpp @@ -7,6 +7,7 @@ // C headers // C++ headers +#include #include // std::numeric_limits // Parthenon headers @@ -41,6 +42,70 @@ class AdiabaticHydroEOS : public EquationOfState { return std::sqrt(gamma_ * prim[IPR] / prim[IDN]); } + //---------------------------------------------------------------------------------------- + // \!fn Real EquationOfState::ConsToPrim(View4D cons, View4D prim, const int& k, const + // int& j, const int& i) \brief Fills an array of primitives given an array of + // conserveds, potentially updating the conserved with floors + template + KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &k, + const int &j, const int &i) const { + Real &u_d = cons(IDN, k, j, i); + Real &u_m1 = cons(IM1, k, j, i); + Real &u_m2 = cons(IM2, k, j, i); + Real &u_m3 = cons(IM3, k, j, i); + Real &u_e = cons(IEN, k, j, i); + + Real &w_d = prim(IDN, k, j, i); + Real &w_vx = prim(IV1, k, j, i); + Real &w_vy = prim(IV2, k, j, i); + Real &w_vz = prim(IV3, k, j, i); + Real &w_p = prim(IPR, k, j, i); + + // apply density floor, without changing momentum or energy + u_d = (u_d > density_floor_) ? u_d : density_floor_; + w_d = u_d; + + Real di = 1.0 / u_d; + w_vx = u_m1 * di; + w_vy = u_m2 * di; + w_vz = u_m3 * di; + + Real e_k = 0.5 * di * (SQR(u_m1) + SQR(u_m2) + SQR(u_m3)); + Real gm1 = gamma_ - 1.0; + w_p = gm1 * (u_e - e_k); + + // apply pressure floor, correct total energy + u_e = (w_p > pressure_floor_) ? u_e : ((pressure_floor_ / gm1) + e_k); + w_p = (w_p > pressure_floor_) ? w_p : pressure_floor_; + } + + //---------------------------------------------------------------------------------------- + // \!fn Real EquationOfState::PrimToCons(View4D prim, View4D cons, const int& k, const + // int& j, const int& i) \brief Fills an array of conserveds given an array of + // primitives, + template + KOKKOS_INLINE_FUNCTION void PrimToCons(View4D prim, View4D cons, const int &k, + const int &j, const int &i) const { + Real &u_d = cons(IDN, k, j, i); + Real &u_m1 = cons(IM1, k, j, i); + Real &u_m2 = cons(IM2, k, j, i); + Real &u_m3 = cons(IM3, k, j, i); + Real &u_e = cons(IEN, k, j, i); + + const Real &w_d = prim(IDN, k, j, i); + const Real &w_vx = prim(IV1, k, j, i); + const Real &w_vy = prim(IV2, k, j, i); + const Real &w_vz = prim(IV3, k, j, i); + const Real &w_p = prim(IPR, k, j, i); + + const Real igm1 = 1 / (gamma_ - 1.0); + u_d = w_d; + u_m1 = w_vx * w_d; + u_m2 = w_vy * w_d; + u_m3 = w_vz * w_d; + u_e = w_p * igm1 + 0.5 * w_d * (SQR(w_vx) + SQR(w_vy) + SQR(w_vz)); + } + private: Real gamma_; // ratio of specific heats }; diff --git a/src/eos/eos.hpp b/src/eos/eos.hpp index abba4950..78969119 100644 --- a/src/eos/eos.hpp +++ b/src/eos/eos.hpp @@ -46,7 +46,7 @@ class EquationOfState { KOKKOS_INLINE_FUNCTION Real GetInternalEFloor() const { return internal_e_floor_; } - private: + protected: Real pressure_floor_, density_floor_, internal_e_floor_; }; diff --git a/src/hydro/hydro_driver.cpp b/src/hydro/hydro_driver.cpp index 2340586a..e8ec18db 100644 --- a/src/hydro/hydro_driver.cpp +++ b/src/hydro/hydro_driver.cpp @@ -12,6 +12,7 @@ // Parthenon headers #include "bvals/cc/bvals_cc_in_one.hpp" +#include "interface/state_descriptor.hpp" #include "interface/update.hpp" #include "parthenon/driver.hpp" #include "parthenon/package.hpp" @@ -20,6 +21,8 @@ #include "utils/partition_stl_containers.hpp" // AthenaPK headers #include "../eos/adiabatic_hydro.hpp" +#include "../pgen/cluster/agn_triggering.hpp" +#include "../pgen/cluster/magnetic_tower.hpp" #include "glmmhd/glmmhd.hpp" #include "hydro.hpp" #include "hydro_driver.hpp" @@ -90,6 +93,44 @@ TaskCollection HydroDriver::MakeTaskCollection(BlockList_t &blocks, int stage) { // required to be 1 or blocks.size() but could also only apply to a subset of blocks. auto num_task_lists_executed_independently = blocks.size(); + const int num_partitions = pmesh->DefaultNumPartitions(); + + // calculate agn triggering accretion rate + if ((stage == 1) && + hydro_pkg->AllParams().hasKey("agn_triggering_reduce_accretion_rate") && + hydro_pkg->Param("agn_triggering_reduce_accretion_rate")) { + + // need to make sure that there's only one region in order to MPI_reduce to work + TaskRegion &single_task_region = tc.AddRegion(1); + auto &tl = single_task_region[0]; + // First globally reset triggering quantities + auto prev_task = + tl.AddTask(none, cluster::AGNTriggeringResetTriggering, hydro_pkg.get()); + + // Adding one task for each partition. Given that they're all in one task list + // they'll be executed sequentially. Given that a par_reduce to a host var is + // blocking it's also save to store the variable in the Params for now. + for (int i = 0; i < num_partitions; i++) { + auto &mu0 = pmesh->mesh_data.GetOrAdd("base", i); + auto new_agn_triggering = + tl.AddTask(prev_task, cluster::AGNTriggeringReduceTriggering, mu0.get(), tm.dt); + prev_task = new_agn_triggering; + } +#ifdef MPI_PARALLEL + auto reduce_agn_triggering = + tl.AddTask(prev_task, cluster::AGNTriggeringMPIReduceTriggering, hydro_pkg.get()); + prev_task = reduce_agn_triggering; +#endif + + // Remove accreted gas + for (int i = 0; i < num_partitions; i++) { + auto &mu0 = pmesh->mesh_data.GetOrAdd("base", i); + auto new_remove_accreted_gas = + tl.AddTask(prev_task, cluster::AGNTriggeringFinalizeTriggering, mu0.get(), tm); + prev_task = new_remove_accreted_gas; + } + } + TaskRegion &async_region_1 = tc.AddRegion(num_task_lists_executed_independently); for (int i = 0; i < blocks.size(); i++) { auto &pmb = blocks[i]; @@ -104,8 +145,6 @@ TaskCollection HydroDriver::MakeTaskCollection(BlockList_t &blocks, int stage) { } } - const int num_partitions = pmesh->DefaultNumPartitions(); - // Calculate hyperbolic divergence cleaning speed // TODO(pgrete) Calculating mindx is only required after remeshing. Need to find a clean // solution for this one-off global reduction. @@ -162,6 +201,48 @@ TaskCollection HydroDriver::MakeTaskCollection(BlockList_t &blocks, int stage) { hydro_pkg.get()); } + // calculate magnetic tower scaling + if ((stage == 1) && hydro_pkg->AllParams().hasKey("magnetic_tower_power_scaling") && + hydro_pkg->Param("magnetic_tower_power_scaling")) { + const auto &magnetic_tower = + hydro_pkg->Param("magnetic_tower"); + + // need to make sure that there's only one region in order to MPI_reduce to work + TaskRegion &single_task_region = tc.AddRegion(1); + auto &tl = single_task_region[0]; + // First globally reset magnetic_tower_linear_contrib and + // magnetic_tower_quadratic_contrib + auto prev_task = + tl.AddTask(none, cluster::MagneticTowerResetPowerContribs, hydro_pkg.get()); + + // Adding one task for each partition. Given that they're all in one task list + // they'll be executed sequentially. Given that a par_reduce to a host var is + // blocking it's also save to store the variable in the Params for now. + for (int i = 0; i < num_partitions; i++) { + auto &mu0 = pmesh->mesh_data.GetOrAdd("base", i); + auto new_magnetic_tower_power_contrib = + tl.AddTask(prev_task, cluster::MagneticTowerReducePowerContribs, mu0.get(), tm); + prev_task = new_magnetic_tower_power_contrib; + } +#ifdef MPI_PARALLEL + auto reduce_magnetic_tower_power_contrib = tl.AddTask( + prev_task, + [](StateDescriptor *hydro_pkg) { + Real magnetic_tower_contribs[] = { + hydro_pkg->Param("magnetic_tower_linear_contrib"), + hydro_pkg->Param("magnetic_tower_quadratic_contrib")}; + PARTHENON_MPI_CHECK(MPI_Allreduce(MPI_IN_PLACE, magnetic_tower_contribs, 2, + MPI_PARTHENON_REAL, MPI_MAX, MPI_COMM_WORLD)); + hydro_pkg->UpdateParam("magnetic_tower_linear_contrib", + magnetic_tower_contribs[0]); + hydro_pkg->UpdateParam("magnetic_tower_quadratic_contrib", + magnetic_tower_contribs[1]); + return TaskStatus::complete; + }, + hydro_pkg.get()); +#endif + } + // First add split sources before the main time integration if (stage == 1) { TaskRegion &strang_init_region = tc.AddRegion(num_partitions); diff --git a/src/hydro/srcterms/gravitational_field.hpp b/src/hydro/srcterms/gravitational_field.hpp index 02d26e8c..0b0796f8 100644 --- a/src/hydro/srcterms/gravitational_field.hpp +++ b/src/hydro/srcterms/gravitational_field.hpp @@ -53,13 +53,10 @@ void GravitationalFieldSrcTerm(parthenon::MeshData *md, // Apply g_r as a source term const Real den = prim(IDN, k, j, i); - const Real src = - (r == 0) ? 0 - : beta_dt * den * g_r / r; // FIXME watch out for previous /r errors + const Real src = (r == 0) ? 0 : beta_dt * den * g_r / r; cons(IM1, k, j, i) -= src * coords.x1v(i); cons(IM2, k, j, i) -= src * coords.x2v(j); cons(IM3, k, j, i) -= src * coords.x3v(k); - // FIXME Double check this cons(IEN, k, j, i) -= src * (coords.x1v(i) * prim(IV1, k, j, i) + coords.x2v(j) * prim(IV2, k, j, i) + coords.x3v(k) * prim(IV3, k, j, i)); diff --git a/src/hydro/srcterms/tabular_cooling.hpp b/src/hydro/srcterms/tabular_cooling.hpp index 89da332f..d715039d 100644 --- a/src/hydro/srcterms/tabular_cooling.hpp +++ b/src/hydro/srcterms/tabular_cooling.hpp @@ -142,14 +142,15 @@ class TabularCooling { if (log_temp < log_temp_start) { // Below table - return 0 or first entry? // log_lambda = log_lambdas(0); - // TODO(forrestglines):Currently, no cooling is implemented above the - // table. This behavior could be generalized via templates + // TODO(forrestglines):Currently, zero cooling is use for temperatures + // below the table. This behavior could be generalized via templates return 0; } else if (log_temp > log_temp_final) { // Above table // Return de/dt - // TODO(forrestglines):Currently free-free cooling is implemented above the - // table. This behavior could be generalized via templates + // TODO(forrestglines):Currently free-free cooling is used for + // temperatures above the table. This behavior could be generalized via + // templates log_lambda = 0.5 * log_temp - 0.5 * log_temp_final + log_lambdas(n_temp - 1); } else { // Inside table diff --git a/src/main.cpp b/src/main.cpp index e15213bf..d89df12d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -83,6 +83,7 @@ int main(int argc, char *argv[]) { } else if (problem == "cluster") { pman.app_input->ProblemGenerator = cluster::ProblemGenerator; Hydro::ProblemSourceUnsplit = cluster::ClusterSrcTerm; + Hydro::ProblemEstimateTimestep = cluster::ClusterEstimateTimestep; } else if (problem == "sod") { pman.app_input->ProblemGenerator = sod::ProblemGenerator; } diff --git a/src/main.hpp b/src/main.hpp index d7e9a209..de49b9fb 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -39,6 +39,8 @@ enum class Conduction { none, spitzer, thermal_diff }; enum class Hst { idx, ekin, emag, divb }; +enum class CartesianDir { x, y, z }; + constexpr parthenon::Real float_min{std::numeric_limits::min()}; #endif // MAIN_HPP_ diff --git a/src/pgen/CMakeLists.txt b/src/pgen/CMakeLists.txt index 23ccf38f..bf89f978 100644 --- a/src/pgen/CMakeLists.txt +++ b/src/pgen/CMakeLists.txt @@ -6,7 +6,10 @@ target_sources(athenaPK PRIVATE blast.cpp cloud.cpp cluster.cpp + cluster/agn_feedback.cpp + cluster/agn_triggering.cpp cluster/hydrostatic_equilibrium_sphere.cpp + cluster/magnetic_tower.cpp cpaw.cpp diffusion.cpp field_loop.cpp diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index cb850793..c7cbdd3b 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -9,7 +9,7 @@ // Setups up an idealized galaxy cluster with an ACCEPT-like entropy profile in // hydrostatic equilbrium with an NFW+BCG+SMBH gravitational profile, // optionally with an initial magnetic tower field. Includes tabular cooling, -// AGN feedback, AGN triggering via cold gas, simple SNIA Feedback +// AGN feedback, AGN triggering via cold gas, simple SNIA Feedback(TODO) //======================================================================================== // C headers @@ -25,6 +25,7 @@ #include // c_str() // Parthenon headers +#include "mesh/domain.hpp" #include "mesh/mesh.hpp" #include #include @@ -35,15 +36,19 @@ #include "../main.hpp" // Cluster headers +#include "cluster/agn_feedback.hpp" +#include "cluster/agn_triggering.hpp" #include "cluster/cluster_gravity.hpp" #include "cluster/entropy_profiles.hpp" #include "cluster/hydrostatic_equilibrium_sphere.hpp" +#include "cluster/magnetic_tower.hpp" namespace cluster { using namespace parthenon::driver::prelude; using namespace parthenon::package::prelude; -void ClusterSrcTerm(MeshData *md, const parthenon::SimTime, const Real beta_dt) { +void ClusterSrcTerm(MeshData *md, const parthenon::SimTime &tm, + const Real beta_dt) { auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); const bool &gravity_srcterm = hydro_pkg->Param("gravity_srcterm"); @@ -54,6 +59,26 @@ void ClusterSrcTerm(MeshData *md, const parthenon::SimTime, const Real bet GravitationalFieldSrcTerm(md, beta_dt, cluster_gravity); } + + const auto &agn_feedback = hydro_pkg->Param("agn_feedback"); + agn_feedback.FeedbackSrcTerm(md, beta_dt, tm); + + const auto &magnetic_tower = hydro_pkg->Param("magnetic_tower"); + magnetic_tower.FixedFieldSrcTerm(md, beta_dt, tm); +} + +Real ClusterEstimateTimestep(MeshData *md) { + Real min_dt = std::numeric_limits::max(); + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + // TODO time constraints imposed by thermal AGN feedback, jet velocity, + // magnetic tower + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + const Real agn_triggering_min_dt = agn_triggering.EstimateTimeStep(md); + min_dt = std::min(min_dt, agn_triggering_min_dt); + + return min_dt; } //======================================================================================== @@ -71,15 +96,15 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { ************************************************************/ const bool init_uniform_gas = - pin->GetOrAddBoolean("problem/cluster", "init_uniform_gas", false); + pin->GetOrAddBoolean("problem/cluster/uniform_gas", "init_uniform_gas", false); hydro_pkg->AddParam<>("init_uniform_gas", init_uniform_gas); if (init_uniform_gas) { - const Real uniform_gas_rho = pin->GetReal("problem/cluster", "uniform_gas_rho"); - const Real uniform_gas_ux = pin->GetReal("problem/cluster", "uniform_gas_ux"); - const Real uniform_gas_uy = pin->GetReal("problem/cluster", "uniform_gas_uy"); - const Real uniform_gas_uz = pin->GetReal("problem/cluster", "uniform_gas_uz"); - const Real uniform_gas_pres = pin->GetReal("problem/cluster", "uniform_gas_pres"); + const Real uniform_gas_rho = pin->GetReal("problem/cluster/uniform_gas", "rho"); + const Real uniform_gas_ux = pin->GetReal("problem/cluster/uniform_gas", "ux"); + const Real uniform_gas_uy = pin->GetReal("problem/cluster/uniform_gas", "uy"); + const Real uniform_gas_uz = pin->GetReal("problem/cluster/uniform_gas", "uz"); + const Real uniform_gas_pres = pin->GetReal("problem/cluster/uniform_gas", "pres"); hydro_pkg->AddParam<>("uniform_gas_rho", uniform_gas_rho); hydro_pkg->AddParam<>("uniform_gas_ux", uniform_gas_ux); @@ -93,11 +118,12 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { ************************************************************/ // Build cluster_gravity object - ClusterGravity cluster_gravity(pin); - hydro_pkg->AddParam<>("cluster_gravity", cluster_gravity); + ClusterGravity cluster_gravity(pin, hydro_pkg); + // hydro_pkg->AddParam<>("cluster_gravity", cluster_gravity); // Include gravity as a source term during evolution - const bool gravity_srcterm = pin->GetBoolean("problem/cluster", "gravity_srcterm"); + const bool gravity_srcterm = + pin->GetBoolean("problem/cluster/gravity", "gravity_srcterm"); hydro_pkg->AddParam<>("gravity_srcterm", gravity_srcterm); /************************************************************ @@ -111,26 +137,58 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { * Build Hydrostatic Equilibrium Sphere ************************************************************/ - HydrostaticEquilibriumSphere hse_sphere(pin, cluster_gravity, entropy_profile); - hydro_pkg->AddParam<>("hydrostatic_equilibirum_sphere", hse_sphere); + HydrostaticEquilibriumSphere hse_sphere(pin, hydro_pkg, cluster_gravity, + entropy_profile); + + /************************************************************ + * Read Precessing Jet Coordinate system + ************************************************************/ + + JetCoordsFactory jet_coords_factory(pin, hydro_pkg); + + /************************************************************ + * Read AGN Feedback + ************************************************************/ + + AGNFeedback agn_feedback(pin, hydro_pkg); + + /************************************************************ + * Read AGN Triggering + ************************************************************/ + AGNTriggering agn_triggering(pin, hydro_pkg); + + /************************************************************ + * Read Magnetic Tower + ************************************************************/ + + // Build Magnetic Tower + MagneticTower magnetic_tower(pin, hydro_pkg); + + // Determine if magnetic_tower_power_scaling is needed + // Is AGN Power and Magnetic fraction non-zero? + bool magnetic_tower_power_scaling = + (agn_feedback.magnetic_fraction_ != 0 && + (agn_feedback.fixed_power_ != 0 || + agn_triggering.triggering_mode_ != AGNTriggeringMode::NONE)); + hydro_pkg->AddParam("magnetic_tower_power_scaling", magnetic_tower_power_scaling); } IndexRange ib = pmb->cellbounds.GetBoundsI(IndexDomain::interior); IndexRange jb = pmb->cellbounds.GetBoundsJ(IndexDomain::interior); IndexRange kb = pmb->cellbounds.GetBoundsK(IndexDomain::interior); - // initialize conserved variables - auto &rc = pmb->meshblock_data.Get(); - auto &u_dev = rc->Get("cons").data; - auto &coords = pmb->coords; - // Initialize the conserved variables - auto u = u_dev.GetHostMirrorAndCopy(); + auto &u = pmb->meshblock_data.Get()->Get("cons").data; + + auto &coords = pmb->coords; // Get Adiabatic Index const Real gam = pin->GetReal("hydro", "gamma"); const Real gm1 = (gam - 1.0); + /************************************************************ + * Initialize the initial hydro state + ************************************************************/ const auto &init_uniform_gas = hydro_pkg->Param("init_uniform_gas"); if (init_uniform_gas) { const Real rho = hydro_pkg->Param("uniform_gas_rho"); @@ -144,18 +202,16 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { const Real Mz = rho * uz; const Real E = rho * (0.5 * (ux * ux + uy * uy + uz * uz) + pres / (gm1 * rho)); - for (int k = kb.s; k <= kb.e; k++) { - for (int j = jb.s; j <= jb.e; j++) { - for (int i = ib.s; i <= ib.e; i++) { - + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "Cluster::ProblemGenerator::UniformGas", + parthenon::DevExecSpace(), kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, + KOKKOS_LAMBDA(const int &k, const int &j, const int &i) { u(IDN, k, j, i) = rho; u(IM1, k, j, i) = Mx; u(IM2, k, j, i) = My; u(IM3, k, j, i) = Mz; u(IEN, k, j, i) = E; - } - } - } + }); } else { /************************************************************ @@ -166,16 +222,14 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { ->Param>( "hydrostatic_equilibirum_sphere"); - const auto P_rho_profile = he_sphere.generate_P_rho_profile< - Kokkos::View, - parthenon::UniformCartesian>(ib, jb, kb, coords); + const auto P_rho_profile = + he_sphere.generate_P_rho_profile(ib, jb, kb, coords); // initialize conserved variables - for (int k = kb.s; k <= kb.e; k++) { - for (int j = jb.s; j <= jb.e; j++) { - for (int i = ib.s; i <= ib.e; i++) { - + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "cluster::ProblemGenerator::UniformGas", + parthenon::DevExecSpace(), kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, + KOKKOS_LAMBDA(const int &k, const int &j, const int &i) { // Calculate radius const Real r = sqrt(coords.x1v(i) * coords.x1v(i) + coords.x2v(j) * coords.x2v(j) + @@ -191,13 +245,56 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { u(IM2, k, j, i) = 0.0; u(IM3, k, j, i) = 0.0; u(IEN, k, j, i) = P_r / gm1; - } - } - } + }); } - // copy initialized cons to device - u_dev.DeepCopy(u); + if (hydro_pkg->Param("fluid") == Fluid::glmmhd) { + /************************************************************ + * Initialize the initial magnetic field state via a vector potential + ************************************************************/ + parthenon::ParArray4D A("A", 3, pmb->cellbounds.ncellsk(IndexDomain::entire), + pmb->cellbounds.ncellsj(IndexDomain::entire), + pmb->cellbounds.ncellsi(IndexDomain::entire)); + + IndexRange a_ib = ib; + a_ib.s -= 1; + a_ib.e += 1; + IndexRange a_jb = jb; + a_jb.s -= 1; + a_jb.e += 1; + IndexRange a_kb = kb; + a_kb.s -= 1; + a_kb.e += 1; + + /************************************************************ + * Initialize an initial magnetic tower + ************************************************************/ + const auto &magnetic_tower = hydro_pkg->Param("magnetic_tower"); + + magnetic_tower.AddInitialFieldToPotential(pmb, a_kb, a_jb, a_ib, A); + + /************************************************************ + * Apply the potential to the conserved variables + ************************************************************/ + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "cluster::ProblemGenerator::ApplyMagneticPotential", + parthenon::DevExecSpace(), kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, + KOKKOS_LAMBDA(const int &k, const int &j, const int &i) { + u(IB1, k, j, i) = + (A(2, k, j + 1, i) - A(2, k, j - 1, i)) / coords.dx2v(j) / 2.0 - + (A(1, k + 1, j, i) - A(1, k - 1, j, i)) / coords.dx3v(k) / 2.0; + u(IB2, k, j, i) = + (A(0, k + 1, j, i) - A(0, k - 1, j, i)) / coords.dx3v(k) / 2.0 - + (A(2, k, j, i + 1) - A(2, k, j, i - 1)) / coords.dx1v(i) / 2.0; + u(IB3, k, j, i) = + (A(1, k, j, i + 1) - A(1, k, j, i - 1)) / coords.dx1v(i) / 2.0 - + (A(0, k, j + 1, i) - A(0, k, j - 1, i)) / coords.dx2v(j) / 2.0; + + u(IEN, k, j, i) += + 0.5 * (SQR(u(IB1, k, j, i)) + SQR(u(IB2, k, j, i)) + SQR(u(IB3, k, j, i))); + }); + + } // END if(hydro_pkg->Param("fluid") == Fluid::glmmhd) } } // namespace cluster diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp new file mode 100644 index 00000000..ccad69ae --- /dev/null +++ b/src/pgen/cluster/agn_feedback.cpp @@ -0,0 +1,193 @@ +//======================================================================================== +// Athena++ astrophysical MHD code +// Copyright(C) 2014 James M. Stone and other code contributors +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file magnetic_tower.hpp +// \brief Class for defining magnetic tower + +// Parthenon headers +#include +#include +#include +#include +#include +#include + +// Athena headers +#include "../../eos/adiabatic_glmmhd.hpp" +#include "../../eos/adiabatic_hydro.hpp" +#include "../../main.hpp" +#include "../../units.hpp" +#include "agn_feedback.hpp" +#include "agn_triggering.hpp" +#include "cluster_utils.hpp" +#include "magnetic_tower.hpp" + +namespace cluster { +using namespace parthenon; + +AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, + const std::shared_ptr &hydro_pkg) + : fixed_power_(pin->GetOrAddReal("problem/cluster/agn_feedback", "fixed_power", 0.0)), + efficiency_(pin->GetOrAddReal("problem/cluster/agn_feedback", "efficiency", 1e-3)), + thermal_fraction_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "thermal_fraction", 0.0)), + kinetic_fraction_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_fraction", 0.0)), + magnetic_fraction_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "magnetic_fraction", 0.0)), + thermal_radius_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "thermal_radius", 0.01)), + kinetic_jet_radius_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_radius", 0.01)), + kinetic_jet_height_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_height", 0.02)), + disabled_(pin->GetOrAddBoolean("problem/cluster/agn_feedback", "disabled", false)) { + + hydro_pkg->AddParam<>("agn_feedback", *this); +} + +void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, + const parthenon::SimTime &tm) const { + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + auto fluid = hydro_pkg->Param("fluid"); + if (fluid == Fluid::euler) { + FeedbackSrcTerm(md, beta_dt, tm, hydro_pkg->Param("eos")); + } else if (fluid == Fluid::glmmhd) { + FeedbackSrcTerm(md, beta_dt, tm, hydro_pkg->Param("eos")); + } else { + PARTHENON_FAIL("AGNFeedback::FeedbackSrcTerm: Unknown EOS"); + } +} +template +void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, + const parthenon::SimTime &tm, const EOS &eos) const { + using parthenon::IndexDomain; + using parthenon::IndexRange; + using parthenon::Real; + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + auto units = hydro_pkg->Param("units"); + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + + const Real accretion_rate = agn_triggering.GetAccretionRate(hydro_pkg.get()); + const Real power = + fixed_power_ + accretion_rate * efficiency_ * pow(units.speed_of_light(), 2); + const Real mass_rate = + efficiency_ == 0 ? 0 : power / (efficiency_ * pow(units.speed_of_light(), 2)); + + if (power == 0 || disabled_) { + // No AGN feedback, return + return; + } + + if (magnetic_fraction_ == 0 && thermal_fraction_ == 0 && kinetic_fraction_ == 0) { + PARTHENON_FAIL("AGNFeedback::FeedbackSrcTerm Magnetic, Thermal, and Kinetic " + "fractions are all zero"); + } + + // Grab some necessary variables + const auto &prim_pack = md->PackVariables(std::vector{"prim"}); + const auto &cons_pack = md->PackVariables(std::vector{"cons"}); + IndexRange ib = cons_pack.cellbounds.GetBoundsI(IndexDomain::interior); + IndexRange jb = cons_pack.cellbounds.GetBoundsJ(IndexDomain::interior); + IndexRange kb = cons_pack.cellbounds.GetBoundsK(IndexDomain::interior); + + // Thermal quantities + const Real thermal_power = power * thermal_fraction_; + const Real thermal_scaling_factor = 1 / (4. / 3. * M_PI * pow(thermal_radius_, 3)); + const Real thermal_feedback = + thermal_power * thermal_scaling_factor * beta_dt; // energy/volume + const Real thermal_density_rate = + mass_rate * thermal_scaling_factor * thermal_fraction_; + const Real thermal_radius2 = thermal_radius_ * thermal_radius_; + + // Kinetic Jet Quantities + const Real kinetic_power = power * kinetic_fraction_; + const Real kinetic_scaling_factor = + 1 / (2 * kinetic_jet_height_ * M_PI * pow(kinetic_jet_radius_, 2)); + // Matches 1/2.*jet_density*jet_velocity*jet_velocity*beta_dt; + const Real kinetic_feedback = + kinetic_power * kinetic_scaling_factor * beta_dt; // energy/volume + + // Note that new mass is injected to create the kinetic power, separate from the + // existing gas + const Real kinetic_jet_total_mass = mass_rate * kinetic_fraction_; + const Real kinetic_jet_density_rate = kinetic_jet_total_mass * kinetic_scaling_factor; + const Real kinetic_jet_velocity = + std::sqrt(2. * kinetic_power / kinetic_jet_total_mass); + + const Real kinetic_jet_radius = kinetic_jet_radius_; + const Real kinetic_jet_height = kinetic_jet_height_; + + const parthenon::Real time = tm.time; + + const auto &jet_coords_factory = + hydro_pkg->Param("jet_coords_factory"); + const JetCoords jet_coords = jet_coords_factory.CreateJetCoords(time); + + // Constant volumetric heating + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "HydroAGNFeedback::FeedbackSrcTerm", + parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, + ib.e, KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i) { + auto &cons = cons_pack(b); + auto &prim = prim_pack(b); + const auto &coords = cons_pack.coords(b); + + const Real x = coords.x1v(i); + const Real y = coords.x2v(j); + const Real z = coords.x3v(k); + + // Thermal Feedback + if (thermal_power > 0) { + const Real r2 = x * x + y * y + z * z; + // Determine if point is in sphere r<=thermal_radius + if (r2 <= thermal_radius2) { + // Apply heating + cons(IEN, k, j, i) += thermal_feedback; + // Add density at constant velocity + AddDensityToConsAtFixedVel(thermal_density_rate, cons, prim, eos, k, j, i); + } + } + + // Kinetic Jet Feedback + if (kinetic_power > 0) { + // Get position in jet cylindrical coords + Real r, cos_theta, sin_theta, h; + jet_coords.SimCartToJetCylCoords(x, y, z, r, cos_theta, sin_theta, h); + + if (r < kinetic_jet_radius && fabs(h) < kinetic_jet_height) { + // Cell falls inside jet deposition volume + + // Get the vector of the jet axis + Real jet_axis_x, jet_axis_y, jet_axis_z; + jet_coords.JetCylToSimCartVector(cos_theta, sin_theta, 0, 0, 1, jet_axis_x, + jet_axis_y, jet_axis_z); + + const int sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk + + cons(IDN, k, j, i) += kinetic_jet_density_rate * beta_dt; // mass/volume + cons(IM1, k, j, i) += kinetic_jet_density_rate * sign_jet * jet_axis_x * + kinetic_jet_velocity * beta_dt; // velocity*mass/volume + cons(IM2, k, j, i) += kinetic_jet_density_rate * sign_jet * jet_axis_y * + kinetic_jet_velocity * beta_dt; // velocity*mass/volume + cons(IM3, k, j, i) += kinetic_jet_density_rate * sign_jet * jet_axis_z * + kinetic_jet_velocity * beta_dt; // velocity*mass/volume + cons(IEN, k, j, i) += kinetic_feedback; // energy/volume + } + } + }); + + // Apply magnetic tower feedback + const auto &magnetic_tower = hydro_pkg->Param("magnetic_tower"); + + const Real magnetic_power = power * magnetic_fraction_; + const Real magnetic_mass_rate = mass_rate * magnetic_fraction_; + magnetic_tower.PowerSrcTerm(magnetic_power, magnetic_mass_rate, md, beta_dt, tm); +} + +} // namespace cluster \ No newline at end of file diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp new file mode 100644 index 00000000..3c8063bd --- /dev/null +++ b/src/pgen/cluster/agn_feedback.hpp @@ -0,0 +1,60 @@ +#ifndef CLUSTER_AGN_FEEDBACK_HPP_ +#define CLUSTER_AGN_FEEDBACK_HPP_ +//======================================================================================== +// Athena++ astrophysical MHD code +// Copyright(C) 2014 James M. Stone and other code contributors +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file hydro_agn_feedback.hpp +// \brief Class for defining hydrodynamic AGN feedback + +// parthenon headers +#include +#include +#include +#include +#include + +#include "jet_coords.hpp" + +namespace cluster { + +/************************************************************ + * AGNFeedback + ************************************************************/ +class AGNFeedback { + public: + const parthenon::Real fixed_power_; + const parthenon::Real thermal_fraction_, kinetic_fraction_, magnetic_fraction_; + + // Efficiency converting mass to energy + const parthenon::Real efficiency_; + + // Thermal Heating Parameters + const parthenon::Real thermal_radius_; + + // Kinetic Feedback Parameters + const parthenon::Real kinetic_jet_radius_, kinetic_jet_height_; + + const bool disabled_; + + AGNFeedback(parthenon::ParameterInput *pin, + const std::shared_ptr &hydro_pkg); + + // parthenon::Real GetPower() const { return fixed_power_; } + // void SetPower(const parthenon::Real power) { fixed_power_ = power; } + + // Apply the feedback from hydrodynamic AGN feedback (kinetic jets and thermal feedback) + void FeedbackSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, const parthenon::SimTime &tm) const; + + // Apply the feedback from hydrodynamic AGN feedback (kinetic jets and thermal feedback) + template + void FeedbackSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, const parthenon::SimTime &tm, + const EOS &eos) const; +}; + +} // namespace cluster + +#endif // CLUSTER_AGN_FEEDBACK_HPP_ diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp new file mode 100644 index 00000000..f2e32fa2 --- /dev/null +++ b/src/pgen/cluster/agn_triggering.cpp @@ -0,0 +1,561 @@ +//======================================================================================== +// Athena++ astrophysical MHD code +// Copyright(C) 2014 James M. Stone and other code contributors +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file magnetic_tower.hpp +// \brief Class for defining magnetic tower + +// Parthenon headers +#include +#include +#include +#include +#include +#include +#include +#include + +// Athena headers +#include "../../eos/adiabatic_glmmhd.hpp" +#include "../../eos/adiabatic_hydro.hpp" +#include "../../main.hpp" +#include "../../reduction_utils.hpp" +#include "../../units.hpp" +#include "Kokkos_HostSpace.hpp" +#include "Kokkos_View.hpp" +#include "agn_feedback.hpp" +#include "agn_triggering.hpp" +#include "cluster_utils.hpp" + +namespace cluster { +using namespace parthenon; + +AGNTriggeringMode ParseAGNTriggeringMode(const std::string &mode_str) { + + if (mode_str == "COLD_GAS") { + return AGNTriggeringMode::COLD_GAS; + } else if (mode_str == "BOOSTED_BONDI") { + return AGNTriggeringMode::BOOSTED_BONDI; + } else if (mode_str == "BOOTH_SCHAYE") { + return AGNTriggeringMode::BOOTH_SCHAYE; + } else if (mode_str == "NONE") { + return AGNTriggeringMode::NONE; + } else { + std::stringstream msg; + msg << "### FATAL ERROR in function [ParseAGNTriggeringMode]" << std::endl + << "Unrecognized AGNTriggeringMode: \"" << mode_str << "\"" << std::endl; + PARTHENON_FAIL(msg); + } + return AGNTriggeringMode::NONE; +} + +AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, + const std::shared_ptr &hydro_pkg, + const std::string &block) + : gamma_(pin->GetReal("hydro", "gamma")), + triggering_mode_( + ParseAGNTriggeringMode(pin->GetOrAddString(block, "triggering_mode", "NONE"))), + accretion_radius_(pin->GetOrAddReal(block, "accretion_radius", 0)), + cold_temp_thresh_(pin->GetOrAddReal(block, "cold_temp_thresh", 0)), + cold_t_acc_(pin->GetOrAddReal(block, "cold_t_acc", 0)), + bondi_alpha_(pin->GetOrAddReal(block, "bondi_alpha", 0)), + bondi_M_smbh_(pin->GetOrAddReal("problem/cluster/gravity", "m_smbh", 0)), + bondi_n0_(pin->GetOrAddReal(block, "bondi_n0", 0)), + bondi_beta_(pin->GetOrAddReal(block, "bondi_beta", 0)), + accretion_cfl_(pin->GetOrAddReal(block, "accretion_cfl", 1e-1)), + write_to_file_(pin->GetOrAddBoolean(block, "write_to_file", false)), + triggering_filename_( + pin->GetOrAddString(block, "triggering_filename", "agn_triggering.dat")) { + + const auto units = hydro_pkg->Param("units"); + const parthenon::Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); + const parthenon::Real H_mass_fraction = 1.0 - He_mass_fraction; + const parthenon::Real mu = + 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); + + mean_molecular_mass_ = mu * units.atomic_mass_unit(); + + if (triggering_mode_ == AGNTriggeringMode::NONE) { + hydro_pkg->AddParam("agn_triggering_reduce_accretion_rate", false); + } else { + hydro_pkg->AddParam("agn_triggering_reduce_accretion_rate", true); + } + switch (triggering_mode_) { + case AGNTriggeringMode::COLD_GAS: { + hydro_pkg->AddParam("agn_triggering_cold_mass", 0); + break; + } + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + hydro_pkg->AddParam("agn_triggering_total_mass", 0); + hydro_pkg->AddParam("agn_triggering_mass_weighted_density", 0); + hydro_pkg->AddParam("agn_triggering_mass_weighted_velocity", 0); + hydro_pkg->AddParam("agn_triggering_mass_weighted_cs", 0); + break; + } + case AGNTriggeringMode::NONE: { + break; + } + } + + if (write_to_file_ && parthenon::Globals::my_rank == 0) { + // Clear the triggering_file + std::ofstream triggering_file; + triggering_file.open(triggering_filename_, std::ofstream::out | std::ofstream::trunc); + triggering_file.close(); + } + + hydro_pkg->AddParam("agn_triggering", *this); +} + +// Compute Cold gas accretion rate within the accretion radius for cold gas triggering +// and simultaneously remove cold gas (updating conserveds and primitives) +template +void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, + parthenon::MeshData *md, + const parthenon::Real dt, const EOS eos) const { + using parthenon::IndexDomain; + using parthenon::IndexRange; + using parthenon::Real; + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + // Grab some necessary variables + const auto &prim_pack = md->PackVariables(std::vector{"prim"}); + const auto &cons_pack = md->PackVariables(std::vector{"cons"}); + IndexRange ib = cons_pack.cellbounds.GetBoundsI(IndexDomain::interior); + IndexRange jb = cons_pack.cellbounds.GetBoundsJ(IndexDomain::interior); + IndexRange kb = cons_pack.cellbounds.GetBoundsK(IndexDomain::interior); + + const Real accretion_radius2 = pow(accretion_radius_, 2); + + // Reduce just the cold gas + const auto units = hydro_pkg->Param("units"); + const Real mean_molecular_mass_by_kb = mean_molecular_mass_ / units.k_boltzmann(); + + const Real cold_temp_thresh = cold_temp_thresh_; + const Real cold_t_acc = cold_t_acc_; + + Real md_cold_mass = 0; + + parthenon::par_reduce( + parthenon::loop_pattern_mdrange_tag, "AGNTriggering::ReduceColdGas", + parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, + ib.e, + KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i, + Real &team_cold_mass) { + auto &cons = cons_pack(b); + auto &prim = prim_pack(b); + const auto &coords = cons_pack.coords(b); + + const parthenon::Real r2 = + pow(coords.x1v(i), 2) + pow(coords.x2v(j), 2) + pow(coords.x3v(k), 2); + if (r2 < accretion_radius2) { + const Real cell_volume = coords.Volume(k, j, i); + const Real cell_mass = prim(IDN, k, j, i) * cell_volume; + + const Real temp = + mean_molecular_mass_by_kb * prim(IPR, k, j, i) / prim(IDN, k, j, i); + + if (temp <= cold_temp_thresh) { + const Real cell_cold_mass = cell_mass; + team_cold_mass += cell_cold_mass; + + const Real cell_delta_rho = -prim(IDN, k, j, i) / cold_t_acc * dt; + + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); + // Update the Primitives + eos.ConsToPrim(cons, prim, k, j, i); + } + } + }, + Kokkos::Sum(md_cold_mass)); + cold_mass += md_cold_mass; +} + +// Compute Mass-weighted total density, velocity, and sound speed and total mass +// for Bondi accretion +void AGNTriggering::ReduceBondiTriggeringQuantities( + parthenon::Real &total_mass, parthenon::Real &mass_weighted_density, + parthenon::Real &mass_weighted_velocity, parthenon::Real &mass_weighted_cs, + parthenon::MeshData *md) const { + using parthenon::IndexDomain; + using parthenon::IndexRange; + using parthenon::Real; + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + // Grab some necessary variables + const auto &prim_pack = md->PackVariables(std::vector{"prim"}); + const auto &cons_pack = md->PackVariables(std::vector{"cons"}); + IndexRange ib = cons_pack.cellbounds.GetBoundsI(IndexDomain::interior); + IndexRange jb = cons_pack.cellbounds.GetBoundsJ(IndexDomain::interior); + IndexRange kb = cons_pack.cellbounds.GetBoundsK(IndexDomain::interior); + + const Real accretion_radius2 = pow(accretion_radius_, 2); + + // Reduce Mass-weighted total density, velocity, and sound speed and total + // mass (in that order). Will need to divide the three latter quantities by + // total mass in order to get their mass-weighted averaged values + const unsigned int MASS_IDX = 0; + const unsigned int DENSITY_IDX = 1; + const unsigned int VELOCITY_IDX = 2; + const unsigned int CS_IDX = 3; + ReductionSumArray triggering_reduction; + Kokkos::Sum> reducer_sum(triggering_reduction); + + const parthenon::Real gamma = gamma_; + + parthenon::par_reduce( + parthenon::loop_pattern_mdrange_tag, "AGNTriggering::ReduceBondi", + parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, + ib.e, + KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i, + ReductionSumArray &team_triggering_reduction) { + auto &cons = cons_pack(b); + auto &prim = prim_pack(b); + const auto &coords = cons_pack.coords(b); + const parthenon::Real r2 = + pow(coords.x1v(i), 2) + pow(coords.x2v(j), 2) + pow(coords.x3v(k), 2); + if (r2 < accretion_radius2) { + const Real cell_mass = prim(IDN, k, j, i) * coords.Volume(k, j, i); + + const Real cell_mass_weighted_density = cell_mass * prim(IDN, k, j, i); + const Real cell_mass_weighted_velocity = + cell_mass * sqrt(pow(prim(IV1, k, j, i), 2) + pow(prim(IV2, k, j, i), 2) + + pow(prim(IV3, k, j, i), 2)); + const Real cell_mass_weighted_cs = + cell_mass * sqrt(gamma * prim(IPR, k, j, i) / prim(IDN, k, j, i)); + + team_triggering_reduction.data[MASS_IDX] += cell_mass; + team_triggering_reduction.data[DENSITY_IDX] += cell_mass_weighted_density; + team_triggering_reduction.data[VELOCITY_IDX] += cell_mass_weighted_velocity; + team_triggering_reduction.data[CS_IDX] += cell_mass_weighted_cs; + } + }, + reducer_sum); + // Save the reduction results to triggering_quantities + total_mass += triggering_reduction.data[MASS_IDX]; + mass_weighted_density += triggering_reduction.data[DENSITY_IDX]; + mass_weighted_velocity += triggering_reduction.data[VELOCITY_IDX]; + mass_weighted_cs += triggering_reduction.data[CS_IDX]; +} + +// Remove gas consistent with Bondi accretion +/// i.e. proportional to the accretion rate, weighted by the local gas mass +template +void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData *md, + const parthenon::Real dt, + const EOS eos) const { + using parthenon::IndexDomain; + using parthenon::IndexRange; + using parthenon::Real; + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + // Grab some necessary variables + // FIXME(forrestglines) When reductions are called, is `prim` up to date? + const auto &prim_pack = md->PackVariables(std::vector{"prim"}); + const auto &cons_pack = md->PackVariables(std::vector{"cons"}); + IndexRange ib = cons_pack.cellbounds.GetBoundsI(IndexDomain::interior); + IndexRange jb = cons_pack.cellbounds.GetBoundsJ(IndexDomain::interior); + IndexRange kb = cons_pack.cellbounds.GetBoundsK(IndexDomain::interior); + + const Real accretion_radius2 = pow(accretion_radius_, 2); + + const Real accretion_rate = GetAccretionRate(hydro_pkg.get()); + const Real total_mass = hydro_pkg->Param("agn_triggering_total_mass"); + + parthenon::par_for( + parthenon::loop_pattern_mdrange_tag, "AGNTriggering::RemoveBondiAccretedGas", + parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, + ib.e, KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i) { + auto &cons = cons_pack(b); + auto &prim = prim_pack(b); + const auto &coords = cons_pack.coords(b); + + const parthenon::Real r2 = + pow(coords.x1v(i), 2) + pow(coords.x2v(j), 2) + pow(coords.x3v(k), 2); + if (r2 < accretion_radius2) { + const Real cell_volume = coords.Volume(k, j, i); + + const Real cell_accretion_rate = + prim(IDN, k, j, i) / total_mass * accretion_rate; + + const Real cell_delta_rho = -cell_accretion_rate * dt; + + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); + // Update the Primitives + eos.ConsToPrim(cons, prim, k, j, i); + } + }); +} + +// Compute and return the current AGN accretion rate from already globally +// reduced quantities +parthenon::Real +AGNTriggering::GetAccretionRate(parthenon::StateDescriptor *hydro_pkg) const { + switch (triggering_mode_) { + case AGNTriggeringMode::COLD_GAS: { + // Test the Cold-Gas-like triggering methods + const parthenon::Real cold_mass = hydro_pkg->Param("agn_triggering_cold_mass"); + + return cold_mass / cold_t_acc_; + } + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + // Test the Bondi-like triggering methods + auto units = hydro_pkg->Param("units"); + const Real total_mass = hydro_pkg->Param("agn_triggering_total_mass"); + const Real mean_mass_weighted_density = + hydro_pkg->Param("agn_triggering_mass_weighted_density") / total_mass; + const Real mean_mass_weighted_velocity = + hydro_pkg->Param("agn_triggering_mass_weighted_velocity") / total_mass; + const Real mean_mass_weighted_cs = + hydro_pkg->Param("agn_triggering_mass_weighted_cs") / total_mass; + + Real alpha = 0; + if (triggering_mode_ == AGNTriggeringMode::BOOSTED_BONDI) { + alpha = bondi_alpha_; + } else if (triggering_mode_ == AGNTriggeringMode::BOOTH_SCHAYE) { + const Real mean_mass_weighted_n = mean_mass_weighted_density / mean_molecular_mass_; + alpha = (mean_mass_weighted_n <= bondi_n0_) + ? 1 + : pow(mean_mass_weighted_n / bondi_n0_, bondi_beta_); + } else { + PARTHENON_FAIL("### FATAL ERROR in AGNTriggering::AccretionRate unsupported " + "Bondi-like triggering"); + } + const Real mdot = + alpha * 2 * M_PI * pow(units.gravitational_constant(), 2) * + pow(bondi_M_smbh_, 2) * mean_mass_weighted_density / + (pow(pow(mean_mass_weighted_velocity, 2) + pow(mean_mass_weighted_cs, 2), + 3. / 2.)); + + return mdot; + } + case AGNTriggeringMode::NONE: { + return 0; + } + } + return 0; +} + +parthenon::TaskStatus +AGNTriggeringResetTriggering(parthenon::StateDescriptor *hydro_pkg) { + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + + switch (agn_triggering.triggering_mode_) { + case AGNTriggeringMode::COLD_GAS: { + hydro_pkg->UpdateParam("agn_triggering_cold_mass", 0); + break; + } + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + hydro_pkg->UpdateParam("agn_triggering_total_mass", 0); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_density", 0); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_velocity", 0); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_cs", 0); + break; + } + case AGNTriggeringMode::NONE: { + break; + } + } + return TaskStatus::complete; +} + +parthenon::TaskStatus +AGNTriggeringReduceTriggering(parthenon::MeshData *md, + const parthenon::Real dt) { + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + + switch (agn_triggering.triggering_mode_) { + case AGNTriggeringMode::COLD_GAS: { + Real cold_mass = hydro_pkg->Param("agn_triggering_cold_mass"); + + auto fluid = hydro_pkg->Param("fluid"); + if (fluid == Fluid::euler) { + agn_triggering.ReduceColdMass(cold_mass, md, dt, + hydro_pkg->Param("eos")); + } else if (fluid == Fluid::glmmhd) { + agn_triggering.ReduceColdMass(cold_mass, md, dt, + hydro_pkg->Param("eos")); + } else { + PARTHENON_FAIL("AGNTriggeringReduceTriggeringQuantities: Unknown EOS"); + } + + hydro_pkg->UpdateParam("agn_triggering_cold_mass", cold_mass); + break; + } + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + Real total_mass = hydro_pkg->Param("agn_triggering_total_mass"); + Real mass_weighted_density = + hydro_pkg->Param("agn_triggering_mass_weighted_density"); + Real mass_weighted_velocity = + hydro_pkg->Param("agn_triggering_mass_weighted_velocity"); + Real mass_weighted_cs = + hydro_pkg->Param("agn_triggering_mass_weighted_cs"); + + agn_triggering.ReduceBondiTriggeringQuantities( + total_mass, mass_weighted_density, mass_weighted_velocity, mass_weighted_cs, md); + + hydro_pkg->UpdateParam("agn_triggering_total_mass", total_mass); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_density", mass_weighted_density); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_velocity", + mass_weighted_velocity); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_cs", mass_weighted_cs); + break; + } + case AGNTriggeringMode::NONE: { + break; + } + } + return TaskStatus::complete; +} + +parthenon::TaskStatus +AGNTriggeringMPIReduceTriggering(parthenon::StateDescriptor *hydro_pkg) { +#ifdef MPI_PARALLEL + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + switch (agn_triggering.triggering_mode_) { + case AGNTriggeringMode::COLD_GAS: { + + Real accretion_rate = hydro_pkg->Param("agn_triggering_cold_mass"); + PARTHENON_MPI_CHECK(MPI_Allreduce(MPI_IN_PLACE, &accretion_rate, 1, + MPI_PARTHENON_REAL, MPI_MAX, MPI_COMM_WORLD)); + hydro_pkg->UpdateParam("agn_triggering_cold_mass", accretion_rate); + break; + } + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + Real triggering_quantities[] = { + hydro_pkg->Param("agn_triggering_total_mass"), + hydro_pkg->Param("agn_triggering_mass_weighted_density"), + hydro_pkg->Param("agn_triggering_mass_weighted_velocity"), + hydro_pkg->Param("agn_triggering_mass_weighted_cs"), + }; + + PARTHENON_MPI_CHECK(MPI_Allreduce(MPI_IN_PLACE, &triggering_quantities, 4, + MPI_PARTHENON_REAL, MPI_MAX, MPI_COMM_WORLD)); + + hydro_pkg->UpdateParam("agn_triggering_total_mass", triggering_quantities[0]); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_density", + triggering_quantities[1]); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_velocity", + triggering_quantities[2]); + hydro_pkg->UpdateParam("agn_triggering_mass_weighted_cs", triggering_quantities[3]); + break; + } + case AGNTriggeringMode::NONE: { + break; + } + } +#endif + return TaskStatus::complete; +} + +parthenon::TaskStatus +AGNTriggeringFinalizeTriggering(parthenon::MeshData *md, + const parthenon::SimTime &tm) { + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + + // Append quantities to file if applicable + if (agn_triggering.write_to_file_ && parthenon::Globals::my_rank == 0) { + std::ofstream triggering_file; + triggering_file.open(agn_triggering.triggering_filename_, std::ofstream::app); + + triggering_file << tm.time << " " << tm.dt << " " + << agn_triggering.GetAccretionRate(hydro_pkg.get()) << " "; + + switch (agn_triggering.triggering_mode_) { + case AGNTriggeringMode::COLD_GAS: { + + triggering_file << hydro_pkg->Param("agn_triggering_cold_mass"); + break; + } + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + const auto &total_mass = hydro_pkg->Param("agn_triggering_total_mass"); + const auto &avg_density = + hydro_pkg->Param("agn_triggering_mass_weighted_density") / total_mass; + const auto &avg_velocity = + hydro_pkg->Param("agn_triggering_mass_weighted_velocity") / total_mass; + const auto &avg_cs = + hydro_pkg->Param("agn_triggering_mass_weighted_cs") / total_mass; + triggering_file << total_mass << " " << avg_density << " " << avg_velocity << " " + << avg_cs; + break; + } + case AGNTriggeringMode::NONE: { + break; + } + } + + triggering_file << std::endl; + triggering_file.close(); + } + + // Remove accreted gas if using a Bondi-like mode + switch (agn_triggering.triggering_mode_) { + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + auto fluid = hydro_pkg->Param("fluid"); + if (fluid == Fluid::euler) { + agn_triggering.RemoveBondiAccretedGas(md, tm.dt, + hydro_pkg->Param("eos")); + } else if (fluid == Fluid::glmmhd) { + agn_triggering.RemoveBondiAccretedGas(md, tm.dt, + hydro_pkg->Param("eos")); + } else { + PARTHENON_FAIL("AGNTriggeringFinalizeTriggering: Unknown EOS"); + } + break; + } + case AGNTriggeringMode::COLD_GAS: // Already removed during reduction + case AGNTriggeringMode::NONE: { + break; + } + } + return TaskStatus::complete; +} + +// Limit timestep to a factor of the cold gas accretion time for Cold Gas +// triggered cooling, or a factor of the time to accrete the total mass for +// Bondi-like accretion +parthenon::Real +AGNTriggering::EstimateTimeStep(parthenon::MeshData *md) const { + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + switch (triggering_mode_) { + case AGNTriggeringMode::COLD_GAS: { + return accretion_cfl_ * cold_t_acc_; + } + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + // Test the Bondi-like triggering methods + const Real total_mass = hydro_pkg->Param("agn_triggering_total_mass"); + if (total_mass == 0) { + // TODO(forrestglines)During the first timestep, the total mass and + // accretion rate has not yet been reduced. However, since accreted mass is + // removed during that reduction, the timestep is needed to execute that + // reduction. As a compromise, we ignore the timestep constraint during the + // first timestep, assuming that accretion is slow initially + return std::numeric_limits::max(); + } + const Real mdot = GetAccretionRate(hydro_pkg.get()); + return accretion_cfl_ * total_mass / mdot; + } + case AGNTriggeringMode::NONE: { + return std::numeric_limits::max(); + } + } + return std::numeric_limits::max(); +} + +} // namespace cluster diff --git a/src/pgen/cluster/agn_triggering.hpp b/src/pgen/cluster/agn_triggering.hpp new file mode 100644 index 00000000..c0bb3a9a --- /dev/null +++ b/src/pgen/cluster/agn_triggering.hpp @@ -0,0 +1,129 @@ +#ifndef CLUSTER_AGN_TRIGGERING_HPP_ +#define CLUSTER_AGN_TRIGGERING_HPP_ +//======================================================================================== +// Athena++ astrophysical MHD code +// Copyright(C) 2014 James M. Stone and other code contributors +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file agn_triggering.hpp +// \brief Class for AGN Triggering + +// parthenon headers +#include +#include +#include +#include +#include +#include + +// AthenaPK headers +#include "../../units.hpp" +#include "jet_coords.hpp" +#include "utils/error_checking.hpp" + +namespace cluster { + +enum class AGNTriggeringMode { COLD_GAS, BOOSTED_BONDI, BOOTH_SCHAYE, NONE }; + +AGNTriggeringMode ParseAGNTriggeringMode(const std::string &mode_str); + +/************************************************************ + * AGN Triggering class : For computing the mass triggering the AGN + ************************************************************/ +class AGNTriggering { + private: + const parthenon::Real gamma_; + parthenon::Real mean_molecular_mass_; + + public: + const AGNTriggeringMode triggering_mode_; + + const parthenon::Real accretion_radius_; + + // Parameters for cold-gas triggering + const parthenon::Real cold_temp_thresh_; + const parthenon::Real cold_t_acc_; + + // Parameters necessary for Boosted Bondi accretion + const parthenon::Real bondi_alpha_; //(Only for boosted Bondi) + const parthenon::Real bondi_M_smbh_; + + // Additional parameters for Booth Schaye + const parthenon::Real bondi_n0_; + const parthenon::Real bondi_beta_; + + // Used in timestep estimation + const parthenon::Real accretion_cfl_; + + // Write triggering quantities (accretion rate or Bondi quantities) to file at + // every timestep. Intended for testing quantities at every timestep, since + // this file does not work across restarts, and since these quantities are + // included in Parthenon phdf outputs. + const bool write_to_file_; + const std::string triggering_filename_; + + AGNTriggering(parthenon::ParameterInput *pin, + const std::shared_ptr &hydro_pkg, + const std::string &block = "problem/cluster/agn_triggering"); + + // Compute Cold gas accretion rate within the accretion radius for cold gas triggering + // and simultaneously remove cold gas (updating conserveds and primitives) + template + void ReduceColdMass(parthenon::Real &cold_mass, + parthenon::MeshData *md, const parthenon::Real dt, + const EOS eos) const; + + // Compute Mass-weighted total density, velocity, and sound speed and total mass + // for Bondi accretion + void ReduceBondiTriggeringQuantities(parthenon::Real &total_mass, + parthenon::Real &mass_weighted_density, + parthenon::Real &mass_weighted_velocity, + parthenon::Real &mass_weighted_cs, + parthenon::MeshData *md) const; + + // Remove gas consistent with Bondi accretion + /// i.e. proportional to the accretion rate, weighted by the local gas mass + template + void RemoveBondiAccretedGas(parthenon::MeshData *md, + const parthenon::Real dt, const EOS eos) const; + + // Compute and return the current AGN accretion rate from already globally + // reduced quantities + parthenon::Real GetAccretionRate(parthenon::StateDescriptor *hydro_pkg) const; + + friend parthenon::TaskStatus + AGNTriggeringResetTriggering(parthenon::StateDescriptor *hydro_pkg); + + friend parthenon::TaskStatus + AGNTriggeringReduceTriggering(parthenon::MeshData *md, + const parthenon::Real dt); + + friend parthenon::TaskStatus + AGNTriggeringMPIReduceTriggering(parthenon::StateDescriptor *hydro_pkg); + + friend parthenon::TaskStatus + AGNTriggeringFinalizeTriggering(parthenon::MeshData *md, + const parthenon::SimTime &tm); + + // Limit timestep to a factor of the cold gas accretion time for Cold Gas + // triggered cooling, or a factor of the time to accrete the total mass for + // Bondi-like accretion + parthenon::Real EstimateTimeStep(parthenon::MeshData *md) const; +}; + +parthenon::TaskStatus AGNTriggeringResetTriggering(parthenon::StateDescriptor *hydro_pkg); + +parthenon::TaskStatus +AGNTriggeringReduceTriggering(parthenon::MeshData *md, + const parthenon::Real dt); + +parthenon::TaskStatus +AGNTriggeringMPIReduceTriggering(parthenon::StateDescriptor *hydro_pkg); + +parthenon::TaskStatus +AGNTriggeringFinalizeTriggering(parthenon::MeshData *md, + const parthenon::SimTime &tm); + +} // namespace cluster + +#endif // CLUSTER_AGN_TRIGGERING_HPP_ diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 2c7c2a18..6998fb0b 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -33,14 +33,14 @@ class ClusterGravity { bool include_smbh_g_; // NFW Parameters - parthenon::Real R_nfw_s_; + parthenon::Real r_nfw_s_; parthenon::Real GMC_nfw_; // G , Mass, and Constants rolled into one, to minimize footprint // BCG Parameters parthenon::Real alpha_bcg_s_; parthenon::Real beta_bcg_s_; - parthenon::Real R_bcg_s_; + parthenon::Real r_bcg_s_; parthenon::Real GMC_bcg_; // G , Mass, and Constants rolled into one, to minimize footprint @@ -53,49 +53,51 @@ class ClusterGravity { // Static Helper functions to calculate constants to minimize in-kernel work static parthenon::Real calc_R_nfw_s(const parthenon::Real rho_crit, - const parthenon::Real M_nfw_200, + const parthenon::Real m_nfw_200, const parthenon::Real c_nfw) { const parthenon::Real rho_nfw_0 = 200 / 3. * rho_crit * pow(c_nfw, 3.) / (log(1 + c_nfw) - c_nfw / (1 + c_nfw)); const parthenon::Real R_nfw_s = - pow(M_nfw_200 / (4 * M_PI * rho_nfw_0 * (log(1 + c_nfw) - c_nfw / (1 + c_nfw))), + pow(m_nfw_200 / (4 * M_PI * rho_nfw_0 * (log(1 + c_nfw) - c_nfw / (1 + c_nfw))), 1. / 3.); return R_nfw_s; } static parthenon::Real calc_GMC_nfw(const parthenon::Real gravitational_constant, - const parthenon::Real M_nfw_200, + const parthenon::Real m_nfw_200, const parthenon::Real c_nfw) { - return gravitational_constant * M_nfw_200 / (log(1 + c_nfw) - c_nfw / (1 + c_nfw)); + return gravitational_constant * m_nfw_200 / (log(1 + c_nfw) - c_nfw / (1 + c_nfw)); } static parthenon::Real calc_GMC_bcg(const parthenon::Real gravitational_constant, - BCG which_bcg_g, const parthenon::Real M_bcg_s, - const parthenon::Real R_bcg_s, + BCG which_bcg_g, const parthenon::Real m_bcg_s, + const parthenon::Real r_bcg_s, const parthenon::Real alpha_bcg_s, const parthenon::Real beta_bcg_s) { switch (which_bcg_g) { case BCG::NONE: return 0; case BCG::MATHEWS: - return 1 / (R_bcg_s * R_bcg_s); + return 1 / (r_bcg_s * r_bcg_s); case BCG::HERNQUIST: - return gravitational_constant * M_bcg_s / (R_bcg_s * R_bcg_s); + return gravitational_constant * m_bcg_s / (r_bcg_s * r_bcg_s); } return NAN; } static KOKKOS_INLINE_FUNCTION parthenon::Real calc_GMC_smbh(const parthenon::Real gravitational_constant, - const parthenon::Real M_smbh) { - return gravitational_constant * M_smbh; + const parthenon::Real m_smbh) { + return gravitational_constant * m_smbh; } public: - ClusterGravity(parthenon::ParameterInput *pin) { + ClusterGravity(parthenon::ParameterInput *pin, + const std::shared_ptr &hydro_pkg) { Units units(pin); // Determine which element to include - include_nfw_g_ = pin->GetOrAddBoolean("problem/cluster", "include_nfw_g", false); + include_nfw_g_ = + pin->GetOrAddBoolean("problem/cluster/gravity", "include_nfw_g", false); const std::string which_bcg_g_str = - pin->GetOrAddString("problem/cluster", "which_bcg_g", "NONE"); + pin->GetOrAddString("problem/cluster/gravity", "which_bcg_g", "NONE"); if (which_bcg_g_str == "NONE") { which_bcg_g_ = BCG::NONE; } else if (which_bcg_g_str == "MATHEWS") { @@ -109,7 +111,8 @@ class ClusterGravity { PARTHENON_FAIL(msg); } - include_smbh_g_ = pin->GetOrAddBoolean("problem/cluster", "include_smbh_g", false); + include_smbh_g_ = + pin->GetOrAddBoolean("problem/cluster/gravity", "include_smbh_g", false); // Initialize the NFW Profile const parthenon::Real hubble_parameter = pin->GetOrAddReal( @@ -118,25 +121,29 @@ class ClusterGravity { (8 * M_PI * units.gravitational_constant()); const parthenon::Real M_nfw_200 = - pin->GetOrAddReal("problem/cluster", "M_nfw_200", 8.5e14 * units.msun()); - const parthenon::Real c_nfw = pin->GetOrAddReal("problem/cluster", "c_nfw", 6.81); - R_nfw_s_ = calc_R_nfw_s(rho_crit, M_nfw_200, c_nfw); + pin->GetOrAddReal("problem/cluster/gravity", "m_nfw_200", 8.5e14 * units.msun()); + const parthenon::Real c_nfw = + pin->GetOrAddReal("problem/cluster/gravity", "c_nfw", 6.81); + r_nfw_s_ = calc_R_nfw_s(rho_crit, M_nfw_200, c_nfw); GMC_nfw_ = calc_GMC_nfw(units.gravitational_constant(), M_nfw_200, c_nfw); // Initialize the NFW Profile - alpha_bcg_s_ = pin->GetOrAddReal("problem/cluster", "alpha_bcg_s", 0.1); - beta_bcg_s_ = pin->GetOrAddReal("problem/cluster", "beta_bcg_s", 1.43); + alpha_bcg_s_ = pin->GetOrAddReal("problem/cluster/gravity", "alpha_bcg_s", 0.1); + beta_bcg_s_ = pin->GetOrAddReal("problem/cluster/gravity", "beta_bcg_s", 1.43); const parthenon::Real M_bcg_s = - pin->GetOrAddReal("problem/cluster", "M_bcg_s", 7.5e10 * units.msun()); - R_bcg_s_ = pin->GetOrAddReal("problem/cluster", "R_bcg_s", 4 * units.kpc()); + pin->GetOrAddReal("problem/cluster/gravity", "m_bcg_s", 7.5e10 * units.msun()); + r_bcg_s_ = pin->GetOrAddReal("problem/cluster/gravity", "r_bcg_s", 4 * units.kpc()); GMC_bcg_ = calc_GMC_bcg(units.gravitational_constant(), which_bcg_g_, M_bcg_s, - R_bcg_s_, alpha_bcg_s_, beta_bcg_s_); + r_bcg_s_, alpha_bcg_s_, beta_bcg_s_); const parthenon::Real m_smbh = - pin->GetOrAddReal("problem/cluster", "m_smbh", 3.4e8 * units.msun()); + pin->GetOrAddReal("problem/cluster/gravity", "m_smbh", 3.4e8 * units.msun()); GMC_smbh_ = calc_GMC_smbh(units.gravitational_constant(), m_smbh), - smoothing_r_ = pin->GetOrAddReal("problem/cluster", "g_smoothing_radius", 0.0); + smoothing_r_ = + pin->GetOrAddReal("problem/cluster/gravity", "g_smoothing_radius", 0.0); + + hydro_pkg->AddParam<>("cluster_gravity", *this); } // Inline functions to compute gravitational acceleration @@ -150,7 +157,7 @@ class ClusterGravity { // Add NFW gravity if (include_nfw_g_) { - g_r += GMC_nfw_ * (log(1 + r / R_nfw_s_) - r / (r + R_nfw_s_)) / r2; + g_r += GMC_nfw_ * (log(1 + r / r_nfw_s_) - r / (r + r_nfw_s_)) / r2; } // Add BCG gravity @@ -160,12 +167,12 @@ class ClusterGravity { case BCG::MATHEWS: { const parthenon::Real s_bcg = 0.9; g_r += GMC_bcg_ * // Note: *cm**3*s**-2 //To make units work - pow(pow(r / R_bcg_s_, 0.5975 / 3.206e-7 * s_bcg) + - pow(pow(r / R_bcg_s_, 1.849 / 1.861e-6), s_bcg), + pow(pow(r / r_bcg_s_, 0.5975 / 3.206e-7 * s_bcg) + + pow(pow(r / r_bcg_s_, 1.849 / 1.861e-6), s_bcg), -1 / s_bcg); } break; case BCG::HERNQUIST: - g_r += GMC_bcg_ / ((1 + r / R_bcg_s_) * (1 + r / R_bcg_s_)); + g_r += GMC_bcg_ / ((1 + r / r_bcg_s_) * (1 + r / r_bcg_s_)); break; } diff --git a/src/pgen/cluster/cluster_utils.hpp b/src/pgen/cluster/cluster_utils.hpp new file mode 100644 index 00000000..11f30b39 --- /dev/null +++ b/src/pgen/cluster/cluster_utils.hpp @@ -0,0 +1,88 @@ +#ifndef CLUSTER_CLUSTER_UTILS_HPP_ +#define CLUSTER_CLUSTER_UTILS_HPP_ +//======================================================================================== +// Athena++ astrophysical MHD code +// Copyright(C) 2014 James M. Stone and other code contributors +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file cluster_utils.hpp +// \brief Utilities for galaxy cluster functions + +// parthenon headers +#include + +// AthenaPK headers +#include "../../eos/adiabatic_glmmhd.hpp" +#include "../../eos/adiabatic_hydro.hpp" +#include "utils/error_checking.hpp" + +namespace cluster { + +// Add a density to the conserved variables while keeping velocity fixed +template +KOKKOS_INLINE_FUNCTION void +AddDensityToConsAtFixedVel(const parthenon::Real density, View4D &cons, + const View4D &prim, const AdiabaticHydroEOS &eos, const int &k, + const int &j, const int &i) { + // Add density such that velocity and temperature (propto pressure/density) is fixed + cons(IDN, k, j, i) += density; + cons(IM1, k, j, i) += density * prim(IV1, k, j, i); + cons(IM2, k, j, i) += density * prim(IV2, k, j, i); + cons(IM3, k, j, i) += density * prim(IV3, k, j, i); + cons(IEN, k, j, i) += + density * (0.5 * (SQR(prim(IV1, k, j, i)) + SQR(prim(IV2, k, j, i)) + + SQR(prim(IV3, k, j, i)))); +} + +template +KOKKOS_INLINE_FUNCTION void +AddDensityToConsAtFixedVel(const parthenon::Real density, View4D &cons, + const View4D &prim, const AdiabaticGLMMHDEOS &eos, + const int &k, const int &j, const int &i) { + // Add density such that velocity and temperature (propto pressure/density) is fixed + cons(IDN, k, j, i) += density; + cons(IM1, k, j, i) += density * prim(IV1, k, j, i); + cons(IM2, k, j, i) += density * prim(IV2, k, j, i); + cons(IM3, k, j, i) += density * prim(IV3, k, j, i); + cons(IEN, k, j, i) += + density * (0.5 * (SQR(prim(IV1, k, j, i)) + SQR(prim(IV2, k, j, i)) + + SQR(prim(IV3, k, j, i)))); +} + +// Add a density to the conserved variables while keeping velocity and +// temperature ( propto pressure/density) fixed +template +KOKKOS_INLINE_FUNCTION void +AddDensityToConsAtFixedVelTemp(const parthenon::Real density, View4D &cons, + const View4D &prim, const AdiabaticHydroEOS &eos, + const int &k, const int &j, const int &i) { + // Add density such that velocity and temperature (propto pressure/density) is fixed + cons(IDN, k, j, i) += density; + cons(IM1, k, j, i) += density * prim(IV1, k, j, i); + cons(IM2, k, j, i) += density * prim(IV2, k, j, i); + cons(IM3, k, j, i) += density * prim(IV3, k, j, i); + cons(IEN, k, j, i) += + density * (0.5 * (SQR(prim(IV1, k, j, i)) + SQR(prim(IV2, k, j, i)) + + SQR(prim(IV3, k, j, i))) + + 1 / (eos.GetGamma() - 1.0) * prim(IPR, k, j, i) / prim(IDN, k, j, i)); +} + +template +KOKKOS_INLINE_FUNCTION void +AddDensityToConsAtFixedVelTemp(const parthenon::Real density, View4D &cons, + const View4D &prim, const AdiabaticGLMMHDEOS &eos, + const int &k, const int &j, const int &i) { + // Add density such that velocity and temperature (propto pressure/density) is fixed + cons(IDN, k, j, i) += density; + cons(IM1, k, j, i) += density * prim(IV1, k, j, i); + cons(IM2, k, j, i) += density * prim(IV2, k, j, i); + cons(IM3, k, j, i) += density * prim(IV3, k, j, i); + cons(IEN, k, j, i) += + density * (0.5 * (SQR(prim(IV1, k, j, i)) + SQR(prim(IV2, k, j, i)) + + SQR(prim(IV3, k, j, i))) + + 1 / (eos.GetGamma() - 1.0) * prim(IPR, k, j, i) / prim(IDN, k, j, i)); +} + +} // namespace cluster + +#endif // CLUSTER_CLUSTER_UTILS_HPP_ diff --git a/src/pgen/cluster/entropy_profiles.hpp b/src/pgen/cluster/entropy_profiles.hpp index b45ef632..5e7d2361 100644 --- a/src/pgen/cluster/entropy_profiles.hpp +++ b/src/pgen/cluster/entropy_profiles.hpp @@ -19,24 +19,24 @@ namespace cluster { class ACCEPTEntropyProfile { private: // Entropy Profile - parthenon::Real K_0_, K_100_, R_K_, alpha_K_; + parthenon::Real k_0_, k_100_, r_k_, alpha_k_; public: ACCEPTEntropyProfile(parthenon::ParameterInput *pin) { Units units(pin); - K_0_ = pin->GetOrAddReal("problem/cluster", "K_0", + k_0_ = pin->GetOrAddReal("problem/cluster/entropy_profile", "k_0", 20 * units.kev() * units.cm() * units.cm()); - K_100_ = pin->GetOrAddReal("problem/cluster", "K_100", + k_100_ = pin->GetOrAddReal("problem/cluster/entropy_profile", "k_100", 120 * units.kev() * units.cm() * units.cm()); - R_K_ = pin->GetOrAddReal("problem/cluster", "R_K", 100 * units.kpc()); - alpha_K_ = pin->GetOrAddReal("problem/cluster", "alpha_K", 1.75); + r_k_ = pin->GetOrAddReal("problem/cluster/entropy_profile", "r_k", 100 * units.kpc()); + alpha_k_ = pin->GetOrAddReal("problem/cluster/entropy_profile", "alpha_k", 1.75); } // Get entropy from radius, using broken power law profile for entropy - parthenon::Real K_from_r(const parthenon::Real r) const { - const parthenon::Real K = K_0_ + K_100_ * pow(r / R_K_, alpha_K_); - return K; + KOKKOS_INLINE_FUNCTION parthenon::Real K_from_r(const parthenon::Real r) const { + const parthenon::Real k = k_0_ + k_100_ * pow(r / r_k_, alpha_k_); + return k; } }; diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp index 9dd09d88..a45a6dd4 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp @@ -35,9 +35,9 @@ using namespace parthenon; ************************************************************/ template HydrostaticEquilibriumSphere:: - HydrostaticEquilibriumSphere(ParameterInput *pin, - GravitationalField gravitational_field, - EntropyProfile entropy_profile) + HydrostaticEquilibriumSphere( + ParameterInput *pin, const std::shared_ptr &hydro_pkg, + GravitationalField gravitational_field, EntropyProfile entropy_profile) : gravitational_field_(gravitational_field), entropy_profile_(entropy_profile) { Units units(pin); @@ -50,31 +50,33 @@ HydrostaticEquilibriumSphere:: mu_ = 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); mu_e_ = 1 / (He_mass_fraction * 2. / 4. + (1 - He_mass_fraction)); - R_fix_ = - pin->GetOrAddReal("problem/cluster", "R_fix", 1953.9724519818478 * units.kpc()); - rho_fix_ = pin->GetOrAddReal("problem/cluster", "rho_fix", + r_fix_ = pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", "r_fix", + 1953.9724519818478 * units.kpc()); + rho_fix_ = pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", "rho_fix", 8.607065015897638e-30 * units.g() / pow(units.kpc(), 3)); const Real gam = pin->GetReal("hydro", "gamma"); const Real gm1 = (gam - 1.0); - R_sampling_ = pin->GetOrAddReal("problem/cluster", "R_sampling", 4.0); - max_dR_ = pin->GetOrAddReal("problem/cluster", "max_dR", 1e-3); + r_sampling_ = + pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", "r_sampling", 4.0); + max_dr_ = pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", "max_dr", 1e-3); // Test out the HSE sphere if requested - const bool test_he_sphere = - pin->GetOrAddBoolean("problem/cluster", "test_he_sphere", false); + const bool test_he_sphere = pin->GetOrAddBoolean( + "problem/cluster/hydrostatic_equilibrium", "test_he_sphere", false); if (test_he_sphere) { - const Real test_he_sphere_R_start = pin->GetOrAddReal( - "problem/cluster", "test_he_sphere_R_start_kpc", 1e-3 * units.kpc()); - const Real test_he_sphere_R_end = pin->GetOrAddReal( - "problem/cluster", "test_he_sphere_R_end_kpc", 4000 * units.kpc()); - const int test_he_sphere_n_r = - pin->GetOrAddInteger("problem/cluster", "test_he_sphere_n_r", 4000); + const Real test_he_sphere_r_start = + pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", + "test_he_sphere_r_start_kpc", 1e-3 * units.kpc()); + const Real test_he_sphere_r_end = + pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", + "test_he_sphere_r_end_kpc", 4000 * units.kpc()); + const int test_he_sphere_n_r = pin->GetOrAddInteger( + "problem/cluster/hydrostatic_equilibrium", "test_he_sphere_n_r", 4000); if (Globals::my_rank == 0) { - typedef Kokkos::View View1D; - auto P_rho_profile = generate_P_rho_profile( - test_he_sphere_R_start, test_he_sphere_R_end, test_he_sphere_n_r); + auto P_rho_profile = generate_P_rho_profile( + test_he_sphere_r_start, test_he_sphere_r_end, test_he_sphere_n_r); std::ofstream test_he_file; test_he_file.open("test_he_sphere.dat"); @@ -82,88 +84,49 @@ HydrostaticEquilibriumSphere:: test_he_file.close(); } } -} - -/************************************************************ - * PRhoProfile::P_from_r - ************************************************************/ -template -template -Real HydrostaticEquilibriumSphere::PRhoProfile< - View1D>::P_from_r(const Real r) const { - - // Determine indices in R bounding r - const int i_r = - static_cast(floor((n_R_ - 1) / (R_end_ - R_start_) * (r - R_start_))); - - if (r < R_(i_r) - kRTol || r > R_(i_r + 1) + kRTol) { - std::stringstream msg; - msg << "### FATAL ERROR in function [HydrostaticEquilibriumSphere::PRhoProfile]" - << std::endl - << "R(i_r) to R_(i_r+1) does not contain r" << std::endl - << "R(i_r) R_r R(i_r+1):" << R_(i_r) << " " << r << " " << R_(i_r + 1) - << std::endl; - PARTHENON_FAIL(msg); - } - // Linearly interpolate Pressure from P - const Real P_r = (P_(i_r) * (R_(i_r + 1) - r) + P_(i_r + 1) * (r - R_(i_r))) / - (R_(i_r + 1) - R_(i_r)); - - return P_r; -} - -/************************************************************ - * PRhoProfile::rho_from_r - ************************************************************/ -template -template -Real HydrostaticEquilibriumSphere::PRhoProfile< - View1D>::rho_from_r(const Real r) const { - - // Get pressure first - const Real P_r = P_from_r(r); - // Compute entropy and pressure here - const Real K_r = sphere_.entropy_profile_.K_from_r(r); - const Real rho_r = sphere_.rho_from_P_K(P_r, K_r); - return rho_r; + hydro_pkg->AddParam<>("hydrostatic_equilibirum_sphere", *this); } /************************************************************ * PRhoProfile::write_to_ostream ************************************************************/ -template -template -std::ostream & -HydrostaticEquilibriumSphere::PRhoProfile< - View1D>::write_to_ostream(std::ostream &os) const { - - const dP_dr_from_r_P_functor dP_dr_func(sphere_); - for (int i = 0; i < R_.extent(0); i++) { - const Real r = R_(i); - const Real P = P_(i); - const Real K = sphere_.entropy_profile_.K_from_r(r); - const Real rho = sphere_.rho_from_P_K(P, K); +template +std::ostream &PRhoProfile::write_to_ostream( + std::ostream &os) const { + + const typename HydrostaticEquilibriumSphere< + GravitationalField, EntropyProfile>::dP_dr_from_r_P_functor dP_dr_func(sphere_); + + auto host_r = Kokkos::create_mirror_view(r_); + Kokkos::deep_copy(host_r, r_); + auto host_p = Kokkos::create_mirror_view(p_); + Kokkos::deep_copy(host_p, p_); + + for (int i = 0; i < host_r.extent(0); i++) { + const Real r = host_r(i); + const Real p = host_p(i); + const Real k = sphere_.entropy_profile_.K_from_r(r); + const Real rho = sphere_.rho_from_P_K(p, k); const Real n = sphere_.n_from_rho(rho); const Real ne = sphere_.ne_from_rho(rho); - const Real T = sphere_.T_from_rho_P(rho, P); + const Real temp = sphere_.T_from_rho_P(rho, p); const Real g = sphere_.gravitational_field_.g_from_r(r); - const Real dP_dr = dP_dr_func(r, P); + const Real dP_dr = dP_dr_func(r, p); - os << r << " " << P << " " << K << " " << rho << " " << n << " " << ne << " " << T + os << r << " " << p << " " << k << " " << rho << " " << n << " " << ne << " " << temp << " " << g << " " << dP_dr << std::endl; } return os; } /************************************************************ - * HydrostaticEquilibriumSphere::generate_P_rho_profile(x,y,z) + *HydrostaticEquilibriumSphere::generate_P_rho_profile(x,y,z) ************************************************************/ -template -template -typename HydrostaticEquilibriumSphere::template PRhoProfile -HydrostaticEquilibriumSphere::generate_P_rho_profile( +template +template +PRhoProfile +HydrostaticEquilibriumSphere::generate_P_rho_profile( IndexRange ib, IndexRange jb, IndexRange kb, Coords coords) const { /************************************************************ @@ -173,19 +136,19 @@ HydrostaticEquilibriumSphere::generate_P_rho ************************************************************/ // Determine spacing of grid (WARNING assumes equispaced grid in x,y,z) - PARTHENON_REQUIRE(coords.dx1v(0) == coords.dx1v(1), "No equidistant grid in x1dir"); - PARTHENON_REQUIRE(coords.dx2v(0) == coords.dx2v(1), "No equidistant grid in x2dir"); - PARTHENON_REQUIRE(coords.dx3v(0) == coords.dx3v(1), "No equidistant grid in x3dir"); - PARTHENON_REQUIRE(coords.dx1v(0) == coords.dx2v(1), - "No equidistant grid between x1 and x2 dir"); - PARTHENON_REQUIRE(coords.dx2v(0) == coords.dx3v(1), - "No equidistant grid between x2 and x3 dir"); - const Real dR = std::min(coords.dx1v(0) / R_sampling_, max_dR_); + // FIXME(forrestglines) There's some floating point comparison issues with these tests + // PARTHENON_REQUIRE(coords.dx1v(0) == coords.dx1v(1), "No equidistant grid in x1dir"); + // PARTHENON_REQUIRE(coords.dx2v(0) == coords.dx2v(1), "No equidistant grid in x2dir"); + // PARTHENON_REQUIRE(coords.dx3v(0) == coords.dx3v(1), "No equidistant grid in x3dir"); + // PARTHENON_REQUIRE(coords.dx1v(0) == coords.dx2v(1), "No equidistant grid between x1 + // and x2 dir"); PARTHENON_REQUIRE(coords.dx2v(0) == coords.dx3v(1), "No equidistant + // grid between x2 and x3 dir"); + const Real dr = std::min(coords.dx1v(0) / r_sampling_, max_dr_); // Loop through mesh for minimum and maximum radius // Make sure to include R_fix_ - Real R_start = R_fix_; - Real R_end = R_fix_; + Real r_start = r_fix_; + Real r_end = r_fix_; for (int k = kb.s; k <= kb.e; k++) { for (int j = jb.s; j <= jb.e; j++) { for (int i = ib.s; i <= ib.e; i++) { @@ -193,66 +156,66 @@ HydrostaticEquilibriumSphere::generate_P_rho const Real r = sqrt(coords.x1v(i) * coords.x1v(i) + coords.x2v(j) * coords.x2v(j) + coords.x3v(k) * coords.x3v(k)); - R_start = std::min(r, R_start); - R_end = std::max(r, R_end); + r_start = std::min(r, r_start); + r_end = std::max(r, r_end); } } } // Add some room for R_start and R_end - R_start = std::max(0.0, R_start - R_sampling_ * dR); - R_end += R_sampling_ * dR; + r_start = std::max(0.0, r_start - r_sampling_ * dr); + r_end += r_sampling_ * dr; // Compute number of cells needed - const unsigned int n_R = static_cast(ceil((R_end - R_start) / dR)); + const auto n_r = static_cast(ceil((r_end - r_start) / dr)); // Make R_end consistent - R_end = R_start + dR * (n_R - 1); + r_end = r_start + dr * (n_r - 1); - return generate_P_rho_profile(R_start, R_end, n_R); + return generate_P_rho_profile(r_start, r_end, n_r); } /************************************************************ * HydrostaticEquilibriumSphere::generate_P_rho_profile(Ri,Re,nR) ************************************************************/ -template -template -typename HydrostaticEquilibriumSphere::template PRhoProfile -HydrostaticEquilibriumSphere::generate_P_rho_profile( - const Real R_start, const Real R_end, const unsigned int n_R) const { +template +PRhoProfile +HydrostaticEquilibriumSphere::generate_P_rho_profile( + const Real r_start, const Real r_end, const unsigned int n_r) const { // Array of radii along which to compute the profile - View1D R("R", n_R); - const Real dR = (R_end - R_start) / (n_R - 1.0); + ParArray1D device_r("PRhoProfile r", n_r); + auto r = Kokkos::create_mirror_view(device_r); + const Real dr = (r_end - r_start) / (n_r - 1.0); // Use a linear R - possibly adapt if using a mesh with logrithmic r - for (int i = 0; i < n_R; i++) { - R(i) = R_start + i * dR; + for (int i = 0; i < n_r; i++) { + r(i) = r_start + i * dr; } /************************************************************ * Integrate Pressure inward and outward from virial radius ************************************************************/ // Create array for pressure - View1D P("P", n_R); + ParArray1D device_p("PRhoProfile p", n_r); + auto p = Kokkos::create_mirror_view(device_p); - const Real K_fix = entropy_profile_.K_from_r(R_fix_); - const Real P_fix = P_from_rho_K(rho_fix_, K_fix); + const Real k_fix = entropy_profile_.K_from_r(r_fix_); + const Real p_fix = P_from_rho_K(rho_fix_, k_fix); // Integrate P inward from R_fix_ - Real Ri = R_fix_; // Start Ri at R_fix_ first - Real Pi = P_fix; // Start with pressure at R_fix_ + Real r_i = r_fix_; // Start Ri at R_fix_ first + Real p_i = p_fix; // Start with pressure at R_fix_ // Find the index in R right before R_fix_ - int i_fix = static_cast(floor((n_R - 1) / (R_end - R_start) * (R_fix_ - R_start))); - if (R_fix_ < R(i_fix) - kRTol || R_fix_ > R(i_fix + 1) + kRTol) { + int i_fix = static_cast(floor((n_r - 1) / (r_end - r_start) * (r_fix_ - r_start))); + if (r_fix_ < r(i_fix) - kRTol || r_fix_ > r(i_fix + 1) + kRTol) { std::stringstream msg; msg << "### FATAL ERROR in function " "[HydrostaticEquilibriumSphere::generate_P_rho_profile]" << std::endl - << "R(i_fix) to R_(i_fix+1) does not contain R_fix_" << std::endl - << "R(i_fix) R_fix_ R(i_fix+1):" << R(i_fix) << " " << R_fix_ << " " - << R(i_fix + 1) << std::endl; + << "r(i_fix) to r_(i_fix+1) does not contain r_fix_" << std::endl + << "r(i_fix) r_fix_ r(i_fix+1):" << r(i_fix) << " " << r_fix_ << " " + << r(i_fix + 1) << std::endl; PARTHENON_FAIL(msg); } @@ -260,64 +223,41 @@ HydrostaticEquilibriumSphere::generate_P_rho // Make is the i right before R_fix_ for (int i = i_fix + 1; i > 0; i--) { // Move is up one, to account for initial R_fix_ - P(i - 1) = step_rk4(Ri, R(i - 1), Pi, dP_dr_from_r_P); - Ri = R(i - 1); - Pi = P(i - 1); + p(i - 1) = step_rk4(r_i, r(i - 1), p_i, dP_dr_from_r_P); + r_i = r(i - 1); + p_i = p(i - 1); } // Integrate P outward from R_fix_ - Ri = R_fix_; // Start Ri at R_fix_ first - Pi = P_fix; // Start with pressure at R_fix_ + r_i = r_fix_; // Start Ri at R_fix_ first + p_i = p_fix; // Start with pressure at R_fix_ // Make is the i right after R_fix_ - for (int i = i_fix; i < n_R - 1; + for (int i = i_fix; i < n_r - 1; i++) { // Move is back one, to account for initial R_fix_ - P(i + 1) = step_rk4(Ri, R(i + 1), Pi, dP_dr_from_r_P); - Ri = R(i + 1); - Pi = P(i + 1); + p(i + 1) = step_rk4(r_i, r(i + 1), p_i, dP_dr_from_r_P); + r_i = r(i + 1); + p_i = p(i + 1); } - return PRhoProfile(R, P, *this); + Kokkos::deep_copy(device_r, r); + Kokkos::deep_copy(device_p, p); + + return PRhoProfile(device_r, device_p, r(0), + r(n_r - 1), *this); } // Instantiate HydrostaticEquilibriumSphere template class HydrostaticEquilibriumSphere; // Instantiate PRhoProfile -template class HydrostaticEquilibriumSphere:: - PRhoProfile>; -#if defined(KOKKOS_ENABLE_CUDA) -template class HydrostaticEquilibriumSphere:: - PRhoProfile>; -#endif +template class PRhoProfile; // Instantiate generate_P_rho_profile -template HydrostaticEquilibriumSphere::PRhoProfile< - parthenon::ParArray1D> +template PRhoProfile HydrostaticEquilibriumSphere:: - generate_P_rho_profile, - parthenon::UniformCartesian>( + generate_P_rho_profile( parthenon::IndexRange, parthenon::IndexRange, parthenon::IndexRange, parthenon::UniformCartesian) const; -template HydrostaticEquilibriumSphere::PRhoProfile< - parthenon::ParArray1D> -HydrostaticEquilibriumSphere:: - generate_P_rho_profile>( - const parthenon::Real, const parthenon::Real, const unsigned int) const; -#if defined(KOKKOS_ENABLE_CUDA) -template HydrostaticEquilibriumSphere::PRhoProfile< - Kokkos::View> - HydrostaticEquilibriumSphere:: - generate_P_rho_profile< - Kokkos::View, - parthenon::UniformCartesian>(parthenon::IndexRange, parthenon::IndexRange, - parthenon::IndexRange, - parthenon::UniformCartesian) const; -template HydrostaticEquilibriumSphere::PRhoProfile< - Kokkos::View> -HydrostaticEquilibriumSphere:: - generate_P_rho_profile>( - const parthenon::Real, const parthenon::Real, const unsigned int) const; -#endif } // namespace cluster diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp index 80c08548..6832e29d 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp @@ -18,6 +18,9 @@ namespace cluster { +template +class PRhoProfile; + /************************************************************ * Hydrostatic Equilbrium Spnere Class, * for initializing a sphere in hydrostatic equiblibrium @@ -39,13 +42,13 @@ class HydrostaticEquilibriumSphere { parthenon::Real atomic_mass_unit_, k_boltzmann_; // Density to fix baryons at a radius (change to temperature?) - parthenon::Real R_fix_, rho_fix_; + parthenon::Real r_fix_, rho_fix_; // Molecular weights parthenon::Real mu_, mu_e_; // R mesh sampling parameters - parthenon::Real R_sampling_, max_dR_; + parthenon::Real r_sampling_, max_dr_; /************************************************************ * Functions to build the cluster model @@ -56,35 +59,38 @@ class HydrostaticEquilibriumSphere { // Get pressure from density and entropy, using ideal gas law and definition // of entropy - parthenon::Real P_from_rho_K(const parthenon::Real rho, const parthenon::Real K) const { - const parthenon::Real P = - K * pow(mu_ / mu_e_, 2. / 3.) * pow(rho / (mu_ * atomic_mass_unit_), 5. / 3.); - return P; + KOKKOS_INLINE_FUNCTION parthenon::Real P_from_rho_K(const parthenon::Real rho, + const parthenon::Real k) const { + const parthenon::Real p = + k * pow(mu_ / mu_e_, 2. / 3.) * pow(rho / (mu_ * atomic_mass_unit_), 5. / 3.); + return p; } // Get density from pressure and entropy, using ideal gas law and definition // of entropy - parthenon::Real rho_from_P_K(const parthenon::Real P, const parthenon::Real K) const { + KOKKOS_INLINE_FUNCTION parthenon::Real rho_from_P_K(const parthenon::Real p, + const parthenon::Real k) const { const parthenon::Real rho = - pow(P / K, 3. / 5.) * mu_ * atomic_mass_unit_ / pow(mu_ / mu_e_, 2. / 5); + pow(p / k, 3. / 5.) * mu_ * atomic_mass_unit_ / pow(mu_ / mu_e_, 2. / 5); return rho; } // Get total number density from density - parthenon::Real n_from_rho(const parthenon::Real rho) const { + KOKKOS_INLINE_FUNCTION parthenon::Real n_from_rho(const parthenon::Real rho) const { const parthenon::Real n = rho / (mu_ * atomic_mass_unit_); return n; } // Get electron number density from density - parthenon::Real ne_from_rho(const parthenon::Real rho) const { + KOKKOS_INLINE_FUNCTION parthenon::Real ne_from_rho(const parthenon::Real rho) const { const parthenon::Real ne = mu_ / mu_e_ * n_from_rho(rho); return ne; } // Get the temperature from density and pressure - parthenon::Real T_from_rho_P(const parthenon::Real rho, const parthenon::Real P) const { - const parthenon::Real T = P / (n_from_rho(rho) * k_boltzmann_); + KOKKOS_INLINE_FUNCTION parthenon::Real T_from_rho_P(const parthenon::Real rho, + const parthenon::Real p) const { + const parthenon::Real T = p / (n_from_rho(rho) * k_boltzmann_); return T; } @@ -102,11 +108,11 @@ class HydrostaticEquilibriumSphere { dP_dr_from_r_P_functor( const HydrostaticEquilibriumSphere &sphere) : sphere_(sphere) {} - parthenon::Real operator()(const parthenon::Real r, const parthenon::Real P) const { + parthenon::Real operator()(const parthenon::Real r, const parthenon::Real p) const { const parthenon::Real g = sphere_.gravitational_field_.g_from_r(r); - const parthenon::Real K = sphere_.entropy_profile_.K_from_r(r); - const parthenon::Real rho = sphere_.rho_from_P_K(P, K); + const parthenon::Real k = sphere_.entropy_profile_.K_from_r(r); + const parthenon::Real rho = sphere_.rho_from_P_K(p, k); const parthenon::Real dP_dr = -rho * g; return dP_dr; } @@ -128,40 +134,70 @@ class HydrostaticEquilibriumSphere { static constexpr parthenon::Real kRTol = 1e-15; public: - HydrostaticEquilibriumSphere(parthenon::ParameterInput *pin, - GravitationalField gravitational_field, - EntropyProfile entropy_profile); + HydrostaticEquilibriumSphere( + parthenon::ParameterInput *pin, + const std::shared_ptr &hydro_pkg, + GravitationalField gravitational_field, EntropyProfile entropy_profile); - template - class PRhoProfile { - private: - const View1D R_; - const View1D P_; - const HydrostaticEquilibriumSphere &sphere_; + template + PRhoProfile + generate_P_rho_profile(parthenon::IndexRange ib, parthenon::IndexRange jb, + parthenon::IndexRange kb, Coords coords) const; - const int n_R_; - const parthenon::Real R_start_, R_end_; + PRhoProfile + generate_P_rho_profile(const parthenon::Real r_start, const parthenon::Real r_end, + const unsigned int n_R) const; - public: - PRhoProfile(const View1D R, const View1D P, - const HydrostaticEquilibriumSphere &sphere) - : R_(R), P_(P), sphere_(sphere), n_R_(R_.extent(0)), R_start_(R_(0)), - R_end_(R_(n_R_ - 1)) {} - - parthenon::Real P_from_r(const parthenon::Real r) const; - parthenon::Real rho_from_r(const parthenon::Real r) const; - std::ostream &write_to_ostream(std::ostream &os) const; - }; + template + friend class PRhoProfile; +}; - template - PRhoProfile - generate_P_rho_profile(parthenon::IndexRange ib, parthenon::IndexRange jb, - parthenon::IndexRange kb, Coords coords) const; +template +class PRhoProfile { + private: + const parthenon::ParArray1D r_; + const parthenon::ParArray1D p_; + const HydrostaticEquilibriumSphere sphere_; + + const int n_r_; + const parthenon::Real r_start_, r_end_; + + public: + PRhoProfile( + const parthenon::ParArray1D &r, + const parthenon::ParArray1D &p, const parthenon::Real r_start, + const parthenon::Real r_end, + const HydrostaticEquilibriumSphere &sphere) + : r_(r), p_(p), sphere_(sphere), n_r_(r_.extent(0)), r_start_(r_start), + r_end_(r_end) {} + + KOKKOS_INLINE_FUNCTION parthenon::Real P_from_r(const parthenon::Real r) const { + // Determine indices in R bounding r + const int i_r = + static_cast(floor((n_r_ - 1) / (r_end_ - r_start_) * (r - r_start_))); + + if (r < r_(i_r) - sphere_.kRTol || r > r_(i_r + 1) + sphere_.kRTol) { + Kokkos::abort("PRhoProfile::P_from_r R(i_r) to R_(i_r+1) does not contain r"); + } - template - PRhoProfile generate_P_rho_profile(const parthenon::Real R_start, - const parthenon::Real R_end, - const unsigned int n_R) const; + // Linearly interpolate Pressure from P + const parthenon::Real P_r = + (p_(i_r) * (r_(i_r + 1) - r) + p_(i_r + 1) * (r - r_(i_r))) / + (r_(i_r + 1) - r_(i_r)); + + return P_r; + } + + KOKKOS_INLINE_FUNCTION parthenon::Real rho_from_r(const parthenon::Real r) const { + using parthenon::Real; + // Get pressure first + const Real p_r = P_from_r(r); + // Compute entropy and pressure here + const Real k_r = sphere_.entropy_profile_.K_from_r(r); + const Real rho_r = sphere_.rho_from_P_K(p_r, k_r); + return rho_r; + } + std::ostream &write_to_ostream(std::ostream &os) const; }; } // namespace cluster diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp new file mode 100644 index 00000000..0bcb950c --- /dev/null +++ b/src/pgen/cluster/jet_coords.hpp @@ -0,0 +1,115 @@ +#ifndef CLUSTER_JET_COORDS_HPP_ +#define CLUSTER_JET_COORDS_HPP_ +//======================================================================================== +// Athena++ astrophysical MHD code +// Copyright(C) 2014 James M. Stone and other code contributors +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file jet_coords.hpp +// \brief Class for working with precesing jet + +// Parthenon headers +#include "Kokkos_Macros.hpp" +#include +#include +#include +#include + +namespace cluster { + +/************************************************************ + * Jet Coordinates Class, for computing cylindrical coordinates in reference to + * a jet along a fixed tilted axis + * Lightweight object intended for inlined computation, within kernels. + ************************************************************/ +class JetCoords { + private: + // cos and sin of angle of the jet axis off the z-axis + const parthenon::Real cos_theta_jet_axis_, sin_theta_jet_axis_; + // cos and sin of angle of the jet axis around the z-axis + const parthenon::Real cos_phi_jet_axis_, sin_phi_jet_axis_; + + public: + explicit JetCoords(const parthenon::Real theta_jet_axis, + const parthenon::Real phi_jet_axis) + : cos_theta_jet_axis_(cos(theta_jet_axis)), + sin_theta_jet_axis_(sin(theta_jet_axis)), cos_phi_jet_axis_(cos(phi_jet_axis)), + sin_phi_jet_axis_(sin(phi_jet_axis)) {} + + // Convert simulation cartesian coordinates to jet cylindrical coordinates + KOKKOS_INLINE_FUNCTION void + SimCartToJetCylCoords(const parthenon::Real x_sim, const parthenon::Real y_sim, + const parthenon::Real z_sim, parthenon::Real &r_jet, + parthenon::Real &cos_theta_jet, parthenon::Real &sin_theta_jet, + parthenon::Real &h_jet) const __attribute__((always_inline)) { + + // Position in jet-cartesian coordinates + const parthenon::Real x_jet = x_sim * cos_phi_jet_axis_ * cos_theta_jet_axis_ + + y_sim * sin_phi_jet_axis_ - + z_sim * sin_theta_jet_axis_ * cos_phi_jet_axis_; + const parthenon::Real y_jet = -x_sim * sin_phi_jet_axis_ * cos_theta_jet_axis_ + + y_sim * cos_phi_jet_axis_ + + z_sim * sin_phi_jet_axis_ * sin_theta_jet_axis_; + const parthenon::Real z_jet = + x_sim * sin_theta_jet_axis_ + z_sim * cos_theta_jet_axis_; + + // Position in jet-cylindrical coordinates + r_jet = sqrt(pow(fabs(x_jet), 2) + pow(fabs(y_jet), 2)); + cos_theta_jet = x_jet / r_jet; + sin_theta_jet = y_jet / r_jet; + h_jet = z_jet; + } + + // Convert jet cylindrical vector to simulation cartesian vector + KOKKOS_INLINE_FUNCTION void JetCylToSimCartVector( + const parthenon::Real cos_theta_jet, const parthenon::Real sin_theta_jet, + const parthenon::Real v_r_jet, const parthenon::Real v_theta_jet, + const parthenon::Real v_h_jet, parthenon::Real &v_x_sim, parthenon::Real &v_y_sim, + parthenon::Real &v_z_sim) const __attribute__((always_inline)) { + // The vector in jet-cartesian coordinates + const parthenon::Real v_x_jet = v_r_jet * cos_theta_jet - v_theta_jet * sin_theta_jet; + const parthenon::Real v_y_jet = v_r_jet * sin_theta_jet + v_theta_jet * cos_theta_jet; + const parthenon::Real v_z_jet = v_h_jet; + + // Multiply v_jet by the DCM matrix to take Jet cartesian to Simulation Cartesian + v_x_sim = v_x_jet * cos_phi_jet_axis_ * cos_theta_jet_axis_ - + v_y_jet * sin_phi_jet_axis_ * cos_theta_jet_axis_ + + v_z_jet * sin_theta_jet_axis_; + v_y_sim = v_x_jet * sin_phi_jet_axis_ + v_y_jet * cos_phi_jet_axis_; + v_z_sim = -v_x_jet * sin_theta_jet_axis_ * cos_phi_jet_axis_ + + v_y_jet * sin_phi_jet_axis_ * sin_theta_jet_axis_ + + v_z_jet * cos_theta_jet_axis_; + } +}; +/************************************************************ + * Jet Coordinates Factory Class + * A factory for creating JetCoords objects given a time + * Keeps track of the precession + ************************************************************/ +class JetCoordsFactory { + private: + // Jet-axis Radians off the z-axis + const parthenon::Real theta_jet_axis_; + // Precesion rate of Jet-axis, radians/time + const parthenon::Real phi_dot_jet_axis_; + // Initial precession offset in radians of Jet-axis (Useful for testing) + const parthenon::Real phi0_jet_axis_; + + public: + explicit JetCoordsFactory(parthenon::ParameterInput *pin, + const std::shared_ptr &hydro_pkg, + const std::string &block = "problem/cluster/precessing_jet") + : theta_jet_axis_(pin->GetOrAddReal(block, "jet_theta", 0)), + phi_dot_jet_axis_(pin->GetOrAddReal(block, "jet_phi_dot", 0)), + phi0_jet_axis_(pin->GetOrAddReal(block, "jet_phi0", 0)) { + hydro_pkg->AddParam<>("jet_coords_factory", *this); + } + + JetCoords CreateJetCoords(const parthenon::Real time) const { + return JetCoords(theta_jet_axis_, phi0_jet_axis_ + time * phi0_jet_axis_); + } +}; + +} // namespace cluster + +#endif // CLUSTER_MAGNETIC_TOWER_HPP_ diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp new file mode 100644 index 00000000..37817066 --- /dev/null +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -0,0 +1,312 @@ +//======================================================================================== +// Athena++ astrophysical MHD code +// Copyright(C) 2014 James M. Stone and other code contributors +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file magnetic_tower.hpp +// \brief Class for defining magnetic tower + +// Parthenon headers +#include +#include +#include +#include +#include +#include +#include + +// Athena headers +#include "../../eos/adiabatic_glmmhd.hpp" +#include "../../main.hpp" +#include "../../reduction_utils.hpp" +#include "../../units.hpp" +#include "Kokkos_HostSpace.hpp" +#include "cluster_utils.hpp" +#include "magnetic_tower.hpp" + +namespace cluster { +using namespace parthenon; + +void MagneticTower::AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mass_to_add, + parthenon::MeshData *md, + const parthenon::SimTime &tm) const { + using parthenon::IndexDomain; + using parthenon::IndexRange; + using parthenon::Real; + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + if (hydro_pkg->Param("fluid") != Fluid::glmmhd) { + PARTHENON_FAIL("MagneticTower::AddSrcTerm: Only Fluid::glmmhd is supported"); + } + + // Grab some necessary variables + const auto &prim_pack = md->PackVariables(std::vector{"prim"}); + const auto &cons_pack = md->PackVariables(std::vector{"cons"}); + IndexRange ib = cons_pack.cellbounds.GetBoundsI(IndexDomain::interior); + IndexRange jb = cons_pack.cellbounds.GetBoundsJ(IndexDomain::interior); + IndexRange kb = cons_pack.cellbounds.GetBoundsK(IndexDomain::interior); + + // Scale density_to_add to match mass_to_add when integrated over all space + const Real density_to_add = mass_to_add / (pow(l_mass_scale_, 3) * pow(M_PI, 3. / 2.)); + + const JetCoords jet_coords = + hydro_pkg->Param("jet_coords_factory").CreateJetCoords(tm.time); + const MagneticTowerObj mt = MagneticTowerObj(field_to_add, alpha_, l_scale_, + density_to_add, l_mass_scale_, jet_coords); + + const auto &eos = hydro_pkg->Param("eos"); + + // Construct magnetic vector potential then compute magnetic fields + + // Currently reallocates this vector potential everytime step and constructs + // the potential in a separate kernel. There are two solutions: + // 1. Allocate a dependant variable in the hydro package for scratch + // variables, use to store this potential. Would save time in allocations + // but would still require more DRAM memory and two kernel launches + // 2. Compute the potential (12 needed in all) in the same kernel, + // constructing the derivative without storing the potential (more + // arithmetically intensive, maybe faster) + ParArray5D A("magnetic_tower_A", 3, cons_pack.GetDim(5), + cons_pack.cellbounds.ncellsk(IndexDomain::entire), + cons_pack.cellbounds.ncellsj(IndexDomain::entire), + cons_pack.cellbounds.ncellsi(IndexDomain::entire)); + IndexRange a_ib = ib; + a_ib.s -= 1; + a_ib.e += 1; + IndexRange a_jb = jb; + a_jb.s -= 1; + a_jb.e += 1; + IndexRange a_kb = kb; + a_kb.s -= 1; + a_kb.e += 1; + + // Construct the magnetic tower potential + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "MagneticTower::AddFieldSrcTerm::ConstructPotential", + parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, a_kb.s, a_kb.e, a_jb.s, + a_jb.e, a_ib.s, a_ib.e, + KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i) { + // Compute and apply potential + const auto &coords = cons_pack.coords(b); + + Real a_x_, a_y_, a_z_; + mt.PotentialInSimCart(coords.x1v(i), coords.x2v(j), coords.x3v(k), a_x_, a_y_, + a_z_); + + A(0, b, k, j, i) = a_x_; + A(1, b, k, j, i) = a_y_; + A(2, b, k, j, i) = a_z_; + }); + + // Take the curl of the potential and apply the new magnetic field + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "MagneticTower::MagneticFieldSrcTerm::ApplyPotential", + parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, + ib.e, KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i) { + auto &cons = cons_pack(b); + auto &prim = prim_pack(b); + const auto &coords = cons_pack.coords(b); + + // Take the curl of a to compute the magnetic field + const Real b_x = + (A(2, b, k, j + 1, i) - A(2, b, k, j - 1, i)) / coords.dx2v(j) / 2.0 - + (A(1, b, k + 1, j, i) - A(1, b, k - 1, j, i)) / coords.dx3v(k) / 2.0; + const Real b_y = + (A(0, b, k + 1, j, i) - A(0, b, k - 1, j, i)) / coords.dx3v(k) / 2.0 - + (A(2, b, k, j, i + 1) - A(2, b, k, j, i - 1)) / coords.dx1v(i) / 2.0; + const Real b_z = + (A(1, b, k, j, i + 1) - A(1, b, k, j, i - 1)) / coords.dx1v(i) / 2.0 - + (A(0, b, k, j + 1, i) - A(0, b, k, j - 1, i)) / coords.dx2v(j) / 2.0; + + // Add the magnetic field to the conserved variables + cons(IB1, k, j, i) += b_x; + cons(IB2, k, j, i) += b_y; + cons(IB3, k, j, i) += b_z; + + // Add the magnetic field energy given the existing field in prim + // dE_B = 1/2*( 2*dt*B_old*B_new + dt**2*B_new**2) + cons(IEN, k, j, i) += prim(IB1, k, j, i) * b_x + prim(IB2, k, j, i) * b_y + + prim(IB3, k, j, i) * b_z + + 0.5 * (b_x * b_x + b_y * b_y + b_z * b_z); + + // Add density + const Real cell_delta_rho = + mt.DensityFromSimCart(coords.x1v(i), coords.x1v(i), coords.x1v(i)); + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); + }); +} + +// Compute the increase to magnetic energy (1/2*B**2) over local meshes. Adds +// to linear_contrib and quadratic_contrib +// increases relative to B0 and B0**2. Necessary for scaling magnetic fields +// to inject a specified magnetic energy +void MagneticTower::ReducePowerContribs(parthenon::Real &linear_contrib, + parthenon::Real &quadratic_contrib, + parthenon::MeshData *md, + const parthenon::SimTime &tm) const { + using parthenon::IndexDomain; + using parthenon::IndexRange; + using parthenon::Real; + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + // Grab some necessary variables + const auto &prim_pack = md->PackVariables(std::vector{"prim"}); + const auto &cons_pack = md->PackVariables(std::vector{"cons"}); + IndexRange ib = cons_pack.cellbounds.GetBoundsI(IndexDomain::interior); + IndexRange jb = cons_pack.cellbounds.GetBoundsJ(IndexDomain::interior); + IndexRange kb = cons_pack.cellbounds.GetBoundsK(IndexDomain::interior); + + const JetCoords jet_coords = + hydro_pkg->Param("jet_coords_factory").CreateJetCoords(tm.time); + + // Make a construct a copy of this with field strength 1 to send to the device + const MagneticTowerObj mt = + MagneticTowerObj(1, alpha_, l_scale_, 0, l_mass_scale_, jet_coords); + + // Get the reduction of the linear and quadratic contributions ready + ReductionSumArray mt_power_reduction; + Kokkos::Sum> reducer_sum(mt_power_reduction); + + parthenon::par_reduce( + parthenon::loop_pattern_mdrange_tag, "MagneticTowerScaleFactor", + parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, + ib.e, + KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i, + ReductionSumArray &team_mt_power_reduction) { + auto &cons = cons_pack(b); + auto &prim = prim_pack(b); + const auto &coords = cons_pack.coords(b); + + const Real cell_volume = coords.Volume(k, j, i); + + // Compute the magnetic field at cell centers directly + Real b_x, b_y, b_z; + mt.FieldInSimCart(coords.x1v(i), coords.x2v(j), coords.x3v(k), b_x, b_y, b_z); + + // increases B**2 by 2*B0*Bnew + dt**2*Bnew**2) + team_mt_power_reduction.data[0] += + (prim(IB1, k, j, i) * b_x + prim(IB2, k, j, i) * b_y + + prim(IB3, k, j, i) * b_z) * + cell_volume; + team_mt_power_reduction.data[1] += + 0.5 * (b_x * b_x + b_y * b_y + b_z * b_z) * cell_volume; + }, + reducer_sum); + + linear_contrib += mt_power_reduction.data[0]; + quadratic_contrib += mt_power_reduction.data[1]; +} + +// Add magnetic potential to provided potential +template +void MagneticTower::AddInitialFieldToPotential(parthenon::MeshBlock *pmb, + parthenon::IndexRange kb, + parthenon::IndexRange jb, + parthenon::IndexRange ib, + const View4D &A) const { + + auto hydro_pkg = pmb->packages.Get("Hydro"); + const auto &coords = pmb->coords; + + const JetCoords jet_coords = + hydro_pkg->Param("jet_coords_factory").CreateJetCoords(0.0); + const MagneticTowerObj mt(initial_field_, alpha_, l_scale_, 0, l_mass_scale_, + jet_coords); + + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "MagneticTower::AddInitialFieldToPotential", + parthenon::DevExecSpace(), kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, + KOKKOS_LAMBDA(const int &k, const int &j, const int &i) { + // Compute and apply potential + Real a_x, a_y, a_z; + mt.PotentialInSimCart(coords.x1v(i), coords.x2v(j), coords.x3v(k), a_x, a_y, a_z); + A(0, k, j, i) += a_x; + A(1, k, j, i) += a_y; + A(2, k, j, i) += a_z; + }); +} + +// Instantiate the template definition in this source file +template void +MagneticTower::AddInitialFieldToPotential<>(MeshBlock *pmb, IndexRange kb, IndexRange jb, + IndexRange ib, + const ParArray4D &A) const; + +// Add the fixed_field_rate (and associated magnetic energy) to the +// conserved variables for all meshblocks with a MeshData +void MagneticTower::FixedFieldSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, + const parthenon::SimTime &tm) const { + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + if (fixed_field_rate_ != 0) { + AddSrcTerm(fixed_field_rate_ * beta_dt, fixed_mass_rate_ * beta_dt, md, tm); + } +} + +// Add the specified magnetic power (and associated magnetic field) to the +// conserved variables for all meshblocks with a MeshData +void MagneticTower::PowerSrcTerm(const parthenon::Real power, + const parthenon::Real mass_rate, + parthenon::MeshData *md, + const parthenon::Real beta_dt, + const parthenon::SimTime &tm) const { + if (power == 0) { + // Nothing to inject, return + return; + } + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + const Real linear_contrib = hydro_pkg->Param("magnetic_tower_linear_contrib"); + const Real quadratic_contrib = + hydro_pkg->Param("magnetic_tower_quadratic_contrib"); + if (linear_contrib == 0 && quadratic_contrib == 0) { + PARTHENON_FAIL("MagneticTowerModel::PowerSrcTerm mt_linear_contrib " + "and mt_quadratic_contrib are both zero. " + "(Has MagneticTowerReducePowerContribs been called?)"); + } + + const Real disc = + linear_contrib * linear_contrib + 4 * quadratic_contrib * beta_dt * power; + if (disc < 0 || quadratic_contrib == 0) { + std::stringstream msg; + msg << "MagneticTowerModel::PowerSrcTerm No field rate is viable" + << " linear_contrib: " << std::to_string(linear_contrib) + << " quadratic_contrib: " << std::to_string(quadratic_contrib); + PARTHENON_FAIL(msg.str().c_str()); + } + const Real field_to_add = (-linear_contrib + sqrt(disc)) / (2 * quadratic_contrib); + const Real mass_to_add = mass_rate * beta_dt; + + AddSrcTerm(field_to_add, mass_to_add, md, tm); +} + +parthenon::TaskStatus +MagneticTowerResetPowerContribs(parthenon::StateDescriptor *hydro_pkg) { + hydro_pkg->UpdateParam("magnetic_tower_linear_contrib", 0.0); + hydro_pkg->UpdateParam("magnetic_tower_quadratic_contrib", 0.0); + return TaskStatus::complete; +} + +parthenon::TaskStatus +MagneticTowerReducePowerContribs(parthenon::MeshData *md, + const parthenon::SimTime &tm) { + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + const auto &magnetic_tower = hydro_pkg->Param("magnetic_tower"); + + parthenon::Real linear_contrib = + hydro_pkg->Param("magnetic_tower_linear_contrib"); + parthenon::Real quadratic_contrib = + hydro_pkg->Param("magnetic_tower_quadratic_contrib"); + magnetic_tower.ReducePowerContribs(linear_contrib, quadratic_contrib, md, tm); + + hydro_pkg->UpdateParam("magnetic_tower_linear_contrib", linear_contrib); + hydro_pkg->UpdateParam("magnetic_tower_quadratic_contrib", quadratic_contrib); + return TaskStatus::complete; +} + +} // namespace cluster diff --git a/src/pgen/cluster/magnetic_tower.hpp b/src/pgen/cluster/magnetic_tower.hpp new file mode 100644 index 00000000..669ce66e --- /dev/null +++ b/src/pgen/cluster/magnetic_tower.hpp @@ -0,0 +1,194 @@ +#ifndef CLUSTER_MAGNETIC_TOWER_HPP_ +#define CLUSTER_MAGNETIC_TOWER_HPP_ +//======================================================================================== +// Athena++ astrophysical MHD code +// Copyright(C) 2014 James M. Stone and other code contributors +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file magnetic_tower.hpp +// \brief Class for defining magnetic tower + +// parthenon headers +#include +#include +#include +#include +#include +#include + +#include "jet_coords.hpp" + +namespace cluster { +/************************************************************ + * Magnetic Tower Object, for computing magnetic field, vector potential at a + * fixed time with a fixed field + * Lightweight object intended for inlined computation within kernels + ************************************************************/ +class MagneticTowerObj { + private: + const parthenon::Real field_; + const parthenon::Real alpha_, l_scale_; + + const parthenon::Real density_, l_mass_scale2_; + + JetCoords jet_coords_; + + public: + MagneticTowerObj(const parthenon::Real field, const parthenon::Real alpha, + const parthenon::Real l_scale, const parthenon::Real density, + const parthenon::Real l_mass_scale, const JetCoords jet_coords) + : field_(field), alpha_(alpha), l_scale_(l_scale), density_(density), + l_mass_scale2_(SQR(l_mass_scale)), jet_coords_(jet_coords) {} + + // Compute Jet Potential in jet cylindrical coordinates + KOKKOS_INLINE_FUNCTION void + PotentialInJetCyl(const parthenon::Real r, const parthenon::Real h, + parthenon::Real &a_r, parthenon::Real &a_theta, + parthenon::Real &a_h) const __attribute__((always_inline)) { + const parthenon::Real exp_r2_h2 = exp(-pow(r / l_scale_, 2) - pow(h / l_scale_, 2)); + // Compute the potential in jet cylindrical coordinates + a_r = 0.0; + a_theta = field_ * l_scale_ * (r / l_scale_) * exp_r2_h2; + a_h = field_ * l_scale_ * alpha_ / 2.0 * exp_r2_h2; + } + + // Compute Magnetic Potential in simulation Cartesian coordinates + KOKKOS_INLINE_FUNCTION void + PotentialInSimCart(const parthenon::Real x, const parthenon::Real y, + const parthenon::Real z, parthenon::Real &a_x, parthenon::Real &a_y, + parthenon::Real &a_z) const __attribute__((always_inline)) { + // Compute the jet cylindrical coordinates + parthenon::Real r, cos_theta, sin_theta, h; + jet_coords_.SimCartToJetCylCoords(x, y, z, r, cos_theta, sin_theta, h); + + // Compute the potential in jet cylindrical coordinates + parthenon::Real a_r, a_theta, a_h; + PotentialInJetCyl(r, h, a_r, a_theta, a_h); + + // Convert vector potential from jet cylindrical to simulation cartesian + jet_coords_.JetCylToSimCartVector(cos_theta, sin_theta, a_r, a_theta, a_h, a_x, a_y, + a_z); + } + + // Compute Magnetic Fields in Jet cylindrical Coordinates + KOKKOS_INLINE_FUNCTION void + FieldInJetCyl(const parthenon::Real r, const parthenon::Real h, parthenon::Real &b_r, + parthenon::Real &b_theta, parthenon::Real &b_h) const + __attribute__((always_inline)) { + + const parthenon::Real exp_r2_h2 = exp(-pow(r / l_scale_, 2) - pow(h / l_scale_, 2)); + // Compute the field in jet cylindrical coordinates + b_r = field_ * 2 * (h / l_scale_) * (r / l_scale_) * exp_r2_h2; + b_theta = field_ * alpha_ * (r / l_scale_) * exp_r2_h2; + b_h = field_ * 2 * (1 - pow(r / l_scale_, 2)) * exp_r2_h2; + } + + // Compute Magnetic field in Simulation Cartesian coordinates + KOKKOS_INLINE_FUNCTION void + FieldInSimCart(const parthenon::Real x, const parthenon::Real y, + const parthenon::Real z, parthenon::Real &b_x, parthenon::Real &b_y, + parthenon::Real &b_z) const __attribute__((always_inline)) { + // Compute the jet cylindrical coordinates + parthenon::Real r, cos_theta, sin_theta, h; + jet_coords_.SimCartToJetCylCoords(x, y, z, r, cos_theta, sin_theta, h); + + // Compute the magnetic field in jet_coords + parthenon::Real b_r, b_theta, b_h; + FieldInJetCyl(r, h, b_r, b_theta, b_h); + + // Convert potential to cartesian + jet_coords_.JetCylToSimCartVector(cos_theta, sin_theta, b_r, b_theta, b_h, b_x, b_y, + b_z); + } + + // Compute Density injection from Simulation Cartesian coordinates + KOKKOS_INLINE_FUNCTION parthenon::Real DensityFromSimCart(const parthenon::Real x, + const parthenon::Real y, + const parthenon::Real z) const + __attribute__((always_inline)) { + // Compute the jet cylindrical coordinates + parthenon::Real r, cos_theta, sin_theta, h; + jet_coords_.SimCartToJetCylCoords(x, y, z, r, cos_theta, sin_theta, h); + + return density_ * exp(-(SQR(r) + SQR(h)) / l_mass_scale2_); + } +}; + +/************************************************************ + * Magnetic Tower Model, for initializing a magnetic tower and tasks related to + * injecting a magnetic tower as a source term + ************************************************************/ +class MagneticTower { + public: + const parthenon::Real alpha_, l_scale_; + + const parthenon::Real initial_field_; + const parthenon::Real fixed_field_rate_; + + const parthenon::Real fixed_mass_rate_; + const parthenon::Real l_mass_scale_; + + MagneticTower(parthenon::ParameterInput *pin, + const std::shared_ptr &hydro_pkg, + const std::string &block = "problem/cluster/magnetic_tower") + : alpha_(pin->GetOrAddReal(block, "alpha", 0)), + l_scale_(pin->GetOrAddReal(block, "l_scale", 0)), + initial_field_(pin->GetOrAddReal(block, "initial_field", 0)), + fixed_field_rate_(pin->GetOrAddReal(block, "fixed_field_rate", 0)), + fixed_mass_rate_(pin->GetOrAddReal(block, "fixed_mass_rate", 0)), + l_mass_scale_(pin->GetOrAddReal(block, "l_mass_scale", 1)) { + hydro_pkg->AddParam<>("magnetic_tower", *this); + hydro_pkg->AddParam("magnetic_tower_linear_contrib", 0.0); + hydro_pkg->AddParam("magnetic_tower_quadratic_contrib", 0.0); + } + + // Add initial magnetic field to provided potential with a single meshblock + template + void AddInitialFieldToPotential(parthenon::MeshBlock *pmb, parthenon::IndexRange kb, + parthenon::IndexRange jb, parthenon::IndexRange ib, + const View4D &A) const; + + // Add the fixed_field_rate (and associated magnetic energy) to the + // conserved variables for all meshblocks with a MeshData + void FixedFieldSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, + const parthenon::SimTime &tm) const; + + // Add the specified magnetic power (and associated magnetic field) to the + // conserved variables for all meshblocks with a MeshData + void PowerSrcTerm(const parthenon::Real power, const parthenon::Real mass_rate, + parthenon::MeshData *md, + const parthenon::Real beta_dt, const parthenon::SimTime &tm) const; + + // Add the specified magnetic field (and associated magnetic energy) to the + // conserved variables for all meshblocks with a MeshData + void AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mass_to_add, + parthenon::MeshData *md, + const parthenon::SimTime &tm) const; + + // Compute the increase to magnetic energy (1/2*B**2) over local meshes. Adds + // to linear_contrib and quadratic_contrib + // increases relative to B0 and B0**2. Necessary for scaling magnetic fields + // to inject a specified magnetic energy + void ReducePowerContribs(parthenon::Real &linear_contrib, + parthenon::Real &quadratic_contrib, + parthenon::MeshData *md, + const parthenon::SimTime &tm) const; + + friend parthenon::TaskStatus + MagneticTowerResetPowerContribs(parthenon::StateDescriptor *hydro_pkg); + + friend parthenon::TaskStatus + MagneticTowerReducePowerContribs(parthenon::MeshData *md, + const parthenon::SimTime &tm); +}; + +parthenon::TaskStatus +MagneticTowerResetPowerContribs(parthenon::StateDescriptor *hydro_pkg); +parthenon::TaskStatus +MagneticTowerReducePowerContribs(parthenon::MeshData *md, + const parthenon::SimTime &tm); + +} // namespace cluster + +#endif // CLUSTER_MAGNETIC_TOWER_HPP_ diff --git a/src/pgen/pgen.hpp b/src/pgen/pgen.hpp index 0899b7eb..8dd08eff 100644 --- a/src/pgen/pgen.hpp +++ b/src/pgen/pgen.hpp @@ -98,7 +98,8 @@ using namespace parthenon::driver::prelude; void InitUserMeshData(ParameterInput *pin); void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin); -void ClusterSrcTerm(MeshData *md, const parthenon::SimTime, const Real beta_dt); +void ClusterSrcTerm(MeshData *md, const parthenon::SimTime &tm, const Real beta_dt); +parthenon::Real ClusterEstimateTimestep(MeshData *md); } // namespace cluster namespace sod { diff --git a/src/reduction_utils.hpp b/src/reduction_utils.hpp new file mode 100644 index 00000000..9e2fc5a1 --- /dev/null +++ b/src/reduction_utils.hpp @@ -0,0 +1,65 @@ + +// Athena-Parthenon - a performance portable block structured AMR MHD code +// Copyright (c) 2020, Athena Parthenon Collaboration. All rights reserved. +// Licensed under the 3-Clause License (the "LICENSE") + +#ifndef REDUCTION_UTILS_HPP_ +#define REDUCTION_UTILS_HPP_ +// Consider moving ReductionSumArray to Parthenon and extending to other operations + +#include + +// AthenaPK headers +#include "basic_types.hpp" + +// Reduction array from +// https://github.com/kokkos/kokkos/wiki/Custom-Reductions%3A-Built-In-Reducers-with-Custom-Scalar-Types +template +struct ReductionSumArray { + ScalarType data[N]; + + KOKKOS_INLINE_FUNCTION // Default constructor - Initialize to 0's + ReductionSumArray() { + for (int i = 0; i < N; i++) { + data[i] = 0; + } + } + KOKKOS_INLINE_FUNCTION // Copy Constructor + ReductionSumArray(const ReductionSumArray &rhs) { + for (int i = 0; i < N; i++) { + data[i] = rhs.data[i]; + } + } + KOKKOS_INLINE_FUNCTION // add operator + ReductionSumArray & + operator+=(const ReductionSumArray &src) { + for (int i = 0; i < N; i++) { + data[i] += src.data[i]; + } + return *this; + } + KOKKOS_INLINE_FUNCTION // volatile add operator + void + operator+=(const volatile ReductionSumArray &src) volatile { + for (int i = 0; i < N; i++) { + data[i] += src.data[i]; + } + } +}; + +namespace Kokkos { // reduction identity must be defined in Kokkos namespace +template <> +struct reduction_identity> { + KOKKOS_FORCEINLINE_FUNCTION static ReductionSumArray sum() { + return ReductionSumArray(); + } +}; +template <> +struct reduction_identity> { + KOKKOS_FORCEINLINE_FUNCTION static ReductionSumArray sum() { + return ReductionSumArray(); + } +}; +} // namespace Kokkos + +#endif // REDUCTION_UNITS_HPP_ diff --git a/src/units.hpp b/src/units.hpp index c3666f6b..122acde9 100644 --- a/src/units.hpp +++ b/src/units.hpp @@ -127,4 +127,4 @@ class Units { parthenon::Real microgauss() const { return microgauss_cgs / code_magnetic_cgs(); } }; -#endif // PHYSICAL_CONSTANTS_HPP_ +#endif // UNITS_HPP_ diff --git a/tst/regression/CMakeLists.txt b/tst/regression/CMakeLists.txt index 6e8bf180..2f47a6bc 100644 --- a/tst/regression/CMakeLists.txt +++ b/tst/regression/CMakeLists.txt @@ -28,3 +28,12 @@ setup_test_serial("cluster_tabular_cooling" "--driver ${PROJECT_BINARY_DIR}/bin/ setup_test_serial("field_loop" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ --driver_input ${PROJECT_SOURCE_DIR}/inputs/field_loop.in --num_steps 9" "convergence") + +setup_test_serial("cluster_magnetic_tower" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ + --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/magnetic_tower.in --num_steps 4" "convergence") + +setup_test_serial("cluster_hydro_agn_feedback" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ + --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/hydro_agn_feedback.in --num_steps 6" "convergence") + +setup_test_serial("cluster_agn_triggering" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ + --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/agn_triggering.in --num_steps 3" "convergence") diff --git a/tst/regression/test_suites/cluster_agn_triggering/__init__.py b/tst/regression/test_suites/cluster_agn_triggering/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py new file mode 100644 index 00000000..9995c833 --- /dev/null +++ b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py @@ -0,0 +1,343 @@ +#======================================================================================== +# AthenaPK - a performance portable block structured AMR MHD code +# Copyright (c) 2020-2021, Athena Parthenon Collaboration. All rights reserved. +# Licensed under the 3-clause BSD License, see LICENSE file for details +#======================================================================================== +# (C) (or copyright) 2020. Triad National Security, LLC. All rights reserved. +# +# This program was produced under U.S. Government contract 89233218CNA000001 for Los +# Alamos National Laboratory (LANL), which is operated by Triad National Security, LLC +# for the U.S. Department of Energy/National Nuclear Security Administration. All rights +# in the program are reserved by Triad National Security, LLC, and the U.S. Department +# of Energy/National Nuclear Security Administration. The Government is granted for +# itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide +# license in this material to reproduce, prepare derivative works, distribute copies to +# the public, perform publicly and display publicly, and to permit others to do so. +#======================================================================================== + +# Modules +import math +import numpy as np +import matplotlib +matplotlib.use('agg') +import matplotlib.pylab as plt +import sys +import os +import utils.test_case +import unyt +import itertools + +""" To prevent littering up imported folders with .pyc files or __pycache_ folder""" +sys.dont_write_bytecode = True + +class TestCase(utils.test_case.TestCaseAbs): + def __init__(self): + + #Define cluster parameters + #Setup units + unyt.define_unit("code_length",(1,"Mpc")) + unyt.define_unit("code_mass",(1e14,"Msun")) + unyt.define_unit("code_time",(1,"Gyr")) + self.code_length = unyt.unyt_quantity(1,"code_length") + self.code_mass = unyt.unyt_quantity(1,"code_mass") + self.code_time = unyt.unyt_quantity(1,"code_time") + + self.tlim = unyt.unyt_quantity(0.1,"code_time") + + #Setup constants + self.k_b = unyt.kb_cgs + self.G = unyt.G_cgs + self.m_u =unyt.amu + + self.adiabatic_index = 5./3. + self.He_mass_fraction = 0.25 + self.mu = 1/(self.He_mass_fraction*3./4. + (1-self.He_mass_fraction)*2) + self.mean_molecular_mass = self.mu*self.m_u + + #Define the initial uniform gas + self.uniform_gas_rho = unyt.unyt_quantity(1e-22,"g/cm**3") + self.uniform_gas_ux = unyt.unyt_quantity(60000,"cm/s") + self.uniform_gas_uy = unyt.unyt_quantity(40000,"cm/s") + self.uniform_gas_uz = unyt.unyt_quantity(-50000,"cm/s") + self.uniform_gas_pres = unyt.unyt_quantity(1e-10,"dyne/cm**2") + + self.uniform_gas_Mx = self.uniform_gas_rho*self.uniform_gas_ux + self.uniform_gas_My = self.uniform_gas_rho*self.uniform_gas_uy + self.uniform_gas_Mz = self.uniform_gas_rho*self.uniform_gas_uz + self.uniform_gas_energy_density = \ + 1./2.*self.uniform_gas_rho*(self.uniform_gas_ux**2 + self.uniform_gas_uy**2 + self.uniform_gas_uz**2) \ + + self.uniform_gas_pres/(self.adiabatic_index - 1.) + + self.uniform_gas_vel = np.sqrt( self.uniform_gas_ux**2 + + self.uniform_gas_uy**2 + + self.uniform_gas_uz**2 ) + + self.uniform_gas_temp = self.mu*self.m_u/self.k_b*self.uniform_gas_pres/self.uniform_gas_rho + print("Uniform gas temperature: ",self.uniform_gas_temp.in_units("K")*1.01) + + #SMBH parameters (for Bondi-like accretion) + self.M_smbh = unyt.unyt_quantity(1e8,"Msun") + + #Triggering parameters + self.accretion_radius = unyt.unyt_quantity(20,"kpc") + self.cold_temp_thresh = self.uniform_gas_temp*1.01 + self.cold_t_acc = unyt.unyt_quantity(100,"Myr") + self.bondi_alpha = 100 + self.bondi_beta = 2 + self.bondi_n0 = 0.05*(self.uniform_gas_rho/self.mean_molecular_mass) + + self.norm_tol = 1e-3 + self.linf_accretion_rate_tol = 1e-3 + + self.step_params_list = ["COLD_GAS","BOOSTED_BONDI","BOOTH_SCHAYE"] + self.steps = len(self.step_params_list) + + def Prepare(self,parameters, step): + """ + Any preprocessing that is needed before the drive is run can be done in + this method + + This includes preparing files or any other pre processing steps that + need to be implemented. The method also provides access to the + parameters object which controls which parameters are being used to run + the driver. + + It is possible to append arguments to the driver_cmd_line_args if it is + desired to override the parthenon input file. Each element in the list + is simply a string of the form '/=', where the + contents of the string are exactly what one would type on the command + line run running a parthenon driver. + + As an example if the following block was uncommented it would overwrite + any of the parameters that were specified in the parthenon input file + parameters.driver_cmd_line_args = ['output1/file_type=vtk', + 'output1/variable=cons', + 'output1/dt=0.4', + 'time/tlim=0.4', + 'mesh/nx1=400'] + """ + triggering_mode = self.step_params_list[step-1] + output_id = triggering_mode + + parameters.driver_cmd_line_args = [ + f"parthenon/output2/id={output_id}", + f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", + f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", + f"hydro/gamma={self.adiabatic_index}", + f"hydro/He_mass_fraction={self.He_mass_fraction}", + + f"units/code_length_cgs={self.code_length.in_units('cm').v}", + f"units/code_mass_cgs={self.code_mass.in_units('g').v}", + f"units/code_time_cgs={self.code_time.in_units('s').v}", + + f"problem/cluster/uniform_gas/init_uniform_gas=true", + f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", + f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", + + f"problem/cluster/gravity/m_smbh={self.M_smbh.in_units('code_mass').v}", + + f"problem/cluster/agn_triggering/triggering_mode={triggering_mode}", + f"problem/cluster/agn_triggering/accretion_radius={self.accretion_radius.in_units('code_length').v}", + f"problem/cluster/agn_triggering/cold_temp_thresh={self.cold_temp_thresh.in_units('K').v}", + f"problem/cluster/agn_triggering/cold_t_acc={self.cold_t_acc.in_units('code_time').v}", + f"problem/cluster/agn_triggering/bondi_alpha={self.bondi_alpha}", + f"problem/cluster/agn_triggering/bondi_beta={self.bondi_beta}", + f"problem/cluster/agn_triggering/bondi_n0={self.bondi_n0.in_units('code_length**-3').v}", + f"problem/cluster/agn_triggering/write_to_file=true", + f"problem/cluster/agn_triggering/triggering_filename={triggering_mode}_triggering.dat", + ] + + + return parameters + + def Analyse(self,parameters): + """ + Analyze the output and determine if the test passes. + + This function is called after the driver has been executed. It is + responsible for reading whatever data it needs and making a judgment + about whether or not the test passes. It takes no inputs. Output should + be True (test passes) or False (test fails). + + The parameters that are passed in provide the paths to relevant + locations and commands. Of particular importance is the path to the + output folder. All files from a drivers run should appear in and output + folder located in + parthenon/tst/regression/test_suites/test_name/output. + + It is possible in this function to read any of the output files such as + hdf5 output and compare them to expected quantities. + + """ + analyze_status = True + + for step in range(1,self.steps+1): + triggering_mode = self.step_params_list[step-1] + output_id = triggering_mode + step_status = True + + print(f"Testing {output_id}") + + #Read the triggering data produced by the sim, replicate the + #integration of triggering to determine the final state of the gas + sim_data = np.loadtxt( + f"{triggering_mode}_triggering.dat") + + sim_times = unyt.unyt_array(sim_data[:,0],"code_time") + sim_dts = unyt.unyt_array(sim_data[:,1],"code_time") + sim_accretion_rate = unyt.unyt_array(sim_data[:,2],"code_mass/code_time") + + if triggering_mode == "COLD_GAS": + sim_cold_mass = unyt.unyt_array(sim_data[:,3],"code_mass") + elif triggering_mode == "BOOSTED_BONDI" or triggering_mode == "BOOTH_SCHAYE": + sim_total_mass = unyt.unyt_array(sim_data[:,3],"code_mass") + sim_avg_density = unyt.unyt_array(sim_data[:,4],"code_mass/code_length**3") + sim_avg_velocity = unyt.unyt_array(sim_data[:,5],"code_length/code_time") + sim_avg_cs = unyt.unyt_array(sim_data[:,6],"code_length/code_time") + else: + raise Exception(f"Triggering mode {triggering_mode} not supported in analysis") + + n_times = sim_data.shape[0] + + analytic_density = unyt.unyt_array(np.empty(n_times+1),"code_mass*code_length**-3") + analytic_pressure = unyt.unyt_array(np.empty(n_times+1),"code_mass/(code_length*code_time**2)") + analytic_accretion_rate = unyt.unyt_array(np.empty(n_times),"code_mass*code_time**-1") + + analytic_density[0] = self.uniform_gas_rho.in_units("code_mass*code_length**-3") + analytic_pressure[0] = self.uniform_gas_pres.in_units("code_mass/(code_length*code_time**2)") + + accretion_volume = 4./3.*np.pi*self.accretion_radius**3 + + for i in range(n_times): + dt = sim_dts[i] + + if triggering_mode == "COLD_GAS": + #Temperature should stay fixed below cold gas threshold + accretion_rate = analytic_density[i]*accretion_volume/self.cold_t_acc + elif triggering_mode == "BOOSTED_BONDI" or triggering_mode == "BOOTH_SCHAYE": + + if triggering_mode == "BOOSTED_BONDI": + alpha = self.bondi_alpha + elif triggering_mode == "BOOTH_SCHAYE": + n = analytic_density[i]/(self.mu*self.m_u) + if n <= self.bondi_n0: + alpha = 1. + else: + alpha = (n/self.bondi_n0)**self.bondi_beta + else: + raise Exception(f"Triggering mode {triggering_mode} not supported in analysis") + + cs2 = self.adiabatic_index*self.uniform_gas_pres/self.uniform_gas_rho + accretion_rate = alpha * \ + (2 * np.pi *unyt.G_cgs**2 * self.M_smbh**2 * analytic_density[i]) / \ + (self.uniform_gas_vel**2 + cs2)**(3./2.) + else: + raise Exception(f"Triggering mode {triggering_mode} not supported in analysis") + + accretion_rate_density = accretion_rate/accretion_volume + + analytic_density[i+1] = (analytic_density[i] - accretion_rate_density*dt).in_units(analytic_density.units) + analytic_pressure[i+1] = (analytic_pressure[i] \ + - accretion_rate_density*dt*analytic_pressure[i]/analytic_density[i]).in_units(analytic_pressure.units) + analytic_accretion_rate[i] = accretion_rate.in_units(analytic_accretion_rate.units) + + #Compare the analytic accretion_rate + accretion_rate_err = np.abs((analytic_accretion_rate - sim_accretion_rate)/analytic_accretion_rate) + + if np.max(accretion_rate_err) > self.linf_accretion_rate_tol: + analyze_status = False + print(f"{triggering_mode} linf_accretion_rate_err {np.max(accretion_rate_err)}" + f" exceeds tolerance {self.linf_accretion_rate_tol}" + f" at i={np.argmax(accretion_rate_err)}" + f" time={sim_times[np.argmax(accretion_rate_err)]}") + + + final_rho = analytic_density[-1] + final_pres = analytic_pressure[-1] + final_Mx = self.uniform_gas_ux*final_rho + final_My = self.uniform_gas_uy*final_rho + final_Mz = self.uniform_gas_uz*final_rho + final_energy_density = 1./2.*final_rho*(self.uniform_gas_ux**2 + + self.uniform_gas_uy**2 + + self.uniform_gas_uz**2) \ + + final_pres/(self.adiabatic_index - 1.) + + + def accretion_mask( Z,Y,X, inner_state, outer_state ): + pos_cart = unyt.unyt_array((X,Y,Z),"code_length") + + r = np.sqrt(np.sum(pos_cart**2,axis=0)) + + state = inner_state*(r < self.accretion_radius) + outer_state*(r >= self.accretion_radius) + + return state + + #Check that the initial and final outputs match the expected tower + sys.path.insert(1, parameters.parthenon_path + '/scripts/python/packages/parthenon_tools/parthenon_tools') + + try: + import compare_analytic + except ModuleNotFoundError: + print("Couldn't find module to analyze Parthenon hdf5 files.") + return False + + initial_analytic_components = { + "Density":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_rho.in_units("code_mass/code_length**3").v, + "MomentumDensity1":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_Mx.in_units("code_mass*code_length**-2*code_time**-1").v, + "MomentumDensity2":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_My.in_units("code_mass*code_length**-2*code_time**-1").v, + "MomentumDensity3":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_Mz.in_units("code_mass*code_length**-2*code_time**-1").v, + "TotalEnergyDensity":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_energy_density.in_units("code_mass*code_length**-1*code_time**-2").v, + "Velocity1":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_ux.in_units("code_length*code_time**-1").v, + "Velocity2":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_uy.in_units("code_length*code_time**-1").v, + "Velocity3":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_uz.in_units("code_length*code_time**-1").v, + "Pressure":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_pres.in_units("code_mass/(code_length*code_time**2)").v, + } + + + + final_analytic_components = { + "Density":lambda Z,Y,X,time : + accretion_mask(Z,Y,X,final_rho,self.uniform_gas_rho).in_units("code_mass/code_length**3").v, + "MomentumDensity1":lambda Z,Y,X,time : + accretion_mask(Z,Y,X,final_Mx,self.uniform_gas_Mx).in_units("code_mass*code_length**-2*code_time**-1").v, + "MomentumDensity2":lambda Z,Y,X,time : + accretion_mask(Z,Y,X,final_My,self.uniform_gas_My).in_units("code_mass*code_length**-2*code_time**-1").v, + "MomentumDensity3":lambda Z,Y,X,time : + accretion_mask(Z,Y,X,final_Mz,self.uniform_gas_Mz).in_units("code_mass*code_length**-2*code_time**-1").v, + "TotalEnergyDensity":lambda Z,Y,X,time : + accretion_mask(Z,Y,X,final_energy_density,self.uniform_gas_energy_density).in_units("code_mass*code_length**-1*code_time**-2").v, + "Velocity1":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_ux.in_units("code_length*code_time**-1").v, + "Velocity2":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_uy.in_units("code_length*code_time**-1").v, + "Velocity3":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_uz.in_units("code_length*code_time**-1").v, + "Pressure":lambda Z,Y,X,time : + accretion_mask(Z,Y,X,final_pres,self.uniform_gas_pres).in_units("code_mass/(code_length*code_time**2)").v, + } + + phdf_files = [f"{parameters.output_path}/parthenon.{output_id}.{i:05d}.phdf" for i in range(2)] + + #Use a very loose tolerance, linf relative error + analytic_status = True + for analytic_components,phdf_file in zip((initial_analytic_components,final_analytic_components),phdf_files): + analytic_status &= compare_analytic.compare_analytic( phdf_file, analytic_components, + err_func=lambda gold,test: compare_analytic.norm_err_func(gold, test, norm_ord=np.inf,relative=True), tol=self.norm_tol) + + + analyze_status &= analytic_status + + + return analyze_status diff --git a/tst/regression/test_suites/cluster_hse/cluster_hse.py b/tst/regression/test_suites/cluster_hse/cluster_hse.py index 7124e6d6..75931af2 100644 --- a/tst/regression/test_suites/cluster_hse/cluster_hse.py +++ b/tst/regression/test_suites/cluster_hse/cluster_hse.py @@ -57,14 +57,14 @@ def __init__(self): #NFW parameters self.c_nfw = 6.0 - self.M_nfw_200 = unyt.unyt_quantity(1e15,"Msun") + self.m_nfw_200 = unyt.unyt_quantity(1e15,"Msun") #BCG parameters - self.M_bcg_s = unyt.unyt_quantity(1e11,"Msun") - self.R_bcg_s = unyt.unyt_quantity(4,"kpc") + self.m_bcg_s = unyt.unyt_quantity(1e11,"Msun") + self.r_bcg_s = unyt.unyt_quantity(4,"kpc") #SMBH parameters - self.M_smbh = unyt.unyt_quantity(1e8,"Msun") + self.m_smbh = unyt.unyt_quantity(1e8,"Msun") #Smooth gravity at origin, for numerical reasons self.g_smoothing_radius = unyt.unyt_quantity(0.0,"code_length") @@ -122,23 +122,23 @@ def Prepare(self,parameters, step): f"units/code_mass_cgs={self.code_mass.in_units('g').v}", f"units/code_time_cgs={self.code_time.in_units('s').v}", f"problem/cluster/hubble_parameter={self.hubble_parameter.in_units('1/code_time').v}", - f"problem/cluster/include_nfw_g={self.include_nfw_g}", - f"problem/cluster/which_bcg_g={self.which_bcg_g}", - f"problem/cluster/include_smbh_g={self.include_smbh_g}", - f"problem/cluster/c_nfw={self.c_nfw}", - f"problem/cluster/M_nfw_200={self.M_nfw_200.in_units('code_mass').v}", - f"problem/cluster/M_bcg_s={self.M_bcg_s.in_units('code_mass').v}", - f"problem/cluster/R_bcg_s={self.R_bcg_s.in_units('code_length').v}", - f"problem/cluster/M_smbh={self.M_smbh.in_units('code_mass').v}", - f"problem/cluster/g_smoothing_radius={self.g_smoothing_radius.in_units('code_length').v}", - f"problem/cluster/K_0={self.K_0.in_units('code_length**4*code_mass/code_time**2').v}", - f"problem/cluster/K_100={self.K_100.in_units('code_length**4*code_mass/code_time**2').v}", - f"problem/cluster/R_K={self.R_K.in_units('code_length').v}", - f"problem/cluster/alpha_K={self.alpha_K}", - f"problem/cluster/R_fix={self.R_fix.in_units('code_length').v}", - f"problem/cluster/rho_fix={self.rho_fix.in_units('code_mass/code_length**3').v}", - f"problem/cluster/R_sampling={self.R_sampling}", - f"problem/cluster/max_dR={self.max_dR}", + f"problem/cluster/gravity/include_nfw_g={self.include_nfw_g}", + f"problem/cluster/gravity/which_bcg_g={self.which_bcg_g}", + f"problem/cluster/gravity/include_smbh_g={self.include_smbh_g}", + f"problem/cluster/gravity/c_nfw={self.c_nfw}", + f"problem/cluster/gravity/m_nfw_200={self.m_nfw_200.in_units('code_mass').v}", + f"problem/cluster/gravity/m_bcg_s={self.m_bcg_s.in_units('code_mass').v}", + f"problem/cluster/gravity/r_bcg_s={self.r_bcg_s.in_units('code_length').v}", + f"problem/cluster/gravity/m_smbh={self.m_smbh.in_units('code_mass').v}", + f"problem/cluster/gravity/g_smoothing_radius={self.g_smoothing_radius.in_units('code_length').v}", + f"problem/cluster/entropy_profile/k_0={self.K_0.in_units('code_length**4*code_mass/code_time**2').v}", + f"problem/cluster/entropy_profile/k_100={self.K_100.in_units('code_length**4*code_mass/code_time**2').v}", + f"problem/cluster/entropy_profile/r_k={self.R_K.in_units('code_length').v}", + f"problem/cluster/entropy_profile/alpha_k={self.alpha_K}", + f"problem/cluster/hydrostatic_equilibrium/r_fix={self.R_fix.in_units('code_length').v}", + f"problem/cluster/hydrostatic_equilibrium/rho_fix={self.rho_fix.in_units('code_mass/code_length**3').v}", + f"problem/cluster/hydrostatic_equilibrium/r_sampling={self.R_sampling}", + f"problem/cluster/hydrostatic_equilibrium/max_dr={self.max_dR}", ] @@ -179,7 +179,7 @@ def Analyse(self,parameters): self.rho_crit = 3*self.hubble_parameter**2/(8*np.pi*self.G) self.rho_nfw_0 = 200./3*self.rho_crit*self.c_nfw**3/(np.log(1 + self.c_nfw) - self.c_nfw/(1 + self.c_nfw)) - self.R_nfw_s = (self.M_nfw_200/(4*np.pi*self.rho_nfw_0*(np.log(1+self.c_nfw)-self.c_nfw/(1+self.c_nfw))))**(1./3.) + self.R_nfw_s = (self.m_nfw_200/(4*np.pi*self.rho_nfw_0*(np.log(1+self.c_nfw)-self.c_nfw/(1+self.c_nfw))))**(1./3.) #Thermodynamics+Entropy helpers def P_from_rho_K(rho,K): @@ -197,16 +197,16 @@ def T_from_rho_P(rho,P): #Gravity helpers def g_nfw_from_r(r): - return self.G * self.M_nfw_200/( np.log(1 + self.c_nfw) - self.c_nfw/(1+self.c_nfw))*( np.log(1 + r/self.R_nfw_s) - r/(r+self.R_nfw_s))/r**2 + return self.G * self.m_nfw_200/( np.log(1 + self.c_nfw) - self.c_nfw/(1+self.c_nfw))*( np.log(1 + r/self.R_nfw_s) - r/(r+self.R_nfw_s))/r**2 def g_bcg_hernquist_from_r(r): - #M_bcg = 8*self.M_bcg_s*(r/self.R_bcg_s)**2/( 2*( 1 + r/self.R_bcg_s)**2) - #return G*M_bcg/r**2 - g = self.G*self.M_bcg_s/(self.R_bcg_s**2)/( 2*( 1 + r/self.R_bcg_s)**2) + #m_bcg = 8*self.m_bcg_s*(r/self.r_bcg_s)**2/( 2*( 1 + r/self.r_bcg_s)**2) + #return G*m_bcg/r**2 + g = self.G*self.m_bcg_s/(self.r_bcg_s**2)/( 2*( 1 + r/self.r_bcg_s)**2) return g def g_smbh_from_r(r): - return self.G*self.M_smbh/r**2 + return self.G*self.m_smbh/r**2 def g_from_r(r,include_gs): g = unyt.unyt_array(np.zeros_like(r),"code_length*code_time**-2") diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/__init__.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py new file mode 100644 index 00000000..160f9c20 --- /dev/null +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -0,0 +1,355 @@ +#======================================================================================== +# AthenaPK - a performance portable block structured AMR MHD code +# Copyright (c) 2020-2021, Athena Parthenon Collaboration. All rights reserved. +# Licensed under the 3-clause BSD License, see LICENSE file for details +#======================================================================================== +# (C) (or copyright) 2020. Triad National Security, LLC. All rights reserved. +# +# This program was produced under U.S. Government contract 89233218CNA000001 for Los +# Alamos National Laboratory (LANL), which is operated by Triad National Security, LLC +# for the U.S. Department of Energy/National Nuclear Security Administration. All rights +# in the program are reserved by Triad National Security, LLC, and the U.S. Department +# of Energy/National Nuclear Security Administration. The Government is granted for +# itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide +# license in this material to reproduce, prepare derivative works, distribute copies to +# the public, perform publicly and display publicly, and to permit others to do so. +#======================================================================================== + +# Modules +import math +import numpy as np +import matplotlib +matplotlib.use('agg') +import matplotlib.pylab as plt +import sys +import os +import utils.test_case +import unyt +import itertools + + +class PrecessedJetCoords: + def __init__(self,theta,phi): + self.theta = theta + self.phi = phi + + #Axis of the jet + self.jet_n = np.array((np.cos(self.theta)*np.sin(self.phi), + np.sin(self.theta)*np.sin(self.phi), + np.cos(self.phi))) + + def cart_to_rho_h(self,pos_cart): + """ + Convert from cartesian coordinates to jet coordinates + """ + + pos_h = np.sum(pos_cart*self.jet_n[:,None],axis=0) + pos_rho = np.linalg.norm( pos_cart - pos_h*self.jet_n[:,None],axis=0) + return (pos_rho,pos_h) + +class ZJetCoords: + def __init__(self): + self.jet_n = np.array((0,0,1.0)) + + def cart_to_rho_h(self,pos_cart): + """ + Convert from cartesian coordinates to jet coordinates + """ + + pos_rho = np.linalg.norm(pos_cart[:2]) + pos_h = pos_cart[2] + + return pos_rho,pos_h + +""" To prevent littering up imported folders with .pyc files or __pycache_ folder""" +sys.dont_write_bytecode = True + +class TestCase(utils.test_case.TestCaseAbs): + def __init__(self): + + #Define cluster parameters + #Setup units + unyt.define_unit("code_length",(1,"Mpc")) + unyt.define_unit("code_mass",(1e14,"Msun")) + unyt.define_unit("code_time",(1,"Gyr")) + self.code_length = unyt.unyt_quantity(1,"code_length") + self.code_mass = unyt.unyt_quantity(1,"code_mass") + self.code_time = unyt.unyt_quantity(1,"code_time") + + self.tlim = unyt.unyt_quantity(5e-3,"code_time") + + #Setup constants + self.k_b = unyt.kb_cgs + self.G = unyt.G_cgs + self.m_u =unyt.amu + + self.adiabatic_index = 5./3. + self.He_mass_fraction = 0.25 + + #Define the initial uniform gas + self.uniform_gas_rho = unyt.unyt_quantity(1e-24,"g/cm**3") + self.uniform_gas_ux = unyt.unyt_quantity(0,"cm/s") + self.uniform_gas_uy = unyt.unyt_quantity(0,"cm/s") + self.uniform_gas_uz = unyt.unyt_quantity(0,"cm/s") + self.uniform_gas_pres = unyt.unyt_quantity(1e-10,"dyne/cm**2") + + self.uniform_gas_Mx = self.uniform_gas_rho*self.uniform_gas_ux + self.uniform_gas_My = self.uniform_gas_rho*self.uniform_gas_uy + self.uniform_gas_Mz = self.uniform_gas_rho*self.uniform_gas_uz + self.uniform_gas_energy_density = \ + 1./2.*self.uniform_gas_rho*(self.uniform_gas_ux**2 + self.uniform_gas_uy**2 + self.uniform_gas_uz**2) \ + + self.uniform_gas_pres/(self.adiabatic_index - 1.) + + #The precessing jet + self.jet_phi = 0.2 + self.jet_theta_dot = 0 + self.jet_theta0 = 1 + self.precessed_jet_coords = PrecessedJetCoords(self.jet_theta0,self.jet_phi) + self.zjet_coords = ZJetCoords() + + #Feedback parameters + self.fixed_power = unyt.unyt_quantity(1e44,"erg/s") + self.agn_thermal_radius = unyt.unyt_quantity(0.5,"kpc") + self.efficiency = 1.0e-3 + self.agn_jet_radius = unyt.unyt_quantity(0.25,"kpc") + self.agn_jet_height = unyt.unyt_quantity(1,"kpc") + + self.norm_tol = 1e-3 + + self.steps = 4 + self.step_params_list = list(itertools.product( + ("thermal_only","kinetic_only","combined"),(True,False))) + + def Prepare(self,parameters, step): + """ + Any preprocessing that is needed before the drive is run can be done in + this method + + This includes preparing files or any other pre processing steps that + need to be implemented. The method also provides access to the + parameters object which controls which parameters are being used to run + the driver. + + It is possible to append arguments to the driver_cmd_line_args if it is + desired to override the parthenon input file. Each element in the list + is simply a string of the form '/=', where the + contents of the string are exactly what one would type on the command + line run running a parthenon driver. + + As an example if the following block was uncommented it would overwrite + any of the parameters that were specified in the parthenon input file + parameters.driver_cmd_line_args = ['output1/file_type=vtk', + 'output1/variable=cons', + 'output1/dt=0.4', + 'time/tlim=0.4', + 'mesh/nx1=400'] + """ + feedback_mode,precessed_jet = self.step_params_list[step-1] + output_id = f"{feedback_mode}_precessed_{precessed_jet}" + + if feedback_mode == "thermal_only": + agn_kinetic_fraction = 0.0 + agn_thermal_fraction = 1.0 + elif feedback_mode == "kinetic_only": + agn_kinetic_fraction = 1.0 + agn_thermal_fraction = 0.0 + elif feedback_mode == "combined": + agn_kinetic_fraction = 0.5 + agn_thermal_fraction = 0.5 + else: + raise Exception(f"Feedback mode {feedback_mode} not supported in analysis") + + + + parameters.driver_cmd_line_args = [ + f"parthenon/output2/id={output_id}", + f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", + f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", + f"hydro/gamma={self.adiabatic_index}", + f"hydro/He_mass_fraction={self.He_mass_fraction}", + + f"units/code_length_cgs={self.code_length.in_units('cm').v}", + f"units/code_mass_cgs={self.code_mass.in_units('g').v}", + f"units/code_time_cgs={self.code_time.in_units('s').v}", + + f"problem/cluster/uniform_gas/init_uniform_gas=true", + f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", + f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", + + f"problem/cluster/precessing_jet/jet_phi={self.jet_phi if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_theta_dot={self.jet_theta_dot if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_theta0={self.jet_theta0 if precessed_jet else 0}", + + f"problem/cluster/agn_feedback/fixed_power={self.fixed_power.in_units('code_mass*code_length**2/code_time**3').v}", + f"problem/cluster/agn_feedback/efficiency={self.efficiency}", + f"problem/cluster/agn_feedback/thermal_fraction={agn_thermal_fraction}", + f"problem/cluster/agn_feedback/kinetic_fraction={agn_kinetic_fraction}", + f"problem/cluster/agn_feedback/magnetic_fraction=0", + f"problem/cluster/agn_feedback/thermal_radius={self.agn_thermal_radius.in_units('code_length').v}", + f"problem/cluster/agn_feedback/kinetic_jet_radius={self.agn_jet_radius.in_units('code_length').v}", + f"problem/cluster/agn_feedback/kinetic_jet_height={self.agn_jet_height.in_units('code_length').v}", + ] + + + return parameters + + def Analyse(self,parameters): + """ + Analyze the output and determine if the test passes. + + This function is called after the driver has been executed. It is + responsible for reading whatever data it needs and making a judgment + about whether or not the test passes. It takes no inputs. Output should + be True (test passes) or False (test fails). + + The parameters that are passed in provide the paths to relevant + locations and commands. Of particular importance is the path to the + output folder. All files from a drivers run should appear in and output + folder located in + parthenon/tst/regression/test_suites/test_name/output. + + It is possible in this function to read any of the output files such as + hdf5 output and compare them to expected quantities. + + """ + analyze_status = True + + self.Yp = self.He_mass_fraction + self.mu = 1/(self.Yp*3./4. + (1-self.Yp)*2) + self.mu_e = 1/(self.Yp*2./4. + (1-self.Yp)) + + + for step in range(1,self.steps+1): + feedback_mode,precessed_jet = self.step_params_list[step-1] + output_id = f"{feedback_mode}_precessed_{precessed_jet}" + step_status = True + + print(f"Testing {output_id}") + + if precessed_jet is True: + jet_coords = self.precessed_jet_coords + else: + jet_coords = self.zjet_coords + + if feedback_mode == "thermal_only": + agn_kinetic_fraction = 0.0 + agn_thermal_fraction = 1.0 + elif feedback_mode == "kinetic_only": + agn_kinetic_fraction = 1.0 + agn_thermal_fraction = 0.0 + elif feedback_mode == "combined": + agn_kinetic_fraction = 0.5 + agn_thermal_fraction = 0.5 + else: + raise Exception(f"Feedback mode {feedback_mode} not supported in analysis") + + jet_density = (agn_kinetic_fraction*self.fixed_power)/(self.efficiency*unyt.c_cgs**2)/( + 2*np.pi*self.agn_jet_radius**2*self.agn_jet_height) + + jet_velocity = np.sqrt( 2*self.efficiency)*unyt.c_cgs + + def kinetic_feedback(Z,Y,X,time): + if not hasattr(time,"units"): + time = unyt.unyt_quantity(time,"code_time") + R,H = jet_coords.cart_to_rho_h(np.array((X,Y,Z))) + R = unyt.unyt_array(R,"code_length") + H = unyt.unyt_array(H,"code_length") + + sign_jet = np.piecewise(H,[H <=0, H > 0],[-1,1]) + inside_jet = np.piecewise(R,[ R <= self.agn_jet_radius,],[1,0]) \ + *np.piecewise(H,[ np.abs(H) <= self.agn_jet_height,],[1,0]) + + drho = inside_jet*agn_kinetic_fraction*time*jet_density + dMx = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[0] + dMy = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[1] + dMz = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[2] + dE = inside_jet*agn_kinetic_fraction*time*0.5*jet_density*jet_velocity**2 + + return drho,dMx,dMy,dMz,dE + + def thermal_feedback(Z,Y,X,time): + if not hasattr(time,"units"): + time = unyt.unyt_quantity(time,"code_time") + R = np.sqrt( X**2 + Y**2 + Z**2) + inside_sphere = np.piecewise(R,[ R <= self.agn_thermal_radius.in_units("code_length"),],[1,0]) + dE = inside_sphere*time*(self.fixed_power*agn_thermal_fraction/(4./3.*np.pi*self.agn_thermal_radius**3)) + + drho = inside_sphere*time*(self.fixed_power/(self.efficiency*unyt.c_cgs**2)*agn_thermal_fraction/(4./3.*np.pi*self.agn_thermal_radius**3)) + #Assume no velocity, no change in momentum with mass injection + return drho,dE + + + def agn_feedback(Z,Y,X,dt): + + drho_k,dMx_k,dMy_k,dMz_k,dE_k = kinetic_feedback(Z,Y,X,dt) + drho_t,dE_t = thermal_feedback(Z,Y,X,dt) + + drho = drho_k + drho_t + dMx = dMx_k + dMy = dMx_k + dMz = dMx_k + dE = dE_k + dE_t + + return drho,dMx,dMy,dMz,dE + + + #Check that the initial and final outputs match the expected tower + sys.path.insert(1, parameters.parthenon_path + '/scripts/python/packages/parthenon_tools/parthenon_tools') + + try: + import compare_analytic + except ModuleNotFoundError: + print("Couldn't find module to analyze Parthenon hdf5 files.") + return False + + initial_analytic_components = { + "Density":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_rho.in_units("code_mass/code_length**3").v, + "MomentumDensity1":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_Mx.in_units("code_mass*code_length**-2*code_time**-1").v, + "MomentumDensity2":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_My.in_units("code_mass*code_length**-2*code_time**-1").v, + "MomentumDensity3":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_Mz.in_units("code_mass*code_length**-2*code_time**-1").v, + "TotalEnergyDensity":lambda Z,Y,X,time : + np.ones_like(Z)*self.uniform_gas_energy_density.in_units("code_mass*code_length**-1*code_time**-2").v, + } + + final_analytic_components = { + "Density":lambda Z,Y,X,time : + ( self.uniform_gas_rho + agn_feedback(Z,Y,X,time)[0]).in_units("code_mass/code_length**3").v, + "MomentumDensity1":lambda Z,Y,X,time : + ( self.uniform_gas_Mx + agn_feedback(Z,Y,X,time)[1]).in_units("code_mass*code_length**-2*code_time**-1").v, + "MomentumDensity2":lambda Z,Y,X,time : + ( self.uniform_gas_Mx + agn_feedback(Z,Y,X,time)[2]).in_units("code_mass*code_length**-2*code_time**-1").v, + "MomentumDensity3":lambda Z,Y,X,time : + ( self.uniform_gas_Mx + agn_feedback(Z,Y,X,time)[3]).in_units("code_mass*code_length**-2*code_time**-1").v, + "TotalEnergyDensity":lambda Z,Y,X,time : + (self.uniform_gas_energy_density + agn_feedback(Z,Y,X,time)[4]).in_units("code_mass*code_length**-1*code_time**-2").v, + } + + phdf_files = [f"{parameters.output_path}/parthenon.{output_id}.{i:05d}.phdf" for i in range(2)] + + def zero_corrected_linf_err(gold,test): + non_zero_linf = np.max(np.abs((gold[gold!=0]-test[gold!=0])/gold[gold!=0]),initial=0) + zero_linf = np.max(np.abs((gold[gold==0]-test[gold==0])),initial=0) + + return np.max((non_zero_linf,zero_linf)) + + #Use a very loose tolerance, linf relative error + initial_analytic_status,final_analytic_status = [ + compare_analytic.compare_analytic( + phdf_file, analytic_components,err_func=zero_corrected_linf_err,tol=1e-3) + for analytic_components,phdf_file in zip((initial_analytic_components,final_analytic_components), + phdf_files)] + + print(" Initial analytic status",initial_analytic_status) + print(" Final analytic status",final_analytic_status) + + analyze_status &= initial_analytic_status & final_analytic_status + + + return analyze_status diff --git a/tst/regression/test_suites/cluster_magnetic_tower/__init__.py b/tst/regression/test_suites/cluster_magnetic_tower/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py new file mode 100644 index 00000000..09319c15 --- /dev/null +++ b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py @@ -0,0 +1,523 @@ +#======================================================================================== +# AthenaPK - a performance portable block structured AMR MHD code +# Copyright (c) 2020-2021, Athena Parthenon Collaboration. All rights reserved. +# Licensed under the 3-clause BSD License, see LICENSE file for details +#======================================================================================== +# (C) (or copyright) 2020. Triad National Security, LLC. All rights reserved. +# +# This program was produced under U.S. Government contract 89233218CNA000001 for Los +# Alamos National Laboratory (LANL), which is operated by Triad National Security, LLC +# for the U.S. Department of Energy/National Nuclear Security Administration. All rights +# in the program are reserved by Triad National Security, LLC, and the U.S. Department +# of Energy/National Nuclear Security Administration. The Government is granted for +# itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide +# license in this material to reproduce, prepare derivative works, distribute copies to +# the public, perform publicly and display publicly, and to permit others to do so. +#======================================================================================== + +# Modules +import math +import numpy as np +import matplotlib +matplotlib.use('agg') +import matplotlib.pylab as plt +import sys +import os +import utils.test_case +import unyt +import itertools + + +class PrecessedJetCoords: + #Note: Does note rotate the vector around the jet axis, only rotates the vector to `z_hat` + def __init__(self,phi_jet,theta_jet): + self.phi_jet = phi_jet + self.theta_jet = theta_jet + + def cart_to_jet_coords(self,pos_sim): + """ + Convert from simulation cartesian coordinates to jet cylindrical coordinates + """ + + x_sim = pos_sim[0] + y_sim = pos_sim[1] + z_sim = pos_sim[2] + + x_jet = x_sim*np.cos(self.phi_jet)*np.cos(self.theta_jet) + y_sim*np.sin(self.phi_jet) - z_sim*np.sin(self.theta_jet)*np.cos(self.phi_jet) + y_jet = -x_sim*np.sin(self.phi_jet)*np.cos(self.theta_jet) + y_sim*np.cos(self.phi_jet) + z_sim*np.sin(self.phi_jet)*np.sin(self.theta_jet) + z_jet = x_sim*np.sin(self.theta_jet) + z_sim*np.cos(self.theta_jet) + + r_jet = np.sqrt( x_jet**2 + y_jet**2) + theta_jet = np.arctan2(y_jet,x_jet) + h_jet = z_jet + return (r_jet, theta_jet, h_jet) + + def jet_to_cart_vec(self,pos_sim,vec_jet): + + r_pos, theta_pos, h_pos = self.cart_to_jet_coords(pos_sim) + + #Convert jet-cylindrical vec_jet into jet-cartesian + R = np.array((( np.cos(theta_pos),-np.sin(theta_pos), np.zeros_like(theta_pos)), + ( np.sin(theta_pos), np.cos(theta_pos), np.zeros_like(theta_pos)), + ( np.zeros_like(theta_pos),np.zeros_like(theta_pos), np.ones_like(theta_pos)))).reshape( + (3,3,*(theta_pos.shape))) + #vec_jet_cart = np.einsum("ij,jxyz",R,vec_jet) + vec_jet_cart = unyt.unyt_array((R[0,0]*vec_jet[0] + R[0,1]*vec_jet[1] + R[0,2]*vec_jet[2], + R[1,0]*vec_jet[0] + R[1,1]*vec_jet[1] + R[1,2]*vec_jet[2], + R[2,0]*vec_jet[0] + R[2,1]*vec_jet[1] + R[2,2]*vec_jet[2]), + vec_jet.units) + + + R = np.array(((np.cos(self.phi_jet)*np.cos(self.theta_jet), + -np.sin(self.phi_jet)*np.cos(self.theta_jet), + np.sin(self.theta_jet)), + + (np.sin(self.phi_jet), + np.cos(self.phi_jet), + 0), + + (-np.sin(self.theta_jet)*np.cos(self.phi_jet), + np.sin(self.phi_jet)*np.sin(self.theta_jet), + np.cos(self.theta_jet)) + )) + + #vec_cart = np.matmul(R,vec_jet_cart) + vec_cart = unyt.unyt_array((R[0,0]*vec_jet_cart[0] + R[0,1]*vec_jet_cart[1] + R[0,2]*vec_jet_cart[2], + R[1,0]*vec_jet_cart[0] + R[1,1]*vec_jet_cart[1] + R[1,2]*vec_jet_cart[2], + R[2,0]*vec_jet_cart[0] + R[2,1]*vec_jet_cart[1] + R[2,2]*vec_jet_cart[2]), + vec_jet_cart.units) + + return vec_cart + +class ZJetCoords: + def __init__(self): + pass + + def cart_to_jet_coords(self,pos_cart): + """ + Convert from cartesian coordinates to jet coordinates + """ + + pos_rho = np.sqrt(pos_cart[0]**2 + pos_cart[1]**2) + pos_theta = np.arctan2(pos_cart[1],pos_cart[0]) + pos_theta[pos_rho == 0] = 0 + pos_h = pos_cart[2] + + return (pos_rho,pos_theta,pos_h) + + def jet_to_cart_vec(self,pos_cart,vec_jet): + + vec_rho = vec_jet[0] + vec_theta = vec_jet[1] + vec_h = vec_jet[2] + + r_pos,theta_pos,h_pos = self.cart_to_jet_coords(pos_cart) + + #Compute vector in cartesian coords + vec_x = vec_rho*np.cos(theta_pos) - vec_theta*np.sin(theta_pos) + vec_y = vec_rho*np.sin(theta_pos) + vec_theta*np.cos(theta_pos) + vec_z = vec_h + + return (vec_x,vec_y,vec_z) + +""" To prevent littering up imported folders with .pyc files or __pycache_ folder""" +sys.dont_write_bytecode = True + +class TestCase(utils.test_case.TestCaseAbs): + def __init__(self): + + #Define cluster parameters + #Setup units + unyt.define_unit("code_length",(1,"Mpc")) + unyt.define_unit("code_mass",(1e14,"Msun")) + unyt.define_unit("code_time",(1,"Gyr")) + self.code_length = unyt.unyt_quantity(1,"code_length") + self.code_mass = unyt.unyt_quantity(1,"code_mass") + self.code_time = unyt.unyt_quantity(1,"code_time") + + self.tlim = unyt.unyt_quantity(1e-2,"code_time") + + #Setup constants + self.k_b = unyt.kb_cgs + self.G = unyt.G_cgs + self.m_u =unyt.amu + + self.adiabatic_index = 5./3. + self.He_mass_fraction = 0.25 + + #Define the initial uniform gas + self.uniform_gas_rho = unyt.unyt_quantity(1e-24,"g/cm**3") + self.uniform_gas_ux = unyt.unyt_quantity(0,"cm/s") + self.uniform_gas_uy = unyt.unyt_quantity(0,"cm/s") + self.uniform_gas_uz = unyt.unyt_quantity(0,"cm/s") + self.uniform_gas_pres = unyt.unyt_quantity(1e-10,"dyne/cm**2") + + self.uniform_gas_Mx = self.uniform_gas_rho*self.uniform_gas_ux + self.uniform_gas_My = self.uniform_gas_rho*self.uniform_gas_uy + self.uniform_gas_Mz = self.uniform_gas_rho*self.uniform_gas_uz + self.uniform_gas_energy_density = \ + 1./2.*self.uniform_gas_rho*(self.uniform_gas_ux**2 + self.uniform_gas_uy**2 + self.uniform_gas_uz**2) \ + + self.uniform_gas_pres/(self.adiabatic_index - 1.) + + #Efficiency of power to accretion rate (controls rate of mass injection for this test) + #self.efficiency = 0 + self.efficiency = 1e-3 + + #The precessing jet + self.theta_jet = 0.2 + self.phi_dot_jet = 0 #Use phi_dot = 0 for stationary jet + self.phi_jet0 = 1 #Offset initial jet + self.precessed_jet_coords = PrecessedJetCoords(self.phi_jet0,self.theta_jet) + self.zjet_coords = ZJetCoords() + + + #Initial and Feedback shared parameters + self.magnetic_tower_alpha = 20 + #self.magnetic_tower_l_scale = unyt.unyt_quantity(1,"code_length") + self.magnetic_tower_l_scale = unyt.unyt_quantity(10,"kpc") + self.magnetic_tower_l_mass_scale = unyt.unyt_quantity(5,"kpc") + + #The Initial Tower + self.initial_magnetic_tower_field = unyt.unyt_quantity(1e-6,"G") + + #The Feedback Tower + #For const field tests + self.feedback_magnetic_tower_field = unyt.unyt_quantity(1e-4,"G/Gyr") + #For const energy tests + self.feedback_magnetic_tower_power = unyt.unyt_quantity(1e44,"erg/s") + #For const field tests + #self.feedback_magnetic_tower_mass = unyt.unyt_quantity(0,"g/s") + self.feedback_magnetic_tower_mass = self.feedback_magnetic_tower_power/( self.efficiency*unyt.c_cgs**2 ) + + self.energy_density_tol = 1e-2 + + #Tolerance of linf error of magnetic fields, total energy density, and density + self.linf_analytic_tol = 5e-2 + + #Tolerance on total initial and final magnetic energy + self.b_eng_initial_tol = 1e-2 + self.b_eng_final_tol = 1e-2 + + #Tolerance in max divergence over magnetic tower field scale + self.divB_tol = 1e-11 + + self.steps = 4 + self.step_params_list = list(itertools.product( ("const_field","const_power"),(True,False))) + + + def Prepare(self,parameters, step): + """ + Any preprocessing that is needed before the drive is run can be done in + this method + + This includes preparing files or any other pre processing steps that + need to be implemented. The method also provides access to the + parameters object which controls which parameters are being used to run + the driver. + + It is possible to append arguments to the driver_cmd_line_args if it is + desired to override the parthenon input file. Each element in the list + is simply a string of the form '/=', where the + contents of the string are exactly what one would type on the command + line run running a parthenon driver. + + As an example if the following block was uncommented it would overwrite + any of the parameters that were specified in the parthenon input file + parameters.driver_cmd_line_args = ['output1/file_type=vtk', + 'output1/variable=cons', + 'output1/dt=0.4', + 'time/tlim=0.4', + 'mesh/nx1=400'] + """ + feedback_mode,precessed_jet = self.step_params_list[step-1] + output_id = f"{feedback_mode}_precessed_{precessed_jet}" + + if feedback_mode == "const_power": + fixed_power = self.feedback_magnetic_tower_power.in_units('code_mass*code_length**2/code_time**3').v + fixed_field_rate = 0 + fixed_mass_rate = 0 + else: + fixed_power = 0 + fixed_field_rate = self.feedback_magnetic_tower_field.in_units('sqrt(code_mass)/sqrt(code_length)/code_time**2').v + fixed_mass_rate = self.feedback_magnetic_tower_mass.in_units("code_mass/code_time").v + + parameters.driver_cmd_line_args = [ + f"parthenon/output2/id={output_id}", + f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", + f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", + f"hydro/gamma={self.adiabatic_index}", + f"hydro/He_mass_fraction={self.He_mass_fraction}", + + f"units/code_length_cgs={self.code_length.in_units('cm').v}", + f"units/code_mass_cgs={self.code_mass.in_units('g').v}", + f"units/code_time_cgs={self.code_time.in_units('s').v}", + + f"problem/cluster/uniform_gas/init_uniform_gas=true", + f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", + f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", + + f"problem/cluster/precessing_jet/jet_theta={self.theta_jet if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_phi_dot={self.phi_dot_jet if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_phi0={self.phi_jet0 if precessed_jet else 0}", + + f"problem/cluster/agn_feedback/fixed_power={fixed_power}", + f"problem/cluster/agn_feedback/efficiency={self.efficiency}", + f"problem/cluster/agn_feedback/magnetic_fraction=1", + f"problem/cluster/agn_feedback/kinetic_fraction=0", + f"problem/cluster/agn_feedback/thermal_fraction=0", + + f"problem/cluster/magnetic_tower/alpha={self.magnetic_tower_alpha}", + f"problem/cluster/magnetic_tower/l_scale={self.magnetic_tower_l_scale.in_units('code_length').v}", + f"problem/cluster/magnetic_tower/initial_field={self.initial_magnetic_tower_field.in_units('sqrt(code_mass)/sqrt(code_length)/code_time').v}", + f"problem/cluster/magnetic_tower/fixed_field_rate={fixed_field_rate}", + f"problem/cluster/magnetic_tower/fixed_mass_rate={fixed_mass_rate}", + f"problem/cluster/magnetic_tower/l_mass_scale={self.magnetic_tower_l_mass_scale.in_units('code_length').v}", + ] + + + return parameters + + def Analyse(self,parameters): + """ + Analyze the output and determine if the test passes. + + This function is called after the driver has been executed. It is + responsible for reading whatever data it needs and making a judgment + about whether or not the test passes. It takes no inputs. Output should + be True (test passes) or False (test fails). + + The parameters that are passed in provide the paths to relevant + locations and commands. Of particular importance is the path to the + output folder. All files from a drivers run should appear in and output + folder located in + parthenon/tst/regression/test_suites/test_name/output. + + It is possible in this function to read any of the output files such as + hdf5 output and compare them to expected quantities. + + """ + analyze_status = True + + self.Yp = self.He_mass_fraction + self.mu = 1/(self.Yp*3./4. + (1-self.Yp)*2) + self.mu_e = 1/(self.Yp*2./4. + (1-self.Yp)) + + magnetic_units = 'sqrt(code_mass)/sqrt(code_length)/code_time' + + for step in range(1,self.steps+1): + feedback_mode,precessed_jet = self.step_params_list[step-1] + output_id = f"{feedback_mode}_precessed_{precessed_jet}" + step_status = True + + print(">"*20) + print(f"Testing {output_id}") + print(">"*20) + + B0_initial = self.initial_magnetic_tower_field + #Compute the initial magnetic energy + b_eng_initial_anyl = np.pi**(3./2.)/(8*np.sqrt(2))*(5 + self.magnetic_tower_alpha**2)*self.magnetic_tower_l_scale**3*B0_initial**2 + + if feedback_mode == "const_field": + B0_final = self.feedback_magnetic_tower_field*self.tlim + self.initial_magnetic_tower_field + #Estimate the final magnetic field using the total energy of the tower out to inifinity + b_eng_final_anyl = np.pi**(3./2.)/(8*np.sqrt(2))*(5 + self.magnetic_tower_alpha**2)*self.magnetic_tower_l_scale**3*B0_final**2 + injected_mass = self.feedback_magnetic_tower_mass*self.tlim + elif feedback_mode == "const_power": + #Estimate the final magnetic field using the total energy of the tower out to inifinity + #Slightly inaccurate due to finite domain + B0_final = np.sqrt((b_eng_initial_anyl + self.feedback_magnetic_tower_power*self.tlim)/( + np.pi**(3./2.)/(8*np.sqrt(2))*(5 + self.magnetic_tower_alpha**2)*self.magnetic_tower_l_scale**3)) + b_eng_final_anyl = self.feedback_magnetic_tower_power*self.tlim + \ + (np.pi**(3./2.)/(8*np.sqrt(2))*(5 + self.magnetic_tower_alpha**2)*self.magnetic_tower_l_scale**3)*B0_initial**2 + injected_mass = self.feedback_magnetic_tower_power/(self.efficiency*unyt.c_cgs**2)*self.tlim + else: + raise Exception(f"Feedback mode {feedback_mode} not supported in analysis") + + rho0_final = injected_mass/(self.magnetic_tower_l_mass_scale**3*np.pi**(3./2.)) + + if precessed_jet is True: + jet_coords = self.precessed_jet_coords + else: + jet_coords = self.zjet_coords + + def field_func(Z,Y,X,B0): + l = self.magnetic_tower_l_scale + alpha = self.magnetic_tower_alpha + + pos_cart = unyt.unyt_array((X,Y,Z),"code_length") + R,Theta,H = jet_coords.cart_to_jet_coords(pos_cart) + + B_r = B0*2*(H/l)*(R/l) *np.exp( -(R/l)**2 -(H/l)**2) + B_theta = B0*alpha*(R/l) *np.exp( -(R/l)**2 -(H/l)**2) + B_h = B0*2*( 1 - (R/l)**2 )*np.exp( -(R/l)**2 -(H/l)**2) + B_jet = unyt.unyt_array((B_r,B_theta,B_h),magnetic_units) + + B_x,B_y,B_z = jet_coords.jet_to_cart_vec(pos_cart,B_jet) + + + return unyt.unyt_array((B_x, B_y, B_z),magnetic_units) + + b_energy_func = lambda Z,Y,X,B0 : 0.5*np.sum(field_func(Z,Y,X,B0)**2,axis=0) + + def density_func(Z,Y,X,rho0): + pos_cart = unyt.unyt_array((X,Y,Z),"code_length") + R,Theta,H = jet_coords.cart_to_jet_coords(pos_cart) + + Density = self.uniform_gas_rho + rho0*np.exp( -(R**2 + H**2)/self.magnetic_tower_l_mass_scale**2 ) + return Density + + def internal_energy_density_func(Z,Y,X,rho0): + pos_cart = unyt.unyt_array((X,Y,Z),"code_length") + R,Theta,H = jet_coords.cart_to_jet_coords(pos_cart) + + rho_e = self.uniform_gas_energy_density + 1./(self.adiabatic_index - 1)*( + rho0*np.exp( -(R**2 + H**2)/self.magnetic_tower_l_mass_scale**2 ) + *(self.uniform_gas_pres/self.uniform_gas_rho)) + return rho_e + + + #Check that the initial and final outputs match the expected tower + sys.path.insert(1, parameters.parthenon_path + '/scripts/python/packages/parthenon_tools/parthenon_tools') + + try: + import compare_analytic + import phdf + except ModuleNotFoundError: + print("Couldn't find module to compare Parthenon hdf5 files.") + return False + + ######################################## + # Compare to the analytically expected densities, total energy + # densities, and magnetic fields + ######################################## + + phdf_filenames = [f"{parameters.output_path}/parthenon.{output_id}.{i:05d}.phdf" for i in range(2)] + + #Create a relative L-Inf errpr function, ignore where zero in gold data + rel_linf_err_func = lambda gold, test: compare_analytic.norm_err_func( + gold,test,norm_ord=np.inf,relative=True,ignore_gold_zero=True) + + #Create a linf error function scaled by a magnetic field + #Avoids relative comparisons in areas where magnetic field is close to zero + def B_scaled_linf_err(gold,test,B0): + err_val = np.abs((gold - test)/B0) + return err_val.max() + + #Use a very loose tolerance, linf relative error + analytic_statuses = [] + for B_field,rho0,phdf_filename,label in zip( + (B0_initial,B0_final), + (unyt.unyt_quantity(0,"code_mass*code_length**-3"),rho0_final), + phdf_filenames, + ("Initial","Final")): + + #Construct lambda functions for initial and final analytically + #expected density and total energy density + densities_analytic_components = { + "Density":lambda Z,Y,X,time : density_func(Z,Y,X,rho0).in_units("code_mass*code_length**-3").v, + "TotalEnergyDensity":lambda Z,Y,X,time : ( + internal_energy_density_func(Z,Y,X,rho0) + + b_energy_func(Z,Y,X,B_field)).in_units("code_mass*code_length**-1*code_time**-2").v + } + + + #Compare the simulation and analytic density and total energy density + densities_analytic_status = compare_analytic.compare_analytic( + phdf_filename, densities_analytic_components, + err_func=rel_linf_err_func, + tol=self.linf_analytic_tol) + + #Construct lambda functions for initial and final analytically expected magnetic fields + field_analytic_components = { + "MagneticField1":lambda Z,Y,X,time : + field_func(Z,Y,X,B_field).in_units(magnetic_units)[0].v, + "MagneticField2":lambda Z,Y,X,time : + field_func(Z,Y,X,B_field).in_units(magnetic_units)[1].v, + "MagneticField3":lambda Z,Y,X,time : + field_func(Z,Y,X,B_field).in_units(magnetic_units)[2].v, + } + + #Compare the simulation and analytic magnetic fields, + #scaled by magnetic tower field scale + field_analytic_status = compare_analytic.compare_analytic( + phdf_filename, field_analytic_components, + err_func=lambda gold,test: B_scaled_linf_err(gold,test, + B_field.in_units(magnetic_units).v[()]), + tol=self.linf_analytic_tol) + + analytic_status = (densities_analytic_status and field_analytic_status) + if not analytic_status: + print(f"{label} Analytic comparison failed\n") + + analytic_statuses.append(analytic_status) + + analyze_status &= np.all(analytic_statuses) + + for phdf_filename,b_eng_anyl,B0,b_eng_tol,label in zip(phdf_filenames, + (b_eng_initial_anyl,b_eng_final_anyl), + (B0_initial,B0_final), + (self.b_eng_initial_tol,self.b_eng_final_tol), + ("Initial","Final")): + + ######################################## + # Compare with the analytically expected total magnetic energy + ######################################## + phdf_file = phdf.phdf(phdf_filename) + + #Get the cell volumes from phdf_file + xf = phdf_file.xf + yf = phdf_file.yf + zf = phdf_file.zf + cell_vols = unyt.unyt_array(np.einsum('ai,aj,ak->aijk', + np.diff(zf),np.diff(yf),np.diff(xf)),"code_length**3") + + #Get the magnetic energy from phdf_file + B = unyt.unyt_array( + list(phdf_file.GetComponents(["MagneticField1","MagneticField2","MagneticField3"],flatten=False).values()), + magnetic_units) + b_eng = np.sum(0.5*np.sum(B**2,axis=0)*cell_vols) + + #Get the estimated magnetic energy from the expected mt_tower field + + Z,Y,X = phdf_file.GetVolumeLocations(flatten=False) + Z = unyt.unyt_array(Z,"code_length") + Y = unyt.unyt_array(Y,"code_length") + X = unyt.unyt_array(X,"code_length") + b_eng_numer = np.sum( b_energy_func(Z,Y,X,B0)*cell_vols ) + + b_eng_anyl_rel_err = np.abs((b_eng - b_eng_anyl)/b_eng_anyl) + b_eng_numer_rel_err = np.abs((b_eng - b_eng_numer)/b_eng_numer) + + if b_eng_anyl_rel_err > b_eng_tol: + print(f"{label} Analytically Integrated Relative Energy Error: {b_eng_anyl_rel_err} exceeds tolerance {b_eng_tol}", + f"Analytic {'>' if b_eng_anyl > b_eng else '<'} Simulation") + analyze_status = False + if b_eng_numer_rel_err > b_eng_tol: + print(f"{label} Numerically Integrated Relative Energy Error: {b_eng_numer_rel_err} exceeds tolerance {b_eng_tol}", + f"Numerical {'>' if b_eng_numer > b_eng else '<'} Simulation") + analyze_status = False + + ######################################## + # Check divB + ######################################## + + #FIXME: This computation of the fluxes would work better with 1 ghostzone from the simulation + #Compute cell lengths (note: these are NGridxNBlockSide) + dxf = np.diff(xf,axis=1) + dyf = np.diff(yf,axis=1) + dzf = np.diff(zf,axis=1) + + dBxdx = 0.5*(B[0,:,:,:,2:]-B[0,:,:,:,:-2])[:,1:-1,1:-1,:]/dxf[:,np.newaxis,np.newaxis,1:-1] + dBydy = 0.5*(B[1,:,:,2:,:]-B[1,:,:,:-2,:])[:,1:-1,:,1:-1]/dyf[:,np.newaxis,1:-1,np.newaxis] + dBzdz = 0.5*(B[2,:,2:,:,:]-B[2,:,:-2,:,:])[:,:,1:-1,1:-1]/dzf[:,1:-1,np.newaxis,np.newaxis] + + divB = dBxdx + dBydy + dBzdz + + if np.max(divB)/B0 > self.divB_tol: + print(f"{label} Max div B Error: Max divB/B0 {np.max(divB)/B0} exceeds tolerance {self.divB_tol}") + analyze_status = False + + return analyze_status diff --git a/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py b/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py index 6940f329..4da2d729 100644 --- a/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py +++ b/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py @@ -153,12 +153,12 @@ def Prepare(self,parameters, step): f"units/code_mass_cgs={self.code_mass.in_units('g').v}", f"units/code_time_cgs={self.code_time.in_units('s').v}", - f"problem/cluster/init_uniform_gas=true", - f"problem/cluster/uniform_gas_rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", - f"problem/cluster/uniform_gas_ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas_uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas_uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas_pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", + f"problem/cluster/uniform_gas/init_uniform_gas=true", + f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", + f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", f"cooling/table_filename={table_filename}", f"cooling/log_temp_col=0", From 3efae1dc2adff95395c8b5a20940a1bf6c676ee9 Mon Sep 17 00:00:00 2001 From: Philipp Grete Date: Mon, 20 Sep 2021 16:12:47 +0200 Subject: [PATCH 02/95] Finish rebase by updating input files --- inputs/cluster/agn_triggering.in | 6 +++--- inputs/cluster/hydro_agn_feedback.in | 6 +++--- inputs/cluster/magnetic_tower.in | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/inputs/cluster/agn_triggering.in b/inputs/cluster/agn_triggering.in index b7631a75..423c97d8 100644 --- a/inputs/cluster/agn_triggering.in +++ b/inputs/cluster/agn_triggering.in @@ -64,11 +64,11 @@ nx3 = 8 # Number of zones in X3-direction fluid = euler gamma = 1.6666666666666667 # gamma = C_p/C_v eos = adiabatic -riemann = hlle -reconstruction = plm +riemann = none +reconstruction = dc +calc_dt_hyp = true use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM -integrate_flux_div = false He_mass_fraction = 0.25 diff --git a/inputs/cluster/hydro_agn_feedback.in b/inputs/cluster/hydro_agn_feedback.in index c8886ef7..82acffe6 100644 --- a/inputs/cluster/hydro_agn_feedback.in +++ b/inputs/cluster/hydro_agn_feedback.in @@ -64,11 +64,11 @@ nx3 = 8 # Number of zones in X3-direction fluid = euler gamma = 1.6666666666666667 # gamma = C_p/C_v eos = adiabatic -riemann = hlle -reconstruction = plm +riemann = none +reconstruction = dc +calc_dt_hyp = true use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM -integrate_flux_div = false He_mass_fraction = 0.25 diff --git a/inputs/cluster/magnetic_tower.in b/inputs/cluster/magnetic_tower.in index 368ce580..9051c733 100644 --- a/inputs/cluster/magnetic_tower.in +++ b/inputs/cluster/magnetic_tower.in @@ -64,11 +64,11 @@ nx3 = 8 # Number of zones in X3-direction fluid = glmmhd gamma = 1.6666666666666667 # gamma = C_p/C_v eos = adiabatic -riemann = hlle -reconstruction = plm +riemann = none +reconstruction = dc +calc_dt_hyp = true use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM -integrate_flux_div = false He_mass_fraction = 0.25 From 49cd19d895c9e8323c511235edaddc7dd634fe86 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 18 Oct 2021 19:56:13 -0400 Subject: [PATCH 03/95] Small changes --- external/parthenon | 2 +- src/hydro/srcterms/tabular_cooling.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/parthenon b/external/parthenon index b0678c49..cd5216fe 160000 --- a/external/parthenon +++ b/external/parthenon @@ -1 +1 @@ -Subproject commit b0678c494d995952f62daa9fba062eb0839ef8af +Subproject commit cd5216feaebada06a90ac0632a328831a6e9ef73 diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 51973a25..421ce476 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -568,7 +568,7 @@ Real TabularCooling::EstimateTimeStep(MeshData *md) const { const Real cooling_time = (de_dt == 0 ? std::numeric_limits::infinity() : fabs(internal_e / de_dt)); - thread_min_cooling_time = std::min(cooling_time, min_cooling_time); + thread_min_cooling_time = std::min(cooling_time, thread_min_cooling_time); }, reducer_min); From 664c9bf2bd5f3296d36147971e261336fdb5c07b Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 19 Oct 2021 10:37:39 -0400 Subject: [PATCH 04/95] Fixed cluster_hydro_agn_feedback python --- .../cluster_hydro_agn_feedback.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 160f9c20..55ce5c6a 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -258,9 +258,9 @@ def kinetic_feedback(Z,Y,X,time): R = unyt.unyt_array(R,"code_length") H = unyt.unyt_array(H,"code_length") - sign_jet = np.piecewise(H,[H <=0, H > 0],[-1,1]) - inside_jet = np.piecewise(R,[ R <= self.agn_jet_radius,],[1,0]) \ - *np.piecewise(H,[ np.abs(H) <= self.agn_jet_height,],[1,0]) + sign_jet = np.piecewise(H,[H <=0, H > 0],[-1,1]).v + inside_jet = (np.piecewise(R,[ R <= self.agn_jet_radius,],[1,0]) \ + *np.piecewise(H,[ np.abs(H) <= self.agn_jet_height,],[1,0])).v drho = inside_jet*agn_kinetic_fraction*time*jet_density dMx = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[0] From 129e6b616b8419aa8b60411ccc2acc09e916062f Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 19 Oct 2021 11:49:11 -0400 Subject: [PATCH 05/95] Changed to parthenon/develop --- external/parthenon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/parthenon b/external/parthenon index cd5216fe..c99874d7 160000 --- a/external/parthenon +++ b/external/parthenon @@ -1 +1 @@ -Subproject commit cd5216feaebada06a90ac0632a328831a6e9ef73 +Subproject commit c99874d71620c412f338ded6412683a715efaffb From 469545b5fe1ecb1d0e656a112f6378026f30cf40 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 29 Nov 2021 12:19:32 -0500 Subject: [PATCH 06/95] Fixed hydro feedback test for Parthenon develop --- external/parthenon | 2 +- .../cluster_hydro_agn_feedback.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/external/parthenon b/external/parthenon index 72d301cf..51e5f0cd 160000 --- a/external/parthenon +++ b/external/parthenon @@ -1 +1 @@ -Subproject commit 72d301cfa3e40fe71db03cd13c3d925068d7d28f +Subproject commit 51e5f0cd5e5a98c64f2d6c2a49d60a4ab1adb839 diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 55ce5c6a..61268331 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -258,9 +258,9 @@ def kinetic_feedback(Z,Y,X,time): R = unyt.unyt_array(R,"code_length") H = unyt.unyt_array(H,"code_length") - sign_jet = np.piecewise(H,[H <=0, H > 0],[-1,1]).v + sign_jet = np.piecewise(H,[H <=0, H > 0],[-1,1]) inside_jet = (np.piecewise(R,[ R <= self.agn_jet_radius,],[1,0]) \ - *np.piecewise(H,[ np.abs(H) <= self.agn_jet_height,],[1,0])).v + *np.piecewise(H,[ np.abs(H) <= self.agn_jet_height,],[1,0])) drho = inside_jet*agn_kinetic_fraction*time*jet_density dMx = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[0] @@ -331,7 +331,7 @@ def agn_feedback(Z,Y,X,dt): (self.uniform_gas_energy_density + agn_feedback(Z,Y,X,time)[4]).in_units("code_mass*code_length**-1*code_time**-2").v, } - phdf_files = [f"{parameters.output_path}/parthenon.{output_id}.{i:05d}.phdf" for i in range(2)] + phdf_files = [f"{parameters.output_path}/parthenon.{output_id}.00000.phdf",f"{parameters.output_path}/parthenon.{output_id}.final.phdf"] def zero_corrected_linf_err(gold,test): non_zero_linf = np.max(np.abs((gold[gold!=0]-test[gold!=0])/gold[gold!=0]),initial=0) From 145dd3f23f6667935beaf8ba579e1820e669b614 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 29 Nov 2021 13:33:37 -0500 Subject: [PATCH 07/95] Fixed final output in regression tests --- .../aniso_therm_cond_ring_conv/aniso_therm_cond_ring_conv.py | 2 +- .../aniso_therm_cond_ring_multid.py | 2 +- .../cluster_agn_triggering/cluster_agn_triggering.py | 2 +- tst/regression/test_suites/cluster_hse/cluster_hse.py | 3 ++- .../cluster_magnetic_tower/cluster_magnetic_tower.py | 2 +- .../cluster_tabular_cooling/cluster_tabular_cooling.py | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tst/regression/test_suites/aniso_therm_cond_ring_conv/aniso_therm_cond_ring_conv.py b/tst/regression/test_suites/aniso_therm_cond_ring_conv/aniso_therm_cond_ring_conv.py index f45ae370..b971f545 100644 --- a/tst/regression/test_suites/aniso_therm_cond_ring_conv/aniso_therm_cond_ring_conv.py +++ b/tst/regression/test_suites/aniso_therm_cond_ring_conv/aniso_therm_cond_ring_conv.py @@ -70,7 +70,7 @@ def Analyse(self,parameters): errs = [] for res in res_cfgs: - data_filename = f"{parameters.output_path}/parthenon.{res}.00001.phdf" + data_filename = f"{parameters.output_path}/parthenon.{res}.final.phdf" data_file = phdf.phdf(data_filename) prim = data_file.Get("prim") T = prim[:,4] # because of gamma = 2.0 and rho = 1 -> p = e = T diff --git a/tst/regression/test_suites/aniso_therm_cond_ring_multid/aniso_therm_cond_ring_multid.py b/tst/regression/test_suites/aniso_therm_cond_ring_multid/aniso_therm_cond_ring_multid.py index 86d56945..c5499b63 100644 --- a/tst/regression/test_suites/aniso_therm_cond_ring_multid/aniso_therm_cond_ring_multid.py +++ b/tst/regression/test_suites/aniso_therm_cond_ring_multid/aniso_therm_cond_ring_multid.py @@ -102,7 +102,7 @@ def Analyse(self,parameters): errs = [] for step in range(1,5): - data_filename = f"{parameters.output_path}/parthenon.{step}.00001.phdf" + data_filename = f"{parameters.output_path}/parthenon.{step}.final.phdf" data_file = phdf.phdf(data_filename) prim = data_file.Get("prim") T = prim[:,4] # because of gamma = 2.0 and rho = 1 -> p = e = T diff --git a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py index 9995c833..4cdf75a3 100644 --- a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py +++ b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py @@ -328,7 +328,7 @@ def accretion_mask( Z,Y,X, inner_state, outer_state ): accretion_mask(Z,Y,X,final_pres,self.uniform_gas_pres).in_units("code_mass/(code_length*code_time**2)").v, } - phdf_files = [f"{parameters.output_path}/parthenon.{output_id}.{i:05d}.phdf" for i in range(2)] + phdf_files = [f"{parameters.output_path}/parthenon.{output_id}.00000.phdf",f"{parameters.output_path}/parthenon.{output_id}.final.phdf"] #Use a very loose tolerance, linf relative error analytic_status = True diff --git a/tst/regression/test_suites/cluster_hse/cluster_hse.py b/tst/regression/test_suites/cluster_hse/cluster_hse.py index 75931af2..a87bdbf5 100644 --- a/tst/regression/test_suites/cluster_hse/cluster_hse.py +++ b/tst/regression/test_suites/cluster_hse/cluster_hse.py @@ -338,7 +338,8 @@ def rk4(f,y0,T): print("Couldn't find module to compare Parthenon hdf5 files.") return False - files = [f"{parameters.output_path}/parthenon.prim.{i:05d}.phdf" for i in range(2)] + files = [f"{parameters.output_path}/parthenon.prim.00000.phdf",f"{parameters.output_path}/parthenon.prim.final.phdf"] + #files = [f"{parameters.output_path}/parthenon.prim.{i:05d}.phdf" for i in range(2)] #Compare the initial output to the analytic model def analytic_gold(Z,Y,X,analytic_var): diff --git a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py index 09319c15..cef65e55 100644 --- a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py +++ b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py @@ -394,7 +394,7 @@ def internal_energy_density_func(Z,Y,X,rho0): # densities, and magnetic fields ######################################## - phdf_filenames = [f"{parameters.output_path}/parthenon.{output_id}.{i:05d}.phdf" for i in range(2)] + phdf_filenames = [f"{parameters.output_path}/parthenon.prim.00000.phdf",f"{parameters.output_path}/parthenon.prim.final.phdf"] #Create a relative L-Inf errpr function, ignore where zero in gold data rel_linf_err_func = lambda gold, test: compare_analytic.norm_err_func( diff --git a/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py b/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py index 4da2d729..b506a425 100644 --- a/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py +++ b/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py @@ -298,7 +298,7 @@ def zero_corrected_linf_err(gold,test): adapt_final_internal_es = {} #internal_e for the adaptive tests for step in range(1,self.n_steps+1): - data_filename = f"{parameters.output_path}/parthenon.tabular_cooling_{step}.00001.phdf" + data_filename = f"{parameters.output_path}/parthenon.tabular_cooling_{step}.final.phdf" #Check that gas state matches the initial uniform gas except for the pressure final_uniform_gas_status = compare_analytic.compare_analytic( From 7e203bf9d419fdf01183d201ed90161dbbda2fd9 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 7 Dec 2021 12:33:50 -0500 Subject: [PATCH 08/95] Fixed magnetic tower ctest --- .../cluster_magnetic_tower/cluster_magnetic_tower.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py index cef65e55..b3ab25d9 100644 --- a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py +++ b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py @@ -394,7 +394,7 @@ def internal_energy_density_func(Z,Y,X,rho0): # densities, and magnetic fields ######################################## - phdf_filenames = [f"{parameters.output_path}/parthenon.prim.00000.phdf",f"{parameters.output_path}/parthenon.prim.final.phdf"] + phdf_filenames = [f"{parameters.output_path}/parthenon.{output_id}.00000.phdf",f"{parameters.output_path}/parthenon.{output_id}.final.phdf"] #Create a relative L-Inf errpr function, ignore where zero in gold data rel_linf_err_func = lambda gold, test: compare_analytic.norm_err_func( From 67a9f3ae66062ac588c27ae401fd31b43c19dfb5 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Fri, 10 Dec 2021 09:36:43 -0500 Subject: [PATCH 09/95] Zero masking divB when B^2==0 --- src/hydro/hydro.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/hydro/hydro.cpp b/src/hydro/hydro.cpp index 03a911c0..8fa5e856 100644 --- a/src/hydro/hydro.cpp +++ b/src/hydro/hydro.cpp @@ -101,14 +101,17 @@ Real HydroHst(MeshData *md) { divb += (cons(IB3, k + 1, j, i) - cons(IB3, k - 1, j, i)) / coords.Dx(X3DIR, k, j, i); } - lsum += + + Real abs_b = std::sqrt(SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + + SQR(cons(IB3, k, j, i))); + + lsum += ( abs_b != 0 ) ? 0.5 * (std::sqrt(SQR(coords.Dx(X1DIR, k, j, i)) + SQR(coords.Dx(X2DIR, k, j, i)) + SQR(coords.Dx(X3DIR, k, j, i)))) * - std::abs(divb) / - std::sqrt(SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + - SQR(cons(IB3, k, j, i))) * - coords.Volume(k, j, i); + std::abs(divb) / abs_b * + coords.Volume(k, j, i) + : 0 ;//Add zero when abs_b ==0 } }, sum); From c7733f1f90feefb43bda97c8f9da6e87b6e5062d Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 13 Dec 2021 12:28:03 -0500 Subject: [PATCH 10/95] Fixed Cluster restarts --- src/main.cpp | 1 + src/pgen/cluster.cpp | 145 +++++++++--------- src/pgen/cluster/agn_feedback.cpp | 4 +- src/pgen/cluster/agn_feedback.hpp | 2 +- src/pgen/cluster/agn_triggering.cpp | 2 +- src/pgen/cluster/agn_triggering.hpp | 2 +- src/pgen/cluster/cluster_gravity.hpp | 2 +- .../hydrostatic_equilibrium_sphere.cpp | 2 +- .../hydrostatic_equilibrium_sphere.hpp | 2 +- src/pgen/cluster/jet_coords.hpp | 2 +- src/pgen/cluster/magnetic_tower.hpp | 2 +- src/pgen/pgen.hpp | 1 + 12 files changed, 87 insertions(+), 80 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index d89df12d..cc8db4bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -82,6 +82,7 @@ int main(int argc, char *argv[]) { Hydro::ProblemSourceFirstOrder = rand_blast::RandomBlasts; } else if (problem == "cluster") { pman.app_input->ProblemGenerator = cluster::ProblemGenerator; + Hydro::ProblemInitPackageData = cluster::ProblemInitPackageData; Hydro::ProblemSourceUnsplit = cluster::ClusterSrcTerm; Hydro::ProblemEstimateTimestep = cluster::ClusterEstimateTimestep; } else if (problem == "sod") { diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index c7cbdd3b..1ff1335d 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -82,96 +82,101 @@ Real ClusterEstimateTimestep(MeshData *md) { } //======================================================================================== -//! \fn void InitUserMeshData(ParameterInput *pin) -// \brief Function to initialize problem-specific data in mesh class. Can also be used -// to initialize variables which are global to (and therefore can be passed to) other -// functions in this file. Called in Mesh constructor. +//! \fn void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) +//! \brief Init package data from parameter input //======================================================================================== -void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { - auto hydro_pkg = pmb->packages.Get("Hydro"); - if (pmb->lid == 0) { - /************************************************************ - * Read Uniform Gas - ************************************************************/ +void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) { - const bool init_uniform_gas = - pin->GetOrAddBoolean("problem/cluster/uniform_gas", "init_uniform_gas", false); - hydro_pkg->AddParam<>("init_uniform_gas", init_uniform_gas); + /************************************************************ + * Read Uniform Gas + ************************************************************/ - if (init_uniform_gas) { - const Real uniform_gas_rho = pin->GetReal("problem/cluster/uniform_gas", "rho"); - const Real uniform_gas_ux = pin->GetReal("problem/cluster/uniform_gas", "ux"); - const Real uniform_gas_uy = pin->GetReal("problem/cluster/uniform_gas", "uy"); - const Real uniform_gas_uz = pin->GetReal("problem/cluster/uniform_gas", "uz"); - const Real uniform_gas_pres = pin->GetReal("problem/cluster/uniform_gas", "pres"); + const bool init_uniform_gas = + pin->GetOrAddBoolean("problem/cluster/uniform_gas", "init_uniform_gas", false); + hydro_pkg->AddParam<>("init_uniform_gas", init_uniform_gas); - hydro_pkg->AddParam<>("uniform_gas_rho", uniform_gas_rho); - hydro_pkg->AddParam<>("uniform_gas_ux", uniform_gas_ux); - hydro_pkg->AddParam<>("uniform_gas_uy", uniform_gas_uy); - hydro_pkg->AddParam<>("uniform_gas_uz", uniform_gas_uz); - hydro_pkg->AddParam<>("uniform_gas_pres", uniform_gas_pres); - } + if (init_uniform_gas) { + const Real uniform_gas_rho = pin->GetReal("problem/cluster/uniform_gas", "rho"); + const Real uniform_gas_ux = pin->GetReal("problem/cluster/uniform_gas", "ux"); + const Real uniform_gas_uy = pin->GetReal("problem/cluster/uniform_gas", "uy"); + const Real uniform_gas_uz = pin->GetReal("problem/cluster/uniform_gas", "uz"); + const Real uniform_gas_pres = pin->GetReal("problem/cluster/uniform_gas", "pres"); + + hydro_pkg->AddParam<>("uniform_gas_rho", uniform_gas_rho); + hydro_pkg->AddParam<>("uniform_gas_ux", uniform_gas_ux); + hydro_pkg->AddParam<>("uniform_gas_uy", uniform_gas_uy); + hydro_pkg->AddParam<>("uniform_gas_uz", uniform_gas_uz); + hydro_pkg->AddParam<>("uniform_gas_pres", uniform_gas_pres); + } - /************************************************************ - * Read Cluster Gravity Parameters - ************************************************************/ + /************************************************************ + * Read Cluster Gravity Parameters + ************************************************************/ - // Build cluster_gravity object - ClusterGravity cluster_gravity(pin, hydro_pkg); - // hydro_pkg->AddParam<>("cluster_gravity", cluster_gravity); + // Build cluster_gravity object + ClusterGravity cluster_gravity(pin, hydro_pkg); + // hydro_pkg->AddParam<>("cluster_gravity", cluster_gravity); - // Include gravity as a source term during evolution - const bool gravity_srcterm = - pin->GetBoolean("problem/cluster/gravity", "gravity_srcterm"); - hydro_pkg->AddParam<>("gravity_srcterm", gravity_srcterm); + // Include gravity as a source term during evolution + const bool gravity_srcterm = + pin->GetBoolean("problem/cluster/gravity", "gravity_srcterm"); + hydro_pkg->AddParam<>("gravity_srcterm", gravity_srcterm); - /************************************************************ - * Read Initial Entropy Profile - ************************************************************/ + /************************************************************ + * Read Initial Entropy Profile + ************************************************************/ - // Build entropy_profile object - ACCEPTEntropyProfile entropy_profile(pin); + // Build entropy_profile object + ACCEPTEntropyProfile entropy_profile(pin); - /************************************************************ - * Build Hydrostatic Equilibrium Sphere - ************************************************************/ + /************************************************************ + * Build Hydrostatic Equilibrium Sphere + ************************************************************/ - HydrostaticEquilibriumSphere hse_sphere(pin, hydro_pkg, cluster_gravity, - entropy_profile); + HydrostaticEquilibriumSphere hse_sphere(pin, hydro_pkg, cluster_gravity, + entropy_profile); - /************************************************************ - * Read Precessing Jet Coordinate system - ************************************************************/ + /************************************************************ + * Read Precessing Jet Coordinate system + ************************************************************/ - JetCoordsFactory jet_coords_factory(pin, hydro_pkg); + JetCoordsFactory jet_coords_factory(pin, hydro_pkg); - /************************************************************ - * Read AGN Feedback - ************************************************************/ + /************************************************************ + * Read AGN Feedback + ************************************************************/ - AGNFeedback agn_feedback(pin, hydro_pkg); + AGNFeedback agn_feedback(pin, hydro_pkg); - /************************************************************ - * Read AGN Triggering - ************************************************************/ - AGNTriggering agn_triggering(pin, hydro_pkg); + /************************************************************ + * Read AGN Triggering + ************************************************************/ + AGNTriggering agn_triggering(pin, hydro_pkg); - /************************************************************ - * Read Magnetic Tower - ************************************************************/ + /************************************************************ + * Read Magnetic Tower + ************************************************************/ - // Build Magnetic Tower - MagneticTower magnetic_tower(pin, hydro_pkg); + // Build Magnetic Tower + MagneticTower magnetic_tower(pin, hydro_pkg); - // Determine if magnetic_tower_power_scaling is needed - // Is AGN Power and Magnetic fraction non-zero? - bool magnetic_tower_power_scaling = - (agn_feedback.magnetic_fraction_ != 0 && - (agn_feedback.fixed_power_ != 0 || - agn_triggering.triggering_mode_ != AGNTriggeringMode::NONE)); - hydro_pkg->AddParam("magnetic_tower_power_scaling", magnetic_tower_power_scaling); - } + // Determine if magnetic_tower_power_scaling is needed + // Is AGN Power and Magnetic fraction non-zero? + bool magnetic_tower_power_scaling = + (agn_feedback.magnetic_fraction_ != 0 && + (agn_feedback.fixed_power_ != 0 || + agn_triggering.triggering_mode_ != AGNTriggeringMode::NONE)); + hydro_pkg->AddParam("magnetic_tower_power_scaling", magnetic_tower_power_scaling); +} + +//======================================================================================== +//! \fn void MeshBlock::ProblemGenerator(ParameterInput *pin) +//! \brief Generate problem data on each meshblock +//======================================================================================== + +void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { + auto hydro_pkg = pmb->packages.Get("Hydro"); IndexRange ib = pmb->cellbounds.GetBoundsI(IndexDomain::interior); IndexRange jb = pmb->cellbounds.GetBoundsJ(IndexDomain::interior); diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index ccad69ae..251fa6a0 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -28,7 +28,7 @@ namespace cluster { using namespace parthenon; AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, - const std::shared_ptr &hydro_pkg) + parthenon::StateDescriptor* hydro_pkg) : fixed_power_(pin->GetOrAddReal("problem/cluster/agn_feedback", "fixed_power", 0.0)), efficiency_(pin->GetOrAddReal("problem/cluster/agn_feedback", "efficiency", 1e-3)), thermal_fraction_( @@ -190,4 +190,4 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, magnetic_tower.PowerSrcTerm(magnetic_power, magnetic_mass_rate, md, beta_dt, tm); } -} // namespace cluster \ No newline at end of file +} // namespace cluster diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index 3c8063bd..2511daba 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -39,7 +39,7 @@ class AGNFeedback { const bool disabled_; AGNFeedback(parthenon::ParameterInput *pin, - const std::shared_ptr &hydro_pkg); + parthenon::StateDescriptor* hydro_pkg); // parthenon::Real GetPower() const { return fixed_power_; } // void SetPower(const parthenon::Real power) { fixed_power_ = power; } diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index f2e32fa2..cf49deda 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -51,7 +51,7 @@ AGNTriggeringMode ParseAGNTriggeringMode(const std::string &mode_str) { } AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, - const std::shared_ptr &hydro_pkg, + parthenon::StateDescriptor* hydro_pkg, const std::string &block) : gamma_(pin->GetReal("hydro", "gamma")), triggering_mode_( diff --git a/src/pgen/cluster/agn_triggering.hpp b/src/pgen/cluster/agn_triggering.hpp index c0bb3a9a..b822bc6a 100644 --- a/src/pgen/cluster/agn_triggering.hpp +++ b/src/pgen/cluster/agn_triggering.hpp @@ -63,7 +63,7 @@ class AGNTriggering { const std::string triggering_filename_; AGNTriggering(parthenon::ParameterInput *pin, - const std::shared_ptr &hydro_pkg, + parthenon::StateDescriptor* hydro_pkg, const std::string &block = "problem/cluster/agn_triggering"); // Compute Cold gas accretion rate within the accretion radius for cold gas triggering diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 6998fb0b..0b900da2 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -90,7 +90,7 @@ class ClusterGravity { public: ClusterGravity(parthenon::ParameterInput *pin, - const std::shared_ptr &hydro_pkg) { + parthenon::StateDescriptor *hydro_pkg) { Units units(pin); // Determine which element to include diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp index a45a6dd4..8b1758b8 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp @@ -36,7 +36,7 @@ using namespace parthenon; template HydrostaticEquilibriumSphere:: HydrostaticEquilibriumSphere( - ParameterInput *pin, const std::shared_ptr &hydro_pkg, + ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg, GravitationalField gravitational_field, EntropyProfile entropy_profile) : gravitational_field_(gravitational_field), entropy_profile_(entropy_profile) { Units units(pin); diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp index 6832e29d..7f29e07b 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp @@ -136,7 +136,7 @@ class HydrostaticEquilibriumSphere { public: HydrostaticEquilibriumSphere( parthenon::ParameterInput *pin, - const std::shared_ptr &hydro_pkg, + parthenon::StateDescriptor *hydro_pkg, GravitationalField gravitational_field, EntropyProfile entropy_profile); template diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp index 0bcb950c..e044f066 100644 --- a/src/pgen/cluster/jet_coords.hpp +++ b/src/pgen/cluster/jet_coords.hpp @@ -97,7 +97,7 @@ class JetCoordsFactory { public: explicit JetCoordsFactory(parthenon::ParameterInput *pin, - const std::shared_ptr &hydro_pkg, + parthenon::StateDescriptor *hydro_pkg, const std::string &block = "problem/cluster/precessing_jet") : theta_jet_axis_(pin->GetOrAddReal(block, "jet_theta", 0)), phi_dot_jet_axis_(pin->GetOrAddReal(block, "jet_phi_dot", 0)), diff --git a/src/pgen/cluster/magnetic_tower.hpp b/src/pgen/cluster/magnetic_tower.hpp index 669ce66e..950661e7 100644 --- a/src/pgen/cluster/magnetic_tower.hpp +++ b/src/pgen/cluster/magnetic_tower.hpp @@ -129,7 +129,7 @@ class MagneticTower { const parthenon::Real l_mass_scale_; MagneticTower(parthenon::ParameterInput *pin, - const std::shared_ptr &hydro_pkg, + parthenon::StateDescriptor *hydro_pkg, const std::string &block = "problem/cluster/magnetic_tower") : alpha_(pin->GetOrAddReal(block, "alpha", 0)), l_scale_(pin->GetOrAddReal(block, "l_scale", 0)), diff --git a/src/pgen/pgen.hpp b/src/pgen/pgen.hpp index 8dd08eff..ed274689 100644 --- a/src/pgen/pgen.hpp +++ b/src/pgen/pgen.hpp @@ -96,6 +96,7 @@ void RandomBlasts(MeshData *md, const parthenon::SimTime &tm, const Real); namespace cluster { using namespace parthenon::driver::prelude; +void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *pkg); void InitUserMeshData(ParameterInput *pin); void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin); void ClusterSrcTerm(MeshData *md, const parthenon::SimTime &tm, const Real beta_dt); From cc17cfbd72da1b601ddec39b1f2a161fba089e02 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 13 Dec 2021 12:28:20 -0500 Subject: [PATCH 11/95] Cooling table T floor enforced --- src/hydro/srcterms/tabular_cooling.cpp | 30 ++++++++++++++++++++------ src/hydro/srcterms/tabular_cooling.hpp | 3 +++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 421ce476..d346e080 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -204,6 +204,9 @@ TabularCooling::TabularCooling(ParameterInput *pin) { } // Copy host_log_lambdas into device memory Kokkos::deep_copy(log_lambdas_, host_log_lambdas); + + //Change T_floor_ to be the max of the hydro temperature floor and the cooling table floor + T_floor_ = max(T_floor_,pow(10,log_temp_start_)); } void TabularCooling::SrcTerm(MeshData *md, const Real dt) const { @@ -299,6 +302,7 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt sub_dt = min_sub_dt; } + //subcycles iteration unsigned int sub_iter = 0; // check for dedt != 0.0 required in case cooling floor it hit during subcycling while ((sub_t * (1 + KEpsilon_) < dt) && @@ -329,13 +333,21 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt sub_attempt++; - if (!dedt_valid) { + if (!dedt_valid || internal_e_next_h <= internal_e_floor) { if (sub_dt == min_sub_dt) { - PARTHENON_FAIL("FATAL ERROR in [TabularCooling::SubcyclingSplitSrcTerm]: " - "Minumum sub_dt leads to negative internal energy"); + if( internal_e_floor < 0 ){ + PARTHENON_FAIL("FATAL ERROR in [TabularCooling::SubcyclingSplitSrcTerm]: " + "Minumum sub_dt leads to negative internal energy"); + } + //Set to internal_e_floor + internal_e_next_h = internal_e_floor; + //Cooling is finished: skip to end of cooling cycle with this subcycle + sub_dt = dt - sub_t; + reattempt_sub = false; + } else { + reattempt_sub = true; + sub_dt = min_sub_dt; } - reattempt_sub = true; - sub_dt = min_sub_dt; } else { // Compute error @@ -399,7 +411,13 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt sub_iter++; } - PARTHENON_REQUIRE(internal_e > internal_e_floor, "cooled below floor"); + //NOTE (forrestglines) It's unclear whether this section of code is necessary + //if( internal_e < internal_e_floor ){ + // internal_e = internal_e_floor; + //} + + //PARTHENON_REQUIRE(internal_e >= internal_energy_floor, "cooled below floor"); + PARTHENON_REQUIRE(internal_e > 0, "cooled below zero internal energy"); // Remove the cooling from the specific total energy cons(IEN, k, j, i) += rho * (internal_e - internal_e_initial); diff --git a/src/hydro/srcterms/tabular_cooling.hpp b/src/hydro/srcterms/tabular_cooling.hpp index d715039d..bfa529f2 100644 --- a/src/hydro/srcterms/tabular_cooling.hpp +++ b/src/hydro/srcterms/tabular_cooling.hpp @@ -108,6 +108,9 @@ class TabularCooling { CoolIntegrator integrator_; // Temperature floor (assumed in Kelvin and only used in cooling function) + // This is either the temperature floor used by the hydro method or the + // lowest temperature in the cooling table (assuming zero cooling below the + // table), whichever temperature is higher parthenon::Real T_floor_; // Maximum number of iterations/subcycles From a8574f31364c4b24eaa6b59417ab8c8f5d2a2094 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Sun, 26 Dec 2021 14:28:32 -0500 Subject: [PATCH 12/95] WIP Adding AGN triggering to history --- src/pgen/cluster/agn_feedback.cpp | 32 +++++++++++++++--- src/pgen/cluster/agn_feedback.hpp | 6 ++-- src/pgen/cluster/agn_triggering.cpp | 52 +++++++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 251fa6a0..5470813d 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -45,9 +45,36 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_height", 0.02)), disabled_(pin->GetOrAddBoolean("problem/cluster/agn_feedback", "disabled", false)) { + //Add user history output variable for AGN power + auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); + if( ! disabled_){ + //HACK (forrestglines): The operations should be a + //parthenon::UserHistoryOperation::no_reduce, which is as of writing + //unimplemented + hst_vars.emplace_back(parthenon::HistoryOutputVar(parthenon::UserHistoryOperation::max, + [this](MeshData *md) { + auto pmb = md->GetBlockData(0)->GetBlockPointer(); + auto hydro_pkg = pmb->packages.Get("Hydro"); + return this->GetFeedbackPower(hydro_pkg.get()); + }, "agn_feedback_power")); + } + hydro_pkg->UpdateParam(parthenon::hist_param_key, hst_vars); + hydro_pkg->AddParam<>("agn_feedback", *this); } +parthenon::Real AGNFeedback::GetFeedbackPower(StateDescriptor *hydro_pkg) const { + auto units = hydro_pkg->Param("units"); + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + + const Real accretion_rate = agn_triggering.GetAccretionRate(hydro_pkg); + const Real power = + fixed_power_ + accretion_rate * efficiency_ * pow(units.speed_of_light(), 2); + + return power; + +} + void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const parthenon::Real beta_dt, const parthenon::SimTime &tm) const { @@ -71,11 +98,8 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); auto units = hydro_pkg->Param("units"); - const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); - const Real accretion_rate = agn_triggering.GetAccretionRate(hydro_pkg.get()); - const Real power = - fixed_power_ + accretion_rate * efficiency_ * pow(units.speed_of_light(), 2); + const Real power = GetFeedbackPower(hydro_pkg.get()); const Real mass_rate = efficiency_ == 0 ? 0 : power / (efficiency_ * pow(units.speed_of_light(), 2)); diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index 2511daba..137b560f 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -39,12 +39,10 @@ class AGNFeedback { const bool disabled_; AGNFeedback(parthenon::ParameterInput *pin, - parthenon::StateDescriptor* hydro_pkg); + parthenon::StateDescriptor *hydro_pkg); - // parthenon::Real GetPower() const { return fixed_power_; } - // void SetPower(const parthenon::Real power) { fixed_power_ = power; } + parthenon::Real GetFeedbackPower(parthenon::StateDescriptor *hydro_pkg) const; - // Apply the feedback from hydrodynamic AGN feedback (kinetic jets and thermal feedback) void FeedbackSrcTerm(parthenon::MeshData *md, const parthenon::Real beta_dt, const parthenon::SimTime &tm) const; diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index cf49deda..b9d8b77d 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -99,6 +99,10 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, } } + + //Set up writing the triggering to file, used for debugging. Note that this is + //written every timestep, which is more frequently than history outputs. It + //is also not reduced across ranks and so is only valid without MPI if (write_to_file_ && parthenon::Globals::my_rank == 0) { // Clear the triggering_file std::ofstream triggering_file; @@ -106,6 +110,54 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, triggering_file.close(); } + //Set up UserHistoryOutput (Are all these triggering variables necessary? + //They might be for post-mortem debugging) + auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); + std::vector history_var_names; + switch (triggering_mode_) { + case AGNTriggeringMode::COLD_GAS: { + history_var_names.push_back("agn_triggering_cold_mass"); + break; + } + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + history_var_names.push_back("agn_triggering_total_mass"); + history_var_names.push_back("agn_triggering_mass_weighted_density"); + history_var_names.push_back("agn_triggering_mass_weighted_velocity"); + history_var_names.push_back("agn_triggering_mass_weighted_cs"); + break; + } + case AGNTriggeringMode::NONE: { + break; + } + } + + for( auto var_name : history_var_names){ + //HACK (forrestglines): The operations should be a + //parthenon::UserHistoryOperation::no_reduce, which is as of writing + //unimplemented + hst_vars.emplace_back(parthenon::HistoryOutputVar(parthenon::UserHistoryOperation::max, + [this,var_name](MeshData *md) { + auto pmb = md->GetBlockData(0)->GetBlockPointer(); + auto hydro_pkg = pmb->packages.Get("Hydro"); + return hydro_pkg->Param(var_name); + }, var_name)); + } + + //Add accretion rate too + if ( triggering_mode_ != AGNTriggeringMode::NONE ){ + //HACK (forrestglines): The operations should be a + //parthenon::UserHistoryOperation::no_reduce, which is as of writing + //unimplemented + hst_vars.emplace_back(parthenon::HistoryOutputVar(parthenon::UserHistoryOperation::max, + [this](MeshData *md) { + auto pmb = md->GetBlockData(0)->GetBlockPointer(); + auto hydro_pkg = pmb->packages.Get("Hydro"); + return this->GetAccretionRate(hydro_pkg.get()); + }, "agn_accretion_rate")); + } + hydro_pkg->UpdateParam(parthenon::hist_param_key, hst_vars); + hydro_pkg->AddParam("agn_triggering", *this); } From 26550e7b6679b6552958e63f60eae60e9d612835 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Sun, 26 Dec 2021 17:08:39 -0500 Subject: [PATCH 13/95] Fixed std::max in tabular_cooling --- src/hydro/srcterms/tabular_cooling.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index d346e080..eabaf109 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -206,7 +206,7 @@ TabularCooling::TabularCooling(ParameterInput *pin) { Kokkos::deep_copy(log_lambdas_, host_log_lambdas); //Change T_floor_ to be the max of the hydro temperature floor and the cooling table floor - T_floor_ = max(T_floor_,pow(10,log_temp_start_)); + T_floor_ = std::max(T_floor_,pow(10,log_temp_start_)); } void TabularCooling::SrcTerm(MeshData *md, const Real dt) const { From 37aa08c8497d3be2649cc75a3a4396911e096eb3 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 10 Jan 2022 15:15:44 -0500 Subject: [PATCH 14/95] Debugging statements for tabular cooling --- src/hydro/srcterms/tabular_cooling.cpp | 19 +++++++++++++++++++ src/hydro/srcterms/tabular_cooling.hpp | 3 ++- src/pgen/cluster.cpp | 4 ++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index eabaf109..b28affdb 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -266,6 +266,15 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt const Real rho = cons(IDN, k, j, i); // TODO(pgrete) with potentially more EOS, a separate get_pressure (or similar) // function could be useful. + //DEBUGGING(forrestglines) please remove + Real total_e = cons(IEN, k, j, i); + Real kinetic_e = 0.5 * (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + + SQR(cons(IM3, k, j, i)) / rho); + Real magnetic_e = 0.5 * (SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + + SQR(cons(IB3, k, j, i))); + Real pres = prim(IPR, k, j, i); + //END DEBUGGING + Real internal_e = cons(IEN, k, j, i) - 0.5 * (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + SQR(cons(IM3, k, j, i)) / rho); @@ -273,6 +282,8 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt internal_e -= 0.5 * (SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + SQR(cons(IB3, k, j, i))); } + + //Switch to specific internal energy internal_e /= rho; const Real internal_e_initial = internal_e; @@ -293,6 +304,14 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt // Check if cooling is actually happening, e.g., when T below T_cool_min or if // temperature is already below floor. const Real dedt_initial = DeDt_wrapper(0.0, internal_e_initial, dedt_valid); + + //DEBUGGING(forrestglines) please remove + //Testing if current cooling rate would drop the internal energy below the floor + if ( dedt_initial == 0 ) { + printf("Zero initial cooling\n"); + } + //END DEBUGGING + if (dedt_initial == 0.0 || internal_e_initial < internal_e_floor) { return; } diff --git a/src/hydro/srcterms/tabular_cooling.hpp b/src/hydro/srcterms/tabular_cooling.hpp index bfa529f2..f730f6e6 100644 --- a/src/hydro/srcterms/tabular_cooling.hpp +++ b/src/hydro/srcterms/tabular_cooling.hpp @@ -147,7 +147,8 @@ class TabularCooling { // log_lambda = log_lambdas(0); // TODO(forrestglines):Currently, zero cooling is use for temperatures // below the table. This behavior could be generalized via templates - return 0; + //return 0; + return log_lambdas(0);//HACK - to get it to cool to floor } else if (log_temp > log_temp_final) { // Above table // Return de/dt diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index 1ff1335d..3b3bcc51 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -8,8 +8,8 @@ // // Setups up an idealized galaxy cluster with an ACCEPT-like entropy profile in // hydrostatic equilbrium with an NFW+BCG+SMBH gravitational profile, -// optionally with an initial magnetic tower field. Includes tabular cooling, -// AGN feedback, AGN triggering via cold gas, simple SNIA Feedback(TODO) +// optionally with an initial magnetic tower field. Includes AGN feedback, AGN +// triggering via cold gas, simple SNIA Feedback(TODO) //======================================================================================== // C headers From a24cc21719576ee20981ef9584c95f5646ed6d84 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 16 May 2022 10:31:47 -0400 Subject: [PATCH 15/95] Bug fixes in tabular cooling --- src/hydro/srcterms/tabular_cooling.cpp | 40 +++++++++++++++++++------- src/hydro/srcterms/tabular_cooling.hpp | 9 ++++-- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index b28affdb..246f0cdb 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -59,6 +59,7 @@ TabularCooling::TabularCooling(ParameterInput *pin) { } max_iter_ = pin->GetOrAddInteger("cooling", "max_iter", 100); cooling_time_cfl_ = pin->GetOrAddReal("cooling", "cfl", 0.1); + min_cooling_timestep_ = pin->GetOrAddReal("cooling", "min_timestep", -1.0); d_log_temp_tol_ = pin->GetOrAddReal("cooling", "d_log_temp_tol", 1e-8); d_e_tol_ = pin->GetOrAddReal("cooling", "d_e_tol", 1e-8); // negative means disabled @@ -275,9 +276,11 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt Real pres = prim(IPR, k, j, i); //END DEBUGGING - Real internal_e = cons(IEN, k, j, i) - - 0.5 * (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + - SQR(cons(IM3, k, j, i)) / rho); + Real internal_e = + cons(IEN, k, j, i) - 0.5 * + (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + + SQR(cons(IM3, k, j, i))) / + rho; if (mhd_enabled) { internal_e -= 0.5 * (SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + SQR(cons(IB3, k, j, i))); @@ -307,9 +310,12 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt //DEBUGGING(forrestglines) please remove //Testing if current cooling rate would drop the internal energy below the floor - if ( dedt_initial == 0 ) { - printf("Zero initial cooling\n"); - } + //if ( internal_e_initial + dt*dedt_initial < 0 ) { + // printf("Fast Cooling\n"); + //} + //if ( dedt_initial == 0 ) { + // printf("Zero initial cooling\n"); + //} //END DEBUGGING if (dedt_initial == 0.0 || internal_e_initial < internal_e_floor) { @@ -356,7 +362,7 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt if (sub_dt == min_sub_dt) { if( internal_e_floor < 0 ){ PARTHENON_FAIL("FATAL ERROR in [TabularCooling::SubcyclingSplitSrcTerm]: " - "Minumum sub_dt leads to negative internal energy"); + "Minumum sub_dt leads to negative internal energy, no internal energy floor defined"); } //Set to internal_e_floor internal_e_next_h = internal_e_floor; @@ -488,9 +494,11 @@ void TabularCooling::MixedIntSrcTerm(parthenon::MeshData *md, // TODO(pgrete) with potentially more EOS, a separate get_pressure (or similar) // function could be useful. - Real internal_e = cons(IEN, k, j, i) - - 0.5 * (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + - SQR(cons(IM3, k, j, i)) / rho); + Real internal_e = + cons(IEN, k, j, i) - 0.5 * + (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + + SQR(cons(IM3, k, j, i))) / + rho; if (mhd_enabled) { internal_e -= 0.5 * (SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + SQR(cons(IB3, k, j, i))); @@ -557,6 +565,12 @@ void TabularCooling::MixedIntSrcTerm(parthenon::MeshData *md, } Real TabularCooling::EstimateTimeStep(MeshData *md) const { + + //If the min_cooling_timestep_ is infinity, don't constrain the timestep + if( min_cooling_timestep_ == std::numeric_limits::infinity() ){ + return min_cooling_timestep_; + } + // Grab member variables for compiler // Everything needed by DeDt @@ -609,7 +623,11 @@ Real TabularCooling::EstimateTimeStep(MeshData *md) const { }, reducer_min); - return cooling_time_cfl_ * min_cooling_time; + Real estimated_timestep = cooling_time_cfl_ * min_cooling_time; + if( estimated_timestep < min_cooling_timestep_){ + estimated_timestep = min_cooling_timestep_; + } + return estimated_timestep; } void TabularCooling::TestCoolingTable(ParameterInput *pin) const { diff --git a/src/hydro/srcterms/tabular_cooling.hpp b/src/hydro/srcterms/tabular_cooling.hpp index f730f6e6..1624b260 100644 --- a/src/hydro/srcterms/tabular_cooling.hpp +++ b/src/hydro/srcterms/tabular_cooling.hpp @@ -119,6 +119,10 @@ class TabularCooling { // Cooling CFL parthenon::Real cooling_time_cfl_; + // Minimum timestep that the cooling may limit the simulation timestep + // Use nonpositive values to disable + parthenon::Real min_cooling_timestep_; + // Tolerances parthenon::Real d_log_temp_tol_, d_e_tol_; @@ -148,7 +152,7 @@ class TabularCooling { // TODO(forrestglines):Currently, zero cooling is use for temperatures // below the table. This behavior could be generalized via templates //return 0; - return log_lambdas(0);//HACK - to get it to cool to floor + log_lambda = log_lambdas(0);//HACK - to get it to cool to floor } else if (log_temp > log_temp_final) { // Above table // Return de/dt @@ -178,7 +182,8 @@ class TabularCooling { } // Return de/dt const Real lambda = pow(10., log_lambda); - return -lambda * n_h2_by_rho; + const Real de_dt = -lambda * n_h2_by_rho; + return de_dt; } public: From a283abbb59af1b3ae74f0ed23331e8809f761c0a Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 16 May 2022 10:41:08 -0400 Subject: [PATCH 16/95] Bug fixes in AGN triggering --- src/pgen/cluster/agn_triggering.cpp | 65 +++++++++++++++++------------ src/pgen/cluster/agn_triggering.hpp | 3 ++ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index b9d8b77d..3cf32717 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -64,6 +64,7 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, bondi_n0_(pin->GetOrAddReal(block, "bondi_n0", 0)), bondi_beta_(pin->GetOrAddReal(block, "bondi_beta", 0)), accretion_cfl_(pin->GetOrAddReal(block, "accretion_cfl", 1e-1)), + remove_accreted_mass_(pin->GetOrAddBoolean(block, "removed_accreted_mass", true)), write_to_file_(pin->GetOrAddBoolean(block, "write_to_file", false)), triggering_filename_( pin->GetOrAddString(block, "triggering_filename", "agn_triggering.dat")) { @@ -189,6 +190,8 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, const Real cold_temp_thresh = cold_temp_thresh_; const Real cold_t_acc = cold_t_acc_; + const bool remove_accreted_mass = remove_accreted_mass_; + Real md_cold_mass = 0; parthenon::par_reduce( @@ -204,21 +207,22 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, const parthenon::Real r2 = pow(coords.x1v(i), 2) + pow(coords.x2v(j), 2) + pow(coords.x3v(k), 2); if (r2 < accretion_radius2) { - const Real cell_volume = coords.Volume(k, j, i); - const Real cell_mass = prim(IDN, k, j, i) * cell_volume; const Real temp = mean_molecular_mass_by_kb * prim(IPR, k, j, i) / prim(IDN, k, j, i); if (temp <= cold_temp_thresh) { - const Real cell_cold_mass = cell_mass; + + const Real cell_cold_mass = prim(IDN, k, j, i) * coords.Volume(k, j, i); team_cold_mass += cell_cold_mass; const Real cell_delta_rho = -prim(IDN, k, j, i) / cold_t_acc * dt; - - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); - // Update the Primitives - eos.ConsToPrim(cons, prim, k, j, i); + + if( remove_accreted_mass){ + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); + // Update the Primitives + eos.ConsToPrim(cons, prim, k, j, i); + } } } }, @@ -479,7 +483,7 @@ AGNTriggeringMPIReduceTriggering(parthenon::StateDescriptor *hydro_pkg) { Real accretion_rate = hydro_pkg->Param("agn_triggering_cold_mass"); PARTHENON_MPI_CHECK(MPI_Allreduce(MPI_IN_PLACE, &accretion_rate, 1, - MPI_PARTHENON_REAL, MPI_MAX, MPI_COMM_WORLD)); + MPI_PARTHENON_REAL, MPI_SUM, MPI_COMM_WORLD)); hydro_pkg->UpdateParam("agn_triggering_cold_mass", accretion_rate); break; } @@ -493,7 +497,7 @@ AGNTriggeringMPIReduceTriggering(parthenon::StateDescriptor *hydro_pkg) { }; PARTHENON_MPI_CHECK(MPI_Allreduce(MPI_IN_PLACE, &triggering_quantities, 4, - MPI_PARTHENON_REAL, MPI_MAX, MPI_COMM_WORLD)); + MPI_PARTHENON_REAL, MPI_SUM, MPI_COMM_WORLD)); hydro_pkg->UpdateParam("agn_triggering_total_mass", triggering_quantities[0]); hydro_pkg->UpdateParam("agn_triggering_mass_weighted_density", @@ -554,26 +558,35 @@ AGNTriggeringFinalizeTriggering(parthenon::MeshData *md, } // Remove accreted gas if using a Bondi-like mode - switch (agn_triggering.triggering_mode_) { - case AGNTriggeringMode::BOOSTED_BONDI: - case AGNTriggeringMode::BOOTH_SCHAYE: { - auto fluid = hydro_pkg->Param("fluid"); - if (fluid == Fluid::euler) { - agn_triggering.RemoveBondiAccretedGas(md, tm.dt, - hydro_pkg->Param("eos")); - } else if (fluid == Fluid::glmmhd) { - agn_triggering.RemoveBondiAccretedGas(md, tm.dt, - hydro_pkg->Param("eos")); - } else { - PARTHENON_FAIL("AGNTriggeringFinalizeTriggering: Unknown EOS"); + if ( agn_triggering.remove_accreted_mass_){ + switch (agn_triggering.triggering_mode_) { + case AGNTriggeringMode::BOOSTED_BONDI: + case AGNTriggeringMode::BOOTH_SCHAYE: { + auto fluid = hydro_pkg->Param("fluid"); + if (fluid == Fluid::euler) { + agn_triggering.RemoveBondiAccretedGas(md, tm.dt, + hydro_pkg->Param("eos")); + } else if (fluid == Fluid::glmmhd) { + agn_triggering.RemoveBondiAccretedGas(md, tm.dt, + hydro_pkg->Param("eos")); + } else { + PARTHENON_FAIL("AGNTriggeringFinalizeTriggering: Unknown EOS"); + } + break; + } + case AGNTriggeringMode::COLD_GAS: // Already removed during reduction + case AGNTriggeringMode::NONE: { + break; + } } - break; - } - case AGNTriggeringMode::COLD_GAS: // Already removed during reduction - case AGNTriggeringMode::NONE: { - break; } + + //DEBUGGING (forrestglines) + if( agn_triggering.GetAccretionRate(hydro_pkg.get()) > 0){ + printf("Accreting cold gas!\n"); } + //END DEBUGGING + return TaskStatus::complete; } diff --git a/src/pgen/cluster/agn_triggering.hpp b/src/pgen/cluster/agn_triggering.hpp index b822bc6a..ca704bba 100644 --- a/src/pgen/cluster/agn_triggering.hpp +++ b/src/pgen/cluster/agn_triggering.hpp @@ -55,6 +55,9 @@ class AGNTriggering { // Used in timestep estimation const parthenon::Real accretion_cfl_; + // Useful for debugging + const bool remove_accreted_mass_; + // Write triggering quantities (accretion rate or Bondi quantities) to file at // every timestep. Intended for testing quantities at every timestep, since // this file does not work across restarts, and since these quantities are From c5453a8b257014f7d4c1b5ca41513036292ca8b6 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 16 May 2022 10:43:48 -0400 Subject: [PATCH 17/95] Bug fixes in AGN Feedback --- src/pgen/cluster/agn_feedback.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 5470813d..df3c6b82 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -140,9 +140,10 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Note that new mass is injected to create the kinetic power, separate from the // existing gas const Real kinetic_jet_total_mass = mass_rate * kinetic_fraction_; - const Real kinetic_jet_density_rate = kinetic_jet_total_mass * kinetic_scaling_factor; + const Real kinetic_jet_density_rate = kinetic_jet_total_mass * kinetic_scaling_factor * beta_dt; const Real kinetic_jet_velocity = std::sqrt(2. * kinetic_power / kinetic_jet_total_mass); + const Real kinetic_jet_momentum_rate = kinetic_jet_density_rate * kinetic_jet_velocity; const Real kinetic_jet_radius = kinetic_jet_radius_; const Real kinetic_jet_height = kinetic_jet_height_; @@ -171,10 +172,12 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real r2 = x * x + y * y + z * z; // Determine if point is in sphere r<=thermal_radius if (r2 <= thermal_radius2) { + // Add density at constant velocity and temperature + //AddDensityToConsAtFixedVelTemp(thermal_density_rate, cons, prim, eos, k, j, i); // Apply heating cons(IEN, k, j, i) += thermal_feedback; // Add density at constant velocity - AddDensityToConsAtFixedVel(thermal_density_rate, cons, prim, eos, k, j, i); + //AddDensityToConsAtFixedVel(thermal_density_rate, cons, prim, eos, k, j, i); } } @@ -194,14 +197,14 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const int sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk - cons(IDN, k, j, i) += kinetic_jet_density_rate * beta_dt; // mass/volume - cons(IM1, k, j, i) += kinetic_jet_density_rate * sign_jet * jet_axis_x * - kinetic_jet_velocity * beta_dt; // velocity*mass/volume - cons(IM2, k, j, i) += kinetic_jet_density_rate * sign_jet * jet_axis_y * - kinetic_jet_velocity * beta_dt; // velocity*mass/volume - cons(IM3, k, j, i) += kinetic_jet_density_rate * sign_jet * jet_axis_z * - kinetic_jet_velocity * beta_dt; // velocity*mass/volume - cons(IEN, k, j, i) += kinetic_feedback; // energy/volume + cons(IDN, k, j, i) += kinetic_jet_density_rate; // mass/volume + cons(IM1, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_x; + // velocity*mass/volume + cons(IM2, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_y; + // velocity*mass/volume + cons(IM3, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_z; + // velocity*mass/volume + cons(IEN, k, j, i) += kinetic_feedback; // energy/volume } } }); From c3e2354677ce4ca77042ef90d0718a250a69b07f Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Wed, 1 Jun 2022 17:57:50 -0400 Subject: [PATCH 18/95] AddParam fixes for upstream changes --- src/pgen/cluster/agn_triggering.cpp | 10 +++++----- src/pgen/cluster/magnetic_tower.hpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 6e2ed339..6c3e1315 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -84,15 +84,15 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, } switch (triggering_mode_) { case AGNTriggeringMode::COLD_GAS: { - hydro_pkg->AddParam("agn_triggering_cold_mass", 0); + hydro_pkg->AddParam("agn_triggering_cold_mass", 0, true); break; } case AGNTriggeringMode::BOOSTED_BONDI: case AGNTriggeringMode::BOOTH_SCHAYE: { - hydro_pkg->AddParam("agn_triggering_total_mass", 0); - hydro_pkg->AddParam("agn_triggering_mass_weighted_density", 0); - hydro_pkg->AddParam("agn_triggering_mass_weighted_velocity", 0); - hydro_pkg->AddParam("agn_triggering_mass_weighted_cs", 0); + hydro_pkg->AddParam("agn_triggering_total_mass", 0, true); + hydro_pkg->AddParam("agn_triggering_mass_weighted_density", 0, true); + hydro_pkg->AddParam("agn_triggering_mass_weighted_velocity", 0, true); + hydro_pkg->AddParam("agn_triggering_mass_weighted_cs", 0, true); break; } case AGNTriggeringMode::NONE: { diff --git a/src/pgen/cluster/magnetic_tower.hpp b/src/pgen/cluster/magnetic_tower.hpp index 950661e7..edfe5345 100644 --- a/src/pgen/cluster/magnetic_tower.hpp +++ b/src/pgen/cluster/magnetic_tower.hpp @@ -138,8 +138,8 @@ class MagneticTower { fixed_mass_rate_(pin->GetOrAddReal(block, "fixed_mass_rate", 0)), l_mass_scale_(pin->GetOrAddReal(block, "l_mass_scale", 1)) { hydro_pkg->AddParam<>("magnetic_tower", *this); - hydro_pkg->AddParam("magnetic_tower_linear_contrib", 0.0); - hydro_pkg->AddParam("magnetic_tower_quadratic_contrib", 0.0); + hydro_pkg->AddParam("magnetic_tower_linear_contrib", 0.0, true); + hydro_pkg->AddParam("magnetic_tower_quadratic_contrib", 0.0, true); } // Add initial magnetic field to provided potential with a single meshblock From 631343b4c27da1d4213c24752a424505c6b8da9a Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 16 Jun 2022 21:13:52 -0400 Subject: [PATCH 19/95] Fixed bug in jet precession --- src/pgen/cluster/jet_coords.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp index e044f066..ac7073b2 100644 --- a/src/pgen/cluster/jet_coords.hpp +++ b/src/pgen/cluster/jet_coords.hpp @@ -106,7 +106,7 @@ class JetCoordsFactory { } JetCoords CreateJetCoords(const parthenon::Real time) const { - return JetCoords(theta_jet_axis_, phi0_jet_axis_ + time * phi0_jet_axis_); + return JetCoords(theta_jet_axis_, phi0_jet_axis_ + time * phi_dot_jet_axis_); } }; From b9d1f9824074ac83e93d48b52a7cc2012b4e8514 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 16 Jun 2022 21:10:51 -0700 Subject: [PATCH 20/95] Formatting --- src/hydro/hydro.cpp | 17 +++-- src/hydro/srcterms/tabular_cooling.cpp | 61 +++++++++--------- src/hydro/srcterms/tabular_cooling.hpp | 4 +- src/pgen/cluster.cpp | 4 +- src/pgen/cluster/agn_feedback.cpp | 35 ++++++----- src/pgen/cluster/agn_feedback.hpp | 3 +- src/pgen/cluster/agn_triggering.cpp | 63 ++++++++++--------- src/pgen/cluster/agn_triggering.hpp | 3 +- src/pgen/cluster/cluster_gravity.hpp | 3 +- .../hydrostatic_equilibrium_sphere.cpp | 7 ++- .../hydrostatic_equilibrium_sphere.hpp | 8 +-- src/pgen/cluster/magnetic_tower.cpp | 9 +-- src/pgen/cluster/magnetic_tower.hpp | 3 +- 13 files changed, 113 insertions(+), 107 deletions(-) diff --git a/src/hydro/hydro.cpp b/src/hydro/hydro.cpp index 53a1f426..dfc58f91 100644 --- a/src/hydro/hydro.cpp +++ b/src/hydro/hydro.cpp @@ -104,15 +104,14 @@ Real HydroHst(MeshData *md) { } Real abs_b = std::sqrt(SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + - SQR(cons(IB3, k, j, i))); - - lsum += ( abs_b != 0 ) ? - 0.5 * - (std::sqrt(SQR(coords.Dx(X1DIR, k, j, i)) + SQR(coords.Dx(X2DIR, k, j, i)) + - SQR(coords.Dx(X3DIR, k, j, i)))) * - std::abs(divb) / abs_b * - coords.Volume(k, j, i) - : 0 ;//Add zero when abs_b ==0 + SQR(cons(IB3, k, j, i))); + + lsum += (abs_b != 0) ? 0.5 * + (std::sqrt(SQR(coords.Dx(X1DIR, k, j, i)) + + SQR(coords.Dx(X2DIR, k, j, i)) + + SQR(coords.Dx(X3DIR, k, j, i)))) * + std::abs(divb) / abs_b * coords.Volume(k, j, i) + : 0; // Add zero when abs_b ==0 } }, sum); diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 2fe21168..91eaa081 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -206,8 +206,9 @@ TabularCooling::TabularCooling(ParameterInput *pin) { // Copy host_log_lambdas into device memory Kokkos::deep_copy(log_lambdas_, host_log_lambdas); - //Change T_floor_ to be the max of the hydro temperature floor and the cooling table floor - T_floor_ = std::max(T_floor_,pow(10,log_temp_start_)); + // Change T_floor_ to be the max of the hydro temperature floor and the cooling table + // floor + T_floor_ = std::max(T_floor_, pow(10, log_temp_start_)); } void TabularCooling::SrcTerm(MeshData *md, const Real dt) const { @@ -267,15 +268,15 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt const Real rho = cons(IDN, k, j, i); // TODO(pgrete) with potentially more EOS, a separate get_pressure (or similar) // function could be useful. - - //DEBUGGING(forrestglines) please remove + + // DEBUGGING(forrestglines) please remove Real total_e = cons(IEN, k, j, i); Real kinetic_e = 0.5 * (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + SQR(cons(IM3, k, j, i)) / rho); Real magnetic_e = 0.5 * (SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + SQR(cons(IB3, k, j, i))); Real pres = prim(IPR, k, j, i); - //END DEBUGGING + // END DEBUGGING Real internal_e = cons(IEN, k, j, i) - 0.5 * @@ -287,7 +288,7 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt SQR(cons(IB3, k, j, i))); } - //Switch to specific internal energy + // Switch to specific internal energy internal_e /= rho; const Real internal_e_initial = internal_e; @@ -309,15 +310,15 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt // temperature is already below floor. const Real dedt_initial = DeDt_wrapper(0.0, internal_e_initial, dedt_valid); - //DEBUGGING(forrestglines) please remove - //Testing if current cooling rate would drop the internal energy below the floor - //if ( internal_e_initial + dt*dedt_initial < 0 ) { - // printf("Fast Cooling\n"); - //} - //if ( dedt_initial == 0 ) { - // printf("Zero initial cooling\n"); - //} - //END DEBUGGING + // DEBUGGING(forrestglines) please remove + // Testing if current cooling rate would drop the internal energy below the floor + // if ( internal_e_initial + dt*dedt_initial < 0 ) { + // printf("Fast Cooling\n"); + // } + // if ( dedt_initial == 0 ) { + // printf("Zero initial cooling\n"); + // } + // END DEBUGGING if (dedt_initial == 0.0 || internal_e_initial < internal_e_floor) { return; @@ -328,7 +329,7 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt sub_dt = min_sub_dt; } - //subcycles iteration + // subcycles iteration unsigned int sub_iter = 0; // check for dedt != 0.0 required in case cooling floor it hit during subcycling while ((sub_t * (1 + KEpsilon_) < dt) && @@ -361,13 +362,15 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt if (!dedt_valid || internal_e_next_h <= internal_e_floor) { if (sub_dt == min_sub_dt) { - if( internal_e_floor < 0 ){ - PARTHENON_FAIL("FATAL ERROR in [TabularCooling::SubcyclingSplitSrcTerm]: " - "Minumum sub_dt leads to negative internal energy, no internal energy floor defined"); + if (internal_e_floor < 0) { + PARTHENON_FAIL( + "FATAL ERROR in [TabularCooling::SubcyclingSplitSrcTerm]: " + "Minumum sub_dt leads to negative internal energy, no internal " + "energy floor defined"); } - //Set to internal_e_floor + // Set to internal_e_floor internal_e_next_h = internal_e_floor; - //Cooling is finished: skip to end of cooling cycle with this subcycle + // Cooling is finished: skip to end of cooling cycle with this subcycle sub_dt = dt - sub_t; reattempt_sub = false; } else { @@ -437,12 +440,12 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt sub_iter++; } - //NOTE (forrestglines) It's unclear whether this section of code is necessary - //if( internal_e < internal_e_floor ){ - // internal_e = internal_e_floor; - //} + // NOTE (forrestglines) It's unclear whether this section of code is necessary + // if( internal_e < internal_e_floor ){ + // internal_e = internal_e_floor; + // } - //PARTHENON_REQUIRE(internal_e >= internal_energy_floor, "cooled below floor"); + // PARTHENON_REQUIRE(internal_e >= internal_energy_floor, "cooled below floor"); PARTHENON_REQUIRE(internal_e > 0, "cooled below zero internal energy"); // Remove the cooling from the specific total energy @@ -567,8 +570,8 @@ void TabularCooling::MixedIntSrcTerm(parthenon::MeshData *md, Real TabularCooling::EstimateTimeStep(MeshData *md) const { - //If the min_cooling_timestep_ is infinity, don't constrain the timestep - if( min_cooling_timestep_ == std::numeric_limits::infinity() ){ + // If the min_cooling_timestep_ is infinity, don't constrain the timestep + if (min_cooling_timestep_ == std::numeric_limits::infinity()) { return min_cooling_timestep_; } @@ -625,7 +628,7 @@ Real TabularCooling::EstimateTimeStep(MeshData *md) const { reducer_min); Real estimated_timestep = cooling_time_cfl_ * min_cooling_time; - if( estimated_timestep < min_cooling_timestep_){ + if (estimated_timestep < min_cooling_timestep_) { estimated_timestep = min_cooling_timestep_; } return estimated_timestep; diff --git a/src/hydro/srcterms/tabular_cooling.hpp b/src/hydro/srcterms/tabular_cooling.hpp index 1624b260..0a5aa308 100644 --- a/src/hydro/srcterms/tabular_cooling.hpp +++ b/src/hydro/srcterms/tabular_cooling.hpp @@ -151,8 +151,8 @@ class TabularCooling { // log_lambda = log_lambdas(0); // TODO(forrestglines):Currently, zero cooling is use for temperatures // below the table. This behavior could be generalized via templates - //return 0; - log_lambda = log_lambdas(0);//HACK - to get it to cool to floor + // return 0; + log_lambda = log_lambdas(0); // HACK - to get it to cool to floor } else if (log_temp > log_temp_final) { // Above table // Return de/dt diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index 3b3bcc51..51200c8e 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -82,8 +82,8 @@ Real ClusterEstimateTimestep(MeshData *md) { } //======================================================================================== -//! \fn void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) -//! \brief Init package data from parameter input +//! \fn void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor +//! *hydro_pkg) \brief Init package data from parameter input //======================================================================================== void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) { diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index f18a4deb..f8668770 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -28,7 +28,7 @@ namespace cluster { using namespace parthenon; AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, - parthenon::StateDescriptor* hydro_pkg) + parthenon::StateDescriptor *hydro_pkg) : fixed_power_(pin->GetOrAddReal("problem/cluster/agn_feedback", "fixed_power", 0.0)), efficiency_(pin->GetOrAddReal("problem/cluster/agn_feedback", "efficiency", 1e-3)), thermal_fraction_( @@ -45,18 +45,20 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_height", 0.02)), disabled_(pin->GetOrAddBoolean("problem/cluster/agn_feedback", "disabled", false)) { - //Add user history output variable for AGN power + // Add user history output variable for AGN power auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); - if( ! disabled_){ - //HACK (forrestglines): The operations should be a - //parthenon::UserHistoryOperation::no_reduce, which is as of writing - //unimplemented - hst_vars.emplace_back(parthenon::HistoryOutputVar(parthenon::UserHistoryOperation::max, + if (!disabled_) { + // HACK (forrestglines): The operations should be a + // parthenon::UserHistoryOperation::no_reduce, which is as of writing + // unimplemented + hst_vars.emplace_back(parthenon::HistoryOutputVar( + parthenon::UserHistoryOperation::max, [this](MeshData *md) { auto pmb = md->GetBlockData(0)->GetBlockPointer(); auto hydro_pkg = pmb->packages.Get("Hydro"); return this->GetFeedbackPower(hydro_pkg.get()); - }, "agn_feedback_power")); + }, + "agn_feedback_power")); } hydro_pkg->UpdateParam(parthenon::hist_param_key, hst_vars); @@ -72,7 +74,6 @@ parthenon::Real AGNFeedback::GetFeedbackPower(StateDescriptor *hydro_pkg) const fixed_power_ + accretion_rate * efficiency_ * pow(units.speed_of_light(), 2); return power; - } void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, @@ -140,7 +141,8 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Note that new mass is injected to create the kinetic power, separate from the // existing gas const Real kinetic_jet_total_mass = mass_rate * kinetic_fraction_; - const Real kinetic_jet_density_rate = kinetic_jet_total_mass * kinetic_scaling_factor * beta_dt; + const Real kinetic_jet_density_rate = + kinetic_jet_total_mass * kinetic_scaling_factor * beta_dt; const Real kinetic_jet_velocity = std::sqrt(2. * kinetic_power / kinetic_jet_total_mass); const Real kinetic_jet_momentum_rate = kinetic_jet_density_rate * kinetic_jet_velocity; @@ -173,11 +175,12 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Determine if point is in sphere r<=thermal_radius if (r2 <= thermal_radius2) { // Add density at constant velocity and temperature - //AddDensityToConsAtFixedVelTemp(thermal_density_rate, cons, prim, eos, k, j, i); + // AddDensityToConsAtFixedVelTemp(thermal_density_rate, cons, prim, eos, k, j, + // i); // Apply heating cons(IEN, k, j, i) += thermal_feedback; // Add density at constant velocity - //AddDensityToConsAtFixedVel(thermal_density_rate, cons, prim, eos, k, j, i); + // AddDensityToConsAtFixedVel(thermal_density_rate, cons, prim, eos, k, j, i); } } @@ -198,12 +201,12 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const int sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk cons(IDN, k, j, i) += kinetic_jet_density_rate; // mass/volume - cons(IM1, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_x; - // velocity*mass/volume + cons(IM1, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_x; + // velocity*mass/volume cons(IM2, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_y; - // velocity*mass/volume + // velocity*mass/volume cons(IM3, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_z; - // velocity*mass/volume + // velocity*mass/volume cons(IEN, k, j, i) += kinetic_feedback; // energy/volume } } diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index 137b560f..70cfeeac 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -38,8 +38,7 @@ class AGNFeedback { const bool disabled_; - AGNFeedback(parthenon::ParameterInput *pin, - parthenon::StateDescriptor *hydro_pkg); + AGNFeedback(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg); parthenon::Real GetFeedbackPower(parthenon::StateDescriptor *hydro_pkg) const; diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 6c3e1315..fae66528 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -51,7 +51,7 @@ AGNTriggeringMode ParseAGNTriggeringMode(const std::string &mode_str) { } AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, - parthenon::StateDescriptor* hydro_pkg, + parthenon::StateDescriptor *hydro_pkg, const std::string &block) : gamma_(pin->GetReal("hydro", "gamma")), triggering_mode_( @@ -100,10 +100,9 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, } } - - //Set up writing the triggering to file, used for debugging. Note that this is - //written every timestep, which is more frequently than history outputs. It - //is also not reduced across ranks and so is only valid without MPI + // Set up writing the triggering to file, used for debugging. Note that this is + // written every timestep, which is more frequently than history outputs. It + // is also not reduced across ranks and so is only valid without MPI if (write_to_file_ && parthenon::Globals::my_rank == 0) { // Clear the triggering_file std::ofstream triggering_file; @@ -111,8 +110,8 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, triggering_file.close(); } - //Set up UserHistoryOutput (Are all these triggering variables necessary? - //They might be for post-mortem debugging) + // Set up UserHistoryOutput (Are all these triggering variables necessary? + // They might be for post-mortem debugging) auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); std::vector history_var_names; switch (triggering_mode_) { @@ -133,29 +132,33 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, } } - for( auto var_name : history_var_names){ - //HACK (forrestglines): The operations should be a - //parthenon::UserHistoryOperation::no_reduce, which is as of writing - //unimplemented - hst_vars.emplace_back(parthenon::HistoryOutputVar(parthenon::UserHistoryOperation::max, - [this,var_name](MeshData *md) { + for (auto var_name : history_var_names) { + // HACK (forrestglines): The operations should be a + // parthenon::UserHistoryOperation::no_reduce, which is as of writing + // unimplemented + hst_vars.emplace_back(parthenon::HistoryOutputVar( + parthenon::UserHistoryOperation::max, + [this, var_name](MeshData *md) { auto pmb = md->GetBlockData(0)->GetBlockPointer(); auto hydro_pkg = pmb->packages.Get("Hydro"); return hydro_pkg->Param(var_name); - }, var_name)); + }, + var_name)); } - //Add accretion rate too - if ( triggering_mode_ != AGNTriggeringMode::NONE ){ - //HACK (forrestglines): The operations should be a - //parthenon::UserHistoryOperation::no_reduce, which is as of writing - //unimplemented - hst_vars.emplace_back(parthenon::HistoryOutputVar(parthenon::UserHistoryOperation::max, + // Add accretion rate too + if (triggering_mode_ != AGNTriggeringMode::NONE) { + // HACK (forrestglines): The operations should be a + // parthenon::UserHistoryOperation::no_reduce, which is as of writing + // unimplemented + hst_vars.emplace_back(parthenon::HistoryOutputVar( + parthenon::UserHistoryOperation::max, [this](MeshData *md) { auto pmb = md->GetBlockData(0)->GetBlockPointer(); auto hydro_pkg = pmb->packages.Get("Hydro"); return this->GetAccretionRate(hydro_pkg.get()); - }, "agn_accretion_rate")); + }, + "agn_accretion_rate")); } hydro_pkg->UpdateParam(parthenon::hist_param_key, hst_vars); @@ -212,13 +215,13 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, mean_molecular_mass_by_kb * prim(IPR, k, j, i) / prim(IDN, k, j, i); if (temp <= cold_temp_thresh) { - + const Real cell_cold_mass = prim(IDN, k, j, i) * coords.Volume(k, j, i); team_cold_mass += cell_cold_mass; const Real cell_delta_rho = -prim(IDN, k, j, i) / cold_t_acc * dt; - - if( remove_accreted_mass){ + + if (remove_accreted_mass) { AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); // Update the Primitives eos.ConsToPrim(cons, prim, k, j, i); @@ -558,7 +561,7 @@ AGNTriggeringFinalizeTriggering(parthenon::MeshData *md, } // Remove accreted gas if using a Bondi-like mode - if ( agn_triggering.remove_accreted_mass_){ + if (agn_triggering.remove_accreted_mass_) { switch (agn_triggering.triggering_mode_) { case AGNTriggeringMode::BOOSTED_BONDI: case AGNTriggeringMode::BOOTH_SCHAYE: { @@ -567,8 +570,8 @@ AGNTriggeringFinalizeTriggering(parthenon::MeshData *md, agn_triggering.RemoveBondiAccretedGas(md, tm.dt, hydro_pkg->Param("eos")); } else if (fluid == Fluid::glmmhd) { - agn_triggering.RemoveBondiAccretedGas(md, tm.dt, - hydro_pkg->Param("eos")); + agn_triggering.RemoveBondiAccretedGas( + md, tm.dt, hydro_pkg->Param("eos")); } else { PARTHENON_FAIL("AGNTriggeringFinalizeTriggering: Unknown EOS"); } @@ -581,11 +584,11 @@ AGNTriggeringFinalizeTriggering(parthenon::MeshData *md, } } - //DEBUGGING (forrestglines) - if( agn_triggering.GetAccretionRate(hydro_pkg.get()) > 0){ + // DEBUGGING (forrestglines) + if (agn_triggering.GetAccretionRate(hydro_pkg.get()) > 0) { printf("Accreting cold gas!\n"); } - //END DEBUGGING + // END DEBUGGING return TaskStatus::complete; } diff --git a/src/pgen/cluster/agn_triggering.hpp b/src/pgen/cluster/agn_triggering.hpp index ca704bba..2e0472ab 100644 --- a/src/pgen/cluster/agn_triggering.hpp +++ b/src/pgen/cluster/agn_triggering.hpp @@ -65,8 +65,7 @@ class AGNTriggering { const bool write_to_file_; const std::string triggering_filename_; - AGNTriggering(parthenon::ParameterInput *pin, - parthenon::StateDescriptor* hydro_pkg, + AGNTriggering(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg, const std::string &block = "problem/cluster/agn_triggering"); // Compute Cold gas accretion rate within the accretion radius for cold gas triggering diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 0b900da2..5af240b5 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -89,8 +89,7 @@ class ClusterGravity { } public: - ClusterGravity(parthenon::ParameterInput *pin, - parthenon::StateDescriptor *hydro_pkg) { + ClusterGravity(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) { Units units(pin); // Determine which element to include diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp index 8b1758b8..65b1fc8c 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp @@ -35,9 +35,10 @@ using namespace parthenon; ************************************************************/ template HydrostaticEquilibriumSphere:: - HydrostaticEquilibriumSphere( - ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg, - GravitationalField gravitational_field, EntropyProfile entropy_profile) + HydrostaticEquilibriumSphere(ParameterInput *pin, + parthenon::StateDescriptor *hydro_pkg, + GravitationalField gravitational_field, + EntropyProfile entropy_profile) : gravitational_field_(gravitational_field), entropy_profile_(entropy_profile) { Units units(pin); diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp index 7f29e07b..d1b809bf 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp @@ -134,10 +134,10 @@ class HydrostaticEquilibriumSphere { static constexpr parthenon::Real kRTol = 1e-15; public: - HydrostaticEquilibriumSphere( - parthenon::ParameterInput *pin, - parthenon::StateDescriptor *hydro_pkg, - GravitationalField gravitational_field, EntropyProfile entropy_profile); + HydrostaticEquilibriumSphere(parthenon::ParameterInput *pin, + parthenon::StateDescriptor *hydro_pkg, + GravitationalField gravitational_field, + EntropyProfile entropy_profile); template PRhoProfile diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index ccc9a9ca..3731336a 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -66,10 +66,11 @@ void MagneticTower::AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mas // 2. Compute the potential (12 needed in all) in the same kernel, // constructing the derivative without storing the potential (more // arithmetically intensive, maybe faster) - ParArray5D A("magnetic_tower_A", 3, cons_pack.GetDim(5), - md->GetBlockData(0)->GetBlockPointer()->cellbounds.ncellsk(IndexDomain::entire), - md->GetBlockData(0)->GetBlockPointer()->cellbounds.ncellsj(IndexDomain::entire), - md->GetBlockData(0)->GetBlockPointer()->cellbounds.ncellsi(IndexDomain::entire)); + ParArray5D A( + "magnetic_tower_A", 3, cons_pack.GetDim(5), + md->GetBlockData(0)->GetBlockPointer()->cellbounds.ncellsk(IndexDomain::entire), + md->GetBlockData(0)->GetBlockPointer()->cellbounds.ncellsj(IndexDomain::entire), + md->GetBlockData(0)->GetBlockPointer()->cellbounds.ncellsi(IndexDomain::entire)); IndexRange a_ib = ib; a_ib.s -= 1; a_ib.e += 1; diff --git a/src/pgen/cluster/magnetic_tower.hpp b/src/pgen/cluster/magnetic_tower.hpp index edfe5345..bea30861 100644 --- a/src/pgen/cluster/magnetic_tower.hpp +++ b/src/pgen/cluster/magnetic_tower.hpp @@ -128,8 +128,7 @@ class MagneticTower { const parthenon::Real fixed_mass_rate_; const parthenon::Real l_mass_scale_; - MagneticTower(parthenon::ParameterInput *pin, - parthenon::StateDescriptor *hydro_pkg, + MagneticTower(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg, const std::string &block = "problem/cluster/magnetic_tower") : alpha_(pin->GetOrAddReal(block, "alpha", 0)), l_scale_(pin->GetOrAddReal(block, "l_scale", 0)), From 8f4b52cb1e913518924ef0f6de06b2ab3cd66983 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 19 Jul 2022 19:03:11 -0400 Subject: [PATCH 21/95] Fixed AGN Triggering Mode in history dumps --- src/pgen/cluster/agn_triggering.cpp | 7 ++++--- src/pgen/cluster/agn_triggering.hpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index fae66528..cbf810e7 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -138,7 +138,7 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, // unimplemented hst_vars.emplace_back(parthenon::HistoryOutputVar( parthenon::UserHistoryOperation::max, - [this, var_name](MeshData *md) { + [var_name](MeshData *md) { auto pmb = md->GetBlockData(0)->GetBlockPointer(); auto hydro_pkg = pmb->packages.Get("Hydro"); return hydro_pkg->Param(var_name); @@ -153,10 +153,11 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, // unimplemented hst_vars.emplace_back(parthenon::HistoryOutputVar( parthenon::UserHistoryOperation::max, - [this](MeshData *md) { + [](MeshData *md) { auto pmb = md->GetBlockData(0)->GetBlockPointer(); auto hydro_pkg = pmb->packages.Get("Hydro"); - return this->GetAccretionRate(hydro_pkg.get()); + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + return agn_triggering.GetAccretionRate(hydro_pkg.get()); }, "agn_accretion_rate")); } diff --git a/src/pgen/cluster/agn_triggering.hpp b/src/pgen/cluster/agn_triggering.hpp index 2e0472ab..decdf4f9 100644 --- a/src/pgen/cluster/agn_triggering.hpp +++ b/src/pgen/cluster/agn_triggering.hpp @@ -23,7 +23,7 @@ namespace cluster { -enum class AGNTriggeringMode { COLD_GAS, BOOSTED_BONDI, BOOTH_SCHAYE, NONE }; +enum class AGNTriggeringMode { NONE, COLD_GAS, BOOSTED_BONDI, BOOTH_SCHAYE }; AGNTriggeringMode ParseAGNTriggeringMode(const std::string &mode_str); From 8b789409ffe37d60164515e0300c9b2afa5b3709 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 19 Jul 2022 19:09:19 -0400 Subject: [PATCH 22/95] Removed DEBUGGING statements --- src/hydro/srcterms/tabular_cooling.cpp | 19 ------------------- src/pgen/cluster/agn_triggering.cpp | 6 ------ 2 files changed, 25 deletions(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 91eaa081..72696266 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -269,15 +269,6 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt // TODO(pgrete) with potentially more EOS, a separate get_pressure (or similar) // function could be useful. - // DEBUGGING(forrestglines) please remove - Real total_e = cons(IEN, k, j, i); - Real kinetic_e = 0.5 * (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + - SQR(cons(IM3, k, j, i)) / rho); - Real magnetic_e = 0.5 * (SQR(cons(IB1, k, j, i)) + SQR(cons(IB2, k, j, i)) + - SQR(cons(IB3, k, j, i))); - Real pres = prim(IPR, k, j, i); - // END DEBUGGING - Real internal_e = cons(IEN, k, j, i) - 0.5 * (SQR(cons(IM1, k, j, i)) + SQR(cons(IM2, k, j, i)) + @@ -310,16 +301,6 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt // temperature is already below floor. const Real dedt_initial = DeDt_wrapper(0.0, internal_e_initial, dedt_valid); - // DEBUGGING(forrestglines) please remove - // Testing if current cooling rate would drop the internal energy below the floor - // if ( internal_e_initial + dt*dedt_initial < 0 ) { - // printf("Fast Cooling\n"); - // } - // if ( dedt_initial == 0 ) { - // printf("Zero initial cooling\n"); - // } - // END DEBUGGING - if (dedt_initial == 0.0 || internal_e_initial < internal_e_floor) { return; } diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index cbf810e7..be82c4e4 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -585,12 +585,6 @@ AGNTriggeringFinalizeTriggering(parthenon::MeshData *md, } } - // DEBUGGING (forrestglines) - if (agn_triggering.GetAccretionRate(hydro_pkg.get()) > 0) { - printf("Accreting cold gas!\n"); - } - // END DEBUGGING - return TaskStatus::complete; } From 0398a6bfac9fc5b18fb5c326093e1393e2143101 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 10 Oct 2022 16:08:43 -0500 Subject: [PATCH 23/95] Fixed unyt unit error in testing --- .../cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 61268331..12182535 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -258,9 +258,9 @@ def kinetic_feedback(Z,Y,X,time): R = unyt.unyt_array(R,"code_length") H = unyt.unyt_array(H,"code_length") - sign_jet = np.piecewise(H,[H <=0, H > 0],[-1,1]) + sign_jet = np.piecewise(H,[H <=0, H > 0],[-1,1]).v inside_jet = (np.piecewise(R,[ R <= self.agn_jet_radius,],[1,0]) \ - *np.piecewise(H,[ np.abs(H) <= self.agn_jet_height,],[1,0])) + *np.piecewise(H,[ np.abs(H) <= self.agn_jet_height,],[1,0])).v drho = inside_jet*agn_kinetic_fraction*time*jet_density dMx = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[0] @@ -283,7 +283,6 @@ def thermal_feedback(Z,Y,X,time): def agn_feedback(Z,Y,X,dt): - drho_k,dMx_k,dMy_k,dMz_k,dE_k = kinetic_feedback(Z,Y,X,dt) drho_t,dE_t = thermal_feedback(Z,Y,X,dt) From 5bfffd0718d47792c8d6735a039868a4edf6c3fb Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 11 Oct 2022 16:12:40 -0500 Subject: [PATCH 24/95] Hack for tabular_cooling on DeltaGPUs --- src/hydro/srcterms/tabular_cooling.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 72696266..4a58a3c9 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -225,8 +225,9 @@ void TabularCooling::SrcTerm(MeshData *md, const Real dt) const { } template -void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt, +void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt_, const RKStepper rk_stepper) const { + const Real dt = dt_;//HACK(forrestglines) to work on DeltaGPU auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); const bool mhd_enabled = hydro_pkg->Param("fluid") == Fluid::glmmhd; From 82276e6c060e0df7c8e0e7c50fda54d7c849f175 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 14 Nov 2022 19:38:39 -0700 Subject: [PATCH 25/95] Fixed MPI Reduction error for magnetized AGN jets --- src/hydro/hydro_driver.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hydro/hydro_driver.cpp b/src/hydro/hydro_driver.cpp index 8c9e1b66..0bcb8d1a 100644 --- a/src/hydro/hydro_driver.cpp +++ b/src/hydro/hydro_driver.cpp @@ -126,7 +126,6 @@ TaskCollection HydroDriver::MakeTaskCollection(BlockList_t &blocks, int stage) { } } - TaskRegion &async_region_1 = tc.AddRegion(num_task_lists_executed_independently); for (int i = 0; i < blocks.size(); i++) { auto &pmb = blocks[i]; // Using "base" as u0, which already exists (and returned by using plain Get()) @@ -220,7 +219,7 @@ TaskCollection HydroDriver::MakeTaskCollection(BlockList_t &blocks, int stage) { hydro_pkg->Param("magnetic_tower_linear_contrib"), hydro_pkg->Param("magnetic_tower_quadratic_contrib")}; PARTHENON_MPI_CHECK(MPI_Allreduce(MPI_IN_PLACE, magnetic_tower_contribs, 2, - MPI_PARTHENON_REAL, MPI_MAX, MPI_COMM_WORLD)); + MPI_PARTHENON_REAL, MPI_SUM, MPI_COMM_WORLD)); hydro_pkg->UpdateParam("magnetic_tower_linear_contrib", magnetic_tower_contribs[0]); hydro_pkg->UpdateParam("magnetic_tower_quadratic_contrib", From 17d58b6ca57116a7b0d4844d64339cd6f89b4a9b Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 14 Nov 2022 22:51:04 -0700 Subject: [PATCH 26/95] Consolidated EOS ConsToPrim Functions --- src/eos/adiabatic_glmmhd.cpp | 76 ++------------------------ src/eos/adiabatic_glmmhd.hpp | 84 +++++++++++++---------------- src/eos/adiabatic_hydro.cpp | 60 ++------------------- src/eos/adiabatic_hydro.hpp | 68 +++++++++++------------ src/eos/eos.hpp | 2 +- src/pgen/cluster/agn_triggering.cpp | 8 ++- 6 files changed, 86 insertions(+), 212 deletions(-) diff --git a/src/eos/adiabatic_glmmhd.cpp b/src/eos/adiabatic_glmmhd.cpp index 9059ba97..bb2643be 100644 --- a/src/eos/adiabatic_glmmhd.cpp +++ b/src/eos/adiabatic_glmmhd.cpp @@ -37,88 +37,20 @@ void AdiabaticGLMMHDEOS::ConservedToPrimitive(MeshData *md) const { auto jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::entire); auto kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::entire); - auto gam = GetGamma(); - auto gm1 = gam - 1.0; - auto density_floor_ = GetDensityFloor(); - auto pressure_floor_ = GetPressureFloor(); - auto e_floor_ = GetInternalEFloor(); - auto pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); const auto nhydro = pkg->Param("nhydro"); const auto nscalars = pkg->Param("nscalars"); + auto this_on_device = (*this); + parthenon::par_for( DEFAULT_LOOP_PATTERN, "ConservedToPrimitive", parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, KOKKOS_LAMBDA(const int b, const int k, const int j, const int i) { const auto &cons = cons_pack(b); auto &prim = prim_pack(b); - // auto &nu = entropy_pack(b); - - Real &u_d = cons(IDN, k, j, i); - Real &u_m1 = cons(IM1, k, j, i); - Real &u_m2 = cons(IM2, k, j, i); - Real &u_m3 = cons(IM3, k, j, i); - Real &u_e = cons(IEN, k, j, i); - Real &u_b1 = cons(IB1, k, j, i); - Real &u_b2 = cons(IB2, k, j, i); - Real &u_b3 = cons(IB3, k, j, i); - Real &u_psi = cons(IPS, k, j, i); - - Real &w_d = prim(IDN, k, j, i); - Real &w_vx = prim(IV1, k, j, i); - Real &w_vy = prim(IV2, k, j, i); - Real &w_vz = prim(IV3, k, j, i); - Real &w_p = prim(IPR, k, j, i); - Real &w_Bx = prim(IB1, k, j, i); - Real &w_By = prim(IB2, k, j, i); - Real &w_Bz = prim(IB3, k, j, i); - Real &w_psi = prim(IPS, k, j, i); - - // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) - // and the code will fail if a negative density is encountered. - PARTHENON_REQUIRE(u_d > 0.0 || density_floor_ > 0.0, - "Got negative density. Consider enabling first-order flux " - "correction or setting a reasonble density floor."); - // apply density floor, without changing momentum or energy - u_d = (u_d > density_floor_) ? u_d : density_floor_; - w_d = u_d; - - Real di = 1.0 / u_d; - w_vx = u_m1 * di; - w_vy = u_m2 * di; - w_vz = u_m3 * di; - - w_Bx = u_b1; - w_By = u_b2; - w_Bz = u_b3; - w_psi = u_psi; - - Real e_k = 0.5 * di * (SQR(u_m1) + SQR(u_m2) + SQR(u_m3)); - Real e_B = 0.5 * (SQR(u_b1) + SQR(u_b2) + SQR(u_b3)); - w_p = gm1 * (u_e - e_k - e_B); - // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) - // and the code will fail if a negative pressure is encountered. - PARTHENON_REQUIRE( - w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, - "Got negative pressure. Consider enabling first-order flux " - "correction or setting a reasonble pressure or temperature floor."); - // Temperature floor (if present) takes precedence over pressure floor - if (e_floor_ > 0.0) { - // apply temperature floor, correct total energy - const Real eff_pressure_floor = gm1 * u_d * e_floor_; - u_e = (w_p > eff_pressure_floor) ? u_e : ((u_d * e_floor_) + e_k + e_B); - w_p = (w_p > eff_pressure_floor) ? w_p : eff_pressure_floor; - } else { - // apply pressure floor, correct total energy - u_e = (w_p > pressure_floor_) ? u_e : ((pressure_floor_ / gm1) + e_k + e_B); - w_p = (w_p > pressure_floor_) ? w_p : pressure_floor_; - } + return this_on_device.ConsToPrim(cons,prim,nhydro,nscalars,k,j,i); + }); - // Convert passive scalars - for (auto n = nhydro; n < nhydro + nscalars; ++n) { - prim(n, k, j, i) = cons(n, k, j, i) * di; - } - }); } diff --git a/src/eos/adiabatic_glmmhd.hpp b/src/eos/adiabatic_glmmhd.hpp index a0d5d0d4..59e4f7e9 100644 --- a/src/eos/adiabatic_glmmhd.hpp +++ b/src/eos/adiabatic_glmmhd.hpp @@ -57,8 +57,14 @@ class AdiabaticGLMMHDEOS : public EquationOfState { // int& j, const int& i) \brief Fills an array of primitives given an array of // conserveds, potentially updating the conserved with floors template - KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &k, - const int &j, const int &i) const { + KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &nhydro, const int &nscalars, const int &k, + const int &j, const int &i) const { + auto gam = GetGamma(); + auto gm1 = gam - 1.0; + auto density_floor_ = GetDensityFloor(); + auto pressure_floor_ = GetPressureFloor(); + auto e_floor_ = GetInternalEFloor(); + Real &u_d = cons(IDN, k, j, i); Real &u_m1 = cons(IM1, k, j, i); Real &u_m2 = cons(IM2, k, j, i); @@ -79,6 +85,11 @@ class AdiabaticGLMMHDEOS : public EquationOfState { Real &w_Bz = prim(IB3, k, j, i); Real &w_psi = prim(IPS, k, j, i); + // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) + // and the code will fail if a negative density is encountered. + PARTHENON_REQUIRE(u_d > 0.0 || density_floor_ > 0.0, + "Got negative density. Consider enabling first-order flux " + "correction or setting a reasonble density floor."); // apply density floor, without changing momentum or energy u_d = (u_d > density_floor_) ? u_d : density_floor_; w_d = u_d; @@ -95,57 +106,34 @@ class AdiabaticGLMMHDEOS : public EquationOfState { Real e_k = 0.5 * di * (SQR(u_m1) + SQR(u_m2) + SQR(u_m3)); Real e_B = 0.5 * (SQR(u_b1) + SQR(u_b2) + SQR(u_b3)); - Real gm1 = gamma_ - 1.0; w_p = gm1 * (u_e - e_k - e_B); - // apply pressure floor, correct total energy - u_e = (w_p > pressure_floor_) ? u_e : ((pressure_floor_ / gm1) + e_k + e_B); - w_p = (w_p > pressure_floor_) ? w_p : pressure_floor_; - } - - //---------------------------------------------------------------------------------------- - // \!fn Real EquationOfState::PrimToCons(View4D prim, View4D cons, const int& k, const - // int& j, const int& i) \brief Fills an array of conserveds given an array of - // primitives, - template - KOKKOS_INLINE_FUNCTION void PrimToCons(View4D prim, View4D cons, const int &k, - const int &j, const int &i) const { - Real &u_d = cons(IDN, k, j, i); - Real &u_m1 = cons(IM1, k, j, i); - Real &u_m2 = cons(IM2, k, j, i); - Real &u_m3 = cons(IM3, k, j, i); - Real &u_e = cons(IEN, k, j, i); - Real &u_b1 = cons(IB1, k, j, i); - Real &u_b2 = cons(IB2, k, j, i); - Real &u_b3 = cons(IB3, k, j, i); - Real &u_psi = cons(IPS, k, j, i); - - const Real &w_d = prim(IDN, k, j, i); - const Real &w_vx = prim(IV1, k, j, i); - const Real &w_vy = prim(IV2, k, j, i); - const Real &w_vz = prim(IV3, k, j, i); - const Real &w_p = prim(IPR, k, j, i); - const Real &w_Bx = prim(IB1, k, j, i); - const Real &w_By = prim(IB2, k, j, i); - const Real &w_Bz = prim(IB3, k, j, i); - const Real &w_psi = prim(IPS, k, j, i); - - const Real igm1 = 1 / (gamma_ - 1.0); - u_d = w_d; - u_m1 = w_vx * w_d; - u_m2 = w_vy * w_d; - u_m3 = w_vz * w_d; - u_e = w_p * igm1 + 0.5 * w_d * (SQR(w_vx) + SQR(w_vy) + SQR(w_vz)) + - 0.5 * (SQR(w_Bx) + SQR(w_By) + SQR(w_Bz)); - - u_b1 = w_Bx; - u_b2 = w_By; - u_b3 = w_Bz; - u_psi = w_psi; + // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) + // and the code will fail if a negative pressure is encountered. + PARTHENON_REQUIRE( + w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, + "Got negative pressure. Consider enabling first-order flux " + "correction or setting a reasonble pressure or temperature floor."); + // Temperature floor (if present) takes precedence over pressure floor + if (e_floor_ > 0.0) { + // apply temperature floor, correct total energy + const Real eff_pressure_floor = gm1 * u_d * e_floor_; + u_e = (w_p > eff_pressure_floor) ? u_e : ((u_d * e_floor_) + e_k + e_B); + w_p = (w_p > eff_pressure_floor) ? w_p : eff_pressure_floor; + } else { + // apply pressure floor, correct total energy + u_e = (w_p > pressure_floor_) ? u_e : ((pressure_floor_ / gm1) + e_k + e_B); + w_p = (w_p > pressure_floor_) ? w_p : pressure_floor_; + } + + // Convert passive scalars + for (auto n = nhydro; n < nhydro + nscalars; ++n) { + prim(n, k, j, i) = cons(n, k, j, i) * di; + } } private: Real gamma_; // ratio of specific heats }; -#endif // EOS_ADIABATIC_GLMMHD_HPP_ \ No newline at end of file +#endif // EOS_ADIABATIC_GLMMHD_HPP_ diff --git a/src/eos/adiabatic_hydro.cpp b/src/eos/adiabatic_hydro.cpp index fe73973e..0f0fefaa 100644 --- a/src/eos/adiabatic_hydro.cpp +++ b/src/eos/adiabatic_hydro.cpp @@ -36,72 +36,20 @@ void AdiabaticHydroEOS::ConservedToPrimitive(MeshData *md) const { auto ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::entire); auto jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::entire); auto kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::entire); - Real gm1 = GetGamma() - 1.0; - auto density_floor_ = GetDensityFloor(); - auto pressure_floor_ = GetPressureFloor(); - auto e_floor_ = GetInternalEFloor(); auto pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); const auto nhydro = pkg->Param("nhydro"); const auto nscalars = pkg->Param("nscalars"); + auto this_on_device = (*this); + parthenon::par_for( DEFAULT_LOOP_PATTERN, "ConservedToPrimitive", parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, KOKKOS_LAMBDA(const int b, const int k, const int j, const int i) { const auto &cons = cons_pack(b); auto &prim = prim_pack(b); - Real &u_d = cons(IDN, k, j, i); - Real &u_m1 = cons(IM1, k, j, i); - Real &u_m2 = cons(IM2, k, j, i); - Real &u_m3 = cons(IM3, k, j, i); - Real &u_e = cons(IEN, k, j, i); - - Real &w_d = prim(IDN, k, j, i); - Real &w_vx = prim(IV1, k, j, i); - Real &w_vy = prim(IV2, k, j, i); - Real &w_vz = prim(IV3, k, j, i); - Real &w_p = prim(IPR, k, j, i); - - // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) - // and the code will fail if a negative density is encountered. - PARTHENON_REQUIRE(u_d > 0.0 || density_floor_ > 0.0, - "Got negative density. Consider enabling first-order flux " - "correction or setting a reasonble density floor."); - // apply density floor, without changing momentum or energy - u_d = (u_d > density_floor_) ? u_d : density_floor_; - w_d = u_d; - - Real di = 1.0 / u_d; - w_vx = u_m1 * di; - w_vy = u_m2 * di; - w_vz = u_m3 * di; - - Real e_k = 0.5 * di * (SQR(u_m1) + SQR(u_m2) + SQR(u_m3)); - w_p = gm1 * (u_e - e_k); - - // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) - // and the code will fail if a negative pressure is encountered. - PARTHENON_REQUIRE( - w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, - "Got negative pressure. Consider enabling first-order flux " - "correction or setting a reasonble pressure or temperature floor."); - - // Temperature floor (if present) takes precedence over pressure floor - if (e_floor_ > 0.0) { - // apply temperature floor, correct total energy - const Real eff_pressure_floor = gm1 * u_d * e_floor_; - u_e = (w_p > eff_pressure_floor) ? u_e : ((u_d * e_floor_) + e_k); - w_p = (w_p > eff_pressure_floor) ? w_p : eff_pressure_floor; - } else { - // apply pressure floor, correct total energy - u_e = (w_p > pressure_floor_) ? u_e : ((pressure_floor_ / gm1) + e_k); - w_p = (w_p > pressure_floor_) ? w_p : pressure_floor_; - } - - // Convert passive scalars - for (auto n = nhydro; n < nhydro + nscalars; ++n) { - prim(n, k, j, i) = cons(n, k, j, i) * di; - } + + return this_on_device.ConsToPrim(cons,prim,nhydro,nscalars,k,j,i); }); } diff --git a/src/eos/adiabatic_hydro.hpp b/src/eos/adiabatic_hydro.hpp index f4189738..765640cf 100644 --- a/src/eos/adiabatic_hydro.hpp +++ b/src/eos/adiabatic_hydro.hpp @@ -47,8 +47,13 @@ class AdiabaticHydroEOS : public EquationOfState { // int& j, const int& i) \brief Fills an array of primitives given an array of // conserveds, potentially updating the conserved with floors template - KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &k, + KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &nhydro, const int &nscalars, const int &k, const int &j, const int &i) const { + Real gm1 = GetGamma() - 1.0; + auto density_floor_ = GetDensityFloor(); + auto pressure_floor_ = GetPressureFloor(); + auto e_floor_ = GetInternalEFloor(); + Real &u_d = cons(IDN, k, j, i); Real &u_m1 = cons(IM1, k, j, i); Real &u_m2 = cons(IM2, k, j, i); @@ -61,6 +66,11 @@ class AdiabaticHydroEOS : public EquationOfState { Real &w_vz = prim(IV3, k, j, i); Real &w_p = prim(IPR, k, j, i); + // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) + // and the code will fail if a negative density is encountered. + PARTHENON_REQUIRE(u_d > 0.0 || density_floor_ > 0.0, + "Got negative density. Consider enabling first-order flux " + "correction or setting a reasonble density floor."); // apply density floor, without changing momentum or energy u_d = (u_d > density_floor_) ? u_d : density_floor_; w_d = u_d; @@ -71,43 +81,35 @@ class AdiabaticHydroEOS : public EquationOfState { w_vz = u_m3 * di; Real e_k = 0.5 * di * (SQR(u_m1) + SQR(u_m2) + SQR(u_m3)); - Real gm1 = gamma_ - 1.0; w_p = gm1 * (u_e - e_k); - // apply pressure floor, correct total energy - u_e = (w_p > pressure_floor_) ? u_e : ((pressure_floor_ / gm1) + e_k); - w_p = (w_p > pressure_floor_) ? w_p : pressure_floor_; - } - - //---------------------------------------------------------------------------------------- - // \!fn Real EquationOfState::PrimToCons(View4D prim, View4D cons, const int& k, const - // int& j, const int& i) \brief Fills an array of conserveds given an array of - // primitives, - template - KOKKOS_INLINE_FUNCTION void PrimToCons(View4D prim, View4D cons, const int &k, - const int &j, const int &i) const { - Real &u_d = cons(IDN, k, j, i); - Real &u_m1 = cons(IM1, k, j, i); - Real &u_m2 = cons(IM2, k, j, i); - Real &u_m3 = cons(IM3, k, j, i); - Real &u_e = cons(IEN, k, j, i); - - const Real &w_d = prim(IDN, k, j, i); - const Real &w_vx = prim(IV1, k, j, i); - const Real &w_vy = prim(IV2, k, j, i); - const Real &w_vz = prim(IV3, k, j, i); - const Real &w_p = prim(IPR, k, j, i); - - const Real igm1 = 1 / (gamma_ - 1.0); - u_d = w_d; - u_m1 = w_vx * w_d; - u_m2 = w_vy * w_d; - u_m3 = w_vz * w_d; - u_e = w_p * igm1 + 0.5 * w_d * (SQR(w_vx) + SQR(w_vy) + SQR(w_vz)); + // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) + // and the code will fail if a negative pressure is encountered. + PARTHENON_REQUIRE( + w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, + "Got negative pressure. Consider enabling first-order flux " + "correction or setting a reasonble pressure or temperature floor."); + + // Temperature floor (if present) takes precedence over pressure floor + if (e_floor_ > 0.0) { + // apply temperature floor, correct total energy + const Real eff_pressure_floor = gm1 * u_d * e_floor_; + u_e = (w_p > eff_pressure_floor) ? u_e : ((u_d * e_floor_) + e_k); + w_p = (w_p > eff_pressure_floor) ? w_p : eff_pressure_floor; + } else { + // apply pressure floor, correct total energy + u_e = (w_p > pressure_floor_) ? u_e : ((pressure_floor_ / gm1) + e_k); + w_p = (w_p > pressure_floor_) ? w_p : pressure_floor_; + } + + // Convert passive scalars + for (auto n = nhydro; n < nhydro + nscalars; ++n) { + prim(n, k, j, i) = cons(n, k, j, i) * di; + } } private: Real gamma_; // ratio of specific heats }; -#endif // EOS_ADIABATIC_HYDRO_HPP_ \ No newline at end of file +#endif // EOS_ADIABATIC_HYDRO_HPP_ diff --git a/src/eos/eos.hpp b/src/eos/eos.hpp index 78969119..abba4950 100644 --- a/src/eos/eos.hpp +++ b/src/eos/eos.hpp @@ -46,7 +46,7 @@ class EquationOfState { KOKKOS_INLINE_FUNCTION Real GetInternalEFloor() const { return internal_e_floor_; } - protected: + private: Real pressure_floor_, density_floor_, internal_e_floor_; }; diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index be82c4e4..74cac2ee 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -184,6 +184,8 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::interior); IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); + const auto nhydro = hydro_pkg->Param("nhydro"); + const auto nscalars = hydro_pkg->Param("nscalars"); const Real accretion_radius2 = pow(accretion_radius_, 2); @@ -225,7 +227,7 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, if (remove_accreted_mass) { AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); // Update the Primitives - eos.ConsToPrim(cons, prim, k, j, i); + eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); } } } @@ -321,6 +323,8 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::interior); IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); + const auto nhydro = hydro_pkg->Param("nhydro"); + const auto nscalars = hydro_pkg->Param("nscalars"); const Real accretion_radius2 = pow(accretion_radius_, 2); @@ -347,7 +351,7 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); // Update the Primitives - eos.ConsToPrim(cons, prim, k, j, i); + eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); } }); } From 64411efe16ac9f91343e164f063dc87b87f72d5d Mon Sep 17 00:00:00 2001 From: par-hermes Date: Tue, 15 Nov 2022 15:58:14 +0000 Subject: [PATCH 27/95] cpp-py-formatter --- src/eos/adiabatic_glmmhd.cpp | 5 +- src/eos/adiabatic_glmmhd.hpp | 16 +- src/eos/adiabatic_hydro.cpp | 4 +- src/eos/adiabatic_hydro.hpp | 16 +- .../cluster_agn_triggering.py | 496 +++++++----- .../test_suites/cluster_hse/cluster_hse.py | 466 +++++++----- .../cluster_hydro_agn_feedback.py | 484 +++++++----- .../cluster_magnetic_tower.py | 719 +++++++++++------- .../cluster_tabular_cooling.py | 518 +++++++------ 9 files changed, 1632 insertions(+), 1092 deletions(-) diff --git a/src/eos/adiabatic_glmmhd.cpp b/src/eos/adiabatic_glmmhd.cpp index bb2643be..999f9203 100644 --- a/src/eos/adiabatic_glmmhd.cpp +++ b/src/eos/adiabatic_glmmhd.cpp @@ -50,7 +50,6 @@ void AdiabaticGLMMHDEOS::ConservedToPrimitive(MeshData *md) const { const auto &cons = cons_pack(b); auto &prim = prim_pack(b); - return this_on_device.ConsToPrim(cons,prim,nhydro,nscalars,k,j,i); - }); - + return this_on_device.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); + }); } diff --git a/src/eos/adiabatic_glmmhd.hpp b/src/eos/adiabatic_glmmhd.hpp index 59e4f7e9..70acb0e6 100644 --- a/src/eos/adiabatic_glmmhd.hpp +++ b/src/eos/adiabatic_glmmhd.hpp @@ -57,8 +57,9 @@ class AdiabaticGLMMHDEOS : public EquationOfState { // int& j, const int& i) \brief Fills an array of primitives given an array of // conserveds, potentially updating the conserved with floors template - KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &nhydro, const int &nscalars, const int &k, - const int &j, const int &i) const { + KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &nhydro, + const int &nscalars, const int &k, const int &j, + const int &i) const { auto gam = GetGamma(); auto gm1 = gam - 1.0; auto density_floor_ = GetDensityFloor(); @@ -88,8 +89,8 @@ class AdiabaticGLMMHDEOS : public EquationOfState { // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) // and the code will fail if a negative density is encountered. PARTHENON_REQUIRE(u_d > 0.0 || density_floor_ > 0.0, - "Got negative density. Consider enabling first-order flux " - "correction or setting a reasonble density floor."); + "Got negative density. Consider enabling first-order flux " + "correction or setting a reasonble density floor."); // apply density floor, without changing momentum or energy u_d = (u_d > density_floor_) ? u_d : density_floor_; w_d = u_d; @@ -110,10 +111,9 @@ class AdiabaticGLMMHDEOS : public EquationOfState { // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) // and the code will fail if a negative pressure is encountered. - PARTHENON_REQUIRE( - w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, - "Got negative pressure. Consider enabling first-order flux " - "correction or setting a reasonble pressure or temperature floor."); + PARTHENON_REQUIRE(w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, + "Got negative pressure. Consider enabling first-order flux " + "correction or setting a reasonble pressure or temperature floor."); // Temperature floor (if present) takes precedence over pressure floor if (e_floor_ > 0.0) { // apply temperature floor, correct total energy diff --git a/src/eos/adiabatic_hydro.cpp b/src/eos/adiabatic_hydro.cpp index 0f0fefaa..20e53a75 100644 --- a/src/eos/adiabatic_hydro.cpp +++ b/src/eos/adiabatic_hydro.cpp @@ -49,7 +49,7 @@ void AdiabaticHydroEOS::ConservedToPrimitive(MeshData *md) const { KOKKOS_LAMBDA(const int b, const int k, const int j, const int i) { const auto &cons = cons_pack(b); auto &prim = prim_pack(b); - - return this_on_device.ConsToPrim(cons,prim,nhydro,nscalars,k,j,i); + + return this_on_device.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); }); } diff --git a/src/eos/adiabatic_hydro.hpp b/src/eos/adiabatic_hydro.hpp index 765640cf..69a78293 100644 --- a/src/eos/adiabatic_hydro.hpp +++ b/src/eos/adiabatic_hydro.hpp @@ -47,8 +47,9 @@ class AdiabaticHydroEOS : public EquationOfState { // int& j, const int& i) \brief Fills an array of primitives given an array of // conserveds, potentially updating the conserved with floors template - KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &nhydro, const int &nscalars, const int &k, - const int &j, const int &i) const { + KOKKOS_INLINE_FUNCTION void ConsToPrim(View4D cons, View4D prim, const int &nhydro, + const int &nscalars, const int &k, const int &j, + const int &i) const { Real gm1 = GetGamma() - 1.0; auto density_floor_ = GetDensityFloor(); auto pressure_floor_ = GetPressureFloor(); @@ -69,8 +70,8 @@ class AdiabaticHydroEOS : public EquationOfState { // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) // and the code will fail if a negative density is encountered. PARTHENON_REQUIRE(u_d > 0.0 || density_floor_ > 0.0, - "Got negative density. Consider enabling first-order flux " - "correction or setting a reasonble density floor."); + "Got negative density. Consider enabling first-order flux " + "correction or setting a reasonble density floor."); // apply density floor, without changing momentum or energy u_d = (u_d > density_floor_) ? u_d : density_floor_; w_d = u_d; @@ -85,10 +86,9 @@ class AdiabaticHydroEOS : public EquationOfState { // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) // and the code will fail if a negative pressure is encountered. - PARTHENON_REQUIRE( - w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, - "Got negative pressure. Consider enabling first-order flux " - "correction or setting a reasonble pressure or temperature floor."); + PARTHENON_REQUIRE(w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, + "Got negative pressure. Consider enabling first-order flux " + "correction or setting a reasonble pressure or temperature floor."); // Temperature floor (if present) takes precedence over pressure floor if (e_floor_ > 0.0) { diff --git a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py index 4cdf75a3..fed03b28 100644 --- a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py +++ b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py @@ -1,8 +1,8 @@ -#======================================================================================== +# ======================================================================================== # AthenaPK - a performance portable block structured AMR MHD code # Copyright (c) 2020-2021, Athena Parthenon Collaboration. All rights reserved. # Licensed under the 3-clause BSD License, see LICENSE file for details -#======================================================================================== +# ======================================================================================== # (C) (or copyright) 2020. Triad National Security, LLC. All rights reserved. # # This program was produced under U.S. Government contract 89233218CNA000001 for Los @@ -13,13 +13,14 @@ # itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide # license in this material to reproduce, prepare derivative works, distribute copies to # the public, perform publicly and display publicly, and to permit others to do so. -#======================================================================================== +# ======================================================================================== # Modules import math import numpy as np import matplotlib -matplotlib.use('agg') + +matplotlib.use("agg") import matplotlib.pylab as plt import sys import os @@ -30,69 +31,78 @@ """ To prevent littering up imported folders with .pyc files or __pycache_ folder""" sys.dont_write_bytecode = True + class TestCase(utils.test_case.TestCaseAbs): def __init__(self): - #Define cluster parameters - #Setup units - unyt.define_unit("code_length",(1,"Mpc")) - unyt.define_unit("code_mass",(1e14,"Msun")) - unyt.define_unit("code_time",(1,"Gyr")) - self.code_length = unyt.unyt_quantity(1,"code_length") - self.code_mass = unyt.unyt_quantity(1,"code_mass") - self.code_time = unyt.unyt_quantity(1,"code_time") + # Define cluster parameters + # Setup units + unyt.define_unit("code_length", (1, "Mpc")) + unyt.define_unit("code_mass", (1e14, "Msun")) + unyt.define_unit("code_time", (1, "Gyr")) + self.code_length = unyt.unyt_quantity(1, "code_length") + self.code_mass = unyt.unyt_quantity(1, "code_mass") + self.code_time = unyt.unyt_quantity(1, "code_time") - self.tlim = unyt.unyt_quantity(0.1,"code_time") + self.tlim = unyt.unyt_quantity(0.1, "code_time") - #Setup constants + # Setup constants self.k_b = unyt.kb_cgs self.G = unyt.G_cgs - self.m_u =unyt.amu + self.m_u = unyt.amu - self.adiabatic_index = 5./3. + self.adiabatic_index = 5.0 / 3.0 self.He_mass_fraction = 0.25 - self.mu = 1/(self.He_mass_fraction*3./4. + (1-self.He_mass_fraction)*2) - self.mean_molecular_mass = self.mu*self.m_u - - #Define the initial uniform gas - self.uniform_gas_rho = unyt.unyt_quantity(1e-22,"g/cm**3") - self.uniform_gas_ux = unyt.unyt_quantity(60000,"cm/s") - self.uniform_gas_uy = unyt.unyt_quantity(40000,"cm/s") - self.uniform_gas_uz = unyt.unyt_quantity(-50000,"cm/s") - self.uniform_gas_pres = unyt.unyt_quantity(1e-10,"dyne/cm**2") - - self.uniform_gas_Mx = self.uniform_gas_rho*self.uniform_gas_ux - self.uniform_gas_My = self.uniform_gas_rho*self.uniform_gas_uy - self.uniform_gas_Mz = self.uniform_gas_rho*self.uniform_gas_uz - self.uniform_gas_energy_density = \ - 1./2.*self.uniform_gas_rho*(self.uniform_gas_ux**2 + self.uniform_gas_uy**2 + self.uniform_gas_uz**2) \ - + self.uniform_gas_pres/(self.adiabatic_index - 1.) - - self.uniform_gas_vel = np.sqrt( self.uniform_gas_ux**2 - + self.uniform_gas_uy**2 - + self.uniform_gas_uz**2 ) - - self.uniform_gas_temp = self.mu*self.m_u/self.k_b*self.uniform_gas_pres/self.uniform_gas_rho - print("Uniform gas temperature: ",self.uniform_gas_temp.in_units("K")*1.01) - - #SMBH parameters (for Bondi-like accretion) - self.M_smbh = unyt.unyt_quantity(1e8,"Msun") - - #Triggering parameters - self.accretion_radius = unyt.unyt_quantity(20,"kpc") - self.cold_temp_thresh = self.uniform_gas_temp*1.01 - self.cold_t_acc = unyt.unyt_quantity(100,"Myr") + self.mu = 1 / ( + self.He_mass_fraction * 3.0 / 4.0 + (1 - self.He_mass_fraction) * 2 + ) + self.mean_molecular_mass = self.mu * self.m_u + + # Define the initial uniform gas + self.uniform_gas_rho = unyt.unyt_quantity(1e-22, "g/cm**3") + self.uniform_gas_ux = unyt.unyt_quantity(60000, "cm/s") + self.uniform_gas_uy = unyt.unyt_quantity(40000, "cm/s") + self.uniform_gas_uz = unyt.unyt_quantity(-50000, "cm/s") + self.uniform_gas_pres = unyt.unyt_quantity(1e-10, "dyne/cm**2") + + self.uniform_gas_Mx = self.uniform_gas_rho * self.uniform_gas_ux + self.uniform_gas_My = self.uniform_gas_rho * self.uniform_gas_uy + self.uniform_gas_Mz = self.uniform_gas_rho * self.uniform_gas_uz + self.uniform_gas_energy_density = 1.0 / 2.0 * self.uniform_gas_rho * ( + self.uniform_gas_ux**2 + + self.uniform_gas_uy**2 + + self.uniform_gas_uz**2 + ) + self.uniform_gas_pres / (self.adiabatic_index - 1.0) + + self.uniform_gas_vel = np.sqrt( + self.uniform_gas_ux**2 + + self.uniform_gas_uy**2 + + self.uniform_gas_uz**2 + ) + + self.uniform_gas_temp = ( + self.mu * self.m_u / self.k_b * self.uniform_gas_pres / self.uniform_gas_rho + ) + print("Uniform gas temperature: ", self.uniform_gas_temp.in_units("K") * 1.01) + + # SMBH parameters (for Bondi-like accretion) + self.M_smbh = unyt.unyt_quantity(1e8, "Msun") + + # Triggering parameters + self.accretion_radius = unyt.unyt_quantity(20, "kpc") + self.cold_temp_thresh = self.uniform_gas_temp * 1.01 + self.cold_t_acc = unyt.unyt_quantity(100, "Myr") self.bondi_alpha = 100 self.bondi_beta = 2 - self.bondi_n0 = 0.05*(self.uniform_gas_rho/self.mean_molecular_mass) - + self.bondi_n0 = 0.05 * (self.uniform_gas_rho / self.mean_molecular_mass) + self.norm_tol = 1e-3 self.linf_accretion_rate_tol = 1e-3 - self.step_params_list = ["COLD_GAS","BOOSTED_BONDI","BOOTH_SCHAYE"] + self.step_params_list = ["COLD_GAS", "BOOSTED_BONDI", "BOOTH_SCHAYE"] self.steps = len(self.step_params_list) - def Prepare(self,parameters, step): + def Prepare(self, parameters, step): """ Any preprocessing that is needed before the drive is run can be done in this method @@ -100,7 +110,7 @@ def Prepare(self,parameters, step): This includes preparing files or any other pre processing steps that need to be implemented. The method also provides access to the parameters object which controls which parameters are being used to run - the driver. + the driver. It is possible to append arguments to the driver_cmd_line_args if it is desired to override the parthenon input file. Each element in the list @@ -116,51 +126,46 @@ def Prepare(self,parameters, step): 'time/tlim=0.4', 'mesh/nx1=400'] """ - triggering_mode = self.step_params_list[step-1] + triggering_mode = self.step_params_list[step - 1] output_id = triggering_mode parameters.driver_cmd_line_args = [ - f"parthenon/output2/id={output_id}", - f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", - f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", - f"hydro/gamma={self.adiabatic_index}", - f"hydro/He_mass_fraction={self.He_mass_fraction}", - - f"units/code_length_cgs={self.code_length.in_units('cm').v}", - f"units/code_mass_cgs={self.code_mass.in_units('g').v}", - f"units/code_time_cgs={self.code_time.in_units('s').v}", - - f"problem/cluster/uniform_gas/init_uniform_gas=true", - f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", - f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", - - f"problem/cluster/gravity/m_smbh={self.M_smbh.in_units('code_mass').v}", - - f"problem/cluster/agn_triggering/triggering_mode={triggering_mode}", - f"problem/cluster/agn_triggering/accretion_radius={self.accretion_radius.in_units('code_length').v}", - f"problem/cluster/agn_triggering/cold_temp_thresh={self.cold_temp_thresh.in_units('K').v}", - f"problem/cluster/agn_triggering/cold_t_acc={self.cold_t_acc.in_units('code_time').v}", - f"problem/cluster/agn_triggering/bondi_alpha={self.bondi_alpha}", - f"problem/cluster/agn_triggering/bondi_beta={self.bondi_beta}", - f"problem/cluster/agn_triggering/bondi_n0={self.bondi_n0.in_units('code_length**-3').v}", - f"problem/cluster/agn_triggering/write_to_file=true", - f"problem/cluster/agn_triggering/triggering_filename={triggering_mode}_triggering.dat", - ] - + f"parthenon/output2/id={output_id}", + f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", + f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", + f"hydro/gamma={self.adiabatic_index}", + f"hydro/He_mass_fraction={self.He_mass_fraction}", + f"units/code_length_cgs={self.code_length.in_units('cm').v}", + f"units/code_mass_cgs={self.code_mass.in_units('g').v}", + f"units/code_time_cgs={self.code_time.in_units('s').v}", + f"problem/cluster/uniform_gas/init_uniform_gas=true", + f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", + f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", + f"problem/cluster/gravity/m_smbh={self.M_smbh.in_units('code_mass').v}", + f"problem/cluster/agn_triggering/triggering_mode={triggering_mode}", + f"problem/cluster/agn_triggering/accretion_radius={self.accretion_radius.in_units('code_length').v}", + f"problem/cluster/agn_triggering/cold_temp_thresh={self.cold_temp_thresh.in_units('K').v}", + f"problem/cluster/agn_triggering/cold_t_acc={self.cold_t_acc.in_units('code_time').v}", + f"problem/cluster/agn_triggering/bondi_alpha={self.bondi_alpha}", + f"problem/cluster/agn_triggering/bondi_beta={self.bondi_beta}", + f"problem/cluster/agn_triggering/bondi_n0={self.bondi_n0.in_units('code_length**-3').v}", + f"problem/cluster/agn_triggering/write_to_file=true", + f"problem/cluster/agn_triggering/triggering_filename={triggering_mode}_triggering.dat", + ] return parameters - def Analyse(self,parameters): + def Analyse(self, parameters): """ Analyze the output and determine if the test passes. This function is called after the driver has been executed. It is responsible for reading whatever data it needs and making a judgment about whether or not the test passes. It takes no inputs. Output should - be True (test passes) or False (test fails). + be True (test passes) or False (test fails). The parameters that are passed in provide the paths to relevant locations and commands. Of particular importance is the path to the @@ -174,109 +179,165 @@ def Analyse(self,parameters): """ analyze_status = True - for step in range(1,self.steps+1): - triggering_mode = self.step_params_list[step-1] + for step in range(1, self.steps + 1): + triggering_mode = self.step_params_list[step - 1] output_id = triggering_mode step_status = True print(f"Testing {output_id}") - #Read the triggering data produced by the sim, replicate the - #integration of triggering to determine the final state of the gas - sim_data = np.loadtxt( - f"{triggering_mode}_triggering.dat") + # Read the triggering data produced by the sim, replicate the + # integration of triggering to determine the final state of the gas + sim_data = np.loadtxt(f"{triggering_mode}_triggering.dat") - sim_times = unyt.unyt_array(sim_data[:,0],"code_time") - sim_dts = unyt.unyt_array(sim_data[:,1],"code_time") - sim_accretion_rate = unyt.unyt_array(sim_data[:,2],"code_mass/code_time") + sim_times = unyt.unyt_array(sim_data[:, 0], "code_time") + sim_dts = unyt.unyt_array(sim_data[:, 1], "code_time") + sim_accretion_rate = unyt.unyt_array(sim_data[:, 2], "code_mass/code_time") if triggering_mode == "COLD_GAS": - sim_cold_mass = unyt.unyt_array(sim_data[:,3],"code_mass") - elif triggering_mode == "BOOSTED_BONDI" or triggering_mode == "BOOTH_SCHAYE": - sim_total_mass = unyt.unyt_array(sim_data[:,3],"code_mass") - sim_avg_density = unyt.unyt_array(sim_data[:,4],"code_mass/code_length**3") - sim_avg_velocity = unyt.unyt_array(sim_data[:,5],"code_length/code_time") - sim_avg_cs = unyt.unyt_array(sim_data[:,6],"code_length/code_time") + sim_cold_mass = unyt.unyt_array(sim_data[:, 3], "code_mass") + elif ( + triggering_mode == "BOOSTED_BONDI" or triggering_mode == "BOOTH_SCHAYE" + ): + sim_total_mass = unyt.unyt_array(sim_data[:, 3], "code_mass") + sim_avg_density = unyt.unyt_array( + sim_data[:, 4], "code_mass/code_length**3" + ) + sim_avg_velocity = unyt.unyt_array( + sim_data[:, 5], "code_length/code_time" + ) + sim_avg_cs = unyt.unyt_array(sim_data[:, 6], "code_length/code_time") else: - raise Exception(f"Triggering mode {triggering_mode} not supported in analysis") + raise Exception( + f"Triggering mode {triggering_mode} not supported in analysis" + ) n_times = sim_data.shape[0] - analytic_density = unyt.unyt_array(np.empty(n_times+1),"code_mass*code_length**-3") - analytic_pressure = unyt.unyt_array(np.empty(n_times+1),"code_mass/(code_length*code_time**2)") - analytic_accretion_rate = unyt.unyt_array(np.empty(n_times),"code_mass*code_time**-1") - - analytic_density[0] = self.uniform_gas_rho.in_units("code_mass*code_length**-3") - analytic_pressure[0] = self.uniform_gas_pres.in_units("code_mass/(code_length*code_time**2)") - - accretion_volume = 4./3.*np.pi*self.accretion_radius**3 + analytic_density = unyt.unyt_array( + np.empty(n_times + 1), "code_mass*code_length**-3" + ) + analytic_pressure = unyt.unyt_array( + np.empty(n_times + 1), "code_mass/(code_length*code_time**2)" + ) + analytic_accretion_rate = unyt.unyt_array( + np.empty(n_times), "code_mass*code_time**-1" + ) + + analytic_density[0] = self.uniform_gas_rho.in_units( + "code_mass*code_length**-3" + ) + analytic_pressure[0] = self.uniform_gas_pres.in_units( + "code_mass/(code_length*code_time**2)" + ) + + accretion_volume = 4.0 / 3.0 * np.pi * self.accretion_radius**3 for i in range(n_times): dt = sim_dts[i] if triggering_mode == "COLD_GAS": - #Temperature should stay fixed below cold gas threshold - accretion_rate = analytic_density[i]*accretion_volume/self.cold_t_acc - elif triggering_mode == "BOOSTED_BONDI" or triggering_mode == "BOOTH_SCHAYE": + # Temperature should stay fixed below cold gas threshold + accretion_rate = ( + analytic_density[i] * accretion_volume / self.cold_t_acc + ) + elif ( + triggering_mode == "BOOSTED_BONDI" + or triggering_mode == "BOOTH_SCHAYE" + ): if triggering_mode == "BOOSTED_BONDI": alpha = self.bondi_alpha elif triggering_mode == "BOOTH_SCHAYE": - n = analytic_density[i]/(self.mu*self.m_u) + n = analytic_density[i] / (self.mu * self.m_u) if n <= self.bondi_n0: - alpha = 1. + alpha = 1.0 else: - alpha = (n/self.bondi_n0)**self.bondi_beta + alpha = (n / self.bondi_n0) ** self.bondi_beta else: - raise Exception(f"Triggering mode {triggering_mode} not supported in analysis") - - cs2 = self.adiabatic_index*self.uniform_gas_pres/self.uniform_gas_rho - accretion_rate = alpha * \ - (2 * np.pi *unyt.G_cgs**2 * self.M_smbh**2 * analytic_density[i]) / \ - (self.uniform_gas_vel**2 + cs2)**(3./2.) + raise Exception( + f"Triggering mode {triggering_mode} not supported in analysis" + ) + + cs2 = ( + self.adiabatic_index + * self.uniform_gas_pres + / self.uniform_gas_rho + ) + accretion_rate = ( + alpha + * ( + 2 + * np.pi + * unyt.G_cgs**2 + * self.M_smbh**2 + * analytic_density[i] + ) + / (self.uniform_gas_vel**2 + cs2) ** (3.0 / 2.0) + ) else: - raise Exception(f"Triggering mode {triggering_mode} not supported in analysis") - - accretion_rate_density = accretion_rate/accretion_volume - - analytic_density[i+1] = (analytic_density[i] - accretion_rate_density*dt).in_units(analytic_density.units) - analytic_pressure[i+1] = (analytic_pressure[i] \ - - accretion_rate_density*dt*analytic_pressure[i]/analytic_density[i]).in_units(analytic_pressure.units) - analytic_accretion_rate[i] = accretion_rate.in_units(analytic_accretion_rate.units) - - #Compare the analytic accretion_rate - accretion_rate_err = np.abs((analytic_accretion_rate - sim_accretion_rate)/analytic_accretion_rate) + raise Exception( + f"Triggering mode {triggering_mode} not supported in analysis" + ) + + accretion_rate_density = accretion_rate / accretion_volume + + analytic_density[i + 1] = ( + analytic_density[i] - accretion_rate_density * dt + ).in_units(analytic_density.units) + analytic_pressure[i + 1] = ( + analytic_pressure[i] + - accretion_rate_density + * dt + * analytic_pressure[i] + / analytic_density[i] + ).in_units(analytic_pressure.units) + analytic_accretion_rate[i] = accretion_rate.in_units( + analytic_accretion_rate.units + ) + + # Compare the analytic accretion_rate + accretion_rate_err = np.abs( + (analytic_accretion_rate - sim_accretion_rate) / analytic_accretion_rate + ) if np.max(accretion_rate_err) > self.linf_accretion_rate_tol: analyze_status = False - print(f"{triggering_mode} linf_accretion_rate_err {np.max(accretion_rate_err)}" - f" exceeds tolerance {self.linf_accretion_rate_tol}" - f" at i={np.argmax(accretion_rate_err)}" - f" time={sim_times[np.argmax(accretion_rate_err)]}") - + print( + f"{triggering_mode} linf_accretion_rate_err {np.max(accretion_rate_err)}" + f" exceeds tolerance {self.linf_accretion_rate_tol}" + f" at i={np.argmax(accretion_rate_err)}" + f" time={sim_times[np.argmax(accretion_rate_err)]}" + ) final_rho = analytic_density[-1] final_pres = analytic_pressure[-1] - final_Mx = self.uniform_gas_ux*final_rho - final_My = self.uniform_gas_uy*final_rho - final_Mz = self.uniform_gas_uz*final_rho - final_energy_density = 1./2.*final_rho*(self.uniform_gas_ux**2 - + self.uniform_gas_uy**2 - + self.uniform_gas_uz**2) \ - + final_pres/(self.adiabatic_index - 1.) - + final_Mx = self.uniform_gas_ux * final_rho + final_My = self.uniform_gas_uy * final_rho + final_Mz = self.uniform_gas_uz * final_rho + final_energy_density = 1.0 / 2.0 * final_rho * ( + self.uniform_gas_ux**2 + + self.uniform_gas_uy**2 + + self.uniform_gas_uz**2 + ) + final_pres / (self.adiabatic_index - 1.0) - def accretion_mask( Z,Y,X, inner_state, outer_state ): - pos_cart = unyt.unyt_array((X,Y,Z),"code_length") + def accretion_mask(Z, Y, X, inner_state, outer_state): + pos_cart = unyt.unyt_array((X, Y, Z), "code_length") - r = np.sqrt(np.sum(pos_cart**2,axis=0)) + r = np.sqrt(np.sum(pos_cart**2, axis=0)) - state = inner_state*(r < self.accretion_radius) + outer_state*(r >= self.accretion_radius) + state = inner_state * (r < self.accretion_radius) + outer_state * ( + r >= self.accretion_radius + ) return state - #Check that the initial and final outputs match the expected tower - sys.path.insert(1, parameters.parthenon_path + '/scripts/python/packages/parthenon_tools/parthenon_tools') + # Check that the initial and final outputs match the expected tower + sys.path.insert( + 1, + parameters.parthenon_path + + "/scripts/python/packages/parthenon_tools/parthenon_tools", + ) try: import compare_analytic @@ -285,59 +346,94 @@ def accretion_mask( Z,Y,X, inner_state, outer_state ): return False initial_analytic_components = { - "Density":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_rho.in_units("code_mass/code_length**3").v, - "MomentumDensity1":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_Mx.in_units("code_mass*code_length**-2*code_time**-1").v, - "MomentumDensity2":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_My.in_units("code_mass*code_length**-2*code_time**-1").v, - "MomentumDensity3":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_Mz.in_units("code_mass*code_length**-2*code_time**-1").v, - "TotalEnergyDensity":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_energy_density.in_units("code_mass*code_length**-1*code_time**-2").v, - "Velocity1":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_ux.in_units("code_length*code_time**-1").v, - "Velocity2":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_uy.in_units("code_length*code_time**-1").v, - "Velocity3":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_uz.in_units("code_length*code_time**-1").v, - "Pressure":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_pres.in_units("code_mass/(code_length*code_time**2)").v, - } - - + "Density": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_rho.in_units("code_mass/code_length**3").v, + "MomentumDensity1": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_Mx.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "MomentumDensity2": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_My.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "MomentumDensity3": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_Mz.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "TotalEnergyDensity": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_energy_density.in_units( + "code_mass*code_length**-1*code_time**-2" + ).v, + "Velocity1": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_ux.in_units("code_length*code_time**-1").v, + "Velocity2": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_uy.in_units("code_length*code_time**-1").v, + "Velocity3": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_uz.in_units("code_length*code_time**-1").v, + "Pressure": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_pres.in_units( + "code_mass/(code_length*code_time**2)" + ).v, + } final_analytic_components = { - "Density":lambda Z,Y,X,time : - accretion_mask(Z,Y,X,final_rho,self.uniform_gas_rho).in_units("code_mass/code_length**3").v, - "MomentumDensity1":lambda Z,Y,X,time : - accretion_mask(Z,Y,X,final_Mx,self.uniform_gas_Mx).in_units("code_mass*code_length**-2*code_time**-1").v, - "MomentumDensity2":lambda Z,Y,X,time : - accretion_mask(Z,Y,X,final_My,self.uniform_gas_My).in_units("code_mass*code_length**-2*code_time**-1").v, - "MomentumDensity3":lambda Z,Y,X,time : - accretion_mask(Z,Y,X,final_Mz,self.uniform_gas_Mz).in_units("code_mass*code_length**-2*code_time**-1").v, - "TotalEnergyDensity":lambda Z,Y,X,time : - accretion_mask(Z,Y,X,final_energy_density,self.uniform_gas_energy_density).in_units("code_mass*code_length**-1*code_time**-2").v, - "Velocity1":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_ux.in_units("code_length*code_time**-1").v, - "Velocity2":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_uy.in_units("code_length*code_time**-1").v, - "Velocity3":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_uz.in_units("code_length*code_time**-1").v, - "Pressure":lambda Z,Y,X,time : - accretion_mask(Z,Y,X,final_pres,self.uniform_gas_pres).in_units("code_mass/(code_length*code_time**2)").v, - } - - phdf_files = [f"{parameters.output_path}/parthenon.{output_id}.00000.phdf",f"{parameters.output_path}/parthenon.{output_id}.final.phdf"] - - #Use a very loose tolerance, linf relative error - analytic_status = True - for analytic_components,phdf_file in zip((initial_analytic_components,final_analytic_components),phdf_files): - analytic_status &= compare_analytic.compare_analytic( phdf_file, analytic_components, - err_func=lambda gold,test: compare_analytic.norm_err_func(gold, test, norm_ord=np.inf,relative=True), tol=self.norm_tol) + "Density": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_rho, self.uniform_gas_rho + ) + .in_units("code_mass/code_length**3") + .v, + "MomentumDensity1": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_Mx, self.uniform_gas_Mx + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "MomentumDensity2": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_My, self.uniform_gas_My + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "MomentumDensity3": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_Mz, self.uniform_gas_Mz + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "TotalEnergyDensity": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_energy_density, self.uniform_gas_energy_density + ) + .in_units("code_mass*code_length**-1*code_time**-2") + .v, + "Velocity1": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_ux.in_units("code_length*code_time**-1").v, + "Velocity2": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_uy.in_units("code_length*code_time**-1").v, + "Velocity3": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_uz.in_units("code_length*code_time**-1").v, + "Pressure": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_pres, self.uniform_gas_pres + ) + .in_units("code_mass/(code_length*code_time**2)") + .v, + } + + phdf_files = [ + f"{parameters.output_path}/parthenon.{output_id}.00000.phdf", + f"{parameters.output_path}/parthenon.{output_id}.final.phdf", + ] + # Use a very loose tolerance, linf relative error + analytic_status = True + for analytic_components, phdf_file in zip( + (initial_analytic_components, final_analytic_components), phdf_files + ): + analytic_status &= compare_analytic.compare_analytic( + phdf_file, + analytic_components, + err_func=lambda gold, test: compare_analytic.norm_err_func( + gold, test, norm_ord=np.inf, relative=True + ), + tol=self.norm_tol, + ) analyze_status &= analytic_status - return analyze_status diff --git a/tst/regression/test_suites/cluster_hse/cluster_hse.py b/tst/regression/test_suites/cluster_hse/cluster_hse.py index 029cd434..c47cfc31 100644 --- a/tst/regression/test_suites/cluster_hse/cluster_hse.py +++ b/tst/regression/test_suites/cluster_hse/cluster_hse.py @@ -1,8 +1,8 @@ -#======================================================================================== +# ======================================================================================== # AthenaPK - a performance portable block structured AMR MHD code # Copyright (c) 2020-2021, Athena Parthenon Collaboration. All rights reserved. # Licensed under the 3-clause BSD License, see LICENSE file for details -#======================================================================================== +# ======================================================================================== # (C) (or copyright) 2020. Triad National Security, LLC. All rights reserved. # # This program was produced under U.S. Government contract 89233218CNA000001 for Los @@ -13,13 +13,14 @@ # itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide # license in this material to reproduce, prepare derivative works, distribute copies to # the public, perform publicly and display publicly, and to permit others to do so. -#======================================================================================== +# ======================================================================================== # Modules import math import numpy as np import matplotlib -matplotlib.use('agg') + +matplotlib.use("agg") import matplotlib.pylab as plt import sys import os @@ -29,68 +30,69 @@ """ To prevent littering up imported folders with .pyc files or __pycache_ folder""" sys.dont_write_bytecode = True + class TestCase(utils.test_case.TestCaseAbs): def __init__(self): - #Define cluster parameters - #Setup units - unyt.define_unit("code_length",(1,"Mpc")) - unyt.define_unit("code_mass",(1e14,"Msun")) - unyt.define_unit("code_time",(1,"Gyr")) - self.code_length = unyt.unyt_quantity(1,"code_length") - self.code_mass = unyt.unyt_quantity(1,"code_mass") - self.code_time = unyt.unyt_quantity(1,"code_time") + # Define cluster parameters + # Setup units + unyt.define_unit("code_length", (1, "Mpc")) + unyt.define_unit("code_mass", (1e14, "Msun")) + unyt.define_unit("code_time", (1, "Gyr")) + self.code_length = unyt.unyt_quantity(1, "code_length") + self.code_mass = unyt.unyt_quantity(1, "code_mass") + self.code_time = unyt.unyt_quantity(1, "code_time") - #Setup constants + # Setup constants self.k_b = unyt.kb_cgs self.G = unyt.G_cgs - self.m_u =unyt.amu + self.m_u = unyt.amu - self.adiabatic_index = 5./3. + self.adiabatic_index = 5.0 / 3.0 - self.hubble_parameter = unyt.unyt_quantity(70,"km*s**-1*Mpc**-1") + self.hubble_parameter = unyt.unyt_quantity(70, "km*s**-1*Mpc**-1") - #Which gravitational fields to include + # Which gravitational fields to include self.include_nfw_g = True self.which_bcg_g = "HERNQUIST" self.include_smbh_g = True - #NFW parameters + # NFW parameters self.c_nfw = 6.0 - self.m_nfw_200 = unyt.unyt_quantity(1e15,"Msun") + self.m_nfw_200 = unyt.unyt_quantity(1e15, "Msun") - #BCG parameters - self.m_bcg_s = unyt.unyt_quantity(1e11,"Msun") - self.r_bcg_s = unyt.unyt_quantity(4,"kpc") + # BCG parameters + self.m_bcg_s = unyt.unyt_quantity(1e11, "Msun") + self.r_bcg_s = unyt.unyt_quantity(4, "kpc") - #SMBH parameters - self.m_smbh = unyt.unyt_quantity(1e8,"Msun") + # SMBH parameters + self.m_smbh = unyt.unyt_quantity(1e8, "Msun") - #Smooth gravity at origin, for numerical reasons - self.g_smoothing_radius = unyt.unyt_quantity(0.0,"code_length") + # Smooth gravity at origin, for numerical reasons + self.g_smoothing_radius = unyt.unyt_quantity(0.0, "code_length") - #Entropy profile parameters - self.K_0 = unyt.unyt_quantity(10,"keV*cm**2") - self.K_100 = unyt.unyt_quantity(150,"keV*cm**2") - self.R_K = unyt.unyt_quantity(100,"kpc") + # Entropy profile parameters + self.K_0 = unyt.unyt_quantity(10, "keV*cm**2") + self.K_100 = unyt.unyt_quantity(150, "keV*cm**2") + self.R_K = unyt.unyt_quantity(100, "kpc") self.alpha_K = 1.1 self.He_mass_fraction = 0.25 - #Fix density at radius to close system of equations - self.R_fix = unyt.unyt_quantity(2e3,"kpc") - self.rho_fix = unyt.unyt_quantity(1e-28,"g*cm**-3") + # Fix density at radius to close system of equations + self.R_fix = unyt.unyt_quantity(2e3, "kpc") + self.rho_fix = unyt.unyt_quantity(1e-28, "g*cm**-3") - #Building the radii at which to sample initial rho,P + # Building the radii at which to sample initial rho,P self.R_sampling = 4.0 self.max_dR = 0.001 - self.R_min = unyt.unyt_quantity(1e-3,"kpc") - self.R_max = unyt.unyt_quantity(5e3,"kpc") - + self.R_min = unyt.unyt_quantity(1e-3, "kpc") + self.R_max = unyt.unyt_quantity(5e3, "kpc") + self.norm_tol = 1e-3 - def Prepare(self,parameters, step): + def Prepare(self, parameters, step): """ Any preprocessing that is needed before the drive is run can be done in this method @@ -98,7 +100,7 @@ def Prepare(self,parameters, step): This includes preparing files or any other pre processing steps that need to be implemented. The method also provides access to the parameters object which controls which parameters are being used to run - the driver. + the driver. It is possible to append arguments to the driver_cmd_line_args if it is desired to override the parthenon input file. Each element in the list @@ -116,42 +118,41 @@ def Prepare(self,parameters, step): """ parameters.driver_cmd_line_args = [ - f"hydro/gamma={self.adiabatic_index}", - f"hydro/He_mass_fraction={self.He_mass_fraction}", - f"units/code_length_cgs={self.code_length.in_units('cm').v}", - f"units/code_mass_cgs={self.code_mass.in_units('g').v}", - f"units/code_time_cgs={self.code_time.in_units('s').v}", - f"problem/cluster/hubble_parameter={self.hubble_parameter.in_units('1/code_time').v}", - f"problem/cluster/gravity/include_nfw_g={self.include_nfw_g}", - f"problem/cluster/gravity/which_bcg_g={self.which_bcg_g}", - f"problem/cluster/gravity/include_smbh_g={self.include_smbh_g}", - f"problem/cluster/gravity/c_nfw={self.c_nfw}", - f"problem/cluster/gravity/m_nfw_200={self.m_nfw_200.in_units('code_mass').v}", - f"problem/cluster/gravity/m_bcg_s={self.m_bcg_s.in_units('code_mass').v}", - f"problem/cluster/gravity/r_bcg_s={self.r_bcg_s.in_units('code_length').v}", - f"problem/cluster/gravity/m_smbh={self.m_smbh.in_units('code_mass').v}", - f"problem/cluster/gravity/g_smoothing_radius={self.g_smoothing_radius.in_units('code_length').v}", - f"problem/cluster/entropy_profile/k_0={self.K_0.in_units('code_length**4*code_mass/code_time**2').v}", - f"problem/cluster/entropy_profile/k_100={self.K_100.in_units('code_length**4*code_mass/code_time**2').v}", - f"problem/cluster/entropy_profile/r_k={self.R_K.in_units('code_length').v}", - f"problem/cluster/entropy_profile/alpha_k={self.alpha_K}", - f"problem/cluster/hydrostatic_equilibrium/r_fix={self.R_fix.in_units('code_length').v}", - f"problem/cluster/hydrostatic_equilibrium/rho_fix={self.rho_fix.in_units('code_mass/code_length**3').v}", - f"problem/cluster/hydrostatic_equilibrium/r_sampling={self.R_sampling}", - f"problem/cluster/hydrostatic_equilibrium/max_dr={self.max_dR}", - ] - + f"hydro/gamma={self.adiabatic_index}", + f"hydro/He_mass_fraction={self.He_mass_fraction}", + f"units/code_length_cgs={self.code_length.in_units('cm').v}", + f"units/code_mass_cgs={self.code_mass.in_units('g').v}", + f"units/code_time_cgs={self.code_time.in_units('s').v}", + f"problem/cluster/hubble_parameter={self.hubble_parameter.in_units('1/code_time').v}", + f"problem/cluster/gravity/include_nfw_g={self.include_nfw_g}", + f"problem/cluster/gravity/which_bcg_g={self.which_bcg_g}", + f"problem/cluster/gravity/include_smbh_g={self.include_smbh_g}", + f"problem/cluster/gravity/c_nfw={self.c_nfw}", + f"problem/cluster/gravity/m_nfw_200={self.m_nfw_200.in_units('code_mass').v}", + f"problem/cluster/gravity/m_bcg_s={self.m_bcg_s.in_units('code_mass').v}", + f"problem/cluster/gravity/r_bcg_s={self.r_bcg_s.in_units('code_length').v}", + f"problem/cluster/gravity/m_smbh={self.m_smbh.in_units('code_mass').v}", + f"problem/cluster/gravity/g_smoothing_radius={self.g_smoothing_radius.in_units('code_length').v}", + f"problem/cluster/entropy_profile/k_0={self.K_0.in_units('code_length**4*code_mass/code_time**2').v}", + f"problem/cluster/entropy_profile/k_100={self.K_100.in_units('code_length**4*code_mass/code_time**2').v}", + f"problem/cluster/entropy_profile/r_k={self.R_K.in_units('code_length').v}", + f"problem/cluster/entropy_profile/alpha_k={self.alpha_K}", + f"problem/cluster/hydrostatic_equilibrium/r_fix={self.R_fix.in_units('code_length').v}", + f"problem/cluster/hydrostatic_equilibrium/rho_fix={self.rho_fix.in_units('code_mass/code_length**3').v}", + f"problem/cluster/hydrostatic_equilibrium/r_sampling={self.R_sampling}", + f"problem/cluster/hydrostatic_equilibrium/max_dr={self.max_dR}", + ] return parameters - def Analyse(self,parameters): + def Analyse(self, parameters): """ Analyze the output and determine if the test passes. This function is called after the driver has been executed. It is responsible for reading whatever data it needs and making a judgment about whether or not the test passes. It takes no inputs. Output should - be True (test passes) or False (test fails). + be True (test passes) or False (test fails). The parameters that are passed in provide the paths to relevant locations and commands. Of particular importance is the path to the @@ -166,50 +167,89 @@ def Analyse(self,parameters): analyze_status = True self.Yp = self.He_mass_fraction - self.mu = 1/(self.Yp*3./4. + (1-self.Yp)*2) - self.mu_e = 1/(self.Yp*2./4. + (1-self.Yp)) + self.mu = 1 / (self.Yp * 3.0 / 4.0 + (1 - self.Yp) * 2) + self.mu_e = 1 / (self.Yp * 2.0 / 4.0 + (1 - self.Yp)) self.include_gs = [] - if(self.include_nfw_g): + if self.include_nfw_g: self.include_gs.append("nfw") - if(self.which_bcg_g == "HERNQUIST"): + if self.which_bcg_g == "HERNQUIST": self.include_gs.append("bcg_hernquist") - if(self.include_smbh_g): + if self.include_smbh_g: self.include_gs.append("smbh") - self.rho_crit = 3*self.hubble_parameter**2/(8*np.pi*self.G) - self.rho_nfw_0 = 200./3*self.rho_crit*self.c_nfw**3/(np.log(1 + self.c_nfw) - self.c_nfw/(1 + self.c_nfw)) - self.R_nfw_s = (self.m_nfw_200/(4*np.pi*self.rho_nfw_0*(np.log(1+self.c_nfw)-self.c_nfw/(1+self.c_nfw))))**(1./3.) + self.rho_crit = 3 * self.hubble_parameter**2 / (8 * np.pi * self.G) + self.rho_nfw_0 = ( + 200.0 + / 3 + * self.rho_crit + * self.c_nfw**3 + / (np.log(1 + self.c_nfw) - self.c_nfw / (1 + self.c_nfw)) + ) + self.R_nfw_s = ( + self.m_nfw_200 + / ( + 4 + * np.pi + * self.rho_nfw_0 + * (np.log(1 + self.c_nfw) - self.c_nfw / (1 + self.c_nfw)) + ) + ) ** (1.0 / 3.0) + + # Thermodynamics+Entropy helpers + def P_from_rho_K(rho, K): + return ( + K + * (self.mu / self.mu_e) ** (2.0 / 3.0) + * (rho / (self.mu * self.m_u)) ** (5.0 / 3.0) + ) + + def rho_from_P_K(P, K): + return ( + (P / K) ** (3.0 / 5.0) + * self.mu + * self.m_u + / (self.mu / self.mu_e) ** (2.0 / 5.0) + ) - #Thermodynamics+Entropy helpers - def P_from_rho_K(rho,K): - return K*(self.mu/self.mu_e)**(2./3.)*(rho/(self.mu*self.m_u))**(5./3.) - def rho_from_P_K(P,K): - return (P/K)**(3./5.)*self.mu*self.m_u/(self.mu/self.mu_e)**(2./5.) def K_from_r(r): - return self.K_0 + self.K_100*(r/self.R_K)**self.alpha_K + return self.K_0 + self.K_100 * (r / self.R_K) ** self.alpha_K + def n_from_rho(rho): - return rho/(self.mu*self.m_u) + return rho / (self.mu * self.m_u) + def ne_from_rho(rho): - return self.mu/self.mu_e*n_from_rho(rho) - def T_from_rho_P(rho,P): - return P/(n_from_rho(rho)*self.k_b) + return self.mu / self.mu_e * n_from_rho(rho) - #Gravity helpers + def T_from_rho_P(rho, P): + return P / (n_from_rho(rho) * self.k_b) + + # Gravity helpers def g_nfw_from_r(r): - return self.G * self.m_nfw_200/( np.log(1 + self.c_nfw) - self.c_nfw/(1+self.c_nfw))*( np.log(1 + r/self.R_nfw_s) - r/(r+self.R_nfw_s))/r**2 - + return ( + self.G + * self.m_nfw_200 + / (np.log(1 + self.c_nfw) - self.c_nfw / (1 + self.c_nfw)) + * (np.log(1 + r / self.R_nfw_s) - r / (r + self.R_nfw_s)) + / r**2 + ) + def g_bcg_hernquist_from_r(r): - #m_bcg = 8*self.m_bcg_s*(r/self.r_bcg_s)**2/( 2*( 1 + r/self.r_bcg_s)**2) - #return G*m_bcg/r**2 - g = self.G*self.m_bcg_s/(self.r_bcg_s**2)/( 2*( 1 + r/self.r_bcg_s)**2) + # m_bcg = 8*self.m_bcg_s*(r/self.r_bcg_s)**2/( 2*( 1 + r/self.r_bcg_s)**2) + # return G*m_bcg/r**2 + g = ( + self.G + * self.m_bcg_s + / (self.r_bcg_s**2) + / (2 * (1 + r / self.r_bcg_s) ** 2) + ) return g def g_smbh_from_r(r): - return self.G*self.m_smbh/r**2 + return self.G * self.m_smbh / r**2 - def g_from_r(r,include_gs): - g = unyt.unyt_array(np.zeros_like(r),"code_length*code_time**-2") + def g_from_r(r, include_gs): + g = unyt.unyt_array(np.zeros_like(r), "code_length*code_time**-2") if "nfw" in include_gs: g += g_nfw_from_r(r) @@ -219,117 +259,167 @@ def g_from_r(r,include_gs): g += g_smbh_from_r(r) return g - #Pressure function to integrate - def dP_dr(r,P): - return -rho_from_P_K(P,K_from_r(r))*g_from_r(r,self.include_gs) + # Pressure function to integrate + def dP_dr(r, P): + return -rho_from_P_K(P, K_from_r(r)) * g_from_r(r, self.include_gs) - #RK4 Integrator - def rk4(f,y0,T): - y = np.zeros(T.size)*y0 + # RK4 Integrator + def rk4(f, y0, T): + y = np.zeros(T.size) * y0 y[0] = y0 - for i in np.arange(T.size-1): - h = T[i+1] - T[i] - k1 = f(T[i] ,y[i] ) - k2 = f(T[i]+h/2.,y[i] + h/2.*k1) - k3 = f(T[i]+h/2.,y[i] + h/2.*k2) - k4 = f(T[i]+h ,y[i] + h*k3 ) - y[i+1] = y[i] + h/6.*(k1 + 2*k2 + 2*k3 + k4) + for i in np.arange(T.size - 1): + h = T[i + 1] - T[i] + k1 = f(T[i], y[i]) + k2 = f(T[i] + h / 2.0, y[i] + h / 2.0 * k1) + k3 = f(T[i] + h / 2.0, y[i] + h / 2.0 * k2) + k4 = f(T[i] + h, y[i] + h * k3) + y[i + 1] = y[i] + h / 6.0 * (k1 + 2 * k2 + 2 * k3 + k4) return y - - - #Defining the R mesh to integrate along - R = unyt.unyt_array(np.geomspace(self.R_min.in_units("code_length"),self.R_max.in_units("code_length"),4000),"code_length") - - #Calculate an entropy and pressure at the fixed radius + # Defining the R mesh to integrate along + R = unyt.unyt_array( + np.geomspace( + self.R_min.in_units("code_length"), + self.R_max.in_units("code_length"), + 4000, + ), + "code_length", + ) + + # Calculate an entropy and pressure at the fixed radius K_fix = K_from_r(self.R_fix) - P_fix = P_from_rho_K(self.rho_fix,K_fix) - - #Prepare two array of R leading inwards and outwards from the virial radius, to integrate - R_in = unyt.unyt_array(np.hstack((R[R < self.R_fix],self.R_fix.in_units("code_length"))),"code_length") - R_out = unyt.unyt_array(np.hstack((self.R_fix.in_units("code_length"),R[R > self.R_fix])),"code_length") - - #Integrate in from self.R_fix - P_in = rk4(dP_dr,P_fix,R_in[::-1]) - P_in = P_in[::-1]#Flip P to match R - - #Integrate out from P - P_out = rk4(dP_dr,P_fix,R_out) - - #Put the two pieces of P together - P = unyt.unyt_array(np.hstack((P_in[:-1].in_units("dyne/cm**2"),P_out[1:].in_units("dyne/cm**2"))),"dyne/cm**2") + P_fix = P_from_rho_K(self.rho_fix, K_fix) + + # Prepare two array of R leading inwards and outwards from the virial radius, to integrate + R_in = unyt.unyt_array( + np.hstack((R[R < self.R_fix], self.R_fix.in_units("code_length"))), + "code_length", + ) + R_out = unyt.unyt_array( + np.hstack((self.R_fix.in_units("code_length"), R[R > self.R_fix])), + "code_length", + ) + + # Integrate in from self.R_fix + P_in = rk4(dP_dr, P_fix, R_in[::-1]) + P_in = P_in[::-1] # Flip P to match R + + # Integrate out from P + P_out = rk4(dP_dr, P_fix, R_out) + + # Put the two pieces of P together + P = unyt.unyt_array( + np.hstack( + (P_in[:-1].in_units("dyne/cm**2"), P_out[1:].in_units("dyne/cm**2")) + ), + "dyne/cm**2", + ) K = K_from_r(R) - rho = rho_from_P_K(P,K) + rho = rho_from_P_K(P, K) - T = T_from_rho_P(rho,P) + T = T_from_rho_P(rho, P) - g = g_from_r(R,self.include_gs) + g = g_from_r(R, self.include_gs) - gs = {include_g: g_from_r(R,(include_g,)) for include_g in self.include_gs} - Ms = {include_g: gs[include_g]*R**2/self.G for include_g in self.include_gs} + gs = {include_g: g_from_r(R, (include_g,)) for include_g in self.include_gs} + Ms = { + include_g: gs[include_g] * R**2 / self.G for include_g in self.include_gs + } - #The analytic pressure and density profiles + # The analytic pressure and density profiles analytic_R = R.in_units("code_length") analytic_P = P.in_units("code_mass/(code_length*code_time**2)") analytic_K = K.in_units("code_length**4*code_mass*code_time**-2") - analytic_rho =rho.in_units("code_mass/code_length**3") + analytic_rho = rho.in_units("code_mass/code_length**3") analytic_g = g.in_units("code_length*code_time**-2") - #Check that test_he_sphere.dat matches python model + # Check that test_he_sphere.dat matches python model try: he_sphere_data = np.loadtxt(f"{parameters.output_path}/test_he_sphere.dat") - he_sphere_R = unyt.unyt_array(he_sphere_data[1:,0],"code_length") - he_sphere_P = unyt.unyt_array(he_sphere_data[1:,1],"code_mass/(code_length*code_time**2)") - he_sphere_K = unyt.unyt_array(he_sphere_data[1:,2],"code_length**4*code_mass*code_time**-2") - he_sphere_rho = unyt.unyt_array(he_sphere_data[1:,3],"code_mass*code_length**-3") - he_sphere_n = unyt.unyt_array(he_sphere_data[1:,4],"code_length**-3") - he_sphere_ne = unyt.unyt_array(he_sphere_data[1:,5],"code_length**-3") - he_sphere_T = unyt.unyt_array(he_sphere_data[1:,6],"K") - he_sphere_g = unyt.unyt_array(he_sphere_data[1:,7],"code_length*code_time**-2") - he_sphere_dP_dr = unyt.unyt_array(he_sphere_data[1:,8],"code_mass/(code_length**2*code_time**2)") + he_sphere_R = unyt.unyt_array(he_sphere_data[1:, 0], "code_length") + he_sphere_P = unyt.unyt_array( + he_sphere_data[1:, 1], "code_mass/(code_length*code_time**2)" + ) + he_sphere_K = unyt.unyt_array( + he_sphere_data[1:, 2], "code_length**4*code_mass*code_time**-2" + ) + he_sphere_rho = unyt.unyt_array( + he_sphere_data[1:, 3], "code_mass*code_length**-3" + ) + he_sphere_n = unyt.unyt_array(he_sphere_data[1:, 4], "code_length**-3") + he_sphere_ne = unyt.unyt_array(he_sphere_data[1:, 5], "code_length**-3") + he_sphere_T = unyt.unyt_array(he_sphere_data[1:, 6], "K") + he_sphere_g = unyt.unyt_array( + he_sphere_data[1:, 7], "code_length*code_time**-2" + ) + he_sphere_dP_dr = unyt.unyt_array( + he_sphere_data[1:, 8], "code_mass/(code_length**2*code_time**2)" + ) except IOError: print("test_he_sphere.dat file not accessible") return False - profile_comparison_vars = ((analytic_P, he_sphere_P, "Pressure"), - (analytic_K, he_sphere_K, "Entropy"), - (analytic_rho, he_sphere_rho, "Density"), - (analytic_g, he_sphere_g, "Gravity") ) + profile_comparison_vars = ( + (analytic_P, he_sphere_P, "Pressure"), + (analytic_K, he_sphere_K, "Entropy"), + (analytic_rho, he_sphere_rho, "Density"), + (analytic_g, he_sphere_g, "Gravity"), + ) - fig,axes = plt.subplots(2,2) + fig, axes = plt.subplots(2, 2) - for ax,(analytic_var,he_sphere_var,label) in zip(axes.flatten(),profile_comparison_vars): - #Interpolate analytic profile to the same radii as he_sphere - analytic_interp = unyt.unyt_array(np.interp(he_sphere_R,analytic_R,analytic_var),analytic_var.units) + for ax, (analytic_var, he_sphere_var, label) in zip( + axes.flatten(), profile_comparison_vars + ): + # Interpolate analytic profile to the same radii as he_sphere + analytic_interp = unyt.unyt_array( + np.interp(he_sphere_R, analytic_R, analytic_var), analytic_var.units + ) - #Compute relative error at he_sphere_R - rel_err = np.abs((analytic_interp - he_sphere_var)/analytic_interp) + # Compute relative error at he_sphere_R + rel_err = np.abs((analytic_interp - he_sphere_var) / analytic_interp) - norm_rel_err = np.linalg.norm(rel_err[1:-1])/rel_err.size + norm_rel_err = np.linalg.norm(rel_err[1:-1]) / rel_err.size if norm_rel_err > self.norm_tol: analyze_status = False - print(f"test_he_sphere.dat field {label} relative error {norm_rel_err} exceeds tolerance {self.norm_tol}") - - #Plot the analytic profile and test_he_sphere.dat profile - ax.plot(he_sphere_R,he_sphere_var,label="test_he_sphere.dat") - ax.plot(he_sphere_R,analytic_interp,label="Analytic Interpolation",linestyle="--") + print( + f"test_he_sphere.dat field {label} relative error {norm_rel_err} exceeds tolerance {self.norm_tol}" + ) + + # Plot the analytic profile and test_he_sphere.dat profile + ax.plot(he_sphere_R, he_sphere_var, label="test_he_sphere.dat") + ax.plot( + he_sphere_R, + analytic_interp, + label="Analytic Interpolation", + linestyle="--", + ) ax.set_ylabel(label) ax.set_xscale("log") ax.set_yscale("log") - ax = axes[1,1] + ax = axes[1, 1] for include_g in gs.keys(): - ax.plot(R,gs[include_g].in_units("code_length*code_time**-2"),label=include_g,linestyle=":") - - axes[0,0].legend() + ax.plot( + R, + gs[include_g].in_units("code_length*code_time**-2"), + label=include_g, + linestyle=":", + ) + + axes[0, 0].legend() plt.tight_layout() plt.savefig(f"{parameters.output_path}/analytic_comparison.png") - #Check that the initial and final outputs match - sys.path.insert(1, parameters.parthenon_path + '/scripts/python/packages/parthenon_tools/parthenon_tools') + # Check that the initial and final outputs match + sys.path.insert( + 1, + parameters.parthenon_path + + "/scripts/python/packages/parthenon_tools/parthenon_tools", + ) try: import phdf_diff @@ -340,38 +430,44 @@ def rk4(f,y0,T): files = [ f"{parameters.output_path}/parthenon.prim.00000.phdf", - f"{parameters.output_path}/parthenon.prim.final.phdf" + f"{parameters.output_path}/parthenon.prim.final.phdf", ] - #Compare the initial output to the analytic model - def analytic_gold(Z,Y,X,analytic_var): + # Compare the initial output to the analytic model + def analytic_gold(Z, Y, X, analytic_var): r = np.sqrt(X**2 + Y**2 + Z**2) - analytic_interp = unyt.unyt_array(np.interp(r,analytic_R,analytic_var), - analytic_var.units) + analytic_interp = unyt.unyt_array( + np.interp(r, analytic_R, analytic_var), analytic_var.units + ) return analytic_interp analytic_components = { - "Pressure":lambda Z,Y,X,time : analytic_gold(Z,Y,X,analytic_P).v, - "Density":lambda Z,Y,X,time : analytic_gold(Z,Y,X,analytic_rho).v, - } + "Pressure": lambda Z, Y, X, time: analytic_gold(Z, Y, X, analytic_P).v, + "Density": lambda Z, Y, X, time: analytic_gold(Z, Y, X, analytic_rho).v, + } - #Use a very loose tolerance, linf relative error + # Use a very loose tolerance, linf relative error analytic_status = compare_analytic.compare_analytic( - files[0], analytic_components, - err_func=lambda gold,test:compare_analytic.norm_err_func(gold, test, - norm_ord=np.inf, relative=True, ignore_gold_zero=False),tol=1e-1) + files[0], + analytic_components, + err_func=lambda gold, test: compare_analytic.norm_err_func( + gold, test, norm_ord=np.inf, relative=True, ignore_gold_zero=False + ), + tol=1e-1, + ) - print(analytic_status,analyze_status) + print(analytic_status, analyze_status) - analyze_status &= (analytic_status) + analyze_status &= analytic_status - #Due to HSE, initial and final outputs should match, within a loose tolerance - compare_status = phdf_diff.compare(files,check_metadata=False,tol=5e-2, - relative=True,quiet=False,one=True) + # Due to HSE, initial and final outputs should match, within a loose tolerance + compare_status = phdf_diff.compare( + files, check_metadata=False, tol=5e-2, relative=True, quiet=False, one=True + ) - if(compare_status != 0): + if compare_status != 0: print("ERROR: initial and final outputs differ by too much") - analyze_status &= (compare_status==0) + analyze_status &= compare_status == 0 return analyze_status diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 12182535..dfb11387 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -1,8 +1,8 @@ -#======================================================================================== +# ======================================================================================== # AthenaPK - a performance portable block structured AMR MHD code # Copyright (c) 2020-2021, Athena Parthenon Collaboration. All rights reserved. # Licensed under the 3-clause BSD License, see LICENSE file for details -#======================================================================================== +# ======================================================================================== # (C) (or copyright) 2020. Triad National Security, LLC. All rights reserved. # # This program was produced under U.S. Government contract 89233218CNA000001 for Los @@ -13,13 +13,14 @@ # itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide # license in this material to reproduce, prepare derivative works, distribute copies to # the public, perform publicly and display publicly, and to permit others to do so. -#======================================================================================== +# ======================================================================================== # Modules import math import numpy as np import matplotlib -matplotlib.use('agg') + +matplotlib.use("agg") import matplotlib.pylab as plt import sys import os @@ -29,29 +30,34 @@ class PrecessedJetCoords: - def __init__(self,theta,phi): + def __init__(self, theta, phi): self.theta = theta self.phi = phi - #Axis of the jet - self.jet_n = np.array((np.cos(self.theta)*np.sin(self.phi), - np.sin(self.theta)*np.sin(self.phi), - np.cos(self.phi))) - - def cart_to_rho_h(self,pos_cart): + # Axis of the jet + self.jet_n = np.array( + ( + np.cos(self.theta) * np.sin(self.phi), + np.sin(self.theta) * np.sin(self.phi), + np.cos(self.phi), + ) + ) + + def cart_to_rho_h(self, pos_cart): """ Convert from cartesian coordinates to jet coordinates """ - pos_h = np.sum(pos_cart*self.jet_n[:,None],axis=0) - pos_rho = np.linalg.norm( pos_cart - pos_h*self.jet_n[:,None],axis=0) - return (pos_rho,pos_h) + pos_h = np.sum(pos_cart * self.jet_n[:, None], axis=0) + pos_rho = np.linalg.norm(pos_cart - pos_h * self.jet_n[:, None], axis=0) + return (pos_rho, pos_h) + class ZJetCoords: def __init__(self): - self.jet_n = np.array((0,0,1.0)) + self.jet_n = np.array((0, 0, 1.0)) - def cart_to_rho_h(self,pos_cart): + def cart_to_rho_h(self, pos_cart): """ Convert from cartesian coordinates to jet coordinates """ @@ -59,68 +65,75 @@ def cart_to_rho_h(self,pos_cart): pos_rho = np.linalg.norm(pos_cart[:2]) pos_h = pos_cart[2] - return pos_rho,pos_h + return pos_rho, pos_h + """ To prevent littering up imported folders with .pyc files or __pycache_ folder""" sys.dont_write_bytecode = True + class TestCase(utils.test_case.TestCaseAbs): def __init__(self): - #Define cluster parameters - #Setup units - unyt.define_unit("code_length",(1,"Mpc")) - unyt.define_unit("code_mass",(1e14,"Msun")) - unyt.define_unit("code_time",(1,"Gyr")) - self.code_length = unyt.unyt_quantity(1,"code_length") - self.code_mass = unyt.unyt_quantity(1,"code_mass") - self.code_time = unyt.unyt_quantity(1,"code_time") + # Define cluster parameters + # Setup units + unyt.define_unit("code_length", (1, "Mpc")) + unyt.define_unit("code_mass", (1e14, "Msun")) + unyt.define_unit("code_time", (1, "Gyr")) + self.code_length = unyt.unyt_quantity(1, "code_length") + self.code_mass = unyt.unyt_quantity(1, "code_mass") + self.code_time = unyt.unyt_quantity(1, "code_time") - self.tlim = unyt.unyt_quantity(5e-3,"code_time") + self.tlim = unyt.unyt_quantity(5e-3, "code_time") - #Setup constants + # Setup constants self.k_b = unyt.kb_cgs self.G = unyt.G_cgs - self.m_u =unyt.amu + self.m_u = unyt.amu - self.adiabatic_index = 5./3. + self.adiabatic_index = 5.0 / 3.0 self.He_mass_fraction = 0.25 - #Define the initial uniform gas - self.uniform_gas_rho = unyt.unyt_quantity(1e-24,"g/cm**3") - self.uniform_gas_ux = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_uy = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_uz = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_pres = unyt.unyt_quantity(1e-10,"dyne/cm**2") - - self.uniform_gas_Mx = self.uniform_gas_rho*self.uniform_gas_ux - self.uniform_gas_My = self.uniform_gas_rho*self.uniform_gas_uy - self.uniform_gas_Mz = self.uniform_gas_rho*self.uniform_gas_uz - self.uniform_gas_energy_density = \ - 1./2.*self.uniform_gas_rho*(self.uniform_gas_ux**2 + self.uniform_gas_uy**2 + self.uniform_gas_uz**2) \ - + self.uniform_gas_pres/(self.adiabatic_index - 1.) - - #The precessing jet + # Define the initial uniform gas + self.uniform_gas_rho = unyt.unyt_quantity(1e-24, "g/cm**3") + self.uniform_gas_ux = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_uy = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_uz = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_pres = unyt.unyt_quantity(1e-10, "dyne/cm**2") + + self.uniform_gas_Mx = self.uniform_gas_rho * self.uniform_gas_ux + self.uniform_gas_My = self.uniform_gas_rho * self.uniform_gas_uy + self.uniform_gas_Mz = self.uniform_gas_rho * self.uniform_gas_uz + self.uniform_gas_energy_density = 1.0 / 2.0 * self.uniform_gas_rho * ( + self.uniform_gas_ux**2 + + self.uniform_gas_uy**2 + + self.uniform_gas_uz**2 + ) + self.uniform_gas_pres / (self.adiabatic_index - 1.0) + + # The precessing jet self.jet_phi = 0.2 self.jet_theta_dot = 0 self.jet_theta0 = 1 - self.precessed_jet_coords = PrecessedJetCoords(self.jet_theta0,self.jet_phi) + self.precessed_jet_coords = PrecessedJetCoords(self.jet_theta0, self.jet_phi) self.zjet_coords = ZJetCoords() - #Feedback parameters - self.fixed_power = unyt.unyt_quantity(1e44,"erg/s") - self.agn_thermal_radius = unyt.unyt_quantity(0.5,"kpc") + # Feedback parameters + self.fixed_power = unyt.unyt_quantity(1e44, "erg/s") + self.agn_thermal_radius = unyt.unyt_quantity(0.5, "kpc") self.efficiency = 1.0e-3 - self.agn_jet_radius = unyt.unyt_quantity(0.25,"kpc") - self.agn_jet_height = unyt.unyt_quantity(1,"kpc") - + self.agn_jet_radius = unyt.unyt_quantity(0.25, "kpc") + self.agn_jet_height = unyt.unyt_quantity(1, "kpc") + self.norm_tol = 1e-3 self.steps = 4 - self.step_params_list = list(itertools.product( - ("thermal_only","kinetic_only","combined"),(True,False))) + self.step_params_list = list( + itertools.product( + ("thermal_only", "kinetic_only", "combined"), (True, False) + ) + ) - def Prepare(self,parameters, step): + def Prepare(self, parameters, step): """ Any preprocessing that is needed before the drive is run can be done in this method @@ -128,7 +141,7 @@ def Prepare(self,parameters, step): This includes preparing files or any other pre processing steps that need to be implemented. The method also provides access to the parameters object which controls which parameters are being used to run - the driver. + the driver. It is possible to append arguments to the driver_cmd_line_args if it is desired to override the parthenon input file. Each element in the list @@ -144,7 +157,7 @@ def Prepare(self,parameters, step): 'time/tlim=0.4', 'mesh/nx1=400'] """ - feedback_mode,precessed_jet = self.step_params_list[step-1] + feedback_mode, precessed_jet = self.step_params_list[step - 1] output_id = f"{feedback_mode}_precessed_{precessed_jet}" if feedback_mode == "thermal_only": @@ -159,51 +172,44 @@ def Prepare(self,parameters, step): else: raise Exception(f"Feedback mode {feedback_mode} not supported in analysis") - - parameters.driver_cmd_line_args = [ - f"parthenon/output2/id={output_id}", - f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", - f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", - f"hydro/gamma={self.adiabatic_index}", - f"hydro/He_mass_fraction={self.He_mass_fraction}", - - f"units/code_length_cgs={self.code_length.in_units('cm').v}", - f"units/code_mass_cgs={self.code_mass.in_units('g').v}", - f"units/code_time_cgs={self.code_time.in_units('s').v}", - - f"problem/cluster/uniform_gas/init_uniform_gas=true", - f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", - f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", - - f"problem/cluster/precessing_jet/jet_phi={self.jet_phi if precessed_jet else 0}", - f"problem/cluster/precessing_jet/jet_theta_dot={self.jet_theta_dot if precessed_jet else 0}", - f"problem/cluster/precessing_jet/jet_theta0={self.jet_theta0 if precessed_jet else 0}", - - f"problem/cluster/agn_feedback/fixed_power={self.fixed_power.in_units('code_mass*code_length**2/code_time**3').v}", - f"problem/cluster/agn_feedback/efficiency={self.efficiency}", - f"problem/cluster/agn_feedback/thermal_fraction={agn_thermal_fraction}", - f"problem/cluster/agn_feedback/kinetic_fraction={agn_kinetic_fraction}", - f"problem/cluster/agn_feedback/magnetic_fraction=0", - f"problem/cluster/agn_feedback/thermal_radius={self.agn_thermal_radius.in_units('code_length').v}", - f"problem/cluster/agn_feedback/kinetic_jet_radius={self.agn_jet_radius.in_units('code_length').v}", - f"problem/cluster/agn_feedback/kinetic_jet_height={self.agn_jet_height.in_units('code_length').v}", - ] - + f"parthenon/output2/id={output_id}", + f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", + f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", + f"hydro/gamma={self.adiabatic_index}", + f"hydro/He_mass_fraction={self.He_mass_fraction}", + f"units/code_length_cgs={self.code_length.in_units('cm').v}", + f"units/code_mass_cgs={self.code_mass.in_units('g').v}", + f"units/code_time_cgs={self.code_time.in_units('s').v}", + f"problem/cluster/uniform_gas/init_uniform_gas=true", + f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", + f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", + f"problem/cluster/precessing_jet/jet_phi={self.jet_phi if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_theta_dot={self.jet_theta_dot if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_theta0={self.jet_theta0 if precessed_jet else 0}", + f"problem/cluster/agn_feedback/fixed_power={self.fixed_power.in_units('code_mass*code_length**2/code_time**3').v}", + f"problem/cluster/agn_feedback/efficiency={self.efficiency}", + f"problem/cluster/agn_feedback/thermal_fraction={agn_thermal_fraction}", + f"problem/cluster/agn_feedback/kinetic_fraction={agn_kinetic_fraction}", + f"problem/cluster/agn_feedback/magnetic_fraction=0", + f"problem/cluster/agn_feedback/thermal_radius={self.agn_thermal_radius.in_units('code_length').v}", + f"problem/cluster/agn_feedback/kinetic_jet_radius={self.agn_jet_radius.in_units('code_length').v}", + f"problem/cluster/agn_feedback/kinetic_jet_height={self.agn_jet_height.in_units('code_length').v}", + ] return parameters - def Analyse(self,parameters): + def Analyse(self, parameters): """ Analyze the output and determine if the test passes. This function is called after the driver has been executed. It is responsible for reading whatever data it needs and making a judgment about whether or not the test passes. It takes no inputs. Output should - be True (test passes) or False (test fails). + be True (test passes) or False (test fails). The parameters that are passed in provide the paths to relevant locations and commands. Of particular importance is the path to the @@ -218,12 +224,11 @@ def Analyse(self,parameters): analyze_status = True self.Yp = self.He_mass_fraction - self.mu = 1/(self.Yp*3./4. + (1-self.Yp)*2) - self.mu_e = 1/(self.Yp*2./4. + (1-self.Yp)) - + self.mu = 1 / (self.Yp * 3.0 / 4.0 + (1 - self.Yp) * 2) + self.mu_e = 1 / (self.Yp * 2.0 / 4.0 + (1 - self.Yp)) - for step in range(1,self.steps+1): - feedback_mode,precessed_jet = self.step_params_list[step-1] + for step in range(1, self.steps + 1): + feedback_mode, precessed_jet = self.step_params_list[step - 1] output_id = f"{feedback_mode}_precessed_{precessed_jet}" step_status = True @@ -244,59 +249,134 @@ def Analyse(self,parameters): agn_kinetic_fraction = 0.5 agn_thermal_fraction = 0.5 else: - raise Exception(f"Feedback mode {feedback_mode} not supported in analysis") - - jet_density = (agn_kinetic_fraction*self.fixed_power)/(self.efficiency*unyt.c_cgs**2)/( - 2*np.pi*self.agn_jet_radius**2*self.agn_jet_height) - - jet_velocity = np.sqrt( 2*self.efficiency)*unyt.c_cgs - - def kinetic_feedback(Z,Y,X,time): - if not hasattr(time,"units"): - time = unyt.unyt_quantity(time,"code_time") - R,H = jet_coords.cart_to_rho_h(np.array((X,Y,Z))) - R = unyt.unyt_array(R,"code_length") - H = unyt.unyt_array(H,"code_length") - - sign_jet = np.piecewise(H,[H <=0, H > 0],[-1,1]).v - inside_jet = (np.piecewise(R,[ R <= self.agn_jet_radius,],[1,0]) \ - *np.piecewise(H,[ np.abs(H) <= self.agn_jet_height,],[1,0])).v - - drho = inside_jet*agn_kinetic_fraction*time*jet_density - dMx = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[0] - dMy = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[1] - dMz = inside_jet*agn_kinetic_fraction*time*sign_jet*jet_density*jet_velocity*jet_coords.jet_n[2] - dE = inside_jet*agn_kinetic_fraction*time*0.5*jet_density*jet_velocity**2 - - return drho,dMx,dMy,dMz,dE - - def thermal_feedback(Z,Y,X,time): - if not hasattr(time,"units"): - time = unyt.unyt_quantity(time,"code_time") - R = np.sqrt( X**2 + Y**2 + Z**2) - inside_sphere = np.piecewise(R,[ R <= self.agn_thermal_radius.in_units("code_length"),],[1,0]) - dE = inside_sphere*time*(self.fixed_power*agn_thermal_fraction/(4./3.*np.pi*self.agn_thermal_radius**3)) - - drho = inside_sphere*time*(self.fixed_power/(self.efficiency*unyt.c_cgs**2)*agn_thermal_fraction/(4./3.*np.pi*self.agn_thermal_radius**3)) - #Assume no velocity, no change in momentum with mass injection - return drho,dE - - - def agn_feedback(Z,Y,X,dt): - drho_k,dMx_k,dMy_k,dMz_k,dE_k = kinetic_feedback(Z,Y,X,dt) - drho_t,dE_t = thermal_feedback(Z,Y,X,dt) + raise Exception( + f"Feedback mode {feedback_mode} not supported in analysis" + ) + + jet_density = ( + (agn_kinetic_fraction * self.fixed_power) + / (self.efficiency * unyt.c_cgs**2) + / (2 * np.pi * self.agn_jet_radius**2 * self.agn_jet_height) + ) + + jet_velocity = np.sqrt(2 * self.efficiency) * unyt.c_cgs + + def kinetic_feedback(Z, Y, X, time): + if not hasattr(time, "units"): + time = unyt.unyt_quantity(time, "code_time") + R, H = jet_coords.cart_to_rho_h(np.array((X, Y, Z))) + R = unyt.unyt_array(R, "code_length") + H = unyt.unyt_array(H, "code_length") + + sign_jet = np.piecewise(H, [H <= 0, H > 0], [-1, 1]).v + inside_jet = ( + np.piecewise( + R, + [ + R <= self.agn_jet_radius, + ], + [1, 0], + ) + * np.piecewise( + H, + [ + np.abs(H) <= self.agn_jet_height, + ], + [1, 0], + ) + ).v + + drho = inside_jet * agn_kinetic_fraction * time * jet_density + dMx = ( + inside_jet + * agn_kinetic_fraction + * time + * sign_jet + * jet_density + * jet_velocity + * jet_coords.jet_n[0] + ) + dMy = ( + inside_jet + * agn_kinetic_fraction + * time + * sign_jet + * jet_density + * jet_velocity + * jet_coords.jet_n[1] + ) + dMz = ( + inside_jet + * agn_kinetic_fraction + * time + * sign_jet + * jet_density + * jet_velocity + * jet_coords.jet_n[2] + ) + dE = ( + inside_jet + * agn_kinetic_fraction + * time + * 0.5 + * jet_density + * jet_velocity**2 + ) + + return drho, dMx, dMy, dMz, dE + + def thermal_feedback(Z, Y, X, time): + if not hasattr(time, "units"): + time = unyt.unyt_quantity(time, "code_time") + R = np.sqrt(X**2 + Y**2 + Z**2) + inside_sphere = np.piecewise( + R, + [ + R <= self.agn_thermal_radius.in_units("code_length"), + ], + [1, 0], + ) + dE = ( + inside_sphere + * time + * ( + self.fixed_power + * agn_thermal_fraction + / (4.0 / 3.0 * np.pi * self.agn_thermal_radius**3) + ) + ) + + drho = ( + inside_sphere + * time + * ( + self.fixed_power + / (self.efficiency * unyt.c_cgs**2) + * agn_thermal_fraction + / (4.0 / 3.0 * np.pi * self.agn_thermal_radius**3) + ) + ) + # Assume no velocity, no change in momentum with mass injection + return drho, dE + + def agn_feedback(Z, Y, X, dt): + drho_k, dMx_k, dMy_k, dMz_k, dE_k = kinetic_feedback(Z, Y, X, dt) + drho_t, dE_t = thermal_feedback(Z, Y, X, dt) drho = drho_k + drho_t - dMx = dMx_k - dMy = dMx_k - dMz = dMx_k - dE = dE_k + dE_t - - return drho,dMx,dMy,dMz,dE + dMx = dMx_k + dMy = dMx_k + dMz = dMx_k + dE = dE_k + dE_t + return drho, dMx, dMy, dMz, dE - #Check that the initial and final outputs match the expected tower - sys.path.insert(1, parameters.parthenon_path + '/scripts/python/packages/parthenon_tools/parthenon_tools') + # Check that the initial and final outputs match the expected tower + sys.path.insert( + 1, + parameters.parthenon_path + + "/scripts/python/packages/parthenon_tools/parthenon_tools", + ) try: import compare_analytic @@ -305,50 +385,86 @@ def agn_feedback(Z,Y,X,dt): return False initial_analytic_components = { - "Density":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_rho.in_units("code_mass/code_length**3").v, - "MomentumDensity1":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_Mx.in_units("code_mass*code_length**-2*code_time**-1").v, - "MomentumDensity2":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_My.in_units("code_mass*code_length**-2*code_time**-1").v, - "MomentumDensity3":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_Mz.in_units("code_mass*code_length**-2*code_time**-1").v, - "TotalEnergyDensity":lambda Z,Y,X,time : - np.ones_like(Z)*self.uniform_gas_energy_density.in_units("code_mass*code_length**-1*code_time**-2").v, - } + "Density": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_rho.in_units("code_mass/code_length**3").v, + "MomentumDensity1": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_Mx.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "MomentumDensity2": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_My.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "MomentumDensity3": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_Mz.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "TotalEnergyDensity": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_energy_density.in_units( + "code_mass*code_length**-1*code_time**-2" + ).v, + } final_analytic_components = { - "Density":lambda Z,Y,X,time : - ( self.uniform_gas_rho + agn_feedback(Z,Y,X,time)[0]).in_units("code_mass/code_length**3").v, - "MomentumDensity1":lambda Z,Y,X,time : - ( self.uniform_gas_Mx + agn_feedback(Z,Y,X,time)[1]).in_units("code_mass*code_length**-2*code_time**-1").v, - "MomentumDensity2":lambda Z,Y,X,time : - ( self.uniform_gas_Mx + agn_feedback(Z,Y,X,time)[2]).in_units("code_mass*code_length**-2*code_time**-1").v, - "MomentumDensity3":lambda Z,Y,X,time : - ( self.uniform_gas_Mx + agn_feedback(Z,Y,X,time)[3]).in_units("code_mass*code_length**-2*code_time**-1").v, - "TotalEnergyDensity":lambda Z,Y,X,time : - (self.uniform_gas_energy_density + agn_feedback(Z,Y,X,time)[4]).in_units("code_mass*code_length**-1*code_time**-2").v, - } - - phdf_files = [f"{parameters.output_path}/parthenon.{output_id}.00000.phdf",f"{parameters.output_path}/parthenon.{output_id}.final.phdf"] - - def zero_corrected_linf_err(gold,test): - non_zero_linf = np.max(np.abs((gold[gold!=0]-test[gold!=0])/gold[gold!=0]),initial=0) - zero_linf = np.max(np.abs((gold[gold==0]-test[gold==0])),initial=0) - - return np.max((non_zero_linf,zero_linf)) - - #Use a very loose tolerance, linf relative error - initial_analytic_status,final_analytic_status = [ + "Density": lambda Z, Y, X, time: ( + self.uniform_gas_rho + agn_feedback(Z, Y, X, time)[0] + ) + .in_units("code_mass/code_length**3") + .v, + "MomentumDensity1": lambda Z, Y, X, time: ( + self.uniform_gas_Mx + agn_feedback(Z, Y, X, time)[1] + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "MomentumDensity2": lambda Z, Y, X, time: ( + self.uniform_gas_Mx + agn_feedback(Z, Y, X, time)[2] + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "MomentumDensity3": lambda Z, Y, X, time: ( + self.uniform_gas_Mx + agn_feedback(Z, Y, X, time)[3] + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "TotalEnergyDensity": lambda Z, Y, X, time: ( + self.uniform_gas_energy_density + agn_feedback(Z, Y, X, time)[4] + ) + .in_units("code_mass*code_length**-1*code_time**-2") + .v, + } + + phdf_files = [ + f"{parameters.output_path}/parthenon.{output_id}.00000.phdf", + f"{parameters.output_path}/parthenon.{output_id}.final.phdf", + ] + + def zero_corrected_linf_err(gold, test): + non_zero_linf = np.max( + np.abs((gold[gold != 0] - test[gold != 0]) / gold[gold != 0]), + initial=0, + ) + zero_linf = np.max( + np.abs((gold[gold == 0] - test[gold == 0])), initial=0 + ) + + return np.max((non_zero_linf, zero_linf)) + + # Use a very loose tolerance, linf relative error + initial_analytic_status, final_analytic_status = [ compare_analytic.compare_analytic( - phdf_file, analytic_components,err_func=zero_corrected_linf_err,tol=1e-3) - for analytic_components,phdf_file in zip((initial_analytic_components,final_analytic_components), - phdf_files)] + phdf_file, + analytic_components, + err_func=zero_corrected_linf_err, + tol=1e-3, + ) + for analytic_components, phdf_file in zip( + (initial_analytic_components, final_analytic_components), phdf_files + ) + ] - print(" Initial analytic status",initial_analytic_status) - print(" Final analytic status",final_analytic_status) + print(" Initial analytic status", initial_analytic_status) + print(" Final analytic status", final_analytic_status) analyze_status &= initial_analytic_status & final_analytic_status - return analyze_status diff --git a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py index b3ab25d9..dbba642c 100644 --- a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py +++ b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py @@ -1,8 +1,8 @@ -#======================================================================================== +# ======================================================================================== # AthenaPK - a performance portable block structured AMR MHD code # Copyright (c) 2020-2021, Athena Parthenon Collaboration. All rights reserved. # Licensed under the 3-clause BSD License, see LICENSE file for details -#======================================================================================== +# ======================================================================================== # (C) (or copyright) 2020. Triad National Security, LLC. All rights reserved. # # This program was produced under U.S. Government contract 89233218CNA000001 for Los @@ -13,13 +13,14 @@ # itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide # license in this material to reproduce, prepare derivative works, distribute copies to # the public, perform publicly and display publicly, and to permit others to do so. -#======================================================================================== +# ======================================================================================== # Modules import math import numpy as np import matplotlib -matplotlib.use('agg') + +matplotlib.use("agg") import matplotlib.pylab as plt import sys import os @@ -29,12 +30,12 @@ class PrecessedJetCoords: - #Note: Does note rotate the vector around the jet axis, only rotates the vector to `z_hat` - def __init__(self,phi_jet,theta_jet): + # Note: Does note rotate the vector around the jet axis, only rotates the vector to `z_hat` + def __init__(self, phi_jet, theta_jet): self.phi_jet = phi_jet self.theta_jet = theta_jet - def cart_to_jet_coords(self,pos_sim): + def cart_to_jet_coords(self, pos_sim): """ Convert from simulation cartesian coordinates to jet cylindrical coordinates """ @@ -43,169 +44,207 @@ def cart_to_jet_coords(self,pos_sim): y_sim = pos_sim[1] z_sim = pos_sim[2] - x_jet = x_sim*np.cos(self.phi_jet)*np.cos(self.theta_jet) + y_sim*np.sin(self.phi_jet) - z_sim*np.sin(self.theta_jet)*np.cos(self.phi_jet) - y_jet = -x_sim*np.sin(self.phi_jet)*np.cos(self.theta_jet) + y_sim*np.cos(self.phi_jet) + z_sim*np.sin(self.phi_jet)*np.sin(self.theta_jet) - z_jet = x_sim*np.sin(self.theta_jet) + z_sim*np.cos(self.theta_jet) - - r_jet = np.sqrt( x_jet**2 + y_jet**2) - theta_jet = np.arctan2(y_jet,x_jet) + x_jet = ( + x_sim * np.cos(self.phi_jet) * np.cos(self.theta_jet) + + y_sim * np.sin(self.phi_jet) + - z_sim * np.sin(self.theta_jet) * np.cos(self.phi_jet) + ) + y_jet = ( + -x_sim * np.sin(self.phi_jet) * np.cos(self.theta_jet) + + y_sim * np.cos(self.phi_jet) + + z_sim * np.sin(self.phi_jet) * np.sin(self.theta_jet) + ) + z_jet = x_sim * np.sin(self.theta_jet) + z_sim * np.cos(self.theta_jet) + + r_jet = np.sqrt(x_jet**2 + y_jet**2) + theta_jet = np.arctan2(y_jet, x_jet) h_jet = z_jet return (r_jet, theta_jet, h_jet) - def jet_to_cart_vec(self,pos_sim,vec_jet): + def jet_to_cart_vec(self, pos_sim, vec_jet): r_pos, theta_pos, h_pos = self.cart_to_jet_coords(pos_sim) - #Convert jet-cylindrical vec_jet into jet-cartesian - R = np.array((( np.cos(theta_pos),-np.sin(theta_pos), np.zeros_like(theta_pos)), - ( np.sin(theta_pos), np.cos(theta_pos), np.zeros_like(theta_pos)), - ( np.zeros_like(theta_pos),np.zeros_like(theta_pos), np.ones_like(theta_pos)))).reshape( - (3,3,*(theta_pos.shape))) - #vec_jet_cart = np.einsum("ij,jxyz",R,vec_jet) - vec_jet_cart = unyt.unyt_array((R[0,0]*vec_jet[0] + R[0,1]*vec_jet[1] + R[0,2]*vec_jet[2], - R[1,0]*vec_jet[0] + R[1,1]*vec_jet[1] + R[1,2]*vec_jet[2], - R[2,0]*vec_jet[0] + R[2,1]*vec_jet[1] + R[2,2]*vec_jet[2]), - vec_jet.units) - - - R = np.array(((np.cos(self.phi_jet)*np.cos(self.theta_jet), - -np.sin(self.phi_jet)*np.cos(self.theta_jet), - np.sin(self.theta_jet)), - - (np.sin(self.phi_jet), - np.cos(self.phi_jet), - 0), - - (-np.sin(self.theta_jet)*np.cos(self.phi_jet), - np.sin(self.phi_jet)*np.sin(self.theta_jet), - np.cos(self.theta_jet)) - )) - - #vec_cart = np.matmul(R,vec_jet_cart) - vec_cart = unyt.unyt_array((R[0,0]*vec_jet_cart[0] + R[0,1]*vec_jet_cart[1] + R[0,2]*vec_jet_cart[2], - R[1,0]*vec_jet_cart[0] + R[1,1]*vec_jet_cart[1] + R[1,2]*vec_jet_cart[2], - R[2,0]*vec_jet_cart[0] + R[2,1]*vec_jet_cart[1] + R[2,2]*vec_jet_cart[2]), - vec_jet_cart.units) + # Convert jet-cylindrical vec_jet into jet-cartesian + R = np.array( + ( + (np.cos(theta_pos), -np.sin(theta_pos), np.zeros_like(theta_pos)), + (np.sin(theta_pos), np.cos(theta_pos), np.zeros_like(theta_pos)), + ( + np.zeros_like(theta_pos), + np.zeros_like(theta_pos), + np.ones_like(theta_pos), + ), + ) + ).reshape((3, 3, *(theta_pos.shape))) + # vec_jet_cart = np.einsum("ij,jxyz",R,vec_jet) + vec_jet_cart = unyt.unyt_array( + ( + R[0, 0] * vec_jet[0] + R[0, 1] * vec_jet[1] + R[0, 2] * vec_jet[2], + R[1, 0] * vec_jet[0] + R[1, 1] * vec_jet[1] + R[1, 2] * vec_jet[2], + R[2, 0] * vec_jet[0] + R[2, 1] * vec_jet[1] + R[2, 2] * vec_jet[2], + ), + vec_jet.units, + ) + + R = np.array( + ( + ( + np.cos(self.phi_jet) * np.cos(self.theta_jet), + -np.sin(self.phi_jet) * np.cos(self.theta_jet), + np.sin(self.theta_jet), + ), + (np.sin(self.phi_jet), np.cos(self.phi_jet), 0), + ( + -np.sin(self.theta_jet) * np.cos(self.phi_jet), + np.sin(self.phi_jet) * np.sin(self.theta_jet), + np.cos(self.theta_jet), + ), + ) + ) + + # vec_cart = np.matmul(R,vec_jet_cart) + vec_cart = unyt.unyt_array( + ( + R[0, 0] * vec_jet_cart[0] + + R[0, 1] * vec_jet_cart[1] + + R[0, 2] * vec_jet_cart[2], + R[1, 0] * vec_jet_cart[0] + + R[1, 1] * vec_jet_cart[1] + + R[1, 2] * vec_jet_cart[2], + R[2, 0] * vec_jet_cart[0] + + R[2, 1] * vec_jet_cart[1] + + R[2, 2] * vec_jet_cart[2], + ), + vec_jet_cart.units, + ) return vec_cart + class ZJetCoords: def __init__(self): pass - def cart_to_jet_coords(self,pos_cart): + def cart_to_jet_coords(self, pos_cart): """ Convert from cartesian coordinates to jet coordinates """ - pos_rho = np.sqrt(pos_cart[0]**2 + pos_cart[1]**2) - pos_theta = np.arctan2(pos_cart[1],pos_cart[0]) + pos_rho = np.sqrt(pos_cart[0] ** 2 + pos_cart[1] ** 2) + pos_theta = np.arctan2(pos_cart[1], pos_cart[0]) pos_theta[pos_rho == 0] = 0 pos_h = pos_cart[2] - return (pos_rho,pos_theta,pos_h) + return (pos_rho, pos_theta, pos_h) + + def jet_to_cart_vec(self, pos_cart, vec_jet): - def jet_to_cart_vec(self,pos_cart,vec_jet): - vec_rho = vec_jet[0] vec_theta = vec_jet[1] vec_h = vec_jet[2] - r_pos,theta_pos,h_pos = self.cart_to_jet_coords(pos_cart) - - #Compute vector in cartesian coords - vec_x = vec_rho*np.cos(theta_pos) - vec_theta*np.sin(theta_pos) - vec_y = vec_rho*np.sin(theta_pos) + vec_theta*np.cos(theta_pos) + r_pos, theta_pos, h_pos = self.cart_to_jet_coords(pos_cart) + + # Compute vector in cartesian coords + vec_x = vec_rho * np.cos(theta_pos) - vec_theta * np.sin(theta_pos) + vec_y = vec_rho * np.sin(theta_pos) + vec_theta * np.cos(theta_pos) vec_z = vec_h - - return (vec_x,vec_y,vec_z) + + return (vec_x, vec_y, vec_z) + """ To prevent littering up imported folders with .pyc files or __pycache_ folder""" sys.dont_write_bytecode = True + class TestCase(utils.test_case.TestCaseAbs): def __init__(self): - #Define cluster parameters - #Setup units - unyt.define_unit("code_length",(1,"Mpc")) - unyt.define_unit("code_mass",(1e14,"Msun")) - unyt.define_unit("code_time",(1,"Gyr")) - self.code_length = unyt.unyt_quantity(1,"code_length") - self.code_mass = unyt.unyt_quantity(1,"code_mass") - self.code_time = unyt.unyt_quantity(1,"code_time") + # Define cluster parameters + # Setup units + unyt.define_unit("code_length", (1, "Mpc")) + unyt.define_unit("code_mass", (1e14, "Msun")) + unyt.define_unit("code_time", (1, "Gyr")) + self.code_length = unyt.unyt_quantity(1, "code_length") + self.code_mass = unyt.unyt_quantity(1, "code_mass") + self.code_time = unyt.unyt_quantity(1, "code_time") - self.tlim = unyt.unyt_quantity(1e-2,"code_time") + self.tlim = unyt.unyt_quantity(1e-2, "code_time") - #Setup constants + # Setup constants self.k_b = unyt.kb_cgs self.G = unyt.G_cgs - self.m_u =unyt.amu + self.m_u = unyt.amu - self.adiabatic_index = 5./3. + self.adiabatic_index = 5.0 / 3.0 self.He_mass_fraction = 0.25 - #Define the initial uniform gas - self.uniform_gas_rho = unyt.unyt_quantity(1e-24,"g/cm**3") - self.uniform_gas_ux = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_uy = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_uz = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_pres = unyt.unyt_quantity(1e-10,"dyne/cm**2") - - self.uniform_gas_Mx = self.uniform_gas_rho*self.uniform_gas_ux - self.uniform_gas_My = self.uniform_gas_rho*self.uniform_gas_uy - self.uniform_gas_Mz = self.uniform_gas_rho*self.uniform_gas_uz - self.uniform_gas_energy_density = \ - 1./2.*self.uniform_gas_rho*(self.uniform_gas_ux**2 + self.uniform_gas_uy**2 + self.uniform_gas_uz**2) \ - + self.uniform_gas_pres/(self.adiabatic_index - 1.) - - #Efficiency of power to accretion rate (controls rate of mass injection for this test) - #self.efficiency = 0 + # Define the initial uniform gas + self.uniform_gas_rho = unyt.unyt_quantity(1e-24, "g/cm**3") + self.uniform_gas_ux = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_uy = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_uz = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_pres = unyt.unyt_quantity(1e-10, "dyne/cm**2") + + self.uniform_gas_Mx = self.uniform_gas_rho * self.uniform_gas_ux + self.uniform_gas_My = self.uniform_gas_rho * self.uniform_gas_uy + self.uniform_gas_Mz = self.uniform_gas_rho * self.uniform_gas_uz + self.uniform_gas_energy_density = 1.0 / 2.0 * self.uniform_gas_rho * ( + self.uniform_gas_ux**2 + + self.uniform_gas_uy**2 + + self.uniform_gas_uz**2 + ) + self.uniform_gas_pres / (self.adiabatic_index - 1.0) + + # Efficiency of power to accretion rate (controls rate of mass injection for this test) + # self.efficiency = 0 self.efficiency = 1e-3 - #The precessing jet + # The precessing jet self.theta_jet = 0.2 - self.phi_dot_jet = 0 #Use phi_dot = 0 for stationary jet - self.phi_jet0 = 1 #Offset initial jet - self.precessed_jet_coords = PrecessedJetCoords(self.phi_jet0,self.theta_jet) + self.phi_dot_jet = 0 # Use phi_dot = 0 for stationary jet + self.phi_jet0 = 1 # Offset initial jet + self.precessed_jet_coords = PrecessedJetCoords(self.phi_jet0, self.theta_jet) self.zjet_coords = ZJetCoords() - - #Initial and Feedback shared parameters + # Initial and Feedback shared parameters self.magnetic_tower_alpha = 20 - #self.magnetic_tower_l_scale = unyt.unyt_quantity(1,"code_length") - self.magnetic_tower_l_scale = unyt.unyt_quantity(10,"kpc") - self.magnetic_tower_l_mass_scale = unyt.unyt_quantity(5,"kpc") - - #The Initial Tower - self.initial_magnetic_tower_field = unyt.unyt_quantity(1e-6,"G") - - #The Feedback Tower - #For const field tests - self.feedback_magnetic_tower_field = unyt.unyt_quantity(1e-4,"G/Gyr") - #For const energy tests - self.feedback_magnetic_tower_power = unyt.unyt_quantity(1e44,"erg/s") - #For const field tests - #self.feedback_magnetic_tower_mass = unyt.unyt_quantity(0,"g/s") - self.feedback_magnetic_tower_mass = self.feedback_magnetic_tower_power/( self.efficiency*unyt.c_cgs**2 ) + # self.magnetic_tower_l_scale = unyt.unyt_quantity(1,"code_length") + self.magnetic_tower_l_scale = unyt.unyt_quantity(10, "kpc") + self.magnetic_tower_l_mass_scale = unyt.unyt_quantity(5, "kpc") + + # The Initial Tower + self.initial_magnetic_tower_field = unyt.unyt_quantity(1e-6, "G") + + # The Feedback Tower + # For const field tests + self.feedback_magnetic_tower_field = unyt.unyt_quantity(1e-4, "G/Gyr") + # For const energy tests + self.feedback_magnetic_tower_power = unyt.unyt_quantity(1e44, "erg/s") + # For const field tests + # self.feedback_magnetic_tower_mass = unyt.unyt_quantity(0,"g/s") + self.feedback_magnetic_tower_mass = self.feedback_magnetic_tower_power / ( + self.efficiency * unyt.c_cgs**2 + ) self.energy_density_tol = 1e-2 - #Tolerance of linf error of magnetic fields, total energy density, and density + # Tolerance of linf error of magnetic fields, total energy density, and density self.linf_analytic_tol = 5e-2 - #Tolerance on total initial and final magnetic energy + # Tolerance on total initial and final magnetic energy self.b_eng_initial_tol = 1e-2 self.b_eng_final_tol = 1e-2 - #Tolerance in max divergence over magnetic tower field scale + # Tolerance in max divergence over magnetic tower field scale self.divB_tol = 1e-11 self.steps = 4 - self.step_params_list = list(itertools.product( ("const_field","const_power"),(True,False))) + self.step_params_list = list( + itertools.product(("const_field", "const_power"), (True, False)) + ) - - def Prepare(self,parameters, step): + def Prepare(self, parameters, step): """ Any preprocessing that is needed before the drive is run can be done in this method @@ -213,7 +252,7 @@ def Prepare(self,parameters, step): This includes preparing files or any other pre processing steps that need to be implemented. The method also provides access to the parameters object which controls which parameters are being used to run - the driver. + the driver. It is possible to append arguments to the driver_cmd_line_args if it is desired to override the parthenon input file. Each element in the list @@ -229,65 +268,65 @@ def Prepare(self,parameters, step): 'time/tlim=0.4', 'mesh/nx1=400'] """ - feedback_mode,precessed_jet = self.step_params_list[step-1] + feedback_mode, precessed_jet = self.step_params_list[step - 1] output_id = f"{feedback_mode}_precessed_{precessed_jet}" if feedback_mode == "const_power": - fixed_power = self.feedback_magnetic_tower_power.in_units('code_mass*code_length**2/code_time**3').v - fixed_field_rate = 0 + fixed_power = self.feedback_magnetic_tower_power.in_units( + "code_mass*code_length**2/code_time**3" + ).v + fixed_field_rate = 0 fixed_mass_rate = 0 else: fixed_power = 0 - fixed_field_rate = self.feedback_magnetic_tower_field.in_units('sqrt(code_mass)/sqrt(code_length)/code_time**2').v - fixed_mass_rate = self.feedback_magnetic_tower_mass.in_units("code_mass/code_time").v + fixed_field_rate = self.feedback_magnetic_tower_field.in_units( + "sqrt(code_mass)/sqrt(code_length)/code_time**2" + ).v + fixed_mass_rate = self.feedback_magnetic_tower_mass.in_units( + "code_mass/code_time" + ).v parameters.driver_cmd_line_args = [ - f"parthenon/output2/id={output_id}", - f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", - f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", - f"hydro/gamma={self.adiabatic_index}", - f"hydro/He_mass_fraction={self.He_mass_fraction}", - - f"units/code_length_cgs={self.code_length.in_units('cm').v}", - f"units/code_mass_cgs={self.code_mass.in_units('g').v}", - f"units/code_time_cgs={self.code_time.in_units('s').v}", - - f"problem/cluster/uniform_gas/init_uniform_gas=true", - f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", - f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", - - f"problem/cluster/precessing_jet/jet_theta={self.theta_jet if precessed_jet else 0}", - f"problem/cluster/precessing_jet/jet_phi_dot={self.phi_dot_jet if precessed_jet else 0}", - f"problem/cluster/precessing_jet/jet_phi0={self.phi_jet0 if precessed_jet else 0}", - - f"problem/cluster/agn_feedback/fixed_power={fixed_power}", - f"problem/cluster/agn_feedback/efficiency={self.efficiency}", - f"problem/cluster/agn_feedback/magnetic_fraction=1", - f"problem/cluster/agn_feedback/kinetic_fraction=0", - f"problem/cluster/agn_feedback/thermal_fraction=0", - - f"problem/cluster/magnetic_tower/alpha={self.magnetic_tower_alpha}", - f"problem/cluster/magnetic_tower/l_scale={self.magnetic_tower_l_scale.in_units('code_length').v}", - f"problem/cluster/magnetic_tower/initial_field={self.initial_magnetic_tower_field.in_units('sqrt(code_mass)/sqrt(code_length)/code_time').v}", - f"problem/cluster/magnetic_tower/fixed_field_rate={fixed_field_rate}", - f"problem/cluster/magnetic_tower/fixed_mass_rate={fixed_mass_rate}", - f"problem/cluster/magnetic_tower/l_mass_scale={self.magnetic_tower_l_mass_scale.in_units('code_length').v}", - ] - + f"parthenon/output2/id={output_id}", + f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", + f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", + f"hydro/gamma={self.adiabatic_index}", + f"hydro/He_mass_fraction={self.He_mass_fraction}", + f"units/code_length_cgs={self.code_length.in_units('cm').v}", + f"units/code_mass_cgs={self.code_mass.in_units('g').v}", + f"units/code_time_cgs={self.code_time.in_units('s').v}", + f"problem/cluster/uniform_gas/init_uniform_gas=true", + f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", + f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", + f"problem/cluster/precessing_jet/jet_theta={self.theta_jet if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_phi_dot={self.phi_dot_jet if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_phi0={self.phi_jet0 if precessed_jet else 0}", + f"problem/cluster/agn_feedback/fixed_power={fixed_power}", + f"problem/cluster/agn_feedback/efficiency={self.efficiency}", + f"problem/cluster/agn_feedback/magnetic_fraction=1", + f"problem/cluster/agn_feedback/kinetic_fraction=0", + f"problem/cluster/agn_feedback/thermal_fraction=0", + f"problem/cluster/magnetic_tower/alpha={self.magnetic_tower_alpha}", + f"problem/cluster/magnetic_tower/l_scale={self.magnetic_tower_l_scale.in_units('code_length').v}", + f"problem/cluster/magnetic_tower/initial_field={self.initial_magnetic_tower_field.in_units('sqrt(code_mass)/sqrt(code_length)/code_time').v}", + f"problem/cluster/magnetic_tower/fixed_field_rate={fixed_field_rate}", + f"problem/cluster/magnetic_tower/fixed_mass_rate={fixed_mass_rate}", + f"problem/cluster/magnetic_tower/l_mass_scale={self.magnetic_tower_l_mass_scale.in_units('code_length').v}", + ] return parameters - def Analyse(self,parameters): + def Analyse(self, parameters): """ Analyze the output and determine if the test passes. This function is called after the driver has been executed. It is responsible for reading whatever data it needs and making a judgment about whether or not the test passes. It takes no inputs. Output should - be True (test passes) or False (test fails). + be True (test passes) or False (test fails). The parameters that are passed in provide the paths to relevant locations and commands. Of particular importance is the path to the @@ -302,153 +341,225 @@ def Analyse(self,parameters): analyze_status = True self.Yp = self.He_mass_fraction - self.mu = 1/(self.Yp*3./4. + (1-self.Yp)*2) - self.mu_e = 1/(self.Yp*2./4. + (1-self.Yp)) - - magnetic_units = 'sqrt(code_mass)/sqrt(code_length)/code_time' + self.mu = 1 / (self.Yp * 3.0 / 4.0 + (1 - self.Yp) * 2) + self.mu_e = 1 / (self.Yp * 2.0 / 4.0 + (1 - self.Yp)) - for step in range(1,self.steps+1): - feedback_mode,precessed_jet = self.step_params_list[step-1] + magnetic_units = "sqrt(code_mass)/sqrt(code_length)/code_time" + + for step in range(1, self.steps + 1): + feedback_mode, precessed_jet = self.step_params_list[step - 1] output_id = f"{feedback_mode}_precessed_{precessed_jet}" step_status = True - print(">"*20) + print(">" * 20) print(f"Testing {output_id}") - print(">"*20) + print(">" * 20) B0_initial = self.initial_magnetic_tower_field - #Compute the initial magnetic energy - b_eng_initial_anyl = np.pi**(3./2.)/(8*np.sqrt(2))*(5 + self.magnetic_tower_alpha**2)*self.magnetic_tower_l_scale**3*B0_initial**2 + # Compute the initial magnetic energy + b_eng_initial_anyl = ( + np.pi ** (3.0 / 2.0) + / (8 * np.sqrt(2)) + * (5 + self.magnetic_tower_alpha**2) + * self.magnetic_tower_l_scale**3 + * B0_initial**2 + ) if feedback_mode == "const_field": - B0_final = self.feedback_magnetic_tower_field*self.tlim + self.initial_magnetic_tower_field - #Estimate the final magnetic field using the total energy of the tower out to inifinity - b_eng_final_anyl = np.pi**(3./2.)/(8*np.sqrt(2))*(5 + self.magnetic_tower_alpha**2)*self.magnetic_tower_l_scale**3*B0_final**2 - injected_mass = self.feedback_magnetic_tower_mass*self.tlim + B0_final = ( + self.feedback_magnetic_tower_field * self.tlim + + self.initial_magnetic_tower_field + ) + # Estimate the final magnetic field using the total energy of the tower out to inifinity + b_eng_final_anyl = ( + np.pi ** (3.0 / 2.0) + / (8 * np.sqrt(2)) + * (5 + self.magnetic_tower_alpha**2) + * self.magnetic_tower_l_scale**3 + * B0_final**2 + ) + injected_mass = self.feedback_magnetic_tower_mass * self.tlim elif feedback_mode == "const_power": - #Estimate the final magnetic field using the total energy of the tower out to inifinity - #Slightly inaccurate due to finite domain - B0_final = np.sqrt((b_eng_initial_anyl + self.feedback_magnetic_tower_power*self.tlim)/( - np.pi**(3./2.)/(8*np.sqrt(2))*(5 + self.magnetic_tower_alpha**2)*self.magnetic_tower_l_scale**3)) - b_eng_final_anyl = self.feedback_magnetic_tower_power*self.tlim + \ - (np.pi**(3./2.)/(8*np.sqrt(2))*(5 + self.magnetic_tower_alpha**2)*self.magnetic_tower_l_scale**3)*B0_initial**2 - injected_mass = self.feedback_magnetic_tower_power/(self.efficiency*unyt.c_cgs**2)*self.tlim + # Estimate the final magnetic field using the total energy of the tower out to inifinity + # Slightly inaccurate due to finite domain + B0_final = np.sqrt( + ( + b_eng_initial_anyl + + self.feedback_magnetic_tower_power * self.tlim + ) + / ( + np.pi ** (3.0 / 2.0) + / (8 * np.sqrt(2)) + * (5 + self.magnetic_tower_alpha**2) + * self.magnetic_tower_l_scale**3 + ) + ) + b_eng_final_anyl = ( + self.feedback_magnetic_tower_power * self.tlim + + ( + np.pi ** (3.0 / 2.0) + / (8 * np.sqrt(2)) + * (5 + self.magnetic_tower_alpha**2) + * self.magnetic_tower_l_scale**3 + ) + * B0_initial**2 + ) + injected_mass = ( + self.feedback_magnetic_tower_power + / (self.efficiency * unyt.c_cgs**2) + * self.tlim + ) else: - raise Exception(f"Feedback mode {feedback_mode} not supported in analysis") + raise Exception( + f"Feedback mode {feedback_mode} not supported in analysis" + ) - rho0_final = injected_mass/(self.magnetic_tower_l_mass_scale**3*np.pi**(3./2.)) + rho0_final = injected_mass / ( + self.magnetic_tower_l_mass_scale**3 * np.pi ** (3.0 / 2.0) + ) if precessed_jet is True: jet_coords = self.precessed_jet_coords else: jet_coords = self.zjet_coords - def field_func(Z,Y,X,B0): + def field_func(Z, Y, X, B0): l = self.magnetic_tower_l_scale alpha = self.magnetic_tower_alpha - pos_cart = unyt.unyt_array((X,Y,Z),"code_length") - R,Theta,H = jet_coords.cart_to_jet_coords(pos_cart) - - B_r = B0*2*(H/l)*(R/l) *np.exp( -(R/l)**2 -(H/l)**2) - B_theta = B0*alpha*(R/l) *np.exp( -(R/l)**2 -(H/l)**2) - B_h = B0*2*( 1 - (R/l)**2 )*np.exp( -(R/l)**2 -(H/l)**2) - B_jet = unyt.unyt_array((B_r,B_theta,B_h),magnetic_units) + pos_cart = unyt.unyt_array((X, Y, Z), "code_length") + R, Theta, H = jet_coords.cart_to_jet_coords(pos_cart) - B_x,B_y,B_z = jet_coords.jet_to_cart_vec(pos_cart,B_jet) + B_r = ( + B0 * 2 * (H / l) * (R / l) * np.exp(-((R / l) ** 2) - (H / l) ** 2) + ) + B_theta = B0 * alpha * (R / l) * np.exp(-((R / l) ** 2) - (H / l) ** 2) + B_h = ( + B0 * 2 * (1 - (R / l) ** 2) * np.exp(-((R / l) ** 2) - (H / l) ** 2) + ) + B_jet = unyt.unyt_array((B_r, B_theta, B_h), magnetic_units) + B_x, B_y, B_z = jet_coords.jet_to_cart_vec(pos_cart, B_jet) - return unyt.unyt_array((B_x, B_y, B_z),magnetic_units) + return unyt.unyt_array((B_x, B_y, B_z), magnetic_units) - b_energy_func = lambda Z,Y,X,B0 : 0.5*np.sum(field_func(Z,Y,X,B0)**2,axis=0) + b_energy_func = lambda Z, Y, X, B0: 0.5 * np.sum( + field_func(Z, Y, X, B0) ** 2, axis=0 + ) - def density_func(Z,Y,X,rho0): - pos_cart = unyt.unyt_array((X,Y,Z),"code_length") - R,Theta,H = jet_coords.cart_to_jet_coords(pos_cart) + def density_func(Z, Y, X, rho0): + pos_cart = unyt.unyt_array((X, Y, Z), "code_length") + R, Theta, H = jet_coords.cart_to_jet_coords(pos_cart) - Density = self.uniform_gas_rho + rho0*np.exp( -(R**2 + H**2)/self.magnetic_tower_l_mass_scale**2 ) + Density = self.uniform_gas_rho + rho0 * np.exp( + -(R**2 + H**2) / self.magnetic_tower_l_mass_scale**2 + ) return Density - - def internal_energy_density_func(Z,Y,X,rho0): - pos_cart = unyt.unyt_array((X,Y,Z),"code_length") - R,Theta,H = jet_coords.cart_to_jet_coords(pos_cart) - - rho_e = self.uniform_gas_energy_density + 1./(self.adiabatic_index - 1)*( - rho0*np.exp( -(R**2 + H**2)/self.magnetic_tower_l_mass_scale**2 ) - *(self.uniform_gas_pres/self.uniform_gas_rho)) - return rho_e + def internal_energy_density_func(Z, Y, X, rho0): + pos_cart = unyt.unyt_array((X, Y, Z), "code_length") + R, Theta, H = jet_coords.cart_to_jet_coords(pos_cart) + + rho_e = self.uniform_gas_energy_density + 1.0 / ( + self.adiabatic_index - 1 + ) * ( + rho0 + * np.exp(-(R**2 + H**2) / self.magnetic_tower_l_mass_scale**2) + * (self.uniform_gas_pres / self.uniform_gas_rho) + ) + return rho_e - #Check that the initial and final outputs match the expected tower - sys.path.insert(1, parameters.parthenon_path + '/scripts/python/packages/parthenon_tools/parthenon_tools') + # Check that the initial and final outputs match the expected tower + sys.path.insert( + 1, + parameters.parthenon_path + + "/scripts/python/packages/parthenon_tools/parthenon_tools", + ) try: import compare_analytic import phdf except ModuleNotFoundError: print("Couldn't find module to compare Parthenon hdf5 files.") - return False + return False - ######################################## + ######################################## # Compare to the analytically expected densities, total energy # densities, and magnetic fields - ######################################## + ######################################## - phdf_filenames = [f"{parameters.output_path}/parthenon.{output_id}.00000.phdf",f"{parameters.output_path}/parthenon.{output_id}.final.phdf"] + phdf_filenames = [ + f"{parameters.output_path}/parthenon.{output_id}.00000.phdf", + f"{parameters.output_path}/parthenon.{output_id}.final.phdf", + ] - #Create a relative L-Inf errpr function, ignore where zero in gold data + # Create a relative L-Inf errpr function, ignore where zero in gold data rel_linf_err_func = lambda gold, test: compare_analytic.norm_err_func( - gold,test,norm_ord=np.inf,relative=True,ignore_gold_zero=True) + gold, test, norm_ord=np.inf, relative=True, ignore_gold_zero=True + ) - #Create a linf error function scaled by a magnetic field - #Avoids relative comparisons in areas where magnetic field is close to zero - def B_scaled_linf_err(gold,test,B0): - err_val = np.abs((gold - test)/B0) + # Create a linf error function scaled by a magnetic field + # Avoids relative comparisons in areas where magnetic field is close to zero + def B_scaled_linf_err(gold, test, B0): + err_val = np.abs((gold - test) / B0) return err_val.max() - #Use a very loose tolerance, linf relative error + # Use a very loose tolerance, linf relative error analytic_statuses = [] - for B_field,rho0,phdf_filename,label in zip( - (B0_initial,B0_final), - (unyt.unyt_quantity(0,"code_mass*code_length**-3"),rho0_final), + for B_field, rho0, phdf_filename, label in zip( + (B0_initial, B0_final), + (unyt.unyt_quantity(0, "code_mass*code_length**-3"), rho0_final), phdf_filenames, - ("Initial","Final")): + ("Initial", "Final"), + ): - #Construct lambda functions for initial and final analytically - #expected density and total energy density + # Construct lambda functions for initial and final analytically + # expected density and total energy density densities_analytic_components = { - "Density":lambda Z,Y,X,time : density_func(Z,Y,X,rho0).in_units("code_mass*code_length**-3").v, - "TotalEnergyDensity":lambda Z,Y,X,time : ( - internal_energy_density_func(Z,Y,X,rho0) - + b_energy_func(Z,Y,X,B_field)).in_units("code_mass*code_length**-1*code_time**-2").v + "Density": lambda Z, Y, X, time: density_func(Z, Y, X, rho0) + .in_units("code_mass*code_length**-3") + .v, + "TotalEnergyDensity": lambda Z, Y, X, time: ( + internal_energy_density_func(Z, Y, X, rho0) + + b_energy_func(Z, Y, X, B_field) + ) + .in_units("code_mass*code_length**-1*code_time**-2") + .v, } - - #Compare the simulation and analytic density and total energy density + # Compare the simulation and analytic density and total energy density densities_analytic_status = compare_analytic.compare_analytic( - phdf_filename, densities_analytic_components, - err_func=rel_linf_err_func, - tol=self.linf_analytic_tol) + phdf_filename, + densities_analytic_components, + err_func=rel_linf_err_func, + tol=self.linf_analytic_tol, + ) - #Construct lambda functions for initial and final analytically expected magnetic fields + # Construct lambda functions for initial and final analytically expected magnetic fields field_analytic_components = { - "MagneticField1":lambda Z,Y,X,time : - field_func(Z,Y,X,B_field).in_units(magnetic_units)[0].v, - "MagneticField2":lambda Z,Y,X,time : - field_func(Z,Y,X,B_field).in_units(magnetic_units)[1].v, - "MagneticField3":lambda Z,Y,X,time : - field_func(Z,Y,X,B_field).in_units(magnetic_units)[2].v, + "MagneticField1": lambda Z, Y, X, time: field_func(Z, Y, X, B_field) + .in_units(magnetic_units)[0] + .v, + "MagneticField2": lambda Z, Y, X, time: field_func(Z, Y, X, B_field) + .in_units(magnetic_units)[1] + .v, + "MagneticField3": lambda Z, Y, X, time: field_func(Z, Y, X, B_field) + .in_units(magnetic_units)[2] + .v, } - #Compare the simulation and analytic magnetic fields, - #scaled by magnetic tower field scale + # Compare the simulation and analytic magnetic fields, + # scaled by magnetic tower field scale field_analytic_status = compare_analytic.compare_analytic( - phdf_filename, field_analytic_components, - err_func=lambda gold,test: B_scaled_linf_err(gold,test, - B_field.in_units(magnetic_units).v[()]), - tol=self.linf_analytic_tol) - - analytic_status = (densities_analytic_status and field_analytic_status) + phdf_filename, + field_analytic_components, + err_func=lambda gold, test: B_scaled_linf_err( + gold, test, B_field.in_units(magnetic_units).v[()] + ), + tol=self.linf_analytic_tol, + ) + + analytic_status = densities_analytic_status and field_analytic_status if not analytic_status: print(f"{label} Analytic comparison failed\n") @@ -456,68 +567,96 @@ def B_scaled_linf_err(gold,test,B0): analyze_status &= np.all(analytic_statuses) - for phdf_filename,b_eng_anyl,B0,b_eng_tol,label in zip(phdf_filenames, - (b_eng_initial_anyl,b_eng_final_anyl), - (B0_initial,B0_final), - (self.b_eng_initial_tol,self.b_eng_final_tol), - ("Initial","Final")): + for phdf_filename, b_eng_anyl, B0, b_eng_tol, label in zip( + phdf_filenames, + (b_eng_initial_anyl, b_eng_final_anyl), + (B0_initial, B0_final), + (self.b_eng_initial_tol, self.b_eng_final_tol), + ("Initial", "Final"), + ): - ######################################## + ######################################## # Compare with the analytically expected total magnetic energy - ######################################## + ######################################## phdf_file = phdf.phdf(phdf_filename) - #Get the cell volumes from phdf_file + # Get the cell volumes from phdf_file xf = phdf_file.xf yf = phdf_file.yf zf = phdf_file.zf - cell_vols = unyt.unyt_array(np.einsum('ai,aj,ak->aijk', - np.diff(zf),np.diff(yf),np.diff(xf)),"code_length**3") - - #Get the magnetic energy from phdf_file - B = unyt.unyt_array( - list(phdf_file.GetComponents(["MagneticField1","MagneticField2","MagneticField3"],flatten=False).values()), - magnetic_units) - b_eng = np.sum(0.5*np.sum(B**2,axis=0)*cell_vols) - - #Get the estimated magnetic energy from the expected mt_tower field - - Z,Y,X = phdf_file.GetVolumeLocations(flatten=False) - Z = unyt.unyt_array(Z,"code_length") - Y = unyt.unyt_array(Y,"code_length") - X = unyt.unyt_array(X,"code_length") - b_eng_numer = np.sum( b_energy_func(Z,Y,X,B0)*cell_vols ) - - b_eng_anyl_rel_err = np.abs((b_eng - b_eng_anyl)/b_eng_anyl) - b_eng_numer_rel_err = np.abs((b_eng - b_eng_numer)/b_eng_numer) + cell_vols = unyt.unyt_array( + np.einsum("ai,aj,ak->aijk", np.diff(zf), np.diff(yf), np.diff(xf)), + "code_length**3", + ) + + # Get the magnetic energy from phdf_file + B = unyt.unyt_array( + list( + phdf_file.GetComponents( + ["MagneticField1", "MagneticField2", "MagneticField3"], + flatten=False, + ).values() + ), + magnetic_units, + ) + b_eng = np.sum(0.5 * np.sum(B**2, axis=0) * cell_vols) + + # Get the estimated magnetic energy from the expected mt_tower field + + Z, Y, X = phdf_file.GetVolumeLocations(flatten=False) + Z = unyt.unyt_array(Z, "code_length") + Y = unyt.unyt_array(Y, "code_length") + X = unyt.unyt_array(X, "code_length") + b_eng_numer = np.sum(b_energy_func(Z, Y, X, B0) * cell_vols) + + b_eng_anyl_rel_err = np.abs((b_eng - b_eng_anyl) / b_eng_anyl) + b_eng_numer_rel_err = np.abs((b_eng - b_eng_numer) / b_eng_numer) if b_eng_anyl_rel_err > b_eng_tol: - print(f"{label} Analytically Integrated Relative Energy Error: {b_eng_anyl_rel_err} exceeds tolerance {b_eng_tol}", - f"Analytic {'>' if b_eng_anyl > b_eng else '<'} Simulation") + print( + f"{label} Analytically Integrated Relative Energy Error: {b_eng_anyl_rel_err} exceeds tolerance {b_eng_tol}", + f"Analytic {'>' if b_eng_anyl > b_eng else '<'} Simulation", + ) analyze_status = False if b_eng_numer_rel_err > b_eng_tol: - print(f"{label} Numerically Integrated Relative Energy Error: {b_eng_numer_rel_err} exceeds tolerance {b_eng_tol}", - f"Numerical {'>' if b_eng_numer > b_eng else '<'} Simulation") + print( + f"{label} Numerically Integrated Relative Energy Error: {b_eng_numer_rel_err} exceeds tolerance {b_eng_tol}", + f"Numerical {'>' if b_eng_numer > b_eng else '<'} Simulation", + ) analyze_status = False - ######################################## + ######################################## # Check divB - ######################################## - - #FIXME: This computation of the fluxes would work better with 1 ghostzone from the simulation - #Compute cell lengths (note: these are NGridxNBlockSide) - dxf = np.diff(xf,axis=1) - dyf = np.diff(yf,axis=1) - dzf = np.diff(zf,axis=1) - - dBxdx = 0.5*(B[0,:,:,:,2:]-B[0,:,:,:,:-2])[:,1:-1,1:-1,:]/dxf[:,np.newaxis,np.newaxis,1:-1] - dBydy = 0.5*(B[1,:,:,2:,:]-B[1,:,:,:-2,:])[:,1:-1,:,1:-1]/dyf[:,np.newaxis,1:-1,np.newaxis] - dBzdz = 0.5*(B[2,:,2:,:,:]-B[2,:,:-2,:,:])[:,:,1:-1,1:-1]/dzf[:,1:-1,np.newaxis,np.newaxis] + ######################################## + + # FIXME: This computation of the fluxes would work better with 1 ghostzone from the simulation + # Compute cell lengths (note: these are NGridxNBlockSide) + dxf = np.diff(xf, axis=1) + dyf = np.diff(yf, axis=1) + dzf = np.diff(zf, axis=1) + + dBxdx = ( + 0.5 + * (B[0, :, :, :, 2:] - B[0, :, :, :, :-2])[:, 1:-1, 1:-1, :] + / dxf[:, np.newaxis, np.newaxis, 1:-1] + ) + dBydy = ( + 0.5 + * (B[1, :, :, 2:, :] - B[1, :, :, :-2, :])[:, 1:-1, :, 1:-1] + / dyf[:, np.newaxis, 1:-1, np.newaxis] + ) + dBzdz = ( + 0.5 + * (B[2, :, 2:, :, :] - B[2, :, :-2, :, :])[:, :, 1:-1, 1:-1] + / dzf[:, 1:-1, np.newaxis, np.newaxis] + ) divB = dBxdx + dBydy + dBzdz - if np.max(divB)/B0 > self.divB_tol: - print(f"{label} Max div B Error: Max divB/B0 {np.max(divB)/B0} exceeds tolerance {self.divB_tol}") + if np.max(divB) / B0 > self.divB_tol: + print( + f"{label} Max div B Error: Max divB/B0 {np.max(divB)/B0} exceeds tolerance {self.divB_tol}" + ) analyze_status = False return analyze_status diff --git a/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py b/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py index c83fd9f2..994a8e76 100644 --- a/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py +++ b/tst/regression/test_suites/cluster_tabular_cooling/cluster_tabular_cooling.py @@ -1,8 +1,8 @@ -#======================================================================================== +# ======================================================================================== # AthenaPK - a performance portable block structured AMR MHD code # Copyright (c) 2020-2021, Athena Parthenon Collaboration. All rights reserved. # Licensed under the 3-clause BSD License, see LICENSE file for details -#======================================================================================== +# ======================================================================================== # (C) (or copyright) 2020. Triad National Security, LLC. All rights reserved. # # This program was produced under U.S. Government contract 89233218CNA000001 for Los @@ -13,7 +13,7 @@ # itself and others acting on its behalf a nonexclusive, paid-up, irrevocable worldwide # license in this material to reproduce, prepare derivative works, distribute copies to # the public, perform publicly and display publicly, and to permit others to do so. -#======================================================================================== +# ======================================================================================== # Modules import math @@ -21,7 +21,8 @@ import scipy as sp import scipy.optimize import matplotlib -matplotlib.use('agg') + +matplotlib.use("agg") import matplotlib.pylab as plt import sys import os @@ -32,63 +33,66 @@ """ To prevent littering up imported folders with .pyc files or __pycache_ folder""" sys.dont_write_bytecode = True + class TestCase(utils.test_case.TestCaseAbs): def __init__(self): - #Define cluster parameters - #Setup units - unyt.define_unit("code_length",(1,"Mpc")) - unyt.define_unit("code_mass",(1e14,"Msun")) - unyt.define_unit("code_time",(1,"Gyr")) - self.code_length = unyt.unyt_quantity(1,"code_length") - self.code_mass = unyt.unyt_quantity(1,"code_mass") - self.code_time = unyt.unyt_quantity(1,"code_time") + # Define cluster parameters + # Setup units + unyt.define_unit("code_length", (1, "Mpc")) + unyt.define_unit("code_mass", (1e14, "Msun")) + unyt.define_unit("code_time", (1, "Gyr")) + self.code_length = unyt.unyt_quantity(1, "code_length") + self.code_mass = unyt.unyt_quantity(1, "code_mass") + self.code_time = unyt.unyt_quantity(1, "code_time") - self.tlim = unyt.unyt_quantity(1,"code_time") + self.tlim = unyt.unyt_quantity(1, "code_time") - self.adiabatic_index = 5./3. + self.adiabatic_index = 5.0 / 3.0 self.He_mass_fraction = 0.25 - #Define an exponential cooling function for testing (in log cgs) + # Define an exponential cooling function for testing (in log cgs) self.log_temp0 = 4 self.log_temp1 = 6 self.n_log_temp = 100 - self.log_lambda0 = -30 + self.log_lambda0 = -30 self.log_lambda1 = -25 self.n_log_lambda = 100 - #self.cooling_m = (log_lambda1 - log_lambda0)/(log_temp1 - log_temp0) - #self.cooling_b = log_lambda1 - log_temp1*cooling_m + # self.cooling_m = (log_lambda1 - log_lambda0)/(log_temp1 - log_temp0) + # self.cooling_b = log_lambda1 - log_temp1*cooling_m - #self.log_cooling_func = lambda log_temp: cooling_m*log_temp + cooling_b - #self.cooling_func = lambda temp: unyt.unyt_array( + # self.log_cooling_func = lambda log_temp: cooling_m*log_temp + cooling_b + # self.cooling_func = lambda temp: unyt.unyt_array( # 10**(self.log_cooling_func(np.log10(temp))),"erg*cm**3*s**-1") - #Define the initial uniform gas - self.uniform_gas_rho = unyt.unyt_quantity(1e-24,"g/cm**3") - self.uniform_gas_ux = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_uy = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_uz = unyt.unyt_quantity(0,"cm/s") - self.uniform_gas_pres = unyt.unyt_quantity(1e-10,"dyne/cm**2") + # Define the initial uniform gas + self.uniform_gas_rho = unyt.unyt_quantity(1e-24, "g/cm**3") + self.uniform_gas_ux = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_uy = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_uz = unyt.unyt_quantity(0, "cm/s") + self.uniform_gas_pres = unyt.unyt_quantity(1e-10, "dyne/cm**2") - #Table of parameters to test + # Table of parameters to test self.integrators = ("rk12", "rk45") - self.max_iters=(4,10,25,50) - self.integrators_and_max_iters = list(itertools.product(self.integrators,self.max_iters)) - self.n_steps = len(self.integrators_and_max_iters)+len(self.integrators) + self.max_iters = (4, 10, 25, 50) + self.integrators_and_max_iters = list( + itertools.product(self.integrators, self.max_iters) + ) + self.n_steps = len(self.integrators_and_max_iters) + len(self.integrators) self.norm_tol = 1e-3 self.machine_epsilon = 1e-14 - self.integrator_tol = {"rk12" : 1e-4, "rk45" : 1e-10} - self.integrator_order = {"rk12" : 2, "rk45" : 5} + self.integrator_tol = {"rk12": 1e-4, "rk45": 1e-10} + self.integrator_order = {"rk12": 2, "rk45": 5} self.cooling_cfl_convergence_test = 1e100 self.cooling_cfl_dual_order_test = 0.1 self.convergence_tol = 0.1 - def Prepare(self,parameters, step): + def Prepare(self, parameters, step): """ Any preprocessing that is needed before the drive is run can be done in this method @@ -96,7 +100,7 @@ def Prepare(self,parameters, step): This includes preparing files or any other pre processing steps that need to be implemented. The method also provides access to the parameters object which controls which parameters are being used to run - the driver. + the driver. It is possible to append arguments to the driver_cmd_line_args if it is desired to override the parthenon input file. Each element in the list @@ -113,75 +117,70 @@ def Prepare(self,parameters, step): 'mesh/nx1=400'] """ - #Get the cooling integrator and max_iter for this run + # Get the cooling integrator and max_iter for this run if step <= len(self.integrators_and_max_iters): - #Convergence Tests - #Use a specific iteration count - integrator,max_iter = self.integrators_and_max_iters[step-1] - #force the full number of iterations - d_e_tol = 0 - #Use a very high cooling cfl so that cooling time doesn't affect the timestep - #(Makes the convergence more obvious) + # Convergence Tests + # Use a specific iteration count + integrator, max_iter = self.integrators_and_max_iters[step - 1] + # force the full number of iterations + d_e_tol = 0 + # Use a very high cooling cfl so that cooling time doesn't affect the timestep + # (Makes the convergence more obvious) cooling_cfl = self.cooling_cfl_convergence_test else: - #Test the adaptiveness of the dual-order RK integrators + # Test the adaptiveness of the dual-order RK integrators adapt_step = step - len(self.integrators_and_max_iters) - integrator = self.integrators[adapt_step-1] - #Use plenty of iterations + integrator = self.integrators[adapt_step - 1] + # Use plenty of iterations max_iter = max(self.max_iters) - #Use a small but non-zero tolerance + # Use a small but non-zero tolerance d_e_tol = self.machine_epsilon - #Use a reasonable cooling cfl + # Use a reasonable cooling cfl cooling_cfl = self.cooling_cfl_convergence_test - #Create the tabular cooling file (in log cgs) + # Create the tabular cooling file (in log cgs) table_filename = "exponential.cooling" - log_temps = np.linspace(self.log_temp0,self.log_temp1,self.n_log_temp) - log_lambdas = np.linspace(self.log_lambda0,self.log_lambda1,self.n_log_lambda) + log_temps = np.linspace(self.log_temp0, self.log_temp1, self.n_log_temp) + log_lambdas = np.linspace(self.log_lambda0, self.log_lambda1, self.n_log_lambda) - cooling_table = np.vstack((log_temps,log_lambdas)).T - np.savetxt(table_filename,cooling_table,delimiter=" ") + cooling_table = np.vstack((log_temps, log_lambdas)).T + np.savetxt(table_filename, cooling_table, delimiter=" ") parameters.driver_cmd_line_args = [ - f"parthenon/output2/id=tabular_cooling_{step}", - f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", - f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", - f"hydro/gamma={self.adiabatic_index}", - f"hydro/He_mass_fraction={self.He_mass_fraction}", - - f"units/code_length_cgs={self.code_length.in_units('cm').v}", - f"units/code_mass_cgs={self.code_mass.in_units('g').v}", - f"units/code_time_cgs={self.code_time.in_units('s').v}", - - f"problem/cluster/uniform_gas/init_uniform_gas=true", - f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", - f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", - f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", - - f"cooling/table_filename={table_filename}", - f"cooling/log_temp_col=0", - f"cooling/log_lambda_col=1", - f"cooling/lambda_units_cgs=1", - - f"cooling/integrator={integrator}", - f"cooling/cfl={cooling_cfl}", - f"cooling/max_iter={max_iter}", - f"cooling/d_e_tol={d_e_tol}", - ] - + f"parthenon/output2/id=tabular_cooling_{step}", + f"parthenon/output2/dt={self.tlim.in_units('code_time').v}", + f"parthenon/time/tlim={self.tlim.in_units('code_time').v}", + f"hydro/gamma={self.adiabatic_index}", + f"hydro/He_mass_fraction={self.He_mass_fraction}", + f"units/code_length_cgs={self.code_length.in_units('cm').v}", + f"units/code_mass_cgs={self.code_mass.in_units('g').v}", + f"units/code_time_cgs={self.code_time.in_units('s').v}", + f"problem/cluster/uniform_gas/init_uniform_gas=true", + f"problem/cluster/uniform_gas/rho={self.uniform_gas_rho.in_units('code_mass*code_length**-3').v}", + f"problem/cluster/uniform_gas/ux={self.uniform_gas_ux.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", + f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", + f"cooling/table_filename={table_filename}", + f"cooling/log_temp_col=0", + f"cooling/log_lambda_col=1", + f"cooling/lambda_units_cgs=1", + f"cooling/integrator={integrator}", + f"cooling/cfl={cooling_cfl}", + f"cooling/max_iter={max_iter}", + f"cooling/d_e_tol={d_e_tol}", + ] return parameters - def Analyse(self,parameters): + def Analyse(self, parameters): """ Analyze the output and determine if the test passes. This function is called after the driver has been executed. It is responsible for reading whatever data it needs and making a judgment about whether or not the test passes. It takes no inputs. Output should - be True (test passes) or False (test fails). + be True (test passes) or False (test fails). The parameters that are passed in provide the paths to relevant locations and commands. Of particular importance is the path to the @@ -197,41 +196,51 @@ def Analyse(self,parameters): H_mass_fraction = 1.0 - self.He_mass_fraction Yp = self.He_mass_fraction - mu = 1/(Yp*3./4. + (1-Yp)*2) - mu_e = 1/(Yp*2./4. + (1-Yp)) + mu = 1 / (Yp * 3.0 / 4.0 + (1 - Yp) * 2) + mu_e = 1 / (Yp * 2.0 / 4.0 + (1 - Yp)) gm1 = self.adiabatic_index - 1.0 - #First evolve the analytic model - #Compute the hydrogen number density - initial_internal_e = (self.uniform_gas_pres/(self.uniform_gas_rho*(self.adiabatic_index-1))).in_units("erg/g") - n_h = (self.uniform_gas_rho * H_mass_fraction/unyt.amu).in_units("1/cm**3") - - #Compute temperature - initial_temp = initial_internal_e*(mu*unyt.amu*gm1/unyt.kb) - - - #Unit helpers - Ta = unyt.unyt_quantity(1,"K") - ea = unyt.unyt_quantity(1,"erg/g") - - #Some intermediate coefficients for integration - cooling_m = (self.log_lambda1 - self.log_lambda0)/(self.log_temp1 - self.log_temp0) - cooling_b = self.log_lambda1 - self.log_temp1*cooling_m - B = unyt.unyt_quantity(10**cooling_b,"erg*cm**3/s") - X = mu * unyt.amu *(self.adiabatic_index-1)/(unyt.kb*Ta) - Y = B * n_h**2/ self.uniform_gas_rho - - #Integrate for the final internal energy - analytic_internal_e = lambda t: ea* ( (initial_internal_e/ea)**(1-cooling_m) - -Y/ea*t*(1-cooling_m)*( X*ea )**cooling_m - )**(1./(1-cooling_m)) - analytic_final_internal_e = analytic_internal_e(self.tlim) + # First evolve the analytic model + # Compute the hydrogen number density + initial_internal_e = ( + self.uniform_gas_pres / (self.uniform_gas_rho * (self.adiabatic_index - 1)) + ).in_units("erg/g") + n_h = (self.uniform_gas_rho * H_mass_fraction / unyt.amu).in_units("1/cm**3") + + # Compute temperature + initial_temp = initial_internal_e * (mu * unyt.amu * gm1 / unyt.kb) + + # Unit helpers + Ta = unyt.unyt_quantity(1, "K") + ea = unyt.unyt_quantity(1, "erg/g") + + # Some intermediate coefficients for integration + cooling_m = (self.log_lambda1 - self.log_lambda0) / ( + self.log_temp1 - self.log_temp0 + ) + cooling_b = self.log_lambda1 - self.log_temp1 * cooling_m + B = unyt.unyt_quantity(10**cooling_b, "erg*cm**3/s") + X = mu * unyt.amu * (self.adiabatic_index - 1) / (unyt.kb * Ta) + Y = B * n_h**2 / self.uniform_gas_rho + + # Integrate for the final internal energy + analytic_internal_e = lambda t: ea * ( + (initial_internal_e / ea) ** (1 - cooling_m) + - Y / ea * t * (1 - cooling_m) * (X * ea) ** cooling_m + ) ** (1.0 / (1 - cooling_m)) + analytic_final_internal_e = analytic_internal_e(self.tlim) initial_internal_e = initial_internal_e.in_units("code_length**2/code_time**2") - analytic_final_internal_e = analytic_final_internal_e.in_units("code_length**2/code_time**2") + analytic_final_internal_e = analytic_final_internal_e.in_units( + "code_length**2/code_time**2" + ) - sys.path.insert(1, parameters.parthenon_path + '/scripts/python/packages/parthenon_tools/parthenon_tools') + sys.path.insert( + 1, + parameters.parthenon_path + + "/scripts/python/packages/parthenon_tools/parthenon_tools", + ) try: import phdf @@ -241,150 +250,235 @@ def Analyse(self,parameters): return False analytic_uniform_gas_components = { - "Density":lambda Z,Y,X,time : - (np.ones_like(X)*self.uniform_gas_rho).in_units("code_mass*code_length**-3").v, - "Velocity1":lambda Z,Y,X,time : - (np.ones_like(X)*self.uniform_gas_ux).in_units("code_length*code_time**-1").v, - "Velocity2":lambda Z,Y,X,time : - (np.ones_like(X)*self.uniform_gas_uy).in_units("code_length*code_time**-1").v, - "Velocity3":lambda Z,Y,X,time : - (np.ones_like(X)*self.uniform_gas_uz).in_units("code_length*code_time**-1").v, - "Pressure":lambda Z,Y,X,time : - (np.ones_like(X)*self.uniform_gas_pres).in_units("code_mass*code_length**-1*code_time**-2").v, - } - def zero_corrected_linf_err(gold,test): - non_zero_linf = np.max(np.abs((gold[gold!=0]-test[gold!=0])/gold[gold!=0]),initial=0) - zero_linf = np.max(np.abs((gold[gold==0]-test[gold==0])),initial=0) - - return np.max((non_zero_linf,zero_linf)) - - #Verify the initial state - for step in range(1,self.n_steps+1): - data_filename = f"{parameters.output_path}/parthenon.tabular_cooling_{step}.00000.phdf" - - #Check that initial state matches the uniform gas + "Density": lambda Z, Y, X, time: (np.ones_like(X) * self.uniform_gas_rho) + .in_units("code_mass*code_length**-3") + .v, + "Velocity1": lambda Z, Y, X, time: (np.ones_like(X) * self.uniform_gas_ux) + .in_units("code_length*code_time**-1") + .v, + "Velocity2": lambda Z, Y, X, time: (np.ones_like(X) * self.uniform_gas_uy) + .in_units("code_length*code_time**-1") + .v, + "Velocity3": lambda Z, Y, X, time: (np.ones_like(X) * self.uniform_gas_uz) + .in_units("code_length*code_time**-1") + .v, + "Pressure": lambda Z, Y, X, time: (np.ones_like(X) * self.uniform_gas_pres) + .in_units("code_mass*code_length**-1*code_time**-2") + .v, + } + + def zero_corrected_linf_err(gold, test): + non_zero_linf = np.max( + np.abs((gold[gold != 0] - test[gold != 0]) / gold[gold != 0]), initial=0 + ) + zero_linf = np.max(np.abs((gold[gold == 0] - test[gold == 0])), initial=0) + + return np.max((non_zero_linf, zero_linf)) + + # Verify the initial state + for step in range(1, self.n_steps + 1): + data_filename = ( + f"{parameters.output_path}/parthenon.tabular_cooling_{step}.00000.phdf" + ) + + # Check that initial state matches the uniform gas initial_uniform_gas_status = compare_analytic.compare_analytic( - data_filename, analytic_uniform_gas_components, + data_filename, + analytic_uniform_gas_components, err_func=zero_corrected_linf_err, - tol=self.machine_epsilon) - + tol=self.machine_epsilon, + ) + if not initial_uniform_gas_status: print(f"Initial state of sim {step} does not match expected.") analyze_status = False data_file = phdf.phdf(data_filename) prim = data_file.Get("prim") - - #FIXME: TODO(forrestglines) For now this is hard coded - a component mapping should be done by phdf - prim_col_dict = {"Density":0,"Velocity1":1,"Velocity2":2,"Velocity3":3,"Pressure":4} - - rho = unyt.unyt_array(prim[:,prim_col_dict["Density" ]],"code_mass*code_length**-3") - ux = unyt.unyt_array(prim[:,prim_col_dict["Velocity1"]],"code_length*code_time**-1") - uy = unyt.unyt_array(prim[:,prim_col_dict["Velocity2"]],"code_length*code_time**-1") - uz = unyt.unyt_array(prim[:,prim_col_dict["Velocity3"]],"code_length*code_time**-1") - pres = unyt.unyt_array(prim[:,prim_col_dict["Pressure" ]],"code_mass*code_length**-1*code_time**-2") - - #Check initial internal_e - internal_e = (pres/(rho*(self.adiabatic_index-1))).mean() - err = np.abs( (internal_e -initial_internal_e)/initial_internal_e) + + # FIXME: TODO(forrestglines) For now this is hard coded - a component mapping should be done by phdf + prim_col_dict = { + "Density": 0, + "Velocity1": 1, + "Velocity2": 2, + "Velocity3": 3, + "Pressure": 4, + } + + rho = unyt.unyt_array( + prim[:, prim_col_dict["Density"]], "code_mass*code_length**-3" + ) + ux = unyt.unyt_array( + prim[:, prim_col_dict["Velocity1"]], "code_length*code_time**-1" + ) + uy = unyt.unyt_array( + prim[:, prim_col_dict["Velocity2"]], "code_length*code_time**-1" + ) + uz = unyt.unyt_array( + prim[:, prim_col_dict["Velocity3"]], "code_length*code_time**-1" + ) + pres = unyt.unyt_array( + prim[:, prim_col_dict["Pressure"]], + "code_mass*code_length**-1*code_time**-2", + ) + + # Check initial internal_e + internal_e = (pres / (rho * (self.adiabatic_index - 1))).mean() + err = np.abs((internal_e - initial_internal_e) / initial_internal_e) if err > self.machine_epsilon: - print(f"Initial internal energy {internal_e} differs from expected {initial_internal_e} err={err}") + print( + f"Initial internal energy {internal_e} differs from expected {initial_internal_e} err={err}" + ) analyze_status = False + # Read and check the final state of all sims + conv_final_internal_es = {} # internal_e for convergence study + adapt_final_internal_es = {} # internal_e for the adaptive tests - #Read and check the final state of all sims - conv_final_internal_es = {} #internal_e for convergence study - adapt_final_internal_es = {} #internal_e for the adaptive tests + for step in range(1, self.n_steps + 1): + data_filename = ( + f"{parameters.output_path}/parthenon.tabular_cooling_{step}.final.phdf" + ) - for step in range(1,self.n_steps+1): - data_filename = f"{parameters.output_path}/parthenon.tabular_cooling_{step}.final.phdf" - - #Check that gas state matches the initial uniform gas except for the pressure + # Check that gas state matches the initial uniform gas except for the pressure final_uniform_gas_status = compare_analytic.compare_analytic( data_filename, - { k:v for k,v in analytic_uniform_gas_components.items() if k != "Pressure"}, + { + k: v + for k, v in analytic_uniform_gas_components.items() + if k != "Pressure" + }, err_func=zero_corrected_linf_err, - tol=self.machine_epsilon) - + tol=self.machine_epsilon, + ) + if not final_uniform_gas_status: - print(f"Final density and/or velocity of sim {step} does not match expected.") + print( + f"Final density and/or velocity of sim {step} does not match expected." + ) analyze_status = False data_file = phdf.phdf(data_filename) prim = data_file.Get("prim") - - #FIXME: TODO(forrestglines) For now this is hard coded - a component mapping should be done by phdf - prim_col_dict = {"Density":0,"Velocity1":1,"Velocity2":2,"Velocity3":3,"Pressure":4} - - rho = unyt.unyt_array(prim[prim_col_dict["Density" ]],"code_mass*code_length**-3") - ux = unyt.unyt_array(prim[prim_col_dict["Velocity1"]],"code_length*code_time**-1") - uy = unyt.unyt_array(prim[prim_col_dict["Velocity2"]],"code_length*code_time**-1") - uz = unyt.unyt_array(prim[prim_col_dict["Velocity3"]],"code_length*code_time**-1") - pres = unyt.unyt_array(prim[prim_col_dict["Pressure" ]],"code_mass*code_length**-1*code_time**-2") - - #Save the final internal_e - internal_e = (pres/(rho*(self.adiabatic_index-1))).mean() + + # FIXME: TODO(forrestglines) For now this is hard coded - a component mapping should be done by phdf + prim_col_dict = { + "Density": 0, + "Velocity1": 1, + "Velocity2": 2, + "Velocity3": 3, + "Pressure": 4, + } + + rho = unyt.unyt_array( + prim[prim_col_dict["Density"]], "code_mass*code_length**-3" + ) + ux = unyt.unyt_array( + prim[prim_col_dict["Velocity1"]], "code_length*code_time**-1" + ) + uy = unyt.unyt_array( + prim[prim_col_dict["Velocity2"]], "code_length*code_time**-1" + ) + uz = unyt.unyt_array( + prim[prim_col_dict["Velocity3"]], "code_length*code_time**-1" + ) + pres = unyt.unyt_array( + prim[prim_col_dict["Pressure"]], + "code_mass*code_length**-1*code_time**-2", + ) + + # Save the final internal_e + internal_e = (pres / (rho * (self.adiabatic_index - 1))).mean() if step <= len(self.integrators_and_max_iters): - conv_final_internal_es[self.integrators_and_max_iters[step-1]] = internal_e + conv_final_internal_es[ + self.integrators_and_max_iters[step - 1] + ] = internal_e else: adapt_step = step - len(self.integrators_and_max_iters) - integrator = self.integrators[adapt_step-1] + integrator = self.integrators[adapt_step - 1] adapt_final_internal_es[integrator] = internal_e for integrator in self.integrators: final_internal_es = unyt.unyt_array( - [ conv_final_internal_es[(integrator,mi)].in_units("code_length**2*code_time**-2") - for mi in self.max_iters ], - "code_length**2*code_time**-2") - - - #Plot the error for the convergence test - fig,ax = plt.subplots(1,1) + [ + conv_final_internal_es[(integrator, mi)].in_units( + "code_length**2*code_time**-2" + ) + for mi in self.max_iters + ], + "code_length**2*code_time**-2", + ) + + # Plot the error for the convergence test + fig, ax = plt.subplots(1, 1) for integrator in self.integrators: order = self.integrator_order[integrator] final_internal_es = unyt.unyt_array( - [ conv_final_internal_es[(integrator,mi)].in_units("code_length**2*code_time**-2") - for mi in self.max_iters ], - "code_length**2*code_time**-2") - max_iters = np.array(self.max_iters,dtype=float) - - final_internal_e_errs = np.abs( (analytic_final_internal_e-final_internal_es)/analytic_final_internal_e) - - #Estimate the converge rate for each integration order - conv_model = lambda log_n,log_a,conv_rate : conv_rate*log_n + log_a - popt,pconv = sp.optimize.curve_fit(conv_model,np.log10(max_iters),np.log10(final_internal_e_errs)) - - conv_a,conv_measured = popt - - if conv_measured > -order + self.convergence_tol: + [ + conv_final_internal_es[(integrator, mi)].in_units( + "code_length**2*code_time**-2" + ) + for mi in self.max_iters + ], + "code_length**2*code_time**-2", + ) + max_iters = np.array(self.max_iters, dtype=float) + + final_internal_e_errs = np.abs( + (analytic_final_internal_e - final_internal_es) + / analytic_final_internal_e + ) + + # Estimate the converge rate for each integration order + conv_model = lambda log_n, log_a, conv_rate: conv_rate * log_n + log_a + popt, pconv = sp.optimize.curve_fit( + conv_model, np.log10(max_iters), np.log10(final_internal_e_errs) + ) + + conv_a, conv_measured = popt + + if conv_measured > -order + self.convergence_tol: print(f"ERROR: Convergence rate of {integrator} exceeds tolerance") print(f" {conv_measured} > {-order} + {self.convergence_tol}") analyze_status = False - #Plot this err versus steps - sc = ax.scatter(max_iters,final_internal_e_errs,label=f"{integrator}") - - #Plot the expected scaling for the error - ax.plot(self.max_iters, (final_internal_e_errs[0]/max_iters[0]**-order)* - max_iters**(-order), - color=sc.get_facecolors()[0],linestyle="--",label=f"Expected: $n^{{{-order}}}$") - - #Plot the Measured scaling for the error - ax.plot(self.max_iters, (10**conv_a)*max_iters**conv_measured, - color=sc.get_facecolors()[0],linestyle=":",label=f"Measured: $n^{{{conv_measured:.1f}}}$") - - #Check that the adaptive approach is within test_epsilon of the best err + # Plot this err versus steps + sc = ax.scatter(max_iters, final_internal_e_errs, label=f"{integrator}") + + # Plot the expected scaling for the error + ax.plot( + self.max_iters, + (final_internal_e_errs[0] / max_iters[0] ** -order) + * max_iters ** (-order), + color=sc.get_facecolors()[0], + linestyle="--", + label=f"Expected: $n^{{{-order}}}$", + ) + + # Plot the Measured scaling for the error + ax.plot( + self.max_iters, + (10**conv_a) * max_iters**conv_measured, + color=sc.get_facecolors()[0], + linestyle=":", + label=f"Measured: $n^{{{conv_measured:.1f}}}$", + ) + + # Check that the adaptive approach is within test_epsilon of the best err adapt_final_internal_e = adapt_final_internal_es[integrator] - adapt_err = np.abs( (analytic_final_internal_e-adapt_final_internal_e)/analytic_final_internal_e) + adapt_err = np.abs( + (analytic_final_internal_e - adapt_final_internal_e) + / analytic_final_internal_e + ) - if( adapt_err >= self.integrator_tol[integrator] ): + if adapt_err >= self.integrator_tol[integrator]: print(f"ERROR: Adaptive {integrator} error exceeds tolerance") print(f" {adapt_err} >= {self.integrator_tol[integrator]}") analyze_status = False - + ax.set_xscale("log") ax.set_yscale("log") From 0d9e6e565ad92db84b663bf27764607fa9afb4d4 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 15 Nov 2022 11:00:50 -0700 Subject: [PATCH 28/95] Cleanup inputs, move cooling tables --- inputs/cluster/agn_triggering.in | 2 +- inputs/cluster/cluster.in | 6 +++--- inputs/cluster/hse.in | 6 +++--- inputs/{cluster => cooling_tables}/schure.cooling | 0 .../{cluster => cooling_tables}/sutherland_dopita.cooling | 0 5 files changed, 7 insertions(+), 7 deletions(-) rename inputs/{cluster => cooling_tables}/schure.cooling (100%) rename inputs/{cluster => cooling_tables}/sutherland_dopita.cooling (100%) diff --git a/inputs/cluster/agn_triggering.in b/inputs/cluster/agn_triggering.in index 423c97d8..6c554e6f 100644 --- a/inputs/cluster/agn_triggering.in +++ b/inputs/cluster/agn_triggering.in @@ -83,7 +83,7 @@ code_time_cgs = 3.15576e+16 # 1 Gyr in s #Define SMBH for Bondi accretion -m_smbh = 1.0000000000000002e-06 +m_smbh = 1.0e-06 #Disable gravity as a source term gravity_srcterm = false diff --git a/inputs/cluster/cluster.in b/inputs/cluster/cluster.in index 3f587ff4..846367cb 100644 --- a/inputs/cluster/cluster.in +++ b/inputs/cluster/cluster.in @@ -115,14 +115,14 @@ include_smbh_g = True #NFW parameters c_nfw = 6.0 -m_nfw_200 = 10.000000000000002 +m_nfw_200 = 10.0 #BCG parameters -m_bcg_s = 0.0010000000000000002 +m_bcg_s = 0.001 r_bcg_s = 0.004 #SMBH parameters -m_smbh = 1.0000000000000002e-06 +m_smbh = 1.0e-06 #Smooth gravity at origin, for numerical reasons g_smoothing_radius = 0.0 diff --git a/inputs/cluster/hse.in b/inputs/cluster/hse.in index 7b24c397..121a3329 100644 --- a/inputs/cluster/hse.in +++ b/inputs/cluster/hse.in @@ -90,14 +90,14 @@ include_smbh_g = True #NFW parameters c_nfw = 6.0 -m_nfw_200 = 10.000000000000002 +m_nfw_200 = 10.0 #BCG parameters -m_bcg_s = 0.0010000000000000002 +m_bcg_s = 0.001 r_bcg_s = 0.004 #SMBH parameters -m_smbh = 1.0000000000000002e-06 +m_smbh = 1.0e-06 #Smooth gravity at origin, for numerical reasons g_smoothing_radius = 1e-6 diff --git a/inputs/cluster/schure.cooling b/inputs/cooling_tables/schure.cooling similarity index 100% rename from inputs/cluster/schure.cooling rename to inputs/cooling_tables/schure.cooling diff --git a/inputs/cluster/sutherland_dopita.cooling b/inputs/cooling_tables/sutherland_dopita.cooling similarity index 100% rename from inputs/cluster/sutherland_dopita.cooling rename to inputs/cooling_tables/sutherland_dopita.cooling From 4d53adf5a12b8271612654a4659dc0afdd41141f Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 13 Dec 2022 12:02:03 -0600 Subject: [PATCH 29/95] Cooling table quick-fixes to run sims --- src/hydro/srcterms/tabular_cooling.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 4be21cd1..99e7ac1a 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -329,11 +329,18 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt if (!dedt_valid) { if (sub_dt == min_sub_dt) { - PARTHENON_FAIL("FATAL ERROR in [TabularCooling::SubcyclingSplitSrcTerm]: " - "Minumum sub_dt leads to negative internal energy"); + //PARTHENON_FAIL("FATAL ERROR in [TabularCooling::SubcyclingSplitSrcTerm]: " + // "Minumum sub_dt leads to negative internal energy"); + + //HACK + //Even the minimum subcycle dt would lead to negative internal energy -- so just cool to the floor + sub_dt = (dt - sub_t); + internal_e_next_h = internal_e_floor; + reattempt_sub = false; + } else { + reattempt_sub = true; + sub_dt = min_sub_dt; } - reattempt_sub = true; - sub_dt = min_sub_dt; } else { // Compute error @@ -397,7 +404,11 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt sub_iter++; } - PARTHENON_REQUIRE(internal_e > internal_e_floor, "cooled below floor"); + //PARTHENON_REQUIRE(internal_e > internal_e_floor, "cooled below floor"); + //HACK + if(internal_e < internal_e_floor){ + internal_e = internal_e_floor; + } // Remove the cooling from the specific total energy cons(IEN, k, j, i) += rho * (internal_e - internal_e_initial); @@ -521,6 +532,7 @@ void TabularCooling::MixedIntSrcTerm(parthenon::MeshData *md, Real TabularCooling::EstimateTimeStep(MeshData *md) const { // Grab member variables for compiler + return std::numeric_limits::infinity(); //HACK // Everything needed by DeDt const Real mu_m_u_gm1_by_k_B = mu_m_u_gm1_by_k_B_; From 604d2da2d344376c7d9a77ce3a9e47142265f973 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 5 Jan 2023 11:26:35 -0600 Subject: [PATCH 30/95] Cleaned up cooling cfl hack --- src/hydro/srcterms/tabular_cooling.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 3a786471..e8a00555 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -10,11 +10,12 @@ // C++ headers #include +#include +#include // Parthenon headers #include #include -#include #include #include @@ -718,8 +719,11 @@ Real TabularCooling::EstimateTimeStep(MeshData *md) const { if (integrator_ == CoolIntegrator::townsend) { return std::numeric_limits::max(); } - // Grab member variables for compiler - return std::numeric_limits::infinity(); //HACK + + + if( cooling_time_cfl_ <= 0.0 || isnan(cooling_time_cfl_) || isinf(cooling_time_cfl_) ){ + return std::numeric_limits::infinity(); + } // Everything needed by DeDt const Real mu_m_u_gm1_by_k_B = mu_m_u_gm1_by_k_B_; From 8d537bb1d2d9e18216ca4991d5e839093ecf8cbc Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 5 Jan 2023 17:46:36 -0700 Subject: [PATCH 31/95] Thermal feedback fixes, Bondi fixes --- src/pgen/cluster/agn_feedback.cpp | 11 ++++++++--- src/pgen/cluster/agn_feedback.hpp | 2 +- src/pgen/cluster/agn_triggering.cpp | 2 +- src/pgen/cluster/cluster_gravity.hpp | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index fe0c691c..f216036d 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -45,6 +45,12 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_height", 0.02)), disabled_(pin->GetOrAddBoolean("problem/cluster/agn_feedback", "disabled", false)) { + //Normalize the thermal, kinetic, and magnetic fractions to sum to 1.0 + const Real total_frac = thermal_fraction_ + kinetic_fraction_ + magnetic_fraction_; + thermal_fraction_ = thermal_fraction_/total_frac; + kinetic_fraction_ = kinetic_fraction_/total_frac; + magnetic_fraction_ = magnetic_fraction_/total_frac; + // Add user history output variable for AGN power auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); if (!disabled_) { @@ -175,9 +181,8 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Determine if point is in sphere r<=thermal_radius if (r2 <= thermal_radius2) { // Add density at constant velocity and temperature - // AddDensityToConsAtFixedVelTemp(thermal_density_rate, cons, prim, eos, k, j, - // i); - // Apply heating + AddDensityToConsAtFixedVelTemp(thermal_density_rate, cons, prim, eos, k, j,i); + // Then apply heating cons(IEN, k, j, i) += thermal_feedback; // Add density at constant velocity // AddDensityToConsAtFixedVel(thermal_density_rate, cons, prim, eos, k, j, i); diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index 70cfeeac..e8c7eb19 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -25,7 +25,7 @@ namespace cluster { class AGNFeedback { public: const parthenon::Real fixed_power_; - const parthenon::Real thermal_fraction_, kinetic_fraction_, magnetic_fraction_; + parthenon::Real thermal_fraction_, kinetic_fraction_, magnetic_fraction_; // Efficiency converting mass to energy const parthenon::Real efficiency_; diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 88baf012..6711b8a7 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -345,7 +345,7 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData const Real cell_accretion_rate = prim(IDN, k, j, i) / total_mass * accretion_rate; - const Real cell_delta_rho = -cell_accretion_rate * dt; + const Real cell_delta_rho = -cell_accretion_rate/cell_volume * dt; AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); // Update the Primitives diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 5af240b5..52c4488b 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -126,7 +126,7 @@ class ClusterGravity { r_nfw_s_ = calc_R_nfw_s(rho_crit, M_nfw_200, c_nfw); GMC_nfw_ = calc_GMC_nfw(units.gravitational_constant(), M_nfw_200, c_nfw); - // Initialize the NFW Profile + // Initialize the BCG Profile alpha_bcg_s_ = pin->GetOrAddReal("problem/cluster/gravity", "alpha_bcg_s", 0.1); beta_bcg_s_ = pin->GetOrAddReal("problem/cluster/gravity", "beta_bcg_s", 1.43); const parthenon::Real M_bcg_s = From 889efa01f7a96aa358fdf01663155a810776d727 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 5 Jan 2023 17:47:01 -0700 Subject: [PATCH 32/95] WIP docs for Clusters --- docs/cluster.md | 310 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 docs/cluster.md diff --git a/docs/cluster.md b/docs/cluster.md new file mode 100644 index 00000000..e4493c06 --- /dev/null +++ b/docs/cluster.md @@ -0,0 +1,310 @@ +# Galaxy Cluster and Cluster-like Problem Setup + +This problem generator initializes an isolated ideal galaxy cluster (or a +galaxy-cluster-like object). Simulations begin as a spherically symmetric set up of +gas in hydrostatic equilibrium with a fixed defined gravitational potential and +an ACCEPT-like entropy profile. An initial magnetic tower can also be included. + +In addition to the fixed gravitational potential source term, the problem +generator also includes AGN feedback via any combination of the injection of a +magnetic tower, kinetic jet, and a flat volumetric thermal dump around the +fixed AGN. Feedback via the magnetic tower and jet can be set to precess around +the z-axis. The AGN feedback power can be set to a fixed power or triggered via +Boosted Bondi accretion, Bondi-Schaye accretion, or cold gas near the AGN. + +## Units + +All parameters in `` are defined in code units. Code units can +be defined under ` +#Units parameters +code_length_cgs = 3.085677580962325e+24 # in cm +code_mass_cgs = 1.98841586e+47 # in g +code_time_cgs = 3.15576e+16 # in s +``` +will set the code length-unit to 1 Mpc, the code mass to 10^14 solar masses, +and the code time to 1 Gyr. + + +## Fixed Gravitational Profile + +A gravitational profile can be defined including components from an NFW dark +matter halo, a brightest cluster galaxy (BCG), and a point-source central +supermassive black hole. This gravitational potential is used to determine +initial conditions in hydrostatic equilbrium and by default as a source term +during evolution. Parameters for the gravitationl profile are placed into ``. + + +The toggles to include different components are as follows: +``` + +include_nfw_g = True +which_bcg_g = HERNQUIST #or NONE or MATHEWS +include_smbh_g = True +``` +Where `include_nfw_g` for the NFW dark-matter halo (CITEME) is boolean; +`which_bcg_g` for the BCG can be `NONE` for no BCG, `HERNQUIST` for Hernquist +profile (CITEME), or `MATHEWS` for a Mathews (CITEME) profile, and +`include_smbh_g` for the SMBH is boolean. + +Parameters for the NFW profile are +``` + +c_nfw = 6.0 # Unitless +m_nfw_200 = 10.0 # in code_mass +``` +which adds a gravitational acceleration defined by + +(EQUATIONME) + + +Parameter for both a HERNQUIST and MATHEWS BCG are: +``` + + +m_bcg_s = 0.001 # in code_mass +r_bcg_s = 0.004 # in code_length +``` +where a HERNQUIST profile adds a gravitational acceleration defined by + +(EQUATIONME) + +and a MATHEWS profile adds a gravitational acceleration defined by + +(EQUATIONME) + +Gravitational acceleration from the SMBH is defined solely by its mass +``` + +m_smbh = 1.0e-06 # in code_mass +``` + +Some acceleration profiles may be ill-defined at the origin. For this, we provide a smoothing length parameter, +``` + +g_smoothing_radius = 0.0 # in code_length +``` +which works as a minimum r when the gravitational potential is applied. It +effectively modifies the gravitation acceleration to work as + +(EQUATIONME) + +By default, the gravitational profile used to create the initial conditions is +also used as an accelerating source term during evolution. This source term can +be turned off, letting this gravitational profile to only apply to +initialization, with the following parameter in ``. +``` + +gravity_srcterm = False +``` + +## Entropy Profile + +The `cluster` problem generator initializes a galaxy-cluster-like system with an entropy profile following the ACCEPT profile + +(EQUATIONME, DEFINE ENTROPY K) + +This profile is determined by these parameters +``` + +k_0 = 8.851337676479303e-121 # in FIXME +k_100 = 1.3277006514718954e-119 # in FIXME +r_k = 0.1 # in code_length +alpha_k = 1.1 # unitless +``` + +## Defining Initial Hydrostatic Equilibrium + +With a gravitational profile and initial entropy profile defined, the system of +equations for initial hydrostatic equilibrium are still not closed. In order to +close them, we fix the density of the cluster to a defined value at a defined +radius. This radius and density is set by the parameters +``` + +r_fix = 2.0 # in code_length +rho_fix = 0.01477557589278723 # in code_mass/code_length**3 +``` + +(FIXME) `r_sampling` and `max_dr` determine details about how the profile for +the initial hydrodynamic equilbrium is integrated. They can potentially be +removed. +``` + +r_fix = 2.0 # in code_length +r_sampling = 4.0 +max_dr = 0.001 +``` + +## AGN Triggering + +If AGN triggering is enabled, at the end of each time step, a mass accretion +rate `mdot` is determined from conditions around the AGN according to the +different triggering prescriptions. The accreted mass is removed from the gas +around the AGN, with details depending on each prescription explained below, +and is used as input for AGN feedback power. + +The triggering prescriptions currently implemented are "boosted Bondi accretion," "Bondi-Schaye accretion," and "cold gas." These modes can be chosen via +``` + +triggering_mode = COLD_GAS # or NONE, BOOSTED_BONDI, BONDI_SCHAYE +``` +where `triggering_mode=NONE` will disable AGN triggering. + +With BOOSTED_BONDI accretion, the mass rate of accretion follows + +(EQUATIONME) + +where `rho`, `v`, and `cs` are respectively the mass weighted density, +velocity, and sound speed within the accretion region. The mass of the SMBH, +the radius of the sphere of accretion around the AGN, and the `alpha` parameter +can be set with +``` + +m_smbh = 1.0e-06 # in code_mass + + +accretion_radius = 0.001 # in code_length +bondi_alpha= 100.0 # unitless +``` + +With BONDI_SCHAYE accretion, the `alpha` used for BOOSTED_BONDI accretion is modified to depend on the number density following: + +(EQUATIONME) + +where `n` is the mass weighted mean density within the accretion region and the parameter `n_0` and `beta` can be set with +``` + +bondi_n0= 2.9379989445851786e+72 # in 1/code_length**3 +bondi_beta= 2.0 # unitless +``` + +With both BOOSTED_BONDI and BONDI_SCHAYE accretion, mass is removed from each +cell within the accretion zone at a mass weighted rate. E.g. the mass in each +cell within the accretion region changes by +``` +new_cell_mass = cell_mass - cell_mass/total_mass*mdot*dt; +``` +where `total_mass` is the total mass within the accretion zone. This mass is +removed from the conserved variables such that the velocity and temperature of +the cell remains the same. Momentum and energy density thus will also change. + +With COLD_GAS accretion, the accretion rate becomes the total mass within the accretion zone equal to or +below a defined cold temperature threshold divided by a defined accretion +timescale. The temperature threshold and accretion timescale are defined by +``` + +cold_temp_thresh= 100000.0 +cold_t_acc= 0.1 +``` +Mass is removed from each cell in the accretion zone on the accretion +timescale. E.g. for each cell in the accretion zone with cold gas +``` +new_cell_mass = cell_mass - cell_mass/cold_t_acc*dt; +``` +As with the Bondi-like accretion prescriptions, this mass is removed such that +the velocity and temperature of the cell remains the same. Momentum and energy +density thus will also change. + + +## AGN Feedback + +AGN feedback can be both triggered via the mechanisms in the section above and with a fixed power. +``` + +fixed_power = 0.0 +efficiency = 0.001 +``` +Where and `mdot` calculated from AGN triggering will lead to an an AGN feedback +power of `agn_power = efficiency*mdot*c**2`. The fixed power and triggered +power are not mutually exclusive; if both `fixed_power` is defined and +triggering is enabled with a non-zero `efficiency`, then the `fixed_power` will +be added to the triggered AGN power. + + +AGN feedback can be injected via any combination of an injected magnetic tower, +a thermal dump around the AGN, and a kinetic jet. The fraction deposited into +each mechansim can be controlled via +``` + +magnetic_fraction = 0.3333 +thermal_fraction = 0.3333 +kinetic_fraction = 0.3333 +``` +These values are automatically normalized to sum to 1.0 at run time. + +Thermal feedback is deposited at a flat power density within a sphere of defined radius +``` + +thermal_radius = 0.0005 +``` +Mass is also injected into the sphere at a flat density rate with the existing +velocity and temperature to match the accreted mass proportioned to thermal +feedback, e.g. +``` +thermal_injected_mass = mdot * normalized_thermal_fraction; +``` + +Kinetic feedback is deposited into cylinder along the axis of the jet within a +defined radius and height above and below the plane of the AGN disk. +``` + +kinetic_jet_radius = 0.0005 +kinetic_jet_height = 0.0005 +``` +The axis of the jet can be set to precess with +``` + +jet_theta= 0.15 # in radians +jet_phi0= 0.2 # in radians +jet_phi_dot= 628.3185307179587 # in radians/code_time +``` +at defined precession angle off of the z-axis (`jet_theta`), an initial +azimuthal angle (`jet_phi0`), and an rate of azimuthal precession +(`jet_phi_dot`). + +Kinetic jet energy is injected at a flat power density within the cynlinder of +injection as purely kinetic energy. The injected mass will match the +proportioned kinetic feedback, e.g. +``` +kinetic_injected_mass = mdot * normalized_kinetic_fraction; +``` +and the injected momentum will total the injected kinetic feedback energy. Gas +energy desnity will remain unchanged. As a result, the injected mass density rate will be + +(EQUATIONME) + +and the velocity of the injected gas will be + +(EQUATIONME). + +Magnetic feedback is injected following (CITEME) where the injected magnetic field follows + +(EQUATIONME). + +The parameters `alpha` and `l` +``` + +alpha = 20 +l_scale = 0.001 +``` + +Mass is also injected along with the magnetic field following + +(EQUATIONME) + +``` + +l_mass_scale = 0.001 +``` + +A magnetic tower can also be inserted at runtime and injected at a fixed +increase in magnetic field, and additional mass can be injected at a fixed +rate. +``` + +initial_field = 0.12431560000204142 +fixed_field_rate = 1.0 +fixed_mass_rate = 1.0 +``` + From b0abe6a2d0f73209cc33ac7d7b1e519d2bd16f76 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 9 Jan 2023 11:59:46 -0700 Subject: [PATCH 33/95] Adding equations to cluster docs --- docs/cluster.md | 98 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 75 insertions(+), 23 deletions(-) diff --git a/docs/cluster.md b/docs/cluster.md index e4493c06..817c831c 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -56,8 +56,22 @@ m_nfw_200 = 10.0 # in code_mass ``` which adds a gravitational acceleration defined by -(EQUATIONME) - +$$ +g_{\text{NFW}}(r) = + \frac{G}{r^2} \frac{M_{NFW} \left [ \ln{\left(1 + \frac{r}{R_{NFW}} \right )} - \frac{r}{r+R_{NFW}} \right ]}{ \ln{\left(1 + c_{NFW}\right)} - \frac{ c_{NFW}}{1 + c_{NFW}} }. +$$ +The scale radius $R_{NFW}$ for the NFW profile is computed from +$$ +R_{NFW} = \left ( \frac{M_{NFW}}{ 4 \pi \rho_{NFW} \left [ \ln{\left ( 1 + c_{NFW} \right )} - c_{NFW}/\left(1 + c_{NFW} \right ) \right ] }\right )^{1/3} +$$ +where the scale density $\rho_{NFW}$ is computed from +$$ +\rho_{NFW} = \frac{200}{3} \rho_{crit} \frac{c_{NFW}^3}{\ln{\left ( 1 + c_{NFW} \right )} - c_{NFW}/\left(1 + c_{NFW} \right )}. +$$ +The critical density $\rho_{crit}$ is computed from +$$ + \frac{3 H_0^2}{8 \pi G}. +$$ Parameter for both a HERNQUIST and MATHEWS BCG are: ``` @@ -68,13 +82,15 @@ r_bcg_s = 0.004 # in code_length ``` where a HERNQUIST profile adds a gravitational acceleration defined by -(EQUATIONME) +$$ + g_{BCG}(r) = G \frac{ M_{BCG} }{R^2} \frac{1}{\left( 1 + \frac{r}{R}\right)^2} +$$ and a MATHEWS profile adds a gravitational acceleration defined by (EQUATIONME) -Gravitational acceleration from the SMBH is defined solely by its mass +Gravitational acceleration from the SMBH is inserted as a point source defined solely by its mass ``` m_smbh = 1.0e-06 # in code_mass @@ -88,7 +104,9 @@ g_smoothing_radius = 0.0 # in code_length which works as a minimum r when the gravitational potential is applied. It effectively modifies the gravitation acceleration to work as -(EQUATIONME) +$$ +\tilde{g} (r) = g( max( r, r_{smooth})) +$$ By default, the gravitational profile used to create the initial conditions is also used as an accelerating source term during evolution. This source term can @@ -102,9 +120,13 @@ gravity_srcterm = False ## Entropy Profile The `cluster` problem generator initializes a galaxy-cluster-like system with an entropy profile following the ACCEPT profile - -(EQUATIONME, DEFINE ENTROPY K) - +$$ + K(r) = K_{0} + K_{100} \left ( r/ 100 \text{ kpc} \right )^{\alpha_K} + $$ +where we are using the entropy $K$ is defined as +$$ + K \equiv \frac{ k_bT}{n_e^{2/3} } + $$ This profile is determined by these parameters ``` @@ -153,11 +175,14 @@ where `triggering_mode=NONE` will disable AGN triggering. With BOOSTED_BONDI accretion, the mass rate of accretion follows -(EQUATIONME) +$$ +\dot{M} = \alpha \frac { 2 \pi G^2 M^2_{SMBH} \hat {\rho} } { +\left ( \hat{v}^2 + \hat{c}_s^2 \right ) ^{3/2} } +$$ -where `rho`, `v`, and `cs` are respectively the mass weighted density, +where $\hat{rho}$, $\hat{v}$, and $\hat{c}_s$ are respectively the mass weighted density, velocity, and sound speed within the accretion region. The mass of the SMBH, -the radius of the sphere of accretion around the AGN, and the `alpha` parameter +the radius of the sphere of accretion around the AGN, and the $alpha$ parameter can be set with ``` @@ -170,7 +195,13 @@ bondi_alpha= 100.0 # unitless With BONDI_SCHAYE accretion, the `alpha` used for BOOSTED_BONDI accretion is modified to depend on the number density following: -(EQUATIONME) +$$ +\alpha = + \begin{cases} +1 & n \leq n_0 \\\\ + ( n/n_0 ) ^\beta & n > n_0\\\\ +\end{cases} +$$ where `n` is the mass weighted mean density within the accretion region and the parameter `n_0` and `beta` can be set with ``` @@ -271,27 +302,48 @@ kinetic_injected_mass = mdot * normalized_kinetic_fraction; ``` and the injected momentum will total the injected kinetic feedback energy. Gas energy desnity will remain unchanged. As a result, the injected mass density rate will be - -(EQUATIONME) - +$$ +\dot{\rho} = \frac{\dot{M} f_{kinetic}}{2 \pi h_{jet} r_{jet}^2} +$$ and the velocity of the injected gas will be - -(EQUATIONME). +$$ +c\sqrt{ 2 \epsilon } +$$ +where $\epsilon$ is the `efficiency` parameter described earlier in this section. Magnetic feedback is injected following (CITEME) where the injected magnetic field follows - -(EQUATIONME). - -The parameters `alpha` and `l` +$$ +\begin{align} +\mathcal{B}_r &=\mathcal{B}_0 2 \frac{h r}{\ell^2} \exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right )} \\\\ +\mathcal{B}_\theta &=\mathcal{B}_0 \alpha \frac{r}{\ell} \exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right ) } \\\\ +\mathcal{B}_h &=\mathcal{B}_0 2 \left( 1 - \frac{r^2}{\ell^2} \right ) \exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right )} \\\\ +\end{align} +$$ +which has the corresponding vector potential field +$$ +\begin{align} +\mathcal{A}_r &= 0 \\\\ +\mathcal{A}_{\theta} &= \mathcal{B}_0 \ell \frac{r}{\ell} \exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right )} \\\\ +\mathcal{A}_h &= \mathcal{B}_0 \ell \frac{\alpha}{2}\exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right )} +\end{align} +$$ +The parameters $\alpha$ and $\ell$ can be changed with ``` alpha = 20 l_scale = 0.001 ``` +When injected as a fraction of Mass is also injected along with the magnetic field following - -(EQUATIONME) +$$ +\dot{\rho} = \dot{\rho}_B * \exp{ \frac{ -r^2 + -h^2}{\ell^2} } +$$ +where $\dot{\rho}_B$ is set to +$$ +\dot{\rho}_B = \frac{3 \pi}{2} \frac{\dot{M} f_{magnetic}}{\ell^3} +$$ +so that the total mass injected matches the accreted mass propotioned to magnetic feedback. ``` From 08ae6b3011314d268bfa174ebaa212b0f8d2bf83 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 9 Jan 2023 12:00:06 -0700 Subject: [PATCH 34/95] Fix copyrights for cluster pgen --- src/pgen/cluster/agn_feedback.cpp | 8 ++++---- src/pgen/cluster/agn_feedback.hpp | 8 ++++---- src/pgen/cluster/agn_triggering.cpp | 8 ++++---- src/pgen/cluster/agn_triggering.hpp | 6 +++--- src/pgen/cluster/cluster_gravity.hpp | 6 +++--- src/pgen/cluster/cluster_utils.hpp | 12 ++++++------ src/pgen/cluster/entropy_profiles.hpp | 6 +++--- src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp | 2 +- src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp | 5 ++--- src/pgen/cluster/jet_coords.hpp | 4 ++-- src/pgen/cluster/magnetic_tower.cpp | 12 ++++++------ src/pgen/cluster/magnetic_tower.hpp | 10 +++++----- 12 files changed, 43 insertions(+), 44 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index f216036d..ef026c83 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -1,10 +1,10 @@ //======================================================================================== -// Athena++ astrophysical MHD code -// Copyright(C) 2014 James M. Stone and other code contributors +// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== -//! \file magnetic_tower.hpp -// \brief Class for defining magnetic tower +//! \file agn_feedback.cpp +// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic tower // Parthenon headers #include diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index e8c7eb19..d8d84b32 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -1,12 +1,12 @@ #ifndef CLUSTER_AGN_FEEDBACK_HPP_ #define CLUSTER_AGN_FEEDBACK_HPP_ //======================================================================================== -// Athena++ astrophysical MHD code -// Copyright(C) 2014 James M. Stone and other code contributors +// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== -//! \file hydro_agn_feedback.hpp -// \brief Class for defining hydrodynamic AGN feedback +//! \file agn_feedback.hpp +// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic tower // parthenon headers #include diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 6711b8a7..3f9ad793 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -1,10 +1,10 @@ //======================================================================================== -// Athena++ astrophysical MHD code -// Copyright(C) 2014 James M. Stone and other code contributors +// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== -//! \file magnetic_tower.hpp -// \brief Class for defining magnetic tower +//! \file agn_triggering.cpp +// \brief Class for computing AGN triggering from Bondi-like and cold gas accretion // Parthenon headers #include diff --git a/src/pgen/cluster/agn_triggering.hpp b/src/pgen/cluster/agn_triggering.hpp index decdf4f9..8236a291 100644 --- a/src/pgen/cluster/agn_triggering.hpp +++ b/src/pgen/cluster/agn_triggering.hpp @@ -1,12 +1,12 @@ #ifndef CLUSTER_AGN_TRIGGERING_HPP_ #define CLUSTER_AGN_TRIGGERING_HPP_ //======================================================================================== -// Athena++ astrophysical MHD code -// Copyright(C) 2014 James M. Stone and other code contributors +// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== //! \file agn_triggering.hpp -// \brief Class for AGN Triggering +// \brief Class for computing AGN triggering from Bondi-like and cold gas accretion // parthenon headers #include diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 52c4488b..5b31d0c4 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -1,12 +1,12 @@ +#ifndef CLUSTER_CLUSTER_GRAVITY_HPP_ +#define CLUSTER_CLUSTER_GRAVITY_HPP_ //======================================================================================== // AthenaPK - a performance portable block structured AMR astrophysical MHD code. -// Copyright (c) 2021, Athena-Parthenon Collaboration. All rights reserved. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== //! \file cluster_gravity.hpp // \brief Class for defining gravitational acceleration for a cluster+bcg+smbh -#ifndef CLUSTER_CLUSTER_GRAVITY_HPP_ -#define CLUSTER_CLUSTER_GRAVITY_HPP_ // Parthenon headers #include diff --git a/src/pgen/cluster/cluster_utils.hpp b/src/pgen/cluster/cluster_utils.hpp index 11f30b39..75a7de3c 100644 --- a/src/pgen/cluster/cluster_utils.hpp +++ b/src/pgen/cluster/cluster_utils.hpp @@ -1,12 +1,12 @@ -#ifndef CLUSTER_CLUSTER_UTILS_HPP_ -#define CLUSTER_CLUSTER_UTILS_HPP_ -//======================================================================================== -// Athena++ astrophysical MHD code -// Copyright(C) 2014 James M. Stone and other code contributors -// Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== +//// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +///// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. +///// Licensed under the 3-clause BSD License, see LICENSE file for details +/////======================================================================================== //! \file cluster_utils.hpp // \brief Utilities for galaxy cluster functions +#ifndef CLUSTER_CLUSTER_UTILS_HPP_ +#define CLUSTER_CLUSTER_UTILS_HPP_ // parthenon headers #include diff --git a/src/pgen/cluster/entropy_profiles.hpp b/src/pgen/cluster/entropy_profiles.hpp index 5e7d2361..1dc17531 100644 --- a/src/pgen/cluster/entropy_profiles.hpp +++ b/src/pgen/cluster/entropy_profiles.hpp @@ -1,12 +1,12 @@ +#ifndef CLUSTER_ENTROPY_PROFILES_HPP_ +#define CLUSTER_ENTROPY_PROFILES_HPP_ //======================================================================================== // AthenaPK - a performance portable block structured AMR astrophysical MHD code. -// Copyright (c) 2021, Athena-Parthenon Collaboration. All rights reserved. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== //! \file entropy profiles.hpp // \brief Classes defining initial entropy profile -#ifndef CLUSTER_ENTROPY_PROFILES_HPP_ -#define CLUSTER_ENTROPY_PROFILES_HPP_ // Parthenon headers #include diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp index 09bef818..54c8c906 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp @@ -254,4 +254,4 @@ template class HydrostaticEquilibriumSphere; -} // namespace cluster \ No newline at end of file +} // namespace cluster diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp index 7ab9ef31..3bd842ae 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp @@ -1,3 +1,5 @@ +#ifndef CLUSTER_HYDROSTATIC_EQUILIBRIUM_SPHERE_HPP_ +#define CLUSTER_HYDROSTATIC_EQUILIBRIUM_SPHERE_HPP_ //======================================================================================== // AthenaPK - a performance portable block structured AMR astrophysical MHD code. // Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. @@ -6,9 +8,6 @@ //! \file hydrostatic_equilbirum_sphere // \brief Class for initializing a sphere in hydrostatic equiblibrium -#ifndef CLUSTER_HYDROSTATIC_EQUILIBRIUM_SPHERE_HPP_ -#define CLUSTER_HYDROSTATIC_EQUILIBRIUM_SPHERE_HPP_ - // Parthenon headers #include #include diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp index ac7073b2..87933ce0 100644 --- a/src/pgen/cluster/jet_coords.hpp +++ b/src/pgen/cluster/jet_coords.hpp @@ -1,8 +1,8 @@ #ifndef CLUSTER_JET_COORDS_HPP_ #define CLUSTER_JET_COORDS_HPP_ //======================================================================================== -// Athena++ astrophysical MHD code -// Copyright(C) 2014 James M. Stone and other code contributors +// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== //! \file jet_coords.hpp diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index d8cfde0e..bb208c45 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -1,10 +1,10 @@ //======================================================================================== -// Athena++ astrophysical MHD code -// Copyright(C) 2014 James M. Stone and other code contributors -// Licensed under the 3-clause BSD License, see LICENSE file for details -//======================================================================================== -//! \file magnetic_tower.hpp -// \brief Class for defining magnetic tower +//// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +///// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. +///// Licensed under the 3-clause BSD License, see LICENSE file for details +/////======================================================================================== +//! \file magnetic_tower.cpp +// \brief Class for defining magnetic towers // Parthenon headers #include diff --git a/src/pgen/cluster/magnetic_tower.hpp b/src/pgen/cluster/magnetic_tower.hpp index bea30861..abf41272 100644 --- a/src/pgen/cluster/magnetic_tower.hpp +++ b/src/pgen/cluster/magnetic_tower.hpp @@ -1,12 +1,12 @@ #ifndef CLUSTER_MAGNETIC_TOWER_HPP_ #define CLUSTER_MAGNETIC_TOWER_HPP_ //======================================================================================== -// Athena++ astrophysical MHD code -// Copyright(C) 2014 James M. Stone and other code contributors -// Licensed under the 3-clause BSD License, see LICENSE file for details -//======================================================================================== +//// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +///// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. +///// Licensed under the 3-clause BSD License, see LICENSE file for details +/////======================================================================================== //! \file magnetic_tower.hpp -// \brief Class for defining magnetic tower +// \brief Class for defining magnetic towers // parthenon headers #include From a0f3acddaef0729b5220b77f472ff96b31525c6c Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 9 Jan 2023 12:29:36 -0700 Subject: [PATCH 35/95] Added Mathews BCG equation --- docs/cluster.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/cluster.md b/docs/cluster.md index 817c831c..dc7c0179 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -83,12 +83,17 @@ r_bcg_s = 0.004 # in code_length where a HERNQUIST profile adds a gravitational acceleration defined by $$ - g_{BCG}(r) = G \frac{ M_{BCG} }{R^2} \frac{1}{\left( 1 + \frac{r}{R}\right)^2} + g_{BCG}(r) = G \frac{ M_{BCG} }{R_{BCG}^2} \frac{1}{\left( 1 + \frac{r}{R_{BCG}}\right)^2} $$ - and a MATHEWS profile adds a gravitational acceleration defined by - -(EQUATIONME) +$$ +g_{BCG}(r) = \frac{1}{R_{BCG}^2} +\left [ +\left ( r/R_{BCG}\right )^{ 0.5975 / 3.206 \times 10^{-7} s_{BCG} } ++ \left ( r/R_{BCG}\right )^{ 0.5975 / 3.206 \times 10^{-7} s_{BCG} } +\right ]^{-1/s_{BCG}} +$$ +where $s_{BCG} = 0.9$ Gravitational acceleration from the SMBH is inserted as a point source defined solely by its mass ``` From 843e0950e1ba8dedf12667f8298d00aaced4ad98 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 9 Jan 2023 12:51:15 -0700 Subject: [PATCH 36/95] Fixed Mathews BCG profile to match reference --- docs/cluster.md | 30 +++++++++++++++------------- src/pgen/cluster/cluster_gravity.hpp | 4 ++-- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/cluster.md b/docs/cluster.md index dc7c0179..0bff6a4c 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -43,10 +43,12 @@ include_nfw_g = True which_bcg_g = HERNQUIST #or NONE or MATHEWS include_smbh_g = True ``` -Where `include_nfw_g` for the NFW dark-matter halo (CITEME) is boolean; -`which_bcg_g` for the BCG can be `NONE` for no BCG, `HERNQUIST` for Hernquist -profile (CITEME), or `MATHEWS` for a Mathews (CITEME) profile, and -`include_smbh_g` for the SMBH is boolean. +Where `include_nfw_g` for the NFW dark-matter halo ([Navarro +1997](doi.org/10.1086/304888)) is boolean; `which_bcg_g` for the BCG can be +`NONE` for no BCG, `HERNQUIST` for Hernquist profile ([Hernquist +1990](doi.org/10.1086/168845)), or `MATHEWS` for a Mathews ([Mathews +2006](doi.org/10.1086/499119)) profile, and `include_smbh_g` for the SMBH is +boolean. Parameters for the NFW profile are ``` @@ -73,7 +75,7 @@ $$ \frac{3 H_0^2}{8 \pi G}. $$ -Parameter for both a HERNQUIST and MATHEWS BCG are: +Parameters for both the HERNQUIST and MATHEWS BCG are controlled via: ``` @@ -81,17 +83,16 @@ m_bcg_s = 0.001 # in code_mass r_bcg_s = 0.004 # in code_length ``` where a HERNQUIST profile adds a gravitational acceleration defined by - $$ g_{BCG}(r) = G \frac{ M_{BCG} }{R_{BCG}^2} \frac{1}{\left( 1 + \frac{r}{R_{BCG}}\right)^2} $$ and a MATHEWS profile adds a gravitational acceleration defined by $$ g_{BCG}(r) = \frac{1}{R_{BCG}^2} -\left [ -\left ( r/R_{BCG}\right )^{ 0.5975 / 3.206 \times 10^{-7} s_{BCG} } -+ \left ( r/R_{BCG}\right )^{ 0.5975 / 3.206 \times 10^{-7} s_{BCG} } -\right ]^{-1/s_{BCG}} +\left \{ +\left [ \frac{\left ( r/R_{BCG}\right )^{ 0.5975}} {3.206 \times 10^{-7}} \right )^s_{BCG} }+ +\left [ \frac{\left ( r/R_{BCG}\right )^{ 1.849 }} {1.861 \times 10^{-6}} \right )^s_{BCG} } +\right \}^{-1/s_{BCG}} $$ where $s_{BCG} = 0.9$ @@ -152,10 +153,11 @@ radius. This radius and density is set by the parameters r_fix = 2.0 # in code_length rho_fix = 0.01477557589278723 # in code_mass/code_length**3 ``` - -(FIXME) `r_sampling` and `max_dr` determine details about how the profile for -the initial hydrodynamic equilbrium is integrated. They can potentially be -removed. +The parameters `r_sampling` and `max_dr` determine details about how the +profile for the initial hydrodynamic equilbrium is integrated. (FIXME -- these +parameters are esoteric and can potentially be removed. They were added at +somepoint to ensure that the HSE profile is integrated over a domain that +includes the current meshblock and the r_fix radius) ``` r_fix = 2.0 # in code_length diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 5b31d0c4..6d45aca6 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -166,8 +166,8 @@ class ClusterGravity { case BCG::MATHEWS: { const parthenon::Real s_bcg = 0.9; g_r += GMC_bcg_ * // Note: *cm**3*s**-2 //To make units work - pow(pow(r / r_bcg_s_, 0.5975 / 3.206e-7 * s_bcg) + - pow(pow(r / r_bcg_s_, 1.849 / 1.861e-6), s_bcg), + pow( pow( pow(r/r_bcg_s_, 0.5975) / 3.206e-7,s_bcg) + + pow( pow(r/r_bcg_s_, 1.849 ) / 1.861e-6,s_bcg), -1 / s_bcg); } break; case BCG::HERNQUIST: From e7ada14ac7f19bd86c57bd34c53a3d9823519f51 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 9 Jan 2023 13:04:32 -0700 Subject: [PATCH 37/95] Added citations to cluster docs --- docs/cluster.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/cluster.md b/docs/cluster.md index 0bff6a4c..3d2119e5 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -173,7 +173,11 @@ different triggering prescriptions. The accreted mass is removed from the gas around the AGN, with details depending on each prescription explained below, and is used as input for AGN feedback power. -The triggering prescriptions currently implemented are "boosted Bondi accretion," "Bondi-Schaye accretion," and "cold gas." These modes can be chosen via +The triggering prescriptions currently implemented are "boosted Bondi +accretion" ([Bondi 1952](doi.org/10.1093/mnras/112.2.195), [Meece +2017](doi.org/10.3847/1538-4357/aa6fb1)), "Bondi-Schaye accretion" ([Bondi and +Schaye 2009](doi.org/10.1111/j.1365-2966.2009.15043.x)), and "cold gas" +([Meece 2017](doi.org/10.3847/1538-4357/aa6fb1)). These modes can be chosen via ``` triggering_mode = COLD_GAS # or NONE, BOOSTED_BONDI, BONDI_SCHAYE @@ -199,9 +203,7 @@ m_smbh = 1.0e-06 # in code_mass accretion_radius = 0.001 # in code_length bondi_alpha= 100.0 # unitless ``` - With BONDI_SCHAYE accretion, the `alpha` used for BOOSTED_BONDI accretion is modified to depend on the number density following: - $$ \alpha = \begin{cases} @@ -302,8 +304,9 @@ azimuthal angle (`jet_phi0`), and an rate of azimuthal precession (`jet_phi_dot`). Kinetic jet energy is injected at a flat power density within the cynlinder of -injection as purely kinetic energy. The injected mass will match the -proportioned kinetic feedback, e.g. +injection as purely kinetic energy, following [Meece +2017](doi.org/10.1086/501499). The injected mass will match the proportioned +kinetic feedback, e.g. ``` kinetic_injected_mass = mdot * normalized_kinetic_fraction; ``` @@ -318,7 +321,8 @@ c\sqrt{ 2 \epsilon } $$ where $\epsilon$ is the `efficiency` parameter described earlier in this section. -Magnetic feedback is injected following (CITEME) where the injected magnetic field follows +Magnetic feedback is injected following ([Hui 2006](doi.org/10.1086/501499)) +where the injected magnetic field follows $$ \begin{align} \mathcal{B}_r &=\mathcal{B}_0 2 \frac{h r}{\ell^2} \exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right )} \\\\ From d694aca60f226d2e1441b135bd9ac16e37b70bca Mon Sep 17 00:00:00 2001 From: forrestglines Date: Mon, 9 Jan 2023 14:12:04 -0700 Subject: [PATCH 38/95] Fixing formatting issues in cluster.md --- docs/cluster.md | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/docs/cluster.md b/docs/cluster.md index 3d2119e5..21b29fce 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -60,7 +60,9 @@ which adds a gravitational acceleration defined by $$ g_{\text{NFW}}(r) = - \frac{G}{r^2} \frac{M_{NFW} \left [ \ln{\left(1 + \frac{r}{R_{NFW}} \right )} - \frac{r}{r+R_{NFW}} \right ]}{ \ln{\left(1 + c_{NFW}\right)} - \frac{ c_{NFW}}{1 + c_{NFW}} }. + \frac{G}{r^2} + \frac{M_{NFW} \left [ \ln{\left(1 + \frac{r}{R_{NFW}} \right )} - \frac{r}{r+R_{NFW}} \right ]} + { \ln{\left(1 + c_{NFW}\right)} - \frac{ c_{NFW}}{1 + c_{NFW}} } $$ The scale radius $R_{NFW}$ for the NFW profile is computed from $$ @@ -83,17 +85,21 @@ m_bcg_s = 0.001 # in code_mass r_bcg_s = 0.004 # in code_length ``` where a HERNQUIST profile adds a gravitational acceleration defined by + $$ g_{BCG}(r) = G \frac{ M_{BCG} }{R_{BCG}^2} \frac{1}{\left( 1 + \frac{r}{R_{BCG}}\right)^2} $$ + and a MATHEWS profile adds a gravitational acceleration defined by + $$ g_{BCG}(r) = \frac{1}{R_{BCG}^2} -\left \{ -\left [ \frac{\left ( r/R_{BCG}\right )^{ 0.5975}} {3.206 \times 10^{-7}} \right )^s_{BCG} }+ -\left [ \frac{\left ( r/R_{BCG}\right )^{ 1.849 }} {1.861 \times 10^{-6}} \right )^s_{BCG} } -\right \}^{-1/s_{BCG}} +\left ( +\left [ \frac{\left ( r/R_{BCG}\right )^{ 0.5975}} {3.206 \times 10^{-7}} \right ]^{s_{BCG}} + +\left [ \frac{\left ( r/R_{BCG}\right )^{ 1.849 }} {1.861 \times 10^{-6}} \right ]^{s_{BCG}} +\right )^{-1/s_{BCG}} $$ + where $s_{BCG} = 0.9$ Gravitational acceleration from the SMBH is inserted as a point source defined solely by its mass @@ -126,13 +132,17 @@ gravity_srcterm = False ## Entropy Profile The `cluster` problem generator initializes a galaxy-cluster-like system with an entropy profile following the ACCEPT profile + $$ K(r) = K_{0} + K_{100} \left ( r/ 100 \text{ kpc} \right )^{\alpha_K} $$ + where we are using the entropy $K$ is defined as + $$ K \equiv \frac{ k_bT}{n_e^{2/3} } $$ + This profile is determined by these parameters ``` @@ -204,6 +214,7 @@ accretion_radius = 0.001 # in code_length bondi_alpha= 100.0 # unitless ``` With BONDI_SCHAYE accretion, the `alpha` used for BOOSTED_BONDI accretion is modified to depend on the number density following: + $$ \alpha = \begin{cases} @@ -312,17 +323,22 @@ kinetic_injected_mass = mdot * normalized_kinetic_fraction; ``` and the injected momentum will total the injected kinetic feedback energy. Gas energy desnity will remain unchanged. As a result, the injected mass density rate will be + $$ \dot{\rho} = \frac{\dot{M} f_{kinetic}}{2 \pi h_{jet} r_{jet}^2} $$ + and the velocity of the injected gas will be + $$ c\sqrt{ 2 \epsilon } $$ + where $\epsilon$ is the `efficiency` parameter described earlier in this section. Magnetic feedback is injected following ([Hui 2006](doi.org/10.1086/501499)) where the injected magnetic field follows + $$ \begin{align} \mathcal{B}_r &=\mathcal{B}_0 2 \frac{h r}{\ell^2} \exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right )} \\\\ @@ -330,7 +346,9 @@ $$ \mathcal{B}_h &=\mathcal{B}_0 2 \left( 1 - \frac{r^2}{\ell^2} \right ) \exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right )} \\\\ \end{align} $$ + which has the corresponding vector potential field + $$ \begin{align} \mathcal{A}_r &= 0 \\\\ @@ -338,6 +356,7 @@ $$ \mathcal{A}_h &= \mathcal{B}_0 \ell \frac{\alpha}{2}\exp{ \left ( \frac{-r^2 - h^2}{\ell^2} \right )} \end{align} $$ + The parameters $\alpha$ and $\ell$ can be changed with ``` @@ -347,13 +366,17 @@ l_scale = 0.001 When injected as a fraction of Mass is also injected along with the magnetic field following + $$ \dot{\rho} = \dot{\rho}_B * \exp{ \frac{ -r^2 + -h^2}{\ell^2} } $$ + where $\dot{\rho}_B$ is set to + $$ \dot{\rho}_B = \frac{3 \pi}{2} \frac{\dot{M} f_{magnetic}}{\ell^3} $$ + so that the total mass injected matches the accreted mass propotioned to magnetic feedback. ``` From 21ee06c8d58c292ac2608c2d1a43d971b2b05678 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 9 Jan 2023 20:25:38 -0600 Subject: [PATCH 39/95] Added ipython notebook for generating cluster inputs --- .gitignore | 4 + inputs/cluster/generate_cluster_input.ipynb | 588 ++++++++++++++++++++ 2 files changed, 592 insertions(+) create mode 100644 inputs/cluster/generate_cluster_input.ipynb diff --git a/.gitignore b/.gitignore index a325e21d..5dfd5c88 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ compile_commands.json # CMake Test files Testing + +# generate_cluster_input.ipynb files +inputs/cluster/generate_cluster_input.ipynb +inputs/cluster/my_cluster.input diff --git a/inputs/cluster/generate_cluster_input.ipynb b/inputs/cluster/generate_cluster_input.ipynb new file mode 100644 index 00000000..484e9754 --- /dev/null +++ b/inputs/cluster/generate_cluster_input.ipynb @@ -0,0 +1,588 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "3f50554f-678e-4a0c-a1a6-cc2a634393b6", + "metadata": {}, + "source": [ + "# Generate AthenaPK inputs for Cluster-like Objects\n", + "\n", + "Notebook to help with generating AthenaPK input files for running cluster-like simulations with the `cluster` problem generator, including AGN feedback and triggering. Check `docs/cluster.md` for more details on the components and parameters of the `cluster` problem generator. Every section marked `CHANGEME` is intended to be modified to change the initial setup.\n", + "\n", + "The `cluster` problem generator uses code units for parameter definitions. This notebook manages the conversion from astronomical units to code units.\n", + "\n", + "Required Python libraries:\n", + "\n", + "- [`unyt`](https://unyt.readthedocs.io/en/stable/), tested with `unyt v2.9.2`\n", + "- [`numpy`](https://numpy.org/), tested with `numpy 1.23.1`\n", + "\n", + "Tested with Python 3.9" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "40314e1e", + "metadata": {}, + "outputs": [], + "source": [ + "import unyt\n", + "import numpy as np\n", + "import copy\n", + "import itertools\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "id": "9b1c6149-a856-4dd9-ad87-22f3ed10ac44", + "metadata": { + "tags": [] + }, + "source": [ + "## CHANGEME: `filename` to write input file to\n", + "\n", + "Make sure the path containing the filename exists" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "963663a7-2148-4163-b97f-57af8bb33aaa", + "metadata": {}, + "outputs": [], + "source": [ + "filename = \"my_cluster.input\"" + ] + }, + { + "cell_type": "markdown", + "id": "0a2dd78b", + "metadata": {}, + "source": [ + "## CHANGEME: Define the code units to use throughout the file\n", + "\n", + "Note that you need to reload the notebook if you change these" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "5f30640c", + "metadata": {}, + "outputs": [], + "source": [ + "# Use MPC, 1e14 Msun, and 1 Gyr for code units\n", + "unyt.define_unit(\"code_length\",(1,unyt.Mpc))\n", + "unyt.define_unit(\"code_mass\",(1e14,unyt.Msun))\n", + "unyt.define_unit(\"code_time\",(1,unyt.Gyr))" + ] + }, + { + "cell_type": "markdown", + "id": "93908a05", + "metadata": {}, + "source": [ + "## CHANGEME: Define AthenaPK parameters for the different general and cluster modules\n", + "\n", + "Read `docs/cluster.md` for more detailed descriptions " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "779e5a45", + "metadata": {}, + "outputs": [], + "source": [ + "params_text = f\"\"\"\n", + "\n", + "fluid = glmmhd\n", + "gamma = 5./3. # gamma = C_p/C_v\n", + "eos = adiabatic\n", + "riemann = hlld\n", + "reconstruction = plm\n", + "use_scratch = false\n", + "scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM\n", + "Tfloor = {unyt.unyt_quantity(1e4,\"K\").v}\n", + "\n", + "first_order_flux_correct = True\n", + "\n", + "He_mass_fraction = 0.25\n", + "\n", + "\n", + "#Units parameters\n", + "code_length_cgs = {unyt.unyt_quantity(1,\"code_length\").in_units(\"cm\").v}\n", + "code_mass_cgs = {unyt.unyt_quantity(1,\"code_mass\").in_units(\"g\").v}\n", + "code_time_cgs = {unyt.unyt_quantity(1,\"code_time\").in_units(\"s\").v}\n", + "\n", + "\n", + "enable_cooling = tabular\n", + "table_filename = schure.cooling\n", + "log_temp_col = 0 # Column to read temperature in cooling table\n", + "log_lambda_col = 1 # Column to read lambda in cooling table\n", + "lambda_units_cgs = {unyt.unyt_quantity(1,\"erg*cm**3/s\").v}\n", + "\n", + "integrator = townsend\n", + "cfl = 0.1 # Restricts hydro step based on minimum cooling time, disabled for \"integrator=townsend\"\n", + "min_timestep = {unyt.unyt_quantity(1,\"Gyr\").in_units(\"code_time\").v}\n", + "max_iter = 100\n", + "d_e_tol = 1e-8\n", + "d_log_temp_tol = 1e-8\n", + "\n", + "\n", + "hubble_parameter = {unyt.unyt_quantity(70,\"km*s**-1*Mpc**-1\").in_units(\"1/code_time\").v}\n", + "\n", + "\n", + "#Include gravity as a source term\n", + "gravity_srcterm = True\n", + "\n", + "#Which gravitational fields to include\n", + "include_nfw_g = True\n", + "which_bcg_g = HERNQUIST\n", + "include_smbh_g = True\n", + "\n", + "#NFW parameters\n", + "c_nfw = 6.0\n", + "m_nfw_200 = {unyt.unyt_quantity(1e15,\"Msun\").in_units(\"code_mass\").v}\n", + "\n", + "#BCG parameters\n", + "m_bcg_s = {unyt.unyt_quantity(1e11,\"Msun\").in_units(\"code_mass\").v}\n", + "r_bcg_s = {unyt.unyt_quantity(4,\"kpc\").in_units(\"code_length\").v}\n", + "\n", + "#SMBH parameters\n", + "m_smbh = {unyt.unyt_quantity(1e8,\"Msun\").in_units(\"code_mass\").v}\n", + "\n", + "#Smooth gravity at origin, for numerical reasons\n", + "g_smoothing_radius = {unyt.unyt_quantity(0,\"code_length\").v}\n", + "\n", + "\n", + "#Entropy profile parameters\n", + "k_0 = {unyt.unyt_quantity(10,\"keV*cm**2\").in_units(\"code_length**4*code_mass/code_time**2\").v}\n", + "k_100 = {unyt.unyt_quantity(150,\"keV*cm**2\").in_units(\"code_length**4*code_mass/code_time**2\").v}\n", + "r_k = {unyt.unyt_quantity(100,\"kpc\").in_units(\"code_length\").v}\n", + "alpha_k = 1.1\n", + "\n", + "\n", + "#Fix density at radius to close system of equations\n", + "r_fix = {unyt.unyt_quantity(2e3,\"kpc\").in_units(\"code_length\").v}\n", + "rho_fix = {unyt.unyt_quantity(1e-28,\"g*cm**-3\").in_units(\"code_mass/code_length**3\").v}\n", + "\n", + "#Building the radii at which to sample initial rho,P\n", + "r_sampling = 4.0\n", + "max_dr = 0.001\n", + "\n", + "\n", + "#Which triggering mode (BOOSTED_BONDI, BOOTH_SCHAYE, COLD_GAS, NONE)\n", + "triggering_mode = COLD_GAS\n", + "\n", + "#Radius of accretion for triggering\n", + "accretion_radius = {unyt.unyt_quantity(1,\"kpc\").in_units(\"code_length\").v}\n", + "\n", + "#BOOSTED_BONDI and BOOTH_SCHAYE Parameters\n", + "bondi_alpha = 100.0\n", + "bondi_beta = 2.0\n", + "bondi_n0 = {unyt.unyt_quantity(0.1,\"cm**-3\").in_units(\"code_length**-3\").v}\n", + "\n", + "#COLD_GAS Parameters\n", + "cold_temp_thresh = {unyt.unyt_quantity(1e5,\"K\").in_units(\"K\").v}\n", + "cold_t_acc = {unyt.unyt_quantity(100,\"Myr\").in_units(\"code_time\").v}\n", + "\n", + "write_to_file = True\n", + "\n", + "\n", + "jet_theta = 0.15\n", + "jet_phi_dot = {(2*np.pi/unyt.unyt_quantity(10,\"Myr\")).in_units(\"code_time**-1\").v}\n", + "jet_phi0 = 0.2\n", + "\n", + "\n", + "# Fixed power, added on top of triggered feedback\n", + "fixed_power = {unyt.unyt_quantity(0,\"erg/s\").in_units(\"code_length**2*code_mass/code_time**3\").v}\n", + "\n", + "# Efficieny in conversion of AGN accreted mass to AGN feedback energy\n", + "efficiency = 1e-3\n", + "\n", + "# Fraction allocated to different mechanisms\n", + "magnetic_fraction = 0.333\n", + "thermal_fraction = 0.333\n", + "kinetic_fraction = 0.333\n", + "\n", + "# Thermal feedback parameters\n", + "thermal_radius = {unyt.unyt_quantity(0.5,\"kpc\").in_units(\"code_length\").v}\n", + "\n", + "# Kinetic jet feedback parameters\n", + "kinetic_jet_radius = {unyt.unyt_quantity(0.5,\"kpc\").in_units(\"code_length\").v}\n", + "kinetic_jet_height = {unyt.unyt_quantity(0.5,\"kpc\").in_units(\"code_length\").v}\n", + "\n", + "\n", + "alpha = 20\n", + "l_scale = {unyt.unyt_quantity(1,\"kpc\").in_units(\"code_length\").v}\n", + "initial_field = {unyt.unyt_quantity(1e-6,\"G\").in_units(\"code_mass**(1/2)*code_length**(-1/2)*code_time**-1\").v}\n", + "l_mass_scale = {unyt.unyt_quantity(1,\"kpc\").in_units(\"code_length\").v}\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "636d1d12-9b0c-4a21-bded-5dc5e9feb40d", + "metadata": {}, + "source": [ + "## CHANGEME: Define the data output for the simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "43effc3a-425b-4568-bb5c-534034afd2e5", + "metadata": {}, + "outputs": [], + "source": [ + "output_text = f\"\"\"\n", + "\n", + "file_type = hst # History data dump\n", + "dt = {unyt.unyt_quantity(0.1,\"Myr\")} # time increment between outputs\n", + "\n", + "\n", + "file_type = rst # restart data dump\n", + "dt = {unyt.unyt_quantity(1.0,\"Myr\")} # Time increment between outputs\n", + "id = restart\n", + "\n", + "# hdf5_compression_level = 0\n", + "use_final_label = false\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "id": "2f219a5f-8f79-4035-a023-03a4a09a2b9e", + "metadata": {}, + "source": [ + "## CHANGEME: Define the time constraints for the simulation" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "11db692a-a428-42f6-9e90-bec04cf7584d", + "metadata": {}, + "outputs": [], + "source": [ + "time_text=f\"\"\"\n", + "\n", + "cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number\n", + "tlim = {unyt.unyt_quantity(0.1,\"Myr\")} # time limit\n", + "integrator = vl2 # time integration algorithm\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c988b2d0-6ef6-43a9-9d94-e1faa5d1ab3c", + "metadata": {}, + "outputs": [], + "source": [ + "## CHANGEME: Define static mesh refinement levels. Used below by `smr_generator` to make the mesh input" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3bbd06c1-3390-40f0-83d3-d16fc6d1441b", + "metadata": {}, + "outputs": [], + "source": [ + "# Number of cells on each side in base mesh\n", + "base_nx = 64\n", + "# List of levels of refinement for SMR regions\n", + "base_width = unyt.unyt_quantity(200,\"kpc\")\n", + "\n", + "#List of levels of refinement for SMR regions\n", + "smr_levels = [2,]\n", + "#List of widths (in code length units) of SMR regions\n", + "smr_widths = unyt.unyt_array([25,],\"kpc\")\n", + "\n", + "# Number of cells on each side of meshblocks\n", + "mb_nx=32\n" + ] + }, + { + "cell_type": "markdown", + "id": "798f7b28", + "metadata": {}, + "source": [ + "## Define different mesh sizes/hierarchies\n", + "\n", + "Define an SMR mesh for the simulation. We provide an automatically generated SMR mesh with `smr_generator`, or you can craft your SMR mesh by hand." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d0c61c53-3959-4208-9f80-07d2c6255ebf", + "metadata": {}, + "outputs": [], + "source": [ + "def smr_generator(base_nx, base_width,\n", + " smr_levels,smr_widths,\n", + " mb_nx=32,quiet=False):\n", + " \"\"\"\n", + " Helper function to quickly define static-mesh refinement meshes for AthenaPK.\n", + " By default, prints out information like smallest cell size, total number of\n", + " cells, estimated data outputs, and estimated NVIDIA A100s needed to run the\n", + " simulation.\n", + " \n", + " Parameters:\n", + " base_nx : Number of cells on each side in base mesh\n", + " base_width : Width of base mesh (in code length units)\n", + "\n", + " smr_levels : List of levels of refinement for SMR regions\n", + " smr_widths : List of widths (in code length units) of SMR regions\n", + "\n", + " mb_nx=32 : Number of cells on each side of meshblocks\n", + " quiet=False : Silence printing of SMR information\n", + " \n", + " Returns: mesh_text, info\n", + " mesh_text: \n", + " \"\"\"\n", + " base_width = base_width.in_units(\"code_length\").v\n", + " smr_widths = smr_widths.in_units(\"code_length\").v\n", + " \n", + " base_dx = base_width/base_nx\n", + " \n", + " specified_widths = {0:base_width}\n", + " for level,width in zip(smr_levels,smr_widths):\n", + " specified_widths[level] = width\n", + " \n", + " #Setup each of the SMR levels to determine the true necessary widths\n", + " levels = np.arange(np.max(smr_levels,0)+1,dtype=int)\n", + " \n", + " meshes = {level:{\"dx\":(base_dx/(2.**level))} for level in levels}\n", + " \n", + " #Assume even number of mesh blocks, using this function\n", + " def ceil_even(x):\n", + " return int(np.ceil(x/2.)*2)\n", + " \n", + " #Create levels for static refinement, starting from highest level\n", + " level = levels[-1]\n", + " #Full number of meshblocks to cover the level along a side\n", + " meshes[level][\"full_nx_mb\"] = ceil_even( specified_widths[level]/(meshes[level][\"dx\"]*mb_nx))\n", + " #Full number of cells to cover level\n", + " meshes[level][\"full_nx\"] = meshes[level][\"full_nx_mb\"]*mb_nx\n", + " #Actual number of meshblocks in this level\n", + " meshes[level][\"n_mb\"] = meshes[level][\"full_nx_mb\"]**3\n", + " \n", + " meshes[level][\"width\"] = meshes[level][\"full_nx\"]*meshes[level][\"dx\"]\n", + " \n", + " #Compute widths of lower levels, extrapolating from highest level\n", + " for level,finer_level in reversed(list(zip(levels[:-1],levels[1:]))):\n", + " dx = meshes[level][\"dx\"]\n", + " \n", + " #This level's width is the max of the specified level width, expanded to fit with \n", + " #mesh block sizes, or the higher SMR level with 2 buffering mesh blocks on this level\n", + " if level in specified_widths.keys():\n", + " mb_specified_width = ceil_even( specified_widths[level]/(dx*mb_nx))*mb_nx*dx\n", + " else:\n", + " mb_specified_width = 0\n", + " meshes[level][\"width\"] = np.max([\n", + " mb_specified_width,\n", + " meshes[finer_level][\"width\"] + 2*mb_nx*dx])\n", + " \n", + " #Calculate number of cells to cover full length of level\n", + " meshes[level][\"full_nx\"] = int(meshes[level][\"width\"]/dx)\n", + " #Calculate number of meshblocks along a side to cover full level\n", + " meshes[level][\"full_nx_mb\"] = int(meshes[level][\"full_nx\"]/mb_nx)\n", + " #Calculate total number of meshblocks in this level, subtracting \n", + " #the blocks already covered in a higher level\n", + " meshes[level][\"n_mb\"] = int( meshes[level][\"full_nx_mb\"]**3 \n", + " - (meshes[finer_level][\"width\"]/(dx*mb_nx))**3)\n", + " \n", + " \n", + " #Flesh out details of all levels\n", + " for level in levels:\n", + " \n", + " meshes[level][\"xmax\"] = meshes[level][\"width\"]/2. ##Needed for creating the input file\n", + " \n", + " if level in specified_widths.keys():\n", + " meshes[level][\"specified_width_used\"] = ( meshes[level][\"width\"] == specified_widths[level])\n", + " else:\n", + " meshes[level][\"specified_width_used\"] = True\n", + " \n", + " meshes[level][\"total_cells\"] = meshes[level][\"n_mb\"]*mb_nx**3\n", + " \n", + " info = {}\n", + " info[\"all_sane\"] = np.all( [mesh[\"specified_width_used\"] for mesh in meshes.values()] )\n", + " info[\"total_cells\"] = np.sum([mesh[\"total_cells\"] for mesh in meshes.values()])\n", + " info[\"total_n_mb\"] = np.sum([mesh[\"n_mb\"] for mesh in meshes.values()])\n", + "\n", + " bytes_per_real = 8\n", + "\n", + " \n", + " reals_output_per_cell = 9\n", + " reals_used_per_cell = reals_output_per_cell*13\n", + "\n", + " info[\"total_used_memory\"] = info[\"total_cells\"]*bytes_per_real*reals_used_per_cell\n", + " info[\"total_output_memory\"] = info[\"total_cells\"]*bytes_per_real*reals_output_per_cell\n", + " \n", + " if not quiet:\n", + " \n", + " finest_dx = unyt.unyt_quantity(meshes[levels[-1]][\"dx\"],\"code_length\")\n", + " print(f\"Finest level covered by { finest_dx } , { finest_dx.in_units('pc') } cells\" )\n", + " \n", + " print(\"Do level widths match specified widths: \", info[\"all_sane\"])\n", + " print(\"\\t Widths: \",[ mesh[\"width\"] for mesh in meshes.values()])\n", + " print(\"\\t NX: \",[ mesh[\"full_nx\"] for mesh in meshes.values()])\n", + " print(\"\\t NX Meshblocks: \",[ mesh[\"full_nx_mb\"] for mesh in meshes.values()])\n", + " print(\"\\t N Meshblocks: \",[ mesh[\"n_mb\"] for mesh in meshes.values()])\n", + " \n", + " print(f\"Total cells: {info['total_cells']} or aprox. {np.cbrt(info['total_cells']):.1f}**3\")\n", + " print(f\"Total meshblocks: {info['total_n_mb']}\" )\n", + " print(f\"Total memory needed: {info['total_used_memory']/1e9} GB\")\n", + " print(f\"Total memory per output: {info['total_output_memory']/1e9} GB\")\n", + " print(f\"A100s needed: {info['total_used_memory']/40e9} \")\n", + " \n", + " print()\n", + "\n", + " #Base mesh text\n", + " base_xmax = base_width/2.\n", + " base_mesh_text = f\"\"\"\n", + "\n", + "refinement = static\n", + "nghost = 2\n", + "\n", + "nx1 = {base_nx} # Number of zones in X1-direction\n", + "x1min =-{base_xmax} # minimum value of X1\n", + "x1max = {base_xmax} # maximum value of X1\n", + "ix1_bc = outflow # inner-X1 boundary flag\n", + "ox1_bc = outflow # outer-X1 boundary flag\n", + "\n", + "nx2 = {base_nx} # Number of zones in X2-direction\n", + "x2min =-{base_xmax} # minimum value of X2\n", + "x2max = {base_xmax} # maximum value of X2\n", + "ix2_bc = outflow # inner-X2 boundary flag\n", + "ox2_bc = outflow # outer-X2 boundary flag\n", + "\n", + "nx3 = {base_nx} # Number of zones in X3-direction\n", + "x3min =-{base_xmax} # minimum value of X3\n", + "x3max = {base_xmax} # maximum value of X3\n", + "ix3_bc = outflow # inner-X3 boundary flag\n", + "ox3_bc = outflow # outer-X3 boundary flag\n", + "\n", + "\n", + "nx1 = {mb_nx} # Number of zones in X1-direction\n", + "nx2 = {mb_nx} # Number of zones in X2-direction\n", + "nx3 = {mb_nx} # Number of zones in X3-direction\n", + "\n", + "\"\"\"\n", + " \n", + " #\n", + " smr_texts = []\n", + " for level in smr_levels:\n", + " smr_texts.append(\n", + "f\"\"\"\n", + "\n", + "x1min = -{meshes[level][\"xmax\"]} \n", + "x1max = {meshes[level][\"xmax\"]}\n", + "x2min = -{meshes[level][\"xmax\"]}\n", + "x2max = {meshes[level][\"xmax\"]}\n", + "x3min = -{meshes[level][\"xmax\"]}\n", + "x3max = {meshes[level][\"xmax\"]}\n", + "level = {level}\n", + "\n", + "\"\"\")\n", + " return base_mesh_text + \"\".join(smr_texts),info" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "54cafbc6-e20d-49f1-8a57-10e5bbec52ab", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Finest level covered by 0.00078125 code_length , 781.25 pc cells\n", + "Do level widths match specified widths: False\n", + "\t Widths: [0.35000000000000003, 0.15000000000000002, 0.05]\n", + "\t NX: [112, 96, 64]\n", + "\t NX Meshblocks: [3, 3, 2]\n", + "\t N Meshblocks: [23, 26, 8]\n", + "Total cells: 1867776 or aprox. 123.2**3\n", + "Total meshblocks: 57\n", + "Total memory needed: 1.748238336 GB\n", + "Total memory per output: 0.134479872 GB\n", + "A100s needed: 0.0437059584 \n", + "\n" + ] + } + ], + "source": [ + "mesh_text,mesh_info = smr_generator( base_nx, base_width,\n", + " smr_levels, smr_widths,\n", + " mb_nx, quiet=False)\n", + "# print(mesh_text)" + ] + }, + { + "cell_type": "markdown", + "id": "e8c95676-83e2-4347-8750-cf06aa1e293d", + "metadata": {}, + "source": [ + "## Write input file to `filename`" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "cc6b5970", + "metadata": {}, + "outputs": [], + "source": [ + "input_text = f\"\"\" \n", + "# File autogenerated with Python script\n", + "# Changes might be overwritten!\n", + "\n", + "problem = Isolated galaxy cluster\n", + "\n", + "\n", + "problem_id = cluster # problem ID: basename of output filenames\n", + "\n", + "{output_text}\n", + "\n", + "{time_text}\n", + "\n", + "{mesh_text}\n", + "\n", + "{params_text}\n", + "\n", + "\"\"\"\n", + "\n", + "with open(filename,\"w\") as f:\n", + " f.write(input_text)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3.9", + "language": "python", + "name": "py39" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 436da8975e7579acba55fbd8651701efb3ea215d Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 10 Jan 2023 11:07:06 -0600 Subject: [PATCH 40/95] Cleaned up cluster notebook --- inputs/cluster/generate_cluster_input.ipynb | 43 ++++++--------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/inputs/cluster/generate_cluster_input.ipynb b/inputs/cluster/generate_cluster_input.ipynb index 484e9754..e436fb1e 100644 --- a/inputs/cluster/generate_cluster_input.ipynb +++ b/inputs/cluster/generate_cluster_input.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "40314e1e", "metadata": {}, "outputs": [], @@ -47,7 +47,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "963663a7-2148-4163-b97f-57af8bb33aaa", "metadata": {}, "outputs": [], @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "5f30640c", "metadata": {}, "outputs": [], @@ -90,7 +90,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "779e5a45", "metadata": {}, "outputs": [], @@ -232,7 +232,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "43effc3a-425b-4568-bb5c-534034afd2e5", "metadata": {}, "outputs": [], @@ -262,7 +262,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "11db692a-a428-42f6-9e90-bec04cf7584d", "metadata": {}, "outputs": [], @@ -277,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "c988b2d0-6ef6-43a9-9d94-e1faa5d1ab3c", "metadata": {}, "outputs": [], @@ -287,7 +287,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "3bbd06c1-3390-40f0-83d3-d16fc6d1441b", "metadata": {}, "outputs": [], @@ -318,7 +318,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "d0c61c53-3959-4208-9f80-07d2c6255ebf", "metadata": {}, "outputs": [], @@ -495,29 +495,10 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "54cafbc6-e20d-49f1-8a57-10e5bbec52ab", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Finest level covered by 0.00078125 code_length , 781.25 pc cells\n", - "Do level widths match specified widths: False\n", - "\t Widths: [0.35000000000000003, 0.15000000000000002, 0.05]\n", - "\t NX: [112, 96, 64]\n", - "\t NX Meshblocks: [3, 3, 2]\n", - "\t N Meshblocks: [23, 26, 8]\n", - "Total cells: 1867776 or aprox. 123.2**3\n", - "Total meshblocks: 57\n", - "Total memory needed: 1.748238336 GB\n", - "Total memory per output: 0.134479872 GB\n", - "A100s needed: 0.0437059584 \n", - "\n" - ] - } - ], + "outputs": [], "source": [ "mesh_text,mesh_info = smr_generator( base_nx, base_width,\n", " smr_levels, smr_widths,\n", @@ -535,7 +516,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "cc6b5970", "metadata": {}, "outputs": [], From 446a429fad3de4e479a2392c0cd28248f4e6caf3 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 10 Jan 2023 15:58:20 -0600 Subject: [PATCH 41/95] HACK disable mass injection with thermal feedback --- src/pgen/cluster/agn_feedback.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index ef026c83..9a4291dc 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -181,7 +181,9 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Determine if point is in sphere r<=thermal_radius if (r2 <= thermal_radius2) { // Add density at constant velocity and temperature - AddDensityToConsAtFixedVelTemp(thermal_density_rate, cons, prim, eos, k, j,i); + // HACK Disabling injection of mass with thermal feedback since it + // leads to small timesteps + //AddDensityToConsAtFixedVelTemp(thermal_density_rate, cons, prim, eos, k, j,i); // Then apply heating cons(IEN, k, j, i) += thermal_feedback; // Add density at constant velocity From cd38751ebd78f1f183f2a9dc827a5ed67c927584 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Wed, 11 Jan 2023 10:25:58 -0600 Subject: [PATCH 42/95] Added optional AGN feedback mass fractions --- src/pgen/cluster/agn_feedback.cpp | 116 +++++++++++++++++++---------- src/pgen/cluster/agn_feedback.hpp | 2 + src/pgen/cluster/cluster_utils.hpp | 4 +- 3 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 9a4291dc..d5209669 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -6,6 +6,8 @@ //! \file agn_feedback.cpp // \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic tower +#include + // Parthenon headers #include #include @@ -37,6 +39,12 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_fraction", 0.0)), magnetic_fraction_( pin->GetOrAddReal("problem/cluster/agn_feedback", "magnetic_fraction", 0.0)), + thermal_mass_fraction_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "thermal_mass_fraction", NAN)), + kinetic_mass_fraction_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_mass_fraction", NAN)), + magnetic_mass_fraction_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "magnetic_mass_fraction", NAN)), thermal_radius_( pin->GetOrAddReal("problem/cluster/agn_feedback", "thermal_radius", 0.01)), kinetic_jet_radius_( @@ -45,11 +53,31 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_height", 0.02)), disabled_(pin->GetOrAddBoolean("problem/cluster/agn_feedback", "disabled", false)) { + //If any mass_fractions aren't set, set to energy fraction + if(std::isnan(thermal_mass_fraction_)) thermal_mass_fraction_ = thermal_fraction_; + if(std::isnan(magnetic_mass_fraction_)) magnetic_mass_fraction_ = magnetic_fraction_; + if(std::isnan(kinetic_mass_fraction_)) kinetic_mass_fraction_ = kinetic_fraction_; + //Normalize the thermal, kinetic, and magnetic fractions to sum to 1.0 const Real total_frac = thermal_fraction_ + kinetic_fraction_ + magnetic_fraction_; - thermal_fraction_ = thermal_fraction_/total_frac; - kinetic_fraction_ = kinetic_fraction_/total_frac; - magnetic_fraction_ = magnetic_fraction_/total_frac; + if( total_frac > 0){ + thermal_fraction_ = thermal_fraction_/total_frac; + kinetic_fraction_ = kinetic_fraction_/total_frac; + magnetic_fraction_ = magnetic_fraction_/total_frac; + } + + //Normalize the thermal, kinetic, and magnetic mass fractions to sum to 1.0 + const Real total_mass_frac = thermal_mass_fraction_ + kinetic_mass_fraction_ + magnetic_mass_fraction_; + if( total_mass_frac > 0){ + thermal_mass_fraction_ = thermal_mass_fraction_/total_mass_frac; + kinetic_mass_fraction_ = kinetic_mass_fraction_/total_mass_frac; + magnetic_mass_fraction_ = magnetic_mass_fraction_/total_mass_frac; + } + + PARTHENON_REQUIRE( thermal_fraction_ >= 0 && kinetic_fraction_ >= 0 && magnetic_fraction_ >= 0, + "AGN feedback energy fractions must be non-negative."); + PARTHENON_REQUIRE( thermal_mass_fraction_ >= 0 && kinetic_mass_fraction_ >= 0 && magnetic_mass_fraction_ >= 0, + "AGN feedback mass fractions must be non-negative."); // Add user history output variable for AGN power auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); @@ -81,6 +109,19 @@ parthenon::Real AGNFeedback::GetFeedbackPower(StateDescriptor *hydro_pkg) const return power; } +parthenon::Real AGNFeedback::GetFeedbackMassRate(StateDescriptor *hydro_pkg) const { + auto units = hydro_pkg->Param("units"); + const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); + + const Real accretion_rate = agn_triggering.GetAccretionRate(hydro_pkg); + + //Return a mass_rate equal to the accretion_rate minus energy-mass conversion + //to feedback energy. We could divert mass to increase the SMBH/leave out + //from mass injection + const Real mass_rate = accretion_rate*( 1 - efficiency_); + + return mass_rate; +} void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const parthenon::Real beta_dt, @@ -107,8 +148,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, auto units = hydro_pkg->Param("units"); const Real power = GetFeedbackPower(hydro_pkg.get()); - const Real mass_rate = - efficiency_ == 0 ? 0 : power / (efficiency_ * pow(units.speed_of_light(), 2)); + const Real mass_rate = GetFeedbackMassRate(hydro_pkg.get()); if (power == 0 || disabled_) { // No AGN feedback, return @@ -127,35 +167,39 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); + //////////////////////////////////////////////////////////////////////////////// // Thermal quantities - const Real thermal_power = power * thermal_fraction_; - const Real thermal_scaling_factor = 1 / (4. / 3. * M_PI * pow(thermal_radius_, 3)); - const Real thermal_feedback = - thermal_power * thermal_scaling_factor * beta_dt; // energy/volume - const Real thermal_density_rate = - mass_rate * thermal_scaling_factor * thermal_fraction_; + //////////////////////////////////////////////////////////////////////////////// const Real thermal_radius2 = thermal_radius_ * thermal_radius_; + const Real thermal_scaling_factor = 1 / (4. / 3. * M_PI * pow(thermal_radius_, 3)); + //Amount of energy/volume to dump in each cell + const Real thermal_feedback = thermal_fraction_* power * thermal_scaling_factor * beta_dt; + //Amount of density to dump in each cell + const Real thermal_density = thermal_mass_fraction_ * mass_rate * thermal_scaling_factor * beta_dt; + + //////////////////////////////////////////////////////////////////////////////// // Kinetic Jet Quantities - const Real kinetic_power = power * kinetic_fraction_; + //////////////////////////////////////////////////////////////////////////////// const Real kinetic_scaling_factor = 1 / (2 * kinetic_jet_height_ * M_PI * pow(kinetic_jet_radius_, 2)); - // Matches 1/2.*jet_density*jet_velocity*jet_velocity*beta_dt; - const Real kinetic_feedback = - kinetic_power * kinetic_scaling_factor * beta_dt; // energy/volume - - // Note that new mass is injected to create the kinetic power, separate from the - // existing gas - const Real kinetic_jet_total_mass = mass_rate * kinetic_fraction_; - const Real kinetic_jet_density_rate = - kinetic_jet_total_mass * kinetic_scaling_factor * beta_dt; - const Real kinetic_jet_velocity = - std::sqrt(2. * kinetic_power / kinetic_jet_total_mass); - const Real kinetic_jet_momentum_rate = kinetic_jet_density_rate * kinetic_jet_velocity; const Real kinetic_jet_radius = kinetic_jet_radius_; const Real kinetic_jet_height = kinetic_jet_height_; + // Matches 1/2.*jet_density*jet_velocity*jet_velocity*beta_dt; + const Real kinetic_feedback = kinetic_fraction_ * power * kinetic_scaling_factor * beta_dt; // energy/volume + + //Amount of density to dump in each cell + const Real kinetic_density = kinetic_mass_fraction_ * mass_rate * kinetic_scaling_factor * beta_dt; + + //Velocity of added gas + const Real kinetic_velocity = std::sqrt(2. * kinetic_fraction_ * power / ( kinetic_fraction_ * mass_rate) ); + + //Amount of momentum density ( density * velocity) to dump in each cell + const Real kinetic_momentum = kinetic_density * kinetic_velocity; + //////////////////////////////////////////////////////////////////////////////// + const parthenon::Real time = tm.time; const auto &jet_coords_factory = @@ -176,23 +220,21 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real z = coords.Xc<3>(k); // Thermal Feedback - if (thermal_power > 0) { + if (thermal_feedback > 0 || thermal_density > 0) { const Real r2 = x * x + y * y + z * z; // Determine if point is in sphere r<=thermal_radius if (r2 <= thermal_radius2) { - // Add density at constant velocity and temperature - // HACK Disabling injection of mass with thermal feedback since it - // leads to small timesteps - //AddDensityToConsAtFixedVelTemp(thermal_density_rate, cons, prim, eos, k, j,i); // Then apply heating - cons(IEN, k, j, i) += thermal_feedback; + if( thermal_feedback > 0) + cons(IEN, k, j, i) += thermal_feedback; // Add density at constant velocity - // AddDensityToConsAtFixedVel(thermal_density_rate, cons, prim, eos, k, j, i); + if( thermal_density > 0) + AddDensityToConsAtFixedVel(thermal_density, cons, prim, eos, k, j,i); } } // Kinetic Jet Feedback - if (kinetic_power > 0) { + if (kinetic_feedback > 0 || kinetic_density > 0) { // Get position in jet cylindrical coords Real r, cos_theta, sin_theta, h; jet_coords.SimCartToJetCylCoords(x, y, z, r, cos_theta, sin_theta, h); @@ -207,13 +249,11 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const int sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk - cons(IDN, k, j, i) += kinetic_jet_density_rate; // mass/volume - cons(IM1, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_x; - // velocity*mass/volume - cons(IM2, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_y; - // velocity*mass/volume - cons(IM3, k, j, i) += kinetic_jet_momentum_rate * sign_jet * jet_axis_z; + cons(IDN, k, j, i) += kinetic_density; // mass/volume // velocity*mass/volume + cons(IM1, k, j, i) += kinetic_momentum * sign_jet * jet_axis_x; + cons(IM2, k, j, i) += kinetic_momentum * sign_jet * jet_axis_y; + cons(IM3, k, j, i) += kinetic_momentum * sign_jet * jet_axis_z; cons(IEN, k, j, i) += kinetic_feedback; // energy/volume } } diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index d8d84b32..4d3c6e96 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -26,6 +26,7 @@ class AGNFeedback { public: const parthenon::Real fixed_power_; parthenon::Real thermal_fraction_, kinetic_fraction_, magnetic_fraction_; + parthenon::Real thermal_mass_fraction_, kinetic_mass_fraction_, magnetic_mass_fraction_; // Efficiency converting mass to energy const parthenon::Real efficiency_; @@ -41,6 +42,7 @@ class AGNFeedback { AGNFeedback(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg); parthenon::Real GetFeedbackPower(parthenon::StateDescriptor *hydro_pkg) const; + parthenon::Real GetFeedbackMassRate(parthenon::StateDescriptor *hydro_pkg) const; void FeedbackSrcTerm(parthenon::MeshData *md, const parthenon::Real beta_dt, const parthenon::SimTime &tm) const; diff --git a/src/pgen/cluster/cluster_utils.hpp b/src/pgen/cluster/cluster_utils.hpp index 75a7de3c..360e433d 100644 --- a/src/pgen/cluster/cluster_utils.hpp +++ b/src/pgen/cluster/cluster_utils.hpp @@ -24,7 +24,7 @@ KOKKOS_INLINE_FUNCTION void AddDensityToConsAtFixedVel(const parthenon::Real density, View4D &cons, const View4D &prim, const AdiabaticHydroEOS &eos, const int &k, const int &j, const int &i) { - // Add density such that velocity and temperature (propto pressure/density) is fixed + // Add density such that velocity is fixed cons(IDN, k, j, i) += density; cons(IM1, k, j, i) += density * prim(IV1, k, j, i); cons(IM2, k, j, i) += density * prim(IV2, k, j, i); @@ -39,7 +39,7 @@ KOKKOS_INLINE_FUNCTION void AddDensityToConsAtFixedVel(const parthenon::Real density, View4D &cons, const View4D &prim, const AdiabaticGLMMHDEOS &eos, const int &k, const int &j, const int &i) { - // Add density such that velocity and temperature (propto pressure/density) is fixed + // Add density such that velocity and temperature is fixed cons(IDN, k, j, i) += density; cons(IM1, k, j, i) += density * prim(IV1, k, j, i); cons(IM2, k, j, i) += density * prim(IV2, k, j, i); From 2e73cbc4cacd509f104d105c0bc6714360581a05 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 26 Jan 2023 15:14:47 -0600 Subject: [PATCH 43/95] Moved gnat-sternberg.cooling --- inputs/{cluster => cooling_tables}/gnat-sternberg.cooling | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename inputs/{cluster => cooling_tables}/gnat-sternberg.cooling (100%) diff --git a/inputs/cluster/gnat-sternberg.cooling b/inputs/cooling_tables/gnat-sternberg.cooling similarity index 100% rename from inputs/cluster/gnat-sternberg.cooling rename to inputs/cooling_tables/gnat-sternberg.cooling From 9d42b7882bd0004b747c31854491166a91f3bb2b Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 26 Jan 2023 15:24:01 -0600 Subject: [PATCH 44/95] Fixed Bondi Accretion mass removal --- src/pgen/cluster/agn_triggering.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 3f9ad793..15c3f914 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -6,12 +6,13 @@ //! \file agn_triggering.cpp // \brief Class for computing AGN triggering from Bondi-like and cold gas accretion -// Parthenon headers #include +#include + +// Parthenon headers #include #include #include -#include #include #include #include @@ -340,14 +341,11 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData const parthenon::Real r2 = pow(coords.Xc<1>(i), 2) + pow(coords.Xc<2>(j), 2) + pow(coords.Xc<3>(k), 2); if (r2 < accretion_radius2) { - const Real cell_volume = coords.CellVolume(k, j, i); - - const Real cell_accretion_rate = - prim(IDN, k, j, i) / total_mass * accretion_rate; - const Real cell_delta_rho = -cell_accretion_rate/cell_volume * dt; + const Real cell_delta_rho = -prim(IDN, k, j, i) / total_mass * accretion_rate * dt; AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); + // Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); } From d3785edcdddd7fbaa1a4a7eede3e1af69550cb21 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 31 Jan 2023 09:21:50 -0600 Subject: [PATCH 45/95] Comments+Clarifications --- src/pgen/cluster.cpp | 1 + src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp | 4 ++-- src/pgen/cluster/magnetic_tower.hpp | 4 ++-- .../cluster_agn_triggering/cluster_agn_triggering.py | 1 - 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index 5be27a38..2aee5774 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -218,6 +218,7 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { u(IEN, k, j, i) = E; }); + //end if(init_uniform_gas) } else { /************************************************************ * Initialize a HydrostaticEquilibriumSphere diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp index 3bd842ae..826c4bab 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp @@ -61,7 +61,7 @@ class HydrostaticEquilibriumSphere { KOKKOS_INLINE_FUNCTION parthenon::Real P_from_rho_K(const parthenon::Real rho, const parthenon::Real k) const { const parthenon::Real p = - k * pow(mu_ / mu_e_, 2. / 3.) * pow(rho / (mu_ * atomic_mass_unit_), 5. / 3.); + k * pow(rho/atomic_mass_unit_, 5. / 3.) /( mu_ * pow(mu_e_, 2. / 3.) ); return p; } @@ -70,7 +70,7 @@ class HydrostaticEquilibriumSphere { KOKKOS_INLINE_FUNCTION parthenon::Real rho_from_P_K(const parthenon::Real p, const parthenon::Real k) const { const parthenon::Real rho = - pow(p / k, 3. / 5.) * mu_ * atomic_mass_unit_ / pow(mu_ / mu_e_, 2. / 5); + pow(mu_ * p / k, 3. / 5.) * atomic_mass_unit_ * pow( mu_e_, 2. / 5); return rho; } diff --git a/src/pgen/cluster/magnetic_tower.hpp b/src/pgen/cluster/magnetic_tower.hpp index abf41272..49d7bb18 100644 --- a/src/pgen/cluster/magnetic_tower.hpp +++ b/src/pgen/cluster/magnetic_tower.hpp @@ -148,13 +148,13 @@ class MagneticTower { const View4D &A) const; // Add the fixed_field_rate (and associated magnetic energy) to the - // conserved variables for all meshblocks with a MeshData + // conserved variables for all meshblocks within a MeshData void FixedFieldSrcTerm(parthenon::MeshData *md, const parthenon::Real beta_dt, const parthenon::SimTime &tm) const; // Add the specified magnetic power (and associated magnetic field) to the - // conserved variables for all meshblocks with a MeshData + // conserved variables for all meshblocks within a MeshData void PowerSrcTerm(const parthenon::Real power, const parthenon::Real mass_rate, parthenon::MeshData *md, const parthenon::Real beta_dt, const parthenon::SimTime &tm) const; diff --git a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py index fed03b28..76b74789 100644 --- a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py +++ b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py @@ -83,7 +83,6 @@ def __init__(self): self.uniform_gas_temp = ( self.mu * self.m_u / self.k_b * self.uniform_gas_pres / self.uniform_gas_rho ) - print("Uniform gas temperature: ", self.uniform_gas_temp.in_units("K") * 1.01) # SMBH parameters (for Bondi-like accretion) self.M_smbh = unyt.unyt_quantity(1e8, "Msun") From 812698ed0a339c52d36fb0eea484f241ba21fc85 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 31 Jan 2023 18:02:06 -0600 Subject: [PATCH 46/95] Fixed magnetic tower density bug --- src/pgen/cluster/magnetic_tower.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index bb208c45..9a873ed5 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -131,7 +131,7 @@ void MagneticTower::AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mas // Add density const Real cell_delta_rho = - mt.DensityFromSimCart(coords.Xc<1>(i), coords.Xc<1>(i), coords.Xc<1>(i)); + mt.DensityFromSimCart(coords.Xc<1>(i), coords.Xc<2>(j), coords.Xc<3>(k)); AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); }); } From 1eb789eadcbf838cec6f0533cf87cb9221ea328f Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 31 Jan 2023 18:02:22 -0600 Subject: [PATCH 47/95] Added Prim update to AGN Hydro feedback --- src/pgen/cluster/agn_feedback.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index d5209669..e1a663f4 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -166,6 +166,8 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::interior); IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); + const auto nhydro = hydro_pkg->Param("nhydro"); + const auto nscalars = hydro_pkg->Param("nscalars"); //////////////////////////////////////////////////////////////////////////////// // Thermal quantities @@ -257,6 +259,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, cons(IEN, k, j, i) += kinetic_feedback; // energy/volume } } + eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); }); // Apply magnetic tower feedback From 288a1deb6a79dca324cf7828c47f94977488bfba Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Wed, 1 Feb 2023 17:11:02 -0600 Subject: [PATCH 48/95] Removed Mathews BCG gravity, added density functions --- docs/cluster.md | 19 +---- src/pgen/cluster/cluster_gravity.hpp | 104 +++++++++++++++++++-------- 2 files changed, 78 insertions(+), 45 deletions(-) diff --git a/docs/cluster.md b/docs/cluster.md index 21b29fce..631da22b 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -40,14 +40,13 @@ The toggles to include different components are as follows: ``` include_nfw_g = True -which_bcg_g = HERNQUIST #or NONE or MATHEWS +which_bcg_g = HERNQUIST #or NONE include_smbh_g = True ``` Where `include_nfw_g` for the NFW dark-matter halo ([Navarro 1997](doi.org/10.1086/304888)) is boolean; `which_bcg_g` for the BCG can be `NONE` for no BCG, `HERNQUIST` for Hernquist profile ([Hernquist -1990](doi.org/10.1086/168845)), or `MATHEWS` for a Mathews ([Mathews -2006](doi.org/10.1086/499119)) profile, and `include_smbh_g` for the SMBH is +1990](doi.org/10.1086/168845)), and `include_smbh_g` for the SMBH is boolean. Parameters for the NFW profile are @@ -77,7 +76,7 @@ $$ \frac{3 H_0^2}{8 \pi G}. $$ -Parameters for both the HERNQUIST and MATHEWS BCG are controlled via: +Parameters for the HERNQUIST BCG are controlled via: ``` @@ -90,18 +89,6 @@ $$ g_{BCG}(r) = G \frac{ M_{BCG} }{R_{BCG}^2} \frac{1}{\left( 1 + \frac{r}{R_{BCG}}\right)^2} $$ -and a MATHEWS profile adds a gravitational acceleration defined by - -$$ -g_{BCG}(r) = \frac{1}{R_{BCG}^2} -\left ( -\left [ \frac{\left ( r/R_{BCG}\right )^{ 0.5975}} {3.206 \times 10^{-7}} \right ]^{s_{BCG}} + -\left [ \frac{\left ( r/R_{BCG}\right )^{ 1.849 }} {1.861 \times 10^{-6}} \right ]^{s_{BCG}} -\right )^{-1/s_{BCG}} -$$ - -where $s_{BCG} = 0.9$ - Gravitational acceleration from the SMBH is inserted as a point source defined solely by its mass ``` diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 6d45aca6..20555a41 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -17,8 +17,7 @@ namespace cluster { // Types of BCG's -enum class BCG { NONE, MATHEWS, HERNQUIST }; -// Mathews BCG: Mathews 2006 DOI: 10.1086/499119 +enum class BCG { NONE, HERNQUIST }; // Hernquiest BCG: Hernquist 1990 DOI:10.1086/168845 /************************************************************ @@ -34,19 +33,21 @@ class ClusterGravity { // NFW Parameters parthenon::Real r_nfw_s_; - parthenon::Real - GMC_nfw_; // G , Mass, and Constants rolled into one, to minimize footprint + // G , Mass, and Constants rolled into one + parthenon::Real g_const_nfw_; + parthenon::Real rho_const_nfw_; // BCG Parameters parthenon::Real alpha_bcg_s_; parthenon::Real beta_bcg_s_; parthenon::Real r_bcg_s_; - parthenon::Real - GMC_bcg_; // G , Mass, and Constants rolled into one, to minimize footprint + // G , Mass, and Constants rolled into one + parthenon::Real g_const_bcg_; + parthenon::Real rho_const_bcg_; // SMBH Parameters - parthenon::Real - GMC_smbh_; // G , Mass, and Constants rolled into one, to minimize footprint + // G , Mass, and Constants rolled into one + parthenon::Real g_const_smbh_; // Radius underwhich to truncate parthenon::Real smoothing_r_; @@ -62,12 +63,17 @@ class ClusterGravity { 1. / 3.); return R_nfw_s; } - static parthenon::Real calc_GMC_nfw(const parthenon::Real gravitational_constant, + static parthenon::Real calc_g_const_nfw(const parthenon::Real gravitational_constant, const parthenon::Real m_nfw_200, const parthenon::Real c_nfw) { return gravitational_constant * m_nfw_200 / (log(1 + c_nfw) - c_nfw / (1 + c_nfw)); } - static parthenon::Real calc_GMC_bcg(const parthenon::Real gravitational_constant, + static parthenon::Real calc_rho_const_nfw(const parthenon::Real gravitational_constant, + const parthenon::Real m_nfw_200, + const parthenon::Real c_nfw) { + return m_nfw_200 / (4*M_PI*(log(1 + c_nfw) - c_nfw / (1 + c_nfw))); + } + static parthenon::Real calc_g_const_bcg(const parthenon::Real gravitational_constant, BCG which_bcg_g, const parthenon::Real m_bcg_s, const parthenon::Real r_bcg_s, const parthenon::Real alpha_bcg_s, @@ -75,21 +81,37 @@ class ClusterGravity { switch (which_bcg_g) { case BCG::NONE: return 0; - case BCG::MATHEWS: - return 1 / (r_bcg_s * r_bcg_s); case BCG::HERNQUIST: return gravitational_constant * m_bcg_s / (r_bcg_s * r_bcg_s); } return NAN; } + static parthenon::Real calc_rho_const_bcg(const parthenon::Real gravitational_constant, + BCG which_bcg_g, const parthenon::Real m_bcg_s, + const parthenon::Real r_bcg_s, + const parthenon::Real alpha_bcg_s, + const parthenon::Real beta_bcg_s) { + switch (which_bcg_g) { + case BCG::NONE: + return 0; + case BCG::HERNQUIST: + return m_bcg_s * r_bcg_s/ (2*M_PI); + } + return NAN; + } static KOKKOS_INLINE_FUNCTION parthenon::Real - calc_GMC_smbh(const parthenon::Real gravitational_constant, + calc_g_const_smbh(const parthenon::Real gravitational_constant, const parthenon::Real m_smbh) { return gravitational_constant * m_smbh; } public: - ClusterGravity(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) { + //ClusterGravity(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) + //is called from cluster.cpp to add the ClusterGravity object to hydro_pkg + // + //ClusterGravity(parthenon::ParameterInput *pin) is used in SNIAFeedback to + //calculate the BCG density profile + ClusterGravity(parthenon::ParameterInput *pin) { Units units(pin); // Determine which element to include @@ -99,8 +121,6 @@ class ClusterGravity { pin->GetOrAddString("problem/cluster/gravity", "which_bcg_g", "NONE"); if (which_bcg_g_str == "NONE") { which_bcg_g_ = BCG::NONE; - } else if (which_bcg_g_str == "MATHEWS") { - which_bcg_g_ = BCG::MATHEWS; } else if (which_bcg_g_str == "HERNQUIST") { which_bcg_g_ = BCG::HERNQUIST; } else { @@ -124,7 +144,7 @@ class ClusterGravity { const parthenon::Real c_nfw = pin->GetOrAddReal("problem/cluster/gravity", "c_nfw", 6.81); r_nfw_s_ = calc_R_nfw_s(rho_crit, M_nfw_200, c_nfw); - GMC_nfw_ = calc_GMC_nfw(units.gravitational_constant(), M_nfw_200, c_nfw); + g_const_nfw_ = calc_g_const_nfw(units.gravitational_constant(), M_nfw_200, c_nfw); // Initialize the BCG Profile alpha_bcg_s_ = pin->GetOrAddReal("problem/cluster/gravity", "alpha_bcg_s", 0.1); @@ -132,16 +152,19 @@ class ClusterGravity { const parthenon::Real M_bcg_s = pin->GetOrAddReal("problem/cluster/gravity", "m_bcg_s", 7.5e10 * units.msun()); r_bcg_s_ = pin->GetOrAddReal("problem/cluster/gravity", "r_bcg_s", 4 * units.kpc()); - GMC_bcg_ = calc_GMC_bcg(units.gravitational_constant(), which_bcg_g_, M_bcg_s, + g_const_bcg_ = calc_g_const_bcg(units.gravitational_constant(), which_bcg_g_, M_bcg_s, r_bcg_s_, alpha_bcg_s_, beta_bcg_s_); const parthenon::Real m_smbh = pin->GetOrAddReal("problem/cluster/gravity", "m_smbh", 3.4e8 * units.msun()); - GMC_smbh_ = calc_GMC_smbh(units.gravitational_constant(), m_smbh), + g_const_smbh_ = calc_g_const_smbh(units.gravitational_constant(), m_smbh), smoothing_r_ = pin->GetOrAddReal("problem/cluster/gravity", "g_smoothing_radius", 0.0); + } + + ClusterGravity(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) : ClusterGravity(pin) { hydro_pkg->AddParam<>("cluster_gravity", *this); } @@ -156,32 +179,55 @@ class ClusterGravity { // Add NFW gravity if (include_nfw_g_) { - g_r += GMC_nfw_ * (log(1 + r / r_nfw_s_) - r / (r + r_nfw_s_)) / r2; + g_r += g_const_nfw_ * (log(1 + r / r_nfw_s_) - r / (r + r_nfw_s_)) / r2; } // Add BCG gravity switch (which_bcg_g_) { case BCG::NONE: break; - case BCG::MATHEWS: { - const parthenon::Real s_bcg = 0.9; - g_r += GMC_bcg_ * // Note: *cm**3*s**-2 //To make units work - pow( pow( pow(r/r_bcg_s_, 0.5975) / 3.206e-7,s_bcg) + - pow( pow(r/r_bcg_s_, 1.849 ) / 1.861e-6,s_bcg), - -1 / s_bcg); - } break; case BCG::HERNQUIST: - g_r += GMC_bcg_ / ((1 + r / r_bcg_s_) * (1 + r / r_bcg_s_)); + g_r += g_const_bcg_ / ((1 + r / r_bcg_s_) * (1 + r / r_bcg_s_)); break; } // Add SMBH, point mass gravity if (include_smbh_g_) { - g_r += GMC_smbh_ / r2; + g_r += g_const_smbh_ / r2; } return g_r; } + // Inline functions to compute density + KOKKOS_INLINE_FUNCTION parthenon::Real rho_from_r(const parthenon::Real r_in) const + __attribute__((always_inline)) { + + const parthenon::Real r = std::max(r_in, smoothing_r_); + + parthenon::Real rho = 0; + + // Add NFW gravity + if (include_nfw_g_) { + rho += rho_const_nfw_ / ( r * pow(r + r_nfw_s_,2)); + } + + // Add BCG gravity + switch (which_bcg_g_) { + case BCG::NONE: + break; + case BCG::HERNQUIST: + rho += rho_const_bcg_ / ( r * pow(r + r_bcg_s_, 3)); + break; + } + + // SMBH, point mass gravity -- density is not defined. Throw an error + if (include_smbh_g_ && r <= smoothing_r_) { + Kokkos::abort("ClusterGravity::SMBH density is not defined"); + } + + return rho; + } + }; } // namespace cluster From bdb4409aaebce9ac2454730638340f7da3bc6e98 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 6 Feb 2023 18:08:04 -0600 Subject: [PATCH 49/95] Added SNIA Feedback (no docs nor regression tests) --- src/pgen/CMakeLists.txt | 1 + src/pgen/cluster.cpp | 10 +++ src/pgen/cluster/cluster_gravity.hpp | 3 + src/pgen/cluster/snia_feedback.cpp | 120 +++++++++++++++++++++++++++ src/pgen/cluster/snia_feedback.hpp | 51 ++++++++++++ 5 files changed, 185 insertions(+) create mode 100644 src/pgen/cluster/snia_feedback.cpp create mode 100644 src/pgen/cluster/snia_feedback.hpp diff --git a/src/pgen/CMakeLists.txt b/src/pgen/CMakeLists.txt index 04a40fc0..443c4465 100644 --- a/src/pgen/CMakeLists.txt +++ b/src/pgen/CMakeLists.txt @@ -10,6 +10,7 @@ target_sources(athenaPK PRIVATE cluster/agn_triggering.cpp cluster/hydrostatic_equilibrium_sphere.cpp cluster/magnetic_tower.cpp + cluster/snia_feedback.cpp cpaw.cpp diffusion.cpp field_loop.cpp diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index 2aee5774..bc05cafd 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -42,6 +42,7 @@ #include "cluster/entropy_profiles.hpp" #include "cluster/hydrostatic_equilibrium_sphere.hpp" #include "cluster/magnetic_tower.hpp" +#include "cluster/snia_feedback.hpp" namespace cluster { using namespace parthenon::driver::prelude; @@ -65,6 +66,9 @@ void ClusterSrcTerm(MeshData *md, const parthenon::SimTime &tm, const auto &magnetic_tower = hydro_pkg->Param("magnetic_tower"); magnetic_tower.FixedFieldSrcTerm(md, beta_dt, tm); + + const auto &snia_feedback = hydro_pkg->Param("snia_feedback"); + snia_feedback.FeedbackSrcTerm(md, beta_dt, tm); } Real ClusterEstimateTimestep(MeshData *md) { @@ -168,6 +172,12 @@ void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hyd (agn_feedback.fixed_power_ != 0 || agn_triggering.triggering_mode_ != AGNTriggeringMode::NONE)); hydro_pkg->AddParam("magnetic_tower_power_scaling", magnetic_tower_power_scaling); + + /************************************************************ + * Read SNIA Feedback + ************************************************************/ + + SNIAFeedback snia_feedback(pin, hydro_pkg); } //======================================================================================== diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 20555a41..76cb5b4f 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -228,6 +228,9 @@ class ClusterGravity { return rho; } + //SNIAFeedback needs to be a friend to disable the SMBH and NFW + friend class SNIAFeedback; + }; } // namespace cluster diff --git a/src/pgen/cluster/snia_feedback.cpp b/src/pgen/cluster/snia_feedback.cpp new file mode 100644 index 00000000..12e8145e --- /dev/null +++ b/src/pgen/cluster/snia_feedback.cpp @@ -0,0 +1,120 @@ +//======================================================================================== +// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file agn_feedback.cpp +// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic tower + +#include + +// Parthenon headers +#include +#include +#include +#include +#include +#include + +// Athena headers +#include "../../eos/adiabatic_glmmhd.hpp" +#include "../../eos/adiabatic_hydro.hpp" +#include "../../main.hpp" +#include "../../units.hpp" +#include "cluster_gravity.hpp" +#include "cluster_utils.hpp" +#include "snia_feedback.hpp" + +namespace cluster { +using namespace parthenon; + +SNIAFeedback::SNIAFeedback(parthenon::ParameterInput *pin, + parthenon::StateDescriptor *hydro_pkg) + : + power_per_bcg_mass_(pin->GetOrAddReal("problem/cluster/snia_feedback", "power_per_bcg_mass", 0.0)), + mass_rate_per_bcg_mass_(pin->GetOrAddReal("problem/cluster/snia_feedback", "mass_rate_per_bcg_mass", 0.0)), + bcg_gravity_(pin), + disabled_(pin->GetOrAddBoolean("problem/cluster/snia_feedback", "disabled", false)) { + + //Initialize the gravity from the cluster + //Turn off the NFW and SMBH to get just the BCG gravity + bcg_gravity_.include_nfw_g_ = false; + bcg_gravity_.include_smbh_g_ = false; + + PARTHENON_REQUIRE( disabled_ || bcg_gravity_.which_bcg_g_ != BCG::NONE, + "BCG must be defined for SNIA Feedback to be enabled"); + hydro_pkg->AddParam("snia_feedback", *this); +} + +void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, + const parthenon::SimTime &tm) const { + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + auto fluid = hydro_pkg->Param("fluid"); + if (fluid == Fluid::euler) { + FeedbackSrcTerm(md, beta_dt, tm, hydro_pkg->Param("eos")); + } else if (fluid == Fluid::glmmhd) { + FeedbackSrcTerm(md, beta_dt, tm, hydro_pkg->Param("eos")); + } else { + PARTHENON_FAIL("SNIAFeedback::FeedbackSrcTerm: Unknown EOS"); + } +} +template +void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, + const parthenon::SimTime &tm, const EOS &eos) const { + using parthenon::IndexDomain; + using parthenon::IndexRange; + using parthenon::Real; + + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); + + if ( (power_per_bcg_mass_ == 0 && mass_rate_per_bcg_mass_ == 0) || disabled_) { + // No AGN feedback, return + return; + } + + // Grab some necessary variables + const auto &prim_pack = md->PackVariables(std::vector{"prim"}); + const auto &cons_pack = md->PackVariables(std::vector{"cons"}); + IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::interior); + IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); + IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); + const auto nhydro = hydro_pkg->Param("nhydro"); + const auto nscalars = hydro_pkg->Param("nscalars"); + + const Real energy_per_bcg_mass = power_per_bcg_mass_*beta_dt; + const Real mass_per_bcg_mass = mass_rate_per_bcg_mass_*beta_dt; + + const ClusterGravity bcg_gravity = bcg_gravity_; + + //////////////////////////////////////////////////////////////////////////////// + + // Constant volumetric heating + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "SNIAFeedback::FeedbackSrcTerm", + parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, + ib.e, KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i) { + auto &cons = cons_pack(b); + auto &prim = prim_pack(b); + const auto &coords = cons_pack.GetCoords(b); + + const Real x = coords.Xc<1>(i); + const Real y = coords.Xc<2>(j); + const Real z = coords.Xc<3>(k); + + const Real r = sqrt(x*x + y*y + z*z); + + const Real bcg_density = bcg_gravity.rho_from_r(r); + + const Real snia_energy_density = energy_per_bcg_mass*bcg_density; + const Real snia_mass_density = mass_per_bcg_mass*bcg_density; + + cons(IEN, k, j, i) += snia_energy_density; + AddDensityToConsAtFixedVel(snia_mass_density, cons, prim, eos, k, j,i); + + eos.ConsToPrim(cons,prim, nhydro, nscalars, k, j, i); + }); +} + +} // namespace cluster diff --git a/src/pgen/cluster/snia_feedback.hpp b/src/pgen/cluster/snia_feedback.hpp new file mode 100644 index 00000000..2e5f3aa6 --- /dev/null +++ b/src/pgen/cluster/snia_feedback.hpp @@ -0,0 +1,51 @@ +#ifndef CLUSTER_SNIA_FEEDBACK_HPP_ +#define CLUSTER_SNIA_FEEDBACK_HPP_ +//======================================================================================== +// AthenaPK - a performance portable block structured AMR astrophysical MHD code. +// Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. +// Licensed under the 3-clause BSD License, see LICENSE file for details +//======================================================================================== +//! \file snia_feedback.hpp +// \brief Class for injecting SNIA feedback following BCG density + +// parthenon headers +#include +#include +#include +#include +#include + +#include "jet_coords.hpp" + +namespace cluster { + +/************************************************************ + * AGNFeedback + ************************************************************/ +class SNIAFeedback { + public: + + //Power and Mass to inject per mass in the BCG + parthenon::Real power_per_bcg_mass_; //energy/(mass*time) + parthenon::Real mass_rate_per_bcg_mass_; // 1/(time) + + //ClusterGravity object to calculate BCG density + ClusterGravity bcg_gravity_; + + const bool disabled_; + + SNIAFeedback(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg); + + void FeedbackSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, const parthenon::SimTime &tm) const; + + // Apply the feedback from SNIAe tied to the BCG density + template + void FeedbackSrcTerm(parthenon::MeshData *md, + const parthenon::Real beta_dt, const parthenon::SimTime &tm, + const EOS &eos) const; +}; + +} // namespace cluster + +#endif // CLUSTER_SNIAE_FEEDBACK_HPP_ From 12c28644cc8079d2f5b3bd2308184935bc65e2bb Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 6 Feb 2023 19:06:06 -0600 Subject: [PATCH 50/95] Added SNIA feedback to docs and cluster input generator --- docs/cluster.md | 19 +- inputs/cluster/generate_cluster_input.ipynb | 6 + inputs/cluster/my_cluster.input | 202 ++++++++++++++++++++ 3 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 inputs/cluster/my_cluster.input diff --git a/docs/cluster.md b/docs/cluster.md index 631da22b..8221a8c6 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -133,8 +133,8 @@ $$ This profile is determined by these parameters ``` -k_0 = 8.851337676479303e-121 # in FIXME -k_100 = 1.3277006514718954e-119 # in FIXME +k_0 = 8.851337676479303e-121 # in code_length**4*code_mass/code_time**2 +k_100 = 1.3277006514718954e-119 # in code_length**4*code_mass/code_time**2 r_k = 0.1 # in code_length alpha_k = 1.1 # unitless ``` @@ -381,3 +381,18 @@ fixed_field_rate = 1.0 fixed_mass_rate = 1.0 ``` +## SNIA Feedback + +Following [Prasad 2020](doi.org/10.1093/mnras/112.2.195), AthenaPK can inject +mass and energy from type Ia supernovae following the mass profile of the BCG. +This SNIA feedback can be configured with +``` + +power_per_bcg_mass = 0.0015780504379367209 # in units code_length**2/code_time**3 +mass_rate_per_bcg_mass = 0.00315576 # in units 1/code_time +disabled = False +``` +where `power_per_bcg_mass` and `mass_rate_per_bcg_mass` is the power and mass +per time respectively injected per BCG mass at a given radius. This SNIA +feedback is otherwise fixed in time, spherically symmetric, and dependant on +the BCG specified in ``. diff --git a/inputs/cluster/generate_cluster_input.ipynb b/inputs/cluster/generate_cluster_input.ipynb index e436fb1e..a75f08c1 100644 --- a/inputs/cluster/generate_cluster_input.ipynb +++ b/inputs/cluster/generate_cluster_input.ipynb @@ -219,6 +219,12 @@ "l_scale = {unyt.unyt_quantity(1,\"kpc\").in_units(\"code_length\").v}\n", "initial_field = {unyt.unyt_quantity(1e-6,\"G\").in_units(\"code_mass**(1/2)*code_length**(-1/2)*code_time**-1\").v}\n", "l_mass_scale = {unyt.unyt_quantity(1,\"kpc\").in_units(\"code_length\").v}\n", + "\n", + "\n", + "\n", + "power_per_bcg_mass = {unyt.unyt_quantity(1e51*3e-14,\"erg/yr/Msun\").in_units(\"code_length**2/code_time**3\").v}\n", + "mass_rate_per_bcg_mass = {unyt.unyt_quantity(1e-19,\"1/s\").in_units(\"1/code_time\").v}\n", + "disabled = False\n", "\"\"\"" ] }, diff --git a/inputs/cluster/my_cluster.input b/inputs/cluster/my_cluster.input new file mode 100644 index 00000000..129b865c --- /dev/null +++ b/inputs/cluster/my_cluster.input @@ -0,0 +1,202 @@ + +# File autogenerated with Python script +# Changes might be overwritten! + +problem = Isolated galaxy cluster + + +problem_id = cluster # problem ID: basename of output filenames + + + +file_type = hst # History data dump +dt = 0.1 Myr # time increment between outputs + + +file_type = rst # restart data dump +dt = 1.0 Myr # Time increment between outputs +id = restart + +# hdf5_compression_level = 0 +use_final_label = false + + + + +cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number +tlim = 0.1 Myr # time limit +integrator = vl2 # time integration algorithm + + + + +refinement = static +nghost = 2 + +nx1 = 64 # Number of zones in X1-direction +x1min =-0.1 # minimum value of X1 +x1max = 0.1 # maximum value of X1 +ix1_bc = outflow # inner-X1 boundary flag +ox1_bc = outflow # outer-X1 boundary flag + +nx2 = 64 # Number of zones in X2-direction +x2min =-0.1 # minimum value of X2 +x2max = 0.1 # maximum value of X2 +ix2_bc = outflow # inner-X2 boundary flag +ox2_bc = outflow # outer-X2 boundary flag + +nx3 = 64 # Number of zones in X3-direction +x3min =-0.1 # minimum value of X3 +x3max = 0.1 # maximum value of X3 +ix3_bc = outflow # inner-X3 boundary flag +ox3_bc = outflow # outer-X3 boundary flag + + +nx1 = 32 # Number of zones in X1-direction +nx2 = 32 # Number of zones in X2-direction +nx3 = 32 # Number of zones in X3-direction + + + +x1min = -0.025 +x1max = 0.025 +x2min = -0.025 +x2max = 0.025 +x3min = -0.025 +x3max = 0.025 +level = 2 + + + + + +fluid = glmmhd +gamma = 5./3. # gamma = C_p/C_v +eos = adiabatic +riemann = hlld +reconstruction = plm +use_scratch = false +scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM +Tfloor = 10000.0 + +first_order_flux_correct = True + +He_mass_fraction = 0.25 + + +#Units parameters +code_length_cgs = 3.085677580962325e+24 +code_mass_cgs = 1.98841586e+47 +code_time_cgs = 3.15576e+16 + + +enable_cooling = tabular +table_filename = schure.cooling +log_temp_col = 0 # Column to read temperature in cooling table +log_lambda_col = 1 # Column to read lambda in cooling table +lambda_units_cgs = 1 + +integrator = townsend +cfl = 0.1 # Restricts hydro step based on minimum cooling time, disabled for "integrator=townsend" +min_timestep = 1.0 +max_iter = 100 +d_e_tol = 1e-8 +d_log_temp_tol = 1e-8 + + +hubble_parameter = 0.0715898515654728 + + +#Include gravity as a source term +gravity_srcterm = True + +#Which gravitational fields to include +include_nfw_g = True +which_bcg_g = HERNQUIST +include_smbh_g = True + +#NFW parameters +c_nfw = 6.0 +m_nfw_200 = 10.000000000000002 + +#BCG parameters +m_bcg_s = 0.0010000000000000002 +r_bcg_s = 0.004 + +#SMBH parameters +m_smbh = 1.0000000000000002e-06 + +#Smooth gravity at origin, for numerical reasons +g_smoothing_radius = 0 + + +#Entropy profile parameters +k_0 = 8.851337676479303e-121 +k_100 = 1.3277006514718954e-119 +r_k = 0.1 +alpha_k = 1.1 + + +#Fix density at radius to close system of equations +r_fix = 2.0 +rho_fix = 0.01477557589278723 + +#Building the radii at which to sample initial rho,P +r_sampling = 4.0 +max_dr = 0.001 + + +#Which triggering mode (BOOSTED_BONDI, BOOTH_SCHAYE, COLD_GAS, NONE) +triggering_mode = COLD_GAS + +#Radius of accretion for triggering +accretion_radius = 0.001 + +#BOOSTED_BONDI and BOOTH_SCHAYE Parameters +bondi_alpha = 100.0 +bondi_beta = 2.0 +bondi_n0 = 2.9379989445851786e+72 + +#COLD_GAS Parameters +cold_temp_thresh = 100000.0 +cold_t_acc = 0.1 + +write_to_file = True + + +jet_theta = 0.15 +jet_phi_dot = 628.3185307179587 +jet_phi0 = 0.2 + + +# Fixed power, added on top of triggered feedback +fixed_power = 0.0 + +# Efficieny in conversion of AGN accreted mass to AGN feedback energy +efficiency = 1e-3 + +# Fraction allocated to different mechanisms +magnetic_fraction = 0.333 +thermal_fraction = 0.333 +kinetic_fraction = 0.333 + +# Thermal feedback parameters +thermal_radius = 0.0005 + +# Kinetic jet feedback parameters +kinetic_jet_radius = 0.0005 +kinetic_jet_height = 0.0005 + + +alpha = 20 +l_scale = 0.001 +initial_field = 0.12431560000204142 +l_mass_scale = 0.001 + + + +power_per_bcg_mass = 0.0015780504379367209 +mass_rate_per_bcg_mass = 0.00315576 +disabled = False + + From 6b22174575fff757fc7f5b9c4a3584343664eea2 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 7 Feb 2023 13:16:41 -0600 Subject: [PATCH 51/95] Added optional initial uniform B field --- src/pgen/cluster.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index bc05cafd..25875ff5 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -114,6 +114,24 @@ void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hyd hydro_pkg->AddParam<>("uniform_gas_pres", uniform_gas_pres); } + /************************************************************ + * Read Uniform Magnetic Field + ************************************************************/ + + const bool init_uniform_b_field = + pin->GetOrAddBoolean("problem/cluster/uniform_b_field", "init_uniform_b_field", false); + hydro_pkg->AddParam<>("init_uniform_b_field", init_uniform_b_field); + + if (init_uniform_b_field) { + const Real uniform_b_field_bx = pin->GetReal("problem/cluster/uniform_b_field", "bx"); + const Real uniform_b_field_by = pin->GetReal("problem/cluster/uniform_b_field", "by"); + const Real uniform_b_field_bz = pin->GetReal("problem/cluster/uniform_b_field", "bz"); + + hydro_pkg->AddParam<>("uniform_b_field_bx", uniform_b_field_bx); + hydro_pkg->AddParam<>("uniform_b_field_by", uniform_b_field_by); + hydro_pkg->AddParam<>("uniform_b_field_bz", uniform_b_field_bz); + } + /************************************************************ * Read Cluster Gravity Parameters ************************************************************/ @@ -309,6 +327,35 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { 0.5 * (SQR(u(IB1, k, j, i)) + SQR(u(IB2, k, j, i)) + SQR(u(IB3, k, j, i))); }); + + /************************************************************ + * Add uniform magnetic field to the conserved variables + ************************************************************/ + const auto &init_uniform_b_field = hydro_pkg->Param("init_uniform_b_field"); + if (init_uniform_b_field) { + const Real bx = hydro_pkg->Param("uniform_b_field_bx"); + const Real by = hydro_pkg->Param("uniform_b_field_by"); + const Real bz = hydro_pkg->Param("uniform_b_field_bz"); + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "cluster::ProblemGenerator::ApplyUniformBField", + parthenon::DevExecSpace(), kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, + KOKKOS_LAMBDA(const int &k, const int &j, const int &i) { + const Real bx_i = u(IB1, k, j, i); + const Real by_i = u(IB2, k, j, i); + const Real bz_i = u(IB3, k, j, i); + + u(IB1, k, j, i) += bx; + u(IB2, k, j, i) += by; + u(IB3, k, j, i) += bz; + + //Old magnetic energy is b_i^2, new Magnetic energy should be 0.5*(b_i + b)^2, + //add b_i*b + 0.5b^2 to old energy to accomplish that + u(IEN, k, j, i) += bx_i*bx + by_i*by + bz_i*bz+ + 0.5 * (SQR(bx) + SQR(by) + SQR(bz)); + }); + //end if(init_uniform_b_field) + } + } // END if(hydro_pkg->Param("fluid") == Fluid::glmmhd) } From 8a8ae5f32815d16cb593e784a4bebb8334317188 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 14 Mar 2023 08:44:22 -0500 Subject: [PATCH 52/95] Add optional dipole magnetic field --- src/pgen/cluster.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index 25875ff5..f5686053 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -132,6 +132,24 @@ void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hyd hydro_pkg->AddParam<>("uniform_b_field_bz", uniform_b_field_bz); } + /************************************************************ + * Read Uniform Magnetic Field + ************************************************************/ + + const bool init_dipole_b_field = + pin->GetOrAddBoolean("problem/cluster/dipole_b_field", "init_dipole_b_field", false); + hydro_pkg->AddParam<>("init_dipole_b_field", init_dipole_b_field); + + if (init_dipole_b_field) { + const Real dipole_b_field_mx = pin->GetReal("problem/cluster/dipole_b_field", "mx"); + const Real dipole_b_field_my = pin->GetReal("problem/cluster/dipole_b_field", "my"); + const Real dipole_b_field_mz = pin->GetReal("problem/cluster/dipole_b_field", "mz"); + + hydro_pkg->AddParam<>("dipole_b_field_mx", dipole_b_field_mx); + hydro_pkg->AddParam<>("dipole_b_field_my", dipole_b_field_my); + hydro_pkg->AddParam<>("dipole_b_field_mz", dipole_b_field_mz); + } + /************************************************************ * Read Cluster Gravity Parameters ************************************************************/ @@ -306,6 +324,35 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { magnetic_tower.AddInitialFieldToPotential(pmb, a_kb, a_jb, a_ib, A); + /************************************************************ + * Add dipole magnetic field to the magnetic potential + ************************************************************/ + const auto &init_dipole_b_field = hydro_pkg->Param("init_dipole_b_field"); + if (init_dipole_b_field) { + const Real mx = hydro_pkg->Param("dipole_b_field_mx"); + const Real my = hydro_pkg->Param("dipole_b_field_my"); + const Real mz = hydro_pkg->Param("dipole_b_field_mz"); + parthenon::par_for( + DEFAULT_LOOP_PATTERN, "MagneticTower::AddInitialFieldToPotential", + parthenon::DevExecSpace(), a_kb.s, a_kb.e, a_jb.s, a_jb.e, a_ib.s, a_ib.e, + KOKKOS_LAMBDA(const int &k, const int &j, const int &i) { + // Compute and apply potential + const Real x = coords.Xc<1>(i); + const Real y = coords.Xc<2>(j); + const Real z = coords.Xc<3>(k); + + const Real r3 = pow( SQR(x) + SQR(y) + SQR(z),3./2); + + const Real m_cross_r_x = my*z - mz*y; + const Real m_cross_r_y = mz*x - mx*z; + const Real m_cross_r_z = mx*y - mx*y; + + A(0, k, j, i) += m_cross_r_x/(4*M_PI*r3); + A(1, k, j, i) += m_cross_r_y/(4*M_PI*r3); + A(2, k, j, i) += m_cross_r_z/(4*M_PI*r3); + }); + } + /************************************************************ * Apply the potential to the conserved variables ************************************************************/ From 5470811c73a2be226ca879bb6e68fdee486b6a8f Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 27 Mar 2023 18:23:30 -0600 Subject: [PATCH 53/95] Streamlined cluster_utils mass adding funcs --- src/pgen/cluster/agn_feedback.cpp | 8 +++---- src/pgen/cluster/cluster_utils.hpp | 37 +++-------------------------- src/pgen/cluster/magnetic_tower.cpp | 2 +- src/pgen/cluster/snia_feedback.cpp | 2 +- 4 files changed, 8 insertions(+), 41 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index e1a663f4..e73faf71 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -155,10 +155,8 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, return; } - if (magnetic_fraction_ == 0 && thermal_fraction_ == 0 && kinetic_fraction_ == 0) { - PARTHENON_FAIL("AGNFeedback::FeedbackSrcTerm Magnetic, Thermal, and Kinetic " - "fractions are all zero"); - } + PARTHENON_REQUIRE( magnetic_fraction_ != 0 || thermal_fraction_ != 0 || kinetic_fraction_ != 0, + "AGNFeedback::FeedbackSrcTerm Magnetic, Thermal, and Kinetic fractions are all zero"); // Grab some necessary variables const auto &prim_pack = md->PackVariables(std::vector{"prim"}); @@ -231,7 +229,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, cons(IEN, k, j, i) += thermal_feedback; // Add density at constant velocity if( thermal_density > 0) - AddDensityToConsAtFixedVel(thermal_density, cons, prim, eos, k, j,i); + AddDensityToConsAtFixedVel(thermal_density, cons, prim, k, j,i); } } diff --git a/src/pgen/cluster/cluster_utils.hpp b/src/pgen/cluster/cluster_utils.hpp index 360e433d..9d9f50e9 100644 --- a/src/pgen/cluster/cluster_utils.hpp +++ b/src/pgen/cluster/cluster_utils.hpp @@ -22,7 +22,7 @@ namespace cluster { template KOKKOS_INLINE_FUNCTION void AddDensityToConsAtFixedVel(const parthenon::Real density, View4D &cons, - const View4D &prim, const AdiabaticHydroEOS &eos, const int &k, + const View4D &prim, const int &k, const int &j, const int &i) { // Add density such that velocity is fixed cons(IDN, k, j, i) += density; @@ -34,43 +34,12 @@ AddDensityToConsAtFixedVel(const parthenon::Real density, View4D &cons, SQR(prim(IV3, k, j, i)))); } -template -KOKKOS_INLINE_FUNCTION void -AddDensityToConsAtFixedVel(const parthenon::Real density, View4D &cons, - const View4D &prim, const AdiabaticGLMMHDEOS &eos, - const int &k, const int &j, const int &i) { - // Add density such that velocity and temperature is fixed - cons(IDN, k, j, i) += density; - cons(IM1, k, j, i) += density * prim(IV1, k, j, i); - cons(IM2, k, j, i) += density * prim(IV2, k, j, i); - cons(IM3, k, j, i) += density * prim(IV3, k, j, i); - cons(IEN, k, j, i) += - density * (0.5 * (SQR(prim(IV1, k, j, i)) + SQR(prim(IV2, k, j, i)) + - SQR(prim(IV3, k, j, i)))); -} - // Add a density to the conserved variables while keeping velocity and // temperature ( propto pressure/density) fixed template KOKKOS_INLINE_FUNCTION void AddDensityToConsAtFixedVelTemp(const parthenon::Real density, View4D &cons, - const View4D &prim, const AdiabaticHydroEOS &eos, - const int &k, const int &j, const int &i) { - // Add density such that velocity and temperature (propto pressure/density) is fixed - cons(IDN, k, j, i) += density; - cons(IM1, k, j, i) += density * prim(IV1, k, j, i); - cons(IM2, k, j, i) += density * prim(IV2, k, j, i); - cons(IM3, k, j, i) += density * prim(IV3, k, j, i); - cons(IEN, k, j, i) += - density * (0.5 * (SQR(prim(IV1, k, j, i)) + SQR(prim(IV2, k, j, i)) + - SQR(prim(IV3, k, j, i))) + - 1 / (eos.GetGamma() - 1.0) * prim(IPR, k, j, i) / prim(IDN, k, j, i)); -} - -template -KOKKOS_INLINE_FUNCTION void -AddDensityToConsAtFixedVelTemp(const parthenon::Real density, View4D &cons, - const View4D &prim, const AdiabaticGLMMHDEOS &eos, + const View4D &prim, const Real adiabaticIndex, const int &k, const int &j, const int &i) { // Add density such that velocity and temperature (propto pressure/density) is fixed cons(IDN, k, j, i) += density; @@ -80,7 +49,7 @@ AddDensityToConsAtFixedVelTemp(const parthenon::Real density, View4D &cons, cons(IEN, k, j, i) += density * (0.5 * (SQR(prim(IV1, k, j, i)) + SQR(prim(IV2, k, j, i)) + SQR(prim(IV3, k, j, i))) + - 1 / (eos.GetGamma() - 1.0) * prim(IPR, k, j, i) / prim(IDN, k, j, i)); + 1 / (adiabaticIndex - 1.0) * prim(IPR, k, j, i) / prim(IDN, k, j, i)); } } // namespace cluster diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index 9a873ed5..1825d07a 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -132,7 +132,7 @@ void MagneticTower::AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mas // Add density const Real cell_delta_rho = mt.DensityFromSimCart(coords.Xc<1>(i), coords.Xc<2>(j), coords.Xc<3>(k)); - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, i); }); } diff --git a/src/pgen/cluster/snia_feedback.cpp b/src/pgen/cluster/snia_feedback.cpp index 12e8145e..63754f2c 100644 --- a/src/pgen/cluster/snia_feedback.cpp +++ b/src/pgen/cluster/snia_feedback.cpp @@ -111,7 +111,7 @@ void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real snia_mass_density = mass_per_bcg_mass*bcg_density; cons(IEN, k, j, i) += snia_energy_density; - AddDensityToConsAtFixedVel(snia_mass_density, cons, prim, eos, k, j,i); + AddDensityToConsAtFixedVel(snia_mass_density, cons, prim, k, j,i); eos.ConsToPrim(cons,prim, nhydro, nscalars, k, j, i); }); From ff56cc916eadf94239bc087d9bc3afebd00e6abf Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 27 Mar 2023 18:28:01 -0600 Subject: [PATCH 54/95] Fixed number of hydro AGN feedback tests --- .../cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index dfb11387..69171f8c 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -126,7 +126,7 @@ def __init__(self): self.norm_tol = 1e-3 - self.steps = 4 + self.steps = 6 self.step_params_list = list( itertools.product( ("thermal_only", "kinetic_only", "combined"), (True, False) From 1cc0836b10424ef162589852b36287d9cc50dcb4 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 27 Mar 2023 18:30:10 -0600 Subject: [PATCH 55/95] Fixed more add mass funcs --- src/pgen/cluster/entropy_profiles.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pgen/cluster/entropy_profiles.hpp b/src/pgen/cluster/entropy_profiles.hpp index 1dc17531..1fd75580 100644 --- a/src/pgen/cluster/entropy_profiles.hpp +++ b/src/pgen/cluster/entropy_profiles.hpp @@ -17,11 +17,11 @@ namespace cluster { class ACCEPTEntropyProfile { - private: + + public: // Entropy Profile parthenon::Real k_0_, k_100_, r_k_, alpha_k_; - public: ACCEPTEntropyProfile(parthenon::ParameterInput *pin) { Units units(pin); From 45e1008161642729505e095a6060d09d370e9fc4 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 27 Mar 2023 18:57:40 -0600 Subject: [PATCH 56/95] Clarified HSE r-sampling parameter, removed maxdr --- docs/cluster.md | 18 +++++++-------- inputs/cluster/cluster.in | 1 - inputs/cluster/hse.in | 1 - .../hydrostatic_equilibrium_sphere.cpp | 22 ++++++++++--------- .../hydrostatic_equilibrium_sphere.hpp | 4 ++-- .../test_suites/cluster_hse/cluster_hse.py | 3 +-- 6 files changed, 24 insertions(+), 25 deletions(-) diff --git a/docs/cluster.md b/docs/cluster.md index 8221a8c6..61f16146 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -150,17 +150,17 @@ radius. This radius and density is set by the parameters r_fix = 2.0 # in code_length rho_fix = 0.01477557589278723 # in code_mass/code_length**3 ``` -The parameters `r_sampling` and `max_dr` determine details about how the -profile for the initial hydrodynamic equilbrium is integrated. (FIXME -- these -parameters are esoteric and can potentially be removed. They were added at -somepoint to ensure that the HSE profile is integrated over a domain that -includes the current meshblock and the r_fix radius) +In each meshblock the equations for hydrostatic equilbirium and the entropy +profile are integrated to obtain density and pressure profiles inward and +outward from `r_fix` as needed to cover the meshblock. The parameter +`r_sampling` controls the resolution of the profiles created, where higher +values give higher resolution. ``` -r_fix = 2.0 # in code_length r_sampling = 4.0 -max_dr = 0.001 ``` +Specifically, the resolution of the 1D profile for each meshblock is either +`min(dx,dy,dz)/r_sampling` or `r_k/r_sampling`, whichever is smaller. ## AGN Triggering @@ -190,7 +190,7 @@ $$ where $\hat{rho}$, $\hat{v}$, and $\hat{c}_s$ are respectively the mass weighted density, velocity, and sound speed within the accretion region. The mass of the SMBH, -the radius of the sphere of accretion around the AGN, and the $alpha$ parameter +the radius of the sphere of accretion around the AGN, and the $\alpha$ parameter can be set with ``` @@ -200,7 +200,7 @@ m_smbh = 1.0e-06 # in code_mass accretion_radius = 0.001 # in code_length bondi_alpha= 100.0 # unitless ``` -With BONDI_SCHAYE accretion, the `alpha` used for BOOSTED_BONDI accretion is modified to depend on the number density following: +With BONDI_SCHAYE accretion, the `$\alpha$` used for BOOSTED_BONDI accretion is modified to depend on the number density following: $$ \alpha = diff --git a/inputs/cluster/cluster.in b/inputs/cluster/cluster.in index 846367cb..0badc7bd 100644 --- a/inputs/cluster/cluster.in +++ b/inputs/cluster/cluster.in @@ -151,7 +151,6 @@ rho_fix = 0.01477557589278723 #Building the radii at which to sample initial rho,P r_sampling = 4.0 -max_dr = 0.001 triggering_mode = COLD_GAS diff --git a/inputs/cluster/hse.in b/inputs/cluster/hse.in index 121a3329..cb25b5ce 100644 --- a/inputs/cluster/hse.in +++ b/inputs/cluster/hse.in @@ -121,6 +121,5 @@ rho_fix = 0.01477557589278723 #Building the radii at which to sample initial rho,P r_sampling = 4.0 -max_dr = 0.001 test_he_sphere = true diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp index 54c8c906..0948ecb2 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp @@ -12,6 +12,7 @@ // C++ headers #include +#include // Parthenon headers #include @@ -60,7 +61,6 @@ HydrostaticEquilibriumSphere:: r_sampling_ = pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", "r_sampling", 4.0); - max_dr_ = pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", "max_dr", 1e-3); // Test out the HSE sphere if requested const bool test_he_sphere = pin->GetOrAddBoolean( @@ -68,10 +68,10 @@ HydrostaticEquilibriumSphere:: if (test_he_sphere) { const Real test_he_sphere_r_start = pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", - "test_he_sphere_r_start_kpc", 1e-3 * units.kpc()); + "test_he_sphere_r_start", 1e-3 * units.kpc()); const Real test_he_sphere_r_end = pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", - "test_he_sphere_r_end_kpc", 4000 * units.kpc()); + "test_he_sphere_r_end", 4000 * units.kpc()); const int test_he_sphere_n_r = pin->GetOrAddInteger( "problem/cluster/hydrostatic_equilibrium", "test_he_sphere_n_r", 4000); if (Globals::my_rank == 0) { @@ -138,13 +138,15 @@ HydrostaticEquilibriumSphere::generate_P_rho // Determine spacing of grid (WARNING assumes equispaced grid in x,y,z) // FIXME(forrestglines) There's some floating point comparison issues with these tests - // PARTHENON_REQUIRE(coords.Dxc<1>(0) == coords.Dxc<1>(1), "No equidistant grid in - // x1dir"); PARTHENON_REQUIRE(coords.Dxc<2>(0) == coords.Dxc<2>(1), "No equidistant grid - // in x2dir"); PARTHENON_REQUIRE(coords.Dxc<3>(0) == coords.Dxc<3>(1), "No equidistant - // grid in x3dir"); PARTHENON_REQUIRE(coords.Dxc<1>(0) == coords.Dxc<2>(1), "No - // equidistant grid between x1 and x2 dir"); PARTHENON_REQUIRE(coords.Dxc<2>(0) == - // coords.Dxc<3>(1), "No equidistant grid between x2 and x3 dir"); - const Real dr = std::min(coords.Dxc<1>(0) / r_sampling_, max_dr_); + PARTHENON_REQUIRE( std::abs(coords.Dxc<1>(0) - coords.Dxc<1>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x1dir"); + PARTHENON_REQUIRE( std::abs(coords.Dxc<2>(0) - coords.Dxc<2>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x2dir"); + PARTHENON_REQUIRE( std::abs(coords.Dxc<3>(0) - coords.Dxc<3>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x3dir"); + //Resolution of profile on this meshbock -- use 1/r_sampling_ of resolution + //or 1/r_sampling_ of r_k, whichever is smaller + const Real dr = std::min( + std::min(coords.Dxc<1>(0),std::min(coords.Dxc<2>(0),coords.Dxc<3>(0)))/r_sampling_, + entropy_profile_.r_k_/r_sampling_); + // Loop through mesh for minimum and maximum radius // Make sure to include R_fix_ diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp index 826c4bab..33b242a5 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp @@ -46,8 +46,8 @@ class HydrostaticEquilibriumSphere { // Molecular weights parthenon::Real mu_, mu_e_; - // R mesh sampling parameters - parthenon::Real r_sampling_, max_dr_; + // R mesh sampling parameter + parthenon::Real r_sampling_; /************************************************************ * Functions to build the cluster model diff --git a/tst/regression/test_suites/cluster_hse/cluster_hse.py b/tst/regression/test_suites/cluster_hse/cluster_hse.py index c47cfc31..a5d83789 100644 --- a/tst/regression/test_suites/cluster_hse/cluster_hse.py +++ b/tst/regression/test_suites/cluster_hse/cluster_hse.py @@ -139,8 +139,7 @@ def Prepare(self, parameters, step): f"problem/cluster/entropy_profile/alpha_k={self.alpha_K}", f"problem/cluster/hydrostatic_equilibrium/r_fix={self.R_fix.in_units('code_length').v}", f"problem/cluster/hydrostatic_equilibrium/rho_fix={self.rho_fix.in_units('code_mass/code_length**3').v}", - f"problem/cluster/hydrostatic_equilibrium/r_sampling={self.R_sampling}", - f"problem/cluster/hydrostatic_equilibrium/max_dr={self.max_dR}", + f"problem/cluster/hydrostatic_equilibrium/r_sampling={self.R_sampling}" ] return parameters From 0d619b2ae2ae1f5a3a34946e247e4c2b98b49577 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 27 Mar 2023 18:58:22 -0600 Subject: [PATCH 57/95] Removed AGN triggering reductions from HST --- src/pgen/cluster/agn_triggering.cpp | 64 +++-------------------------- 1 file changed, 6 insertions(+), 58 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 15c3f914..b2891d0b 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -99,9 +99,10 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, } } - // Set up writing the triggering to file, used for debugging. Note that this is - // written every timestep, which is more frequently than history outputs. It - // is also not reduced across ranks and so is only valid without MPI + // Set up writing the triggering to file, used for debugging and regression + // testing. Note that this is written every timestep, which is more + // frequently than history outputs. It is also not reduced across ranks and + // so is only valid without MPI if (write_to_file_ && parthenon::Globals::my_rank == 0) { // Clear the triggering_file std::ofstream triggering_file; @@ -109,59 +110,6 @@ AGNTriggering::AGNTriggering(parthenon::ParameterInput *pin, triggering_file.close(); } - // Set up UserHistoryOutput (Are all these triggering variables necessary? - // They might be for post-mortem debugging) - auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); - std::vector history_var_names; - switch (triggering_mode_) { - case AGNTriggeringMode::COLD_GAS: { - history_var_names.push_back("agn_triggering_cold_mass"); - break; - } - case AGNTriggeringMode::BOOSTED_BONDI: - case AGNTriggeringMode::BOOTH_SCHAYE: { - history_var_names.push_back("agn_triggering_total_mass"); - history_var_names.push_back("agn_triggering_mass_weighted_density"); - history_var_names.push_back("agn_triggering_mass_weighted_velocity"); - history_var_names.push_back("agn_triggering_mass_weighted_cs"); - break; - } - case AGNTriggeringMode::NONE: { - break; - } - } - - for (auto var_name : history_var_names) { - // HACK (forrestglines): The operations should be a - // parthenon::UserHistoryOperation::no_reduce, which is as of writing - // unimplemented - hst_vars.emplace_back(parthenon::HistoryOutputVar( - parthenon::UserHistoryOperation::max, - [var_name](MeshData *md) { - auto pmb = md->GetBlockData(0)->GetBlockPointer(); - auto hydro_pkg = pmb->packages.Get("Hydro"); - return hydro_pkg->Param(var_name); - }, - var_name)); - } - - // Add accretion rate too - if (triggering_mode_ != AGNTriggeringMode::NONE) { - // HACK (forrestglines): The operations should be a - // parthenon::UserHistoryOperation::no_reduce, which is as of writing - // unimplemented - hst_vars.emplace_back(parthenon::HistoryOutputVar( - parthenon::UserHistoryOperation::max, - [](MeshData *md) { - auto pmb = md->GetBlockData(0)->GetBlockPointer(); - auto hydro_pkg = pmb->packages.Get("Hydro"); - const auto &agn_triggering = hydro_pkg->Param("agn_triggering"); - return agn_triggering.GetAccretionRate(hydro_pkg.get()); - }, - "agn_accretion_rate")); - } - hydro_pkg->UpdateParam(parthenon::hist_param_key, hst_vars); - hydro_pkg->AddParam("agn_triggering", *this); } @@ -224,7 +172,7 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, const Real cell_delta_rho = -prim(IDN, k, j, i) / cold_t_acc * dt; if (remove_accreted_mass) { - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, i); // Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); } @@ -344,7 +292,7 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData const Real cell_delta_rho = -prim(IDN, k, j, i) / total_mass * accretion_rate * dt; - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos, k, j, i); + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, i); // Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); From 3e1ad0cf948236be6903b9493bd7f20ac815dde3 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 27 Mar 2023 19:13:18 -0600 Subject: [PATCH 58/95] Removed defunct comment --- src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp index 0948ecb2..4a46f418 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp @@ -137,7 +137,6 @@ HydrostaticEquilibriumSphere::generate_P_rho ************************************************************/ // Determine spacing of grid (WARNING assumes equispaced grid in x,y,z) - // FIXME(forrestglines) There's some floating point comparison issues with these tests PARTHENON_REQUIRE( std::abs(coords.Dxc<1>(0) - coords.Dxc<1>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x1dir"); PARTHENON_REQUIRE( std::abs(coords.Dxc<2>(0) - coords.Dxc<2>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x2dir"); PARTHENON_REQUIRE( std::abs(coords.Dxc<3>(0) - coords.Dxc<3>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x3dir"); From 5e69a58f135952aba87690096ee00cb38f787938 Mon Sep 17 00:00:00 2001 From: forrestglines Date: Mon, 27 Mar 2023 19:17:34 -0600 Subject: [PATCH 59/95] Avoid implicit casting of sign_jet Co-authored-by: Philipp Grete --- src/pgen/cluster/agn_feedback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index e73faf71..1bf73f1d 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -247,7 +247,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, jet_coords.JetCylToSimCartVector(cos_theta, sin_theta, 0, 0, 1, jet_axis_x, jet_axis_y, jet_axis_z); - const int sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk + const Real sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk cons(IDN, k, j, i) += kinetic_density; // mass/volume // velocity*mass/volume From b86514d837ae8809fe542012637cf26cb893e818 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 27 Mar 2023 20:00:18 -0600 Subject: [PATCH 60/95] Removed defunct parameters from notebooks --- inputs/cluster/generate_cluster_input.ipynb | 7 +++---- inputs/cluster/my_cluster.input | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/inputs/cluster/generate_cluster_input.ipynb b/inputs/cluster/generate_cluster_input.ipynb index a75f08c1..008466d3 100644 --- a/inputs/cluster/generate_cluster_input.ipynb +++ b/inputs/cluster/generate_cluster_input.ipynb @@ -170,7 +170,6 @@ "\n", "#Building the radii at which to sample initial rho,P\n", "r_sampling = 4.0\n", - "max_dr = 0.001\n", "\n", "\n", "#Which triggering mode (BOOSTED_BONDI, BOOTH_SCHAYE, COLD_GAS, NONE)\n", @@ -553,9 +552,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.9", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "py39" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -567,7 +566,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.10.9" } }, "nbformat": 4, diff --git a/inputs/cluster/my_cluster.input b/inputs/cluster/my_cluster.input index 129b865c..2fed6696 100644 --- a/inputs/cluster/my_cluster.input +++ b/inputs/cluster/my_cluster.input @@ -143,7 +143,6 @@ rho_fix = 0.01477557589278723 #Building the radii at which to sample initial rho,P r_sampling = 4.0 -max_dr = 0.001 #Which triggering mode (BOOSTED_BONDI, BOOTH_SCHAYE, COLD_GAS, NONE) From e3d47330c6dc2213dcce4847b5070c351c59232b Mon Sep 17 00:00:00 2001 From: par-hermes Date: Tue, 28 Mar 2023 17:17:41 +0000 Subject: [PATCH 61/95] cpp-py-formatter --- src/hydro/srcterms/tabular_cooling.cpp | 21 ++-- src/pgen/cluster.cpp | 37 ++++--- src/pgen/cluster/agn_feedback.cpp | 102 ++++++++++-------- src/pgen/cluster/agn_feedback.hpp | 3 +- src/pgen/cluster/agn_triggering.cpp | 9 +- src/pgen/cluster/cluster_gravity.hpp | 54 +++++----- src/pgen/cluster/cluster_utils.hpp | 3 +- .../hydrostatic_equilibrium_sphere.cpp | 24 +++-- .../hydrostatic_equilibrium_sphere.hpp | 4 +- src/pgen/cluster/magnetic_tower.cpp | 3 +- src/pgen/cluster/snia_feedback.cpp | 56 +++++----- src/pgen/cluster/snia_feedback.hpp | 7 +- .../test_suites/cluster_hse/cluster_hse.py | 2 +- 13 files changed, 173 insertions(+), 152 deletions(-) diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index e8a00555..72b6fdf0 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -9,9 +9,9 @@ //======================================================================================== // C++ headers +#include #include #include -#include // Parthenon headers #include @@ -393,11 +393,13 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt if (!dedt_valid) { if (sub_dt == min_sub_dt) { - //PARTHENON_FAIL("FATAL ERROR in [TabularCooling::SubcyclingSplitSrcTerm]: " + // PARTHENON_FAIL("FATAL ERROR in + // [TabularCooling::SubcyclingSplitSrcTerm]: " // "Minumum sub_dt leads to negative internal energy"); - - //HACK - //Even the minimum subcycle dt would lead to negative internal energy -- so just cool to the floor + + // HACK + // Even the minimum subcycle dt would lead to negative internal energy -- + // so just cool to the floor sub_dt = (dt - sub_t); internal_e_next_h = internal_e_floor; reattempt_sub = false; @@ -468,9 +470,9 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt sub_iter++; } - //PARTHENON_REQUIRE(internal_e > internal_e_floor, "cooled below floor"); - //HACK - if(internal_e < internal_e_floor){ + // PARTHENON_REQUIRE(internal_e > internal_e_floor, "cooled below floor"); + // HACK + if (internal_e < internal_e_floor) { internal_e = internal_e_floor; } @@ -720,8 +722,7 @@ Real TabularCooling::EstimateTimeStep(MeshData *md) const { return std::numeric_limits::max(); } - - if( cooling_time_cfl_ <= 0.0 || isnan(cooling_time_cfl_) || isinf(cooling_time_cfl_) ){ + if (cooling_time_cfl_ <= 0.0 || isnan(cooling_time_cfl_) || isinf(cooling_time_cfl_)) { return std::numeric_limits::infinity(); } diff --git a/src/pgen/cluster.cpp b/src/pgen/cluster.cpp index f5686053..757f203a 100644 --- a/src/pgen/cluster.cpp +++ b/src/pgen/cluster.cpp @@ -118,8 +118,8 @@ void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hyd * Read Uniform Magnetic Field ************************************************************/ - const bool init_uniform_b_field = - pin->GetOrAddBoolean("problem/cluster/uniform_b_field", "init_uniform_b_field", false); + const bool init_uniform_b_field = pin->GetOrAddBoolean( + "problem/cluster/uniform_b_field", "init_uniform_b_field", false); hydro_pkg->AddParam<>("init_uniform_b_field", init_uniform_b_field); if (init_uniform_b_field) { @@ -136,8 +136,8 @@ void ProblemInitPackageData(ParameterInput *pin, parthenon::StateDescriptor *hyd * Read Uniform Magnetic Field ************************************************************/ - const bool init_dipole_b_field = - pin->GetOrAddBoolean("problem/cluster/dipole_b_field", "init_dipole_b_field", false); + const bool init_dipole_b_field = pin->GetOrAddBoolean("problem/cluster/dipole_b_field", + "init_dipole_b_field", false); hydro_pkg->AddParam<>("init_dipole_b_field", init_dipole_b_field); if (init_dipole_b_field) { @@ -264,7 +264,7 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { u(IEN, k, j, i) = E; }); - //end if(init_uniform_gas) + // end if(init_uniform_gas) } else { /************************************************************ * Initialize a HydrostaticEquilibriumSphere @@ -341,15 +341,15 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { const Real y = coords.Xc<2>(j); const Real z = coords.Xc<3>(k); - const Real r3 = pow( SQR(x) + SQR(y) + SQR(z),3./2); - - const Real m_cross_r_x = my*z - mz*y; - const Real m_cross_r_y = mz*x - mx*z; - const Real m_cross_r_z = mx*y - mx*y; + const Real r3 = pow(SQR(x) + SQR(y) + SQR(z), 3. / 2); - A(0, k, j, i) += m_cross_r_x/(4*M_PI*r3); - A(1, k, j, i) += m_cross_r_y/(4*M_PI*r3); - A(2, k, j, i) += m_cross_r_z/(4*M_PI*r3); + const Real m_cross_r_x = my * z - mz * y; + const Real m_cross_r_y = mz * x - mx * z; + const Real m_cross_r_z = mx * y - mx * y; + + A(0, k, j, i) += m_cross_r_x / (4 * M_PI * r3); + A(1, k, j, i) += m_cross_r_y / (4 * M_PI * r3); + A(2, k, j, i) += m_cross_r_z / (4 * M_PI * r3); }); } @@ -374,7 +374,6 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { 0.5 * (SQR(u(IB1, k, j, i)) + SQR(u(IB2, k, j, i)) + SQR(u(IB3, k, j, i))); }); - /************************************************************ * Add uniform magnetic field to the conserved variables ************************************************************/ @@ -395,12 +394,12 @@ void ProblemGenerator(MeshBlock *pmb, parthenon::ParameterInput *pin) { u(IB2, k, j, i) += by; u(IB3, k, j, i) += bz; - //Old magnetic energy is b_i^2, new Magnetic energy should be 0.5*(b_i + b)^2, - //add b_i*b + 0.5b^2 to old energy to accomplish that - u(IEN, k, j, i) += bx_i*bx + by_i*by + bz_i*bz+ - 0.5 * (SQR(bx) + SQR(by) + SQR(bz)); + // Old magnetic energy is b_i^2, new Magnetic energy should be 0.5*(b_i + + // b)^2, add b_i*b + 0.5b^2 to old energy to accomplish that + u(IEN, k, j, i) += + bx_i * bx + by_i * by + bz_i * bz + 0.5 * (SQR(bx) + SQR(by) + SQR(bz)); }); - //end if(init_uniform_b_field) + // end if(init_uniform_b_field) } } // END if(hydro_pkg->Param("fluid") == Fluid::glmmhd) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 1bf73f1d..f7314627 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -4,7 +4,8 @@ // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== //! \file agn_feedback.cpp -// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic tower +// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic +// tower #include @@ -39,12 +40,12 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_fraction", 0.0)), magnetic_fraction_( pin->GetOrAddReal("problem/cluster/agn_feedback", "magnetic_fraction", 0.0)), - thermal_mass_fraction_( - pin->GetOrAddReal("problem/cluster/agn_feedback", "thermal_mass_fraction", NAN)), - kinetic_mass_fraction_( - pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_mass_fraction", NAN)), - magnetic_mass_fraction_( - pin->GetOrAddReal("problem/cluster/agn_feedback", "magnetic_mass_fraction", NAN)), + thermal_mass_fraction_(pin->GetOrAddReal("problem/cluster/agn_feedback", + "thermal_mass_fraction", NAN)), + kinetic_mass_fraction_(pin->GetOrAddReal("problem/cluster/agn_feedback", + "kinetic_mass_fraction", NAN)), + magnetic_mass_fraction_(pin->GetOrAddReal("problem/cluster/agn_feedback", + "magnetic_mass_fraction", NAN)), thermal_radius_( pin->GetOrAddReal("problem/cluster/agn_feedback", "thermal_radius", 0.01)), kinetic_jet_radius_( @@ -53,31 +54,34 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_height", 0.02)), disabled_(pin->GetOrAddBoolean("problem/cluster/agn_feedback", "disabled", false)) { - //If any mass_fractions aren't set, set to energy fraction - if(std::isnan(thermal_mass_fraction_)) thermal_mass_fraction_ = thermal_fraction_; - if(std::isnan(magnetic_mass_fraction_)) magnetic_mass_fraction_ = magnetic_fraction_; - if(std::isnan(kinetic_mass_fraction_)) kinetic_mass_fraction_ = kinetic_fraction_; + // If any mass_fractions aren't set, set to energy fraction + if (std::isnan(thermal_mass_fraction_)) thermal_mass_fraction_ = thermal_fraction_; + if (std::isnan(magnetic_mass_fraction_)) magnetic_mass_fraction_ = magnetic_fraction_; + if (std::isnan(kinetic_mass_fraction_)) kinetic_mass_fraction_ = kinetic_fraction_; - //Normalize the thermal, kinetic, and magnetic fractions to sum to 1.0 + // Normalize the thermal, kinetic, and magnetic fractions to sum to 1.0 const Real total_frac = thermal_fraction_ + kinetic_fraction_ + magnetic_fraction_; - if( total_frac > 0){ - thermal_fraction_ = thermal_fraction_/total_frac; - kinetic_fraction_ = kinetic_fraction_/total_frac; - magnetic_fraction_ = magnetic_fraction_/total_frac; + if (total_frac > 0) { + thermal_fraction_ = thermal_fraction_ / total_frac; + kinetic_fraction_ = kinetic_fraction_ / total_frac; + magnetic_fraction_ = magnetic_fraction_ / total_frac; } - //Normalize the thermal, kinetic, and magnetic mass fractions to sum to 1.0 - const Real total_mass_frac = thermal_mass_fraction_ + kinetic_mass_fraction_ + magnetic_mass_fraction_; - if( total_mass_frac > 0){ - thermal_mass_fraction_ = thermal_mass_fraction_/total_mass_frac; - kinetic_mass_fraction_ = kinetic_mass_fraction_/total_mass_frac; - magnetic_mass_fraction_ = magnetic_mass_fraction_/total_mass_frac; + // Normalize the thermal, kinetic, and magnetic mass fractions to sum to 1.0 + const Real total_mass_frac = + thermal_mass_fraction_ + kinetic_mass_fraction_ + magnetic_mass_fraction_; + if (total_mass_frac > 0) { + thermal_mass_fraction_ = thermal_mass_fraction_ / total_mass_frac; + kinetic_mass_fraction_ = kinetic_mass_fraction_ / total_mass_frac; + magnetic_mass_fraction_ = magnetic_mass_fraction_ / total_mass_frac; } - PARTHENON_REQUIRE( thermal_fraction_ >= 0 && kinetic_fraction_ >= 0 && magnetic_fraction_ >= 0, - "AGN feedback energy fractions must be non-negative."); - PARTHENON_REQUIRE( thermal_mass_fraction_ >= 0 && kinetic_mass_fraction_ >= 0 && magnetic_mass_fraction_ >= 0, - "AGN feedback mass fractions must be non-negative."); + PARTHENON_REQUIRE(thermal_fraction_ >= 0 && kinetic_fraction_ >= 0 && + magnetic_fraction_ >= 0, + "AGN feedback energy fractions must be non-negative."); + PARTHENON_REQUIRE(thermal_mass_fraction_ >= 0 && kinetic_mass_fraction_ >= 0 && + magnetic_mass_fraction_ >= 0, + "AGN feedback mass fractions must be non-negative."); // Add user history output variable for AGN power auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); @@ -115,10 +119,10 @@ parthenon::Real AGNFeedback::GetFeedbackMassRate(StateDescriptor *hydro_pkg) con const Real accretion_rate = agn_triggering.GetAccretionRate(hydro_pkg); - //Return a mass_rate equal to the accretion_rate minus energy-mass conversion - //to feedback energy. We could divert mass to increase the SMBH/leave out - //from mass injection - const Real mass_rate = accretion_rate*( 1 - efficiency_); + // Return a mass_rate equal to the accretion_rate minus energy-mass conversion + // to feedback energy. We could divert mass to increase the SMBH/leave out + // from mass injection + const Real mass_rate = accretion_rate * (1 - efficiency_); return mass_rate; } @@ -155,8 +159,10 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, return; } - PARTHENON_REQUIRE( magnetic_fraction_ != 0 || thermal_fraction_ != 0 || kinetic_fraction_ != 0, - "AGNFeedback::FeedbackSrcTerm Magnetic, Thermal, and Kinetic fractions are all zero"); + PARTHENON_REQUIRE(magnetic_fraction_ != 0 || thermal_fraction_ != 0 || + kinetic_fraction_ != 0, + "AGNFeedback::FeedbackSrcTerm Magnetic, Thermal, and Kinetic " + "fractions are all zero"); // Grab some necessary variables const auto &prim_pack = md->PackVariables(std::vector{"prim"}); @@ -173,10 +179,12 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real thermal_radius2 = thermal_radius_ * thermal_radius_; const Real thermal_scaling_factor = 1 / (4. / 3. * M_PI * pow(thermal_radius_, 3)); - //Amount of energy/volume to dump in each cell - const Real thermal_feedback = thermal_fraction_* power * thermal_scaling_factor * beta_dt; - //Amount of density to dump in each cell - const Real thermal_density = thermal_mass_fraction_ * mass_rate * thermal_scaling_factor * beta_dt; + // Amount of energy/volume to dump in each cell + const Real thermal_feedback = + thermal_fraction_ * power * thermal_scaling_factor * beta_dt; + // Amount of density to dump in each cell + const Real thermal_density = + thermal_mass_fraction_ * mass_rate * thermal_scaling_factor * beta_dt; //////////////////////////////////////////////////////////////////////////////// // Kinetic Jet Quantities @@ -188,15 +196,18 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real kinetic_jet_height = kinetic_jet_height_; // Matches 1/2.*jet_density*jet_velocity*jet_velocity*beta_dt; - const Real kinetic_feedback = kinetic_fraction_ * power * kinetic_scaling_factor * beta_dt; // energy/volume + const Real kinetic_feedback = + kinetic_fraction_ * power * kinetic_scaling_factor * beta_dt; // energy/volume - //Amount of density to dump in each cell - const Real kinetic_density = kinetic_mass_fraction_ * mass_rate * kinetic_scaling_factor * beta_dt; + // Amount of density to dump in each cell + const Real kinetic_density = + kinetic_mass_fraction_ * mass_rate * kinetic_scaling_factor * beta_dt; - //Velocity of added gas - const Real kinetic_velocity = std::sqrt(2. * kinetic_fraction_ * power / ( kinetic_fraction_ * mass_rate) ); + // Velocity of added gas + const Real kinetic_velocity = + std::sqrt(2. * kinetic_fraction_ * power / (kinetic_fraction_ * mass_rate)); - //Amount of momentum density ( density * velocity) to dump in each cell + // Amount of momentum density ( density * velocity) to dump in each cell const Real kinetic_momentum = kinetic_density * kinetic_velocity; //////////////////////////////////////////////////////////////////////////////// @@ -225,11 +236,10 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Determine if point is in sphere r<=thermal_radius if (r2 <= thermal_radius2) { // Then apply heating - if( thermal_feedback > 0) - cons(IEN, k, j, i) += thermal_feedback; + if (thermal_feedback > 0) cons(IEN, k, j, i) += thermal_feedback; // Add density at constant velocity - if( thermal_density > 0) - AddDensityToConsAtFixedVel(thermal_density, cons, prim, k, j,i); + if (thermal_density > 0) + AddDensityToConsAtFixedVel(thermal_density, cons, prim, k, j, i); } } diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index 4d3c6e96..d2d06ba4 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -6,7 +6,8 @@ // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== //! \file agn_feedback.hpp -// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic tower +// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic +// tower // parthenon headers #include diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index b2891d0b..5d2fdc46 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -172,7 +172,8 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, const Real cell_delta_rho = -prim(IDN, k, j, i) / cold_t_acc * dt; if (remove_accreted_mass) { - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, i); + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), + k, j, i); // Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); } @@ -290,9 +291,11 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData pow(coords.Xc<1>(i), 2) + pow(coords.Xc<2>(j), 2) + pow(coords.Xc<3>(k), 2); if (r2 < accretion_radius2) { - const Real cell_delta_rho = -prim(IDN, k, j, i) / total_mass * accretion_rate * dt; + const Real cell_delta_rho = + -prim(IDN, k, j, i) / total_mass * accretion_rate * dt; - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, i); + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, + i); // Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); diff --git a/src/pgen/cluster/cluster_gravity.hpp b/src/pgen/cluster/cluster_gravity.hpp index 76cb5b4f..e35e60e4 100644 --- a/src/pgen/cluster/cluster_gravity.hpp +++ b/src/pgen/cluster/cluster_gravity.hpp @@ -64,20 +64,20 @@ class ClusterGravity { return R_nfw_s; } static parthenon::Real calc_g_const_nfw(const parthenon::Real gravitational_constant, - const parthenon::Real m_nfw_200, - const parthenon::Real c_nfw) { + const parthenon::Real m_nfw_200, + const parthenon::Real c_nfw) { return gravitational_constant * m_nfw_200 / (log(1 + c_nfw) - c_nfw / (1 + c_nfw)); } static parthenon::Real calc_rho_const_nfw(const parthenon::Real gravitational_constant, - const parthenon::Real m_nfw_200, - const parthenon::Real c_nfw) { - return m_nfw_200 / (4*M_PI*(log(1 + c_nfw) - c_nfw / (1 + c_nfw))); + const parthenon::Real m_nfw_200, + const parthenon::Real c_nfw) { + return m_nfw_200 / (4 * M_PI * (log(1 + c_nfw) - c_nfw / (1 + c_nfw))); } static parthenon::Real calc_g_const_bcg(const parthenon::Real gravitational_constant, - BCG which_bcg_g, const parthenon::Real m_bcg_s, - const parthenon::Real r_bcg_s, - const parthenon::Real alpha_bcg_s, - const parthenon::Real beta_bcg_s) { + BCG which_bcg_g, const parthenon::Real m_bcg_s, + const parthenon::Real r_bcg_s, + const parthenon::Real alpha_bcg_s, + const parthenon::Real beta_bcg_s) { switch (which_bcg_g) { case BCG::NONE: return 0; @@ -87,30 +87,31 @@ class ClusterGravity { return NAN; } static parthenon::Real calc_rho_const_bcg(const parthenon::Real gravitational_constant, - BCG which_bcg_g, const parthenon::Real m_bcg_s, - const parthenon::Real r_bcg_s, - const parthenon::Real alpha_bcg_s, - const parthenon::Real beta_bcg_s) { + BCG which_bcg_g, + const parthenon::Real m_bcg_s, + const parthenon::Real r_bcg_s, + const parthenon::Real alpha_bcg_s, + const parthenon::Real beta_bcg_s) { switch (which_bcg_g) { case BCG::NONE: return 0; case BCG::HERNQUIST: - return m_bcg_s * r_bcg_s/ (2*M_PI); + return m_bcg_s * r_bcg_s / (2 * M_PI); } return NAN; } static KOKKOS_INLINE_FUNCTION parthenon::Real calc_g_const_smbh(const parthenon::Real gravitational_constant, - const parthenon::Real m_smbh) { + const parthenon::Real m_smbh) { return gravitational_constant * m_smbh; } public: - //ClusterGravity(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) - //is called from cluster.cpp to add the ClusterGravity object to hydro_pkg + // ClusterGravity(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) + // is called from cluster.cpp to add the ClusterGravity object to hydro_pkg // - //ClusterGravity(parthenon::ParameterInput *pin) is used in SNIAFeedback to - //calculate the BCG density profile + // ClusterGravity(parthenon::ParameterInput *pin) is used in SNIAFeedback to + // calculate the BCG density profile ClusterGravity(parthenon::ParameterInput *pin) { Units units(pin); @@ -153,7 +154,7 @@ class ClusterGravity { pin->GetOrAddReal("problem/cluster/gravity", "m_bcg_s", 7.5e10 * units.msun()); r_bcg_s_ = pin->GetOrAddReal("problem/cluster/gravity", "r_bcg_s", 4 * units.kpc()); g_const_bcg_ = calc_g_const_bcg(units.gravitational_constant(), which_bcg_g_, M_bcg_s, - r_bcg_s_, alpha_bcg_s_, beta_bcg_s_); + r_bcg_s_, alpha_bcg_s_, beta_bcg_s_); const parthenon::Real m_smbh = pin->GetOrAddReal("problem/cluster/gravity", "m_smbh", 3.4e8 * units.msun()); @@ -161,10 +162,10 @@ class ClusterGravity { smoothing_r_ = pin->GetOrAddReal("problem/cluster/gravity", "g_smoothing_radius", 0.0); - } - ClusterGravity(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) : ClusterGravity(pin) { + ClusterGravity(parthenon::ParameterInput *pin, parthenon::StateDescriptor *hydro_pkg) + : ClusterGravity(pin) { hydro_pkg->AddParam<>("cluster_gravity", *this); } @@ -208,7 +209,7 @@ class ClusterGravity { // Add NFW gravity if (include_nfw_g_) { - rho += rho_const_nfw_ / ( r * pow(r + r_nfw_s_,2)); + rho += rho_const_nfw_ / (r * pow(r + r_nfw_s_, 2)); } // Add BCG gravity @@ -216,21 +217,20 @@ class ClusterGravity { case BCG::NONE: break; case BCG::HERNQUIST: - rho += rho_const_bcg_ / ( r * pow(r + r_bcg_s_, 3)); + rho += rho_const_bcg_ / (r * pow(r + r_bcg_s_, 3)); break; } // SMBH, point mass gravity -- density is not defined. Throw an error if (include_smbh_g_ && r <= smoothing_r_) { - Kokkos::abort("ClusterGravity::SMBH density is not defined"); + Kokkos::abort("ClusterGravity::SMBH density is not defined"); } return rho; } - //SNIAFeedback needs to be a friend to disable the SMBH and NFW + // SNIAFeedback needs to be a friend to disable the SMBH and NFW friend class SNIAFeedback; - }; } // namespace cluster diff --git a/src/pgen/cluster/cluster_utils.hpp b/src/pgen/cluster/cluster_utils.hpp index 9d9f50e9..e76e2e84 100644 --- a/src/pgen/cluster/cluster_utils.hpp +++ b/src/pgen/cluster/cluster_utils.hpp @@ -22,8 +22,7 @@ namespace cluster { template KOKKOS_INLINE_FUNCTION void AddDensityToConsAtFixedVel(const parthenon::Real density, View4D &cons, - const View4D &prim, const int &k, - const int &j, const int &i) { + const View4D &prim, const int &k, const int &j, const int &i) { // Add density such that velocity is fixed cons(IDN, k, j, i) += density; cons(IM1, k, j, i) += density * prim(IV1, k, j, i); diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp index 4a46f418..53912e2a 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp @@ -137,15 +137,21 @@ HydrostaticEquilibriumSphere::generate_P_rho ************************************************************/ // Determine spacing of grid (WARNING assumes equispaced grid in x,y,z) - PARTHENON_REQUIRE( std::abs(coords.Dxc<1>(0) - coords.Dxc<1>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x1dir"); - PARTHENON_REQUIRE( std::abs(coords.Dxc<2>(0) - coords.Dxc<2>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x2dir"); - PARTHENON_REQUIRE( std::abs(coords.Dxc<3>(0) - coords.Dxc<3>(1) ) < 10*std::numeric_limits::epsilon(), "No equidistant grid in x3dir"); - //Resolution of profile on this meshbock -- use 1/r_sampling_ of resolution - //or 1/r_sampling_ of r_k, whichever is smaller - const Real dr = std::min( - std::min(coords.Dxc<1>(0),std::min(coords.Dxc<2>(0),coords.Dxc<3>(0)))/r_sampling_, - entropy_profile_.r_k_/r_sampling_); - + PARTHENON_REQUIRE(std::abs(coords.Dxc<1>(0) - coords.Dxc<1>(1)) < + 10 * std::numeric_limits::epsilon(), + "No equidistant grid in x1dir"); + PARTHENON_REQUIRE(std::abs(coords.Dxc<2>(0) - coords.Dxc<2>(1)) < + 10 * std::numeric_limits::epsilon(), + "No equidistant grid in x2dir"); + PARTHENON_REQUIRE(std::abs(coords.Dxc<3>(0) - coords.Dxc<3>(1)) < + 10 * std::numeric_limits::epsilon(), + "No equidistant grid in x3dir"); + // Resolution of profile on this meshbock -- use 1/r_sampling_ of resolution + // or 1/r_sampling_ of r_k, whichever is smaller + const Real dr = + std::min(std::min(coords.Dxc<1>(0), std::min(coords.Dxc<2>(0), coords.Dxc<3>(0))) / + r_sampling_, + entropy_profile_.r_k_ / r_sampling_); // Loop through mesh for minimum and maximum radius // Make sure to include R_fix_ diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp index 33b242a5..1daf976e 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.hpp @@ -61,7 +61,7 @@ class HydrostaticEquilibriumSphere { KOKKOS_INLINE_FUNCTION parthenon::Real P_from_rho_K(const parthenon::Real rho, const parthenon::Real k) const { const parthenon::Real p = - k * pow(rho/atomic_mass_unit_, 5. / 3.) /( mu_ * pow(mu_e_, 2. / 3.) ); + k * pow(rho / atomic_mass_unit_, 5. / 3.) / (mu_ * pow(mu_e_, 2. / 3.)); return p; } @@ -70,7 +70,7 @@ class HydrostaticEquilibriumSphere { KOKKOS_INLINE_FUNCTION parthenon::Real rho_from_P_K(const parthenon::Real p, const parthenon::Real k) const { const parthenon::Real rho = - pow(mu_ * p / k, 3. / 5.) * atomic_mass_unit_ * pow( mu_e_, 2. / 5); + pow(mu_ * p / k, 3. / 5.) * atomic_mass_unit_ * pow(mu_e_, 2. / 5); return rho; } diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index 1825d07a..39d390e6 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -132,7 +132,8 @@ void MagneticTower::AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mas // Add density const Real cell_delta_rho = mt.DensityFromSimCart(coords.Xc<1>(i), coords.Xc<2>(j), coords.Xc<3>(k)); - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, i); + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, + i); }); } diff --git a/src/pgen/cluster/snia_feedback.cpp b/src/pgen/cluster/snia_feedback.cpp index 63754f2c..fee74c1a 100644 --- a/src/pgen/cluster/snia_feedback.cpp +++ b/src/pgen/cluster/snia_feedback.cpp @@ -4,7 +4,8 @@ // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== //! \file agn_feedback.cpp -// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic tower +// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic +// tower #include @@ -29,26 +30,27 @@ namespace cluster { using namespace parthenon; SNIAFeedback::SNIAFeedback(parthenon::ParameterInput *pin, - parthenon::StateDescriptor *hydro_pkg) - : - power_per_bcg_mass_(pin->GetOrAddReal("problem/cluster/snia_feedback", "power_per_bcg_mass", 0.0)), - mass_rate_per_bcg_mass_(pin->GetOrAddReal("problem/cluster/snia_feedback", "mass_rate_per_bcg_mass", 0.0)), - bcg_gravity_(pin), - disabled_(pin->GetOrAddBoolean("problem/cluster/snia_feedback", "disabled", false)) { - - //Initialize the gravity from the cluster - //Turn off the NFW and SMBH to get just the BCG gravity + parthenon::StateDescriptor *hydro_pkg) + : power_per_bcg_mass_( + pin->GetOrAddReal("problem/cluster/snia_feedback", "power_per_bcg_mass", 0.0)), + mass_rate_per_bcg_mass_(pin->GetOrAddReal("problem/cluster/snia_feedback", + "mass_rate_per_bcg_mass", 0.0)), + bcg_gravity_(pin), disabled_(pin->GetOrAddBoolean("problem/cluster/snia_feedback", + "disabled", false)) { + + // Initialize the gravity from the cluster + // Turn off the NFW and SMBH to get just the BCG gravity bcg_gravity_.include_nfw_g_ = false; bcg_gravity_.include_smbh_g_ = false; - PARTHENON_REQUIRE( disabled_ || bcg_gravity_.which_bcg_g_ != BCG::NONE, - "BCG must be defined for SNIA Feedback to be enabled"); + PARTHENON_REQUIRE(disabled_ || bcg_gravity_.which_bcg_g_ != BCG::NONE, + "BCG must be defined for SNIA Feedback to be enabled"); hydro_pkg->AddParam("snia_feedback", *this); } void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, - const parthenon::Real beta_dt, - const parthenon::SimTime &tm) const { + const parthenon::Real beta_dt, + const parthenon::SimTime &tm) const { auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); auto fluid = hydro_pkg->Param("fluid"); if (fluid == Fluid::euler) { @@ -61,15 +63,15 @@ void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, } template void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, - const parthenon::Real beta_dt, - const parthenon::SimTime &tm, const EOS &eos) const { + const parthenon::Real beta_dt, + const parthenon::SimTime &tm, const EOS &eos) const { using parthenon::IndexDomain; using parthenon::IndexRange; using parthenon::Real; auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); - if ( (power_per_bcg_mass_ == 0 && mass_rate_per_bcg_mass_ == 0) || disabled_) { + if ((power_per_bcg_mass_ == 0 && mass_rate_per_bcg_mass_ == 0) || disabled_) { // No AGN feedback, return return; } @@ -83,8 +85,8 @@ void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const auto nhydro = hydro_pkg->Param("nhydro"); const auto nscalars = hydro_pkg->Param("nscalars"); - const Real energy_per_bcg_mass = power_per_bcg_mass_*beta_dt; - const Real mass_per_bcg_mass = mass_rate_per_bcg_mass_*beta_dt; + const Real energy_per_bcg_mass = power_per_bcg_mass_ * beta_dt; + const Real mass_per_bcg_mass = mass_rate_per_bcg_mass_ * beta_dt; const ClusterGravity bcg_gravity = bcg_gravity_; @@ -92,9 +94,9 @@ void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Constant volumetric heating parthenon::par_for( - DEFAULT_LOOP_PATTERN, "SNIAFeedback::FeedbackSrcTerm", - parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, - ib.e, KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i) { + DEFAULT_LOOP_PATTERN, "SNIAFeedback::FeedbackSrcTerm", parthenon::DevExecSpace(), 0, + cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, + KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i) { auto &cons = cons_pack(b); auto &prim = prim_pack(b); const auto &coords = cons_pack.GetCoords(b); @@ -103,17 +105,17 @@ void SNIAFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real y = coords.Xc<2>(j); const Real z = coords.Xc<3>(k); - const Real r = sqrt(x*x + y*y + z*z); + const Real r = sqrt(x * x + y * y + z * z); const Real bcg_density = bcg_gravity.rho_from_r(r); - const Real snia_energy_density = energy_per_bcg_mass*bcg_density; - const Real snia_mass_density = mass_per_bcg_mass*bcg_density; + const Real snia_energy_density = energy_per_bcg_mass * bcg_density; + const Real snia_mass_density = mass_per_bcg_mass * bcg_density; cons(IEN, k, j, i) += snia_energy_density; - AddDensityToConsAtFixedVel(snia_mass_density, cons, prim, k, j,i); + AddDensityToConsAtFixedVel(snia_mass_density, cons, prim, k, j, i); - eos.ConsToPrim(cons,prim, nhydro, nscalars, k, j, i); + eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); }); } diff --git a/src/pgen/cluster/snia_feedback.hpp b/src/pgen/cluster/snia_feedback.hpp index 2e5f3aa6..307f972e 100644 --- a/src/pgen/cluster/snia_feedback.hpp +++ b/src/pgen/cluster/snia_feedback.hpp @@ -24,12 +24,11 @@ namespace cluster { ************************************************************/ class SNIAFeedback { public: - - //Power and Mass to inject per mass in the BCG - parthenon::Real power_per_bcg_mass_; //energy/(mass*time) + // Power and Mass to inject per mass in the BCG + parthenon::Real power_per_bcg_mass_; // energy/(mass*time) parthenon::Real mass_rate_per_bcg_mass_; // 1/(time) - //ClusterGravity object to calculate BCG density + // ClusterGravity object to calculate BCG density ClusterGravity bcg_gravity_; const bool disabled_; diff --git a/tst/regression/test_suites/cluster_hse/cluster_hse.py b/tst/regression/test_suites/cluster_hse/cluster_hse.py index a5d83789..9fb7ba6a 100644 --- a/tst/regression/test_suites/cluster_hse/cluster_hse.py +++ b/tst/regression/test_suites/cluster_hse/cluster_hse.py @@ -139,7 +139,7 @@ def Prepare(self, parameters, step): f"problem/cluster/entropy_profile/alpha_k={self.alpha_K}", f"problem/cluster/hydrostatic_equilibrium/r_fix={self.R_fix.in_units('code_length').v}", f"problem/cluster/hydrostatic_equilibrium/rho_fix={self.rho_fix.in_units('code_mass/code_length**3').v}", - f"problem/cluster/hydrostatic_equilibrium/r_sampling={self.R_sampling}" + f"problem/cluster/hydrostatic_equilibrium/r_sampling={self.R_sampling}", ] return parameters From 63487362efdf99fc6d9a36bd918e15f1af767622 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 28 Mar 2023 11:55:59 -0600 Subject: [PATCH 62/95] Removed defunct `use_scratch` --- inputs/cluster/agn_triggering.in | 1 - inputs/cluster/cluster.in | 1 - inputs/cluster/cooling.in | 1 - inputs/cluster/generate_cluster_input.ipynb | 1 - inputs/cluster/hse.in | 1 - inputs/cluster/hydro_agn_feedback.in | 1 - inputs/cluster/magnetic_tower.in | 1 - inputs/cluster/my_cluster.input | 1 - 8 files changed, 8 deletions(-) diff --git a/inputs/cluster/agn_triggering.in b/inputs/cluster/agn_triggering.in index 6c554e6f..bee9bd9c 100644 --- a/inputs/cluster/agn_triggering.in +++ b/inputs/cluster/agn_triggering.in @@ -67,7 +67,6 @@ eos = adiabatic riemann = none reconstruction = dc calc_dt_hyp = true -use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM He_mass_fraction = 0.25 diff --git a/inputs/cluster/cluster.in b/inputs/cluster/cluster.in index 0badc7bd..501ea64f 100644 --- a/inputs/cluster/cluster.in +++ b/inputs/cluster/cluster.in @@ -74,7 +74,6 @@ gamma = 1.6666666666666667 # gamma = C_p/C_v eos = adiabatic riemann = hlld reconstruction = plm -use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM He_mass_fraction = 0.25 diff --git a/inputs/cluster/cooling.in b/inputs/cluster/cooling.in index 2979860f..6e17bb59 100644 --- a/inputs/cluster/cooling.in +++ b/inputs/cluster/cooling.in @@ -56,7 +56,6 @@ gamma = 1.6666666666666667 # gamma = C_p/C_v eos = adiabatic riemann = hlle reconstruction = plm -use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM He_mass_fraction = 0.25 diff --git a/inputs/cluster/generate_cluster_input.ipynb b/inputs/cluster/generate_cluster_input.ipynb index 008466d3..b23f9d0a 100644 --- a/inputs/cluster/generate_cluster_input.ipynb +++ b/inputs/cluster/generate_cluster_input.ipynb @@ -102,7 +102,6 @@ "eos = adiabatic\n", "riemann = hlld\n", "reconstruction = plm\n", - "use_scratch = false\n", "scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM\n", "Tfloor = {unyt.unyt_quantity(1e4,\"K\").v}\n", "\n", diff --git a/inputs/cluster/hse.in b/inputs/cluster/hse.in index cb25b5ce..0d57f436 100644 --- a/inputs/cluster/hse.in +++ b/inputs/cluster/hse.in @@ -66,7 +66,6 @@ gamma = 1.6666666666666667 # gamma = C_p/C_v eos = adiabatic riemann = hlle reconstruction = plm -use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM He_mass_fraction = 0.25 diff --git a/inputs/cluster/hydro_agn_feedback.in b/inputs/cluster/hydro_agn_feedback.in index 82acffe6..f5ec6554 100644 --- a/inputs/cluster/hydro_agn_feedback.in +++ b/inputs/cluster/hydro_agn_feedback.in @@ -67,7 +67,6 @@ eos = adiabatic riemann = none reconstruction = dc calc_dt_hyp = true -use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM He_mass_fraction = 0.25 diff --git a/inputs/cluster/magnetic_tower.in b/inputs/cluster/magnetic_tower.in index 9051c733..216a2fd7 100644 --- a/inputs/cluster/magnetic_tower.in +++ b/inputs/cluster/magnetic_tower.in @@ -67,7 +67,6 @@ eos = adiabatic riemann = none reconstruction = dc calc_dt_hyp = true -use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM He_mass_fraction = 0.25 diff --git a/inputs/cluster/my_cluster.input b/inputs/cluster/my_cluster.input index 2fed6696..6a99841b 100644 --- a/inputs/cluster/my_cluster.input +++ b/inputs/cluster/my_cluster.input @@ -75,7 +75,6 @@ gamma = 5./3. # gamma = C_p/C_v eos = adiabatic riemann = hlld reconstruction = plm -use_scratch = false scratch_level = 0 # 0 is actual scratch (tiny); 1 is HBM Tfloor = 10000.0 From 6a6226a25c85c8577a2603867529d6ed0317b427 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 28 Mar 2023 14:04:34 -0500 Subject: [PATCH 63/95] Disable SNIA feedback in cluster tests --- inputs/cluster/agn_triggering.in | 2 ++ inputs/cluster/cooling.in | 3 +++ inputs/cluster/hse.in | 3 +++ inputs/cluster/hydro_agn_feedback.in | 3 +++ inputs/cluster/magnetic_tower.in | 3 +++ 5 files changed, 14 insertions(+) diff --git a/inputs/cluster/agn_triggering.in b/inputs/cluster/agn_triggering.in index bee9bd9c..9da96872 100644 --- a/inputs/cluster/agn_triggering.in +++ b/inputs/cluster/agn_triggering.in @@ -111,3 +111,5 @@ triggering_filename= agn_triggering.dat #Don't do any feedback with the triggering disabled = true + +disabled = True diff --git a/inputs/cluster/cooling.in b/inputs/cluster/cooling.in index 6e17bb59..eeb7243f 100644 --- a/inputs/cluster/cooling.in +++ b/inputs/cluster/cooling.in @@ -94,3 +94,6 @@ ux = 0 uy = 0 uz = 0 pres = 1.5454368403867562 + + +disabled = True diff --git a/inputs/cluster/hse.in b/inputs/cluster/hse.in index 0d57f436..266373b2 100644 --- a/inputs/cluster/hse.in +++ b/inputs/cluster/hse.in @@ -122,3 +122,6 @@ rho_fix = 0.01477557589278723 r_sampling = 4.0 test_he_sphere = true + + +disabled = True diff --git a/inputs/cluster/hydro_agn_feedback.in b/inputs/cluster/hydro_agn_feedback.in index f5ec6554..b144f32a 100644 --- a/inputs/cluster/hydro_agn_feedback.in +++ b/inputs/cluster/hydro_agn_feedback.in @@ -111,3 +111,6 @@ thermal_radius = 0.0125 kinetic_jet_radius = 0.01 kinetic_jet_height = 0.02 + + +disabled = True diff --git a/inputs/cluster/magnetic_tower.in b/inputs/cluster/magnetic_tower.in index 216a2fd7..7459a4b8 100644 --- a/inputs/cluster/magnetic_tower.in +++ b/inputs/cluster/magnetic_tower.in @@ -113,3 +113,6 @@ initial_field = 0.12431560000204142 fixed_field_rate = 12.431560000204144 fixed_mass_rate = 1.7658562333594375e-05 l_mass_scale = 0.005 + + +disabled = True From f7c70e36163052b108b9bfffbd041c0ec8478a57 Mon Sep 17 00:00:00 2001 From: par-hermes Date: Tue, 28 Mar 2023 19:49:09 +0000 Subject: [PATCH 64/95] cpp-py-formatter --- src/eos/adiabatic_glmmhd.hpp | 7 +++---- src/eos/adiabatic_hydro.hpp | 7 +++---- src/hydro/srcterms/tabular_cooling.cpp | 5 +++-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/src/eos/adiabatic_glmmhd.hpp b/src/eos/adiabatic_glmmhd.hpp index a065bead..9fa80b76 100644 --- a/src/eos/adiabatic_glmmhd.hpp +++ b/src/eos/adiabatic_glmmhd.hpp @@ -111,10 +111,9 @@ class AdiabaticGLMMHDEOS : public EquationOfState { // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) // and the code will fail if a negative pressure is encountered. - PARTHENON_REQUIRE( - w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, - "Got negative pressure. Consider enabling first-order flux " - "correction or setting a reasonble pressure or temperature floor."); + PARTHENON_REQUIRE(w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, + "Got negative pressure. Consider enabling first-order flux " + "correction or setting a reasonble pressure or temperature floor."); // Pressure floor (if present) takes precedence over temperature floor if ((pressure_floor_ > 0.0) && (w_p < pressure_floor_)) { diff --git a/src/eos/adiabatic_hydro.hpp b/src/eos/adiabatic_hydro.hpp index 599650df..4dca9ce2 100644 --- a/src/eos/adiabatic_hydro.hpp +++ b/src/eos/adiabatic_hydro.hpp @@ -86,10 +86,9 @@ class AdiabaticHydroEOS : public EquationOfState { // Let's apply floors explicitly, i.e., by default floor will be disabled (<=0) // and the code will fail if a negative pressure is encountered. - PARTHENON_REQUIRE( - w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, - "Got negative pressure. Consider enabling first-order flux " - "correction or setting a reasonble pressure or temperature floor."); + PARTHENON_REQUIRE(w_p > 0.0 || pressure_floor_ > 0.0 || e_floor_ > 0.0, + "Got negative pressure. Consider enabling first-order flux " + "correction or setting a reasonble pressure or temperature floor."); // Pressure floor (if present) takes precedence over temperature floor if ((pressure_floor_ > 0.0) && (w_p < pressure_floor_)) { diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 8e261b95..0daa2955 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -395,8 +395,9 @@ void TabularCooling::SubcyclingFixedIntSrcTerm(MeshData *md, const Real dt if (!dedt_valid) { if (sub_dt == min_sub_dt) { - // Cooling is so fast that even the minimum subcycle dt would lead to negative internal - // energy -- so just cool to the floor of the cooling table + // Cooling is so fast that even the minimum subcycle dt would lead to + // negative internal energy -- so just cool to the floor of the cooling + // table sub_dt = (dt - sub_t); internal_e_next_h = internal_e_floor; reattempt_sub = false; From f934f93fe38b7d7f5492c59a393c9ef4d1a25ad8 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Wed, 29 Mar 2023 12:17:20 -0500 Subject: [PATCH 65/95] Fixed bug in AGN power history output --- src/pgen/cluster/agn_feedback.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index f7314627..d0e0840f 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -94,7 +94,8 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, [this](MeshData *md) { auto pmb = md->GetBlockData(0)->GetBlockPointer(); auto hydro_pkg = pmb->packages.Get("Hydro"); - return this->GetFeedbackPower(hydro_pkg.get()); + const auto &agn_feedback = hydro_pkg->Param("agn_feedback"); + return agn_feedback.GetFeedbackPower(hydro_pkg.get()); }, "agn_feedback_power")); } From 2bbdd405f07a8971f2f74537da2f72b8b0399b91 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Wed, 29 Mar 2023 12:18:08 -0500 Subject: [PATCH 66/95] Fixed non-testing of hydro AGN feedback (still broken) --- src/pgen/cluster/agn_feedback.cpp | 4 +++- .../cluster_hydro_agn_feedback.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index d0e0840f..061d1558 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -123,7 +123,9 @@ parthenon::Real AGNFeedback::GetFeedbackMassRate(StateDescriptor *hydro_pkg) con // Return a mass_rate equal to the accretion_rate minus energy-mass conversion // to feedback energy. We could divert mass to increase the SMBH/leave out // from mass injection - const Real mass_rate = accretion_rate * (1 - efficiency_); + // + // Also add fixed_power/(efficiency_*c**2) when fixed_power is enabled + const Real mass_rate = accretion_rate * (1 - efficiency_) + fixed_power_/(efficiency_*pow(units.speed_of_light(),2)); return mass_rate; } diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 69171f8c..75dcf36a 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -119,10 +119,10 @@ def __init__(self): # Feedback parameters self.fixed_power = unyt.unyt_quantity(1e44, "erg/s") - self.agn_thermal_radius = unyt.unyt_quantity(0.5, "kpc") + self.agn_thermal_radius = unyt.unyt_quantity(100, "kpc") self.efficiency = 1.0e-3 - self.agn_jet_radius = unyt.unyt_quantity(0.25, "kpc") - self.agn_jet_height = unyt.unyt_quantity(1, "kpc") + self.agn_jet_radius = unyt.unyt_quantity(50, "kpc") + self.agn_jet_height = unyt.unyt_quantity(150, "kpc") self.norm_tol = 1e-3 From c1373d6faf97d61acca1208d6cc9b380d6a8f38c Mon Sep 17 00:00:00 2001 From: par-hermes Date: Thu, 30 Mar 2023 00:46:33 +0000 Subject: [PATCH 67/95] cpp-py-formatter --- src/pgen/cluster/agn_feedback.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 061d1558..ecaf8948 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -125,7 +125,8 @@ parthenon::Real AGNFeedback::GetFeedbackMassRate(StateDescriptor *hydro_pkg) con // from mass injection // // Also add fixed_power/(efficiency_*c**2) when fixed_power is enabled - const Real mass_rate = accretion_rate * (1 - efficiency_) + fixed_power_/(efficiency_*pow(units.speed_of_light(),2)); + const Real mass_rate = accretion_rate * (1 - efficiency_) + + fixed_power_ / (efficiency_ * pow(units.speed_of_light(), 2)); return mass_rate; } From 1d0787921ff3f07087c3fbecd53f09d930638f4f Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Wed, 29 Mar 2023 21:45:46 -0500 Subject: [PATCH 68/95] Fixed some coordinate issues in hydro_agn_feedback test --- tst/regression/CMakeLists.txt | 2 +- .../cluster_hydro_agn_feedback.py | 27 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/tst/regression/CMakeLists.txt b/tst/regression/CMakeLists.txt index 7c572421..6db83662 100644 --- a/tst/regression/CMakeLists.txt +++ b/tst/regression/CMakeLists.txt @@ -53,7 +53,7 @@ setup_test_serial("cluster_magnetic_tower" "--driver ${PROJECT_BINARY_DIR}/bin/a --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/magnetic_tower.in --num_steps 4" "convergence") setup_test_serial("cluster_hydro_agn_feedback" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ - --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/hydro_agn_feedback.in --num_steps 6" "convergence") + --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/hydro_agn_feedback.in --num_steps 5" "convergence") setup_test_serial("cluster_agn_triggering" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/agn_triggering.in --num_steps 3" "convergence") diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 75dcf36a..7aa5b0d8 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -37,7 +37,7 @@ def __init__(self, theta, phi): # Axis of the jet self.jet_n = np.array( ( - np.cos(self.theta) * np.sin(self.phi), + np.sin(self.theta) * np.cos(self.phi), np.sin(self.theta) * np.sin(self.phi), np.cos(self.phi), ) @@ -111,10 +111,10 @@ def __init__(self): ) + self.uniform_gas_pres / (self.adiabatic_index - 1.0) # The precessing jet - self.jet_phi = 0.2 - self.jet_theta_dot = 0 - self.jet_theta0 = 1 - self.precessed_jet_coords = PrecessedJetCoords(self.jet_theta0, self.jet_phi) + self.jet_phi0 = 1.2 + self.jet_phi_dot = 0 + self.jet_theta = 0.4 + self.precessed_jet_coords = PrecessedJetCoords(self.jet_theta, self.jet_phi0) self.zjet_coords = ZJetCoords() # Feedback parameters @@ -126,12 +126,14 @@ def __init__(self): self.norm_tol = 1e-3 - self.steps = 6 + self.steps = 5 self.step_params_list = list( itertools.product( ("thermal_only", "kinetic_only", "combined"), (True, False) - ) - ) + ) ) + # Remove ("thermal_only",True) since it is redudant, jet precession is + # irrelevant with only thermal feedback + self.step_params_list.remove(("thermal_only",True)) def Prepare(self, parameters, step): """ @@ -187,9 +189,9 @@ def Prepare(self, parameters, step): f"problem/cluster/uniform_gas/uy={self.uniform_gas_uy.in_units('code_length*code_time**-1').v}", f"problem/cluster/uniform_gas/uz={self.uniform_gas_uz.in_units('code_length*code_time**-1').v}", f"problem/cluster/uniform_gas/pres={self.uniform_gas_pres.in_units('code_mass*code_length**-1*code_time**-2').v}", - f"problem/cluster/precessing_jet/jet_phi={self.jet_phi if precessed_jet else 0}", - f"problem/cluster/precessing_jet/jet_theta_dot={self.jet_theta_dot if precessed_jet else 0}", - f"problem/cluster/precessing_jet/jet_theta0={self.jet_theta0 if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_phi0={self.jet_phi0 if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_phi_dot={self.jet_phi_dot if precessed_jet else 0}", + f"problem/cluster/precessing_jet/jet_theta={self.jet_theta if precessed_jet else 0}", f"problem/cluster/agn_feedback/fixed_power={self.fixed_power.in_units('code_mass*code_length**2/code_time**3').v}", f"problem/cluster/agn_feedback/efficiency={self.efficiency}", f"problem/cluster/agn_feedback/thermal_fraction={agn_thermal_fraction}", @@ -447,8 +449,9 @@ def zero_corrected_linf_err(gold, test): np.abs((gold[gold == 0] - test[gold == 0])), initial=0 ) - return np.max((non_zero_linf, zero_linf)) + return np.max((non_zero_linf, zero_linf)) + # Use a very loose tolerance, linf relative error initial_analytic_status, final_analytic_status = [ compare_analytic.compare_analytic( From 40cecc021830944fba4a27090b6370fc7b7e1f1b Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 30 Mar 2023 13:45:06 -0500 Subject: [PATCH 69/95] Fixed bugs in jet coords, hydro AGN feedback --- inputs/cluster/hydro_agn_feedback.in | 6 +-- src/pgen/cluster/agn_feedback.cpp | 2 +- src/pgen/cluster/jet_coords.hpp | 21 +++------ .../cluster_hydro_agn_feedback.py | 47 +++++++++++-------- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/inputs/cluster/hydro_agn_feedback.in b/inputs/cluster/hydro_agn_feedback.in index b144f32a..1d2c6385 100644 --- a/inputs/cluster/hydro_agn_feedback.in +++ b/inputs/cluster/hydro_agn_feedback.in @@ -95,9 +95,9 @@ pres = 1.5454368403867562 #Define a precessing jet -jet_phi = 0.2 -jet_theta_dot = 1 -jet_theta0 = 2 +jet_phi0 = 1.2 +jet_phi_dot = 1 +jet_theta = 0.4 fixed_power = 1.65998282e-04 diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index ecaf8948..e64d69c5 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -209,7 +209,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Velocity of added gas const Real kinetic_velocity = - std::sqrt(2. * kinetic_fraction_ * power / (kinetic_fraction_ * mass_rate)); + std::sqrt(2. * kinetic_fraction_ * power / (kinetic_mass_fraction_ * mass_rate)); // Amount of momentum density ( density * velocity) to dump in each cell const Real kinetic_momentum = kinetic_density * kinetic_velocity; diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp index 87933ce0..2caeedce 100644 --- a/src/pgen/cluster/jet_coords.hpp +++ b/src/pgen/cluster/jet_coords.hpp @@ -44,14 +44,9 @@ class JetCoords { parthenon::Real &h_jet) const __attribute__((always_inline)) { // Position in jet-cartesian coordinates - const parthenon::Real x_jet = x_sim * cos_phi_jet_axis_ * cos_theta_jet_axis_ + - y_sim * sin_phi_jet_axis_ - - z_sim * sin_theta_jet_axis_ * cos_phi_jet_axis_; - const parthenon::Real y_jet = -x_sim * sin_phi_jet_axis_ * cos_theta_jet_axis_ + - y_sim * cos_phi_jet_axis_ + - z_sim * sin_phi_jet_axis_ * sin_theta_jet_axis_; - const parthenon::Real z_jet = - x_sim * sin_theta_jet_axis_ + z_sim * cos_theta_jet_axis_; + const parthenon::Real x_jet = x_sim*cos_phi_jet_axis_*cos_theta_jet_axis_ + y_sim*sin_phi_jet_axis_*cos_theta_jet_axis_ - z_sim*sin_theta_jet_axis_; + const parthenon::Real y_jet = -x_sim*sin_phi_jet_axis_ + y_sim*cos_phi_jet_axis_; + const parthenon::Real z_jet = x_sim*sin_theta_jet_axis_*cos_phi_jet_axis_ + y_sim*sin_phi_jet_axis_*sin_theta_jet_axis_ + z_sim*cos_theta_jet_axis_; // Position in jet-cylindrical coordinates r_jet = sqrt(pow(fabs(x_jet), 2) + pow(fabs(y_jet), 2)); @@ -72,13 +67,9 @@ class JetCoords { const parthenon::Real v_z_jet = v_h_jet; // Multiply v_jet by the DCM matrix to take Jet cartesian to Simulation Cartesian - v_x_sim = v_x_jet * cos_phi_jet_axis_ * cos_theta_jet_axis_ - - v_y_jet * sin_phi_jet_axis_ * cos_theta_jet_axis_ + - v_z_jet * sin_theta_jet_axis_; - v_y_sim = v_x_jet * sin_phi_jet_axis_ + v_y_jet * cos_phi_jet_axis_; - v_z_sim = -v_x_jet * sin_theta_jet_axis_ * cos_phi_jet_axis_ + - v_y_jet * sin_phi_jet_axis_ * sin_theta_jet_axis_ + - v_z_jet * cos_theta_jet_axis_; + v_x_sim = v_x_jet*cos_phi_jet_axis_*cos_theta_jet_axis_ - v_y_jet*sin_phi_jet_axis_ + v_z_jet*sin_theta_jet_axis_*cos_phi_jet_axis_; + v_y_sim = v_x_jet*sin_phi_jet_axis_*cos_theta_jet_axis_ + v_y_jet*cos_phi_jet_axis_ + v_z_jet*sin_phi_jet_axis_*sin_theta_jet_axis_; + v_z_sim = -v_x_jet*sin_theta_jet_axis_ + v_z_jet*cos_theta_jet_axis_; } }; /************************************************************ diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 7aa5b0d8..2d0ebac5 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -39,7 +39,7 @@ def __init__(self, theta, phi): ( np.sin(self.theta) * np.cos(self.phi), np.sin(self.theta) * np.sin(self.phi), - np.cos(self.phi), + np.cos(self.theta), ) ) @@ -62,7 +62,7 @@ def cart_to_rho_h(self, pos_cart): Convert from cartesian coordinates to jet coordinates """ - pos_rho = np.linalg.norm(pos_cart[:2]) + pos_rho = np.linalg.norm(pos_cart[:2],axis=0) pos_h = pos_cart[2] return pos_rho, pos_h @@ -270,6 +270,7 @@ def kinetic_feedback(Z, Y, X, time): R = unyt.unyt_array(R, "code_length") H = unyt.unyt_array(H, "code_length") + #sign_jet = np.piecewise(H, [H <= 0, H > 0], [1, -1]).v #Backwards jet REMOVEME sign_jet = np.piecewise(H, [H <= 0, H > 0], [-1, 1]).v inside_jet = ( np.piecewise( @@ -288,10 +289,9 @@ def kinetic_feedback(Z, Y, X, time): ) ).v - drho = inside_jet * agn_kinetic_fraction * time * jet_density + drho = inside_jet * time * jet_density dMx = ( inside_jet - * agn_kinetic_fraction * time * sign_jet * jet_density @@ -300,7 +300,6 @@ def kinetic_feedback(Z, Y, X, time): ) dMy = ( inside_jet - * agn_kinetic_fraction * time * sign_jet * jet_density @@ -309,7 +308,6 @@ def kinetic_feedback(Z, Y, X, time): ) dMz = ( inside_jet - * agn_kinetic_fraction * time * sign_jet * jet_density @@ -318,7 +316,6 @@ def kinetic_feedback(Z, Y, X, time): ) dE = ( inside_jet - * agn_kinetic_fraction * time * 0.5 * jet_density @@ -367,8 +364,8 @@ def agn_feedback(Z, Y, X, dt): drho = drho_k + drho_t dMx = dMx_k - dMy = dMx_k - dMz = dMx_k + dMy = dMy_k + dMz = dMz_k dE = dE_k + dE_t return drho, dMx, dMy, dMz, dE @@ -419,12 +416,12 @@ def agn_feedback(Z, Y, X, dt): .in_units("code_mass*code_length**-2*code_time**-1") .v, "MomentumDensity2": lambda Z, Y, X, time: ( - self.uniform_gas_Mx + agn_feedback(Z, Y, X, time)[2] + self.uniform_gas_My + agn_feedback(Z, Y, X, time)[2] ) .in_units("code_mass*code_length**-2*code_time**-1") .v, "MomentumDensity3": lambda Z, Y, X, time: ( - self.uniform_gas_Mx + agn_feedback(Z, Y, X, time)[3] + self.uniform_gas_Mz + agn_feedback(Z, Y, X, time)[3] ) .in_units("code_mass*code_length**-2*code_time**-1") .v, @@ -453,17 +450,29 @@ def zero_corrected_linf_err(gold, test): return np.max((non_zero_linf, zero_linf)) # Use a very loose tolerance, linf relative error - initial_analytic_status, final_analytic_status = [ - compare_analytic.compare_analytic( - phdf_file, - analytic_components, + #initial_analytic_status, final_analytic_status = [ + # compare_analytic.compare_analytic( + # phdf_file, + # analytic_components, + # err_func=zero_corrected_linf_err, + # tol=1e-3, + # ) + # for analytic_components, phdf_file in zip( + # (initial_analytic_components, final_analytic_components), phdf_files + # ) + #] + initial_analytic_status = compare_analytic.compare_analytic( + phdf_files[0], + initial_analytic_components, err_func=zero_corrected_linf_err, - tol=1e-3, + tol=1e-6, ) - for analytic_components, phdf_file in zip( - (initial_analytic_components, final_analytic_components), phdf_files + final_analytic_status = compare_analytic.compare_analytic( + phdf_files[1], + final_analytic_components, + err_func=zero_corrected_linf_err, + tol=1e-3, ) - ] print(" Initial analytic status", initial_analytic_status) print(" Final analytic status", final_analytic_status) From d2a287319822fdfc1972c0a4fe33c6163277f7a3 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Thu, 30 Mar 2023 14:11:15 -0500 Subject: [PATCH 70/95] Fixed coordinates in magnetic tower test --- .../cluster_magnetic_tower.py | 80 ++++--------------- 1 file changed, 16 insertions(+), 64 deletions(-) diff --git a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py index dbba642c..f5319c28 100644 --- a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py +++ b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py @@ -44,82 +44,34 @@ def cart_to_jet_coords(self, pos_sim): y_sim = pos_sim[1] z_sim = pos_sim[2] - x_jet = ( - x_sim * np.cos(self.phi_jet) * np.cos(self.theta_jet) - + y_sim * np.sin(self.phi_jet) - - z_sim * np.sin(self.theta_jet) * np.cos(self.phi_jet) - ) - y_jet = ( - -x_sim * np.sin(self.phi_jet) * np.cos(self.theta_jet) - + y_sim * np.cos(self.phi_jet) - + z_sim * np.sin(self.phi_jet) * np.sin(self.theta_jet) - ) - z_jet = x_sim * np.sin(self.theta_jet) + z_sim * np.cos(self.theta_jet) + x_jet = x_sim*np.cos(self.phi_jet)*np.cos(self.theta_jet) + \ + y_sim*np.sin(self.phi_jet)*np.cos(self.theta_jet) - \ + z_sim*np.sin(self.theta_jet) + y_jet = -x_sim*np.sin(self.phi_jet) + y_sim*np.cos(self.phi_jet) + z_jet = x_sim*np.sin(self.theta_jet)*np.cos(self.phi_jet) + y_sim*np.sin(self.phi_jet)*np.sin(self.theta_jet) + z_sim*np.cos(self.theta_jet); r_jet = np.sqrt(x_jet**2 + y_jet**2) theta_jet = np.arctan2(y_jet, x_jet) h_jet = z_jet + return (r_jet, theta_jet, h_jet) def jet_to_cart_vec(self, pos_sim, vec_jet): + """ + Convert vector in jet cylindrical coordinates to simulation cartesian coordinates + """ r_pos, theta_pos, h_pos = self.cart_to_jet_coords(pos_sim) - # Convert jet-cylindrical vec_jet into jet-cartesian - R = np.array( - ( - (np.cos(theta_pos), -np.sin(theta_pos), np.zeros_like(theta_pos)), - (np.sin(theta_pos), np.cos(theta_pos), np.zeros_like(theta_pos)), - ( - np.zeros_like(theta_pos), - np.zeros_like(theta_pos), - np.ones_like(theta_pos), - ), - ) - ).reshape((3, 3, *(theta_pos.shape))) - # vec_jet_cart = np.einsum("ij,jxyz",R,vec_jet) - vec_jet_cart = unyt.unyt_array( - ( - R[0, 0] * vec_jet[0] + R[0, 1] * vec_jet[1] + R[0, 2] * vec_jet[2], - R[1, 0] * vec_jet[0] + R[1, 1] * vec_jet[1] + R[1, 2] * vec_jet[2], - R[2, 0] * vec_jet[0] + R[2, 1] * vec_jet[1] + R[2, 2] * vec_jet[2], - ), - vec_jet.units, - ) + v_x_jet = vec_jet[0]*np.cos(theta_pos) - vec_jet[1]*np.sin(theta_pos) + v_y_jet = vec_jet[0]*np.sin(theta_pos) + vec_jet[1]*np.cos(theta_pos) + v_z_jet = vec_jet[2] - R = np.array( - ( - ( - np.cos(self.phi_jet) * np.cos(self.theta_jet), - -np.sin(self.phi_jet) * np.cos(self.theta_jet), - np.sin(self.theta_jet), - ), - (np.sin(self.phi_jet), np.cos(self.phi_jet), 0), - ( - -np.sin(self.theta_jet) * np.cos(self.phi_jet), - np.sin(self.phi_jet) * np.sin(self.theta_jet), - np.cos(self.theta_jet), - ), - ) - ) - - # vec_cart = np.matmul(R,vec_jet_cart) - vec_cart = unyt.unyt_array( - ( - R[0, 0] * vec_jet_cart[0] - + R[0, 1] * vec_jet_cart[1] - + R[0, 2] * vec_jet_cart[2], - R[1, 0] * vec_jet_cart[0] - + R[1, 1] * vec_jet_cart[1] - + R[1, 2] * vec_jet_cart[2], - R[2, 0] * vec_jet_cart[0] - + R[2, 1] * vec_jet_cart[1] - + R[2, 2] * vec_jet_cart[2], - ), - vec_jet_cart.units, - ) + v_x_sim = v_x_jet*np.cos(self.phi_jet)*np.cos(self.theta_jet) - v_y_jet*np.sin(self.phi_jet) + v_z_jet*np.sin(self.theta_jet)*np.cos(self.phi_jet); + v_y_sim = v_x_jet*np.sin(self.phi_jet)*np.cos(self.theta_jet) + v_y_jet*np.cos(self.phi_jet) + v_z_jet*np.sin(self.phi_jet)*np.sin(self.theta_jet); + v_z_sim = -v_x_jet*np.sin(self.theta_jet) + v_z_jet*np.cos(self.theta_jet); - return vec_cart + return (v_x_sim,v_y_sim,v_z_sim) class ZJetCoords: From 311297ac7f430cf37cca0a00c12f718a3c91fbeb Mon Sep 17 00:00:00 2001 From: par-hermes Date: Thu, 30 Mar 2023 19:22:39 +0000 Subject: [PATCH 71/95] cpp-py-formatter --- src/pgen/cluster/jet_coords.hpp | 20 ++++++--- .../cluster_hydro_agn_feedback.py | 44 ++++++++----------- .../cluster_magnetic_tower.py | 38 +++++++++++----- 3 files changed, 59 insertions(+), 43 deletions(-) diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp index 2caeedce..a2a697ff 100644 --- a/src/pgen/cluster/jet_coords.hpp +++ b/src/pgen/cluster/jet_coords.hpp @@ -44,9 +44,13 @@ class JetCoords { parthenon::Real &h_jet) const __attribute__((always_inline)) { // Position in jet-cartesian coordinates - const parthenon::Real x_jet = x_sim*cos_phi_jet_axis_*cos_theta_jet_axis_ + y_sim*sin_phi_jet_axis_*cos_theta_jet_axis_ - z_sim*sin_theta_jet_axis_; - const parthenon::Real y_jet = -x_sim*sin_phi_jet_axis_ + y_sim*cos_phi_jet_axis_; - const parthenon::Real z_jet = x_sim*sin_theta_jet_axis_*cos_phi_jet_axis_ + y_sim*sin_phi_jet_axis_*sin_theta_jet_axis_ + z_sim*cos_theta_jet_axis_; + const parthenon::Real x_jet = x_sim * cos_phi_jet_axis_ * cos_theta_jet_axis_ + + y_sim * sin_phi_jet_axis_ * cos_theta_jet_axis_ - + z_sim * sin_theta_jet_axis_; + const parthenon::Real y_jet = -x_sim * sin_phi_jet_axis_ + y_sim * cos_phi_jet_axis_; + const parthenon::Real z_jet = x_sim * sin_theta_jet_axis_ * cos_phi_jet_axis_ + + y_sim * sin_phi_jet_axis_ * sin_theta_jet_axis_ + + z_sim * cos_theta_jet_axis_; // Position in jet-cylindrical coordinates r_jet = sqrt(pow(fabs(x_jet), 2) + pow(fabs(y_jet), 2)); @@ -67,9 +71,13 @@ class JetCoords { const parthenon::Real v_z_jet = v_h_jet; // Multiply v_jet by the DCM matrix to take Jet cartesian to Simulation Cartesian - v_x_sim = v_x_jet*cos_phi_jet_axis_*cos_theta_jet_axis_ - v_y_jet*sin_phi_jet_axis_ + v_z_jet*sin_theta_jet_axis_*cos_phi_jet_axis_; - v_y_sim = v_x_jet*sin_phi_jet_axis_*cos_theta_jet_axis_ + v_y_jet*cos_phi_jet_axis_ + v_z_jet*sin_phi_jet_axis_*sin_theta_jet_axis_; - v_z_sim = -v_x_jet*sin_theta_jet_axis_ + v_z_jet*cos_theta_jet_axis_; + v_x_sim = v_x_jet * cos_phi_jet_axis_ * cos_theta_jet_axis_ - + v_y_jet * sin_phi_jet_axis_ + + v_z_jet * sin_theta_jet_axis_ * cos_phi_jet_axis_; + v_y_sim = v_x_jet * sin_phi_jet_axis_ * cos_theta_jet_axis_ + + v_y_jet * cos_phi_jet_axis_ + + v_z_jet * sin_phi_jet_axis_ * sin_theta_jet_axis_; + v_z_sim = -v_x_jet * sin_theta_jet_axis_ + v_z_jet * cos_theta_jet_axis_; } }; /************************************************************ diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 2d0ebac5..a1c1dfac 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -62,7 +62,7 @@ def cart_to_rho_h(self, pos_cart): Convert from cartesian coordinates to jet coordinates """ - pos_rho = np.linalg.norm(pos_cart[:2],axis=0) + pos_rho = np.linalg.norm(pos_cart[:2], axis=0) pos_h = pos_cart[2] return pos_rho, pos_h @@ -130,10 +130,11 @@ def __init__(self): self.step_params_list = list( itertools.product( ("thermal_only", "kinetic_only", "combined"), (True, False) - ) ) + ) + ) # Remove ("thermal_only",True) since it is redudant, jet precession is # irrelevant with only thermal feedback - self.step_params_list.remove(("thermal_only",True)) + self.step_params_list.remove(("thermal_only", True)) def Prepare(self, parameters, step): """ @@ -270,7 +271,7 @@ def kinetic_feedback(Z, Y, X, time): R = unyt.unyt_array(R, "code_length") H = unyt.unyt_array(H, "code_length") - #sign_jet = np.piecewise(H, [H <= 0, H > 0], [1, -1]).v #Backwards jet REMOVEME + # sign_jet = np.piecewise(H, [H <= 0, H > 0], [1, -1]).v #Backwards jet REMOVEME sign_jet = np.piecewise(H, [H <= 0, H > 0], [-1, 1]).v inside_jet = ( np.piecewise( @@ -314,13 +315,7 @@ def kinetic_feedback(Z, Y, X, time): * jet_velocity * jet_coords.jet_n[2] ) - dE = ( - inside_jet - * time - * 0.5 - * jet_density - * jet_velocity**2 - ) + dE = inside_jet * time * 0.5 * jet_density * jet_velocity**2 return drho, dMx, dMy, dMz, dE @@ -446,11 +441,10 @@ def zero_corrected_linf_err(gold, test): np.abs((gold[gold == 0] - test[gold == 0])), initial=0 ) - return np.max((non_zero_linf, zero_linf)) - + # Use a very loose tolerance, linf relative error - #initial_analytic_status, final_analytic_status = [ + # initial_analytic_status, final_analytic_status = [ # compare_analytic.compare_analytic( # phdf_file, # analytic_components, @@ -460,19 +454,19 @@ def zero_corrected_linf_err(gold, test): # for analytic_components, phdf_file in zip( # (initial_analytic_components, final_analytic_components), phdf_files # ) - #] + # ] initial_analytic_status = compare_analytic.compare_analytic( - phdf_files[0], - initial_analytic_components, - err_func=zero_corrected_linf_err, - tol=1e-6, - ) + phdf_files[0], + initial_analytic_components, + err_func=zero_corrected_linf_err, + tol=1e-6, + ) final_analytic_status = compare_analytic.compare_analytic( - phdf_files[1], - final_analytic_components, - err_func=zero_corrected_linf_err, - tol=1e-3, - ) + phdf_files[1], + final_analytic_components, + err_func=zero_corrected_linf_err, + tol=1e-3, + ) print(" Initial analytic status", initial_analytic_status) print(" Final analytic status", final_analytic_status) diff --git a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py index f5319c28..2aad2e77 100644 --- a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py +++ b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py @@ -44,11 +44,17 @@ def cart_to_jet_coords(self, pos_sim): y_sim = pos_sim[1] z_sim = pos_sim[2] - x_jet = x_sim*np.cos(self.phi_jet)*np.cos(self.theta_jet) + \ - y_sim*np.sin(self.phi_jet)*np.cos(self.theta_jet) - \ - z_sim*np.sin(self.theta_jet) - y_jet = -x_sim*np.sin(self.phi_jet) + y_sim*np.cos(self.phi_jet) - z_jet = x_sim*np.sin(self.theta_jet)*np.cos(self.phi_jet) + y_sim*np.sin(self.phi_jet)*np.sin(self.theta_jet) + z_sim*np.cos(self.theta_jet); + x_jet = ( + x_sim * np.cos(self.phi_jet) * np.cos(self.theta_jet) + + y_sim * np.sin(self.phi_jet) * np.cos(self.theta_jet) + - z_sim * np.sin(self.theta_jet) + ) + y_jet = -x_sim * np.sin(self.phi_jet) + y_sim * np.cos(self.phi_jet) + z_jet = ( + x_sim * np.sin(self.theta_jet) * np.cos(self.phi_jet) + + y_sim * np.sin(self.phi_jet) * np.sin(self.theta_jet) + + z_sim * np.cos(self.theta_jet) + ) r_jet = np.sqrt(x_jet**2 + y_jet**2) theta_jet = np.arctan2(y_jet, x_jet) @@ -63,15 +69,23 @@ def jet_to_cart_vec(self, pos_sim, vec_jet): r_pos, theta_pos, h_pos = self.cart_to_jet_coords(pos_sim) - v_x_jet = vec_jet[0]*np.cos(theta_pos) - vec_jet[1]*np.sin(theta_pos) - v_y_jet = vec_jet[0]*np.sin(theta_pos) + vec_jet[1]*np.cos(theta_pos) - v_z_jet = vec_jet[2] + v_x_jet = vec_jet[0] * np.cos(theta_pos) - vec_jet[1] * np.sin(theta_pos) + v_y_jet = vec_jet[0] * np.sin(theta_pos) + vec_jet[1] * np.cos(theta_pos) + v_z_jet = vec_jet[2] - v_x_sim = v_x_jet*np.cos(self.phi_jet)*np.cos(self.theta_jet) - v_y_jet*np.sin(self.phi_jet) + v_z_jet*np.sin(self.theta_jet)*np.cos(self.phi_jet); - v_y_sim = v_x_jet*np.sin(self.phi_jet)*np.cos(self.theta_jet) + v_y_jet*np.cos(self.phi_jet) + v_z_jet*np.sin(self.phi_jet)*np.sin(self.theta_jet); - v_z_sim = -v_x_jet*np.sin(self.theta_jet) + v_z_jet*np.cos(self.theta_jet); + v_x_sim = ( + v_x_jet * np.cos(self.phi_jet) * np.cos(self.theta_jet) + - v_y_jet * np.sin(self.phi_jet) + + v_z_jet * np.sin(self.theta_jet) * np.cos(self.phi_jet) + ) + v_y_sim = ( + v_x_jet * np.sin(self.phi_jet) * np.cos(self.theta_jet) + + v_y_jet * np.cos(self.phi_jet) + + v_z_jet * np.sin(self.phi_jet) * np.sin(self.theta_jet) + ) + v_z_sim = -v_x_jet * np.sin(self.theta_jet) + v_z_jet * np.cos(self.theta_jet) - return (v_x_sim,v_y_sim,v_z_sim) + return (v_x_sim, v_y_sim, v_z_sim) class ZJetCoords: From 6ab7a6511ffed3d1c8cb2c97d1325c9458ce0a9b Mon Sep 17 00:00:00 2001 From: Philipp Grete Date: Mon, 10 Apr 2023 07:05:22 -0400 Subject: [PATCH 72/95] Cosmetic changes and enabling MPI CI for cluster tests --- inputs/cluster/agn_triggering.in | 1 - inputs/cluster/cluster.in | 1 - inputs/cluster/cooling.in | 1 - inputs/cluster/generate_cluster_input.ipynb | 13 ++++++------- inputs/cluster/hse.in | 1 - inputs/cluster/hydro_agn_feedback.in | 1 - inputs/cluster/magnetic_tower.in | 1 - inputs/cluster/my_cluster.input | 9 ++++----- src/hydro/srcterms/tabular_cooling.cpp | 2 +- src/pgen/cluster/agn_triggering.cpp | 6 ++---- src/pgen/cluster/magnetic_tower.cpp | 2 +- src/pgen/cluster/snia_feedback.cpp | 5 ++--- tst/regression/CMakeLists.txt | 6 +++--- 13 files changed, 19 insertions(+), 30 deletions(-) diff --git a/inputs/cluster/agn_triggering.in b/inputs/cluster/agn_triggering.in index 9da96872..0e0ac815 100644 --- a/inputs/cluster/agn_triggering.in +++ b/inputs/cluster/agn_triggering.in @@ -20,7 +20,6 @@ cfl_number = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number nlim = -1 # cycle limit tlim = 0.1 # time limit integrator = vl2 # time integration algorithm -perf_cycle_offset = 10 # interval for stdout summary info diff --git a/inputs/cluster/cluster.in b/inputs/cluster/cluster.in index 501ea64f..af98b0a1 100644 --- a/inputs/cluster/cluster.in +++ b/inputs/cluster/cluster.in @@ -20,7 +20,6 @@ cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number nlim = -1 # cycle limit tlim = 1e-1 # time limit (100 Myr) integrator = vl2 # time integration algorithm -perf_cycle_offset = 10 # interval for stdout summary info diff --git a/inputs/cluster/cooling.in b/inputs/cluster/cooling.in index eeb7243f..6ef75535 100644 --- a/inputs/cluster/cooling.in +++ b/inputs/cluster/cooling.in @@ -20,7 +20,6 @@ cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number nlim = -1 # cycle limit tlim = 1.0 # time limit integrator = vl2 # time integration algorithm -perf_cycle_offset = 10 # interval for stdout summary info diff --git a/inputs/cluster/generate_cluster_input.ipynb b/inputs/cluster/generate_cluster_input.ipynb index b23f9d0a..4b4ae51e 100644 --- a/inputs/cluster/generate_cluster_input.ipynb +++ b/inputs/cluster/generate_cluster_input.ipynb @@ -123,9 +123,8 @@ "lambda_units_cgs = {unyt.unyt_quantity(1,\"erg*cm**3/s\").v}\n", "\n", "integrator = townsend\n", - "cfl = 0.1 # Restricts hydro step based on minimum cooling time, disabled for \"integrator=townsend\"\n", + "cfl = 0.1 # Restricts hydro step based on fraction of minimum cooling time\n", "min_timestep = {unyt.unyt_quantity(1,\"Gyr\").in_units(\"code_time\").v}\n", - "max_iter = 100\n", "d_e_tol = 1e-8\n", "d_log_temp_tol = 1e-8\n", "\n", @@ -244,11 +243,11 @@ "output_text = f\"\"\"\n", "\n", "file_type = hst # History data dump\n", - "dt = {unyt.unyt_quantity(0.1,\"Myr\")} # time increment between outputs\n", + "dt = {unyt.unyt_quantity(0.1,\"Myr\").in_units(\"code_time\").v} # time increment between outputs\n", "\n", "\n", "file_type = rst # restart data dump\n", - "dt = {unyt.unyt_quantity(1.0,\"Myr\")} # Time increment between outputs\n", + "dt = {unyt.unyt_quantity(1.0,\"Myr\").in_units(\"code_time\").v} # Time increment between outputs\n", "id = restart\n", "\n", "# hdf5_compression_level = 0\n", @@ -274,7 +273,7 @@ "time_text=f\"\"\"\n", "\n", "cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number\n", - "tlim = {unyt.unyt_quantity(0.1,\"Myr\")} # time limit\n", + "tlim = {unyt.unyt_quantity(0.1,\"Myr\").in_units(\"code_time\").v} # time limit\n", "integrator = vl2 # time integration algorithm\n", "\"\"\"" ] @@ -443,7 +442,7 @@ " print(f\"Total meshblocks: {info['total_n_mb']}\" )\n", " print(f\"Total memory needed: {info['total_used_memory']/1e9} GB\")\n", " print(f\"Total memory per output: {info['total_output_memory']/1e9} GB\")\n", - " print(f\"A100s needed: {info['total_used_memory']/40e9} \")\n", + " print(f\"A100s (40G) needed: {info['total_used_memory']/40e9} \")\n", " \n", " print()\n", "\n", @@ -565,7 +564,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.10.10" } }, "nbformat": 4, diff --git a/inputs/cluster/hse.in b/inputs/cluster/hse.in index 266373b2..b3862cf0 100644 --- a/inputs/cluster/hse.in +++ b/inputs/cluster/hse.in @@ -20,7 +20,6 @@ cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number nlim = -1 # cycle limit tlim = 1e-3 # time limit integrator = vl2 # time integration algorithm -perf_cycle_offset = 10 # interval for stdout summary info diff --git a/inputs/cluster/hydro_agn_feedback.in b/inputs/cluster/hydro_agn_feedback.in index 1d2c6385..6dcd4010 100644 --- a/inputs/cluster/hydro_agn_feedback.in +++ b/inputs/cluster/hydro_agn_feedback.in @@ -20,7 +20,6 @@ cfl_number = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number nlim = -1 # cycle limit tlim = 0.01 # time limit integrator = vl2 # time integration algorithm -perf_cycle_offset = 10 # interval for stdout summary info diff --git a/inputs/cluster/magnetic_tower.in b/inputs/cluster/magnetic_tower.in index 7459a4b8..be5aa5e1 100644 --- a/inputs/cluster/magnetic_tower.in +++ b/inputs/cluster/magnetic_tower.in @@ -20,7 +20,6 @@ cfl_number = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number nlim = -1 # cycle limit tlim = 0.01 # time limit integrator = vl2 # time integration algorithm -perf_cycle_offset = 10 # interval for stdout summary info diff --git a/inputs/cluster/my_cluster.input b/inputs/cluster/my_cluster.input index 6a99841b..4a8b83ac 100644 --- a/inputs/cluster/my_cluster.input +++ b/inputs/cluster/my_cluster.input @@ -10,11 +10,11 @@ problem_id = cluster # problem ID: basename of output filenames file_type = hst # History data dump -dt = 0.1 Myr # time increment between outputs +dt = 0.0001 # time increment between outputs file_type = rst # restart data dump -dt = 1.0 Myr # Time increment between outputs +dt = 0.001 # Time increment between outputs id = restart # hdf5_compression_level = 0 @@ -24,7 +24,7 @@ use_final_label = false cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number -tlim = 0.1 Myr # time limit +tlim = 0.0001 # time limit integrator = vl2 # time integration algorithm @@ -96,9 +96,8 @@ log_lambda_col = 1 # Column to read lambda in cooling table lambda_units_cgs = 1 integrator = townsend -cfl = 0.1 # Restricts hydro step based on minimum cooling time, disabled for "integrator=townsend" +cfl = 0.1 # Restricts hydro step based on fraction of minimum cooling time min_timestep = 1.0 -max_iter = 100 d_e_tol = 1e-8 d_log_temp_tol = 1e-8 diff --git a/src/hydro/srcterms/tabular_cooling.cpp b/src/hydro/srcterms/tabular_cooling.cpp index 0daa2955..67a1442e 100644 --- a/src/hydro/srcterms/tabular_cooling.cpp +++ b/src/hydro/srcterms/tabular_cooling.cpp @@ -602,7 +602,7 @@ Real TabularCooling::EstimateTimeStep(MeshData *md) const { return std::numeric_limits::max(); } - if (cooling_time_cfl_ <= 0.0 || isnan(cooling_time_cfl_) || isinf(cooling_time_cfl_)) { + if (isnan(cooling_time_cfl_) || isinf(cooling_time_cfl_)) { return std::numeric_limits::infinity(); } diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 5d2fdc46..14441188 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -198,7 +198,6 @@ void AGNTriggering::ReduceBondiTriggeringQuantities( // Grab some necessary variables const auto &prim_pack = md->PackVariables(std::vector{"prim"}); - const auto &cons_pack = md->PackVariables(std::vector{"cons"}); IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::interior); IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); @@ -219,13 +218,12 @@ void AGNTriggering::ReduceBondiTriggeringQuantities( parthenon::par_reduce( parthenon::loop_pattern_mdrange_tag, "AGNTriggering::ReduceBondi", - parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, + parthenon::DevExecSpace(), 0, prim_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, ib.e, KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i, ReductionSumArray &team_triggering_reduction) { - auto &cons = cons_pack(b); auto &prim = prim_pack(b); - const auto &coords = cons_pack.GetCoords(b); + const auto &coords = prim_pack.GetCoords(b); const parthenon::Real r2 = pow(coords.Xc<1>(i), 2) + pow(coords.Xc<2>(j), 2) + pow(coords.Xc<3>(k), 2); if (r2 < accretion_radius2) { diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index 39d390e6..b7dc6f74 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -279,7 +279,7 @@ void MagneticTower::PowerSrcTerm(const parthenon::Real power, msg << "MagneticTowerModel::PowerSrcTerm No field rate is viable" << " linear_contrib: " << std::to_string(linear_contrib) << " quadratic_contrib: " << std::to_string(quadratic_contrib); - PARTHENON_FAIL(msg.str().c_str()); + PARTHENON_FAIL(msg); } const Real field_to_add = (-linear_contrib + sqrt(disc)) / (2 * quadratic_contrib); const Real mass_to_add = mass_rate * beta_dt; diff --git a/src/pgen/cluster/snia_feedback.cpp b/src/pgen/cluster/snia_feedback.cpp index fee74c1a..ed41657f 100644 --- a/src/pgen/cluster/snia_feedback.cpp +++ b/src/pgen/cluster/snia_feedback.cpp @@ -3,9 +3,8 @@ // Copyright (c) 2021-2023, Athena-Parthenon Collaboration. All rights reserved. // Licensed under the 3-clause BSD License, see LICENSE file for details //======================================================================================== -//! \file agn_feedback.cpp -// \brief Class for injecting AGN feedback via thermal dump, kinetic jet, and magnetic -// tower +//! \file snia_feedback.cpp +// \brief Class for injecting SNIA feedback following BCG density #include diff --git a/tst/regression/CMakeLists.txt b/tst/regression/CMakeLists.txt index 6db83662..31d2876f 100644 --- a/tst/regression/CMakeLists.txt +++ b/tst/regression/CMakeLists.txt @@ -49,11 +49,11 @@ setup_test_both("aniso_therm_cond_ring_multid" "--driver ${PROJECT_BINARY_DIR}/b setup_test_both("field_loop" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ --driver_input ${PROJECT_SOURCE_DIR}/inputs/field_loop.in --num_steps 12" "convergence") -setup_test_serial("cluster_magnetic_tower" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ +setup_test_both("cluster_magnetic_tower" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/magnetic_tower.in --num_steps 4" "convergence") -setup_test_serial("cluster_hydro_agn_feedback" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ +setup_test_both("cluster_hydro_agn_feedback" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/hydro_agn_feedback.in --num_steps 5" "convergence") -setup_test_serial("cluster_agn_triggering" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ +setup_test_both("cluster_agn_triggering" "--driver ${PROJECT_BINARY_DIR}/bin/athenaPK \ --driver_input ${PROJECT_SOURCE_DIR}/inputs/cluster/agn_triggering.in --num_steps 3" "convergence") From 69ed14613fd6ec9fc75b406fca92eb9c3df2c597 Mon Sep 17 00:00:00 2001 From: Philipp Grete Date: Mon, 10 Apr 2023 11:26:40 -0400 Subject: [PATCH 73/95] Dont cool below the lower end of the cooling table --- src/hydro/srcterms/tabular_cooling.hpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/hydro/srcterms/tabular_cooling.hpp b/src/hydro/srcterms/tabular_cooling.hpp index 1a1a5ef3..89529f20 100644 --- a/src/hydro/srcterms/tabular_cooling.hpp +++ b/src/hydro/srcterms/tabular_cooling.hpp @@ -155,12 +155,7 @@ class TabularCooling { const Real log_temp = log10(temp); Real log_lambda; if (log_temp < log_temp_start) { - // Below table - return 0 or first entry? - // log_lambda = log_lambdas(0); - // TODO(forrestglines):Currently, zero cooling is use for temperatures - // below the table. This behavior could be generalized via templates - // return 0; - log_lambda = log_lambdas(0); // HACK - to get it to cool to floor + return 0; } else if (log_temp > log_temp_final) { // Above table // Return de/dt From 105d48fb49f2d7cc99776d7ae30677c44c6a6cc8 Mon Sep 17 00:00:00 2001 From: Philipp Grete Date: Mon, 10 Apr 2023 11:46:06 -0400 Subject: [PATCH 74/95] Use Kokkos reductions --- src/pgen/cluster/agn_triggering.cpp | 41 +++++++++--------- src/pgen/cluster/magnetic_tower.cpp | 34 +++++++-------- src/reduction_utils.hpp | 65 ----------------------------- 3 files changed, 36 insertions(+), 104 deletions(-) delete mode 100644 src/reduction_utils.hpp diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 14441188..610bc4ac 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -21,7 +21,6 @@ #include "../../eos/adiabatic_glmmhd.hpp" #include "../../eos/adiabatic_hydro.hpp" #include "../../main.hpp" -#include "../../reduction_utils.hpp" #include "../../units.hpp" #include "agn_feedback.hpp" #include "agn_triggering.hpp" @@ -207,21 +206,20 @@ void AGNTriggering::ReduceBondiTriggeringQuantities( // Reduce Mass-weighted total density, velocity, and sound speed and total // mass (in that order). Will need to divide the three latter quantities by // total mass in order to get their mass-weighted averaged values - const unsigned int MASS_IDX = 0; - const unsigned int DENSITY_IDX = 1; - const unsigned int VELOCITY_IDX = 2; - const unsigned int CS_IDX = 3; - ReductionSumArray triggering_reduction; - Kokkos::Sum> reducer_sum(triggering_reduction); + Real total_mass_red, mass_weighted_density_red, mass_weighted_velocity_red, + mass_weighted_cs_red; const parthenon::Real gamma = gamma_; - parthenon::par_reduce( - parthenon::loop_pattern_mdrange_tag, "AGNTriggering::ReduceBondi", - parthenon::DevExecSpace(), 0, prim_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, - ib.e, + Kokkos::parallel_reduce( + "AGNTriggering::ReduceBondi", + Kokkos::MDRangePolicy>( + DevExecSpace(), {0, kb.s, jb.s, ib.s}, + {prim_pack.GetDim(5), kb.e + 1, jb.e + 1, ib.e + 1}, + {1, 1, 1, ib.e + 1 - ib.s}), KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i, - ReductionSumArray &team_triggering_reduction) { + Real <otal_mass_red, Real &lmass_weighted_density_red, + Real &lmass_weighted_velocity_red, Real &lmass_weighted_cs_red) { auto &prim = prim_pack(b); const auto &coords = prim_pack.GetCoords(b); const parthenon::Real r2 = @@ -236,18 +234,19 @@ void AGNTriggering::ReduceBondiTriggeringQuantities( const Real cell_mass_weighted_cs = cell_mass * sqrt(gamma * prim(IPR, k, j, i) / prim(IDN, k, j, i)); - team_triggering_reduction.data[MASS_IDX] += cell_mass; - team_triggering_reduction.data[DENSITY_IDX] += cell_mass_weighted_density; - team_triggering_reduction.data[VELOCITY_IDX] += cell_mass_weighted_velocity; - team_triggering_reduction.data[CS_IDX] += cell_mass_weighted_cs; + ltotal_mass_red += cell_mass; + lmass_weighted_density_red += cell_mass_weighted_density; + lmass_weighted_velocity_red += cell_mass_weighted_velocity; + lmass_weighted_cs_red += cell_mass_weighted_cs; } }, - reducer_sum); + total_mass_red, mass_weighted_density_red, mass_weighted_velocity_red, + mass_weighted_cs_red); // Save the reduction results to triggering_quantities - total_mass += triggering_reduction.data[MASS_IDX]; - mass_weighted_density += triggering_reduction.data[DENSITY_IDX]; - mass_weighted_velocity += triggering_reduction.data[VELOCITY_IDX]; - mass_weighted_cs += triggering_reduction.data[CS_IDX]; + total_mass += total_mass_red; + mass_weighted_density += mass_weighted_density_red; + mass_weighted_velocity += mass_weighted_velocity_red; + mass_weighted_cs += mass_weighted_cs_red; } // Remove gas consistent with Bondi accretion diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index b7dc6f74..f944e965 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -18,7 +18,6 @@ // Athena headers #include "../../eos/adiabatic_glmmhd.hpp" #include "../../main.hpp" -#include "../../reduction_utils.hpp" #include "../../units.hpp" #include "cluster_utils.hpp" #include "magnetic_tower.hpp" @@ -166,15 +165,16 @@ void MagneticTower::ReducePowerContribs(parthenon::Real &linear_contrib, MagneticTowerObj(1, alpha_, l_scale_, 0, l_mass_scale_, jet_coords); // Get the reduction of the linear and quadratic contributions ready - ReductionSumArray mt_power_reduction; - Kokkos::Sum> reducer_sum(mt_power_reduction); - - parthenon::par_reduce( - parthenon::loop_pattern_mdrange_tag, "MagneticTowerScaleFactor", - parthenon::DevExecSpace(), 0, cons_pack.GetDim(5) - 1, kb.s, kb.e, jb.s, jb.e, ib.s, - ib.e, + Real linear_contrib_red, quadratic_contrib_red; + + Kokkos::parallel_reduce( + "MagneticTowerScaleFactor", + Kokkos::MDRangePolicy>( + DevExecSpace(), {0, kb.s, jb.s, ib.s}, + {prim_pack.GetDim(5), kb.e + 1, jb.e + 1, ib.e + 1}, + {1, 1, 1, ib.e + 1 - ib.s}), KOKKOS_LAMBDA(const int &b, const int &k, const int &j, const int &i, - ReductionSumArray &team_mt_power_reduction) { + Real &llinear_contrib_red, Real &lquadratic_contrib_red) { auto &cons = cons_pack(b); auto &prim = prim_pack(b); const auto &coords = cons_pack.GetCoords(b); @@ -187,17 +187,15 @@ void MagneticTower::ReducePowerContribs(parthenon::Real &linear_contrib, b_z); // increases B**2 by 2*B0*Bnew + dt**2*Bnew**2) - team_mt_power_reduction.data[0] += - (prim(IB1, k, j, i) * b_x + prim(IB2, k, j, i) * b_y + - prim(IB3, k, j, i) * b_z) * - cell_volume; - team_mt_power_reduction.data[1] += - 0.5 * (b_x * b_x + b_y * b_y + b_z * b_z) * cell_volume; + llinear_contrib_red += (prim(IB1, k, j, i) * b_x + prim(IB2, k, j, i) * b_y + + prim(IB3, k, j, i) * b_z) * + cell_volume; + lquadratic_contrib_red += 0.5 * (b_x * b_x + b_y * b_y + b_z * b_z) * cell_volume; }, - reducer_sum); + linear_contrib_red, quadratic_contrib_red); - linear_contrib += mt_power_reduction.data[0]; - quadratic_contrib += mt_power_reduction.data[1]; + linear_contrib += linear_contrib_red; + quadratic_contrib += quadratic_contrib_red; } // Add magnetic potential to provided potential diff --git a/src/reduction_utils.hpp b/src/reduction_utils.hpp deleted file mode 100644 index 9e2fc5a1..00000000 --- a/src/reduction_utils.hpp +++ /dev/null @@ -1,65 +0,0 @@ - -// Athena-Parthenon - a performance portable block structured AMR MHD code -// Copyright (c) 2020, Athena Parthenon Collaboration. All rights reserved. -// Licensed under the 3-Clause License (the "LICENSE") - -#ifndef REDUCTION_UTILS_HPP_ -#define REDUCTION_UTILS_HPP_ -// Consider moving ReductionSumArray to Parthenon and extending to other operations - -#include - -// AthenaPK headers -#include "basic_types.hpp" - -// Reduction array from -// https://github.com/kokkos/kokkos/wiki/Custom-Reductions%3A-Built-In-Reducers-with-Custom-Scalar-Types -template -struct ReductionSumArray { - ScalarType data[N]; - - KOKKOS_INLINE_FUNCTION // Default constructor - Initialize to 0's - ReductionSumArray() { - for (int i = 0; i < N; i++) { - data[i] = 0; - } - } - KOKKOS_INLINE_FUNCTION // Copy Constructor - ReductionSumArray(const ReductionSumArray &rhs) { - for (int i = 0; i < N; i++) { - data[i] = rhs.data[i]; - } - } - KOKKOS_INLINE_FUNCTION // add operator - ReductionSumArray & - operator+=(const ReductionSumArray &src) { - for (int i = 0; i < N; i++) { - data[i] += src.data[i]; - } - return *this; - } - KOKKOS_INLINE_FUNCTION // volatile add operator - void - operator+=(const volatile ReductionSumArray &src) volatile { - for (int i = 0; i < N; i++) { - data[i] += src.data[i]; - } - } -}; - -namespace Kokkos { // reduction identity must be defined in Kokkos namespace -template <> -struct reduction_identity> { - KOKKOS_FORCEINLINE_FUNCTION static ReductionSumArray sum() { - return ReductionSumArray(); - } -}; -template <> -struct reduction_identity> { - KOKKOS_FORCEINLINE_FUNCTION static ReductionSumArray sum() { - return ReductionSumArray(); - } -}; -} // namespace Kokkos - -#endif // REDUCTION_UNITS_HPP_ From 92be2c1aa95231bc53aadb0ecb3642273af2f878 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 10 Apr 2023 13:03:33 -0500 Subject: [PATCH 75/95] Added checks for zero length scales, zero radii --- src/pgen/cluster/jet_coords.hpp | 6 +++--- src/pgen/cluster/magnetic_tower.cpp | 7 +++++++ src/pgen/cluster/magnetic_tower.hpp | 7 +++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp index a2a697ff..1e096306 100644 --- a/src/pgen/cluster/jet_coords.hpp +++ b/src/pgen/cluster/jet_coords.hpp @@ -54,8 +54,8 @@ class JetCoords { // Position in jet-cylindrical coordinates r_jet = sqrt(pow(fabs(x_jet), 2) + pow(fabs(y_jet), 2)); - cos_theta_jet = x_jet / r_jet; - sin_theta_jet = y_jet / r_jet; + cos_theta_jet = (r_jet != 0) ? x_jet / r_jet : 0; + sin_theta_jet = (r_jet != 0) ? y_jet / r_jet : 0; h_jet = z_jet; } @@ -111,4 +111,4 @@ class JetCoordsFactory { } // namespace cluster -#endif // CLUSTER_MAGNETIC_TOWER_HPP_ +#endif // CLUSTER_JET_COORDS_HPP_ diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index f944e965..3f5c9e5a 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -32,6 +32,10 @@ void MagneticTower::AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mas using parthenon::IndexRange; using parthenon::Real; + if( field_to_add == 0 && mass_to_add == 0){ + return; // Nothing to do + } + auto hydro_pkg = md->GetBlockData(0)->GetBlockPointer()->packages.Get("Hydro"); if (hydro_pkg->Param("fluid") != Fluid::glmmhd) { PARTHENON_FAIL("MagneticTower::AddSrcTerm: Only Fluid::glmmhd is supported"); @@ -205,6 +209,9 @@ void MagneticTower::AddInitialFieldToPotential(parthenon::MeshBlock *pmb, parthenon::IndexRange jb, parthenon::IndexRange ib, const View4D &A) const { + if( initial_field_ == 0){ + return; //Nothing to do + } auto hydro_pkg = pmb->packages.Get("Hydro"); const auto &coords = pmb->coords; diff --git a/src/pgen/cluster/magnetic_tower.hpp b/src/pgen/cluster/magnetic_tower.hpp index 49d7bb18..d874443f 100644 --- a/src/pgen/cluster/magnetic_tower.hpp +++ b/src/pgen/cluster/magnetic_tower.hpp @@ -38,7 +38,10 @@ class MagneticTowerObj { const parthenon::Real l_scale, const parthenon::Real density, const parthenon::Real l_mass_scale, const JetCoords jet_coords) : field_(field), alpha_(alpha), l_scale_(l_scale), density_(density), - l_mass_scale2_(SQR(l_mass_scale)), jet_coords_(jet_coords) {} + l_mass_scale2_(SQR(l_mass_scale)), jet_coords_(jet_coords) { + PARTHENON_REQUIRE(l_scale > 0, "Magnetic Tower Length scale must be strictly postitive"); + PARTHENON_REQUIRE(l_mass_scale > 0, "Magnetic Tower Mass Length scale must be strictly postitive"); + } // Compute Jet Potential in jet cylindrical coordinates KOKKOS_INLINE_FUNCTION void @@ -135,7 +138,7 @@ class MagneticTower { initial_field_(pin->GetOrAddReal(block, "initial_field", 0)), fixed_field_rate_(pin->GetOrAddReal(block, "fixed_field_rate", 0)), fixed_mass_rate_(pin->GetOrAddReal(block, "fixed_mass_rate", 0)), - l_mass_scale_(pin->GetOrAddReal(block, "l_mass_scale", 1)) { + l_mass_scale_(pin->GetOrAddReal(block, "l_mass_scale", 0)) { hydro_pkg->AddParam<>("magnetic_tower", *this); hydro_pkg->AddParam("magnetic_tower_linear_contrib", 0.0, true); hydro_pkg->AddParam("magnetic_tower_quadratic_contrib", 0.0, true); From 819c9febbebde60ed736767d810ab6fea22281dc Mon Sep 17 00:00:00 2001 From: Philipp Grete Date: Mon, 10 Apr 2023 14:27:35 -0400 Subject: [PATCH 76/95] Add comment on theta for r=0 edge case in coord trans --- src/pgen/cluster/jet_coords.hpp | 3 +++ src/pgen/cluster/magnetic_tower.cpp | 6 +++--- src/pgen/cluster/magnetic_tower.hpp | 6 ++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp index 1e096306..c82eab48 100644 --- a/src/pgen/cluster/jet_coords.hpp +++ b/src/pgen/cluster/jet_coords.hpp @@ -54,6 +54,9 @@ class JetCoords { // Position in jet-cylindrical coordinates r_jet = sqrt(pow(fabs(x_jet), 2) + pow(fabs(y_jet), 2)); + // Setting cos_theta and sin_theta to 0 for r = 0 as all places where + // those variables are used (SimCardToJetCylCoords) an r = 0 leads to the x and y + // component being 0, too. cos_theta_jet = (r_jet != 0) ? x_jet / r_jet : 0; sin_theta_jet = (r_jet != 0) ? y_jet / r_jet : 0; h_jet = z_jet; diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index 3f5c9e5a..43c43ac7 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -32,7 +32,7 @@ void MagneticTower::AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mas using parthenon::IndexRange; using parthenon::Real; - if( field_to_add == 0 && mass_to_add == 0){ + if (field_to_add == 0 && mass_to_add == 0) { return; // Nothing to do } @@ -209,8 +209,8 @@ void MagneticTower::AddInitialFieldToPotential(parthenon::MeshBlock *pmb, parthenon::IndexRange jb, parthenon::IndexRange ib, const View4D &A) const { - if( initial_field_ == 0){ - return; //Nothing to do + if (initial_field_ == 0) { + return; // Nothing to do } auto hydro_pkg = pmb->packages.Get("Hydro"); diff --git a/src/pgen/cluster/magnetic_tower.hpp b/src/pgen/cluster/magnetic_tower.hpp index d874443f..d6a987d7 100644 --- a/src/pgen/cluster/magnetic_tower.hpp +++ b/src/pgen/cluster/magnetic_tower.hpp @@ -39,8 +39,10 @@ class MagneticTowerObj { const parthenon::Real l_mass_scale, const JetCoords jet_coords) : field_(field), alpha_(alpha), l_scale_(l_scale), density_(density), l_mass_scale2_(SQR(l_mass_scale)), jet_coords_(jet_coords) { - PARTHENON_REQUIRE(l_scale > 0, "Magnetic Tower Length scale must be strictly postitive"); - PARTHENON_REQUIRE(l_mass_scale > 0, "Magnetic Tower Mass Length scale must be strictly postitive"); + PARTHENON_REQUIRE(l_scale > 0, + "Magnetic Tower Length scale must be strictly postitive"); + PARTHENON_REQUIRE(l_mass_scale > 0, + "Magnetic Tower Mass Length scale must be strictly postitive"); } // Compute Jet Potential in jet cylindrical coordinates From 56d8dc8ccd0646d1daea98356d2d84cd1c67f37b Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Wed, 12 Apr 2023 13:01:49 -0500 Subject: [PATCH 77/95] Fix temperature with kinetic feedback --- src/pgen/cluster/agn_feedback.cpp | 52 ++++++++++++++----- .../cluster_hydro_agn_feedback.py | 8 ++- 2 files changed, 47 insertions(+), 13 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index e64d69c5..7da388d8 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -200,19 +200,20 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real kinetic_jet_height = kinetic_jet_height_; // Matches 1/2.*jet_density*jet_velocity*jet_velocity*beta_dt; - const Real kinetic_feedback = - kinetic_fraction_ * power * kinetic_scaling_factor * beta_dt; // energy/volume + //const Real kinetic_feedback = + // kinetic_fraction_ * power * kinetic_scaling_factor * beta_dt; // energy/volume // Amount of density to dump in each cell - const Real kinetic_density = + const Real jet_density = kinetic_mass_fraction_ * mass_rate * kinetic_scaling_factor * beta_dt; // Velocity of added gas - const Real kinetic_velocity = + const Real jet_velocity = std::sqrt(2. * kinetic_fraction_ * power / (kinetic_mass_fraction_ * mass_rate)); // Amount of momentum density ( density * velocity) to dump in each cell - const Real kinetic_momentum = kinetic_density * kinetic_velocity; + const Real jet_momentum = jet_density * jet_velocity; + const Real jet_momentum2 = SQR(jet_momentum); //////////////////////////////////////////////////////////////////////////////// const parthenon::Real time = tm.time; @@ -248,7 +249,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, } // Kinetic Jet Feedback - if (kinetic_feedback > 0 || kinetic_density > 0) { + if ( jet_density > 0) { // Get position in jet cylindrical coords Real r, cos_theta, sin_theta, h; jet_coords.SimCartToJetCylCoords(x, y, z, r, cos_theta, sin_theta, h); @@ -263,12 +264,39 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk - cons(IDN, k, j, i) += kinetic_density; // mass/volume - // velocity*mass/volume - cons(IM1, k, j, i) += kinetic_momentum * sign_jet * jet_axis_x; - cons(IM2, k, j, i) += kinetic_momentum * sign_jet * jet_axis_y; - cons(IM3, k, j, i) += kinetic_momentum * sign_jet * jet_axis_z; - cons(IEN, k, j, i) += kinetic_feedback; // energy/volume + /////////////////////////////////////////////////////////////////// + // We add the kinetic jet with a fixed jet density and jet + // velocity while keeping the same ambient temperature. Therefore, + // we keep the same specific internal energy of the existing gas + // while adding the jet density and jet momentum to the existing + // gas + /////////////////////////////////////////////////////////////////// + + //Compute the primitives of the old variables to get specific internal energy + eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); + const Real specific_internal_e = prim( IPR, k, j, i)/( prim(IDN, k, j, i) * (eos.GetGamma() - 1.) ); + + //Helps comput + const Real M_old2 = SQR(cons(IM1, k, j, i)) + + SQR(cons(IM2, k, j, i)) + + SQR(cons(IM3, k, j, i)); + const Real M_old_dot_M_jet = cons(IM1, k, j, i) * jet_momentum * sign_jet * jet_axis_x + + cons(IM2, k, j, i) * jet_momentum * sign_jet * jet_axis_y + + cons(IM3, k, j, i) * jet_momentum * sign_jet * jet_axis_z; + const Real M_jet2 = jet_momentum * jet_momentum; + + cons(IDN, k, j, i) += jet_density; + cons(IM1, k, j, i) += jet_momentum * sign_jet * jet_axis_x; + cons(IM2, k, j, i) += jet_momentum * sign_jet * jet_axis_y; + cons(IM3, k, j, i) += jet_momentum * sign_jet * jet_axis_z; + + // Add thermal energy to keep the same temperature and kinetic energy to match the new momentum density + //cons(IEN, k, j, i) += new_thermal_e + new_kinetic_e; + const Real old_density = prim(IDN, k, j, i); + const Real new_density = cons(IDN, k, j, i); + cons(IEN, k, j, i) += jet_density*specific_internal_e + + 0.5 * ( -M_old2*jet_density/old_density + + 2 * M_old_dot_M_jet + M_jet2 )/new_density; } } eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index a1c1dfac..f6a227ed 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -315,7 +315,13 @@ def kinetic_feedback(Z, Y, X, time): * jet_velocity * jet_coords.jet_n[2] ) - dE = inside_jet * time * 0.5 * jet_density * jet_velocity**2 + dKE = jet_density * inside_jet * time * 0.5 * jet_velocity**2 + dTE = jet_density * inside_jet * time *self.uniform_gas_pres / (self.uniform_gas_rho*(self.adiabatic_index - 1.0)) + dE = dKE + dTE + + # DELETEME + #print(dKE.max().in_units("code_mass*code_length**-1*code_time**-2"), + # dTE.max().in_units("code_mass*code_length**-1*code_time**-2")) return drho, dMx, dMy, dMz, dE From 7153b842479a2809751e5468a29246ffc823cd2f Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 25 Apr 2023 10:10:38 -0500 Subject: [PATCH 78/95] Added jet temperature and/or velocity --- inputs/cluster/cluster.in | 7 +- inputs/cluster/hydro_agn_feedback.in | 10 +- src/pgen/cluster/agn_feedback.cpp | 151 +++++++++++------- src/pgen/cluster/agn_feedback.hpp | 4 +- src/pgen/cluster/magnetic_tower.cpp | 3 +- .../cluster_hydro_agn_feedback.py | 46 ++++-- 6 files changed, 141 insertions(+), 80 deletions(-) diff --git a/inputs/cluster/cluster.in b/inputs/cluster/cluster.in index af98b0a1..61d7bc93 100644 --- a/inputs/cluster/cluster.in +++ b/inputs/cluster/cluster.in @@ -175,9 +175,10 @@ kinetic_fraction = 0.3 #thermal_fraction = 0.5 #kinetic_fraction = 0.5 -thermal_radius = 0.0005 -kinetic_jet_radius = 0.0005 -kinetic_jet_height = 0.0005 +thermal_radius = 0.1 +kinetic_jet_radius = 0.1 +kinetic_jet_thickness = 0.05 +kinetic_jet_offset = 0.05 diff --git a/inputs/cluster/hydro_agn_feedback.in b/inputs/cluster/hydro_agn_feedback.in index 6dcd4010..717e76b5 100644 --- a/inputs/cluster/hydro_agn_feedback.in +++ b/inputs/cluster/hydro_agn_feedback.in @@ -16,9 +16,9 @@ dt = 0.01 # Time increment between outputs id = vars # Name to append to output -cfl_number = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number +cfl = 1e1 # The Courant, Friedrichs, & Lewy (CFL) Number nlim = -1 # cycle limit -tlim = 0.01 # time limit +tlim = 0.1 # time limit integrator = vl2 # time integration algorithm @@ -108,8 +108,10 @@ kinetic_fraction = 0.5 thermal_radius = 0.0125 -kinetic_jet_radius = 0.01 -kinetic_jet_height = 0.02 +kinetic_jet_radius = 0.02 +kinetic_jet_thickness = 0.01 +kinetic_jet_offset = 0.01 +kinetic_jet_temperature = 1e7 disabled = True diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 7da388d8..07c7ef05 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -40,25 +40,16 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_fraction", 0.0)), magnetic_fraction_( pin->GetOrAddReal("problem/cluster/agn_feedback", "magnetic_fraction", 0.0)), - thermal_mass_fraction_(pin->GetOrAddReal("problem/cluster/agn_feedback", - "thermal_mass_fraction", NAN)), - kinetic_mass_fraction_(pin->GetOrAddReal("problem/cluster/agn_feedback", - "kinetic_mass_fraction", NAN)), - magnetic_mass_fraction_(pin->GetOrAddReal("problem/cluster/agn_feedback", - "magnetic_mass_fraction", NAN)), thermal_radius_( pin->GetOrAddReal("problem/cluster/agn_feedback", "thermal_radius", 0.01)), kinetic_jet_radius_( pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_radius", 0.01)), - kinetic_jet_height_( - pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_height", 0.02)), + kinetic_jet_thickness_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_thickness", 0.02)), + kinetic_jet_offset_( + pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_offset", 0.02)), disabled_(pin->GetOrAddBoolean("problem/cluster/agn_feedback", "disabled", false)) { - // If any mass_fractions aren't set, set to energy fraction - if (std::isnan(thermal_mass_fraction_)) thermal_mass_fraction_ = thermal_fraction_; - if (std::isnan(magnetic_mass_fraction_)) magnetic_mass_fraction_ = magnetic_fraction_; - if (std::isnan(kinetic_mass_fraction_)) kinetic_mass_fraction_ = kinetic_fraction_; - // Normalize the thermal, kinetic, and magnetic fractions to sum to 1.0 const Real total_frac = thermal_fraction_ + kinetic_fraction_ + magnetic_fraction_; if (total_frac > 0) { @@ -67,21 +58,74 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, magnetic_fraction_ = magnetic_fraction_ / total_frac; } - // Normalize the thermal, kinetic, and magnetic mass fractions to sum to 1.0 - const Real total_mass_frac = - thermal_mass_fraction_ + kinetic_mass_fraction_ + magnetic_mass_fraction_; - if (total_mass_frac > 0) { - thermal_mass_fraction_ = thermal_mass_fraction_ / total_mass_frac; - kinetic_mass_fraction_ = kinetic_mass_fraction_ / total_mass_frac; - magnetic_mass_fraction_ = magnetic_mass_fraction_ / total_mass_frac; - } - PARTHENON_REQUIRE(thermal_fraction_ >= 0 && kinetic_fraction_ >= 0 && magnetic_fraction_ >= 0, "AGN feedback energy fractions must be non-negative."); - PARTHENON_REQUIRE(thermal_mass_fraction_ >= 0 && kinetic_mass_fraction_ >= 0 && - magnetic_mass_fraction_ >= 0, - "AGN feedback mass fractions must be non-negative."); + + ///////////////////////////////////////////////////// + // Read in or calculate jet velocity and temperature. Either and or both can + // be defined but they must satify + // + // v_jet = sqrt( 2*(eps*c^2 - (1-eps)*e_jet) ) + // + // With real, non-negative values for v_jet and e_jet + ///////////////////////////////////////////////////// + + kinetic_jet_velocity_ = NAN; + kinetic_jet_temperature_ = NAN; + + Real kinetic_jet_e; + + const auto units = hydro_pkg->Param("units"); + + if( pin->DoesParameterExist("problem/cluster/agn_feedback","kinetic_jet_velocity")){ + kinetic_jet_velocity_ = pin->GetReal("problem/cluster/agn_feedback","kinetic_jet_velocity"); + } + if(pin->DoesParameterExist("problem/cluster/agn_feedback","kinetic_jet_temperature")){ + kinetic_jet_temperature_ = pin->GetReal("problem/cluster/agn_feedback","kinetic_jet_temperature"); + + const Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); + const Real H_mass_fraction = 1.0 - He_mass_fraction; + const Real mu = 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); + const Real gam = pin->GetReal("hydro", "gamma"); + const Real gm1 = (gam - 1.0); + kinetic_jet_e = units.k_boltzmann()*kinetic_jet_temperature_/(mu*units.atomic_mass_unit()*gm1); + } + + if( std::isnan(kinetic_jet_velocity_) && std::isnan(kinetic_jet_temperature_) ){ + //Both velocity and temperature are missing, assume 0K temperature + kinetic_jet_velocity_ = units.speed_of_light()*sqrt( 2*(efficiency_) ); + kinetic_jet_temperature_ = 0; + kinetic_jet_e = 0; + std::cout << "### WARNING Kinetic jet velocity nor temperature not specified. Assuming 0K temperature jet" << std::endl; + } else if ( std::isnan(kinetic_jet_velocity_) ){ + //Velocity is missing, compute it from e_jet + kinetic_jet_velocity_ = sqrt( 2 * (efficiency_ * SQR(units.speed_of_light()) - (1.0 - efficiency_)*kinetic_jet_e)); + } else if ( std::isnan(kinetic_jet_temperature_) ){ + //Temperature is missing, compute e_jet and T_jet from v_jet + kinetic_jet_e = (efficiency_*SQR(units.speed_of_light()) - 2*SQR(kinetic_jet_velocity_))/(1 - efficiency_); + const Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); + const Real H_mass_fraction = 1.0 - He_mass_fraction; + const Real mu = 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); + const Real gam = pin->GetReal("hydro", "gamma"); + const Real gm1 = (gam - 1.0); + kinetic_jet_temperature_ = (kinetic_jet_e*mu*units.atomic_mass_unit()*gm1)/units.k_boltzmann(); + } + + + //Verify all equations are satified. NAN's here should give failures + PARTHENON_REQUIRE( kinetic_jet_velocity_ + - fabs( sqrt( 2*(efficiency_*SQR(units.speed_of_light()) - ( 1 - efficiency_)*kinetic_jet_e))) < 10*std::numeric_limits::epsilon(), + "Specified kinetic jet velocity and temperature are incompatible with mass to energy conversion efficiency. Choose either velocity or temperature."); + + PARTHENON_REQUIRE( kinetic_jet_velocity_ <= units.speed_of_light()*sqrt(2*efficiency_), + "Kinetic jet velocity implies negative temperature of the jet"); + + PARTHENON_REQUIRE( kinetic_jet_e <= SQR(units.speed_of_light())*efficiency_/( 1- efficiency_), + "Kinetic jet temperature implies negative kinetic energy of the jet"); + + PARTHENON_REQUIRE(kinetic_jet_velocity_ >= 0, "Kinetic jet velocity must be non-negative"); + PARTHENON_REQUIRE(kinetic_jet_temperature_ >= 0, "Kinetic jet temperature must be non-negative"); // Add user history output variable for AGN power auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); @@ -188,16 +232,17 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, thermal_fraction_ * power * thermal_scaling_factor * beta_dt; // Amount of density to dump in each cell const Real thermal_density = - thermal_mass_fraction_ * mass_rate * thermal_scaling_factor * beta_dt; + thermal_fraction_ * mass_rate * thermal_scaling_factor * beta_dt; //////////////////////////////////////////////////////////////////////////////// // Kinetic Jet Quantities //////////////////////////////////////////////////////////////////////////////// const Real kinetic_scaling_factor = - 1 / (2 * kinetic_jet_height_ * M_PI * pow(kinetic_jet_radius_, 2)); + 1 / (2 * kinetic_jet_thickness_ * M_PI * pow(kinetic_jet_radius_, 2)); const Real kinetic_jet_radius = kinetic_jet_radius_; - const Real kinetic_jet_height = kinetic_jet_height_; + const Real kinetic_jet_thickness = kinetic_jet_thickness_; + const Real kinetic_jet_offset = kinetic_jet_offset_; // Matches 1/2.*jet_density*jet_velocity*jet_velocity*beta_dt; //const Real kinetic_feedback = @@ -205,15 +250,16 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Amount of density to dump in each cell const Real jet_density = - kinetic_mass_fraction_ * mass_rate * kinetic_scaling_factor * beta_dt; + kinetic_fraction_ * mass_rate * kinetic_scaling_factor * beta_dt; // Velocity of added gas - const Real jet_velocity = - std::sqrt(2. * kinetic_fraction_ * power / (kinetic_mass_fraction_ * mass_rate)); + const Real jet_velocity = kinetic_jet_velocity_; // Amount of momentum density ( density * velocity) to dump in each cell const Real jet_momentum = jet_density * jet_velocity; - const Real jet_momentum2 = SQR(jet_momentum); + + //Amount of total energy to dump in each cell + const Real jet_feedback = kinetic_fraction_ * power * kinetic_scaling_factor * beta_dt; //////////////////////////////////////////////////////////////////////////////// const parthenon::Real time = tm.time; @@ -254,7 +300,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, Real r, cos_theta, sin_theta, h; jet_coords.SimCartToJetCylCoords(x, y, z, r, cos_theta, sin_theta, h); - if (r < kinetic_jet_radius && fabs(h) < kinetic_jet_height) { + if (r < kinetic_jet_radius && fabs(h) >= kinetic_jet_offset && fabs(h) <= kinetic_jet_offset + kinetic_jet_thickness) { // Cell falls inside jet deposition volume // Get the vector of the jet axis @@ -265,38 +311,29 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk /////////////////////////////////////////////////////////////////// - // We add the kinetic jet with a fixed jet density and jet - // velocity while keeping the same ambient temperature. Therefore, - // we keep the same specific internal energy of the existing gas - // while adding the jet density and jet momentum to the existing - // gas + // We add the kinetic jet with a fixed jet velocity and specific + // internal energy/temperature of the added gas. The density, + // momentum, and total energy added depend on the triggered power. /////////////////////////////////////////////////////////////////// - - //Compute the primitives of the old variables to get specific internal energy + + #ifdef DEBUG eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); - const Real specific_internal_e = prim( IPR, k, j, i)/( prim(IDN, k, j, i) * (eos.GetGamma() - 1.) ); - - //Helps comput - const Real M_old2 = SQR(cons(IM1, k, j, i)) - + SQR(cons(IM2, k, j, i)) - + SQR(cons(IM3, k, j, i)); - const Real M_old_dot_M_jet = cons(IM1, k, j, i) * jet_momentum * sign_jet * jet_axis_x - + cons(IM2, k, j, i) * jet_momentum * sign_jet * jet_axis_y - + cons(IM3, k, j, i) * jet_momentum * sign_jet * jet_axis_z; - const Real M_jet2 = jet_momentum * jet_momentum; + const Real old_specific_internal_e = prim( IPR, k, j, i)/( prim(IDN, k, j, i) * (eos.GetGamma() - 1.) ); + #endif cons(IDN, k, j, i) += jet_density; cons(IM1, k, j, i) += jet_momentum * sign_jet * jet_axis_x; cons(IM2, k, j, i) += jet_momentum * sign_jet * jet_axis_y; cons(IM3, k, j, i) += jet_momentum * sign_jet * jet_axis_z; + cons(IEN, k, j, i) += jet_feedback; + + #ifdef DEBUG + eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); + const Real new_specific_internal_e = prim( IPR, k, j, i)/( prim(IDN, k, j, i) * (eos.GetGamma() - 1.) ); + PARTHENON_DEBUG_REQUIRE( new_specific_internal_e > jet_specific_internal_e || new_specific_internal_e > old_specific_internal_e, + "Kinetic injection leads to temperature below jet and existing gas"); + #endif - // Add thermal energy to keep the same temperature and kinetic energy to match the new momentum density - //cons(IEN, k, j, i) += new_thermal_e + new_kinetic_e; - const Real old_density = prim(IDN, k, j, i); - const Real new_density = cons(IDN, k, j, i); - cons(IEN, k, j, i) += jet_density*specific_internal_e + - 0.5 * ( -M_old2*jet_density/old_density - + 2 * M_old_dot_M_jet + M_jet2 )/new_density; } } eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index d2d06ba4..97b8852f 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -27,7 +27,6 @@ class AGNFeedback { public: const parthenon::Real fixed_power_; parthenon::Real thermal_fraction_, kinetic_fraction_, magnetic_fraction_; - parthenon::Real thermal_mass_fraction_, kinetic_mass_fraction_, magnetic_mass_fraction_; // Efficiency converting mass to energy const parthenon::Real efficiency_; @@ -36,7 +35,8 @@ class AGNFeedback { const parthenon::Real thermal_radius_; // Kinetic Feedback Parameters - const parthenon::Real kinetic_jet_radius_, kinetic_jet_height_; + const parthenon::Real kinetic_jet_radius_, kinetic_jet_thickness_, kinetic_jet_offset_; + parthenon::Real kinetic_jet_velocity_, kinetic_jet_temperature_; const bool disabled_; diff --git a/src/pgen/cluster/magnetic_tower.cpp b/src/pgen/cluster/magnetic_tower.cpp index 43c43ac7..124f120d 100644 --- a/src/pgen/cluster/magnetic_tower.cpp +++ b/src/pgen/cluster/magnetic_tower.cpp @@ -135,8 +135,7 @@ void MagneticTower::AddSrcTerm(parthenon::Real field_to_add, parthenon::Real mas // Add density const Real cell_delta_rho = mt.DensityFromSimCart(coords.Xc<1>(i), coords.Xc<2>(j), coords.Xc<3>(k)); - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, - i); + cons(IDN, k, j, i) += cell_delta_rho; }); } diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index f6a227ed..ccf41dd3 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -118,11 +118,17 @@ def __init__(self): self.zjet_coords = ZJetCoords() # Feedback parameters - self.fixed_power = unyt.unyt_quantity(1e44, "erg/s") + self.fixed_power = unyt.unyt_quantity(2e46, "erg/s") self.agn_thermal_radius = unyt.unyt_quantity(100, "kpc") self.efficiency = 1.0e-3 - self.agn_jet_radius = unyt.unyt_quantity(50, "kpc") - self.agn_jet_height = unyt.unyt_quantity(150, "kpc") + self.jet_temperature = unyt.unyt_quantity(1e7,"K") + self.jet_radius = unyt.unyt_quantity(50, "kpc") + self.jet_thickness = unyt.unyt_quantity(50, "kpc") + self.jet_offset = unyt.unyt_quantity(10, "kpc") + + + mu= 1./(3./4.*self.He_mass_fraction + (1-self.He_mass_fraction)*2) + self.jet_internal_e = self.jet_temperature*unyt.boltzmann_constant/(mu*unyt.amu*(self.adiabatic_index -1.)) self.norm_tol = 1e-3 @@ -199,8 +205,10 @@ def Prepare(self, parameters, step): f"problem/cluster/agn_feedback/kinetic_fraction={agn_kinetic_fraction}", f"problem/cluster/agn_feedback/magnetic_fraction=0", f"problem/cluster/agn_feedback/thermal_radius={self.agn_thermal_radius.in_units('code_length').v}", - f"problem/cluster/agn_feedback/kinetic_jet_radius={self.agn_jet_radius.in_units('code_length').v}", - f"problem/cluster/agn_feedback/kinetic_jet_height={self.agn_jet_height.in_units('code_length').v}", + f"problem/cluster/agn_feedback/kinetic_jet_temperature={self.jet_temperature.in_units('K').v}", + f"problem/cluster/agn_feedback/kinetic_jet_radius={self.jet_radius.in_units('code_length').v}", + f"problem/cluster/agn_feedback/kinetic_jet_thickness={self.jet_thickness.in_units('code_length').v}", + f"problem/cluster/agn_feedback/kinetic_jet_offset={self.jet_offset.in_units('code_length').v}", ] return parameters @@ -259,10 +267,11 @@ def Analyse(self, parameters): jet_density = ( (agn_kinetic_fraction * self.fixed_power) / (self.efficiency * unyt.c_cgs**2) - / (2 * np.pi * self.agn_jet_radius**2 * self.agn_jet_height) + / (2 * np.pi * self.jet_radius**2 * self.jet_thickness) ) - jet_velocity = np.sqrt(2 * self.efficiency) * unyt.c_cgs + jet_velocity = np.sqrt(2 * ( self.efficiency * unyt.c_cgs**2 - (1-self.efficiency)*self.jet_internal_e)) + jet_feedback = self.fixed_power*agn_kinetic_fraction/ (2 * np.pi * self.jet_radius**2 * self.jet_thickness) def kinetic_feedback(Z, Y, X, time): if not hasattr(time, "units"): @@ -277,14 +286,21 @@ def kinetic_feedback(Z, Y, X, time): np.piecewise( R, [ - R <= self.agn_jet_radius, + R <= self.jet_radius, ], [1, 0], ) * np.piecewise( H, [ - np.abs(H) <= self.agn_jet_height, + np.abs(H) >= self.jet_offset, + ], + [1, 0], + ) + * np.piecewise( + H, + [ + np.abs(H) <= self.jet_offset + self.jet_thickness, ], [1, 0], ) @@ -315,13 +331,19 @@ def kinetic_feedback(Z, Y, X, time): * jet_velocity * jet_coords.jet_n[2] ) - dKE = jet_density * inside_jet * time * 0.5 * jet_velocity**2 - dTE = jet_density * inside_jet * time *self.uniform_gas_pres / (self.uniform_gas_rho*(self.adiabatic_index - 1.0)) - dE = dKE + dTE + + #Note: Final density should be correct by thermal mass injected + #final_density = (time*jet_density + self.uniform_gas_rho) + #final_velocity = (time*jet_density*jet_velocity)/( final_density) + #dKE = inside_jet * 0.5 * final_density * final_velocity**2 + #dTE = inside_jet * time * jet_density * self.uniform_gas_pres / (self.uniform_gas_rho*(self.adiabatic_index - 1.0)) + dE = jet_feedback*time*inside_jet # DELETEME #print(dKE.max().in_units("code_mass*code_length**-1*code_time**-2"), # dTE.max().in_units("code_mass*code_length**-1*code_time**-2")) + #print(dE.max()/( + # time* agn_kinetic_fraction*self.fixed_power/(2*np.pi*self.jet_radius**2*self.jet_thickness))) return drho, dMx, dMy, dMz, dE From 5d59d7e7fc7776c691072911b975b84910e16b89 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 25 Apr 2023 10:11:06 -0500 Subject: [PATCH 79/95] Removing triggered gas conserves M,E --- src/pgen/cluster/agn_triggering.cpp | 9 +-- .../cluster_agn_triggering.py | 74 +++++++++---------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 610bc4ac..906352fb 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -171,9 +171,9 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, const Real cell_delta_rho = -prim(IDN, k, j, i) / cold_t_acc * dt; if (remove_accreted_mass) { - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), - k, j, i); - // Update the Primitives + cons(IDN, k, j, i) += cell_delta_rho; + + //// Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); } } @@ -291,8 +291,7 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData const Real cell_delta_rho = -prim(IDN, k, j, i) / total_mass * accretion_rate * dt; - AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, - i); + cons(IDN, k, j, i) += cell_delta_rho; // Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); diff --git a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py index 76b74789..4d728e1d 100644 --- a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py +++ b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py @@ -79,6 +79,11 @@ def __init__(self): + self.uniform_gas_uy**2 + self.uniform_gas_uz**2 ) + self.uniform_gas_M = np.sqrt( + self.uniform_gas_Mx**2 + + self.uniform_gas_My**2 + + self.uniform_gas_Mz**2 + ) self.uniform_gas_temp = ( self.mu * self.m_u / self.k_b * self.uniform_gas_pres / self.uniform_gas_rho @@ -89,14 +94,17 @@ def __init__(self): # Triggering parameters self.accretion_radius = unyt.unyt_quantity(20, "kpc") - self.cold_temp_thresh = self.uniform_gas_temp * 1.01 + self.cold_temp_thresh = self.uniform_gas_temp * 1e1 self.cold_t_acc = unyt.unyt_quantity(100, "Myr") self.bondi_alpha = 100 self.bondi_beta = 2 self.bondi_n0 = 0.05 * (self.uniform_gas_rho / self.mean_molecular_mass) - self.norm_tol = 1e-3 - self.linf_accretion_rate_tol = 1e-3 + #Discrepency expected due to volume integration of sphere with cubic + #cells, difference in integration order between simulation and + #determined densities and pressures here + self.norm_tol = 1e-2 + self.linf_accretion_rate_tol = 1e-2 self.step_params_list = ["COLD_GAS", "BOOSTED_BONDI", "BOOTH_SCHAYE"] self.steps = len(self.step_params_list) @@ -232,9 +240,15 @@ def Analyse(self, parameters): accretion_volume = 4.0 / 3.0 * np.pi * self.accretion_radius**3 + + ################################################################# + # Integrate the accretion rate, density, and pressure at the same + # times used by the simulation + ################################################################# for i in range(n_times): dt = sim_dts[i] + #Compute accretion rate from integrated density if triggering_mode == "COLD_GAS": # Temperature should stay fixed below cold gas threshold accretion_rate = ( @@ -260,9 +274,10 @@ def Analyse(self, parameters): cs2 = ( self.adiabatic_index - * self.uniform_gas_pres - / self.uniform_gas_rho + * analytic_pressure[i] + / analytic_density[i] ) + v2 = (self.uniform_gas_M/analytic_density[i])**2 accretion_rate = ( alpha * ( @@ -272,7 +287,7 @@ def Analyse(self, parameters): * self.M_smbh**2 * analytic_density[i] ) - / (self.uniform_gas_vel**2 + cs2) ** (3.0 / 2.0) + / (v2 + cs2) ** (3.0 / 2.0) ) else: raise Exception( @@ -281,16 +296,13 @@ def Analyse(self, parameters): accretion_rate_density = accretion_rate / accretion_volume + # From computed accretion rate, determine the new density and pressure analytic_density[i + 1] = ( analytic_density[i] - accretion_rate_density * dt ).in_units(analytic_density.units) - analytic_pressure[i + 1] = ( - analytic_pressure[i] - - accretion_rate_density - * dt - * analytic_pressure[i] - / analytic_density[i] - ).in_units(analytic_pressure.units) + + analytic_pressure[i + 1] = ( (self.adiabatic_index - 1.0) *(self.uniform_gas_energy_density - 0.5*analytic_density[i+1]*self.uniform_gas_vel**2) ).in_units(analytic_pressure.units) + analytic_accretion_rate[i] = accretion_rate.in_units( analytic_accretion_rate.units ) @@ -300,6 +312,8 @@ def Analyse(self, parameters): (analytic_accretion_rate - sim_accretion_rate) / analytic_accretion_rate ) + #import ipdb;ipdb.set_trace() + if np.max(accretion_rate_err) > self.linf_accretion_rate_tol: analyze_status = False print( @@ -311,14 +325,10 @@ def Analyse(self, parameters): final_rho = analytic_density[-1] final_pres = analytic_pressure[-1] - final_Mx = self.uniform_gas_ux * final_rho - final_My = self.uniform_gas_uy * final_rho - final_Mz = self.uniform_gas_uz * final_rho - final_energy_density = 1.0 / 2.0 * final_rho * ( - self.uniform_gas_ux**2 - + self.uniform_gas_uy**2 - + self.uniform_gas_uz**2 - ) + final_pres / (self.adiabatic_index - 1.0) + final_Mx = self.uniform_gas_Mx + final_My = self.uniform_gas_My + final_Mz = self.uniform_gas_Mz + final_energy_density = self.uniform_gas_energy_density def accretion_mask(Z, Y, X, inner_state, outer_state): pos_cart = unyt.unyt_array((X, Y, Z), "code_length") @@ -381,32 +391,18 @@ def accretion_mask(Z, Y, X, inner_state, outer_state): ) .in_units("code_mass/code_length**3") .v, - "MomentumDensity1": lambda Z, Y, X, time: accretion_mask( - Z, Y, X, final_Mx, self.uniform_gas_Mx - ) + "MomentumDensity1": lambda Z, Y, X, time: self.uniform_gas_Mx .in_units("code_mass*code_length**-2*code_time**-1") .v, - "MomentumDensity2": lambda Z, Y, X, time: accretion_mask( - Z, Y, X, final_My, self.uniform_gas_My - ) + "MomentumDensity2": lambda Z, Y, X, time: self.uniform_gas_My .in_units("code_mass*code_length**-2*code_time**-1") .v, - "MomentumDensity3": lambda Z, Y, X, time: accretion_mask( - Z, Y, X, final_Mz, self.uniform_gas_Mz - ) + "MomentumDensity3": lambda Z, Y, X, time: self.uniform_gas_Mz .in_units("code_mass*code_length**-2*code_time**-1") .v, - "TotalEnergyDensity": lambda Z, Y, X, time: accretion_mask( - Z, Y, X, final_energy_density, self.uniform_gas_energy_density - ) + "TotalEnergyDensity": lambda Z, Y, X, time: self.uniform_gas_energy_density .in_units("code_mass*code_length**-1*code_time**-2") .v, - "Velocity1": lambda Z, Y, X, time: np.ones_like(Z) - * self.uniform_gas_ux.in_units("code_length*code_time**-1").v, - "Velocity2": lambda Z, Y, X, time: np.ones_like(Z) - * self.uniform_gas_uy.in_units("code_length*code_time**-1").v, - "Velocity3": lambda Z, Y, X, time: np.ones_like(Z) - * self.uniform_gas_uz.in_units("code_length*code_time**-1").v, "Pressure": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_pres, self.uniform_gas_pres ) From 1339a73f29b4c61024d3ff8b17fff7ded2ad9b95 Mon Sep 17 00:00:00 2001 From: par-hermes Date: Tue, 25 Apr 2023 15:14:08 +0000 Subject: [PATCH 80/95] cpp-py-formatter --- src/pgen/cluster/agn_feedback.cpp | 116 ++++++++++-------- .../cluster_agn_triggering.py | 49 ++++---- .../cluster_hydro_agn_feedback.py | 41 ++++--- 3 files changed, 122 insertions(+), 84 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 07c7ef05..806e3c02 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -44,8 +44,8 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, pin->GetOrAddReal("problem/cluster/agn_feedback", "thermal_radius", 0.01)), kinetic_jet_radius_( pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_radius", 0.01)), - kinetic_jet_thickness_( - pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_thickness", 0.02)), + kinetic_jet_thickness_(pin->GetOrAddReal("problem/cluster/agn_feedback", + "kinetic_jet_thickness", 0.02)), kinetic_jet_offset_( pin->GetOrAddReal("problem/cluster/agn_feedback", "kinetic_jet_offset", 0.02)), disabled_(pin->GetOrAddBoolean("problem/cluster/agn_feedback", "disabled", false)) { @@ -61,7 +61,7 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, PARTHENON_REQUIRE(thermal_fraction_ >= 0 && kinetic_fraction_ >= 0 && magnetic_fraction_ >= 0, "AGN feedback energy fractions must be non-negative."); - + ///////////////////////////////////////////////////// // Read in or calculate jet velocity and temperature. Either and or both can // be defined but they must satify @@ -78,54 +78,70 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, const auto units = hydro_pkg->Param("units"); - if( pin->DoesParameterExist("problem/cluster/agn_feedback","kinetic_jet_velocity")){ - kinetic_jet_velocity_ = pin->GetReal("problem/cluster/agn_feedback","kinetic_jet_velocity"); + if (pin->DoesParameterExist("problem/cluster/agn_feedback", "kinetic_jet_velocity")) { + kinetic_jet_velocity_ = + pin->GetReal("problem/cluster/agn_feedback", "kinetic_jet_velocity"); } - if(pin->DoesParameterExist("problem/cluster/agn_feedback","kinetic_jet_temperature")){ - kinetic_jet_temperature_ = pin->GetReal("problem/cluster/agn_feedback","kinetic_jet_temperature"); + if (pin->DoesParameterExist("problem/cluster/agn_feedback", + "kinetic_jet_temperature")) { + kinetic_jet_temperature_ = + pin->GetReal("problem/cluster/agn_feedback", "kinetic_jet_temperature"); const Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); const Real H_mass_fraction = 1.0 - He_mass_fraction; const Real mu = 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); const Real gam = pin->GetReal("hydro", "gamma"); const Real gm1 = (gam - 1.0); - kinetic_jet_e = units.k_boltzmann()*kinetic_jet_temperature_/(mu*units.atomic_mass_unit()*gm1); + kinetic_jet_e = units.k_boltzmann() * kinetic_jet_temperature_ / + (mu * units.atomic_mass_unit() * gm1); } - if( std::isnan(kinetic_jet_velocity_) && std::isnan(kinetic_jet_temperature_) ){ - //Both velocity and temperature are missing, assume 0K temperature - kinetic_jet_velocity_ = units.speed_of_light()*sqrt( 2*(efficiency_) ); + if (std::isnan(kinetic_jet_velocity_) && std::isnan(kinetic_jet_temperature_)) { + // Both velocity and temperature are missing, assume 0K temperature + kinetic_jet_velocity_ = units.speed_of_light() * sqrt(2 * (efficiency_)); kinetic_jet_temperature_ = 0; kinetic_jet_e = 0; - std::cout << "### WARNING Kinetic jet velocity nor temperature not specified. Assuming 0K temperature jet" << std::endl; - } else if ( std::isnan(kinetic_jet_velocity_) ){ - //Velocity is missing, compute it from e_jet - kinetic_jet_velocity_ = sqrt( 2 * (efficiency_ * SQR(units.speed_of_light()) - (1.0 - efficiency_)*kinetic_jet_e)); - } else if ( std::isnan(kinetic_jet_temperature_) ){ - //Temperature is missing, compute e_jet and T_jet from v_jet - kinetic_jet_e = (efficiency_*SQR(units.speed_of_light()) - 2*SQR(kinetic_jet_velocity_))/(1 - efficiency_); + std::cout << "### WARNING Kinetic jet velocity nor temperature not specified. " + "Assuming 0K temperature jet" + << std::endl; + } else if (std::isnan(kinetic_jet_velocity_)) { + // Velocity is missing, compute it from e_jet + kinetic_jet_velocity_ = sqrt(2 * (efficiency_ * SQR(units.speed_of_light()) - + (1.0 - efficiency_) * kinetic_jet_e)); + } else if (std::isnan(kinetic_jet_temperature_)) { + // Temperature is missing, compute e_jet and T_jet from v_jet + kinetic_jet_e = + (efficiency_ * SQR(units.speed_of_light()) - 2 * SQR(kinetic_jet_velocity_)) / + (1 - efficiency_); const Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); const Real H_mass_fraction = 1.0 - He_mass_fraction; const Real mu = 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); const Real gam = pin->GetReal("hydro", "gamma"); const Real gm1 = (gam - 1.0); - kinetic_jet_temperature_ = (kinetic_jet_e*mu*units.atomic_mass_unit()*gm1)/units.k_boltzmann(); + kinetic_jet_temperature_ = + (kinetic_jet_e * mu * units.atomic_mass_unit() * gm1) / units.k_boltzmann(); } + // Verify all equations are satified. NAN's here should give failures + PARTHENON_REQUIRE( + kinetic_jet_velocity_ - fabs(sqrt(2 * (efficiency_ * SQR(units.speed_of_light()) - + (1 - efficiency_) * kinetic_jet_e))) < + 10 * std::numeric_limits::epsilon(), + "Specified kinetic jet velocity and temperature are incompatible with mass to " + "energy conversion efficiency. Choose either velocity or temperature."); - //Verify all equations are satified. NAN's here should give failures - PARTHENON_REQUIRE( kinetic_jet_velocity_ - - fabs( sqrt( 2*(efficiency_*SQR(units.speed_of_light()) - ( 1 - efficiency_)*kinetic_jet_e))) < 10*std::numeric_limits::epsilon(), - "Specified kinetic jet velocity and temperature are incompatible with mass to energy conversion efficiency. Choose either velocity or temperature."); - - PARTHENON_REQUIRE( kinetic_jet_velocity_ <= units.speed_of_light()*sqrt(2*efficiency_), - "Kinetic jet velocity implies negative temperature of the jet"); + PARTHENON_REQUIRE(kinetic_jet_velocity_ <= + units.speed_of_light() * sqrt(2 * efficiency_), + "Kinetic jet velocity implies negative temperature of the jet"); - PARTHENON_REQUIRE( kinetic_jet_e <= SQR(units.speed_of_light())*efficiency_/( 1- efficiency_), - "Kinetic jet temperature implies negative kinetic energy of the jet"); + PARTHENON_REQUIRE(kinetic_jet_e <= + SQR(units.speed_of_light()) * efficiency_ / (1 - efficiency_), + "Kinetic jet temperature implies negative kinetic energy of the jet"); - PARTHENON_REQUIRE(kinetic_jet_velocity_ >= 0, "Kinetic jet velocity must be non-negative"); - PARTHENON_REQUIRE(kinetic_jet_temperature_ >= 0, "Kinetic jet temperature must be non-negative"); + PARTHENON_REQUIRE(kinetic_jet_velocity_ >= 0, + "Kinetic jet velocity must be non-negative"); + PARTHENON_REQUIRE(kinetic_jet_temperature_ >= 0, + "Kinetic jet temperature must be non-negative"); // Add user history output variable for AGN power auto hst_vars = hydro_pkg->Param(parthenon::hist_param_key); @@ -245,7 +261,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real kinetic_jet_offset = kinetic_jet_offset_; // Matches 1/2.*jet_density*jet_velocity*jet_velocity*beta_dt; - //const Real kinetic_feedback = + // const Real kinetic_feedback = // kinetic_fraction_ * power * kinetic_scaling_factor * beta_dt; // energy/volume // Amount of density to dump in each cell @@ -258,7 +274,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Amount of momentum density ( density * velocity) to dump in each cell const Real jet_momentum = jet_density * jet_velocity; - //Amount of total energy to dump in each cell + // Amount of total energy to dump in each cell const Real jet_feedback = kinetic_fraction_ * power * kinetic_scaling_factor * beta_dt; //////////////////////////////////////////////////////////////////////////////// @@ -295,12 +311,13 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, } // Kinetic Jet Feedback - if ( jet_density > 0) { + if (jet_density > 0) { // Get position in jet cylindrical coords Real r, cos_theta, sin_theta, h; jet_coords.SimCartToJetCylCoords(x, y, z, r, cos_theta, sin_theta, h); - if (r < kinetic_jet_radius && fabs(h) >= kinetic_jet_offset && fabs(h) <= kinetic_jet_offset + kinetic_jet_thickness) { + if (r < kinetic_jet_radius && fabs(h) >= kinetic_jet_offset && + fabs(h) <= kinetic_jet_offset + kinetic_jet_thickness) { // Cell falls inside jet deposition volume // Get the vector of the jet axis @@ -310,16 +327,17 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, const Real sign_jet = (h > 0) ? 1 : -1; // Above or below jet-disk - /////////////////////////////////////////////////////////////////// - // We add the kinetic jet with a fixed jet velocity and specific - // internal energy/temperature of the added gas. The density, - // momentum, and total energy added depend on the triggered power. - /////////////////////////////////////////////////////////////////// - - #ifdef DEBUG + /////////////////////////////////////////////////////////////////// + // We add the kinetic jet with a fixed jet velocity and specific + // internal energy/temperature of the added gas. The density, + // momentum, and total energy added depend on the triggered power. + /////////////////////////////////////////////////////////////////// + +#ifdef DEBUG eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); - const Real old_specific_internal_e = prim( IPR, k, j, i)/( prim(IDN, k, j, i) * (eos.GetGamma() - 1.) ); - #endif + const Real old_specific_internal_e = + prim(IPR, k, j, i) / (prim(IDN, k, j, i) * (eos.GetGamma() - 1.)); +#endif cons(IDN, k, j, i) += jet_density; cons(IM1, k, j, i) += jet_momentum * sign_jet * jet_axis_x; @@ -327,13 +345,15 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, cons(IM3, k, j, i) += jet_momentum * sign_jet * jet_axis_z; cons(IEN, k, j, i) += jet_feedback; - #ifdef DEBUG +#ifdef DEBUG eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); - const Real new_specific_internal_e = prim( IPR, k, j, i)/( prim(IDN, k, j, i) * (eos.GetGamma() - 1.) ); - PARTHENON_DEBUG_REQUIRE( new_specific_internal_e > jet_specific_internal_e || new_specific_internal_e > old_specific_internal_e, + const Real new_specific_internal_e = + prim(IPR, k, j, i) / (prim(IDN, k, j, i) * (eos.GetGamma() - 1.)); + PARTHENON_DEBUG_REQUIRE( + new_specific_internal_e > jet_specific_internal_e || + new_specific_internal_e > old_specific_internal_e, "Kinetic injection leads to temperature below jet and existing gas"); - #endif - +#endif } } eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); diff --git a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py index 4d728e1d..02d7d88b 100644 --- a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py +++ b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py @@ -100,9 +100,9 @@ def __init__(self): self.bondi_beta = 2 self.bondi_n0 = 0.05 * (self.uniform_gas_rho / self.mean_molecular_mass) - #Discrepency expected due to volume integration of sphere with cubic - #cells, difference in integration order between simulation and - #determined densities and pressures here + # Discrepency expected due to volume integration of sphere with cubic + # cells, difference in integration order between simulation and + # determined densities and pressures here self.norm_tol = 1e-2 self.linf_accretion_rate_tol = 1e-2 @@ -240,7 +240,6 @@ def Analyse(self, parameters): accretion_volume = 4.0 / 3.0 * np.pi * self.accretion_radius**3 - ################################################################# # Integrate the accretion rate, density, and pressure at the same # times used by the simulation @@ -248,7 +247,7 @@ def Analyse(self, parameters): for i in range(n_times): dt = sim_dts[i] - #Compute accretion rate from integrated density + # Compute accretion rate from integrated density if triggering_mode == "COLD_GAS": # Temperature should stay fixed below cold gas threshold accretion_rate = ( @@ -277,7 +276,7 @@ def Analyse(self, parameters): * analytic_pressure[i] / analytic_density[i] ) - v2 = (self.uniform_gas_M/analytic_density[i])**2 + v2 = (self.uniform_gas_M / analytic_density[i]) ** 2 accretion_rate = ( alpha * ( @@ -296,12 +295,18 @@ def Analyse(self, parameters): accretion_rate_density = accretion_rate / accretion_volume - # From computed accretion rate, determine the new density and pressure + # From computed accretion rate, determine the new density and pressure analytic_density[i + 1] = ( analytic_density[i] - accretion_rate_density * dt ).in_units(analytic_density.units) - analytic_pressure[i + 1] = ( (self.adiabatic_index - 1.0) *(self.uniform_gas_energy_density - 0.5*analytic_density[i+1]*self.uniform_gas_vel**2) ).in_units(analytic_pressure.units) + analytic_pressure[i + 1] = ( + (self.adiabatic_index - 1.0) + * ( + self.uniform_gas_energy_density + - 0.5 * analytic_density[i + 1] * self.uniform_gas_vel**2 + ) + ).in_units(analytic_pressure.units) analytic_accretion_rate[i] = accretion_rate.in_units( analytic_accretion_rate.units @@ -312,7 +317,7 @@ def Analyse(self, parameters): (analytic_accretion_rate - sim_accretion_rate) / analytic_accretion_rate ) - #import ipdb;ipdb.set_trace() + # import ipdb;ipdb.set_trace() if np.max(accretion_rate_err) > self.linf_accretion_rate_tol: analyze_status = False @@ -328,7 +333,7 @@ def Analyse(self, parameters): final_Mx = self.uniform_gas_Mx final_My = self.uniform_gas_My final_Mz = self.uniform_gas_Mz - final_energy_density = self.uniform_gas_energy_density + final_energy_density = self.uniform_gas_energy_density def accretion_mask(Z, Y, X, inner_state, outer_state): pos_cart = unyt.unyt_array((X, Y, Z), "code_length") @@ -391,18 +396,18 @@ def accretion_mask(Z, Y, X, inner_state, outer_state): ) .in_units("code_mass/code_length**3") .v, - "MomentumDensity1": lambda Z, Y, X, time: self.uniform_gas_Mx - .in_units("code_mass*code_length**-2*code_time**-1") - .v, - "MomentumDensity2": lambda Z, Y, X, time: self.uniform_gas_My - .in_units("code_mass*code_length**-2*code_time**-1") - .v, - "MomentumDensity3": lambda Z, Y, X, time: self.uniform_gas_Mz - .in_units("code_mass*code_length**-2*code_time**-1") - .v, - "TotalEnergyDensity": lambda Z, Y, X, time: self.uniform_gas_energy_density - .in_units("code_mass*code_length**-1*code_time**-2") - .v, + "MomentumDensity1": lambda Z, Y, X, time: self.uniform_gas_Mx.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "MomentumDensity2": lambda Z, Y, X, time: self.uniform_gas_My.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "MomentumDensity3": lambda Z, Y, X, time: self.uniform_gas_Mz.in_units( + "code_mass*code_length**-2*code_time**-1" + ).v, + "TotalEnergyDensity": lambda Z, Y, X, time: self.uniform_gas_energy_density.in_units( + "code_mass*code_length**-1*code_time**-2" + ).v, "Pressure": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_pres, self.uniform_gas_pres ) diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index ccf41dd3..0caec690 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -121,14 +121,17 @@ def __init__(self): self.fixed_power = unyt.unyt_quantity(2e46, "erg/s") self.agn_thermal_radius = unyt.unyt_quantity(100, "kpc") self.efficiency = 1.0e-3 - self.jet_temperature = unyt.unyt_quantity(1e7,"K") + self.jet_temperature = unyt.unyt_quantity(1e7, "K") self.jet_radius = unyt.unyt_quantity(50, "kpc") self.jet_thickness = unyt.unyt_quantity(50, "kpc") self.jet_offset = unyt.unyt_quantity(10, "kpc") - - mu= 1./(3./4.*self.He_mass_fraction + (1-self.He_mass_fraction)*2) - self.jet_internal_e = self.jet_temperature*unyt.boltzmann_constant/(mu*unyt.amu*(self.adiabatic_index -1.)) + mu = 1.0 / (3.0 / 4.0 * self.He_mass_fraction + (1 - self.He_mass_fraction) * 2) + self.jet_internal_e = ( + self.jet_temperature + * unyt.boltzmann_constant + / (mu * unyt.amu * (self.adiabatic_index - 1.0)) + ) self.norm_tol = 1e-3 @@ -270,8 +273,18 @@ def Analyse(self, parameters): / (2 * np.pi * self.jet_radius**2 * self.jet_thickness) ) - jet_velocity = np.sqrt(2 * ( self.efficiency * unyt.c_cgs**2 - (1-self.efficiency)*self.jet_internal_e)) - jet_feedback = self.fixed_power*agn_kinetic_fraction/ (2 * np.pi * self.jet_radius**2 * self.jet_thickness) + jet_velocity = np.sqrt( + 2 + * ( + self.efficiency * unyt.c_cgs**2 + - (1 - self.efficiency) * self.jet_internal_e + ) + ) + jet_feedback = ( + self.fixed_power + * agn_kinetic_fraction + / (2 * np.pi * self.jet_radius**2 * self.jet_thickness) + ) def kinetic_feedback(Z, Y, X, time): if not hasattr(time, "units"): @@ -332,17 +345,17 @@ def kinetic_feedback(Z, Y, X, time): * jet_coords.jet_n[2] ) - #Note: Final density should be correct by thermal mass injected - #final_density = (time*jet_density + self.uniform_gas_rho) - #final_velocity = (time*jet_density*jet_velocity)/( final_density) - #dKE = inside_jet * 0.5 * final_density * final_velocity**2 - #dTE = inside_jet * time * jet_density * self.uniform_gas_pres / (self.uniform_gas_rho*(self.adiabatic_index - 1.0)) - dE = jet_feedback*time*inside_jet + # Note: Final density should be correct by thermal mass injected + # final_density = (time*jet_density + self.uniform_gas_rho) + # final_velocity = (time*jet_density*jet_velocity)/( final_density) + # dKE = inside_jet * 0.5 * final_density * final_velocity**2 + # dTE = inside_jet * time * jet_density * self.uniform_gas_pres / (self.uniform_gas_rho*(self.adiabatic_index - 1.0)) + dE = jet_feedback * time * inside_jet # DELETEME - #print(dKE.max().in_units("code_mass*code_length**-1*code_time**-2"), + # print(dKE.max().in_units("code_mass*code_length**-1*code_time**-2"), # dTE.max().in_units("code_mass*code_length**-1*code_time**-2")) - #print(dE.max()/( + # print(dE.max()/( # time* agn_kinetic_fraction*self.fixed_power/(2*np.pi*self.jet_radius**2*self.jet_thickness))) return drho, dMx, dMy, dMz, dE From 57660d380cbed2ca4464e61e0453a56a97c34513 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 25 Apr 2023 10:59:44 -0500 Subject: [PATCH 81/95] Updated documentation for new jet parameters --- docs/cluster.md | 81 ++++++++++++++------- inputs/cluster/generate_cluster_input.ipynb | 12 +-- inputs/cluster/my_cluster.input | 6 +- 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/docs/cluster.md b/docs/cluster.md index 61f16146..9fb6f20a 100644 --- a/docs/cluster.md +++ b/docs/cluster.md @@ -223,9 +223,11 @@ cell within the accretion region changes by ``` new_cell_mass = cell_mass - cell_mass/total_mass*mdot*dt; ``` -where `total_mass` is the total mass within the accretion zone. This mass is -removed from the conserved variables such that the velocity and temperature of -the cell remains the same. Momentum and energy density thus will also change. +where `total_mass` is the total mass within the accretion zone. The accreted +mass is removed from the gas which momentum density and energy density +unchanged. Thus velocities and temperatures will increase where mass is +removed. + With COLD_GAS accretion, the accretion rate becomes the total mass within the accretion zone equal to or below a defined cold temperature threshold divided by a defined accretion @@ -241,8 +243,7 @@ timescale. E.g. for each cell in the accretion zone with cold gas new_cell_mass = cell_mass - cell_mass/cold_t_acc*dt; ``` As with the Bondi-like accretion prescriptions, this mass is removed such that -the velocity and temperature of the cell remains the same. Momentum and energy -density thus will also change. +the momentum and energy densities are unchanged. ## AGN Feedback @@ -254,10 +255,11 @@ fixed_power = 0.0 efficiency = 0.001 ``` Where and `mdot` calculated from AGN triggering will lead to an an AGN feedback -power of `agn_power = efficiency*mdot*c**2`. The fixed power and triggered -power are not mutually exclusive; if both `fixed_power` is defined and -triggering is enabled with a non-zero `efficiency`, then the `fixed_power` will -be added to the triggered AGN power. +power of `agn_power = efficiency*mdot*c**2`. The parameter `efficiency` is +specifically the AGN's effiency converting in-falling mass into energy in the +jet. The fixed power and triggered power are not mutually exclusive; if both +`fixed_power` is defined and triggering is enabled with a non-zero +`efficiency`, then the `fixed_power` will be added to the triggered AGN power. AGN feedback can be injected via any combination of an injected magnetic tower, @@ -280,16 +282,24 @@ Mass is also injected into the sphere at a flat density rate with the existing velocity and temperature to match the accreted mass proportioned to thermal feedback, e.g. ``` -thermal_injected_mass = mdot * normalized_thermal_fraction; +thermal_injected_mass = mdot * (1 - efficiency) * normalized_thermal_fraction; ``` -Kinetic feedback is deposited into cylinder along the axis of the jet within a -defined radius and height above and below the plane of the AGN disk. +Kinetic feedback is deposited into two disks along the axis of the jet within a +defined radius, thickness of each disk, and an offset above and below the plane +of the AGN disk where each disk begins. ``` kinetic_jet_radius = 0.0005 -kinetic_jet_height = 0.0005 +kinetic_jet_thickness = 0.0005 +kinetic_jet_offset = 0.0005 ``` +Along the axis of the jet, kinetic energy will be deposited as far away as +`kinetic_jet_offset+kinetic_jet_thickness` in either direction. With a z-axis +aligned jet, `kinetic_jet_thickness` should be a multiple of the deposition +zone grid size, otherwise feedback will be lost due to systematic integration +error. + The axis of the jet can be set to precess with ``` @@ -301,27 +311,42 @@ at defined precession angle off of the z-axis (`jet_theta`), an initial azimuthal angle (`jet_phi0`), and an rate of azimuthal precession (`jet_phi_dot`). -Kinetic jet energy is injected at a flat power density within the cynlinder of -injection as purely kinetic energy, following [Meece -2017](doi.org/10.1086/501499). The injected mass will match the proportioned -kinetic feedback, e.g. +Kinetic jet feedback is injected is injected as if disk of fixed temperature +and velocity and changing density to match the AGN triggering rate were added +to the existing ambient gas. Either or both the jet temperature $T_{jet}$ and +velocity $v_{jet}$ can be set via ``` -kinetic_injected_mass = mdot * normalized_kinetic_fraction; + +#kinetic_jet_velocity = 13.695710297774411 # code_length/code_time +kinetic_jet_temperature = 1e7 # K ``` -and the injected momentum will total the injected kinetic feedback energy. Gas -energy desnity will remain unchanged. As a result, the injected mass density rate will be - +However, $T_{jet}$ and $v_{jet}$ must be non-negative and fulfill $$ -\dot{\rho} = \frac{\dot{M} f_{kinetic}}{2 \pi h_{jet} r_{jet}^2} +v_{jet} = \sqrt{ 2 \left ( \epsilon c^2 - (1 - \epsilon) \frac{k_B T_{jet}}{ \mu m_h \left( \gamma - 1 \right} \right ) } $$ - -and the velocity of the injected gas will be - +to ensure that the sum of rest mass energy, thermal energy, and kinetic energy of the new gas sums to $\dot{M} c^2$. Note that these equations places limits on $T_{jet}$ and $v_{jet}$, specifically $$ -c\sqrt{ 2 \epsilon } +v_{jet} \leq c \sqrt{ 2 \epsilon } \qquad \text{and} \qquad \frac{k_B T_{jet}}{ \mu m_h \left( \gamma - 1 \right} \leq c^2 \frac{ \epsilon}{1 - \epsilon} $$ +If the above equations are not satified then an exception will be thrown at +initialization. If neither $T_{jet}$ nor $v_{jet}$ are specified, then +$v_{jet}$ will be computed assuming $T_{jet}=0$ and a warning will be given +that the temperature of the jet is assumed to be 0 K. -where $\epsilon$ is the `efficiency` parameter described earlier in this section. +The total mass injected with kinetic jet feedback at each time step is +``` +kinetic_injected_mass = mdot * (1 - efficiency) * normalized_kinetic_fraction; +``` +In each cell the added density, momentum, and energy are +``` +kinetic_injected_density = kinetic_injected_mass/(2*kinetic_jet_thickness*pi*kinetic_jet_radius**2) +kinetic_injected_momentum_density = kinetc_injected_density*kinetic_jet_velocity**2 +kinetic_injected_energy_density = mdot*efficiency*normalized_kinetic_fraction/(2*kinetic_jet_thickness*pi*kinetic_jet_radius**2 +``` +Note that this only guarentees a fixed change in momentum density and total +energy density; changes in kinetic energy density will depend on the velocity +of the ambient gas. Temperature will also change but should always increase +with kinetic jet feedback. Magnetic feedback is injected following ([Hui 2006](doi.org/10.1086/501499)) where the injected magnetic field follows @@ -361,7 +386,7 @@ $$ where $\dot{\rho}_B$ is set to $$ -\dot{\rho}_B = \frac{3 \pi}{2} \frac{\dot{M} f_{magnetic}}{\ell^3} +\dot{\rho}_B = \frac{3 \pi}{2} \frac{\dot{M} \left ( 1 - \epsilon \right ) f_{magnetic}}{\ell^3} $$ so that the total mass injected matches the accreted mass propotioned to magnetic feedback. diff --git a/inputs/cluster/generate_cluster_input.ipynb b/inputs/cluster/generate_cluster_input.ipynb index 4b4ae51e..fc148696 100644 --- a/inputs/cluster/generate_cluster_input.ipynb +++ b/inputs/cluster/generate_cluster_input.ipynb @@ -209,7 +209,9 @@ "\n", "# Kinetic jet feedback parameters\n", "kinetic_jet_radius = {unyt.unyt_quantity(0.5,\"kpc\").in_units(\"code_length\").v}\n", - "kinetic_jet_height = {unyt.unyt_quantity(0.5,\"kpc\").in_units(\"code_length\").v}\n", + "kinetic_jet_thickness = {unyt.unyt_quantity(0.5,\"kpc\").in_units(\"code_length\").v}\n", + "kinetic_jet_offset = {unyt.unyt_quantity(0.5,\"kpc\").in_units(\"code_length\").v}\n", + "kinetic_jet_temperature = {unyt.unyt_quantity(1e6,\"K\").in_units(\"K\").v}\n", "\n", "\n", "alpha = 20\n", @@ -273,7 +275,7 @@ "time_text=f\"\"\"\n", "\n", "cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number\n", - "tlim = {unyt.unyt_quantity(0.1,\"Myr\").in_units(\"code_time\").v} # time limit\n", + "tlim = {unyt.unyt_quantity(10,\"Myr\").in_units(\"code_time\").v} # time limit\n", "integrator = vl2 # time integration algorithm\n", "\"\"\"" ] @@ -550,9 +552,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python [conda env:.conda-py39]", "language": "python", - "name": "python3" + "name": "conda-env-.conda-py39-py" }, "language_info": { "codemirror_mode": { @@ -564,7 +566,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.10" + "version": "3.9.13" } }, "nbformat": 4, diff --git a/inputs/cluster/my_cluster.input b/inputs/cluster/my_cluster.input index 4a8b83ac..3823f7b8 100644 --- a/inputs/cluster/my_cluster.input +++ b/inputs/cluster/my_cluster.input @@ -24,7 +24,7 @@ use_final_label = false cfl = 0.3 # The Courant, Friedrichs, & Lewy (CFL) Number -tlim = 0.0001 # time limit +tlim = 0.01 # time limit integrator = vl2 # time integration algorithm @@ -182,7 +182,9 @@ thermal_radius = 0.0005 # Kinetic jet feedback parameters kinetic_jet_radius = 0.0005 -kinetic_jet_height = 0.0005 +kinetic_jet_thickness = 0.0005 +kinetic_jet_offset = 0.0005 +kinetic_jet_temperature = 1000000.0 alpha = 20 From 115c94ac3225f30d6b13d64edd2303bb0bb137c5 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 2 May 2023 09:09:46 -0500 Subject: [PATCH 82/95] Fixed compute jet temp from jet velocity --- src/pgen/cluster/agn_feedback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 806e3c02..7f45157c 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -111,7 +111,7 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, } else if (std::isnan(kinetic_jet_temperature_)) { // Temperature is missing, compute e_jet and T_jet from v_jet kinetic_jet_e = - (efficiency_ * SQR(units.speed_of_light()) - 2 * SQR(kinetic_jet_velocity_)) / + (efficiency_ * SQR(units.speed_of_light()) - 0.5 * SQR(kinetic_jet_velocity_)) / (1 - efficiency_); const Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); const Real H_mass_fraction = 1.0 - He_mass_fraction; From ad9a5521cea20eb163f1b8b68bd36fdbc97136e7 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 2 May 2023 10:42:01 -0500 Subject: [PATCH 83/95] Reverted triggering changes --- src/pgen/cluster/agn_triggering.cpp | 9 +- .../cluster_agn_triggering.py | 91 +++++++++---------- 2 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 906352fb..610bc4ac 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -171,9 +171,9 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, const Real cell_delta_rho = -prim(IDN, k, j, i) / cold_t_acc * dt; if (remove_accreted_mass) { - cons(IDN, k, j, i) += cell_delta_rho; - - //// Update the Primitives + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), + k, j, i); + // Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); } } @@ -291,7 +291,8 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData const Real cell_delta_rho = -prim(IDN, k, j, i) / total_mass * accretion_rate * dt; - cons(IDN, k, j, i) += cell_delta_rho; + AddDensityToConsAtFixedVelTemp(cell_delta_rho, cons, prim, eos.GetGamma(), k, j, + i); // Update the Primitives eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); diff --git a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py index 02d7d88b..76b74789 100644 --- a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py +++ b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py @@ -79,11 +79,6 @@ def __init__(self): + self.uniform_gas_uy**2 + self.uniform_gas_uz**2 ) - self.uniform_gas_M = np.sqrt( - self.uniform_gas_Mx**2 - + self.uniform_gas_My**2 - + self.uniform_gas_Mz**2 - ) self.uniform_gas_temp = ( self.mu * self.m_u / self.k_b * self.uniform_gas_pres / self.uniform_gas_rho @@ -94,17 +89,14 @@ def __init__(self): # Triggering parameters self.accretion_radius = unyt.unyt_quantity(20, "kpc") - self.cold_temp_thresh = self.uniform_gas_temp * 1e1 + self.cold_temp_thresh = self.uniform_gas_temp * 1.01 self.cold_t_acc = unyt.unyt_quantity(100, "Myr") self.bondi_alpha = 100 self.bondi_beta = 2 self.bondi_n0 = 0.05 * (self.uniform_gas_rho / self.mean_molecular_mass) - # Discrepency expected due to volume integration of sphere with cubic - # cells, difference in integration order between simulation and - # determined densities and pressures here - self.norm_tol = 1e-2 - self.linf_accretion_rate_tol = 1e-2 + self.norm_tol = 1e-3 + self.linf_accretion_rate_tol = 1e-3 self.step_params_list = ["COLD_GAS", "BOOSTED_BONDI", "BOOTH_SCHAYE"] self.steps = len(self.step_params_list) @@ -240,14 +232,9 @@ def Analyse(self, parameters): accretion_volume = 4.0 / 3.0 * np.pi * self.accretion_radius**3 - ################################################################# - # Integrate the accretion rate, density, and pressure at the same - # times used by the simulation - ################################################################# for i in range(n_times): dt = sim_dts[i] - # Compute accretion rate from integrated density if triggering_mode == "COLD_GAS": # Temperature should stay fixed below cold gas threshold accretion_rate = ( @@ -273,10 +260,9 @@ def Analyse(self, parameters): cs2 = ( self.adiabatic_index - * analytic_pressure[i] - / analytic_density[i] + * self.uniform_gas_pres + / self.uniform_gas_rho ) - v2 = (self.uniform_gas_M / analytic_density[i]) ** 2 accretion_rate = ( alpha * ( @@ -286,7 +272,7 @@ def Analyse(self, parameters): * self.M_smbh**2 * analytic_density[i] ) - / (v2 + cs2) ** (3.0 / 2.0) + / (self.uniform_gas_vel**2 + cs2) ** (3.0 / 2.0) ) else: raise Exception( @@ -295,19 +281,16 @@ def Analyse(self, parameters): accretion_rate_density = accretion_rate / accretion_volume - # From computed accretion rate, determine the new density and pressure analytic_density[i + 1] = ( analytic_density[i] - accretion_rate_density * dt ).in_units(analytic_density.units) - analytic_pressure[i + 1] = ( - (self.adiabatic_index - 1.0) - * ( - self.uniform_gas_energy_density - - 0.5 * analytic_density[i + 1] * self.uniform_gas_vel**2 - ) + analytic_pressure[i] + - accretion_rate_density + * dt + * analytic_pressure[i] + / analytic_density[i] ).in_units(analytic_pressure.units) - analytic_accretion_rate[i] = accretion_rate.in_units( analytic_accretion_rate.units ) @@ -317,8 +300,6 @@ def Analyse(self, parameters): (analytic_accretion_rate - sim_accretion_rate) / analytic_accretion_rate ) - # import ipdb;ipdb.set_trace() - if np.max(accretion_rate_err) > self.linf_accretion_rate_tol: analyze_status = False print( @@ -330,10 +311,14 @@ def Analyse(self, parameters): final_rho = analytic_density[-1] final_pres = analytic_pressure[-1] - final_Mx = self.uniform_gas_Mx - final_My = self.uniform_gas_My - final_Mz = self.uniform_gas_Mz - final_energy_density = self.uniform_gas_energy_density + final_Mx = self.uniform_gas_ux * final_rho + final_My = self.uniform_gas_uy * final_rho + final_Mz = self.uniform_gas_uz * final_rho + final_energy_density = 1.0 / 2.0 * final_rho * ( + self.uniform_gas_ux**2 + + self.uniform_gas_uy**2 + + self.uniform_gas_uz**2 + ) + final_pres / (self.adiabatic_index - 1.0) def accretion_mask(Z, Y, X, inner_state, outer_state): pos_cart = unyt.unyt_array((X, Y, Z), "code_length") @@ -396,18 +381,32 @@ def accretion_mask(Z, Y, X, inner_state, outer_state): ) .in_units("code_mass/code_length**3") .v, - "MomentumDensity1": lambda Z, Y, X, time: self.uniform_gas_Mx.in_units( - "code_mass*code_length**-2*code_time**-1" - ).v, - "MomentumDensity2": lambda Z, Y, X, time: self.uniform_gas_My.in_units( - "code_mass*code_length**-2*code_time**-1" - ).v, - "MomentumDensity3": lambda Z, Y, X, time: self.uniform_gas_Mz.in_units( - "code_mass*code_length**-2*code_time**-1" - ).v, - "TotalEnergyDensity": lambda Z, Y, X, time: self.uniform_gas_energy_density.in_units( - "code_mass*code_length**-1*code_time**-2" - ).v, + "MomentumDensity1": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_Mx, self.uniform_gas_Mx + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "MomentumDensity2": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_My, self.uniform_gas_My + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "MomentumDensity3": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_Mz, self.uniform_gas_Mz + ) + .in_units("code_mass*code_length**-2*code_time**-1") + .v, + "TotalEnergyDensity": lambda Z, Y, X, time: accretion_mask( + Z, Y, X, final_energy_density, self.uniform_gas_energy_density + ) + .in_units("code_mass*code_length**-1*code_time**-2") + .v, + "Velocity1": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_ux.in_units("code_length*code_time**-1").v, + "Velocity2": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_uy.in_units("code_length*code_time**-1").v, + "Velocity3": lambda Z, Y, X, time: np.ones_like(Z) + * self.uniform_gas_uz.in_units("code_length*code_time**-1").v, "Pressure": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_pres, self.uniform_gas_pres ) From b65b8d9a7189797c3869a9a1bcd8f5df6c62d048 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Tue, 2 May 2023 12:08:55 -0500 Subject: [PATCH 84/95] Fixed jet temperature and velocity check --- src/pgen/cluster/agn_feedback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 7f45157c..585a003c 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -124,7 +124,7 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, // Verify all equations are satified. NAN's here should give failures PARTHENON_REQUIRE( - kinetic_jet_velocity_ - fabs(sqrt(2 * (efficiency_ * SQR(units.speed_of_light()) - + fabs(kinetic_jet_velocity_ - sqrt(2 * (efficiency_ * SQR(units.speed_of_light()) - (1 - efficiency_) * kinetic_jet_e))) < 10 * std::numeric_limits::epsilon(), "Specified kinetic jet velocity and temperature are incompatible with mass to " From 15cc60d787ac0328deead00f146945b8a63dc641 Mon Sep 17 00:00:00 2001 From: Philipp Grete Date: Fri, 5 May 2023 13:37:42 +0200 Subject: [PATCH 85/95] Fix typo --- src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp index 2b340402..9c88269d 100644 --- a/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp +++ b/src/pgen/cluster/hydrostatic_equilibrium_sphere.cpp @@ -47,7 +47,7 @@ HydrostaticEquilibriumSphere:: k_boltzmann_ = units.k_boltzmann(); mu_ = hydro_pkg->Param("mu"); - mu_e_ = hydro_pkg->Param("me_e"); + mu_e_ = hydro_pkg->Param("mu_e"); r_fix_ = pin->GetOrAddReal("problem/cluster/hydrostatic_equilibrium", "r_fix", 1953.9724519818478 * units.kpc()); From 8bdcd267380eb36772a3a70bca30820460aa280f Mon Sep 17 00:00:00 2001 From: Philipp Grete Date: Tue, 9 May 2023 13:00:34 +0200 Subject: [PATCH 86/95] Update var names --- .../cluster_agn_triggering.py | 36 +++++++++---------- .../cluster_hydro_agn_feedback.py | 20 +++++------ .../cluster_magnetic_tower.py | 4 +-- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py index 76b74789..49fdaafd 100644 --- a/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py +++ b/tst/regression/test_suites/cluster_agn_triggering/cluster_agn_triggering.py @@ -345,69 +345,69 @@ def accretion_mask(Z, Y, X, inner_state, outer_state): return False initial_analytic_components = { - "Density": lambda Z, Y, X, time: np.ones_like(Z) + "cons_density": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_rho.in_units("code_mass/code_length**3").v, - "MomentumDensity1": lambda Z, Y, X, time: np.ones_like(Z) + "cons_momentum_density_1": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_Mx.in_units( "code_mass*code_length**-2*code_time**-1" ).v, - "MomentumDensity2": lambda Z, Y, X, time: np.ones_like(Z) + "cons_momentum_density_2": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_My.in_units( "code_mass*code_length**-2*code_time**-1" ).v, - "MomentumDensity3": lambda Z, Y, X, time: np.ones_like(Z) + "cons_momentum_density_3": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_Mz.in_units( "code_mass*code_length**-2*code_time**-1" ).v, - "TotalEnergyDensity": lambda Z, Y, X, time: np.ones_like(Z) + "cons_total_energy_density": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_energy_density.in_units( "code_mass*code_length**-1*code_time**-2" ).v, - "Velocity1": lambda Z, Y, X, time: np.ones_like(Z) + "prim_velocity_1": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_ux.in_units("code_length*code_time**-1").v, - "Velocity2": lambda Z, Y, X, time: np.ones_like(Z) + "prim_velocity_2": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_uy.in_units("code_length*code_time**-1").v, - "Velocity3": lambda Z, Y, X, time: np.ones_like(Z) + "prim_velocity_3": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_uz.in_units("code_length*code_time**-1").v, - "Pressure": lambda Z, Y, X, time: np.ones_like(Z) + "prim_pressure": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_pres.in_units( "code_mass/(code_length*code_time**2)" ).v, } final_analytic_components = { - "Density": lambda Z, Y, X, time: accretion_mask( + "cons_density": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_rho, self.uniform_gas_rho ) .in_units("code_mass/code_length**3") .v, - "MomentumDensity1": lambda Z, Y, X, time: accretion_mask( + "cons_momentum_density_1": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_Mx, self.uniform_gas_Mx ) .in_units("code_mass*code_length**-2*code_time**-1") .v, - "MomentumDensity2": lambda Z, Y, X, time: accretion_mask( + "cons_momentum_density_2": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_My, self.uniform_gas_My ) .in_units("code_mass*code_length**-2*code_time**-1") .v, - "MomentumDensity3": lambda Z, Y, X, time: accretion_mask( + "cons_momentum_density_3": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_Mz, self.uniform_gas_Mz ) .in_units("code_mass*code_length**-2*code_time**-1") .v, - "TotalEnergyDensity": lambda Z, Y, X, time: accretion_mask( + "cons_total_energy_density": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_energy_density, self.uniform_gas_energy_density ) .in_units("code_mass*code_length**-1*code_time**-2") .v, - "Velocity1": lambda Z, Y, X, time: np.ones_like(Z) + "prim_velocity_1": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_ux.in_units("code_length*code_time**-1").v, - "Velocity2": lambda Z, Y, X, time: np.ones_like(Z) + "prim_velocity_2": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_uy.in_units("code_length*code_time**-1").v, - "Velocity3": lambda Z, Y, X, time: np.ones_like(Z) + "prim_velocity_3": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_uz.in_units("code_length*code_time**-1").v, - "Pressure": lambda Z, Y, X, time: accretion_mask( + "prim_pressure": lambda Z, Y, X, time: accretion_mask( Z, Y, X, final_pres, self.uniform_gas_pres ) .in_units("code_mass/(code_length*code_time**2)") diff --git a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py index 0caec690..29dc4d69 100644 --- a/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py +++ b/tst/regression/test_suites/cluster_hydro_agn_feedback/cluster_hydro_agn_feedback.py @@ -420,48 +420,48 @@ def agn_feedback(Z, Y, X, dt): return False initial_analytic_components = { - "Density": lambda Z, Y, X, time: np.ones_like(Z) + "cons_density": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_rho.in_units("code_mass/code_length**3").v, - "MomentumDensity1": lambda Z, Y, X, time: np.ones_like(Z) + "cons_momentum_density_1": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_Mx.in_units( "code_mass*code_length**-2*code_time**-1" ).v, - "MomentumDensity2": lambda Z, Y, X, time: np.ones_like(Z) + "cons_momentum_density_2": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_My.in_units( "code_mass*code_length**-2*code_time**-1" ).v, - "MomentumDensity3": lambda Z, Y, X, time: np.ones_like(Z) + "cons_momentum_density_3": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_Mz.in_units( "code_mass*code_length**-2*code_time**-1" ).v, - "TotalEnergyDensity": lambda Z, Y, X, time: np.ones_like(Z) + "cons_total_energy_density": lambda Z, Y, X, time: np.ones_like(Z) * self.uniform_gas_energy_density.in_units( "code_mass*code_length**-1*code_time**-2" ).v, } final_analytic_components = { - "Density": lambda Z, Y, X, time: ( + "cons_density": lambda Z, Y, X, time: ( self.uniform_gas_rho + agn_feedback(Z, Y, X, time)[0] ) .in_units("code_mass/code_length**3") .v, - "MomentumDensity1": lambda Z, Y, X, time: ( + "cons_momentum_density_1": lambda Z, Y, X, time: ( self.uniform_gas_Mx + agn_feedback(Z, Y, X, time)[1] ) .in_units("code_mass*code_length**-2*code_time**-1") .v, - "MomentumDensity2": lambda Z, Y, X, time: ( + "cons_momentum_density_2": lambda Z, Y, X, time: ( self.uniform_gas_My + agn_feedback(Z, Y, X, time)[2] ) .in_units("code_mass*code_length**-2*code_time**-1") .v, - "MomentumDensity3": lambda Z, Y, X, time: ( + "cons_momentum_density_3": lambda Z, Y, X, time: ( self.uniform_gas_Mz + agn_feedback(Z, Y, X, time)[3] ) .in_units("code_mass*code_length**-2*code_time**-1") .v, - "TotalEnergyDensity": lambda Z, Y, X, time: ( + "cons_total_energy_density": lambda Z, Y, X, time: ( self.uniform_gas_energy_density + agn_feedback(Z, Y, X, time)[4] ) .in_units("code_mass*code_length**-1*code_time**-2") diff --git a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py index 2aad2e77..4f0bb97f 100644 --- a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py +++ b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py @@ -482,10 +482,10 @@ def B_scaled_linf_err(gold, test, B0): # Construct lambda functions for initial and final analytically # expected density and total energy density densities_analytic_components = { - "Density": lambda Z, Y, X, time: density_func(Z, Y, X, rho0) + "cons_density": lambda Z, Y, X, time: density_func(Z, Y, X, rho0) .in_units("code_mass*code_length**-3") .v, - "TotalEnergyDensity": lambda Z, Y, X, time: ( + "cons_total_energy_density": lambda Z, Y, X, time: ( internal_energy_density_func(Z, Y, X, rho0) + b_energy_func(Z, Y, X, B_field) ) From 9ec261b34e55ce0779adf4d40f6e35946e137ae1 Mon Sep 17 00:00:00 2001 From: Philipp Grete Date: Tue, 9 May 2023 14:07:05 +0200 Subject: [PATCH 87/95] Fix outdated magnetic field var names --- .../cluster_magnetic_tower.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py index 4f0bb97f..4da1eeb1 100644 --- a/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py +++ b/tst/regression/test_suites/cluster_magnetic_tower/cluster_magnetic_tower.py @@ -503,13 +503,19 @@ def B_scaled_linf_err(gold, test, B0): # Construct lambda functions for initial and final analytically expected magnetic fields field_analytic_components = { - "MagneticField1": lambda Z, Y, X, time: field_func(Z, Y, X, B_field) + "cons_magnetic_field_1": lambda Z, Y, X, time: field_func( + Z, Y, X, B_field + ) .in_units(magnetic_units)[0] .v, - "MagneticField2": lambda Z, Y, X, time: field_func(Z, Y, X, B_field) + "cons_magnetic_field_2": lambda Z, Y, X, time: field_func( + Z, Y, X, B_field + ) .in_units(magnetic_units)[1] .v, - "MagneticField3": lambda Z, Y, X, time: field_func(Z, Y, X, B_field) + "cons_magnetic_field_3": lambda Z, Y, X, time: field_func( + Z, Y, X, B_field + ) .in_units(magnetic_units)[2] .v, } @@ -559,7 +565,11 @@ def B_scaled_linf_err(gold, test, B0): B = unyt.unyt_array( list( phdf_file.GetComponents( - ["MagneticField1", "MagneticField2", "MagneticField3"], + [ + "cons_magnetic_field_1", + "cons_magnetic_field_2", + "cons_magnetic_field_3", + ], flatten=False, ).values() ), From bf69490242c5c2a1c1db38bcee959889d97409c7 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 15 May 2023 13:10:33 -0500 Subject: [PATCH 88/95] Fixed kinetic jet lower temp check when DEBUG --- src/pgen/cluster/agn_feedback.cpp | 23 +++++++++++++---------- src/pgen/cluster/agn_feedback.hpp | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 585a003c..9c7b8e97 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -73,8 +73,8 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, kinetic_jet_velocity_ = NAN; kinetic_jet_temperature_ = NAN; + kinetic_jet_e_ = NAN; - Real kinetic_jet_e; const auto units = hydro_pkg->Param("units"); @@ -92,7 +92,7 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, const Real mu = 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); const Real gam = pin->GetReal("hydro", "gamma"); const Real gm1 = (gam - 1.0); - kinetic_jet_e = units.k_boltzmann() * kinetic_jet_temperature_ / + kinetic_jet_e_ = units.k_boltzmann() * kinetic_jet_temperature_ / (mu * units.atomic_mass_unit() * gm1); } @@ -100,17 +100,17 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, // Both velocity and temperature are missing, assume 0K temperature kinetic_jet_velocity_ = units.speed_of_light() * sqrt(2 * (efficiency_)); kinetic_jet_temperature_ = 0; - kinetic_jet_e = 0; + kinetic_jet_e_ = 0; std::cout << "### WARNING Kinetic jet velocity nor temperature not specified. " "Assuming 0K temperature jet" << std::endl; } else if (std::isnan(kinetic_jet_velocity_)) { // Velocity is missing, compute it from e_jet kinetic_jet_velocity_ = sqrt(2 * (efficiency_ * SQR(units.speed_of_light()) - - (1.0 - efficiency_) * kinetic_jet_e)); + (1.0 - efficiency_) * kinetic_jet_e_)); } else if (std::isnan(kinetic_jet_temperature_)) { // Temperature is missing, compute e_jet and T_jet from v_jet - kinetic_jet_e = + kinetic_jet_e_ = (efficiency_ * SQR(units.speed_of_light()) - 0.5 * SQR(kinetic_jet_velocity_)) / (1 - efficiency_); const Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); @@ -119,13 +119,13 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, const Real gam = pin->GetReal("hydro", "gamma"); const Real gm1 = (gam - 1.0); kinetic_jet_temperature_ = - (kinetic_jet_e * mu * units.atomic_mass_unit() * gm1) / units.k_boltzmann(); + (kinetic_jet_e_ * mu * units.atomic_mass_unit() * gm1) / units.k_boltzmann(); } // Verify all equations are satified. NAN's here should give failures PARTHENON_REQUIRE( fabs(kinetic_jet_velocity_ - sqrt(2 * (efficiency_ * SQR(units.speed_of_light()) - - (1 - efficiency_) * kinetic_jet_e))) < + (1 - efficiency_) * kinetic_jet_e_))) < 10 * std::numeric_limits::epsilon(), "Specified kinetic jet velocity and temperature are incompatible with mass to " "energy conversion efficiency. Choose either velocity or temperature."); @@ -134,7 +134,7 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, units.speed_of_light() * sqrt(2 * efficiency_), "Kinetic jet velocity implies negative temperature of the jet"); - PARTHENON_REQUIRE(kinetic_jet_e <= + PARTHENON_REQUIRE(kinetic_jet_e_ <= SQR(units.speed_of_light()) * efficiency_ / (1 - efficiency_), "Kinetic jet temperature implies negative kinetic energy of the jet"); @@ -270,6 +270,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Velocity of added gas const Real jet_velocity = kinetic_jet_velocity_; + const Real jet_specific_internal_e = kinetic_jet_e_; // Amount of momentum density ( density * velocity) to dump in each cell const Real jet_momentum = jet_density * jet_velocity; @@ -333,7 +334,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // momentum, and total energy added depend on the triggered power. /////////////////////////////////////////////////////////////////// -#ifdef DEBUG +#ifndef NDEBUG eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); const Real old_specific_internal_e = prim(IPR, k, j, i) / (prim(IDN, k, j, i) * (eos.GetGamma() - 1.)); @@ -345,7 +346,7 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, cons(IM3, k, j, i) += jet_momentum * sign_jet * jet_axis_z; cons(IEN, k, j, i) += jet_feedback; -#ifdef DEBUG +#ifndef NDEBUG eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); const Real new_specific_internal_e = prim(IPR, k, j, i) / (prim(IDN, k, j, i) * (eos.GetGamma() - 1.)); @@ -357,6 +358,8 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, } } eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); + PARTHENON_DEBUG_REQUIRE( prim(IPR, k, j, i) > 0, + "Kinetic injection leads to negative pressure"); }); // Apply magnetic tower feedback diff --git a/src/pgen/cluster/agn_feedback.hpp b/src/pgen/cluster/agn_feedback.hpp index 97b8852f..1c88a61e 100644 --- a/src/pgen/cluster/agn_feedback.hpp +++ b/src/pgen/cluster/agn_feedback.hpp @@ -36,7 +36,7 @@ class AGNFeedback { // Kinetic Feedback Parameters const parthenon::Real kinetic_jet_radius_, kinetic_jet_thickness_, kinetic_jet_offset_; - parthenon::Real kinetic_jet_velocity_, kinetic_jet_temperature_; + parthenon::Real kinetic_jet_velocity_, kinetic_jet_temperature_, kinetic_jet_e_; const bool disabled_; From d376c63114243c020766f55a5ca92698198ff93c Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 15 May 2023 13:13:40 -0500 Subject: [PATCH 89/95] Use mbar_over_kb for kinetic jet temp --- src/pgen/cluster/agn_feedback.cpp | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 9c7b8e97..7818ca15 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -77,7 +77,10 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, const auto units = hydro_pkg->Param("units"); + auto mbar_gm1_over_kb = hydro_pkg->Param("mbar_over_kb") * (pin->GetReal("hydro", "gamma") - 1); + + // Get jet velocity and temperature/internal_e if in the sim parameters. These are NAN otherwise if (pin->DoesParameterExist("problem/cluster/agn_feedback", "kinetic_jet_velocity")) { kinetic_jet_velocity_ = pin->GetReal("problem/cluster/agn_feedback", "kinetic_jet_velocity"); @@ -87,13 +90,7 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, kinetic_jet_temperature_ = pin->GetReal("problem/cluster/agn_feedback", "kinetic_jet_temperature"); - const Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); - const Real H_mass_fraction = 1.0 - He_mass_fraction; - const Real mu = 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); - const Real gam = pin->GetReal("hydro", "gamma"); - const Real gm1 = (gam - 1.0); - kinetic_jet_e_ = units.k_boltzmann() * kinetic_jet_temperature_ / - (mu * units.atomic_mass_unit() * gm1); + kinetic_jet_e_ = kinetic_jet_temperature_ / mbar_gm1_over_kb; } if (std::isnan(kinetic_jet_velocity_) && std::isnan(kinetic_jet_temperature_)) { @@ -113,13 +110,7 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, kinetic_jet_e_ = (efficiency_ * SQR(units.speed_of_light()) - 0.5 * SQR(kinetic_jet_velocity_)) / (1 - efficiency_); - const Real He_mass_fraction = pin->GetReal("hydro", "He_mass_fraction"); - const Real H_mass_fraction = 1.0 - He_mass_fraction; - const Real mu = 1 / (He_mass_fraction * 3. / 4. + (1 - He_mass_fraction) * 2); - const Real gam = pin->GetReal("hydro", "gamma"); - const Real gm1 = (gam - 1.0); - kinetic_jet_temperature_ = - (kinetic_jet_e_ * mu * units.atomic_mass_unit() * gm1) / units.k_boltzmann(); + kinetic_jet_temperature_ = mbar_gm1_over_kb * kinetic_jet_e_; } // Verify all equations are satified. NAN's here should give failures @@ -270,7 +261,9 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, // Velocity of added gas const Real jet_velocity = kinetic_jet_velocity_; +#ifndef NDEBUG const Real jet_specific_internal_e = kinetic_jet_e_; +#endif // Amount of momentum density ( density * velocity) to dump in each cell const Real jet_momentum = jet_density * jet_velocity; From 1044288e1cb84c6b8238b4a6db7c679a37cd21cd Mon Sep 17 00:00:00 2001 From: par-hermes Date: Mon, 15 May 2023 19:18:34 +0000 Subject: [PATCH 90/95] cpp-py-formatter --- src/pgen/cluster/agn_feedback.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pgen/cluster/agn_feedback.cpp b/src/pgen/cluster/agn_feedback.cpp index 7818ca15..cd367150 100644 --- a/src/pgen/cluster/agn_feedback.cpp +++ b/src/pgen/cluster/agn_feedback.cpp @@ -75,12 +75,12 @@ AGNFeedback::AGNFeedback(parthenon::ParameterInput *pin, kinetic_jet_temperature_ = NAN; kinetic_jet_e_ = NAN; - const auto units = hydro_pkg->Param("units"); - auto mbar_gm1_over_kb = hydro_pkg->Param("mbar_over_kb") * (pin->GetReal("hydro", "gamma") - 1); - + auto mbar_gm1_over_kb = + hydro_pkg->Param("mbar_over_kb") * (pin->GetReal("hydro", "gamma") - 1); - // Get jet velocity and temperature/internal_e if in the sim parameters. These are NAN otherwise + // Get jet velocity and temperature/internal_e if in the sim parameters. These are NAN + // otherwise if (pin->DoesParameterExist("problem/cluster/agn_feedback", "kinetic_jet_velocity")) { kinetic_jet_velocity_ = pin->GetReal("problem/cluster/agn_feedback", "kinetic_jet_velocity"); @@ -351,8 +351,8 @@ void AGNFeedback::FeedbackSrcTerm(parthenon::MeshData *md, } } eos.ConsToPrim(cons, prim, nhydro, nscalars, k, j, i); - PARTHENON_DEBUG_REQUIRE( prim(IPR, k, j, i) > 0, - "Kinetic injection leads to negative pressure"); + PARTHENON_DEBUG_REQUIRE(prim(IPR, k, j, i) > 0, + "Kinetic injection leads to negative pressure"); }); // Apply magnetic tower feedback From 2906f85bca54c532fb79ddf287d7c512e845acb6 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 15 May 2023 14:29:20 -0500 Subject: [PATCH 91/95] Added header comments to cluster testing inputs --- inputs/cluster/agn_triggering.in | 5 ++++- inputs/cluster/cooling.in | 6 ++++-- inputs/cluster/hse.in | 5 ++++- inputs/cluster/hydro_agn_feedback.in | 5 ++++- inputs/cluster/magnetic_tower.in | 5 ++++- 5 files changed, 20 insertions(+), 6 deletions(-) diff --git a/inputs/cluster/agn_triggering.in b/inputs/cluster/agn_triggering.in index 0e0ac815..1ba04ec3 100644 --- a/inputs/cluster/agn_triggering.in +++ b/inputs/cluster/agn_triggering.in @@ -1,4 +1,7 @@ - +################################################################################ +# Input file for testing AGN triggering without fluid +# evolution +################################################################################ problem = AGN Triggering Test diff --git a/inputs/cluster/cooling.in b/inputs/cluster/cooling.in index 6ef75535..63f8dd20 100644 --- a/inputs/cluster/cooling.in +++ b/inputs/cluster/cooling.in @@ -1,6 +1,8 @@ - +################################################################################ +# Input file for testing tabular cooling +################################################################################ -problem = Galaxy Cluster Cooling Test +problem = Cooling Test problem_id = cluster # problem ID: basename of output filenames diff --git a/inputs/cluster/hse.in b/inputs/cluster/hse.in index b3862cf0..7bb5080d 100644 --- a/inputs/cluster/hse.in +++ b/inputs/cluster/hse.in @@ -1,4 +1,7 @@ - +################################################################################ +# Input file for testing hydrostatic equilbrium setup for galaxy cluster-like +# objects +################################################################################ problem = Galaxy Cluster Hydrostatic Equilibrium Test diff --git a/inputs/cluster/hydro_agn_feedback.in b/inputs/cluster/hydro_agn_feedback.in index 717e76b5..a909819a 100644 --- a/inputs/cluster/hydro_agn_feedback.in +++ b/inputs/cluster/hydro_agn_feedback.in @@ -1,4 +1,7 @@ - +################################################################################ +# Input file for testing kinetic and thermal AGN feedback without fluid +# evolution +################################################################################ problem = Hydro AGN Feedback Test diff --git a/inputs/cluster/magnetic_tower.in b/inputs/cluster/magnetic_tower.in index be5aa5e1..66564dca 100644 --- a/inputs/cluster/magnetic_tower.in +++ b/inputs/cluster/magnetic_tower.in @@ -1,4 +1,7 @@ - +################################################################################ +# Input file for testing magnetic tower AGN feedback without fluid +# evolution +################################################################################ problem = Magnetic Tower Test From 827c63639ec95260d381296a20998ee33a0b7e4d Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 15 May 2023 16:28:52 -0500 Subject: [PATCH 92/95] Can change device mem in cluster notebook --- inputs/cluster/generate_cluster_input.ipynb | 8 +++++--- src/pgen/cluster/jet_coords.hpp | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/inputs/cluster/generate_cluster_input.ipynb b/inputs/cluster/generate_cluster_input.ipynb index fc148696..449283f1 100644 --- a/inputs/cluster/generate_cluster_input.ipynb +++ b/inputs/cluster/generate_cluster_input.ipynb @@ -330,7 +330,8 @@ "source": [ "def smr_generator(base_nx, base_width,\n", " smr_levels,smr_widths,\n", - " mb_nx=32,quiet=False):\n", + " mb_nx=32,quiet=False,\n", + " mem_per_device=40e9):\n", " \"\"\"\n", " Helper function to quickly define static-mesh refinement meshes for AthenaPK.\n", " By default, prints out information like smallest cell size, total number of\n", @@ -444,7 +445,7 @@ " print(f\"Total meshblocks: {info['total_n_mb']}\" )\n", " print(f\"Total memory needed: {info['total_used_memory']/1e9} GB\")\n", " print(f\"Total memory per output: {info['total_output_memory']/1e9} GB\")\n", - " print(f\"A100s (40G) needed: {info['total_used_memory']/40e9} \")\n", + " print(f\"Devices needed with {mem_per_device/1e9:.2e} GB per deivce: {info['total_used_memory']/mem_per_device:.2e} \")\n", " \n", " print()\n", "\n", @@ -507,7 +508,8 @@ "source": [ "mesh_text,mesh_info = smr_generator( base_nx, base_width,\n", " smr_levels, smr_widths,\n", - " mb_nx, quiet=False)\n", + " mb_nx, quiet=False,\n", + " mem_per_device=40e9) #Report devices needed using memory of NVidia A100\n", "# print(mesh_text)" ] }, diff --git a/src/pgen/cluster/jet_coords.hpp b/src/pgen/cluster/jet_coords.hpp index c82eab48..d7eb1c26 100644 --- a/src/pgen/cluster/jet_coords.hpp +++ b/src/pgen/cluster/jet_coords.hpp @@ -86,7 +86,6 @@ class JetCoords { /************************************************************ * Jet Coordinates Factory Class * A factory for creating JetCoords objects given a time - * Keeps track of the precession ************************************************************/ class JetCoordsFactory { private: From f1c1d8bff1edf89d08a545482421e4c4320d18eb Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 15 May 2023 16:44:56 -0500 Subject: [PATCH 93/95] Jupyter notebbok with Precessing jet coord math --- src/pgen/cluster/JetCoordsMath.ipynb | 406 +++++++++++++++++++++++++++ 1 file changed, 406 insertions(+) create mode 100644 src/pgen/cluster/JetCoordsMath.ipynb diff --git a/src/pgen/cluster/JetCoordsMath.ipynb b/src/pgen/cluster/JetCoordsMath.ipynb new file mode 100644 index 00000000..1c2e9c4b --- /dev/null +++ b/src/pgen/cluster/JetCoordsMath.ipynb @@ -0,0 +1,406 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "3d49a71c", + "metadata": {}, + "outputs": [], + "source": [ + "import sympy as sy\n", + "import sympy.physics.vector\n", + "import sympy.vector\n", + "from sympy.codegen.ast import Assignment\n", + "import numpy as np" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "2762bbb0", + "metadata": {}, + "outputs": [], + "source": [ + "#Define the simulation cartesian coordinate frame\n", + "S_cart = sympy.vector.CoordSys3D(\"S_{cart}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "6c8a2a1b", + "metadata": {}, + "outputs": [], + "source": [ + "#Azimuthal and inclination angle of the jet\n", + "phi_jet, theta_jet = sy.symbols(\"phi_jet theta_jet\")\n", + "\n", + "#Define the cartesian coordinate system of the jet\n", + "J_cart = S_cart.orient_new_space(\"J_{cart}\", theta_jet, phi_jet, 0, \"YZX\")\n", + "\n", + "\n", + "#Define the cylindrical coordinate system of the jet\n", + "J_cyl = J_cart.create_new(\"J_{cyl}\",transformation=\"cylindrical\")" + ] + }, + { + "cell_type": "markdown", + "id": "324f0c8d", + "metadata": {}, + "source": [ + "# Equations and code to convert simulation cartesian coordinates to jet cylindrical coordinates" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1ce2d3b4", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Equation for jet cartesian coords from simulation cartesian coords\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle x_{jet} = x_{sim} \\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} - z_{sim} \\sin{\\left(\\theta_{jet} \\right)}$" + ], + "text/plain": [ + "Eq(x_jet, x_sim*cos(phi_jet)*cos(theta_jet) + y_sim*sin(phi_jet)*cos(theta_jet) - z_sim*sin(theta_jet))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle y_{jet} = - x_{sim} \\sin{\\left(\\phi_{jet} \\right)} + y_{sim} \\cos{\\left(\\phi_{jet} \\right)}$" + ], + "text/plain": [ + "Eq(y_jet, -x_sim*sin(phi_jet) + y_sim*cos(phi_jet))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle z_{jet} = x_{sim} \\sin{\\left(\\theta_{jet} \\right)} \\cos{\\left(\\phi_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\sin{\\left(\\theta_{jet} \\right)} + z_{sim} \\cos{\\left(\\theta_{jet} \\right)}$" + ], + "text/plain": [ + "Eq(z_jet, x_sim*sin(theta_jet)*cos(phi_jet) + y_sim*sin(phi_jet)*sin(theta_jet) + z_sim*cos(theta_jet))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Latex for jet cartesian coords from simulation cartesian coords\n", + "x_{jet} = x_{sim} \\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} - z_{sim} \\sin{\\left(\\theta_{jet} \\right)}\n", + "y_{jet} = - x_{sim} \\sin{\\left(\\phi_{jet} \\right)} + y_{sim} \\cos{\\left(\\phi_{jet} \\right)}\n", + "z_{jet} = x_{sim} \\sin{\\left(\\theta_{jet} \\right)} \\cos{\\left(\\phi_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\sin{\\left(\\theta_{jet} \\right)} + z_{sim} \\cos{\\left(\\theta_{jet} \\right)}\n", + "\n", + "Code for jet_cartesian vector as a sim cartesian_vector\n", + "x_jet = x_sim*cos(phi_jet)*cos(theta_jet) + y_sim*sin(phi_jet)*cos(theta_jet) - z_sim*sin(theta_jet);\n", + "y_jet = -x_sim*sin(phi_jet) + y_sim*cos(phi_jet);\n", + "z_jet = x_sim*sin(theta_jet)*cos(phi_jet) + y_sim*sin(phi_jet)*sin(theta_jet) + z_sim*cos(theta_jet);\n", + "\n", + "Equation for jet cylindrical coords from simulation cartesian coords\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle pos_{r} = \\sqrt{\\left(- x_{sim} \\sin{\\left(\\phi_{jet} \\right)} + y_{sim} \\cos{\\left(\\phi_{jet} \\right)}\\right)^{2} + \\left(x_{sim} \\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} - z_{sim} \\sin{\\left(\\theta_{jet} \\right)}\\right)^{2}}$" + ], + "text/plain": [ + "Eq(pos_r, sqrt((-x_sim*sin(phi_jet) + y_sim*cos(phi_jet))**2 + (x_sim*cos(phi_jet)*cos(theta_jet) + y_sim*sin(phi_jet)*cos(theta_jet) - z_sim*sin(theta_jet))**2))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle pos_{\\theta} = \\operatorname{atan}_{2}{\\left(- x_{sim} \\sin{\\left(\\phi_{jet} \\right)} + y_{sim} \\cos{\\left(\\phi_{jet} \\right)},x_{sim} \\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} - z_{sim} \\sin{\\left(\\theta_{jet} \\right)} \\right)}$" + ], + "text/plain": [ + "Eq(pos_theta, atan2(-x_sim*sin(phi_jet) + y_sim*cos(phi_jet), x_sim*cos(phi_jet)*cos(theta_jet) + y_sim*sin(phi_jet)*cos(theta_jet) - z_sim*sin(theta_jet)))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle pos_{h} = x_{sim} \\sin{\\left(\\theta_{jet} \\right)} \\cos{\\left(\\phi_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\sin{\\left(\\theta_{jet} \\right)} + z_{sim} \\cos{\\left(\\theta_{jet} \\right)}$" + ], + "text/plain": [ + "Eq(pos_h, x_sim*sin(theta_jet)*cos(phi_jet) + y_sim*sin(phi_jet)*sin(theta_jet) + z_sim*cos(theta_jet))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Latex for jet cylindrical coords from simulation cartesian coords\n", + "pos_{r} = \\sqrt{\\left(- x_{sim} \\sin{\\left(\\phi_{jet} \\right)} + y_{sim} \\cos{\\left(\\phi_{jet} \\right)}\\right)^{2} + \\left(x_{sim} \\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} - z_{sim} \\sin{\\left(\\theta_{jet} \\right)}\\right)^{2}}\n", + "pos_{\\theta} = \\operatorname{atan}_{2}{\\left(- x_{sim} \\sin{\\left(\\phi_{jet} \\right)} + y_{sim} \\cos{\\left(\\phi_{jet} \\right)},x_{sim} \\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} - z_{sim} \\sin{\\left(\\theta_{jet} \\right)} \\right)}\n", + "pos_{h} = x_{sim} \\sin{\\left(\\theta_{jet} \\right)} \\cos{\\left(\\phi_{jet} \\right)} + y_{sim} \\sin{\\left(\\phi_{jet} \\right)} \\sin{\\left(\\theta_{jet} \\right)} + z_{sim} \\cos{\\left(\\theta_{jet} \\right)}\n", + "Equation for jet cylindrical coords from simulation cartesian coords\n", + "pos_r = sqrt(pow(-x_sim*sin(phi_jet) + y_sim*cos(phi_jet), 2) + pow(x_sim*cos(phi_jet)*cos(theta_jet) + y_sim*sin(phi_jet)*cos(theta_jet) - z_sim*sin(theta_jet), 2));\n", + "pos_theta = atan2(-x_sim*sin(phi_jet) + y_sim*cos(phi_jet), x_sim*cos(phi_jet)*cos(theta_jet) + y_sim*sin(phi_jet)*cos(theta_jet) - z_sim*sin(theta_jet));\n", + "pos_h = x_sim*sin(theta_jet)*cos(phi_jet) + y_sim*sin(phi_jet)*sin(theta_jet) + z_sim*cos(theta_jet);\n" + ] + } + ], + "source": [ + "#Define a position in simulation-cartesian\n", + "x_sim,y_sim,z_sim = sy.symbols(\"x_sim y_sim z_sim\")\n", + "\n", + "pos_sim = S_cart.origin.locate_new(\"p_sim\",x_sim*S_cart.i + y_sim*S_cart.j + z_sim*S_cart.k)\n", + "\n", + "#Express that simulation-cartesian position in jet-cartesian\n", + "pos_jet = pos_sim.express_coordinates(J_cart)\n", + "\n", + "print(\"Equation for jet cartesian coords from simulation cartesian coords\")\n", + "for i,pos_i in enumerate(pos_jet):\n", + " display(sy.Eq(sy.symbols(f\"{'xyz'[i]}_jet\"),pos_i,evaluate=False))\n", + "print()\n", + "\n", + "print(\"Latex for jet cartesian coords from simulation cartesian coords\")\n", + "for i,pos_i in enumerate(pos_jet):\n", + " print(sy.latex(sy.Eq(sy.symbols(f\"{'xyz'[i]}_jet\"),pos_i,evaluate=False)))\n", + "print()\n", + "\n", + "\n", + "print(\"Code for jet_cartesian vector as a sim cartesian_vector\")\n", + "for i,pos_i in enumerate(pos_jet):\n", + " print(sy.ccode(Assignment(sy.symbols(f\"{'xyz'[i]}_jet\"),pos_i)))\n", + "print()\n", + "\n", + "#Express the simulation-cartesian position in jet-cylindrical\n", + "pos_r = sy.sqrt(pos_jet[0]**2 + pos_jet[1]**2)\n", + "pos_theta = sy.atan2(pos_jet[1],pos_jet[0])\n", + "pos_h = pos_jet[2]\n", + "\n", + "print(\"Equation for jet cylindrical coords from simulation cartesian coords\")\n", + "for symbol,var in zip(sy.symbols(\"pos_r pos_theta pos_h\"),(pos_r,pos_theta,pos_h)):\n", + " display(sy.Eq(symbol,var,evaluate=False))\n", + "\n", + "print(\"Latex for jet cylindrical coords from simulation cartesian coords\")\n", + "for symbol,var in zip(sy.symbols(\"pos_r pos_theta pos_h\"),(pos_r,pos_theta,pos_h)):\n", + " print(sy.latex(sy.Eq(symbol,var,evaluate=False)))\n", + " \n", + "print(\"Equation for jet cylindrical coords from simulation cartesian coords\")\n", + "for symbol,var in zip(sy.symbols(\"pos_r pos_theta pos_h\"),(pos_r,pos_theta,pos_h)):\n", + " print(sy.ccode(Assignment(symbol,var)))\n" + ] + }, + { + "cell_type": "markdown", + "id": "7df8918b", + "metadata": {}, + "source": [ + "# Equations and code to convert jet cylindrical vectors to simulation cartesian vectors" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "66b32b15", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Equation for DCM matrix for jet cartesian to sim cartesian\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle \\left[\\begin{matrix}\\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} & - \\sin{\\left(\\phi_{jet} \\right)} & \\sin{\\left(\\theta_{jet} \\right)} \\cos{\\left(\\phi_{jet} \\right)}\\\\\\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} & \\cos{\\left(\\phi_{jet} \\right)} & \\sin{\\left(\\phi_{jet} \\right)} \\sin{\\left(\\theta_{jet} \\right)}\\\\- \\sin{\\left(\\theta_{jet} \\right)} & 0 & \\cos{\\left(\\theta_{jet} \\right)}\\end{matrix}\\right]$" + ], + "text/plain": [ + "Matrix([\n", + "[cos(phi_jet)*cos(theta_jet), -sin(phi_jet), sin(theta_jet)*cos(phi_jet)],\n", + "[sin(phi_jet)*cos(theta_jet), cos(phi_jet), sin(phi_jet)*sin(theta_jet)],\n", + "[ -sin(theta_jet), 0, cos(theta_jet)]])" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Latex for DCM matrix for jet cartesian to sim cartesian\n", + "\\left[\\begin{matrix}\\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} & - \\sin{\\left(\\phi_{jet} \\right)} & \\sin{\\left(\\theta_{jet} \\right)} \\cos{\\left(\\phi_{jet} \\right)}\\\\\\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} & \\cos{\\left(\\phi_{jet} \\right)} & \\sin{\\left(\\phi_{jet} \\right)} \\sin{\\left(\\theta_{jet} \\right)}\\\\- \\sin{\\left(\\theta_{jet} \\right)} & 0 & \\cos{\\left(\\theta_{jet} \\right)}\\end{matrix}\\right]\n", + "\n", + "Equations for jet_cartesian vector as a sim cartesian_vector\n" + ] + }, + { + "data": { + "text/latex": [ + "$\\displaystyle v_{xsim} = v_{xjet} \\cos{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} - v_{yjet} \\sin{\\left(\\phi_{jet} \\right)} + v_{zjet} \\sin{\\left(\\theta_{jet} \\right)} \\cos{\\left(\\phi_{jet} \\right)}$" + ], + "text/plain": [ + "Eq(v_xsim, v_xjet*cos(phi_jet)*cos(theta_jet) - v_yjet*sin(phi_jet) + v_zjet*sin(theta_jet)*cos(phi_jet))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle v_{ysim} = v_{xjet} \\sin{\\left(\\phi_{jet} \\right)} \\cos{\\left(\\theta_{jet} \\right)} + v_{yjet} \\cos{\\left(\\phi_{jet} \\right)} + v_{zjet} \\sin{\\left(\\phi_{jet} \\right)} \\sin{\\left(\\theta_{jet} \\right)}$" + ], + "text/plain": [ + "Eq(v_ysim, v_xjet*sin(phi_jet)*cos(theta_jet) + v_yjet*cos(phi_jet) + v_zjet*sin(phi_jet)*sin(theta_jet))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/latex": [ + "$\\displaystyle v_{zsim} = - v_{xjet} \\sin{\\left(\\theta_{jet} \\right)} + v_{zjet} \\cos{\\left(\\theta_{jet} \\right)}$" + ], + "text/plain": [ + "Eq(v_zsim, -v_xjet*sin(theta_jet) + v_zjet*cos(theta_jet))" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Code for jet_cartesian vector as a sim cartesian_vector\n", + "v_xsim = v_xjet*cos(phi_jet)*cos(theta_jet) - v_yjet*sin(phi_jet) + v_zjet*sin(theta_jet)*cos(phi_jet);\n", + "v_ysim = v_xjet*sin(phi_jet)*cos(theta_jet) + v_yjet*cos(phi_jet) + v_zjet*sin(phi_jet)*sin(theta_jet);\n", + "v_zsim = -v_xjet*sin(theta_jet) + v_zjet*cos(theta_jet);\n" + ] + } + ], + "source": [ + "#Get a rotation matrix for vectors from jet-cartesian to simulation-cartesian\n", + "DCM_jet_to_sim = S_cart.rotation_matrix(J_cart)\n", + "\n", + "print(\"Equation for DCM matrix for jet cartesian to sim cartesian\")\n", + "display(DCM_jet_to_sim)\n", + "print()\n", + "\n", + "print(\"Latex for DCM matrix for jet cartesian to sim cartesian\")\n", + "print(sy.latex(DCM_jet_to_sim))\n", + "print()\n", + "\n", + "#Express the equation for jet-cylindrical vectors to simulation-cartesian vectors\n", + "v_x, v_y, v_z = sy.symbols(\"v_xjet v_yjet v_zjet\")\n", + "v_jet = v_x*J_cart.i + v_y*J_cart.j + v_z*J_cart.k\n", + "\n", + "print(\"Equations for jet_cartesian vector as a sim cartesian_vector\")\n", + "for i,unit in enumerate((S_cart.i, S_cart.j, S_cart.k)):\n", + " out = sy.symbols(f\"v_{'xyz'[i]}sim\")\n", + " display(sy.Eq(out,unit.dot(v_jet),evaluate=False))\n", + "print()\n", + "\n", + "print(\"Code for jet_cartesian vector as a sim cartesian_vector\")\n", + "for i,unit in enumerate((S_cart.i, S_cart.j, S_cart.k)):\n", + " out = sy.symbols(f\"v_{'xyz'[i]}sim\")\n", + " print(sy.ccode(Assignment(out,unit.dot(v_jet))))" + ] + }, + { + "cell_type": "markdown", + "id": "0d18aa6e", + "metadata": {}, + "source": [ + "# Verification" + ] + }, + { + "cell_type": "markdown", + "id": "fd3ab37d", + "metadata": {}, + "source": [ + " Verify that a vector along the jet axis points down $(1,\\theta_{jet},\\phi_{jet})$" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3ea1df6b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Vector along jet axis is consistent\n" + ] + } + ], + "source": [ + "v_jet = J_cyl.k\n", + "v_jet_cart = sy.sin(theta_jet)*sy.cos(phi_jet)*S_cart.i + \\\n", + " sy.sin(theta_jet)*sy.sin(phi_jet)*S_cart.j + \\\n", + " sy.cos(theta_jet)*S_cart.k\n", + "\n", + "if v_jet_cart == sy.vector.express(v_jet,S_cart):\n", + " print(\"Vector along jet axis is consistent\")\n", + "else:\n", + " print(\"FAIL: Vector along jet axis is NOT consistent\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From fc1837257acbeff9285879244edf2b1247c9e956 Mon Sep 17 00:00:00 2001 From: Forrest Glines Date: Mon, 15 May 2023 17:23:51 -0500 Subject: [PATCH 94/95] Fixed AGN accretion to remove gas from entire grid --- src/pgen/cluster/agn_triggering.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index 610bc4ac..d000dedd 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -127,9 +127,12 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, // Grab some necessary variables const auto &prim_pack = md->PackVariables(std::vector{"prim"}); const auto &cons_pack = md->PackVariables(std::vector{"cons"}); - IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::interior); - IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); - IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); + IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::entire); + IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::entire); + IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::entire); + IndexRange int_ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::interior); + IndexRange int_jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); + IndexRange int_kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); const auto nhydro = hydro_pkg->Param("nhydro"); const auto nscalars = hydro_pkg->Param("nscalars"); @@ -166,7 +169,13 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, if (temp <= cold_temp_thresh) { const Real cell_cold_mass = prim(IDN, k, j, i) * coords.CellVolume(k, j, i); - team_cold_mass += cell_cold_mass; + + if( k >= int_kb.s && k <= int_kb.e && + j >= int_jb.s && j <= int_jb.e && + i >= int_ib.s && i <= int_ib.e) { + //Only reduce the cold gas that exists on the interior grid + team_cold_mass += cell_cold_mass; + } const Real cell_delta_rho = -prim(IDN, k, j, i) / cold_t_acc * dt; @@ -265,9 +274,9 @@ void AGNTriggering::RemoveBondiAccretedGas(parthenon::MeshData // FIXME(forrestglines) When reductions are called, is `prim` up to date? const auto &prim_pack = md->PackVariables(std::vector{"prim"}); const auto &cons_pack = md->PackVariables(std::vector{"cons"}); - IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::interior); - IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::interior); - IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::interior); + IndexRange ib = md->GetBlockData(0)->GetBoundsI(IndexDomain::entire); + IndexRange jb = md->GetBlockData(0)->GetBoundsJ(IndexDomain::entire); + IndexRange kb = md->GetBlockData(0)->GetBoundsK(IndexDomain::entire); const auto nhydro = hydro_pkg->Param("nhydro"); const auto nscalars = hydro_pkg->Param("nscalars"); From d8d654f90d6e731520eeeefbe8ff1679c6bdcec4 Mon Sep 17 00:00:00 2001 From: par-hermes Date: Mon, 15 May 2023 22:28:16 +0000 Subject: [PATCH 95/95] cpp-py-formatter --- src/pgen/cluster/agn_triggering.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pgen/cluster/agn_triggering.cpp b/src/pgen/cluster/agn_triggering.cpp index d000dedd..0efb97ae 100644 --- a/src/pgen/cluster/agn_triggering.cpp +++ b/src/pgen/cluster/agn_triggering.cpp @@ -170,10 +170,9 @@ void AGNTriggering::ReduceColdMass(parthenon::Real &cold_mass, const Real cell_cold_mass = prim(IDN, k, j, i) * coords.CellVolume(k, j, i); - if( k >= int_kb.s && k <= int_kb.e && - j >= int_jb.s && j <= int_jb.e && + if (k >= int_kb.s && k <= int_kb.e && j >= int_jb.s && j <= int_jb.e && i >= int_ib.s && i <= int_ib.e) { - //Only reduce the cold gas that exists on the interior grid + // Only reduce the cold gas that exists on the interior grid team_cold_mass += cell_cold_mass; }