diff --git a/include/openmc/angle_energy.h b/include/openmc/angle_energy.h index ac931b1b533..35e5b68b64b 100644 --- a/include/openmc/angle_energy.h +++ b/include/openmc/angle_energy.h @@ -16,6 +16,8 @@ class AngleEnergy { public: virtual void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const = 0; + virtual double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const = 0; virtual ~AngleEnergy() = default; }; diff --git a/include/openmc/chain.h b/include/openmc/chain.h index a3bc6f3a364..c2a45f75adc 100644 --- a/include/openmc/chain.h +++ b/include/openmc/chain.h @@ -70,6 +70,8 @@ class DecayPhotonAngleEnergy : public AngleEnergy { //! \param[inout] seed Pseudorandom seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: const Distribution* photon_energy_; diff --git a/include/openmc/distribution_angle.h b/include/openmc/distribution_angle.h index efd4e58425b..a7e021085f0 100644 --- a/include/openmc/distribution_angle.h +++ b/include/openmc/distribution_angle.h @@ -25,6 +25,7 @@ class AngleDistribution { //! \param[inout] seed pseudorandom number seed pointer //! \return Cosine of the angle in the range [-1,1] double sample(double E, uint64_t* seed) const; + double evaluate(double E, double mu) const; //! Determine whether angle distribution is empty //! \return Whether distribution is empty diff --git a/include/openmc/distribution_multi.h b/include/openmc/distribution_multi.h index 7b9c2abf8ce..d53e5ed560e 100644 --- a/include/openmc/distribution_multi.h +++ b/include/openmc/distribution_multi.h @@ -6,6 +6,7 @@ #include "pugixml.hpp" #include "openmc/distribution.h" +#include "openmc/error.h" #include "openmc/position.h" namespace openmc { @@ -29,6 +30,11 @@ class UnitSphereDistribution { //! \return (sampled Direction, sample weight) virtual std::pair sample(uint64_t* seed) const = 0; + virtual double evaluate(Direction u) const + { + fatal_error("evaluate not available for this UnitSphereDistribution type"); + } + Direction u_ref_ {0.0, 0.0, 1.0}; //!< reference direction }; @@ -52,6 +58,8 @@ class PolarAzimuthal : public UnitSphereDistribution { //! \return (sampled Direction, value of the PDF at this Direction) std::pair sample_as_bias(uint64_t* seed) const; + double evaluate(Direction u) const override; + // Observing pointers Distribution* mu() const { return mu_.get(); } Distribution* phi() const { return phi_.get(); } @@ -87,6 +95,8 @@ class Isotropic : public UnitSphereDistribution { //! \return (sampled direction, sample weight) std::pair sample(uint64_t* seed) const override; + double evaluate(Direction u) const override; + // Set or get bias distribution void set_bias(std::unique_ptr bias) { diff --git a/include/openmc/reaction_product.h b/include/openmc/reaction_product.h index 4fbbc1b626a..98e5910db9b 100644 --- a/include/openmc/reaction_product.h +++ b/include/openmc/reaction_product.h @@ -49,6 +49,11 @@ class ReactionProduct { //! \param[inout] seed Pseudorandom seed pointer void sample(double E_in, double& E_out, double& mu, uint64_t* seed) const; + AngleEnergy& sample_dist(double E_in, uint64_t* seed) const; + + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const; + ParticleType particle_; //!< Particle type EmissionMode emission_mode_; //!< Emission mode double decay_rate_; //!< Decay rate (for delayed neutron precursors) in [1/s] diff --git a/include/openmc/secondary_correlated.h b/include/openmc/secondary_correlated.h index 6905c38e369..e7c933bb614 100644 --- a/include/openmc/secondary_correlated.h +++ b/include/openmc/secondary_correlated.h @@ -40,6 +40,9 @@ class CorrelatedAngleEnergy : public AngleEnergy { //! \param[inout] seed Pseudorandom seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + Distribution& sample_dist(double E_in, double& E_out, uint64_t* seed) const; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; // energy property vector& energy() { return energy_; } diff --git a/include/openmc/secondary_kalbach.h b/include/openmc/secondary_kalbach.h index 83806d35248..0168ee13ced 100644 --- a/include/openmc/secondary_kalbach.h +++ b/include/openmc/secondary_kalbach.h @@ -31,6 +31,10 @@ class KalbachMann : public AngleEnergy { //! \param[inout] seed Pseudorandom seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + void sample_params(double E_in, double& E_out, double& km_a, double& km_r, + uint64_t* seed) const; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: //! Outgoing energy/angle at a single incoming energy diff --git a/include/openmc/secondary_nbody.h b/include/openmc/secondary_nbody.h index efb4fd75ba1..dad896cd740 100644 --- a/include/openmc/secondary_nbody.h +++ b/include/openmc/secondary_nbody.h @@ -27,6 +27,9 @@ class NBodyPhaseSpace : public AngleEnergy { //! \param[inout] seed Pseudorandom seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + double sample_energy(double E_in, uint64_t* seed) const; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: int n_bodies_; //!< Number of particles distributed diff --git a/include/openmc/secondary_thermal.h b/include/openmc/secondary_thermal.h index 5b18902afbb..b7de6ab4e61 100644 --- a/include/openmc/secondary_thermal.h +++ b/include/openmc/secondary_thermal.h @@ -6,6 +6,7 @@ #include "openmc/angle_energy.h" #include "openmc/endf.h" +#include "openmc/search.h" #include "openmc/secondary_correlated.h" #include "openmc/vector.h" @@ -32,6 +33,8 @@ class CoherentElasticAE : public AngleEnergy { //! \param[inout] seed Pseudorandom seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: const CoherentElasticXS& xs_; //!< Coherent elastic scattering cross section @@ -55,6 +58,8 @@ class IncoherentElasticAE : public AngleEnergy { //! \param[inout] seed Pseudorandom number seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: double debye_waller_; @@ -80,6 +85,8 @@ class IncoherentElasticAEDiscrete : public AngleEnergy { //! \param[inout] seed Pseudorandom number seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: const vector& energy_; //!< Energies at which cosines are tabulated @@ -106,6 +113,9 @@ class IncoherentInelasticAEDiscrete : public AngleEnergy { //! \param[inout] seed Pseudorandom number seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + void sample_params(double E_in, double& E_out, int& j, uint64_t* seed) const; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: const vector& energy_; //!< Incident energies @@ -134,6 +144,10 @@ class IncoherentInelasticAE : public AngleEnergy { //! \param[inout] seed Pseudorandom number seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + void sample_params(double E_in, double& E_out, double& f, int& l, int& j, + uint64_t* seed) const; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: //! Secondary energy/angle distribution @@ -169,6 +183,9 @@ class MixedElasticAE : public AngleEnergy { //! \param[inout] seed Pseudorandom number seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + const AngleEnergy& sample_dist(double E_in, uint64_t* seed) const; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; private: CoherentElasticAE coherent_dist_; //!< Coherent distribution @@ -178,6 +195,52 @@ class MixedElasticAE : public AngleEnergy { const Function1D& incoherent_xs_; //!< Polymorphic ref. to incoherent XS }; +struct DoubleVector { + double data; + const double& operator[](size_t index) const { return data; } +}; + +template +double get_pdf_discrete(const vector& mu, const T& w, double mu_0) +{ + // Make sure mu is in range [-1,1] + if (std::abs(mu_0) > 1.0) + mu_0 = std::copysign(1.0, mu_0); + double a0; + double a1; + double b0; + double b1; + int32_t ai = -1; + int32_t bi = -1; + if (mu_0 > mu[0]) { + ai = lower_bound_index(mu.begin(), mu.end(), mu_0); + a0 = mu[ai]; + a1 = (ai > 1) ? mu[ai - 1] : -1.0; + } else { + a0 = -1.0; + a1 = -1.0; + } + if (mu_0 < mu[mu.size() - 1]) { + bi = upper_bound_index(mu.begin(), mu.end(), mu_0); + b0 = mu[bi]; + b1 = (bi < mu.size() - 1) ? mu[bi + 1] : 1.0; + } else { + b0 = 1.0; + b1 = 1.0; + } + + // Calculate Delta_a and Delta_b + double delta_a = 0.5 * std::min(b0 - a0, a0 - a1); + double delta_b = 0.5 * std::min(b1 - b0, b0 - a0); + + if (mu_0 < a0 + delta_a) + return w[ai] / (2.0 * delta_a); + else if (mu_0 + delta_b < b0) + return w[bi] / (2.0 * delta_b); + else + return 0.0; +} + } // namespace openmc #endif // OPENMC_SECONDARY_THERMAL_H diff --git a/include/openmc/secondary_uncorrelated.h b/include/openmc/secondary_uncorrelated.h index 3afa3d9ceb7..d971231b277 100644 --- a/include/openmc/secondary_uncorrelated.h +++ b/include/openmc/secondary_uncorrelated.h @@ -31,6 +31,8 @@ class UncorrelatedAngleEnergy : public AngleEnergy { //! \param[inout] seed Pseudorandom seed pointer void sample( double E_in, double& E_out, double& mu, uint64_t* seed) const override; + double sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const override; // Accessors AngleDistribution& angle() { return angle_; } diff --git a/include/openmc/thermal.h b/include/openmc/thermal.h index de0767d0af0..73c343ad6a7 100644 --- a/include/openmc/thermal.h +++ b/include/openmc/thermal.h @@ -51,7 +51,11 @@ class ThermalData { //! \param[out] mu Outgoing scattering angle cosine //! \param[inout] seed Pseudorandom seed pointer void sample(const NuclideMicroXS& micro_xs, double E_in, double* E_out, - double* mu, uint64_t* seed); + double* mu, uint64_t* seed) const; + AngleEnergy& sample_dist( + const NuclideMicroXS& micro_xs, double E, uint64_t* seed) const; + double sample_energy_and_pdf(const NuclideMicroXS& micro_xs, double E_in, + double mu, double& E_out, uint64_t* seed) const; private: struct Reaction { diff --git a/src/chain.cpp b/src/chain.cpp index a60ef12cd6c..85d9c119791 100644 --- a/src/chain.cpp +++ b/src/chain.cpp @@ -74,6 +74,13 @@ void DecayPhotonAngleEnergy::sample( mu = Uniform(-1., 1.).sample(seed).first; } +double DecayPhotonAngleEnergy::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + E_out = photon_energy_->sample(seed).first; + return 0.5; +} + //============================================================================== // Global variables //============================================================================== diff --git a/src/distribution_angle.cpp b/src/distribution_angle.cpp index 50f1aca112b..66accac25ae 100644 --- a/src/distribution_angle.cpp +++ b/src/distribution_angle.cpp @@ -83,4 +83,19 @@ double AngleDistribution::sample(double E, uint64_t* seed) const return mu; } +double AngleDistribution::evaluate(double E, double mu) const +{ + // Find energy bin and calculate interpolation factor + int i; + double r; + get_energy_index(energy_, E, i, r); + + double pdf = 0.0; + if (r > 0.0) + pdf += r * distribution_[i + 1]->evaluate(mu); + if (r < 1.0) + pdf += (1.0 - r) * distribution_[i]->evaluate(mu); + return pdf; +} + } // namespace openmc diff --git a/src/distribution_multi.cpp b/src/distribution_multi.cpp index 857e1c30b40..8ceb712ac08 100644 --- a/src/distribution_multi.cpp +++ b/src/distribution_multi.cpp @@ -44,6 +44,7 @@ UnitSphereDistribution::UnitSphereDistribution(pugi::xml_node node) fatal_error("Angular distribution reference direction must have " "three parameters specified."); u_ref_ = Direction(u_ref.data()); + u_ref_ /= u_ref_.norm(); } } @@ -65,6 +66,7 @@ PolarAzimuthal::PolarAzimuthal(pugi::xml_node node) fatal_error("Angular distribution reference v direction must have " "three parameters specified."); v_ref_ = Direction(v_ref.data()); + v_ref_ /= v_ref_.norm(); } w_ref_ = u_ref_.cross(v_ref_); if (check_for_node(node, "mu")) { @@ -116,6 +118,13 @@ std::pair PolarAzimuthal::sample_impl( weight}; } +double PolarAzimuthal::evaluate(Direction u) const +{ + double mu = u.dot(u_ref_); + double phi = std::acos(u.dot(v_ref_) / std::sqrt(1 - mu * mu)); + return mu_->evaluate(mu) * phi_->evaluate(phi); +} + //============================================================================== // Isotropic implementation //============================================================================== @@ -157,6 +166,11 @@ std::pair Isotropic::sample(uint64_t* seed) const } } +double Isotropic::evaluate(Direction u) const +{ + return 1.0 / (4.0 * PI); +} + //============================================================================== // Monodirectional implementation //============================================================================== diff --git a/src/reaction_product.cpp b/src/reaction_product.cpp index 3ba2c0cfb05..6cfd164a01d 100644 --- a/src/reaction_product.cpp +++ b/src/reaction_product.cpp @@ -106,8 +106,7 @@ ReactionProduct::ReactionProduct(const ChainNuclide::Product& product) make_unique(chain_nuc->photon_energy())); } -void ReactionProduct::sample( - double E_in, double& E_out, double& mu, uint64_t* seed) const +AngleEnergy& ReactionProduct::sample_dist(double E_in, uint64_t* seed) const { auto n = applicability_.size(); if (n > 1) { @@ -118,15 +117,25 @@ void ReactionProduct::sample( prob += applicability_[i](E_in); // If i-th distribution is sampled, sample energy from the distribution - if (c <= prob) { - distribution_[i]->sample(E_in, E_out, mu, seed); - break; - } + if (c <= prob) + return *distribution_[i]; } } else { // If only one distribution is present, go ahead and sample it - distribution_[0]->sample(E_in, E_out, mu, seed); + return *distribution_[0]; } } +void ReactionProduct::sample( + double E_in, double& E_out, double& mu, uint64_t* seed) const +{ + sample_dist(E_in, seed).sample(E_in, E_out, mu, seed); +} + +double ReactionProduct::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + return sample_dist(E_in, seed).sample_energy_and_pdf(E_in, mu, E_out, seed); +} + } // namespace openmc diff --git a/src/secondary_correlated.cpp b/src/secondary_correlated.cpp index e820419b484..dfde3f1e03b 100644 --- a/src/secondary_correlated.cpp +++ b/src/secondary_correlated.cpp @@ -153,9 +153,8 @@ CorrelatedAngleEnergy::CorrelatedAngleEnergy(hid_t group) distribution_.push_back(std::move(d)); } // incoming energies } - -void CorrelatedAngleEnergy::sample( - double E_in, double& E_out, double& mu, uint64_t* seed) const +Distribution& CorrelatedAngleEnergy::sample_dist( + double E_in, double& E_out, uint64_t* seed) const { // Find energy bin and calculate interpolation factor int i; @@ -247,10 +246,22 @@ void CorrelatedAngleEnergy::sample( // Find correlated angular distribution for closest outgoing energy bin if (r1 - c_k < c_k1 - r1 || distribution_[l].interpolation == Interpolation::histogram) { - mu = distribution_[l].angle[k]->sample(seed).first; + return *distribution_[l].angle[k]; } else { - mu = distribution_[l].angle[k + 1]->sample(seed).first; + return *distribution_[l].angle[k + 1]; } } +void CorrelatedAngleEnergy::sample( + double E_in, double& E_out, double& mu, uint64_t* seed) const +{ + mu = sample_dist(E_in, E_out, seed).sample(seed).first; +} + +double CorrelatedAngleEnergy::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + return sample_dist(E_in, E_out, seed).evaluate(mu); +} + } // namespace openmc diff --git a/src/secondary_kalbach.cpp b/src/secondary_kalbach.cpp index 6ac91e665b9..43a40c0cd4e 100644 --- a/src/secondary_kalbach.cpp +++ b/src/secondary_kalbach.cpp @@ -115,8 +115,8 @@ KalbachMann::KalbachMann(hid_t group) } // incoming energies } -void KalbachMann::sample( - double E_in, double& E_out, double& mu, uint64_t* seed) const +void KalbachMann::sample_params( + double E_in, double& E_out, double& km_a, double& km_r, uint64_t* seed) const { // Find energy bin and calculate interpolation factor int i; @@ -171,7 +171,6 @@ void KalbachMann::sample( double E_l_k = distribution_[l].e_out[k]; double p_l_k = distribution_[l].p[k]; - double km_r, km_a; if (distribution_[l].interpolation == Interpolation::histogram) { // Histogram interpolation if (p_l_k > 0.0 && k >= n_discrete) { @@ -217,6 +216,13 @@ void KalbachMann::sample( E_out = E_1 + (E_out - E_i1_1) * (E_K - E_1) / (E_i1_K - E_i1_1); } } +} + +void KalbachMann::sample( + double E_in, double& E_out, double& mu, uint64_t* seed) const +{ + double km_r, km_a; + sample_params(E_in, E_out, km_a, km_r, seed); // Sampled correlated angle from Kalbach-Mann parameters if (prn(seed) > km_r) { @@ -227,5 +233,15 @@ void KalbachMann::sample( mu = std::log(r1 * std::exp(km_a) + (1.0 - r1) * std::exp(-km_a)) / km_a; } } +double KalbachMann::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + double km_r, km_a; + sample_params(E_in, E_out, km_a, km_r, seed); + + // https://docs.openmc.org/en/latest/methods/neutron_physics.html#equation-KM-pdf-angle + return km_a / (2 * std::sinh(km_a)) * + (std::cosh(km_a * mu) + km_r * std::sinh(km_a * mu)); +} } // namespace openmc diff --git a/src/secondary_nbody.cpp b/src/secondary_nbody.cpp index da0bb81c471..72f0b0b92d0 100644 --- a/src/secondary_nbody.cpp +++ b/src/secondary_nbody.cpp @@ -22,13 +22,8 @@ NBodyPhaseSpace::NBodyPhaseSpace(hid_t group) read_attribute(group, "q_value", Q_); } -void NBodyPhaseSpace::sample( - double E_in, double& E_out, double& mu, uint64_t* seed) const +double NBodyPhaseSpace::sample_energy(double E_in, uint64_t* seed) const { - // By definition, the distribution of the angle is isotropic for an N-body - // phase space distribution - mu = uniform_distribution(-1., 1., seed); - // Determine E_max parameter double Ap = mass_ratio_; double E_max = (Ap - 1.0) / Ap * (A_ / (A_ + 1.0) * E_in + Q_); @@ -59,12 +54,29 @@ void NBodyPhaseSpace::sample( std::log(r5) * std::pow(std::cos(PI / 2.0 * r6), 2); break; default: - throw std::runtime_error {"N-body phase space with >5 bodies."}; + fatal_error("N-body phase space with >5 bodies."); } // Now determine v and E_out double v = x / (x + y); - E_out = E_max * v; + return E_max * v; +} + +void NBodyPhaseSpace::sample( + double E_in, double& E_out, double& mu, uint64_t* seed) const +{ + // By definition, the distribution of the angle is isotropic for an N-body + // phase space distribution + mu = uniform_distribution(-1., 1., seed); + + E_out = sample_energy(E_in, seed); +} + +double NBodyPhaseSpace::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + E_out = sample_energy(E_in, seed); + return 0.5; } } // namespace openmc diff --git a/src/secondary_thermal.cpp b/src/secondary_thermal.cpp index 030d398aabe..240bbf8cf36 100644 --- a/src/secondary_thermal.cpp +++ b/src/secondary_thermal.cpp @@ -4,6 +4,7 @@ #include "openmc/math_functions.h" #include "openmc/random_lcg.h" #include "openmc/search.h" +#include "openmc/vector.h" #include "xtensor/xview.hpp" @@ -12,6 +13,12 @@ namespace openmc { +double get_pdf_discrete(const vector& mu, double mu_0) +{ + DoubleVector w {1.0 / mu.size()}; + return get_pdf_discrete(mu, w, mu_0); +} + //============================================================================== // CoherentElasticAE implementation //============================================================================== @@ -42,6 +49,39 @@ void CoherentElasticAE::sample( mu = 1.0 - 2.0 * energies[k] / E_in; } +double CoherentElasticAE::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + // Energy doesn't change in elastic scattering (ENDF-102, Eq. 7-1) + + double pdf; + E_out = E_in; + const auto& energies {xs_.bragg_edges()}; + const auto& factors = xs_.factors(); + + if (E_in < energies.front() || E_in > energies.back()) { + return 0; + } + + const int n = upper_bound_index(energies.begin(), energies.end(), E_in); + + vector mu_vector; + mu_vector.reserve(n); + + std::transform(energies.rbegin() + n - 1, energies.rend(), + std::back_inserter(mu_vector), + [E_in](double Ei) { return 1 - 2 * Ei / E_in; }); + + vector weights; + weights.reserve(n); + + weights.emplace_back(factors[0] / factors[n]); + for (int i = 1; i <= n; ++i) { + weights.emplace_back((factors[i] - factors[i - 1]) / factors[n]); + } + return get_pdf_discrete(mu_vector, weights, mu); +} + //============================================================================== // IncoherentElasticAE implementation //============================================================================== @@ -54,12 +94,21 @@ IncoherentElasticAE::IncoherentElasticAE(hid_t group) void IncoherentElasticAE::sample( double E_in, double& E_out, double& mu, uint64_t* seed) const { + E_out = E_in; + // Sample angle by inverting the distribution in ENDF-102, Eq. 7.4 double c = 2 * E_in * debye_waller_; mu = std::log(1.0 + prn(seed) * (std::exp(2.0 * c) - 1)) / c - 1.0; - - // Energy doesn't change in elastic scattering (ENDF-102, Eq. 7.4) +} +double IncoherentElasticAE::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ E_out = E_in; + + // Sample angle by inverting the distribution in ENDF-102, Eq. 7.4 + double c = 2 * E_in * debye_waller_; + double A = c / (1 - std::exp(-2.0 * c)); // normalization factor + return A * std::exp(-c * (1 - mu)); } //============================================================================== @@ -116,6 +165,28 @@ void IncoherentElasticAEDiscrete::sample( E_out = E_in; } +double IncoherentElasticAEDiscrete::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + // Get index and interpolation factor for elastic grid + int i; + double f; + get_energy_index(energy_, E_in, i, f); + // Energy doesn't change in elastic scattering + E_out = E_in; + int n_mu = mu_out_.shape()[1]; + + std::vector mu_vector; + mu_vector.reserve(n_mu); + + for (int k = 0; k < n_mu; ++k) { + double mu_k = mu_out_(i, k) + f * (mu_out_(i + 1, k) - mu_out_(i, k)); + mu_vector.push_back(mu_k); + } + + return get_pdf_discrete(mu_vector, mu); +} + //============================================================================== // IncoherentInelasticAEDiscrete implementation //============================================================================== @@ -129,8 +200,8 @@ IncoherentInelasticAEDiscrete::IncoherentInelasticAEDiscrete( read_dataset(group, "skewed", skewed_); } -void IncoherentInelasticAEDiscrete::sample( - double E_in, double& E_out, double& mu, uint64_t* seed) const +void IncoherentInelasticAEDiscrete::sample_params( + double E_in, double& E_out, int& j, uint64_t* seed) const { // Get index and interpolation factor for inelastic grid int i; @@ -144,7 +215,6 @@ void IncoherentInelasticAEDiscrete::sample( // for the second and second to last bins, relative to a normal bin // probability of 1). Otherwise, each bin is equally probable. - int j; int n = energy_out_.shape()[1]; if (!skewed_) { // All bins equally likely @@ -176,6 +246,18 @@ void IncoherentInelasticAEDiscrete::sample( // Outgoing energy E_out = (1 - f) * E_ij + f * E_i1j; +} + +void IncoherentInelasticAEDiscrete::sample( + double E_in, double& E_out, double& mu, uint64_t* seed) const +{ + // Get index and interpolation factor for inelastic grid + int i; + double f; + get_energy_index(energy_, E_in, i, f); + + int j; + sample_params(E_in, E_out, j, seed); // Sample outgoing cosine bin int m = mu_out_.shape()[2]; @@ -189,6 +271,30 @@ void IncoherentInelasticAEDiscrete::sample( mu = (1 - f) * mu_ijk + f * mu_i1jk; } +double IncoherentInelasticAEDiscrete::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + // Get index and interpolation factor for inelastic grid + int i; + double f; + get_energy_index(energy_, E_in, i, f); + int j; + sample_params(E_in, E_out, j, seed); + + int m = mu_out_.shape()[2]; + std::vector mu_vector; + mu_vector.reserve(m); + + for (int k = 0; k < m; ++k) { + double mu_ijk = mu_out_(i, j, k); + double mu_i1jk = mu_out_(i + 1, j, k); + double mu_k = (1 - f) * mu_ijk + f * mu_i1jk; + mu_vector.push_back(mu_k); + } + + return get_pdf_discrete(mu_vector, mu); +} + //============================================================================== // IncoherentInelasticAE implementation //============================================================================== @@ -231,24 +337,23 @@ IncoherentInelasticAE::IncoherentInelasticAE(hid_t group) } } -void IncoherentInelasticAE::sample( - double E_in, double& E_out, double& mu, uint64_t* seed) const +void IncoherentInelasticAE::sample_params( + double E_in, double& E_out, double& f, int& l, int& j, uint64_t* seed) const { // Get index and interpolation factor for inelastic grid int i; - double f; - get_energy_index(energy_, E_in, i, f); + double f0; + get_energy_index(energy_, E_in, i, f0); // Pick closer energy based on interpolation factor - int l = f > 0.5 ? i + 1 : i; + l = f0 > 0.5 ? i + 1 : i; // Determine outgoing energy bin // (First reset n_energy_out to the right value) - auto n = distribution_[l].n_e_out; + int n = distribution_[l].n_e_out; double r1 = prn(seed); double c_j = distribution_[l].e_out_cdf[0]; double c_j1; - std::size_t j; for (j = 0; j < n - 1; ++j) { c_j1 = distribution_[l].e_out_cdf[j + 1]; if (r1 < c_j1) @@ -286,6 +391,15 @@ void IncoherentInelasticAE::sample( E_out += E_in - E_l; } + f = (r1 - c_j) / (c_j1 - c_j); +} +void IncoherentInelasticAE::sample( + double E_in, double& E_out, double& mu, uint64_t* seed) const +{ + double f; + int l, j; + sample_params(E_in, E_out, f, l, j, seed); + // Sample outgoing cosine bin int n_mu = distribution_[l].mu.shape()[1]; std::size_t k = prn(seed) * n_mu; @@ -294,7 +408,6 @@ void IncoherentInelasticAE::sample( // a bin of width 0.5*min(mu[k] - mu[k-1], mu[k+1] - mu[k]) centered on the // discrete mu value itself. const auto& mu_l = distribution_[l].mu; - f = (r1 - c_j) / (c_j1 - c_j); // Interpolate kth mu value between distributions at energies j and j+1 mu = mu_l(j, k) + f * (mu_l(j + 1, k) - mu_l(j, k)); @@ -318,6 +431,25 @@ void IncoherentInelasticAE::sample( mu += std::min(mu - mu_left, mu_right - mu) * (prn(seed) - 0.5); } +double IncoherentInelasticAE::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + double f; + int l, j; + sample_params(E_in, E_out, f, l, j, seed); + + int n_mu = distribution_[l].mu.shape()[1]; + const auto& mu_l = distribution_[l].mu; + std::vector mu_vector; + + for (int k = 0; k < n_mu; ++k) { + double mu_k = mu_l(j, k) + f * (mu_l(j + 1, k) - mu_l(j, k)); + mu_vector.push_back(mu_k); + } + + return get_pdf_discrete(mu_vector, mu); +} + //============================================================================== // MixedElasticAE implementation //============================================================================== @@ -340,18 +472,30 @@ MixedElasticAE::MixedElasticAE( close_group(incoherent_group); } -void MixedElasticAE::sample( - double E_in, double& E_out, double& mu, uint64_t* seed) const +const AngleEnergy& MixedElasticAE::sample_dist( + double E_in, uint64_t* seed) const { // Evaluate coherent and incoherent elastic cross sections double xs_coh = coherent_xs_(E_in); double xs_incoh = incoherent_xs_(E_in); if (prn(seed) * (xs_coh + xs_incoh) < xs_coh) { - coherent_dist_.sample(E_in, E_out, mu, seed); + return coherent_dist_; } else { - incoherent_dist_->sample(E_in, E_out, mu, seed); + return *incoherent_dist_; } } +void MixedElasticAE::sample( + double E_in, double& E_out, double& mu, uint64_t* seed) const +{ + sample_dist(E_in, seed).sample(E_in, E_out, mu, seed); +} + +double MixedElasticAE::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + return sample_dist(E_in, seed).sample_energy_and_pdf(E_in, mu, E_out, seed); +} + } // namespace openmc diff --git a/src/secondary_uncorrelated.cpp b/src/secondary_uncorrelated.cpp index 5cbb76fb9b9..ec2af710258 100644 --- a/src/secondary_uncorrelated.cpp +++ b/src/secondary_uncorrelated.cpp @@ -65,4 +65,22 @@ void UncorrelatedAngleEnergy::sample( E_out = energy_->sample(E_in, seed); } +double UncorrelatedAngleEnergy::sample_energy_and_pdf( + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + // Sample outgoing energy + if (energy_ != nullptr) { + E_out = energy_->sample(E_in, seed); + } else { + E_out = E_in; + } + + if (!angle_.empty()) { + return angle_.evaluate(E_in, mu); + } else { + // no angle distribution given => assume isotropic for all energies + return 0.5; + } +} + } // namespace openmc diff --git a/src/thermal.cpp b/src/thermal.cpp index cbe0983ed65..1a06d1d19a3 100644 --- a/src/thermal.cpp +++ b/src/thermal.cpp @@ -296,16 +296,21 @@ void ThermalData::calculate_xs( *inelastic = (*inelastic_.xs)(E); } -void ThermalData::sample(const NuclideMicroXS& micro_xs, double E, - double* E_out, double* mu, uint64_t* seed) +AngleEnergy& ThermalData::sample_dist( + const NuclideMicroXS& micro_xs, double E, uint64_t* seed) const { // Determine whether inelastic or elastic scattering will occur if (prn(seed) < micro_xs.thermal_elastic / micro_xs.thermal) { - elastic_.distribution->sample(E, *E_out, *mu, seed); + return *elastic_.distribution; } else { - inelastic_.distribution->sample(E, *E_out, *mu, seed); + return *inelastic_.distribution; } +} +void ThermalData::sample(const NuclideMicroXS& micro_xs, double E, + double* E_out, double* mu, uint64_t* seed) const +{ + sample_dist(micro_xs, E, seed).sample(E, *E_out, *mu, seed); // Because of floating-point roundoff, it may be possible for mu to be // outside of the range [-1,1). In these cases, we just set mu to exactly // -1 or 1 @@ -313,6 +318,13 @@ void ThermalData::sample(const NuclideMicroXS& micro_xs, double E, *mu = std::copysign(1.0, *mu); } +double ThermalData::sample_energy_and_pdf(const NuclideMicroXS& micro_xs, + double E_in, double mu, double& E_out, uint64_t* seed) const +{ + return sample_dist(micro_xs, E_in, seed) + .sample_energy_and_pdf(E_in, mu, E_out, seed); +} + void free_memory_thermal() { data::thermal_scatt.clear();