//! \file distribution.h //! Univariate probability distributions #ifndef OPENMC_DISTRIBUTION_H #define OPENMC_DISTRIBUTION_H #include // for size_t #include "pugixml.hpp" #include "openmc/constants.h" #include "openmc/memory.h" // for unique_ptr #include "openmc/span.h" #include "openmc/vector.h" // for vector namespace openmc { //============================================================================== // Helper function for computing importance weights from biased sampling //============================================================================== //! Compute importance weights for biased sampling //! \param p Unnormalized original probability vector //! \param b Unnormalized bias probability vector //! \return Vector of importance weights (p_norm[i] / b_norm[i]) vector compute_importance_weights( const vector& p, const vector& b); //============================================================================== //! Abstract class representing a univariate probability distribution //============================================================================== class Distribution { public: virtual ~Distribution() = default; //! Sample a value from the distribution, handling biasing automatically //! \param seed Pseudorandom number seed pointer //! \return (sampled value, importance weight) virtual std::pair sample(uint64_t* seed) const; //! Evaluate probability density, f(x), at a point //! \param x Point to evaluate f(x) //! \return f(x) virtual double evaluate(double x) const; //! Return integral of distribution //! \return Integral of distribution virtual double integral() const { return 1.0; }; //! Set bias distribution virtual void set_bias(std::unique_ptr bias) { bias_ = std::move(bias); } const Distribution* bias() const { return bias_.get(); } protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value virtual double sample_unbiased(uint64_t* seed) const = 0; //! Read bias distribution from XML //! \param node XML node that may contain a bias child element void read_bias_from_xml(pugi::xml_node node); // Biasing distribution unique_ptr bias_; }; using UPtrDist = unique_ptr; //! Return univariate probability distribution specified in XML file //! \param[in] node XML node representing distribution //! \return Unique pointer to distribution UPtrDist distribution_from_xml(pugi::xml_node node); //============================================================================== //! A discrete distribution index (probability mass function) //============================================================================== class DiscreteIndex { public: DiscreteIndex() {}; DiscreteIndex(pugi::xml_node node); DiscreteIndex(span p); void assign(span p); //! Sample a value from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled index size_t sample(uint64_t* seed) const; // Properties const vector& prob() const { return prob_; } const vector& alias() const { return alias_; } double integral() const { return integral_; } private: vector prob_; //!< Probability of accepting the uniformly sampled bin, //!< mapped to alias method table vector alias_; //!< Alias table double integral_; //!< Integral of distribution //! Normalize distribution so that probabilities sum to unity void normalize(); //! Initialize alias table for sampling void init_alias(); }; //============================================================================== //! A discrete distribution (probability mass function) //============================================================================== class Discrete : public Distribution { public: explicit Discrete(pugi::xml_node node); Discrete(const double* x, const double* p, size_t n); //! Sample a value from the distribution //! \param seed Pseudorandom number seed pointer //! \return (sampled value, sample weight) std::pair sample(uint64_t* seed) const override; double integral() const override { return di_.integral(); }; //! Override set_bias as no-op (bias handled in constructor) void set_bias(std::unique_ptr bias) override {} // Properties const vector& x() const { return x_; } const vector& prob() const { return di_.prob(); } const vector& alias() const { return di_.alias(); } const vector& weight() const { return weight_; } protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: vector x_; //!< Possible outcomes vector weight_; //!< Importance weights (empty if unbiased) DiscreteIndex di_; //!< Discrete probability distribution of outcome indices }; //============================================================================== //! Uniform distribution over the interval [a,b] //============================================================================== class Uniform : public Distribution { public: explicit Uniform(pugi::xml_node node); Uniform(double a, double b) : a_ {a}, b_ {b} {}; //! Evaluate probability density, f(x), at a point //! \param x Point to evaluate f(x) //! \return f(x) double evaluate(double x) const override; double a() const { return a_; } double b() const { return b_; } protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: double a_; //!< Lower bound of distribution double b_; //!< Upper bound of distribution }; //============================================================================== //! PowerLaw distribution over the interval [a,b] with exponent n : p(x)=c x^n //============================================================================== class PowerLaw : public Distribution { public: explicit PowerLaw(pugi::xml_node node); PowerLaw(double a, double b, double n) : offset_ {std::pow(a, n + 1)}, span_ {std::pow(b, n + 1) - offset_}, ninv_ {1 / (n + 1)} {}; //! Evaluate probability density, f(x), at a point //! \param x Point to evaluate f(x) //! \return f(x) double evaluate(double x) const override; double a() const { return std::pow(offset_, ninv_); } double b() const { return std::pow(offset_ + span_, ninv_); } double n() const { return 1 / ninv_ - 1; } protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: //! Store processed values in object to allow for faster sampling double offset_; //!< a^(n+1) double span_; //!< b^(n+1) - a^(n+1) double ninv_; //!< 1/(n+1) }; //============================================================================== //! Maxwellian distribution of form c*sqrt(E)*exp(-E/theta) //============================================================================== class Maxwell : public Distribution { public: explicit Maxwell(pugi::xml_node node); Maxwell(double theta) : theta_ {theta} {}; //! Evaluate probability density, f(x), at a point //! \param x Point to evaluate f(x) //! \return f(x) double evaluate(double x) const override; double theta() const { return theta_; } protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: double theta_; //!< Factor in exponential [eV] }; //============================================================================== //! Watt fission spectrum with form c*exp(-E/a)*sinh(sqrt(b*E)) //============================================================================== class Watt : public Distribution { public: explicit Watt(pugi::xml_node node); Watt(double a, double b) : a_ {a}, b_ {b} {}; //! Evaluate probability density, f(x), at a point //! \param x Point to evaluate f(x) //! \return f(x) double evaluate(double x) const override; double a() const { return a_; } double b() const { return b_; } protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: double a_; //!< Factor in exponential [eV] double b_; //!< Factor in square root [1/eV] }; //============================================================================== //! Normal distribution with optional truncation bounds. //! //! The standard normal PDF is 1/(sqrt(2*pi)*sigma) * //! exp(-(x-mu)^2/(2*sigma^2)). When truncated to [lower, upper], the PDF is //! renormalized so that it integrates to 1 over the truncation interval. //============================================================================== class Normal : public Distribution { public: explicit Normal(pugi::xml_node node); Normal(double mean_value, double std_dev, double lower = -INFTY, double upper = INFTY); //! Evaluate probability density, f(x), at a point //! \param x Point to evaluate f(x) //! \return f(x), accounting for truncation normalization double evaluate(double x) const override; double mean_value() const { return mean_value_; } double std_dev() const { return std_dev_; } double lower() const { return lower_; } double upper() const { return upper_; } bool is_truncated() const { return is_truncated_; } protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: double mean_value_; //!< Mean of distribution double std_dev_; //!< Standard deviation double lower_; //!< Lower truncation bound (default: -INFTY) double upper_; //!< Upper truncation bound (default: +INFTY) bool is_truncated_; //!< True if bounds are finite double norm_factor_; //!< Normalization factor for truncated distribution //! Compute normalization factor for truncated distribution void compute_normalization(); }; //============================================================================== //! Histogram or linear-linear interpolated tabular distribution //============================================================================== class Tabular : public Distribution { public: explicit Tabular(pugi::xml_node node); Tabular(const double* x, const double* p, int n, Interpolation interp, const double* c = nullptr); //! Evaluate probability density, f(x), at a point //! \param x Point to evaluate f(x) //! \return f(x) double evaluate(double x) const override; // properties vector& x() { return x_; } const vector& x() const { return x_; } const vector& p() const { return p_; } Interpolation interp() const { return interp_; } double integral() const override { return integral_; }; protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: vector x_; //!< tabulated independent variable vector p_; //!< tabulated probability density vector c_; //!< cumulative distribution at tabulated values Interpolation interp_; //!< interpolation rule double integral_; //!< Integral of distribution //! Initialize tabulated probability density function //! \param x Array of values for independent variable //! \param p Array of tabulated probabilities //! \param n Number of tabulated values void init( const double* x, const double* p, std::size_t n, const double* c = nullptr); }; //============================================================================== //! Equiprobable distribution //============================================================================== class Equiprobable : public Distribution { public: explicit Equiprobable(pugi::xml_node node); Equiprobable(const double* x, int n) : x_ {x, x + n} {}; //! Evaluate probability density, f(x), at a point //! \param x Point to evaluate f(x) //! \return f(x) double evaluate(double x) const override; const vector& x() const { return x_; } protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: vector x_; //! Possible outcomes }; //============================================================================== //! Mixture distribution //============================================================================== class Mixture : public Distribution { public: explicit Mixture(pugi::xml_node node); //! Sample a value from the distribution //! \param seed Pseudorandom number seed pointer //! \return (sampled value, sample weight) std::pair sample(uint64_t* seed) const override; double integral() const override { return integral_; } //! Override set_bias as no-op (bias handled in constructor) void set_bias(std::unique_ptr bias) override {} protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: vector distribution_; //!< Sub-distributions vector weight_; //!< Importance weights for component selection DiscreteIndex di_; //!< Discrete probability distribution of indices double integral_; //!< Integral of distribution }; //============================================================================== // DecaySpectrum — non-owning mixture of decay photon distributions //============================================================================== //! Energy distribution formed by mixing multiple decay photon spectra. //! //! Unlike the general Mixture distribution, this class holds non-owning //! pointers to the component distributions (which live in //! data::chain_nuclides). Each component is weighted by the activity //! (atoms * decay_constant) of the corresponding nuclide. class DecaySpectrum : public Distribution { public: //============================================================================ // Types, aliases struct Sample { double energy; double weight; int parent_nuclide; }; //============================================================================ // Constructors //! Construct from an XML node containing nuclide names and atom densities. //! //! Reads child ```` elements with ``name`` and ``density`` //! attributes, resolves them against the loaded depletion chain, and //! constructs the mixed distribution. explicit DecaySpectrum(pugi::xml_node node); //============================================================================ // Methods //! Sample a value from the distribution and return the parent nuclide index //! \param seed Pseudorandom number seed pointer //! \return (Sampled energy, sample weight, chain nuclide index) Sample sample_with_parent(uint64_t* seed) const; //! Sample a value from the distribution //! \param seed Pseudorandom number seed pointer //! \return (sampled value, sample weight) std::pair sample(uint64_t* seed) const override; double integral() const override; protected: //! Sample a value (unbiased) from the distribution //! \param seed Pseudorandom number seed pointer //! \return Sampled value double sample_unbiased(uint64_t* seed) const override; private: //! Initialize decay spectrum sampling data //! \param nuclide_indices Indices of decay photon emitters in //! data::chain_nuclides //! \param atoms Number of atoms for each component. void init(vector nuclide_indices, const vector& atoms); vector nuclide_indices_; //!< Indices of emitting nuclides in the chain DiscreteIndex di_; //!< Discrete index for component selection double integral_; //!< Total photon emission rate }; } // namespace openmc #endif // OPENMC_DISTRIBUTION_H