/******************************************************************************* * This file is part of SWIFT. * Copyright (c) 2018 Matthieu Schaller (schaller@strw.leidenuniv.nl) * 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_RANDOM_H #define SWIFT_RANDOM_H /* Code configuration */ #include /* Standard header */ #include #include #include #include /* Local headers */ #include "sincos.h" /** * @brief The categories of random number generated. * * The values of the fields are carefully chose numbers * the numbers are very large primes such that the IDs * will not have a prime factorization with this coefficient * this results in a very high period for the random number * generator. * Only change when you know what you are doing, changing * the numbers to bad values will break the random number * generator. */ enum random_number_type { random_number_star_formation = 0LL, random_number_sink_formation = 5947309451LL, random_number_stellar_feedback_1 = 3947008991LL, random_number_stellar_feedback_2 = 6977309513LL, random_number_stellar_feedback_3 = 9762399103LL, random_number_isotropic_SNII_feedback_ray_theta = 3298327511LL, random_number_isotropic_SNII_feedback_ray_phi = 6311114273LL, random_number_isotropic_SNIa_feedback_ray_theta = 11134675471LL, random_number_isotropic_SNIa_feedback_ray_phi = 5165786851LL, random_number_isotropic_AGN_feedback_ray_theta = 8899891613LL, random_number_isotropic_AGN_feedback_ray_phi = 10594523341LL, random_number_stellar_enrichment = 2936881973LL, random_number_BH_feedback = 1640531371LL, random_number_BH_swallow = 4947009007LL, random_number_BH_reposition = 59969537LL, random_number_BH_spin = 193877777LL, random_number_BH_kick = 303595777LL, random_number_sink_swallow = 7337737777LL, random_number_snapshot_sampling = 6561001LL, random_number_stellar_winds = 5947309451LL, random_number_HII_regions = 8134165677LL, random_number_enrichment_1 = 7245742351LL, random_number_enrichment_2 = 1156895281LL, random_number_enrichment_3 = 2189989727LL, random_number_mosaic_powerlaw = 406586897LL, random_number_mosaic_schechter = 562448657LL, random_number_mosaic_poisson = 384160001LL, random_number_powerspectrum_split = 126247697LL, }; #ifndef __APPLE__ #include #include #include /* Inline the default RNG functions to avoid costly function calls. These functions are minor modifications, but functional equivalents, of their glibc counterparts. */ INLINE static int inl_rand_r(uint32_t *seed) { uint32_t next = *seed; int result; next *= 1103515245; next += 12345; result = (uint32_t)(next / 65536) % 2048; next *= 1103515245; next += 12345; result <<= 10; result ^= (uint32_t)(next / 65536) % 1024; next *= 1103515245; next += 12345; result <<= 10; result ^= (uint32_t)(next / 65536) % 1024; *seed = next; return result; } INLINE static void inl_drand48_iterate(uint16_t xsubi[3]) { uint64_t X; uint64_t result; const uint64_t __a = 0x5deece66dull; const uint16_t __c = 0xb; /* Do the real work. We choose a data type which contains at least 48 bits. Because we compute the modulus it does not care how many bits really are computed. */ X = (uint64_t)xsubi[2] << 32 | (uint32_t)xsubi[1] << 16 | xsubi[0]; result = X * __a + __c; xsubi[0] = result & 0xffff; xsubi[1] = (result >> 16) & 0xffff; xsubi[2] = (result >> 32) & 0xffff; } INLINE static double inl_erand48(uint16_t xsubi[3]) { union ieee754_double temp; /* Compute next state. */ inl_drand48_iterate(xsubi); /* Construct a positive double with the 48 random bits distributed over its fractional part so the resulting FP number is [0.0,1.0). */ temp.ieee.negative = 0; temp.ieee.exponent = IEEE754_DOUBLE_BIAS; temp.ieee.mantissa0 = (xsubi[2] << 4) | (xsubi[1] >> 12); temp.ieee.mantissa1 = (((uint32_t)xsubi[1] & 0xfff) << 20) | (xsubi[0] << 4); /* Please note the lower 4 bits of mantissa1 are always 0. */ return temp.d - 1.0; } #else /* In the case of OSX, we default to the platform's default implementation. */ INLINE static int inl_rand_r(uint32_t *seed) { return rand_r(seed); } INLINE static double inl_erand48(uint16_t xsubi[3]) { return erand48(xsubi); } #endif /** * @brief Returns a pseudo-random number in the range [0, 1[. * * We generate numbers that are always reproducible for a given particle ID and * simulation time (on the integer time-line). If more than one number per * time-step per particle is needed, additional randomness can be obtained by * using the type argument. * * @param id The ID of the particle for which to generate a number. * @param ti_current The time (on the time-line) for which to generate a number. * @param type The #random_number_type to generate. * @return a random number in the interval [0, 1.[. */ INLINE static double random_unit_interval(int64_t id, const integertime_t ti_current, const enum random_number_type type) { /* Start by packing the state into a sequence of 16-bit seeds for rand_r. */ uint16_t buff[9]; id += type; memcpy(&buff[0], &id, 8); memcpy(&buff[4], &ti_current, 8); /* The inputs give 16 bytes of state, but we need a multiple of 6 for the calls to erand48(), so we add an additional aribrary constant two-byte value to get 18 bytes of state. */ buff[8] = 6178; /* Use the random seed to generate a new random number */ buff[0] = buff[0] ^ (uint16_t)SWIFT_RANDOM_SEED_XOR; /* Shuffle the buffer values, this will be our source of entropy for the erand48 generator. */ uint32_t seed16 = 0; for (int k = 0; k < 9; k++) { seed16 ^= buff[k]; inl_rand_r(&seed16); } for (int k = 0; k < 9; k++) buff[k] ^= inl_rand_r(&seed16) & 0xffff; /* Do three steps of erand48() over the state generated previously. */ uint16_t seed48[3] = {0, 0, 0}; for (int k = 0; k < 3; k++) { for (int j = 0; j < 3; j++) seed48[j] ^= buff[3 * k + j]; inl_erand48(seed48); } /* Generate one final value, this is our output. */ return inl_erand48(seed48); } /** * @brief Returns a pseudo-random number in the range [0, 1[. * * We generate numbers that are always reproducible for a given pair of particle * IDs and simulation time (on the integer time-line). If more than one number * per time-step per particle is needed, additional randomness can be obtained * by using the type argument. * * @param id_star The ID of the first particle for which to generate a number. * @param id_gas The ID of the second particle for which to generate a number. * @param ti_current The time (on the time-line) for which to generate a number. * @param type The #random_number_type to generate. * @return a random number in the interval [0, 1.[. */ INLINE static double random_unit_interval_two_IDs( const int64_t id_star, const int64_t id_gas, const integertime_t ti_current, const enum random_number_type type) { /* We need to combine the gas and star IDs such that we do not get correlation * for same id_star + id_gas pairs, because of this we combine everything * nonlinearly */ int64_t input_id = (id_star * id_gas + id_star * ti_current + id_gas * ti_current * ti_current) % INT64_MAX; return random_unit_interval(input_id, ti_current, type); } /** * @brief Returns a pseudo-random number in the range [0, 1[. * * We generate numbers that are always reproducible for a given particle * ID, integer index and simulation time (on the integer time-line). If more * than one number per time-step per particle is needed, additional randomness * can be obtained by using the type argument. * * @param id the particle ID. * @param index A integer added to the randomness. * @param ti_current The current integer time. * @param type The type of random number (aka. #random_number_type seed). */ INLINE static double random_unit_interval_part_ID_and_index( const int64_t id, const int index, const integertime_t ti_current, const enum random_number_type type) { /* For better mixing, we apply a non-linear transformation y=1+x^3 */ const long long index_3 = index * index * index; const long long index_3_one = index_3 + 1LL; return random_unit_interval_two_IDs(id, index_3_one, ti_current, type); } /** * @brief Return a random integer following a Poisson distribution. * * Uses the Knuth-Junhao method to avoid underflow when lambda is large. * * @param id The ID of the particle for which to generate a number. * @param lambda The parameter of the Poisson distribution. * @param ti_current The time (on the time-line) for which to generate a number. * @param type The #random_number_type to generate. */ INLINE static int random_poisson(const int64_t id, const double lambda, const integertime_t ti_current, const enum random_number_type type) { const double step = 500.; const double exp_step = exp(step); double lambda_left = lambda; int k = 0; double p = 1.; do { k++; const double r = random_unit_interval_part_ID_and_index(id, k, ti_current, type); p *= r; while (p < 1. && lambda_left > 0.) { if (lambda_left > step) { p *= exp_step; lambda_left -= step; } else { p *= exp(lambda_left); lambda_left = 0.; } }; } while (p > 1.); return k - 1; } /** * @brief Generates a unit vector within a cone. * * We draw a random unit vector around the unit vector a = (a0, a1, a2), * such that it is uniformly distributed in solid angle from 0 opening * angle (perfectly aligned with the vector a) to a maximum of * opening_angle radians. * * @param id_bh The ID of the (BH) particle which is doing the jet feedback. * @param ti_current The time (on the time-line) for which to generate the * random kick direction. * @param type The #random_number_type to generate. * @param opening_angle Opening angle of the cone. * @param a Reference direction that defines the cone. * @param rand_cone_direction Return value. */ INLINE static void random_direction_in_cone(const int64_t id_bh, const integertime_t ti_current, const enum random_number_type type, const float opening_angle, const float a[3], float rand_cone_direction[3]) { /* We want to draw a random unit vector from a cone around the unit * vector a = (a0, a1, a2). We do this in a frame x'y'z', where the z' * axis is aligned with the a vector, i.e. the a vector is one of its * basis vectors. The choice of the other two axes, x' and y', is * arbitrary. We can use any two unit vectors that are orthogonal to the * a vector. These two vectors can be obtained using cross products. * However, we need to first start from an initial vector that is not * perfectly aligned with a. The choice of this vector is also arbitrary; * we choose a vector that is perfectly aligned with the smallest * component of the spin vector a. */ float init_unit[3] = {0.f, 0.f, 1.f}; /* Find which of the x, y or z is the smallest components of a. We also * need to take into account the possibility that two of the components * are equal. In this case it doesn't matter which of the two we * choose. */ const float a0_abs = fabsf(a[0]); const float a1_abs = fabsf(a[1]); const float a2_abs = fabsf(a[2]); if (((a0_abs < a1_abs) && (a0_abs < a2_abs)) || ((a0_abs == a1_abs) && (a0_abs < a2_abs))) { init_unit[0] = 1.f; } else if (((a1_abs < a2_abs) && (a1_abs < a0_abs)) || ((a1_abs == a2_abs) && (a1_abs < a0_abs))) { init_unit[1] = 1.f; } else if (((a2_abs < a0_abs) && (a2_abs < a1_abs)) || ((a2_abs == a0_abs) && (a2_abs < a1_abs))) { init_unit[2] = 1.f; } /* Using this vector and the a vector, we can find the first basis * vector x (alongside a) that will also be orthogonal to a, by using a * cross product, such that x = init_unit cross a. This vector needs to be * normalized to get an actual unit vector. */ float basis_vec_x[3]; basis_vec_x[0] = init_unit[1] * a[2] - init_unit[2] * a[1]; basis_vec_x[1] = init_unit[2] * a[0] - init_unit[0] * a[2]; basis_vec_x[2] = init_unit[0] * a[1] - init_unit[1] * a[0]; const float basis_vec_x_magn = sqrtf(basis_vec_x[0] * basis_vec_x[0] + basis_vec_x[1] * basis_vec_x[1] + basis_vec_x[2] * basis_vec_x[2]); basis_vec_x[0] = basis_vec_x[0] / basis_vec_x_magn; basis_vec_x[1] = basis_vec_x[1] / basis_vec_x_magn; basis_vec_x[2] = basis_vec_x[2] / basis_vec_x_magn; /* The other basis vector, y, follows as x cross a. It is already * normalized since x and a are orthogonal. */ const float basis_vec_y[3] = {basis_vec_x[1] * a[2] - basis_vec_x[2] * a[1], basis_vec_x[2] * a[0] - basis_vec_x[0] * a[2], basis_vec_x[0] * a[1] - basis_vec_x[1] * a[0]}; /* Draw a random cosine confined to the range [cos(opening_angle), 1] */ const float rand_cos_theta = 1.f - (1.f - cosf(opening_angle)) * random_unit_interval(id_bh, ti_current, type); /* Get the corresponding sine */ const float rand_sin_theta = sqrtf(max(0.f, (1.f - rand_cos_theta) * (1.f + rand_cos_theta))); /* Get a random equitorial angle from [0, 180] deg */ const float rand_phi = ((float)(2. * M_PI)) * random_unit_interval(id_bh * id_bh, ti_current, type); float rand_cos_phi, rand_sin_phi; sincosf(rand_phi, &rand_sin_phi, &rand_cos_phi); /* We now calculate the direction of the final vector by picking a random * direction within a cone around a, with basis_vec_x and basis_vec_y * playing the role of x and y unit vectors, and a playing the role of z * unit vector. In other words, in vector notation rand_cone_direction = * sin(theta)cos(phi) * basis_vec_x + sin(theta)sin(phi) * basis_vec_y * + cos(theta) * a . */ rand_cone_direction[0] = rand_sin_theta * rand_cos_phi * basis_vec_x[0] + rand_sin_theta * rand_sin_phi * basis_vec_y[0] + rand_cos_theta * a[0], rand_cone_direction[1] = rand_sin_theta * rand_cos_phi * basis_vec_x[1] + rand_sin_theta * rand_sin_phi * basis_vec_y[1] + rand_cos_theta * a[1], rand_cone_direction[2] = rand_sin_theta * rand_cos_phi * basis_vec_x[2] + rand_sin_theta * rand_sin_phi * basis_vec_y[2] + rand_cos_theta * a[2]; } #endif /* SWIFT_RANDOM_H */