diff --git a/.gitignore b/.gitignore index 5213975f72..8738cad4d8 100644 --- a/.gitignore +++ b/.gitignore @@ -77,6 +77,8 @@ examples/SubgridTests/CosmologicalStellarEvolution/StellarEvolutionSolution* examples/SmallCosmoVolume/SmallCosmoVolume_DM/power_spectra examples/SmallCosmoVolume/SmallCosmoVolume_cooling/snapshots/ examples/SmallCosmoVolume/SmallCosmoVolume_hydro/snapshots/ +examples/**/chem5 +examples/**/CloudyData_UVB=FG2011_shielded.h5 examples/**/CloudyData_UVB=HM2012.h5 examples/**/CloudyData_UVB=HM2012_shielded.h5 examples/**/CloudyData_UVB=HM2012_high_density.h5 diff --git a/configure.ac b/configure.ac index ada2860200..8bf8e293bd 100644 --- a/configure.ac +++ b/configure.ac @@ -319,6 +319,54 @@ if test "$enable_timers" = "yes"; then AC_DEFINE([SWIFT_USE_TIMERS],1,[Enable individual timers]) fi +# Check if Simba expensive debugging is on. +AC_ARG_ENABLE([simba-debugging-checks], + [AS_HELP_STRING([--enable-simba-debugging-checks], + [Activate expensive consistency checks @<:@yes/no@:>@] + )], + [simba_enable_debugging_checks="$enableval"], + [simba_enable_debugging_checks="no"] +) +if test "$enable_simba_debugging_checks" = "yes"; then + AC_DEFINE([SIMBA_DEBUG_CHECKS],1,[Enable Simba expensive debugging]) +fi + +# Check if KIARA expensive debugging is on. +AC_ARG_ENABLE([kiara-debugging-checks], + [AS_HELP_STRING([--enable-kiara-debugging-checks], + [Activate expensive consistency checks @<:@yes/no@:>@] + )], + [kiara_enable_debugging_checks="$enableval"], + [kiara_enable_debugging_checks="no"] +) +if test "$enable_kiara_debugging_checks" = "yes"; then + AC_DEFINE([KIARA_DEBUG_CHECKS],1,[Enable KIARA expensive debugging]) +fi + +# Check if Firehose expensive debugging is on. +AC_ARG_ENABLE([firehose-debugging-checks], + [AS_HELP_STRING([--enable-firehose-debugging-checks], + [Activate expensive consistency checks @<:@yes/no@:>@] + )], + [firehose_enable_debugging_checks="$enableval"], + [firehose_enable_debugging_checks="no"] +) +if test "$enable_firehose_debugging_checks" = "yes"; then + AC_DEFINE([FIREHOSE_DEBUG_CHECKS],1,[Enable firehose expensive debugging]) +fi + +# Check if Obsidian expensive debugging is on. +AC_ARG_ENABLE([obsidian-debugging-checks], + [AS_HELP_STRING([--enable-obsidian-debugging-checks], + [Activate expensive consistency checks @<:@yes/no@:>@] + )], + [obsidian_enable_debugging_checks="$enableval"], + [obsidian_enable_debugging_checks="no"] +) +if test "$enable_obsidian_debugging_checks" = "yes"; then + AC_DEFINE([OBSIDIAN_DEBUG_CHECKS],1,[Enable Obsidian expensive debugging]) +fi + AC_ARG_ENABLE([likwid], [AS_HELP_STRING([--enable-likwid], [Enable LIKWID performance monitoring])], @@ -343,6 +391,18 @@ if test "$enable_debugging_checks" = "yes"; then AC_DEFINE([SWIFT_DEBUG_CHECKS],1,[Enable expensive debugging]) fi +# Check if expensive debugging is on. +AC_ARG_ENABLE([magma2-debugging-checks], + [AS_HELP_STRING([--enable-magma2-debugging-checks], + [Activate expensive MAGMA2 debugging checks @<:@yes/no@:>@] + )], + [enable_magma2_debugging_checks="$enableval"], + [enable_magma2_debugging_checks="no"] +) +if test "$enable_magma2_debugging_checks" = "yes"; then + AC_DEFINE([MAGMA2_DEBUG_CHECKS],1,[Enable expensive MAGMA2 debugging]) +fi + # Check if cell graph is on. AC_ARG_ENABLE([cell-graph], [AS_HELP_STRING([--enable-cell-graph], @@ -1457,16 +1517,18 @@ if test "x$with_grackle" != "xno"; then AC_PROG_FC AC_FC_LIBRARY_LDFLAGS if test "x$with_grackle" != "xyes" -a "x$with_grackle" != "x"; then - GRACKLE_LIBS="-L$with_grackle/lib -lgrackle" - GRACKLE_INCS="-I$with_grackle/include" + GRACKLE_LIBS="-L$with_grackle/lib -lgrackle -Wl,-rpath=$with_grackle/lib" + GRACKLE_INCS="-I$with_grackle/include -DOMIT_LEGACY_INTERNAL_GRACKLE_FUNC" else GRACKLE_LIBS="-lgrackle" GRACKLE_INCS="" fi + # AC_MSG_NOTICE([Grackle include is $GRACKLE_INCS and grackle lib is $GRACKLE_LIBS]) + # note that if you have multiple grackle libs included in your LD_LIBRARY_PATH, the code will use the first one it finds, not this specific one defined here! have_grackle="yes" - AS_VAR_APPEND([GRACKLE_LIBS], ["$FCLIBS"]) + # AS_VAR_APPEND([GRACKLE_LIBS], ["$FCLIBS"]) AC_CHECK_LIB( [grackle], @@ -1491,6 +1553,7 @@ if test "x$with_grackle" != "xno"; then [AC_MSG_ERROR(Wrong grackle library version. Please consult the documentation for specifics.)], [$GRACKLE_LIBS]) + # AC_MSG_NOTICE([The value of Grackle include is $GRACKLE_INCS from $with_grackle]) fi AC_SUBST([GRACKLE_LIBS]) @@ -1996,8 +2059,7 @@ AC_SUBST([SUNDIALS_INCS]) # As an example for this, see the call to AC_ARG_WITH for cooling. AC_ARG_WITH([subgrid], [AS_HELP_STRING([--with-subgrid=], - [Master switch for subgrid methods. Inexperienced user should - start here. Options are: @<:@none, GEAR, GEAR-G3, AGORA, QLA, QLA-EAGLE, EAGLE, EAGLE-XL, SPIN_JET_EAGLE default: none@:>@] + [Master switch for subgrid methods. Inexperienced user should start here. Options are: @<:@none, GEAR, GEAR-G3, AGORA, QLA, QLA-EAGLE, EAGLE, EAGLE-XL, SPIN_JET_EAGLE, SIMBA, KIARA, KIARART default: none@:>@] )], [with_subgrid="$withval"], [with_subgrid=none] @@ -2012,6 +2074,7 @@ with_subgrid_pressure_floor=none with_subgrid_stars=none with_subgrid_star_formation=none with_subgrid_feedback=none +with_subgrid_black_holes=none with_subgrid_sink=none with_subgrid_extra_io=none @@ -2044,6 +2107,7 @@ case "$with_subgrid" in with_subgrid_sink=GEAR with_subgrid_extra_io=none enable_fof=no + enable_fof_galaxies=no ;; AGORA) with_subgrid_cooling=grackle_0 @@ -2069,6 +2133,7 @@ case "$with_subgrid" in with_subgrid_sink=none with_subgrid_extra_io=none enable_fof=no + enable_fof_galaxies=no ;; QLA-EAGLE) with_subgrid_cooling=QLA-EAGLE @@ -2081,6 +2146,7 @@ case "$with_subgrid" in with_subgrid_black_holes=none with_subgrid_sink=none enable_fof=no + enable_fof_galaxies=no ;; EAGLE) with_subgrid_cooling=EAGLE @@ -2094,6 +2160,7 @@ case "$with_subgrid" in with_subgrid_sink=none with_subgrid_extra_io=none enable_fof=yes + enable_fof_galaxies=no ;; EAGLE-XL) with_subgrid_cooling=PS2020 @@ -2107,6 +2174,63 @@ case "$with_subgrid" in with_subgrid_sink=none with_subgrid_extra_io=none enable_fof=yes + enable_fof_galaxies=no + ;; + Obsidian) + with_subgrid_cooling=KIARA + with_subgrid_chemistry=KIARA + with_subgrid_tracers=none + with_subgrid_entropy_floor=none + with_subgrid_stars=SIMBA + with_subgrid_star_formation=KIARA + with_subgrid_feedback=KIARA + with_subgrid_black_holes=Obsidian + with_subgrid_sink=none + with_subgrid_extra_io=none + enable_fof=yes + enable_fof_galaxies=yes + ;; + ObsidianNoBH) + with_subgrid_cooling=KIARA + with_subgrid_chemistry=KIARA + with_subgrid_tracers=none + with_subgrid_entropy_floor=SIMBA + with_subgrid_stars=SIMBA + with_subgrid_star_formation=KIARA + with_subgrid_feedback=KIARA + with_subgrid_black_holes=none + with_subgrid_sink=none + with_subgrid_extra_io=none + enable_fof=yes + enable_fof_galaxies=yes + ;; + KIARA) + with_subgrid_cooling=KIARA + with_subgrid_chemistry=KIARA + with_subgrid_tracers=none + with_subgrid_entropy_floor=none + with_subgrid_stars=KIARA + with_subgrid_star_formation=KIARA + with_subgrid_feedback=KIARA + with_subgrid_black_holes=Obsidian + with_subgrid_sink=none + with_subgrid_extra_io=none + enable_fof=yes + enable_fof_galaxies=yes + ;; + KIARART) + with_subgrid_cooling=KIARA + with_subgrid_chemistry=KIARA + with_subgrid_tracers=none + with_subgrid_entropy_floor=none + with_subgrid_stars=KIARA + with_subgrid_star_formation=KIARA + with_subgrid_feedback=KIARA + with_subgrid_black_holes=Obsidian + with_subgrid_sink=none + with_subgrid_extra_io=none + enable_fof=yes + enable_fof_galaxies=yes ;; SPIN_JET_EAGLE) with_subgrid_cooling=EAGLE @@ -2151,6 +2275,18 @@ if test "$enable_fof" = "yes"; then AC_DEFINE([WITH_FOF], 1, [Enable FoF]) fi +# Check if we are looking for galaxies. +AC_ARG_ENABLE([fof-galaxies], + [AS_HELP_STRING([--enable-fof-galaxies], + [Activate the friends-of-friends (FoF) code with galaxy finding.], + )], + [enable_fof_galaxies="$enableval"], + [enable_fof_galaxies="no"] +) +if test "$enable_fof_galaxies" = "yes"; then + AC_DEFINE([WITH_FOF_GALAXIES], 1, [Enable FoF Galaxies]) +fi + # Check if stand-alone FoF is on. AC_ARG_ENABLE([stand-alone-fof], [AS_HELP_STRING([--enable-stand-alone-fof], @@ -2260,6 +2396,9 @@ case "$with_hydro" in gasoline) AC_DEFINE([GASOLINE_SPH], [1], [Gasoline SPH]) ;; + magma2) + AC_DEFINE([MAGMA2_SPH], [1], [MAGMA2 SPH]) + ;; anarchy-pu) AC_DEFINE([ANARCHY_PU_SPH], [1], [ANARCHY (PU) SPH]) ;; @@ -2594,6 +2733,15 @@ case "$with_chemistry" in AC_DEFINE([CHEMISTRY_EAGLE], [1], [Chemistry taken from the EAGLE model]) with_chemistry_name="EAGLE (9 elements + smoothing)" ;; + SIMBA) + AC_DEFINE([CHEMISTRY_SIMBA], [1], [Chemistry taken from the SIMBA model]) + with_chemistry_name="SIMBA (11 elements + smoothing)" + ;; + KIARA) + AC_DEFINE([CHEMISTRY_KIARA], [1], [Chemistry taken from the KIARA model]) + with_chemistry_name="KIARA (11 elements + smoothing + diffusion)" + ;; + *) AC_MSG_ERROR([Unknown chemistry function: $with_chemistry]) ;; @@ -2652,6 +2800,16 @@ case "$with_cooling" in with_cooling_name="Grackle $primordial_chemistry" with_cooling="grackle" ;; + SIMBA) + AC_DEFINE([COOLING_SIMBA], [1], [Cooling via the grackle library v3+ with extensions]) + AC_DEFINE([COOLING_GRACKLE_MODE], [1], [Cooling via Grackle mode=1]) + with_cooling_name="SIMBA (Grackle 3+ with extensions)" + ;; + KIARA) + AC_DEFINE([COOLING_KIARA], [1], [Cooling via the grackle library v3.2+ incl. dust+H2 formation]) + AC_DEFINE([COOLING_GRACKLE_MODE], [2], [Cooling via Grackle mode=2]) + with_cooling_name="KIARA (Grackle 3.2+ incl. dust+H2 formation)" + ;; QLA) AC_DEFINE([COOLING_QLA], [1], [Cooling following the Quick-Lyman-alpha model]) with_cooling_name="QLA (Ploeckinger+20 tables) with constant primordial Z" @@ -2747,7 +2905,7 @@ esac # Stellar model. AC_ARG_WITH([stars], [AS_HELP_STRING([--with-stars=], - [Stellar model to use @<:@none, basic, EAGLE, GEAR, default: basic@:>@] + [Stellar model to use @<:@none, basic, EAGLE, GEAR, KIARA, default: basic@:>@] )], [with_stars="$withval"], [with_stars="basic"] @@ -2767,6 +2925,9 @@ case "$with_stars" in ;; GEAR) AC_DEFINE([STARS_GEAR], [1], [GEAR stellar model]) + ;; + KIARA) + AC_DEFINE([STARS_KIARA], [1], [KIARA stellar model]) ;; basic) AC_DEFINE([STARS_BASIC], [1], [Basic stellar model]) @@ -2782,7 +2943,7 @@ esac # Feedback model AC_ARG_WITH([feedback], [AS_HELP_STRING([--with-feedback=], - [Feedback model to use @<:@none, EAGLE, EAGLE-thermal, EAGLE-kinetic, GEAR, AGORA default: none@:>@] + [Feedback model to use @<:@none, EAGLE, EAGLE-thermal, EAGLE-kinetic, GEAR, SIMBA, KIARA, AGORA default: none@:>@] )], [with_feedback="$withval"], [with_feedback="none"] @@ -2814,6 +2975,14 @@ case "$with_feedback" in AC_DEFINE([FEEDBACK_GEAR], [1], [GEAR stellar feedback and evolution model]) with_feedback_name="GEAR" ;; + SIMBA) + AC_DEFINE([FEEDBACK_SIMBA], [1], [SIMBA stellar feedback and evolution model]) + with_feedback_name="SIMBA decoupled winds feedback" + ;; + KIARA) + AC_DEFINE([FEEDBACK_KIARA], [1], [KIARA stellar feedback and evolution model]) + with_feedback_name="KIARA decoupled winds feedback and Chem5 enrichment" + ;; AGORA) AC_DEFINE([FEEDBACK_AGORA], [1], [AGORA stellar feedback and evolution model]) with_feedback_name="AGORA" @@ -2871,6 +3040,13 @@ case "$with_black_holes" in EAGLE) AC_DEFINE([BLACK_HOLES_EAGLE], [1], [EAGLE black hole model]) ;; + SIMBA) + AC_DEFINE([BLACK_HOLES_SIMBA], [1], [SIMBA black hole model]) + ;; + Obsidian) + AC_DEFINE([BLACK_HOLES_OBSIDIAN], [1], [Obsidian black hole model]) + with_black_holes="Three regime model (Rennehan+24)" + ;; SPIN_JET) AC_DEFINE([BLACK_HOLES_SPIN_JET], [1], [Spin and jet black hole model]) with_black_holes="SPIN_JETS (Husko+22)" @@ -2998,7 +3174,7 @@ esac # Entropy floor AC_ARG_WITH([entropy-floor], [AS_HELP_STRING([--with-entropy-floor=], - [entropy floor @<:@none, QLA, EAGLE, default: none@:>@] + [entropy floor @<:@none, QLA, EAGLE, SIMBA, default: none@:>@] )], [with_entropy_floor="$withval"], [with_entropy_floor="none"] @@ -3021,6 +3197,9 @@ case "$with_entropy_floor" in EAGLE) AC_DEFINE([ENTROPY_FLOOR_EAGLE], [1], [EAGLE entropy floor]) ;; + SIMBA) + AC_DEFINE([ENTROPY_FLOOR_SIMBA], [1], [SIMBA entropy floor]) + ;; *) AC_MSG_ERROR([Unknown entropy floor model]) ;; @@ -3058,7 +3237,7 @@ esac # Star formation AC_ARG_WITH([star-formation], [AS_HELP_STRING([--with-star-formation=], - [star formation @<:@none, QLA, EAGLE, GEAR, default: none@:>@] + [star formation @<:@none, QLA, EAGLE, GEAR, SIMBA, KIARA, default: none@:>@] )], [with_star_formation="$withval"], [with_star_formation="none"] @@ -3076,7 +3255,7 @@ case "$with_star_formation" in AC_DEFINE([STAR_FORMATION_NONE], [1], [No star formation]) ;; QLA) - AC_DEFINE([STAR_FORMATION_QLA], [1], [Quick Lyman-alpha star formation model)]) + AC_DEFINE([STAR_FORMATION_QLA], [1], [Quick Lyman-alpha star formation model]) ;; EAGLE) AC_DEFINE([STAR_FORMATION_EAGLE], [1], [EAGLE star formation model (Schaye and Dalla Vecchia (2008))]) @@ -3084,6 +3263,12 @@ case "$with_star_formation" in GEAR) AC_DEFINE([STAR_FORMATION_GEAR], [1], [GEAR star formation model (Revaz and Jablonka (2018))]) ;; + SIMBA) + AC_DEFINE([STAR_FORMATION_SIMBA], [1], [SIMBA star formation model]) + ;; + KIARA) + AC_DEFINE([STAR_FORMATION_KIARA], [1], [KIARA star formation model]) + ;; *) AC_MSG_ERROR([Unknown star formation model]) ;; @@ -3113,18 +3298,18 @@ AC_DEFINE_UNQUOTED([SELF_GRAVITY_MULTIPOLE_ORDER], [$with_multipole_order], [Mul # Radiative transfer scheme AC_ARG_WITH([rt], [AS_HELP_STRING([--with-rt=], - [Radiative transfer scheme to use @<:@none, GEAR_*, SPHM1RT_*, debug default: none@:>@. - For GEAR and SPHM1RT, the number of photon groups (e.g. GEAR_4) needs to be provided.] + [Radiative transfer scheme to use @<:@none, GEAR_*, SPHM1RT_*, KIARA_* debug default: none@:>@. + For GEAR, SPHM1RT, and KIARA the number of photon groups (e.g. GEAR_4) needs to be provided.] )], [with_rt="$withval"], [with_rt="none"] ) -# For GEAR-RT scheme: Select a RT Riemann solver +# For GEAR-RT/KIARA-RT scheme: Select a RT Riemann solver AC_ARG_WITH([rt-riemann-solver], [AS_HELP_STRING([--with-rt-riemann-solver=], [Riemann solver for the moments of the ratiadiative transfer equation with the M1 closure to use @<:@none, HLL, GLF, default: none@:>@. - For the GEAR RT scheme, you need to select one Riemann solver.] + For the GEAR/KIARA RT scheme, you need to select one Riemann solver.] )], [with_rt_riemann_solver="$withval"], [with_rt_riemann_solver="none"] @@ -3169,11 +3354,11 @@ case "$with_rt" in fi case "$with_hydro" in - "gizmo-mfv" | "sphenix") + "gizmo-mfv" | "sphenix" | "magma2") # allowed. ;; *) - AC_MSG_ERROR([GEAR-RT: Cannot work without gizmo-mfv or sphenix hydro. Compile using --with-hydro=gizmo-mfv or --with-hydro=sphenix]) + AC_MSG_ERROR([GEAR-RT: Cannot work without gizmo-mfv or sphenix/magma2 hydro. Compile using --with-hydro=gizmo-mfv or --with-hydro=sphenix or --with-hydro=magma2]) ;; esac @@ -3185,6 +3370,51 @@ case "$with_rt" in AC_MSG_ERROR([GEAR-RT: You need the grackle library for GEAR-RT. (--with-grackle=PATH)]) fi + if test "$with_cooling" = "none"; then + AC_MSG_ERROR([GEAR-RT: You need to select a cooling module (--with-cooling=grackle_1)]) + fi + + ;; + KIARA_*) + AC_DEFINE([RT_KIARA], [1], [KIARA M1 closure scheme]) + number_group=${with_rt#*_} + AC_DEFINE_UNQUOTED([RT_NGROUPS], [$number_group], [Number of photon groups to follow]) + AC_DEFINE([MPI_SYMMETRIC_FORCE_INTERACTION_RT], [1], [Do symmetric MPI interactions]) + + if test "$number_group" = "0"; then + AC_MSG_ERROR([KIARA-RT: Cannot work with zero photon groups]) + fi + if ! test $number_group -eq $number_group; then + # abuse -eq to check whether $number_group is an integer. -eq + # only works with those. + AC_MSG_ERROR([KIARA-RT: Cannot work with non-integer photon groups]) + fi + + if test "$enable_debugging_checks" = "yes"; then + AC_DEFINE([SWIFT_RT_DEBUG_CHECKS], [1], [additional debugging checks for RT]) + fi + + case "$with_hydro" in + "sphenix" | "magma2") + # allowed. + ;; + *) + AC_MSG_ERROR([KIARA-RT: Needs sphenix/magma2 hydro. Compile using --with-hydro=sphenix or --with-hydro=magma2]) + ;; + esac + + if test "$with_rt_riemann_solver" = "none"; then + AC_MSG_ERROR([KIARA-RT: You need to select an RT Riemann solver (--with-rt-riemann-solver=...)]) + fi + + if test "$have_grackle" != "yes"; then + AC_MSG_ERROR([KIARA-RT: You need the grackle library for KIARA-RT. (--with-grackle=PATH)]) + fi + + if test "$with_cooling" = "none"; then + AC_MSG_ERROR([KIARA-RT: You need to select a cooling module (--with-cooling=grackle_1)]) + fi + ;; debug) AC_DEFINE([RT_DEBUG], [1], [debugging scheme]) @@ -3288,12 +3518,24 @@ AM_CONDITIONAL([HAVEEAGLEKINETICFEEDBACK], [test "$with_feedback" = "EAGLE-kinet # check if using grackle cooling AM_CONDITIONAL([HAVEGRACKLECOOLING], [test "$with_cooling" = "grackle"]) +# check if using grackle cooling +AM_CONDITIONAL([HAVESIMBACOOLING], [test "$with_cooling" = "SIMBA"]) + +# check if using grackle cooling +AM_CONDITIONAL([HAVEKIARACOOLING], [test "$with_cooling" = "KIARA"]) + # check if using EAGLE floor AM_CONDITIONAL([HAVEEAGLEFLOOR], [test "$with_entropy_floor" = "EAGLE"]) # check if using gear feedback AM_CONDITIONAL([HAVEGEARFEEDBACK], [test "$with_feedback" = "GEAR"]) +# check if using SIMBA feedback +AM_CONDITIONAL([HAVESIMBAFEEDBACK], [test "$with_feedback" = "SIMBA"]) + +# check if using KIARA feedback +AM_CONDITIONAL([HAVEKIARAFEEDBACK], [test "$with_feedback" = "KIARA"]) + # Check for GEAR and GEAR_MECHANICAL feedback flags to set the common files # variable AS_IF([test "$with_feedback" = "GEAR"], @@ -3319,18 +3561,33 @@ AM_CONDITIONAL([HAVE_CHEMISTRY_GEAR], [test "$with_chemistry" = "GEAR" || test " # check if using AGORA chemistry AM_CONDITIONAL([HAVE_CHEMISTRY_AGORA], [test "$with_chemistry" = "AGORA" || test "$with_chemistry" = "GEAR_DIFFUSION"]) +# check if using SIMBA chemistry +AM_CONDITIONAL([HAVE_CHEMISTRY_SIMBA], [test "$with_chemistry" = "SIMBA"]) + +# check if using KIARA chemistry +AM_CONDITIONAL([HAVE_CHEMISTRY_KIARA], [test "$with_chemistry" = "KIARA"]) + # check if using default stars AM_CONDITIONAL([HAVE_STARS_BASIC], [test "$with_stars" = "basic"]) # check if using GEAR stars AM_CONDITIONAL([HAVE_STARS_GEAR], [test "$with_stars" = "GEAR"]) +# check if using GEAR stars +AM_CONDITIONAL([HAVE_STARS_SIMBA], [test "$with_stars" = "SIMBA"]) + # check if using default star formation AM_CONDITIONAL([HAVE_STAR_FORMATION_DEFAULT], [test "$with_star_formation" = "none"]) # check if using GEAR star formation AM_CONDITIONAL([HAVE_STAR_FORMATION_GEAR], [test "$with_star_formation" = "GEAR"]) +# check if using SIMBA star formation +AM_CONDITIONAL([HAVE_STAR_FORMATION_SIMBA], [test "$with_star_formation" = "SIMBA"]) + +# check if using KIARA star formation +AM_CONDITIONAL([HAVE_STAR_FORMATION_KIARA], [test "$with_star_formation" = "KIARA"]) + # check if using multi softening gravity AM_CONDITIONAL([HAVE_GRAVITY_MULTISOFTENING], [test "$with_gravity" = "with-multi-softening"]) @@ -3340,12 +3597,13 @@ AM_CONDITIONAL([HAVESPHM1RTRT], [test "${with_rt:0:7}" = "SPHM1RT"]) # Check if using GEAR-RT radiative transfer AM_CONDITIONAL([HAVEGEARRT], [test "${with_rt:0:4}" = "GEAR"]) +# Check if using KIARA-RT radiative transfer +AM_CONDITIONAL([HAVEKIARART], [test "${with_rt:0:5}" = "KIARA"]) + # Check if using Moving mesh AM_CONDITIONAL([HAVE_MOVING_MESH], [test "$enable_moving_mesh" = "yes"]) - - # Handle .in files. AC_CONFIG_FILES([Makefile src/Makefile examples/Makefile examples/Cooling/CoolingRates/Makefile doc/Makefile doc/Doxyfile tests/Makefile]) AC_CONFIG_FILES([argparse/Makefile tools/Makefile]) diff --git a/src/Makefile.am b/src/Makefile.am index a27e37aaf2..d2923efb97 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -164,6 +164,13 @@ if HAVEGEARRT GEAR_RT_SOURCES += rt/GEAR/rt_interaction_cross_sections.c endif +# source files for KIARA RT +KIARA_RT_SOURCES = +if HAVEKIARART +KIARA_RT_SOURCES += rt/KIARA/rt_interaction_cross_sections.c +KIARA_RT_SOURCES += rt/KIARA/rt_thermochemistry.c +endif + # source files for SPHM1RT cooling SPHM1RT_RT_SOURCES = if HAVESPHM1RTRT @@ -171,6 +178,18 @@ SPHM1RT_RT_SOURCES += rt/SPHM1RT/rt_rate_equations.c SPHM1RT_RT_SOURCES += rt/SPHM1RT/rt_cooling.c endif +# source files for KIARA cooling +KIARA_COOLING_SOURCES = +if HAVEKIARACOOLING +KIARA_COOLING_SOURCES += cooling/KIARA/cooling.c +endif + +# source files for KIARA feedback +KIARA_FEEDBACK_SOURCES = +if HAVEKIARAFEEDBACK +KIARA_FEEDBACK_SOURCES += feedback/KIARA/feedback.c +endif + # Common source files AM_SOURCES = space.c space_rebuild.c space_regrid.c space_unique_id.c AM_SOURCES += space_sort.c space_split.c space_extras.c space_first_init.c space_init.c @@ -220,6 +239,8 @@ AM_SOURCES += $(AGORA_FEEDBACK_SOURCES) AM_SOURCES += $(PS2020_COOLING_SOURCES) AM_SOURCES += $(SPHM1RT_RT_SOURCES) AM_SOURCES += $(GEAR_RT_SOURCES) +AM_SOURCES += $(KIARA_RT_SOURCES) +AM_SOURCES += $(KIARA_COOLING_SOURCES) $(KIARA_FEEDBACK_SOURCES) AM_SOURCES += swift_lustre_api.c # Include files for distribution, not installation. @@ -354,6 +375,32 @@ nobase_noinst_HEADERS += rt/GEAR/rt_struct.h nobase_noinst_HEADERS += rt/GEAR/rt_thermochemistry.h nobase_noinst_HEADERS += rt/GEAR/rt_thermochemistry_utils.h nobase_noinst_HEADERS += rt/GEAR/rt_unphysical.h +nobase_noinst_HEADERS += rt/KIARA/rt_additions.h +nobase_noinst_HEADERS += rt/KIARA/rt_blackbody.h +nobase_noinst_HEADERS += rt/KIARA/rt_debugging.h +nobase_noinst_HEADERS += rt/KIARA/rt_flux.h +nobase_noinst_HEADERS += rt/KIARA/rt_getters.h +nobase_noinst_HEADERS += rt/KIARA/rt_grackle_utils.h +nobase_noinst_HEADERS += rt/KIARA/rt_gradients.h +nobase_noinst_HEADERS += rt/KIARA/rt.h +nobase_noinst_HEADERS += rt/KIARA/rt_iact.h +nobase_noinst_HEADERS += rt/KIARA/rt_interaction_cross_sections.h +nobase_noinst_HEADERS += rt/KIARA/rt_interaction_rates.h +nobase_noinst_HEADERS += rt/KIARA/rt_io.h +nobase_noinst_HEADERS += rt/KIARA/rt_ionization_equilibrium.h +nobase_noinst_HEADERS += rt/KIARA/rt_parameters.h +nobase_noinst_HEADERS += rt/KIARA/rt_properties.h +nobase_noinst_HEADERS += rt/KIARA/rt_riemann_GLF.h +nobase_noinst_HEADERS += rt/KIARA/rt_riemann_HLL_eigenvalues.h +nobase_noinst_HEADERS += rt/KIARA/rt_riemann_HLL.h +nobase_noinst_HEADERS += rt/KIARA/rt_slope_limiters_cell.h +nobase_noinst_HEADERS += rt/KIARA/rt_slope_limiters_face.h +nobase_noinst_HEADERS += rt/KIARA/rt_species.h +nobase_noinst_HEADERS += rt/KIARA/rt_stellar_emission_model.h +nobase_noinst_HEADERS += rt/KIARA/rt_struct.h +nobase_noinst_HEADERS += rt/KIARA/rt_thermochemistry.h +nobase_noinst_HEADERS += rt/KIARA/rt_thermochemistry_utils.h +nobase_noinst_HEADERS += rt/KIARA/rt_unphysical.h nobase_noinst_HEADERS += rt/SPHM1RT/rt.h nobase_noinst_HEADERS += rt/SPHM1RT/rt_getters.h nobase_noinst_HEADERS += rt/SPHM1RT/rt_setters.h @@ -424,6 +471,8 @@ nobase_noinst_HEADERS += cooling/PS2020/cooling.h cooling/PS2020/cooling_struct. nobase_noinst_HEADERS += cooling/PS2020/cooling_io.h cooling/PS2020/interpolate.h cooling/PS2020/cooling_rates.h nobase_noinst_HEADERS += cooling/PS2020/cooling_tables.h cooling/PS2020/cooling_subgrid.h nobase_noinst_HEADERS += cooling/PS2020/cooling_properties.h cooling/PS2020/cooling_debug.h +nobase_noinst_HEADERS += cooling/KIARA/cooling.h cooling/KIARA/cooling_struct.h +nobase_noinst_HEADERS += cooling/KIARA/cooling_io.h cooling/KIARA/cooling_properties.h cooling/KIARA/cooling_debug.h nobase_noinst_HEADERS += chemistry/none/chemistry.h nobase_noinst_HEADERS += chemistry/none/chemistry_additions.h nobase_noinst_HEADERS += chemistry/none/chemistry_io.h @@ -488,6 +537,9 @@ nobase_noinst_HEADERS += feedback/GEAR/initial_mass_function.h feedback/GEAR/sup nobase_noinst_HEADERS += feedback/GEAR/lifetime.h feedback/GEAR/hdf5_functions.h feedback/GEAR/interpolation.h nobase_noinst_HEADERS += feedback/GEAR/stellar_wind.h nobase_noinst_HEADERS += feedback/GEAR/feedback_debug.h +nobase_noinst_HEADERS += feedback/KIARA/feedback.h feedback/KIARA/feedback_struct.h +nobase_noinst_HEADERS += feedback/KIARA/feedback_properties.h feedback/KIARA/feedback_iact.h +nobase_noinst_HEADERS += feedback/KIARA/feedback_debug.h nobase_noinst_HEADERS += black_holes/Default/black_holes.h black_holes/Default/black_holes_io.h nobase_noinst_HEADERS += black_holes/Default/black_holes_part.h black_holes/Default/black_holes_iact.h nobase_noinst_HEADERS += black_holes/Default/black_holes_properties.h diff --git a/src/black_holes.h b/src/black_holes.h index 4051727192..b2fa561001 100644 --- a/src/black_holes.h +++ b/src/black_holes.h @@ -29,6 +29,8 @@ #include "./black_holes/EAGLE/black_holes.h" #elif defined(BLACK_HOLES_SPIN_JET) #include "./black_holes/SPIN_JET/black_holes.h" +#elif defined(BLACK_HOLES_OBSIDIAN) +#include "./black_holes/Obsidian/black_holes.h" #else #error "Invalid choice of black hole model" #endif diff --git a/src/black_holes/Obsidian/black_holes.h b/src/black_holes/Obsidian/black_holes.h new file mode 100644 index 0000000000..ba62d49a31 --- /dev/null +++ b/src/black_holes/Obsidian/black_holes.h @@ -0,0 +1,1914 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_OBSIDIAN_BLACK_HOLES_H +#define SWIFT_OBSIDIAN_BLACK_HOLES_H + +/* Local includes */ +#include "black_holes_properties.h" +#include "black_holes_struct.h" +#include "cooling.h" +#include "cosmology.h" +#include "dimension.h" +#include "exp10.h" +#include "gravity.h" +#include "kernel_hydro.h" +#include "minmax.h" +#include "physical_constants.h" +#include "random.h" +#include "star_formation.h" + +/* Standard includes */ +#include +#include +#include + +/** + * @brief How much of the feedback actually couples to the medium? + * + * @param bp The black hole particle. + * @param props The properties of the black hole scheme. + * @param BH_state The current state of the black hole. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static double get_black_hole_coupling( + const struct bpart *const bp, const struct black_holes_props *props, + const struct cosmology *cosmo, const struct phys_const *phys_const) { + const int BH_state = bp->state; + switch (BH_state) { + case BH_states_adaf: { + float scaling = 1.f; + if (props->adaf_coupling < 0.f) { + min(pow(1. + cosmo->z, props->adaf_z_scaling), 1.); + } + return fabs(props->adaf_coupling) * scaling; + break; + } + case BH_states_quasar: { + float quasar_coupling = fabs(props->quasar_coupling); + const double c = phys_const->const_speed_light_c; + const double luminosity = + bp->radiative_efficiency * bp->accretion_rate * c * c; + const float mass_limit = get_black_hole_adaf_mass_limit(bp, props, cosmo); + if (luminosity > props->quasar_luminosity_thresh && + props->quasar_luminosity_thresh > 0.f && + bp->subgrid_mass * props->mass_to_solar_mass > mass_limit) { + quasar_coupling = fmin( + quasar_coupling * luminosity / props->quasar_luminosity_thresh, 1.); + } + return quasar_coupling; + break; + } + case BH_states_slim_disk: + return fabs(props->slim_disk_coupling); + break; + default: + error("Invalid black hole state."); + return 0.; + break; + } +} + +/** + * @brief Computes the radiative efficiency in the slim disk mode. + * + * @param props The properties of the black hole scheme. + * @param f_Edd M_dot,BH / M_dot,Edd + */ +__attribute__((always_inline)) INLINE static double +get_black_hole_slim_disk_efficiency(const struct black_holes_props *props, + const double f_Edd) { + if (f_Edd <= 0.) return 0.; + const double R = 1. / f_Edd; + /* Efficiency from Lupi et al. (2014), + * super eddington accretion and feedback */ + return (R / 16.) * props->A_sd * + (0.985 / (R + (5. / 8.) * props->B_sd) + + 0.015 / (R + (5. / 8.) * props->C_sd)); +} + +/** + * @brief Computes the radiative efficiency in the ADAF mode. + * + * @param props The properties of the black hole scheme. + * @param f_Edd M_dot,BH / M_dot,Edd + */ +__attribute__((always_inline)) INLINE static double +get_black_hole_adaf_efficiency(const struct black_holes_props *props, + const double f_Edd) { + return props->epsilon_r * f_Edd; /* scales with M_dot,BH */ +} + +/** + * @brief Chooses and calls the proper radiative efficiency function for the + * state. + * + * @param props The properties of the black hole scheme. + * @param f_Edd The accretion rate over the Eddington rate. + * @param BH_state The current state of the BH. + */ +__attribute__((always_inline)) INLINE static double +get_black_hole_radiative_efficiency(const struct black_holes_props *props, + const double f_Edd, const int BH_state) { + switch (BH_state) { + case BH_states_adaf: + return get_black_hole_adaf_efficiency(props, f_Edd); + case BH_states_quasar: + return props->epsilon_r; + case BH_states_slim_disk: + return get_black_hole_slim_disk_efficiency(props, f_Edd); + default: + error("Invalid black hole state."); + break; + } + + return 0.; +} + +/** + * @brief Compute the wind launch speed for this feedback step. + * + * @param props The properties of the black hole scheme. + * @param phys_const The physical phys_const (in internal units). + * @param bp The black hole particle. + */ +__attribute__((always_inline)) INLINE static double get_black_hole_wind_speed( + const struct black_holes_props *props, const struct phys_const *phys_const, + const struct bpart *bp) { + + if (bp->accretion_rate < 0.f || bp->m_dot_inflow < 0.f) return 0.f; + + float v_kick = 0.f; + if (props->quasar_wind_speed < 0.f || props->slim_disk_wind_speed < 0.f) { + const float subgrid_mass_Msun = + bp->subgrid_mass * props->mass_to_solar_mass; + + if (bp->subgrid_mass > props->subgrid_seed_mass) { + const float min_BH_mass_Msun = + props->minimum_black_hole_mass_v_kick * props->mass_to_solar_mass; + const float dlog10_BH_mass = + log10f(subgrid_mass_Msun) - log10f(min_BH_mass_Msun); + v_kick = fabs(props->quasar_wind_speed) + (fabs(props->slim_disk_wind_speed) / 3.f) * dlog10_BH_mass; + + /* Sometimes can get very small leading to huge mass loadings */ + if (v_kick < props->minimum_v_kick_km_s) { + v_kick = props->minimum_v_kick_km_s; + } + + v_kick *= props->kms_to_internal; + } + } + + switch (bp->state) { + case BH_states_adaf: + return fabs(props->adaf_wind_speed); + break; + case BH_states_quasar: + if (props->quasar_wind_speed < 0.f && v_kick > 0.f) { + return v_kick; + } else { + return fabs(props->quasar_wind_speed); + } + break; + case BH_states_slim_disk: + if (props->slim_disk_wind_speed < 0.f && v_kick > 0.f) { + return v_kick; + } else { + return fabs(props->slim_disk_wind_speed); + } + break; + default: + error("Invalid black hole state."); + return 0.f; + break; + } +} + +/** + * @brief Computes the fraction of M_dot,inflow that should go into the BH. + * + * @param props The properties of the black hole scheme. + * @param phys_const The physical phys_const (in internal units). + * @param cosmo The current cosmological model. + * @param bp The black hole particle. + * @param m_dot_inflow_m_dot_edd M_dot,inflow scaled to M_dot,Edd for the BH. + */ +__attribute__((always_inline)) INLINE static double +get_black_hole_upper_mdot_medd(const struct black_holes_props *props, + const struct phys_const *phys_const, + const struct cosmology *cosmo, + const struct bpart *const bp, + const double m_dot_inflow_m_dot_edd) { + + if (m_dot_inflow_m_dot_edd <= 0.) return 0.; + + double x1, x2, x3; + double a3, a2, a1, a0; + + double phi = props->slim_disk_phi; + if (props->slim_disk_wind_speed < 0.f) { + const float v_kick = get_black_hole_wind_speed(props, phys_const, bp); + + if (v_kick > 0.f) { + /* Set the slim disk mass loading to be continuous at the + * eta upper boundary. Compute the phi term to solve for the + * accretion fraction. + * WARNING: Using the quasar_wind_momentum_flux because that is the + * default way of using the model. Any changes to that require a + * change here. */ + const double c_over_v = phys_const->const_speed_light_c / v_kick; + + /* TODO: Compute once at the beginning of the simulation */ + double mom_flux_times_epsilon_sd = + props->quasar_wind_momentum_flux * + get_black_hole_coupling(bp, props, cosmo, phys_const); + phi = mom_flux_times_epsilon_sd * c_over_v; + } + } + + int num_roots; + + a3 = ((5. * 5.) / (8. * 8.)) * props->B_sd * props->C_sd; + a2 = + (5. / 8.) * + ((props->B_sd + props->C_sd) + + (phi / 16.) * props->A_sd * (0.015 * props->B_sd + 0.985 * props->C_sd) - + (5. / 8.) * props->B_sd * props->C_sd * m_dot_inflow_m_dot_edd); + a1 = 1. + (phi / 16.) * props->A_sd - + (5. / 8.) * (props->B_sd + props->C_sd) * m_dot_inflow_m_dot_edd; + a0 = -m_dot_inflow_m_dot_edd; + + a2 /= a3; + a1 /= a3; + a0 /= a3; + + num_roots = gsl_poly_solve_cubic(a2, a1, a0, &x1, &x2, &x3); + if (num_roots == 1) { + if (x1 >= 0.) { + return x1; + } else { + if (m_dot_inflow_m_dot_edd > 1.e-6) + warning( + "num_roots=1 m_dot_inflow_m_dot_edd=%g phi=%g a3=%g a2=%g " + "a1=%g a0=%g", + m_dot_inflow_m_dot_edd, phi, a3, a2, a1, a0); + return 0.; + } + } + if (x3 >= 0.) { + return x3; + } else { + if (m_dot_inflow_m_dot_edd > 1.e-6) + warning( + "num_roots=0 m_dot_inflow_m_dot_edd=%g phi=%g a3=%g a2=%g a1=%g " + "a0=%g", + m_dot_inflow_m_dot_edd, phi, a3, a2, a1, a0); + return 0.; + } + + return 0.; +} + +/** + * @brief Computes the fraction of M_dot,inflow that should go into the BH. + * + * @param props The properties of the black hole scheme. + * @param phys_const The physical phys_const (in internal units). + * @param m_dot_inflow M_dot,inflow in internal units. + * @param BH_mass The subgrid mass of the BH in internal units. + * @param BH_state The current state of the BH. + * @param Eddington_rate M_dot,Edd in internal units. + */ +__attribute__((always_inline)) INLINE static double +get_black_hole_accretion_factor(const struct black_holes_props *props, + const struct phys_const *phys_const, + const struct cosmology *cosmo, + const struct bpart *const bp, + const double Eddington_rate) { + + const double m_dot_inflow = bp->m_dot_inflow; + const double BH_mass = bp->subgrid_mass; + const int BH_state = bp->state; + + if (m_dot_inflow <= 0. || BH_mass <= 0.) return 0.; + + switch (BH_state) { + case BH_states_adaf: + return props->adaf_f_accretion; + break; + case BH_states_quasar: { + float v_kick = 0.f; + float f_accretion = 0.f; + if (props->quasar_wind_speed < 0.f) { + /* Save computation by only computing when specified by the user */ + v_kick = get_black_hole_wind_speed(props, phys_const, bp); + if (v_kick > 0.) { + const double c = phys_const->const_speed_light_c; + const double c_over_v = c / v_kick; + double quasar_coupling = + get_black_hole_coupling(bp, props, cosmo, phys_const); + double quasar_wind_mass_loading = props->quasar_wind_momentum_flux * + quasar_coupling * props->epsilon_r * + c_over_v; + f_accretion = 1.f / (1.f + quasar_wind_mass_loading); + } + } + + if (f_accretion > 0.f) { + return f_accretion; + } else { + return props->quasar_f_accretion; + } + break; + } + case BH_states_slim_disk: { + /* This is the FRACTION of the total so divide by M_dot,inflow */ + const double f_edd = m_dot_inflow / Eddington_rate; + double mdot_medd = + get_black_hole_upper_mdot_medd(props, phys_const, cosmo, bp, f_edd); + return mdot_medd * Eddington_rate / m_dot_inflow; + break; + } + default: + error("Invalid black hole state."); + return 0.; + break; + } +} + +/** + * @brief Computes the time-step of a given black hole particle. + * + * @param bp Pointer to the s-particle data. + * @param props The properties of the black hole scheme. + * @param phys_const The physical phys_const (in internal units). + */ +__attribute__((always_inline)) INLINE static float black_holes_compute_timestep( + const struct bpart *const bp, const struct black_holes_props *props, + const struct phys_const *phys_const, const struct cosmology *cosmo) { + + /* Allow for finer timestepping if necessary! */ + float dt_accr = FLT_MAX; + float dt_overall = FLT_MAX; + float dt_kick = FLT_MAX; + const float min_subgrid_mass = props->minimum_black_hole_mass_unresolved; + + /* Only limit when in the resolved feedback regime */ + if (bp->accretion_rate > 0.f && bp->subgrid_mass > min_subgrid_mass) { + dt_accr = props->dt_accretion_factor * bp->mass / bp->accretion_rate; + + if (bp->state == BH_states_adaf && bp->jet_mass_loading > 0.f) { + dt_kick = bp->ngb_mass / (bp->jet_mass_loading * bp->accretion_rate); + } else { + if (bp->f_accretion > 0.f) { + /* Make sure that the wind mass does not exceed the kernel gas mass */ + const float psi = (1.f - bp->f_accretion) / bp->f_accretion; + dt_kick = bp->ngb_mass / (psi * bp->accretion_rate); + } + } + + dt_overall = min(dt_kick, dt_accr); + } + + if (dt_overall < props->time_step_min) { + message( + "Warning! BH_TIMESTEP_LOW: id=%lld (%g Myr) is below time_step_min (%g " + "Myr).", + bp->id, dt_overall * props->time_to_Myr, + props->time_step_min * props->time_to_Myr); + } + + return max(dt_overall, props->time_step_min); +} + +/** + * @brief Initialises the b-particles for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions. + * + * @param bp The particle to act upon + * @param props The properties of the black holes model. + */ +__attribute__((always_inline)) INLINE static void black_holes_first_init_bpart( + struct bpart *bp, const struct black_holes_props *props) { + + bp->time_bin = 0; + if (props->use_subgrid_mass_from_ics == 0) { + bp->subgrid_mass = bp->mass; + } else if (props->with_subgrid_mass_check && bp->subgrid_mass <= 0) { + error( + "Black hole %lld has a subgrid mass of %f (internal units).\n" + "If this is because the ICs do not contain a 'SubgridMass' data " + "set, you should set the parameter " + "'ObsidianAGN:use_subgrid_mass_from_ics' to 0 to initialize the " + "black hole subgrid masses to the corresponding dynamical masses.\n" + "If the subgrid mass is intentionally set to this value, you can " + "disable this error by setting 'ObsidianAGN:with_subgrid_mass_check' " + "to 0.", + bp->id, bp->subgrid_mass); + } + bp->total_accreted_mass = 0.f; + bp->accretion_disk_mass = 0.f; + bp->accretion_rate = 0.f; + bp->bondi_accretion_rate = 0.f; + bp->formation_time = -1.f; + bp->cumulative_number_seeds = 1; + bp->number_of_mergers = 0; + bp->number_of_gas_swallows = 0; + bp->number_of_direct_gas_swallows = 0; + bp->number_of_repositions = 0; + bp->number_of_reposition_attempts = 0; + bp->number_of_time_steps = 0; + bp->last_minor_merger_time = -1.; + bp->last_major_merger_time = -1.; + bp->swallowed_angular_momentum[0] = 0.f; + bp->swallowed_angular_momentum[1] = 0.f; + bp->swallowed_angular_momentum[2] = 0.f; + bp->accreted_angular_momentum[0] = 0.f; + bp->accreted_angular_momentum[1] = 0.f; + bp->accreted_angular_momentum[2] = 0.f; + bp->last_repos_vel = 0.f; + bp->radiative_luminosity = 0.f; + bp->delta_energy_this_timestep = 0.f; + bp->state = BH_states_slim_disk; + bp->radiative_efficiency = 0.f; + bp->f_accretion = 0.f; + bp->m_dot_inflow = 0.f; + bp->cold_disk_mass = 0.f; + bp->jet_mass_reservoir = 0.f; + /* Default to the original value at fixed jet_velocity */ + bp->jet_mass_loading = props->jet_mass_loading; + bp->jet_mass_kicked_this_step = 0.f; + bp->adaf_energy_to_dump = 0.f; + bp->adaf_energy_used_this_step = 0.f; + /* Large value signifies its not in a galaxy */ + bp->galactocentric_radius = FLT_MAX; +} + +/** + * @brief Prepares a b-particle for its interactions + * + * @param bp The particle to act upon + */ +__attribute__((always_inline)) INLINE static void black_holes_init_bpart( + struct bpart *bp) { + +#ifdef DEBUG_INTERACTIONS_BLACK_HOLES + for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_STARS; ++i) + bp->ids_ngbs_density[i] = -1; + bp->num_ngb_density = 0; +#endif + + bp->density.wcount = 0.f; + bp->density.wcount_dh = 0.f; + bp->rho_gas = 0.f; + bp->sound_speed_gas = 0.f; + bp->internal_energy_gas = 0.f; + bp->gas_SFR = 0.f; + bp->hot_gas_mass = 0.f; + bp->cold_gas_mass = 0.f; + bp->cold_disk_mass = 0.f; + bp->hot_gas_internal_energy = 0.f; + bp->sound_speed_subgrid_gas = -1.f; + bp->velocity_gas[0] = 0.f; + bp->velocity_gas[1] = 0.f; + bp->velocity_gas[2] = 0.f; + bp->circular_velocity_gas[0] = 0.f; + bp->circular_velocity_gas[1] = 0.f; + bp->circular_velocity_gas[2] = 0.f; + bp->angular_momentum_gas[0] = 0.f; + bp->angular_momentum_gas[1] = 0.f; + bp->angular_momentum_gas[2] = 0.f; + bp->stellar_mass = 0.f; + bp->stellar_bulge_mass = 0.f; + bp->radiative_luminosity = 0.f; + bp->ngb_mass = 0.f; + bp->gravitational_ngb_mass = 0.f; + bp->num_ngbs = 0; + bp->num_gravitational_ngbs = 0; + bp->reposition.delta_x[0] = -FLT_MAX; + bp->reposition.delta_x[1] = -FLT_MAX; + bp->reposition.delta_x[2] = -FLT_MAX; + bp->reposition.min_potential = FLT_MAX; + bp->reposition.potential = FLT_MAX; + bp->accretion_rate = 0.f; /* Optionally accumulated ngb-by-ngb */ + bp->bondi_accretion_rate = 0.f; /* Optionally accumulated ngb-by-ngb */ + bp->mass_at_start_of_step = bp->mass; /* bp->mass may grow in nibbling mode */ + bp->m_dot_inflow = 0.f; /* reset accretion rate */ + bp->kernel_wt_sum = 0.f; + + /* update the reservoir */ + bp->jet_mass_reservoir -= bp->jet_mass_kicked_this_step; + bp->jet_mass_kicked_this_step = 0.f; + if (bp->jet_mass_reservoir < 0.f) { + bp->jet_mass_reservoir = 0.f; /* reset reservoir if used up */ + } + /* update the unresolved reservoir */ + bp->unresolved_mass_reservoir -= bp->unresolved_mass_kicked_this_step; + bp->unresolved_mass_kicked_this_step = 0.f; + if (bp->unresolved_mass_reservoir < 0.f) { + bp->unresolved_mass_reservoir = 0.f; + } + /* update the adaf energy reservoir */ + if (bp->adaf_wt_sum > 0.f) { + const double adaf_energy_used = + bp->adaf_energy_used_this_step / bp->adaf_wt_sum; + bp->adaf_energy_to_dump -= adaf_energy_used; + bp->adaf_wt_sum = 0.f; + bp->adaf_energy_used_this_step = 0.f; + if (bp->adaf_energy_to_dump < 0.f) { + bp->adaf_energy_to_dump = 0.f; + } + } else { + bp->adaf_energy_used_this_step = 0.f; + } +} + +/** + * @brief Predict additional particle fields forward in time when drifting + * + * The fields do not get predicted but we move the BH to its new position + * if a new one was calculated in the repositioning loop. + * + * @param bp The particle + * @param dt_drift The drift time-step for positions. + */ +__attribute__((always_inline)) INLINE static void black_holes_predict_extra( + struct bpart *restrict bp, float dt_drift) { + + /* Are we doing some repositioning? */ + if (bp->reposition.min_potential != FLT_MAX) { + +#ifdef SWIFT_DEBUG_CHECKS + if (bp->reposition.delta_x[0] == -FLT_MAX || + bp->reposition.delta_x[1] == -FLT_MAX || + bp->reposition.delta_x[2] == -FLT_MAX) { + error("Something went wrong with the new repositioning position"); + } + + const double dx = bp->reposition.delta_x[0]; + const double dy = bp->reposition.delta_x[1]; + const double dz = bp->reposition.delta_x[2]; + const double d = sqrt(dx * dx + dy * dy + dz * dz); + if (d > 1.01 * kernel_gamma * bp->h) + error("Repositioning BH beyond the kernel support!"); +#endif + + /* Move the black hole */ + bp->x[0] += bp->reposition.delta_x[0]; + bp->x[1] += bp->reposition.delta_x[1]; + bp->x[2] += bp->reposition.delta_x[2]; + + /* Move its gravity properties as well */ + bp->gpart->x[0] += bp->reposition.delta_x[0]; + bp->gpart->x[1] += bp->reposition.delta_x[1]; + bp->gpart->x[2] += bp->reposition.delta_x[2]; + + /* Store the delta position */ + bp->x_diff[0] -= bp->reposition.delta_x[0]; + bp->x_diff[1] -= bp->reposition.delta_x[1]; + bp->x_diff[2] -= bp->reposition.delta_x[2]; + + /* Reset the reposition variables */ + bp->reposition.delta_x[0] = -FLT_MAX; + bp->reposition.delta_x[1] = -FLT_MAX; + bp->reposition.delta_x[2] = -FLT_MAX; + bp->reposition.min_potential = FLT_MAX; + + /* Count the jump */ + bp->number_of_repositions++; + } +} + +/** + * @brief Sets the values to be predicted in the drifts to their values at a + * kick time + * + * @param bp The particle. + */ +__attribute__((always_inline)) INLINE static void +black_holes_reset_predicted_values(struct bpart *bp) {} + +/** + * @brief Kick the additional variables + * + * @param bp The particle to act upon + * @param dt The time-step for this kick + */ +__attribute__((always_inline)) INLINE static void black_holes_kick_extra( + struct bpart *bp, float dt) {} + +/** + * @brief Finishes the calculation of density on black holes + * + * @param bp The particle to act upon + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void black_holes_end_density( + struct bpart *bp, const struct cosmology *cosmo) { + + /* Some smoothing length multiples. */ + const float h = bp->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */ + + /* --- Finish the calculation by inserting the missing h factors --- */ + bp->density.wcount *= h_inv_dim; + bp->density.wcount_dh *= h_inv_dim_plus_one; + bp->rho_gas *= h_inv_dim; + float rho_inv = 1.f; + if (bp->rho_gas > 0.f) rho_inv = 1.f / bp->rho_gas; + + /* For the following, we also have to undo the mass smoothing + * (N.B.: bp->velocity_gas is in BH frame, in internal units). */ + bp->sound_speed_gas *= h_inv_dim * rho_inv; + bp->internal_energy_gas *= h_inv_dim * rho_inv; + + /* Non-weighted (no decoupled winds) properties below. + * All mass-weighted quantities are for the hot & cold gas */ + float m_hot_inv = 1.f; + if (bp->hot_gas_mass > 0.f) m_hot_inv /= bp->hot_gas_mass; + /* Or the total mass */ + float m_tot_inv = 1.f; + if (bp->ngb_mass > 0.f) m_tot_inv /= bp->ngb_mass; + + bp->hot_gas_internal_energy *= m_hot_inv; + bp->velocity_gas[0] *= m_tot_inv; + bp->velocity_gas[1] *= m_tot_inv; + bp->velocity_gas[2] *= m_tot_inv; + bp->circular_velocity_gas[0] *= m_tot_inv; + bp->circular_velocity_gas[1] *= m_tot_inv; + bp->circular_velocity_gas[2] *= m_tot_inv; + + /* Calculate circular velocity at the smoothing radius from specific + * angular momentum (extra h_inv). It is now a VELOCITY. + */ + bp->circular_velocity_gas[0] *= h_inv; + bp->circular_velocity_gas[1] *= h_inv; + bp->circular_velocity_gas[2] *= h_inv; +} + +/** + * @brief Sets all particle fields to sensible values when the #spart has 0 + * ngbs. + * + * @param bp The particle to act upon + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void +black_holes_bpart_has_no_neighbours(struct bpart *bp, + const struct cosmology *cosmo) { + + // warning( + // "BH particle with ID %lld treated as having no neighbours (h: %g, " + // "wcount: %g).", + // bp->id, bp->h, bp->density.wcount); + + /* Some smoothing length multiples. */ + const float h = bp->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + + /* Re-set problematic values */ + bp->density.wcount = kernel_root * h_inv_dim; + bp->density.wcount_dh = 0.f; + + bp->velocity_gas[0] = FLT_MAX; + bp->velocity_gas[1] = FLT_MAX; + bp->velocity_gas[2] = FLT_MAX; + + bp->internal_energy_gas = -FLT_MAX; + bp->hot_gas_internal_energy = -FLT_MAX; +} + +/** + * @brief Return the current instantaneous accretion rate of the BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_accretion_rate(const struct bpart *bp) { + return bp->accretion_rate; +} + +/** + * @brief Return the total accreted gas mass of this BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_accreted_mass(const struct bpart *bp) { + return bp->total_accreted_mass; +} + +/** + * @brief Return the subgrid mass of this BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_subgrid_mass(const struct bpart *bp) { + return bp->subgrid_mass; +} + +/** + * @brief Return the current bolometric luminosity of the BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_bolometric_luminosity(const struct bpart *bp, + const struct phys_const *phys_const) { + const double c = phys_const->const_speed_light_c; + return bp->accretion_rate * bp->radiative_efficiency * c * c; +} + +/** + * @brief Return the current kinetic jet power of the BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double black_holes_get_jet_power( + const struct bpart *bp, const struct phys_const *phys_const) { + const double c = phys_const->const_speed_light_c; + /* accretion_rate is M_dot,acc from the paper */ + return bp->radiative_efficiency * bp->accretion_rate * c * c; +} + +/** + * @brief Update the properties of a black hole particles by swallowing + * a gas particle. + * + * @param bp The #bpart to update. + * @param p The #part that is swallowed. + * @param xp The #xpart that is swallowed. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void black_holes_swallow_part( + struct bpart *bp, const struct part *p, const struct xpart *xp, + const struct cosmology *cosmo) { + + /* Get the current dynamical masses */ + const float gas_mass = hydro_get_mass(p); + const float BH_mass = bp->mass; + + /* Increase the dynamical mass of the BH. */ + bp->mass += gas_mass; + bp->gpart->mass += gas_mass; + + /* Physical velocity difference between the particles */ + const float dv[3] = {(bp->v[0] - p->v[0]) * cosmo->a_inv, + (bp->v[1] - p->v[1]) * cosmo->a_inv, + (bp->v[2] - p->v[2]) * cosmo->a_inv}; + + /* Physical distance between the particles */ + const float dx[3] = {(bp->x[0] - p->x[0]) * cosmo->a, + (bp->x[1] - p->x[1]) * cosmo->a, + (bp->x[2] - p->x[2]) * cosmo->a}; + + /* Collect the swallowed angular momentum */ + bp->swallowed_angular_momentum[0] += + gas_mass * (dx[1] * dv[2] - dx[2] * dv[1]); + bp->swallowed_angular_momentum[1] += + gas_mass * (dx[2] * dv[0] - dx[0] * dv[2]); + bp->swallowed_angular_momentum[2] += + gas_mass * (dx[0] * dv[1] - dx[1] * dv[0]); + + /* Update the BH momentum */ + const float BH_mom[3] = {BH_mass * bp->v[0] + gas_mass * p->v[0], + BH_mass * bp->v[1] + gas_mass * p->v[1], + BH_mass * bp->v[2] + gas_mass * p->v[2]}; + + bp->v[0] = BH_mom[0] / bp->mass; + bp->v[1] = BH_mom[1] / bp->mass; + bp->v[2] = BH_mom[2] / bp->mass; + bp->gpart->v_full[0] = bp->v[0]; + bp->gpart->v_full[1] = bp->v[1]; + bp->gpart->v_full[2] = bp->v[2]; + + const float dr = sqrt(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]); + message( + "BH %lld swallowing gas particle %lld " + "(Delta_v = [%f, %f, %f] U_V, " + "Delta_x = [%f, %f, %f] U_L, " + "Delta_v_rad = %f)", + bp->id, p->id, -dv[0], -dv[1], -dv[2], -dx[0], -dx[1], -dx[2], + (dv[0] * dx[0] + dv[1] * dx[1] + dv[2] * dx[2]) / dr); + + /* Update the BH metal masses */ + struct chemistry_bpart_data *bp_chem = &bp->chemistry_data; + const struct chemistry_part_data *p_chem = &p->chemistry_data; + chemistry_add_part_to_bpart(bp_chem, p_chem, gas_mass); + + /* This BH swallowed a gas particle */ + bp->number_of_gas_swallows++; + bp->number_of_direct_gas_swallows++; + + /* This BH lost a neighbour */ + bp->num_ngbs--; + bp->num_gravitational_ngbs--; + bp->ngb_mass -= gas_mass; +} + +/** + * @brief Update the properties of a black hole particles by swallowing + * a BH particle. + * + * @param bpi The #bpart to update. + * @param bpj The #bpart that is swallowed. + * @param cosmo The current cosmological model. + * @param time Time since the start of the simulation (non-cosmo mode). + * @param with_cosmology Are we running with cosmology? + * @param props The properties of the black hole scheme. + */ +__attribute__((always_inline)) INLINE static void black_holes_swallow_bpart( + struct bpart *bpi, const struct bpart *bpj, const struct cosmology *cosmo, + const double time, const int with_cosmology, + const struct black_holes_props *props, + const struct phys_const *phys_const) { + + /* Get the current dynamical masses */ + const float bpi_dyn_mass = bpi->mass; + const float bpj_dyn_mass = bpj->mass; + + /* Is this merger ratio above the threshold for recording? */ + const double merger_ratio = bpj->subgrid_mass / bpi->subgrid_mass; + if (merger_ratio > props->major_merger_threshold) { + if (with_cosmology) { + bpi->last_major_merger_scale_factor = cosmo->a; + } else { + bpi->last_major_merger_time = time; + } + } else if (merger_ratio > props->minor_merger_threshold) { + if (with_cosmology) { + bpi->last_minor_merger_scale_factor = cosmo->a; + } else { + bpi->last_minor_merger_time = time; + } + } + + /* Increase the masses of the BH. */ + bpi->mass += bpj->mass; + bpi->gpart->mass += bpj->mass; + bpi->subgrid_mass += bpj->subgrid_mass; + + /* Collect the swallowed angular momentum */ + bpi->swallowed_angular_momentum[0] += bpj->swallowed_angular_momentum[0]; + bpi->swallowed_angular_momentum[1] += bpj->swallowed_angular_momentum[1]; + bpi->swallowed_angular_momentum[2] += bpj->swallowed_angular_momentum[2]; + + /* Update the BH momentum */ + const float BH_mom[3] = {bpi_dyn_mass * bpi->v[0] + bpj_dyn_mass * bpj->v[0], + bpi_dyn_mass * bpi->v[1] + bpj_dyn_mass * bpj->v[1], + bpi_dyn_mass * bpi->v[2] + bpj_dyn_mass * bpj->v[2]}; + + bpi->v[0] = BH_mom[0] / bpi->mass; + bpi->v[1] = BH_mom[1] / bpi->mass; + bpi->v[2] = BH_mom[2] / bpi->mass; + bpi->gpart->v_full[0] = bpi->v[0]; + bpi->gpart->v_full[1] = bpi->v[1]; + bpi->gpart->v_full[2] = bpi->v[2]; + + /* Update the BH metal masses */ + struct chemistry_bpart_data *bpi_chem = &bpi->chemistry_data; + const struct chemistry_bpart_data *bpj_chem = &bpj->chemistry_data; + chemistry_add_bpart_to_bpart(bpi_chem, bpj_chem); + + /* Update the energy reservoir */ + bpi->jet_mass_reservoir += bpj->jet_mass_reservoir; + + /* Add up all the BH seeds */ + bpi->cumulative_number_seeds += bpj->cumulative_number_seeds; + + /* Add up all the gas particles we swallowed */ + bpi->number_of_gas_swallows += bpj->number_of_gas_swallows; + + /* Add the subgrid angular momentum that we swallowed */ + bpi->accreted_angular_momentum[0] += bpj->accreted_angular_momentum[0]; + bpi->accreted_angular_momentum[1] += bpj->accreted_angular_momentum[1]; + bpi->accreted_angular_momentum[2] += bpj->accreted_angular_momentum[2]; + + /* We had another merger */ + bpi->number_of_mergers++; +//#ifdef OBSIDIAN_DEBUG_CHECKS + const float galaxy_mstar_i = bpi->galaxy_data.stellar_mass; + const float galaxy_sfr_i = bpi->galaxy_data.stellar_mass * bpi->galaxy_data.specific_sfr; + const float galaxy_mstar_j = bpj->galaxy_data.stellar_mass; + const float galaxy_sfr_j = bpj->galaxy_data.stellar_mass * bpj->galaxy_data.specific_sfr; + printf( + "BH_MERGER:" + " z=%g bid_i=%lld bid_j=%lld mdyni=%g mdynj=%g mbhi=%g mbhj=%g nmerge=%d" + " galM*_i=%g galSFR_i=%g galM*_j=%g galSFR_j=%g\n", + cosmo->z, bpi->id, bpj->id, + bpi->mass * props->mass_to_solar_mass, + bpj->mass * props->mass_to_solar_mass, + bpi->subgrid_mass * props->mass_to_solar_mass, + bpj->subgrid_mass * props->mass_to_solar_mass, + bpi->number_of_mergers, + galaxy_mstar_i * props->mass_to_solar_mass, + galaxy_sfr_i * props->mass_to_solar_mass / props->time_to_yr, + galaxy_mstar_j * props->mass_to_solar_mass, + galaxy_sfr_j * props->mass_to_solar_mass / props->time_to_yr); +//#endif +} + +/** + * @brief Function to generate a random number from a Gaussian distribution. + * @param mu Mean of Gaussian + * @param sigma Standard deviation of Gaussian + * @param u1 Random number in (0,1) + * @param u2 Random number in (0,1) + */ +__attribute__((always_inline)) INLINE static float gaussian_random_number( + float mu, float sigma, double u1, double u2) { + double mag, z0, z1; + + /* Apply the Box-Muller transform */ + mag = sigma * sqrt(-2.0 * log(u1)); + z0 = mag * cos(2.0 * M_PI * u2) + mu; + z1 = mag * sin(2.0 * M_PI * u2) + mu; + if (u1 + u2 < 1.f) { + return z0; + } + return z1; +} + +/** + * @brief Compute the accretion rate of the black hole and all the quantities + * required for the feedback loop. + * + * @param bp The black hole particle. + * @param props The properties of the black hole scheme. + * @param phys_const The physical phys_const (in internal units). + * @param cosmo The cosmological model. + * @param cooling Properties of the cooling model. + * @param floor_props Properties of the entropy fllor. + * @param time Time since the start of the simulation (non-cosmo mode). + * @param with_cosmology Are we running with cosmology? + * @param dt The time-step size (in physical internal units). + * @param ti_begin The time at which the step begun (ti_current). + */ +__attribute__((always_inline)) INLINE static void black_holes_prepare_feedback( + struct bpart *restrict bp, const struct black_holes_props *props, + const struct phys_const *phys_const, const struct cosmology *cosmo, + const struct cooling_function_data *cooling, + const struct entropy_floor_properties *floor_props, const double time, + const int with_cosmology, const double dt, const integertime_t ti_begin) { + + /* Record that the black hole has another active time step */ + bp->number_of_time_steps++; + + if (dt == 0. || bp->rho_gas == 0. || bp->h == 0.) return; + + /* Collect information about galaxy that the particle belongs to */ + const float galaxy_mstar = bp->galaxy_data.stellar_mass; + const float galaxy_mgas = bp->galaxy_data.gas_mass; + + /* A black hole should never accrete/feedback if it is not in a galaxy */ + if (galaxy_mstar <= 0.f) return; + + /* Gather some physical phys_const (all in internal units) */ + const double G = phys_const->const_newton_G; + const double c = phys_const->const_speed_light_c; + const double proton_mass = phys_const->const_proton_mass; + const double sigma_Thomson = phys_const->const_thomson_cross_section; + + /* Gather the parameters of the model */ + const double f_Edd_maximum = props->f_Edd_maximum; + +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(bp->subgrid_mass)) error("subgrid_mass nan"); +#endif + /* (Subgrid) mass of the BH (internal units) */ + const double BH_mass = bp->subgrid_mass; + + /* Compute the Eddington rate (internal units). + * IMPORTANT: epsilon_r = 0.1 is the SET value for the Eddington rate. + * It is assumed in all of the derivations for the state model. + */ + const double Eddington_rate = + 4. * M_PI * G * BH_mass * proton_mass / (0.1 * c * sigma_Thomson); + + const double bh_h = kernel_gamma * bp->h; + const double bh_h_inv = 1. / bh_h; + const double volume_bh_inv = + (3. / (4. * M_PI)) * bh_h_inv * bh_h_inv * bh_h_inv; + double gas_rho = 0.; + if (props->bondi_use_all_gas) { + gas_rho = bp->rho_gas; + } else { + gas_rho = bp->hot_gas_mass * volume_bh_inv; + } + + const double gas_rho_phys = gas_rho * cosmo->a3_inv; + + /* We can now compute the Bondi accretion rate (internal units) + * D. Rennehan: In Simba, we only consider the hot gas within + * the kernel for the Bondi rate, and the cold gas using the + * torque accretion estimator. + */ + double Bondi_rate = 0.; + + /* Use all gas within the kernel or only the hot gas*/ + double gas_internal_energy = 0.; + if (props->bondi_use_all_gas) { + if (bp->internal_energy_gas > 0.) { + gas_internal_energy = bp->internal_energy_gas; + } + } else { + if (bp->hot_gas_internal_energy > 0.) { + gas_internal_energy = bp->hot_gas_internal_energy; + } + } + + /* Check if there is hot/any gas in the kernel */ + if (gas_internal_energy > 0.) { + double gas_c = + gas_soundspeed_from_internal_energy(gas_rho, gas_internal_energy); + + if (gas_c > 0.) { + const double gas_c_phys_inv = 1. / (cosmo->a_factor_sound_speed * gas_c); + + float BH_mass_bondi = BH_mass; + if (BH_mass_bondi > props->bondi_BH_mass_cap) + BH_mass_bondi = props->bondi_BH_mass_cap; + + Bondi_rate = 4. * M_PI * G * G * BH_mass_bondi * BH_mass_bondi * gas_rho_phys * + gas_c_phys_inv * gas_c_phys_inv * gas_c_phys_inv; + + /* In the case of standard Bondi, we limit it to the Eddington rate */ + Bondi_rate = + fmin(Bondi_rate, props->f_Edd_Bondi_maximum * Eddington_rate); + } + } + + /* The accretion rate estimators give Mdot,inflow + * (Mdot,BH = f_acc * Mdot,inflow) */ + const double bondi_accr_rate = props->bondi_alpha * Bondi_rate; + + /* Compute the torque-limited accretion rate */ + double torque_accr_rate = 0.; + + double f_corr_stellar = 10.; + if (galaxy_mgas > 0.) { + f_corr_stellar = min(galaxy_mstar / galaxy_mgas, f_corr_stellar); + } + + /* Torque accretion rate based on some fraction of gas near BH + * falling in on dynamical time. (This is the default.) + * Here the accretion rate is only based on Mgas / tdyn. + * We do not use the DM mass to compute tdyn since it probably + * doesn't contribute much near the core of the system. We also + * assume that the gas fraction is constant in the galaxy in + * order to compute Mstar within the kernel of the black hole. + * Therefore, Mdot = Mgas / tdyn = Mgas / sqrt(3pi/(32 G rho)) + * and rho = (Mgas + Mstar + Mdm) / (4pi h^3 / 3) where + * Mstar = Mgas / fgas, Mdm = 0. Therefore, + * rho = 3 * ((1 + fgas) / fgas) * Mgas / (4 * pi * h^3) + * and + * Mdot = Mgas * sqrt(32 * G * 3 * ((1 + fgas) / fgas) * Mgas)) / + * sqrt(3 * pi * 4 * pi * h^3) + * = sqrt(96 * G * ((1 + fgas) / fgas) * Mgas^3) / + * sqrt(12 * pi^2 * h^3) + * = (1 / pi) * sqrt(8 * G * ((1 + fgas) / fgas) * (Mgas / h)^3)) + */ + float tdyn_inv = FLT_MAX; + const float potential = fabs(gravity_get_comoving_potential(bp->gpart)); + /* Includes dynamical mass of the BH */ + switch (props->dynamical_time_calculation_method) { + /* Assume gas fraction is the same in the kernel and outside */ + case 0: { + /* Compute correction to total dynamical mass around + * BH contributed by stars */ + const float m_star_gal = galaxy_mstar; + const float m_gas_cold_gal = galaxy_mgas; + const float m_gas_bh = bp->gravitational_ngb_mass; + const float m_bh = bp->mass; + + /* Compute stellar mass assuming a constant cold gas fraction in the + * entire galaxy. If m_gas_cold_bh is zero it doesn't matter since + * the BH won't acrrete in the torque mode anyway. */ + const float m_gas_cold_bh = bp->cold_gas_mass; + float m_star_bh = 0.; + if (m_gas_cold_gal > 0.) { + m_star_bh = fmin(m_star_gal / m_gas_cold_gal, 10.f) * m_gas_cold_bh; + } + + /* Have to assume baryon dominance within the kernel */ + const float rho_est = (m_star_bh + m_gas_bh + m_bh) * volume_bh_inv; + + /* Inverse physical dynamical time */ + tdyn_inv = sqrt(32. * G * rho_est * cosmo->a3_inv / (3. * M_PI)); + break; + } + + /* Assume BH potential */ + case 1: + if (potential >= 0.f) { + tdyn_inv = (sqrt(potential) / bh_h) * cosmo->a2_inv; + } + break; + + /* Assume dynamical time from the kernel mass */ + case 2: { + /* do not have gravity_props here */ + const float hsml = bh_h; + const float volume = (4. * M_PI / 3.) * hsml * hsml * hsml; + const float rho = bp->mass / volume; + tdyn_inv = sqrt(32. * G * rho * cosmo->a3_inv / (3. * M_PI)); + break; + } + + default: + error("Unknown dynamical time calculation method %d", + props->dynamical_time_calculation_method); + break; + } + + /* Compute inverse of accretion time into BH */ + tdyn_inv /= props->dynamical_time_factor; + + /* Limit by max dynamical time, with z=0 value scaled by H0/H */ + if (props->dynamical_time_max > 0.) { + const float t_inv_min = cosmo->H / (props->dynamical_time_max * cosmo->H0); + tdyn_inv = fmax(tdyn_inv, t_inv_min); + } + + /* Create a spread in accretion times, with minimum at + * free-fall time=0.5*tdyn */ + const float tdyn_sigma = props->tdyn_sigma; + if (tdyn_sigma > 0.f) { + const double ran1 = + random_unit_interval(bp->id, ti_begin, random_number_BH_swallow); + const double ran2 = + random_unit_interval(bp->id, ti_begin, random_number_BH_swallow); + const float gaussian_random = + gaussian_random_number(0.f, tdyn_sigma, ran1, ran2); + tdyn_inv /= 0.5 * (1.f + fabs(gaussian_random)); + } + + float f_suppress = 1.f; + + const float corot_gas_mass = + bp->cold_gas_mass - 2. * (bp->cold_gas_mass - bp->cold_disk_mass); + float torque_norm = fabs(props->torque_accretion_norm); + if (props->torque_accretion_norm < 0.f && bp->subgrid_mass * props->mass_to_solar_mass > 1.e9) { + torque_norm *= exp(-bp->subgrid_mass * props->mass_to_solar_mass * 1.e-9) * 2.71828; // suppress torque accr + } + if (torque_norm > 0.f) { + switch (props->torque_accretion_method) { + case 0: + if (galaxy_mgas > 0.) { + torque_accr_rate = + torque_norm * bp->cold_disk_mass * tdyn_inv; + } + break; + + case 1: + if (corot_gas_mass > 0. && bp->cold_gas_mass > 0.) { + torque_accr_rate = + torque_norm * corot_gas_mass * tdyn_inv; + } + break; + + case 2: + if (corot_gas_mass > 0. && bp->cold_gas_mass > 0.) { + const float m_disk = bp->cold_gas_mass * f_corr_stellar; + const float f_disk = corot_gas_mass / bp->cold_gas_mass; + + const float r0 = bh_h * cosmo->a * (props->length_to_parsec * 0.01); + + const float alpha = 5.; + const float mass_to_1e9solar = props->mass_to_solar_mass * 1.0e-9; + const float mass_to_1e8solar = props->mass_to_solar_mass * 1.0e-8; + + const float f0 = + 0.31 * f_disk * f_disk * pow(m_disk * mass_to_1e9solar, -1. / 3.); + const float f_gas = corot_gas_mass / m_disk; + const float mass_in_1e8solar = BH_mass * mass_to_1e8solar; + + torque_accr_rate = torque_norm * alpha * + corot_gas_mass * mass_to_1e9solar * + powf(f_disk, 5. / 2.) * + powf(mass_in_1e8solar, 1. / 6.) * + powf(r0, -3. / 2.) / (1. + f0 / f_gas); + torque_accr_rate *= (props->time_to_yr / props->mass_to_solar_mass); + } + break; + + case 3: + if (galaxy_mgas > 0.) { + torque_accr_rate = + torque_norm * bp->cold_gas_mass * tdyn_inv; + } + break; + + default: + error("Unknown torque_accretion_method=%d", + props->torque_accretion_method); + break; + } + + /* Do suppression of BH growth */ + switch (props->suppress_growth) { + case 1: { + const double r0 = bh_h * cosmo->a * props->length_to_parsec; + const double sigma_eff = f_corr_stellar * bp->ngb_mass * + props->mass_to_solar_mass / (M_PI * r0 * r0); + + f_suppress = sigma_eff / (sigma_eff + 3000.); + break; + } + + case 2: + case 6: + case 7: { + double m_suppress = fabs(props->bh_characteristic_suppression_mass); + if (props->bh_characteristic_suppression_mass < 0) { + m_suppress *= cosmo->a; + } + + f_suppress = + 1. - exp(-BH_mass * props->mass_to_solar_mass / m_suppress); + break; + } + + case 4: + case 5: { + /* compute mass loading factor from SF feedback, + * should be same as used in feedback_mass_loading_factor() + */ + const float galaxy_stellar_mass = galaxy_mstar; + const float eta_norm = props->FIRE_eta_normalization; + const float eta_break = props->FIRE_eta_break; + const float eta_lower_slope = props->FIRE_eta_lower_slope; + const float eta_upper_slope = props->FIRE_eta_upper_slope; + const float eta_lower_slope_EOR = props->FIRE_eta_lower_slope; + const float eta_minmass = props->minimum_galaxy_stellar_mass; + const float eta_suppress = props->wind_eta_suppression_redshift; + + const double eta = feedback_mass_loading_factor( + cosmo, galaxy_stellar_mass, eta_minmass, eta_norm, eta_break, + eta_lower_slope, eta_upper_slope, eta_lower_slope_EOR, + eta_suppress); + + if (bp->cold_gas_mass * tdyn_inv > 0.f) { + /* star formation efficiency, frac of gas converted + * to stars per tdyn */ + float sf_eff = props->suppression_sf_eff; + if (sf_eff < 0.f) { + /* SF efficiency within BH kernel. Cap at cloud-scale SFE from + * Leroy+25 */ + sf_eff = fmin(bp->gas_SFR / (tdyn_inv * bp->cold_gas_mass), + fabs(sf_eff)); + } + + /* Suppresses accretion by factor accounting for mass + * lost in outflow over accretion time. ODE: + * dM/dt = -eta * sf_eff * M / tdyn */ + f_suppress = exp(-eta * sf_eff); + // message("BH_SUPPRESS: z=%g id=%lld M*=%g eta=%g eff=%g tfac=%g + // fsupp=%g", cosmo->z, bp->id, galaxy_stellar_mass * + // props->mass_to_solar_mass, eta, sf_eff, t_accrete * tdyn_inv, + // exp(-eta * sf_eff * t_accrete * tdyn_inv)); + } + break; + } + + default: + break; + } + } + + /* Apply suppression factor to torque accretion */ + torque_accr_rate *= f_suppress; + + /* If not near galaxy center, torque accr is suppressed + const float suppression_distance = kernel_gravity_softening_plummer_equivalent_inv * + props->max_reposition_distance_ratio * bp->h; + if (bp->galactocentric_radius > suppression_distance) { + torque_accr_rate *= exp(-(bp->galactocentric_radius - suppression_distance) / suppression_distance); + torque_accr_rate = 0.f; + }*/ + +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(bondi_accr_rate)) error("bondi_accr_rate nan"); + if (isnan(torque_accr_rate)) error("torque_accr_rate nan"); +#endif + + /* Right now this is M_dot,inflow. We will multiply by + * f_accretion later to make it M_dot,acc */ + bp->accretion_rate = bondi_accr_rate + torque_accr_rate; + + /* We will use eddington_fraction_lower_boundary and + * eddington_fraction_upper_boundary to divide up the accretion rate + * in three regimes. + * + * In order to switch out of a regime (i.e. a state), it is necessary + * for the true accretion rate (compared to Eddington rate) to switch + * over the boundary. Therefore, before we switch a state we must calculate + * what the previous state predicts the true accretion rate onto the SMBH is, + * and then update the state if it crosses a boundary. + */ + + /* We need to store the full M_dot,inflow rate to calculate the + * fraction at high accretion rate */ + bp->m_dot_inflow = bp->accretion_rate; + const double f_accretion = get_black_hole_accretion_factor( + props, phys_const, cosmo, bp, Eddington_rate); + double predicted_mdot_medd = + bp->accretion_rate * f_accretion / Eddington_rate; + const float my_adaf_mass_limit = + get_black_hole_adaf_mass_limit(bp, props, cosmo); + + /* Switch between states depending on f_edd */ + switch (bp->state) { + case BH_states_adaf: + if (predicted_mdot_medd > props->eddington_fraction_upper_boundary) { + bp->state = BH_states_slim_disk; + break; + } + if (predicted_mdot_medd > props->eddington_fraction_lower_boundary) { + bp->state = BH_states_quasar; + } + + break; /* end case ADAF */ + case BH_states_quasar: + if (BH_mass > my_adaf_mass_limit && + predicted_mdot_medd < props->eddington_fraction_lower_boundary) { + bp->state = BH_states_adaf; + break; + } + + if (predicted_mdot_medd > props->eddington_fraction_upper_boundary) { + bp->state = BH_states_slim_disk; + } + + break; /* end case quasar */ + case BH_states_slim_disk: + if (BH_mass > my_adaf_mass_limit && + predicted_mdot_medd < props->eddington_fraction_lower_boundary) { + bp->state = BH_states_adaf; + break; + } + + if (predicted_mdot_medd < props->eddington_fraction_upper_boundary) { + bp->state = BH_states_quasar; + } + + break; /* end case slim disk */ + default: + error("Invalid black hole state."); + break; + } + + /* This depends on the new state */ + bp->f_accretion = get_black_hole_accretion_factor(props, phys_const, cosmo, + bp, Eddington_rate); +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(bp->f_accretion)) error("f_accretion nan"); +#endif + if (bp->f_accretion <= 1.e-10f) bp->f_accretion = 0.f; + + /* bp->accretion_rate is M_dot,acc in Rennehan+24 */ + bp->accretion_rate *= bp->f_accretion; + + /* Track Bondi accretion separately for diagnostics (remainder is torque) */ + bp->bondi_accretion_rate = bondi_accr_rate * bp->f_accretion; + + if (!props->bondi_use_all_gas) { + /* Now we can Eddington limit. */ + bp->accretion_rate = + min(bp->accretion_rate, f_Edd_maximum * Eddington_rate); + } + + /* All accretion is done, now we can set the eddington fraction */ + bp->eddington_fraction = bp->accretion_rate / Eddington_rate; + + /* Get the new radiative efficiency based on the new state */ + bp->radiative_efficiency = get_black_hole_radiative_efficiency( + props, bp->eddington_fraction, bp->state); +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(bp->radiative_efficiency)) error("radiative_efficiency nan"); +#endif + if (bp->radiative_efficiency < 1.e-10f) bp->radiative_efficiency = 0.f; + + double mass_rate = 0.; + const double luminosity = + bp->radiative_efficiency * bp->accretion_rate * c * c; + + /* Factor in the radiative efficiency, don't subtract + * jet BZ efficiency (spin is fixed) */ + mass_rate = (1. - bp->radiative_efficiency) * bp->accretion_rate; + + /* This is used for jet feedback later */ + bp->radiative_luminosity = luminosity; + +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(dt)) error("dt nan"); +#endif + /* Integrate forward in time */ + double delta_mass = mass_rate * dt; + + /* If desired we put mass into accretion disk which feeds BH on some + * frac of tdyn + */ + if (tdyn_inv > 0.f) { + /* Add accreted mass into a reservoir representing BH accretion disk */ + bp->accretion_disk_mass += delta_mass; + + /* Compute mass that will actually go into BH */ + delta_mass = bp->accretion_disk_mass * (1. - exp(-dt * tdyn_inv)); + + /* This mass gets removed from the accretion disk */ + if (bp->accretion_disk_mass > delta_mass) { + bp->accretion_disk_mass -= delta_mass; + } else { + delta_mass = bp->accretion_disk_mass; + bp->accretion_disk_mass = 0.; + } + + /* Recompute accretion rate based on the reservoir change */ + bp->accretion_rate = delta_mass / (dt * (1. - bp->radiative_efficiency)); + } + + bp->subgrid_mass += delta_mass; + bp->total_accreted_mass += delta_mass; + + /* Note: bp->subgrid_mass has been integrated, so avoid BH_mass variable */ + if (bp->state == BH_states_adaf && BH_mass > my_adaf_mass_limit) { + + /* ergs to dump in a kernel-weighted fashion */ + if (props->adaf_wind_mass_loading == 0.f) { + if (bp->subgrid_mass < my_adaf_mass_limit) { + bp->adaf_energy_to_dump = 0.f; + } + /*else if (bp->subgrid_mass < 1.5f * my_adaf_mass_limit) { + bp->adaf_energy_to_dump *= + 4.f * powf(bp->subgrid_mass / my_adaf_mass_limit - 1.f, 2.f); + }*/ + else { + bp->adaf_energy_to_dump = + get_black_hole_coupling(bp, props, cosmo, phys_const) * + props->adaf_disk_efficiency * bp->accretion_rate * c * c * dt; + } + } else { + const float adaf_v2 = props->adaf_wind_speed * props->adaf_wind_speed; + const float mass_this_step = + props->adaf_wind_mass_loading * bp->accretion_rate * dt; + bp->adaf_energy_to_dump += 0.5f * mass_this_step * adaf_v2; + } + } + + if (bp->state == BH_states_adaf || + (props->slim_disk_jet_active && bp->state == BH_states_slim_disk) || + (bp->radiative_luminosity > props->lum_thresh_always_jet && + props->lum_thresh_always_jet > 0.f)) { + + float jet_velocity = black_hole_compute_jet_velocity(bp, cosmo, props); + + /* If there is a variable jet velocity we must recalculate the mass loading + */ + if (jet_velocity != props->jet_velocity) { + const double c_over_v = phys_const->const_speed_light_c / jet_velocity; + + if (props->jet_loading_type == BH_jet_momentum_loaded) { + bp->jet_mass_loading = props->jet_efficiency * c_over_v; + } else if (props->jet_loading_type == BH_jet_mixed_loaded) { + const double energy_loading = + 2. * props->jet_efficiency * pow(c_over_v, 2.); + const double momentum_loading = props->jet_efficiency * c_over_v; + + /* Divide the contribution between energy and momentum loading */ + const double energy_term = props->jet_frac_energy * energy_loading; + const double momentum_term = + (1. - props->jet_frac_energy) * momentum_loading; + + bp->jet_mass_loading = energy_term + momentum_term; + } else { + bp->jet_mass_loading = 2. * props->jet_efficiency * pow(c_over_v, 2.); + } + + /* Psi_jet*M_dot,acc*dt is the total mass expected in the jet this step */ + bp->jet_mass_reservoir += bp->jet_mass_loading * bp->accretion_rate * dt; + } else { + bp->jet_mass_reservoir += + props->jet_mass_loading * bp->accretion_rate * dt; + } + } + + if (bp->subgrid_mass < bp->mass) { + /* In this case, the BH is still accreting from its (assumed) subgrid gas + * mass reservoir left over when it was formed. There is some loss in this + * due to radiative losses, so we must decrease the particle mass + * in proportion to its current accretion rate. We do not account for this + * in the swallowing approach, however. */ + bp->mass -= bp->radiative_efficiency * bp->accretion_rate * dt; + + if (bp->mass < 0) { + error("Black hole %lld reached negative mass (%g). Trouble ahead...", + bp->id, bp->mass); + } + + /* Make sure not to destroy low mass galaxies */ + if (bp->subgrid_mass > props->minimum_black_hole_mass_unresolved && + bp->state != BH_states_adaf) { + /* Make sure if many mergers have driven up the dynamical mass at low + * subgrid mass, that we still kick out particles! */ + const float psi = (1.f - bp->f_accretion) / bp->f_accretion; + bp->unresolved_mass_reservoir += psi * bp->accretion_rate * dt; + } + } + + /* Increase the subgrid angular momentum according to what we accreted + * Note that this is already in physical units, a factors from velocity and + * radius cancel each other. Also, the circular velocity contains an extra + * smoothing length factor that we undo here. */ + const double m_times_r = (mass_rate * dt) * bp->h; + /* L = m * r * v */ + bp->accreted_angular_momentum[0] += m_times_r * bp->circular_velocity_gas[0]; + bp->accreted_angular_momentum[1] += m_times_r * bp->circular_velocity_gas[1]; + bp->accreted_angular_momentum[2] += m_times_r * bp->circular_velocity_gas[2]; + + /* Keep v_kick physical, there are a lot of comparisons */ + bp->v_kick = get_black_hole_wind_speed(props, phys_const, bp); + + /* This is always true in the ADAF mode; only heating happens */ + if (bp->state == BH_states_adaf) bp->v_kick = 0.f; + +#ifdef OBSIDIAN_DEBUG_CHECKS + const float galaxy_sfr = bp->galaxy_data.stellar_mass * bp->galaxy_data.specific_sfr; + tdyn_inv = (tdyn_inv > 0.f) ? tdyn_inv : FLT_MIN; + message( + "BH_ACC: z=%g bid=%lld ms=%g dms=%g sfr=%g mbh=%g dmbh=%g state=%d " + "torque=%g bondi=%g fEdd=%g facc=%g fsupp=%g mcold=%g mhot=%g mdisk=%g" + " tin=%g vkick=%g dmass=%g radeff=%g mres=%g tdyn=%g", + cosmo->z, bp->id, galaxy_mstar * props->mass_to_solar_mass, + galaxy_sfr * dt * props->mass_to_solar_mass, + galaxy_sfr * props->mass_to_solar_mass / props->time_to_yr, + bp->subgrid_mass * props->mass_to_solar_mass, + delta_mass * props->mass_to_solar_mass, bp->state, + torque_accr_rate * props->mass_to_solar_mass / props->time_to_yr, + bondi_accr_rate * props->mass_to_solar_mass / props->time_to_yr, + bp->eddington_fraction, bp->f_accretion, + 1. - exp(-bp->subgrid_mass * props->mass_to_solar_mass / + fabs(props->bh_characteristic_suppression_mass) * cosmo->a), + bp->cold_gas_mass * props->mass_to_solar_mass, + bp->hot_gas_mass * props->mass_to_solar_mass, + corot_gas_mass * props->mass_to_solar_mass, props->time_to_Myr / tdyn_inv, + bp->v_kick / props->kms_to_internal, delta_mass, bp->radiative_efficiency, + bp->accretion_disk_mass, (1.f / tdyn_inv) * props->time_to_Myr); + + message( + "BH_STATES: id=%lld, new_state=%d, predicted_mdot_medd=%g, " + "eps_r=%g, f_Edd=%g, f_acc=%g, " + "luminosity=%g, accr_rate=%g Msun/yr, coupling=%g, v_kick=%g km/s, " + "jet_mass_reservoir=%g Msun unresolved_reservoir=%g Msun " + "jet_mass_loading=%g", + bp->id, bp->state, predicted_mdot_medd, bp->radiative_efficiency, + bp->eddington_fraction, bp->f_accretion, + bp->radiative_luminosity * props->conv_factor_energy_rate_to_cgs, + bp->accretion_rate * props->mass_to_solar_mass / props->time_to_yr, + get_black_hole_coupling(bp, props, cosmo, phys_const), + bp->v_kick / props->kms_to_internal, + bp->jet_mass_reservoir * props->mass_to_solar_mass, + bp->unresolved_mass_reservoir * props->mass_to_solar_mass, + bp->jet_mass_loading); +#endif + +#define OBSIDIAN_BH_DETAILS +#ifdef OBSIDIAN_BH_DETAILS + const float galaxy_sfr = bp->galaxy_data.stellar_mass * bp->galaxy_data.specific_sfr; + printf( + "BH_DETAILS " + "z=%2.12f bid=%lld galM*=%g galSFR=%g" + " Mdyn=%g MBH=%g h=%g R/h=%g Mres=%g BHAR=%g" + " Bondi=%g torque=%g dt=%g dM=%g" + " nH=%g Thot=%g SFR=%g mngb=%g " + " mhot=%g mcold=%g m*=%g mdisk=%g mcorot=%g " + " rx=%2.10f ry=%2.10f rz=%2.10f " + " vx=%2.7f vy=%2.7f vz=%2.7f " + " Lgasx=%g Lgasy=%g Lgasz=%g Lbhx=%g Lbhy=%g Lbhz=%g" + " Lrad=%g state=%d facc=%g radeff=%g" + " fedd=%g madaf=%g mngb=%g tdyn=%g fsupp=%g\n", + cosmo->z, bp->id, + galaxy_mstar * props->mass_to_solar_mass, + galaxy_sfr * props->mass_to_solar_mass / props->time_to_yr, + bp->mass * props->mass_to_solar_mass, + bp->subgrid_mass * props->mass_to_solar_mass, + bp->h * cosmo->a * props->length_to_parsec / 1.0e3f, + bp->galactocentric_radius / bp->h, + bp->jet_mass_reservoir * props->mass_to_solar_mass, + bp->accretion_rate * props->mass_to_solar_mass / props->time_to_yr, + bp->bondi_accretion_rate * props->mass_to_solar_mass / props->time_to_yr, + (bp->accretion_rate - bp->bondi_accretion_rate) * props->mass_to_solar_mass / props->time_to_yr, + dt * props->time_to_Myr, + bp->accretion_rate * dt * props->mass_to_solar_mass, + (bp->rho_gas * cosmo->a3_inv) * props->rho_to_n_cgs, + bp->hot_gas_internal_energy * cosmo->a_factor_internal_energy / + (props->T_K_to_int * props->temp_to_u_factor), + bp->gas_SFR * props->mass_to_solar_mass / props->time_to_yr, + bp->ngb_mass * props->mass_to_solar_mass, + bp->hot_gas_mass * props->mass_to_solar_mass, + bp->cold_gas_mass * props->mass_to_solar_mass, + bp->stellar_mass * props->mass_to_solar_mass, + bp->cold_disk_mass * props->mass_to_solar_mass, + (bp->cold_gas_mass - 2. * (bp->cold_gas_mass - bp->cold_disk_mass)) * props->mass_to_solar_mass, + bp->x[0] * cosmo->a * props->length_to_parsec / 1.0e3f, + bp->x[1] * cosmo->a * props->length_to_parsec / 1.0e3f, + bp->x[2] * cosmo->a * props->length_to_parsec / 1.0e3f, + bp->v[0] * cosmo->a_inv / props->kms_to_internal, + bp->v[1] * cosmo->a_inv / props->kms_to_internal, + bp->v[2] * cosmo->a_inv / props->kms_to_internal, + bp->angular_momentum_gas[0], bp->angular_momentum_gas[1], + bp->angular_momentum_gas[2], + bp->swallowed_angular_momentum[0], bp->swallowed_angular_momentum[1], + bp->swallowed_angular_momentum[2], + bp->radiative_luminosity * props->conv_factor_energy_rate_to_cgs, + bp->state, bp->f_accretion, bp->radiative_efficiency, + bp->eddington_fraction, my_adaf_mass_limit * props->mass_to_solar_mass, + bp->gravitational_ngb_mass * props->mass_to_solar_mass, + 1.f / tdyn_inv * 1.e-6 * props->time_to_yr, f_suppress); +#endif +} + +/** + * @brief Computes the (maximal) repositioning speed for a black hole. + * + * Calculated as upsilon * (m_BH / m_ref) ^ beta_m * (n_H_BH / n_ref) ^ beta_n + * where m_BH = BH subgrid mass, n_H_BH = physical gas density around BH + * and upsilon, m_ref, beta_m, n_ref, and beta_n are parameters. + * + * @param bp The #bpart. + * @param props The properties of the black hole model. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_repositioning_speed(const struct bpart *restrict bp, + const struct black_holes_props *props, + const struct cosmology *cosmo) { + + const double n_gas_phys = bp->rho_gas * cosmo->a3_inv * props->rho_to_n_cgs; + const double v_repos = + props->reposition_coefficient_upsilon * + pow(bp->subgrid_mass / props->reposition_reference_mass, + props->reposition_exponent_mass) * + pow(n_gas_phys / props->reposition_reference_n_H, + props->reposition_exponent_n_H); + + /* Make sure the repositioning is not back-firing... */ + if (v_repos < 0) + error( + "BH %lld wants to reposition at negative speed (%g U_V). Do you " + "think you are being funny? No-one is laughing.", + bp->id, v_repos); + + return v_repos; +} + +/** + * @brief Finish the calculation of the new BH position. + * + * Here, we check that the BH should indeed be moved in the next drift. + * + * @param bp The black hole particle. + * @param props The properties of the black hole scheme. + * @param phys_const The physical phys_const (in internal units). + * @param cosmo The cosmological model. + * @param dt The black hole particle's time step. + * @param ti_begin The time at the start of the temp + */ +__attribute__((always_inline)) INLINE static void black_holes_end_reposition( + struct bpart *restrict bp, const struct black_holes_props *props, + const struct phys_const *phys_const, const struct cosmology *cosmo, + const double dt, const integertime_t ti_begin) { + + /* First check: did we find any eligible neighbour particle to jump to? */ + if (bp->reposition.min_potential != FLT_MAX) { + + /* Record that we have a (possible) repositioning situation */ + bp->number_of_reposition_attempts++; + + /* Is the potential lower (i.e. the BH is at the bottom already) + * OR is the BH massive enough that we don't reposition? */ + const float potential = gravity_get_comoving_potential(bp->gpart); + if (potential < bp->reposition.min_potential || + bp->subgrid_mass > props->max_reposition_mass) { + + /* No need to reposition */ + bp->reposition.min_potential = FLT_MAX; + bp->reposition.delta_x[0] = -FLT_MAX; + bp->reposition.delta_x[1] = -FLT_MAX; + bp->reposition.delta_x[2] = -FLT_MAX; + + } else if (props->set_reposition_speed) { + + /* If we are re-positioning, move the BH a fraction of delta_x, so + * that we have a well-defined re-positioning velocity (repos_vel + * cannot be negative). */ + double repos_vel = black_holes_get_repositioning_speed(bp, props, cosmo); + + /* Convert target reposition velocity to a fractional reposition + * along reposition.delta_x */ + const double dx = bp->reposition.delta_x[0]; + const double dy = bp->reposition.delta_x[1]; + const double dz = bp->reposition.delta_x[2]; + const double d = sqrt(dx * dx + dy * dy + dz * dz); + + /* Exclude the pathological case of repositioning by zero distance */ + if (d > 0) { + double repos_frac = repos_vel * dt / d; + + /* We should never get negative repositioning fractions... */ + if (repos_frac < 0) + error("Wanting to reposition by negative fraction (%g)?", repos_frac); + + /* ... but fractions > 1 can occur if the target velocity is high. + * We do not want this, because it could lead to overshooting the + * actual potential minimum. */ + if (repos_frac > 1) { + repos_frac = 1.; + repos_vel = repos_frac * d / dt; + } + + bp->last_repos_vel = (float)repos_vel; + bp->reposition.delta_x[0] *= repos_frac; + bp->reposition.delta_x[1] *= repos_frac; + bp->reposition.delta_x[2] *= repos_frac; + } + + /* ends section for fractional repositioning */ + } else { + + /* We _should_ reposition, but not fractionally. Here, we will + * reposition exactly on top of another gas particle - which + * could cause issues, so we add on a small fractional offset + * of magnitude 0.001 h in the reposition delta. */ + + /* Generate three random numbers in the interval [-0.5, 0.5[; id, + * id**2, and id**3 are required to give unique random numbers (as + * random_unit_interval is completely reproducible). */ + const float offset_dx = + random_unit_interval(bp->id, ti_begin, random_number_BH_reposition) - + 0.5f; + const float offset_dy = + random_unit_interval(bp->id * bp->id, ti_begin, + random_number_BH_reposition) - + 0.5f; + const float offset_dz = + random_unit_interval(bp->id * bp->id * bp->id, ti_begin, + random_number_BH_reposition) - + 0.5f; + + const float length_inv = + 1.0f / sqrtf(offset_dx * offset_dx + offset_dy * offset_dy + + offset_dz * offset_dz); + + const float norm = 0.001f * bp->h * length_inv; + + bp->reposition.delta_x[0] += offset_dx * norm; + bp->reposition.delta_x[1] += offset_dy * norm; + bp->reposition.delta_x[2] += offset_dz * norm; + } + } /* ends section if we found eligible repositioning target(s) */ +} + +/** + * @brief Reset acceleration fields of a particle + * + * This is the equivalent of hydro_reset_acceleration. + * We do not compute the acceleration on black hole, therefore no need to use + * it. + * + * @param bp The particle to act upon + */ +__attribute__((always_inline)) INLINE static void black_holes_reset_feedback( + struct bpart *restrict bp) { + +#ifdef DEBUG_INTERACTIONS_BLACK_HOLES + for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_STARS; ++i) + bp->ids_ngbs_force[i] = -1; + bp->num_ngb_force = 0; +#endif +} + +/** + * @brief Store the gravitational potential of a black hole by copying it from + * its #gpart friend. + * + * @param bp The black hole particle. + * @param gp The black hole's #gpart. + */ +__attribute__((always_inline)) INLINE static void +black_holes_store_potential_in_bpart(struct bpart *bp, const struct gpart *gp) { + +#ifdef SWIFT_DEBUG_CHECKS + if (bp->gpart != gp) error("Copying potential to the wrong black hole!"); +#endif + + bp->reposition.potential = gp->potential; +} + +/** + * @brief Store the gravitational potential of a particle by copying it from + * its #gpart friend. + * + * @param p_data The black hole data of a gas particle. + * @param gp The black hole's #gpart. + */ +__attribute__((always_inline)) INLINE static void +black_holes_store_potential_in_part(struct black_holes_part_data *p_data, + const struct gpart *gp) { + p_data->potential = gp->potential; +} + +/** + * @brief Initialise a BH particle that has just been seeded. + * + * @param bp The #bpart to initialise. + * @param props The properties of the black hole scheme. + * @param phys_const The physical phys_const in internal units. + * @param cosmo The current cosmological model. + * @param p The #part that became a black hole. + * @param xp The #xpart that became a black hole. + */ +INLINE static void black_holes_create_from_gas( + struct bpart *bp, const struct black_holes_props *props, + const struct phys_const *phys_const, const struct cosmology *cosmo, + const struct part *p, const struct xpart *xp, + const integertime_t ti_current) { + + /* All the non-basic properties of the black hole have been zeroed + * in the FOF code. We update them here. + * (i.e. position, velocity, mass, time-step have been set) */ + + /* Birth time and density */ + bp->formation_scale_factor = cosmo->a; + bp->formation_gas_density = hydro_get_physical_density(p, cosmo); + + /* Copy FoF galaxy data from spawning particle */ + bp->galaxy_data.stellar_mass = p->galaxy_data.stellar_mass; + bp->galaxy_data.gas_mass = p->galaxy_data.gas_mass; + bp->galaxy_data.specific_sfr = p->galaxy_data.specific_sfr; + + /* Initial seed mass */ + bp->subgrid_mass = props->subgrid_seed_mass; + + /* We haven't accreted anything yet */ + bp->total_accreted_mass = 0.f; + bp->cumulative_number_seeds = 1; + bp->number_of_mergers = 0; + bp->number_of_gas_swallows = 0; + bp->number_of_direct_gas_swallows = 0; + bp->number_of_time_steps = 0; + + /* We haven't repositioned yet, nor attempted it */ + bp->number_of_repositions = 0; + bp->number_of_reposition_attempts = 0; + bp->last_repos_vel = 0.f; + + /* Copy over the splitting struct */ + bp->split_data = xp->split_data; + + /* Initial metal masses */ + const float gas_mass = hydro_get_mass(p); + struct chemistry_bpart_data *bp_chem = &bp->chemistry_data; + const struct chemistry_part_data *p_chem = &p->chemistry_data; + chemistry_bpart_from_part(bp_chem, p_chem, gas_mass); + + /* No swallowed angular momentum */ + bp->swallowed_angular_momentum[0] = 0.f; + bp->swallowed_angular_momentum[1] = 0.f; + bp->swallowed_angular_momentum[2] = 0.f; + + /* Last time of mergers */ + bp->last_minor_merger_time = -1.; + bp->last_major_merger_time = -1.; + + /* First initialisation */ + black_holes_init_bpart(bp); + + bp->state = BH_states_slim_disk; + + black_holes_mark_bpart_as_not_swallowed(&bp->merger_data); +} + +#endif /* SWIFT_OBSIDIAN_BLACK_HOLES_H */ diff --git a/src/black_holes/Obsidian/black_holes.h.rad b/src/black_holes/Obsidian/black_holes.h.rad new file mode 100644 index 0000000000..684b896167 --- /dev/null +++ b/src/black_holes/Obsidian/black_holes.h.rad @@ -0,0 +1,1730 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_OBSIDIAN_BLACK_HOLES_H +#define SWIFT_OBSIDIAN_BLACK_HOLES_H + +/* Local includes */ +#include "black_holes_properties.h" +#include "black_holes_struct.h" +#include "cooling.h" +#include "cosmology.h" +#include "dimension.h" +#include "exp10.h" +#include "gravity.h" +#include "kernel_hydro.h" +#include "minmax.h" +#include "physical_constants.h" +#include "random.h" + +/* Standard includes */ +#include +#include +#include + + +/** + * @brief How much of the feedback actually couples to the medium? + * + * @param props The properties of the black hole scheme. + * @param BH_state The current state of the black hole. + */ +__attribute__((always_inline)) INLINE static double get_black_hole_coupling( + const struct black_holes_props* props, const struct cosmology* cosmo, + const int BH_state) { + switch (BH_state) { + case BH_states_adaf: + { + const double scaling = + min(pow(1. + cosmo->z, props->adaf_z_scaling), 1.); + return props->adaf_coupling * scaling; + break; + } + case BH_states_quasar: + return props->quasar_coupling; + break; + case BH_states_slim_disk: + return props->slim_disk_coupling; + break; + default: + error("Invalid black hole state."); + return 0.; + break; + } +} + +/** + * @brief Computes the radiative efficiency in the slim disk mode. + * + * @param props The properties of the black hole scheme. + * @param f_Edd M_dot,BH / M_dot,Edd + */ +__attribute__((always_inline)) INLINE static +double get_black_hole_slim_disk_efficiency( + const struct black_holes_props* props, + const double f_Edd) { + if (f_Edd <= 0.) return 0.; + const double R = 1. / f_Edd; + /* Efficiency from Lupi et al. (2014), + * super eddington accretion and feedback */ + return (R / 16.) * props->A_lupi * + (0.985 / (R + (5. / 8.) * props->B_lupi) + 0.015 / + (R + (5. / 8.) * props->C_lupi)); +} + +/** + * @brief Computes the radiative efficiency in the ADAF mode. + * + * @param props The properties of the black hole scheme. + * @param f_Edd M_dot,BH / M_dot,Edd + */ +__attribute__((always_inline)) INLINE static +double get_black_hole_adaf_efficiency( + const struct black_holes_props* props, const double f_Edd) { + return props->epsilon_r * f_Edd; /* scales with M_dot,BH */ +} + +/** + * @brief Chooses and calls the proper radiative efficiency function for the state. + * + * @param props The properties of the black hole scheme. + * @param f_Edd The accretion rate over the Eddington rate. + * @param BH_state The current state of the BH. + */ +__attribute__((always_inline)) INLINE static +double get_black_hole_radiative_efficiency( + const struct black_holes_props* props, + const double f_Edd, const int BH_state) { + switch(BH_state) { + case BH_states_adaf: + return get_black_hole_adaf_efficiency(props, f_Edd); + case BH_states_quasar: + return props->epsilon_r; + case BH_states_slim_disk: + return get_black_hole_slim_disk_efficiency(props, f_Edd); + default: + error("Invalid black hole state."); + break; + } + + return 0.; +} + +/** + * @brief Computes the fraction of M_dot,inflow that should go into the BH. + * + * @param props The properties of the black hole scheme. + * @param constants The physical constants (in internal units). + * @param m_dot_inflow_m_dot_edd M_dot,inflow scaled to M_dot,Edd for the BH. + */ +__attribute__((always_inline)) INLINE static double +get_black_hole_upper_mdot_medd( + const struct black_holes_props* props, + const struct phys_const* constants, + const double m_dot_inflow_m_dot_edd) { + + if (m_dot_inflow_m_dot_edd <= 0.) return 0.; + + double x1, x2, x3; + double a3, a2, a1, a0; + const double phi = props->slim_disk_phi; + + int num_roots; + + a3 = ((5. * 5.) / (8. * 8.)) * props->B_lupi * props->C_lupi; + a2 = (5. / 8.) * ((props->B_lupi + props->C_lupi) + (phi / 16.) * + props->A_lupi * (0.015 * props->B_lupi + 0.985 * props->C_lupi) - + (5. / 8.) * props->B_lupi * props->C_lupi * m_dot_inflow_m_dot_edd); + a1 = 1. + (phi / 16.) * props->A_lupi - (5. / 8.) * + (props->B_lupi + props->C_lupi) * m_dot_inflow_m_dot_edd; + a0 = -m_dot_inflow_m_dot_edd; + + a2 /= a3; + a1 /= a3; + a0 /= a3; + + num_roots = gsl_poly_solve_cubic(a2, a1, a0, &x1, &x2, &x3); + if (num_roots == 1) { + if (x1 >= 0.) { + return x1; + } + else { + warning("num_roots=1 m_dot_inflow_m_dot_edd=%g phi=%g a3=%g a2=%g " + "a1=%g a0=%g", + m_dot_inflow_m_dot_edd, phi, a3, a2, a1, a0); + return 0.; + } + } + if (x3 >= 0.) { + return x3; + } + else { + warning("num_roots=0 m_dot_inflow_m_dot_edd=%g phi=%g a3=%g a2=%g a1=%g a0=%g", + m_dot_inflow_m_dot_edd, phi, a3, a2, a1, a0); + return 0.; + } + + return 0.; +} + +/** + * @brief Computes the fraction of M_dot,inflow that should go into the BH. + * + * @param props The properties of the black hole scheme. + * @param constants The physical constants (in internal units). + * @param m_dot_inflow M_dot,inflow in internal units. + * @param BH_mass The subgrid mass of the BH in internal units. + * @param BH_state The current state of the BH. + * @param Eddington_rate M_dot,Edd in internal units. + */ +__attribute__((always_inline)) INLINE static +double get_black_hole_accretion_factor( + const struct black_holes_props* props, + const struct phys_const* constants, + const double m_dot_inflow, const double BH_mass, + const int BH_state, + const double Eddington_rate) { + + if (m_dot_inflow <= 0. || BH_mass <= 0.) return 0.; + + + switch (BH_state) { + case BH_states_adaf: + return props->adaf_f_accretion; + break; + case BH_states_quasar: + return props->quasar_f_accretion; + break; + case BH_states_slim_disk: + { + /* This is the FRACTION of the total so divide by M_dot,inflow */ + const double f_edd = m_dot_inflow / Eddington_rate; + double mdot_medd = + get_black_hole_upper_mdot_medd(props, constants, f_edd); + return mdot_medd * Eddington_rate / m_dot_inflow; + break; + } + default: + error("Invalid black hole state."); + return 0.; + break; + } +} + +/** + * @brief Computes the time-step of a given black hole particle. + * + * @param bp Pointer to the s-particle data. + * @param props The properties of the black hole scheme. + * @param constants The physical constants (in internal units). + */ +__attribute__((always_inline)) INLINE static float black_holes_compute_timestep( + const struct bpart* const bp, const struct black_holes_props* props, + const struct phys_const* constants, const struct cosmology* cosmo) { + + /* Allow for finer timestepping if necessary! */ + float dt_accr = FLT_MAX; + float dt_overall = FLT_MAX; + float dt_kick = FLT_MAX; + const float min_subgrid_mass = props->minimum_black_hole_mass_unresolved; + + /* Only limit when in the resolved feedback regime */ + if (bp->accretion_rate > 0.f && bp->subgrid_mass > min_subgrid_mass) { + dt_accr = props->dt_accretion_factor * bp->mass / bp->accretion_rate; + + if (bp->state == BH_states_adaf) { + dt_kick = bp->ngb_mass / (props->jet_mass_loading * bp->accretion_rate); + } + else { + if (bp->f_accretion > 0.f) { + /* Make sure that the wind mass does not exceed the kernel gas mass */ + const float psi = (1.f - bp->f_accretion) / bp->f_accretion; + dt_kick = bp->ngb_mass / (psi * bp->accretion_rate); + } + } + + dt_overall = min(dt_kick, dt_accr); + } + + if (dt_overall < props->time_step_min) { + message( + "Warning! BH_TIMESTEP_LOW: id=%lld (%g Myr) is below time_step_min (%g " + "Myr).", + bp->id, dt_overall * props->time_to_Myr, + props->time_step_min * props->time_to_Myr); + } + + return max(dt_overall, props->time_step_min); +} + +/** + * @brief Initialises the b-particles for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions. + * + * @param bp The particle to act upon + * @param props The properties of the black holes model. + */ +__attribute__((always_inline)) INLINE static void black_holes_first_init_bpart( + struct bpart* bp, const struct black_holes_props* props) { + + bp->time_bin = 0; + if (props->use_subgrid_mass_from_ics == 0) { + bp->subgrid_mass = bp->mass; + } else if (props->with_subgrid_mass_check && bp->subgrid_mass <= 0) { + error( + "Black hole %lld has a subgrid mass of %f (internal units).\n" + "If this is because the ICs do not contain a 'SubgridMass' data " + "set, you should set the parameter " + "'ObsidianAGN:use_subgrid_mass_from_ics' to 0 to initialize the " + "black hole subgrid masses to the corresponding dynamical masses.\n" + "If the subgrid mass is intentionally set to this value, you can " + "disable this error by setting 'ObsidianAGN:with_subgrid_mass_check' " + "to 0.", + bp->id, bp->subgrid_mass); + } + bp->total_accreted_mass = 0.f; + bp->accretion_disk_mass = 0.f; + bp->gas_SFR = 0.f; + bp->accretion_rate = 0.f; + bp->formation_time = -1.f; + bp->cumulative_number_seeds = 1; + bp->number_of_mergers = 0; + bp->number_of_gas_swallows = 0; + bp->number_of_direct_gas_swallows = 0; + bp->number_of_repositions = 0; + bp->number_of_reposition_attempts = 0; + bp->number_of_time_steps = 0; + bp->last_minor_merger_time = -1.; + bp->last_major_merger_time = -1.; + bp->swallowed_angular_momentum[0] = 0.f; + bp->swallowed_angular_momentum[1] = 0.f; + bp->swallowed_angular_momentum[2] = 0.f; + bp->accreted_angular_momentum[0] = 0.f; + bp->accreted_angular_momentum[1] = 0.f; + bp->accreted_angular_momentum[2] = 0.f; + bp->last_repos_vel = 0.f; + bp->radiative_luminosity = 0.f; + bp->delta_energy_this_timestep = 0.f; + bp->state = BH_states_slim_disk; + bp->radiative_efficiency = 0.f; + bp->f_accretion = 0.f; + bp->m_dot_inflow = 0.f; + bp->cold_disk_mass = 0.f; + bp->jet_mass_reservoir = 0.f; + bp->jet_mass_kicked_this_step = 0.f; + bp->adaf_energy_to_dump = 0.f; + bp->adaf_energy_used_this_step = 0.f; + +#ifdef WITH_FOF_GALAXIES + bp->group_data.mass = 0.f; + bp->group_data.stellar_mass = 0.f; + bp->group_data.ssfr = 0.f; +#endif +} + +/** + * @brief Prepares a b-particle for its interactions + * + * @param bp The particle to act upon + */ +__attribute__((always_inline)) INLINE static void black_holes_init_bpart( + struct bpart* bp) { + +#ifdef DEBUG_INTERACTIONS_BLACK_HOLES + for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_STARS; ++i) + bp->ids_ngbs_density[i] = -1; + bp->num_ngb_density = 0; +#endif + + bp->density.wcount = 0.f; + bp->density.wcount_dh = 0.f; + bp->rho_gas = 0.f; + bp->sound_speed_gas = 0.f; + bp->internal_energy_gas = 0.f; + bp->hot_gas_mass = 0.f; + bp->cold_gas_mass = 0.f; + bp->hot_gas_internal_energy = 0.f; + bp->sound_speed_subgrid_gas = -1.f; + bp->velocity_gas[0] = 0.f; + bp->velocity_gas[1] = 0.f; + bp->velocity_gas[2] = 0.f; + bp->circular_velocity_gas[0] = 0.f; + bp->circular_velocity_gas[1] = 0.f; + bp->circular_velocity_gas[2] = 0.f; + bp->angular_momentum_gas[0] = 0.f; + bp->angular_momentum_gas[1] = 0.f; + bp->angular_momentum_gas[2] = 0.f; + bp->stellar_mass = 0.f; + bp->stellar_bulge_mass = 0.f; + bp->radiative_luminosity = 0.f; + bp->ngb_mass = 0.f; + bp->gravitational_ngb_mass = 0.f; + bp->num_ngbs = 0; + bp->num_gravitational_ngbs = 0; + bp->reposition.delta_x[0] = -FLT_MAX; + bp->reposition.delta_x[1] = -FLT_MAX; + bp->reposition.delta_x[2] = -FLT_MAX; + bp->reposition.min_potential = FLT_MAX; + bp->reposition.potential = FLT_MAX; + bp->accretion_rate = 0.f; /* Optionally accumulated ngb-by-ngb */ + bp->cold_disk_mass = 0.f; + bp->mass_at_start_of_step = bp->mass; /* bp->mass may grow in nibbling mode */ + bp->m_dot_inflow = 0.f; /* reset accretion rate */ + bp->kernel_wt_sum = 0.f; + /* update the reservoir */ + bp->jet_mass_reservoir -= bp->jet_mass_kicked_this_step; + bp->jet_mass_kicked_this_step = 0.f; + if (bp->jet_mass_reservoir < 0.f) { + bp->jet_mass_reservoir = 0.f; /* reset reservoir if used up */ + } + /* update the unresolved reservoir */ + bp->unresolved_mass_reservoir -= bp->unresolved_mass_kicked_this_step; + bp->unresolved_mass_kicked_this_step = 0.f; + if (bp->unresolved_mass_reservoir < 0.f) { + bp->unresolved_mass_reservoir = 0.f; + } + /* update the adaf energy reservoir */ + if (bp->adaf_wt_sum > 0.f) { + const float adaf_energy_used = + bp->adaf_energy_used_this_step / bp->adaf_wt_sum; + bp->adaf_energy_to_dump -= adaf_energy_used; + bp->adaf_wt_sum = 0.f; + bp->adaf_energy_used_this_step = 0.f; + if (bp->adaf_energy_to_dump < 0.f) { + bp->adaf_energy_to_dump = 0.f; + } + } + else { + bp->adaf_energy_used_this_step = 0.f; + } +} + +/** + * @brief Predict additional particle fields forward in time when drifting + * + * The fields do not get predicted but we move the BH to its new position + * if a new one was calculated in the repositioning loop. + * + * @param bp The particle + * @param dt_drift The drift time-step for positions. + */ +__attribute__((always_inline)) INLINE static void black_holes_predict_extra( + struct bpart* restrict bp, float dt_drift) { + + /* Are we doing some repositioning? */ + if (bp->reposition.min_potential != FLT_MAX) { + +#ifdef SWIFT_DEBUG_CHECKS + if (bp->reposition.delta_x[0] == -FLT_MAX || + bp->reposition.delta_x[1] == -FLT_MAX || + bp->reposition.delta_x[2] == -FLT_MAX) { + error("Something went wrong with the new repositioning position"); + } + + const double dx = bp->reposition.delta_x[0]; + const double dy = bp->reposition.delta_x[1]; + const double dz = bp->reposition.delta_x[2]; + const double d = sqrt(dx * dx + dy * dy + dz * dz); + if (d > 1.01 * kernel_gamma * bp->h) + error("Repositioning BH beyond the kernel support!"); +#endif + + /* Move the black hole */ + bp->x[0] += bp->reposition.delta_x[0]; + bp->x[1] += bp->reposition.delta_x[1]; + bp->x[2] += bp->reposition.delta_x[2]; + + /* Move its gravity properties as well */ + bp->gpart->x[0] += bp->reposition.delta_x[0]; + bp->gpart->x[1] += bp->reposition.delta_x[1]; + bp->gpart->x[2] += bp->reposition.delta_x[2]; + + /* Store the delta position */ + bp->x_diff[0] -= bp->reposition.delta_x[0]; + bp->x_diff[1] -= bp->reposition.delta_x[1]; + bp->x_diff[2] -= bp->reposition.delta_x[2]; + + /* Reset the reposition variables */ + bp->reposition.delta_x[0] = -FLT_MAX; + bp->reposition.delta_x[1] = -FLT_MAX; + bp->reposition.delta_x[2] = -FLT_MAX; + bp->reposition.min_potential = FLT_MAX; + + /* Count the jump */ + bp->number_of_repositions++; + } +} + +/** + * @brief Sets the values to be predicted in the drifts to their values at a + * kick time + * + * @param bp The particle. + */ +__attribute__((always_inline)) INLINE static void +black_holes_reset_predicted_values(struct bpart* bp) {} + +/** + * @brief Kick the additional variables + * + * @param bp The particle to act upon + * @param dt The time-step for this kick + */ +__attribute__((always_inline)) INLINE static void black_holes_kick_extra( + struct bpart* bp, float dt) {} + +/** + * @brief Finishes the calculation of density on black holes + * + * @param bp The particle to act upon + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void black_holes_end_density( + struct bpart* bp, const struct cosmology* cosmo) { + + /* Some smoothing length multiples. */ + const float h = bp->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */ + + /* --- Finish the calculation by inserting the missing h factors --- */ + bp->density.wcount *= h_inv_dim; + bp->density.wcount_dh *= h_inv_dim_plus_one; + bp->rho_gas *= h_inv_dim; + float rho_inv = 1.f; + if (bp->rho_gas > 0.f) rho_inv = 1.f / bp->rho_gas; + + /* For the following, we also have to undo the mass smoothing + * (N.B.: bp->velocity_gas is in BH frame, in internal units). */ + bp->sound_speed_gas *= h_inv_dim * rho_inv; + bp->internal_energy_gas *= h_inv_dim * rho_inv; + + /* Non-weighted (no decoupled winds) properties below. + * All mass-weighted quantities are for the hot & cold gas */ + float m_hot_inv = 1.f; + if (bp->hot_gas_mass > 0.f) m_hot_inv /= bp->hot_gas_mass; + /* Or the total mass */ + float m_tot_inv = 1.f; + if (bp->ngb_mass > 0.f) m_tot_inv /= bp->ngb_mass; + + bp->hot_gas_internal_energy *= m_hot_inv; + bp->velocity_gas[0] *= m_tot_inv; + bp->velocity_gas[1] *= m_tot_inv; + bp->velocity_gas[2] *= m_tot_inv; + bp->circular_velocity_gas[0] *= m_tot_inv; + bp->circular_velocity_gas[1] *= m_tot_inv; + bp->circular_velocity_gas[2] *= m_tot_inv; + + /* Calculate circular velocity at the smoothing radius from specific + * angular momentum (extra h_inv). It is now a VELOCITY. + */ + bp->circular_velocity_gas[0] *= h_inv; + bp->circular_velocity_gas[1] *= h_inv; + bp->circular_velocity_gas[2] *= h_inv; + +} + +/** + * @brief Sets all particle fields to sensible values when the #spart has 0 + * ngbs. + * + * @param bp The particle to act upon + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void black_holes_bpart_has_no_neighbours(struct bpart* bp, + const struct cosmology* cosmo) { + + //warning( + // "BH particle with ID %lld treated as having no neighbours (h: %g, " + // "wcount: %g).", + // bp->id, bp->h, bp->density.wcount); + + /* Some smoothing length multiples. */ + const float h = bp->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + + /* Re-set problematic values */ + bp->density.wcount = kernel_root * h_inv_dim; + bp->density.wcount_dh = 0.f; + + bp->velocity_gas[0] = FLT_MAX; + bp->velocity_gas[1] = FLT_MAX; + bp->velocity_gas[2] = FLT_MAX; + + bp->internal_energy_gas = -FLT_MAX; + bp->hot_gas_internal_energy = -FLT_MAX; +} + +/** + * @brief Return the current instantaneous accretion rate of the BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_accretion_rate(const struct bpart* bp) { + return bp->accretion_rate; +} + +/** + * @brief Return the total accreted gas mass of this BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_accreted_mass(const struct bpart* bp) { + return bp->total_accreted_mass; +} + +/** + * @brief Return the subgrid mass of this BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_subgrid_mass(const struct bpart* bp) { + return bp->subgrid_mass; +} + +/** + * @brief Return the current bolometric luminosity of the BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_bolometric_luminosity(const struct bpart* bp, + const struct phys_const* constants) { + const double c = constants->const_speed_light_c; + return bp->accretion_rate * bp->radiative_efficiency * c * c; +} + +/** + * @brief Return the current kinetic jet power of the BH. + * + * @param bp the #bpart. + */ +__attribute__((always_inline)) INLINE static double black_holes_get_jet_power( + const struct bpart* bp, const struct phys_const* constants, + const struct black_holes_props* props) { + const double c = constants->const_speed_light_c; + double eta_jet = props->jet_efficiency; + if (bp->state != BH_states_adaf) { + eta_jet = FLT_MIN; + } + /* accretion_rate is M_dot,acc from the paper */ + return eta_jet * bp->accretion_rate * c * c; +} + +/** + * @brief Update the properties of a black hole particles by swallowing + * a gas particle. + * + * @param bp The #bpart to update. + * @param p The #part that is swallowed. + * @param xp The #xpart that is swallowed. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void black_holes_swallow_part( + struct bpart* bp, const struct part* p, const struct xpart* xp, + const struct cosmology* cosmo) { + + /* Get the current dynamical masses */ + const float gas_mass = hydro_get_mass(p); + const float BH_mass = bp->mass; + + /* Increase the dynamical mass of the BH. */ + bp->mass += gas_mass; + bp->gpart->mass += gas_mass; + + /* Physical velocity difference between the particles */ + const float dv[3] = {(bp->v[0] - p->v[0]) * cosmo->a_inv, + (bp->v[1] - p->v[1]) * cosmo->a_inv, + (bp->v[2] - p->v[2]) * cosmo->a_inv}; + + /* Physical distance between the particles */ + const float dx[3] = {(bp->x[0] - p->x[0]) * cosmo->a, + (bp->x[1] - p->x[1]) * cosmo->a, + (bp->x[2] - p->x[2]) * cosmo->a}; + + /* Collect the swallowed angular momentum */ + bp->swallowed_angular_momentum[0] += + gas_mass * (dx[1] * dv[2] - dx[2] * dv[1]); + bp->swallowed_angular_momentum[1] += + gas_mass * (dx[2] * dv[0] - dx[0] * dv[2]); + bp->swallowed_angular_momentum[2] += + gas_mass * (dx[0] * dv[1] - dx[1] * dv[0]); + + /* Update the BH momentum */ + const float BH_mom[3] = {BH_mass * bp->v[0] + gas_mass * p->v[0], + BH_mass * bp->v[1] + gas_mass * p->v[1], + BH_mass * bp->v[2] + gas_mass * p->v[2]}; + + bp->v[0] = BH_mom[0] / bp->mass; + bp->v[1] = BH_mom[1] / bp->mass; + bp->v[2] = BH_mom[2] / bp->mass; + bp->gpart->v_full[0] = bp->v[0]; + bp->gpart->v_full[1] = bp->v[1]; + bp->gpart->v_full[2] = bp->v[2]; + + const float dr = sqrt(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]); + message( + "BH %lld swallowing gas particle %lld " + "(Delta_v = [%f, %f, %f] U_V, " + "Delta_x = [%f, %f, %f] U_L, " + "Delta_v_rad = %f)", + bp->id, p->id, -dv[0], -dv[1], -dv[2], -dx[0], -dx[1], -dx[2], + (dv[0] * dx[0] + dv[1] * dx[1] + dv[2] * dx[2]) / dr); + + /* Update the BH metal masses */ + struct chemistry_bpart_data* bp_chem = &bp->chemistry_data; + const struct chemistry_part_data* p_chem = &p->chemistry_data; + chemistry_add_part_to_bpart(bp_chem, p_chem, gas_mass); + + /* This BH swallowed a gas particle */ + bp->number_of_gas_swallows++; + bp->number_of_direct_gas_swallows++; + + /* This BH lost a neighbour */ + bp->num_ngbs--; + bp->num_gravitational_ngbs--; + bp->ngb_mass -= gas_mass; +} + +/** + * @brief Update the properties of a black hole particles by swallowing + * a BH particle. + * + * @param bpi The #bpart to update. + * @param bpj The #bpart that is swallowed. + * @param cosmo The current cosmological model. + * @param time Time since the start of the simulation (non-cosmo mode). + * @param with_cosmology Are we running with cosmology? + * @param props The properties of the black hole scheme. + */ +__attribute__((always_inline)) INLINE static void black_holes_swallow_bpart( + struct bpart* bpi, const struct bpart* bpj, const struct cosmology* cosmo, + const double time, const int with_cosmology, + const struct black_holes_props* props, const struct phys_const* constants) { + + /* Get the current dynamical masses */ + const float bpi_dyn_mass = bpi->mass; + const float bpj_dyn_mass = bpj->mass; + + /* Is this merger ratio above the threshold for recording? */ + const double merger_ratio = bpj->subgrid_mass / bpi->subgrid_mass; + if (merger_ratio > props->major_merger_threshold) { + if (with_cosmology) { + bpi->last_major_merger_scale_factor = cosmo->a; + } else { + bpi->last_major_merger_time = time; + } + } else if (merger_ratio > props->minor_merger_threshold) { + if (with_cosmology) { + bpi->last_minor_merger_scale_factor = cosmo->a; + } else { + bpi->last_minor_merger_time = time; + } + } + + /* Increase the masses of the BH. */ + bpi->mass += bpj->mass; + bpi->gpart->mass += bpj->mass; + bpi->subgrid_mass += bpj->subgrid_mass; + + /* Collect the swallowed angular momentum */ + bpi->swallowed_angular_momentum[0] += bpj->swallowed_angular_momentum[0]; + bpi->swallowed_angular_momentum[1] += bpj->swallowed_angular_momentum[1]; + bpi->swallowed_angular_momentum[2] += bpj->swallowed_angular_momentum[2]; + + /* Update the BH momentum */ + const float BH_mom[3] = {bpi_dyn_mass * bpi->v[0] + bpj_dyn_mass * bpj->v[0], + bpi_dyn_mass * bpi->v[1] + bpj_dyn_mass * bpj->v[1], + bpi_dyn_mass * bpi->v[2] + bpj_dyn_mass * bpj->v[2]}; + + bpi->v[0] = BH_mom[0] / bpi->mass; + bpi->v[1] = BH_mom[1] / bpi->mass; + bpi->v[2] = BH_mom[2] / bpi->mass; + bpi->gpart->v_full[0] = bpi->v[0]; + bpi->gpart->v_full[1] = bpi->v[1]; + bpi->gpart->v_full[2] = bpi->v[2]; + + /* Update the BH metal masses */ + struct chemistry_bpart_data* bpi_chem = &bpi->chemistry_data; + const struct chemistry_bpart_data* bpj_chem = &bpj->chemistry_data; + chemistry_add_bpart_to_bpart(bpi_chem, bpj_chem); + + /* Update the energy reservoir */ + bpi->jet_mass_reservoir += bpj->jet_mass_reservoir; + + /* Add up all the BH seeds */ + bpi->cumulative_number_seeds += bpj->cumulative_number_seeds; + + /* Add up all the gas particles we swallowed */ + bpi->number_of_gas_swallows += bpj->number_of_gas_swallows; + + /* Add the subgrid angular momentum that we swallowed */ + bpi->accreted_angular_momentum[0] += bpj->accreted_angular_momentum[0]; + bpi->accreted_angular_momentum[1] += bpj->accreted_angular_momentum[1]; + bpi->accreted_angular_momentum[2] += bpj->accreted_angular_momentum[2]; + + /* We had another merger */ + bpi->number_of_mergers++; +} + +/** + * @brief Compute the wind launch speed for this feedback step. + * + * @param props The properties of the black hole scheme. + * @param constants The physical constants (in internal units). + * @param bp The black hole particle. + * @param Eddington_rate M_dot,Edd for the black hole. + */ +__attribute__((always_inline)) INLINE static double get_black_hole_wind_speed( + const struct black_holes_props* props, + const struct phys_const* constants, + const struct bpart *bp, + const double Eddington_rate) { + + if (bp->accretion_rate < 0.f || bp->m_dot_inflow < 0.f) return 0.f; + + switch (bp->state) { + case BH_states_adaf: + return props->adaf_wind_speed; + break; + case BH_states_quasar: + return props->quasar_wind_speed; + break; + case BH_states_slim_disk: + return props->slim_disk_wind_speed; + break; + default: + error("Invalid black hole state."); + return 0.f; + break; + } +} + +/** + * @brief Compute the accretion rate of the black hole and all the quantities + * required for the feedback loop. + * + * @param bp The black hole particle. + * @param props The properties of the black hole scheme. + * @param constants The physical constants (in internal units). + * @param cosmo The cosmological model. + * @param cooling Properties of the cooling model. + * @param floor_props Properties of the entropy fllor. + * @param time Time since the start of the simulation (non-cosmo mode). + * @param with_cosmology Are we running with cosmology? + * @param dt The time-step size (in physical internal units). + * @param ti_begin The time at which the step begun (ti_current). + */ +__attribute__((always_inline)) INLINE static void black_holes_prepare_feedback( + struct bpart* restrict bp, const struct black_holes_props* props, + const struct phys_const* constants, const struct cosmology* cosmo, + const struct cooling_function_data* cooling, + const struct entropy_floor_properties* floor_props, const double time, + const int with_cosmology, const double dt, const integertime_t ti_begin) { + + /* Record that the black hole has another active time step */ + bp->number_of_time_steps++; + + if (dt == 0. || bp->rho_gas == 0. || bp->h == 0.) return; + + /* A black hole should never accrete/feedback if it is not in a galaxy */ + if (bp->group_data.mass <= 0.f) return; + + /* Gather some physical constants (all in internal units) */ + const double G = constants->const_newton_G; + const double c = constants->const_speed_light_c; + const double proton_mass = constants->const_proton_mass; + const double sigma_Thomson = constants->const_thomson_cross_section; + + /* Gather the parameters of the model */ + const double f_Edd_maximum = props->f_Edd_maximum; + +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(bp->subgrid_mass)) error("subgrid_mass nan"); +#endif + /* (Subgrid) mass of the BH (internal units) */ + const double BH_mass = bp->subgrid_mass; + + /* Compute the Eddington rate (internal units). + * IMPORTANT: epsilon_r = 0.1 is the SET value for the Eddington rate. + * It is assumed in all of the derivations for the state model. + */ + const double Eddington_rate = + 4. * M_PI * G * BH_mass * proton_mass / (0.1 * c * sigma_Thomson); + + const double bh_h = kernel_gamma * bp->h; + const double bh_h_inv = 1. / bh_h; + const double volume_bh_inv = + (3. / (4. * M_PI)) * bh_h_inv * bh_h_inv * bh_h_inv; + double gas_rho = 0.; + if (props->bondi_use_all_gas) { + gas_rho = bp->rho_gas; + } + else { + gas_rho = bp->hot_gas_mass * volume_bh_inv; + } + + const double gas_rho_phys = gas_rho * cosmo->a3_inv; + + /* We can now compute the Bondi accretion rate (internal units) + * D. Rennehan: In Simba, we only consider the hot gas within + * the kernel for the Bondi rate, and the cold gas using the + * torque accretion estimator. + */ + double Bondi_rate = 0.; + + /* Use all gas within the kernel or only the hot gas*/ + double gas_internal_energy = 0.; + if (props->bondi_use_all_gas) { + if (bp->internal_energy_gas > 0.) { + gas_internal_energy = bp->internal_energy_gas; + } + } + else { + if (bp->hot_gas_internal_energy > 0.) { + gas_internal_energy = bp->hot_gas_internal_energy; + } + } + + /* Check if there is hot/any gas in the kernel */ + if (gas_internal_energy > 0.) { + double gas_c = + gas_soundspeed_from_internal_energy(gas_rho, gas_internal_energy); + + if (gas_c > 0.) { + const double gas_c_phys_inv = + 1. / (cosmo->a_factor_sound_speed * gas_c); + + Bondi_rate = 4. * M_PI * G * G * BH_mass * BH_mass * gas_rho_phys * + gas_c_phys_inv * gas_c_phys_inv * gas_c_phys_inv; + + /* In the case of standard Bondi, we limit it to the Eddington rate */ + Bondi_rate = fmin(Bondi_rate, props->f_Edd_Bondi_maximum * Eddington_rate); + } + } + + /* The accretion rate estimators give Mdot,inflow + * (Mdot,BH = f_acc * Mdot,inflow) */ + const double bondi_accr_rate = props->bondi_alpha * Bondi_rate; + + /* Compute the torque-limited accretion rate */ + double torque_accr_rate = 0.; + + double f_corr_stellar = 10.; + const double galaxy_gas_mass = + bp->group_data.mass - bp->group_data.stellar_mass; + if (galaxy_gas_mass > 0.) { + f_corr_stellar = + min(bp->group_data.stellar_mass / galaxy_gas_mass, f_corr_stellar); + } + + /* Torque accretion rate based on some fraction of gas near BH + * falling in on dynamical time. (This is the default.) + * Here the accretion rate is only based on Mgas / tdyn. + * We do not use the DM mass to compute tdyn since it probably + * doesn't contribute much near the core of the system. We also + * assume that the gas fraction is constant in the galaxy in + * order to compute Mstar within the kernel of the black hole. + * Therefore, Mdot = Mgas / tdyn = Mgas / sqrt(3pi/(32 G rho)) + * and rho = (Mgas + Mstar + Mdm) / (4pi h^3 / 3) where + * Mstar = Mgas / fgas, Mdm = 0. Therefore, + * rho = 3 * ((1 + fgas) / fgas) * Mgas / (4 * pi * h^3) + * and + * Mdot = Mgas * sqrt(32 * G * 3 * ((1 + fgas) / fgas) * Mgas)) / + * sqrt(3 * pi * 4 * pi * h^3) + * = sqrt(96 * G * ((1 + fgas) / fgas) * Mgas^3) / + * sqrt(12 * pi^2 * h^3) + * = (1 / pi) * sqrt(8 * G * ((1 + fgas) / fgas) * (Mgas / h)^3)) + */ + double tdyn_inv = FLT_MAX; + const float potential = fabs(gravity_get_comoving_potential(bp->gpart)); + /* Includes dynamical mass of the BH */ + double total_mass = gravity_get_total_mass(bp->gpart); + switch (props->dynamical_time_calculation_method) { + /* Assume gas fraction is the same in the kernel and outside */ + case 0: + { + /* Compute correction to total dynamical mass around + * BH contributed by stars */ + const double m_star_gal = bp->group_data.stellar_mass; + const double m_gas_cold_gal = + bp->group_data.mass - bp->group_data.stellar_mass; + const double m_gas_bh = bp->gravitational_ngb_mass; + const double m_bh = bp->mass; + + /* Compute stellar mass assuming a constant cold gas fraction in the + * entire galaxy. If m_gas_cold_bh is zero it doesn't matter since + * the BH won't acrrete in the torque mode anyway. */ + const double m_gas_cold_bh = bp->cold_gas_mass; + double m_star_bh = 0.; + if (m_gas_cold_gal > 0.) { + m_star_bh = (m_star_gal / m_gas_cold_gal) * m_gas_cold_bh; + } + + /* Have to assume baryon dominance within the kernel */ + const double rho_est = (m_star_bh + m_gas_bh + m_bh) * volume_bh_inv; + + /* Inverse physical dynamical time */ + tdyn_inv = sqrt(32. * G * rho_est * cosmo->a3_inv / (3. * M_PI)); + break; + } + + /* Assume BH potential */ + case 1: + if (potential >= 0.f) { + tdyn_inv = (sqrt(potential) / bh_h) * cosmo->a2_inv; + } + break; + + /* Assume dynamical time from the kernel mass */ + case 2: + { + /* do not have gravity_props here */ + const double eps = gravity_get_softening(bp->gpart, NULL); + const double volume = (4.f * M_PI / 3.f) * eps * eps * eps; + const double rho = total_mass / volume; + tdyn_inv = sqrt(32. * G * rho * cosmo->a3_inv / (3. * M_PI)); + break; + } + + default: + error("Unknown dynamical time calculation method %d", + props->dynamical_time_calculation_method); + break; + } + + /* Compute infall times to BH at this redshift */ + double t_infall = props->bh_accr_dyn_time_fac / tdyn_inv; + + /* If the input value is above 10, assume it is constant in Myr, + * scaled with 1/H */ + if (props->bh_accr_dyn_time_fac > 10.) { + t_infall = + props->bh_accr_dyn_time_fac * cosmo->H0 / + (props->time_to_Myr * cosmo->H); + } + + const double corot_gas_mass = + bp->cold_gas_mass - 2. * (bp->cold_gas_mass - bp->cold_disk_mass); + if (props->torque_accretion_norm > 0.f) { + switch (props->torque_accretion_method) { + case 0: + if (galaxy_gas_mass > 0.) { + torque_accr_rate = + props->torque_accretion_norm * bp->cold_disk_mass * tdyn_inv; + } + break; + + case 1: + if (corot_gas_mass > 0. && bp->cold_gas_mass > 0.) { + torque_accr_rate = + props->torque_accretion_norm * corot_gas_mass * tdyn_inv; + } + break; + + case 2: + if (corot_gas_mass > 0. && bp->cold_gas_mass > 0.) { + const double m_disk = bp->cold_gas_mass * f_corr_stellar; + const double f_disk = corot_gas_mass / bp->cold_gas_mass; + + const double r0 = bh_h * cosmo->a * (props->length_to_parsec * 0.01); + + const double alpha = 5.; + const double mass_to_1e9solar = props->mass_to_solar_mass * 1.0e-9; + const double mass_to_1e8solar = props->mass_to_solar_mass * 1.0e-8; + + const double f0 = + 0.31 * f_disk * f_disk * + pow(m_disk * mass_to_1e9solar, -1. / 3.); + const double f_gas = corot_gas_mass / m_disk; + const double mass_in_1e8solar = bp->subgrid_mass * mass_to_1e8solar; + + torque_accr_rate = props->torque_accretion_norm * alpha * + corot_gas_mass * mass_to_1e9solar * + powf(f_disk, 5. / 2.) * + powf(mass_in_1e8solar, 1. / 6.) * + powf(r0, -3. / 2.) / (1. + f0 / f_gas); + torque_accr_rate *= + (props->time_to_yr / props->mass_to_solar_mass); + } + break; + + case 3: + if (galaxy_gas_mass > 0.) { + torque_accr_rate = + props->torque_accretion_norm * bp->cold_gas_mass * tdyn_inv; + } + break; + + default: + error("Unknown torque_accretion_method=%d", + props->torque_accretion_method); + break; + } + + /* Do suppression of BH growth */ + switch (props->suppress_growth) { + case 1: + { + const double r0 = bh_h * cosmo->a * props->length_to_parsec; + const double sigma_eff = f_corr_stellar * bp->ngb_mass * + props->mass_to_solar_mass / + (M_PI * r0 * r0); + + torque_accr_rate *= sigma_eff / (sigma_eff + 3000.); + break; + } + + case 2: + case 6: + case 7: + { + double m_suppress = fabs(props->bh_characteristic_suppression_mass); + if (props->bh_characteristic_suppression_mass < 0) { + m_suppress *= cosmo->a; + } + + torque_accr_rate *= + 1. - exp(-bp->subgrid_mass * props->mass_to_solar_mass / m_suppress); + break; + } + + case 4: + case 5: + { + double slope = -0.317; + /* compute mass loading factor from SF feedback, + * should be same as used in feedback_mass_loading_factor() + */ + const double galaxy_stellar_mass = + max(bp->group_data.stellar_mass, 5.8e8 / props->mass_to_solar_mass); + + const double min_mass = 5.2e9 / props->mass_to_solar_mass; + if (galaxy_stellar_mass > min_mass) slope = -0.716; + + const double eta = + 12. * pow(galaxy_stellar_mass / min_mass, slope); + + /* compute fraction of mass within kernel going into outflows + * over accretion time + */ + double sfr = 0.; + if (bp->cold_gas_mass > 0.f) { + sfr = bp->group_data.ssfr * bp->group_data.stellar_mass; + sfr *= props->torque_accretion_norm; + } + + /* suppress accretion by factor accounting for mass lost + * in SF-driven outflow + */ + if (sfr > 0.) { + torque_accr_rate *= torque_accr_rate / (torque_accr_rate + eta * sfr); + } + break; + } + + default: + break; + } + } + +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(bondi_accr_rate)) error("bondi_accr_rate nan"); + if (isnan(torque_accr_rate)) error("torque_accr_rate nan"); +#endif + + /* Right now this is M_dot,inflow. We will multiply by + * f_accretion later to make it M_dot,acc */ + bp->accretion_rate = bondi_accr_rate + torque_accr_rate; + + /* We will use eddington_fraction_lower_boundary and + * eddington_fraction_upper_boundary to divide up the accretion rate + * in three regimes. + * + * In order to switch out of a regime (i.e. a state), it is necessary + * for the true accretion rate (compared to Eddington rate) to switch + * over the boundary. Therefore, before we switch a state we must calculate + * what the previous state predicts the true accretion rate onto the SMBH is, + * and then update the state if it crosses a boundary. + */ + double predicted_mdot_medd = 0.; + switch (bp->state) { + case BH_states_adaf: + predicted_mdot_medd + = bp->accretion_rate * props->adaf_f_accretion / Eddington_rate; + + if (predicted_mdot_medd > props->eddington_fraction_upper_boundary) { + bp->state = BH_states_slim_disk; + break; + } + if (predicted_mdot_medd > props->eddington_fraction_lower_boundary) { + bp->state = BH_states_quasar; + } + + break; /* end case ADAF */ + case BH_states_quasar: + predicted_mdot_medd + = bp->accretion_rate * props->quasar_f_accretion / Eddington_rate; + + if (BH_mass > props->adaf_mass_limit && + predicted_mdot_medd < props->eddington_fraction_lower_boundary) { + bp->state = BH_states_adaf; + break; + } + + if (predicted_mdot_medd > props->eddington_fraction_upper_boundary) { + bp->state = BH_states_slim_disk; + } + + break; /* end case quasar */ + case BH_states_slim_disk: + predicted_mdot_medd = + get_black_hole_upper_mdot_medd(props, constants, + bp->accretion_rate / Eddington_rate); + + if (BH_mass > props->adaf_mass_limit && + predicted_mdot_medd < props->eddington_fraction_lower_boundary) { + bp->state = BH_states_adaf; + break; + } + + if (predicted_mdot_medd < props->eddington_fraction_upper_boundary) { + bp->state = BH_states_quasar; + } + + break; /* end case slim disk */ + default: + error("Invalid black hole state."); + break; + } + + /* We need to store the full M_dot,inflow rate to calculate the + * fraction at high accretion rate */ + bp->m_dot_inflow = bp->accretion_rate; + + /* This depends on the new state */ + bp->f_accretion = + get_black_hole_accretion_factor(props, constants, bp->m_dot_inflow, + BH_mass, bp->state, Eddington_rate); +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(bp->f_accretion)) error("f_accretion nan"); +#endif + if (bp->f_accretion <= 1.e-10f) bp->f_accretion = 0.f; + + /* bp->accretion_rate is M_dot,acc in Rennehan+24 */ + bp->accretion_rate *= bp->f_accretion; + + if (!props->bondi_use_all_gas) { + /* Now we can Eddington limit. */ + bp->accretion_rate = + min(bp->accretion_rate, f_Edd_maximum * Eddington_rate); + } + + /* All accretion is done, now we can set the eddington fraction */ + bp->eddington_fraction = bp->accretion_rate / Eddington_rate; + + /* Get the new radiative efficiency based on the new state */ + bp->radiative_efficiency = + get_black_hole_radiative_efficiency(props, bp->eddington_fraction, + bp->state); +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(bp->radiative_efficiency)) error("radiative_efficiency nan"); +#endif + if (bp->radiative_efficiency < 1.e-10f) bp->radiative_efficiency = 0.f; + + double mass_rate = 0.; + const double luminosity = + bp->radiative_efficiency * bp->accretion_rate * c * c; + + /* Factor in the radiative efficiency, don't subtract + * jet BZ efficiency (spin is fixed) */ + mass_rate = (1. - bp->radiative_efficiency) * bp->accretion_rate; + + /* This is used for X-ray feedback later */ + bp->radiative_luminosity = luminosity; + +#ifdef OBSIDIAN_DEBUG_CHECKS + if (isnan(dt)) error("dt nan"); +#endif + /* Integrate forward in time */ + double delta_mass = mass_rate * dt; + + /* If desired we put mass into accretion disk which feeds BH on some + * frac of tdyn + */ + if (t_infall > 0.f) { + /* Add accreted mass into a reservoir representing BH accretion disk */ + bp->accretion_disk_mass += delta_mass; + + /* Compute mass that will actually go into BH */ + delta_mass = bp->accretion_disk_mass * (1. - exp(-dt / t_infall)); + + /* This mass gets removed from the accretion disk */ + if (bp->accretion_disk_mass > delta_mass) { + bp->accretion_disk_mass -= delta_mass; + } + else { + delta_mass = bp->accretion_disk_mass; + bp->accretion_disk_mass = 0.; + } + + /* Recompute accretion rate based on the reservoir change */ + bp->accretion_rate = delta_mass / (dt * (1. - bp->radiative_efficiency)); + } + + bp->subgrid_mass += delta_mass; + bp->total_accreted_mass += delta_mass; + + if (bp->state == BH_states_adaf) { + /* ergs to dump in a kernel-weighted fashion */ + if (props->adaf_wind_mass_loading == 0.f) { + bp->adaf_energy_to_dump = + get_black_hole_coupling(props, cosmo, bp->state) * + props->adaf_disk_efficiency * bp->accretion_rate * c * c * dt; + } + else { + const float adaf_v2 = props->adaf_wind_speed * props->adaf_wind_speed; + float mass_this_step = + props->adaf_wind_mass_loading * bp->accretion_rate * dt; + bp->adaf_energy_to_dump += 0.5f * mass_this_step * adaf_v2; + } + } + + if (bp->state == BH_states_adaf || + (props->slim_disk_jet_active && bp->state == BH_states_slim_disk)) { + /* Psi_jet*M_dot,acc*dt is the total mass expected in the jet this step */ + bp->jet_mass_reservoir += props->jet_mass_loading * bp->accretion_rate * dt; + } + + if (bp->subgrid_mass < bp->mass) { + /* In this case, the BH is still accreting from its (assumed) subgrid gas + * mass reservoir left over when it was formed. There is some loss in this + * due to radiative losses, so we must decrease the particle mass + * in proportion to its current accretion rate. We do not account for this + * in the swallowing approach, however. */ + bp->mass -= bp->radiative_efficiency * bp->accretion_rate * dt; + + if (bp->mass < 0) { + error("Black hole %lld reached negative mass (%g). Trouble ahead...", + bp->id, bp->mass); + } + + /* Make sure not to destroy low mass galaxies */ + if (bp->subgrid_mass > props->minimum_black_hole_mass_unresolved && + bp->state != BH_states_adaf) { + /* Make sure if many mergers have driven up the dynamical mass at low + * subgrid mass, that we still kick out particles! */ + const float psi = (1.f - bp->f_accretion) / bp->f_accretion; + bp->unresolved_mass_reservoir += psi * bp->accretion_rate * dt; + } + } + + /* Increase the subgrid angular momentum according to what we accreted + * Note that this is already in physical units, a factors from velocity and + * radius cancel each other. Also, the circular velocity contains an extra + * smoothing length factor that we undo here. */ + const double m_times_r = (mass_rate * dt) * bp->h; + /* L = m * r * v */ + bp->accreted_angular_momentum[0] += m_times_r * bp->circular_velocity_gas[0]; + bp->accreted_angular_momentum[1] += m_times_r * bp->circular_velocity_gas[1]; + bp->accreted_angular_momentum[2] += m_times_r * bp->circular_velocity_gas[2]; + + /* Keep v_kick physical, there are a lot of comparisons */ + bp->v_kick = + get_black_hole_wind_speed(props, constants, bp, Eddington_rate); + + /* This is always true in the ADAF mode; only heating happens */ + if (bp->state == BH_states_adaf) { + bp->v_kick = 0.f; + } + +#ifdef OBSIDIAN_DEBUG_CHECKS + tdyn_inv = (tdyn_inv > 0.f) ? tdyn_inv : FLT_MIN; + message("BH_ACC: z=%g bid=%lld ms=%g dms=%g sfr=%g mbh=%g dmbh=%g state=%d " + "torque=%g bondi=%g fEdd=%g facc=%g fsupp=%g mcold=%g mhot=%g mdisk=%g" + " tin=%g vkick=%g dmass=%g radeff=%g mres=%g tdyn=%g", + cosmo->z, + bp->id, + bp->group_data.stellar_mass * props->mass_to_solar_mass, + bp->group_data.ssfr * bp->group_data.stellar_mass * dt * + props->mass_to_solar_mass, + bp->group_data.ssfr * bp->group_data.stellar_mass * + props->mass_to_solar_mass / props->time_to_yr, + bp->subgrid_mass * props->mass_to_solar_mass, + delta_mass * props->mass_to_solar_mass, + bp->state, + torque_accr_rate * props->mass_to_solar_mass / props->time_to_yr, + bondi_accr_rate * props->mass_to_solar_mass / props->time_to_yr, + bp->eddington_fraction, + bp->f_accretion, + 1. - exp(-bp->subgrid_mass * props->mass_to_solar_mass / + fabs(props->bh_characteristic_suppression_mass) * cosmo->a), + bp->cold_gas_mass * props->mass_to_solar_mass, + bp->hot_gas_mass * props->mass_to_solar_mass, + corot_gas_mass * props->mass_to_solar_mass, + t_infall * props->time_to_Myr, + bp->v_kick / props->kms_to_internal, + delta_mass, + bp->radiative_efficiency, + bp->accretion_disk_mass, + (1.f / tdyn_inv) * props->time_to_Myr); + + message("BH_STATES: id=%lld, new_state=%d, predicted_mdot_medd=%g, " + "eps_r=%g, f_Edd=%g, f_acc=%g, " + "luminosity=%g, accr_rate=%g Msun/yr, coupling=%g, v_kick=%g km/s, " + "jet_mass_reservoir=%g Msun unresolved_reservoir=%g Msun", + bp->id, + bp->state, + predicted_mdot_medd, + bp->radiative_efficiency, + bp->eddington_fraction, + bp->f_accretion, + bp->radiative_luminosity * props->conv_factor_energy_rate_to_cgs, + bp->accretion_rate * props->mass_to_solar_mass / props->time_to_yr, + get_black_hole_coupling(props, cosmo, bp->state), + bp->v_kick / props->kms_to_internal, + bp->jet_mass_reservoir * props->mass_to_solar_mass, + bp->unresolved_mass_reservoir * props->mass_to_solar_mass); +#endif + + printf("BH_DETAILS " + "%2.12f %lld " + " %g %g %g %g %g %g %g " + " %g %g %g %g " + " %g %g %g %g %g " + " %2.10f %2.10f %2.10f " + " %2.7f %2.7f %2.7f " + " %g %g %g %g %g %g" + " %g %d %g %g" + " %g %g\n", + cosmo->a, + bp->id, + bp->mass * props->mass_to_solar_mass, + bp->subgrid_mass * props->mass_to_solar_mass, + total_mass * props->mass_to_solar_mass, + bp->accretion_rate * props->mass_to_solar_mass / props->time_to_yr, + Bondi_rate * props->mass_to_solar_mass / props->time_to_yr, + torque_accr_rate * props->mass_to_solar_mass / props->time_to_yr, + dt * props->time_to_Myr, + (bp->rho_gas * cosmo->a3_inv) * props->rho_to_n_cgs, + bp->hot_gas_internal_energy * cosmo->a_factor_internal_energy * + props->conv_factor_specific_energy_to_cgs, + bp->gas_SFR * props->mass_to_solar_mass / props->time_to_yr, + bp->ngb_mass * props->mass_to_solar_mass, + bp->hot_gas_mass * props->mass_to_solar_mass, + bp->stellar_mass * props->mass_to_solar_mass, + 0.f /* Mgas,bulge */, + bp->stellar_bulge_mass * props->mass_to_solar_mass, + 0.f, + bp->x[0] * cosmo->a * props->length_to_parsec / 1.0e3f, + bp->x[1] * cosmo->a * props->length_to_parsec / 1.0e3f, + bp->x[2] * cosmo->a * props->length_to_parsec / 1.0e3f, + bp->v[0] * cosmo->a_inv / props->kms_to_internal, + bp->v[1] * cosmo->a_inv / props->kms_to_internal, + bp->v[2] * cosmo->a_inv / props->kms_to_internal, + bp->angular_momentum_gas[0], + bp->angular_momentum_gas[1], + bp->angular_momentum_gas[2], + 0.f, /* specific angular momentum of the stars */ + 0.f, /* specific angular momentum of the stars */ + 0.f, /* specific angular momentum of the stars */ + bp->radiative_luminosity * props->conv_factor_energy_rate_to_cgs, + bp->state, + bp->f_accretion, + bp->radiative_efficiency, + bp->eddington_fraction, + bp->gravitational_ngb_mass * props->mass_to_solar_mass); +} + +/** + * @brief Computes the (maximal) repositioning speed for a black hole. + * + * Calculated as upsilon * (m_BH / m_ref) ^ beta_m * (n_H_BH / n_ref) ^ beta_n + * where m_BH = BH subgrid mass, n_H_BH = physical gas density around BH + * and upsilon, m_ref, beta_m, n_ref, and beta_n are parameters. + * + * @param bp The #bpart. + * @param props The properties of the black hole model. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static double +black_holes_get_repositioning_speed(const struct bpart* restrict bp, + const struct black_holes_props* props, + const struct cosmology* cosmo) { + + const double n_gas_phys = bp->rho_gas * cosmo->a3_inv * props->rho_to_n_cgs; + const double v_repos = + props->reposition_coefficient_upsilon * + pow(bp->subgrid_mass / props->reposition_reference_mass, + props->reposition_exponent_mass) * + pow(n_gas_phys / props->reposition_reference_n_H, + props->reposition_exponent_n_H); + + /* Make sure the repositioning is not back-firing... */ + if (v_repos < 0) + error( + "BH %lld wants to reposition at negative speed (%g U_V). Do you " + "think you are being funny? No-one is laughing.", + bp->id, v_repos); + + return v_repos; +} + +/** + * @brief Finish the calculation of the new BH position. + * + * Here, we check that the BH should indeed be moved in the next drift. + * + * @param bp The black hole particle. + * @param props The properties of the black hole scheme. + * @param constants The physical constants (in internal units). + * @param cosmo The cosmological model. + * @param dt The black hole particle's time step. + * @param ti_begin The time at the start of the temp + */ +__attribute__((always_inline)) INLINE static void black_holes_end_reposition( + struct bpart* restrict bp, const struct black_holes_props* props, + const struct phys_const* constants, const struct cosmology* cosmo, + const double dt, const integertime_t ti_begin) { + + /* First check: did we find any eligible neighbour particle to jump to? */ + if (bp->reposition.min_potential != FLT_MAX) { + + /* Record that we have a (possible) repositioning situation */ + bp->number_of_reposition_attempts++; + + /* Is the potential lower (i.e. the BH is at the bottom already) + * OR is the BH massive enough that we don't reposition? */ + const float potential = gravity_get_comoving_potential(bp->gpart); + if (potential < bp->reposition.min_potential || + bp->subgrid_mass > props->max_reposition_mass) { + + /* No need to reposition */ + bp->reposition.min_potential = FLT_MAX; + bp->reposition.delta_x[0] = -FLT_MAX; + bp->reposition.delta_x[1] = -FLT_MAX; + bp->reposition.delta_x[2] = -FLT_MAX; + + } else if (props->set_reposition_speed) { + + /* If we are re-positioning, move the BH a fraction of delta_x, so + * that we have a well-defined re-positioning velocity (repos_vel + * cannot be negative). */ + double repos_vel = black_holes_get_repositioning_speed(bp, props, cosmo); + + /* Convert target reposition velocity to a fractional reposition + * along reposition.delta_x */ + const double dx = bp->reposition.delta_x[0]; + const double dy = bp->reposition.delta_x[1]; + const double dz = bp->reposition.delta_x[2]; + const double d = sqrt(dx * dx + dy * dy + dz * dz); + + /* Exclude the pathological case of repositioning by zero distance */ + if (d > 0) { + double repos_frac = repos_vel * dt / d; + + /* We should never get negative repositioning fractions... */ + if (repos_frac < 0) + error("Wanting to reposition by negative fraction (%g)?", repos_frac); + + /* ... but fractions > 1 can occur if the target velocity is high. + * We do not want this, because it could lead to overshooting the + * actual potential minimum. */ + if (repos_frac > 1) { + repos_frac = 1.; + repos_vel = repos_frac * d / dt; + } + + bp->last_repos_vel = (float)repos_vel; + bp->reposition.delta_x[0] *= repos_frac; + bp->reposition.delta_x[1] *= repos_frac; + bp->reposition.delta_x[2] *= repos_frac; + } + + /* ends section for fractional repositioning */ + } else { + + /* We _should_ reposition, but not fractionally. Here, we will + * reposition exactly on top of another gas particle - which + * could cause issues, so we add on a small fractional offset + * of magnitude 0.001 h in the reposition delta. */ + + /* Generate three random numbers in the interval [-0.5, 0.5[; id, + * id**2, and id**3 are required to give unique random numbers (as + * random_unit_interval is completely reproducible). */ + const float offset_dx = + random_unit_interval(bp->id, ti_begin, random_number_BH_reposition) - + 0.5f; + const float offset_dy = + random_unit_interval(bp->id * bp->id, ti_begin, + random_number_BH_reposition) - + 0.5f; + const float offset_dz = + random_unit_interval(bp->id * bp->id * bp->id, ti_begin, + random_number_BH_reposition) - + 0.5f; + + const float length_inv = + 1.0f / sqrtf(offset_dx * offset_dx + offset_dy * offset_dy + + offset_dz * offset_dz); + + const float norm = 0.001f * bp->h * length_inv; + + bp->reposition.delta_x[0] += offset_dx * norm; + bp->reposition.delta_x[1] += offset_dy * norm; + bp->reposition.delta_x[2] += offset_dz * norm; + } + } /* ends section if we found eligible repositioning target(s) */ +} + +/** + * @brief Reset acceleration fields of a particle + * + * This is the equivalent of hydro_reset_acceleration. + * We do not compute the acceleration on black hole, therefore no need to use + * it. + * + * @param bp The particle to act upon + */ +__attribute__((always_inline)) INLINE static void black_holes_reset_feedback( + struct bpart* restrict bp) { + +#ifdef DEBUG_INTERACTIONS_BLACK_HOLES + for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_STARS; ++i) + bp->ids_ngbs_force[i] = -1; + bp->num_ngb_force = 0; +#endif +} + +/** + * @brief Store the gravitational potential of a black hole by copying it from + * its #gpart friend. + * + * @param bp The black hole particle. + * @param gp The black hole's #gpart. + */ +__attribute__((always_inline)) INLINE static void +black_holes_store_potential_in_bpart(struct bpart* bp, const struct gpart* gp) { + +#ifdef SWIFT_DEBUG_CHECKS + if (bp->gpart != gp) error("Copying potential to the wrong black hole!"); +#endif + + bp->reposition.potential = gp->potential; +} + +/** + * @brief Store the gravitational potential of a particle by copying it from + * its #gpart friend. + * + * @param p_data The black hole data of a gas particle. + * @param gp The black hole's #gpart. + */ +__attribute__((always_inline)) INLINE static void +black_holes_store_potential_in_part(struct black_holes_part_data* p_data, + const struct gpart* gp) { + p_data->potential = gp->potential; +} + +/** + * @brief Initialise a BH particle that has just been seeded. + * + * @param bp The #bpart to initialise. + * @param props The properties of the black hole scheme. + * @param constants The physical constants in internal units. + * @param cosmo The current cosmological model. + * @param p The #part that became a black hole. + * @param xp The #xpart that became a black hole. + */ +INLINE static void black_holes_create_from_gas( + struct bpart* bp, const struct black_holes_props* props, + const struct phys_const* constants, const struct cosmology* cosmo, + const struct part* p, const struct xpart* xp, + const integertime_t ti_current) { + + /* All the non-basic properties of the black hole have been zeroed + * in the FOF code. We update them here. + * (i.e. position, velocity, mass, time-step have been set) */ + + /* Birth time and density */ + bp->formation_scale_factor = cosmo->a; + bp->formation_gas_density = hydro_get_physical_density(p, cosmo); + + /* Initial seed mass */ + bp->subgrid_mass = props->subgrid_seed_mass; + + /* We haven't accreted anything yet */ + bp->total_accreted_mass = 0.f; + bp->cumulative_number_seeds = 1; + bp->number_of_mergers = 0; + bp->number_of_gas_swallows = 0; + bp->number_of_direct_gas_swallows = 0; + bp->number_of_time_steps = 0; + + /* We haven't repositioned yet, nor attempted it */ + bp->number_of_repositions = 0; + bp->number_of_reposition_attempts = 0; + bp->last_repos_vel = 0.f; + + /* Copy over the splitting struct */ + bp->split_data = xp->split_data; + + /* Initial metal masses */ + const float gas_mass = hydro_get_mass(p); + struct chemistry_bpart_data* bp_chem = &bp->chemistry_data; + const struct chemistry_part_data* p_chem = &p->chemistry_data; + chemistry_bpart_from_part(bp_chem, p_chem, gas_mass); + + /* No swallowed angular momentum */ + bp->swallowed_angular_momentum[0] = 0.f; + bp->swallowed_angular_momentum[1] = 0.f; + bp->swallowed_angular_momentum[2] = 0.f; + + /* Last time of mergers */ + bp->last_minor_merger_time = -1.; + bp->last_major_merger_time = -1.; + + /* First initialisation */ + black_holes_init_bpart(bp); + + bp->state = BH_states_slim_disk; + + black_holes_mark_bpart_as_not_swallowed(&bp->merger_data); +} + +/** + * @brief Should this bh particle be doing any stars looping? + * + * @param bp The #bpart. + * @param e The #engine. + */ +__attribute__((always_inline)) INLINE static int bh_stars_loop_is_active( + const struct bpart* bp, const struct engine* e) { + /* Active bhs never do the stars loop for the Obsidian model */ + return 0; +} + +#endif /* SWIFT_OBSIDIAN_BLACK_HOLES_H */ diff --git a/src/black_holes/Obsidian/black_holes_debug.h b/src/black_holes/Obsidian/black_holes_debug.h new file mode 100644 index 0000000000..5b97fc5c33 --- /dev/null +++ b/src/black_holes/Obsidian/black_holes_debug.h @@ -0,0 +1,31 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_BLACK_HOLES_OBSIDIAN_DEBUG_H +#define SWIFT_BLACK_HOLES_OBSIDIAN_DEBUG_H + +__attribute__((always_inline)) INLINE static void black_holes_debug_particle( + const struct part *p, const struct xpart *xp) { + + warning("[PID%lld] black_holes_part_data:", p->id); + warning("[PID%lld] swallow_id = %lld, potential = %.3e", p->id, + p->black_holes_data.swallow_id, p->black_holes_data.potential); +} + +#endif /* SWIFT_BLACK_HOLES_OBSIDIAN_DEBUG_H */ diff --git a/src/black_holes/Obsidian/black_holes_iact.h b/src/black_holes/Obsidian/black_holes_iact.h new file mode 100644 index 0000000000..9be723eab3 --- /dev/null +++ b/src/black_holes/Obsidian/black_holes_iact.h @@ -0,0 +1,1387 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2021 Edo Altamura (edoardo.altamura@manchester.ac.uk) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_OBSIDIAN_BH_IACT_H +#define SWIFT_OBSIDIAN_BH_IACT_H + +/* Local includes */ +#include "black_holes_parameters.h" +#include "black_holes_properties.h" +#include "entropy_floor.h" +#include "equation_of_state.h" +#include "gravity.h" +#include "gravity_iact.h" +#include "hydro.h" +#include "random.h" +#include "space.h" +#include "timestep_sync_part.h" +#include "tracers.h" + +// #define OBSIDIAN_DEBUG_CHECKS + +/** + * @brief Set direction of black hole feedback kick + * + * @param bi Black hole particle. + * @param pj Gas particle being kicked. + * @param ti_current Current integer time value (for random numbers). + * @param dir_flag Flag to choose direction: 0=rendom, 1=L_gas, 2=L_BH. + * @param dir Direction of kick (returned). + */ +__attribute__((always_inline)) INLINE static float +black_hole_set_kick_direction(const struct bpart *bi, const struct part *pj, + const integertime_t ti_current, + const int dir_flag, float *dir) { + + float kick_dir = 1.f; + double random_number = 1.; + switch (dir_flag) { + /* Isotropic */ + case 0: { + const double random_for_theta = + random_unit_interval(bi->id, ti_current, random_number_BH_feedback); + const double random_for_phi = + random_unit_interval(bi->id, ti_current, random_number_BH_feedback); + + const float theta = acosf(2.f * random_for_theta - 1.f); + const float phi = 2.f * M_PI * random_for_phi; + + dir[0] = sinf(theta) * cosf(phi); + dir[1] = sinf(theta) * sinf(phi); + dir[2] = cosf(theta); + break; + } + + /* Along the angular momentum vector of the gas */ + case 1: + dir[0] = bi->angular_momentum_gas[0]; + dir[1] = bi->angular_momentum_gas[1]; + dir[2] = bi->angular_momentum_gas[2]; + random_number = + random_unit_interval(bi->id, ti_current, random_number_BH_feedback); + kick_dir = (random_number > 0.5) ? 1.f : -1.f; + break; + + /* Along the accreted angular momentum of the gas */ + case 2: + dir[0] = + bi->accreted_angular_momentum[0] + bi->swallowed_angular_momentum[0]; + dir[1] = + bi->accreted_angular_momentum[1] + bi->swallowed_angular_momentum[1]; + dir[2] = + bi->accreted_angular_momentum[2] + bi->swallowed_angular_momentum[2]; + random_number = + random_unit_interval(bi->id, ti_current, random_number_BH_feedback); + kick_dir = (random_number > 0.5) ? 1.f : -1.f; + break; + + /* Outwards from BH*/ + case 3: + dir[0] = pj->x[0] - bi->x[0]; + dir[1] = pj->x[1] - bi->x[1]; + dir[2] = pj->x[2] - bi->x[2]; + break; + + default: + error("dir_flag=%d but must be 0, 1, or 2", dir_flag); + break; + } + + return kick_dir; +} + +/** + * @brief Categorise gas temperature as hot or cold or neither for BH accretion + * + * @param bi First particle (black hole). + * @param pj Second particle (gas, not updated). + * @param cosmo The cosmological model. + * @param bh_props The properties of the BH scheme + */ +__attribute__((always_inline)) INLINE static int +black_hole_gas_hot_or_cold(const struct bpart *bi, const struct part *pj, + const struct cosmology *cosmo, const struct black_holes_props *bh_props) +{ + /* Neighbour internal energy */ + const float uj = hydro_get_drifted_comoving_internal_energy(pj); + + /* Determine if it is hot or cold gas surrounding the SMBH */ + int gas_state = 0; + const float Tj = + uj * cosmo->a_factor_internal_energy / bh_props->temp_to_u_factor; + if (Tj > bh_props->environment_temperature_cut && pj->sf_data.SFR <= 0.f) { + gas_state = 1; + } + /* Cold gas must be cool and SF'ing */ + if (Tj < bh_props->cold_gas_temperature_cut && pj->sf_data.SFR > 0.) { + gas_state = -1; + } + + return gas_state; +} + +/** + * @brief Density interaction between two particles (non-symmetric). + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param bi First particle (black hole). + * @param pj Second particle (gas, not updated). + * @param xpj The extended data of the second particle (not updated). + * @param with_cosmology Are we doing a cosmological run? + * @param cosmo The cosmological model. + * @param grav_props The properties of the gravity scheme (softening, G, ...). + * @param bh_props The properties of the BH scheme + * @param ti_current Current integer time value (for random numbers). + * @param time Current physical time in the simulation. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_bh_gas_density( + const float r2, const float dx[3], const float hi, const float hj, + struct bpart *bi, const struct part *pj, const struct xpart *xpj, + const int with_cosmology, const struct cosmology *cosmo, + const struct gravity_props *grav_props, + const struct black_holes_props *bh_props, + const struct entropy_floor_properties *floor_props, + const integertime_t ti_current, const double time) { + + /* Get the total gravitationally interacting mass within the kernel */ + const float mj = hydro_get_mass(pj); + + /* Compute total mass that contributes to the dynamical time */ + bi->gravitational_ngb_mass += mj; + bi->num_gravitational_ngbs += 1; + + float wi, wi_dx; + + /* Compute the kernel function; note that r cannot be optimised + * to r2 / sqrtf(r2) because of 1 / 0 behaviour. */ + const float r = sqrtf(r2); + const float hi_inv = 1.0f / hi; + const float ui = r * hi_inv; + kernel_deval(ui, &wi, &wi_dx); + + /* Contribution to the BH gas density */ + bi->rho_gas += mj * wi; + + /* Compute contribution to the number of neighbours */ + bi->density.wcount += wi; + bi->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx); + + /* Contribution to the smoothed sound speed */ + const float cj = hydro_get_comoving_soundspeed(pj); + bi->sound_speed_gas += mj * wi * cj; + + /* Neighbour internal energy */ + const float uj = hydro_get_drifted_comoving_internal_energy(pj); + + /* Contribution to the smoothed internal energy */ + bi->internal_energy_gas += mj * uj * wi; + + /* weighting for feedback */ + bi->kernel_wt_sum += mj * wi; + + /* Contribution to the number of neighbours */ + bi->num_ngbs += 1; + + /* Contribution to the total neighbour mass */ + bi->ngb_mass += mj; + + /* Neighbour's (drifted) velocity in the frame of the black hole + * (we don't include a Hubble term since we are interested in the + * velocity contribution at the location of the black hole) */ + const float dv[3] = {pj->v[0] - bi->v[0], pj->v[1] - bi->v[1], + pj->v[2] - bi->v[2]}; + + /* Classify gas as hot or cold for accretion */ + const int gas_temperature_state = black_hole_gas_hot_or_cold(bi, pj, cosmo, bh_props); + + if (gas_temperature_state == 1) { + bi->hot_gas_mass += mj; + bi->hot_gas_internal_energy += mj * uj; /* Not kernel weighted */ + } + else if (gas_temperature_state == -1) { +#if COOLING_GRACKLE_MODE >= 2 + /* With subgrid ISM model, only allow H2 component to be accreted */ + bi->cold_gas_mass += mj * pj->cooling_data.subgrid_fcold * pj->sf_data.H2_fraction; +#else + bi->cold_gas_mass += mj; +#endif + bi->gas_SFR += max(pj->sf_data.SFR, 0.); + //if (bi->subgrid_mass * bh_props->mass_to_solar_mass > 1.e10) message("BH_SFR bid=%lld pid=%lld mj=%g psfr=%g nH=%g T=%g fH2=%g totSFR=%g", bi->id, pj->id, mj * bh_props->mass_to_solar_mass, max(pj->sf_data.SFR, 0.) * bh_props->mass_to_solar_mass / bh_props->time_to_yr, pj->rho * bh_props->conv_factor_density_to_cgs / 1.673e-24, pj->u * cosmo->a_factor_internal_energy / (bh_props->T_K_to_int * bh_props->temp_to_u_factor), pj->sf_data.H2_fraction, bi->gas_SFR * bh_props->mass_to_solar_mass / bh_props->time_to_yr); + } + + const float L_x = mj * (dx[1] * dv[2] - dx[2] * dv[1]); + const float L_y = mj * (dx[2] * dv[0] - dx[0] * dv[2]); + const float L_z = mj * (dx[0] * dv[1] - dx[1] * dv[0]); + + /* Gas angular momentum in kernel */ + bi->angular_momentum_gas[0] -= L_x; + bi->angular_momentum_gas[1] -= L_y; + bi->angular_momentum_gas[2] -= L_z; + + /* Contribution to the velocity (gas w.r.t. black hole) */ + bi->velocity_gas[0] += mj * dv[0]; + bi->velocity_gas[1] += mj * dv[1]; + bi->velocity_gas[2] += mj * dv[2]; + + /* Contribution to the specific angular momentum of gas, which is later + * converted to the circular velocity */ + bi->circular_velocity_gas[0] -= L_x; + bi->circular_velocity_gas[1] -= L_y; + bi->circular_velocity_gas[2] -= L_z; + +#ifdef DEBUG_INTERACTIONS_BH + /* Update ngb counters */ + if (si->num_ngb_density < MAX_NUM_OF_NEIGHBOURS_BH) + bi->ids_ngbs_density[si->num_ngb_density] = pj->id; + + /* Update ngb counters */ + ++si->num_ngb_density; +#endif +} + +/** + * @brief Repositioning interaction between two particles (non-symmetric). + * + * Function used to identify the gas particle that this BH may move towards. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param bi First particle (black hole). + * @param pj Second particle (gas) + * @param xpj The extended data of the second particle. + * @param with_cosmology Are we doing a cosmological run? + * @param cosmo The cosmological model. + * @param grav_props The properties of the gravity scheme (softening, G, ...). + * @param bh_props The properties of the BH scheme + * @param ti_current Current integer time value (for random numbers). + * @param time Current physical time in the simulation. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_bh_gas_repos( + const float r2, const float dx[3], const float hi, const float hj, + struct bpart *bi, const struct part *pj, const struct xpart *xpj, + const int with_cosmology, const struct cosmology *cosmo, + const struct gravity_props *grav_props, + const struct black_holes_props *bh_props, + const struct entropy_floor_properties *floor_props, + const integertime_t ti_current, const double time) { + + /* Ignore decoupled wind particles for repositioning BH */ + if (pj->decoupled) return; + + float wi; + + /* Compute the kernel function; note that r cannot be optimised + * to r2 / sqrtf(r2) because of 1 / 0 behaviour. */ + const float r = sqrtf(r2); + const float hi_inv = 1.0f / hi; + const float ui = r * hi_inv; + kernel_eval(ui, &wi); + + /* (Square of) Max repositioning distance allowed based on the softening */ + const float max_dist_repos2 = + kernel_gravity_softening_plummer_equivalent_inv * + kernel_gravity_softening_plummer_equivalent_inv * + bh_props->max_reposition_distance_ratio * + bh_props->max_reposition_distance_ratio * bi->h * bi->h; + + /* Is this gas neighbour close enough that we can consider its potential + for repositioning? */ + if (r2 < max_dist_repos2) { + + /* Flag to check whether neighbour is slow enough to be considered + * as repositioning target. Always true if velocity cut is switched off. */ + int neighbour_is_slow_enough = 1; + if (bh_props->with_reposition_velocity_threshold) { + + /* Compute relative peculiar velocity between the two BHs + * Recall that in SWIFT v is (v_pec * a) */ + const float delta_v[3] = {bi->v[0] - pj->v[0], bi->v[1] - pj->v[1], + bi->v[2] - pj->v[2]}; + const float v2 = delta_v[0] * delta_v[0] + delta_v[1] * delta_v[1] + + delta_v[2] * delta_v[2]; + const float v2_pec = v2 * cosmo->a2_inv; + + /* Compute the maximum allowed velocity */ + float v2_max = bh_props->max_reposition_velocity_ratio * + bh_props->max_reposition_velocity_ratio * + bi->sound_speed_gas * bi->sound_speed_gas * + cosmo->a_factor_sound_speed * cosmo->a_factor_sound_speed; + + /* If desired, limit the value of the threshold (v2_max) to be no + * smaller than a user-defined value */ + if (bh_props->min_reposition_velocity_threshold > 0) { + const float v2_min_thresh = + bh_props->min_reposition_velocity_threshold * + bh_props->min_reposition_velocity_threshold; + v2_max = max(v2_max, v2_min_thresh); + } + + /* Is the neighbour too fast to jump to? */ + if (v2_pec >= v2_max) neighbour_is_slow_enough = 0; + } + + if (neighbour_is_slow_enough) { + float potential = pj->black_holes_data.potential; + + if (bh_props->correct_bh_potential_for_repositioning) { + + /* Let's not include the contribution of the BH + * itself to the potential of the gas particle */ + + /* Note: This assumes the BH and gas have the same + * softening, which is currently true */ + const float eps = gravity_get_softening(bi->gpart, grav_props); + const float eps2 = eps * eps; + const float eps_inv = 1.f / eps; + const float eps_inv3 = eps_inv * eps_inv * eps_inv; + const float BH_mass = bi->mass; + + /* Compute the Newtonian or truncated potential the BH + * exherts onto the gas particle */ + float dummy, pot_ij; + runner_iact_grav_pp_full(r2, eps2, eps_inv, eps_inv3, BH_mass, &dummy, + &pot_ij); + + /* Deduct the BH contribution */ + potential -= pot_ij * grav_props->G_Newton; + } + + /* Is the potential lower? */ + if (potential < bi->reposition.min_potential) { + + /* Store this as our new best */ + bi->reposition.min_potential = potential; + bi->reposition.delta_x[0] = -dx[0]; + bi->reposition.delta_x[1] = -dx[1]; + bi->reposition.delta_x[2] = -dx[2]; + } + } + } +} + +/** + * @brief Swallowing interaction between two particles (non-symmetric). + * + * Function used to flag the gas particles that will be swallowed + * by the black hole particle. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param bi First particle (black hole). + * @param pj Second particle (gas) + * @param xpj The extended data of the second particle. + * @param with_cosmology Are we doing a cosmological run? + * @param cosmo The cosmological model. + * @param grav_props The properties of the gravity scheme (softening, G, ...). + * @param bh_props The properties of the BH scheme + * @param ti_current Current integer time value (for random numbers). + * @param time Current physical time in the simulation. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_bh_gas_swallow( + const float r2, const float dx[3], const float hi, const float hj, + struct bpart *bi, struct part *pj, struct xpart *xpj, + const int with_cosmology, const struct cosmology *cosmo, + const struct gravity_props *grav_props, + const struct black_holes_props *bh_props, + const struct entropy_floor_properties *floor_props, + const integertime_t ti_current, const double time) { + + /* Collect information about galaxy that the particle belongs to */ + float galaxy_mstar = bi->galaxy_data.stellar_mass; + + /* A black hole should never accrete/feedback if it is not in a galaxy */ + if (galaxy_mstar <= 0.f) return; + + /* If there is no gas, skip */ + if (bi->ngb_mass <= 0.f) return; + + /* Black hole should only accrete gas within the same galaxy */ + if (galaxy_mstar != pj->galaxy_data.stellar_mass) return; + + /* Gas particle must be bound to BH kernel mass to be swallowed */ + const float r = sqrtf(r2); + const float dv[3] = {bi->v[0] - pj->v[0], bi->v[1] - pj->v[1], + bi->v[2] - pj->v[2]}; + const float u_kinetic = 0.5 * (dv[0] * dv[0] + dv[1] * dv[1] + dv[2] * dv[2]); + const float u_potential = + bh_props->const_newton_G * (bi->ngb_mass + bi->mass) / r; + + if (u_kinetic > u_potential) return; + + /* Compute the kernel function */ + float wi; + const float hi_inv = 1.0f / hi; + const float ui = r * hi_inv; + kernel_eval(ui, &wi); + + /* Sum up cold disk mass corotating relative to total angular momentum. + * This can't be used to compute accretion since it is not summable + * prior to this swallow routine; we need an intermediate loop + * if we want to use this to set accretion ala Simba.*/ + + const float mj = hydro_get_mass(pj); + + /* Classify gas as hot or cold for accretion */ + const int gas_temperature_state = black_hole_gas_hot_or_cold(bi, pj, cosmo, bh_props); + + const float Lx = mj * (dx[1] * dv[2] - dx[2] * dv[1]); + const float Ly = mj * (dx[2] * dv[0] - dx[0] * dv[2]); + const float Lz = mj * (dx[0] * dv[1] - dx[1] * dv[0]); + const float proj = Lx * bi->angular_momentum_gas[0] + + Ly * bi->angular_momentum_gas[1] + + Lz * bi->angular_momentum_gas[2]; + if ((proj > 0.f) && gas_temperature_state == -1) { + bi->cold_disk_mass += mj; + } + + /* Probability to swallow this particle */ + float prob = -1.f; + float f_accretion = bi->f_accretion; + + /* No accretion? Nothing to do */ + if (f_accretion <= 0.f) return; + + const float pj_mass_orig = mj; + const float nibbled_mass = f_accretion * pj_mass_orig; + + /* Normalize the weights */ + const float kernel_wt = + (bi->kernel_wt_sum > 0.f) ? wi / bi->kernel_wt_sum : 0.f; + + /* Radiation was already accounted for in bi->subgrid_mass + * so if is is bigger than bi->mass we can simply + * flag particles to eat and satisfy the mass constraint. + * + * If bi->subgrid_mass < bi->mass then there is a problem, + * but we use the full accretion rate to select particles + * and then don't actually take anything away from them. + * The bi->mass variable is decreased previously to account + * for the radiative losses. + */ + const float mass_deficit = bi->subgrid_mass - bi->mass_at_start_of_step; + if (mass_deficit > 0.f) { + /* Don't nibble from particles that are too small already */ + if (mj < bh_props->min_gas_mass_for_nibbling) return; + + /* Just enough to satisfy M_dot,inflow */ + prob = (mass_deficit / f_accretion) * kernel_wt; + } else { + /* Do not grow the physical mass, only kick */ + f_accretion = 0.f; + + /* Check the accretion reservoir and if it has passed the limit */ + if (bi->unresolved_mass_reservoir > 0.f) { + prob = bi->unresolved_mass_reservoir * kernel_wt; + } + } + + /* Draw a random number (Note mixing both IDs) */ + const float rand = random_unit_interval(bi->id + pj->id, ti_current, + random_number_BH_swallow); + float new_gas_mass = pj_mass_orig; + /* Are we lucky? */ + if (rand < prob) { + + if (f_accretion > 0.f) { + const float bi_mass_orig = bi->mass; + new_gas_mass = pj_mass_orig - nibbled_mass; + /* Don't go below the minimum for stability */ + if (new_gas_mass < bh_props->min_gas_mass_for_nibbling) return; + + bi->mass += nibbled_mass; + hydro_set_mass(pj, new_gas_mass); + + /* Add the angular momentum of the accreted gas to the BH total. + * Note no change to gas here. The cosmological conversion factors for + * velocity (a^-1) and distance (a) cancel out, so the angular momentum + * is already in physical units. */ + bi->swallowed_angular_momentum[0] += + nibbled_mass * (dx[1] * dv[2] - dx[2] * dv[1]); + bi->swallowed_angular_momentum[1] += + nibbled_mass * (dx[2] * dv[0] - dx[0] * dv[2]); + bi->swallowed_angular_momentum[2] += + nibbled_mass * (dx[0] * dv[1] - dx[1] * dv[0]); + + /* Update the BH momentum and velocity. Again, no change to gas here. */ + const float bi_mom[3] = { + bi_mass_orig * bi->v[0] + nibbled_mass * pj->v[0], + bi_mass_orig * bi->v[1] + nibbled_mass * pj->v[1], + bi_mass_orig * bi->v[2] + nibbled_mass * pj->v[2]}; + + /* TODO: Spoke to Matthieu about this, it is a bug cannot assign here. */ + bi->v[0] = bi_mom[0] / bi->mass; + bi->v[1] = bi_mom[1] / bi->mass; + bi->v[2] = bi_mom[2] / bi->mass; + + /* Update the BH and also gas metal masses */ + struct chemistry_bpart_data *bi_chem = &bi->chemistry_data; + struct chemistry_part_data *pj_chem = &pj->chemistry_data; + chemistry_transfer_part_to_bpart(bi_chem, pj_chem, nibbled_mass, + nibbled_mass / pj_mass_orig); + } + + /* This particle is swallowed by the BH with the largest ID of all the + * candidates wanting to swallow it */ + if (pj->black_holes_data.swallow_id < bi->id) { + /* Handle the ADAF heating separately */ + if (bi->state != BH_states_adaf) { + pj->black_holes_data.swallow_id = bi->id; + } + + /* Keep track of unresolved mass kicks */ + if (mass_deficit <= 0.f) { + bi->unresolved_mass_kicked_this_step += new_gas_mass; + } + } else { + message( + "BH %lld wants to swallow gas particle %lld BUT CANNOT (old " + "swallow id=%lld)", + bi->id, pj->id, pj->black_holes_data.swallow_id); + } + } + + /* When there is zero mass loading the ADAF mode heats the entire kernel + * so all of the weights are required in the sum. */ + if (bi->state == BH_states_adaf) { + /* Zero mass loading implies entire + kernel heating */ + if (bh_props->adaf_wind_mass_loading == 0.f) { + const float adaf_wt = new_gas_mass * wi; + bi->adaf_wt_sum += adaf_wt; + + /* Normalized later */ + bi->adaf_energy_used_this_step += bi->adaf_energy_to_dump * adaf_wt; + } else { + /* --- The entire kernel is NOT heated here ---- */ + + /* Heating is based on specific energy and the mass loading */ + if (bi->adaf_energy_to_dump > 0.f && bh_props->adaf_wind_speed > 0.f) { + const float adaf_v2 = + bh_props->adaf_wind_speed * bh_props->adaf_wind_speed; + const float adaf_mass_to_heat = 2.f * bi->adaf_energy_to_dump / adaf_v2; + + /* Bernoulli trial P = M_adaf * wi / sum(mj * wj) */ + const float adaf_heat_prob = adaf_mass_to_heat * kernel_wt; + + /* Draw a random number (Note mixing both IDs) */ + const float adaf_rand = random_unit_interval( + bi->id + pj->id, ti_current, random_number_BH_swallow); + + /* Identify ADAF heating particles by their ID, and only heat those! */ + if (adaf_rand < adaf_heat_prob) { + if (pj->black_holes_data.adaf_id < bi->id) { + pj->black_holes_data.adaf_id = bi->id; + + /* New normalization for ADAF heating. Use new gas mass since that + * is what is in the feedback loop. */ + const float adaf_wt = new_gas_mass * wi; + bi->adaf_wt_sum += adaf_wt; + /* Will be normalized at the end when reducing the reservoir */ + bi->adaf_energy_used_this_step += bi->adaf_energy_to_dump * adaf_wt; + } else { + message( + "BH %lld wants to heat particle %lld BUT CANNOT (old " + "adaf_id=%lld)", + bi->id, pj->id, pj->black_holes_data.adaf_id); + } + } + } + } + } + + /* Check jet reservoir regardless of the state, allows simultaneous + * ADAF heating and a kinetic jet. */ + if (bi->jet_mass_reservoir >= bh_props->jet_minimum_reservoir_mass) { + + /* Make sure there is enough gas to kick */ + if (bi->ngb_mass < bh_props->jet_minimum_reservoir_mass) return; + + float jet_prob = bi->jet_mass_reservoir * kernel_wt; + const float rand_jet = random_unit_interval(bi->id + pj->id, ti_current, + random_number_BH_kick); + + /* Here the particle is also identified to be kicked out as a jet */ + if (rand_jet < jet_prob) { + + /* If we also are accreting above, the mass loss is already taken + * into account */ + + if (pj->black_holes_data.jet_id < bi->id) { + bi->jet_mass_kicked_this_step += new_gas_mass; + pj->black_holes_data.jet_id = bi->id; + } else { + message( + "BH %lld wants to kick jet particle %lld BUT CANNOT (old " + "jet_id=%lld)", + bi->id, pj->id, pj->black_holes_data.jet_id); + } + } + } +} + +/** + * @brief Swallowing interaction between two BH particles (non-symmetric). + * + * Function used to identify the BH particle that this BH may move towards. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param bi First particle (black hole). + * @param bj Second particle (black hole) + * @param cosmo The cosmological model. + * @param grav_props The properties of the gravity scheme (softening, G, ...). + * @param bh_props The properties of the BH scheme + * @param ti_current Current integer time value (for random numbers). + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_bh_bh_repos(const float r2, const float dx[3], + const float hi, const float hj, struct bpart *bi, + const struct bpart *bj, + const struct cosmology *cosmo, + const struct gravity_props *grav_props, + const struct black_holes_props *bh_props, + const integertime_t ti_current) { + + /* Compute relative peculiar velocity between the two BHs + * Recall that in SWIFT v is (v_pec * a) */ + const float delta_v[3] = {bi->v[0] - bj->v[0], bi->v[1] - bj->v[1], + bi->v[2] - bj->v[2]}; + const float v2 = delta_v[0] * delta_v[0] + delta_v[1] * delta_v[1] + + delta_v[2] * delta_v[2]; + + const float v2_pec = v2 * cosmo->a2_inv; + + /* (Square of) Max repositioning distance allowed based on the softening */ + const float max_dist_repos2 = + kernel_gravity_softening_plummer_equivalent_inv * + kernel_gravity_softening_plummer_equivalent_inv * + bh_props->max_reposition_distance_ratio * + bh_props->max_reposition_distance_ratio * bi->h * bi->h; + + /* Is this BH neighbour close enough that we can consider its potential + for repositioning? */ + if (r2 < max_dist_repos2) { + + /* Flag to check whether neighbour is slow enough to be considered + * as repositioning target. Always true if velocity cut switched off */ + int neighbour_is_slow_enough = 1; + if (bh_props->with_reposition_velocity_threshold) { + + /* Compute the maximum allowed velocity */ + float v2_max = bh_props->max_reposition_velocity_ratio * + bh_props->max_reposition_velocity_ratio * + bi->sound_speed_gas * bi->sound_speed_gas * + cosmo->a_factor_sound_speed * cosmo->a_factor_sound_speed; + + /* If desired, limit the value of the threshold (v2_max) to be no + * smaller than a user-defined value */ + if (bh_props->min_reposition_velocity_threshold > 0) { + const float v2_min_thresh = + bh_props->min_reposition_velocity_threshold * + bh_props->min_reposition_velocity_threshold; + v2_max = max(v2_max, v2_min_thresh); + } + + /* Is the neighbour too fast to jump to? */ + if (v2_pec >= v2_max) neighbour_is_slow_enough = 0; + } + + if (neighbour_is_slow_enough) { + float potential = bj->reposition.potential; + + if (bh_props->correct_bh_potential_for_repositioning) { + + /* Let's not include the contribution of the BH i + * to the potential of the BH j */ + const float eps = gravity_get_softening(bi->gpart, grav_props); + const float eps2 = eps * eps; + const float eps_inv = 1.f / eps; + const float eps_inv3 = eps_inv * eps_inv * eps_inv; + const float BH_mass = bi->mass; + + /* Compute the Newtonian or truncated potential the BH + * exherts onto the gas particle */ + float dummy, pot_ij; + runner_iact_grav_pp_full(r2, eps2, eps_inv, eps_inv3, BH_mass, &dummy, + &pot_ij); + + /* Deduct the BH contribution */ + potential -= pot_ij * grav_props->G_Newton; + } + + /* Is the potential lower? */ + if (potential < bi->reposition.min_potential) { + + /* Store this as our new best */ + bi->reposition.min_potential = potential; + bi->reposition.delta_x[0] = -dx[0]; + bi->reposition.delta_x[1] = -dx[1]; + bi->reposition.delta_x[2] = -dx[2]; + } + } + } +} + +/** + * @brief Swallowing interaction between two BH particles (non-symmetric). + * + * Function used to flag the BH particles that will be swallowed + * by the black hole particle. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param bi First particle (black hole). + * @param bj Second particle (black hole) + * @param cosmo The cosmological model. + * @param grav_props The properties of the gravity scheme (softening, G, ...). + * @param bh_props The properties of the BH scheme + * @param ti_current Current integer time value (for random numbers). + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_bh_bh_swallow(const float r2, const float dx[3], + const float hi, const float hj, + struct bpart *bi, struct bpart *bj, + const struct cosmology *cosmo, + const struct gravity_props *grav_props, + const struct black_holes_props *bh_props, + const integertime_t ti_current) { + + /* Compute relative peculiar velocity between the two BHs + * Recall that in SWIFT v is (v_pec * a) */ + const float delta_v[3] = {bi->v[0] - bj->v[0], bi->v[1] - bj->v[1], + bi->v[2] - bj->v[2]}; + const float v2 = delta_v[0] * delta_v[0] + delta_v[1] * delta_v[1] + + delta_v[2] * delta_v[2]; + + const float v2_pec = v2 * cosmo->a2_inv; + + /* Find the most massive of the two BHs */ + float M = bi->subgrid_mass; + float h = hi; + if (bj->subgrid_mass > M) { + M = bj->subgrid_mass; + h = hj; + } + + /* (Square of) max swallowing distance allowed based on the softening */ + const float max_dist_merge2 = + kernel_gravity_softening_plummer_equivalent_inv * + kernel_gravity_softening_plummer_equivalent_inv * + bh_props->max_merging_distance_ratio * + bh_props->max_merging_distance_ratio * bi->h * bi->h; + + const float G_Newton = grav_props->G_Newton; + + /* The BH with the smaller mass will be merged onto the one with the + * larger mass. + * To avoid rounding issues, we additionally check for IDs if the BHs + * have the exact same mass. */ + if ((bj->subgrid_mass < bi->subgrid_mass) || + (bj->subgrid_mass == bi->subgrid_mass && bj->id < bi->id)) { + + /* Merge if gravitationally bound AND if within max distance + * Note that we use the kernel support here as the size and not just the + * smoothing length */ + + /* Maximum velocity difference between BHs allowed to merge */ + float v2_threshold; + + if (bh_props->merger_threshold_type == BH_mergers_circular_velocity) { + + /* 'Old-style' merger threshold using circular velocity at the + * edge of the more massive BH's kernel */ + v2_threshold = G_Newton * M / (kernel_gamma * h); + } else { + + /* Arguably better merger threshold using the escape velocity at + * the distance between the BHs */ + + if (bh_props->merger_threshold_type == BH_mergers_escape_velocity) { + + /* Standard formula (not softening BH interactions) */ + v2_threshold = 2.f * G_Newton * M / sqrt(r2); + } else if (bh_props->merger_threshold_type == + BH_mergers_dynamical_escape_velocity) { + + /* General two-body escape velocity based on dynamical masses */ + v2_threshold = 2.f * G_Newton * (bi->mass + bj->mass) / sqrt(r2); + } else { + /* Cannot happen! */ +#ifdef SWIFT_DEBUG_CHECKS + error("Invalid choice of BH merger threshold type"); +#endif + v2_threshold = 0.f; + } + } /* Ends sections for different merger thresholds */ + + if ((v2_pec < v2_threshold) && (r2 < max_dist_merge2)) { + + /* This particle is swallowed by the BH with the largest mass of all the + * candidates wanting to swallow it (we use IDs to break ties)*/ + if ((bj->merger_data.swallow_mass < bi->subgrid_mass) || + (bj->merger_data.swallow_mass == bi->subgrid_mass && + bj->merger_data.swallow_id < bi->id)) { + +#ifdef OBSIDIAN_DEBUG_CHECKS + //if (bi->mass * bh_props->mass_to_solar_mass > 1.e9) message("BH_MERGER: z=%g bid=%lld to swallow bid=%lld: MBHi=%g MBHj=%g Mgali=%g Mgalj=%g", cosmo->z, bi->id, bj->id, bi->subgrid_mass * bh_props->mass_to_solar_mass, bj->subgrid_mass * bh_props->mass_to_solar_mass, bi->galaxy_data.stellar_mass * bh_props->mass_to_solar_mass, bj->galaxy_data.stellar_mass * bh_props->mass_to_solar_mass); +#endif + + bj->merger_data.swallow_id = bi->id; + bj->merger_data.swallow_mass = bi->subgrid_mass; + + } else { + + message( + "BH %lld wants to swallow bh particle %lld BUT CANNOT (old " + "swallow id=%lld)", + bi->id, bj->id, bj->merger_data.swallow_id); + } + } + } +} + +/** + * @brief Feedback interaction between two particles (non-symmetric). + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param bi First particle (black hole). + * @param pj Second particle (gas) + * @param xpj The extended data of the second particle. + * @param with_cosmology Are we doing a cosmological run? + * @param cosmo The cosmological model. + * @param grav_props The properties of the gravity scheme (softening, G, ...). + * @param bh_props The properties of the BH scheme + * @param ti_current Current integer time value (for random numbers). + * @param time current physical time in the simulation + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_bh_gas_feedback( + const float r2, const float dx[3], const float hi, const float hj, + const struct bpart *bi, struct part *pj, struct xpart *xpj, + const int with_cosmology, const struct cosmology *cosmo, + const struct gravity_props *grav_props, + const struct black_holes_props *bh_props, + const struct entropy_floor_properties *floor_props, + const integertime_t ti_current, const double time) { + + /* Gas particle must be bound to BH kernel mass to have feedback + * (avoids fast-moving decoupled particles) */ + const float r = sqrtf(r2); + const float dv[3] = {bi->v[0] - pj->v[0], bi->v[1] - pj->v[1], + bi->v[2] - pj->v[2]}; + const float u_kinetic = 0.5 * (dv[0] * dv[0] + dv[1] * dv[1] + dv[2] * dv[2]); + const float u_potential = + bh_props->const_newton_G * (bi->ngb_mass + bi->mass) / r; + + if (u_kinetic > u_potential) return; + + /* Collect information about galaxy that the particle belongs to */ + const float galaxy_mstar = bi->galaxy_data.stellar_mass; + + /* A black hole should never accrete/feedback if it is not in a galaxy */ + if (galaxy_mstar <= 0.f) return; + + /* A black hole should have gas surrounding it. */ + if (bi->ngb_mass <= 0.f) return; + + /* Need time-step for decoupling and ADAF heating */ + double dt; + if (with_cosmology) { + const integertime_t ti_step = get_integer_timestep(bi->time_bin); + const integertime_t ti_begin = + get_integer_time_begin(ti_current - 1, bi->time_bin); + + dt = cosmology_get_delta_time(cosmo, ti_begin, ti_begin + ti_step); + } else { + error("Kiara BH model can only be run with cosmology."); + dt = 0.; + } + + /* Save gas density and entropy before feedback */ + tracers_before_black_holes_feedback(pj, xpj, cosmo->a); + + float v_kick = bi->v_kick; /* PHYSICAL */ + + const float bh_mass_msun = bi->subgrid_mass * bh_props->mass_to_solar_mass; + + /* by-eye fit from Fig 6 of Zhang+2023 (2305.06803) */ + double halo_mass = 1.e12 * pow(bh_mass_msun * 1.e-7, 0.75); + if (halo_mass < 6.3e11) halo_mass = 6.3e11; + + /* In internal temperature units */ + const double T_vir = 9.52e7 * pow(halo_mass * 1.e-15, 0.6666) * + bh_props->T_K_to_int * (1. + cosmo->z); + + /* In the swallow loop the particle was marked as a kick particle */ + const int swallow_flag = (pj->black_holes_data.swallow_id == bi->id); + + /* Can get reset if there is a adaf_kick_factor > 0 and heating */ + int adaf_kick_flag = 0; + + /* Initialize heating */ + const double u_init = hydro_get_physical_internal_energy(pj, xpj, cosmo); + double E_heat = 0.; + double E_inject = 0.; + double u_new = u_init; + double T_new = u_new / bh_props->temp_to_u_factor; + + int adaf_energy_flag = + (bi->state == BH_states_adaf && bi->adaf_energy_to_dump > 0.f); + int adaf_heat_flag = + (bi->state == BH_states_adaf && pj->black_holes_data.adaf_id == bi->id); + + /* In the case of non-zero mass loading, require only certain particles + * to be heated to satisfy M_dot,ADAF = psi_ADAF * M_dot,acc */ + if (bh_props->adaf_wind_mass_loading > 0.f) { + adaf_heat_flag = (adaf_energy_flag && adaf_heat_flag); + } else { + /* In this case, all of the kernel is heated with adaf_energy_to_dump */ + adaf_heat_flag = adaf_energy_flag; + } + + /* In the swallow loop the particle was marked as a jet particle */ + int jet_flag = (pj->black_holes_data.jet_id == bi->id); + + /* Compute ramp-up in energy above ADAF mass limit */ + float jet_ramp = black_hole_compute_jet_energy_ramp(bi, cosmo, bh_props); + + /* ADAF heating: Only heat this particle if it is NOT a jet particle */ + if (adaf_heat_flag && !jet_flag) { + + /* compute kernel weights */ + float wj; + kernel_eval(sqrtf(r2) / hi, &wj); + const float mj = hydro_get_mass(pj); + + /* Below is equivalent to + * E_inject_i = E_ADAF * (w_j * m_j) / Sum(w_i * mi) */ + E_inject = bi->adaf_energy_to_dump * mj * wj / bi->adaf_wt_sum; + + /* Initialise heat energy to injection energy */ + E_heat = E_inject; + + /* Heat and/or kick the particle */ + if (E_inject > 0.f) { + + const double n_H_cgs = + hydro_get_physical_density(pj, cosmo) * bh_props->rho_to_n_cgs; + const double T_gas_cgs = + u_init / (bh_props->temp_to_u_factor * bh_props->T_K_to_int); + const double T_EoS_cgs = + entropy_floor_temperature(pj, cosmo, floor_props) / + bh_props->T_K_to_int; + + /* Check whether we are close to the entropy floor or SF/ing. If we are, + * we classify the gas as cold regardless of temperature. */ + if ((n_H_cgs > bh_props->adaf_heating_n_H_threshold_cgs && + (T_gas_cgs < bh_props->adaf_heating_T_threshold_cgs || + T_gas_cgs < T_EoS_cgs * bh_props->fixed_T_above_EoS_factor)) || + pj->sf_data.SFR > 0.f) { + + /* Kick with some fraction of the energy, if desired */ + if (bh_props->adaf_kick_factor > 0.f) { + + /* Compute kick velocity */ + double E_kick = bh_props->adaf_kick_factor * E_inject; + v_kick = sqrt(2. * E_kick / mj); + + /* Apply ramp-up in kick velocity above ADAF mass limit */ + const float adaf_max_speed = + bh_props->adaf_wind_speed * sqrtf(jet_ramp); + + /* Limit kick energy if velocity exceeds max */ + if (v_kick > adaf_max_speed) { + v_kick = adaf_max_speed; + E_kick = 0.5 * mj * v_kick * v_kick; + } + + /* Reduce energy available to heat */ + E_heat = E_inject - E_kick; + + /* Later will apply velocity as if it was flagged to swallow */ + adaf_kick_flag = 1; + + } /* adaf_kick_factor > 0 */ + + } /* If in ISM */ + + /* Heat gas with remaining energy, if any */ + if (E_heat > 0.) { + + /* Compute new energy per unit mass of this particle */ + u_new = u_init + E_heat / mj; + + /* New temperature */ + T_new = u_new / bh_props->temp_to_u_factor; + + /* Limit heating. There can sometimes be VERY large amounts of + * energy to deposit */ + if (bh_props->adaf_maximum_temperature > 0.f) { + if (T_new > bh_props->adaf_maximum_temperature) { + u_new = + bh_props->adaf_maximum_temperature * bh_props->temp_to_u_factor; + } + } else { + const float T_max = fabs(bh_props->adaf_maximum_temperature) * T_vir; + if (T_new > T_max) { + u_new = T_max * bh_props->temp_to_u_factor; + T_new = T_max; + } + } + + /* Reset in case clipped at the upper limit */ + E_heat = (u_new - u_init) * mj; + + /* Heat particle: We are overwriting the internal energy of the + * particle */ + hydro_set_physical_internal_energy(pj, xpj, cosmo, u_new); + hydro_set_drifted_physical_internal_energy(pj, cosmo, NULL, u_new); + +#if COOLING_GRACKLE_MODE >= 2 + /* Take AGN-heated gas out of subgrid ISM mode */ + pj->cooling_data.subgrid_temp = 0.f; + pj->cooling_data.subgrid_dens = hydro_get_physical_density(pj, cosmo); + pj->cooling_data.subgrid_fcold = 0.f; +#endif + + /* Shut off cooling for some time, if desired */ + if (bh_props->adaf_cooling_shutoff_factor > 0.f) { + + /* u_init is physical so cs_physical is physical */ + const double u_com = u_new / cosmo->a_factor_internal_energy; + const double cs = gas_soundspeed_from_internal_energy(pj->rho, u_com); + + const float h_phys = kernel_gamma * pj->h * cosmo->a; + const float cs_physical = cs * cosmo->a_factor_sound_speed; + const float dt_sound_phys = h_phys / cs_physical; + + /* a_factor_sound_speed converts cs_physical to comoving units, + * twice the BH timestep as a lower limit */ + pj->feedback_data.cooling_shutoff_delay_time = + bh_props->adaf_cooling_shutoff_factor * min(dt_sound_phys, dt); + } + + } /* E_heat > 0 */ + + } /* E_inject > 0 */ + + } /* non-jet ADAF mode */ + + /* ----- If particle is marked as a jet, do jet feedback ----- */ + + /* Heat the particle and set kinetic kick information if jet particle */ + if (jet_flag) { + + /* Set jet velocity, accounting for energy ramp-up */ + v_kick = black_hole_compute_jet_velocity(bi, cosmo, bh_props); + v_kick *= sqrtf(jet_ramp); + + /* Heat jet particle */ + float new_Tj = bh_props->jet_temperature; + + /* Use the halo T_vir? */ + if (bh_props->jet_temperature < 0.f) { + new_Tj = fabs(bh_props->jet_temperature) * T_vir; + } + + /* Compute new energy per unit mass of this particle */ + u_new = new_Tj * bh_props->temp_to_u_factor; + + /* Only increase the gas temperature if it's below the target T */ + if (u_new > u_init) { + /* account for energy ramp-up */ + u_new = jet_ramp * (u_new - u_init) + u_init; + /* We are overwriting the internal energy of the particle */ + hydro_set_physical_internal_energy(pj, xpj, cosmo, u_new); + hydro_set_drifted_physical_internal_energy(pj, cosmo, NULL, u_new); + + const double delta_energy = (u_new - u_init) * hydro_get_mass(pj); + E_heat += delta_energy; + + tracers_after_black_holes_feedback(pj, xpj, with_cosmology, cosmo->a, + time, delta_energy); + } + + } /* jet_flag */ + +#ifdef OBSIDIAN_DEBUG_CHECKS + float pj_vel_norm = FLT_MAX; +#endif + + /* Flagged if it is a jet particle, marked to swallow (i.e. kick) or + * if there was an ADAF kick because of energy splitting. */ + int flagged_to_kick = + jet_flag || (swallow_flag && !adaf_heat_flag) || adaf_kick_flag; + + /* Kick the particle if is was tagged only */ + if (v_kick > 0.f && flagged_to_kick) { + + /* Set direction of launch: 0=random, 1=L_gas, 2=L_BH, 3=outwards */ + float dir[3] = {0.f, 0.f, 0.f}; + int dir_flag = 0; + if (jet_flag) { + dir_flag = bh_props->jet_launch_dir; + } else if (adaf_heat_flag) { + dir_flag = bh_props->adaf_wind_dir; + } else if (bi->state == BH_states_quasar) { + dir_flag = bh_props->quasar_wind_dir; + if (bi->radiative_luminosity > bh_props->quasar_luminosity_thresh && + bh_props->quasar_luminosity_thresh > 0.f) { + dir_flag = 3; // outwards blowout above threshold luminosity + } + } else if (bi->state == BH_states_slim_disk) { + dir_flag = bh_props->slim_disk_wind_dir; + } else { + warning( + "Cannot determine wind direction (BH state=%d) for v_kick=%g, " + "setting to random", + bi->state, v_kick / bh_props->kms_to_internal); + } + + float dirsign = + black_hole_set_kick_direction(bi, pj, ti_current, dir_flag, dir); + + /* Do the kick */ + const float norm = + sqrtf(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]); + + if (norm > 0.f) { + const float prefactor = v_kick * cosmo->a * dirsign / norm; + +#ifdef OBSIDIAN_DEBUG_CHECKS + pj_vel_norm = sqrtf(xpj->v_full[0] * xpj->v_full[0] + + xpj->v_full[1] * xpj->v_full[1] + + xpj->v_full[2] * xpj->v_full[2]); +#endif + + xpj->v_full[0] += prefactor * dir[0]; + xpj->v_full[1] += prefactor * dir[1]; + xpj->v_full[2] += prefactor * dir[2]; + +#ifdef OBSIDIAN_DEBUG_CHECKS + const float v_mag = sqrtf(xpj->v_full[0] * xpj->v_full[0] + + xpj->v_full[1] * xpj->v_full[1] + + xpj->v_full[2] * xpj->v_full[2]); + + if (prefactor * norm > 1.e3 * v_mag) { + warning( + "LARGE KICK! z=%g id=%lld dv=%g vkick=%g vadaf=%g vjet=%g v=%g " + "(%g,%g,%g) dir=%g,%g,%g", + cosmo->z, pj->id, prefactor, v_kick, bh_props->adaf_wind_speed, + bh_props->jet_velocity, v_mag, xpj->v_full[0], xpj->v_full[1], + xpj->v_full[2], dir[0], dir[1], dir[2]); + } +#endif + + /* Update the signal velocity of the particle based + * on the PHYSICAL velocity kick. */ + hydro_set_v_sig_based_on_velocity_kick(pj, cosmo, v_kick); + pj->chemistry_data.diffusion_coefficient = 0.f; + + float f_decouple = 0.f; + switch (bi->state) { + case BH_states_adaf: + f_decouple = bh_props->adaf_decouple_time_factor; + break; + case BH_states_quasar: + f_decouple = bh_props->quasar_decouple_time_factor; + break; + case BH_states_slim_disk: + f_decouple = bh_props->slim_disk_decouple_time_factor; + break; + } + + /* Hubble time in internal units */ + const double t_H = cosmology_get_time_since_big_bang(cosmo, cosmo->a); + + /* Set delay time to at least the time-step*/ + pj->feedback_data.decoupling_delay_time = dt + f_decouple * t_H; + pj->decoupled = 1; + + /* Count number of decouplings */ + if (jet_flag) { + if (bh_props->jet_decouple_time_factor > 0.f) { + pj->feedback_data.decoupling_delay_time = + dt + bh_props->jet_decouple_time_factor * t_H; + } else { + pj->feedback_data.decoupling_delay_time = 0.f; + } + pj->feedback_data.number_of_times_decoupled += 100000; + } else { + if ((bh_props->slim_disk_decouple_time_factor > 0.f && + bi->state == BH_states_slim_disk) || + (bh_props->quasar_decouple_time_factor > 0.f && + bi->state == BH_states_quasar)) { + pj->feedback_data.decoupling_delay_time = + dt + bh_props->slim_disk_decouple_time_factor * t_H; + } else { + pj->feedback_data.decoupling_delay_time = 0.f; + } + pj->feedback_data.number_of_times_decoupled += 1000; + } + } else { + v_kick = 0.f; + } + } + + /* This particle was touched by BH feedback, so reset some variables */ + if ((v_kick > 0.f && flagged_to_kick) || E_heat > 0.f) { + /* set SFR=0 for BH feedback particle */ + if (pj->sf_data.SFR > 0.f) { + /* Record the current time as an indicator of when this particle was last + star-forming. */ + if (with_cosmology) { + pj->sf_data.SFR = -cosmo->a; + } else { + pj->sf_data.SFR = -time; + } + } + + /* Destroy all H2 and put into HI */ + xpj->cooling_data.HI_frac += xpj->cooling_data.HM_frac + + xpj->cooling_data.H2I_frac + + xpj->cooling_data.H2II_frac; + xpj->cooling_data.HM_frac = 0.f; + xpj->cooling_data.H2I_frac = 0.f; + xpj->cooling_data.H2II_frac = 0.f; + + /* Only take it out of ISM mode if it was kicked */ + if (v_kick > 0.f && flagged_to_kick) { + /* Take particle out of subgrid ISM mode */ + pj->cooling_data.subgrid_temp = 0.f; + pj->cooling_data.subgrid_dens = hydro_get_physical_density(pj, cosmo); + pj->cooling_data.subgrid_fcold = 0.f; + } + + /* Destroy all dust in ADAF-"touched" gas and the jet */ + if (jet_flag || E_inject > 0.) { +#if COOLING_GRACKLE_MODE >= 2 + const float old_dust_mass = pj->cooling_data.dust_mass; + pj->cooling_data.dust_mass = 0.f; + float new_Z_total = 0.f; + pj->chemistry_data.metal_mass_fraction_total = 0.f; + for (int elem = chemistry_element_He; elem < chemistry_element_count; + ++elem) { + const float old_metal_mass_elem = + pj->chemistry_data.metal_mass_fraction[elem] * hydro_get_mass(pj); + const float old_dust_mass_elem = + pj->cooling_data.dust_mass_fraction[elem] * old_dust_mass; + + pj->chemistry_data.metal_mass_fraction[elem] = + (old_metal_mass_elem + old_dust_mass_elem) / hydro_get_mass(pj); + + if (elem != chemistry_element_H && elem != chemistry_element_He) { + new_Z_total += pj->chemistry_data.metal_mass_fraction[elem]; + } + + pj->cooling_data.dust_mass_fraction[elem] = 0.f; + } + + pj->chemistry_data.metal_mass_fraction_total = new_Z_total; +#endif + } + + /* Impose maximal viscosity */ + hydro_diffusive_feedback_reset(pj); + + /* Synchronize the particle on the timeline */ + timestep_sync_part(pj); + +#ifdef OBSIDIAN_DEBUG_CHECKS + if (E_heat > 0.f) { + message( + "BH_HEAT_ADAF: z=%g bid=%lld pid=%lld mbh=%g Msun u=%g T=%g K " + "Tvir=%g K", + cosmo->z, bi->id, pj->id, bh_mass_msun, pj->u, + T_new / bh_props->T_K_to_int, T_vir / bh_props->T_K_to_int); + } + + switch (bi->state) { + case BH_states_quasar: + message( + "BH_KICK_QSO: z=%g bid=%lld mbh=%g Msun v_kick=%g km/s " + "v_kick/v_part=%g T=%g K", + cosmo->z, bi->id, bh_mass_msun, v_kick / bh_props->kms_to_internal, + v_kick * cosmo->a / pj_vel_norm, + hydro_get_physical_internal_energy(pj, xpj, cosmo) / + (bh_props->T_K_to_int * bh_props->temp_to_u_factor)); + break; + case BH_states_slim_disk: + message("BH_KICK_SLIM: z=%g bid=%lld mbh=%g Msun v_kick=%g km/s T=%g K", + cosmo->z, bi->id, bh_mass_msun, + v_kick / bh_props->kms_to_internal, + hydro_get_physical_internal_energy(pj, xpj, cosmo) / + (bh_props->T_K_to_int * bh_props->temp_to_u_factor)); + break; + case BH_states_adaf: + if (jet_flag) { + message( + "BH_KICK_JET: z=%g bid=%lld mbh=%g Msun v_kick=%g km/s " + "v_kick/v_part=%g T=%g", + cosmo->z, bi->id, bh_mass_msun, + v_kick / bh_props->kms_to_internal, + v_kick * cosmo->a / pj_vel_norm, + hydro_get_physical_internal_energy(pj, xpj, cosmo) / + (bh_props->T_K_to_int * bh_props->temp_to_u_factor)); + } else { + message( + "BH_KICK_ADAF: z=%g bid=%lld pid=%lld mbh=%g Msun " + "v_kick=%g km/s " + "v_kick/v_part=%g u=%g T=%g", + cosmo->z, bi->id, pj->id, bh_mass_msun, + v_kick / bh_props->kms_to_internal, + v_kick * cosmo->a / pj_vel_norm, pj->u, + hydro_get_physical_internal_energy(pj, xpj, cosmo) / + (bh_props->T_K_to_int * bh_props->temp_to_u_factor)); + } + break; + } +#endif + } + + if (swallow_flag) { + /* IMPORTANT: The particle MUST NOT be swallowed. + * We are taking a f_accretion from each particle, and then + * kicking the rest. We used the swallow marker as a temporary + * passer in order to remember which particles have been "nibbled" + * so that we can kick them out. + */ + black_holes_mark_part_as_not_swallowed(&pj->black_holes_data); + } +} + +#endif /* SWIFT_OBSIDIAN_BH_IACT_H */ diff --git a/src/black_holes/Obsidian/black_holes_io.h b/src/black_holes/Obsidian/black_holes_io.h new file mode 100644 index 0000000000..73e7aed459 --- /dev/null +++ b/src/black_holes/Obsidian/black_holes_io.h @@ -0,0 +1,458 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_OBSIDIAN_BLACK_HOLES_IO_H +#define SWIFT_OBSIDIAN_BLACK_HOLES_IO_H + +#include "adiabatic_index.h" +#include "black_holes_part.h" +#include "black_holes_properties.h" +#include "io_properties.h" +#include "kick.h" + +/** + * @brief Specifies which b-particle fields to read from a dataset + * + * @param bparts The b-particle array. + * @param list The list of i/o properties to read. + * @param num_fields The number of i/o fields to read. + */ +INLINE static void black_holes_read_particles(struct bpart *bparts, + struct io_props *list, + int *num_fields) { + + int num = 0; + + /* List what we want to read */ + list[num] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY, + UNIT_CONV_LENGTH, bparts, x); + num++; + + list[num] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY, + UNIT_CONV_SPEED, bparts, v); + num++; + + list[num] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, + UNIT_CONV_MASS, bparts, mass); + num++; + + list[num] = io_make_input_field("ParticleIDs", LONGLONG, 1, COMPULSORY, + UNIT_CONV_NO_UNITS, bparts, id); + num++; + + list[num] = io_make_input_field("SmoothingLength", FLOAT, 1, OPTIONAL, + UNIT_CONV_LENGTH, bparts, h); + num++; + + list[num] = io_make_input_field("JetMassReservoirs", FLOAT, 1, OPTIONAL, + UNIT_CONV_MASS, bparts, jet_mass_reservoir); + num++; + + list[num] = io_make_input_field("SubgridMasses", FLOAT, 1, OPTIONAL, + UNIT_CONV_MASS, bparts, subgrid_mass); + num++; + + *num_fields = num; +} + +INLINE static void convert_bpart_pos(const struct engine *e, + const struct bpart *bp, double *ret) { + + const struct space *s = e->s; + if (s->periodic) { + ret[0] = box_wrap(bp->x[0], 0.0, s->dim[0]); + ret[1] = box_wrap(bp->x[1], 0.0, s->dim[1]); + ret[2] = box_wrap(bp->x[2], 0.0, s->dim[2]); + } else { + ret[0] = bp->x[0]; + ret[1] = bp->x[1]; + ret[2] = bp->x[2]; + } + if (e->snapshot_use_delta_from_edge) { + ret[0] = min(ret[0], s->dim[0] - e->snapshot_delta_from_edge); + ret[1] = min(ret[1], s->dim[1] - e->snapshot_delta_from_edge); + ret[2] = min(ret[2], s->dim[2] - e->snapshot_delta_from_edge); + } +} + +INLINE static void convert_bpart_vel(const struct engine *e, + const struct bpart *bp, float *ret) { + + const int with_cosmology = (e->policy & engine_policy_cosmology); + const struct cosmology *cosmo = e->cosmology; + const integertime_t ti_current = e->ti_current; + const double time_base = e->time_base; + const float dt_kick_grav_mesh = e->dt_kick_grav_mesh_for_io; + + const integertime_t ti_beg = get_integer_time_begin(ti_current, bp->time_bin); + const integertime_t ti_end = get_integer_time_end(ti_current, bp->time_bin); + + /* Get time-step since the last kick */ + const float dt_kick_grav = + kick_get_grav_kick_dt(ti_beg, ti_current, time_base, with_cosmology, + cosmo) - + kick_get_grav_kick_dt(ti_beg, (ti_beg + ti_end) / 2, time_base, + with_cosmology, cosmo); + + /* Extrapolate the velocites to the current time */ + const struct gpart *gp = bp->gpart; + ret[0] = gp->v_full[0] + gp->a_grav[0] * dt_kick_grav; + ret[1] = gp->v_full[1] + gp->a_grav[1] * dt_kick_grav; + ret[2] = gp->v_full[2] + gp->a_grav[2] * dt_kick_grav; + + /* Extrapolate the velocites to the current time (mesh forces) */ + ret[0] += gp->a_grav_mesh[0] * dt_kick_grav_mesh; + ret[1] += gp->a_grav_mesh[1] * dt_kick_grav_mesh; + ret[2] += gp->a_grav_mesh[2] * dt_kick_grav_mesh; + + /* Conversion from internal to physical units */ + ret[0] *= cosmo->a_inv; + ret[1] *= cosmo->a_inv; + ret[2] *= cosmo->a_inv; +} + +INLINE static void convert_bpart_potential(const struct engine *e, + const struct bpart *bp, float *ret) { + + if (bp->gpart != NULL) + ret[0] = gravity_get_comoving_potential(bp->gpart); + else + ret[0] = 0.f; +} + +INLINE static void convert_bpart_gas_vel(const struct engine *e, + const struct bpart *bp, float *ret) { + + const struct cosmology *cosmo = e->cosmology; + + /* Convert relative velocities to physical units */ + ret[0] = bp->velocity_gas[0] * cosmo->a_inv; + ret[1] = bp->velocity_gas[1] * cosmo->a_inv; + ret[2] = bp->velocity_gas[2] * cosmo->a_inv; +} + +INLINE static void convert_bpart_gas_circular_vel(const struct engine *e, + const struct bpart *bp, + float *ret) { + + const struct cosmology *cosmo = e->cosmology; + + /* Conversion from internal to physical units */ + ret[0] = bp->circular_velocity_gas[0] * cosmo->a_inv; + ret[1] = bp->circular_velocity_gas[1] * cosmo->a_inv; + ret[2] = bp->circular_velocity_gas[2] * cosmo->a_inv; +} + +INLINE static void convert_bpart_gas_temperatures(const struct engine *e, + const struct bpart *bp, + float *ret) { + + const struct black_holes_props *props = e->black_holes_properties; + const struct cosmology *cosmo = e->cosmology; + + /* Conversion from specific internal energy to temperature */ + ret[0] = bp->internal_energy_gas * cosmo->a_factor_internal_energy / + props->temp_to_u_factor; +} + +/** + * @brief Specifies which b-particle fields to write to a dataset + * + * @param bparts The b-particle array. + * @param list The list of i/o properties to write. + * @param num_fields The number of i/o fields to write. + * @param with_cosmology Are we running a cosmological simulation? + */ +INLINE static void black_holes_write_particles(const struct bpart *bparts, + struct io_props *list, + int *num_fields, + int with_cosmology) { + + int num = 0; + + /* List what we want to write */ + list[num] = io_make_output_field_convert_bpart( + "Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH, 1.f, bparts, + convert_bpart_pos, "Co-moving position of the particles"); + num++; + + list[num] = io_make_output_field_convert_bpart( + "Velocities", FLOAT, 3, UNIT_CONV_SPEED, 0.f, bparts, convert_bpart_vel, + "Peculiar velocities of the particles. This is a * dx/dt where x is the " + "co-moving position of the particles."); + num++; + + list[num] = + io_make_output_field("DynamicalMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, + bparts, mass, "Dynamical masses of the particles"); + num++; + + list[num] = + io_make_output_field("ParticleIDs", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0.f, + bparts, id, "Unique ID of the particles"); + num++; + + list[num] = io_make_output_field( + "SmoothingLengths", FLOAT, 1, UNIT_CONV_LENGTH, 1.f, bparts, h, + "Co-moving smoothing lengths (FWHM of the kernel) of the particles"); + num++; + + list[num] = io_make_output_field( + "SubgridMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, bparts, subgrid_mass, + "Subgrid masses of the particles; this is the actual BH mass"); + num++; + + if (with_cosmology) { + list[num] = io_make_output_field( + "FormationScaleFactors", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + formation_scale_factor, "Scale-factors at which the BHs were formed"); + } else { + list[num] = io_make_output_field("FormationTimes", FLOAT, 1, UNIT_CONV_TIME, + 0.f, bparts, formation_time, + "Times at which the BHs were formed"); + } + num++; + + list[num] = io_make_output_field( + "GasDensities", FLOAT, 1, UNIT_CONV_DENSITY, -3.f, bparts, rho_gas, + "Co-moving densities of the gas around the particles"); + num++; + + list[num] = io_make_output_field( + "GasSoundSpeeds", FLOAT, 1, UNIT_CONV_SPEED, + -1.5f * hydro_gamma_minus_one, bparts, sound_speed_gas, + "Co-moving sound-speeds of the gas around the particles"); + num++; + + list[num] = io_make_output_field( + "JetMassReservoirs", FLOAT, 1, UNIT_CONV_MASS, 0.f, bparts, + jet_mass_reservoir, + "Physcial mass contained in the jet reservoir of the particles"); + num++; + + list[num] = io_make_output_field( + "UnresolvedMassReservoirs", FLOAT, 1, UNIT_CONV_MASS, 0.f, bparts, + unresolved_mass_reservoir, + "Physcial mass contained in the unresolved reservoir of the particles"); + num++; + + list[num] = io_make_output_field( + "AccretionRates", FLOAT, 1, UNIT_CONV_MASS_PER_UNIT_TIME, 0.f, bparts, + accretion_rate, + "Physical instantaneous accretion rates of the particles"); + num++; + + list[num] = io_make_output_field( + "BondiAccretionRates", FLOAT, 1, UNIT_CONV_MASS_PER_UNIT_TIME, 0.f, + bparts, bondi_accretion_rate, + "Physical instantaneous Bondi accretion rates of the particles"); + num++; + + list[num] = io_make_output_field( + "TotalAccretedMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, bparts, + total_accreted_mass, + "Total mass accreted onto the particles since its birth"); + num++; + + list[num] = io_make_output_field( + "CumulativeNumberOfSeeds", INT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + cumulative_number_seeds, + "Total number of BH seeds that have merged into this black hole"); + num++; + + list[num] = + io_make_output_field("NumberOfMergers", INT, 1, UNIT_CONV_NO_UNITS, 0.f, + bparts, number_of_mergers, + "Number of mergers the black holes went through. " + "This does not include the number of mergers " + "accumulated by any merged black hole."); + num++; + + if (with_cosmology) { + list[num] = io_make_output_field( + "LastMinorMergerScaleFactors", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, + bparts, last_minor_merger_scale_factor, + "Scale-factors at which the black holes last had a minor merger."); + } else { + list[num] = io_make_output_field( + "LastMinorMergerTimes", FLOAT, 1, UNIT_CONV_TIME, 0.f, bparts, + last_minor_merger_time, + "Times at which the black holes last had a minor merger."); + } + num++; + + if (with_cosmology) { + list[num] = io_make_output_field( + "LastMajorMergerScaleFactors", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, + bparts, last_major_merger_scale_factor, + "Scale-factors at which the black holes last had a major merger."); + } else { + list[num] = io_make_output_field( + "LastMajorMergerTimes", FLOAT, 1, UNIT_CONV_TIME, 0.f, bparts, + last_major_merger_time, + "Times at which the black holes last had a major merger."); + } + num++; + + list[num] = io_make_output_field( + "SwallowedAngularMomenta", FLOAT, 3, UNIT_CONV_ANGULAR_MOMENTUM, 0.f, + bparts, swallowed_angular_momentum, + "Physical angular momenta that the black holes have accumulated by " + "swallowing gas particles."); + num++; + + list[num] = io_make_output_field_convert_bpart( + "GasRelativeVelocities", FLOAT, 3, UNIT_CONV_SPEED, 0.f, bparts, + convert_bpart_gas_vel, + "Peculiar relative velocities of the gas particles around the black " + "holes. This is a * dx/dt where x is the co-moving position of the " + "particles."); + num++; + + list[num] = io_make_output_field_convert_bpart( + "GasCircularVelocities", FLOAT, 3, UNIT_CONV_SPEED, 0.f, bparts, + convert_bpart_gas_circular_vel, + "Circular velocities of the gas around the black hole at the " + "smoothing radius. This is j / h_BH, where j is the smoothed, peculiar " + "specific angular momentum of gas around the black holes, and h_BH is " + "the smoothing length of each black hole."); + num++; + + list[num] = + io_make_output_field("TimeBins", CHAR, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + time_bin, "Time-bins of the particles"); + num++; + + list[num] = io_make_output_field( + "NumberOfSwallows", INT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + number_of_gas_swallows, + "Number of gas particles the black holes have swallowed. " + "This includes the particles swallowed by any of the black holes that " + "merged into this one."); + num++; + + list[num] = io_make_output_field( + "NumberOfDirectSwallows", INT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + number_of_direct_gas_swallows, + "Number of gas particles the black holes have swallowed. " + "This does not include any particles swallowed by any of the black holes " + "that merged into this one."); + num++; + + list[num] = io_make_output_field( + "NumberOfRepositions", INT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + number_of_repositions, + "Number of repositioning events the black holes went through. This does " + "not include the number of reposition events accumulated by any merged " + "black holes."); + num++; + + list[num] = io_make_output_field( + "NumberOfRepositionAttempts", INT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + number_of_reposition_attempts, + "Number of time steps in which the black holes had an eligible particle " + "to reposition to. They may or may not have ended up moving there, " + "depending on their subgrid mass and on whether these particles were at " + "a lower or higher potential than the black holes themselves. It does " + "not include attempted repositioning events accumulated by any merged " + "black holes."); + num++; + + list[num] = io_make_output_field( + "NumberOfTimeSteps", INT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + number_of_time_steps, + "Total number of time steps at which the black holes were active."); + num++; + + list[num] = io_make_output_field( + "SubgridSoundSpeeds", FLOAT, 1, UNIT_CONV_SPEED, 0.f, bparts, + sound_speed_subgrid_gas, + "Physical subgrid sound-speeds used in the subgrid-Bondi model."); + num++; + + list[num] = io_make_output_field( + "BirthGasDensities", FLOAT, 1, UNIT_CONV_DENSITY, 0.f, bparts, + formation_gas_density, + "Physical densities of the converted part at the time of birth. " + "We store the physical density at the birth redshift, no conversion is " + "needed."); + num++; + + list[num] = io_make_output_field( + "AccretedAngularMomenta", FLOAT, 3, UNIT_CONV_ANGULAR_MOMENTUM, 0.f, + bparts, accreted_angular_momentum, + "Physical angular momenta that the black holes have accumulated through " + "subgrid accretion."); + num++; + + list[num] = io_make_output_field( + "NumberOfGasNeighbours", INT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + num_ngbs, + "Integer number of gas neighbour particles within the black hole " + "kernels."); + num++; + + list[num] = io_make_output_field( + "LastRepositionVelocities", FLOAT, 1, UNIT_CONV_SPEED, 0.f, bparts, + last_repos_vel, + "Physical speeds at which the black holes repositioned most recently. " + "This is 0 for black holes that have never repositioned, or if the " + "simulation has been run without prescribed repositioning speed."); + num++; + + list[num] = io_make_output_field_convert_bpart( + "GasTemperatures", FLOAT, 1, UNIT_CONV_TEMPERATURE, 0.f, bparts, + convert_bpart_gas_temperatures, + "Temperature of the gas surrounding the black holes."); + num++; + + list[num] = io_make_output_field( + "EddingtonFractions", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, bparts, + eddington_fraction, + "Accretion rates of black holes in units of their Eddington rates. " + "This is based on the unlimited accretion rates, so these fractions " + "can be above the limiting fEdd."); + num++; + + list[num] = io_make_output_field_convert_bpart( + "Potentials", FLOAT, 1, UNIT_CONV_POTENTIAL, -1.f, bparts, + convert_bpart_potential, "Gravitational potentials of the particles"); + num++; + + *num_fields = num; + +#ifdef DEBUG_INTERACTIONS_BLACK_HOLES + + list += *num_fields; + *num_fields += 4; + + list[0] = io_make_output_field("Num_ngb_density", INT, 1, UNIT_CONV_NO_UNITS, + bparts, num_ngb_density); + list[1] = io_make_output_field("Num_ngb_force", INT, 1, UNIT_CONV_NO_UNITS, + bparts, num_ngb_force); + list[2] = io_make_output_field("Ids_ngb_density", LONGLONG, + MAX_NUM_OF_NEIGHBOURS_BLACK_HOLES, + UNIT_CONV_NO_UNITS, bparts, ids_ngbs_density); + list[3] = io_make_output_field("Ids_ngb_force", LONGLONG, + MAX_NUM_OF_NEIGHBOURS_BLACK_HOLES, + UNIT_CONV_NO_UNITS, bparts, ids_ngbs_force); +#endif +} + +#endif /* SWIFT_OBSIDIAN_BLACK_HOLES_IO_H */ diff --git a/src/black_holes/Obsidian/black_holes_parameters.h b/src/black_holes/Obsidian/black_holes_parameters.h new file mode 100644 index 0000000000..d195c1df4c --- /dev/null +++ b/src/black_holes/Obsidian/black_holes_parameters.h @@ -0,0 +1,42 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_OBSIDIAN_BLACK_HOLES_PARAMETERS_H +#define SWIFT_OBSIDIAN_BLACK_HOLES_PARAMETERS_H + +/* Configuration file */ +#include "config.h" + +/** + * @file Obsidian/black_holes_parameters.h + * @brief Parameters of the Obsidian black holes + * model that need to be defined at compile time. + * + * @note In this branch, these properties are not used anywhere! + */ + +/*! Maximal distance for merging particles in units of the (spline not Plummer) + * softening length. */ +#define const_max_merging_distance_ratio 3.f + +/*! Maximal distance for repositioning particles in units of the (spline not + * Plummer) softening length. */ +#define const_max_repositioning_distance_ratio 3.f + +#endif /* SWIFT_OBSIDIAN_BLACK_HOLES_PARAMETERS_H */ diff --git a/src/black_holes/Obsidian/black_holes_part.h b/src/black_holes/Obsidian/black_holes_part.h new file mode 100644 index 0000000000..2941fe888e --- /dev/null +++ b/src/black_holes/Obsidian/black_holes_part.h @@ -0,0 +1,331 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_OBSIDIAN_BLACK_HOLE_PART_H +#define SWIFT_OBSIDIAN_BLACK_HOLE_PART_H + +#include "black_holes_struct.h" +#include "chemistry_struct.h" +#ifdef WITH_FOF_GALAXIES +#include "fof_struct.h" +#endif +#include "particle_splitting_struct.h" +#include "timeline.h" + +/** + * @brief Particle fields for the black hole particles. + * + * All quantities related to gravity are stored in the associate #gpart. + */ +struct bpart { + + /*! Particle ID. */ + long long id; + + /*! Pointer to corresponding gravity part. */ + struct gpart *gpart; + + /*! Particle position. */ + double x[3]; + + /* Offset between current position and position at last tree rebuild. */ + float x_diff[3]; + + /*! Particle velocity. */ + float v[3]; + + /*! Black hole mass */ + float mass; + + /*! Black hole mass at the start of each step, prior to any nibbling */ + float mass_at_start_of_step; + + /* Particle cutoff radius. */ + float h; + + /*! Particle time bin */ + timebin_t time_bin; + + /*! Tree-depth at which size / 2 <= h * gamma < size */ + char depth_h; + + struct { + + /* Number of neighbours. */ + float wcount; + + /* Number of neighbours spatial derivative. */ + float wcount_dh; + + } density; + +#ifdef WITH_FOF_GALAXIES + /*! Struct for host galaxy information */ + struct fof_galaxy_data galaxy_data; + + /*! Distance to host galaxy center of mass */ + float galactocentric_radius; +#endif + + /*! Union for the formation time and formation scale factor */ + union { + + /*! Formation time */ + float formation_time; + + /*! Formation scale factor */ + float formation_scale_factor; + }; + + /*! Physical density of the converted part (internal units) */ + float formation_gas_density; + + /*! Subgrid mass of the black hole */ + float subgrid_mass; + + /*! Total accreted mass of the black hole (not including mass merged in + * from other black holes) */ + float total_accreted_mass; + + /*! Instantaneous accretion rate */ + float accretion_rate; + + /*! Instantaneous Bondi component of accretion rate */ + float bondi_accretion_rate; + + /*! Density of the gas surrounding the black hole. */ + float rho_gas; + + /*! Internal energy of the gas surrounding the black hole. */ + float internal_energy_gas; + + /*! The mass of hot gas surrounding the black hole */ + float hot_gas_mass; + + /*! The mass of cold gas surrounding the black hole */ + float cold_gas_mass; + + /*! The total SFR of cold gas surrounding the black hole */ + float gas_SFR; + + /*! The current state of the black hole */ + int state; + + /*! The radiative efficiency associated with M_dot,bh */ + float radiative_efficiency; + + /*! The large scale accretion rate onto the black hole */ + float m_dot_inflow; + + /*! The amount of jet energy available */ + float jet_mass_reservoir; + + /*! The amount of jet mass kicked this time step */ + float jet_mass_kicked_this_step; + + /*! The mass loading in the jet for the variable velocity scheme */ + float jet_mass_loading; + + /*! The amount of unresolved mass available to kick */ + float unresolved_mass_reservoir; + + /*! The amount of unresolved mass kicked this step */ + float unresolved_mass_kicked_this_step; + + /*! Energy to dump this step via the ADAF hot-wind, kernel-weighted */ + float adaf_energy_to_dump; + + /*! Energy injected this step in the ADAF mode */ + float adaf_energy_used_this_step; + + /*! sum(mi * wi) weights for accretion/feedback */ + float kernel_wt_sum; + + /*! sum(mi * wi) weights for adaf heating */ + float adaf_wt_sum; + + /*! The mass of cold disk around the black hole */ + float cold_disk_mass; + + /*! Mass in accretion disk from which BH accretes */ + float accretion_disk_mass; + + /*! The mass-weighted internal energy surrounding the black hole (unsmoothed) + */ + float hot_gas_internal_energy; + + /*! Smoothed sound speed of the gas surrounding the black hole. */ + float sound_speed_gas; + + /*! Total gravitational gas mass within the kernel */ + float gravitational_ngb_mass; + + /*! Subgrid physical sound speed of the gas (updated when using the subgrid + * Bondi model) */ + float sound_speed_subgrid_gas; + + /*! Smoothed velocity of the gas surrounding the black hole, + * in the frame of the black hole (internal units) */ + float velocity_gas[3]; + + /*! The real angular momentum of the gas in the kernel */ + float angular_momentum_gas[3]; + + /*! Circular velocity of the gas around the black hole at the smoothing + * radius (calculated as j_gas / h_BH, where j is specific ang. mom.) */ + float circular_velocity_gas[3]; + + /*! Total mass of the gas neighbours. */ + float ngb_mass; + + /*! Integer number of neighbours */ + int num_ngbs; + + /*! Integer number of gravitational neighbors */ + int num_gravitational_ngbs; + + /*! Number of seeds in this BH (i.e. itself + the merged ones) */ + int cumulative_number_seeds; + + /*! Total number of BH merger events (i.e. not including all progenies) */ + int number_of_mergers; + + /*! Total number of gas particles swallowed (including particles swallowed + * by merged-in black holes) */ + int number_of_gas_swallows; + + /*! Total number of gas particles swallowed (excluding particles swallowed + * by merged-in black holes) */ + int number_of_direct_gas_swallows; + + /*! Total number of times the black hole has been repositioned (excluding + * repositionings of merged-in black holes) */ + int number_of_repositions; + + /*! Total number of times a black hole attempted repositioning (including + * cases where it was aborted because the black hole was already at a + * lower potential than all eligible neighbours) */ + int number_of_reposition_attempts; + + /* Velocity of most recent reposition jump */ + float last_repos_vel; + + /*! Total number of time steps in which the black hole was active. */ + int number_of_time_steps; + + /*! Total (physical) angular momentum accumulated by swallowing particles */ + float swallowed_angular_momentum[3]; + + /*! Total (physical) angular momentum accumulated from subgrid accretion */ + float accreted_angular_momentum[3]; + + /*! Eddington fractions */ + float eddington_fraction; + + /*! Wind velocity kick */ + float v_kick; + + /*! Fraction of Mdot,inflow that should be accreted, the rest is a wind */ + float f_accretion; + + /*! Bulge mass of stars within the kernel (twice the counter-rotating mass) */ + float stellar_bulge_mass; + + /*! The mass of stars within the kernel */ + float stellar_mass; + + /*! The radiative luminosity of the black hole */ + float radiative_luminosity; + + /*! How much energy has been given away in this timestep? */ + float delta_energy_this_timestep; + + /*! Union for the last minor merger point in time */ + union { + + /*! Last time the BH had a a high Eddington fraction */ + float last_minor_merger_time; + + /*! Last scale factor the BH had a a high Eddington fraction */ + float last_minor_merger_scale_factor; + }; + + /*! Union for the last major merger point in time */ + union { + + /*! Last time the BH had a a high Eddington fraction */ + float last_major_merger_time; + + /*! Last scale factor the BH had a major merger */ + float last_major_merger_scale_factor; + }; + + struct { + + /*! Gravitational potential copied from the #gpart. */ + float potential; + + /*! Value of the minimum potential across all neighbours. */ + float min_potential; + + /*! Delta position to apply after the reposition procedure */ + double delta_x[3]; + + } reposition; + + /*! Splitting structure */ + struct particle_splitting_data split_data; + + /*! Chemistry information (e.g. metal content at birth, swallowed metal + * content, etc.) */ + struct chemistry_bpart_data chemistry_data; + + /*! Black holes merger information (e.g. merging ID) */ + struct black_holes_bpart_data merger_data; + + /*! Tracer structure */ + struct tracers_bpart_data tracers_data; + +#ifdef SWIFT_DEBUG_CHECKS + + /* Time of the last drift */ + integertime_t ti_drift; + + /* Time of the last kick */ + integertime_t ti_kick; + +#endif + +#ifdef DEBUG_INTERACTIONS_BLACK_HOLES + /*! Number of interactions in the density SELF and PAIR */ + int num_ngb_density; + + /*! List of interacting particles in the density SELF and PAIR */ + long long ids_ngbs_density[MAX_NUM_OF_NEIGHBOURS_BLACK_HOLES]; + + /*! Number of interactions in the force SELF and PAIR */ + int num_ngb_force; + + /*! List of interacting particles in the force SELF and PAIR */ + long long ids_ngbs_force[MAX_NUM_OF_NEIGHBOURS_BLACK_HOLES]; +#endif + +} SWIFT_STRUCT_ALIGN; + +#endif /* SWIFT_OBSIDIAN_BLACK_HOLE_PART_H */ diff --git a/src/black_holes/Obsidian/black_holes_properties.h b/src/black_holes/Obsidian/black_holes_properties.h new file mode 100644 index 0000000000..dbf69b2d70 --- /dev/null +++ b/src/black_holes/Obsidian/black_holes_properties.h @@ -0,0 +1,1280 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_OBSIDIAN_BLACK_HOLES_PROPERTIES_H +#define SWIFT_OBSIDIAN_BLACK_HOLES_PROPERTIES_H + +/* Config parameters. */ +#include "../config.h" + +/* Local includes. */ +#include "chemistry.h" +#include "exp10.h" +#include "hydro_properties.h" + +/* Includes. */ +#include + +enum BH_states { + BH_states_adaf = 0, /* < 0.03 Mdot,BH / Mdot,Edd */ + BH_states_quasar, /* 0.03 < Mdot,BH / Mdot,Edd < 0.3 */ + BH_states_slim_disk /* Mdot,BH/Mdot,Edd > 0.3 */ +}; + +enum BH_merger_thresholds { + BH_mergers_circular_velocity, /*< v_circ at separation, as in EAGLE */ + BH_mergers_escape_velocity, /*< v_esc at separation */ + BH_mergers_dynamical_escape_velocity /*< combined v_esc_dyn at separation */ +}; + +enum BH_loading_types { + BH_jet_momentum_loaded, /* Momentum loaded jet, with subgrid energy loading */ + BH_jet_energy_loaded, /* Energy loaded jet, no subgrid */ + BH_jet_mixed_loaded /* A mix between momentum and energy loading */ +}; + +/** + * @brief Properties of black holes and AGN feedback in the EAGEL model. + */ +struct black_holes_props { + + /* ----- Basic neighbour search properties ------ */ + + /*! Resolution parameter */ + float eta_neighbours; + + /*! Target weightd number of neighbours (for info only)*/ + float target_neighbours; + + /*! Smoothing length tolerance */ + float h_tolerance; + + /*! Maximum smoothing length */ + float h_max; + + /*! Minimum smoothing length */ + float h_min; + + /*! Tolerance on neighbour number (for info only)*/ + float delta_neighbours; + + /*! Maximal number of iterations to converge h */ + int max_smoothing_iterations; + + /*! Maximal change of h over one time-step */ + float log_max_h_change; + + /*! Gravitational constant */ + float const_newton_G; + + /* ----- Initialisation properties ------ */ + + /*! Mass of a BH seed at creation time */ + float subgrid_seed_mass; + + /*! Should we use the subgrid mass specified in ICs? */ + int use_subgrid_mass_from_ics; + + /*! Should we enforce positive subgrid masses initially? */ + int with_subgrid_mass_check; + + /* ----- Properties of the accretion model ------ */ + + /*! Radiative efficiency of the black holes. */ + float epsilon_r; + + /*! Maximal fraction of the Eddington rate allowed for total accretion. */ + float f_Edd_maximum; + + /*! Maximal fraction of the Eddington rate allowed for Bondi accretion alone. + */ + float f_Edd_Bondi_maximum; + + /*! Maximum BH mass to use in calculating Bondi rate */ + float bondi_BH_mass_cap; + + /*! Minimum gas particle mass in nibbling mode */ + float min_gas_mass_for_nibbling; + + /*! Switch to calculate the sound speed with a fixed T near the EoS */ + int with_fixed_T_near_EoS; + + /*! Factor above EoS below which fixed T applies for sound speed */ + float fixed_T_above_EoS_factor; + + /*! Where do we distinguish between hot gas for Bondi? */ + float environment_temperature_cut; + + /*! Where do we distinguish between cold gas for torque accretion? */ + float cold_gas_temperature_cut; + + /*! Number of dynamical times over which gas is accreted from accretion disk + */ + float dynamical_time_factor; + + /*! Max dynamical time over which gas is accreted from accretion disk */ + float dynamical_time_max; + + /*! Method to compute torque accretion rate: + * 0=Mgas/tdyn on all gas; 1=Mgas/tdyn on disk gas; 2=Simba-style(HQ11) */ + int torque_accretion_method; + + /*! Normalization of the torque accretion rate */ + float torque_accretion_norm; + + /*! Factor in front of M/(dM/dt) for timestepping */ + float dt_accretion_factor; + + /*! Factor for exponentially limiting black hole growth in early stages. */ + float bh_characteristic_suppression_mass; + + /*! SF efficiency in BH kernel for suppression by winds (<0 means compute on + * the fly). */ + float suppression_sf_eff; + + /*! Gaussian spread in infall times when using SF-based growth suppression. */ + int tdyn_sigma; + + /*! Method to suppress early growth of BH */ + int suppress_growth; + + /*! A from Lupi+17 */ + float A_sd; + + /*! B from Lupi+17 */ + float B_sd; + + /*! C from Lupi+17 */ + float C_sd; + + /*! The spin of EVERY black hole */ + float fixed_spin; + + /*! Method to compute the dynamical time within the kernel */ + int dynamical_time_calculation_method; + + /* ---- Properties of the feedback model ------- */ + + /*! The loading for the jet: momentum or energy */ + enum BH_loading_types jet_loading_type; + + /*! What is the physical max. velocity of the jet? (km/s) */ + float jet_velocity; + + /*! How long to decouple black hole winds? */ + float jet_decouple_time_factor; + + /*! The temperature of the jet. Set < 0.f for halo virial temperature */ + float jet_temperature; + + /*! What lower Mdot,BH/Mdot,Edd boundary does the jet activate? */ + float eddington_fraction_lower_boundary; + + /*! What upper Mdot,BH/Mdot,Edd boundary does the slim disk mode activate? */ + float eddington_fraction_upper_boundary; + + /*! How long to decouple black hole winds? */ + float quasar_decouple_time_factor; + + /*! Constrains momentum of outflowing wind to p = F * L / c */ + float quasar_wind_momentum_flux; + + /*! The mass loading of the quasar outflow */ + float quasar_wind_mass_loading; + + /*! The wind speed of the quasar outflow */ + float quasar_wind_speed; + + /*! Direction of quasar winds: 0=random, 1=L_gas, 2=L_BH, 3=outwards */ + int quasar_wind_dir; + + /*! f_acc for the quasar mode */ + float quasar_f_accretion; + + /*! eps_f for the quasar mode */ + float quasar_coupling; + + /*! luminosity in system units above which to boost quasar eps_f quasar mode + */ + double quasar_luminosity_thresh; + + /*! The disk wind efficiency from Benson & Babul 2009 */ + float adaf_disk_efficiency; + + /*! The wind speed of the ADAF outflow */ + float adaf_wind_speed; + + /*! The mass loading of the ADAF wind */ + float adaf_wind_mass_loading; + + /*! eps_f for the ADAF mode */ + float adaf_coupling; + + /*! Power-law scaling of adaf_coupling with (1+z) */ + float adaf_z_scaling; + + /*! f_acc for the ADAF mode */ + float adaf_f_accretion; + + /*! The maximum temperature to heat a gas particle */ + float adaf_maximum_temperature; + + /*! Above this density we should shut off cooling for heated particles */ + double adaf_heating_n_H_threshold_cgs; + + /*! Below this temperature we should shut off cooling for particles */ + double adaf_heating_T_threshold_cgs; + + /*! Lower mass limit (internal units) for BH to enter ADAF mode */ + float adaf_mass_limit; + + /*! Sets upper mass range (internal units) for BH to enter ADAF mode */ + float adaf_mass_limit_spread; + + /*! Sets power-law of expansion factor dependence of ADAF mass limit */ + float adaf_mass_limit_a_scaling; + + /*! Sets maximum expansion factor for evolving ADAF mass limit */ + float adaf_mass_limit_a_min; + + /*! A multiplicative factor for delaying cooling on a particle */ + float adaf_cooling_shutoff_factor; + + /*! A multiplicative factor 0. < f < 1. to multiply E_inject in the ADAF mode + */ + float adaf_kick_factor; + + /*! Direction of ADAF winds: 0=random, 1=L_gas, 2=L_BH, 3=outwards */ + int adaf_wind_dir; + + /*! How long to decouple black hole winds? */ + float adaf_decouple_time_factor; + + /*! Should we use nibbling */ + int use_nibbling; + + /*! Use all of the gas in the kernel to compute Bondi */ + int bondi_use_all_gas; + + /*! Multiplicative factor in front of Bondi rate */ + float bondi_alpha; + + /*! Minimum BH mass for unresolved feedback (internal units) */ + float minimum_black_hole_mass_unresolved; + + /*! Minimum BH mass for the v_kick formula (internal units) */ + float minimum_black_hole_mass_v_kick; + + /*! Minimum kick velocity for variable v_kick */ + float minimum_v_kick_km_s; + + /*! The phi term for the slim disk mode (Eq. 9 from Rennehan+24) */ + float slim_disk_phi; + + /*! eps_f for the slim disk mode */ + float slim_disk_coupling; + + /*! wind speed in the slim disk mode */ + float slim_disk_wind_speed; + + /*! How long to decouple black hole winds? */ + float slim_disk_decouple_time_factor; + + /*! Direction of slim disk winds: 0=random, 1=L_gas, 2=L_BH, 3=outwards */ + int slim_disk_wind_dir; + + /*! Is the slim disk jet model active? */ + int slim_disk_jet_active; + + /*! The efficiency of the jet */ + float jet_efficiency; + + /*! The fraction of energy loading when using a mixed jet */ + float jet_frac_energy; + + /*! The mass loading in the jet */ + float jet_mass_loading; + + /*! The subgrid jet speed to set the accretion fraction */ + float jet_subgrid_velocity; + + /*! power-law scaling of jet vel with (MBH/1.e8 Mo) */ + float jet_velocity_scaling_with_BH_mass; + + /*! power-law scaling of jet vel with (LBH/1.e45 erg/s) */ + float jet_velocity_scaling_with_BH_lum; + + /*! Direction of jet launch: 0=random, 1=L_gas, 2=L_BH, 3=outwards */ + int jet_launch_dir; + + /*! The minimum mass required before the jet will launch */ + float jet_minimum_reservoir_mass; + + /*! Above this luminosity in erg/s always launch a jet */ + double lum_thresh_always_jet; + + /* ---- Properties of the repositioning model --- */ + + /*! Maximal mass of BH to reposition */ + float max_reposition_mass; + + /*! Maximal distance to reposition, in units of softening length */ + float max_reposition_distance_ratio; + + /*! Switch to enable a relative velocity limit for particles to which the + * black holes can reposition */ + int with_reposition_velocity_threshold; + + /*! Maximal velocity offset of particles to which the black hole can + * reposition, in units of the ambient sound speed of the black hole */ + float max_reposition_velocity_ratio; + + /*! Minimum value of the velocity repositioning threshold */ + float min_reposition_velocity_threshold; + + /*! Switch to enable repositioning at fixed (maximum) speed */ + int set_reposition_speed; + + /*! Normalisation factor for repositioning velocity */ + float reposition_coefficient_upsilon; + + /*! Reference black hole mass for repositioning scaling */ + float reposition_reference_mass; + + /*! Repositioning velocity scaling with black hole mass */ + float reposition_exponent_mass; + + /*! Reference gas density for repositioning scaling */ + float reposition_reference_n_H; + + /*! Repositioning velocity scaling with gas density */ + float reposition_exponent_n_H; + + /*! Correct potential of BH? */ + int correct_bh_potential_for_repositioning; + + /* ---- Properties of the merger model ---------- */ + + /*! Mass ratio above which a merger is considered 'minor' */ + float minor_merger_threshold; + + /*! Mass ratio above which a merger is considered 'major' */ + float major_merger_threshold; + + /*! Type of merger threshold */ + enum BH_merger_thresholds merger_threshold_type; + + /*! Maximal distance over which BHs merge, in units of softening length */ + float max_merging_distance_ratio; + + /* ---- Black hole time-step properties ---------- */ + + /*! Minimum allowed time-step of BH (internal units) */ + float time_step_min; + + /* ---- Common conversion factors --------------- */ + + /*! Conversion factor from temperature to internal energy */ + double temp_to_u_factor; + + /*! Conversion factor from physical density to n_H [cgs] */ + double rho_to_n_cgs; + + /*! Conversion factor from internal mass to solar masses */ + double mass_to_solar_mass; + + /*! Conversion factor from km/s to internal velocity units (without a-factor) + */ + double kms_to_internal; + + /*! Conversion factor from internal length to parsec */ + double length_to_parsec; + + /*! Conversion factor from internal time to yr */ + double time_to_yr; + + /*! Conversion factor from internal time to Myr */ + double time_to_Myr; + + /*! Conversion factor from density to cgs */ + double conv_factor_density_to_cgs; + + /*! Conversion factor from luminosity to cgs */ + double conv_factor_energy_rate_to_cgs; + + /*! Conversion factor from length to cgs */ + double conv_factor_length_to_cgs; + + /*! Conversion factor from mass to cgs */ + double conv_factor_mass_to_cgs; + + /*! Conversion factor from time to cgs */ + double conv_factor_time_to_cgs; + + /*! Conversion factor from specific energy to cgs */ + double conv_factor_specific_energy_to_cgs; + + /*! Proton mass */ + double proton_mass_cgs_inv; + + /*! Convert Kelvin to internal temperature */ + double T_K_to_int; + + /* ------------ Stellar feedback properties for eta computation + * --------------- */ + + /*! Normalization for the mass loading curve */ + float FIRE_eta_normalization; + + /*! The location (in internal mass units) where the break in the + * mass loading curve occurs */ + float FIRE_eta_break; + + /*! The power-law slope of eta below FIRE_eta_break */ + float FIRE_eta_lower_slope; + + /*! The power-law slope of eta above FIRE_eta_break */ + float FIRE_eta_upper_slope; + + /*! The minimum galaxy stellar mass in internal units */ + float minimum_galaxy_stellar_mass; + + /*! The mass loading factor of stellar feedback suppressed above this z */ + float wind_eta_suppression_redshift; +}; + +/** + * @brief Initialise the black hole properties from the parameter file. + * + * For the basic black holes neighbour finding properties we use the + * defaults from the hydro scheme if the users did not provide specific + * values. + * + * @param bp The #black_holes_props. + * @param phys_const The physical constants in the internal unit system. + * @param us The internal unit system. + * @param params The parsed parameters. + * @param hydro_props The already read-in properties of the hydro scheme. + * @param cosmo The cosmological model. + */ +INLINE static void black_holes_props_init(struct black_holes_props *bp, + const struct phys_const *phys_const, + const struct unit_system *us, + struct swift_params *params, + const struct hydro_props *hydro_props, + const struct cosmology *cosmo) { + + /* Calculate conversion factors (all internal units) */ + const double k_B = phys_const->const_boltzmann_k; + const double m_p = phys_const->const_proton_mass; + const double mu = hydro_props->mu_ionised; + bp->temp_to_u_factor = k_B / (mu * hydro_gamma_minus_one * m_p); + + const double Myr_in_cgs = 1e6 * 365.25 * 24. * 60. * 60.; + bp->time_to_Myr = + units_cgs_conversion_factor(us, UNIT_CONV_TIME) / Myr_in_cgs; + + /* Store constant of gravity */ + bp->const_newton_G = phys_const->const_newton_G; + + /* Read in the basic neighbour search properties or default to the hydro + ones if the user did not provide any different values */ + + /* Kernel properties */ + bp->eta_neighbours = parser_get_opt_param_float( + params, "BlackHoles:resolution_eta", hydro_props->eta_neighbours); + + /* Tolerance for the smoothing length Newton-Raphson scheme */ + bp->h_tolerance = parser_get_opt_param_float(params, "BlackHoles:h_tolerance", + hydro_props->h_tolerance); + + /* Get derived properties */ + bp->target_neighbours = pow_dimension(bp->eta_neighbours) * kernel_norm; + const float delta_eta = bp->eta_neighbours * (1.f + bp->h_tolerance); + bp->delta_neighbours = + (pow_dimension(delta_eta) - pow_dimension(bp->eta_neighbours)) * + kernel_norm; + + /* Number of iterations to converge h */ + bp->max_smoothing_iterations = + parser_get_opt_param_int(params, "BlackHoles:max_ghost_iterations", + hydro_props->max_smoothing_iterations); + + /* Maximum smoothing length */ + bp->h_max = parser_get_opt_param_float(params, "BlackHoles:h_max", + hydro_props->h_max); + + /* Minimum smoothing length */ + bp->h_min = parser_get_opt_param_float(params, "BlackHoles:h_min", + hydro_props->h_min); + + /* Time integration properties */ + const float max_volume_change = + parser_get_opt_param_float(params, "BlackHoles:max_volume_change", -1); + if (max_volume_change == -1) + bp->log_max_h_change = hydro_props->log_max_h_change; + else + bp->log_max_h_change = logf(powf(max_volume_change, hydro_dimension_inv)); + + /* Initialisation properties ---------------------------- */ + + bp->subgrid_seed_mass = + parser_get_param_float(params, "ObsidianAGN:subgrid_seed_mass_Msun"); + + /* Convert to internal units */ + bp->subgrid_seed_mass *= phys_const->const_solar_mass; + + bp->use_subgrid_mass_from_ics = parser_get_opt_param_int( + params, "ObsidianAGN:use_subgrid_mass_from_ics", 1); + if (bp->use_subgrid_mass_from_ics) { + bp->with_subgrid_mass_check = parser_get_opt_param_int( + params, "ObsidianAGN:with_subgrid_mass_check", 1); + } + + /* Accretion parameters ---------------------------------- */ + + /* Conversion factor for internal mass to M_solar */ + bp->mass_to_solar_mass = 1.f / phys_const->const_solar_mass; + + bp->min_gas_mass_for_nibbling = parser_get_param_float( + params, "ObsidianAGN:min_gas_mass_for_nibbling_Msun"); + bp->min_gas_mass_for_nibbling /= bp->mass_to_solar_mass; + + const double T_K_to_int = + 1. / units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + + bp->environment_temperature_cut = parser_get_opt_param_float( + params, "ObsidianAGN:environment_temperature_cut_K", 1.0e5f); + + bp->cold_gas_temperature_cut = parser_get_opt_param_float( + params, "ObsidianAGN:cold_gas_temperature_cut_K", bp->environment_temperature_cut); + + bp->environment_temperature_cut *= T_K_to_int; + bp->cold_gas_temperature_cut *= T_K_to_int; + + bp->dynamical_time_factor = parser_get_opt_param_float( + params, "ObsidianAGN:dynamical_time_factor", 1.f); + + bp->dynamical_time_max = parser_get_opt_param_float( + params, "ObsidianAGN:dynamical_time_max_in_Myr", 0.f); + bp->dynamical_time_max /= bp->time_to_Myr; + + bp->torque_accretion_method = parser_get_opt_param_int( + params, "ObsidianAGN:torque_accretion_method", 0); + + bp->torque_accretion_norm = + parser_get_param_float(params, "ObsidianAGN:torque_accretion_norm"); + + bp->suppress_growth = + parser_get_opt_param_int(params, "ObsidianAGN:suppress_growth", 0); + + if (bp->suppress_growth == 5 && bp->torque_accretion_method == 2) { + error( + "SF-based suppression of BH mass will not work correctly with " + "Simba-style torque-limited BH growth -- use tdyn method"); + } + + bp->dt_accretion_factor = parser_get_opt_param_float( + params, "ObsidianAGN:dt_accretion_factor", 1.f); + if (bp->dt_accretion_factor > 1.f || bp->dt_accretion_factor < 0.f) { + error("ObsidianAGN:dt_accretion_factor must be between 0 and 1"); + } + + bp->bh_characteristic_suppression_mass = parser_get_opt_param_float( + params, "ObsidianAGN:bh_characteristic_suppression_mass", 0.f); + + bp->suppression_sf_eff = + parser_get_opt_param_float(params, "ObsidianAGN:suppression_sf_eff", 0.f); + + bp->tdyn_sigma = + parser_get_opt_param_float(params, "ObsidianAGN:tdyn_sigma", 0.f); + + if (bp->suppress_growth == 4 || bp->suppress_growth == 5) { + bp->FIRE_eta_normalization = + parser_get_param_float(params, "KIARAFeedback:FIRE_eta_normalization"); + bp->FIRE_eta_break = + parser_get_param_float(params, "KIARAFeedback:FIRE_eta_break_Msun"); + bp->FIRE_eta_break /= bp->mass_to_solar_mass; + bp->FIRE_eta_lower_slope = + parser_get_param_float(params, "KIARAFeedback:FIRE_eta_lower_slope"); + bp->FIRE_eta_upper_slope = + parser_get_param_float(params, "KIARAFeedback:FIRE_eta_upper_slope"); + bp->minimum_galaxy_stellar_mass = parser_get_param_float( + params, "KIARAFeedback:minimum_galaxy_stellar_mass_Msun"); + bp->minimum_galaxy_stellar_mass /= bp->mass_to_solar_mass; + bp->wind_eta_suppression_redshift = parser_get_opt_param_float( + params, "KIARAFeedback:wind_eta_suppression_redshift", 0.f); + } + + bp->f_Edd_maximum = + parser_get_param_float(params, "ObsidianAGN:max_eddington_fraction"); + + bp->f_Edd_Bondi_maximum = parser_get_opt_param_float( + params, "ObsidianAGN:max_bondi_eddington_fraction", 1.f); + + bp->bondi_BH_mass_cap = parser_get_opt_param_float( + params, "ObsidianAGN:bondi_BH_mass_cap_Msun", FLT_MAX); + bp->bondi_BH_mass_cap /= bp->mass_to_solar_mass; + + bp->fixed_T_above_EoS_factor = exp10( + parser_get_param_float(params, "ObsidianAGN:fixed_T_above_EoS_dex")); + + bp->dynamical_time_calculation_method = parser_get_opt_param_int( + params, "ObsidianAGN:dynamical_time_calculation_method", 1); + + /* Feedback parameters ---------------------------------- */ + + bp->kms_to_internal = + 1.0e5f / units_cgs_conversion_factor(us, UNIT_CONV_SPEED); + + char temp3[40]; + parser_get_param_string(params, "ObsidianAGN:jet_loading_type", temp3); + if (strcmp(temp3, "EnergyLoaded") == 0) { + bp->jet_loading_type = BH_jet_energy_loaded; + } else if (strcmp(temp3, "MomentumLoaded") == 0) { + bp->jet_loading_type = BH_jet_momentum_loaded; + } else if (strcmp(temp3, "MixedLoaded") == 0) { + bp->jet_loading_type = BH_jet_mixed_loaded; + } else { + error( + "The BH jet loading must be either EnergyLoaded or " + "MomentumLoaded or MixedLoaded, not %s", + temp3); + } + + bp->jet_velocity = + parser_get_param_float(params, "ObsidianAGN:jet_velocity_km_s"); + bp->jet_velocity *= bp->kms_to_internal; + if (bp->jet_velocity == 0.f) { + error("jet_velocity must be >0.f (or <0.f if scaling with z)."); + } + + /* Use this in all of the physical calculations */ + const float jet_velocity = fabs(bp->jet_velocity); + + bp->jet_temperature = + parser_get_param_float(params, "ObsidianAGN:jet_temperature_K"); + bp->jet_temperature *= T_K_to_int; + + bp->eddington_fraction_lower_boundary = parser_get_param_float( + params, "ObsidianAGN:eddington_fraction_lower_boundary"); + + bp->eddington_fraction_upper_boundary = parser_get_param_float( + params, "ObsidianAGN:eddington_fraction_upper_boundary"); + + const double kpc_per_km = 3.24078e-17; + const double age_s = 13800. * Myr_in_cgs; /* Approximate age at z = 0 */ + const double jet_velocity_kpc_s = + (jet_velocity / bp->kms_to_internal) * kpc_per_km; + const double recouple_distance_kpc = 10.; + const double f_jet_recouple = + recouple_distance_kpc / (jet_velocity_kpc_s * age_s); + + bp->jet_decouple_time_factor = parser_get_opt_param_float( + params, "ObsidianAGN:jet_decouple_time_factor", f_jet_recouple); + + bp->fixed_spin = parser_get_param_float(params, "ObsidianAGN:fixed_spin"); + if (bp->fixed_spin >= 1.f || bp->fixed_spin <= 0.f) { + error("Black hole must have spin > 0.0 and < 1.0"); + } + + bp->A_sd = powf(0.9663f - 0.9292f * bp->fixed_spin, -0.5639f); + bp->B_sd = powf(4.627f - 4.445f * bp->fixed_spin, -0.5524f); + bp->C_sd = powf(827.3f - 718.1f * bp->fixed_spin, -0.7060f); + + const float phi_bh = -20.2f * powf(bp->fixed_spin, 3.f) - + 14.9f * powf(bp->fixed_spin, 2.f) + + 34.f * bp->fixed_spin + 52.6f; + const float big_J = + bp->fixed_spin / (2.f * (1.f + sqrtf(1.f - powf(bp->fixed_spin, 2.f)))); + const float f_j = + powf(big_J, 2.f) + 1.38f * powf(big_J, 4.f) - 9.2f * powf(big_J, 6.f); + + bp->jet_efficiency = (1.f / (24.f * M_PI * M_PI)) * powf(phi_bh, 2.f) * f_j; + + /* Useful when computing the mass loadings below */ + const double c_over_v = phys_const->const_speed_light_c / jet_velocity; + + /* How are we loading the jet? Momentum, mixed, or energy? */ + if (bp->jet_loading_type == BH_jet_momentum_loaded) { + bp->jet_mass_loading = bp->jet_efficiency * c_over_v; + } else if (bp->jet_loading_type == BH_jet_mixed_loaded) { + bp->jet_frac_energy = + parser_get_param_float(params, "ObsidianAGN:jet_frac_energy_loaded"); + if (bp->jet_frac_energy <= 0.f || bp->jet_frac_energy >= 1.f) { + error("jet_frac_energy_loaded must be >0 and <1."); + } + + const double energy_loading = 2. * bp->jet_efficiency * pow(c_over_v, 2.); + const double momentum_loading = bp->jet_efficiency * c_over_v; + const double energy_term = bp->jet_frac_energy * energy_loading; + const double momentum_term = (1. - bp->jet_frac_energy) * momentum_loading; + bp->jet_mass_loading = energy_term + momentum_term; + } else { + bp->jet_mass_loading = 2. * bp->jet_efficiency * pow(c_over_v, 2.); + } + + bp->jet_subgrid_velocity = + parser_get_param_float(params, "ObsidianAGN:jet_subgrid_velocity_km_s"); + bp->jet_subgrid_velocity *= bp->kms_to_internal; + + const float R = 1.f / bp->eddington_fraction_upper_boundary; + const float eta_at_slim_disk_boundary = + (R / 16.f) * bp->A_sd * + ((0.985f / (R + (5.f / 8.f) * bp->B_sd)) + + (0.015f / (R + (5.f / 8.f) * bp->C_sd))); + + /* If we scale BH v_jet, choose power law scaling with MBH/1.e8 or LBH/1.e45. + * The minimum v_jet is always given by jet_subgrid_velocity */ + bp->jet_velocity_scaling_with_BH_mass = parser_get_opt_param_float( + params, "ObsidianAGN:jet_velocity_scaling_with_BH_mass", 0.f); + + bp->jet_velocity_scaling_with_BH_lum = parser_get_opt_param_float( + params, "ObsidianAGN:jet_velocity_scaling_with_BH_lum", 0.f); + + bp->jet_launch_dir = + parser_get_param_int(params, "ObsidianAGN:jet_launch_dir"); + + bp->jet_minimum_reservoir_mass = parser_get_param_float( + params, "ObsidianAGN:jet_minimum_reservoir_mass_Msun"); + bp->jet_minimum_reservoir_mass /= bp->mass_to_solar_mass; + + bp->lum_thresh_always_jet = parser_get_opt_param_float( + params, "ObsidianAGN:lum_thresh_always_jet_1e45_erg_s", 0.f); + bp->lum_thresh_always_jet *= + 1.e45 * units_cgs_conversion_factor(us, UNIT_CONV_TIME) / + units_cgs_conversion_factor(us, UNIT_CONV_ENERGY); + + /* We need to keep epsilon_r continuous over all M_dot,BH/M_dot,Edd */ + bp->epsilon_r = eta_at_slim_disk_boundary; + if (bp->epsilon_r > 1.f) error("Somehow epsilon_r is greater than 1.0."); + + bp->adaf_coupling = + parser_get_param_float(params, "ObsidianAGN:adaf_coupling"); + bp->adaf_z_scaling = + parser_get_opt_param_float(params, "ObsidianAGN:adaf_z_scaling", 0.f); + bp->quasar_coupling = + parser_get_param_float(params, "ObsidianAGN:quasar_coupling"); + bp->quasar_luminosity_thresh = parser_get_opt_param_float( + params, "ObsidianAGN:quasar_lum_thresh_1e45_erg_s", 0.f); + bp->quasar_luminosity_thresh *= + units_cgs_conversion_factor(us, UNIT_CONV_TIME) / + units_cgs_conversion_factor(us, UNIT_CONV_ENERGY) * 1.e45; + bp->slim_disk_coupling = parser_get_opt_param_float( + params, "ObsidianAGN:slim_disk_coupling", bp->quasar_coupling); + + /* These are for momentum constrained winds */ + bp->quasar_wind_momentum_flux = parser_get_opt_param_float( + params, "ObsidianAGN:quasar_wind_momentum_flux", 20.f); + bp->quasar_wind_speed = + parser_get_param_float(params, "ObsidianAGN:quasar_wind_speed_km_s"); + bp->quasar_wind_speed *= bp->kms_to_internal; + + const double recouple_distance_non_jet_kpc = 1.5; + const double quasar_velocity_kpc_s = + (fabs(bp->quasar_wind_speed) / bp->kms_to_internal) * kpc_per_km; + const double f_quasar_recouple = + recouple_distance_non_jet_kpc / (quasar_velocity_kpc_s * age_s); + + bp->quasar_decouple_time_factor = parser_get_opt_param_float( + params, "ObsidianAGN:quasar_decouple_time_factor", f_quasar_recouple); + + bp->quasar_wind_dir = + parser_get_param_int(params, "ObsidianAGN:quasar_wind_dir"); + + bp->quasar_wind_mass_loading = + bp->quasar_wind_momentum_flux * fabs(bp->quasar_coupling) * + bp->epsilon_r * + (phys_const->const_speed_light_c / fabs(bp->quasar_wind_speed)); + bp->quasar_f_accretion = 1.f / (1.f + bp->quasar_wind_mass_loading); + + const double slim_disk_wind_momentum_flux = parser_get_opt_param_float( + params, "ObsidianAGN:slim_disk_wind_momentum_flux", + bp->quasar_wind_momentum_flux); + + bp->slim_disk_wind_speed = parser_get_opt_param_float( + params, "ObsidianAGN:slim_disk_wind_speed_km_s", + bp->quasar_wind_speed / bp->kms_to_internal); + bp->slim_disk_wind_speed *= bp->kms_to_internal; + + bp->slim_disk_wind_dir = + parser_get_param_int(params, "ObsidianAGN:slim_disk_wind_dir"); + + const double slim_disk_velocity_kpc_s = + (fabs(bp->slim_disk_wind_speed) / bp->kms_to_internal) * kpc_per_km; + const double f_slim_disk_recouple = + recouple_distance_non_jet_kpc / (slim_disk_velocity_kpc_s * age_s); + + bp->slim_disk_decouple_time_factor = parser_get_opt_param_float( + params, "ObsidianAGN:slim_disk_decouple_time_factor", + f_slim_disk_recouple); + + /* Set the slim disk mass loading to be continuous at the + * eta upper boundary. Compute the phi term to solve for the + * accretion fraction */ + bp->slim_disk_phi = + slim_disk_wind_momentum_flux * fabs(bp->slim_disk_coupling) * + (phys_const->const_speed_light_c / fabs(bp->slim_disk_wind_speed)); + const double slim_disk_wind_mass_loading = bp->slim_disk_phi * bp->epsilon_r; + + bp->slim_disk_jet_active = + parser_get_param_int(params, "ObsidianAGN:slim_disk_jet_active"); + + bp->adaf_disk_efficiency = + parser_get_param_float(params, "ObsidianAGN:adaf_disk_efficiency"); + + bp->adaf_kick_factor = + parser_get_opt_param_float(params, "ObsidianAGN:adaf_kick_factor", 0.5f); + if (bp->adaf_kick_factor < 0.f || bp->adaf_kick_factor > 1.f) { + error("adaf_kick_factor must be >= 0 and <= 1."); + } + + bp->adaf_wind_speed = parser_get_opt_param_float( + params, "ObsidianAGN:adaf_wind_speed_km_s", 0.f); + bp->adaf_wind_speed *= bp->kms_to_internal; + + const float f_psi = + parser_get_opt_param_float(params, "ObsidianAGN:adaf_f_quasar_psi", -1.f); + if (f_psi > 1.f) { + error("adaf_f_quasar_psi must be <= 1."); + } + + bp->adaf_wind_dir = parser_get_param_int(params, "ObsidianAGN:adaf_wind_dir"); + + float jet_subgrid_mass_loading = + 2.f * bp->jet_efficiency * + (phys_const->const_speed_light_c / bp->jet_subgrid_velocity) * + (phys_const->const_speed_light_c / bp->jet_subgrid_velocity); + + /* f_acc = 1 / (1 + psi_jet,sub + psi_adaf) must still be true, but f_acc + * is fixed at quasar_f_accretion if f_psi > 0 + */ + if (f_psi > 0.f) { + + /* This is always true in the negative case */ + bp->adaf_f_accretion = bp->quasar_f_accretion; + + const double jet_eff_psi_quasar = + 2. * bp->jet_efficiency / bp->quasar_wind_mass_loading; + if (jet_eff_psi_quasar > 1.) { + error( + "The jet efficiency is too high or the quasar wind mass loading" + " is too low for your choice of how to distribute energy in" + " the ADAF mode."); + } + + const double psi_jet_subgrid = bp->quasar_wind_mass_loading * (1. - f_psi); + const double c_frac = sqrt(2. * bp->jet_efficiency / psi_jet_subgrid); + + /* Do not exceed the speed-of-light */ + if (c_frac >= 1.) { + const double f_max = 1. - jet_eff_psi_quasar; + error( + "Cannot request more than f = %g of the quasar wind mass " + "loading as the ADAF mass loading since it violates the " + "speed-of-light in the sub-grid jet velocity.", + f_max); + } + + bp->jet_subgrid_velocity = c_frac * phys_const->const_speed_light_c; + + /* Reset the sub-grid mass loading with the new velocity */ + jet_subgrid_mass_loading = + 2.f * bp->jet_efficiency * + (phys_const->const_speed_light_c / bp->jet_subgrid_velocity) * + (phys_const->const_speed_light_c / bp->jet_subgrid_velocity); + + bp->adaf_wind_mass_loading = f_psi * bp->quasar_wind_mass_loading; + + const double adaf_eps = bp->adaf_coupling * bp->adaf_disk_efficiency; + bp->adaf_wind_speed = sqrt(2. * adaf_eps / bp->adaf_wind_mass_loading); + bp->adaf_wind_speed *= phys_const->const_speed_light_c; + if (bp->adaf_wind_speed > bp->jet_subgrid_velocity) { + error( + "The ADAF wind speed is above the sub-grid jet velocity. Are " + "you sure this is right?"); + } + } else { + if (f_psi < 0.f) { + /* Heat everything in the kernel in this case */ + bp->adaf_wind_mass_loading = 0.f; + + /* This is always true in the negative case */ + bp->adaf_f_accretion = 1.f / (1.f + jet_subgrid_mass_loading); + } else { + if (bp->adaf_wind_speed > 0.f) { + bp->adaf_wind_mass_loading = + 2.f * bp->adaf_coupling * bp->adaf_disk_efficiency; + bp->adaf_wind_mass_loading *= + pow(phys_const->const_speed_light_c / bp->adaf_wind_speed, 2.f); + bp->adaf_f_accretion = + 1.f / (1.f + jet_subgrid_mass_loading + bp->adaf_wind_mass_loading); + } else { + error("adaf_wind_speed_km_s must be non-zero in this case!"); + } + } + } + + /* Do not decouple the ADAF winds */ + bp->adaf_decouple_time_factor = 0.; + + bp->adaf_maximum_temperature = parser_get_opt_param_float( + params, "ObsidianAGN:adaf_maximum_temperature_K", 5.e7f); + bp->adaf_maximum_temperature *= T_K_to_int; + + bp->adaf_heating_n_H_threshold_cgs = parser_get_opt_param_float( + params, "ObsidianAGN:adaf_heating_n_H_threshold_cgs", 0.13f); + + bp->adaf_heating_T_threshold_cgs = parser_get_opt_param_float( + params, "ObsidianAGN:adaf_heating_T_threshold_cgs", 5.0e5f); + + bp->adaf_mass_limit = + parser_get_param_float(params, "ObsidianAGN:adaf_mass_limit_Msun"); + bp->adaf_mass_limit /= bp->mass_to_solar_mass; + + bp->adaf_mass_limit_spread = parser_get_opt_param_float( + params, "ObsidianAGN:adaf_mass_limit_spread_Msun", 0.f); + bp->adaf_mass_limit_spread /= bp->mass_to_solar_mass; + + bp->adaf_mass_limit_a_scaling = parser_get_opt_param_float( + params, "ObsidianAGN:adaf_mass_limit_a_scaling", 0.f); + + bp->adaf_mass_limit_a_min = parser_get_opt_param_float( + params, "ObsidianAGN:adaf_mass_limit_a_min", 0.f); + + bp->adaf_cooling_shutoff_factor = parser_get_opt_param_float( + params, "ObsidianAGN:adaf_cooling_shutoff_factor", -1.f); + + /* Always use nibbling in Obsidian */ + bp->use_nibbling = 1; + + bp->bondi_use_all_gas = + parser_get_opt_param_int(params, "ObsidianAGN:bondi_use_all_gas", 0); + + bp->bondi_alpha = + parser_get_opt_param_float(params, "ObsidianAGN:bondi_alpha", 1.f); + + bp->minimum_black_hole_mass_unresolved = parser_get_param_float( + params, "ObsidianAGN:minimum_black_hole_mass_unresolved_Msun"); + bp->minimum_black_hole_mass_unresolved /= bp->mass_to_solar_mass; + + bp->minimum_black_hole_mass_v_kick = parser_get_param_float( + params, "ObsidianAGN:minimum_black_hole_mass_v_kick_Msun"); + bp->minimum_black_hole_mass_v_kick /= bp->mass_to_solar_mass; + + bp->minimum_v_kick_km_s = parser_get_opt_param_float( + params, "ObsidianAGN:minimum_v_kick_km_s", 10.f); + + /* Reposition parameters --------------------------------- */ + + bp->max_reposition_mass = + parser_get_param_float(params, "ObsidianAGN:max_reposition_mass") * + phys_const->const_solar_mass; + bp->max_reposition_distance_ratio = parser_get_param_float( + params, "ObsidianAGN:max_reposition_distance_ratio"); + + bp->with_reposition_velocity_threshold = parser_get_param_int( + params, "ObsidianAGN:with_reposition_velocity_threshold"); + + if (bp->with_reposition_velocity_threshold) { + bp->max_reposition_velocity_ratio = parser_get_param_float( + params, "ObsidianAGN:max_reposition_velocity_ratio"); + + /* Prevent nonsensical input */ + if (bp->max_reposition_velocity_ratio <= 0) + error("max_reposition_velocity_ratio must be positive, not %f.", + bp->max_reposition_velocity_ratio); + + bp->min_reposition_velocity_threshold = parser_get_param_float( + params, "ObsidianAGN:min_reposition_velocity_threshold"); + /* Convert from km/s to internal units */ + bp->min_reposition_velocity_threshold *= + (1e5 / (us->UnitLength_in_cgs / us->UnitTime_in_cgs)); + } + + bp->set_reposition_speed = + parser_get_param_int(params, "ObsidianAGN:set_reposition_speed"); + + if (bp->set_reposition_speed) { + bp->reposition_coefficient_upsilon = parser_get_param_float( + params, "ObsidianAGN:reposition_coefficient_upsilon"); + + /* Prevent the user from making silly wishes */ + if (bp->reposition_coefficient_upsilon <= 0) + error( + "reposition_coefficient_upsilon must be positive, not %f " + "km/s/M_sun.", + bp->reposition_coefficient_upsilon); + + /* Convert from km/s to internal units */ + bp->reposition_coefficient_upsilon *= + (1e5 / (us->UnitLength_in_cgs / us->UnitTime_in_cgs)); + + /* Scaling parameters with BH mass and gas density */ + bp->reposition_reference_mass = + parser_get_param_float(params, + "ObsidianAGN:reposition_reference_mass") * + phys_const->const_solar_mass; + bp->reposition_exponent_mass = parser_get_opt_param_float( + params, "ObsidianAGN:reposition_exponent_mass", 2.0); + bp->reposition_reference_n_H = + parser_get_param_float(params, "ObsidianAGN:reposition_reference_n_H"); + bp->reposition_exponent_n_H = parser_get_opt_param_float( + params, "ObsidianAGN:reposition_exponent_n_H", 1.0); + } + + bp->correct_bh_potential_for_repositioning = + parser_get_param_int(params, "ObsidianAGN:with_potential_correction"); + + /* Merger parameters ------------------------------------- */ + + bp->minor_merger_threshold = + parser_get_param_float(params, "ObsidianAGN:threshold_minor_merger"); + + bp->major_merger_threshold = + parser_get_param_float(params, "ObsidianAGN:threshold_major_merger"); + + char temp2[40]; + parser_get_param_string(params, "ObsidianAGN:merger_threshold_type", temp2); + if (strcmp(temp2, "CircularVelocity") == 0) + bp->merger_threshold_type = BH_mergers_circular_velocity; + else if (strcmp(temp2, "EscapeVelocity") == 0) + bp->merger_threshold_type = BH_mergers_escape_velocity; + else if (strcmp(temp2, "DynamicalEscapeVelocity") == 0) + bp->merger_threshold_type = BH_mergers_dynamical_escape_velocity; + else + error( + "The BH merger model must be either CircularVelocity, EscapeVelocity, " + "or DynamicalEscapeVelocity, not %s", + temp2); + + bp->max_merging_distance_ratio = + parser_get_param_float(params, "ObsidianAGN:merger_max_distance_ratio"); + + /* ---- Black hole time-step properties ------------------ */ + + const double time_step_min_Myr = parser_get_opt_param_float( + params, "ObsidianAGN:minimum_timestep_Myr", FLT_MAX); + + bp->time_step_min = time_step_min_Myr * Myr_in_cgs / + units_cgs_conversion_factor(us, UNIT_CONV_TIME); + + /* Common conversion factors ----------------------------- */ + + /* Calculate conversion factor from rho to n_H. + * Note this assumes primoridal abundance */ + const double X_H = hydro_props->hydrogen_mass_fraction; + bp->rho_to_n_cgs = + (X_H / m_p) * units_cgs_conversion_factor(us, UNIT_CONV_NUMBER_DENSITY); + + bp->length_to_parsec = 1.f / phys_const->const_parsec; + + bp->time_to_yr = 1.f / phys_const->const_year; + + /* Some useful conversion values */ + bp->conv_factor_density_to_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_DENSITY); + bp->conv_factor_energy_rate_to_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_ENERGY) / + units_cgs_conversion_factor(us, UNIT_CONV_TIME); + bp->conv_factor_length_to_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_LENGTH); + bp->conv_factor_mass_to_cgs = units_cgs_conversion_factor(us, UNIT_CONV_MASS); + bp->conv_factor_time_to_cgs = units_cgs_conversion_factor(us, UNIT_CONV_TIME); + bp->conv_factor_specific_energy_to_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS); + + /* Useful constants */ + bp->proton_mass_cgs_inv = + 1. / (phys_const->const_proton_mass * + units_cgs_conversion_factor(us, UNIT_CONV_MASS)); + + bp->T_K_to_int = T_K_to_int; + + if (engine_rank == 0) { + message("Black holes kernel: %s with eta=%f (%.2f neighbours).", + kernel_name, bp->eta_neighbours, bp->target_neighbours); + + message("Black holes relative tolerance in h: %.5f (+/- %.4f neighbours).", + bp->h_tolerance, bp->delta_neighbours); + + message("Black hole model is Obsidian (Rennehan+24, with modifications for KIARA)"); + message("Black hole torque accretion efficiency is %g", + bp->torque_accretion_norm); + message("Black hole Bondi accretion alpha is %g", + bp->bondi_alpha); + message("Black hole Bondi accretion BH mass cap is %g Msun", + bp->bondi_BH_mass_cap * bp->mass_to_solar_mass); + message("Black hole jet velocity is %g km/s", + bp->jet_velocity / bp->kms_to_internal); + if (bp->jet_loading_type == BH_jet_momentum_loaded) { + message("Black hole jet loading (momentum) is %g", bp->jet_mass_loading); + } else if (bp->jet_loading_type == BH_jet_mixed_loaded) { + message("Black hole jet loading (mixed) is %g", bp->jet_mass_loading); + } else { + message("Black hole jet loading (energy) is %g", bp->jet_mass_loading); + } + message("Black hole subgrid jet velocity is %g km/s", + bp->jet_subgrid_velocity / bp->kms_to_internal); + message("Black hole subgrid jet loading (energy) is %g", + jet_subgrid_mass_loading); + message("Black hole jet efficiency is %g", bp->jet_efficiency); + message("Black hole jet recouple factor is %g", f_jet_recouple); + message("Black hole quasar radiative efficiency is %g", bp->epsilon_r); + if (bp->quasar_luminosity_thresh > 0.f) { + message( + "Black hole quasar coupling %g is boosted above Lbol>%g erg/s", + bp->quasar_coupling, + bp->quasar_luminosity_thresh * bp->conv_factor_energy_rate_to_cgs); + } + if (bp->lum_thresh_always_jet > 0.f) { + message("Black hole jet mode always on above Lbol>%g erg/s", + bp->lum_thresh_always_jet * bp->conv_factor_energy_rate_to_cgs); + } + message("Black hole quasar wind speed is %g km/s", + bp->quasar_wind_speed / bp->kms_to_internal); + message("Black hole quasar mass loading (momentum) is %g", + bp->quasar_wind_mass_loading); + message("Black hole quasar f_accretion is %g", bp->quasar_f_accretion); + message("Black hole quasar recouple factor is %g", f_quasar_recouple); + message("Black hole slim disk wind speed is %g km/s", + bp->slim_disk_wind_speed / bp->kms_to_internal); + message( + "Black hole slim disk mass loading (momentum) is %g " + "(at the eta=%g boundary)", + slim_disk_wind_mass_loading, bp->epsilon_r); + message("Black hole slim disk recouple factor is %g", f_slim_disk_recouple); + message("Black hole ADAF mass loading (energy) is %g", + bp->adaf_wind_mass_loading); + message("Black hole ADAF f_accretion is %g", bp->adaf_f_accretion); + message("Black hole ADAF v_wind is %g km/s (thermally dumped)", + bp->adaf_wind_speed / bp->kms_to_internal); + } +} + +/** + * @brief Computes the mass limit above which ADAF mode is allowed. + * + * @param bp The black hole particle. + * @param props The properties of the black hole scheme. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static double +get_black_hole_adaf_mass_limit(const struct bpart *const bp, + const struct black_holes_props *props, + const struct cosmology *cosmo) { + double mass_min = fabs(props->adaf_mass_limit); + if (props->adaf_mass_limit < 0.) + mass_min *= pow(fmax(cosmo->a, props->adaf_mass_limit_a_min), + props->adaf_mass_limit_a_scaling); + mass_min += 0.01f * (float)(bp->id % 100) * props->adaf_mass_limit_spread; + return mass_min; +} + +/** + * @brief Compute velocity of jet. + * + * @param bi Black hole particle. + * @param cosmo The current cosmological model. + * @param props The properties of the black hole scheme. + */ +__attribute__((always_inline)) INLINE static float +black_hole_compute_jet_velocity(const struct bpart *bi, + const struct cosmology *cosmo, + const struct black_holes_props *props) { + + float jet_velocity = fabs(props->jet_velocity); + /* If the user supplied a variable jet velocity we must recalculate the + * mass loading based on the variable jet velocity */ + if (props->jet_velocity < 0.f || + props->jet_velocity_scaling_with_BH_mass > 0.f || + props->jet_velocity_scaling_with_BH_lum > 0.f) { + if (props->jet_velocity < 0.f) { + jet_velocity *= powf(cosmo->H / cosmo->H0, 1.f / 3.f); + } + if (props->jet_velocity_scaling_with_BH_mass > 0.f) { + float BH_mass_scaled = + bi->subgrid_mass * props->mass_to_solar_mass * 1.0e-8; + BH_mass_scaled = fmax(BH_mass_scaled, 1.f); + jet_velocity *= + powf(BH_mass_scaled, props->jet_velocity_scaling_with_BH_mass); + } + if (props->jet_velocity_scaling_with_BH_lum > 0.f) { + float BH_lum_scaled = bi->radiative_luminosity * + props->conv_factor_energy_rate_to_cgs * 1.e-45; + BH_lum_scaled = fmax(BH_lum_scaled, 1.f); + jet_velocity *= + powf(BH_lum_scaled, props->jet_velocity_scaling_with_BH_lum); + } + } + return jet_velocity; +} + +/** + * @brief Compute ramp-up of jet energy above mass limit + * + * @param bi Black hole particle. + * @param cosmo The current cosmological model. + * @param props The properties of the black hole scheme. + */ +__attribute__((always_inline)) INLINE static float +black_hole_compute_jet_energy_ramp(const struct bpart *bi, + const struct cosmology *cosmo, + const struct black_holes_props *props) { + + /* Only do jet if above ADAF mass limit */ + const float my_adaf_mass_limit = + get_black_hole_adaf_mass_limit(bi, props, cosmo); + + /* Compute ramp-up of jet feedback energy */ + float jet_ramp = 0.f; + /* Ramp-up above ADAF min mass */ + if (bi->subgrid_mass > my_adaf_mass_limit) { + jet_ramp = fmin(bi->subgrid_mass / my_adaf_mass_limit - 1.f, 1.f); + } + return jet_ramp; +} + +/** + * @brief Write a black_holes_props struct to the given FILE as a stream of + * bytes. + * + * @param props the black hole properties struct + * @param stream the file stream + */ +INLINE static void black_holes_struct_dump( + const struct black_holes_props *props, FILE *stream) { + restart_write_blocks((void *)props, sizeof(struct black_holes_props), 1, + stream, "black_holes props", "black holes props"); +} + +/** + * @brief Restore a black_holes_props struct from the given FILE as a stream of + * bytes. + * + * @param props the black hole properties struct + * @param stream the file stream + */ +INLINE static void black_holes_struct_restore( + const struct black_holes_props *props, FILE *stream) { + restart_read_blocks((void *)props, sizeof(struct black_holes_props), 1, + stream, NULL, "black holes props"); +} + +#endif /* SWIFT_OBSIDIAN_BLACK_HOLES_PROPERTIES_H */ diff --git a/src/black_holes/Obsidian/black_holes_struct.h b/src/black_holes/Obsidian/black_holes_struct.h new file mode 100644 index 0000000000..644077d9f2 --- /dev/null +++ b/src/black_holes/Obsidian/black_holes_struct.h @@ -0,0 +1,143 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_BLACK_HOLES_STRUCT_OBSIDIAN_H +#define SWIFT_BLACK_HOLES_STRUCT_OBSIDIAN_H + +/* Standard headers */ +#include + +/* Local includes */ +#include "inline.h" + +/** + * @brief Black holes-related fields carried by each *gas* particle. + */ +struct black_holes_part_data { + + /*! ID of the black-hole that will swallow this #part. */ + long long swallow_id; + + /*! ID of the black-hole that will kick this particle as a jet particle */ + long long jet_id; + + /*! ID of the black-hole that will heat this particle in the ADAF mode */ + long long adaf_id; + + /*! Gravitational potential of the particle (for repositioning) */ + float potential; +}; + +/** + * @brief Black holes-related fields carried by each *BH* particle. + */ +struct black_holes_bpart_data { + + /*! ID of the black-hole that will swallow this #bpart. */ + long long swallow_id; + + /*! Mass of the black-hole that will swallow this #bpart. */ + float swallow_mass; +}; + +/** + * @brief Update a given #part's BH data field to mark the particle has + * not yet been swallowed. + * + * @param p_data The #part's #black_holes_part_data structure. + */ +__attribute__((always_inline)) INLINE static void +black_holes_mark_part_as_not_swallowed(struct black_holes_part_data *p_data) { + + p_data->swallow_id = -1; + p_data->jet_id = -1; + p_data->adaf_id = -1; +} + +/** + * @brief Reset the particle-carried potential at the start of a time-step. + * + * @param p_data The #part's black hole data. + */ +__attribute__((always_inline)) INLINE static void black_holes_init_potential( + struct black_holes_part_data *p_data) { + p_data->potential = FLT_MAX; +} + +/** + * @brief Update a given #part's BH data field to mark the particle has + * having been been swallowed. + * + * @param p_data The #part's #black_holes_part_data structure. + */ +__attribute__((always_inline)) INLINE static void +black_holes_mark_part_as_swallowed(struct black_holes_part_data *p_data) { + + p_data->swallow_id = -2; +} + +/** + * @brief Return the ID of the BH that should swallow this #part. + * + * @param p_data The #part's #black_holes_part_data structure. + */ +__attribute__((always_inline)) INLINE static long long +black_holes_get_part_swallow_id(struct black_holes_part_data *p_data) { + + return p_data->swallow_id; +} + +/** + * @brief Update a given #bpart's BH data field to mark the particle has + * not yet been swallowed. + * + * @param p_data The #bpart's #black_holes_bpart_data structure. + */ +__attribute__((always_inline)) INLINE static void +black_holes_mark_bpart_as_not_swallowed(struct black_holes_bpart_data *p_data) { + + p_data->swallow_id = -1; + p_data->swallow_mass = 0.f; +} + +/** + * @brief Update a given #bpart's BH data field to mark the particle has + * having been been swallowed. + * + * @param p_data The #bpart's #black_holes_bpart_data structure. + */ +__attribute__((always_inline)) INLINE static void +black_holes_mark_bpart_as_merged(struct black_holes_bpart_data *p_data) { + + p_data->swallow_id = -2; + p_data->swallow_mass = -1.f; +} + +/** + * @brief Return the ID of the BH that should swallow this #bpart. + * + * @param p_data The #bpart's #black_holes_bpart_data structure. + */ +__attribute__((always_inline)) INLINE static long long +black_holes_get_bpart_swallow_id(struct black_holes_bpart_data *p_data) { + + return p_data->swallow_id; +} + +#endif /* SWIFT_BLACK_HOLES_STRUCT_OBSIDIAN_H */ diff --git a/src/black_holes_debug.h b/src/black_holes_debug.h index 1a00ff2c5d..a33d6f5b40 100644 --- a/src/black_holes_debug.h +++ b/src/black_holes_debug.h @@ -29,6 +29,8 @@ #include "./black_holes/EAGLE/black_holes_debug.h" #elif defined(BLACK_HOLES_SPIN_JET) #include "./black_holes/SPIN_JET/black_holes_debug.h" +#elif defined(BLACK_HOLES_OBSIDIAN) +#include "./black_holes/Obsidian/black_holes_debug.h" #else #error "Invalid choice of BH model" #endif diff --git a/src/black_holes_iact.h b/src/black_holes_iact.h index 01fcd73160..f31736de18 100644 --- a/src/black_holes_iact.h +++ b/src/black_holes_iact.h @@ -29,6 +29,8 @@ #include "./black_holes/EAGLE/black_holes_iact.h" #elif defined(BLACK_HOLES_SPIN_JET) #include "./black_holes/SPIN_JET/black_holes_iact.h" +#elif defined(BLACK_HOLES_OBSIDIAN) +#include "./black_holes/Obsidian/black_holes_iact.h" #else #error "Invalid choice of black hole model" #endif diff --git a/src/black_holes_io.h b/src/black_holes_io.h index 0507cef106..5f6737dff3 100644 --- a/src/black_holes_io.h +++ b/src/black_holes_io.h @@ -31,6 +31,8 @@ #include "./black_holes/EAGLE/black_holes_io.h" #elif defined(BLACK_HOLES_SPIN_JET) #include "./black_holes/SPIN_JET/black_holes_io.h" +#elif defined(BLACK_HOLES_OBSIDIAN) +#include "./black_holes/Obsidian/black_holes_io.h" #else #error "Invalid choice of BH model" #endif diff --git a/src/black_holes_properties.h b/src/black_holes_properties.h index c54db15a93..bb4488cdc9 100644 --- a/src/black_holes_properties.h +++ b/src/black_holes_properties.h @@ -29,6 +29,8 @@ #include "./black_holes/EAGLE/black_holes_properties.h" #elif defined(BLACK_HOLES_SPIN_JET) #include "./black_holes/SPIN_JET/black_holes_properties.h" +#elif defined(BLACK_HOLES_OBSIDIAN) +#include "./black_holes/Obsidian/black_holes_properties.h" #else #error "Invalid choice of black hole model" #endif diff --git a/src/black_holes_struct.h b/src/black_holes_struct.h index 09027e83f7..a52ec1f57b 100644 --- a/src/black_holes_struct.h +++ b/src/black_holes_struct.h @@ -37,6 +37,8 @@ #include "./black_holes/EAGLE/black_holes_struct.h" #elif defined(BLACK_HOLES_SPIN_JET) #include "./black_holes/SPIN_JET/black_holes_struct.h" +#elif defined(BLACK_HOLES_OBSIDIAN) +#include "./black_holes/Obsidian/black_holes_struct.h" #else #error "Invalid choice of black hole model." #endif diff --git a/src/chemistry.h b/src/chemistry.h index 388346f900..e418acd422 100644 --- a/src/chemistry.h +++ b/src/chemistry.h @@ -49,6 +49,9 @@ #elif defined(CHEMISTRY_EAGLE) #include "./chemistry/EAGLE/chemistry.h" #include "./chemistry/EAGLE/chemistry_iact.h" +#elif defined(CHEMISTRY_KIARA) +#include "./chemistry/KIARA/chemistry.h" +#include "./chemistry/KIARA/chemistry_iact.h" #else #error "Invalid choice of chemistry function." #endif diff --git a/src/chemistry/KIARA/chemistry.h b/src/chemistry/KIARA/chemistry.h new file mode 100644 index 0000000000..61675c27d3 --- /dev/null +++ b/src/chemistry/KIARA/chemistry.h @@ -0,0 +1,1486 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_CHEMISTRY_KIARA_H +#define SWIFT_CHEMISTRY_KIARA_H + +/** + * @file src/chemistry/KIARA/chemistry.h + * @brief Empty infrastructure for the cases without chemistry function + */ + +/* Some standard headers. */ +#include +#include +#include + +/* Local includes. */ +#include "chemistry_struct.h" +#include "error.h" +#include "hydro.h" +#include "parser.h" +#include "part.h" +#include "physical_constants.h" +#include "timestep_sync_part.h" +#include "units.h" + +/** + * @brief Initializes summed particle quantities for the firehose wind model + * + * This is called from chemistry_init_part. + * + * @param p The particle to act upon + * @param cd #chemistry_global_data containing chemistry informations. + */ +__attribute__((always_inline)) INLINE static void +firehose_init_ambient_quantities(struct part *restrict p, + const struct chemistry_global_data *cd) { + + struct chemistry_part_data *cpd = &p->chemistry_data; + + cpd->w_ambient = 0.f; + cpd->rho_ambient = 0.f; + cpd->u_ambient = 0.f; +} + +__attribute__((always_inline)) INLINE static void logger_windprops_printprops( + struct part *pi, const struct cosmology *cosmo, + const struct chemistry_global_data *cd) { + + /* Ignore COUPLED particles */ + if (!pi->decoupled) return; + +#ifdef CHEMISTRY_OUTPUT_FIREHOSE_LOG + /* Print wind properties */ + const float length_convert = cosmo->a * cd->length_to_kpc; + const float rho_convert = cosmo->a3_inv * cd->rho_to_n_cgs; + const float u_convert = + cosmo->a_factor_internal_energy / cd->temp_to_u_factor; + + message( + "FIREHOSE: z=%.3f id=%lld Mgal=%g h=%g T=%g rho=%g Rs=%g Z=%g tdel=%g " + "Ndec=%d rhoamb=%g Tamb=%g tcmix=%g\n", + cosmo->z, pi->id, + (pi->galaxy_data.gas_mass + pi->galaxy_data.stellar_mass) * + cd->mass_to_solar_mass, + pi->h * cosmo->a * cd->length_to_kpc, + hydro_get_drifted_comoving_internal_energy(pi) * u_convert, + pi->rho * rho_convert, pi->chemistry_data.radius_stream * length_convert, + pi->chemistry_data.metal_mass_fraction_total, + pi->chemistry_data.decoupling_delay_time * cd->time_to_Myr, + pi->chemistry_data.number_of_times_decoupled, + pi->chemistry_data.rho_ambient * cd->rho_to_n_cgs * cosmo->a3_inv, + pi->chemistry_data.u_ambient * cosmo->a_factor_internal_energy / + cd->temp_to_u_factor, + pi->cooling_data.mixing_layer_cool_time); +#endif + + return; +} + +/** + * @brief Finishes up ambient quantity calculation for the firehose wind model + * + * This is called from chemistry_end_density + * + * @param p The particle to act upon + * @param cd #chemistry_global_data containing chemistry informations. + */ +__attribute__((always_inline)) INLINE static void +firehose_end_ambient_quantities(struct part *restrict p, + struct xpart *restrict xp, + const struct chemistry_global_data *cd, + const struct cosmology *cosmo) { + + const float u_floor = cd->firehose_u_floor / cosmo->a_factor_internal_energy; + const float rho_max = + cd->firehose_ambient_rho_max * cosmo->a * cosmo->a * cosmo->a; + + /* No ambient properties for non-wind particles */ + if (p->decoupled) { + + /* Some smoothing length multiples. */ + const float h = p->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + +#ifdef FIREHOSE_DEBUG_CHECKS + message( + "FIREHOSE_prelim: id=%lld rhoamb=%g wamb=%g uamb=%g" + "h=%g h_inv=%g h_inv_dim=%g", + p->id, p->chemistry_data.rho_ambient, p->chemistry_data.w_ambient, + p->chemistry_data.u_ambient, h, h_inv, h_inv_dim); +#endif + + /* h_inv_dim can sometimes lead to rho_ambient -> 0 after normalization */ + p->chemistry_data.rho_ambient *= h_inv_dim; + + if (p->chemistry_data.rho_ambient > 0.f) { + p->chemistry_data.u_ambient *= h_inv_dim / p->chemistry_data.rho_ambient; + } else { + p->chemistry_data.rho_ambient = hydro_get_comoving_density(p); + p->chemistry_data.u_ambient = u_floor; + } + +#ifdef FIREHOSE_DEBUG_CHECKS + message("FIREHOSE_lim: id=%lld rhoamb=%g wamb=%g uamb=%g ufloor=%g\n", + p->id, p->chemistry_data.rho_ambient, p->chemistry_data.w_ambient, + p->chemistry_data.u_ambient, + cd->firehose_u_floor / cd->temp_to_u_factor); +#endif + } else { + /* Set them to reasonable values for non-wind, just in case */ + p->chemistry_data.rho_ambient = hydro_get_comoving_density(p); + p->chemistry_data.u_ambient = hydro_get_drifted_comoving_internal_energy(p); + } + + /* Limit ambient density to the user settings */ + p->chemistry_data.rho_ambient = min(p->chemistry_data.rho_ambient, rho_max); + p->chemistry_data.u_ambient = max(p->chemistry_data.u_ambient, u_floor); +#ifdef FIREHOSE_DEBUG_CHECKS + if (p->decoupled) { + message( + "FIREHOSE_AMB: z=%g id=%lld nH=%g nHamb=%g u=%g uamb=%g T=%g " + "Tamb=%g tcool=%g", + cosmo->z, p->id, p->rho * cd->rho_to_n_cgs * cosmo->a3_inv, + p->chemistry_data.rho_ambient * cd->rho_to_n_cgs * cosmo->a3_inv, + hydro_get_drifted_comoving_internal_energy(p), + p->chemistry_data.u_ambient, + hydro_get_drifted_comoving_internal_energy(p) * + cosmo->a_factor_internal_energy / cd->temp_to_u_factor, + p->chemistry_data.u_ambient * cosmo->a_factor_internal_energy / + cd->temp_to_u_factor, + p->cooling_data.mixing_layer_cool_time); + } +#endif + +#ifdef FIREHOSE_DEBUG_CHECKS + logger_windprops_printprops(p, cosmo, cd); +#endif +} + +/** + * @brief Return a string containing the name of a given #chemistry_element. + */ +__attribute__((always_inline)) INLINE static const char * +chemistry_get_element_name(enum chemistry_element elem) { + + static const char *chemistry_element_names[chemistry_element_count] = { + "Hydrogen", "Helium", "Carbon", "Nitrogen", "Oxygen", "Neon", + "Magnesium", "Silicon", "Sulfur", "Calcium", "Iron"}; + + return chemistry_element_names[elem]; +} + +/** + * @brief Prepares a particle for the smooth metal calculation. + * + * Zeroes all the relevant arrays in preparation for the sums taking place in + * the various smooth metallicity tasks + * + * @param p The particle to act upon + * @param cd #chemistry_global_data containing chemistry informations. + */ +__attribute__((always_inline)) INLINE static void chemistry_init_part( + struct part *restrict p, const struct chemistry_global_data *cd) { + + struct chemistry_part_data *cpd = &p->chemistry_data; + + /* Reset the shear tensor */ + for (int i = 0; i < 3; i++) { + cpd->shear_tensor[i][0] = 0.f; + cpd->shear_tensor[i][1] = 0.f; + cpd->shear_tensor[i][2] = 0.f; + + /* Accumulated velocity from the firehose model */ + cpd->dv[i] = 0.f; + } + + /* Reset the diffusion. */ + cpd->diffusion_coefficient = 0.f; + + /* Reset the changes for the accumulated properties */ + cpd->dZ_dt_total = 0.f; + cpd->du = 0.; + cpd->dm = 0.f; + cpd->dm_dust = 0.f; + for (int elem = 0; elem < chemistry_element_count; ++elem) { + cpd->dZ_dt[elem] = 0.f; + cpd->dm_Z[elem] = 0.f; + cpd->dm_dust_Z[elem] = 0.f; + } + +#if COOLING_GRACKLE_MODE >= 2 + cpd->local_sfr_density = 0.f; +#endif + + if (cd->use_firehose_wind_model) { + firehose_init_ambient_quantities(p, cd); + } +} + +/** + * @brief Finishes the smooth metal calculation. + * + * Multiplies the metallicity and number of neighbours by the + * appropiate constants and add the self-contribution term. + * + * This function requires the #hydro_end_density to have been called. + * + * @param p The particle to act upon. + * @param cd #chemistry_global_data containing chemistry informations. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void chemistry_end_density( + struct part *restrict p, struct xpart *restrict xp, + const struct chemistry_global_data *cd, const struct cosmology *cosmo) { + + /* Some smoothing length multiples. */ + const float h = p->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + + struct chemistry_part_data *cpd = &p->chemistry_data; + + /* If diffusion is on, finish up shear tensor & particle's diffusion coeff */ + if (cd->diffusion_flag == 1 && cd->C_Smagorinsky > 0.f) { + const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */ + const float rho = hydro_get_comoving_density(p); + + /* convert the shear factor into physical */ + const float factor_shear = h_inv_dim_plus_one * cosmo->a2_inv / rho; + for (int k = 0; k < 3; k++) { + cpd->shear_tensor[k][0] *= factor_shear; + cpd->shear_tensor[k][1] *= factor_shear; + cpd->shear_tensor[k][2] *= factor_shear; + } + + /* Compute the trace over 3 and add the hubble flow. */ + float trace_3 = 0.f; + for (int i = 0; i < 3; i++) { + cpd->shear_tensor[i][i] += cosmo->H; + trace_3 += cpd->shear_tensor[i][i]; + } + trace_3 /= 3.f; + + float shear_tensor[3][3] = {{0.f}}; + for (int i = 0; i < 3; i++) { + /* Make the tensor symmetric. */ + float avg = 0.5f * (cpd->shear_tensor[i][0] + cpd->shear_tensor[0][i]); + shear_tensor[i][0] = avg; + shear_tensor[0][i] = avg; + + avg = 0.5f * (cpd->shear_tensor[i][1] + cpd->shear_tensor[1][i]); + shear_tensor[i][1] = avg; + shear_tensor[1][i] = avg; + + avg = 0.5f * (cpd->shear_tensor[i][2] + cpd->shear_tensor[2][i]); + shear_tensor[i][2] = avg; + shear_tensor[2][i] = avg; + + /* Remove the trace. */ + shear_tensor[i][i] -= trace_3; + } + + /* Compute the norm. */ + float velocity_gradient_norm = 0.f; + for (int i = 0; i < 3; i++) { + velocity_gradient_norm += shear_tensor[i][0] * shear_tensor[i][0]; + velocity_gradient_norm += shear_tensor[i][1] * shear_tensor[i][1]; + velocity_gradient_norm += shear_tensor[i][2] * shear_tensor[i][2]; + + /* Copy the final values into the particle quantity */ + cpd->shear_tensor[i][0] = shear_tensor[i][0]; + cpd->shear_tensor[i][1] = shear_tensor[i][1]; + cpd->shear_tensor[i][2] = shear_tensor[i][2]; + } + + velocity_gradient_norm = sqrtf(velocity_gradient_norm); + + /* Never set D for wind, or ISM particles */ + if (!(p->decoupled) && !(p->cooling_data.subgrid_temp > 0.f)) { + + /* Rennehan: Limit to maximum resolvable velocity scale */ + const float v_phys = + sqrtf(p->v[0] * p->v[0] + p->v[1] * p->v[1] + p->v[2] * p->v[2]) * + cosmo->a_inv; + const float h_phys = cosmo->a * p->h * kernel_gamma; + const float vel_norm_phys_max = 0.5f * v_phys / h_phys; + if (velocity_gradient_norm > vel_norm_phys_max) { + velocity_gradient_norm = vel_norm_phys_max; + } + + /* Compute the diffusion coefficient in physical coordinates. + * The norm is already in physical coordinates. + * kernel_gamma is necessary (see Rennehan 2021) + */ + const float rho_phys = hydro_get_physical_density(p, cosmo); + const float smag_length_scale = cd->C_Smagorinsky * h_phys; + const float D_phys = rho_phys * smag_length_scale * smag_length_scale * + velocity_gradient_norm; + + cpd->diffusion_coefficient = D_phys; + } + } /* end Smagorinsky diffusion */ + +#if COOLING_GRACKLE_MODE >= 2 + /* Finish SFR density calculation */ + cpd->local_sfr_density *= h_inv_dim; +#endif + + if (cd->use_firehose_wind_model) { + firehose_end_ambient_quantities(p, xp, cd, cosmo); + } +} + +/** + * @brief Sets all particle fields to sensible values when the #part has 0 ngbs. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + * @param cd #chemistry_global_data containing chemistry informations. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void +chemistry_part_has_no_neighbours(struct part *restrict p, + struct xpart *restrict xp, + const struct chemistry_global_data *cd, + const struct cosmology *cosmo) { + + /* Just make all the smoothed fields default to the un-smoothed values */ + struct chemistry_part_data *cpd = &p->chemistry_data; + /* Reset the shear tensor */ + for (int i = 0; i < 3; i++) { + cpd->shear_tensor[i][0] = 0.f; + cpd->shear_tensor[i][1] = 0.f; + cpd->shear_tensor[i][2] = 0.f; + } + + /* Reset the diffusion. */ + cpd->diffusion_coefficient = 0.f; + + /* Reset the change in metallicity */ + cpd->dZ_dt_total = 0.f; + for (int elem = 0; elem < chemistry_element_count; ++elem) { + cpd->dZ_dt[elem] = 0.f; + } + +#if COOLING_GRACKLE_MODE >= 2 + /* If there is no nearby SF, set to zero */ + cpd->local_sfr_density = 0.f; +#endif +} + +/** + * @brief Sets the chemistry properties of the (x-)particles to a valid start + * state. + * + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param cosmo The current cosmological model. + * @param data The global chemistry information. + * @param p Pointer to the particle data. + * @param xp Pointer to the extended particle data. + */ +__attribute__((always_inline)) INLINE static void chemistry_first_init_part( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct chemistry_global_data *data, struct part *restrict p, + struct xpart *restrict xp) { + + /* Initialize mass fractions for total metals and each metal individually */ + if (data->initial_metal_mass_fraction_total != -1) { + p->chemistry_data.metal_mass_fraction_total = + data->initial_metal_mass_fraction_total; + + for (int elem = 0; elem < chemistry_element_count; ++elem) { + p->chemistry_data.metal_mass_fraction[elem] = + data->initial_metal_mass_fraction[elem]; + } + } + chemistry_init_part(p, data); + + if (data->use_firehose_wind_model) { + firehose_init_ambient_quantities(p, data); + } +} + +/** + * @brief Sets the chemistry properties of the sparticles to a valid start + * state. + * + * @param data The global chemistry information. + * @param sp Pointer to the sparticle data. + */ +__attribute__((always_inline)) INLINE static void chemistry_first_init_spart( + const struct chemistry_global_data *data, struct spart *restrict sp) { + + /* Initialize mass fractions for total metals and each metal individually */ + if (data->initial_metal_mass_fraction_total != -1) { + sp->chemistry_data.metal_mass_fraction_total = + data->initial_metal_mass_fraction_total; + + for (int elem = 0; elem < chemistry_element_count; ++elem) + sp->chemistry_data.metal_mass_fraction[elem] = + data->initial_metal_mass_fraction[elem]; + } +} + +/** + * @brief Sets the chemistry properties of the sink particles to a valid start + * state. + * + * @param data The global chemistry information. + * @param sink Pointer to the sink particle data. + * Required by space_first_init.c + */ +__attribute__((always_inline)) INLINE static void chemistry_first_init_sink( + const struct chemistry_global_data *data, struct sink *restrict sink) {} + +/** + * @brief Initialises the chemistry properties. + * + * @param parameter_file The parsed parameter file. + * @param us The current internal system of units. + * @param phys_const The physical constants in internal units. + * @param data The properties to initialise. + */ +static INLINE void chemistry_init_backend(struct swift_params *parameter_file, + const struct unit_system *us, + const struct phys_const *phys_const, + struct chemistry_global_data *data) { + + /* Set some useful unit conversions */ + const double Msun_cgs = phys_const->const_solar_mass * + units_cgs_conversion_factor(us, UNIT_CONV_MASS); + const double unit_mass_cgs = units_cgs_conversion_factor(us, UNIT_CONV_MASS); + data->mass_to_solar_mass = unit_mass_cgs / Msun_cgs; + data->temp_to_u_factor = + phys_const->const_boltzmann_k / + (hydro_gamma_minus_one * phys_const->const_proton_mass * + units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE)); + data->T_to_internal = + 1. / units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + const double X_H = 0.752; + data->rho_to_n_cgs = + (X_H / phys_const->const_proton_mass) * + units_cgs_conversion_factor(us, UNIT_CONV_NUMBER_DENSITY); + data->kms_to_internal = + 1.0e5 / units_cgs_conversion_factor(us, UNIT_CONV_SPEED); + data->time_to_Myr = units_cgs_conversion_factor(us, UNIT_CONV_TIME) / + (1.e6 * 365.25 * 24. * 60. * 60.); + data->length_to_kpc = + units_cgs_conversion_factor(us, UNIT_CONV_LENGTH) / 3.08567758e21; + + /* Is metal diffusion turned on? */ + data->diffusion_flag = + parser_get_param_int(parameter_file, "KIARAChemistry:diffusion_on"); + + /* Read the diffusion coefficient */ + data->C_Smagorinsky = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:diffusion_coefficient", 0.23f); + + /* Time-step restriction to <= 0.15*rho*h^2 / D from + Parshikov & Medin 2002 equation 41 */ + data->diffusion_beta = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:diffusion_beta", 0.1f); + if (data->diffusion_beta < 0.f || data->diffusion_beta > 0.1f) { + error("diffusion_beta must be >= 0 and <= 0.1"); + } + + data->time_step_min = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:minimum_timestep_Myr", 0.1f); + data->time_step_min /= data->time_to_Myr; + if (data->time_step_min < 0.f) { + error("time_step_min must be > 0"); + } + + data->max_fractional_Z_transfer = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:max_fractional_Z_transfer", 0.25f); + if (data->max_fractional_Z_transfer < 0.f || + data->max_fractional_Z_transfer > 1.f) { + error("diffusion_beta must be >= 0 and <= 1"); + } + + /* Are we using the firehose wind model? */ + data->use_firehose_wind_model = parser_get_opt_param_int( + parameter_file, "KIARAChemistry:use_firehose_wind_model", 0); + + if (data->use_firehose_wind_model) { + /* Firehose model parameters */ + data->firehose_ambient_rho_max = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:firehose_nh_ambient_max_cgs", 0.1f); + data->firehose_ambient_rho_max /= data->rho_to_n_cgs; + + data->firehose_u_floor = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:firehose_temp_floor", 1.e4f); + data->firehose_u_floor *= data->temp_to_u_factor * data->T_to_internal; + + /* Firehose recoupling parameters */ + data->firehose_recoupling_mach = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:firehose_recoupling_mach", 0.5f); + + data->firehose_recoupling_u_factor = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:firehose_recoupling_u_factor", 0.5f); + + data->firehose_recoupling_fmix = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:firehose_recoupling_fmix", 0.9f); + + data->firehose_max_velocity = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:firehose_max_velocity", 2000.f); + data->firehose_max_velocity *= data->kms_to_internal; + + data->firehose_max_fmix_per_step = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:firehose_max_fmix_per_step", 0.1f); + } + + /* Read the total metallicity */ + data->initial_metal_mass_fraction_total = parser_get_opt_param_float( + parameter_file, "KIARAChemistry:init_abundance_metal", -1); + + if (data->initial_metal_mass_fraction_total != -1) { + /* Read the individual mass fractions */ + for (int elem = 0; elem < chemistry_element_count; ++elem) { + char buffer[50]; + sprintf(buffer, "KIARAChemistry:init_abundance_%s", + chemistry_get_element_name((enum chemistry_element)elem)); + + data->initial_metal_mass_fraction[elem] = + parser_get_param_float(parameter_file, buffer); + } + + /* Let's check that things make sense (broadly) */ + + /* H + He + Z should be ~1 */ + float total_frac = data->initial_metal_mass_fraction[chemistry_element_H] + + data->initial_metal_mass_fraction[chemistry_element_He] + + data->initial_metal_mass_fraction_total; + + if (total_frac < 0.98 || total_frac > 1.02) + error("The abundances provided seem odd! H + He + Z = %f =/= 1.", + total_frac); + + /* Sum of metal elements should be <= Z */ + total_frac = 0.f; + for (int elem = 0; elem < chemistry_element_count; ++elem) { + if (elem != chemistry_element_H && elem != chemistry_element_He) { + total_frac += data->initial_metal_mass_fraction[elem]; + } + } + + if (!data->diffusion_flag) { + if (total_frac > 1.02 * data->initial_metal_mass_fraction_total) { + error( + "The abundances provided seem odd! \\sum metal elements (%f) > Z " + "(%f)", + total_frac, data->initial_metal_mass_fraction_total); + } + } else { + /* If diffusion is on, need a metallicity floor so reset Z total */ + if (total_frac > 1.02 * data->initial_metal_mass_fraction_total) { + warning("Resetting total Z to the sum of all available metals."); + data->initial_metal_mass_fraction_total = total_frac; + + /* H + He + Z should be ~1 */ + float total_frac_check = + data->initial_metal_mass_fraction[chemistry_element_H] + + data->initial_metal_mass_fraction[chemistry_element_He] + + data->initial_metal_mass_fraction_total; + + if (total_frac_check < 0.98 || total_frac_check > 1.02) { + error( + "After resetting, the abundances provided seem odd! " + "H + He + Z = %f =/= 1.", + total_frac_check); + } + } + } + + /* Sum of all elements should be <= 1 */ + total_frac = 0.f; + for (int elem = 0; elem < chemistry_element_count; ++elem) { + total_frac += data->initial_metal_mass_fraction[elem]; + } + + if (total_frac > 1.02) { + error("The abundances provided seem odd! \\sum elements (%f) > 1", + total_frac); + } + } +} + +/** + * @brief Prints the properties of the chemistry model to stdout. + * + * @brief The #chemistry_global_data containing information about the current + * model. + */ +static INLINE void chemistry_print_backend( + const struct chemistry_global_data *data) { + + if (data->use_firehose_wind_model) { + if (data->diffusion_flag) { + message( + "Chemistry model is 'KIARA' tracking %d elements with the " + "firehose wind model and metal diffusion.", + chemistry_element_count); + } else { + message( + "Chemistry model is 'KIARA' tracking %d elements with " + "the firehose wind model.", + chemistry_element_count); + } + } else { + if (data->diffusion_flag) { + message( + "Chemistry model is 'KIARA' tracking %d elements with " + " metal diffusion on.", + chemistry_element_count); + } else { + message("Chemistry model is 'KIARA' tracking %d elements.", + chemistry_element_count); + } + } +} + +/** + * @brief Check recoupling criterion for firehose stream particle . + * Returns negative value if it should recouple. + * Actual recoupling is done in feedback.h. + * + * @param pi Wind particle (not updated). + * @param Mach Stream Mach number vs ambient + * @param r_stream Current radius of stream + * @param cd #chemistry_global_data containing chemistry information. + * + */ +__attribute__((always_inline)) INLINE static float +firehose_recoupling_criterion(struct part *p, const float Mach, + const float r_stream, + const struct chemistry_global_data *cd) { + + if (!cd->use_firehose_wind_model) return 0.f; + + float rs = r_stream; + const double u = hydro_get_drifted_comoving_internal_energy(p); + const double u_max = max(u, p->chemistry_data.u_ambient); + const double u_diff = fabs(u - p->chemistry_data.u_ambient) / u_max; + if (Mach < cd->firehose_recoupling_mach && + u_diff < cd->firehose_recoupling_u_factor) + rs = -1.f; + + const float exchanged_mass_frac = + p->chemistry_data.exchanged_mass / hydro_get_mass(p); + + if (exchanged_mass_frac > cd->firehose_recoupling_fmix) rs = -1.f; + if (r_stream == 0.f) rs = -1.f; + + return rs; +} + +/** + * @brief Finishes the gradient calculation. + * + * Nothing to do here. + * + * @param p The particle to act upon. + * @param cd The global properties of the chemistry scheme. + */ +__attribute__((always_inline)) INLINE static void chemistry_end_gradient( + struct part *p, const struct chemistry_global_data *cd) {} + +/** + * @brief Prepare a particle for the force calculation. + * + * Nothing to do here. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + * @param cosmo The current cosmological model. + * @param dt_alpha The time-step used to evolve non-cosmological quantities such + * as the artificial viscosity. + * @param dt_therm The time-step used to evolve hydrodynamical quantities. + * @param cd The global properties of the chemistry scheme. + */ +__attribute__((always_inline)) INLINE static void chemistry_prepare_force( + struct part *restrict p, struct xpart *restrict xp, + const struct cosmology *cosmo, const float dt_alpha, const float dt_therm, + const struct chemistry_global_data *cd) {} + +/** + * @brief Updates to the chemistry data after the hydro force loop. + * + * Finish off the diffusion by actually exchanging the metals + * + * @param p The particle to act upon. + * @param cosmo The current cosmological model. + * @param with_cosmology Are we running with the cosmology? + * @param time Current time of the simulation. + * @param dt Time step (in physical units). + */ +__attribute__((always_inline)) INLINE static void chemistry_end_force( + struct part *restrict p, struct xpart *xp, const struct cosmology *cosmo, + const int with_cosmology, const double time, const double dt, + const struct chemistry_global_data *cd) { + + if (dt == 0.) return; + + struct chemistry_part_data *ch = &p->chemistry_data; + + const float h_inv = 1.f / p->h; + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + /* Missing factors in iact. */ + const float factor = h_inv_dim * h_inv; + + if (cd->use_firehose_wind_model && ch->dm > 0.f) { + struct cooling_part_data *co = &p->cooling_data; + + const float m = hydro_get_mass(p); + const float v = + sqrtf(p->v[0] * p->v[0] + p->v[1] * p->v[1] + + p->v[2] * p->v[2]); + const float dv = sqrtf(ch->dv[0] * ch->dv[0] + ch->dv[1] * ch->dv[1] + + ch->dv[2] * ch->dv[2]); + float dv_phys = dv * cosmo->a_inv; + + /* Use this to limit energy change in v^2 and u */ + float alpha = 1.f; + + if (dv >= FIREHOSE_EPSILON_TOLERANCE * v) { + const float v_new[3] = {p->v[0] + ch->dv[0], + p->v[1] + ch->dv[1], + p->v[2] + ch->dv[2]}; + const float v_new_norm = sqrtf(v_new[0] * v_new[0] + v_new[1] * v_new[1] + + v_new[2] * v_new[2]); + + /* Apply a kinetic energy limiter */ + const double v2 = v * v; + double dv2 = dv * dv; + const double v_new2 = v_new_norm * v_new_norm; + const double KE_ratio = (v > 0.) ? v_new2 / v2 : 1.; + const int KE_low_flag = (KE_ratio < FIREHOSE_COOLLIM); + const int KE_high_flag = (KE_ratio > FIREHOSE_HEATLIM); + const int KE_out_of_bounds = KE_low_flag || KE_high_flag; + + if (KE_out_of_bounds && dv2 > 0.) { + /* Solve the same scaling equation, just with a different target */ + const float target_KE_factor = + (KE_low_flag) ? FIREHOSE_COOLLIM : FIREHOSE_HEATLIM; + + const float v_dot_dv = p->v[0] * ch->dv[0] + + p->v[1] * ch->dv[1] + + p->v[2] * ch->dv[2]; + + /* How to scale all components equally? Solve quadratic: + * v^2 + 2 * alpha * v * dv + alpha^2 * dv^2 = target_KE_factor * v^2 + * + * Or equivalently: + * A * alpha^2 + B * alpha + C = 0 + * + * where A = 1 + * B = 2 * (v * dv) / (dv^2)) + * C = (v / dv)^2 * (1 - target_KE_factor) + */ + const float B = 2.f * v_dot_dv / dv2; + const float C = (v2 / dv2) * (1.f - target_KE_factor); + const float discriminant = B * B - 4.f * C; + /* For logging */ + const double u_drift = hydro_get_drifted_comoving_internal_energy(p); + + if (discriminant >= 0.) { + const float alpha1 = (-B - sqrtf(discriminant)) / 2.f; + const float alpha2 = (-B + sqrtf(discriminant)) / 2.f; + + /* Minimize alpha1 and alpha2 between (0, 1) */ + if (alpha1 > 0.f && alpha1 < 1.f) alpha = alpha1; + if (alpha2 < alpha && alpha2 > 0.f) alpha = alpha2; + + /* If there is predicted to be no change, just cancel the operation */ + if (alpha == 1.f) alpha = 0.f; + + ch->dv[0] *= alpha; + ch->dv[1] *= alpha; + ch->dv[2] *= alpha; + + message( + "FIREHOSE_KE_LIMIT p=%lld alpha=%.4g KE_ratio=%.4g v=%.4g " + "dv=%g m=%g dm=%g u=%g du=%g " + "dv[0]=%g dv[1]=%g dv[2]=%g " + "v[0]=%g v[1]=%g v[2]=%g", + p->id, alpha, KE_ratio, v, dv, m, ch->dm, u_drift, ch->du, + ch->dv[0], ch->dv[1], ch->dv[2], p->v[0], p->v[1], p->v[2]); + } else { + ch->dv[0] = 0.f; + ch->dv[1] = 0.f; + ch->dv[2] = 0.f; + + message( + "FIREHOSE_KE_LIMIT p=%lld alpha=INVALID KE_ratio=%.4g v=%.4g " + "dv=%g m=%g dm=%g u=%g du=%g " + "dv[0]=%g dv[1]=%g dv[2]=%g " + "v[0]=%g v[1]=%g v[2]=%g", + p->id, KE_ratio, v, dv, m, ch->dm, u_drift, ch->du, + ch->dv[0], ch->dv[1], ch->dv[2], p->v[0], p->v[1], p->v[2]); + } + + /* Recompute the new updated limited values to set v_sig */ + dv2 = ch->dv[0] * ch->dv[0] + ch->dv[1] * ch->dv[1] + + ch->dv[2] * ch->dv[2]; + dv_phys = sqrtf(dv2) * cosmo->a_inv; + } + } else { + /* Cancel everything if the kick is so small it doesn't matter */ + dv_phys = 0.f; + } + + /* Make sure there were also no problems with the KE of the particle. + * Skip all exchanges if there was! */ + if (dv_phys > 0.f) { + + hydro_set_v_sig_based_on_velocity_kick(p, cosmo, dv_phys); + + p->v[0] += ch->dv[0]; + p->v[1] += ch->dv[1]; + p->v[2] += ch->dv[2]; + + /* Grab the comoving internal energy at last kick */ + const double u = hydro_get_drifted_comoving_internal_energy(p); + + /* Reset du based on previously calculated alpha limiter */ + ch->du *= alpha * alpha; + + double u_new = u + ch->du; + const double u_floor = + cd->firehose_u_floor / cosmo->a_factor_internal_energy; + if (u_new < u_floor) { + u_new = u_floor; + ch->du = u_new - u; + } + + /* Ignore small changes to the internal energy */ + const double u_eps = fabs(ch->du) / u; + + if (u_eps > FIREHOSE_EPSILON_TOLERANCE) { +#ifdef FIREHOSE_DEBUG_CHECKS + if (!isfinite(u) || !isfinite(ch->du)) { + message("FIREHOSE_BAD p=%lld u=%g du=%g dv_phys=%g m=%g dm=%g", p->id, + u, ch->du, dv_phys, m, ch->dm); + } +#endif + + const double energy_frac = (u > 0.) ? u_new / u : 1.; + if (energy_frac > FIREHOSE_HEATLIM) u_new = FIREHOSE_HEATLIM * u; + if (energy_frac < FIREHOSE_COOLLIM) u_new = FIREHOSE_COOLLIM * u; + + /* If it's in subgrid ISM mode, use additional heat to + * lower ISM cold fraction */ + const int firehose_add_heat_to_ISM = + (p->cooling_data.subgrid_temp > 0.f && + p->cooling_data.subgrid_fcold > 0.f && ch->du > 0.); + + if (firehose_add_heat_to_ISM) { + + /* 0.8125 is mu for a fully neutral gas with XH=0.75; + * approximate but good enough */ + const double T_conv = + cd->temp_to_u_factor / cosmo->a_factor_internal_energy; + const double u_cold = 0.8125 * p->cooling_data.subgrid_temp * T_conv; + + const double delta_u = u - u_cold; + double f_evap = 0.; + + if (delta_u > FIREHOSE_EPSILON_TOLERANCE * u) { + f_evap = ch->du / delta_u; + f_evap = min(f_evap, 1.0); + } else { + f_evap = 1.0; + } + + /* Clip values in case of overflow */ + if (f_evap > 0.) { + + p->cooling_data.subgrid_fcold *= 1. - f_evap; + + /* Make sure any extra heat goes into the particle */ + const double u_remaining = ch->du - f_evap * delta_u; + u_new = u + max(u_remaining, 0.); + + if (p->cooling_data.subgrid_fcold <= 0.f) { + p->cooling_data.subgrid_temp = 0.f; + p->cooling_data.subgrid_dens = + hydro_get_physical_density(p, cosmo); + p->cooling_data.subgrid_fcold = 0.f; + } + } + } + + double u_phys = u_new * cosmo->a_factor_internal_energy; + + hydro_set_physical_internal_energy(p, xp, cosmo, u_phys); + hydro_set_drifted_physical_internal_energy(p, cosmo, NULL, u_phys); + } else { + ch->du = 0.; + } + + /* Check dust change */ + float dust_eps = 0.f; + + /* Check dust change */ + if (co->dust_mass > 0.f) { + dust_eps = fabs(ch->dm_dust) / co->dust_mass; + } + + float new_dust_mass = co->dust_mass; + if (dust_eps >= FIREHOSE_EPSILON_TOLERANCE) new_dust_mass += ch->dm_dust; + + ch->metal_mass_fraction_total = 0.f; + for (int elem = 0; elem < chemistry_element_count; ++elem) { + const float old_mass_Z = ch->metal_mass_fraction[elem] * m; + if (old_mass_Z > 0.f) { + const float Z_eps = fabs(ch->dm_Z[elem]) / old_mass_Z; + + if (Z_eps >= FIREHOSE_EPSILON_TOLERANCE) { + ch->metal_mass_fraction[elem] = (old_mass_Z + ch->dm_Z[elem]) / m; + } + } + + /* Recompute Z */ + if (elem != chemistry_element_H && elem != chemistry_element_He) { + ch->metal_mass_fraction_total += ch->metal_mass_fraction[elem]; + } + + if (dust_eps >= FIREHOSE_EPSILON_TOLERANCE) { + const float old_dust_mass_Z = + co->dust_mass_fraction[elem] * co->dust_mass; + co->dust_mass_fraction[elem] = + (old_dust_mass_Z + ch->dm_dust_Z[elem]) / new_dust_mass; + } + } + + /* Set the new dust mass from the exchange */ + co->dust_mass = (new_dust_mass > 0.f) ? new_dust_mass : 0.f; + if (co->dust_mass <= 0.f) { + for (int elem = 0; elem < chemistry_element_count; ++elem) { + co->dust_mass_fraction[elem] = 0.f; + } + + co->dust_mass = 0.f; + } + + /* Make sure that X + Y + Z = 1 */ + const float Y_He = ch->metal_mass_fraction[chemistry_element_He]; + ch->metal_mass_fraction[chemistry_element_H] = + 1.f - Y_He - ch->metal_mass_fraction_total; + + /* Make sure H fraction does not go out of bounds */ + if (ch->metal_mass_fraction[chemistry_element_H] > 1.f || + ch->metal_mass_fraction[chemistry_element_H] < 0.f) { + for (int i = chemistry_element_H; i < chemistry_element_count; i++) { + warning("\telem[%d] is %g", i, ch->metal_mass_fraction[i]); + } + + error( + "Hydrogen fraction exeeds unity or is negative for" + " particle id=%lld due to firehose exchange", + p->id); + } + + /* Update stream radius for stream particle */ + if (p->decoupled) { + const float stream_growth_factor = 1.f + ch->dm / hydro_get_mass(p); + ch->radius_stream *= sqrtf(stream_growth_factor); + + const double c_s = + sqrt(ch->u_ambient * hydro_gamma * hydro_gamma_minus_one); + const float Mach = dv_phys / (cosmo->a_factor_sound_speed * c_s); + ch->radius_stream = + firehose_recoupling_criterion(p, Mach, ch->radius_stream, cd); + } + } + } + + /* Are we a decoupled wind? Skip diffusion. */ + if (p->decoupled) return; + + /* Check if we are hypersonic*/ + /* Reset dZ_dt and return? */ + bool reset_time_derivatives = false; + + /* Add diffused metals to particle */ + const float dZ_tot = ch->dZ_dt_total * dt * factor; + const float new_metal_mass_fraction_total = + ch->metal_mass_fraction_total + dZ_tot; + if (ch->metal_mass_fraction_total > 0.f) { + const float abs_fractional_change = + fabs(dZ_tot) / ch->metal_mass_fraction_total; + /* Check if dZ is bigger than 1/4 of the Z */ + if (abs_fractional_change > cd->max_fractional_Z_transfer) { + reset_time_derivatives = true; + } + } + + /* Handle edge case where diffusion leads to negative metallicity */ + if (new_metal_mass_fraction_total < 0.f) { + warning( + "Metal diffusion led to negative metallicity!\n" + "\tpid=%lld\n\tdt=%g\n\tZ=%g\n\tdZ_dt=%g\n" + "\tdZtot=%g\n\tZnewtot=%g\n\tfactor=%g", + p->id, dt, ch->metal_mass_fraction_total, ch->dZ_dt_total, dZ_tot, + new_metal_mass_fraction_total, factor); + reset_time_derivatives = true; + } + + /* Handle edge case where diffusion leads to super-unity metallicity */ + if (new_metal_mass_fraction_total > 1.f) { + warning( + "Metal diffusion led to metal fractions above unity!\n" + "pid=%lld\n\tdt=%g\n\tZ=%g\n\tdZ_dt=%g\n" + "\tdZtot=%g\n\tZnewtot=%g\n\tfactor=%g", + p->id, dt, ch->metal_mass_fraction_total, ch->dZ_dt_total, dZ_tot, + new_metal_mass_fraction_total, factor); + reset_time_derivatives = true; + } + + /* Add individual element contributions from diffusion */ + for (int elem = 0; elem < chemistry_element_count; elem++) { + const float dZ = ch->dZ_dt[elem] * dt * factor; + const float new_metal_fraction_elem = ch->metal_mass_fraction[elem] + dZ; + + if (ch->metal_mass_fraction[elem] > 0.f) { + const float abs_fractional_change = + fabs(dZ) / ch->metal_mass_fraction[elem]; + if (abs_fractional_change > cd->max_fractional_Z_transfer) { + reset_time_derivatives = true; + } + } + + /* Make sure that the metallicity is 0 <= x <= 1 */ + if (new_metal_fraction_elem < 0.f) { + warning( + "Z[elem] < 0! pid=%lld, dt=%g, elem=%d, Z=%g, dZ_dt=%g, dZ=%g, " + "dZtot=%g Ztot=%g Zdust=%g.", + p->id, dt, elem, ch->metal_mass_fraction[elem], ch->dZ_dt[elem], dZ, + dZ_tot, ch->metal_mass_fraction_total, + p->cooling_data.dust_mass_fraction[elem]); + reset_time_derivatives = true; + } + + if (new_metal_fraction_elem > 1.f) { + warning( + "Z[elem] > 1! pid=%lld, dt=%g, elem=%d, Z=%g, dZ_dt=%g, " + "dZ=%g, dZtot=%g Ztot=%g.", + p->id, dt, elem, ch->metal_mass_fraction[elem], ch->dZ_dt[elem], dZ, + dZ_tot, ch->metal_mass_fraction_total); + reset_time_derivatives = true; + } + } + + /* Found weird dZ_dt values so we should reset everything and exit */ + if (reset_time_derivatives) { + ch->dZ_dt_total = 0.f; + for (int elem = 0; elem < chemistry_element_count; elem++) { + ch->dZ_dt[elem] = 0.f; + } + return; + } else { +#if COOLING_GRACKLE_MODE >= 2 + if (ch->metal_mass_fraction_total > 0.f) { + /* Add diffused dust to particle, in proportion to added metals */ + p->cooling_data.dust_mass *= 1.f + dZ_tot / ch->metal_mass_fraction_total; + } +#endif + + /* Reset the total metallicity Z */ + ch->metal_mass_fraction_total = new_metal_mass_fraction_total; + + /* Add individual element contributions from diffusion */ + for (int elem = 0; elem < chemistry_element_count; elem++) { + const float dZ = ch->dZ_dt[elem] * dt * factor; + const float new_metal_fraction_elem = ch->metal_mass_fraction[elem] + dZ; + +#if COOLING_GRACKLE_MODE >= 2 + /* Add diffused dust to particle, in proportion to added metals */ + if (ch->metal_mass_fraction[elem] > 0.f) { + p->cooling_data.dust_mass_fraction[elem] *= + 1.f + dZ / ch->metal_mass_fraction[elem]; + } +#endif + + /* Treating Z like a passive scalar */ + ch->metal_mass_fraction[elem] = new_metal_fraction_elem; + } + } + + /* Make sure that X + Y + Z = 1 */ + const float Y_He = ch->metal_mass_fraction[chemistry_element_He]; + ch->metal_mass_fraction[chemistry_element_H] = + 1.f - Y_He - ch->metal_mass_fraction_total; + + /* Make sure H fraction does not go out of bounds */ + if (ch->metal_mass_fraction[chemistry_element_H] > 1.f || + ch->metal_mass_fraction[chemistry_element_H] < 0.f) { + for (int i = chemistry_element_H; i < chemistry_element_count; i++) { + warning("\telem[%d] is %g", i, ch->metal_mass_fraction[i]); + } + + error( + "Hydrogen fraction exeeds unity or is negative for" + " particle id=%lld due to metal diffusion", + p->id); + } +} + +/** + * @brief Computes the chemistry-related time-step constraint. + * + * Only constraint in KIARA is the diffusion time-step. + * + * @param phys_const The physical constants in internal units. + * @param cosmo The current cosmological model. + * @param us The internal system of units. + * @param hydro_props The properties of the hydro scheme. + * @param cd The global properties of the chemistry scheme. + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float chemistry_timestep( + const struct phys_const *restrict phys_const, + const struct cosmology *restrict cosmo, + const struct unit_system *restrict us, + const struct hydro_props *hydro_props, + const struct chemistry_global_data *cd, const struct part *restrict p) { + + float dt_chem = FLT_MAX; + if (cd->diffusion_flag) { + if (p->chemistry_data.diffusion_coefficient > 0.f) { + const struct chemistry_part_data *ch = &p->chemistry_data; + + /* Parshikov & Medin 2002 equation 41 */ + const float h_phys = p->h * cosmo->a * kernel_gamma; + const float D_phys = ch->diffusion_coefficient; + const float rho_phys = hydro_get_physical_density(p, cosmo); + dt_chem = cd->diffusion_beta * rho_phys * h_phys * h_phys / D_phys; + if (dt_chem < cd->time_step_min) { + message( + "Warning! dZ_dt timestep low: id=%lld (%g Myr) is below " + "time_step_min (%g Myr).", + p->id, dt_chem * cd->time_to_Myr, + cd->time_step_min * cd->time_to_Myr); + } + + dt_chem = max(dt_chem, cd->time_step_min); + } + } + + if (cd->use_firehose_wind_model) { + /* About-to-recouple winds need the hydro time-step. */ + if (p->decoupled == 2) { + const float CFL_condition = hydro_props->CFL_condition; + const float h = kernel_gamma * cosmo->a * p->h; + const float v_sig = 2.f * hydro_get_physical_soundspeed(p, cosmo); + const float dt_cfl = 2.f * CFL_condition * h / v_sig; + + /* The actual minimum time-step is handled in the runner file. */ + dt_chem = min(dt_chem, dt_cfl); + } + } + + return dt_chem; +} + +/** + * @brief Initialise the chemistry properties of a black hole with + * the chemistry properties of the gas it is born from. + * + * Black holes don't store fractions so we need to use element masses. + * + * @param bp_data The black hole data to initialise. + * @param p_data The gas data to use. + * @param gas_mass The mass of the gas particle. + */ +__attribute__((always_inline)) INLINE static void chemistry_bpart_from_part( + struct chemistry_bpart_data *bp_data, + const struct chemistry_part_data *p_data, const double gas_mass) { + + bp_data->metal_mass_total = p_data->metal_mass_fraction_total * gas_mass; + for (int i = 0; i < chemistry_element_count; ++i) { + bp_data->metal_mass[i] = p_data->metal_mass_fraction[i] * gas_mass; + } + + bp_data->formation_metallicity = p_data->metal_mass_fraction_total; +} + +/** + * @brief Add the chemistry data of a gas particle to a black hole. + * + * Black holes don't store fractions so we need to add element masses. + * + * @param bp_data The black hole data to add to. + * @param p_data The gas data to use. + * @param gas_mass The mass of the gas particle. + */ +__attribute__((always_inline)) INLINE static void chemistry_add_part_to_bpart( + struct chemistry_bpart_data *bp_data, + const struct chemistry_part_data *p_data, const double gas_mass) { + + bp_data->metal_mass_total += p_data->metal_mass_fraction_total * gas_mass; + for (int i = 0; i < chemistry_element_count; ++i) { + bp_data->metal_mass[i] += p_data->metal_mass_fraction[i] * gas_mass; + } +} + +/** + * @brief Transfer chemistry data of a gas particle to a black hole. + * + * In contrast to `chemistry_add_part_to_bpart`, only a fraction of the + * masses stored in the gas particle are transferred here. Absolute masses + * of the gas particle are adjusted as well. + * Black holes don't store fractions so we need to add element masses. + * + * We expect the nibble_mass to be the gas particle mass multiplied by the + * nibble_fraction. + * + * @param bp_data The black hole data to add to. + * @param p_data The gas data to use. + * @param nibble_mass The mass to be removed from the gas particle. + * @param nibble_fraction The fraction of the (original) mass of the gas + * particle that is removed. + */ +__attribute__((always_inline)) INLINE static void +chemistry_transfer_part_to_bpart(struct chemistry_bpart_data *bp_data, + struct chemistry_part_data *p_data, + const double nibble_mass, + const double nibble_fraction) { + + bp_data->metal_mass_total += p_data->metal_mass_fraction_total * nibble_mass; + for (int i = 0; i < chemistry_element_count; ++i) + bp_data->metal_mass[i] += p_data->metal_mass_fraction[i] * nibble_mass; +} + +/** + * @brief Add the chemistry data of a black hole to another one. + * + * @param bp_data The black hole data to add to. + * @param swallowed_data The black hole data to use. + */ +__attribute__((always_inline)) INLINE static void chemistry_add_bpart_to_bpart( + struct chemistry_bpart_data *bp_data, + const struct chemistry_bpart_data *swallowed_data) { + + bp_data->metal_mass_total += swallowed_data->metal_mass_total; + for (int i = 0; i < chemistry_element_count; ++i) { + bp_data->metal_mass[i] += swallowed_data->metal_mass[i]; + } +} + +/** + * @brief Split the metal content of a particle into n pieces + * + * We only need to split the fields that are not fractions. + * + * @param p The #part. + * @param n The number of pieces to split into. + */ +__attribute__((always_inline)) INLINE static void chemistry_split_part( + struct part *p, const double n) {} + +/** + * @brief Returns the total metallicity (metal mass fraction) of the + * gas particle to be used in feedback/enrichment related routines. + * + * We return the un-smoothed quantity here as the star will smooth + * over its gas neighbours. + * + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float +chemistry_get_total_metal_mass_fraction_for_feedback( + const struct part *restrict p) { + + return p->chemistry_data.metal_mass_fraction_total; +} + +/** + * @brief Returns the abundance array (metal mass fractions) of the + * gas particle to be used in feedback/enrichment related routines. + * + * We return the un-smoothed quantity here as the star will smooth + * over its gas neighbours. + * + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float const * +chemistry_get_metal_mass_fraction_for_feedback(const struct part *restrict p) { + + return p->chemistry_data.metal_mass_fraction; +} + +/** + * @brief Returns the total metallicity (metal mass fraction) of the + * star particle to be used in feedback/enrichment related routines. + * + * KIARA uses smooth abundances for everything. + * + * @param sp Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float +chemistry_get_star_total_metal_mass_fraction_for_feedback( + const struct spart *restrict sp) { + + return sp->chemistry_data.metal_mass_fraction_total; +} + +/** + * @brief Returns the abundance array (metal mass fractions) of the + * star particle to be used in feedback/enrichment related routines. + * + * KIARA uses smooth abundances for everything. + * + * @param sp Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float const * +chemistry_get_star_metal_mass_fraction_for_feedback( + const struct spart *restrict sp) { + + return sp->chemistry_data.metal_mass_fraction; +} + +/** + * @brief Returns the total metallicity (metal mass fraction) of the + * gas particle to be used in cooling related routines. + * + * KIARA uses smooth abundances for everything. + * + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float +chemistry_get_total_metal_mass_fraction_for_cooling( + const struct part *restrict p) { + + return p->chemistry_data.metal_mass_fraction_total; +} + +/** + * @brief Returns the abundance array (metal mass fractions) of the + * gas particle to be used in cooling related routines. + * + * KIARA uses smooth abundances for everything. + * + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float const * +chemistry_get_metal_mass_fraction_for_cooling(const struct part *restrict p) { + + return p->chemistry_data.metal_mass_fraction; +} + +/** + * @brief Returns the total metallicity (metal mass fraction) of the + * gas particle to be used in star formation related routines. + * + * KIARA uses smooth abundances for everything. + * + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float +chemistry_get_total_metal_mass_fraction_for_star_formation( + const struct part *restrict p) { + + return p->chemistry_data.metal_mass_fraction_total; +} + +/** + * @brief Returns the abundance array (metal mass fractions) of the + * gas particle to be used in star formation related routines. + * + * KIARA uses smooth abundances for everything. + * + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float const * +chemistry_get_metal_mass_fraction_for_star_formation( + const struct part *restrict p) { + + return p->chemistry_data.metal_mass_fraction; +} + +/** + * @brief Returns the total metal mass of the + * gas particle to be used in the stats related routines. + * + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float +chemistry_get_total_metal_mass_for_stats(const struct part *restrict p) { + + return p->chemistry_data.metal_mass_fraction_total * hydro_get_mass(p); +} + +/** + * @brief Returns the total metal mass of the + * star particle to be used in the stats related routines. + * + * @param sp Pointer to the star particle data. + */ +__attribute__((always_inline)) INLINE static float +chemistry_get_star_total_metal_mass_for_stats(const struct spart *restrict sp) { + + return sp->chemistry_data.metal_mass_fraction_total * sp->mass; +} + +/** + * @brief Returns the total metal mass of the + * black hole particle to be used in the stats related routines. + * + * @param bp Pointer to the BH particle data. + */ +__attribute__((always_inline)) INLINE static float +chemistry_get_bh_total_metal_mass_for_stats(const struct bpart *restrict bp) { + + return bp->chemistry_data.metal_mass_total; +} + +/** + * @brief Returns the total metallicity (metal mass fraction) of the + * star particle to be used in the luminosity calculations. + * + * @param sp Pointer to the star particle data. + */ +__attribute__((always_inline)) INLINE static float +chemistry_get_star_total_metal_mass_fraction_for_luminosity( + const struct spart *restrict sp) { + + return sp->chemistry_data.metal_mass_fraction_total; +} + +/** + * @brief Extra chemistry operations to be done during the drift. + * + * @param p Particle to act upon. + * @param xp The extended particle data to act upon. + * @param dt_drift The drift time-step for positions. + * @param dt_therm The drift time-step for thermal quantities. + * @param cosmo The current cosmological model. + * @param chem_data The global properties of the chemistry scheme. + */ +__attribute__((always_inline)) INLINE static void chemistry_predict_extra( + struct part *p, struct xpart *xp, float dt_drift, float dt_therm, + const struct cosmology *cosmo, + const struct chemistry_global_data *chem_data) {} + +#endif /* SWIFT_CHEMISTRY_KIARA_H */ diff --git a/src/chemistry/KIARA/chemistry_additions.h b/src/chemistry/KIARA/chemistry_additions.h new file mode 100644 index 0000000000..67831265a1 --- /dev/null +++ b/src/chemistry/KIARA/chemistry_additions.h @@ -0,0 +1,56 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2023 Yolan Uyttenhove (yolan.uyttenhove@ugent.be) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef SWIFT_CHEMISTRY_KIARA_ADDITIONS_H +#define SWIFT_CHEMISTRY_KIARA_ADDITIONS_H + +/** + * @brief Extra operations done during the kick. This needs to be + * done before the particle mass is updated in the hydro_kick_extra. + * + * @param p Particle to act upon. + * @param dt_therm Thermal energy time-step @f$\frac{dt}{a^2}@f$. + * @param dt_grav Gravity time-step @f$\frac{dt}{a}@f$. + * @param dt_hydro Hydro acceleration time-step + * @f$\frac{dt}{a^{3(\gamma{}-1)}}@f$. + * @param dt_kick_corr Gravity correction time-step @f$adt@f$. + * @param cosmo Cosmology. + * @param hydro_props Additional hydro properties. + */ +__attribute__((always_inline)) INLINE static void chemistry_kick_extra( + struct part *p, float dt_therm, float dt_grav, float dt_hydro, + float dt_kick_corr, const struct cosmology *cosmo, + const struct hydro_props *hydro_props) {} + +/** + * @brief update metal mass fluxes between two interacting particles during + * hydro_iact_(non)sym(...) calls. + * + * @param pi first interacting particle + * @param pj second interacting particle + * @param mass_flux the mass flux between these two particles. + * @param flux_dt the time-step over which the fluxes are exchanged + * @param mode 0: non-symmetric interaction, update i only. 1: symmetric + * interaction. + **/ +__attribute__((always_inline)) INLINE static void runner_iact_chemistry_fluxes( + struct part *restrict pi, struct part *restrict pj, float mass_flux, + float flux_dt, int mode) {} + +#endif // SWIFT_CHEMISTRY_KIARA_ADDITIONS_H diff --git a/src/chemistry/KIARA/chemistry_debug.h b/src/chemistry/KIARA/chemistry_debug.h new file mode 100644 index 0000000000..69e2526499 --- /dev/null +++ b/src/chemistry/KIARA/chemistry_debug.h @@ -0,0 +1,40 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_CHEMISTRY_KIARA_DEBUG_H +#define SWIFT_CHEMISTRY_KIARA_DEBUG_H + +__attribute__((always_inline)) INLINE static void chemistry_debug_particle( + const struct part *p, const struct xpart *xp) { + + warning("[PID%lld chemistry_part_data:", p->id); + for (int i = 0; i < chemistry_element_count; i++) { + warning("[PID%lld metal_mass_fraction[%i]: %.3e", p->id, i, + p->chemistry_data.metal_mass_fraction[i]); + } + warning("[PID%lld metal_mass_fraction_total: %.3e", p->id, + p->chemistry_data.metal_mass_fraction_total); + for (int i = 0; i < chemistry_element_count; i++) { + warning("[PID%lld metal_mass_fraction[%i]: %.3e", p->id, i, + p->chemistry_data.metal_mass_fraction[i]); + } + warning("[PID%lld metal_mass_fraction_total: %.3e", p->id, + p->chemistry_data.metal_mass_fraction_total); +} + +#endif /* SWIFT_CHEMISTRY_KIARA_DEBUG_H */ diff --git a/src/chemistry/KIARA/chemistry_iact.h b/src/chemistry/KIARA/chemistry_iact.h new file mode 100644 index 0000000000..ad9def28e2 --- /dev/null +++ b/src/chemistry/KIARA/chemistry_iact.h @@ -0,0 +1,882 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_KIARA_CHEMISTRY_IACT_H +#define SWIFT_KIARA_CHEMISTRY_IACT_H + +/** + * @file KIARA/chemistry_iact.h + * @brief Smooth metal interaction functions following the KIARA model. + */ + +#include "timestep_sync_part.h" + +#include + +/** + * @brief Sums ambient quantities for the firehose wind model + * + * This is called from runner_iact_chemistry, which is called during the density + * loop + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle. + */ +__attribute__((always_inline)) INLINE static void firehose_compute_ambient_sym( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj) { + + /* Only decoupled particles need the ambient properties since they + * are in the stream. */ + const int decoupled_i = pi->decoupled; + const int decoupled_j = pj->decoupled; + if (decoupled_i && decoupled_j) return; + + const float r = sqrtf(r2); + const float eint_i = hydro_get_drifted_comoving_internal_energy(pi); + const float eint_j = hydro_get_drifted_comoving_internal_energy(pj); + + /* Accumulate ambient neighbour quantities with an SPH gather operation */ + if (decoupled_i && !decoupled_j) { + struct chemistry_part_data *chi = &pi->chemistry_data; + const float hi_inv = 1. / hi; + const float ui = r * hi_inv; + const float mj = hydro_get_mass(pj); + float wi; + kernel_eval(ui, &wi); + +#ifdef FIREHOSE_DEBUG_CHECKS + if (!isfinite(mj * eint_j * wi)) { + message( + "FIREHOSE_BAD pi=%lld ui=%g neighbour pj=%lld uj=%g mj=%g hi=%g" + "wi=%g\n", + pi->id, eint_i, pj->id, eint_j, mj, hi, wi); + } +#endif + + chi->u_ambient += mj * eint_j * wi; + chi->rho_ambient += mj * wi; + chi->w_ambient += wi; + } + + if (!decoupled_i && decoupled_j) { + struct chemistry_part_data *chj = &pj->chemistry_data; + const float hj_inv = 1. / hj; + const float uj = r * hj_inv; + const float mi = hydro_get_mass(pi); + float wj; + kernel_eval(uj, &wj); + +#ifdef FIREHOSE_DEBUG_CHECKS + if (!isfinite(mi * eint_i * wj)) { + message( + "FIREHOSE_BAD pj=%lld uj=%g neighbour pi=%lld ui=%g mi=%g hj=%g" + "wj=%g\n", + pj->id, eint_j, pi->id, eint_i, mi, hj, wj); + } +#endif + + chj->u_ambient += mi * eint_i * wj; + chj->rho_ambient += mi * wj; + chj->w_ambient += wj; + } +} + +/** + * @brief Sums ambient quantities for the firehose wind model + * + * This is called from runner_iact_chemistry, which is called during the density + * loop + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle. + */ +__attribute__((always_inline)) INLINE static void +firehose_compute_ambient_nonsym(const float r2, const float dx[3], + const float hi, const float hj, + struct part *restrict pi, + const struct part *restrict pj) { + + /* Only decoupled winds need the ambient quantities computed because + * they are in the stream. */ + const int decoupled_i = pi->decoupled; + const int decoupled_j = pj->decoupled; + + /* A wind particle cannot be in the ambient medium */ + if (!decoupled_i || decoupled_j) return; + + struct chemistry_part_data *chi = &pi->chemistry_data; + + /* Do accumulation of ambient quantities */ + const float eint_j = hydro_get_drifted_comoving_internal_energy(pj); + + /* Compute the kernel function for pi */ + const float r = sqrtf(r2); + const float h_inv = 1. / hi; + const float ui = r * h_inv; + const float mj = hydro_get_mass(pj); + float wi; + kernel_eval(ui, &wi); + +#ifdef FIREHOSE_DEBUG_CHECKS + const float eint_i = hydro_get_drifted_comoving_internal_energy(pi); + if (!isfinite(mj * eint_j * wi)) { + message( + "FIREHOSE_BAD pi=%lld ui=%g neighbour pj=%lld uj=%g mj=%g hi=%g" + "wi=%g\n", + pi->id, eint_i, pj->id, eint_j, mj, hi, wi); + } +#endif + + /* Accumulate ambient neighbour quantities with an SPH gather operation */ + chi->u_ambient += mj * eint_j * wi; + chi->rho_ambient += mj * wi; + chi->w_ambient += wi; +} + +/** + * @brief do chemistry computation after the runner_iact_density (symmetric + * version) + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_chemistry( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H) { + + /* If in wind mode, compute ambient quantities for firehose wind diffusion */ + firehose_compute_ambient_sym(r2, dx, hi, hj, pi, pj); + + /* Do not need diffusion properties for wind particles */ + if (pi->decoupled || pj->decoupled) return; + + struct chemistry_part_data *chi = &pi->chemistry_data; + struct chemistry_part_data *chj = &pj->chemistry_data; + + float wi, wi_dx; + float wj, wj_dx; + + /* Get the masses. */ + const float mi = hydro_get_mass(pi); + const float mj = hydro_get_mass(pj); + + /* Get r */ + const float r = sqrtf(r2); + const float r_inv = 1.f / r; + + /* Compute the kernel function for pi */ + const float ui = r / hi; + kernel_deval(ui, &wi, &wi_dx); + + /* Compute the kernel function for pj */ + const float uj = r / hj; + kernel_deval(uj, &wj, &wj_dx); + + const float wi_dr = wi_dx * r_inv; + const float mj_wi_dr = mj * wi_dr; + + const float wj_dr = wj_dx * r_inv; + const float mi_wj_dr = mi * wj_dr; + + /* dx[i] is from i -> j, so should dv_ij be from i -> j */ + const float dv_ij[3] = {pi->v[0] - pj->v[0], pi->v[1] - pj->v[1], + pi->v[2] - pj->v[2]}; + + /* Compute the shear tensor, i = spatial direction */ + for (int i = 0; i < 3; i++) { + const float dxi_mj_wi_dr = dx[i] * mj_wi_dr; + const float dxi_mi_wj_dr = dx[i] * mi_wj_dr; + + chi->shear_tensor[0][i] += dv_ij[0] * dxi_mj_wi_dr; + chi->shear_tensor[1][i] += dv_ij[1] * dxi_mj_wi_dr; + chi->shear_tensor[2][i] += dv_ij[2] * dxi_mj_wi_dr; + + /* Sign must be positive since dx is always i -> j */ + chj->shear_tensor[0][i] += dv_ij[0] * dxi_mi_wj_dr; + chj->shear_tensor[1][i] += dv_ij[1] * dxi_mi_wj_dr; + chj->shear_tensor[2][i] += dv_ij[2] * dxi_mi_wj_dr; + } + +#if COOLING_GRACKLE_MODE >= 2 + /* Sum up local SFR density for computing G0 */ + chi->local_sfr_density += wi * max(0.f, pj->sf_data.SFR); + chj->local_sfr_density += wj * max(0.f, pi->sf_data.SFR); +#endif +} + +/** + * @brief do chemistry computation after the runner_iact_density (non symmetric + * version) + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle (not updated). + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_chemistry( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, const struct part *restrict pj, const float a, + const float H) { + + /* If in wind mode, compute ambient quantities for firehose wind diffusion */ + firehose_compute_ambient_nonsym(r2, dx, hi, hj, pi, pj); + + /* Do not need diffusion properties for wind particles */ + if (pi->decoupled || pj->decoupled) return; + + struct chemistry_part_data *chi = &pi->chemistry_data; + + float wi, wi_dx; + + /* Get the masses. */ + const float mj = hydro_get_mass(pj); + + /* Get r */ + const float r = sqrtf(r2); + const float r_inv = 1.f / r; + + /* Compute the kernel function for pi */ + const float ui = r / hi; + kernel_deval(ui, &wi, &wi_dx); + + const float wi_dr = wi_dx * r_inv; + const float mj_wi_dr = mj * wi_dr; + + const float dv_ij[3] = {pi->v[0] - pj->v[0], pi->v[1] - pj->v[1], + pi->v[2] - pj->v[2]}; + + /* Compute the shear tensor */ + for (int i = 0; i < 3; i++) { + const float dxi_mj_wi_dr = dx[i] * mj_wi_dr; + chi->shear_tensor[0][i] += dv_ij[0] * dxi_mj_wi_dr; + chi->shear_tensor[1][i] += dv_ij[1] * dxi_mj_wi_dr; + chi->shear_tensor[2][i] += dv_ij[2] * dxi_mj_wi_dr; + } + +#if COOLING_GRACKLE_MODE >= 2 + /* Sum up local SFR density for computing G0 */ + chi->local_sfr_density += wi * max(0.f, pj->sf_data.SFR); +#endif +} + +/** + * @brief Computes the mass exchanged between the firehose stream + * and the ambient medium. Note that either i or j could be the stream + * particle, with j or i being ambient. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Wind particle. + * @param pj Ambient particle. + * @param time_base The time base used to convert integer to float time. + * @param ti_current Current integer time for seeding random number generator + * @param cd #chemistry_global_data containing chemistry information. + * @param v2 velocity difference squared between i and j. + * + */ +__attribute__((always_inline)) INLINE static float +firehose_compute_mass_exchange(const float r2, const float dx[3], + const float hi, const float hj, + const struct part *pi, const struct part *pj, + const float time_base, + const integertime_t ti_current, + const struct chemistry_global_data *cd, + float *v2, const struct cosmology *cosmo) { + + const int i_stream = pi->decoupled; + const int j_stream = pj->decoupled; + + /* Both particles cannot be in the stream. The one with >0 delay time is + * the stream particle. At least one particle must be in the stream. */ + if (i_stream && j_stream) return 0.f; + if (!i_stream && !j_stream) return 0.f; + + const double dt_i = get_timestep(pi->time_bin, time_base); + const double dt_j = get_timestep(pj->time_bin, time_base); + + /* Must be a timestep for both */ + if (dt_i <= 0. || dt_j <= 0.) return 0.f; + + /* For stream particle, make sure the stream radius > 0 */ + if (i_stream && pi->chemistry_data.radius_stream <= 0.f) return 0.f; + if (j_stream && pj->chemistry_data.radius_stream <= 0.f) return 0.f; + + /* Stream particle cannot be an AGN jet particle + if (i_stream && + pi->feedback_data.number_of_times_decoupled >= 10000) return 0.f; + if (j_stream && + pj->feedback_data.number_of_times_decoupled >= 10000) return 0.f;*/ + + /* Compute the velocity of the stream relative to ambient gas. + * Order does not matter here because it is symmetric. */ + *v2 = 0.f; + for (int i = 0; i < 3; i++) { + const float dv = pi->v[i] - pj->v[i]; + *v2 += dv * dv; + } + + /* Don't apply above some velocity to avoid jets */ + const float v2_phys = *v2 * cosmo->a2_inv; + const float max_v2_phys = + cd->firehose_max_velocity * cd->firehose_max_velocity; + if (v2_phys > max_v2_phys) return 0.f; + + /* Get mixing layer cooling time, which is negative if cooling */ + const float mixing_layer_time_i = pi->cooling_data.mixing_layer_cool_time; + const float mixing_layer_time_j = pj->cooling_data.mixing_layer_cool_time; + + const float r = sqrtf(r2); + const float gamma_gamma_minus_1 = hydro_gamma * hydro_gamma_minus_one; + + /* Set these based on which is in the stream */ + float mi; + float wi; + float sum_wi; + float chi; + float c_stream; + float c_amb; + float radius_stream; + float mixing_layer_time; + double dt = 0.; + + if (i_stream) { + dt = dt_i; + mi = hydro_get_mass(pi); + const float h_inv = 1. / hi; + const float ui = r * h_inv; + kernel_eval(ui, &wi); + + /* Normalization of the ambient medium */ + sum_wi = pi->chemistry_data.w_ambient; + + /* Compute thermal energy ratio for stream and ambient */ + const float eint_i = hydro_get_drifted_comoving_internal_energy(pi); + chi = pi->chemistry_data.u_ambient / eint_i; + c_stream = sqrtf(eint_i * gamma_gamma_minus_1); + c_amb = sqrtf(pi->chemistry_data.u_ambient * gamma_gamma_minus_1); + radius_stream = pi->chemistry_data.radius_stream; + mixing_layer_time = mixing_layer_time_i; + } + + if (j_stream) { /* j must be the stream here */ + dt = dt_j; + mi = hydro_get_mass(pj); + const float h_inv = 1. / hj; + const float ui = r * h_inv; + kernel_eval(ui, &wi); + + /* Normalization of the ambient medium */ + sum_wi = pj->chemistry_data.w_ambient; + + /* Compute thermal energy ratio for stream and ambient */ + const float eint_j = hydro_get_drifted_comoving_internal_energy(pj); + chi = pj->chemistry_data.u_ambient / eint_j; + c_stream = sqrtf(eint_j * gamma_gamma_minus_1); + c_amb = sqrtf(pj->chemistry_data.u_ambient * gamma_gamma_minus_1); + radius_stream = pj->chemistry_data.radius_stream; + mixing_layer_time = mixing_layer_time_j; + } + + /* This should never happen. */ + if (dt == 0.) return 0.f; + + double dm = 0.; + double t_cool_mix = 1.e10 * dt; + + /* Mass change is growth due to cooling minus loss due to shearing, + kernel-weighted. */ + + /* Must compare internal velocity and soundspeed physically */ + const float a_factor_Mach = cosmo->a_inv / cosmo->a_factor_sound_speed; + const float a_factor_L_over_V = cosmo->a * cosmo->a; + const float a_factor_L_over_Cs = cosmo->a / cosmo->a_factor_sound_speed; + + const float v_stream = sqrtf(*v2); + const float Mach = a_factor_Mach * (v_stream / (c_stream + c_amb)); + const float alpha = 0.21f * (0.8f * exp(-3.f * Mach * Mach) + 0.2f); + + t_cool_mix = (mixing_layer_time < 0.f) ? fabs(mixing_layer_time) : t_cool_mix; + + const double t_shear = + a_factor_L_over_V * (radius_stream / (alpha * v_stream)); + const double t_sound = a_factor_L_over_Cs * (2.f * radius_stream / c_stream); + + const double delta_growth = + (4. / (chi * t_sound)) * pow(t_cool_mix / t_sound, -0.25) * dt; + + double delta_shear = 0.; + if (t_shear < t_cool_mix) delta_shear = (1. - exp(-dt / t_shear)); + + dm = mi * (delta_growth - delta_shear) * (wi / sum_wi); + + /* If stream is growing, don't mix */ + if (dm > 0.f) dm = 0.f; + +#ifdef FIREHOSE_DEBUG_CHECKS + if (dm < 0.f && i_stream && pj->cooling_data.subgrid_temp > 0.f) { + message( + "FIREHOSE: z=%g %lld %lld m=%g nHamb=%g rhoamb/rhoi=%g rhoamb/rhoj=%g" + " Tamb=%g Tj/Tamb=%g cstr/camb=%g M=%g r=%g grow=%g shear=%g tshear=%g" + " tcool=%g fexch=%g", + cosmo->z, pi->id, pj->id, pi->mass, + pi->chemistry_data.rho_ambient * cosmo->a3_inv * cd->rho_to_n_cgs, + pi->chemistry_data.rho_ambient / pi->rho, + pi->chemistry_data.rho_ambient / pj->rho, + pi->chemistry_data.u_ambient * cosmo->a_factor_internal_energy / + cd->temp_to_u_factor, + eint_j / pi->chemistry_data.u_ambient, c_stream / c_amb, Mach, + radius_stream * cd->length_to_kpc * cosmo->a, delta_growth, delta_shear, + t_shear, pi->cooling_data.mixing_layer_cool_time, dm / pi->mass); + } +#endif + + return dm; +} + +/** + * @brief Computes the particle interaction via the firehose stream model + * + * This is called from runner_iact_diffusion, which is called during the force + * loop + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Wind particle. + * @param pj Ambient particle. + * @param time_base The time base used to convert integer to float time. + * @param ti_current Current integer time for seeding random number generator. + * @param cd #chemistry_global_data containing chemistry information. + * + */ +__attribute__((always_inline)) INLINE static void firehose_evolve_particle_sym( + const float r2, const float dx[3], const float hi, const float hj, + struct part *pi, struct part *pj, + const float time_base, const integertime_t ti_current, + const struct chemistry_global_data *cd, + const struct cosmology *cosmo) { + + /* Both particles must be within each others smoothing lengths */ + const float Hi = kernel_gamma * hi; + const float Hj = kernel_gamma * hj; + const int r_in_Hi = (r2 < Hi * Hi) ? 1 : 0; + const int r_in_Hj = (r2 < Hj * Hj) ? 1 : 0; + if (!r_in_Hi || !r_in_Hj) return; + + const int i_stream = pi->decoupled; + const int j_stream = pj->decoupled; + + /* Only one of the particles must be in the stream. */ + if (i_stream && j_stream) return; + if (!i_stream && !j_stream) return; + + if (r2 <= 0.f) return; + + struct chemistry_part_data *chi = &pi->chemistry_data; + struct chemistry_part_data *chj = &pj->chemistry_data; + + /* Compute the amount of mass mixed between stream particle and ambient gas */ + float v2 = 0.f; + const float dm = firehose_compute_mass_exchange(r2, dx, hi, hj, pi, pj, + time_base, ti_current, + cd, &v2, cosmo); + float delta_m = fabs(dm); + if (delta_m <= 0.f) return; + + /* Limit mass exchange to some fraction of particles' mass */ + const float mi = hydro_get_mass(pi); + const float mj = hydro_get_mass(pj); + + const float max_fmix_this_step = cd->firehose_max_fmix_per_step; + if (delta_m > max_fmix_this_step * mi) delta_m = max_fmix_this_step * mi; + if (delta_m > max_fmix_this_step * mj) delta_m = max_fmix_this_step * mj; + + /* Track amount of gas mixed in stream particle */ + if (i_stream) chi->exchanged_mass += delta_m; + if (j_stream) chj->exchanged_mass += delta_m; + + chi->dm += delta_m; + chj->dm += delta_m; + + /* set weights for averaging i and j */ + const float pii_weight = (mi - delta_m) / mi; + const float pij_weight = delta_m / mi; + const float pji_weight = delta_m / mj; + const float pjj_weight = (mj - delta_m) / mj; + + /* Mixing is negligibly small, avoid underflows */ + if (pij_weight < 1.e-10f || pji_weight < 1.e-10f) return; + + const float wt_ii = pii_weight * mi; + const float wt_ij = pij_weight * mi; + const float wt_jj = pjj_weight * mj; + const float wt_ji = pji_weight * mj; + + /* Spread dust masses between particles */ + const float pi_dust_mass = pi->cooling_data.dust_mass; + const float pj_dust_mass = pj->cooling_data.dust_mass; + + const float dust_wt_ii = pii_weight * pi_dust_mass; + const float dust_wt_ij = pij_weight * pi_dust_mass; + const float dust_wt_ji = pji_weight * pj_dust_mass; + const float dust_wt_jj = pjj_weight * pj_dust_mass; + + const float new_pi_dust_mass = dust_wt_ii + dust_wt_ij; + const float new_pj_dust_mass = dust_wt_ji + dust_wt_jj; + chi->dm_dust += new_pi_dust_mass - pi_dust_mass; + chj->dm_dust += new_pj_dust_mass - pj_dust_mass; + + /* Spread individual dust elements */ + for (int elem = 0; elem < chemistry_element_count; ++elem) { + /* Exchange metals */ + const float term_ii = wt_ii * chi->metal_mass_fraction[elem]; + const float term_ij = wt_ij * chj->metal_mass_fraction[elem]; + const float term_jj = wt_jj * chj->metal_mass_fraction[elem]; + const float term_ji = wt_ji * chi->metal_mass_fraction[elem]; + + const float old_pi_Z_mass = mi * chi->metal_mass_fraction[elem]; + const float new_pi_Z_mass = term_ii + term_ij; + const float old_pj_Z_mass = mj * chj->metal_mass_fraction[elem]; + const float new_pj_Z_mass = term_jj + term_ji; + + chi->dm_Z[elem] += new_pi_Z_mass - old_pi_Z_mass; + chj->dm_Z[elem] += new_pj_Z_mass - old_pj_Z_mass; + + /* Exchange dust metals */ + const float pi_dust_frac = pi->cooling_data.dust_mass_fraction[elem]; + const float pj_dust_frac = pj->cooling_data.dust_mass_fraction[elem]; + + /* Particle i */ + const float old_pi_dust_mass_Z = pi_dust_frac * pi_dust_mass; + + const float dust_term_ii = dust_wt_ii * pi_dust_frac; + const float dust_term_ij = dust_wt_ij * pj_dust_frac; + const float new_pi_dust_mass_Z = dust_term_ii + dust_term_ij; + + chi->dm_dust_Z[elem] += new_pi_dust_mass_Z - old_pi_dust_mass_Z; + + /* Particle j */ + const float old_pj_dust_mass_Z = pj_dust_frac * pj_dust_mass; + + const float dust_term_jj = dust_wt_jj * pj_dust_frac; + const float dust_term_ji = dust_wt_ji * pi_dust_frac; + const float new_pj_dust_mass_Z = dust_term_jj + dust_term_ji; + + chj->dm_dust_Z[elem] += new_pj_dust_mass_Z - old_pj_dust_mass_Z; + } + + /* Update particles' internal energy per unit mass */ + const float old_pi_u = hydro_get_drifted_comoving_internal_energy(pi); + const float old_pj_u = hydro_get_drifted_comoving_internal_energy(pj); + + float new_pi_u = (wt_ii * old_pi_u + wt_ij * old_pj_u) / mi; + float new_pj_u = (wt_ji * old_pi_u + wt_jj * old_pj_u) / mj; + + /* Accumulate the velocity exchange due to the mass, conserving the + * momentum */ + float new_v2 = 0.f; + for (int i = 0; i < 3; i++) { + const float new_pi_v_full_i = + (wt_ii * pi->v[i] + wt_ij * pj->v[i]) / mi; + /* Keep track of the final new velocity */ + chi->dv[i] += new_pi_v_full_i - pi->v[i]; + + const float new_pj_v_full_i = + (wt_ji * pi->v[i] + wt_jj * pj->v[i]) / mj; + chj->dv[i] += new_pj_v_full_i - pj->v[i]; + + const float dv_i = new_pi_v_full_i - new_pj_v_full_i; + new_v2 += dv_i * dv_i; + } + + /* 4) Split excess energy between stream and ambient particle */ + float delta_KE = 0.5f * delta_m * (v2 - new_v2); + const float min_KE = min(mi * new_pi_u, mj * new_pj_u); + + /* Limit to minimum of the two particles */ + if (delta_KE > min_KE) delta_KE = min_KE; + + const float dKE_split = 0.5f * delta_KE; + + /* Add in the delta KE difference */ + new_pi_u += dKE_split / mi; + new_pj_u += dKE_split / mj; + + /* Accumulate the changes in energy in all interactions */ + chi->du += new_pi_u - old_pi_u; + chj->du += new_pj_u - old_pj_u; + +#ifdef FIREHOSE_DEBUG_CHECKS + message( + "FIREHOSE_EXCHANGE: pi=%lld pj=%lld si=%d sj=%d npi_u=%g npj_u=%g " + "opi_u=%g opj_u=%g dKE=%g nv2=%g ov2=%g", + pi->id, pj->id, i_stream, j_stream, new_pi_u, new_pj_u, old_pi_u, + old_pj_u, delta_KE, new_v2, v2); +#endif +} + +/** + * @brief do metal diffusion computation in the + * (symmetric version) + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + * @param time_base The time base used to convert integer to float time. + * @param ti_current The current time (in integer) + * @param cosmo The cosmology information. + * @param with_cosmology Are we running with cosmology? + * + */ +__attribute__((always_inline)) INLINE static void runner_iact_diffusion( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H, const float time_base, const integertime_t t_current, + const struct cosmology *cosmo, const int with_cosmology, + const struct chemistry_global_data *cd) { + + if (pi->decoupled || pj->decoupled) { + if (cd->use_firehose_wind_model) { + /* If in wind mode, do firehose wind diffusion */ + firehose_evolve_particle_sym(r2, dx, hi, hj, pi, pj, time_base, + t_current, cd, cosmo); + } + + return; + } + + struct chemistry_part_data *chi = &pi->chemistry_data; + struct chemistry_part_data *chj = &pj->chemistry_data; + + /* No need to diffuse if both particles are not diffusing. */ + if (chj->diffusion_coefficient > 0.f && chi->diffusion_coefficient > 0.f) { + + /* Get mass */ + const float mj = hydro_get_mass(pj); + const float mi = hydro_get_mass(pi); + const float rhoj = hydro_get_physical_density(pj, cosmo); + const float rhoi = hydro_get_physical_density(pi, cosmo); + + float wi, wj, dwi_dx, dwj_dx; + + /* Get r */ + const float r = sqrtf(r2); + + /* part j */ + /* Get the kernel for hj */ + const float hj_inv = 1.0f / hj; + + /* Compute the kernel function for pj */ + const float xj = r * hj_inv; + kernel_deval(xj, &wj, &dwj_dx); + + /* part i */ + /* Get the kernel for hi */ + const float hi_inv = 1.0f / hi; + + /* Compute the kernel function for pi */ + const float xi = r * hi_inv; + kernel_deval(xi, &wi, &dwi_dx); + + /* Get 1/r */ + const float r_inv = r > 0.f ? 1.f / r : 0.f; + + const float wi_dr = dwi_dx * r_inv; + const float wj_dr = dwj_dx * r_inv; + + const float mj_dw_r = mj * wi_dr; + const float mi_dw_r = mi * wj_dr; + + const float rhoij_inv = 1.f / (rhoi * rhoj); + + /** + * Compute the diffusion following Eq. 2.14 + * from Monaghan, Huppert, & Worster (2006). + */ + float coef = 4.f * chi->diffusion_coefficient * chj->diffusion_coefficient; + coef /= chi->diffusion_coefficient + chj->diffusion_coefficient; + + const float coef_i = coef * mj_dw_r * rhoij_inv; + const float coef_j = coef * mi_dw_r * rhoij_inv; + + /* Compute the time derivative of metals due to diffusion */ + const float dZ_ij_tot = + chi->metal_mass_fraction_total - chj->metal_mass_fraction_total; + chi->dZ_dt_total += coef_i * dZ_ij_tot; + chj->dZ_dt_total -= coef_j * dZ_ij_tot; + + for (int elem = 0; elem < chemistry_element_count; elem++) { + const float dZ_ij = + chi->metal_mass_fraction[elem] - chj->metal_mass_fraction[elem]; + chi->dZ_dt[elem] += coef_i * dZ_ij; + chj->dZ_dt[elem] -= coef_j * dZ_ij; + } + } +} + +/** + * @brief do metal diffusion computation in the + * (nonsymmetric version) + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle. (not updated) + * @param a Current scale factor. + * @param H Current Hubble parameter. + * @param time_base The time base used to convert integer to float time. + * @param ti_current The current time (in integer) + * @param cosmo The #cosmology. + * @param with_cosmology Are we running with cosmology? + * @param cd #chemistry_global_data containing chemistry information. + * + */ +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_diffusion( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, const struct part *restrict pj, const float a, + const float H, const float time_base, const integertime_t t_current, + const struct cosmology *cosmo, const int with_cosmology, + const struct chemistry_global_data *cd) { + + /* In nonsym case, two cases: depending on whether i is stream or ambient */ + if (pi->decoupled || pj->decoupled) return; + + struct chemistry_part_data *chi = &pi->chemistry_data; + const struct chemistry_part_data *chj = &pj->chemistry_data; + + if (chj->diffusion_coefficient > 0 && chi->diffusion_coefficient > 0) { + + /* Get mass */ + const float mj = hydro_get_mass(pj); + const float rhoj = hydro_get_physical_density(pj, cosmo); + const float rhoi = hydro_get_physical_density(pi, cosmo); + + float wi, dwi_dx; + + /* Get r */ + const float r = sqrtf(r2); + + /* part i */ + /* Get the kernel for hi */ + const float hi_inv = 1.0f / hi; + + /* Compute the kernel function for pi */ + const float xi = r * hi_inv; + kernel_deval(xi, &wi, &dwi_dx); + + /* Get 1/r */ + const float r_inv = 1.f / sqrtf(r2); + const float wi_dr = dwi_dx * r_inv; + + const float mj_dw_r = mj * wi_dr; + + const float rhoij_inv = 1.f / (rhoi * rhoj); + + /** + * Compute the diffusion following Eq. 2.14 + * from Monaghan, Huppert, & Worster (2006). + */ + float coef = 4.f * chi->diffusion_coefficient * chj->diffusion_coefficient; + coef /= chi->diffusion_coefficient + chj->diffusion_coefficient; + + const float coef_i = coef * mj_dw_r * rhoij_inv; + + /* Compute the time derivative */ + const float dZ_ij_tot = + chi->metal_mass_fraction_total - chj->metal_mass_fraction_total; + chi->dZ_dt_total += coef_i * dZ_ij_tot; + + for (int elem = 0; elem < chemistry_element_count; elem++) { + const float dZ_ij = + chi->metal_mass_fraction[elem] - chj->metal_mass_fraction[elem]; + chi->dZ_dt[elem] += coef_i * dZ_ij; + } + } +} + +/** + * @brief do metal diffusion computation in the + * (symmetric version) + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_gradient_diffusion(const float r2, const float dx[3], + const float hi, const float hj, + struct part *restrict pi, + struct part *restrict pj, const float a, + const float H) {} + +/** + * @brief do metal diffusion computation in the + * (nonsymmetric version) + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_gradient_diffusion(const float r2, const float dx[3], + const float hi, const float hj, + struct part *restrict pi, + struct part *restrict pj, const float a, + const float H) {} + +#endif /* SWIFT_KIARA_CHEMISTRY_IACT_H */ diff --git a/src/chemistry/KIARA/chemistry_io.h b/src/chemistry/KIARA/chemistry_io.h new file mode 100644 index 0000000000..f506df45a1 --- /dev/null +++ b/src/chemistry/KIARA/chemistry_io.h @@ -0,0 +1,230 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_CHEMISTRY_IO_KIARA_H +#define SWIFT_CHEMISTRY_IO_KIARA_H + +#include "chemistry.h" +#include "io_properties.h" + +/** + * @brief Specifies which particle fields to read from a dataset + * + * @param parts The particle array. + * @param list The list of i/o properties to read. + * + * @return Returns the number of fields to read. + */ +INLINE static int chemistry_read_particles(struct part *parts, + struct io_props *list) { + int num = 0; + /* List what we want to read */ + list[num] = io_make_input_field( + "ElementMassFractions", FLOAT, chemistry_element_count, OPTIONAL, + UNIT_CONV_NO_UNITS, parts, chemistry_data.metal_mass_fraction); + num++; + + list[num] = io_make_input_field("MetalMassFractions", FLOAT, 1, OPTIONAL, + UNIT_CONV_NO_UNITS, parts, + chemistry_data.metal_mass_fraction_total); + num++; + + return num; +} + +/** + * @brief Specifies which particle fields to write to a dataset + * + * @param parts The particle array. + * @param xparts The extra particle array. + * @param list The list of i/o properties to write. + * @param with_cosmology Are we running with cosmology? + * + * @return Returns the number of fields to write. + */ +INLINE static int chemistry_write_particles(const struct part *parts, + const struct xpart *xparts, + struct io_props *list, + const int with_cosmology) { + + int num = 0; + + /* List what we want to write */ + list[num] = io_make_output_field( + "ElementMassFractions", FLOAT, chemistry_element_count, + UNIT_CONV_NO_UNITS, 0.f, parts, chemistry_data.metal_mass_fraction, + "Fractions of the particles' masses that are in the gas phase of a given " + "element"); + num++; + + list[num] = io_make_output_field( + "MetalMassFractions", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, parts, + chemistry_data.metal_mass_fraction_total, + "Fractions of the particles' masses that are in metals"); + num++; + + list[num] = io_make_output_field( + "DiffusionCoefficients", FLOAT, 1, UNIT_CONV_DIFF_COEFF, 0.f, parts, + chemistry_data.diffusion_coefficient, "The full diffusion coefficient"); + num++; + + list[num] = + io_make_output_field("DecouplingDelayTimes", FLOAT, 1, UNIT_CONV_TIME, + 0.f, parts, feedback_data.decoupling_delay_time, + "Maximum time left as a firehose wind particle"); + num++; + + list[num] = io_make_output_field( + "NumberOfTimesDecoupled", INT, 1, UNIT_CONV_NO_UNITS, 0.f, parts, + feedback_data.number_of_times_decoupled, + "Number of times decoupled. Units of 1 are from SF feedback," + "units of 1000 are from non-jet AGN feedback," + "units of 100000 are from jet AGN feedback"); + num++; + +#ifdef KIARA_DEBUG_CHECKS + list[num] = io_make_output_field( + "ElementDiffusionRates", FLOAT, chemistry_element_count, + UNIT_CONV_DIFF_RATE, 0.f, parts, chemistry_data.dZ_dt, + "The rate of transfer of metal concentration for each element"); + num++; + + list[num] = + io_make_output_field("MetalDiffusionRates", FLOAT, 1, UNIT_CONV_DIFF_RATE, + 0.f, parts, chemistry_data.dZ_dt_total, + "The rate of transfer of total metal concentration"); + num++; +#endif + + return num; +} + +/** + * @brief Specifies which star particle fields to write to a dataset + * + * @param sparts The star particle array. + * @param list The list of i/o properties to write. + * + * @return Returns the number of fields to write. + */ +INLINE static int chemistry_write_sparticles(const struct spart *sparts, + struct io_props *list) { + + int num = 0; + + /* List what we want to write */ + list[num] = io_make_output_field( + "ElementMassFractions", FLOAT, chemistry_element_count, + UNIT_CONV_NO_UNITS, 0.f, sparts, chemistry_data.metal_mass_fraction, + "Fractions of the particles' masses that are in the given element"); + num++; + + list[num] = io_make_output_field( + "MetalMassFractions", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, sparts, + chemistry_data.metal_mass_fraction_total, + "Fractions of the particles' masses that are in metals"); + num++; + + return num; +} + +/** + * @brief Specifies which sink fields to write to a dataset + * + * @param sinks The #sink array. + * @param list The list of i/o properties to write. + * + * @return Returns the number of fields to write. + * required by src/common_io.c + */ +INLINE static int chemistry_write_sinkparticles(const struct sink *sinks, + struct io_props *list) { + return 0; +} + +/** + * @brief Specifies which black hole particle fields to write to a dataset + * + * @param bparts The black hole particle array. + * @param list The list of i/o properties to write. + * + * @return Returns the number of fields to write. + */ +INLINE static int chemistry_write_bparticles(const struct bpart *bparts, + struct io_props *list) { + + int num = 0; + + /* List what we want to write */ + list[num] = io_make_output_field( + "ElementMasses", FLOAT, chemistry_element_count, UNIT_CONV_MASS, 0.f, + bparts, chemistry_data.metal_mass, + "Masses of the BH particles in a given element"); + num++; + + list[num] = io_make_output_field("MetalMasses", FLOAT, + chemistry_element_count, UNIT_CONV_MASS, 0.f, + bparts, chemistry_data.metal_mass_total, + "Masses of the BH particles in a metals"); + num++; + + return num; +} + +#ifdef HAVE_HDF5 + +/** + * @brief Writes the current model of chemistry to the file + * @param h_grp The HDF5 group in which to write + * @param h_grp_columns The HDF5 group containing named columns + * @param e The #engine. + */ +INLINE static void chemistry_write_flavour(hid_t h_grp, hid_t h_grp_columns, + const struct engine *e) { + + /* Write the chemistry model */ + io_write_attribute_s(h_grp, "Chemistry Model", "KIARA"); + + /* Create an array of element names */ + const int element_name_length = 32; + char element_names[chemistry_element_count][element_name_length]; + for (int elem = 0; elem < chemistry_element_count; ++elem) { + sprintf(element_names[elem], "%s", + chemistry_get_element_name((enum chemistry_element)elem)); + } + + /* Add to the named columns */ + hsize_t dims[1] = {chemistry_element_count}; + hid_t type = H5Tcopy(H5T_C_S1); + H5Tset_size(type, element_name_length); + hid_t space = H5Screate_simple(1, dims, NULL); + hid_t dset = H5Dcreate(h_grp_columns, "ElementMassFractions", type, space, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dset, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, element_names[0]); + H5Dclose(dset); + dset = H5Dcreate(h_grp_columns, "SmoothedElementMassFractions", type, space, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dset, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, element_names[0]); + H5Dclose(dset); + + H5Tclose(type); + H5Sclose(space); +} +#endif + +#endif /* SWIFT_CHEMISTRY_IO_KIARA_H */ diff --git a/src/chemistry/KIARA/chemistry_struct.h b/src/chemistry/KIARA/chemistry_struct.h new file mode 100644 index 0000000000..06a309fd9e --- /dev/null +++ b/src/chemistry/KIARA/chemistry_struct.h @@ -0,0 +1,267 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_CHEMISTRY_STRUCT_KIARA_H +#define SWIFT_CHEMISTRY_STRUCT_KIARA_H + +#define FIREHOSE_COOLLIM 0.1f /* -u_new / u */ +#define FIREHOSE_HEATLIM 10.f /* +u_new / u */ +#define FIREHOSE_EPSILON_TOLERANCE 1.e-6 /* Minimum rel. difference to add */ + +/** + * @brief The individual elements traced in the KIARA model. + */ +enum chemistry_element { + chemistry_element_H = 0, + chemistry_element_He, + chemistry_element_C, + chemistry_element_N, + chemistry_element_O, + chemistry_element_Ne, + chemistry_element_Mg, + chemistry_element_Si, + chemistry_element_S, + chemistry_element_Ca, + chemistry_element_Fe, + chemistry_element_count +}; + +#if COOLING_GRACKLE_MODE >= 2 +/** + * @brief The individual elements traced in the Grackle dust model. + */ +enum dust_element { + dust_element_C, + dust_element_O, + dust_element_Mg, + dust_element_Si, + dust_element_S, + dust_element_Ca, + dust_element_Fe, + dust_element_count +}; +#endif + +/** + * @brief Global chemical abundance information in the KIARA model. + */ +struct chemistry_global_data { + + /*! Fraction of the particle mass in given elements at the start of the run */ + float initial_metal_mass_fraction[chemistry_element_count]; + + /*! Fraction of the particle mass in *all* metals at the start of the run */ + float initial_metal_mass_fraction_total; + + /*! Is metal diffusion turned on? */ + int diffusion_flag; + + /*! The timestep beta value from Parshikov & Medin 2002 equation 41 */ + float diffusion_beta; + + /*! The minimum time step size in internal units for diffusion */ + float time_step_min; + + /*! A limiter for how much Z/Z_init can be transferred (~0.25) */ + float max_fractional_Z_transfer; + + /*! The metal diffusion coefficient (Smag ~0.23) */ + float C_Smagorinsky; + + /*! Use Firehose wind model (1) or standard decoupled winds (0) */ + int use_firehose_wind_model; + + /*! Firehose wind model maximum density */ + float firehose_ambient_rho_max; + + /*! Firehose wind model minimum thermal energy */ + float firehose_u_floor; + + /*! Firehose wind model Mach number threshold for recoupling */ + float firehose_recoupling_mach; + + /*! Firehose wind model thermal energy ratio threshold for recoupling */ + float firehose_recoupling_u_factor; + + /*! Firehose wind model mixing mass threshold for recoupling */ + float firehose_recoupling_fmix; + + /*! Firehose threshold relative velocity (km/s) above which model is turned + * off */ + float firehose_max_velocity; + + /*! Firehose maximum fraction of particles' mass that can be mixed in a single + * step */ + float firehose_max_fmix_per_step; + + /*! Dust sputtering constant */ + float dust_sputtering_const; + + /*! Conversion factor from internal mass unit to solar mass */ + double mass_to_solar_mass; + + /*! Conversion factor from density in internal units to Hydrogen number + * density in cgs */ + double rho_to_n_cgs; + + /*! Converts temperature to internal energy */ + float temp_to_u_factor; + + /*! Converst temperature to internal units */ + float T_to_internal; + + /*! Factor to convert km/s to internal units */ + float kms_to_internal; + + /*! Convert internal units to kpc */ + float length_to_kpc; + + /*! Convert internal time to Myr */ + float time_to_Myr; +}; + +/** + * @brief Chemical abundances traced by the #part in the KIARA model. + */ +struct chemistry_part_data { + + /*! Fraction of the particle mass in a given element */ + float metal_mass_fraction[chemistry_element_count]; + + /*! Fraction of the particle mass in *all* metals */ + float metal_mass_fraction_total; + + /*! Mass coming from SNIa */ + float mass_from_SNIa; + + /*! Fraction of total gas mass in metals coming from SNIa */ + float metal_mass_fraction_from_SNIa; + + /*! Mass coming from AGB */ + float mass_from_AGB; + + /*! Fraction of total gas mass in metals coming from AGB */ + float metal_mass_fraction_from_AGB; + + /*! Mass coming from SNII */ + float mass_from_SNII; + + /*! Fraction of total gas mass in metals coming from SNII */ + float metal_mass_fraction_from_SNII; + + /*! Fraction of total gas mass in Iron coming from SNIa */ + float iron_mass_fraction_from_SNIa; + + /*! Diffusion coefficient */ + float diffusion_coefficient; + + /*! Variation of the total metal mass */ + float dZ_dt_total; + + /*! Variation of the metal mass by element */ + float dZ_dt[chemistry_element_count]; + + /*! Velocity shear tensor in internal and physical units. */ + float shear_tensor[3][3]; + +#if COOLING_GRACKLE_MODE >= 2 + /*! SFR density (physical) within smoothing kernel needed for G0 calculation + */ + float local_sfr_density; +#endif + + /*! Firehose ambient gas thermal energy */ + float u_ambient; + + /*! Firehose ambient gas density */ + float rho_ambient; + + /*! Weighting factor for ambient thermal energy sum */ + float w_ambient; + + /*! Firehose radius of outflowing stream */ + float radius_stream; + + /*! Firehose initial mass of the stream */ + float exchanged_mass; + + /*! Firehose exchanged mass this step */ + float dm; + + /*! Firehose exchanged metal fractions */ + float dm_Z[chemistry_element_count]; + + /*! Firehose exchanged dust mass */ + float dm_dust; + + /*! Firehose exchanged dust mass metals */ + float dm_dust_Z[chemistry_element_count]; + + /*! Firehose exchanged internal energy, internal units */ + double du; + + /*! Firehose exchanged velocities, internal units */ + float dv[3]; +}; + +#define chemistry_spart_data chemistry_part_data + +/** + * @brief Chemical abundances traced by the #bpart in the KIARA model. + */ +struct chemistry_bpart_data { + + /*! Mass in a given element */ + float metal_mass[chemistry_element_count]; + + /*! Mass in *all* metals */ + float metal_mass_total; + + /*! Mass coming from SNIa */ + float mass_from_SNIa; + + /*! Mass coming from AGB */ + float mass_from_AGB; + + /*! Mass coming from SNII */ + float mass_from_SNII; + + /*! Metal mass coming from SNIa */ + float metal_mass_from_SNIa; + + /*! Metal mass coming from AGB */ + float metal_mass_from_AGB; + + /*! Metal mass coming from SNII */ + float metal_mass_from_SNII; + + /*! Iron mass coming from SNIa */ + float iron_mass_from_SNIa; + + /*! Metallicity of converted part. */ + float formation_metallicity; +}; + +/** + * @brief Chemical abundances traced by the #sink in the KIARA model. + * + * Nothing here. + */ +struct chemistry_sink_data {}; + +#endif /* SWIFT_CHEMISTRY_STRUCT_KIARA_H */ diff --git a/src/chemistry/none/chemistry.h b/src/chemistry/none/chemistry.h index 813d0ffb08..cb2998f9fb 100644 --- a/src/chemistry/none/chemistry.h +++ b/src/chemistry/none/chemistry.h @@ -95,8 +95,8 @@ static INLINE void chemistry_print_backend( * @param cosmo The current cosmological model. */ __attribute__((always_inline)) INLINE static void chemistry_end_density( - struct part *restrict p, const struct chemistry_global_data *cd, - const struct cosmology *cosmo) {} + struct part *restrict p, struct xpart *restrict xp, + const struct chemistry_global_data *cd, const struct cosmology *cosmo) {} /** * @brief Finishes the gradient calculation. @@ -139,7 +139,7 @@ __attribute__((always_inline)) INLINE static void chemistry_prepare_force( * @param chem_data The global properties of the chemistry scheme. */ __attribute__((always_inline)) INLINE static void chemistry_end_force( - struct part *restrict p, const struct cosmology *cosmo, + struct part *restrict p, struct xpart *xp, const struct cosmology *cosmo, const int with_cosmology, const double time, const double dt, const struct chemistry_global_data *cd) {} diff --git a/src/chemistry/none/chemistry_iact.h b/src/chemistry/none/chemistry_iact.h index 3bff6980dd..1f08d21250 100644 --- a/src/chemistry/none/chemistry_iact.h +++ b/src/chemistry/none/chemistry_iact.h @@ -122,9 +122,11 @@ runner_iact_nonsym_gradient_diffusion(const float r2, const float dx[3], */ __attribute__((always_inline)) INLINE static void runner_iact_diffusion( const float r2, const float dx[3], const float hi, const float hj, - struct part *restrict pi, struct part *restrict pj, const float a, + struct part *restrict pi, struct part *restrict pj, + struct xpart *restrict xpi, struct xpart *restrict xpj, const float a, const float H, const float time_base, const integertime_t t_current, const struct cosmology *cosmo, const int with_cosmology, + const struct phys_const *phys_const, const struct chemistry_global_data *chem_data) {} /** diff --git a/src/chemistry_debug.h b/src/chemistry_debug.h index 077e1a7424..0ebfb139ce 100644 --- a/src/chemistry_debug.h +++ b/src/chemistry_debug.h @@ -35,6 +35,8 @@ #include "./chemistry/QLA/chemistry_debug.h" #elif defined(CHEMISTRY_EAGLE) #include "./chemistry/EAGLE/chemistry_debug.h" +#elif defined(CHEMISTRY_KIARA) +#include "./chemistry/KIARA/chemistry_debug.h" #else #error "Invalid choice of chemistry function." #endif diff --git a/src/chemistry_io.h b/src/chemistry_io.h index 83cbba15c3..bf31ea251a 100644 --- a/src/chemistry_io.h +++ b/src/chemistry_io.h @@ -35,6 +35,8 @@ #include "./chemistry/QLA/chemistry_io.h" #elif defined(CHEMISTRY_EAGLE) #include "./chemistry/EAGLE/chemistry_io.h" +#elif defined(CHEMISTRY_KIARA) +#include "./chemistry/KIARA/chemistry_io.h" #else #error "Invalid choice of chemistry function." #endif diff --git a/src/chemistry_struct.h b/src/chemistry_struct.h index 9089db4df1..ace430c31e 100644 --- a/src/chemistry_struct.h +++ b/src/chemistry_struct.h @@ -40,6 +40,8 @@ #include "./chemistry/QLA/chemistry_struct.h" #elif defined(CHEMISTRY_EAGLE) #include "./chemistry/EAGLE/chemistry_struct.h" +#elif defined(CHEMISTRY_KIARA) +#include "./chemistry/KIARA/chemistry_struct.h" #else #error "Invalid choice of chemistry function." #endif diff --git a/src/common_io.c b/src/common_io.c index 720b2a08ac..8a9e1552d1 100644 --- a/src/common_io.c +++ b/src/common_io.c @@ -1763,7 +1763,7 @@ void io_select_hydro_fields(const struct part *const parts, *num_fields += star_formation_write_particles(parts, xparts, list + *num_fields); if (with_rt) { - *num_fields += rt_write_particles(parts, list + *num_fields); + *num_fields += rt_write_particles(parts, xparts, list + *num_fields); } *num_fields += extra_io_write_particles(parts, xparts, list + *num_fields, with_cosmology); diff --git a/src/const.h b/src/const.h index 75be9963f5..fddaa843db 100644 --- a/src/const.h +++ b/src/const.h @@ -93,5 +93,6 @@ /* GRACKLE doesn't really like exact zeroes, so use something * comparatively small instead. */ #define RT_GEAR_TINY_MASS_FRACTION 1.e-20 +#define RT_KIARA_TINY_MASS_FRACTION 1.e-20 #endif /* SWIFT_CONST_H */ diff --git a/src/cooling.h b/src/cooling.h index 377271cc02..56dd321dd0 100644 --- a/src/cooling.h +++ b/src/cooling.h @@ -53,6 +53,8 @@ #include "./cooling/EAGLE/cooling.h" #elif defined(COOLING_PS2020) #include "./cooling/PS2020/cooling.h" +#elif defined(COOLING_KIARA) +#include "./cooling/KIARA/cooling.h" #else #error "Invalid choice of cooling function." #endif diff --git a/src/cooling/KIARA/cooling.c b/src/cooling/KIARA/cooling.c new file mode 100644 index 0000000000..66475f7c4f --- /dev/null +++ b/src/cooling/KIARA/cooling.c @@ -0,0 +1,2040 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +/** + * @file src/cooling/KIARA/cooling.c + * @brief Cooling using the GRACKLE 3.x library. + */ + +#include + +/* Some standard headers. */ +#include +#include +#include +#include + +/* Include header */ +#include "cooling.h" + +/* Some standard headers. */ +#include +#include +#include +#include + +/* The grackle library itself */ +#include +extern chemistry_data *grackle_data; + +/* Local includes. */ +#include "chemistry.h" +#include "cooling_io.h" +#include "entropy_floor.h" +#include "error.h" +#include "fof.h" +#include "hydro.h" +#include "parser.h" +#include "part.h" +#include "physical_constants.h" +#include "star_formation.h" +#include "units.h" + +/** + * @brief Common operations performed on the cooling function at a + * given time-step or redshift. Predominantly used to read cooling tables + * above and below the current redshift, if not already read in. + * + * Also calls the additional H reionisation energy injection if need be. + * + * @param cosmo The current cosmological model. + * @param pressure_floor Properties of the pressure floor. + * @param cooling The #cooling_function_data used in the run. + * @param s The space data, including a pointer to array of particles + * @param time The current system time + */ +void cooling_update(const struct phys_const *phys_const, + const struct cosmology *cosmo, + const struct pressure_floor_props *pressure_floor, + struct cooling_function_data *cooling, struct space *s, + const double time) { + + /* set current time */ + if (cooling->redshift == -1) { + cooling->units.a_value = cosmo->a; + } else { + cooling->units.a_value = 1. / (1. + cooling->redshift); + } +} + +/** + * @brief Print the chemical network + * + * @param xp The #xpart to print + */ +void cooling_print_fractions(const struct xpart *restrict xp) { + + const struct cooling_xpart_data tmp = xp->cooling_data; +#if COOLING_GRACKLE_MODE > 0 + message("HI %g, HII %g, HeI %g, HeII %g, HeIII %g, e %g", tmp.HI_frac, + tmp.HII_frac, tmp.HeI_frac, tmp.HeII_frac, tmp.HeIII_frac, + tmp.e_frac); +#endif + +#if COOLING_GRACKLE_MODE > 1 + message("HM %g, H2I %g, H2II %g", tmp.HM_frac, tmp.H2I_frac, tmp.H2II_frac); +#endif + +#if COOLING_GRACKLE_MODE > 2 + message("DI %g, DII %g, HDI %g", tmp.DI_frac, tmp.DII_frac, tmp.HDI_frac); +#endif + message("Metal: %g", tmp.metal_frac); +} + +/** + * @brief Initializes grackle particle quantities + * assuming purely neutral gas + * + * Nothing to do here. + * + * @param phys_const The physical constant in internal units. + * @param us The unit system. + * @param hydro_props The properties of the hydro scheme. + * @param cosmo The current cosmological model. + * @param cooling The properties of the cooling function. + * @param p Pointer to the particle data. + * @param xp Pointer to the extended particle data. + */ +__attribute__((always_inline)) INLINE void cooling_grackle_init_part( + const struct cooling_function_data *cooling, struct part *restrict p, + struct xpart *restrict xp) { + +#if COOLING_GRACKLE_MODE >= 1 + gr_float zero = 1.e-20; + zero = 0.f; + + /* primordial chemistry >= 1: Start with everything neutral (as in dark ages) + */ + xp->cooling_data.HI_frac = + p->chemistry_data.metal_mass_fraction[chemistry_element_H]; + xp->cooling_data.HII_frac = zero; + xp->cooling_data.HeI_frac = + p->chemistry_data.metal_mass_fraction[chemistry_element_He]; + xp->cooling_data.HeII_frac = zero; + xp->cooling_data.HeIII_frac = zero; + xp->cooling_data.e_frac = xp->cooling_data.HII_frac + + 0.25 * xp->cooling_data.HeII_frac + + 0.5 * xp->cooling_data.HeIII_frac; +#endif // MODE >= 1 + +#if COOLING_GRACKLE_MODE >= 2 + /* primordial chemistry >= 2 */ + xp->cooling_data.HM_frac = zero; + xp->cooling_data.H2I_frac = zero; + xp->cooling_data.H2II_frac = zero; +#endif // MODE >= 2 + +#if COOLING_GRACKLE_MODE >= 3 + /* primordial chemistry >= 3 */ + xp->cooling_data.DI_frac = grackle_data->DeuteriumToHydrogenRatio * + grackle_data->HydrogenFractionByMass; + xp->cooling_data.DII_frac = zero; + xp->cooling_data.HDI_frac = zero; +#endif // MODE >= 3 +} + +/** + * @brief Sets the cooling properties of the (x-)particles to a valid start + * state. + * + * @param phys_const The #phys_const. + * @param us The #unit_system. + * @param hydro_props The #hydro_props. + * @param cosmo The #cosmology. + * @param cooling The properties of the cooling function. + * @param p Pointer to the particle data. + * @param xp Pointer to the extended particle data. + */ +void cooling_first_init_part(const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct hydro_props *hydro_props, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *cooling, + struct part *restrict p, + struct xpart *restrict xp) { + + xp->cooling_data.radiated_energy = 0.f; + xp->cooling_data.time_last_event = -cooling->thermal_time; + + /* Initialize grackle ionization fractions */ + cooling_grackle_init_part(cooling, p, xp); + + p->cooling_data.subgrid_fcold = 0.f; + + /* Initialize dust properties */ +#if COOLING_GRACKLE_MODE >= 2 + p->cooling_data.dust_mass = 0.f; + for (int i = 0; i < chemistry_element_count; i++) { + p->cooling_data.dust_mass_fraction[i] = 0.f; + } + + p->cooling_data.dust_temperature = 0.f; +#endif +} + +/** + * @brief Perform additional init on the cooling properties of the + * (x-)particles that requires the density to be known. + * + * Nothing to do here. + * + * @param phys_const The physical constant in internal units. + * @param us The unit system. + * @param hydro_props The properties of the hydro scheme. + * @param cosmo The current cosmological model. + * @param cooling The properties of the cooling function. + * @param p Pointer to the particle data. + * @param xp Pointer to the extended particle data. + */ +__attribute__((always_inline)) INLINE void cooling_post_init_part( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct hydro_props *hydro_props, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *cooling, const struct part *restrict p, + struct xpart *restrict xp) {} + +/** + * @brief Returns the total radiated energy by this particle. + * + * @param xp The extended particle data + */ +float cooling_get_radiated_energy(const struct xpart *restrict xp) { + + return xp->cooling_data.radiated_energy; +} + +/** + * @brief Computes H and H2 self-shielding for G0 calculation. + * Based on Schauer et al. 2015 eqs 8,9. + * + * @param p The particle to act upon. + * @param cooling The properties of the cooling function. + */ +__attribute__((always_inline)) INLINE static float +cooling_compute_self_shielding(const struct part *restrict p, + const struct cooling_function_data *cooling) { + + float fH2_shield = 1.f; +#if COOLING_GRACKLE_MODE >= 2 + float T_ism = p->cooling_data.subgrid_temp; + if (T_ism > 0.f) { + /* Compute self-shielding from H */ + const float a = cooling->units.a_value; + const float a3_inv = 1.f / (a * a * a); + const double rho_grad_norm2 = p->rho_gradient[0] * p->rho_gradient[0] + + p->rho_gradient[1] * p->rho_gradient[1] + + p->rho_gradient[2] * p->rho_gradient[2]; + const double rho_grad_norm_inv = + (rho_grad_norm2 > 0.) ? 1. / sqrt(rho_grad_norm2) : 0.; + const double rho_com = hydro_get_comoving_density(p); + double L_eff_com = rho_com * rho_grad_norm_inv; + const double L_eff_com_max = kernel_gamma * p->h; + const double L_eff_com_min = MIN_SHIELD_H_FRAC * p->h; + L_eff_com = fmin(L_eff_com, L_eff_com_max); + L_eff_com = fmax(L_eff_com, L_eff_com_min); + const double L_eff_in_cm = L_eff_com * a * cooling->units.length_units; + const double rho_to_n_cgs = + cooling->units.density_units * 5.97729e23 * 0.75; + const double rho_cgs_phys = rho_com * a3_inv * rho_to_n_cgs; + const double NH_cgs = rho_cgs_phys * L_eff_in_cm; + const double xH = NH_cgs * 3.50877e-24; + const double fH_shield = pow(1.f + xH, -1.62) * exp(-0.149 * xH); + + fH2_shield *= fH_shield; + /* Extra self-shielding from H2 if present - DON'T DO THIS HERE SINCE IT IS IN CRACKLE + const float fH2 = p->sf_data.H2_fraction; + if (fH2 > 0.f) { + const double NH2_cgs = fH2 * NH_cgs; + const double DH2_cgs = 1.e-5 * sqrt(2.*1.38e-16 * T_ism * 2.98864e23); + const double xH2 = NH2_cgs * 1.18133e-14; + fH2_shield *= 0.9379 * pow(1.f + xH2 / DH2_cgs, -1.879) + + 0.03465 * pow(1.f + xH2, -0.473) * exp(-2.293e-4 * sqrt(1.f + xH2)); + } */ + } +#endif + + return fH2_shield; +} + +/** + * @brief Returns the value of G0 for given particle p + * + * @param p Pointer to the particle data. + * @param rho Physical density in system units. + * @param cooling The properties of the cooling function. + * @param dt The cooling timestep. + * + */ +__attribute__((always_inline)) INLINE static float cooling_compute_G0( + const struct part *restrict p, const float rho, + const struct cooling_function_data *cooling, const float mstar, + const float ssfr, const double dt) { + + float G0 = 0.f; + float fH2_shield = 1.f; + /* Determine ISRF in Habing units based on chosen method */ + if (cooling->G0_computation_method == 0) { + G0 = 0.f; + } + else if (cooling->G0_computation_method == 1) { + fH2_shield = cooling_compute_self_shielding(p, cooling); + G0 = fH2_shield * p->chemistry_data.local_sfr_density * cooling->G0_factor1; + } + else if (cooling->G0_computation_method == 2) { + G0 = ssfr * cooling->G0_factor2; + } + else if (cooling->G0_computation_method == 3) { + if (ssfr > 0.) { + G0 = ssfr * cooling->G0_factor2; + } + else { + fH2_shield = cooling_compute_self_shielding(p, cooling); + G0 = fH2_shield * p->chemistry_data.local_sfr_density * + cooling->G0_factor1; + } + } + else if (cooling->G0_computation_method == -3) { + if (p->chemistry_data.local_sfr_density > 0.) { + fH2_shield = cooling_compute_self_shielding(p, cooling); + G0 = fH2_shield * p->chemistry_data.local_sfr_density * + cooling->G0_factor1; + } + else { + G0 = ssfr * cooling->G0_factor2; + } + } +#if COOLING_GRACKLE_MODE >= 2 + else if (cooling->G0_computation_method == 4) { + /* Remember SNe_ThisTimeStep stores SN **rate** */ + G0 = p->cooling_data.SNe_ThisTimeStep * cooling->G0_factorSNe * dt; + } + else if (cooling->G0_computation_method == 5) { + float pssfr = max(p->sf_data.SFR, 0.f); + pssfr /= max(mstar, 8. * p->mass); + G0 = max(ssfr, pssfr) * cooling->G0_factor2 + + p->cooling_data.SNe_ThisTimeStep * cooling->G0_factorSNe * dt; + } +#endif + else { + error("G0_computation_method %d not recognized\n", + cooling->G0_computation_method); + } + + /* Scale G0 by user-input value */ + G0 *= cooling->G0_multiplier; + + if (mstar * 1.e10 > 1.e9 && p->id % 100000 == 0 && p->chemistry_data.local_sfr_density > 0) { + message("G0: id=%lld M*=%g SFR=%g rho_sfr=%g SNe=%g Td=%g fshield=%g G0=%g", + p->id, + mstar * 1.e10, + mstar * 1.e10 * + ssfr / (1.e6 * cooling->time_to_Myr), + p->chemistry_data.local_sfr_density * 0.002 / 1.6, + p->cooling_data.SNe_ThisTimeStep, + p->cooling_data.dust_temperature, + fH2_shield, + G0); + } + + return G0; +} + +/** + * @brief Prints the properties of the cooling model to stdout. + * + * @param cooling The properties of the cooling function. + */ +void cooling_print_backend(const struct cooling_function_data *cooling) { + + if (cooling->chemistry.use_grackle == 1) { + message("Cooling function is 'Grackle', mode = %i", + cooling->chemistry.primordial_chemistry); + } else if (cooling->chemistry.use_grackle == 2) { + message("Cooling function is 'Crackle', mode = %i", + cooling->chemistry.primordial_chemistry); + } + + message("CloudyTable = %s", cooling->cloudy_table); + message("Redshift = %g", cooling->redshift); + message("UV background flag = %d", cooling->with_uv_background); + message("Metal cooling flag = %i", cooling->chemistry.metal_cooling); + message("Self Shielding flag = %i", cooling->self_shielding_method); + message("Max subgrid density (internal units) = %g", + cooling->max_subgrid_density); + message("Thermal time = %g", cooling->thermal_time); + message("Specific Heating Rates flag = %i", + cooling->provide_specific_heating_rates); + message("Volumetric Heating Rates flag = %i", + cooling->provide_volumetric_heating_rates); + message("Units:"); + message("\tComoving = %i", cooling->units.comoving_coordinates); + message("\tLength = %g", cooling->units.length_units); + message("\tNumber Density = %g", cooling->units.density_units); + message("\tTime = %g", cooling->units.time_units); + message("\tScale Factor = %g (units: %g)", cooling->units.a_value, + cooling->units.a_units); +} + +/** + * @brief copy a #xpart to the grackle data + * + * @param data The grackle_field_data structure from grackle. + * @param p The #part + * @param xp The #xpart + * @param rho Particle density + */ +#if COOLING_GRACKLE_MODE >= 1 +void cooling_copy_to_grackle1(grackle_field_data *data, const struct part *p, + const struct xpart *xp, gr_float rho, + gr_float species_densities[N_SPECIES]) { + /* HI */ + species_densities[0] = xp->cooling_data.HI_frac * rho; + data->HI_density = &species_densities[0]; + + /* HII */ + species_densities[1] = xp->cooling_data.HII_frac * rho; + data->HII_density = &species_densities[1]; + + /* HeI */ + species_densities[2] = xp->cooling_data.HeI_frac * rho; + data->HeI_density = &species_densities[2]; + + /* HeII */ + species_densities[3] = xp->cooling_data.HeII_frac * rho; + data->HeII_density = &species_densities[3]; + + /* HeIII */ + species_densities[4] = xp->cooling_data.HeIII_frac * rho; + data->HeIII_density = &species_densities[4]; + + /* electrons */ + species_densities[5] = xp->cooling_data.e_frac * rho; + data->e_density = &species_densities[5]; +} +#else +void cooling_copy_to_grackle1(grackle_field_data *data, const struct part *p, + const struct xpart *xp, gr_float rho, + gr_float species_densities[N_SPECIES]) { + data->HI_density = NULL; + data->HII_density = NULL; + data->HeI_density = NULL; + data->HeII_density = NULL; + data->HeIII_density = NULL; + data->e_density = NULL; +} +#endif + +/** + * @brief copy a #xpart to the grackle data + * + * @param data The grackle_field_data structure from grackle. + * @param p The #part + * @param xp The #xpart + * @param rho Particle density + */ +#if COOLING_GRACKLE_MODE >= 2 +void cooling_copy_to_grackle2( + grackle_field_data *data, const struct part *p, const struct xpart *xp, + const struct cooling_function_data *restrict cooling, const double dt, + gr_float rho, gr_float species_densities[N_SPECIES]) { + /* HM */ + species_densities[6] = xp->cooling_data.HM_frac * rho; + data->HM_density = &species_densities[6]; + + /* H2I */ + species_densities[7] = xp->cooling_data.H2I_frac * rho; + data->H2I_density = &species_densities[7]; + + /* H2II */ + species_densities[8] = xp->cooling_data.H2II_frac * rho; + data->H2II_density = &species_densities[8]; + + /* Dust model */ + if (cooling->use_grackle_dust_evol == 1) { + /* Load dust and metal info */ + species_densities[20] = + p->cooling_data.dust_mass / p->mass * species_densities[12]; + data->dust_density = &species_densities[20]; + species_densities[21] = + p->cooling_data.SNe_ThisTimeStep * dt / p->mass * species_densities[12]; + /* need to pass the number of SNe per volume of particle; + * recall SNe_ThisTimeStep is the SNe rate */ + + // if (species_densities[21] > 1.e15) message("SNe_density: %g %g %g + // %g\n",p->mass, species_densities[12], p->cooling_data.SNe_ThisTimeStep, + // species_densities[21]); + data->SNe_ThisTimeStep = &species_densities[21]; + // if( chemistry_get_total_metal_mass_fraction_for_cooling(p)>0.f) + // message("Zsm= %g Zp= %g Z= %g Zd= + // %g",chemistry_get_total_metal_mass_fraction_for_cooling(p), + // p->chemistry_data.metal_mass_fraction_total, species_densities[19], + // species_densities[20]); + + /* Determine ISRF in Habing units based on chosen method, -1 == non-ISM */ + if (p->cooling_data.subgrid_temp == 0.f) { + species_densities[22] = -1.f; + } else { + species_densities[22] = p->cooling_data.G0; + } + + data->isrf_habing = &species_densities[22]; + + const double rho_grad_norm2 = p->rho_gradient[0] * p->rho_gradient[0] + + p->rho_gradient[1] * p->rho_gradient[1] + + p->rho_gradient[2] * p->rho_gradient[2]; + const double rho_grad_norm_inv = + (rho_grad_norm2 > 0.) ? 1. / sqrt(rho_grad_norm2) : 0.; + const double rho_com = hydro_get_comoving_density(p); + double L_eff_com = rho_com * rho_grad_norm_inv; + const double L_eff_com_max = kernel_gamma * p->h; + const double L_eff_com_min = MIN_SHIELD_H_FRAC * p->h; + L_eff_com = fmin(L_eff_com, L_eff_com_max); + L_eff_com = fmax(L_eff_com, L_eff_com_min); + + species_densities[23] = L_eff_com * cooling->units.a_value; + data->H2_self_shielding_length = &species_densities[23]; + + /* Load gas metallicities */ + for (int i = 0; i < chemistry_element_count; i++) { + species_densities[24 + i] = max( + p->chemistry_data.metal_mass_fraction[i] * species_densities[12], 0.); + species_densities[24 + chemistry_element_count + i] = + max(p->cooling_data.dust_mass_fraction[i] * species_densities[20], 0); + // if (i>0) printf("dust densities: %d %g %g + // %g\n",i,species_densities[23+chemistry_element_count+i],species_densities[23+chemistry_element_count+i]/data->dust_density[0],p->cooling_data.dust_mass_fraction[i]*p->mass + // / p->cooling_data.dust_mass); + } + + data->He_gas_metalDensity = &species_densities[25]; + data->C_gas_metalDensity = &species_densities[26]; + data->N_gas_metalDensity = &species_densities[27]; + data->O_gas_metalDensity = &species_densities[28]; + data->Ne_gas_metalDensity = &species_densities[29]; + data->Mg_gas_metalDensity = &species_densities[30]; + data->Si_gas_metalDensity = &species_densities[31]; + data->S_gas_metalDensity = &species_densities[32]; + data->Ca_gas_metalDensity = &species_densities[32]; + data->Fe_gas_metalDensity = &species_densities[34]; + /* Load dust metallicities */ + data->He_dust_metalDensity = &species_densities[36]; + data->C_dust_metalDensity = &species_densities[37]; + data->N_dust_metalDensity = &species_densities[38]; + data->O_dust_metalDensity = &species_densities[39]; + data->Ne_dust_metalDensity = &species_densities[40]; + data->Mg_dust_metalDensity = &species_densities[41]; + data->Si_dust_metalDensity = &species_densities[42]; + data->S_dust_metalDensity = &species_densities[43]; + data->Ca_dust_metalDensity = &species_densities[44]; + data->Fe_dust_metalDensity = &species_densities[45]; + } else { + data->dust_density = NULL; + data->SNe_ThisTimeStep = NULL; + data->isrf_habing = NULL; + data->He_gas_metalDensity = NULL; + data->C_gas_metalDensity = NULL; + data->N_gas_metalDensity = NULL; + data->O_gas_metalDensity = NULL; + data->Ne_gas_metalDensity = NULL; + data->Mg_gas_metalDensity = NULL; + data->Si_gas_metalDensity = NULL; + data->S_gas_metalDensity = NULL; + data->Ca_gas_metalDensity = NULL; + data->Fe_gas_metalDensity = NULL; + data->He_dust_metalDensity = NULL; + data->C_dust_metalDensity = NULL; + data->N_dust_metalDensity = NULL; + data->O_dust_metalDensity = NULL; + data->Ne_dust_metalDensity = NULL; + data->Mg_dust_metalDensity = NULL; + data->Si_dust_metalDensity = NULL; + data->S_dust_metalDensity = NULL; + data->Ca_dust_metalDensity = NULL; + data->Fe_dust_metalDensity = NULL; + } +} +#else +void cooling_copy_to_grackle2( + grackle_field_data *data, const struct part *p, const struct xpart *xp, + const struct cooling_function_data *restrict cooling, const double dt, + gr_float rho, gr_float species_densities[N_SPECIES]) { + data->HM_density = NULL; + data->H2I_density = NULL; + data->H2II_density = NULL; +} +#endif + +/** + * @brief copy a #xpart to the grackle data + * + * @param data The grackle_field_data structure from grackle. + * @param p The #part + * @param xp The #xpart + * @param rho Particle density + */ +#if COOLING_GRACKLE_MODE >= 3 +void cooling_copy_to_grackle3(grackle_field_data *data, const struct part *p, + const struct xpart *xp, gr_float rho, + gr_float species_densities[N_SPECIES]) { + /* DI */ + species_densities[9] = xp->cooling_data.DI_frac * rho; + data->DI_density = &species_densities[9]; + + /* DII */ + species_densities[10] = xp->cooling_data.DII_frac * rho; + data->DII_density = &species_densities[10]; + + /* HDI */ + species_densities[11] = xp->cooling_data.HDI_frac * rho; + data->HDI_density = &species_densities[11]; +} +#else +void cooling_copy_to_grackle3(grackle_field_data *data, const struct part *p, + const struct xpart *xp, gr_float rho, + gr_float species_densities[N_SPECIES]) { + data->DI_density = NULL; + data->DII_density = NULL; + data->HDI_density = NULL; +} +#endif + +/** + * @brief copy the grackle data to a #xpart + * + * @param data The grackle_field_data structure from grackle. + * @param p The #part. + * @param xp The #xpart. + * @param rho The particle density. + */ +#if COOLING_GRACKLE_MODE >= 1 +void cooling_copy_from_grackle1(grackle_field_data *data, const struct part *p, + struct xpart *xp, gr_float rho) { + + const double rhoinv = 1.f / rho; + /* HI */ + xp->cooling_data.HI_frac = *data->HI_density * rhoinv; + + /* HII */ + xp->cooling_data.HII_frac = *data->HII_density * rhoinv; + + /* HeI */ + xp->cooling_data.HeI_frac = *data->HeI_density * rhoinv; + + /* HeII */ + xp->cooling_data.HeII_frac = *data->HeII_density * rhoinv; + + /* HeIII */ + xp->cooling_data.HeIII_frac = *data->HeIII_density * rhoinv; + + /* e */ + xp->cooling_data.e_frac = *data->e_density * rhoinv; +} +#else +void cooling_copy_from_grackle1(grackle_field_data *data, const struct part *p, + struct xpart *xp, gr_float rho) {} +#endif + +/** + * @brief copy the grackle data to a #xpart. + * + * @param data The grackle_field_data structure from grackle. + * @param p The #part. + * @param xp The #xpart. + * @param rho The particle density. + */ +#if COOLING_GRACKLE_MODE >= 2 +void cooling_copy_from_grackle2( + grackle_field_data *data, struct part *p, struct xpart *xp, + const struct cooling_function_data *restrict cooling, gr_float rho) { + double rhoinv = 1.f / rho; + + /* HM */ + xp->cooling_data.HM_frac = *data->HM_density * rhoinv; + /* H2I */ + xp->cooling_data.H2I_frac = *data->H2I_density * rhoinv; + /* H2II */ + xp->cooling_data.H2II_frac = *data->H2II_density * rhoinv; + + /* Dust model */ + if (cooling->use_grackle_dust_evol == 1) { + /* Load gas metallicities */ + p->chemistry_data.metal_mass_fraction_total = *data->metal_density * rhoinv; + p->chemistry_data.metal_mass_fraction[1] = + *data->He_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[2] = + *data->C_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[3] = + *data->N_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[4] = + *data->O_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[5] = + *data->Ne_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[6] = + *data->Mg_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[7] = + *data->Si_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[8] = + *data->S_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[9] = + *data->Ca_gas_metalDensity * rhoinv; + p->chemistry_data.metal_mass_fraction[10] = + *data->Fe_gas_metalDensity * rhoinv; + + /* Load dust metallicities */ + p->cooling_data.dust_mass = *data->dust_density * p->mass * rhoinv; + + if (p->cooling_data.dust_mass > 0.5 * p->mass) { + warning("DUST > METALS Mg=%g Zg=%g mdust=%g mmet=%g\n", p->mass, + chemistry_get_total_metal_mass_fraction_for_cooling(p), + p->cooling_data.dust_mass, + p->mass * chemistry_get_total_metal_mass_fraction_for_cooling(p)); + } + + p->cooling_data.dust_mass_fraction[0] = 0.f; + if (*data->dust_density > 0.f) { + rhoinv = 1. / *data->dust_density; + + const double rhodust[chemistry_element_count - 1] = { + *data->He_dust_metalDensity, *data->C_dust_metalDensity, + *data->N_dust_metalDensity, *data->O_dust_metalDensity, + *data->Ne_dust_metalDensity, *data->Mg_dust_metalDensity, + *data->Si_dust_metalDensity, *data->S_dust_metalDensity, + *data->Ca_dust_metalDensity, *data->Fe_dust_metalDensity}; + + /* no Helium metal density */ + assert(rhodust[0] == 0.); + + for (int i = 1; i < chemistry_element_count; i++) { + p->cooling_data.dust_mass_fraction[i] = rhodust[i - 1] * rhoinv; + p->cooling_data.dust_mass_fraction[0] += + p->cooling_data.dust_mass_fraction[i]; + } + } else { + for (int i = 1; i < chemistry_element_count; i++) { + p->cooling_data.dust_mass_fraction[i] = 0.f; + } + } + } +} +#else +void cooling_copy_from_grackle2( + grackle_field_data *data, struct part *p, struct xpart *xp, + const struct cooling_function_data *restrict cooling, gr_float rho) {} +#endif + +/** + * @brief copy the grackle data to a #xpart + * + * @param data The grackle_field_data structure from grackle. + * @param p The #part. + * @param xp The #xpart. + * @param rho The particle density. + */ +#if COOLING_GRACKLE_MODE > 2 +void cooling_copy_from_grackle3(grackle_field_data *data, const struct part *p, + struct xpart *xp, gr_float rho) { + + const double rhoinv = 1.f / rho; + /* DI */ + xp->cooling_data.DI_frac = *data->DI_density * rhoinv; + + /* DII */ + xp->cooling_data.DII_frac = *data->DII_density * rhoinv; + + /* HDI */ + xp->cooling_data.HDI_frac = *data->HDI_density * rhoinv; +} +#else +void cooling_copy_from_grackle3(grackle_field_data *data, const struct part *p, + struct xpart *xp, gr_float rho) {} +#endif + +/** + * @brief copy a #xpart to the grackle data + * + * Warning this function creates some variable, therefore the grackle call + * should be in a block that still has the variables. + * + * @param data The grackle_field_data structure from grackle. + * @param p The #part. + * @param xp The #xpart. + * @param rho The particle density. + */ +void cooling_copy_to_grackle( + grackle_field_data *data, const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, const struct part *p, + const struct xpart *xp, const double dt, const double T_warm, + gr_float species_densities[N_SPECIES], gr_float *iact_rates, int mode) { + + int i; + /* set values */ + /* grid */ + data->grid_dx = 0.f; + data->grid_rank = GRACKLE_RANK; + + data->grid_dimension = malloc(GRACKLE_RANK * sizeof(int)); + data->grid_start = malloc(GRACKLE_RANK * sizeof(int)); + data->grid_end = malloc(GRACKLE_RANK * sizeof(int)); + for (i = 0; i < 3; i++) { + /* The active dimension not including ghost zones */ + data->grid_dimension[i] = 1; + data->grid_start[i] = 0; + data->grid_end[i] = 0; + } + + data->grid_dimension[0] = GRACKLE_NPART; + data->grid_end[0] = GRACKLE_NPART - 1; + + /* get particle density, internal energy in + physical coordinates (still code units) */ + double T_subgrid = cooling_get_subgrid_temperature(p, xp); + /* mode 1 is cooling time, here we want non-subgrid values in all cases */ + if (mode == 1) { + species_densities[12] = hydro_get_physical_density(p, cosmo); + species_densities[13] = hydro_get_physical_internal_energy(p, xp, cosmo); + species_densities[14] = cooling->T_CMB_0 * (1.f + cosmo->z); + species_densities[15] = 0.f; + data->grid_end[0] = -1; // this signals to crackle to turn off UVB + } + /* non-subgrid case, here we set the floor temperature + * by the EoS (if applicable). Note the cold fraction has a small + * limit otherwise one can get underflows in crackle. */ + else if (p->cooling_data.subgrid_temp == 0. || + p->cooling_data.subgrid_fcold <= 1.e-6) { + species_densities[12] = hydro_get_physical_density(p, cosmo); + species_densities[13] = hydro_get_physical_internal_energy(p, xp, cosmo); + species_densities[14] = T_warm; + /* specific_heating_rate has to be in cgs units; + no unit conversion done within grackle */ + species_densities[15] = + hydro_get_physical_internal_energy_dt(p, cosmo) * cooling->dudt_units; + } + /* subgrid ISM case, use subgrid ISM values and set floor by T_CMB */ + else { + /* Physical sub-grid density*/ + species_densities[12] = + cooling_get_subgrid_density(p, xp) * p->cooling_data.subgrid_fcold; + /* Physical internal energy */ + species_densities[13] = cooling_convert_temp_to_u( + T_subgrid, xp->cooling_data.e_frac, cooling, p); + /* CMB temp is floor*/ + species_densities[14] = cooling->T_CMB_0 * (1.f + cosmo->z); + /* If tracking H2, turn off specific heating rate in ISM. */ + species_densities[15] = 0.f; + if (cooling->ism_adiabatic_heating_method == 1) { + species_densities[15] += + hydro_get_physical_internal_energy_dt(p, cosmo) * cooling->dudt_units; + } + } + /* load into grackle structure */ + data->density = &species_densities[12]; + data->internal_energy = &species_densities[13]; + data->temperature_floor = &species_densities[14]; + data->specific_heating_rate = &species_densities[15]; + + /* velocity (maybe not needed?) */ + species_densities[16] = xp->v_full[0] * cosmo->a_inv; + species_densities[17] = xp->v_full[1] * cosmo->a_inv; + species_densities[18] = xp->v_full[2] * cosmo->a_inv; + data->x_velocity = &species_densities[16]; + data->y_velocity = &species_densities[17]; + data->z_velocity = &species_densities[18]; + + cooling_copy_to_grackle1(data, p, xp, species_densities[12], + species_densities); + cooling_copy_to_grackle2(data, p, xp, cooling, dt, species_densities[12], + species_densities); + cooling_copy_to_grackle3(data, p, xp, species_densities[12], + species_densities); + + /* RT heating and ionisation rates */ + data->RT_heating_rate = &iact_rates[0]; + data->RT_HI_ionization_rate = &iact_rates[1]; + data->RT_HeI_ionization_rate = &iact_rates[2]; + data->RT_HeII_ionization_rate = &iact_rates[3]; + data->RT_H2_dissociation_rate = &iact_rates[4]; + + species_densities[19] = + chemistry_get_total_metal_mass_fraction_for_cooling(p) * + species_densities[12]; + data->metal_density = &species_densities[19]; + + for (i = 0; i < N_SPECIES; i++) { + if (fpclassify(species_densities[i]) == FP_NAN || + fpclassify(species_densities[i]) == FP_INFINITE) { + error( + "Passing a non-finite value to grackle! " + "i=%d / %d, species_densities[i]=%g\n", + i, N_SPECIES, species_densities[i]); + } + } +} + +/** + * @brief copy a #xpart to the grackle data + * + * Warning this function creates some variable, therefore the grackle call + * should be in a block that still has the variables. + * + * @param data The grackle_field_data structure from grackle. + * @param p The #part. + * @param xp The #xpart. + * @param rho The particle density. + */ +void cooling_copy_from_grackle( + grackle_field_data *data, struct part *p, struct xpart *xp, + const struct cooling_function_data *restrict cooling, gr_float rho) { + + cooling_copy_from_grackle1(data, p, xp, rho); + cooling_copy_from_grackle2(data, p, xp, cooling, rho); + cooling_copy_from_grackle3(data, p, xp, rho); +} + +/** + * @brief free memory associated with grackle driver + * + * @param data The grackle_field_data structure from grackle. + */ +void cooling_grackle_free_data(grackle_field_data *data) { + + free(data->grid_dimension); + free(data->grid_start); + free(data->grid_end); +} + +/** + * @brief Compute the energy of a particle after dt and update the particle + * chemistry data + * + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param cosmo The #cosmology. + * @param hydro_props The #hydro_props. + * @param cooling The #cooling_function_data used in the run. + * @param p Pointer to the particle data. + * @param xp Pointer to the particle extra data + * @param dt The time-step of this particle. + * @param mode 0=energy, 1=cooling time, 2=temperature, 3=pressure, 4=gamma + * + * @return desired quantity based on mode + */ +gr_float cooling_grackle_driver( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct hydro_props *hydro_props, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, gr_float *iact_rates, + double dt, double T_warm, int mode) { + + /* set current units for conversion to physical quantities */ + code_units units = cooling->units; + + /* initialize data to send to grackle */ + gr_float *species_densities; + species_densities = (gr_float *)calloc(N_SPECIES, sizeof(gr_float)); + grackle_field_data data; + // cooling_grackle_malloc_fields(&data, 1, cooling->chemistry.use_dust_evol); + + /* load particle information from particle to grackle data */ + cooling_copy_to_grackle(&data, us, cosmo, cooling, p, xp, dt, T_warm, + species_densities, iact_rates, mode); + + /* Run Grackle in desired mode */ + gr_float return_value = 0.f; + double t_dust = 0.f; + + switch (mode) { + case 0: + /* solve chemistry, advance thermal energy by dt */ + if (solve_chemistry(&units, &data, dt) == 0) { + error("Error in Grackle solve_chemistry."); + } + // if (solve_chemistry(&units, &data, -dt) == 0) { + // error("Error in Crackle solve_chemistry."); + // } + /* copy from grackle data to particle */ + cooling_copy_from_grackle(&data, p, xp, cooling, species_densities[12]); + return_value = data.internal_energy[0]; +#if COOLING_GRACKLE_MODE >= 2 + /* Compute dust temperature */ + t_dust = p->cooling_data.dust_temperature; + if (calculate_dust_temperature(&units, &data, &t_dust) == 0) { + error("Error in Grackle calculate dust temperature."); + } + + p->cooling_data.dust_temperature = t_dust; + + /* Reset accumulated local variables to zero */ + p->cooling_data.SNe_ThisTimeStep = 0.f; +#endif + break; + + case 1: + /* compute cooling time */ + if (calculate_cooling_time(&units, &data, &return_value) == 0) { + error("Error in Grackle calculate_cooling_time."); + } + break; + + case 2: + /* compute temperature */ + if (calculate_temperature(&units, &data, &return_value) == 0) { + error("Error in Grackle calculate_temperature."); + } + break; + + case 3: + /* compute pressure */ + if (calculate_pressure(&units, &data, &return_value) == 0) { + error("Error in Grackle calculate_pressure."); + } + break; + case 4: + /* compute gamma */ + if (calculate_gamma(&units, &data, &return_value) == 0) { + error("Error in Grackle calculate_gamma."); + } + break; + } + cooling_grackle_free_data(&data); + free(species_densities); + + return return_value; +} + +/** + * @brief Compute the cooling time. Optionally uses input values + * for rho and u, but leaves all other properties of p the same. + * + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param hydro_props The #hydro_props. + * @param cosmo The #cosmology. + * @param cooling The #cooling_function_data used in the run. + * @param p Pointer to the particle data. + * @param xp Pointer to the particle extra data. + * @param rhocool Density used in tcool calculation. + * @param ucool Density used in tcool calculation. + * + * @return cooling time + */ +gr_float cooling_time(const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct hydro_props *hydro_properties, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, + const struct part *restrict p, struct xpart *restrict xp, + const float rhocool, const float ucool) { + + /* Removes const in declaration*/ + struct part p_temp = *p; + + if (rhocool > 0.f) p_temp.rho = rhocool; + if (ucool > 0.f) p_temp.u = ucool; + + gr_float iact_rates[5] = {0., 0., 0., 0., 0.}; + + gr_float cooling_time = + cooling_grackle_driver(phys_const, us, cosmo, hydro_properties, cooling, + &p_temp, xp, iact_rates, 0., 0., 1); + + return cooling_time; +} + +/** + * @brief Compute the temperature of a #part based on the cooling function. + * + * @param phys_const #phys_const data structure. + * @param hydro_props The properties of the hydro scheme. + * @param us The internal system of units. + * @param cosmo #cosmology data structure. + * @param cooling #cooling_function_data struct. + * @param p #part data. + * @param xp Pointer to the #xpart data. + */ +float cooling_get_temperature( + const struct phys_const *restrict phys_const, + const struct hydro_props *restrict hydro_properties, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, + const struct part *restrict p, const struct xpart *restrict xp) { + + /* Remove const in declaration*/ + struct part p_temp = *p; + struct xpart xp_temp = *xp; + + gr_float iact_rates[5] = {0., 0., 0., 0., 0.}; + + const float temperature = + cooling_grackle_driver(phys_const, us, cosmo, hydro_properties, cooling, + &p_temp, &xp_temp, iact_rates, 0., 0., 2); + + return temperature; +} + +/** + * @brief Do dust sputtering and return metals back to gas phase + * + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param cooling The #cooling_function_data used in the run. + * @param p Pointer to the particle data. + * @param xp Pointer to the #xpart data. + * @param dt The time-step of this particle. + */ +__attribute__((always_inline)) INLINE void cooling_sputter_dust( + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, const double dt) { + + /* Do dust destruction in stream particle */ + if (cooling->use_grackle_dust_evol && p->cooling_data.dust_mass > 0.f) { + const float u_phys = hydro_get_physical_internal_energy(p, xp, cosmo); + const float Tstream = + cooling_convert_u_to_temp(u_phys, xp->cooling_data.e_frac, cooling, p); + const double Tstream_K = + Tstream * units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + + /* Sputtering negligible at low-T */ + if (Tstream_K > 1.e4) { + const double rho_cgs = hydro_get_physical_density(p, cosmo) * + units_cgs_conversion_factor(us, UNIT_CONV_DENSITY); + + /* sputtering timescale, Tsai & Mathews (1995) */ + const double tsp = 1.7e8 * 3.15569251e7 / + units_cgs_conversion_factor(us, UNIT_CONV_TIME) * + (cooling->dust_grainsize / 0.1) * (1.e-27 / rho_cgs) * + (pow(2.e6 / Tstream, 2.5) + 1.0); + + const float dust_mass_old = p->cooling_data.dust_mass; + + /* Update dust mass but limit the destruction */ + p->cooling_data.dust_mass -= + dust_mass_old * (1.f - exp(-3.f * min(dt / tsp, 5.f))); + if (p->cooling_data.dust_mass < 0.5f * dust_mass_old) { + p->cooling_data.dust_mass = 0.5f * dust_mass_old; + } + +#ifdef FIREHOSE_DEBUG_CHECKS + message( + "FIREHOSE_SPUT: id=%lld mdust=%g mdustnew=%g T=%g rho=%g " + "tsp_dt_ratio=%g", + p->id, dust_mass_old, p->cooling_data.dust_mass, Tstream, + rho_cgs / 1.673e-24, /* units of mp */ + tsp / dt); +#endif + /* factor by which dust mass changed */ + const float dust_mass_new = p->cooling_data.dust_mass; + const float dust_mass_ratio = dust_mass_new / dust_mass_old; + p->chemistry_data.metal_mass_fraction_total = 0.f; + + for (int elem = chemistry_element_He; elem < chemistry_element_count; + ++elem) { + const float Z_dust_elem_old = p->cooling_data.dust_mass_fraction[elem]; + const float Z_dust_elem_new = Z_dust_elem_old * dust_mass_ratio; + const float Z_elem_old = p->chemistry_data.metal_mass_fraction[elem]; + const float elem_mass_old = Z_elem_old * hydro_get_mass(p); + + /* This is the positive amount of metal mass to add since we + * are losing dust mass when sputtering. */ + const float delta_metal_mass_elem = + (Z_dust_elem_old * dust_mass_old - Z_dust_elem_new * dust_mass_new); + const float elem_mass_new = elem_mass_old + delta_metal_mass_elem; + const float Z_elem_new = elem_mass_new / hydro_get_mass(p); + + p->chemistry_data.metal_mass_fraction[elem] = Z_elem_new; + p->cooling_data.dust_mass_fraction[elem] *= dust_mass_ratio; + + /* Sum up to get the new Z value */ + if (elem != chemistry_element_H && elem != chemistry_element_He) { + p->chemistry_data.metal_mass_fraction_total += Z_elem_new; + } + } + + /* Make sure that X + Y + Z = 1 */ + const float Y_He = + p->chemistry_data.metal_mass_fraction[chemistry_element_He]; + p->chemistry_data.metal_mass_fraction[chemistry_element_H] = + 1.f - Y_He - p->chemistry_data.metal_mass_fraction_total; + + /* Make sure H fraction does not go out of bounds */ + if (p->chemistry_data.metal_mass_fraction[chemistry_element_H] > 1.f || + p->chemistry_data.metal_mass_fraction[chemistry_element_H] < 0.f) { + for (int i = chemistry_element_H; i < chemistry_element_count; i++) { + warning("\telem[%d] is %g", i, + p->chemistry_data.metal_mass_fraction[i]); + } + + error( + "Hydrogen fraction exeeds unity or is negative for" + " particle id=%lld due to dust sputtering", + p->id); + } + } + } +} + +/** + * @brief Compute particle quantities for the firehose model + * + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param cooling The #cooling_function_data used in the run. + * @param p Pointer to the particle data. + * @param xp Pointer to the #xpart data. + * @param dt The time-step of this particle. + */ +__attribute__((always_inline)) INLINE void firehose_cooling_and_dust( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct hydro_props *restrict hydro_props, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, const double dt) { + + /* Initialize cooling time */ + p->cooling_data.mixing_layer_cool_time = 0.f; + + const float u = hydro_get_comoving_internal_energy(p, xp); + + /* If it's not a firehose particle, just compute particle cooling time */ + if (p->chemistry_data.radius_stream <= 0.f || + p->chemistry_data.rho_ambient <= 0.f) { + p->cooling_data.mixing_layer_cool_time = cooling_time( + phys_const, us, hydro_props, cosmo, cooling, p, xp, p->rho, u); + return; + } + + /* It's a firehose particles, so compute the cooling rate + * in the mixing layer */ + const float rhocool = 0.5f * (p->chemistry_data.rho_ambient + p->rho); + const float ucool = 0.5f * (p->chemistry_data.u_ambient + u); + + /* +ive if heating -ive if cooling*/ + p->cooling_data.mixing_layer_cool_time = cooling_time( + phys_const, us, hydro_props, cosmo, cooling, p, xp, rhocool, ucool); +#ifdef FIREHOSE_DEBUG_CHECKS + message("FIREHOSE_COOLING: id=%lld nH=%g T=%g rhoamb=%g Tamb=%g tcool=%g", + p->id, + hydro_get_physical_density(p, cosmo) * cooling->units.density_units * + 0.75 / 1.673e-24, + u_old * cosmo->a_factor_internal_energy / cooling->temp_to_u_factor, + p->chemistry_data.rho_ambient, + p->chemistry_data.u_ambient * cosmo->a_factor_internal_energy / + cooling->temp_to_u_factor, + p->cooling_data.mixing_layer_cool_time); +#endif + + cooling_sputter_dust(us, cosmo, cooling, p, xp, dt); +} + +/** + * @brief Set subgrid ISM properties and initialise chemistry + * + * @param cooling The #cooling_function_data used in the run. + * @param p Pointer to the particle data. + */ +void cooling_init_chemistry( + const struct cooling_function_data *restrict cooling, + struct part *restrict p) { + + /* If it's eligible for SF and metal-free, crudely self-enrich to + very small level; needed to kick-start Grackle dust. Arbitrary + amount to kick-start the model */ + const float init_dust_to_gas = 0.2f; + const float total_Z = chemistry_get_total_metal_mass_fraction_for_cooling(p); + const float self_Z = + (1.f - init_dust_to_gas) * cooling->self_enrichment_metallicity; + if (p->cooling_data.subgrid_temp > 0.f && total_Z < self_Z) { + float Z_sun = 0.f; + for (int i = 1; i < 10; i++) { + Z_sun += cooling->chemistry.SolarAbundances[i]; + } + + /* Distribute the self-enrichment metallicity among elements + assuming solar abundance ratios*/ + p->chemistry_data.metal_mass_fraction_total = 0.f; + p->cooling_data.dust_mass = 0.f; + /* Offset index for the SolarAbundaces array (starts at He -> Fe) */ + int j = 1; + for (int i = chemistry_element_C; i < chemistry_element_count; i++) { + /* fraction of gas mass in each element */ + p->chemistry_data.metal_mass_fraction[i] = + (1.f - init_dust_to_gas) * cooling->chemistry.SolarAbundances[j] * + cooling->self_enrichment_metallicity; + p->chemistry_data.metal_mass_fraction[i] /= Z_sun; + + /* Update to the new metal mass fraction */ + p->chemistry_data.metal_mass_fraction_total += + p->chemistry_data.metal_mass_fraction[i]; + + /* fraction of dust mass in each element */ + p->cooling_data.dust_mass_fraction[i] = + init_dust_to_gas * cooling->chemistry.SolarAbundances[j]; + p->cooling_data.dust_mass_fraction[i] /= Z_sun; + + /* Sum up all of the dust mass */ + p->cooling_data.dust_mass += p->cooling_data.dust_mass_fraction[i] * + p->chemistry_data.metal_mass_fraction[i] * + p->mass; + j++; + } + + p->chemistry_data.metal_mass_fraction[chemistry_element_He] = + cooling->chemistry.SolarAbundances[0]; + /* Since He is fixed at SolarAbundances[0], make sure the hydrogen + fraction makes sense, i.e. X_H + Y_He + Z = 1. */ + p->chemistry_data.metal_mass_fraction[chemistry_element_H] = + 1.f - p->chemistry_data.metal_mass_fraction[chemistry_element_He] - + p->chemistry_data.metal_mass_fraction_total; + + if (p->chemistry_data.metal_mass_fraction[chemistry_element_H] > 1.f || + p->chemistry_data.metal_mass_fraction[chemistry_element_H] < 0.f) { + for (int i = chemistry_element_H; i < chemistry_element_count; i++) { + warning("\telem[%d] is %g", i, + p->chemistry_data.metal_mass_fraction[i]); + } + + error( + "Hydrogen fraction exeeds unity or is negative for" + " particle id=%lld", + p->id); + } + } +} + +/** + * @brief Apply the cooling function to a particle. + * + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param floor_props Properties of the entropy floor. + * @param cooling The #cooling_function_data used in the run. + * @param p Pointer to the particle data. + * @param xp Pointer to the particle' extended data. + * @param iact_rates Interaction rates for radiative transfer (if used) + * @param dt The time-step of this particle. + * @param dt_therm The time-step operator used for thermal quantities. + * @param time The current time (since the Big Bang or start of the run) in + * internal units. + */ +void cooling_do_grackle_cooling( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct hydro_props *hydro_props, + const struct entropy_floor_properties *floor_props, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, gr_float *iact_rates, + const double dt, const double dt_therm) { + + /* Self-enrich gas if very low metallicity */ + cooling_init_chemistry(cooling, p); + + /* Collect information about galaxy that the particle belongs to */ + const float galaxy_mstar = p->galaxy_data.stellar_mass; + const float galaxy_ssfr = p->galaxy_data.specific_sfr; + + /* Compute the ISRF */ + p->cooling_data.G0 = + fmax(cooling_compute_G0(p, p->cooling_data.subgrid_dens, cooling, + galaxy_mstar, galaxy_ssfr, dt), + 0.); + + /* Compute the entropy floor */ + // const double T_warm = entropy_floor_temperature(p, cosmo, floor_props); + const double T_warm = warm_ISM_temperature(p, cooling, phys_const, cosmo); + const double u_warm = + cooling_convert_temp_to_u(T_warm, xp->cooling_data.e_frac, cooling, p); + + /* Do grackle cooling */ + const float u_old = hydro_get_physical_internal_energy(p, xp, cosmo); + // const float T_old = cooling_get_temperature( phys_const, hydro_props, us, + // cosmo, cooling, p, xp); // for debugging only + gr_float u_new = u_old; + u_new = cooling_grackle_driver(phys_const, us, cosmo, hydro_props, cooling, p, + xp, iact_rates, dt, T_warm, 0); + + /* Apply simulation-wide minimum temperature */ + u_new = max(u_new, hydro_props->minimal_internal_energy); + + /* Assign new thermal energy to particle */ + /* Calculate the cooling rate */ + float cool_du_dt = (u_new - u_old) / dt_therm; + + if (p->cooling_data.subgrid_temp == 0.) { + /* Normal cooling; check that we are not going to go + * below any of the limits */ + if (u_new > GRACKLE_HEATLIM * u_old) u_new = GRACKLE_HEATLIM * u_old; + if (u_new < GRACKLE_COOLLIM * u_old) u_new = GRACKLE_COOLLIM * u_old; + // u_new = max(u_new, u_warm); + + /* Rennehan: Recompute the actual thermal evolution after setting min/max */ + cool_du_dt = (u_new - u_old) / dt_therm; + + /* Update the internal energy time derivative, + * which will be evolved later */ + hydro_set_physical_internal_energy_dt(p, cosmo, cool_du_dt); + + /* If there is any dust outside of the ISM, sputter it + * back into gas phase metals */ + cooling_sputter_dust(us, cosmo, cooling, p, xp, dt); + } else { + /* Particle is in subgrid mode; result is stored in subgrid_temp */ + p->cooling_data.subgrid_temp = + cooling_convert_u_to_temp(u_new, xp->cooling_data.e_frac, cooling, p); + + /* Set the subgrid cold ISM fraction for particle */ + /* Get H number density */ + const double rho = hydro_get_physical_density(p, cosmo); + const float X_H = + chemistry_get_metal_mass_fraction_for_cooling(p)[chemistry_element_H]; + const double n_H = rho * X_H / phys_const->const_proton_mass; + + const double fcold_max = cooling_compute_cold_ISM_fraction(n_H, cooling); + + /* Compute cooling time in warm ISM component */ + float rhocool = p->rho * (1.f - p->cooling_data.subgrid_fcold); + rhocool = fmax(rhocool, 0.001f * p->rho); + const float u = hydro_get_comoving_internal_energy(p, xp); + float tcool = cooling_time(phys_const, us, hydro_props, cosmo, cooling, p, + xp, rhocool, u); + + /* Evolve fcold upwards by cooling from warm ISM on + * relevant cooling timescale */ + if (tcool < 0.f) { + p->cooling_data.subgrid_fcold += + (fcold_max - p->cooling_data.subgrid_fcold) * (1.f - exp(dt / tcool)); + } + + /* Compare the adiabatic heating to the heating required to heat the + * gas back up to the equation of state line, and assume this is the + * fraction of cold cloud mass destroyed. */ + if (cooling->ism_adiabatic_heating_method == 2) { + /* Use adiabatic du/dt to evaporate cold gas clouds, into warm phase */ + const double f_evap = + hydro_get_physical_internal_energy_dt(p, cosmo) * dt_therm / + (hydro_get_physical_internal_energy(p, xp, cosmo) - u_new); + + /* If it's in the ISM of a galaxy, suppress cold fraction */ + if (f_evap > 0.f && galaxy_mstar > 0.f) { + p->cooling_data.subgrid_fcold *= max(1. - f_evap, 0.f); + } + } + + /* Set internal energy time derivative to 0 for overall particle */ + hydro_set_physical_internal_energy_dt(p, cosmo, 0.f); + + /* No cooling in warm phase since it is fixed on the EoS */ + cool_du_dt = 0.f; + + /* Force the overall particle to lie on the equation of state + hydro_set_physical_internal_energy(p, xp, cosmo, u_warm);*/ + + /* set subgrid properties for use in SF routine */ + cooling_set_particle_subgrid_properties(phys_const, us, cosmo, hydro_props, + floor_props, cooling, p, xp); + + /* Overall particle u is combination of EOS u and subgrid u */ + const float u_part = p->cooling_data.subgrid_fcold * u_new + + (1. - p->cooling_data.subgrid_fcold) * u_warm; + hydro_set_physical_internal_energy(p, xp, cosmo, u_part); + } /* subgrid mode */ + + /* Store the radiated energy */ + xp->cooling_data.radiated_energy -= hydro_get_mass(p) * cool_du_dt * dt_therm; +} + +/** + * @brief Apply the cooling function to a particle. + * + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param floor_props Properties of the entropy floor. + * @param cooling The #cooling_function_data used in the run. + * @param p Pointer to the particle data. + * @param xp Pointer to the particle' extended data. + * @param dt The time-step of this particle. + * @param dt_therm The time-step operator used for thermal quantities. + * @param time The current time (since the Big Bang or start of the run) in + * internal units. + */ +void cooling_cool_part(const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct hydro_props *hydro_props, + const struct entropy_floor_properties *floor_props, + const struct pressure_floor_props *pressure_floor_props, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, + const double dt, const double dt_therm, + const double time) { + + /* Compute cooling time and other quantities needed for firehose */ + firehose_cooling_and_dust(phys_const, us, cosmo, hydro_props, cooling, p, xp, + dt); + + /* Update the subgrid properties */ + cooling_set_particle_subgrid_properties(phys_const, us, cosmo, hydro_props, + floor_props, cooling, p, xp); + + /* No cooling if particle is decoupled */ + if (p->decoupled) return; + + if (cooling->do_cooling_in_rt) return; + + /* No cooling happens over zero time */ + if (dt == 0.f || dt_therm == 0.f) return; + + /* Interaction rates for RT; not used here */ + gr_float iact_rates[5] = {0., 0., 0., 0., 0.}; + + /* Do the cooling and chemistry */ + cooling_do_grackle_cooling(phys_const, us, cosmo, hydro_props, floor_props, + cooling, p, xp, iact_rates, dt, dt_therm); + + /* Record this cooling event */ + xp->cooling_data.time_last_event = time; +} + +/** + * @brief Set the subgrid properties (rho, T) of the gas particle for use + * in SF routine + * + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param cosmo The current cosmological model. + * @param hydro_props the hydro_props struct + * @param floor_props Properties of the entropy floor. + * @param cooling The #cooling_function_data used in the run. + * @param p Pointer to the particle data. + * @param xp Pointer to the extended particle data. + */ +void cooling_set_particle_subgrid_properties( + const struct phys_const *phys_const, const struct unit_system *us, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct entropy_floor_properties *floor_props, + const struct cooling_function_data *cooling, struct part *p, + struct xpart *xp) { + + /* No subgrid ISM if particle is decoupled */ + if (p->decoupled) { + /* Make sure these are always set for the wind particles */ + p->cooling_data.subgrid_dens = hydro_get_physical_density(p, cosmo); + p->cooling_data.subgrid_temp = 0.; + p->cooling_data.subgrid_fcold = 0.f; + + return; + } + + /* Get temperature of overall particle */ + const double u = hydro_get_physical_internal_energy(p, xp, cosmo); + const float temperature = + cooling_convert_u_to_temp(u, xp->cooling_data.e_frac, cooling, p); + + /* Get density */ + const double rho = hydro_get_physical_density(p, cosmo); + + /* Subgrid model is on if particle is in the Jeans EOS regime */ + const double T_warm = warm_ISM_temperature(p, cooling, phys_const, cosmo); + // entropy_floor_gas_temperature( rho, rho_com, cosmo, floor_props); + const double u_warm = + cooling_convert_temp_to_u(T_warm, xp->cooling_data.e_frac, cooling, p); + + /* Check if it is in subgrid mode: Must be in Jeans EoS regime + * and have nonzero cold gas */ + if (T_warm > 0 && u < u_warm * cooling->entropy_floor_margin) { + /* YES: If first time in subgrid, set temperature to particle T, + otherwise limit to particle T */ + if (p->cooling_data.subgrid_temp == 0.) { + p->cooling_data.subgrid_temp = temperature; + /* Reset grackle subgrid quantities assuming neutral gas */ + cooling_grackle_init_part(cooling, p, xp); + /* Initialize ISM cold fraction */ + p->cooling_data.subgrid_fcold = cooling->cold_ISM_frac; + } else { + /* Subgrid temperature should be no higher + * than overall particle temperature */ + p->cooling_data.subgrid_temp = + min(p->cooling_data.subgrid_temp, temperature); + } + + /* Compute subgrid density assuming pressure equilibrium */ + const float X_H = + chemistry_get_metal_mass_fraction_for_cooling(p)[chemistry_element_H]; + const double n_H = rho * X_H / phys_const->const_proton_mass; + p->cooling_data.subgrid_dens = cooling_compute_subgrid_density( + rho, n_H, temperature, p->cooling_data.subgrid_temp, cooling); + } else { + /* NO: subgrid density is the actual particle's physical density */ + p->cooling_data.subgrid_dens = rho; + + /* set subgrid temperature to 0 indicating it's not in subgrid mode */ + p->cooling_data.subgrid_temp = 0.f; + + /* No more cold gas! */ + p->cooling_data.subgrid_fcold = 0.f; + } +} + +/* + * @brief Returns the subgrid temperature of a particle. + * + * @param p The particle. + * @param xp The extended particle data. + * @return The subgrid temperature in internal units. + */ +float cooling_get_subgrid_temperature(const struct part *p, + const struct xpart *xp) { + return p->cooling_data.subgrid_temp; +} + +/* + * @brief Returns the subgrid density of a particle. + * + * @param p The particle. + * @param xp The extended particle data. + * @return The subgrid density in physical internal units. + */ +float cooling_get_subgrid_density(const struct part *p, + const struct xpart *xp) { + return p->cooling_data.subgrid_dens; +} + +/** + * @brief Compute the y-Compton contribution of a #part based on the cooling + * function. + * + * Does not exist in this model. We return 0. + * + * @param phys_const #phys_const data structure. + * @param hydro_props The properties of the hydro scheme. + * @param us The internal system of units. + * @param cosmo #cosmology data structure. + * @param cooling #cooling_function_data struct. + * @param p #part data. + * @param xp Pointer to the #xpart data. + */ +double Cooling_get_ycompton(const struct phys_const *phys_const, + const struct hydro_props *hydro_props, + const struct unit_system *us, + const struct cosmology *cosmo, + const struct cooling_function_data *cooling, + const struct part *p, const struct xpart *xp) { + + return 0.; +} + +/** + * @brief Computes the cooling time-step. + * + * We return FLT_MAX so as to impose no limit on the time-step, + * since Grackle sub-cycles the cooling as needed. + * + * @param cooling The #cooling_function_data used in the run. + * @param phys_const The physical constants in internal units. + * @param cosmo The #cosmology. + * @param us The internal system of units. + * @param hydro_props The #hydro_props. + * @param p Pointer to the particle data. + * @param xp Pointer to the particle extra data + */ +float cooling_timestep(const struct cooling_function_data *restrict cooling, + const struct phys_const *restrict phys_const, + const struct cosmology *restrict cosmo, + const struct unit_system *restrict us, + const struct hydro_props *hydro_props, + const struct part *restrict p, + const struct xpart *restrict xp) { + + return FLT_MAX; +} + +/** + * @brief Split the coolong content of a particle into n pieces + * + * @param p The #part. + * @param xp The #xpart. + * @param n The number of pieces to split into. + */ +void cooling_split_part(struct part *p, struct xpart *xp, double n) { + + xp->cooling_data.radiated_energy /= n; +} + +/** + * @brief Initialises the cooling unit system. + * + * @param us The current internal system of units. + * @param phys_const The #phys_const. + * @param cooling The cooling properties to initialize + */ +void cooling_init_units(const struct unit_system *us, + const struct phys_const *phys_const, + struct cooling_function_data *cooling) { + + /* These are conversions from code units to cgs. */ + + /* first cosmo: + units for the expansion factor, + use cosmological redshift! + a_value is updated in cooling_update() */ + cooling->units.a_units = 1.0; + if (cooling->redshift == -1) { + cooling->units.a_value = 0.01; + } else { + cooling->units.a_value = 1.0; + } + + /* We assume here all quantities to be in physical coordinate */ + cooling->units.comoving_coordinates = 0; + + /* CMB temperature */ + cooling->T_CMB_0 = phys_const->const_T_CMB_0 * + units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + + /* then units */ + /* converts physical density to cgs number density for H */ + cooling->units.density_units = + units_cgs_conversion_factor(us, UNIT_CONV_DENSITY); + cooling->units.length_units = + units_cgs_conversion_factor(us, UNIT_CONV_LENGTH); + cooling->units.time_units = units_cgs_conversion_factor(us, UNIT_CONV_TIME); + cooling->units.velocity_units = + units_cgs_conversion_factor(us, UNIT_CONV_VELOCITY); + + cooling->temp_to_u_factor = + phys_const->const_boltzmann_k / + (hydro_gamma_minus_one * phys_const->const_proton_mass * + units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE)); + cooling->dudt_units = + units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS) / + units_cgs_conversion_factor(us, UNIT_CONV_TIME); + + /* converts galaxy sSFR into G0 by scaling to MW values */ + const double time_to_yr = units_cgs_conversion_factor(us, UNIT_CONV_TIME) / + (365.25f * 24.f * 60.f * 60.f); + const double mass_to_solar_mass = 1.f / phys_const->const_solar_mass; + const double length_to_pc = + units_cgs_conversion_factor(us, UNIT_CONV_LENGTH) / 3.08567758e18f; + + cooling->time_to_Myr = time_to_yr * 1.e-6; + + /* G0 for MW=1.6 (Parravano etal 2003). */ + /* Scaled to SFR density in solar neighborhood =0.002 Mo/Gyr/pc^3 + (J. Isern 2019) */ + cooling->G0_factor1 = 1.6f * mass_to_solar_mass / + (0.002f * time_to_yr * 1.e-9) / + (length_to_pc * length_to_pc * length_to_pc); + + /* Calibrated to sSFR for MW=2.71e-11 (Licquia etal 2015) */ + cooling->G0_factor2 = 1.6f / (2.71e-11f * time_to_yr); + + /* Calibrated to 0.015 SNe per year for MW (BC Reed 2005) */ + cooling->G0_factorSNe = 1.6f / 0.015; +} + +/** + * @brief Initialises Grackle. + * + * @param cooling The cooling properties to initialize + */ +void cooling_init_grackle(struct cooling_function_data *cooling) { + +#ifdef SWIFT_DEBUG_CHECKS + /* enable verbose for grackle */ + grackle_verbose = 1; +#endif + + chemistry_data *chemistry = &cooling->chemistry; + + /* Create a chemistry object for parameters and rate data. */ + if (set_default_chemistry_parameters(chemistry) == 0) { + error("Error in set_default_chemistry_parameters."); + } + + // Set parameter values for chemistry & cooling + + // Flag to activate the grackle machinery: + chemistry->use_grackle = 2; // 1=original grackle, 2=crackle + /* Flag to include radiative cooling and actually update the thermal energy + * during the chemistry solver. If off, the chemistry species will still be + * updated. The most common reason to set this to off is to iterate the + * chemistry network to an equilibrium state. */ + chemistry->with_radiative_cooling = 1; + + /* Flag to control which primordial chemistry network is used (set by Config + * file) */ + chemistry->primordial_chemistry = COOLING_GRACKLE_MODE; + + /** Flag to enable H2 formation on dust grains, dust cooling, and dust-gas + * heat transfer follow Omukai (2000). This assumes that the dust to gas ratio + * scales with the metallicity. Default: 0. */ + chemistry->h2_on_dust = 0; + + /* Flag to enable metal cooling using the Cloudy tables. If enabled, the + * cooling table to be used must be specified with the grackle_data_file + * parameter. Default: 0. */ + chemistry->metal_cooling = cooling->with_metal_cooling; + + /* Flag to enable an effective CMB temperature floor. This is implemented by + * subtracting the value of the cooling rate at TCMB from the total cooling + * rate. Default: 1. */ + chemistry->cmb_temperature_floor = 1; + + /* Flag to enable a UV background. If enabled, the cooling table to be used + * must be specified with the grackle_data_file parameter. Default: 0. */ + chemistry->UVbackground = cooling->with_uv_background; + + /* Path to the data file containing the metal cooling and UV background + * tables: */ + chemistry->grackle_data_file = cooling->cloudy_table; + + /* The ratio of specific heats for an ideal gas. A direct calculation for the + * molecular component is used if primordial_chemistry > 1. Default: 5/3. */ + chemistry->Gamma = hydro_gamma; + + /* Flag to control which three-body H2 formation rate is used. */ + chemistry->three_body_rate = 0; + + /* Flag to enable H2 collision-induced emission cooling from Ripamonti & Abel + * (2004). Default: 0. */ + chemistry->cie_cooling = 0; + + /* Flag to enable H2 cooling attenuation from Ripamonti & Abel (2004). + * Default: 0 */ + chemistry->h2_optical_depth_approximation = 0; + /* Flag to enable a spatially uniform heating term approximating + * photo-electric heating from dust from Tasker & Bryan (2008). Default: 0. */ + chemistry->photoelectric_heating = 0; + /* photo-electric on [but not adjusted to local background, beware!] */ + chemistry->photoelectric_heating_rate = 8.5e-26; + + /* Flag to enable Compton heating from an X-ray background following Madau & + * Efstathiou (1999). Default: 0. */ + chemistry->Compton_xray_heating = 0; + + /* Intensity of a constant Lyman-Werner H2 photo-dissociating radiation field, + * in units of 10-21 erg s-1 cm-2 Hz-1 sr-1. Default: 0. */ + chemistry->LWbackground_intensity = 0; + + /* Flag to enable suppression of Lyman-Werner flux due to Lyman-series + * absorption + * (giving a sawtooth pattern), taken from Haiman & Abel, & Rees (2000). + * Default: 0. */ + chemistry->LWbackground_sawtooth_suppression = 0; + + /* volumetric heating rates is being provided in the volumetric_heating_rate + * field of grackle_field_data */ + chemistry->use_volumetric_heating_rate = + cooling->provide_volumetric_heating_rates; + + /* specific heating rates is being provided in the specific_heating_rate field + * of grackle_field_data */ + chemistry->use_specific_heating_rate = + cooling->provide_specific_heating_rates; + + /* Set parameters of temperature floor: 0=none, 1=provide scalar, + * 2=provide array */ + chemistry->use_temperature_floor = 2; + + /* arrays of ionization and heating rates from radiative transfer solutions + * are being provided */ + chemistry->use_radiative_transfer = 0; + + /* must be enabled to couple the passed radiative transfer fields to the + * chemistry solver */ + chemistry->radiative_transfer_coupled_rate_solver = 0; + + /* enable intermediate stepping in applying radiative transfer fields to + * chemistry solver. */ + chemistry->radiative_transfer_intermediate_step = 0; + + /* only use hydrogen ionization and heating rates from the radiative transfer + * solutions. */ + chemistry->radiative_transfer_hydrogen_only = 0; + + /* Use Rahmati+13 self-shielding; 0=none, 1=HI only, 2=HI+HeI, 3=HI+HeI but + * set HeII rates to 0 */ + chemistry->self_shielding_method = cooling->self_shielding_method; + + /* control behaviour of Grackle sub-step integrator */ + chemistry->max_iterations = cooling->max_step; + chemistry->exit_after_iterations_exceeded = 0; + chemistry->accuracy = cooling->timestep_accuracy; + + /* control behaviour of Grackle sub-step integration damping */ + if (cooling->grackle_damping_interval > 0) { + chemistry->use_subcycle_timestep_damping = 1; + chemistry->subcycle_timestep_damping_interval = + cooling->grackle_damping_interval; + } else { + chemistry->use_subcycle_timestep_damping = 0; + chemistry->subcycle_timestep_damping_interval = 0; + } + /* run on a single thread since Swift sends each particle to a single thread + *chemistry->omp_nthreads = 1; */ + + /* Turn on Li+ 2019 dust evolution model */ + chemistry->use_dust_evol = cooling->use_grackle_dust_evol; + chemistry->use_dust_density_field = cooling->use_grackle_dust_evol; + + /* Load dust evolution parameters */ + if (cooling->use_grackle_dust_evol == 1) { + chemistry->dust_destruction_eff = cooling->dust_destruction_eff; + chemistry->sne_coeff = cooling->dust_sne_coeff; + chemistry->sne_shockspeed = cooling->dust_sne_shockspeed; + chemistry->dust_grainsize = cooling->dust_grainsize; + chemistry->dust_growth_densref = cooling->dust_growth_densref; + chemistry->dust_growth_tauref = cooling->dust_growth_tauref; + /* Enable dust temperature calculation using ISRF */ + chemistry->metal_cooling = 1; + chemistry->dust_chemistry = 1; + chemistry->h2_on_dust = 1; + chemistry->use_isrf_field = 1; + chemistry->H2_self_shielding = 4; + /* 2 means we specify the H2 shielding + length ourselves (the gas smoothing length) */ + chemistry->H2_custom_shielding = 2; + /* Solar abundances to pass to Grackle: + He (10.93 in units where log[H]=12, so photospheric mass fraction + -> Y=0.2485 [Hydrogen X=0.7381]; Anders+Grevesse Y=0.2485, X=0.7314) + C (8.43 -> 2.38e-3, AG=3.18e-3) + N (7.83 -> 0.70e-3, AG=1.15e-3) + O (8.69 -> 5.79e-3, AG=9.97e-3) + Ne (7.93 -> 1.26e-3, AG=1.72e-3) + Mg (7.60 -> 7.14e-4, AG=6.75e-4) + Si (7.51 -> 6.71e-4, AG=7.30e-4) + S (7.12 -> 3.12e-4, AG=3.80e-4) + Ca (6.34 -> 0.65e-4, AG=0.67e-4) + Fe (7.50 -> 1.31e-3, AG=1.92e-3) + */ + chemistry->SolarAbundances[0] = 0.2485; + chemistry->SolarAbundances[1] = 2.38e-3; + chemistry->SolarAbundances[2] = 0.70e-3; + chemistry->SolarAbundances[3] = 5.79e-3; + chemistry->SolarAbundances[4] = 1.26e-3; + chemistry->SolarAbundances[5] = 7.14e-4; + chemistry->SolarAbundances[6] = 6.71e-3; + chemistry->SolarAbundances[7] = 3.12e-4; + chemistry->SolarAbundances[8] = 0.65e-4; + chemistry->SolarAbundances[9] = 1.31e-3; + } else { + chemistry->use_dust_evol = 0; + } + + cooling->use_grackle_h2_form = + cooling->use_grackle_dust_evol && COOLING_GRACKLE_MODE >= 2; + + /* Initialize the chemistry object. */ + if (initialize_chemistry_data(&cooling->units) == 0) { + error("Error in initialize_chemistry_data."); + } +} + +/** + * @brief Initialises the cooling properties. + * + * @param parameter_file The parsed parameter file. + * @param us The current internal system of units. + * @param phys_const The physical constants in internal units. + * @param hydro_props The properties of the hydro scheme. + * @param cooling The cooling properties to initialize + */ +void cooling_init_backend(struct swift_params *parameter_file, + const struct unit_system *us, + const struct phys_const *phys_const, + const struct hydro_props *hydro_props, + struct cooling_function_data *cooling) { + + if (GRACKLE_NPART != 1) + error("Grackle with multiple particles not implemented"); + + /* read parameters */ + cooling_read_parameters(parameter_file, cooling, phys_const, us); + + /* Set up the units system. */ + cooling_init_units(us, phys_const, cooling); + + /* Set up grackle */ + cooling_init_grackle(cooling); +} + +/** + * @brief Clean-up the memory allocated for the cooling routines + * + * @param cooling the cooling data structure. + */ +void cooling_clean(struct cooling_function_data *cooling) { + //_free_chemistry_data(&cooling->chemistry, &grackle_rates); +} + +/** + * @brief Write a cooling struct to the given FILE as a stream of bytes. + * + * Nothing to do beyond writing the structure from the stream. + * + * @param cooling the struct + * @param stream the file stream + */ +void cooling_struct_dump(const struct cooling_function_data *cooling, + FILE *stream) { + restart_write_blocks((void *)cooling, sizeof(struct cooling_function_data), 1, + stream, "cooling", "cooling function"); +} + +/** + * @brief Restore a hydro_props struct from the given FILE as a stream of + * bytes. + * + * Nothing to do beyond reading the structure from the stream. + * + * @param cooling the struct + * @param stream the file stream + * @param cosmo #cosmology structure + */ +void cooling_struct_restore(struct cooling_function_data *cooling, FILE *stream, + const struct cosmology *cosmo) { + restart_read_blocks((void *)cooling, sizeof(struct cooling_function_data), 1, + stream, NULL, "cooling function"); + + /* Set up grackle */ + cooling_init_grackle(cooling); +} diff --git a/src/cooling/KIARA/cooling.h b/src/cooling/KIARA/cooling.h new file mode 100644 index 0000000000..390c8fae03 --- /dev/null +++ b/src/cooling/KIARA/cooling.h @@ -0,0 +1,632 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_COOLING_KIARA_H +#define SWIFT_COOLING_KIARA_H + +/** + * @file src/cooling/KIARA/cooling.h + * @brief Cooling using the GRACKLE 3.1.1 library. + */ + +/* Some standard headers. */ +#include +#include +#include + +/* The grackle library itself */ +#include + +/* Local includes. */ +#include "chemistry.h" +#include "cooling_properties.h" +#include "entropy_floor.h" +#include "error.h" +#include "fof.h" +#include "hydro.h" +#include "parser.h" +#include "part.h" +#include "physical_constants.h" +#include "units.h" + +/* need to rework (and check) code if changed */ +#define GRACKLE_NPART 1 +#define GRACKLE_RANK 3 +#if COOLING_GRACKLE_MODE >= 2 +#define N_SPECIES 46 /* This further includes properties for dust model */ +#else +#define N_SPECIES \ + 21 /* This includes extra values at end to hold \ + rho,u,dudt,vx,vy,vz,u_floor,mZ,dummyvar */ +#endif + +/* define heating and cooling limits on thermal energy, per timestep */ +#define GRACKLE_HEATLIM 1000.f +#define GRACKLE_COOLLIM 0.01f +#define MAX_COLD_ISM_FRACTION 0.9f +/* Minimum particle column length as a fraction of p->h. + * Should be <= mean interparticle spacing. + * For 48 Ngb mean spacing ~ 0.887 + * For 57 Ngb mean spacing ~ 0.837 + * For 114 Ngb mean spacing ~ 0.664 + * + * Basically a limiter on rho / |grad rho| for a depth + * for shielding, and how bad you think |grad rho| + * really is at estimating the depth. */ +#define MIN_SHIELD_H_FRAC 0.332f + +void cooling_update(const struct phys_const *phys_const, + const struct cosmology *cosmo, + const struct pressure_floor_props *pressure_floor, + struct cooling_function_data *cooling, struct space *s, + const double time); + +void cooling_print_fractions(const struct xpart *restrict xp); +void cooling_first_init_part(const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct hydro_props *hydro_properties, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *cooling, + struct part *restrict p, + struct xpart *restrict xp); +void cooling_post_init_part( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct hydro_props *hydro_props, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, + const struct part *restrict p, struct xpart *restrict xp); + +void cooling_print_backend(const struct cooling_function_data *cooling); + +void cooling_copy_to_grackle1(grackle_field_data *data, const struct part *p, + const struct xpart *xp, gr_float rho, + gr_float species_densities[N_SPECIES]); +void cooling_copy_to_grackle2( + grackle_field_data *data, const struct part *p, const struct xpart *xp, + const struct cooling_function_data *restrict cooling, const double dt, + gr_float rho, gr_float species_densities[N_SPECIES]); +void cooling_copy_to_grackle3(grackle_field_data *data, const struct part *p, + const struct xpart *xp, gr_float rho, + gr_float species_densities[N_SPECIES]); +void cooling_copy_from_grackle1(grackle_field_data *data, const struct part *p, + struct xpart *xp, gr_float rho); +void cooling_copy_from_grackle2( + grackle_field_data *data, struct part *p, struct xpart *xp, + const struct cooling_function_data *restrict cooling, gr_float rho); +void cooling_copy_from_grackle3(grackle_field_data *data, const struct part *p, + struct xpart *xp, gr_float rho); +void cooling_copy_to_grackle( + grackle_field_data *data, const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, const struct part *p, + const struct xpart *xp, const double dt, const double T_floor, + gr_float species_densities[N_SPECIES], gr_float *iact_rates, int mode); +void cooling_copy_from_grackle( + grackle_field_data *data, struct part *p, struct xpart *xp, + const struct cooling_function_data *restrict cooling, gr_float rho); +void cooling_grackle_free_data(grackle_field_data *data); +gr_float cooling_grackle_driver( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct hydro_props *hydro_properties, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, gr_float *iact_rates, + double dt, double T_floor, int mode); +void cooling_do_grackle_cooling( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct hydro_props *hydro_props, + const struct entropy_floor_properties *floor_props, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, gr_float *iact_rates, + const double dt, const double dt_therm); +gr_float cooling_time(const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct hydro_props *hydro_properties, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, + const struct part *restrict p, struct xpart *restrict xp, + const float rhocool, const float ucool); + +float cooling_get_temperature( + const struct phys_const *restrict phys_const, + const struct hydro_props *hydro_properties, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, + const struct part *restrict p, const struct xpart *restrict xp); + +void firehose_cooling_and_dust( + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct hydro_props *restrict hydro_props, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, const double dt); + +void cooling_cool_part(const struct phys_const *restrict phys_const, + const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct hydro_props *hydro_properties, + const struct entropy_floor_properties *floor_props, + const struct pressure_floor_props *pressure_floor_props, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, + const double dt, const double dt_therm, + const double time); + +void cooling_set_particle_subgrid_properties( + const struct phys_const *phys_const, const struct unit_system *us, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct entropy_floor_properties *floor_props, + const struct cooling_function_data *cooling, struct part *p, + struct xpart *xp); + +float cooling_get_subgrid_temperature(const struct part *p, + const struct xpart *xp); + +float cooling_get_subgrid_density(const struct part *p, const struct xpart *xp); + +float cooling_get_radiated_energy(const struct xpart *restrict xp); + +double cooling_get_ycompton(const struct phys_const *phys_const, + const struct hydro_props *hydro_props, + const struct unit_system *us, + const struct cosmology *cosmo, + const struct cooling_function_data *cooling, + const struct part *p, const struct xpart *xp); + +float cooling_timestep(const struct cooling_function_data *restrict cooling, + const struct phys_const *restrict phys_const, + const struct cosmology *restrict cosmo, + const struct unit_system *restrict us, + const struct hydro_props *hydro_properties, + const struct part *restrict p, + const struct xpart *restrict xp); + +void cooling_split_part(struct part *p, struct xpart *xp, double n); + +void cooling_init_units(const struct unit_system *us, + const struct phys_const *phys_const, + struct cooling_function_data *cooling); +void cooling_init_grackle(struct cooling_function_data *cooling); + +void cooling_init_backend(struct swift_params *parameter_file, + const struct unit_system *us, + const struct phys_const *phys_const, + const struct hydro_props *hydro_props, + struct cooling_function_data *cooling); + +void cooling_clean(struct cooling_function_data *cooling); +void cooling_struct_dump(const struct cooling_function_data *cooling, + FILE *stream); +void cooling_struct_restore(struct cooling_function_data *cooling, FILE *stream, + const struct cosmology *cosmo); +void cooling_sputter_dust(const struct unit_system *restrict us, + const struct cosmology *restrict cosmo, + const struct cooling_function_data *restrict cooling, + struct part *restrict p, struct xpart *restrict xp, + const double dt); + +/** + * @brief Compute the electron pressure of a #part based on the cooling + * function. + * + * Does not exist in this model. We return 0. + * + * @param phys_const #phys_const data structure. + * @param hydro_props The properties of the hydro scheme. + * @param us The internal system of units. + * @param cosmo #cosmology data structure. + * @param cooling #cooling_function_data struct. + * @param p #part data. + * @param xp Pointer to the #xpart data. + */ +INLINE static double cooling_get_electron_pressure( + const struct phys_const *phys_const, const struct hydro_props *hydro_props, + const struct unit_system *us, const struct cosmology *cosmo, + const struct cooling_function_data *cooling, const struct part *p, + const struct xpart *xp) { + return 0; +} + +/** + * @brief Compute the specific thermal energy (physical) for a given + * temperature. + * + * Converts T to u (internal physical units) for a given particle. + * + * @param temperature Particle temperature in K + * @param ne Electron number density relative to H atom density + * @param cooling #cooling_function_data struct. + * @param p #part data. + */ +INLINE static double cooling_convert_temp_to_u( + const double temperature, const double ne, + const struct cooling_function_data *cooling, const struct part *p) { + + const float X_H = + chemistry_get_metal_mass_fraction_for_cooling(p)[chemistry_element_H]; + const float yhelium = (1. - X_H) / (4. * X_H); + const float mu = (1. + yhelium) / (1. + ne + 4. * yhelium); + + return temperature * mu * cooling->temp_to_u_factor; +} + +/** + * @brief Compute the temperature for a given physical specific energy + * + * Converts T to u (internal physical units) for a given particle. + * + * @param u Physical specific energy + * @param ne Electron number density relative to H atom density + * @param cooling #cooling_function_data struct. + * @param p #part data. + */ +INLINE static double cooling_convert_u_to_temp( + const double u, const double ne, + const struct cooling_function_data *cooling, const struct part *p) { + + const float X_H = + chemistry_get_metal_mass_fraction_for_cooling(p)[chemistry_element_H]; + const float yhelium = (1. - X_H) / (4. * X_H); + const float mu = (1. + yhelium) / (1. + ne + 4. * yhelium); + + return u / (mu * cooling->temp_to_u_factor); +} + +/** + * @brief Compute the cold ISM fraction at a given factor above subgrid + * threshold density + * + * Compute the cold ISM fraction at a given factor above subgrid threshold + * density. This uses a fit to the density vs. cold gas fraction relation from + * Springel+Hernquist 2003. + * + * @param dens_fac Density factor above threshold density + * @param cooling #cooling_function_data struct. + */ +INLINE static double cooling_compute_cold_ISM_fraction( + const double n_H, const struct cooling_function_data *cooling) { + + float fc = cooling->cold_ISM_frac; + float dens_fac = n_H * cooling->subgrid_threshold_n_H_inv; + if (dens_fac > 1.) { + fc = cooling->cold_ISM_frac + + (1. - cooling->cold_ISM_frac) * (1. - exp(-log10(dens_fac))); + fc = fmin(fc, MAX_COLD_ISM_FRACTION); + } + return fc; +} + +/** + * @brief Compute the subgrid density based on pressure equilibrium in a 2-phase + * ISM model + * + * We set the subgrid density based on pressure equilibrium with overall + * particle. The pressure is set by 1-cold_ISM_frac of the mass in the warm + * phase. + * + * @param rho SPH (non-subgrid) physical particle density. + * @param n_H SPH (non-subgrid) physical particle H number density. + * @param temp SPH (non-subgrid) particle temperature. + * @param subgrid_temp Subgrid particle temperature. + * @param cooling #cooling_function_data struct. + */ +INLINE static double cooling_compute_subgrid_density( + const double rho, const double n_H, const double temp, + const double subgrid_temp, const struct cooling_function_data *cooling) { + + const double ism_frac = cooling_compute_cold_ISM_fraction( + n_H * cooling->subgrid_threshold_n_H_inv, cooling); + double subgrid_dens = + (1.f - ism_frac) * rho * temp / (ism_frac * subgrid_temp); + + /* Cap at max value which should be something vaguely like GMC densities */ + subgrid_dens = fmin(subgrid_dens, cooling->max_subgrid_density); + return subgrid_dens; +} + +/** + * @brief Return warm ISM temperature if above SF threshold density, otherwise + * 0. + * + * @param p Pointer to the particle data. + * @param cooling The properties of the cooling function. + * @param us The unit system. + * @param phys_const The physical constant in internal units. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static float warm_ISM_temperature( + const struct part *restrict p, const struct cooling_function_data *cooling, + const struct phys_const *phys_const, const struct cosmology *cosmo) { + + float temperature = 0.f; + + /* Mean baryon density in co-moving internal units for over-density condition + * (Recall cosmo->critical_density_0 is 0 in a non-cosmological run, + * making the over-density condition a no-op) */ + const float rho_crit_0 = cosmo->critical_density_0; + const float rho_crit_baryon = cosmo->Omega_b * rho_crit_0; + const double rho_com = hydro_get_comoving_density(p); + const double rho_phys = hydro_get_physical_density(p, cosmo); + + if (rho_com >= rho_crit_baryon * 100.f) { + const double n_H = rho_phys * 0.75 / phys_const->const_proton_mass; + if (n_H * cooling->subgrid_threshold_n_H_inv > 1.f) { + const float EOS_slope = cooling->subgrid_warm_ism_EOS; + temperature = cooling->subgrid_threshold_T * + pow(n_H * cooling->subgrid_threshold_n_H_inv, EOS_slope); + } + } + return temperature; +} + +/** + * @brief Copy all grackle fields into a new set. THIS IS ONLY USED FOR + * DEBUGGING. + * + * @param my_fields The target (new) set of grackle particle properties. + * @param old_fields The original (old) set of grackle particle properties. + * @param field_size Number of particles to copy. + */ +INLINE static void cooling_copy_grackle_fields(grackle_field_data *my_fields, + grackle_field_data *old_fields, + int field_size) { + int i; + + for (i = 0; i < field_size; i++) { + + printf("loop copy_grackle_fields %g %p\n", old_fields->density[0], + my_fields->density); + + my_fields->density[i] = old_fields->density[i]; + my_fields->HI_density[i] = old_fields->HI_density[i]; + my_fields->HII_density[i] = old_fields->HII_density[i]; + my_fields->HM_density[i] = old_fields->HM_density[i]; + my_fields->HeI_density[i] = old_fields->HeI_density[i]; + my_fields->HeII_density[i] = old_fields->HeII_density[i]; + my_fields->HeIII_density[i] = old_fields->HeIII_density[i]; + my_fields->H2I_density[i] = old_fields->H2I_density[i]; + my_fields->H2II_density[i] = old_fields->H2II_density[i]; + my_fields->DI_density[i] = old_fields->DI_density[i]; + my_fields->DII_density[i] = old_fields->DII_density[i]; + my_fields->HDI_density[i] = old_fields->HDI_density[i]; + my_fields->e_density[i] = old_fields->e_density[i]; + // solar metallicity + my_fields->metal_density[i] = old_fields->metal_density[i]; + + my_fields->x_velocity[i] = 0.0; + my_fields->y_velocity[i] = 0.0; + my_fields->z_velocity[i] = 0.0; + + // initilize internal energy (here 1000 K for no reason) + my_fields->internal_energy[i] = old_fields->internal_energy[i]; + + my_fields->volumetric_heating_rate[i] = + old_fields->volumetric_heating_rate[i]; + my_fields->specific_heating_rate[i] = old_fields->specific_heating_rate[i]; + my_fields->temperature_floor[i] = old_fields->temperature_floor[i]; + + my_fields->isrf_habing[i] = old_fields->isrf_habing[i]; + my_fields->RT_HI_ionization_rate[i] = old_fields->RT_HI_ionization_rate[i]; + my_fields->RT_HeI_ionization_rate[i] = + old_fields->RT_HeI_ionization_rate[i]; + my_fields->RT_HeII_ionization_rate[i] = + old_fields->RT_HeII_ionization_rate[i]; + my_fields->RT_H2_dissociation_rate[i] = + old_fields->RT_H2_dissociation_rate[i]; + my_fields->RT_heating_rate[i] = old_fields->RT_heating_rate[i]; + + if (grackle_data->use_dust_evol) { + my_fields->dust_density[i] = old_fields->dust_density[i]; + my_fields->He_gas_metalDensity[i] = old_fields->He_gas_metalDensity[i]; + my_fields->C_gas_metalDensity[i] = old_fields->C_gas_metalDensity[i]; + my_fields->N_gas_metalDensity[i] = old_fields->N_gas_metalDensity[i]; + my_fields->O_gas_metalDensity[i] = old_fields->O_gas_metalDensity[i]; + my_fields->Ne_gas_metalDensity[i] = old_fields->Ne_gas_metalDensity[i]; + my_fields->Mg_gas_metalDensity[i] = old_fields->Mg_gas_metalDensity[i]; + my_fields->Si_gas_metalDensity[i] = old_fields->Si_gas_metalDensity[i]; + my_fields->S_gas_metalDensity[i] = old_fields->S_gas_metalDensity[i]; + my_fields->Ca_gas_metalDensity[i] = old_fields->Ca_gas_metalDensity[i]; + my_fields->Fe_gas_metalDensity[i] = old_fields->Fe_gas_metalDensity[i]; + my_fields->He_dust_metalDensity[i] = old_fields->He_dust_metalDensity[i]; + my_fields->C_dust_metalDensity[i] = old_fields->C_dust_metalDensity[i]; + my_fields->N_dust_metalDensity[i] = old_fields->N_dust_metalDensity[i]; + my_fields->O_dust_metalDensity[i] = old_fields->O_dust_metalDensity[i]; + my_fields->Ne_dust_metalDensity[i] = old_fields->Ne_dust_metalDensity[i]; + my_fields->Mg_dust_metalDensity[i] = old_fields->Mg_dust_metalDensity[i]; + my_fields->Si_dust_metalDensity[i] = old_fields->Si_dust_metalDensity[i]; + my_fields->S_dust_metalDensity[i] = old_fields->S_dust_metalDensity[i]; + my_fields->Ca_dust_metalDensity[i] = old_fields->Ca_dust_metalDensity[i]; + my_fields->Fe_dust_metalDensity[i] = old_fields->Fe_dust_metalDensity[i]; + my_fields->SNe_ThisTimeStep[i] = old_fields->SNe_ThisTimeStep[i]; + } + } + printf("done copy_grackle_fields\n"); + + return; +} + +/** + * @brief Allocate a new set of grackle fields in memory. THIS IS ONLY USED FOR + * DEBUGGING. + * + * @param my_fields The target (new) set of grackle particle properties. + * @param field_size Number of particles to copy. + * @param dust_flag Are we using grackle's dust model (Jones, Smith, Dave 2024)? + */ +INLINE static void cooling_grackle_malloc_fields(grackle_field_data *my_fields, + int field_size, + int dust_flag) { + my_fields->density = malloc(field_size * sizeof(gr_float)); + my_fields->internal_energy = malloc(field_size * sizeof(gr_float)); + my_fields->x_velocity = malloc(field_size * sizeof(gr_float)); + my_fields->y_velocity = malloc(field_size * sizeof(gr_float)); + my_fields->z_velocity = malloc(field_size * sizeof(gr_float)); + // for primordial_chemistry >= 1 + my_fields->HI_density = malloc(field_size * sizeof(gr_float)); + my_fields->HII_density = malloc(field_size * sizeof(gr_float)); + my_fields->HeI_density = malloc(field_size * sizeof(gr_float)); + my_fields->HeII_density = malloc(field_size * sizeof(gr_float)); + my_fields->HeIII_density = malloc(field_size * sizeof(gr_float)); + my_fields->e_density = malloc(field_size * sizeof(gr_float)); + // for primordial_chemistry >= 2 + my_fields->HM_density = malloc(field_size * sizeof(gr_float)); + my_fields->H2I_density = malloc(field_size * sizeof(gr_float)); + my_fields->H2II_density = malloc(field_size * sizeof(gr_float)); + // for primordial_chemistry >= 3 + my_fields->DI_density = malloc(field_size * sizeof(gr_float)); + my_fields->DII_density = malloc(field_size * sizeof(gr_float)); + my_fields->HDI_density = malloc(field_size * sizeof(gr_float)); + // for metal_cooling = 1 + my_fields->metal_density = malloc(field_size * sizeof(gr_float)); + + // volumetric heating rate (provide in units [erg s^-1 cm^-3]) + my_fields->volumetric_heating_rate = malloc(field_size * sizeof(gr_float)); + // specific heating rate (provide in units [egs s^-1 g^-1] + my_fields->specific_heating_rate = malloc(field_size * sizeof(gr_float)); + my_fields->temperature_floor = malloc(field_size * sizeof(gr_float)); + + // radiative transfer ionization / dissociation rate fields (provide in units + // [1/s]) + my_fields->RT_HI_ionization_rate = malloc(field_size * sizeof(gr_float)); + my_fields->RT_HeI_ionization_rate = malloc(field_size * sizeof(gr_float)); + my_fields->RT_HeII_ionization_rate = malloc(field_size * sizeof(gr_float)); + my_fields->RT_H2_dissociation_rate = malloc(field_size * sizeof(gr_float)); + // radiative transfer heating rate field (provide in units [erg s^-1 cm^-3]) + my_fields->RT_heating_rate = malloc(field_size * sizeof(gr_float)); + + // H2 model + my_fields->H2_self_shielding_length = malloc(field_size * sizeof(gr_float)); + my_fields->H2_custom_shielding_factor = malloc(field_size * sizeof(gr_float)); + my_fields->isrf_habing = malloc(field_size * sizeof(gr_float)); + + if (dust_flag) { + my_fields->dust_density = malloc(field_size * sizeof(gr_float)); + my_fields->He_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->C_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->N_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->O_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Ne_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Mg_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Si_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->S_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Ca_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Fe_gas_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->He_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->C_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->N_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->O_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Ne_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Mg_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Si_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->S_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Ca_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->Fe_dust_metalDensity = malloc(field_size * sizeof(gr_float)); + my_fields->SNe_ThisTimeStep = malloc(field_size * sizeof(gr_float)); + } + return; +} + +/** + * @brief Free a set of grackle fields from memory. THIS IS ONLY USED FOR + * DEBUGGING. + * + * @param my_fields The target (new) set of grackle particle properties. + * @param dust_flag Are we using grackle's dust model (Jones, Smith, Dave 2024)? + */ +INLINE static void cooling_grackle_free_fields(grackle_field_data *my_fields, + int dust_flag) { + free(my_fields->grid_dimension); + free(my_fields->grid_start); + free(my_fields->grid_end); + + free(my_fields->density); + free(my_fields->internal_energy); + free(my_fields->x_velocity); + free(my_fields->y_velocity); + free(my_fields->z_velocity); + // for primordial_chemistry >= 1 + free(my_fields->HI_density); + free(my_fields->HII_density); + free(my_fields->HeI_density); + free(my_fields->HeII_density); + free(my_fields->HeIII_density); + free(my_fields->e_density); + // for primordial_chemistry >= 2 + free(my_fields->HM_density); + free(my_fields->H2I_density); + free(my_fields->H2II_density); + // for primordial_chemistry >= 3 + free(my_fields->DI_density); + free(my_fields->DII_density); + free(my_fields->HDI_density); + // for metal_cooling = 1 + free(my_fields->metal_density); + + // volumetric heating rate (provide in units [erg s^-1 cm^-3]) + free(my_fields->volumetric_heating_rate); + // specific heating rate (provide in units [egs s^-1 g^-1] + free(my_fields->specific_heating_rate); + free(my_fields->temperature_floor); + + // radiative transfer ionization / dissociation rate fields (provide in units + // [1/s]) + free(my_fields->RT_HI_ionization_rate); + free(my_fields->RT_HeI_ionization_rate); + free(my_fields->RT_HeII_ionization_rate); + free(my_fields->RT_H2_dissociation_rate); + // radiative transfer heating rate field (provide in units [erg s^-1 cm^-3]) + free(my_fields->RT_heating_rate); + + // H2 model + free(my_fields->H2_self_shielding_length); + free(my_fields->H2_custom_shielding_factor); + free(my_fields->isrf_habing); + + if (dust_flag) { + free(my_fields->dust_density); + free(my_fields->He_gas_metalDensity); + free(my_fields->C_gas_metalDensity); + free(my_fields->N_gas_metalDensity); + free(my_fields->O_gas_metalDensity); + free(my_fields->Ne_gas_metalDensity); + free(my_fields->Mg_gas_metalDensity); + free(my_fields->Si_gas_metalDensity); + free(my_fields->S_gas_metalDensity); + free(my_fields->Ca_gas_metalDensity); + free(my_fields->Fe_gas_metalDensity); + free(my_fields->He_dust_metalDensity); + free(my_fields->C_dust_metalDensity); + free(my_fields->N_dust_metalDensity); + free(my_fields->O_dust_metalDensity); + free(my_fields->Ne_dust_metalDensity); + free(my_fields->Mg_dust_metalDensity); + free(my_fields->Si_dust_metalDensity); + free(my_fields->S_dust_metalDensity); + free(my_fields->Ca_dust_metalDensity); + free(my_fields->Fe_dust_metalDensity); + free(my_fields->SNe_ThisTimeStep); + } + return; +} +#endif /* SWIFT_COOLING_KIARA_H */ diff --git a/src/cooling/KIARA/cooling_debug.h b/src/cooling/KIARA/cooling_debug.h new file mode 100644 index 0000000000..8167f820bb --- /dev/null +++ b/src/cooling/KIARA/cooling_debug.h @@ -0,0 +1,54 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_COOLING_KIARA_DEBUG_H +#define SWIFT_COOLING_KIARA_DEBUG_H + +__attribute__((always_inline)) INLINE static void cooling_debug_particle( + const struct part *p, const struct xpart *xp) { + + if (xp != NULL) { + warning("[PID%lld] cooling_xpart_data:", p->id); + warning("[PID%lld] radiated_energy = %.3e, time_last_event = %.3e", p->id, + xp->cooling_data.radiated_energy, xp->cooling_data.time_last_event); + +#if COOLING_GRACKLE_MODE >= 1 + warning( + "[PID%lld] HI_frac = %.3e, HII_frac = %.3e, HeI_frac = %.3e, HeII_frac " + "= " + "%.3e, " + "HeIII_frac = %.3e, e_frac = %.3e", + p->id, xp->cooling_data.HI_frac, xp->cooling_data.HII_frac, + xp->cooling_data.HeI_frac, xp->cooling_data.HeII_frac, + xp->cooling_data.HeIII_frac, xp->cooling_data.e_frac); +#if COOLING_GRACKLE_MODE >= 2 + warning("[PID%lld] HM_frac = %.3e, H2I_frac = %.3e, H2II_frac = %.3e", + p->id, xp->cooling_data.HM_frac, xp->cooling_data.H2I_frac, + xp->cooling_data.H2II_frac); +#if COOLING_GRACKLE_MODE >= 3 + warning("[PID%lld] DI_frac = %.3e, DII_frac = %.3e, HDI_frac = %.3e", p->id, + xp->cooling_data.DI_frac, xp->cooling_data.DII_frac, + xp->cooling_data.HDI_frac); +#endif +#endif +#endif + warning("[PID%lld] metal_frac = %.3e", p->id, xp->cooling_data.metal_frac); + } +} + +#endif /* SWIFT_COOLING_KIARA_DEBUG_H */ diff --git a/src/cooling/KIARA/cooling_io.h b/src/cooling/KIARA/cooling_io.h new file mode 100644 index 0000000000..10bcdd020f --- /dev/null +++ b/src/cooling/KIARA/cooling_io.h @@ -0,0 +1,373 @@ + +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_COOLING_KIARA_IO_H +#define SWIFT_COOLING_KIARA_IO_H + +/* Local includes */ +#include "cooling.h" +#include "engine.h" +#include "io_properties.h" +#include "cooling_properties.h" + +#ifdef HAVE_HDF5 + +/** + * @brief Writes the current model of cooling to the file + * + * @param h_grp The HDF5 group in which to write + * @param h_grp_columns The HDF5 group containing named columns + * @param cooling The #cooling_function_data + */ +__attribute__((always_inline)) INLINE static void cooling_write_flavour( + hid_t h_grp, hid_t h_grp_columns, + const struct cooling_function_data *cooling) { + +#if COOLING_GRACKLE_MODE == 0 + io_write_attribute_s(h_grp, "Cooling Model", "Grackle0"); +#elif COOLING_GRACKLE_MODE == 1 + io_write_attribute_s(h_grp, "Cooling Model", "Grackle1"); +#elif COOLING_GRACKLE_MODE == 2 + io_write_attribute_s(h_grp, "Cooling Model", "Grackle2"); +#elif COOLING_GRACKLE_MODE == 3 + io_write_attribute_s(h_grp, "Cooling Model", "Grackle3"); +#else + error("This function should be called only with one of the Grackle cooling."); +#endif +} +#endif + +INLINE static void convert_part_HI_mass(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + + *ret = hydro_get_mass(p) * xp->cooling_data.HI_frac; +} + +INLINE static void convert_part_H2_mass(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + + float H2_frac = 0.; + // const float X_H = + // chemistry_get_metal_mass_fraction_for_cooling(p)[chemistry_element_H]; +#if COOLING_GRACKLE_MODE >= 2 + H2_frac = xp->cooling_data.H2I_frac + xp->cooling_data.H2II_frac; + *ret = hydro_get_mass(p) * p->cooling_data.subgrid_fcold * H2_frac; +#else + if (p->sf_data.SFR > 0) H2_frac = 1. - xp->cooling_data.HI_frac; + *ret = hydro_get_mass(p) * H2_frac; +#endif +} + +INLINE static void convert_part_HII_mass(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + + *ret = hydro_get_mass(p) * xp->cooling_data.HII_frac; +} + +INLINE static void convert_part_HeI_mass(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + + *ret = hydro_get_mass(p) * xp->cooling_data.HeI_frac; +} + +INLINE static void convert_part_HeII_mass(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + + *ret = hydro_get_mass(p) * xp->cooling_data.HeII_frac; +} + +INLINE static void convert_part_HeIII_mass(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + + *ret = hydro_get_mass(p) * xp->cooling_data.HeIII_frac; +} + +INLINE static void convert_part_e_density(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + + *ret = (float)xp->cooling_data.e_frac; +} + +INLINE static void convert_part_T(const struct engine *e, const struct part *p, + const struct xpart *xp, float *ret) { + + const float u = hydro_get_physical_internal_energy(p, xp, e->cosmology); + const float ne = xp->cooling_data.e_frac; + *ret = cooling_convert_u_to_temp(u, ne, e->cooling_func, p); +} + +#ifdef RT_NONE +INLINE static void convert_mass_fractions(const struct engine *engine, + const struct part *part, + const struct xpart *xpart, + float *ret) { + + ret[0] = (float)xpart->cooling_data.HI_frac; + ret[1] = (float)xpart->cooling_data.HII_frac; +} +#endif + +/** + * @brief Specifies which particle fields to write to a dataset + * + * @param parts The particle array. + * @param xparts The extra particle array. + * @param list The list of i/o properties to write. + * + * @return Returns the number of fields to write. + */ +__attribute__((always_inline)) INLINE static int cooling_write_particles( + const struct part *parts, const struct xpart *xparts, + struct io_props *list) { + + int num = 0; + +#if COOLING_GRACKLE_MODE >= 1 + /* List what we want to write */ + list[num] = io_make_output_field_convert_part( + "AtomicHydrogenMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, parts, xparts, + convert_part_HI_mass, "Atomic hydrogen (HI) masses."); + num++; + + list[num] = io_make_output_field_convert_part( + "IonizedHydrogenMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, parts, xparts, + convert_part_HII_mass, "Ionized hydrogen (HII) masses."); + num++; + + list[num] = io_make_output_field_convert_part( + "MolecularHydrogenMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, parts, xparts, + convert_part_H2_mass, "Molecular hydrogen (H2) masses."); + num++; + + list[num] = io_make_output_field_convert_part( + "HeIMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, parts, xparts, + convert_part_HeII_mass, "HeI masses."); + num++; + + list[num] = io_make_output_field_convert_part( + "HeIIMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, parts, xparts, + convert_part_HeII_mass, "HeII masses."); + num++; + + list[num] = io_make_output_field_convert_part( + "HeIIIMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, parts, xparts, + convert_part_HeIII_mass, "HeIII masses."); + num++; + + list[num] = io_make_output_field_convert_part( + "ElectronNumberDensities", FLOAT, 1, UNIT_CONV_NO_UNITS, -3.f, + parts, xparts, convert_part_e_density, "Electron number densities" + "in units of the hydrogen density."); + num++; + + list[num] = io_make_output_field_convert_part( + "Temperatures", FLOAT, 1, UNIT_CONV_TEMPERATURE, 0.f, parts, xparts, + convert_part_T, "Temperatures of the overall gas particles."); + num++; + +#if COOLING_GRACKLE_MODE >= 2 + list[num] = io_make_output_field( + "SubgridTemperatures", FLOAT, 1, UNIT_CONV_TEMPERATURE, 0.f, parts, + cooling_data.subgrid_temp, "Temperatures of the cold phase" + "of the subgrid ISM gas particles."); + num++; + + list[num] = + io_make_output_field("SubgridDensities", FLOAT, 1, UNIT_CONV_DENSITY, + -3.f, parts, cooling_data.subgrid_dens, + "Mass densities in physical units of the " + "subgrid ISM gas particles."); + num++; + + list[num] = io_make_output_field( + "SubgridColdISMFraction", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, parts, + cooling_data.subgrid_fcold, + "Fraction of gas particle masses in cold component of subgrid ISM."); + num++; + + list[num] = + io_make_output_field("DustMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, parts, + cooling_data.dust_mass, "Total masses in dust."); + num++; + + list[num] = io_make_output_field("DustMassFractions", FLOAT, + chemistry_element_count, UNIT_CONV_NO_UNITS, + 0.f, parts, cooling_data.dust_mass_fraction, + "Fractions of the particles' masses that " + "are in dust for a given element."); + num++; + + list[num] = + io_make_output_field("DustTemperatures", FLOAT, 1, UNIT_CONV_TEMPERATURE, + 0.f, parts, cooling_data.dust_temperature, + "Dust temperatures in subgrid ISM dust model."); + num++; + + list[num] = io_make_output_field( + "CoolingTimes", FLOAT, 1, UNIT_CONV_TIME, 0.f, parts, + cooling_data.mixing_layer_cool_time, + "Cooling times for the gas particle. If it's currently a firehose wind" + "particle (decoupling_delay_time>0), this is the mixing layer cooling time."); + num++; +#endif +#endif + +#ifdef RT_NONE + list[num] = io_make_output_field_convert_part( + "IonMassFractions", FLOAT, 2, UNIT_CONV_NO_UNITS, 0, parts, xparts, + convert_mass_fractions, "Mass fractions of all constituent species."); + num++; +#endif + return num; +} + +/** + * @brief Parser the parameter file and initialize the #cooling_function_data + * + * @param parameter_file The parser parameter file + * @param cooling The cooling properties to initialize + * @param phys_const The #phys_const. + */ +__attribute__((always_inline)) INLINE static void cooling_read_parameters( + struct swift_params *parameter_file, struct cooling_function_data *cooling, + const struct phys_const *phys_const, const struct unit_system *us) { + + parser_get_param_string(parameter_file, "KIARACooling:cloudy_table", + cooling->cloudy_table); + + cooling->with_uv_background = + parser_get_param_int(parameter_file, "KIARACooling:with_UV_background"); + + cooling->redshift = + parser_get_param_double(parameter_file, "KIARACooling:redshift"); + + cooling->with_metal_cooling = + parser_get_param_int(parameter_file, "KIARACooling:with_metal_cooling"); + + cooling->provide_volumetric_heating_rates = parser_get_opt_param_int( + parameter_file, "KIARACooling:provide_volumetric_heating_rates", -1); + + cooling->provide_specific_heating_rates = parser_get_opt_param_int( + parameter_file, "KIARACooling:provide_specific_heating_rates", 1); + + /* Use lookup tables when outside ISM */ + cooling->use_tables_outside_ism = parser_get_opt_param_int( + parameter_file, "KIARACooling:use_tables_outside_ism", 0); + + /* Self shielding */ + cooling->self_shielding_method = parser_get_opt_param_int( + parameter_file, "KIARACooling:self_shielding_method", 3); + + /* What to do with adiabatic du/dt when in ISM mode */ + cooling->ism_adiabatic_heating_method = parser_get_opt_param_int( + parameter_file, "KIARACooling:ism_adiabatic_heating_method", 1); + + /* Initial step convergence */ + cooling->max_step = parser_get_opt_param_int( + parameter_file, "KIARACooling:grackle_max_steps", 500); + + cooling->timestep_accuracy = parser_get_opt_param_double( + parameter_file, "KIARACooling:timestep_accuracy", 0.2); + + cooling->grackle_damping_interval = parser_get_opt_param_double( + parameter_file, "KIARACooling:grackle_damping_interval", 5); + + cooling->thermal_time = parser_get_opt_param_double( + parameter_file, "KIARACooling:thermal_time_myr", 0.); + cooling->thermal_time *= phys_const->const_year * 1e6; + + /* flag to turn on dust evolution option, only works for GRACKLE_CHEMISTRY>=2 + * (KIARA) */ + cooling->use_grackle_dust_evol = parser_get_opt_param_int( + parameter_file, "KIARACooling:use_grackle_dust_evol", 1); +#if COOLING_GRACKLE_MODE <= 1 + message("WARNING: Dust evol not implemented in SIMBA; use KIARA instead."); + cooling->use_grackle_dust_evol = 0; +#endif + + /* These are dust parameters for KIARA's dust model (MODE>=2); irrelevant + * otherwise */ + cooling->dust_destruction_eff = parser_get_opt_param_double( + parameter_file, "KIARACooling:dust_destruction_eff", 0.3); + + cooling->dust_sne_coeff = parser_get_opt_param_double( + parameter_file, "KIARACooling:dust_sne_coeff", 1.0); + + cooling->dust_sne_shockspeed = parser_get_opt_param_double( + parameter_file, "KIARACooling:dust_sne_shockspeed", 100.0); + + cooling->dust_grainsize = parser_get_opt_param_double( + parameter_file, "KIARACooling:dust_grainsize", 0.1); + + cooling->dust_growth_densref = parser_get_opt_param_double( + parameter_file, "KIARACooling:dust_growth_densref", 2.3e-20); + + cooling->dust_growth_tauref = parser_get_opt_param_double( + parameter_file, "KIARACooling:dust_growth_tauref", 1.0); + + cooling->cold_ISM_frac = parser_get_opt_param_double( + parameter_file, "KIARACooling:cold_ISM_frac", 1.0); + + cooling->G0_computation_method = parser_get_opt_param_int( + parameter_file, "KIARACooling:G0_computation_method", 3); + + cooling->G0_multiplier = parser_get_opt_param_double( + parameter_file, "KIARACooling:G0_multiplier", 1.0); + + cooling->max_subgrid_density = parser_get_opt_param_double( + parameter_file, "KIARACooling:max_subgrid_density_g_p_cm3", FLT_MAX); + /* convert to internal units */ + cooling->max_subgrid_density /= + units_cgs_conversion_factor(us, UNIT_CONV_DENSITY); + + cooling->subgrid_threshold_n_H_inv = parser_get_opt_param_double( + parameter_file, "KIARACooling:subgrid_threshold_n_H_cgs", 0.13); + /* convert to internal units, take inverse to save compute time */ + cooling->subgrid_threshold_n_H_inv /= + units_cgs_conversion_factor(us, UNIT_CONV_NUMBER_DENSITY); + cooling->subgrid_threshold_n_H_inv = 1.f / cooling->subgrid_threshold_n_H_inv; + + cooling->subgrid_threshold_T = parser_get_opt_param_double( + parameter_file, "KIARACooling:subgrid_threshold_T_K", 1.e4); + /* convert to internal units */ + cooling->subgrid_threshold_T /= + units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + + cooling->subgrid_warm_ism_EOS = parser_get_opt_param_double( + parameter_file, "KIARACooling:subgrid_warm_ism_EOS", 0.f); + + cooling->entropy_floor_margin = parser_get_opt_param_double( + parameter_file, "KIARACooling:entropy_floor_margin_dex", 1.0); + cooling->entropy_floor_margin = pow(10.f, cooling->entropy_floor_margin); + + cooling->self_enrichment_metallicity = parser_get_opt_param_double( + parameter_file, "KIARACooling:self_enrichment_metallicity", 0.f); + + cooling->do_cooling_in_rt = parser_get_opt_param_int( + parameter_file, "KIARACooling:do_cooling_in_rt", 0); +} + +#endif /* SWIFT_COOLING_KIARA_IO_H */ diff --git a/src/cooling/KIARA/cooling_properties.h b/src/cooling/KIARA/cooling_properties.h new file mode 100644 index 0000000000..1a497547cf --- /dev/null +++ b/src/cooling/KIARA/cooling_properties.h @@ -0,0 +1,154 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_COOLING_PROPERTIES_KIARA_H +#define SWIFT_COOLING_PROPERTIES_KIARA_H + +/* include grackle */ +#include + +/** + * @file src/cooling/grackle/cooling_properties.h + * @brief Empty infrastructure for the cases without cooling function + */ + +/** + * @brief Properties of the cooling function. + */ +struct cooling_function_data { + + /*! Filename of the Cloudy Table */ + char cloudy_table[200]; + + /*! Enable/Disable UV backgroud */ + int with_uv_background; + + /*! Redshift to use for the UV backgroud (-1 to use cosmological one) */ + double redshift; + + /*! unit system */ + code_units units; + + /*! Temperature of the CMB at present day (for quick access) */ + double T_CMB_0; + + /*! k_Boltz/m_p plus conversion factor for converting u<->T */ + double temp_to_u_factor; + + /*! Convert time to Myr */ + double time_to_Myr; + + /*! conversion unit factor for rate of change of thermal energy */ + double dudt_units; + + /*! grackle chemistry data */ + chemistry_data chemistry; + + /*! Enable/Disable metal cooling */ + int with_metal_cooling; + + /*! User provide volumetric heating rates */ + int provide_volumetric_heating_rates; + + /*! User provide specific heating rates */ + int provide_specific_heating_rates; + + /*! Self shielding method (1 -> 3 for grackle's ones, 0 for none and -1 for + * GEAR) */ + int self_shielding_method; + + /*! What to do with adiabatic du/dt when in ISM mode: 0=no adiabatic heating, + * 1=evolve in grackle, 2=use to evaporate cold ism */ + int ism_adiabatic_heating_method; + + /*! number of step max for first init */ + int max_step; + + /*! max fractional change in quantities in single grackle substep */ + double timestep_accuracy; + + /*! parameter to control how fast grackle damps oscillatory behaviour + * (lower=more aggressive) */ + int grackle_damping_interval; + + /*! Duration for switching off cooling after an event (e.g. supernovae) */ + double thermal_time; + + /*! track dust growth and destruction (only available in KIARA) */ + int use_grackle_dust_evol; + + /*! track H2 formation; this is set within the code based on selection options + */ + int use_grackle_h2_form; + + /*! G0 conversion factors, scales to MW value based on local/global galaxy + * props */ + double G0_factor1; + double G0_factor2; + double G0_factorSNe; + + /*! Dust parameters; see sample yml file */ + double dust_destruction_eff; + double dust_sne_coeff; + double dust_sne_shockspeed; + double dust_grainsize; + double dust_growth_densref; + double dust_growth_tauref; + + /*! For dust model, need self-enrichment up to a small metallicity to + * kick-start dust */ + double self_enrichment_metallicity; + + /*! For subgrid model (eg KIARA) need a subgrid ISM fraction */ + double cold_ISM_frac; + + /*! For Grackle subgrid model, choose way to determine G0: 1=Local SFR density; + * 2=Global sSFR; 3=2 if sSFR != 0, else 1; -3: vice versa */ + int G0_computation_method; + + /*! For Grackle subgrid model, arbitrary multiplier for G0 */ + double G0_multiplier; + + /*! For Grackle subgrid model, set max density to avoid pointlessly + * over-iterating in Grackle */ + double max_subgrid_density; + + /*! For Grackle subgrid model, inverse of threshold nH above which multi-phase + * ISM model kicks in */ + double subgrid_threshold_n_H_inv; + + /*! For Grackle subgrid model, temperature at threshold nH */ + double subgrid_threshold_T; + + /*! For Grackle subgrid model, Power-law eqn of state for warm ISM component + * above threshold n_H */ + double subgrid_warm_ism_EOS; + + /*! For Grackle subgrid model, factor above entropy floor allowed to be in + * subgrid mode */ + double entropy_floor_margin; + + /*! Option to use Cloudy lookup tables when outside ISM */ + int use_tables_outside_ism; + + /*! When using radiative transfer, set this on if you want thermochemistry + * done within RT modules (eg in KIARART) */ + int do_cooling_in_rt; +}; + +#endif /* SWIFT_COOLING_PROPERTIES_KIARA_H */ diff --git a/src/cooling/KIARA/cooling_struct.h b/src/cooling/KIARA/cooling_struct.h new file mode 100644 index 0000000000..de3e2dcb35 --- /dev/null +++ b/src/cooling/KIARA/cooling_struct.h @@ -0,0 +1,100 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_COOLING_STRUCT_KIARA_H +#define SWIFT_COOLING_STRUCT_KIARA_H + +/** + * * @brief Properties of the cooling stored in the #part data. + * */ +struct cooling_part_data { + + /*! Subgrid temperature */ + float subgrid_temp; + + /*! Subgrid density (internal units, physical frame) */ + float subgrid_dens; + + /*! Subgrid fraction of cold mass */ + float subgrid_fcold; + +#if COOLING_GRACKLE_MODE >= 2 + /*! Dust properties when use_grackle_dust_evol=1 */ + + /* Total mass in dust */ + float dust_mass; // total mass in dust + + /* Fraction of each metal in dust */ + float dust_mass_fraction[chemistry_element_count]; + + /* Temperature of subgrid ISM */ + float dust_temperature; +#endif + + /*! Cooling time in mixing layer between stream and ambient gas */ + float mixing_layer_cool_time; + + /*! The strength of the interstellar radiation field in Habing units */ + float G0; + +#if COOLING_GRACKLE_MODE >= 2 + /*! Number of SNe (of any type) going off in nearby stars */ + float SNe_ThisTimeStep; +#endif +}; + +/** + * @brief Properties of the cooling stored in the extra particle data + */ +struct cooling_xpart_data { + + /*! Energy radiated away by this particle since the start of the run */ + float radiated_energy; + + /*! Last time the cooling was switch off */ + double time_last_event; + +/*! here all fractions are mass fraction */ +#if COOLING_GRACKLE_MODE >= 1 + float HI_frac; + float HII_frac; + float HeI_frac; + float HeII_frac; + float HeIII_frac; + float e_frac; + +#if COOLING_GRACKLE_MODE >= 2 + float HM_frac; + float H2I_frac; + float H2II_frac; + +#if COOLING_GRACKLE_MODE >= 3 + float DI_frac; + float DII_frac; + float HDI_frac; +#endif // MODE >= 3 + +#endif // MODE >= 2 + +#endif // MODE >= 1 + + /*! metal cooling = 1 */ + float metal_frac; +}; + +#endif /* SWIFT_COOLING_STRUCT_KIARA_H */ diff --git a/src/cooling_debug.h b/src/cooling_debug.h index 37d34e6bd8..ae8770ae4f 100644 --- a/src/cooling_debug.h +++ b/src/cooling_debug.h @@ -41,6 +41,8 @@ #include "./cooling/EAGLE/cooling_debug.h" #elif defined(COOLING_PS2020) #include "./cooling/PS2020/cooling_debug.h" +#elif defined(COOLING_KIARA) +#include "./cooling/KIARA/cooling_debug.h" #else #error "Invalid choice of cooling function." #endif diff --git a/src/cooling_io.h b/src/cooling_io.h index d3439b4d0a..1b84849834 100644 --- a/src/cooling_io.h +++ b/src/cooling_io.h @@ -41,6 +41,8 @@ #include "./cooling/EAGLE/cooling_io.h" #elif defined(COOLING_PS2020) #include "./cooling/PS2020/cooling_io.h" +#elif defined(COOLING_KIARA) +#include "./cooling/KIARA/cooling_io.h" #else #error "Invalid choice of cooling function." #endif diff --git a/src/cooling_properties.h b/src/cooling_properties.h index f48d153137..5383b17e15 100644 --- a/src/cooling_properties.h +++ b/src/cooling_properties.h @@ -46,6 +46,8 @@ #include "./cooling/EAGLE/cooling_properties.h" #elif defined(COOLING_PS2020) #include "./cooling/PS2020/cooling_properties.h" +#elif defined(COOLING_KIARA) +#include "./cooling/KIARA/cooling_properties.h" #else #error "Invalid choice of cooling function." #endif diff --git a/src/cooling_struct.h b/src/cooling_struct.h index b205cbfef3..b977bc0930 100644 --- a/src/cooling_struct.h +++ b/src/cooling_struct.h @@ -46,6 +46,8 @@ #include "./cooling/EAGLE/cooling_struct.h" #elif defined(COOLING_PS2020) #include "./cooling/PS2020/cooling_struct.h" +#elif defined(COOLING_KIARA) +#include "./cooling/KIARA/cooling_struct.h" #else #error "Invalid choice of cooling function." #endif diff --git a/src/debug.c b/src/debug.c index 2db8f69dff..32db343678 100644 --- a/src/debug.c +++ b/src/debug.c @@ -80,6 +80,8 @@ #include "./hydro/SPHENIX/hydro_debug.h" #elif defined(GASOLINE_SPH) #include "./hydro/Gasoline/hydro_debug.h" +#elif defined(MAGMA2_SPH) +#include "./hydro/MAGMA2/hydro_debug.h" #elif defined(ANARCHY_PU_SPH) #include "./hydro/AnarchyPU/hydro_debug.h" #else diff --git a/src/engine_unskip.c b/src/engine_unskip.c index f2e3feba67..37c5b927a7 100644 --- a/src/engine_unskip.c +++ b/src/engine_unskip.c @@ -225,10 +225,10 @@ static void engine_do_unskip_gravity(struct cell *c, struct engine *e) { if (!cell_is_active_gravity(c, e)) return; /* At the top level we need to recursively check particles haven't moved - * too far for the mesh gravity (if using the mesh). */ + * too far for the mesh gravity (if using the mesh). if (c->depth == 0 && e->s->periodic) { cell_check_grav_mesh_pairs(c, e); - } + }*/ /* Recurse */ if (c->split && ((c->maxdepth - c->depth) >= space_subdepth_diff_grav)) { diff --git a/src/feedback.h b/src/feedback.h index 65a64704c1..f8c3ff48a7 100644 --- a/src/feedback.h +++ b/src/feedback.h @@ -34,6 +34,9 @@ #include "./feedback/GEAR/feedback.h" #elif defined(FEEDBACK_AGORA) #include "./feedback/AGORA/feedback.h" +#elif defined(FEEDBACK_KIARA) +#include "./feedback/KIARA/feedback.h" +#define EXTRA_STAR_LOOPS #else #error "Invalid choice of feedback model" #endif diff --git a/src/feedback/KIARA/feedback.c b/src/feedback/KIARA/feedback.c new file mode 100644 index 0000000000..c5701d133b --- /dev/null +++ b/src/feedback/KIARA/feedback.c @@ -0,0 +1,1923 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +/* This file's header */ +#include "feedback.h" + +/* Local includes. */ +#include "hydro_properties.h" +#include "inline.h" +#include "random.h" +#include "timers.h" +#include "timestep_sync_part.h" + +#if COOLING_GRACKLE_MODE >= 2 + +/* This seems to be needed to get N_SNe and mass loss rates + * correct in chem5 Kroupa/Chabrier. Not sure why. */ +#define IMF_FUDGE_FACTOR 1.0f + +/** + * @brief Return log10 of the Habing band luminosity for a given star + * based on its age and metallicity, in erg/s + * + * @param sp The #spart outputting the radiation + * @param age The age of the star in internal units + */ +double feedback_get_lum_from_star_particle( + const struct spart *sp, double age, const struct feedback_props *fb_props) { + + /* Get age, convert to Myr */ + age *= fb_props->time_to_Myr; + if (age < 1.) age = 1.; /* lum is roughly constant prior to 1 Myr */ + /* Stars past 1000 Myr have negligible Habing flux; return very small log10 */ + if (age > 1000.) return -20.f; + + age = log10(age); + + /* Get mass in units of 10^6 Mo, which is the units of the STARBURST99 models + */ + double logmass6 = log10(sp->mass * fb_props->mass_to_solar_mass * 1.e-6); + + /* set up metallicity interpolation */ + double z = sp->chemistry_data.metal_mass_fraction_total; + double z_bins[5] = {0.04, 0.02, 0.008, 0.004, 0.001}; + double lum1, lum2, fhi = 0., flo = 1.; + + /* Interpolate luminosity in Habing band based on fits to STARBURST99 models + * (cf. G0_polyfit.py), for various metallicities + */ + if (age > 0.8) { /* Do older star case, well fit by power law */ + if (z > z_bins[0]) { + lum1 = 42.9568 - 1.66469 * age; + lum2 = lum1; + } else if (z > z_bins[1]) { + lum1 = 42.9568 - 1.66469 * age; + lum2 = 42.9754 - 1.57329 * age; + fhi = LOG_INTERPOLATION(z, z_bins[0], z_bins[1]); + } else if (z > z_bins[2]) { + lum1 = 42.9754 - 1.57329 * age; + lum2 = 43.003 - 1.49815 * age; + fhi = LOG_INTERPOLATION(z, z_bins[1], z_bins[2]); + } else if (z > z_bins[3]) { + lum1 = 43.003 - 1.49815 * age; + lum2 = 43.0151 - 1.46258 * age; + fhi = LOG_INTERPOLATION(z, z_bins[2], z_bins[3]); + } else if (z > z_bins[4]) { + lum1 = 43.0151 - 1.46258 * age; + lum2 = 43.0254 - 1.40997 * age; + fhi = LOG_INTERPOLATION(z, z_bins[3], z_bins[4]); + } else { + lum1 = 43.0254 - 1.40997 * age; + lum2 = lum1; + } + } else { /* Otherwise the star is very young and bright, so use more accurate + 6th order polynomial fit */ + if (z > z_bins[0]) { + lum1 = 41.8537 + 6.40018 * pow(age, 1) - 46.6675 * pow(age, 2) + + 180.784 * pow(age, 3) - 373.188 * pow(age, 4) + + 374.251 * pow(age, 5) - 144.345 * pow(age, 6); + lum2 = lum1; + } else if (z > z_bins[1]) { + lum1 = 41.8537 + 6.40018 * pow(age, 1) - 46.6675 * pow(age, 2) + + 180.784 * pow(age, 3) - 373.188 * pow(age, 4) + + 374.251 * pow(age, 5) - 144.345 * pow(age, 6); + lum2 = 41.3428 + 17.0277 * pow(age, 1) - 132.565 * pow(age, 2) + + 508.436 * pow(age, 3) - 998.223 * pow(age, 4) + + 954.621 * pow(age, 5) - 353.419 * pow(age, 6); + fhi = LOG_INTERPOLATION(z, z_bins[0], z_bins[1]); + } else if (z > z_bins[2]) { + lum1 = 41.3428 + 17.0277 * pow(age, 1) - 132.565 * pow(age, 2) + + 508.436 * pow(age, 3) - 998.223 * pow(age, 4) + + 954.621 * pow(age, 5) - 353.419 * pow(age, 6); + lum2 = 41.0623 + 22.0205 * pow(age, 1) - 172.018 * pow(age, 2) + + 655.587 * pow(age, 3) - 1270.91 * pow(age, 4) + + 1201.92 * pow(age, 5) - 441.57 * pow(age, 6); + fhi = LOG_INTERPOLATION(z, z_bins[1], z_bins[2]); + } else if (z > z_bins[3]) { + lum1 = 41.0623 + 22.0205 * pow(age, 1) - 172.018 * pow(age, 2) + + 655.587 * pow(age, 3) - 1270.91 * pow(age, 4) + + 1201.92 * pow(age, 5) - 441.57 * pow(age, 6); + lum2 = 41.3442 + 16.0189 * pow(age, 1) - 126.891 * pow(age, 2) + + 488.303 * pow(age, 3) - 945.774 * pow(age, 4) + + 887.47 * pow(age, 5) - 322.584 * pow(age, 6); + fhi = LOG_INTERPOLATION(z, z_bins[2], z_bins[3]); + } else if (z > z_bins[4]) { + lum1 = 41.3442 + 16.0189 * pow(age, 1) - 126.891 * pow(age, 2) + + 488.303 * pow(age, 3) - 945.774 * pow(age, 4) + + 887.47 * pow(age, 5) - 322.584 * pow(age, 6); + lum2 = 40.738 + 25.8218 * pow(age, 1) - 185.778 * pow(age, 2) + + 641.036 * pow(age, 3) - 1113.61 * pow(age, 4) + + 937.23 * pow(age, 5) - 304.342 * pow(age, 6); + fhi = LOG_INTERPOLATION(z, z_bins[3], z_bins[4]); + } else { + lum1 = 40.738 + 25.8218 * pow(age, 1) - 185.778 * pow(age, 2) + + 641.036 * pow(age, 3) - 1113.61 * pow(age, 4) + + 937.23 * pow(age, 5) - 304.342 * pow(age, 6); + lum2 = lum1; + } + } + + flo = 1. - fhi; + + /* return the interpolated Habing luminosity for this star in log10 erg/s */ + return lum1 * fhi + lum2 * flo + logmass6; +} + +void feedback_dust_production_condensation( + struct spart *sp, double star_age, const struct feedback_props *fb_props, + double delta_metal_mass[chemistry_element_count]) { + + const double *delta_table; + int k; + + /* Get age of star to separate AGB from SNII */ + star_age *= fb_props->time_to_Myr; + + /* initialize change in dust mass */ + for (k = 0; k < chemistry_element_count; k++) { + sp->feedback_data.delta_dust_mass[k] = 0.f; + } + + const double C_minus_O = delta_metal_mass[chemistry_element_C] - + delta_metal_mass[chemistry_element_O]; + if (star_age > 100. && C_minus_O > 0.) { + /* Compute dust mass created in high-C/O AGB stars + * (atomic C forms graphite) + */ + sp->feedback_data.delta_dust_mass[chemistry_element_C] = + fb_props->delta_AGBCOG1[chemistry_element_C] * + (delta_metal_mass[chemistry_element_C] - + 0.75 * delta_metal_mass[chemistry_element_O]); + const double max_dust_C = + fb_props->max_dust_fraction * delta_metal_mass[chemistry_element_C]; + /* Cap the new dust mass formed to some fraction of total ejecta + * metals in that element + */ + if (sp->feedback_data.delta_dust_mass[chemistry_element_C] > max_dust_C) { + sp->feedback_data.delta_dust_mass[chemistry_element_C] = max_dust_C; + } + + /* Subtract this from ejecta metals */ + delta_metal_mass[chemistry_element_C] -= + sp->feedback_data.delta_dust_mass[chemistry_element_C]; + } else { + /* Choose dust table: If age > 100 Myr, assume ejecta is from AGB, + * otherwise SNII + */ + if (star_age > 100.) { + delta_table = fb_props->delta_AGBCOL1; + } else { + delta_table = fb_props->delta_SNII; + } + + /* Compute dust mass created in either SNII or low-C/O AGB stars + * (same type of dust, just different coefficients) + */ + for (k = chemistry_element_He; k < chemistry_element_count; k++) { + if (k == + chemistry_element_O) { /* O in oxide of Mg, Si, S, Ca, (Ti), Fe */ + sp->feedback_data.delta_dust_mass[k] = + 16.0 * (delta_table[chemistry_element_Mg] * + delta_metal_mass[chemistry_element_Mg] / 24.305 + + delta_table[chemistry_element_Si] * + delta_metal_mass[chemistry_element_Si] / 28.0855 + + fb_props->delta_AGBCOL1[chemistry_element_S] * + delta_metal_mass[chemistry_element_S] / 32.065 + + fb_props->delta_AGBCOL1[chemistry_element_Ca] * + delta_metal_mass[chemistry_element_Ca] / 40.078 + + fb_props->delta_AGBCOL1[chemistry_element_Fe] * + delta_metal_mass[chemistry_element_Fe] / 55.845); + } else { + sp->feedback_data.delta_dust_mass[k] = + delta_table[k] * delta_metal_mass[k]; + } + + const double max_dust = fb_props->max_dust_fraction * delta_metal_mass[k]; + if (sp->feedback_data.delta_dust_mass[k] > max_dust) { + sp->feedback_data.delta_dust_mass[k] = max_dust; + } + + delta_metal_mass[k] -= sp->feedback_data.delta_dust_mass[k]; + } + } +} +#endif + +/** + * @brief Run the Chem5 module that interpolates the yield tables and returns + * the ejected mass, metals, and unprocessed materials. + * + * @param sp The #spart to consider. + * @param age The stellar age in code units. + * @param fb_props The feedback properties. + * @param dt The current timestep. + * @param ejecta_energy The total ejected energy in code units. + * @param ejecta_mass The total ejected mass in code units. + * @param ejecta_unprocessed The unprocessed mass in code units. + * @param ejecta_metal_mass The metal masses for each element in + * chem5_element_count in code units. + */ +void feedback_get_ejecta_from_star_particle( + const struct spart *sp, double age, const struct feedback_props *fb_props, + double dt, double *N_SNe, double *ejecta_energy, double *ejecta_mass, + double *ejecta_unprocessed, double ejecta_metal_mass[chem5_element_count]) { + int j, k, j1, j2, l, l1 = 0, l2 = 0, ll1 = 0, ll2 = 0, lll1 = 0, lll2 = 0; + double SW_R, SNII_R, SNII_U, SNII_E, SNII_Z[chem5_element_count]; + double SNII_ENE, SNIa_R, SNIa_E = 0., SNIa_Z[chem5_element_count]; + double SNn = 0., SWn = 0., ejecta_mass_Ia = 0.; + double SNIIa, SNIIb, z, lz; + + /* Convert to yr for code below */ + age *= fb_props->time_to_yr; + dt *= fb_props->time_to_yr; + + *ejecta_energy = 0.; + *ejecta_mass = 0.; + *ejecta_unprocessed = 0.; + for (k = 0; k < chem5_element_count; k++) ejecta_metal_mass[k] = 0.; + + /* @TODO What does "fb" mean? fb stage? */ + int fb = 0; + int fb_first = 0 + fb; + + if (sp->mass_init == sp->mass) fb_first = 1; + + z = sp->chemistry_data.metal_mass_fraction_total; + + /* [Fe/H] */ + double feh = -10.; + if (z < 1.e-10) { + lz = -10.; + } else { + lz = log10(z); + feh = sp->chemistry_data.metal_mass_fraction[chemistry_element_Fe] / + sp->chemistry_data.metal_mass_fraction[chemistry_element_H]; + if (feh > 0.) feh = log10((feh / fb_props->Fe_mf) * fb_props->H_mf); + if (feh > fb_props->tables.SNLZ1R[NZSN1R - 1]) { + feh = fb_props->tables.SNLZ1R[NZSN1R - 1]; + } + } + + double tm1 = feedback_get_turnover_mass(fb_props, age, z); + + if (tm1 >= fb_props->M_u3) return; + + double ltm = log10(tm1 * fb_props->solar_mass_to_mass); + + for (j = 1; j < NM; j++) { + j1 = j - 1; + j2 = j; + if (fb_props->tables.SNLM[j] < ltm) break; + } + + /* This is only true if we do PopIII stars */ + if (z <= fb_props->zmax3) { + SNII_U = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(1, 0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(1, 0, j2)], + ltm); + SNII_E = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(2, 0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(2, 0, j2)], + ltm); + SNII_ENE = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(0, 0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(0, 0, j2)], + ltm); + + for (k = 0; k < chem5_element_count; k++) { + SNII_Z[k] = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], + fb_props->tables.SN2E[SN2E_idx((k + 3), 0, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2E[SN2E_idx((k + 3), 0, j2)], ltm); + } + + SNII_R = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2R[SN2R_idx(0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2R[SN2R_idx(0, j2)], ltm); + SW_R = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SWR[SWR_idx(0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SWR[SWR_idx(0, j2)], ltm); + SNIa_R = 0.0; + } else { + for (l = 2; l < NZSN; l++) { + l1 = l - 1; + l2 = l; + if (fb_props->tables.SNLZ[l] > lz) break; + } + for (l = 1; l < NZSN1R; l++) { + ll1 = l - 1; + ll2 = l; + if (fb_props->tables.SNLZ1R[l] > feh) break; + } + for (l = 1; l < NZSN1Y; l++) { + lll1 = l - 1; + lll2 = l; + if (fb_props->tables.SNLZ1Y[l] > lz) break; + } + + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(1, l1, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(1, l1, j2)], + ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(1, l2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(1, l2, j2)], + ltm); + SNII_U = LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(2, l1, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(2, l1, j2)], + ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(2, l2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(2, l2, j2)], + ltm); + SNII_E = LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + + if (l2 == NZSN - 1) { + SNII_ENE = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(0, l2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(0, l2, j2)], + ltm); + } else { + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLZ[l1], fb_props->tables.SN2E[SN2E_idx(0, l1, j1)], + fb_props->tables.SNLZ[l2], fb_props->tables.SN2E[SN2E_idx(0, l2, j1)], + lz); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLZ[l1], fb_props->tables.SN2E[SN2E_idx(0, l1, j2)], + fb_props->tables.SNLZ[l2], fb_props->tables.SN2E[SN2E_idx(0, l2, j2)], + lz); + SNII_ENE = LINEAR_INTERPOLATION(fb_props->tables.SNLM[j1], SNIIa, + fb_props->tables.SNLM[j2], SNIIb, ltm); + } + + for (k = 0; k < chem5_element_count; k++) { + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], + fb_props->tables.SN2E[SN2E_idx((k + 3), l1, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2E[SN2E_idx((k + 3), l1, j2)], ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], + fb_props->tables.SN2E[SN2E_idx((k + 3), l2, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2E[SN2E_idx((k + 3), l2, j2)], ltm); + SNII_Z[k] = LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + } + + SNIIa = LINEAR_INTERPOLATION(fb_props->tables.SNLM[j1], + fb_props->tables.SN2R[SN2R_idx(l1, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2R[SN2R_idx(l1, j2)], ltm); + SNIIb = LINEAR_INTERPOLATION(fb_props->tables.SNLM[j1], + fb_props->tables.SN2R[SN2R_idx(l2, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2R[SN2R_idx(l2, j2)], ltm); + SNII_R = LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SWR[SWR_idx(l1, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SWR[SWR_idx(l1, j2)], ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SWR[SWR_idx(l2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SWR[SWR_idx(l2, j2)], ltm); + SW_R = LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + + if (feh < fb_props->tables.SNLZ1R[0]) { + SNIa_R = 0.; + } else { + SNIa_E = + LINEAR_INTERPOLATION(fb_props->tables.SNLZ1Y[lll1], + fb_props->tables.SN1E[SN1E_idx(2, lll1)], + fb_props->tables.SNLZ1Y[lll2], + fb_props->tables.SN1E[SN1E_idx(2, lll2)], lz); + + for (k = 0; k < chem5_element_count; k++) { + SNIa_Z[k] = LINEAR_INTERPOLATION( + fb_props->tables.SNLZ1Y[lll1], + fb_props->tables.SN1E[SN1E_idx((k + 3), lll1)], + fb_props->tables.SNLZ1Y[lll2], + fb_props->tables.SN1E[SN1E_idx((k + 3), lll2)], lz); + } + + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN1R[SN1R_idx(ll1, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN1R[SN1R_idx(ll1, j2)], + ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN1R[SN1R_idx(ll2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN1R[SN1R_idx(ll2, j2)], + ltm); + SNIa_R = LINEAR_INTERPOLATION(fb_props->tables.SNLZ1R[ll1], SNIIa, + fb_props->tables.SNLZ1R[ll2], SNIIb, feh); + } + } + + double tm2 = 1.e-10; + + if (age > dt) { + tm2 = feedback_get_turnover_mass(fb_props, age - dt, z); + + ltm = log10(tm2 * fb_props->solar_mass_to_mass); + for (j = 1; j < NM; j++) { + j1 = j - 1; + j2 = j; + if (fb_props->tables.SNLM[j] < ltm) break; + } + + if (z <= fb_props->zmax3) { + SNII_U -= LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(1, 0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(1, 0, j2)], + ltm); + SNII_E -= LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(2, 0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(2, 0, j2)], + ltm); + SNII_ENE -= LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(0, 0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(0, 0, j2)], + ltm); + + for (k = 0; k < chem5_element_count; k++) { + SNII_Z[k] -= LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], + fb_props->tables.SN2E[SN2E_idx((k + 3), 0, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2E[SN2E_idx((k + 3), 0, j2)], ltm); + } + + SNII_R -= LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2R[SN2R_idx(0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2R[SN2R_idx(0, j2)], + ltm); + SW_R -= LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SWR[SWR_idx(0, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SWR[SWR_idx(0, j2)], ltm); + } else { + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(1, l1, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(1, l1, j2)], + ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(1, l2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(1, l2, j2)], + ltm); + SNII_U -= LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(2, l1, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(2, l1, j2)], + ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2E[SN2E_idx(2, l2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2E[SN2E_idx(2, l2, j2)], + ltm); + SNII_E -= LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + + if (l2 == NZSN - 1) { + SNII_ENE -= LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], + fb_props->tables.SN2E[SN2E_idx(0, l2, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2E[SN2E_idx(0, l2, j2)], ltm); + } else { + SNIIa = LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], + fb_props->tables.SN2E[SN2E_idx(0, l1, j1)], + fb_props->tables.SNLZ[l2], + fb_props->tables.SN2E[SN2E_idx(0, l2, j1)], + lz); + SNIIb = LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], + fb_props->tables.SN2E[SN2E_idx(0, l1, j2)], + fb_props->tables.SNLZ[l2], + fb_props->tables.SN2E[SN2E_idx(0, l2, j2)], + lz); + SNII_ENE -= LINEAR_INTERPOLATION(fb_props->tables.SNLM[j1], SNIIa, + fb_props->tables.SNLM[j2], SNIIb, ltm); + } + + for (k = 0; k < chem5_element_count; k++) { + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], + fb_props->tables.SN2E[SN2E_idx((k + 3), l1, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2E[SN2E_idx((k + 3), l1, j2)], ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], + fb_props->tables.SN2E[SN2E_idx((k + 3), l2, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SN2E[SN2E_idx((k + 3), l2, j2)], ltm); + SNII_Z[k] -= LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + } + + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2R[SN2R_idx(l1, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2R[SN2R_idx(l1, j2)], + ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN2R[SN2R_idx(l2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN2R[SN2R_idx(l2, j2)], + ltm); + SNII_R -= LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + SNIIa = LINEAR_INTERPOLATION(fb_props->tables.SNLM[j1], + fb_props->tables.SWR[SWR_idx(l1, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SWR[SWR_idx(l1, j2)], ltm); + SNIIb = LINEAR_INTERPOLATION(fb_props->tables.SNLM[j1], + fb_props->tables.SWR[SWR_idx(l2, j1)], + fb_props->tables.SNLM[j2], + fb_props->tables.SWR[SWR_idx(l2, j2)], ltm); + SW_R -= LINEAR_INTERPOLATION(fb_props->tables.SNLZ[l1], SNIIa, + fb_props->tables.SNLZ[l2], SNIIb, lz); + + if (feh < fb_props->tables.SNLZ1R[0]) { + SNIa_R = 0.; + } else { + SNIIa = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN1R[SN1R_idx(ll1, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN1R[SN1R_idx(ll1, j2)], + ltm); + SNIIb = LINEAR_INTERPOLATION( + fb_props->tables.SNLM[j1], fb_props->tables.SN1R[SN1R_idx(ll2, j1)], + fb_props->tables.SNLM[j2], fb_props->tables.SN1R[SN1R_idx(ll2, j2)], + ltm); + SNIa_R -= + LINEAR_INTERPOLATION(fb_props->tables.SNLZ1R[ll1], SNIIa, + fb_props->tables.SNLZ1R[ll2], SNIIb, feh); + } + } + } + + *ejecta_unprocessed = max(0., sp->mass_init * SNII_U); + *ejecta_mass = max(0., sp->mass_init * SNII_E); + + /* For some reason at the first step this might happen */ + if (isnan(SNII_U) || isnan(SNII_E)) { + warning("SNII_U or SNII_E is NaN, j1=%d l1=%d z=%g mturn=%g %g age=%g", j1, + l1, z, tm1, tm2, age); + *ejecta_unprocessed = *ejecta_mass = 0.; + return; + } + + if (tm1 > fb_props->M_u2) { + fb = 3; + if (z == 0.) { + *ejecta_energy = 0.; + } else { + SWn = sp->mass_init * SW_R; + if (fb_props->with_HN_energy_from_chem5) { + /* E_sw converts to code units */ + *ejecta_energy = SWn * fb_props->E_sw * pow(z / fb_props->Z_mf, 0.8); + } + + /* Needed for dust model within Grackle; for now treat PopIII SNe + * same as PopI/II + */ + *N_SNe = SWn; + } + } else { + if (tm2 > fb_props->M_l2 || fb_first == 1) { + fb = 2; + + SWn = sp->mass_init * SW_R; + SNn = sp->mass_init * SNII_R; + if (fb_props->with_SNII_energy_from_chem5) { + *ejecta_energy = SWn * fb_props->E_sw; + *ejecta_energy += sp->mass_init * SNII_ENE; + } + /* Needed for dust model within Grackle; for now + * treat PopIII SNe same as PopI/II + */ + *N_SNe = SNn + SWn; + } + + for (k = 0; k < chem5_element_count; k++) { + ejecta_metal_mass[k] = sp->mass_init * SNII_Z[k]; + } + + if (tm1 <= fb_props->M_l2) { + if (feh < fb_props->tables.SNLZ1R[0]) { + SNIa_R = 0.; + } else { + fb = 1; + SNn = sp->mass_init * SNIa_R; + /* SNIa always contribute */ + if (fb_props->with_SNIa_energy_from_chem5 == 1) { + *ejecta_energy += SNn * fb_props->E_sn1; + } + /* SNIa contribute if age < with_SNIa_energy_from_chem5 */ + else if (fb_props->with_SNIa_energy_from_chem5 > 10.) { + const double age_in_Myr = + (age / fb_props->time_to_yr) * fb_props->time_to_Myr; + if (age_in_Myr < fb_props->with_SNIa_energy_from_chem5) { + *ejecta_energy += SNn * fb_props->E_sn1; + } + } + + ejecta_mass_Ia += SNn * SNIa_E; + *ejecta_mass += SNn * SNIa_E; + for (k = 0; k < chem5_element_count; k++) { + ejecta_metal_mass[k] += SNn * SNIa_Z[k]; + } + + // Add in the TypeIa's too + *N_SNe += SNn; + } + } + } + + if (*ejecta_energy < 0.) { + warning( + "Star %lld energy<0! m=%g (frac=%g), age=%g Myr, Z=%g " + "is ejecting %g Msun (fIa=%g, Zej=%g) and %g erg (%g in SNe) " + "in %g Myr.", + sp->id, sp->mass * fb_props->mass_to_solar_mass, + sp->mass / sp->mass_init, + (age / fb_props->time_to_yr) * fb_props->time_to_Myr, log10(z + 1.e-6), + *ejecta_mass * fb_props->mass_to_solar_mass, + ejecta_mass_Ia / *ejecta_mass, + log10(ejecta_metal_mass[0] / *ejecta_mass + 1.e-6), + *ejecta_energy * fb_props->energy_to_cgs, *N_SNe * 1.e51, + (dt / fb_props->time_to_yr) * fb_props->time_to_Myr); + } +} + +double feedback_life_time(const struct feedback_props *fb_props, const double m, + const double z) { + int j, j1, j2, l, l1, l2; + double lm, lz, ta, tb, t; + + lm = log10(m); + j1 = 0; + j2 = 1; + for (j = 1; j < NMLF; j++) { + j1 = j - 1; + j2 = j; + if (fb_props->tables.LFLM[j] > lm) break; + } + + if (z == 0.) { + lz = -990.; + } else { + lz = log10(z); + } + + if (lz <= fb_props->tables.LFLZ[0]) { + t = LINEAR_INTERPOLATION( + fb_props->tables.LFLM[j1], fb_props->tables.LFLT[LFLT_idx(0, j1)], + fb_props->tables.LFLM[j2], fb_props->tables.LFLT[LFLT_idx(0, j2)], lm); + } else { + l1 = 0; + l2 = 1; + for (l = 1; l < NZLF; l++) { + l1 = l - 1; + l2 = l; + if (fb_props->tables.LFLZ[l] > lz) break; + } + ta = LINEAR_INTERPOLATION( + fb_props->tables.LFLZ[l1], fb_props->tables.LFLT[LFLT_idx(l1, j1)], + fb_props->tables.LFLZ[l2], fb_props->tables.LFLT[LFLT_idx(l2, j1)], lz); + tb = LINEAR_INTERPOLATION( + fb_props->tables.LFLZ[l1], fb_props->tables.LFLT[LFLT_idx(l1, j2)], + fb_props->tables.LFLZ[l2], fb_props->tables.LFLT[LFLT_idx(l2, j2)], lz); + t = LINEAR_INTERPOLATION(fb_props->tables.LFLM[j1], ta, + fb_props->tables.LFLM[j2], tb, lm); + } + + return pow(10., t); +} + +double feedback_imf(const struct feedback_props *fb_props, const double m) { + if (fb_props->imf == 0) { /* Kroupa */ + if (m >= 0.5) { + return pow(m, -fb_props->ximf) * 0.5; + } else if (m >= 0.08) { + return pow(m, -0.3); + } else { + return pow(m, 0.7) / 0.08; + } + } else if (fb_props->imf == 1) { /* Chabrier */ + if (m <= 1.0 && m > 0.01) { + const double dm = log10(m) - log10(0.079); + return 0.7895218 * exp((-1. * pow(dm, 2.)) / (2. * pow(0.69, 2.))); + } else { + return 0.2203457 * pow(m, -fb_props->ximf); + } + } else { + return pow(m, -fb_props->ximf); + } +} + +void feedback_set_turnover_mass(const struct feedback_props *fb_props, + const double z, double *LFLT2) { + double lz; + int j, l, l1, l2; + + if (z == 0.) { + lz = -4.1; + } else { + lz = log10f(z); + } + + if (lz < -4.1) lz = -4.1; + + l1 = 0; + l2 = 1; + for (l = 1; l < NZLF; l++) { + l1 = l - 1; + l2 = l; + if (fb_props->tables.LFLZ[l] > lz) break; + } + + for (j = 0; j < NMLF; j++) { + LFLT2[j] = LINEAR_INTERPOLATION( + fb_props->tables.LFLZ[l1], fb_props->tables.LFLT[LFLT_idx(l1, j)], + fb_props->tables.LFLZ[l2], fb_props->tables.LFLT[LFLT_idx(l2, j)], lz); + } + return; +} + +double feedback_get_turnover_mass(const struct feedback_props *fb_props, + const double t, const double z) { + if (t == 0.) return fb_props->M_u3; + + double result, m, lt; + int j, j1, j2; + double LFLT2[NMLF]; + + feedback_set_turnover_mass(fb_props, z, LFLT2); + + lt = log10(t); + j1 = 0; + j2 = 1; + for (j = 1; j < NMLF; j++) { + j1 = j - 1; + j2 = j; + if (LFLT2[j] < lt) break; + } + + m = LINEAR_INTERPOLATION(LFLT2[j1], fb_props->tables.LFLM[j1], LFLT2[j2], + fb_props->tables.LFLM[j2], lt); + result = pow(10., m); + + if (result < fb_props->M_l) return fb_props->M_l; + if (result > fb_props->M_u3) return fb_props->M_u3; + + return result; +} + +void feedback_prepare_interpolation_tables( + const struct feedback_props *fb_props) { + int i, j, k, j1, j2, l; + double sni[NXSNall][NZSN1Y], sn[NXSNall][NZSN][NMSN - 2], + hn[NXSNall][NZSN][4]; + double sniilm[NMSN], snii[chem5_NXSN][NZSN][NMSN]; + double SN1wd[NZSN1R][NM], SN1ms[NZSN1R][NM], SN1rg[NZSN1R][NM]; + double m[NM], imf[NZSN][NM]; + double snii2_hi, snii2_lo; + double dlm, norm, norm3; + double m_l; + FILE *fp; + char buf[1000], *dummy; + double a1, a2, a3, a4, a5, a6, a7; + double a8, a9, a10, a11, a12, a13, a14, a15, a16; + double a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27; + double a28, a29, a30; + double effSNz[NZSN], temp, tempz; + const double lfz[NZLF] = {.0001, .0002, .0005, .0010, .0020, + .0040, .0080, .0200, .0500}; + double temp_ms, temp_rg; + + /* Massloss (Kobayashi et al. 2000) + * sw[2][24]: progenitor mass, He core mass = NSorWD mass + */ + const double sw[2][NMSN] = { + {40., 30., 25., 20., 18., 15., 13., 10., 9., 8.5, 8., + 7.5, 7., 6.5, 6.0, 5.5, 5.0, 4.5, 4., 3.5, 3., 2.5, + 2.25, 2., 1.9, 1.75, 1.5, 1.25, 1.0, 0.9, 0.7, 0.05}, + {0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., + 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.473, 0.459, 0.05}}; + const double sniie[9] = {30, 20, 10, 10, 1, 1, 1, 1, 1}; + const double sniiz[NZSN] = {0., 0., .001, .004, .008, .02, .05}; + const double sniz[NZSN1Y] = {0., .002, .01, .02, .04, .06, .10}; + const double effHNz[NZSN] = {0.5, 0.5, 0.5, 0.4, 0.232036142, 0.01, 0.01}; + + /* [Fe/H] */ + const double feh_ia[NZSN1R] = {-1.1, -1.0, -0.69896996, 0., 0.39794001}; + const double M_l1rg[NZSN1R] = {0.9, 0.9, 0.9, 0.9, 0.8}; + const double M_l1ms[NZSN1R] = {1.8, 1.8, 1.8, 1.8, 1.8}; + const double M_u1rg[NZSN1R] = {0.9, 1.5, 2.0, 3.0, 3.5}; + const double M_u1ms[NZSN1R] = {1.8, 2.6, 4.0, 5.5, 6.0}; + const double M_l1wd[NZSN1R] = {2.4, 2.5, 2.80103004, 3.5, 3.89794001}; + const double M_u1wd[NZSN1R] = {6.75, 6.75, 7.05, 7.95, 7.95}; + + for (l = 0; l < NZSN1R; l++) fb_props->tables.SNLZ1R[l] = feh_ia[l]; + + if (engine_rank == 0) { + message("set nucleosynthesis yields for %i elements...", + chem5_element_count); + message("Z-dependent HN efficiency !!! "); + message("effHN = %f %f", effHNz[0], effHNz[NZSN - 1]); + message("Z-dependent SNIa model !!! "); + message("b=(%.3f %.3f) [Fe/H] > %f", fb_props->b_rg, fb_props->b_ms, + fb_props->tables.SNLZ1R[0]); + message("Z-dependent SAGB!!!"); + } + + sprintf(buf, "%s/SN2SAGBYIELD.DAT", fb_props->tables_path); + if ((fp = fopen(buf, "r")) == NULL) { + fprintf(stderr, "Can not open File %s\n", buf); + exit(-1); + } + + for (j = 1; j < NZSN; j++) { + dummy = fgets(buf, 1000, fp); /* metallicity */ + dummy = fgets(buf, 1000, fp); /* mass */ + dummy = fgets(buf, 1000, fp); /* energy */ + for (k = 0; k < NXSNall; k++) { /* k=0: masscut */ + dummy = fgets(buf, 1000, fp); + sscanf(buf, + "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf" + "%lf%lf%lf%lf%lf%lf%lf%lf%lf%lf\n", + &a1, &a2, &a3, &a4, &a5, &a6, &a7, &a8, &a9, &a10, &a11, &a12, + &a13, &a14, &a15, &a16, &a17, &a18, &a19, &a20, &a21, &a22, &a23, + &a24, &a25, &a26, &a27, &a28, &a29, &a30); + sn[k][j][0] = a30; + sn[k][j][1] = a29; + sn[k][j][2] = a28; + sn[k][j][3] = a27; + sn[k][j][4] = a26; + sn[k][j][5] = a25; + sn[k][j][6] = a24; + sn[k][j][7] = a23; + sn[k][j][8] = a22; + sn[k][j][9] = a21; + sn[k][j][10] = a20; + sn[k][j][11] = a19; + sn[k][j][12] = a18; + sn[k][j][13] = a17; + sn[k][j][14] = a16; + sn[k][j][15] = a15; + sn[k][j][16] = a14; + sn[k][j][17] = a13; + sn[k][j][18] = a12; + sn[k][j][19] = a11; + sn[k][j][20] = a10; + sn[k][j][21] = a9; + sn[k][j][22] = a8; + sn[k][j][23] = a7; + sn[k][j][24] = a6; + sn[k][j][25] = a5; + sn[k][j][26] = a4; + sn[k][j][27] = a3; + sn[k][j][28] = a2; + sn[k][j][29] = a1; + } + } + + for (j = 1; j < NZSN; j++) { + dummy = fgets(buf, 200, fp); + dummy = fgets(buf, 200, fp); + dummy = fgets(buf, 200, fp); + for (k = 0; k < NXSNall; k++) { + dummy = fgets(buf, 200, fp); + sscanf(buf, "%lf%lf%lf%lf\n", &a1, &a2, &a3, &a4); + hn[k][j][0] = a4; + hn[k][j][1] = a3; + hn[k][j][2] = a2; + hn[k][j][3] = a1; + } + } + + fclose(fp); + + for (i = 0; i < NMSN - 2; i++) { + for (k = 0; k < NXSNall; k++) sn[k][0][i] = sn[k][1][i]; /* pop3 */ + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < NXSNall; k++) hn[k][0][i] = hn[k][1][i]; /* pop3 */ + } + + for (j = 0; j < NZSN; j++) effSNz[j] = 1. - effHNz[j]; + + for (i = 0; i < 4; i++) { + for (j = 0; j < NZSN; j++) { + for (k = 0; k < NXSNall; k++) { + sn[k][j][i] = effSNz[j] * sn[k][j][i] + effHNz[j] * hn[k][j][i]; + } + } + } + + /* Set up SNII yield tables */ + for (i = 0; i < NMSN - 2; i++) { + sniilm[i] = log10(sw[0][i]); + for (j = 0; j < NZSN; j++) { + temp = tempz = 0.; + for (k = 0; k < NXSNall; k++) { + sn[k][j][i] /= sw[0][i]; + if (k > 0) temp += sn[k][j][i]; + if (k > 9) tempz += sn[k][j][i]; + } + + snii[2][j][i] = 1. - sn[0][j][i]; /* ejected mass */ + snii[1][j][i] = snii[2][j][i] - temp; /* unprocessed mass */ + if (snii[1][j][i] < 0.) { + snii[2][j][i] = temp; + snii[1][j][i] = 0.; + } + snii[3][j][i] = tempz; /* Z */ + snii[4][j][i] = sn[1][j][i] + sn[2][j][i]; /* H */ + snii[5][j][i] = sn[3][j][i] + sn[4][j][i]; /* He */ + snii[6][j][i] = sn[5][j][i] + sn[6][j][i]; /* Li */ + snii[7][j][i] = sn[7][j][i]; /* Be */ + snii[8][j][i] = sn[8][j][i] + sn[9][j][i]; /* B */ + snii[9][j][i] = sn[10][j][i] + sn[11][j][i]; /* C */ + snii[10][j][i] = sn[12][j][i] + sn[13][j][i]; /* N */ + snii[11][j][i] = sn[14][j][i] + sn[15][j][i] + sn[16][j][i]; /* O */ + snii[12][j][i] = sn[17][j][i]; /* F */ + snii[13][j][i] = sn[18][j][i] + sn[19][j][i] + sn[20][j][i]; /* Ne */ + snii[14][j][i] = sn[21][j][i]; /* Na */ + snii[15][j][i] = sn[22][j][i] + sn[23][j][i] + sn[24][j][i]; /* Mg */ + snii[16][j][i] = sn[25][j][i]; /* Al */ + snii[17][j][i] = sn[26][j][i] + sn[27][j][i] + sn[28][j][i]; /* Si */ + snii[18][j][i] = sn[29][j][i]; /* P */ + snii[19][j][i] = + sn[30][j][i] + sn[31][j][i] + sn[32][j][i] + sn[33][j][i]; /* S */ + snii[20][j][i] = sn[34][j][i] + sn[35][j][i]; /* Cl */ + snii[21][j][i] = sn[36][j][i] + sn[37][j][i] + sn[38][j][i]; /* Ar */ + snii[22][j][i] = sn[39][j][i] + sn[40][j][i] + sn[41][j][i]; /* K */ + snii[23][j][i] = sn[42][j][i] + sn[43][j][i] + sn[44][j][i] + + sn[45][j][i] + sn[46][j][i] + sn[47][j][i]; /* Ca */ + snii[24][j][i] = sn[48][j][i]; /* Sc */ + snii[25][j][i] = sn[49][j][i] + sn[50][j][i] + sn[51][j][i] + + sn[52][j][i] + sn[53][j][i]; /* Ti */ + snii[26][j][i] = sn[54][j][i] + sn[55][j][i]; /* V */ + snii[27][j][i] = + sn[56][j][i] + sn[57][j][i] + sn[58][j][i] + sn[59][j][i]; /* Cr */ + snii[28][j][i] = sn[60][j][i]; /* Mn */ + snii[29][j][i] = + sn[61][j][i] + sn[62][j][i] + sn[63][j][i] + sn[64][j][i]; /* Fe */ + snii[30][j][i] = sn[65][j][i]; /* Co */ + snii[31][j][i] = sn[66][j][i] + sn[67][j][i] + sn[68][j][i] + + sn[69][j][i] + sn[70][j][i]; /* Ni */ + snii[32][j][i] = sn[71][j][i] + sn[72][j][i]; /* Cu */ + snii[33][j][i] = sn[73][j][i] + sn[74][j][i] + sn[75][j][i] + + sn[76][j][i] + sn[77][j][i]; /* Zn */ + snii[34][j][i] = sn[78][j][i] + sn[79][j][i]; /* Ga */ + snii[35][j][i] = + sn[80][j][i] + sn[81][j][i] + sn[82][j][i] + sn[83][j][i]; /* Ge */ + snii[36][j][i] = 0.; + } + } + + for (i = 9; i < NMSN - 2; i++) { + for (j = 0; j < NZSN; j++) { + for (k = 29; k < 36; k++) snii[k][j][i] = 0.; /* Fe in AGB 09.10.12 */ + } + } + + for (i = NMSN - 2; i < NMSN; i++) /* 1 < Msun */ { + sniilm[i] = log10(sw[0][i]); + for (j = 0; j < NZSN; j++) { + snii[1][j][i] = 1. - sw[1][i] / sw[0][i]; + snii[2][j][i] = snii[1][j][i]; + for (k = 3; k < chem5_NXSN; k++) snii[k][j][i] = 0.; + } + } + + for (j = 0; j < NZSN; j++) { + /* Energy, 10-40 Msun */ + for (i = 0; i < 9; i++) { + snii[0][j][i] = effSNz[j] * 1.0 + effHNz[j] * sniie[i]; + } + + for (i = 9; i < NMSN; i++) { + snii[0][j][i] = 0.; + } + } + + /* Multiply all metal yields by a constant factor if desired */ + for (i = 0; i < NMSN - 2; i++) { + for (j = 0; j < NZSN; j++) { + for (k = 6; k < 36; k++) + snii[k][j][i] *= fb_props->metal_yield_multiplier; + } + } + + fb_props->tables.SNLZ[0] = -999.; /* z=0 */ + fb_props->tables.SNLZ[1] = -10.; /* z=0 */ + for (j = 2; j < NZSN; j++) fb_props->tables.SNLZ[j] = log10(sniiz[j]); + + fb_props->tables.SNLZ1Y[0] = -999.; /* z=0 */ + for (j = 1; j < NZSN1Y; j++) fb_props->tables.SNLZ1Y[j] = log10(sniz[j]); + + /* SNIa yield (Kobayashi et al. 2020a, DDT) */ + sprintf(buf, "%s/SN1YIELD_Z.DAT", fb_props->tables_path); + if ((fp = fopen(buf, "r")) == NULL) { + fprintf(stderr, "Can not open File %s %s\n", buf, dummy); + exit(-1); + } + + dummy = fgets(buf, 1000, fp); /* metallicity */ + for (k = 0; k < NXSNall; k++) { /* k=0: ejected mass */ + dummy = fgets(buf, 1000, fp); + sscanf(buf, "%lf%lf%lf%lf%lf%lf%lf\n", &a1, &a2, &a3, &a4, &a5, &a6, &a7); + sni[k][0] = a1; + sni[k][1] = a2; + sni[k][2] = a3; + sni[k][3] = a4; + sni[k][4] = a5; + sni[k][5] = a6; + sni[k][6] = a7; + } + fclose(fp); + + /* Set up SNIa+AGB yield tables */ + for (j = 0; j < NZSN1Y; j++) { + temp = tempz = 0.; + for (k = 0; k < NXSNall; k++) { + if (k > 0) temp += sni[k][j]; + if (k > 9) tempz += sni[k][j]; + } + + fb_props->tables.SN1E[SN1E_idx(0, j)] = 1.3; /* energy */ + fb_props->tables.SN1E[SN1E_idx(1, j)] = 0.; /* unprocessed */ + fb_props->tables.SN1E[SN1E_idx(2, j)] = temp; /* ejected */ + fb_props->tables.SN1E[SN1E_idx(3, j)] = tempz; /* Z */ + fb_props->tables.SN1E[SN1E_idx(4, j)] = sni[1][j] + sni[2][j]; /* H */ + fb_props->tables.SN1E[SN1E_idx(5, j)] = sni[3][j] + sni[4][j]; /* He */ + fb_props->tables.SN1E[SN1E_idx(6, j)] = sni[5][j] + sni[6][j]; /* Li */ + fb_props->tables.SN1E[SN1E_idx(7, j)] = sni[7][j]; /* Be */ + fb_props->tables.SN1E[SN1E_idx(8, j)] = sni[8][j] + sni[9][j]; /* B */ + fb_props->tables.SN1E[SN1E_idx(9, j)] = sni[10][j] + sni[11][j]; /* C */ + fb_props->tables.SN1E[SN1E_idx(10, j)] = sni[12][j] + sni[13][j]; /* N */ + fb_props->tables.SN1E[SN1E_idx(11, j)] = + sni[14][j] + sni[15][j] + sni[16][j]; /* O */ + fb_props->tables.SN1E[SN1E_idx(12, j)] = sni[17][j]; /* F */ + fb_props->tables.SN1E[SN1E_idx(13, j)] = + sni[18][j] + sni[19][j] + sni[20][j]; /* Ne */ + fb_props->tables.SN1E[SN1E_idx(14, j)] = sni[21][j]; /* Na */ + fb_props->tables.SN1E[SN1E_idx(15, j)] = + sni[22][j] + sni[23][j] + sni[24][j]; /* Mg */ + fb_props->tables.SN1E[SN1E_idx(16, j)] = sni[25][j]; /* Al */ + fb_props->tables.SN1E[SN1E_idx(17, j)] = + sni[26][j] + sni[27][j] + sni[28][j]; /* Si */ + fb_props->tables.SN1E[SN1E_idx(18, j)] = sni[29][j]; /* P */ + fb_props->tables.SN1E[SN1E_idx(19, j)] = + sni[30][j] + sni[31][j] + sni[32][j] + sni[33][j]; /* S */ + fb_props->tables.SN1E[SN1E_idx(20, j)] = sni[34][j] + sni[35][j]; /* Cl */ + fb_props->tables.SN1E[SN1E_idx(21, j)] = + sni[36][j] + sni[37][j] + sni[38][j]; /* Ar */ + fb_props->tables.SN1E[SN1E_idx(22, j)] = + sni[39][j] + sni[40][j] + sni[41][j]; /* K */ + fb_props->tables.SN1E[SN1E_idx(23, j)] = sni[42][j] + sni[43][j] + + sni[44][j] + sni[45][j] + + sni[46][j] + sni[47][j]; /* Ca */ + fb_props->tables.SN1E[SN1E_idx(24, j)] = sni[48][j]; /* Sc */ + fb_props->tables.SN1E[SN1E_idx(25, j)] = + sni[49][j] + sni[50][j] + sni[51][j] + sni[52][j] + sni[53][j]; /* Ti */ + fb_props->tables.SN1E[SN1E_idx(26, j)] = sni[54][j] + sni[55][j]; /* V */ + fb_props->tables.SN1E[SN1E_idx(27, j)] = + sni[56][j] + sni[57][j] + sni[58][j] + sni[59][j]; /* Cr */ + fb_props->tables.SN1E[SN1E_idx(28, j)] = sni[60][j]; /* Mn */ + fb_props->tables.SN1E[SN1E_idx(29, j)] = + sni[61][j] + sni[62][j] + sni[63][j] + sni[64][j]; /* Fe */ + fb_props->tables.SN1E[SN1E_idx(30, j)] = sni[65][j]; /* Co */ + fb_props->tables.SN1E[SN1E_idx(31, j)] = + sni[66][j] + sni[67][j] + sni[68][j] + sni[69][j] + sni[70][j]; /* Ni */ + fb_props->tables.SN1E[SN1E_idx(32, j)] = sni[71][j] + sni[72][j]; /* Cu */ + fb_props->tables.SN1E[SN1E_idx(33, j)] = + sni[73][j] + sni[74][j] + sni[75][j] + sni[76][j] + sni[77][j]; /* Zn */ + fb_props->tables.SN1E[SN1E_idx(34, j)] = sni[78][j] + sni[79][j]; /* Ga */ + fb_props->tables.SN1E[SN1E_idx(35, j)] = + sni[80][j] + sni[81][j] + sni[82][j] + sni[83][j]; /* Ge */ + fb_props->tables.SN1E[SN1E_idx(36, j)] = + fb_props->tables.SN1E[SN1E_idx(29, j)]; + for (k = 1; k < chem5_NXSN; k++) { + fb_props->tables.SN1E[SN1E_idx(k, j)] *= fb_props->solar_mass_to_mass; + } + } + + /* lifetime (Kobayashi et al. 2000) */ + sprintf(buf, "%s/LIFETIME.DAT", fb_props->tables_path); + if ((fp = fopen(buf, "r")) == NULL) { + fprintf(stderr, "Can not open File %s\n", buf); + exit(-1); + } + dummy = fgets(buf, 1000, fp); + + for (j = 0; j < NZLF; j++) { + fb_props->tables.LFLZ[j] = log10(lfz[j]); + dummy = fgets(buf, 1000, fp); + dummy = fgets(buf, 1000, fp); + dummy = fgets(buf, 1000, fp); + for (i = 0; i < NMLF; i++) { + dummy = fgets(buf, 1000, fp); + sscanf(buf, "%lf%lf\n", &a1, &a2); + fb_props->tables.LFLM[i] = log10(a1); /* sm */ + fb_props->tables.LFLT[LFLT_idx(j, i)] = log10(a2); /* yr */ + } + } + fclose(fp); + + if (engine_rank == 0) { + message("total: %.2f %.1f %.2e %.2e", fb_props->M_l, fb_props->M_u, + feedback_life_time(fb_props, fb_props->M_l, 0.02), + feedback_life_time(fb_props, fb_props->M_u, 0.02)); + message("SN2: %.2f %.1f %.2e %.2e x=%.2f", fb_props->M_l2, + fb_props->M_u2, feedback_life_time(fb_props, fb_props->M_l2, 0.02), + feedback_life_time(fb_props, fb_props->M_u2, 0.02), fb_props->ximf); + if (fb_props->zmax3 >= 0.0f) { + message( + "Pop3: %.2f %.1f %.2e %.2e x=%.2f\n", fb_props->M_l3, + fb_props->M_u3, feedback_life_time(fb_props, fb_props->M_l3, 0.02), + feedback_life_time(fb_props, fb_props->M_u3, 0.02), fb_props->ximf3); + } + } + + /* Set up IMF, normalized to 1 solar mass */ + if (fb_props->imf == 0) { /* Kroupa */ + if (fb_props->ximf == 1.) { + norm = log10(fb_props->M_u / 0.5) * 0.5 + + (pow(0.5, 0.7) - pow(0.08, 0.7)) / 0.7 + + (pow(0.08, 1.7) - pow(fb_props->M_l, 1.7)) / 1.7 / 0.08; + } else { + norm = (pow(fb_props->M_u, 1. - fb_props->ximf) - + pow(0.5, 1. - fb_props->ximf)) / + (1. - fb_props->ximf) * 0.5 + + (pow(0.5, 0.7) - pow(0.08, 0.7)) / 0.7f + + (pow(0.08, 1.7) - pow(fb_props->M_l, 1.7)) / 1.7 / 0.08; + } + + norm = 1. / norm; + } else { /* Chabrier, anything else */ + if (fb_props->ximf == 1.) { + norm = 1. / log(fb_props->M_u / fb_props->M_l); + } else { + norm = + (1. - fb_props->ximf) / (pow(fb_props->M_u, (1. - fb_props->ximf)) - + pow(fb_props->M_l, (1. - fb_props->ximf))); + } + } + + if (fb_props->ximf3 == 1.) { + norm3 = 1. / log(fb_props->M_u3 / fb_props->M_l3); + } else { + norm3 = + (1. - fb_props->ximf3) / (powf(fb_props->M_u3, (1. - fb_props->ximf3)) - + powf(fb_props->M_l3, (1. - fb_props->ximf3))); + } + + /* Set up IMF integration */ + dlm = (log10(fb_props->M_u3) - log10(fb_props->M_l)) / NM; + for (i = 0; i < NM; i++) { + fb_props->tables.SNLM[i] = log10(fb_props->M_u3) - dlm * i; + m[i] = pow(10., fb_props->tables.SNLM[i]); + + if (m[i] >= fb_props->M_l3) { + imf[0][i] = pow(m[i], -fb_props->ximf3) * norm3; + } else { + imf[0][i] = 0.; + } + + if (fb_props->imf == 1) { /* Chabrier */ + if (m[i] <= fb_props->M_u) { + imf[1][i] = IMF_FUDGE_FACTOR * feedback_imf(fb_props, m[i]); + } else { + imf[1][i] = 0.; + } + } else { /* Kroupa/else */ + if (m[i] <= fb_props->M_u) { + imf[1][i] = IMF_FUDGE_FACTOR * feedback_imf(fb_props, m[i]) * norm; + } else { + imf[1][i] = 0.; + } + } + + for (l = 2; l < NZSN; l++) { + imf[l][i] = imf[1][i]; + } + } + + /* Loop over all NM masses to set up IMF-integrated yield tables */ + for (i = 0; i < NM; i++) { + for (l = 0; l < NZSN1R; l++) { + SN1wd[l][i] = 0.; + SN1ms[l][i] = 0.; + SN1rg[l][i] = 0.; + fb_props->tables.SN1R[SN1R_idx(l, i)] = 0.; + } + + for (l = 0; l < NZSN; l++) { + fb_props->tables.SN2R[SN2R_idx(l, i)] = 0.; + fb_props->tables.SWR[SWR_idx(l, i)] = 0.; + for (k = 0; k < chem5_NXSN; k++) { + fb_props->tables.SN2E[SN2E_idx(k, l, i)] = 0.; + } + } + } + + for (i = 1; i < NM; i++) { + /* find indices for interp. from NMSN mass entries to NM mass entries */ + j1 = 0; + j2 = 1; + for (j = 1; j < NMSN; j++) { + j1 = j - 1; + j2 = j; + if (sniilm[j] < fb_props->tables.SNLM[i]) break; + } + + /* For this mass, loop over metallicity values */ + for (l = 0; l < NZSN; l++) { + if (l == 0) { + m_l = max(fb_props->M_l2, fb_props->M_l3); + } else { + m_l = fb_props->M_l2; + } + + if (m[i] > m_l) { + fb_props->tables.SWR[SWR_idx(l, i)] = + fb_props->tables.SWR[SWR_idx(l, (i - 1))] + + sqrt(imf[l][i] * imf[l][i - 1]) * dlm * log(10.); + } else { + fb_props->tables.SWR[SWR_idx(l, i)] = + fb_props->tables.SWR[SWR_idx(l, (i - 1))]; + } + + if (l == 0) { + m_l = fb_props->M_l3; + } else { + m_l = 0.; + } + + /* This is where we integrate up the IMF */ + if (m[i] > m_l) { /* H/He change from stars that go SN */ + for (k = 1; k < 3; k++) { + snii2_hi = + LINEAR_INTERPOLATION(sniilm[j1], snii[k][l][j1], sniilm[j2], + snii[k][l][j2], fb_props->tables.SNLM[i]); + snii2_lo = LINEAR_INTERPOLATION(sniilm[j1], snii[k][l][j1], + sniilm[j2], snii[k][l][j2], + fb_props->tables.SNLM[i - 1]); + if (snii2_hi < 0.) snii2_hi = 0.; + if (snii2_lo < 0.) snii2_lo = 0.; + fb_props->tables.SN2E[SN2E_idx(k, l, i)] = + fb_props->tables.SN2E[SN2E_idx(k, l, (i - 1))] + + (snii2_hi + snii2_lo) / 2. * + sqrt(m[i] * m[i - 1] * imf[l][i] * imf[l][i - 1]) * dlm * + log(10.); + } + } else { /* low mass stars */ + for (k = 1; k < 3; k++) { + fb_props->tables.SN2E[SN2E_idx(k, l, i)] = + fb_props->tables.SN2E[SN2E_idx(k, l, (i - 1))]; + } + } + + /* Metals from things that don't direct collapse to BH */ + if (m[i] > m_l && m[i] < fb_props->M_u2) { + for (k = 3; k < chem5_NXSN; k++) { + snii2_hi = + LINEAR_INTERPOLATION(sniilm[j1], snii[k][l][j1], sniilm[j2], + snii[k][l][j2], fb_props->tables.SNLM[i]); + snii2_lo = LINEAR_INTERPOLATION(sniilm[j1], snii[k][l][j1], + sniilm[j2], snii[k][l][j2], + fb_props->tables.SNLM[i - 1]); + if (snii2_hi < 0.) snii2_hi = 0.; + if (snii2_lo < 0.) snii2_lo = 0.; + fb_props->tables.SN2E[SN2E_idx(k, l, i)] = + fb_props->tables.SN2E[SN2E_idx(k, l, (i - 1))] + + (snii2_hi + snii2_lo) / 2. * + sqrt(m[i] * m[i - 1] * imf[l][i] * imf[l][i - 1]) * dlm * + log(10.); + } + } else { // low-mass stars, no metals from Type II + for (k = 3; k < chem5_NXSN; k++) { + fb_props->tables.SN2E[SN2E_idx(k, l, i)] = + fb_props->tables.SN2E[SN2E_idx(k, l, (i - 1))]; + } + } + + if (l == 0) { + m_l = max(fb_props->M_l2, fb_props->M_l3); + } else { + m_l = fb_props->M_l2; + } + + /* IMF integration for total metal mass yield */ + if (m[i] > m_l && m[i] < fb_props->M_u2) { + snii2_hi = + LINEAR_INTERPOLATION(sniilm[j1], snii[0][l][j1], sniilm[j2], + snii[0][l][j2], fb_props->tables.SNLM[i]); + snii2_lo = + LINEAR_INTERPOLATION(sniilm[j1], snii[0][l][j1], sniilm[j2], + snii[0][l][j2], fb_props->tables.SNLM[i - 1]); + if (snii2_hi < 0.) snii2_hi = 0.; + if (snii2_lo < 0.) snii2_lo = 0.; + fb_props->tables.SN2R[SN2R_idx(l, i)] = + fb_props->tables.SN2R[SN2R_idx(l, (i - 1))] + + sqrt(imf[l][i] * imf[l][i - 1]) * dlm * log(10.); + fb_props->tables.SN2E[SN2E_idx(0, l, i)] = + fb_props->tables.SN2E[SN2E_idx(0, l, (i - 1))] + + (snii2_hi + snii2_lo) / 2. * sqrt(imf[l][i] * imf[l][i - 1]) * dlm * + log(10.); + } else { + fb_props->tables.SN2R[SN2R_idx(l, i)] = + fb_props->tables.SN2R[SN2R_idx(l, (i - 1))]; + fb_props->tables.SN2E[SN2E_idx(0, l, i)] = + fb_props->tables.SN2E[SN2E_idx(0, l, (i - 1))]; + } + } + for (l = 1; l < NZSN1R; l++) { + if (m[i] > M_l1wd[l] && m[i] < M_u1wd[l]) { + SN1wd[l][i] = + SN1wd[l][i - 1] + sqrt(imf[l][i] * imf[l][i - 1]) * dlm * log(10.); + } else { + SN1wd[l][i] = SN1wd[l][i - 1]; + } + + if (m[i] > M_l1ms[l] && m[i] < M_u1ms[l]) { + SN1ms[l][i] = SN1ms[l][i - 1] + + pow(sqrt(m[i] * m[i - 1]), -0.35) * dlm * log(10.); + } else { + SN1ms[l][i] = SN1ms[l][i - 1]; + } + + if (m[i] > M_l1rg[l] && m[i] < M_u1rg[l]) { + SN1rg[l][i] = SN1rg[l][i - 1] + + pow(sqrt(m[i] * m[i - 1]), -0.35) * dlm * log(10.); + } else { + SN1rg[l][i] = SN1rg[l][i - 1]; + } + } + } + temp_ms = SN1ms[2][NM - 1]; /* normalized at Z=0.004 */ + temp_rg = SN1rg[2][NM - 1]; /* normalized at Z=0.004 */ + + /* Put everything the code units */ + for (i = 0; i < NM; i++) { + for (l = 1; l < NZSN1R; l++) { + SN1ms[l][i] *= fb_props->b_ms / temp_ms; + SN1rg[l][i] *= fb_props->b_rg / temp_rg; + fb_props->tables.SN1R[SN1R_idx(l, i)] = + SN1wd[l][i] * (SN1ms[l][i] + SN1rg[l][i]); + fb_props->tables.SN1R[SN1R_idx(l, i)] /= fb_props->solar_mass_to_mass; + } + + for (l = 1; l < NZSN1Y; l++) { + fb_props->tables.SN1E[SN1E_idx(0, l)] *= + (1.e51 / fb_props->energy_to_cgs); + } + + /* convert solar mass to code */ + fb_props->tables.SNLM[i] += log10(fb_props->solar_mass_to_mass); + + for (l = 0; l < NZSN; l++) { + fb_props->tables.SN2R[SN2R_idx(l, i)] /= fb_props->solar_mass_to_mass; + fb_props->tables.SWR[SWR_idx(l, i)] /= fb_props->solar_mass_to_mass; + fb_props->tables.SN2E[SN2E_idx(0, l, i)] *= + (1.e51 / fb_props->energy_to_cgs / fb_props->solar_mass_to_mass); + } + } + + if (engine_rank == 0) { + message("Done Chem5 setup."); + } +} + +/** + * @brief allocates space for the yield tables + * + * @param feedback_props the #feedback_props data struct to store the tables in + */ +INLINE static void feedback_allocate_feedback_tables( + struct feedback_props *feedback_props) { + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.LFLT, + SWIFT_STRUCT_ALIGNMENT, + NZLF * NMLF * sizeof(double)) != 0) { + error("Failed to allocate LFLT array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.LFLM, + SWIFT_STRUCT_ALIGNMENT, NMLF * sizeof(double)) != 0) { + error("Failed to allocate LFLM array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.LFLZ, + SWIFT_STRUCT_ALIGNMENT, NZLF * sizeof(double)) != 0) { + error("Failed to allocate LFLZ array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SWR, + SWIFT_STRUCT_ALIGNMENT, NZSN * NM * sizeof(double)) != 0) { + error("Failed to allocate SWR array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SN2E, + SWIFT_STRUCT_ALIGNMENT, + chem5_NXSN * NZSN * NM * sizeof(double)) != 0) { + error("Failed to allocate SN2E array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SN2R, + SWIFT_STRUCT_ALIGNMENT, NZSN * NM * sizeof(double)) != 0) { + error("Failed to allocate SN2R array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SN1R, + SWIFT_STRUCT_ALIGNMENT, + NZSN1R * NM * sizeof(double)) != 0) { + error("Failed to allocate SN1R array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SNLM, + SWIFT_STRUCT_ALIGNMENT, NM * sizeof(double)) != 0) { + error("Failed to allocate SNLM array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SNLZ, + SWIFT_STRUCT_ALIGNMENT, NZSN * sizeof(double)) != 0) { + error("Failed to allocate SNLZ array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SNLZ1R, + SWIFT_STRUCT_ALIGNMENT, NZSN1R * sizeof(double)) != 0) { + error("Failed to allocate SNLZ1R array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SN1E, + SWIFT_STRUCT_ALIGNMENT, + chem5_NXSN * NZSN1Y * sizeof(double)) != 0) { + error("Failed to allocate SN1E array"); + } + + if (swift_memalign("feedback-tables", (void **)&feedback_props->tables.SNLZ1Y, + SWIFT_STRUCT_ALIGNMENT, NZSN1Y * sizeof(double)) != 0) { + error("Failed to allocate SNLZ1Y array"); + } +} + +/** + * @brief Initialize the global properties of the feedback scheme. + * + * @param fp The #feedback_props. + * @param phys_const The physical constants in the internal unit system. + * @param us The internal unit system. + * @param params The parsed parameters. + * @param hydro_props The already read-in properties of the hydro scheme. + * @param cosmo The cosmological model. + */ +void feedback_props_init(struct feedback_props *fp, + const struct phys_const *phys_const, + const struct unit_system *us, + struct swift_params *params, + const struct hydro_props *hydro_props, + const struct cosmology *cosmo) { + + /* Common conversions ------------------------------------------------- */ + + /* Calculate internal mass to solar mass conversion factor */ + const double Msun_cgs = phys_const->const_solar_mass * + units_cgs_conversion_factor(us, UNIT_CONV_MASS); + const double unit_mass_cgs = units_cgs_conversion_factor(us, UNIT_CONV_MASS); + fp->mass_to_solar_mass = unit_mass_cgs / Msun_cgs; + fp->solar_mass_in_g = Msun_cgs; + fp->solar_mass_to_mass = 1. / fp->mass_to_solar_mass; + + /* Calculate temperature to internal energy conversion factor (all internal + * units) */ + const double k_B = phys_const->const_boltzmann_k; + const double m_p = phys_const->const_proton_mass; + const double mu = hydro_props->mu_ionised; + fp->temp_to_u_factor = k_B / (mu * hydro_gamma_minus_one * m_p); + + /* Calculate conversion factor from rho to n_H + * Note this assumes primoridal abundance */ + const double X_H = hydro_props->hydrogen_mass_fraction; + fp->rho_to_n_cgs = + (X_H / m_p) * units_cgs_conversion_factor(us, UNIT_CONV_NUMBER_DENSITY); + + fp->kms_to_internal = 1.e5 / units_cgs_conversion_factor(us, UNIT_CONV_SPEED); + + fp->kms_to_cms = 1.e5; + + fp->time_to_Myr = units_cgs_conversion_factor(us, UNIT_CONV_TIME) / + (1.e6 * 365.25 * 24. * 60. * 60.); + + /* Convert to Myr first, then multiply by a factor of 1e6 yr / 1 Myr */ + fp->time_to_yr = fp->time_to_Myr * 1.e6; + + fp->length_to_kpc = + units_cgs_conversion_factor(us, UNIT_CONV_LENGTH) / 3.08567758e21f; + + fp->energy_to_cgs = units_cgs_conversion_factor(us, UNIT_CONV_ENERGY); + + fp->T_to_internal = + 1. / units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + + /* Constant Chem5 parameters ---------------------------------------------- */ + + /* Solar values */ + fp->H_mf = 7.35224e-1; + fp->He_mf = 2.50274e-1; + fp->Z_mf = 0.0144404378; + fp->O_mf = 0.675327e-2 + 0.272594e-5 + 0.152311e-4; + fp->Fe_mf = 0.733849e-4 + 0.119465e-2 + 0.280824e-4 + 0.380282e-5; + + /* supernova energy in foe (D. Rennehan: What does foe mean??) */ + fp->E_sw = 0.2 * (1.e51 / fp->energy_to_cgs); + fp->E_sn1 = 1.3 * (1.e51 / fp->energy_to_cgs); + + fp->imf = parser_get_param_int(params, "KIARAFeedback:imf"); + + /* Kroupa IMF || Chabrier IMF */ + if (fp->imf == 0 || fp->imf == 1) { + fp->ximf = 1.3; + fp->M_u = 120.; + fp->M_l = 0.01; + } else { + fp->ximf = 1.35; + fp->M_u = 120.; + fp->M_l = 0.07; + } + + fp->ximf3 = 1.35; + fp->M_u3 = 120.; /* >= M_u */ + fp->M_l3 = 20.; /* >= M_l */ + fp->zmax3 = -999.; + fp->M_u2 = 50.; + fp->M_l2 = 8.; + fp->b_rg = 0.02; /* binary parameter for SNIa */ + fp->b_ms = 0.04; /* binary parameter for SNIa */ + + /* Chem5 indices to Swift */ + fp->element_index_conversions[chemistry_element_H] = chem5_element_H; + fp->element_index_conversions[chemistry_element_He] = chem5_element_He; + fp->element_index_conversions[chemistry_element_C] = chem5_element_C; + fp->element_index_conversions[chemistry_element_N] = chem5_element_N; + fp->element_index_conversions[chemistry_element_O] = chem5_element_O; + fp->element_index_conversions[chemistry_element_Ne] = chem5_element_Ne; + fp->element_index_conversions[chemistry_element_Mg] = chem5_element_Mg; + fp->element_index_conversions[chemistry_element_Si] = chem5_element_Si; + fp->element_index_conversions[chemistry_element_S] = chem5_element_S; + fp->element_index_conversions[chemistry_element_Ca] = chem5_element_Ca; + fp->element_index_conversions[chemistry_element_Fe] = chem5_element_Fe; + +#if COOLING_GRACKLE_MODE >= 2 + /* Production tables: AGB for C/O>1, AGB for C/O<1, and SNII (ignore SNIa) */ + fp->delta_AGBCOG1[chemistry_element_H] = 0.0; + fp->delta_AGBCOG1[chemistry_element_He] = 0.0; /* He could be removed */ + fp->delta_AGBCOG1[chemistry_element_C] = 0.2; + fp->delta_AGBCOG1[chemistry_element_N] = 0.0; /* N could be removed */ + fp->delta_AGBCOG1[chemistry_element_O] = 0.0; + fp->delta_AGBCOG1[chemistry_element_Ne] = 0.0; /* Ne could be removed */ + fp->delta_AGBCOG1[chemistry_element_Mg] = 0.0; + fp->delta_AGBCOG1[chemistry_element_Si] = 0.0; + fp->delta_AGBCOG1[chemistry_element_S] = 0.0; + fp->delta_AGBCOG1[chemistry_element_Ca] = 0.0; + fp->delta_AGBCOG1[chemistry_element_Fe] = 0.0; + + fp->delta_AGBCOL1[chemistry_element_H] = 0.0; + fp->delta_AGBCOL1[chemistry_element_He] = 0.0; + fp->delta_AGBCOL1[chemistry_element_C] = 0.0; + fp->delta_AGBCOL1[chemistry_element_N] = 0.0; + fp->delta_AGBCOL1[chemistry_element_O] = 0.2; + fp->delta_AGBCOL1[chemistry_element_Ne] = 0.0; + fp->delta_AGBCOL1[chemistry_element_Mg] = 0.2; + fp->delta_AGBCOL1[chemistry_element_Si] = 0.2; + fp->delta_AGBCOL1[chemistry_element_S] = 0.2; + fp->delta_AGBCOL1[chemistry_element_Ca] = 0.2; + fp->delta_AGBCOL1[chemistry_element_Fe] = 0.2; + + /* From Poping+17, default=2 in Simba's dust model */ + const float dust_boost_factor = parser_get_opt_param_float( + params, "KIARAFeedback:dust_boost_factor", 2.f); + + fp->delta_SNII[chemistry_element_H] = 0.00 * dust_boost_factor; + fp->delta_SNII[chemistry_element_He] = 0.00 * dust_boost_factor; + fp->delta_SNII[chemistry_element_C] = 0.15 * dust_boost_factor; + fp->delta_SNII[chemistry_element_N] = 0.00 * dust_boost_factor; + fp->delta_SNII[chemistry_element_O] = 0.15 * dust_boost_factor; + fp->delta_SNII[chemistry_element_Ne] = 0.00 * dust_boost_factor; + fp->delta_SNII[chemistry_element_Mg] = 0.15 * dust_boost_factor; + fp->delta_SNII[chemistry_element_Si] = 0.15 * dust_boost_factor; + fp->delta_SNII[chemistry_element_S] = 0.15 * dust_boost_factor; + fp->delta_SNII[chemistry_element_Ca] = 0.15 * dust_boost_factor; + fp->delta_SNII[chemistry_element_Fe] = 0.15 * dust_boost_factor; +#endif + + /* chem5 operation modes ------------------------------------------------- */ + + fp->with_HN_energy_from_chem5 = + parser_get_param_int(params, "KIARAFeedback:use_HN_energy_from_chem5"); + + fp->with_SNII_energy_from_chem5 = + parser_get_param_int(params, "KIARAFeedback:use_SNII_energy_from_chem5"); + + fp->with_SNIa_energy_from_chem5 = + parser_get_param_int(params, "KIARAFeedback:use_SNIa_energy_from_chem5"); + + fp->stellar_enrichment_frequency = parser_get_opt_param_float( + params, "KIARAFeedback:stellar_enrichment_frequency", 0.f); + + /* Properties of the enrichment down-sampling ----------------------------- */ + + fp->stellar_evolution_age_cut = + parser_get_param_double(params, + "KIARAFeedback:stellar_evolution_age_cut_Gyr") * + phys_const->const_year * 1e9; + + /* Stellar feedback cannot grow a particle bigger than this factor + * times the particles' current mass */ + fp->max_mass_increase_factor = parser_get_opt_param_float( + params, "KIARAFeedback:max_mass_increase_factor", 1.5f); + + /* Stellar feedback heating cannot increase a particle's internal + * energy more than this factor */ + fp->max_energy_increase_factor = parser_get_opt_param_float( + params, "KIARAFeedback:max_energy_increase_factor", 10.f); + + /* Momentum exchange lower limit from stellar feedback mass injection + fp->min_energy_decrease_factor = + parser_get_opt_param_float(params, + "KIARAFeedback:min_energy_decrease_factor", + 0.5f);*/ + + /* Option to use heat from SNIa to move gas off of the EoS */ + fp->SNIa_add_heat_to_ISM = + parser_get_opt_param_int(params, "KIARAFeedback:SNIa_add_heat_to_ISM", 0); + fp->SNIa_add_heat_to_ISM_tolerance = parser_get_opt_param_float( + params, "KIARAFeedback:SNIa_add_heat_to_ISM_tolerance", 1.e-6f); + + fp->stellar_evolution_sampling_rate = parser_get_param_double( + params, "KIARAFeedback:stellar_evolution_sampling_rate"); + + if (fp->stellar_evolution_sampling_rate < 1 || + fp->stellar_evolution_sampling_rate >= (1 << (8 * sizeof(char) - 1))) + error("Stellar evolution sampling rate too large. Must be >0 and <%d", + (1 << (8 * sizeof(char) - 1))); + + fp->metal_yield_multiplier = parser_get_opt_param_float( + params, "KIARAFeedback:metal_yield_multiplier", 1.f); + + /* Properties of Simba kinetic winds -------------------------------------- */ + + fp->FIRE_velocity_normalization = parser_get_param_double( + params, "KIARAFeedback:FIRE_velocity_normalization"); + fp->FIRE_velocity_slope = + parser_get_param_double(params, "KIARAFeedback:FIRE_velocity_slope"); + fp->FIRE_eta_normalization = + parser_get_param_double(params, "KIARAFeedback:FIRE_eta_normalization"); + fp->FIRE_eta_break = + parser_get_param_double(params, "KIARAFeedback:FIRE_eta_break_Msun"); + fp->FIRE_eta_break *= fp->solar_mass_to_mass; + fp->FIRE_eta_lower_slope = + parser_get_param_double(params, "KIARAFeedback:FIRE_eta_lower_slope"); + fp->FIRE_eta_upper_slope = + parser_get_param_double(params, "KIARAFeedback:FIRE_eta_upper_slope"); + + fp->wind_velocity_suppression_redshift = parser_get_opt_param_float( + params, "KIARAFeedback:wind_velocity_suppression_redshift", 0.f); + + fp->wind_eta_suppression_redshift = parser_get_opt_param_float( + params, "KIARAFeedback:wind_eta_suppression_redshift", 0.f); + + fp->SNII_energy_multiplier = parser_get_opt_param_float( + params, "KIARAFeedback:SNII_energy_multiplier", 1.f); + + fp->kick_radius_over_h = parser_get_opt_param_float( + params, "KIARAFeedback:kick_radius_over_h", 0.5f); + + fp->max_frac_of_kernel_to_launch = parser_get_opt_param_float( + params, "KIARAFeedback:max_frac_of_kernel_to_launch", 1.0f); + + fp->use_sfr_weighted_launch = parser_get_opt_param_int( + params, "KIARAFeedback:use_sfr_weighted_launch", 1); + + fp->metal_dependent_vwind = parser_get_opt_param_int( + params, "KIARAFeedback:metal_dependent_vwind", 0); + + fp->minimum_galaxy_stellar_mass = parser_get_param_double( + params, "KIARAFeedback:minimum_galaxy_stellar_mass_Msun"); + fp->minimum_galaxy_stellar_mass *= fp->solar_mass_to_mass; + + fp->galaxy_particle_resolution_count = parser_get_opt_param_int( + params, "KIARAFeedback:galaxy_particle_resolution_count", 0); + + fp->eta_suppression_factor_floor = parser_get_opt_param_float( + params, "KIARAFeedback:eta_suppression_factor_floor", 0.2f); + + fp->kick_direction_flag = parser_get_opt_param_double( + params, "KIARAFeedback:kick_direction_flag", 1); + + fp->kick_velocity_scatter = + parser_get_param_double(params, "KIARAFeedback:kick_velocity_scatter"); + + fp->wind_decouple_time_factor = parser_get_param_double( + params, "KIARAFeedback:wind_decouple_time_factor"); + fp->recouple_ism_density_nH_cgs = parser_get_opt_param_double( + params, "KIARAFeedback:recouple_ism_density_nH_cgs", 0.075); + fp->recouple_density_factor = parser_get_opt_param_double( + params, "KIARAFeedback:recouple_density_factor", 0.01); + + fp->cold_wind_internal_energy = parser_get_opt_param_double( + params, "KIARAFeedback:cold_wind_temperature_K", 1.e4); + if (fp->cold_wind_internal_energy <= 0.) { + error("KIARAFeedback:cold_wind_temperature_K must be strictly positive."); + } + fp->hot_wind_internal_energy = parser_get_opt_param_double( + params, "KIARAFeedback:hot_wind_temperature_K", 1.e4); + if (fp->hot_wind_internal_energy <= 0.) { + error("KIARAFeedback:hot_wind_temperature_K must be strictly positive."); + } + /* Check if we are using Firehose model, if so turn off hot winds */ + int firehose_on = parser_get_opt_param_int( + params, "KIARAChemistry:use_firehose_wind_model", 0); + if (firehose_on) { + if (engine_rank == 0) { + message( + "WARNING: Firehose model is on. Setting hot_wind_temperature_K to " + "cold_wind_temperature_K"); + } + + fp->use_firehose_model = 1; + fp->hot_wind_internal_energy = fp->cold_wind_internal_energy; + } else { + fp->use_firehose_model = 0; + } + + /* Early stellar feedback model of Keller et al 2022. */ + fp->early_stellar_feedback_alpha = parser_get_opt_param_float( + params, "KIARAFeedback:early_stellar_feedback_alpha", 0.); + /* Cloud-scale SF efficiency for early stellar feedback; default from Leroy+25 + */ + const float epssf = parser_get_opt_param_float( + params, "KIARAFeedback:early_stellar_feedback_epssf", 0.35); + fp->early_stellar_feedback_epsterm = (1. - epssf) / epssf; + /* Early stellar feedback timescale in Myr; store inverse for efficiency */ + fp->early_stellar_feedback_tfb = parser_get_opt_param_float( + params, "KIARAFeedback:early_stellar_feedback_tfb", 3.31); + fp->early_stellar_feedback_tfb /= fp->time_to_Myr; + fp->early_stellar_feedback_tfb_inv = 1.f / fp->early_stellar_feedback_tfb; + +#if COOLING_GRACKLE_MODE >= 2 + fp->max_dust_fraction = parser_get_opt_param_double( + params, "KIARAFeedback:max_dust_fraction", 0.9); + fp->SNe_smoothing_time_in_Myr = parser_get_opt_param_double( + params, "KIARAFeedback:SNe_smoothing_time_in_Myr", 0.); +#endif + + /* Convert Kelvin to internal energy and internal units */ + fp->cold_wind_internal_energy *= + fp->temp_to_u_factor / + units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + fp->hot_wind_internal_energy = + fp->temp_to_u_factor / + units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + + /* Read yield table filepath */ + parser_get_param_string(params, "KIARAFeedback:tables_path", fp->tables_path); + + /* Allocate the memory for all of the feedback tables --------------------- */ + feedback_allocate_feedback_tables(fp); + + /* Initialise the yield/mass tables --------------------------------------- */ + feedback_prepare_interpolation_tables(fp); + + /* Output some information to the people ---------------------------------- */ + + if (engine_rank == 0) { + message("Feedback model is KIARA"); + message("Feedback FIRE velocity normalization: %g", + fp->FIRE_velocity_normalization); + message("Feedback FIRE velocity slope: %g", fp->FIRE_velocity_slope); + message("Feedback velocity scatter: %g", fp->kick_velocity_scatter); + message("Feedback FIRE eta normalization: %g", fp->FIRE_eta_normalization); + message("Feedback FIRE eta break: %g", fp->FIRE_eta_break); + message("Feedback FIRE eta upper slope: %g", fp->FIRE_eta_upper_slope); + message("Feedback FIRE eta lower slope: %g", fp->FIRE_eta_lower_slope); + + if (fabs(fp->wind_velocity_suppression_redshift) != 0.f) { + message( + "Feedback wind speed early suppression enabled " + "above redshift: %g", + fp->wind_velocity_suppression_redshift); + } + + message("Feedback use Chem5 SNII energy: %d", + fp->with_SNII_energy_from_chem5); + message("Feedback use Chem5 SNIa energy: %d", + fp->with_SNIa_energy_from_chem5); + } +} + +/** + * @brief Zero pointers in feedback_table structs + * + * @param table feedback_tables struct in which pointers to tables + * set to NULL + */ +void feedback_zero_table_pointers(struct feedback_tables *table) { + + table->LFLT = NULL; + table->LFLM = NULL; + table->LFLZ = NULL; + table->SWR = NULL; + table->SN2E = NULL; + table->SN2R = NULL; + table->SN1R = NULL; + table->SNLM = NULL; + table->SNLZ = NULL; + table->SNLZ1R = NULL; + table->SN1E = NULL; + table->SNLZ1Y = NULL; +} + +/** + * @brief Restore feedback tables (if applicable) after + * restart + * + * @param fp the #feedback_props structure + */ +void feedback_restore_tables(struct feedback_props *fp) { + + /* Allocate the memory for all of the feedback tables --------------------- */ + feedback_allocate_feedback_tables(fp); + + /* Initialise the yield/mass tables --------------------------------------- */ + feedback_prepare_interpolation_tables(fp); +} + +/** + * @brief Clean-up the memory allocated for the feedback routines + * + * We simply free all the arrays. + * + * @param fp the feedback data structure. + */ +void feedback_clean(struct feedback_props *fp) {} + +/** + * @brief Write a feedback struct to the given FILE as a stream of bytes. + * + * @param feedback the struct + * @param stream the file stream + */ +void feedback_struct_dump(const struct feedback_props *feedback, FILE *stream) { + /* To make sure everything is restored correctly, we zero all the pointers to + tables. If they are not restored correctly, we would crash after restart on + the first call to the feedback routines. Helps debugging. */ + struct feedback_props feedback_copy = *feedback; + + feedback_zero_table_pointers(&feedback_copy.tables); + + restart_write_blocks((void *)&feedback_copy, sizeof(struct feedback_props), 1, + stream, "feedback", "feedback function"); +} + +/** + * @brief Restore a hydro_props struct from the given FILE as a stream of + * bytes. + * + * Read the structure from the stream and restore the feedback tables by + * re-reading them. + * + * @param feedback the struct + * @param stream the file stream + */ +void feedback_struct_restore(struct feedback_props *feedback, FILE *stream) { + restart_read_blocks((void *)feedback, sizeof(struct feedback_props), 1, + stream, NULL, "feedback function"); + + if (strlen(feedback->tables_path) != 0) feedback_restore_tables(feedback); +} diff --git a/src/feedback/KIARA/feedback.h b/src/feedback/KIARA/feedback.h new file mode 100644 index 0000000000..f3c809d3d1 --- /dev/null +++ b/src/feedback/KIARA/feedback.h @@ -0,0 +1,875 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_FEEDBACK_KIARA_H +#define SWIFT_FEEDBACK_KIARA_H + +#include "cosmology.h" +#include "engine.h" +#include "error.h" +#include "feedback_properties.h" +#include "hydro_properties.h" +#include "part.h" +#include "star_formation.h" +#include "timers.h" +#include "timestep_sync.h" +#include "timestep_sync_part.h" +#include "units.h" + +#include + +double feedback_get_lum_from_star_particle( + const struct spart *sp, double age, const struct feedback_props *fb_props); +void feedback_get_ejecta_from_star_particle( + const struct spart *sp, double age, const struct feedback_props *fb_props, + double dt, double *N_SNe, double *ejecta_energy, double *ejecta_mass, + double *ejecta_unprocessed, double ejecta_metal_mass[chem5_element_count]); +void feedback_dust_production_condensation( + struct spart *sp, double star_age, const struct feedback_props *fb_props, + double delta_metal_mass[chemistry_element_count]); +double feedback_life_time(const struct feedback_props *fb_props, const double m, + const double z); +double feedback_imf(const struct feedback_props *fb_props, const double m); +void feedback_set_turnover_mass(const struct feedback_props *fb_props, + const double z, double *LFLT2); +double feedback_get_turnover_mass(const struct feedback_props *fb_props, + const double t, const double z); +void feedback_prepare_interpolation_tables( + const struct feedback_props *fb_props); + +/** + * @brief Recouple wind particles. + * + * @param p The #part to consider. + * @param xp The #xpart to consider. + * @param e The #engine. + * @param with_cosmology Is this a cosmological simulation? + */ +__attribute__((always_inline)) INLINE static void feedback_recouple_set_flags( + struct part *p, const struct cosmology *cosmo) { + + p->feedback_data.decoupling_delay_time = 0.f; + p->decoupled = 0; + p->chemistry_data.radius_stream = 0.f; + + /* Reset subgrid properties */ + p->cooling_data.subgrid_temp = 0.f; + p->cooling_data.subgrid_dens = hydro_get_physical_density(p, cosmo); + p->cooling_data.subgrid_fcold = 0.f; + + /* Make sure to sync the newly coupled part on the timeline */ + timestep_sync_part(p); +} + +/** + * @brief Recouple wind particles. + * + * @param p The #part to consider. + * @param xp The #xpart to consider. + * @param e The #engine. + * @param with_cosmology Is this a cosmological simulation? + * @param cosmo The cosmology of the simulation. + * @param fb_props The #feedback_props feedback parameters. + */ +__attribute__((always_inline)) INLINE static void feedback_recouple_part( + struct part *p, struct xpart *xp, const struct engine *e, + const int with_cosmology, const struct cosmology *cosmo, + const struct feedback_props *fb_props) { + + if (p->decoupled) { + const integertime_t ti_step = get_integer_timestep(p->time_bin); + const integertime_t ti_begin = + get_integer_time_begin(e->ti_current - 1, p->time_bin); + + /* Get particle time-step */ + double dt_part; + if (with_cosmology) { + dt_part = + cosmology_get_delta_time(e->cosmology, ti_begin, ti_begin + ti_step); + } else { + dt_part = get_timestep(p->time_bin, e->time_base); + } + + /* Decrement the counter */ + p->feedback_data.decoupling_delay_time -= dt_part; + + /* Estimate if it will decouple in the next step, if so set flag=2 */ + if (p->feedback_data.decoupling_delay_time <= dt_part) { + p->decoupled = 2; + } + + /** + * Recouple under 3 conditions: + * (1) Below the density threshold. + * (2) If the stream radius is negative. + * (3) If the timer has run out. + */ + const double rho_nH_cgs = + hydro_get_physical_density(p, cosmo) * fb_props->rho_to_n_cgs; + const double rho_recouple_cgs = fb_props->recouple_density_factor * + fb_props->recouple_ism_density_nH_cgs; + + const int recouple = (p->feedback_data.decoupling_delay_time <= 0.f || + p->chemistry_data.radius_stream < 0.f || + rho_nH_cgs < rho_recouple_cgs); + + if (recouple && p->decoupled == 1) { + /* If it is recoupling, do one more decoupled step to set timestep etc */ + p->decoupled = 2; + } else if (recouple) { + /* extra decoupled step is done, now properly recouple */ + feedback_recouple_set_flags(p, cosmo); + } else { + /* Reset subgrid properties if decoupled for safety */ + p->cooling_data.subgrid_temp = 0.f; + p->cooling_data.subgrid_dens = hydro_get_physical_density(p, cosmo); + p->cooling_data.subgrid_fcold = 0.f; + } + } +} + +/** + * @brief Sets the wind direction vector for feedback kicks + * + * @param p The #part to consider. + * @param xp The #xpart to consider. + * @param e The #engine. + * @param with_cosmology Is this a cosmological simulation? + * @param cosmo The cosmology of the simulation. + * @param fb_props The #feedback_props feedback parameters. + */ +__attribute__((always_inline)) INLINE static void feedback_set_wind_direction( + struct part *p, struct xpart *xp, const struct engine *e, + const int with_cosmology, const struct cosmology *cosmo, + const struct feedback_props *fb_props) { + + p->feedback_data.wind_direction[0] = + p->gpart->a_grav[1] * p->gpart->v_full[2] - + p->gpart->a_grav[2] * p->gpart->v_full[1]; + p->feedback_data.wind_direction[1] = + p->gpart->a_grav[2] * p->gpart->v_full[0] - + p->gpart->a_grav[0] * p->gpart->v_full[2]; + p->feedback_data.wind_direction[2] = + p->gpart->a_grav[0] * p->gpart->v_full[1] - + p->gpart->a_grav[1] * p->gpart->v_full[0]; +} + +/** + * @brief Update the properties of a particle due to feedback effects after + * the cooling was applied. + * + * Nothing to do here in the KIARA model. + * + * @param p The #part to consider. + * @param xp The #xpart to consider. + * @param e The #engine. + * @param with_cosmology Is this a cosmological simulation? + */ +__attribute__((always_inline)) INLINE static void feedback_update_part( + struct part *p, struct xpart *xp, const struct engine *e) {} + +/** + * @brief Reset the gas particle-carried fields related to feedback at the + * start of a step. + * + * @param p The particle. + * @param xp The extended data of the particle. + */ +__attribute__((always_inline)) INLINE static void feedback_reset_part( + struct part *p, struct xpart *xp) {} + +/** + * @brief Should this particle be doing any feedback-related operation? + * + * @param sp The #spart. + * @param e The #engine. + */ +__attribute__((always_inline)) INLINE static int feedback_is_active( + const struct spart *sp, const struct engine *e) { + + // message("FEEDBACK_IS_ACTIVE %d %g %d yes? %d", e->step, sp->birth_time, + // sp->count_since_last_enrichment, e->step <= 0 || + // ((sp->birth_time != -1.) && (sp->count_since_last_enrichment == + // 0))); + return e->step <= 0 || + ((sp->birth_time != -1.) && (sp->count_since_last_enrichment == 0)); +} + +/** + * @brief Should this particle be doing any DM looping? + * + * @param sp The #spart. + * @param e The #engine. + */ +__attribute__((always_inline)) INLINE static int stars_dm_loop_is_active( + const struct spart *sp, const struct engine *e) { + /* Active stars always do the DM loop for the KIARA model */ + return 0; +} + +/** + * @brief Prepares a s-particle for its feedback interactions + * + * @param sp The particle to act upon + */ +__attribute__((always_inline)) INLINE static void feedback_init_spart( + struct spart *sp) { + + /* Default to not suppression the mass loading in the winds */ + sp->feedback_data.eta_suppression_factor = 1.f; + sp->feedback_data.kernel_wt_sum = 0.f; + sp->feedback_data.wind_wt_sum = 0.f; + sp->feedback_data.ngb_mass = 0.f; + sp->feedback_data.wind_ngb_mass = 0.f; + + /* Check reservoirs each time-step for out-of-bounds values */ + if (sp->feedback_data.mass_to_launch < 0.f) { + sp->feedback_data.mass_to_launch = 0.f; + } + + if (sp->feedback_data.physical_energy_reservoir < 0.) { + sp->feedback_data.physical_energy_reservoir = 0.; + } + +#ifdef SWIFT_STARS_DENSITY_CHECKS + sp->has_done_feedback = 0; +#endif +} + +/** + * @brief Returns the length of time since the particle last did + * enrichment/feedback. + * + * @param sp The #spart. + * @param with_cosmology Are we running with cosmological time integration on? + * @param cosmo The cosmological model. + * @param time The current time (since the Big Bang / start of the run) in + * internal units. + * @param dt_star the length of this particle's time-step in internal units. + * @return The length of the enrichment step in internal units. + */ +INLINE static double feedback_get_enrichment_timestep( + const struct spart *sp, const int with_cosmology, + const struct cosmology *cosmo, const double time, const double dt_star) { + + if (with_cosmology) { + return cosmology_get_delta_time_from_scale_factors( + cosmo, (double)sp->last_enrichment_time, cosmo->a); + } else { + return time - (double)sp->last_enrichment_time; + } +} + +/** + * @brief Prepares a star's feedback field before computing what + * needs to be distributed. + */ +__attribute__((always_inline)) INLINE static void feedback_reset_feedback( + struct spart *sp, const struct feedback_props *feedback_props) { + + /* Zero the amount of mass that is distributed */ + sp->feedback_data.mass = 0.; + + /* Zero the metal enrichment quantities */ + for (int i = 0; i < chemistry_element_count; i++) { + sp->feedback_data.metal_mass[i] = 0.; +#if COOLING_GRACKLE_MODE >= 2 + sp->feedback_data.delta_dust_mass[i] = 0.; +#endif + } + sp->feedback_data.total_metal_mass = 0.; + + /* Zero the energy to inject */ + sp->feedback_data.energy = 0.; +} + +/** + * @brief Initialises the s-particles feedback props for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions. + * + * @param sp The particle to act upon. + * @param feedback_props The properties of the feedback model. + */ +__attribute__((always_inline)) INLINE static void feedback_first_init_spart( + struct spart *sp, const struct feedback_props *feedback_props) { + + feedback_init_spart(sp); + sp->feedback_data.SNe_ThisTimeStep = 0.; + sp->feedback_data.SNe_Total = 0.; + sp->feedback_data.firehose_radius_stream = 0.f; + sp->feedback_data.mass_to_launch = 0.f; + sp->feedback_data.total_mass_kicked = 0.f; + sp->feedback_data.wind_velocity = 0.f; + sp->feedback_data.physical_energy_reservoir = 0.; + sp->feedback_data.N_launched = 0; + sp->feedback_data.eta_suppression_factor = 1.f; +} + +/** + * @brief Initialises the particles for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions or assignments between the particle + * and extended particle fields. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + */ +__attribute__((always_inline)) INLINE static void feedback_first_init_part( + struct part *restrict p, struct xpart *restrict xp) { + + p->feedback_data.decoupling_delay_time = 0.f; + p->feedback_data.number_of_times_decoupled = 0; + p->feedback_data.cooling_shutoff_delay_time = 0.f; + p->feedback_data.kick_id = -1; + p->feedback_data.mass_limiter_count = 0; + p->feedback_data.heating_limiter_count = 0; + for (int i = 0; i < 3; i++) p->feedback_data.wind_direction[i] = 0.f; +} + +/** + * @brief Initialises the s-particles feedback props for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions. + * + * @param sp The particle to act upon. + * @param feedback_props The properties of the feedback model. + */ +__attribute__((always_inline)) INLINE static void feedback_prepare_spart( + struct spart *sp, const struct feedback_props *feedback_props) {} + +/** + * @brief Compute kick velocity for particle sp based on host galaxy properties, + * in code units + * + * @param sp The #spart to consider + * @param cosmo The cosmological model. + * @param fb_props Properties of the feedback scheme. + * @param ti_current Current integer time used value for seeding random number + */ +__attribute__((always_inline)) INLINE static double +feedback_compute_kick_velocity(const float galaxy_stellar_mass, + const size_t sp_id, + const struct cosmology *cosmo, + const struct feedback_props *fb_props, + const integertime_t ti_current) { + + /* Compute galaxy mass. This is done in the RUNNER files. */ + float galaxy_stellar_mass_Msun = + galaxy_stellar_mass; // in code units for now + if (galaxy_stellar_mass_Msun < fb_props->minimum_galaxy_stellar_mass) { + galaxy_stellar_mass_Msun = fb_props->minimum_galaxy_stellar_mass; + } + galaxy_stellar_mass_Msun *= fb_props->mass_to_solar_mass; // convert to Msun + + /* Physical circular velocity km/s from z=0-2 DEEP2 + measurements by Dutton+11 */ + + /* Dutton+11 eq 6: log (M* / 1e10) = -0.61 + 4.51 log (vdisk / 100) */ + const float v_circ_km_s = + 100.f * powf(4.0738f * galaxy_stellar_mass_Msun * 1.e-10f, 0.221729f) * + pow(cosmo->H / cosmo->H0, 1.f / 3.f); + + const float rand_for_scatter = + random_unit_interval(sp_id, ti_current, random_number_stellar_feedback_2); + + /* The wind velocity in internal units and COMOVING from FIRE scalings */ + float wind_velocity = + fb_props->FIRE_velocity_normalization * + powf(v_circ_km_s / 200.f, fb_props->FIRE_velocity_slope) * + (1.f - fb_props->kick_velocity_scatter + + 2.f * fb_props->kick_velocity_scatter * rand_for_scatter) * + v_circ_km_s * fb_props->kms_to_internal * + /* Note that xpj->v_full = a^2 * dx/dt, with x the comoving coordinate. + * Thus a physical kick, dv, gets translated into a code velocity kick, + * a * dv */ + cosmo->a; + + const float a_suppress_inv = + (1.f + fabs(fb_props->wind_velocity_suppression_redshift)); + if (fb_props->wind_velocity_suppression_redshift > 0 && + cosmo->z > fb_props->wind_velocity_suppression_redshift) { + wind_velocity *= cosmo->a * cosmo->a * a_suppress_inv * a_suppress_inv; + } else if (fb_props->wind_velocity_suppression_redshift < 0) { + wind_velocity *= expf(-powf(cosmo->a * a_suppress_inv, -3.f)); + } + + /* internal, COMOVING units */ + return wind_velocity; +} + +/** + * @brief Prepare a #spart for the feedback task. + * + * In KIARA, this function does the stellar evolution for a #spart. + * + * @param sp The particle to act upon + * @param feedback_props The #feedback_props structure. + * @param cosmo The current cosmological model. + * @param us The unit system. + * @param phys_const The physical constants in internal units. + * @param star_age_beg_step The age of the star at the star of the time-step in + * internal units. + * @param dt The time-step size of this star in internal units. + * @param time The physical time in internal units. + * @param ti_begin The integer time at the beginning of the step. + * @param with_cosmology Are we running with cosmology on? + */ +__attribute__((always_inline)) INLINE static void feedback_prepare_feedback( + struct spart *restrict sp, const struct feedback_props *feedback_props, + const struct cosmology *cosmo, const struct unit_system *us, + const struct phys_const *phys_const, const double star_age_beg_step, + const double dt, const double time, const integertime_t ti_begin, + const int with_cosmology) { + + if (sp->feedback_data.ngb_mass <= 0.f) { + warning("Star %lld has zero neighbor gas density.", sp->id); + return; + } + +#ifdef SWIFT_DEBUG_CHECKS + if (sp->birth_time == -1.) error("Evolving a star particle that should not!"); +#endif + + TIMER_TIC; + +#if COOLING_GRACKLE_MODE >= 2 + /* Compute Habing luminosity of star for use in ISRF + (only with Grackle subgrid ISM model) */ + /*sp->feedback_data.lum_habing = + feedback_get_lum_from_star_particle(sp, star_age_beg_step, + feedback_props); message("G0: age %g Lhabing %g\n", star_age_beg_step * + feedback_props->time_to_Myr, sp->feedback_data.lum_habing); + */ +#endif + + /* Zero out mass and energy return for this step */ + sp->feedback_data.mass = 0.; + sp->feedback_data.energy = 0.; + + /* Do chem5 chemical evolution model */ + int elem; + double N_SNe = 0.; + double ejecta_energy = 0.; + double ejecta_mass = 0.; + double ejecta_unprocessed = 0.; + double ejecta_metal_mass[chem5_element_count]; + for (elem = 0; elem < chem5_element_count; elem++) { + ejecta_metal_mass[elem] = 0.; + } + + feedback_get_ejecta_from_star_particle( + sp, star_age_beg_step, feedback_props, dt, &N_SNe, &ejecta_energy, + &ejecta_mass, &ejecta_unprocessed, ejecta_metal_mass); + + ejecta_mass *= 0.5f; // fudge factor + + if (isnan(ejecta_mass)) { + for (elem = 0; elem < chem5_element_count; elem++) { + message("ejecta_metal_mass[%d]=%g", elem, ejecta_metal_mass[elem]); + } + + message("[Fe/H] = %g", + sp->chemistry_data.metal_mass_fraction[chemistry_element_Fe] / + sp->chemistry_data.metal_mass_fraction[chemistry_element_H]); + message("Z = %g", sp->chemistry_data.metal_mass_fraction_total); + + error( + "Star particle %lld with mass %g (init_mass %g) is trying to give " + "away NaN mass (Mejecta=%g, Energy=%g, Unprocessed=%g)!", + sp->id, sp->mass, sp->mass_init, ejecta_mass, ejecta_energy, + ejecta_unprocessed); + } + + if (ejecta_energy < 0.f) { + warning( + "Star particle %lld with mass %g (init_mass %g) is trying to give " + "away negative energy (Mejecta=%g, Energy=%g, Unprocessed=%g)!", + sp->id, sp->mass, sp->mass_init, ejecta_mass, ejecta_energy, + ejecta_unprocessed); + feedback_reset_feedback(sp, feedback_props); + return; + } + + if (sp->mass - ejecta_mass < 0.2 * sp->mass_init) { + warning( + "Star particle %lld with mass %g is trying to lower its mass " + "past 0.2 of initial (Mejecta=%g)!", + sp->id, sp->mass, ejecta_mass); + feedback_reset_feedback(sp, feedback_props); + return; + } + + /* Collect information about galaxy that the particle belongs to */ + const float M_star = sp->galaxy_data.stellar_mass; + const float M_star_min = feedback_props->minimum_galaxy_stellar_mass; + const float FIRE_eta_norm = feedback_props->FIRE_eta_normalization; + const float FIRE_eta_break = feedback_props->FIRE_eta_break; + const float FIRE_eta_lower_slope = feedback_props->FIRE_eta_lower_slope; + const float FIRE_eta_upper_slope = feedback_props->FIRE_eta_upper_slope; + const float FIRE_eta_lower_slope_EOR = + feedback_props->FIRE_eta_lower_slope; + const float wind_velocity_suppression_redshift = + feedback_props->wind_velocity_suppression_redshift; + + float eta = feedback_mass_loading_factor( + cosmo, M_star, M_star_min, FIRE_eta_norm, FIRE_eta_break, + FIRE_eta_lower_slope, FIRE_eta_upper_slope, FIRE_eta_lower_slope_EOR, + wind_velocity_suppression_redshift); + + /* velocity in internal units which is a^2*comoving, or a*physical */ + float v_internal = feedback_compute_kick_velocity(M_star, sp->id, cosmo, + feedback_props, ti_begin); + + /* Early (non-SN) stellar feedback energy from Keller+22 eq. 10 */ + const float alpha = feedback_props->early_stellar_feedback_alpha; + const float alpha_power = 4.f * alpha - 1.f; + const float tfb_inv = feedback_props->early_stellar_feedback_tfb_inv; + if (alpha_power > 0.f && + star_age_beg_step < feedback_props->early_stellar_feedback_tfb) { + + const float eps_term = feedback_props->early_stellar_feedback_epsterm; + const float h_phys = kernel_gamma * sp->h * cosmo->a; + const float v_phys = v_internal * cosmo->a_inv; + + /* p0 is momentum per unit mass in km/s from early feedback sources */ + const float p0 = h_phys * eps_term * M_PI * tfb_inv; + const float t_prev = fmax(star_age_beg_step - dt, 0.f); + const float term1 = pow(star_age_beg_step * tfb_inv, alpha_power); + const float term2 = pow(t_prev * tfb_inv, alpha_power); + const double delta_p = alpha * p0 * sp->mass * (term1 - term2); + sp->feedback_data.physical_energy_reservoir += 0.5 * delta_p * v_phys; + +#ifdef KIARA_DEBUG_CHECKS + message( + "ESF: id=%lld age=%g dt=%g Myr, Etot=%g E_ESF=%g f_inc=%g", sp->id, + t_prev * feedback_props->time_to_Myr, dt * feedback_props->time_to_Myr, + sp->feedback_data.physical_energy_reservoir, 0.5f * delta_p * v_phys, + 0.5f * delta_p * v_phys / sp->feedback_data.physical_energy_reservoir); +#endif + } + + /** + * Compute the mass loading and energy reservoirs for the stellar feedback. + * Mass loading will be limited by the physical energy available from chem5 + * directly at each step. Later, when computing the probability to kick + * a particle, the mass_to_launch will be limited by eta_suppression_factor. + */ + const float wind_mass = eta * sp->mass_init; + const float total_mass_kicked = sp->feedback_data.total_mass_kicked; + + if (total_mass_kicked < wind_mass) { + + /* Boost wind speed based on metallicity which governs + * photon energy output */ + float Z_fac = 1.f; + const int vwind_boost_flag = feedback_props->metal_dependent_vwind; + if (vwind_boost_flag != kiara_metal_boosting_off) { + Z_fac = 2.61634f; + const float Z_met = sp->chemistry_data.metal_mass_fraction_total; + if (Z_met > 1.e-9f) { + Z_fac = + powf(10.f, -0.0029f * powf(log10f(Z_met) + 9.f, 2.5f) + 0.417694f); + } + + Z_fac = max(Z_fac, 1.f); + } + + switch (vwind_boost_flag) { + case kiara_metal_boosting_vwind: + v_internal *= sqrtf(Z_fac); + break; + case kiara_metal_boosting_eta: + eta *= Z_fac; + break; + case kiara_metal_boosting_both: + v_internal *= sqrtf(Z_fac); + eta *= Z_fac; + break; + } + + /* ------ SNII Energy and Wind Launch Setup ------ */ + + /* Total SNII energy this timestep (physical units) */ + const double E_SNII_phys = 1.e51 * N_SNe / feedback_props->energy_to_cgs; + + /* Apply energy multiplier and metallicity scaling */ + const float energy_boost = feedback_props->SNII_energy_multiplier * Z_fac; + + /* Add to physical energy reservoir */ + sp->feedback_data.physical_energy_reservoir += E_SNII_phys * energy_boost; + + /* Store updated wind velocity */ + sp->feedback_data.wind_velocity = v_internal; + + /* ------ Compute allowable wind mass this step ------ */ + + /* Wind energy per unit mass in physical units */ + const double specific_energy_phys = + 0.5 * v_internal * v_internal * cosmo->a2_inv; + + /* Max wind mass supportable by current energy */ + const double wind_mass_max = + sp->feedback_data.physical_energy_reservoir / specific_energy_phys; + + /* Remaining mass left to launch */ + const double wind_mass_left = max(wind_mass - total_mass_kicked, 0.); + + /* Launch only what is both allowed by energy and remaining */ + const double mass_to_launch = min(wind_mass_max, wind_mass_left); + + sp->feedback_data.mass_to_launch = mass_to_launch; + +#ifdef KIARA_DEBUG_CHECKS + message( + "ETA: z=%g id=%lld age=%g Eres=%g dE=%g NSNe=%g NSNtot=%g eta=%g " + "max=%g tot=%g mlaunch=%g Ntot=%d", + cosmo->z, sp->id, star_age_beg_step * feedback_props->time_to_Myr, + sp->feedback_data.physical_energy_reservoir * + feedback_props->energy_to_cgs, + 1.e51 * N_SNe * scaling, N_SNe, + sp->mass_init * feedback_props->mass_to_solar_mass / 80.f, + /* 1 SNII for ~80 Mo for Kroupa/Chabrier IMF */ + mass_to_launch / sp->mass_init, eta_max_this_timestep, eta, + sp->feedback_data.mass_to_launch, sp->feedback_data.N_launched); +#endif + + /* Set stream radius for firehose particles kicked by this star */ + + /* This is the physical initial density */ + const double stream_init_density = 0.1; /* n_H units CGS */ + const double rho_volumefilling_phys = + stream_init_density / feedback_props->rho_to_n_cgs; + float galaxy_stellar_mass_Msun = M_star; + const float min_gal_mass = feedback_props->minimum_galaxy_stellar_mass; + if (galaxy_stellar_mass_Msun < min_gal_mass) { + galaxy_stellar_mass_Msun = min_gal_mass; + } + galaxy_stellar_mass_Msun *= feedback_props->mass_to_solar_mass; + + /* stream size = 2 * comoving effective size of disk galaxies + * (Ward+2024 CEERS) */ + const float redge_obs = 2.f * 7.1f * pow(cosmo->a, 0.63f) * + pow(galaxy_stellar_mass_Msun / 5.e10, 0.16f); + + /* Convert to internal units */ + sp->feedback_data.firehose_radius_stream = + cosmo->a_inv * redge_obs / feedback_props->length_to_kpc; + + if (sp->galaxy_data.stellar_mass > 0.f && + sp->galaxy_data.specific_sfr > 0.f && eta > 0.f && + sp->feedback_data.wind_velocity != 0.f) { + + const float v_phys = fabs(sp->feedback_data.wind_velocity) * cosmo->a_inv; + const float m_dot_wind_sfr = + eta * sp->galaxy_data.specific_sfr * sp->galaxy_data.stellar_mass; + const float specific_m_dot_wind_vel = + M_PI * rho_volumefilling_phys * v_phys; + + /* Put into comoving units */ + const float redge_est = + sqrtf(m_dot_wind_sfr / specific_m_dot_wind_vel) * cosmo->a_inv; + + sp->feedback_data.firehose_radius_stream = fmin(redge_est, redge_obs); + } + + /* Stream cannot be smaller than the smoothing length */ + sp->feedback_data.firehose_radius_stream = + fmax(sp->feedback_data.firehose_radius_stream, kernel_gamma * sp->h); + } + + /* D. Rennehan: Do some magic that I still don't understand + */ + double dum = 0.; + int flag_negative = 0; + /* Here we can loop over Swift metals because metal_mass_fraction + * would be zero for the unique Chem5 metals anyway, and would + * not activate the condition. + */ + for (elem = 0; elem < chemistry_element_count; elem++) { + dum = ejecta_unprocessed * sp->chemistry_data.metal_mass_fraction[elem]; + const int elem_conv = feedback_props->element_index_conversions[elem]; + ejecta_metal_mass[elem_conv] += dum; + if (ejecta_metal_mass[elem_conv] < 0.) { + ejecta_metal_mass[elem_conv] = 0.; + flag_negative = 1; + /* Do not break here, we need the zeroed elements where negative */ + } + } + + /* Check for any remaining that have negative mass after adding unprocessed */ + for (elem = 0; elem < chem5_element_count; elem++) { + if (ejecta_metal_mass[elem] < 0.) { + ejecta_metal_mass[elem] = 0.; + flag_negative = 1; + } + } + + /* If ANY element ended up negative we recompute everything */ + if (flag_negative) { + ejecta_mass = 0.; + ejecta_metal_mass[chem5_element_Z] = 0.; + for (elem = chem5_element_H; elem < chem5_element_Zn; elem++) { + ejecta_mass += ejecta_metal_mass[elem]; + } + + for (elem = chem5_element_C; elem < chem5_element_Zn; elem++) { + ejecta_metal_mass[chem5_element_Z] += ejecta_metal_mass[elem]; + } + } + + /* Now we loop over the Swift metals and set the proper values using the + conversion map */ + sp->feedback_data.total_metal_mass = ejecta_metal_mass[chem5_element_Z]; + for (elem = 0; elem < chemistry_element_count; elem++) { + sp->feedback_data.metal_mass[elem] = + ejecta_metal_mass[feedback_props->element_index_conversions[elem]]; + } + +#if COOLING_GRACKLE_MODE >= 2 + /* Put some of the ejecta metals into dust. Must be done after + chem5->chemistry conversion map is applied */ + if (sp->feedback_data.total_metal_mass > 0.) { + feedback_dust_production_condensation(sp, star_age_beg_step, feedback_props, + sp->feedback_data.metal_mass); + } +#endif + + /* Compute the total mass to distribute */ + sp->feedback_data.mass = ejecta_mass; + sp->feedback_data.energy = ejecta_energy; + + /* Decrease star mass by amount of mass distributed to gas neighbours */ + sp->mass -= ejecta_mass; + + /* Mark this is the last time we did enrichment */ + sp->last_enrichment_time = (with_cosmology) ? cosmo->a : time; + +#if COOLING_GRACKLE_MODE >= 2 + /* Update the number of SNe that have gone off, used in Grackle dust model. + Actually stores SNe rate */ + sp->feedback_data.SNe_ThisTimeStep = N_SNe / dt; + sp->feedback_data.SNe_Total += N_SNe; +#endif + +#ifdef SWIFT_STARS_DENSITY_CHECKS + sp->has_done_feedback = 1; +#endif + + TIMER_TOC(timer_do_star_evol); +} + +/** + * @brief Will this star particle want to do feedback during the next time-step? + * + * This is called in the time step task and increases counters of time-steps + * that have been performed. + * + * @param sp The particle to act upon + * @param feedback_props The #feedback_props structure. + * @param cosmo The current cosmological model. + * @param us The unit system. + * @param phys_const The #phys_const. + * @param time The physical time in internal units. + * @param with_cosmology Are we running with cosmology on? + * @param ti_current The current time (in integer) + * @param time_base The time base. + */ +__attribute__((always_inline)) INLINE static void feedback_will_do_feedback( + struct spart *sp, const struct feedback_props *feedback_props, + const int with_cosmology, const struct cosmology *cosmo, const double time, + const struct unit_system *us, const struct phys_const *phys_const, + const integertime_t ti_current, const double time_base) { + + /* Special case for new-born stars */ + if (with_cosmology) { + if (sp->birth_scale_factor == (float)cosmo->a) { + + /* Set the counter to "let's do enrichment" */ + sp->count_since_last_enrichment = 0; + + /* Ok, we are done. */ + return; + } + } else { + if (sp->birth_time == (float)time) { + + /* Set the counter to "let's do enrichment" */ + sp->count_since_last_enrichment = 0; + + /* Ok, we are done. */ + return; + } + } + + /* Calculate age of the star at current time */ + double age_of_star; + if (with_cosmology) { + age_of_star = cosmology_get_delta_time_from_scale_factors( + cosmo, (double)sp->birth_scale_factor, cosmo->a); + } else { + age_of_star = time - (double)sp->birth_time; + } + + /* Is the star still young? */ + if (age_of_star < feedback_props->stellar_evolution_age_cut) { + + /* Set the counter to "let's do enrichment" */ + sp->count_since_last_enrichment = 0; + + } else { + + /* Increment counter */ + sp->count_since_last_enrichment++; + + if ((sp->count_since_last_enrichment % + feedback_props->stellar_evolution_sampling_rate) == 0) { + + /* Reset counter */ + sp->count_since_last_enrichment = 0; + } + } +} + +void feedback_clean(struct feedback_props *fp); + +void feedback_struct_dump(const struct feedback_props *feedback, FILE *stream); + +void feedback_struct_restore(struct feedback_props *feedback, FILE *stream); + +#ifdef HAVE_HDF5 +/** + * @brief Writes the current model of feedback to the file + * + * @param feedback The properties of the feedback scheme. + * @param h_grp The HDF5 group in which to write. + */ +INLINE static void feedback_write_flavour(struct feedback_props *feedback, + hid_t h_grp) { + + io_write_attribute_s(h_grp, "Feedback Model", + "KIARA " + "(decoupled kinetic + chem5 enrichment)"); +} +#endif // HAVE_HDF5 + +#endif /* SWIFT_FEEDBACK_KIARA_H */ diff --git a/src/feedback/KIARA/feedback_debug.h b/src/feedback/KIARA/feedback_debug.h new file mode 100644 index 0000000000..5d78a3fb88 --- /dev/null +++ b/src/feedback/KIARA/feedback_debug.h @@ -0,0 +1,26 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_FEEDBACK_KIARA_DEBUG_H +#define SWIFT_FEEDBACK_KIARA_DEBUG_H + +__attribute__((always_inline)) INLINE static void feedback_debug_particle( + const struct part *p, const struct xpart *xp) {} + +#endif /* SWIFT_FEEDBACK_KIARA_DEBUG_H */ diff --git a/src/feedback/KIARA/feedback_iact.h b/src/feedback/KIARA/feedback_iact.h new file mode 100644 index 0000000000..4bda827be1 --- /dev/null +++ b/src/feedback/KIARA/feedback_iact.h @@ -0,0 +1,866 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_KIARA_FEEDBACK_IACT_H +#define SWIFT_KIARA_FEEDBACK_IACT_H + +/* Local includes */ +#include "random.h" +#include "timestep_sync_part.h" +#include "tools.h" +#include "tracers.h" + +#include + +#define KIARA_WIND_LOG + +/** + * @brief Set direction of stellar feedback kick + * + * @param si Star particle. + * @param pj Gas particle being kicked. + * @param ti_current Current integer time value (for random numbers). + * @param dir_flag Flag to choose direction: 0=rendom, 1=L_gas, 2=L_BH. + * @param dir Direction of kick (returned). + */ +__attribute__((always_inline)) INLINE static float feedback_set_kick_direction( + const struct spart *si, const struct part *pj, + const integertime_t ti_current, const int dir_flag, float *dir) { + + float kick_dir = 1.f; + double random_number = 1.; + + switch (dir_flag) { + /* Isotropic */ + case 0: { + const double random_for_theta = random_unit_interval( + si->id, ti_current, random_number_isotropic_SNII_feedback_ray_theta); + const double random_for_phi = random_unit_interval( + si->id, ti_current, random_number_isotropic_SNII_feedback_ray_phi); + + const float theta = acosf(2.f * random_for_theta - 1.f); + const float phi = 2.f * M_PI * random_for_phi; + + dir[0] = sinf(theta) * cosf(phi); + dir[1] = sinf(theta) * sinf(phi); + dir[2] = cosf(theta); + break; + } + + /* Along the v x a direction */ + case 1: { + dir[0] = pj->gpart->a_grav[1] * pj->gpart->v_full[2] - + pj->gpart->a_grav[2] * pj->gpart->v_full[1]; + dir[1] = pj->gpart->a_grav[2] * pj->gpart->v_full[0] - + pj->gpart->a_grav[0] * pj->gpart->v_full[2]; + dir[2] = pj->gpart->a_grav[0] * pj->gpart->v_full[1] - + pj->gpart->a_grav[1] * pj->gpart->v_full[0]; + + random_number = random_unit_interval(si->id, ti_current, + random_number_stellar_feedback_1); + kick_dir = (random_number > 0.5) ? 1.f : -1.f; + break; + } + /* Outwards from star */ + case 2: + dir[0] = pj->x[0] - si->x[0]; + dir[1] = pj->x[1] - si->x[1]; + dir[2] = pj->x[2] - si->x[2]; + break; + + default: + error("dir_flag=%d but must be 0, 1, or 2", dir_flag); + break; + } + + return kick_dir; +} + +/** + * @brief Compute customized kernel weight for feedback + * + * @param pj gas particle. + * @param wi SPH kernel weight at location of pj + */ +__attribute__((always_inline)) INLINE static float feedback_kernel_weight( + const struct part *pj, const float wi, const float ui, + const struct feedback_props *fb_props) { + + /* If it's beyond the kick radius, then the weighting is zero */ + if (ui >= fb_props->kick_radius_over_h) return 0.f; + + /* Weight towards higher SFR particles. As SFR->0, SFR_wi->wi and + * then radial weighting returns to normal. */ + float weight = wi; + if (fb_props->use_sfr_weighted_launch == 1) { + weight = (pj->sf_data.SFR > 0.f) ? wi + pj->sf_data.SFR : wi; + } + weight *= hydro_get_mass(pj); + + return weight; +} + +/** + * @brief Density interaction between two particles (non-symmetric). + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param si First sparticle. + * @param pj Second particle (not updated). + * @param xpj Extra particle data (not updated). + * @param cosmo The cosmological model. + * @param fb_props Properties of the feedback scheme. + * @param ti_current Current integer time value + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_feedback_density(const float r2, const float dx[3], + const float hi, const float hj, + struct spart *si, const struct part *pj, + const struct xpart *xpj, + const struct cosmology *cosmo, + const struct feedback_props *fb_props, + const integertime_t ti_current) { + + /* Do not count winds in the density */ + // if (pj->decoupled) return; + + const float rho = hydro_get_comoving_density(pj); + if (rho <= 0.f) return; + + /* Get the gas mass. */ + const float mj = hydro_get_mass(pj); + + /* Get r. */ + const float r = sqrtf(r2); + + /* Compute the kernel function */ + const float hi_inv = 1.0f / hi; + const float ui = r * hi_inv; + + float wi; + kernel_eval(ui, &wi); + + /* Add mass of pj to neighbour mass of si */ + si->feedback_data.ngb_mass += mj; + + /* sum(mj * wj) */ + si->feedback_data.kernel_wt_sum += mj * wi; + + /* If pj is being kicked in this step, don't kick again */ + if (pj->feedback_data.kick_id > -1) return; + + /* Sum up the weights for normalizing the kernel later */ + const float wt = feedback_kernel_weight(pj, wi, ui, fb_props); + si->feedback_data.wind_wt_sum += wt; + if (wt > 0.f) si->feedback_data.wind_ngb_mass += mj; +} + +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_feedback_prep1(const float r2, const float dx[3], + const float hi, const float hj, + const struct spart *si, struct part *pj, + const struct xpart *xpj, + const struct cosmology *cosmo, + const struct feedback_props *fb_props, + const integertime_t ti_current) { + + /* No need to even check anything else if there is no mass or energy to launch + */ + if (si->feedback_data.mass_to_launch <= 0.f) return; + + if (si->feedback_data.physical_energy_reservoir <= 0.f) return; + + /* No (eligible) mass surrounding the star, no kick */ + if (si->feedback_data.wind_wt_sum <= 0.f) return; + + /* If pj is being kicked by another star particle, don't kick again */ + if (pj->feedback_data.kick_id > -1) return; + + /* If pj is already a wind particle, don't kick again */ + // if (pj->decoupled) return; + + /* Get r. */ + const float r = sqrtf(r2); + + /* No kicks far away from the star */ + if (r >= fb_props->kick_radius_over_h * hi) return; + + /* Compute the kernel function */ + const float hi_inv = 1.0f / hi; + const float ui = r * hi_inv; + + float wi; + kernel_eval(ui, &wi); + + /* Bias towards the center of the kernel and to high SFR. Note: contains mj */ + const float wt = feedback_kernel_weight(pj, wi, ui, fb_props); + + /* No kick if weight is zero */ + if (wt <= 0.f) return; + + /* Total wind ngb mass in kernel */ + const float ngb_mass = si->feedback_data.wind_ngb_mass; + + /* Total mass to launch for this star particle */ + float mass_to_launch = si->feedback_data.mass_to_launch; + + /* Suppress based on the input parameter file */ + mass_to_launch *= si->feedback_data.eta_suppression_factor; + + /* Estimated number of particles to kick out of the kernel */ + const float mass_frac_to_launch = mass_to_launch / ngb_mass; + + if (mass_frac_to_launch > fb_props->max_frac_of_kernel_to_launch) { + mass_to_launch = fb_props->max_frac_of_kernel_to_launch * ngb_mass; + } + + /* Correct the weight term for the proper Bernoulli trial */ + const float wj = wt / hydro_get_mass(pj); + + /* Probability to swallow this particle */ + const float prob = mass_to_launch * wj / si->feedback_data.wind_wt_sum; + + /* Draw a random number (Note mixing both IDs), up to max probability */ + const float rand = random_unit_interval(si->id + pj->id, ti_current, + random_number_stellar_feedback_1); + + /* We kick! */ + if (rand < prob) { + pj->feedback_data.kick_id = si->id; + } + +#ifdef KIARA_DEBUG_CHECKS + message( + "KICK_PROB: z=%g sid=%lld, gid=%lld, prob=%g, rand=%g, eta=%g, " + "mlaunch=%g, " + "m*=%g, wj=%g, wt_sum=%g, kicked? %d", + cosmo->z, si->id, pj->id, prob, rand, + si->feedback_data.mass_to_launch / si->mass_init, + si->feedback_data.mass_to_launch, si->mass_init, wj, + si->feedback_data.wind_wt_sum, pj->feedback_data.kick_id == si->id); +#endif +} + +/** + * @brief Compile gas particles to be kicked by stellar feedback in this step, + * + * + * @param r2 Distance squared from star i to gas j. + * @param dx[3] x,y,z distance from star i to gas j. + * @param hi Smoothing length of star. + * @param hj Smoothing length of gas. + * @param si First (star) particle. + * @param pj Second (gas) particle (not updated). + * @param xpj Extra particle data. + * @param cosmo The cosmological model. + * @param ti_current Current integer time. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_feedback_prep2(const float r2, const float dx[3], + const float hi, const float hj, + struct spart *si, const struct part *pj, + const struct xpart *xpj, + const struct cosmology *cosmo, + const integertime_t ti_current) { + + /* Remove mass from the mass_to_launch reservoir */ + if (pj->feedback_data.kick_id == si->id) { + + si->feedback_data.mass_to_launch -= hydro_get_mass(pj); + si->feedback_data.total_mass_kicked += hydro_get_mass(pj); + + /* Work done on the particle */ + const float v2 = + si->feedback_data.wind_velocity * si->feedback_data.wind_velocity; + const double energy_phys = 0.5 * hydro_get_mass(pj) * v2 * cosmo->a2_inv; + + /* Remove energy used to kick particle from the SNII energy reservoir */ + si->feedback_data.physical_energy_reservoir -= energy_phys; + if (si->feedback_data.physical_energy_reservoir < 0.f) { + si->feedback_data.physical_energy_reservoir = 0.f; + } + + /* Keep track of how many particles launched */ + si->feedback_data.N_launched += 1; + } +} + +/** + * @brief Kick and sometimes heat gas particle near a star, + * if star has enough mass and energy for an ejection event. + * + * @param si First (star) particle (not updated). + * @param pj Second (gas) particle. + * @param xpj Extra particle data + * @param cosmo The cosmological model. + * @param fb_props Properties of the feedback scheme. + * @param ti_current Current integer time used value for seeding random number + * generator + */ +__attribute__((always_inline)) INLINE static void feedback_kick_gas_around_star( + const struct spart *si, struct part *pj, struct xpart *xpj, + const struct cosmology *cosmo, const struct feedback_props *fb_props, + const integertime_t ti_current) { + + if (pj->feedback_data.kick_id == si->id) { + + /* Need time-step for decoupling */ + const integertime_t ti_step = get_integer_timestep(pj->time_bin); + const integertime_t ti_begin = + get_integer_time_begin(ti_current - 1, pj->time_bin); + + /* TODO: Requires always having with_cosmology! */ + const double dt = + cosmology_get_delta_time(cosmo, ti_begin, ti_begin + ti_step); + + /* Compute velocity and KE of wind event. + * Note that xpj->v_full = a^2 * dx/dt, with x the comoving + * coordinate. Therefore, a physical kick, dv, gets translated into a + * code velocity kick, a * dv. + */ + const float wind_velocity = si->feedback_data.wind_velocity; + const float wind_velocity_phys = fabs(wind_velocity * cosmo->a_inv); + float dir[3] = {0.f, 0.f, 0.f}; + const int dir_flag = fb_props->kick_direction_flag; + const float dirsign = + feedback_set_kick_direction(si, pj, ti_current, dir_flag, dir); + + float norm = sqrtf(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]); + + /* Zero normalization (should basically never happen); randomize direction + */ + if (norm <= 0.f) { + warning( + "z=%g sid=%lld pid=%lld normalization of wind direction is " + "zero!\n(x, y, z) " + "= (%g, %g, %g); vw=%g. Randomizing direction.", + cosmo->z, si->id, pj->id, dir[0], dir[1], dir[2], + fabs(wind_velocity * cosmo->a_inv)); + dir[0] = random_unit_interval(pj->id, ti_current, + random_number_stellar_feedback_1); + dir[1] = random_unit_interval(pj->id, ti_current, + random_number_stellar_feedback_2); + dir[2] = random_unit_interval(pj->id, ti_current, + random_number_stellar_feedback_3); + norm = sqrtf(dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2]); + } + + const float prefactor = dirsign * wind_velocity / norm; + + /* Do the kicks by updating the particle velocity. */ + xpj->v_full[0] += dir[0] * prefactor; + xpj->v_full[1] += dir[1] * prefactor; + xpj->v_full[2] += dir[2] * prefactor; + + /* DO WIND HEATING */ + double u_new = fb_props->cold_wind_internal_energy; + if (!fb_props->use_firehose_model) { + float galaxy_stellar_mass = si->galaxy_data.stellar_mass; + + if (galaxy_stellar_mass < fb_props->minimum_galaxy_stellar_mass) { + galaxy_stellar_mass = fb_props->minimum_galaxy_stellar_mass; + } + const float galaxy_stellar_mass_Msun = + galaxy_stellar_mass * fb_props->mass_to_solar_mass; + + /* Based on Pandya et al 2022 FIRE results */ + float pandya_slope = 0.f; + if (galaxy_stellar_mass_Msun > 3.16e10) { + pandya_slope = -2.1f; + } else { + pandya_slope = -0.1f; + } + + /* 0.2511886 = pow(10., -0.6) */ + const float f_warm = + 0.2511886f * pow(galaxy_stellar_mass_Msun / 3.16e10f, pandya_slope); + /* additional 10% removed for cold phase */ + const float hot_wind_fraction = max(0.f, 0.9f - f_warm); + const float rand_for_hot = random_unit_interval( + pj->id, ti_current, random_number_stellar_feedback_2); + const float rand_for_spread = random_unit_interval( + pj->id, ti_current, random_number_stellar_feedback_3); + + /* If selected, heat the particle */ + const double u_wind = 0.5 * wind_velocity_phys * wind_velocity_phys; + if (rand_for_hot < hot_wind_fraction && + fb_props->hot_wind_internal_energy > u_wind) { + u_new = (fb_props->hot_wind_internal_energy - u_wind) * + (0.5 + rand_for_spread); + u_new += hydro_get_physical_internal_energy(pj, xpj, cosmo); + } + } + + /* Set the wind particle internal energy */ + hydro_set_physical_internal_energy(pj, xpj, cosmo, u_new); + hydro_set_drifted_physical_internal_energy(pj, cosmo, NULL, u_new); + +#ifdef FIREHOSE_DEBUG_CHECKS + /* For firehose model, set initial radius of stream */ + if (si->feedback_data.firehose_radius_stream <= 0.f) { + error( + "Firehose error: firehose_radius_stream <= 0. sid=%lld " + "pid=%lld Rstream=%g", + si->id, pj->id, si->feedback_data.firehose_radius_stream); + } +#endif + + pj->chemistry_data.radius_stream = si->feedback_data.firehose_radius_stream; + pj->chemistry_data.exchanged_mass = 0.f; + + /* FINISH UP FEEDBACK */ + /* Turn off any star formation in wind particle. + * Record exp factor of when this particle was last ejected as -SFR. */ + pj->sf_data.SFR = -cosmo->a; + + /* Update the signal velocity of the particle based on the velocity kick, + wind_velocity must be PHYSICAL passed into this function */ + hydro_set_v_sig_based_on_velocity_kick(pj, cosmo, wind_velocity_phys); + + /* Impose maximal viscosity */ + hydro_diffusive_feedback_reset(pj); + + /* Synchronize the particle on the timeline */ + timestep_sync_part(pj); + + if (fb_props->wind_decouple_time_factor > 0.f) { + /* Decouple the particles from the hydrodynamics */ + pj->feedback_data.decoupling_delay_time = + dt + fb_props->wind_decouple_time_factor * + cosmology_get_time_since_big_bang(cosmo, cosmo->a); + pj->decoupled = 1; + } else { + pj->feedback_data.decoupling_delay_time = 0.f; + pj->decoupled = 0; + } + + /* TODO: Move to chemistry module */ + pj->chemistry_data.diffusion_coefficient = 0.f; + + /* Take particle out of subgrid ISM mode */ + pj->cooling_data.subgrid_temp = 0.f; + pj->cooling_data.subgrid_dens = hydro_get_physical_density(pj, cosmo); + pj->cooling_data.subgrid_fcold = 0.f; + + pj->feedback_data.number_of_times_decoupled += 1; + + /* Kicked and handled */ + pj->feedback_data.kick_id = -1; + +#ifdef KIARA_WIND_LOG + /** Log the wind event. + * z starid gasid dt M* vkick vkx vky vkz h x y z vx vy vz T rho v_sig tdec + * Ndec Z + */ + const float length_convert = cosmo->a * fb_props->length_to_kpc; + const float velocity_convert = cosmo->a_inv / fb_props->kms_to_internal; + const float rho_convert = cosmo->a3_inv * fb_props->rho_to_n_cgs; + const float u_convert = + cosmo->a_factor_internal_energy / fb_props->temp_to_u_factor; + /* Collect information about galaxy that the particle belongs to */ + const float galaxy_mstar = si->galaxy_data.stellar_mass; + const float galaxy_ssfr = si->galaxy_data.specific_sfr; + + printf( + "WIND_LOG z=%.5f sid=%lld mlaunch=%g mkicked=%g Nkicked=%g zbirth=%g " + "M*=%g sSFR=%g pid=%lld vw=%g vwx=%g vwy=%g vwz=%g h=%g x=%g " + "y=%g z=%g vx=%g vy=%g vz=%g " + "T=%g nH=%g tdel=%g Ndec=%d fZ=%g\n", + cosmo->z, si->id, + si->feedback_data.mass_to_launch * fb_props->mass_to_solar_mass, + si->feedback_data.total_mass_kicked * fb_props->mass_to_solar_mass, + si->feedback_data.total_mass_kicked / si->mass, + 1.f / si->birth_scale_factor - 1.f, + galaxy_mstar * fb_props->mass_to_solar_mass, + galaxy_ssfr / fb_props->time_to_yr, pj->id, + fabs(wind_velocity) * velocity_convert, + prefactor * dir[0] * velocity_convert, + prefactor * dir[1] * velocity_convert, + prefactor * dir[2] * velocity_convert, pj->h * length_convert, + pj->x[0] * length_convert, pj->x[1] * length_convert, + pj->x[2] * length_convert, xpj->v_full[0] * velocity_convert, + xpj->v_full[1] * velocity_convert, xpj->v_full[2] * velocity_convert, + hydro_get_comoving_internal_energy(pj, xpj) * u_convert, + pj->rho * rho_convert, + pj->feedback_data.decoupling_delay_time * fb_props->time_to_Myr, + pj->feedback_data.number_of_times_decoupled, + pj->chemistry_data.metal_mass_fraction_total); +#endif + } +} + +/** + * @brief Feedback interaction between two particles (non-symmetric). + * Used for updating properties of gas particles neighbouring a star particle + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (si - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param si First (star) particle (not updated). + * @param pj Second (gas) particle. + * @param xpj Extra particle data + * @param cosmo The cosmological model. + * @param fb_props Properties of the feedback scheme. + * @param ti_current Current integer time used value for seeding random number + * generator + */ +__attribute__((always_inline)) INLINE static void +feedback_do_chemical_enrichment_of_gas_around_star( + const float r2, const float dx[3], const float hi, const float hj, + const struct spart *si, struct part *pj, struct xpart *xpj, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct feedback_props *fb_props, const integertime_t ti_current) { + + /* Nothing to distribute */ + if (si->feedback_data.mass <= 0.f || si->feedback_data.kernel_wt_sum <= 0.f) + return; + + /* Gas particle density */ + const float rho_j = hydro_get_comoving_density(pj); + if (rho_j <= 0.f) return; + + /* Get r. */ + const float r = sqrtf(r2); + + /* Compute the kernel function */ + const float hi_inv = 1.0f / hi; + const float ui = r * hi_inv; + float wi; + kernel_eval(ui, &wi); + + const double current_mass = hydro_get_mass(pj); + /* Compute weighting for distributing feedback quantities. + * f = (mi * wi) / sum(mj * wj) */ + float Omega_frac = current_mass * wi / si->feedback_data.kernel_wt_sum; + + /* Never apply feedback if Omega_frac is bigger than or equal to unity */ + if (Omega_frac < 0.f || (Omega_frac > 1.f && ui < 1.f)) { + warning( + "Invalid fraction of material to distribute for star ID=%lld " + "Omega_frac=%e count since last enrich=%d kernel_wt_sum=%g " + "wi=%g rho_j=%g", + si->id, Omega_frac, si->count_since_last_enrichment, + si->feedback_data.kernel_wt_sum, wi, rho_j); + if (Omega_frac < 0.f || (Omega_frac > 1.01f && ui < 1.f)) { + error("Omega_frac negative or too large! aborting"); + } + + Omega_frac = fmin(Omega_frac, 1.f); + } + + /* ------ Handle mass from SN explosions ------ */ + + /* Update particle mass */ + double delta_mass = si->feedback_data.mass * Omega_frac; + double new_mass = current_mass + delta_mass; + const double max_new_mass = current_mass * fb_props->max_mass_increase_factor; + + if (new_mass > max_new_mass) { + /* Count for logging in the snapshot. */ + pj->feedback_data.mass_limiter_count++; + + /* Limit the mass growth and any other quantity below */ + delta_mass = max_new_mass - current_mass; + Omega_frac = delta_mass / si->feedback_data.mass; + + new_mass = current_mass + delta_mass; + +#ifdef KIARA_DEBUG_CHECKS + warning("New mass %g exceeds maximum %g for particle id=%lld --- limiting!", + new_mass, max_new_mass, pj->id); +#endif + } + + hydro_set_mass(pj, new_mass); + + /* Inverse of the new mass */ + const double new_mass_inv = 1. / new_mass; + + /* ------ Energy from SN explosions ------ */ + + if (si->feedback_data.energy > 0.f) { + /* Update particle energy */ + double injected_energy = si->feedback_data.energy * Omega_frac; + + /* Compute the current thermal energy */ + const double current_thermal_energy = + current_mass * hydro_get_physical_internal_energy(pj, xpj, cosmo); + + /* To check overheating */ + const double current_u_phys = current_thermal_energy / current_mass; + + /* Check if we are gonna blow up */ + double new_u_phys = current_u_phys + injected_energy * new_mass_inv; + + const double max_new_u_phys = + fb_props->max_energy_increase_factor * current_u_phys; + + /* PHYSICAL comparison */ + if (new_u_phys > max_new_u_phys) { + + /* Count for logging in the snapshot. */ + pj->feedback_data.heating_limiter_count++; + + injected_energy = + max_new_u_phys * new_mass - current_u_phys * current_mass; + + /* Make sure the injected energy doesn't decrease */ + if (injected_energy < 0.) injected_energy = 0.; + +#ifdef KIARA_DEBUG_CHECKS + warning( + "Injected energy %g exceeds maximum %g for particle id=%lld" + " --- limiting!", + new_u_phys, max_new_u_phys, pj->id); +#endif + } + + const double new_thermal_energy = current_thermal_energy + injected_energy; + + /* Update after momentum conservation and limiting */ + new_u_phys = new_thermal_energy * new_mass_inv; + + /* Do we want to move things off of the ISM if there is sufficient heating? + */ + if (fb_props->SNIa_add_heat_to_ISM) { + + if (pj->cooling_data.subgrid_temp > 0.f && + pj->cooling_data.subgrid_fcold > 0.f) { + + /* 0.8125 is mu for a fully neutral gas with XH=0.75; + * approximate but good enough */ + const double u_cold_phys = + 0.8125 * pj->cooling_data.subgrid_temp * fb_props->temp_to_u_factor; + + const double delta_u_ISM_phys = current_u_phys - u_cold_phys; + double f_evap = 0.; + + const double du_phys = new_u_phys - current_u_phys; + + /* Use extra heat to move off of the ISM */ + if (du_phys > 0. && delta_u_ISM_phys >= 0.) { + const double u_phys_tol = + fb_props->SNIa_add_heat_to_ISM_tolerance * current_u_phys; + + if (delta_u_ISM_phys > u_phys_tol) { + f_evap = du_phys / delta_u_ISM_phys; + f_evap = min(f_evap, 1.0); + } else { + f_evap = 1.0; + } + + /* Clip values in case of overflow */ + if (f_evap > 0.) { + pj->cooling_data.subgrid_fcold *= 1. - f_evap; + + const double u_remaining_phys = du_phys - f_evap * delta_u_ISM_phys; + new_u_phys = current_u_phys + max(u_remaining_phys, 0.); + + /* Limit internal energy increase here as well */ + if (new_u_phys > max_new_u_phys) { + new_u_phys = max_new_u_phys; + pj->feedback_data.heating_limiter_count++; + } + + if (pj->cooling_data.subgrid_fcold <= 0.f) { + pj->cooling_data.subgrid_temp = 0.f; + pj->cooling_data.subgrid_dens = + hydro_get_physical_density(pj, cosmo); + pj->cooling_data.subgrid_fcold = 0.f; + } + } + } + } + } + + hydro_set_physical_internal_energy(pj, xpj, cosmo, new_u_phys); + hydro_set_drifted_physical_internal_energy(pj, cosmo, /*pfloor=*/NULL, + new_u_phys); + } /* si->feedback_data.energy > 0.f */ + + /* ------ Handle metal injection from SN explosions ------ */ + + /* Recompute Z since we do not track all of the metals from Chem5 */ + pj->chemistry_data.metal_mass_fraction_total = 0.f; + + /* Update mass fraction of each tracked element */ + for (int elem = 0; elem < chemistry_element_count; elem++) { + const double current_metal_mass = + pj->chemistry_data.metal_mass_fraction[elem] * current_mass; + const double delta_metal_mass = + si->feedback_data.metal_mass[elem] * Omega_frac; + const double new_metal_mass = current_metal_mass + delta_metal_mass; + + pj->chemistry_data.metal_mass_fraction[elem] = + new_metal_mass * new_mass_inv; + + if (elem != chemistry_element_H && elem != chemistry_element_He) { + pj->chemistry_data.metal_mass_fraction_total += + pj->chemistry_data.metal_mass_fraction[elem]; + } + } + + /* Make sure that X + Y + Z = 1 */ + const float Y_He = + pj->chemistry_data.metal_mass_fraction[chemistry_element_He]; + const float Z = pj->chemistry_data.metal_mass_fraction_total; + const float X_H = 1.f - Y_He - Z; + + if (X_H < 0.f || X_H > 1.f) { + for (int elem = 0; elem < chemistry_element_count; elem++) { + warning("\telem[%d] is %g", elem, + pj->chemistry_data.metal_mass_fraction[elem]); + } + + error( + "Hydrogen fraction exeeds unity or is negative for" + " particle id=%lld due to stellar feedback.", + pj->id); + } + + pj->chemistry_data.metal_mass_fraction[chemistry_element_H] = X_H; + + /* Compute kernel-smoothed contribution to number of SNe going off + * this timestep */ + pj->feedback_data.SNe_ThisTimeStep += + si->feedback_data.SNe_ThisTimeStep * Omega_frac; + pj->feedback_data.SNe_ThisTimeStep = + fmax(pj->feedback_data.SNe_ThisTimeStep, 0.); + + /* Spread dust ejecta to gas */ + for (int elem = chemistry_element_He; elem < chemistry_element_count; + elem++) { + const double current_dust_mass = + pj->cooling_data.dust_mass_fraction[elem] * pj->cooling_data.dust_mass; + const double delta_dust_mass = + si->feedback_data.delta_dust_mass[elem] * Omega_frac; + + /* at the moment this stores the mass (not mass frac) in each elem */ + pj->cooling_data.dust_mass_fraction[elem] = + (current_dust_mass + delta_dust_mass); + } + + /* Sum up each element to get total dust mass */ + pj->cooling_data.dust_mass = 0.; + for (int elem = chemistry_element_He; elem < chemistry_element_count; + elem++) { + pj->cooling_data.dust_mass += pj->cooling_data.dust_mass_fraction[elem]; + } + + if (pj->cooling_data.dust_mass > 0.) { + const double dust_mass_inv = 1. / pj->cooling_data.dust_mass; + + /* Divide by new dust mass to get the fractions */ + for (int elem = chemistry_element_He; elem < chemistry_element_count; + elem++) { + pj->cooling_data.dust_mass_fraction[elem] *= dust_mass_inv; + } + + /* Check for inconsistency */ + if (pj->cooling_data.dust_mass > pj->mass) { + for (int elem = chemistry_element_He; elem < chemistry_element_count; + elem++) { + message("DUST EXCEEDS MASS elem=%d md=%g delta=%g \n", elem, + pj->cooling_data.dust_mass_fraction[elem] * + pj->cooling_data.dust_mass, + si->feedback_data.delta_dust_mass[elem] * Omega_frac); + } + + error("DUST EXCEEDS MASS mgas=%g mdust=%g\n", pj->mass, + pj->cooling_data.dust_mass); + } + } else { + /* For some reason the dust mass is zero or negative */ + for (int elem = 0; elem < chemistry_element_count; elem++) { + pj->cooling_data.dust_mass_fraction[elem] = 0.f; + } + } +} + +/** + * @brief Feedback interaction between two particles (non-symmetric). + * Used for updating properties of gas particles neighbouring a star particle + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (si - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param si First (star) particle (not updated). + * @param pj Second (gas) particle. + * @param xpj Extra particle data + * @param cosmo The cosmological model. + * @param fb_props Properties of the feedback scheme. + * @param ti_current Current integer time used value for seeding random number + * generator + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_feedback_apply( + const float r2, const float dx[3], const float hi, const float hj, + const struct spart *si, struct part *pj, struct xpart *xpj, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct feedback_props *fb_props, const integertime_t ti_current) { + + /* Ignore decoupled particles */ + // if (pj->decoupled) return; + + /* Do chemical enrichment of gas, metals and dust from star */ + feedback_do_chemical_enrichment_of_gas_around_star( + r2, dx, hi, hj, si, pj, xpj, cosmo, hydro_props, fb_props, ti_current); + + /* Do kinetic wind feedback */ + feedback_kick_gas_around_star(si, pj, xpj, cosmo, fb_props, ti_current); + +#if COOLING_GRACKLE_MODE >= 2 + /* NOT USED: Compute G0 contribution from star to the gas particle in Habing + units of + * 1.6e-3 erg/s/cm^2. Note that this value includes the 4*pi geometric factor + if (0) { + const float length_to_physical_cm = cosmo->a * fb_props->length_to_kpc + * 3.08567758e21f; + // Compute a softened distance from star to gas particle + const double r2_in_cm = (r2 + 0.01*hi*hi) * length_to_physical_cm * + length_to_physical_cm; const double r_in_cm = sqrt(r2_in_cm); + + // Compute self-shielding from H2, from Schauer et al. 2015 eq 8,9 + // H attenuation factor + const double NH_cgs = hydro_get_physical_density(pj, cosmo) * + fb_props->rho_to_n_cgs * r_in_cm; const double xH = NH_cgs / 2.85e23; const + double fH_shield = pow(1.f+xH,-1.62) * exp(-0.149*xH); + // H2 attenuation factor + const double NH2_cgs = pj->sf_data.H2_fraction * NH_cgs; + const double DH2_cgs = 1.e-5 * + sqrt(2.*1.38e-16*cooling_get_subgrid_temperature(pj, xpj) / 3.346e-24); const + double xH2 = NH2_cgs / 8.465e13; const double fH2_shield = + 0.9379/pow(1.f+xH2/DH2_cgs,1.879) + 0.03465/pow(1.f+xH2,0.473) * + exp(-2.293e-4*sqrt(1+xH2)); + //message("G0 shield: r=%g xH2=%g xH=%g fH2=%g + fH=%g\n",r_in_cm/3.086e21,xH2,xH,fH2_shield,fH_shield); + + if (si->feedback_data.lum_habing > -10.) { + pj->chemistry_data.G0 += fH2_shield * fH_shield * + pow(10.,si->feedback_data.lum_habing) / (1.6e-3 * r2_in_cm); + } + }*/ +#endif +} + +#endif /* SWIFT_KIARA_FEEDBACK_IACT_H */ diff --git a/src/feedback/KIARA/feedback_properties.h b/src/feedback/KIARA/feedback_properties.h new file mode 100644 index 0000000000..46f6ef96a7 --- /dev/null +++ b/src/feedback/KIARA/feedback_properties.h @@ -0,0 +1,391 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_KIARA_FEEDBACK_PROPERTIES_H +#define SWIFT_KIARA_FEEDBACK_PROPERTIES_H + +/* Config parameters. */ +#include "../config.h" + +/* Local includes. */ +#include "chemistry.h" +#include "hydro_properties.h" + +#define NM 5000 +#define NZSN 7 +#define NMLF 41 +#define NZLF 9 +#define NZSN1R 5 /* secondary mass ranges */ +#define NZSN1Y 7 /* yields */ +#define NMSN 32 +#define NXSNall 84 + +#define SN1E_idx(A, B) ((A) * NZSN1Y + B) +#define LFLT_idx(A, B) ((A) * NMLF + B) +#define SN1R_idx(A, B) ((A) * NM + B) +#define SN2R_idx(A, B) ((A) * NM + B) +#define SWR_idx(A, B) ((A) * NM + B) +#define SN2E_idx(A, B, C) ((A) * NZSN * NM + (B) * NM + C) + +#define LINEAR_INTERPOLATION(x1, y1, x2, y2, x) \ + (((y2 - y1) / (x2 - x1)) * (x - x1) + y1) +#define LOG_INTERPOLATION(x, x2, x1) \ + ((log10(x2) - log10(x)) / (log10(x2) - log10(x1))) + +enum kiara_metal_boosting { + kiara_metal_boosting_off, + kiara_metal_boosting_vwind, + kiara_metal_boosting_eta, + kiara_metal_boosting_both +}; + +/* Chem5 tracks A LOT of elements but we will just map the standard 11 back */ +enum chem5_element { + chem5_element_Z = 0, + chem5_element_H, + chem5_element_He, + chem5_element_Li, + chem5_element_Be, + chem5_element_B, + chem5_element_C, + chem5_element_N, + chem5_element_O, + chem5_element_F, + chem5_element_Ne, + chem5_element_Na, + chem5_element_Mg, + chem5_element_Al, + chem5_element_Si, + chem5_element_P, + chem5_element_S, + chem5_element_Cl, + chem5_element_Ar, + chem5_element_K, + chem5_element_Ca, + chem5_element_Sc, + chem5_element_Ti, + chem5_element_V, + chem5_element_Cr, + chem5_element_Mn, + chem5_element_Fe, + chem5_element_Co, + chem5_element_Ni, + chem5_element_Cu, + chem5_element_Zn, + chem5_element_Ga, + chem5_element_Ge, + chem5_element_unknown, + chem5_element_count, + chem5_dummy1, + chem5_dummy2, + chem5_NXSN +}; + +/** + * @brief Stores the yield tables + */ +struct feedback_tables { + double *LFLT; + double *LFLM; + double *LFLZ; + double *LFLT2; + double *SWR; + double *SN2E; + double *SN2R; + double *SN1R; + double *SNLM; + double *SNLZ; + double *SNLZ1R; + double *SN1E; + double *SNLZ1Y; +}; + +/** + * @brief Properties of the KIARA feedback model. + */ +struct feedback_props { + + /* ------------ Main operation modes ------------- */ + + /*! Are we depositing energy from HN directly from Chem5? */ + int with_HN_energy_from_chem5; + + /*! Are we depositing energy from SNII directly from Chem5? */ + int with_SNII_energy_from_chem5; + + /*! Are we depositing energy from SNIa directly from Chem5? */ + int with_SNIa_energy_from_chem5; + + /*! If time since last chemical enrichment is above this value times the + * current stellar age, recompute */ + float stellar_enrichment_frequency; + + /* ------------ Yield tables ----------------- */ + + struct feedback_tables tables; + + /* Conversion indices from Chem5 to Swift */ + /* 0-Z, 2-He, 6-C , 7-N, 8-O, 10-Ne, 12-Mg, 14-Si, 26-Fe */ + int element_index_conversions[chemistry_element_count]; + + /* Location of feedback tables */ + char tables_path[200]; + + /* ------------- Conversion factors --------------- */ + + /*! Conversion factor from internal mass unit to solar mass */ + double mass_to_solar_mass; + + /*! The mass of the sun in g */ + double solar_mass_in_g; + + /*! Conversion factor from internal mass unit to solar mass */ + double solar_mass_to_mass; + + /*! Conversion factor from density in internal units to Hydrogen number + * density in cgs */ + double rho_to_n_cgs; + + /*! Conversion factor from temperature to internal energy */ + double temp_to_u_factor; + + /*! Conversion factor from km/s to cm/s */ + double kms_to_cms; + + /*! Factor to convert km/s to internal units */ + double kms_to_internal; + + /*! Convert internal units to kpc */ + double length_to_kpc; + + /*! Convert internal time to Myr */ + double time_to_Myr; + + /*! Convert internal time to yr */ + double time_to_yr; + + /*! Convert code energy units to cgs */ + double energy_to_cgs; + + /*! Convert temperature in K to internal */ + double T_to_internal; + + /* ------------ Enrichment sampling properties ------------ */ + + /*! Star age above which the enrichment will be downsampled (in internal + * units) */ + double stellar_evolution_age_cut; + + /*! Number of time-steps in-between two enrichment events */ + int stellar_evolution_sampling_rate; + + /* ------------ Kinetic feedback properties --------------- */ + + /*! Velocity normalization */ + float FIRE_velocity_normalization; + + /*! FIRE velocity slope */ + float FIRE_velocity_slope; + + /*! Normalization for the mass loading curve */ + float FIRE_eta_normalization; + + /*! The location (in internal mass units) where the break in the + * mass loading curve occurs */ + float FIRE_eta_break; + + /*! The power-law slope of eta below FIRE_eta_break */ + float FIRE_eta_lower_slope; + + /*! The power-law slope of eta above FIRE_eta_break */ + float FIRE_eta_upper_slope; + + /*! The wind speed of stellar feedback suppressed above this z */ + float wind_velocity_suppression_redshift; + + /*! The mass loading factor of stellar feedback suppressed above this z */ + float wind_eta_suppression_redshift; + + /*! Maxiumum multiple of SNII energy that is available to launch winds */ + float SNII_energy_multiplier; + + /*! For KIARA, the radius from within which to launch wind */ + float kick_radius_over_h; + + /*! For KIARA, the maximum fraction of gas mass within kernel to launch */ + float max_frac_of_kernel_to_launch; + + /*! For KIARA, weight the launch probability by the particles's SFR (0/1) */ + int use_sfr_weighted_launch; + + /*! Flag to set feedback boost at low Z: + * 0=Off, 1=vwind boost, 2=eta boost, 3=both boost */ + int metal_dependent_vwind; + + /*! The minimum galaxy stellar mass in internal units */ + float minimum_galaxy_stellar_mass; + + /*! The number of star particles when a galaxy is considered resolved */ + int galaxy_particle_resolution_count; + + /*! Floor for the eta suppression factor */ + float eta_suppression_factor_floor; + + /*! Direction to launch wind: 0=random, 1=v x a, 2=outwards */ + float kick_direction_flag; + + /*! Added scatter to the wind velocities */ + float kick_velocity_scatter; + + /*! max decoupling time is (this factor) * current Hubble time */ + float wind_decouple_time_factor; + + /*! Density (cgs) above which recoupling considers it within ISM */ + float recouple_ism_density_nH_cgs; + + /*! Factor (<1) below ISM density below which to recouple */ + float recouple_density_factor; + + /*! The internal energy corresponding to the unheated wind temperature */ + float cold_wind_internal_energy; + + /*! The internal energy corresponding to the heated wind temperature */ + float hot_wind_internal_energy; + + /*! Whether the firehose wind model is on */ + char use_firehose_model; + + /*! Early stellar feedback alpha value from Keller et al 2022 */ + float early_stellar_feedback_alpha; + + /*! Early stellar feedback term for SF efficiency Keller et al 2022 */ + float early_stellar_feedback_epsterm; + + /*! Early stellar feedback t_fb from Keller et al 2022 */ + float early_stellar_feedback_tfb; + + /*! Early stellar feedback t_fb inverse */ + float early_stellar_feedback_tfb_inv; + + /*! Maximum mass growth factor from stellar feedback for a particle */ + float max_mass_increase_factor; + + /*! Maximum internal energy growth factor from stellar feedback to particle */ + float max_energy_increase_factor; + + /*! Minimum internal energy loss factor from momentum exchange */ + // float min_energy_decrease_factor; + + /*! Flag to add heat to destroy cold gas in the ISM from SNIa gas */ + int SNIa_add_heat_to_ISM; + + /*! Avoid floating point errors when comparing internal energies in the ISM */ + float SNIa_add_heat_to_ISM_tolerance; + + /* ------------ Chem5 Default Parameters --------------- */ + + /*! Which IMF? Kroupa=0, Chabrier=1, Else=2 */ + int imf; + + /*! Solar H */ + double H_mf; + + /*! Solar He */ + double He_mf; + + /*! Solar Z */ + double Z_mf; + + /*! Solar O */ + double O_mf; + + /*! Solar Fe */ + double Fe_mf; + + /*! IMF parameter */ + double ximf; + + /*! Upper limit for IMF integration */ + double M_u; + + /*! Lower limit for IMF integration */ + double M_l; + + /*! IMF parameter */ + double ximf3; + + /*! >= M_u */ + double M_u3; + + /*! >= M_l */ + double M_l3; + + /*! If set greater than zero, activates Pop3 stars */ + double zmax3; + + /*! Upper limit on IMF integration */ + double M_u2; + + /*! Lower limit on IMF integration */ + double M_l2; + + /*! binary parameter for SNIa */ + double b_rg; + + /*! binary parameter for SNIa */ + double b_ms; + + /*! Energy in supernova (Ia) */ + double E_sn1; + + /*! Energy in supernova (II) */ + double E_sw; + +#if COOLING_GRACKLE_MODE >= 2 + /* ------------ Dust Efficiency Tables --------------- */ + + /*! dust condensation efficiency for C/O>1 */ + double delta_AGBCOG1[chemistry_element_count]; + + /*! dust condensation efficiency for C/O<1 */ + double delta_AGBCOL1[chemistry_element_count]; + + /*! dust condensation efficiency from SNII */ + double delta_SNII[chemistry_element_count]; + + /*! max fraction of metals locked into dust */ + float max_dust_fraction; + + /*! Rolling value for number of SNe is smoothed over this timescale + * in Myr (0 for instantaneous) */ + float SNe_smoothing_time_in_Myr; +#endif + + /*! chem5 metal yield multiplier */ + float metal_yield_multiplier; +}; + +void feedback_props_init(struct feedback_props *fp, + const struct phys_const *phys_const, + const struct unit_system *us, + struct swift_params *params, + const struct hydro_props *hydro_props, + const struct cosmology *cosmo); + +#endif /* SWIFT_KIARA_FEEDBACK_PROPERTIES_H */ diff --git a/src/feedback/KIARA/feedback_struct.h b/src/feedback/KIARA/feedback_struct.h new file mode 100644 index 0000000000..168917009d --- /dev/null +++ b/src/feedback/KIARA/feedback_struct.h @@ -0,0 +1,127 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2022 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_FEEDBACK_STRUCT_KIARA_H +#define SWIFT_FEEDBACK_STRUCT_KIARA_H + +#include "chemistry_struct.h" + +/** + * @brief Feedback fields carried by each hydro particles + */ +struct feedback_part_data { + /*! remaining time left for decoupling */ + float decoupling_delay_time; + + /*! Number of times decoupled */ + int number_of_times_decoupled; + + /*! The time to shut off cooling for this particle */ + float cooling_shutoff_delay_time; + + /*! The ID of the star particle that is kicking this particle */ + long long kick_id; + + /*! The direction vector for wind kicks */ + float wind_direction[3]; + + /*! The number of times the SF mass limiter was applied */ + int mass_limiter_count; + + /*! The number of times the SF heat limiter was applied */ + int heating_limiter_count; + +#if COOLING_GRACKLE_MODE >= 2 + /*! Number of SNe (of any type) going off in nearby stars */ + float SNe_ThisTimeStep; +#endif +}; + +/** + * @brief Extra feedback fields carried by each hydro particles + */ +struct feedback_xpart_data {}; + +/** + * @brief Feedback fields carried by each star particles + */ +struct feedback_spart_data { + + /*! Normalisation factor used for the enrichment */ + float kernel_wt_sum; + + /*! Normalisation factor used for the kicking */ + float wind_wt_sum; + + /*! Total mass (unweighted) of neighbouring gas particles */ + float ngb_mass; + + /*! Total mass (unweighted) of neighbouring gas particles eligible for wind */ + float wind_ngb_mass; + + /*! Mass released */ + double mass; + + /*! Total metal mass released */ + double total_metal_mass; + + /*! Total mass released by each element */ + double metal_mass[chemistry_element_count]; + + /*! Energy change due to thermal and kinetic energy of ejecta */ + double energy; + + /*! Cumulative SNII energy available to launch wind */ + double physical_energy_reservoir; + + /*! Number of particles launched over the stars' lifetime */ + int N_launched; + + /*! Total mass left to be ejected in winds by this star */ + float mass_to_launch; + + /*! Total mass kicked over the stars' lifetime */ + float total_mass_kicked; + + /*! Kick velocity for gas launched by this star COMOVING */ + float wind_velocity; + + /*! The factor to multiply the wind_mass to prevent galaxy destruction */ + float eta_suppression_factor; + +#if COOLING_GRACKLE_MODE >= 2 + /*! Luminosity emitted by star in Habing band (912-1112 A) */ + float lum_habing; + + /*! Number of SNe (of any type) going off within star during this step */ + double SNe_ThisTimeStep; + + /*! Cumulative number of SNe that have gone off in this star from chem5 (for + * debugging) */ + double SNe_Total; + + /*! Total dust mass change for each element */ + double delta_dust_mass[chemistry_element_count]; +#endif + + /*! Initial stream radius for firehose model */ + float firehose_radius_stream; +}; + +#endif /* SWIFT_FEEDBACK_STRUCT_KIARA_H */ diff --git a/src/feedback/none/feedback.h b/src/feedback/none/feedback.h index 26ddb3f3b0..6be3d876d6 100644 --- a/src/feedback/none/feedback.h +++ b/src/feedback/none/feedback.h @@ -50,6 +50,11 @@ feedback_compute_spart_timestep( return FLT_MAX; } +__attribute__((always_inline)) INLINE static void feedback_recouple_part( + struct part *p, struct xpart *xp, const struct engine *e, + const int with_cosmology, const struct cosmology *cosmo, + const struct feedback_props *fb_props) {} + /** * @brief Update the properties of a particle fue to feedback effects after * the cooling was applied. @@ -155,6 +160,19 @@ __attribute__((always_inline)) INLINE static void feedback_reset_feedback( __attribute__((always_inline)) INLINE static void feedback_first_init_spart( struct spart *sp, const struct feedback_props *feedback_props) {} +/** + * @brief Initialises the particles for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions or assignments between the particle + * and extended particle fields. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + */ +__attribute__((always_inline)) INLINE static void feedback_first_init_part( + struct part *restrict p, struct xpart *restrict xp) {} + /** * @brief Initialises the s-particles feedback props for the first time * diff --git a/src/feedback_debug.h b/src/feedback_debug.h index ba76463099..baa8ab3d85 100644 --- a/src/feedback_debug.h +++ b/src/feedback_debug.h @@ -33,6 +33,8 @@ #include "./feedback/GEAR/feedback_debug.h" #elif defined(FEEDBACK_AGORA) #include "./feedback/AGORA/feedback_debug.h" +#elif defined(FEEDBACK_KIARA) +#include "./feedback/KIARA/feedback_debug.h" #else #error "Invalid choice of feedback model" #endif diff --git a/src/feedback_iact.h b/src/feedback_iact.h index 91ec58619f..8ea0fe5c32 100644 --- a/src/feedback_iact.h +++ b/src/feedback_iact.h @@ -33,6 +33,8 @@ #include "./feedback/GEAR/feedback_iact.h" #elif defined(FEEDBACK_AGORA) #include "./feedback/AGORA/feedback_iact.h" +#elif defined(FEEDBACK_KIARA) +#include "./feedback/KIARA/feedback_iact.h" #else #error "Invalid choice of feedback model" #endif diff --git a/src/feedback_new_stars.h b/src/feedback_new_stars.h index da012c646a..fe559d0a6a 100644 --- a/src/feedback_new_stars.h +++ b/src/feedback_new_stars.h @@ -34,6 +34,8 @@ #define feedback_use_newborn_stars 1 #elif defined(FEEDBACK_AGORA) #define feedback_use_newborn_stars 1 +#elif defined(FEEDBACK_KIARA) +#define feedback_use_newborn_stars 0 #else #error "Invalid choice of feedback model" #endif diff --git a/src/feedback_properties.h b/src/feedback_properties.h index 93504ff6aa..cb346f9a38 100644 --- a/src/feedback_properties.h +++ b/src/feedback_properties.h @@ -33,6 +33,8 @@ #include "./feedback/GEAR/feedback_properties.h" #elif defined(FEEDBACK_AGORA) #include "./feedback/AGORA/feedback_properties.h" +#elif defined(FEEDBACK_KIARA) +#include "./feedback/KIARA/feedback_properties.h" #else #error "Invalid choice of feedback model" #endif diff --git a/src/feedback_struct.h b/src/feedback_struct.h index 063a657095..a776773911 100644 --- a/src/feedback_struct.h +++ b/src/feedback_struct.h @@ -38,6 +38,8 @@ #include "./feedback/GEAR/feedback_struct.h" #elif defined(FEEDBACK_AGORA) #include "./feedback/AGORA/feedback_struct.h" +#elif defined(FEEDBACK_KIARA) +#include "./feedback/KIARA/feedback_struct.h" #else #error "Invalid choice of feedback function." #endif diff --git a/src/fof.c b/src/fof.c index 9489b6a1c6..60d605fade 100644 --- a/src/fof.c +++ b/src/fof.c @@ -2673,7 +2673,12 @@ void fof_calc_group_mass(struct fof_props *props, const struct space *s, centre_of_mass[index * 3 + 2] += gparts[i].mass * x[2]; /* Should we seed a BH in this group? */ - if (!has_black_hole[index] && group_mass[index] > seed_halo_mass) { + float group_mass_for_seeding = group_mass[index]; +#ifdef WITH_FOF_GALAXIES + /* In Kiara this is actually a limit on stellar mass not halo mass */ + group_mass_for_seeding = stellar_mass[index]; +#endif + if (!has_black_hole[index] && group_mass_for_seeding > seed_halo_mass) { /* Is this a gas particle? */ if (gparts[i].type == swift_type_gas) { @@ -2752,7 +2757,12 @@ void fof_calc_group_mass(struct fof_props *props, const struct space *s, radii[index] = fmaxf(radii[index], r); /* Should we seed a BH in this group? */ - if (!has_black_hole[index] && group_mass[index] > seed_halo_mass) { + float group_mass_for_seeding = group_mass[index]; +#ifdef WITH_FOF_GALAXIES + /* In Kiara this is actually a limit on stellar mass not halo mass */ + group_mass_for_seeding = stellar_mass[index]; +#endif + if (!has_black_hole[index] && group_mass_for_seeding > seed_halo_mass) { /* Is this a gas particle? */ if (gparts[i].type == swift_type_gas) { @@ -2763,6 +2773,33 @@ void fof_calc_group_mass(struct fof_props *props, const struct space *s, (*number_of_local_seeds)++; } } + +#ifdef WITH_FOF_GALAXIES + /* Get a handle on the gpart. */ + const struct gpart *restrict gp = &gparts[i]; + + /* Load FoF data into particles of various types */ + if (gp->type == swift_type_gas) { + struct part *restrict p = &(s->parts[-gp->id_or_neg_offset]); + p->galaxy_data.stellar_mass = stellar_mass[index]; + p->galaxy_data.gas_mass = gas_mass[index]; + if (stellar_mass[index] > 0.f) p->galaxy_data.specific_sfr = + star_formation_rate[index] / stellar_mass[index]; + } else if (gp->type == swift_type_stars) { + struct spart *restrict sp = &(s->sparts[-gp->id_or_neg_offset]); + sp->galaxy_data.stellar_mass = stellar_mass[index]; + sp->galaxy_data.gas_mass = gas_mass[index]; + if (stellar_mass[index] > 0.f) sp->galaxy_data.specific_sfr = + star_formation_rate[index] / stellar_mass[index]; + } else if (gp->type == swift_type_black_hole) { + struct bpart *restrict bp = &(s->bparts[-gp->id_or_neg_offset]); + bp->galaxy_data.stellar_mass = stellar_mass[index]; + bp->galaxy_data.gas_mass = gas_mass[index]; + if (stellar_mass[index] > 0.f) bp->galaxy_data.specific_sfr = + star_formation_rate[index] / stellar_mass[index]; + bp->galactocentric_radius = r; + } +#endif } #ifdef WITH_MPI @@ -2831,6 +2868,9 @@ void fof_seed_black_holes(const struct fof_props *props, /* Direct pointers to the arrays */ double *group_mass = props->group_mass; +#ifdef WITH_FOF_GALAXIES + float *stellar_mass = props->group_stellar_mass; +#endif char *has_black_hole = props->has_black_hole; long long *id_gas_particle_to_convert = props->id_gas_particle_to_convert; @@ -2858,7 +2898,11 @@ void fof_seed_black_holes(const struct fof_props *props, const size_t index = gparts[i].fof_data.group_id - 1; /* Should we seed a BH in this group? */ - if (!has_black_hole[index] && group_mass[index] > seed_halo_mass) { + float group_mass_for_seeding = group_mass[index]; +#ifdef WITH_FOF_GALAXIES + group_mass_for_seeding = stellar_mass[index]; +#endif + if (!has_black_hole[index] && group_mass_for_seeding > seed_halo_mass) { /* Does it match the max density for this group? * (i.e. is it the particle we identified as the one to convert?) */ diff --git a/src/fof_struct.h b/src/fof_struct.h index ea788b5bf6..a1271d7c18 100644 --- a/src/fof_struct.h +++ b/src/fof_struct.h @@ -36,6 +36,24 @@ struct fof_gpart_data { size_t group_size; }; +#ifdef WITH_FOF_GALAXIES +/** + * @brief Particle-carried fields for the FoF galaxies scheme (e.g. KIARA). + */ +struct fof_galaxy_data { + + /*! Host galaxy stellar mass */ + float stellar_mass; + + /*! Host galaxy gas mass */ + float gas_mass; + + /*! Host galaxy specific star formation rate = SFR/M* */ + float specific_sfr; +}; + +#endif + #else /** diff --git a/src/forcing/roberts_flow_acceleration/forcing.h b/src/forcing/roberts_flow_acceleration/forcing.h index df1882e170..586360611a 100644 --- a/src/forcing/roberts_flow_acceleration/forcing.h +++ b/src/forcing/roberts_flow_acceleration/forcing.h @@ -87,8 +87,13 @@ __attribute__((always_inline)) INLINE static void forcing_hydro_terms_apply( /* Effective viscosity from artificial viscosity, as in eq. 100 from * arXiv:1012.1885 */ +#ifndef MAGMA2_SPH const float nu = terms->nu * p->viscosity.alpha * c_s * p->h / (2.f * (hydro_dimension + 2.f)); +#else + const float nu = terms->nu * p->viscosity.alpha * c_s * p->h / + (2.f * (hydro_dimension + 2.f)); +#endif const float Vz_factor = terms->Vz_factor; const double k0 = (2. * M_PI / L) * terms->kv; diff --git a/src/fvpm_geometry.h b/src/fvpm_geometry.h index 847e241501..e14bf1017d 100644 --- a/src/fvpm_geometry.h +++ b/src/fvpm_geometry.h @@ -23,7 +23,7 @@ #include /* Import the right FVPM geometry functions */ -#if defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH) || defined(RT_GEAR) +#if defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH) || defined(RT_GEAR) || defined(RT_KIARA) #include "./fvpm_geometry/Gizmo/fvpm_geometry.h" #else #include "./fvpm_geometry/None/fvpm_geometry.h" diff --git a/src/fvpm_geometry/Gizmo/fvpm_geometry.h b/src/fvpm_geometry/Gizmo/fvpm_geometry.h index 7ae5d47719..f7d0ffdf00 100644 --- a/src/fvpm_geometry/Gizmo/fvpm_geometry.h +++ b/src/fvpm_geometry/Gizmo/fvpm_geometry.h @@ -24,7 +24,7 @@ #error "Combining GIZMO MFM and GEAR-RT not implemented yet." #endif -#if defined(GIZMO_MFV_SPH) || defined(RT_GEAR) +#if defined(GIZMO_MFV_SPH) || defined(RT_GEAR) || defined(RT_KIARA) #include "./MFV/fvpm_geometry.h" #elif defined(GIZMO_MFM_SPH) #include "./MFM/fvpm_geometry.h" diff --git a/src/fvpm_geometry_struct.h b/src/fvpm_geometry_struct.h index 9cd32b7846..9de267f021 100644 --- a/src/fvpm_geometry_struct.h +++ b/src/fvpm_geometry_struct.h @@ -23,7 +23,7 @@ #include /* Import the right geometry struct definition */ -#if defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH) || defined(RT_GEAR) +#if defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH) || defined(RT_GEAR) || defined(RT_KIARA) #include "./fvpm_geometry/Gizmo/fvpm_geometry_struct.h" #else #include "./fvpm_geometry/None/fvpm_geometry_struct.h" diff --git a/src/hydro.h b/src/hydro.h index c82c402e81..8f7b07e554 100644 --- a/src/hydro.h +++ b/src/hydro.h @@ -82,6 +82,10 @@ #include "./hydro/Gasoline/hydro.h" #include "./hydro/Gasoline/hydro_iact.h" #define SPH_IMPLEMENTATION "Gasoline-2 (Wadsley+ 2017)" +#elif defined(MAGMA2_SPH) +#include "./hydro/MAGMA2/hydro.h" +#include "./hydro/MAGMA2/hydro_iact.h" +#define SPH_IMPLEMENTATION "MAGMA2 (Rosswog 2020)" #elif defined(ANARCHY_PU_SPH) #include "./hydro/AnarchyPU/hydro.h" #include "./hydro/AnarchyPU/hydro_iact.h" diff --git a/src/hydro/MAGMA2/hydro.h b/src/hydro/MAGMA2/hydro.h new file mode 100644 index 0000000000..dbc93b162b --- /dev/null +++ b/src/hydro/MAGMA2/hydro.h @@ -0,0 +1,2359 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk) & + * Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2025 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_MAGMA2_HYDRO_H +#define SWIFT_MAGMA2_HYDRO_H + +/** + * @file MAGMA2/hydro.h + * @brief Density-Energy non-conservative implementation of SPH, + * with added MAGMA2 physics (Rosswog 2020) (Non-neighbour loop + * equations) + */ + +#include "adiabatic_index.h" +#include "approx_math.h" +#include "cosmology.h" +#include "dimension.h" +#include "entropy_floor.h" +#include "equation_of_state.h" +#include "fvpm_geometry.h" +#include "hydro_parameters.h" +#include "hydro_properties.h" +#include "hydro_space.h" +#include "kernel_hydro.h" +#include "minmax.h" +#include "pressure_floor.h" + +#include +#include +#include +#include + +/** + * @brief Returns the comoving internal energy of a particle at the last + * time the particle was kicked. + * + * For implementations where the main thermodynamic variable + * is not internal energy, this function computes the internal + * energy from the thermodynamic variable. + * + * @param p The particle of interest + * @param xp The extended data of the particle of interest. + */ +__attribute__((always_inline)) INLINE static float +hydro_get_comoving_internal_energy(const struct part *restrict p, + const struct xpart *restrict xp) { + return xp->u_full; +} + +/** + * @brief Returns the physical internal energy of a particle at the last + * time the particle was kicked. + * + * For implementations where the main thermodynamic variable + * is not internal energy, this function computes the internal + * energy from the thermodynamic variable and converts it to + * physical coordinates. + * + * @param p The particle of interest. + * @param xp The extended data of the particle of interest. + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float +hydro_get_physical_internal_energy(const struct part *restrict p, + const struct xpart *restrict xp, + const struct cosmology *cosmo) { + return xp->u_full * cosmo->a_factor_internal_energy; +} + +/** + * @brief Returns the comoving internal energy of a particle drifted to the + * current time. + * + * @param p The particle of interest + */ +__attribute__((always_inline)) INLINE static float +hydro_get_drifted_comoving_internal_energy(const struct part *restrict p) { + return p->u; +} + +/** + * @brief Returns the physical internal energy of a particle drifted to the + * current time. + * + * @param p The particle of interest. + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float +hydro_get_drifted_physical_internal_energy(const struct part *restrict p, + const struct cosmology *cosmo) { + return p->u * cosmo->a_factor_internal_energy; +} + +/** + * @brief Returns the comoving pressure of a particle + * + * Computes the pressure based on the particle's properties. + * + * @param p The particle of interest + */ +__attribute__((always_inline)) INLINE static float hydro_get_comoving_pressure( + const struct part *restrict p) { + return gas_pressure_from_internal_energy(p->rho, p->u); +} + +/** + * @brief Returns the physical pressure of a particle + * + * Computes the pressure based on the particle's properties and + * convert it to physical coordinates. + * + * @param p The particle of interest + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float hydro_get_physical_pressure( + const struct part *restrict p, const struct cosmology *cosmo) { + return cosmo->a_factor_pressure * hydro_get_comoving_pressure(p); +} + +/** + * @brief Returns the comoving entropy of a particle at the last + * time the particle was kicked. + * + * For implementations where the main thermodynamic variable + * is not entropy, this function computes the entropy from + * the thermodynamic variable. + * + * @param p The particle of interest + * @param xp The extended data of the particle of interest. + */ +__attribute__((always_inline)) INLINE static float hydro_get_comoving_entropy( + const struct part *restrict p, const struct xpart *restrict xp) { + return gas_entropy_from_internal_energy(p->rho, xp->u_full); +} + +/** + * @brief Returns the physical entropy of a particle at the last + * time the particle was kicked. + * + * For implementations where the main thermodynamic variable + * is not entropy, this function computes the entropy from + * the thermodynamic variable and converts it to + * physical coordinates. + * + * @param p The particle of interest. + * @param xp The extended data of the particle of interest. + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float hydro_get_physical_entropy( + const struct part *restrict p, const struct xpart *restrict xp, + const struct cosmology *cosmo) { + /* Note: no cosmological conversion required here with our choice of + * coordinates. */ + return gas_entropy_from_internal_energy(p->rho, xp->u_full); +} + +/** + * @brief Returns the comoving entropy of a particle drifted to the + * current time. + * + * @param p The particle of interest. + */ +__attribute__((always_inline)) INLINE static float +hydro_get_drifted_comoving_entropy(const struct part *restrict p) { + return gas_entropy_from_internal_energy(p->rho, p->u); +} + +/** + * @brief Returns the physical entropy of a particle drifted to the + * current time. + * + * @param p The particle of interest. + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float +hydro_get_drifted_physical_entropy(const struct part *restrict p, + const struct cosmology *cosmo) { + /* Note: no cosmological conversion required here with our choice of + * coordinates. */ + return gas_entropy_from_internal_energy(p->rho, p->u); +} + +/** + * @brief Returns the comoving sound speed of a particle + * + * @param p The particle of interest + */ +__attribute__((always_inline)) INLINE static float +hydro_get_comoving_soundspeed(const struct part *restrict p) { + return gas_soundspeed_from_internal_energy(p->rho, p->u); +} + +/** + * @brief Returns the physical sound speed of a particle + * + * @param p The particle of interest + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float +hydro_get_physical_soundspeed(const struct part *restrict p, + const struct cosmology *cosmo) { + return cosmo->a_factor_sound_speed * hydro_get_comoving_soundspeed(p); +} + +/** + * @brief Returns the comoving density of a particle + * + * @param p The particle of interest + */ +__attribute__((always_inline)) INLINE static float hydro_get_comoving_density( + const struct part *restrict p) { + return p->rho; +} + +/** + * @brief Returns the comoving density of a particle. + * + * @param p The particle of interest + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float hydro_get_physical_density( + const struct part *restrict p, const struct cosmology *cosmo) { + return cosmo->a3_inv * p->rho; +} + +/** + * @brief Returns the mass of a particle + * + * @param p The particle of interest + */ +__attribute__((always_inline)) INLINE static float hydro_get_mass( + const struct part *restrict p) { + return p->mass; +} + +/** + * @brief Sets the mass of a particle + * + * @param p The particle of interest + * @param m The mass to set. + */ +__attribute__((always_inline)) INLINE static void hydro_set_mass( + struct part *restrict p, float m) { + p->mass = m; +} + +/** + * @brief Returns the time derivative of internal energy of a particle + * + * We assume a constant density. + * + * @param p The particle of interest + */ +__attribute__((always_inline)) INLINE static float +hydro_get_comoving_internal_energy_dt(const struct part *restrict p) { + return p->u_dt; +} + +/** + * @brief Returns the time derivative of internal energy of a particle + * + * We assume a constant density. + * + * @param p The particle of interest + * @param cosmo Cosmology data structure + */ +__attribute__((always_inline)) INLINE static float +hydro_get_physical_internal_energy_dt(const struct part *restrict p, + const struct cosmology *cosmo) { + return p->u_dt * cosmo->a_factor_internal_energy; +} + +/** + * @brief Sets the time derivative of internal energy of a particle + * + * We assume a constant density. + * + * @param p The particle of interest. + * @param du_dt The new time derivative of the internal energy. + */ +__attribute__((always_inline)) INLINE static void +hydro_set_comoving_internal_energy_dt(struct part *restrict p, float du_dt) { + p->u_dt = du_dt; +} + +/** + * @brief Returns the time derivative of internal energy of a particle + * + * We assume a constant density. + * + * @param p The particle of interest. + * @param cosmo Cosmology data structure + * @param du_dt The new time derivative of the internal energy. + */ +__attribute__((always_inline)) INLINE static void +hydro_set_physical_internal_energy_dt(struct part *restrict p, + const struct cosmology *cosmo, + float du_dt) { + p->u_dt = du_dt / cosmo->a_factor_internal_energy; +} + +/** + * @brief Sets the physical entropy of a particle + * + * @param p The particle of interest. + * @param xp The extended particle data. + * @param cosmo Cosmology data structure + * @param entropy The physical entropy + */ +__attribute__((always_inline)) INLINE static void hydro_set_physical_entropy( + struct part *p, struct xpart *xp, const struct cosmology *cosmo, + const float entropy) { + /* Note there is no conversion from physical to comoving entropy */ + const float comoving_entropy = entropy; + xp->u_full = gas_internal_energy_from_entropy(p->rho, comoving_entropy); +} + +/** + * @brief Sets the physical internal energy of a particle + * + * @param p The particle of interest. + * @param xp The extended particle data. + * @param cosmo Cosmology data structure + * @param u The physical internal energy + */ +__attribute__((always_inline)) INLINE static void +hydro_set_physical_internal_energy(struct part *p, struct xpart *xp, + const struct cosmology *cosmo, + const float u) { + xp->u_full = u / cosmo->a_factor_internal_energy; +} + +/** + * @brief Sets the drifted physical internal energy of a particle + * + * @param p The particle of interest. + * @param cosmo Cosmology data structure + * @param pressure_floor The properties of the pressure floor. + * @param u The physical internal energy + */ +__attribute__((always_inline)) INLINE static void +hydro_set_drifted_physical_internal_energy( + struct part *p, const struct cosmology *cosmo, + const struct pressure_floor_props *pressure_floor, const float u) { + /* There is no need to use the floor here as this function is called in the + * feedback, so the new value of the internal energy should be strictly + * higher than the old value. */ + + p->u = u / cosmo->a_factor_internal_energy; + + /* Now recompute the extra quantities */ + + /* Compute the sound speed */ + const float pressure = gas_pressure_from_internal_energy(p->rho, p->u); + const float pressure_including_floor = + pressure_floor_get_comoving_pressure(p, pressure_floor, pressure, cosmo); + const float soundspeed = + gas_soundspeed_from_pressure(p->rho, pressure_including_floor); + + /* Update variables. */ + p->force.soundspeed = soundspeed; + p->force.pressure = pressure_including_floor; + + /* Signal velocity */ + const float v_sig = const_viscosity_alpha_prefactor * soundspeed; + + p->dt_min = min(p->dt_min, p->h_min / v_sig); +} + +/** + * @brief Correct the signal velocity of the particle partaking in + * supernova (kinetic) feedback based on the velocity kick the particle receives + * + * @param p The particle of interest. + * @param cosmo Cosmology data structure + * @param dv_phys The velocity kick received by the particle expressed in + * physical units (note that dv_phys must be positive or equal to zero) + */ +__attribute__((always_inline)) INLINE static void +hydro_set_v_sig_based_on_velocity_kick(struct part *p, + const struct cosmology *cosmo, + const float dv_phys) { + /* Compute the velocity kick in comoving coordinates */ + const float dv = dv_phys / cosmo->a_factor_sound_speed; + + /* Sound speed */ + const float soundspeed = hydro_get_comoving_soundspeed(p); + + /* Signal speed */ + const float v_sig_sound = const_viscosity_alpha_prefactor * soundspeed; + const float v_sig_kick = const_viscosity_beta_prefactor * dv; + const float v_sig = v_sig_sound + v_sig_kick; + + p->dt_min = min(p->dt_min, p->h_min / v_sig); +} + +/** + * @brief Update the value of the viscosity alpha for the scheme. + * + * @param p the particle of interest + * @param alpha the new value for the viscosity coefficient. + */ +__attribute__((always_inline)) INLINE static void hydro_set_viscosity_alpha( + struct part *restrict p, float alpha) {} + +/** + * @brief Update the value of the diffusive coefficients to the + * feedback reset value for the scheme. + * + * @param p the particle of interest + */ +__attribute__((always_inline)) INLINE static void +hydro_diffusive_feedback_reset(struct part *restrict p) { + /* Set the viscosity to the max, and the diffusion to the min */ + hydro_set_viscosity_alpha(p, const_viscosity_alpha); +} + +/** + * @brief Computes the hydro time-step of a given particle + * + * This function returns the time-step of a particle given its hydro-dynamical + * state. A typical time-step calculation would be the use of the CFL condition. + * + * @param p Pointer to the particle data + * @param xp Pointer to the extended particle data + * @param hydro_properties The SPH parameters + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float hydro_compute_timestep( + const struct part *restrict p, const struct xpart *restrict xp, + const struct hydro_props *restrict hydro_properties, + const struct cosmology *restrict cosmo) { + + if (p->dt_min == 0.f || p->decoupled == 1) return FLT_MAX; + + /* Use full kernel support and physical time */ + const float conv = kernel_gamma * cosmo->a / cosmo->a_factor_sound_speed; + + /* CFL condition */ + const float dt_cfl = 2. * hydro_properties->CFL_condition * conv * p->dt_min; + + return dt_cfl; +} + +/** + * @brief Compute the signal velocity between two gas particles + * + * @param dx Comoving vector separating both particles (pi - pj). + * @brief pi The first #part. + * @brief pj The second #part. + * @brief mu_ij The velocity on the axis linking the particles, or zero if the + * particles are moving away from each other, + * @brief beta The non-linear viscosity constant. + */ +__attribute__((always_inline)) INLINE static float hydro_signal_velocity( + const float dx[3], const struct part *restrict pi, + const struct part *restrict pj, const float mu_ij, const float beta) { + + const float ci = pi->force.soundspeed; + const float cj = pj->force.soundspeed; + const float c_ij = 0.5 * (ci + cj); + + const float v_sig_alpha = const_viscosity_alpha_prefactor * c_ij; + const float v_sig_beta = const_viscosity_beta_prefactor * mu_ij; + const float v_sig = v_sig_alpha - fmin(v_sig_beta, 0.f); + + return v_sig; +} + +/** + * @brief Returns the physical velocity divergence. + * + * @brief p The particle + */ +__attribute__((always_inline)) INLINE static float hydro_get_div_v( + const struct part *restrict p) { + + return p->gradients.velocity_tensor[0][0] + + p->gradients.velocity_tensor[1][1] + + p->gradients.velocity_tensor[2][2]; +} + +/** + * @brief Returns the physical velocity divergence. + * + * @brief p The particle + */ +__attribute__((always_inline)) INLINE static float hydro_get_physical_div_v( + const struct part *restrict p, const struct cosmology *cosmo) { + + const float div_v = hydro_get_div_v(p); + return div_v * cosmo->a2_inv + hydro_dimension * cosmo->H; +} + +/** + * @brief Does some extra hydro operations once the actual physical time step + * for the particle is known. + * + * @param p The particle to act upon. + * @param dt Physical time step of the particle during the next step. + */ +__attribute__((always_inline)) INLINE static void hydro_timestep_extra( + struct part *p, float dt) {} + +/** + * @brief Operations performed when a particle gets removed from the + * simulation volume. + * + * @param p The particle. + * @param xp The extended particle data. + * @param time The simulation time. + */ +__attribute__((always_inline)) INLINE static void hydro_remove_part( + const struct part *p, const struct xpart *xp, const double time) {} + +/** + * @brief Prepares a particle for the density calculation. + * + * Zeroes all the relevant arrays in preparation for the sums taking place in + * the various density loop over neighbours. Typically, all fields of the + * density sub-structure of a particle get zeroed in here. + * + * @param p The particle to act upon + * @param hs #hydro_space containing hydro specific space information. + */ +__attribute__((always_inline)) INLINE static void hydro_init_part( + struct part *restrict p, const struct hydro_space *hs) { + p->density.wcount = 0.f; + p->density.wcount_dh = 0.f; + p->rho = 0.f; + p->rho_gradient[0] = 0.f; + p->rho_gradient[1] = 0.f; + p->rho_gradient[2] = 0.f; + p->density.rho_dh = 0.f; + p->density.div_v = 0.f; +#ifdef MAGMA2_DEBUG_CHECKS + p->debug.num_ngb = 0; +#endif + +#ifdef hydro_props_use_adiabatic_correction + p->gradients.adiabatic_f_numerator = 0.; +#endif + + p->gradients.wcount = 0.; + p->gradients.u_well_conditioned = 1; + p->gradients.D_well_conditioned = 1; + + p->gradients.du_min = FLT_MAX; + p->gradients.du_max = -FLT_MAX; + p->gradients.kernel_size = FLT_MIN; + + /* These must be zeroed before the density loop */ + for (int i = 0; i < 3; i++) { + p->gradients.u_aux[i] = 0.; + p->gradients.u_aux_norm[i] = 0.; + + p->gradients.dv_min[i] = FLT_MAX; + p->gradients.dv_max[i] = -FLT_MAX; + + for (int j = 0; j < 3; j++) { + p->gradients.velocity_tensor_aux[i][j] = 0.; + p->gradients.velocity_tensor_aux_norm[i][j] = 0.; + } + } + + /* Init geometry for FVPM Radiative Transfer */ + fvpm_geometry_init(p); +} + +/** + * @brief Computes the condition number of a matrix A + * + * + * @param A The matrix to compute the condition number of. + */ +__attribute__((always_inline)) INLINE static double condition_number( + gsl_matrix *A) { + + gsl_matrix *A_copy = gsl_matrix_alloc(3, 3); + gsl_matrix_memcpy(A_copy, A); + + gsl_vector *S = gsl_vector_alloc(3); + gsl_vector *work = gsl_vector_alloc(3); + gsl_matrix *V = gsl_matrix_alloc(3, 3); + + gsl_linalg_SV_decomp(A_copy, V, S, work); + + double s_max = gsl_vector_get(S, 0); + double s_min = gsl_vector_get(S, 2); + + gsl_matrix_free(A_copy); + gsl_matrix_free(V); + gsl_vector_free(S); + gsl_vector_free(work); + + return (s_min != 0.) ? s_max / s_min : const_condition_number_upper_limit; +} + +/** + * @brief Vector dot product of two 3D vectors. + * + * + * @param vec_a The first vector. + * @param vec_b The second vector. + * @param result The result of the dot product. + */ +__attribute__((always_inline)) INLINE static hydro_real_t hydro_vec3_vec3_dot( + const hydro_real_t *restrict vec_a, const hydro_real_t *restrict vec_b) { + + return vec_a[0] * vec_b[0] + vec_a[1] * vec_b[1] + vec_a[2] * vec_b[2]; +} + +/** + * @brief Norm of two 3D vectors. + * + * + * @param vec The vector. + * @param result The result of the norm. + */ +__attribute__((always_inline)) INLINE static hydro_real_t hydro_vec3_norm( + const hydro_real_t *restrict vec) { + + return sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]); +} + +/** + * @brief Unit vector of the given vector. + * + * + * @param vec The vector. + * @param result The unit vector of vec + */ +__attribute__((always_inline)) INLINE static void hydro_vec3_unit( + const hydro_real_t *restrict vec, hydro_real_t *restrict result) { + + result[0] = 0.; + result[1] = 0.; + result[2] = 0.; + + const hydro_real_t vec_norm = hydro_vec3_norm(vec); + if (vec_norm > 0.) { + result[0] = vec[0] / vec_norm; + result[1] = vec[1] / vec_norm; + result[2] = vec[2] / vec_norm; + } +} + +/** + * @brief The Frobenius inner product of two matrices. + * + * + * @param mat The matrix to contract with the vector. + * @param vec The vector to contract with the matrix. + * @param result The result of the contraction. + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_mat3x3_mat3x3_dot(const hydro_real_t (*restrict mat_a)[3], + const hydro_real_t (*restrict mat_b)[3]) { + + return mat_a[0][0] * mat_b[0][0] + mat_a[0][1] * mat_b[0][1] + + mat_a[0][2] * mat_b[0][2] + mat_a[1][0] * mat_b[1][0] + + mat_a[1][1] * mat_b[1][1] + mat_a[1][2] * mat_b[1][2] + + mat_a[2][0] * mat_b[2][0] + mat_a[2][1] * mat_b[2][1] + + mat_a[2][2] * mat_b[2][2]; +} + +/** + * @brief Contracts the last index of matrix mat with a vector vec and stores in + * result. + * + * + * @param mat The matrix to contract with the vector. + * @param vec The vector to contract with the matrix. + * @param result The result of the contraction. + */ +__attribute__((always_inline)) INLINE static void hydro_mat3x3_vec3_dot( + const hydro_real_t (*restrict mat)[3], const hydro_real_t *restrict vec, + hydro_real_t *restrict result) { + + result[0] = mat[0][0] * vec[0] + mat[0][1] * vec[1] + mat[0][2] * vec[2]; + result[1] = mat[1][0] * vec[0] + mat[1][1] * vec[1] + mat[1][2] * vec[2]; + result[2] = mat[2][0] * vec[0] + mat[2][1] * vec[1] + mat[2][2] * vec[2]; +} + +/** + * @brief Contracts the last two indices of the tensor tensor with a matrix + * mat and stores in result. Form: mat^T * tensor * mat. + * + * + * @param tensor The tensor to contract with the matrix. + * @param mat The matrix to contract with the tensor. + * @param result The result of the contraction. + */ +__attribute__((always_inline)) INLINE static void +hydro_tensor3x3x3_matrix3x3_dot(const hydro_real_t (*restrict tensor)[3][3], + const hydro_real_t (*restrict mat)[3], + hydro_real_t *restrict result) { + + result[0] = tensor[0][0][0] * mat[0][0] + tensor[0][0][1] * mat[0][1] + + tensor[0][0][2] * mat[0][2] + tensor[0][1][0] * mat[1][0] + + tensor[0][1][1] * mat[1][1] + tensor[0][1][2] * mat[1][2] + + tensor[0][2][0] * mat[2][0] + tensor[0][2][1] * mat[2][1] + + tensor[0][2][2] * mat[2][2]; + + result[1] = tensor[1][0][0] * mat[0][0] + tensor[1][0][1] * mat[0][1] + + tensor[1][0][2] * mat[0][2] + tensor[1][1][0] * mat[1][0] + + tensor[1][1][1] * mat[1][1] + tensor[1][1][2] * mat[1][2] + + tensor[1][2][0] * mat[2][0] + tensor[1][2][1] * mat[2][1] + + tensor[1][2][2] * mat[2][2]; + + result[2] = tensor[2][0][0] * mat[0][0] + tensor[2][0][1] * mat[0][1] + + tensor[2][0][2] * mat[0][2] + tensor[2][1][0] * mat[1][0] + + tensor[2][1][1] * mat[1][1] + tensor[2][1][2] * mat[1][2] + + tensor[2][2][0] * mat[2][0] + tensor[2][2][1] * mat[2][1] + + tensor[2][2][2] * mat[2][2]; +} + +/** + * @brief Constructs the outer product of two 3D vectors. + * + * + * @param vec_a The first vector. + * @param vec_b The second vector. + * @param result The result of the outer product. + */ +__attribute__((always_inline)) INLINE static void hydro_vec3_vec3_outer( + const hydro_real_t *restrict vec_a, const hydro_real_t *restrict vec_b, + hydro_real_t (*restrict result)[3]) { + + result[0][0] = vec_a[0] * vec_b[0]; + result[0][1] = vec_a[0] * vec_b[1]; + result[0][2] = vec_a[0] * vec_b[2]; + + result[1][0] = vec_a[1] * vec_b[0]; + result[1][1] = vec_a[1] * vec_b[1]; + result[1][2] = vec_a[1] * vec_b[2]; + + result[2][0] = vec_a[2] * vec_b[0]; + result[2][1] = vec_a[2] * vec_b[1]; + result[2][2] = vec_a[2] * vec_b[2]; +} + +/** + * @brief Limit gradients to variation across the kernel. + * + * + * @param df_raw Raw value + * @param df_reconstructed Reconstructed value + */ +__attribute__((always_inline)) INLINE static hydro_real_t hydro_scalar_minmod( + const hydro_real_t df_raw, const hydro_real_t df_reconstructed) { + + if (df_raw * df_reconstructed <= 0.) return 0.; + + return (fabs(df_raw) < fabs(df_reconstructed)) ? df_raw : df_reconstructed; +} + +/** + * @brief Limit gradients to variation across the kernel. + * + * + * @param df_reconstructed Reconstructed estimate of df + * @param df_raw Particle estimate of df + * @param df_min_i Minimum value of df_raw across the kernel. + * @param df_max_i Maximum value of df_raw across the kernel. + * @param df_min_j Minimum value of df_raw across the kernel. + * @param df_max_j Maximum value of df_raw across the kernel + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_scalar_minmod_limiter(const hydro_real_t df_reconstructed, + const hydro_real_t df_raw, + const hydro_real_t df_min_i, + const hydro_real_t df_max_i, + const hydro_real_t df_min_j, + const hydro_real_t df_max_j) { + +#ifdef hydro_props_use_strict_minmod_limiter + hydro_real_t df = hydro_scalar_minmod(df_raw, df_reconstructed); + + const hydro_real_t lo = fmax(df_min_i, -df_max_j); + const hydro_real_t hi = fmin(df_max_i, -df_min_j); + + if (lo > hi) return df_raw; + + if (df < lo) df = lo; + if (df > hi) df = hi; + + return df; +#else + return df_reconstructed; +#endif +} + +/** + * @brief Limit variations across the kernel (for 3-vectors) + * + * + * @param dv_reconstructed Reconstructed estimate of df + * @param dv_raw Particle estimate of df + * @param dv_min_i Minimum value of df_raw across the kernel. + * @param dv_max_i Maximum value of df_raw across the kernel. + * @param dv_min_j Minimum value of df_raw across the kernel. + * @param dv_max_j Maximum value of df_raw across the kernel + * @param dv_ij The vector to return. + */ +__attribute__((always_inline)) INLINE static void hydro_vec_minmod_limiter( + const hydro_real_t *restrict dv_reconstructed, + const hydro_real_t *restrict dv_raw, const hydro_real_t *restrict dv_min_i, + const hydro_real_t *restrict dv_max_i, + const hydro_real_t *restrict dv_min_j, + const hydro_real_t *restrict dv_max_j, hydro_real_t *restrict dv_ij) { + + dv_ij[0] = + hydro_scalar_minmod_limiter(dv_reconstructed[0], dv_raw[0], dv_min_i[0], + dv_max_i[0], dv_min_j[0], dv_max_j[0]); + dv_ij[1] = + hydro_scalar_minmod_limiter(dv_reconstructed[1], dv_raw[1], dv_min_i[1], + dv_max_i[1], dv_min_j[1], dv_max_j[1]); + dv_ij[2] = + hydro_scalar_minmod_limiter(dv_reconstructed[2], dv_raw[2], dv_min_i[2], + dv_max_i[2], dv_min_j[2], dv_max_j[2]); + + /* If any of the components are exactly zero, we return a zero vector */ + if (dv_ij[0] == 0. || dv_ij[1] == 0. || dv_ij[2] == 0.) { + dv_ij[0] = 0.; + dv_ij[1] = 0.; + dv_ij[2] = 0.; + } +} + +/** + * @brief Limit gradients to variation across the kernel. + * + * + * @param df_min Minimum value of delta f across the kernel. + * @param df_max Maximum value of delta f across the kernel. + * @param kernel_size Interaction distance across the kernel. + * @param grad The vector gradient to slope limit. + */ +__attribute__((always_inline)) INLINE static void hydro_vec_slope_limiter( + const hydro_real_t df_min, const hydro_real_t df_max, + const hydro_real_t kernel_size, hydro_real_t *restrict grad) { + +#ifdef hydro_props_use_extra_slope_limiter + const hydro_real_t grad_norm = hydro_vec3_norm(grad); + const hydro_real_t length = const_grad_overshoot_length * kernel_size; + + /* Nothing to do if there is no gradient or no look-ahead distance */ + if (grad_norm > 0. && length > 0.) { + const hydro_real_t df_min_abs = (df_min < 0.) ? fabs(df_min) : 0.; + const hydro_real_t df_max_abs = (df_max > 0.) ? fabs(df_max) : 0.; + const hydro_real_t df_abs_min = fmin(df_min_abs, df_max_abs); + + hydro_real_t bound = df_abs_min; + +#ifdef const_grad_overshoot_tolerance + const hydro_real_t tolerance = const_grad_overshoot_tolerance; + if (tolerance > 0.) { + const hydro_real_t df_abs_max = fmax(df_min_abs, df_max_abs); + const hydro_real_t extra = tolerance * df_abs_max; + const hydro_real_t cap = + (df_abs_min + extra < df_abs_max) ? df_abs_min + extra : df_abs_max; + bound = cap; + } +#endif + + const hydro_real_t limiter = + (bound > 0.) ? (bound / (length * grad_norm)) : 0.; + + if (limiter < 1.) { + grad[0] *= limiter; + grad[1] *= limiter; + grad[2] *= limiter; + } + } +#endif +} + +/** + * @brief Computes Phi_ab from Rosswog 2020 21 for a field given A. This is the + * van Leer 1974 slope limiting procedure. + * + * + * @param A_ij The ratio of the gradients of the two particles. + * @param eta_i The normed smoothing length of the first particle. + * @param eta_j The normed smoothing length of the second particle. + */ +__attribute__((always_inline)) INLINE static hydro_real_t hydro_van_leer_phi( + const hydro_real_t A_ij, const hydro_real_t eta_i, + const hydro_real_t eta_j) { + + hydro_real_t phi_raw = (4. * A_ij) / ((1. + A_ij) * (1. + A_ij)); + phi_raw = fmin(1., phi_raw); + phi_raw = fmax(0., phi_raw); + + /* η_ab and η_crit damping */ + const hydro_real_t eta_ij = min(eta_i, eta_j); + + hydro_real_t damping = 1.; + if (eta_ij <= const_slope_limiter_eta_crit) { + const hydro_real_t diff = + (eta_ij - const_slope_limiter_eta_crit) / const_slope_limiter_eta_fold; + damping = exp(-diff * diff); + } + + phi_raw *= damping; + + /* Handle any edge cases */ + phi_raw = fmin(1., phi_raw); + phi_raw = fmax(0., phi_raw); + + return phi_raw; +} + +/** + * @brief Computes A_ab from Rosswog 2020 Eq. 22 for a scalar field. + * + * + * @param grad_i The gradient of the quantity for the first particle. + * @param grad_j The gradient of the quantity for the second particle. + * @param dx The distance vector between the two particles ( ri - rj ). + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_scalar_van_leer_A(const hydro_real_t *restrict grad_i, + const hydro_real_t *restrict grad_j, + const hydro_real_t *restrict dx) { + + const hydro_real_t grad_dot_x_i = hydro_vec3_vec3_dot(grad_i, dx); + const hydro_real_t grad_dot_x_j = hydro_vec3_vec3_dot(grad_j, dx); + + /* Fall-back to raw estimates */ + if (grad_dot_x_i * grad_dot_x_j <= 0.) return 0.; + + /* Regularize denominator */ + if (fabs(grad_dot_x_j) < 1.e-10) return 0.; + + const hydro_real_t A_ij = grad_dot_x_i / grad_dot_x_j; + + return A_ij; +} + +/** + * @brief Computes A_ab from Rosswog 2020 Eq. 22 for a vector field. + * + * + * @param grad_i The gradient tensor for the first particle. + * @param grad_j The gradient tensor for the second particle. + * @param dx The distance vector between the two particles ( ri - rj ). + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_vector_van_leer_A(const hydro_real_t (*restrict grad_i)[3], + const hydro_real_t (*restrict grad_j)[3], + const hydro_real_t *restrict dx) { + + hydro_real_t delta_ij[3][3] = {0}; + hydro_vec3_vec3_outer(dx, dx, delta_ij); + + const hydro_real_t grad_dot_x_x_i = hydro_mat3x3_mat3x3_dot(grad_i, delta_ij); + const hydro_real_t grad_dot_x_x_j = hydro_mat3x3_mat3x3_dot(grad_j, delta_ij); + + /* Fall-back to raw estimates */ + if (grad_dot_x_x_i * grad_dot_x_x_j <= 0.) return 0.; + + /* Regularize denominator */ + if (fabs(grad_dot_x_x_j) < 1.e-10) return 0.; + + const hydro_real_t A_ij = grad_dot_x_x_i / grad_dot_x_x_j; + + return A_ij; +} + +/** + * @brief Computes Phi_ab from Rosswog 2020 21 for a scalar field. This is the + * van Leer 1974 slope limiting procedure. + * + * + * @param grad_i The gradient of the quantity for the first particle. + * @param grad_j The gradient of the quantity for the second particle. + * @param dx The distance vector between the two particles ( ri - rj ). + * @param eta_i The normed smoothing length of the first particle. + * @param eta_j The normed smoothing length of the second particle. + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_scalar_van_leer_phi(const hydro_real_t *restrict grad_i, + const hydro_real_t *restrict grad_j, + const hydro_real_t *restrict dx, + const hydro_real_t eta_i, const hydro_real_t eta_j) { + + const hydro_real_t A_ij = hydro_scalar_van_leer_A(grad_i, grad_j, dx); + + return hydro_van_leer_phi(A_ij, eta_i, eta_j); +} + +/** + * @brief Computes Phi_ab from Rosswog 2020 21 for a vector field. This is the + * van Leer 1974 slope limiting procedure. + * + * + * @param grad_i The gradient of the quantity for the first particle. + * @param grad_j The gradient of the quantity for the second particle. + * @param dx The distance vector between the two particles ( ri - rj ). + * @param eta_i The normed smoothing length of the first particle. + * @param eta_j The normed smoothing length of the second particle. + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_vector_van_leer_phi(const hydro_real_t (*restrict grad_i)[3], + const hydro_real_t (*restrict grad_j)[3], + const hydro_real_t *restrict dx, + const hydro_real_t eta_i, const hydro_real_t eta_j) { + + const hydro_real_t A_ij = hydro_vector_van_leer_A(grad_i, grad_j, dx); + + return hydro_van_leer_phi(A_ij, eta_i, eta_j); +} + +/** + * @brief Reconstructs the scalar field at the mid-point between to the + * two particles to second order. + * + * + * @param phi The slope limiter value from the van Leer 1974 scheme. + * @param dx The distance vector between the two particles ( ri - rj ). + * @param f The field at the particle. + * @param grad The gradient of the field at the particle. + * @param hess The Hessian of the field at the particle. + * @param f_reconstructed The reconstructed field at the mid-point (2nd order). + */ +__attribute__((always_inline)) INLINE static void +hydro_scalar_second_order_reconstruction(const hydro_real_t phi, + const hydro_real_t *restrict dx, + const hydro_real_t f, + const hydro_real_t *restrict grad, + const hydro_real_t (*restrict hess)[3], + hydro_real_t *f_reconstructed) { + + /* Midpoint from Equation 17 Rosswog 2020 */ + const hydro_real_t midpoint[3] = {0.5 * dx[0], 0.5 * dx[1], 0.5 * dx[2]}; + hydro_real_t delta_ij[3][3] = {0}; + hydro_vec3_vec3_outer(midpoint, midpoint, delta_ij); + + const hydro_real_t df_dx_dot_r = hydro_vec3_vec3_dot(grad, midpoint); + const hydro_real_t df_dx_dx_dot_r2 = hydro_mat3x3_mat3x3_dot(hess, delta_ij); + + /* Apply limited slope reconstruction */ + *f_reconstructed = f + phi * (df_dx_dot_r + 0.5 * df_dx_dx_dot_r2); +} + +/** + * @brief Reconstructs the vector field at the mid-point between to the + * two particles to second order. + * + * + * @param phi The slope limiter value from the van Leer 1974 scheme. + * @param dx The distance vector between the two particles ( ri - rj ). + * @param f The vector field at the particle. + * @param grad The gradient of the vector field at the particle. + * @param hess The Hessian of the vector field at the particle. + * @param f_reconstructed The reconstructed vector field at the mid-point (2nd + * order). + */ +__attribute__((always_inline)) INLINE static void +hydro_vector_second_order_reconstruction( + const hydro_real_t phi, const hydro_real_t *restrict dx, + const hydro_real_t *restrict f, const hydro_real_t (*restrict grad)[3], + const hydro_real_t (*restrict hess)[3][3], + hydro_real_t *restrict f_reconstructed) { + + /* Midpoint from Equation 17 Rosswog 2020 */ + const hydro_real_t midpoint[3] = {0.5 * dx[0], 0.5 * dx[1], 0.5 * dx[2]}; + hydro_real_t delta_ij[3][3] = {0}; + hydro_vec3_vec3_outer(midpoint, midpoint, delta_ij); + + hydro_real_t df_dx_dot_r[3] = {0}; + hydro_real_t df_dx_dx_dot_r2[3] = {0}; + + hydro_mat3x3_vec3_dot(grad, midpoint, df_dx_dot_r); + hydro_tensor3x3x3_matrix3x3_dot(hess, delta_ij, df_dx_dx_dot_r2); + + /* Apply limited slope reconstruction */ + f_reconstructed[0] = f[0] + phi * (df_dx_dot_r[0] + 0.5 * df_dx_dx_dot_r2[0]); + f_reconstructed[1] = f[1] + phi * (df_dx_dot_r[1] + 0.5 * df_dx_dx_dot_r2[1]); + f_reconstructed[2] = f[2] + phi * (df_dx_dot_r[2] + 0.5 * df_dx_dx_dot_r2[2]); +} + +/** + * @brief Get the balsara limiter for viscosity. + * + * + * @param p Particle p + * @param cosmo The cosmology structure + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_get_balsara_limiter(const struct part *restrict p, + const struct cosmology *cosmo) { + +#ifdef hydro_props_use_balsara_limiter + const hydro_real_t fac_B = cosmo->a_factor_Balsara_eps; + hydro_real_t balsara = 1.; + + /* Can't trust velocity_tensor when having ill-conditioned matrices */ + if (p->gradients.C_well_conditioned && p->gradients.D_well_conditioned) { + const hydro_real_t div_v_phys = hydro_get_physical_div_v(p, cosmo); + + const hydro_real_t curl_v[3] = { + p->gradients.velocity_tensor[2][1] - p->gradients.velocity_tensor[1][2], + p->gradients.velocity_tensor[0][2] - p->gradients.velocity_tensor[2][0], + p->gradients.velocity_tensor[1][0] - + p->gradients.velocity_tensor[0][1]}; + + const hydro_real_t curl_v_norm_phys = + hydro_vec3_norm(curl_v) * cosmo->a2_inv; + const hydro_real_t Balsara_eps = 1.e-4 * fac_B * p->force.soundspeed / p->h; + balsara = + fabs(div_v_phys) / (fabs(div_v_phys) + curl_v_norm_phys + Balsara_eps); + balsara = min(1., balsara); + balsara = max(0., balsara); + } + + return balsara; +#else + return 1.; +#endif +} + +/** + * @brief Computes the average kernel gradient between pi and pj + * + * + * @param pi Particle pi + * @param pj Particle pj + * @param G_i Kernel gradient at pi + * @param G_j Kernel gradient at pj + * @param G_ij Kernel gradient average to fill + */ +__attribute__((always_inline)) INLINE static void +hydro_get_average_kernel_gradient(const struct part *restrict pi, + const struct part *restrict pj, + const hydro_real_t *restrict G_i, + const hydro_real_t *restrict G_j, + hydro_real_t *restrict G_ij) { + +#if (hydro_props_kernel_gradient_weighting == 0) + G_ij[0] = 0.5 * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * (G_i[2] + G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 1) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + G_ij[0] = 0.5 * (f_i * G_i[0] + f_j * G_j[0]); + G_ij[1] = 0.5 * (f_i * G_i[1] + f_j * G_j[1]); + G_ij[2] = 0.5 * (f_i * G_i[2] + f_j * G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 2) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + const hydro_real_t f_ij = 0.5 * (f_i + f_j); + G_ij[0] = 0.5 * f_ij * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * f_ij * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * f_ij * (G_i[2] + G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 3) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + hydro_real_t f_ij = 0.; + const hydro_real_t f_sum = f_i + f_j; + if (f_sum > 0.) f_ij = 2. * f_i * f_j / f_sum; + + G_ij[0] = 0.5 * f_ij * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * f_ij * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * f_ij * (G_i[2] + G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 4) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + const hydro_real_t f_ij = sqrt(f_i * f_j); + G_ij[0] = 0.5 * f_ij * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * f_ij * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * f_ij * (G_i[2] + G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 5) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + const hydro_real_t rho_sum = pi->rho + pj->rho; + const hydro_real_t f_ij = (pi->rho * f_i + pj->rho * f_j) / rho_sum; + G_ij[0] = 0.5 * f_ij * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * f_ij * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * f_ij * (G_i[2] + G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 6) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + const hydro_real_t rho_f_sum = pi->rho * f_i + pj->rho * f_j; + const hydro_real_t f_ij = 2. * pi->rho * f_i * pj->rho * f_j / rho_f_sum; + G_ij[0] = 0.5 * f_ij * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * f_ij * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * f_ij * (G_i[2] + G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 7) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + const hydro_real_t P_sum = pi->force.pressure + pj->force.pressure; + const hydro_real_t f_ij = + (pi->force.pressure * f_i + pj->force.pressure * f_j) / P_sum; + G_ij[0] = 0.5 * f_ij * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * f_ij * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * f_ij * (G_i[2] + G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 8) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + const hydro_real_t P_f_sum = + pi->force.pressure * f_i + pj->force.pressure * f_j; + const hydro_real_t f_ij = + 2. * pi->force.pressure * f_i * pj->force.pressure * f_j / P_f_sum; + G_ij[0] = 0.5 * f_ij * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * f_ij * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * f_ij * (G_i[2] + G_j[2]); +#elif (hydro_props_kernel_gradient_weighting == 9) + const hydro_real_t f_i = pi->force.f; + const hydro_real_t f_j = pj->force.f; + const hydro_real_t mi = hydro_get_mass(pi); + const hydro_real_t mj = hydro_get_mass(pj); + const hydro_real_t rhoi_inv = 1. / hydro_get_comoving_density(pi); + const hydro_real_t rhoj_inv = 1. / hydro_get_comoving_density(pj); + const hydro_real_t Vi = mi * rhoi_inv; + const hydro_real_t Vj = mj * rhoj_inv; + + const hydro_real_t f_ij = 0.5 * (Vi * f_i + Vj * f_j) / (Vi + Vj); + G_ij[0] = 0.5 * f_ij * (G_i[0] + G_j[0]); + G_ij[1] = 0.5 * f_ij * (G_i[1] + G_j[1]); + G_ij[2] = 0.5 * f_ij * (G_i[2] + G_j[2]); +#else + error("Invalid hydro_props_kernel_gradient_weighting value: %d", + hydro_props_kernel_gradient_weighting); +#endif +} + +/** + * @brief Computes the viscosity acceleration and mu_ij terms. + * + * + * @param pi Particle pi + * @param pj Particle pj + * @param dv Second order reconstructed velocity difference + * @param dx Distance vector + * @param r Distance vector norm + * @param fac_mu Cosmological factor for mu_ij + * @param a2_Hubble Hubble flow + * @param mu_i The velocity jump indicator at pi to fill + * @param mu_j The velocity jump indicator at pj to fill + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_get_visc_acc_term(const struct part *restrict pi, + const struct part *restrict pj, + const hydro_real_t *restrict dv, + const hydro_real_t *restrict dx, + const hydro_real_t fac_mu, + const hydro_real_t a2_Hubble) { + + const hydro_real_t rhoi = pi->rho; + const hydro_real_t rhoj = pj->rho; + + const hydro_real_t bi = pi->gradients.balsara; + const hydro_real_t bj = pj->gradients.balsara; + + /* Viscosity direction */ + hydro_real_t dx_hat[3] = {0., 0., 0.}; + hydro_vec3_unit(dx, dx_hat); + + /* Separation and velocity along the gradient vector */ + const hydro_real_t dv_Hubble[3] = {dv[0] + a2_Hubble * dx[0], + dv[1] + a2_Hubble * dx[1], + dv[2] + a2_Hubble * dx[2]}; + const hydro_real_t dv_dot_dx_hat = hydro_vec3_vec3_dot(dv_Hubble, dx_hat); + const hydro_real_t conv = (dv_dot_dx_hat < 0.) ? fac_mu : 0.; + + /* Must be a converging flow */ + if (conv == 0.) return 0.; + + const hydro_real_t mu_ij = conv * dv_dot_dx_hat; + +#ifdef hydro_props_viscosity_weighting_type +#if (hydro_props_viscosity_weighting_type == 0) + + /* Each particle gets its own Q and then weighted by density */ + const hydro_real_t rhoij_inv = 1.0 / (rhoi * rhoj); + + const hydro_real_t q_i_alpha = + -const_viscosity_alpha * pi->force.soundspeed * mu_ij; + const hydro_real_t q_i_beta = const_viscosity_beta * mu_ij * mu_ij; + const hydro_real_t Q_i = rhoi * (q_i_alpha + q_i_beta); + + const hydro_real_t q_j_alpha = + -const_viscosity_alpha * pj->force.soundspeed * mu_ij; + const hydro_real_t q_j_beta = const_viscosity_beta * mu_ij * mu_ij; + const hydro_real_t Q_j = rhoj * (q_j_alpha + q_j_beta); + + return (bi * Q_i + bj * Q_j) * rhoij_inv; + +#elif (hydro_props_viscosity_weighting_type == 1) + + /* Each particle has the same Q but is density weighted */ + const hydro_real_t b_ij = 0.5 * (bi + bj); + const hydro_real_t rhoij_inv = 1. / (rhoi * rhoj); + const hydro_real_t c_ij = 0.5 * (pi->force.soundspeed + pj->force.soundspeed); + const hydro_real_t q_ij_alpha = -const_viscosity_alpha * c_ij * mu_ij; + const hydro_real_t q_ij_beta = const_viscosity_beta * mu_ij * mu_ij; + const hydro_real_t Q_ij = (rhoi + rhoj) * (q_ij_alpha + q_ij_beta); + + return b_ij * Q_ij * rhoij_inv; + +#elif (hydro_props_viscosity_weighting_type == 2) + + /* Particles average symmetrically and arithmetically */ + const hydro_real_t b_ij = 0.5 * (bi + bj); + const hydro_real_t c_ij = 0.5 * (pi->force.soundspeed + pj->force.soundspeed); + const hydro_real_t q_ij_alpha = -const_viscosity_alpha * c_ij * mu_ij; + const hydro_real_t q_ij_beta = const_viscosity_beta * mu_ij * mu_ij; + const hydro_real_t q_ij = 2. * (q_ij_alpha + q_ij_beta) / (rhoi + rhoj); + + return b_ij * q_ij; + +#endif +#else + error( + "Unknown compiled hydro_props_viscosity_weighting_type value: %d\n" + "Valid values are 0, 1, or 2.\n", + (int)hydro_props_viscosity_weighting_type); +#endif +} + +/** + * @brief Gets the signal velocity for the conduction interaction based on + * mu_ij. + * + * + * @param dx The separation vector + * @param pi Particle pi + * @param pj Particle pj + * @param mu_i The velocity jump indicator at pi + * @param mu_j The velocity jump indicator at pj + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_get_cond_signal_velocity(const float *restrict dx, + const struct part *restrict pi, + const struct part *restrict pj, const float mu_i, + const float mu_j, const float beta) { + + const float mu_ij = 0.5f * (mu_i + mu_j); + return hydro_signal_velocity(dx, pi, pj, mu_ij, beta); +} + +/** + * @brief Gets the signal velocity for the viscous interaction based on mu_ij. + * + * + * @param dx The separation vector + * @param pi Particle pi + * @param pj Particle pj + * @param mu_i The velocity jump indicator at pi + * @param mu_j The velocity jump indicator at pj + */ +__attribute__((always_inline)) INLINE static hydro_real_t +hydro_get_visc_signal_velocity(const float *restrict dx, + const struct part *restrict pi, + const struct part *restrict pj, const float mu_i, + const float mu_j, const float beta) { + +#ifdef hydro_props_viscosity_weighting_type +#if (hydro_props_viscosity_weighting_type == 0) + const float dx_ji[3] = {-dx[0], -dx[1], -dx[2]}; + const float v_sig_visc_i = hydro_signal_velocity(dx, pi, pj, mu_i, beta); + const float v_sig_visc_j = hydro_signal_velocity(dx_ji, pj, pi, mu_j, beta); + return 0.5 * (v_sig_visc_i + v_sig_visc_j); +#else + const float mu_ij = 0.5f * (mu_i + mu_j); + return hydro_signal_velocity(dx, pi, pj, mu_ij, beta); +#endif +#else + error("No viscosity weighting type defined at compile time."); +#endif +} + +/** + * @brief Finishes the density calculation. + * + * Multiplies the density and number of neighbours by the appropiate constants + * and add the self-contribution term. + * Additional quantities such as velocity gradients will also get the final + * terms added to them here. + * + * Also adds/multiplies the cosmological terms if need be. + * + * @param p The particle to act upon + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static void hydro_end_density( + struct part *restrict p, const struct cosmology *cosmo) { + /* Some smoothing length multiples. */ + const float h = p->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */ + + /* Final operation on the density (add self-contribution). */ + p->rho += p->mass * kernel_root; + p->density.rho_dh -= hydro_dimension * p->mass * kernel_root; + p->density.wcount += kernel_root; + p->density.wcount_dh -= hydro_dimension * kernel_root; + + /* Finish the calculation by inserting the missing h-factors */ + p->rho *= h_inv_dim; + p->density.rho_dh *= h_inv_dim_plus_one; + p->density.wcount *= h_inv_dim; + p->density.wcount_dh *= h_inv_dim_plus_one; + + /* Finish calculation of the velocity divergence */ + const float rho_inv = 1.f / p->rho; + const float a_inv2 = cosmo->a2_inv; + p->density.div_v *= h_inv_dim_plus_one * rho_inv * a_inv2; + p->density.div_v += cosmo->H * hydro_dimension; +/** +} + + * @brief Prepare a particle for the gradient calculation. + * + * This function is called after the density loop and before the gradient loop. + * + * We use it to set the physical timestep for the particle and to copy the + * actual velocities, which we need to boost our interfaces during the flux + * calculation. We also initialize the variables used for the time step + * calculation. + * + * @param p The particle to act upon. + * @param xp The extended particle data to act upon. + * @param cosmo The cosmological model. + * @param hydro_props Hydrodynamic properties. + * @param pressure_floor The properties of the pressure floor. +__attribute__((always_inline)) INLINE static void hydro_prepare_gradient( + struct part *restrict p, struct xpart *restrict xp, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct pressure_floor_props *pressure_floor) { + + const float h = p->h; + const float h_inv = 1.0f / h; + const float h_inv_dim = pow_dimension(h_inv); + const float h_inv_dim_plus_one = h_inv_dim * h_inv; + */ + + /* Need this for correct dh/dt */ + p->gradients.wcount = p->density.wcount; + + p->gradients.u_aux[0] *= h_inv_dim_plus_one; + p->gradients.u_aux[1] *= h_inv_dim_plus_one; + p->gradients.u_aux[2] *= h_inv_dim_plus_one; + + p->gradients.u_aux_norm[0] *= h_inv_dim_plus_one; + p->gradients.u_aux_norm[1] *= h_inv_dim_plus_one; + p->gradients.u_aux_norm[2] *= h_inv_dim_plus_one; + + if (p->gradients.u_aux_norm[0] != 0. && p->gradients.u_aux_norm[1] != 0. && + p->gradients.u_aux_norm[2] != 0.) { + p->gradients.u_aux[0] /= p->gradients.u_aux_norm[0]; + p->gradients.u_aux[1] /= p->gradients.u_aux_norm[1]; + p->gradients.u_aux[2] /= p->gradients.u_aux_norm[2]; + + hydro_vec_slope_limiter(p->gradients.du_min, p->gradients.du_max, + p->gradients.kernel_size, p->gradients.u_aux); + } else { + p->gradients.u_well_conditioned = 0; +#ifdef MAGMA2_DEBUG_CHECKS + p->debug.u_aux[0] = p->gradients.u_aux[0]; + p->debug.u_aux[1] = p->gradients.u_aux[1]; + p->debug.u_aux[2] = p->gradients.u_aux[2]; + + p->debug.u_aux_norm[0] = p->gradients.u_aux_norm[0]; + p->debug.u_aux_norm[1] = p->gradients.u_aux_norm[1]; + p->debug.u_aux_norm[2] = p->gradients.u_aux_norm[2]; + + p->debug.u_ill_conditioned_count++; +#endif + + p->gradients.u_aux[0] = 0.; + p->gradients.u_aux[1] = 0.; + p->gradients.u_aux[2] = 0.; + + p->gradients.u_aux_norm[0] = 0.; + p->gradients.u_aux_norm[1] = 0.; + p->gradients.u_aux_norm[2] = 0.; + } + + double aux_norm[3][3] = {0}; + for (int i = 0; i < 3; i++) { + p->gradients.velocity_tensor_aux[i][0] *= h_inv_dim_plus_one; + p->gradients.velocity_tensor_aux[i][1] *= h_inv_dim_plus_one; + p->gradients.velocity_tensor_aux[i][2] *= h_inv_dim_plus_one; + p->gradients.velocity_tensor_aux_norm[i][0] *= h_inv_dim_plus_one; + p->gradients.velocity_tensor_aux_norm[i][1] *= h_inv_dim_plus_one; + p->gradients.velocity_tensor_aux_norm[i][2] *= h_inv_dim_plus_one; + aux_norm[i][0] = p->gradients.velocity_tensor_aux_norm[i][0]; + aux_norm[i][1] = p->gradients.velocity_tensor_aux_norm[i][1]; + aux_norm[i][2] = p->gradients.velocity_tensor_aux_norm[i][2]; + } + + /* Invert the aux_norm matrix */ + gsl_matrix_view A_view = gsl_matrix_view_array((double *)aux_norm, 3, 3); + gsl_matrix *A = &A_view.matrix; + + double cond = condition_number(A); + if (cond < const_condition_number_upper_limit) { + gsl_matrix *A_inv = gsl_matrix_alloc(3, 3); + gsl_permutation *p_perm = gsl_permutation_alloc(3); + int signum; + + gsl_linalg_LU_decomp(A, p_perm, &signum); + gsl_linalg_LU_invert(A, p_perm, A_inv); + + for (int i = 0; i < 3; i++) { + p->gradients.velocity_tensor_aux_norm[i][0] = gsl_matrix_get(A_inv, i, 0); + p->gradients.velocity_tensor_aux_norm[i][1] = gsl_matrix_get(A_inv, i, 1); + p->gradients.velocity_tensor_aux_norm[i][2] = gsl_matrix_get(A_inv, i, 2); + } + + gsl_matrix_free(A_inv); + gsl_permutation_free(p_perm); + + /* aux_norm is equation 20 in Rosswog 2020, finalize the gradient in 19 */ + hydro_real_t aux_matrix[3][3] = {0}; + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + /** + * The indices of aux_norm and velocity_gradient_aux are important. + * aux_norm j: dx vector direction. + * k: kernel gradient direction + * + * velocity_gradient_aux i: dv vector direction + * k: kernel gradient direction + * + * aux_matrix j: spatial derivative direction + * i: velocity direction + */ + for (int k = 0; k < 3; k++) { + aux_matrix[j][i] += p->gradients.velocity_tensor_aux_norm[j][k] * + p->gradients.velocity_tensor_aux[i][k]; + } + } + } + + /* Copy over the matrices for use later */ + + /* D. Rennehan: For some reason, memcpy does not work here? Could it + * be because of the union in the particle struct? */ + for (int j = 0; j < 3; j++) { + hydro_vec_slope_limiter(p->gradients.dv_min[j], p->gradients.dv_max[j], + p->gradients.kernel_size, aux_matrix[j]); + + p->gradients.velocity_tensor_aux[j][0] = aux_matrix[j][0]; + p->gradients.velocity_tensor_aux[j][1] = aux_matrix[j][1]; + p->gradients.velocity_tensor_aux[j][2] = aux_matrix[j][2]; + } + } else { + + p->gradients.D_well_conditioned = 0; +#ifdef MAGMA2_DEBUG_CHECKS + p->debug.D_ill_conditioned_count++; +#endif + + /* Ensure no crazy gradients later */ + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { +#ifdef MAGMA2_DEBUG_CHECKS + p->debug.velocity_tensor_aux[i][j] = + p->gradients.velocity_tensor_aux[i][j]; + p->debug.velocity_tensor_aux_norm[i][j] = + p->gradients.velocity_tensor_aux_norm[i][j]; +#endif + + p->gradients.velocity_tensor_aux[i][j] = 0.; + p->gradients.velocity_tensor_aux_norm[i][j] = 0.; + } + } + } + + + /* Finish matrix and volume computations for FVPM Radiative Transfer */ + fvpm_compute_volume_and_matrix(p, h_inv_dim); +} + +/** + * @brief Prepare a particle for the gradient calculation. + * + * This function is called after the density loop and before the gradient loop. + * + * We use it to set the physical timestep for the particle and to copy the + * actual velocities, which we need to boost our interfaces during the flux + * calculation. We also initialize the variables used for the time step + * calculation. + * + * @param p The particle to act upon. + * @param xp The extended particle data to act upon. + * @param cosmo The cosmological model. + * @param hydro_props Hydrodynamic properties. + * @param pressure_floor The properties of the pressure floor. + */ +__attribute__((always_inline)) INLINE static void hydro_prepare_gradient( + struct part *restrict p, struct xpart *restrict xp, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct pressure_floor_props *pressure_floor) { + +#ifdef hydro_props_use_adiabatic_correction + /* Prepare the denominator for the adiabatic correction term */ + p->gradients.adiabatic_f_denominator = 0.; +#endif + + /* Compute the sound speed */ + const float pressure = hydro_get_comoving_pressure(p); + const float pressure_including_floor = + pressure_floor_get_comoving_pressure(p, pressure_floor, pressure, cosmo); + const float soundspeed = + gas_soundspeed_from_pressure(p->rho, pressure_including_floor); + + /* Update variables. */ + p->force.pressure = pressure_including_floor; + p->force.soundspeed = soundspeed; + + /* ------ Compute the kernel correction for SPH gradients ------ */ + + /* Compute the "grad h" term */ + const float common_factor = p->h * hydro_dimension_inv / p->density.wcount; + float grad_W_term = -1.f; + + /* Ignore changing-kernel effects when h ~= h_max */ + if (p->h <= 0.9999f * hydro_props->h_max) { + + grad_W_term = common_factor * p->density.wcount_dh; + + if (grad_W_term < -0.9999f) { + /* if we get here, we either had very small neighbour contributions + (which should be treated as a no neighbour case in the ghost) or + a very weird particle distribution (e.g. particles sitting on + top of each other). Either way, we cannot use the normal + expression, since that would lead to overflow or excessive round + off and cause excessively high accelerations in the force loop */ + grad_W_term = -1.f; + warning( + "grad_W_term very small for particle with ID %lld (h: %g, wcount: " + "%g, wcount_dh: %g)", + p->id, p->h, p->density.wcount, p->density.wcount_dh); + } + } + + /* Update variables. */ + p->force.f = (grad_W_term > -1.f) ? 1.f / (1.f + grad_W_term) : 1.f; +} + +/** + * @brief Resets the variables that are required for a gradient calculation. + * + * This function is called after hydro_prepare_gradient. + * + * @param p The particle to act upon. + * @param xp The extended particle data to act upon. + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static void hydro_reset_gradient( + struct part *restrict p) { + + p->rho_gradient[0] = 0.f; + p->rho_gradient[1] = 0.f; + p->rho_gradient[2] = 0.f; + + p->gradients.C_well_conditioned = 1; + + for (int i = 0; i < 3; i++) { + p->gradients.u[i] = 0.; + p->gradients.u_hessian[i][0] = 0.; + p->gradients.u_hessian[i][1] = 0.; + p->gradients.u_hessian[i][2] = 0.; + + p->gradients.correction_matrix[i][0] = 0.; + p->gradients.correction_matrix[i][1] = 0.; + p->gradients.correction_matrix[i][2] = 0.; + + p->gradients.velocity_tensor[i][0] = 0.; + p->gradients.velocity_tensor[i][1] = 0.; + p->gradients.velocity_tensor[i][2] = 0.; + + for (int j = 0; j < 3; j++) { + p->gradients.velocity_hessian[i][j][0] = 0.; + p->gradients.velocity_hessian[i][j][1] = 0.; + p->gradients.velocity_hessian[i][j][2] = 0.; + } + } +} + +/** + * @brief Finishes the gradient calculation. + * + * Just a wrapper around hydro_gradients_finalize, which can be an empty method, + * in which case no gradients are used. + * + * This method also initializes the force loop variables. + * + * @param p The particle to act upon. + */ +__attribute__((always_inline)) INLINE static void hydro_end_gradient( + struct part *p) { + + /* Calculate smoothing length powers */ + const float h = p->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */ + + const hydro_real_t rho_inv = 1. / hydro_get_comoving_density(p); + p->rho_gradient[0] *= h_inv_dim_plus_one * rho_inv; + p->rho_gradient[1] *= h_inv_dim_plus_one * rho_inv; + p->rho_gradient[2] *= h_inv_dim_plus_one * rho_inv; + + p->gradients.u[0] *= h_inv_dim; + p->gradients.u[1] *= h_inv_dim; + p->gradients.u[2] *= h_inv_dim; + + /* Temporary double for GSL */ + double correction_matrix[3][3] = {0}; + + /* Apply correct normalisation */ + for (int k = 0; k < 3; k++) { + p->gradients.correction_matrix[k][0] *= h_inv_dim; + p->gradients.correction_matrix[k][1] *= h_inv_dim; + p->gradients.correction_matrix[k][2] *= h_inv_dim; + + correction_matrix[k][0] = p->gradients.correction_matrix[k][0]; + correction_matrix[k][1] = p->gradients.correction_matrix[k][1]; + correction_matrix[k][2] = p->gradients.correction_matrix[k][2]; + + p->gradients.u_hessian[k][0] *= h_inv_dim; + p->gradients.u_hessian[k][1] *= h_inv_dim; + p->gradients.u_hessian[k][2] *= h_inv_dim; + + p->gradients.velocity_tensor[k][0] *= h_inv_dim; + p->gradients.velocity_tensor[k][1] *= h_inv_dim; + p->gradients.velocity_tensor[k][2] *= h_inv_dim; + + for (int j = 0; j < 3; j++) { + p->gradients.velocity_hessian[k][j][0] *= h_inv_dim; + p->gradients.velocity_hessian[k][j][1] *= h_inv_dim; + p->gradients.velocity_hessian[k][j][2] *= h_inv_dim; + } + } + + /* Invert the p->gradients.correction_matrix[3][3] matrix */ + gsl_matrix_view C_view = + gsl_matrix_view_array((double *)correction_matrix, 3, 3); + gsl_matrix *C = &C_view.matrix; + + const double cond = condition_number(C); + if (cond < const_condition_number_upper_limit) { + gsl_matrix *C_inv = gsl_matrix_alloc(3, 3); + gsl_permutation *p_perm = gsl_permutation_alloc(3); + int signum; + + gsl_linalg_LU_decomp(C, p_perm, &signum); + gsl_linalg_LU_invert(C, p_perm, C_inv); + + for (int k = 0; k < 3; k++) { + p->gradients.correction_matrix[k][0] = gsl_matrix_get(C_inv, k, 0); + p->gradients.correction_matrix[k][1] = gsl_matrix_get(C_inv, k, 1); + p->gradients.correction_matrix[k][2] = gsl_matrix_get(C_inv, k, 2); + } + + gsl_matrix_free(C_inv); + gsl_permutation_free(p_perm); + } else { +#ifdef MAGMA2_DEBUG_CHECKS + for (int k = 0; k < 3; k++) { + p->debug.correction_matrix[k][0] = p->gradients.correction_matrix[k][0]; + p->debug.correction_matrix[k][1] = p->gradients.correction_matrix[k][1]; + p->debug.correction_matrix[k][2] = p->gradients.correction_matrix[k][2]; + } + + p->debug.C_ill_conditioned_count++; +#endif + + /* Ill-condition matrix, revert back to normal SPH gradients */ + p->gradients.C_well_conditioned = 0; + for (int k = 0; k < 3; k++) { + p->gradients.correction_matrix[k][0] = 0.; + p->gradients.correction_matrix[k][1] = 0.; + p->gradients.correction_matrix[k][2] = 0.; + } + } + + /* Contract the correction matrix with the internal energy gradient and with + * the velocity tensor */ + hydro_real_t u_gradient[3] = {0}; + hydro_real_t u_hessian[3][3] = {0}; + hydro_real_t velocity_tensor[3][3] = {0}; + hydro_real_t velocity_hessian[3][3][3] = {0}; + for (int j = 0; j < 3; j++) { + for (int i = 0; i < 3; i++) { + u_gradient[j] += p->gradients.correction_matrix[j][i] * p->gradients.u[i]; + + for (int k = 0; k < 3; k++) { + u_hessian[j][i] += + p->gradients.correction_matrix[j][k] * p->gradients.u_hessian[i][k]; + velocity_tensor[j][i] += p->gradients.correction_matrix[j][k] * + p->gradients.velocity_tensor[i][k]; + + for (int m = 0; m < 3; m++) { + velocity_hessian[j][i][k] += p->gradients.correction_matrix[j][m] * + p->gradients.velocity_hessian[j][i][m]; + } + } + } + } + + /* Slope limiter for internal energy */ + hydro_vec_slope_limiter(p->gradients.du_min, p->gradients.du_max, + p->gradients.kernel_size, u_gradient); + + /* Copy back over to the particle for later */ + p->gradients.u[0] = u_gradient[0]; + p->gradients.u[1] = u_gradient[1]; + p->gradients.u[2] = u_gradient[2]; + + for (int j = 0; j < 3; j++) { + p->gradients.u_hessian[j][0] = u_hessian[j][0]; + p->gradients.u_hessian[j][1] = u_hessian[j][1]; + p->gradients.u_hessian[j][2] = u_hessian[j][2]; + + hydro_vec_slope_limiter(p->gradients.dv_min[j], p->gradients.dv_max[j], + p->gradients.kernel_size, velocity_tensor[j]); + + p->gradients.velocity_tensor[j][0] = velocity_tensor[j][0]; + p->gradients.velocity_tensor[j][1] = velocity_tensor[j][1]; + p->gradients.velocity_tensor[j][2] = velocity_tensor[j][2]; + + p->gradients.velocity_hessian[j][0][0] = velocity_hessian[j][0][0]; + p->gradients.velocity_hessian[j][0][1] = velocity_hessian[j][0][1]; + p->gradients.velocity_hessian[j][0][2] = velocity_hessian[j][0][2]; + + p->gradients.velocity_hessian[j][1][0] = velocity_hessian[j][1][0]; + p->gradients.velocity_hessian[j][1][1] = velocity_hessian[j][1][1]; + p->gradients.velocity_hessian[j][1][2] = velocity_hessian[j][1][2]; + + p->gradients.velocity_hessian[j][2][0] = velocity_hessian[j][2][0]; + p->gradients.velocity_hessian[j][2][1] = velocity_hessian[j][2][1]; + p->gradients.velocity_hessian[j][2][2] = velocity_hessian[j][2][2]; + } +} + +/** + * @brief Sets all particle fields to sensible values when the #part has 0 ngbs. + * + * In the desperate case where a particle has no neighbours (likely because + * of the h_max ceiling), set the particle fields to something sensible to avoid + * NaNs in the next calculations. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static void hydro_part_has_no_neighbours( + struct part *restrict p, struct xpart *restrict xp, + const struct cosmology *cosmo) { + /* Some smoothing length multiples. */ + const float h = p->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + + /* Re-set problematic values */ + p->rho = p->mass * kernel_root * h_inv_dim; + p->rho_gradient[0] = 0.f; + p->rho_gradient[1] = 0.f; + p->rho_gradient[2] = 0.f; + p->h_min = FLT_MAX; + p->dt_min = FLT_MAX; + p->density.wcount = kernel_root * h_inv_dim; + p->density.rho_dh = 0.f; + p->density.wcount_dh = 0.f; + p->density.div_v = 0.f; +#ifdef MAGMA2_DEBUG_CHECKS + p->debug.num_ngb = 0; +#endif + p->gradients.C_well_conditioned = 0; + p->gradients.D_well_conditioned = 0; + p->gradients.u_well_conditioned = 0; + p->gradients.du_min = 0.; + p->gradients.du_max = 0.; + p->gradients.kernel_size = (hydro_real_t)h; + + for (int i = 0; i < 3; i++) { + p->gradients.u[i] = 0.; + p->gradients.u_aux[i] = 0.; + p->gradients.u_aux_norm[i] = 0.; + + p->gradients.dv_min[i] = 0.; + p->gradients.dv_max[i] = 0.; + + for (int j = 0; j < 3; j++) { + p->gradients.correction_matrix[i][j] = 0.; + p->gradients.velocity_tensor[i][j] = 0.; + p->gradients.velocity_tensor_aux[i][j] = 0.; + p->gradients.velocity_tensor_aux_norm[i][j] = 0.; + p->gradients.u_hessian[i][j] = 0.; + + for (int k = 0; k < 3; k++) { + p->gradients.velocity_hessian[i][j][k] = 0.; + } + } + } +} + +/** + * @brief Prepare a particle for the force calculation. + * + * This function is called in the ghost task to convert some quantities coming + * from the density loop over neighbours into quantities ready to be used in the + * force loop over neighbours. Quantities are typically read from the density + * sub-structure and written to the force sub-structure. + * Examples of calculations done here include the calculation of viscosity term + * constants, thermal conduction terms, hydro conversions, etc. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + * @param cosmo The current cosmological model. + * @param hydro_props Hydrodynamic properties. + * @param pressure_floor The properties of the pressure floor. + * @param dt_alpha The time-step used to evolve non-cosmological quantities such + * as the artificial viscosity. + * @param dt_therm The time-step used to evolve hydrodynamical quantities. + */ +__attribute__((always_inline)) INLINE static void hydro_prepare_force( + struct part *restrict p, struct xpart *restrict xp, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct pressure_floor_props *pressure_floor, const float dt_alpha, + const float dt_therm) { + + /* First estimates for the timestepping. Missing the kernel_gamma factors + * for now, but will be added at the end of the force loop. */ + p->h_min = FLT_MAX; + p->dt_min = FLT_MAX; + p->gradients.balsara = hydro_get_balsara_limiter(p, cosmo); + +#ifdef hydro_props_use_adiabatic_correction + const hydro_real_t prev_f = p->force.f; + const hydro_real_t rho_inv = 1. / hydro_get_comoving_density(p); + /* Finish final kernel gradient correction factor */ + if (p->gradients.adiabatic_f_denominator != 0.) { + const hydro_real_t kernel_r2 = p->gradients.adiabatic_f_numerator; + const hydro_real_t weighted_kernel_r2_inv = + 1. / p->gradients.adiabatic_f_denominator; + p->force.f = rho_inv * kernel_r2 * weighted_kernel_r2_inv; + } else { + p->force.f = 1.; + } + +#ifdef MAGMA2_DEBUG_CHECKS + if (p->force.f > 100.) { + warning( + "Final force factor is very high for particle with ID %lld" + " (prev f: %g, f: %g, numerator: %g, denominator: %g, rho_inv: %g)", + p->id, prev_f, p->force.f, p->gradients.adiabatic_f_numerator, + p->gradients.adiabatic_f_denominator, rho_inv); + } +#endif +#endif +} + +/** + * @brief Reset acceleration fields of a particle + * + * Resets all hydro acceleration and time derivative fields in preparation + * for the sums taking place in the various force tasks. + * + * @param p The particle to act upon + */ +__attribute__((always_inline)) INLINE static void hydro_reset_acceleration( + struct part *restrict p) { + /* Reset the acceleration. */ + p->a_hydro[0] = 0.0f; + p->a_hydro[1] = 0.0f; + p->a_hydro[2] = 0.0f; + + /* Reset the time derivatives. */ + p->u_dt = 0.0f; + p->u_dt_cond = 0.0f; + p->force.h_dt = 0.0f; +} + +/** + * @brief Sets the values to be predicted in the drifts to their values at a + * kick time + * + * @param p The particle. + * @param xp The extended data of this particle. + * @param cosmo The cosmological model. + * @param pressure_floor The properties of the pressure floor. + */ +__attribute__((always_inline)) INLINE static void hydro_reset_predicted_values( + struct part *restrict p, const struct xpart *restrict xp, + const struct cosmology *cosmo, + const struct pressure_floor_props *pressure_floor) { + /* Re-set the predicted velocities */ + p->v[0] = xp->v_full[0]; + p->v[1] = xp->v_full[1]; + p->v[2] = xp->v_full[2]; + + /* Re-set the entropy */ + p->u = xp->u_full; + + /* Compute the sound speed */ + const float pressure = gas_pressure_from_internal_energy(p->rho, p->u); + const float pressure_including_floor = + pressure_floor_get_comoving_pressure(p, pressure_floor, pressure, cosmo); + const float soundspeed = + gas_soundspeed_from_pressure(p->rho, pressure_including_floor); + + p->force.pressure = pressure_including_floor; + p->force.soundspeed = soundspeed; + + /* Signal speed */ + const float v_sig = const_viscosity_alpha_prefactor * soundspeed; + + /* Update the signal velocity, if we need to. */ + p->dt_min = min(p->dt_min, p->h_min / v_sig); +} + +/** + * @brief Predict additional particle fields forward in time when drifting + * + * Additional hydrodynamic quantites are drifted forward in time here. These + * include thermal quantities (thermal energy or total energy or entropy, ...). + * + * Note the different time-step sizes used for the different quantities as they + * include cosmological factors. + * + * @param p The particle. + * @param xp The extended data of the particle. + * @param dt_drift The drift time-step for positions. + * @param dt_therm The drift time-step for thermal quantities. + * @param dt_kick_grav The time-step for gravity quantities. + * @param cosmo The cosmological model. + * @param hydro_props The properties of the hydro scheme. + * @param floor_props The properties of the entropy floor. + * @param pressure_floor The properties of the pressure floor. + */ +__attribute__((always_inline)) INLINE static void hydro_predict_extra( + struct part *restrict p, const struct xpart *restrict xp, float dt_drift, + float dt_therm, float dt_kick_grav, const struct cosmology *cosmo, + const struct hydro_props *hydro_props, + const struct entropy_floor_properties *floor_props, + const struct pressure_floor_props *pressure_floor) { + + /* Predict the internal energy */ + p->u += p->u_dt * dt_therm; + + const float h_inv = 1.f / p->h; + + /* Predict smoothing length */ + const float w1 = p->force.h_dt * h_inv * dt_drift; + if (fabsf(w1) < 0.2f) + p->h *= approx_expf(w1); /* 4th order expansion of exp(w) */ + else + p->h *= expf(w1); + + /* Predict density and weighted pressure */ + const float w2 = -hydro_dimension * w1; + if (fabsf(w2) < 0.2f) { + const float expf_approx = + approx_expf(w2); /* 4th order expansion of exp(w) */ + p->rho *= expf_approx; + } else { + const float expf_exact = expf(w2); + p->rho *= expf_exact; + } + + /* Check against entropy floor - explicitly do this after drifting the + * density as this has a density dependence. */ + const float floor_A = entropy_floor(p, cosmo, floor_props); + const float floor_u = gas_internal_energy_from_entropy(p->rho, floor_A); + + /* Check against absolute minimum */ + const float min_u = + hydro_props->minimal_internal_energy / cosmo->a_factor_internal_energy; + + p->u = max(p->u, floor_u); + p->u = max(p->u, min_u); + + /* Compute the new sound speed */ + const float pressure = gas_pressure_from_internal_energy(p->rho, p->u); + const float pressure_including_floor = + pressure_floor_get_comoving_pressure(p, pressure_floor, pressure, cosmo); + const float soundspeed = + gas_soundspeed_from_pressure(p->rho, pressure_including_floor); + + p->force.pressure = pressure_including_floor; + p->force.soundspeed = soundspeed; + + /* Signal speed */ + const float v_sig = const_viscosity_alpha_prefactor * soundspeed; + + /* Update signal velocity if we need to */ + p->dt_min = min(p->dt_min, p->h_min / v_sig); +} + +/** + * @brief Returns the sum term for the dh/dt calculation + * + * + * @param m The particle mass of the neighbour + * @param rho_inv The inverse density of the neighbour + * @param r_inv The inverse distance between particles + * @param w_dr The kernel gradient for this particle + */ +__attribute__((always_inline)) INLINE static hydro_real_t hydro_get_h_dt_sum( + const hydro_real_t dv_dot_dx, const hydro_real_t dv_dot_G, + const hydro_real_t m, const hydro_real_t rho_inv, const hydro_real_t r_inv, + const hydro_real_t w_dr) { + + hydro_real_t dvdx = 0.; +#ifdef hydro_props_dh_dt_estimator_type +#if (hydro_props_dh_dt_estimator_type == 0) + const hydro_real_t grad = r_inv * w_dr; + const hydro_real_t wt = m * rho_inv; + dvdx = dv_dot_dx; +#elif (hydro_props_dh_dt_estimator_type == 1) + const hydro_real_t grad = r_inv * w_dr; + const hydro_real_t wt = 1.; + dvdx = dv_dot_dx; +#elif (hydro_props_dh_dt_estimator_type == 2) + const hydro_real_t grad = 1.; + const hydro_real_t wt = m * rho_inv; + dvdx = dv_dot_G; +#else + error("Compiled with an unknown dh/dt estimator type"); +#endif +#else + error("Must compile with hydro_props_dh_dt_estimator_type."); +#endif + + return wt * dvdx * grad; +} + +/** + * @brief Returns the normalization for the dh/dt sum + * + * + * @param p The particle + */ +__attribute__((always_inline)) INLINE static hydro_real_t hydro_get_h_dt_norm( + struct part *restrict p) { + + hydro_real_t renormalization = 1.; + +#ifdef hydro_props_dh_dt_estimator_type +#if (hydro_props_dh_dt_estimator_type == 1) + renormalization = p->force.f / p->gradients.wcount; +#endif +#else + error("Must compile with hydro_props_dh_dt_estimator_type."); +#endif + + return renormalization * p->h * hydro_dimension_inv; +} + +/** + * @brief Finishes the force calculation. + * + * Multiplies the force and accelerations by the appropiate constants + * and add the self-contribution term. In most cases, there is little + * to do here. + * + * Cosmological terms are also added/multiplied here. + * + * @param p The particle to act upon + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void hydro_end_force( + struct part *restrict p, const struct cosmology *cosmo) { + + p->force.h_dt *= hydro_get_h_dt_norm(p); +} + +/** + * @brief Kick the additional variables + * + * Additional hydrodynamic quantites are kicked forward in time here. These + * include thermal quantities (thermal energy or total energy or entropy, ...). + * + * @param p The particle to act upon. + * @param xp The particle extended data to act upon. + * @param dt_therm The time-step for this kick (for thermodynamic quantities). + * @param dt_grav The time-step for this kick (for gravity quantities). + * @param dt_grav_mesh The time-step for this kick (mesh gravity). + * @param dt_hydro The time-step for this kick (for hydro quantities). + * @param dt_kick_corr The time-step for this kick (for gravity corrections). + * @param cosmo The cosmological model. + * @param hydro_props The constants used in the scheme + * @param floor_props The properties of the entropy floor. + */ +__attribute__((always_inline)) INLINE static void hydro_kick_extra( + struct part *restrict p, struct xpart *restrict xp, float dt_therm, + float dt_grav, float dt_grav_mesh, float dt_hydro, float dt_kick_corr, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct entropy_floor_properties *floor_props) { + + /* Integrate the internal energy forward in time */ + const float delta_u = p->u_dt * dt_therm; + + /* Do not decrease the energy by more than a factor of 2*/ + xp->u_full = max(xp->u_full + delta_u, 0.5f * xp->u_full); + + /* Check against entropy floor */ + const float floor_A = entropy_floor(p, cosmo, floor_props); + const float floor_u = gas_internal_energy_from_entropy(p->rho, floor_A); + + /* Check against absolute minimum */ + const float min_u = + hydro_props->minimal_internal_energy / cosmo->a_factor_internal_energy; + + /* Take highest of both limits */ + const float energy_min = max(min_u, floor_u); + + if (xp->u_full < energy_min) { + xp->u_full = energy_min; + p->u_dt = 0.f; + } +} + +/** + * @brief Converts hydro quantity of a particle at the start of a run + * + * This function is called once at the end of the engine_init_particle() + * routine (at the start of a calculation) after the densities of + * particles have been computed. + * This can be used to convert internal energy into entropy for instance. + * + * @param p The particle to act upon + * @param xp The extended particle to act upon + * @param cosmo The cosmological model. + * @param hydro_props The constants used in the scheme. + * @param pressure_floor The properties of the pressure floor. + */ +__attribute__((always_inline)) INLINE static void hydro_convert_quantities( + struct part *restrict p, struct xpart *restrict xp, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct pressure_floor_props *pressure_floor) { + /* Convert the physcial internal energy to the comoving one. */ + /* u' = a^(3(g-1)) u */ + const float factor = 1.f / cosmo->a_factor_internal_energy; + p->u *= factor; + xp->u_full = p->u; + + /* Apply the minimal energy limit */ + const float min_comoving_energy = + hydro_props->minimal_internal_energy / cosmo->a_factor_internal_energy; + if (xp->u_full < min_comoving_energy) { + xp->u_full = min_comoving_energy; + p->u = min_comoving_energy; + p->u_dt = 0.f; + } + + /* Set the initial value of the artificial viscosity based on the non-variable + schemes for safety */ + + const float pressure = gas_pressure_from_internal_energy(p->rho, p->u); + const float pressure_including_floor = + pressure_floor_get_comoving_pressure(p, pressure_floor, pressure, cosmo); + const float soundspeed = + gas_soundspeed_from_pressure(p->rho, pressure_including_floor); + + p->force.pressure = pressure_including_floor; + p->force.soundspeed = soundspeed; +} + +/** + * @brief Initialises the particles for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions or assignments between the particle + * and extended particle fields. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + */ +__attribute__((always_inline)) INLINE static void hydro_first_init_part( + struct part *restrict p, struct xpart *restrict xp) { + p->time_bin = 0; + + xp->v_full[0] = p->v[0]; + xp->v_full[1] = p->v[1]; + xp->v_full[2] = p->v[2]; + xp->u_full = p->u; + + p->gradients.C_well_conditioned = 1; + p->gradients.D_well_conditioned = 1; + p->gradients.u_well_conditioned = 1; +#ifdef MAGMA2_DEBUG_CHECKS + p->debug.N_force_low_order_grad = 0; + p->debug.N_force_high_order_grad = 0; + p->debug.C_ill_conditioned_count = 0; + p->debug.D_ill_conditioned_count = 0; + p->debug.u_ill_conditioned_count = 0; + for (int i = 0; i < 3; i++) { + p->debug.correction_matrix[i][0] = 0.; + p->debug.correction_matrix[i][1] = 0.; + p->debug.correction_matrix[i][2] = 0.; + + p->debug.velocity_tensor_aux[i][0] = 0.; + p->debug.velocity_tensor_aux[i][1] = 0.; + p->debug.velocity_tensor_aux[i][2] = 0.; + + p->debug.velocity_tensor_aux_norm[i][0] = 0.; + p->debug.velocity_tensor_aux_norm[i][1] = 0.; + p->debug.velocity_tensor_aux_norm[i][2] = 0.; + } +#endif + p->decoupled = 0; + +#ifdef WITH_FOF_GALAXIES + /* Initialise FoF galaxy data */ + p->galaxy_data.stellar_mass = 0.f; + p->galaxy_data.gas_mass = 0.f; + p->galaxy_data.specific_sfr = 0.f; +#endif + + hydro_reset_acceleration(p); + hydro_init_part(p, NULL); +} + +/** + * @brief Overwrite the initial internal energy of a particle. + * + * Note that in the cases where the thermodynamic variable is not + * internal energy but gets converted later, we must overwrite that + * field. The conversion to the actual variable happens later after + * the initial fake time-step. + * + * @param p The #part to write to. + * @param u_init The new initial internal energy. + */ +__attribute__((always_inline)) INLINE static void +hydro_set_init_internal_energy(struct part *p, float u_init) { + p->u = u_init; +} + +#endif /* SWIFT_MAGMA2_HYDRO_H */ diff --git a/src/hydro/MAGMA2/hydro_debug.h b/src/hydro/MAGMA2/hydro_debug.h new file mode 100644 index 0000000000..ec04ba95c3 --- /dev/null +++ b/src/hydro/MAGMA2/hydro_debug.h @@ -0,0 +1,62 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk) & + * Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2025 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef SWIFT_MAGMA2_HYDRO_DEBUG_H +#define SWIFT_MAGMA2_HYDRO_DEBUG_H + +/** + * @file MAGMA2/hydro_debug.h + * @brief Density-Energy non-conservative implementation of SPH, + * with added MAGMA2 physics (Rosswog 2020) (Debugging routines) + */ + +__attribute__((always_inline)) INLINE static void hydro_debug_particle( + const struct part *p, const struct xpart *xp) { + warning("[PID%lld] part:", p->id); + warning("[PID%lld] decoupled: %d", p->id, p->decoupled); + warning("[PID%lld] x=[%.3e,%.3e,%.3e]", p->id, p->x[0], p->x[1], p->x[2]); + warning("[PID%lld] v=[%.3e,%.3e,%.3e]", p->id, p->v[0], p->v[1], p->v[2]); + warning("[PID%lld] a=[%.3e,%.3e,%.3e]", p->id, p->a_hydro[0], p->a_hydro[1], + p->a_hydro[2]); + warning("[PID%lld] u=%.3e, du/dt=%.3e P=%.3e", p->id, p->u, p->u_dt, + hydro_get_comoving_pressure(p)); + warning("[PID%lld] h=%.3e, dh/dt=%.3e wcount=%d, m=%.3e, dh_drho=%.3e", p->id, + p->h, p->force.h_dt, (int)p->density.wcount, p->mass, + p->density.rho_dh); + warning( + "[PID%lld] time_bin=%d, rho=%.3e, velocity_gradient=[" + "[%.3e,%.3e,%.3e]," + "[%.3e,%.3e,%.3e]," + "[%.3e,%.3e,%.3e]]", + p->id, p->time_bin, p->rho, p->gradients.velocity_tensor[0][0], + p->gradients.velocity_tensor[0][1], p->gradients.velocity_tensor[0][2], + p->gradients.velocity_tensor[1][0], p->gradients.velocity_tensor[1][1], + p->gradients.velocity_tensor[1][2], p->gradients.velocity_tensor[2][0], + p->gradients.velocity_tensor[2][1], p->gradients.velocity_tensor[2][2]); + + if (xp != NULL) { + warning("[PID%lld] xpart:", p->id); + warning("[PID%lld] v_full=[%.3e,%.3e,%.3e]", p->id, xp->v_full[0], + xp->v_full[1], xp->v_full[2]); + } +} + +#endif /* SWIFT_MAGMA2_HYDRO_DEBUG_H */ diff --git a/src/hydro/MAGMA2/hydro_iact.h b/src/hydro/MAGMA2/hydro_iact.h new file mode 100644 index 0000000000..a5193858ba --- /dev/null +++ b/src/hydro/MAGMA2/hydro_iact.h @@ -0,0 +1,1466 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk) & + * Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2025 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_MAGMA2_HYDRO_IACT_H +#define SWIFT_MAGMA2_HYDRO_IACT_H + +/** + * @file MAGMA2/hydro_iact.h + * @brief Density-Energy non-conservative implementation of SPH, + * with added MAGMA2 physics (Rosswog 2020) (interaction routines) + */ + +#include "adaptive_softening_iact.h" +#include "adiabatic_index.h" +#include "hydro_parameters.h" +#include "minmax.h" +#include "signal_velocity.h" +#include "fvpm_geometry.h" + +/** + * @brief Density interaction between two particles. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of part*icle i. + * @param hj Comoving smoothing-length of part*icle j. + * @param pi First part*icle. + * @param pj Second part*icle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_density( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H) { + + /* Kernel weights to be filled */ + float wi, wj, wi_dx, wj_dx; + + const hydro_real_t r = sqrt(r2); + + /* Get the masses. */ + const hydro_real_t mi = pi->mass; + const hydro_real_t mj = pj->mass; + + /* Compute density of pi. */ + const hydro_real_t hi_inv = 1. / hi; + const float xi = r * hi_inv; + + kernel_deval(xi, &wi, &wi_dx); + + pi->rho += mj * wi; + pi->density.rho_dh -= mj * (hydro_dimension * wi + xi * wi_dx); + + pi->density.wcount += wi; + pi->density.wcount_dh -= (hydro_dimension * wi + xi * wi_dx); + + adaptive_softening_add_correction_term(pi, xi, hi_inv, mj); + + /* Collect data for FVPM matrix construction */ + fvpm_accumulate_geometry_and_matrix(pi, wi, dx); + fvpm_update_centroid_left(pi, dx, wi); + + /* Compute density of pj. */ + const hydro_real_t hj_inv = 1. / hj; + const float xj = r * hj_inv; + kernel_deval(xj, &wj, &wj_dx); + + pj->rho += mi * wj; + pj->density.rho_dh -= mi * (hydro_dimension * wj + xj * wj_dx); + + pj->density.wcount += wj; + pj->density.wcount_dh -= (hydro_dimension * wj + xj * wj_dx); + + adaptive_softening_add_correction_term(pj, xj, hj_inv, mi); + + /* Collect data for FVPM matrix construction */ + fvpm_accumulate_geometry_and_matrix(pj, wj, dx); + fvpm_update_centroid_right(pj, dx, wj); + + /* Now we need to compute the div terms */ + const hydro_real_t r_inv = r ? 1.0 / r : 0.0; + const hydro_real_t faci = mj * wi_dx * r_inv; + const hydro_real_t facj = mi * wj_dx * r_inv; + + /* Compute dv dot r */ + const hydro_real_t dv[3] = {pi->v[0] - pj->v[0], pi->v[1] - pj->v[1], + pi->v[2] - pj->v[2]}; + const float dvdr = dv[0] * dx[0] + dv[1] * dx[1] + dv[2] * dx[2]; + + pi->density.div_v -= faci * dvdr; + pj->density.div_v -= facj * dvdr; + + /* For slope limiter */ + pi->gradients.kernel_size = fmax(r, pi->gradients.kernel_size); + pj->gradients.kernel_size = fmax(r, pj->gradients.kernel_size); + + /* Now we need to compute the derivative terms */ + /* Equations 19 & 20 in Rosswog 2020. Compute the internal energy auxiliary + * vector and norm for the gradient */ + const hydro_real_t du = pi->u - pj->u; + + /* For slope limiter */ + pi->gradients.du_min = fmin(-du, pi->gradients.du_min); + pi->gradients.du_max = fmax(-du, pi->gradients.du_max); + + pj->gradients.du_min = fmin(du, pj->gradients.du_min); + pj->gradients.du_max = fmax(du, pj->gradients.du_max); + + pi->gradients.u_aux[0] += du * dx[0] * faci; + pi->gradients.u_aux[1] += du * dx[1] * faci; + pi->gradients.u_aux[2] += du * dx[2] * faci; + + pj->gradients.u_aux[0] += du * dx[0] * facj; + pj->gradients.u_aux[1] += du * dx[1] * facj; + pj->gradients.u_aux[2] += du * dx[2] * facj; + + pi->gradients.u_aux_norm[0] += dx[0] * dx[0] * faci; + pi->gradients.u_aux_norm[1] += dx[1] * dx[1] * faci; + pi->gradients.u_aux_norm[2] += dx[2] * dx[2] * faci; + + pj->gradients.u_aux_norm[0] += dx[0] * dx[0] * facj; + pj->gradients.u_aux_norm[1] += dx[1] * dx[1] * facj; + pj->gradients.u_aux_norm[2] += dx[2] * dx[2] * facj; + + /* Equations 19 & 20 in Rosswog 2020. Signs are all positive because + * dv * dx always results in a positive sign. */ + for (int i = 0; i < 3; i++) { + + /* For slope limiter */ + pi->gradients.dv_min[i] = fmin(-dv[i], pi->gradients.dv_min[i]); + pi->gradients.dv_max[i] = fmax(-dv[i], pi->gradients.dv_max[i]); + + pj->gradients.dv_min[i] = fmin(dv[i], pj->gradients.dv_min[i]); + pj->gradients.dv_max[i] = fmax(dv[i], pj->gradients.dv_max[i]); + + for (int k = 0; k < 3; k++) { + pi->gradients.velocity_tensor_aux[i][k] += dv[i] * dx[k] * faci; + pj->gradients.velocity_tensor_aux[i][k] += dv[i] * dx[k] * facj; + + pi->gradients.velocity_tensor_aux_norm[i][k] += dx[i] * dx[k] * faci; + pj->gradients.velocity_tensor_aux_norm[i][k] += dx[i] * dx[k] * facj; + } + } + +#ifdef MAGMA2_DEBUG_CHECKS + /* Number of neighbors */ + pi->debug.num_ngb++; + pj->debug.num_ngb++; +#endif + +#ifdef hydro_props_use_adiabatic_correction + /* Needed for the adiabatic kernel correction factor */ + pi->gradients.adiabatic_f_numerator += mj * r2 * wi; + pj->gradients.adiabatic_f_numerator += mi * r2 * wj; +#endif +} + +/** + * @brief Density interaction between two particles (non-symmetric). + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of part*icle i. + * @param hj Comoving smoothing-length of part*icle j. + * @param pi First part*icle. + * @param pj Second part*icle (not updated). + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_density( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, const struct part *restrict pj, const float a, + const float H) { + + /* Kernel weights to be filled */ + float wi, wi_dx; + + /* Get the masses. */ + const hydro_real_t mj = pj->mass; + + /* Get r and r inverse. */ + const hydro_real_t r = sqrt(r2); + + const hydro_real_t h_inv = 1. / hi; + const float xi = r * h_inv; + kernel_deval(xi, &wi, &wi_dx); + + pi->rho += mj * wi; + pi->density.rho_dh -= mj * (hydro_dimension * wi + xi * wi_dx); + + pi->density.wcount += wi; + pi->density.wcount_dh -= (hydro_dimension * wi + xi * wi_dx); + + adaptive_softening_add_correction_term(pi, xi, h_inv, mj); + + /* Collect data for FVPM matrix construction */ + fvpm_accumulate_geometry_and_matrix(pi, wi, dx); + fvpm_update_centroid_left(pi, dx, wi); + + const hydro_real_t r_inv = r ? 1.0 / r : 0.0; + const hydro_real_t faci = mj * wi_dx * r_inv; + + /* Compute dv dot r */ + const hydro_real_t dv[3] = {pi->v[0] - pj->v[0], pi->v[1] - pj->v[1], + pi->v[2] - pj->v[2]}; + const float dvdr = dv[0] * dx[0] + dv[1] * dx[1] + dv[2] * dx[2]; + pi->density.div_v -= faci * dvdr; + + /* For slope limiter */ + pi->gradients.kernel_size = fmax(r, pi->gradients.kernel_size); + + /* Equations 19 & 20 in Rosswog 2020. Compute the internal energy auxiliary + * vector and norm for the gradient */ + const hydro_real_t du = pi->u - pj->u; + + /* For slope limiter */ + pi->gradients.du_min = fmin(-du, pi->gradients.du_min); + pi->gradients.du_max = fmax(-du, pi->gradients.du_max); + + pi->gradients.u_aux[0] += du * dx[0] * faci; + pi->gradients.u_aux[1] += du * dx[1] * faci; + pi->gradients.u_aux[2] += du * dx[2] * faci; + + pi->gradients.u_aux_norm[0] += dx[0] * dx[0] * faci; + pi->gradients.u_aux_norm[1] += dx[1] * dx[1] * faci; + pi->gradients.u_aux_norm[2] += dx[2] * dx[2] * faci; + + /* Equations 19 & 20 in Rosswog 2020. Signs are all positive because + * dv * dx always results in a positive sign. */ + for (int i = 0; i < 3; i++) { + + /* For slope limiter */ + pi->gradients.dv_min[i] = fmin(-dv[i], pi->gradients.dv_min[i]); + pi->gradients.dv_max[i] = fmax(-dv[i], pi->gradients.dv_max[i]); + + for (int k = 0; k < 3; k++) { + pi->gradients.velocity_tensor_aux[i][k] += dv[i] * dx[k] * faci; + pi->gradients.velocity_tensor_aux_norm[i][k] += dx[i] * dx[k] * faci; + } + } + +#ifdef MAGMA2_DEBUG_CHECKS + /* Neighbour number */ + pi->debug.num_ngb++; +#endif + +#ifdef hydro_props_use_adiabatic_correction + /* Needed for the adiabatic kernel correction factor */ + pi->gradients.adiabatic_f_numerator += mj * r2 * wi; +#endif +} + +/** + * @brief Calculate the gradient interaction between particle i and particle j + * + * This method wraps around hydro_gradients_collect, which can be an empty + * method, in which case no gradients are used. + * + * @param r2 Comoving squared distance between particle i and particle j. + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Particle i. + * @param pj Particle j. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_gradient( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H) { + + /* Get particle properties */ + const hydro_real_t mi = hydro_get_mass(pi); + const hydro_real_t mj = hydro_get_mass(pj); + + const hydro_real_t rhoi = hydro_get_comoving_density(pi); + const hydro_real_t rhoj = hydro_get_comoving_density(pj); + + const hydro_real_t rhoi_inv = 1. / rhoi; + const hydro_real_t rhoj_inv = 1. / rhoj; + + const hydro_real_t r = sqrt(r2); + const hydro_real_t r_inv = r ? 1.0 / r : 0.0; + + float wi, wi_dx, wj, wj_dx; + + const float xi = r / hi; + const float xj = r / hj; + + kernel_deval(xi, &wi, &wi_dx); + kernel_deval(xj, &wj, &wj_dx); + + const hydro_real_t faci = mj * rhoj_inv * wi; + const hydro_real_t facj = mi * rhoi_inv * wj; + + /* Compute all of the first-order gradients, and second-order gradients */ + + /* Rosswog 2020 Equation 18 gradients. In the paper he uses (vj - vi) and + * (rj - ri), however this is symmetric so no sign problems. */ + + /* Internal energy gradient */ + const hydro_real_t du = pi->u - pj->u; + + pi->gradients.u[0] += du * dx[0] * faci; + pi->gradients.u[1] += du * dx[1] * faci; + pi->gradients.u[2] += du * dx[2] * faci; + + pj->gradients.u[0] += du * dx[0] * facj; + pj->gradients.u[1] += du * dx[1] * facj; + pj->gradients.u[2] += du * dx[2] * facj; + + /* Velocity gradients */ + const hydro_real_t dv[3] = {pi->v[0] - pj->v[0], pi->v[1] - pj->v[1], + pi->v[2] - pj->v[2]}; + + for (int k = 0; k < 3; k++) { + const hydro_real_t du_k = pi->gradients.u_aux[k] - pj->gradients.u_aux[k]; + + for (int i = 0; i < 3; i++) { + pi->gradients.u_hessian[k][i] += du_k * dx[i] * faci; + pj->gradients.u_hessian[k][i] += du_k * dx[i] * facj; + + /* dx is signed as (pi - pj), but it is symmetric so we add */ + pi->gradients.correction_matrix[k][i] += dx[k] * dx[i] * faci; + pj->gradients.correction_matrix[k][i] += dx[k] * dx[i] * facj; + + /* Indices in Rosswog 2020 are i for dv and k for dx. In this loop, + * they are swapped just because correction_matrix is computed with + * the paper indices. */ + pi->gradients.velocity_tensor[k][i] += dv[k] * dx[i] * faci; + pj->gradients.velocity_tensor[k][i] += dv[k] * dx[i] * facj; + + const hydro_real_t dv_grad_ki = pi->gradients.velocity_tensor_aux[k][i] - + pj->gradients.velocity_tensor_aux[k][i]; + + /* Equation 19 indices: + * Index i: velocity direction + * Index k: gradient direction + * + * Our indices: + * Index k: velocity direction + * Index i: gradient direction + * Index j: second derivative gradient direction + */ + for (int j = 0; j < 3; j++) { + pi->gradients.velocity_hessian[k][i][j] += dv_grad_ki * dx[j] * faci; + pj->gradients.velocity_hessian[k][i][j] += dv_grad_ki * dx[j] * facj; + } + } + } + + /* Density gradients */ + const hydro_real_t drho_ij = rhoi - rhoj; + const hydro_real_t wi_dr = wi_dx * r_inv; + const hydro_real_t wj_dr = wj_dx * r_inv; + + pi->rho_gradient[0] += mj * drho_ij * dx[0] * wi_dr; + pi->rho_gradient[1] += mj * drho_ij * dx[1] * wi_dr; + pi->rho_gradient[2] += mj * drho_ij * dx[2] * wi_dr; + + pj->rho_gradient[0] += mi * drho_ij * dx[0] * wj_dr; + pj->rho_gradient[1] += mi * drho_ij * dx[1] * wj_dr; + pj->rho_gradient[2] += mi * drho_ij * dx[2] * wj_dr; + +#ifdef hydro_props_use_adiabatic_correction + /* Correction terms for div v */ + pi->gradients.adiabatic_f_denominator += mj * rhoj_inv * r2 * wi; + pj->gradients.adiabatic_f_denominator += mi * rhoi_inv * r2 * wj; +#endif +} + +/** + * @brief Calculate the gradient interaction between particle i and particle j: + * non-symmetric version + * + * This method wraps around hydro_gradients_nonsym_collect, which can be an + * empty method, in which case no gradients are used. + * + * @param r2 Comoving squared distance between particle i and particle j. + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Particle i. + * @param pj Particle j. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_gradient( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H) { + + /* Get particle properties */ + const hydro_real_t mj = hydro_get_mass(pj); + const hydro_real_t rhoi = hydro_get_comoving_density(pi); + const hydro_real_t rhoj = hydro_get_comoving_density(pj); + const hydro_real_t rhoj_inv = 1. / rhoj; + const hydro_real_t r = sqrt(r2); + const hydro_real_t r_inv = r ? 1.0 / r : 0.0; + float wi, wi_dx; + const float xi = r / hi; + kernel_deval(xi, &wi, &wi_dx); + const hydro_real_t faci = mj * rhoj_inv * wi; + + /* Compute all of the first-order gradients, and second-order gradients */ + + /* Rosswog 2020 Equation 18 gradients. In the paper he uses (vj - vi) and + * (rj - ri), however this is symmetric so no sign problems. */ + + /* Internal energy gradient */ + const hydro_real_t du = pi->u - pj->u; + + pi->gradients.u[0] += du * dx[0] * faci; + pi->gradients.u[1] += du * dx[1] * faci; + pi->gradients.u[2] += du * dx[2] * faci; + + /* Velocity gradients */ + const hydro_real_t dv[3] = {pi->v[0] - pj->v[0], pi->v[1] - pj->v[1], + pi->v[2] - pj->v[2]}; + + for (int k = 0; k < 3; k++) { + const hydro_real_t du_k = pi->gradients.u_aux[k] - pj->gradients.u_aux[k]; + + for (int i = 0; i < 3; i++) { + pi->gradients.u_hessian[k][i] += du_k * dx[i] * faci; + + /* dx is signed as (pi - pj), but it is symmetric so we add */ + pi->gradients.correction_matrix[k][i] += dx[k] * dx[i] * faci; + + /* Indices in Rosswog 2020 are i for dv and k for dx. In this loop, + * they are swapped just because correction_matrix is computed with + * the paper indices. */ + pi->gradients.velocity_tensor[k][i] += dv[k] * dx[i] * faci; + + const hydro_real_t dv_grad_ki = pi->gradients.velocity_tensor_aux[k][i] - + pj->gradients.velocity_tensor_aux[k][i]; + + /* Equation 19 indices: + * Index i: velocity direction + * Index k: gradient direction + * + * Our indices: + * Index k: velocity direction + * Index i: gradient direction + * Index j: second derivative gradient direction + */ + for (int j = 0; j < 3; j++) { + pi->gradients.velocity_hessian[k][i][j] += dv_grad_ki * dx[j] * faci; + } + } + } + + /* Density gradients */ + const hydro_real_t drho_ij = rhoi - rhoj; + const hydro_real_t wi_dr = wi_dx * r_inv; + + pi->rho_gradient[0] += mj * drho_ij * dx[0] * wi_dr; + pi->rho_gradient[1] += mj * drho_ij * dx[1] * wi_dr; + pi->rho_gradient[2] += mj * drho_ij * dx[2] * wi_dr; + +#ifdef hydro_props_use_adiabatic_correction + /* Correction terms for div v */ + pi->gradients.adiabatic_f_denominator += mj * rhoj_inv * r2 * wi; +#endif +} + +/** + * @brief Force interaction between two particles. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of part*icle i. + * @param hj Comoving smoothing-length of part*icle j. + * @param pi First part*icle. + * @param pj Second part*icle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_force( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H) { + + /* Cosmological factors entering the EoMs */ + const hydro_real_t fac_mu = pow_three_gamma_minus_five_over_two(a); + const hydro_real_t a2_Hubble = a * a * H; + + const hydro_real_t r = sqrt(r2); + const hydro_real_t r_inv = r ? 1.0 / r : 0.0; + + /* Recover some data */ + const hydro_real_t mj = pj->mass; + const hydro_real_t mi = pi->mass; + + const hydro_real_t rhoi = pi->rho; + const hydro_real_t rhoj = pj->rho; + const hydro_real_t rhoi_inv = 1. / rhoi; + const hydro_real_t rhoj_inv = 1. / rhoj; + const hydro_real_t rhoij_inv = rhoi_inv * rhoj_inv; + + const hydro_real_t pressurei = pi->force.pressure; + const hydro_real_t pressurej = pj->force.pressure; + + /* Get the kernel for hi. */ + const hydro_real_t hi_inv = 1. / hi; + const hydro_real_t hi_inv_dim = pow_dimension(hi_inv); + const float xi = r * hi_inv; + float wi, wi_dx; + kernel_deval(xi, &wi, &wi_dx); + + /* Get the kernel for hj. */ + const hydro_real_t hj_inv = 1. / hj; + const hydro_real_t hj_inv_dim = pow_dimension(hj_inv); + const float xj = r * hj_inv; + float wj, wj_dx; + kernel_deval(xj, &wj, &wj_dx); + + /* For dh/dt and fall-back SPH */ + const hydro_real_t hi_inv_dim_plus_one = hi_inv * hi_inv_dim; /* 1/h^(d+1) */ + const hydro_real_t hj_inv_dim_plus_one = hj_inv * hj_inv_dim; + const hydro_real_t wi_dr = hi_inv_dim_plus_one * wi_dx; + const hydro_real_t wj_dr = hj_inv_dim_plus_one * wj_dx; + + /* Peculiar velocity difference vector */ + const hydro_real_t dv[3] = {pi->v[0] - pj->v[0], pi->v[1] - pj->v[1], + pi->v[2] - pj->v[2]}; + const hydro_real_t dv_raw[3] = {dv[0], dv[1], dv[2]}; + + /* For MAGMA2, this is the full anti-symmetric gradient vector. For the + * fallback Gasoline2-style SPH, this will just be the direction vector + * between the two particles (r_i - r_j). */ + hydro_real_t G_ij[3] = {0., 0., 0.}; + hydro_real_t G_rad_ij[3] = {0., 0., 0.}; + hydro_real_t G_ij_norm = 0.; + hydro_real_t G_rad_ij_norm = 0.; + + /* Separation vectors and swapped */ + const hydro_real_t dx_ij[3] = {dx[0], dx[1], dx[2]}; + const hydro_real_t dx_ji[3] = {-dx[0], -dx[1], -dx[2]}; + hydro_real_t dx_ij_hat[3] = {0., 0., 0.}; + hydro_vec3_unit(dx_ij, dx_ij_hat); + + /* These are set whether or not we fall back onto SPH gradients */ + hydro_real_t visc_acc_term = 0.; + hydro_real_t sph_acc_term = (pressurei + pressurej) * rhoij_inv; + hydro_real_t sph_du_term_i = pressurei * rhoij_inv; + hydro_real_t sph_du_term_j = pressurej * rhoij_inv; + hydro_real_t visc_du_term = 0.; + hydro_real_t cond_du_term = 0.; + + /* Correction factor must be well-conditioned. */ + const unsigned char C_well_conditioned = + (pi->gradients.C_well_conditioned && pj->gradients.C_well_conditioned); + /* First order density-independent velocity gradients */ + const unsigned char D_well_conditioned = + (pi->gradients.D_well_conditioned && pj->gradients.D_well_conditioned); + const unsigned char u_well_conditioned = + (pi->gradients.u_well_conditioned && pj->gradients.u_well_conditioned); + + /* Flag to revert to use high order gradients */ + unsigned char high_order_gradients_flag = 0; + + /* Always use high order gradients when both pi and pj are well-conditioned */ + if (C_well_conditioned && D_well_conditioned && u_well_conditioned) { + + /* Corrected gradients */ + hydro_real_t G_i[3] = {0., 0., 0.}; + hydro_real_t G_j[3] = {0., 0., 0.}; + /* Rosswog 2020 Eqs 4 & 5 use dx_ji for both */ + hydro_mat3x3_vec3_dot(pi->gradients.correction_matrix, dx_ji, G_i); + hydro_mat3x3_vec3_dot(pj->gradients.correction_matrix, dx_ji, G_j); + + G_i[0] *= wi * hi_inv_dim; + G_i[1] *= wi * hi_inv_dim; + G_i[2] *= wi * hi_inv_dim; + + G_j[0] *= wj * hj_inv_dim; + G_j[1] *= wj * hj_inv_dim; + G_j[2] *= wj * hj_inv_dim; + + /* Averaged correction gradient. Note: antisymmetric, so only need + * a sign flip for pj */ + hydro_get_average_kernel_gradient(pi, pj, G_i, G_j, G_ij); + + /* Compute from j perspective to ensure perfectly anti-symmetric */ + G_j[0] = 0.; + G_j[1] = 0.; + G_j[2] = 0.; + + G_i[0] = 0.; + G_i[1] = 0.; + G_i[2] = 0.; + + /* Swap G_i and G_j */ + hydro_mat3x3_vec3_dot(pj->gradients.correction_matrix, dx_ij, G_j); + hydro_mat3x3_vec3_dot(pi->gradients.correction_matrix, dx_ij, G_i); + + G_j[0] *= wj * hj_inv_dim; + G_j[1] *= wj * hj_inv_dim; + G_j[2] *= wj * hj_inv_dim; + + G_i[0] *= wi * hi_inv_dim; + G_i[1] *= wi * hi_inv_dim; + G_i[2] *= wi * hi_inv_dim; + + hydro_real_t G_ji[3] = {0., 0., 0.}; + hydro_get_average_kernel_gradient(pj, pi, G_j, G_i, G_ji); + + /* Average the two estimators */ + G_ij[0] = 0.5 * (G_ij[0] - G_ji[0]); + G_ij[1] = 0.5 * (G_ij[1] - G_ji[1]); + G_ij[2] = 0.5 * (G_ij[2] - G_ji[2]); + + /* Check if G_ij is extremely misaligned with the radial direction */ + G_ij_norm = hydro_vec3_norm(G_ij); + + /* Get G_ij along the separation vector */ + hydro_real_t G_ij_dot_dx_ij_hat = hydro_vec3_vec3_dot(G_ij, dx_ij_hat); + const hydro_real_t G_ij_dot_dx_ij_hat_abs = fabs(G_ij_dot_dx_ij_hat); + + /* Find the cos(theta) term between G and dx */ + hydro_real_t cosine_G_ij_dx_ij_hat = + G_ij_dot_dx_ij_hat_abs / (G_ij_norm + 1.e-10); + + /* Handle floating point errors */ + if (cosine_G_ij_dx_ij_hat > 1.) cosine_G_ij_dx_ij_hat = 1.; + + const unsigned char G_has_large_angle = + (cosine_G_ij_dx_ij_hat < const_viscosity_cosine_limit); + const unsigned char G_in_wrong_direction = (G_ij_dot_dx_ij_hat > 0.); + + /* Good angle between separation and correct direction, good to go! */ + if (!G_has_large_angle && !G_in_wrong_direction) { + + /* Make sure we use the correct interaction */ + high_order_gradients_flag = 1; + +#ifdef hydro_props_use_radial_artificial_terms + /* G along the separation vector */ + G_rad_ij[0] = G_ij_dot_dx_ij_hat * dx_ij_hat[0]; + G_rad_ij[1] = G_ij_dot_dx_ij_hat * dx_ij_hat[1]; + G_rad_ij[2] = G_ij_dot_dx_ij_hat * dx_ij_hat[2]; +#else + G_rad_ij[0] = G_ij[0]; + G_rad_ij[1] = G_ij[1]; + G_rad_ij[2] = G_ij[2]; +#endif + } else { + /* Revert back to standard separation vector */ + G_ij[0] = dx[0]; + G_ij[1] = dx[1]; + G_ij[2] = dx[2]; + G_ij_norm = hydro_vec3_norm(G_ij); + + /* Use the separation vector for SPH */ + G_rad_ij[0] = dx[0]; + G_rad_ij[1] = dx[1]; + G_rad_ij[2] = dx[2]; + } + + G_rad_ij_norm = hydro_vec3_norm(G_rad_ij); + } + + /* MAGMA2-style gradients (Matrix-Inversion-2 SPH) */ + if (high_order_gradients_flag) { + + /* Compute second order reconstruction of velocity between pi & pj */ + hydro_real_t vi_reconstructed[3] = {0., 0., 0.}; + hydro_real_t vj_reconstructed[3] = {0., 0., 0.}; + + /* Important: Rosswog 2020 h_i and h_j are without kernel_gamma. Therefore, + * use xi and xj in the slope limiting procedure. */ + + /* Compute global Van Leer limiter (scalar, not component-wise) */ + const hydro_real_t phi_ij_vec = + hydro_vector_van_leer_phi(pi->gradients.velocity_tensor, + pj->gradients.velocity_tensor, dx_ij, xi, xj); + + const hydro_real_t phi_ji_vec = + hydro_vector_van_leer_phi(pj->gradients.velocity_tensor, + pi->gradients.velocity_tensor, dx_ji, xj, xi); + + /* Make sure no floating point problems */ + hydro_real_t phi_vec_sym = fmin(phi_ij_vec, phi_ji_vec); + phi_vec_sym = fmin(1., phi_vec_sym); + phi_vec_sym = fmax(0., phi_vec_sym); + + /* Need these recast in case of switching precision */ + const hydro_real_t v_i[3] = {pi->v[0], pi->v[1], pi->v[2]}; + const hydro_real_t v_j[3] = {pj->v[0], pj->v[1], pj->v[2]}; + + /* dx_ji for particle i and dx_ij for particle j */ + hydro_vector_second_order_reconstruction( + phi_vec_sym, dx_ji, v_i, pi->gradients.velocity_tensor, + pi->gradients.velocity_hessian, vi_reconstructed); + + hydro_vector_second_order_reconstruction( + phi_vec_sym, dx_ij, v_j, pj->gradients.velocity_tensor, + pj->gradients.velocity_hessian, vj_reconstructed); + + const hydro_real_t dv_reconstructed[3] = { + vi_reconstructed[0] - vj_reconstructed[0], + vi_reconstructed[1] - vj_reconstructed[1], + vi_reconstructed[2] - vj_reconstructed[2]}; + + /* Get velocity difference, but limit reconstructed values */ + hydro_real_t dv_ij[3] = {0., 0., 0.}; + hydro_vec_minmod_limiter(dv_reconstructed, dv_raw, pi->gradients.dv_min, + pi->gradients.dv_max, pj->gradients.dv_min, + pj->gradients.dv_max, dv_ij); + + /* Artificial viscosity */ + + /* Get the acceleration term, depends on the weighting scheme */ + visc_acc_term = + hydro_get_visc_acc_term(pi, pj, dv_ij, dx_ij, fac_mu, a2_Hubble); + + /* Split heating between the two particles */ + visc_du_term = 0.5 * visc_acc_term; + + /* Artificial conductivity */ + + hydro_real_t ui_reconstructed = 0.; + hydro_real_t uj_reconstructed = 0.; + + /* Compute global Van Leer limiter (scalar, not component-wise) */ + const hydro_real_t phi_ij_scalar = hydro_scalar_van_leer_phi( + pi->gradients.u, pj->gradients.u, dx_ij, xi, xj); + const hydro_real_t phi_ji_scalar = hydro_scalar_van_leer_phi( + pj->gradients.u, pi->gradients.u, dx_ji, xj, xi); + + /* Make sure no floating point problems */ + hydro_real_t phi_scalar_sym = fmin(phi_ij_scalar, phi_ji_scalar); + phi_scalar_sym = fmin(1., phi_scalar_sym); + phi_scalar_sym = fmax(0., phi_scalar_sym); + + /* dx_ji for particle i and dx_ij for particle j */ + hydro_scalar_second_order_reconstruction( + phi_scalar_sym, dx_ji, (hydro_real_t)pi->u, pi->gradients.u, + pi->gradients.u_hessian, &ui_reconstructed); + + hydro_scalar_second_order_reconstruction( + phi_scalar_sym, dx_ij, (hydro_real_t)pj->u, pj->gradients.u, + pj->gradients.u_hessian, &uj_reconstructed); + + const hydro_real_t rho_ij = 0.5 * (rhoi + rhoj); + const hydro_real_t rho_ij_inv = 1. / rho_ij; + const hydro_real_t dv_Hubble[3] = {dv[0] + a2_Hubble * dx_ij[0], + dv[1] + a2_Hubble * dx_ij[1], + dv[2] + a2_Hubble * dx_ij[2]}; + + const hydro_real_t dv_Hubble_dot_dx_ij_hat = + hydro_vec3_vec3_dot(dv_Hubble, dx_ij_hat); + + /* Limit art. cond. to only when information is communicable */ + const hydro_real_t c_ij = + 0.5 * (pi->force.soundspeed + pj->force.soundspeed); + const hydro_real_t v_sig_alpha = c_ij * (1. + 0.75 * const_viscosity_alpha); + + /* Must connect the particles along the LOS */ + hydro_real_t mu_ij = fac_mu * dv_Hubble_dot_dx_ij_hat; + const hydro_real_t v_sig_beta = 0.75 * const_viscosity_beta * mu_ij; + + /* Skip conduction if expansion beats sound speed along LOS */ + if (v_sig_alpha > v_sig_beta) { + + const hydro_real_t dv_ij_Hubble[3] = {dv_ij[0] + a2_Hubble * dx_ij[0], + dv_ij[1] + a2_Hubble * dx_ij[1], + dv_ij[2] + a2_Hubble * dx_ij[2]}; + + /* Signal velocity from speed contributions */ +#ifdef hydro_props_use_radial_artificial_terms + const hydro_real_t dv_ij_Hubble_dot_dx_ij_hat = + hydro_vec3_vec3_dot(dv_ij_Hubble, dx_ij_hat); + const hydro_real_t v_sig_speed = + fac_mu * fabs(dv_ij_Hubble_dot_dx_ij_hat); +#else + const hydro_real_t v_sig_speed = fac_mu * hydro_vec3_norm(dv_ij_Hubble); +#endif + + /* Get spec. energy difference, but limit reconstructed values */ + const hydro_real_t du_raw = pi->u - pj->u; + const hydro_real_t du_reconstructed = ui_reconstructed - uj_reconstructed; + const hydro_real_t du_ij = hydro_scalar_minmod_limiter( + du_reconstructed, du_raw, pi->gradients.du_min, pi->gradients.du_max, + pj->gradients.du_min, pj->gradients.du_max); + + const hydro_real_t alpha_cond = const_conductivity_alpha; + const hydro_real_t delta_P = fabs(pressurei - pressurej); + const hydro_real_t P_lim = delta_P / (pressurei + pressurej); + + /* Add conductivity to the specific energy */ + cond_du_term = alpha_cond * P_lim * v_sig_speed * du_ij * rho_ij_inv; + } else { + mu_ij = 0.; + } + + /* Finalize everything with the correct normalizations. */ + + /* Compute dv dot G_ij, reduces to dv dot dx in regular SPH. */ + const hydro_real_t dv_dot_G_ij = hydro_vec3_vec3_dot(dv_raw, G_ij); + +#ifdef hydro_props_use_radial_artificial_terms + /* Compute Hubble flow along LOS */ + const hydro_real_t dv_Hubble_along_dx_ij[3] = { + dv_Hubble_dot_dx_ij_hat * dx_ij_hat[0], + dv_Hubble_dot_dx_ij_hat * dx_ij_hat[1], + dv_Hubble_dot_dx_ij_hat * dx_ij_hat[2]}; + + const hydro_real_t dv_Hubble_dot_G_rad_ij = + hydro_vec3_vec3_dot(dv_Hubble_along_dx_ij, G_rad_ij); +#else + const hydro_real_t dv_Hubble_dot_G_rad_ij = + hydro_vec3_vec3_dot(dv_Hubble, G_rad_ij); +#endif + + /* Evolve the heating terms using the velocity divergence */ + sph_du_term_i *= dv_dot_G_ij; + sph_du_term_j *= dv_dot_G_ij; + cond_du_term *= -G_rad_ij_norm; /* Eq. 24 Rosswog 2020 */ + visc_du_term *= dv_Hubble_dot_G_rad_ij; + + if (visc_du_term <= 0.) { + visc_acc_term = 0.; + visc_du_term = 0.; + } + + /* Get the time derivative for h. */ + + /* Velocity divergence is from the SPH estimator, not the G_ij vector */ + const hydro_real_t dv_dot_dx_ij = hydro_vec3_vec3_dot(dv_raw, dx_ij); + pi->force.h_dt -= hydro_get_h_dt_sum(dv_dot_dx_ij, dv_dot_G_ij, mj, + rhoj_inv, r_inv, wi_dr); + pj->force.h_dt -= hydro_get_h_dt_sum(dv_dot_dx_ij, dv_dot_G_ij, mi, + rhoi_inv, r_inv, wj_dr); + + /* Timestepping */ + + /* Compute based on raw velocities */ + const hydro_real_t v_sig_visc = + signal_velocity(dx, pi, pj, mu_ij, const_viscosity_beta); + + const hydro_real_t h_ij = 0.5 * (hi + hj); + pi->h_min = fmin(pi->h_min, h_ij); + pj->h_min = fmin(pj->h_min, h_ij); + + /* New timestep estimate */ + const hydro_real_t dt_min_i = h_ij / v_sig_visc; + const hydro_real_t dt_min_j = h_ij / v_sig_visc; + pi->dt_min = fmin(pi->dt_min, dt_min_i); + pj->dt_min = fmin(pj->dt_min, dt_min_j); + +#ifdef MAGMA2_DEBUG_CHECKS + pi->debug.N_force_high_order_grad++; + pj->debug.N_force_high_order_grad++; +#endif + } else { /* Gasoline-like SPH fallback */ + + /* Compute dv dot dr. */ + const hydro_real_t dvdr = hydro_vec3_vec3_dot(dv_raw, G_ij); + + /* Includes the hubble flow term; not used for du/dt */ + const hydro_real_t dvdr_Hubble = dvdr + a2_Hubble * r2; + + /* Are the particles moving towards each others ? */ + const hydro_real_t omega_ij = fmin(dvdr_Hubble, 0.); + + hydro_real_t mu_full_ij = fac_mu * r_inv * dvdr_Hubble; + const hydro_real_t mu_ij = fac_mu * r_inv * omega_ij; + + /* Construct the full viscosity term */ + const hydro_real_t b_ij = + 0.5 * (pi->gradients.balsara + pj->gradients.balsara); + const hydro_real_t rho_ij = rhoi + rhoj; + const hydro_real_t cs_ij = pi->force.soundspeed + pj->force.soundspeed; + const hydro_real_t c_ij = 0.5 * cs_ij; + const hydro_real_t alpha = const_viscosity_alpha; + const hydro_real_t visc = omega_ij < 0. + ? (-0.25 * alpha * cs_ij * mu_ij + + const_viscosity_beta * mu_ij * mu_ij) * + b_ij / (0.5 * rho_ij) + : 0.; + + visc_acc_term = const_fallback_reduction_factor * visc; + visc_du_term = 0.5 * visc_acc_term; + + const hydro_real_t v_sig_alpha = c_ij * (1. + 0.75 * const_viscosity_alpha); + const hydro_real_t v_sig_beta = 0.75 * const_viscosity_beta * mu_full_ij; + + if (v_sig_alpha > v_sig_beta) { + const hydro_real_t rho_ij_inv = 2. / rho_ij; + const hydro_real_t du = pi->u - pj->u; + + const hydro_real_t alpha_cond = const_conductivity_alpha; + const hydro_real_t delta_P = fabs(pressurei - pressurej); + const hydro_real_t P_lim = delta_P / (pressurei + pressurej); + + cond_du_term = alpha_cond * P_lim * fabs(mu_full_ij) * du * rho_ij_inv; + cond_du_term *= const_fallback_reduction_factor; + } + + /* New signal velocity */ + const hydro_real_t new_v_sig_visc = + signal_velocity(dx, pi, pj, mu_ij, const_viscosity_beta); + + const hydro_real_t h_ij = 0.5 * (hi + hj); + pi->h_min = fmin(pi->h_min, h_ij); + pj->h_min = fmin(pj->h_min, h_ij); + + /* New timestep estimate */ + const hydro_real_t dt_min_i = h_ij / new_v_sig_visc; + const hydro_real_t dt_min_j = h_ij / new_v_sig_visc; + pi->dt_min = fmin(pi->dt_min, dt_min_i); + pj->dt_min = fmin(pj->dt_min, dt_min_j); + + const hydro_real_t kernel_gradient = 0.5 * (wi_dr + wj_dr) * r_inv; + + visc_acc_term *= kernel_gradient; + sph_acc_term *= kernel_gradient; + + sph_du_term_i *= dvdr * kernel_gradient; + sph_du_term_j *= dvdr * kernel_gradient; + visc_du_term *= dvdr_Hubble * kernel_gradient; + cond_du_term *= kernel_gradient; + + /* Get the time derivative for h. */ + pi->force.h_dt -= hydro_get_h_dt_sum(dvdr, 0., mj, rhoj_inv, r_inv, wi_dr); + pj->force.h_dt -= hydro_get_h_dt_sum(dvdr, 0., mi, rhoi_inv, r_inv, wj_dr); + +#ifdef MAGMA2_DEBUG_CHECKS + pi->debug.N_force_low_order_grad++; + pj->debug.N_force_low_order_grad++; +#endif + } + + /* If one or both are decoupled they do not contribute to accel or u_dt */ + if (pi->decoupled || pj->decoupled) return; + + /* Get the time derivative for v. */ + + /* Assemble the acceleration */ + const hydro_real_t acc[3] = { + sph_acc_term * G_ij[0] + visc_acc_term * G_rad_ij[0], + sph_acc_term * G_ij[1] + visc_acc_term * G_rad_ij[1], + sph_acc_term * G_ij[2] + visc_acc_term * G_rad_ij[2]}; + + /* Use the force Luke ! */ + pi->a_hydro[0] -= mj * acc[0]; + pi->a_hydro[1] -= mj * acc[1]; + pi->a_hydro[2] -= mj * acc[2]; + + pj->a_hydro[0] += mi * acc[0]; + pj->a_hydro[1] += mi * acc[1]; + pj->a_hydro[2] += mi * acc[2]; + + /* Get the time derivative for u. */ + + /* Assemble the energy equation term */ + const hydro_real_t du_dt_i = sph_du_term_i + visc_du_term + cond_du_term; + const hydro_real_t du_dt_j = sph_du_term_j + visc_du_term - cond_du_term; + + /* Internal energy time derivative */ + pi->u_dt += du_dt_i * mj; + pj->u_dt += du_dt_j * mi; + + pi->u_dt_cond += cond_du_term * mj; + pj->u_dt_cond -= cond_du_term * mi; +} + +/** + * @brief Force interaction between two particles (non-symmetric). + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of part*icle i. + * @param hj Comoving smoothing-length of part*icle j. + * @param pi First part*icle. + * @param pj Second part*icle (not updated). + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_force( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, const struct part *restrict pj, const float a, + const float H) { + + /* Cosmological factors entering the EoMs */ + const hydro_real_t fac_mu = pow_three_gamma_minus_five_over_two(a); + const hydro_real_t a2_Hubble = a * a * H; + + const hydro_real_t r = sqrt(r2); + const hydro_real_t r_inv = r ? 1.0 / r : 0.0; + + /* Recover some data */ + const hydro_real_t mj = pj->mass; + + const hydro_real_t rhoi = pi->rho; + const hydro_real_t rhoj = pj->rho; + const hydro_real_t rhoi_inv = 1. / rhoi; + const hydro_real_t rhoj_inv = 1. / rhoj; + const hydro_real_t rhoij_inv = rhoi_inv * rhoj_inv; + + const hydro_real_t pressurei = pi->force.pressure; + const hydro_real_t pressurej = pj->force.pressure; + + /* Get the kernel for hi. */ + const hydro_real_t hi_inv = 1.0 / hi; + const hydro_real_t hi_inv_dim = pow_dimension(hi_inv); + const float xi = r * hi_inv; + float wi, wi_dx; + kernel_deval(xi, &wi, &wi_dx); + + /* Get the kernel for hj. */ + const hydro_real_t hj_inv = 1.0 / hj; + const hydro_real_t hj_inv_dim = pow_dimension(hj_inv); + const float xj = r * hj_inv; + float wj, wj_dx; + kernel_deval(xj, &wj, &wj_dx); + + /* For dh/dt and fall-back SPH */ + const hydro_real_t hi_inv_dim_plus_one = hi_inv * hi_inv_dim; /* 1/h^(d+1) */ + const hydro_real_t hj_inv_dim_plus_one = hj_inv * hj_inv_dim; + const hydro_real_t wi_dr = hi_inv_dim_plus_one * wi_dx; + const hydro_real_t wj_dr = hj_inv_dim_plus_one * wj_dx; + + /* Peculiar velocity difference vector */ + const hydro_real_t dv[3] = {pi->v[0] - pj->v[0], pi->v[1] - pj->v[1], + pi->v[2] - pj->v[2]}; + const hydro_real_t dv_raw[3] = {dv[0], dv[1], dv[2]}; + + /* For MAGMA2, this is the full anti-symmetric gradient vector. For the + * fallback Gasoline2-style SPH, this will just be the direction vector + * between the two particles (r_i - r_j). */ + hydro_real_t G_ij[3] = {0., 0., 0.}; + hydro_real_t G_rad_ij[3] = {0., 0., 0.}; + hydro_real_t G_ij_norm = 0.; + hydro_real_t G_rad_ij_norm = 0.; + + /* Separation vectors and swapped */ + const hydro_real_t dx_ij[3] = {dx[0], dx[1], dx[2]}; + const hydro_real_t dx_ji[3] = {-dx[0], -dx[1], -dx[2]}; + hydro_real_t dx_ij_hat[3] = {0., 0., 0.}; + hydro_vec3_unit(dx_ij, dx_ij_hat); + + /* These are set whether or not we fall back onto SPH gradients */ + hydro_real_t visc_acc_term = 0.; + hydro_real_t sph_acc_term = (pressurei + pressurej) * rhoij_inv; + hydro_real_t sph_du_term_i = pressurei * rhoij_inv; + hydro_real_t visc_du_term = 0.; + hydro_real_t cond_du_term = 0.; + + /* Correction factor must be well-conditioned. */ + const unsigned char C_well_conditioned = + (pi->gradients.C_well_conditioned && pj->gradients.C_well_conditioned); + /* First order density-independent velocity gradients */ + const unsigned char D_well_conditioned = + (pi->gradients.D_well_conditioned && pj->gradients.D_well_conditioned); + const unsigned char u_well_conditioned = + (pi->gradients.u_well_conditioned && pj->gradients.u_well_conditioned); + + /* Flag to use second order gradients */ + unsigned char high_order_gradients_flag = 0; + + /* Always use SPH gradients between particles if one of them has an + * ill-conditioned C matrix */ + if (C_well_conditioned && D_well_conditioned && u_well_conditioned) { + + /* Corrected gradients */ + hydro_real_t G_i[3] = {0., 0., 0.}; + hydro_real_t G_j[3] = {0., 0., 0.}; + /* Rosswog 2020 Eqs 4 & 5 use dx_ji for both */ + hydro_mat3x3_vec3_dot(pi->gradients.correction_matrix, dx_ji, G_i); + hydro_mat3x3_vec3_dot(pj->gradients.correction_matrix, dx_ji, G_j); + + G_i[0] *= wi * hi_inv_dim; + G_i[1] *= wi * hi_inv_dim; + G_i[2] *= wi * hi_inv_dim; + + G_j[0] *= wj * hj_inv_dim; + G_j[1] *= wj * hj_inv_dim; + G_j[2] *= wj * hj_inv_dim; + + /* Averaged correction gradient. Note: antisymmetric, so only need + * a sign flip for pj */ + hydro_get_average_kernel_gradient(pi, pj, G_i, G_j, G_ij); + + /* Compute from j perspective to ensure perfectly anti-symmetric */ + G_j[0] = 0.; + G_j[1] = 0.; + G_j[2] = 0.; + + G_i[0] = 0.; + G_i[1] = 0.; + G_i[2] = 0.; + + /* Swap G_i and G_j */ + hydro_mat3x3_vec3_dot(pj->gradients.correction_matrix, dx_ij, G_j); + hydro_mat3x3_vec3_dot(pi->gradients.correction_matrix, dx_ij, G_i); + + G_j[0] *= wj * hj_inv_dim; + G_j[1] *= wj * hj_inv_dim; + G_j[2] *= wj * hj_inv_dim; + + G_i[0] *= wi * hi_inv_dim; + G_i[1] *= wi * hi_inv_dim; + G_i[2] *= wi * hi_inv_dim; + + hydro_real_t G_ji[3] = {0., 0., 0.}; + hydro_get_average_kernel_gradient(pj, pi, G_j, G_i, G_ji); + + /* Average the two estimators */ + G_ij[0] = 0.5 * (G_ij[0] - G_ji[0]); + G_ij[1] = 0.5 * (G_ij[1] - G_ji[1]); + G_ij[2] = 0.5 * (G_ij[2] - G_ji[2]); + + /* Check if G_ij is extremely misaligned with the radial direction */ + G_ij_norm = hydro_vec3_norm(G_ij); + + /* Get G_ij along the separation vector */ + hydro_real_t G_ij_dot_dx_ij_hat = hydro_vec3_vec3_dot(G_ij, dx_ij_hat); + const hydro_real_t G_ij_dot_dx_ij_hat_abs = fabs(G_ij_dot_dx_ij_hat); + + /* Find the cos(theta) term between G and dx */ + hydro_real_t cosine_G_ij_dx_ij_hat = + G_ij_dot_dx_ij_hat_abs / (G_ij_norm + 1.e-10); + + /* Handle floating point errors */ + if (cosine_G_ij_dx_ij_hat > 1.) cosine_G_ij_dx_ij_hat = 1.; + + const unsigned char G_has_large_angle = + (cosine_G_ij_dx_ij_hat < const_viscosity_cosine_limit); + const unsigned char G_in_wrong_direction = (G_ij_dot_dx_ij_hat > 0.); + + /* Good angle between separation and correct direction, good to go! */ + if (!G_has_large_angle && !G_in_wrong_direction) { + + /* Make sure we use the correct gradients below */ + high_order_gradients_flag = 1; + +#ifdef hydro_props_use_radial_artificial_terms + /* G along the separation vector */ + G_rad_ij[0] = G_ij_dot_dx_ij_hat * dx_ij_hat[0]; + G_rad_ij[1] = G_ij_dot_dx_ij_hat * dx_ij_hat[1]; + G_rad_ij[2] = G_ij_dot_dx_ij_hat * dx_ij_hat[2]; +#else + G_rad_ij[0] = G_ij[0]; + G_rad_ij[1] = G_ij[1]; + G_rad_ij[2] = G_ij[2]; +#endif + } else { + /* Revert back to standard separation vector */ + G_ij[0] = dx[0]; + G_ij[1] = dx[1]; + G_ij[2] = dx[2]; + G_ij_norm = hydro_vec3_norm(G_ij); + + /* Use the separation vector for SPH */ + G_rad_ij[0] = dx[0]; + G_rad_ij[1] = dx[1]; + G_rad_ij[2] = dx[2]; + } + + G_rad_ij_norm = hydro_vec3_norm(G_rad_ij); + } + + /* MAGMA2 style gradients (Matrix-Inversion-2 SPH) */ + if (high_order_gradients_flag) { + + /* Compute second order reconstruction of velocity between pi & pj */ + hydro_real_t vi_reconstructed[3] = {0., 0., 0.}; + hydro_real_t vj_reconstructed[3] = {0., 0., 0.}; + + /* Important: Rosswog 2020 h_i and h_j are without kernel_gamma. Therefore, + * use xi and xj in the slope limiting procedure. */ + + /* Compute global Van Leer limiter (scalar, not component-wise) */ + const hydro_real_t phi_ij_vec = + hydro_vector_van_leer_phi(pi->gradients.velocity_tensor, + pj->gradients.velocity_tensor, dx_ij, xi, xj); + const hydro_real_t phi_ji_vec = + hydro_vector_van_leer_phi(pj->gradients.velocity_tensor, + pi->gradients.velocity_tensor, dx_ji, xj, xi); + + /* Make sure no floating point problems */ + hydro_real_t phi_vec_sym = fmin(phi_ij_vec, phi_ji_vec); + phi_vec_sym = fmin(1., phi_vec_sym); + phi_vec_sym = fmax(0., phi_vec_sym); + + /* Need these recast in case of switching precision */ + const hydro_real_t v_i[3] = {pi->v[0], pi->v[1], pi->v[2]}; + const hydro_real_t v_j[3] = {pj->v[0], pj->v[1], pj->v[2]}; + + /* dx_ji for particle i and dx_ij for particle j */ + hydro_vector_second_order_reconstruction( + phi_vec_sym, dx_ji, v_i, pi->gradients.velocity_tensor, + pi->gradients.velocity_hessian, vi_reconstructed); + + hydro_vector_second_order_reconstruction( + phi_vec_sym, dx_ij, v_j, pj->gradients.velocity_tensor, + pj->gradients.velocity_hessian, vj_reconstructed); + + const hydro_real_t dv_reconstructed[3] = { + vi_reconstructed[0] - vj_reconstructed[0], + vi_reconstructed[1] - vj_reconstructed[1], + vi_reconstructed[2] - vj_reconstructed[2]}; + + /* Get velocity difference, but limit reconstructed values */ + hydro_real_t dv_ij[3] = {0., 0., 0.}; + hydro_vec_minmod_limiter(dv_reconstructed, dv_raw, pi->gradients.dv_min, + pi->gradients.dv_max, pj->gradients.dv_min, + pj->gradients.dv_max, dv_ij); + + /* Artificial viscosity */ + + /* Get the acceleration term, depends on the weighting scheme */ + visc_acc_term = + hydro_get_visc_acc_term(pi, pj, dv_ij, dx_ij, fac_mu, a2_Hubble); + + /* Split heating between the two particles */ + visc_du_term = 0.5 * visc_acc_term; + + /* Artificial conductivity */ + + hydro_real_t ui_reconstructed = 0.; + hydro_real_t uj_reconstructed = 0.; + + /* Compute global Van Leer limiter (scalar, not component-wise) */ + const hydro_real_t phi_ij_scalar = hydro_scalar_van_leer_phi( + pi->gradients.u, pj->gradients.u, dx_ij, xi, xj); + const hydro_real_t phi_ji_scalar = hydro_scalar_van_leer_phi( + pj->gradients.u, pi->gradients.u, dx_ji, xj, xi); + + /* Make sure no floating point problems */ + hydro_real_t phi_scalar_sym = fmin(phi_ij_scalar, phi_ji_scalar); + phi_scalar_sym = fmin(1., phi_scalar_sym); + phi_scalar_sym = fmax(0., phi_scalar_sym); + + /* dx_ji for particle i and dx_ij for particle j */ + hydro_scalar_second_order_reconstruction( + phi_scalar_sym, dx_ji, (hydro_real_t)pi->u, pi->gradients.u, + pi->gradients.u_hessian, &ui_reconstructed); + + hydro_scalar_second_order_reconstruction( + phi_scalar_sym, dx_ij, (hydro_real_t)pj->u, pj->gradients.u, + pj->gradients.u_hessian, &uj_reconstructed); + + const hydro_real_t rho_ij = 0.5 * (rhoi + rhoj); + const hydro_real_t rho_ij_inv = 1. / rho_ij; + const hydro_real_t dv_Hubble[3] = {dv[0] + a2_Hubble * dx_ij[0], + dv[1] + a2_Hubble * dx_ij[1], + dv[2] + a2_Hubble * dx_ij[2]}; + + const hydro_real_t dv_Hubble_dot_dx_ij_hat = + hydro_vec3_vec3_dot(dv_Hubble, dx_ij_hat); + + /* Limit art. cond. to only when information is communicable */ + const hydro_real_t c_ij = + 0.5 * (pi->force.soundspeed + pj->force.soundspeed); + const hydro_real_t v_sig_alpha = c_ij * (1. + 0.75 * const_viscosity_alpha); + + /* Must connect the particles along the LOS */ + hydro_real_t mu_ij = fac_mu * dv_Hubble_dot_dx_ij_hat; + const hydro_real_t v_sig_beta = 0.75 * const_viscosity_beta * mu_ij; + + /* Skip conduction if expansion beats sound speed along LOS */ + if (v_sig_alpha > v_sig_beta) { + + const hydro_real_t dv_ij_Hubble[3] = {dv_ij[0] + a2_Hubble * dx_ij[0], + dv_ij[1] + a2_Hubble * dx_ij[1], + dv_ij[2] + a2_Hubble * dx_ij[2]}; + + /* Signal velocity from speed contributions */ +#ifdef hydro_props_use_radial_artificial_terms + const hydro_real_t dv_ij_Hubble_dot_dx_ij_hat = + hydro_vec3_vec3_dot(dv_ij_Hubble, dx_ij_hat); + const hydro_real_t v_sig_speed = + fac_mu * fabs(dv_ij_Hubble_dot_dx_ij_hat); +#else + const hydro_real_t v_sig_speed = fac_mu * hydro_vec3_norm(dv_ij_Hubble); +#endif + + /* Get spec. energy difference, but limit reconstructed values */ + const hydro_real_t du_raw = pi->u - pj->u; + const hydro_real_t du_reconstructed = ui_reconstructed - uj_reconstructed; + const hydro_real_t du_ij = hydro_scalar_minmod_limiter( + du_reconstructed, du_raw, pi->gradients.du_min, pi->gradients.du_max, + pj->gradients.du_min, pj->gradients.du_max); + + const hydro_real_t alpha_cond = const_conductivity_alpha; + const hydro_real_t delta_P = fabs(pressurei - pressurej); + const hydro_real_t P_lim = delta_P / (pressurei + pressurej); + + /* Add conductivity to the specific energy */ + cond_du_term = alpha_cond * P_lim * v_sig_speed * du_ij * rho_ij_inv; + } else { + mu_ij = 0.; + } + + /* Finalize the viscosity and conductivity with correct normalizations. */ + + /* Compute dv dot G_ij, reduces to dv dot dx in regular SPH. */ + const hydro_real_t dv_dot_G_ij = hydro_vec3_vec3_dot(dv_raw, G_ij); + +#ifdef hydro_props_use_radial_artificial_terms + /* Get Hubble flow along dx */ + const hydro_real_t dv_Hubble_along_dx_ij[3] = { + dv_Hubble_dot_dx_ij_hat * dx_ij_hat[0], + dv_Hubble_dot_dx_ij_hat * dx_ij_hat[1], + dv_Hubble_dot_dx_ij_hat * dx_ij_hat[2]}; + + /* Get Hubble flow contribution */ + const hydro_real_t dv_Hubble_dot_G_rad_ij = + hydro_vec3_vec3_dot(dv_Hubble_along_dx_ij, G_rad_ij); +#else + const hydro_real_t dv_Hubble_dot_G_rad_ij = + hydro_vec3_vec3_dot(dv_Hubble, G_rad_ij); +#endif + + sph_du_term_i *= dv_dot_G_ij; + cond_du_term *= -G_rad_ij_norm; /* Eq. 24 Rosswog 2020 */ + visc_du_term *= dv_Hubble_dot_G_rad_ij; + + if (visc_du_term <= 0.) { + visc_acc_term = 0.; + visc_du_term = 0.; + } + + /* Get the time derivative for h. */ + + const hydro_real_t dv_dot_dx_ij = hydro_vec3_vec3_dot(dv_raw, dx_ij); + pi->force.h_dt -= hydro_get_h_dt_sum(dv_dot_dx_ij, dv_dot_G_ij, mj, + rhoj_inv, r_inv, wi_dr); + + /* Timestepping */ + + /* Compute based on raw velocities */ + const hydro_real_t v_sig_visc = + signal_velocity(dx, pi, pj, mu_ij, const_viscosity_beta); + + const hydro_real_t h_ij = 0.5 * (hi + hj); + pi->h_min = fmin(pi->h_min, h_ij); + + const hydro_real_t dt_min_i = h_ij / v_sig_visc; + pi->dt_min = fmin(pi->dt_min, dt_min_i); + +#ifdef MAGMA2_DEBUG_CHECKS + pi->debug.N_force_high_order_grad++; +#endif + } else { /* Gasoline2 SPH fallback*/ + + /* Compute dv dot dr. */ + const hydro_real_t dvdr = hydro_vec3_vec3_dot(dv_raw, G_ij); + + /* Includes the hubble flow term; not used for du/dt */ + const hydro_real_t dvdr_Hubble = dvdr + a2_Hubble * r2; + + /* Are the particles moving towards each others ? */ + const hydro_real_t omega_ij = fmin(dvdr_Hubble, 0.); + + hydro_real_t mu_full_ij = fac_mu * r_inv * dvdr_Hubble; + const hydro_real_t mu_ij = fac_mu * r_inv * omega_ij; + + /* Construct the full viscosity term */ + const hydro_real_t b_ij = + 0.5 * (pi->gradients.balsara + pj->gradients.balsara); + const hydro_real_t rho_ij = rhoi + rhoj; + const hydro_real_t cs_ij = pi->force.soundspeed + pj->force.soundspeed; + const hydro_real_t c_ij = 0.5 * cs_ij; + const hydro_real_t alpha = const_viscosity_alpha; + const hydro_real_t visc = omega_ij < 0. + ? (-0.25 * alpha * cs_ij * mu_ij + + const_viscosity_beta * mu_ij * mu_ij) * + b_ij / (0.5 * rho_ij) + : 0.; + + visc_acc_term = const_fallback_reduction_factor * visc; + visc_du_term = 0.5 * visc_acc_term; + + const hydro_real_t v_sig_alpha = c_ij * (1. + 0.75 * const_viscosity_alpha); + const hydro_real_t v_sig_beta = 0.75 * const_viscosity_beta * mu_full_ij; + + if (v_sig_alpha > v_sig_beta) { + const hydro_real_t rho_ij_inv = 2. / rho_ij; + const hydro_real_t du = pi->u - pj->u; + + const hydro_real_t alpha_cond = const_conductivity_alpha; + const hydro_real_t delta_P = fabs(pressurei - pressurej); + const hydro_real_t P_lim = delta_P / (pressurei + pressurej); + + cond_du_term = alpha_cond * P_lim * fabs(mu_full_ij) * du * rho_ij_inv; + cond_du_term *= const_fallback_reduction_factor; + } + + /* New signal velocity */ + const hydro_real_t new_v_sig_visc = + signal_velocity(dx, pi, pj, mu_ij, const_viscosity_beta); + + const hydro_real_t h_ij = 0.5 * (hi + hj); + pi->h_min = fmin(pi->h_min, h_ij); + + /* New time-step estimate */ + const hydro_real_t dt_min_i = h_ij / new_v_sig_visc; + pi->dt_min = fmin(pi->dt_min, dt_min_i); + + /* Variable smoothing length term */ + const hydro_real_t kernel_gradient = 0.5 * (wi_dr + wj_dr) * r_inv; + + visc_acc_term *= kernel_gradient; + sph_acc_term *= kernel_gradient; + sph_du_term_i *= dvdr * kernel_gradient; + visc_du_term *= dvdr_Hubble * kernel_gradient; + cond_du_term *= kernel_gradient; + + /* Get the time derivative for h. */ + pi->force.h_dt -= hydro_get_h_dt_sum(dvdr, 0., mj, rhoj_inv, r_inv, wi_dr); + +#ifdef MAGMA2_DEBUG_CHECKS + pi->debug.N_force_low_order_grad++; +#endif + } + + /* If one or both are decoupled they do not contribute to accel or u_dt */ + if (pi->decoupled || pj->decoupled) return; + + /* Assemble the acceleration */ + const hydro_real_t acc[3] = { + sph_acc_term * G_ij[0] + visc_acc_term * G_rad_ij[0], + sph_acc_term * G_ij[1] + visc_acc_term * G_rad_ij[1], + sph_acc_term * G_ij[2] + visc_acc_term * G_rad_ij[2]}; + + /* Use the force Luke ! */ + pi->a_hydro[0] -= mj * acc[0]; + pi->a_hydro[1] -= mj * acc[1]; + pi->a_hydro[2] -= mj * acc[2]; + + /* Assemble the energy equation term */ + const hydro_real_t du_dt_i = sph_du_term_i + visc_du_term + cond_du_term; + + /* Internal energy time derivative */ + pi->u_dt += du_dt_i * mj; + + pi->u_dt_cond += cond_du_term * mj; +} + +#endif /* SWIFT_MAGMA2_HYDRO_IACT_H */ diff --git a/src/hydro/MAGMA2/hydro_io.h b/src/hydro/MAGMA2/hydro_io.h new file mode 100644 index 0000000000..6205774efd --- /dev/null +++ b/src/hydro/MAGMA2/hydro_io.h @@ -0,0 +1,350 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk) & + * Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2025 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_MAGMA2_HYDRO_IO_H +#define SWIFT_MAGMA2_HYDRO_IO_H + +/** + * @file MAGMA2/hydro_io.h + * @brief Density-Energy non-conservative implementation of SPH, + * with added MAGMA2 physics (Rosswog 2020) (i/o routines) + */ + +#include "adiabatic_index.h" +#include "hydro.h" +#include "hydro_parameters.h" +#include "io_properties.h" +#include "kernel_hydro.h" + +/** + * @brief Specifies which particle fields to read from a dataset + * + * @param parts The particle array. + * @param list The list of i/o properties to read. + * @param num_fields The number of i/o fields to read. + */ +INLINE static void hydro_read_particles(struct part *parts, + struct io_props *list, + int *num_fields) { + *num_fields = 8; + + /* List what we want to read */ + list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY, + UNIT_CONV_LENGTH, parts, x); + list[1] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY, + UNIT_CONV_SPEED, parts, v); + list[2] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS, + parts, mass); + list[3] = io_make_input_field("SmoothingLength", FLOAT, 1, COMPULSORY, + UNIT_CONV_LENGTH, parts, h); + list[4] = io_make_input_field("InternalEnergy", FLOAT, 1, COMPULSORY, + UNIT_CONV_ENERGY_PER_UNIT_MASS, parts, u); + list[5] = io_make_input_field("ParticleIDs", ULONGLONG, 1, COMPULSORY, + UNIT_CONV_NO_UNITS, parts, id); + list[6] = io_make_input_field("Accelerations", FLOAT, 3, OPTIONAL, + UNIT_CONV_ACCELERATION, parts, a_hydro); + list[7] = io_make_input_field("Density", FLOAT, 1, OPTIONAL, + UNIT_CONV_DENSITY, parts, rho); +} + +INLINE static void convert_S(const struct engine *e, const struct part *p, + const struct xpart *xp, float *ret) { + ret[0] = hydro_get_comoving_entropy(p, xp); +} + +INLINE static void convert_P(const struct engine *e, const struct part *p, + const struct xpart *xp, float *ret) { + ret[0] = hydro_get_comoving_pressure(p); +} + +INLINE static void convert_div_v(const struct engine *e, const struct part *p, + const struct xpart *xp, float *ret) { + ret[0] = p->gradients.velocity_tensor[0][0] + + p->gradients.velocity_tensor[1][1] + + p->gradients.velocity_tensor[2][2]; +} + +INLINE static void convert_part_pos(const struct engine *e, + const struct part *p, + const struct xpart *xp, double *ret) { + const struct space *s = e->s; + if (s->periodic) { + ret[0] = box_wrap(p->x[0], 0.0, s->dim[0]); + ret[1] = box_wrap(p->x[1], 0.0, s->dim[1]); + ret[2] = box_wrap(p->x[2], 0.0, s->dim[2]); + } else { + ret[0] = p->x[0]; + ret[1] = p->x[1]; + ret[2] = p->x[2]; + } + if (e->snapshot_use_delta_from_edge) { + ret[0] = min(ret[0], s->dim[0] - e->snapshot_delta_from_edge); + ret[1] = min(ret[1], s->dim[1] - e->snapshot_delta_from_edge); + ret[2] = min(ret[2], s->dim[2] - e->snapshot_delta_from_edge); + } +} + +INLINE static void convert_part_vel(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + const int with_cosmology = (e->policy & engine_policy_cosmology); + const struct cosmology *cosmo = e->cosmology; + const integertime_t ti_current = e->ti_current; + const double time_base = e->time_base; + const float dt_kick_grav_mesh = e->dt_kick_grav_mesh_for_io; + + const integertime_t ti_beg = get_integer_time_begin(ti_current, p->time_bin); + const integertime_t ti_end = get_integer_time_end(ti_current, p->time_bin); + + /* Get time-step since the last kick */ + float dt_kick_grav, dt_kick_hydro; + if (with_cosmology) { + dt_kick_grav = cosmology_get_grav_kick_factor(cosmo, ti_beg, ti_current); + dt_kick_grav -= + cosmology_get_grav_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2); + dt_kick_hydro = cosmology_get_hydro_kick_factor(cosmo, ti_beg, ti_current); + dt_kick_hydro -= + cosmology_get_hydro_kick_factor(cosmo, ti_beg, (ti_beg + ti_end) / 2); + } else { + dt_kick_grav = (ti_current - ((ti_beg + ti_end) / 2)) * time_base; + dt_kick_hydro = (ti_current - ((ti_beg + ti_end) / 2)) * time_base; + } + + /* Extrapolate the velocites to the current time (hydro term)*/ + ret[0] = xp->v_full[0] + p->a_hydro[0] * dt_kick_hydro; + ret[1] = xp->v_full[1] + p->a_hydro[1] * dt_kick_hydro; + ret[2] = xp->v_full[2] + p->a_hydro[2] * dt_kick_hydro; + + /* Add the gravity term */ + if (p->gpart != NULL) { + ret[0] += p->gpart->a_grav[0] * dt_kick_grav; + ret[1] += p->gpart->a_grav[1] * dt_kick_grav; + ret[2] += p->gpart->a_grav[2] * dt_kick_grav; + } + + /* And the mesh gravity term */ + if (p->gpart != NULL) { + ret[0] += p->gpart->a_grav_mesh[0] * dt_kick_grav_mesh; + ret[1] += p->gpart->a_grav_mesh[1] * dt_kick_grav_mesh; + ret[2] += p->gpart->a_grav_mesh[2] * dt_kick_grav_mesh; + } + + /* Conversion from internal units to peculiar velocities */ + ret[0] *= cosmo->a_inv; + ret[1] *= cosmo->a_inv; + ret[2] *= cosmo->a_inv; +} + +INLINE static void convert_part_potential(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + if (p->gpart != NULL) + ret[0] = gravity_get_comoving_potential(p->gpart); + else + ret[0] = 0.f; +} + +INLINE static void convert_part_softening(const struct engine *e, + const struct part *p, + const struct xpart *xp, float *ret) { + if (p->gpart != NULL) { + ret[0] = kernel_gravity_softening_plummer_equivalent_inv * + gravity_get_softening(p->gpart, e->gravity_properties); + } else { + ret[0] = 0.f; + } +} + +/** + * @brief Specifies which particle fields to write to a dataset + * + * @param parts The particle array. + * @param list The list of i/o properties to write. + * @param num_fields The number of i/o fields to write. + */ +INLINE static void hydro_write_particles(const struct part *parts, + const struct xpart *xparts, + struct io_props *list, + int *num_fields) { + *num_fields = 0; + int num = 0; + + /* List what we want to write */ + list[num] = io_make_output_field_convert_part( + "Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH, 1.f, parts, xparts, + convert_part_pos, "Co-moving positions of the particles"); + num++; + + list[num] = io_make_output_field_convert_part( + "Velocities", FLOAT, 3, UNIT_CONV_SPEED, 0.f, parts, xparts, + convert_part_vel, + "Peculiar velocities of the stars. This is (a * dx/dt) where x is the " + "co-moving positions of the particles"); + num++; + + list[num] = io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, 0.f, + parts, mass, "Masses of the particles"); + num++; + + list[num] = io_make_output_field( + "SmoothingLengths", FLOAT, 1, UNIT_CONV_LENGTH, 1.f, parts, h, + "Co-moving smoothing lengths (FWHM of the kernel) of the particles"); + num++; + + list[num] = io_make_output_field( + "InternalEnergies", FLOAT, 1, UNIT_CONV_ENERGY_PER_UNIT_MASS, + -3.f * hydro_gamma_minus_one, parts, u, + "Co-moving thermal energies per unit mass of the particles"); + num++; + + list[num] = io_make_physical_output_field( + "ParticleIDs", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0.f, parts, id, + /*can convert to comoving=*/0, "Unique IDs of the particles"); + num++; + + list[num] = io_make_output_field("Densities", FLOAT, 1, UNIT_CONV_DENSITY, + -3.f, parts, rho, + "Co-moving mass densities of the particles"); + num++; + + list[num] = io_make_output_field_convert_part( + "Entropies", FLOAT, 1, UNIT_CONV_ENTROPY_PER_UNIT_MASS, 0.f, parts, + xparts, convert_S, "Co-moving entropies per unit mass of the particles"); + num++; + + list[num] = io_make_output_field_convert_part( + "Pressures", FLOAT, 1, UNIT_CONV_PRESSURE, -3.f * hydro_gamma, parts, + xparts, convert_P, "Co-moving pressures of the particles"); + num++; + + list[num] = io_make_output_field_convert_part( + "VelocityDivergences", FLOAT, 1, UNIT_CONV_FREQUENCY, 0.f, parts, xparts, + convert_div_v, + "Local velocity divergence field around the particles. Provided without " + "cosmology, as this includes the Hubble flow. To return to a peculiar " + "velocity divergence, div . v_pec = a^2 (div . v - n_D H)"); + num++; + + list[num] = io_make_output_field_convert_part( + "Potentials", FLOAT, 1, UNIT_CONV_POTENTIAL, -1.f, parts, xparts, + convert_part_potential, + "Co-moving gravitational potential at position of the particles"); + num++; + + list[num] = io_make_output_field_convert_part( + "Softenings", FLOAT, 1, UNIT_CONV_LENGTH, 1.f, parts, xparts, + convert_part_softening, + "Co-moving gravitational Plummer-equivalent softenings of the particles"); + num++; + +#ifdef MAGMA2_DEBUG_CHECKS + list[num] = + io_make_output_field("NumberOfNeighbours", INT, 1, UNIT_CONV_NO_UNITS, + 0.f, parts, debug.num_ngb, "Number of neighbours."); + num++; + + list[num] = io_make_output_field( + "LowOrderGradientsCount", INT, 1, UNIT_CONV_NO_UNITS, 0.f, parts, + debug.N_force_low_order_grad, + "Cumulative number of low order gradient force interactions."); + num++; + + list[num] = io_make_output_field( + "HighOrderGradientsCount", INT, 1, UNIT_CONV_NO_UNITS, 0.f, parts, + debug.N_force_high_order_grad, + "Cumulative number of high order gradient force interactions."); + num++; + + list[num] = io_make_output_field( + "CorrectionMatrices", FLOAT, 9, UNIT_CONV_LENGTH * UNIT_CONV_LENGTH, 2.f, + parts, debug.correction_matrix, + "Co-moving correction matrices for the particles."); + num++; + + list[num] = io_make_output_field( + "CorrectionIllConditionedCounts", INT, 1, UNIT_CONV_NO_UNITS, 0.f, parts, + debug.C_ill_conditioned_count, + "Count for how many times this particle had an ill-conditioned C matrix"); + num++; + + list[num] = io_make_output_field( + "VelocityGradientNumeratorMatrices", FLOAT, 9, + UNIT_CONV_DENSITY * UNIT_CONV_SPEED / UNIT_CONV_LENGTH, -5.f, parts, + debug.velocity_tensor_aux, + "Co-moving numerator matrices for the particles."); + num++; + + list[num] = io_make_output_field( + "VelocityGradientDenominatorMatrices", FLOAT, 9, UNIT_CONV_DENSITY, -3.f, + parts, debug.velocity_tensor_aux_norm, + "Co-moving denominator matrices for the particles."); + num++; + + list[num] = io_make_output_field( + "VelocityGradientIllConditionedCounts", INT, 1, UNIT_CONV_NO_UNITS, 0.f, + parts, debug.D_ill_conditioned_count, + "Count for how many times this particle had an ill-conditioned D matrix"); + num++; + + list[num] = io_make_output_field( + "SpecificEnergyGradientNumerators", FLOAT, 3, + UNIT_CONV_DENSITY * UNIT_CONV_ENERGY_PER_UNIT_MASS / UNIT_CONV_LENGTH, + -6.f, parts, debug.u_aux, + "Co-moving specific energy numerator matrices for the particles."); + num++; + + list[num] = io_make_output_field( + "SpecificEnergyGradientDenominators", FLOAT, 3, UNIT_CONV_DENSITY, -3.f, + parts, debug.u_aux, + "Co-moving specific energy gradient numerator for the particles."); + num++; + + list[num] = io_make_output_field("SpecificEnergyIllConditionedCounts", INT, 1, + UNIT_CONV_NO_UNITS, 0.f, parts, + debug.u_ill_conditioned_count, + "Count for how many times this particle had " + "an ill-conditioned u_aux_norm"); + num++; +#endif + + *num_fields = num; +} + +/** + * @brief Writes the current model of SPH to the file + * @param h_grpsph The HDF5 group in which to write + */ +INLINE static void hydro_write_flavour(hid_t h_grpsph) { + /* Viscosity and thermal conduction */ + /* Nothing in this minimal model... */ + io_write_attribute_s(h_grpsph, "Thermal Conductivity Model", + "Simple treatment as in Price (2008)"); + io_write_attribute_s(h_grpsph, "Viscosity Model", + "Gingold & Monaghan (1983)"); +} + +/** + * @brief Are we writing entropy in the internal energy field ? + * + * @return 1 if entropy is in 'internal energy', 0 otherwise. + */ +INLINE static int writeEntropyFlag(void) { return 0; } + +#endif /* SWIFT_MAGMA2_HYDRO_IO_H */ diff --git a/src/hydro/MAGMA2/hydro_parameters.h b/src/hydro/MAGMA2/hydro_parameters.h new file mode 100644 index 0000000000..89228142a1 --- /dev/null +++ b/src/hydro/MAGMA2/hydro_parameters.h @@ -0,0 +1,295 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk) + * 2025 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef SWIFT_MAGMA2_HYDRO_PARAMETERS_H +#define SWIFT_MAGMA2_HYDRO_PARAMETERS_H + +/* Configuration file */ +#include + +/* Global headers */ +#if defined(HAVE_HDF5) +#include +#endif + +/* Local headers */ +#include "common_io.h" +#include "error.h" +#include "inline.h" + +/** + * @file MAGMA2/hydro_parameters.h + * @brief Density-Energy non-conservative implementation of SPH, + * with added MAGMA2 physics (Rosswog 2020) (default compile-time + * parameters). + * + * This file defines a number of things that are used in + * hydro_properties.c as defaults for run-time parameters + * as well as a number of compile-time parameters. + */ + +/* ---------- Viscosity & Conductivitiy parameters ---------- */ + +/*! Alpha viscosity, usually =1.0. For lower N_ngb, should be higher */ +#define const_viscosity_alpha 2.0 + +/*! Alpha conductivity, usually =0.05. At lower N_ngb, should be higher */ +#define const_conductivity_alpha 0.075 + +/*! Desired number of neighbours -- CRITICAL that this matches hydro props */ +#if defined(HYDRO_DIMENSION_1D) +#define const_kernel_target_neighbours 8.0 +#elif defined(HYDRO_DIMENSION_2D) +#define const_kernel_target_neighbours 34.0 +#else +#define const_kernel_target_neighbours 114.0 +#endif + +/* ---------- These parameters should not be changed ---------- */ + +/*! Use a Swift-like estimator for dh/dt rather than the correct formula + * 0 = Simple mass flow estimator + * 1 = Correct formula based on number density constraint + * 2 = Using v_ij dot G_ij with simple mass flow estimator + */ +#define hydro_props_dh_dt_estimator_type 0 + +/*! Flag to use Balsara limiter */ +#define hydro_props_use_balsara_limiter + +/*! Flag to use additional slope limiting procedures */ +// #define hydro_props_use_extra_slope_limiter + +/* Flag to disallow sign flip in reconstructed quantities */ +// #define hydro_props_use_strict_minmod_limiter + +/* Slope limiter length, fraction of max. distance in kernel */ +#ifdef hydro_props_use_extra_slope_limiter +#define const_grad_overshoot_length 0.25 + +/*! Slope limiter tolerance */ +#define const_grad_overshoot_tolerance 0.0 +#endif + +/* Viscosity floor when G_ij is extremely misaligned with dx_ij */ +#define const_viscosity_cosine_limit 0.1736 + +/* Viscosity weighting scheme: + * 0 = (rho_i * q_i + rho_j * q_j) / (rho_i * rho_j) + * 1 = (rho_i * q_ij + rho_j * q_ij) / (rho_i * rho_j) + * 2 = 2.0 * q_ij / (rho_i + rho_j) */ +#define hydro_props_viscosity_weighting_type 2 + +/* Flag to use radial gradients for viscosity and conductivity */ +// #define hydro_props_use_radial_artificial_terms + +/*! Use the correction terms to make the internal energy match the mass flux */ +// #define hydro_props_use_adiabatic_correction + +/* Kernel gradient weighting scheme: + * 0 = 0.5 * (G_i + G_j) + * 1 = 0.5 * (f_i * G_i + f_j * G_j) + * 2 = 0.5 * f_ij * (G_i + G_j) + * with f_ij = 0.5 * (f_i + f_j) + * 3 = 0.5 * f_ij * (G_i + G_j) + * with f_ij = 2 * f_i * f_j / (f_i + f_j) + * 4 = 0.5 * f_ij * (G_i + G_j) + * with f_ij = sqrt(f_i * f_j) + * 5 = 0.5 * f_ij * (G_i + G_j) + * with f_ij = (f_i * rho_i + f_j * rho_j) / (rho_i + rho_j) + * 6 = 0.5 * f_ij * (G_i + G_j) + * with f_ij = 2 * f_i * f_j / (f_i * rho_i + f_j * rho_j) + * 7 = 0.5 * f_ij * (G_i + G_j) + * with f_ij = (f_i * P_i + f_j * P_j) / (P_i + P_j) + * 8 = 0.5 * f_ij * (G_i + G_j) + * with f_ij = 2 * f_i * f_j / (f_i * P_i + f_j * P_j) + * 9 = 0.5 * f_ij * (G_i + G_j) + * with f_ij = (f_i * V_i + f_j * V_j) / (V_i + V_j) + */ +#define hydro_props_kernel_gradient_weighting 0 + +/*! Use double precision for all matrix/vector operations */ +// #define hydro_props_use_double_precision + +#ifdef hydro_props_use_double_precision +/*! Consider matrix inversion to be ill-conditioned above this limit */ +#define const_condition_number_upper_limit 300. +/*! Mean interparticle spacing for this kernel and neighbour number */ +#define const_kernel_mean_spacing \ + (kernel_gamma * \ + pow(4. * M_PI / (3. * (double)const_kernel_target_neighbours), 1. / 3.)) +#else +/*! Consider matrix inversion to be ill-conditioned above this limit */ +#define const_condition_number_upper_limit 60. +/*! Mean interparticle spacing for this kernel and neighbour number */ +#define const_kernel_mean_spacing \ + (kernel_gamma * \ + powf(4. * M_PI / (3. * (float)const_kernel_target_neighbours), 1. / 3.)) +#endif + +/*! eta_crit Rosswog 2020 Eq 23. Of order the mean interparticle spacing. */ +#define const_slope_limiter_eta_crit (const_kernel_mean_spacing) + +/*! eta_fold from Frontiere+'17 Equation 51 */ +#define const_slope_limiter_eta_fold 0.2 + +/*! Softening squared (epsilon^2) in Eq. 15 Rosswog 2020 */ +#define const_viscosity_epsilon2 0.01 + +/*! Cosmology default const_viscosity_beta=2*const_viscosity_alpha + * Beta is defined as in e.g. Price (2010) Eqn (103) */ +#define const_viscosity_beta (2.0 * const_viscosity_alpha) + +/*! Prefactor for alpha term in signal velocity */ +#define const_viscosity_alpha_prefactor \ + (1.25 * (1. + 0.75 * const_viscosity_alpha)) + +/*! Prefactor for beta term in signal velocity */ +#define const_viscosity_beta_prefactor (1.25 * 0.75 * const_viscosity_beta) + +/*! Fallback multiplier for alpha/beta terms to reduce spread */ +#define const_fallback_reduction_factor 0.25 + +/* ---------- Structures for below ---------- */ + +/*! Artificial viscosity parameters */ +struct viscosity_global_data {}; + +/*! Thermal diffusion parameters */ +struct diffusion_global_data {}; + +/* Functions for reading from parameter file */ + +/* Forward declartions */ +struct swift_params; +struct phys_const; +struct unit_system; + +/* Define float or double depending on hydro_props_use_double_precision */ +#if defined(hydro_props_use_double_precision) +typedef double hydro_real_t; +#else +typedef float hydro_real_t; +#endif + +/* Viscosity */ + +/** + * @brief Initialises the viscosity parameters in the struct from + * the parameter file, or sets them to defaults. + * + * @param params: the pointer to the swift_params file + * @param unit_system: pointer to the unit system + * @param phys_const: pointer to the physical constants system + * @param viscosity: pointer to the viscosity_global_data struct to be filled. + **/ +static INLINE void viscosity_init(struct swift_params *params, + const struct unit_system *us, + const struct phys_const *phys_const, + struct viscosity_global_data *viscosity) {} + +/** + * @brief Initialises a viscosity struct to sensible numbers for mocking + * purposes. + * + * @param viscosity: pointer to the viscosity_global_data struct to be filled. + **/ +static INLINE void viscosity_init_no_hydro( + struct viscosity_global_data *viscosity) {} + +/** + * @brief Prints out the viscosity parameters at the start of a run. + * + * @param viscosity: pointer to the viscosity_global_data struct found in + * hydro_properties + **/ +static INLINE void viscosity_print( + const struct viscosity_global_data *viscosity) { + message("Artificial viscosity alpha set to %.3f", const_viscosity_alpha); + message("Artificial viscosity beta set to %.3f", const_viscosity_beta); +} + +#if defined(HAVE_HDF5) +/** + * @brief Prints the viscosity information to the snapshot when writing. + * + * @param h_grpsph: the SPH group in the ICs to write attributes to. + * @param viscosity: pointer to the viscosity_global_data struct. + **/ +static INLINE void viscosity_print_snapshot( + hid_t h_grpsph, const struct viscosity_global_data *viscosity) { + + io_write_attribute_f(h_grpsph, "Alpha viscosity", const_viscosity_alpha); + io_write_attribute_f(h_grpsph, "Beta viscosity", const_viscosity_beta); +} +#endif + +/* Diffusion */ + +/** + * @brief Initialises the diffusion parameters in the struct from + * the parameter file, or sets them to defaults. + * + * @param params: the pointer to the swift_params file + * @param unit_system: pointer to the unit system + * @param phys_const: pointer to the physical constants system + * @param diffusion_global_data: pointer to the diffusion struct to be filled. + **/ +static INLINE void diffusion_init(struct swift_params *params, + const struct unit_system *us, + const struct phys_const *phys_const, + struct diffusion_global_data *diffusion) {} + +/** + * @brief Initialises a diffusion struct to sensible numbers for mocking + * purposes. + * + * @param diffusion: pointer to the diffusion_global_data struct to be filled. + **/ +static INLINE void diffusion_init_no_hydro( + struct diffusion_global_data *diffusion) {} + +/** + * @brief Prints out the diffusion parameters at the start of a run. + * + * @param diffusion: pointer to the diffusion_global_data struct found in + * hydro_properties + **/ +static INLINE void diffusion_print( + const struct diffusion_global_data *diffusion) { + message("Artificial conductivity alpha set to %.3f", + const_conductivity_alpha); +} + +#ifdef HAVE_HDF5 +/** + * @brief Prints the diffusion information to the snapshot when writing. + * + * @param h_grpsph: the SPH group in the ICs to write attributes to. + * @param diffusion: pointer to the diffusion_global_data struct. + **/ +static INLINE void diffusion_print_snapshot( + hid_t h_grpsph, const struct diffusion_global_data *diffusion) { + io_write_attribute_f(h_grpsph, "Conductivity alpha", + const_conductivity_alpha); +} +#endif + +#endif /* SWIFT_MAGMA2_HYDRO_PARAMETERS_H */ diff --git a/src/hydro/MAGMA2/hydro_part.h b/src/hydro/MAGMA2/hydro_part.h new file mode 100644 index 0000000000..400b45b99f --- /dev/null +++ b/src/hydro/MAGMA2/hydro_part.h @@ -0,0 +1,366 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk) & + * Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2025 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_MAGMA2_HYDRO_PART_H +#define SWIFT_MAGMA2_HYDRO_PART_H + +/** + * @file MAGMA2/hydro_part.h + * @brief Density-Energy non-conservative implementation of SPH, + * with added MAGMA2 physics (Rosswog 2020) (particle definition) + */ + +#include "adaptive_softening_struct.h" +#include "black_holes_struct.h" +#include "chemistry_struct.h" +#include "cooling_struct.h" +#include "csds.h" +#include "feedback_struct.h" +#ifdef WITH_FOF_GALAXIES +#include "fof_struct.h" +#endif +#include "fvpm_geometry_struct.h" +#include "hydro_parameters.h" +#include "mhd_struct.h" +#include "particle_splitting_struct.h" +#include "pressure_floor_struct.h" +#include "rt_struct.h" +#include "sink_struct.h" +#include "star_formation_struct.h" +#include "timestep_limiter_struct.h" +#include "tracers_struct.h" + +/** + * @brief Particle fields not needed during the SPH loops over neighbours. + * + * This structure contains the particle fields that are not used in the + * density or force loops. Quantities should be used in the kick, drift and + * potentially ghost tasks only. + */ +struct xpart { + /*! Offset between current position and position at last tree rebuild. */ + float x_diff[3]; + + /*! Offset between the current position and position at the last sort. */ + float x_diff_sort[3]; + + /*! Velocity at the last full step. */ + float v_full[3]; + + /*! Gravitational acceleration at the end of the last step */ + float a_grav[3]; + + /*! Internal energy at the last full step. */ + float u_full; + + /*! Additional data used to record particle splits */ + struct particle_splitting_data split_data; + + /*! Additional data used to record cooling information */ + struct cooling_xpart_data cooling_data; + + /* Additional data used by the tracers */ + struct tracers_xpart_data tracers_data; + + /* Additional data used by the SF routines */ + struct star_formation_xpart_data sf_data; + + /* Additional data used by the feedback */ + struct feedback_xpart_data feedback_data; + + /*! Additional data used by the MHD scheme */ + struct mhd_xpart_data mhd_data; + +#ifdef WITH_CSDS + /* Additional data for the particle csds */ + struct csds_part_data csds_data; +#endif + +} SWIFT_STRUCT_ALIGN; + +/** + * @brief Particle fields for the SPH particles + * + * The density and force substructures are used to contain variables only used + * within the density and force loops over neighbours. All more permanent + * variables should be declared in the main part of the part structure, + */ +struct part { + /*! Particle unique ID. */ + long long id; + + /*! Pointer to corresponding gravity part. */ + struct gpart *gpart; + + /*! Particle position. */ + double x[3]; + + /*! Particle predicted velocity. */ + float v[3]; + + /*! Particle acceleration. */ + float a_hydro[3]; + + /*! Particle mass. */ + float mass; + + /*! Particle smoothing length. */ + float h; + + /*! Particle internal energy. */ + float u; + + /*! Time derivative of the internal energy. */ + float u_dt; + + /*! Particle density. */ + float rho; + + /*! Particle density gradient */ + float rho_gradient[3]; + + /*! Minimum smoothing length in the kernel */ + float h_min; + + /*! Minimum time-step amongst neighbours */ + float dt_min; + + /*! Conduction du/dt */ + float u_dt_cond; + +#ifdef WITH_FOF_GALAXIES + /*! Additional data used to record host galaxy information */ + struct fof_galaxy_data galaxy_data; +#endif + +#ifdef MAGMA2_DEBUG_CHECKS + struct { + /*! Correction matrix at the last time it was ill-conditioned */ + hydro_real_t correction_matrix[3][3]; + + /*! Velocity tensor at ill-condition time */ + hydro_real_t velocity_tensor_aux[3][3]; + + /*! Velocity tensor norm ill-conditioned */ + hydro_real_t velocity_tensor_aux_norm[3][3]; + + /*! u_aux tensor at ill-condition time */ + hydro_real_t u_aux[3]; + + /*! u_aux_norm tensor ill-conditioned */ + hydro_real_t u_aux_norm[3]; + + /*! Number of times correction_matrix was ill-conditioned */ + int C_ill_conditioned_count; + + /*! Number of times velocity_tensor_aux_norm was ill-conditioned */ + int D_ill_conditioned_count; + + /*! Number of times u_aux_norm was ill-conditioned */ + int u_ill_conditioned_count; + + /*! How many low-order SPH gradients in force interactions */ + int N_force_low_order_grad; + + /*! How many high-order SPH gradients in force interactions */ + int N_force_high_order_grad; + + /*! Number of neighbors in the kernel */ + int num_ngb; + + } debug; +#endif + + /* Store gradients in a separate struct */ + struct { +#ifdef hydro_props_use_adiabatic_correction + /*! Adiabatic kernel correction factor numerator */ + hydro_real_t adiabatic_f_numerator; + + /*! Adiabatic kernel correction factor denominator */ + hydro_real_t adiabatic_f_denominator; +#endif + + /*! Sum of the kernel weights */ + hydro_real_t wcount; + + /*! Correction matrix (C^ki in Rosswog 2020) */ + hydro_real_t correction_matrix[3][3]; + + /*! Flag for whether C is ill-conditioned */ + char C_well_conditioned; + + /*! Full velocity gradient tensor */ + hydro_real_t velocity_tensor[3][3]; + + /*! Auxiliary full velocity gradient tensor */ + hydro_real_t velocity_tensor_aux[3][3]; + + /*! Flag for whether D (velocity_tensor_aux) is ill-conditioned */ + char D_well_conditioned; + + /*! Normalization for computing velocity_tensor_aux */ + hydro_real_t velocity_tensor_aux_norm[3][3]; + + /*! Hessian tensor */ + hydro_real_t velocity_hessian[3][3][3]; + + /*! Internal energy gradient */ + hydro_real_t u[3]; + + /*! Auxiliary internal energy gradient */ + hydro_real_t u_aux[3]; + + /*! Normalization for computing u_aux */ + hydro_real_t u_aux_norm[3]; + + /*! Flag for whether u_aux_norm is ill-conditioned */ + char u_well_conditioned; + + /*! Internal energy Hessian */ + hydro_real_t u_hessian[3][3]; + + /*! Minimum delta u across kernel */ + hydro_real_t du_min; + + /*! Maximum delta u across kernel */ + hydro_real_t du_max; + + /*! Minimum dv across kernel */ + hydro_real_t dv_min[3]; + + /*! Maximum dv across kernel */ + hydro_real_t dv_max[3]; + + /*! Kernel size for slope limiting */ + hydro_real_t kernel_size; + + /*! Balsara limiter for divergences */ + hydro_real_t balsara; + + } gradients; + + /* Store density/force specific stuff. */ + union { + /** + * @brief Structure for the variables only used in the density loop over + * neighbours. + * + * Quantities in this sub-structure should only be accessed in the density + * loop over neighbours and the ghost task. + */ + struct { + /*! Neighbour number count. */ + float wcount; + + /*! Derivative of the neighbour number with respect to h. */ + float wcount_dh; + + /*! Derivative of density with respect to h */ + float rho_dh; + + /*! Particle velocity divergence. */ + float div_v; + } density; + + /** + * @brief Structure for the variables only used in the force loop over + * neighbours. + * + * Quantities in this sub-structure should only be accessed in the force + * loop over neighbours and the ghost, drift and kick tasks. + */ + struct { + /*! "Grad h" term -- only partial in P-U */ + float f; + + /*! Particle pressure. */ + float pressure; + + /*! Particle soundspeed. */ + float soundspeed; + + /*! Time derivative of smoothing length */ + float h_dt; + + } force; + }; + + /*! Additional data used for adaptive softening */ + struct adaptive_softening_part_data adaptive_softening_data; + + /*! Additional data used by the MHD scheme */ + struct mhd_part_data mhd_data; + + /*! Chemistry information */ + struct chemistry_part_data chemistry_data; + + /*! Cooling information */ + struct cooling_part_data cooling_data; + + /*! Additional data used by the feedback */ + struct feedback_part_data feedback_data; + + /*! Black holes information (e.g. swallowing ID) */ + struct black_holes_part_data black_holes_data; + + /* Additional data used by the SF routines */ + struct star_formation_part_data sf_data; + + /*! Sink information (e.g. swallowing ID) */ + struct sink_part_data sink_data; + + /*! Additional data used by the pressure floor */ + struct pressure_floor_part_data pressure_floor_data; + + /*! Additional Radiative Transfer Data */ + struct rt_part_data rt_data; + + /*! RT sub-cycling time stepping data */ + struct rt_timestepping_data rt_time_data; + + /*! Time-step length */ + timebin_t time_bin; + + /*! Tree-depth at which size / 2 <= h * gamma < size */ + char depth_h; + + /*! Time-step limiter information */ + struct timestep_limiter_data limiter_data; + + /*! Flag to indicate particle is decoupled */ + int decoupled; + + /*! Geometrical quantities used for Finite Volume Particle Method RT. */ + struct fvpm_geometry_struct geometry; + +#ifdef SWIFT_DEBUG_CHECKS + + /* Time of the last drift */ + integertime_t ti_drift; + + /* Time of the last kick */ + integertime_t ti_kick; + +#endif + +} SWIFT_STRUCT_ALIGN; + +#endif /* SWIFT_MAGMA2_HYDRO_PART_H */ diff --git a/src/hydro_csds.h b/src/hydro_csds.h index a0593ca7b3..088f9f6341 100644 --- a/src/hydro_csds.h +++ b/src/hydro_csds.h @@ -53,6 +53,8 @@ #include "./hydro/SPHENIX/hydro_csds.h" #elif defined(GASOLINE_SPH) #error TODO +#elif defined(MAGMA2_SPH) +#error TODO #elif defined(ANARCHY_PU_SPH) #error TODO #else diff --git a/src/hydro_io.h b/src/hydro_io.h index 5a64a284cc..5920dd0a9b 100644 --- a/src/hydro_io.h +++ b/src/hydro_io.h @@ -49,6 +49,8 @@ #include "./hydro/SPHENIX/hydro_io.h" #elif defined(GASOLINE_SPH) #include "./hydro/Gasoline/hydro_io.h" +#elif defined(MAGMA2_SPH) +#include "./hydro/MAGMA2/hydro_io.h" #elif defined(ANARCHY_PU_SPH) #include "./hydro/AnarchyPU/hydro_io.h" #else diff --git a/src/hydro_parameters.h b/src/hydro_parameters.h index 46d93ad43a..a370c7df71 100644 --- a/src/hydro_parameters.h +++ b/src/hydro_parameters.h @@ -58,6 +58,8 @@ #include "./hydro/SPHENIX/hydro_parameters.h" #elif defined(GASOLINE_SPH) #include "./hydro/Gasoline/hydro_parameters.h" +#elif defined(MAGMA2_SPH) +#include "./hydro/MAGMA2/hydro_parameters.h" #elif defined(ANARCHY_PU_SPH) #include "./hydro/AnarchyPU/hydro_parameters.h" #else diff --git a/src/hydro_properties.c b/src/hydro_properties.c index f8b547bc56..ff184a166b 100644 --- a/src/hydro_properties.c +++ b/src/hydro_properties.c @@ -63,19 +63,56 @@ void hydro_props_init(struct hydro_props *p, /* ------ Smoothing lengths parameters ---------- */ /* Kernel properties */ - p->eta_neighbours = parser_get_param_float(params, "SPH:resolution_eta"); + p->eta_neighbours = + parser_get_opt_param_float(params, "SPH:resolution_eta", 0.f); + + /* Target number of neighbours (optional) */ + p->target_neighbours = + parser_get_opt_param_float(params, "SPH:target_neighbours", 0.f); + + if (p->eta_neighbours <= 0.f && p->target_neighbours <= 0.f) { + error( + "You must set either SPH:resolution_eta or SPH:target_neighbours " + "in the parameter file."); + } /* Tolerance for the smoothing length Newton-Raphson scheme */ p->h_tolerance = parser_get_opt_param_float(params, "SPH:h_tolerance", hydro_props_default_h_tolerance); /* Get derived properties */ - p->target_neighbours = pow_dimension(p->eta_neighbours) * kernel_norm; + /* Target number of neighbours */ + if (p->eta_neighbours > 0.f) { + p->target_neighbours = pow_dimension(p->eta_neighbours) * kernel_norm; + } else { + p->eta_neighbours = + powf(p->target_neighbours / kernel_norm, hydro_dimension_inv); + } + const float delta_eta = p->eta_neighbours * (1.f + p->h_tolerance); p->delta_neighbours = (pow_dimension(delta_eta) - pow_dimension(p->eta_neighbours)) * kernel_norm; +#ifdef MAGMA2_SPH +#ifndef const_kernel_target_neighbours + error( + "When using MAGMA2 SPH, the constant " + "const_kernel_target_neighbours must be defined in the header file " + "hydro_parameters.h. This is a compile-time constant and must be set " + "to the desired number of neighbours in the parameter file."); +#else + if (fabsf((float)const_kernel_target_neighbours - p->target_neighbours) > + 0.05f * p->target_neighbours) { + error( + "When using MAGMA2 SPH, the compiled constant " + "const_kernel_target_neighbours (%g) must be within 5 percent of " + "the desired number of neighbours (%g) in the parameter file.", + (float)const_kernel_target_neighbours, p->target_neighbours); + } +#endif +#endif + #ifdef SHADOWFAX_SPH /* change the meaning of target_neighbours and delta_neighbours */ p->target_neighbours = 1.0f; diff --git a/src/part.h b/src/part.h index 08fb3121f8..f6385cb8c0 100644 --- a/src/part.h +++ b/src/part.h @@ -93,6 +93,10 @@ struct threadpool; #include "./hydro/Gasoline/hydro_part.h" #define hydro_need_extra_init_loop 0 #define EXTRA_HYDRO_LOOP +#elif defined(MAGMA2_SPH) +#include "./hydro/MAGMA2/hydro_part.h" +#define hydro_need_extra_init_loop 0 +#define EXTRA_HYDRO_LOOP #elif defined(ANARCHY_PU_SPH) #include "./hydro/AnarchyPU/hydro_part.h" #define hydro_need_extra_init_loop 0 @@ -119,6 +123,8 @@ struct threadpool; #include "./stars/EAGLE/stars_part.h" #elif defined(STARS_GEAR) #include "./stars/GEAR/stars_part.h" +#elif defined(STARS_KIARA) +#include "./stars/KIARA/stars_part.h" #else #error "Invalid choice of star particle" #endif @@ -130,6 +136,8 @@ struct threadpool; #include "./black_holes/EAGLE/black_holes_part.h" #elif defined(BLACK_HOLES_SPIN_JET) #include "./black_holes/SPIN_JET/black_holes_part.h" +#elif defined(BLACK_HOLES_OBSIDIAN) +#include "./black_holes/Obsidian/black_holes_part.h" #else #error "Invalid choice of black hole particle" #endif diff --git a/src/rt.h b/src/rt.h index 43abd3d0b2..fdeb597ede 100644 --- a/src/rt.h +++ b/src/rt.h @@ -38,6 +38,9 @@ #elif defined(RT_GEAR) #include "./rt/GEAR/rt.h" #include "./rt/GEAR/rt_iact.h" +#elif defined(RT_KIARA) +#include "./rt/KIARA/rt.h" +#include "./rt/KIARA/rt_iact.h" #elif defined(RT_SPHM1RT) #include "./rt/SPHM1RT/rt.h" #include "./rt/SPHM1RT/rt_iact.h" diff --git a/src/rt/KIARA/rt.h b/src/rt/KIARA/rt.h new file mode 100644 index 0000000000..5a051f8f45 --- /dev/null +++ b/src/rt/KIARA/rt.h @@ -0,0 +1,803 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2020 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_KIARA_H +#define SWIFT_RT_KIARA_H + +#include "rt_debugging.h" +#include "rt_flux.h" +#include "rt_gradients.h" +#include "rt_properties.h" +/* #include "rt_slope_limiters_cell.h" [> skipped for now <] */ +#include "rt_stellar_emission_model.h" +#include "rt_thermochemistry.h" + +#include + +/** + * @file src/rt/KIARA/rt.h + * @brief Main header file for the KIARA M1 Closure radiative transfer scheme. + */ + +/** + * @brief Compute the photon emission rates for this stellar particle. + * This function is called every time the spart is being reset + * (during start-up and during stars ghost if spart is active) + * and assumes that the photon emission rate is an intrinsic + * stellar property, i.e. doesn't depend on the environment. + * + * @param sp star particle to work on + * @param time current system time + * @param star_age age of the star *at the end of the step* + * @param dt star time step + * @param rt_props RT properties struct + * @param phys_const physical constants struct + * @param internal_units struct holding internal units + */ +__attribute__((always_inline)) INLINE static void +rt_compute_stellar_emission_rate(struct spart* restrict sp, double time, + double star_age, double dt, + const struct rt_props* rt_props, + const struct phys_const* phys_const, + const struct unit_system* internal_units) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + sp->rt_data.debug_emission_rate_set += 1; +#endif + + /* Skip initial fake time-step */ + if (dt == 0.0l) return; + + if (time == 0.l) { + /* if function is called before the first actual step, time is still + * at zero unless specified otherwise in parameter file.*/ + /* We're going to need the star age later for more sophistiscated models, + * but for now the compiler won't let me get away with keeping this here, + * so keep it as a comment. */ + star_age = dt; + } + + /* TODO: this is for later, when we use more sophisticated models. */ + /* now get the emission rates */ + double star_age_begin_of_step = star_age - dt; + star_age_begin_of_step = max(0.l, star_age_begin_of_step); + + double emission_this_step[RT_NGROUPS]; + for (int g = 0; g < RT_NGROUPS; g++) emission_this_step[g] = 0.; + + if (rt_props->stellar_emission_model == rt_stellar_emission_model_const) { + rt_get_emission_this_step_const(emission_this_step, + rt_props->stellar_const_emission_rates, dt); + } else if (rt_props->stellar_emission_model == + rt_stellar_emission_model_IlievTest) { + rt_get_emission_this_step_IlievTest( + emission_this_step, sp->mass, dt, rt_props->photon_number_integral, + rt_props->average_photon_energy, phys_const, internal_units); + } else if (rt_props->stellar_emission_model == + rt_stellar_emission_model_BPASS) { + rt_get_emission_this_step_BPASS( + emission_this_step, sp->mass, sp->chemistry_data.metal_mass_fraction_total, + star_age_begin_of_step, star_age, rt_props->ionizing_tables, + rt_props->average_photon_energy, phys_const, internal_units, rt_props->f_esc); + /* Convert some quantities. */ + const double time_to_Myr = units_cgs_conversion_factor(internal_units, UNIT_CONV_TIME) / + (365.25f * 24.f * 60.f * 60.f * 1e6f); + const double energy_units = + units_cgs_conversion_factor(internal_units, UNIT_CONV_ENERGY); + if(star_age_begin_of_step * time_to_Myr < 5) message("RT_energy: id=%lld dE=%g (%g erg) age_start=%g age_now=%g Z=%g", sp->id, emission_this_step[0], emission_this_step[0] * energy_units, star_age_begin_of_step * time_to_Myr, star_age * time_to_Myr, sp->chemistry_data.metal_mass_fraction_total); + + } else { + error("Unknown stellar emission rate model %d", + rt_props->stellar_emission_model); + } + + for (int g = 0; g < RT_NGROUPS; g++) + sp->rt_data.emission_this_step[g] = emission_this_step[g]; +} + +/** + * @brief Initialisation of the RT density loop related particle data. + * Note: during initalisation (space_init), rt_reset_part and rt_init_part + * are both called individually. + * + * @param p Particle to work on + */ +__attribute__((always_inline)) INLINE static void rt_init_part( + struct part* restrict p) { + + /* Correct the energy densities and fluxes for change in volume */ + float h_ratio = p->rt_data.h_previous / p->h; + const float h_ratio2 = h_ratio * h_ratio; + for (int g = 0; g < RT_NGROUPS; g++) { + p->rt_data.radiation[g].energy_density *= h_ratio * h_ratio2; + p->rt_data.radiation[g].flux[0] *= h_ratio2; + p->rt_data.radiation[g].flux[1] *= h_ratio2; + p->rt_data.radiation[g].flux[2] *= h_ratio2; + } + p->rt_data.h_previous = p->h; +} + +/** + * @brief Reset the RT hydro particle data not related to the hydro density. + * Note: during initalisation (space_init), rt_reset_part and rt_init_part + * are both called individually. To reset RT data needed in each RT sub-cycle, + * use rt_reset_part_each_subcycle(). + * + * @param p particle to work on + * @param cosmo Cosmology. + */ +__attribute__((always_inline)) INLINE static void rt_reset_part( + struct part* restrict p, const struct cosmology* cosmo) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + /* reset this here as well as in the rt_debugging_checks_end_of_step() + * routine to test task dependencies are done right */ + p->rt_data.debug_iact_stars_inject = 0; + p->rt_data.debug_nsubcycles = 0; + p->rt_data.debug_kicked = 0; +#endif +} + +/** + * @brief Reset RT particle data which needs to be reset each sub-cycle. + * + * @param p the particle to work on + * @param cosmo Cosmology. + * @param dt the current particle RT time step + */ +__attribute__((always_inline)) INLINE static void rt_reset_part_each_subcycle( + struct part* restrict p, const struct cosmology* cosmo, double dt) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + rt_debugging_reset_each_subcycle(p); +#endif + + rt_gradients_init(p); + /* the Gizmo-style slope limiting doesn't help for RT as is, + * so we're skipping it for now. */ + /* rt_slope_limit_cell_init(p); */ + + p->rt_data.flux_dt = dt; +} + +/** + * @brief First initialisation of the RT hydro particle data. + * + * @param p particle to work on + * @param cosmo #cosmology data structure. + * @param rt_props RT properties struct + */ +__attribute__((always_inline)) INLINE static void rt_first_init_part( + struct part* restrict p, const struct cosmology* cosmo, + const struct rt_props* restrict rt_props) { + + /* Don't reset conserved quantities here! ICs will be overwritten */ + rt_init_part(p); + rt_reset_part(p, cosmo); + rt_part_reset_mass_fluxes(p); + rt_reset_part_each_subcycle(p, cosmo, 0.); + rt_part_reset_fluxes(p); + +#ifdef SWIFT_RT_DEBUG_CHECKS + p->rt_data.debug_radiation_absorbed_tot = 0ULL; +#endif + + p->rt_data.h_previous = p->h; +} + +/** + * @brief Initialisation of the RT density loop related star particle data. + * Note: during initalisation (space_init), rt_reset_spart and rt_init_spart + * are both called individually. + * + * @param sp star particle to work on + */ +__attribute__((always_inline)) INLINE static void rt_init_spart( + struct spart* restrict sp) { + + for (int i = 0; i < 8; i++) { + sp->rt_data.octant_weights[i] = 0.f; + } + +#ifdef SWIFT_RT_DEBUG_CHECKS + /* reset this here as well as in the rt_debugging_checks_end_of_step() + * routine to test task dependencies are done right */ + sp->rt_data.debug_iact_hydro_inject_prep = 0; + sp->rt_data.debug_iact_hydro_inject = 0; + sp->rt_data.debug_emission_rate_set = 0; + + for (int g = 0; g < RT_NGROUPS; g++) { + sp->rt_data.debug_injected_energy[g] = 0.f; + } + for (int g = 0; g < RT_NGROUPS; g++) { + sp->rt_data.emission_this_step[g] = 0.f; + } + sp->rt_data.debug_psi_sum = 0.f; +#endif +} + +/** + * @brief Reset of the RT star particle data not related to the density. + * Note: during initalisation (space_init), rt_reset_spart and rt_init_spart + * are both called individually. + * + * @param sp star particle to work on + */ +__attribute__((always_inline)) INLINE static void rt_reset_spart( + struct spart* restrict sp) { + + for (int g = 0; g < RT_NGROUPS; g++) { + sp->rt_data.emission_this_step[g] = 0.f; + } +} + +/** + * @brief First initialisation of the RT star particle data. + * + * @param sp star particle to work on + */ +__attribute__((always_inline)) INLINE static void rt_first_init_spart( + struct spart* restrict sp) { + + rt_init_spart(sp); + rt_reset_spart(sp); +#ifdef SWIFT_RT_DEBUG_CHECKS + sp->rt_data.debug_radiation_emitted_tot = 0ULL; + for (int g = 0; g < RT_NGROUPS; g++) { + sp->rt_data.debug_injected_energy_tot[g] = 0.f; + } +#endif +} + +/** + * @brief Split the RT data of a particle into n pieces + * + * @param p The #part. + * @param n The number of pieces to split into. + */ +__attribute__((always_inline)) INLINE static void rt_split_part(struct part* p, + double n) { + error("RT can't run with split particles for now."); +} + +/** + * @brief Exception handle a hydro part not having any neighbours in ghost task + * + * @param p The #part. + */ +__attribute__((always_inline)) INLINE static void rt_part_has_no_neighbours( + struct part* p) { + message("WARNING: found particle without neighbours"); +} + +/** + * @brief Exception handle a star part not having any neighbours in ghost task + * + * @param sp The #spart. + */ +__attribute__((always_inline)) INLINE static void rt_spart_has_no_neighbours( + struct spart* sp) { + + /* Reset energy to be injected so that global statistics + * checks still work */ + for (int g = 0; g < RT_NGROUPS; g++) { + sp->rt_data.emission_this_step[g] = 0.f; + } + message("WARNING: found star without neighbours"); +} + +/** + * @brief Do checks/conversions on particles on startup. + * + * @param p The particle to work on + * @param rt_props The RT properties struct + * @param hydro_props The hydro properties struct + * @param phys_const physical constants struct + * @param us unit_system struct + * @param cosmo cosmology struct + */ +__attribute__((always_inline)) INLINE static void rt_convert_quantities( + struct part* restrict p, struct xpart* restrict xp, const struct rt_props* rt_props, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct unit_system* restrict us, + const struct cooling_function_data* cooling, + const struct cosmology* restrict cosmo) { + + /* If we're reducing the speed of light, then we may encounter + * photon fluxes which are way too high than the physically + * allowable limit. This can lead to catastrophic problems for + * the propagation of photons, as the pressure tensor assumes + * the upper limit to be respected. So check this and correct + * it if necessary. + * We only read in conserved quantities, so only check those. */ + + struct rt_part_data* rtd = &p->rt_data; + const float Vinv = 1.f / p->geometry.volume; + + /* If we read in radiation energy, we read in + * total energy and store it as energy density. + * Same for fluxes. + * Correct that now. */ + for (int g = 0; g < RT_NGROUPS; g++) { + rtd->radiation[g].energy_density *= Vinv; + rtd->radiation[g].flux[0] *= Vinv; + rtd->radiation[g].flux[1] *= Vinv; + rtd->radiation[g].flux[2] *= Vinv; + + /* Additional check with possible exit for ICs */ + rt_check_unphysical_state_ICs(p, g, &rtd->radiation[g].energy_density, + rtd->radiation[g].flux, + phys_const->const_speed_light_c); + /* Check for too high fluxes */ + rt_check_unphysical_state(&rtd->radiation[g].energy_density, + rtd->radiation[g].flux, /*e_old =*/0.f, + /*callloc=*/0); + } + + /* If we're setting up ionising equilibrium initial conditions, + * then the particles need to have their densities known first. + * So we can call the mass fractions initialization now. */ + rt_tchem_first_init_part(p, xp, rt_props, hydro_props, phys_const, us, cooling, cosmo); +} + +/** + * @brief Computes the next radiative transfer time step size + * of a given particle (during timestep tasks) + * + * @param p Particle to work on. + * @param xp Particle extra data. + * @param rt_props RT properties struct. + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param dt The time-step of this particle. + */ +__attribute__((always_inline)) INLINE static float rt_compute_timestep( + const struct part* restrict p, const struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us) { + + /* If no radiation, it's not doing RT, so don't limit timestep */ + float radiation_energy_density[RT_NGROUPS], total_energy_density = 0.f; + rt_part_get_physical_radiation_energy_density(p, radiation_energy_density, cosmo); + for (int i=0; ia * cosmo->a * + powf(p->geometry.volume / hydro_dimension_unit_sphere, + hydro_dimension_inv); + float dt = psize * rt_params.reduced_speed_of_light_inverse * + rt_props->CFL_condition; + + if (rt_props->skip_thermochemistry) return dt; + + float dt_cool = 1e20f; + //if (rt_props->f_limit_cooling_time > 0.f) + /* Note: cooling time may be negative if the gas is being heated */ + // dt_cool = rt_props->f_limit_cooling_time * + // rt_tchem_get_tchem_time(p, xp, rt_props, cosmo, hydro_props, + // phys_const, cooling, us); + + return min(dt, fabsf(dt_cool)); +} + +/** + * @brief Computes the next radiative transfer time step size + * of a given star particle (during timestep tasks). + * + * @param sp spart to work on + * @param rt_props the RT properties struct + * @param cosmo the cosmology + */ +__attribute__((always_inline)) INLINE static float rt_compute_spart_timestep( + const struct spart* restrict sp, const struct rt_props* restrict rt_props, + const struct cosmology* restrict cosmo) { + + /* For now, the only thing we care about is the upper threshold for stars. */ + return rt_props->stars_max_timestep; +} + +/** + * @brief Compute the time-step length for an RT step of a particle from given + * integer times ti_beg and ti_end. This time-step length is then used to + * compute the actual time integration of the transport/force step and the + * thermochemistry. This is not used to determine the time-step length during + * the time-step tasks. + * + * @param ti_beg Start of the time-step (on the integer time-line). + * @param ti_end End of the time-step (on the integer time-line). + * @param time_base Minimal time-step size on the time-line. + * @param with_cosmology Are we running with cosmology integration? + * @param cosmo The #cosmology object. + * + * @return The time-step size for the rt integration. (internal units). + */ +__attribute__((always_inline)) INLINE static double rt_part_dt( + const integertime_t ti_beg, const integertime_t ti_end, + const double time_base, const int with_cosmology, + const struct cosmology* cosmo) { + if (with_cosmology) { + return cosmology_get_delta_time(cosmo, ti_beg, ti_end); + } else { + return (ti_end - ti_beg) * time_base; + } +} + +/** + * @brief Compute the time-step length for an RT step of a particle from given + * integer times ti_beg and ti_end. This time-step length is then used to + * compute the actual time integration of the transport/force step and the + * thermochemistry. This is not used to determine the time-step length during + * the time-step tasks. + * + * @param ti_beg Start of the time-step (on the integer time-line). + * @param ti_end End of the time-step (on the integer time-line). + * @param time_base Minimal time-step size on the time-line. + * @param with_cosmology Are we running with cosmology integration? + * @param cosmo The #cosmology object. + * + * @return The time-step size for the rt integration. (internal units). + */ +__attribute__((always_inline)) INLINE static double rt_part_dt_therm( + const integertime_t ti_beg, const integertime_t ti_end, + const double time_base, const int with_cosmology, + const struct cosmology* cosmo) { + if (with_cosmology) { + return cosmology_get_therm_kick_factor(cosmo, ti_beg, ti_end); + } else { + return (ti_end - ti_beg) * time_base; + } +} + +/** + * @brief This function finalises the injection step. + * + * @param p particle to work on + * @param props struct #rt_props that contains global RT properties + */ +__attribute__((always_inline)) INLINE static void rt_finalise_injection( + struct part* restrict p, struct rt_props* props) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + rt_debug_sequence_check(p, 1, "rt_ghost1/rt_finalise_injection"); + + p->rt_data.debug_injection_done += 1; +#endif + + for (int g = 0; g < RT_NGROUPS; g++) { + rt_check_unphysical_state(&p->rt_data.radiation[g].energy_density, + p->rt_data.radiation[g].flux, /*e_old=*/0.f, + /*callloc=*/3); + } +} + +/** + * @brief finishes up the gradient computation + * + * @param p particle to work on + * @param cosmo #cosmology data structure. + */ +__attribute__((always_inline)) INLINE static void rt_end_gradient( + struct part* restrict p, const struct cosmology* cosmo) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + rt_debug_sequence_check(p, 2, __func__); + + if (p->rt_data.debug_calls_iact_gradient_interaction == 0) + message( + "WARNING: Called finalise gradient on particle %lld" + "with iact gradient count from rt_iact = %d", + p->id, p->rt_data.debug_calls_iact_gradient_interaction); + + p->rt_data.debug_gradients_done += 1; +#endif + + rt_finalise_gradient_part(p); +} + +/** + * @brief finishes up the transport step + * + * @param p particle to work on + * @param dt the current time step of the particle + * @param cosmo #cosmology data structure. + */ +__attribute__((always_inline)) INLINE static void rt_finalise_transport( + struct part* restrict p, struct rt_props* rtp, const double dt, + const struct cosmology* restrict cosmo) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + rt_debug_sequence_check(p, 3, __func__); + + if (p->rt_data.debug_calls_iact_transport_interaction == 0) + message( + "WARNING: Called finalise transport on particle %lld" + "with iact transport count from rt_iact = %d", + p->id, p->rt_data.debug_calls_iact_transport_interaction); + + p->rt_data.debug_transport_done += 1; +#endif + + struct rt_part_data* restrict rtd = &p->rt_data; + const float Vinv = 1.f / p->geometry.volume; + + /* Do not redshift if we have a constant spectrum (type == 0) */ + const float redshift_factor = + (rtp->stellar_spectrum_type == 0) ? 0. : cosmo->H * dt; + + for (int g = 0; g < RT_NGROUPS; g++) { + const float e_old = rtd->radiation[g].energy_density; + + /* Note: in this scheme, we're updating d/dt (U * V) + sum F * A * dt = 0. + * So we'll need the division by the volume here. */ + + rtd->radiation[g].energy_density += rtd->flux[g].energy * Vinv; + rtd->radiation[g].energy_density -= + rtd->radiation[g].energy_density * + redshift_factor; // Energy lost due to redshift + + rtd->radiation[g].flux[0] += rtd->flux[g].flux[0] * Vinv; + rtd->radiation[g].flux[0] -= + rtd->radiation[g].flux[0] * + redshift_factor; // Energy lost due to redshift + + rtd->radiation[g].flux[1] += rtd->flux[g].flux[1] * Vinv; + rtd->radiation[g].flux[1] -= + rtd->radiation[g].flux[1] * + redshift_factor; // Energy lost due to redshift + + rtd->radiation[g].flux[2] += rtd->flux[g].flux[2] * Vinv; + rtd->radiation[g].flux[2] -= + rtd->radiation[g].flux[2] * + redshift_factor; // Energy lost due to redshift + + rt_check_unphysical_state(&rtd->radiation[g].energy_density, + rtd->radiation[g].flux, e_old, /*callloc=*/4); + } + + /* Reset the fluxes now that they have been applied. */ + rt_part_reset_fluxes(p); + /* Mark the particle as inactive for now. */ + rtd->flux_dt = -1.f; +} + +/** + * @brief Do the thermochemistry on a particle. + * + * This function wraps around rt_do_thermochemistry function. + * + * @param p Particle to work on. + * @param xp Pointer to the particle' extended data. + * @param rt_props RT properties struct + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param floor_props Properties of the entropy floor. + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param dt The time-step of this particle. + * @param dt_therm The time-step operator used for thermal quantities. + * @param time The current time (since the Big Bang or start of the run) in + * internal units. + */ +__attribute__((always_inline)) INLINE static void rt_tchem( + struct part* restrict p, struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct entropy_floor_properties* floor_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us, const double dt, + const double dt_therm, const double time) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + rt_debug_sequence_check(p, 4, __func__); + p->rt_data.debug_thermochem_done += 1; +#endif + + if (rt_props->rt_with_galaxy_subgrid) { + rt_do_thermochemistry_with_subgrid(p, xp, rt_props, cosmo, hydro_props, floor_props, + phys_const, cooling, us, dt, dt_therm, + 0); + + /* Record this cooling event */ + xp->cooling_data.time_last_event = time; + } else { + /* Note: Can't pass rt_props as const struct because of grackle + * accessinging its properties there */ + rt_do_thermochemistry(p, xp, rt_props, cosmo, hydro_props, phys_const, cooling, us, dt, dt_therm, + 0); + }//RT_WITH_COOLING_SUBGRID we couple rt with some subgrid physics properties. +} + +/** + * @brief Extra operations done during the kick. This needs to be + * done before the particle mass is updated in the hydro_kick_extra. + * + * @param p Particle to act upon. + * @param dt_therm Thermal energy time-step @f$\frac{dt}{a^2}@f$. + * @param dt_grav Gravity time-step @f$\frac{dt}{a}@f$. + * @param dt_hydro Hydro acceleration time-step + * @f$\frac{dt}{a^{3(\gamma{}-1)}}@f$. + * @param dt_kick_corr Gravity correction time-step @f$adt@f$. + * @param cosmo Cosmology. + * @param hydro_props Additional hydro properties. + */ +__attribute__((always_inline)) INLINE static void rt_kick_extra( + struct part* p, float dt_therm, float dt_grav, float dt_hydro, + float dt_kick_corr, const struct cosmology* cosmo, + const struct hydro_props* hydro_props) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + /* Don't account for timestep_sync backward kicks */ + if (dt_therm >= 0.f && dt_grav >= 0.f && dt_hydro >= 0.f && + dt_kick_corr >= 0.f) { + + rt_debug_sequence_check(p, 0, __func__); + p->rt_data.debug_kicked += 1; + } +#endif + +#ifdef GIZMO_MFV_SPH + + /* Note: We need to mimick here what Gizmo does for the mass fluxes. + * The relevant time scale is the hydro time step for the mass fluxes, + * not the RT times. We also need to prevent the kick to apply the mass + * fluxes twice, so exit if the particle time step < 0 */ + + if (p->flux.dt > 0.0f) { + /* Update the mass fraction changes due to interparticle fluxes */ + const float current_mass = p->conserved.mass; + + if ((current_mass <= 0.f) || (p->rho <= 0.f)) { + /* Deal with vacuum. Let hydro deal with actuall mass < 0, just do your + * mass fractions thing. */ + p->rt_data.tchem.mass_fraction_HI = 0.f; + p->rt_data.tchem.mass_fraction_HII = 0.f; + p->rt_data.tchem.mass_fraction_HeI = 0.f; + p->rt_data.tchem.mass_fraction_HeII = 0.f; + p->rt_data.tchem.mass_fraction_HeIII = 0.f; + rt_part_reset_mass_fluxes(p); + return; + } + + const float current_mass_HI = + current_mass * p->rt_data.tchem.mass_fraction_HI; + const float current_mass_HII = + current_mass * p->rt_data.tchem.mass_fraction_HII; + const float current_mass_HeI = + current_mass * p->rt_data.tchem.mass_fraction_HeI; + const float current_mass_HeII = + current_mass * p->rt_data.tchem.mass_fraction_HeII; + const float current_mass_HeIII = + current_mass * p->rt_data.tchem.mass_fraction_HeIII; + + /* At this point, we're exchanging (time integrated) mass fluxes, + * which in rare cases can lead to unphysical results, i.e. negative + * masses. Make sure we prevent unphysical solutions propagating by + * enforcing a minumum of zero. */ + const float new_mass_HI = + max(current_mass_HI + p->rt_data.mass_flux.HI, 0.f); + const float new_mass_HII = + max(current_mass_HII + p->rt_data.mass_flux.HII, 0.f); + const float new_mass_HeI = + max(current_mass_HeI + p->rt_data.mass_flux.HeI, 0.f); + const float new_mass_HeII = + max(current_mass_HeII + p->rt_data.mass_flux.HeII, 0.f); + const float new_mass_HeIII = + max(current_mass_HeIII + p->rt_data.mass_flux.HeIII, 0.f); + + const float new_mass_tot = new_mass_HI + new_mass_HII + new_mass_HeI + + new_mass_HeII + new_mass_HeIII; + + /* During the initial fake time step, if the mass fractions haven't been set + * up yet, we could encounter divisions by zero here, so skip that. If it + * isn't the initial time step, the error will be caught down the line by + * another call to rt_check_unphysical_mass_fractions() (not the one 10 + * lines below this) */ + if (new_mass_tot == 0.f) return; + + const float new_mass_tot_inv = 1.f / new_mass_tot; + + p->rt_data.tchem.mass_fraction_HI = new_mass_HI * new_mass_tot_inv; + p->rt_data.tchem.mass_fraction_HII = new_mass_HII * new_mass_tot_inv; + p->rt_data.tchem.mass_fraction_HeI = new_mass_HeI * new_mass_tot_inv; + p->rt_data.tchem.mass_fraction_HeII = new_mass_HeII * new_mass_tot_inv; + p->rt_data.tchem.mass_fraction_HeIII = new_mass_HeIII * new_mass_tot_inv; + + /* Reset fluxes after they have been applied, so they can be collected + * again even when particle is inactive. */ + rt_part_reset_mass_fluxes(p); + + /* Don't update actual particle mass, that'll be done in the + * hydro_kick_extra calls */ + } + +#endif + + //rt_check_unphysical_mass_fractions(p); +} + +/** + * @brief Prepare a particle for the !HYDRO! force calculation. + * E.g. for the meshless schemes, we need to take into account the + * mass fluxes of the constituent species between particles. + * NOTE: don't call this during rt_init_part or rt_reset_part, + * follow the hydro_prepare_force logic. + * + * @param p particle to work on + **/ +__attribute__((always_inline)) INLINE static void rt_prepare_force( + struct part* p) {} + +/** + * @brief Extra operations to be done during the drift + * + * @param p Particle to act upon. + * @param xp The extended particle data to act upon. + * @param dt_drift The drift time-step for positions. + */ +__attribute__((always_inline)) INLINE static void rt_predict_extra( + struct part* p, struct xpart* xp, float dt_drift) { + + float dx[3] = {xp->v_full[0] * dt_drift, xp->v_full[1] * dt_drift, + xp->v_full[2] * dt_drift}; + + for (int g = 0; g < RT_NGROUPS; g++) { + float Unew[4]; + rt_gradients_predict_drift(p, Unew, g, dx); + p->rt_data.radiation[g].energy_density = Unew[0]; + p->rt_data.radiation[g].flux[0] = Unew[1]; + p->rt_data.radiation[g].flux[1] = Unew[2]; + p->rt_data.radiation[g].flux[2] = Unew[3]; + } +} + +/** + * @brief Clean the allocated memory inside the RT properties struct. + * + * @param props the #rt_props. + * @param restart did we restart? + */ +__attribute__((always_inline)) INLINE static void rt_clean( + struct rt_props* props, int restart) { + + /* If we were restarting, free-ing manually will lead to + * segfaults since we didn't malloc the stuff */ + if (!restart) { + /* Clean up grackle data. This is a call to a grackle function */ + local_free_chemistry_data(&props->grackle_chemistry_data, + &props->grackle_chemistry_rates); + + for (int g = 0; g < RT_NGROUPS; g++) { + free(props->energy_weighted_cross_sections[g]); + free(props->number_weighted_cross_sections[g]); + } + free(props->energy_weighted_cross_sections); + free(props->number_weighted_cross_sections); + } +} + +#endif /* SWIFT_RT_KIARA_H */ diff --git a/src/rt/KIARA/rt_additions.h b/src/rt/KIARA/rt_additions.h new file mode 100644 index 0000000000..349896dfa5 --- /dev/null +++ b/src/rt/KIARA/rt_additions.h @@ -0,0 +1,93 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_KIARA_ADDITIONS_H +#define SWIFT_RT_KIARA_ADDITIONS_H + +/** + * @file src/rt/KIARA/rt_additions.h + * @brief additional functions required for files outside of the RT modules + * moved to separate file to avoid circular inclusions + * */ + +/** + * @brief update mass fluxes between two interacting particles during + * hydro_iact_(non)sym(...) calls. + * + * @param pi first interacting particle + * @param pj second interacting particle + * @param mass_flux the mass flux between these two particles + * @param mode 0: non-symmetric interaction, update i only. 1: symmetric + * interaction. + **/ +__attribute__((always_inline)) INLINE static void rt_part_update_mass_fluxes( + struct part* restrict pi, struct part* restrict pj, float mass_flux, + int mode) { + + /* If a particle is losing mass, then it loses mass according to + * its own mass fractions. If it's gaining mass, it's gaining mass + * according to the interacting particle's mass fractions. */ + + /* get the time step for the flux exchange. This is always the smallest time + step among the two particles */ + const float mindt = + (pj->flux.dt > 0.0f) ? fminf(pi->flux.dt, pj->flux.dt) : pi->flux.dt; + + const float mdt = mass_flux * mindt; + + /* Convention: negative flux for "left" particle pi */ + if (mass_flux < 0.f) { + /* "left" particle is gaining mass */ + pi->rt_data.mass_flux.HI -= pj->rt_data.tchem.mass_fraction_HI * mdt; + pi->rt_data.mass_flux.HII -= pj->rt_data.tchem.mass_fraction_HII * mdt; + pi->rt_data.mass_flux.HeI -= pj->rt_data.tchem.mass_fraction_HeI * mdt; + pi->rt_data.mass_flux.HeII -= pj->rt_data.tchem.mass_fraction_HeII * mdt; + pi->rt_data.mass_flux.HeIII -= pj->rt_data.tchem.mass_fraction_HeIII * mdt; + } else { + /* "left" particle is losing mass */ + pi->rt_data.mass_flux.HI -= pi->rt_data.tchem.mass_fraction_HI * mdt; + pi->rt_data.mass_flux.HII -= pi->rt_data.tchem.mass_fraction_HII * mdt; + pi->rt_data.mass_flux.HeI -= pi->rt_data.tchem.mass_fraction_HeI * mdt; + pi->rt_data.mass_flux.HeII -= pi->rt_data.tchem.mass_fraction_HeII * mdt; + pi->rt_data.mass_flux.HeIII -= pi->rt_data.tchem.mass_fraction_HeIII * mdt; + } + + if (mode == 1 || (pj->flux.dt < 0.f)) { + /* Update right particle as well, even if it's inactive */ + + if (mass_flux > 0.f) { + /* "right" particle is gaining mass */ + pj->rt_data.mass_flux.HI += pi->rt_data.tchem.mass_fraction_HI * mdt; + pj->rt_data.mass_flux.HII += pi->rt_data.tchem.mass_fraction_HII * mdt; + pj->rt_data.mass_flux.HeI += pi->rt_data.tchem.mass_fraction_HeI * mdt; + pj->rt_data.mass_flux.HeII += pi->rt_data.tchem.mass_fraction_HeII * mdt; + pj->rt_data.mass_flux.HeIII += + pi->rt_data.tchem.mass_fraction_HeIII * mdt; + } else { + /* "right" particle is losing mass */ + pj->rt_data.mass_flux.HI += pj->rt_data.tchem.mass_fraction_HI * mdt; + pj->rt_data.mass_flux.HII += pj->rt_data.tchem.mass_fraction_HII * mdt; + pj->rt_data.mass_flux.HeI += pj->rt_data.tchem.mass_fraction_HeI * mdt; + pj->rt_data.mass_flux.HeII += pj->rt_data.tchem.mass_fraction_HeII * mdt; + pj->rt_data.mass_flux.HeIII += + pj->rt_data.tchem.mass_fraction_HeIII * mdt; + } + } +} + +#endif /* SWIFT_RT_KIARA_ADDITIONS_H */ diff --git a/src/rt/KIARA/rt_blackbody.h b/src/rt/KIARA/rt_blackbody.h new file mode 100644 index 0000000000..d702b60e10 --- /dev/null +++ b/src/rt/KIARA/rt_blackbody.h @@ -0,0 +1,88 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_KIARA_BLACKBODY_H +#define SWIFT_RT_KIARA_BLACKBODY_H + +/** + * @file src/rt/KIARA/rt_blackbody.h + * @brief Functions related to the blackbody spectrum + */ + +#include "inline.h" + +#include + +/** + * @brief Return the specific intensity of the blackbody spectrum + * + * @param nu frequency at which to compute specific intensity + * @param T temperature characterizing the spectrum + * @param kB Boltzmann constant + * @param h_planck Planck's constant + * @param c speed of light + */ +__attribute__((always_inline)) INLINE double blackbody_spectrum_intensity( + const double nu, const double T, const double kB, const double h_planck, + const double c) { + + const double hnu = h_planck * nu; + const double kT = kB * T; + const double nu2 = nu * nu; + double temp; + if (hnu / kT < 1e-6) { + /* prevent division by zero, use Taylor approximation */ + temp = kT; + } else if (hnu / kT > 700.) { + /* prevent infs */ + temp = 0.; + } else { + temp = 1. / (exp(hnu / kT) - 1.); + } + return 2. * hnu * nu2 / (c * c) * temp; +} + +/** + * Return the blackbody spectrum energy density + * + * @param nu frequency at which to compute specific intensity + * @param T temperature characterizing the spectrum + * @param kB Boltzmann constant + * @param h_planck Planck's constant + * @param c speed of light + */ +__attribute__((always_inline)) INLINE double blackbody_spectrum_energy_density( + const double nu, const double T, const double kB, const double h_planck, + const double c) { + return 4. * M_PI / c * blackbody_spectrum_intensity(nu, T, kB, h_planck, c); +} + +/** + * Return the frequency at which the blackbody spectrum at a given tempterature + * peaks. + * + * @param T temperature characterizing the spectrum + * @param kB Boltzmann constant + * @param h_planck Planck's constant + */ +__attribute__((always_inline)) INLINE double blackbody_peak_frequency( + const double T, const double kB, const double h_planck) { + return 2.82144 * kB * T / h_planck; +} + +#endif diff --git a/src/rt/KIARA/rt_debugging.h b/src/rt/KIARA/rt_debugging.h new file mode 100644 index 0000000000..84e070dd6b --- /dev/null +++ b/src/rt/KIARA/rt_debugging.h @@ -0,0 +1,362 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_DEBUGGING_KIARA_H +#define SWIFT_RT_DEBUGGING_KIARA_H + +#ifdef SWIFT_RT_DEBUG_CHECKS + +#include "active.h" +#include "rt_properties.h" +#include "timeline.h" + +/** + * @file src/rt/KIARA/rt_debugging.h + * @brief Main header file for the KIARA radiative transfer scheme + * extra debugging functions. + */ + +/** + * @brief Check whether RT time step size is valid compared to the hydro one. + * + * @param p particle to work on + * @param dti_rt current RT integer time step + * @param dti_hydro current hydro integer time step + * @param max_nr_rt_subcycles max number of RT sub-cycles. + * @param time_base minimal time step in this run + */ +__attribute__((always_inline)) INLINE static void rt_debugging_check_timestep( + const struct part *restrict p, integertime_t *dti_rt, + const integertime_t *dti_hydro, int max_nr_rt_subcycles, double time_base) { + + if (*dti_hydro < *dti_rt) + error("part %lld has hydro time (%lld/%g) < RT time step (%lld/%g", p->id, + *dti_hydro, *dti_hydro * time_base, *dti_rt, *dti_rt * time_base); +} + +/** + * @brief This resets particle carried quantities after each subcycling + * step such that the internal checks are still consistent. + * @param p the particle to work on + */ +__attribute__((always_inline)) INLINE static void rt_debugging_count_subcycle( + struct part *restrict p) { + p->rt_data.debug_nsubcycles += 1; +} + +/** + * @brief Check that the particle completed the correct number of subcycles. + * This is checked in every rt_reset_part, before the subcycling count is reset. + * @param p the particle to work on + * @param rt_props RT properties struct + */ +__attribute__((always_inline)) INLINE static void +rt_debugging_check_nr_subcycles(struct part *restrict p, + const struct rt_props *rt_props) { + + /* TODO: this check may fail when running with limiter/sync. */ + + /* NOTE: we need to do this check somewhere in the hydro tasks. + * (1) it needs to be done when all tasks are active and before the + * particle hydro time step is changed. + * (2) If you do it during RT tasks, it won't properly check how + * many sub-cycles you did during a single hydro task. + * (3) You can't do it during the timestep task, since between + * the hydro and the timestep we already do an RT step. */ + + /* skip initialization */ + if (p->time_bin == 0) return; + if (p->rt_time_data.time_bin == 0) + error("Got part %lld with RT time bin 0", p->id); + + timebin_t bindiff = p->time_bin - p->rt_time_data.time_bin; + + if (rt_props->debug_max_nr_subcycles <= 1) { + /* Running without subcycling. */ + if (bindiff != 0) + error("Running without subcycling but got bindiff=%d for part=%lld", + bindiff, p->id); + if (p->rt_data.debug_nsubcycles != 1) + error("Running without subcycling but got part=%lld subcycle count=%d", + p->id, p->rt_data.debug_nsubcycles); + return; + } +} + +/** + * @brief This resets particle carried quantities after each subcycling + * step such that the internal checks are still consistent. + * @param p the particle to work on + */ +__attribute__((always_inline)) INLINE static void +rt_debugging_reset_each_subcycle(struct part *restrict p) { + + p->rt_data.debug_calls_iact_gradient_interaction = 0; + p->rt_data.debug_calls_iact_transport_interaction = 0; + + p->rt_data.debug_injection_done = 0; + p->rt_data.debug_gradients_done = 0; + p->rt_data.debug_transport_done = 0; + p->rt_data.debug_thermochem_done = 0; +} + +/** + * @brief Debugging checks loop over all star particles after each time step + */ +static void rt_debugging_end_of_step_stars_mapper(void *restrict map_data, + int scount, + void *restrict extra_data) { + + struct spart *restrict sparts = (struct spart *)map_data; + const struct engine *restrict e = (struct engine *)extra_data; + + unsigned long long emission_sum_this_step = 0ULL; + unsigned long long emission_sum_tot = 0ULL; + + for (int k = 0; k < scount; k++) { + + struct spart *restrict sp = &sparts[k]; + emission_sum_this_step += sp->rt_data.debug_iact_hydro_inject; + emission_sum_tot += sp->rt_data.debug_radiation_emitted_tot; + /* Reset all values here in case stars won't be active next step */ + sp->rt_data.debug_iact_hydro_inject = 0; + sp->rt_data.debug_iact_hydro_inject_prep = 0; + +#ifndef WITH_MPI + /* These checks will fail with MPI since we're not + * sending data back */ + for (int g = 0; g < RT_NGROUPS; g++) { + /* also check now that we actually injected the correct + * amount of energy + * sp->rt_data.emission_this_step: energy we should distribute + * this step + * sp->rt_data.debug_injected_energy: energy we actually did + * distribute this step */ + if (sp->rt_data.debug_injected_energy[g] != 0.f) { + float diff = 1.f - sp->rt_data.emission_this_step[g] / + sp->rt_data.debug_injected_energy[g]; + + if (fabsf(diff) > 1e-4) { + /* Dividing the total into several parts and summing them up again + * while hoping to obtain the same results may lead to diappointment + * due to roundoff errors. Check that the sum of the individual + * weights and the ones we collected for the injection are close + * enough. */ + float psi_sum_now = 0.f; + for (int i = 0; i < 8; i++) + psi_sum_now += sp->rt_data.octant_weights[i]; + float diff_weights = 1.f - sp->rt_data.debug_psi_sum / psi_sum_now; + if (fabsf(diff_weights) > 1e-4) + message( + "Incorrect injection ID %lld: " + "group %d expected %.3g got %.3g diff %.3g diff_weights %.3g", + sp->id, g, sp->rt_data.emission_this_step[g], + sp->rt_data.debug_injected_energy[g], diff, diff_weights); + } + } + } +#endif + + for (int g = 0; g < RT_NGROUPS; g++) { + sp->rt_data.debug_injected_energy[g] = 0.f; + } + for (int g = 0; g < RT_NGROUPS; g++) { + sp->rt_data.emission_this_step[g] = 0.f; + } + } + + atomic_add(&e->rt_props->debug_radiation_emitted_this_step, + emission_sum_this_step); + atomic_add(&e->rt_props->debug_radiation_emitted_tot, emission_sum_tot); +} + +/** + * @brief Debugging checks loop over all hydro particles after each time step + */ +static void rt_debugging_end_of_step_hydro_mapper(void *restrict map_data, + int count, + void *restrict extra_data) { + + struct part *restrict parts = (struct part *)map_data; + const struct engine *restrict e = (struct engine *)extra_data; + + unsigned long long absorption_sum_this_step = 0ULL; + unsigned long long absorption_sum_tot = 0ULL; + float energy_sum[RT_NGROUPS]; + for (int g = 0; g < RT_NGROUPS; g++) energy_sum[g] = 0.f; + + for (int k = 0; k < count; k++) { + + struct part *restrict p = &parts[k]; + absorption_sum_this_step += p->rt_data.debug_iact_stars_inject; + absorption_sum_tot += p->rt_data.debug_radiation_absorbed_tot; + + /* Reset all values here in case particles won't be active next step */ + p->rt_data.debug_iact_stars_inject = 0; + + /* Sum up total energies for budget */ + for (int g = 0; g < RT_NGROUPS; g++) { + energy_sum[g] += + p->rt_data.radiation[g].energy_density * p->geometry.volume; + } + } + + atomic_add(&e->rt_props->debug_radiation_absorbed_this_step, + absorption_sum_this_step); + atomic_add(&e->rt_props->debug_radiation_absorbed_tot, absorption_sum_tot); +} + +/** + * @brief At the end of each time step, loop over both hydro and star + * particles and do whatever checks for this particular time step you + * want done. + * + * @param e The #engine. + * @param verbose Are we talkative? + */ +__attribute__((always_inline)) INLINE static void +rt_debugging_checks_end_of_step(struct engine *e, int verbose) { + + struct space *s = e->s; + if (!(e->policy & engine_policy_rt)) return; + + const ticks tic = getticks(); + + /* reset values before the particle loops. + * reset total counts as well. We track the totals since the beginning + * of time in particles individually. */ + e->rt_props->debug_radiation_emitted_this_step = 0ULL; + e->rt_props->debug_radiation_absorbed_this_step = 0ULL; + e->rt_props->debug_radiation_emitted_tot = 0ULL; + e->rt_props->debug_radiation_absorbed_tot = 0ULL; + + /* hydro particle loop */ + if (s->nr_parts > 0) + threadpool_map(&e->threadpool, rt_debugging_end_of_step_hydro_mapper, + s->parts, s->nr_parts, sizeof(struct part), + threadpool_auto_chunk_size, /*extra_data=*/e); + + /* star particle loop */ + if (s->nr_sparts > 0) + threadpool_map(&e->threadpool, rt_debugging_end_of_step_stars_mapper, + s->sparts, s->nr_sparts, sizeof(struct spart), + threadpool_auto_chunk_size, /*extra_data=*/e); + +#ifdef WITH_MPI + /* Since we aren't sending data back, none of these checks will + * pass a run over MPI. Make sure you run the threadpool functions + * first though so certain variables can get reset properly. */ + return; +#endif + + /* Have we accidentally invented or deleted some radiation somewhere? */ + + if ((e->rt_props->debug_radiation_emitted_this_step != + e->rt_props->debug_radiation_absorbed_this_step) || + (e->rt_props->debug_radiation_emitted_tot != + e->rt_props->debug_radiation_absorbed_tot)) + error( + "Emitted and absorbed radiation vary.\n" + " This step: star emission %12lld; gas absorption %12lld\n" + "Since start: star emission %12lld; gas absorption %12lld", + e->rt_props->debug_radiation_emitted_this_step, + e->rt_props->debug_radiation_absorbed_this_step, + e->rt_props->debug_radiation_emitted_tot, + e->rt_props->debug_radiation_absorbed_tot); + + if (verbose) + message("took %.3f %s.", clocks_from_ticks(getticks() - tic), + clocks_getunit()); +} + +/** + * @brief Perform a series of consistency and sanity checks. + * + * @param p particle to check + * @param loc location where this is called from. This determines which checks + * will be done: + * + * 0: during kicks/after drifts. + * 1: during rt_ghost1/finalise_injection / after kicks. + * 2: during gradients / after injection. + * 3: during transport / after gradients. + * 4: during thermochem / after transport. + * 5: after thermochem. + * + * @param function_name: Function name (or message) you want printed on error. + */ +__attribute__((always_inline)) INLINE static void rt_debug_sequence_check( + struct part *restrict p, int loc, const char *function_name) { + + /* Note: Checking whether a particle has been drifted at all is not + * compatible with subcycling. There is no reliable point where to + * reset the counters and have sensible results. */ + + if (loc > 0) { + /* Are kicks done? */ + if (p->rt_data.debug_nsubcycles == 0) { + if (p->rt_data.debug_kicked != 1) + error( + "called %s on particle %lld with wrong kick count=%d (expected " + "1) cycle=%d", + function_name, p->id, p->rt_data.debug_kicked, + p->rt_data.debug_nsubcycles); + } else if (p->rt_data.debug_nsubcycles > 0) { + /* This covers case 1, 2, 3, 4, 5 */ + if (p->rt_data.debug_kicked != 2) + error( + "called %s on particle %lld with wrong kick count=%d (expected 2) " + "cycle=%d", + function_name, p->id, p->rt_data.debug_kicked, + p->rt_data.debug_nsubcycles); + } else { + error("Got negative subcycle???"); + } + } + + if (loc > 1) { + /* is injection done? */ + if (p->rt_data.debug_injection_done != 1) + error("called %s on part %lld when finalise injection count is %d ID", + function_name, p->id, p->rt_data.debug_injection_done); + } + + if (loc > 2) { + /* are gradients done? */ + if (p->rt_data.debug_gradients_done != 1) + error("called %s on part %lld when gradients_done count is %d", + function_name, p->id, p->rt_data.debug_gradients_done); + } + + if (loc > 3) { + /* is transport done? */ + if (p->rt_data.debug_transport_done != 1) + error("called %s on part %lld when transport_done != 1: %d", + function_name, p->id, p->rt_data.debug_transport_done); + } + + if (loc > 4) { + /* is thermochemistry done? */ + if (p->rt_data.debug_thermochem_done != 1) + error("called %s on part %lld with thermochem_done count=%d", + function_name, p->id, p->rt_data.debug_thermochem_done); + } +} + +#endif /* SWIFT_RT_DEBUG_CHECKS */ +#endif /* SWIFT_RT_DEBUGGING_KIARA_H */ diff --git a/src/rt/KIARA/rt_flux.h b/src/rt/KIARA/rt_flux.h new file mode 100644 index 0000000000..8386f261c0 --- /dev/null +++ b/src/rt/KIARA/rt_flux.h @@ -0,0 +1,114 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef SWIFT_KIARA_RT_FLUX_H +#define SWIFT_KIARA_RT_FLUX_H + +#if defined(RT_RIEMANN_SOLVER_GLF) +#include "rt_riemann_GLF.h" +#elif defined(RT_RIEMANN_SOLVER_HLL) +#include "rt_riemann_HLL.h" +#else +#error "No valid choice of RT Riemann solver has been selected" +#endif + +#include "rt_unphysical.h" + +/** + * @file src/rt/KIARA/rt_flux.h + * @brief Functions related to compute the interparticle flux term of the + * conservation law. This is its own file so we can switch between Riemann + * solvers more easily. + */ + +/** + * @brief Reset the fluxes for the given particle. + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void rt_part_reset_fluxes( + struct part* restrict p) { + + for (int g = 0; g < RT_NGROUPS; g++) { + p->rt_data.flux[g].energy = 0.f; + p->rt_data.flux[g].flux[0] = 0.f; + p->rt_data.flux[g].flux[1] = 0.f; + p->rt_data.flux[g].flux[2] = 0.f; + } +} + +/** + * @brief Compute the flux between a left state UL and a right + * state UR along the direction of the unit vector n_unit + * through a surface of size Anorm. + * + * @param UL left state (energy density, fluxes) + * @param UR right state (energy density, fluxes) + * @param n_unit unit vector of the direction of the surface + * @param Anorm size of the surface through which the flux goes + * @param fluxes (return) the resulting flux + */ +__attribute__((always_inline)) INLINE static void rt_compute_flux( + float UL[4], float UR[4], const float n_unit[3], const float Anorm, + float fluxes[4]) { + + /* Unphysical check not necessary here. + * It's done in gradients_predict as well. */ + + const float FLnorm = sqrtf(UL[1] * UL[1] + UL[2] * UL[2] + UL[3] * UL[3]); + const float FRnorm = sqrtf(UR[1] * UR[1] + UR[2] * UR[2] + UR[3] * UR[3]); + + /* Get the fluxes in the hyperbolic conservation law sense. */ + float hyperFluxL[4][3]; + rt_get_hyperbolic_flux(UL, FLnorm, hyperFluxL); + float hyperFluxR[4][3]; + rt_get_hyperbolic_flux(UR, FRnorm, hyperFluxR); + +#ifdef SWIFT_RT_DEBUG_CHECKS + rt_check_unphysical_hyperbolic_flux(hyperFluxL); + rt_check_unphysical_hyperbolic_flux(hyperFluxR); +#endif + + rt_riemann_solve_for_flux(UL, UR, FLnorm, FRnorm, hyperFluxL, hyperFluxR, + n_unit, fluxes); + + /* get the actual flux */ + fluxes[0] *= Anorm; + fluxes[1] *= Anorm; + fluxes[2] *= Anorm; + fluxes[3] *= Anorm; +} + +/** + * @brief reset the mass fluxes of constituent species for a particle. + * + * @param p particle to work on. + **/ +__attribute__((always_inline)) INLINE static void rt_part_reset_mass_fluxes( + struct part* restrict p) { +#ifdef GIZMO_MFV_SPH + p->rt_data.mass_flux.HI = 0.f; + p->rt_data.mass_flux.HII = 0.f; + p->rt_data.mass_flux.HeI = 0.f; + p->rt_data.mass_flux.HeII = 0.f; + p->rt_data.mass_flux.HeIII = 0.f; +#endif +} + +#endif /* SWIFT_KIARA_RT_FLUX_H */ diff --git a/src/rt/KIARA/rt_getters.h b/src/rt/KIARA/rt_getters.h new file mode 100644 index 0000000000..4e53261adc --- /dev/null +++ b/src/rt/KIARA/rt_getters.h @@ -0,0 +1,230 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef SWIFT_KIARA_RT_GETTERS_H +#define SWIFT_KIARA_RT_GETTERS_H + +#include "rt_parameters.h" + +/** + * @file src/rt/KIARA/rt_getters.h + * @brief Getter functions for KIARA RT scheme to keep code clean and lean + */ + +/** + * @brief Get the comoving radiation energy densities of a particle. + * + * @param p Particle. + * @param E (return) Pointer to the array in which the result needs to be stored + */ +__attribute__((always_inline)) INLINE static void +rt_part_get_comoving_radiation_energy_density(const struct part *restrict p, + float E[RT_NGROUPS]) { + + for (int g = 0; g < RT_NGROUPS; g++) { + E[g] = p->rt_data.radiation[g].energy_density; + } +} + +/** + * @brief Get the physical radiation energy densities of a particle + * + * @param p Particle. + * @param E (return) Pointer to the array in which the result needs to be stored + */ +__attribute__((always_inline)) INLINE static void +rt_part_get_physical_radiation_energy_density(const struct part *restrict p, + float E[RT_NGROUPS], + const struct cosmology *cosmo) { + for (int g = 0; g < RT_NGROUPS; g++) { + E[g] = cosmo->a3_inv * p->rt_data.radiation[g].energy_density; + } +} + +/** + * @brief Get a 4-element state vector U containing the radiation energy + * density and fluxes for a specific photon group. + * + * @param p Particle. + * @param group Index of photon group + * @param U Pointer to the array in which the result needs to be stored + */ +__attribute__((always_inline)) INLINE static void +rt_part_get_radiation_state_vector(const struct part *restrict p, int group, + float U[4]) { + + U[0] = p->rt_data.radiation[group].energy_density; + U[1] = p->rt_data.radiation[group].flux[0]; + U[2] = p->rt_data.radiation[group].flux[1]; + U[3] = p->rt_data.radiation[group].flux[2]; +} + +/** + * @brief Get the gradients of energy density and fluxes for a given photon + * group + * + * @param p Particle. + * @param group Index of photon group + * @param dE (return) Array to write energy density gradient into + * @param dFx (return) Array to write flux x component gradient into + * @param dFy (return) Array to write flux y component gradient into + * @param dFz (return) Array to write flux z component gradient into + */ +__attribute__((always_inline)) INLINE static void rt_part_get_gradients( + const struct part *restrict p, int group, float dE[3], float dFx[3], + float dFy[3], float dFz[3]) { + + dE[0] = p->rt_data.gradient[group].energy_density[0]; + dE[1] = p->rt_data.gradient[group].energy_density[1]; + dE[2] = p->rt_data.gradient[group].energy_density[2]; + + dFx[0] = p->rt_data.gradient[group].flux[0][0]; + dFx[1] = p->rt_data.gradient[group].flux[0][1]; + dFx[2] = p->rt_data.gradient[group].flux[0][2]; + + dFy[0] = p->rt_data.gradient[group].flux[1][0]; + dFy[1] = p->rt_data.gradient[group].flux[1][1]; + dFy[2] = p->rt_data.gradient[group].flux[1][2]; + + dFz[0] = p->rt_data.gradient[group].flux[2][0]; + dFz[1] = p->rt_data.gradient[group].flux[2][1]; + dFz[2] = p->rt_data.gradient[group].flux[2][2]; +} + +/** + * @brief compute the pressure tensor for a given radiation state U + * + * @param U the state (radiation energy density, radiation flux) to use + * @param Fnorm the norm of the radiation flux + * @param pressure_tensor (return) 3x3 array to write resulting Eddington + * pressure tensor into + */ +__attribute__((always_inline)) INLINE static void rt_get_pressure_tensor( + const float U[4], const float Fnorm, float pressure_tensor[3][3]) { + + /* We may encounter zero flux even with nonzero energy. + * Also even with nonzero flux, the norm may round down + * to exactly zero, so exit early if that is the case. */ + if (Fnorm == 0.f) { + const float diagonal_element = U[0] / 3.f; + pressure_tensor[0][0] = diagonal_element; + pressure_tensor[0][1] = 0.f; + pressure_tensor[0][2] = 0.f; + pressure_tensor[1][0] = 0.f; + pressure_tensor[1][1] = diagonal_element; + pressure_tensor[1][2] = 0.f; + pressure_tensor[2][0] = 0.f; + pressure_tensor[2][1] = 0.f; + pressure_tensor[2][2] = diagonal_element; + return; + } + + /* f mustn't be > 1. This may happen because of the reduced speed of light. + * Energy density U[0] is nonzero at this point, or this function wouldn't + * have been called. */ + const float f = + min(1.f, rt_params.reduced_speed_of_light_inverse * Fnorm / U[0]); + const float f2 = f * f; + const float rootterm = 4.f - 3.f * f2; + const float chi = (3.f + 4.f * f2) / (5.f + 2.f * sqrtf(rootterm)); + + /* get unit vector n */ + const float Fnorm_inv = 1.f / Fnorm; + const float n[3] = {U[1] * Fnorm_inv, U[2] * Fnorm_inv, U[3] * Fnorm_inv}; + + const float temp = 0.5f * (3.f * chi - 1.f); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + pressure_tensor[i][j] = temp * n[i] * n[j]; + } + } + + const float temp2 = 0.5f * (1.f - chi); + pressure_tensor[0][0] += temp2; + pressure_tensor[1][1] += temp2; + pressure_tensor[2][2] += temp2; + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + pressure_tensor[i][j] *= U[0]; + } + } + +#ifdef SWIFT_RT_DEBUG_CHECKS + if (pressure_tensor[0][0] != pressure_tensor[0][0]) { + message( + "Found NaNs in pressure tensor: 1/c %.3e |" + " |F| %.3e f %.3e f^2 %.3e root %.3e chi %.3e |" + " n %.3e %.3e %.3e | U %.3e %.3e %.3e |" + " temp %.3e %.3e", + rt_params.reduced_speed_of_light_inverse, Fnorm, f, f2, rootterm, chi, + n[0], n[1], n[2], U[1], U[2], U[3], temp, temp2); + } +#endif +} + +/** + * @brief compute the flux of the hyperbolic conservation law for a given + * state U + * + * @param U the state (radiation energy density, radiation flux) to use + * @param Fnorm the norm of the radiation flux + * @param hypflux (return) resulting flux F(U) of the hyperbolic conservation + * law + */ +__attribute__((always_inline)) INLINE static void rt_get_hyperbolic_flux( + const float U[4], const float Fnorm, float hypflux[4][3]) { + + if (U[0] == 0.f) { + /* At this point, the state U has been corrected to not contain + * unphysical values. If we encounter this situation, it means + * that the fluxes are zero as well, meaning that when we compute + * 1/|F| we get infinities. So skip this. The pressure tensor is + * P_ij = D_ij * E_i anyway. */ + + for (int i = 0; i < 4; i++) { + hypflux[i][0] = 0.f; + hypflux[i][1] = 0.f; + hypflux[i][2] = 0.f; + } + return; + } + + hypflux[0][0] = U[1]; + hypflux[0][1] = U[2]; + hypflux[0][2] = U[3]; + + float pressure_tensor[3][3]; + rt_get_pressure_tensor(U, Fnorm, pressure_tensor); + + const float c_red = rt_params.reduced_speed_of_light; + const float c2 = c_red * c_red; + hypflux[1][0] = pressure_tensor[0][0] * c2; + hypflux[1][1] = pressure_tensor[0][1] * c2; + hypflux[1][2] = pressure_tensor[0][2] * c2; + hypflux[2][0] = pressure_tensor[1][0] * c2; + hypflux[2][1] = pressure_tensor[1][1] * c2; + hypflux[2][2] = pressure_tensor[1][2] * c2; + hypflux[3][0] = pressure_tensor[2][0] * c2; + hypflux[3][1] = pressure_tensor[2][1] * c2; + hypflux[3][2] = pressure_tensor[2][2] * c2; +} + +#endif diff --git a/src/rt/KIARA/rt_grackle_utils.h b/src/rt/KIARA/rt_grackle_utils.h new file mode 100644 index 0000000000..971f9b618c --- /dev/null +++ b/src/rt/KIARA/rt_grackle_utils.h @@ -0,0 +1,540 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_GRACKLE_UTILS_H +#define SWIFT_RT_GRACKLE_UTILS_H + +/* skip deprecation warnings. I cleaned old API calls. */ +//#define OMIT_LEGACY_INTERNAL_GRACKLE_FUNC + +/* need hydro gamma */ +#include "hydro.h" + +#include + +/* need to rework (and check) code if changed */ +#define FIELD_SIZE 1 +#define DUST_MODEL 1 + +/** + * @file src/rt/KIARA/rt_grackle_utils.h + * @brief Utility and helper functions related to using grackle. + */ + +/** + * @brief Update grackle units during run + * + * @param grackle_units grackle units struct + * @param cosmo cosmology struct + * + * NOTE: In the current implementation, this function does nothing. + * However, there might be use-cases in the future (e.g. switching + * UV background on or off depending on redshift) that might be + * needed in the future, which can be implemented into this function. + */ +__attribute__((always_inline)) INLINE void update_grackle_units_cosmo( + code_units *grackle_units, const struct unit_system *us, + const struct cosmology *restrict cosmo) { + /*TODO: add the if statement when it is not in cosmology mode. */ + grackle_units->a_value = cosmo->a; + +} + +/** + * @brief initialize grackle during rt_props_init + * + * @param grackle_units grackle units struct to fill up correctly. + * @param grackle_chemistry_dat grackle chemistry data struct to fill up + *correctly. + * @param hydrogen_mass_fraction global hydrogen mass fraction. + * @param grackle_verb run grackle in verbose mode? + * @param case_B_recombination use grackle with case B recombination? + * @param us #unit_system struct + **/ +__attribute__((always_inline)) INLINE static void rt_init_grackle( + code_units *grackle_units, chemistry_data *grackle_chemistry_data, + chemistry_data_storage *grackle_chemistry_rates, + float hydrogen_mass_fraction, const int grackle_verb, + const int case_B_recombination, const struct unit_system *us, + const struct cosmology *restrict cosmo) { + + grackle_verbose = grackle_verb; + + /* Initialize units */ + /* ---------------- */ + /* we assume all quantities to be physical, not comoving */ + grackle_units->a_units = 1.0; + grackle_units->a_value = 0.01; + grackle_units->comoving_coordinates = 0; + grackle_units->density_units = + units_cgs_conversion_factor(us, UNIT_CONV_DENSITY); + grackle_units->length_units = + units_cgs_conversion_factor(us, UNIT_CONV_LENGTH); + grackle_units->time_units = units_cgs_conversion_factor(us, UNIT_CONV_TIME); + /* Set velocity units */ + //set_velocity_units(grackle_units); + grackle_units->velocity_units = + units_cgs_conversion_factor(us, UNIT_CONV_VELOCITY); + + /* Chemistry Parameters */ + /* -------------------- */ + /* More details on + * https://grackle.readthedocs.io/en/grackle-3.2.0/Integration.html#chemistry-data + */ + + if (local_initialize_chemistry_parameters(grackle_chemistry_data) == 0) { + error("Error in set_default_chemistry_parameters."); + } + + /* chemistry on */ + grackle_chemistry_data->use_grackle = 2; + /* cooling on */ + /* NOTE: without cooling on, it also won't heat... */ + grackle_chemistry_data->with_radiative_cooling = 1; + /* 6 species atomic H and He */ + grackle_chemistry_data->primordial_chemistry = COOLING_GRACKLE_MODE; + /* No dust processes */ + grackle_chemistry_data->dust_chemistry = 0; + /* No H2 formation on dust */ + grackle_chemistry_data->h2_on_dust = 0; + /* metal cooling (uses Cloudy) off (for now) */ + grackle_chemistry_data->metal_cooling = 1; + /* no cooling below CMB temperature */ + grackle_chemistry_data->cmb_temperature_floor = 1; + /* UV background off */ + grackle_chemistry_data->UVbackground = 0; + /* data file - currently not used */ + grackle_chemistry_data->grackle_data_file = "CloudyData_UVB=FG2011_shielded.h5"; + /* adiabatic index */ + grackle_chemistry_data->Gamma = hydro_gamma; + /* we'll provide grackle with ionization and heating rates from RT */ + grackle_chemistry_data->use_radiative_transfer = 1; + + //volumetric heating rates is being provided in the volumetric_heating_rate + // field of grackle_field_data + grackle_chemistry_data->use_volumetric_heating_rate = 0; + // specific heating rates is being provided in the specific_heating_rate field + // of grackle_field_data + grackle_chemistry_data->use_specific_heating_rate = 1; + // Set parameters of temperature floor: 0=none, 1=provide scalar, 2=provide array + grackle_chemistry_data->use_temperature_floor = 2; + // control behaviour of Grackle sub-step integrator + grackle_chemistry_data->max_iterations = 300; + grackle_chemistry_data->exit_after_iterations_exceeded = 0; + + grackle_chemistry_data->use_subcycle_timestep_damping = 0; + grackle_chemistry_data->subcycle_timestep_damping_interval = 0; + + // Use Rahmati+13 self-shielding; 0=none, 1=HI only, 2=HI+HeI, 3=HI+HeI but + // set HeII rates to 0 + grackle_chemistry_data->self_shielding_method = 0; + grackle_chemistry_data->accuracy = 0.2; + + // Turn on Li+ 2019 dust evolution model + grackle_chemistry_data->use_dust_evol = 1; + grackle_chemistry_data->use_dust_density_field = 1; + + if (DUST_MODEL) { + + grackle_chemistry_data->dust_destruction_eff = 0.3; + grackle_chemistry_data->sne_coeff = 1.0; + grackle_chemistry_data->sne_shockspeed = 100.0; + grackle_chemistry_data->dust_grainsize = 0.1; + grackle_chemistry_data->dust_growth_densref = 1.673e-24; + grackle_chemistry_data->dust_growth_tauref = 1.0; + // Enable dust temperature calculation using ISRF + grackle_chemistry_data->metal_cooling = 1; + grackle_chemistry_data->dust_chemistry = 1; + grackle_chemistry_data->h2_on_dust = 1; + grackle_chemistry_data->use_isrf_field = 1; + grackle_chemistry_data->H2_self_shielding = 4; + grackle_chemistry_data->H2_custom_shielding = 2; // 2 means we specify the H2 shielding length ourselves ( the gas smoothing length) + // Solar abundances to pass to Grackle + grackle_chemistry_data->SolarAbundances[0]=0.2485; // He (10.93 in units where log[H]=12, so photospheric mass fraction -> Y=0.2485 [Hydrogen X=0.7381]; Anders+Grevesse Y=0.2485, X=0.7314) + grackle_chemistry_data->SolarAbundances[1]=2.38e-3; // C (8.43 -> 2.38e-3, AG=3.18e-3) + grackle_chemistry_data->SolarAbundances[2]=0.70e-3; // N (7.83 -> 0.70e-3, AG=1.15e-3) + grackle_chemistry_data->SolarAbundances[3]=5.79e-3; // O (8.69 -> 5.79e-3, AG=9.97e-3) + grackle_chemistry_data->SolarAbundances[4]=1.26e-3; // Ne (7.93 -> 1.26e-3, AG=1.72e-3) + grackle_chemistry_data->SolarAbundances[5]=7.14e-4; // Mg (7.60 -> 7.14e-4, AG=6.75e-4) + grackle_chemistry_data->SolarAbundances[6]=6.71e-3; // Si (7.51 -> 6.71e-4, AG=7.30e-4) + grackle_chemistry_data->SolarAbundances[7]=3.12e-4; // S (7.12 -> 3.12e-4, AG=3.80e-4) + grackle_chemistry_data->SolarAbundances[8]=0.65e-4; // Ca (6.34 -> 0.65e-4, AG=0.67e-4) + grackle_chemistry_data->SolarAbundances[9]=1.31e-3; // Fe (7.50 -> 1.31e-3, AG=1.92e-3) + } else { + grackle_chemistry_data->use_dust_evol = 0; + } + + /* fraction by mass of Hydrogen in the metal-free portion of the gas */ + //grackle_chemistry_data->HydrogenFractionByMass = hydrogen_mass_fraction; + /* Use case B recombination? (On-the-spot approximation) */ + grackle_chemistry_data->CaseBRecombination = case_B_recombination; + + if (local_initialize_chemistry_data(grackle_chemistry_data, + grackle_chemistry_rates, + grackle_units) == 0) { + error("Error in initialize_chemistry_data"); + } +} + +/** + * @brief fill out a grackle field struct with the relevant (gas) data from a + *particle + * + * @param grackle_fields (return) grackle field to copy into + * @param density array of particle density + * @param internal_energy array of particle internal_energy + * @param species_densities array of species densities of particle (HI, HII, + *HeI, HeII, HeIII, e-) + * @param iact_rates array of interaction rates (heating, 3 ioniziation, H2 + *dissociation) + * + **/ +__attribute__((always_inline)) INLINE static void +rt_get_grackle_particle_fields(grackle_field_data *grackle_fields, + gr_float density, gr_float internal_energy, + gr_float species_densities[6], + gr_float iact_rates[5]) { + + int *dimension = malloc(3 * sizeof(int)); + int *start = malloc(3 * sizeof(int)); + int *end = malloc(3 * sizeof(int)); + + dimension[0] = FIELD_SIZE; + dimension[1] = 0; + dimension[2] = 0; + start[0] = 0; + start[1] = 0; + start[2] = 0; + end[0] = FIELD_SIZE - 1; + end[1] = 0; + end[2] = 0; + + grackle_fields->grid_dx = 0.; + grackle_fields->grid_rank = 3; + grackle_fields->grid_dimension = dimension; + grackle_fields->grid_start = start; + grackle_fields->grid_end = end; + + /* Set initial quantities */ + grackle_fields->density = malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->internal_energy = malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->x_velocity = NULL; + grackle_fields->y_velocity = NULL; + grackle_fields->z_velocity = NULL; + /* for primordial_chemistry >= 1 */ + grackle_fields->HI_density = malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->HII_density = malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->HeI_density = malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->HeII_density = malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->HeIII_density = malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->e_density = malloc(FIELD_SIZE * sizeof(gr_float)); + /* for primordial_chemistry >= 2 */ + grackle_fields->HM_density = NULL; + grackle_fields->H2I_density = NULL; + grackle_fields->H2II_density = NULL; + /* for primordial_chemistry >= 3 */ + grackle_fields->DI_density = NULL; + grackle_fields->DII_density = NULL; + grackle_fields->HDI_density = NULL; + /* for metal_cooling = 1 */ + grackle_fields->metal_density = NULL; + /* for use_dust_density_field = 1 */ + grackle_fields->dust_density = NULL; + + /* volumetric heating rate (provide in units [erg s^-1 cm^-3]) */ + grackle_fields->volumetric_heating_rate = NULL; + /* specific heating rate (provide in units [egs s^-1 g^-1] */ + grackle_fields->specific_heating_rate = NULL; + + /* radiative transfer ionization / dissociation rate fields (provide in units + * [1/s]) */ + grackle_fields->RT_HI_ionization_rate = malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->RT_HeI_ionization_rate = + malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->RT_HeII_ionization_rate = + malloc(FIELD_SIZE * sizeof(gr_float)); + grackle_fields->RT_H2_dissociation_rate = + malloc(FIELD_SIZE * sizeof(gr_float)); + /* radiative transfer heating rate field + * (provide in units [erg s^-1 cm^-3] / nHI in cgs) */ + grackle_fields->RT_heating_rate = malloc(FIELD_SIZE * sizeof(gr_float)); + + grackle_fields->H2_self_shielding_length = NULL; + grackle_fields->H2_custom_shielding_factor = NULL; + grackle_fields->isrf_habing = NULL; + + for (int i = 0; i < FIELD_SIZE; i++) { + + grackle_fields->density[i] = density; + grackle_fields->internal_energy[i] = internal_energy; + + grackle_fields->HI_density[i] = species_densities[0]; + grackle_fields->HII_density[i] = species_densities[1]; + grackle_fields->HeI_density[i] = species_densities[2]; + grackle_fields->HeII_density[i] = species_densities[3]; + grackle_fields->HeIII_density[i] = species_densities[4]; + /* e_density = electron density*mh/me = n_e * m_h */ + grackle_fields->e_density[i] = species_densities[5]; + + /* grackle_fields->HM_density[i] = species_densities[6]; */ + /* grackle_fields->H2I_density[i] = species_densities[7]; */ + /* grackle_fields->H2II_density[i] = species_densities[8]; */ + /* grackle_fields->DI_density[i] = species_densities[9]; */ + /* grackle_fields->DII_density[i] = species_densities[10]; */ + /* grackle_fields->HDI_density[i] = species_densities[11]; */ + + /* grackle_fields->metal_density[i] = 0.0; */ + /* solar metallicity */ + /* grackle_chemistry_data.SolarMetalFractionByMass * + * grackle_fields->density[i]; */ + + /* grackle_fields->x_velocity[i] = 0.0; */ + /* grackle_fields->y_velocity[i] = 0.0; */ + /* grackle_fields->z_velocity[i] = 0.0; */ + + /* grackle_fields->volumetric_heating_rate[i] = 0.0; */ + /* grackle_fields->specific_heating_rate[i] = 0.0; */ + + grackle_fields->RT_heating_rate[i] = iact_rates[0]; + grackle_fields->RT_HI_ionization_rate[i] = iact_rates[1]; + grackle_fields->RT_HeI_ionization_rate[i] = iact_rates[2]; + grackle_fields->RT_HeII_ionization_rate[i] = iact_rates[3]; + grackle_fields->RT_H2_dissociation_rate[i] = iact_rates[4]; + } +} + +/** + * @brief free arrays allocated in grackle_fields. + * + * @param grackle_fields grackle fields to clean up + * + **/ +__attribute__((always_inline)) INLINE static void rt_clean_grackle_fields( + grackle_field_data *grackle_fields) { + + free(grackle_fields->grid_dimension); + free(grackle_fields->grid_start); + free(grackle_fields->grid_end); + + /* initial quantities */ + free(grackle_fields->density); + free(grackle_fields->internal_energy); + /* free(grackle_fields->x_velocity); */ + /* free(grackle_fields->y_velocity); */ + /* free(grackle_fields->z_velocity); */ + + /* for primordial_chemistry >= 1 */ + free(grackle_fields->HI_density); + free(grackle_fields->HII_density); + free(grackle_fields->HeI_density); + free(grackle_fields->HeII_density); + free(grackle_fields->HeIII_density); + free(grackle_fields->e_density); + + /* for primordial_chemistry >= 2 */ + /* free(grackle_fields->HM_density); */ + /* free(grackle_fields->H2I_density); */ + /* free(grackle_fields->H2II_density); */ + + /* for primordial_chemistry >= 3 */ + /* free(grackle_fields->DI_density); */ + /* free(grackle_fields->DII_density); */ + /* free(grackle_fields->HDI_density); */ + + /* for metal_cooling = 1 */ + /* free(grackle_fields->metal_density); */ + + /* for use_dust_density_field = 1 */ + /* free(grackle_fields->dust_density); */ + + /* free(grackle_fields->volumetric_heating_rate); */ + /* free(grackle_fields->specific_heating_rate); */ + + free(grackle_fields->RT_HI_ionization_rate); + free(grackle_fields->RT_HeI_ionization_rate); + free(grackle_fields->RT_HeII_ionization_rate); + free(grackle_fields->RT_H2_dissociation_rate); + free(grackle_fields->RT_heating_rate); + + /* free(grackle_fields->H2_self_shielding_length); */ + /* free(grackle_fields->H2_custom_shielding_factor); */ + /* free(grackle_fields->isrf_habing); */ +} + +/** + * @brief Write out all available grackle field data for a given index + * and setup to a file. + * This function is intended for debugging. + * + * @param fp FILE pointer to write into. + * @param grackle_fields grackle field data + * @param grackle_chemistry_data grackle chemistry data. + * @param grackle_units units used by grackle + * @param field_index grackle field index to print out. + **/ +__attribute__((always_inline)) INLINE static void +rt_write_grackle_setup_and_field(FILE *fp, grackle_field_data grackle_fields, + chemistry_data *grackle_chemistry_data, + code_units *grackle_units, int field_index) { + + fprintf(fp, "Grackle chemistry parameters:\n"); + + fprintf(fp, "use_grackle = %d\n", + grackle_chemistry_data->use_grackle); + fprintf(fp, "with_radiative_cooling = %d\n", + grackle_chemistry_data->with_radiative_cooling); + fprintf(fp, "primordial_chemistry = %d\n", + grackle_chemistry_data->primordial_chemistry); + fprintf(fp, "dust_chemistry = %d\n", + grackle_chemistry_data->dust_chemistry); + fprintf(fp, "metal_cooling = %d\n", + grackle_chemistry_data->metal_cooling); + fprintf(fp, "UVbackground = %d\n", + grackle_chemistry_data->UVbackground); + fprintf(fp, "grackle_data_file = %s\n", + grackle_chemistry_data->grackle_data_file); + fprintf(fp, "cmb_temperature_floor = %d\n", + grackle_chemistry_data->cmb_temperature_floor); + fprintf(fp, "Gamma = %g\n", + grackle_chemistry_data->Gamma); + fprintf(fp, "h2_on_dust = %d\n", + grackle_chemistry_data->h2_on_dust); + fprintf(fp, "use_dust_density_field = %d\n", + grackle_chemistry_data->use_dust_density_field); + fprintf(fp, "dust_recombination_cooling = %d\n", + grackle_chemistry_data->dust_recombination_cooling); + fprintf(fp, "photoelectric_heating = %d\n", + grackle_chemistry_data->photoelectric_heating); + fprintf(fp, "photoelectric_heating_rate = %g\n", + grackle_chemistry_data->photoelectric_heating_rate); + fprintf(fp, "use_isrf_field = %d\n", + grackle_chemistry_data->use_isrf_field); + fprintf(fp, "interstellar_radiation_field = %g\n", + grackle_chemistry_data->interstellar_radiation_field); + fprintf(fp, "use_volumetric_heating_rate = %d\n", + grackle_chemistry_data->use_volumetric_heating_rate); + fprintf(fp, "use_specific_heating_rate = %d\n", + grackle_chemistry_data->use_specific_heating_rate); + fprintf(fp, "three_body_rate = %d\n", + grackle_chemistry_data->three_body_rate); + fprintf(fp, "cie_cooling = %d\n", + grackle_chemistry_data->cie_cooling); + fprintf(fp, "h2_optical_depth_approximation = %d\n", + grackle_chemistry_data->h2_optical_depth_approximation); + fprintf(fp, "ih2co = %d\n", + grackle_chemistry_data->ih2co); + fprintf(fp, "ipiht = %d\n", + grackle_chemistry_data->ipiht); + fprintf(fp, "HydrogenFractionByMass = %g\n", + grackle_chemistry_data->HydrogenFractionByMass); + fprintf(fp, "DeuteriumToHydrogenRatio = %g\n", + grackle_chemistry_data->DeuteriumToHydrogenRatio); + fprintf(fp, "SolarMetalFractionByMass = %g\n", + grackle_chemistry_data->SolarMetalFractionByMass); + fprintf(fp, "local_dust_to_gas_ratio = %g\n", + grackle_chemistry_data->local_dust_to_gas_ratio); + fprintf(fp, "NumberOfTemperatureBins = %d\n", + grackle_chemistry_data->NumberOfTemperatureBins); + fprintf(fp, "CaseBRecombination = %d\n", + grackle_chemistry_data->CaseBRecombination); + fprintf(fp, "TemperatureStart = %g\n", + grackle_chemistry_data->TemperatureStart); + fprintf(fp, "TemperatureEnd = %g\n", + grackle_chemistry_data->TemperatureEnd); + fprintf(fp, "NumberOfDustTemperatureBins = %d\n", + grackle_chemistry_data->NumberOfDustTemperatureBins); + fprintf(fp, "DustTemperatureStart = %g\n", + grackle_chemistry_data->DustTemperatureStart); + fprintf(fp, "DustTemperatureEnd = %g\n", + grackle_chemistry_data->DustTemperatureEnd); + fprintf(fp, "Compton_xray_heating = %d\n", + grackle_chemistry_data->Compton_xray_heating); + fprintf(fp, "LWbackground_sawtooth_suppression = %d\n", + grackle_chemistry_data->LWbackground_sawtooth_suppression); + fprintf(fp, "LWbackground_intensity = %g\n", + grackle_chemistry_data->LWbackground_intensity); + fprintf(fp, "UVbackground_redshift_on = %g\n", + grackle_chemistry_data->UVbackground_redshift_on); + fprintf(fp, "UVbackground_redshift_off = %g\n", + grackle_chemistry_data->UVbackground_redshift_off); + fprintf(fp, "UVbackground_redshift_fullon = %g\n", + grackle_chemistry_data->UVbackground_redshift_fullon); + fprintf(fp, "UVbackground_redshift_drop = %g\n", + grackle_chemistry_data->UVbackground_redshift_drop); + fprintf(fp, "cloudy_electron_fraction_factor = %g\n", + grackle_chemistry_data->cloudy_electron_fraction_factor); + fprintf(fp, "use_radiative_transfer = %d\n", + grackle_chemistry_data->use_radiative_transfer); + fprintf(fp, "radiative_transfer_coupled_rate_solver = %d\n", + grackle_chemistry_data->radiative_transfer_coupled_rate_solver); + fprintf(fp, "radiative_transfer_intermediate_step = %d\n", + grackle_chemistry_data->radiative_transfer_intermediate_step); + fprintf(fp, "radiative_transfer_hydrogen_only = %d\n", + grackle_chemistry_data->radiative_transfer_hydrogen_only); + fprintf(fp, "self_shielding_method = %d\n", + grackle_chemistry_data->self_shielding_method); + fprintf(fp, "H2_custom_shielding = %d\n", + grackle_chemistry_data->H2_custom_shielding); + fprintf(fp, "H2_self_shielding = %d\n", + grackle_chemistry_data->H2_self_shielding); + + fprintf(fp, "\nUnits:\n"); + fprintf(fp, "a_units = %g\n", grackle_units->a_units); + fprintf(fp, "a_value = %g\n", grackle_units->a_value); + fprintf(fp, "comoving_coordinates = %d\n", + grackle_units->comoving_coordinates); + fprintf(fp, "density_units = %g\n", grackle_units->density_units); + fprintf(fp, "length_units = %g\n", grackle_units->length_units); + fprintf(fp, "time_units = %g\n", grackle_units->time_units); + fprintf(fp, "velocity_units = %g\n", grackle_units->velocity_units); + +#define rt_print_grackle_field(v) \ + if (grackle_fields.v != NULL) \ + fprintf(fp, "grackle_fields." #v " = %g\n", grackle_fields.v[field_index]) + + fprintf(fp, "\nGrackle field data:\n"); + rt_print_grackle_field(density); + rt_print_grackle_field(internal_energy); + rt_print_grackle_field(HI_density); + rt_print_grackle_field(HII_density); + rt_print_grackle_field(HeI_density); + rt_print_grackle_field(HeII_density); + rt_print_grackle_field(HeIII_density); + rt_print_grackle_field(e_density); + rt_print_grackle_field(HM_density); + rt_print_grackle_field(H2I_density); + rt_print_grackle_field(H2II_density); + rt_print_grackle_field(DI_density); + rt_print_grackle_field(DII_density); + rt_print_grackle_field(HDI_density); + rt_print_grackle_field(metal_density); + rt_print_grackle_field(x_velocity); + rt_print_grackle_field(y_velocity); + rt_print_grackle_field(z_velocity); + rt_print_grackle_field(volumetric_heating_rate); + rt_print_grackle_field(specific_heating_rate); + rt_print_grackle_field(RT_HI_ionization_rate); + rt_print_grackle_field(RT_HeI_ionization_rate); + rt_print_grackle_field(RT_HeII_ionization_rate); + rt_print_grackle_field(RT_H2_dissociation_rate); + rt_print_grackle_field(RT_heating_rate); + +#undef rt_print_grackle_field +} + +#endif /* SWIFT_RT_GRACKLE_UTILS_H */ diff --git a/src/rt/KIARA/rt_gradients.h b/src/rt/KIARA/rt_gradients.h new file mode 100644 index 0000000000..60ca44b3bb --- /dev/null +++ b/src/rt/KIARA/rt_gradients.h @@ -0,0 +1,474 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * Copyright (c) 2020 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_GRADIENTS_KIARA_H +#define SWIFT_RT_GRADIENTS_KIARA_H + +#include "fvpm_geometry.h" +#include "rt_getters.h" +/* #include "rt_slope_limiters_cell.h" [> skipped for now <] */ +#include "rt_slope_limiters_face.h" +#include "rt_unphysical.h" + +/** + * @file src/rt/KIARA/rt_gradients.h + * @brief Main header file for the KIARA M1 closure radiative transfer scheme + * gradients + */ + +/** + * @brief Initialisation of the RT gradient data. + * + * @param p particle to work on + */ +__attribute__((always_inline)) INLINE static void rt_gradients_init( + struct part *restrict p) { + + struct rt_part_data *rtd = &p->rt_data; + + for (int g = 0; g < RT_NGROUPS; g++) { + for (int i = 0; i < 3; i++) { + rtd->gradient[g].energy_density[i] = 0.f; + rtd->gradient[g].flux[i][0] = 0.f; + rtd->gradient[g].flux[i][1] = 0.f; + rtd->gradient[g].flux[i][2] = 0.f; + } + } +} + +/** + * @brief Update the gradients for the given particle with the + * given contributions. + * + * @param p Particle + * @param g photon group index to update (0 <= g < RT_NGROUPS) + * @param dE energy density gradient + * @param dFx gradient of the x direction flux component + * @param dFy gradient of the y direction flux component + * @param dFz gradient of the z direction flux component + */ +__attribute__((always_inline)) INLINE static void rt_gradients_update_part( + struct part *restrict p, int g, float dE[3], float dFx[3], float dFy[3], + float dFz[3]) { + + struct rt_part_data *rtd = &p->rt_data; + + rtd->gradient[g].energy_density[0] += dE[0]; + rtd->gradient[g].energy_density[1] += dE[1]; + rtd->gradient[g].energy_density[2] += dE[2]; + + rtd->gradient[g].flux[0][0] += dFx[0]; + rtd->gradient[g].flux[0][1] += dFx[1]; + rtd->gradient[g].flux[0][2] += dFx[2]; + + rtd->gradient[g].flux[1][0] += dFy[0]; + rtd->gradient[g].flux[1][1] += dFy[1]; + rtd->gradient[g].flux[1][2] += dFy[2]; + + rtd->gradient[g].flux[2][0] += dFz[0]; + rtd->gradient[g].flux[2][1] += dFz[1]; + rtd->gradient[g].flux[2][2] += dFz[2]; +} + +/** + * @brief Finalise the gradient computation after all + * particle-particle interactions are finished. + * + * @param p the Particle + **/ + +__attribute__((always_inline)) INLINE static void rt_finalise_gradient_part( + struct part *restrict p) { + + /* add kernel normalization to gradients */ + const float h = p->h; + const float h_inv = 1.0f / h; + + float norm; + if (fvpm_part_geometry_well_behaved(p)) { + const float hinvdim = pow_dimension(h_inv); + norm = hinvdim; + } else { + const float hinvdimp1 = pow_dimension_plus_one(h_inv); + const float volume = p->geometry.volume; + norm = hinvdimp1 * volume; + } + + struct rt_part_data *rtd = &p->rt_data; + for (int g = 0; g < RT_NGROUPS; g++) { + rtd->gradient[g].energy_density[0] *= norm; + rtd->gradient[g].energy_density[1] *= norm; + rtd->gradient[g].energy_density[2] *= norm; + for (int i = 0; i < 3; i++) { + rtd->gradient[g].flux[i][0] *= norm; + rtd->gradient[g].flux[i][1] *= norm; + rtd->gradient[g].flux[i][2] *= norm; + } + } + + /* the Gizmo-style slope limiting doesn't help for RT as is, + * so we're skipping it for now. */ + /* rt_slope_limit_cell(p); */ +} + +/** + * @brief symmetric gradient calculations done during the neighbour loop + * + * @param r2 Squared distance between the two particles. + * @param dx Distance vector (pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void rt_gradients_collect( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + rt_debug_sequence_check(pi, 2, __func__); + rt_debug_sequence_check(pj, 2, __func__); + + pi->rt_data.debug_calls_iact_gradient_interaction += 1; + pj->rt_data.debug_calls_iact_gradient_interaction += 1; +#endif + + /* Get r and 1/r. */ + const float r = sqrtf(r2); + const float r_inv = 1.0f / r; + + float Bi[3][3]; + float Bj[3][3]; + + for (int k = 0; k < 3; k++) { + for (int l = 0; l < 3; l++) { + Bi[k][l] = pi->geometry.matrix_E[k][l]; + Bj[k][l] = pj->geometry.matrix_E[k][l]; + } + } + + /* Compute kernel of pi. */ + float wi, wi_dx; + const float hi_inv = 1.0f / hi; + const float qi = r * hi_inv; + /* Note: factor 1/omega for psi is swallowed in matrix */ + kernel_deval(qi, &wi, &wi_dx); + + /* Compute kernel of pj */ + float wj, wj_dx; + const float hj_inv = 1.0f / hj; + const float qj = r * hj_inv; + kernel_deval(qj, &wj, &wj_dx); + + /* Compute psi tilde */ + float psii_tilde[3]; + if (fvpm_part_geometry_well_behaved(pi)) { + psii_tilde[0] = + wi * (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]); + psii_tilde[1] = + wi * (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]); + psii_tilde[2] = + wi * (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]); + } else { + const float norm = -wi_dx * r_inv; + psii_tilde[0] = norm * dx[0]; + psii_tilde[1] = norm * dx[1]; + psii_tilde[2] = norm * dx[2]; + } + + float psij_tilde[3]; + if (fvpm_part_geometry_well_behaved(pj)) { + psij_tilde[0] = + wi * (Bj[0][0] * dx[0] + Bj[0][1] * dx[1] + Bj[0][2] * dx[2]); + psij_tilde[1] = + wi * (Bj[1][0] * dx[0] + Bj[1][1] * dx[1] + Bj[1][2] * dx[2]); + psij_tilde[2] = + wi * (Bj[2][0] * dx[0] + Bj[2][1] * dx[1] + Bj[2][2] * dx[2]); + } else { + const float norm = -wj_dx * r_inv; + psij_tilde[0] = norm * dx[0]; + psij_tilde[1] = norm * dx[1]; + psij_tilde[2] = norm * dx[2]; + } + + for (int g = 0; g < RT_NGROUPS; g++) { + + float Ui[4], Uj[4]; + rt_part_get_radiation_state_vector(pi, g, Ui); + rt_part_get_radiation_state_vector(pj, g, Uj); + const float dU[4] = {Ui[0] - Uj[0], Ui[1] - Uj[1], Ui[2] - Uj[2], + Ui[3] - Uj[3]}; + + /* First to the gradients of pi */ + float dE_i[3], dFx_i[3], dFy_i[3], dFz_i[3]; + + /* Compute gradients for pi */ + /* there is a sign difference w.r.t. eqn. (6) because of the inverse + * definition of dx */ + dE_i[0] = dU[0] * psii_tilde[0]; + dE_i[1] = dU[0] * psii_tilde[1]; + dE_i[2] = dU[0] * psii_tilde[2]; + + dFx_i[0] = dU[1] * psii_tilde[0]; + dFx_i[1] = dU[1] * psii_tilde[1]; + dFx_i[2] = dU[1] * psii_tilde[2]; + dFy_i[0] = dU[2] * psii_tilde[0]; + dFy_i[1] = dU[2] * psii_tilde[1]; + dFy_i[2] = dU[2] * psii_tilde[2]; + dFz_i[0] = dU[3] * psii_tilde[0]; + dFz_i[1] = dU[3] * psii_tilde[1]; + dFz_i[2] = dU[3] * psii_tilde[2]; + + rt_gradients_update_part(pi, g, dE_i, dFx_i, dFy_i, dFz_i); + /* the Gizmo-style slope limiting doesn't help for RT as is, + * so we're skipping it for now. */ + /* rt_slope_limit_cell_collect(pi, pj, g); */ + + /* Now do the gradients of pj */ + float dE_j[3], dFx_j[3], dFy_j[3], dFz_j[3]; + + /* We don't need a sign change here: both the dx and the dU + * should switch their sign, resulting in no net change */ + dE_j[0] = dU[0] * psij_tilde[0]; + dE_j[1] = dU[0] * psij_tilde[1]; + dE_j[2] = dU[0] * psij_tilde[2]; + + dFx_j[0] = dU[1] * psij_tilde[0]; + dFx_j[1] = dU[1] * psij_tilde[1]; + dFx_j[2] = dU[1] * psij_tilde[2]; + dFy_j[0] = dU[2] * psij_tilde[0]; + dFy_j[1] = dU[2] * psij_tilde[1]; + dFy_j[2] = dU[2] * psij_tilde[2]; + dFz_j[0] = dU[3] * psij_tilde[0]; + dFz_j[1] = dU[3] * psij_tilde[1]; + dFz_j[2] = dU[3] * psij_tilde[2]; + + rt_gradients_update_part(pj, g, dE_j, dFx_j, dFy_j, dFz_j); + /* the Gizmo-style slope limiting doesn't help for RT as is, + * so we're skipping it for now. */ + /* rt_slope_limit_cell_collect(pj, pi, g); */ + } +} + +/** + * @brief Non-symmetric gradient calculations done during the neighbour loop + * + * @param r2 Squared distance between the two particles. + * @param dx Distance vector (pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void rt_gradients_nonsym_collect( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + rt_debug_sequence_check(pi, 2, __func__); + pi->rt_data.debug_calls_iact_gradient_interaction += 1; +#endif + + /* Get r and 1/r. */ + const float r = sqrtf(r2); + const float r_inv = 1.0f / r; + + float Bi[3][3]; + + for (int k = 0; k < 3; k++) { + for (int l = 0; l < 3; l++) { + Bi[k][l] = pi->geometry.matrix_E[k][l]; + } + } + + /* Compute kernel of pi. */ + float wi, wi_dx; + const float hi_inv = 1.0f / hi; + const float qi = r * hi_inv; + /* factor 1/omega for psi is swallowed in matrix */ + kernel_deval(qi, &wi, &wi_dx); + + /* Compute psi tilde */ + float psii_tilde[3]; + if (fvpm_part_geometry_well_behaved(pi)) { + psii_tilde[0] = + wi * (Bi[0][0] * dx[0] + Bi[0][1] * dx[1] + Bi[0][2] * dx[2]); + psii_tilde[1] = + wi * (Bi[1][0] * dx[0] + Bi[1][1] * dx[1] + Bi[1][2] * dx[2]); + psii_tilde[2] = + wi * (Bi[2][0] * dx[0] + Bi[2][1] * dx[1] + Bi[2][2] * dx[2]); + } else { + const float norm = -wi_dx * r_inv; + psii_tilde[0] = norm * dx[0]; + psii_tilde[1] = norm * dx[1]; + psii_tilde[2] = norm * dx[2]; + } + + for (int g = 0; g < RT_NGROUPS; g++) { + + float Ui[4], Uj[4]; + rt_part_get_radiation_state_vector(pi, g, Ui); + rt_part_get_radiation_state_vector(pj, g, Uj); + const float dU[4] = {Ui[0] - Uj[0], Ui[1] - Uj[1], Ui[2] - Uj[2], + Ui[3] - Uj[3]}; + + float dE_i[3], dFx_i[3], dFy_i[3], dFz_i[3]; + + /* Compute gradients for pi */ + /* there is a sign difference w.r.t. eqn. (6) because of the inverse + * definition of dx */ + dE_i[0] = dU[0] * psii_tilde[0]; + dE_i[1] = dU[0] * psii_tilde[1]; + dE_i[2] = dU[0] * psii_tilde[2]; + + dFx_i[0] = dU[1] * psii_tilde[0]; + dFx_i[1] = dU[1] * psii_tilde[1]; + dFx_i[2] = dU[1] * psii_tilde[2]; + dFy_i[0] = dU[2] * psii_tilde[0]; + dFy_i[1] = dU[2] * psii_tilde[1]; + dFy_i[2] = dU[2] * psii_tilde[2]; + dFz_i[0] = dU[3] * psii_tilde[0]; + dFz_i[1] = dU[3] * psii_tilde[1]; + dFz_i[2] = dU[3] * psii_tilde[2]; + + rt_gradients_update_part(pi, g, dE_i, dFx_i, dFy_i, dFz_i); + /* the Gizmo-style slope limiting doesn't help for RT as is, + * so we're skipping it for now. */ + /* rt_slope_limit_cell_collect(pi, pj, g); */ + } +} + +/** + * @brief Extrapolate the given gradient over the given distance. + * + * @param dU Gradient of the quantity + * @param dx Distance vector + * @return Change in the quantity after a displacement along the given distance + * vector. + */ +__attribute__((always_inline)) INLINE static float rt_gradients_extrapolate( + const float dU[3], const float dx[3]) { + + return dU[0] * dx[0] + dU[1] * dx[1] + dU[2] * dx[2]; +} + +/** + * @brief Gradients reconstruction. Predict the value at point x_ij given + * current values at particle positions and gradients at particle positions. + * + * @param pi Particle i + * @param pj Particle j + * @param Ui (return) Resulting predicted and limited radiation state of + * particle i + * @param Uj (return) Resulting predicted and limited radiation state of + * particle j + * @param group which photon group to use + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param r Comoving distance between particle i and particle j. + * @param xij_i Position of the "interface" w.r.t. position of particle i + */ +__attribute__((always_inline)) INLINE static void rt_gradients_predict( + const struct part *restrict pi, const struct part *restrict pj, float Ui[4], + float Uj[4], int group, const float dx[3], const float r, + const float xij_i[3]) { + + rt_part_get_radiation_state_vector(pi, group, Ui); + rt_part_get_radiation_state_vector(pj, group, Uj); + /* No need to check unphysical state here: + * they haven't been touched since the call + * to rt_injection_update_photon_density */ + + float dE_i[3], dFx_i[3], dFy_i[3], dFz_i[3]; + float dE_j[3], dFx_j[3], dFy_j[3], dFz_j[3]; + rt_part_get_gradients(pi, group, dE_i, dFx_i, dFy_i, dFz_i); + rt_part_get_gradients(pj, group, dE_j, dFx_j, dFy_j, dFz_j); + + /* Compute interface position (relative to pj, since we don't need the actual + * position) eqn. (8) + * Do it this way in case dx contains periodicity corrections already */ + const float xij_j[3] = {xij_i[0] + dx[0], xij_i[1] + dx[1], xij_i[2] + dx[2]}; + + float dUi[4]; + dUi[0] = rt_gradients_extrapolate(dE_i, xij_i); + dUi[1] = rt_gradients_extrapolate(dFx_i, xij_i); + dUi[2] = rt_gradients_extrapolate(dFy_i, xij_i); + dUi[3] = rt_gradients_extrapolate(dFz_i, xij_i); + + float dUj[4]; + dUj[0] = rt_gradients_extrapolate(dE_j, xij_j); + dUj[1] = rt_gradients_extrapolate(dFx_j, xij_j); + dUj[2] = rt_gradients_extrapolate(dFy_j, xij_j); + dUj[3] = rt_gradients_extrapolate(dFz_j, xij_j); + + /* Apply the slope limiter at this interface */ + rt_slope_limit_face(Ui, Uj, dUi, dUj, dx, r, xij_i, xij_j); + + Ui[0] += dUi[0]; + Ui[1] += dUi[1]; + Ui[2] += dUi[2]; + Ui[3] += dUi[3]; + + Uj[0] += dUj[0]; + Uj[1] += dUj[1]; + Uj[2] += dUj[2]; + Uj[3] += dUj[3]; + + /* Check and correct unphysical extrapolated states */ + rt_check_unphysical_state(Ui, &Ui[1], /*e_old=*/0.f, /*callloc=*/1); + rt_check_unphysical_state(Uj, &Uj[1], /*e_old=*/0.f, /*callloc=*/1); +} + +/** + * @brief Gradients reconstruction. Predict the value at point x_ij given + * current values at particle positions and gradients at particle positions. + * + * @param p Particle to work on + * @param U (return) Resulting predicted and limited radiation state of + * particle + * @param group which photon group to use + * @param dx The drift distance + */ +__attribute__((always_inline)) INLINE static void rt_gradients_predict_drift( + const struct part *restrict p, float U[4], int group, const float dx[3]) { + + rt_part_get_radiation_state_vector(p, group, U); + /* No need to check unphysical state here: + * they haven't been touched since the call + * to rt_injection_update_photon_density */ + + float dE[3], dFx[3], dFy[3], dFz[3]; + rt_part_get_gradients(p, group, dE, dFx, dFy, dFz); + + float dU[4]; + dU[0] = rt_gradients_extrapolate(dE, dx); + dU[1] = rt_gradients_extrapolate(dFx, dx); + dU[2] = rt_gradients_extrapolate(dFy, dx); + dU[3] = rt_gradients_extrapolate(dFz, dx); + + U[0] += dU[0]; + U[1] += dU[1]; + U[2] += dU[2]; + U[3] += dU[3]; + + /* Check and correct unphysical extrapolated states */ + rt_check_unphysical_state(U, &U[1], /*e_old=*/0.f, /*callloc=*/1); +} + +#endif /* SWIFT_RT_GRADIENT_KIARA_H */ diff --git a/src/rt/KIARA/rt_iact.h b/src/rt/KIARA/rt_iact.h new file mode 100644 index 0000000000..840238320b --- /dev/null +++ b/src/rt/KIARA/rt_iact.h @@ -0,0 +1,481 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2020 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_IACT_KIARA_H +#define SWIFT_RT_IACT_KIARA_H + +#include "fvpm_geometry.h" +#include "rt_debugging.h" +#include "rt_flux.h" +#include "rt_gradients.h" + +/** + * @file src/rt/KIARA/rt_iact.h + * @brief Main header file for the KIARA M1 closure radiative transfer scheme + * particle interactions. + */ + +/** + * @brief Preparation step for injection to gather necessary data. + * This function gets called during the feedback force loop. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (si - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param si First (star) particle. + * @param pj Second (gas) particle (not updated). + * @param cosmo The cosmological model. + * @param rt_props Properties of the RT scheme. + */ + +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_rt_injection_prep(const float r2, const float dx[3], + const float hi, const float hj, + struct spart *si, const struct part *pj, + const struct cosmology *cosmo, + const struct rt_props *rt_props) { + + /* If the star doesn't have any neighbours, we + * have nothing to do here. */ + if (si->density.wcount == 0.f) return; + +#ifdef SWIFT_RT_DEBUG_CHECKS + si->rt_data.debug_iact_hydro_inject_prep += 1; +#endif + + /* Compute the weight of the neighbouring particle */ + const float hi_inv = 1.f / hi; + const float r = sqrtf(r2); + const float xi = r * hi_inv; + float wi; + kernel_eval(xi, &wi); + const float hi_inv_dim = pow_dimension(hi_inv); + /* psi(x_star - x_gas, h_star) */ + /* Note: skip the devision by si->density.wcount here. It'll cancel out by the + * normalization anyway, and furthermore now that the injection prep is done + * during the star density loop, si->density.wcount won't be computed at this + * stage yet. */ + const float psi = wi * hi_inv_dim; + + /* Now add that weight to the appropriate octant */ + int octant_index = 0; + + if (dx[0] > 0.f) octant_index += 1; + if (dx[1] > 0.f) octant_index += 2; + if (dx[2] > 0.f) octant_index += 4; + + si->rt_data.octant_weights[octant_index] += psi; +} + +/** + * @brief Injection step interaction between star and hydro particles. + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param si Star particle. + * @param pj Hydro particle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + * @param rt_props Properties of the RT scheme. + */ +__attribute__((always_inline)) INLINE static void runner_iact_rt_inject( + const float r2, const float dx[3], const float hi, const float hj, + struct spart *restrict si, struct part *restrict pj, const float a, + const float H, const struct rt_props *rt_props) { + + /* If the star doesn't have any neighbours, we + * have nothing to do here. */ + if (si->density.wcount == 0.f) return; + +#ifdef SWIFT_RT_DEBUG_CHECKS + + /* Do some checks and increase neighbour counts + * before other potential early exits */ + if (si->rt_data.debug_iact_hydro_inject_prep == 0) + error( + "Injecting energy from star that wasn't called during injection prep"); + + if (!si->rt_data.debug_emission_rate_set) + error("Injecting energy from star without setting emission rate"); + + si->rt_data.debug_iact_hydro_inject += 1; + si->rt_data.debug_radiation_emitted_tot += 1ULL; + + pj->rt_data.debug_iact_stars_inject += 1; + pj->rt_data.debug_radiation_absorbed_tot += 1ULL; + +#endif + + /* Compute the weight of the neighbouring particle */ + const float hi_inv = 1.f / hi; + const float r = sqrtf(r2); + const float xi = r * hi_inv; + float wi; + kernel_eval(xi, &wi); + const float hi_inv_dim = pow_dimension(hi_inv); + /* psi(x_star - x_gas, h_star) */ + /* Skip the division by si->density.wcount to remain consistent */ + const float psi = wi * hi_inv_dim; + +#if defined(HYDRO_DIMENSION_3D) + const int maxind = 8; +#elif defined(HYDRO_DIMENSION_2D) + const int maxind = 4; +#elif defined(HYDRO_DIMENSION_1D) + const int maxind = 2; +#endif + + /* Get weight for particle, including isotropy correction */ + float nonempty_octants = 0.f; + + for (int i = 0; i < maxind; i++) { + if (si->rt_data.octant_weights[i] > 0.f) nonempty_octants += 1.f; + } + + int octant_index = 0; + if (dx[0] > 0.f) octant_index += 1; + if (dx[1] > 0.f) octant_index += 2; + if (dx[2] > 0.f) octant_index += 4; + + const float octw = si->rt_data.octant_weights[octant_index]; + /* We might end up in this scenario due to roundoff errors */ + if (psi == 0.f || octw == 0.f) return; + + const float weight = psi / (nonempty_octants * octw); + const float Vinv = 1.f / pj->geometry.volume; + + /* Nurse, the patient is ready now */ + for (int g = 0; g < RT_NGROUPS; g++) { + /* Inject energy. */ + const float injected_energy_density = + si->rt_data.emission_this_step[g] * weight * Vinv; + pj->rt_data.radiation[g].energy_density += injected_energy_density; + if (pj->rt_data.radiation[g].energy_density > 0.f && g==0) { + //message("RT_energy_inj: z=%g sid=%lld pid=%lld RTgrp=%d w=%g Vinv=%g Reff=%g h=%g Enew=%g Eold=%g frac=%g\n", 1./a-1., si->id, pj->id, g, weight, Vinv, pow(3./(4.*M_PI*Vinv), 1./3), pj->h, injected_energy_density, pj->rt_data.radiation[g].energy_density-injected_energy_density, injected_energy_density/pj->rt_data.radiation[g].energy_density); + } + /* Don't inject flux. */ + } + +#ifdef SWIFT_RT_DEBUG_CHECKS + /* Take note how much energy we actually injected */ + for (int g = 0; g < RT_NGROUPS; g++) { + const float injected_energy = si->rt_data.emission_this_step[g] * weight; + if (isinf(injected_energy) || isnan(injected_energy)) + error( + "Injecting abnormal energy spart %lld part %lld group %d | %.6e %.6e " + "%.6e", + si->id, pj->id, g, injected_energy, weight, + + si->rt_data.emission_this_step[g]); + si->rt_data.debug_injected_energy[g] += injected_energy; + si->rt_data.debug_injected_energy_tot[g] += injected_energy; + } + si->rt_data.debug_psi_sum += psi; +#endif +} + +/** + * @brief Flux calculation between particle i and particle j + * + * This method calls runner_iact_rt_fluxes_common with mode 1. + * + * @param r2 Comoving squared distance between particle i and particle j. + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Particle i. + * @param pj Particle j. + * @param a Current scale factor. + * @param H Current Hubble parameter. + * @param mode 0 if non-symmetric interaction, 1 if symmetric + */ +__attribute__((always_inline)) INLINE static void runner_iact_rt_flux_common( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H, int mode) { + +#ifdef SWIFT_RT_DEBUG_CHECKS + const char *func_name = (mode == 1) ? "sym flux iact" : "nonsym flux iact"; + rt_debug_sequence_check(pi, 3, func_name); + pi->rt_data.debug_calls_iact_transport_interaction += 1; + + if (mode == 1) { + rt_debug_sequence_check(pj, 3, func_name); + pj->rt_data.debug_calls_iact_transport_interaction += 1; + } +#endif + + /* Get r and 1/r. */ + const float r = sqrtf(r2); + const float r_inv = 1.0f / r; + + /* Initialize local variables */ + float Bi[3][3]; + float Bj[3][3]; + for (int k = 0; k < 3; k++) { + for (int l = 0; l < 3; l++) { + Bi[k][l] = pi->geometry.matrix_E[k][l]; + Bj[k][l] = pj->geometry.matrix_E[k][l]; + } + } + const float Vi = pi->geometry.volume; + const float Vj = pj->geometry.volume; + + /* Compute kernel of pi. */ + float wi, wi_dx; + const float hi_inv = 1.0f / hi; + const float hi_inv_dim = pow_dimension(hi_inv); + const float xi = r * hi_inv; + kernel_deval(xi, &wi, &wi_dx); + + /* Compute kernel of pj. */ + float wj, wj_dx; + const float hj_inv = 1.0f / hj; + const float hj_inv_dim = pow_dimension(hj_inv); + const float xj = r * hj_inv; + kernel_deval(xj, &wj, &wj_dx); + + /* Compute (square of) area */ + /* eqn. (7) */ + float Anorm2 = 0.0f; + float A[3]; + if (fvpm_part_geometry_well_behaved(pi) && + fvpm_part_geometry_well_behaved(pj)) { + /* in principle, we use Vi and Vj as weights for the left and right + * contributions to the generalized surface vector. + * However, if Vi and Vj are very different (because they have very + * different smoothing lengths), then the expressions below are more + * stable. */ + float Xi = Vi; + float Xj = Vj; +#ifdef GIZMO_VOLUME_CORRECTION + if (fabsf(Vi - Vj) / min(Vi, Vj) > 1.5f * hydro_dimension) { + Xi = (Vi * hj + Vj * hi) / (hi + hj); + Xj = Xi; + } +#endif + for (int k = 0; k < 3; k++) { + /* we add a minus sign since dx is pi->x - pj->x */ + A[k] = -Xi * (Bi[k][0] * dx[0] + Bi[k][1] * dx[1] + Bi[k][2] * dx[2]) * + wi * hi_inv_dim - + Xj * (Bj[k][0] * dx[0] + Bj[k][1] * dx[1] + Bj[k][2] * dx[2]) * + wj * hj_inv_dim; + Anorm2 += A[k] * A[k]; + } + } else { + /* ill condition gradient matrix: revert to SPH face area */ + const float hidp1 = pow_dimension_plus_one(hi_inv); + const float hjdp1 = pow_dimension_plus_one(hj_inv); + const float Anorm = + -(hidp1 * Vi * Vi * wi_dx + hjdp1 * Vj * Vj * wj_dx) * r_inv; + A[0] = -Anorm * dx[0]; + A[1] = -Anorm * dx[1]; + A[2] = -Anorm * dx[2]; + Anorm2 = Anorm * Anorm * r2; + } + + /* if the interface has no area, nothing happens and we return */ + /* continuing results in dividing by zero and NaN's... */ + if (Anorm2 == 0.0f) { + return; + } + + /* Compute the area */ + const float Anorm_inv = 1.0f / sqrtf(Anorm2); + const float Anorm = Anorm2 * Anorm_inv; + +#ifdef SWIFT_DEBUG_CHECKS + /* For stability reasons, we do require A and dx to have opposite + * directions (basically meaning that the surface normal for the surface + * always points from particle i to particle j, as it would in a real + * moving-mesh code). If not, our scheme is no longer upwind and hence can + * become unstable. */ + const float dA_dot_dx = A[0] * dx[0] + A[1] * dx[1] + A[2] * dx[2]; + /* In GIZMO, Phil Hopkins reverts to an SPH integration scheme if this + * happens. We curently just ignore this case and display a message. */ + const float rdim = pow_dimension(r); + if (dA_dot_dx > 1.e-6f * rdim) { + message("Ill conditioned gradient matrix (%g %g %g %g %g)!", dA_dot_dx, + Anorm, Vi, Vj, r); + } +#endif + + /* compute the normal vector of the interface */ + const float n_unit[3] = {A[0] * Anorm_inv, A[1] * Anorm_inv, + A[2] * Anorm_inv}; + + /* Compute interface position (relative to pi, since we don't need + * the actual position) eqn. (8) */ + const float xfac = -hi / (hi + hj); + const float xij_i[3] = {xfac * dx[0], xfac * dx[1], xfac * dx[2]}; + + struct rt_part_data *restrict rti = &pi->rt_data; + struct rt_part_data *restrict rtj = &pj->rt_data; + /* Get the time step for the flux exchange. This is always the smallest time + * step among the two particles. */ + const float mindt = + (rtj->flux_dt > 0.f) ? fminf(rti->flux_dt, rtj->flux_dt) : rti->flux_dt; + + for (int g = 0; g < RT_NGROUPS; g++) { + + /* radiation state to be used to compute the flux */ + float Ui[4], Uj[4]; + rt_gradients_predict(pi, pj, Ui, Uj, g, dx, r, xij_i); + + /* For first order method, skip the gradients */ + /* float Ui[4], Uj[4]; */ + /* rt_part_get_radiation_state_vector(pi, g, Ui); */ + /* rt_part_get_radiation_state_vector(pj, g, Uj); */ + /* No need to check for unphysical quantities, they + * haven't been touched since + * rt_injection_update_photon_densities */ + + float totflux[4]; + + rt_compute_flux(Ui, Uj, n_unit, Anorm, totflux); + + /* When solving the Riemann problem, we assume pi is left state, and + * pj is right state. The sign convention is that a positive total + * flux is subtracted from the left state, and added to the right + * state, based on how we chose the unit vector. By this convention, + * the time integration results in conserved quantity += flux * dt */ + /* Unlike in SPH schemes, we do need to update inactive neighbours, so that + * the fluxes are always exchanged symmetrically. Thanks to our sneaky use + * of flux_dt, we can detect inactive neighbours through their negative time + * step. */ + /* Make sure mindt larger than 0 to avoid mindt=-1 case. */ + if (mindt > 0.f) { + rti->flux[g].energy -= totflux[0] * mindt; + rti->flux[g].flux[0] -= totflux[1] * mindt; + rti->flux[g].flux[1] -= totflux[2] * mindt; + rti->flux[g].flux[2] -= totflux[3] * mindt; + if (mode == 1 || (rtj->flux_dt < 0.f)) { + rtj->flux[g].energy += totflux[0] * mindt; + rtj->flux[g].flux[0] += totflux[1] * mindt; + rtj->flux[g].flux[1] += totflux[2] * mindt; + rtj->flux[g].flux[2] += totflux[3] * mindt; + } + } + } +} + +/** + * @brief Flux calculation between particle i and particle j + * + * This method calls runner_iact_rt_fluxes_common with mode 1. + * + * @param r2 Comoving squared distance between particle i and particle j. + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Particle i. + * @param pj Particle j. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_rt_transport( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H) { + + runner_iact_rt_flux_common(r2, dx, hi, hj, pi, pj, a, H, 1); +} + +/** + * @brief Flux calculation between particle i and particle j: non-symmetric + * version + * + * This method calls runner_iact_rt_fluxes_common with mode 0. + * + * @param r2 Comoving squared distance between particle i and particle j. + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Particle i. + * @param pj Particle j. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_rt_transport(const float r2, const float dx[3], + const float hi, const float hj, + struct part *restrict pi, + struct part *restrict pj, const float a, + const float H) { + + runner_iact_rt_flux_common(r2, dx, hi, hj, pi, pj, a, H, 0); +} + +/** + * @brief Calculate the gradient interaction between particle i and particle j + * + * This method wraps around rt_gradients_collect, which can be an empty + * method, in which case no gradients are used. + * + * @param r2 Comoving squared distance between particle i and particle j. + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Particle i. + * @param pj Particle j. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_rt_gradient( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H) { + + rt_gradients_collect(r2, dx, hi, hj, pi, pj); +} + +/** + * @brief Calculate the gradient interaction between particle i and particle j: + * non-symmetric version + * + * This method wraps around rt_gradients_nonsym_collect, which can be an + * empty method, in which case no gradients are used. + * + * @param r2 Comoving squared distance between particle i and particle j. + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi Particle i. + * @param pj Particle j. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_rt_gradient(const float r2, const float dx[3], + const float hi, const float hj, + struct part *restrict pi, + struct part *restrict pj, const float a, + const float H) { + + rt_gradients_nonsym_collect(r2, dx, hi, hj, pi, pj); +} + +#endif /* SWIFT_RT_IACT_KIARA_H */ diff --git a/src/rt/KIARA/rt_interaction_cross_sections.c b/src/rt/KIARA/rt_interaction_cross_sections.c new file mode 100644 index 0000000000..bdb77bc632 --- /dev/null +++ b/src/rt/KIARA/rt_interaction_cross_sections.c @@ -0,0 +1,458 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#include "rt_interaction_cross_sections.h" + +#include "error.h" +#include "rt_blackbody.h" +#include "rt_properties.h" +#include "rt_species.h" + +#include + +/** + * @brief compute the chosen spectrum for a given frequency nu. + * This function is intended to be used to integrate the spectra + * over a frequency range in order to obtain averaged ionization + * cross sections. + * + * @param nu frequency at which to evaluate spectrum + * @param params spectrum integration params. Needs to be of type + * void* for GSL integrators. + */ +__attribute__((always_inline)) INLINE static double +rt_interaction_rates_get_spectrum(const double nu, void *params) { + /* Keep this function in the .c file so you don't have to + * include the spectra .h files like rt_blackbody.h anywhere + * else */ + + struct rt_spectrum_integration_params *pars = + (struct rt_spectrum_integration_params *)params; + + if (pars->spectrum_type == 0) { + /* Constant spectrum */ + if (nu <= pars->const_stellar_spectrum_frequency_max) { + return 1.; + } else { + return 0.; + } + } else if (pars->spectrum_type == 1) { + /* Blackbody spectrum */ + const double T = pars->T; + const double kB = pars->kB; + const double h_planck = pars->h_planck; + const double c = pars->c; + return blackbody_spectrum_intensity(nu, T, kB, h_planck, c); + } else if (pars->spectrum_type == 2) { + /* TODO: currently set it to Constant spectrum */ + if (nu <= pars->const_stellar_spectrum_frequency_max) { + return 1.; + } else { + return 0.; + } + } else { + error("Unknown stellar spectrum type selected: %d", pars->spectrum_type); + return 0.; + } +} + +/** + * Spectrum function to be integrated. + * This function is called by the GSL integrator. + * + * @param nu frequency at which to evaluate spectrum + * @param params spectrum integration params. Needs to be of type + * void* for GSL integrators. + */ +static double spectrum_integrand(double nu, void *params) { + return rt_interaction_rates_get_spectrum(nu, params); +} + +/** + * Spectrum function divided by photon energy h*nu to be integrated. + * This function is called by the GSL integrator. + * + * @param nu frequency at which to evaluate spectrum + * @param params spectrum integration params. Needs to be of type + * void* for GSL integrators. + */ +static double spectrum_over_hnu_integrand(double nu, void *params) { + struct rt_spectrum_integration_params *p = + (struct rt_spectrum_integration_params *)params; + const double E = nu * p->h_planck; + const double J = rt_interaction_rates_get_spectrum(nu, params); + if (E > 0.) { + return J / E; + } else { + return 0.; + } +} + +/** + * Spectrum times cross section function to be integrated. + * This function is called by the GSL integrator. + * + * @param nu frequency at which to evaluate spectrum + * @param params spectrum integration params. Needs to be of type + * void* for GSL integrators. + */ +static double spectrum_times_sigma_integrand(double nu, void *params) { + struct rt_spectrum_integration_params *p = + (struct rt_spectrum_integration_params *)params; + const double E = nu * p->h_planck; + const double sigma = + photoionization_cross_section(E, p->species, p->cs_params); + const double J = rt_interaction_rates_get_spectrum(nu, params); + return J * sigma; +} + +/** + * Spectrum times cross section divided by h*nu function to be integrated. + * This function is called by the GSL integrator. + * + * @param nu frequency at which to evaluate spectrum + * @param params spectrum integration params. Needs to be of type + * void* for GSL integrators. + */ +static double spectrum_times_sigma_over_hnu_integrand(double nu, void *params) { + struct rt_spectrum_integration_params *p = + (struct rt_spectrum_integration_params *)params; + const double E = nu * p->h_planck; + const double sigma = + photoionization_cross_section(E, p->species, p->cs_params); + const double J = rt_interaction_rates_get_spectrum(nu, params); + return J * sigma / E; +} + +/** + * Integrate a function from nu_start to nu_stop with GSL routines + * + * @param function function to integrate + * @param nu_start lower boundary of the integral + * @param nu_stop upper boundary of the integral + * @param params spectrum integration params. + * */ +static double rt_cross_sections_integrate_gsl( + double (*function)(double, void *), double nu_start, double nu_stop, + int npoints, struct rt_spectrum_integration_params *params) { + + gsl_function F; + gsl_integration_workspace *w = gsl_integration_workspace_alloc(npoints); + double result, error; + + F.function = function; + F.params = (void *)params; + /* NOTE: there is an option to use the integrator with an upper limit + * of infinity, but this is accurate enough for now when setting a + * high enough maximal frequency. */ + gsl_integration_qags(&F, nu_start, nu_stop, /*espabs=*/0., /*epsrel=*/1e-7, + npoints, w, &result, &error); + + /* Clean up after yourself. */ + gsl_integration_workspace_free(w); + + return result; +} + +/** + * @brief allocate and pre-compute the averaged cross sections + * for each photon group and ionizing species. + * + * @param rt_props RT properties struct + * @param phys_const physical constants struct + * @param us internal units struct + **/ +void rt_cross_sections_init(struct rt_props *restrict rt_props, + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us) { + + /* Allocate the space to store the (cross section) integrals */ + /* --------------------------------------------------------- */ + + double **cse = malloc(RT_NGROUPS * sizeof(double *)); + double **csn = malloc(RT_NGROUPS * sizeof(double *)); + double *av_energy = rt_props->average_photon_energy; + double *photon_number_integral = rt_props->photon_number_integral; + for (int group = 0; group < RT_NGROUPS; group++) { + cse[group] = malloc(rt_ionizing_species_count * sizeof(double)); + csn[group] = malloc(rt_ionizing_species_count * sizeof(double)); + av_energy[group] = 0.; + photon_number_integral[group] = 0.; + } + + /* TODO: BPASS photon group properties are hard code in now, + * need to think a better way to read in. + * Current values are using the values in first year review. */ + if (rt_props->stellar_spectrum_type == 2){ + /* Energy weighted cross section. unit cm^2 */ + cse[0][0] = 3.21e-18, cse[0][1] = 0, cse[0][2] = 0; + cse[1][0] = 6.99e-19, cse[1][1] = 5.14e-18, cse[1][2] = 0; + cse[2][0] = 1.19e-19, cse[2][1] = 1.64e-18, cse[2][2] = 4.64e-19; + + /* Number weighted cross section. unit cm^2 */ + csn[0][0] = 3.46e-18, csn[0][1] = 0, csn[0][2] = 0; + csn[1][0] = 7.55e-19, csn[1][1] = 5.42e-18, csn[1][2] = 0; + csn[2][0] = 1.19e-19, csn[2][1] = 1.65e-18, csn[2][2] = 4.55e-19; + + /* Average photon energy. unit erg */ + av_energy[0] = 2.864693e-11; + av_energy[1] = 4.955534e-11; + av_energy[2] = 8.834406e-11; + + } else { + + double integral_E[RT_NGROUPS]; + double integral_N[RT_NGROUPS]; + double integral_sigma_E[RT_NGROUPS][rt_ionizing_species_count]; + double integral_sigma_E_over_hnu[RT_NGROUPS][rt_ionizing_species_count]; + + /* Grab constants and conversions in cgs */ + /* ------------------------------------- */ + const double T_cgs = units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + + const float dimension_kB[5] = {1, 2, -2, 0, -1}; /* [g cm^2 s^-2 K^-1] */ + const double kB_to_cgs = + units_general_cgs_conversion_factor(us, dimension_kB); + const double kB_cgs = phys_const->const_boltzmann_k * kB_to_cgs; + + const float dimension_h[5] = {1, 2, -1, 0, 0}; /* [g cm^2 s^-1] */ + const double h_to_cgs = units_general_cgs_conversion_factor(us, dimension_h); + const double h_planck_cgs = phys_const->const_planck_h * h_to_cgs; + + const double c_cgs = phys_const->const_speed_light_c * + units_cgs_conversion_factor(us, UNIT_CONV_VELOCITY); + + /* Prepare parameter struct for integration functions */ + /* -------------------------------------------------- */ + const int spectype = rt_props->stellar_spectrum_type; + const double maxfreq_const_spectrum = + rt_props->const_stellar_spectrum_max_frequency; + const double T_bb = rt_props->stellar_spectrum_blackbody_T * T_cgs; + struct rt_photoion_cs_parameters cs_params = rt_init_photoion_cs_params_cgs(); + + struct rt_spectrum_integration_params integration_params = { + /*species=*/0, + /*spectrum_type=*/spectype, + /*freq_max for const spectrum=*/maxfreq_const_spectrum, + /*T=*/T_bb, + /*kB=*/kB_cgs, + /*h_planck=*/h_planck_cgs, + /*c=*/c_cgs, + /*cross section params=*/&cs_params}; + + /* Set up Integration Limits */ + /* ------------------------- */ + + /* Get start and end points of the integrals */ + double nu_start[RT_NGROUPS]; + double nu_stop[RT_NGROUPS]; + for (int group = 0; group < RT_NGROUPS; group++) + nu_start[group] = rt_props->photon_groups[group]; + for (int group = 0; group < RT_NGROUPS - 1; group++) + nu_stop[group] = rt_props->photon_groups[group + 1]; + + if (RT_NGROUPS == 1) { + /* If we only have one group, start integrating from the Hydrogen + * ionization frequency, not from zero. The reasoning here is that + * typically you define the *ionizing* radiation as stellar emission + * rates, not the *total* radiation. */ + nu_start[0] = cs_params.E_ion[rt_ionizing_species_HI] / h_planck_cgs; + if (engine_rank == 0) + message( + "WARNING: with only 1 photon group, I'll start integrating" + " the cross sections at the first ionizing frequency %.3g", + nu_start[0]); + } else { + /* don't start at exactly 0 to avoid unlucky divisions */ + if (nu_start[0] == 0.) nu_start[0] = min(1e-20, 1e-12 * nu_start[1]); + } + + /* Get frequency at which we stop integrating */ + double nu_stop_final; + if (rt_props->stellar_spectrum_type == 0) { + nu_stop_final = rt_props->const_stellar_spectrum_max_frequency; + } else if (rt_props->stellar_spectrum_type == 1) { + nu_stop_final = 10. * blackbody_peak_frequency(T_bb, kB_cgs, h_planck_cgs); + } else if (rt_props->stellar_spectrum_type == 2) { + nu_stop_final = rt_props->const_stellar_spectrum_max_frequency; + /* TODO: set it as the const spectrum now. Later we need to add the real value we use. */ + } else { + nu_stop_final = -1.; + error("Unknown stellar spectrum type %d", rt_props->stellar_spectrum_type); + } + nu_stop[RT_NGROUPS - 1] = nu_stop_final; + + /* Compute Integrals */ + /* ----------------- */ + for (int g = 0; g < RT_NGROUPS; g++) { + /* This is independent of species. */ + integral_E[g] = rt_cross_sections_integrate_gsl( + spectrum_integrand, nu_start[g], nu_stop[g], RT_INTEGRAL_NPOINTS, + &integration_params); + integral_N[g] = rt_cross_sections_integrate_gsl( + spectrum_over_hnu_integrand, nu_start[g], nu_stop[g], + RT_INTEGRAL_NPOINTS, &integration_params); + + for (int s = 0; s < rt_ionizing_species_count; s++) { + integration_params.species = s; + integral_sigma_E[g][s] = rt_cross_sections_integrate_gsl( + spectrum_times_sigma_integrand, nu_start[g], nu_stop[g], + RT_INTEGRAL_NPOINTS, &integration_params); + integral_sigma_E_over_hnu[g][s] = rt_cross_sections_integrate_gsl( + spectrum_times_sigma_over_hnu_integrand, nu_start[g], nu_stop[g], + RT_INTEGRAL_NPOINTS, &integration_params); + } + } + + /* Now compute the actual average cross sections */ + /* --------------------------------------------- */ + for (int g = 0; g < RT_NGROUPS; g++) { + photon_number_integral[g] = integral_N[g]; + if (integral_N[g] > 0.) { + av_energy[g] = integral_E[g] / integral_N[g]; + } else { + av_energy[g] = 0.; + } + for (int s = 0; s < rt_ionizing_species_count; s++) { + if (integral_E[g] > 0.) { + cse[g][s] = integral_sigma_E[g][s] / integral_E[g]; + csn[g][s] = integral_sigma_E_over_hnu[g][s] / integral_N[g]; + } else { + /* No radiation = no interaction */ + cse[g][s] = 0.; + csn[g][s] = 0.; + } + } + } + + }//end for the simple spectrum calculation. + + /* for (int g = 0; g < RT_NGROUPS; g++) { */ + /* printf("\nGroup %d\n", g); */ + /* printf("nu_start: %12.6g\n", nu_start[g]); */ + /* printf("nu_end: %12.6g\n", nu_stop[g]); */ + /* printf("spectrum energy integral: %12.6g\n", integral_E[g]); */ + /* printf("spectrum number integral: %12.6g\n", integral_N[g]); */ + /* printf("average photon energy: %12.6g\n", av_energy[g]); */ + /* */ + /* printf("species: "); */ + /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12d ", s); + */ + /* printf("\n"); */ + /* */ + /* printf("integral sigma * E: "); */ + /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12.6g ", */ + /* integral_sigma_E[g][s]); */ + /* printf("\n"); */ + /* */ + /* printf("integral sigma * E / h nu: "); */ + /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12.6g ", */ + /* integral_sigma_E_over_hnu[g][s]); */ + /* printf("\n"); */ + /* */ + /* printf("energy weighted c.section: "); */ + /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12.6g ", */ + /* cse[g][s]); */ + /* printf("\n"); */ + /* */ + /* printf("number weighted c.section: "); */ + /* for (int s = 0; s < rt_ionizing_species_count; s++) printf("%12.6g ", */ + /* csn[g][s]); */ + /* printf("\n"); */ + /* } */ + + /* Store the results */ + /* ----------------- */ + + if (rt_props->energy_weighted_cross_sections != NULL) { + for (int g = 0; g < RT_NGROUPS; g++) { + if (rt_props->energy_weighted_cross_sections[g] != NULL) + free(rt_props->energy_weighted_cross_sections[g]); + } + free(rt_props->energy_weighted_cross_sections); + } + rt_props->energy_weighted_cross_sections = cse; + + if (rt_props->number_weighted_cross_sections != NULL) { + for (int g = 0; g < RT_NGROUPS; g++) { + if (rt_props->number_weighted_cross_sections[g] != NULL) + free(rt_props->number_weighted_cross_sections[g]); + } + free(rt_props->number_weighted_cross_sections); + } + rt_props->number_weighted_cross_sections = csn; +} + +/** + * @brief allocate and pre-compute photon number table from BPASS. + * + * @param file_name bpass hdf5 file name char + * @param dataset_name each photon group data set name within the hdf5 file + **/ +double **read_Bpass_from_hdf5(char *file_name, char *dataset_name){ + double** Table; + + // Open the HDF5 file + hid_t file_id = H5Fopen(file_name, H5F_ACC_RDONLY, H5P_DEFAULT); + if (file_id < 0) error("Error: Could not open file %s.\n", file_name); + + + // Open the dataset for HI group + hid_t dataset_id = H5Dopen2(file_id, dataset_name, H5P_DEFAULT); + if (dataset_id < 0) error("Error: Could not open dataset %s.\n", dataset_name); + + /* read element name array into temporary array */ + hid_t dataspace = H5Dget_space(dataset_id); + + // Get the dimensions of the dataset + hsize_t dims[2]; // Assuming a 2D dataset + H5Sget_simple_extent_dims(dataspace, dims, NULL); + + // Use a buffer to read HDF5 dataset + double *buffer = malloc(dims[0] * dims[1] * sizeof(double)); + herr_t status = H5Dread(dataset_id, H5T_NATIVE_DOUBLE, H5S_ALL, H5S_ALL, H5P_DEFAULT, buffer); + if (status < 0) error("Error: Could not read dataset.\n"); + + // Allocate memory for ionizing_HI_table + Table = malloc(dims[0] * sizeof(double *)); + for (hsize_t i = 0; i < dims[0]; i++) { + Table[i] = malloc(dims[1] * sizeof(double)); + } + + // Copy the element names into their final destination + for (hsize_t i = 0; i < dims[0]; i++){ + for (hsize_t j = 0; j < dims[1]; j++) { + Table[i][j] = buffer[i * dims[1] + j]; + if (fabs(Table[i][j])< 0) error("Negative photon number in the table! row:%lu, column:%lu\n", (long int)i, (long int)j); + } + } + + // Free allocated memory + free(buffer); + + // Close HDF5 dataset + status = H5Sclose(dataspace); + if (status < 0) error("error closing dataspace"); + status = H5Dclose(dataset_id); + if (status < 0) error("error closing dataset"); + status = H5Fclose(file_id); + if (status < 0) error("error closing file"); + + return Table; +} diff --git a/src/rt/KIARA/rt_interaction_cross_sections.h b/src/rt/KIARA/rt_interaction_cross_sections.h new file mode 100644 index 0000000000..4409d83848 --- /dev/null +++ b/src/rt/KIARA/rt_interaction_cross_sections.h @@ -0,0 +1,153 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_KIARA_INTERACION_CROSS_SECTIONS_H +#define SWIFT_RT_KIARA_INTERACION_CROSS_SECTIONS_H + +#include "inline.h" +#include "rt_species.h" + +#include +#include + +/** + * @file src/rt/KIARA/rt_interaction_cross_sections.h + * @brief header file concerning photoionization cross sections + **/ + +#define RT_INTEGRAL_NPOINTS 10000 + +/*! Struct containing the parametrized cross section parameters + * for each photoionizing species. + * Correct usage is to call rt_init_photoion_cs_params_cgs(), + * which returns a fully initialized struct. */ +struct rt_photoion_cs_parameters { + double E_ion[rt_ionizing_species_count]; /* erg */ + double E_zero[rt_ionizing_species_count]; /* erg */ + double sigma_zero[rt_ionizing_species_count]; /* cm^-2 */ + double P[rt_ionizing_species_count]; + double ya[rt_ionizing_species_count]; + double yw[rt_ionizing_species_count]; + double y0[rt_ionizing_species_count]; + double y1[rt_ionizing_species_count]; +}; + +/*! Parameters needed to compute the stellar spectra integrals - + * the data needs to be encapsulated in a single struct to be passed + * as an argument for GSL integrators. */ +struct rt_spectrum_integration_params { + /* Which species are we dealing with? */ + int species; + /* Which spectrum type are we dealing with? */ + int spectrum_type; + /* Max frequency for const spectrum */ + double const_stellar_spectrum_frequency_max; + /* Temperature of blackbody in correct units */ + double T; + /* Boltzmann constant in correct units */ + double kB; + /* Planck constant in correct units */ + double h_planck; + /* speed of light in correct units */ + double c; + /* Values for the cross section parametrization */ + struct rt_photoion_cs_parameters *cs_params; +}; + +/** + * Initialize the parameters for the cross section computation in cgs, + * and return a fully and correctly initialized struct. + * The data is taken from Verner et al. 1996 + * (ui.adsabs.harvard.edu/abs/1996ApJ...465..487V) via Rosdahl et al. 2013 + * (ui.adsabs.harvard.edu/abs/2013MNRAS.436.2188R) (Table E1) + */ +__attribute__((always_inline)) INLINE static struct rt_photoion_cs_parameters +rt_init_photoion_cs_params_cgs(void) { + + struct rt_photoion_cs_parameters photoion_cs_params_cgs; + double E_ion[rt_ionizing_species_count]; + rt_species_get_ionizing_energy(E_ion); + + photoion_cs_params_cgs.E_ion[rt_ionizing_species_HI] = + E_ion[rt_ionizing_species_HI]; + photoion_cs_params_cgs.E_zero[rt_ionizing_species_HI] = 6.886e-13; + photoion_cs_params_cgs.sigma_zero[rt_ionizing_species_HI] = 5.475e-14; + photoion_cs_params_cgs.P[rt_ionizing_species_HI] = 2.963; + photoion_cs_params_cgs.ya[rt_ionizing_species_HI] = 32.88; + photoion_cs_params_cgs.yw[rt_ionizing_species_HI] = 0.; + photoion_cs_params_cgs.y0[rt_ionizing_species_HI] = 0.; + photoion_cs_params_cgs.y1[rt_ionizing_species_HI] = 0.; + + photoion_cs_params_cgs.E_ion[rt_ionizing_species_HeI] = + E_ion[rt_ionizing_species_HeI]; + /* Note: The value of 0.1361 eV for E_0 of HeI given in table 5.1 + * in Rosdahl et al. is an error. The correct value is 13.61 eV */ + photoion_cs_params_cgs.E_zero[rt_ionizing_species_HeI] = 2.181e-11; + photoion_cs_params_cgs.sigma_zero[rt_ionizing_species_HeI] = 9.492e-16; + photoion_cs_params_cgs.P[rt_ionizing_species_HeI] = 3.188; + photoion_cs_params_cgs.ya[rt_ionizing_species_HeI] = 1.469; + photoion_cs_params_cgs.yw[rt_ionizing_species_HeI] = 2.039; + photoion_cs_params_cgs.y0[rt_ionizing_species_HeI] = 0.4434; + photoion_cs_params_cgs.y1[rt_ionizing_species_HeI] = 2.136; + + photoion_cs_params_cgs.E_ion[rt_ionizing_species_HeII] = + E_ion[rt_ionizing_species_HeII]; + photoion_cs_params_cgs.E_zero[rt_ionizing_species_HeII] = 2.756e-12; + photoion_cs_params_cgs.sigma_zero[rt_ionizing_species_HeII] = 1.369e-14; + photoion_cs_params_cgs.P[rt_ionizing_species_HeII] = 2.963; + photoion_cs_params_cgs.ya[rt_ionizing_species_HeII] = 32.88; + photoion_cs_params_cgs.yw[rt_ionizing_species_HeII] = 0.; + photoion_cs_params_cgs.y0[rt_ionizing_species_HeII] = 0.; + photoion_cs_params_cgs.y1[rt_ionizing_species_HeII] = 0.; + + return photoion_cs_params_cgs; +} + +/** + * Compute the parametrized cross section for a given energy and species. + * The parametrization is taken from Verner et al. 1996 + * (ui.adsabs.harvard.edu/abs/1996ApJ...465..487V) via Rosdahl et al. 2013 + * (ui.adsabs.harvard.edu/abs/2013MNRAS.436.2188R) + * + * @param E energy for which to compute the cross section, in erg + * @param species index of species, 0 < species < rt_ionizing_species_count + * @param params cross section parameters struct + */ +__attribute__((always_inline)) INLINE static double +photoionization_cross_section(const double E, const int species, + const struct rt_photoion_cs_parameters *params) { + + const double E0 = params->E_zero[species]; + const double E_ion = params->E_ion[species]; + const double y0 = params->y0[species]; + const double y1 = params->y1[species]; + const double yw = params->yw[species]; + const double ya = params->ya[species]; + const double P = params->P[species]; + const double sigma_0 = params->sigma_zero[species]; + + if (E < E_ion) return 0.; + + const double x = E / E0 - y0; + const double y = sqrt(x * x + y1 * y1); + const double temp1 = pow(y, 0.5 * P - 5.5); + const double temp2 = pow(1. + sqrt(y / ya), -P); + return sigma_0 * ((x - 1.) * (x - 1.) + yw * yw) * temp1 * temp2; +} + +#endif /* SWIFT_RT_KIARA_INTERACION_CROSS_SECTIONS_H */ diff --git a/src/rt/KIARA/rt_interaction_rates.h b/src/rt/KIARA/rt_interaction_rates.h new file mode 100644 index 0000000000..5d4fc12379 --- /dev/null +++ b/src/rt/KIARA/rt_interaction_rates.h @@ -0,0 +1,180 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_KIARA_INTERACTION_RATES_H +#define SWIFT_RT_KIARA_INTERACTION_RATES_H + +#include "rt_parameters.h" +#include "rt_properties.h" +#include "rt_species.h" +#include "rt_thermochemistry_utils.h" + +/** + * @file src/rt/KIARA/rt_interaction_rates.h + * @brief header file concerning photoionization and photoheating rates + **/ + +/** + * @brief compute the heating, ionization, and dissassociation rates + * for the particle radiation field as needed by grackle. + * + * @param rates (return) Interaction rates for grackle. [0]: heating rate. + * [1]: HI ionization. [2]: HeI ionization. [3]: HeII ionization. + * [4]: H2 dissociation. + * @param energy_density energy densities of each photon group [internal units] + * @param species_densities physical densities of all species [internal units] + * @param average_photon_energy mean photon energy in group, in erg + * @param cse energy weighted photon interaction cross sections, in cm^2 + * @param csn number weighted photon interaction cross sections, in cm^2 + * @param phys_const physical constants struct + * @param us internal units struct + **/ +__attribute__((always_inline)) INLINE static void +rt_get_interaction_rates_for_grackle( + gr_float rates[5], float energy_density[RT_NGROUPS], + gr_float species_densities[6], + const double average_photon_energy[RT_NGROUPS], double **cse, double **csn, + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us) { + + rates[0] = 0.; /* Needs to be in [erg / s / cm^3 / nHI] for grackle. */ + rates[1] = 0.; /* [1 / time_units] */ + rates[2] = 0.; /* [1 / time_units] */ + rates[3] = 0.; /* [1 / time_units] */ + rates[4] = 0.; /* [1 / time_units] */ + + double E_ion_cgs[rt_ionizing_species_count]; + rt_species_get_ionizing_energy(E_ion_cgs); + + /* Get some conversions and constants first. */ + const double c_cgs = rt_params.reduced_speed_of_light * + units_cgs_conversion_factor(us, UNIT_CONV_VELOCITY); + const double to_energy_density_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_DENSITY); + const double inv_time_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_INV_TIME); + + /* get species number densities in cgs */ + double ns_cgs[rt_ionizing_species_count]; /* in cm^-3 */ + rt_tchem_get_ionizing_species_number_densities(ns_cgs, species_densities, + phys_const, us); + + /* store photoionization rate for each species here */ + double ionization_rates[rt_ionizing_species_count]; + for (int s = 0; s < rt_ionizing_species_count; s++) { + ionization_rates[s] = 0.; + } + + for (int g = 0; g < RT_NGROUPS; g++) { + + /* Sum results for this group over all species */ + double heating_rate_group_cgs = 0.; + const double Eg = energy_density[g] * to_energy_density_cgs; + const double Emean_g = average_photon_energy[g]; + const double Ng = (Emean_g > 0.) ? Eg / Emean_g : 0.; + + for (int s = 0; s < rt_ionizing_species_count; s++) { + /* All quantities here are in cgs. */ + heating_rate_group_cgs += + (cse[g][s] * Emean_g - E_ion_cgs[s] * csn[g][s]) * ns_cgs[s]; + ionization_rates[s] += csn[g][s] * Ng * c_cgs; + } + rates[0] += heating_rate_group_cgs * Ng * c_cgs; + } + + /* Convert into correct units. */ + const double nHI = ns_cgs[rt_ionizing_species_HI]; + if (nHI > 0.) + rates[0] /= nHI; + else + rates[0] = 0.; + + for (int s = 0; s < rt_ionizing_species_count; s++) { + ionization_rates[s] /= inv_time_cgs; /* internal units T^-1 */ +#ifdef SWIFT_RT_DEBUG_CHECKS + if (ionization_rates[s] < 0.) + error("unphysical ion rate spec %d - %.4g", s, ionization_rates[s]); +#endif + } + + /* We're done. Write the results in correct place */ + rates[1] = ionization_rates[0]; + rates[2] = ionization_rates[1]; + rates[3] = ionization_rates[2]; + /* rates[4] = skipped for now */ + +#ifdef SWIFT_RT_DEBUG_CHECKS + for (int i = 0; i < 5; i++) { + if (rates[i] < 0.) error("unphysical rate %d %.4g", i, rates[i]); + else if (isnan(rates[i])) { + error("NaN detected in rate %d", i); + } + } +#endif +} + +/** + * @brief compute the rates at which the photons get absorbed/destroyed + * during interactions with gas. + * + * @param absorption_rates (return) the energy absorption rates in + * internal units for each photon group. + * @param species_densities the physical densities of all traced species + *[internal units] + * @param average_photon_energy mean photon energy in group, in erg + * @param csn number weighted photon interaction cross sections, in cm^2 + * @param phys_const physical constants struct + * @param us internal units struct + **/ +__attribute__((always_inline)) INLINE static void rt_get_absorption_rates( + double absorption_rates[RT_NGROUPS], gr_float species_densities[6], + const double average_photon_energy[RT_NGROUPS], double **csn, + const struct phys_const *restrict phys_const, + const struct unit_system *restrict us) { + + for (int g = 0; g < RT_NGROUPS; g++) absorption_rates[g] = 0.; + + double E_ion_cgs[rt_ionizing_species_count]; + rt_species_get_ionizing_energy(E_ion_cgs); + + /* Get some conversions and constants first. */ + const double c_cgs = rt_params.reduced_speed_of_light * + units_cgs_conversion_factor(us, UNIT_CONV_VELOCITY); + const double inv_time_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_INV_TIME); + + double ns_cgs[rt_ionizing_species_count]; /* in cm^-3 */ + rt_tchem_get_ionizing_species_number_densities(ns_cgs, species_densities, + phys_const, us); + + for (int g = 0; g < RT_NGROUPS; g++) { + for (int s = 0; s < rt_ionizing_species_count; s++) { + absorption_rates[g] += c_cgs * csn[g][s] * ns_cgs[s]; + } + } + + for (int g = 0; g < RT_NGROUPS; g++) { + absorption_rates[g] /= inv_time_cgs; +#ifdef SWIFT_RT_DEBUG_CHECKS + if (absorption_rates[g] < 0.) + error("unphysical rate %d - %.4g", g, absorption_rates[g]); +#endif + } +} + +#endif /* SWIFT_RT_KIARA_INTERACION_RATES_H */ diff --git a/src/rt/KIARA/rt_io.h b/src/rt/KIARA/rt_io.h new file mode 100644 index 0000000000..573e6edcb0 --- /dev/null +++ b/src/rt/KIARA/rt_io.h @@ -0,0 +1,470 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2020 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_IO_KIARA_H +#define SWIFT_RT_IO_KIARA_H + +#define RT_LABELS_SIZE 10 +#include "cooling_io.h" + +/** + * @file src/rt/KIARA/rt_io.h + * @brief Main header file for KIARA M1 Closure radiative transfer + * scheme IO routines. + */ + +/** + * @brief Specifies which particle fields to read from a dataset + * + * @param parts The particle array. + * @param list The list of i/o properties to read. + * + * @return Returns the number of fields to read. + */ +INLINE static int rt_read_particles(const struct part* parts, + struct io_props* list) { + + /* List what we want to read */ + + char fieldname[30]; + int count = 0; + for (int phg = 0; phg < RT_NGROUPS; phg++) { + sprintf(fieldname, "PhotonEnergiesGroup%d", phg + 1); + list[count++] = + io_make_input_field(fieldname, FLOAT, 1, OPTIONAL, UNIT_CONV_ENERGY, + parts, rt_data.radiation[phg].energy_density); + sprintf(fieldname, "PhotonFluxesGroup%d", phg + 1); + list[count++] = io_make_input_field(fieldname, FLOAT, 3, OPTIONAL, + UNIT_CONV_ENERGY_VELOCITY, parts, + rt_data.radiation[phg].flux); + } + + list[count++] = io_make_input_field("MassFractionHI", FLOAT, 1, OPTIONAL, + UNIT_CONV_NO_UNITS, parts, + rt_data.tchem.mass_fraction_HI); + list[count++] = io_make_input_field("MassFractionHII", FLOAT, 1, OPTIONAL, + UNIT_CONV_NO_UNITS, parts, + rt_data.tchem.mass_fraction_HII); + list[count++] = io_make_input_field("MassFractionHeI", FLOAT, 1, OPTIONAL, + UNIT_CONV_NO_UNITS, parts, + rt_data.tchem.mass_fraction_HeI); + list[count++] = io_make_input_field("MassFractionHeII", FLOAT, 1, OPTIONAL, + UNIT_CONV_NO_UNITS, parts, + rt_data.tchem.mass_fraction_HeII); + list[count++] = io_make_input_field("MassFractionHeIII", FLOAT, 1, OPTIONAL, + UNIT_CONV_NO_UNITS, parts, + rt_data.tchem.mass_fraction_HeIII); + + return count; +} + +/** + * @brief Specifies which particle fields to read from a dataset + * + * @param sparts The star particle array. + * @param list The list of i/o properties to read. + * + * @return Returns the number of fields to read. + */ +INLINE static int rt_read_stars(const struct spart* sparts, + struct io_props* list) { + return 0; +} + +/** + * @brief Extract radiation energies of radiation struct for all photon groups + * Note: "allocation" of `float* ret` happens in io_copy_temp_buffer() + * + * @param engine the engine + * @param part the particle to extract data from + * @param xpart the according xpart to extract data from + * @param ret (return) the extracted data + */ +INLINE static void rt_convert_radiation_energies(const struct engine* engine, + const struct part* part, + const struct xpart* xpart, + float* ret) { + + for (int g = 0; g < RT_NGROUPS; g++) { + ret[g] = part->rt_data.radiation[g].energy_density * part->geometry.volume; + } +} + +/** + * @brief Extract radiation fluxes of radiation struct for all photon groups + * Note: "allocation" of `float* ret` happens in io_copy_temp_buffer() + * + * @param engine the engine + * @param part the particle to extract data from + * @param xpart the according xpart to extract data from + * @param ret (return) the extracted data + */ +INLINE static void rt_convert_radiation_fluxes(const struct engine* engine, + const struct part* part, + const struct xpart* xpart, + float* ret) { + + int i = 0; + for (int g = 0; g < RT_NGROUPS; g++) { + ret[i++] = part->rt_data.radiation[g].flux[0]; + ret[i++] = part->rt_data.radiation[g].flux[1]; + ret[i++] = part->rt_data.radiation[g].flux[2]; + } +} + +/** + * @brief Extract mass fractions of constituent species from tchem struct. + * Note: "allocation" of `float* ret` happens in io_copy_temp_buffer() + * + * @param engine the engine + * @param part the particle to extract data from + * @param xpart the according xpart to extract data from + * @param ret (return) the extracted data + */ +INLINE static void rt_convert_mass_fractions(const struct engine* engine, + const struct part* part, + const struct xpart* xpart, + float* ret) { + + ret[0] = part->rt_data.tchem.mass_fraction_HI; + ret[1] = part->rt_data.tchem.mass_fraction_HII; + ret[2] = part->rt_data.tchem.mass_fraction_HeI; + ret[3] = part->rt_data.tchem.mass_fraction_HeII; + ret[4] = part->rt_data.tchem.mass_fraction_HeIII; +} + +/** + * @brief Creates additional output fields for the radiative + * transfer data of hydro particles. + * + * @param parts The particle array. + * @param xparts The particle extra information. + * @param list The list of i/o properties to write. + * + * @return Returns the number of fields to write. + */ +INLINE static int rt_write_particles(const struct part* parts, + const struct xpart* xparts, struct io_props* list) { + + int num_elements = 3; + + list[0] = io_make_physical_output_field_convert_part( + "PhotonEnergies", FLOAT, RT_NGROUPS, UNIT_CONV_ENERGY, 0, parts, + /*xparts=*/NULL, + /*convertible to comoving=*/1, rt_convert_radiation_energies, + "Photon Energies (all groups)"); + + list[1] = io_make_physical_output_field_convert_part( + "PhotonFluxes", FLOAT, 3 * RT_NGROUPS, UNIT_CONV_RADIATION_FLUX, 0, parts, + /*xparts=*/NULL, + /*convertible to comoving=*/1, rt_convert_radiation_fluxes, + "Photon Fluxes (all groups; x, y, and z coordinates)"); + list[2] = io_make_output_field_convert_part( + "IonMassFractions", FLOAT, 5, UNIT_CONV_NO_UNITS, 0, parts, + /*xparts=*/NULL, rt_convert_mass_fractions, + "Mass fractions of all constituent species"); + +#ifdef SWIFT_RT_DEBUG_CHECKS + num_elements += 8; + list[3] = + io_make_output_field("RTDebugInjectionDone", INT, 1, UNIT_CONV_NO_UNITS, + 0, parts, rt_data.debug_injection_done, + "How many times rt_injection_update_photon_density " + "has been called"); + list[4] = io_make_output_field( + "RTDebugCallsIactGradientInteractions", INT, 1, UNIT_CONV_NO_UNITS, 0, + parts, rt_data.debug_calls_iact_gradient_interaction, + "number of calls to this particle during the gradient interaction loop " + "from the actual interaction function"); + list[5] = io_make_output_field("RTDebugCallsIactTransportInteractions", INT, + 1, UNIT_CONV_NO_UNITS, 0, parts, + rt_data.debug_calls_iact_transport_interaction, + "number of calls to this particle during the " + "transport interaction loop from the actual " + "interaction function"); + list[6] = + io_make_output_field("RTDebugGradientsDone", INT, 1, UNIT_CONV_NO_UNITS, + 0, parts, rt_data.debug_gradients_done, + "How many times finalise_gradients was called"); + list[7] = + io_make_output_field("RTDebugTransportDone", INT, 1, UNIT_CONV_NO_UNITS, + 0, parts, rt_data.debug_transport_done, + "How many times finalise_transport was called"); + list[8] = io_make_output_field( + "RTDebugThermochemistryDone", INT, 1, UNIT_CONV_NO_UNITS, 0, parts, + rt_data.debug_thermochem_done, "How many times rt_tchem was called"); + list[9] = io_make_output_field( + "RTDebugRadAbsorbedTot", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0, parts, + rt_data.debug_radiation_absorbed_tot, + "Radiation absorbed by this part during its lifetime"); + list[10] = io_make_output_field( + "RTDebugSubcycles", INT, 1, UNIT_CONV_NO_UNITS, 0, parts, + rt_data.debug_nsubcycles, "How many times this part was subcycled"); +#endif + + return num_elements; +} + +/** + * @brief Creates additional output fields for the radiative + * transfer data of star particles. + * + * @param sparts The star particle array. + * @param list The list of i/o properties to write. + * + * @return Returns the number of fields to write. + */ +INLINE static int rt_write_stars(const struct spart* sparts, + struct io_props* list) { + int num_elements = 0; + +#ifdef SWIFT_RT_DEBUG_CHECKS + num_elements += 4; + list[0] = io_make_output_field("RTDebugHydroIact", INT, 1, UNIT_CONV_NO_UNITS, + 0, sparts, rt_data.debug_iact_hydro_inject, + "number of interactions between this star " + "particle and any particle during injection"); + list[1] = io_make_output_field( + "RTDebugEmissionRateSet", INT, 1, UNIT_CONV_NO_UNITS, 0, sparts, + rt_data.debug_emission_rate_set, "Stellar photon emission rates set?"); + list[2] = io_make_output_field( + "RTDebugRadEmittedTot", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0, sparts, + rt_data.debug_radiation_emitted_tot, + "Total radiation emitted during the lifetime of this star"); + list[3] = io_make_output_field("RTDebugInjectedPhotonEnergy", FLOAT, + RT_NGROUPS, UNIT_CONV_ENERGY, 0, sparts, + rt_data.debug_injected_energy_tot, + "Total radiation actually injected into gas"); +#endif + + return num_elements; +} + +/** + * @brief Write the RT model properties to the snapshot. + * + * @param h_grp The HDF5 group in which to write + * @param h_grp_columns The HDF5 group containing named columns + * @param e The engine + * @param internal_units The internal unit system + * @param snapshot_units Units used for the snapshot + * @param rtp The #rt_props + */ +INLINE static void rt_write_flavour(hid_t h_grp, hid_t h_grp_columns, + const struct engine* e, + const struct unit_system* internal_units, + const struct unit_system* snapshot_units, + const struct rt_props* rtp) { + +#if defined(HAVE_HDF5) + + /* Write scheme name */ + /* ----------------- */ + io_write_attribute_s(h_grp, "RT Scheme", RT_IMPLEMENTATION); + io_write_attribute_s(h_grp, "RT Riemann Solver", RT_RIEMANN_SOLVER_NAME); + + /* Write photon group counts */ + /* ------------------------- */ + io_write_attribute_i(h_grp, "PhotonGroupNumber", RT_NGROUPS); + + /* Write photon group bin edges */ + /* ---------------------------- */ + + /* Note: photon frequency bin edges are kept in cgs. Convert them here to + * internal units so we're still compatible with swiftsimio. */ + const float Hz_internal = + units_cgs_conversion_factor(internal_units, UNIT_CONV_INV_TIME); + const float Hz_internal_inv = 1.f / Hz_internal; + float photon_groups_internal[RT_NGROUPS]; + for (int g = 0; g < RT_NGROUPS; g++) + photon_groups_internal[g] = rtp->photon_groups[g] * Hz_internal_inv; + + hid_t type_float = H5Tcopy(io_hdf5_type(FLOAT)); + + hsize_t dims[1] = {RT_NGROUPS}; + hid_t space = H5Screate_simple(1, dims, NULL); + hid_t dset = H5Dcreate(h_grp, "PhotonGroupEdges", type_float, space, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dset, type_float, H5S_ALL, H5S_ALL, H5P_DEFAULT, + photon_groups_internal); + + /* Write unit conversion factors for this data set */ + char buffer[FIELD_BUFFER_SIZE] = {0}; + units_cgs_conversion_string(buffer, snapshot_units, UNIT_CONV_INV_TIME, + /*scale_factor_exponent=*/0); + float baseUnitsExp[5]; + units_get_base_unit_exponents_array(baseUnitsExp, UNIT_CONV_INV_TIME); + io_write_attribute_f(dset, "U_M exponent", baseUnitsExp[UNIT_MASS]); + io_write_attribute_f(dset, "U_L exponent", baseUnitsExp[UNIT_LENGTH]); + io_write_attribute_f(dset, "U_t exponent", baseUnitsExp[UNIT_TIME]); + io_write_attribute_f(dset, "U_I exponent", baseUnitsExp[UNIT_CURRENT]); + io_write_attribute_f(dset, "U_T exponent", baseUnitsExp[UNIT_TEMPERATURE]); + io_write_attribute_f(dset, "h-scale exponent", 0.f); + io_write_attribute_f(dset, "a-scale exponent", 0.f); + io_write_attribute_s(dset, "Expression for physical CGS units", buffer); + io_write_attribute_b(dset, "Value stored as physical", 1); + io_write_attribute_b(dset, "Property can be converted to comoving", 0); + + /* Write the actual number this conversion factor corresponds to */ + const double factor = + units_cgs_conversion_factor(snapshot_units, UNIT_CONV_INV_TIME); + io_write_attribute_d( + dset, "Conversion factor to CGS (not including cosmological corrections)", + factor); + io_write_attribute_d( + dset, + "Conversion factor to physical CGS (including cosmological corrections)", + factor * pow(e->cosmology->a, 0.f)); + + H5Dclose(dset); + /* H5Tclose(type_float); [> close this later <] */ + + /* If without RT, we have nothing more to do. */ + const int with_rt = e->policy & engine_policy_rt; + if (!with_rt) return; + + /* Write photon group names */ + /* -------------------------*/ + + /* Generate Energy Group names */ + char names_energy[RT_NGROUPS * RT_LABELS_SIZE]; + for (int g = 0; g < RT_NGROUPS; g++) { + char newEname[RT_LABELS_SIZE]; + sprintf(newEname, "Group%d", g + 1); + strcpy(names_energy + g * RT_LABELS_SIZE, newEname); + } + + /* Now write them down */ + hid_t type_string_label = H5Tcopy(H5T_C_S1); + H5Tset_size(type_string_label, RT_LABELS_SIZE); + + hsize_t dimsE[1] = {RT_NGROUPS}; + hid_t spaceE = H5Screate_simple(1, dimsE, NULL); + hid_t dsetE = H5Dcreate(h_grp_columns, "PhotonEnergies", type_string_label, + spaceE, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dsetE, type_string_label, H5S_ALL, H5S_ALL, H5P_DEFAULT, + names_energy); + H5Dclose(dsetE); + + /* Generate Fluxes Group Names */ + char names_fluxes[3 * RT_NGROUPS * RT_LABELS_SIZE]; + int i = 0; + for (int g = 0; g < RT_NGROUPS; g++) { + char newFnameX[RT_LABELS_SIZE]; + sprintf(newFnameX, "Group%dX", g + 1); + strcpy(names_fluxes + i * RT_LABELS_SIZE, newFnameX); + i++; + char newFnameY[RT_LABELS_SIZE]; + sprintf(newFnameY, "Group%dY", g + 1); + strcpy(names_fluxes + i * RT_LABELS_SIZE, newFnameY); + i++; + char newFnameZ[RT_LABELS_SIZE]; + sprintf(newFnameZ, "Group%dZ", g + 1); + strcpy(names_fluxes + i * RT_LABELS_SIZE, newFnameZ); + i++; + } + + /* Now write them down */ + hsize_t dimsF[1] = {3 * RT_NGROUPS}; + hid_t spaceF = H5Screate_simple(1, dimsF, NULL); + hid_t dsetF = H5Dcreate(h_grp_columns, "PhotonFluxes", type_string_label, + spaceF, H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dsetF, type_string_label, H5S_ALL, H5S_ALL, H5P_DEFAULT, + names_fluxes); + H5Dclose(dsetF); + + /* H5Tclose(type_string_label); [> close this later <] */ + + /* Write reduced speed of light */ + /* ---------------------------- */ + /* hid_t type2 = H5Tcopy(io_hdf5_type(FLOAT)); */ + + hsize_t dims_cred[1] = {1}; + hid_t space_cred = H5Screate_simple(1, dims_cred, NULL); + hid_t dset_cred = + H5Dcreate(h_grp, "ReducedLightspeed", type_float, space_cred, H5P_DEFAULT, + H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dset_cred, type_float, H5S_ALL, H5S_ALL, H5P_DEFAULT, + &rt_params.reduced_speed_of_light); + + /* Write unit conversion factors for this data set */ + char buffer_cred[FIELD_BUFFER_SIZE] = {0}; + units_cgs_conversion_string(buffer_cred, snapshot_units, UNIT_CONV_VELOCITY, + /*scale_factor_exponent=*/0); + float baseUnitsExp_cred[5]; + units_get_base_unit_exponents_array(baseUnitsExp_cred, UNIT_CONV_VELOCITY); + io_write_attribute_f(dset_cred, "U_M exponent", baseUnitsExp_cred[UNIT_MASS]); + io_write_attribute_f(dset_cred, "U_L exponent", + baseUnitsExp_cred[UNIT_LENGTH]); + io_write_attribute_f(dset_cred, "U_t exponent", baseUnitsExp_cred[UNIT_TIME]); + io_write_attribute_f(dset_cred, "U_I exponent", + baseUnitsExp_cred[UNIT_CURRENT]); + io_write_attribute_f(dset_cred, "U_T exponent", + baseUnitsExp_cred[UNIT_TEMPERATURE]); + io_write_attribute_f(dset_cred, "h-scale exponent", 0.f); + io_write_attribute_f(dset_cred, "a-scale exponent", 0.f); + io_write_attribute_s(dset_cred, "Expression for physical CGS units", + buffer_cred); + io_write_attribute_b(dset_cred, "Value stored as physical", 1); + io_write_attribute_b(dset_cred, "Property can be converted to comoving", 0); + + /* Write the actual number this conversion factor corresponds to */ + /* TODO Mladen: check cosmology. reduced_speed_of_light is physical only for + * now. */ + const double factor_cred = + units_cgs_conversion_factor(snapshot_units, UNIT_CONV_VELOCITY); + io_write_attribute_d( + dset_cred, + "Conversion factor to CGS (not including cosmological corrections)", + factor_cred); + io_write_attribute_d( + dset_cred, + "Conversion factor to physical CGS (including cosmological corrections)", + factor_cred * pow(e->cosmology->a, 0.f)); + + H5Dclose(dset_cred); + + /* Write constituent species mass fractions */ + /* ---------------------------------------- */ + + char names_mf[5 * RT_LABELS_SIZE]; + strcpy(names_mf + 0 * RT_LABELS_SIZE, "HI\0"); + strcpy(names_mf + 1 * RT_LABELS_SIZE, "HII\0"); + strcpy(names_mf + 2 * RT_LABELS_SIZE, "HeI\0"); + strcpy(names_mf + 3 * RT_LABELS_SIZE, "HeII\0"); + strcpy(names_mf + 4 * RT_LABELS_SIZE, "HeIII\0"); + + hsize_t dims_mf[1] = {5}; + hid_t space_mf = H5Screate_simple(1, dims_mf, NULL); + hid_t dset_mf = + H5Dcreate(h_grp_columns, "IonMassFractions", type_string_label, space_mf, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dset_mf, type_string_label, H5S_ALL, H5S_ALL, H5P_DEFAULT, names_mf); + H5Dclose(dset_mf); + + /* Clean up after yourself */ + /* ----------------------- */ + + /* Close up the types */ + H5Tclose(type_float); + H5Tclose(type_string_label); + +#endif /* HAVE_HDF5 */ +} + +#endif /* SWIFT_RT_IO_KIARA_H */ diff --git a/src/rt/KIARA/rt_ionization_equilibrium.h b/src/rt/KIARA/rt_ionization_equilibrium.h new file mode 100644 index 0000000000..64a504d5c1 --- /dev/null +++ b/src/rt/KIARA/rt_ionization_equilibrium.h @@ -0,0 +1,294 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_IONIZATION_EQUILIBRIUM_H +#define SWIFT_RT_IONIZATION_EQUILIBRIUM_H + +#include "rt_thermochemistry_utils.h" + +/* some local definitions */ +#define RT_ION_EQUIL_ITER_MAX 50 +#define RT_ION_EQUIL_EPSILON 1e-4 + +/** + * @file src/rt/KIARA/rt_ionization_equilibrium.h + * @brief Function to get the number densities for primordial species as a + * function of the temperature, assuming ionization equilibrium (see Katz et + * al. 1996; ui.adsabs.harvard.edu/abs/1996ApJS..105...19K) + */ + +/** + * @brief compute the ionization equilibrium mass fractions + * for a given temperature + * @param T temperature in K + * @param X total mass fraction of all hydrogen species + * @param Y total mass fraction of all helium species + * @param XHI (return) mass fraction of HI + * @param XHII (return) mass fraction of HII + * @param XHeI (return) mass fraction of HeI + * @param XHeII (return) mass fraction of HeII + * @param XHeIII (return) mass fraction of HeII + */ +__attribute__((always_inline)) INLINE static void +rt_ion_equil_mass_fractions_from_T(double T, float X, float Y, float* XHI, + float* XHII, float* XHeI, float* XHeII, + float* XHeIII) { + + if (fabsf(X + Y - 1.f) > 1e-4) + error("mass fractions don't add up to one: X=%.3e, Y=%.3e", X, Y); + + /* Total number densities for all H and He species, respectively. + * We don't really care about the actual number densities of the + * species, but only want the mass fractions. So all quantities + * will be per unit volume, specifically per rho_gas / m_proton. */ + double nH, nHe; + + /* TODO: this assumes no metals. */ + if (X <= RT_KIARA_TINY_MASS_FRACTION) { + nH = RT_KIARA_TINY_MASS_FRACTION; + nHe = 1.; + } else { + nH = X; + nHe = 0.25 * Y; + } + + /* Number densities of the actual species */ + float nHI, nHII, nHeI, nHeII, nHeIII; + + if (T < 5e3) { + /* Below 5000K, we just go with fully neutral gas. + * Not only is this the case, but some divisions + * would otherwise not be safe and lead to problems + * if we don't exception handle it here. */ + nHI = nH; + nHII = 0.f; + nHeI = nHe; + nHeII = 0.f; + nHeIII = 0.f; + + } else { + + const double T_inv = 1. / T; + const double sqrtT = sqrt(T); + const double temp_pow07 = 1. / (1. + pow(T * 1e-6, 0.7)); + const double temp_sqrtT5 = 1. / (1. + sqrt(T * 1e-5)); + const double temp_pow02 = pow(T * 1e-3, -0.2); + /* Recombination rate for H+ in units of cm^3 s^-1 */ + const double A_Hp = 8.40e-11 / sqrtT * temp_pow02 * temp_pow07; + /* Dielectronic recombination rate for He+ in units of cm^3 s^-1 */ + const double A_d = 1.9e-3 / pow(T, 1.5) * exp(-470000.0 * T_inv) * + (1.0 + 0.3 * exp(-94000.0 * T_inv)); + /* Recombination rate for He+ in units of cm^3 s^-1 */ + const double A_Hep = 1.5e-10 / pow(T, 0.6353); + /* Recombination rate for He++ in units of cm^3 s^-1 */ + const double A_Hepp = 3.36e-10 / sqrtT * temp_pow02 * temp_pow07; + /* collisional ionization rate for H0 in units of cm^3 s^-1 */ + const double G_H0 = 5.85e-11 * sqrtT * exp(-157809.1 * T_inv) * temp_sqrtT5; + /* collisional ionization rate for He0 in units of cm^3 s^-1 */ + const double G_He0 = + 2.38e-11 * sqrtT * exp(-285335.4 * T_inv) * temp_sqrtT5; + /* collisional ionization rate for He+ in units of cm^3 s^-1 */ + const double G_Hep = + 5.68e-12 * sqrtT * exp(-631515.0 * T_inv) * temp_sqrtT5; + + /* Katz et al. 1996 eq. 33 - 38 */ + /* Note: We assume all photoionization rates to be zero. */ + nHI = nH * A_Hp / (A_Hp + G_H0); + nHII = nH - nHI; + const double div1 = (A_Hep + A_d) / G_He0; + const double div2 = G_Hep / A_Hepp; + nHeII = nHe / (1. + div1 + div2); + nHeI = nHeII * div1; + nHeIII = nHeII * div2; + /* ne = nHII + nHeII + 2.f * nHeIII; */ + } + + /* With the number densities per unit volume given, + * let's compute the mass fractions now. */ + const double mHI = nHI; + const double mHII = nHII; + const double mHeI = 4.0f * nHeI; + const double mHeII = 4.0f * nHeII; + const double mHeIII = 4.0f * nHeIII; + /* we ignore the electron mass fraction. */ + + const double mtot_inv = 1. / (mHI + mHII + mHeI + mHeII + mHeIII); + + *XHI = (float)(mHI * mtot_inv); + *XHI = max(*XHI, RT_KIARA_TINY_MASS_FRACTION); + *XHII = (float)(mHII * mtot_inv); + *XHII = max(*XHII, RT_KIARA_TINY_MASS_FRACTION); + *XHeI = (float)(mHeI * mtot_inv); + *XHeI = max(*XHeI, RT_KIARA_TINY_MASS_FRACTION); + *XHeII = (float)(mHeII * mtot_inv); + *XHeII = max(*XHeII, RT_KIARA_TINY_MASS_FRACTION); + *XHeIII = (float)(mHeIII * mtot_inv); + *XHeIII = max(*XHeIII, RT_KIARA_TINY_MASS_FRACTION); +} + +/** + * @brief compute the ionization equilibrium mass fractions + * for a given particle. + * @param XHI (return) mass fraction of HI + * @param XHII (return) mass fraction of HII + * @param XHeI (return) mass fraction of HeI + * @param XHeII (return) mass fraction of HeII + * @param XHeIII (return) mass fraction of HeII + * @param p part to work with + * @param rt_props rt_properties struct + * @param hydro_props hydro properties struct + * @param phys_const physical constants struct + * @param us unit system struct + * @param cosmo cosmology struct + */ +__attribute__((always_inline)) INLINE static void +rt_ion_equil_get_mass_fractions(float* XHI, float* XHII, float* XHeI, + float* XHeII, float* XHeIII, + struct part* restrict p, + const struct rt_props* rt_props, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct unit_system* restrict us, + const struct cosmology* restrict cosmo) { + + /* get conversions and constants */ + const double internal_energy_to_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_PER_UNIT_MASS); + const float dimension_kB[5] = {1, 2, -2, 0, -1}; /* [g cm^2 s^-2 K^-1] */ + const double kB_to_cgs = + units_general_cgs_conversion_factor(us, dimension_kB); + const double kB_cgs = phys_const->const_boltzmann_k * kB_to_cgs; + const double mp_to_cgs = units_cgs_conversion_factor(us, UNIT_CONV_MASS); + const double mp_cgs = phys_const->const_proton_mass * mp_to_cgs; + + /* Get the specific internal energy of the gas */ + const float u_minimal = hydro_props->minimal_internal_energy; + /* Using 'drifted' version here because I'm lazy and don't want to pass + * the xpart down to use in this function. */ + const float u_part = hydro_get_drifted_physical_internal_energy(p, cosmo); + const double u_expect = + ((double)max(u_minimal, u_part)) * internal_energy_to_cgs; + double mu_guess, T_guess; + + /* Get a first estimate for gas temperature. */ + const float X = rt_props->hydrogen_mass_fraction; + const float Y = rt_props->helium_mass_fraction; + *XHI = X; + *XHII = 0.f; + *XHeI = Y; + *XHeII = 0.f; + *XHeIII = 0.f; + mu_guess = + rt_tchem_get_mean_molecular_weight(*XHI, *XHII, *XHeI, *XHeII, *XHeIII); + T_guess = rt_tchem_temperature_from_internal_energy(u_expect, mu_guess, + kB_cgs, mp_cgs); + + if (T_guess > 1e4) { + /* T_guess should be in K, so it's fine to hardcode + * the ionization limit of ~1e4 K for the first guess here + * If we're above the temperature threshold with this guess, + * assume we're fully ionized as first guess instead. */ + *XHI = 0.f; + *XHII = X; + *XHeI = 0.f; + *XHeII = 0.f; + *XHeIII = Y; + mu_guess = + rt_tchem_get_mean_molecular_weight(*XHI, *XHII, *XHeI, *XHeII, *XHeIII); + T_guess = rt_tchem_temperature_from_internal_energy(u_expect, mu_guess, + kB_cgs, mp_cgs); + } + + /* Now given the first temperature guess, update + * the mass fractions and mean molecular weight */ + rt_ion_equil_mass_fractions_from_T(T_guess, X, Y, XHI, XHII, XHeI, XHeII, + XHeIII); + mu_guess = + rt_tchem_get_mean_molecular_weight(*XHI, *XHII, *XHeI, *XHeII, *XHeIII); + + /* Get first guess for internal energy */ + double u_guess = + rt_tchem_internal_energy_from_T(T_guess, mu_guess, kB_cgs, mp_cgs); + + int iter = 0; + double du = u_expect - u_guess; + double du_old = du; /* initialize as same value */ + + while (fabs(du) >= RT_ION_EQUIL_EPSILON * u_expect) { + iter += 1; + if (iter > RT_ION_EQUIL_ITER_MAX) { + message( + "Warning: Ionization Equilibrium iteration didn't converge; " + "T=%.6g, 1 - u/u_correct = %.6g", + T_guess, 1. - u_guess / u_expect); + break; + } + + if (T_guess < 0.) { + message("Warning: Got negative temperature, resetting"); + T_guess = 0.1; /* Note: T_guess should be in K here */ + } + + /* find next temperature guess by solving linear equation + * m * T_next + n = u_expect - u_guess_new ~ 0 + * NOTE: we pretend that the function that we're looking the + * root of is f(T) = u_expect - u_guess(T), with u_expect = const. + * so df/dT = - du_guess/dT, therefore we add a minus sign here. */ + double m = -rt_tchem_internal_energy_dT(mu_guess, kB_cgs, mp_cgs); + double n = u_expect - u_guess - m * T_guess; + double T_next = -n / m; + + /* Given the new temperature guess, compute the + * expected mean molecular weight */ + rt_ion_equil_mass_fractions_from_T(T_next, X, Y, XHI, XHII, XHeI, XHeII, + XHeIII); + double mu_next = + rt_tchem_get_mean_molecular_weight(*XHI, *XHII, *XHeI, *XHeII, *XHeIII); + + /* now given the new temperature and mass fraction guess, + * update the expected gas internal energy */ + double u_next = + rt_tchem_internal_energy_from_T(T_next, mu_next, kB_cgs, mp_cgs); + + /* save the old internal energy, and get the new one */ + du_old = du; + du = u_expect - u_next; + + /* if we're oscillating between positive and negative + * values, try a bisection to help out */ + if (du_old * du < 0.0) { + T_next = 0.5 * (T_guess + T_next); + rt_ion_equil_mass_fractions_from_T(T_next, X, Y, XHI, XHII, XHeI, XHeII, + XHeIII); + mu_next = rt_tchem_get_mean_molecular_weight(*XHI, *XHII, *XHeI, *XHeII, + *XHeIII); + u_next = rt_tchem_internal_energy_from_T(T_next, mu_next, kB_cgs, mp_cgs); + } + + /* prepare for next iteration */ + T_guess = T_next; + mu_guess = mu_next; + u_guess = u_next; + du = u_expect - u_next; + } +} + +#undef RT_ION_EQUIL_ITER_MAX +#undef RT_ION_EQUIL_EPSILON + +#endif /* SWIFT_RT_IONIZATION_EQUILIBRIUM_H */ diff --git a/src/rt/KIARA/rt_parameters.h b/src/rt/KIARA/rt_parameters.h new file mode 100644 index 0000000000..8d9b422e64 --- /dev/null +++ b/src/rt/KIARA/rt_parameters.h @@ -0,0 +1,38 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef SWIFT_KIARA_RT_PARAMETERS_H +#define SWIFT_KIARA_RT_PARAMETERS_H + +/** + * @file src/rt/KIARA/rt_parameters.h + * @brief Global RT parameters. + */ + +extern struct rt_parameters rt_params; + +/** + * Some global RT related parameters. + */ +struct rt_parameters { + float reduced_speed_of_light; + float reduced_speed_of_light_inverse; +}; + +#endif /* SWIFT_KIARA_RT_PARAMETERS_H */ diff --git a/src/rt/KIARA/rt_properties.h b/src/rt/KIARA/rt_properties.h new file mode 100644 index 0000000000..27187711de --- /dev/null +++ b/src/rt/KIARA/rt_properties.h @@ -0,0 +1,622 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_PROPERTIES_KIARA_H +#define SWIFT_RT_PROPERTIES_KIARA_H + +#include "rt_grackle_utils.h" +#include "rt_interaction_rates.h" +#include "rt_parameters.h" +#include "rt_stellar_emission_model.h" + +#include + +/** + * @file src/rt/KIARA/rt_properties.h + * @brief Main header file for the 'KIARA' radiative transfer scheme + * properties. + */ + +#define RT_IMPLEMENTATION "KIARA M1closure" + +#if defined(RT_RIEMANN_SOLVER_GLF) +#define RT_RIEMANN_SOLVER_NAME "GLF Riemann Solver" +#elif defined(RT_RIEMANN_SOLVER_HLL) +#define RT_RIEMANN_SOLVER_NAME "HLL Riemann Solver" +#else +#error "No valid choice of RT Riemann solver has been selected" +#endif + +/** + * @brief Properties of the 'KIARA' radiative transfer model + */ +struct rt_props { + + /* Which stellar emission model to use */ + enum rt_stellar_emission_models stellar_emission_model; + + /* (Lower) frequency bin edges for photon groups */ + float photon_groups[RT_NGROUPS]; + + /* Global constant stellar emission rates */ + double stellar_const_emission_rates[RT_NGROUPS]; + + /* CFL condition */ + float CFL_condition; + + /* Factor to limit cooling time by */ + float f_limit_cooling_time; + + /* do we set initial ionization mass fractions manually? */ + int set_initial_ionization_mass_fractions; + int set_equilibrium_initial_ionization_mass_fractions; + + /* initial mass fractions for ionization species */ + /* the following are required for manually setting exact values */ + float mass_fraction_HI_init; + float mass_fraction_HII_init; + float mass_fraction_HeI_init; + float mass_fraction_HeII_init; + float mass_fraction_HeIII_init; + /* float number_density_electrons_init; [> todo: do we need this? <] */ + + /* Hydrogen and Helium mass fractions of the non-metal portion of the gas */ + float hydrogen_mass_fraction; + float helium_mass_fraction; + + /* Skip thermochemistry? For testing/debugging only! */ + int skip_thermochemistry; + + /* Coupling with galaxy subgrid physics? */ + int rt_with_galaxy_subgrid; + + /* Re-do thermochemistry recursively if difference in internal energy is too + * big? */ + int max_tchem_recursion; + + /* Optionally restrict maximal timestep for stars */ + float stars_max_timestep; + + /* Which stellar spectrum type to use? */ + int stellar_spectrum_type; + /* If constant: get max frequency */ + double const_stellar_spectrum_max_frequency; + /* If blackbody: get temperature */ + double stellar_spectrum_blackbody_T; + + /* Storage for integrated photoionization cross sections */ + /* Note: they are always in cgs. */ + double** energy_weighted_cross_sections; + double** number_weighted_cross_sections; + /* Mean photon energy in frequency bin for user provided spectrum. In erg.*/ + double average_photon_energy[RT_NGROUPS]; + /* Integral over photon numbers of user provided spectrum. */ + double photon_number_integral[RT_NGROUPS]; + + /* Location of Stellar spectrum tables. */ + char stellar_table_path[200]; + + /* Storeage for the BPASS photon number table in a 3d array with three photon + * group 2d tables. + * ionizing_tables[0] = ionizing_HI_table + * ionizing_tables[1] = ionizing_HeI_table + * ionizing_tables[2] = ionizing_HeII_table*/ + double*** ionizing_tables; + + /* Photon escape fraction from stellar emission model. */ + double f_esc; + + /* Grackle Stuff */ + /* ------------- */ + + /*! grackle unit system */ + code_units grackle_units; + + /*! grackle chemistry data */ + chemistry_data grackle_chemistry_data; + + /*! grackle chemistry data storage + * (needed for local function calls) */ + chemistry_data_storage grackle_chemistry_rates; + + /*! use case B recombination? */ + int case_B_recombination; + + /*! make grackle talkative? */ + int grackle_verbose; + + /* Max number of subcycles per hydro step */ + int debug_max_nr_subcycles; + +#ifdef SWIFT_RT_DEBUG_CHECKS + /* radiation emitted by stars this step. This is not really a property, + * but a placeholder to sum up a global variable. It's being reset + * every timestep. */ + unsigned long long debug_radiation_emitted_this_step; + + /* total radiation emitted by stars. This is not really a property, + * but a placeholder to sum up a global variable */ + unsigned long long debug_radiation_emitted_tot; + + /* radiation absorbed by gas this step. This is not really a property, + * but a placeholder to sum up a global variable */ + unsigned long long debug_radiation_absorbed_this_step; + + /* total radiation absorbed by gas. This is not really a property, + * but a placeholder to sum up a global variable */ + unsigned long long debug_radiation_absorbed_tot; +#endif +}; + +/* Some declarations to avoid cyclical inclusions. */ +/* ----------------------------------------------- */ +/* Keep the declarations for *after* the definition of rt_props struct */ + +/** + * @brief allocate and pre-compute the averaged cross sections + * for each photon group and ionizing species. + * Declare this here to avoid cyclical inclusions. + * + * @param rt_props RT properties struct + * @param phys_const physical constants struct + * @param us internal units struct + **/ +void rt_cross_sections_init(struct rt_props* restrict rt_props, + const struct phys_const* restrict phys_const, + const struct unit_system* restrict us); + +/* Function to read the table. */ +double **read_Bpass_from_hdf5(char *file_name, char *dataset_name); + +/* Now for the good stuff */ +/* ------------------------------------- */ + +/** + * @brief Print the RT model. + * + * @param rtp The #rt_props + */ +__attribute__((always_inline)) INLINE static void rt_props_print( + const struct rt_props* rtp) { + + /* Only the master print */ + if (engine_rank != 0) return; + + message("Radiative transfer scheme: '%s'", RT_IMPLEMENTATION); + message("RT Riemann Solver used: '%s'", RT_RIEMANN_SOLVER_NAME); + char messagestring[200] = "Using photon frequency bins (Ryd): [ "; + char freqstring[20]; + for (int g = 0; g < RT_NGROUPS; g++) { + sprintf(freqstring, "%.3g ", rtp->photon_groups[g] / 3.288e15); + strcat(messagestring, freqstring); + } + strcat(messagestring, "]"); + message("%s", messagestring); + + if (rtp->stellar_emission_model == rt_stellar_emission_model_const) { + strcpy(messagestring, "Using constant stellar emission rates: [ "); + for (int g = 0; g < RT_NGROUPS; g++) { + sprintf(freqstring, "%.3g ", rtp->stellar_const_emission_rates[g]); + strcat(messagestring, freqstring); + } + strcat(messagestring, "]"); + message("%s", messagestring); + } else if (rtp->stellar_emission_model == + rt_stellar_emission_model_IlievTest) { + message("Using Iliev+06 Test 4 stellar emission model."); + } else if (rtp->stellar_emission_model == + rt_stellar_emission_model_BPASS) { + message("Using BPASS stellar emission model."); + } else { + error("Unknown stellar emission model %d", rtp->stellar_emission_model); + } + + if (rtp->set_equilibrium_initial_ionization_mass_fractions) + message( + "Setting initial ionization mass fractions " + "assuming ionization equilibrium"); + if (rtp->set_initial_ionization_mass_fractions) + message( + "Using initial ionization mass fractions specified in parameter file"); + if (rtp->skip_thermochemistry) + message("WARNING: Thermochemistry will be skipped."); + if (rtp->rt_with_galaxy_subgrid) + message("RT is coupling with galaxy subgrid modules."); + message("RT max number of subcycles: %d", rtp->debug_max_nr_subcycles); +} + +/** + * @brief Initialize the global properties of the RT scheme. + * + * @param rtp The #rt_props. + * @param phys_const The physical constants in the internal unit system. + * @param us The internal unit system. + * @param params The parsed parameters. + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static void rt_props_init( + struct rt_props* rtp, const struct phys_const* phys_const, + const struct unit_system* us, struct swift_params* params, + struct cosmology* cosmo) { + + /* Make sure we reset debugging counters correctly after + * zeroth step. */ + + /* Read in photon frequency group properties */ + /* ----------------------------------------- */ + if (RT_NGROUPS <= 0) { + error( + "You need to run KIARA-RT with at least 1 photon group, " + "you have %d", + RT_NGROUPS); + } else { + /* !! Keep the frequencies in Hz for now. !! */ + parser_get_param_float_array(params, "KIARART:photon_groups_Hz", RT_NGROUPS, + rtp->photon_groups); + } + + /* Sanity check: photon group edges must be in increasing order. */ + for (int g = 0; g < RT_NGROUPS - 1; g++) { + if (rtp->photon_groups[g + 1] <= rtp->photon_groups[g]) + error( + "Photon frequency bin edges need to be in increasing order. " + "Found index %d %.3g <= %.3g", + g + 1, rtp->photon_groups[g + 1], rtp->photon_groups[g]); + } + + /* Get stellar emission rate model related parameters */ + /* -------------------------------------------------- */ + + /* First initialize everything */ + for (int g = 0; g < RT_NGROUPS; g++) { + rtp->stellar_const_emission_rates[g] = 0.; + } + + rtp->stellar_emission_model = rt_stellar_emission_model_none; + + char stellar_model_str[80]; + parser_get_param_string(params, "KIARART:stellar_luminosity_model", + stellar_model_str); + + if (strcmp(stellar_model_str, "const") == 0) { + rtp->stellar_emission_model = rt_stellar_emission_model_const; + } else if (strcmp(stellar_model_str, "IlievTest4") == 0) { + rtp->stellar_emission_model = rt_stellar_emission_model_IlievTest; + } else if (strcmp(stellar_model_str, "BPASS") == 0) { + rtp->stellar_emission_model = rt_stellar_emission_model_BPASS; + } else { + error("Unknown stellar luminosity model '%s'", stellar_model_str); + } + + if (rtp->stellar_emission_model == rt_stellar_emission_model_const) { + /* Read the luminosities from the parameter file */ + double emission_rates[RT_NGROUPS]; + parser_get_param_double_array(params, + "KIARART:const_stellar_luminosities_LSol", + RT_NGROUPS, emission_rates); + const double unit_power = units_cgs_conversion_factor(us, UNIT_CONV_POWER); + const double unit_power_inv = 1. / unit_power; + for (int g = 0; g < RT_NGROUPS; g++) { + rtp->stellar_const_emission_rates[g] = emission_rates[g] * unit_power_inv; + } + } else if (rtp->stellar_emission_model == + rt_stellar_emission_model_IlievTest) { + /* Nothing to do here */ + } else if (rtp->stellar_emission_model == + rt_stellar_emission_model_BPASS) { + parser_get_param_string(params, "KIARART:stellar_table_path", + rtp->stellar_table_path); + } else { + error("Unknown stellar emission model %d", rtp->stellar_emission_model); + } + + /* get reduced speed of light factor */ + /* --------------------------------- */ + const float f_r = parser_get_param_float(params, "KIARART:f_reduce_c"); + if (f_r <= 0.f) + error("Invalid speed of light reduction factor: %.3e <= 0.", f_r); + rt_params.reduced_speed_of_light = phys_const->const_speed_light_c * f_r; + rt_params.reduced_speed_of_light_inverse = + 1.f / rt_params.reduced_speed_of_light; + + /* get CFL condition */ + /* ----------------- */ + const float CFL = parser_get_param_float(params, "KIARART:CFL_condition"); + if (CFL <= 0.f) error("Invalid CFL number: %.3e <= 0.", CFL); + rtp->CFL_condition = CFL; + + const float f_limit_cooling_time = parser_get_opt_param_float( + params, "KIARART:f_limit_cooling_time", /*default=*/0.6); + if (f_limit_cooling_time < 0.f) + error("Invalid cooling time reduction factor: %.3e < 0.", + f_limit_cooling_time); + else if (f_limit_cooling_time == 0.f) + message("Warning: Computation of cooling time will be skipped"); + rtp->f_limit_cooling_time = f_limit_cooling_time; + + /* Get thermochemistry set-up */ + /* -------------------------- */ + rtp->hydrogen_mass_fraction = + parser_get_param_float(params, "KIARART:hydrogen_mass_fraction"); + rtp->helium_mass_fraction = 1.f - rtp->hydrogen_mass_fraction; + if (rtp->hydrogen_mass_fraction <= 0.f || rtp->hydrogen_mass_fraction > 1.f) + error("Invalid hydrogen mass fraction: %g", rtp->hydrogen_mass_fraction); + + /* Are we manually overwriting initial mass fractions of H and He? */ + rtp->set_initial_ionization_mass_fractions = parser_get_opt_param_int( + params, "KIARART:set_initial_ionization_mass_fractions", + /* default = */ 0); + if (rtp->set_initial_ionization_mass_fractions) { + /* Read in mass fractions */ + rtp->mass_fraction_HI_init = + parser_get_param_float(params, "KIARART:mass_fraction_HI"); + rtp->mass_fraction_HII_init = + parser_get_param_float(params, "KIARART:mass_fraction_HII"); + rtp->mass_fraction_HeI_init = + parser_get_param_float(params, "KIARART:mass_fraction_HeI"); + rtp->mass_fraction_HeII_init = + parser_get_param_float(params, "KIARART:mass_fraction_HeII"); + rtp->mass_fraction_HeIII_init = + parser_get_param_float(params, "KIARART:mass_fraction_HeIII"); + + /* Temporary check neglecting metals. Make sure we sum up to 1. */ + const float h_sum = + rtp->mass_fraction_HI_init + rtp->mass_fraction_HII_init; + if (fabsf(h_sum - rtp->hydrogen_mass_fraction) > 1e-4) + error( + "Inconsistent H mass fractions: XH_tot %.6g != XHI %.6g + XHII %.6g", + rtp->hydrogen_mass_fraction, rtp->mass_fraction_HI_init, + rtp->mass_fraction_HII_init); + + const float he_sum = rtp->mass_fraction_HeI_init + + rtp->mass_fraction_HeII_init + + rtp->mass_fraction_HeIII_init; + if (fabsf(he_sum - rtp->helium_mass_fraction) > 1e-4) + error( + "Inconsistent He mass fractions: XHe_tot %.6g != XHeI %.6g + XHeII " + "%.6g + XHeIII %.6g", + rtp->helium_mass_fraction, rtp->mass_fraction_HeI_init, + rtp->mass_fraction_HeII_init, rtp->mass_fraction_HeIII_init); + + const float mass_fraction_sum = h_sum + he_sum; + if (fabsf(mass_fraction_sum - 1.f) > 1e-5) + error("Constituent species mass fraction sums up to %.6f, I expect 1.0", + mass_fraction_sum); + } else { + /* Initialize properties to deliberately bogus values */ + rtp->mass_fraction_HI_init = -1.f; + rtp->mass_fraction_HII_init = -1.f; + rtp->mass_fraction_HeI_init = -1.f; + rtp->mass_fraction_HeII_init = -1.f; + rtp->mass_fraction_HeIII_init = -1.f; + } + + /* Are we setting up initial mass fractions in equilibrium? */ + rtp->set_equilibrium_initial_ionization_mass_fractions = + parser_get_opt_param_int( + params, "KIARART:set_equilibrium_initial_ionization_mass_fractions", + /* default = */ 0); + + if (rtp->set_equilibrium_initial_ionization_mass_fractions && + rtp->set_initial_ionization_mass_fractions) + error( + "Can't use equilibrium initial ionization mass fractions " + "simultaneously with manually set mass fractions. Pick one."); + + /* Are we skipping thermochemistry? */ + rtp->skip_thermochemistry = parser_get_opt_param_int( + params, "KIARART:skip_thermochemistry", /* default = */ 0); + + /* Are we coupling with galaxy subgrid modules? */ + rtp->rt_with_galaxy_subgrid = parser_get_opt_param_int( + params, "KIARART:rt_with_galaxy_subgrid", /* default = */ 0); + + /* Are we re-doing thermochemistry? */ + rtp->max_tchem_recursion = parser_get_opt_param_int( + params, "KIARART:max_tchem_recursion", /* default = */ 0); + + /* Stellar Spectra */ + /* --------------- */ + + /* Initialize conditional parameters to bogus values */ + rtp->const_stellar_spectrum_max_frequency = -1.; + rtp->stellar_spectrum_blackbody_T = -1.; + rtp->ionizing_tables = NULL; + rtp->f_esc = -1.; + + rtp->stellar_spectrum_type = + parser_get_param_int(params, "KIARART:stellar_spectrum_type"); + + /* Check for BPASS setup. */ + if (rtp->stellar_emission_model == rt_stellar_emission_model_BPASS && rtp->stellar_spectrum_type != 2) { + error("Warning: stellar_luminosity_model is BPASS, but stellar_spectrum_type is not 2!\n"); + } else if (rtp->stellar_emission_model != rt_stellar_emission_model_BPASS && rtp->stellar_spectrum_type == 2) { + error( "Warning: stellar_spectrum_type is 2, but stellar_luminosity_model is not BPASS!\n"); + } + + if (rtp->stellar_spectrum_type == 0) { + /* Constant spectrum: Read additional parameter */ + /* TODO: also translate back to internal units at later. For now, keep it in + * Hz */ + rtp->const_stellar_spectrum_max_frequency = parser_get_param_float( + params, "KIARART:stellar_spectrum_const_max_frequency_Hz"); + } else if (rtp->stellar_spectrum_type == 1) { + /* Blackbody spectrum: Read additional parameter */ + rtp->stellar_spectrum_blackbody_T = parser_get_param_float( + params, "KIARART:stellar_spectrum_blackbody_temperature_K"); + rtp->stellar_spectrum_blackbody_T /= + units_cgs_conversion_factor(us, UNIT_CONV_TEMPERATURE); + } else if (rtp->stellar_spectrum_type == 2) { + /* TODO: currently we use the const stellar spectrum to calculate photon group properties. + * We need to change it to use BPASS model value. */ + rtp->const_stellar_spectrum_max_frequency = parser_get_param_float( + params, "KIARART:stellar_spectrum_const_max_frequency_Hz"); + + /* Read the photon escape fraction value. */ + rtp->f_esc = parser_get_param_float( + params, "KIARART:photon_escape_fraction"); + + /* Read the table from BPASS. */ + char *fname = malloc(256); + char *dataset_name_HI = "/Table_HI/block0_values"; + char *dataset_name_HeI = "/Table_HeI/block0_values"; + char *dataset_name_HeII = "/Table_HeII/block0_values"; + /* Read stellar table filename. */ + sprintf(fname, "%s", rtp->stellar_table_path); + + int num_tables = 3; + rtp->ionizing_tables = malloc(num_tables * sizeof(double**)); + + rtp->ionizing_tables[0] = read_Bpass_from_hdf5(fname, dataset_name_HI); + rtp->ionizing_tables[1] = read_Bpass_from_hdf5(fname, dataset_name_HeI); + rtp->ionizing_tables[2] = read_Bpass_from_hdf5(fname, dataset_name_HeII); + + /* print table check. */ + //for (hsize_t i = 0; i < 13; i++){ + // for (hsize_t j = 0; j < 22; j++) { + // printf("%.2f\n", rtp->ionizing_tables[0][i][j]); + // } + // } + + /* free the data name. */ + free(fname); + } else { + error("Selected unknown stellar spectrum type %d", + rtp->stellar_spectrum_type); + } + + /* Maximal Star Timestep? */ + /* ---------------------- */ + rtp->stars_max_timestep = parser_get_opt_param_float( + params, "KIARART:stars_max_timestep", /*default=*/FLT_MAX); + /* Turn off if negative value given */ + if (rtp->stars_max_timestep < 0.f) rtp->stars_max_timestep = FLT_MAX; + /* Better safe than sorry */ + if (rtp->stars_max_timestep == 0.f) + error("You are restricting star time step to 0. That's a no-no."); + +#ifdef SWIFT_RT_DEBUG_CHECKS + rtp->debug_radiation_emitted_tot = 0ULL; + rtp->debug_radiation_emitted_this_step = 0ULL; + + rtp->debug_radiation_absorbed_tot = 0ULL; + rtp->debug_radiation_absorbed_this_step = 0ULL; + +#endif + + /* Don't make it an optional parameter here so we crash + * if I forgot to provide it */ + rtp->debug_max_nr_subcycles = + parser_get_param_int(params, "TimeIntegration:max_nr_rt_subcycles"); + + /* Grackle setup */ + /* ------------- */ + rtp->grackle_verbose = + parser_get_opt_param_int(params, "KIARART:grackle_verbose", /*default=*/0); + rtp->case_B_recombination = parser_get_opt_param_int( + params, "KIARART:case_B_recombination", /*default=*/1); + rt_init_grackle(&rtp->grackle_units, &rtp->grackle_chemistry_data, + &rtp->grackle_chemistry_rates, rtp->hydrogen_mass_fraction, + rtp->grackle_verbose, rtp->case_B_recombination, us, cosmo); + + /* Pre-compute interaction rates/cross sections */ + /* -------------------------------------------- */ + + rtp->energy_weighted_cross_sections = NULL; + rtp->number_weighted_cross_sections = NULL; + rt_cross_sections_init(rtp, phys_const, us); + + /* Finishers */ + /* --------- */ +} + +__attribute__((always_inline)) INLINE static void rt_props_update( + struct rt_props* rtp, const struct unit_system* us, + struct cosmology* cosmo) { + update_grackle_units_cosmo(&(rtp->grackle_units), us, cosmo); +} + +/** + * @brief Write an RT properties struct to the given FILE as a + * stream of bytes. + * + * @param props the struct + * @param stream the file stream + */ +__attribute__((always_inline)) INLINE static void rt_struct_dump( + const struct rt_props* props, FILE* stream) { + + restart_write_blocks((void*)props, sizeof(struct rt_props), 1, stream, + "RT props", "RT properties struct"); + /* The RT parameters, in particular the reduced speed of light, are + * not defined at compile time. So we need to read them in again. */ + restart_write_blocks(&rt_params, sizeof(struct rt_parameters), 1, stream, + "RT global parameters", "RT global parameters struct"); +} + +/** + * @brief Restore an RT properties struct from the given FILE as + * a stream of bytes. + * + * @param props the struct + * @param stream the file stream + * @param phys_const The physical constants in the internal unit system. + * @param us The internal unit system. + * @param cosmo the #cosmology + */ +__attribute__((always_inline)) INLINE static void rt_struct_restore( + struct rt_props* props, FILE* stream, const struct phys_const* phys_const, + const struct unit_system* us, const struct cosmology* restrict cosmo) { + + restart_read_blocks((void*)props, sizeof(struct rt_props), 1, stream, NULL, + "RT properties struct"); + /* Set up stuff that needs array allocation */ + rt_init_grackle(&props->grackle_units, &props->grackle_chemistry_data, + &props->grackle_chemistry_rates, + props->hydrogen_mass_fraction, props->grackle_verbose, + props->case_B_recombination, us, cosmo); + + props->energy_weighted_cross_sections = NULL; + props->number_weighted_cross_sections = NULL; + rt_cross_sections_init(props, phys_const, us); + + /* Read the table from BPASS. */ + char *fname = malloc(256); + char *dataset_name_HI = "/Table_HI/block0_values"; + char *dataset_name_HeI = "/Table_HeI/block0_values"; + char *dataset_name_HeII = "/Table_HeII/block0_values"; + /* Read stellar table filename. */ + sprintf(fname, "%s", props->stellar_table_path); + + int num_tables = 3; + props->ionizing_tables = malloc(num_tables * sizeof(double**)); + + props->ionizing_tables[0] = read_Bpass_from_hdf5(fname, dataset_name_HI); + props->ionizing_tables[1] = read_Bpass_from_hdf5(fname, dataset_name_HeI); + props->ionizing_tables[2] = read_Bpass_from_hdf5(fname, dataset_name_HeII); + + /* free the data name. */ + free(fname); + + /* The RT parameters, in particular the reduced speed of light, are + * not defined at compile time. So we need to write them down. */ + restart_read_blocks(&rt_params, sizeof(struct rt_parameters), 1, stream, NULL, + "RT global parameters struct"); +} + +#endif /* SWIFT_RT_PROPERTIES_KIARA_H */ diff --git a/src/rt/KIARA/rt_riemann_GLF.h b/src/rt/KIARA/rt_riemann_GLF.h new file mode 100644 index 0000000000..2ce9e10d24 --- /dev/null +++ b/src/rt/KIARA/rt_riemann_GLF.h @@ -0,0 +1,80 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef SWIFT_KIARA_RT_RIEMANN_GLF_H +#define SWIFT_KIARA_RT_RIEMANN_GLF_H + +#include "rt_getters.h" +#include "rt_parameters.h" +#include "rt_unphysical.h" + +/** + * @file src/rt/KIARA/rt_riemann_GLF.h + * @brief The Global Lax-Friedrich (GLF) Riemann solver for the moments of the + * radiative transfer equation following Rosdahl et al 2013 + * */ + +/** + * @brief Solve the Riemann problem for the RT equations and return the + * flux at the interface. + * + * @param UL left state (radiation energy density, flux) + * @param UR right state (radiation energy density, flux) + * @param FLnorm the norm of the radiation flux of the left state + * @param FRnorm the norm of the radiation flux of the right state + * @param hyperFluxL the flux of the hyperbolic conservation law of the left + * state + * @param hyperFluxR the flux of the hyperbolic conservation law of the right + * state + * @param n_unit the unit vector perpendicular to the "intercell" surface. + * @param flux_half (return) the resulting flux at the interface + */ +__attribute__((always_inline)) INLINE static void rt_riemann_solve_for_flux( + const float UL[4], const float UR[4], const float FLnorm, + const float FRnorm, float hyperFluxL[4][3], float hyperFluxR[4][3], + const float n_unit[3], float flux_half[4]) { + float fluxL[4]; + fluxL[0] = hyperFluxL[0][0] * n_unit[0] + hyperFluxL[0][1] * n_unit[1] + + hyperFluxL[0][2] * n_unit[2]; + fluxL[1] = hyperFluxL[1][0] * n_unit[0] + hyperFluxL[1][1] * n_unit[1] + + hyperFluxL[1][2] * n_unit[2]; + fluxL[2] = hyperFluxL[2][0] * n_unit[0] + hyperFluxL[2][1] * n_unit[1] + + hyperFluxL[2][2] * n_unit[2]; + fluxL[3] = hyperFluxL[3][0] * n_unit[0] + hyperFluxL[3][1] * n_unit[1] + + hyperFluxL[3][2] * n_unit[2]; + + float fluxR[4]; + fluxR[0] = hyperFluxR[0][0] * n_unit[0] + hyperFluxR[0][1] * n_unit[1] + + hyperFluxR[0][2] * n_unit[2]; + fluxR[1] = hyperFluxR[1][0] * n_unit[0] + hyperFluxR[1][1] * n_unit[1] + + hyperFluxR[1][2] * n_unit[2]; + fluxR[2] = hyperFluxR[2][0] * n_unit[0] + hyperFluxR[2][1] * n_unit[1] + + hyperFluxR[2][2] * n_unit[2]; + fluxR[3] = hyperFluxR[3][0] * n_unit[0] + hyperFluxR[3][1] * n_unit[1] + + hyperFluxR[3][2] * n_unit[2]; + + const float c_red = rt_params.reduced_speed_of_light; + + flux_half[0] = 0.5f * (fluxL[0] + fluxR[0] - c_red * (UR[0] - UL[0])); + flux_half[1] = 0.5f * (fluxL[1] + fluxR[1] - c_red * (UR[1] - UL[1])); + flux_half[2] = 0.5f * (fluxL[2] + fluxR[2] - c_red * (UR[2] - UL[2])); + flux_half[3] = 0.5f * (fluxL[3] + fluxR[3] - c_red * (UR[3] - UL[3])); +} + +#endif /* SWIFT_KIARA_RT_RIEMANN_GLF_H */ diff --git a/src/rt/KIARA/rt_riemann_HLL.h b/src/rt/KIARA/rt_riemann_HLL.h new file mode 100644 index 0000000000..c20c160dec --- /dev/null +++ b/src/rt/KIARA/rt_riemann_HLL.h @@ -0,0 +1,224 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef SWIFT_KIARA_RT_RIEMANN_HLL_H +#define SWIFT_KIARA_RT_RIEMANN_HLL_H + +#define RT_RIEMANN_HLL_NPOINTS 100 +#define RT_RIEMANN_HLL_DF (1.f / (float)(RT_RIEMANN_HLL_NPOINTS - 1)) +#define RT_RIEMANN_HLL_ONE_OVER_DF ((float)RT_RIEMANN_HLL_NPOINTS - 1) +#define RT_RIEMANN_HLL_DTHETA (M_PI / ((float)(RT_RIEMANN_HLL_NPOINTS - 1))) +#define RT_RIEMANN_HLL_ONE_OVER_DTHETA \ + (((float)(RT_RIEMANN_HLL_NPOINTS - 1)) / M_PI) + +#include "rt_getters.h" +#include "rt_parameters.h" +#include "rt_riemann_HLL_eigenvalues.h" +#include "rt_unphysical.h" + +/** + * @file src/rt/KIARA/rt_riemann_HLL.h + * @brief The Hartmann-Lax-van Leer Riemann solver for the moments of the + * radiative transfer equation following following Gonzalez et al 2007 + * (ui.adsabs.harvard.edu/abs/2007A%26A...464..429G). + * */ + +/** + * @brief Interpolate the minimal and maximal eigenvalues from + * the lookup table given the reduced flux f and angle w.r.t. + * the surface theta. + * + * @param f reduced flux |F|/(c E) + * @param theta angle between flux and surface + * @param lambda_min minimal eigenvalue + * @param lambda_max maximal eigenvalue + */ + +__attribute__((always_inline)) INLINE static void +rt_riemann_interpolate_eigenvals(float f, float theta, float *lambda_min, + float *lambda_max) { + + /* find lower table indices for f and theta */ + int f_ind = floor(RT_RIEMANN_HLL_ONE_OVER_DF * f); + if (f_ind >= RT_RIEMANN_HLL_NPOINTS - 1) f_ind = RT_RIEMANN_HLL_NPOINTS - 2; + int theta_ind = floor(RT_RIEMANN_HLL_ONE_OVER_DTHETA * theta); + if (theta_ind >= RT_RIEMANN_HLL_NPOINTS - 1) + theta_ind = RT_RIEMANN_HLL_NPOINTS - 2; + + /* Grab the data */ + const float Q11min = rt_riemann_HLL_eigenvals[f_ind][theta_ind][0]; + const float Q12min = rt_riemann_HLL_eigenvals[f_ind][theta_ind + 1][0]; + const float Q21min = rt_riemann_HLL_eigenvals[f_ind + 1][theta_ind][0]; + const float Q22min = rt_riemann_HLL_eigenvals[f_ind + 1][theta_ind + 1][0]; + const float Q11max = rt_riemann_HLL_eigenvals[f_ind][theta_ind][1]; + const float Q12max = rt_riemann_HLL_eigenvals[f_ind][theta_ind + 1][1]; + const float Q21max = rt_riemann_HLL_eigenvals[f_ind + 1][theta_ind][1]; + const float Q22max = rt_riemann_HLL_eigenvals[f_ind + 1][theta_ind + 1][1]; + + /* (f - f1)/(f2 - f1) = (f - f_ind * Delta f)/Delta f */ + const float df1 = f * RT_RIEMANN_HLL_ONE_OVER_DF - (float)f_ind; + /* (f2 - f)/(f2 - f1) = ((f_ind + 1) * Delta f - f)/Delta f */ + const float df2 = 1.f - df1; + + /* linear interpolation in f direction */ + const float fmid1_min = df2 * Q11min + df1 * Q21min; + const float fmid2_min = df2 * Q12min + df1 * Q22min; + const float fmid1_max = df2 * Q11max + df1 * Q21max; + const float fmid2_max = df2 * Q12max + df1 * Q22max; + + /* Now second interpolation in theta direction */ + /* (theta - theta1)/(theta2 - theta1) */ + const float dtheta1 = + theta * RT_RIEMANN_HLL_ONE_OVER_DTHETA - (float)theta_ind; + /* (theta2 - theta)/(theta2 - theta1) */ + const float dtheta2 = 1.f - dtheta1; + + /* Make sure -1 < eigenvalue < 1 */ + float lmin = dtheta2 * fmid1_min + dtheta1 * fmid2_min; + lmin = max(lmin, -1.f); + lmin = min(lmin, 1.f); + *lambda_min = lmin; + float lmax = dtheta2 * fmid1_max + dtheta1 * fmid2_max; + lmax = max(lmax, -1.f); + lmax = min(lmax, 1.f); + *lambda_max = lmax; +} + +/** + * @brief Solve the Riemann problem for the RT equations and return the + * flux at the interface. + * + * @param UL left state (radiation energy density, flux) + * @param UR right state (radiation energy density, flux) + * @param FLnorm the norm of the radiation flux of the left state + * @param FRnorm the norm of the radiation flux of the right state + * @param hyperFluxL the flux of the hyperbolic conservation law of the left + * state + * @param hyperFluxR the flux of the hyperbolic conservation law of the right + * state + * @param n_unit the unit vector perpendicular to the "intercell" surface. + * @param flux_half (return) the resulting flux at the interface + */ +__attribute__((always_inline)) INLINE static void rt_riemann_solve_for_flux( + const float UL[4], const float UR[4], const float FLnorm, + const float FRnorm, float hyperFluxL[4][3], float hyperFluxR[4][3], + const float n_unit[3], float flux_half[4]) { + + /* Compute reduced fluxes and angles between surface and flux. + * These are based on physical fluxes, not hyperbolic fluxes. */ + const float c_red = rt_params.reduced_speed_of_light; + const float c_red_inv = rt_params.reduced_speed_of_light_inverse; + + float fL = 0.f; + float thetaL = 0.f; + + if (UL[0] > 0.f) { + fL = FLnorm / UL[0] * c_red_inv; + fL = min(fL, 1.f); + } + + if (FLnorm > 0.f) { + /* cos theta = F * n / (|F| |n|) */ + const float FLdotn = + UL[1] * n_unit[0] + UL[2] * n_unit[1] + UL[3] * n_unit[2]; + float costhetaL = min(FLdotn / FLnorm, 1.f); + costhetaL = max(costhetaL, -1.f); + thetaL = acosf(costhetaL); + } + + float fR = 0.f; + float thetaR = 0.f; + + if (UR[0] > 0.f) { + fR = FRnorm / UR[0] * c_red_inv; + fR = min(fR, 1.f); + } + + if (FRnorm > 0.f) { + const float FRdotn = + UR[1] * n_unit[0] + UR[2] * n_unit[1] + UR[3] * n_unit[2]; + float costhetaR = min(FRdotn / FRnorm, 1.f); + costhetaR = max(costhetaR, -1.f); + thetaR = acosf(costhetaR); + } + + /* interpolate eigenvalues in lookup table */ + float lambdaLmin = 0; + float lambdaLmax = 0; + rt_riemann_interpolate_eigenvals(fL, thetaL, &lambdaLmin, &lambdaLmax); + float lambdaRmin = 0; + float lambdaRmax = 0; + rt_riemann_interpolate_eigenvals(fR, thetaR, &lambdaRmin, &lambdaRmax); + + const float lminus = min3(lambdaLmin, lambdaRmin, 0.f); + const float lplus = max3(lambdaLmax, lambdaRmax, 0.f); + + /* Sanity check: This should give the same results as GLF solver */ + /* const float lminus = -1.f; */ + /* const float lplus = +1.f; */ + + if (lminus == 0.f && lplus == 0.f) { + flux_half[0] = 0.f; + flux_half[1] = 0.f; + flux_half[2] = 0.f; + flux_half[3] = 0.f; + return; + } + + /* Project the (hyperbolic) flux along the surface, + * reduce the problem to 1D with 4 quantities*/ + float fluxL[4]; + fluxL[0] = hyperFluxL[0][0] * n_unit[0] + hyperFluxL[0][1] * n_unit[1] + + hyperFluxL[0][2] * n_unit[2]; + fluxL[1] = hyperFluxL[1][0] * n_unit[0] + hyperFluxL[1][1] * n_unit[1] + + hyperFluxL[1][2] * n_unit[2]; + fluxL[2] = hyperFluxL[2][0] * n_unit[0] + hyperFluxL[2][1] * n_unit[1] + + hyperFluxL[2][2] * n_unit[2]; + fluxL[3] = hyperFluxL[3][0] * n_unit[0] + hyperFluxL[3][1] * n_unit[1] + + hyperFluxL[3][2] * n_unit[2]; + + float fluxR[4]; + fluxR[0] = hyperFluxR[0][0] * n_unit[0] + hyperFluxR[0][1] * n_unit[1] + + hyperFluxR[0][2] * n_unit[2]; + fluxR[1] = hyperFluxR[1][0] * n_unit[0] + hyperFluxR[1][1] * n_unit[1] + + hyperFluxR[1][2] * n_unit[2]; + fluxR[2] = hyperFluxR[2][0] * n_unit[0] + hyperFluxR[2][1] * n_unit[1] + + hyperFluxR[2][2] * n_unit[2]; + fluxR[3] = hyperFluxR[3][0] * n_unit[0] + hyperFluxR[3][1] * n_unit[1] + + hyperFluxR[3][2] * n_unit[2]; + + const float one_over_dl = 1.f / (lplus - lminus); + /* Remember that the eigenvalues are in units of c */ + const float lprod = lplus * lminus * c_red; + + flux_half[0] = + (lplus * fluxL[0] - lminus * fluxR[0] + lprod * (UR[0] - UL[0])) * + one_over_dl; + flux_half[1] = + (lplus * fluxL[1] - lminus * fluxR[1] + lprod * (UR[1] - UL[1])) * + one_over_dl; + flux_half[2] = + (lplus * fluxL[2] - lminus * fluxR[2] + lprod * (UR[2] - UL[2])) * + one_over_dl; + flux_half[3] = + (lplus * fluxL[3] - lminus * fluxR[3] + lprod * (UR[3] - UL[3])) * + one_over_dl; +} + +#endif /* SWIFT_KIARA_RT_RIEMANN_HLL_H */ diff --git a/src/rt/KIARA/rt_riemann_HLL_eigenvalues.h b/src/rt/KIARA/rt_riemann_HLL_eigenvalues.h new file mode 100644 index 0000000000..ff0d489863 --- /dev/null +++ b/src/rt/KIARA/rt_riemann_HLL_eigenvalues.h @@ -0,0 +1,5246 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ + +#ifndef RT_RIEMANN_HLL_EIGENVALUES_H +#define RT_RIEMANN_HLL_EIGENVALUES_H + +/** + * @file src/rt/KIARA/rt_riemann_HLL_eigenvalues.h + * @brief Contains the tabulated eigenvalues for the + * HLL RT Riemann solver following Gonzalez et al 2007 + * (ui.adsabs.harvard.edu/abs/2007A%26A...464..429G). + * + * For more details on how these eigenvalues are computed, look + * into /theory/RadiativeTransfer/HLLRiemannSolverEigenvalues + * */ + +/* We store the minimal and maximal eigenvalues for + * RT_RIEMANN_HLL_NPOINTS different values of + * 0 < f < 1 and 0 < theta < pi. + * + * First index: f + * Second index: theta + * [f][theta][0]: min eigenvalue + * [f][theta][1]: max eigenvalue + * */ +static float rt_riemann_HLL_eigenvals + [RT_RIEMANN_HLL_NPOINTS][RT_RIEMANN_HLL_NPOINTS][2] = { + { + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + {-.577350E+00, 0.577350E+00}, {-.577350E+00, 0.577350E+00}, + }, + { + {-.572277E+00, 0.582379E+00}, {-.572280E+00, 0.582376E+00}, + {-.572288E+00, 0.582369E+00}, {-.572300E+00, 0.582356E+00}, + {-.572318E+00, 0.582338E+00}, {-.572341E+00, 0.582316E+00}, + {-.572369E+00, 0.582288E+00}, {-.572402E+00, 0.582255E+00}, + {-.572440E+00, 0.582218E+00}, {-.572483E+00, 0.582175E+00}, + {-.572531E+00, 0.582128E+00}, {-.572583E+00, 0.582076E+00}, + {-.572641E+00, 0.582019E+00}, {-.572703E+00, 0.581957E+00}, + {-.572770E+00, 0.581891E+00}, {-.572841E+00, 0.581820E+00}, + {-.572917E+00, 0.581744E+00}, {-.572998E+00, 0.581665E+00}, + {-.573082E+00, 0.581580E+00}, {-.573172E+00, 0.581492E+00}, + {-.573265E+00, 0.581399E+00}, {-.573362E+00, 0.581303E+00}, + {-.573464E+00, 0.581202E+00}, {-.573569E+00, 0.581097E+00}, + {-.573678E+00, 0.580989E+00}, {-.573791E+00, 0.580877E+00}, + {-.573907E+00, 0.580761E+00}, {-.574027E+00, 0.580642E+00}, + {-.574150E+00, 0.580520E+00}, {-.574276E+00, 0.580394E+00}, + {-.574406E+00, 0.580265E+00}, {-.574538E+00, 0.580134E+00}, + {-.574673E+00, 0.579999E+00}, {-.574811E+00, 0.579862E+00}, + {-.574951E+00, 0.579722E+00}, {-.575094E+00, 0.579580E+00}, + {-.575239E+00, 0.579435E+00}, {-.575386E+00, 0.579289E+00}, + {-.575535E+00, 0.579140E+00}, {-.575686E+00, 0.578990E+00}, + {-.575839E+00, 0.578838E+00}, {-.575993E+00, 0.578684E+00}, + {-.576148E+00, 0.578529E+00}, {-.576304E+00, 0.578373E+00}, + {-.576462E+00, 0.578216E+00}, {-.576620E+00, 0.578058E+00}, + {-.576779E+00, 0.577899E+00}, {-.576939E+00, 0.577739E+00}, + {-.577099E+00, 0.577580E+00}, {-.577259E+00, 0.577419E+00}, + {-.577419E+00, 0.577259E+00}, {-.577580E+00, 0.577099E+00}, + {-.577739E+00, 0.576939E+00}, {-.577899E+00, 0.576779E+00}, + {-.578058E+00, 0.576620E+00}, {-.578216E+00, 0.576462E+00}, + {-.578373E+00, 0.576304E+00}, {-.578529E+00, 0.576148E+00}, + {-.578684E+00, 0.575993E+00}, {-.578838E+00, 0.575839E+00}, + {-.578990E+00, 0.575686E+00}, {-.579140E+00, 0.575535E+00}, + {-.579289E+00, 0.575386E+00}, {-.579435E+00, 0.575239E+00}, + {-.579580E+00, 0.575094E+00}, {-.579722E+00, 0.574951E+00}, + {-.579862E+00, 0.574811E+00}, {-.579999E+00, 0.574673E+00}, + {-.580134E+00, 0.574538E+00}, {-.580265E+00, 0.574406E+00}, + {-.580394E+00, 0.574276E+00}, {-.580520E+00, 0.574150E+00}, + {-.580642E+00, 0.574027E+00}, {-.580761E+00, 0.573907E+00}, + {-.580877E+00, 0.573791E+00}, {-.580989E+00, 0.573678E+00}, + {-.581097E+00, 0.573569E+00}, {-.581202E+00, 0.573464E+00}, + {-.581303E+00, 0.573362E+00}, {-.581399E+00, 0.573265E+00}, + {-.581492E+00, 0.573172E+00}, {-.581580E+00, 0.573082E+00}, + {-.581665E+00, 0.572998E+00}, {-.581744E+00, 0.572917E+00}, + {-.581820E+00, 0.572841E+00}, {-.581891E+00, 0.572770E+00}, + {-.581957E+00, 0.572703E+00}, {-.582019E+00, 0.572641E+00}, + {-.582076E+00, 0.572583E+00}, {-.582128E+00, 0.572531E+00}, + {-.582175E+00, 0.572483E+00}, {-.582218E+00, 0.572440E+00}, + {-.582255E+00, 0.572402E+00}, {-.582288E+00, 0.572369E+00}, + {-.582316E+00, 0.572341E+00}, {-.582338E+00, 0.572318E+00}, + {-.582356E+00, 0.572300E+00}, {-.582369E+00, 0.572288E+00}, + {-.582376E+00, 0.572280E+00}, {-.582379E+00, 0.572277E+00}, + }, + { + {-.567159E+00, 0.587364E+00}, {-.567164E+00, 0.587359E+00}, + {-.567180E+00, 0.587344E+00}, {-.567205E+00, 0.587319E+00}, + {-.567241E+00, 0.587284E+00}, {-.567287E+00, 0.587239E+00}, + {-.567343E+00, 0.587183E+00}, {-.567410E+00, 0.587118E+00}, + {-.567486E+00, 0.587043E+00}, {-.567572E+00, 0.586959E+00}, + {-.567668E+00, 0.586864E+00}, {-.567774E+00, 0.586760E+00}, + {-.567889E+00, 0.586647E+00}, {-.568014E+00, 0.586524E+00}, + {-.568148E+00, 0.586392E+00}, {-.568292E+00, 0.586251E+00}, + {-.568444E+00, 0.586100E+00}, {-.568606E+00, 0.585941E+00}, + {-.568776E+00, 0.585774E+00}, {-.568955E+00, 0.585597E+00}, + {-.569142E+00, 0.585413E+00}, {-.569338E+00, 0.585220E+00}, + {-.569541E+00, 0.585019E+00}, {-.569752E+00, 0.584811E+00}, + {-.569971E+00, 0.584594E+00}, {-.570198E+00, 0.584371E+00}, + {-.570431E+00, 0.584140E+00}, {-.570671E+00, 0.583903E+00}, + {-.570918E+00, 0.583659E+00}, {-.571172E+00, 0.583408E+00}, + {-.571431E+00, 0.583151E+00}, {-.571697E+00, 0.582889E+00}, + {-.571967E+00, 0.582620E+00}, {-.572244E+00, 0.582346E+00}, + {-.572525E+00, 0.582067E+00}, {-.572811E+00, 0.581784E+00}, + {-.573102E+00, 0.581495E+00}, {-.573396E+00, 0.581203E+00}, + {-.573695E+00, 0.580906E+00}, {-.573997E+00, 0.580606E+00}, + {-.574303E+00, 0.580302E+00}, {-.574611E+00, 0.579995E+00}, + {-.574922E+00, 0.579685E+00}, {-.575235E+00, 0.579373E+00}, + {-.575550E+00, 0.579059E+00}, {-.575867E+00, 0.578743E+00}, + {-.576186E+00, 0.578425E+00}, {-.576505E+00, 0.578106E+00}, + {-.576825E+00, 0.577787E+00}, {-.577146E+00, 0.577466E+00}, + {-.577466E+00, 0.577146E+00}, {-.577787E+00, 0.576825E+00}, + {-.578106E+00, 0.576505E+00}, {-.578425E+00, 0.576186E+00}, + {-.578743E+00, 0.575867E+00}, {-.579059E+00, 0.575550E+00}, + {-.579373E+00, 0.575235E+00}, {-.579685E+00, 0.574922E+00}, + {-.579995E+00, 0.574611E+00}, {-.580302E+00, 0.574303E+00}, + {-.580606E+00, 0.573997E+00}, {-.580906E+00, 0.573695E+00}, + {-.581203E+00, 0.573396E+00}, {-.581495E+00, 0.573102E+00}, + {-.581784E+00, 0.572811E+00}, {-.582067E+00, 0.572525E+00}, + {-.582346E+00, 0.572244E+00}, {-.582620E+00, 0.571967E+00}, + {-.582889E+00, 0.571697E+00}, {-.583151E+00, 0.571431E+00}, + {-.583408E+00, 0.571172E+00}, {-.583659E+00, 0.570918E+00}, + {-.583903E+00, 0.570671E+00}, {-.584140E+00, 0.570431E+00}, + {-.584371E+00, 0.570198E+00}, {-.584594E+00, 0.569971E+00}, + {-.584811E+00, 0.569752E+00}, {-.585019E+00, 0.569541E+00}, + {-.585220E+00, 0.569338E+00}, {-.585413E+00, 0.569142E+00}, + {-.585597E+00, 0.568955E+00}, {-.585774E+00, 0.568776E+00}, + {-.585941E+00, 0.568606E+00}, {-.586100E+00, 0.568444E+00}, + {-.586251E+00, 0.568292E+00}, {-.586392E+00, 0.568148E+00}, + {-.586524E+00, 0.568014E+00}, {-.586647E+00, 0.567889E+00}, + {-.586760E+00, 0.567774E+00}, {-.586864E+00, 0.567668E+00}, + {-.586959E+00, 0.567572E+00}, {-.587043E+00, 0.567486E+00}, + {-.587118E+00, 0.567410E+00}, {-.587183E+00, 0.567343E+00}, + {-.587239E+00, 0.567287E+00}, {-.587284E+00, 0.567241E+00}, + {-.587319E+00, 0.567205E+00}, {-.587344E+00, 0.567180E+00}, + {-.587359E+00, 0.567164E+00}, {-.587364E+00, 0.567159E+00}, + }, + { + {-.561995E+00, 0.592308E+00}, {-.562002E+00, 0.592301E+00}, + {-.562026E+00, 0.592278E+00}, {-.562064E+00, 0.592240E+00}, + {-.562118E+00, 0.592188E+00}, {-.562187E+00, 0.592120E+00}, + {-.562272E+00, 0.592038E+00}, {-.562372E+00, 0.591941E+00}, + {-.562487E+00, 0.591829E+00}, {-.562616E+00, 0.591702E+00}, + {-.562761E+00, 0.591561E+00}, {-.562920E+00, 0.591406E+00}, + {-.563094E+00, 0.591236E+00}, {-.563282E+00, 0.591053E+00}, + {-.563484E+00, 0.590855E+00}, {-.563700E+00, 0.590644E+00}, + {-.563930E+00, 0.590420E+00}, {-.564173E+00, 0.590182E+00}, + {-.564430E+00, 0.589931E+00}, {-.564699E+00, 0.589668E+00}, + {-.564981E+00, 0.589392E+00}, {-.565275E+00, 0.589103E+00}, + {-.565582E+00, 0.588803E+00}, {-.565900E+00, 0.588491E+00}, + {-.566229E+00, 0.588168E+00}, {-.566570E+00, 0.587834E+00}, + {-.566921E+00, 0.587489E+00}, {-.567283E+00, 0.587134E+00}, + {-.567654E+00, 0.586768E+00}, {-.568035E+00, 0.586393E+00}, + {-.568426E+00, 0.586009E+00}, {-.568825E+00, 0.585616E+00}, + {-.569232E+00, 0.585214E+00}, {-.569648E+00, 0.584804E+00}, + {-.570071E+00, 0.584387E+00}, {-.570501E+00, 0.583962E+00}, + {-.570937E+00, 0.583530E+00}, {-.571380E+00, 0.583092E+00}, + {-.571829E+00, 0.582647E+00}, {-.572283E+00, 0.582197E+00}, + {-.572742E+00, 0.581742E+00}, {-.573205E+00, 0.581283E+00}, + {-.573672E+00, 0.580819E+00}, {-.574142E+00, 0.580351E+00}, + {-.574616E+00, 0.579880E+00}, {-.575092E+00, 0.579406E+00}, + {-.575570E+00, 0.578930E+00}, {-.576049E+00, 0.578451E+00}, + {-.576529E+00, 0.577972E+00}, {-.577010E+00, 0.577491E+00}, + {-.577491E+00, 0.577010E+00}, {-.577972E+00, 0.576529E+00}, + {-.578451E+00, 0.576049E+00}, {-.578930E+00, 0.575570E+00}, + {-.579406E+00, 0.575092E+00}, {-.579880E+00, 0.574616E+00}, + {-.580351E+00, 0.574142E+00}, {-.580819E+00, 0.573672E+00}, + {-.581283E+00, 0.573205E+00}, {-.581742E+00, 0.572742E+00}, + {-.582197E+00, 0.572283E+00}, {-.582647E+00, 0.571829E+00}, + {-.583092E+00, 0.571380E+00}, {-.583530E+00, 0.570937E+00}, + {-.583962E+00, 0.570501E+00}, {-.584387E+00, 0.570071E+00}, + {-.584804E+00, 0.569648E+00}, {-.585214E+00, 0.569232E+00}, + {-.585616E+00, 0.568825E+00}, {-.586009E+00, 0.568426E+00}, + {-.586393E+00, 0.568035E+00}, {-.586768E+00, 0.567654E+00}, + {-.587134E+00, 0.567283E+00}, {-.587489E+00, 0.566921E+00}, + {-.587834E+00, 0.566570E+00}, {-.588168E+00, 0.566229E+00}, + {-.588491E+00, 0.565900E+00}, {-.588803E+00, 0.565582E+00}, + {-.589103E+00, 0.565275E+00}, {-.589392E+00, 0.564981E+00}, + {-.589668E+00, 0.564699E+00}, {-.589931E+00, 0.564430E+00}, + {-.590182E+00, 0.564173E+00}, {-.590420E+00, 0.563930E+00}, + {-.590644E+00, 0.563700E+00}, {-.590855E+00, 0.563484E+00}, + {-.591053E+00, 0.563282E+00}, {-.591236E+00, 0.563094E+00}, + {-.591406E+00, 0.562920E+00}, {-.591561E+00, 0.562761E+00}, + {-.591702E+00, 0.562616E+00}, {-.591829E+00, 0.562487E+00}, + {-.591941E+00, 0.562372E+00}, {-.592038E+00, 0.562272E+00}, + {-.592120E+00, 0.562187E+00}, {-.592188E+00, 0.562118E+00}, + {-.592240E+00, 0.562064E+00}, {-.592278E+00, 0.562026E+00}, + {-.592301E+00, 0.562002E+00}, {-.592308E+00, 0.561995E+00}, + }, + { + {-.556782E+00, 0.597211E+00}, {-.556792E+00, 0.597201E+00}, + {-.556824E+00, 0.597171E+00}, {-.556875E+00, 0.597121E+00}, + {-.556948E+00, 0.597051E+00}, {-.557040E+00, 0.596961E+00}, + {-.557154E+00, 0.596852E+00}, {-.557287E+00, 0.596723E+00}, + {-.557441E+00, 0.596574E+00}, {-.557615E+00, 0.596406E+00}, + {-.557809E+00, 0.596219E+00}, {-.558022E+00, 0.596013E+00}, + {-.558255E+00, 0.595787E+00}, {-.558506E+00, 0.595544E+00}, + {-.558777E+00, 0.595281E+00}, {-.559066E+00, 0.595001E+00}, + {-.559374E+00, 0.594703E+00}, {-.559700E+00, 0.594387E+00}, + {-.560043E+00, 0.594054E+00}, {-.560403E+00, 0.593704E+00}, + {-.560781E+00, 0.593337E+00}, {-.561174E+00, 0.592954E+00}, + {-.561584E+00, 0.592555E+00}, {-.562010E+00, 0.592140E+00}, + {-.562451E+00, 0.591711E+00}, {-.562906E+00, 0.591266E+00}, + {-.563376E+00, 0.590808E+00}, {-.563860E+00, 0.590335E+00}, + {-.564357E+00, 0.589849E+00}, {-.564866E+00, 0.589351E+00}, + {-.565388E+00, 0.588839E+00}, {-.565922E+00, 0.588316E+00}, + {-.566467E+00, 0.587782E+00}, {-.567022E+00, 0.587236E+00}, + {-.567587E+00, 0.586681E+00}, {-.568162E+00, 0.586115E+00}, + {-.568745E+00, 0.585540E+00}, {-.569337E+00, 0.584957E+00}, + {-.569937E+00, 0.584365E+00}, {-.570543E+00, 0.583766E+00}, + {-.571156E+00, 0.583160E+00}, {-.571774E+00, 0.582547E+00}, + {-.572398E+00, 0.581929E+00}, {-.573026E+00, 0.581306E+00}, + {-.573658E+00, 0.580678E+00}, {-.574293E+00, 0.580047E+00}, + {-.574931E+00, 0.579412E+00}, {-.575570E+00, 0.578774E+00}, + {-.576211E+00, 0.578135E+00}, {-.576853E+00, 0.577494E+00}, + {-.577494E+00, 0.576853E+00}, {-.578135E+00, 0.576211E+00}, + {-.578774E+00, 0.575570E+00}, {-.579412E+00, 0.574931E+00}, + {-.580047E+00, 0.574293E+00}, {-.580678E+00, 0.573658E+00}, + {-.581306E+00, 0.573026E+00}, {-.581929E+00, 0.572398E+00}, + {-.582547E+00, 0.571774E+00}, {-.583160E+00, 0.571156E+00}, + {-.583766E+00, 0.570543E+00}, {-.584365E+00, 0.569937E+00}, + {-.584957E+00, 0.569337E+00}, {-.585540E+00, 0.568745E+00}, + {-.586115E+00, 0.568162E+00}, {-.586681E+00, 0.567587E+00}, + {-.587236E+00, 0.567022E+00}, {-.587782E+00, 0.566467E+00}, + {-.588316E+00, 0.565922E+00}, {-.588839E+00, 0.565388E+00}, + {-.589351E+00, 0.564866E+00}, {-.589849E+00, 0.564357E+00}, + {-.590335E+00, 0.563860E+00}, {-.590808E+00, 0.563376E+00}, + {-.591266E+00, 0.562906E+00}, {-.591711E+00, 0.562451E+00}, + {-.592140E+00, 0.562010E+00}, {-.592555E+00, 0.561584E+00}, + {-.592954E+00, 0.561174E+00}, {-.593337E+00, 0.560781E+00}, + {-.593704E+00, 0.560403E+00}, {-.594054E+00, 0.560043E+00}, + {-.594387E+00, 0.559700E+00}, {-.594703E+00, 0.559374E+00}, + {-.595001E+00, 0.559066E+00}, {-.595281E+00, 0.558777E+00}, + {-.595544E+00, 0.558506E+00}, {-.595787E+00, 0.558255E+00}, + {-.596013E+00, 0.558022E+00}, {-.596219E+00, 0.557809E+00}, + {-.596406E+00, 0.557615E+00}, {-.596574E+00, 0.557441E+00}, + {-.596723E+00, 0.557287E+00}, {-.596852E+00, 0.557154E+00}, + {-.596961E+00, 0.557040E+00}, {-.597051E+00, 0.556948E+00}, + {-.597121E+00, 0.556875E+00}, {-.597171E+00, 0.556824E+00}, + {-.597201E+00, 0.556792E+00}, {-.597211E+00, 0.556782E+00}, + }, + { + {-.551521E+00, 0.602074E+00}, {-.551534E+00, 0.602061E+00}, + {-.551573E+00, 0.602024E+00}, {-.551637E+00, 0.601962E+00}, + {-.551728E+00, 0.601875E+00}, {-.551845E+00, 0.601763E+00}, + {-.551987E+00, 0.601627E+00}, {-.552155E+00, 0.601466E+00}, + {-.552348E+00, 0.601281E+00}, {-.552566E+00, 0.601072E+00}, + {-.552809E+00, 0.600839E+00}, {-.553077E+00, 0.600582E+00}, + {-.553369E+00, 0.600302E+00}, {-.553685E+00, 0.599998E+00}, + {-.554025E+00, 0.599671E+00}, {-.554388E+00, 0.599322E+00}, + {-.554775E+00, 0.598951E+00}, {-.555183E+00, 0.598557E+00}, + {-.555614E+00, 0.598142E+00}, {-.556066E+00, 0.597706E+00}, + {-.556540E+00, 0.597249E+00}, {-.557034E+00, 0.596772E+00}, + {-.557548E+00, 0.596275E+00}, {-.558082E+00, 0.595758E+00}, + {-.558635E+00, 0.595223E+00}, {-.559207E+00, 0.594669E+00}, + {-.559796E+00, 0.594097E+00}, {-.560402E+00, 0.593508E+00}, + {-.561025E+00, 0.592902E+00}, {-.561664E+00, 0.592280E+00}, + {-.562319E+00, 0.591643E+00}, {-.562988E+00, 0.590990E+00}, + {-.563670E+00, 0.590323E+00}, {-.564366E+00, 0.589643E+00}, + {-.565075E+00, 0.588949E+00}, {-.565795E+00, 0.588244E+00}, + {-.566526E+00, 0.587526E+00}, {-.567267E+00, 0.586798E+00}, + {-.568018E+00, 0.586059E+00}, {-.568777E+00, 0.585311E+00}, + {-.569544E+00, 0.584554E+00}, {-.570318E+00, 0.583790E+00}, + {-.571099E+00, 0.583018E+00}, {-.571885E+00, 0.582239E+00}, + {-.572676E+00, 0.581455E+00}, {-.573471E+00, 0.580665E+00}, + {-.574269E+00, 0.579872E+00}, {-.575069E+00, 0.579075E+00}, + {-.575870E+00, 0.578276E+00}, {-.576673E+00, 0.577475E+00}, + {-.577475E+00, 0.576673E+00}, {-.578276E+00, 0.575870E+00}, + {-.579075E+00, 0.575069E+00}, {-.579872E+00, 0.574269E+00}, + {-.580665E+00, 0.573471E+00}, {-.581455E+00, 0.572676E+00}, + {-.582239E+00, 0.571885E+00}, {-.583018E+00, 0.571099E+00}, + {-.583790E+00, 0.570318E+00}, {-.584554E+00, 0.569544E+00}, + {-.585311E+00, 0.568777E+00}, {-.586059E+00, 0.568018E+00}, + {-.586798E+00, 0.567267E+00}, {-.587526E+00, 0.566526E+00}, + {-.588244E+00, 0.565795E+00}, {-.588949E+00, 0.565075E+00}, + {-.589643E+00, 0.564366E+00}, {-.590323E+00, 0.563670E+00}, + {-.590990E+00, 0.562988E+00}, {-.591643E+00, 0.562319E+00}, + {-.592280E+00, 0.561664E+00}, {-.592902E+00, 0.561025E+00}, + {-.593508E+00, 0.560402E+00}, {-.594097E+00, 0.559796E+00}, + {-.594669E+00, 0.559207E+00}, {-.595223E+00, 0.558635E+00}, + {-.595758E+00, 0.558082E+00}, {-.596275E+00, 0.557548E+00}, + {-.596772E+00, 0.557034E+00}, {-.597249E+00, 0.556540E+00}, + {-.597706E+00, 0.556066E+00}, {-.598142E+00, 0.555614E+00}, + {-.598557E+00, 0.555183E+00}, {-.598951E+00, 0.554775E+00}, + {-.599322E+00, 0.554388E+00}, {-.599671E+00, 0.554025E+00}, + {-.599998E+00, 0.553685E+00}, {-.600302E+00, 0.553369E+00}, + {-.600582E+00, 0.553077E+00}, {-.600839E+00, 0.552809E+00}, + {-.601072E+00, 0.552566E+00}, {-.601281E+00, 0.552348E+00}, + {-.601466E+00, 0.552155E+00}, {-.601627E+00, 0.551987E+00}, + {-.601763E+00, 0.551845E+00}, {-.601875E+00, 0.551728E+00}, + {-.601962E+00, 0.551637E+00}, {-.602024E+00, 0.551573E+00}, + {-.602061E+00, 0.551534E+00}, {-.602074E+00, 0.551521E+00}, + }, + { + {-.546209E+00, 0.606898E+00}, {-.546224E+00, 0.606883E+00}, + {-.546271E+00, 0.606839E+00}, {-.546350E+00, 0.606764E+00}, + {-.546459E+00, 0.606660E+00}, {-.546600E+00, 0.606527E+00}, + {-.546771E+00, 0.606364E+00}, {-.546973E+00, 0.606172E+00}, + {-.547206E+00, 0.605951E+00}, {-.547469E+00, 0.605701E+00}, + {-.547762E+00, 0.605422E+00}, {-.548085E+00, 0.605115E+00}, + {-.548437E+00, 0.604780E+00}, {-.548818E+00, 0.604417E+00}, + {-.549228E+00, 0.604026E+00}, {-.549665E+00, 0.603609E+00}, + {-.550131E+00, 0.603164E+00}, {-.550623E+00, 0.602694E+00}, + {-.551142E+00, 0.602198E+00}, {-.551687E+00, 0.601676E+00}, + {-.552258E+00, 0.601129E+00}, {-.552853E+00, 0.600558E+00}, + {-.553472E+00, 0.599963E+00}, {-.554116E+00, 0.599345E+00}, + {-.554781E+00, 0.598705E+00}, {-.555470E+00, 0.598042E+00}, + {-.556179E+00, 0.597358E+00}, {-.556909E+00, 0.596653E+00}, + {-.557659E+00, 0.595927E+00}, {-.558428E+00, 0.595183E+00}, + {-.559216E+00, 0.594419E+00}, {-.560021E+00, 0.593638E+00}, + {-.560842E+00, 0.592840E+00}, {-.561680E+00, 0.592025E+00}, + {-.562532E+00, 0.591194E+00}, {-.563398E+00, 0.590348E+00}, + {-.564277E+00, 0.589489E+00}, {-.565169E+00, 0.588616E+00}, + {-.566071E+00, 0.587731E+00}, {-.566984E+00, 0.586834E+00}, + {-.567907E+00, 0.585927E+00}, {-.568837E+00, 0.585010E+00}, + {-.569775E+00, 0.584084E+00}, {-.570720E+00, 0.583150E+00}, + {-.571670E+00, 0.582209E+00}, {-.572625E+00, 0.581262E+00}, + {-.573583E+00, 0.580310E+00}, {-.574544E+00, 0.579354E+00}, + {-.575507E+00, 0.578395E+00}, {-.576470E+00, 0.577433E+00}, + {-.577433E+00, 0.576470E+00}, {-.578395E+00, 0.575507E+00}, + {-.579354E+00, 0.574544E+00}, {-.580310E+00, 0.573583E+00}, + {-.581262E+00, 0.572625E+00}, {-.582209E+00, 0.571670E+00}, + {-.583150E+00, 0.570720E+00}, {-.584084E+00, 0.569775E+00}, + {-.585010E+00, 0.568837E+00}, {-.585927E+00, 0.567907E+00}, + {-.586834E+00, 0.566984E+00}, {-.587731E+00, 0.566071E+00}, + {-.588616E+00, 0.565169E+00}, {-.589489E+00, 0.564277E+00}, + {-.590348E+00, 0.563398E+00}, {-.591194E+00, 0.562532E+00}, + {-.592025E+00, 0.561680E+00}, {-.592840E+00, 0.560842E+00}, + {-.593638E+00, 0.560021E+00}, {-.594419E+00, 0.559216E+00}, + {-.595183E+00, 0.558428E+00}, {-.595927E+00, 0.557659E+00}, + {-.596653E+00, 0.556909E+00}, {-.597358E+00, 0.556179E+00}, + {-.598042E+00, 0.555470E+00}, {-.598705E+00, 0.554781E+00}, + {-.599345E+00, 0.554116E+00}, {-.599963E+00, 0.553472E+00}, + {-.600558E+00, 0.552853E+00}, {-.601129E+00, 0.552258E+00}, + {-.601676E+00, 0.551687E+00}, {-.602198E+00, 0.551142E+00}, + {-.602694E+00, 0.550623E+00}, {-.603164E+00, 0.550131E+00}, + {-.603609E+00, 0.549665E+00}, {-.604026E+00, 0.549228E+00}, + {-.604417E+00, 0.548818E+00}, {-.604780E+00, 0.548437E+00}, + {-.605115E+00, 0.548085E+00}, {-.605422E+00, 0.547762E+00}, + {-.605701E+00, 0.547469E+00}, {-.605951E+00, 0.547206E+00}, + {-.606172E+00, 0.546973E+00}, {-.606364E+00, 0.546771E+00}, + {-.606527E+00, 0.546600E+00}, {-.606660E+00, 0.546459E+00}, + {-.606764E+00, 0.546350E+00}, {-.606839E+00, 0.546271E+00}, + {-.606883E+00, 0.546224E+00}, {-.606898E+00, 0.546209E+00}, + }, + { + {-.540845E+00, 0.611685E+00}, {-.540863E+00, 0.611668E+00}, + {-.540918E+00, 0.611616E+00}, {-.541010E+00, 0.611529E+00}, + {-.541138E+00, 0.611409E+00}, {-.541303E+00, 0.611253E+00}, + {-.541504E+00, 0.611064E+00}, {-.541741E+00, 0.610841E+00}, + {-.542014E+00, 0.610584E+00}, {-.542323E+00, 0.610293E+00}, + {-.542666E+00, 0.609969E+00}, {-.543044E+00, 0.609612E+00}, + {-.543457E+00, 0.609223E+00}, {-.543903E+00, 0.608801E+00}, + {-.544383E+00, 0.608347E+00}, {-.544896E+00, 0.607861E+00}, + {-.545441E+00, 0.607345E+00}, {-.546018E+00, 0.606798E+00}, + {-.546626E+00, 0.606221E+00}, {-.547265E+00, 0.605614E+00}, + {-.547933E+00, 0.604978E+00}, {-.548630E+00, 0.604314E+00}, + {-.549356E+00, 0.603622E+00}, {-.550109E+00, 0.602903E+00}, + {-.550889E+00, 0.602158E+00}, {-.551694E+00, 0.601387E+00}, + {-.552525E+00, 0.600590E+00}, {-.553380E+00, 0.599770E+00}, + {-.554257E+00, 0.598926E+00}, {-.555158E+00, 0.598059E+00}, + {-.556079E+00, 0.597170E+00}, {-.557021E+00, 0.596261E+00}, + {-.557982E+00, 0.595331E+00}, {-.558962E+00, 0.594382E+00}, + {-.559958E+00, 0.593414E+00}, {-.560972E+00, 0.592429E+00}, + {-.562000E+00, 0.591428E+00}, {-.563042E+00, 0.590411E+00}, + {-.564097E+00, 0.589379E+00}, {-.565165E+00, 0.588334E+00}, + {-.566243E+00, 0.587276E+00}, {-.567330E+00, 0.586207E+00}, + {-.568427E+00, 0.585128E+00}, {-.569530E+00, 0.584039E+00}, + {-.570640E+00, 0.582942E+00}, {-.571756E+00, 0.581837E+00}, + {-.572875E+00, 0.580727E+00}, {-.573997E+00, 0.579611E+00}, + {-.575121E+00, 0.578491E+00}, {-.576245E+00, 0.577369E+00}, + {-.577369E+00, 0.576245E+00}, {-.578491E+00, 0.575121E+00}, + {-.579611E+00, 0.573997E+00}, {-.580727E+00, 0.572875E+00}, + {-.581837E+00, 0.571756E+00}, {-.582942E+00, 0.570640E+00}, + {-.584039E+00, 0.569530E+00}, {-.585128E+00, 0.568427E+00}, + {-.586207E+00, 0.567330E+00}, {-.587276E+00, 0.566243E+00}, + {-.588334E+00, 0.565165E+00}, {-.589379E+00, 0.564097E+00}, + {-.590411E+00, 0.563042E+00}, {-.591428E+00, 0.562000E+00}, + {-.592429E+00, 0.560972E+00}, {-.593414E+00, 0.559958E+00}, + {-.594382E+00, 0.558962E+00}, {-.595331E+00, 0.557982E+00}, + {-.596261E+00, 0.557021E+00}, {-.597170E+00, 0.556079E+00}, + {-.598059E+00, 0.555158E+00}, {-.598926E+00, 0.554257E+00}, + {-.599770E+00, 0.553380E+00}, {-.600590E+00, 0.552525E+00}, + {-.601387E+00, 0.551694E+00}, {-.602158E+00, 0.550889E+00}, + {-.602903E+00, 0.550109E+00}, {-.603622E+00, 0.549356E+00}, + {-.604314E+00, 0.548630E+00}, {-.604978E+00, 0.547933E+00}, + {-.605614E+00, 0.547265E+00}, {-.606221E+00, 0.546626E+00}, + {-.606798E+00, 0.546018E+00}, {-.607345E+00, 0.545441E+00}, + {-.607861E+00, 0.544896E+00}, {-.608347E+00, 0.544383E+00}, + {-.608801E+00, 0.543903E+00}, {-.609223E+00, 0.543457E+00}, + {-.609612E+00, 0.543044E+00}, {-.609969E+00, 0.542666E+00}, + {-.610293E+00, 0.542323E+00}, {-.610584E+00, 0.542014E+00}, + {-.610841E+00, 0.541741E+00}, {-.611064E+00, 0.541504E+00}, + {-.611253E+00, 0.541303E+00}, {-.611409E+00, 0.541138E+00}, + {-.611529E+00, 0.541010E+00}, {-.611616E+00, 0.540918E+00}, + {-.611668E+00, 0.540863E+00}, {-.611685E+00, 0.540845E+00}, + }, + { + {-.535428E+00, 0.616435E+00}, {-.535449E+00, 0.616415E+00}, + {-.535512E+00, 0.616356E+00}, {-.535618E+00, 0.616258E+00}, + {-.535765E+00, 0.616120E+00}, {-.535954E+00, 0.615944E+00}, + {-.536185E+00, 0.615728E+00}, {-.536458E+00, 0.615474E+00}, + {-.536771E+00, 0.615181E+00}, {-.537125E+00, 0.614850E+00}, + {-.537519E+00, 0.614481E+00}, {-.537954E+00, 0.614075E+00}, + {-.538427E+00, 0.613631E+00}, {-.538940E+00, 0.613151E+00}, + {-.539491E+00, 0.612634E+00}, {-.540079E+00, 0.612081E+00}, + {-.540705E+00, 0.611493E+00}, {-.541367E+00, 0.610870E+00}, + {-.542065E+00, 0.610212E+00}, {-.542798E+00, 0.609521E+00}, + {-.543565E+00, 0.608797E+00}, {-.544365E+00, 0.608040E+00}, + {-.545197E+00, 0.607252E+00}, {-.546061E+00, 0.606432E+00}, + {-.546955E+00, 0.605583E+00}, {-.547879E+00, 0.604704E+00}, + {-.548832E+00, 0.603796E+00}, {-.549812E+00, 0.602860E+00}, + {-.550819E+00, 0.601898E+00}, {-.551851E+00, 0.600910E+00}, + {-.552908E+00, 0.599896E+00}, {-.553987E+00, 0.598858E+00}, + {-.555089E+00, 0.597798E+00}, {-.556211E+00, 0.596715E+00}, + {-.557354E+00, 0.595611E+00}, {-.558514E+00, 0.594487E+00}, + {-.559692E+00, 0.593344E+00}, {-.560886E+00, 0.592183E+00}, + {-.562095E+00, 0.591005E+00}, {-.563317E+00, 0.589812E+00}, + {-.564552E+00, 0.588604E+00}, {-.565797E+00, 0.587383E+00}, + {-.567052E+00, 0.586150E+00}, {-.568315E+00, 0.584906E+00}, + {-.569586E+00, 0.583652E+00}, {-.570862E+00, 0.582390E+00}, + {-.572142E+00, 0.581121E+00}, {-.573426E+00, 0.579846E+00}, + {-.574712E+00, 0.578566E+00}, {-.575998E+00, 0.577283E+00}, + {-.577283E+00, 0.575998E+00}, {-.578566E+00, 0.574712E+00}, + {-.579846E+00, 0.573426E+00}, {-.581121E+00, 0.572142E+00}, + {-.582390E+00, 0.570862E+00}, {-.583652E+00, 0.569586E+00}, + {-.584906E+00, 0.568315E+00}, {-.586150E+00, 0.567052E+00}, + {-.587383E+00, 0.565797E+00}, {-.588604E+00, 0.564552E+00}, + {-.589812E+00, 0.563317E+00}, {-.591005E+00, 0.562095E+00}, + {-.592183E+00, 0.560886E+00}, {-.593344E+00, 0.559692E+00}, + {-.594487E+00, 0.558514E+00}, {-.595611E+00, 0.557354E+00}, + {-.596715E+00, 0.556211E+00}, {-.597798E+00, 0.555089E+00}, + {-.598858E+00, 0.553987E+00}, {-.599896E+00, 0.552908E+00}, + {-.600910E+00, 0.551851E+00}, {-.601898E+00, 0.550819E+00}, + {-.602860E+00, 0.549812E+00}, {-.603796E+00, 0.548832E+00}, + {-.604704E+00, 0.547879E+00}, {-.605583E+00, 0.546955E+00}, + {-.606432E+00, 0.546061E+00}, {-.607252E+00, 0.545197E+00}, + {-.608040E+00, 0.544365E+00}, {-.608797E+00, 0.543565E+00}, + {-.609521E+00, 0.542798E+00}, {-.610212E+00, 0.542065E+00}, + {-.610870E+00, 0.541367E+00}, {-.611493E+00, 0.540705E+00}, + {-.612081E+00, 0.540079E+00}, {-.612634E+00, 0.539491E+00}, + {-.613151E+00, 0.538940E+00}, {-.613631E+00, 0.538427E+00}, + {-.614075E+00, 0.537954E+00}, {-.614481E+00, 0.537519E+00}, + {-.614850E+00, 0.537125E+00}, {-.615181E+00, 0.536771E+00}, + {-.615474E+00, 0.536458E+00}, {-.615728E+00, 0.536185E+00}, + {-.615944E+00, 0.535954E+00}, {-.616120E+00, 0.535765E+00}, + {-.616258E+00, 0.535618E+00}, {-.616356E+00, 0.535512E+00}, + {-.616415E+00, 0.535449E+00}, {-.616435E+00, 0.535428E+00}, + }, + { + {-.529957E+00, 0.621149E+00}, {-.529980E+00, 0.621127E+00}, + {-.530052E+00, 0.621060E+00}, {-.530171E+00, 0.620950E+00}, + {-.530338E+00, 0.620796E+00}, {-.530552E+00, 0.620598E+00}, + {-.530813E+00, 0.620357E+00}, {-.531120E+00, 0.620072E+00}, + {-.531475E+00, 0.619744E+00}, {-.531875E+00, 0.619373E+00}, + {-.532321E+00, 0.618960E+00}, {-.532811E+00, 0.618504E+00}, + {-.533347E+00, 0.618007E+00}, {-.533926E+00, 0.617468E+00}, + {-.534549E+00, 0.616889E+00}, {-.535214E+00, 0.616269E+00}, + {-.535921E+00, 0.615609E+00}, {-.536669E+00, 0.614910E+00}, + {-.537457E+00, 0.614173E+00}, {-.538285E+00, 0.613398E+00}, + {-.539151E+00, 0.612586E+00}, {-.540055E+00, 0.611737E+00}, + {-.540995E+00, 0.610852E+00}, {-.541971E+00, 0.609933E+00}, + {-.542981E+00, 0.608980E+00}, {-.544024E+00, 0.607993E+00}, + {-.545100E+00, 0.606975E+00}, {-.546207E+00, 0.605925E+00}, + {-.547343E+00, 0.604845E+00}, {-.548508E+00, 0.603735E+00}, + {-.549700E+00, 0.602597E+00}, {-.550919E+00, 0.601432E+00}, + {-.552162E+00, 0.600241E+00}, {-.553428E+00, 0.599024E+00}, + {-.554717E+00, 0.597784E+00}, {-.556026E+00, 0.596521E+00}, + {-.557355E+00, 0.595237E+00}, {-.558701E+00, 0.593933E+00}, + {-.560064E+00, 0.592609E+00}, {-.561442E+00, 0.591268E+00}, + {-.562833E+00, 0.589910E+00}, {-.564237E+00, 0.588537E+00}, + {-.565651E+00, 0.587151E+00}, {-.567075E+00, 0.585751E+00}, + {-.568506E+00, 0.584341E+00}, {-.569944E+00, 0.582922E+00}, + {-.571386E+00, 0.581493E+00}, {-.572832E+00, 0.580059E+00}, + {-.574279E+00, 0.578618E+00}, {-.575727E+00, 0.577174E+00}, + {-.577174E+00, 0.575727E+00}, {-.578618E+00, 0.574279E+00}, + {-.580059E+00, 0.572832E+00}, {-.581493E+00, 0.571386E+00}, + {-.582922E+00, 0.569944E+00}, {-.584341E+00, 0.568506E+00}, + {-.585751E+00, 0.567075E+00}, {-.587151E+00, 0.565651E+00}, + {-.588537E+00, 0.564237E+00}, {-.589910E+00, 0.562833E+00}, + {-.591268E+00, 0.561442E+00}, {-.592609E+00, 0.560064E+00}, + {-.593933E+00, 0.558701E+00}, {-.595237E+00, 0.557355E+00}, + {-.596521E+00, 0.556026E+00}, {-.597784E+00, 0.554717E+00}, + {-.599024E+00, 0.553428E+00}, {-.600241E+00, 0.552162E+00}, + {-.601432E+00, 0.550919E+00}, {-.602597E+00, 0.549700E+00}, + {-.603735E+00, 0.548508E+00}, {-.604845E+00, 0.547343E+00}, + {-.605925E+00, 0.546207E+00}, {-.606975E+00, 0.545100E+00}, + {-.607993E+00, 0.544024E+00}, {-.608980E+00, 0.542981E+00}, + {-.609933E+00, 0.541971E+00}, {-.610852E+00, 0.540995E+00}, + {-.611737E+00, 0.540055E+00}, {-.612586E+00, 0.539151E+00}, + {-.613398E+00, 0.538285E+00}, {-.614173E+00, 0.537457E+00}, + {-.614910E+00, 0.536669E+00}, {-.615609E+00, 0.535921E+00}, + {-.616269E+00, 0.535214E+00}, {-.616889E+00, 0.534549E+00}, + {-.617468E+00, 0.533926E+00}, {-.618007E+00, 0.533347E+00}, + {-.618504E+00, 0.532811E+00}, {-.618960E+00, 0.532321E+00}, + {-.619373E+00, 0.531875E+00}, {-.619744E+00, 0.531475E+00}, + {-.620072E+00, 0.531120E+00}, {-.620357E+00, 0.530813E+00}, + {-.620598E+00, 0.530552E+00}, {-.620796E+00, 0.530338E+00}, + {-.620950E+00, 0.530171E+00}, {-.621060E+00, 0.530052E+00}, + {-.621127E+00, 0.529980E+00}, {-.621149E+00, 0.529957E+00}, + }, + { + {-.524429E+00, 0.625828E+00}, {-.524456E+00, 0.625803E+00}, + {-.524536E+00, 0.625730E+00}, {-.524669E+00, 0.625608E+00}, + {-.524855E+00, 0.625438E+00}, {-.525094E+00, 0.625219E+00}, + {-.525385E+00, 0.624951E+00}, {-.525729E+00, 0.624636E+00}, + {-.526124E+00, 0.624273E+00}, {-.526571E+00, 0.623862E+00}, + {-.527069E+00, 0.623405E+00}, {-.527617E+00, 0.622900E+00}, + {-.528214E+00, 0.622350E+00}, {-.528861E+00, 0.621753E+00}, + {-.529556E+00, 0.621111E+00}, {-.530298E+00, 0.620425E+00}, + {-.531087E+00, 0.619695E+00}, {-.531922E+00, 0.618921E+00}, + {-.532802E+00, 0.618104E+00}, {-.533726E+00, 0.617246E+00}, + {-.534692E+00, 0.616346E+00}, {-.535700E+00, 0.615405E+00}, + {-.536749E+00, 0.614425E+00}, {-.537838E+00, 0.613407E+00}, + {-.538964E+00, 0.612350E+00}, {-.540128E+00, 0.611257E+00}, + {-.541328E+00, 0.610128E+00}, {-.542562E+00, 0.608964E+00}, + {-.543829E+00, 0.607766E+00}, {-.545127E+00, 0.606536E+00}, + {-.546457E+00, 0.605274E+00}, {-.547815E+00, 0.603981E+00}, + {-.549200E+00, 0.602660E+00}, {-.550611E+00, 0.601311E+00}, + {-.552047E+00, 0.599935E+00}, {-.553506E+00, 0.598534E+00}, + {-.554986E+00, 0.597108E+00}, {-.556485E+00, 0.595660E+00}, + {-.558003E+00, 0.594191E+00}, {-.559538E+00, 0.592702E+00}, + {-.561087E+00, 0.591194E+00}, {-.562650E+00, 0.589670E+00}, + {-.564224E+00, 0.588130E+00}, {-.565808E+00, 0.586575E+00}, + {-.567401E+00, 0.585009E+00}, {-.569001E+00, 0.583431E+00}, + {-.570605E+00, 0.581844E+00}, {-.572213E+00, 0.580249E+00}, + {-.573823E+00, 0.578648E+00}, {-.575434E+00, 0.577042E+00}, + {-.577042E+00, 0.575434E+00}, {-.578648E+00, 0.573823E+00}, + {-.580249E+00, 0.572213E+00}, {-.581844E+00, 0.570605E+00}, + {-.583431E+00, 0.569001E+00}, {-.585009E+00, 0.567401E+00}, + {-.586575E+00, 0.565808E+00}, {-.588130E+00, 0.564224E+00}, + {-.589670E+00, 0.562650E+00}, {-.591194E+00, 0.561087E+00}, + {-.592702E+00, 0.559538E+00}, {-.594191E+00, 0.558003E+00}, + {-.595660E+00, 0.556485E+00}, {-.597108E+00, 0.554986E+00}, + {-.598534E+00, 0.553506E+00}, {-.599935E+00, 0.552047E+00}, + {-.601311E+00, 0.550611E+00}, {-.602660E+00, 0.549200E+00}, + {-.603981E+00, 0.547815E+00}, {-.605274E+00, 0.546457E+00}, + {-.606536E+00, 0.545127E+00}, {-.607766E+00, 0.543829E+00}, + {-.608964E+00, 0.542562E+00}, {-.610128E+00, 0.541328E+00}, + {-.611257E+00, 0.540128E+00}, {-.612350E+00, 0.538964E+00}, + {-.613407E+00, 0.537838E+00}, {-.614425E+00, 0.536749E+00}, + {-.615405E+00, 0.535700E+00}, {-.616346E+00, 0.534692E+00}, + {-.617246E+00, 0.533726E+00}, {-.618104E+00, 0.532802E+00}, + {-.618921E+00, 0.531922E+00}, {-.619695E+00, 0.531087E+00}, + {-.620425E+00, 0.530298E+00}, {-.621111E+00, 0.529556E+00}, + {-.621753E+00, 0.528861E+00}, {-.622350E+00, 0.528214E+00}, + {-.622900E+00, 0.527617E+00}, {-.623405E+00, 0.527069E+00}, + {-.623862E+00, 0.526571E+00}, {-.624273E+00, 0.526124E+00}, + {-.624636E+00, 0.525729E+00}, {-.624951E+00, 0.525385E+00}, + {-.625219E+00, 0.525094E+00}, {-.625438E+00, 0.524855E+00}, + {-.625608E+00, 0.524669E+00}, {-.625730E+00, 0.524536E+00}, + {-.625803E+00, 0.524456E+00}, {-.625828E+00, 0.524429E+00}, + }, + { + {-.518844E+00, 0.630473E+00}, {-.518874E+00, 0.630446E+00}, + {-.518962E+00, 0.630366E+00}, {-.519109E+00, 0.630233E+00}, + {-.519315E+00, 0.630046E+00}, {-.519579E+00, 0.629806E+00}, + {-.519901E+00, 0.629513E+00}, {-.520281E+00, 0.629167E+00}, + {-.520718E+00, 0.628769E+00}, {-.521212E+00, 0.628319E+00}, + {-.521762E+00, 0.627817E+00}, {-.522368E+00, 0.627265E+00}, + {-.523028E+00, 0.626661E+00}, {-.523743E+00, 0.626007E+00}, + {-.524511E+00, 0.625304E+00}, {-.525331E+00, 0.624551E+00}, + {-.526203E+00, 0.623750E+00}, {-.527126E+00, 0.622902E+00}, + {-.528098E+00, 0.622006E+00}, {-.529118E+00, 0.621064E+00}, + {-.530186E+00, 0.620077E+00}, {-.531299E+00, 0.619046E+00}, + {-.532458E+00, 0.617971E+00}, {-.533660E+00, 0.616853E+00}, + {-.534904E+00, 0.615694E+00}, {-.536189E+00, 0.614494E+00}, + {-.537514E+00, 0.613255E+00}, {-.538876E+00, 0.611978E+00}, + {-.540275E+00, 0.610663E+00}, {-.541708E+00, 0.609312E+00}, + {-.543175E+00, 0.607927E+00}, {-.544674E+00, 0.606508E+00}, + {-.546203E+00, 0.605056E+00}, {-.547760E+00, 0.603574E+00}, + {-.549344E+00, 0.602063E+00}, {-.550953E+00, 0.600523E+00}, + {-.552585E+00, 0.598957E+00}, {-.554239E+00, 0.597366E+00}, + {-.555912E+00, 0.595751E+00}, {-.557604E+00, 0.594114E+00}, + {-.559312E+00, 0.592457E+00}, {-.561034E+00, 0.590781E+00}, + {-.562769E+00, 0.589087E+00}, {-.564515E+00, 0.587378E+00}, + {-.566270E+00, 0.585654E+00}, {-.568032E+00, 0.583919E+00}, + {-.569800E+00, 0.582173E+00}, {-.571571E+00, 0.580418E+00}, + {-.573344E+00, 0.578656E+00}, {-.575117E+00, 0.576888E+00}, + {-.576888E+00, 0.575117E+00}, {-.578656E+00, 0.573344E+00}, + {-.580418E+00, 0.571571E+00}, {-.582173E+00, 0.569800E+00}, + {-.583919E+00, 0.568032E+00}, {-.585654E+00, 0.566270E+00}, + {-.587378E+00, 0.564515E+00}, {-.589087E+00, 0.562769E+00}, + {-.590781E+00, 0.561034E+00}, {-.592457E+00, 0.559312E+00}, + {-.594114E+00, 0.557604E+00}, {-.595751E+00, 0.555912E+00}, + {-.597366E+00, 0.554239E+00}, {-.598957E+00, 0.552585E+00}, + {-.600523E+00, 0.550953E+00}, {-.602063E+00, 0.549344E+00}, + {-.603574E+00, 0.547760E+00}, {-.605056E+00, 0.546203E+00}, + {-.606508E+00, 0.544674E+00}, {-.607927E+00, 0.543175E+00}, + {-.609312E+00, 0.541708E+00}, {-.610663E+00, 0.540275E+00}, + {-.611978E+00, 0.538876E+00}, {-.613255E+00, 0.537514E+00}, + {-.614494E+00, 0.536189E+00}, {-.615694E+00, 0.534904E+00}, + {-.616853E+00, 0.533660E+00}, {-.617971E+00, 0.532458E+00}, + {-.619046E+00, 0.531299E+00}, {-.620077E+00, 0.530186E+00}, + {-.621064E+00, 0.529118E+00}, {-.622006E+00, 0.528098E+00}, + {-.622902E+00, 0.527126E+00}, {-.623750E+00, 0.526203E+00}, + {-.624551E+00, 0.525331E+00}, {-.625304E+00, 0.524511E+00}, + {-.626007E+00, 0.523743E+00}, {-.626661E+00, 0.523028E+00}, + {-.627265E+00, 0.522368E+00}, {-.627817E+00, 0.521762E+00}, + {-.628319E+00, 0.521212E+00}, {-.628769E+00, 0.520718E+00}, + {-.629167E+00, 0.520281E+00}, {-.629513E+00, 0.519901E+00}, + {-.629806E+00, 0.519579E+00}, {-.630046E+00, 0.519315E+00}, + {-.630233E+00, 0.519109E+00}, {-.630366E+00, 0.518962E+00}, + {-.630446E+00, 0.518874E+00}, {-.630473E+00, 0.518844E+00}, + }, + { + {-.513200E+00, 0.635086E+00}, {-.513232E+00, 0.635056E+00}, + {-.513329E+00, 0.634969E+00}, {-.513490E+00, 0.634824E+00}, + {-.513716E+00, 0.634621E+00}, {-.514006E+00, 0.634360E+00}, + {-.514359E+00, 0.634042E+00}, {-.514775E+00, 0.633666E+00}, + {-.515254E+00, 0.633233E+00}, {-.515796E+00, 0.632744E+00}, + {-.516399E+00, 0.632199E+00}, {-.517063E+00, 0.631598E+00}, + {-.517787E+00, 0.630942E+00}, {-.518570E+00, 0.630231E+00}, + {-.519412E+00, 0.629466E+00}, {-.520311E+00, 0.628647E+00}, + {-.521267E+00, 0.627777E+00}, {-.522278E+00, 0.626854E+00}, + {-.523343E+00, 0.625880E+00}, {-.524461E+00, 0.624855E+00}, + {-.525631E+00, 0.623782E+00}, {-.526851E+00, 0.622659E+00}, + {-.528120E+00, 0.621490E+00}, {-.529437E+00, 0.620274E+00}, + {-.530800E+00, 0.619012E+00}, {-.532207E+00, 0.617707E+00}, + {-.533657E+00, 0.616358E+00}, {-.535149E+00, 0.614967E+00}, + {-.536680E+00, 0.613536E+00}, {-.538250E+00, 0.612065E+00}, + {-.539856E+00, 0.610556E+00}, {-.541496E+00, 0.609011E+00}, + {-.543169E+00, 0.607430E+00}, {-.544873E+00, 0.605816E+00}, + {-.546606E+00, 0.604169E+00}, {-.548366E+00, 0.602491E+00}, + {-.550152E+00, 0.600785E+00}, {-.551961E+00, 0.599050E+00}, + {-.553791E+00, 0.597290E+00}, {-.555641E+00, 0.595506E+00}, + {-.557508E+00, 0.593698E+00}, {-.559391E+00, 0.591870E+00}, + {-.561288E+00, 0.590023E+00}, {-.563196E+00, 0.588159E+00}, + {-.565113E+00, 0.586279E+00}, {-.567039E+00, 0.584385E+00}, + {-.568970E+00, 0.582479E+00}, {-.570905E+00, 0.580564E+00}, + {-.572841E+00, 0.578640E+00}, {-.574777E+00, 0.576711E+00}, + {-.576711E+00, 0.574777E+00}, {-.578640E+00, 0.572841E+00}, + {-.580564E+00, 0.570905E+00}, {-.582479E+00, 0.568970E+00}, + {-.584385E+00, 0.567039E+00}, {-.586279E+00, 0.565113E+00}, + {-.588159E+00, 0.563196E+00}, {-.590023E+00, 0.561288E+00}, + {-.591870E+00, 0.559391E+00}, {-.593698E+00, 0.557508E+00}, + {-.595506E+00, 0.555641E+00}, {-.597290E+00, 0.553791E+00}, + {-.599050E+00, 0.551961E+00}, {-.600785E+00, 0.550152E+00}, + {-.602491E+00, 0.548366E+00}, {-.604169E+00, 0.546606E+00}, + {-.605816E+00, 0.544873E+00}, {-.607430E+00, 0.543169E+00}, + {-.609011E+00, 0.541496E+00}, {-.610556E+00, 0.539856E+00}, + {-.612065E+00, 0.538250E+00}, {-.613536E+00, 0.536680E+00}, + {-.614967E+00, 0.535149E+00}, {-.616358E+00, 0.533657E+00}, + {-.617707E+00, 0.532207E+00}, {-.619012E+00, 0.530800E+00}, + {-.620274E+00, 0.529437E+00}, {-.621490E+00, 0.528120E+00}, + {-.622659E+00, 0.526851E+00}, {-.623782E+00, 0.525631E+00}, + {-.624855E+00, 0.524461E+00}, {-.625880E+00, 0.523343E+00}, + {-.626854E+00, 0.522278E+00}, {-.627777E+00, 0.521267E+00}, + {-.628647E+00, 0.520311E+00}, {-.629466E+00, 0.519412E+00}, + {-.630231E+00, 0.518570E+00}, {-.630942E+00, 0.517787E+00}, + {-.631598E+00, 0.517063E+00}, {-.632199E+00, 0.516399E+00}, + {-.632744E+00, 0.515796E+00}, {-.633233E+00, 0.515254E+00}, + {-.633666E+00, 0.514775E+00}, {-.634042E+00, 0.514359E+00}, + {-.634360E+00, 0.514006E+00}, {-.634621E+00, 0.513716E+00}, + {-.634824E+00, 0.513490E+00}, {-.634969E+00, 0.513329E+00}, + {-.635056E+00, 0.513232E+00}, {-.635086E+00, 0.513200E+00}, + }, + { + {-.507495E+00, 0.639666E+00}, {-.507530E+00, 0.639634E+00}, + {-.507636E+00, 0.639540E+00}, {-.507811E+00, 0.639383E+00}, + {-.508057E+00, 0.639164E+00}, {-.508372E+00, 0.638883E+00}, + {-.508757E+00, 0.638539E+00}, {-.509210E+00, 0.638133E+00}, + {-.509732E+00, 0.637666E+00}, {-.510322E+00, 0.637138E+00}, + {-.510978E+00, 0.636550E+00}, {-.511701E+00, 0.635901E+00}, + {-.512489E+00, 0.635192E+00}, {-.513342E+00, 0.634425E+00}, + {-.514258E+00, 0.633599E+00}, {-.515237E+00, 0.632715E+00}, + {-.516277E+00, 0.631774E+00}, {-.517377E+00, 0.630778E+00}, + {-.518537E+00, 0.629726E+00}, {-.519753E+00, 0.628619E+00}, + {-.521026E+00, 0.627459E+00}, {-.522354E+00, 0.626247E+00}, + {-.523734E+00, 0.624983E+00}, {-.525167E+00, 0.623669E+00}, + {-.526649E+00, 0.622305E+00}, {-.528180E+00, 0.620894E+00}, + {-.529757E+00, 0.619436E+00}, {-.531379E+00, 0.617933E+00}, + {-.533045E+00, 0.616385E+00}, {-.534751E+00, 0.614795E+00}, + {-.536497E+00, 0.613163E+00}, {-.538280E+00, 0.611491E+00}, + {-.540098E+00, 0.609782E+00}, {-.541950E+00, 0.608035E+00}, + {-.543833E+00, 0.606253E+00}, {-.545745E+00, 0.604438E+00}, + {-.547685E+00, 0.602591E+00}, {-.549650E+00, 0.600713E+00}, + {-.551638E+00, 0.598808E+00}, {-.553647E+00, 0.596876E+00}, + {-.555675E+00, 0.594919E+00}, {-.557719E+00, 0.592939E+00}, + {-.559778E+00, 0.590938E+00}, {-.561849E+00, 0.588918E+00}, + {-.563930E+00, 0.586881E+00}, {-.566019E+00, 0.584829E+00}, + {-.568114E+00, 0.582764E+00}, {-.570213E+00, 0.580688E+00}, + {-.572314E+00, 0.578602E+00}, {-.574413E+00, 0.576510E+00}, + {-.576510E+00, 0.574413E+00}, {-.578602E+00, 0.572314E+00}, + {-.580688E+00, 0.570213E+00}, {-.582764E+00, 0.568114E+00}, + {-.584829E+00, 0.566019E+00}, {-.586881E+00, 0.563930E+00}, + {-.588918E+00, 0.561849E+00}, {-.590938E+00, 0.559778E+00}, + {-.592939E+00, 0.557719E+00}, {-.594919E+00, 0.555675E+00}, + {-.596876E+00, 0.553647E+00}, {-.598808E+00, 0.551638E+00}, + {-.600713E+00, 0.549650E+00}, {-.602591E+00, 0.547685E+00}, + {-.604438E+00, 0.545745E+00}, {-.606253E+00, 0.543833E+00}, + {-.608035E+00, 0.541950E+00}, {-.609782E+00, 0.540098E+00}, + {-.611491E+00, 0.538280E+00}, {-.613163E+00, 0.536497E+00}, + {-.614795E+00, 0.534751E+00}, {-.616385E+00, 0.533045E+00}, + {-.617933E+00, 0.531379E+00}, {-.619436E+00, 0.529757E+00}, + {-.620894E+00, 0.528180E+00}, {-.622305E+00, 0.526649E+00}, + {-.623669E+00, 0.525167E+00}, {-.624983E+00, 0.523734E+00}, + {-.626247E+00, 0.522354E+00}, {-.627459E+00, 0.521026E+00}, + {-.628619E+00, 0.519753E+00}, {-.629726E+00, 0.518537E+00}, + {-.630778E+00, 0.517377E+00}, {-.631774E+00, 0.516277E+00}, + {-.632715E+00, 0.515237E+00}, {-.633599E+00, 0.514258E+00}, + {-.634425E+00, 0.513342E+00}, {-.635192E+00, 0.512489E+00}, + {-.635901E+00, 0.511701E+00}, {-.636550E+00, 0.510978E+00}, + {-.637138E+00, 0.510322E+00}, {-.637666E+00, 0.509732E+00}, + {-.638133E+00, 0.509210E+00}, {-.638539E+00, 0.508757E+00}, + {-.638883E+00, 0.508372E+00}, {-.639164E+00, 0.508057E+00}, + {-.639383E+00, 0.507811E+00}, {-.639540E+00, 0.507636E+00}, + {-.639634E+00, 0.507530E+00}, {-.639666E+00, 0.507495E+00}, + }, + { + {-.501728E+00, 0.644215E+00}, {-.501766E+00, 0.644181E+00}, + {-.501880E+00, 0.644080E+00}, {-.502070E+00, 0.643912E+00}, + {-.502336E+00, 0.643677E+00}, {-.502677E+00, 0.643374E+00}, + {-.503094E+00, 0.643006E+00}, {-.503585E+00, 0.642570E+00}, + {-.504149E+00, 0.642069E+00}, {-.504788E+00, 0.641503E+00}, + {-.505498E+00, 0.640871E+00}, {-.506280E+00, 0.640174E+00}, + {-.507134E+00, 0.639414E+00}, {-.508056E+00, 0.638590E+00}, + {-.509048E+00, 0.637703E+00}, {-.510107E+00, 0.636754E+00}, + {-.511233E+00, 0.635745E+00}, {-.512423E+00, 0.634674E+00}, + {-.513677E+00, 0.633545E+00}, {-.514993E+00, 0.632356E+00}, + {-.516370E+00, 0.631111E+00}, {-.517806E+00, 0.629808E+00}, + {-.519300E+00, 0.628451E+00}, {-.520849E+00, 0.627039E+00}, + {-.522452E+00, 0.625574E+00}, {-.524107E+00, 0.624058E+00}, + {-.525812E+00, 0.622491E+00}, {-.527566E+00, 0.620875E+00}, + {-.529366E+00, 0.619211E+00}, {-.531210E+00, 0.617502E+00}, + {-.533097E+00, 0.615747E+00}, {-.535024E+00, 0.613950E+00}, + {-.536989E+00, 0.612111E+00}, {-.538989E+00, 0.610233E+00}, + {-.541024E+00, 0.608316E+00}, {-.543090E+00, 0.606363E+00}, + {-.545185E+00, 0.604376E+00}, {-.547306E+00, 0.602355E+00}, + {-.549453E+00, 0.600304E+00}, {-.551622E+00, 0.598225E+00}, + {-.553811E+00, 0.596118E+00}, {-.556017E+00, 0.593986E+00}, + {-.558239E+00, 0.591832E+00}, {-.560474E+00, 0.589656E+00}, + {-.562720E+00, 0.587462E+00}, {-.564973E+00, 0.585251E+00}, + {-.567233E+00, 0.583026E+00}, {-.569497E+00, 0.580789E+00}, + {-.571762E+00, 0.578542E+00}, {-.574026E+00, 0.576286E+00}, + {-.576286E+00, 0.574026E+00}, {-.578542E+00, 0.571762E+00}, + {-.580789E+00, 0.569497E+00}, {-.583026E+00, 0.567233E+00}, + {-.585251E+00, 0.564973E+00}, {-.587462E+00, 0.562720E+00}, + {-.589656E+00, 0.560474E+00}, {-.591832E+00, 0.558239E+00}, + {-.593986E+00, 0.556017E+00}, {-.596118E+00, 0.553811E+00}, + {-.598225E+00, 0.551622E+00}, {-.600304E+00, 0.549453E+00}, + {-.602355E+00, 0.547306E+00}, {-.604376E+00, 0.545185E+00}, + {-.606363E+00, 0.543090E+00}, {-.608316E+00, 0.541024E+00}, + {-.610233E+00, 0.538989E+00}, {-.612111E+00, 0.536989E+00}, + {-.613950E+00, 0.535024E+00}, {-.615747E+00, 0.533097E+00}, + {-.617502E+00, 0.531210E+00}, {-.619211E+00, 0.529366E+00}, + {-.620875E+00, 0.527566E+00}, {-.622491E+00, 0.525812E+00}, + {-.624058E+00, 0.524107E+00}, {-.625574E+00, 0.522452E+00}, + {-.627039E+00, 0.520849E+00}, {-.628451E+00, 0.519300E+00}, + {-.629808E+00, 0.517806E+00}, {-.631111E+00, 0.516370E+00}, + {-.632356E+00, 0.514993E+00}, {-.633545E+00, 0.513677E+00}, + {-.634674E+00, 0.512423E+00}, {-.635745E+00, 0.511233E+00}, + {-.636754E+00, 0.510107E+00}, {-.637703E+00, 0.509048E+00}, + {-.638590E+00, 0.508056E+00}, {-.639414E+00, 0.507134E+00}, + {-.640174E+00, 0.506280E+00}, {-.640871E+00, 0.505498E+00}, + {-.641503E+00, 0.504788E+00}, {-.642069E+00, 0.504149E+00}, + {-.642570E+00, 0.503585E+00}, {-.643006E+00, 0.503094E+00}, + {-.643374E+00, 0.502677E+00}, {-.643677E+00, 0.502336E+00}, + {-.643912E+00, 0.502070E+00}, {-.644080E+00, 0.501880E+00}, + {-.644181E+00, 0.501766E+00}, {-.644215E+00, 0.501728E+00}, + }, + { + {-.495897E+00, 0.648733E+00}, {-.495938E+00, 0.648697E+00}, + {-.496061E+00, 0.648589E+00}, {-.496265E+00, 0.648410E+00}, + {-.496552E+00, 0.648159E+00}, {-.496919E+00, 0.647836E+00}, + {-.497368E+00, 0.647442E+00}, {-.497896E+00, 0.646978E+00}, + {-.498505E+00, 0.646443E+00}, {-.499192E+00, 0.645837E+00}, + {-.499957E+00, 0.645163E+00}, {-.500800E+00, 0.644419E+00}, + {-.501718E+00, 0.643607E+00}, {-.502712E+00, 0.642727E+00}, + {-.503780E+00, 0.641780E+00}, {-.504920E+00, 0.640767E+00}, + {-.506132E+00, 0.639688E+00}, {-.507413E+00, 0.638544E+00}, + {-.508763E+00, 0.637337E+00}, {-.510180E+00, 0.636068E+00}, + {-.511662E+00, 0.634737E+00}, {-.513207E+00, 0.633345E+00}, + {-.514814E+00, 0.631894E+00}, {-.516481E+00, 0.630385E+00}, + {-.518206E+00, 0.628819E+00}, {-.519987E+00, 0.627198E+00}, + {-.521821E+00, 0.625522E+00}, {-.523708E+00, 0.623794E+00}, + {-.525644E+00, 0.622015E+00}, {-.527627E+00, 0.620187E+00}, + {-.529656E+00, 0.618310E+00}, {-.531728E+00, 0.616387E+00}, + {-.533840E+00, 0.614420E+00}, {-.535991E+00, 0.612409E+00}, + {-.538178E+00, 0.610358E+00}, {-.540398E+00, 0.608267E+00}, + {-.542649E+00, 0.606140E+00}, {-.544929E+00, 0.603977E+00}, + {-.547235E+00, 0.601780E+00}, {-.549565E+00, 0.599553E+00}, + {-.551916E+00, 0.597296E+00}, {-.554286E+00, 0.595012E+00}, + {-.556671E+00, 0.592704E+00}, {-.559071E+00, 0.590373E+00}, + {-.561482E+00, 0.588021E+00}, {-.563901E+00, 0.585652E+00}, + {-.566326E+00, 0.583266E+00}, {-.568755E+00, 0.580867E+00}, + {-.571185E+00, 0.578457E+00}, {-.573614E+00, 0.576039E+00}, + {-.576039E+00, 0.573614E+00}, {-.578457E+00, 0.571185E+00}, + {-.580867E+00, 0.568755E+00}, {-.583266E+00, 0.566326E+00}, + {-.585652E+00, 0.563901E+00}, {-.588021E+00, 0.561482E+00}, + {-.590373E+00, 0.559071E+00}, {-.592704E+00, 0.556671E+00}, + {-.595012E+00, 0.554286E+00}, {-.597296E+00, 0.551916E+00}, + {-.599553E+00, 0.549565E+00}, {-.601780E+00, 0.547235E+00}, + {-.603977E+00, 0.544929E+00}, {-.606140E+00, 0.542649E+00}, + {-.608267E+00, 0.540398E+00}, {-.610358E+00, 0.538178E+00}, + {-.612409E+00, 0.535991E+00}, {-.614420E+00, 0.533840E+00}, + {-.616387E+00, 0.531728E+00}, {-.618310E+00, 0.529656E+00}, + {-.620187E+00, 0.527627E+00}, {-.622015E+00, 0.525644E+00}, + {-.623794E+00, 0.523708E+00}, {-.625522E+00, 0.521821E+00}, + {-.627198E+00, 0.519987E+00}, {-.628819E+00, 0.518206E+00}, + {-.630385E+00, 0.516481E+00}, {-.631894E+00, 0.514814E+00}, + {-.633345E+00, 0.513207E+00}, {-.634737E+00, 0.511662E+00}, + {-.636068E+00, 0.510180E+00}, {-.637337E+00, 0.508763E+00}, + {-.638544E+00, 0.507413E+00}, {-.639688E+00, 0.506132E+00}, + {-.640767E+00, 0.504920E+00}, {-.641780E+00, 0.503780E+00}, + {-.642727E+00, 0.502712E+00}, {-.643607E+00, 0.501718E+00}, + {-.644419E+00, 0.500800E+00}, {-.645163E+00, 0.499957E+00}, + {-.645837E+00, 0.499192E+00}, {-.646443E+00, 0.498505E+00}, + {-.646978E+00, 0.497896E+00}, {-.647442E+00, 0.497368E+00}, + {-.647836E+00, 0.496919E+00}, {-.648159E+00, 0.496552E+00}, + {-.648410E+00, 0.496265E+00}, {-.648589E+00, 0.496061E+00}, + {-.648697E+00, 0.495938E+00}, {-.648733E+00, 0.495897E+00}, + }, + { + {-.489999E+00, 0.653222E+00}, {-.490043E+00, 0.653184E+00}, + {-.490175E+00, 0.653069E+00}, {-.490395E+00, 0.652879E+00}, + {-.490702E+00, 0.652612E+00}, {-.491096E+00, 0.652269E+00}, + {-.491577E+00, 0.651850E+00}, {-.492144E+00, 0.651356E+00}, + {-.492796E+00, 0.650787E+00}, {-.493533E+00, 0.650144E+00}, + {-.494353E+00, 0.649427E+00}, {-.495257E+00, 0.648636E+00}, + {-.496242E+00, 0.647772E+00}, {-.497307E+00, 0.646837E+00}, + {-.498451E+00, 0.645830E+00}, {-.499674E+00, 0.644752E+00}, + {-.500973E+00, 0.643605E+00}, {-.502346E+00, 0.642389E+00}, + {-.503793E+00, 0.641105E+00}, {-.505311E+00, 0.639754E+00}, + {-.506899E+00, 0.638338E+00}, {-.508555E+00, 0.636857E+00}, + {-.510277E+00, 0.635313E+00}, {-.512063E+00, 0.633707E+00}, + {-.513911E+00, 0.632041E+00}, {-.515818E+00, 0.630315E+00}, + {-.517783E+00, 0.628531E+00}, {-.519803E+00, 0.626692E+00}, + {-.521877E+00, 0.624797E+00}, {-.524000E+00, 0.622850E+00}, + {-.526172E+00, 0.620851E+00}, {-.528390E+00, 0.618803E+00}, + {-.530651E+00, 0.616707E+00}, {-.532953E+00, 0.614565E+00}, + {-.535293E+00, 0.612379E+00}, {-.537669E+00, 0.610151E+00}, + {-.540078E+00, 0.607883E+00}, {-.542517E+00, 0.605577E+00}, + {-.544984E+00, 0.603235E+00}, {-.547475E+00, 0.600860E+00}, + {-.549989E+00, 0.598454E+00}, {-.552523E+00, 0.596018E+00}, + {-.555074E+00, 0.593555E+00}, {-.557639E+00, 0.591068E+00}, + {-.560216E+00, 0.588559E+00}, {-.562801E+00, 0.586030E+00}, + {-.565393E+00, 0.583484E+00}, {-.567988E+00, 0.580923E+00}, + {-.570584E+00, 0.578350E+00}, {-.573178E+00, 0.575767E+00}, + {-.575767E+00, 0.573178E+00}, {-.578350E+00, 0.570584E+00}, + {-.580923E+00, 0.567988E+00}, {-.583484E+00, 0.565393E+00}, + {-.586030E+00, 0.562801E+00}, {-.588559E+00, 0.560216E+00}, + {-.591068E+00, 0.557639E+00}, {-.593555E+00, 0.555074E+00}, + {-.596018E+00, 0.552523E+00}, {-.598454E+00, 0.549989E+00}, + {-.600860E+00, 0.547475E+00}, {-.603235E+00, 0.544984E+00}, + {-.605577E+00, 0.542517E+00}, {-.607883E+00, 0.540078E+00}, + {-.610151E+00, 0.537669E+00}, {-.612379E+00, 0.535293E+00}, + {-.614565E+00, 0.532953E+00}, {-.616707E+00, 0.530651E+00}, + {-.618803E+00, 0.528390E+00}, {-.620851E+00, 0.526172E+00}, + {-.622850E+00, 0.524000E+00}, {-.624797E+00, 0.521877E+00}, + {-.626692E+00, 0.519803E+00}, {-.628531E+00, 0.517783E+00}, + {-.630315E+00, 0.515818E+00}, {-.632041E+00, 0.513911E+00}, + {-.633707E+00, 0.512063E+00}, {-.635313E+00, 0.510277E+00}, + {-.636857E+00, 0.508555E+00}, {-.638338E+00, 0.506899E+00}, + {-.639754E+00, 0.505311E+00}, {-.641105E+00, 0.503793E+00}, + {-.642389E+00, 0.502346E+00}, {-.643605E+00, 0.500973E+00}, + {-.644752E+00, 0.499674E+00}, {-.645830E+00, 0.498451E+00}, + {-.646837E+00, 0.497307E+00}, {-.647772E+00, 0.496242E+00}, + {-.648636E+00, 0.495257E+00}, {-.649427E+00, 0.494353E+00}, + {-.650144E+00, 0.493533E+00}, {-.650787E+00, 0.492796E+00}, + {-.651356E+00, 0.492144E+00}, {-.651850E+00, 0.491577E+00}, + {-.652269E+00, 0.491096E+00}, {-.652612E+00, 0.490702E+00}, + {-.652879E+00, 0.490395E+00}, {-.653069E+00, 0.490175E+00}, + {-.653184E+00, 0.490043E+00}, {-.653222E+00, 0.489999E+00}, + }, + { + {-.484034E+00, 0.657682E+00}, {-.484081E+00, 0.657642E+00}, + {-.484222E+00, 0.657521E+00}, {-.484457E+00, 0.657319E+00}, + {-.484785E+00, 0.657036E+00}, {-.485206E+00, 0.656673E+00}, + {-.485720E+00, 0.656230E+00}, {-.486325E+00, 0.655707E+00}, + {-.487022E+00, 0.655104E+00}, {-.487809E+00, 0.654423E+00}, + {-.488685E+00, 0.653663E+00}, {-.489650E+00, 0.652826E+00}, + {-.490702E+00, 0.651911E+00}, {-.491840E+00, 0.650920E+00}, + {-.493062E+00, 0.649853E+00}, {-.494367E+00, 0.648712E+00}, + {-.495754E+00, 0.647496E+00}, {-.497221E+00, 0.646208E+00}, + {-.498765E+00, 0.644847E+00}, {-.500386E+00, 0.643416E+00}, + {-.502081E+00, 0.641915E+00}, {-.503849E+00, 0.640346E+00}, + {-.505687E+00, 0.638709E+00}, {-.507593E+00, 0.637006E+00}, + {-.509564E+00, 0.635239E+00}, {-.511600E+00, 0.633410E+00}, + {-.513696E+00, 0.631518E+00}, {-.515852E+00, 0.629567E+00}, + {-.518063E+00, 0.627557E+00}, {-.520329E+00, 0.625492E+00}, + {-.522645E+00, 0.623371E+00}, {-.525010E+00, 0.621197E+00}, + {-.527421E+00, 0.618973E+00}, {-.529876E+00, 0.616700E+00}, + {-.532370E+00, 0.614379E+00}, {-.534903E+00, 0.612014E+00}, + {-.537470E+00, 0.609606E+00}, {-.540069E+00, 0.607157E+00}, + {-.542698E+00, 0.604670E+00}, {-.545352E+00, 0.602147E+00}, + {-.548031E+00, 0.599590E+00}, {-.550730E+00, 0.597002E+00}, + {-.553446E+00, 0.594385E+00}, {-.556178E+00, 0.591742E+00}, + {-.558921E+00, 0.589075E+00}, {-.561673E+00, 0.586386E+00}, + {-.564432E+00, 0.583679E+00}, {-.567194E+00, 0.580956E+00}, + {-.569957E+00, 0.578219E+00}, {-.572717E+00, 0.575472E+00}, + {-.575472E+00, 0.572717E+00}, {-.578219E+00, 0.569957E+00}, + {-.580956E+00, 0.567194E+00}, {-.583679E+00, 0.564432E+00}, + {-.586386E+00, 0.561673E+00}, {-.589075E+00, 0.558921E+00}, + {-.591742E+00, 0.556178E+00}, {-.594385E+00, 0.553446E+00}, + {-.597002E+00, 0.550730E+00}, {-.599590E+00, 0.548031E+00}, + {-.602147E+00, 0.545352E+00}, {-.604670E+00, 0.542698E+00}, + {-.607157E+00, 0.540069E+00}, {-.609606E+00, 0.537470E+00}, + {-.612014E+00, 0.534903E+00}, {-.614379E+00, 0.532370E+00}, + {-.616700E+00, 0.529876E+00}, {-.618973E+00, 0.527421E+00}, + {-.621197E+00, 0.525010E+00}, {-.623371E+00, 0.522645E+00}, + {-.625492E+00, 0.520329E+00}, {-.627557E+00, 0.518063E+00}, + {-.629567E+00, 0.515852E+00}, {-.631518E+00, 0.513696E+00}, + {-.633410E+00, 0.511600E+00}, {-.635239E+00, 0.509564E+00}, + {-.637006E+00, 0.507593E+00}, {-.638709E+00, 0.505687E+00}, + {-.640346E+00, 0.503849E+00}, {-.641915E+00, 0.502081E+00}, + {-.643416E+00, 0.500386E+00}, {-.644847E+00, 0.498765E+00}, + {-.646208E+00, 0.497221E+00}, {-.647496E+00, 0.495754E+00}, + {-.648712E+00, 0.494367E+00}, {-.649853E+00, 0.493062E+00}, + {-.650920E+00, 0.491840E+00}, {-.651911E+00, 0.490702E+00}, + {-.652826E+00, 0.489650E+00}, {-.653663E+00, 0.488685E+00}, + {-.654423E+00, 0.487809E+00}, {-.655104E+00, 0.487022E+00}, + {-.655707E+00, 0.486325E+00}, {-.656230E+00, 0.485720E+00}, + {-.656673E+00, 0.485206E+00}, {-.657036E+00, 0.484785E+00}, + {-.657319E+00, 0.484457E+00}, {-.657521E+00, 0.484222E+00}, + {-.657642E+00, 0.484081E+00}, {-.657682E+00, 0.484034E+00}, + }, + { + {-.478000E+00, 0.662115E+00}, {-.478050E+00, 0.662072E+00}, + {-.478200E+00, 0.661944E+00}, {-.478449E+00, 0.661731E+00}, + {-.478799E+00, 0.661432E+00}, {-.479247E+00, 0.661049E+00}, + {-.479794E+00, 0.660582E+00}, {-.480439E+00, 0.660030E+00}, + {-.481180E+00, 0.659394E+00}, {-.482018E+00, 0.658675E+00}, + {-.482951E+00, 0.657873E+00}, {-.483978E+00, 0.656989E+00}, + {-.485097E+00, 0.656024E+00}, {-.486308E+00, 0.654978E+00}, + {-.487609E+00, 0.653851E+00}, {-.488998E+00, 0.652646E+00}, + {-.490474E+00, 0.651363E+00}, {-.492035E+00, 0.650002E+00}, + {-.493678E+00, 0.648566E+00}, {-.495403E+00, 0.647054E+00}, + {-.497206E+00, 0.645469E+00}, {-.499087E+00, 0.643811E+00}, + {-.501042E+00, 0.642082E+00}, {-.503069E+00, 0.640283E+00}, + {-.505166E+00, 0.638416E+00}, {-.507330E+00, 0.636482E+00}, + {-.509560E+00, 0.634483E+00}, {-.511851E+00, 0.632421E+00}, + {-.514203E+00, 0.630297E+00}, {-.516611E+00, 0.628113E+00}, + {-.519073E+00, 0.625870E+00}, {-.521587E+00, 0.623572E+00}, + {-.524149E+00, 0.621219E+00}, {-.526757E+00, 0.618814E+00}, + {-.529407E+00, 0.616359E+00}, {-.532097E+00, 0.613857E+00}, + {-.534824E+00, 0.611309E+00}, {-.537585E+00, 0.608717E+00}, + {-.540376E+00, 0.606084E+00}, {-.543195E+00, 0.603413E+00}, + {-.546039E+00, 0.600706E+00}, {-.548904E+00, 0.597966E+00}, + {-.551787E+00, 0.595194E+00}, {-.554686E+00, 0.592394E+00}, + {-.557597E+00, 0.589568E+00}, {-.560518E+00, 0.586720E+00}, + {-.563444E+00, 0.583851E+00}, {-.566374E+00, 0.580965E+00}, + {-.569304E+00, 0.578064E+00}, {-.572231E+00, 0.575152E+00}, + {-.575152E+00, 0.572231E+00}, {-.578064E+00, 0.569304E+00}, + {-.580965E+00, 0.566374E+00}, {-.583851E+00, 0.563444E+00}, + {-.586720E+00, 0.560518E+00}, {-.589568E+00, 0.557597E+00}, + {-.592394E+00, 0.554686E+00}, {-.595194E+00, 0.551787E+00}, + {-.597966E+00, 0.548904E+00}, {-.600706E+00, 0.546039E+00}, + {-.603413E+00, 0.543195E+00}, {-.606084E+00, 0.540376E+00}, + {-.608717E+00, 0.537585E+00}, {-.611309E+00, 0.534824E+00}, + {-.613857E+00, 0.532097E+00}, {-.616359E+00, 0.529407E+00}, + {-.618814E+00, 0.526757E+00}, {-.621219E+00, 0.524149E+00}, + {-.623572E+00, 0.521587E+00}, {-.625870E+00, 0.519073E+00}, + {-.628113E+00, 0.516611E+00}, {-.630297E+00, 0.514203E+00}, + {-.632421E+00, 0.511851E+00}, {-.634483E+00, 0.509560E+00}, + {-.636482E+00, 0.507330E+00}, {-.638416E+00, 0.505166E+00}, + {-.640283E+00, 0.503069E+00}, {-.642082E+00, 0.501042E+00}, + {-.643811E+00, 0.499087E+00}, {-.645469E+00, 0.497206E+00}, + {-.647054E+00, 0.495403E+00}, {-.648566E+00, 0.493678E+00}, + {-.650002E+00, 0.492035E+00}, {-.651363E+00, 0.490474E+00}, + {-.652646E+00, 0.488998E+00}, {-.653851E+00, 0.487609E+00}, + {-.654978E+00, 0.486308E+00}, {-.656024E+00, 0.485097E+00}, + {-.656989E+00, 0.483978E+00}, {-.657873E+00, 0.482951E+00}, + {-.658675E+00, 0.482018E+00}, {-.659394E+00, 0.481180E+00}, + {-.660030E+00, 0.480439E+00}, {-.660582E+00, 0.479794E+00}, + {-.661049E+00, 0.479247E+00}, {-.661432E+00, 0.478799E+00}, + {-.661731E+00, 0.478449E+00}, {-.661944E+00, 0.478200E+00}, + {-.662072E+00, 0.478050E+00}, {-.662115E+00, 0.478000E+00}, + }, + { + {-.471893E+00, 0.666520E+00}, {-.471947E+00, 0.666475E+00}, + {-.472106E+00, 0.666340E+00}, {-.472371E+00, 0.666116E+00}, + {-.472742E+00, 0.665802E+00}, {-.473217E+00, 0.665399E+00}, + {-.473798E+00, 0.664907E+00}, {-.474482E+00, 0.664326E+00}, + {-.475269E+00, 0.663657E+00}, {-.476158E+00, 0.662901E+00}, + {-.477148E+00, 0.662057E+00}, {-.478238E+00, 0.661127E+00}, + {-.479426E+00, 0.660111E+00}, {-.480711E+00, 0.659010E+00}, + {-.482092E+00, 0.657825E+00}, {-.483566E+00, 0.656556E+00}, + {-.485131E+00, 0.655205E+00}, {-.486787E+00, 0.653773E+00}, + {-.488530E+00, 0.652261E+00}, {-.490360E+00, 0.650669E+00}, + {-.492273E+00, 0.649000E+00}, {-.494267E+00, 0.647254E+00}, + {-.496340E+00, 0.645433E+00}, {-.498490E+00, 0.643538E+00}, + {-.500714E+00, 0.641571E+00}, {-.503008E+00, 0.639534E+00}, + {-.505372E+00, 0.637427E+00}, {-.507801E+00, 0.635254E+00}, + {-.510293E+00, 0.633015E+00}, {-.512845E+00, 0.630713E+00}, + {-.515455E+00, 0.628349E+00}, {-.518118E+00, 0.625926E+00}, + {-.520833E+00, 0.623445E+00}, {-.523596E+00, 0.620909E+00}, + {-.526403E+00, 0.618320E+00}, {-.529253E+00, 0.615680E+00}, + {-.532140E+00, 0.612991E+00}, {-.535064E+00, 0.610257E+00}, + {-.538019E+00, 0.607478E+00}, {-.541003E+00, 0.604659E+00}, + {-.544013E+00, 0.601802E+00}, {-.547045E+00, 0.598908E+00}, + {-.550097E+00, 0.595982E+00}, {-.553164E+00, 0.593025E+00}, + {-.556244E+00, 0.590040E+00}, {-.559333E+00, 0.587031E+00}, + {-.562428E+00, 0.584000E+00}, {-.565527E+00, 0.580951E+00}, + {-.568624E+00, 0.577885E+00}, {-.571719E+00, 0.574807E+00}, + {-.574807E+00, 0.571719E+00}, {-.577885E+00, 0.568624E+00}, + {-.580951E+00, 0.565527E+00}, {-.584000E+00, 0.562428E+00}, + {-.587031E+00, 0.559333E+00}, {-.590040E+00, 0.556244E+00}, + {-.593025E+00, 0.553164E+00}, {-.595982E+00, 0.550097E+00}, + {-.598908E+00, 0.547045E+00}, {-.601802E+00, 0.544013E+00}, + {-.604659E+00, 0.541003E+00}, {-.607478E+00, 0.538019E+00}, + {-.610257E+00, 0.535064E+00}, {-.612991E+00, 0.532140E+00}, + {-.615680E+00, 0.529253E+00}, {-.618320E+00, 0.526403E+00}, + {-.620909E+00, 0.523596E+00}, {-.623445E+00, 0.520833E+00}, + {-.625926E+00, 0.518118E+00}, {-.628349E+00, 0.515455E+00}, + {-.630713E+00, 0.512845E+00}, {-.633015E+00, 0.510293E+00}, + {-.635254E+00, 0.507801E+00}, {-.637427E+00, 0.505372E+00}, + {-.639534E+00, 0.503008E+00}, {-.641571E+00, 0.500714E+00}, + {-.643538E+00, 0.498490E+00}, {-.645433E+00, 0.496340E+00}, + {-.647254E+00, 0.494267E+00}, {-.649000E+00, 0.492273E+00}, + {-.650669E+00, 0.490360E+00}, {-.652261E+00, 0.488530E+00}, + {-.653773E+00, 0.486787E+00}, {-.655205E+00, 0.485131E+00}, + {-.656556E+00, 0.483566E+00}, {-.657825E+00, 0.482092E+00}, + {-.659010E+00, 0.480711E+00}, {-.660111E+00, 0.479426E+00}, + {-.661127E+00, 0.478238E+00}, {-.662057E+00, 0.477148E+00}, + {-.662901E+00, 0.476158E+00}, {-.663657E+00, 0.475269E+00}, + {-.664326E+00, 0.474482E+00}, {-.664907E+00, 0.473798E+00}, + {-.665399E+00, 0.473217E+00}, {-.665802E+00, 0.472742E+00}, + {-.666116E+00, 0.472371E+00}, {-.666340E+00, 0.472106E+00}, + {-.666475E+00, 0.471947E+00}, {-.666520E+00, 0.471893E+00}, + }, + { + {-.465714E+00, 0.670898E+00}, {-.465770E+00, 0.670851E+00}, + {-.465938E+00, 0.670710E+00}, {-.466219E+00, 0.670475E+00}, + {-.466611E+00, 0.670146E+00}, {-.467115E+00, 0.669723E+00}, + {-.467729E+00, 0.669206E+00}, {-.468454E+00, 0.668597E+00}, + {-.469287E+00, 0.667895E+00}, {-.470228E+00, 0.667102E+00}, + {-.471276E+00, 0.666216E+00}, {-.472429E+00, 0.665240E+00}, + {-.473687E+00, 0.664174E+00}, {-.475047E+00, 0.663018E+00}, + {-.476507E+00, 0.661774E+00}, {-.478067E+00, 0.660443E+00}, + {-.479724E+00, 0.659024E+00}, {-.481475E+00, 0.657521E+00}, + {-.483320E+00, 0.655933E+00}, {-.485255E+00, 0.654261E+00}, + {-.487279E+00, 0.652508E+00}, {-.489388E+00, 0.650674E+00}, + {-.491581E+00, 0.648762E+00}, {-.493854E+00, 0.646771E+00}, + {-.496206E+00, 0.644705E+00}, {-.498632E+00, 0.642564E+00}, + {-.501131E+00, 0.640351E+00}, {-.503699E+00, 0.638067E+00}, + {-.506334E+00, 0.635714E+00}, {-.509031E+00, 0.633293E+00}, + {-.511789E+00, 0.630808E+00}, {-.514604E+00, 0.628260E+00}, + {-.517472E+00, 0.625651E+00}, {-.520391E+00, 0.622984E+00}, + {-.523357E+00, 0.620260E+00}, {-.526367E+00, 0.617483E+00}, + {-.529417E+00, 0.614654E+00}, {-.532504E+00, 0.611776E+00}, + {-.535625E+00, 0.608852E+00}, {-.538775E+00, 0.605885E+00}, + {-.541953E+00, 0.602876E+00}, {-.545153E+00, 0.599830E+00}, + {-.548374E+00, 0.596748E+00}, {-.551610E+00, 0.593634E+00}, + {-.554860E+00, 0.590490E+00}, {-.558119E+00, 0.587320E+00}, + {-.561384E+00, 0.584126E+00}, {-.564652E+00, 0.580913E+00}, + {-.567919E+00, 0.577682E+00}, {-.571181E+00, 0.574437E+00}, + {-.574437E+00, 0.571181E+00}, {-.577682E+00, 0.567919E+00}, + {-.580913E+00, 0.564652E+00}, {-.584126E+00, 0.561384E+00}, + {-.587320E+00, 0.558119E+00}, {-.590490E+00, 0.554860E+00}, + {-.593634E+00, 0.551610E+00}, {-.596748E+00, 0.548374E+00}, + {-.599830E+00, 0.545153E+00}, {-.602876E+00, 0.541953E+00}, + {-.605885E+00, 0.538775E+00}, {-.608852E+00, 0.535625E+00}, + {-.611776E+00, 0.532504E+00}, {-.614654E+00, 0.529417E+00}, + {-.617483E+00, 0.526367E+00}, {-.620260E+00, 0.523357E+00}, + {-.622984E+00, 0.520391E+00}, {-.625651E+00, 0.517472E+00}, + {-.628260E+00, 0.514604E+00}, {-.630808E+00, 0.511789E+00}, + {-.633293E+00, 0.509031E+00}, {-.635714E+00, 0.506334E+00}, + {-.638067E+00, 0.503699E+00}, {-.640351E+00, 0.501131E+00}, + {-.642564E+00, 0.498632E+00}, {-.644705E+00, 0.496206E+00}, + {-.646771E+00, 0.493854E+00}, {-.648762E+00, 0.491581E+00}, + {-.650674E+00, 0.489388E+00}, {-.652508E+00, 0.487279E+00}, + {-.654261E+00, 0.485255E+00}, {-.655933E+00, 0.483320E+00}, + {-.657521E+00, 0.481475E+00}, {-.659024E+00, 0.479724E+00}, + {-.660443E+00, 0.478067E+00}, {-.661774E+00, 0.476507E+00}, + {-.663018E+00, 0.475047E+00}, {-.664174E+00, 0.473687E+00}, + {-.665240E+00, 0.472429E+00}, {-.666216E+00, 0.471276E+00}, + {-.667102E+00, 0.470228E+00}, {-.667895E+00, 0.469287E+00}, + {-.668597E+00, 0.468454E+00}, {-.669206E+00, 0.467729E+00}, + {-.669723E+00, 0.467115E+00}, {-.670146E+00, 0.466611E+00}, + {-.670475E+00, 0.466219E+00}, {-.670710E+00, 0.465938E+00}, + {-.670851E+00, 0.465770E+00}, {-.670898E+00, 0.465714E+00}, + }, + { + {-.459458E+00, 0.675252E+00}, {-.459517E+00, 0.675202E+00}, + {-.459695E+00, 0.675054E+00}, {-.459992E+00, 0.674808E+00}, + {-.460406E+00, 0.674464E+00}, {-.460938E+00, 0.674021E+00}, + {-.461587E+00, 0.673481E+00}, {-.462352E+00, 0.672843E+00}, + {-.463231E+00, 0.672108E+00}, {-.464225E+00, 0.671278E+00}, + {-.465332E+00, 0.670351E+00}, {-.466549E+00, 0.669329E+00}, + {-.467877E+00, 0.668213E+00}, {-.469312E+00, 0.667003E+00}, + {-.470854E+00, 0.665700E+00}, {-.472501E+00, 0.664306E+00}, + {-.474249E+00, 0.662821E+00}, {-.476098E+00, 0.661246E+00}, + {-.478045E+00, 0.659582E+00}, {-.480087E+00, 0.657832E+00}, + {-.482223E+00, 0.655995E+00}, {-.484449E+00, 0.654074E+00}, + {-.486762E+00, 0.652070E+00}, {-.489161E+00, 0.649984E+00}, + {-.491641E+00, 0.647818E+00}, {-.494201E+00, 0.645575E+00}, + {-.496836E+00, 0.643254E+00}, {-.499545E+00, 0.640860E+00}, + {-.502323E+00, 0.638392E+00}, {-.505167E+00, 0.635854E+00}, + {-.508075E+00, 0.633248E+00}, {-.511042E+00, 0.630575E+00}, + {-.514066E+00, 0.627838E+00}, {-.517142E+00, 0.625039E+00}, + {-.520268E+00, 0.622181E+00}, {-.523440E+00, 0.619266E+00}, + {-.526653E+00, 0.616297E+00}, {-.529905E+00, 0.613276E+00}, + {-.533192E+00, 0.610206E+00}, {-.536511E+00, 0.607090E+00}, + {-.539857E+00, 0.603931E+00}, {-.543227E+00, 0.600730E+00}, + {-.546618E+00, 0.597493E+00}, {-.550025E+00, 0.594221E+00}, + {-.553445E+00, 0.590917E+00}, {-.556875E+00, 0.587586E+00}, + {-.560311E+00, 0.584229E+00}, {-.563749E+00, 0.580850E+00}, + {-.567186E+00, 0.577453E+00}, {-.570618E+00, 0.574041E+00}, + {-.574041E+00, 0.570618E+00}, {-.577453E+00, 0.567186E+00}, + {-.580850E+00, 0.563749E+00}, {-.584229E+00, 0.560311E+00}, + {-.587586E+00, 0.556875E+00}, {-.590917E+00, 0.553445E+00}, + {-.594221E+00, 0.550025E+00}, {-.597493E+00, 0.546618E+00}, + {-.600730E+00, 0.543227E+00}, {-.603931E+00, 0.539857E+00}, + {-.607090E+00, 0.536511E+00}, {-.610206E+00, 0.533192E+00}, + {-.613276E+00, 0.529905E+00}, {-.616297E+00, 0.526653E+00}, + {-.619266E+00, 0.523440E+00}, {-.622181E+00, 0.520268E+00}, + {-.625039E+00, 0.517142E+00}, {-.627838E+00, 0.514066E+00}, + {-.630575E+00, 0.511042E+00}, {-.633248E+00, 0.508075E+00}, + {-.635854E+00, 0.505167E+00}, {-.638392E+00, 0.502323E+00}, + {-.640860E+00, 0.499545E+00}, {-.643254E+00, 0.496836E+00}, + {-.645575E+00, 0.494201E+00}, {-.647818E+00, 0.491641E+00}, + {-.649984E+00, 0.489161E+00}, {-.652070E+00, 0.486762E+00}, + {-.654074E+00, 0.484449E+00}, {-.655995E+00, 0.482223E+00}, + {-.657832E+00, 0.480087E+00}, {-.659582E+00, 0.478045E+00}, + {-.661246E+00, 0.476098E+00}, {-.662821E+00, 0.474249E+00}, + {-.664306E+00, 0.472501E+00}, {-.665700E+00, 0.470854E+00}, + {-.667003E+00, 0.469312E+00}, {-.668213E+00, 0.467877E+00}, + {-.669329E+00, 0.466549E+00}, {-.670351E+00, 0.465332E+00}, + {-.671278E+00, 0.464225E+00}, {-.672108E+00, 0.463231E+00}, + {-.672843E+00, 0.462352E+00}, {-.673481E+00, 0.461587E+00}, + {-.674021E+00, 0.460938E+00}, {-.674464E+00, 0.460406E+00}, + {-.674808E+00, 0.459992E+00}, {-.675054E+00, 0.459695E+00}, + {-.675202E+00, 0.459517E+00}, {-.675252E+00, 0.459458E+00}, + }, + { + {-.453124E+00, 0.679580E+00}, {-.453187E+00, 0.679528E+00}, + {-.453375E+00, 0.679374E+00}, {-.453687E+00, 0.679117E+00}, + {-.454123E+00, 0.678757E+00}, {-.454684E+00, 0.678295E+00}, + {-.455368E+00, 0.677731E+00}, {-.456173E+00, 0.677065E+00}, + {-.457100E+00, 0.676297E+00}, {-.458147E+00, 0.675430E+00}, + {-.459313E+00, 0.674462E+00}, {-.460596E+00, 0.673394E+00}, + {-.461994E+00, 0.672228E+00}, {-.463507E+00, 0.670964E+00}, + {-.465131E+00, 0.669603E+00}, {-.466865E+00, 0.668146E+00}, + {-.468707E+00, 0.666595E+00}, {-.470654E+00, 0.664949E+00}, + {-.472704E+00, 0.663210E+00}, {-.474855E+00, 0.661381E+00}, + {-.477103E+00, 0.659461E+00}, {-.479447E+00, 0.657453E+00}, + {-.481882E+00, 0.655357E+00}, {-.484407E+00, 0.653176E+00}, + {-.487018E+00, 0.650912E+00}, {-.489712E+00, 0.648565E+00}, + {-.492486E+00, 0.646138E+00}, {-.495336E+00, 0.643633E+00}, + {-.498259E+00, 0.641051E+00}, {-.501252E+00, 0.638396E+00}, + {-.504311E+00, 0.635668E+00}, {-.507432E+00, 0.632870E+00}, + {-.510612E+00, 0.630005E+00}, {-.513848E+00, 0.627075E+00}, + {-.517135E+00, 0.624083E+00}, {-.520469E+00, 0.621030E+00}, + {-.523848E+00, 0.617921E+00}, {-.527266E+00, 0.614756E+00}, + {-.530721E+00, 0.611540E+00}, {-.534209E+00, 0.608275E+00}, + {-.537725E+00, 0.604964E+00}, {-.541266E+00, 0.601610E+00}, + {-.544828E+00, 0.598216E+00}, {-.548406E+00, 0.594786E+00}, + {-.551999E+00, 0.591322E+00}, {-.555600E+00, 0.587828E+00}, + {-.559208E+00, 0.584308E+00}, {-.562817E+00, 0.580764E+00}, + {-.566425E+00, 0.577200E+00}, {-.570027E+00, 0.573620E+00}, + {-.573620E+00, 0.570027E+00}, {-.577200E+00, 0.566425E+00}, + {-.580764E+00, 0.562817E+00}, {-.584308E+00, 0.559208E+00}, + {-.587828E+00, 0.555600E+00}, {-.591322E+00, 0.551999E+00}, + {-.594786E+00, 0.548406E+00}, {-.598216E+00, 0.544828E+00}, + {-.601610E+00, 0.541266E+00}, {-.604964E+00, 0.537725E+00}, + {-.608275E+00, 0.534209E+00}, {-.611540E+00, 0.530721E+00}, + {-.614756E+00, 0.527266E+00}, {-.617921E+00, 0.523848E+00}, + {-.621030E+00, 0.520469E+00}, {-.624083E+00, 0.517135E+00}, + {-.627075E+00, 0.513848E+00}, {-.630005E+00, 0.510612E+00}, + {-.632870E+00, 0.507432E+00}, {-.635668E+00, 0.504311E+00}, + {-.638396E+00, 0.501252E+00}, {-.641051E+00, 0.498259E+00}, + {-.643633E+00, 0.495336E+00}, {-.646138E+00, 0.492486E+00}, + {-.648565E+00, 0.489712E+00}, {-.650912E+00, 0.487018E+00}, + {-.653176E+00, 0.484407E+00}, {-.655357E+00, 0.481882E+00}, + {-.657453E+00, 0.479447E+00}, {-.659461E+00, 0.477103E+00}, + {-.661381E+00, 0.474855E+00}, {-.663210E+00, 0.472704E+00}, + {-.664949E+00, 0.470654E+00}, {-.666595E+00, 0.468707E+00}, + {-.668146E+00, 0.466865E+00}, {-.669603E+00, 0.465131E+00}, + {-.670964E+00, 0.463507E+00}, {-.672228E+00, 0.461994E+00}, + {-.673394E+00, 0.460596E+00}, {-.674462E+00, 0.459313E+00}, + {-.675430E+00, 0.458147E+00}, {-.676297E+00, 0.457100E+00}, + {-.677065E+00, 0.456173E+00}, {-.677731E+00, 0.455368E+00}, + {-.678295E+00, 0.454684E+00}, {-.678757E+00, 0.454123E+00}, + {-.679117E+00, 0.453687E+00}, {-.679374E+00, 0.453375E+00}, + {-.679528E+00, 0.453187E+00}, {-.679580E+00, 0.453124E+00}, + }, + { + {-.446711E+00, 0.683884E+00}, {-.446776E+00, 0.683830E+00}, + {-.446974E+00, 0.683669E+00}, {-.447302E+00, 0.683401E+00}, + {-.447761E+00, 0.683027E+00}, {-.448351E+00, 0.682545E+00}, + {-.449070E+00, 0.681957E+00}, {-.449917E+00, 0.681263E+00}, + {-.450892E+00, 0.680463E+00}, {-.451992E+00, 0.679559E+00}, + {-.453218E+00, 0.678550E+00}, {-.454567E+00, 0.677437E+00}, + {-.456037E+00, 0.676221E+00}, {-.457627E+00, 0.674903E+00}, + {-.459334E+00, 0.673485E+00}, {-.461157E+00, 0.671965E+00}, + {-.463093E+00, 0.670347E+00}, {-.465140E+00, 0.668631E+00}, + {-.467295E+00, 0.666818E+00}, {-.469555E+00, 0.664909E+00}, + {-.471918E+00, 0.662906E+00}, {-.474380E+00, 0.660811E+00}, + {-.476939E+00, 0.658625E+00}, {-.479592E+00, 0.656349E+00}, + {-.482335E+00, 0.653985E+00}, {-.485165E+00, 0.651536E+00}, + {-.488078E+00, 0.649002E+00}, {-.491071E+00, 0.646387E+00}, + {-.494141E+00, 0.643691E+00}, {-.497284E+00, 0.640918E+00}, + {-.500495E+00, 0.638069E+00}, {-.503772E+00, 0.635147E+00}, + {-.507110E+00, 0.632154E+00}, {-.510506E+00, 0.629093E+00}, + {-.513955E+00, 0.625965E+00}, {-.517455E+00, 0.622775E+00}, + {-.520999E+00, 0.619525E+00}, {-.524586E+00, 0.616217E+00}, + {-.528210E+00, 0.612854E+00}, {-.531868E+00, 0.609440E+00}, + {-.535555E+00, 0.605977E+00}, {-.539268E+00, 0.602469E+00}, + {-.543003E+00, 0.598918E+00}, {-.546755E+00, 0.595329E+00}, + {-.550520E+00, 0.591705E+00}, {-.554295E+00, 0.588048E+00}, + {-.558075E+00, 0.584363E+00}, {-.561857E+00, 0.580653E+00}, + {-.565636E+00, 0.576921E+00}, {-.569409E+00, 0.573172E+00}, + {-.573172E+00, 0.569409E+00}, {-.576921E+00, 0.565636E+00}, + {-.580653E+00, 0.561857E+00}, {-.584363E+00, 0.558075E+00}, + {-.588048E+00, 0.554295E+00}, {-.591705E+00, 0.550520E+00}, + {-.595329E+00, 0.546755E+00}, {-.598918E+00, 0.543003E+00}, + {-.602469E+00, 0.539268E+00}, {-.605977E+00, 0.535555E+00}, + {-.609440E+00, 0.531868E+00}, {-.612854E+00, 0.528210E+00}, + {-.616217E+00, 0.524586E+00}, {-.619525E+00, 0.520999E+00}, + {-.622775E+00, 0.517455E+00}, {-.625965E+00, 0.513955E+00}, + {-.629093E+00, 0.510506E+00}, {-.632154E+00, 0.507110E+00}, + {-.635147E+00, 0.503772E+00}, {-.638069E+00, 0.500495E+00}, + {-.640918E+00, 0.497284E+00}, {-.643691E+00, 0.494141E+00}, + {-.646387E+00, 0.491071E+00}, {-.649002E+00, 0.488078E+00}, + {-.651536E+00, 0.485165E+00}, {-.653985E+00, 0.482335E+00}, + {-.656349E+00, 0.479592E+00}, {-.658625E+00, 0.476939E+00}, + {-.660811E+00, 0.474380E+00}, {-.662906E+00, 0.471918E+00}, + {-.664909E+00, 0.469555E+00}, {-.666818E+00, 0.467295E+00}, + {-.668631E+00, 0.465140E+00}, {-.670347E+00, 0.463093E+00}, + {-.671965E+00, 0.461157E+00}, {-.673485E+00, 0.459334E+00}, + {-.674903E+00, 0.457627E+00}, {-.676221E+00, 0.456037E+00}, + {-.677437E+00, 0.454567E+00}, {-.678550E+00, 0.453218E+00}, + {-.679559E+00, 0.451992E+00}, {-.680463E+00, 0.450892E+00}, + {-.681263E+00, 0.449917E+00}, {-.681957E+00, 0.449070E+00}, + {-.682545E+00, 0.448351E+00}, {-.683027E+00, 0.447761E+00}, + {-.683401E+00, 0.447302E+00}, {-.683669E+00, 0.446974E+00}, + {-.683830E+00, 0.446776E+00}, {-.683884E+00, 0.446711E+00}, + }, + { + {-.440214E+00, 0.688165E+00}, {-.440283E+00, 0.688109E+00}, + {-.440491E+00, 0.687942E+00}, {-.440835E+00, 0.687663E+00}, + {-.441317E+00, 0.687273E+00}, {-.441936E+00, 0.686772E+00}, + {-.442690E+00, 0.686160E+00}, {-.443580E+00, 0.685438E+00}, + {-.444603E+00, 0.684606E+00}, {-.445758E+00, 0.683665E+00}, + {-.447045E+00, 0.682615E+00}, {-.448460E+00, 0.681457E+00}, + {-.450003E+00, 0.680192E+00}, {-.451672E+00, 0.678821E+00}, + {-.453463E+00, 0.677344E+00}, {-.455376E+00, 0.675763E+00}, + {-.457407E+00, 0.674079E+00}, {-.459555E+00, 0.672292E+00}, + {-.461815E+00, 0.670404E+00}, {-.464186E+00, 0.668417E+00}, + {-.466665E+00, 0.666332E+00}, {-.469247E+00, 0.664150E+00}, + {-.471931E+00, 0.661872E+00}, {-.474713E+00, 0.659502E+00}, + {-.477590E+00, 0.657040E+00}, {-.480557E+00, 0.654488E+00}, + {-.483611E+00, 0.651848E+00}, {-.486749E+00, 0.649122E+00}, + {-.489967E+00, 0.646313E+00}, {-.493261E+00, 0.643422E+00}, + {-.496626E+00, 0.640452E+00}, {-.500060E+00, 0.637405E+00}, + {-.503558E+00, 0.634284E+00}, {-.507116E+00, 0.631091E+00}, + {-.510729E+00, 0.627829E+00}, {-.514395E+00, 0.624501E+00}, + {-.518107E+00, 0.621109E+00}, {-.521863E+00, 0.617657E+00}, + {-.525658E+00, 0.614148E+00}, {-.529487E+00, 0.610584E+00}, + {-.533347E+00, 0.606969E+00}, {-.537234E+00, 0.603306E+00}, + {-.541142E+00, 0.599599E+00}, {-.545068E+00, 0.595850E+00}, + {-.549008E+00, 0.592064E+00}, {-.552957E+00, 0.588244E+00}, + {-.556911E+00, 0.584393E+00}, {-.560866E+00, 0.580516E+00}, + {-.564818E+00, 0.576616E+00}, {-.568763E+00, 0.572697E+00}, + {-.572697E+00, 0.568763E+00}, {-.576616E+00, 0.564818E+00}, + {-.580516E+00, 0.560866E+00}, {-.584393E+00, 0.556911E+00}, + {-.588244E+00, 0.552957E+00}, {-.592064E+00, 0.549008E+00}, + {-.595850E+00, 0.545068E+00}, {-.599599E+00, 0.541142E+00}, + {-.603306E+00, 0.537234E+00}, {-.606969E+00, 0.533347E+00}, + {-.610584E+00, 0.529487E+00}, {-.614148E+00, 0.525658E+00}, + {-.617657E+00, 0.521863E+00}, {-.621109E+00, 0.518107E+00}, + {-.624501E+00, 0.514395E+00}, {-.627829E+00, 0.510729E+00}, + {-.631091E+00, 0.507116E+00}, {-.634284E+00, 0.503558E+00}, + {-.637405E+00, 0.500060E+00}, {-.640452E+00, 0.496626E+00}, + {-.643422E+00, 0.493261E+00}, {-.646313E+00, 0.489967E+00}, + {-.649122E+00, 0.486749E+00}, {-.651848E+00, 0.483611E+00}, + {-.654488E+00, 0.480557E+00}, {-.657040E+00, 0.477590E+00}, + {-.659502E+00, 0.474713E+00}, {-.661872E+00, 0.471931E+00}, + {-.664150E+00, 0.469247E+00}, {-.666332E+00, 0.466665E+00}, + {-.668417E+00, 0.464186E+00}, {-.670404E+00, 0.461815E+00}, + {-.672292E+00, 0.459555E+00}, {-.674079E+00, 0.457407E+00}, + {-.675763E+00, 0.455376E+00}, {-.677344E+00, 0.453463E+00}, + {-.678821E+00, 0.451672E+00}, {-.680192E+00, 0.450003E+00}, + {-.681457E+00, 0.448460E+00}, {-.682615E+00, 0.447045E+00}, + {-.683665E+00, 0.445758E+00}, {-.684606E+00, 0.444603E+00}, + {-.685438E+00, 0.443580E+00}, {-.686160E+00, 0.442690E+00}, + {-.686772E+00, 0.441936E+00}, {-.687273E+00, 0.441317E+00}, + {-.687663E+00, 0.440835E+00}, {-.687942E+00, 0.440491E+00}, + {-.688109E+00, 0.440283E+00}, {-.688165E+00, 0.440214E+00}, + }, + { + {-.433633E+00, 0.692423E+00}, {-.433706E+00, 0.692365E+00}, + {-.433923E+00, 0.692191E+00}, {-.434284E+00, 0.691902E+00}, + {-.434789E+00, 0.691497E+00}, {-.435437E+00, 0.690976E+00}, + {-.436228E+00, 0.690341E+00}, {-.437160E+00, 0.689591E+00}, + {-.438232E+00, 0.688727E+00}, {-.439443E+00, 0.687750E+00}, + {-.440791E+00, 0.686659E+00}, {-.442274E+00, 0.685456E+00}, + {-.443890E+00, 0.684142E+00}, {-.445638E+00, 0.682718E+00}, + {-.447515E+00, 0.681183E+00}, {-.449519E+00, 0.679540E+00}, + {-.451647E+00, 0.677790E+00}, {-.453896E+00, 0.675933E+00}, + {-.456263E+00, 0.673971E+00}, {-.458747E+00, 0.671906E+00}, + {-.461342E+00, 0.669738E+00}, {-.464047E+00, 0.667469E+00}, + {-.466857E+00, 0.665101E+00}, {-.469769E+00, 0.662636E+00}, + {-.472781E+00, 0.660075E+00}, {-.475887E+00, 0.657421E+00}, + {-.479084E+00, 0.654675E+00}, {-.482368E+00, 0.651839E+00}, + {-.485735E+00, 0.648916E+00}, {-.489182E+00, 0.645907E+00}, + {-.492703E+00, 0.642816E+00}, {-.496296E+00, 0.639645E+00}, + {-.499955E+00, 0.636395E+00}, {-.503676E+00, 0.633071E+00}, + {-.507455E+00, 0.629674E+00}, {-.511288E+00, 0.626208E+00}, + {-.515170E+00, 0.622675E+00}, {-.519097E+00, 0.619079E+00}, + {-.523064E+00, 0.615422E+00}, {-.527066E+00, 0.611708E+00}, + {-.531100E+00, 0.607940E+00}, {-.535161E+00, 0.604122E+00}, + {-.539245E+00, 0.600257E+00}, {-.543347E+00, 0.596348E+00}, + {-.547462E+00, 0.592400E+00}, {-.551586E+00, 0.588416E+00}, + {-.555715E+00, 0.584399E+00}, {-.559845E+00, 0.580354E+00}, + {-.563971E+00, 0.576285E+00}, {-.568089E+00, 0.572195E+00}, + {-.572195E+00, 0.568089E+00}, {-.576285E+00, 0.563971E+00}, + {-.580354E+00, 0.559845E+00}, {-.584399E+00, 0.555715E+00}, + {-.588416E+00, 0.551586E+00}, {-.592400E+00, 0.547462E+00}, + {-.596348E+00, 0.543347E+00}, {-.600257E+00, 0.539245E+00}, + {-.604122E+00, 0.535161E+00}, {-.607940E+00, 0.531100E+00}, + {-.611708E+00, 0.527066E+00}, {-.615422E+00, 0.523064E+00}, + {-.619079E+00, 0.519097E+00}, {-.622675E+00, 0.515170E+00}, + {-.626208E+00, 0.511288E+00}, {-.629674E+00, 0.507455E+00}, + {-.633071E+00, 0.503676E+00}, {-.636395E+00, 0.499955E+00}, + {-.639645E+00, 0.496296E+00}, {-.642816E+00, 0.492703E+00}, + {-.645907E+00, 0.489182E+00}, {-.648916E+00, 0.485735E+00}, + {-.651839E+00, 0.482368E+00}, {-.654675E+00, 0.479084E+00}, + {-.657421E+00, 0.475887E+00}, {-.660075E+00, 0.472781E+00}, + {-.662636E+00, 0.469769E+00}, {-.665101E+00, 0.466857E+00}, + {-.667469E+00, 0.464047E+00}, {-.669738E+00, 0.461342E+00}, + {-.671906E+00, 0.458747E+00}, {-.673971E+00, 0.456263E+00}, + {-.675933E+00, 0.453896E+00}, {-.677790E+00, 0.451647E+00}, + {-.679540E+00, 0.449519E+00}, {-.681183E+00, 0.447515E+00}, + {-.682718E+00, 0.445638E+00}, {-.684142E+00, 0.443890E+00}, + {-.685456E+00, 0.442274E+00}, {-.686659E+00, 0.440791E+00}, + {-.687750E+00, 0.439443E+00}, {-.688727E+00, 0.438232E+00}, + {-.689591E+00, 0.437160E+00}, {-.690341E+00, 0.436228E+00}, + {-.690976E+00, 0.435437E+00}, {-.691497E+00, 0.434789E+00}, + {-.691902E+00, 0.434284E+00}, {-.692191E+00, 0.433923E+00}, + {-.692365E+00, 0.433706E+00}, {-.692423E+00, 0.433633E+00}, + }, + { + {-.426965E+00, 0.696659E+00}, {-.427040E+00, 0.696599E+00}, + {-.427267E+00, 0.696419E+00}, {-.427645E+00, 0.696119E+00}, + {-.428174E+00, 0.695699E+00}, {-.428852E+00, 0.695159E+00}, + {-.429680E+00, 0.694500E+00}, {-.430655E+00, 0.693723E+00}, + {-.431776E+00, 0.692827E+00}, {-.433043E+00, 0.691813E+00}, + {-.434453E+00, 0.690682E+00}, {-.436005E+00, 0.689435E+00}, + {-.437696E+00, 0.688072E+00}, {-.439525E+00, 0.686594E+00}, + {-.441488E+00, 0.685002E+00}, {-.443584E+00, 0.683298E+00}, + {-.445809E+00, 0.681481E+00}, {-.448161E+00, 0.679555E+00}, + {-.450637E+00, 0.677519E+00}, {-.453234E+00, 0.675375E+00}, + {-.455948E+00, 0.673125E+00}, {-.458776E+00, 0.670770E+00}, + {-.461714E+00, 0.668312E+00}, {-.464758E+00, 0.665752E+00}, + {-.467906E+00, 0.663093E+00}, {-.471152E+00, 0.660336E+00}, + {-.474494E+00, 0.657484E+00}, {-.477926E+00, 0.654538E+00}, + {-.481444E+00, 0.651501E+00}, {-.485045E+00, 0.648375E+00}, + {-.488724E+00, 0.645163E+00}, {-.492477E+00, 0.641866E+00}, + {-.496299E+00, 0.638489E+00}, {-.500185E+00, 0.635032E+00}, + {-.504132E+00, 0.631501E+00}, {-.508134E+00, 0.627896E+00}, + {-.512186E+00, 0.624221E+00}, {-.516285E+00, 0.620480E+00}, + {-.520426E+00, 0.616676E+00}, {-.524603E+00, 0.612812E+00}, + {-.528813E+00, 0.608891E+00}, {-.533050E+00, 0.604917E+00}, + {-.537310E+00, 0.600893E+00}, {-.541589E+00, 0.596824E+00}, + {-.545881E+00, 0.592713E+00}, {-.550182E+00, 0.588564E+00}, + {-.554488E+00, 0.584380E+00}, {-.558793E+00, 0.580167E+00}, + {-.563094E+00, 0.575927E+00}, {-.567387E+00, 0.571666E+00}, + {-.571666E+00, 0.567387E+00}, {-.575927E+00, 0.563094E+00}, + {-.580167E+00, 0.558793E+00}, {-.584380E+00, 0.554488E+00}, + {-.588564E+00, 0.550182E+00}, {-.592713E+00, 0.545881E+00}, + {-.596824E+00, 0.541589E+00}, {-.600893E+00, 0.537310E+00}, + {-.604917E+00, 0.533050E+00}, {-.608891E+00, 0.528813E+00}, + {-.612812E+00, 0.524603E+00}, {-.616676E+00, 0.520426E+00}, + {-.620480E+00, 0.516285E+00}, {-.624221E+00, 0.512186E+00}, + {-.627896E+00, 0.508134E+00}, {-.631501E+00, 0.504132E+00}, + {-.635032E+00, 0.500185E+00}, {-.638489E+00, 0.496299E+00}, + {-.641866E+00, 0.492477E+00}, {-.645163E+00, 0.488724E+00}, + {-.648375E+00, 0.485045E+00}, {-.651501E+00, 0.481444E+00}, + {-.654538E+00, 0.477926E+00}, {-.657484E+00, 0.474494E+00}, + {-.660336E+00, 0.471152E+00}, {-.663093E+00, 0.467906E+00}, + {-.665752E+00, 0.464758E+00}, {-.668312E+00, 0.461714E+00}, + {-.670770E+00, 0.458776E+00}, {-.673125E+00, 0.455948E+00}, + {-.675375E+00, 0.453234E+00}, {-.677519E+00, 0.450637E+00}, + {-.679555E+00, 0.448161E+00}, {-.681481E+00, 0.445809E+00}, + {-.683298E+00, 0.443584E+00}, {-.685002E+00, 0.441488E+00}, + {-.686594E+00, 0.439525E+00}, {-.688072E+00, 0.437696E+00}, + {-.689435E+00, 0.436005E+00}, {-.690682E+00, 0.434453E+00}, + {-.691813E+00, 0.433043E+00}, {-.692827E+00, 0.431776E+00}, + {-.693723E+00, 0.430655E+00}, {-.694500E+00, 0.429680E+00}, + {-.695159E+00, 0.428852E+00}, {-.695699E+00, 0.428174E+00}, + {-.696119E+00, 0.427645E+00}, {-.696419E+00, 0.427267E+00}, + {-.696599E+00, 0.427040E+00}, {-.696659E+00, 0.426965E+00}, + }, + { + {-.420206E+00, 0.700874E+00}, {-.420285E+00, 0.700812E+00}, + {-.420522E+00, 0.700625E+00}, {-.420917E+00, 0.700314E+00}, + {-.421469E+00, 0.699880E+00}, {-.422178E+00, 0.699321E+00}, + {-.423043E+00, 0.698639E+00}, {-.424062E+00, 0.697834E+00}, + {-.425234E+00, 0.696906E+00}, {-.426557E+00, 0.695856E+00}, + {-.428030E+00, 0.694685E+00}, {-.429651E+00, 0.693393E+00}, + {-.431418E+00, 0.691981E+00}, {-.433328E+00, 0.690451E+00}, + {-.435379E+00, 0.688802E+00}, {-.437568E+00, 0.687036E+00}, + {-.439892E+00, 0.685154E+00}, {-.442349E+00, 0.683158E+00}, + {-.444935E+00, 0.681048E+00}, {-.447646E+00, 0.678826E+00}, + {-.450480E+00, 0.676494E+00}, {-.453432E+00, 0.674053E+00}, + {-.456500E+00, 0.671504E+00}, {-.459678E+00, 0.668850E+00}, + {-.462964E+00, 0.666093E+00}, {-.466352E+00, 0.663234E+00}, + {-.469839E+00, 0.660275E+00}, {-.473421E+00, 0.657219E+00}, + {-.477092E+00, 0.654069E+00}, {-.480850E+00, 0.650825E+00}, + {-.484688E+00, 0.647491E+00}, {-.488602E+00, 0.644070E+00}, + {-.492588E+00, 0.640564E+00}, {-.496642E+00, 0.636976E+00}, + {-.500757E+00, 0.633308E+00}, {-.504930E+00, 0.629565E+00}, + {-.509155E+00, 0.625749E+00}, {-.513428E+00, 0.621863E+00}, + {-.517744E+00, 0.617910E+00}, {-.522098E+00, 0.613895E+00}, + {-.526484E+00, 0.609820E+00}, {-.530899E+00, 0.605690E+00}, + {-.535337E+00, 0.601507E+00}, {-.539794E+00, 0.597277E+00}, + {-.544264E+00, 0.593002E+00}, {-.548744E+00, 0.588687E+00}, + {-.553227E+00, 0.584336E+00}, {-.557709E+00, 0.579952E+00}, + {-.562187E+00, 0.575542E+00}, {-.566654E+00, 0.571107E+00}, + {-.571107E+00, 0.566654E+00}, {-.575542E+00, 0.562187E+00}, + {-.579952E+00, 0.557709E+00}, {-.584336E+00, 0.553227E+00}, + {-.588687E+00, 0.548744E+00}, {-.593002E+00, 0.544264E+00}, + {-.597277E+00, 0.539794E+00}, {-.601507E+00, 0.535337E+00}, + {-.605690E+00, 0.530899E+00}, {-.609820E+00, 0.526484E+00}, + {-.613895E+00, 0.522098E+00}, {-.617910E+00, 0.517744E+00}, + {-.621863E+00, 0.513428E+00}, {-.625749E+00, 0.509155E+00}, + {-.629565E+00, 0.504930E+00}, {-.633308E+00, 0.500757E+00}, + {-.636976E+00, 0.496642E+00}, {-.640564E+00, 0.492588E+00}, + {-.644070E+00, 0.488602E+00}, {-.647491E+00, 0.484688E+00}, + {-.650825E+00, 0.480850E+00}, {-.654069E+00, 0.477092E+00}, + {-.657219E+00, 0.473421E+00}, {-.660275E+00, 0.469839E+00}, + {-.663234E+00, 0.466352E+00}, {-.666093E+00, 0.462964E+00}, + {-.668850E+00, 0.459678E+00}, {-.671504E+00, 0.456500E+00}, + {-.674053E+00, 0.453432E+00}, {-.676494E+00, 0.450480E+00}, + {-.678826E+00, 0.447646E+00}, {-.681048E+00, 0.444935E+00}, + {-.683158E+00, 0.442349E+00}, {-.685154E+00, 0.439892E+00}, + {-.687036E+00, 0.437568E+00}, {-.688802E+00, 0.435379E+00}, + {-.690451E+00, 0.433328E+00}, {-.691981E+00, 0.431418E+00}, + {-.693393E+00, 0.429651E+00}, {-.694685E+00, 0.428030E+00}, + {-.695856E+00, 0.426557E+00}, {-.696906E+00, 0.425234E+00}, + {-.697834E+00, 0.424062E+00}, {-.698639E+00, 0.423043E+00}, + {-.699321E+00, 0.422178E+00}, {-.699880E+00, 0.421469E+00}, + {-.700314E+00, 0.420917E+00}, {-.700625E+00, 0.420522E+00}, + {-.700812E+00, 0.420285E+00}, {-.700874E+00, 0.420206E+00}, + }, + { + {-.413354E+00, 0.705068E+00}, {-.413437E+00, 0.705004E+00}, + {-.413684E+00, 0.704811E+00}, {-.414096E+00, 0.704490E+00}, + {-.414673E+00, 0.704040E+00}, {-.415413E+00, 0.703462E+00}, + {-.416315E+00, 0.702757E+00}, {-.417378E+00, 0.701925E+00}, + {-.418601E+00, 0.700965E+00}, {-.419982E+00, 0.699879E+00}, + {-.421519E+00, 0.698668E+00}, {-.423210E+00, 0.697332E+00}, + {-.425054E+00, 0.695872E+00}, {-.427046E+00, 0.694288E+00}, + {-.429186E+00, 0.692582E+00}, {-.431469E+00, 0.690755E+00}, + {-.433894E+00, 0.688808E+00}, {-.436456E+00, 0.686742E+00}, + {-.439153E+00, 0.684559E+00}, {-.441981E+00, 0.682259E+00}, + {-.444936E+00, 0.679845E+00}, {-.448015E+00, 0.677318E+00}, + {-.451213E+00, 0.674679E+00}, {-.454527E+00, 0.671931E+00}, + {-.457952E+00, 0.669075E+00}, {-.461484E+00, 0.666114E+00}, + {-.465119E+00, 0.663049E+00}, {-.468851E+00, 0.659884E+00}, + {-.472678E+00, 0.656619E+00}, {-.476593E+00, 0.653258E+00}, + {-.480592E+00, 0.649802E+00}, {-.484670E+00, 0.646256E+00}, + {-.488822E+00, 0.642621E+00}, {-.493044E+00, 0.638901E+00}, + {-.497330E+00, 0.635098E+00}, {-.501675E+00, 0.631216E+00}, + {-.506075E+00, 0.627257E+00}, {-.510523E+00, 0.623226E+00}, + {-.515016E+00, 0.619125E+00}, {-.519548E+00, 0.614958E+00}, + {-.524113E+00, 0.610729E+00}, {-.528707E+00, 0.606441E+00}, + {-.533325E+00, 0.602099E+00}, {-.537961E+00, 0.597706E+00}, + {-.542611E+00, 0.593267E+00}, {-.547270E+00, 0.588785E+00}, + {-.551932E+00, 0.584265E+00}, {-.556593E+00, 0.579711E+00}, + {-.561248E+00, 0.575128E+00}, {-.565892E+00, 0.570520E+00}, + {-.570520E+00, 0.565892E+00}, {-.575128E+00, 0.561248E+00}, + {-.579711E+00, 0.556593E+00}, {-.584265E+00, 0.551932E+00}, + {-.588785E+00, 0.547270E+00}, {-.593267E+00, 0.542611E+00}, + {-.597706E+00, 0.537961E+00}, {-.602099E+00, 0.533325E+00}, + {-.606441E+00, 0.528707E+00}, {-.610729E+00, 0.524113E+00}, + {-.614958E+00, 0.519548E+00}, {-.619125E+00, 0.515016E+00}, + {-.623226E+00, 0.510523E+00}, {-.627257E+00, 0.506075E+00}, + {-.631216E+00, 0.501675E+00}, {-.635098E+00, 0.497330E+00}, + {-.638901E+00, 0.493044E+00}, {-.642621E+00, 0.488822E+00}, + {-.646256E+00, 0.484670E+00}, {-.649802E+00, 0.480592E+00}, + {-.653258E+00, 0.476593E+00}, {-.656619E+00, 0.472678E+00}, + {-.659884E+00, 0.468851E+00}, {-.663049E+00, 0.465119E+00}, + {-.666114E+00, 0.461484E+00}, {-.669075E+00, 0.457952E+00}, + {-.671931E+00, 0.454527E+00}, {-.674679E+00, 0.451213E+00}, + {-.677318E+00, 0.448015E+00}, {-.679845E+00, 0.444936E+00}, + {-.682259E+00, 0.441981E+00}, {-.684559E+00, 0.439153E+00}, + {-.686742E+00, 0.436456E+00}, {-.688808E+00, 0.433894E+00}, + {-.690755E+00, 0.431469E+00}, {-.692582E+00, 0.429186E+00}, + {-.694288E+00, 0.427046E+00}, {-.695872E+00, 0.425054E+00}, + {-.697332E+00, 0.423210E+00}, {-.698668E+00, 0.421519E+00}, + {-.699879E+00, 0.419982E+00}, {-.700965E+00, 0.418601E+00}, + {-.701925E+00, 0.417378E+00}, {-.702757E+00, 0.416315E+00}, + {-.703462E+00, 0.415413E+00}, {-.704040E+00, 0.414673E+00}, + {-.704490E+00, 0.414096E+00}, {-.704811E+00, 0.413684E+00}, + {-.705004E+00, 0.413437E+00}, {-.705068E+00, 0.413354E+00}, + }, + { + {-.406407E+00, 0.709243E+00}, {-.406493E+00, 0.709176E+00}, + {-.406751E+00, 0.708977E+00}, {-.407181E+00, 0.708645E+00}, + {-.407781E+00, 0.708181E+00}, {-.408552E+00, 0.707584E+00}, + {-.409493E+00, 0.706856E+00}, {-.410601E+00, 0.705996E+00}, + {-.411875E+00, 0.705005E+00}, {-.413315E+00, 0.703884E+00}, + {-.414917E+00, 0.702632E+00}, {-.416679E+00, 0.701252E+00}, + {-.418600E+00, 0.699743E+00}, {-.420677E+00, 0.698107E+00}, + {-.422906E+00, 0.696345E+00}, {-.425285E+00, 0.694457E+00}, + {-.427811E+00, 0.692444E+00}, {-.430481E+00, 0.690309E+00}, + {-.433290E+00, 0.688052E+00}, {-.436236E+00, 0.685675E+00}, + {-.439314E+00, 0.683179E+00}, {-.442520E+00, 0.680565E+00}, + {-.445851E+00, 0.677837E+00}, {-.449302E+00, 0.674995E+00}, + {-.452868E+00, 0.672041E+00}, {-.456546E+00, 0.668978E+00}, + {-.460330E+00, 0.665807E+00}, {-.464216E+00, 0.662531E+00}, + {-.468198E+00, 0.659152E+00}, {-.472273E+00, 0.655673E+00}, + {-.476434E+00, 0.652097E+00}, {-.480678E+00, 0.648425E+00}, + {-.484998E+00, 0.644661E+00}, {-.489390E+00, 0.640808E+00}, + {-.493849E+00, 0.636869E+00}, {-.498368E+00, 0.632848E+00}, + {-.502944E+00, 0.628746E+00}, {-.507570E+00, 0.624569E+00}, + {-.512241E+00, 0.620319E+00}, {-.516952E+00, 0.616000E+00}, + {-.521698E+00, 0.611616E+00}, {-.526473E+00, 0.607171E+00}, + {-.531272E+00, 0.602668E+00}, {-.536089E+00, 0.598112E+00}, + {-.540921E+00, 0.593508E+00}, {-.545760E+00, 0.588858E+00}, + {-.550603E+00, 0.584169E+00}, {-.555443E+00, 0.579443E+00}, + {-.560277E+00, 0.574687E+00}, {-.565099E+00, 0.569904E+00}, + {-.569904E+00, 0.565099E+00}, {-.574687E+00, 0.560277E+00}, + {-.579443E+00, 0.555443E+00}, {-.584169E+00, 0.550603E+00}, + {-.588858E+00, 0.545760E+00}, {-.593508E+00, 0.540921E+00}, + {-.598112E+00, 0.536089E+00}, {-.602668E+00, 0.531272E+00}, + {-.607171E+00, 0.526473E+00}, {-.611616E+00, 0.521698E+00}, + {-.616000E+00, 0.516952E+00}, {-.620319E+00, 0.512241E+00}, + {-.624569E+00, 0.507570E+00}, {-.628746E+00, 0.502944E+00}, + {-.632848E+00, 0.498368E+00}, {-.636869E+00, 0.493849E+00}, + {-.640808E+00, 0.489390E+00}, {-.644661E+00, 0.484998E+00}, + {-.648425E+00, 0.480678E+00}, {-.652097E+00, 0.476434E+00}, + {-.655673E+00, 0.472273E+00}, {-.659152E+00, 0.468198E+00}, + {-.662531E+00, 0.464216E+00}, {-.665807E+00, 0.460330E+00}, + {-.668978E+00, 0.456546E+00}, {-.672041E+00, 0.452868E+00}, + {-.674995E+00, 0.449302E+00}, {-.677837E+00, 0.445851E+00}, + {-.680565E+00, 0.442520E+00}, {-.683179E+00, 0.439314E+00}, + {-.685675E+00, 0.436236E+00}, {-.688052E+00, 0.433290E+00}, + {-.690309E+00, 0.430481E+00}, {-.692444E+00, 0.427811E+00}, + {-.694457E+00, 0.425285E+00}, {-.696345E+00, 0.422906E+00}, + {-.698107E+00, 0.420677E+00}, {-.699743E+00, 0.418600E+00}, + {-.701252E+00, 0.416679E+00}, {-.702632E+00, 0.414917E+00}, + {-.703884E+00, 0.413315E+00}, {-.705005E+00, 0.411875E+00}, + {-.705996E+00, 0.410601E+00}, {-.706856E+00, 0.409493E+00}, + {-.707584E+00, 0.408552E+00}, {-.708181E+00, 0.407781E+00}, + {-.708645E+00, 0.407181E+00}, {-.708977E+00, 0.406751E+00}, + {-.709176E+00, 0.406493E+00}, {-.709243E+00, 0.406407E+00}, + }, + { + {-.399361E+00, 0.713398E+00}, {-.399450E+00, 0.713329E+00}, + {-.399719E+00, 0.713124E+00}, {-.400167E+00, 0.712782E+00}, + {-.400792E+00, 0.712303E+00}, {-.401595E+00, 0.711687E+00}, + {-.402574E+00, 0.710936E+00}, {-.403728E+00, 0.710049E+00}, + {-.405054E+00, 0.709026E+00}, {-.406553E+00, 0.707869E+00}, + {-.408221E+00, 0.706578E+00}, {-.410055E+00, 0.705154E+00}, + {-.412055E+00, 0.703597E+00}, {-.414216E+00, 0.701908E+00}, + {-.416537E+00, 0.700089E+00}, {-.419013E+00, 0.698140E+00}, + {-.421642E+00, 0.696063E+00}, {-.424420E+00, 0.693859E+00}, + {-.427343E+00, 0.691528E+00}, {-.430408E+00, 0.689073E+00}, + {-.433611E+00, 0.686496E+00}, {-.436947E+00, 0.683797E+00}, + {-.440412E+00, 0.680978E+00}, {-.444001E+00, 0.678042E+00}, + {-.447711E+00, 0.674990E+00}, {-.451535E+00, 0.671825E+00}, + {-.455471E+00, 0.668548E+00}, {-.459511E+00, 0.665162E+00}, + {-.463652E+00, 0.661669E+00}, {-.467888E+00, 0.658072E+00}, + {-.472214E+00, 0.654374E+00}, {-.476625E+00, 0.650577E+00}, + {-.481115E+00, 0.646684E+00}, {-.485679E+00, 0.642698E+00}, + {-.490312E+00, 0.638623E+00}, {-.495008E+00, 0.634461E+00}, + {-.499761E+00, 0.630217E+00}, {-.504566E+00, 0.625893E+00}, + {-.509418E+00, 0.621493E+00}, {-.514310E+00, 0.617021E+00}, + {-.519238E+00, 0.612482E+00}, {-.524195E+00, 0.607878E+00}, + {-.529177E+00, 0.603214E+00}, {-.534177E+00, 0.598494E+00}, + {-.539191E+00, 0.593723E+00}, {-.544213E+00, 0.588905E+00}, + {-.549238E+00, 0.584045E+00}, {-.554259E+00, 0.579147E+00}, + {-.559273E+00, 0.574216E+00}, {-.564274E+00, 0.569257E+00}, + {-.569257E+00, 0.564274E+00}, {-.574216E+00, 0.559273E+00}, + {-.579147E+00, 0.554259E+00}, {-.584045E+00, 0.549238E+00}, + {-.588905E+00, 0.544213E+00}, {-.593723E+00, 0.539191E+00}, + {-.598494E+00, 0.534177E+00}, {-.603214E+00, 0.529177E+00}, + {-.607878E+00, 0.524195E+00}, {-.612482E+00, 0.519238E+00}, + {-.617021E+00, 0.514310E+00}, {-.621493E+00, 0.509418E+00}, + {-.625893E+00, 0.504566E+00}, {-.630217E+00, 0.499761E+00}, + {-.634461E+00, 0.495008E+00}, {-.638623E+00, 0.490312E+00}, + {-.642698E+00, 0.485679E+00}, {-.646684E+00, 0.481115E+00}, + {-.650577E+00, 0.476625E+00}, {-.654374E+00, 0.472214E+00}, + {-.658072E+00, 0.467888E+00}, {-.661669E+00, 0.463652E+00}, + {-.665162E+00, 0.459511E+00}, {-.668548E+00, 0.455471E+00}, + {-.671825E+00, 0.451535E+00}, {-.674990E+00, 0.447711E+00}, + {-.678042E+00, 0.444001E+00}, {-.680978E+00, 0.440412E+00}, + {-.683797E+00, 0.436947E+00}, {-.686496E+00, 0.433611E+00}, + {-.689073E+00, 0.430408E+00}, {-.691528E+00, 0.427343E+00}, + {-.693859E+00, 0.424420E+00}, {-.696063E+00, 0.421642E+00}, + {-.698140E+00, 0.419013E+00}, {-.700089E+00, 0.416537E+00}, + {-.701908E+00, 0.414216E+00}, {-.703597E+00, 0.412055E+00}, + {-.705154E+00, 0.410055E+00}, {-.706578E+00, 0.408221E+00}, + {-.707869E+00, 0.406553E+00}, {-.709026E+00, 0.405054E+00}, + {-.710049E+00, 0.403728E+00}, {-.710936E+00, 0.402574E+00}, + {-.711687E+00, 0.401595E+00}, {-.712303E+00, 0.400792E+00}, + {-.712782E+00, 0.400167E+00}, {-.713124E+00, 0.399719E+00}, + {-.713329E+00, 0.399450E+00}, {-.713398E+00, 0.399361E+00}, + }, + { + {-.392213E+00, 0.717535E+00}, {-.392306E+00, 0.717464E+00}, + {-.392586E+00, 0.717252E+00}, {-.393051E+00, 0.716900E+00}, + {-.393702E+00, 0.716406E+00}, {-.394537E+00, 0.715772E+00}, + {-.395555E+00, 0.714998E+00}, {-.396755E+00, 0.714083E+00}, + {-.398135E+00, 0.713030E+00}, {-.399693E+00, 0.711837E+00}, + {-.401428E+00, 0.710506E+00}, {-.403336E+00, 0.709038E+00}, + {-.405415E+00, 0.707433E+00}, {-.407663E+00, 0.705692E+00}, + {-.410075E+00, 0.703817E+00}, {-.412650E+00, 0.701807E+00}, + {-.415383E+00, 0.699665E+00}, {-.418271E+00, 0.697392E+00}, + {-.421310E+00, 0.694988E+00}, {-.424496E+00, 0.692456E+00}, + {-.427825E+00, 0.689797E+00}, {-.431292E+00, 0.687012E+00}, + {-.434893E+00, 0.684103E+00}, {-.438623E+00, 0.681073E+00}, + {-.442477E+00, 0.677923E+00}, {-.446451E+00, 0.674656E+00}, + {-.450539E+00, 0.671272E+00}, {-.454736E+00, 0.667776E+00}, + {-.459037E+00, 0.664169E+00}, {-.463436E+00, 0.660454E+00}, + {-.467929E+00, 0.656634E+00}, {-.472509E+00, 0.652711E+00}, + {-.477171E+00, 0.648689E+00}, {-.481909E+00, 0.644570E+00}, + {-.486718E+00, 0.640358E+00}, {-.491592E+00, 0.636056E+00}, + {-.496524E+00, 0.631668E+00}, {-.501511E+00, 0.627197E+00}, + {-.506544E+00, 0.622647E+00}, {-.511620E+00, 0.618022E+00}, + {-.516731E+00, 0.613326E+00}, {-.521873E+00, 0.608563E+00}, + {-.527039E+00, 0.603737E+00}, {-.532224E+00, 0.598852E+00}, + {-.537423E+00, 0.593914E+00}, {-.542628E+00, 0.588926E+00}, + {-.547836E+00, 0.583894E+00}, {-.553041E+00, 0.578822E+00}, + {-.558236E+00, 0.573715E+00}, {-.563417E+00, 0.568579E+00}, + {-.568579E+00, 0.563417E+00}, {-.573715E+00, 0.558236E+00}, + {-.578822E+00, 0.553041E+00}, {-.583894E+00, 0.547836E+00}, + {-.588926E+00, 0.542628E+00}, {-.593914E+00, 0.537423E+00}, + {-.598852E+00, 0.532224E+00}, {-.603737E+00, 0.527039E+00}, + {-.608563E+00, 0.521873E+00}, {-.613326E+00, 0.516731E+00}, + {-.618022E+00, 0.511620E+00}, {-.622647E+00, 0.506544E+00}, + {-.627197E+00, 0.501511E+00}, {-.631668E+00, 0.496524E+00}, + {-.636056E+00, 0.491592E+00}, {-.640358E+00, 0.486718E+00}, + {-.644570E+00, 0.481909E+00}, {-.648689E+00, 0.477171E+00}, + {-.652711E+00, 0.472509E+00}, {-.656634E+00, 0.467929E+00}, + {-.660454E+00, 0.463436E+00}, {-.664169E+00, 0.459037E+00}, + {-.667776E+00, 0.454736E+00}, {-.671272E+00, 0.450539E+00}, + {-.674656E+00, 0.446451E+00}, {-.677923E+00, 0.442477E+00}, + {-.681073E+00, 0.438623E+00}, {-.684103E+00, 0.434893E+00}, + {-.687012E+00, 0.431292E+00}, {-.689797E+00, 0.427825E+00}, + {-.692456E+00, 0.424496E+00}, {-.694988E+00, 0.421310E+00}, + {-.697392E+00, 0.418271E+00}, {-.699665E+00, 0.415383E+00}, + {-.701807E+00, 0.412650E+00}, {-.703817E+00, 0.410075E+00}, + {-.705692E+00, 0.407663E+00}, {-.707433E+00, 0.405415E+00}, + {-.709038E+00, 0.403336E+00}, {-.710506E+00, 0.401428E+00}, + {-.711837E+00, 0.399693E+00}, {-.713030E+00, 0.398135E+00}, + {-.714083E+00, 0.396755E+00}, {-.714998E+00, 0.395555E+00}, + {-.715772E+00, 0.394537E+00}, {-.716406E+00, 0.393702E+00}, + {-.716900E+00, 0.393051E+00}, {-.717252E+00, 0.392586E+00}, + {-.717464E+00, 0.392306E+00}, {-.717535E+00, 0.392213E+00}, + }, + { + {-.384961E+00, 0.721654E+00}, {-.385058E+00, 0.721581E+00}, + {-.385348E+00, 0.721363E+00}, {-.385832E+00, 0.721000E+00}, + {-.386508E+00, 0.720492E+00}, {-.387375E+00, 0.719839E+00}, + {-.388433E+00, 0.719042E+00}, {-.389680E+00, 0.718101E+00}, + {-.391114E+00, 0.717016E+00}, {-.392733E+00, 0.715788E+00}, + {-.394535E+00, 0.714418E+00}, {-.396518E+00, 0.712906E+00}, + {-.398678E+00, 0.711253E+00}, {-.401013E+00, 0.709460E+00}, + {-.403519E+00, 0.707528E+00}, {-.406193E+00, 0.705458E+00}, + {-.409032E+00, 0.703251E+00}, {-.412032E+00, 0.700909E+00}, + {-.415188E+00, 0.698432E+00}, {-.418496E+00, 0.695822E+00}, + {-.421953E+00, 0.693082E+00}, {-.425553E+00, 0.690211E+00}, + {-.429291E+00, 0.687213E+00}, {-.433164E+00, 0.684089E+00}, + {-.437165E+00, 0.680841E+00}, {-.441289E+00, 0.677471E+00}, + {-.445532E+00, 0.673981E+00}, {-.449888E+00, 0.670375E+00}, + {-.454351E+00, 0.666654E+00}, {-.458916E+00, 0.662820E+00}, + {-.463577E+00, 0.658878E+00}, {-.468328E+00, 0.654829E+00}, + {-.473164E+00, 0.650677E+00}, {-.478078E+00, 0.646424E+00}, + {-.483065E+00, 0.642075E+00}, {-.488118E+00, 0.637632E+00}, + {-.493233E+00, 0.633100E+00}, {-.498402E+00, 0.628481E+00}, + {-.503619E+00, 0.623781E+00}, {-.508880E+00, 0.619001E+00}, + {-.514177E+00, 0.614148E+00}, {-.519505E+00, 0.609225E+00}, + {-.524857E+00, 0.604236E+00}, {-.530229E+00, 0.599186E+00}, + {-.535613E+00, 0.594079E+00}, {-.541004E+00, 0.588921E+00}, + {-.546397E+00, 0.583716E+00}, {-.551786E+00, 0.578468E+00}, + {-.557164E+00, 0.573185E+00}, {-.562527E+00, 0.567869E+00}, + {-.567869E+00, 0.562527E+00}, {-.573185E+00, 0.557164E+00}, + {-.578468E+00, 0.551786E+00}, {-.583716E+00, 0.546397E+00}, + {-.588921E+00, 0.541004E+00}, {-.594079E+00, 0.535613E+00}, + {-.599186E+00, 0.530229E+00}, {-.604236E+00, 0.524857E+00}, + {-.609225E+00, 0.519505E+00}, {-.614148E+00, 0.514177E+00}, + {-.619001E+00, 0.508880E+00}, {-.623781E+00, 0.503619E+00}, + {-.628481E+00, 0.498402E+00}, {-.633100E+00, 0.493233E+00}, + {-.637632E+00, 0.488118E+00}, {-.642075E+00, 0.483065E+00}, + {-.646424E+00, 0.478078E+00}, {-.650677E+00, 0.473164E+00}, + {-.654829E+00, 0.468328E+00}, {-.658878E+00, 0.463577E+00}, + {-.662820E+00, 0.458916E+00}, {-.666654E+00, 0.454351E+00}, + {-.670375E+00, 0.449888E+00}, {-.673981E+00, 0.445532E+00}, + {-.677471E+00, 0.441289E+00}, {-.680841E+00, 0.437165E+00}, + {-.684089E+00, 0.433164E+00}, {-.687213E+00, 0.429291E+00}, + {-.690211E+00, 0.425553E+00}, {-.693082E+00, 0.421953E+00}, + {-.695822E+00, 0.418496E+00}, {-.698432E+00, 0.415188E+00}, + {-.700909E+00, 0.412032E+00}, {-.703251E+00, 0.409032E+00}, + {-.705458E+00, 0.406193E+00}, {-.707528E+00, 0.403519E+00}, + {-.709460E+00, 0.401013E+00}, {-.711253E+00, 0.398678E+00}, + {-.712906E+00, 0.396518E+00}, {-.714418E+00, 0.394535E+00}, + {-.715788E+00, 0.392733E+00}, {-.717016E+00, 0.391114E+00}, + {-.718101E+00, 0.389680E+00}, {-.719042E+00, 0.388433E+00}, + {-.719839E+00, 0.387375E+00}, {-.720492E+00, 0.386508E+00}, + {-.721000E+00, 0.385832E+00}, {-.721363E+00, 0.385348E+00}, + {-.721581E+00, 0.385058E+00}, {-.721654E+00, 0.384961E+00}, + }, + { + {-.377600E+00, 0.725756E+00}, {-.377701E+00, 0.725681E+00}, + {-.378002E+00, 0.725457E+00}, {-.378504E+00, 0.725083E+00}, + {-.379206E+00, 0.724561E+00}, {-.380107E+00, 0.723889E+00}, + {-.381206E+00, 0.723069E+00}, {-.382500E+00, 0.722101E+00}, + {-.383989E+00, 0.720985E+00}, {-.385669E+00, 0.719722E+00}, + {-.387540E+00, 0.718312E+00}, {-.389598E+00, 0.716757E+00}, + {-.391840E+00, 0.715056E+00}, {-.394263E+00, 0.713211E+00}, + {-.396865E+00, 0.711223E+00}, {-.399640E+00, 0.709093E+00}, + {-.402586E+00, 0.706822E+00}, {-.405699E+00, 0.704410E+00}, + {-.408974E+00, 0.701861E+00}, {-.412406E+00, 0.699174E+00}, + {-.415992E+00, 0.696352E+00}, {-.419727E+00, 0.693395E+00}, + {-.423605E+00, 0.690307E+00}, {-.427621E+00, 0.687089E+00}, + {-.431771E+00, 0.683743E+00}, {-.436049E+00, 0.680271E+00}, + {-.440448E+00, 0.676675E+00}, {-.444965E+00, 0.672958E+00}, + {-.449592E+00, 0.669122E+00}, {-.454324E+00, 0.665170E+00}, + {-.459155E+00, 0.661105E+00}, {-.464080E+00, 0.656930E+00}, + {-.469091E+00, 0.652648E+00}, {-.474184E+00, 0.648261E+00}, + {-.479351E+00, 0.643774E+00}, {-.484586E+00, 0.639190E+00}, + {-.489884E+00, 0.634513E+00}, {-.495238E+00, 0.629746E+00}, + {-.500642E+00, 0.624894E+00}, {-.506089E+00, 0.619959E+00}, + {-.511574E+00, 0.614948E+00}, {-.517089E+00, 0.609864E+00}, + {-.522630E+00, 0.604711E+00}, {-.528189E+00, 0.599494E+00}, + {-.533761E+00, 0.594218E+00}, {-.539340E+00, 0.588888E+00}, + {-.544919E+00, 0.583508E+00}, {-.550494E+00, 0.578085E+00}, + {-.556057E+00, 0.572623E+00}, {-.561603E+00, 0.567127E+00}, + {-.567127E+00, 0.561603E+00}, {-.572623E+00, 0.556057E+00}, + {-.578085E+00, 0.550494E+00}, {-.583508E+00, 0.544919E+00}, + {-.588888E+00, 0.539340E+00}, {-.594218E+00, 0.533761E+00}, + {-.599494E+00, 0.528189E+00}, {-.604711E+00, 0.522630E+00}, + {-.609864E+00, 0.517089E+00}, {-.614948E+00, 0.511574E+00}, + {-.619959E+00, 0.506089E+00}, {-.624894E+00, 0.500642E+00}, + {-.629746E+00, 0.495238E+00}, {-.634513E+00, 0.489884E+00}, + {-.639190E+00, 0.484586E+00}, {-.643774E+00, 0.479351E+00}, + {-.648261E+00, 0.474184E+00}, {-.652648E+00, 0.469091E+00}, + {-.656930E+00, 0.464080E+00}, {-.661105E+00, 0.459155E+00}, + {-.665170E+00, 0.454324E+00}, {-.669122E+00, 0.449592E+00}, + {-.672958E+00, 0.444965E+00}, {-.676675E+00, 0.440448E+00}, + {-.680271E+00, 0.436049E+00}, {-.683743E+00, 0.431771E+00}, + {-.687089E+00, 0.427621E+00}, {-.690307E+00, 0.423605E+00}, + {-.693395E+00, 0.419727E+00}, {-.696352E+00, 0.415992E+00}, + {-.699174E+00, 0.412406E+00}, {-.701861E+00, 0.408974E+00}, + {-.704410E+00, 0.405699E+00}, {-.706822E+00, 0.402586E+00}, + {-.709093E+00, 0.399640E+00}, {-.711223E+00, 0.396865E+00}, + {-.713211E+00, 0.394263E+00}, {-.715056E+00, 0.391840E+00}, + {-.716757E+00, 0.389598E+00}, {-.718312E+00, 0.387540E+00}, + {-.719722E+00, 0.385669E+00}, {-.720985E+00, 0.383989E+00}, + {-.722101E+00, 0.382500E+00}, {-.723069E+00, 0.381206E+00}, + {-.723889E+00, 0.380107E+00}, {-.724561E+00, 0.379206E+00}, + {-.725083E+00, 0.378504E+00}, {-.725457E+00, 0.378002E+00}, + {-.725681E+00, 0.377701E+00}, {-.725756E+00, 0.377600E+00}, + }, + { + {-.370128E+00, 0.729841E+00}, {-.370232E+00, 0.729764E+00}, + {-.370545E+00, 0.729534E+00}, {-.371066E+00, 0.729150E+00}, + {-.371794E+00, 0.728613E+00}, {-.372729E+00, 0.727923E+00}, + {-.373868E+00, 0.727080E+00}, {-.375211E+00, 0.726085E+00}, + {-.376755E+00, 0.724939E+00}, {-.378498E+00, 0.723640E+00}, + {-.380439E+00, 0.722191E+00}, {-.382573E+00, 0.720592E+00}, + {-.384898E+00, 0.718844E+00}, {-.387411E+00, 0.716947E+00}, + {-.390109E+00, 0.714903E+00}, {-.392987E+00, 0.712713E+00}, + {-.396042E+00, 0.710377E+00}, {-.399269E+00, 0.707897E+00}, + {-.402664E+00, 0.705274E+00}, {-.406223E+00, 0.702510E+00}, + {-.409941E+00, 0.699607E+00}, {-.413812E+00, 0.696565E+00}, + {-.417831E+00, 0.693387E+00}, {-.421994E+00, 0.690075E+00}, + {-.426294E+00, 0.686630E+00}, {-.430726E+00, 0.683056E+00}, + {-.435285E+00, 0.679353E+00}, {-.439964E+00, 0.675526E+00}, + {-.444757E+00, 0.671575E+00}, {-.449659E+00, 0.667504E+00}, + {-.454663E+00, 0.663317E+00}, {-.459763E+00, 0.659015E+00}, + {-.464952E+00, 0.654602E+00}, {-.470224E+00, 0.650081E+00}, + {-.475574E+00, 0.645456E+00}, {-.480993E+00, 0.640730E+00}, + {-.486477E+00, 0.635907E+00}, {-.492018E+00, 0.630991E+00}, + {-.497609E+00, 0.625986E+00}, {-.503246E+00, 0.620896E+00}, + {-.508920E+00, 0.615726E+00}, {-.514625E+00, 0.610479E+00}, + {-.520356E+00, 0.605161E+00}, {-.526105E+00, 0.599777E+00}, + {-.531867E+00, 0.594330E+00}, {-.537634E+00, 0.588827E+00}, + {-.543402E+00, 0.583272E+00}, {-.549164E+00, 0.577671E+00}, + {-.554913E+00, 0.572029E+00}, {-.560644E+00, 0.566351E+00}, + {-.566351E+00, 0.560644E+00}, {-.572029E+00, 0.554913E+00}, + {-.577671E+00, 0.549164E+00}, {-.583272E+00, 0.543402E+00}, + {-.588827E+00, 0.537634E+00}, {-.594330E+00, 0.531867E+00}, + {-.599777E+00, 0.526105E+00}, {-.605161E+00, 0.520356E+00}, + {-.610479E+00, 0.514625E+00}, {-.615726E+00, 0.508920E+00}, + {-.620896E+00, 0.503246E+00}, {-.625986E+00, 0.497609E+00}, + {-.630991E+00, 0.492018E+00}, {-.635907E+00, 0.486477E+00}, + {-.640730E+00, 0.480993E+00}, {-.645456E+00, 0.475574E+00}, + {-.650081E+00, 0.470224E+00}, {-.654602E+00, 0.464952E+00}, + {-.659015E+00, 0.459763E+00}, {-.663317E+00, 0.454663E+00}, + {-.667504E+00, 0.449659E+00}, {-.671575E+00, 0.444757E+00}, + {-.675526E+00, 0.439964E+00}, {-.679353E+00, 0.435285E+00}, + {-.683056E+00, 0.430726E+00}, {-.686630E+00, 0.426294E+00}, + {-.690075E+00, 0.421994E+00}, {-.693387E+00, 0.417831E+00}, + {-.696565E+00, 0.413812E+00}, {-.699607E+00, 0.409941E+00}, + {-.702510E+00, 0.406223E+00}, {-.705274E+00, 0.402664E+00}, + {-.707897E+00, 0.399269E+00}, {-.710377E+00, 0.396042E+00}, + {-.712713E+00, 0.392987E+00}, {-.714903E+00, 0.390109E+00}, + {-.716947E+00, 0.387411E+00}, {-.718844E+00, 0.384898E+00}, + {-.720592E+00, 0.382573E+00}, {-.722191E+00, 0.380439E+00}, + {-.723640E+00, 0.378498E+00}, {-.724939E+00, 0.376755E+00}, + {-.726085E+00, 0.375211E+00}, {-.727080E+00, 0.373868E+00}, + {-.727923E+00, 0.372729E+00}, {-.728613E+00, 0.371794E+00}, + {-.729150E+00, 0.371066E+00}, {-.729534E+00, 0.370545E+00}, + {-.729764E+00, 0.370232E+00}, {-.729841E+00, 0.370128E+00}, + }, + { + {-.362540E+00, 0.733910E+00}, {-.362649E+00, 0.733831E+00}, + {-.362973E+00, 0.733595E+00}, {-.363513E+00, 0.733201E+00}, + {-.364268E+00, 0.732650E+00}, {-.365236E+00, 0.731941E+00}, + {-.366417E+00, 0.731076E+00}, {-.367809E+00, 0.730054E+00}, + {-.369410E+00, 0.728877E+00}, {-.371217E+00, 0.727543E+00}, + {-.373228E+00, 0.726055E+00}, {-.375439E+00, 0.724413E+00}, + {-.377849E+00, 0.722617E+00}, {-.380454E+00, 0.720669E+00}, + {-.383249E+00, 0.718569E+00}, {-.386231E+00, 0.716318E+00}, + {-.389396E+00, 0.713918E+00}, {-.392740E+00, 0.711369E+00}, + {-.396257E+00, 0.708673E+00}, {-.399944E+00, 0.705832E+00}, + {-.403794E+00, 0.702847E+00}, {-.407804E+00, 0.699720E+00}, + {-.411967E+00, 0.696452E+00}, {-.416277E+00, 0.693046E+00}, + {-.420730E+00, 0.689503E+00}, {-.425320E+00, 0.685826E+00}, + {-.430039E+00, 0.682017E+00}, {-.434883E+00, 0.678078E+00}, + {-.439845E+00, 0.674013E+00}, {-.444918E+00, 0.669823E+00}, + {-.450097E+00, 0.665512E+00}, {-.455374E+00, 0.661083E+00}, + {-.460743E+00, 0.656539E+00}, {-.466198E+00, 0.651883E+00}, + {-.471732E+00, 0.647119E+00}, {-.477338E+00, 0.642251E+00}, + {-.483009E+00, 0.637281E+00}, {-.488739E+00, 0.632216E+00}, + {-.494521E+00, 0.627057E+00}, {-.500348E+00, 0.621811E+00}, + {-.506213E+00, 0.616481E+00}, {-.512111E+00, 0.611071E+00}, + {-.518033E+00, 0.605587E+00}, {-.523974E+00, 0.600033E+00}, + {-.529927E+00, 0.594415E+00}, {-.535886E+00, 0.588738E+00}, + {-.541844E+00, 0.583006E+00}, {-.547795E+00, 0.577226E+00}, + {-.553732E+00, 0.571402E+00}, {-.559650E+00, 0.565542E+00}, + {-.565542E+00, 0.559650E+00}, {-.571402E+00, 0.553732E+00}, + {-.577226E+00, 0.547795E+00}, {-.583006E+00, 0.541844E+00}, + {-.588738E+00, 0.535886E+00}, {-.594415E+00, 0.529927E+00}, + {-.600033E+00, 0.523974E+00}, {-.605587E+00, 0.518033E+00}, + {-.611071E+00, 0.512111E+00}, {-.616481E+00, 0.506213E+00}, + {-.621811E+00, 0.500348E+00}, {-.627057E+00, 0.494521E+00}, + {-.632216E+00, 0.488739E+00}, {-.637281E+00, 0.483009E+00}, + {-.642251E+00, 0.477338E+00}, {-.647119E+00, 0.471732E+00}, + {-.651883E+00, 0.466198E+00}, {-.656539E+00, 0.460743E+00}, + {-.661083E+00, 0.455374E+00}, {-.665512E+00, 0.450097E+00}, + {-.669823E+00, 0.444918E+00}, {-.674013E+00, 0.439845E+00}, + {-.678078E+00, 0.434883E+00}, {-.682017E+00, 0.430039E+00}, + {-.685826E+00, 0.425320E+00}, {-.689503E+00, 0.420730E+00}, + {-.693046E+00, 0.416277E+00}, {-.696452E+00, 0.411967E+00}, + {-.699720E+00, 0.407804E+00}, {-.702847E+00, 0.403794E+00}, + {-.705832E+00, 0.399944E+00}, {-.708673E+00, 0.396257E+00}, + {-.711369E+00, 0.392740E+00}, {-.713918E+00, 0.389396E+00}, + {-.716318E+00, 0.386231E+00}, {-.718569E+00, 0.383249E+00}, + {-.720669E+00, 0.380454E+00}, {-.722617E+00, 0.377849E+00}, + {-.724413E+00, 0.375439E+00}, {-.726055E+00, 0.373228E+00}, + {-.727543E+00, 0.371217E+00}, {-.728877E+00, 0.369410E+00}, + {-.730054E+00, 0.367809E+00}, {-.731076E+00, 0.366417E+00}, + {-.731941E+00, 0.365236E+00}, {-.732650E+00, 0.364268E+00}, + {-.733201E+00, 0.363513E+00}, {-.733595E+00, 0.362973E+00}, + {-.733831E+00, 0.362649E+00}, {-.733910E+00, 0.362540E+00}, + }, + { + {-.354834E+00, 0.737964E+00}, {-.354946E+00, 0.737884E+00}, + {-.355282E+00, 0.737641E+00}, {-.355841E+00, 0.737237E+00}, + {-.356623E+00, 0.736671E+00}, {-.357626E+00, 0.735944E+00}, + {-.358850E+00, 0.735057E+00}, {-.360291E+00, 0.734008E+00}, + {-.361949E+00, 0.732800E+00}, {-.363821E+00, 0.731432E+00}, + {-.365903E+00, 0.729904E+00}, {-.368194E+00, 0.728219E+00}, + {-.370690E+00, 0.726376E+00}, {-.373387E+00, 0.724376E+00}, + {-.376281E+00, 0.722220E+00}, {-.379369E+00, 0.719909E+00}, + {-.382646E+00, 0.717444E+00}, {-.386108E+00, 0.714827E+00}, + {-.389749E+00, 0.712059E+00}, {-.393565E+00, 0.709141E+00}, + {-.397551E+00, 0.706074E+00}, {-.401701E+00, 0.702861E+00}, + {-.406009E+00, 0.699504E+00}, {-.410470E+00, 0.696003E+00}, + {-.415077E+00, 0.692362E+00}, {-.419826E+00, 0.688582E+00}, + {-.424708E+00, 0.684666E+00}, {-.429719E+00, 0.680616E+00}, + {-.434851E+00, 0.676435E+00}, {-.440099E+00, 0.672126E+00}, + {-.445454E+00, 0.667692E+00}, {-.450911E+00, 0.663135E+00}, + {-.456463E+00, 0.658459E+00}, {-.462102E+00, 0.653668E+00}, + {-.467823E+00, 0.648764E+00}, {-.473617E+00, 0.643753E+00}, + {-.479478E+00, 0.638637E+00}, {-.485400E+00, 0.633420E+00}, + {-.491374E+00, 0.628108E+00}, {-.497394E+00, 0.622704E+00}, + {-.503453E+00, 0.617212E+00}, {-.509544E+00, 0.611639E+00}, + {-.515661E+00, 0.605987E+00}, {-.521796E+00, 0.600264E+00}, + {-.527942E+00, 0.594472E+00}, {-.534094E+00, 0.588619E+00}, + {-.540244E+00, 0.582709E+00}, {-.546385E+00, 0.576749E+00}, + {-.552512E+00, 0.570743E+00}, {-.558618E+00, 0.564697E+00}, + {-.564697E+00, 0.558618E+00}, {-.570743E+00, 0.552512E+00}, + {-.576749E+00, 0.546385E+00}, {-.582709E+00, 0.540244E+00}, + {-.588619E+00, 0.534094E+00}, {-.594472E+00, 0.527942E+00}, + {-.600264E+00, 0.521796E+00}, {-.605987E+00, 0.515661E+00}, + {-.611639E+00, 0.509544E+00}, {-.617212E+00, 0.503453E+00}, + {-.622704E+00, 0.497394E+00}, {-.628108E+00, 0.491374E+00}, + {-.633420E+00, 0.485400E+00}, {-.638637E+00, 0.479478E+00}, + {-.643753E+00, 0.473617E+00}, {-.648764E+00, 0.467823E+00}, + {-.653668E+00, 0.462102E+00}, {-.658459E+00, 0.456463E+00}, + {-.663135E+00, 0.450911E+00}, {-.667692E+00, 0.445454E+00}, + {-.672126E+00, 0.440099E+00}, {-.676435E+00, 0.434851E+00}, + {-.680616E+00, 0.429719E+00}, {-.684666E+00, 0.424708E+00}, + {-.688582E+00, 0.419826E+00}, {-.692362E+00, 0.415077E+00}, + {-.696003E+00, 0.410470E+00}, {-.699504E+00, 0.406009E+00}, + {-.702861E+00, 0.401701E+00}, {-.706074E+00, 0.397551E+00}, + {-.709141E+00, 0.393565E+00}, {-.712059E+00, 0.389749E+00}, + {-.714827E+00, 0.386108E+00}, {-.717444E+00, 0.382646E+00}, + {-.719909E+00, 0.379369E+00}, {-.722220E+00, 0.376281E+00}, + {-.724376E+00, 0.373387E+00}, {-.726376E+00, 0.370690E+00}, + {-.728219E+00, 0.368194E+00}, {-.729904E+00, 0.365903E+00}, + {-.731432E+00, 0.363821E+00}, {-.732800E+00, 0.361949E+00}, + {-.734008E+00, 0.360291E+00}, {-.735057E+00, 0.358850E+00}, + {-.735944E+00, 0.357626E+00}, {-.736671E+00, 0.356623E+00}, + {-.737237E+00, 0.355841E+00}, {-.737641E+00, 0.355282E+00}, + {-.737884E+00, 0.354946E+00}, {-.737964E+00, 0.354834E+00}, + }, + { + {-.347004E+00, 0.742004E+00}, {-.347120E+00, 0.741921E+00}, + {-.347468E+00, 0.741672E+00}, {-.348047E+00, 0.741258E+00}, + {-.348856E+00, 0.740678E+00}, {-.349895E+00, 0.739933E+00}, + {-.351161E+00, 0.739023E+00}, {-.352654E+00, 0.737948E+00}, + {-.354369E+00, 0.736709E+00}, {-.356307E+00, 0.735306E+00}, + {-.358462E+00, 0.733740E+00}, {-.360833E+00, 0.732011E+00}, + {-.363416E+00, 0.730121E+00}, {-.366207E+00, 0.728069E+00}, + {-.369202E+00, 0.725857E+00}, {-.372397E+00, 0.723487E+00}, + {-.375788E+00, 0.720958E+00}, {-.379369E+00, 0.718272E+00}, + {-.383136E+00, 0.715431E+00}, {-.387084E+00, 0.712436E+00}, + {-.391207E+00, 0.709288E+00}, {-.395499E+00, 0.705989E+00}, + {-.399954E+00, 0.702542E+00}, {-.404568E+00, 0.698947E+00}, + {-.409332E+00, 0.695207E+00}, {-.414242E+00, 0.691324E+00}, + {-.419290E+00, 0.687301E+00}, {-.424470E+00, 0.683140E+00}, + {-.429775E+00, 0.678843E+00}, {-.435199E+00, 0.674414E+00}, + {-.440733E+00, 0.669856E+00}, {-.446373E+00, 0.665171E+00}, + {-.452109E+00, 0.660363E+00}, {-.457935E+00, 0.655435E+00}, + {-.463845E+00, 0.650392E+00}, {-.469830E+00, 0.645236E+00}, + {-.475883E+00, 0.639972E+00}, {-.481998E+00, 0.634604E+00}, + {-.488167E+00, 0.629137E+00}, {-.494382E+00, 0.623574E+00}, + {-.500637E+00, 0.617921E+00}, {-.506925E+00, 0.612182E+00}, + {-.513237E+00, 0.606362E+00}, {-.519568E+00, 0.600467E+00}, + {-.525910E+00, 0.594501E+00}, {-.532257E+00, 0.588471E+00}, + {-.538600E+00, 0.582381E+00}, {-.544935E+00, 0.576238E+00}, + {-.551253E+00, 0.570048E+00}, {-.557549E+00, 0.563816E+00}, + {-.563816E+00, 0.557549E+00}, {-.570048E+00, 0.551253E+00}, + {-.576238E+00, 0.544935E+00}, {-.582381E+00, 0.538600E+00}, + {-.588471E+00, 0.532257E+00}, {-.594501E+00, 0.525910E+00}, + {-.600467E+00, 0.519568E+00}, {-.606362E+00, 0.513237E+00}, + {-.612182E+00, 0.506925E+00}, {-.617921E+00, 0.500637E+00}, + {-.623574E+00, 0.494382E+00}, {-.629137E+00, 0.488167E+00}, + {-.634604E+00, 0.481998E+00}, {-.639972E+00, 0.475883E+00}, + {-.645236E+00, 0.469830E+00}, {-.650392E+00, 0.463845E+00}, + {-.655435E+00, 0.457935E+00}, {-.660363E+00, 0.452109E+00}, + {-.665171E+00, 0.446373E+00}, {-.669856E+00, 0.440733E+00}, + {-.674414E+00, 0.435199E+00}, {-.678843E+00, 0.429775E+00}, + {-.683140E+00, 0.424470E+00}, {-.687301E+00, 0.419290E+00}, + {-.691324E+00, 0.414242E+00}, {-.695207E+00, 0.409332E+00}, + {-.698947E+00, 0.404568E+00}, {-.702542E+00, 0.399954E+00}, + {-.705989E+00, 0.395499E+00}, {-.709288E+00, 0.391207E+00}, + {-.712436E+00, 0.387084E+00}, {-.715431E+00, 0.383136E+00}, + {-.718272E+00, 0.379369E+00}, {-.720958E+00, 0.375788E+00}, + {-.723487E+00, 0.372397E+00}, {-.725857E+00, 0.369202E+00}, + {-.728069E+00, 0.366207E+00}, {-.730121E+00, 0.363416E+00}, + {-.732011E+00, 0.360833E+00}, {-.733740E+00, 0.358462E+00}, + {-.735306E+00, 0.356307E+00}, {-.736709E+00, 0.354369E+00}, + {-.737948E+00, 0.352654E+00}, {-.739023E+00, 0.351161E+00}, + {-.739933E+00, 0.349895E+00}, {-.740678E+00, 0.348856E+00}, + {-.741258E+00, 0.348047E+00}, {-.741672E+00, 0.347468E+00}, + {-.741921E+00, 0.347120E+00}, {-.742004E+00, 0.347004E+00}, + }, + { + {-.339048E+00, 0.746030E+00}, {-.339168E+00, 0.745945E+00}, + {-.339528E+00, 0.745690E+00}, {-.340127E+00, 0.745266E+00}, + {-.340964E+00, 0.744672E+00}, {-.342038E+00, 0.743908E+00}, + {-.343348E+00, 0.742976E+00}, {-.344892E+00, 0.741874E+00}, + {-.346667E+00, 0.740604E+00}, {-.348670E+00, 0.739167E+00}, + {-.350900E+00, 0.737562E+00}, {-.353352E+00, 0.735790E+00}, + {-.356023E+00, 0.733852E+00}, {-.358910E+00, 0.731749E+00}, + {-.362007E+00, 0.729482E+00}, {-.365311E+00, 0.727051E+00}, + {-.368817E+00, 0.724458E+00}, {-.372520E+00, 0.721704E+00}, + {-.376415E+00, 0.718790E+00}, {-.380496E+00, 0.715718E+00}, + {-.384758E+00, 0.712489E+00}, {-.389195E+00, 0.709104E+00}, + {-.393800E+00, 0.705567E+00}, {-.398568E+00, 0.701877E+00}, + {-.403492E+00, 0.698039E+00}, {-.408565E+00, 0.694053E+00}, + {-.413781E+00, 0.689922E+00}, {-.419133E+00, 0.685649E+00}, + {-.424613E+00, 0.681236E+00}, {-.430215E+00, 0.676687E+00}, + {-.435931E+00, 0.672004E+00}, {-.441755E+00, 0.667191E+00}, + {-.447678E+00, 0.662250E+00}, {-.453694E+00, 0.657185E+00}, + {-.459795E+00, 0.652001E+00}, {-.465973E+00, 0.646700E+00}, + {-.472221E+00, 0.641288E+00}, {-.478532E+00, 0.635768E+00}, + {-.484898E+00, 0.630144E+00}, {-.491311E+00, 0.624421E+00}, + {-.497764E+00, 0.618605E+00}, {-.504250E+00, 0.612700E+00}, + {-.510761E+00, 0.606710E+00}, {-.517289E+00, 0.600642E+00}, + {-.523829E+00, 0.594501E+00}, {-.530372E+00, 0.588292E+00}, + {-.536912E+00, 0.582021E+00}, {-.543441E+00, 0.575695E+00}, + {-.549953E+00, 0.569318E+00}, {-.556441E+00, 0.562898E+00}, + {-.562898E+00, 0.556441E+00}, {-.569318E+00, 0.549953E+00}, + {-.575695E+00, 0.543441E+00}, {-.582021E+00, 0.536912E+00}, + {-.588292E+00, 0.530372E+00}, {-.594501E+00, 0.523829E+00}, + {-.600642E+00, 0.517289E+00}, {-.606710E+00, 0.510761E+00}, + {-.612700E+00, 0.504250E+00}, {-.618605E+00, 0.497764E+00}, + {-.624421E+00, 0.491311E+00}, {-.630144E+00, 0.484898E+00}, + {-.635768E+00, 0.478532E+00}, {-.641288E+00, 0.472221E+00}, + {-.646700E+00, 0.465973E+00}, {-.652001E+00, 0.459795E+00}, + {-.657185E+00, 0.453694E+00}, {-.662250E+00, 0.447678E+00}, + {-.667191E+00, 0.441755E+00}, {-.672004E+00, 0.435931E+00}, + {-.676687E+00, 0.430215E+00}, {-.681236E+00, 0.424613E+00}, + {-.685649E+00, 0.419133E+00}, {-.689922E+00, 0.413781E+00}, + {-.694053E+00, 0.408565E+00}, {-.698039E+00, 0.403492E+00}, + {-.701877E+00, 0.398568E+00}, {-.705567E+00, 0.393800E+00}, + {-.709104E+00, 0.389195E+00}, {-.712489E+00, 0.384758E+00}, + {-.715718E+00, 0.380496E+00}, {-.718790E+00, 0.376415E+00}, + {-.721704E+00, 0.372520E+00}, {-.724458E+00, 0.368817E+00}, + {-.727051E+00, 0.365311E+00}, {-.729482E+00, 0.362007E+00}, + {-.731749E+00, 0.358910E+00}, {-.733852E+00, 0.356023E+00}, + {-.735790E+00, 0.353352E+00}, {-.737562E+00, 0.350900E+00}, + {-.739167E+00, 0.348670E+00}, {-.740604E+00, 0.346667E+00}, + {-.741874E+00, 0.344892E+00}, {-.742976E+00, 0.343348E+00}, + {-.743908E+00, 0.342038E+00}, {-.744672E+00, 0.340964E+00}, + {-.745266E+00, 0.340127E+00}, {-.745690E+00, 0.339528E+00}, + {-.745945E+00, 0.339168E+00}, {-.746030E+00, 0.339048E+00}, + }, + { + {-.330960E+00, 0.750042E+00}, {-.331084E+00, 0.749955E+00}, + {-.331456E+00, 0.749695E+00}, {-.332075E+00, 0.749260E+00}, + {-.332941E+00, 0.748652E+00}, {-.334052E+00, 0.747870E+00}, + {-.335406E+00, 0.746915E+00}, {-.337002E+00, 0.745787E+00}, + {-.338837E+00, 0.744487E+00}, {-.340908E+00, 0.743015E+00}, + {-.343213E+00, 0.741371E+00}, {-.345748E+00, 0.739556E+00}, + {-.348509E+00, 0.737572E+00}, {-.351492E+00, 0.735417E+00}, + {-.354694E+00, 0.733094E+00}, {-.358108E+00, 0.730604E+00}, + {-.361732E+00, 0.727947E+00}, {-.365558E+00, 0.725124E+00}, + {-.369583E+00, 0.722137E+00}, {-.373799E+00, 0.718988E+00}, + {-.378203E+00, 0.715677E+00}, {-.382786E+00, 0.712207E+00}, + {-.387543E+00, 0.708579E+00}, {-.392468E+00, 0.704795E+00}, + {-.397553E+00, 0.700857E+00}, {-.402792E+00, 0.696768E+00}, + {-.408178E+00, 0.692529E+00}, {-.413704E+00, 0.688144E+00}, + {-.419362E+00, 0.683615E+00}, {-.425145E+00, 0.678945E+00}, + {-.431045E+00, 0.674137E+00}, {-.437056E+00, 0.669194E+00}, + {-.443169E+00, 0.664120E+00}, {-.449377E+00, 0.658918E+00}, + {-.455671E+00, 0.653592E+00}, {-.462045E+00, 0.648146E+00}, + {-.468491E+00, 0.642584E+00}, {-.474999E+00, 0.636910E+00}, + {-.481564E+00, 0.631129E+00}, {-.488177E+00, 0.625246E+00}, + {-.494831E+00, 0.619265E+00}, {-.501517E+00, 0.613192E+00}, + {-.508229E+00, 0.607031E+00}, {-.514958E+00, 0.600789E+00}, + {-.521698E+00, 0.594471E+00}, {-.528440E+00, 0.588082E+00}, + {-.535178E+00, 0.581628E+00}, {-.541904E+00, 0.575116E+00}, + {-.548611E+00, 0.568552E+00}, {-.555293E+00, 0.561942E+00}, + {-.561942E+00, 0.555293E+00}, {-.568552E+00, 0.548611E+00}, + {-.575116E+00, 0.541904E+00}, {-.581628E+00, 0.535178E+00}, + {-.588082E+00, 0.528440E+00}, {-.594471E+00, 0.521698E+00}, + {-.600789E+00, 0.514958E+00}, {-.607031E+00, 0.508229E+00}, + {-.613192E+00, 0.501517E+00}, {-.619265E+00, 0.494831E+00}, + {-.625246E+00, 0.488177E+00}, {-.631129E+00, 0.481564E+00}, + {-.636910E+00, 0.474999E+00}, {-.642584E+00, 0.468491E+00}, + {-.648146E+00, 0.462045E+00}, {-.653592E+00, 0.455671E+00}, + {-.658918E+00, 0.449377E+00}, {-.664120E+00, 0.443169E+00}, + {-.669194E+00, 0.437056E+00}, {-.674137E+00, 0.431045E+00}, + {-.678945E+00, 0.425145E+00}, {-.683615E+00, 0.419362E+00}, + {-.688144E+00, 0.413704E+00}, {-.692529E+00, 0.408178E+00}, + {-.696768E+00, 0.402792E+00}, {-.700857E+00, 0.397553E+00}, + {-.704795E+00, 0.392468E+00}, {-.708579E+00, 0.387543E+00}, + {-.712207E+00, 0.382786E+00}, {-.715677E+00, 0.378203E+00}, + {-.718988E+00, 0.373799E+00}, {-.722137E+00, 0.369583E+00}, + {-.725124E+00, 0.365558E+00}, {-.727947E+00, 0.361732E+00}, + {-.730604E+00, 0.358108E+00}, {-.733094E+00, 0.354694E+00}, + {-.735417E+00, 0.351492E+00}, {-.737572E+00, 0.348509E+00}, + {-.739556E+00, 0.345748E+00}, {-.741371E+00, 0.343213E+00}, + {-.743015E+00, 0.340908E+00}, {-.744487E+00, 0.338837E+00}, + {-.745787E+00, 0.337002E+00}, {-.746915E+00, 0.335406E+00}, + {-.747870E+00, 0.334052E+00}, {-.748652E+00, 0.332941E+00}, + {-.749260E+00, 0.332075E+00}, {-.749695E+00, 0.331456E+00}, + {-.749955E+00, 0.331084E+00}, {-.750042E+00, 0.330960E+00}, + }, + { + {-.322736E+00, 0.754042E+00}, {-.322864E+00, 0.753953E+00}, + {-.323249E+00, 0.753686E+00}, {-.323889E+00, 0.753242E+00}, + {-.324783E+00, 0.752619E+00}, {-.325931E+00, 0.751820E+00}, + {-.327331E+00, 0.750842E+00}, {-.328980E+00, 0.749688E+00}, + {-.330876E+00, 0.748358E+00}, {-.333016E+00, 0.746851E+00}, + {-.335397E+00, 0.745168E+00}, {-.338016E+00, 0.743311E+00}, + {-.340868E+00, 0.741279E+00}, {-.343950E+00, 0.739073E+00}, + {-.347257E+00, 0.736695E+00}, {-.350784E+00, 0.734144E+00}, + {-.354526E+00, 0.731423E+00}, {-.358478E+00, 0.728532E+00}, + {-.362635E+00, 0.725472E+00}, {-.366989E+00, 0.722246E+00}, + {-.371536E+00, 0.718853E+00}, {-.376268E+00, 0.715297E+00}, + {-.381179E+00, 0.711579E+00}, {-.386263E+00, 0.707700E+00}, + {-.391512E+00, 0.703663E+00}, {-.396920E+00, 0.699470E+00}, + {-.402478E+00, 0.695123E+00}, {-.408180E+00, 0.690626E+00}, + {-.414019E+00, 0.685980E+00}, {-.419985E+00, 0.681188E+00}, + {-.426073E+00, 0.676255E+00}, {-.432273E+00, 0.671182E+00}, + {-.438578E+00, 0.665974E+00}, {-.444980E+00, 0.660633E+00}, + {-.451471E+00, 0.655165E+00}, {-.458043E+00, 0.649572E+00}, + {-.464688E+00, 0.643859E+00}, {-.471398E+00, 0.638031E+00}, + {-.478165E+00, 0.632092E+00}, {-.484980E+00, 0.626047E+00}, + {-.491837E+00, 0.619900E+00}, {-.498726E+00, 0.613658E+00}, + {-.505641E+00, 0.607325E+00}, {-.512573E+00, 0.600907E+00}, + {-.519514E+00, 0.594410E+00}, {-.526458E+00, 0.587839E+00}, + {-.533396E+00, 0.581201E+00}, {-.540321E+00, 0.574502E+00}, + {-.547226E+00, 0.567748E+00}, {-.554103E+00, 0.560946E+00}, + {-.560946E+00, 0.554103E+00}, {-.567748E+00, 0.547226E+00}, + {-.574502E+00, 0.540321E+00}, {-.581201E+00, 0.533396E+00}, + {-.587839E+00, 0.526458E+00}, {-.594410E+00, 0.519514E+00}, + {-.600907E+00, 0.512573E+00}, {-.607325E+00, 0.505641E+00}, + {-.613658E+00, 0.498726E+00}, {-.619900E+00, 0.491837E+00}, + {-.626047E+00, 0.484980E+00}, {-.632092E+00, 0.478165E+00}, + {-.638031E+00, 0.471398E+00}, {-.643859E+00, 0.464688E+00}, + {-.649572E+00, 0.458043E+00}, {-.655165E+00, 0.451471E+00}, + {-.660633E+00, 0.444980E+00}, {-.665974E+00, 0.438578E+00}, + {-.671182E+00, 0.432273E+00}, {-.676255E+00, 0.426073E+00}, + {-.681188E+00, 0.419985E+00}, {-.685980E+00, 0.414019E+00}, + {-.690626E+00, 0.408180E+00}, {-.695123E+00, 0.402478E+00}, + {-.699470E+00, 0.396920E+00}, {-.703663E+00, 0.391512E+00}, + {-.707700E+00, 0.386263E+00}, {-.711579E+00, 0.381179E+00}, + {-.715297E+00, 0.376268E+00}, {-.718853E+00, 0.371536E+00}, + {-.722246E+00, 0.366989E+00}, {-.725472E+00, 0.362635E+00}, + {-.728532E+00, 0.358478E+00}, {-.731423E+00, 0.354526E+00}, + {-.734144E+00, 0.350784E+00}, {-.736695E+00, 0.347257E+00}, + {-.739073E+00, 0.343950E+00}, {-.741279E+00, 0.340868E+00}, + {-.743311E+00, 0.338016E+00}, {-.745168E+00, 0.335397E+00}, + {-.746851E+00, 0.333016E+00}, {-.748358E+00, 0.330876E+00}, + {-.749688E+00, 0.328980E+00}, {-.750842E+00, 0.327331E+00}, + {-.751820E+00, 0.325931E+00}, {-.752619E+00, 0.324783E+00}, + {-.753242E+00, 0.323889E+00}, {-.753686E+00, 0.323249E+00}, + {-.753953E+00, 0.322864E+00}, {-.754042E+00, 0.322736E+00}, + }, + { + {-.314372E+00, 0.758030E+00}, {-.314504E+00, 0.757939E+00}, + {-.314901E+00, 0.757666E+00}, {-.315562E+00, 0.757212E+00}, + {-.316486E+00, 0.756575E+00}, {-.317672E+00, 0.755757E+00}, + {-.319117E+00, 0.754758E+00}, {-.320820E+00, 0.753578E+00}, + {-.322778E+00, 0.752217E+00}, {-.324988E+00, 0.750675E+00}, + {-.327447E+00, 0.748954E+00}, {-.330151E+00, 0.747054E+00}, + {-.333097E+00, 0.744975E+00}, {-.336279E+00, 0.742718E+00}, + {-.339693E+00, 0.740284E+00}, {-.343334E+00, 0.737674E+00}, + {-.347198E+00, 0.734888E+00}, {-.351277E+00, 0.731929E+00}, + {-.355567E+00, 0.728796E+00}, {-.360061E+00, 0.725492E+00}, + {-.364753E+00, 0.722018E+00}, {-.369637E+00, 0.718376E+00}, + {-.374705E+00, 0.714567E+00}, {-.379950E+00, 0.710593E+00}, + {-.385366E+00, 0.706456E+00}, {-.390944E+00, 0.702159E+00}, + {-.396678E+00, 0.697704E+00}, {-.402559E+00, 0.693094E+00}, + {-.408580E+00, 0.688330E+00}, {-.414733E+00, 0.683417E+00}, + {-.421010E+00, 0.678357E+00}, {-.427402E+00, 0.673154E+00}, + {-.433902E+00, 0.667810E+00}, {-.440502E+00, 0.662331E+00}, + {-.447192E+00, 0.656719E+00}, {-.453965E+00, 0.650979E+00}, + {-.460812E+00, 0.645114E+00}, {-.467725E+00, 0.639131E+00}, + {-.474696E+00, 0.633032E+00}, {-.481717E+00, 0.626824E+00}, + {-.488779E+00, 0.620510E+00}, {-.495874E+00, 0.614097E+00}, + {-.502994E+00, 0.607590E+00}, {-.510131E+00, 0.600995E+00}, + {-.517277E+00, 0.594317E+00}, {-.524424E+00, 0.587563E+00}, + {-.531565E+00, 0.580739E+00}, {-.538691E+00, 0.573851E+00}, + {-.545795E+00, 0.566906E+00}, {-.552871E+00, 0.559910E+00}, + {-.559910E+00, 0.552871E+00}, {-.566906E+00, 0.545795E+00}, + {-.573851E+00, 0.538691E+00}, {-.580739E+00, 0.531565E+00}, + {-.587563E+00, 0.524424E+00}, {-.594317E+00, 0.517277E+00}, + {-.600995E+00, 0.510131E+00}, {-.607590E+00, 0.502994E+00}, + {-.614097E+00, 0.495874E+00}, {-.620510E+00, 0.488779E+00}, + {-.626824E+00, 0.481717E+00}, {-.633032E+00, 0.474696E+00}, + {-.639131E+00, 0.467725E+00}, {-.645114E+00, 0.460812E+00}, + {-.650979E+00, 0.453965E+00}, {-.656719E+00, 0.447192E+00}, + {-.662331E+00, 0.440502E+00}, {-.667810E+00, 0.433902E+00}, + {-.673154E+00, 0.427402E+00}, {-.678357E+00, 0.421010E+00}, + {-.683417E+00, 0.414733E+00}, {-.688330E+00, 0.408580E+00}, + {-.693094E+00, 0.402559E+00}, {-.697704E+00, 0.396678E+00}, + {-.702159E+00, 0.390944E+00}, {-.706456E+00, 0.385366E+00}, + {-.710593E+00, 0.379950E+00}, {-.714567E+00, 0.374705E+00}, + {-.718376E+00, 0.369637E+00}, {-.722018E+00, 0.364753E+00}, + {-.725492E+00, 0.360061E+00}, {-.728796E+00, 0.355567E+00}, + {-.731929E+00, 0.351277E+00}, {-.734888E+00, 0.347198E+00}, + {-.737674E+00, 0.343334E+00}, {-.740284E+00, 0.339693E+00}, + {-.742718E+00, 0.336279E+00}, {-.744975E+00, 0.333097E+00}, + {-.747054E+00, 0.330151E+00}, {-.748954E+00, 0.327447E+00}, + {-.750675E+00, 0.324988E+00}, {-.752217E+00, 0.322778E+00}, + {-.753578E+00, 0.320820E+00}, {-.754758E+00, 0.319117E+00}, + {-.755757E+00, 0.317672E+00}, {-.756575E+00, 0.316486E+00}, + {-.757212E+00, 0.315562E+00}, {-.757666E+00, 0.314901E+00}, + {-.757939E+00, 0.314504E+00}, {-.758030E+00, 0.314372E+00}, + }, + { + {-.305862E+00, 0.762007E+00}, {-.305999E+00, 0.761914E+00}, + {-.306409E+00, 0.761635E+00}, {-.307091E+00, 0.761170E+00}, + {-.308045E+00, 0.760520E+00}, {-.309269E+00, 0.759684E+00}, + {-.310761E+00, 0.758662E+00}, {-.312519E+00, 0.757456E+00}, + {-.314540E+00, 0.756064E+00}, {-.316821E+00, 0.754489E+00}, + {-.319359E+00, 0.752729E+00}, {-.322150E+00, 0.750786E+00}, + {-.325190E+00, 0.748660E+00}, {-.328474E+00, 0.746352E+00}, + {-.331997E+00, 0.743862E+00}, {-.335755E+00, 0.741192E+00}, + {-.339741E+00, 0.738343E+00}, {-.343950E+00, 0.735314E+00}, + {-.348376E+00, 0.732109E+00}, {-.353012E+00, 0.728728E+00}, + {-.357852E+00, 0.725172E+00}, {-.362889E+00, 0.721443E+00}, + {-.368116E+00, 0.717543E+00}, {-.373526E+00, 0.713474E+00}, + {-.379110E+00, 0.709238E+00}, {-.384862E+00, 0.704836E+00}, + {-.390774E+00, 0.700272E+00}, {-.396837E+00, 0.695548E+00}, + {-.403044E+00, 0.690667E+00}, {-.409385E+00, 0.685631E+00}, + {-.415854E+00, 0.680444E+00}, {-.422442E+00, 0.675109E+00}, + {-.429139E+00, 0.669630E+00}, {-.435938E+00, 0.664011E+00}, + {-.442830E+00, 0.658254E+00}, {-.449807E+00, 0.652366E+00}, + {-.456859E+00, 0.646349E+00}, {-.463979E+00, 0.640208E+00}, + {-.471157E+00, 0.633949E+00}, {-.478385E+00, 0.627576E+00}, + {-.485655E+00, 0.621094E+00}, {-.492959E+00, 0.614509E+00}, + {-.500287E+00, 0.607827E+00}, {-.507631E+00, 0.601053E+00}, + {-.514984E+00, 0.594193E+00}, {-.522337E+00, 0.587253E+00}, + {-.529683E+00, 0.580241E+00}, {-.537012E+00, 0.573162E+00}, + {-.544319E+00, 0.566023E+00}, {-.551594E+00, 0.558832E+00}, + {-.558832E+00, 0.551594E+00}, {-.566023E+00, 0.544319E+00}, + {-.573162E+00, 0.537012E+00}, {-.580241E+00, 0.529683E+00}, + {-.587253E+00, 0.522337E+00}, {-.594193E+00, 0.514984E+00}, + {-.601053E+00, 0.507631E+00}, {-.607827E+00, 0.500287E+00}, + {-.614509E+00, 0.492959E+00}, {-.621094E+00, 0.485655E+00}, + {-.627576E+00, 0.478385E+00}, {-.633949E+00, 0.471157E+00}, + {-.640208E+00, 0.463979E+00}, {-.646349E+00, 0.456859E+00}, + {-.652366E+00, 0.449807E+00}, {-.658254E+00, 0.442830E+00}, + {-.664011E+00, 0.435938E+00}, {-.669630E+00, 0.429139E+00}, + {-.675109E+00, 0.422442E+00}, {-.680444E+00, 0.415854E+00}, + {-.685631E+00, 0.409385E+00}, {-.690667E+00, 0.403044E+00}, + {-.695548E+00, 0.396837E+00}, {-.700272E+00, 0.390774E+00}, + {-.704836E+00, 0.384862E+00}, {-.709238E+00, 0.379110E+00}, + {-.713474E+00, 0.373526E+00}, {-.717543E+00, 0.368116E+00}, + {-.721443E+00, 0.362889E+00}, {-.725172E+00, 0.357852E+00}, + {-.728728E+00, 0.353012E+00}, {-.732109E+00, 0.348376E+00}, + {-.735314E+00, 0.343950E+00}, {-.738343E+00, 0.339741E+00}, + {-.741192E+00, 0.335755E+00}, {-.743862E+00, 0.331997E+00}, + {-.746352E+00, 0.328474E+00}, {-.748660E+00, 0.325190E+00}, + {-.750786E+00, 0.322150E+00}, {-.752729E+00, 0.319359E+00}, + {-.754489E+00, 0.316821E+00}, {-.756064E+00, 0.314540E+00}, + {-.757456E+00, 0.312519E+00}, {-.758662E+00, 0.310761E+00}, + {-.759684E+00, 0.309269E+00}, {-.760520E+00, 0.308045E+00}, + {-.761170E+00, 0.307091E+00}, {-.761635E+00, 0.306409E+00}, + {-.761914E+00, 0.305999E+00}, {-.762007E+00, 0.305862E+00}, + }, + { + {-.297202E+00, 0.765973E+00}, {-.297343E+00, 0.765878E+00}, + {-.297766E+00, 0.765593E+00}, {-.298470E+00, 0.765118E+00}, + {-.299454E+00, 0.764454E+00}, {-.300717E+00, 0.763600E+00}, + {-.302257E+00, 0.762556E+00}, {-.304070E+00, 0.761324E+00}, + {-.306156E+00, 0.759902E+00}, {-.308509E+00, 0.758292E+00}, + {-.311128E+00, 0.756494E+00}, {-.314007E+00, 0.754508E+00}, + {-.317143E+00, 0.752335E+00}, {-.320530E+00, 0.749975E+00}, + {-.324165E+00, 0.747430E+00}, {-.328040E+00, 0.744701E+00}, + {-.332151E+00, 0.741787E+00}, {-.336492E+00, 0.738690E+00}, + {-.341056E+00, 0.735412E+00}, {-.345837E+00, 0.731953E+00}, + {-.350828E+00, 0.728315E+00}, {-.356021E+00, 0.724500E+00}, + {-.361409E+00, 0.720509E+00}, {-.366985E+00, 0.716344E+00}, + {-.372742E+00, 0.712007E+00}, {-.378670E+00, 0.707501E+00}, + {-.384762E+00, 0.702827E+00}, {-.391010E+00, 0.697989E+00}, + {-.397405E+00, 0.692989E+00}, {-.403938E+00, 0.687831E+00}, + {-.410602E+00, 0.682516E+00}, {-.417388E+00, 0.677049E+00}, + {-.424286E+00, 0.671434E+00}, {-.431287E+00, 0.665673E+00}, + {-.438384E+00, 0.659771E+00}, {-.445567E+00, 0.653733E+00}, + {-.452827E+00, 0.647562E+00}, {-.460156E+00, 0.641263E+00}, + {-.467544E+00, 0.634842E+00}, {-.474983E+00, 0.628303E+00}, + {-.482464E+00, 0.621651E+00}, {-.489978E+00, 0.614893E+00}, + {-.497516E+00, 0.608033E+00}, {-.505071E+00, 0.601079E+00}, + {-.512633E+00, 0.594035E+00}, {-.520195E+00, 0.586908E+00}, + {-.527748E+00, 0.579706E+00}, {-.535284E+00, 0.572434E+00}, + {-.542794E+00, 0.565099E+00}, {-.550272E+00, 0.557710E+00}, + {-.557710E+00, 0.550272E+00}, {-.565099E+00, 0.542794E+00}, + {-.572434E+00, 0.535284E+00}, {-.579706E+00, 0.527748E+00}, + {-.586908E+00, 0.520195E+00}, {-.594035E+00, 0.512633E+00}, + {-.601079E+00, 0.505071E+00}, {-.608033E+00, 0.497516E+00}, + {-.614893E+00, 0.489978E+00}, {-.621651E+00, 0.482464E+00}, + {-.628303E+00, 0.474983E+00}, {-.634842E+00, 0.467544E+00}, + {-.641263E+00, 0.460156E+00}, {-.647562E+00, 0.452827E+00}, + {-.653733E+00, 0.445567E+00}, {-.659771E+00, 0.438384E+00}, + {-.665673E+00, 0.431287E+00}, {-.671434E+00, 0.424286E+00}, + {-.677049E+00, 0.417388E+00}, {-.682516E+00, 0.410602E+00}, + {-.687831E+00, 0.403938E+00}, {-.692989E+00, 0.397405E+00}, + {-.697989E+00, 0.391010E+00}, {-.702827E+00, 0.384762E+00}, + {-.707501E+00, 0.378670E+00}, {-.712007E+00, 0.372742E+00}, + {-.716344E+00, 0.366985E+00}, {-.720509E+00, 0.361409E+00}, + {-.724500E+00, 0.356021E+00}, {-.728315E+00, 0.350828E+00}, + {-.731953E+00, 0.345837E+00}, {-.735412E+00, 0.341056E+00}, + {-.738690E+00, 0.336492E+00}, {-.741787E+00, 0.332151E+00}, + {-.744701E+00, 0.328040E+00}, {-.747430E+00, 0.324165E+00}, + {-.749975E+00, 0.320530E+00}, {-.752335E+00, 0.317143E+00}, + {-.754508E+00, 0.314007E+00}, {-.756494E+00, 0.311128E+00}, + {-.758292E+00, 0.308509E+00}, {-.759902E+00, 0.306156E+00}, + {-.761324E+00, 0.304070E+00}, {-.762556E+00, 0.302257E+00}, + {-.763600E+00, 0.300717E+00}, {-.764454E+00, 0.299454E+00}, + {-.765118E+00, 0.298470E+00}, {-.765593E+00, 0.297766E+00}, + {-.765878E+00, 0.297343E+00}, {-.765973E+00, 0.297202E+00}, + }, + { + {-.288386E+00, 0.769929E+00}, {-.288531E+00, 0.769832E+00}, + {-.288967E+00, 0.769541E+00}, {-.289694E+00, 0.769056E+00}, + {-.290709E+00, 0.768378E+00}, {-.292011E+00, 0.767506E+00}, + {-.293599E+00, 0.766440E+00}, {-.295470E+00, 0.765181E+00}, + {-.297620E+00, 0.763730E+00}, {-.300048E+00, 0.762085E+00}, + {-.302748E+00, 0.760249E+00}, {-.305717E+00, 0.758220E+00}, + {-.308951E+00, 0.756000E+00}, {-.312444E+00, 0.753590E+00}, + {-.316191E+00, 0.750989E+00}, {-.320186E+00, 0.748199E+00}, + {-.324425E+00, 0.745221E+00}, {-.328900E+00, 0.742056E+00}, + {-.333604E+00, 0.738704E+00}, {-.338532E+00, 0.735168E+00}, + {-.343675E+00, 0.731448E+00}, {-.349027E+00, 0.727546E+00}, + {-.354580E+00, 0.723463E+00}, {-.360325E+00, 0.719202E+00}, + {-.366256E+00, 0.714765E+00}, {-.372363E+00, 0.710154E+00}, + {-.378639E+00, 0.705370E+00}, {-.385074E+00, 0.700418E+00}, + {-.391660E+00, 0.695299E+00}, {-.398389E+00, 0.690016E+00}, + {-.405250E+00, 0.684573E+00}, {-.412236E+00, 0.678973E+00}, + {-.419338E+00, 0.673220E+00}, {-.426545E+00, 0.667317E+00}, + {-.433850E+00, 0.661269E+00}, {-.441242E+00, 0.655080E+00}, + {-.448713E+00, 0.648754E+00}, {-.456254E+00, 0.642296E+00}, + {-.463855E+00, 0.635711E+00}, {-.471507E+00, 0.629004E+00}, + {-.479201E+00, 0.622181E+00}, {-.486929E+00, 0.615248E+00}, + {-.494681E+00, 0.608209E+00}, {-.502449E+00, 0.601072E+00}, + {-.510223E+00, 0.593842E+00}, {-.517996E+00, 0.586527E+00}, + {-.525759E+00, 0.579132E+00}, {-.533503E+00, 0.571665E+00}, + {-.541220E+00, 0.564133E+00}, {-.548903E+00, 0.556543E+00}, + {-.556543E+00, 0.548903E+00}, {-.564133E+00, 0.541220E+00}, + {-.571665E+00, 0.533503E+00}, {-.579132E+00, 0.525759E+00}, + {-.586527E+00, 0.517996E+00}, {-.593842E+00, 0.510223E+00}, + {-.601072E+00, 0.502449E+00}, {-.608209E+00, 0.494681E+00}, + {-.615248E+00, 0.486929E+00}, {-.622181E+00, 0.479201E+00}, + {-.629004E+00, 0.471507E+00}, {-.635711E+00, 0.463855E+00}, + {-.642296E+00, 0.456254E+00}, {-.648754E+00, 0.448713E+00}, + {-.655080E+00, 0.441242E+00}, {-.661269E+00, 0.433850E+00}, + {-.667317E+00, 0.426545E+00}, {-.673220E+00, 0.419338E+00}, + {-.678973E+00, 0.412236E+00}, {-.684573E+00, 0.405250E+00}, + {-.690016E+00, 0.398389E+00}, {-.695299E+00, 0.391660E+00}, + {-.700418E+00, 0.385074E+00}, {-.705370E+00, 0.378639E+00}, + {-.710154E+00, 0.372363E+00}, {-.714765E+00, 0.366256E+00}, + {-.719202E+00, 0.360325E+00}, {-.723463E+00, 0.354580E+00}, + {-.727546E+00, 0.349027E+00}, {-.731448E+00, 0.343675E+00}, + {-.735168E+00, 0.338532E+00}, {-.738704E+00, 0.333604E+00}, + {-.742056E+00, 0.328900E+00}, {-.745221E+00, 0.324425E+00}, + {-.748199E+00, 0.320186E+00}, {-.750989E+00, 0.316191E+00}, + {-.753590E+00, 0.312444E+00}, {-.756000E+00, 0.308951E+00}, + {-.758220E+00, 0.305717E+00}, {-.760249E+00, 0.302748E+00}, + {-.762085E+00, 0.300048E+00}, {-.763730E+00, 0.297620E+00}, + {-.765181E+00, 0.295470E+00}, {-.766440E+00, 0.293599E+00}, + {-.767506E+00, 0.292011E+00}, {-.768378E+00, 0.290709E+00}, + {-.769056E+00, 0.289694E+00}, {-.769541E+00, 0.288967E+00}, + {-.769832E+00, 0.288531E+00}, {-.769929E+00, 0.288386E+00}, + }, + { + {-.279408E+00, 0.773876E+00}, {-.279557E+00, 0.773777E+00}, + {-.280007E+00, 0.773480E+00}, {-.280756E+00, 0.772985E+00}, + {-.281803E+00, 0.772293E+00}, {-.283146E+00, 0.771403E+00}, + {-.284783E+00, 0.770315E+00}, {-.286711E+00, 0.769030E+00}, + {-.288928E+00, 0.767548E+00}, {-.291431E+00, 0.765870E+00}, + {-.294214E+00, 0.763994E+00}, {-.297275E+00, 0.761923E+00}, + {-.300608E+00, 0.759657E+00}, {-.304208E+00, 0.757195E+00}, + {-.308070E+00, 0.754539E+00}, {-.312188E+00, 0.751689E+00}, + {-.316556E+00, 0.748647E+00}, {-.321167E+00, 0.745412E+00}, + {-.326015E+00, 0.741988E+00}, {-.331091E+00, 0.738373E+00}, + {-.336390E+00, 0.734571E+00}, {-.341903E+00, 0.730582E+00}, + {-.347623E+00, 0.726408E+00}, {-.353541E+00, 0.722050E+00}, + {-.359649E+00, 0.717512E+00}, {-.365938E+00, 0.712795E+00}, + {-.372400E+00, 0.707901E+00}, {-.379025E+00, 0.702833E+00}, + {-.385806E+00, 0.697594E+00}, {-.392732E+00, 0.692187E+00}, + {-.399795E+00, 0.686614E+00}, {-.406985E+00, 0.680881E+00}, + {-.414293E+00, 0.674989E+00}, {-.421709E+00, 0.668943E+00}, + {-.429224E+00, 0.662747E+00}, {-.436829E+00, 0.656406E+00}, + {-.444514E+00, 0.649923E+00}, {-.452269E+00, 0.643305E+00}, + {-.460086E+00, 0.636555E+00}, {-.467954E+00, 0.629679E+00}, + {-.475865E+00, 0.622683E+00}, {-.483810E+00, 0.615573E+00}, + {-.491778E+00, 0.608353E+00}, {-.499761E+00, 0.601032E+00}, + {-.507751E+00, 0.593614E+00}, {-.515738E+00, 0.586108E+00}, + {-.523713E+00, 0.578519E+00}, {-.531668E+00, 0.570855E+00}, + {-.539595E+00, 0.563122E+00}, {-.547485E+00, 0.555330E+00}, + {-.555330E+00, 0.547485E+00}, {-.563122E+00, 0.539595E+00}, + {-.570855E+00, 0.531668E+00}, {-.578519E+00, 0.523713E+00}, + {-.586108E+00, 0.515738E+00}, {-.593614E+00, 0.507751E+00}, + {-.601032E+00, 0.499761E+00}, {-.608353E+00, 0.491778E+00}, + {-.615573E+00, 0.483810E+00}, {-.622683E+00, 0.475865E+00}, + {-.629679E+00, 0.467954E+00}, {-.636555E+00, 0.460086E+00}, + {-.643305E+00, 0.452269E+00}, {-.649923E+00, 0.444514E+00}, + {-.656406E+00, 0.436829E+00}, {-.662747E+00, 0.429224E+00}, + {-.668943E+00, 0.421709E+00}, {-.674989E+00, 0.414293E+00}, + {-.680881E+00, 0.406985E+00}, {-.686614E+00, 0.399795E+00}, + {-.692187E+00, 0.392732E+00}, {-.697594E+00, 0.385806E+00}, + {-.702833E+00, 0.379025E+00}, {-.707901E+00, 0.372400E+00}, + {-.712795E+00, 0.365938E+00}, {-.717512E+00, 0.359649E+00}, + {-.722050E+00, 0.353541E+00}, {-.726408E+00, 0.347623E+00}, + {-.730582E+00, 0.341903E+00}, {-.734571E+00, 0.336390E+00}, + {-.738373E+00, 0.331091E+00}, {-.741988E+00, 0.326015E+00}, + {-.745412E+00, 0.321167E+00}, {-.748647E+00, 0.316556E+00}, + {-.751689E+00, 0.312188E+00}, {-.754539E+00, 0.308070E+00}, + {-.757195E+00, 0.304208E+00}, {-.759657E+00, 0.300608E+00}, + {-.761923E+00, 0.297275E+00}, {-.763994E+00, 0.294214E+00}, + {-.765870E+00, 0.291431E+00}, {-.767548E+00, 0.288928E+00}, + {-.769030E+00, 0.286711E+00}, {-.770315E+00, 0.284783E+00}, + {-.771403E+00, 0.283146E+00}, {-.772293E+00, 0.281803E+00}, + {-.772985E+00, 0.280756E+00}, {-.773480E+00, 0.280007E+00}, + {-.773777E+00, 0.279557E+00}, {-.773876E+00, 0.279408E+00}, + }, + { + {-.270262E+00, 0.777814E+00}, {-.270416E+00, 0.777713E+00}, + {-.270880E+00, 0.777410E+00}, {-.271652E+00, 0.776905E+00}, + {-.272730E+00, 0.776199E+00}, {-.274115E+00, 0.775291E+00}, + {-.275802E+00, 0.774181E+00}, {-.277789E+00, 0.772871E+00}, + {-.280074E+00, 0.771359E+00}, {-.282653E+00, 0.769646E+00}, + {-.285522E+00, 0.767732E+00}, {-.288675E+00, 0.765618E+00}, + {-.292110E+00, 0.763304E+00}, {-.295819E+00, 0.760792E+00}, + {-.299798E+00, 0.758080E+00}, {-.304040E+00, 0.755170E+00}, + {-.308540E+00, 0.752063E+00}, {-.313289E+00, 0.748760E+00}, + {-.318282E+00, 0.745262E+00}, {-.323511E+00, 0.741570E+00}, + {-.328968E+00, 0.737684E+00}, {-.334645E+00, 0.733608E+00}, + {-.340534E+00, 0.729342E+00}, {-.346627E+00, 0.724888E+00}, + {-.352915E+00, 0.720248E+00}, {-.359389E+00, 0.715424E+00}, + {-.366041E+00, 0.710419E+00}, {-.372860E+00, 0.705236E+00}, + {-.379838E+00, 0.699876E+00}, {-.386965E+00, 0.694343E+00}, + {-.394232E+00, 0.688641E+00}, {-.401629E+00, 0.682772E+00}, + {-.409146E+00, 0.676741E+00}, {-.416774E+00, 0.670550E+00}, + {-.424504E+00, 0.664206E+00}, {-.432324E+00, 0.657711E+00}, + {-.440226E+00, 0.651071E+00}, {-.448200E+00, 0.644290E+00}, + {-.456235E+00, 0.637373E+00}, {-.464323E+00, 0.630327E+00}, + {-.472454E+00, 0.623156E+00}, {-.480618E+00, 0.615867E+00}, + {-.488805E+00, 0.608465E+00}, {-.497007E+00, 0.600957E+00}, + {-.505214E+00, 0.593350E+00}, {-.513418E+00, 0.585650E+00}, + {-.521608E+00, 0.577864E+00}, {-.529777E+00, 0.570001E+00}, + {-.537916E+00, 0.562066E+00}, {-.546016E+00, 0.554068E+00}, + {-.554068E+00, 0.546016E+00}, {-.562066E+00, 0.537916E+00}, + {-.570001E+00, 0.529777E+00}, {-.577864E+00, 0.521608E+00}, + {-.585650E+00, 0.513418E+00}, {-.593350E+00, 0.505214E+00}, + {-.600957E+00, 0.497007E+00}, {-.608465E+00, 0.488805E+00}, + {-.615867E+00, 0.480618E+00}, {-.623156E+00, 0.472454E+00}, + {-.630327E+00, 0.464323E+00}, {-.637373E+00, 0.456235E+00}, + {-.644290E+00, 0.448200E+00}, {-.651071E+00, 0.440226E+00}, + {-.657711E+00, 0.432324E+00}, {-.664206E+00, 0.424504E+00}, + {-.670550E+00, 0.416774E+00}, {-.676741E+00, 0.409146E+00}, + {-.682772E+00, 0.401629E+00}, {-.688641E+00, 0.394232E+00}, + {-.694343E+00, 0.386965E+00}, {-.699876E+00, 0.379838E+00}, + {-.705236E+00, 0.372860E+00}, {-.710419E+00, 0.366041E+00}, + {-.715424E+00, 0.359389E+00}, {-.720248E+00, 0.352915E+00}, + {-.724888E+00, 0.346627E+00}, {-.729342E+00, 0.340534E+00}, + {-.733608E+00, 0.334645E+00}, {-.737684E+00, 0.328968E+00}, + {-.741570E+00, 0.323511E+00}, {-.745262E+00, 0.318282E+00}, + {-.748760E+00, 0.313289E+00}, {-.752063E+00, 0.308540E+00}, + {-.755170E+00, 0.304040E+00}, {-.758080E+00, 0.299798E+00}, + {-.760792E+00, 0.295819E+00}, {-.763304E+00, 0.292110E+00}, + {-.765618E+00, 0.288675E+00}, {-.767732E+00, 0.285522E+00}, + {-.769646E+00, 0.282653E+00}, {-.771359E+00, 0.280074E+00}, + {-.772871E+00, 0.277789E+00}, {-.774181E+00, 0.275802E+00}, + {-.775291E+00, 0.274115E+00}, {-.776199E+00, 0.272730E+00}, + {-.776905E+00, 0.271652E+00}, {-.777410E+00, 0.270880E+00}, + {-.777713E+00, 0.270416E+00}, {-.777814E+00, 0.270262E+00}, + }, + { + {-.260942E+00, 0.781744E+00}, {-.261101E+00, 0.781641E+00}, + {-.261579E+00, 0.781332E+00}, {-.262374E+00, 0.780818E+00}, + {-.263486E+00, 0.780097E+00}, {-.264912E+00, 0.779171E+00}, + {-.266650E+00, 0.778040E+00}, {-.268698E+00, 0.776703E+00}, + {-.271052E+00, 0.775161E+00}, {-.273708E+00, 0.773414E+00}, + {-.276663E+00, 0.771462E+00}, {-.279912E+00, 0.769305E+00}, + {-.283449E+00, 0.766945E+00}, {-.287270E+00, 0.764380E+00}, + {-.291368E+00, 0.761613E+00}, {-.295737E+00, 0.758644E+00}, + {-.300370E+00, 0.755472E+00}, {-.305261E+00, 0.752100E+00}, + {-.310402E+00, 0.748528E+00}, {-.315785E+00, 0.744757E+00}, + {-.321403E+00, 0.740789E+00}, {-.327247E+00, 0.736625E+00}, + {-.333309E+00, 0.732266E+00}, {-.339580E+00, 0.727715E+00}, + {-.346051E+00, 0.722973E+00}, {-.352713E+00, 0.718043E+00}, + {-.359557E+00, 0.712926E+00}, {-.366573E+00, 0.707626E+00}, + {-.373752E+00, 0.702144E+00}, {-.381083E+00, 0.696485E+00}, + {-.388557E+00, 0.690652E+00}, {-.396165E+00, 0.684647E+00}, + {-.403895E+00, 0.678475E+00}, {-.411738E+00, 0.672139E+00}, + {-.419685E+00, 0.665644E+00}, {-.427724E+00, 0.658995E+00}, + {-.435846E+00, 0.652195E+00}, {-.444041E+00, 0.645250E+00}, + {-.452299E+00, 0.638166E+00}, {-.460610E+00, 0.630947E+00}, + {-.468963E+00, 0.623600E+00}, {-.477350E+00, 0.616129E+00}, + {-.485759E+00, 0.608543E+00}, {-.494183E+00, 0.600847E+00}, + {-.502611E+00, 0.593047E+00}, {-.511034E+00, 0.585152E+00}, + {-.519443E+00, 0.577168E+00}, {-.527828E+00, 0.569102E+00}, + {-.536182E+00, 0.560962E+00}, {-.544494E+00, 0.552757E+00}, + {-.552757E+00, 0.544494E+00}, {-.560962E+00, 0.536182E+00}, + {-.569102E+00, 0.527828E+00}, {-.577168E+00, 0.519443E+00}, + {-.585152E+00, 0.511034E+00}, {-.593047E+00, 0.502611E+00}, + {-.600847E+00, 0.494183E+00}, {-.608543E+00, 0.485759E+00}, + {-.616129E+00, 0.477350E+00}, {-.623600E+00, 0.468963E+00}, + {-.630947E+00, 0.460610E+00}, {-.638166E+00, 0.452299E+00}, + {-.645250E+00, 0.444041E+00}, {-.652195E+00, 0.435846E+00}, + {-.658995E+00, 0.427724E+00}, {-.665644E+00, 0.419685E+00}, + {-.672139E+00, 0.411738E+00}, {-.678475E+00, 0.403895E+00}, + {-.684647E+00, 0.396165E+00}, {-.690652E+00, 0.388557E+00}, + {-.696485E+00, 0.381083E+00}, {-.702144E+00, 0.373752E+00}, + {-.707626E+00, 0.366573E+00}, {-.712926E+00, 0.359557E+00}, + {-.718043E+00, 0.352713E+00}, {-.722973E+00, 0.346051E+00}, + {-.727715E+00, 0.339580E+00}, {-.732266E+00, 0.333309E+00}, + {-.736625E+00, 0.327247E+00}, {-.740789E+00, 0.321403E+00}, + {-.744757E+00, 0.315785E+00}, {-.748528E+00, 0.310402E+00}, + {-.752100E+00, 0.305261E+00}, {-.755472E+00, 0.300370E+00}, + {-.758644E+00, 0.295737E+00}, {-.761613E+00, 0.291368E+00}, + {-.764380E+00, 0.287270E+00}, {-.766945E+00, 0.283449E+00}, + {-.769305E+00, 0.279912E+00}, {-.771462E+00, 0.276663E+00}, + {-.773414E+00, 0.273708E+00}, {-.775161E+00, 0.271052E+00}, + {-.776703E+00, 0.268698E+00}, {-.778040E+00, 0.266650E+00}, + {-.779171E+00, 0.264912E+00}, {-.780097E+00, 0.263486E+00}, + {-.780818E+00, 0.262374E+00}, {-.781332E+00, 0.261579E+00}, + {-.781641E+00, 0.261101E+00}, {-.781744E+00, 0.260942E+00}, + }, + { + {-.251442E+00, 0.785666E+00}, {-.251606E+00, 0.785561E+00}, + {-.252098E+00, 0.785247E+00}, {-.252917E+00, 0.784722E+00}, + {-.254062E+00, 0.783988E+00}, {-.255530E+00, 0.783045E+00}, + {-.257321E+00, 0.781891E+00}, {-.259430E+00, 0.780528E+00}, + {-.261854E+00, 0.778956E+00}, {-.264590E+00, 0.777175E+00}, + {-.267633E+00, 0.775184E+00}, {-.270978E+00, 0.772985E+00}, + {-.274621E+00, 0.770578E+00}, {-.278555E+00, 0.767962E+00}, + {-.282774E+00, 0.765139E+00}, {-.287272E+00, 0.762109E+00}, + {-.292042E+00, 0.758873E+00}, {-.297076E+00, 0.755432E+00}, + {-.302368E+00, 0.751786E+00}, {-.307909E+00, 0.747937E+00}, + {-.313690E+00, 0.743885E+00}, {-.319704E+00, 0.739633E+00}, + {-.325942E+00, 0.735182E+00}, {-.332394E+00, 0.730533E+00}, + {-.339051E+00, 0.725688E+00}, {-.345905E+00, 0.720650E+00}, + {-.352944E+00, 0.715421E+00}, {-.360160E+00, 0.710003E+00}, + {-.367543E+00, 0.704399E+00}, {-.375082E+00, 0.698613E+00}, + {-.382767E+00, 0.692647E+00}, {-.390588E+00, 0.686505E+00}, + {-.398535E+00, 0.680191E+00}, {-.406597E+00, 0.673709E+00}, + {-.414764E+00, 0.667063E+00}, {-.423025E+00, 0.660257E+00}, + {-.431371E+00, 0.653296E+00}, {-.439791E+00, 0.646186E+00}, + {-.448274E+00, 0.638932E+00}, {-.456811E+00, 0.631538E+00}, + {-.465390E+00, 0.624012E+00}, {-.474003E+00, 0.616359E+00}, + {-.482638E+00, 0.608586E+00}, {-.491287E+00, 0.600699E+00}, + {-.499939E+00, 0.592706E+00}, {-.508584E+00, 0.584612E+00}, + {-.517214E+00, 0.576427E+00}, {-.525819E+00, 0.568157E+00}, + {-.534390E+00, 0.559810E+00}, {-.542918E+00, 0.551394E+00}, + {-.551394E+00, 0.542918E+00}, {-.559810E+00, 0.534390E+00}, + {-.568157E+00, 0.525819E+00}, {-.576427E+00, 0.517214E+00}, + {-.584612E+00, 0.508584E+00}, {-.592706E+00, 0.499939E+00}, + {-.600699E+00, 0.491287E+00}, {-.608586E+00, 0.482638E+00}, + {-.616359E+00, 0.474003E+00}, {-.624012E+00, 0.465390E+00}, + {-.631538E+00, 0.456811E+00}, {-.638932E+00, 0.448274E+00}, + {-.646186E+00, 0.439791E+00}, {-.653296E+00, 0.431371E+00}, + {-.660257E+00, 0.423025E+00}, {-.667063E+00, 0.414764E+00}, + {-.673709E+00, 0.406597E+00}, {-.680191E+00, 0.398535E+00}, + {-.686505E+00, 0.390588E+00}, {-.692647E+00, 0.382767E+00}, + {-.698613E+00, 0.375082E+00}, {-.704399E+00, 0.367543E+00}, + {-.710003E+00, 0.360160E+00}, {-.715421E+00, 0.352944E+00}, + {-.720650E+00, 0.345905E+00}, {-.725688E+00, 0.339051E+00}, + {-.730533E+00, 0.332394E+00}, {-.735182E+00, 0.325942E+00}, + {-.739633E+00, 0.319704E+00}, {-.743885E+00, 0.313690E+00}, + {-.747937E+00, 0.307909E+00}, {-.751786E+00, 0.302368E+00}, + {-.755432E+00, 0.297076E+00}, {-.758873E+00, 0.292042E+00}, + {-.762109E+00, 0.287272E+00}, {-.765139E+00, 0.282774E+00}, + {-.767962E+00, 0.278555E+00}, {-.770578E+00, 0.274621E+00}, + {-.772985E+00, 0.270978E+00}, {-.775184E+00, 0.267633E+00}, + {-.777175E+00, 0.264590E+00}, {-.778956E+00, 0.261854E+00}, + {-.780528E+00, 0.259430E+00}, {-.781891E+00, 0.257321E+00}, + {-.783045E+00, 0.255530E+00}, {-.783988E+00, 0.254062E+00}, + {-.784722E+00, 0.252917E+00}, {-.785247E+00, 0.252098E+00}, + {-.785561E+00, 0.251606E+00}, {-.785666E+00, 0.251442E+00}, + }, + { + {-.241754E+00, 0.789582E+00}, {-.241923E+00, 0.789475E+00}, + {-.242430E+00, 0.789155E+00}, {-.243273E+00, 0.788621E+00}, + {-.244452E+00, 0.787873E+00}, {-.245964E+00, 0.786911E+00}, + {-.247808E+00, 0.785736E+00}, {-.249979E+00, 0.784347E+00}, + {-.252475E+00, 0.782745E+00}, {-.255292E+00, 0.780929E+00}, + {-.258425E+00, 0.778900E+00}, {-.261868E+00, 0.776658E+00}, + {-.265618E+00, 0.774204E+00}, {-.269667E+00, 0.771537E+00}, + {-.274010E+00, 0.768658E+00}, {-.278639E+00, 0.765568E+00}, + {-.283548E+00, 0.762267E+00}, {-.288729E+00, 0.758757E+00}, + {-.294174E+00, 0.755037E+00}, {-.299875E+00, 0.751108E+00}, + {-.305824E+00, 0.746973E+00}, {-.312011E+00, 0.742633E+00}, + {-.318427E+00, 0.738088E+00}, {-.325064E+00, 0.733340E+00}, + {-.331911E+00, 0.728393E+00}, {-.338959E+00, 0.723246E+00}, + {-.346197E+00, 0.717904E+00}, {-.353617E+00, 0.712368E+00}, + {-.361207E+00, 0.706641E+00}, {-.368956E+00, 0.700726E+00}, + {-.376856E+00, 0.694627E+00}, {-.384894E+00, 0.688347E+00}, + {-.393061E+00, 0.681890E+00}, {-.401345E+00, 0.675259E+00}, + {-.409737E+00, 0.668460E+00}, {-.418224E+00, 0.661496E+00}, + {-.426797E+00, 0.654373E+00}, {-.435445E+00, 0.647096E+00}, + {-.444157E+00, 0.639669E+00}, {-.452923E+00, 0.632100E+00}, + {-.461732E+00, 0.624393E+00}, {-.470574E+00, 0.616556E+00}, + {-.479438E+00, 0.608594E+00}, {-.488315E+00, 0.600514E+00}, + {-.497194E+00, 0.592323E+00}, {-.506066E+00, 0.584030E+00}, + {-.514920E+00, 0.575640E+00}, {-.523748E+00, 0.567163E+00}, + {-.532539E+00, 0.558606E+00}, {-.541285E+00, 0.549977E+00}, + {-.549977E+00, 0.541285E+00}, {-.558606E+00, 0.532539E+00}, + {-.567163E+00, 0.523748E+00}, {-.575640E+00, 0.514920E+00}, + {-.584030E+00, 0.506066E+00}, {-.592323E+00, 0.497194E+00}, + {-.600514E+00, 0.488315E+00}, {-.608594E+00, 0.479438E+00}, + {-.616556E+00, 0.470574E+00}, {-.624393E+00, 0.461732E+00}, + {-.632100E+00, 0.452923E+00}, {-.639669E+00, 0.444157E+00}, + {-.647096E+00, 0.435445E+00}, {-.654373E+00, 0.426797E+00}, + {-.661496E+00, 0.418224E+00}, {-.668460E+00, 0.409737E+00}, + {-.675259E+00, 0.401345E+00}, {-.681890E+00, 0.393061E+00}, + {-.688347E+00, 0.384894E+00}, {-.694627E+00, 0.376856E+00}, + {-.700726E+00, 0.368956E+00}, {-.706641E+00, 0.361207E+00}, + {-.712368E+00, 0.353617E+00}, {-.717904E+00, 0.346197E+00}, + {-.723246E+00, 0.338959E+00}, {-.728393E+00, 0.331911E+00}, + {-.733340E+00, 0.325064E+00}, {-.738088E+00, 0.318427E+00}, + {-.742633E+00, 0.312011E+00}, {-.746973E+00, 0.305824E+00}, + {-.751108E+00, 0.299875E+00}, {-.755037E+00, 0.294174E+00}, + {-.758757E+00, 0.288729E+00}, {-.762267E+00, 0.283548E+00}, + {-.765568E+00, 0.278639E+00}, {-.768658E+00, 0.274010E+00}, + {-.771537E+00, 0.269667E+00}, {-.774204E+00, 0.265618E+00}, + {-.776658E+00, 0.261868E+00}, {-.778900E+00, 0.258425E+00}, + {-.780929E+00, 0.255292E+00}, {-.782745E+00, 0.252475E+00}, + {-.784347E+00, 0.249979E+00}, {-.785736E+00, 0.247808E+00}, + {-.786911E+00, 0.245964E+00}, {-.787873E+00, 0.244452E+00}, + {-.788621E+00, 0.243273E+00}, {-.789155E+00, 0.242430E+00}, + {-.789475E+00, 0.241923E+00}, {-.789582E+00, 0.241754E+00}, + }, + { + {-.231872E+00, 0.793492E+00}, {-.232046E+00, 0.793383E+00}, + {-.232567E+00, 0.793057E+00}, {-.233435E+00, 0.792513E+00}, + {-.234649E+00, 0.791751E+00}, {-.236206E+00, 0.790772E+00}, + {-.238103E+00, 0.789574E+00}, {-.240338E+00, 0.788160E+00}, + {-.242908E+00, 0.786527E+00}, {-.245807E+00, 0.784678E+00}, + {-.249031E+00, 0.782610E+00}, {-.252575E+00, 0.780326E+00}, + {-.256434E+00, 0.777824E+00}, {-.260601E+00, 0.775106E+00}, + {-.265070E+00, 0.772172E+00}, {-.269833E+00, 0.769021E+00}, + {-.274883E+00, 0.765655E+00}, {-.280214E+00, 0.762075E+00}, + {-.285815E+00, 0.758280E+00}, {-.291679E+00, 0.754273E+00}, + {-.297797E+00, 0.750054E+00}, {-.304160E+00, 0.745624E+00}, + {-.310759E+00, 0.740985E+00}, {-.317583E+00, 0.736139E+00}, + {-.324623E+00, 0.731087E+00}, {-.331869E+00, 0.725832E+00}, + {-.339311E+00, 0.720376E+00}, {-.346937E+00, 0.714720E+00}, + {-.354738E+00, 0.708869E+00}, {-.362702E+00, 0.702825E+00}, + {-.370820E+00, 0.696592E+00}, {-.379079E+00, 0.690172E+00}, + {-.387469E+00, 0.683570E+00}, {-.395980E+00, 0.676790E+00}, + {-.404599E+00, 0.669836E+00}, {-.413316E+00, 0.662713E+00}, + {-.422120E+00, 0.655425E+00}, {-.430999E+00, 0.647979E+00}, + {-.439944E+00, 0.640379E+00}, {-.448943E+00, 0.632631E+00}, + {-.457985E+00, 0.624742E+00}, {-.467060E+00, 0.616717E+00}, + {-.476156E+00, 0.608563E+00}, {-.485265E+00, 0.600288E+00}, + {-.494374E+00, 0.591899E+00}, {-.503475E+00, 0.583402E+00}, + {-.512557E+00, 0.574806E+00}, {-.521611E+00, 0.566119E+00}, + {-.530626E+00, 0.557349E+00}, {-.539593E+00, 0.548504E+00}, + {-.548504E+00, 0.539593E+00}, {-.557349E+00, 0.530626E+00}, + {-.566119E+00, 0.521611E+00}, {-.574806E+00, 0.512557E+00}, + {-.583402E+00, 0.503475E+00}, {-.591899E+00, 0.494374E+00}, + {-.600288E+00, 0.485265E+00}, {-.608563E+00, 0.476156E+00}, + {-.616717E+00, 0.467060E+00}, {-.624742E+00, 0.457985E+00}, + {-.632631E+00, 0.448943E+00}, {-.640379E+00, 0.439944E+00}, + {-.647979E+00, 0.430999E+00}, {-.655425E+00, 0.422120E+00}, + {-.662713E+00, 0.413316E+00}, {-.669836E+00, 0.404599E+00}, + {-.676790E+00, 0.395980E+00}, {-.683570E+00, 0.387469E+00}, + {-.690172E+00, 0.379079E+00}, {-.696592E+00, 0.370820E+00}, + {-.702825E+00, 0.362702E+00}, {-.708869E+00, 0.354738E+00}, + {-.714720E+00, 0.346937E+00}, {-.720376E+00, 0.339311E+00}, + {-.725832E+00, 0.331869E+00}, {-.731087E+00, 0.324623E+00}, + {-.736139E+00, 0.317583E+00}, {-.740985E+00, 0.310759E+00}, + {-.745624E+00, 0.304160E+00}, {-.750054E+00, 0.297797E+00}, + {-.754273E+00, 0.291679E+00}, {-.758280E+00, 0.285815E+00}, + {-.762075E+00, 0.280214E+00}, {-.765655E+00, 0.274883E+00}, + {-.769021E+00, 0.269833E+00}, {-.772172E+00, 0.265070E+00}, + {-.775106E+00, 0.260601E+00}, {-.777824E+00, 0.256434E+00}, + {-.780326E+00, 0.252575E+00}, {-.782610E+00, 0.249031E+00}, + {-.784678E+00, 0.245807E+00}, {-.786527E+00, 0.242908E+00}, + {-.788160E+00, 0.240338E+00}, {-.789574E+00, 0.238103E+00}, + {-.790772E+00, 0.236206E+00}, {-.791751E+00, 0.234649E+00}, + {-.792513E+00, 0.233435E+00}, {-.793057E+00, 0.232567E+00}, + {-.793383E+00, 0.232046E+00}, {-.793492E+00, 0.231872E+00}, + }, + { + {-.221787E+00, 0.797397E+00}, {-.221966E+00, 0.797286E+00}, + {-.222503E+00, 0.796954E+00}, {-.223396E+00, 0.796400E+00}, + {-.224645E+00, 0.795624E+00}, {-.226247E+00, 0.794627E+00}, + {-.228200E+00, 0.793408E+00}, {-.230500E+00, 0.791967E+00}, + {-.233144E+00, 0.790305E+00}, {-.236127E+00, 0.788421E+00}, + {-.239445E+00, 0.786315E+00}, {-.243091E+00, 0.783988E+00}, + {-.247062E+00, 0.781439E+00}, {-.251348E+00, 0.778670E+00}, + {-.255945E+00, 0.775679E+00}, {-.260845E+00, 0.772468E+00}, + {-.266040E+00, 0.769037E+00}, {-.271522E+00, 0.765386E+00}, + {-.277283E+00, 0.761517E+00}, {-.283314E+00, 0.757430E+00}, + {-.289605E+00, 0.753127E+00}, {-.296147E+00, 0.748608E+00}, + {-.302931E+00, 0.743874E+00}, {-.309947E+00, 0.738929E+00}, + {-.317184E+00, 0.733772E+00}, {-.324631E+00, 0.728407E+00}, + {-.332279E+00, 0.722836E+00}, {-.340116E+00, 0.717060E+00}, + {-.348132E+00, 0.711084E+00}, {-.356314E+00, 0.704909E+00}, + {-.364654E+00, 0.698540E+00}, {-.373138E+00, 0.691980E+00}, + {-.381755E+00, 0.685231E+00}, {-.390495E+00, 0.678300E+00}, + {-.399346E+00, 0.671190E+00}, {-.408296E+00, 0.663905E+00}, + {-.417335E+00, 0.656452E+00}, {-.426450E+00, 0.648834E+00}, + {-.435631E+00, 0.641058E+00}, {-.444867E+00, 0.633130E+00}, + {-.454146E+00, 0.625056E+00}, {-.463457E+00, 0.616842E+00}, + {-.472789E+00, 0.608495E+00}, {-.482133E+00, 0.600022E+00}, + {-.491477E+00, 0.591430E+00}, {-.500810E+00, 0.582728E+00}, + {-.510123E+00, 0.573923E+00}, {-.519406E+00, 0.565023E+00}, + {-.528648E+00, 0.556036E+00}, {-.537840E+00, 0.546972E+00}, + {-.546972E+00, 0.537840E+00}, {-.556036E+00, 0.528648E+00}, + {-.565023E+00, 0.519406E+00}, {-.573923E+00, 0.510123E+00}, + {-.582728E+00, 0.500810E+00}, {-.591430E+00, 0.491477E+00}, + {-.600022E+00, 0.482133E+00}, {-.608495E+00, 0.472789E+00}, + {-.616842E+00, 0.463457E+00}, {-.625056E+00, 0.454146E+00}, + {-.633130E+00, 0.444867E+00}, {-.641058E+00, 0.435631E+00}, + {-.648834E+00, 0.426450E+00}, {-.656452E+00, 0.417335E+00}, + {-.663905E+00, 0.408296E+00}, {-.671190E+00, 0.399346E+00}, + {-.678300E+00, 0.390495E+00}, {-.685231E+00, 0.381755E+00}, + {-.691980E+00, 0.373138E+00}, {-.698540E+00, 0.364654E+00}, + {-.704909E+00, 0.356314E+00}, {-.711084E+00, 0.348132E+00}, + {-.717060E+00, 0.340116E+00}, {-.722836E+00, 0.332279E+00}, + {-.728407E+00, 0.324631E+00}, {-.733772E+00, 0.317184E+00}, + {-.738929E+00, 0.309947E+00}, {-.743874E+00, 0.302931E+00}, + {-.748608E+00, 0.296147E+00}, {-.753127E+00, 0.289605E+00}, + {-.757430E+00, 0.283314E+00}, {-.761517E+00, 0.277283E+00}, + {-.765386E+00, 0.271522E+00}, {-.769037E+00, 0.266040E+00}, + {-.772468E+00, 0.260845E+00}, {-.775679E+00, 0.255945E+00}, + {-.778670E+00, 0.251348E+00}, {-.781439E+00, 0.247062E+00}, + {-.783988E+00, 0.243091E+00}, {-.786315E+00, 0.239445E+00}, + {-.788421E+00, 0.236127E+00}, {-.790305E+00, 0.233144E+00}, + {-.791967E+00, 0.230500E+00}, {-.793408E+00, 0.228200E+00}, + {-.794627E+00, 0.226247E+00}, {-.795624E+00, 0.224645E+00}, + {-.796400E+00, 0.223396E+00}, {-.796954E+00, 0.222503E+00}, + {-.797286E+00, 0.221966E+00}, {-.797397E+00, 0.221787E+00}, + }, + { + {-.211491E+00, 0.801297E+00}, {-.211676E+00, 0.801184E+00}, + {-.212228E+00, 0.800846E+00}, {-.213147E+00, 0.800282E+00}, + {-.214432E+00, 0.799493E+00}, {-.216081E+00, 0.798478E+00}, + {-.218090E+00, 0.797237E+00}, {-.220456E+00, 0.795770E+00}, + {-.223176E+00, 0.794078E+00}, {-.226245E+00, 0.792160E+00}, + {-.229658E+00, 0.790016E+00}, {-.233409E+00, 0.787646E+00}, + {-.237493E+00, 0.785050E+00}, {-.241902E+00, 0.782228E+00}, + {-.246630E+00, 0.779182E+00}, {-.251669E+00, 0.775910E+00}, + {-.257012E+00, 0.772413E+00}, {-.262649E+00, 0.768692E+00}, + {-.268572E+00, 0.764748E+00}, {-.274772E+00, 0.760581E+00}, + {-.281240E+00, 0.756193E+00}, {-.287965E+00, 0.751584E+00}, + {-.294938E+00, 0.746755E+00}, {-.302148E+00, 0.741709E+00}, + {-.309585E+00, 0.736448E+00}, {-.317238E+00, 0.730972E+00}, + {-.325096E+00, 0.725285E+00}, {-.333147E+00, 0.719388E+00}, + {-.341382E+00, 0.713285E+00}, {-.349787E+00, 0.706979E+00}, + {-.358352E+00, 0.700472E+00}, {-.367064E+00, 0.693769E+00}, + {-.375913E+00, 0.686874E+00}, {-.384887E+00, 0.679790E+00}, + {-.393973E+00, 0.672521E+00}, {-.403161E+00, 0.665074E+00}, + {-.412438E+00, 0.657452E+00}, {-.421793E+00, 0.649661E+00}, + {-.431214E+00, 0.641708E+00}, {-.440690E+00, 0.633597E+00}, + {-.450210E+00, 0.625335E+00}, {-.459761E+00, 0.616929E+00}, + {-.469334E+00, 0.608386E+00}, {-.478916E+00, 0.599712E+00}, + {-.488497E+00, 0.590916E+00}, {-.498067E+00, 0.582005E+00}, + {-.507615E+00, 0.572988E+00}, {-.517130E+00, 0.563872E+00}, + {-.526602E+00, 0.554666E+00}, {-.536022E+00, 0.545380E+00}, + {-.545380E+00, 0.536022E+00}, {-.554666E+00, 0.526602E+00}, + {-.563872E+00, 0.517130E+00}, {-.572988E+00, 0.507615E+00}, + {-.582005E+00, 0.498067E+00}, {-.590916E+00, 0.488497E+00}, + {-.599712E+00, 0.478916E+00}, {-.608386E+00, 0.469334E+00}, + {-.616929E+00, 0.459761E+00}, {-.625335E+00, 0.450210E+00}, + {-.633597E+00, 0.440690E+00}, {-.641708E+00, 0.431214E+00}, + {-.649661E+00, 0.421793E+00}, {-.657452E+00, 0.412438E+00}, + {-.665074E+00, 0.403161E+00}, {-.672521E+00, 0.393973E+00}, + {-.679790E+00, 0.384887E+00}, {-.686874E+00, 0.375913E+00}, + {-.693769E+00, 0.367064E+00}, {-.700472E+00, 0.358352E+00}, + {-.706979E+00, 0.349787E+00}, {-.713285E+00, 0.341382E+00}, + {-.719388E+00, 0.333147E+00}, {-.725285E+00, 0.325096E+00}, + {-.730972E+00, 0.317238E+00}, {-.736448E+00, 0.309585E+00}, + {-.741709E+00, 0.302148E+00}, {-.746755E+00, 0.294938E+00}, + {-.751584E+00, 0.287965E+00}, {-.756193E+00, 0.281240E+00}, + {-.760581E+00, 0.274772E+00}, {-.764748E+00, 0.268572E+00}, + {-.768692E+00, 0.262649E+00}, {-.772413E+00, 0.257012E+00}, + {-.775910E+00, 0.251669E+00}, {-.779182E+00, 0.246630E+00}, + {-.782228E+00, 0.241902E+00}, {-.785050E+00, 0.237493E+00}, + {-.787646E+00, 0.233409E+00}, {-.790016E+00, 0.229658E+00}, + {-.792160E+00, 0.226245E+00}, {-.794078E+00, 0.223176E+00}, + {-.795770E+00, 0.220456E+00}, {-.797237E+00, 0.218090E+00}, + {-.798478E+00, 0.216081E+00}, {-.799493E+00, 0.214432E+00}, + {-.800282E+00, 0.213147E+00}, {-.800846E+00, 0.212228E+00}, + {-.801184E+00, 0.211676E+00}, {-.801297E+00, 0.211491E+00}, + }, + { + {-.200977E+00, 0.805193E+00}, {-.201166E+00, 0.805079E+00}, + {-.201734E+00, 0.804734E+00}, {-.202680E+00, 0.804161E+00}, + {-.204002E+00, 0.803358E+00}, {-.205698E+00, 0.802325E+00}, + {-.207764E+00, 0.801062E+00}, {-.210199E+00, 0.799570E+00}, + {-.212996E+00, 0.797847E+00}, {-.216153E+00, 0.795895E+00}, + {-.219663E+00, 0.793712E+00}, {-.223521E+00, 0.791299E+00}, + {-.227721E+00, 0.788656E+00}, {-.232255E+00, 0.785783E+00}, + {-.237117E+00, 0.782680E+00}, {-.242298E+00, 0.779347E+00}, + {-.247790E+00, 0.775785E+00}, {-.253585E+00, 0.771993E+00}, + {-.259674E+00, 0.767974E+00}, {-.266047E+00, 0.763726E+00}, + {-.272695E+00, 0.759252E+00}, {-.279606E+00, 0.754553E+00}, + {-.286772E+00, 0.749629E+00}, {-.294181E+00, 0.744482E+00}, + {-.301822E+00, 0.739114E+00}, {-.309684E+00, 0.733526E+00}, + {-.317755E+00, 0.727722E+00}, {-.326026E+00, 0.721703E+00}, + {-.334482E+00, 0.715473E+00}, {-.343114E+00, 0.709033E+00}, + {-.351908E+00, 0.702388E+00}, {-.360854E+00, 0.695541E+00}, + {-.369938E+00, 0.688497E+00}, {-.379149E+00, 0.681258E+00}, + {-.388476E+00, 0.673830E+00}, {-.397905E+00, 0.666217E+00}, + {-.407424E+00, 0.658425E+00}, {-.417023E+00, 0.650459E+00}, + {-.426689E+00, 0.642325E+00}, {-.436409E+00, 0.634029E+00}, + {-.446173E+00, 0.625577E+00}, {-.455969E+00, 0.616977E+00}, + {-.465785E+00, 0.608235E+00}, {-.475610E+00, 0.599358E+00}, + {-.485433E+00, 0.590354E+00}, {-.495243E+00, 0.581232E+00}, + {-.505029E+00, 0.571999E+00}, {-.514780E+00, 0.562664E+00}, + {-.524487E+00, 0.553236E+00}, {-.534138E+00, 0.543724E+00}, + {-.543724E+00, 0.534138E+00}, {-.553236E+00, 0.524487E+00}, + {-.562664E+00, 0.514780E+00}, {-.571999E+00, 0.505029E+00}, + {-.581232E+00, 0.495243E+00}, {-.590354E+00, 0.485433E+00}, + {-.599358E+00, 0.475610E+00}, {-.608235E+00, 0.465785E+00}, + {-.616977E+00, 0.455969E+00}, {-.625577E+00, 0.446173E+00}, + {-.634029E+00, 0.436409E+00}, {-.642325E+00, 0.426689E+00}, + {-.650459E+00, 0.417023E+00}, {-.658425E+00, 0.407424E+00}, + {-.666217E+00, 0.397905E+00}, {-.673830E+00, 0.388476E+00}, + {-.681258E+00, 0.379149E+00}, {-.688497E+00, 0.369938E+00}, + {-.695541E+00, 0.360854E+00}, {-.702388E+00, 0.351908E+00}, + {-.709033E+00, 0.343114E+00}, {-.715473E+00, 0.334482E+00}, + {-.721703E+00, 0.326026E+00}, {-.727722E+00, 0.317755E+00}, + {-.733526E+00, 0.309684E+00}, {-.739114E+00, 0.301822E+00}, + {-.744482E+00, 0.294181E+00}, {-.749629E+00, 0.286772E+00}, + {-.754553E+00, 0.279606E+00}, {-.759252E+00, 0.272695E+00}, + {-.763726E+00, 0.266047E+00}, {-.767974E+00, 0.259674E+00}, + {-.771993E+00, 0.253585E+00}, {-.775785E+00, 0.247790E+00}, + {-.779347E+00, 0.242298E+00}, {-.782680E+00, 0.237117E+00}, + {-.785783E+00, 0.232255E+00}, {-.788656E+00, 0.227721E+00}, + {-.791299E+00, 0.223521E+00}, {-.793712E+00, 0.219663E+00}, + {-.795895E+00, 0.216153E+00}, {-.797847E+00, 0.212996E+00}, + {-.799570E+00, 0.210199E+00}, {-.801062E+00, 0.207764E+00}, + {-.802325E+00, 0.205698E+00}, {-.803358E+00, 0.204002E+00}, + {-.804161E+00, 0.202680E+00}, {-.804734E+00, 0.201734E+00}, + {-.805079E+00, 0.201166E+00}, {-.805193E+00, 0.200977E+00}, + }, + { + {-.190234E+00, 0.809086E+00}, {-.190429E+00, 0.808970E+00}, + {-.191013E+00, 0.808620E+00}, {-.191986E+00, 0.808036E+00}, + {-.193345E+00, 0.807219E+00}, {-.195089E+00, 0.806169E+00}, + {-.197215E+00, 0.804884E+00}, {-.199718E+00, 0.803366E+00}, + {-.202595E+00, 0.801613E+00}, {-.205841E+00, 0.799626E+00}, + {-.209451E+00, 0.797405E+00}, {-.213418E+00, 0.794949E+00}, + {-.217736E+00, 0.792259E+00}, {-.222398E+00, 0.789334E+00}, + {-.227396E+00, 0.786174E+00}, {-.232722E+00, 0.782780E+00}, + {-.238368E+00, 0.779152E+00}, {-.244324E+00, 0.775290E+00}, + {-.250582E+00, 0.771194E+00}, {-.257131E+00, 0.766866E+00}, + {-.263962E+00, 0.762306E+00}, {-.271064E+00, 0.757515E+00}, + {-.278426E+00, 0.752494E+00}, {-.286037E+00, 0.747246E+00}, + {-.293886E+00, 0.741771E+00}, {-.301961E+00, 0.736071E+00}, + {-.310251E+00, 0.730148E+00}, {-.318744E+00, 0.724006E+00}, + {-.327427E+00, 0.717646E+00}, {-.336289E+00, 0.711072E+00}, + {-.345318E+00, 0.704287E+00}, {-.354500E+00, 0.697295E+00}, + {-.363824E+00, 0.690099E+00}, {-.373278E+00, 0.682704E+00}, + {-.382848E+00, 0.675114E+00}, {-.392522E+00, 0.667334E+00}, + {-.402289E+00, 0.659370E+00}, {-.412136E+00, 0.651226E+00}, + {-.422050E+00, 0.642910E+00}, {-.432019E+00, 0.634426E+00}, + {-.442032E+00, 0.625782E+00}, {-.452076E+00, 0.616984E+00}, + {-.462140E+00, 0.608040E+00}, {-.472212E+00, 0.598957E+00}, + {-.482280E+00, 0.589743E+00}, {-.492334E+00, 0.580406E+00}, + {-.502362E+00, 0.570954E+00}, {-.512353E+00, 0.561397E+00}, + {-.522297E+00, 0.551744E+00}, {-.532184E+00, 0.542003E+00}, + {-.542003E+00, 0.532184E+00}, {-.551744E+00, 0.522297E+00}, + {-.561397E+00, 0.512353E+00}, {-.570954E+00, 0.502362E+00}, + {-.580406E+00, 0.492334E+00}, {-.589743E+00, 0.482280E+00}, + {-.598957E+00, 0.472212E+00}, {-.608040E+00, 0.462140E+00}, + {-.616984E+00, 0.452076E+00}, {-.625782E+00, 0.442032E+00}, + {-.634426E+00, 0.432019E+00}, {-.642910E+00, 0.422050E+00}, + {-.651226E+00, 0.412136E+00}, {-.659370E+00, 0.402289E+00}, + {-.667334E+00, 0.392522E+00}, {-.675114E+00, 0.382848E+00}, + {-.682704E+00, 0.373278E+00}, {-.690099E+00, 0.363824E+00}, + {-.697295E+00, 0.354500E+00}, {-.704287E+00, 0.345318E+00}, + {-.711072E+00, 0.336289E+00}, {-.717646E+00, 0.327427E+00}, + {-.724006E+00, 0.318744E+00}, {-.730148E+00, 0.310251E+00}, + {-.736071E+00, 0.301961E+00}, {-.741771E+00, 0.293886E+00}, + {-.747246E+00, 0.286037E+00}, {-.752494E+00, 0.278426E+00}, + {-.757515E+00, 0.271064E+00}, {-.762306E+00, 0.263962E+00}, + {-.766866E+00, 0.257131E+00}, {-.771194E+00, 0.250582E+00}, + {-.775290E+00, 0.244324E+00}, {-.779152E+00, 0.238368E+00}, + {-.782780E+00, 0.232722E+00}, {-.786174E+00, 0.227396E+00}, + {-.789334E+00, 0.222398E+00}, {-.792259E+00, 0.217736E+00}, + {-.794949E+00, 0.213418E+00}, {-.797405E+00, 0.209451E+00}, + {-.799626E+00, 0.205841E+00}, {-.801613E+00, 0.202595E+00}, + {-.803366E+00, 0.199718E+00}, {-.804884E+00, 0.197215E+00}, + {-.806169E+00, 0.195089E+00}, {-.807219E+00, 0.193345E+00}, + {-.808036E+00, 0.191986E+00}, {-.808620E+00, 0.191013E+00}, + {-.808970E+00, 0.190429E+00}, {-.809086E+00, 0.190234E+00}, + }, + { + {-.179253E+00, 0.812977E+00}, {-.179453E+00, 0.812858E+00}, + {-.180054E+00, 0.812503E+00}, {-.181054E+00, 0.811909E+00}, + {-.182453E+00, 0.811078E+00}, {-.184246E+00, 0.810010E+00}, + {-.186432E+00, 0.808704E+00}, {-.189006E+00, 0.807159E+00}, + {-.191964E+00, 0.805377E+00}, {-.195302E+00, 0.803356E+00}, + {-.199012E+00, 0.801096E+00}, {-.203091E+00, 0.798597E+00}, + {-.207530E+00, 0.795859E+00}, {-.212322E+00, 0.792882E+00}, + {-.217459E+00, 0.789665E+00}, {-.222933E+00, 0.786210E+00}, + {-.228736E+00, 0.782515E+00}, {-.234857E+00, 0.778582E+00}, + {-.241287E+00, 0.774410E+00}, {-.248016E+00, 0.770000E+00}, + {-.255034E+00, 0.765354E+00}, {-.262330E+00, 0.760471E+00}, + {-.269892E+00, 0.755353E+00}, {-.277710E+00, 0.750002E+00}, + {-.285771E+00, 0.744418E+00}, {-.294063E+00, 0.738605E+00}, + {-.302575E+00, 0.732563E+00}, {-.311295E+00, 0.726296E+00}, + {-.320210E+00, 0.719806E+00}, {-.329307E+00, 0.713096E+00}, + {-.338573E+00, 0.706169E+00}, {-.347997E+00, 0.699030E+00}, + {-.357565E+00, 0.691681E+00}, {-.367265E+00, 0.684127E+00}, + {-.377084E+00, 0.676374E+00}, {-.387009E+00, 0.668424E+00}, + {-.397027E+00, 0.660285E+00}, {-.407126E+00, 0.651962E+00}, + {-.417292E+00, 0.643460E+00}, {-.427515E+00, 0.634786E+00}, + {-.437781E+00, 0.625947E+00}, {-.448078E+00, 0.616949E+00}, + {-.458394E+00, 0.607800E+00}, {-.468716E+00, 0.598507E+00}, + {-.479034E+00, 0.589080E+00}, {-.489336E+00, 0.579525E+00}, + {-.499610E+00, 0.569851E+00}, {-.509846E+00, 0.560068E+00}, + {-.520031E+00, 0.550185E+00}, {-.530157E+00, 0.540211E+00}, + {-.540211E+00, 0.530157E+00}, {-.550185E+00, 0.520031E+00}, + {-.560068E+00, 0.509846E+00}, {-.569851E+00, 0.499610E+00}, + {-.579525E+00, 0.489336E+00}, {-.589080E+00, 0.479034E+00}, + {-.598507E+00, 0.468716E+00}, {-.607800E+00, 0.458394E+00}, + {-.616949E+00, 0.448078E+00}, {-.625947E+00, 0.437781E+00}, + {-.634786E+00, 0.427515E+00}, {-.643460E+00, 0.417292E+00}, + {-.651962E+00, 0.407126E+00}, {-.660285E+00, 0.397027E+00}, + {-.668424E+00, 0.387009E+00}, {-.676374E+00, 0.377084E+00}, + {-.684127E+00, 0.367265E+00}, {-.691681E+00, 0.357565E+00}, + {-.699030E+00, 0.347997E+00}, {-.706169E+00, 0.338573E+00}, + {-.713096E+00, 0.329307E+00}, {-.719806E+00, 0.320210E+00}, + {-.726296E+00, 0.311295E+00}, {-.732563E+00, 0.302575E+00}, + {-.738605E+00, 0.294063E+00}, {-.744418E+00, 0.285771E+00}, + {-.750002E+00, 0.277710E+00}, {-.755353E+00, 0.269892E+00}, + {-.760471E+00, 0.262330E+00}, {-.765354E+00, 0.255034E+00}, + {-.770000E+00, 0.248016E+00}, {-.774410E+00, 0.241287E+00}, + {-.778582E+00, 0.234857E+00}, {-.782515E+00, 0.228736E+00}, + {-.786210E+00, 0.222933E+00}, {-.789665E+00, 0.217459E+00}, + {-.792882E+00, 0.212322E+00}, {-.795859E+00, 0.207530E+00}, + {-.798597E+00, 0.203091E+00}, {-.801096E+00, 0.199012E+00}, + {-.803356E+00, 0.195302E+00}, {-.805377E+00, 0.191964E+00}, + {-.807159E+00, 0.189006E+00}, {-.808704E+00, 0.186432E+00}, + {-.810010E+00, 0.184246E+00}, {-.811078E+00, 0.182453E+00}, + {-.811909E+00, 0.181054E+00}, {-.812503E+00, 0.180054E+00}, + {-.812858E+00, 0.179453E+00}, {-.812977E+00, 0.179253E+00}, + }, + { + {-.168024E+00, 0.816866E+00}, {-.168230E+00, 0.816745E+00}, + {-.168848E+00, 0.816384E+00}, {-.169876E+00, 0.815781E+00}, + {-.171314E+00, 0.814936E+00}, {-.173158E+00, 0.813850E+00}, + {-.175405E+00, 0.812522E+00}, {-.178051E+00, 0.810951E+00}, + {-.181093E+00, 0.809138E+00}, {-.184523E+00, 0.807083E+00}, + {-.188338E+00, 0.804784E+00}, {-.192530E+00, 0.802242E+00}, + {-.197093E+00, 0.799457E+00}, {-.202018E+00, 0.796427E+00}, + {-.207297E+00, 0.793154E+00}, {-.212923E+00, 0.789637E+00}, + {-.218885E+00, 0.785875E+00}, {-.225174E+00, 0.781870E+00}, + {-.231781E+00, 0.777621E+00}, {-.238694E+00, 0.773130E+00}, + {-.245903E+00, 0.768396E+00}, {-.253396E+00, 0.763420E+00}, + {-.261163E+00, 0.758205E+00}, {-.269191E+00, 0.752750E+00}, + {-.277468E+00, 0.747057E+00}, {-.285982E+00, 0.741129E+00}, + {-.294721E+00, 0.734967E+00}, {-.303672E+00, 0.728574E+00}, + {-.312822E+00, 0.721952E+00}, {-.322159E+00, 0.715104E+00}, + {-.331669E+00, 0.708034E+00}, {-.341338E+00, 0.700745E+00}, + {-.351155E+00, 0.693241E+00}, {-.361106E+00, 0.685527E+00}, + {-.371178E+00, 0.677607E+00}, {-.381357E+00, 0.669487E+00}, + {-.391632E+00, 0.661170E+00}, {-.401987E+00, 0.652664E+00}, + {-.412412E+00, 0.643974E+00}, {-.422892E+00, 0.635107E+00}, + {-.433415E+00, 0.626070E+00}, {-.443969E+00, 0.616869E+00}, + {-.454542E+00, 0.607512E+00}, {-.465120E+00, 0.598007E+00}, + {-.475692E+00, 0.588362E+00}, {-.486246E+00, 0.578586E+00}, + {-.496770E+00, 0.568687E+00}, {-.507254E+00, 0.558674E+00}, + {-.517685E+00, 0.548558E+00}, {-.528053E+00, 0.538348E+00}, + {-.538348E+00, 0.528053E+00}, {-.548558E+00, 0.517685E+00}, + {-.558674E+00, 0.507254E+00}, {-.568687E+00, 0.496770E+00}, + {-.578586E+00, 0.486246E+00}, {-.588362E+00, 0.475692E+00}, + {-.598007E+00, 0.465120E+00}, {-.607512E+00, 0.454542E+00}, + {-.616869E+00, 0.443969E+00}, {-.626070E+00, 0.433415E+00}, + {-.635107E+00, 0.422892E+00}, {-.643974E+00, 0.412412E+00}, + {-.652664E+00, 0.401987E+00}, {-.661170E+00, 0.391632E+00}, + {-.669487E+00, 0.381357E+00}, {-.677607E+00, 0.371178E+00}, + {-.685527E+00, 0.361106E+00}, {-.693241E+00, 0.351155E+00}, + {-.700745E+00, 0.341338E+00}, {-.708034E+00, 0.331669E+00}, + {-.715104E+00, 0.322159E+00}, {-.721952E+00, 0.312822E+00}, + {-.728574E+00, 0.303672E+00}, {-.734967E+00, 0.294721E+00}, + {-.741129E+00, 0.285982E+00}, {-.747057E+00, 0.277468E+00}, + {-.752750E+00, 0.269191E+00}, {-.758205E+00, 0.261163E+00}, + {-.763420E+00, 0.253396E+00}, {-.768396E+00, 0.245903E+00}, + {-.773130E+00, 0.238694E+00}, {-.777621E+00, 0.231781E+00}, + {-.781870E+00, 0.225174E+00}, {-.785875E+00, 0.218885E+00}, + {-.789637E+00, 0.212923E+00}, {-.793154E+00, 0.207297E+00}, + {-.796427E+00, 0.202018E+00}, {-.799457E+00, 0.197093E+00}, + {-.802242E+00, 0.192530E+00}, {-.804784E+00, 0.188338E+00}, + {-.807083E+00, 0.184523E+00}, {-.809138E+00, 0.181093E+00}, + {-.810951E+00, 0.178051E+00}, {-.812522E+00, 0.175405E+00}, + {-.813850E+00, 0.173158E+00}, {-.814936E+00, 0.171314E+00}, + {-.815781E+00, 0.169876E+00}, {-.816384E+00, 0.168848E+00}, + {-.816745E+00, 0.168230E+00}, {-.816866E+00, 0.168024E+00}, + }, + { + {-.156536E+00, 0.820754E+00}, {-.156748E+00, 0.820632E+00}, + {-.157383E+00, 0.820264E+00}, {-.158441E+00, 0.819651E+00}, + {-.159918E+00, 0.818793E+00}, {-.161814E+00, 0.817688E+00}, + {-.164124E+00, 0.816338E+00}, {-.166844E+00, 0.814742E+00}, + {-.169970E+00, 0.812899E+00}, {-.173497E+00, 0.810809E+00}, + {-.177418E+00, 0.808472E+00}, {-.181726E+00, 0.805887E+00}, + {-.186415E+00, 0.803053E+00}, {-.191476E+00, 0.799971E+00}, + {-.196901E+00, 0.796641E+00}, {-.202681E+00, 0.793061E+00}, + {-.208806E+00, 0.789233E+00}, {-.215267E+00, 0.785155E+00}, + {-.222054E+00, 0.780829E+00}, {-.229154E+00, 0.776255E+00}, + {-.236558E+00, 0.771433E+00}, {-.244254E+00, 0.766364E+00}, + {-.252229E+00, 0.761050E+00}, {-.260472E+00, 0.755490E+00}, + {-.268970E+00, 0.749687E+00}, {-.277711E+00, 0.743643E+00}, + {-.286681E+00, 0.737359E+00}, {-.295868E+00, 0.730838E+00}, + {-.305258E+00, 0.724082E+00}, {-.314839E+00, 0.717095E+00}, + {-.324596E+00, 0.709880E+00}, {-.334517E+00, 0.702440E+00}, + {-.344587E+00, 0.694780E+00}, {-.354794E+00, 0.686903E+00}, + {-.365124E+00, 0.678815E+00}, {-.375563E+00, 0.670520E+00}, + {-.386097E+00, 0.662024E+00}, {-.396715E+00, 0.653332E+00}, + {-.407401E+00, 0.644452E+00}, {-.418144E+00, 0.635388E+00}, + {-.428930E+00, 0.626150E+00}, {-.439746E+00, 0.616742E+00}, + {-.450579E+00, 0.607174E+00}, {-.461417E+00, 0.597453E+00}, + {-.472247E+00, 0.587587E+00}, {-.483058E+00, 0.577586E+00}, + {-.493837E+00, 0.567458E+00}, {-.504573E+00, 0.557212E+00}, + {-.515254E+00, 0.546859E+00}, {-.525870E+00, 0.536408E+00}, + {-.536408E+00, 0.525870E+00}, {-.546859E+00, 0.515254E+00}, + {-.557212E+00, 0.504573E+00}, {-.567458E+00, 0.493837E+00}, + {-.577586E+00, 0.483058E+00}, {-.587587E+00, 0.472247E+00}, + {-.597453E+00, 0.461417E+00}, {-.607174E+00, 0.450579E+00}, + {-.616742E+00, 0.439746E+00}, {-.626150E+00, 0.428930E+00}, + {-.635388E+00, 0.418144E+00}, {-.644452E+00, 0.407401E+00}, + {-.653332E+00, 0.396715E+00}, {-.662024E+00, 0.386097E+00}, + {-.670520E+00, 0.375563E+00}, {-.678815E+00, 0.365124E+00}, + {-.686903E+00, 0.354794E+00}, {-.694780E+00, 0.344587E+00}, + {-.702440E+00, 0.334517E+00}, {-.709880E+00, 0.324596E+00}, + {-.717095E+00, 0.314839E+00}, {-.724082E+00, 0.305258E+00}, + {-.730838E+00, 0.295868E+00}, {-.737359E+00, 0.286681E+00}, + {-.743643E+00, 0.277711E+00}, {-.749687E+00, 0.268970E+00}, + {-.755490E+00, 0.260472E+00}, {-.761050E+00, 0.252229E+00}, + {-.766364E+00, 0.244254E+00}, {-.771433E+00, 0.236558E+00}, + {-.776255E+00, 0.229154E+00}, {-.780829E+00, 0.222054E+00}, + {-.785155E+00, 0.215267E+00}, {-.789233E+00, 0.208806E+00}, + {-.793061E+00, 0.202681E+00}, {-.796641E+00, 0.196901E+00}, + {-.799971E+00, 0.191476E+00}, {-.803053E+00, 0.186415E+00}, + {-.805887E+00, 0.181726E+00}, {-.808472E+00, 0.177418E+00}, + {-.810809E+00, 0.173497E+00}, {-.812899E+00, 0.169970E+00}, + {-.814742E+00, 0.166844E+00}, {-.816338E+00, 0.164124E+00}, + {-.817688E+00, 0.161814E+00}, {-.818793E+00, 0.159918E+00}, + {-.819651E+00, 0.158441E+00}, {-.820264E+00, 0.157383E+00}, + {-.820632E+00, 0.156748E+00}, {-.820754E+00, 0.156536E+00}, + }, + { + {-.144778E+00, 0.824642E+00}, {-.144996E+00, 0.824518E+00}, + {-.145649E+00, 0.824144E+00}, {-.146736E+00, 0.823521E+00}, + {-.148255E+00, 0.822649E+00}, {-.150203E+00, 0.821527E+00}, + {-.152577E+00, 0.820155E+00}, {-.155373E+00, 0.818533E+00}, + {-.158586E+00, 0.816660E+00}, {-.162211E+00, 0.814535E+00}, + {-.166240E+00, 0.812159E+00}, {-.170667E+00, 0.809530E+00}, + {-.175485E+00, 0.806649E+00}, {-.180685E+00, 0.803514E+00}, + {-.186259E+00, 0.800126E+00}, {-.192197E+00, 0.796484E+00}, + {-.198489E+00, 0.792588E+00}, {-.205126E+00, 0.788438E+00}, + {-.212096E+00, 0.784034E+00}, {-.219389E+00, 0.779377E+00}, + {-.226992E+00, 0.774466E+00}, {-.234894E+00, 0.769303E+00}, + {-.243082E+00, 0.763888E+00}, {-.251544E+00, 0.758223E+00}, + {-.260268E+00, 0.752308E+00}, {-.269239E+00, 0.746147E+00}, + {-.278445E+00, 0.739739E+00}, {-.287873E+00, 0.733089E+00}, + {-.297509E+00, 0.726199E+00}, {-.307338E+00, 0.719070E+00}, + {-.317348E+00, 0.711708E+00}, {-.327525E+00, 0.704115E+00}, + {-.337854E+00, 0.696295E+00}, {-.348321E+00, 0.688253E+00}, + {-.358914E+00, 0.679994E+00}, {-.369617E+00, 0.671522E+00}, + {-.380418E+00, 0.662844E+00}, {-.391302E+00, 0.653964E+00}, + {-.402256E+00, 0.644890E+00}, {-.413266E+00, 0.635627E+00}, + {-.424318E+00, 0.626184E+00}, {-.435401E+00, 0.616567E+00}, + {-.446500E+00, 0.606784E+00}, {-.457603E+00, 0.596843E+00}, + {-.468696E+00, 0.586753E+00}, {-.479769E+00, 0.576523E+00}, + {-.490807E+00, 0.566162E+00}, {-.501800E+00, 0.555679E+00}, + {-.512735E+00, 0.545085E+00}, {-.523602E+00, 0.534389E+00}, + {-.534389E+00, 0.523602E+00}, {-.545085E+00, 0.512735E+00}, + {-.555679E+00, 0.501800E+00}, {-.566162E+00, 0.490807E+00}, + {-.576523E+00, 0.479769E+00}, {-.586753E+00, 0.468696E+00}, + {-.596843E+00, 0.457603E+00}, {-.606784E+00, 0.446500E+00}, + {-.616567E+00, 0.435401E+00}, {-.626184E+00, 0.424318E+00}, + {-.635627E+00, 0.413266E+00}, {-.644890E+00, 0.402256E+00}, + {-.653964E+00, 0.391302E+00}, {-.662844E+00, 0.380418E+00}, + {-.671522E+00, 0.369617E+00}, {-.679994E+00, 0.358914E+00}, + {-.688253E+00, 0.348321E+00}, {-.696295E+00, 0.337854E+00}, + {-.704115E+00, 0.327525E+00}, {-.711708E+00, 0.317348E+00}, + {-.719070E+00, 0.307338E+00}, {-.726199E+00, 0.297509E+00}, + {-.733089E+00, 0.287873E+00}, {-.739739E+00, 0.278445E+00}, + {-.746147E+00, 0.269239E+00}, {-.752308E+00, 0.260268E+00}, + {-.758223E+00, 0.251544E+00}, {-.763888E+00, 0.243082E+00}, + {-.769303E+00, 0.234894E+00}, {-.774466E+00, 0.226992E+00}, + {-.779377E+00, 0.219389E+00}, {-.784034E+00, 0.212096E+00}, + {-.788438E+00, 0.205126E+00}, {-.792588E+00, 0.198489E+00}, + {-.796484E+00, 0.192197E+00}, {-.800126E+00, 0.186259E+00}, + {-.803514E+00, 0.180685E+00}, {-.806649E+00, 0.175485E+00}, + {-.809530E+00, 0.170667E+00}, {-.812159E+00, 0.166240E+00}, + {-.814535E+00, 0.162211E+00}, {-.816660E+00, 0.158586E+00}, + {-.818533E+00, 0.155373E+00}, {-.820155E+00, 0.152577E+00}, + {-.821527E+00, 0.150203E+00}, {-.822649E+00, 0.148255E+00}, + {-.823521E+00, 0.146736E+00}, {-.824144E+00, 0.145649E+00}, + {-.824518E+00, 0.144996E+00}, {-.824642E+00, 0.144778E+00}, + }, + { + {-.132738E+00, 0.828531E+00}, {-.132962E+00, 0.828404E+00}, + {-.133633E+00, 0.828025E+00}, {-.134750E+00, 0.827392E+00}, + {-.136311E+00, 0.826506E+00}, {-.138314E+00, 0.825367E+00}, + {-.140754E+00, 0.823973E+00}, {-.143627E+00, 0.822324E+00}, + {-.146929E+00, 0.820421E+00}, {-.150653E+00, 0.818262E+00}, + {-.154793E+00, 0.815846E+00}, {-.159342E+00, 0.813174E+00}, + {-.164292E+00, 0.810244E+00}, {-.169635E+00, 0.807057E+00}, + {-.175361E+00, 0.803611E+00}, {-.181460E+00, 0.799906E+00}, + {-.187924E+00, 0.795942E+00}, {-.194740E+00, 0.791719E+00}, + {-.201898E+00, 0.787236E+00}, {-.209386E+00, 0.782495E+00}, + {-.217193E+00, 0.777494E+00}, {-.225306E+00, 0.772236E+00}, + {-.233712E+00, 0.766720E+00}, {-.242398E+00, 0.760948E+00}, + {-.251352E+00, 0.754921E+00}, {-.260559E+00, 0.748640E+00}, + {-.270006E+00, 0.742108E+00}, {-.279680E+00, 0.735327E+00}, + {-.289565E+00, 0.728299E+00}, {-.299649E+00, 0.721028E+00}, + {-.309917E+00, 0.713516E+00}, {-.320354E+00, 0.705768E+00}, + {-.330947E+00, 0.697787E+00}, {-.341681E+00, 0.689577E+00}, + {-.352541E+00, 0.681144E+00}, {-.363514E+00, 0.672493E+00}, + {-.374586E+00, 0.663629E+00}, {-.385742E+00, 0.654558E+00}, + {-.396968E+00, 0.645287E+00}, {-.408250E+00, 0.635822E+00}, + {-.419575E+00, 0.626171E+00}, {-.430930E+00, 0.616340E+00}, + {-.442299E+00, 0.606339E+00}, {-.453672E+00, 0.596175E+00}, + {-.465034E+00, 0.585857E+00}, {-.476372E+00, 0.575394E+00}, + {-.487675E+00, 0.564795E+00}, {-.498929E+00, 0.554071E+00}, + {-.510124E+00, 0.543231E+00}, {-.521246E+00, 0.532286E+00}, + {-.532286E+00, 0.521246E+00}, {-.543231E+00, 0.510124E+00}, + {-.554071E+00, 0.498929E+00}, {-.564795E+00, 0.487675E+00}, + {-.575394E+00, 0.476372E+00}, {-.585857E+00, 0.465034E+00}, + {-.596175E+00, 0.453672E+00}, {-.606339E+00, 0.442299E+00}, + {-.616340E+00, 0.430930E+00}, {-.626171E+00, 0.419575E+00}, + {-.635822E+00, 0.408250E+00}, {-.645287E+00, 0.396968E+00}, + {-.654558E+00, 0.385742E+00}, {-.663629E+00, 0.374586E+00}, + {-.672493E+00, 0.363514E+00}, {-.681144E+00, 0.352541E+00}, + {-.689577E+00, 0.341681E+00}, {-.697787E+00, 0.330947E+00}, + {-.705768E+00, 0.320354E+00}, {-.713516E+00, 0.309917E+00}, + {-.721028E+00, 0.299649E+00}, {-.728299E+00, 0.289565E+00}, + {-.735327E+00, 0.279680E+00}, {-.742108E+00, 0.270006E+00}, + {-.748640E+00, 0.260559E+00}, {-.754921E+00, 0.251352E+00}, + {-.760948E+00, 0.242398E+00}, {-.766720E+00, 0.233712E+00}, + {-.772236E+00, 0.225306E+00}, {-.777494E+00, 0.217193E+00}, + {-.782495E+00, 0.209386E+00}, {-.787236E+00, 0.201898E+00}, + {-.791719E+00, 0.194740E+00}, {-.795942E+00, 0.187924E+00}, + {-.799906E+00, 0.181460E+00}, {-.803611E+00, 0.175361E+00}, + {-.807057E+00, 0.169635E+00}, {-.810244E+00, 0.164292E+00}, + {-.813174E+00, 0.159342E+00}, {-.815846E+00, 0.154793E+00}, + {-.818262E+00, 0.150653E+00}, {-.820421E+00, 0.146929E+00}, + {-.822324E+00, 0.143627E+00}, {-.823973E+00, 0.140754E+00}, + {-.825367E+00, 0.138314E+00}, {-.826506E+00, 0.136311E+00}, + {-.827392E+00, 0.134750E+00}, {-.828025E+00, 0.133633E+00}, + {-.828404E+00, 0.132962E+00}, {-.828531E+00, 0.132738E+00}, + }, + { + {-.120402E+00, 0.832421E+00}, {-.120632E+00, 0.832292E+00}, + {-.121322E+00, 0.831907E+00}, {-.122470E+00, 0.831265E+00}, + {-.124074E+00, 0.830365E+00}, {-.126132E+00, 0.829207E+00}, + {-.128640E+00, 0.827792E+00}, {-.131593E+00, 0.826117E+00}, + {-.134985E+00, 0.824183E+00}, {-.138812E+00, 0.821989E+00}, + {-.143066E+00, 0.819535E+00}, {-.147740E+00, 0.816819E+00}, + {-.152825E+00, 0.813840E+00}, {-.158313E+00, 0.810600E+00}, + {-.164195E+00, 0.807095E+00}, {-.170459E+00, 0.803327E+00}, + {-.177097E+00, 0.799295E+00}, {-.184097E+00, 0.794998E+00}, + {-.191448E+00, 0.790436E+00}, {-.199136E+00, 0.785610E+00}, + {-.207151E+00, 0.780519E+00}, {-.215479E+00, 0.775164E+00}, + {-.224108E+00, 0.769546E+00}, {-.233023E+00, 0.763666E+00}, + {-.242212E+00, 0.757524E+00}, {-.251660E+00, 0.751123E+00}, + {-.261353E+00, 0.744465E+00}, {-.271278E+00, 0.737551E+00}, + {-.281419E+00, 0.730384E+00}, {-.291762E+00, 0.722968E+00}, + {-.302293E+00, 0.715304E+00}, {-.312997E+00, 0.707398E+00}, + {-.323859E+00, 0.699253E+00}, {-.334864E+00, 0.690873E+00}, + {-.345998E+00, 0.682264E+00}, {-.357246E+00, 0.673430E+00}, + {-.368594E+00, 0.664378E+00}, {-.380027E+00, 0.655112E+00}, + {-.391531E+00, 0.645641E+00}, {-.403091E+00, 0.635970E+00}, + {-.414694E+00, 0.626107E+00}, {-.426325E+00, 0.616060E+00}, + {-.437971E+00, 0.605836E+00}, {-.449618E+00, 0.595445E+00}, + {-.461253E+00, 0.584894E+00}, {-.472863E+00, 0.574194E+00}, + {-.484435E+00, 0.563354E+00}, {-.495956E+00, 0.552384E+00}, + {-.507415E+00, 0.541294E+00}, {-.518798E+00, 0.530095E+00}, + {-.530095E+00, 0.518798E+00}, {-.541294E+00, 0.507415E+00}, + {-.552384E+00, 0.495956E+00}, {-.563354E+00, 0.484435E+00}, + {-.574194E+00, 0.472863E+00}, {-.584894E+00, 0.461253E+00}, + {-.595445E+00, 0.449618E+00}, {-.605836E+00, 0.437971E+00}, + {-.616060E+00, 0.426325E+00}, {-.626107E+00, 0.414694E+00}, + {-.635970E+00, 0.403091E+00}, {-.645641E+00, 0.391531E+00}, + {-.655112E+00, 0.380027E+00}, {-.664378E+00, 0.368594E+00}, + {-.673430E+00, 0.357246E+00}, {-.682264E+00, 0.345998E+00}, + {-.690873E+00, 0.334864E+00}, {-.699253E+00, 0.323859E+00}, + {-.707398E+00, 0.312997E+00}, {-.715304E+00, 0.302293E+00}, + {-.722968E+00, 0.291762E+00}, {-.730384E+00, 0.281419E+00}, + {-.737551E+00, 0.271278E+00}, {-.744465E+00, 0.261353E+00}, + {-.751123E+00, 0.251660E+00}, {-.757524E+00, 0.242212E+00}, + {-.763666E+00, 0.233023E+00}, {-.769546E+00, 0.224108E+00}, + {-.775164E+00, 0.215479E+00}, {-.780519E+00, 0.207151E+00}, + {-.785610E+00, 0.199136E+00}, {-.790436E+00, 0.191448E+00}, + {-.794998E+00, 0.184097E+00}, {-.799295E+00, 0.177097E+00}, + {-.803327E+00, 0.170459E+00}, {-.807095E+00, 0.164195E+00}, + {-.810600E+00, 0.158313E+00}, {-.813840E+00, 0.152825E+00}, + {-.816819E+00, 0.147740E+00}, {-.819535E+00, 0.143066E+00}, + {-.821989E+00, 0.138812E+00}, {-.824183E+00, 0.134985E+00}, + {-.826117E+00, 0.131593E+00}, {-.827792E+00, 0.128640E+00}, + {-.829207E+00, 0.126132E+00}, {-.830365E+00, 0.124074E+00}, + {-.831265E+00, 0.122470E+00}, {-.831907E+00, 0.121322E+00}, + {-.832292E+00, 0.120632E+00}, {-.832421E+00, 0.120402E+00}, + }, + { + {-.107757E+00, 0.836313E+00}, {-.107994E+00, 0.836183E+00}, + {-.108702E+00, 0.835792E+00}, {-.109882E+00, 0.835139E+00}, + {-.111531E+00, 0.834226E+00}, {-.113646E+00, 0.833050E+00}, + {-.116222E+00, 0.831613E+00}, {-.119256E+00, 0.829912E+00}, + {-.122742E+00, 0.827948E+00}, {-.126674E+00, 0.825719E+00}, + {-.131045E+00, 0.823225E+00}, {-.135846E+00, 0.820465E+00}, + {-.141070E+00, 0.817438E+00}, {-.146707E+00, 0.814143E+00}, + {-.152748E+00, 0.810581E+00}, {-.159182E+00, 0.806749E+00}, + {-.165999E+00, 0.802647E+00}, {-.173186E+00, 0.798276E+00}, + {-.180733E+00, 0.793634E+00}, {-.188627E+00, 0.788722E+00}, + {-.196855E+00, 0.783539E+00}, {-.205403E+00, 0.778087E+00}, + {-.214259E+00, 0.772366E+00}, {-.223409E+00, 0.766376E+00}, + {-.232838E+00, 0.760119E+00}, {-.242532E+00, 0.753596E+00}, + {-.252477E+00, 0.746809E+00}, {-.262658E+00, 0.739761E+00}, + {-.273060E+00, 0.732453E+00}, {-.283668E+00, 0.724889E+00}, + {-.294468E+00, 0.717072E+00}, {-.305443E+00, 0.709005E+00}, + {-.316580E+00, 0.700693E+00}, {-.327862E+00, 0.692141E+00}, + {-.339276E+00, 0.683352E+00}, {-.350805E+00, 0.674332E+00}, + {-.362435E+00, 0.665088E+00}, {-.374151E+00, 0.655625E+00}, + {-.385938E+00, 0.645950E+00}, {-.397782E+00, 0.636069E+00}, + {-.409667E+00, 0.625991E+00}, {-.421581E+00, 0.615722E+00}, + {-.433509E+00, 0.605272E+00}, {-.445436E+00, 0.594649E+00}, + {-.457350E+00, 0.583862E+00}, {-.469236E+00, 0.572921E+00}, + {-.481083E+00, 0.561834E+00}, {-.492876E+00, 0.550614E+00}, + {-.504603E+00, 0.539269E+00}, {-.516252E+00, 0.527811E+00}, + {-.527811E+00, 0.516252E+00}, {-.539269E+00, 0.504603E+00}, + {-.550614E+00, 0.492876E+00}, {-.561834E+00, 0.481083E+00}, + {-.572921E+00, 0.469236E+00}, {-.583862E+00, 0.457350E+00}, + {-.594649E+00, 0.445436E+00}, {-.605272E+00, 0.433509E+00}, + {-.615722E+00, 0.421581E+00}, {-.625991E+00, 0.409667E+00}, + {-.636069E+00, 0.397782E+00}, {-.645950E+00, 0.385938E+00}, + {-.655625E+00, 0.374151E+00}, {-.665088E+00, 0.362435E+00}, + {-.674332E+00, 0.350805E+00}, {-.683352E+00, 0.339276E+00}, + {-.692141E+00, 0.327862E+00}, {-.700693E+00, 0.316580E+00}, + {-.709005E+00, 0.305443E+00}, {-.717072E+00, 0.294468E+00}, + {-.724889E+00, 0.283668E+00}, {-.732453E+00, 0.273060E+00}, + {-.739761E+00, 0.262658E+00}, {-.746809E+00, 0.252477E+00}, + {-.753596E+00, 0.242532E+00}, {-.760119E+00, 0.232838E+00}, + {-.766376E+00, 0.223409E+00}, {-.772366E+00, 0.214259E+00}, + {-.778087E+00, 0.205403E+00}, {-.783539E+00, 0.196855E+00}, + {-.788722E+00, 0.188627E+00}, {-.793634E+00, 0.180733E+00}, + {-.798276E+00, 0.173186E+00}, {-.802647E+00, 0.165999E+00}, + {-.806749E+00, 0.159182E+00}, {-.810581E+00, 0.152748E+00}, + {-.814143E+00, 0.146707E+00}, {-.817438E+00, 0.141070E+00}, + {-.820465E+00, 0.135846E+00}, {-.823225E+00, 0.131045E+00}, + {-.825719E+00, 0.126674E+00}, {-.827948E+00, 0.122742E+00}, + {-.829912E+00, 0.119256E+00}, {-.831613E+00, 0.116222E+00}, + {-.833050E+00, 0.113646E+00}, {-.834226E+00, 0.111531E+00}, + {-.835139E+00, 0.109882E+00}, {-.835792E+00, 0.108702E+00}, + {-.836183E+00, 0.107994E+00}, {-.836313E+00, 0.107757E+00}, + }, + { + {-.947884E-01, 0.840209E+00}, {-.950314E-01, 0.840077E+00}, + {-.957597E-01, 0.839680E+00}, {-.969721E-01, 0.839017E+00}, + {-.986664E-01, 0.838090E+00}, {-.100839E+00, 0.836897E+00}, + {-.103487E+00, 0.835437E+00}, {-.106604E+00, 0.833710E+00}, + {-.110186E+00, 0.831715E+00}, {-.114225E+00, 0.829451E+00}, + {-.118715E+00, 0.826918E+00}, {-.123647E+00, 0.824113E+00}, + {-.129013E+00, 0.821038E+00}, {-.134803E+00, 0.817689E+00}, + {-.141007E+00, 0.814067E+00}, {-.147615E+00, 0.810171E+00}, + {-.154614E+00, 0.806000E+00}, {-.161995E+00, 0.801553E+00}, + {-.169743E+00, 0.796830E+00}, {-.177846E+00, 0.791832E+00}, + {-.186292E+00, 0.786557E+00}, {-.195066E+00, 0.781006E+00}, + {-.204154E+00, 0.775180E+00}, {-.213544E+00, 0.769079E+00}, + {-.223218E+00, 0.762705E+00}, {-.233164E+00, 0.756058E+00}, + {-.243366E+00, 0.749141E+00}, {-.253809E+00, 0.741956E+00}, + {-.264478E+00, 0.734505E+00}, {-.275357E+00, 0.726791E+00}, + {-.286431E+00, 0.718817E+00}, {-.297684E+00, 0.710588E+00}, + {-.309102E+00, 0.702106E+00}, {-.320667E+00, 0.693378E+00}, + {-.332366E+00, 0.684406E+00}, {-.344181E+00, 0.675198E+00}, + {-.356099E+00, 0.665758E+00}, {-.368104E+00, 0.656094E+00}, + {-.380180E+00, 0.646211E+00}, {-.392313E+00, 0.636116E+00}, + {-.404488E+00, 0.625819E+00}, {-.416690E+00, 0.615325E+00}, + {-.428905E+00, 0.604644E+00}, {-.441118E+00, 0.593785E+00}, + {-.453316E+00, 0.582757E+00}, {-.465485E+00, 0.571569E+00}, + {-.477611E+00, 0.560232E+00}, {-.489681E+00, 0.548756E+00}, + {-.501683E+00, 0.537151E+00}, {-.513603E+00, 0.525430E+00}, + {-.525430E+00, 0.513603E+00}, {-.537151E+00, 0.501683E+00}, + {-.548756E+00, 0.489681E+00}, {-.560232E+00, 0.477611E+00}, + {-.571569E+00, 0.465485E+00}, {-.582757E+00, 0.453316E+00}, + {-.593785E+00, 0.441118E+00}, {-.604644E+00, 0.428905E+00}, + {-.615325E+00, 0.416690E+00}, {-.625819E+00, 0.404488E+00}, + {-.636116E+00, 0.392313E+00}, {-.646211E+00, 0.380180E+00}, + {-.656094E+00, 0.368104E+00}, {-.665758E+00, 0.356099E+00}, + {-.675198E+00, 0.344181E+00}, {-.684406E+00, 0.332366E+00}, + {-.693378E+00, 0.320667E+00}, {-.702106E+00, 0.309102E+00}, + {-.710588E+00, 0.297684E+00}, {-.718817E+00, 0.286431E+00}, + {-.726791E+00, 0.275357E+00}, {-.734505E+00, 0.264478E+00}, + {-.741956E+00, 0.253809E+00}, {-.749141E+00, 0.243366E+00}, + {-.756058E+00, 0.233164E+00}, {-.762705E+00, 0.223218E+00}, + {-.769079E+00, 0.213544E+00}, {-.775180E+00, 0.204154E+00}, + {-.781006E+00, 0.195066E+00}, {-.786557E+00, 0.186292E+00}, + {-.791832E+00, 0.177846E+00}, {-.796830E+00, 0.169743E+00}, + {-.801553E+00, 0.161995E+00}, {-.806000E+00, 0.154614E+00}, + {-.810171E+00, 0.147615E+00}, {-.814067E+00, 0.141007E+00}, + {-.817689E+00, 0.134803E+00}, {-.821038E+00, 0.129013E+00}, + {-.824113E+00, 0.123647E+00}, {-.826918E+00, 0.118715E+00}, + {-.829451E+00, 0.114225E+00}, {-.831715E+00, 0.110186E+00}, + {-.833710E+00, 0.106604E+00}, {-.835437E+00, 0.103487E+00}, + {-.836897E+00, 0.100839E+00}, {-.838090E+00, 0.986664E-01}, + {-.839017E+00, 0.969721E-01}, {-.839680E+00, 0.957597E-01}, + {-.840077E+00, 0.950314E-01}, {-.840209E+00, 0.947884E-01}, + }, + { + {-.814802E-01, 0.844109E+00}, {-.817299E-01, 0.843975E+00}, + {-.824783E-01, 0.843571E+00}, {-.837241E-01, 0.842899E+00}, + {-.854650E-01, 0.841958E+00}, {-.876977E-01, 0.840747E+00}, + {-.904180E-01, 0.839265E+00}, {-.936210E-01, 0.837512E+00}, + {-.973007E-01, 0.835486E+00}, {-.101450E+00, 0.833187E+00}, + {-.106062E+00, 0.830614E+00}, {-.111129E+00, 0.827765E+00}, + {-.116640E+00, 0.824640E+00}, {-.122587E+00, 0.821237E+00}, + {-.128958E+00, 0.817556E+00}, {-.135743E+00, 0.813595E+00}, + {-.142931E+00, 0.809353E+00}, {-.150508E+00, 0.804830E+00}, + {-.158462E+00, 0.800026E+00}, {-.166780E+00, 0.794940E+00}, + {-.175449E+00, 0.789571E+00}, {-.184454E+00, 0.783920E+00}, + {-.193781E+00, 0.777988E+00}, {-.203415E+00, 0.771775E+00}, + {-.213341E+00, 0.765281E+00}, {-.223544E+00, 0.758509E+00}, + {-.234009E+00, 0.751460E+00}, {-.244720E+00, 0.744136E+00}, + {-.255662E+00, 0.736539E+00}, {-.266818E+00, 0.728673E+00}, + {-.278172E+00, 0.720540E+00}, {-.289709E+00, 0.712145E+00}, + {-.301413E+00, 0.703491E+00}, {-.313268E+00, 0.694582E+00}, + {-.325258E+00, 0.685425E+00}, {-.337366E+00, 0.676024E+00}, + {-.349578E+00, 0.666386E+00}, {-.361878E+00, 0.656516E+00}, + {-.374249E+00, 0.646421E+00}, {-.386678E+00, 0.636109E+00}, + {-.399148E+00, 0.625588E+00}, {-.411644E+00, 0.614865E+00}, + {-.424152E+00, 0.603949E+00}, {-.436657E+00, 0.592849E+00}, + {-.449145E+00, 0.581575E+00}, {-.461602E+00, 0.570136E+00}, + {-.474014E+00, 0.558542E+00}, {-.486367E+00, 0.546806E+00}, + {-.498649E+00, 0.534936E+00}, {-.510846E+00, 0.522946E+00}, + {-.522946E+00, 0.510846E+00}, {-.534936E+00, 0.498649E+00}, + {-.546806E+00, 0.486367E+00}, {-.558542E+00, 0.474014E+00}, + {-.570136E+00, 0.461602E+00}, {-.581575E+00, 0.449145E+00}, + {-.592849E+00, 0.436657E+00}, {-.603949E+00, 0.424152E+00}, + {-.614865E+00, 0.411644E+00}, {-.625588E+00, 0.399148E+00}, + {-.636109E+00, 0.386678E+00}, {-.646421E+00, 0.374249E+00}, + {-.656516E+00, 0.361878E+00}, {-.666386E+00, 0.349578E+00}, + {-.676024E+00, 0.337366E+00}, {-.685425E+00, 0.325258E+00}, + {-.694582E+00, 0.313268E+00}, {-.703491E+00, 0.301413E+00}, + {-.712145E+00, 0.289709E+00}, {-.720540E+00, 0.278172E+00}, + {-.728673E+00, 0.266818E+00}, {-.736539E+00, 0.255662E+00}, + {-.744136E+00, 0.244720E+00}, {-.751460E+00, 0.234009E+00}, + {-.758509E+00, 0.223544E+00}, {-.765281E+00, 0.213341E+00}, + {-.771775E+00, 0.203415E+00}, {-.777988E+00, 0.193781E+00}, + {-.783920E+00, 0.184454E+00}, {-.789571E+00, 0.175449E+00}, + {-.794940E+00, 0.166780E+00}, {-.800026E+00, 0.158462E+00}, + {-.804830E+00, 0.150508E+00}, {-.809353E+00, 0.142931E+00}, + {-.813595E+00, 0.135743E+00}, {-.817556E+00, 0.128958E+00}, + {-.821237E+00, 0.122587E+00}, {-.824640E+00, 0.116640E+00}, + {-.827765E+00, 0.111129E+00}, {-.830614E+00, 0.106062E+00}, + {-.833187E+00, 0.101450E+00}, {-.835486E+00, 0.973007E-01}, + {-.837512E+00, 0.936210E-01}, {-.839265E+00, 0.904180E-01}, + {-.840747E+00, 0.876977E-01}, {-.841958E+00, 0.854650E-01}, + {-.842899E+00, 0.837241E-01}, {-.843571E+00, 0.824783E-01}, + {-.843975E+00, 0.817299E-01}, {-.844109E+00, 0.814802E-01}, + }, + { + {-.678158E-01, 0.848014E+00}, {-.680723E-01, 0.847877E+00}, + {-.688414E-01, 0.847468E+00}, {-.701215E-01, 0.846786E+00}, + {-.719103E-01, 0.845831E+00}, {-.742043E-01, 0.844602E+00}, + {-.769993E-01, 0.843098E+00}, {-.802900E-01, 0.841318E+00}, + {-.840704E-01, 0.839262E+00}, {-.883333E-01, 0.836928E+00}, + {-.930710E-01, 0.834314E+00}, {-.982748E-01, 0.831421E+00}, + {-.103935E+00, 0.828246E+00}, {-.110043E+00, 0.824788E+00}, + {-.116586E+00, 0.821047E+00}, {-.123553E+00, 0.817020E+00}, + {-.130932E+00, 0.812708E+00}, {-.138712E+00, 0.808108E+00}, + {-.146877E+00, 0.803221E+00}, {-.155415E+00, 0.798046E+00}, + {-.164313E+00, 0.792583E+00}, {-.173554E+00, 0.786831E+00}, + {-.183124E+00, 0.780791E+00}, {-.193009E+00, 0.774463E+00}, + {-.203193E+00, 0.767849E+00}, {-.213660E+00, 0.760949E+00}, + {-.224394E+00, 0.753765E+00}, {-.235379E+00, 0.746300E+00}, + {-.246599E+00, 0.738555E+00}, {-.258038E+00, 0.730533E+00}, + {-.269680E+00, 0.722239E+00}, {-.281507E+00, 0.713674E+00}, + {-.293504E+00, 0.704844E+00}, {-.305654E+00, 0.695753E+00}, + {-.317942E+00, 0.686407E+00}, {-.330350E+00, 0.676809E+00}, + {-.342862E+00, 0.666968E+00}, {-.355463E+00, 0.656888E+00}, + {-.368136E+00, 0.646578E+00}, {-.380866E+00, 0.636044E+00}, + {-.393638E+00, 0.625294E+00}, {-.406435E+00, 0.614337E+00}, + {-.419242E+00, 0.603181E+00}, {-.432046E+00, 0.591835E+00}, + {-.444830E+00, 0.580310E+00}, {-.457581E+00, 0.568615E+00}, + {-.470285E+00, 0.556760E+00}, {-.482927E+00, 0.544757E+00}, + {-.495494E+00, 0.532617E+00}, {-.507974E+00, 0.520352E+00}, + {-.520352E+00, 0.507974E+00}, {-.532617E+00, 0.495494E+00}, + {-.544757E+00, 0.482927E+00}, {-.556760E+00, 0.470285E+00}, + {-.568615E+00, 0.457581E+00}, {-.580310E+00, 0.444830E+00}, + {-.591835E+00, 0.432046E+00}, {-.603181E+00, 0.419242E+00}, + {-.614337E+00, 0.406435E+00}, {-.625294E+00, 0.393638E+00}, + {-.636044E+00, 0.380866E+00}, {-.646578E+00, 0.368136E+00}, + {-.656888E+00, 0.355463E+00}, {-.666968E+00, 0.342862E+00}, + {-.676809E+00, 0.330350E+00}, {-.686407E+00, 0.317942E+00}, + {-.695753E+00, 0.305654E+00}, {-.704844E+00, 0.293504E+00}, + {-.713674E+00, 0.281507E+00}, {-.722239E+00, 0.269680E+00}, + {-.730533E+00, 0.258038E+00}, {-.738555E+00, 0.246599E+00}, + {-.746300E+00, 0.235379E+00}, {-.753765E+00, 0.224394E+00}, + {-.760949E+00, 0.213660E+00}, {-.767849E+00, 0.203193E+00}, + {-.774463E+00, 0.193009E+00}, {-.780791E+00, 0.183124E+00}, + {-.786831E+00, 0.173554E+00}, {-.792583E+00, 0.164313E+00}, + {-.798046E+00, 0.155415E+00}, {-.803221E+00, 0.146877E+00}, + {-.808108E+00, 0.138712E+00}, {-.812708E+00, 0.130932E+00}, + {-.817020E+00, 0.123553E+00}, {-.821047E+00, 0.116586E+00}, + {-.824788E+00, 0.110043E+00}, {-.828246E+00, 0.103935E+00}, + {-.831421E+00, 0.982748E-01}, {-.834314E+00, 0.930710E-01}, + {-.836928E+00, 0.883333E-01}, {-.839262E+00, 0.840704E-01}, + {-.841318E+00, 0.802900E-01}, {-.843098E+00, 0.769993E-01}, + {-.844602E+00, 0.742043E-01}, {-.845831E+00, 0.719103E-01}, + {-.846786E+00, 0.701215E-01}, {-.847468E+00, 0.688414E-01}, + {-.847877E+00, 0.680723E-01}, {-.848014E+00, 0.678158E-01}, + }, + { + {-.537773E-01, 0.851924E+00}, {-.540409E-01, 0.851786E+00}, + {-.548311E-01, 0.851371E+00}, {-.561464E-01, 0.850679E+00}, + {-.579844E-01, 0.849710E+00}, {-.603415E-01, 0.848463E+00}, + {-.632131E-01, 0.846937E+00}, {-.665940E-01, 0.845130E+00}, + {-.704777E-01, 0.843043E+00}, {-.748569E-01, 0.840673E+00}, + {-.797235E-01, 0.838020E+00}, {-.850686E-01, 0.835081E+00}, + {-.908824E-01, 0.831857E+00}, {-.971544E-01, 0.828344E+00}, + {-.103873E+00, 0.824542E+00}, {-.111027E+00, 0.820449E+00}, + {-.118604E+00, 0.816064E+00}, {-.126590E+00, 0.811387E+00}, + {-.134972E+00, 0.806416E+00}, {-.143736E+00, 0.801151E+00}, + {-.152867E+00, 0.795591E+00}, {-.162351E+00, 0.789737E+00}, + {-.172171E+00, 0.783587E+00}, {-.182313E+00, 0.777144E+00}, + {-.192760E+00, 0.770406E+00}, {-.203497E+00, 0.763377E+00}, + {-.214506E+00, 0.756056E+00}, {-.225772E+00, 0.748447E+00}, + {-.237277E+00, 0.740551E+00}, {-.249006E+00, 0.732372E+00}, + {-.260941E+00, 0.723912E+00}, {-.273065E+00, 0.715175E+00}, + {-.285362E+00, 0.706166E+00}, {-.297815E+00, 0.696888E+00}, + {-.310406E+00, 0.687348E+00}, {-.323120E+00, 0.677551E+00}, + {-.335940E+00, 0.667502E+00}, {-.348849E+00, 0.657209E+00}, + {-.361831E+00, 0.646678E+00}, {-.374869E+00, 0.635917E+00}, + {-.387948E+00, 0.624935E+00}, {-.401053E+00, 0.613738E+00}, + {-.414167E+00, 0.602337E+00}, {-.427275E+00, 0.590740E+00}, + {-.440362E+00, 0.578959E+00}, {-.453413E+00, 0.567002E+00}, + {-.466415E+00, 0.554880E+00}, {-.479352E+00, 0.542605E+00}, + {-.492212E+00, 0.530189E+00}, {-.504980E+00, 0.517643E+00}, + {-.517643E+00, 0.504980E+00}, {-.530189E+00, 0.492212E+00}, + {-.542605E+00, 0.479352E+00}, {-.554880E+00, 0.466415E+00}, + {-.567002E+00, 0.453413E+00}, {-.578959E+00, 0.440362E+00}, + {-.590740E+00, 0.427275E+00}, {-.602337E+00, 0.414167E+00}, + {-.613738E+00, 0.401053E+00}, {-.624935E+00, 0.387948E+00}, + {-.635917E+00, 0.374869E+00}, {-.646678E+00, 0.361831E+00}, + {-.657209E+00, 0.348849E+00}, {-.667502E+00, 0.335940E+00}, + {-.677551E+00, 0.323120E+00}, {-.687348E+00, 0.310406E+00}, + {-.696888E+00, 0.297815E+00}, {-.706166E+00, 0.285362E+00}, + {-.715175E+00, 0.273065E+00}, {-.723912E+00, 0.260941E+00}, + {-.732372E+00, 0.249006E+00}, {-.740551E+00, 0.237277E+00}, + {-.748447E+00, 0.225772E+00}, {-.756056E+00, 0.214506E+00}, + {-.763377E+00, 0.203497E+00}, {-.770406E+00, 0.192760E+00}, + {-.777144E+00, 0.182313E+00}, {-.783587E+00, 0.172171E+00}, + {-.789737E+00, 0.162351E+00}, {-.795591E+00, 0.152867E+00}, + {-.801151E+00, 0.143736E+00}, {-.806416E+00, 0.134972E+00}, + {-.811387E+00, 0.126590E+00}, {-.816064E+00, 0.118604E+00}, + {-.820449E+00, 0.111027E+00}, {-.824542E+00, 0.103873E+00}, + {-.828344E+00, 0.971544E-01}, {-.831857E+00, 0.908824E-01}, + {-.835081E+00, 0.850686E-01}, {-.838020E+00, 0.797235E-01}, + {-.840673E+00, 0.748569E-01}, {-.843043E+00, 0.704777E-01}, + {-.845130E+00, 0.665940E-01}, {-.846937E+00, 0.632131E-01}, + {-.848463E+00, 0.603415E-01}, {-.849710E+00, 0.579844E-01}, + {-.850679E+00, 0.561464E-01}, {-.851371E+00, 0.548311E-01}, + {-.851786E+00, 0.540409E-01}, {-.851924E+00, 0.537773E-01}, + }, + { + {-.393454E-01, 0.855842E+00}, {-.396163E-01, 0.855702E+00}, + {-.404283E-01, 0.855281E+00}, {-.417799E-01, 0.854579E+00}, + {-.436684E-01, 0.853596E+00}, {-.460902E-01, 0.852331E+00}, + {-.490407E-01, 0.850782E+00}, {-.525142E-01, 0.848949E+00}, + {-.565040E-01, 0.846831E+00}, {-.610027E-01, 0.844425E+00}, + {-.660017E-01, 0.841732E+00}, {-.714919E-01, 0.838748E+00}, + {-.774630E-01, 0.835472E+00}, {-.839041E-01, 0.831904E+00}, + {-.908038E-01, 0.828040E+00}, {-.981495E-01, 0.823881E+00}, + {-.105929E+00, 0.819424E+00}, {-.114127E+00, 0.814668E+00}, + {-.122731E+00, 0.809612E+00}, {-.131726E+00, 0.804256E+00}, + {-.141098E+00, 0.798598E+00}, {-.150829E+00, 0.792639E+00}, + {-.160906E+00, 0.786378E+00}, {-.171311E+00, 0.779816E+00}, + {-.182028E+00, 0.772954E+00}, {-.193040E+00, 0.765792E+00}, + {-.204332E+00, 0.758332E+00}, {-.215885E+00, 0.750577E+00}, + {-.227683E+00, 0.742527E+00}, {-.239708E+00, 0.734186E+00}, + {-.251943E+00, 0.725558E+00}, {-.264371E+00, 0.716645E+00}, + {-.276975E+00, 0.707453E+00}, {-.289737E+00, 0.697985E+00}, + {-.302640E+00, 0.688248E+00}, {-.315667E+00, 0.678246E+00}, + {-.328801E+00, 0.667986E+00}, {-.342025E+00, 0.657474E+00}, + {-.355322E+00, 0.646718E+00}, {-.368676E+00, 0.635726E+00}, + {-.382070E+00, 0.624505E+00}, {-.395489E+00, 0.613064E+00}, + {-.408915E+00, 0.601412E+00}, {-.422335E+00, 0.589559E+00}, + {-.435732E+00, 0.577515E+00}, {-.449091E+00, 0.565290E+00}, + {-.462397E+00, 0.552896E+00}, {-.475636E+00, 0.540343E+00}, + {-.488794E+00, 0.527645E+00}, {-.501857E+00, 0.514812E+00}, + {-.514812E+00, 0.501857E+00}, {-.527645E+00, 0.488794E+00}, + {-.540343E+00, 0.475636E+00}, {-.552896E+00, 0.462397E+00}, + {-.565290E+00, 0.449091E+00}, {-.577515E+00, 0.435732E+00}, + {-.589559E+00, 0.422335E+00}, {-.601412E+00, 0.408915E+00}, + {-.613064E+00, 0.395489E+00}, {-.624505E+00, 0.382070E+00}, + {-.635726E+00, 0.368676E+00}, {-.646718E+00, 0.355322E+00}, + {-.657474E+00, 0.342025E+00}, {-.667986E+00, 0.328801E+00}, + {-.678246E+00, 0.315667E+00}, {-.688248E+00, 0.302640E+00}, + {-.697985E+00, 0.289737E+00}, {-.707453E+00, 0.276975E+00}, + {-.716645E+00, 0.264371E+00}, {-.725558E+00, 0.251943E+00}, + {-.734186E+00, 0.239708E+00}, {-.742527E+00, 0.227683E+00}, + {-.750577E+00, 0.215885E+00}, {-.758332E+00, 0.204332E+00}, + {-.765792E+00, 0.193040E+00}, {-.772954E+00, 0.182028E+00}, + {-.779816E+00, 0.171311E+00}, {-.786378E+00, 0.160906E+00}, + {-.792639E+00, 0.150829E+00}, {-.798598E+00, 0.141098E+00}, + {-.804256E+00, 0.131726E+00}, {-.809612E+00, 0.122731E+00}, + {-.814668E+00, 0.114127E+00}, {-.819424E+00, 0.105929E+00}, + {-.823881E+00, 0.981495E-01}, {-.828040E+00, 0.908038E-01}, + {-.831904E+00, 0.839041E-01}, {-.835472E+00, 0.774630E-01}, + {-.838748E+00, 0.714919E-01}, {-.841732E+00, 0.660017E-01}, + {-.844425E+00, 0.610027E-01}, {-.846831E+00, 0.565040E-01}, + {-.848949E+00, 0.525142E-01}, {-.850782E+00, 0.490407E-01}, + {-.852331E+00, 0.460902E-01}, {-.853596E+00, 0.436684E-01}, + {-.854579E+00, 0.417799E-01}, {-.855281E+00, 0.404283E-01}, + {-.855702E+00, 0.396163E-01}, {-.855842E+00, 0.393454E-01}, + }, + { + {-.244998E-01, 0.859768E+00}, {-.247781E-01, 0.859625E+00}, + {-.256125E-01, 0.859199E+00}, {-.270013E-01, 0.858487E+00}, + {-.289418E-01, 0.857490E+00}, {-.314303E-01, 0.856206E+00}, + {-.344618E-01, 0.854635E+00}, {-.380305E-01, 0.852776E+00}, + {-.421295E-01, 0.850626E+00}, {-.467510E-01, 0.848185E+00}, + {-.518861E-01, 0.845450E+00}, {-.575253E-01, 0.842420E+00}, + {-.636581E-01, 0.839094E+00}, {-.702730E-01, 0.835469E+00}, + {-.773582E-01, 0.831544E+00}, {-.849008E-01, 0.827317E+00}, + {-.928874E-01, 0.822786E+00}, {-.101304E+00, 0.817950E+00}, + {-.110136E+00, 0.812808E+00}, {-.119369E+00, 0.807359E+00}, + {-.128986E+00, 0.801602E+00}, {-.138972E+00, 0.795537E+00}, + {-.149311E+00, 0.789163E+00}, {-.159986E+00, 0.782481E+00}, + {-.170980E+00, 0.775491E+00}, {-.182275E+00, 0.768195E+00}, + {-.193856E+00, 0.760593E+00}, {-.205703E+00, 0.752687E+00}, + {-.217800E+00, 0.744481E+00}, {-.230129E+00, 0.735975E+00}, + {-.242672E+00, 0.727175E+00}, {-.255411E+00, 0.718083E+00}, + {-.268329E+00, 0.708704E+00}, {-.281408E+00, 0.699041E+00}, + {-.294629E+00, 0.689102E+00}, {-.307977E+00, 0.678891E+00}, + {-.321432E+00, 0.668415E+00}, {-.334978E+00, 0.657680E+00}, + {-.348598E+00, 0.646694E+00}, {-.362275E+00, 0.635464E+00}, + {-.375991E+00, 0.623999E+00}, {-.389731E+00, 0.612308E+00}, + {-.403478E+00, 0.600400E+00}, {-.417217E+00, 0.588285E+00}, + {-.430930E+00, 0.575973E+00}, {-.444603E+00, 0.563474E+00}, + {-.458221E+00, 0.550801E+00}, {-.471769E+00, 0.537964E+00}, + {-.485233E+00, 0.524976E+00}, {-.498598E+00, 0.511850E+00}, + {-.511850E+00, 0.498598E+00}, {-.524976E+00, 0.485233E+00}, + {-.537964E+00, 0.471769E+00}, {-.550801E+00, 0.458221E+00}, + {-.563474E+00, 0.444603E+00}, {-.575973E+00, 0.430930E+00}, + {-.588285E+00, 0.417217E+00}, {-.600400E+00, 0.403478E+00}, + {-.612308E+00, 0.389731E+00}, {-.623999E+00, 0.375991E+00}, + {-.635464E+00, 0.362275E+00}, {-.646694E+00, 0.348598E+00}, + {-.657680E+00, 0.334978E+00}, {-.668415E+00, 0.321432E+00}, + {-.678891E+00, 0.307977E+00}, {-.689102E+00, 0.294629E+00}, + {-.699041E+00, 0.281408E+00}, {-.708704E+00, 0.268329E+00}, + {-.718083E+00, 0.255411E+00}, {-.727175E+00, 0.242672E+00}, + {-.735975E+00, 0.230129E+00}, {-.744481E+00, 0.217800E+00}, + {-.752687E+00, 0.205703E+00}, {-.760593E+00, 0.193856E+00}, + {-.768195E+00, 0.182275E+00}, {-.775491E+00, 0.170980E+00}, + {-.782481E+00, 0.159986E+00}, {-.789163E+00, 0.149311E+00}, + {-.795537E+00, 0.138972E+00}, {-.801602E+00, 0.128986E+00}, + {-.807359E+00, 0.119369E+00}, {-.812808E+00, 0.110136E+00}, + {-.817950E+00, 0.101304E+00}, {-.822786E+00, 0.928874E-01}, + {-.827317E+00, 0.849008E-01}, {-.831544E+00, 0.773582E-01}, + {-.835469E+00, 0.702730E-01}, {-.839094E+00, 0.636581E-01}, + {-.842420E+00, 0.575253E-01}, {-.845450E+00, 0.518861E-01}, + {-.848185E+00, 0.467510E-01}, {-.850626E+00, 0.421295E-01}, + {-.852776E+00, 0.380305E-01}, {-.854635E+00, 0.344618E-01}, + {-.856206E+00, 0.314303E-01}, {-.857490E+00, 0.289418E-01}, + {-.858487E+00, 0.270013E-01}, {-.859199E+00, 0.256125E-01}, + {-.859625E+00, 0.247781E-01}, {-.859768E+00, 0.244998E-01}, + }, + { + {-.921795E-02, 0.863702E+00}, {-.950396E-02, 0.863558E+00}, + {-.103614E-01, 0.863125E+00}, {-.117886E-01, 0.862404E+00}, + {-.137827E-01, 0.861393E+00}, {-.163397E-01, 0.860091E+00}, + {-.194546E-01, 0.858497E+00}, {-.231213E-01, 0.856611E+00}, + {-.273326E-01, 0.854430E+00}, {-.320803E-01, 0.851952E+00}, + {-.373555E-01, 0.849176E+00}, {-.431480E-01, 0.846101E+00}, + {-.494469E-01, 0.842723E+00}, {-.562405E-01, 0.839041E+00}, + {-.635163E-01, 0.835053E+00}, {-.712611E-01, 0.830757E+00}, + {-.794610E-01, 0.826152E+00}, {-.881016E-01, 0.821235E+00}, + {-.971677E-01, 0.816006E+00}, {-.106644E+00, 0.810463E+00}, + {-.116514E+00, 0.804605E+00}, {-.126761E+00, 0.798431E+00}, + {-.137369E+00, 0.791942E+00}, {-.148321E+00, 0.785138E+00}, + {-.159598E+00, 0.778018E+00}, {-.171184E+00, 0.770584E+00}, + {-.183061E+00, 0.762837E+00}, {-.195210E+00, 0.754778E+00}, + {-.207614E+00, 0.746411E+00}, {-.220254E+00, 0.737738E+00}, + {-.233112E+00, 0.728762E+00}, {-.246170E+00, 0.719486E+00}, + {-.259409E+00, 0.709915E+00}, {-.272812E+00, 0.700054E+00}, + {-.286360E+00, 0.689908E+00}, {-.300035E+00, 0.679483E+00}, + {-.313820E+00, 0.668786E+00}, {-.327696E+00, 0.657822E+00}, + {-.341647E+00, 0.646601E+00}, {-.355654E+00, 0.635128E+00}, + {-.369700E+00, 0.623414E+00}, {-.383769E+00, 0.611467E+00}, + {-.397844E+00, 0.599297E+00}, {-.411909E+00, 0.586913E+00}, + {-.425946E+00, 0.574326E+00}, {-.439941E+00, 0.561547E+00}, + {-.453879E+00, 0.548588E+00}, {-.467743E+00, 0.535461E+00}, + {-.481519E+00, 0.522177E+00}, {-.495192E+00, 0.508750E+00}, + {-.508750E+00, 0.495192E+00}, {-.522177E+00, 0.481519E+00}, + {-.535461E+00, 0.467743E+00}, {-.548588E+00, 0.453879E+00}, + {-.561547E+00, 0.439941E+00}, {-.574326E+00, 0.425946E+00}, + {-.586913E+00, 0.411909E+00}, {-.599297E+00, 0.397844E+00}, + {-.611467E+00, 0.383769E+00}, {-.623414E+00, 0.369700E+00}, + {-.635128E+00, 0.355654E+00}, {-.646601E+00, 0.341647E+00}, + {-.657822E+00, 0.327696E+00}, {-.668786E+00, 0.313820E+00}, + {-.679483E+00, 0.300035E+00}, {-.689908E+00, 0.286360E+00}, + {-.700054E+00, 0.272812E+00}, {-.709915E+00, 0.259409E+00}, + {-.719486E+00, 0.246170E+00}, {-.728762E+00, 0.233112E+00}, + {-.737738E+00, 0.220254E+00}, {-.746411E+00, 0.207614E+00}, + {-.754778E+00, 0.195210E+00}, {-.762837E+00, 0.183061E+00}, + {-.770584E+00, 0.171184E+00}, {-.778018E+00, 0.159598E+00}, + {-.785138E+00, 0.148321E+00}, {-.791942E+00, 0.137369E+00}, + {-.798431E+00, 0.126761E+00}, {-.804605E+00, 0.116514E+00}, + {-.810463E+00, 0.106644E+00}, {-.816006E+00, 0.971677E-01}, + {-.821235E+00, 0.881016E-01}, {-.826152E+00, 0.794610E-01}, + {-.830757E+00, 0.712611E-01}, {-.835053E+00, 0.635163E-01}, + {-.839041E+00, 0.562405E-01}, {-.842723E+00, 0.494469E-01}, + {-.846101E+00, 0.431480E-01}, {-.849176E+00, 0.373555E-01}, + {-.851952E+00, 0.320803E-01}, {-.854430E+00, 0.273326E-01}, + {-.856611E+00, 0.231213E-01}, {-.858497E+00, 0.194546E-01}, + {-.860091E+00, 0.163397E-01}, {-.861393E+00, 0.137827E-01}, + {-.862404E+00, 0.117886E-01}, {-.863125E+00, 0.103614E-01}, + {-.863558E+00, 0.950396E-02}, {-.863702E+00, 0.921795E-02}, + }, + { + {0.652373E-02, 0.867647E+00}, {0.622980E-02, 0.867501E+00}, + {0.534860E-02, 0.867062E+00}, {0.388191E-02, 0.866331E+00}, + {0.183270E-02, 0.865305E+00}, {-.794918E-03, 0.863985E+00}, + {-.399569E-02, 0.862369E+00}, {-.776326E-02, 0.860456E+00}, + {-.120902E-01, 0.858243E+00}, {-.169680E-01, 0.855729E+00}, + {-.223871E-01, 0.852912E+00}, {-.283373E-01, 0.849789E+00}, + {-.348071E-01, 0.846359E+00}, {-.417845E-01, 0.842620E+00}, + {-.492563E-01, 0.838568E+00}, {-.572091E-01, 0.834203E+00}, + {-.656282E-01, 0.829522E+00}, {-.744988E-01, 0.824523E+00}, + {-.838053E-01, 0.819205E+00}, {-.935315E-01, 0.813566E+00}, + {-.103661E+00, 0.807605E+00}, {-.114176E+00, 0.801321E+00}, + {-.125061E+00, 0.794715E+00}, {-.136296E+00, 0.787785E+00}, + {-.147865E+00, 0.780532E+00}, {-.159749E+00, 0.772958E+00}, + {-.171929E+00, 0.765063E+00}, {-.184388E+00, 0.756848E+00}, + {-.197106E+00, 0.748317E+00}, {-.210065E+00, 0.739471E+00}, + {-.223246E+00, 0.730315E+00}, {-.236631E+00, 0.720851E+00}, + {-.250200E+00, 0.711085E+00}, {-.263935E+00, 0.701020E+00}, + {-.277817E+00, 0.690663E+00}, {-.291829E+00, 0.680019E+00}, + {-.305951E+00, 0.669094E+00}, {-.320165E+00, 0.657897E+00}, + {-.334454E+00, 0.646434E+00}, {-.348799E+00, 0.634713E+00}, + {-.363184E+00, 0.622743E+00}, {-.377590E+00, 0.610534E+00}, + {-.392001E+00, 0.598094E+00}, {-.406399E+00, 0.585435E+00}, + {-.420769E+00, 0.572567E+00}, {-.435094E+00, 0.559501E+00}, + {-.449358E+00, 0.546249E+00}, {-.463546E+00, 0.532824E+00}, + {-.477642E+00, 0.519237E+00}, {-.491632E+00, 0.505502E+00}, + {-.505502E+00, 0.491632E+00}, {-.519237E+00, 0.477642E+00}, + {-.532824E+00, 0.463546E+00}, {-.546249E+00, 0.449358E+00}, + {-.559501E+00, 0.435094E+00}, {-.572567E+00, 0.420769E+00}, + {-.585435E+00, 0.406399E+00}, {-.598094E+00, 0.392001E+00}, + {-.610534E+00, 0.377590E+00}, {-.622743E+00, 0.363184E+00}, + {-.634713E+00, 0.348799E+00}, {-.646434E+00, 0.334454E+00}, + {-.657897E+00, 0.320165E+00}, {-.669094E+00, 0.305951E+00}, + {-.680019E+00, 0.291829E+00}, {-.690663E+00, 0.277817E+00}, + {-.701020E+00, 0.263935E+00}, {-.711085E+00, 0.250200E+00}, + {-.720851E+00, 0.236631E+00}, {-.730315E+00, 0.223246E+00}, + {-.739471E+00, 0.210065E+00}, {-.748317E+00, 0.197106E+00}, + {-.756848E+00, 0.184388E+00}, {-.765063E+00, 0.171929E+00}, + {-.772958E+00, 0.159749E+00}, {-.780532E+00, 0.147865E+00}, + {-.787785E+00, 0.136296E+00}, {-.794715E+00, 0.125061E+00}, + {-.801321E+00, 0.114176E+00}, {-.807605E+00, 0.103661E+00}, + {-.813566E+00, 0.935315E-01}, {-.819205E+00, 0.838053E-01}, + {-.824523E+00, 0.744988E-01}, {-.829522E+00, 0.656282E-01}, + {-.834203E+00, 0.572091E-01}, {-.838568E+00, 0.492563E-01}, + {-.842620E+00, 0.417845E-01}, {-.846359E+00, 0.348071E-01}, + {-.849789E+00, 0.283373E-01}, {-.852912E+00, 0.223871E-01}, + {-.855729E+00, 0.169680E-01}, {-.858243E+00, 0.120902E-01}, + {-.860456E+00, 0.776326E-02}, {-.862369E+00, 0.399569E-02}, + {-.863985E+00, 0.794918E-03}, {-.865305E+00, -.183270E-02}, + {-.866331E+00, -.388191E-02}, {-.867062E+00, -.534860E-02}, + {-.867501E+00, -.622980E-02}, {-.867647E+00, -.652373E-02}, + }, + { + {0.227509E-01, 0.871603E+00}, {0.224488E-01, 0.871455E+00}, + {0.215432E-01, 0.871010E+00}, {0.200358E-01, 0.870269E+00}, + {0.179297E-01, 0.869229E+00}, {0.152293E-01, 0.867891E+00}, + {0.119401E-01, 0.866252E+00}, {0.806858E-02, 0.864311E+00}, + {0.362257E-02, 0.862066E+00}, {-.138913E-02, 0.859516E+00}, + {-.695665E-02, 0.856657E+00}, {-.130692E-01, 0.853487E+00}, + {-.197150E-01, 0.850005E+00}, {-.268814E-01, 0.846207E+00}, + {-.345549E-01, 0.842091E+00}, {-.427215E-01, 0.837655E+00}, + {-.513661E-01, 0.832897E+00}, {-.604732E-01, 0.827814E+00}, + {-.700268E-01, 0.822405E+00}, {-.800101E-01, 0.816669E+00}, + {-.904060E-01, 0.810603E+00}, {-.101197E+00, 0.804207E+00}, + {-.112365E+00, 0.797480E+00}, {-.123892E+00, 0.790423E+00}, + {-.135760E+00, 0.783035E+00}, {-.147949E+00, 0.775316E+00}, + {-.160442E+00, 0.767269E+00}, {-.173218E+00, 0.758895E+00}, + {-.186258E+00, 0.750196E+00}, {-.199544E+00, 0.741174E+00}, + {-.213057E+00, 0.731833E+00}, {-.226776E+00, 0.722177E+00}, + {-.240683E+00, 0.712210E+00}, {-.254759E+00, 0.701936E+00}, + {-.268984E+00, 0.691362E+00}, {-.283340E+00, 0.680493E+00}, + {-.297808E+00, 0.669336E+00}, {-.312369E+00, 0.657898E+00}, + {-.327005E+00, 0.646187E+00}, {-.341697E+00, 0.634212E+00}, + {-.356428E+00, 0.621980E+00}, {-.371179E+00, 0.609501E+00}, + {-.385934E+00, 0.596786E+00}, {-.400675E+00, 0.583845E+00}, + {-.415385E+00, 0.570688E+00}, {-.430048E+00, 0.557328E+00}, + {-.444647E+00, 0.543776E+00}, {-.459167E+00, 0.530045E+00}, + {-.473592E+00, 0.516147E+00}, {-.487906E+00, 0.502096E+00}, + {-.502096E+00, 0.487906E+00}, {-.516147E+00, 0.473592E+00}, + {-.530045E+00, 0.459167E+00}, {-.543776E+00, 0.444647E+00}, + {-.557328E+00, 0.430048E+00}, {-.570688E+00, 0.415385E+00}, + {-.583845E+00, 0.400675E+00}, {-.596786E+00, 0.385934E+00}, + {-.609501E+00, 0.371179E+00}, {-.621980E+00, 0.356428E+00}, + {-.634212E+00, 0.341697E+00}, {-.646187E+00, 0.327005E+00}, + {-.657898E+00, 0.312369E+00}, {-.669336E+00, 0.297808E+00}, + {-.680493E+00, 0.283340E+00}, {-.691362E+00, 0.268984E+00}, + {-.701936E+00, 0.254759E+00}, {-.712210E+00, 0.240683E+00}, + {-.722177E+00, 0.226776E+00}, {-.731833E+00, 0.213057E+00}, + {-.741174E+00, 0.199544E+00}, {-.750196E+00, 0.186258E+00}, + {-.758895E+00, 0.173218E+00}, {-.767269E+00, 0.160442E+00}, + {-.775316E+00, 0.147949E+00}, {-.783035E+00, 0.135760E+00}, + {-.790423E+00, 0.123892E+00}, {-.797480E+00, 0.112365E+00}, + {-.804207E+00, 0.101197E+00}, {-.810603E+00, 0.904060E-01}, + {-.816669E+00, 0.800101E-01}, {-.822405E+00, 0.700268E-01}, + {-.827814E+00, 0.604732E-01}, {-.832897E+00, 0.513661E-01}, + {-.837655E+00, 0.427215E-01}, {-.842091E+00, 0.345549E-01}, + {-.846207E+00, 0.268814E-01}, {-.850005E+00, 0.197150E-01}, + {-.853487E+00, 0.130692E-01}, {-.856657E+00, 0.695665E-02}, + {-.859516E+00, 0.138913E-02}, {-.862066E+00, -.362257E-02}, + {-.864311E+00, -.806858E-02}, {-.866252E+00, -.119401E-01}, + {-.867891E+00, -.152293E-01}, {-.869229E+00, -.179297E-01}, + {-.870269E+00, -.200358E-01}, {-.871010E+00, -.215432E-01}, + {-.871455E+00, -.224488E-01}, {-.871603E+00, -.227509E-01}, + }, + { + {0.394914E-01, 0.875572E+00}, {0.391809E-01, 0.875422E+00}, + {0.382500E-01, 0.874971E+00}, {0.367006E-01, 0.874219E+00}, + {0.345359E-01, 0.873165E+00}, {0.317605E-01, 0.871809E+00}, + {0.283800E-01, 0.870147E+00}, {0.244013E-01, 0.868179E+00}, + {0.198325E-01, 0.865902E+00}, {0.146828E-01, 0.863314E+00}, + {0.896243E-02, 0.860413E+00}, {0.268263E-02, 0.857196E+00}, + {-.414437E-02, 0.853660E+00}, {-.115055E-01, 0.849803E+00}, + {-.193867E-01, 0.845622E+00}, {-.277733E-01, 0.841114E+00}, + {-.366499E-01, 0.836277E+00}, {-.460004E-01, 0.831109E+00}, + {-.558081E-01, 0.825608E+00}, {-.660557E-01, 0.819772E+00}, + {-.767257E-01, 0.813599E+00}, {-.877998E-01, 0.807088E+00}, + {-.992596E-01, 0.800239E+00}, {-.111086E+00, 0.793050E+00}, + {-.123261E+00, 0.785523E+00}, {-.135764E+00, 0.777658E+00}, + {-.148576E+00, 0.769455E+00}, {-.161678E+00, 0.760917E+00}, + {-.175050E+00, 0.752045E+00}, {-.188672E+00, 0.742843E+00}, + {-.202524E+00, 0.733313E+00}, {-.216587E+00, 0.723459E+00}, + {-.230841E+00, 0.713285E+00}, {-.245266E+00, 0.702798E+00}, + {-.259843E+00, 0.692001E+00}, {-.274552E+00, 0.680901E+00}, + {-.289375E+00, 0.669506E+00}, {-.304291E+00, 0.657821E+00}, + {-.319283E+00, 0.645856E+00}, {-.334331E+00, 0.633618E+00}, + {-.349417E+00, 0.621118E+00}, {-.364523E+00, 0.608363E+00}, + {-.379630E+00, 0.595365E+00}, {-.394723E+00, 0.582134E+00}, + {-.409782E+00, 0.568681E+00}, {-.424791E+00, 0.555018E+00}, + {-.439734E+00, 0.541158E+00}, {-.454594E+00, 0.527113E+00}, + {-.469356E+00, 0.512896E+00}, {-.484003E+00, 0.498522E+00}, + {-.498522E+00, 0.484003E+00}, {-.512896E+00, 0.469356E+00}, + {-.527113E+00, 0.454594E+00}, {-.541158E+00, 0.439734E+00}, + {-.555018E+00, 0.424791E+00}, {-.568681E+00, 0.409782E+00}, + {-.582134E+00, 0.394723E+00}, {-.595365E+00, 0.379630E+00}, + {-.608363E+00, 0.364523E+00}, {-.621118E+00, 0.349417E+00}, + {-.633618E+00, 0.334331E+00}, {-.645856E+00, 0.319283E+00}, + {-.657821E+00, 0.304291E+00}, {-.669506E+00, 0.289375E+00}, + {-.680901E+00, 0.274552E+00}, {-.692001E+00, 0.259843E+00}, + {-.702798E+00, 0.245266E+00}, {-.713285E+00, 0.230841E+00}, + {-.723459E+00, 0.216587E+00}, {-.733313E+00, 0.202524E+00}, + {-.742843E+00, 0.188672E+00}, {-.752045E+00, 0.175050E+00}, + {-.760917E+00, 0.161678E+00}, {-.769455E+00, 0.148576E+00}, + {-.777658E+00, 0.135764E+00}, {-.785523E+00, 0.123261E+00}, + {-.793050E+00, 0.111086E+00}, {-.800239E+00, 0.992596E-01}, + {-.807088E+00, 0.877998E-01}, {-.813599E+00, 0.767257E-01}, + {-.819772E+00, 0.660557E-01}, {-.825608E+00, 0.558081E-01}, + {-.831109E+00, 0.460004E-01}, {-.836277E+00, 0.366499E-01}, + {-.841114E+00, 0.277733E-01}, {-.845622E+00, 0.193867E-01}, + {-.849803E+00, 0.115055E-01}, {-.853660E+00, 0.414437E-02}, + {-.857196E+00, -.268263E-02}, {-.860413E+00, -.896243E-02}, + {-.863314E+00, -.146828E-01}, {-.865902E+00, -.198325E-01}, + {-.868179E+00, -.244013E-01}, {-.870147E+00, -.283800E-01}, + {-.871809E+00, -.317605E-01}, {-.873165E+00, -.345359E-01}, + {-.874219E+00, -.367006E-01}, {-.874971E+00, -.382500E-01}, + {-.875422E+00, -.391809E-01}, {-.875572E+00, -.394914E-01}, + }, + { + {0.567752E-01, 0.879554E+00}, {0.564560E-01, 0.879402E+00}, + {0.554990E-01, 0.878945E+00}, {0.539062E-01, 0.878183E+00}, + {0.516811E-01, 0.877115E+00}, {0.488282E-01, 0.875740E+00}, + {0.453535E-01, 0.874055E+00}, {0.412642E-01, 0.872059E+00}, + {0.365688E-01, 0.869750E+00}, {0.312767E-01, 0.867125E+00}, + {0.253987E-01, 0.864181E+00}, {0.189465E-01, 0.860915E+00}, + {0.119327E-01, 0.857325E+00}, {0.437094E-02, 0.853408E+00}, + {-.372420E-02, 0.849161E+00}, {-.123375E-01, 0.844580E+00}, + {-.214530E-01, 0.839664E+00}, {-.310539E-01, 0.834409E+00}, + {-.411231E-01, 0.828813E+00}, {-.516427E-01, 0.822875E+00}, + {-.625945E-01, 0.816593E+00}, {-.739597E-01, 0.809964E+00}, + {-.857193E-01, 0.802989E+00}, {-.978539E-01, 0.795667E+00}, + {-.110344E+00, 0.787997E+00}, {-.123169E+00, 0.779981E+00}, + {-.136311E+00, 0.771619E+00}, {-.149747E+00, 0.762912E+00}, + {-.163459E+00, 0.753864E+00}, {-.177425E+00, 0.744475E+00}, + {-.191627E+00, 0.734751E+00}, {-.206042E+00, 0.724694E+00}, + {-.220652E+00, 0.714309E+00}, {-.235436E+00, 0.703600E+00}, + {-.250374E+00, 0.692575E+00}, {-.265445E+00, 0.681238E+00}, + {-.280632E+00, 0.669597E+00}, {-.295913E+00, 0.657659E+00}, + {-.311270E+00, 0.645433E+00}, {-.326683E+00, 0.632926E+00}, + {-.342133E+00, 0.620149E+00}, {-.357603E+00, 0.607110E+00}, + {-.373073E+00, 0.593821E+00}, {-.388526E+00, 0.580292E+00}, + {-.403943E+00, 0.566535E+00}, {-.419308E+00, 0.552562E+00}, + {-.434604E+00, 0.538385E+00}, {-.449814E+00, 0.524018E+00}, + {-.464921E+00, 0.509473E+00}, {-.479910E+00, 0.494766E+00}, + {-.494766E+00, 0.479910E+00}, {-.509473E+00, 0.464921E+00}, + {-.524018E+00, 0.449814E+00}, {-.538385E+00, 0.434604E+00}, + {-.552562E+00, 0.419308E+00}, {-.566535E+00, 0.403943E+00}, + {-.580292E+00, 0.388526E+00}, {-.593821E+00, 0.373073E+00}, + {-.607110E+00, 0.357603E+00}, {-.620149E+00, 0.342133E+00}, + {-.632926E+00, 0.326683E+00}, {-.645433E+00, 0.311270E+00}, + {-.657659E+00, 0.295913E+00}, {-.669597E+00, 0.280632E+00}, + {-.681238E+00, 0.265445E+00}, {-.692575E+00, 0.250374E+00}, + {-.703600E+00, 0.235436E+00}, {-.714309E+00, 0.220652E+00}, + {-.724694E+00, 0.206042E+00}, {-.734751E+00, 0.191627E+00}, + {-.744475E+00, 0.177425E+00}, {-.753864E+00, 0.163459E+00}, + {-.762912E+00, 0.149747E+00}, {-.771619E+00, 0.136311E+00}, + {-.779981E+00, 0.123169E+00}, {-.787997E+00, 0.110344E+00}, + {-.795667E+00, 0.978539E-01}, {-.802989E+00, 0.857193E-01}, + {-.809964E+00, 0.739597E-01}, {-.816593E+00, 0.625945E-01}, + {-.822875E+00, 0.516427E-01}, {-.828813E+00, 0.411231E-01}, + {-.834409E+00, 0.310539E-01}, {-.839664E+00, 0.214530E-01}, + {-.844580E+00, 0.123375E-01}, {-.849161E+00, 0.372420E-02}, + {-.853408E+00, -.437094E-02}, {-.857325E+00, -.119327E-01}, + {-.860915E+00, -.189465E-01}, {-.864181E+00, -.253987E-01}, + {-.867125E+00, -.312767E-01}, {-.869750E+00, -.365688E-01}, + {-.872059E+00, -.412642E-01}, {-.874055E+00, -.453535E-01}, + {-.875740E+00, -.488282E-01}, {-.877115E+00, -.516811E-01}, + {-.878183E+00, -.539062E-01}, {-.878945E+00, -.554990E-01}, + {-.879402E+00, -.564560E-01}, {-.879554E+00, -.567752E-01}, + }, + { + {0.746346E-01, 0.883551E+00}, {0.743064E-01, 0.883397E+00}, + {0.733225E-01, 0.882934E+00}, {0.716849E-01, 0.882162E+00}, + {0.693973E-01, 0.881080E+00}, {0.664643E-01, 0.879686E+00}, + {0.628924E-01, 0.877978E+00}, {0.586890E-01, 0.875954E+00}, + {0.538627E-01, 0.873612E+00}, {0.484237E-01, 0.870949E+00}, + {0.423831E-01, 0.867962E+00}, {0.357529E-01, 0.864647E+00}, + {0.285464E-01, 0.861003E+00}, {0.207778E-01, 0.857024E+00}, + {0.124622E-01, 0.852709E+00}, {0.361527E-02, 0.848054E+00}, + {-.574629E-02, 0.843056E+00}, {-.156052E-01, 0.837713E+00}, + {-.259437E-01, 0.832021E+00}, {-.367433E-01, 0.825979E+00}, + {-.479850E-01, 0.819584E+00}, {-.596497E-01, 0.812835E+00}, + {-.717177E-01, 0.805731E+00}, {-.841690E-01, 0.798271E+00}, + {-.969833E-01, 0.790455E+00}, {-.110140E+00, 0.782284E+00}, + {-.123619E+00, 0.773757E+00}, {-.137400E+00, 0.764878E+00}, + {-.151461E+00, 0.755647E+00}, {-.165781E+00, 0.746068E+00}, + {-.180341E+00, 0.736143E+00}, {-.195119E+00, 0.725877E+00}, + {-.210094E+00, 0.715274E+00}, {-.225246E+00, 0.704339E+00}, + {-.240555E+00, 0.693078E+00}, {-.255999E+00, 0.681497E+00}, + {-.271559E+00, 0.669604E+00}, {-.287215E+00, 0.657405E+00}, + {-.302946E+00, 0.644910E+00}, {-.318734E+00, 0.632126E+00}, + {-.334559E+00, 0.619064E+00}, {-.350402E+00, 0.605733E+00}, + {-.366244E+00, 0.592145E+00}, {-.382067E+00, 0.578310E+00}, + {-.397853E+00, 0.564240E+00}, {-.413583E+00, 0.549947E+00}, + {-.429241E+00, 0.535445E+00}, {-.444810E+00, 0.520746E+00}, + {-.460273E+00, 0.505865E+00}, {-.475614E+00, 0.490816E+00}, + {-.490816E+00, 0.475614E+00}, {-.505865E+00, 0.460273E+00}, + {-.520746E+00, 0.444810E+00}, {-.535445E+00, 0.429241E+00}, + {-.549947E+00, 0.413583E+00}, {-.564240E+00, 0.397853E+00}, + {-.578310E+00, 0.382067E+00}, {-.592145E+00, 0.366244E+00}, + {-.605733E+00, 0.350402E+00}, {-.619064E+00, 0.334559E+00}, + {-.632126E+00, 0.318734E+00}, {-.644910E+00, 0.302946E+00}, + {-.657405E+00, 0.287215E+00}, {-.669604E+00, 0.271559E+00}, + {-.681497E+00, 0.255999E+00}, {-.693078E+00, 0.240555E+00}, + {-.704339E+00, 0.225246E+00}, {-.715274E+00, 0.210094E+00}, + {-.725877E+00, 0.195119E+00}, {-.736143E+00, 0.180341E+00}, + {-.746068E+00, 0.165781E+00}, {-.755647E+00, 0.151461E+00}, + {-.764878E+00, 0.137400E+00}, {-.773757E+00, 0.123619E+00}, + {-.782284E+00, 0.110140E+00}, {-.790455E+00, 0.969833E-01}, + {-.798271E+00, 0.841690E-01}, {-.805731E+00, 0.717177E-01}, + {-.812835E+00, 0.596497E-01}, {-.819584E+00, 0.479850E-01}, + {-.825979E+00, 0.367433E-01}, {-.832021E+00, 0.259437E-01}, + {-.837713E+00, 0.156052E-01}, {-.843056E+00, 0.574629E-02}, + {-.848054E+00, -.361527E-02}, {-.852709E+00, -.124622E-01}, + {-.857024E+00, -.207778E-01}, {-.861003E+00, -.285464E-01}, + {-.864647E+00, -.357529E-01}, {-.867962E+00, -.423831E-01}, + {-.870949E+00, -.484237E-01}, {-.873612E+00, -.538627E-01}, + {-.875954E+00, -.586890E-01}, {-.877978E+00, -.628924E-01}, + {-.879686E+00, -.664643E-01}, {-.881080E+00, -.693973E-01}, + {-.882162E+00, -.716849E-01}, {-.882934E+00, -.733225E-01}, + {-.883397E+00, -.743064E-01}, {-.883551E+00, -.746346E-01}, + }, + { + {0.931052E-01, 0.887565E+00}, {0.927677E-01, 0.887409E+00}, + {0.917559E-01, 0.886940E+00}, {0.900720E-01, 0.886158E+00}, + {0.877197E-01, 0.885061E+00}, {0.847041E-01, 0.883648E+00}, + {0.810316E-01, 0.881917E+00}, {0.767102E-01, 0.879865E+00}, + {0.717488E-01, 0.877490E+00}, {0.661580E-01, 0.874788E+00}, + {0.599494E-01, 0.871757E+00}, {0.531355E-01, 0.868393E+00}, + {0.457302E-01, 0.864693E+00}, {0.377481E-01, 0.860652E+00}, + {0.292050E-01, 0.856268E+00}, {0.201171E-01, 0.851537E+00}, + {0.105019E-01, 0.846456E+00}, {0.377057E-03, 0.841022E+00}, + {-.102388E-01, 0.835231E+00}, {-.213268E-01, 0.829082E+00}, + {-.328672E-01, 0.822572E+00}, {-.448402E-01, 0.815699E+00}, + {-.572256E-01, 0.808463E+00}, {-.700026E-01, 0.800861E+00}, + {-.831506E-01, 0.792895E+00}, {-.966484E-01, 0.784564E+00}, + {-.110475E+00, 0.775869E+00}, {-.124609E+00, 0.766811E+00}, + {-.139029E+00, 0.757393E+00}, {-.153714E+00, 0.747617E+00}, + {-.168642E+00, 0.737486E+00}, {-.183792E+00, 0.727005E+00}, + {-.199143E+00, 0.716177E+00}, {-.214673E+00, 0.705008E+00}, + {-.230362E+00, 0.693504E+00}, {-.246189E+00, 0.681672E+00}, + {-.262133E+00, 0.669518E+00}, {-.278174E+00, 0.657051E+00}, + {-.294290E+00, 0.644278E+00}, {-.310463E+00, 0.631209E+00}, + {-.326673E+00, 0.617854E+00}, {-.342900E+00, 0.604223E+00}, + {-.359124E+00, 0.590326E+00}, {-.375328E+00, 0.576175E+00}, + {-.391492E+00, 0.561783E+00}, {-.407598E+00, 0.547162E+00}, + {-.423629E+00, 0.532325E+00}, {-.439567E+00, 0.517285E+00}, + {-.455395E+00, 0.502057E+00}, {-.471097E+00, 0.486656E+00}, + {-.486656E+00, 0.471097E+00}, {-.502057E+00, 0.455395E+00}, + {-.517285E+00, 0.439567E+00}, {-.532325E+00, 0.423629E+00}, + {-.547162E+00, 0.407598E+00}, {-.561783E+00, 0.391492E+00}, + {-.576175E+00, 0.375328E+00}, {-.590326E+00, 0.359124E+00}, + {-.604223E+00, 0.342900E+00}, {-.617854E+00, 0.326673E+00}, + {-.631209E+00, 0.310463E+00}, {-.644278E+00, 0.294290E+00}, + {-.657051E+00, 0.278174E+00}, {-.669518E+00, 0.262133E+00}, + {-.681672E+00, 0.246189E+00}, {-.693504E+00, 0.230362E+00}, + {-.705008E+00, 0.214673E+00}, {-.716177E+00, 0.199143E+00}, + {-.727005E+00, 0.183792E+00}, {-.737486E+00, 0.168642E+00}, + {-.747617E+00, 0.153714E+00}, {-.757393E+00, 0.139029E+00}, + {-.766811E+00, 0.124609E+00}, {-.775869E+00, 0.110475E+00}, + {-.784564E+00, 0.966484E-01}, {-.792895E+00, 0.831506E-01}, + {-.800861E+00, 0.700026E-01}, {-.808463E+00, 0.572256E-01}, + {-.815699E+00, 0.448402E-01}, {-.822572E+00, 0.328672E-01}, + {-.829082E+00, 0.213268E-01}, {-.835231E+00, 0.102388E-01}, + {-.841022E+00, -.377057E-03}, {-.846456E+00, -.105019E-01}, + {-.851537E+00, -.201171E-01}, {-.856268E+00, -.292050E-01}, + {-.860652E+00, -.377481E-01}, {-.864693E+00, -.457302E-01}, + {-.868393E+00, -.531355E-01}, {-.871757E+00, -.599494E-01}, + {-.874788E+00, -.661580E-01}, {-.877490E+00, -.717488E-01}, + {-.879865E+00, -.767102E-01}, {-.881917E+00, -.810316E-01}, + {-.883648E+00, -.847041E-01}, {-.885061E+00, -.877197E-01}, + {-.886158E+00, -.900720E-01}, {-.886940E+00, -.917559E-01}, + {-.887409E+00, -.927677E-01}, {-.887565E+00, -.931052E-01}, + }, + { + {0.112225E+00, 0.891596E+00}, {0.111878E+00, 0.891438E+00}, + {0.110838E+00, 0.890963E+00}, {0.109106E+00, 0.890171E+00}, + {0.106687E+00, 0.889059E+00}, {0.103585E+00, 0.887627E+00}, + {0.998088E-01, 0.885873E+00}, {0.953652E-01, 0.883792E+00}, + {0.902642E-01, 0.881384E+00}, {0.845165E-01, 0.878643E+00}, + {0.781341E-01, 0.875568E+00}, {0.711305E-01, 0.872153E+00}, + {0.635197E-01, 0.868396E+00}, {0.553172E-01, 0.864292E+00}, + {0.465393E-01, 0.859838E+00}, {0.372028E-01, 0.855030E+00}, + {0.273259E-01, 0.849863E+00}, {0.169268E-01, 0.844336E+00}, + {0.602490E-02, 0.838444E+00}, {-.536023E-02, 0.832185E+00}, + {-.172084E-01, 0.825556E+00}, {-.294990E-01, 0.818556E+00}, + {-.422112E-01, 0.811183E+00}, {-.553236E-01, 0.803436E+00}, + {-.688150E-01, 0.795315E+00}, {-.826635E-01, 0.786819E+00}, + {-.968476E-01, 0.777950E+00}, {-.111345E+00, 0.768709E+00}, + {-.126135E+00, 0.759097E+00}, {-.141194E+00, 0.749118E+00}, + {-.156501E+00, 0.738774E+00}, {-.172033E+00, 0.728070E+00}, + {-.187770E+00, 0.717011E+00}, {-.203690E+00, 0.705601E+00}, + {-.219771E+00, 0.693847E+00}, {-.235991E+00, 0.681755E+00}, + {-.252329E+00, 0.669332E+00}, {-.268766E+00, 0.656587E+00}, + {-.285278E+00, 0.643529E+00}, {-.301848E+00, 0.630166E+00}, + {-.318453E+00, 0.616508E+00}, {-.335074E+00, 0.602566E+00}, + {-.351692E+00, 0.588352E+00}, {-.368287E+00, 0.573876E+00}, + {-.384840E+00, 0.559152E+00}, {-.401333E+00, 0.544191E+00}, + {-.417747E+00, 0.529009E+00}, {-.434065E+00, 0.513618E+00}, + {-.450269E+00, 0.498033E+00}, {-.466343E+00, 0.482270E+00}, + {-.482270E+00, 0.466343E+00}, {-.498033E+00, 0.450269E+00}, + {-.513618E+00, 0.434065E+00}, {-.529009E+00, 0.417747E+00}, + {-.544191E+00, 0.401333E+00}, {-.559152E+00, 0.384840E+00}, + {-.573876E+00, 0.368287E+00}, {-.588352E+00, 0.351692E+00}, + {-.602566E+00, 0.335074E+00}, {-.616508E+00, 0.318453E+00}, + {-.630166E+00, 0.301848E+00}, {-.643529E+00, 0.285278E+00}, + {-.656587E+00, 0.268766E+00}, {-.669332E+00, 0.252329E+00}, + {-.681755E+00, 0.235991E+00}, {-.693847E+00, 0.219771E+00}, + {-.705601E+00, 0.203690E+00}, {-.717011E+00, 0.187770E+00}, + {-.728070E+00, 0.172033E+00}, {-.738774E+00, 0.156501E+00}, + {-.749118E+00, 0.141194E+00}, {-.759097E+00, 0.126135E+00}, + {-.768709E+00, 0.111345E+00}, {-.777950E+00, 0.968476E-01}, + {-.786819E+00, 0.826635E-01}, {-.795315E+00, 0.688150E-01}, + {-.803436E+00, 0.553236E-01}, {-.811183E+00, 0.422112E-01}, + {-.818556E+00, 0.294990E-01}, {-.825556E+00, 0.172084E-01}, + {-.832185E+00, 0.536023E-02}, {-.838444E+00, -.602490E-02}, + {-.844336E+00, -.169268E-01}, {-.849863E+00, -.273259E-01}, + {-.855030E+00, -.372028E-01}, {-.859838E+00, -.465393E-01}, + {-.864292E+00, -.553172E-01}, {-.868396E+00, -.635197E-01}, + {-.872153E+00, -.711305E-01}, {-.875568E+00, -.781341E-01}, + {-.878643E+00, -.845165E-01}, {-.881384E+00, -.902642E-01}, + {-.883792E+00, -.953652E-01}, {-.885873E+00, -.998088E-01}, + {-.887627E+00, -.103585E+00}, {-.889059E+00, -.106687E+00}, + {-.890171E+00, -.109106E+00}, {-.890963E+00, -.110838E+00}, + {-.891438E+00, -.111878E+00}, {-.891596E+00, -.112225E+00}, + }, + { + {0.132037E+00, 0.895647E+00}, {0.131680E+00, 0.895487E+00}, + {0.130609E+00, 0.895006E+00}, {0.128828E+00, 0.894203E+00}, + {0.126339E+00, 0.893077E+00}, {0.123149E+00, 0.891626E+00}, + {0.119265E+00, 0.889848E+00}, {0.114695E+00, 0.887739E+00}, + {0.109449E+00, 0.885296E+00}, {0.103539E+00, 0.882516E+00}, + {0.969773E-01, 0.879396E+00}, {0.897774E-01, 0.875930E+00}, + {0.819543E-01, 0.872115E+00}, {0.735240E-01, 0.867946E+00}, + {0.645035E-01, 0.863420E+00}, {0.549104E-01, 0.858532E+00}, + {0.447632E-01, 0.853278E+00}, {0.340812E-01, 0.847655E+00}, + {0.228842E-01, 0.841659E+00}, {0.111926E-01, 0.835287E+00}, + {-.972810E-03, 0.828536E+00}, {-.135907E-01, 0.821405E+00}, + {-.266396E-01, 0.813891E+00}, {-.400976E-01, 0.805994E+00}, + {-.539426E-01, 0.797713E+00}, {-.681523E-01, 0.789047E+00}, + {-.827044E-01, 0.779998E+00}, {-.975764E-01, 0.770566E+00}, + {-.112746E+00, 0.760755E+00}, {-.128190E+00, 0.750566E+00}, + {-.143887E+00, 0.740002E+00}, {-.159813E+00, 0.729068E+00}, + {-.175948E+00, 0.717769E+00}, {-.192268E+00, 0.706110E+00}, + {-.208751E+00, 0.694096E+00}, {-.225376E+00, 0.681736E+00}, + {-.242120E+00, 0.669036E+00}, {-.258963E+00, 0.656004E+00}, + {-.275884E+00, 0.642650E+00}, {-.292860E+00, 0.628983E+00}, + {-.309873E+00, 0.615013E+00}, {-.326900E+00, 0.600751E+00}, + {-.343922E+00, 0.586208E+00}, {-.360920E+00, 0.571397E+00}, + {-.377874E+00, 0.556330E+00}, {-.394765E+00, 0.541020E+00}, + {-.411574E+00, 0.525481E+00}, {-.428284E+00, 0.509728E+00}, + {-.444876E+00, 0.493775E+00}, {-.461333E+00, 0.477638E+00}, + {-.477638E+00, 0.461333E+00}, {-.493775E+00, 0.444876E+00}, + {-.509728E+00, 0.428284E+00}, {-.525481E+00, 0.411574E+00}, + {-.541020E+00, 0.394765E+00}, {-.556330E+00, 0.377874E+00}, + {-.571397E+00, 0.360920E+00}, {-.586208E+00, 0.343922E+00}, + {-.600751E+00, 0.326900E+00}, {-.615013E+00, 0.309873E+00}, + {-.628983E+00, 0.292860E+00}, {-.642650E+00, 0.275884E+00}, + {-.656004E+00, 0.258963E+00}, {-.669036E+00, 0.242120E+00}, + {-.681736E+00, 0.225376E+00}, {-.694096E+00, 0.208751E+00}, + {-.706110E+00, 0.192268E+00}, {-.717769E+00, 0.175948E+00}, + {-.729068E+00, 0.159813E+00}, {-.740002E+00, 0.143887E+00}, + {-.750566E+00, 0.128190E+00}, {-.760755E+00, 0.112746E+00}, + {-.770566E+00, 0.975764E-01}, {-.779998E+00, 0.827044E-01}, + {-.789047E+00, 0.681523E-01}, {-.797713E+00, 0.539426E-01}, + {-.805994E+00, 0.400976E-01}, {-.813891E+00, 0.266396E-01}, + {-.821405E+00, 0.135907E-01}, {-.828536E+00, 0.972810E-03}, + {-.835287E+00, -.111926E-01}, {-.841659E+00, -.228842E-01}, + {-.847655E+00, -.340812E-01}, {-.853278E+00, -.447632E-01}, + {-.858532E+00, -.549104E-01}, {-.863420E+00, -.645035E-01}, + {-.867946E+00, -.735240E-01}, {-.872115E+00, -.819543E-01}, + {-.875930E+00, -.897774E-01}, {-.879396E+00, -.969773E-01}, + {-.882516E+00, -.103539E+00}, {-.885296E+00, -.109449E+00}, + {-.887739E+00, -.114695E+00}, {-.889848E+00, -.119265E+00}, + {-.891626E+00, -.123149E+00}, {-.893077E+00, -.126339E+00}, + {-.894203E+00, -.128828E+00}, {-.895006E+00, -.130609E+00}, + {-.895487E+00, -.131680E+00}, {-.895647E+00, -.132037E+00}, + }, + { + {0.152585E+00, 0.899719E+00}, {0.152218E+00, 0.899557E+00}, + {0.151116E+00, 0.899070E+00}, {0.149283E+00, 0.898256E+00}, + {0.146723E+00, 0.897116E+00}, {0.143441E+00, 0.895645E+00}, + {0.139445E+00, 0.893843E+00}, {0.134744E+00, 0.891705E+00}, + {0.129349E+00, 0.889228E+00}, {0.123271E+00, 0.886408E+00}, + {0.116523E+00, 0.883241E+00}, {0.109120E+00, 0.879723E+00}, + {0.101077E+00, 0.875849E+00}, {0.924110E-01, 0.871614E+00}, + {0.831396E-01, 0.867014E+00}, {0.732812E-01, 0.862044E+00}, + {0.628550E-01, 0.856701E+00}, {0.518808E-01, 0.850979E+00}, + {0.403792E-01, 0.844876E+00}, {0.283714E-01, 0.838387E+00}, + {0.158787E-01, 0.831511E+00}, {0.292324E-02, 0.824244E+00}, + {-.104728E-01, 0.816585E+00}, {-.242869E-01, 0.808532E+00}, + {-.384963E-01, 0.800085E+00}, {-.530781E-01, 0.791243E+00}, + {-.680092E-01, 0.782008E+00}, {-.832667E-01, 0.772380E+00}, + {-.988275E-01, 0.762361E+00}, {-.114668E+00, 0.751954E+00}, + {-.130766E+00, 0.741163E+00}, {-.147098E+00, 0.729991E+00}, + {-.163641E+00, 0.718443E+00}, {-.180373E+00, 0.706526E+00}, + {-.197271E+00, 0.694244E+00}, {-.214312E+00, 0.681606E+00}, + {-.231475E+00, 0.668618E+00}, {-.248737E+00, 0.655290E+00}, + {-.266077E+00, 0.641630E+00}, {-.283473E+00, 0.627648E+00}, + {-.300904E+00, 0.613355E+00}, {-.318350E+00, 0.598762E+00}, + {-.335789E+00, 0.583880E+00}, {-.353202E+00, 0.568721E+00}, + {-.370569E+00, 0.553300E+00}, {-.387870E+00, 0.537629E+00}, + {-.405087E+00, 0.521722E+00}, {-.422200E+00, 0.505595E+00}, + {-.439192E+00, 0.489262E+00}, {-.456044E+00, 0.472740E+00}, + {-.472740E+00, 0.456044E+00}, {-.489262E+00, 0.439192E+00}, + {-.505595E+00, 0.422200E+00}, {-.521722E+00, 0.405087E+00}, + {-.537629E+00, 0.387870E+00}, {-.553300E+00, 0.370569E+00}, + {-.568721E+00, 0.353202E+00}, {-.583880E+00, 0.335789E+00}, + {-.598762E+00, 0.318350E+00}, {-.613355E+00, 0.300904E+00}, + {-.627648E+00, 0.283473E+00}, {-.641630E+00, 0.266077E+00}, + {-.655290E+00, 0.248737E+00}, {-.668618E+00, 0.231475E+00}, + {-.681606E+00, 0.214312E+00}, {-.694244E+00, 0.197271E+00}, + {-.706526E+00, 0.180373E+00}, {-.718443E+00, 0.163641E+00}, + {-.729991E+00, 0.147098E+00}, {-.741163E+00, 0.130766E+00}, + {-.751954E+00, 0.114668E+00}, {-.762361E+00, 0.988275E-01}, + {-.772380E+00, 0.832667E-01}, {-.782008E+00, 0.680092E-01}, + {-.791243E+00, 0.530781E-01}, {-.800085E+00, 0.384963E-01}, + {-.808532E+00, 0.242869E-01}, {-.816585E+00, 0.104728E-01}, + {-.824244E+00, -.292324E-02}, {-.831511E+00, -.158787E-01}, + {-.838387E+00, -.283714E-01}, {-.844876E+00, -.403792E-01}, + {-.850979E+00, -.518808E-01}, {-.856701E+00, -.628550E-01}, + {-.862044E+00, -.732812E-01}, {-.867014E+00, -.831396E-01}, + {-.871614E+00, -.924110E-01}, {-.875849E+00, -.101077E+00}, + {-.879723E+00, -.109120E+00}, {-.883241E+00, -.116523E+00}, + {-.886408E+00, -.123271E+00}, {-.889228E+00, -.129349E+00}, + {-.891705E+00, -.134744E+00}, {-.893843E+00, -.139445E+00}, + {-.895645E+00, -.143441E+00}, {-.897116E+00, -.146723E+00}, + {-.898256E+00, -.149283E+00}, {-.899070E+00, -.151116E+00}, + {-.899557E+00, -.152218E+00}, {-.899719E+00, -.152585E+00}, + }, + { + {0.173921E+00, 0.903814E+00}, {0.173543E+00, 0.903650E+00}, + {0.172409E+00, 0.903156E+00}, {0.170522E+00, 0.902332E+00}, + {0.167888E+00, 0.901177E+00}, {0.164510E+00, 0.899687E+00}, + {0.160399E+00, 0.897860E+00}, {0.155562E+00, 0.895693E+00}, + {0.150011E+00, 0.893181E+00}, {0.143758E+00, 0.890320E+00}, + {0.136818E+00, 0.887107E+00}, {0.129204E+00, 0.883535E+00}, + {0.120934E+00, 0.879600E+00}, {0.112025E+00, 0.875297E+00}, + {0.102494E+00, 0.870621E+00}, {0.923611E-01, 0.865568E+00}, + {0.816463E-01, 0.860131E+00}, {0.703703E-01, 0.854308E+00}, + {0.585541E-01, 0.848094E+00}, {0.462197E-01, 0.841485E+00}, + {0.333893E-01, 0.834478E+00}, {0.200856E-01, 0.827071E+00}, + {0.633144E-02, 0.819261E+00}, {-.784986E-02, 0.811047E+00}, + {-.224349E-01, 0.802428E+00}, {-.374002E-01, 0.793404E+00}, + {-.527220E-01, 0.783975E+00}, {-.683767E-01, 0.774143E+00}, + {-.843406E-01, 0.763909E+00}, {-.100590E+00, 0.753277E+00}, + {-.117101E+00, 0.742249E+00}, {-.133851E+00, 0.730830E+00}, + {-.150815E+00, 0.719024E+00}, {-.167971E+00, 0.706839E+00}, + {-.185296E+00, 0.694279E+00}, {-.202766E+00, 0.681352E+00}, + {-.220359E+00, 0.668066E+00}, {-.238053E+00, 0.654431E+00}, + {-.255825E+00, 0.640454E+00}, {-.273654E+00, 0.626146E+00}, + {-.291517E+00, 0.611518E+00}, {-.309393E+00, 0.596582E+00}, + {-.327263E+00, 0.581348E+00}, {-.345104E+00, 0.565831E+00}, + {-.362896E+00, 0.550043E+00}, {-.380621E+00, 0.533998E+00}, + {-.398257E+00, 0.517711E+00}, {-.415787E+00, 0.501197E+00}, + {-.433191E+00, 0.484472E+00}, {-.450452E+00, 0.467551E+00}, + {-.467551E+00, 0.450452E+00}, {-.484472E+00, 0.433191E+00}, + {-.501197E+00, 0.415787E+00}, {-.517711E+00, 0.398257E+00}, + {-.533998E+00, 0.380621E+00}, {-.550043E+00, 0.362896E+00}, + {-.565831E+00, 0.345104E+00}, {-.581348E+00, 0.327263E+00}, + {-.596582E+00, 0.309393E+00}, {-.611518E+00, 0.291517E+00}, + {-.626146E+00, 0.273654E+00}, {-.640454E+00, 0.255825E+00}, + {-.654431E+00, 0.238053E+00}, {-.668066E+00, 0.220359E+00}, + {-.681352E+00, 0.202766E+00}, {-.694279E+00, 0.185296E+00}, + {-.706839E+00, 0.167971E+00}, {-.719024E+00, 0.150815E+00}, + {-.730830E+00, 0.133851E+00}, {-.742249E+00, 0.117101E+00}, + {-.753277E+00, 0.100590E+00}, {-.763909E+00, 0.843406E-01}, + {-.774143E+00, 0.683767E-01}, {-.783975E+00, 0.527220E-01}, + {-.793404E+00, 0.374002E-01}, {-.802428E+00, 0.224349E-01}, + {-.811047E+00, 0.784986E-02}, {-.819261E+00, -.633144E-02}, + {-.827071E+00, -.200856E-01}, {-.834478E+00, -.333893E-01}, + {-.841485E+00, -.462197E-01}, {-.848094E+00, -.585541E-01}, + {-.854308E+00, -.703703E-01}, {-.860131E+00, -.816463E-01}, + {-.865568E+00, -.923611E-01}, {-.870621E+00, -.102494E+00}, + {-.875297E+00, -.112025E+00}, {-.879600E+00, -.120934E+00}, + {-.883535E+00, -.129204E+00}, {-.887107E+00, -.136818E+00}, + {-.890320E+00, -.143758E+00}, {-.893181E+00, -.150011E+00}, + {-.895693E+00, -.155562E+00}, {-.897860E+00, -.160399E+00}, + {-.899687E+00, -.164510E+00}, {-.901177E+00, -.167888E+00}, + {-.902332E+00, -.170522E+00}, {-.903156E+00, -.172409E+00}, + {-.903650E+00, -.173543E+00}, {-.903814E+00, -.173921E+00}, + }, + { + {0.196099E+00, 0.907933E+00}, {0.195709E+00, 0.907767E+00}, + {0.194542E+00, 0.907267E+00}, {0.192600E+00, 0.906432E+00}, + {0.189888E+00, 0.905262E+00}, {0.186411E+00, 0.903752E+00}, + {0.182179E+00, 0.901901E+00}, {0.177202E+00, 0.899704E+00}, + {0.171489E+00, 0.897156E+00}, {0.165056E+00, 0.894255E+00}, + {0.157916E+00, 0.890993E+00}, {0.150084E+00, 0.887366E+00}, + {0.141578E+00, 0.883369E+00}, {0.132416E+00, 0.878997E+00}, + {0.122617E+00, 0.874243E+00}, {0.112200E+00, 0.869102E+00}, + {0.101187E+00, 0.863570E+00}, {0.895986E-01, 0.857642E+00}, + {0.774573E-01, 0.851313E+00}, {0.647856E-01, 0.844579E+00}, + {0.516064E-01, 0.837437E+00}, {0.379431E-01, 0.829884E+00}, + {0.238194E-01, 0.821918E+00}, {0.925929E-02, 0.813536E+00}, + {-.571325E-02, 0.804738E+00}, {-.210740E-01, 0.795524E+00}, + {-.367986E-01, 0.785894E+00}, {-.528627E-01, 0.775850E+00}, + {-.692421E-01, 0.765392E+00}, {-.859125E-01, 0.754525E+00}, + {-.102850E+00, 0.743251E+00}, {-.120029E+00, 0.731575E+00}, + {-.137428E+00, 0.719501E+00}, {-.155021E+00, 0.707037E+00}, + {-.172786E+00, 0.694188E+00}, {-.190698E+00, 0.680962E+00}, + {-.208735E+00, 0.667366E+00}, {-.226874E+00, 0.653411E+00}, + {-.245091E+00, 0.639105E+00}, {-.263366E+00, 0.624459E+00}, + {-.281674E+00, 0.609484E+00}, {-.299996E+00, 0.594191E+00}, + {-.318308E+00, 0.578594E+00}, {-.336591E+00, 0.562704E+00}, + {-.354823E+00, 0.546537E+00}, {-.372985E+00, 0.530105E+00}, + {-.391055E+00, 0.513424E+00}, {-.409015E+00, 0.496510E+00}, + {-.426846E+00, 0.479378E+00}, {-.444529E+00, 0.462045E+00}, + {-.462045E+00, 0.444529E+00}, {-.479378E+00, 0.426846E+00}, + {-.496510E+00, 0.409015E+00}, {-.513424E+00, 0.391055E+00}, + {-.530105E+00, 0.372985E+00}, {-.546537E+00, 0.354823E+00}, + {-.562704E+00, 0.336591E+00}, {-.578594E+00, 0.318308E+00}, + {-.594191E+00, 0.299996E+00}, {-.609484E+00, 0.281674E+00}, + {-.624459E+00, 0.263366E+00}, {-.639105E+00, 0.245091E+00}, + {-.653411E+00, 0.226874E+00}, {-.667366E+00, 0.208735E+00}, + {-.680962E+00, 0.190698E+00}, {-.694188E+00, 0.172786E+00}, + {-.707037E+00, 0.155021E+00}, {-.719501E+00, 0.137428E+00}, + {-.731575E+00, 0.120029E+00}, {-.743251E+00, 0.102850E+00}, + {-.754525E+00, 0.859125E-01}, {-.765392E+00, 0.692421E-01}, + {-.775850E+00, 0.528627E-01}, {-.785894E+00, 0.367986E-01}, + {-.795524E+00, 0.210740E-01}, {-.804738E+00, 0.571325E-02}, + {-.813536E+00, -.925929E-02}, {-.821918E+00, -.238194E-01}, + {-.829884E+00, -.379431E-01}, {-.837437E+00, -.516064E-01}, + {-.844579E+00, -.647856E-01}, {-.851313E+00, -.774573E-01}, + {-.857642E+00, -.895986E-01}, {-.863570E+00, -.101187E+00}, + {-.869102E+00, -.112200E+00}, {-.874243E+00, -.122617E+00}, + {-.878997E+00, -.132416E+00}, {-.883369E+00, -.141578E+00}, + {-.887366E+00, -.150084E+00}, {-.890993E+00, -.157916E+00}, + {-.894255E+00, -.165056E+00}, {-.897156E+00, -.171489E+00}, + {-.899704E+00, -.177202E+00}, {-.901901E+00, -.182179E+00}, + {-.903752E+00, -.186411E+00}, {-.905262E+00, -.189888E+00}, + {-.906432E+00, -.192600E+00}, {-.907267E+00, -.194542E+00}, + {-.907767E+00, -.195709E+00}, {-.907933E+00, -.196099E+00}, + }, + { + {0.219179E+00, 0.912079E+00}, {0.218778E+00, 0.911910E+00}, + {0.217576E+00, 0.911404E+00}, {0.215576E+00, 0.910559E+00}, + {0.212783E+00, 0.909373E+00}, {0.209204E+00, 0.907844E+00}, + {0.204847E+00, 0.905968E+00}, {0.199722E+00, 0.903740E+00}, + {0.193843E+00, 0.901157E+00}, {0.187222E+00, 0.898212E+00}, + {0.179874E+00, 0.894902E+00}, {0.171816E+00, 0.891219E+00}, + {0.163065E+00, 0.887158E+00}, {0.153642E+00, 0.882713E+00}, + {0.143564E+00, 0.877879E+00}, {0.132853E+00, 0.872649E+00}, + {0.121531E+00, 0.867017E+00}, {0.109620E+00, 0.860980E+00}, + {0.971428E-01, 0.854532E+00}, {0.841224E-01, 0.847668E+00}, + {0.705828E-01, 0.840385E+00}, {0.565482E-01, 0.832680E+00}, + {0.420429E-01, 0.824551E+00}, {0.270917E-01, 0.815994E+00}, + {0.117193E-01, 0.807010E+00}, {-.404941E-02, 0.797598E+00}, + {-.201895E-01, 0.787758E+00}, {-.366759E-01, 0.777492E+00}, + {-.534838E-01, 0.766801E+00}, {-.705883E-01, 0.755689E+00}, + {-.879645E-01, 0.744158E+00}, {-.105588E+00, 0.732214E+00}, + {-.123434E+00, 0.719862E+00}, {-.141478E+00, 0.707107E+00}, + {-.159696E+00, 0.693956E+00}, {-.178064E+00, 0.680418E+00}, + {-.196559E+00, 0.666501E+00}, {-.215157E+00, 0.652213E+00}, + {-.233834E+00, 0.637565E+00}, {-.252569E+00, 0.622567E+00}, + {-.271337E+00, 0.607231E+00}, {-.290118E+00, 0.591568E+00}, + {-.308888E+00, 0.575592E+00}, {-.327627E+00, 0.559317E+00}, + {-.346314E+00, 0.542755E+00}, {-.364927E+00, 0.525922E+00}, + {-.383446E+00, 0.508832E+00}, {-.401851E+00, 0.491503E+00}, + {-.420123E+00, 0.473950E+00}, {-.438242E+00, 0.456191E+00}, + {-.456191E+00, 0.438242E+00}, {-.473950E+00, 0.420123E+00}, + {-.491503E+00, 0.401851E+00}, {-.508832E+00, 0.383446E+00}, + {-.525922E+00, 0.364927E+00}, {-.542755E+00, 0.346314E+00}, + {-.559317E+00, 0.327627E+00}, {-.575592E+00, 0.308888E+00}, + {-.591568E+00, 0.290118E+00}, {-.607231E+00, 0.271337E+00}, + {-.622567E+00, 0.252569E+00}, {-.637565E+00, 0.233834E+00}, + {-.652213E+00, 0.215157E+00}, {-.666501E+00, 0.196559E+00}, + {-.680418E+00, 0.178064E+00}, {-.693956E+00, 0.159696E+00}, + {-.707107E+00, 0.141478E+00}, {-.719862E+00, 0.123434E+00}, + {-.732214E+00, 0.105588E+00}, {-.744158E+00, 0.879645E-01}, + {-.755689E+00, 0.705883E-01}, {-.766801E+00, 0.534838E-01}, + {-.777492E+00, 0.366759E-01}, {-.787758E+00, 0.201895E-01}, + {-.797598E+00, 0.404941E-02}, {-.807010E+00, -.117193E-01}, + {-.815994E+00, -.270917E-01}, {-.824551E+00, -.420429E-01}, + {-.832680E+00, -.565482E-01}, {-.840385E+00, -.705828E-01}, + {-.847668E+00, -.841224E-01}, {-.854532E+00, -.971428E-01}, + {-.860980E+00, -.109620E+00}, {-.867017E+00, -.121531E+00}, + {-.872649E+00, -.132853E+00}, {-.877879E+00, -.143564E+00}, + {-.882713E+00, -.153642E+00}, {-.887158E+00, -.163065E+00}, + {-.891219E+00, -.171816E+00}, {-.894902E+00, -.179874E+00}, + {-.898212E+00, -.187222E+00}, {-.901157E+00, -.193843E+00}, + {-.903740E+00, -.199722E+00}, {-.905968E+00, -.204847E+00}, + {-.907844E+00, -.209204E+00}, {-.909373E+00, -.212783E+00}, + {-.910559E+00, -.215576E+00}, {-.911404E+00, -.217576E+00}, + {-.911910E+00, -.218778E+00}, {-.912079E+00, -.219179E+00}, + }, + { + {0.243229E+00, 0.916253E+00}, {0.242816E+00, 0.916082E+00}, + {0.241578E+00, 0.915570E+00}, {0.239518E+00, 0.914714E+00}, + {0.236641E+00, 0.913513E+00}, {0.232954E+00, 0.911963E+00}, + {0.228467E+00, 0.910062E+00}, {0.223190E+00, 0.907803E+00}, + {0.217136E+00, 0.905183E+00}, {0.210320E+00, 0.902195E+00}, + {0.202756E+00, 0.898834E+00}, {0.194463E+00, 0.895093E+00}, + {0.185459E+00, 0.890967E+00}, {0.175764E+00, 0.886447E+00}, + {0.165398E+00, 0.881529E+00}, {0.154383E+00, 0.876206E+00}, + {0.142741E+00, 0.870472E+00}, {0.130495E+00, 0.864321E+00}, + {0.117670E+00, 0.857748E+00}, {0.104289E+00, 0.850749E+00}, + {0.903768E-01, 0.843320E+00}, {0.759584E-01, 0.835456E+00}, + {0.610591E-01, 0.827156E+00}, {0.457040E-01, 0.818417E+00}, + {0.299188E-01, 0.809238E+00}, {0.137290E-01, 0.799618E+00}, + {-.283982E-02, 0.789559E+00}, {-.197620E-01, 0.779061E+00}, + {-.370119E-01, 0.768126E+00}, {-.545642E-01, 0.756758E+00}, + {-.723935E-01, 0.744959E+00}, {-.904744E-01, 0.732735E+00}, + {-.108782E+00, 0.720090E+00}, {-.127291E+00, 0.707032E+00}, + {-.145978E+00, 0.693567E+00}, {-.164816E+00, 0.679704E+00}, + {-.183783E+00, 0.665450E+00}, {-.202855E+00, 0.650815E+00}, + {-.222007E+00, 0.635810E+00}, {-.241216E+00, 0.620446E+00}, + {-.260460E+00, 0.604734E+00}, {-.279715E+00, 0.588687E+00}, + {-.298959E+00, 0.572317E+00}, {-.318170E+00, 0.555639E+00}, + {-.337326E+00, 0.538668E+00}, {-.356406E+00, 0.521418E+00}, + {-.375390E+00, 0.503904E+00}, {-.394256E+00, 0.486145E+00}, + {-.412985E+00, 0.468155E+00}, {-.431557E+00, 0.449953E+00}, + {-.449953E+00, 0.431557E+00}, {-.468155E+00, 0.412985E+00}, + {-.486145E+00, 0.394256E+00}, {-.503904E+00, 0.375390E+00}, + {-.521418E+00, 0.356406E+00}, {-.538668E+00, 0.337326E+00}, + {-.555639E+00, 0.318170E+00}, {-.572317E+00, 0.298959E+00}, + {-.588687E+00, 0.279715E+00}, {-.604734E+00, 0.260460E+00}, + {-.620446E+00, 0.241216E+00}, {-.635810E+00, 0.222007E+00}, + {-.650815E+00, 0.202855E+00}, {-.665450E+00, 0.183783E+00}, + {-.679704E+00, 0.164816E+00}, {-.693567E+00, 0.145978E+00}, + {-.707032E+00, 0.127291E+00}, {-.720090E+00, 0.108782E+00}, + {-.732735E+00, 0.904744E-01}, {-.744959E+00, 0.723935E-01}, + {-.756758E+00, 0.545642E-01}, {-.768126E+00, 0.370119E-01}, + {-.779061E+00, 0.197620E-01}, {-.789559E+00, 0.283982E-02}, + {-.799618E+00, -.137290E-01}, {-.809238E+00, -.299188E-01}, + {-.818417E+00, -.457040E-01}, {-.827156E+00, -.610591E-01}, + {-.835456E+00, -.759584E-01}, {-.843320E+00, -.903768E-01}, + {-.850749E+00, -.104289E+00}, {-.857748E+00, -.117670E+00}, + {-.864321E+00, -.130495E+00}, {-.870472E+00, -.142741E+00}, + {-.876206E+00, -.154383E+00}, {-.881529E+00, -.165398E+00}, + {-.886447E+00, -.175764E+00}, {-.890967E+00, -.185459E+00}, + {-.895093E+00, -.194463E+00}, {-.898834E+00, -.202756E+00}, + {-.902195E+00, -.210320E+00}, {-.905183E+00, -.217136E+00}, + {-.907803E+00, -.223190E+00}, {-.910062E+00, -.228467E+00}, + {-.911963E+00, -.232954E+00}, {-.913513E+00, -.236641E+00}, + {-.914714E+00, -.239518E+00}, {-.915570E+00, -.241578E+00}, + {-.916082E+00, -.242816E+00}, {-.916253E+00, -.243229E+00}, + }, + { + {0.268323E+00, 0.920458E+00}, {0.267897E+00, 0.920285E+00}, + {0.266621E+00, 0.919766E+00}, {0.264498E+00, 0.918899E+00}, + {0.261534E+00, 0.917683E+00}, {0.257735E+00, 0.916113E+00}, + {0.253112E+00, 0.914185E+00}, {0.247676E+00, 0.911895E+00}, + {0.241441E+00, 0.909237E+00}, {0.234421E+00, 0.906205E+00}, + {0.226634E+00, 0.902792E+00}, {0.218097E+00, 0.898992E+00}, + {0.208829E+00, 0.894797E+00}, {0.198852E+00, 0.890200E+00}, + {0.188186E+00, 0.885195E+00}, {0.176855E+00, 0.879775E+00}, + {0.164882E+00, 0.873933E+00}, {0.152290E+00, 0.867663E+00}, + {0.139105E+00, 0.860961E+00}, {0.125351E+00, 0.853820E+00}, + {0.111053E+00, 0.846236E+00}, {0.962384E-01, 0.838206E+00}, + {0.809318E-01, 0.829727E+00}, {0.651596E-01, 0.820796E+00}, + {0.489480E-01, 0.811413E+00}, {0.323234E-01, 0.801576E+00}, + {0.153121E-01, 0.791287E+00}, {-.205984E-02, 0.780545E+00}, + {-.197661E-01, 0.769355E+00}, {-.377805E-01, 0.757717E+00}, + {-.560772E-01, 0.745638E+00}, {-.746304E-01, 0.733120E+00}, + {-.934144E-01, 0.720170E+00}, {-.112404E+00, 0.706794E+00}, + {-.131573E+00, 0.693000E+00}, {-.150898E+00, 0.678796E+00}, + {-.170353E+00, 0.664190E+00}, {-.189914E+00, 0.649193E+00}, + {-.209556E+00, 0.633816E+00}, {-.229257E+00, 0.618069E+00}, + {-.248991E+00, 0.601965E+00}, {-.268737E+00, 0.585516E+00}, + {-.288471E+00, 0.568736E+00}, {-.308170E+00, 0.551640E+00}, + {-.327812E+00, 0.534242E+00}, {-.347377E+00, 0.516558E+00}, + {-.366841E+00, 0.498603E+00}, {-.386185E+00, 0.480396E+00}, + {-.405388E+00, 0.461952E+00}, {-.424429E+00, 0.443290E+00}, + {-.443290E+00, 0.424429E+00}, {-.461952E+00, 0.405388E+00}, + {-.480396E+00, 0.386185E+00}, {-.498603E+00, 0.366841E+00}, + {-.516558E+00, 0.347377E+00}, {-.534242E+00, 0.327812E+00}, + {-.551640E+00, 0.308170E+00}, {-.568736E+00, 0.288471E+00}, + {-.585516E+00, 0.268737E+00}, {-.601965E+00, 0.248991E+00}, + {-.618069E+00, 0.229257E+00}, {-.633816E+00, 0.209556E+00}, + {-.649193E+00, 0.189914E+00}, {-.664190E+00, 0.170353E+00}, + {-.678796E+00, 0.150898E+00}, {-.693000E+00, 0.131573E+00}, + {-.706794E+00, 0.112404E+00}, {-.720170E+00, 0.934144E-01}, + {-.733120E+00, 0.746304E-01}, {-.745638E+00, 0.560772E-01}, + {-.757717E+00, 0.377805E-01}, {-.769355E+00, 0.197661E-01}, + {-.780545E+00, 0.205984E-02}, {-.791287E+00, -.153121E-01}, + {-.801576E+00, -.323234E-01}, {-.811413E+00, -.489480E-01}, + {-.820796E+00, -.651596E-01}, {-.829727E+00, -.809318E-01}, + {-.838206E+00, -.962384E-01}, {-.846236E+00, -.111053E+00}, + {-.853820E+00, -.125351E+00}, {-.860961E+00, -.139105E+00}, + {-.867663E+00, -.152290E+00}, {-.873933E+00, -.164882E+00}, + {-.879775E+00, -.176855E+00}, {-.885195E+00, -.188186E+00}, + {-.890200E+00, -.198852E+00}, {-.894797E+00, -.208829E+00}, + {-.898992E+00, -.218097E+00}, {-.902792E+00, -.226634E+00}, + {-.906205E+00, -.234421E+00}, {-.909237E+00, -.241441E+00}, + {-.911895E+00, -.247676E+00}, {-.914185E+00, -.253112E+00}, + {-.916113E+00, -.257735E+00}, {-.917683E+00, -.261534E+00}, + {-.918899E+00, -.264498E+00}, {-.919766E+00, -.266621E+00}, + {-.920285E+00, -.267897E+00}, {-.920458E+00, -.268323E+00}, + }, + { + {0.294542E+00, 0.924695E+00}, {0.294103E+00, 0.924520E+00}, + {0.292788E+00, 0.923995E+00}, {0.290599E+00, 0.923118E+00}, + {0.287543E+00, 0.921885E+00}, {0.283628E+00, 0.920295E+00}, + {0.278863E+00, 0.918341E+00}, {0.273262E+00, 0.916019E+00}, + {0.266838E+00, 0.913322E+00}, {0.259606E+00, 0.910244E+00}, + {0.251585E+00, 0.906777E+00}, {0.242794E+00, 0.902915E+00}, + {0.233252E+00, 0.898649E+00}, {0.222982E+00, 0.893972E+00}, + {0.212006E+00, 0.888876E+00}, {0.200348E+00, 0.883354E+00}, + {0.188031E+00, 0.877400E+00}, {0.175080E+00, 0.871006E+00}, + {0.161522E+00, 0.864166E+00}, {0.147382E+00, 0.856876E+00}, + {0.132686E+00, 0.849130E+00}, {0.117460E+00, 0.840925E+00}, + {0.101733E+00, 0.832257E+00}, {0.855296E-01, 0.823125E+00}, + {0.688776E-01, 0.813526E+00}, {0.518040E-01, 0.803461E+00}, + {0.343356E-01, 0.792929E+00}, {0.164994E-01, 0.781931E+00}, + {-.167782E-02, 0.770471E+00}, {-.201694E-01, 0.758552E+00}, + {-.389487E-01, 0.746176E+00}, {-.579895E-01, 0.733350E+00}, + {-.772654E-01, 0.720079E+00}, {-.967506E-01, 0.706369E+00}, + {-.116419E+00, 0.692230E+00}, {-.136246E+00, 0.677668E+00}, + {-.156205E+00, 0.662694E+00}, {-.176272E+00, 0.647318E+00}, + {-.196421E+00, 0.631550E+00}, {-.216630E+00, 0.615403E+00}, + {-.236872E+00, 0.598889E+00}, {-.257126E+00, 0.582021E+00}, + {-.277366E+00, 0.564813E+00}, {-.297571E+00, 0.547279E+00}, + {-.317718E+00, 0.529437E+00}, {-.337784E+00, 0.511300E+00}, + {-.357748E+00, 0.492886E+00}, {-.377587E+00, 0.474212E+00}, + {-.397282E+00, 0.455296E+00}, {-.416812E+00, 0.436156E+00}, + {-.436156E+00, 0.416812E+00}, {-.455296E+00, 0.397282E+00}, + {-.474212E+00, 0.377587E+00}, {-.492886E+00, 0.357748E+00}, + {-.511300E+00, 0.337784E+00}, {-.529437E+00, 0.317718E+00}, + {-.547279E+00, 0.297571E+00}, {-.564813E+00, 0.277366E+00}, + {-.582021E+00, 0.257126E+00}, {-.598889E+00, 0.236872E+00}, + {-.615403E+00, 0.216630E+00}, {-.631550E+00, 0.196421E+00}, + {-.647318E+00, 0.176272E+00}, {-.662694E+00, 0.156205E+00}, + {-.677668E+00, 0.136246E+00}, {-.692230E+00, 0.116419E+00}, + {-.706369E+00, 0.967506E-01}, {-.720079E+00, 0.772654E-01}, + {-.733350E+00, 0.579895E-01}, {-.746176E+00, 0.389487E-01}, + {-.758552E+00, 0.201694E-01}, {-.770471E+00, 0.167782E-02}, + {-.781931E+00, -.164994E-01}, {-.792929E+00, -.343356E-01}, + {-.803461E+00, -.518040E-01}, {-.813526E+00, -.688776E-01}, + {-.823125E+00, -.855296E-01}, {-.832257E+00, -.101733E+00}, + {-.840925E+00, -.117460E+00}, {-.849130E+00, -.132686E+00}, + {-.856876E+00, -.147382E+00}, {-.864166E+00, -.161522E+00}, + {-.871006E+00, -.175080E+00}, {-.877400E+00, -.188031E+00}, + {-.883354E+00, -.200348E+00}, {-.888876E+00, -.212006E+00}, + {-.893972E+00, -.222982E+00}, {-.898649E+00, -.233252E+00}, + {-.902915E+00, -.242794E+00}, {-.906777E+00, -.251585E+00}, + {-.910244E+00, -.259606E+00}, {-.913322E+00, -.266838E+00}, + {-.916019E+00, -.273262E+00}, {-.918341E+00, -.278863E+00}, + {-.920295E+00, -.283628E+00}, {-.921885E+00, -.287543E+00}, + {-.923118E+00, -.290599E+00}, {-.923995E+00, -.292788E+00}, + {-.924520E+00, -.294103E+00}, {-.924695E+00, -.294542E+00}, + }, + { + {0.321979E+00, 0.928969E+00}, {0.321526E+00, 0.928792E+00}, + {0.320169E+00, 0.928260E+00}, {0.317912E+00, 0.927371E+00}, + {0.314760E+00, 0.926123E+00}, {0.310723E+00, 0.924512E+00}, + {0.305811E+00, 0.922531E+00}, {0.300036E+00, 0.920176E+00}, + {0.293414E+00, 0.917439E+00}, {0.285962E+00, 0.914313E+00}, + {0.277698E+00, 0.910791E+00}, {0.268643E+00, 0.906864E+00}, + {0.258816E+00, 0.902524E+00}, {0.248242E+00, 0.897762E+00}, + {0.236943E+00, 0.892571E+00}, {0.224944E+00, 0.886943E+00}, + {0.212270E+00, 0.880870E+00}, {0.198948E+00, 0.874344E+00}, + {0.185004E+00, 0.867361E+00}, {0.170464E+00, 0.859913E+00}, + {0.155355E+00, 0.851996E+00}, {0.139706E+00, 0.843605E+00}, + {0.123543E+00, 0.834739E+00}, {0.106894E+00, 0.825393E+00}, + {0.897874E-01, 0.815566E+00}, {0.722500E-01, 0.805259E+00}, + {0.543097E-01, 0.794470E+00}, {0.359940E-01, 0.783203E+00}, + {0.173304E-01, 0.771458E+00}, {-.165385E-02, 0.759240E+00}, + {-.209316E-01, 0.746553E+00}, {-.404759E-01, 0.733401E+00}, + {-.602599E-01, 0.719791E+00}, {-.802573E-01, 0.705731E+00}, + {-.100442E+00, 0.691228E+00}, {-.120787E+00, 0.676291E+00}, + {-.141267E+00, 0.660930E+00}, {-.161857E+00, 0.645155E+00}, + {-.182532E+00, 0.628978E+00}, {-.203266E+00, 0.612411E+00}, + {-.224035E+00, 0.595466E+00}, {-.244814E+00, 0.578159E+00}, + {-.265580E+00, 0.560503E+00}, {-.286310E+00, 0.542512E+00}, + {-.306980E+00, 0.524204E+00}, {-.327567E+00, 0.505595E+00}, + {-.348049E+00, 0.486701E+00}, {-.368404E+00, 0.467541E+00}, + {-.388610E+00, 0.448133E+00}, {-.408648E+00, 0.428495E+00}, + {-.428495E+00, 0.408648E+00}, {-.448133E+00, 0.388610E+00}, + {-.467541E+00, 0.368404E+00}, {-.486701E+00, 0.348049E+00}, + {-.505595E+00, 0.327567E+00}, {-.524204E+00, 0.306980E+00}, + {-.542512E+00, 0.286310E+00}, {-.560503E+00, 0.265580E+00}, + {-.578159E+00, 0.244814E+00}, {-.595466E+00, 0.224035E+00}, + {-.612411E+00, 0.203266E+00}, {-.628978E+00, 0.182532E+00}, + {-.645155E+00, 0.161857E+00}, {-.660930E+00, 0.141267E+00}, + {-.676291E+00, 0.120787E+00}, {-.691228E+00, 0.100442E+00}, + {-.705731E+00, 0.802573E-01}, {-.719791E+00, 0.602599E-01}, + {-.733401E+00, 0.404759E-01}, {-.746553E+00, 0.209316E-01}, + {-.759240E+00, 0.165385E-02}, {-.771458E+00, -.173304E-01}, + {-.783203E+00, -.359940E-01}, {-.794470E+00, -.543097E-01}, + {-.805259E+00, -.722500E-01}, {-.815566E+00, -.897874E-01}, + {-.825393E+00, -.106894E+00}, {-.834739E+00, -.123543E+00}, + {-.843605E+00, -.139706E+00}, {-.851996E+00, -.155355E+00}, + {-.859913E+00, -.170464E+00}, {-.867361E+00, -.185004E+00}, + {-.874344E+00, -.198948E+00}, {-.880870E+00, -.212270E+00}, + {-.886943E+00, -.224944E+00}, {-.892571E+00, -.236943E+00}, + {-.897762E+00, -.248242E+00}, {-.902524E+00, -.258816E+00}, + {-.906864E+00, -.268643E+00}, {-.910791E+00, -.277698E+00}, + {-.914313E+00, -.285962E+00}, {-.917439E+00, -.293414E+00}, + {-.920176E+00, -.300036E+00}, {-.922531E+00, -.305811E+00}, + {-.924512E+00, -.310723E+00}, {-.926123E+00, -.314760E+00}, + {-.927371E+00, -.317912E+00}, {-.928260E+00, -.320169E+00}, + {-.928792E+00, -.321526E+00}, {-.928969E+00, -.321979E+00}, + }, + { + {0.350735E+00, 0.933281E+00}, {0.350268E+00, 0.933101E+00}, + {0.348868E+00, 0.932563E+00}, {0.346538E+00, 0.931663E+00}, + {0.343287E+00, 0.930399E+00}, {0.339121E+00, 0.928766E+00}, + {0.334054E+00, 0.926758E+00}, {0.328099E+00, 0.924368E+00}, + {0.321271E+00, 0.921590E+00}, {0.313589E+00, 0.918415E+00}, + {0.305071E+00, 0.914835E+00}, {0.295740E+00, 0.910840E+00}, + {0.285616E+00, 0.906422E+00}, {0.274725E+00, 0.901572E+00}, + {0.263091E+00, 0.896281E+00}, {0.250739E+00, 0.890539E+00}, + {0.237696E+00, 0.884340E+00}, {0.223988E+00, 0.877676E+00}, + {0.209644E+00, 0.870539E+00}, {0.194690E+00, 0.862924E+00}, + {0.179155E+00, 0.854825E+00}, {0.163067E+00, 0.846238E+00}, + {0.146454E+00, 0.837159E+00}, {0.129345E+00, 0.827586E+00}, + {0.111768E+00, 0.817518E+00}, {0.937515E-01, 0.806953E+00}, + {0.753238E-01, 0.795893E+00}, {0.565130E-01, 0.784338E+00}, + {0.373472E-01, 0.772292E+00}, {0.178542E-01, 0.759758E+00}, + {-.193819E-02, 0.746740E+00}, {-.220026E-01, 0.733245E+00}, + {-.423116E-01, 0.719277E+00}, {-.628382E-01, 0.704846E+00}, + {-.835557E-01, 0.689959E+00}, {-.104437E+00, 0.674626E+00}, + {-.125457E+00, 0.658856E+00}, {-.146588E+00, 0.642662E+00}, + {-.167806E+00, 0.626054E+00}, {-.189085E+00, 0.609045E+00}, + {-.210399E+00, 0.591649E+00}, {-.231724E+00, 0.573880E+00}, + {-.253035E+00, 0.555754E+00}, {-.274310E+00, 0.537285E+00}, + {-.295522E+00, 0.518489E+00}, {-.316651E+00, 0.499385E+00}, + {-.337672E+00, 0.479990E+00}, {-.358563E+00, 0.460322E+00}, + {-.379303E+00, 0.440399E+00}, {-.399869E+00, 0.420242E+00}, + {-.420242E+00, 0.399869E+00}, {-.440399E+00, 0.379303E+00}, + {-.460322E+00, 0.358563E+00}, {-.479990E+00, 0.337672E+00}, + {-.499385E+00, 0.316651E+00}, {-.518489E+00, 0.295522E+00}, + {-.537285E+00, 0.274310E+00}, {-.555754E+00, 0.253035E+00}, + {-.573880E+00, 0.231724E+00}, {-.591649E+00, 0.210399E+00}, + {-.609045E+00, 0.189085E+00}, {-.626054E+00, 0.167806E+00}, + {-.642662E+00, 0.146588E+00}, {-.658856E+00, 0.125457E+00}, + {-.674626E+00, 0.104437E+00}, {-.689959E+00, 0.835557E-01}, + {-.704846E+00, 0.628382E-01}, {-.719277E+00, 0.423116E-01}, + {-.733245E+00, 0.220026E-01}, {-.746740E+00, 0.193819E-02}, + {-.759758E+00, -.178542E-01}, {-.772292E+00, -.373472E-01}, + {-.784338E+00, -.565130E-01}, {-.795893E+00, -.753238E-01}, + {-.806953E+00, -.937515E-01}, {-.817518E+00, -.111768E+00}, + {-.827586E+00, -.129345E+00}, {-.837159E+00, -.146454E+00}, + {-.846238E+00, -.163067E+00}, {-.854825E+00, -.179155E+00}, + {-.862924E+00, -.194690E+00}, {-.870539E+00, -.209644E+00}, + {-.877676E+00, -.223988E+00}, {-.884340E+00, -.237696E+00}, + {-.890539E+00, -.250739E+00}, {-.896281E+00, -.263091E+00}, + {-.901572E+00, -.274725E+00}, {-.906422E+00, -.285616E+00}, + {-.910840E+00, -.295740E+00}, {-.914835E+00, -.305071E+00}, + {-.918415E+00, -.313589E+00}, {-.921590E+00, -.321271E+00}, + {-.924368E+00, -.328099E+00}, {-.926758E+00, -.334054E+00}, + {-.928766E+00, -.339121E+00}, {-.930399E+00, -.343287E+00}, + {-.931663E+00, -.346538E+00}, {-.932563E+00, -.348868E+00}, + {-.933101E+00, -.350268E+00}, {-.933281E+00, -.350735E+00}, + }, + { + {0.380926E+00, 0.937634E+00}, {0.380443E+00, 0.937452E+00}, + {0.378998E+00, 0.936907E+00}, {0.376593E+00, 0.935996E+00}, + {0.373236E+00, 0.934715E+00}, {0.368937E+00, 0.933060E+00}, + {0.363708E+00, 0.931024E+00}, {0.357563E+00, 0.928599E+00}, + {0.350520E+00, 0.925778E+00}, {0.342597E+00, 0.922552E+00}, + {0.333815E+00, 0.918911E+00}, {0.324196E+00, 0.914845E+00}, + {0.313763E+00, 0.910345E+00}, {0.302543E+00, 0.905401E+00}, + {0.290560E+00, 0.900002E+00}, {0.277841E+00, 0.894141E+00}, + {0.264415E+00, 0.887808E+00}, {0.250307E+00, 0.880995E+00}, + {0.235548E+00, 0.873694E+00}, {0.220166E+00, 0.865900E+00}, + {0.204190E+00, 0.857607E+00}, {0.187648E+00, 0.848810E+00}, + {0.170570E+00, 0.839505E+00}, {0.152986E+00, 0.829690E+00}, + {0.134923E+00, 0.819363E+00}, {0.116412E+00, 0.808524E+00}, + {0.974814E-01, 0.797174E+00}, {0.781595E-01, 0.785313E+00}, + {0.584752E-01, 0.772946E+00}, {0.384569E-01, 0.760075E+00}, + {0.181330E-01, 0.746706E+00}, {-.246860E-02, 0.732845E+00}, + {-.233200E-01, 0.718498E+00}, {-.443937E-01, 0.703673E+00}, + {-.656624E-01, 0.688380E+00}, {-.870989E-01, 0.672627E+00}, + {-.108677E+00, 0.656426E+00}, {-.130369E+00, 0.639788E+00}, + {-.152149E+00, 0.622725E+00}, {-.173992E+00, 0.605251E+00}, + {-.195872E+00, 0.587379E+00}, {-.217763E+00, 0.569125E+00}, + {-.239641E+00, 0.550503E+00}, {-.261481E+00, 0.531531E+00}, + {-.283259E+00, 0.512224E+00}, {-.304951E+00, 0.492601E+00}, + {-.326533E+00, 0.472680E+00}, {-.347983E+00, 0.452479E+00}, + {-.369279E+00, 0.432019E+00}, {-.390398E+00, 0.411318E+00}, + {-.411318E+00, 0.390398E+00}, {-.432019E+00, 0.369279E+00}, + {-.452479E+00, 0.347983E+00}, {-.472680E+00, 0.326533E+00}, + {-.492601E+00, 0.304951E+00}, {-.512224E+00, 0.283259E+00}, + {-.531531E+00, 0.261481E+00}, {-.550503E+00, 0.239641E+00}, + {-.569125E+00, 0.217763E+00}, {-.587379E+00, 0.195872E+00}, + {-.605251E+00, 0.173992E+00}, {-.622725E+00, 0.152149E+00}, + {-.639788E+00, 0.130369E+00}, {-.656426E+00, 0.108677E+00}, + {-.672627E+00, 0.870989E-01}, {-.688380E+00, 0.656624E-01}, + {-.703673E+00, 0.443937E-01}, {-.718498E+00, 0.233200E-01}, + {-.732845E+00, 0.246860E-02}, {-.746706E+00, -.181330E-01}, + {-.760075E+00, -.384569E-01}, {-.772946E+00, -.584752E-01}, + {-.785313E+00, -.781595E-01}, {-.797174E+00, -.974814E-01}, + {-.808524E+00, -.116412E+00}, {-.819363E+00, -.134923E+00}, + {-.829690E+00, -.152986E+00}, {-.839505E+00, -.170570E+00}, + {-.848810E+00, -.187648E+00}, {-.857607E+00, -.204190E+00}, + {-.865900E+00, -.220166E+00}, {-.873694E+00, -.235548E+00}, + {-.880995E+00, -.250307E+00}, {-.887808E+00, -.264415E+00}, + {-.894141E+00, -.277841E+00}, {-.900002E+00, -.290560E+00}, + {-.905401E+00, -.302543E+00}, {-.910345E+00, -.313763E+00}, + {-.914845E+00, -.324196E+00}, {-.918911E+00, -.333815E+00}, + {-.922552E+00, -.342597E+00}, {-.925778E+00, -.350520E+00}, + {-.928599E+00, -.357563E+00}, {-.931024E+00, -.363708E+00}, + {-.933060E+00, -.368937E+00}, {-.934715E+00, -.373236E+00}, + {-.935996E+00, -.376593E+00}, {-.936907E+00, -.378998E+00}, + {-.937452E+00, -.380443E+00}, {-.937634E+00, -.380926E+00}, + }, + { + {0.412680E+00, 0.942031E+00}, {0.412181E+00, 0.941848E+00}, + {0.410688E+00, 0.941296E+00}, {0.408204E+00, 0.940373E+00}, + {0.404737E+00, 0.939076E+00}, {0.400297E+00, 0.937398E+00}, + {0.394898E+00, 0.935332E+00}, {0.388555E+00, 0.932871E+00}, + {0.381286E+00, 0.930005E+00}, {0.373111E+00, 0.926725E+00}, + {0.364053E+00, 0.923019E+00}, {0.354134E+00, 0.918878E+00}, + {0.343380E+00, 0.914291E+00}, {0.331817E+00, 0.909246E+00}, + {0.319471E+00, 0.903733E+00}, {0.306372E+00, 0.897743E+00}, + {0.292547E+00, 0.891266E+00}, {0.278026E+00, 0.884294E+00}, + {0.262838E+00, 0.876817E+00}, {0.247013E+00, 0.868831E+00}, + {0.230580E+00, 0.860329E+00}, {0.213570E+00, 0.851305E+00}, + {0.196012E+00, 0.841757E+00}, {0.177937E+00, 0.831682E+00}, + {0.159373E+00, 0.821077E+00}, {0.140352E+00, 0.809944E+00}, + {0.120902E+00, 0.798283E+00}, {0.101052E+00, 0.786095E+00}, + {0.808331E-01, 0.773384E+00}, {0.602727E-01, 0.760154E+00}, + {0.394001E-01, 0.746410E+00}, {0.182437E-01, 0.732158E+00}, + {-.316804E-02, 0.717406E+00}, {-.248071E-01, 0.702163E+00}, + {-.466457E-01, 0.686437E+00}, {-.686563E-01, 0.670238E+00}, + {-.908115E-01, 0.653579E+00}, {-.113084E+00, 0.636470E+00}, + {-.135448E+00, 0.618926E+00}, {-.157876E+00, 0.600959E+00}, + {-.180343E+00, 0.582584E+00}, {-.202822E+00, 0.563818E+00}, + {-.225289E+00, 0.544674E+00}, {-.247718E+00, 0.525171E+00}, + {-.270083E+00, 0.505327E+00}, {-.292362E+00, 0.485158E+00}, + {-.314531E+00, 0.464684E+00}, {-.336564E+00, 0.443925E+00}, + {-.358440E+00, 0.422900E+00}, {-.380136E+00, 0.401630E+00}, + {-.401630E+00, 0.380136E+00}, {-.422900E+00, 0.358440E+00}, + {-.443925E+00, 0.336564E+00}, {-.464684E+00, 0.314531E+00}, + {-.485158E+00, 0.292362E+00}, {-.505327E+00, 0.270083E+00}, + {-.525171E+00, 0.247718E+00}, {-.544674E+00, 0.225289E+00}, + {-.563818E+00, 0.202822E+00}, {-.582584E+00, 0.180343E+00}, + {-.600959E+00, 0.157876E+00}, {-.618926E+00, 0.135448E+00}, + {-.636470E+00, 0.113084E+00}, {-.653579E+00, 0.908115E-01}, + {-.670238E+00, 0.686563E-01}, {-.686437E+00, 0.466457E-01}, + {-.702163E+00, 0.248071E-01}, {-.717406E+00, 0.316804E-02}, + {-.732158E+00, -.182437E-01}, {-.746410E+00, -.394001E-01}, + {-.760154E+00, -.602727E-01}, {-.773384E+00, -.808331E-01}, + {-.786095E+00, -.101052E+00}, {-.798283E+00, -.120902E+00}, + {-.809944E+00, -.140352E+00}, {-.821077E+00, -.159373E+00}, + {-.831682E+00, -.177937E+00}, {-.841757E+00, -.196012E+00}, + {-.851305E+00, -.213570E+00}, {-.860329E+00, -.230580E+00}, + {-.868831E+00, -.247013E+00}, {-.876817E+00, -.262838E+00}, + {-.884294E+00, -.278026E+00}, {-.891266E+00, -.292547E+00}, + {-.897743E+00, -.306372E+00}, {-.903733E+00, -.319471E+00}, + {-.909246E+00, -.331817E+00}, {-.914291E+00, -.343380E+00}, + {-.918878E+00, -.354134E+00}, {-.923019E+00, -.364053E+00}, + {-.926725E+00, -.373111E+00}, {-.930005E+00, -.381286E+00}, + {-.932871E+00, -.388555E+00}, {-.935332E+00, -.394898E+00}, + {-.937398E+00, -.400297E+00}, {-.939076E+00, -.404737E+00}, + {-.940373E+00, -.408204E+00}, {-.941296E+00, -.410688E+00}, + {-.941848E+00, -.412181E+00}, {-.942031E+00, -.412680E+00}, + }, + { + {0.446144E+00, 0.946477E+00}, {0.445629E+00, 0.946291E+00}, + {0.444085E+00, 0.945732E+00}, {0.441517E+00, 0.944798E+00}, + {0.437934E+00, 0.943483E+00}, {0.433347E+00, 0.941782E+00}, + {0.427769E+00, 0.939686E+00}, {0.421218E+00, 0.937187E+00}, + {0.413712E+00, 0.934274E+00}, {0.405274E+00, 0.930936E+00}, + {0.395927E+00, 0.927162E+00}, {0.385696E+00, 0.922940E+00}, + {0.374606E+00, 0.918259E+00}, {0.362686E+00, 0.913106E+00}, + {0.349965E+00, 0.907470E+00}, {0.336471E+00, 0.901341E+00}, + {0.322234E+00, 0.894708E+00}, {0.307284E+00, 0.887562E+00}, + {0.291652E+00, 0.879895E+00}, {0.275369E+00, 0.871701E+00}, + {0.258465E+00, 0.862971E+00}, {0.240971E+00, 0.853703E+00}, + {0.222918E+00, 0.843891E+00}, {0.204337E+00, 0.833534E+00}, + {0.185257E+00, 0.822630E+00}, {0.165710E+00, 0.811179E+00}, + {0.145724E+00, 0.799182E+00}, {0.125331E+00, 0.786642E+00}, + {0.104561E+00, 0.773561E+00}, {0.834411E-01, 0.759944E+00}, + {0.620024E-01, 0.745797E+00}, {0.402735E-01, 0.731127E+00}, + {0.182832E-01, 0.715942E+00}, {-.393997E-02, 0.700251E+00}, + {-.263677E-01, 0.684062E+00}, {-.489721E-01, 0.667389E+00}, + {-.717252E-01, 0.650241E+00}, {-.945996E-01, 0.632632E+00}, + {-.117568E+00, 0.614576E+00}, {-.140604E+00, 0.596086E+00}, + {-.163680E+00, 0.577178E+00}, {-.186771E+00, 0.557868E+00}, + {-.209850E+00, 0.538172E+00}, {-.232891E+00, 0.518109E+00}, + {-.255870E+00, 0.497696E+00}, {-.278762E+00, 0.476952E+00}, + {-.301541E+00, 0.455897E+00}, {-.324185E+00, 0.434550E+00}, + {-.346669E+00, 0.412932E+00}, {-.368970E+00, 0.391065E+00}, + {-.391065E+00, 0.368970E+00}, {-.412932E+00, 0.346669E+00}, + {-.434550E+00, 0.324185E+00}, {-.455897E+00, 0.301541E+00}, + {-.476952E+00, 0.278762E+00}, {-.497696E+00, 0.255870E+00}, + {-.518109E+00, 0.232891E+00}, {-.538172E+00, 0.209850E+00}, + {-.557868E+00, 0.186771E+00}, {-.577178E+00, 0.163680E+00}, + {-.596086E+00, 0.140604E+00}, {-.614576E+00, 0.117568E+00}, + {-.632632E+00, 0.945996E-01}, {-.650241E+00, 0.717252E-01}, + {-.667389E+00, 0.489721E-01}, {-.684062E+00, 0.263677E-01}, + {-.700251E+00, 0.393997E-02}, {-.715942E+00, -.182832E-01}, + {-.731127E+00, -.402735E-01}, {-.745797E+00, -.620024E-01}, + {-.759944E+00, -.834411E-01}, {-.773561E+00, -.104561E+00}, + {-.786642E+00, -.125331E+00}, {-.799182E+00, -.145724E+00}, + {-.811179E+00, -.165710E+00}, {-.822630E+00, -.185257E+00}, + {-.833534E+00, -.204337E+00}, {-.843891E+00, -.222918E+00}, + {-.853703E+00, -.240971E+00}, {-.862971E+00, -.258465E+00}, + {-.871701E+00, -.275369E+00}, {-.879895E+00, -.291652E+00}, + {-.887562E+00, -.307284E+00}, {-.894708E+00, -.322234E+00}, + {-.901341E+00, -.336471E+00}, {-.907470E+00, -.349965E+00}, + {-.913106E+00, -.362686E+00}, {-.918259E+00, -.374606E+00}, + {-.922940E+00, -.385696E+00}, {-.927162E+00, -.395927E+00}, + {-.930936E+00, -.405274E+00}, {-.934274E+00, -.413712E+00}, + {-.937187E+00, -.421218E+00}, {-.939686E+00, -.427769E+00}, + {-.941782E+00, -.433347E+00}, {-.943483E+00, -.437934E+00}, + {-.944798E+00, -.441517E+00}, {-.945732E+00, -.444085E+00}, + {-.946291E+00, -.445629E+00}, {-.946477E+00, -.446144E+00}, + }, + { + {0.481485E+00, 0.950975E+00}, {0.480952E+00, 0.950786E+00}, + {0.479355E+00, 0.950221E+00}, {0.476699E+00, 0.949274E+00}, + {0.472993E+00, 0.947942E+00}, {0.468250E+00, 0.946216E+00}, + {0.462484E+00, 0.944089E+00}, {0.455715E+00, 0.941549E+00}, + {0.447962E+00, 0.938586E+00}, {0.439248E+00, 0.935187E+00}, + {0.429599E+00, 0.931339E+00}, {0.419041E+00, 0.927030E+00}, + {0.407603E+00, 0.922246E+00}, {0.395312E+00, 0.916976E+00}, + {0.382200E+00, 0.911205E+00}, {0.368297E+00, 0.904924E+00}, + {0.353633E+00, 0.898121E+00}, {0.338241E+00, 0.890786E+00}, + {0.322151E+00, 0.882910E+00}, {0.305396E+00, 0.874488E+00}, + {0.288006E+00, 0.865510E+00}, {0.270015E+00, 0.855974E+00}, + {0.251452E+00, 0.845875E+00}, {0.232350E+00, 0.835211E+00}, + {0.212739E+00, 0.823981E+00}, {0.192650E+00, 0.812184E+00}, + {0.172114E+00, 0.799823E+00}, {0.151162E+00, 0.786899E+00}, + {0.129823E+00, 0.773418E+00}, {0.108128E+00, 0.759383E+00}, + {0.861064E-01, 0.744802E+00}, {0.637873E-01, 0.729681E+00}, + {0.412002E-01, 0.714029E+00}, {0.183740E-01, 0.697856E+00}, + {-.466248E-02, 0.681172E+00}, {-.278808E-01, 0.663988E+00}, + {-.512527E-01, 0.646318E+00}, {-.747503E-01, 0.628174E+00}, + {-.983459E-01, 0.609571E+00}, {-.122012E+00, 0.590524E+00}, + {-.145722E+00, 0.571048E+00}, {-.169448E+00, 0.551161E+00}, + {-.193164E+00, 0.530879E+00}, {-.216845E+00, 0.510222E+00}, + {-.240464E+00, 0.489207E+00}, {-.263995E+00, 0.467855E+00}, + {-.287414E+00, 0.446185E+00}, {-.310696E+00, 0.424219E+00}, + {-.333817E+00, 0.401976E+00}, {-.356753E+00, 0.379480E+00}, + {-.379480E+00, 0.356753E+00}, {-.401976E+00, 0.333817E+00}, + {-.424219E+00, 0.310696E+00}, {-.446185E+00, 0.287414E+00}, + {-.467855E+00, 0.263995E+00}, {-.489207E+00, 0.240464E+00}, + {-.510222E+00, 0.216845E+00}, {-.530879E+00, 0.193164E+00}, + {-.551161E+00, 0.169448E+00}, {-.571048E+00, 0.145722E+00}, + {-.590524E+00, 0.122012E+00}, {-.609571E+00, 0.983459E-01}, + {-.628174E+00, 0.747503E-01}, {-.646318E+00, 0.512527E-01}, + {-.663988E+00, 0.278808E-01}, {-.681172E+00, 0.466248E-02}, + {-.697856E+00, -.183740E-01}, {-.714029E+00, -.412002E-01}, + {-.729681E+00, -.637873E-01}, {-.744802E+00, -.861064E-01}, + {-.759383E+00, -.108128E+00}, {-.773418E+00, -.129823E+00}, + {-.786899E+00, -.151162E+00}, {-.799823E+00, -.172114E+00}, + {-.812184E+00, -.192650E+00}, {-.823981E+00, -.212739E+00}, + {-.835211E+00, -.232350E+00}, {-.845875E+00, -.251452E+00}, + {-.855974E+00, -.270015E+00}, {-.865510E+00, -.288006E+00}, + {-.874488E+00, -.305396E+00}, {-.882910E+00, -.322151E+00}, + {-.890786E+00, -.338241E+00}, {-.898121E+00, -.353633E+00}, + {-.904924E+00, -.368297E+00}, {-.911205E+00, -.382200E+00}, + {-.916976E+00, -.395312E+00}, {-.922246E+00, -.407603E+00}, + {-.927030E+00, -.419041E+00}, {-.931339E+00, -.429599E+00}, + {-.935187E+00, -.439248E+00}, {-.938586E+00, -.447962E+00}, + {-.941549E+00, -.455715E+00}, {-.944089E+00, -.462484E+00}, + {-.946216E+00, -.468250E+00}, {-.947942E+00, -.472993E+00}, + {-.949274E+00, -.476699E+00}, {-.950221E+00, -.479355E+00}, + {-.950786E+00, -.480952E+00}, {-.950975E+00, -.481485E+00}, + }, + { + {0.518892E+00, 0.955528E+00}, {0.518340E+00, 0.955338E+00}, + {0.516686E+00, 0.954765E+00}, {0.513937E+00, 0.953806E+00}, + {0.510102E+00, 0.952455E+00}, {0.505195E+00, 0.950704E+00}, + {0.499232E+00, 0.948543E+00}, {0.492232E+00, 0.945961E+00}, + {0.484219E+00, 0.942943E+00}, {0.475217E+00, 0.939478E+00}, + {0.465254E+00, 0.935550E+00}, {0.454356E+00, 0.931145E+00}, + {0.442555E+00, 0.926249E+00}, {0.429880E+00, 0.920849E+00}, + {0.416363E+00, 0.914930E+00}, {0.402037E+00, 0.908480E+00}, + {0.386933E+00, 0.901488E+00}, {0.371084E+00, 0.893943E+00}, + {0.354523E+00, 0.885838E+00}, {0.337283E+00, 0.877162E+00}, + {0.319395E+00, 0.867912E+00}, {0.300892E+00, 0.858080E+00}, + {0.281806E+00, 0.847665E+00}, {0.262170E+00, 0.836663E+00}, + {0.242014E+00, 0.825074E+00}, {0.221370E+00, 0.812898E+00}, + {0.200270E+00, 0.800138E+00}, {0.178743E+00, 0.786796E+00}, + {0.156822E+00, 0.772878E+00}, {0.134535E+00, 0.758388E+00}, + {0.111914E+00, 0.743334E+00}, {0.889872E-01, 0.727723E+00}, + {0.657854E-01, 0.711566E+00}, {0.423377E-01, 0.694872E+00}, + {0.186731E-01, 0.677653E+00}, {-.517948E-02, 0.659920E+00}, + {-.291913E-01, 0.641687E+00}, {-.533341E-01, 0.622969E+00}, + {-.775796E-01, 0.603780E+00}, {-.101900E+00, 0.584136E+00}, + {-.126268E+00, 0.564053E+00}, {-.150656E+00, 0.543550E+00}, + {-.175036E+00, 0.522644E+00}, {-.199383E+00, 0.501354E+00}, + {-.223670E+00, 0.479701E+00}, {-.247871E+00, 0.457703E+00}, + {-.271960E+00, 0.435382E+00}, {-.295912E+00, 0.412760E+00}, + {-.319702E+00, 0.389858E+00}, {-.343305E+00, 0.366699E+00}, + {-.366699E+00, 0.343305E+00}, {-.389858E+00, 0.319702E+00}, + {-.412760E+00, 0.295912E+00}, {-.435382E+00, 0.271960E+00}, + {-.457703E+00, 0.247871E+00}, {-.479701E+00, 0.223670E+00}, + {-.501354E+00, 0.199383E+00}, {-.522644E+00, 0.175036E+00}, + {-.543550E+00, 0.150656E+00}, {-.564053E+00, 0.126268E+00}, + {-.584136E+00, 0.101900E+00}, {-.603780E+00, 0.775796E-01}, + {-.622969E+00, 0.533341E-01}, {-.641687E+00, 0.291913E-01}, + {-.659920E+00, 0.517948E-02}, {-.677653E+00, -.186731E-01}, + {-.694872E+00, -.423377E-01}, {-.711566E+00, -.657854E-01}, + {-.727723E+00, -.889872E-01}, {-.743334E+00, -.111914E+00}, + {-.758388E+00, -.134535E+00}, {-.772878E+00, -.156822E+00}, + {-.786796E+00, -.178743E+00}, {-.800138E+00, -.200270E+00}, + {-.812898E+00, -.221370E+00}, {-.825074E+00, -.242014E+00}, + {-.836663E+00, -.262170E+00}, {-.847665E+00, -.281806E+00}, + {-.858080E+00, -.300892E+00}, {-.867912E+00, -.319395E+00}, + {-.877162E+00, -.337283E+00}, {-.885838E+00, -.354523E+00}, + {-.893943E+00, -.371084E+00}, {-.901488E+00, -.386933E+00}, + {-.908480E+00, -.402037E+00}, {-.914930E+00, -.416363E+00}, + {-.920849E+00, -.429880E+00}, {-.926249E+00, -.442555E+00}, + {-.931145E+00, -.454356E+00}, {-.935550E+00, -.465254E+00}, + {-.939478E+00, -.475217E+00}, {-.942943E+00, -.484219E+00}, + {-.945961E+00, -.492232E+00}, {-.948543E+00, -.499232E+00}, + {-.950704E+00, -.505195E+00}, {-.952455E+00, -.510102E+00}, + {-.953806E+00, -.513937E+00}, {-.954765E+00, -.516686E+00}, + {-.955338E+00, -.518340E+00}, {-.955528E+00, -.518892E+00}, + }, + { + {0.558582E+00, 0.960142E+00}, {0.558011E+00, 0.959949E+00}, + {0.556297E+00, 0.959369E+00}, {0.553450E+00, 0.958398E+00}, + {0.549478E+00, 0.957028E+00}, {0.544397E+00, 0.955251E+00}, + {0.538226E+00, 0.953054E+00}, {0.530985E+00, 0.950424E+00}, + {0.522699E+00, 0.947348E+00}, {0.513396E+00, 0.943809E+00}, + {0.503104E+00, 0.939792E+00}, {0.491853E+00, 0.935281E+00}, + {0.479675E+00, 0.930260E+00}, {0.466603E+00, 0.924713E+00}, + {0.452669E+00, 0.918627E+00}, {0.437908E+00, 0.911988E+00}, + {0.422352E+00, 0.904785E+00}, {0.406035E+00, 0.897005E+00}, + {0.388992E+00, 0.888641E+00}, {0.371255E+00, 0.879683E+00}, + {0.352857E+00, 0.870126E+00}, {0.333833E+00, 0.859965E+00}, + {0.314213E+00, 0.849197E+00}, {0.294032E+00, 0.837820E+00}, + {0.273320E+00, 0.825833E+00}, {0.252109E+00, 0.813238E+00}, + {0.230432E+00, 0.800037E+00}, {0.208319E+00, 0.786234E+00}, + {0.185801E+00, 0.771835E+00}, {0.162909E+00, 0.756846E+00}, + {0.139673E+00, 0.741274E+00}, {0.116123E+00, 0.725129E+00}, + {0.922903E-01, 0.708420E+00}, {0.682033E-01, 0.691159E+00}, + {0.438919E-01, 0.673358E+00}, {0.193853E-01, 0.655030E+00}, + {-.528737E-02, 0.636188E+00}, {-.300974E-01, 0.616849E+00}, + {-.550161E-01, 0.597028E+00}, {-.800153E-01, 0.576741E+00}, + {-.105067E+00, 0.556007E+00}, {-.130143E+00, 0.534843E+00}, + {-.155216E+00, 0.513269E+00}, {-.180259E+00, 0.491304E+00}, + {-.205244E+00, 0.468968E+00}, {-.230146E+00, 0.446283E+00}, + {-.254938E+00, 0.423270E+00}, {-.279593E+00, 0.399952E+00}, + {-.304087E+00, 0.376351E+00}, {-.328395E+00, 0.352491E+00}, + {-.352491E+00, 0.328395E+00}, {-.376351E+00, 0.304087E+00}, + {-.399952E+00, 0.279593E+00}, {-.423270E+00, 0.254938E+00}, + {-.446283E+00, 0.230146E+00}, {-.468968E+00, 0.205244E+00}, + {-.491304E+00, 0.180259E+00}, {-.513269E+00, 0.155216E+00}, + {-.534843E+00, 0.130143E+00}, {-.556007E+00, 0.105067E+00}, + {-.576741E+00, 0.800153E-01}, {-.597028E+00, 0.550161E-01}, + {-.616849E+00, 0.300974E-01}, {-.636188E+00, 0.528737E-02}, + {-.655030E+00, -.193853E-01}, {-.673358E+00, -.438919E-01}, + {-.691159E+00, -.682033E-01}, {-.708420E+00, -.922903E-01}, + {-.725129E+00, -.116123E+00}, {-.741274E+00, -.139673E+00}, + {-.756846E+00, -.162909E+00}, {-.771835E+00, -.185801E+00}, + {-.786234E+00, -.208319E+00}, {-.800037E+00, -.230432E+00}, + {-.813238E+00, -.252109E+00}, {-.825833E+00, -.273320E+00}, + {-.837820E+00, -.294032E+00}, {-.849197E+00, -.314213E+00}, + {-.859965E+00, -.333833E+00}, {-.870126E+00, -.352857E+00}, + {-.879683E+00, -.371255E+00}, {-.888641E+00, -.388992E+00}, + {-.897005E+00, -.406035E+00}, {-.904785E+00, -.422352E+00}, + {-.911988E+00, -.437908E+00}, {-.918627E+00, -.452669E+00}, + {-.924713E+00, -.466603E+00}, {-.930260E+00, -.479675E+00}, + {-.935281E+00, -.491853E+00}, {-.939792E+00, -.503104E+00}, + {-.943809E+00, -.513396E+00}, {-.947348E+00, -.522699E+00}, + {-.950424E+00, -.530985E+00}, {-.953054E+00, -.538226E+00}, + {-.955251E+00, -.544397E+00}, {-.957028E+00, -.549478E+00}, + {-.958398E+00, -.553450E+00}, {-.959369E+00, -.556297E+00}, + {-.959949E+00, -.558011E+00}, {-.960142E+00, -.558582E+00}, + }, + { + {0.600809E+00, 0.964822E+00}, {0.600216E+00, 0.964627E+00}, + {0.598440E+00, 0.964040E+00}, {0.595487E+00, 0.963055E+00}, + {0.591371E+00, 0.961665E+00}, {0.586107E+00, 0.959859E+00}, + {0.579715E+00, 0.957623E+00}, {0.572221E+00, 0.954942E+00}, + {0.563651E+00, 0.951800E+00}, {0.554033E+00, 0.948179E+00}, + {0.543400E+00, 0.944061E+00}, {0.531784E+00, 0.939428E+00}, + {0.519218E+00, 0.934264E+00}, {0.505737E+00, 0.928551E+00}, + {0.491376E+00, 0.922274E+00}, {0.476170E+00, 0.915419E+00}, + {0.460153E+00, 0.907973E+00}, {0.443361E+00, 0.899925E+00}, + {0.425827E+00, 0.891266E+00}, {0.407587E+00, 0.881987E+00}, + {0.388673E+00, 0.872083E+00}, {0.369120E+00, 0.861550E+00}, + {0.348960E+00, 0.850384E+00}, {0.328226E+00, 0.838584E+00}, + {0.306951E+00, 0.826150E+00}, {0.285166E+00, 0.813085E+00}, + {0.262903E+00, 0.799392E+00}, {0.240194E+00, 0.785076E+00}, + {0.217070E+00, 0.770143E+00}, {0.193562E+00, 0.754600E+00}, + {0.169699E+00, 0.738456E+00}, {0.145514E+00, 0.721721E+00}, + {0.121035E+00, 0.704406E+00}, {0.962931E-01, 0.686523E+00}, + {0.713179E-01, 0.668085E+00}, {0.461388E-01, 0.649106E+00}, + {0.207854E-01, 0.629601E+00}, {-.471322E-02, 0.609587E+00}, + {-.303281E-01, 0.589080E+00}, {-.560305E-01, 0.568098E+00}, + {-.817918E-01, 0.546660E+00}, {-.107584E+00, 0.524783E+00}, + {-.133378E+00, 0.502490E+00}, {-.159148E+00, 0.479799E+00}, + {-.184864E+00, 0.456733E+00}, {-.210501E+00, 0.433313E+00}, + {-.236031E+00, 0.409562E+00}, {-.261427E+00, 0.385503E+00}, + {-.286663E+00, 0.361159E+00}, {-.311715E+00, 0.336555E+00}, + {-.336555E+00, 0.311715E+00}, {-.361159E+00, 0.286663E+00}, + {-.385503E+00, 0.261427E+00}, {-.409562E+00, 0.236031E+00}, + {-.433313E+00, 0.210501E+00}, {-.456733E+00, 0.184864E+00}, + {-.479799E+00, 0.159148E+00}, {-.502490E+00, 0.133378E+00}, + {-.524783E+00, 0.107584E+00}, {-.546660E+00, 0.817918E-01}, + {-.568098E+00, 0.560305E-01}, {-.589080E+00, 0.303281E-01}, + {-.609587E+00, 0.471322E-02}, {-.629601E+00, -.207854E-01}, + {-.649106E+00, -.461388E-01}, {-.668085E+00, -.713179E-01}, + {-.686523E+00, -.962931E-01}, {-.704406E+00, -.121035E+00}, + {-.721721E+00, -.145514E+00}, {-.738456E+00, -.169699E+00}, + {-.754600E+00, -.193562E+00}, {-.770143E+00, -.217070E+00}, + {-.785076E+00, -.240194E+00}, {-.799392E+00, -.262903E+00}, + {-.813085E+00, -.285166E+00}, {-.826150E+00, -.306951E+00}, + {-.838584E+00, -.328226E+00}, {-.850384E+00, -.348960E+00}, + {-.861550E+00, -.369120E+00}, {-.872083E+00, -.388673E+00}, + {-.881987E+00, -.407587E+00}, {-.891266E+00, -.425827E+00}, + {-.899925E+00, -.443361E+00}, {-.907973E+00, -.460153E+00}, + {-.915419E+00, -.476170E+00}, {-.922274E+00, -.491376E+00}, + {-.928551E+00, -.505737E+00}, {-.934264E+00, -.519218E+00}, + {-.939428E+00, -.531784E+00}, {-.944061E+00, -.543400E+00}, + {-.948179E+00, -.554033E+00}, {-.951800E+00, -.563651E+00}, + {-.954942E+00, -.572221E+00}, {-.957623E+00, -.579715E+00}, + {-.959859E+00, -.586107E+00}, {-.961665E+00, -.591371E+00}, + {-.963055E+00, -.595487E+00}, {-.964040E+00, -.598440E+00}, + {-.964627E+00, -.600216E+00}, {-.964822E+00, -.600809E+00}, + }, + { + {0.645865E+00, 0.969573E+00}, {0.645249E+00, 0.969376E+00}, + {0.643405E+00, 0.968781E+00}, {0.640341E+00, 0.967782E+00}, + {0.636071E+00, 0.966371E+00}, {0.630613E+00, 0.964533E+00}, + {0.623991E+00, 0.962254E+00}, {0.616231E+00, 0.959515E+00}, + {0.607364E+00, 0.956297E+00}, {0.597421E+00, 0.952581E+00}, + {0.586436E+00, 0.948345E+00}, {0.574444E+00, 0.943571E+00}, + {0.561482E+00, 0.938238E+00}, {0.547586E+00, 0.932330E+00}, + {0.532793E+00, 0.925829E+00}, {0.517137E+00, 0.918721E+00}, + {0.500657E+00, 0.910993E+00}, {0.483387E+00, 0.902632E+00}, + {0.465362E+00, 0.893631E+00}, {0.446618E+00, 0.883981E+00}, + {0.427188E+00, 0.873676E+00}, {0.407107E+00, 0.862713E+00}, + {0.386407E+00, 0.851091E+00}, {0.365121E+00, 0.838808E+00}, + {0.343282E+00, 0.825866E+00}, {0.320922E+00, 0.812268E+00}, + {0.298072E+00, 0.798018E+00}, {0.274764E+00, 0.783123E+00}, + {0.251030E+00, 0.767589E+00}, {0.226899E+00, 0.751426E+00}, + {0.202404E+00, 0.734642E+00}, {0.177574E+00, 0.717250E+00}, + {0.152439E+00, 0.699261E+00}, {0.127031E+00, 0.680688E+00}, + {0.101378E+00, 0.661547E+00}, {0.755109E-01, 0.641851E+00}, + {0.494592E-01, 0.621618E+00}, {0.232525E-01, 0.600864E+00}, + {-.308006E-02, 0.579608E+00}, {-.295092E-01, 0.557867E+00}, + {-.560059E-01, 0.535662E+00}, {-.825415E-01, 0.513013E+00}, + {-.109087E+00, 0.489941E+00}, {-.135615E+00, 0.466468E+00}, + {-.162097E+00, 0.442614E+00}, {-.188504E+00, 0.418405E+00}, + {-.214810E+00, 0.393862E+00}, {-.240987E+00, 0.369011E+00}, + {-.267009E+00, 0.343874E+00}, {-.292848E+00, 0.318478E+00}, + {-.318478E+00, 0.292848E+00}, {-.343874E+00, 0.267009E+00}, + {-.369011E+00, 0.240987E+00}, {-.393862E+00, 0.214810E+00}, + {-.418405E+00, 0.188504E+00}, {-.442614E+00, 0.162097E+00}, + {-.466468E+00, 0.135615E+00}, {-.489941E+00, 0.109087E+00}, + {-.513013E+00, 0.825415E-01}, {-.535662E+00, 0.560059E-01}, + {-.557867E+00, 0.295092E-01}, {-.579608E+00, 0.308006E-02}, + {-.600864E+00, -.232525E-01}, {-.621618E+00, -.494592E-01}, + {-.641851E+00, -.755109E-01}, {-.661547E+00, -.101378E+00}, + {-.680688E+00, -.127031E+00}, {-.699261E+00, -.152439E+00}, + {-.717250E+00, -.177574E+00}, {-.734642E+00, -.202404E+00}, + {-.751426E+00, -.226899E+00}, {-.767589E+00, -.251030E+00}, + {-.783123E+00, -.274764E+00}, {-.798018E+00, -.298072E+00}, + {-.812268E+00, -.320922E+00}, {-.825866E+00, -.343282E+00}, + {-.838808E+00, -.365121E+00}, {-.851091E+00, -.386407E+00}, + {-.862713E+00, -.407107E+00}, {-.873676E+00, -.427188E+00}, + {-.883981E+00, -.446618E+00}, {-.893631E+00, -.465362E+00}, + {-.902632E+00, -.483387E+00}, {-.910993E+00, -.500657E+00}, + {-.918721E+00, -.517137E+00}, {-.925829E+00, -.532793E+00}, + {-.932330E+00, -.547586E+00}, {-.938238E+00, -.561482E+00}, + {-.943571E+00, -.574444E+00}, {-.948345E+00, -.586436E+00}, + {-.952581E+00, -.597421E+00}, {-.956297E+00, -.607364E+00}, + {-.959515E+00, -.616231E+00}, {-.962254E+00, -.623991E+00}, + {-.964533E+00, -.630613E+00}, {-.966371E+00, -.636071E+00}, + {-.967782E+00, -.640341E+00}, {-.968781E+00, -.643405E+00}, + {-.969376E+00, -.645249E+00}, {-.969573E+00, -.645865E+00}, + }, + { + {0.694091E+00, 0.974403E+00}, {0.693451E+00, 0.974202E+00}, + {0.691534E+00, 0.973600E+00}, {0.688352E+00, 0.972586E+00}, + {0.683918E+00, 0.971151E+00}, {0.678257E+00, 0.969278E+00}, + {0.671393E+00, 0.966949E+00}, {0.663357E+00, 0.964141E+00}, + {0.654182E+00, 0.960833E+00}, {0.643906E+00, 0.957002E+00}, + {0.632563E+00, 0.952624E+00}, {0.620193E+00, 0.947678E+00}, + {0.606834E+00, 0.942142E+00}, {0.592524E+00, 0.935997E+00}, + {0.577301E+00, 0.929226E+00}, {0.561203E+00, 0.921813E+00}, + {0.544267E+00, 0.913745E+00}, {0.526529E+00, 0.905011E+00}, + {0.508024E+00, 0.895602E+00}, {0.488788E+00, 0.885511E+00}, + {0.468854E+00, 0.874733E+00}, {0.448257E+00, 0.863267E+00}, + {0.427029E+00, 0.851111E+00}, {0.405203E+00, 0.838265E+00}, + {0.382812E+00, 0.824733E+00}, {0.359887E+00, 0.810519E+00}, + {0.336459E+00, 0.795629E+00}, {0.312560E+00, 0.780071E+00}, + {0.288221E+00, 0.763852E+00}, {0.263473E+00, 0.746983E+00}, + {0.238346E+00, 0.729475E+00}, {0.212871E+00, 0.711341E+00}, + {0.187078E+00, 0.692594E+00}, {0.160998E+00, 0.673249E+00}, + {0.134660E+00, 0.653321E+00}, {0.108095E+00, 0.632827E+00}, + {0.813325E-01, 0.611785E+00}, {0.544023E-01, 0.590212E+00}, + {0.273340E-01, 0.568128E+00}, {0.157188E-03, 0.545553E+00}, + {-.270987E-01, 0.522508E+00}, {-.544045E-01, 0.499014E+00}, + {-.817310E-01, 0.475093E+00}, {-.109049E+00, 0.450768E+00}, + {-.136331E+00, 0.426062E+00}, {-.163547E+00, 0.400999E+00}, + {-.190670E+00, 0.375603E+00}, {-.217672E+00, 0.349900E+00}, + {-.244524E+00, 0.323914E+00}, {-.271200E+00, 0.297672E+00}, + {-.297672E+00, 0.271200E+00}, {-.323914E+00, 0.244524E+00}, + {-.349900E+00, 0.217672E+00}, {-.375603E+00, 0.190670E+00}, + {-.400999E+00, 0.163547E+00}, {-.426062E+00, 0.136331E+00}, + {-.450768E+00, 0.109049E+00}, {-.475093E+00, 0.817310E-01}, + {-.499014E+00, 0.544045E-01}, {-.522508E+00, 0.270987E-01}, + {-.545553E+00, -.157188E-03}, {-.568128E+00, -.273340E-01}, + {-.590212E+00, -.544023E-01}, {-.611785E+00, -.813325E-01}, + {-.632827E+00, -.108095E+00}, {-.653321E+00, -.134660E+00}, + {-.673249E+00, -.160998E+00}, {-.692594E+00, -.187078E+00}, + {-.711341E+00, -.212871E+00}, {-.729475E+00, -.238346E+00}, + {-.746983E+00, -.263473E+00}, {-.763852E+00, -.288221E+00}, + {-.780071E+00, -.312560E+00}, {-.795629E+00, -.336459E+00}, + {-.810519E+00, -.359887E+00}, {-.824733E+00, -.382812E+00}, + {-.838265E+00, -.405203E+00}, {-.851111E+00, -.427029E+00}, + {-.863267E+00, -.448257E+00}, {-.874733E+00, -.468854E+00}, + {-.885511E+00, -.488788E+00}, {-.895602E+00, -.508024E+00}, + {-.905011E+00, -.526529E+00}, {-.913745E+00, -.544267E+00}, + {-.921813E+00, -.561203E+00}, {-.929226E+00, -.577301E+00}, + {-.935997E+00, -.592524E+00}, {-.942142E+00, -.606834E+00}, + {-.947678E+00, -.620193E+00}, {-.952624E+00, -.632563E+00}, + {-.957002E+00, -.643906E+00}, {-.960833E+00, -.654182E+00}, + {-.964141E+00, -.663357E+00}, {-.966949E+00, -.671393E+00}, + {-.969278E+00, -.678257E+00}, {-.971151E+00, -.683918E+00}, + {-.972586E+00, -.688352E+00}, {-.973600E+00, -.691534E+00}, + {-.974202E+00, -.693451E+00}, {-.974403E+00, -.694091E+00}, + }, + { + {0.745890E+00, 0.979316E+00}, {0.745224E+00, 0.979114E+00}, + {0.743231E+00, 0.978503E+00}, {0.739921E+00, 0.977473E+00}, + {0.735316E+00, 0.976011E+00}, {0.729440E+00, 0.974096E+00}, + {0.722325E+00, 0.971705E+00}, {0.714006E+00, 0.968812E+00}, + {0.704522E+00, 0.965390E+00}, {0.693911E+00, 0.961413E+00}, + {0.682215E+00, 0.956854E+00}, {0.669476E+00, 0.951688E+00}, + {0.655733E+00, 0.945894E+00}, {0.641027E+00, 0.939450E+00}, + {0.625397E+00, 0.932338E+00}, {0.608882E+00, 0.924543E+00}, + {0.591518E+00, 0.916053E+00}, {0.573342E+00, 0.906856E+00}, + {0.554390E+00, 0.896947E+00}, {0.534695E+00, 0.886318E+00}, + {0.514291E+00, 0.874966E+00}, {0.493212E+00, 0.862892E+00}, + {0.471490E+00, 0.850095E+00}, {0.449157E+00, 0.836578E+00}, + {0.426245E+00, 0.822346E+00}, {0.402784E+00, 0.807405E+00}, + {0.378806E+00, 0.791763E+00}, {0.354341E+00, 0.775429E+00}, + {0.329421E+00, 0.758413E+00}, {0.304075E+00, 0.740727E+00}, + {0.278334E+00, 0.722384E+00}, {0.252228E+00, 0.703398E+00}, + {0.225788E+00, 0.683785E+00}, {0.199044E+00, 0.663560E+00}, + {0.172025E+00, 0.642740E+00}, {0.144762E+00, 0.621344E+00}, + {0.117285E+00, 0.599391E+00}, {0.896242E-01, 0.576901E+00}, + {0.618091E-01, 0.553893E+00}, {0.338696E-01, 0.530390E+00}, + {0.583553E-02, 0.506413E+00}, {-.222633E-01, 0.481986E+00}, + {-.503975E-01, 0.457130E+00}, {-.785374E-01, 0.431871E+00}, + {-.106654E+00, 0.406233E+00}, {-.134718E+00, 0.380240E+00}, + {-.162700E+00, 0.353919E+00}, {-.190573E+00, 0.327295E+00}, + {-.218306E+00, 0.300395E+00}, {-.245873E+00, 0.273245E+00}, + {-.273245E+00, 0.245873E+00}, {-.300395E+00, 0.218306E+00}, + {-.327295E+00, 0.190573E+00}, {-.353919E+00, 0.162700E+00}, + {-.380240E+00, 0.134718E+00}, {-.406233E+00, 0.106654E+00}, + {-.431871E+00, 0.785374E-01}, {-.457130E+00, 0.503975E-01}, + {-.481986E+00, 0.222633E-01}, {-.506413E+00, -.583553E-02}, + {-.530390E+00, -.338696E-01}, {-.553893E+00, -.618091E-01}, + {-.576901E+00, -.896242E-01}, {-.599391E+00, -.117285E+00}, + {-.621344E+00, -.144762E+00}, {-.642740E+00, -.172025E+00}, + {-.663560E+00, -.199044E+00}, {-.683785E+00, -.225788E+00}, + {-.703398E+00, -.252228E+00}, {-.722384E+00, -.278334E+00}, + {-.740727E+00, -.304075E+00}, {-.758413E+00, -.329421E+00}, + {-.775429E+00, -.354341E+00}, {-.791763E+00, -.378806E+00}, + {-.807405E+00, -.402784E+00}, {-.822346E+00, -.426245E+00}, + {-.836578E+00, -.449157E+00}, {-.850095E+00, -.471490E+00}, + {-.862892E+00, -.493212E+00}, {-.874966E+00, -.514291E+00}, + {-.886318E+00, -.534695E+00}, {-.896947E+00, -.554390E+00}, + {-.906856E+00, -.573342E+00}, {-.916053E+00, -.591518E+00}, + {-.924543E+00, -.608882E+00}, {-.932338E+00, -.625397E+00}, + {-.939450E+00, -.641027E+00}, {-.945894E+00, -.655733E+00}, + {-.951688E+00, -.669476E+00}, {-.956854E+00, -.682215E+00}, + {-.961413E+00, -.693911E+00}, {-.965390E+00, -.704522E+00}, + {-.968812E+00, -.714006E+00}, {-.971705E+00, -.722325E+00}, + {-.974096E+00, -.729440E+00}, {-.976011E+00, -.735316E+00}, + {-.977473E+00, -.739921E+00}, {-.978503E+00, -.743231E+00}, + {-.979114E+00, -.745224E+00}, {-.979316E+00, -.745890E+00}, + }, + { + {0.801743E+00, 0.984323E+00}, {0.801049E+00, 0.984118E+00}, + {0.798972E+00, 0.983498E+00}, {0.795529E+00, 0.982450E+00}, + {0.790743E+00, 0.980954E+00}, {0.784647E+00, 0.978984E+00}, + {0.777280E+00, 0.976510E+00}, {0.768682E+00, 0.973500E+00}, + {0.758898E+00, 0.969922E+00}, {0.747974E+00, 0.965744E+00}, + {0.735954E+00, 0.960936E+00}, {0.722882E+00, 0.955472E+00}, + {0.708800E+00, 0.949326E+00}, {0.693750E+00, 0.942480E+00}, + {0.677772E+00, 0.934915E+00}, {0.660903E+00, 0.926616E+00}, + {0.643179E+00, 0.917573E+00}, {0.624637E+00, 0.907778E+00}, + {0.605309E+00, 0.897225E+00}, {0.585230E+00, 0.885911E+00}, + {0.564431E+00, 0.873835E+00}, {0.542944E+00, 0.860998E+00}, + {0.520801E+00, 0.847405E+00}, {0.498031E+00, 0.833060E+00}, + {0.474666E+00, 0.817971E+00}, {0.450735E+00, 0.802145E+00}, + {0.426269E+00, 0.785593E+00}, {0.401298E+00, 0.768327E+00}, + {0.375850E+00, 0.750358E+00}, {0.349957E+00, 0.731702E+00}, + {0.323648E+00, 0.712372E+00}, {0.296953E+00, 0.692385E+00}, + {0.269901E+00, 0.671759E+00}, {0.242523E+00, 0.650510E+00}, + {0.214849E+00, 0.628659E+00}, {0.186908E+00, 0.606224E+00}, + {0.158732E+00, 0.583227E+00}, {0.130349E+00, 0.559689E+00}, + {0.101790E+00, 0.535632E+00}, {0.730852E-01, 0.511080E+00}, + {0.442649E-01, 0.486055E+00}, {0.153589E-01, 0.460581E+00}, + {-.136027E-01, 0.434684E+00}, {-.425898E-01, 0.408388E+00}, + {-.715726E-01, 0.381720E+00}, {-.100521E+00, 0.354705E+00}, + {-.129406E+00, 0.327371E+00}, {-.158198E+00, 0.299744E+00}, + {-.186868E+00, 0.271852E+00}, {-.215386E+00, 0.243723E+00}, + {-.243723E+00, 0.215386E+00}, {-.271852E+00, 0.186868E+00}, + {-.299744E+00, 0.158198E+00}, {-.327371E+00, 0.129406E+00}, + {-.354705E+00, 0.100521E+00}, {-.381720E+00, 0.715726E-01}, + {-.408388E+00, 0.425898E-01}, {-.434684E+00, 0.136027E-01}, + {-.460581E+00, -.153589E-01}, {-.486055E+00, -.442649E-01}, + {-.511080E+00, -.730852E-01}, {-.535632E+00, -.101790E+00}, + {-.559689E+00, -.130349E+00}, {-.583227E+00, -.158732E+00}, + {-.606224E+00, -.186908E+00}, {-.628659E+00, -.214849E+00}, + {-.650510E+00, -.242523E+00}, {-.671759E+00, -.269901E+00}, + {-.692385E+00, -.296953E+00}, {-.712372E+00, -.323648E+00}, + {-.731702E+00, -.349957E+00}, {-.750358E+00, -.375850E+00}, + {-.768327E+00, -.401298E+00}, {-.785593E+00, -.426269E+00}, + {-.802145E+00, -.450735E+00}, {-.817971E+00, -.474666E+00}, + {-.833060E+00, -.498031E+00}, {-.847405E+00, -.520801E+00}, + {-.860998E+00, -.542944E+00}, {-.873835E+00, -.564431E+00}, + {-.885911E+00, -.585230E+00}, {-.897225E+00, -.605309E+00}, + {-.907778E+00, -.624637E+00}, {-.917573E+00, -.643179E+00}, + {-.926616E+00, -.660903E+00}, {-.934915E+00, -.677772E+00}, + {-.942480E+00, -.693750E+00}, {-.949326E+00, -.708800E+00}, + {-.955472E+00, -.722882E+00}, {-.960936E+00, -.735954E+00}, + {-.965744E+00, -.747974E+00}, {-.969922E+00, -.758898E+00}, + {-.973500E+00, -.768682E+00}, {-.976510E+00, -.777280E+00}, + {-.978984E+00, -.784647E+00}, {-.980954E+00, -.790743E+00}, + {-.982450E+00, -.795529E+00}, {-.983498E+00, -.798972E+00}, + {-.984118E+00, -.801049E+00}, {-.984323E+00, -.801743E+00}, + }, + { + {0.862222E+00, 0.989432E+00}, {0.861498E+00, 0.989224E+00}, + {0.859333E+00, 0.988593E+00}, {0.855750E+00, 0.987520E+00}, + {0.850782E+00, 0.985975E+00}, {0.844474E+00, 0.983921E+00}, + {0.836874E+00, 0.981318E+00}, {0.828035E+00, 0.978124E+00}, + {0.818008E+00, 0.974299E+00}, {0.806843E+00, 0.969806E+00}, + {0.794589E+00, 0.964614E+00}, {0.781291E+00, 0.958695E+00}, + {0.766991E+00, 0.952025E+00}, {0.751730E+00, 0.944586E+00}, + {0.735546E+00, 0.936362E+00}, {0.718472E+00, 0.927344E+00}, + {0.700544E+00, 0.917523E+00}, {0.681793E+00, 0.906896E+00}, + {0.662250E+00, 0.895461E+00}, {0.641946E+00, 0.883218E+00}, + {0.620910E+00, 0.870172E+00}, {0.599172E+00, 0.856326E+00}, + {0.576760E+00, 0.841689E+00}, {0.553703E+00, 0.826269E+00}, + {0.530029E+00, 0.810076E+00}, {0.505767E+00, 0.793122E+00}, + {0.480945E+00, 0.775420E+00}, {0.455592E+00, 0.756984E+00}, + {0.429736E+00, 0.737829E+00}, {0.403407E+00, 0.717972E+00}, + {0.376633E+00, 0.697431E+00}, {0.349444E+00, 0.676224E+00}, + {0.321869E+00, 0.654371E+00}, {0.293936E+00, 0.631891E+00}, + {0.265677E+00, 0.608805E+00}, {0.237121E+00, 0.585137E+00}, + {0.208298E+00, 0.560907E+00}, {0.179237E+00, 0.536141E+00}, + {0.149970E+00, 0.510860E+00}, {0.120526E+00, 0.485091E+00}, + {0.909360E-01, 0.458858E+00}, {0.612305E-01, 0.432187E+00}, + {0.314400E-01, 0.405104E+00}, {0.159478E-02, 0.377636E+00}, + {-.282745E-01, 0.349811E+00}, {-.581375E-01, 0.321655E+00}, + {-.879638E-01, 0.293198E+00}, {-.117723E+00, 0.264467E+00}, + {-.147385E+00, 0.235491E+00}, {-.176920E+00, 0.206299E+00}, + {-.206299E+00, 0.176920E+00}, {-.235491E+00, 0.147385E+00}, + {-.264467E+00, 0.117723E+00}, {-.293198E+00, 0.879638E-01}, + {-.321655E+00, 0.581375E-01}, {-.349811E+00, 0.282745E-01}, + {-.377636E+00, -.159478E-02}, {-.405104E+00, -.314400E-01}, + {-.432187E+00, -.612305E-01}, {-.458858E+00, -.909360E-01}, + {-.485091E+00, -.120526E+00}, {-.510860E+00, -.149970E+00}, + {-.536141E+00, -.179237E+00}, {-.560907E+00, -.208298E+00}, + {-.585137E+00, -.237121E+00}, {-.608805E+00, -.265677E+00}, + {-.631891E+00, -.293936E+00}, {-.654371E+00, -.321869E+00}, + {-.676224E+00, -.349444E+00}, {-.697431E+00, -.376633E+00}, + {-.717972E+00, -.403407E+00}, {-.737829E+00, -.429736E+00}, + {-.756984E+00, -.455592E+00}, {-.775420E+00, -.480945E+00}, + {-.793122E+00, -.505767E+00}, {-.810076E+00, -.530029E+00}, + {-.826269E+00, -.553703E+00}, {-.841689E+00, -.576760E+00}, + {-.856326E+00, -.599172E+00}, {-.870172E+00, -.620910E+00}, + {-.883218E+00, -.641946E+00}, {-.895461E+00, -.662250E+00}, + {-.906896E+00, -.681793E+00}, {-.917523E+00, -.700544E+00}, + {-.927344E+00, -.718472E+00}, {-.936362E+00, -.735546E+00}, + {-.944586E+00, -.751730E+00}, {-.952025E+00, -.766991E+00}, + {-.958695E+00, -.781291E+00}, {-.964614E+00, -.794589E+00}, + {-.969806E+00, -.806843E+00}, {-.974299E+00, -.818008E+00}, + {-.978124E+00, -.828035E+00}, {-.981318E+00, -.836874E+00}, + {-.983921E+00, -.844474E+00}, {-.985975E+00, -.850782E+00}, + {-.987520E+00, -.855750E+00}, {-.988593E+00, -.859333E+00}, + {-.989224E+00, -.861498E+00}, {-.989432E+00, -.862222E+00}, + }, + { + {0.928024E+00, 0.994654E+00}, {0.927267E+00, 0.994442E+00}, + {0.925012E+00, 0.993795E+00}, {0.921298E+00, 0.992674E+00}, + {0.916184E+00, 0.991026E+00}, {0.909737E+00, 0.988790E+00}, + {0.902025E+00, 0.985907E+00}, {0.893114E+00, 0.982323E+00}, + {0.883060E+00, 0.977993E+00}, {0.871915E+00, 0.972880E+00}, + {0.859725E+00, 0.966955E+00}, {0.846530E+00, 0.960197E+00}, + {0.832364E+00, 0.952588E+00}, {0.817261E+00, 0.944120E+00}, + {0.801251E+00, 0.934786E+00}, {0.784361E+00, 0.924583E+00}, + {0.766620E+00, 0.913511E+00}, {0.748052E+00, 0.901574E+00}, + {0.728683E+00, 0.888777E+00}, {0.708538E+00, 0.875127E+00}, + {0.687643E+00, 0.860632E+00}, {0.666022E+00, 0.845305E+00}, + {0.643700E+00, 0.829156E+00}, {0.620704E+00, 0.812200E+00}, + {0.597057E+00, 0.794450E+00}, {0.572788E+00, 0.775922E+00}, + {0.547921E+00, 0.756634E+00}, {0.522483E+00, 0.736603E+00}, + {0.496502E+00, 0.715848E+00}, {0.470004E+00, 0.694388E+00}, + {0.443018E+00, 0.672244E+00}, {0.415572E+00, 0.649438E+00}, + {0.387694E+00, 0.625991E+00}, {0.359412E+00, 0.601926E+00}, + {0.330757E+00, 0.577268E+00}, {0.301757E+00, 0.552040E+00}, + {0.272443E+00, 0.526267E+00}, {0.242843E+00, 0.499974E+00}, + {0.212989E+00, 0.473188E+00}, {0.182911E+00, 0.445936E+00}, + {0.152639E+00, 0.418244E+00}, {0.122204E+00, 0.390139E+00}, + {0.916369E-01, 0.361652E+00}, {0.609690E-01, 0.332808E+00}, + {0.302312E-01, 0.303638E+00}, {-.545488E-03, 0.274171E+00}, + {-.313299E-01, 0.244436E+00}, {-.620910E-01, 0.214463E+00}, + {-.927977E-01, 0.184282E+00}, {-.123419E+00, 0.153924E+00}, + {-.153924E+00, 0.123419E+00}, {-.184282E+00, 0.927977E-01}, + {-.214463E+00, 0.620910E-01}, {-.244436E+00, 0.313299E-01}, + {-.274171E+00, 0.545488E-03}, {-.303638E+00, -.302312E-01}, + {-.332808E+00, -.609690E-01}, {-.361652E+00, -.916369E-01}, + {-.390139E+00, -.122204E+00}, {-.418244E+00, -.152639E+00}, + {-.445936E+00, -.182911E+00}, {-.473188E+00, -.212989E+00}, + {-.499974E+00, -.242843E+00}, {-.526267E+00, -.272443E+00}, + {-.552040E+00, -.301757E+00}, {-.577268E+00, -.330757E+00}, + {-.601926E+00, -.359412E+00}, {-.625991E+00, -.387694E+00}, + {-.649438E+00, -.415572E+00}, {-.672244E+00, -.443018E+00}, + {-.694388E+00, -.470004E+00}, {-.715848E+00, -.496502E+00}, + {-.736603E+00, -.522483E+00}, {-.756634E+00, -.547921E+00}, + {-.775922E+00, -.572788E+00}, {-.794450E+00, -.597057E+00}, + {-.812200E+00, -.620704E+00}, {-.829156E+00, -.643700E+00}, + {-.845305E+00, -.666022E+00}, {-.860632E+00, -.687643E+00}, + {-.875127E+00, -.708538E+00}, {-.888777E+00, -.728683E+00}, + {-.901574E+00, -.748052E+00}, {-.913511E+00, -.766620E+00}, + {-.924583E+00, -.784361E+00}, {-.934786E+00, -.801251E+00}, + {-.944120E+00, -.817261E+00}, {-.952588E+00, -.832364E+00}, + {-.960197E+00, -.846530E+00}, {-.966955E+00, -.859725E+00}, + {-.972880E+00, -.871915E+00}, {-.977993E+00, -.883060E+00}, + {-.982323E+00, -.893114E+00}, {-.985907E+00, -.902025E+00}, + {-.988790E+00, -.909737E+00}, {-.991026E+00, -.916184E+00}, + {-.992674E+00, -.921298E+00}, {-.993795E+00, -.925012E+00}, + {-.994442E+00, -.927267E+00}, {-.994654E+00, -.928024E+00}, + }, + { + {0.100000E+01, 0.100000E+01}, {0.999496E+00, 0.999497E+00}, + {0.997986E+00, 0.997988E+00}, {0.995471E+00, 0.995474E+00}, + {0.991954E+00, 0.991957E+00}, {0.987438E+00, 0.987441E+00}, + {0.981925E+00, 0.981930E+00}, {0.975428E+00, 0.975432E+00}, + {0.967946E+00, 0.967950E+00}, {0.959492E+00, 0.959496E+00}, + {0.950069E+00, 0.950072E+00}, {0.939691E+00, 0.939694E+00}, + {0.928363E+00, 0.928371E+00}, {0.916105E+00, 0.916110E+00}, + {0.902924E+00, 0.902933E+00}, {0.888833E+00, 0.888840E+00}, + {0.873844E+00, 0.873852E+00}, {0.857979E+00, 0.857985E+00}, + {0.841248E+00, 0.841256E+00}, {0.823674E+00, 0.823681E+00}, + {0.805268E+00, 0.805275E+00}, {0.786048E+00, 0.786056E+00}, + {0.766043E+00, 0.766047E+00}, {0.745258E+00, 0.745268E+00}, + {0.723730E+00, 0.723742E+00}, {0.701471E+00, 0.701482E+00}, + {0.678504E+00, 0.678512E+00}, {0.654854E+00, 0.654864E+00}, + {0.630550E+00, 0.630557E+00}, {0.605605E+00, 0.605612E+00}, + {0.580055E+00, 0.580058E+00}, {0.553917E+00, 0.553925E+00}, + {0.527218E+00, 0.527229E+00}, {0.499998E+00, 0.500005E+00}, + {0.472268E+00, 0.472277E+00}, {0.444064E+00, 0.444072E+00}, + {0.415409E+00, 0.415418E+00}, {0.386342E+00, 0.386347E+00}, + {0.356884E+00, 0.356891E+00}, {0.327066E+00, 0.327072E+00}, + {0.296919E+00, 0.296923E+00}, {0.266471E+00, 0.266479E+00}, + {0.235751E+00, 0.235763E+00}, {0.204804E+00, 0.204812E+00}, + {0.173646E+00, 0.173653E+00}, {0.142312E+00, 0.142316E+00}, + {0.110836E+00, 0.110839E+00}, {0.792484E-01, 0.792530E-01}, + {0.475798E-01, 0.475862E-01}, {0.158645E-01, 0.158688E-01}, + {-.158676E-01, -.158652E-01}, {-.475839E-01, -.475809E-01}, + {-.792525E-01, -.792449E-01}, {-.110840E+00, -.110834E+00}, + {-.142317E+00, -.142310E+00}, {-.173652E+00, -.173646E+00}, + {-.204809E+00, -.204805E+00}, {-.235763E+00, -.235757E+00}, + {-.266475E+00, -.266471E+00}, {-.296923E+00, -.296916E+00}, + {-.327070E+00, -.327065E+00}, {-.356889E+00, -.356881E+00}, + {-.386349E+00, -.386338E+00}, {-.415418E+00, -.415409E+00}, + {-.444070E+00, -.444060E+00}, {-.472275E+00, -.472263E+00}, + {-.500002E+00, -.499995E+00}, {-.527232E+00, -.527222E+00}, + {-.553923E+00, -.553919E+00}, {-.580061E+00, -.580055E+00}, + {-.605612E+00, -.605604E+00}, {-.630557E+00, -.630550E+00}, + {-.654866E+00, -.654858E+00}, {-.678513E+00, -.678508E+00}, + {-.701480E+00, -.701472E+00}, {-.723742E+00, -.723730E+00}, + {-.745270E+00, -.745262E+00}, {-.766048E+00, -.766042E+00}, + {-.786055E+00, -.786048E+00}, {-.805275E+00, -.805268E+00}, + {-.823681E+00, -.823674E+00}, {-.841256E+00, -.841248E+00}, + {-.857985E+00, -.857979E+00}, {-.873852E+00, -.873844E+00}, + {-.888838E+00, -.888831E+00}, {-.902929E+00, -.902921E+00}, + {-.916111E+00, -.916103E+00}, {-.928372E+00, -.928366E+00}, + {-.939697E+00, -.939690E+00}, {-.950074E+00, -.950070E+00}, + {-.959496E+00, -.959492E+00}, {-.967950E+00, -.967946E+00}, + {-.975432E+00, -.975428E+00}, {-.981931E+00, -.981927E+00}, + {-.987441E+00, -.987438E+00}, {-.991957E+00, -.991954E+00}, + {-.995474E+00, -.995471E+00}, {-.997987E+00, -.997985E+00}, + {-.999497E+00, -.999496E+00}, {-.100000E+01, -.100000E+01}, + }, +}; + +#endif /* #define RT_RIEMANN_HLL_EIGENVALUES_H */ diff --git a/src/rt/KIARA/rt_slope_limiters_cell.h b/src/rt/KIARA/rt_slope_limiters_cell.h new file mode 100644 index 0000000000..5c032b1284 --- /dev/null +++ b/src/rt/KIARA/rt_slope_limiters_cell.h @@ -0,0 +1,162 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_SLOPE_LIMITERS_CELL_KIARA_H +#define SWIFT_RT_SLOPE_LIMITERS_CELL_KIARA_H + +/** + * @file src/rt/KIARA/rt_slope_limiters_cell.h + * @brief File containing routines concerning the cell slope + * limiter for the KIARA RT scheme. (= fist slope limiting step + * that limits gradients such that they don't predict new extrema + * at neighbour praticle's positions ) + * + * The Gizmo-style slope limiter doesn't help for RT problems for + * now, so nothing in this file should actually be called. + * */ + +/** + * @brief Initialize variables for the cell wide slope limiter + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void rt_slope_limit_cell_init( + struct part* p) { + + for (int g = 0; g < RT_NGROUPS; g++) { + p->rt_data.limiter[g].energy_density[0] = FLT_MAX; + p->rt_data.limiter[g].energy_density[1] = -FLT_MAX; + p->rt_data.limiter[g].flux[0][0] = FLT_MAX; + p->rt_data.limiter[g].flux[0][1] = -FLT_MAX; + p->rt_data.limiter[g].flux[1][0] = FLT_MAX; + p->rt_data.limiter[g].flux[1][1] = -FLT_MAX; + p->rt_data.limiter[g].flux[2][0] = FLT_MAX; + p->rt_data.limiter[g].flux[2][1] = -FLT_MAX; + } + + /* just use the hydro one */ + /* p->limiter.maxr = -FLT_MAX; */ +} + +/** + * @brief Collect information for the cell wide slope limiter during the + * neighbour loop. We need the min and max value of densities of conserved + * quantities amongst all neighbours of particle pi. + * + * @param pi Particle i. + * @param pj Particle j. + * @param g index of photon group + */ +__attribute__((always_inline)) INLINE static void rt_slope_limit_cell_collect( + struct part* restrict pi, struct part* restrict pj, int g) { + + struct rt_part_data* rtdi = &pi->rt_data; + struct rt_part_data* rtdj = &pj->rt_data; + + /* basic slope limiter: collect the maximal and the minimal value for the + * primitive variables among the ngbs */ + rtdi->limiter[g].energy_density[0] = min(rtdj->radiation[g].energy_density, + rtdi->limiter[g].energy_density[0]); + rtdi->limiter[g].energy_density[1] = max(rtdj->radiation[g].energy_density, + rtdi->limiter[g].energy_density[1]); + + rtdi->limiter[g].flux[0][0] = + min(rtdj->radiation[g].flux[0], rtdi->limiter[g].flux[0][0]); + rtdi->limiter[g].flux[0][1] = + max(rtdj->radiation[g].flux[0], rtdi->limiter[g].flux[0][1]); + rtdi->limiter[g].flux[1][0] = + min(rtdj->radiation[g].flux[1], rtdi->limiter[g].flux[1][0]); + rtdi->limiter[g].flux[1][1] = + max(rtdj->radiation[g].flux[1], rtdi->limiter[g].flux[1][1]); + rtdi->limiter[g].flux[2][0] = + min(rtdj->radiation[g].flux[2], rtdi->limiter[g].flux[2][0]); + rtdi->limiter[g].flux[2][1] = + max(rtdj->radiation[g].flux[2], rtdi->limiter[g].flux[2][1]); + /* just use the hydro one */ + /* pi->limiter.maxr = max(r, pi->limiter.maxr); */ +} + +/** + * @brief Slope-limit the given quantity. Result will be written directly + * to float gradient[3]. + * + * @param gradient the gradient of the quantity + * @param maxr maximal distance to any neighbour of the particle + * @param value the current value of the quantity + * @param valmin the minimal value amongst all neighbours of the quantity + * @param valmax the maximal value amongst all neighbours of the quantity + */ +__attribute__((always_inline)) INLINE static void rt_slope_limit_quantity( + float gradient[3], const float maxr, const float value, const float valmin, + const float valmax) { + + float gradtrue = sqrtf(gradient[0] * gradient[0] + gradient[1] * gradient[1] + + gradient[2] * gradient[2]); + if (gradtrue != 0.0f) { + gradtrue *= maxr; + const float gradtrue_inv = 1.0f / gradtrue; + const float gradmax = valmax - value; + const float gradmin = valmin - value; + const float beta = 1.f; /* TODO: test for best value here. For now, take + stability over diffusivity. */ + const float min_temp = + min(gradmax * gradtrue_inv, gradmin * gradtrue_inv) * beta; + const float alpha = min(1.f, min_temp); + gradient[0] *= alpha; + gradient[1] *= alpha; + gradient[2] *= alpha; + } +} + +/** + * @brief Slope limit cell gradients. + * This is done in rt_gradients_finalise(). + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void rt_slope_limit_cell( + struct part* p) { + + const float maxr = p->limiter.maxr; + struct rt_part_data* rtd = &p->rt_data; + + for (int g = 0; g < RT_NGROUPS; g++) { + rt_slope_limit_quantity(/*gradient=*/rtd->gradient[g].energy_density, + /*maxr= */ maxr, + /*value= */ rtd->radiation[g].energy_density, + /*valmin= */ rtd->limiter[g].energy_density[0], + /*valmax= */ rtd->limiter[g].energy_density[1]); + rt_slope_limit_quantity(/*gradient=*/rtd->gradient[g].flux[0], + /*maxr= */ maxr, + /*value= */ rtd->radiation[g].flux[0], + /*valmin= */ rtd->limiter[g].flux[0][0], + /*valmax= */ rtd->limiter[g].flux[0][1]); + rt_slope_limit_quantity(/*gradient=*/rtd->gradient[g].flux[1], + /*maxr= */ maxr, + /*value= */ rtd->radiation[g].flux[1], + /*valmin= */ rtd->limiter[g].flux[1][0], + /*valmax= */ rtd->limiter[g].flux[1][1]); + rt_slope_limit_quantity(/*gradient=*/rtd->gradient[g].flux[2], + /*maxr= */ maxr, + /*value= */ rtd->radiation[g].flux[2], + /*valmin= */ rtd->limiter[g].flux[2][0], + /*valmax= */ rtd->limiter[g].flux[2][1]); + } +} +#endif /* SWIFT_RT_SLOPE_LIMITERS_CELL_KIARA_H */ diff --git a/src/rt/KIARA/rt_slope_limiters_face.h b/src/rt/KIARA/rt_slope_limiters_face.h new file mode 100644 index 0000000000..a8aa42cdff --- /dev/null +++ b/src/rt/KIARA/rt_slope_limiters_face.h @@ -0,0 +1,215 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_SLOPE_LIMITERS_FACE_KIARA_H +#define SWIFT_RT_SLOPE_LIMITERS_FACE_KIARA_H + +#include "sign.h" + +/** + * @file src/rt/KIARA/rt_slope_limiters_face.h + * @brief File containing routines concerning the face slope + * limiter for the KIARA RT scheme, i.e. limiting the actual + * interparticle flux. */ + +/** + * @brief Slope limit a single quantity at the interface. CURRENTLY NOT IN USE. + * + * @param phi_i Value of the quantity at the particle position. + * @param phi_j Value of the quantity at the neighbouring particle position. + * @param phi_mid0 Extrapolated value of the quantity at the interface position. + * @param xij_norm Distance between the particle position and the interface + * position. + * @param r_inv Inverse distance between the particle and its neighbour. + * @return The slope limited difference between the quantity at the particle + * position and the quantity at the interface position. + */ +__attribute__((always_inline)) INLINE static float rt_slope_limit_face_quantity( + float phi_i, float phi_j, float phi_mid0, float xij_norm, float r_inv) { + + const float dphi = phi_i - phi_j; + const float gamma1 = 0.5f; + const float gamma2 = 0.25f; + + const float delta1 = gamma1 * fabsf(dphi); + const float delta2 = gamma2 * fabsf(dphi); + + const float phimin = min(phi_i, phi_j); + const float phimax = max(phi_i, phi_j); + + const float phibar = phi_i + xij_norm * r_inv * dphi; + + float phiplus, phiminus, phi_mid; + + if (same_signf(phimax + delta1, phimax)) { + phiplus = phimax + delta1; + } else { + phiplus = phimax / (1.0f + delta1 / (fabsf(phimax) + FLT_MIN)); + } + + if (same_signf(phimin - delta1, phimin)) { + phiminus = phimin - delta1; + } else { + phiminus = phimin / (1.0f + delta1 / (fabsf(phimin) + FLT_MIN)); + } + + if (phi_i < phi_j) { + const float temp = min(phibar + delta2, phi_mid0); + phi_mid = max(phiminus, temp); + } else { + const float temp = max(phibar - delta2, phi_mid0); + phi_mid = min(phiplus, temp); + } + + return phi_mid - phi_i; +} + +/** + * the minmod slope limiter. + * + * @param dQi left slope + * @param dQj right slope + */ +__attribute__((always_inline)) INLINE static void rt_limiter_minmod( + float* dQi, float* dQj) { + + if (*dQi * *dQj > 0.f) { + if (fabsf(*dQi) < fabsf(*dQj)) { + *dQj = *dQi; + } else { + *dQi = *dQj; + } + } else { + *dQi = 0.f; + *dQj = 0.f; + } +} + +/** + * the monotonized cenetral limiter. + * + * @param dQi left slope + * @param dQj right slope + * @return factor to slope limit the slope dQi + */ +__attribute__((always_inline)) INLINE static float rt_limiter_mc( + const float dQi, const float dQj) { + + const float r = dQj == 0.f ? dQi * 1e6 : dQi / dQj; + const float minterm = min3(0.5 * (1. + r), 2.f, 2.f * r); + return max(0.f, minterm); +} + +/** + * the van Leer limiter. + * + * @param dQi left slope + * @param dQj right slope + * @return factor to slope limit the slope dQi + */ +__attribute__((always_inline)) INLINE static float rt_limiter_vanLeer( + const float dQi, const float dQj) { + const float r = dQj == 0.f ? dQi * 1e6 : dQi / dQj; + const float absr = fabsf(r); + return (r + absr) / (1.f + absr); +} + +/** + * the superbee limiter. + * + * @param dQi left slope + * @param dQj right slope + * @return factor to slope limit the slope dQi + */ +__attribute__((always_inline)) INLINE static float rt_limiter_superbee( + const float dQi, const float dQj) { + const float r = dQj == 0.f ? dQi * 1e6 : dQi / dQj; + const float minterm1 = min(1.f, 2.f * r); + const float minterm2 = min(2.f, r); + return max3(0.f, minterm1, minterm2); +} + +/** + * @brief Slope limit the slopes at the interface between two particles + * + * @param Qi RT quantities of particle i (energy density + fluxes in 3 dim) + * @param Qj RT quantities of particle j (energy density + fluxes in 3 dim) + * @param dQi Difference between the RT quantities of particle i at the + * position of particle i and at the interface position. + * @param dQj Difference between the RT quantities of particle j at the + * position of particle j and at the interface position. + * @param dx Comoving distance vector between the particles (dx = pi->x - + * pj->x). + * @param r Comoving distance between particle i and particle j. + * @param xij_i Position of the "interface" w.r.t. position of particle i + * @param xij_j Position of the "interface" w.r.t. position of particle j + */ +__attribute__((always_inline)) INLINE static void rt_slope_limit_face( + const float Qi[4], const float Qj[4], float dQi[4], float dQj[4], + const float* dx, const float r, const float xij_i[3], + const float xij_j[3]) { + + /* In 1D advection tests, any limiter works with the + * GLF solver. If you also activate (uncomment) the + * cell slope limiting, you should avoid the superbee + * and MC limiters. The Gizmo-style clever limiter + * that is used for hydro leads to a more diffusive + * behaviour. + * For the HLL solver, avoid superbee and MC limiters, + * as they lead to unstable results. + */ + + /* const float xij_i_norm = */ + /* sqrtf(xij_i[0] * xij_i[0] + xij_i[1] * xij_i[1] + xij_i[2] * xij_i[2]); + */ + /* */ + /* const float xij_j_norm = */ + /* sqrtf(xij_j[0] * xij_j[0] + xij_j[1] * xij_j[1] + xij_j[2] * xij_j[2]); + */ + /* */ + /* const float r_inv = 1.f / r; */ + + for (int i = 0; i < 4; i++) { + + rt_limiter_minmod(&dQi[i], &dQj[i]); + + /* const float alphai = rt_limiter_mc(dQi[i], dQj[i]); */ + /* const float alphaj = rt_limiter_mc(dQj[i], dQi[i]); */ + /* dQi[i] *= alphai; */ + /* dQj[i] *= alphaj; */ + + /* const float alphai = rt_limiter_superbee(dQi[i], dQj[i]); */ + /* const float alphaj = rt_limiter_superbee(dQj[i], dQi[i]); */ + /* dQi[i] *= alphai; */ + /* dQj[i] *= alphaj; */ + + /* const float alphai = rt_limiter_vanLeer(dQi[i], dQj[i]); */ + /* const float alphaj = rt_limiter_vanLeer(dQj[i], dQi[i]); */ + /* dQi[i] *= alphai; */ + /* dQj[i] *= alphaj; */ + + /* dQi[i] = rt_slope_limit_face_quantity(Qi[i], Qj[i], Qi[i] + dQi[i], */ + /* xij_i_norm, r_inv); */ + /* */ + /* dQj[i] = rt_slope_limit_face_quantity(Qj[i], Qi[i], Qj[i] + dQj[i], */ + /* xij_j_norm, r_inv); */ + } +} + +#endif /* SWIFT_RT_SLOPE_LIMITERS_FACE_KIARA_H */ diff --git a/src/rt/KIARA/rt_species.h b/src/rt/KIARA/rt_species.h new file mode 100644 index 0000000000..2594d90149 --- /dev/null +++ b/src/rt/KIARA/rt_species.h @@ -0,0 +1,47 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_KIARA_SPECIES_H +#define SWIFT_RT_KIARA_SPECIES_H + +/** + * @file src/rt/KIARA/rt_species.h + * @brief header file concerning (ionizing) species. + **/ + +enum rt_ionizing_species { + rt_ionizing_species_HI = 0, + rt_ionizing_species_HeI, + rt_ionizing_species_HeII, + rt_ionizing_species_count +}; + +/** + * Get the ionizing energy in erg for all ionizing species. + * + * @param E_ion (return) the ionizing energies. + **/ +__attribute__((always_inline)) INLINE static void +rt_species_get_ionizing_energy(double E_ion[rt_ionizing_species_count]) { + + E_ion[rt_ionizing_species_HI] = 2.179e-11; /* 13.60 eV in erg */ + E_ion[rt_ionizing_species_HeI] = 3.940e-11; /* 24.59 eV in erg */ + E_ion[rt_ionizing_species_HeII] = 8.719e-11; /* 54.42 eV in erg */ +} + +#endif /* SWIFT_RT_KIARA_SPECIES_H */ diff --git a/src/rt/KIARA/rt_stellar_emission_model.h b/src/rt/KIARA/rt_stellar_emission_model.h new file mode 100644 index 0000000000..bff80dcda2 --- /dev/null +++ b/src/rt/KIARA/rt_stellar_emission_model.h @@ -0,0 +1,388 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_STELLAR_EMISSION_MODEL_KIARA_H +#define SWIFT_RT_STELLAR_EMISSION_MODEL_KIARA_H + +/** + * @file src/rt/KIARA/rt_stellar_emission_model.h + * @brief Main header file for the KIARA M1 closure radiative transfer scheme + * stellar radiation emission models. + */ + +enum rt_stellar_emission_models { + rt_stellar_emission_model_none = 0, + rt_stellar_emission_model_const, + rt_stellar_emission_model_IlievTest, + rt_stellar_emission_model_BPASS, + rt_stellar_emission_model_count +}; + +/** + * @brief Compute the energy emitted from a star during the time step dt. + * This is for the constant emission rate model. + * + * @param emission_this_step (return) the emitted radiation energy of a star + * during the time interval dt + * @param const_stellar_emission_rates the constant emission rates used in this + * run + * @param dt time step size (in internal units) + */ +__attribute__((always_inline)) INLINE static void +rt_get_emission_this_step_const( + double emission_this_step[RT_NGROUPS], + const double const_stellar_emission_rates[RT_NGROUPS], double dt) { + + /* The read-in constant stellar emisison rates are in units of L_sol. + * But they have been read in assuming they are in cgs. Convert this + * only now to proper internal units to avoid float overflows. We only + * store the energy that is to be distributed from this spart to its + * neighbours in this step in internal units.*/ + const double solar_luminosity = 3.828e33; /* erg/s */ + for (int g = 0; g < RT_NGROUPS; g++) { + const double emission_rate_internal_units = + const_stellar_emission_rates[g] * solar_luminosity; + emission_this_step[g] = emission_rate_internal_units * dt; + } +} + +/** + * @brief Compute the energy emitted from a star during the time step dt. + * This is for the Iliev+2006 Test 4. + * + * @param emission_this_step (return) the emitted radiation energy of a star + * during the time interval dt + * @param M the star mass (in internal units) + * @param dt time step size (in internal units) + * @param photon_number_integral Integrated photon numbers over frequency + * interval + * @param average_photon_energy average photon energy in each frequency bin, in + * erg + * @param phys_const struct holding physical constants + * @param internal_units units struct containing internal units + */ +__attribute__((always_inline)) INLINE static void +rt_get_emission_this_step_IlievTest( + double emission_this_step[RT_NGROUPS], float M, const double dt, + const double photon_number_integral[RT_NGROUPS], + const double average_photon_energy[RT_NGROUPS], + const struct phys_const* phys_const, + const struct unit_system* internal_units) { + + /* Note that this model uses the halo mass to determine the luminosity + * of a source. I'm cheating the system here by storing the required halo + * mass as the star mass. This is only ok because the test is supposed to + * run with all dynamics and gravity turned off. */ + + const double Omega_b = 0.043; + const double Omega_0 = 0.27; + const double m_p = phys_const->const_proton_mass; + const double t_s = 3e6 * phys_const->const_year; + const double f_gamma = 250.; + const double Ndot_gamma = (f_gamma * M * Omega_b) / (Omega_0 * m_p * t_s); + + double Nsum = 0.; + for (int g = 0; g < RT_NGROUPS; g++) Nsum += photon_number_integral[g]; + + if (Nsum <= 0.) error("No photons in spectrum...???"); + + const double energy_units = + units_cgs_conversion_factor(internal_units, UNIT_CONV_ENERGY); + for (int g = 0; g < RT_NGROUPS; g++) { + const double fi = photon_number_integral[g] / Nsum; + const double Ndot_i = fi * Ndot_gamma; + /* average photon densities are in cgs! */ + const double Edot_i = average_photon_energy[g] * Ndot_i / energy_units; + emission_this_step[g] = Edot_i * dt; + } +} + +/* + * @brief compute the index of a lower bound bin for a non-uniform spaced 1D array_x. + * It takes an array_x that indicate the positions, its size and a value x for which + * we wish to find the lower bound. It returns only the index of the lower bound bin. + * + * + * @param array_x Values of gridpoints + * @param size Length of array_x + * @param x Value within range of array_x for which we want to find the lower bound + */ +static int interpolate_1D_non_uniform_LowerBound(const double* array_x, + const int size, + const double x) { + + // Handle below bounds explicitly + if (x <= array_x[0]) + return 0; + + // Handle above bounds explicitly + if (x >= array_x[size - 1]) + return size - 2; + + // Find the lower bin: array_x[i] <= x < array_x[i+1] + for (int i = 0; i < size - 1; ++i) { + if (x < array_x[i + 1]) + return i; + } + + // Should not reach here + return size - 2; +} + +/* + * @brief compute an interpolated value from a 2d table (array_y) whose values are + * given on a non-uniformly spaced grids. The first dimension uses the outer bounds + * of the grid when given values below/above their min/max. The second dimension does the + * same for its max but gives an error when below their min. + * + * @param array_x1 Values of gridpoints over first dimension + * @param array_x2 Values of gridpoints over second dimension + * @param array_y Values of a 2D table that we interpolate, lengths must match size1 and size2 + * @param size1 Length of array_x1 + * @param size2 Length of array_x2 + * @param x1 Value within range of array_x1 to interpolate to + * here is specifically metallicity + * @param x2 Value within range of array_x2 to interpolate to + * here is specifically stellar age + */ + +static double interpolate_2D_non_uniform(const double* array_x1,const double* array_x2, + double** array_y, + const int size1, const int size2, + double x1, double x2) { + +// First we check the bounds and if x1 and x2 are within it +// return 0 if Time is above star age threshold, give error when below lowest bin value (should always be 0 though if properly binned) +// check for star age +/* TODO: add debug macro for the error check. */ + +if (array_y == NULL) { + printf("ERROR: array_y is NULL!\n"); + return 0; // Handle error properly +} + +if (x2 > array_x2[size2-1]){ + //printf("\n"); + //printf("x2 Has exceeded the upper bound = %f", x2); + //printf("\n"); + //printf("Setting it to: %f",array_x2[size2-1]); + x2 = array_x2[size2-1]; + +} +if (x2 < array_x2[0]){ // MAKE THIS A REAL ERROR + //printf("\n"); + //printf("x2 = %f", x2); + //printf("\n"); + //printf("x2 IS TOO LOW!!!"); + error("Stellar age %e is below 0!", x2); + //return 0; // MAKE THIS A REAL ERROR +} + +//check for metallicity +if (x1 < array_x1[0]){ + //printf("\n"); + //printf("x1 = %f", x1); + + x1 = array_x1[0]; + //printf("\n"); + //printf("x1 = %f", x1); +} + +if (x1 > array_x1[size1-1]){ + x1 = array_x1[size1-1]; +} +// Now we finished bound checks, consider doing this seperate? + +// First we want to find the 4 indices for the corner values. +// We get the lower index for the Metals +const int Ind1 = interpolate_1D_non_uniform_LowerBound(array_x1,size1,x1); +// Now we get the lower index for the Times +const int Ind2 = interpolate_1D_non_uniform_LowerBound(array_x2,size2,x2); + +if (Ind1 >= size1 - 1 || Ind2 >= size2 - 1) { + printf("Index out of bounds: Ind1=%d, Ind2=%d (size1=%d, size2=%d)\n", Ind1, Ind2, size1, size2); + printf("x1 = %e, x2 = %e\n", x1, x2); + exit(1); // or return a safe default +} + +if (Ind1 < 0 || Ind2 < 0) { + error("Interpolation index out of bounds! Ind1=%d Ind2=%d", Ind1, Ind2); +} + +double dx1 = array_x1[Ind1+1] - array_x1[Ind1]; +double dx2 = array_x2[Ind2+1] - array_x2[Ind2]; + +// Handle very small differences (avoid division by zero) +if (fabs(dx1) < 1e-10 || fabs(dx2) < 1e-10) { + printf("Warning: Small step sizes detected: dx1 = %g, dx2 = %g\n", dx1, dx2); + exit(1); // Or return a default value if this is recoverable +} + +// We know the values we need are at [Ind1,Ind2], [Ind1 + 1,Ind2], [Ind1,Ind2 + 1] and [Ind1 + 1,Ind2 + 1] +// We calculate the offsets over the first dimension +const double offset1 = + (array_x1[Ind1+1] - x1) / dx1; +// And now the second dimension +const double offset2 = + (array_x2[Ind2+1] - x2) / dx2; + +// First Interpolation: +// Interpolate [Ind1,Ind2] --- [Ind1+1,Ind2] and interpolate [Ind1,Ind2+1] --- [Ind1+1,Ind2+1] with offset1 to make 2 new values. + +if (array_y[Ind1] == NULL || array_y[Ind1+1] == NULL) { + printf("ERROR: array_y[%d] or array_y[%d] is NULL!\n", Ind1, Ind1+1); + exit(1); +} + +const double Interpol1 = offset1 * array_y[Ind1][Ind2] + (1. - offset1) * array_y[Ind1+1][Ind2]; +const double Interpol2 = offset1 * array_y[Ind1][Ind2+1] + (1. - offset1) * array_y[Ind1+1][Ind2+1]; + +const double result = offset2 * Interpol1 + (1. - offset2) * Interpol2; + +if (isnan(result) || isinf(result)) { + printf("Interpolation result is NaN or Inf\n"); + printf("Metallicity (x1) = %g, age (x2) = %g, offsets: %g, %g\n", x1, x2, offset1, offset2); + printf("array_y values: %g %g %g %g\n", + array_y[Ind1][Ind2], array_y[Ind1+1][Ind2], + array_y[Ind1][Ind2+1], array_y[Ind1+1][Ind2+1]); + exit(1); +} + +return result; +} + +/** + * @brief Compute the energy emitted from a star during the time step dt. + * This is using BPASS model with three photon bins. + * + * @param emission_this_step (return) the emitted radiation energy of a star + * during the time interval dt + * @param M the star mass (in internal units) + * @param Metallicity the stellar metallicity (in internal units) + * @param star_age_begin_of_step the star age at the beginning of this timestep + * (in internal units) + * @param star_age the star age at the end of this timestep(in internal units) + * @param ionizing_tables BPASS table that stores the total emitted photon number + * at different stellar age + * @param average_photon_energy average photon energy in each frequency bin, in + * erg + * @param phys_const struct holding physical constants + * @param internal_units units struct containing internal units + */ +__attribute__((always_inline)) INLINE static void +rt_get_emission_this_step_BPASS( + double emission_this_step[RT_NGROUPS], float M,float Metallicity, + double star_age_begin_of_step, double star_age, + double ***ionizing_tables, + const double average_photon_energy[RT_NGROUPS], + const struct phys_const* phys_const, + const struct unit_system* internal_units, + const double f_esc) { + + /* Get the array for the table range. + * TODO: Hard code in now, need to replace it. */ + static const double Metallicities[] = {1e-5, 1e-4, 0.01, 0.02, 0.03, 0.04, 0.06, 0.08, 0.1, 0.14, 0.2, 0.3, 0.4}; + int size_Metals = sizeof(Metallicities) / sizeof(Metallicities[0]); + static const double age_100Myr[] = { + 0.0, // 0 + 1.0, // 10^0 + 1.258925, // 10^0.1 + 1.584893, // 10^0.2 + 1.995262, // 10^0.3 + 2.511886, // 10^0.4 + 3.162278, // 10^0.5 + 3.981072, // 10^0.6 + 5.011872, // 10^0.7 + 6.309573, // 10^0.8 + 7.943282, // 10^0.9 + 10.0, // 10^1.0 + 12.589254, // 10^1.1 + 15.848932, // 10^1.2 + 19.952623, // 10^1.3 + 25.118864, // 10^1.4 + 31.622777, // 10^1.5 + 39.810717, // 10^1.6 + 50.118723, // 10^1.7 + 63.095734, // 10^1.8 + 79.432823, // 10^1.9 + 100.0 // 10^2.0 + }; + int size_Times = sizeof(age_100Myr) / sizeof(age_100Myr[0]); + const double normalized_mass = 1; //units of solar mass + + /* Convert some quantities. */ + const double time_to_Myr = units_cgs_conversion_factor(internal_units, UNIT_CONV_TIME) / + (365.25f * 24.f * 60.f * 60.f * 1e6f); + const double energy_units = + units_cgs_conversion_factor(internal_units, UNIT_CONV_ENERGY); + + /* Calculate internal mass to solar mass conversion factor */ + const double Msun_cgs = phys_const->const_solar_mass * + units_cgs_conversion_factor(internal_units, UNIT_CONV_MASS); + const double unit_mass_cgs = units_cgs_conversion_factor(internal_units, UNIT_CONV_MASS); + const double mass_to_solar_mass = unit_mass_cgs / Msun_cgs; + + /* Get the converted quantities. */ + const double star_age_before_Myr = star_age_begin_of_step * time_to_Myr; + const double star_age_now_Myr = star_age * time_to_Myr; + const double star_mass_Msolar = M * mass_to_solar_mass; + const double M_star_fraction = star_mass_Msolar / normalized_mass; + + if (Metallicity < Metallicities[0]) + Metallicity = Metallicities[0]; + else if (Metallicity > Metallicities[size_Metals - 1]) + Metallicity = Metallicities[size_Metals - 1]; + + /* TODO: add debug check macro for error check. */ + if (star_age_before_Myr < 0 || isnan(star_age_before_Myr)) { + error("Invalid star_age_before_Myr = %e", star_age_before_Myr); + } + + for (int g = 0; g < RT_NGROUPS; g++) { + if (star_age_before_Myr > 100.) { + /* If the stellar age before this timestep is above 100Myr, we set the emission to zero*/ + emission_this_step[g] = 0.; + } else { + if (!ionizing_tables[g]) error("ionizing_tables[%d] is NULL", g); + for (int i = 0; i < size_Metals; i++) { + if (!ionizing_tables[g][i]) error("ionizing_tables[%d][%d] is NULL", g, i); + } + double N_total_before = interpolate_2D_non_uniform(Metallicities, age_100Myr, + ionizing_tables[g], + size_Metals, size_Times, + Metallicity, star_age_before_Myr); + double N_total_now = interpolate_2D_non_uniform(Metallicities, age_100Myr, + ionizing_tables[g], + size_Metals, size_Times, + Metallicity, star_age_now_Myr); + double N_emission_this_step = N_total_now - N_total_before; + + /* average photon densities are in cgs! */ + const double E_g = f_esc * average_photon_energy[g] * N_emission_this_step * M_star_fraction / energy_units; + emission_this_step[g] = E_g; + + if (E_g < 0) { + error("Negative Photons??, N_total_before: %e, N_total_now: %e,star_age_before_Myr: %e, star_age_now_Myr: %e",N_total_before, N_total_now, star_age_before_Myr, star_age_now_Myr); + } + + //message("energy this step: %e, N_total_before: %e, N_total_now: %e, average_photon_energy[g]: %e, star_age_before_Myr: %e, star_age_now_Myr: %e, M_star_fraction: %e, N_emission_this_step:%e, Metalicities: %e", E_g, N_total_before, N_total_now, average_photon_energy[g], star_age_before_Myr, star_age_now_Myr, M_star_fraction, N_emission_this_step, Metallicity); + } + } +} + +#endif /* SWIFT_RT_STELLAR_EMISSION_MODEL_KIARA_H */ diff --git a/src/rt/KIARA/rt_struct.h b/src/rt/KIARA/rt_struct.h new file mode 100644 index 0000000000..15abe248d2 --- /dev/null +++ b/src/rt/KIARA/rt_struct.h @@ -0,0 +1,175 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2020 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_STRUCT_KIARA_H +#define SWIFT_RT_STRUCT_KIARA_H + +/** + * @file src/rt/KIARA/rt_struct.h + * @brief Main header file for the KIARA M1 Closure radiative transfer struct. + */ + +/* Additional RT data in hydro particle struct */ +struct rt_part_data { + + /* Radiation state vector. */ + struct { + float energy_density; + float flux[3]; + } radiation[RT_NGROUPS]; + + /* Fluxes in the conservation law sense */ + struct { + float energy; + float flux[3]; + } flux[RT_NGROUPS]; + + /* Particle RT time step. */ + float flux_dt; + + /* Smoothing length from previous timestep */ + float h_previous; + + /* gradients of the radiation state. */ + /* for the flux[3][3] quantity: + * first index: x, y, z coordinate of the flux. + * Second index: gradient along x, y, z direction. */ + struct { + float energy_density[3]; + float flux[3][3]; + } gradient[RT_NGROUPS]; + + /* cell slope limiter quantities */ + /* array of length two: store min among all neighbours + * at first index, store max among all neighbours at + * second index */ + /* the Gizmo-style slope limiting doesn't help for RT as is, + * so we're skipping it for now. */ + /* struct { */ + /* float energy_density[2]; */ + /* float flux[3][2]; */ + /* [> float maxr; [> just use the hydro one <] <] */ + /* } limiter[RT_NGROUPS]; */ + + /* Data for thermochemistry */ + struct { + float mass_fraction_HI; /* mass fraction taken by HI */ + float mass_fraction_HII; /* mass fraction taken by HII */ + float mass_fraction_HeI; /* mass fraction taken by HeI */ + float mass_fraction_HeII; /* mass fraction taken by HeII */ + float mass_fraction_HeIII; /* mass fraction taken by HeIII */ + float number_density_electrons; /* number density of electrons */ + } tchem; + +#ifdef GIZMO_MFV_SPH + /* Keep track of the actual mass fluxes of the gas species */ + struct { + float HI; /* mass fraction taken by HI */ + float HII; /* mass fraction taken by HII */ + float HeI; /* mass fraction taken by HeI */ + float HeII; /* mass fraction taken by HeII */ + float HeIII; /* mass fraction taken by HeIII */ + } mass_flux; +#endif + +#ifdef SWIFT_RT_DEBUG_CHECKS + /* debugging data to store during entire run */ + + /*! how much radiation this part received from stars during total lifetime */ + unsigned long long debug_radiation_absorbed_tot; + + /* data to store during one time step */ + + /*! how many stars this part interacted with during injection*/ + /* Note: It's useless to write this in outputs, as it gets reset + * at the end of every step. */ + int debug_iact_stars_inject; + + /*! calls from gradient interaction loop in actual function */ + int debug_calls_iact_gradient_interaction; + + /*! calls from transport interaction loop in actual function */ + int debug_calls_iact_transport_interaction; + + /* Task completion flags */ + + /*! part got kicked? */ + int debug_kicked; + + /*! calls from ghost1 tasks */ + int debug_injection_done; + + /*! finalised computing gradients? */ + int debug_gradients_done; + + /*! transport step done? */ + int debug_transport_done; + + /*! thermochemistry done? */ + int debug_thermochem_done; + + /* Subcycling flags */ + + /*! Current subcycle wrt (last) hydro step */ + int debug_nsubcycles; + +#endif +}; + +/* Additional RT data in star particle struct */ +struct rt_spart_data { + + /* Stellar energy emission that will be injected in to gas. + * Total energy, not density, not rate! */ + float emission_this_step[RT_NGROUPS]; + + /*! Neighbour weigths in each octant surrounding the star */ + float octant_weights[8]; + +#ifdef SWIFT_RT_DEBUG_CHECKS + /* data to store during entire run */ + + /*! how much radiation this star emitted during total lifetime */ + unsigned long long debug_radiation_emitted_tot; + + /* data to store during one time step */ + + /*! how many hydro particles this particle interacted with + * during injection */ + int debug_iact_hydro_inject; + + /*! how many hydro particles this particle interacted with + * during injection prep*/ + int debug_iact_hydro_inject_prep; + + /*! stellar photon emisison rate computed? */ + int debug_emission_rate_set; + + /*! how much energy this star particle actually has injected into the gas */ + float debug_injected_energy[RT_NGROUPS]; + + /*! how much energy this star particle actually has injected into the gas over + * the entire run*/ + float debug_injected_energy_tot[RT_NGROUPS]; + + /*! sum up total weights used during injection to compare consistency */ + float debug_psi_sum; +#endif +}; + +#endif /* SWIFT_RT_STRUCT_KIARA_H */ diff --git a/src/rt/KIARA/rt_thermochemistry.c b/src/rt/KIARA/rt_thermochemistry.c new file mode 100644 index 0000000000..7a69eec73b --- /dev/null +++ b/src/rt/KIARA/rt_thermochemistry.c @@ -0,0 +1,518 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2020 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#include "rt_thermochemistry.h" +#include "rt_grackle_utils.h" +#include "rt_interaction_cross_sections.h" +#include "rt_interaction_rates.h" +#include "rt_ionization_equilibrium.h" +#include "rt_unphysical.h" +#include "rt_getters.h" +#include "../../cooling.h" + +/* The grackle library itself */ +#include +extern chemistry_data *grackle_data; + +/** + * @file src/rt/KIARA/rt_thermochemistry.h + * @brief Main header file for the KIARA M1 closure radiative transfer scheme + * thermochemistry related functions. + */ + +/** + * @brief initialize particle quantities relevant for the thermochemistry. + * + * @param p part to work with + * @param rt_props rt_properties struct + * @param hydro_props hydro properties struct + * @param phys_const physical constants struct + * @param us unit system struct + * @param cosmo cosmology struct + */ +void rt_tchem_first_init_part( + struct part* restrict p, struct xpart* restrict xp, const struct rt_props* rt_props, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct unit_system* restrict us, + const struct cooling_function_data* cooling, + const struct cosmology* restrict cosmo) { + + if (rt_props->set_equilibrium_initial_ionization_mass_fractions) { + float XHI, XHII, XHeI, XHeII, XHeIII; + rt_ion_equil_get_mass_fractions(&XHI, &XHII, &XHeI, &XHeII, &XHeIII, p, + rt_props, hydro_props, phys_const, us, + cosmo); + p->rt_data.tchem.mass_fraction_HI = XHI; + p->rt_data.tchem.mass_fraction_HII = XHII; + p->rt_data.tchem.mass_fraction_HeI = XHeI; + p->rt_data.tchem.mass_fraction_HeII = XHeII; + p->rt_data.tchem.mass_fraction_HeIII = XHeIII; + } else if (rt_props->set_initial_ionization_mass_fractions) { + p->rt_data.tchem.mass_fraction_HI = rt_props->mass_fraction_HI_init; + p->rt_data.tchem.mass_fraction_HII = rt_props->mass_fraction_HII_init; + p->rt_data.tchem.mass_fraction_HeI = rt_props->mass_fraction_HeI_init; + p->rt_data.tchem.mass_fraction_HeII = rt_props->mass_fraction_HeII_init; + p->rt_data.tchem.mass_fraction_HeIII = rt_props->mass_fraction_HeIII_init; + } + + /* Initialize the cooling initial particle quantities. */ + cooling_first_init_part(phys_const, us, hydro_props, cosmo, cooling, p, xp); + + /* Here for KIARART test. We need to reset the value for cooling data. + * TODO :And we need to restructure it when we do the cosmological run. */ + xp->cooling_data.HI_frac = p->rt_data.tchem.mass_fraction_HI; + xp->cooling_data.HII_frac = p->rt_data.tchem.mass_fraction_HII; + xp->cooling_data.HeI_frac = p->rt_data.tchem.mass_fraction_HeI; + xp->cooling_data.HeII_frac = p->rt_data.tchem.mass_fraction_HeII; + xp->cooling_data.HeIII_frac = p->rt_data.tchem.mass_fraction_HeIII; + xp->cooling_data.e_frac = xp->cooling_data.HII_frac + + 0.25 * xp->cooling_data.HeII_frac + + 0.5 * xp->cooling_data.HeIII_frac; +} + +/** + * @brief Main function for the thermochemistry step. + * + * @param p Particle to work on. + * @param xp Pointer to the particle' extended data. + * @param rt_props RT properties struct + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param dt The time-step of this particle. + * @param depth recursion depth + */ +INLINE void rt_do_thermochemistry( + struct part* restrict p, struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us, const double dt, + const double dt_therm, int depth) { + /* Note: Can't pass rt_props as const struct because of grackle + * accessinging its properties there */ + + /* Nothing to do here? */ + if (rt_props->skip_thermochemistry) return; + if (dt <= 0.) return; + + /* This is where the fun begins */ + /* ---------------------------- */ + + /* initialize data so it'll be in scope */ + //grackle_field_data particle_grackle_data; + + gr_float density = hydro_get_physical_density(p, cosmo); + + /* In rare cases, unphysical solutions can arise with negative densities + * which won't be fixed in the hydro part until further down the dependency + * graph. Also, we can have vacuum, in which case we have nothing to do here. + * So exit early if that is the case. */ + if (density <= 0.) return; + + const float u_minimal = hydro_props->minimal_internal_energy; +#ifdef GIZMO_MFV_SPH + /* Physical internal energy */ + gr_float internal_energy_phys = + hydro_get_physical_internal_energy(p, xp, cosmo); + gr_float internal_energy = max(internal_energy_phys, u_minimal); + + const float u_old = internal_energy; +#else + /* Get physical internal energy */ + const float hydro_du_dt = hydro_get_physical_internal_energy_dt(p, cosmo); + + gr_float internal_energy_phys = hydro_get_physical_internal_energy(p, xp, cosmo); + + gr_float internal_energy = max(internal_energy_phys, u_minimal); + + const float u_old = internal_energy; +#endif + + /* initialize data to send to grackle */ + gr_float *species_densities; + species_densities = (gr_float *)calloc(N_SPECIES, sizeof(gr_float)); + grackle_field_data data; + + /* load particle information from particle to grackle data */ + float radiation_energy_density[RT_NGROUPS]; + rt_part_get_physical_radiation_energy_density(p, radiation_energy_density, cosmo); + + /* TODO: put the iact_rates to the cooling grackle data. */ + gr_float iact_rates[5]; + rt_get_interaction_rates_for_grackle( + iact_rates, radiation_energy_density, species_densities, + rt_props->average_photon_energy, rt_props->energy_weighted_cross_sections, + rt_props->number_weighted_cross_sections, phys_const, us); + + /* TODO: currently manually add iact_rates in grackle data field here. */ + data.RT_heating_rate = &iact_rates[0]; + data.RT_HI_ionization_rate = &iact_rates[1]; + data.RT_HeI_ionization_rate = &iact_rates[2]; + data.RT_HeII_ionization_rate = &iact_rates[3]; + data.RT_H2_dissociation_rate = &iact_rates[4]; + + cooling_copy_to_grackle(&data, us, cosmo, cooling, p, xp, dt, 0., species_densities, iact_rates, 0); + + /* solve chemistry */ + /* Note: `grackle_rates` is a global variable defined by grackle itself. + * Using a manually allocd and initialized variable here fails with MPI + * for some reason. */ + if (local_solve_chemistry( + &rt_props->grackle_chemistry_data, &rt_props->grackle_chemistry_rates, + &rt_props->grackle_units, &data, dt) == 0) + error("Error in solve_chemistry."); + + /* copy from grackle data to particle */ + cooling_copy_from_grackle(&data, p, xp, cooling, species_densities[12]); + + /* copy updated grackle data to particle */ + /* update particle internal energy. Grackle had access by reference + * to internal_energy */ + internal_energy_phys = data.internal_energy[0]; + const float u_new = max(internal_energy_phys, u_minimal); + + /* Re-do thermochemistry? */ + if ((rt_props->max_tchem_recursion > depth) && + (fabsf(u_old - u_new) > 0.1 * u_old)) { + /* Note that grackle already has internal "10% rules". But sometimes, they + * may not suffice. */ + //rt_clean_grackle_fields(&particle_grackle_data); + cooling_grackle_free_data(&data); + free(species_densities); + rt_do_thermochemistry(p, xp, rt_props, cosmo, hydro_props, phys_const, cooling, us, + 0.5 * dt, 0.5 * dt_therm, depth + 1); + rt_do_thermochemistry(p, xp, rt_props, cosmo, hydro_props, phys_const, cooling, us, + 0.5 * dt, 0.5 * dt_therm, depth + 1); + return; + } + + /* If we're good, update the particle data from grackle results */ +#ifdef GIZMO_MFV_SPH + hydro_set_physical_internal_energy(p, u_new); +#else + /* compute the heating/cooling due to the thermochemistry */ + float cool_du_dt = (u_new - u_old) / dt_therm; + + /* check whether the the thermochemistry heating/cooling is larger + * than du/dt of the particle. If it is, directly set the new internal energy + * of the particle, and set du/dt = 0.*/ + if (fabsf(cool_du_dt) > fabsf(hydro_du_dt)){ + hydro_set_physical_internal_energy(p, xp, cosmo, u_new); + + hydro_set_physical_internal_energy_dt(p, cosmo, 0.); + } else { + /* If it isn't, ignore the radiative cooling and apply only hydro du/dt. */ + hydro_set_physical_internal_energy_dt(p, cosmo, hydro_du_dt); + } +#endif + + /* Update mass fractions */ + const gr_float one_over_rho = 1. / density; + p->rt_data.tchem.mass_fraction_HI = + data.HI_density[0] * one_over_rho; + p->rt_data.tchem.mass_fraction_HII = + data.HII_density[0] * one_over_rho; + p->rt_data.tchem.mass_fraction_HeI = + data.HeI_density[0] * one_over_rho; + p->rt_data.tchem.mass_fraction_HeII = + data.HeII_density[0] * one_over_rho; + p->rt_data.tchem.mass_fraction_HeIII = + data.HeIII_density[0] * one_over_rho; + + /* Update radiation fields */ + /* First get absorption rates at the start and the end of the step */ + double absorption_rates[RT_NGROUPS]; + rt_get_absorption_rates( + absorption_rates, species_densities, rt_props->average_photon_energy, + rt_props->number_weighted_cross_sections, phys_const, us); + + gr_float species_densities_new[6]; + species_densities_new[0] = data.HI_density[0]; + species_densities_new[1] = data.HII_density[0]; + species_densities_new[2] = data.HeI_density[0]; + species_densities_new[3] = data.HeII_density[0]; + species_densities_new[4] = data.HeIII_density[0]; + species_densities_new[5] = data.e_density[0]; + double absorption_rates_new[RT_NGROUPS]; + rt_get_absorption_rates(absorption_rates_new, species_densities_new, + rt_props->average_photon_energy, + rt_props->number_weighted_cross_sections, phys_const, + us); + + /* Now remove absorbed radiation */ + for (int g = 0; g < RT_NGROUPS; g++) { + const float E_old = p->rt_data.radiation[g].energy_density; + double f = dt * 0.5 * (absorption_rates[g] + absorption_rates_new[g]); + f = min(1., f); + f = max(0., f); + p->rt_data.radiation[g].energy_density *= (1. - f); + for (int i = 0; i < 3; i++) { + p->rt_data.radiation[g].flux[i] *= (1. - f); + } + + rt_check_unphysical_state(&p->rt_data.radiation[g].energy_density, + p->rt_data.radiation[g].flux, E_old, + /*callloc=*/2); + } + + /* Clean up after yourself. */ + cooling_grackle_free_data(&data); + free(species_densities); +} + +/** + * @brief Computes an upper boundary for the thermochemistry/cooling + * time. + * + * @param p Particle to work on. + * @param xp Pointer to the particle' extended data. + * @param rt_props RT properties struct + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + */ +float rt_tchem_get_tchem_time( + const struct part* restrict p, const struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us) { + /* Note: Can't pass rt_props as const struct because of grackle + * accessinging its properties there */ + + + /* initialize data to send to grackle */ + gr_float *species_densities; + species_densities = (gr_float *)calloc(N_SPECIES, sizeof(gr_float)); + grackle_field_data data; + + float radiation_energy_density[RT_NGROUPS]; + rt_part_get_physical_radiation_energy_density(p, radiation_energy_density, cosmo); + + gr_float iact_rates[5]; + rt_get_interaction_rates_for_grackle( + iact_rates, radiation_energy_density, species_densities, + rt_props->average_photon_energy, rt_props->energy_weighted_cross_sections, + rt_props->number_weighted_cross_sections, phys_const, us); + + /* load particle information from particle to grackle data */ + cooling_copy_to_grackle(&data, us, cosmo, cooling, p, xp, 0., 0., species_densities, iact_rates, 0); + + /* TODO: currently manually add iact_rates in grackle data field here. */ + data.RT_heating_rate = &iact_rates[0]; + data.RT_HI_ionization_rate = &iact_rates[1]; + data.RT_HeI_ionization_rate = &iact_rates[2]; + data.RT_HeII_ionization_rate = &iact_rates[3]; + data.RT_H2_dissociation_rate = &iact_rates[4]; + + /* Compute 'cooling' time */ + /* Note: grackle_rates is a global variable defined by grackle itself. + * Using a manually allocd and initialized variable here fails with MPI + * for some reason. */ + gr_float tchem_time; + if (local_calculate_cooling_time( + &rt_props->grackle_chemistry_data, &rt_props->grackle_chemistry_rates, + &rt_props->grackle_units, &data, &tchem_time) == 0) + error("Error in calculate_cooling_time."); + + /* Clean up after yourself. */ + cooling_grackle_free_data(&data); + free(species_densities); + + return (float)tchem_time; +} + +/** + * @brief Main function for the thermochemistry step when coupling with + * subgrid physics. + * + * @param p Particle to work on. + * @param xp Pointer to the particle' extended data. + * @param rt_props RT properties struct + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param dt The time-step of this particle. + * @param depth recursion depth + */ +INLINE void rt_do_thermochemistry_with_subgrid( + struct part* restrict p, struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct entropy_floor_properties* floor_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us, const double dt, + const double dt_therm, int depth) { + /* Note: Can't pass rt_props as const struct because of grackle + * accessinging its properties there */ + + /* Nothing to do here? */ + if (rt_props->skip_thermochemistry) return; + + /* Compute cooling time and other quantities needed for firehose + if (dt > 0. && dt_therm > 0.) { + firehose_cooling_and_dust(phys_const, us, cosmo, hydro_props, + cooling, p, xp, dt); + }*/ + + /* Update the subgrid properties + cooling_set_particle_subgrid_properties( phys_const, us, + cosmo, hydro_props, floor_props, cooling, p, xp);*/ + + /* No cooling if particle is decoupled + if (p->decoupled) return; */ + + if (dt == 0.f || dt_therm == 0.f) return; + + float radiation_energy_density[RT_NGROUPS]; + rt_part_get_physical_radiation_energy_density(p, radiation_energy_density, cosmo); + + /* TODO: put the iact_rates to the cooling grackle data. */ + gr_float *iact_rates = (gr_float *)malloc(5 * sizeof(gr_float)); + if (iact_rates == NULL) { + fprintf(stderr, "Error: malloc failed for iact_rates\n"); + exit(EXIT_FAILURE); + } + + /* Load species info for computing interaction rates */ + gr_float rt_species[6]; + float rho = hydro_get_physical_density(p, cosmo); + if (p->cooling_data.subgrid_temp > 0. && + p->cooling_data.subgrid_fcold > 1.e-6) { + rho = cooling_get_subgrid_density(p, xp) * p->cooling_data.subgrid_fcold; + } + rt_tchem_get_species_densities(p, rho, rt_species); + + /* Compute interaction rates */ + rt_get_interaction_rates_for_grackle( + iact_rates, radiation_energy_density, rt_species, + rt_props->average_photon_energy, rt_props->energy_weighted_cross_sections, + rt_props->number_weighted_cross_sections, phys_const, us); + + //printf("=== RT computed interaction rates ===\n"); + //printf("RT RT_heating_rate = %e\n", iact_rates[0]); + //printf("RT RT_HI_ionization_rate = %e\n", iact_rates[1]); + //printf("RT RT_HeI_ionization_rate = %e\n", iact_rates[2]); + //printf("RT RT_HeII_ionization_rate = %e\n", iact_rates[3]); + //printf("RT RT_H2_dissociation_rate = %e\n", iact_rates[4]); + //printf("RT data RT_heating_rate = %e\n", *data.RT_heating_rate); + //printf("RT data RT_HI_ionization_rate = %e\n", *data.RT_HI_ionization_rate); + //printf("RT data RT_HeI_ionization_rate = %e\n", *data.RT_HeI_ionization_rate); + //printf("RT data RT_HeII_ionization_rate = %e\n", *data.RT_HeII_ionization_rate); + //printf("RT data RT_H2_dissociation_rate = %e\n", *data.RT_H2_dissociation_rate); + //printf("RT data data->HI_density = %e\n", *data.HI_density); + //printf("RT data data->HII_density = %e\n", *data.HII_density); + + /* Check for unphysical values (e.g., NaN or negative rates) */ + for (int i = 0; i < 5; i++) { + if (iact_rates[i] < 0.) { + error("Unphysical negative rate detected at index %d: %.4g", i, iact_rates[i]); + } else if (isnan(iact_rates[i]) || !isfinite(iact_rates[i])) { + error("NaN detected in rate at index %d", i); + } + //message("RT rate at index %d: %.4g", i, iact_rates[i]); + } + + /* solve chemistry, update thermal energy */ + cooling_do_grackle_cooling(phys_const, us, cosmo, hydro_props, + floor_props, cooling, + p, xp, iact_rates, dt, dt_therm); + + //int target_id = 2134785; + //int range = 50; + + //const float z = 1/cosmo->a - 1; + + //if (p->id >= target_id - range && p->id <= target_id + range) { + // Open file in append mode so new data is added without overwriting + // FILE *file = fopen("particle_track.txt", "a"); + // if (file == NULL) { + // printf("Error opening file!\n"); + // return; + // } + + // fprintf(file, "particle_track: p_id = %llu, density = %e, u_old = %e, u_new = %e, cool_du_dt = %e, hydro_du_dt = %e, p->cooling_data.subgrid_temp = %e, T_floor = %e, z=%e \n", p->id, p->rho, u_old, u_new, cool_du_dt, hydro_du_dt, p->cooling_data.subgrid_temp, T_floor, z); + + // Close the file + // fclose(file); + //} + + //message("particle_track: id = %llu, u_old = %e, u_new = %e, cool_du_dt = %e\n", p->id, u_old, u_new, cool_du_dt); + + /* Load mass fractions into rt_data */ + p->rt_data.tchem.mass_fraction_HI = xp->cooling_data.HI_frac; + p->rt_data.tchem.mass_fraction_HII = xp->cooling_data.HII_frac; + p->rt_data.tchem.mass_fraction_HeI = xp->cooling_data.HeI_frac; + p->rt_data.tchem.mass_fraction_HeII = xp->cooling_data.HeII_frac; + p->rt_data.tchem.mass_fraction_HeIII = xp->cooling_data.HeIII_frac; + + /* Update radiation fields */ + /* First get absorption rates at the start of the step */ + double absorption_rates[RT_NGROUPS]; + rt_get_absorption_rates( + absorption_rates, rt_species, rt_props->average_photon_energy, + rt_props->number_weighted_cross_sections, phys_const, us); + + /* Set up absorption rate calculation at end of step */ + gr_float rt_species_new[6]; + rt_tchem_get_species_densities(p, rho, rt_species_new); + + /* Constrain the value that are not physical */ + if (p->rt_data.tchem.mass_fraction_HI > 0.76f) { + rt_species_new[0] = 0.76f * rho; + } + + if (p->rt_data.tchem.mass_fraction_HeI > 0.24f) { + rt_species_new[2] = 0.24f * rho; + } + + /* Compute absorption rates at the end of the step */ + double absorption_rates_new[RT_NGROUPS]; + rt_get_absorption_rates(absorption_rates_new, rt_species_new, + rt_props->average_photon_energy, + rt_props->number_weighted_cross_sections, phys_const, + us); + + /* Now remove absorbed radiation, using average absorption */ + for (int g = 0; g < RT_NGROUPS; g++) { + const float E_old = p->rt_data.radiation[g].energy_density; + double f = dt * 0.5 * (absorption_rates[g] + absorption_rates_new[g]); + f = min(1., f); + f = max(0., f); + p->rt_data.radiation[g].energy_density *= (1. - f); + for (int i = 0; i < 3; i++) { + p->rt_data.radiation[g].flux[i] *= (1. - f); + } + + rt_check_unphysical_state(&p->rt_data.radiation[g].energy_density, + p->rt_data.radiation[g].flux, E_old, + /*callloc=*/2); + } + + /* Clean up after yourself. */ + free(iact_rates); +} + diff --git a/src/rt/KIARA/rt_thermochemistry.h b/src/rt/KIARA/rt_thermochemistry.h new file mode 100644 index 0000000000..debe743a81 --- /dev/null +++ b/src/rt/KIARA/rt_thermochemistry.h @@ -0,0 +1,120 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2024 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_KIARA_THERMOCHEMISTRY_H +#define SWIFT_RT_KIARA_THERMOCHEMISTRY_H + +#include "part.h" +#include "rt_properties.h" +#include "hydro_properties.h" +#include "physical_constants.h" +#include "units.h" +#include "cosmology.h" +#include "cooling.h" + +/** + * @file src/rt/KIARA/rt_thermochemistry.h + * @brief Main header file for the KIARA M1 closure radiative transfer scheme + * thermochemistry related functions. + */ + +/** + * @brief initialize particle quantities relevant for the thermochemistry. + * + * @param p part to work with + * @param rt_props rt_properties struct + * @param hydro_props hydro properties struct + * @param phys_const physical constants struct + * @param us unit system struct + * @param cosmo cosmology struct + */ +void rt_tchem_first_init_part( + struct part* restrict p, struct xpart* restrict xp, const struct rt_props* rt_props, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct unit_system* restrict us, + const struct cooling_function_data* cooling, + const struct cosmology* restrict cosmo); + +/** + * @brief Main function for the thermochemistry step. + * + * @param p Particle to work on. + * @param xp Pointer to the particle' extended data. + * @param rt_props RT properties struct + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param dt The time-step of this particle. + * @param depth recursion depth + */ +void rt_do_thermochemistry( + struct part* restrict p, struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us, const double dt, + const double dt_therm, int depth); + +/** + * @brief Computes an upper boundary for the thermochemistry/cooling + * time. + * + * @param p Particle to work on. + * @param xp Pointer to the particle' extended data. + * @param rt_props RT properties struct + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + */ +float rt_tchem_get_tchem_time( + const struct part* restrict p, const struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us); + +/** + * @brief Main function for the thermochemistry step when coupling with + * subgrid physics. + * + * @param p Particle to work on. + * @param xp Pointer to the particle' extended data. + * @param rt_props RT properties struct + * @param cosmo The current cosmological model. + * @param hydro_props The #hydro_props. + * @param phys_const The physical constants in internal units. + * @param us The internal system of units. + * @param dt The time-step of this particle. + * @param depth recursion depth + */ +void rt_do_thermochemistry_with_subgrid( + struct part* restrict p, struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct entropy_floor_properties* floor_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us, const double dt, + const double dt_therm, int depth); + +#endif /* SWIFT_RT_KIARA_THERMOCHEMISTRY_H */ diff --git a/src/rt/KIARA/rt_thermochemistry_utils.h b/src/rt/KIARA/rt_thermochemistry_utils.h new file mode 100644 index 0000000000..e3e6086fc7 --- /dev/null +++ b/src/rt/KIARA/rt_thermochemistry_utils.h @@ -0,0 +1,294 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_KIARA_THERMOCHEMISTRY_UTILS_H +#define SWIFT_RT_KIARA_THERMOCHEMISTRY_UTILS_H + +/** + * @file src/rt/KIARA/rt_thermochemistry_utils.h + * @brief thermochemistry utilities and misc functions. + * */ + +#include "rt_species.h" + +/** + * @brief compute the mean molecular weight mu for given + * hydrogen and helium mass fractions + * @param XHI mass fraction of HI + * @param XHII mass fraction of HII + * @param XHeI mass fraction of HeI + * @param XHeII mass fraction of HeII + * @param XHeIII mass fraction of HeII + */ +__attribute__((always_inline)) INLINE static double +rt_tchem_get_mean_molecular_weight(float XHI, float XHII, float XHeI, + float XHeII, float XHeIII) { + + /* 1/mu = sum_j X_j / A_j * (1 + E_j) + * A_H = 1, E_H = 0 + * A_Hp = 1, E_Hp = 1 + * A_He = 4, E_He = 0 + * A_Hep = 4, E_Hep = 1 + * A_Hepp = 4, E_Hepp = 2 */ + const double one_over_mu = + XHI + 2.0 * XHII + 0.25 * XHeI + 0.5 * XHeII + 0.75 * XHeIII; + + return (1.0 / one_over_mu); +} + +/** + * @brief compute the temperature of an ideal gas for a given + * specific internal energy and mean molecular weight + * + * @param u specific internal energy of the gas + * @param mu mean molecular weight of the gas + * @param kB Boltzmann constant + * @param mp proton mass + * */ +__attribute__((always_inline)) INLINE static float +rt_tchem_temperature_from_internal_energy(double u, double mu, const double kB, + const double mp) { + + return u * hydro_gamma_minus_one * mu * mp / kB; +} + +/** + * @brief compute the (physical) specific internal energy + * of an ideal gas for given temperature and mean molecular + * weight. + * + * @param u specific internal energy of the gas + * @param mu mean molecular weight of the gas + * @param kB Boltzmann constant + * @param mp proton mass + * */ +__attribute__((always_inline)) INLINE static float +rt_tchem_internal_energy_from_T(const double T, const double mu, + const double kB, const double mp) { + + return kB * T * hydro_one_over_gamma_minus_one / (mu * mp); +} + +/** + * @brief compute the derivative w.r.t. temperature of the + * (physical) specific internal energy of an ideal gas for + * given temperature and mean molecular weight + * + * @param u specific internal energy of the gas + * @param mu mean molecular weight of the gas + * @param kB Boltzmann constant + * @param mp proton mass + * */ +__attribute__((always_inline)) INLINE static float rt_tchem_internal_energy_dT( + double mu, const double kB, const double mp) { + + const double dudT = kB * hydro_one_over_gamma_minus_one / (mu * mp); + + return dudT; +} + +/** + * @brief get the densities of all species and electrons. + * + * @param p particle to use + * @param rho particle physical density + * @param species_densities array to write densities in + **/ +__attribute__((always_inline)) INLINE static void +rt_tchem_get_species_densities(const struct part* restrict p, gr_float rho, + gr_float species_densities[6]) { + + species_densities[0] = p->rt_data.tchem.mass_fraction_HI * rho; + species_densities[1] = p->rt_data.tchem.mass_fraction_HII * rho; + species_densities[2] = p->rt_data.tchem.mass_fraction_HeI * rho; + species_densities[3] = p->rt_data.tchem.mass_fraction_HeII * rho; + species_densities[4] = p->rt_data.tchem.mass_fraction_HeIII * rho; + + /* nHII = rho_HII / m_p + * nHeII = rho_HeII / 4 m_p + * nHeIII = rho_HeIII / 4 m_p + * ne = nHII + nHeII + 2 * nHeIII + * But: it is grackle convention to use rho_e = n_e * m_p */ + const gr_float rho_e = species_densities[1] + 0.25 * species_densities[3] + + 0.5 * species_densities[4]; + species_densities[5] = rho_e; +} + +/** + * @brief get the number densities of the ionizing species in cgs. + * + * @param ns_cgs (return) number densities in cgs of ionizing species + * @param species_densities densities of all species as returned by + * rt_tchem_get_species_densities() + * @param phys_const physical constants struct + * @param us internal units struct + * + **/ +__attribute__((always_inline)) INLINE static void +rt_tchem_get_ionizing_species_number_densities( + double ns_cgs[rt_ionizing_species_count], gr_float species_densities[6], + const struct phys_const* restrict phys_const, + const struct unit_system* restrict us) { + + const double m_p = phys_const->const_proton_mass; + const double to_inv_volume_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_INV_VOLUME); + const double rho_to_n_cgs = to_inv_volume_cgs / m_p; + + ns_cgs[rt_ionizing_species_HI] = species_densities[0] * rho_to_n_cgs; + ns_cgs[rt_ionizing_species_HeI] = 0.25 * species_densities[2] * rho_to_n_cgs; + ns_cgs[rt_ionizing_species_HeII] = 0.25 * species_densities[3] * rho_to_n_cgs; +} + +/** + * @brief get the (physical) temperature of the gas + * + * @param p particle to use + * @param phys_const physical constants struct + * @param cosmo cosmology struct + **/ +__attribute__((always_inline)) INLINE static double +rt_tchem_get_gas_temperature(const struct part* restrict p, + const struct phys_const* restrict phys_const, + const struct cosmology* restrict cosmo) { + + const double kB = phys_const->const_boltzmann_k; + const double mp = phys_const->const_proton_mass; + + const float XHI = p->rt_data.tchem.mass_fraction_HI; + const float XHII = p->rt_data.tchem.mass_fraction_HII; + const float XHeI = p->rt_data.tchem.mass_fraction_HeI; + const float XHeII = p->rt_data.tchem.mass_fraction_HeII; + const float XHeIII = p->rt_data.tchem.mass_fraction_HeIII; + + const double mu = + rt_tchem_get_mean_molecular_weight(XHI, XHII, XHeI, XHeII, XHeIII); + const double u = hydro_get_drifted_physical_internal_energy(p, cosmo); + + double T = rt_tchem_temperature_from_internal_energy(u, mu, kB, mp); + + return T; +} + +/** + * @brief Set a particle's density and internal energy. + * + * This function is only intended for use in very special case idealized + * tests, like the Iliev+06 tests, where we require fixed densities and + * temperatures. + **/ +__attribute__((always_inline)) INLINE static void +rt_tchem_set_particle_quantities_for_test(struct part* restrict p) { + +#ifdef GIZMO_MFV_SPH + + /* Set the values that you actually want. Needs to be in internal units.*/ + /* 1 hydrogen_atom_mass / cm^3 / (1.98848e18 g/IMU * 3.0857e15cm/ILU^3) */ + /* float density = 2.471e+04; */ + + /* Set the values that you actually want. Needs to be in internal units.*/ + /* 10^-3 hydrogen_atom_mass / cm^3 / (1.98848e18 g/IMU * 3.0857e15cm/ILU^3) */ + float density = 2.471e+01; + + /* 100 K */ + float internal_energy = 1.23816f; + + /* 10000 K with xHII = 1e-3 for Iliev Test 1 */ + /* float internal_energy = 124.8416491f; */ + + /* Be vocal, just in case somebody forgets you exist. */ + if (p->id == 1) message("Setting density from %.3e to %.3e", p->rho, density); + + float mass_corr = density / p->rho; + p->rho = density; + p->conserved.mass *= mass_corr; + /* This assumes zero velocity */ + p->conserved.energy = p->conserved.mass * internal_energy; + hydro_set_internal_energy(p, internal_energy); + +#else + + error("This isn't implemented for SPH yet."); + +#endif +} + +/** + * @brief Set a particle's radiation field given a photon flux. + * + * This function is only intended for use in very special case idealized + * tests, like the Iliev+06 tests, where we require fixed densities and + * temperatures. + * + * @param p particle to modify + * @param time current simulation time + * @param us unity system. + **/ +__attribute__((always_inline)) INLINE static void +rt_tchem_set_particle_radiation_field_for_test( + struct part* restrict p, const double time, + const struct unit_system* restrict us) { + + const double time_to_cgs = units_cgs_conversion_factor(us, UNIT_CONV_TIME); + const double t_Myr = time * time_to_cgs / (3600. * 24. * 365. * 1e6); + + /* NOTE: this assumes that the test is set up with 3 photon groups. */ + double fixed_fluxes[3]; + for (int g = 0; g < 3; g++) fixed_fluxes[g] = 0.; + + if (t_Myr < 0.5) { + /* Be vocal, just in case somebody forgets you exist. */ + if (p->id == 1) message("Setting fixed radiation field."); + /* Set fixed radiation fields, in cgs*/ + fixed_fluxes[0] = 1.350e01; + fixed_fluxes[1] = 2.779e01; + fixed_fluxes[2] = 6.152e00; + } + + const double flux_to_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_ENERGY_FLUX_PER_UNIT_SURFACE); + const double cf = rt_params.reduced_speed_of_light_inverse / flux_to_cgs; + + /* Note that we inject energy / time / surface, not identical to what */ + /* is in Iliev06 paper */ + for (int g = 0; g < RT_NGROUPS; g++) { + p->rt_data.radiation[g].energy_density = fixed_fluxes[g] * cf; + } +} + +/** + * @brief Modify a boundary particle. + * + * This function is only intended for use in very special case idealized + * tests, like the Iliev+06 tests, to deal with boundary conditions in + * a simple manner. + * */ +__attribute__((always_inline)) INLINE static void +rt_tchem_set_boundary_particles_for_test(struct part* restrict p) { + + if (p->id >= 1000000000) { + for (int g = 0; g < RT_NGROUPS; g++) { + p->rt_data.radiation[g].energy_density = 0.f; + p->rt_data.radiation[g].flux[0] = 0.f; + p->rt_data.radiation[g].flux[1] = 0.f; + p->rt_data.radiation[g].flux[2] = 0.f; + } + } +} + +#endif /* SWIFT_RT_KIARA_THERMOCHEMISTRY_UTILS_H */ diff --git a/src/rt/KIARA/rt_unphysical.h b/src/rt/KIARA/rt_unphysical.h new file mode 100644 index 0000000000..191624bad5 --- /dev/null +++ b/src/rt/KIARA/rt_unphysical.h @@ -0,0 +1,236 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_RT_UNPHYSICAL_KIARA_H +#define SWIFT_RT_UNPHYSICAL_KIARA_H + +/** + * @file src/rt/KIARA/rt_unphysical.h + * @brief Routines for checking for and correcting unphysical scenarios + */ + +/** + * @brief check for and correct if needed unphysical + * values for a radiation state. + * + * @param energy_density pointer to the radiation energy density + * @param flux pointer to radiation flux (3 dimensional) + * @param e_old energy density before change to check. Set = 0 if not available + * @param callloc integer indentifier where this function was called from + */ +__attribute__((always_inline)) INLINE static void rt_check_unphysical_state( + float* energy_density, float* flux, const float e_old, int callloc) { + + /* Check for negative energies */ + /* Note to self for printouts: Maximal allowable F = E * c. + * In some cases, e.g. while cooling, we don't modify the fluxes, + * so you can get an estimate of what the photon energy used to be + * by dividing the printed out fluxes by the speed of light in + * code units */ +#ifdef SWIFT_DEBUG_CHECKS + float ratio = -1.f; + char print = 0; + if (e_old == 0.) { + if (*energy_density < -1.e-4f) print = 1; + } else { + if (fabsf(*energy_density) > 1.e-30) { + if (*energy_density < -1.e-4f * fabsf(e_old)) print = 1; + ratio = fabsf(*energy_density / e_old); + } + } + /* callloc = 1 is gradient extrapolation. Don't print out those. */ + if (callloc == 1) print = 0; + if (print) + message("Fixing unphysical energy case %d | %.6e | %.6e %.6e %.6e | %.6e", + callloc, *energy_density, flux[0], flux[1], flux[2], ratio); +#endif + if (isinf(*energy_density) || isnan(*energy_density)) + error("Got inf/nan radiation energy case %d | %.6e | %.6e %.6e %.6e", + callloc, *energy_density, flux[0], flux[1], flux[2]); + + if (*energy_density <= 0.f) { + *energy_density = 0.f; + flux[0] = 0.f; + flux[1] = 0.f; + flux[2] = 0.f; + return; + } + + /* Check for too high fluxes */ + const double flux2 = + flux[0] * flux[0] + flux[1] * flux[1] + flux[2] * flux[2]; + const double flux_norm = sqrt(flux2); + const double flux_max = rt_params.reduced_speed_of_light * *energy_density; + if (flux_norm > flux_max) { + const double correct = flux_max / flux_norm; + flux[0] *= correct; + flux[1] *= correct; + flux[2] *= correct; + } +} + +/** + * @brief Do additional checks after reading in initial conditions, and exit on + * error. + * + * @param p particle we're checking + * @param group current photon group we're checking + * @param energy_density pointer to the radiation energy density + * @param flux pointer to radiation flux (3 dimensional) + * @param c the speed of light (in internal units). NOT the reduced speed of + * light. + */ +__attribute__((always_inline)) INLINE static void rt_check_unphysical_state_ICs( + const struct part* restrict p, int group, float* energy_density, + float* flux, const double c) { + + /* Nothing to do here. The other unphysical check will catch other problems. + */ + if (*energy_density == 0.f) return; + + /* Check for negative energies */ + if (*energy_density < 0.f) + error( + "Found particle with negative energy density after reading in ICs: " + "pid= %lld group=%d E=%.6g", + p->id, group, *energy_density); + if (*energy_density > FLT_MAX || isnan(*energy_density)) + error("Got inf/nan energy_density: %g", *energy_density); + + /* Check for too high fluxes */ + const float flux2 = flux[0] * flux[0] + flux[1] * flux[1] + flux[2] * flux[2]; + const float flux_norm = sqrtf(flux2); + const float flux_max = c * *energy_density; + if (flux_max > FLT_MAX || isnan(flux_max)) + error("Got inf/nan flux_max: %g", flux_max); + if (flux_norm > FLT_MAX || isnan(flux_norm)) + error("Got inf/nan flux_norm: %g", flux_norm); + if (flux_norm > flux_max * 1.0001) { + error( + "Found too high radiation flux for a particle: pid=%lld, group=%d, " + "have=%.6g, max=%.6g", + p->id, group, flux_norm, flux_max); + } +} + +/** + * @brief check for and correct if needed unphysical + * values for a flux in the sense of hyperbolic conservation laws + * + * @param flux hyperbolic flux: 4 components (photon energy + + * photon flux) x 3 dimensions each + */ +__attribute__((always_inline)) INLINE static void +rt_check_unphysical_hyperbolic_flux(float flux[4][3]) { + +#ifdef SWIFT_DEBUG_CHECKS + int nans = 0; + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 3; j++) { + if (isnan(flux[i][j])) { + nans += 1; + break; + } + } + } + + if (nans) { + message( + "Fixing unphysical hyperbolic flux:" + " %.3e %.3e %.3e | %.3e %.3e %.3e |" + " %.3e %.3e %.3e | %.3e %.3e %.3e", + flux[0][0], flux[0][1], flux[0][2], flux[1][0], flux[1][1], flux[1][2], + flux[2][0], flux[2][1], flux[2][2], flux[3][0], flux[3][1], flux[3][2]); + } +#endif + + for (int i = 0; i < 4; i++) { + for (int j = 0; j < 3; j++) { + if (isnan(flux[i][j])) { + flux[i][j] = 0.f; + } + } + } +} + +/** + * @brief check whether gas species mass fractions have physical + * values and correct small errors if necessary. + * + * @param p particle to work on + */ +__attribute__((always_inline)) INLINE static void +rt_check_unphysical_mass_fractions(struct part* restrict p) { + + /* For now, catch either mass or rho being zero. At the moment, they are not + * necessarily both zero. For example, an unphysical check may zero out both + * mass and density when it becomes negative in a hydro step. Once that + * happens, particles may gain mass through flux exchanges with other active + * particles, while they themselves remain inactive. The density of such + * inactive particles however remains zero until the particle is active + * again. See issue #833. */ + + if (hydro_get_mass(p) <= 0.f || p->rho <= 0.f) { + /* Deal with unphysical situations and vacuum. */ + p->rt_data.tchem.mass_fraction_HI = RT_KIARA_TINY_MASS_FRACTION; + p->rt_data.tchem.mass_fraction_HII = RT_KIARA_TINY_MASS_FRACTION; + p->rt_data.tchem.mass_fraction_HeI = RT_KIARA_TINY_MASS_FRACTION; + p->rt_data.tchem.mass_fraction_HeII = RT_KIARA_TINY_MASS_FRACTION; + p->rt_data.tchem.mass_fraction_HeIII = RT_KIARA_TINY_MASS_FRACTION; + return; + } + +#ifdef SWIFT_RT_DEBUG_CHECKS + if (p->rt_data.tchem.mass_fraction_HI < -1e4) + message("WARNING: Got negative HI mass fraction?"); + if (p->rt_data.tchem.mass_fraction_HII < -1e4) + message("WARNING: Got negative HII mass fraction?"); + if (p->rt_data.tchem.mass_fraction_HeI < -1e4) + message("WARNING: Got negative HeI mass fraction?"); + if (p->rt_data.tchem.mass_fraction_HeII < -1e4) + message("WARNING: Got negative HeII mass fraction?"); + if (p->rt_data.tchem.mass_fraction_HeIII < -1e4) + message("WARNING: Got negative HeIII mass fraction?"); +#endif + + /* TODO: this should be a for loop with mass fractions being enums. */ + p->rt_data.tchem.mass_fraction_HI = + max(p->rt_data.tchem.mass_fraction_HI, RT_KIARA_TINY_MASS_FRACTION); + p->rt_data.tchem.mass_fraction_HII = + max(p->rt_data.tchem.mass_fraction_HII, RT_KIARA_TINY_MASS_FRACTION); + p->rt_data.tchem.mass_fraction_HeI = + max(p->rt_data.tchem.mass_fraction_HeI, RT_KIARA_TINY_MASS_FRACTION); + p->rt_data.tchem.mass_fraction_HeII = + max(p->rt_data.tchem.mass_fraction_HeII, RT_KIARA_TINY_MASS_FRACTION); + p->rt_data.tchem.mass_fraction_HeIII = + max(p->rt_data.tchem.mass_fraction_HeIII, RT_KIARA_TINY_MASS_FRACTION); + + const float XHI = p->rt_data.tchem.mass_fraction_HI; + const float XHII = p->rt_data.tchem.mass_fraction_HII; + const float XHeI = p->rt_data.tchem.mass_fraction_HeI; + const float XHeII = p->rt_data.tchem.mass_fraction_HeII; + const float XHeIII = p->rt_data.tchem.mass_fraction_HeIII; + + const float Xtot = XHI + XHII + XHeI + XHeII + XHeIII; + + /* Make sure we sum up to 1. TODO: Assuming we have no metals. */ + if (fabsf(Xtot - 1.f) > 1e-3) + error("Got total mass fraction of gas = %.6g", Xtot); +} + +#endif /* SWIFT_RT_UNPHYSICAL_KIARA_H */ diff --git a/src/rt/none/rt.h b/src/rt/none/rt.h index f014b1088b..c40d696499 100644 --- a/src/rt/none/rt.h +++ b/src/rt/none/rt.h @@ -157,10 +157,12 @@ __attribute__((always_inline)) INLINE static void rt_spart_has_no_neighbours( * @param cosmo cosmology struct */ __attribute__((always_inline)) INLINE static void rt_convert_quantities( - struct part *restrict p, const struct rt_props *rt_props, + struct part *restrict p, struct xpart *restrict xp, + const struct rt_props *rt_props, const struct hydro_props *hydro_props, const struct phys_const *restrict phys_const, const struct unit_system *restrict us, + const struct cooling_function_data *cool_func, const struct cosmology *restrict cosmo) {} /** @@ -168,7 +170,8 @@ __attribute__((always_inline)) INLINE static void rt_convert_quantities( * of a given particle (during timestep tasks) * * @param p Particle to work on. - * @param rt_props RT properties struct + * @param xp Particle extra data. + * @param rt_props RT properties struct. * @param cosmo The current cosmological model. * @param hydro_props The #hydro_props. * @param phys_const The physical constants in internal units. @@ -176,11 +179,12 @@ __attribute__((always_inline)) INLINE static void rt_convert_quantities( * @param dt The time-step of this particle. */ __attribute__((always_inline)) INLINE static float rt_compute_timestep( - const struct part *restrict p, const struct xpart *restrict xp, - struct rt_props *rt_props, const struct cosmology *restrict cosmo, - const struct hydro_props *hydro_props, - const struct phys_const *restrict phys_const, - const struct unit_system *restrict us) { + const struct part* restrict p, const struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us) { return FLT_MAX; } @@ -250,24 +254,57 @@ __attribute__((always_inline)) INLINE static void rt_end_gradient( __attribute__((always_inline)) INLINE static void rt_finalise_transport( struct part *restrict p, struct rt_props *rtp, const double dt, const struct cosmology *restrict cosmo) {} + +/** + * @brief Compute the time-step length for an RT step of a particle from given + * integer times ti_beg and ti_end. This time-step length is then used to + * compute the actual time integration of the transport/force step and the + * thermochemistry. This is not used to determine the time-step length during + * the time-step tasks. + * + * @param ti_beg Start of the time-step (on the integer time-line). + * @param ti_end End of the time-step (on the integer time-line). + * @param time_base Minimal time-step size on the time-line. + * @param with_cosmology Are we running with cosmology integration? + * @param cosmo The #cosmology object. + * + * @return The time-step size for the rt integration. (internal units). + */ +__attribute__((always_inline)) INLINE static double rt_part_dt_therm( + const integertime_t ti_beg, const integertime_t ti_end, + const double time_base, const int with_cosmology, + const struct cosmology* cosmo) { + + return FLT_MAX; +} + /** * @brief Do the thermochemistry on a particle. * + * This function wraps around rt_do_thermochemistry function. + * * @param p Particle to work on. * @param xp Pointer to the particle' extended data. * @param rt_props RT properties struct * @param cosmo The current cosmological model. * @param hydro_props The #hydro_props. + * @param floor_props Properties of the entropy floor. * @param phys_const The physical constants in internal units. * @param us The internal system of units. * @param dt The time-step of this particle. + * @param dt_therm The time-step operator used for thermal quantities. + * @param time The current time (since the Big Bang or start of the run) in + * internal units. */ __attribute__((always_inline)) INLINE static void rt_tchem( - struct part *restrict p, struct xpart *restrict xp, - struct rt_props *rt_props, const struct cosmology *restrict cosmo, - const struct hydro_props *hydro_props, - const struct phys_const *restrict phys_const, - const struct unit_system *restrict us, const double dt) {} + struct part* restrict p, struct xpart* restrict xp, + struct rt_props* rt_props, const struct cosmology* restrict cosmo, + const struct hydro_props* hydro_props, + const struct entropy_floor_properties* floor_props, + const struct phys_const* restrict phys_const, + const struct cooling_function_data* restrict cooling, + const struct unit_system* restrict us, const double dt, + const double dt_therm, const double time) {} /** * @brief Extra operations done during the kick. diff --git a/src/rt/none/rt_io.h b/src/rt/none/rt_io.h index d323363a11..a18e301f8d 100644 --- a/src/rt/none/rt_io.h +++ b/src/rt/none/rt_io.h @@ -57,12 +57,13 @@ INLINE static int rt_read_stars(const struct spart *sparts, * transfer data of hydro particles. * * @param parts The particle array. + * @param xparts The particle extra information. * @param list The list of i/o properties to write. * * @return Returns the number of fields to write. */ -INLINE static int rt_write_particles(const struct part *parts, - struct io_props *list) { +INLINE static int rt_write_particles(const struct part* parts, + const struct xpart* xparts, struct io_props* list) { return 0; } diff --git a/src/rt_additions.h b/src/rt_additions.h index cb7595c238..252069f334 100644 --- a/src/rt_additions.h +++ b/src/rt_additions.h @@ -35,6 +35,8 @@ #include "./rt/debug/rt_additions.h" #elif defined(RT_GEAR) #include "./rt/GEAR/rt_additions.h" +#elif defined(RT_KIARA) +#include "./rt/KIARA/rt_additions.h" #elif defined(RT_SPHM1RT) #include "./rt/SPHM1RT/rt_additions.h" #else diff --git a/src/rt_io.h b/src/rt_io.h index 655c13f3bd..a50b31116d 100644 --- a/src/rt_io.h +++ b/src/rt_io.h @@ -34,6 +34,8 @@ #include "./rt/debug/rt_io.h" #elif defined(RT_GEAR) #include "./rt/GEAR/rt_io.h" +#elif defined(RT_KIARA) +#include "./rt/KIARA/rt_io.h" #elif defined(RT_SPHM1RT) #include "./rt/SPHM1RT/rt_io.h" #else diff --git a/src/rt_parameters.c b/src/rt_parameters.c index a70ef113b4..76536f6a26 100644 --- a/src/rt_parameters.c +++ b/src/rt_parameters.c @@ -31,7 +31,7 @@ * The fake initialisation below forces the compiler to keep the * instance and pass it to the linker stage. */ -#if defined(RT_GEAR) +#if defined(RT_GEAR) || defined(RT_KIARA) struct rt_parameters rt_params = {.reduced_speed_of_light = 1.f, .reduced_speed_of_light_inverse = 1.f}; #else diff --git a/src/rt_parameters.h b/src/rt_parameters.h index fa3821005f..4107f5aedd 100644 --- a/src/rt_parameters.h +++ b/src/rt_parameters.h @@ -34,6 +34,8 @@ #include "./rt/debug/rt_parameters.h" #elif defined(RT_GEAR) #include "./rt/GEAR/rt_parameters.h" +#elif defined(RT_KIARA) +#include "./rt/KIARA/rt_parameters.h" #elif defined(RT_SPHM1RT) #include "./rt/SPHM1RT/rt_parameters.h" #elif defined(RT_NONE) diff --git a/src/rt_properties.h b/src/rt_properties.h index e0454afe79..dd6497c32d 100644 --- a/src/rt_properties.h +++ b/src/rt_properties.h @@ -36,6 +36,8 @@ #include "./rt/SPHM1RT/rt_properties.h" #elif defined(RT_GEAR) #include "./rt/GEAR/rt_properties.h" +#elif defined(RT_KIARA) +#include "./rt/KIARA/rt_properties.h" #else #error "Invalid choice of radiation scheme" #endif diff --git a/src/rt_struct.h b/src/rt_struct.h index 78986ef1af..90fd3cfc81 100644 --- a/src/rt_struct.h +++ b/src/rt_struct.h @@ -36,6 +36,8 @@ #include "./rt/debug/rt_struct.h" #elif defined(RT_GEAR) #include "./rt/GEAR/rt_struct.h" +#elif defined(RT_KIARA) +#include "./rt/KIARA/rt_struct.h" #elif defined(RT_SPHM1RT) #include "./rt/SPHM1RT/rt_struct.h" #else diff --git a/src/runner_doiact_functions_stars.h b/src/runner_doiact_functions_stars.h index 4599e52994..a15f76c751 100644 --- a/src/runner_doiact_functions_stars.h +++ b/src/runner_doiact_functions_stars.h @@ -155,7 +155,7 @@ void DOSELF1_STARS(struct runner *r, const struct cell *c, const int offset, e->feedback_props, ti_current); #elif (FUNCTION_TASK_LOOP == TASK_LOOP_STARS_PREP1) runner_iact_nonsym_feedback_prep1(r2, dx, hi, hj, si, pj, NULL, cosmo, - ti_current); + e->feedback_props, ti_current); #elif (FUNCTION_TASK_LOOP == TASK_LOOP_STARS_PREP2) runner_iact_nonsym_feedback_prep2(r2, dx, hi, hj, si, pj, NULL, cosmo, ti_current); @@ -321,7 +321,7 @@ void DO_NONSYM_PAIR1_STARS_NAIVE(struct runner *r, e->feedback_props, ti_current); #elif (FUNCTION_TASK_LOOP == TASK_LOOP_STARS_PREP1) runner_iact_nonsym_feedback_prep1(r2, dx, hi, hj, si, pj, NULL, cosmo, - ti_current); + e->feedback_props, ti_current); #elif (FUNCTION_TASK_LOOP == TASK_LOOP_STARS_PREP2) runner_iact_nonsym_feedback_prep2(r2, dx, hi, hj, si, pj, NULL, cosmo, ti_current); @@ -552,7 +552,8 @@ void DO_SYM_PAIR1_STARS(struct runner *r, const struct cell *restrict ci, ti_current); #elif (FUNCTION_TASK_LOOP == TASK_LOOP_STARS_PREP1) runner_iact_nonsym_feedback_prep1(r2, dx, hi, hj, spi, pj, NULL, - cosmo, ti_current); + cosmo, e->feedback_props, + ti_current); #elif (FUNCTION_TASK_LOOP == TASK_LOOP_STARS_PREP2) runner_iact_nonsym_feedback_prep2(r2, dx, hi, hj, spi, pj, NULL, cosmo, ti_current); @@ -719,7 +720,8 @@ void DO_SYM_PAIR1_STARS(struct runner *r, const struct cell *restrict ci, ti_current); #elif (FUNCTION_TASK_LOOP == TASK_LOOP_STARS_PREP1) runner_iact_nonsym_feedback_prep1(r2, dx, hj, hi, spj, pi, NULL, - cosmo, ti_current); + cosmo, e->feedback_props, + ti_current); #elif (FUNCTION_TASK_LOOP == TASK_LOOP_STARS_PREP2) runner_iact_nonsym_feedback_prep2(r2, dx, hj, hi, spj, pi, NULL, cosmo, ti_current); diff --git a/src/runner_ghost.c b/src/runner_ghost.c index 3c98034920..4cea53e058 100644 --- a/src/runner_ghost.c +++ b/src/runner_ghost.c @@ -115,6 +115,10 @@ void runner_do_stars_ghost(struct runner *r, struct cell *c, const int offset, if (c->stars.count == 0) return; if (!cell_is_active_stars(c, e)) return; + /* Set up possible bisection if Newton-Raphson over-iterates */ + float h_high = stars_h_max; + float h_low = stars_h_min; + /* Recurse? */ if (c->split) { for (int k = 0; k < 8; k++) { @@ -320,11 +324,21 @@ void runner_do_stars_ghost(struct runner *r, struct cell *c, const int offset, if (num_reruns > max_smoothing_iter - 10) { message( - "Smoothing length convergence problem: iter=%d p->id=%lld " + "Smoothing length convergence problem stars: iter=%d p->id=%lld " "h_init=%12.8e h_old=%12.8e h_new=%12.8e f=%f f_prime=%f " "n_sum=%12.8e n_target=%12.8e left=%12.8e right=%12.8e", num_reruns, sp->id, h_init, h_old, h_new, f, f_prime, n_sum, n_target, left[i], right[i]); + + /* Try bisection */ + if (n_sum < n_target) { + h_new = 0.5 * (h_old + h_high); + h_low = h_old; + } + if (n_sum > n_target) { + h_new = 0.5 * (h_old + h_low); + h_high = h_old; + } } /* Safety check: truncate to the range [ h_old/2 , 2h_old ]. */ @@ -548,7 +562,7 @@ void runner_do_stars_ghost(struct runner *r, struct cell *c, const int offset, sp->density.wcount); } - error("Smoothing length failed to converge on %i particles.", scount); + warning("Smoothing length failed to converge on %i particles.", scount); } /* Be clean */ @@ -755,7 +769,7 @@ void runner_do_black_holes_density_ghost(struct runner *r, struct cell *c, if (num_reruns > max_smoothing_iter - 10) { message( - "Smoothing length convergence problem: iter=%d p->id=%lld " + "Smoothing length convergence problem BH: iter=%d p->id=%lld " "h_init=%12.8e h_old=%12.8e h_new=%12.8e f=%f f_prime=%f " "n_sum=%12.8e n_target=%12.8e left=%12.8e right=%12.8e", num_reruns, bp->id, h_init, h_old, h_new, f, f_prime, n_sum, @@ -1230,7 +1244,7 @@ void runner_do_ghost(struct runner *r, struct cell *c, const int offset, hydro_end_density(p, cosmo); adaptive_softening_end_density(p, e->gravity_properties); mhd_end_density(p, cosmo); - chemistry_end_density(p, chemistry, cosmo); + chemistry_end_density(p, xp, chemistry, cosmo); star_formation_end_density(p, xp, star_formation, cosmo); /* Are we using the alternative definition of the @@ -1360,7 +1374,7 @@ void runner_do_ghost(struct runner *r, struct cell *c, const int offset, if (num_reruns > max_smoothing_iter - 10) { message( - "Smoothing length convergence problem: iter=%d p->id=%lld " + "Smoothing length convergence problem gas: iter=%d p->id=%lld " "h_init=%12.8e h_old=%12.8e h_new=%12.8e f=%f f_prime=%f " "n_sum=%12.8e n_target=%12.8e left=%12.8e right=%12.8e", num_reruns, p->id, h_init, h_old, h_new, f, f_prime, n_sum, @@ -1920,7 +1934,7 @@ void runner_do_sinks_density_ghost(struct runner *r, struct cell *c, if (num_reruns > max_smoothing_iter - 10) { message( - "Smoothing length convergence problem: iter=%d p->id=%lld " + "Smoothing length convergence problem sink: iter=%d p->id=%lld " "h_init=%12.8e h_old=%12.8e h_new=%12.8e f=%f f_prime=%f " "n_sum=%12.8e n_target=%12.8e left=%12.8e right=%12.8e", num_reruns, sp->id, h_init, h_old, h_new, f, f_prime, n_sum, diff --git a/src/runner_others.c b/src/runner_others.c index 86f1d2ae5b..5d5d87d16d 100644 --- a/src/runner_others.c +++ b/src/runner_others.c @@ -818,7 +818,7 @@ void runner_do_end_hydro_force(struct runner *r, struct cell *c, int timer) { hydro_end_force(p, cosmo); mhd_end_force(p, cosmo); timestep_limiter_end_force(p); - chemistry_end_force(p, cosmo, with_cosmology, e->time, dt, + chemistry_end_force(p, xp, cosmo, with_cosmology, e->time, dt, e->chemistry); /* Apply the forcing terms (if any) */ @@ -1242,9 +1242,12 @@ void runner_do_rt_tchem(struct runner *r, struct cell *c, int timer) { const int with_cosmology = (e->policy & engine_policy_cosmology); struct rt_props *rt_props = e->rt_props; const struct hydro_props *hydro_props = e->hydro_properties; + const struct entropy_floor_properties *entropy_floor_props = e->entropy_floor; const struct cosmology *cosmo = e->cosmology; const struct phys_const *phys_const = e->physical_constants; const struct unit_system *us = e->internal_units; + const struct cooling_function_data *cooling = e->cooling_func; + const double time = e->time; /* Anything to do here? */ if (count == 0) return; @@ -1284,6 +1287,8 @@ void runner_do_rt_tchem(struct runner *r, struct cell *c, int timer) { const double dt = rt_part_dt(ti_begin, ti_end, e->time_base, with_cosmology, cosmo); + const double dt_therm = + rt_part_dt_therm(ti_begin, ti_end, e->time_base, with_cosmology, cosmo); #ifdef SWIFT_DEBUG_CHECKS if (ti_begin != ti_current_subcycle) error( @@ -1298,7 +1303,8 @@ void runner_do_rt_tchem(struct runner *r, struct cell *c, int timer) { rt_finalise_transport(p, rt_props, dt, cosmo); /* And finally do thermochemistry */ - rt_tchem(p, xp, rt_props, cosmo, hydro_props, phys_const, us, dt); + rt_tchem(p, xp, rt_props, cosmo, hydro_props, entropy_floor_props, + phys_const, cooling, us, dt, dt_therm, time); } } diff --git a/src/runner_time_integration.c b/src/runner_time_integration.c index f3c2718ffb..dc31950e65 100644 --- a/src/runner_time_integration.c +++ b/src/runner_time_integration.c @@ -90,6 +90,7 @@ void runner_do_kick1(struct runner *r, struct cell *c, const int timer) { const struct cosmology *cosmo = e->cosmology; const struct hydro_props *hydro_props = e->hydro_properties; const struct entropy_floor_properties *entropy_floor = e->entropy_floor; + const struct feedback_props *feedback_props = e->feedback_props; const int periodic = e->s->periodic; const int with_cosmology = (e->policy & engine_policy_cosmology); struct part *restrict parts = c->hydro.parts; @@ -166,6 +167,9 @@ void runner_do_kick1(struct runner *r, struct cell *c, const int timer) { ti_current); #endif + /* Rennehan: Here we recouple the wind particles */ + feedback_recouple_part(p, xp, e, with_cosmology, cosmo, feedback_props); + /* Time intervals for this half-kick */ const double dt_kick_grav = kick_get_grav_kick_dt( ti_begin, ti_end, time_base, with_cosmology, cosmo); @@ -1394,15 +1398,20 @@ void runner_do_limiter(struct runner *r, struct cell *c, int force, /* Bip, bip, bip... wake-up time */ if (p->limiter_data.wakeup != time_bin_not_awake) { - if (!part_is_active(p, e) && p->limiter_data.to_be_synchronized) { + /*if (!part_is_active(p, e) && p->limiter_data.to_be_synchronized) { warning( "Not limiting particle with id %lld because it needs to be " - "synced.", - p->id); + "synced tdel=%g dec=%d.", + p->id, + // MATTHIEU: TODO: Fix this +#ifdef FEEDBACK_KIARA + p->feedback_data.decoupling_delay_time, p->decoupled +#else + 0., 0 +#endif + ); continue; - } - - // message("Limiting particle %lld in cell %lld", p->id, c->cellID); + }*/ /* Apply the limiter and get the new end of time-step */ const integertime_t ti_end_new = timestep_limit_part(p, xp, e); diff --git a/src/sink/GEAR/sink_getters.h b/src/sink/GEAR/sink_getters.h index ea3db715ee..eaec2b43c4 100644 --- a/src/sink/GEAR/sink_getters.h +++ b/src/sink/GEAR/sink_getters.h @@ -106,6 +106,9 @@ INLINE static float sink_get_physical_div_v_from_part( div_v = (1. / 3.) * (p->viscosity.velocity_gradient[0][0] + p->viscosity.velocity_gradient[1][1] + p->viscosity.velocity_gradient[2][2]); +#elif MAGMA2_SPH + /* Copy the velocity divergence */ + div_v = hydro_get_physical_div_v(p, cosmo); #elif HOPKINS_PU_SPH div_v = p->density.div_v; #elif defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH) diff --git a/src/space.c b/src/space.c index 11a233b231..ad6293f8c8 100644 --- a/src/space.c +++ b/src/space.c @@ -853,15 +853,18 @@ void space_convert_rt_quantities_mapper(void *restrict map_data, int count, const struct phys_const *restrict phys_const = e->physical_constants; const struct unit_system *restrict iu = e->internal_units; const struct cosmology *restrict cosmo = e->cosmology; + const struct cooling_function_data *cool_func = e->cooling_func; struct part *restrict parts = (struct part *)map_data; + const ptrdiff_t delta = parts - s->parts; + struct xpart *restrict xp = s->xparts + delta; /* Loop over all the particles ignoring the extra buffer ones for on-the-fly * creation */ for (int k = 0; k < count; k++) { if (parts[k].time_bin <= num_time_bins) - rt_convert_quantities(&parts[k], rt_props, hydro_props, phys_const, iu, - cosmo); + rt_convert_quantities(&parts[k], &xp[k], rt_props, hydro_props, phys_const, iu, + cool_func, cosmo); } } diff --git a/src/space_first_init.c b/src/space_first_init.c index 3232e445f0..ad5831c92e 100644 --- a/src/space_first_init.c +++ b/src/space_first_init.c @@ -130,6 +130,9 @@ void space_first_init_parts_mapper(void *restrict map_data, int count, cooling_first_init_part(phys_const, us, hydro_props, cosmo, cool_func, &p[k], &xp[k]); + /* And the feedback */ + feedback_first_init_part(&p[k], &xp[k]); + /* And the tracers */ tracers_first_init_xpart(&p[k], &xp[k], us, phys_const, cosmo, hydro_props, cool_func); diff --git a/src/star_formation.h b/src/star_formation.h index c96fc3a53f..f598b56f95 100644 --- a/src/star_formation.h +++ b/src/star_formation.h @@ -43,6 +43,9 @@ #elif defined(STAR_FORMATION_GEAR) #define swift_star_formation_model_creates_stars 1 #include "./star_formation/GEAR/star_formation.h" +#elif defined(STAR_FORMATION_KIARA) +#define swift_star_formation_model_creates_stars 1 +#include "./star_formation/KIARA/star_formation.h" #else #error "Invalid choice of star formation law" #endif diff --git a/src/star_formation/GEAR/star_formation.h b/src/star_formation/GEAR/star_formation.h index a4f96e77ea..f49460b671 100644 --- a/src/star_formation/GEAR/star_formation.h +++ b/src/star_formation/GEAR/star_formation.h @@ -465,6 +465,9 @@ __attribute__((always_inline)) INLINE static void star_formation_end_density( xp->sf_data.div_v = (1. / 3.) * (p->viscosity.velocity_gradient[0][0] + p->viscosity.velocity_gradient[1][1] + p->viscosity.velocity_gradient[2][2]); +#elif MAGMA2_SPH + /* Copy the velocity divergence */ + xp->sf_data.div_v = hydro_get_physical_div_v(p, cosmo); #elif HOPKINS_PU_SPH xp->sf_data.div_v = p->density.div_v; #elif defined(GIZMO_MFV_SPH) || defined(GIZMO_MFM_SPH) diff --git a/src/star_formation/KIARA/star_formation.h b/src/star_formation/KIARA/star_formation.h new file mode 100644 index 0000000000..78d9859711 --- /dev/null +++ b/src/star_formation/KIARA/star_formation.h @@ -0,0 +1,1114 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Folkert Nobels (nobels@strw.leidenuniv.nl) + * 2023 Doug Rennehan (douglas.rennehan@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + *******************************************************************************/ +#ifndef SWIFT_KIARA_STAR_FORMATION_H +#define SWIFT_KIARA_STAR_FORMATION_H + +/* Local includes */ +#include "adiabatic_index.h" +#include "chemistry.h" +#include "cooling.h" +#include "cosmology.h" +#include "engine.h" +#include "entropy_floor.h" +#include "equation_of_state.h" +#include "exp10.h" +#include "fof.h" +#include "hydro.h" +#include "parser.h" +#include "part.h" +#include "physical_constants.h" +#include "random.h" +#include "stars.h" +#include "units.h" + +#define star_formation_need_update_dx_max 0 + +/** + * @file src/star_formation/KIARA/star_formation.h + * @brief Star formation model used in the KIARA model + */ + +/** + * @brief Functional form of the star formation law and H2 model + */ +enum star_formation_H2_model { + /*z > 6) slope = FIRE_eta_lower_slope_EOR; + if (m_star > FIRE_eta_break) { + slope = FIRE_eta_upper_slope; + } + + float eta = FIRE_eta_normalization * powf(m_star / FIRE_eta_break, slope); + + const float a_suppress_inv = (1.f + fabs(wind_eta_suppression_redshift)); + if (wind_eta_suppression_redshift > 0 && + cosmo->z > wind_eta_suppression_redshift) { + eta *= cosmo->a * cosmo->a * a_suppress_inv * a_suppress_inv; + } else if (wind_eta_suppression_redshift < 0) { + eta *= expf(-powf(cosmo->a * a_suppress_inv, -3.f)); + } + + return fmax(eta, 0.f); +} + +/** + * @brief Calculate if the satisfies the conditions for star formation. + * + * @param starform the star formation law properties to use. + * @param p the gas particles. + * @param xp the additional properties of the gas particles. + * @param phys_const the physical constants in internal units. + * @param cosmo the cosmological parameters and properties. + * @param hydro_props The properties of the hydro scheme. + * @param us The internal system of units. + * @param cooling The cooling data struct. + * @param entropy_floor_props The entropy floor assumed in this run. + */ +INLINE static int star_formation_is_star_forming_subgrid( + const struct part *p, const struct xpart *xp, + const struct star_formation *starform, const struct phys_const *phys_const, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct unit_system *us, const struct cooling_function_data *cooling, + const struct entropy_floor_properties *entropy_floor_props) { + + const double number_density_to_cgs = + units_cgs_conversion_factor(us, UNIT_CONV_NUMBER_DENSITY); + + /* Get the Hydrogen mass fraction */ + const double XH = chemistry_get_metal_mass_fraction_for_star_formation( + p)[chemistry_element_H]; + + /* Get the subgrid properties + * Note these are both in physical frame already */ + const double subgrid_T_cgs = cooling_get_subgrid_temperature(p, xp); + const double subgrid_rho = cooling_get_subgrid_density(p, xp); + const double subgrid_n_H = subgrid_rho * XH / phys_const->const_proton_mass; + const double subgrid_n_H_cgs = subgrid_n_H * number_density_to_cgs; + + /* Now, determine whether we are very cold or (cold and dense) enough + * + * This would typically be (T < 10^3 OR (T < 10^4.5 AND n_H > 10)) + * with T and n_H subgrid properties. + * + * Recall that particles above the EoS have T_sub = T and rho_sub = rho. + */ + return ((subgrid_T_cgs < starform->subgrid_thresh.T_threshold) && + (subgrid_n_H_cgs > starform->subgrid_thresh.nH_threshold)); +} + +/** + * @brief Calculate if the gas particle satisfies the conditions for star + * formation. + * + * @param starform the star formation law properties to use. + * @param p the gas particles. + * @param xp the additional properties of the gas particles. + * @param phys_const the physical constants in internal units. + * @param cosmo the cosmological parameters and properties. + * @param hydro_props The properties of the hydro scheme. + * @param us The internal system of units. + * @param cooling The cooling data struct. + * @param entropy_floor_props The entropy floor assumed in this run. + */ +INLINE static int star_formation_is_star_forming( + const struct part *p, const struct xpart *xp, + const struct star_formation *starform, const struct phys_const *phys_const, + const struct cosmology *cosmo, const struct hydro_props *hydro_props, + const struct unit_system *us, const struct cooling_function_data *cooling, + const struct entropy_floor_properties *entropy_floor_props) { + + /* Decide whether we should form stars or not */ + + /* No star formation for particles in the wind */ + if (p->decoupled) return 0; + + /* No star formation when outside subgrid model */ + if (cooling_get_subgrid_temperature(p, xp) <= 0.f) return 0; + + /* Minimal density (converted from mean baryonic density) + * for star formation */ + const float rho_mean_b_times_min_over_den = + cosmo->mean_density_Omega_b * starform->min_over_den; + + /* Physical density of the particle */ + const float physical_density = hydro_get_physical_density(p, cosmo); + + /* Check overdensity criterion */ + if (physical_density < rho_mean_b_times_min_over_den) return 0; + + return star_formation_is_star_forming_subgrid(p, xp, starform, phys_const, + cosmo, hydro_props, us, cooling, + entropy_floor_props); +} + +/** + * @brief Compute the star-formation rate of a given particle and store + * it into the #part. The star formation is calculated as a simple + * Schmidt law with an efficiency per free-fall time that can be specified, + * the free-fall time is based on the total SPH density. + * + * @param p #part. + * @param xp the #xpart. + * @param starform the star formation law properties to use + * @param phys_const the physical constants in internal units. + * @param hydro_props The properties of the hydro scheme. + * @param cosmo the cosmological parameters and properties. + * @param dt_star The time-step of this particle. + */ +INLINE static void star_formation_compute_SFR_schmidt_law( + struct part *p, struct xpart *xp, const struct star_formation *starform, + const struct phys_const *phys_const, const struct hydro_props *hydro_props, + const struct cosmology *cosmo, const double dt_star) { + + /* Mass density of this particle */ + // const float physical_density = cooling_get_subgrid_density(p, xp); + const float physical_density = cooling_get_subgrid_density(p, xp); + + /* Calculate the SFR per gas mass */ + const float SFRpergasmass = + starform->schmidt_law.mdot_const * sqrt(physical_density); + + /* Store the SFR */ + p->sf_data.SFR = p->sf_data.H2_fraction * SFRpergasmass * hydro_get_mass(p); +} + +/** + * @brief Compute the star-formation rate of a given particle and store + * it into the #part. The star formation is calculated based on + * the model of Wada & Norman 2007, eq. 17 + * + * @param p #part. + * @param xp the #xpart. + * @param starform the star formation law properties to use + * @param phys_const the physical constants in internal units. + * @param hydro_props The properties of the hydro scheme. + * @param cosmo the cosmological parameters and properties. + * @param dt_star The time-step of this particle. + */ +INLINE static void star_formation_compute_SFR_wn07( + struct part *p, struct xpart *xp, const struct star_formation *starform, + const struct phys_const *phys_const, const struct hydro_props *hydro_props, + const struct cosmology *cosmo, const double dt_star) { + + p->sf_data.SFR = 0.f; + /* Mean density of the gas described by a lognormal PDF (i.e. dense gas). */ + const double rho_V = cooling_get_subgrid_density(p, xp); + const double rho_0 = starform->lognormal.rho0; + + /* Collect information about galaxy that the particle belongs to */ + const float galaxy_mstar = p->galaxy_data.stellar_mass; + const float galaxy_ssfr = p->galaxy_data.specific_sfr; + const float galaxy_sfr = galaxy_mstar * galaxy_ssfr; + + /* Density is too low, so no SF */ + if (rho_V <= 1.001 * rho_0) return; + + /* Calculate SFR efficiency, which WN07 suggests should + * scale from 0.1 for starbursts to 0.001 for normal galaxies + */ + double epsc = 0.01; + /* if it's not in a galaxy, assume it's a small proto-galaxy so starburst */ + if (galaxy_mstar == 0.f) { + epsc = 0.1; + } else if (starform->lognormal.wn07_epsc_method == 0) { /* constant value */ + epsc = 0.01; + } + /* deviation from all-galaxies main sequence taken from Koprowski+24 */ + else if (starform->lognormal.wn07_epsc_method == 1 || + starform->lognormal.wn07_epsc_method == 2) { + const double mstar = galaxy_mstar * starform->lognormal.to_solar_mass; + const double sfrmax_data = pow(10., 3.69 - 3.81 * exp(-0.47 * cosmo->z)); + const double M0_data = pow(10., 11.91 - 2.48 * exp(-0.44 * cosmo->z)); + const double sfr_data = sfrmax_data / (1. + (M0_data / mstar)); + const double sfr_msun_per_yr = + galaxy_sfr * starform->lognormal.to_msun_per_yr; + if (starform->lognormal.wn07_epsc_method == 1) { + epsc = 0.01 * sfr_msun_per_yr / sfr_data; + } else { + epsc = 0.01 * sqrtf(sfr_msun_per_yr / sfr_data); + } + } + /* deviation from SF-galaxies main sequence data taken from Koprowski+24 */ + else if (starform->lognormal.wn07_epsc_method == 3 || + starform->lognormal.wn07_epsc_method == 4) { + const double mstar = galaxy_mstar * starform->lognormal.to_solar_mass; + const double sfrmax_data = pow(10., 3.47 - 3.13 * exp(-0.56 * cosmo->z)); + const double M0_data = pow(10., 11.69 - 1.66 * exp(-0.53 * cosmo->z)); + const double sfr_data = sfrmax_data / (1. + (M0_data / mstar)); + const double sfr_msun_per_yr = + galaxy_sfr * starform->lognormal.to_msun_per_yr; + epsc = 0.01 * sqrt(sfr_msun_per_yr / sfr_data); + } + /* based on direct scaling with sSFR */ + else if (starform->lognormal.wn07_epsc_method == 5) { + epsc = + min(galaxy_ssfr * starform->lognormal.time_to_year_inverse * 1.e7, 0.1); + } + /* Scale with galaxy SFR instead of SSFR */ + else if (starform->lognormal.wn07_epsc_method == 6) { + const float sfr_msun_per_yr = + galaxy_sfr * starform->lognormal.to_msun_per_yr; + epsc = min(0.001 * cbrt(sfr_msun_per_yr * 300.), 0.1); + epsc = max(epsc, 0.001); + } else { + error("wn07_epsc_method value of %d not recognised", + starform->lognormal.wn07_epsc_method); + } + + /* Restrict range to that specified in WN07 */ + epsc = min(epsc, 0.1); + epsc = max(epsc, 0.001); + + /* Calculate parameters in WN07 model */ + const double sigma = sqrt(2. * log(rho_V / rho_0)); + const double z_num = log(starform->lognormal.rhocrit / rho_0) - sigma * sigma; + const double z_den = sqrt(2.) * sigma; + const double z = z_num / z_den; + + /* fraction of dense gas */ + const double fc = 0.5 * erfc(z); + + /* This is the SFR density from eq. 17, except use actual + * density rho_V not estimated density rho_c. 3pi/32=0.294524. + */ + const double rhosfr = epsc * sqrt(0.294524 * phys_const->const_newton_G * rho_V) * fc; + + /* multiply by dense gas effective volume to get SFR (rho_V appears in both + * this eqn and previous one so it is cancelled out for efficiency) */ + const double sfr = + rhosfr * (p->cooling_data.subgrid_fcold * hydro_get_mass(p)); + + /* Multiply by the H2 fraction */ + p->sf_data.SFR = starform->lognormal.epsilon * sfr * p->sf_data.H2_fraction; +} + +/** + * @brief Compute the star-formation rate of a given particle and store + * it into the #part. The star formation is calculated by assuming + * a lognormal density distribution with a mean density given by the + * subgrid density, above a critical density threshold that is an + * input parameter. Lognormal params based on sims by Wada & Norman 2007. + * + * @param p #part. + * @param xp the #xpart. + * @param starform the star formation law properties to use + * @param phys_const the physical constants in internal units. + * @param hydro_props The properties of the hydro scheme. + * @param cosmo the cosmological parameters and properties. + * @param dt_star The time-step of this particle. + */ +INLINE static void star_formation_compute_SFR_lognormal( + struct part *p, struct xpart *xp, const struct star_formation *starform, + const struct phys_const *phys_const, const struct hydro_props *hydro_props, + const struct cosmology *cosmo, const double dt_star) { + + /* Limit for Eq. 12 in Wada+Norman 2007 */ + const double rho_limit = 1.001 * starform->lognormal.rho0; + + /* H2 fraction in the particle */ + const double f_H2 = p->sf_data.H2_fraction; + + /* Mean density of the gas described by a lognormal PDF + * (i.e. subgrid ISM gas) PHYSICAL */ + double rho_V = cooling_get_subgrid_density(p, xp); + + /* SF cannot occur below characteristic + * density~1 cm^-3 (formulae below give nans) + */ + if (rho_V < rho_limit || f_H2 <= 0.) { + p->sf_data.SFR = 0.f; + return; + } + + /* Calculate the SFR based on a lognormal density distribution at rho0 with a + * threshold density for star formation rhocrit. sigma comes from WN07 model. + */ + const double rho_0 = starform->lognormal.rho0; + /* Mass-averaged density for cold phase */ + const double sigma = sqrt(log(2. * rho_V / rho_0)); + const double z_num = + log(starform->lognormal.rhocrit / rho_0) - (sigma * sigma); + const double z_den = sqrt(2.) * sigma; + const double z = z_num / z_den; + + /* Calculate lognormal fraction from the WN07 model */ + const double f_c = 0.5 * erfc(z); + + const double rho_phys = hydro_get_physical_density(p, cosmo); + + /* Calculate the SFR per gas mass, using lognormal mass fraction above + * rhocrit as efficiency + */ + const double sSFR = f_c * starform->lognormal.ff_const_inv * sqrt(rho_phys); + + const double mass = f_H2 * p->cooling_data.subgrid_fcold * hydro_get_mass(p); + + /* Store the SFR */ + p->sf_data.SFR = starform->lognormal.epsilon * sSFR * mass; +} + +/** + * @brief Compute the star-formation rate of a given particle and store + * it into the #part. This involves computing H2 fraction, then using + * the chosen SF model to compute the SFR. + * + * @param p #part. + * @param xp the #xpart. + * @param starform the star formation law properties to use + * @param phys_const the physical constants in internal units. + * @param hydro_props The properties of the hydro scheme. + * @param cosmo the cosmological parameters and properties. + * @param dt_star The time-step of this particle. + */ +INLINE static void star_formation_compute_SFR( + struct part *p, struct xpart *xp, const struct star_formation *starform, + const struct phys_const *phys_const, const struct hydro_props *hydro_props, + const struct cosmology *cosmo, const double dt_star) { + + /* Abort early if time-step size is 0 */ + if (dt_star == 0.) { + p->sf_data.SFR = 0.f; + return; + } + + /* Physical gas density of the particle */ + const double physical_density = hydro_get_physical_density(p, cosmo); + + /* Compute the H2 fraction of the particle */ + switch (starform->H2_model) { + case kiara_star_formation_density_thresh: + p->sf_data.H2_fraction = 1.f; + break; + case kiara_star_formation_kmt_model: + p->sf_data.H2_fraction = 0.f; + + /* gas_sigma is double because we do some cgs conversions */ + double gas_sigma = 0.f; + float gas_Z = 0.f; + float chi = 0.f; + float s = 0.f; + float clumping_factor = 30.f; + float gas_gradrho_mag = 0.f; + + gas_Z = p->chemistry_data.metal_mass_fraction_total; + gas_Z /= starform->Z_solar; + if (gas_Z < 0.01f) { + gas_Z = 0.01f; + } + + if (physical_density > 0.f) { + gas_gradrho_mag = sqrtf(p->rho_gradient[0] * p->rho_gradient[0] + + p->rho_gradient[1] * p->rho_gradient[1] + + p->rho_gradient[2] * p->rho_gradient[2]); + + if (gas_gradrho_mag > 0) { + gas_sigma = (p->rho * p->rho) / gas_gradrho_mag; + + /* surface density must be in Msun/pc^2 */ + gas_sigma *= + starform->surface_rho_to_Msun_per_parsec2 * cosmo->a2_inv; + + /* Lower clumping factor with higher resolution + (CF = 30 @ ~1 kpc resolution) */ + clumping_factor *= starform->clumping_factor_scaling; + if (clumping_factor < 1.f) { + clumping_factor = 1.f; + } + + /* chi ~ 1/R ~ 1/clump from KG11 eq. 3 */ + chi = 0.756f * (1.f + 3.1f * powf(gas_Z, 0.365f)) * + (30.f / clumping_factor); + s = logf(1.f + 0.6f * chi + 0.01f * chi * chi) / + (0.0396f * powf(clumping_factor, 2.f / 3.f) * gas_Z * gas_sigma); + + if (s > 0.f && s < 2.f) { + p->sf_data.H2_fraction = 1.f - 0.75f * (s / (1.f + 0.25f * s)); + } + } + } + break; + case kiara_star_formation_grackle_model: +#if COOLING_GRACKLE_MODE >= 2 + p->sf_data.H2_fraction = + p->cooling_data.subgrid_fcold * + (xp->cooling_data.H2I_frac + xp->cooling_data.H2II_frac); +#else + p->sf_data.H2_fraction = 1. - xp->cooling_data.HI_frac; +#endif + break; + default: + error("Invalid H2 model in star formation!"); + break; + } + + /* Now compute the star formation rate and save it to the particle */ + switch (starform->SF_model) { + case kiara_star_formation_SchmidtLaw: + star_formation_compute_SFR_schmidt_law(p, xp, starform, phys_const, + hydro_props, cosmo, dt_star); + break; + case kiara_star_formation_WadaNorman: + star_formation_compute_SFR_wn07(p, xp, starform, phys_const, hydro_props, + cosmo, dt_star); + break; + case kiara_star_formation_lognormal: + star_formation_compute_SFR_lognormal(p, xp, starform, phys_const, + hydro_props, cosmo, dt_star); + break; + default: + error("Invalid SF model in star formation!!!"); + break; + } +} + +/** + * @brief Returns the number of new star particles to create per SF event. + * + * @param p The #part. + * @param xp The #xpart. + * @param starform The properties of the star formation model. + * + * @return The number of extra star particles to generate per gas particles. + * (return 0 if the gas particle itself is to be converted) + */ +INLINE static int star_formation_number_spart_to_spawn( + struct part *p, struct xpart *xp, const struct star_formation *starform) { + + return 0; +} + +/** + * @brief Returns the number of particles to convert per SF event. + * + * @param p The #part. + * @param xp The #xpart. + * @param starform The properties of the star formation model. + * + * @return The number of particles to generate per gas particles. + * (This has to be 0 or 1) + */ +INLINE static int star_formation_number_spart_to_convert( + const struct part *p, const struct xpart *xp, + const struct star_formation *starform) { + + return 1; +} + +/** + * @brief Decides whether a particle should be converted into a + * star or not. + * + * Equation 21 of Schaye & Dalla Vecchia 2008. + * + * @param p The #part. + * @param xp The #xpart. + * @param starform The properties of the star formation model. + * @param e The #engine (for random numbers). + * @param dt_star The time-step of this particle + * @param star_prob The probability of converting to a star particle. + * @return 1 if a conversion should be done, 0 otherwise. + */ +INLINE static int star_formation_should_convert_to_star( + const struct part *p, const struct xpart *xp, + const struct star_formation *starform, const struct engine *e, + const double dt_star) { + + /* Calculate the propability of forming a star */ + const double prob = max(p->sf_data.SFR, 0.f) * dt_star / hydro_get_mass(p); + + /* Get a unique random number between 0 and 1 for star formation */ + const double random_number = + random_unit_interval(p->id, e->ti_current, random_number_star_formation); + + /* Have we been lucky and need to form a star? */ + return (prob > random_number); +} + +/** + * @brief Decides whether a new particle should be created or if the hydro + * particle needs to be transformed. + * + * @param p The #part. + * @param xp The #xpart. + * @param starform The properties of the star formation model. + * + * @return 1 if a new spart needs to be created. + */ +INLINE static int star_formation_should_spawn_spart( + struct part *p, struct xpart *xp, const struct star_formation *starform) { + return 0; +} + +/** + * @brief Update the SF properties of a particle that is not star forming. + * + * @param p The #part. + * @param xp The #xpart. + * @param e The #engine. + * @param starform The properties of the star formation model. + * @param with_cosmology Are we running with cosmology switched on? + */ +INLINE static void star_formation_update_part_not_SFR( + struct part *p, struct xpart *xp, const struct engine *e, + const struct star_formation *starform, const int with_cosmology) { + + /* Check if it is the first time steps after star formation */ + if (p->sf_data.SFR > 0.f) { + + /* Record the current time as an indicator of when this particle was last + star-forming. */ + if (with_cosmology) { + p->sf_data.SFR = -e->cosmology->a; + } else { + p->sf_data.SFR = -e->time; + } + } +} + +/** + * @brief Copies the properties of the gas particle over to the + * star particle + * + * @param e The #engine + * @param p the gas particles. + * @param xp the additional properties of the gas particles. + * @param sp the new created star particle with its properties. + * @param starform the star formation law properties to use. + * @param cosmo the cosmological parameters and properties. + * @param with_cosmology if we run with cosmology. + * @param phys_const the physical constants in internal units. + * @param hydro_props The properties of the hydro scheme. + * @param us The internal system of units. + * @param cooling The cooling data struct. + * @param chem_data The global properties of the chemistry scheme. + * @param convert_part Did we convert a part (or spawned one)? + * @param (return) displacement The 3D displacement vector of the star with + * respect to the sink position. + */ +INLINE static void star_formation_copy_properties( + const struct part *p, const struct xpart *xp, struct spart *sp, + const struct engine *e, const struct star_formation *starform, + const struct cosmology *cosmo, const int with_cosmology, + const struct phys_const *phys_const, const struct hydro_props *hydro_props, + const struct unit_system *us, const struct cooling_function_data *cooling, + const struct chemistry_global_data *chem_data, const int convert_part, + float displacement[3]) { + + /* Store the current mass */ + sp->mass = hydro_get_mass(p); + + /* Store the current mass as the initial mass */ + sp->mass_init = hydro_get_mass(p); + + /* Store either the birth_scale_factor or birth_time depending */ + if (with_cosmology) { + sp->birth_scale_factor = cosmo->a; + } else { + sp->birth_time = e->time; + } + + /* Move over the splitting data */ + sp->split_data = xp->split_data; + + /* Store the chemistry struct in the star particle */ + sp->chemistry_data = p->chemistry_data; + + /* Store the tracers data */ + sp->tracers_data = xp->tracers_data; + + /* Store the birth density in the star particle */ + sp->birth_density = cooling_get_subgrid_density(p, xp); + + /* Store the birth temperature in the star particle */ + sp->birth_temperature = cooling_get_subgrid_temperature(p, xp); + + /* Flag that this particle has not done feedback yet + sp->feedback_data.physical_energy_reservoir = 0.; + sp->feedback_data.N_launched = 0; + sp->feedback_data.mass_to_launch = 0.f; + sp->feedback_data.total_mass_kicked = 0.f; + sp->feedback_data.wind_velocity = 0.f; + sp->feedback_data.eta_suppression_factor = 1.f;*/ + sp->last_enrichment_time = sp->birth_time; + sp->count_since_last_enrichment = 0; + + /* Copy FoF galaxy data from spawning particle */ + sp->galaxy_data.stellar_mass = p->galaxy_data.stellar_mass; + sp->galaxy_data.gas_mass = p->galaxy_data.gas_mass; + sp->galaxy_data.specific_sfr = p->galaxy_data.specific_sfr; + + /* Slightly displace spawned particle to avoid zeros */ + if (1) { + + const float max_displacement = 0.1; + const double delta_x = + 2.f * random_unit_interval(sp->id, e->ti_current, + (enum random_number_type)0) - + 1.f; + const double delta_y = + 2.f * random_unit_interval(sp->id, e->ti_current, + (enum random_number_type)1) - + 1.f; + const double delta_z = + 2.f * random_unit_interval(sp->id, e->ti_current, + (enum random_number_type)2) - + 1.f; + + /* Update the displacement */ + displacement[0] = delta_x * max_displacement * p->h; + displacement[1] = delta_y * max_displacement * p->h; + displacement[2] = delta_z * max_displacement * p->h; + + /* Move the spart */ + sp->x[0] += displacement[0]; + sp->x[1] += displacement[1]; + sp->x[2] += displacement[2]; + + /* Copy the position to the gpart */ + sp->gpart->x[0] = sp->x[0]; + sp->gpart->x[1] = sp->x[1]; + sp->gpart->x[2] = sp->x[2]; + } +} + +/** + * @brief initialization of the star formation law + * + * @param parameter_file The parsed parameter file + * @param phys_const Physical constants in internal units + * @param us The current internal system of units. + * @param hydro_props The propertis of the hydro model. + * @param cosmo The current cosmological model. + * @param entropy_floor The properties of the entropy floor used in this + * simulation. + * @param starform the star formation law properties to initialize + */ +INLINE static void starformation_init_backend( + struct swift_params *parameter_file, const struct phys_const *phys_const, + const struct unit_system *us, const struct hydro_props *hydro_props, + const struct cosmology *cosmo, + const struct entropy_floor_properties *entropy_floor, + struct star_formation *starform) { + + /* Get the Gravitational constant */ + const double G_newton = phys_const->const_newton_G; + + /* CGS density conversion */ + const double rho_to_cgs = units_cgs_conversion_factor(us, UNIT_CONV_DENSITY); + + /* Get the surface density unit Msun / pc^2 in internal units */ + const double Msun_per_pc2 = + phys_const->const_solar_mass / + (phys_const->const_parsec * phys_const->const_parsec); + + starform->surface_rho_to_Msun_per_parsec2 = 1. / Msun_per_pc2; + starform->conv_factor_surface_rho_to_cgs = + rho_to_cgs * units_cgs_conversion_factor(us, UNIT_CONV_LENGTH); + + /* Read the SF model we are using */ + char SF_model[32]; + parser_get_param_string(parameter_file, "KIARAStarFormation:SF_model", + SF_model); + + if (strstr(SF_model, "SchmidtLaw") != NULL) { + starform->SF_model = kiara_star_formation_SchmidtLaw; + } else if (strstr(SF_model, "WadaNorman") != NULL) { + starform->SF_model = kiara_star_formation_WadaNorman; + } else if (strstr(SF_model, "lognormal") != NULL) { + starform->SF_model = kiara_star_formation_lognormal; + } else { + error("Invalid SF model in SF params %s", SF_model); + } + + /* Read the H2 model we are using */ + char H2_model[32]; + parser_get_param_string(parameter_file, "KIARAStarFormation:H2_model", + H2_model); + + if (strstr(H2_model, "Thresh") != NULL) { + starform->H2_model = kiara_star_formation_density_thresh; + } else if (strstr(H2_model, "KMT") != NULL) { + starform->H2_model = kiara_star_formation_kmt_model; + } else if (strstr(H2_model, "Grackle") != NULL) { + starform->H2_model = kiara_star_formation_grackle_model; + } else { + error("Invalid H2 model in SF params %s", H2_model); + } + + /* Read the ISM subgrid clumping factor value at the resolved scale + * (KMT model only) + */ + starform->clumping_factor_scaling = parser_get_opt_param_double( + parameter_file, "KIARAStarFormation:clumping_factor_scaling", 30.f); + + /* Read the total metal mass fraction of the Sun */ + starform->Z_solar = parser_get_opt_param_double( + parameter_file, "KIARAStarFormation:Z_solar", 0.0134f); + + /* Read the critical density contrast from the parameter file*/ + starform->min_over_den = parser_get_param_double( + parameter_file, "KIARAStarFormation:min_over_density"); + + /* Read threshold properties */ + starform->subgrid_thresh.T_threshold = parser_get_param_double( + parameter_file, "KIARAStarFormation:threshold_temperature_K"); + + starform->subgrid_thresh.nH_threshold = parser_get_param_double( + parameter_file, "KIARAStarFormation:threshold_number_density_H_p_cm3"); + + /* Calculate the ff constant */ + const double ff_const = sqrt(3. * M_PI / (32. * G_newton)); + + if (starform->SF_model == kiara_star_formation_SchmidtLaw) { + /* Get the star formation efficiency */ + starform->schmidt_law.sfe = parser_get_param_double( + parameter_file, "KIARAStarFormation:star_formation_efficiency"); + + /* Calculate the constant */ + starform->schmidt_law.mdot_const = starform->schmidt_law.sfe / ff_const; + } else if (starform->SF_model == kiara_star_formation_WadaNorman || + starform->SF_model == kiara_star_formation_lognormal) { + + /* Critical density for SF (in physical cm^-3) for lognormal SF model */ + starform->lognormal.ncrit = parser_get_opt_param_double( + parameter_file, "KIARAStarFormation:lognormal_critical_density", 1.e3); + + /* code units */ + starform->lognormal.rhocrit = + starform->lognormal.ncrit * 1.673e-24 / rho_to_cgs; + + /* Set characeristic density of ISM lognormal (Table 1 of Wada+Norman07) + * in code units */ + starform->lognormal.rho0 = + pow(10., -1.5) * 1.98841e33 / (rho_to_cgs * pow(3.08567758149e18, 3.)); + + /* Like the star formation efficiency but to control for sub-grid factors */ + starform->lognormal.epsilon = parser_get_param_double( + parameter_file, "KIARAStarFormation:lognormal_epsilon"); + + /* used to scale epsilon_c (efficiency) in lognormal model to sSFR */ + starform->lognormal.time_to_year_inverse = + (365.25 * 24. * 60. * 60.) / + units_cgs_conversion_factor(us, UNIT_CONV_TIME); + + /* used to scale epsilon_c (efficiency) in lognormal model to SFR */ + starform->lognormal.to_solar_mass = + units_cgs_conversion_factor(us, UNIT_CONV_MASS) / 1.98841e33; + + /* used to scale epsilon_c (efficiency) in lognormal model to SFR */ + starform->lognormal.to_msun_per_yr = + units_cgs_conversion_factor(us, UNIT_CONV_SFR) / 1.98841e33 * + (365.25 * 24. * 60. * 60.); + + /* Calculate the constant needed for the free-fall time */ + starform->lognormal.ff_const_inv = 1. / ff_const; + + if (starform->SF_model == kiara_star_formation_WadaNorman) { + starform->lognormal.wn07_epsc_method = parser_get_opt_param_int( + parameter_file, "KIARAStarFormation:wn07_epsc_method", 0); + } + } +} + +/** + * @brief Prints the used parameters of the star formation law + * + * @param starform the star formation law properties. + * */ +INLINE static void starformation_print_backend( + const struct star_formation *starform) { + + message("Star formation model is KIARA"); + + message( + "Particles are eligible for star formation if their have " + "T < %e K, n_H > %e cm^-3, and overdensity > %e", + starform->subgrid_thresh.T_threshold, + starform->subgrid_thresh.nH_threshold, starform->min_over_den); + + if (starform->SF_model == kiara_star_formation_SchmidtLaw) { + message( + "Star formation law is a Schmidt law: Star formation " + "efficiency = %e", + starform->schmidt_law.sfe); + } else if (starform->SF_model == kiara_star_formation_WadaNorman) { + message( + "Star formation based on Wada+Norman 2007: critical " + "density (code units) = %e", + starform->lognormal.rhocrit); + } else if (starform->SF_model == kiara_star_formation_lognormal) { + message( + "Star formation based on lognormal density pdf: " + "critical density (code units) = %e" + "efficiency = %e", + starform->lognormal.rhocrit, starform->lognormal.epsilon); + } else { + error("Invalid SF model in star formation!!!"); + } +} + +/** + * @brief Return the star formation rate of a particle. + * Remember that SFR can be <0 because it stores last expansion factor + * when it was SF. + * + * @param p The particle. + * @param xp The extended data of the particle. + */ +INLINE static float star_formation_get_SFR(const struct part *p, + const struct xpart *xp) { + if (p->sf_data.SFR <= 0.) + return 0.f; + else + return p->sf_data.SFR; +} + +/** + * @brief Finishes the density calculation. + * + * Nothing to do here. We do not need to compute any quantity in the hydro + * density loop for the KIARA star formation model. + * + * @param p The particle to act upon + * @param xp The extra particle to act upon + * @param cd The global star_formation information. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void star_formation_end_density( + struct part *p, struct xpart *xp, const struct star_formation *cd, + const struct cosmology *cosmo) {} + +/** + * @brief Sets all particle fields to sensible values when the #part has 0 ngbs. + * + * Nothing to do here. We do not need to compute any quantity in the hydro + * density loop for the KIARA star formation model. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + * @param cd #star_formation containing star_formation informations. + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void +star_formation_part_has_no_neighbours(struct part *p, struct xpart *xp, + const struct star_formation *cd, + const struct cosmology *cosmo) {} + +/** + * @brief Sets the star_formation properties of the (x-)particles to a valid + * state to start the density loop. + * + * Nothing to do here. We do not need to compute any quantity in the hydro + * density loop for the KIARA star formation model. + * + * @param data The global star_formation information used for this run. + * @param p Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static void star_formation_init_part( + struct part *p, const struct star_formation *data) {} + +/** + * @brief Sets the star_formation properties of the (x-)particles to a valid + * start state at the beginning of the simulation after the ICs have been read. + * + * @param phys_const The physical constant in internal units. + * @param us The unit system. + * @param cosmo The current cosmological model. + * @param data The global star_formation information used for this run. + * @param p Pointer to the particle data. + * @param xp Pointer to the extended particle data. + */ +__attribute__((always_inline)) INLINE static void +star_formation_first_init_part(const struct phys_const *phys_const, + const struct unit_system *us, + const struct cosmology *cosmo, + const struct star_formation *data, + struct part *p, struct xpart *xp) { + /* This may need to be updated elsewhere */ + p->sf_data.H2_fraction = 0.f; + star_formation_init_part(p, data); +} + +/** + * @brief Split the star formation content of a particle into n pieces + * + * We only need to split the SFR if it is positive, i.e. it is not + * storing the redshift/time of last SF event. + * + * @param p The #part. + * @param xp The #xpart. + * @param n The number of pieces to split into. + */ +__attribute__((always_inline)) INLINE static void star_formation_split_part( + struct part *p, struct xpart *xp, const double n) { + + if (p->sf_data.SFR > 0.) p->sf_data.SFR /= n; +} + +/** + * @brief Deal with the case where no spart are available for star formation. + * + * @param e The #engine. + * @param p The #part. + * @param xp The #xpart. + */ +__attribute__((always_inline)) INLINE static void +star_formation_no_spart_available(const struct engine *e, const struct part *p, + const struct xpart *xp) { + /* Nothing to do, we just skip it and deal with it next step */ +} + +/** + * @brief Compute some information for the star formation model based + * on all the particles that were read in. + * + * This is called once on start-up of the code. + * + * Nothing to do here for KIARA. + * + * @param star_form The #star_formation structure. + * @param e The #engine. + */ +__attribute__((always_inline)) INLINE static void +star_formation_first_init_stats(struct star_formation *star_form, + const struct engine *e) {} + +#endif /* SWIFT_KIARA_STAR_FORMATION_H */ diff --git a/src/star_formation/KIARA/star_formation_debug.h b/src/star_formation/KIARA/star_formation_debug.h new file mode 100644 index 0000000000..64e8190724 --- /dev/null +++ b/src/star_formation/KIARA/star_formation_debug.h @@ -0,0 +1,31 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2022 Bert Vandenbroucke (bert.vandenbroucke@gmail.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_STAR_FORMATION_KIARA_DEBUG_H +#define SWIFT_STAR_FORMATION_KIARA_DEBUG_H + +__attribute__((always_inline)) INLINE static void star_formation_debug_particle( + const struct part *p, const struct xpart *xp) { + + if (xp != NULL) { + warning("[PID%lld] sf_data:", p->id); + warning("[PID%lld] SFR = %.3e", p->id, p->sf_data.SFR); + } +} + +#endif /* SWIFT_STAR_FORMATION_KIARA_DEBUG_H */ diff --git a/src/star_formation/KIARA/star_formation_iact.h b/src/star_formation/KIARA/star_formation_iact.h new file mode 100644 index 0000000000..e340fbb2a7 --- /dev/null +++ b/src/star_formation/KIARA/star_formation_iact.h @@ -0,0 +1,73 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_KIARA_STAR_FORMATION_IACT_H +#define SWIFT_KIARA_STAR_FORMATION_IACT_H + +/** + * @file KIARA/star_formation_iact.h + * @brief Density computation + */ + +/** + * @brief do star_formation computation after the runner_iact_density (symmetric + * version) + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void runner_iact_star_formation( + const float r2, const float dx[3], const float hi, const float hj, + struct part *restrict pi, struct part *restrict pj, const float a, + const float H) { + + /* Nothing to do here. We do not need to compute any quantity in the hydro + density loop for the KIARA star formation model. */ +} + +/** + * @brief do star_formation computation after the runner_iact_density (non + * symmetric version) + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param pi First particle. + * @param pj Second particle (not updated). + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_star_formation(const float r2, const float dx[3], + const float hi, const float hj, + struct part *restrict pi, + const struct part *restrict pj, float a, + float H) { + + /* Nothing to do here. We do not need to compute any quantity in the hydro + density loop for the KIARA star formation model. */ +} + +#endif /* SWIFT_KIARA_STAR_FORMATION_IACT_H */ diff --git a/src/star_formation/KIARA/star_formation_io.h b/src/star_formation/KIARA/star_formation_io.h new file mode 100644 index 0000000000..de7c9990b4 --- /dev/null +++ b/src/star_formation/KIARA/star_formation_io.h @@ -0,0 +1,95 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Folkert Nobels (nobels@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_STAR_FORMATION_KIARA_IO_H +#define SWIFT_STAR_FORMATION_KIARA_IO_H + +/* Config parameters. */ +#include "../config.h" + +/* Local includes */ +#include "io_properties.h" + +/** + * @brief Specifies which s-particle fields to read from a dataset + * + * @param sparts The s-particle array. + * @param list The list of i/o properties to read. + * + * @return num_fields The number of i/o fields to read. + */ +INLINE static int star_formation_read_particles(struct spart *sparts, + struct io_props *list) { + return 0; +} + +/** + * @brief Specifies which particle fields to write to a dataset + * + * @param parts The particle array. + * @param xparts The extended data particle array. + * @param list The list of i/o properties to write. + * + * @return Returns the number of fields to write. + */ +__attribute__((always_inline)) INLINE static int star_formation_write_particles( + const struct part *parts, const struct xpart *xparts, + struct io_props *list) { + + int num = 0; + + list[num] = io_make_output_field( + "StarFormationRates", FLOAT, 1, UNIT_CONV_MASS_PER_UNIT_TIME, 0.f, parts, + sf_data.SFR, + "If positive, star formation rates of the particles. If negative, stores " + "the last time/scale-factor at which the gas particle was star-forming. " + "If zero, the particle was never star-forming."); + num++; + +#if COOLING_GRACKLE_MODE < 2 + /* If using Kiara/Grackle then this is output in cooling_io since it is + * tracked in Grackle */ + list[num] = io_make_output_field( + "MolecularHydrogenFractions", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, parts, + sf_data.H2_fraction, "The H2 fraction of the gas particle. "); + num++; +#endif + + list[num] = io_make_output_field( + "InterstellarRadiationField", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, parts, + sf_data.G0, + "The interstellar radiation field strength in Habing units. "); + num++; + + return num; +} + +/** + * @brief Specifies which sparticle fields to write to a dataset + * + * @param sparts The star particle array. + * @param list The list of i/o properties to write. + * + * @return Returns the number of fields to write. + */ +__attribute__((always_inline)) INLINE static int +star_formation_write_sparticles(const struct spart *sparts, + struct io_props *list) { + return 0; +} +#endif /* SWIFT_STAR_FORMATION_KIARA_IO_H */ diff --git a/src/star_formation/KIARA/star_formation_logger.h b/src/star_formation/KIARA/star_formation_logger.h new file mode 100644 index 0000000000..a92598fb0a --- /dev/null +++ b/src/star_formation/KIARA/star_formation_logger.h @@ -0,0 +1,266 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Folkert Nobels (nobels@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + *******************************************************************************/ +#ifndef SWIFT_KIARA_STARFORMATION_LOGGER_H +#define SWIFT_KIARA_STARFORMATION_LOGGER_H + +/* Some standard headers */ +#include + +/* Local includes */ +#include "cell.h" +#include "hydro.h" +#include "part.h" +#include "star_formation_logger_struct.h" +#include "units.h" + +/** + * @brief Update the stellar mass in the current cell after creating + * the new star particle spart sp + * + * @param sp new created star particle + * @param sf the star_formation_history struct of the current cell + */ +INLINE static void star_formation_logger_log_new_spart( + const struct spart *sp, struct star_formation_history *sf) { + + /* Add mass of created sparticle to the total stellar mass in this cell*/ + sf->new_stellar_mass += sp->mass; +} + +/** + * @brief Initialize the star formation history struct in the case the cell is + * inactive + * + * @param sf the star_formation_history struct we want to initialize + */ +INLINE static void star_formation_logger_log_inactive_cell( + struct star_formation_history *sf) { + + /* Initialize the stellar mass to zero*/ + sf->new_stellar_mass = 0.f; + + /* The active SFR becomes the inactive SFR */ + sf->SFR_inactive += sf->SFR_active; + + /* Initialize the active SFR */ + sf->SFR_active = 0.f; + + /* Initialize the SFR*dt active */ + sf->SFRdt_active = 0.f; +} + +/** + * @brief add a star formation history struct to an other star formation history + * struct + * + * @param sf_add the star formation struct which we want to add to the star + * formation history + * @param sf_update the star formation structure which we want to update + */ +INLINE static void star_formation_logger_add( + struct star_formation_history *sf_update, + const struct star_formation_history *sf_add) { + + /* Update the SFH structure */ + sf_update->new_stellar_mass += sf_add->new_stellar_mass; + + sf_update->SFR_active += sf_add->SFR_active; + + sf_update->SFRdt_active += sf_add->SFRdt_active; + + sf_update->SFR_inactive += sf_add->SFR_inactive; +} + +/** + * @brief add a star formation history struct to the engine star formation + * history accumulator struct + * + * @param sf_add the star formation accumulator struct which we want to add to + * the star formation history + * @param sf_update the star formation structure which we want to update + */ +INLINE static void star_formation_logger_add_to_accumulator( + struct star_formation_history_accumulator *sf_update, + const struct star_formation_history *sf_add) { + + /* Update the SFH structure */ + sf_update->new_stellar_mass = sf_add->new_stellar_mass; + + sf_update->SFR_active = sf_add->SFR_active; + + sf_update->SFRdt_active = sf_add->SFRdt_active; + + sf_update->SFR_inactive = sf_add->SFR_inactive; +} + +/** + * @brief Initialize the star formation history structure in the #engine + * + * @param sfh The pointer to the star formation history structure + */ +INLINE static void star_formation_logger_init( + struct star_formation_history *sfh) { + + /* Initialize the collecting SFH structure to zero */ + sfh->new_stellar_mass = 0.f; + + sfh->SFR_active = 0.f; + + sfh->SFRdt_active = 0.f; + + sfh->SFR_inactive = 0.f; +} + +/** + * @brief Initialize the star formation history structure in the #engine + * + * @param sfh The pointer to the star formation history structure + */ +INLINE static void star_formation_logger_accumulator_init( + struct star_formation_history_accumulator *sfh) { + + /* Initialize the collecting SFH structure to zero */ + sfh->new_stellar_mass = 0.f; + + sfh->SFR_active = 0.f; + + sfh->SFRdt_active = 0.f; + + sfh->SFR_inactive = 0.f; +} + +/** + * @brief Write the final SFH to a file + * + * @param fp The file to write to. + * @param time the simulation time (time since Big Bang) in internal units. + * @param a the scale factor. + * @param z the redshift. + * @param sf the #star_formation_history struct. + * @param step The time-step of the simulation. + */ +INLINE static void star_formation_logger_write_to_log_file( + FILE *fp, const double time, const double a, const double z, + const struct star_formation_history_accumulator sf, const int step) { + + /* Calculate the total SFR */ + const float totalSFR = sf.SFR_active + sf.SFR_inactive; + fprintf(fp, "%6d %16e %12.7f %12.7f %14e %14e %14e %14e\n", step, time, a, z, + sf.new_stellar_mass, sf.SFR_active, sf.SFRdt_active, totalSFR); +} + +/** + * @brief Initialize the SFH logger file + * + * @param fp the file pointer + * @param us The current internal system of units. + * @param phys_const Physical constants in internal units + */ +INLINE static void star_formation_logger_init_log_file( + FILE *fp, const struct unit_system *restrict us, + const struct phys_const *phys_const) { + + /* Write some general text to the logger file */ + fprintf(fp, "# Star Formation History Logger file\n"); + fprintf(fp, "######################################################\n"); + fprintf(fp, "# The quantities are all given in internal physical units!\n"); + fprintf(fp, "#\n"); + fprintf(fp, "# (0) Simulation step\n"); + fprintf(fp, "# Unit = dimensionless\n"); + fprintf(fp, + "# (1) Time since Big Bang (cosmological run), Time since start of " + "the simulation (non-cosmological run).\n"); + fprintf(fp, "# Unit = %e s\n", us->UnitTime_in_cgs); + fprintf(fp, "# Unit = %e yr\n", 1.f / phys_const->const_year); + fprintf(fp, "# Unit = %e Myr\n", 1.f / phys_const->const_year / 1e6); + fprintf(fp, "# (2) Scale factor\n"); + fprintf(fp, "# Unit = dimensionless\n"); + fprintf(fp, "# (3) Redshift\n"); + fprintf(fp, "# Unit = dimensionless\n"); + fprintf(fp, "# (4) Total mass stars formed in the current time-step.\n"); + fprintf(fp, "# Unit = %e gram\n", us->UnitMass_in_cgs); + fprintf(fp, "# Unit = %e Msun\n", 1.f / phys_const->const_solar_mass); + fprintf(fp, "# (5) The total SFR of all the active particles.\n"); + fprintf(fp, "# Unit = %e gram/s\n", + us->UnitMass_in_cgs / us->UnitTime_in_cgs); + fprintf(fp, "# Unit = %e Msun/yr\n", + phys_const->const_year / phys_const->const_solar_mass); + fprintf(fp, + "# (6) The star formation rate (SFR) of active particles multiplied " + "by their time-step size.\n"); + fprintf(fp, "# Unit = %e gram\n", us->UnitMass_in_cgs); + fprintf(fp, "# Unit = %e Msun\n", 1.f / phys_const->const_solar_mass); + fprintf(fp, "# (7) The total SFR of all the particles in the simulation.\n"); + fprintf(fp, "# Unit = %e gram/s\n", + us->UnitMass_in_cgs / us->UnitTime_in_cgs); + fprintf(fp, "# Unit = %e Msun/yr\n", + phys_const->const_year / phys_const->const_solar_mass); + fprintf(fp, "#\n"); + fprintf( + fp, + "# (0) (1) (2) (3) (4) " + " (5) (6) (7)\n"); + fprintf( + fp, + "# Time a z total M_stars SFR " + "(active) SFR*dt (active) SFR (total)\n"); +} + +/** + * @brief Add the SFR tracer to the total active SFR of this cell + * + * @param p the #part + * @param xp the #xpart + * @param sf the SFH logger struct + * @param dt_star The length of the time-step in physical internal units. + */ +INLINE static void star_formation_logger_log_active_part( + const struct part *p, const struct xpart *xp, + struct star_formation_history *sf, const double dt_star) { + + /* No SFR logging for wind particles. */ + if (p->decoupled) return; + + /* Add the SFR to the logger file */ + sf->SFR_active += max(p->sf_data.SFR, 0.f); + + /* Update the active SFR*dt */ + sf->SFRdt_active += p->sf_data.SFR * dt_star; +} + +/** + * @brief Add the SFR tracer to the total inactive SFR of this cell as long as + * the SFR tracer is larger than 0 + * + * @param p the #part + * @param xp the #xpart + * @param sf the SFH logger struct + */ +INLINE static void star_formation_logger_log_inactive_part( + const struct part *p, const struct xpart *xp, + struct star_formation_history *sf) { + + /* No SFR logging for wind particles. */ + if (p->decoupled) return; + + /* Add the SFR to the logger file */ + sf->SFR_inactive += max(p->sf_data.SFR, 0.f); +} + +#endif /* SWIFT_KIARA_STARFORMATION_LOGGER_H */ diff --git a/src/star_formation/KIARA/star_formation_logger_struct.h b/src/star_formation/KIARA/star_formation_logger_struct.h new file mode 100644 index 0000000000..c3d4ba0280 --- /dev/null +++ b/src/star_formation/KIARA/star_formation_logger_struct.h @@ -0,0 +1,54 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2019 Folkert Nobels (nobels@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_KIARA_STAR_FORMATION_LOGGER_STRUCT_H +#define SWIFT_KIARA_STAR_FORMATION_LOGGER_STRUCT_H + +/* Starformation history struct */ +struct star_formation_history { + /*! Total new stellar mass */ + float new_stellar_mass; + + /*! SFR of all particles */ + float SFR_inactive; + + /*! SFR of active particles */ + float SFR_active; + + /*! SFR*dt of active particles */ + float SFRdt_active; +}; + +/* Starformation history struct for the engine. + Allows to integrate in time some values. + Nothing to do in KIARA => copy of star_formation_history */ +struct star_formation_history_accumulator { + /*! Total new stellar mass */ + float new_stellar_mass; + + /*! SFR of all particles */ + float SFR_inactive; + + /*! SFR of active particles */ + float SFR_active; + + /*! SFR*dt of active particles */ + float SFRdt_active; +}; + +#endif /* SWIFT_KIARA_STAR_FORMATION_LOGGER_STRUCT_H */ diff --git a/src/star_formation/KIARA/star_formation_struct.h b/src/star_formation/KIARA/star_formation_struct.h new file mode 100644 index 0000000000..4b5f01bb74 --- /dev/null +++ b/src/star_formation/KIARA/star_formation_struct.h @@ -0,0 +1,54 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Folkert Nobels (nobels@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_KIARA_STAR_FORMATION_STRUCT_H +#define SWIFT_KIARA_STAR_FORMATION_STRUCT_H + +/* Do we need unique IDs (only useful when spawning + new particles, conversion gas->stars does not need unique IDs) */ +#define star_formation_need_unique_id 0 + +/** + * @brief Star-formation-related properties stored in the extended particle + * data. + */ +struct star_formation_xpart_data {}; + +/** + * @brief Star-formation-related properties stored in the particle data. + */ +struct star_formation_part_data { + + /*! Star formation rate (internal units) or (if negative) time/scale-factor of + * last SF episode */ + float SFR; + + /*! The fraction of H2 in this gas particle */ + float H2_fraction; + + /*! The fraction of H2 in this gas particle */ + float G0; +}; + +/** + * @brief Star-formation-related properties stored in the star particle + * data. + */ +struct star_formation_spart_data {}; + +#endif /* SWIFT_KIARA_STAR_FORMATION_STRUCT_H */ diff --git a/src/star_formation/none/star_formation_struct.h b/src/star_formation/none/star_formation_struct.h index 909e593bf3..0445e75a7c 100644 --- a/src/star_formation/none/star_formation_struct.h +++ b/src/star_formation/none/star_formation_struct.h @@ -23,6 +23,11 @@ new particles, conversion gas->stars does not need unique IDs) */ #define star_formation_need_unique_id 0 +/** + * @brief Star-formation-related properties stored in the particle data. + */ +struct star_formation_part_data {}; + /** * @brief Star-formation-related properties stored in the extended particle * data. diff --git a/src/star_formation_debug.h b/src/star_formation_debug.h index 81b15daa01..70cd022e7b 100644 --- a/src/star_formation_debug.h +++ b/src/star_formation_debug.h @@ -31,6 +31,8 @@ #include "./star_formation/EAGLE/star_formation_debug.h" #elif defined(STAR_FORMATION_GEAR) #include "./star_formation/GEAR/star_formation_debug.h" +#elif defined(STAR_FORMATION_KIARA) +#include "./star_formation/KIARA/star_formation_debug.h" #else #error "Invalid choice of star formation model." #endif diff --git a/src/star_formation_iact.h b/src/star_formation_iact.h index 9959c52d20..9ca3f6a891 100644 --- a/src/star_formation_iact.h +++ b/src/star_formation_iact.h @@ -37,6 +37,8 @@ #include "./star_formation/EAGLE/star_formation_iact.h" #elif defined(STAR_FORMATION_GEAR) #include "./star_formation/GEAR/star_formation_iact.h" +#elif defined(STAR_FORMATION_KIARA) +#include "./star_formation/KIARA/star_formation_iact.h" #else #error "Invalid choice of star formation law" #endif diff --git a/src/star_formation_io.h b/src/star_formation_io.h index 4edd084e66..c58fa00892 100644 --- a/src/star_formation_io.h +++ b/src/star_formation_io.h @@ -36,6 +36,8 @@ #include "./star_formation/EAGLE/star_formation_io.h" #elif defined(STAR_FORMATION_GEAR) #include "./star_formation/GEAR/star_formation_io.h" +#elif defined(STAR_FORMATION_KIARA) +#include "./star_formation/KIARA/star_formation_io.h" #else #error "Invalid choice of star formation model." #endif diff --git a/src/star_formation_logger.h b/src/star_formation_logger.h index fe1bc7d389..91ef0c0edc 100644 --- a/src/star_formation_logger.h +++ b/src/star_formation_logger.h @@ -36,6 +36,8 @@ #include "./star_formation/EAGLE/star_formation_logger.h" #elif defined(STAR_FORMATION_GEAR) #include "./star_formation/GEAR/star_formation_logger.h" +#elif defined(STAR_FORMATION_KIARA) +#include "./star_formation/KIARA/star_formation_logger.h" #else #error "Invalid choice of star formation model." #endif diff --git a/src/star_formation_logger_struct.h b/src/star_formation_logger_struct.h index 5ba6654fd8..3c67d23204 100644 --- a/src/star_formation_logger_struct.h +++ b/src/star_formation_logger_struct.h @@ -36,6 +36,8 @@ #include "./star_formation/EAGLE/star_formation_logger_struct.h" #elif defined(STAR_FORMATION_GEAR) #include "./star_formation/GEAR/star_formation_logger_struct.h" +#elif defined(STAR_FORMATION_KIARA) +#include "./star_formation/KIARA/star_formation_logger_struct.h" #else #error "Invalid choice of star formation structure." #endif diff --git a/src/star_formation_struct.h b/src/star_formation_struct.h index 2c8e72418e..2d5f472a7b 100644 --- a/src/star_formation_struct.h +++ b/src/star_formation_struct.h @@ -36,6 +36,8 @@ #include "./star_formation/EAGLE/star_formation_struct.h" #elif defined(STAR_FORMATION_GEAR) #include "./star_formation/GEAR/star_formation_struct.h" +#elif defined(STAR_FORMATION_KIARA) +#include "./star_formation/KIARA/star_formation_struct.h" #else #error "Invalid choice of star formation structure." #endif diff --git a/src/stars.h b/src/stars.h index 539f9654c1..33f12603c6 100644 --- a/src/stars.h +++ b/src/stars.h @@ -36,6 +36,9 @@ #include "./stars/GEAR/stars.h" #include "./stars/GEAR/stars_iact.h" #include "./stars/GEAR/stars_stellar_type.h" +#elif defined(STARS_KIARA) +#include "./stars/KIARA/stars.h" +#include "./stars/KIARA/stars_iact.h" #else #error "Invalid choice of star model" #endif diff --git a/src/stars/KIARA/stars.h b/src/stars/KIARA/stars.h new file mode 100644 index 0000000000..13592202d1 --- /dev/null +++ b/src/stars/KIARA/stars.h @@ -0,0 +1,377 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_SIMBA_STARS_H +#define SWIFT_SIMBA_STARS_H + +#include "exp10.h" +#include "feedback_properties.h" + +#include + +/** + * @brief Computes the time-step length of a given star particle from star + * physics + * + * @param sp Pointer to the s-particle data. + * @param stars_properties Properties of the stars model. + * @param feedback_props Properties of the feedback model. + * @param phys_const The #phys_const. + * @param us The #unit_system. + * @param with_cosmology Are we running with cosmological time integration. + * @param cosmo The current cosmological model (used if running with + * cosmology). + * @param ti_current The current time (in integer). + * @param time The current time (used if running without cosmology). + * @param time_base The time base. + */ +__attribute__((always_inline)) INLINE static float stars_compute_timestep( + const struct spart *const sp, const struct stars_props *stars_properties, + const struct feedback_props *feedback_props, + const struct phys_const *phys_const, const struct unit_system *us, + const int with_cosmology, const struct cosmology *cosmo, + const integertime_t ti_current, const double time, const double time_base) { + + /* Background star particles have no time-step limits */ + if (sp->birth_time == -1.) { + return FLT_MAX; + } + + /* Star age (in internal units) */ + double star_age; + if (with_cosmology) { + + /* Deal with rounding issues */ + if (sp->birth_scale_factor >= cosmo->a) { + star_age = 0.; + } else { + star_age = cosmology_get_delta_time_from_scale_factors( + cosmo, sp->birth_scale_factor, cosmo->a); + } + } else { + star_age = time - sp->birth_time; + } + + if (star_age > stars_properties->age_threshold_unlimited) return FLT_MAX; + + float dt_star = FLT_MAX; + if (star_age < stars_properties->age_threshold) { + dt_star = min(star_age * stars_properties->time_step_factor_young, + stars_properties->max_time_step_young); + } else { + dt_star = min(star_age * stars_properties->time_step_factor_old, + stars_properties->max_time_step_old); + } + + return max(stars_properties->min_time_step, dt_star); +} + +/** + * @brief Returns the age of a star in internal units + * + * @param sp The star particle. + * @param cosmo The cosmological model. + * @param time The current time (in internal units). + * @param with_cosmology Are we running with cosmological integration? + */ +__attribute__((always_inline)) INLINE static double stars_compute_age( + const struct spart *sp, const struct cosmology *cosmo, double time, + const int with_cosmology) { + + if (with_cosmology) { + if ((double)sp->birth_scale_factor >= cosmo->a) return 0.; + return cosmology_get_delta_time_from_scale_factors( + cosmo, (double)sp->birth_scale_factor, cosmo->a); + } else { + return time - (double)sp->birth_time; + } +} + +/** + * @brief Prepares a s-particle for its interactions + * + * @param sp The particle to act upon + */ +__attribute__((always_inline)) INLINE static void stars_init_spart( + struct spart *sp) { + +#ifdef DEBUG_INTERACTIONS_STARS + for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_STARS; ++i) + sp->ids_ngbs_density[i] = -1; + sp->num_ngb_density = 0; +#endif + + sp->density.wcount = 0.f; + sp->density.wcount_dh = 0.f; + +#ifdef SWIFT_STARS_DENSITY_CHECKS + sp->N_density = 0; + sp->N_density_exact = 0; + sp->rho = 0.f; + sp->rho_exact = 0.f; + sp->n = 0.f; + sp->n_exact = 0.f; + sp->inhibited_exact = 0; +#endif +} + +/** + * @brief Initialises the s-particles for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions. + * + * @param sp The particle to act upon. + * @param stars_properties Properties of the stars model. + * @param with_cosmology Are we running with cosmological time integration. + * @param scale_factor The current scale-factor (used if running with + * cosmology). + * @param time The current time (used if running without cosmology). + */ +__attribute__((always_inline)) INLINE static void stars_first_init_spart( + struct spart *sp, const struct stars_props *stars_properties, + const int with_cosmology, const double scale_factor, const double time) { + + sp->time_bin = 0; + sp->count_since_last_enrichment = -1; + + if (stars_properties->overwrite_birth_time) + sp->birth_time = stars_properties->spart_first_init_birth_time; + if (stars_properties->overwrite_birth_density) + sp->birth_density = stars_properties->spart_first_init_birth_density; + if (stars_properties->overwrite_birth_temperature) + sp->birth_temperature = + stars_properties->spart_first_init_birth_temperature; + + if (with_cosmology) + sp->last_enrichment_time = scale_factor; + else + sp->last_enrichment_time = time; + + stars_init_spart(sp); +} + +/** + * @brief Predict additional particle fields forward in time when drifting + * + * @param sp The particle + * @param dt_drift The drift time-step for positions. + */ +__attribute__((always_inline)) INLINE static void stars_predict_extra( + struct spart *restrict sp, float dt_drift) {} + +/** + * @brief Sets the values to be predicted in the drifts to their values at a + * kick time + * + * @param sp The particle. + */ +__attribute__((always_inline)) INLINE static void stars_reset_predicted_values( + struct spart *restrict sp) {} + +/** + * @brief Finishes the calculation of (non-gravity) forces acting on stars + * + * Multiplies the forces and accelerations by the appropiate constants + * + * @param sp The particle to act upon + */ +__attribute__((always_inline)) INLINE static void stars_end_feedback( + struct spart *sp) {} + +/** + * @brief Kick the additional variables + * + * @param sp The particle to act upon + * @param dt The time-step for this kick + */ +__attribute__((always_inline)) INLINE static void stars_kick_extra( + struct spart *sp, float dt) {} + +/** + * @brief Finishes the calculation of density on stars + * + * @param sp The particle to act upon + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void stars_end_density( + struct spart *sp, const struct cosmology *cosmo) { + + /* Some smoothing length multiples. */ + const float h = sp->h; + const float h_inv = 1.0f / h; /* 1/h */ + const float h_inv_dim = pow_dimension(h_inv); /* 1/h^d */ + const float h_inv_dim_plus_one = h_inv_dim * h_inv; /* 1/h^(d+1) */ + + /* Finish the calculation by inserting the missing h-factors */ + sp->density.wcount *= h_inv_dim; + sp->density.wcount_dh *= h_inv_dim_plus_one; + +#ifdef SWIFT_STARS_DENSITY_CHECKS + sp->rho *= h_inv_dim; + sp->n *= h_inv_dim; +#endif +} + +/** + * @brief Sets all particle fields to sensible values when the #spart has 0 + * ngbs. + * + * @param sp The particle to act upon + * @param cosmo The current cosmological model. + */ +__attribute__((always_inline)) INLINE static void stars_spart_has_no_neighbours( + struct spart *restrict sp, const struct cosmology *cosmo) { + + warning( + "Star particle with ID %lld treated as having no neighbours (h: %g, " + "wcount: %g).", + sp->id, sp->h, sp->density.wcount); + + /* Re-set problematic values */ + sp->density.wcount = 0.f; + sp->density.wcount_dh = 0.f; +} + +/** + * @brief Reset acceleration fields of a particle + * + * This is the equivalent of hydro_reset_acceleration. + * We do not compute the acceleration on star, therefore no need to use it. + * + * @param p The particle to act upon + */ +__attribute__((always_inline)) INLINE static void stars_reset_acceleration( + struct spart *restrict p) { + +#ifdef DEBUG_INTERACTIONS_STARS + p->num_ngb_feedback = 0; +#endif +} + +/** + * @brief Reset acceleration fields of a particle + * + * This is the equivalent of hydro_reset_acceleration. + * We do not compute the acceleration on star, therefore no need to use it. + * + * @param p The particle to act upon + */ +__attribute__((always_inline)) INLINE static void stars_reset_feedback( + struct spart *restrict p) { + +#ifdef DEBUG_INTERACTIONS_STARS + for (int i = 0; i < MAX_NUM_OF_NEIGHBOURS_STARS; ++i) + p->ids_ngbs_feedback[i] = -1; + p->num_ngb_feedback = 0; +#endif +} + +/** + * @brief Compute the luminosities of a particles in different bands. + * + * @param sp The particle. + * @param with_cosmology Are we running a cosmological simulation? + * @param cosmo The #cosmology object. + * @param time The current physical time (internal units). + * @param phys_const The physical constants in internal units. + * @param props The #stars_props of that run. + * @param luminosities (return) The luminosity in each band. + */ +INLINE static void stars_get_luminosities( + const struct spart *sp, const int with_cosmology, + const struct cosmology *cosmo, const double time, + const struct phys_const *phys_const, const struct stars_props *props, + float luminosities[luminosity_bands_count]) { + + const int count_Z = eagle_stars_lum_tables_N_Z; + const int count_ages = eagle_stars_lum_tables_N_ages; + + /* Get star properties (all in internal units */ + const float Z = + chemistry_get_star_total_metal_mass_fraction_for_luminosity(sp); + const float mass = sp->mass_init; + float age; + if (with_cosmology) + age = cosmology_get_delta_time_from_scale_factors( + cosmo, sp->birth_scale_factor, cosmo->a); + else + age = time - sp->birth_time; + + /* Convert to the units of the tables */ + const float mass_Msun = mass / phys_const->const_solar_mass; + const float age_Gyr = age / phys_const->const_year / 1e9; + + for (int i = 0; i < (int)luminosity_bands_count; ++i) { + + /* Log things */ + float log10_Z = log10(Z + FLT_MIN); + float log10_age_Gyr = log10(age_Gyr + FLT_MIN); + + /* Clip the input */ + log10_Z = max(log10_Z, props->lum_tables_Z[i][0]); + log10_Z = min(log10_Z, props->lum_tables_Z[i][count_Z - 1]); + log10_age_Gyr = max(log10_age_Gyr, props->lum_tables_ages[i][0]); + log10_age_Gyr = + min(log10_age_Gyr, props->lum_tables_ages[i][count_ages - 1]); + + /* Get index along the interpolation axis */ + int Z_index = 0, age_index = 0; + for (int j = 0; j < count_Z - 1; ++j) { + if (log10_Z >= props->lum_tables_Z[i][j]) ++Z_index; + } + for (int j = 0; j < count_ages - 1; ++j) { + if (log10_age_Gyr >= props->lum_tables_ages[i][j]) ++age_index; + } + +#ifdef SWIFT_DEBUG_CHECKS + if (age_index == 0) error("Invalid age index!"); + if (Z_index == 0) error("Invalid Z index!"); +#endif + + const float *array = props->lum_tables_luminosities[i]; + + /* 2D interpolation */ + + const float f_11 = array[(Z_index - 1) * count_ages + (age_index - 1)]; + const float f_12 = array[(Z_index - 0) * count_ages + (age_index - 1)]; + const float f_21 = array[(Z_index - 1) * count_ages + (age_index - 0)]; + const float f_22 = array[(Z_index - 0) * count_ages + (age_index - 0)]; + + const float x_diff1 = props->lum_tables_ages[i][age_index] - log10_age_Gyr; + const float x_diff2 = + log10_age_Gyr - props->lum_tables_ages[i][age_index - 1]; + const float x_diff3 = props->lum_tables_ages[i][age_index] - + props->lum_tables_ages[i][age_index - 1]; + + const float y_diff1 = props->lum_tables_Z[i][Z_index] - log10_Z; + const float y_diff2 = log10_Z - props->lum_tables_Z[i][Z_index - 1]; + const float y_diff3 = + props->lum_tables_Z[i][Z_index] - props->lum_tables_Z[i][Z_index - 1]; + + const float f_1 = (f_11 * x_diff1 + f_21 * x_diff2) / x_diff3; + const float f_2 = (f_12 * x_diff1 + f_22 * x_diff2) / x_diff3; + + const float log10_f = (y_diff1 * f_1 + y_diff2 * f_2) / y_diff3; + + /* Final conversion */ + luminosities[i] = exp10f(log10_f) * mass_Msun * props->lum_tables_factor; + } +} + +#endif /* SWIFT_SIMBA_STARS_H */ diff --git a/src/stars/KIARA/stars_debug.h b/src/stars/KIARA/stars_debug.h new file mode 100644 index 0000000000..ea26f9f3d4 --- /dev/null +++ b/src/stars/KIARA/stars_debug.h @@ -0,0 +1,31 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_SIMBA_STARS_DEBUG_H +#define SWIFT_SIMBA_STARS_DEBUG_H + +__attribute__((always_inline)) INLINE static void stars_debug_particle( + const struct spart *p) { + printf( + "x=[%.3e,%.3e,%.3e], " + "v_full=[%.3e,%.3e,%.3e] p->mass=%.3e \n t_begin=%d, t_end=%d\n", + p->x[0], p->x[1], p->x[2], p->v_full[0], p->v_full[1], p->v_full[2], + p->mass, p->ti_begin, p->ti_end); +} + +#endif /* SWIFT_SIMBA_STARS_DEBUG_H */ diff --git a/src/stars/KIARA/stars_iact.h b/src/stars/KIARA/stars_iact.h new file mode 100644 index 0000000000..fc5db01fbf --- /dev/null +++ b/src/stars/KIARA/stars_iact.h @@ -0,0 +1,112 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_SIMBA_STARS_IACT_H +#define SWIFT_SIMBA_STARS_IACT_H + +#include "random.h" + +/** + * @brief Density interaction between two particles (non-symmetric). + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (pi - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param si First sparticle. + * @param pj Second particle (not updated). + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_stars_density(const float r2, const float dx[3], + const float hi, const float hj, + struct spart *si, const struct part *pj, + const float a, const float H) { + + /* Ignore wind in density computation */ + if (pj->decoupled) return; + + float wi, wi_dx; + + /* Get r. */ + const float r = sqrtf(r2); + + /* Compute the kernel function */ + const float hi_inv = 1.0f / hi; + const float ui = r * hi_inv; + kernel_deval(ui, &wi, &wi_dx); + + /* Compute contribution to the number of neighbours */ + si->density.wcount += wi; + si->density.wcount_dh -= (hydro_dimension * wi + ui * wi_dx); + +#ifdef SWIFT_STARS_DENSITY_CHECKS + si->rho += pj->mass * wi; + si->n += wi; + si->N_density++; +#endif + +#ifdef DEBUG_INTERACTIONS_STARS + /* Update ngb counters */ + if (si->num_ngb_density < MAX_NUM_OF_NEIGHBOURS_STARS) + si->ids_ngbs_density[si->num_ngb_density] = pj->id; + ++si->num_ngb_density; +#endif +} + +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_stars_prep1(const float r2, const float dx[3], + const float hi, const float hj, struct spart *si, + const struct part *pj, const float a, + const float H) {} + +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_stars_prep2(const float r2, const float dx[3], + const float hi, const float hj, struct spart *si, + const struct part *pj, const float a, + const float H) {} + +/** + * @brief Feedback interaction between two particles (non-symmetric). + * Used for updating properties of gas particles neighbouring a star particle + * + * @param r2 Comoving square distance between the two particles. + * @param dx Comoving vector separating both particles (si - pj). + * @param hi Comoving smoothing-length of particle i. + * @param hj Comoving smoothing-length of particle j. + * @param si First (star) particle (not updated). + * @param pj Second (gas) particle. + * @param a Current scale factor. + * @param H Current Hubble parameter. + */ +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_stars_feedback(const float r2, const float dx[3], + const float hi, const float hj, + const struct spart *si, struct part *pj, + const float a, const float H) { + +#ifdef DEBUG_INTERACTIONS_STARS + /* Update ngb counters */ + if (si->num_ngb_feedback < MAX_NUM_OF_NEIGHBOURS_STARS) + si->ids_ngbs_feedback[si->num_ngb_feedback] = pj->id; + ++si->num_ngb_feedback; +#endif +} + +#endif /* SWIFT_SIMBA_STARS_IACT_H */ diff --git a/src/stars/KIARA/stars_io.h b/src/stars/KIARA/stars_io.h new file mode 100644 index 0000000000..a1f85d4db7 --- /dev/null +++ b/src/stars/KIARA/stars_io.h @@ -0,0 +1,525 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2018 Folkert Nobels (nobels@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_SIMBA_STARS_IO_H +#define SWIFT_SIMBA_STARS_IO_H + +#include "io_properties.h" +#include "kick.h" +#include "stars_part.h" + +/** + * @brief Specifies which s-particle fields to read from a dataset + * + * @param sparts The s-particle array. + * @param list The list of i/o properties to read. + * @param num_fields The number of i/o fields to read. + */ +INLINE static void stars_read_particles(struct spart *sparts, + struct io_props *list, + int *num_fields) { + /* Say how much we want to read */ + *num_fields = 9; + + /* List what we want to read */ + list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY, + UNIT_CONV_LENGTH, sparts, x); + list[1] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY, + UNIT_CONV_SPEED, sparts, v); + list[2] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS, + sparts, mass); + list[3] = io_make_input_field("ParticleIDs", LONGLONG, 1, COMPULSORY, + UNIT_CONV_NO_UNITS, sparts, id); + list[4] = io_make_input_field("SmoothingLength", FLOAT, 1, OPTIONAL, + UNIT_CONV_LENGTH, sparts, h); + list[5] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS, + sparts, mass_init); + float def_value = -1.f; + list[6] = io_make_input_field_default("StellarFormationTime", FLOAT, 1, + OPTIONAL, UNIT_CONV_NO_UNITS, sparts, + birth_time, def_value); + list[7] = io_make_input_field("BirthDensities", FLOAT, 1, OPTIONAL, + UNIT_CONV_DENSITY, sparts, birth_density); + list[8] = + io_make_input_field("BirthTemperatures", FLOAT, 1, OPTIONAL, + UNIT_CONV_TEMPERATURE, sparts, birth_temperature); +} + +INLINE static void convert_spart_pos(const struct engine *e, + const struct spart *sp, double *ret) { + const struct space *s = e->s; + if (s->periodic) { + ret[0] = box_wrap(sp->x[0], 0.0, s->dim[0]); + ret[1] = box_wrap(sp->x[1], 0.0, s->dim[1]); + ret[2] = box_wrap(sp->x[2], 0.0, s->dim[2]); + } else { + ret[0] = sp->x[0]; + ret[1] = sp->x[1]; + ret[2] = sp->x[2]; + } + if (e->snapshot_use_delta_from_edge) { + ret[0] = min(ret[0], s->dim[0] - e->snapshot_delta_from_edge); + ret[1] = min(ret[1], s->dim[1] - e->snapshot_delta_from_edge); + ret[2] = min(ret[2], s->dim[2] - e->snapshot_delta_from_edge); + } +} + +INLINE static void convert_spart_vel(const struct engine *e, + const struct spart *sp, float *ret) { + const int with_cosmology = (e->policy & engine_policy_cosmology); + const struct cosmology *cosmo = e->cosmology; + const integertime_t ti_current = e->ti_current; + const double time_base = e->time_base; + const float dt_kick_grav_mesh = e->dt_kick_grav_mesh_for_io; + + const integertime_t ti_beg = get_integer_time_begin(ti_current, sp->time_bin); + const integertime_t ti_end = get_integer_time_end(ti_current, sp->time_bin); + + /* Get time-step since the last kick */ + const float dt_kick_grav = + kick_get_grav_kick_dt(ti_beg, ti_current, time_base, with_cosmology, + cosmo) - + kick_get_grav_kick_dt(ti_beg, (ti_beg + ti_end) / 2, time_base, + with_cosmology, cosmo); + + /* Extrapolate the velocites to the current time */ + const struct gpart *gp = sp->gpart; + ret[0] = gp->v_full[0] + gp->a_grav[0] * dt_kick_grav; + ret[1] = gp->v_full[1] + gp->a_grav[1] * dt_kick_grav; + ret[2] = gp->v_full[2] + gp->a_grav[2] * dt_kick_grav; + + /* Extrapolate the velocites to the current time (mesh forces) */ + ret[0] += gp->a_grav_mesh[0] * dt_kick_grav_mesh; + ret[1] += gp->a_grav_mesh[1] * dt_kick_grav_mesh; + ret[2] += gp->a_grav_mesh[2] * dt_kick_grav_mesh; + + /* Conversion from internal units to peculiar velocities */ + ret[0] *= cosmo->a_inv; + ret[1] *= cosmo->a_inv; + ret[2] *= cosmo->a_inv; +} + +INLINE static void convert_spart_luminosities(const struct engine *e, + const struct spart *sp, + float *ret) { + stars_get_luminosities(sp, e->policy & engine_policy_cosmology, e->cosmology, + e->time, e->physical_constants, e->stars_properties, + ret); +} + +INLINE static void convert_spart_potential(const struct engine *e, + const struct spart *sp, float *ret) { + + if (sp->gpart != NULL) + ret[0] = gravity_get_comoving_potential(sp->gpart); + else + ret[0] = 0.f; +} + +/** + * @brief Specifies which s-particle fields to write to a dataset + * + * @param sparts The s-particle array. + * @param list The list of i/o properties to write. + * @param num_fields The number of i/o fields to write. + * @param with_cosmology Are we running a cosmological simulation? + */ +INLINE static void stars_write_particles(const struct spart *sparts, + struct io_props *list, int *num_fields, + const int with_cosmology) { + /* Say how much we want to write */ + *num_fields = 11; + + /* List what we want to write */ + list[0] = io_make_output_field_convert_spart( + "Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH, 1.f, sparts, + convert_spart_pos, "Co-moving position of the particles"); + + list[1] = io_make_output_field_convert_spart( + "Velocities", FLOAT, 3, UNIT_CONV_SPEED, 0.f, sparts, convert_spart_vel, + "Peculiar velocities of the particles. This is a * dx/dt where x is the " + "co-moving position of the particles."); + + list[2] = io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, 0.f, + sparts, mass, + "Masses of the particles at the current point " + "in time (i.e. after stellar losses"); + + list[3] = + io_make_output_field("ParticleIDs", ULONGLONG, 1, UNIT_CONV_NO_UNITS, 0.f, + sparts, id, "Unique ID of the particles"); + + list[4] = io_make_output_field( + "SmoothingLengths", FLOAT, 1, UNIT_CONV_LENGTH, 1.f, sparts, h, + "Co-moving smoothing lengths (FWHM of the kernel) of the particles"); + + list[5] = io_make_output_field("InitialMasses", FLOAT, 1, UNIT_CONV_MASS, 0.f, + sparts, mass_init, + "Masses of the star particles at birth time"); + + if (with_cosmology) { + list[6] = io_make_output_field( + "BirthScaleFactors", FLOAT, 1, UNIT_CONV_NO_UNITS, 0.f, sparts, + birth_scale_factor, "Scale-factors at which the stars were born"); + } else { + list[6] = io_make_output_field("BirthTimes", FLOAT, 1, UNIT_CONV_TIME, 0.f, + sparts, birth_time, + "Times at which the stars were born"); + } + + list[7] = io_make_output_field( + "BirthDensities", FLOAT, 1, UNIT_CONV_DENSITY, 0.f, sparts, birth_density, + "Physical densities at the time of birth of the gas particles that " + "turned into stars (note that " + "we store the physical density at the birth redshift, no conversion is " + "needed)"); + + list[8] = + io_make_output_field("BirthTemperatures", FLOAT, 1, UNIT_CONV_TEMPERATURE, + 0.f, sparts, birth_temperature, + "Temperatures at the time of birth of the gas " + "particles that turned into stars"); + + list[9] = io_make_output_field_convert_spart( + "Luminosities", FLOAT, luminosity_bands_count, UNIT_CONV_NO_UNITS, 0.f, + sparts, convert_spart_luminosities, + "Rest-frame dust-free AB-luminosities of the star particles in the GAMA " + "bands. These were computed using the BC03 (GALAXEV) models convolved " + "with different filter bands and interpolated in log-log (f(log(Z), " + "log(age)) = log(flux)) as used in the dust-free modelling of Trayford " + "et al. (2015). The luminosities are given in dimensionless units. They " + "have been divided by 3631 Jy already, i.e. they can be turned into " + "absolute AB-magnitudes (rest-frame absolute maggies) directly by " + "applying -2.5 log10(L) without additional corrections."); + + list[10] = io_make_output_field_convert_spart( + "Potentials", FLOAT, 1, UNIT_CONV_POTENTIAL, -1.f, sparts, + convert_spart_potential, "Gravitational potentials of the particles"); +} + +/** + * @brief Initialize the global properties of the stars scheme. + * + * By default, takes the values provided by the hydro. + * + * @param sp The #stars_props. + * @param phys_const The physical constants in the internal unit system. + * @param us The internal unit system. + * @param params The parsed parameters. + * @param p The already read-in properties of the hydro scheme. + * @param cosmo The cosmological model. + */ +INLINE static void stars_props_init(struct stars_props *sp, + const struct phys_const *phys_const, + const struct unit_system *us, + struct swift_params *params, + const struct hydro_props *p, + const struct cosmology *cosmo) { + /* Kernel properties */ + sp->eta_neighbours = parser_get_opt_param_float( + params, "Stars:resolution_eta", p->eta_neighbours); + + /* Tolerance for the smoothing length Newton-Raphson scheme */ + sp->h_tolerance = + parser_get_opt_param_float(params, "Stars:h_tolerance", p->h_tolerance); + + /* Get derived properties */ + sp->target_neighbours = pow_dimension(sp->eta_neighbours) * kernel_norm; + const float delta_eta = sp->eta_neighbours * (1.f + sp->h_tolerance); + sp->delta_neighbours = + (pow_dimension(delta_eta) - pow_dimension(sp->eta_neighbours)) * + kernel_norm; + + /* Number of iterations to converge h */ + sp->max_smoothing_iterations = parser_get_opt_param_int( + params, "Stars:max_ghost_iterations", p->max_smoothing_iterations); + + /* Time integration properties */ + const float max_volume_change = + parser_get_opt_param_float(params, "Stars:max_volume_change", -1); + if (max_volume_change == -1) + sp->log_max_h_change = p->log_max_h_change; + else + sp->log_max_h_change = logf(powf(max_volume_change, hydro_dimension_inv)); + + /* Do we want to overwrite the stars' birth properties? */ + sp->overwrite_birth_time = + parser_get_opt_param_int(params, "Stars:overwrite_birth_time", 0); + sp->overwrite_birth_density = + parser_get_opt_param_int(params, "Stars:overwrite_birth_density", 0); + sp->overwrite_birth_temperature = + parser_get_opt_param_int(params, "Stars:overwrite_birth_temperature", 0); + + /* Read birth time to set all stars in ICs */ + if (sp->overwrite_birth_time) { + sp->spart_first_init_birth_time = + parser_get_param_float(params, "Stars:birth_time"); + } + + /* Read birth density to set all stars in ICs */ + if (sp->overwrite_birth_density) { + sp->spart_first_init_birth_density = + parser_get_param_float(params, "Stars:birth_density"); + } + + /* Read birth temperature to set all stars in ICs */ + if (sp->overwrite_birth_temperature) { + sp->spart_first_init_birth_temperature = + parser_get_param_float(params, "Stars:birth_temperature"); + } + + /* Maximal time-step lengths */ + const double Myr = 1e6 * 365.25 * 24. * 60. * 60.; + const double conv_fac = units_cgs_conversion_factor(us, UNIT_CONV_TIME); + + sp->time_step_factor_young = + parser_get_opt_param_float(params, "Stars:time_step_factor_young", 1.f); + sp->time_step_factor_old = + parser_get_opt_param_float(params, "Stars:time_step_factor_old", 1.f); + const double min_time_step_Myr = + parser_get_opt_param_float(params, "Stars:min_time_step_Myr", 30.); + const double max_time_step_young_Myr = parser_get_opt_param_float( + params, "Stars:max_timestep_young_Myr", FLT_MAX); + const double max_time_step_old_Myr = + parser_get_opt_param_float(params, "Stars:max_timestep_old_Myr", FLT_MAX); + const double age_threshold_Myr = parser_get_opt_param_float( + params, "Stars:timestep_age_threshold_Myr", FLT_MAX); + const double age_threshold_unlimited_Myr = parser_get_opt_param_float( + params, "Stars:timestep_age_threshold_unlimited_Myr", 0.); + + /* Check for consistency */ + if (age_threshold_unlimited_Myr != 0. && age_threshold_Myr != FLT_MAX) { + if (age_threshold_unlimited_Myr < age_threshold_Myr) + error( + "The age threshold for unlimited stellar time-step sizes (%e Myr) is " + "smaller than the transition threshold from young to old ages (%e " + "Myr)", + age_threshold_unlimited_Myr, age_threshold_Myr); + } + + /* Convert to internal units */ + sp->min_time_step = min_time_step_Myr * Myr / conv_fac; + sp->max_time_step_young = max_time_step_young_Myr * Myr / conv_fac; + sp->max_time_step_old = max_time_step_old_Myr * Myr / conv_fac; + sp->age_threshold = age_threshold_Myr * Myr / conv_fac; + sp->age_threshold_unlimited = age_threshold_unlimited_Myr * Myr / conv_fac; + + /* Read luminosity table filepath */ + char base_dir_name[200]; + parser_get_param_string(params, "Stars:luminosity_filename", base_dir_name); + + static const char *luminosity_band_names[luminosity_bands_count] = { + "u", "g", "r", "i", "z", "Y", "J", "H", "K"}; + + /* Luminosity tables */ + for (int i = 0; i < (int)luminosity_bands_count; ++i) { + const int count_Z = eagle_stars_lum_tables_N_Z; + const int count_ages = eagle_stars_lum_tables_N_ages; + const int count_L = count_Z * count_ages; + + sp->lum_tables_Z[i] = (float *)malloc(count_Z * sizeof(float)); + sp->lum_tables_ages[i] = (float *)malloc(count_ages * sizeof(float)); + sp->lum_tables_luminosities[i] = (float *)malloc(count_L * sizeof(float)); + + char fname[256]; + sprintf(fname, "%s/GAMA/%s", base_dir_name, luminosity_band_names[i]); + FILE *file = fopen(fname, "r"); + + if (file != NULL) { + char buffer[200]; + int j = 0, k = 0; + while (fgets(buffer, sizeof(buffer), file) != NULL) { + double z, age, L; + sscanf(buffer, "%le %le %le", &z, &age, &L); + + if (age == 0.) { + sp->lum_tables_Z[i][k++] = log10(z); + } + + if (j < count_ages) { + sp->lum_tables_ages[i][j] = log10(age + FLT_MIN); + } + + sp->lum_tables_luminosities[i][j] = log10(L); + + ++j; + } + } else { + error("Unable to load luminosity table %s", fname); + } + + fclose(file); + } + + /* Luminosity conversion factor */ + const double L_sun = 3.828e26; /* Watt */ + const double pc = 3.08567758149e16; /* m */ + const double A = 4. * M_PI * (10. * pc) * (10 * pc); + const double to_Jansky = 1e26 * L_sun / A; + const double zero_point_AB = 3631; /* Jansky */ + sp->lum_tables_factor = to_Jansky / zero_point_AB; +} + +/** + * @brief Print the global properties of the stars scheme. + * + * @param sp The #stars_props. + */ +INLINE static void stars_props_print(const struct stars_props *sp) { + message("Stars kernel: %s with eta=%f (%.2f neighbours).", kernel_name, + sp->eta_neighbours, sp->target_neighbours); + + message("Stars relative tolerance in h: %.5f (+/- %.4f neighbours).", + sp->h_tolerance, sp->delta_neighbours); + + message( + "Stars integration: Max change of volume: %.2f " + "(max|dlog(h)/dt|=%f).", + pow_dimension(expf(sp->log_max_h_change)), sp->log_max_h_change); + + message("Maximal iterations in ghost task set to %d", + sp->max_smoothing_iterations); + + if (sp->overwrite_birth_time) + message("Stars' birth time read from the ICs will be overwritten to %f", + sp->spart_first_init_birth_time); + + message("Time-step factor for young stars: %e", sp->time_step_factor_young); + message("Time-step factor for old stars: %e", sp->time_step_factor_old); + message("Stars' age threshold for unlimited dt: %e [U_t]", + sp->age_threshold_unlimited); + message("Stars' young/old age threshold: %e [U_t]", sp->age_threshold); + message("Max time-step size of young stars: %e [U_t]", + sp->max_time_step_young); + message("Max time-step size of old stars: %e [U_t]", sp->max_time_step_old); +} + +#if defined(HAVE_HDF5) +INLINE static void stars_props_print_snapshot(hid_t h_grpstars, + hid_t h_grp_columns, + const struct stars_props *sp) { + io_write_attribute_s(h_grpstars, "Kernel function", kernel_name); + io_write_attribute_f(h_grpstars, "Kernel target N_ngb", + sp->target_neighbours); + io_write_attribute_f(h_grpstars, "Kernel delta N_ngb", sp->delta_neighbours); + io_write_attribute_f(h_grpstars, "Kernel eta", sp->eta_neighbours); + io_write_attribute_f(h_grpstars, "Smoothing length tolerance", + sp->h_tolerance); + io_write_attribute_f(h_grpstars, "Volume log(max(delta h))", + sp->log_max_h_change); + io_write_attribute_f(h_grpstars, "Volume max change time-step", + pow_dimension(expf(sp->log_max_h_change))); + io_write_attribute_i(h_grpstars, "Max ghost iterations", + sp->max_smoothing_iterations); + + static const char luminosity_band_names[luminosity_bands_count][32] = { + "GAMA_u", "GAMA_g", "GAMA_r", "GAMA_i", "GAMA_z", + "GAMA_Y", "GAMA_J", "GAMA_H", "GAMA_K"}; + + /* Add to the named columns */ + hsize_t dims[1] = {luminosity_bands_count}; + hid_t type = H5Tcopy(H5T_C_S1); + H5Tset_size(type, 32); + hid_t space = H5Screate_simple(1, dims, NULL); + hid_t dset = H5Dcreate(h_grp_columns, "Luminosities", type, space, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(dset, type, H5S_ALL, H5S_ALL, H5P_DEFAULT, luminosity_band_names[0]); + H5Dclose(dset); + + H5Tclose(type); + H5Sclose(space); +} +#endif + +/** + * @brief Free the memory allocated for the stellar properties. + * + * @param sp The #stars_props structure. + */ +INLINE static void stars_props_clean(struct stars_props *sp) { + for (int i = 0; i < (int)luminosity_bands_count; ++i) { + free(sp->lum_tables_Z[i]); + free(sp->lum_tables_ages[i]); + free(sp->lum_tables_luminosities[i]); + } +} + +/** + * @brief Write a #stars_props struct to the given FILE as a stream of bytes. + * + * @param p the struct + * @param stream the file stream + */ +INLINE static void stars_props_struct_dump(struct stars_props *p, + FILE *stream) { + restart_write_blocks((void *)p, sizeof(struct stars_props), 1, stream, + "starsprops", "stars props"); + + const int count_Z = eagle_stars_lum_tables_N_Z; + const int count_ages = eagle_stars_lum_tables_N_ages; + const int count_L = count_Z * count_ages; + + /* Did we allocate anything? */ + if (p->lum_tables_Z[0]) { + for (int i = 0; i < (int)luminosity_bands_count; ++i) { + restart_write_blocks(p->lum_tables_Z[i], count_Z, sizeof(float), stream, + "luminosity_Z", "stars props"); + restart_write_blocks(p->lum_tables_ages[i], count_ages, sizeof(float), + stream, "luminosity_ages", "stars props"); + restart_write_blocks(p->lum_tables_luminosities[i], count_L, + sizeof(float), stream, "luminosity_L", + "stars props"); + } + } +} + +/** + * @brief Restore a stars_props struct from the given FILE as a stream of + * bytes. + * + * @param p the struct + * @param stream the file stream + */ +INLINE static void stars_props_struct_restore(struct stars_props *p, + FILE *stream) { + restart_read_blocks((void *)p, sizeof(struct stars_props), 1, stream, NULL, + "stars props"); + + /* Did we allocate anything? */ + if (p->lum_tables_Z[0]) { + for (int i = 0; i < (int)luminosity_bands_count; ++i) { + const int count_Z = eagle_stars_lum_tables_N_Z; + const int count_ages = eagle_stars_lum_tables_N_ages; + const int count_L = count_Z * count_ages; + + p->lum_tables_Z[i] = (float *)malloc(count_Z * sizeof(float)); + p->lum_tables_ages[i] = (float *)malloc(count_ages * sizeof(float)); + p->lum_tables_luminosities[i] = (float *)malloc(count_L * sizeof(float)); + + restart_read_blocks((void *)p->lum_tables_Z[i], count_Z, sizeof(float), + stream, NULL, "stars props"); + restart_read_blocks((void *)p->lum_tables_ages[i], count_ages, + sizeof(float), stream, NULL, "stars props"); + restart_read_blocks((void *)p->lum_tables_luminosities[i], count_L, + sizeof(float), stream, NULL, "stars props"); + } + } +} + +#endif /* SWIFT_SIMBA_STAR_IO_H */ diff --git a/src/stars/KIARA/stars_part.h b/src/stars/KIARA/stars_part.h new file mode 100644 index 0000000000..064b377469 --- /dev/null +++ b/src/stars/KIARA/stars_part.h @@ -0,0 +1,280 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Matthieu Schaller (schaller@strw.leidenuniv.nl) + * 2018 Folkert Nobels (nobels@strw.leidenuniv.nl) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + ******************************************************************************/ +#ifndef SWIFT_SIMBA_STAR_PART_H +#define SWIFT_SIMBA_STAR_PART_H + +/* Some standard headers. */ +#include + +/* Read additional aubgrid models */ +#include "chemistry_struct.h" +#include "feedback_struct.h" +#ifdef WITH_FOF_GALAXIES +#include "fof_struct.h" +#endif +#include "particle_splitting_struct.h" +#include "rt_struct.h" +#include "star_formation_struct.h" +#include "tracers_struct.h" + +/* Like EAGLE, we define the xpart and spart tracers to be the same */ +#define tracers_spart_data tracers_xpart_data + +/** + * @brief Particle fields for the star particles. + * + * All quantities related to gravity are stored in the associate #gpart. + */ +struct spart { + + /*! Particle ID. */ + long long id; + + /*! Pointer to corresponding gravity part. */ + struct gpart *gpart; + + /*! Particle position. */ + double x[3]; + + /* Offset between current position and position at last tree rebuild. */ + float x_diff[3]; + + /* Offset between current position and position at last tree rebuild. */ + float x_diff_sort[3]; + + /*! Particle velocity. */ + float v[3]; + + /*! Star mass */ + float mass; + + /*! Particle smoothing length. */ + float h; + + struct { + + /* Number of neighbours. */ + float wcount; + + /* Number of neighbours spatial derivative. */ + float wcount_dh; + + } density; + + /*! Union for the birth time and birth scale factor */ + union { + + /*! Birth time */ + float birth_time; + + /*! Birth scale factor */ + float birth_scale_factor; + }; + + /*! Scale-factor / time at which this particle last did enrichment */ + float last_enrichment_time; + + /*! Initial star mass */ + float mass_init; + + /*! The physical birth density */ + float birth_density; + + /*! The birth temperature */ + float birth_temperature; + +#ifdef WITH_FOF_GALAXIES + /*! Struct for host galaxy information */ + struct fof_galaxy_data galaxy_data; +#endif + + /*! Star formation struct */ + struct star_formation_spart_data sf_data; + + /*! Feedback structure */ + struct feedback_spart_data feedback_data; + + /*! Tracer structure */ + struct tracers_spart_data tracers_data; + + /*! Chemistry structure */ + struct chemistry_spart_data chemistry_data; + + /*! Splitting structure */ + struct particle_splitting_data split_data; + + /*! Radiative Transfer data */ + struct rt_spart_data rt_data; + + /*! Particle time bin */ + timebin_t time_bin; + + /*! Number of time-steps since the last enrichment step */ + char count_since_last_enrichment; + + /*! Tree-depth at which size / 2 <= h * gamma < size */ + char depth_h; + +#ifdef SWIFT_DEBUG_CHECKS + + /* Time of the last drift */ + integertime_t ti_drift; + + /* Time of the last kick */ + integertime_t ti_kick; + +#endif + +#ifdef SWIFT_STARS_DENSITY_CHECKS + + /* Integer number of neighbours in the density loop */ + int N_density; + + /* Exact integer number of neighbours in the density loop */ + int N_density_exact; + + /*! Has this particle interacted with any unhibited neighbour? */ + char inhibited_exact; + + float n; + + float n_exact; + + float rho; + + /*! Exact value of the density field obtained via brute-force loop */ + float rho_exact; + + int has_done_feedback; +#endif + +#ifdef DEBUG_INTERACTIONS_STARS + + /*! Number of interactions in the density SELF and PAIR */ + int num_ngb_density; + + /*! List of interacting particles in the density SELF and PAIR */ + long long ids_ngbs_density[MAX_NUM_OF_NEIGHBOURS_STARS]; + + /*! Number of interactions in the feedback SELF and PAIR */ + int num_ngb_feedback; + + /*! List of interacting particles in the feedback SELF and PAIR */ + long long ids_ngbs_feedback[MAX_NUM_OF_NEIGHBOURS_STARS]; +#endif + +} SWIFT_STRUCT_ALIGN; + +#define eagle_stars_lum_tables_N_Z 6 +#define eagle_stars_lum_tables_N_ages 221 + +/** + * @brief The luminosity bands written in snapshots + */ +enum luminosity_bands { + luminosity_GAMA_u_band, + luminosity_GAMA_g_band, + luminosity_GAMA_r_band, + luminosity_GAMA_i_band, + luminosity_GAMA_z_band, + luminosity_GAMA_Y_band, + luminosity_GAMA_J_band, + luminosity_GAMA_H_band, + luminosity_GAMA_K_band, + luminosity_bands_count, +}; + +/** + * @brief Contains all the constants and parameters of the stars scheme + */ +struct stars_props { + + /*! Resolution parameter */ + float eta_neighbours; + + /*! Target weighted number of neighbours (for info only)*/ + float target_neighbours; + + /*! Smoothing length tolerance */ + float h_tolerance; + + /*! Tolerance on neighbour number (for info only)*/ + float delta_neighbours; + + /*! Maximal number of iterations to converge h */ + int max_smoothing_iterations; + + /*! Maximal change of h over one time-step */ + float log_max_h_change; + + /*! Are we overwriting the stars' birth time read from the ICs? */ + int overwrite_birth_time; + + /*! Are we overwriting the stars' birth density read from the ICs? */ + int overwrite_birth_density; + + /*! Are we overwriting the stars' birth temperature read from the ICs? */ + int overwrite_birth_temperature; + + /*! Value to set birth time of stars read from ICs */ + float spart_first_init_birth_time; + + /*! Value to set birth density of stars read from ICs */ + float spart_first_init_birth_density; + + /*! Value to set birth temperature of stars read from ICs */ + float spart_first_init_birth_temperature; + + /*! Factor to multiply age to get the timestep for young stars */ + float time_step_factor_young; + + /*! Factor to multiply age to get the timestep for old stars */ + float time_step_factor_old; + + /*! Maximal time-step length of young stars (internal units) */ + double max_time_step_young; + + /*! Maximal time-step length of old stars (internal units) */ + double max_time_step_old; + + /*! Minimum time-step length of all stars */ + double min_time_step; + + /*! Age threshold for the young/old transition (internal units) */ + double age_threshold; + + /*! Age threshold for the transition to unlimited time-step size (internal + * units) */ + double age_threshold_unlimited; + + /*! The metallicities (metal mass frac) for the luminosity interpolations */ + float *lum_tables_Z[luminosity_bands_count]; + + /*! The age (in Gyr) for the luminosity interpolations */ + float *lum_tables_ages[luminosity_bands_count]; + + /*! The luminosities */ + float *lum_tables_luminosities[luminosity_bands_count]; + + /*! Conversion factor to luminosities */ + double lum_tables_factor; +}; + +#endif /* SWIFT_SIMBA_STAR_PART_H */ diff --git a/src/stars_io.h b/src/stars_io.h index a629235346..a510538552 100644 --- a/src/stars_io.h +++ b/src/stars_io.h @@ -30,6 +30,8 @@ #include "./stars/EAGLE/stars_io.h" #elif defined(STARS_GEAR) #include "./stars/GEAR/stars_io.h" +#elif defined(STARS_KIARA) +#include "./stars/KIARA/stars_io.h" #else #error "Invalid choice of star model" #endif diff --git a/src/timestep.h b/src/timestep.h index 8db2ea2100..0fc7287778 100644 --- a/src/timestep.h +++ b/src/timestep.h @@ -256,7 +256,7 @@ __attribute__((always_inline)) INLINE static integertime_t get_part_rt_timestep( float new_dt = rt_compute_timestep(p, xp, e->rt_props, e->cosmology, e->hydro_properties, - e->physical_constants, e->internal_units); + e->physical_constants, e->cooling_func, e->internal_units); if ((e->policy & engine_policy_cosmology)) /* Apply the maximal displacement constraint (FLT_MAX if non-cosmological)*/ diff --git a/swift.c b/swift.c index 8eddfb068e..b062eb47c1 100644 --- a/swift.c +++ b/swift.c @@ -212,6 +212,8 @@ int main(int argc, char *argv[]) { int with_line_of_sight = 0; int with_rt = 0; int with_power = 0; + int with_kiara = 0; + int with_kiarart = 0; int verbose = 0; int nr_threads = 1; int nr_pool_threads = -1; @@ -310,6 +312,18 @@ int main(int argc, char *argv[]) { "equivalent to --hydro --limiter --sync --self-gravity --stars " "--star-formation --cooling --feedback.", NULL, 0, 0), + OPT_BOOLEAN( + 0, "kiara", &with_kiara, + "Run with all the options needed for the Kiara model. This is " + "equivalent to --hydro --limiter --sync --self-gravity --stars " + "--star-formation --cooling --feedback --black-holes --fof.", + NULL, 0, 0), + OPT_BOOLEAN( + 0, "kiarart", &with_kiarart, + "Run with all the options needed for the Kiara-RT model. This is " + "equivalent to --hydro --limiter --sync --self-gravity --stars " + "--star-formation --feedback --black-holes --fof.", + NULL, 0, 0), OPT_GROUP(" Control options:\n"), OPT_BOOLEAN('a', "pin", &with_aff, @@ -425,6 +439,32 @@ int main(int argc, char *argv[]) { with_cooling = 1; with_feedback = 1; } + if (with_kiara) { + with_hydro = 1; + with_timestep_limiter = 1; + with_timestep_sync = 1; + with_self_gravity = 1; + with_stars = 1; + with_star_formation = 1; + with_cooling = 1; + // with_hydro_decoupling = 1; + with_feedback = 1; + with_black_holes = 1; + with_fof = 1; + } + if (with_kiarart) { + with_hydro = 1; + with_timestep_limiter = 1; + with_timestep_sync = 1; + with_self_gravity = 1; + with_stars = 1; + with_star_formation = 1; + with_cooling = 1; + // with_hydro_decoupling = 1; + with_feedback = 1; + with_black_holes = 1; + with_fof = 1; + } #ifdef MOVING_MESH if (with_hydro) { with_grid = 1; @@ -694,9 +734,9 @@ int main(int argc, char *argv[]) { "Error: Cannot use radiative transfer without --feedback " "(even if configured --with-feedback=none)."); } - if (with_rt && with_cooling) { - error("Error: Cannot use radiative transfer and cooling simultaneously."); - } + //if (with_rt && with_cooling) { + // error("Error: Cannot use radiative transfer and cooling simultaneously."); + //} #endif /* idfef RT_NONE */ #ifdef SINK_NONE diff --git a/tests/Makefile.am b/tests/Makefile.am index d726fe0726..de4214212d 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,7 +15,7 @@ # along with this program. If not, see . # Add the source directory and the non-standard paths to the included library headers to CFLAGS -AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) $(GSL_INCS) $(FFTW_INCS) $(NUMA_INCS) $(CHEALPIX_CFLAGS) +AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) $(GSL_INCS) $(FFTW_INCS) $(NUMA_INCS) $(CHEALPIX_CFLAGS) $(GRACKLE_INCS) AM_LDFLAGS = ../src/.libs/libswiftsim.a $(HDF5_LDFLAGS) $(HDF5_LIBS) $(FFTW_LIBS) $(NUMA_LIBS) $(TCMALLOC_LIBS) $(JEMALLOC_LIBS) $(TBBMALLOC_LIBS) $(GRACKLE_LIBS) $(GSL_LIBS) $(PROFILER_LIBS) $(CHEALPIX_LIBS) diff --git a/tests/test125cells.c b/tests/test125cells.c index f6f60cf3de..6495e9455e 100644 --- a/tests/test125cells.c +++ b/tests/test125cells.c @@ -111,9 +111,10 @@ void set_energy_state(struct part *part, enum pressure_field press, float size, part->entropy = pressure / pow_gamma(density); #elif defined(PHANTOM_SPH) part->u = pressure / (hydro_gamma_minus_one * density); -#elif defined(MINIMAL_SPH) || defined(HOPKINS_PU_SPH) || \ - defined(HOPKINS_PU_SPH_MONAGHAN) || defined(ANARCHY_PU_SPH) || \ - defined(SPHENIX_SPH) || defined(PHANTOM_SPH) || defined(GASOLINE_SPH) +#elif defined(MINIMAL_SPH) || defined(HOPKINS_PU_SPH) || \ + defined(HOPKINS_PU_SPH_MONAGHAN) || defined(ANARCHY_PU_SPH) || \ + defined(SPHENIX_SPH) || defined(PHANTOM_SPH) || defined(GASOLINE_SPH) || \ + defined(MAGMA2_SPH) part->u = pressure / (hydro_gamma_minus_one * density); #elif defined(PLANETARY_SPH) || defined(REMIX_SPH) set_idg_def(&eos.idg_def, 0); diff --git a/tests/testSymmetry.c b/tests/testSymmetry.c index 07167136f8..e0351d1f51 100644 --- a/tests/testSymmetry.c +++ b/tests/testSymmetry.c @@ -45,6 +45,10 @@ void test(void) { const float mu_0 = 4. * M_PI; const integertime_t ti_current = 1; const double time_base = 1e-5; +#ifdef CHEMISTRY_KIARA + cd.use_firehose_wind_model = 1; + cd.firehose_max_velocity = 1000000.; +#endif /* CHEMISTRY_KIARA */ const int with_cosmology = floor(random_uniform(0., 2.)); struct cosmology cosmo; cosmology_init_no_cosmo(&cosmo); @@ -66,6 +70,10 @@ void test(void) { pj.id = 2ll; pi.time_bin = 1; pj.time_bin = 1; + pi.decoupled = floor(random_uniform(0, 2.)); + pj.decoupled = floor(random_uniform(0, 2.)); + + message("%d %d", pi.decoupled, pj.decoupled); #if defined(GIZMO_MFV_SPH) /* Give the primitive variables sensible values, since the Riemann solver does @@ -118,6 +126,11 @@ void test(void) { bzero(&xpi, sizeof(struct xpart)); bzero(&xpj, sizeof(struct xpart)); + for (size_t i = 0; i < sizeof(struct xpart) / sizeof(float); ++i) { + *(((float *)&xpi) + i) = (float)random_uniform(0., 2.); + *(((float *)&xpj) + i) = (float)random_uniform(0., 2.); + } + /* Make some copies */ struct part pi2, pj2; memcpy(&pi2, &pi, sizeof(struct part));