/******************************************************************************* * 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_GEAR_H #define SWIFT_RT_GEAR_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/GEAR/rt.h * @brief Main header file for the GEAR 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 { 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) {} /** * @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 } /** * @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, 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) { /* 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, rt_props, hydro_props, phys_const, us, cosmo); } /** * @brief Computes the next radiative transfer time step size * of a given particle (during timestep tasks) * * @param p Particle to work on. * @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 unit_system* restrict us) { /* just mimic the gizmo particle "size" for now */ const float psize = cosmo->a * 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 = FLT_MAX; 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, 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 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 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 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) { #ifdef SWIFT_RT_DEBUG_CHECKS rt_debug_sequence_check(p, 4, __func__); p->rt_data.debug_thermochem_done += 1; #endif /* 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, us, dt, 0); } /** * @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_GEAR_H */