Program Listing for File AgentRandom.cuh

Return to documentation for file (include/flamegpu/runtime/random/AgentRandom.cuh)

#ifndef INCLUDE_FLAMEGPU_RUNTIME_RANDOM_AGENTRANDOM_CUH_
#define INCLUDE_FLAMEGPU_RUNTIME_RANDOM_AGENTRANDOM_CUH_

#include <limits>

#include "flamegpu/detail/curand.cuh"
#include "flamegpu/detail/StaticAssert.h"
#include "flamegpu/exception/FLAMEGPUDeviceException.cuh"

namespace flamegpu {

class AgentRandom {
 public:
    __forceinline__ __device__ AgentRandom(detail::curandState *d_rng);
    template<typename T>
    __forceinline__ __device__ T uniform() const;
    template<typename T>
    __forceinline__ __device__ T normal() const;
    template<typename T>
    __forceinline__ __device__ T logNormal(T mean, T stddev) const;
    __forceinline__ __device__ unsigned int poisson(double mean = 1.0f) const;
    template<typename T>
    __forceinline__ __device__ T uniform(T min, T max) const;

 private:
    detail::curandState *d_random_state;
};

__forceinline__ __device__ AgentRandom::AgentRandom(detail::curandState *d_rng) : d_random_state(d_rng) { }
template<>
__forceinline__ __device__ float AgentRandom::uniform() const {
    // curand naturally generates the range (0, 1], we want [0, 1)
    // https://github.com/pytorch/pytorch/blob/059aa34b124916dfd761f3cbdb5fa97d7a01fc93/aten/src/ATen/native/cuda/Distributions.cu#L71-L77
    uint32_t val = curand(d_random_state);  // need just bits
    constexpr auto MASK = static_cast<uint32_t>((static_cast<uint64_t>(1) << std::numeric_limits<float>::digits) - 1);
    constexpr auto DIVISOR = static_cast<float>(1) / (static_cast<uint32_t>(1) << std::numeric_limits<float>::digits);
    return (val & MASK) * DIVISOR;
}
template<>
__forceinline__ __device__ double AgentRandom::uniform() const {
    // curand naturally generates the range (0, 1], we want [0, 1)
    // Conversion of High-Period Random Numbers to Floating Point - Jurgen A Doornik
    // Based on: https://www.doornik.com/research/randomdouble.pdf
    const uint32_t iRan1 = curand(d_random_state);
    const uint32_t iRan2 = curand(d_random_state);
    constexpr double M_RAN_INVM32 = 2.32830643653869628906e-010;
    constexpr double M_RAN_INVM52 = 2.22044604925031308085e-016;
    return (static_cast<int>(iRan1)*M_RAN_INVM32 + (0.5 + M_RAN_INVM52 / 2) + static_cast<int>((iRan2) & 0x000FFFFF) * M_RAN_INVM52);
}

template<>
__forceinline__ __device__ float AgentRandom::normal() const {
    return curand_normal(d_random_state);
}
template<>
__forceinline__ __device__ double AgentRandom::normal() const {
    return curand_normal_double(d_random_state);
}
template<>
__forceinline__ __device__ float AgentRandom::logNormal(const float mean, const float stddev) const {
    return curand_log_normal(d_random_state, mean, stddev);
}
template<>
__forceinline__ __device__ double AgentRandom::logNormal(const double mean, const double stddev) const {
    return curand_log_normal_double(d_random_state, mean, stddev);
}
__forceinline__ __device__ unsigned int AgentRandom::poisson(const double mean) const {
    return curand_poisson(d_random_state, mean);
}
template<typename T>
__forceinline__ __device__ T AgentRandom::uniform(T min, T max) const {
    static_assert(detail::StaticAssert::_Is_IntType<T>::value, "Invalid template argument for AgentRandom::uniform(T lowerBound, T max)");
#if !defined(FLAMEGPU_SEATBELTS) || FLAMEGPU_SEATBELTS
    if (min > max) {
        DTHROW("Invalid arguments passed to AgentRandom::uniform(), %lld > %lld\n", static_cast<int64_t>(min), static_cast<int64_t>(max));
    }
#endif
    return static_cast<T>(min + (1 + max - min) * uniform<float>());
}
template<>
__forceinline__ __device__ int64_t AgentRandom::uniform(const int64_t min, const int64_t max) const {
#if !defined(FLAMEGPU_SEATBELTS) || FLAMEGPU_SEATBELTS
    if (min > max) {
        DTHROW("Invalid arguments passed to AgentRandom::uniform(), %lld > %lld\n", static_cast<int64_t>(min), static_cast<int64_t>(max));
    }
#endif
    return static_cast<int64_t>(min + (1 + max - min) * uniform<double>());
}
template<>
__forceinline__ __device__ uint64_t AgentRandom::uniform(const uint64_t min, const uint64_t max) const {
#if !defined(FLAMEGPU_SEATBELTS) || FLAMEGPU_SEATBELTS
    if (min > max) {
        DTHROW("Invalid arguments passed to AgentRandom::uniform(), %lld > %lld\n", static_cast<int64_t>(min), static_cast<int64_t>(max));
    }
#endif
    return static_cast<uint64_t>(min + (1 + max - min) * uniform<double>());
}
template<>
__forceinline__ __device__ float AgentRandom::uniform(const float min, const float max) const {
#if !defined(FLAMEGPU_SEATBELTS) || FLAMEGPU_SEATBELTS
    if (min > max) {
        DTHROW("Invalid arguments passed to AgentRandom::uniform(), %f > %f\n", min, max);
    }
#endif
    return min + (max - min) * uniform<float>();
}
template<>
__forceinline__ __device__ double AgentRandom::uniform(const double min, const double max) const {
#if !defined(FLAMEGPU_SEATBELTS) || FLAMEGPU_SEATBELTS
    if (min > max) {
        DTHROW("Invalid arguments passed to AgentRandom::uniform(), %f > %f\n", min, max);
    }
#endif
    return min + (max - min) * uniform<double>();
}

}  // namespace flamegpu

#endif  // INCLUDE_FLAMEGPU_RUNTIME_RANDOM_AGENTRANDOM_CUH_