#ifndef OPENMC_PARTICLE_DATA_H #define OPENMC_PARTICLE_DATA_H #include "openmc/array.h" #include "openmc/constants.h" #include "openmc/particle_type.h" #include "openmc/position.h" #include "openmc/random_lcg.h" #include "openmc/tallies/filter_match.h" #include "openmc/vector.h" #ifdef OPENMC_DAGMC_ENABLED #include "DagMC.hpp" #endif namespace openmc { //============================================================================== // Constants //============================================================================== // Since cross section libraries come with different numbers of delayed groups // (e.g. ENDF/B-VII.1 has 6 and JEFF 3.1.1 has 8 delayed groups) and we don't // yet know what cross section library is being used when the tallies.xml file // is read in, we want to have an upper bound on the size of the array we // use to store the bins for delayed group tallies. constexpr int MAX_DELAYED_GROUPS {8}; constexpr double CACHE_INVALID {-1.0}; //========================================================================== // Aliases and type definitions //! Saved ("banked") state of a particle //! NOTE: This structure's MPI type is built in initialize_mpi() of //! initialize.cpp. Any changes made to the struct here must also be //! made when building the Bank MPI type in initialize_mpi(). //! NOTE: This structure is also used on the python side, and is defined //! in lib/core.py. Changes made to the type here must also be made to the //! python defintion. struct SourceSite { Position r; Direction u; double E; double time {0.0}; double wgt {1.0}; int delayed_group {0}; int surf_id {SURFACE_NONE}; ParticleType particle; // Extra attributes that don't show up in source written to file int parent_nuclide {-1}; int64_t parent_id {0}; int64_t progeny_id {0}; double wgt_born {1.0}; double wgt_ww_born {-1.0}; int64_t n_split {0}; }; struct CollisionTrackSite { Position r; Direction u; double E; double dE; double time {0.0}; double wgt {1.0}; int event_mt {0}; int delayed_group {0}; int cell_id {0}; int nuclide_id; int material_id {0}; int universe_id {0}; int n_collision {0}; ParticleType particle; int64_t parent_id; int64_t progeny_id; }; //! State of a particle used for particle track files struct TrackState { Position r; //!< Position in [cm] Direction u; //!< Direction double E; //!< Energy in [eV] double time {0.0}; //!< Time in [s] double wgt {1.0}; //!< Weight int cell_id; //!< Cell ID int cell_instance; //!< Cell instance int material_id {-1}; //!< Material ID (default value indicates void) }; //! Full history of a single particle's track states struct TrackStateHistory { ParticleType particle; std::vector states; }; //! Saved ("banked") state of a particle, for nu-fission tallying struct NuBank { double E; //!< particle energy double wgt; //!< particle weight int delayed_group; //!< particle delayed group }; class LocalCoord { public: void rotate(const vector& rotation); //! clear data from a single coordinate level void reset(); // accessors Position& r() { return r_; } const Position& r() const { return r_; } Direction& u() { return u_; } const Direction& u() const { return u_; } int& cell() { return cell_; } const int& cell() const { return cell_; } int& universe() { return universe_; } const int& universe() const { return universe_; } int& lattice() { return lattice_; } int lattice() const { return lattice_; } array& lattice_index() { return lattice_index_; } const array& lattice_index() const { return lattice_index_; } bool& rotated() { return rotated_; } const bool& rotated() const { return rotated_; } private: // Data members Position r_; //!< particle position Direction u_; //!< particle direction int cell_ {-1}; int universe_ {-1}; int lattice_ {-1}; array lattice_index_ {{-1, -1, -1}}; bool rotated_ {false}; //!< Is the level rotated? }; //============================================================================== //! Cached microscopic cross sections for a particular nuclide at the current //! energy //============================================================================== struct NuclideMicroXS { // Microscopic cross sections in barns double total; //!< total cross section double absorption; //!< absorption (disappearance) double fission; //!< fission double nu_fission; //!< neutron production from fission double elastic; //!< If sab_frac is not 1 or 0, then this value is //!< averaged over bound and non-bound nuclei double thermal; //!< Bound thermal elastic & inelastic scattering double thermal_elastic; //!< Bound thermal elastic scattering double photon_prod; //!< microscopic photon production xs // Cross sections for depletion reactions (note that these are not stored in // macroscopic cache) double reaction[DEPLETION_RX.size()]; // Indicies and factors needed to compute cross sections from the data tables int index_grid; //!< Index on nuclide energy grid int index_temp; //!< Temperature index for nuclide double interp_factor; //!< Interpolation factor on nuc. energy grid int index_sab {-1}; //!< Index in sab_tables int index_temp_sab; //!< Temperature index for sab_tables double sab_frac; //!< Fraction of atoms affected by S(a,b) bool use_ptable; //!< In URR range with probability tables? // Energy and temperature last used to evaluate these cross sections. If // these values have changed, then the cross sections must be re-evaluated. double last_E {0.0}; //!< Last evaluated energy double last_sqrtkT {0.0}; //!< Last temperature in sqrt(Boltzmann constant //!< * temperature (eV)) double ncrystal_xs {-1.0}; //!< NCrystal cross section }; //============================================================================== //! Cached microscopic photon cross sections for a particular element at the //! current energy //============================================================================== struct ElementMicroXS { int index_grid; //!< index on element energy grid double last_E {0.0}; //!< last evaluated energy in [eV] double interp_factor; //!< interpolation factor on energy grid double total; //!< microscopic total photon xs double coherent; //!< microscopic coherent xs double incoherent; //!< microscopic incoherent xs double photoelectric; //!< microscopic photoelectric xs double pair_production; //!< microscopic pair production xs }; //============================================================================== // MacroXS contains cached macroscopic cross sections for the material a // particle is traveling through //============================================================================== struct MacroXS { double total; //!< macroscopic total xs double absorption; //!< macroscopic absorption xs double fission; //!< macroscopic fission xs double nu_fission; //!< macroscopic production xs double photon_prod; //!< macroscopic photon production xs // Photon cross sections double coherent; //!< macroscopic coherent xs double incoherent; //!< macroscopic incoherent xs double photoelectric; //!< macroscopic photoelectric xs double pair_production; //!< macroscopic pair production xs }; //============================================================================== // Cache contains the cached data for an MGXS object //============================================================================== struct CacheDataMG { int material {-1}; //!< material index double sqrtkT; //!< last temperature corresponding to t int t {0}; //!< temperature index int a {0}; //!< angle index Direction u; //!< angle that corresponds to a }; //============================================================================== // Information about nearest boundary crossing //============================================================================== class BoundaryInfo { public: void reset() { distance_ = INFINITY; surface_ = SURFACE_NONE; coord_level_ = 0; lattice_translation_ = {0, 0, 0}; } double& distance() { return distance_; } const double& distance() const { return distance_; } int& surface() { return surface_; } const int& surface() const { return surface_; } int coord_level() const { return coord_level_; } int& coord_level() { return coord_level_; } array& lattice_translation() { return lattice_translation_; } const array& lattice_translation() const { return lattice_translation_; } // TODO: off-by-one int surface_index() const { return std::abs(surface()) - 1; } private: // Data members double distance_ {INFINITY}; //!< distance to nearest boundary int surface_ { SURFACE_NONE}; //!< surface token, non-zero if boundary is surface int coord_level_ {0}; //!< coordinate level after crossing boundary array lattice_translation_ { 0, 0, 0}; //!< which way lattice indices will change }; /* * Contains all geometry state information for a particle. */ class GeometryState { public: GeometryState(); /* * GeometryState does not store any ID info, so give some reasonable behavior * here. The Particle class redefines this. This is only here for the error * reporting behavior that occurs in geometry.cpp. The explanation for * mark_as_lost is the same. */ virtual void mark_as_lost(const char* message); void mark_as_lost(const std::string& message); void mark_as_lost(const std::stringstream& message); // resets all coordinate levels for the particle void clear() { for (auto& level : coord_) { level.reset(); } n_coord_ = 1; for (auto& cell : cell_last_) { cell = C_NONE; } n_coord_last_ = 1; } //! moves the particle by the specified distance to its next location //! \param distance the distance the particle is moved void move_distance(double distance); void advance_to_boundary_from_void(); // Initialize all internal state from position and direction void init_from_r_u(Position r_a, Direction u_a) { clear(); surface() = SURFACE_NONE; material() = C_NONE; r() = r_a; u() = u_a; r_last_current() = r_a; r_last() = r_a; u_last() = u_a; } // Unique ID. This is not geometric info, but the // error reporting in geometry.cpp requires this. // We could save this to implement it in Particle, // but that would require virtuals. int64_t& id() { return id_; } const int64_t& id() const { return id_; } // Number of current coordinate levels int& n_coord() { return n_coord_; } const int& n_coord() const { return n_coord_; } // Offset for distributed properties int& cell_instance() { return cell_instance_; } const int& cell_instance() const { return cell_instance_; } // Coordinates for all nesting levels LocalCoord& coord(int i) { return coord_[i]; } const LocalCoord& coord(int i) const { return coord_[i]; } const vector& coord() const { return coord_; } // Innermost universe nesting coordinates LocalCoord& lowest_coord() { return coord_[n_coord_ - 1]; } const LocalCoord& lowest_coord() const { return coord_[n_coord_ - 1]; } // Last coordinates on all nesting levels, before crossing a surface int& n_coord_last() { return n_coord_last_; } const int& n_coord_last() const { return n_coord_last_; } int& cell_last(int i) { return cell_last_[i]; } const int& cell_last(int i) const { return cell_last_[i]; } // Coordinates at birth Position& r_born() { return r_born_; } const Position& r_born() const { return r_born_; } // Coordinates of last collision or reflective/periodic surface // crossing for current tallies Position& r_last_current() { return r_last_current_; } const Position& r_last_current() const { return r_last_current_; } // Previous direction and spatial coordinates before a collision Position& r_last() { return r_last_; } const Position& r_last() const { return r_last_; } Position& u_last() { return u_last_; } const Position& u_last() const { return u_last_; } // Accessors for position in global coordinates Position& r() { return coord_[0].r(); } const Position& r() const { return coord_[0].r(); } // Accessors for position in local coordinates Position& r_local() { return coord_[n_coord_ - 1].r(); } const Position& r_local() const { return coord_[n_coord_ - 1].r(); } // Accessors for direction in global coordinates Direction& u() { return coord_[0].u(); } const Direction& u() const { return coord_[0].u(); } // Accessors for direction in local coordinates Direction& u_local() { return coord_[n_coord_ - 1].u(); } const Direction& u_local() const { return coord_[n_coord_ - 1].u(); } // Surface token for the surface that the particle is currently on int& surface() { return surface_; } const int& surface() const { return surface_; } // Surface index based on the current value of the surface_ attribute int surface_index() const { // TODO: off-by-one return std::abs(surface_) - 1; } // Boundary information BoundaryInfo& boundary() { return boundary_; } #ifdef OPENMC_DAGMC_ENABLED // DagMC state variables moab::DagMC::RayHistory& history() { return history_; } Direction& last_dir() { return last_dir_; } #endif // material of current and last cell int& material() { return material_; } const int& material() const { return material_; } int& material_last() { return material_last_; } const int& material_last() const { return material_last_; } // temperature of current and last cell double& sqrtkT() { return sqrtkT_; } const double& sqrtkT() const { return sqrtkT_; } double& sqrtkT_last() { return sqrtkT_last_; } // density multiplier of the current and last cell double& density_mult() { return density_mult_; } const double& density_mult() const { return density_mult_; } double& density_mult_last() { return density_mult_last_; } private: int64_t id_ {-1}; //!< Unique ID int n_coord_ {1}; //!< number of current coordinate levels int cell_instance_; //!< offset for distributed properties vector coord_; //!< coordinates for all levels int n_coord_last_ {1}; //!< number of current coordinates vector cell_last_; //!< coordinates for all levels Position r_born_; //!< coordinates at birth Position r_last_current_; //!< coordinates of the last collision or //!< reflective/periodic surface crossing for //!< current tallies Position r_last_; //!< previous coordinates Direction u_last_; //!< previous direction coordinates int surface_ { SURFACE_NONE}; //!< surface token for surface the particle is currently on BoundaryInfo boundary_; //!< Info about the next intersection int material_ {-1}; //!< index for current material int material_last_ {-1}; //!< index for last material double sqrtkT_ {-1.0}; //!< sqrt(k_Boltzmann * temperature) in eV double sqrtkT_last_ {0.0}; //!< last temperature double density_mult_ {1.0}; //!< density multiplier double density_mult_last_ {1.0}; //!< last density multiplier #ifdef OPENMC_DAGMC_ENABLED moab::DagMC::RayHistory history_; Direction last_dir_; #endif }; //============================================================================ //! Defines how particle data is laid out in memory //============================================================================ /* * This class was added in order to separate the layout and access of particle * data from particle physics operations during a development effort to get * OpenMC running on GPUs. In the event-based Monte Carlo method, one creates * an array of particles on which actions like cross section lookup and surface * crossing are done en masse, which works best on vector computers of yore and * modern GPUs. It has been shown in the below publication [1] that arranging * particle data into a structure of arrays rather than an array of structures * enhances performance on GPUs. For instance, rather than having an * std::vector where consecutive particle energies would be separated * by about 400 bytes, one would create a structure which has a single * std::vector of energies. The motivation here is that more coalesced * memory accesses occur, in the parlance of GPU programming. * * So, this class enables switching between the array-of-structures and * structure- of-array data layout at compile time. In GPU branches of the * code, our Particle class inherits from a class that provides an array of * particle energies, and can access them using the E() method (defined below). * In the CPU code, we inherit from this class which gives the conventional * layout of particle data, useful for history-based tracking. * * As a result, we always use the E(), r_last(), etc. methods to access * particle data in order to keep a unified interface between * structure-of-array and array-of-structure code on either CPU or GPU code * while sharing the same physics code on each codebase. * * [1] Hamilton, Steven P., Stuart R. Slattery, and Thomas M. Evans. * “Multigroup Monte Carlo on GPUs: Comparison of History- and Event-Based * Algorithms.” Annals of Nuclear Energy 113 (March 2018): 506–18. * https://doi.org/10.1016/j.anucene.2017.11.032. */ class ParticleData : public GeometryState { private: //========================================================================== // Data members -- see public: below for descriptions vector neutron_xs_; vector photon_xs_; MacroXS macro_xs_; CacheDataMG mg_xs_cache_; ParticleType type_; double E_; double E_last_; int g_ {0}; int g_last_; double wgt_ {1.0}; double wgt_born_ {1.0}; double wgt_ww_born_ {-1.0}; double mu_; double time_ {0.0}; double time_last_ {0.0}; double wgt_last_ {1.0}; bool fission_ {false}; TallyEvent event_; int event_nuclide_; int event_mt_; int delayed_group_ {0}; int parent_nuclide_ {-1}; int n_bank_ {0}; double bank_second_E_ {0.0}; double wgt_bank_ {0.0}; int n_delayed_bank_[MAX_DELAYED_GROUPS]; int cell_born_ {-1}; // Iterated Fission Probability double lifetime_ {0.0}; //!< neutron lifetime [s] int n_collision_ {0}; bool write_track_ {false}; uint64_t seeds_[N_STREAMS]; int stream_; vector local_secondary_bank_; // Keep track of how many secondary particles were created in the collision // and what the starting index is in the secondary bank for this particle int n_secondaries_ {0}; int secondary_bank_index_ {0}; int64_t current_work_ {0}; vector flux_derivs_; vector filter_matches_; vector tracks_; vector nu_bank_; vector pht_storage_; double keff_tally_absorption_ {0.0}; double keff_tally_collision_ {0.0}; double keff_tally_tracklength_ {0.0}; double keff_tally_leakage_ {0.0}; bool trace_ {false}; double collision_distance_; int n_event_ {0}; int64_t n_tracks_ {0}; //!< number of tracks in this particle history int64_t n_split_ {0}; double ww_factor_ {0.0}; int64_t n_progeny_ {0}; public: //---------------------------------------------------------------------------- // Constructors ParticleData(); //========================================================================== // Methods and accessors // Cross section caches // Microscopic neutron cross sections NuclideMicroXS& neutron_xs(int i) { return neutron_xs_[i]; } const NuclideMicroXS& neutron_xs(int i) const { return neutron_xs_[i]; } // Microscopic photon cross sections ElementMicroXS& photon_xs(int i) { return photon_xs_[i]; } // Macroscopic cross sections MacroXS& macro_xs() { return macro_xs_; } const MacroXS& macro_xs() const { return macro_xs_; } // Multigroup macroscopic cross sections CacheDataMG& mg_xs_cache() { return mg_xs_cache_; } const CacheDataMG& mg_xs_cache() const { return mg_xs_cache_; } // Particle type (n, p, e, gamma, etc) ParticleType& type() { return type_; } const ParticleType& type() const { return type_; } // Current particle energy, energy before collision, // and corresponding multigroup group indices. Energy // units are eV. double& E() { return E_; } const double& E() const { return E_; } double& E_last() { return E_last_; } const double& E_last() const { return E_last_; } int& g() { return g_; } const int& g() const { return g_; } int& g_last() { return g_last_; } const int& g_last() const { return g_last_; } // Statistic weight of particle. Setting to zero indicates that the particle // is dead. double& wgt() { return wgt_; } double wgt() const { return wgt_; } // Statistic weight of particle at birth double& wgt_born() { return wgt_born_; } double wgt_born() const { return wgt_born_; } // Weight window value at birth double& wgt_ww_born() { return wgt_ww_born_; } const double& wgt_ww_born() const { return wgt_ww_born_; } // Statistic weight of particle at last collision double& wgt_last() { return wgt_last_; } const double& wgt_last() const { return wgt_last_; } // Whether particle is alive bool alive() const { return wgt_ != 0.0; } // Polar scattering angle after a collision double& mu() { return mu_; } const double& mu() const { return mu_; } // Tracks the time of a particle as it traverses the problem. // Units are seconds. double& time() { return time_; } const double& time() const { return time_; } double& time_last() { return time_last_; } const double& time_last() const { return time_last_; } // Particle lifetime double& lifetime() { return lifetime_; } const double& lifetime() const { return lifetime_; } // What event took place, described in greater detail below TallyEvent& event() { return event_; } const TallyEvent& event() const { return event_; } bool& fission() { return fission_; } // true if implicit fission int& event_nuclide() { return event_nuclide_; } // index of collision nuclide const int& event_nuclide() const { return event_nuclide_; } int& event_mt() { return event_mt_; } // MT number of collision const int& event_mt() const { return event_mt_; } int& delayed_group() { return delayed_group_; } // delayed group const int& delayed_group() const { return delayed_group_; } const int& parent_nuclide() const { return parent_nuclide_; } int& parent_nuclide() { return parent_nuclide_; } // Parent nuclide // Post-collision data double& bank_second_E() { return bank_second_E_; } // energy of last reaction secondaries const double& bank_second_E() const { return bank_second_E_; } int& n_bank() { return n_bank_; } // number of banked fission sites double& wgt_bank() { return wgt_bank_; } // weight of banked fission sites int* n_delayed_bank() { return n_delayed_bank_; } // number of delayed fission sites int& n_delayed_bank(int i) { return n_delayed_bank_[i]; } // number of delayed fission sites // Index of cell particle is born in int& cell_born() { return cell_born_; } const int& cell_born() const { return cell_born_; } // Total number of collisions suffered by particle int& n_collision() { return n_collision_; } const int& n_collision() const { return n_collision_; } // whether this track is to be written bool& write_track() { return write_track_; } // RNG state uint64_t& seeds(int i) { return seeds_[i]; } uint64_t* seeds() { return seeds_; } int& stream() { return stream_; } // secondary particle bank SourceSite& local_secondary_bank(int i) { return local_secondary_bank_[i]; } const SourceSite& local_secondary_bank(int i) const { return local_secondary_bank_[i]; } decltype(local_secondary_bank_)& local_secondary_bank() { return local_secondary_bank_; } // Number of secondaries created in a collision int& n_secondaries() { return n_secondaries_; } const int& n_secondaries() const { return n_secondaries_; } // Starting index in secondary bank for this collision int& secondary_bank_index() { return secondary_bank_index_; } const int& secondary_bank_index() const { return secondary_bank_index_; } // Current simulation work index int64_t& current_work() { return current_work_; } const int64_t& current_work() const { return current_work_; } // Used in tally derivatives double& flux_derivs(int i) { return flux_derivs_[i]; } const double& flux_derivs(int i) const { return flux_derivs_[i]; } // Matches of tallies decltype(filter_matches_)& filter_matches() { return filter_matches_; } FilterMatch& filter_matches(int i) { return filter_matches_[i]; } // Tracks to output to file decltype(tracks_)& tracks() { return tracks_; } // Bank of recently fissioned particles decltype(nu_bank_)& nu_bank() { return nu_bank_; } NuBank& nu_bank(int i) { return nu_bank_[i]; } // Interim pulse height tally storage vector& pht_storage() { return pht_storage_; } // Global tally accumulators double& keff_tally_absorption() { return keff_tally_absorption_; } double& keff_tally_collision() { return keff_tally_collision_; } double& keff_tally_tracklength() { return keff_tally_tracklength_; } double& keff_tally_leakage() { return keff_tally_leakage_; } // Shows debug info bool& trace() { return trace_; } // Distance to the next collision double& collision_distance() { return collision_distance_; } // Number of events particle has undergone int& n_event() { return n_event_; } // Number of times variance reduction has caused a particle split int64_t n_split() const { return n_split_; } int64_t& n_split() { return n_split_; } // Particle-specific factor for on-the-fly weight window adjustment double ww_factor() const { return ww_factor_; } double& ww_factor() { return ww_factor_; } // Number of tracks in this particle history int64_t& n_tracks() { return n_tracks_; } // Number of progeny produced by this particle int64_t& n_progeny() { return n_progeny_; } //! Gets the pointer to the particle's current PRN seed uint64_t* current_seed() { return seeds_ + stream_; } const uint64_t* current_seed() const { return seeds_ + stream_; } //! Force recalculation of neutron xs by setting last energy to zero void invalidate_neutron_xs() { for (auto& micro : neutron_xs_) micro.last_E = 0.0; } //! Get track information based on particle's current state TrackState get_track_state() const; void zero_delayed_bank() { for (int& n : n_delayed_bank_) { n = 0; } } void zero_flux_derivs() { for (double& d : flux_derivs_) { d = 0; } } }; } // namespace openmc #endif // OPENMC_PARTICLE_DATA_H