Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0676ba9
Implemented ray tracing volume estimator in addition to the existent …
vitmog Oct 28, 2025
ca95813
Added "max_iterations" volume calculation parameter (#1702)
vitmog Oct 29, 2025
befb1ae
Fixed an error in volume calculation test geometry
vitmog Oct 30, 2025
d2fc937
Switched some volume test calculations to use the ray tracing estimator
vitmog Oct 30, 2025
9dc66e1
Added description of ray tracing volume calculation method
vitmog Nov 1, 2025
5d3f874
Small changes in variables and arguments declarations
vitmog Nov 14, 2025
763573d
Replaced biderectional ray tracing in volume calculation by unidirect…
vitmog Nov 16, 2025
42b31c3
Clarified comments
vitmog Dec 1, 2025
2329ade
Transferred MPI struct initialization and finalization calls into the…
vitmog Dec 1, 2025
52ba1e3
Avoid division by 0 in get_box_chord() and small changes
vitmog Jan 12, 2026
668d61a
Small decorative changes
vitmog Jan 14, 2026
24de76f
Removed the ray tracing track-length estimator part of Volume Calcula…
vitmog Jan 14, 2026
1109614
Added comments for reviewer about relative changes
vitmog Jan 14, 2026
6dea7cf
Merge branch 'develop' into vol-calc
GuySten Jan 15, 2026
81036cf
Align Volume Calculation MPI-related implementation with the rest of …
vitmog Jan 17, 2026
e0d6fcd
Fix wrong MPI volume tally type declaration
vitmog Jan 18, 2026
5e2f3d6
Fix MPI-struct error made as removing the ray tracing vol. estimator
vitmog Jan 18, 2026
fc09f88
remove redundant mpi_ in mpi datatypes to match code style
GuySten Jan 18, 2026
8a0f486
remove uneeded includes
GuySten Jan 18, 2026
40b01bc
another simplification
GuySten Jan 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/openmc/message_passing.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ extern bool master;
#ifdef OPENMC_MPI
extern MPI_Datatype source_site;
extern MPI_Datatype collision_track_site;
extern MPI_Datatype volume_results;
extern MPI_Datatype volume_tally;
extern MPI_Comm intracomm;
#endif

Expand Down
187 changes: 134 additions & 53 deletions include/openmc/volume_calc.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@
#include <vector>

#include "openmc/array.h"
#include "openmc/cell.h"
#include "openmc/openmp_interface.h"
#include "openmc/position.h"
#include "openmc/tallies/trigger.h"
#include "openmc/timer.h"
#include "openmc/vector.h"

#include "pugixml.hpp"
#include "xtensor/xtensor.hpp"
#ifdef _OPENMP
#include <omp.h>
#endif

namespace openmc {

Expand All @@ -28,57 +27,168 @@ class VolumeCalculation {

public:
// Aliases, types
struct Result {
array<double, 2> volume; //!< Mean/standard deviation of volume
struct NuclResult {
vector<int> nuclides; //!< Index of nuclides
vector<double> atoms; //!< Number of atoms for each nuclide
vector<double> uncertainty; //!< Uncertainty on number of atoms
int iterations; //!< Number of iterations needed to obtain the results
}; // Results for a single domain
}; // Results of nuclides calculation for a single domain

//! \brief Tally corresponding to a single material (AoS)
struct VolTally {
double score {0.0}; //!< Current batch scores accumulator
array<double, 2> score_acc {}; //!< Scores and squared scores accumulator
int32_t index {0}; //!< Material ID

VolTally() = default;
inline VolTally(int i, double s = 0.0)
{
index = i;
score = s;
}

//! \brief Add batch scores means to a tally
//! \param[in] batch_size_1 Inversed batch size
inline void finalize_batch(const double batch_size_1);

//! \brief Pass counters data from a given tally to this
//! \param[in] vol_tally Data source
inline void assign_tally(const VolTally& vol_tally);

//! \brief Add counters data from a given tally to this
//! \param[in] vol_tally Data source
inline void append_tally(const VolTally& vol_tally);

//! \brief Determines given trigger condition satisfaction for this tally
//
//! \param[in] trigger_type Type of trigger condition
//! \param[in] threshold Value for trigger condition (either volume
//! fraction variance or squared rel. err. dependent on the trigger type)
//! \param[in] n_samples Statistics size
//! \return True if the trigger condition is satisfied
inline bool trigger_state(const TriggerMetric trigger_type,
const double threshold, const size_t& n_samples) const;
};

//! \brief Online results of calculation specific for each thread
struct CalcResults {
uint64_t n_samples; //!< Number of samples
int iterations; //!< Number of iterations needed to obtain the results
double cost; //!< Product of spent time and number of threads/processes
Timer sampling_time; // Timer for measurment of the simulation
vector<vector<VolumeCalculation::VolTally>>
vol_tallies; //!< Volume tallies for each domain
vector<VolumeCalculation::NuclResult>
nuc_results; //!< Nuclides of each domain

CalcResults(const VolumeCalculation& vol_calc);
CalcResults() = default;

//! \brief Reset all counters
void reset();

//! \brief Append another counters to this
void append(const CalcResults& other);

#ifdef OPENMC_MPI
//! \brief Collects results from all MPI processes to this
void collect_MPI();
#endif
};

// Constructors
VolumeCalculation(pugi::xml_node node);

VolumeCalculation() = default;

// Methods

//! \brief Stochastically determine the volume of a set of domains along with
//! the
//! average number densities of nuclides within the domain
//! the average number densities of nuclides within the domain
//
//! \param[in,out] results Results of calculation entity for filling
//! \return Vector of results for each user-specified domain
vector<Result> execute() const;
void execute(CalcResults& results) const;

//! \brief Rejection estimator
inline void score_hit(const Particle& p, CalcResults& results) const;

//! \brief Check whether a material has already been hit for a given domain.
//! If not, add new entries to the vectors
//
//! \param[in] i_material Index in global materials vector
//! \param[in] contrib Scoring value
//! \param[in,out] vol_tallies Vector of tallies corresponding to each
//! material
void check_hit(const int32_t i_material, const double contrib,
vector<VolTally>& vol_tallies) const;

//! \brief Reduce vector of volumetric tallies from each thread to a single
//! copy
//
//! \param[in] local_results Results specific to each thread
//! \param[out] results Reduced results
void reduce_results(
const CalcResults& local_results, CalcResults& results) const;

//! \brief Print volume calculation results
//
//! \param[in] results Full volume calculation results
void show_results(const CalcResults& results) const;

//! \brief Prints a statistics parameter
//
//! \param[in] label Name of parameter
//! \param[in] units Units of measure
//! \param[in] value Value of parameter
void show_vol_stat(
const std::string label, const std::string units, const double value) const;

//! \brief Prints volume result for a domain
//
//! \param[in] domain_type Either material or cell, ect.
//! \param[in] domain_id Number of this domain
//! \param[in] region_name Domain description
//! \param[in] mean Center of confidence interval
//! \param[in] stddev Half-width of confidence interval
void show_volume(const std::string domain_type, const int domain_id,
const std::string region_name, const double mean,
const double stddev) const;

//! \brief Write volume calculation results to HDF5 file
//
//! \param[in] filename Path to HDF5 file to write
//! \param[in] results Vector of results for each domain
void to_hdf5(
const std::string& filename, const vector<Result>& results) const;
//! \param[in] results Results entity
void to_hdf5(const std::string& filename, const CalcResults& results) const;

// Tally filter and map types
enum class TallyDomain { UNIVERSE, MATERIAL, CELL };

// Data members
TallyDomain domain_type_; //!< Type of domain (cell, material, etc.)
size_t n_samples_; //!< Number of samples to use
double threshold_ {-1.0}; //!< Error threshold for domain volumes
TallyDomain domain_type_; //!< Type of domain (cell, material, etc.)
size_t n_samples_; //!< Number of samples to use
double volume_sample_; //!< Volume of bounding primitive
double threshold_ {-1.0}; //!< Error threshold for domain volumes
double threshold_cnd_; //!< Pre-computed value for trigger condition
int max_iterations_ {INT_MAX}; //!< Limit of iterations number (necessary
//!< maximum value of data type by default)
TriggerMetric trigger_type_ {
TriggerMetric::not_active}; //!< Trigger metric for the volume calculation
Position lower_left_; //!< Lower-left position of bounding box
Position upper_right_; //!< Upper-right position of bounding box
vector<int> domain_ids_; //!< IDs of domains to find volumes of

private:
//! \brief Check whether a material has already been hit for a given domain.
//! If not, add new entries to the vectors
constexpr static int _INDEX_TOTAL =
-999; //!< Index of zero-element tally for entire domain totals should be
//!< out of material ID range

//! \brief Computes estimated mean and std.dev. for a tally
//
//! \param[in] i_material Index in global materials vector
//! \param[in,out] indices Vector of material indices
//! \param[in,out] hits Number of hits corresponding to each material
void check_hit(
int i_material, vector<uint64_t>& indices, vector<uint64_t>& hits) const;
//! \param[in] n_samples Statistic's size
//! \param[in] coeff_norm Normalization coefficient to multiply
//! \param[in] vol_tally Tally
//! \return Array of mean and stddev
array<double, 2> get_tally_results(const size_t& n_samples,
const double coeff_norm, const VolTally& vol_tally) const;
};

//==============================================================================
Expand All @@ -93,35 +203,6 @@ extern vector<VolumeCalculation> volume_calcs;
// Non-member functions
//==============================================================================

//! Reduce vector of indices and hits from each thread to a single copy
//
//! \param[in] local_indices Indices specific to each thread
//! \param[in] local_hits Hit count specific to each thread
//! \param[out] indices Reduced vector of indices
//! \param[out] hits Reduced vector of hits
template<typename T, typename T2>
void reduce_indices_hits(const vector<T>& local_indices,
const vector<T2>& local_hits, vector<T>& indices, vector<T2>& hits)
{
const int n_threads = num_threads();

#pragma omp for ordered schedule(static, 1)
for (int i = 0; i < n_threads; ++i) {
#pragma omp ordered
for (int j = 0; j < local_indices.size(); ++j) {
// Check if this material has been added to the master list and if
// so, accumulate the number of hits
auto it = std::find(indices.begin(), indices.end(), local_indices[j]);
if (it == indices.end()) {
indices.push_back(local_indices[j]);
hits.push_back(local_hits[j]);
} else {
hits[it - indices.begin()] += local_hits[j];
}
}
}
}

void free_memory_volume();

} // namespace openmc
Expand Down
31 changes: 28 additions & 3 deletions openmc/volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,19 @@ class VolumeCalculation:
Number of iterations over samples (for calculations with a trigger).

.. versionadded:: 0.12
max_iterations : int
Limit of the maximal allowed iterations number (optional, for
calculations with a trigger).

.. versionadded:: 0.15.x

"""
def __init__(self, domains, samples, lower_left=None, upper_right=None):
self._atoms = {}
self._volumes = {}
self._threshold = None
self._trigger_type = None
self._max_iterations = None
self._iterations = None

cv.check_type('domains', domains, Iterable,
Expand Down Expand Up @@ -187,6 +193,18 @@ def trigger_type(self, trigger_type):
('variance', 'std_dev', 'rel_err'))
self._trigger_type = trigger_type

@property
def max_iterations(self):
return self._max_iterations

@max_iterations.setter
def max_iterations(self, max_iterations):
name = 'volume calculation iterations limit'
cv.check_type(name, max_iterations, Integral, none_ok=True)
if max_iterations is not None:
cv.check_greater_than(name, max_iterations, 0)
self._max_iterations = max_iterations

@property
def iterations(self):
return self._iterations
Expand Down Expand Up @@ -230,7 +248,7 @@ def atoms_dataframe(self):

return pd.DataFrame.from_records(items, columns=columns)

def set_trigger(self, threshold, trigger_type):
def set_trigger(self, threshold, trigger_type, max_iterations=None):
"""Set a trigger on the volume calculation

.. versionadded:: 0.12
Expand All @@ -241,9 +259,12 @@ def set_trigger(self, threshold, trigger_type):
Threshold for the maximum standard deviation of volumes
trigger_type : {'variance', 'std_dev', 'rel_err'}
Value type used to halt volume calculation
max_iterations : int
Maximal allowed number of iterations (optional)
"""
self.trigger_type = trigger_type
self.threshold = threshold
self.max_iterations = max_iterations

@classmethod
def from_hdf5(cls, filename):
Expand All @@ -270,6 +291,7 @@ def from_hdf5(cls, filename):

threshold = f.attrs.get('threshold')
trigger_type = f.attrs.get('trigger_type')
max_iterations = f.attrs.get('max_iterations')
iterations = f.attrs.get('iterations', 1)

volumes = {}
Expand Down Expand Up @@ -304,7 +326,7 @@ def from_hdf5(cls, filename):
vol = cls(domains, samples, lower_left, upper_right)

if trigger_type is not None:
vol.set_trigger(threshold, trigger_type.decode())
vol.set_trigger(threshold, trigger_type.decode(), max_iterations)

vol.iterations = iterations
vol.volumes = volumes
Expand Down Expand Up @@ -355,6 +377,8 @@ def to_xml_element(self):
trigger_elem = ET.SubElement(element, "threshold")
trigger_elem.set("type", self.trigger_type)
trigger_elem.set("threshold", str(self.threshold))
if self.max_iterations is not None:
trigger_elem.set("max_iterations", str(self.max_iterations))
return element

@classmethod
Expand Down Expand Up @@ -398,6 +422,7 @@ def from_xml_element(cls, elem):
if trigger_elem is not None:
trigger_type = get_text(trigger_elem, "type")
threshold = float(get_text(trigger_elem, "threshold"))
vol.set_trigger(threshold, trigger_type)
max_iterations = Integral(get_text(trigger_elem, "max_iterations"))
vol.set_trigger(threshold, trigger_type, max_iterations)

return vol
6 changes: 6 additions & 0 deletions src/finalize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ int openmc_finalize()
if (mpi::collision_track_site != MPI_DATATYPE_NULL) {
MPI_Type_free(&mpi::collision_track_site);
}
if (mpi::volume_results != MPI_DATATYPE_NULL) {
MPI_Type_free(&mpi::volume_results);
}
if (mpi::volume_tally != MPI_DATATYPE_NULL) {
MPI_Type_free(&mpi::volume_tally);
}
#endif

openmc_reset_random_ray();
Expand Down
Loading
Loading