/******************************************************************************* * This file is part of SWIFT. * Copyright (c) 2021 Tsang Keung Chan (chantsangkeung@gmail.com) * 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_SPHM1RT_H #define SWIFT_RT_SPHM1RT_H #include "rt_cooling.h" #include "rt_getters.h" #include "rt_properties.h" #include "rt_setters.h" #include "rt_stellar_emission_rate.h" #include "rt_struct.h" #include "rt_unphysical.h" #include /** * @file src/rt/SPHM1RT/rt.h * @brief Main header file for SPHM1RT radiative transfer scheme. * SPHM1RT method described in Chan+21: 2102.08404 */ /** * @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) {} /** * @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) { struct rt_part_data* rpd = &p->rt_data; for (int g = 0; g < RT_NGROUPS; g++) { rpd->dconserved_dt[g].urad = 0.0f; rpd->dconserved_dt[g].frad[0] = 0.0f; rpd->dconserved_dt[g].frad[1] = 0.0f; rpd->dconserved_dt[g].frad[2] = 0.0f; } for (int g = 0; g < RT_NGROUPS; g++) { rpd->viscosity[g].divf = 0.0f; rpd->diffusion[g].graduradc[0] = 0.0f; rpd->diffusion[g].graduradc[1] = 0.0f; rpd->diffusion[g].graduradc[2] = 0.0f; } /* To avoid radiation reaching other dimension and violating conservation */ for (int g = 0; g < RT_NGROUPS; g++) { #if defined(HYDRO_DIMENSION_1D) rpd->conserved[g].frad[1] = 0.0f; rpd->conserved[g].frad[2] = 0.0f; #endif #if defined(HYDRO_DIMENSION_2D) rpd->conserved[g].frad[2] = 0.0f; #endif } float urad_old; const float cred = rt_get_comoving_cred(p, cosmo->a); for (int g = 0; g < RT_NGROUPS; g++) { /* TK: avoid the radiation flux to violate causality. Impose a limit: Fconserved[g].urad; rt_check_unphysical_state(&rpd->conserved[g].urad, rpd->conserved[g].frad, urad_old, cred); } } /** * @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) { struct rt_part_data* rpd = &p->rt_data; for (int g = 0; g < RT_NGROUPS; g++) { rpd->viscosity[g].alpha = 1.0f; rpd->diffusion[g].alpha = 1.0f; rpd->params.chi[g] = rt_props->initialchi[g]; rpd->viscosity[g].divf_previous_step = 0.0f; } /* We can get parameters for diffusion (force loop) */ rpd->params.cred_phys = rt_props->cred_phys; rpd->force.f = 1.0f; rpd->dt = 1.0f; rt_init_part(p); rt_reset_part(p, cosmo); rt_reset_part_each_subcycle(p, cosmo, 0.); } /** * @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) { sp->rt_data.injection_weight = 0.f; for (int g = 0; g < RT_NGROUPS; g++) { sp->rt_data.emission_reinject[g] = 0.f; } } /** * @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); } /** * @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) { 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) { struct rt_part_data* rpd = &p->rt_data; /* Note that in the input, we read radiation energy and flux * then we convert these quantities to radiation energy per mass and flux per * mass */ for (int g = 0; g < RT_NGROUPS; g++) { rpd->conserved[g].urad = rpd->conserved[g].urad / p->mass; rpd->conserved[g].frad[0] = rpd->conserved[g].frad[0] / p->mass; rpd->conserved[g].frad[1] = rpd->conserved[g].frad[1] / p->mass; rpd->conserved[g].frad[2] = rpd->conserved[g].frad[2] / p->mass; } /* rpd->cred_phys and rt_props->cred_phys are in physical unit */ rpd->params.cred_phys = rt_props->cred_phys; /* Initialize element mass fractions accoridng to parameter files. */ rt_tchem_first_init_part(p, rt_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 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. * @return 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) { float cred_phys = rt_get_physical_cred(p, cosmo->a); float dt = p->h * cosmo->a / cred_phys * rt_props->CFL_condition; return dt; } /** * @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 * * @return star time step */ __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) { error("SPHM1RT with cosmology not implemented yet! :("); return 0.f; } 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) {} /** * @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) { /* 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.*/ star_age = dt; } /* 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); rt_set_stellar_emission_rate(sp, star_age_begin_of_step, star_age, rt_props, phys_const, internal_units); } /** * @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) { struct rt_part_data* rpd = &p->rt_data; /* artificial diffusion for shock capturing */ const float vsig_diss = rt_get_comoving_cred(p, cosmo->a); /* similar to Cullen & Dehnen 2010 switch */ float divf, divf_previous_step, urad, viscosity_alpha, diffusion_alpha; float divf_dt, shockest, alphaflim, alpha_f_diss, alpha_f_diss_loc; float alpha_diss_loc, alpha_diss; if (rpd->dt == 0) return; for (int g = 0; g < RT_NGROUPS; g++) { divf = rpd->viscosity[g].divf; divf_previous_step = rpd->viscosity[g].divf_previous_step; urad = rpd->conserved[g].urad; viscosity_alpha = rpd->viscosity[g].alpha; diffusion_alpha = rpd->diffusion[g].alpha; divf_dt = (divf - divf_previous_step) / (rpd->dt); if (urad == 0.f) { shockest = FLT_MAX; } else { shockest = -p->h * p->h / (vsig_diss) / (vsig_diss)*divf_dt * 200.f; shockest /= urad; } alphaflim = max(shockest, 0.0f); /* should be positive or 0 */ alpha_f_diss = viscosity_alpha; alpha_f_diss_loc = 0.0f; /* f diffusion only operates in compression */ if (divf < 0.0f) { /* limit the diffusivity to Courant time step */ alpha_f_diss_loc = min(alphaflim, 1.0f); } if (alpha_f_diss_loc > alpha_f_diss) { /* Reset the value of alpha to the appropriate value */ alpha_f_diss = alpha_f_diss_loc; } else { /* Integrate the alpha forward in time to decay back to alpha = alpha_loc */ alpha_f_diss = alpha_f_diss_loc + (alpha_f_diss - alpha_f_diss_loc) * expf(-rpd->dt * vsig_diss * (1.f / p->h + rpd->params.chi[g] * p->rho)); } /* alpha inspired by Price 2010: it should vanish where radiation energy * difference is small */ alpha_diss_loc = 1.0f; alpha_diss = diffusion_alpha; if (alpha_diss_loc > alpha_diss) { /* Reset the value of alpha to the appropriate value */ alpha_diss = alpha_diss_loc; } else { /* Integrate the alpha forward in time to decay back to alpha = alpha_loc */ alpha_diss = alpha_diss_loc + (alpha_diss - alpha_diss_loc) * expf(-rpd->dt * vsig_diss * (0.01f / p->h + rpd->params.chi[g] * p->rho)); } /* Cap the dissipation to avoid instabilities */ alpha_diss = min(alpha_diss, 1.0f); alpha_diss = max(alpha_diss, 0.0f); alpha_f_diss = min(alpha_f_diss, 1.0f); alpha_f_diss = max(alpha_f_diss, 0.0f); rpd->diffusion[g].alpha = alpha_diss; rpd->viscosity[g].alpha = alpha_f_diss; } } /** * @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) { struct rt_part_data* rpd = &p->rt_data; for (int g = 0; g < RT_NGROUPS; g++) { rpd->conserved[g].urad += rpd->dconserved_dt[g].urad * dt; rpd->conserved[g].frad[0] += rpd->dconserved_dt[g].frad[0] * dt; rpd->conserved[g].frad[1] += rpd->dconserved_dt[g].frad[1] * dt; rpd->conserved[g].frad[2] += rpd->dconserved_dt[g].frad[2] * dt; } /* add frad source term implicitly */ float dfrac, cred; cred = rt_get_comoving_cred(p, cosmo->a); for (int g = 0; g < RT_NGROUPS; g++) { dfrac = -rpd->params.chi[g] * p->rho * cred; rpd->conserved[g].frad[0] *= expf(dfrac * dt); rpd->conserved[g].frad[1] *= expf(dfrac * dt); rpd->conserved[g].frad[2] *= expf(dfrac * dt); /* update urad */ /* limiter to avoid negative urad */ /* negative urad will make the dissipation (diffusion) unstable) */ if (rpd->conserved[g].urad < 0.0f) { rpd->conserved[g].urad = 0.0f; rpd->conserved[g].frad[0] = 0.0f; rpd->conserved[g].frad[1] = 0.0f; rpd->conserved[g].frad[2] = 0.0f; } /* save next time step */ rpd->viscosity[g].divf_previous_step = rpd->viscosity[g].divf; } rpd->dt = dt; /* To avoid radiation reaching other dimension and violating conservation */ for (int g = 0; g < RT_NGROUPS; g++) { #if defined(HYDRO_DIMENSION_1D) rpd->conserved[g].frad[1] = 0.0f; rpd->conserved[g].frad[2] = 0.0f; #endif #if defined(HYDRO_DIMENSION_2D) rpd->conserved[g].frad[2] = 0.0f; #endif } } /** * @brief Do the thermochemistry on a particle. * * @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. */ 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); /** * @brief Extra operations done during the kick. * * @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) {} /** * @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 ionizing 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) { struct rt_part_data* rpd = &p->rt_data; /* Some smoothing length multiples. */ const float rho = hydro_get_comoving_density(p); const float rho_inv = 1.0f / rho; /* 1 / rho */ /* Compute the "grad h" term */ float rho_dh = p->density.rho_dh; const float omega_inv = 1.f / (1.f + hydro_dimension_inv * p->h * rho_dh * rho_inv); /* Update variables. */ rpd->force.f = omega_inv; } /** * @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) {} /** * @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) {} #endif /* SWIFT_RT_SPHM1RT_H */