From 9d2b2dda6068d25db5bb05753f7827cd9761974b Mon Sep 17 00:00:00 2001 From: GuySten Date: Wed, 2 Sep 2026 01:38:18 +0300 Subject: [PATCH 1/2] cleanup Reset ifp_n_generation in finalize.cpp Avoid reference into empty vector Fix formatting Fix again ran clang wip update update --- include/openmc/bank.h | 8 -- include/openmc/ifp.h | 199 ++++++++++++++++++++++------------ include/openmc/settings.h | 8 -- src/bank.cpp | 37 +++---- src/eigenvalue.cpp | 87 +++++++-------- src/ifp.cpp | 100 +++-------------- src/physics.cpp | 2 +- src/settings.cpp | 4 - src/simulation.cpp | 2 +- src/tallies/tally.cpp | 9 +- src/tallies/tally_scoring.cpp | 20 ++-- 11 files changed, 223 insertions(+), 253 deletions(-) diff --git a/include/openmc/bank.h b/include/openmc/bank.h index 6abcdd7f185..51fb9fb6b1a 100644 --- a/include/openmc/bank.h +++ b/include/openmc/bank.h @@ -24,14 +24,6 @@ extern SharedArray collision_track_bank; extern SharedArray fission_bank; -extern vector> ifp_source_delayed_group_bank; - -extern vector> ifp_source_lifetime_bank; - -extern vector> ifp_fission_delayed_group_bank; - -extern vector> ifp_fission_lifetime_bank; - extern vector progeny_per_particle; extern SharedArray shared_secondary_bank_read; diff --git a/include/openmc/ifp.h b/include/openmc/ifp.h index eb62f045ebb..f8f63f4e6d3 100644 --- a/include/openmc/ifp.h +++ b/include/openmc/ifp.h @@ -10,22 +10,6 @@ namespace openmc { -//! Resize IFP vectors -//! -//! \param[in,out] delayed_groups List of delayed group numbers -//! \param[in,out] lifetimes List of lifetimes -//! \param[in] n Dimension to resize vectors -template -void resize_ifp_data(vector& delayed_groups, vector& lifetimes, int64_t n) -{ - if (settings::ifp_delayed_group_on) { - delayed_groups.resize(n); - } - if (settings::ifp_lifetime_on) { - lifetimes.resize(n); - } -} - //! Update a list of values by adding a new value if the size //! of the list can accomodate the new value or by shifting all //! values to the left (removing the first value of the list @@ -56,12 +40,129 @@ vector _ifp(const T& value, const vector& data) return updated; } +//============================================================================== +//! One stream of Iterated Fission Probability history data. +// +//! IFP tracks two quantities with identical bookkeeping: the delayed group +//! number of the ancestor neutron (an int) and its lifetime (a double). Each is +//! a per-particle list of the last ifp_n_generation values, held in a source +//! bank and a fission bank, and each is maintained only if a tally asked for +//! it. +//! +//! Bundling the flag with the two banks it governs means every operation is a +//! no-op when the stream is off, so callers do not branch, and the banks cannot +//! be resized independently of the flag. +//============================================================================== + +template +class IFPStream { +public: + //! Whether this stream is being maintained + bool enabled() const { return enabled_; } + + //! Begin maintaining this stream, when a tally requests a dependent score + void enable() { enabled_ = true; } + + //! Clear the stream, including the flag. Derived from the tallies present in + //! the model, so it must not persist into the next model in this process. + void reset() + { + enabled_ = false; + source_bank_.clear(); + fission_bank_.clear(); + } + + vector>& source_bank() { return source_bank_; } + const vector>& source_bank() const { return source_bank_; } + vector>& fission_bank() { return fission_bank_; } + + //! Resize both banks + void resize_banks(int64_t n_source, int64_t n_fission) + { + if (!enabled_) + return; + source_bank_.resize(n_source); + fission_bank_.resize(n_fission); + } + + //! Append a value to the ancestor history of a new fission site + void store(int64_t i_source, int64_t i_fission, const T& value) + { + if (!enabled_) + return; + fission_bank_[i_fission] = _ifp(value, source_bank_[i_source]); + } + + //! Resize a caller-owned buffer for this stream, if it is enabled. + // + //! Templated on the container because callers pass both the per-particle + //! banks (`vector>`) and the flat MPI serialization buffers + //! (`vector`). + template + void resize_temp(V& temp, int64_t n) const + { + if (enabled_) + temp.resize(n); + } + + //! Copy one entry of the fission bank into a caller-owned buffer + // + //! \param[in] i_fission Index in this stream's fission bank + //! \param[in] i_temp Index in the destination buffer + //! \param[out] out Destination buffer + void copy_from_fission( + int64_t i_fission, int64_t i_temp, vector>& out) const + { + if (enabled_) + out[i_temp] = fission_bank_[i_fission]; + } + + //! Copy a temporary buffer into the fission bank + void copy_temp_to_fission(const vector* temp, size_t n) + { + if (enabled_) + std::copy(temp, temp + n, fission_bank_.data()); + } + + //! Copy a run of a temporary buffer into the source bank + void copy_temp_to_source( + int64_t i_temp, int64_t n, int64_t i_source, const vector>& temp) + { + if (enabled_) + std::copy(&temp[i_temp], &temp[i_temp + n], &source_bank_[i_source]); + } + + //! Copy a whole temporary buffer into the source bank + void copy_all_temp_to_source(const vector>& temp, int64_t n) + { + if (enabled_) + std::copy(temp.data(), temp.data() + n, source_bank_.begin()); + } + +private: + bool enabled_ {false}; + vector> source_bank_; + vector> fission_bank_; +}; + +namespace simulation { + +extern IFPStream ifp_delayed_group; //!< Delayed group numbers +extern IFPStream ifp_lifetime; //!< Neutron lifetimes + +} // namespace simulation + +//! Whether Iterated Fission Probability is in use at all +inline bool ifp_on() +{ + return simulation::ifp_delayed_group.enabled() || + simulation::ifp_lifetime.enabled(); +} + //! \brief Iterated Fission Probability (IFP) method. //! //! Add the IFP information in the IFP banks using the same index //! as the one used to append the fission site to the fission bank. -//! The information stored are the delayed group number and lifetime -//! of the neutron that created the fission event. //! Multithreading protection is guaranteed by the index returned by the //! thread_safe_append call in physics.cpp. //! @@ -72,14 +173,8 @@ void ifp(const Particle& p, int64_t idx); //! Resize the IFP banks used in the simulation void resize_simulation_ifp_banks(); -//! Retrieve IFP data from the IFP fission banks. -//! -//! \param[in] i_bank Index in the fission banks -//! \param[in] i_temp Index in the temporary bank vectors -//! \param[in,out] delayed_groups Delayed group numbers -//! \param[in,out] lifetimes Lifetimes lists -void copy_ifp_data_from_fission_banks(int64_t i_bank, int64_t i_temp, - vector>& delayed_groups, vector>& lifetimes); +//! Clear both streams, including their flags +void reset_ifp_streams(); #ifdef OPENMC_MPI @@ -89,10 +184,10 @@ struct DeserializationInfo { int64_t n; //!< number of sites sent }; -//! Broadcast the number of generation determined by the size of the first -//! element on the first processor. +//! Broadcast the number of generations, determined from the first element on +//! the first processor of whichever stream is enabled. //! -//! \param[in] n_generation Number of generations +//! \param[in,out] n_generation Number of generations //! \param[in] delayed_groups List of delayed group numbers lists //! \param[in] lifetimes List of lifetimes lists void broadcast_ifp_n_generation(int& n_generation, @@ -150,28 +245,20 @@ void receive_ifp_data(int64_t idx, int64_t n, int n_generation, int neighbor, deserialization.push_back(info); } -//! Copy partial IFP data from local lists to source banks. -//! -//! \param[in] idx Index of the first site -//! \param[in] n Number of sites to copy -//! \param[in] i_bank Index in the IFP source banks -//! \param[in] delayed_groups List of delayed group numbers lists -//! \param[in] lifetimes List of lifetimes lists -void copy_partial_ifp_data_to_source_banks(int64_t idx, int n, int64_t i_bank, - const vector>& delayed_groups, - const vector>& lifetimes); - -//! Deserialize IFP information received using MPI and store it in -//! the IFP source banks. +//! Deserialize IFP information received using MPI into a stream's source bank. //! //! \param[in] n_generation Number of generations //! \param[in] data data to deserialize -//! \param[in] bank bank to store data -//! \param[out] deserialization Information to deserialize the received data +//! \param[in,out] stream Stream whose source bank receives the data +//! \param[in] deserialization Information to deserialize the received data template void deserialize_ifp_info(int n_generation, const vector& data, - vector>& bank, const vector& deserialization) + IFPStream& stream, const vector& deserialization) { + if (!stream.enabled()) + return; + + auto& bank = stream.source_bank(); for (auto info : deserialization) { int64_t index_local = info.index_local; int64_t n = info.n; @@ -186,28 +273,6 @@ void deserialize_ifp_info(int n_generation, const vector& data, #endif -//! Copy IFP temporary vectors to source banks. -//! -//! \param[in] delayed_groups List of delayed group numbers lists -//! \param[in] lifetimes List of lifetimes lists -void copy_complete_ifp_data_to_source_banks( - const vector>& delayed_groups, - const vector>& lifetimes); - -//! Allocate temporary vectors for IFP data. -//! -//! \param[in,out] delayed_groups List of delayed group numbers lists -//! \param[in,out] lifetimes List of delayed group numbers lists -void allocate_temporary_vector_ifp( - vector>& delayed_groups, vector>& lifetimes); - -//! Copy local IFP data to IFP fission banks. -//! -//! \param[in] delayed_groups_ptr Pointer to delayed group numbers -//! \param[in] lifetimes_ptr Pointer to lifetimes -void copy_ifp_data_to_fission_banks( - const vector* delayed_groups_ptr, const vector* lifetimes_ptr); - } // namespace openmc #endif // OPENMC_IFP_H diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 13c8bb6d1b6..19376c26a93 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -62,8 +62,6 @@ extern bool extern "C" bool entropy_on; //!< calculate Shannon entropy? extern "C" bool event_based; //!< use event-based mode (instead of history-based) -extern bool ifp_delayed_group_on; //!< Store delayed group IFP data? -extern bool ifp_lifetime_on; //!< Store lifetime IFP data? extern bool legendre_to_tabular; //!< convert Legendre distributions to tabular? extern bool material_cell_offsets; //!< create material cells offsets? extern "C" bool output_summary; //!< write summary.h5? @@ -195,12 +193,6 @@ extern "C" int verbosity; //!< How verbose to make output extern double weight_cutoff; //!< Weight cutoff for Russian roulette extern double weight_survive; //!< Survival weight after Russian roulette -//! Whether Iterated Fission Probability is in use at all. -inline bool ifp_on() -{ - return ifp_delayed_group_on || ifp_lifetime_on; -} - } // namespace settings //============================================================================== diff --git a/src/bank.cpp b/src/bank.cpp index 341364fd51e..bbb631c0b3d 100644 --- a/src/bank.cpp +++ b/src/bank.cpp @@ -31,14 +31,6 @@ SharedArray collision_track_bank; // function. SharedArray fission_bank; -vector> ifp_source_delayed_group_bank; - -vector> ifp_source_lifetime_bank; - -vector> ifp_fission_delayed_group_bank; - -vector> ifp_fission_lifetime_bank; - // Each entry in this vector corresponds to the number of progeny produced // this generation for the particle located at that index. This vector is // used to efficiently sort the fission bank after each iteration. @@ -64,10 +56,7 @@ void free_memory_bank() simulation::collision_track_bank.clear(); simulation::fission_bank.clear(); simulation::progeny_per_particle.clear(); - simulation::ifp_source_delayed_group_bank.clear(); - simulation::ifp_source_lifetime_bank.clear(); - simulation::ifp_fission_delayed_group_bank.clear(); - simulation::ifp_fission_lifetime_bank.clear(); + reset_ifp_streams(); simulation::shared_secondary_bank_read.clear(); simulation::shared_secondary_bank_write.clear(); } @@ -113,9 +102,10 @@ void sort_bank(SharedArray& bank, bool is_fission_bank) sorted_bank = bank.data() + bank.size(); } - if (settings::ifp_on() && is_fission_bank) { - allocate_temporary_vector_ifp( - sorted_ifp_delayed_group_bank, sorted_ifp_lifetime_bank); + if (ifp_on() && is_fission_bank) { + int64_t n = simulation::fission_bank.size(); + simulation::ifp_delayed_group.resize_temp(sorted_ifp_delayed_group_bank, n); + simulation::ifp_lifetime.resize_temp(sorted_ifp_lifetime_bank, n); } // Use parent and progeny indices to sort bank @@ -135,17 +125,22 @@ void sort_bank(SharedArray& bank, bool is_fission_bank) "bank size during sorting."); } sorted_bank[idx] = site; - if (settings::ifp_on() && is_fission_bank) { - copy_ifp_data_from_fission_banks( - i, idx, sorted_ifp_delayed_group_bank, sorted_ifp_lifetime_bank); + if (ifp_on() && is_fission_bank) { + simulation::ifp_delayed_group.copy_from_fission( + i, idx, sorted_ifp_delayed_group_bank); + simulation::ifp_lifetime.copy_from_fission( + i, idx, sorted_ifp_lifetime_bank); } } // Copy sorted bank into the fission bank std::copy(sorted_bank, sorted_bank + bank.size(), bank.data()); - if (settings::ifp_on() && is_fission_bank) { - copy_ifp_data_to_fission_banks( - sorted_ifp_delayed_group_bank.data(), sorted_ifp_lifetime_bank.data()); + if (ifp_on() && is_fission_bank) { + size_t n = simulation::fission_bank.size(); + simulation::ifp_delayed_group.copy_temp_to_fission( + sorted_ifp_delayed_group_bank.data(), n); + simulation::ifp_lifetime.copy_temp_to_fission( + sorted_ifp_lifetime_bank.data(), n); } } diff --git a/src/eigenvalue.cpp b/src/eigenvalue.cpp index 71ae1707ca6..04dbe632889 100644 --- a/src/eigenvalue.cpp +++ b/src/eigenvalue.cpp @@ -135,10 +135,9 @@ void synchronize_bank() // Temporary banks for IFP vector> temp_delayed_groups; vector> temp_lifetimes; - if (settings::ifp_on()) { - resize_ifp_data( - temp_delayed_groups, temp_lifetimes, 3 * simulation::work_per_rank); - } + int64_t n_temp = 3 * simulation::work_per_rank; + simulation::ifp_delayed_group.resize_temp(temp_delayed_groups, n_temp); + simulation::ifp_lifetime.resize_temp(temp_lifetimes, n_temp); // ========================================================================== // SAMPLE N_PARTICLES FROM FISSION BANK AND PLACE IN TEMP_SITES @@ -164,10 +163,9 @@ void synchronize_bank() for (int64_t i = tooth_start; i < tooth_end; i++) { int64_t idx = std::floor(tooth) - start; temp_sites[index_temp] = simulation::fission_bank[idx]; - if (settings::ifp_on()) { - copy_ifp_data_from_fission_banks( - idx, index_temp, temp_delayed_groups, temp_lifetimes); - } + simulation::ifp_delayed_group.copy_from_fission( + idx, index_temp, temp_delayed_groups); + simulation::ifp_lifetime.copy_from_fission(idx, index_temp, temp_lifetimes); ++index_temp; // Next tooth @@ -206,7 +204,7 @@ void synchronize_bank() // IFP number of generation int ifp_n_generation; - if (settings::ifp_on()) { + if (ifp_on()) { broadcast_ifp_n_generation( ifp_n_generation, temp_delayed_groups, temp_lifetimes); } @@ -225,9 +223,10 @@ void synchronize_bank() simulation::work_index.begin(), simulation::work_index.end(), start); // Resize IFP send buffers - if (settings::ifp_on() && mpi::n_procs > 1) { - resize_ifp_data(send_delayed_groups, send_lifetimes, - ifp_n_generation * 3 * simulation::work_per_rank); + if (mpi::n_procs > 1) { + int64_t n_send = ifp_n_generation * 3 * simulation::work_per_rank; + simulation::ifp_delayed_group.resize_temp(send_delayed_groups, n_send); + simulation::ifp_lifetime.resize_temp(send_lifetimes, n_send); } while (start < finish) { @@ -243,15 +242,12 @@ void synchronize_bank() mpi::source_site, neighbor, mpi::rank, mpi::intracomm, &requests.back()); - if (settings::ifp_on()) { - // Send IFP data - if (settings::ifp_delayed_group_on) - send_ifp_info(index_local, n, ifp_n_generation, neighbor, requests, - temp_delayed_groups, send_delayed_groups); - if (settings::ifp_lifetime_on) - send_ifp_info(index_local, n, ifp_n_generation, neighbor, requests, - temp_lifetimes, send_lifetimes); - } + if (simulation::ifp_delayed_group.enabled()) + send_ifp_info(index_local, n, ifp_n_generation, neighbor, requests, + temp_delayed_groups, send_delayed_groups); + if (simulation::ifp_lifetime.enabled()) + send_ifp_info(index_local, n, ifp_n_generation, neighbor, requests, + temp_lifetimes, send_lifetimes); } // Increment all indices @@ -290,9 +286,10 @@ void synchronize_bank() } // Resize IFP receive buffers - if (settings::ifp_on() && mpi::n_procs > 1) { - resize_ifp_data(recv_delayed_groups, recv_lifetimes, - ifp_n_generation * simulation::work_per_rank); + if (mpi::n_procs > 1) { + int64_t n_recv = ifp_n_generation * simulation::work_per_rank; + simulation::ifp_delayed_group.resize_temp(recv_delayed_groups, n_recv); + simulation::ifp_lifetime.resize_temp(recv_lifetimes, n_recv); } while (start < simulation::work_index[mpi::rank + 1]) { @@ -314,15 +311,12 @@ void synchronize_bank() MPI_Irecv(&simulation::source_bank[index_local], static_cast(n), mpi::source_site, neighbor, neighbor, mpi::intracomm, &requests.back()); - if (settings::ifp_on()) { - // Receive IFP data - if (settings::ifp_delayed_group_on) - receive_ifp_data(index_local, n, ifp_n_generation, neighbor, requests, - recv_delayed_groups, deserialization_info); - if (settings::ifp_lifetime_on) - receive_ifp_data(index_local, n, ifp_n_generation, neighbor, requests, - recv_lifetimes, deserialization_info); - } + if (simulation::ifp_delayed_group.enabled()) + receive_ifp_data(index_local, n, ifp_n_generation, neighbor, requests, + recv_delayed_groups, deserialization_info); + if (simulation::ifp_lifetime.enabled()) + receive_ifp_data(index_local, n, ifp_n_generation, neighbor, requests, + recv_lifetimes, deserialization_info); } else { // If the source sites are on this processor, we can simply copy them @@ -332,10 +326,10 @@ void synchronize_bank() std::copy(&temp_sites[index_temp], &temp_sites[index_temp + n], &simulation::source_bank[index_local]); - if (settings::ifp_on()) { - copy_partial_ifp_data_to_source_banks( - index_temp, n, index_local, temp_delayed_groups, temp_lifetimes); - } + simulation::ifp_delayed_group.copy_temp_to_source( + index_temp, n, index_local, temp_delayed_groups); + simulation::ifp_lifetime.copy_temp_to_source( + index_temp, n, index_local, temp_lifetimes); } // Increment all indices @@ -351,21 +345,18 @@ void synchronize_bank() int n_request = requests.size(); MPI_Waitall(n_request, requests.data(), MPI_STATUSES_IGNORE); - if (settings::ifp_on()) { - if (settings::ifp_delayed_group_on) - deserialize_ifp_info(ifp_n_generation, recv_delayed_groups, - simulation::ifp_source_delayed_group_bank, deserialization_info); - if (settings::ifp_lifetime_on) - deserialize_ifp_info(ifp_n_generation, recv_lifetimes, - simulation::ifp_source_lifetime_bank, deserialization_info); - } + deserialize_ifp_info(ifp_n_generation, recv_delayed_groups, + simulation::ifp_delayed_group, deserialization_info); + deserialize_ifp_info(ifp_n_generation, recv_lifetimes, + simulation::ifp_lifetime, deserialization_info); #else std::copy(temp_sites.data(), temp_sites.data() + settings::n_particles, simulation::source_bank.begin()); - if (settings::ifp_on()) { - copy_complete_ifp_data_to_source_banks(temp_delayed_groups, temp_lifetimes); - } + simulation::ifp_delayed_group.copy_all_temp_to_source( + temp_delayed_groups, settings::n_particles); + simulation::ifp_lifetime.copy_all_temp_to_source( + temp_lifetimes, settings::n_particles); #endif simulation::time_bank_sendrecv.stop(); diff --git a/src/ifp.cpp b/src/ifp.cpp index 93f2d343d09..cd73b769ec5 100644 --- a/src/ifp.cpp +++ b/src/ifp.cpp @@ -10,38 +10,30 @@ namespace openmc { +namespace simulation { + +IFPStream ifp_delayed_group; +IFPStream ifp_lifetime; + +} // namespace simulation + void ifp(const Particle& p, int64_t idx) { - if (settings::ifp_delayed_group_on) { - const auto& delayed_groups = - simulation::ifp_source_delayed_group_bank[p.current_work()]; - simulation::ifp_fission_delayed_group_bank[idx] = - _ifp(p.delayed_group(), delayed_groups); - } - if (settings::ifp_lifetime_on) { - const auto& lifetimes = - simulation::ifp_source_lifetime_bank[p.current_work()]; - simulation::ifp_fission_lifetime_bank[idx] = _ifp(p.lifetime(), lifetimes); - } + simulation::ifp_delayed_group.store(p.current_work(), idx, p.delayed_group()); + simulation::ifp_lifetime.store(p.current_work(), idx, p.lifetime()); } void resize_simulation_ifp_banks() { - resize_ifp_data(simulation::ifp_source_delayed_group_bank, - simulation::ifp_source_lifetime_bank, simulation::work_per_rank); - resize_ifp_data(simulation::ifp_fission_delayed_group_bank, - simulation::ifp_fission_lifetime_bank, 3 * simulation::work_per_rank); + int64_t n = simulation::work_per_rank; + simulation::ifp_delayed_group.resize_banks(n, 3 * n); + simulation::ifp_lifetime.resize_banks(n, 3 * n); } -void copy_ifp_data_from_fission_banks(int64_t i_bank, int64_t i_temp, - vector>& delayed_groups, vector>& lifetimes) +void reset_ifp_streams() { - if (settings::ifp_delayed_group_on) { - delayed_groups[i_temp] = simulation::ifp_fission_delayed_group_bank[i_bank]; - } - if (settings::ifp_lifetime_on) { - lifetimes[i_temp] = simulation::ifp_fission_lifetime_bank[i_bank]; - } + simulation::ifp_delayed_group.reset(); + simulation::ifp_lifetime.reset(); } #ifdef OPENMC_MPI @@ -50,68 +42,12 @@ void broadcast_ifp_n_generation(int& n_generation, const vector>& lifetimes) { if (mpi::rank == 0) { - if (settings::ifp_delayed_group_on) { - n_generation = static_cast(delayed_groups[0].size()); - } else { - n_generation = static_cast(lifetimes[0].size()); - } + n_generation = simulation::ifp_delayed_group.enabled() + ? static_cast(delayed_groups[0].size()) + : static_cast(lifetimes[0].size()); } MPI_Bcast(&n_generation, 1, MPI_INT, 0, mpi::intracomm); } - -void copy_partial_ifp_data_to_source_banks(int64_t idx, int n, int64_t i_bank, - const vector>& delayed_groups, - const vector>& lifetimes) -{ - if (settings::ifp_delayed_group_on) { - std::copy(&delayed_groups[idx], &delayed_groups[idx + n], - &simulation::ifp_source_delayed_group_bank[i_bank]); - } - if (settings::ifp_lifetime_on) { - std::copy(&lifetimes[idx], &lifetimes[idx + n], - &simulation::ifp_source_lifetime_bank[i_bank]); - } -} #endif -void copy_complete_ifp_data_to_source_banks( - const vector>& delayed_groups, - const vector>& lifetimes) -{ - if (settings::ifp_delayed_group_on) { - std::copy(delayed_groups.data(), - delayed_groups.data() + settings::n_particles, - simulation::ifp_source_delayed_group_bank.begin()); - } - if (settings::ifp_lifetime_on) { - std::copy(lifetimes.data(), lifetimes.data() + settings::n_particles, - simulation::ifp_source_lifetime_bank.begin()); - } -} - -void allocate_temporary_vector_ifp( - vector>& delayed_groups, vector>& lifetimes) -{ - if (settings::ifp_delayed_group_on) { - delayed_groups.resize(simulation::fission_bank.size()); - } - if (settings::ifp_lifetime_on) { - lifetimes.resize(simulation::fission_bank.size()); - } -} - -void copy_ifp_data_to_fission_banks(const vector* const delayed_groups_ptr, - const vector* lifetimes_ptr) -{ - if (settings::ifp_delayed_group_on) { - std::copy(delayed_groups_ptr, - delayed_groups_ptr + simulation::fission_bank.size(), - simulation::ifp_fission_delayed_group_bank.data()); - } - if (settings::ifp_lifetime_on) { - std::copy(lifetimes_ptr, lifetimes_ptr + simulation::fission_bank.size(), - simulation::ifp_fission_lifetime_bank.data()); - } -} - } // namespace openmc diff --git a/src/physics.cpp b/src/physics.cpp index 71dae94b590..7ab8090dcdc 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -250,7 +250,7 @@ void create_fission_sites(Particle& p, int i_nuclide, const Reaction& rx) break; } // Iterated Fission Probability (IFP) method - if (settings::ifp_on()) { + if (ifp_on()) { ifp(p, idx); } } else { diff --git a/src/settings.cpp b/src/settings.cpp index ddf7407731b..68a768d534b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -55,8 +55,6 @@ bool create_fission_neutrons {true}; bool delayed_photon_scaling {true}; bool entropy_on {false}; bool event_based {false}; -bool ifp_delayed_group_on {false}; -bool ifp_lifetime_on {false}; bool legendre_to_tabular {true}; bool material_cell_offsets {true}; bool output_summary {true}; @@ -1351,8 +1349,6 @@ void free_memory_settings() settings::sourcepoint_batch.clear(); settings::source_write_surf_id.clear(); settings::res_scat_nuclides.clear(); - settings::ifp_delayed_group_on = false; - settings::ifp_lifetime_on = false; } //============================================================================== diff --git a/src/simulation.cpp b/src/simulation.cpp index 50bd8fe8caa..9ba66434ce2 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -459,7 +459,7 @@ void allocate_banks() init_fission_bank(3 * simulation::work_per_rank); // Allocate IFP bank - if (settings::ifp_on()) { + if (ifp_on()) { resize_simulation_ifp_banks(); } } diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index a7703269227..7512424e839 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -7,6 +7,7 @@ #include "openmc/container_util.h" #include "openmc/error.h" #include "openmc/file_utils.h" +#include "openmc/ifp.h" #include "openmc/mesh.h" #include "openmc/message_passing.h" #include "openmc/mgxs_interface.h" @@ -211,7 +212,7 @@ Tally::Tally(pugi::xml_node node) if (wants_lifetime || wants_delayed_group) { // Validate once, when the first IFP tally is encountered - if (!settings::ifp_on()) { + if (!ifp_on()) { if (settings::run_mode == RunMode::FIXED_SOURCE) { fatal_error( "Iterated Fission Probability can only be used in an eigenvalue " @@ -234,8 +235,10 @@ Tally::Tally(pugi::xml_node node) // Only enable in eigenvalue mode; fixed source has already errored above if (settings::run_mode == RunMode::EIGENVALUE) { - settings::ifp_lifetime_on |= wants_lifetime; - settings::ifp_delayed_group_on |= wants_delayed_group; + if (wants_lifetime) + simulation::ifp_lifetime.enable(); + if (wants_delayed_group) + simulation::ifp_delayed_group.enable(); } } diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 27e6671833a..95dfa39ffe4 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -941,11 +941,11 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, break; case SCORE_IFP_TIME_NUM: - if (settings::ifp_on()) { + if (ifp_on()) { if (p.type().is_neutron() && p.fission()) { - if (settings::ifp_lifetime_on) { + if (simulation::ifp_lifetime.enabled()) { const auto& lifetimes = - simulation::ifp_source_lifetime_bank[p.current_work()]; + simulation::ifp_lifetime.source_bank()[p.current_work()]; if (lifetimes.size() == settings::ifp_n_generation) { score = lifetimes[0] * p.wgt_last(); } @@ -955,11 +955,11 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, break; case SCORE_IFP_BETA_NUM: - if (settings::ifp_on()) { + if (ifp_on()) { if (p.type().is_neutron() && p.fission()) { - if (settings::ifp_delayed_group_on) { + if (simulation::ifp_delayed_group.enabled()) { const auto& delayed_groups = - simulation::ifp_source_delayed_group_bank[p.current_work()]; + simulation::ifp_delayed_group.source_bank()[p.current_work()]; if (delayed_groups.size() == settings::ifp_n_generation) { if (delayed_groups[0] > 0) { score = p.wgt_last(); @@ -985,16 +985,16 @@ void score_general_ce_nonanalog(Particle& p, int i_tally, int start_index, break; case SCORE_IFP_DENOM: - if (settings::ifp_on()) { + if (ifp_on()) { if (p.type().is_neutron() && p.fission()) { int ifp_data_size; - if (settings::ifp_delayed_group_on) { + if (simulation::ifp_delayed_group.enabled()) { ifp_data_size = static_cast( - simulation::ifp_source_delayed_group_bank[p.current_work()] + simulation::ifp_delayed_group.source_bank()[p.current_work()] .size()); } else { ifp_data_size = static_cast( - simulation::ifp_source_lifetime_bank[p.current_work()].size()); + simulation::ifp_lifetime.source_bank()[p.current_work()].size()); } if (ifp_data_size == settings::ifp_n_generation) { score = p.wgt_last(); From c62f989ff73e5e3c70d916e823fa87069ae29988 Mon Sep 17 00:00:00 2001 From: GuySten Date: Thu, 3 Sep 2026 00:14:35 +0300 Subject: [PATCH 2/2] estimator guard --- src/tallies/tally.cpp | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 7512424e839..1f655674b2e 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -694,13 +694,21 @@ void Tally::set_scores(const vector& scores) } } - // Make sure all scores are compatible with multigroup mode. + // Make sure all scores are compatible with multigroup mode. IFP scores have + // no case in score_general_mg, so without an explicit check they reach its + // default block and tally zero with no indication that they were not scored. if (!settings::run_CE) { - for (auto sc : scores_) - if (sc > 0) + for (auto sc : scores_) { + if (sc > 0) { fatal_error("Cannot tally " + reaction_name(sc) + " reaction rate " "in multi-group mode"); + } else if (sc == SCORE_IFP_TIME_NUM || sc == SCORE_IFP_BETA_NUM || + sc == SCORE_IFP_DENOM) { + fatal_error( + "Cannot tally " + reaction_name(sc) + " in multi-group mode"); + } + } } // Make sure mesh surface tallies contain only current score. @@ -1165,6 +1173,22 @@ void add_to_time_grid(vector grid) model::time_grid.swap(merged); } +//! Check that a tally carrying an IFP score can estimate it. +// +//! IFP scores are only implemented in score_general_ce_nonanalog, so they need +//! a collision estimator. set_scores selects one, but that choice is not final +//! while scores are being processed: other scores in the same tally can change +//! it, and an explicit estimator element is read afterwards. Without this check +//! an analog estimator reaches score_general_ce_analog, which has no case for +//! these scores, and the tally silently reports zero. +void validate_ifp_tally(const Tally& tally) +{ + if (tally.estimator_ != TallyEstimator::COLLISION) + fatal_error(fmt::format("IFP scores can only be tallied with a collision " + "estimator, but tally {} does not use one.", + tally.id())); +} + void setup_active_tallies() { model::active_tallies.clear(); @@ -1182,6 +1206,13 @@ void setup_active_tallies() if (tally.active_) { model::active_tallies.push_back(i); + for (auto score : tally.scores_) { + if (score == SCORE_IFP_TIME_NUM || score == SCORE_IFP_BETA_NUM || + score == SCORE_IFP_DENOM) { + validate_ifp_tally(tally); + break; + } + } bool mesh_present = (tally.get_filter() || tally.get_filter()); auto time_filter = tally.get_filter();