/******************************************************************************* * This file is part of SWIFT. * Copyright (c) 2016 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 . * ******************************************************************************/ #include #include "adiabatic_index.h" #include "approx_math.h" #include "equation_of_state.h" #include "hydro_gradients.h" #include "hydro_properties.h" #include "hydro_space.h" #include "voronoi_algorithm.h" /** * @brief Computes the hydro time-step of a given particle * * @param p Pointer to the particle data. * @param xp Pointer to the extended particle data. * @param hydro_properties Pointer to the hydro parameters. */ __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 float CFL_condition = hydro_properties->CFL_condition; float R = get_radius_dimension_sphere(p->cell.volume); if (p->timestepvars.vmax == 0.) { /* vmax can be zero in vacuum. We force the time step to become the maximal time step in this case */ return FLT_MAX; } else { return CFL_condition * R / fabsf(p->timestepvars.vmax); } } /** * @brief Does some extra hydro operations once the actual physical time step * for the particle is known. * * We use this to store the physical time step, since it is used for the flux * exchange during the force loop. * * We also set the active flag of the particle to inactive. It will be set to * active in hydro_init_part, which is called the next time the particle becomes * active. * * @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) { p->force.dt = dt; p->force.active = 0; } /** * @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. * * In this case, we copy the particle velocities into the corresponding * primitive variable field. We do this because the particle velocities in GIZMO * can be independent of the actual fluid velocity. The latter is stored as a * primitive variable and integrated using the linear momentum, a conserved * variable. * * @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* p, struct xpart* xp) { const float mass = p->conserved.mass; p->primitives.v[0] = p->v[0]; p->primitives.v[1] = p->v[1]; p->primitives.v[2] = p->v[2]; p->conserved.momentum[0] = mass * p->primitives.v[0]; p->conserved.momentum[1] = mass * p->primitives.v[1]; p->conserved.momentum[2] = mass * p->primitives.v[2]; #ifdef EOS_ISOTHERMAL_GAS p->conserved.energy = mass * const_isothermal_internal_energy; #else p->conserved.energy *= mass; #endif #ifdef SHADOWFAX_TOTAL_ENERGY p->conserved.energy += 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] + p->conserved.momentum[1] * p->primitives.v[1] + p->conserved.momentum[2] * p->primitives.v[2]); #endif #if defined(SHADOWFAX_FIX_CELLS) p->v[0] = 0.; p->v[1] = 0.; p->v[2] = 0.; #endif /* set the initial velocity of the cells */ xp->v_full[0] = p->v[0]; xp->v_full[1] = p->v[1]; xp->v_full[2] = p->v[2]; } /** * @brief Prepares a particle for the volume calculation. * * Simply makes sure all necessary variables are initialized to zero. * Initializes the Voronoi cell. * * @param p The particle to act upon * @param hs #hydro_space containing extra information about the space. */ __attribute__((always_inline)) INLINE static void hydro_init_part( struct part* p, const struct hydro_space* hs) { p->density.wcount = 0.0f; p->density.wcount_dh = 0.0f; voronoi_cell_init(&p->cell, p->x, hs->anchor, hs->side); /* Set the active flag to active. */ p->force.active = 1; } /** * @brief Finishes the volume calculation. * * Calls the finalize method on the Voronoi cell, which calculates the volume * and centroid of the cell. We use the return value of this function to set * a new value for the smoothing length and possibly force another iteration * of the volume calculation for this particle. We then use the volume to * convert conserved variables into primitive variables. * * This method also initializes the gradient variables (if gradients are used). * * @param p The particle to act upon. */ __attribute__((always_inline)) INLINE static void hydro_end_density( struct part* restrict p) { float volume; float m, momentum[3], energy; hydro_gradients_init(p); float hnew = voronoi_cell_finalize(&p->cell); /* Enforce hnew as new smoothing length in the iteration This is annoyingly difficult, as we do not have access to the variables that govern the loop... So here's an idea: let's force in some method somewhere that makes sure r->e->hydro_properties->target_neighbours is 1, and r->e->hydro_properties->delta_neighbours is 0. This way, we can accept an iteration by setting p->density.wcount to 1. To get the right correction for h, we set wcount to something else (say 0), and then set p->density.wcount_dh to 1/(hnew-h). */ if (hnew < p->h) { /* Iteration succesful: we accept, but manually set h to a smaller value for the next time step */ p->density.wcount = 1.0f; p->h = 1.1f * hnew; } else { /* Iteration not succesful: we force h to become 1.1*hnew */ p->density.wcount = 0.0f; p->density.wcount_dh = 1.0f / (1.1f * hnew - p->h); return; } volume = p->cell.volume; #ifdef SWIFT_DEBUG_CHECKS /* the last condition checks for NaN: a NaN value always evaluates to false, even when checked against itself */ if (volume == 0. || volume == INFINITY || volume != volume) { error("Invalid value for cell volume (%g)!", volume); } #endif /* compute primitive variables */ /* eqns (3)-(5) */ m = p->conserved.mass; if (m > 0.) { momentum[0] = p->conserved.momentum[0]; momentum[1] = p->conserved.momentum[1]; momentum[2] = p->conserved.momentum[2]; p->primitives.rho = m / volume; p->primitives.v[0] = momentum[0] / m; p->primitives.v[1] = momentum[1] / m; p->primitives.v[2] = momentum[2] / m; energy = p->conserved.energy; #ifdef SHADOWFAX_TOTAL_ENERGY energy -= 0.5f * (momentum[0] * p->primitives.v[0] + momentum[1] * p->primitives.v[1] + momentum[2] * p->primitives.v[2]); #endif energy /= m; p->primitives.P = gas_pressure_from_internal_energy(p->primitives.rho, energy); } else { p->primitives.rho = 0.; p->primitives.v[0] = 0.; p->primitives.v[1] = 0.; p->primitives.v[2] = 0.; p->primitives.P = 0.; } #ifdef SWIFT_DEBUG_CHECKS if (p->primitives.rho < 0.) { error("Negative density!"); } if (p->primitives.P < 0.) { error("Negative pressure!"); } #endif } /** * @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 */ __attribute__((always_inline)) INLINE static void hydro_part_has_no_neighbours( struct part* restrict p, struct xpart* restrict xp) { /* 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->density.wcount = kernel_root * kernel_norm * h_inv_dim; p->density.wcount_dh = 0.f; } /** * @brief Prepare a particle for the gradient calculation. * * The name of this method is confusing, as this method is really 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. */ __attribute__((always_inline)) INLINE static void hydro_prepare_force( struct part* restrict p, struct xpart* restrict xp) { /* Initialize time step criterion variables */ p->timestepvars.vmax = 0.0f; /* Set the actual velocity of the particle */ p->force.v_full[0] = xp->v_full[0]; p->force.v_full[1] = xp->v_full[1]; p->force.v_full[2] = xp->v_full[2]; } /** * @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. * * @param p The particle to act upon. */ __attribute__((always_inline)) INLINE static void hydro_end_gradient( struct part* p) { hydro_gradients_finalize(p); } /** * @brief Reset acceleration fields of a particle * * This is actually not necessary for Shadowswift, since we just set the * accelerations after the flux calculation. * * @param p The particle to act upon. */ __attribute__((always_inline)) INLINE static void hydro_reset_acceleration( struct part* 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->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. */ __attribute__((always_inline)) INLINE static void hydro_reset_predicted_values( struct part* restrict p, const struct xpart* restrict xp) {} /** * @brief Converts the hydrodynamic variables from the initial condition file to * conserved variables that can be used during the integration * * Requires the volume to be known. * * The initial condition file contains a mixture of primitive and conserved * variables. Mass is a conserved variable, and we just copy the particle * mass into the corresponding conserved quantity. We need the volume to * also derive a density, which is then used to convert the internal energy * to a pressure. However, we do not actually use these variables anymore. * We do need to initialize the linear momentum, based on the mass and the * velocity of the particle. * * @param p The particle to act upon. * @param xp The extended particle data to act upon. */ __attribute__((always_inline)) INLINE static void hydro_convert_quantities( struct part* p, struct xpart* xp) {} /** * @brief Extra operations to be done during the drift * * Not used for Shadowswift. * * @param p Particle to act upon. * @param xp The extended particle data to act upon. * @param dt The drift time-step. */ __attribute__((always_inline)) INLINE static void hydro_predict_extra( struct part* p, struct xpart* xp, float dt) {} /** * @brief Set the particle acceleration after the flux loop. * * @param p Particle to act upon. */ __attribute__((always_inline)) INLINE static void hydro_end_force( struct part* p) {} /** * @brief Extra operations done during the kick * * Not used for Shadowswift. * * @param p Particle to act upon. * @param xp Extended particle data to act upon. * @param dt Physical time step. */ __attribute__((always_inline)) INLINE static void hydro_kick_extra( struct part* p, struct xpart* xp, float dt) { float vcell[3]; /* Update the conserved variables. We do this here and not in the kick, since we need the updated variables below. */ p->conserved.mass += p->conserved.flux.mass; p->conserved.momentum[0] += p->conserved.flux.momentum[0]; p->conserved.momentum[1] += p->conserved.flux.momentum[1]; p->conserved.momentum[2] += p->conserved.flux.momentum[2]; p->conserved.energy += p->conserved.flux.energy; #ifdef EOS_ISOTHERMAL_GAS /* reset the thermal energy */ p->conserved.energy = p->conserved.mass * const_isothermal_internal_energy; #ifdef SHADOWFAX_TOTAL_ENERGY p->conserved.energy += 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] + p->conserved.momentum[1] * p->primitives.v[1] + p->conserved.momentum[2] * p->primitives.v[2]); #endif #endif /* reset fluxes */ /* we can only do this here, since we need to keep the fluxes for inactive particles */ p->conserved.flux.mass = 0.0f; p->conserved.flux.momentum[0] = 0.0f; p->conserved.flux.momentum[1] = 0.0f; p->conserved.flux.momentum[2] = 0.0f; p->conserved.flux.energy = 0.0f; if (p->conserved.mass > 0.) { /* We want the cell velocity to be as close as possible to the fluid velocity */ vcell[0] = p->conserved.momentum[0] / p->conserved.mass; vcell[1] = p->conserved.momentum[1] / p->conserved.mass; vcell[2] = p->conserved.momentum[2] / p->conserved.mass; } else { vcell[0] = 0.; vcell[1] = 0.; vcell[2] = 0.; } #ifdef SHADOWFAX_STEER_CELL_MOTION /* To prevent stupid things like cell crossovers or generators that move outside their cell, we steer the motion of the cell somewhat */ if (p->primitives.rho) { float centroid[3], d[3]; float volume, csnd, R, vfac, fac, dnrm; voronoi_get_centroid(&p->cell, centroid); d[0] = centroid[0] - p->x[0]; d[1] = centroid[1] - p->x[1]; d[2] = centroid[2] - p->x[2]; dnrm = sqrtf(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); csnd = sqrtf(hydro_gamma * p->primitives.P / p->primitives.rho); volume = p->cell.volume; R = get_radius_dimension_sphere(volume); fac = 4.0f * dnrm / R; if (fac > 0.9f) { if (fac < 1.1f) { vfac = csnd * (dnrm - 0.225f * R) / dnrm / (0.05f * R); } else { vfac = csnd / dnrm; } } else { vfac = 0.0f; } vcell[0] += vfac * d[0]; vcell[1] += vfac * d[1]; vcell[2] += vfac * d[2]; } #endif #if defined(SHADOWFAX_FIX_CELLS) xp->v_full[0] = 0.; xp->v_full[1] = 0.; xp->v_full[2] = 0.; p->v[0] = 0.; p->v[1] = 0.; p->v[2] = 0.; #else xp->v_full[0] = vcell[0]; xp->v_full[1] = vcell[1]; xp->v_full[2] = vcell[2]; p->v[0] = xp->v_full[0]; p->v[1] = xp->v_full[1]; p->v[2] = xp->v_full[2]; #endif } /** * @brief Returns the internal energy of a particle * * @param p The particle of interest. * @return Internal energy of the particle. */ __attribute__((always_inline)) INLINE static float hydro_get_internal_energy( const struct part* restrict p) { if (p->primitives.rho > 0.) { return gas_internal_energy_from_pressure(p->primitives.rho, p->primitives.P); } else { return 0.; } } /** * @brief Returns the entropy of a particle * * @param p The particle of interest. * @return Entropy of the particle. */ __attribute__((always_inline)) INLINE static float hydro_get_entropy( const struct part* restrict p) { if (p->primitives.rho > 0.) { return gas_entropy_from_pressure(p->primitives.rho, p->primitives.P); } else { return 0.; } } /** * @brief Returns the sound speed of a particle * * @param p The particle of interest. * @param Sound speed of the particle. */ __attribute__((always_inline)) INLINE static float hydro_get_soundspeed( const struct part* restrict p) { if (p->primitives.rho > 0.) { return gas_soundspeed_from_pressure(p->primitives.rho, p->primitives.P); } else { return 0.; } } /** * @brief Returns the pressure of a particle * * @param p The particle of interest * @param Pressure of the particle. */ __attribute__((always_inline)) INLINE static float hydro_get_pressure( const struct part* restrict p) { return p->primitives.P; } /** * @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->conserved.mass; } /** * @brief Returns the velocities drifted to the current time of a particle. * * @param p The particle of interest * @param xp The extended data of the particle. * @param dt The time since the last kick. * @param v (return) The velocities at the current time. */ __attribute__((always_inline)) INLINE static void hydro_get_drifted_velocities( const struct part* restrict p, const struct xpart* xp, float dt, float v[3]) {} /** * @brief Returns the density of a particle * * @param p The particle of interest */ __attribute__((always_inline)) INLINE static float hydro_get_density( const struct part* restrict p) { return p->primitives.rho; } /** * @brief Modifies the thermal state of a particle to the imposed internal * energy * * This overrides the current state of the particle but does *not* change its * time-derivatives * * @param p The particle * @param u The new internal energy */ __attribute__((always_inline)) INLINE static void hydro_set_internal_energy( struct part* restrict p, float u) { if (p->primitives.rho > 0.) { p->conserved.energy = u * p->conserved.mass; #ifdef SHADOWFAX_TOTAL_ENERGY p->conserved.energy += 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] + p->conserved.momentum[1] * p->primitives.v[1] + p->conserved.momentum[2] * p->primitives.v[2]); #endif p->primitives.P = gas_pressure_from_internal_energy(p->primitives.rho, u); } } /** * @brief Modifies the thermal state of a particle to the imposed entropy * * This overrides the current state of the particle but does *not* change its * time-derivatives * * @param p The particle * @param S The new entropy */ __attribute__((always_inline)) INLINE static void hydro_set_entropy( struct part* restrict p, float S) { if (p->primitives.rho > 0.) { p->conserved.energy = gas_internal_energy_from_entropy(p->primitives.rho, S) * p->conserved.mass; #ifdef SHADOWFAX_TOTAL_ENERGY p->conserved.energy += 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] + p->conserved.momentum[1] * p->primitives.v[1] + p->conserved.momentum[2] * p->primitives.v[2]); #endif p->primitives.P = gas_pressure_from_entropy(p->primitives.rho, S); } }