diff --git a/examples/main.c b/examples/main.c index a66c9bb0c072e617b0ea761c6ff68202973baa47..02ce01776eec309335ad938802428dde9f931f38 100644 --- a/examples/main.c +++ b/examples/main.c @@ -1488,6 +1488,8 @@ int main(int argc, char *argv[]) { if (with_cosmology && with_self_gravity && !dry_run) { const int check_neutrinos = !neutrino_properties.use_delta_f; space_check_cosmology(&s, &cosmo, with_hydro, myrank, check_neutrinos); + neutrino_check_cosmology(&s, &cosmo, &prog_const, params, + &neutrino_properties, myrank, verbose); } /* Write the state of the system before starting time integration. */ diff --git a/src/Makefile.am b/src/Makefile.am index b9021950e4952cdd40043d2c873424d85211829d..61f5b150fab135cf7fa43c13e2fbc18a89c078dc 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -179,7 +179,7 @@ AM_SOURCES += output_list.c velociraptor_dummy.c csds_io.c memuse.c mpiuse.c mem AM_SOURCES += fof.c fof_catalogue_io.c AM_SOURCES += hashmap.c pressure_floor.c AM_SOURCES += runner_neutrino.c -AM_SOURCES += neutrino/Default/fermi_dirac.c +AM_SOURCES += neutrino/Default/fermi_dirac.c neutrino/Default/neutrino.c AM_SOURCES += $(QLA_COOLING_SOURCES) AM_SOURCES += $(EAGLE_COOLING_SOURCES) $(EAGLE_FEEDBACK_SOURCES) AM_SOURCES += $(GRACKLE_COOLING_SOURCES) $(GEAR_FEEDBACK_SOURCES) diff --git a/src/neutrino/Default/neutrino.c b/src/neutrino/Default/neutrino.c new file mode 100644 index 0000000000000000000000000000000000000000..b6e70a0f6381ec23fb4169d580c73109997f9ced --- /dev/null +++ b/src/neutrino/Default/neutrino.c @@ -0,0 +1,227 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2021 Willem Elbers (whe@willemelbers.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 <http://www.gnu.org/licenses/>. + * + ******************************************************************************/ + +/* This object's header. */ +#include "neutrino.h" + +/* Standard headers */ +#include <math.h> + +/** + * @brief Compute diagnostics for the neutrino delta-f method, including + * the mean squared weight. + * + * @param s The #space. + * @param cosmo The current cosmology model. + * @param physical_constants The #phys_const used for this run. + * @param neutrino_props The #neutrino_props used for this run. + * @param rank The MPI rank of this #space. + * @param r Output: correlation coefficient between current and sampled momenta + * @param I_df Output: half the mean squared weight + * @param mass_tot Output: the total mass in neutrino particles + */ +void compute_neutrino_diagnostics( + const struct space *s, const struct cosmology *cosmo, + const struct phys_const *physical_constants, + const struct neutrino_props *neutrino_properties, const int rank, double *r, + double *I_df, double *mass_tot) { + + if (!neutrino_properties->use_delta_f) { + error("Neutrino diagnostics only defined when using the delta-f method."); + } + + struct gpart *gparts = s->gparts; + const size_t nr_gparts = s->nr_gparts; + + /* Retrieve physical and cosmological constants */ + const double c_vel = physical_constants->const_speed_light_c; + const double *m_eV_array = cosmo->M_nu_eV; + const double *deg_array = cosmo->deg_nu; + const int N_nu = cosmo->N_nu; + const double T_eV = cosmo->T_nu_0_eV; + const double fac = 1.0 / (c_vel * T_eV); + const double inv_mass_factor = 1. / s->e->neutrino_mass_conversion_factor; + const long long total_nr_neutrinos = s->e->total_nr_neutrino_gparts; + const long long neutrino_seed = neutrino_properties->neutrino_seed; + + /* Sum up the masses, weights, and momenta for the neutrinos in this space */ + double mass_sum = 0; + double weight2_sum = 0; + double p_sum = 0; // current momenta + double p2_sum = 0; + double pi_sum = 0; // sampled momenta + double pi2_sum = 0; + double ppi_sum = 0; + for (size_t i = 0; i < nr_gparts; ++i) { + + /* Skip extra and non-neutrino particles */ + if (gparts[i].time_bin == time_bin_not_created) continue; + if (gparts[i].type != swift_type_neutrino) continue; + + /* Use a particle id dependent seed */ + const long long seed = gparts[i].id_or_neg_offset + neutrino_seed; + + /* Compute the initial dimensionless momentum from the seed */ + const double pi = neutrino_seed_to_fermi_dirac(seed); + + /* The neutrino mass and degeneracy (we cycle based on the seed) */ + const double m_eV = neutrino_seed_to_mass(N_nu, m_eV_array, seed); + const double deg = neutrino_seed_to_degeneracy(N_nu, deg_array, seed); + const double mass = deg * m_eV * inv_mass_factor; + + /* Compute the current dimensionless momentum */ + double v2 = gparts[i].v_full[0] * gparts[i].v_full[0] + + gparts[i].v_full[1] * gparts[i].v_full[1] + + gparts[i].v_full[2] * gparts[i].v_full[2]; + double p = sqrt(v2) * m_eV * fac; + + /* Compute the initial and current background phase-space density */ + double fi = fermi_dirac_density(pi); + double f = fermi_dirac_density(p); + double weight = 1.0 - f / fi; + + p_sum += p; + p2_sum += p * p; + pi_sum += pi; + pi2_sum += pi * pi; + ppi_sum += p * pi; + mass_sum += mass; + weight2_sum += weight * weight; + } + +/* Reduce the total mass, weights, and momenta */ +#ifdef WITH_MPI + double sums[7] = {p_sum, p2_sum, pi_sum, pi2_sum, + ppi_sum, mass_sum, weight2_sum}; + double total_sums[7]; + + MPI_Reduce(&sums, &total_sums, 7, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD); + + double total_p = total_sums[0]; + double total_p2 = total_sums[1]; + double total_pi = total_sums[2]; + double total_pi2 = total_sums[3]; + double total_ppi = total_sums[4]; + double total_mass = total_sums[5]; + double total_weight2 = total_sums[6]; +#else + double total_p = p_sum; + double total_p2 = p2_sum; + double total_pi = pi_sum; + double total_pi2 = pi2_sum; + double total_ppi = ppi_sum; + double total_mass = mass_sum; + double total_weight2 = weight2_sum; +#endif + + if (rank == 0) { + /* Compute the correlation coefficient */ + double Sp = total_nr_neutrinos * total_p2 - total_p * total_p; + double Spi = total_nr_neutrinos * total_pi2 - total_pi * total_pi; + double Sppi = sqrt(Sp * Spi); + *r = (total_nr_neutrinos * total_ppi - total_p * total_pi) / Sppi; + + /* Half the mean squared weight */ + *I_df = 0.5 * total_weight2 / total_nr_neutrinos; + + /* The total mass in neutrino particles */ + *mass_tot = total_mass; + } +} + +/** + * @brief Verify that the neutrino content matches the cosmological model + * and has been correctly loaded, when using the delta-f method. + * + * @param s The #space. + * @param cosmo The current cosmology model. + * @param physical_constants The #phys_const used for this run. + * @param params The parsed parameters. + * @param neutrino_props The #neutrino_props used for this run. + * @param rank The MPI rank of this #space. + * @param with_neutrinos Are we running with neutrino particles? + * @param verbose Are we verbose? + */ +void neutrino_check_cosmology(const struct space *s, + const struct cosmology *cosmo, + const struct phys_const *physical_constants, + struct swift_params *params, + const struct neutrino_props *neutrino_props, + const int rank, const int verbose) { + + /* Check that we are not missing any neutrino particles */ + if (!s->with_neutrinos) { + int use_df = parser_get_opt_param_int(params, "Neutrino:use_delta_f", 0); + int genics = parser_get_opt_param_int(params, "Neutrino:generate_ics", 0); + if (use_df || genics) + error( + "Running without neutrino particles, but specified a neutrino " + "model in the parameter file."); + } + + /* We are done if the delta-f method is not used, since in that case the + * total mass has already been checked in space_check_cosmology. */ + if (!neutrino_props->use_delta_f) return; + + /* Compute neutrino diagnostics, including the total mass */ + double r, I_df, total_mass; + compute_neutrino_diagnostics(s, cosmo, physical_constants, neutrino_props, + rank, &r, &I_df, &total_mass); + + if (rank == 0) { + /* Check the correlation coefficient */ + if (r < 0.1) + error( + "There is no correlation between current and sampled neutrino " + "momenta (r = %e, I = %e). Most likely, the neutrino seed is " + "incorrect or the particle IDs have been scrambled.", + r, I_df); + + /* Check the mean squared weight */ + else if (I_df > 0.1) + error( + "The neutrino particle weights are very large (r = %e, I = %e). " + "Most likely, the particle velocities are incorrectly normalised.", + r, I_df); + + if (verbose) message("Neutrino delta-f diagnostic: I = %e", I_df); + + /* Check the neutrino mass */ + const double volume = s->dim[0] * s->dim[1] * s->dim[2]; + + /* Current Hubble constant */ + const double H = cosmo->H; + + /* z=0 Hubble parameter */ + const double H0 = cosmo->H0; + + /* Critical density at z=0 */ + const double rho_crit0 = cosmo->critical_density * H0 * H0 / (H * H); + + /* Density in neutrino particles */ + const double Omega_particles_nu = (total_mass / volume) / rho_crit0; + + if (fabs(Omega_particles_nu - cosmo->Omega_nu_0) > 1e-4) + error( + "The massive neutrino content of the simulation does not match the " + "cosmology in the parameter file: cosmo.Omega_nu = %e particles " + "Omega_nu = %e", + cosmo->Omega_nu_0, Omega_particles_nu); + } +} diff --git a/src/neutrino/Default/neutrino.h b/src/neutrino/Default/neutrino.h index b08ae4291951b2c05d99940ffbf2c2cec5ba76d4..d2fd637943ba80e0a2a83a957528c5b82154e3a5 100644 --- a/src/neutrino/Default/neutrino.h +++ b/src/neutrino/Default/neutrino.h @@ -122,4 +122,16 @@ __attribute__((always_inline)) INLINE static void gravity_first_init_neutrino( } } +void compute_neutrino_diagnostics( + const struct space *s, const struct cosmology *cosmo, + const struct phys_const *physical_constants, + const struct neutrino_props *neutrino_properties, const int rank, double *r, + double *I_df, double *mass_tot); +void neutrino_check_cosmology(const struct space *s, + const struct cosmology *cosmo, + const struct phys_const *physical_constants, + struct swift_params *params, + const struct neutrino_props *neutrino_props, + const int rank, const int verbose); + #endif /* SWIFT_DEFAULT_NEUTRINO_H */ diff --git a/src/space.c b/src/space.c index f4a382ebb41e4f41f9f9371350849dc73f9e1e05..1621ea3d810217fe69e11862a32bde743f72ed8b 100644 --- a/src/space.c +++ b/src/space.c @@ -49,6 +49,7 @@ #include "kernel_hydro.h" #include "lock.h" #include "minmax.h" +#include "neutrino/Default/fermi_dirac.h" #include "proxy.h" #include "restart.h" #include "rt.h" diff --git a/src/swift.h b/src/swift.h index a4e2594dc36191cad2357ad30433cfc2c1533b7c..07aabf5d35d38ba0ae62a94b2191ae365381542c 100644 --- a/src/swift.h +++ b/src/swift.h @@ -60,7 +60,7 @@ #include "minmax.h" #include "mpiuse.h" #include "multipole.h" -#include "neutrino_properties.h" +#include "neutrino.h" #include "output_list.h" #include "output_options.h" #include "parallel_io.h"