From 282ba43eb5d41b6155a538c0155c9779cfceeb63 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 15 Dec 2025 10:58:47 -0600 Subject: [PATCH 01/91] First prototype for temperature field --- CMakeLists.txt | 1 + include/openmc/field.h | 30 ++++++++++++ include/openmc/mesh.h | 16 +++++++ include/openmc/particle.h | 3 +- include/openmc/settings.h | 1 + include/openmc/simulation.h | 3 ++ openmc/__init__.py | 1 + openmc/field.py | 58 +++++++++++++++++++++++ openmc/settings.py | 49 +++++++++++++++++++ src/field.cpp | 50 ++++++++++++++++++++ src/finalize.cpp | 3 ++ src/geometry_aux.cpp | 13 ++++++ src/mesh.cpp | 52 +++++++++++++++++++++ src/particle.cpp | 93 ++++++++++++++++++++++++++++++++++--- src/settings.cpp | 39 ++++++++++++++++ src/simulation.cpp | 50 ++++++++++++++++++-- 16 files changed, 450 insertions(+), 12 deletions(-) create mode 100644 include/openmc/field.h create mode 100644 openmc/field.py create mode 100644 src/field.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 87b8789d101..7f53af98d0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,6 +350,7 @@ list(APPEND libopenmc_SOURCES src/endf.cpp src/error.cpp src/event.cpp + src/field.cpp src/file_utils.cpp src/finalize.cpp src/geometry.cpp diff --git a/include/openmc/field.h b/include/openmc/field.h new file mode 100644 index 00000000000..ad64c419f08 --- /dev/null +++ b/include/openmc/field.h @@ -0,0 +1,30 @@ +#ifndef OPENMC_FIELD_H +#define OPENMC_FIELD_H + +#include "openmc/vector.h" +#include "openmc/mesh.h" + +namespace openmc { + +class TemperatureField{ + +public: + Mesh* mesh_ptr; + vector values; + + TemperatureField(){}; + + TemperatureField(Mesh* mesh_ptr, vector values); + + double distance_to_next_cell(Position r, Direction d); + + double get_temperature(Position r); + + double get_sqrtkT(Position r); + + // GeometryState, particle attribute: temperature mesh bin + +}; + +} // namespace openmc +#endif // OPENMC_FIELD_H diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 5c9272e93b9..73722b2d4af 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -173,6 +173,18 @@ class Mesh { virtual void surface_bins_crossed( Position r0, Position r1, const Direction& u, vector& bins) const = 0; + //! Distance to the next boundary + //! If the initial position is outside the mesh, the distance + //! will be from the initial position to the external boundary + //! of the mesh if hit. If the initial position is inside the + //! mesh, the distance will be from the initial position to + //! the boundary of the next cell. + // + //! \param[in] r Position of the particle + //! \param[in] u Particle direction + //! \return Distance to the next boundary + virtual double distance_to_next_boundary(Position r, Position u) const = 0; + //! Get bin at a given position in space // //! \param[in] r Position to get bin for @@ -302,6 +314,8 @@ class StructuredMesh : public Mesh { void surface_bins_crossed(Position r0, Position r1, const Direction& u, vector& bins) const override; + double distance_to_next_boundary(Position r, Position u) const override; + //! Determine which cell or surface bins were crossed by a particle // //! \param[in] r0 Previous position of the particle @@ -684,6 +698,8 @@ class UnstructuredMesh : public Mesh { UnstructuredMesh(pugi::xml_node node); UnstructuredMesh(hid_t group); + double distance_to_next_boundary(Position r, Position u) const override; + static const std::string mesh_type; virtual std::string get_mesh_type() const override; diff --git a/include/openmc/particle.h b/include/openmc/particle.h index 0f37719b94f..2d97cf92c11 100644 --- a/include/openmc/particle.h +++ b/include/openmc/particle.h @@ -66,11 +66,12 @@ class Particle : public ParticleData { // Coarse-grained particle events void event_calculate_xs(); - void event_advance(); + int event_advance(); void event_cross_surface(); void event_collide(); void event_revive_from_secondary(); void event_death(); + void event_cross_temperature_mesh(); //! pulse-height recording void pht_collision_energy(); diff --git a/include/openmc/settings.h b/include/openmc/settings.h index b369c99fef8..6f9979d9e42 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -70,6 +70,7 @@ 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 temperature_field_on ; //!< Is there a temperature field defined? extern bool ifp_on; //!< Use IFP for kinetics parameters? extern bool legendre_to_tabular; //!< convert Legendre distributions to tabular? extern bool material_cell_offsets; //!< create material cells offsets? diff --git a/include/openmc/simulation.h b/include/openmc/simulation.h index 9a6cf1b2131..0bfe2e40308 100644 --- a/include/openmc/simulation.h +++ b/include/openmc/simulation.h @@ -4,6 +4,7 @@ #ifndef OPENMC_SIMULATION_H #define OPENMC_SIMULATION_H +#include "openmc/field.h" #include "openmc/mesh.h" #include "openmc/particle.h" #include "openmc/vector.h" @@ -46,6 +47,8 @@ extern int64_t work_per_rank; //!< number of particles per MPI rank extern const RegularMesh* entropy_mesh; extern const RegularMesh* ufs_mesh; +extern TemperatureField temperature_field; + extern vector k_generation; extern vector work_index; diff --git a/openmc/__init__.py b/openmc/__init__.py index bb972b4e6ad..85cfdc5fb3b 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -19,6 +19,7 @@ from openmc.source import * from openmc.settings import * from openmc.lattice import * +from openmc.field import * from openmc.filter import * from openmc.filter_expansion import * from openmc.trigger import * diff --git a/openmc/field.py b/openmc/field.py new file mode 100644 index 00000000000..eb5fc951082 --- /dev/null +++ b/openmc/field.py @@ -0,0 +1,58 @@ +from abc import ABC, abstractmethod + +import openmc + +from .mixin import IDManagerMixin + + +class ScalarField(IDManagerMixin, ABC): + """ + """ + def to_xml_element(self): + """Return XML representation of the field + + Returns + ------- + element : lxml.etree._Element + XML element containing mesh data + + """ + elem = ET.Element("field") + + elem.set("id", str(self._id)) + if self.name: + elem.set("name", self.name) + + return elem + + +class TemperatureField(ScalarField): + """ + """ + def __init__(self, mesh, values): + """ + """ + self.mesh = mesh + self.values = values + + @classmethod + def from_mesh_and_values(cls, mesh, values): + """ + """ + return cls(mesh, values) + + def to_xml_element(self): + """Return XML representation of the mesh + + Returns + ------- + element : lxml.etree._Element + XML element containing mesh data + + """ + element = super().to_xml_element() + + subelement = ET.SubElement(element, "mesh") + subelement.text = self.mesh.to_xml_element() + + return element diff --git a/openmc/settings.py b/openmc/settings.py index 43c1fe06982..ab121980f54 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -14,6 +14,7 @@ from ._xml import clean_indentation, get_elem_list, get_text from .mesh import _read_meshes, RegularMesh, MeshBase from .source import SourceBase, MeshSource, IndependentSource +from .field import TemperatureField from .utility_funcs import input_path from .volume import VolumeCalculation from .weight_windows import WeightWindows, WeightWindowGenerator, WeightWindowsList @@ -400,6 +401,9 @@ def __init__(self, **kwargs): # Shannon entropy mesh self._entropy_mesh = None + # Temperature field + self._temperature_field = None + # Trigger subelement self._trigger_active = None self._trigger_max_batches = None @@ -712,6 +716,15 @@ def entropy_mesh(self) -> RegularMesh: def entropy_mesh(self, entropy: RegularMesh): cv.check_type('entropy mesh', entropy, RegularMesh) self._entropy_mesh = entropy + + @property + def temperature_field(self) -> TemperatureField: + return self._temperature_field + + @temperature_field.setter + def temperature_field(self, temperature_field: TemperatureField): + cv.check_type('temperature field', temperature_field, TemperatureField) + self._temperature_field = temperature_field @property def trigger_active(self) -> bool: @@ -1650,6 +1663,31 @@ def _create_entropy_mesh_subelement(self, root, mesh_memo=None): if mesh_memo is not None: mesh_memo.add(self.entropy_mesh.id) + def _create_temperature_field_subelement(self, root, mesh_memo=None): + if self.temperature_field is None: + return + + # add mesh ID to this element + element = ET.SubElement(root, "temperature_field") + subelement = ET.SubElement(element, "mesh") + subelement.text = str(self.temperature_field.mesh.id) + + # If this mesh has already been written outside the + # settings element, skip writing it again + if mesh_memo and self.temperature_field.mesh.id in mesh_memo: + return + + # See if a element already exists -- if not, add it + path = f"./mesh[@id='{self.temperature_field.mesh.id}']" + if root.find(path) is None: + root.append(self.temperature_field.mesh.to_xml_element()) + if mesh_memo is not None: + mesh_memo.add(self.temperature_field.mesh.id) + + # Add temperature values + subelement = ET.SubElement(element, "values") + subelement.text = " ".join([str(i) for i in self.temperature_field.values]) + def _create_trigger_subelement(self, root): if self._trigger_active is not None: trigger_element = ET.SubElement(root, "trigger") @@ -2135,6 +2173,15 @@ def _entropy_mesh_from_xml_element(self, root, meshes): raise ValueError(f'Could not locate mesh with ID "{mesh_id}"') self.entropy_mesh = meshes[mesh_id] + def _temperature_field_from_xml_element(self, root, meshes): + text = get_text(root, 'temperature_field') + if text is None: + return + mesh_id = int(text) + if mesh_id not in meshes: + raise ValueError(f'Could not locate mesh with ID "{mesh_id}"') + self.temperature_field.mesh = meshes[mesh_id] + def _trigger_from_xml_element(self, root): elem = root.find('trigger') if elem is not None: @@ -2413,6 +2460,7 @@ def to_xml_element(self, mesh_memo=None): self._create_survival_biasing_subelement(element) self._create_cutoff_subelement(element) self._create_entropy_mesh_subelement(element, mesh_memo) + self._create_temperature_field_subelement(element, mesh_memo) self._create_trigger_subelement(element) self._create_no_reduce_subelement(element) self._create_verbosity_subelement(element) @@ -2527,6 +2575,7 @@ def from_xml_element(cls, elem, meshes=None): settings._survival_biasing_from_xml_element(elem) settings._cutoff_from_xml_element(elem) settings._entropy_mesh_from_xml_element(elem, meshes) + settings._temperature_field_from_xml_element(elem, meshes) settings._trigger_from_xml_element(elem) settings._no_reduce_from_xml_element(elem) settings._verbosity_from_xml_element(elem) diff --git a/src/field.cpp b/src/field.cpp new file mode 100644 index 00000000000..49080c137c7 --- /dev/null +++ b/src/field.cpp @@ -0,0 +1,50 @@ +#include "openmc/field.h" +#include "openmc/mesh.h" +#include "openmc/vector.h" + +namespace openmc { + +TemperatureField::TemperatureField(Mesh* mesh_ptr, vector values) +{ + this->mesh_ptr = mesh_ptr; + for (double v: values) { + this->values.push_back(v); + } +} + +double TemperatureField::distance_to_next_cell(Position r, Direction u) +{ + if (this->mesh_ptr != nullptr) { + return this->mesh_ptr->distance_to_next_boundary(r, u); + } + fatal_error("TODO"); +} + +double TemperatureField::get_temperature(Position r) +{ + // Get bin from position + int i = this->mesh_ptr->get_bin(r); + + // If we have a bin then use it to locate the value + if (i >= 0 && i < this->values.size()) { + // Return the temperature + return this->values[i]; + } + + // Return -1.0 if no values were found (probably outside of the mesh) + return -1.0; +} + +double TemperatureField::get_sqrtkT(Position r) +{ + double temperature = this->get_temperature(r); + if (temperature >= 0) { + return sqrt(temperature * K_BOLTZMANN); + } else { + //TODO + fatal_error(""); + return temperature; + } +} + +} // namespace openmc diff --git a/src/finalize.cpp b/src/finalize.cpp index 9ee94340997..b9c000528d7 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -131,6 +131,7 @@ int openmc_finalize() settings::ssw_max_particles = 0; settings::ssw_max_files = 1; settings::survival_biasing = false; + settings::temperature_field_on = false; settings::temperature_default = 293.6; settings::temperature_method = TemperatureMethod::NEAREST; settings::temperature_multipole = false; @@ -158,6 +159,8 @@ int openmc_finalize() simulation::entropy_mesh = nullptr; simulation::ufs_mesh = nullptr; + simulation::temperature_field; + data::energy_max = {INFTY, INFTY, INFTY, INFTY}; data::energy_min = {0.0, 0.0, 0.0, 0.0}; data::temperature_min = 0.0; diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 8a145fb1f1e..1c07ff7c745 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -18,6 +18,7 @@ #include "openmc/material.h" #include "openmc/settings.h" #include "openmc/surface.h" +#include "openmc/simulation.h" #include "openmc/tallies/filter.h" #include "openmc/tallies/filter_cell_instance.h" #include "openmc/tallies/filter_distribcell.h" @@ -261,6 +262,18 @@ void get_temperatures( } } } + + // Add temperature if temperature field: + if (settings::temperature_field_on) { + for (auto t: simulation::temperature_field.values) { + for (size_t i = 0; i < nuc_temps.size() ; i++){ + if (!contains(nuc_temps[i], t)) { + nuc_temps[i].push_back(t); + } + } + } + } + // TODO: thermal scattering data } //============================================================================== diff --git a/src/mesh.cpp b/src/mesh.cpp index 610d057cf08..b51a6e2484c 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -720,6 +720,12 @@ UnstructuredMesh::UnstructuredMesh(hid_t group) : Mesh(group) } } +double UnstructuredMesh::distance_to_next_boundary(Position r, Position u) const +{ + fatal_error("Not implemented"); + return -1.0; +} + void UnstructuredMesh::determine_bounds() { double xmin = INFTY; @@ -1175,6 +1181,52 @@ void StructuredMesh::surface_bins_crossed( raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins)); } +double StructuredMesh::distance_to_next_boundary(Position r, Position u) const +{ + double distance = -1.0; + + // Algo adapted from mesh.cpp + Position global_r = r; + Position local_r = local_coords(r); + + const int n = 3; + + bool in_mesh; + + StructuredMesh::MeshIndex ijk = get_indices(global_r + TINY_BIT * u, in_mesh); + + // Calculate initial distances to next surfaces in all three dimensions + std::array distances; + for (int k = 0; k < n; ++k) { + distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0); + } + + if (in_mesh) { + + // find surface with minimal distance to current position + const auto k = std::min_element(distances.begin(), distances.end()) - + distances.begin(); + + distance = distances[k].distance; + + } else { // not inside mesh + + // For all directions outside the mesh, find the distance that we need + // to travel to reach the next surface. Use the largest distance, as + // only this will cross all outer surfaces. + int k_max {-1}; + for (int k = 0; k < n; ++k) { + if ((ijk[k] < 1 || ijk[k] > shape_[k]) && + (distances[k].distance > distance)) { + distance = distances[k].distance; + k_max = k; + } + } + } + + return distance; +} + //============================================================================== // RegularMesh implementation //============================================================================== diff --git a/src/particle.cpp b/src/particle.cpp index 2d70d715e22..811110b1e12 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -193,6 +193,18 @@ void Particle::event_calculate_xs() cell_last(j) = coord(j).cell(); } n_coord_last() = n_coord(); + + // Update temperature of the particle if temperature field + if (settings::temperature_field_on) { + double temperature_field_sqrtkT = simulation::temperature_field.get_sqrtkT(r()); + // Update temperature if we are inside the mesh + if (temperature_field_sqrtkT >= 0.) { + sqrtkT() = temperature_field_sqrtkT; + } else { + //TODO + fatal_error(""); + } + } } // Write particle track. @@ -229,9 +241,11 @@ void Particle::event_calculate_xs() } } -void Particle::event_advance() +int Particle::event_advance() { - // Find the distance to the nearest boundary + int stop_condition = 0; + + // Find the distance to the nearest geometry boundary boundary() = distance_to_boundary(*this); // Sample a distance to collision @@ -243,14 +257,32 @@ void Particle::event_advance() collision_distance() = -std::log(prn(current_seed())) / macro_xs().total; } + // Find the distance to the nearest temperature mesh cell surface + double distance_tmesh = INFTY; + if (settings::temperature_field_on) { + distance_tmesh = simulation::temperature_field.distance_to_next_cell(r(), u()); + } + + // Calculate the distance corresponding to the time cutoff double speed = this->speed(); double time_cutoff = settings::time_cutoff[static_cast(type())]; double distance_cutoff = (time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY; - // Select smaller of the three distances + // Select smaller of the four distances double distance = - std::min({boundary().distance(), collision_distance(), distance_cutoff}); + std::min({boundary().distance(), collision_distance(), distance_cutoff, distance_tmesh}); + + // Prepare the stop condition + if (distance == distance_cutoff) { + stop_condition = 4; + } else if (distance == boundary().distance()) { + stop_condition = 1; + } else if (distance == distance_tmesh) { + stop_condition = 3; + } else if (distance == collision_distance()) { + stop_condition = 2; + } // Advance particle in space and time this->move_distance(distance); @@ -279,10 +311,44 @@ void Particle::event_advance() score_track_derivative(*this, distance); } + //if (p.collision_distance() > p.boundary().distance()) { + // p.event_cross_surface(); + + + + //const int CROSS_SURFACE = 1; + //const int COLLIDE = 2; + //const int CROSS_TEMPERATURE_MESH = 3; + //const int CUTOFF = 4; + // Set particle weight to zero if it hit the time boundary - if (distance == distance_cutoff) { - wgt() = 0.0; + //if (distance == distance_cutoff) { + // wgt() = 0.0; + //} + + return stop_condition; +} + +void Particle::event_cross_temperature_mesh() +{ + // Update temperature of the particle + sqrtkT_last() = sqrtkT(); + + // Find the temperture from the mesh + double temperature_field_sqrtkT = simulation::temperature_field.get_sqrtkT(r() + u() * TINY_BIT); + + // Update temperature + // If inside the mesh + if (temperature_field_sqrtkT >= 0.) { + sqrtkT() = temperature_field_sqrtkT; + // If outside the mesh + } else { + //TODO + fatal_error(""); } + + // TODO: what do we do if we are outside the mesh? + // Probably need to use the cell instance temperature } void Particle::event_cross_surface() @@ -322,6 +388,21 @@ void Particle::event_cross_surface() } event() = TallyEvent::SURFACE; } + + // Update temperature of the particle if temperature field + // This is important here just on case we both crossed a cell + // from the geometry and from the temperature mesh + if (settings::temperature_field_on) { + double temperature_field_sqrtkT = simulation::temperature_field.get_sqrtkT(r() + u() * TINY_BIT); + // Update temperature if we are inside the mesh + if (temperature_field_sqrtkT >= 0.) { + sqrtkT() = temperature_field_sqrtkT; + } else { + //TODO + fatal_error(""); + } + } + // Score cell to cell partial currents if (!model::active_surface_tallies.empty()) { score_surface_tally(*this, model::active_surface_tallies); diff --git a/src/settings.cpp b/src/settings.cpp index 9dcf7c8dbc8..82a408d942e 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -19,6 +19,7 @@ #include "openmc/distribution_spatial.h" #include "openmc/eigenvalue.h" #include "openmc/error.h" +#include "openmc/field.h" #include "openmc/file_utils.h" #include "openmc/mcpl_interface.h" #include "openmc/mesh.h" @@ -75,6 +76,7 @@ bool surf_mcpl_write {false}; bool surf_source_read {false}; bool survival_biasing {false}; bool survival_normalization {false}; +bool temperature_field_on {false}; bool temperature_multipole {false}; bool trigger_on {false}; bool trigger_predict {false}; @@ -779,6 +781,43 @@ void read_settings_xml(pugi::xml_node root) "it by specifying its ID in an element."); } } + + // Temperature field + if (check_for_node(root, "temperature_field")) { + temperature_field_on = true; + + // Get pointer to temperature_field node + auto node_tf = root.child("temperature_field"); + + // Mesh parameter + Mesh* tf_mesh_ptr; + if (check_for_node(node_tf, "mesh")) { + int temp = std::stoi(get_node_value(node_tf, "mesh")); + if (model::mesh_map.find(temp) == model::mesh_map.end()) { + fatal_error(fmt::format( + "Mesh {} specified for the temperature field does not exist.", temp)); + } + tf_mesh_ptr = model::meshes[model::mesh_map.at(temp)].get(); + } else { + fatal_error("A mesh should be given for the temperature field."); + } + + // Values parameter + vector tf_values; + if (check_for_node(node_tf, "values")) { + auto temp = get_node_array(node_tf, "values"); + for (const auto& b : temp) { + tf_values.push_back(b); + } + } else { + fatal_error("Temperature values should be given for the temperature field."); + } + + // Instantiate the temperature field + //TemperatureField tf = TemperatureField(tf_mesh_ptr, tf_values); + simulation::temperature_field = TemperatureField(tf_mesh_ptr, tf_values); + } + // Uniform fission source weighting mesh if (check_for_node(root, "ufs_mesh")) { auto temp = std::stoi(get_node_value(root, "ufs_mesh")); diff --git a/src/simulation.cpp b/src/simulation.cpp index b536ae5881f..7675cf04657 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -7,6 +7,7 @@ #include "openmc/eigenvalue.h" #include "openmc/error.h" #include "openmc/event.h" +#include "openmc/field.h" #include "openmc/geometry_aux.h" #include "openmc/ifp.h" #include "openmc/material.h" @@ -320,6 +321,8 @@ int64_t work_per_rank; const RegularMesh* entropy_mesh {nullptr}; const RegularMesh* ufs_mesh {nullptr}; +TemperatureField temperature_field; + vector k_generation; vector work_index; @@ -801,18 +804,55 @@ void free_memory_simulation() simulation::entropy.clear(); } +//void transport_history_based_single_particle(Particle& p) +//{ +// while (p.alive()) { +// p.event_calculate_xs(); +// if (p.alive()) { +// p.event_advance(); +// } +// if (p.alive()) { +// if (p.collision_distance() > p.boundary().distance()) { +// p.event_cross_surface(); +// } else if (p.alive()) { +// p.event_collide(); +// } +// } +// p.event_revive_from_secondary(); +// } +// p.event_death(); +//} + +const int CROSS_SURFACE = 1; +const int COLLIDE = 2; +const int CROSS_TEMPERATURE_MESH = 3; +const int CUTOFF = 4; + void transport_history_based_single_particle(Particle& p) { + int advance_stop_condition; while (p.alive()) { p.event_calculate_xs(); if (p.alive()) { - p.event_advance(); + advance_stop_condition = p.event_advance(); // TODO maybe use a particle attribute instead (next_event) } if (p.alive()) { - if (p.collision_distance() > p.boundary().distance()) { - p.event_cross_surface(); - } else if (p.alive()) { - p.event_collide(); + switch (advance_stop_condition) { + case CROSS_SURFACE: + p.event_cross_surface(); + break; + case COLLIDE: + p.event_collide(); + break; + case CROSS_TEMPERATURE_MESH: + p.event_cross_temperature_mesh(); + break; + case CUTOFF: + p.wgt() = 0.0; + break; + default: + fatal_error("Unknown case"); + break; } } p.event_revive_from_secondary(); From cf1970937c0c821a49a9e7258c78df1231aeaf4f Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 15 Dec 2025 11:17:08 -0600 Subject: [PATCH 02/91] Clean event identifiers for history-based transport --- include/openmc/constants.h | 8 ++++++++ src/particle.cpp | 23 ++++------------------- src/simulation.cpp | 34 +++++----------------------------- 3 files changed, 17 insertions(+), 48 deletions(-) diff --git a/include/openmc/constants.h b/include/openmc/constants.h index bba260c3a4b..15d6bf122a7 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -376,6 +376,14 @@ enum class GeometryType { CSG, DAG }; // representations. This value represents no surface. constexpr int32_t SURFACE_NONE {0}; +//============================================================================== +// EVENT IDENTIFIER IN HISTORY-BASED TRANSPORT + +const int EVENT_CROSS_SURFACE = 1; +const int EVENT_COLLIDE = 2; +const int EVENT_CROSS_TEMPERATURE_MESH = 3; +const int EVENT_TIME_CUTOFF = 4; + } // namespace openmc #endif // OPENMC_CONSTANTS_H diff --git a/src/particle.cpp b/src/particle.cpp index 811110b1e12..7d7fa3bbd38 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -275,13 +275,13 @@ int Particle::event_advance() // Prepare the stop condition if (distance == distance_cutoff) { - stop_condition = 4; + stop_condition = EVENT_TIME_CUTOFF; } else if (distance == boundary().distance()) { - stop_condition = 1; + stop_condition = EVENT_CROSS_SURFACE; } else if (distance == distance_tmesh) { - stop_condition = 3; + stop_condition = EVENT_CROSS_TEMPERATURE_MESH; } else if (distance == collision_distance()) { - stop_condition = 2; + stop_condition = EVENT_COLLIDE; } // Advance particle in space and time @@ -311,21 +311,6 @@ int Particle::event_advance() score_track_derivative(*this, distance); } - //if (p.collision_distance() > p.boundary().distance()) { - // p.event_cross_surface(); - - - - //const int CROSS_SURFACE = 1; - //const int COLLIDE = 2; - //const int CROSS_TEMPERATURE_MESH = 3; - //const int CUTOFF = 4; - - // Set particle weight to zero if it hit the time boundary - //if (distance == distance_cutoff) { - // wgt() = 0.0; - //} - return stop_condition; } diff --git a/src/simulation.cpp b/src/simulation.cpp index 7675cf04657..7201f900ec6 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -804,30 +804,6 @@ void free_memory_simulation() simulation::entropy.clear(); } -//void transport_history_based_single_particle(Particle& p) -//{ -// while (p.alive()) { -// p.event_calculate_xs(); -// if (p.alive()) { -// p.event_advance(); -// } -// if (p.alive()) { -// if (p.collision_distance() > p.boundary().distance()) { -// p.event_cross_surface(); -// } else if (p.alive()) { -// p.event_collide(); -// } -// } -// p.event_revive_from_secondary(); -// } -// p.event_death(); -//} - -const int CROSS_SURFACE = 1; -const int COLLIDE = 2; -const int CROSS_TEMPERATURE_MESH = 3; -const int CUTOFF = 4; - void transport_history_based_single_particle(Particle& p) { int advance_stop_condition; @@ -838,20 +814,20 @@ void transport_history_based_single_particle(Particle& p) } if (p.alive()) { switch (advance_stop_condition) { - case CROSS_SURFACE: + case EVENT_CROSS_SURFACE: p.event_cross_surface(); break; - case COLLIDE: + case EVENT_COLLIDE: p.event_collide(); break; - case CROSS_TEMPERATURE_MESH: + case EVENT_CROSS_TEMPERATURE_MESH: p.event_cross_temperature_mesh(); break; - case CUTOFF: + case EVENT_TIME_CUTOFF: p.wgt() = 0.0; break; default: - fatal_error("Unknown case"); + fatal_error("Unknown event in history-based transport!"); break; } } From 032275a90de12221e14cf5e4064a57e93436bedc Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 15 Dec 2025 11:43:39 -0600 Subject: [PATCH 03/91] New particle attribute for the next event --- include/openmc/particle.h | 4 ++-- include/openmc/particle_data.h | 7 +++++++ src/particle.cpp | 14 +++++--------- src/simulation.cpp | 10 +++++----- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/include/openmc/particle.h b/include/openmc/particle.h index 2d97cf92c11..8daf8290b12 100644 --- a/include/openmc/particle.h +++ b/include/openmc/particle.h @@ -66,12 +66,12 @@ class Particle : public ParticleData { // Coarse-grained particle events void event_calculate_xs(); - int event_advance(); + void event_advance(); void event_cross_surface(); + void event_cross_temperature_mesh(); void event_collide(); void event_revive_from_secondary(); void event_death(); - void event_cross_temperature_mesh(); //! pulse-height recording void pht_collision_energy(); diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index fdacfa765b3..829d503d0fa 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -565,6 +565,8 @@ class ParticleData : public GeometryState { int64_t n_progeny_ {0}; + int next_event_ {0}; + public: //---------------------------------------------------------------------------- // Constructors @@ -768,6 +770,11 @@ class ParticleData : public GeometryState { d = 0; } } + + // Next event identifier for history-based transport + int next_event() const { return next_event_; } + int& next_event() { return next_event_; } + }; } // namespace openmc diff --git a/src/particle.cpp b/src/particle.cpp index 7d7fa3bbd38..c465cae3526 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -241,10 +241,8 @@ void Particle::event_calculate_xs() } } -int Particle::event_advance() +void Particle::event_advance() { - int stop_condition = 0; - // Find the distance to the nearest geometry boundary boundary() = distance_to_boundary(*this); @@ -275,13 +273,13 @@ int Particle::event_advance() // Prepare the stop condition if (distance == distance_cutoff) { - stop_condition = EVENT_TIME_CUTOFF; + next_event() = EVENT_TIME_CUTOFF; } else if (distance == boundary().distance()) { - stop_condition = EVENT_CROSS_SURFACE; + next_event() = EVENT_CROSS_SURFACE; } else if (distance == distance_tmesh) { - stop_condition = EVENT_CROSS_TEMPERATURE_MESH; + next_event() = EVENT_CROSS_TEMPERATURE_MESH; } else if (distance == collision_distance()) { - stop_condition = EVENT_COLLIDE; + next_event() = EVENT_COLLIDE; } // Advance particle in space and time @@ -310,8 +308,6 @@ int Particle::event_advance() if (!model::active_tallies.empty()) { score_track_derivative(*this, distance); } - - return stop_condition; } void Particle::event_cross_temperature_mesh() diff --git a/src/simulation.cpp b/src/simulation.cpp index 7201f900ec6..7a1af7c2afe 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -806,14 +806,13 @@ void free_memory_simulation() void transport_history_based_single_particle(Particle& p) { - int advance_stop_condition; while (p.alive()) { p.event_calculate_xs(); if (p.alive()) { - advance_stop_condition = p.event_advance(); // TODO maybe use a particle attribute instead (next_event) + p.event_advance(); } if (p.alive()) { - switch (advance_stop_condition) { + switch (p.next_event()) { case EVENT_CROSS_SURFACE: p.event_cross_surface(); break; @@ -827,9 +826,10 @@ void transport_history_based_single_particle(Particle& p) p.wgt() = 0.0; break; default: - fatal_error("Unknown event in history-based transport!"); + fatal_error(fmt::format( + "Unknown event '{}' in history-based transport!", p.next_event())); break; - } + } } p.event_revive_from_secondary(); } From 8a138dae5332642cd2bf5fa828e3e819967ce7ef Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 15 Dec 2025 14:30:37 -0600 Subject: [PATCH 04/91] Cleaning --- include/openmc/field.h | 62 ++++++++++++++++++++++++++++++------------ src/field.cpp | 46 ++++++++++++++++++------------- src/geometry_aux.cpp | 2 +- src/particle.cpp | 39 ++++++++++++-------------- 4 files changed, 91 insertions(+), 58 deletions(-) diff --git a/include/openmc/field.h b/include/openmc/field.h index ad64c419f08..8154ec4c6d2 100644 --- a/include/openmc/field.h +++ b/include/openmc/field.h @@ -1,29 +1,57 @@ #ifndef OPENMC_FIELD_H #define OPENMC_FIELD_H -#include "openmc/vector.h" #include "openmc/mesh.h" +#include "openmc/vector.h" namespace openmc { -class TemperatureField{ +class TemperatureField { public: - Mesh* mesh_ptr; - vector values; - - TemperatureField(){}; - - TemperatureField(Mesh* mesh_ptr, vector values); - - double distance_to_next_cell(Position r, Direction d); - - double get_temperature(Position r); - - double get_sqrtkT(Position r); - - // GeometryState, particle attribute: temperature mesh bin - + //---------------------------------------------------------------------------- + // Constructors + TemperatureField() {}; + TemperatureField(Mesh* mesh_ptr, vector values); + + //---------------------------------------------------------------------------- + // Methods + + //! Returns the distance to the next mesh boundary gien a particle position + //! and direction. If the particle is initially outside, the distance will + //! correspond to the nearest distance to the outer boundaries of the mesh. + // + //! \param[in] r Position of the particle + //! \param[in] u Direction of the particle + //! \return The distance in cm to the next mesh boundary + double distance_to_next_boundary(const Position& r, const Direction& d); + + //! Returns the temperature in Kelvin corresponding to the given position. + // + //! \param[in] r Position of the particle + //! \return Temperature in Kelvin + double get_temperature(const Position& r); + + //! Returns the square root of the temperature multiplied by the Boltzmann + //! constant in eV for the given position. + // + //! \param[in] r Position of the particle + //! \return Sqrt(k_Boltzmann * temperature) in eV + double get_sqrtkT(const Position& r); + + //---------------------------------------------------------------------------- + // Accessors + + // Temperature values + double& value(int i) { return values_[i]; } + const double& value(int i) const { return values_[i]; } + const vector& values() const { return values_; } + +private: + //---------------------------------------------------------------------------- + // Data members + Mesh* mesh_ptr_; //!< Pointer to the geometric mesh + vector values_; //!< Temperature values }; } // namespace openmc diff --git a/src/field.cpp b/src/field.cpp index 49080c137c7..ee32ebf477d 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -1,47 +1,55 @@ #include "openmc/field.h" +#include "openmc/constants.h" #include "openmc/mesh.h" #include "openmc/vector.h" namespace openmc { -TemperatureField::TemperatureField(Mesh* mesh_ptr, vector values) +TemperatureField::TemperatureField(Mesh* mesh_ptr, vector values) { - this->mesh_ptr = mesh_ptr; - for (double v: values) { - this->values.push_back(v); + this->mesh_ptr_ = mesh_ptr; + for (double v : values) { + this->values_.push_back(v); } } -double TemperatureField::distance_to_next_cell(Position r, Direction u) +double TemperatureField::distance_to_next_boundary( + const Position& r, const Direction& u) { - if (this->mesh_ptr != nullptr) { - return this->mesh_ptr->distance_to_next_boundary(r, u); + if (this->mesh_ptr_ != nullptr) { + return this->mesh_ptr_->distance_to_next_boundary(r, u); } fatal_error("TODO"); } -double TemperatureField::get_temperature(Position r) +double TemperatureField::get_temperature(const Position& r) { - // Get bin from position - int i = this->mesh_ptr->get_bin(r); + if (this->mesh_ptr_ != nullptr) { - // If we have a bin then use it to locate the value - if (i >= 0 && i < this->values.size()) { - // Return the temperature - return this->values[i]; + // Get bin from position + int i = this->mesh_ptr_->get_bin(r); + + // If we have a bin then use it to locate the value + if (i >= 0 && i < this->values_.size()) { + // Return the temperature + return this->values_[i]; + } + + // Return -1.0 if no values were found (probably outside of the mesh) + return -1.0; + + } else { + fatal_error("TODO"); } - - // Return -1.0 if no values were found (probably outside of the mesh) - return -1.0; } -double TemperatureField::get_sqrtkT(Position r) +double TemperatureField::get_sqrtkT(const Position& r) { double temperature = this->get_temperature(r); if (temperature >= 0) { return sqrt(temperature * K_BOLTZMANN); } else { - //TODO + // TODO fatal_error(""); return temperature; } diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 1c07ff7c745..b0e4e959624 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -265,7 +265,7 @@ void get_temperatures( // Add temperature if temperature field: if (settings::temperature_field_on) { - for (auto t: simulation::temperature_field.values) { + for (auto t: simulation::temperature_field.values()) { for (size_t i = 0; i < nuc_temps.size() ; i++){ if (!contains(nuc_temps[i], t)) { nuc_temps[i].push_back(t); diff --git a/src/particle.cpp b/src/particle.cpp index c465cae3526..5c6932e7ff1 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -196,10 +196,10 @@ void Particle::event_calculate_xs() // Update temperature of the particle if temperature field if (settings::temperature_field_on) { - double temperature_field_sqrtkT = simulation::temperature_field.get_sqrtkT(r()); + double field_sqrtkT = simulation::temperature_field.get_sqrtkT(r()); // Update temperature if we are inside the mesh - if (temperature_field_sqrtkT >= 0.) { - sqrtkT() = temperature_field_sqrtkT; + if (field_sqrtkT >= 0.) { + sqrtkT() = field_sqrtkT; } else { //TODO fatal_error(""); @@ -258,7 +258,7 @@ void Particle::event_advance() // Find the distance to the nearest temperature mesh cell surface double distance_tmesh = INFTY; if (settings::temperature_field_on) { - distance_tmesh = simulation::temperature_field.distance_to_next_cell(r(), u()); + distance_tmesh = simulation::temperature_field.distance_to_next_boundary(r(), u()); } // Calculate the distance corresponding to the time cutoff @@ -312,24 +312,21 @@ void Particle::event_advance() void Particle::event_cross_temperature_mesh() { - // Update temperature of the particle + // Save current temperature sqrtkT_last() = sqrtkT(); - // Find the temperture from the mesh - double temperature_field_sqrtkT = simulation::temperature_field.get_sqrtkT(r() + u() * TINY_BIT); - - // Update temperature - // If inside the mesh - if (temperature_field_sqrtkT >= 0.) { - sqrtkT() = temperature_field_sqrtkT; - // If outside the mesh + // Find the field temperature from the mesh + double field_sqrtkT = + simulation::temperature_field.get_sqrtkT(r() + u() * TINY_BIT); + + // Update the particle temperature + if (field_sqrtkT >= 0.) { + sqrtkT() = field_sqrtkT; } else { - //TODO - fatal_error(""); + // TODO: If we leave the temperature mesh, we need to go back to the cell + // temperature + fatal_error("Not implemented yet"); } - - // TODO: what do we do if we are outside the mesh? - // Probably need to use the cell instance temperature } void Particle::event_cross_surface() @@ -374,10 +371,10 @@ void Particle::event_cross_surface() // This is important here just on case we both crossed a cell // from the geometry and from the temperature mesh if (settings::temperature_field_on) { - double temperature_field_sqrtkT = simulation::temperature_field.get_sqrtkT(r() + u() * TINY_BIT); + double field_sqrtkT = simulation::temperature_field.get_sqrtkT(r() + u() * TINY_BIT); // Update temperature if we are inside the mesh - if (temperature_field_sqrtkT >= 0.) { - sqrtkT() = temperature_field_sqrtkT; + if (field_sqrtkT >= 0.) { + sqrtkT() = field_sqrtkT; } else { //TODO fatal_error(""); From d29e7007956db87f9858a553d7c36efb290ce8b0 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 15 Dec 2025 15:42:44 -0600 Subject: [PATCH 05/91] Common update function for the particle temperature --- include/openmc/field.h | 8 ++++++++ src/field.cpp | 20 ++++++++++++++++++++ src/particle.cpp | 42 +++++++----------------------------------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/include/openmc/field.h b/include/openmc/field.h index 8154ec4c6d2..842886b3fd0 100644 --- a/include/openmc/field.h +++ b/include/openmc/field.h @@ -39,6 +39,14 @@ class TemperatureField { //! \return Sqrt(k_Boltzmann * temperature) in eV double get_sqrtkT(const Position& r); + //! Update the temperature of a particle based on its position and direction. + //! If the particle is inside the temperature field, its temperature is + //! updated. If outside, the particle takes the temperature value + //! associated with the current cell instance. + // + //! \param[inout] p Particle + void update_particle_temperature(Particle& p); + //---------------------------------------------------------------------------- // Accessors diff --git a/src/field.cpp b/src/field.cpp index ee32ebf477d..4a4b1c96698 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -1,4 +1,5 @@ #include "openmc/field.h" +#include "openmc/cell.h" #include "openmc/constants.h" #include "openmc/mesh.h" #include "openmc/vector.h" @@ -55,4 +56,23 @@ double TemperatureField::get_sqrtkT(const Position& r) } } +void TemperatureField::update_particle_temperature(Particle& p) { + + // Save current temperature + p.sqrtkT_last() = p.sqrtkT(); + + // Determine the temperature based on the temperature field + double field_sqrtkT = this->get_sqrtkT(p.r() + p.u() * TINY_BIT); + + // If particle inside the mesh, we use the temperature field + if (field_sqrtkT >= 0.) { + p.sqrtkT() = field_sqrtkT; + + // If particle outside the mesh, go back to the cell instance temperature + } else { + Cell& c {*model::cells[p.lowest_coord().cell()]}; + p.sqrtkT() = c.sqrtkT(p.cell_instance()); + } +} + } // namespace openmc diff --git a/src/particle.cpp b/src/particle.cpp index 5c6932e7ff1..0f68dba912e 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -196,14 +196,7 @@ void Particle::event_calculate_xs() // Update temperature of the particle if temperature field if (settings::temperature_field_on) { - double field_sqrtkT = simulation::temperature_field.get_sqrtkT(r()); - // Update temperature if we are inside the mesh - if (field_sqrtkT >= 0.) { - sqrtkT() = field_sqrtkT; - } else { - //TODO - fatal_error(""); - } + simulation::temperature_field.update_particle_temperature(*this); } } @@ -258,7 +251,8 @@ void Particle::event_advance() // Find the distance to the nearest temperature mesh cell surface double distance_tmesh = INFTY; if (settings::temperature_field_on) { - distance_tmesh = simulation::temperature_field.distance_to_next_boundary(r(), u()); + distance_tmesh = + simulation::temperature_field.distance_to_next_boundary(r(), u()); } // Calculate the distance corresponding to the time cutoff @@ -267,7 +261,7 @@ void Particle::event_advance() double distance_cutoff = (time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY; - // Select smaller of the four distances + // Select smaller distance double distance = std::min({boundary().distance(), collision_distance(), distance_cutoff, distance_tmesh}); @@ -312,21 +306,8 @@ void Particle::event_advance() void Particle::event_cross_temperature_mesh() { - // Save current temperature - sqrtkT_last() = sqrtkT(); - - // Find the field temperature from the mesh - double field_sqrtkT = - simulation::temperature_field.get_sqrtkT(r() + u() * TINY_BIT); - - // Update the particle temperature - if (field_sqrtkT >= 0.) { - sqrtkT() = field_sqrtkT; - } else { - // TODO: If we leave the temperature mesh, we need to go back to the cell - // temperature - fatal_error("Not implemented yet"); - } + // Update temperature of the particle + simulation::temperature_field.update_particle_temperature(*this); } void Particle::event_cross_surface() @@ -368,17 +349,8 @@ void Particle::event_cross_surface() } // Update temperature of the particle if temperature field - // This is important here just on case we both crossed a cell - // from the geometry and from the temperature mesh if (settings::temperature_field_on) { - double field_sqrtkT = simulation::temperature_field.get_sqrtkT(r() + u() * TINY_BIT); - // Update temperature if we are inside the mesh - if (field_sqrtkT >= 0.) { - sqrtkT() = field_sqrtkT; - } else { - //TODO - fatal_error(""); - } + simulation::temperature_field.update_particle_temperature(*this); } // Score cell to cell partial currents From 2e5e0c907c5cd35e0a907d5b2dab063b9d2f2909 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 15 Dec 2025 15:58:39 -0600 Subject: [PATCH 06/91] Cleaning --- src/field.cpp | 23 ++++++++++------------- src/settings.cpp | 4 ++-- src/simulation.cpp | 1 + 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/field.cpp b/src/field.cpp index 4a4b1c96698..dab214efcf9 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -19,8 +19,9 @@ double TemperatureField::distance_to_next_boundary( { if (this->mesh_ptr_ != nullptr) { return this->mesh_ptr_->distance_to_next_boundary(r, u); + } else { + fatal_error("No mesh found for the temperature field!"); } - fatal_error("TODO"); } double TemperatureField::get_temperature(const Position& r) @@ -30,17 +31,16 @@ double TemperatureField::get_temperature(const Position& r) // Get bin from position int i = this->mesh_ptr_->get_bin(r); - // If we have a bin then use it to locate the value + // If we have a bin, we use it to locate the value if (i >= 0 && i < this->values_.size()) { - // Return the temperature return this->values_[i]; } - // Return -1.0 if no values were found (probably outside of the mesh) + // No values were found (outside the mesh) return -1.0; } else { - fatal_error("TODO"); + fatal_error("No mesh found for the temperature field!"); } } @@ -49,26 +49,23 @@ double TemperatureField::get_sqrtkT(const Position& r) double temperature = this->get_temperature(r); if (temperature >= 0) { return sqrt(temperature * K_BOLTZMANN); - } else { - // TODO - fatal_error(""); - return temperature; } + return -1.0; } -void TemperatureField::update_particle_temperature(Particle& p) { - +void TemperatureField::update_particle_temperature(Particle& p) +{ // Save current temperature p.sqrtkT_last() = p.sqrtkT(); // Determine the temperature based on the temperature field double field_sqrtkT = this->get_sqrtkT(p.r() + p.u() * TINY_BIT); - + // If particle inside the mesh, we use the temperature field if (field_sqrtkT >= 0.) { p.sqrtkT() = field_sqrtkT; - // If particle outside the mesh, go back to the cell instance temperature + // If particle outside the mesh, go back to the cell instance temperature } else { Cell& c {*model::cells[p.lowest_coord().cell()]}; p.sqrtkT() = c.sqrtkT(p.cell_instance()); diff --git a/src/settings.cpp b/src/settings.cpp index 82a408d942e..1d4fb8d3041 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -810,11 +810,11 @@ void read_settings_xml(pugi::xml_node root) tf_values.push_back(b); } } else { - fatal_error("Temperature values should be given for the temperature field."); + fatal_error( + "Temperature values should be given for the temperature field."); } // Instantiate the temperature field - //TemperatureField tf = TemperatureField(tf_mesh_ptr, tf_values); simulation::temperature_field = TemperatureField(tf_mesh_ptr, tf_values); } diff --git a/src/simulation.cpp b/src/simulation.cpp index 7a1af7c2afe..2255522ba2d 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -3,6 +3,7 @@ #include "openmc/bank.h" #include "openmc/capi.h" #include "openmc/collision_track.h" +#include "openmc/constants.h" #include "openmc/container_util.h" #include "openmc/eigenvalue.h" #include "openmc/error.h" From ba02d466ce7595b60bdb9c66d30af46b3a25ae89 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 15 Dec 2025 16:13:08 -0600 Subject: [PATCH 07/91] Cleaning --- include/openmc/mesh.h | 6 ++---- src/mesh.cpp | 17 +++++++---------- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 73722b2d4af..43988d0049a 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -173,12 +173,10 @@ class Mesh { virtual void surface_bins_crossed( Position r0, Position r1, const Direction& u, vector& bins) const = 0; - //! Distance to the next boundary + //! Distance to the next boundary. //! If the initial position is outside the mesh, the distance //! will be from the initial position to the external boundary - //! of the mesh if hit. If the initial position is inside the - //! mesh, the distance will be from the initial position to - //! the boundary of the next cell. + //! of the mesh if hit. // //! \param[in] r Position of the particle //! \param[in] u Particle direction diff --git a/src/mesh.cpp b/src/mesh.cpp index b51a6e2484c..0c696eed220 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1183,14 +1183,11 @@ void StructuredMesh::surface_bins_crossed( double StructuredMesh::distance_to_next_boundary(Position r, Position u) const { - double distance = -1.0; - - // Algo adapted from mesh.cpp Position global_r = r; Position local_r = local_coords(r); - const int n = 3; - + double distance = INFTY; + const int n = n_dimension_; bool in_mesh; StructuredMesh::MeshIndex ijk = get_indices(global_r + TINY_BIT * u, in_mesh); @@ -1203,10 +1200,10 @@ double StructuredMesh::distance_to_next_boundary(Position r, Position u) const if (in_mesh) { - // find surface with minimal distance to current position - const auto k = std::min_element(distances.begin(), distances.end()) - - distances.begin(); - + // Find surface with minimal distance to current position + const auto k = + std::min_element(distances.begin(), distances.end()) - distances.begin(); + distance = distances[k].distance; } else { // not inside mesh @@ -1224,7 +1221,7 @@ double StructuredMesh::distance_to_next_boundary(Position r, Position u) const } } - return distance; + return distance; } //============================================================================== From 5c52c5fbb23d05e663842e76013d7118ab82532f Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 17 Dec 2025 15:56:00 -0600 Subject: [PATCH 08/91] Add temperature values during the initialization for thermal scattering data --- src/geometry_aux.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index b0e4e959624..bdb340b963f 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -263,17 +263,27 @@ void get_temperatures( } } - // Add temperature if temperature field: + // Add temperatures from a temperature field. + // We assume that we do not know how geometric cells are impacted by the + // temperature field in advance. If we had access to this information, we + // could limit the declarations of temperature from the temperature field to + // impacted nuclides only. if (settings::temperature_field_on) { for (auto t: simulation::temperature_field.values()) { + // Nuclide temperatures for (size_t i = 0; i < nuc_temps.size() ; i++){ if (!contains(nuc_temps[i], t)) { nuc_temps[i].push_back(t); } } + // Thermal scattering temperatures + for (size_t i = 0; i < thermal_temps.size() ; i++){ + if (!contains(thermal_temps[i], t)) { + thermal_temps[i].push_back(t); + } + } } } - // TODO: thermal scattering data } //============================================================================== From 06f27c7f81f5eed20e445903f03a38e67bd74094 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 19 Dec 2025 15:20:54 -0600 Subject: [PATCH 09/91] Clean Python API --- openmc/field.py | 85 +++++++++++++++++++++++++--------------------- openmc/settings.py | 9 ++++- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/openmc/field.py b/openmc/field.py index eb5fc951082..463d997e602 100644 --- a/openmc/field.py +++ b/openmc/field.py @@ -2,57 +2,66 @@ import openmc -from .mixin import IDManagerMixin +class ScalarField(ABC): + """Scalar field defined on a geometric mesh. -class ScalarField(IDManagerMixin, ABC): - """ - """ - def to_xml_element(self): - """Return XML representation of the field - - Returns - ------- - element : lxml.etree._Element - XML element containing mesh data - - """ - elem = ET.Element("field") - - elem.set("id", str(self._id)) - if self.name: - elem.set("name", self.name) + Attributes + ---------- + mesh : Mesh + Spatial mesh associated with the field + values : iterable of float + List of values associated with each mesh cell - return elem - - -class TemperatureField(ScalarField): - """ """ def __init__(self, mesh, values): - """ + """Initialization. + + Parameters + ---------- + mesh : Mesh + Spatial mesh associated with the field + values : iterable of float + List of values associated with each mesh cell + """ self.mesh = mesh self.values = values @classmethod - def from_mesh_and_values(cls, mesh, values): - """ + def from_exodus_file(cls, filepath): + """Construct a ScalarField from an Exodus mesh file + + Parameters + ---------- + filepath : path-like or str + Path to the Exodus mesh file + """ - return cls(mesh, values) + #TODO + raise NotImplementedError("Constructor not yet implemented.") - def to_xml_element(self): - """Return XML representation of the mesh - Returns - ------- - element : lxml.etree._Element - XML element containing mesh data +class TemperatureField(ScalarField): + """Temperature field. - """ - element = super().to_xml_element() + Attributes + ---------- + mesh : Mesh + Spatial mesh associated with the field + values : iterable of float + List of values associated with each mesh cell - subelement = ET.SubElement(element, "mesh") - subelement.text = self.mesh.to_xml_element() + """ + def __init__(self, mesh, values): + """Initialization. - return element + Parameters + ---------- + mesh : Mesh + Spatial mesh associated with the field + values : iterable of float + List of values associated with each mesh cell + + """ + super().__init__(mesh, values) diff --git a/openmc/settings.py b/openmc/settings.py index ab121980f54..97fad1c03bb 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -4,6 +4,7 @@ from math import ceil from numbers import Integral, Real from pathlib import Path +import textwrap import lxml.etree as ET import warnings @@ -309,6 +310,10 @@ class Settings: sections be loaded at all temperatures within the range. 'multipole' is a boolean indicating whether or not the windowed multipole method should be used to evaluate resolved resonance cross sections. + temperature_field : openmc.TemperatureField + Temperature field based on a geometric mesh used to specify temperatures + in a model. Temperatures declared from a mesh take precedence over + all other temperature definition (cell, material, and global). trace : tuple or list Show detailed information about a single particle, indicated by three integers: the batch number, generation number, and particle number @@ -1686,7 +1691,9 @@ def _create_temperature_field_subelement(self, root, mesh_memo=None): # Add temperature values subelement = ET.SubElement(element, "values") - subelement.text = " ".join([str(i) for i in self.temperature_field.values]) + subelement.text = "\n ".join( + textwrap.wrap(" ".join( + [str(i) for i in self.temperature_field.values]), 80)) def _create_trigger_subelement(self, root): if self._trigger_active is not None: From d20e3ea7793bb21e1960ae9117cdc7d380a9af47 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 19 Dec 2025 16:41:11 -0600 Subject: [PATCH 10/91] Regression test --- .../temperature_field/inputs_true.dat | 48 ++++++++++++++ .../temperature_field/results_true.dat | 2 + .../temperature_field/test.py | 66 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 tests/regression_tests/temperature_field/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/results_true.dat create mode 100755 tests/regression_tests/temperature_field/test.py diff --git a/tests/regression_tests/temperature_field/inputs_true.dat b/tests/regression_tests/temperature_field/inputs_true.dat new file mode 100644 index 00000000000..d4fec68fb12 --- /dev/null +++ b/tests/regression_tests/temperature_field/inputs_true.dat @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + 0.0 0.0 0.0 5.0 5.0 5.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + diff --git a/tests/regression_tests/temperature_field/results_true.dat b/tests/regression_tests/temperature_field/results_true.dat new file mode 100644 index 00000000000..744dbf08866 --- /dev/null +++ b/tests/regression_tests/temperature_field/results_true.dat @@ -0,0 +1,2 @@ +k-combined: +1.507597E+00 1.811642E-02 diff --git a/tests/regression_tests/temperature_field/test.py b/tests/regression_tests/temperature_field/test.py new file mode 100755 index 00000000000..66f2aa4afce --- /dev/null +++ b/tests/regression_tests/temperature_field/test.py @@ -0,0 +1,66 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0., 0., 0.) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=lower_left[0], xmax=upper_right[0], + ymin=lower_left[1], ymax=upper_right[1], + zmin=lower_left[2], zmax=upper_right[2], + boundary_type="reflective" + ) + cell = openmc.Cell(fill=mat, region=-box) + model.geometry = openmc.Geometry([cell]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() From 6c9b91843ccbfbf73458a1867fd14ed315589e0c Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 19 Dec 2025 20:37:54 -0600 Subject: [PATCH 11/91] Update documentation --- docs/source/pythonapi/base.rst | 11 ++++++++++ docs/source/usersguide/materials.rst | 31 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/docs/source/pythonapi/base.rst b/docs/source/pythonapi/base.rst index ce2f6f0f857..3770bc628bb 100644 --- a/docs/source/pythonapi/base.rst +++ b/docs/source/pythonapi/base.rst @@ -168,6 +168,17 @@ Meshes openmc.SphericalMesh openmc.UnstructuredMesh +Fields +------ + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclassinherit.rst + + openmc.ScalarField + openmc.TemperatureField + Geometry Plotting ----------------- diff --git a/docs/source/usersguide/materials.rst b/docs/source/usersguide/materials.rst index 83af5580574..b33b1421f40 100644 --- a/docs/source/usersguide/materials.rst +++ b/docs/source/usersguide/materials.rst @@ -191,6 +191,37 @@ attribute, e.g., :attr:`Material.temperature` or :attr:`Cell.temperature` attributes, respectively. +Alternatively, temperatures can be specified using a temperature field composed +of a geometric mesh and a map that associates a temperature value to each mesh +cell. During the simulation, temperatures associated with particles are also +updated every time a temperature field cell surface is crossed. While a particle +is contained inside the temperature mesh, temperatures from the temperature field +take precedence over temperature declared for a cell, a material or globally. + +The following example shows how to specify temperatures using a temperarature +field based on a regular mesh: + +.. code-block:: python + + # Define a mesh (regular mesh) + dim = 5 + mesh = openmc.RegularMesh() + mesh.lower_left = (0., 0., 0.) + mesh.upper_right = (10.0, 10.0, 10.0) + mesh.dimension = (dim, dim, dim) + + # Define temperature values for each cell + temperature_values = [273.0 + i * 10 for i in range(dim**3)] + + # Create a temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Register the temperature field in the settings + settings = openmc.Settings() + settings.temperature_field = temperature_field + +.. note:: Temperature fields are currently limited to structured meshes only. + ----------------- Material Mixtures ----------------- From 4b660d6224448977001f037e7a11fff6c2029b53 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 19 Dec 2025 21:22:50 -0600 Subject: [PATCH 12/91] Create a main C++ ScalarField class --- include/openmc/field.h | 49 ++++++++++++++++++++++++++---------------- src/field.cpp | 14 ++++++------ 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/include/openmc/field.h b/include/openmc/field.h index 842886b3fd0..ced871146d9 100644 --- a/include/openmc/field.h +++ b/include/openmc/field.h @@ -6,13 +6,12 @@ namespace openmc { -class TemperatureField { - +class ScalarField { public: //---------------------------------------------------------------------------- // Constructors - TemperatureField() {}; - TemperatureField(Mesh* mesh_ptr, vector values); + ScalarField() = default; + ScalarField(Mesh* mesh_ptr, vector values); //---------------------------------------------------------------------------- // Methods @@ -26,6 +25,34 @@ class TemperatureField { //! \return The distance in cm to the next mesh boundary double distance_to_next_boundary(const Position& r, const Direction& d); + //---------------------------------------------------------------------------- + // Accessors + + // Mesh pointer + Mesh* mesh_ptr() const { return mesh_ptr_; } + + // Values + double& value(int i) { return values_[i]; } + const double& value(int i) const { return values_[i]; } + const vector& values() const { return values_; } + +private: + //---------------------------------------------------------------------------- + // Data members + Mesh* mesh_ptr_; //!< Pointer to the geometric mesh + vector values_; //!< Values associated with each mesh cell +}; + +class TemperatureField : public ScalarField { +public: + //---------------------------------------------------------------------------- + // Constructors + TemperatureField() = default; + TemperatureField(Mesh* mesh_ptr, vector values) : ScalarField(mesh_ptr, values) {}; + + //---------------------------------------------------------------------------- + // Methods + //! Returns the temperature in Kelvin corresponding to the given position. // //! \param[in] r Position of the particle @@ -46,20 +73,6 @@ class TemperatureField { // //! \param[inout] p Particle void update_particle_temperature(Particle& p); - - //---------------------------------------------------------------------------- - // Accessors - - // Temperature values - double& value(int i) { return values_[i]; } - const double& value(int i) const { return values_[i]; } - const vector& values() const { return values_; } - -private: - //---------------------------------------------------------------------------- - // Data members - Mesh* mesh_ptr_; //!< Pointer to the geometric mesh - vector values_; //!< Temperature values }; } // namespace openmc diff --git a/src/field.cpp b/src/field.cpp index dab214efcf9..55d9083a7d2 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -6,7 +6,7 @@ namespace openmc { -TemperatureField::TemperatureField(Mesh* mesh_ptr, vector values) +ScalarField::ScalarField(Mesh* mesh_ptr, vector values) { this->mesh_ptr_ = mesh_ptr; for (double v : values) { @@ -14,26 +14,26 @@ TemperatureField::TemperatureField(Mesh* mesh_ptr, vector values) } } -double TemperatureField::distance_to_next_boundary( +double ScalarField::distance_to_next_boundary( const Position& r, const Direction& u) { if (this->mesh_ptr_ != nullptr) { return this->mesh_ptr_->distance_to_next_boundary(r, u); } else { - fatal_error("No mesh found for the temperature field!"); + fatal_error("No mesh found for the scalar field!"); } } double TemperatureField::get_temperature(const Position& r) { - if (this->mesh_ptr_ != nullptr) { + if (this->mesh_ptr() != nullptr) { // Get bin from position - int i = this->mesh_ptr_->get_bin(r); + int i = this->mesh_ptr()->get_bin(r); // If we have a bin, we use it to locate the value - if (i >= 0 && i < this->values_.size()) { - return this->values_[i]; + if (i >= 0 && i < this->values().size()) { + return this->value(i); } // No values were found (outside the mesh) From 3b3b32f97faeb68aad749983864e46116a1b1fe1 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 19 Dec 2025 23:26:32 -0600 Subject: [PATCH 13/91] Check that size of mesh and size of values are consistent in the field - C++ side --- include/openmc/field.h | 22 +++++++++++++++----- src/field.cpp | 47 +++++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 26 deletions(-) diff --git a/include/openmc/field.h b/include/openmc/field.h index ced871146d9..2bfb71fa63b 100644 --- a/include/openmc/field.h +++ b/include/openmc/field.h @@ -11,7 +11,8 @@ class ScalarField { //---------------------------------------------------------------------------- // Constructors ScalarField() = default; - ScalarField(Mesh* mesh_ptr, vector values); + ScalarField(Mesh* mesh_ptr, vector values, const std::string& field_name); + ScalarField(Mesh* mesh_ptr, vector values) : ScalarField(mesh_ptr, values, "ScalarField") {}; //---------------------------------------------------------------------------- // Methods @@ -28,8 +29,17 @@ class ScalarField { //---------------------------------------------------------------------------- // Accessors + // Field type + const std::string& field_type() const { return this->field_type_; } + // Mesh pointer - Mesh* mesh_ptr() const { return mesh_ptr_; } + Mesh* mesh_ptr() const { + if (this->mesh_ptr_ == nullptr) { + fatal_error(fmt::format("No mesh found for {}!", this->field_type_)); + } else { + return this->mesh_ptr_; + } + } // Values double& value(int i) { return values_[i]; } @@ -39,8 +49,9 @@ class ScalarField { private: //---------------------------------------------------------------------------- // Data members - Mesh* mesh_ptr_; //!< Pointer to the geometric mesh - vector values_; //!< Values associated with each mesh cell + std::string field_type_; //! Name of field type + Mesh* mesh_ptr_; //!< Pointer to the geometric mesh + vector values_; //!< Values associated with each mesh cell }; class TemperatureField : public ScalarField { @@ -48,7 +59,8 @@ class TemperatureField : public ScalarField { //---------------------------------------------------------------------------- // Constructors TemperatureField() = default; - TemperatureField(Mesh* mesh_ptr, vector values) : ScalarField(mesh_ptr, values) {}; + TemperatureField(Mesh* mesh_ptr, vector values) + : ScalarField(mesh_ptr, values, "TemperatureField") {}; //---------------------------------------------------------------------------- // Methods diff --git a/src/field.cpp b/src/field.cpp index 55d9083a7d2..2926f4b7de6 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -6,9 +6,24 @@ namespace openmc { -ScalarField::ScalarField(Mesh* mesh_ptr, vector values) +ScalarField::ScalarField( + Mesh* mesh_ptr, vector values, const std::string& field_type) { - this->mesh_ptr_ = mesh_ptr; + this->field_type_ = field_type; + + if (mesh_ptr != nullptr) { + this->mesh_ptr_ = mesh_ptr; + } else { + fatal_error(fmt::format("No mesh found for {}!", field_type)); + } + + if (this->mesh_ptr_->n_bins() != values.size()) { + fatal_error( + fmt::format("The number of bins in the mesh is not consistent with the " + "number of values declared in {}!", + field_type)); + } + for (double v : values) { this->values_.push_back(v); } @@ -17,31 +32,21 @@ ScalarField::ScalarField(Mesh* mesh_ptr, vector values) double ScalarField::distance_to_next_boundary( const Position& r, const Direction& u) { - if (this->mesh_ptr_ != nullptr) { - return this->mesh_ptr_->distance_to_next_boundary(r, u); - } else { - fatal_error("No mesh found for the scalar field!"); - } + return this->mesh_ptr()->distance_to_next_boundary(r, u); } double TemperatureField::get_temperature(const Position& r) { - if (this->mesh_ptr() != nullptr) { - - // Get bin from position - int i = this->mesh_ptr()->get_bin(r); - - // If we have a bin, we use it to locate the value - if (i >= 0 && i < this->values().size()) { - return this->value(i); - } - - // No values were found (outside the mesh) - return -1.0; + // Get bin from position + int i = this->mesh_ptr()->get_bin(r); - } else { - fatal_error("No mesh found for the temperature field!"); + // If we have a bin, we use it to locate the value + if (i >= 0 && i < this->values().size()) { + return this->value(i); } + + // No values were found (outside the mesh) + return -1.0; } double TemperatureField::get_sqrtkT(const Position& r) From 6dd684da8b712c56f42997e83e0e9e36fb0f1f8f Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 19 Dec 2025 23:48:52 -0600 Subject: [PATCH 14/91] Formatting --- include/openmc/field.h | 9 ++++++--- include/openmc/particle_data.h | 1 - include/openmc/settings.h | 6 +++--- src/finalize.cpp | 2 +- src/geometry_aux.cpp | 6 +++--- src/particle.cpp | 4 ++-- src/simulation.cpp | 34 +++++++++++++++++----------------- 7 files changed, 32 insertions(+), 30 deletions(-) diff --git a/include/openmc/field.h b/include/openmc/field.h index 2bfb71fa63b..58b2441a52e 100644 --- a/include/openmc/field.h +++ b/include/openmc/field.h @@ -11,8 +11,10 @@ class ScalarField { //---------------------------------------------------------------------------- // Constructors ScalarField() = default; - ScalarField(Mesh* mesh_ptr, vector values, const std::string& field_name); - ScalarField(Mesh* mesh_ptr, vector values) : ScalarField(mesh_ptr, values, "ScalarField") {}; + ScalarField( + Mesh* mesh_ptr, vector values, const std::string& field_name); + ScalarField(Mesh* mesh_ptr, vector values) + : ScalarField(mesh_ptr, values, "ScalarField") {}; //---------------------------------------------------------------------------- // Methods @@ -33,7 +35,8 @@ class ScalarField { const std::string& field_type() const { return this->field_type_; } // Mesh pointer - Mesh* mesh_ptr() const { + Mesh* mesh_ptr() const + { if (this->mesh_ptr_ == nullptr) { fatal_error(fmt::format("No mesh found for {}!", this->field_type_)); } else { diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 829d503d0fa..6fb1be09830 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -774,7 +774,6 @@ class ParticleData : public GeometryState { // Next event identifier for history-based transport int next_event() const { return next_event_; } int& next_event() { return next_event_; } - }; } // namespace openmc diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 6f9979d9e42..e16ee305680 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -69,9 +69,9 @@ extern bool delayed_photon_scaling; //!< Scale fission photon yield to include delayed extern "C" bool entropy_on; //!< calculate Shannon entropy? extern "C" bool - event_based; //!< use event-based mode (instead of history-based) -extern bool temperature_field_on ; //!< Is there a temperature field defined? -extern bool ifp_on; //!< Use IFP for kinetics parameters? + event_based; //!< use event-based mode (instead of history-based) +extern bool temperature_field_on; //!< Is there a temperature field defined? +extern bool ifp_on; //!< Use IFP for kinetics parameters? 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? diff --git a/src/finalize.cpp b/src/finalize.cpp index b9c000528d7..b2369745e18 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -159,7 +159,7 @@ int openmc_finalize() simulation::entropy_mesh = nullptr; simulation::ufs_mesh = nullptr; - simulation::temperature_field; + simulation::temperature_field = TemperatureField(); data::energy_max = {INFTY, INFTY, INFTY, INFTY}; data::energy_min = {0.0, 0.0, 0.0, 0.0}; diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index bdb340b963f..1e81851bdbd 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -269,15 +269,15 @@ void get_temperatures( // could limit the declarations of temperature from the temperature field to // impacted nuclides only. if (settings::temperature_field_on) { - for (auto t: simulation::temperature_field.values()) { + for (auto t : simulation::temperature_field.values()) { // Nuclide temperatures - for (size_t i = 0; i < nuc_temps.size() ; i++){ + for (size_t i = 0; i < nuc_temps.size(); i++) { if (!contains(nuc_temps[i], t)) { nuc_temps[i].push_back(t); } } // Thermal scattering temperatures - for (size_t i = 0; i < thermal_temps.size() ; i++){ + for (size_t i = 0; i < thermal_temps.size(); i++) { if (!contains(thermal_temps[i], t)) { thermal_temps[i].push_back(t); } diff --git a/src/particle.cpp b/src/particle.cpp index 0f68dba912e..ad469166d17 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -262,8 +262,8 @@ void Particle::event_advance() (time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY; // Select smaller distance - double distance = - std::min({boundary().distance(), collision_distance(), distance_cutoff, distance_tmesh}); + double distance = std::min({boundary().distance(), collision_distance(), + distance_cutoff, distance_tmesh}); // Prepare the stop condition if (distance == distance_cutoff) { diff --git a/src/simulation.cpp b/src/simulation.cpp index 2255522ba2d..f0e5a547c50 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -814,23 +814,23 @@ void transport_history_based_single_particle(Particle& p) } if (p.alive()) { switch (p.next_event()) { - case EVENT_CROSS_SURFACE: - p.event_cross_surface(); - break; - case EVENT_COLLIDE: - p.event_collide(); - break; - case EVENT_CROSS_TEMPERATURE_MESH: - p.event_cross_temperature_mesh(); - break; - case EVENT_TIME_CUTOFF: - p.wgt() = 0.0; - break; - default: - fatal_error(fmt::format( - "Unknown event '{}' in history-based transport!", p.next_event())); - break; - } + case EVENT_CROSS_SURFACE: + p.event_cross_surface(); + break; + case EVENT_COLLIDE: + p.event_collide(); + break; + case EVENT_CROSS_TEMPERATURE_MESH: + p.event_cross_temperature_mesh(); + break; + case EVENT_TIME_CUTOFF: + p.wgt() = 0.0; + break; + default: + fatal_error(fmt::format( + "Unknown event '{}' in history-based transport!", p.next_event())); + break; + } } p.event_revive_from_secondary(); } From 76cdf20b838acb54b9069dee88cfa3e553e9c63a Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Sat, 20 Dec 2025 00:25:27 -0600 Subject: [PATCH 15/91] Manage incompatibility with event-based mode --- src/settings.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/settings.cpp b/src/settings.cpp index 1d4fb8d3041..60104d86a31 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1228,6 +1228,10 @@ void read_settings_xml(pugi::xml_node root) // Check whether to use event-based parallelism if (check_for_node(root, "event_based")) { event_based = get_node_value_bool(root, "event_based"); + if (temperature_field_on && event_based) { + fatal_error( + "Event-based transport is not yet compatible with temperature fields."); + } } // Check whether material cell offsets should be generated From 49bfb76ea0f9ddeb1edea93fa87905122ac301d4 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Sat, 20 Dec 2025 01:11:09 -0600 Subject: [PATCH 16/91] Unit test for the distance to next boundary method --- include/openmc/mesh.h | 6 ++--- src/mesh.cpp | 6 ++--- tests/cpp_unit_tests/test_mesh.cpp | 36 ++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 43988d0049a..1dccf91ea90 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -181,7 +181,7 @@ class Mesh { //! \param[in] r Position of the particle //! \param[in] u Particle direction //! \return Distance to the next boundary - virtual double distance_to_next_boundary(Position r, Position u) const = 0; + virtual double distance_to_next_boundary(Position r, Direction u) const = 0; //! Get bin at a given position in space // @@ -312,7 +312,7 @@ class StructuredMesh : public Mesh { void surface_bins_crossed(Position r0, Position r1, const Direction& u, vector& bins) const override; - double distance_to_next_boundary(Position r, Position u) const override; + double distance_to_next_boundary(Position r, Direction u) const override; //! Determine which cell or surface bins were crossed by a particle // @@ -696,7 +696,7 @@ class UnstructuredMesh : public Mesh { UnstructuredMesh(pugi::xml_node node); UnstructuredMesh(hid_t group); - double distance_to_next_boundary(Position r, Position u) const override; + double distance_to_next_boundary(Position r, Direction u) const override; static const std::string mesh_type; virtual std::string get_mesh_type() const override; diff --git a/src/mesh.cpp b/src/mesh.cpp index 0c696eed220..afa027d21d6 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -720,7 +720,7 @@ UnstructuredMesh::UnstructuredMesh(hid_t group) : Mesh(group) } } -double UnstructuredMesh::distance_to_next_boundary(Position r, Position u) const +double UnstructuredMesh::distance_to_next_boundary(Position r, Direction u) const { fatal_error("Not implemented"); return -1.0; @@ -1181,12 +1181,12 @@ void StructuredMesh::surface_bins_crossed( raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins)); } -double StructuredMesh::distance_to_next_boundary(Position r, Position u) const +double StructuredMesh::distance_to_next_boundary(Position r, Direction u) const { Position global_r = r; Position local_r = local_coords(r); - double distance = INFTY; + double distance = 0.0; const int n = n_dimension_; bool in_mesh; diff --git a/tests/cpp_unit_tests/test_mesh.cpp b/tests/cpp_unit_tests/test_mesh.cpp index 24c4f77373f..1881e6d729e 100644 --- a/tests/cpp_unit_tests/test_mesh.cpp +++ b/tests/cpp_unit_tests/test_mesh.cpp @@ -255,3 +255,39 @@ TEST_CASE("Test multiple meshes HDF5 roundtrip - spherical") REQUIRE(regular_mesh_hdf5->lower_left() == regular_mesh_xml->lower_left()); REQUIRE(regular_mesh_hdf5->upper_right() == regular_mesh_xml->upper_right()); } + +TEST_CASE("Test distance_to_next_boundary() method for regular mesh") +{ + // The XML data as a string + std::string xml_string = R"( + + 2 2 2 + -1 -1 -1 + 1 1 1 + + )"; + + // Create the mesh from a file + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + pugi::xml_node root = doc.child("mesh"); + auto mesh = RegularMesh(root); + + Position r; + Position u; + + // Test inside the mesh + r = Position(0.0, 0.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == 1.0); + + // Test outside the mesh, going toward the mesh + r = Position(-1.5, 0.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.5); + + // Test outside the mesh, not going toward the mesh + r = Position(-2.0, 0.0, 0.0); + u = Position(-1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); +} \ No newline at end of file From 5e79b7b6f0848939e02fc225c0868b1ee6885fb2 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 15 Jan 2026 12:48:40 -0600 Subject: [PATCH 17/91] Missing init file in new regression test --- tests/regression_tests/temperature_field/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/regression_tests/temperature_field/__init__.py diff --git a/tests/regression_tests/temperature_field/__init__.py b/tests/regression_tests/temperature_field/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 0c670167203e5795d0d1234be22c307a9f273a79 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 15 Jan 2026 13:24:44 -0600 Subject: [PATCH 18/91] Formatting --- src/geometry_aux.cpp | 2 +- src/mesh.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 1e81851bdbd..b7d954eaad9 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -17,8 +17,8 @@ #include "openmc/lattice.h" #include "openmc/material.h" #include "openmc/settings.h" -#include "openmc/surface.h" #include "openmc/simulation.h" +#include "openmc/surface.h" #include "openmc/tallies/filter.h" #include "openmc/tallies/filter_cell_instance.h" #include "openmc/tallies/filter_distribcell.h" diff --git a/src/mesh.cpp b/src/mesh.cpp index afa027d21d6..809128da93f 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -720,7 +720,8 @@ UnstructuredMesh::UnstructuredMesh(hid_t group) : Mesh(group) } } -double UnstructuredMesh::distance_to_next_boundary(Position r, Direction u) const +double UnstructuredMesh::distance_to_next_boundary( + Position r, Direction u) const { fatal_error("Not implemented"); return -1.0; From 2ee8edb49240fd6dee7096d138525a36413148e5 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 24 Mar 2026 15:58:04 -0500 Subject: [PATCH 19/91] First attempt to enable event-based mode --- include/openmc/event.h | 5 +++++ include/openmc/timer.h | 1 + src/event.cpp | 42 ++++++++++++++++++++++++++++++++++++++++-- src/output.cpp | 4 ++++ src/simulation.cpp | 5 ++++- src/timer.cpp | 2 ++ 6 files changed, 56 insertions(+), 3 deletions(-) diff --git a/include/openmc/event.h b/include/openmc/event.h index 2d215a10e46..b222608de10 100644 --- a/include/openmc/event.h +++ b/include/openmc/event.h @@ -64,6 +64,7 @@ extern SharedArray calculate_nonfuel_xs_queue; extern SharedArray advance_particle_queue; extern SharedArray surface_crossing_queue; extern SharedArray collision_queue; +extern SharedArray temperature_mesh_crossing_queue; // Particle buffer extern vector particles; @@ -107,6 +108,10 @@ void process_surface_crossing_events(); //! Execute the collision event for all particles in this event's buffer void process_collision_events(); +//! Execute the temperature mesh crossing event fo all particles in this event's +//! buffer +void process_temperature_mesh_crossing_events(); + //! Execute the death event for all particles // //! \param n_particles The number of particles in the particle buffer diff --git a/include/openmc/timer.h b/include/openmc/timer.h index d928aad4560..d4d63ed07fc 100644 --- a/include/openmc/timer.h +++ b/include/openmc/timer.h @@ -30,6 +30,7 @@ extern Timer time_event_calculate_xs; extern Timer time_event_advance_particle; extern Timer time_event_surface_crossing; extern Timer time_event_collision; +extern Timer time_event_temperature_mesh_crossing; extern Timer time_event_death; extern Timer time_update_src; diff --git a/src/event.cpp b/src/event.cpp index f33e132d0af..18d01256492 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -17,6 +17,7 @@ SharedArray calculate_nonfuel_xs_queue; SharedArray advance_particle_queue; SharedArray surface_crossing_queue; SharedArray collision_queue; +SharedArray temperature_mesh_crossing_queue; vector particles; @@ -33,6 +34,9 @@ void init_event_queues(int64_t n_particles) simulation::advance_particle_queue.reserve(n_particles); simulation::surface_crossing_queue.reserve(n_particles); simulation::collision_queue.reserve(n_particles); + if (settings::temperature_field_on) { + simulation::temperature_mesh_crossing_queue.reserve(n_particles); + } simulation::particles.resize(n_particles); } @@ -44,6 +48,7 @@ void free_event_queues(void) simulation::advance_particle_queue.clear(); simulation::surface_crossing_queue.clear(); simulation::collision_queue.clear(); + simulation::temperature_mesh_crossing_queue.clear(); simulation::particles.clear(); } @@ -115,10 +120,24 @@ void process_advance_particle_events() p.event_advance(); if (!p.alive()) continue; - if (p.collision_distance() > p.boundary().distance()) { + + switch (p.next_event()) { + case EVENT_CROSS_SURFACE: simulation::surface_crossing_queue.thread_safe_append({p, buffer_idx}); - } else { + break; + case EVENT_COLLIDE: simulation::collision_queue.thread_safe_append({p, buffer_idx}); + break; + case EVENT_CROSS_TEMPERATURE_MESH: + simulation::temperature_mesh_crossing_queue.thread_safe_append({p, buffer_idx}); + break; + case EVENT_TIME_CUTOFF: + p.wgt() = 0.0; + break; + default: + fatal_error(fmt::format( + "Unknown event '{}' in event-based transport!", p.next_event())); + break; } } @@ -165,6 +184,25 @@ void process_collision_events() simulation::time_event_collision.stop(); } +void process_temperature_mesh_crossing_events() +{ + simulation::time_event_temperature_mesh_crossing.start(); + +#pragma omp parallel for schedule(runtime) + for (int64_t i = 0; i < simulation::temperature_mesh_crossing_queue.size(); i++) { + int64_t buffer_idx = simulation::temperature_mesh_crossing_queue[i].idx; + Particle& p = simulation::particles[buffer_idx]; + p.event_cross_temperature_mesh(); + p.event_revive_from_secondary(); + if (p.alive()) + dispatch_xs_event(buffer_idx); + } + + simulation::temperature_mesh_crossing_queue.resize(0); + + simulation::time_event_temperature_mesh_crossing.stop(); +} + void process_death_events(int64_t n_particles) { simulation::time_event_death.start(); diff --git a/src/output.cpp b/src/output.cpp index 0a14e8843d8..1213ab042ad 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -441,6 +441,10 @@ void print_runtime() show_time("Advancing", time_event_advance_particle.elapsed(), 2); show_time("Surface crossings", time_event_surface_crossing.elapsed(), 2); show_time("Collisions", time_event_collision.elapsed(), 2); + if (settings::temperature_field_on) { + show_time("Temperature mesh crossings", + time_event_temperature_mesh_crossing.elapsed(), 2); + } show_time("Particle death", time_event_death.elapsed(), 2); } if (settings::run_mode == RunMode::EIGENVALUE) { diff --git a/src/simulation.cpp b/src/simulation.cpp index f0e5a547c50..f9ee63b697e 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -872,7 +872,8 @@ void transport_event_based() simulation::calculate_nonfuel_xs_queue.size(), simulation::advance_particle_queue.size(), simulation::surface_crossing_queue.size(), - simulation::collision_queue.size()}); + simulation::collision_queue.size(), + simulation::temperature_mesh_crossing_queue.size()}); // Execute event with the longest queue if (max == 0) { @@ -887,6 +888,8 @@ void transport_event_based() process_surface_crossing_events(); } else if (max == simulation::collision_queue.size()) { process_collision_events(); + } else if (max == simulation::temperature_mesh_crossing_queue.size()) { + process_temperature_mesh_crossing_events(); } } diff --git a/src/timer.cpp b/src/timer.cpp index 6d692d4fbf6..12b8f89fce3 100644 --- a/src/timer.cpp +++ b/src/timer.cpp @@ -25,6 +25,7 @@ Timer time_event_calculate_xs; Timer time_event_advance_particle; Timer time_event_surface_crossing; Timer time_event_collision; +Timer time_event_temperature_mesh_crossing; Timer time_event_death; Timer time_update_src; @@ -85,6 +86,7 @@ void reset_timers() simulation::time_event_advance_particle.reset(); simulation::time_event_surface_crossing.reset(); simulation::time_event_collision.reset(); + simulation::time_event_temperature_mesh_crossing.reset(); simulation::time_event_death.reset(); simulation::time_update_src.reset(); } From cd4443b925ef725db796daa2c11972907f199c27 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 24 Mar 2026 16:05:22 -0500 Subject: [PATCH 20/91] Revert error message for incompatibility with event-based mode --- src/settings.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index 60104d86a31..1d4fb8d3041 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1228,10 +1228,6 @@ void read_settings_xml(pugi::xml_node root) // Check whether to use event-based parallelism if (check_for_node(root, "event_based")) { event_based = get_node_value_bool(root, "event_based"); - if (temperature_field_on && event_based) { - fatal_error( - "Event-based transport is not yet compatible with temperature fields."); - } } // Check whether material cell offsets should be generated From aef0709ce6ce9fbfe172c9c8519897364987ce37 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 24 Mar 2026 16:08:16 -0500 Subject: [PATCH 21/91] Formatting --- src/event.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/event.cpp b/src/event.cpp index 18d01256492..f61aa8d38da 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -129,7 +129,8 @@ void process_advance_particle_events() simulation::collision_queue.thread_safe_append({p, buffer_idx}); break; case EVENT_CROSS_TEMPERATURE_MESH: - simulation::temperature_mesh_crossing_queue.thread_safe_append({p, buffer_idx}); + simulation::temperature_mesh_crossing_queue.thread_safe_append( + {p, buffer_idx}); break; case EVENT_TIME_CUTOFF: p.wgt() = 0.0; @@ -189,7 +190,8 @@ void process_temperature_mesh_crossing_events() simulation::time_event_temperature_mesh_crossing.start(); #pragma omp parallel for schedule(runtime) - for (int64_t i = 0; i < simulation::temperature_mesh_crossing_queue.size(); i++) { + for (int64_t i = 0; i < simulation::temperature_mesh_crossing_queue.size(); + i++) { int64_t buffer_idx = simulation::temperature_mesh_crossing_queue[i].idx; Particle& p = simulation::particles[buffer_idx]; p.event_cross_temperature_mesh(); From 091925ab962106e43d973f37f0ac87cce3e817d2 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 24 Mar 2026 16:26:19 -0500 Subject: [PATCH 22/91] Cleaning --- include/openmc/event.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/openmc/event.h b/include/openmc/event.h index b222608de10..262c1dc44c2 100644 --- a/include/openmc/event.h +++ b/include/openmc/event.h @@ -108,8 +108,8 @@ void process_surface_crossing_events(); //! Execute the collision event for all particles in this event's buffer void process_collision_events(); -//! Execute the temperature mesh crossing event fo all particles in this event's -//! buffer +//! Execute the temperature mesh crossing event for all particles in this +//! event's buffer void process_temperature_mesh_crossing_events(); //! Execute the death event for all particles From b0b28baad449df41aa821b4e919a1f1161cc740c Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 31 Mar 2026 11:44:00 -0500 Subject: [PATCH 23/91] Add distance_to_next_boundary() tests for rectilinear, cylindrical, and spherical meshes --- tests/cpp_unit_tests/test_mesh.cpp | 114 ++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 2 deletions(-) diff --git a/tests/cpp_unit_tests/test_mesh.cpp b/tests/cpp_unit_tests/test_mesh.cpp index 1881e6d729e..a06f721bb9f 100644 --- a/tests/cpp_unit_tests/test_mesh.cpp +++ b/tests/cpp_unit_tests/test_mesh.cpp @@ -256,7 +256,7 @@ TEST_CASE("Test multiple meshes HDF5 roundtrip - spherical") REQUIRE(regular_mesh_hdf5->upper_right() == regular_mesh_xml->upper_right()); } -TEST_CASE("Test distance_to_next_boundary() method for regular mesh") +TEST_CASE("Test distance_to_next_boundary() - regular") { // The XML data as a string std::string xml_string = R"( @@ -290,4 +290,114 @@ TEST_CASE("Test distance_to_next_boundary() method for regular mesh") r = Position(-2.0, 0.0, 0.0); u = Position(-1.0, 0.0, 0.0); REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); -} \ No newline at end of file +} + +TEST_CASE("Test distance_to_next_boundary() - rectilinear") +{ + // The XML data as a string + std::string xml_string = R"( + + 0.0 1.0 7.0 10.0 + -10.0 -3.0 0.0 + -10.0 2.0 10.0 + + )"; + + // Create the mesh from a file + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + pugi::xml_node root = doc.child("mesh"); + auto mesh = RectilinearMesh(root); + + Position r; + Position u; + + // Test inside the mesh + r = Position(5.0, -5.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == 2.0); + + // Test outside the mesh, going toward the mesh + r = Position(-1.0, -5.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == 1.0); + + // Test outside the mesh, not going toward the mesh + r = Position(-1.0, 0.0, 0.0); + u = Position(-1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); +} + +TEST_CASE("Test distance_to_next_boundary() - cylindrical") +{ + // The XML data as a string + std::string xml_string = R"( + + 0.1 0.2 0.5 1.0 + 0.0 6.283185307179586 + 0.0 .1 0.2 0.4 0.6 1.0 + 0 0 0 + + )"; + + // Create the mesh from a file + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + pugi::xml_node root = doc.child("mesh"); + auto mesh = CylindricalMesh(root); + + Position r; + Position u; + + // Test inside the mesh + r = Position(0.0, 0.0, 0.5); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.1); + + // Test outside the mesh, going toward the mesh + r = Position(-1.5, 0.0, 0.5); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.5); + + // Test outside the mesh, not going toward the mesh + r = Position(-2.0, 0.0, 0.0); + u = Position(-1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); +} + +TEST_CASE("Test distance_to_next_boundary() - spherical") +{ + // The XML data as a string + std::string xml_string = R"( + + 0.1 0.2 0.5 1.0 + 0.0 3.141592653589793 + 0.0 6.283185307179586 + 0.0 0.0 0.0 + ' + )"; + + // Create the mesh from a file + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + pugi::xml_node root = doc.child("mesh"); + auto mesh = SphericalMesh(root); + + Position r; + Position u; + + // Test inside the mesh + r = Position(0.0, 0.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.1); + + // Test outside the mesh, going toward the mesh + r = Position(-1.5, 0.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.5); + + // Test outside the mesh, not going toward the mesh + r = Position(-2.0, 0.0, 0.0); + u = Position(-1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); +} From d886d447b52ea8d2e3672473f65923b021fdea10 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 31 Mar 2026 16:52:37 -0500 Subject: [PATCH 24/91] Initialize coord_ with an element to be compatible with p.r() at Particle construction --- include/openmc/particle_data.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 6fb1be09830..d60aeeb4563 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -417,9 +417,9 @@ class GeometryState { 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_ {1}; //!< number of current coordinate levels + int cell_instance_; //!< offset for distributed properties + vector coord_ = {LocalCoord()}; //!< coordinates for all levels int n_coord_last_ {1}; //!< number of current coordinates vector cell_last_; //!< coordinates for all levels From cf903c860a8496683fcd1544aa210da09b0a99dd Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 31 Mar 2026 16:55:14 -0500 Subject: [PATCH 25/91] C++ unit tests for temperature fields --- tests/cpp_unit_tests/CMakeLists.txt | 1 + tests/cpp_unit_tests/test_field.cpp | 55 +++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/cpp_unit_tests/test_field.cpp diff --git a/tests/cpp_unit_tests/CMakeLists.txt b/tests/cpp_unit_tests/CMakeLists.txt index 5f87db9eac2..efa42d2d238 100644 --- a/tests/cpp_unit_tests/CMakeLists.txt +++ b/tests/cpp_unit_tests/CMakeLists.txt @@ -1,5 +1,6 @@ set(TEST_NAMES test_distribution + test_field test_file_utils test_tally test_interpolate diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp new file mode 100644 index 00000000000..2141424f357 --- /dev/null +++ b/tests/cpp_unit_tests/test_field.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include +#include + +#include "openmc/mesh.h" +#include "openmc/field.h" + +using namespace openmc; + +TEST_CASE("Test TemperatureField functions with a regular mesh") +{ + // The XML data as a string + std::string xml_string = R"( + + 2 2 2 + -1 -1 -1 + 1 1 1 + + )"; + + // Create the mesh from a file + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + pugi::xml_node root = doc.child("mesh"); + auto mesh = RegularMesh(root); + + // Define some temperature values + vector values = {10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0}; + + // Create a temperature field + TemperatureField temp_field = TemperatureField(&mesh, values); + + // Get temperature + REQUIRE(temp_field.get_temperature(Position(0.5, 0.5, 0.5)) == 80.0); + REQUIRE(temp_field.get_temperature(Position(-0.5, -0.5, -0.5)) == 10.0); + REQUIRE(temp_field.get_temperature(Position(0.5, -0.5, -0.5)) == 20.0); + REQUIRE(temp_field.get_temperature(Position(-0.5, -0.5, 0.5)) == 50.0); + REQUIRE(temp_field.get_temperature(Position(0.0, 0.0, 0.0)) == 10.0); + + // Get sqrtkT + REQUIRE(temp_field.get_sqrtkT(Position(0.5, 0.5, 0.5)) == + Catch::Approx(0.083029).margin(1.0E-6)); + + // Update particle temperature + Particle p; + REQUIRE(p.sqrtkT() == -1.0); + p.r() = Position(0.5, 0.5, 0.5); + temp_field.update_particle_temperature(p); + REQUIRE(p.sqrtkT() == Catch::Approx(0.083029).margin(1.0E-6)); + p.r() = Position(-0.5, -0.5, -0.5); + temp_field.update_particle_temperature(p); + REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); +} From b1e4fee594b29d252a46d82cc22e1b30a3ca4f22 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 31 Mar 2026 16:58:44 -0500 Subject: [PATCH 26/91] C API interface to update temperature values in the temperature field --- src/field.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/field.cpp b/src/field.cpp index 2926f4b7de6..814e2653e25 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -2,6 +2,7 @@ #include "openmc/cell.h" #include "openmc/constants.h" #include "openmc/mesh.h" +#include "openmc/simulation.h" #include "openmc/vector.h" namespace openmc { @@ -77,4 +78,20 @@ void TemperatureField::update_particle_temperature(Particle& p) } } +//============================================================================== +// C API +//============================================================================== + +extern "C" int openmc_temperature_field_set_temperature( + int32_t index, double temperature) +{ + if (index < 0 || index >= simulation::temperature_field.values().size()) { + set_errmsg("Index in temperature field is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + simulation::temperature_field.value(index) = temperature; + return 0; +} + } // namespace openmc From 68d8d9c3f0c902526fe2e88d9935ae9ffe13ec37 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 1 Apr 2026 15:39:19 -0500 Subject: [PATCH 27/91] C API documentation update --- docs/source/capi/index.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/source/capi/index.rst b/docs/source/capi/index.rst index 2583d51dff2..1e781fe6d94 100644 --- a/docs/source/capi/index.rst +++ b/docs/source/capi/index.rst @@ -753,3 +753,12 @@ Functions :type scores: const int* :return: Return status (negative if an error occurred) :rtype: int + +.. c:function:: int openmc_temperature_field_set_temperature(int32_t index, double temperature) + + Set the temperature value of a given cell in the temperature field + + :param int32_t index: Index in the tempererature mesh + :param double temperature: Temperature in Kelvin + :return: Return status (negative if an error occurred) + :rtype: int From 614564b294abcbe0ce3d081ecae1892ca8072abc Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 1 Apr 2026 15:40:51 -0500 Subject: [PATCH 28/91] Remove unused function in Python API --- openmc/field.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/openmc/field.py b/openmc/field.py index 463d997e602..5c52152da21 100644 --- a/openmc/field.py +++ b/openmc/field.py @@ -28,18 +28,6 @@ def __init__(self, mesh, values): self.mesh = mesh self.values = values - @classmethod - def from_exodus_file(cls, filepath): - """Construct a ScalarField from an Exodus mesh file - - Parameters - ---------- - filepath : path-like or str - Path to the Exodus mesh file - - """ - #TODO - raise NotImplementedError("Constructor not yet implemented.") class TemperatureField(ScalarField): From 7bee82987205c8ddb73e7f4fdb0af1e28a58895f Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 1 Apr 2026 15:42:12 -0500 Subject: [PATCH 29/91] Test that the number of temperature values given is consistent with the number of cells in the mesh --- src/settings.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/settings.cpp b/src/settings.cpp index 1d4fb8d3041..14ed07f21d1 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -806,6 +806,11 @@ void read_settings_xml(pugi::xml_node root) vector tf_values; if (check_for_node(node_tf, "values")) { auto temp = get_node_array(node_tf, "values"); + if (temp.size() != tf_mesh_ptr->n_bins()) { + throw std::runtime_error( + "Inconsistency in the temperature field: the number of " + "values must be equal to the number of bins in the mesh."); + } for (const auto& b : temp) { tf_values.push_back(b); } From 78529b50278403900379bf8c1d9e5a255ea44417 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 1 Apr 2026 15:43:17 -0500 Subject: [PATCH 30/91] Fix XML reader --- openmc/settings.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openmc/settings.py b/openmc/settings.py index 97fad1c03bb..de90149f70a 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -2181,13 +2181,14 @@ def _entropy_mesh_from_xml_element(self, root, meshes): self.entropy_mesh = meshes[mesh_id] def _temperature_field_from_xml_element(self, root, meshes): - text = get_text(root, 'temperature_field') - if text is None: - return - mesh_id = int(text) - if mesh_id not in meshes: - raise ValueError(f'Could not locate mesh with ID "{mesh_id}"') - self.temperature_field.mesh = meshes[mesh_id] + elem = root.find('temperature_field') + if elem is not None: + mesh_id = int(get_text(elem, 'mesh')) + if mesh_id not in meshes: + raise ValueError(f'Could not locate mesh with ID "{mesh_id}"') + mesh = meshes[mesh_id] + values = [float(x) for x in get_text(elem, 'values').split()] + self.temperature_field = TemperatureField(mesh, values) def _trigger_from_xml_element(self, root): elem = root.find('trigger') From d91a64d8e02ede97db1ff9973d03b2bf459dc394 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 1 Apr 2026 15:43:50 -0500 Subject: [PATCH 31/91] Define equality function for ScalarFields --- openmc/field.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openmc/field.py b/openmc/field.py index 5c52152da21..a33d32c695d 100644 --- a/openmc/field.py +++ b/openmc/field.py @@ -28,6 +28,10 @@ def __init__(self, mesh, values): self.mesh = mesh self.values = values + def __eq__(self, other): + if not isinstance(other, ScalarField): + return NotImplementedError() + return self.mesh.id == other.mesh.id and self.values == other.values class TemperatureField(ScalarField): From 068b92b18b0cd85f9a485bcedecd233bd35e5d80 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 1 Apr 2026 15:45:33 -0500 Subject: [PATCH 32/91] Unit test for temperature field - Python API --- tests/unit_tests/test_temperature_field.py | 40 ++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/unit_tests/test_temperature_field.py diff --git a/tests/unit_tests/test_temperature_field.py b/tests/unit_tests/test_temperature_field.py new file mode 100644 index 00000000000..a99337eb1c3 --- /dev/null +++ b/tests/unit_tests/test_temperature_field.py @@ -0,0 +1,40 @@ +"""Test the temperature field Python object.""" + +import openmc +import pytest +import numpy as np + + +@pytest.fixture(scope="module") +def mesh(): + """2x2x2 regular mesh""" + dim = 2 + lower_left = (0., 0., 0.) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + return mesh + + +def test_xml_serialization(mesh, run_in_tmpdir): + """Test XMl serialization for a temperature field declaration. + + """ + # Define values + values = [0., 1., 2., 3., 4., 5., 6., 7.] + + # Create field + temperature_field = openmc.TemperatureField(mesh, values) + + # Export settings + settings = openmc.Settings() + settings.temperature_field = temperature_field + settings.export_to_xml() + + # Read settings from xml file + read_settings = openmc.Settings.from_xml() + + # Check consistency + assert read_settings.temperature_field == temperature_field From 027c35c7dab05613d132d6d22be51c10d20a8c32 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 1 Apr 2026 15:46:48 -0500 Subject: [PATCH 33/91] Change fatal_error to throw --- src/settings.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/settings.cpp b/src/settings.cpp index 14ed07f21d1..24becf99b36 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -794,12 +794,13 @@ void read_settings_xml(pugi::xml_node root) if (check_for_node(node_tf, "mesh")) { int temp = std::stoi(get_node_value(node_tf, "mesh")); if (model::mesh_map.find(temp) == model::mesh_map.end()) { - fatal_error(fmt::format( + throw std::runtime_error(fmt::format( "Mesh {} specified for the temperature field does not exist.", temp)); } tf_mesh_ptr = model::meshes[model::mesh_map.at(temp)].get(); } else { - fatal_error("A mesh should be given for the temperature field."); + throw std::runtime_error( + "A mesh should be given for the temperature field."); } // Values parameter @@ -815,7 +816,7 @@ void read_settings_xml(pugi::xml_node root) tf_values.push_back(b); } } else { - fatal_error( + throw std::runtime_error( "Temperature values should be given for the temperature field."); } From 1bd1b5cdcc24c4d7a14a020cc5c5fc7945f6f2ff Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 1 Apr 2026 15:47:50 -0500 Subject: [PATCH 34/91] Unit test temperature field - C++ side --- tests/cpp_unit_tests/test_field.cpp | 96 ++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 2 deletions(-) diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 2141424f357..897a06ac587 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -1,11 +1,13 @@ #include -#include #include +#include +#include +#include #include -#include "openmc/mesh.h" #include "openmc/field.h" +#include "openmc/mesh.h" using namespace openmc; @@ -53,3 +55,93 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") temp_field.update_particle_temperature(p); REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); } + +TEST_CASE("Test settings declaration exceptions for a temperature field", + "[generators]") +{ + auto [input, error] = GENERATE(table({ + {// If the number of values is not equal to the number of bins -> error + R"( + + eigenvalue + 200 + 20 + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + + )", + "Inconsistency in the temperature field: the number of values must be " + "equal to the number of bins in the mesh."}, + {// If the mesh declared is not defined -> error + R"( + + eigenvalue + 200 + 20 + + 2 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + + )", + "Mesh 2 specified for the temperature field does not exist."}, + {// No mesh declared -> error + R"( + + eigenvalue + 200 + 20 + + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + + )", + "A mesh should be given for the temperature field."}, + {// No values declared -> error + R"( + + eigenvalue + 200 + 20 + + 1 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + + )", + "Temperature values should be given for the temperature field."}, + })); + + free_memory_mesh(); + free_memory_settings(); + settings::run_mode = RunMode::UNSET; + + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(input.c_str()); + pugi::xml_node root = doc.child("settings"); + + CAPTURE(input); + REQUIRE_THROWS_WITH(read_settings_xml(root), error); + doc.reset(); +} From 242a842ca28616de3e400ca657fd402112eaecc2 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 2 Apr 2026 09:15:34 -0500 Subject: [PATCH 35/91] Temporarily disable part of temperature field c++ unit tests --- tests/cpp_unit_tests/test_field.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 897a06ac587..e8d9f3e05f0 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -48,12 +48,12 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") // Update particle temperature Particle p; REQUIRE(p.sqrtkT() == -1.0); - p.r() = Position(0.5, 0.5, 0.5); - temp_field.update_particle_temperature(p); - REQUIRE(p.sqrtkT() == Catch::Approx(0.083029).margin(1.0E-6)); - p.r() = Position(-0.5, -0.5, -0.5); - temp_field.update_particle_temperature(p); - REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); + //p.r() = Position(0.5, 0.5, 0.5); + //temp_field.update_particle_temperature(p); + //REQUIRE(p.sqrtkT() == Catch::Approx(0.083029).margin(1.0E-6)); + //p.r() = Position(-0.5, -0.5, -0.5); + //temp_field.update_particle_temperature(p); + //REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); } TEST_CASE("Test settings declaration exceptions for a temperature field", From 967090ced1c2a0da4bedab2bcb483bfa88fa2dce Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 2 Apr 2026 13:36:15 -0500 Subject: [PATCH 36/91] Revert initialization of coord_ and declare n_coord_levels in test instead --- include/openmc/particle_data.h | 2 +- tests/cpp_unit_tests/test_field.cpp | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index f56aecda7a7..ff617777213 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -417,7 +417,7 @@ class GeometryState { int n_coord_ {1}; //!< number of current coordinate levels int cell_instance_; //!< offset for distributed properties - vector coord_ = {LocalCoord()}; //!< coordinates for all levels + vector coord_; //!< coordinates for all levels int n_coord_last_ {1}; //!< number of current coordinates vector cell_last_; //!< coordinates for all levels diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index e8d9f3e05f0..116117276f5 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -8,6 +8,7 @@ #include "openmc/field.h" #include "openmc/mesh.h" +#include "openmc/geometry.h" using namespace openmc; @@ -46,6 +47,7 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") Catch::Approx(0.083029).margin(1.0E-6)); // Update particle temperature + model::n_coord_levels = 1; Particle p; REQUIRE(p.sqrtkT() == -1.0); //p.r() = Position(0.5, 0.5, 0.5); From a8ddf99feea2e3c28826382d02a010cffd6d6dab Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 2 Apr 2026 13:45:00 -0500 Subject: [PATCH 37/91] Allow c++ test to be compiled with the DAGMC flag --- tests/cpp_unit_tests/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/cpp_unit_tests/CMakeLists.txt b/tests/cpp_unit_tests/CMakeLists.txt index ca5e366c9ec..5cba118e2ca 100644 --- a/tests/cpp_unit_tests/CMakeLists.txt +++ b/tests/cpp_unit_tests/CMakeLists.txt @@ -15,5 +15,8 @@ set(TEST_NAMES foreach(test ${TEST_NAMES}) add_executable(${test} ${test}.cpp) target_link_libraries(${test} Catch2::Catch2WithMain libopenmc) + if (OPENMC_USE_DAGMC) + target_compile_definitions(${test} PRIVATE OPENMC_DAGMC_ENABLED) + endif() add_test(NAME ${test} COMMAND ${test} WORKING_DIRECTORY ${UNIT_TEST_BIN_OUTPUT_DIR}) endforeach() From 3420117d88914798a72e5ead8e5da63c64a2260e Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 2 Apr 2026 13:45:26 -0500 Subject: [PATCH 38/91] Restore c++ unit test --- tests/cpp_unit_tests/test_field.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 116117276f5..9da719a95e5 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -50,12 +50,12 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") model::n_coord_levels = 1; Particle p; REQUIRE(p.sqrtkT() == -1.0); - //p.r() = Position(0.5, 0.5, 0.5); - //temp_field.update_particle_temperature(p); - //REQUIRE(p.sqrtkT() == Catch::Approx(0.083029).margin(1.0E-6)); - //p.r() = Position(-0.5, -0.5, -0.5); - //temp_field.update_particle_temperature(p); - //REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); + p.r() = Position(0.5, 0.5, 0.5); + temp_field.update_particle_temperature(p); + REQUIRE(p.sqrtkT() == Catch::Approx(0.083029).margin(1.0E-6)); + p.r() = Position(-0.5, -0.5, -0.5); + temp_field.update_particle_temperature(p); + REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); } TEST_CASE("Test settings declaration exceptions for a temperature field", From 5ddfca29023a38c2bea9e9ce246fd7831471bd92 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 2 Apr 2026 14:53:01 -0500 Subject: [PATCH 39/91] Formatting --- include/openmc/particle_data.h | 4 ++-- src/event.cpp | 2 +- tests/cpp_unit_tests/test_field.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index ff617777213..e9fa278c851 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -415,8 +415,8 @@ class GeometryState { private: int64_t id_ {-1}; //!< Unique ID - int n_coord_ {1}; //!< number of current coordinate levels - int cell_instance_; //!< offset for distributed properties + 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 diff --git a/src/event.cpp b/src/event.cpp index f61aa8d38da..d406ad25868 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -191,7 +191,7 @@ void process_temperature_mesh_crossing_events() #pragma omp parallel for schedule(runtime) for (int64_t i = 0; i < simulation::temperature_mesh_crossing_queue.size(); - i++) { + i++) { int64_t buffer_idx = simulation::temperature_mesh_crossing_queue[i].idx; Particle& p = simulation::particles[buffer_idx]; p.event_cross_temperature_mesh(); diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 9da719a95e5..23b54de2246 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -7,8 +7,8 @@ #include #include "openmc/field.h" -#include "openmc/mesh.h" #include "openmc/geometry.h" +#include "openmc/mesh.h" using namespace openmc; From d73f0cf2ea9979d97a66b4fbddb927e84ca9f8cd Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 21 Apr 2026 13:39:53 -0500 Subject: [PATCH 40/91] Change where the temperature update is performed for a field --- include/openmc/field.h | 2 +- include/openmc/particle_data.h | 2 +- src/field.cpp | 12 +++++------- src/geometry.cpp | 9 ++++++++- src/particle.cpp | 16 +++++++--------- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/include/openmc/field.h b/include/openmc/field.h index 58b2441a52e..19327903f79 100644 --- a/include/openmc/field.h +++ b/include/openmc/field.h @@ -87,7 +87,7 @@ class TemperatureField : public ScalarField { //! associated with the current cell instance. // //! \param[inout] p Particle - void update_particle_temperature(Particle& p); + void update_particle_temperature(GeometryState& p); }; } // namespace openmc diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index e9fa278c851..19575ba67d9 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -438,7 +438,7 @@ class GeometryState { 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 sqrtkT_last_ {-1.0}; //!< last temperature double density_mult_ {1.0}; //!< density multiplier double density_mult_last_ {1.0}; //!< last density multiplier diff --git a/src/field.cpp b/src/field.cpp index 814e2653e25..e37d125ce8c 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -59,10 +59,11 @@ double TemperatureField::get_sqrtkT(const Position& r) return -1.0; } -void TemperatureField::update_particle_temperature(Particle& p) +void TemperatureField::update_particle_temperature(GeometryState& p) { - // Save current temperature - p.sqrtkT_last() = p.sqrtkT(); + // It is assumed that the temperature of the cell instance is already + // known, so we only update the temperature if the particle is inside + // the mesh of the temperature field. // Determine the temperature based on the temperature field double field_sqrtkT = this->get_sqrtkT(p.r() + p.u() * TINY_BIT); @@ -70,11 +71,8 @@ void TemperatureField::update_particle_temperature(Particle& p) // If particle inside the mesh, we use the temperature field if (field_sqrtkT >= 0.) { p.sqrtkT() = field_sqrtkT; - - // If particle outside the mesh, go back to the cell instance temperature } else { - Cell& c {*model::cells[p.lowest_coord().cell()]}; - p.sqrtkT() = c.sqrtkT(p.cell_instance()); + return; } } diff --git a/src/geometry.cpp b/src/geometry.cpp index ddb61385f18..87dc280abcf 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -172,11 +172,18 @@ bool find_cell_inner( p.cell_instance() = cell_instance_at_level(p, p.n_coord() - 1); } - // Set the material, temperature and density multiplier. + // Set the material. p.material_last() = p.material(); p.material() = c.material(p.cell_instance()); + + // Set the temperature. p.sqrtkT_last() = p.sqrtkT(); p.sqrtkT() = c.sqrtkT(p.cell_instance()); + if (settings::temperature_field_on) { + simulation::temperature_field.update_particle_temperature(p); + } + + // Set the density multiplier. p.density_mult_last() = p.density_mult(); p.density_mult() = c.density_mult(p.cell_instance()); diff --git a/src/particle.cpp b/src/particle.cpp index 324a8fed278..3f68a413f8c 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -199,11 +199,6 @@ void Particle::event_calculate_xs() cell_last(j) = coord(j).cell(); } n_coord_last() = n_coord(); - - // Update temperature of the particle if temperature field - if (settings::temperature_field_on) { - simulation::temperature_field.update_particle_temperature(*this); - } } // Write particle track. @@ -312,6 +307,9 @@ void Particle::event_advance() void Particle::event_cross_temperature_mesh() { + // Save previous temperature + sqrtkT_last() = sqrtkT(); + // Update temperature of the particle simulation::temperature_field.update_particle_temperature(*this); } @@ -376,10 +374,6 @@ void Particle::event_cross_surface() } } - // Update temperature of the particle if temperature field - if (settings::temperature_field_on) { - simulation::temperature_field.update_particle_temperature(*this); - } } void Particle::event_collide() @@ -647,6 +641,10 @@ void Particle::cross_surface(const Surface& surf) material() = cell->material(cell_instance()); sqrtkT() = cell->sqrtkT(cell_instance()); + // Update temperature of the particle if temperature field + if (settings::temperature_field_on) { + simulation::temperature_field.update_particle_temperature(*this); + } density_mult() = cell->density_mult(cell_instance()); return; } From 1e104a9b68cad95891482fd170d4dd2fab1a2b0f Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 21 Apr 2026 15:08:27 -0500 Subject: [PATCH 41/91] Add unit test corresponding to the failing case --- tests/cpp_unit_tests/test_field.cpp | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 23b54de2246..0c36776d32f 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -147,3 +147,44 @@ TEST_CASE("Test settings declaration exceptions for a temperature field", REQUIRE_THROWS_WITH(read_settings_xml(root), error); doc.reset(); } + +TEST_CASE("Test distance to next boundary when the point is close to a surface") +{ + // The XML data as a string + std::string xml_string = R"( + + 5 5 5 + 0.0 0.0 0.0 + 10.0 10.0 10.0 + + )"; + + // Create the mesh from a file + pugi::xml_document doc; + pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); + pugi::xml_node root = doc.child("mesh"); + auto mesh = RegularMesh(root); + + // Define some temperature values + vector values = {893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, + 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0}; + + // Create a temperature field + TemperatureField temp_field = TemperatureField(&mesh, values); + + REQUIRE( + temp_field.distance_to_next_boundary( + Position(2.4284379294472513, 2.7965340367438869, 7.9999999922776048), + Direction(-0.037401146240029437, 0.18369161574591669, + 0.98227213365980526)) == Catch::Approx(0.0000000079).margin(1.0E-10)); +} From 5e6b40958d010bc33b16d01166065cf0bfb0c8a5 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 21 Apr 2026 15:11:42 -0500 Subject: [PATCH 42/91] First attempt to solve the coincidence test case --- src/mesh.cpp | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/mesh.cpp b/src/mesh.cpp index 46b2eaf6ab3..0732ae0f800 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1337,7 +1337,7 @@ double StructuredMesh::distance_to_next_boundary(Position r, Direction u) const const int n = n_dimension_; bool in_mesh; - StructuredMesh::MeshIndex ijk = get_indices(global_r + TINY_BIT * u, in_mesh); + StructuredMesh::MeshIndex ijk = get_indices(global_r, in_mesh); // Calculate initial distances to next surfaces in all three dimensions std::array distances; @@ -1348,11 +1348,26 @@ double StructuredMesh::distance_to_next_boundary(Position r, Direction u) const if (in_mesh) { // Find surface with minimal distance to current position - const auto k = + auto k = std::min_element(distances.begin(), distances.end()) - distances.begin(); distance = distances[k].distance; + // If the particle is on any surface of the mesh + if (distance <= FP_COINCIDENT) { + // Move the particle a bit to determine ijk: + ijk = get_indices(global_r + TINY_BIT * u, in_mesh); + + for (int k = 0; k < n; ++k) { + distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0); + } + + k = std::min_element(distances.begin(), distances.end()) - + distances.begin(); + + distance = distances[k].distance; + } + } else { // not inside mesh // For all directions outside the mesh, find the distance that we need From c954785f7510891226330a9b0b5754a1f9346679 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 21 Apr 2026 15:44:31 -0500 Subject: [PATCH 43/91] Update unit tests --- tests/cpp_unit_tests/test_field.cpp | 13 +++++++++++++ tests/cpp_unit_tests/test_mesh.cpp | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 0c36776d32f..2f09ebed06b 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -41,6 +41,7 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") REQUIRE(temp_field.get_temperature(Position(0.5, -0.5, -0.5)) == 20.0); REQUIRE(temp_field.get_temperature(Position(-0.5, -0.5, 0.5)) == 50.0); REQUIRE(temp_field.get_temperature(Position(0.0, 0.0, 0.0)) == 10.0); + REQUIRE(temp_field.get_temperature(Position(2.0, 2.0, 2.0)) == -1.0); // Get sqrtkT REQUIRE(temp_field.get_sqrtkT(Position(0.5, 0.5, 0.5)) == @@ -56,6 +57,18 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") p.r() = Position(-0.5, -0.5, -0.5); temp_field.update_particle_temperature(p); REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); + + // Check that the temperature is not updated when outside the mesh + p.r() = Position(2.0, 2.0, 2.0); + temp_field.update_particle_temperature(p); + REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); + + // Check that the temperature is not updated when on an external surface with + // the particle is leaving the mesh + p.r() = Position(1.0, 0.0, 0.0); + p.u() = Direction(1.0, 0.0, 0.0); + temp_field.update_particle_temperature(p); + REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); } TEST_CASE("Test settings declaration exceptions for a temperature field", diff --git a/tests/cpp_unit_tests/test_mesh.cpp b/tests/cpp_unit_tests/test_mesh.cpp index a06f721bb9f..02c53244622 100644 --- a/tests/cpp_unit_tests/test_mesh.cpp +++ b/tests/cpp_unit_tests/test_mesh.cpp @@ -290,6 +290,11 @@ TEST_CASE("Test distance_to_next_boundary() - regular") r = Position(-2.0, 0.0, 0.0); u = Position(-1.0, 0.0, 0.0); REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); + + // Test on the mesh boundary, leaving the mesh + r = Position(1.0, 0.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); } TEST_CASE("Test distance_to_next_boundary() - rectilinear") @@ -326,6 +331,11 @@ TEST_CASE("Test distance_to_next_boundary() - rectilinear") r = Position(-1.0, 0.0, 0.0); u = Position(-1.0, 0.0, 0.0); REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); + + // Test on the mesh boundary, leaving the mesh + r = Position(10.0, -5.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); } TEST_CASE("Test distance_to_next_boundary() - cylindrical") @@ -363,6 +373,11 @@ TEST_CASE("Test distance_to_next_boundary() - cylindrical") r = Position(-2.0, 0.0, 0.0); u = Position(-1.0, 0.0, 0.0); REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); + + // Test on the mesh boundary, leaving the mesh + r = Position(1.0, 0.0, 0.5); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); } TEST_CASE("Test distance_to_next_boundary() - spherical") @@ -400,4 +415,9 @@ TEST_CASE("Test distance_to_next_boundary() - spherical") r = Position(-2.0, 0.0, 0.0); u = Position(-1.0, 0.0, 0.0); REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); + + // Test on the mesh boundary, leaving the mesh + r = Position(1.0, 0.0, 0.0); + u = Position(1.0, 0.0, 0.0); + REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); } From 7f799406c3ace3b528b0bf7c54aa29f6ae7817bb Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 23 Apr 2026 13:51:42 -0500 Subject: [PATCH 44/91] Cleaning --- src/field.cpp | 3 +-- src/simulation.cpp | 6 +++--- tests/cpp_unit_tests/test_field.cpp | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/field.cpp b/src/field.cpp index e37d125ce8c..8fc4ab2afb9 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -71,9 +71,8 @@ void TemperatureField::update_particle_temperature(GeometryState& p) // If particle inside the mesh, we use the temperature field if (field_sqrtkT >= 0.) { p.sqrtkT() = field_sqrtkT; - } else { - return; } + return; } //============================================================================== diff --git a/src/simulation.cpp b/src/simulation.cpp index 4e473ab53d3..393181a03b8 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -818,12 +818,12 @@ void transport_history_based_single_particle(Particle& p) case EVENT_CROSS_SURFACE: p.event_cross_surface(); break; - case EVENT_COLLIDE: - p.event_collide(); - break; case EVENT_CROSS_TEMPERATURE_MESH: p.event_cross_temperature_mesh(); break; + case EVENT_COLLIDE: + p.event_collide(); + break; case EVENT_TIME_CUTOFF: p.wgt() = 0.0; break; diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 2f09ebed06b..58df1fc2df3 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -50,6 +50,7 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") // Update particle temperature model::n_coord_levels = 1; Particle p; + p.u() = Direction(1.0, 0.0, 0.0); REQUIRE(p.sqrtkT() == -1.0); p.r() = Position(0.5, 0.5, 0.5); temp_field.update_particle_temperature(p); @@ -66,7 +67,6 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") // Check that the temperature is not updated when on an external surface with // the particle is leaving the mesh p.r() = Position(1.0, 0.0, 0.0); - p.u() = Direction(1.0, 0.0, 0.0); temp_field.update_particle_temperature(p); REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); } From d6c7a267c9daacfbd73bbfa542fa520e5b04b718 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 23 Apr 2026 14:00:08 -0500 Subject: [PATCH 45/91] Cleaner minimun distance selection --- src/particle.cpp | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index 3f68a413f8c..2d609f5d047 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -263,18 +263,20 @@ void Particle::event_advance() double distance_cutoff = (time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY; - // Select smaller distance - double distance = std::min({boundary().distance(), collision_distance(), - distance_cutoff, distance_tmesh}); - - // Prepare the stop condition - if (distance == distance_cutoff) { - next_event() = EVENT_TIME_CUTOFF; - } else if (distance == boundary().distance()) { + // Define minimum distance and stop condition + double distance = distance_cutoff; + next_event() = EVENT_TIME_CUTOFF; + + if (boundary().distance() < distance) { + distance = boundary().distance(); next_event() = EVENT_CROSS_SURFACE; - } else if (distance == distance_tmesh) { + } + if (distance_tmesh < distance) { + distance = distance_tmesh; next_event() = EVENT_CROSS_TEMPERATURE_MESH; - } else if (distance == collision_distance()) { + } + if (collision_distance() < distance) { + distance = collision_distance(); next_event() = EVENT_COLLIDE; } @@ -373,7 +375,6 @@ void Particle::event_cross_surface() score_surface_tally(*this, model::active_surface_tallies, normal); } } - } void Particle::event_collide() From 7b7c80a72b44b89770933d383276422e7bca239b Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 23 Apr 2026 14:22:59 -0500 Subject: [PATCH 46/91] More robust search of the distance to the next boundary in structured mesh --- include/openmc/mesh.h | 10 +++++++ src/mesh.cpp | 66 +++++++++++++++++++++++++------------------ 2 files changed, 49 insertions(+), 27 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 621753d1412..38f16753f4a 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -343,6 +343,16 @@ class StructuredMesh : public Mesh { void surface_bins_crossed(Position r0, Position r1, const Direction& u, vector& bins) const override; + //! Find surface with minimal distance to current position + double get_minimum_distance( + std::array distances) const; + + //! For all directions outside the mesh, find the distance that we need + //! to travel to reach the next surface. Use the largest distance, as + //! only this will cross all outer surfaces. + double get_maximum_outer_distance(StructuredMesh::MeshIndex ijk, + std::array distances) const; + double distance_to_next_boundary(Position r, Direction u) const override; //! Determine which cell or surface bins were crossed by a particle diff --git a/src/mesh.cpp b/src/mesh.cpp index 0732ae0f800..207d989cc32 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1328,59 +1328,71 @@ void StructuredMesh::surface_bins_crossed( raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins)); } +double StructuredMesh::get_minimum_distance( + std::array distances) const +{ + auto k = + std::min_element(distances.begin(), distances.end()) - distances.begin(); + return distances[k].distance; +} + +double StructuredMesh::get_maximum_outer_distance(StructuredMesh::MeshIndex ijk, + std::array distances) const +{ + double distance = 0.0; + int k_max {-1}; + for (int k = 0; k < n_dimension_; ++k) { + if ((ijk[k] < 1 || ijk[k] > shape_[k]) && + (distances[k].distance > distance)) { + distance = distances[k].distance; + k_max = k; + } + } + return distance; +} + double StructuredMesh::distance_to_next_boundary(Position r, Direction u) const { Position global_r = r; Position local_r = local_coords(r); double distance = 0.0; - const int n = n_dimension_; bool in_mesh; StructuredMesh::MeshIndex ijk = get_indices(global_r, in_mesh); // Calculate initial distances to next surfaces in all three dimensions std::array distances; - for (int k = 0; k < n; ++k) { + for (int k = 0; k < n_dimension_; ++k) { distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0); } - if (in_mesh) { - - // Find surface with minimal distance to current position - auto k = - std::min_element(distances.begin(), distances.end()) - distances.begin(); + if (in_mesh) { // inside mesh - distance = distances[k].distance; + // Calculate + distance = get_minimal_distance(distances); - // If the particle is on any surface of the mesh + // If the particle is on a surface if (distance <= FP_COINCIDENT) { - // Move the particle a bit to determine ijk: + + // Move the particle a bit to avoid calculating the distance to the + // surface the particle is currently on, by selecting the next ijk ijk = get_indices(global_r + TINY_BIT * u, in_mesh); - for (int k = 0; k < n; ++k) { + // Update distances + for (int k = 0; k < n_dimension_; ++k) { distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0); } - k = std::min_element(distances.begin(), distances.end()) - - distances.begin(); - - distance = distances[k].distance; + if (in_mesh) { // still inside mesh after moving + distance = get_minimal_distance(distances); + } else { // not inside mesh after moving + distance = get_maximal_outer_distance(ijk, distances); + } } } else { // not inside mesh - - // For all directions outside the mesh, find the distance that we need - // to travel to reach the next surface. Use the largest distance, as - // only this will cross all outer surfaces. - int k_max {-1}; - for (int k = 0; k < n; ++k) { - if ((ijk[k] < 1 || ijk[k] > shape_[k]) && - (distances[k].distance > distance)) { - distance = distances[k].distance; - k_max = k; - } - } + distance = get_maximal_outer_distance(ijk, distances); } return distance; From 6eaef71de9be9d8f2193bf29ff90ae32b32723dd Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 23 Apr 2026 14:50:03 -0500 Subject: [PATCH 47/91] Correction --- src/mesh.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mesh.cpp b/src/mesh.cpp index 207d989cc32..758dd97548a 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1370,7 +1370,7 @@ double StructuredMesh::distance_to_next_boundary(Position r, Direction u) const if (in_mesh) { // inside mesh // Calculate - distance = get_minimal_distance(distances); + distance = get_minimum_distance(distances); // If the particle is on a surface if (distance <= FP_COINCIDENT) { @@ -1385,14 +1385,14 @@ double StructuredMesh::distance_to_next_boundary(Position r, Direction u) const } if (in_mesh) { // still inside mesh after moving - distance = get_minimal_distance(distances); + distance = get_minimum_distance(distances); } else { // not inside mesh after moving - distance = get_maximal_outer_distance(ijk, distances); + distance = get_maximum_outer_distance(ijk, distances); } } } else { // not inside mesh - distance = get_maximal_outer_distance(ijk, distances); + distance = get_maximum_outer_distance(ijk, distances); } return distance; From 2c51de4fb4d24b74de06e24bf163dc027a243d5e Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 13:19:50 -0500 Subject: [PATCH 48/91] Add a new attribute to particles to store information on the next transport event --- include/openmc/constants.h | 1 + include/openmc/particle_data.h | 26 ++++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/include/openmc/constants.h b/include/openmc/constants.h index e60b3f9e6f7..2fe708f1260 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -381,6 +381,7 @@ constexpr int32_t SURFACE_NONE {0}; //============================================================================== // EVENT IDENTIFIER IN HISTORY-BASED TRANSPORT +const int EVENT_UNDEFINED = 0; const int EVENT_CROSS_SURFACE = 1; const int EVENT_COLLIDE = 2; const int EVENT_CROSS_TEMPERATURE_MESH = 3; diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 19575ba67d9..a63da6c7f48 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -265,6 +265,24 @@ class BoundaryInfo { 0, 0, 0}; //!< which way lattice indices will change }; +struct TransportEvent { + int event_type = EVENT_UNDEFINED; //!< Type of transport event (crossing + //!< surface, collision, time cutoff) + bool cross_surface_geometry = + false; //!< if a surface of the geometry has been crossed during the event + bool cross_surface_temperature_field = + false; //!< if a surface of the temperature field has been crossed during + //!< the event + + // resets all information + void clear() + { + event_type = EVENT_UNDEFINED; + cross_surface_geometry = false; + cross_surface_temperature_field = false; + } +}; + /* * Contains all geometry state information for a particle. */ @@ -568,7 +586,7 @@ class ParticleData : public GeometryState { int64_t n_progeny_ {0}; - int next_event_ {0}; + TransportEvent next_event_ = TransportEvent(); public: //---------------------------------------------------------------------------- @@ -787,9 +805,9 @@ class ParticleData : public GeometryState { } } - // Next event identifier for history-based transport - int next_event() const { return next_event_; } - int& next_event() { return next_event_; } + // Next event in transport + TransportEvent& next_event() { return next_event_; } + const TransportEvent& next_event() const { return next_event_; } }; } // namespace openmc From a6f66178da75d355da2eb9bccee707975e40c0a4 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 13:35:35 -0500 Subject: [PATCH 49/91] Refactor history-based transport to only have one cross surface event --- include/openmc/constants.h | 3 +- include/openmc/particle.h | 3 +- src/particle.cpp | 75 +++++++++++++++++++++++++++----------- src/simulation.cpp | 10 ++--- 4 files changed, 61 insertions(+), 30 deletions(-) diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 2fe708f1260..f3e1f027e12 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -384,8 +384,7 @@ constexpr int32_t SURFACE_NONE {0}; const int EVENT_UNDEFINED = 0; const int EVENT_CROSS_SURFACE = 1; const int EVENT_COLLIDE = 2; -const int EVENT_CROSS_TEMPERATURE_MESH = 3; -const int EVENT_TIME_CUTOFF = 4; +const int EVENT_TIME_CUTOFF = 3; } // namespace openmc diff --git a/include/openmc/particle.h b/include/openmc/particle.h index 21c808c36b7..5fdfaeff090 100644 --- a/include/openmc/particle.h +++ b/include/openmc/particle.h @@ -68,7 +68,8 @@ class Particle : public ParticleData { void event_calculate_xs(); void event_advance(); void event_cross_surface(); - void event_cross_temperature_mesh(); + void event_cross_surface_geometry(); + void event_cross_surface_temperature_field(); void event_collide(); void event_revive_from_secondary(); void event_death(); diff --git a/src/particle.cpp b/src/particle.cpp index 2d609f5d047..09cb1f34f82 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -263,21 +263,29 @@ void Particle::event_advance() double distance_cutoff = (time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY; - // Define minimum distance and stop condition - double distance = distance_cutoff; - next_event() = EVENT_TIME_CUTOFF; + // Initialize next event + next_event().clear(); - if (boundary().distance() < distance) { - distance = boundary().distance(); - next_event() = EVENT_CROSS_SURFACE; - } - if (distance_tmesh < distance) { - distance = distance_tmesh; - next_event() = EVENT_CROSS_TEMPERATURE_MESH; - } - if (collision_distance() < distance) { - distance = collision_distance(); - next_event() = EVENT_COLLIDE; + // Determine minimal distance for cross surface events + double distance_cross_surface = + std::min({boundary().distance(), distance_tmesh}); + + // Determine minimal distance of all events + double distance = + std::min({distance_cross_surface, collision_distance(), distance_cutoff}); + + // Determine next event + if (distance == distance_cutoff) { + next_event().event_type = EVENT_TIME_CUTOFF; + } else { + if (collision_distance() > distance_cross_surface) { + next_event().event_type = EVENT_CROSS_SURFACE; + next_event().cross_surface_geometry = (std::abs(distance - boundary().distance()) <= FP_COINCIDENT); + next_event().cross_surface_temperature_field = + (std::abs(distance - distance_tmesh) <= FP_COINCIDENT); + } else { + next_event().event_type = EVENT_COLLIDE; + } } // Advance particle in space and time @@ -307,16 +315,13 @@ void Particle::event_advance() } } -void Particle::event_cross_temperature_mesh() +void Particle::event_cross_surface_temperature_field() { - // Save previous temperature - sqrtkT_last() = sqrtkT(); - - // Update temperature of the particle - simulation::temperature_field.update_particle_temperature(*this); + // Update temperature field bin + tf_bin() = tf_bin_next(); } -void Particle::event_cross_surface() +void Particle::event_cross_surface_geometry() { // Saving previous cell data for (int j = 0; j < n_coord(); ++j) { @@ -377,6 +382,34 @@ void Particle::event_cross_surface() } } +void Particle::event_cross_surface() +{ + if (next_event().cross_surface_temperature_field) { + event_cross_surface_temperature_field(); + } + if (next_event().cross_surface_geometry) { + event_cross_surface_geometry(); + } + + if (next_event().cross_surface_temperature_field && + !next_event().cross_surface_geometry) { + // Save previous temperature + sqrtkT_last() = sqrtkT(); + + // Update temperature of the particle + if (tf_bin() != C_NONE) { + // Using temperature field if inside the mesh + sqrtkT() = + sqrt(simulation::temperature_field.value(tf_bin()) * K_BOLTZMANN); + } else { + // Using cell temperature if outside the mesh + int i_cell = lowest_coord().cell(); + Cell& c {*model::cells[i_cell]}; + sqrtkT() = c.sqrtkT(cell_instance()); + } + } +} + void Particle::event_collide() { diff --git a/src/simulation.cpp b/src/simulation.cpp index 393181a03b8..56d019a3ebd 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -814,13 +814,10 @@ void transport_history_based_single_particle(Particle& p) p.event_advance(); } if (p.alive()) { - switch (p.next_event()) { + switch (p.next_event().event_type) { case EVENT_CROSS_SURFACE: p.event_cross_surface(); break; - case EVENT_CROSS_TEMPERATURE_MESH: - p.event_cross_temperature_mesh(); - break; case EVENT_COLLIDE: p.event_collide(); break; @@ -828,8 +825,9 @@ void transport_history_based_single_particle(Particle& p) p.wgt() = 0.0; break; default: - fatal_error(fmt::format( - "Unknown event '{}' in history-based transport!", p.next_event())); + fatal_error( + fmt::format("Unknown event '{}' in history-based transport!", + p.next_event().event_type)); break; } } From 5e88589c5ec66b1e2ec864ed4efb5e6c2f8aed20 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 13:38:59 -0500 Subject: [PATCH 50/91] Refactor event-based transport to only have one cross surface event --- include/openmc/event.h | 1 - src/event.cpp | 35 +++-------------------------------- src/simulation.cpp | 5 +---- 3 files changed, 4 insertions(+), 37 deletions(-) diff --git a/include/openmc/event.h b/include/openmc/event.h index 262c1dc44c2..c97d161ce56 100644 --- a/include/openmc/event.h +++ b/include/openmc/event.h @@ -64,7 +64,6 @@ extern SharedArray calculate_nonfuel_xs_queue; extern SharedArray advance_particle_queue; extern SharedArray surface_crossing_queue; extern SharedArray collision_queue; -extern SharedArray temperature_mesh_crossing_queue; // Particle buffer extern vector particles; diff --git a/src/event.cpp b/src/event.cpp index d406ad25868..9aea0deefde 100644 --- a/src/event.cpp +++ b/src/event.cpp @@ -17,7 +17,6 @@ SharedArray calculate_nonfuel_xs_queue; SharedArray advance_particle_queue; SharedArray surface_crossing_queue; SharedArray collision_queue; -SharedArray temperature_mesh_crossing_queue; vector particles; @@ -34,9 +33,6 @@ void init_event_queues(int64_t n_particles) simulation::advance_particle_queue.reserve(n_particles); simulation::surface_crossing_queue.reserve(n_particles); simulation::collision_queue.reserve(n_particles); - if (settings::temperature_field_on) { - simulation::temperature_mesh_crossing_queue.reserve(n_particles); - } simulation::particles.resize(n_particles); } @@ -48,7 +44,6 @@ void free_event_queues(void) simulation::advance_particle_queue.clear(); simulation::surface_crossing_queue.clear(); simulation::collision_queue.clear(); - simulation::temperature_mesh_crossing_queue.clear(); simulation::particles.clear(); } @@ -121,23 +116,19 @@ void process_advance_particle_events() if (!p.alive()) continue; - switch (p.next_event()) { + switch (p.next_event().event_type) { case EVENT_CROSS_SURFACE: simulation::surface_crossing_queue.thread_safe_append({p, buffer_idx}); break; case EVENT_COLLIDE: simulation::collision_queue.thread_safe_append({p, buffer_idx}); break; - case EVENT_CROSS_TEMPERATURE_MESH: - simulation::temperature_mesh_crossing_queue.thread_safe_append( - {p, buffer_idx}); - break; case EVENT_TIME_CUTOFF: p.wgt() = 0.0; break; default: - fatal_error(fmt::format( - "Unknown event '{}' in event-based transport!", p.next_event())); + fatal_error(fmt::format("Unknown event '{}' in event-based transport!", + p.next_event().event_type)); break; } } @@ -185,26 +176,6 @@ void process_collision_events() simulation::time_event_collision.stop(); } -void process_temperature_mesh_crossing_events() -{ - simulation::time_event_temperature_mesh_crossing.start(); - -#pragma omp parallel for schedule(runtime) - for (int64_t i = 0; i < simulation::temperature_mesh_crossing_queue.size(); - i++) { - int64_t buffer_idx = simulation::temperature_mesh_crossing_queue[i].idx; - Particle& p = simulation::particles[buffer_idx]; - p.event_cross_temperature_mesh(); - p.event_revive_from_secondary(); - if (p.alive()) - dispatch_xs_event(buffer_idx); - } - - simulation::temperature_mesh_crossing_queue.resize(0); - - simulation::time_event_temperature_mesh_crossing.stop(); -} - void process_death_events(int64_t n_particles) { simulation::time_event_death.start(); diff --git a/src/simulation.cpp b/src/simulation.cpp index 56d019a3ebd..057fd094097 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -871,8 +871,7 @@ void transport_event_based() simulation::calculate_nonfuel_xs_queue.size(), simulation::advance_particle_queue.size(), simulation::surface_crossing_queue.size(), - simulation::collision_queue.size(), - simulation::temperature_mesh_crossing_queue.size()}); + simulation::collision_queue.size()}); // Execute event with the longest queue if (max == 0) { @@ -887,8 +886,6 @@ void transport_event_based() process_surface_crossing_events(); } else if (max == simulation::collision_queue.size()) { process_collision_events(); - } else if (max == simulation::temperature_mesh_crossing_queue.size()) { - process_temperature_mesh_crossing_events(); } } From 3bde4601bbafb41c00ba995ed875d8adba4fb787 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 15:18:48 -0500 Subject: [PATCH 51/91] Add temperature field bin and bin next --- include/openmc/particle_data.h | 9 +++++++++ src/particle.cpp | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index a63da6c7f48..09d03778d47 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -430,6 +430,12 @@ class GeometryState { const double& density_mult() const { return density_mult_; } double& density_mult_last() { return density_mult_last_; } + // Temperature field related information + int& tf_bin() { return tf_bin_; } + const int& tf_bin() const { return tf_bin_; } + int& tf_bin_next() { return tf_bin_next_; } + const int& tf_bin_next() const { return tf_bin_next_; } + private: int64_t id_ {-1}; //!< Unique ID @@ -461,6 +467,9 @@ class GeometryState { double density_mult_ {1.0}; //!< density multiplier double density_mult_last_ {1.0}; //!< last density multiplier + int tf_bin_ = C_NONE; //!< Current temperature field bin + int tf_bin_next_ = C_NONE; //!< Next temperature field bin + #ifdef OPENMC_DAGMC_ENABLED moab::DagMC::RayHistory history_; Direction last_dir_; diff --git a/src/particle.cpp b/src/particle.cpp index 09cb1f34f82..dc3705e37be 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -128,6 +128,10 @@ void Particle::from_source(const SourceSite* src) fission() = false; zero_flux_derivs(); lifetime() = 0.0; + if (settings::temperature_field_on) { + tf_bin() = C_NONE; + tf_bin_next() = C_NONE; + } #ifdef OPENMC_DAGMC_ENABLED history().reset(); #endif @@ -184,6 +188,15 @@ void Particle::event_calculate_xs() // initiate a search for the current cell. This generally happens at the // beginning of the history and again for any secondary particles if (lowest_coord().cell() == C_NONE) { + + // Define temperature field cell + if (settings::temperature_field_on) { + int bin = simulation::temperature_field.mesh_ptr()->get_bin(r()); + if (bin >= 0 && bin < simulation::temperature_field.values().size()) { + tf_bin() = bin; + } + } + if (!exhaustive_find_cell(*this)) { mark_as_lost( "Could not find the cell containing particle " + std::to_string(id())); From a5f63bf21c8a529cea0d4ef08a91cde68f4413bf Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 15:39:32 -0500 Subject: [PATCH 52/91] Cleaning --- src/particle.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index dc3705e37be..3667c6c3b85 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -263,11 +263,13 @@ void Particle::event_advance() collision_distance() = -std::log(prn(current_seed())) / macro_xs().total; } - // Find the distance to the nearest temperature mesh cell surface + // Find the distance to the nearest temperature mesh cell surface and clear + // the next event double distance_tmesh = INFTY; if (settings::temperature_field_on) { - distance_tmesh = - simulation::temperature_field.distance_to_next_boundary(r(), u()); + distance_tmesh = simulation::temperature_field.distance_to_next_boundary( + tf_bin(), r(), u(), tf_bin_next()); + next_event().clear(); } // Calculate the distance corresponding to the time cutoff @@ -276,9 +278,6 @@ void Particle::event_advance() double distance_cutoff = (time_cutoff < INFTY) ? (time_cutoff - time()) * speed : INFTY; - // Initialize next event - next_event().clear(); - // Determine minimal distance for cross surface events double distance_cross_surface = std::min({boundary().distance(), distance_tmesh}); From 7ca5f8f89de9ccd7e5e79d6dec30256f6478a9dd Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 15:43:49 -0500 Subject: [PATCH 53/91] Temporary function to calculate distance to a regular mesh when the point is outside the mesh --- include/openmc/mesh.h | 28 ++++++++++++++++++++++++++++ src/mesh.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 38f16753f4a..9d8599f7fe7 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -438,6 +438,17 @@ class StructuredMesh : public Mesh { virtual MeshDistance distance_to_grid_boundary(const MeshIndex& ijk, int i, const Position& r0, const Direction& u, double l) const = 0; + //! Find the closest distance from a point to the mesh boundaries that are + //! aligned with the k direction. The point has to be located outside the + //! mesh. + //! + //! \param[in] k direction index of grid surface + //! \param[in] r position, from where to calculate the distance + //! \param[in] u direction of flight + //! \return distance to the mesh boundary + virtual double distance_to_mesh_boundary_from_outside( + int k, const Position& r, const Direction& u) const = 0; + //! Get a label for the mesh bin std::string bin_label(int bin) const override; @@ -490,6 +501,13 @@ class PeriodicStructuredMesh : public StructuredMesh { return r - origin_; }; + double distance_to_mesh_boundary_from_outside( + int k, const Position& r, const Direction& u) const override + { + fatal_error("Not implemented"); + return -1.0; + } + // Data members Position origin_ {0.0, 0.0, 0.0}; //!< Origin of the mesh }; @@ -515,6 +533,9 @@ class RegularMesh : public StructuredMesh { MeshDistance distance_to_grid_boundary(const MeshIndex& ijk, int i, const Position& r0, const Direction& u, double l) const override; + double distance_to_mesh_boundary_from_outside( + int k, const Position& r, const Direction& u) const override; + std::pair, vector> plot( Position plot_ll, Position plot_ur) const override; @@ -568,6 +589,13 @@ class RectilinearMesh : public StructuredMesh { MeshDistance distance_to_grid_boundary(const MeshIndex& ijk, int i, const Position& r0, const Direction& u, double l) const override; + double distance_to_mesh_boundary_from_outside( + int k, const Position& r, const Direction& u) const override + { + fatal_error("Not implemented"); + return -1.0; + } + std::pair, vector> plot( Position plot_ll, Position plot_ur) const override; diff --git a/src/mesh.cpp b/src/mesh.cpp index 758dd97548a..5b36e27cbbf 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1590,6 +1590,34 @@ StructuredMesh::MeshDistance RegularMesh::distance_to_grid_boundary( return d; } +double RegularMesh::distance_to_mesh_boundary_from_outside( + int k, const Position& r, const Direction& u) const +{ + double t; + double distance = INFTY; + + if (u[k] > 0.0) { + t = (lower_left_[k] - r[k]) / u[k]; + } else { + t = (upper_right_[k] - r[k]) / u[k]; + } + + if (t > FP_COINCIDENT) { + bool reenter = true; + for (int i = 0; i < n_dimension_; i++) { + if (i != k) { + double a = r[i] + u[i] * t; + reenter &= (a >= lower_left_[i]); + reenter &= (a <= upper_right_[i]); + } + } + if (reenter) { + distance = t; + } + } + return distance; +} + std::pair, vector> RegularMesh::plot( Position plot_ll, Position plot_ur) const { From bcd208ae73b0254e0c88cdf5da29e1dc75e0d743 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 15:53:52 -0500 Subject: [PATCH 54/91] Change signature of distance_to_next_boundary to get bins information --- include/openmc/field.h | 7 +++++-- include/openmc/mesh.h | 13 +++++++++---- src/field.cpp | 5 +++-- src/mesh.cpp | 5 +++-- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/include/openmc/field.h b/include/openmc/field.h index 19327903f79..973a60dc8bf 100644 --- a/include/openmc/field.h +++ b/include/openmc/field.h @@ -19,14 +19,17 @@ class ScalarField { //---------------------------------------------------------------------------- // Methods - //! Returns the distance to the next mesh boundary gien a particle position + //! Returns the distance to the next mesh boundary given a particle position //! and direction. If the particle is initially outside, the distance will //! correspond to the nearest distance to the outer boundaries of the mesh. // + //! \param[in] current_bin Current bin number //! \param[in] r Position of the particle //! \param[in] u Direction of the particle + //! \param[out] bin_next Next bin number //! \return The distance in cm to the next mesh boundary - double distance_to_next_boundary(const Position& r, const Direction& d); + double distance_to_next_boundary( + int current_bin, const Position& r, const Direction& d, int& bin_next); //---------------------------------------------------------------------------- // Accessors diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 9d8599f7fe7..e3f19de400e 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -198,10 +198,13 @@ class Mesh { //! will be from the initial position to the external boundary //! of the mesh if hit. // + //! \param[in] current_bin Current bin number //! \param[in] r Position of the particle - //! \param[in] u Particle direction + //! \param[in] u Direction of the particle + //! \param[out] bin_next Next bin number //! \return Distance to the next boundary - virtual double distance_to_next_boundary(Position r, Direction u) const = 0; + virtual double distance_to_next_boundary( + int current_bin, Position r, Direction u, int& bin_next) const = 0; //! Get bin at a given position in space // @@ -353,7 +356,8 @@ class StructuredMesh : public Mesh { double get_maximum_outer_distance(StructuredMesh::MeshIndex ijk, std::array distances) const; - double distance_to_next_boundary(Position r, Direction u) const override; + double distance_to_next_boundary( + int current_bin, Position r, Direction u, int& bin_next) const override; //! Determine which cell or surface bins were crossed by a particle // @@ -765,7 +769,8 @@ class UnstructuredMesh : public Mesh { UnstructuredMesh(pugi::xml_node node); UnstructuredMesh(hid_t group); - double distance_to_next_boundary(Position r, Direction u) const override; + double distance_to_next_boundary( + int current_bin, Position r, Direction u, int& bin_next) const override; static const std::string mesh_type; virtual std::string get_mesh_type() const override; diff --git a/src/field.cpp b/src/field.cpp index 8fc4ab2afb9..458d53a2dda 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -31,9 +31,10 @@ ScalarField::ScalarField( } double ScalarField::distance_to_next_boundary( - const Position& r, const Direction& u) + int current_bin, const Position& r, const Direction& u, int& bin_next) { - return this->mesh_ptr()->distance_to_next_boundary(r, u); + return this->mesh_ptr()->distance_to_next_boundary( + current_bin, r, u, bin_next); } double TemperatureField::get_temperature(const Position& r) diff --git a/src/mesh.cpp b/src/mesh.cpp index 5b36e27cbbf..16ac8bdf0e0 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -873,7 +873,7 @@ UnstructuredMesh::UnstructuredMesh(hid_t group) : Mesh(group) } double UnstructuredMesh::distance_to_next_boundary( - Position r, Direction u) const + int current_bin, Position r, Direction u, int& bin_next) const { fatal_error("Not implemented"); return -1.0; @@ -1351,7 +1351,8 @@ double StructuredMesh::get_maximum_outer_distance(StructuredMesh::MeshIndex ijk, return distance; } -double StructuredMesh::distance_to_next_boundary(Position r, Direction u) const +double StructuredMesh::distance_to_next_boundary( + int current_bin, Position r, Direction u, int& bin_next) const { Position global_r = r; Position local_r = local_coords(r); From 92271f9b6b6cb2b298096ad396b2ae55fdd985ad Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 15:55:32 -0500 Subject: [PATCH 55/91] Update distance_to_next_boundary --- src/mesh.cpp | 71 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/src/mesh.cpp b/src/mesh.cpp index 16ac8bdf0e0..716390e86c1 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1357,43 +1357,72 @@ double StructuredMesh::distance_to_next_boundary( Position global_r = r; Position local_r = local_coords(r); - double distance = 0.0; + double distance; bool in_mesh; - StructuredMesh::MeshIndex ijk = get_indices(global_r, in_mesh); + // Find the cell indices + StructuredMesh::MeshIndex ijk; + if (current_bin >= 0) { - // Calculate initial distances to next surfaces in all three dimensions - std::array distances; - for (int k = 0; k < n_dimension_; ++k) { - distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0); - } + ijk = get_indices_from_bin(current_bin); - if (in_mesh) { // inside mesh + // Calculate initial distances to next surfaces in all three dimensions + std::array distances; + for (int k = 0; k < n_dimension_; ++k) { + distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0); + } - // Calculate - distance = get_minimum_distance(distances); + // Find next ijk + auto k_min = + std::min_element(distances.begin(), distances.end()) - distances.begin(); + distance = distances[k_min].distance; + ijk[k_min] = distances[k_min].next_index; - // If the particle is on a surface + // If the particle is on a surface, test using the next index if (distance <= FP_COINCIDENT) { - // Move the particle a bit to avoid calculating the distance to the - // surface the particle is currently on, by selecting the next ijk - ijk = get_indices(global_r + TINY_BIT * u, in_mesh); - // Update distances for (int k = 0; k < n_dimension_; ++k) { distances[k] = distance_to_grid_boundary(ijk, k, local_r, u, 0.0); } - if (in_mesh) { // still inside mesh after moving - distance = get_minimum_distance(distances); - } else { // not inside mesh after moving - distance = get_maximum_outer_distance(ijk, distances); + k_min = std::min_element(distances.begin(), distances.end()) - + distances.begin(); + distance = distances[k_min].distance; + ijk[k_min] = distances[k_min].next_index; + } + + // Determine next bin + in_mesh = true; + for (int k = 0; k < n_dimension_; ++k) { + if ((ijk[k] < 1) || (ijk[k] > shape_[k])) { + in_mesh = false; + } + } + if (in_mesh) { + bin_next = get_bin_from_indices(ijk); + } else { + bin_next = C_NONE; + } + + } else { // Outside mesh + + // Calculate distance to mesh from outside + distance = INFTY; + for (int k = 0; k < n_dimension_; k++) { + double d = distance_to_mesh_boundary_from_outside(k, r, u); + if (d < INFTY) { + distance = d; } } - } else { // not inside mesh - distance = get_maximum_outer_distance(ijk, distances); + // Determine next bin + if (distance < INFTY) { + ijk = get_indices(global_r + (distance + TINY_BIT) * u, in_mesh); + bin_next = get_bin_from_indices(ijk); + } else { + bin_next = C_NONE; + } } return distance; From 0150a02b3e96cd6c5fc1abf4b911666dc6971f71 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 15:56:08 -0500 Subject: [PATCH 56/91] Remove unused functions --- include/openmc/mesh.h | 10 ---------- src/mesh.cpp | 23 ----------------------- 2 files changed, 33 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index e3f19de400e..506c5e43e5b 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -346,16 +346,6 @@ class StructuredMesh : public Mesh { void surface_bins_crossed(Position r0, Position r1, const Direction& u, vector& bins) const override; - //! Find surface with minimal distance to current position - double get_minimum_distance( - std::array distances) const; - - //! For all directions outside the mesh, find the distance that we need - //! to travel to reach the next surface. Use the largest distance, as - //! only this will cross all outer surfaces. - double get_maximum_outer_distance(StructuredMesh::MeshIndex ijk, - std::array distances) const; - double distance_to_next_boundary( int current_bin, Position r, Direction u, int& bin_next) const override; diff --git a/src/mesh.cpp b/src/mesh.cpp index 716390e86c1..671943a7098 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1328,29 +1328,6 @@ void StructuredMesh::surface_bins_crossed( raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins)); } -double StructuredMesh::get_minimum_distance( - std::array distances) const -{ - auto k = - std::min_element(distances.begin(), distances.end()) - distances.begin(); - return distances[k].distance; -} - -double StructuredMesh::get_maximum_outer_distance(StructuredMesh::MeshIndex ijk, - std::array distances) const -{ - double distance = 0.0; - int k_max {-1}; - for (int k = 0; k < n_dimension_; ++k) { - if ((ijk[k] < 1 || ijk[k] > shape_[k]) && - (distances[k].distance > distance)) { - distance = distances[k].distance; - k_max = k; - } - } - return distance; -} - double StructuredMesh::distance_to_next_boundary( int current_bin, Position r, Direction u, int& bin_next) const { From 2311beb6f8495b4a540145ce52362dd86b435c19 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 30 Apr 2026 15:59:58 -0500 Subject: [PATCH 57/91] Update particle temperature after locating the new cell --- src/geometry.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/geometry.cpp b/src/geometry.cpp index 87dc280abcf..83c6c808404 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -178,9 +178,11 @@ bool find_cell_inner( // Set the temperature. p.sqrtkT_last() = p.sqrtkT(); - p.sqrtkT() = c.sqrtkT(p.cell_instance()); - if (settings::temperature_field_on) { - simulation::temperature_field.update_particle_temperature(p); + if (settings::temperature_field_on && p.tf_bin() != C_NONE) { + p.sqrtkT() = + sqrt(simulation::temperature_field.value(p.tf_bin()) * K_BOLTZMANN); + } else { + p.sqrtkT() = c.sqrtkT(p.cell_instance()); } // Set the density multiplier. From e02011899ed36b5d7d596ad451544cb208a913d8 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 1 May 2026 14:20:19 -0500 Subject: [PATCH 58/91] Manage temperature field bins with boundary conditions --- include/openmc/particle_data.h | 5 ++++- src/particle.cpp | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index 09d03778d47..a2a58471cfc 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -431,6 +431,8 @@ class GeometryState { double& density_mult_last() { return density_mult_last_; } // Temperature field related information + int& tf_bin_last() { return tf_bin_last_; } + const int& tf_bin_last() const { return tf_bin_last_; } int& tf_bin() { return tf_bin_; } const int& tf_bin() const { return tf_bin_; } int& tf_bin_next() { return tf_bin_next_; } @@ -467,7 +469,8 @@ class GeometryState { double density_mult_ {1.0}; //!< density multiplier double density_mult_last_ {1.0}; //!< last density multiplier - int tf_bin_ = C_NONE; //!< Current temperature field bin + int tf_bin_last_ = C_NONE; //!< Previous temperature field bin + int tf_bin_ = C_NONE; //!< Current temperature field bin int tf_bin_next_ = C_NONE; //!< Next temperature field bin #ifdef OPENMC_DAGMC_ENABLED diff --git a/src/particle.cpp b/src/particle.cpp index 3667c6c3b85..7715e342492 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -129,6 +129,7 @@ void Particle::from_source(const SourceSite* src) zero_flux_derivs(); lifetime() = 0.0; if (settings::temperature_field_on) { + tf_bin_last() = C_NONE; tf_bin() = C_NONE; tf_bin_next() = C_NONE; } @@ -330,6 +331,7 @@ void Particle::event_advance() void Particle::event_cross_surface_temperature_field() { // Update temperature field bin + tf_bin_last() = tf_bin(); tf_bin() = tf_bin_next(); } @@ -792,6 +794,11 @@ void Particle::cross_reflective_bc(const Surface& surf, Direction new_u) coord(0).cell() = cell_last(0); surface() = -surface(); + // Reassign particle's temperature field bin + if (settings::temperature_field_on) { + tf_bin() = tf_bin_last(); + } + // If a reflective surface is coincident with a lattice or universe // boundary, it is necessary to redetermine the particle's coordinates in // the lower universes. @@ -842,6 +849,16 @@ void Particle::cross_periodic_bc( // Reassign particle's surface surface() = new_surface; + // Reassign particle's temperature field bin + if (settings::temperature_field_on) { + int bin = simulation::temperature_field.mesh_ptr()->get_bin(r()); + if (bin >= 0 && bin < simulation::temperature_field.values().size()) { + tf_bin() = bin; + } else { + tf_bin() = C_NONE; + } + } + // Figure out what cell particle is in now n_coord() = 1; From 9949c5637b343b56a05b92e0630cb6b739a35e29 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 1 May 2026 15:55:04 -0500 Subject: [PATCH 59/91] Clean the temperature field interface --- include/openmc/field.h | 21 ++++++++++----------- src/field.cpp | 34 +++++++++++----------------------- src/geometry.cpp | 3 +-- src/particle.cpp | 24 +++++++----------------- 4 files changed, 29 insertions(+), 53 deletions(-) diff --git a/include/openmc/field.h b/include/openmc/field.h index 973a60dc8bf..e14b39a047f 100644 --- a/include/openmc/field.h +++ b/include/openmc/field.h @@ -71,26 +71,25 @@ class TemperatureField : public ScalarField { //---------------------------------------------------------------------------- // Methods - //! Returns the temperature in Kelvin corresponding to the given position. + //! Returns the temperature in Kelvin corresponding to a given bin number + //! relative to the mesh. // //! \param[in] r Position of the particle //! \return Temperature in Kelvin - double get_temperature(const Position& r); + double get_temperature(int bin); //! Returns the square root of the temperature multiplied by the Boltzmann - //! constant in eV for the given position. + //! constant in eV for a given bin number relative to the mesh. // - //! \param[in] r Position of the particle + //! \param[in] bin Bin number //! \return Sqrt(k_Boltzmann * temperature) in eV - double get_sqrtkT(const Position& r); + double get_sqrtkT(int bin); - //! Update the temperature of a particle based on its position and direction. - //! If the particle is inside the temperature field, its temperature is - //! updated. If outside, the particle takes the temperature value - //! associated with the current cell instance. + //! Returns the bin number corresponding to the location of the particle. // - //! \param[inout] p Particle - void update_particle_temperature(GeometryState& p); + //! \param[in] r Position of the particle + //! \return Corresponding bin number or -1 if outside the mesh + int get_bin(const Position& r); }; } // namespace openmc diff --git a/src/field.cpp b/src/field.cpp index 458d53a2dda..814394ed937 100644 --- a/src/field.cpp +++ b/src/field.cpp @@ -37,43 +37,31 @@ double ScalarField::distance_to_next_boundary( current_bin, r, u, bin_next); } -double TemperatureField::get_temperature(const Position& r) +double TemperatureField::get_temperature(int bin) { - // Get bin from position - int i = this->mesh_ptr()->get_bin(r); - - // If we have a bin, we use it to locate the value - if (i >= 0 && i < this->values().size()) { - return this->value(i); + if (bin >= 0 && bin < values().size()) { + return this->value(bin); } - - // No values were found (outside the mesh) return -1.0; } -double TemperatureField::get_sqrtkT(const Position& r) +double TemperatureField::get_sqrtkT(int bin) { - double temperature = this->get_temperature(r); + double temperature = get_temperature(bin); if (temperature >= 0) { return sqrt(temperature * K_BOLTZMANN); } return -1.0; } -void TemperatureField::update_particle_temperature(GeometryState& p) +int TemperatureField::get_bin(const Position& r) { - // It is assumed that the temperature of the cell instance is already - // known, so we only update the temperature if the particle is inside - // the mesh of the temperature field. - - // Determine the temperature based on the temperature field - double field_sqrtkT = this->get_sqrtkT(p.r() + p.u() * TINY_BIT); - - // If particle inside the mesh, we use the temperature field - if (field_sqrtkT >= 0.) { - p.sqrtkT() = field_sqrtkT; + int bin = mesh_ptr()->get_bin(r); + if (bin >= 0 && bin < values().size()) { + return bin; + } else { + return C_NONE; } - return; } //============================================================================== diff --git a/src/geometry.cpp b/src/geometry.cpp index 83c6c808404..04c0ddc67d1 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -179,8 +179,7 @@ bool find_cell_inner( // Set the temperature. p.sqrtkT_last() = p.sqrtkT(); if (settings::temperature_field_on && p.tf_bin() != C_NONE) { - p.sqrtkT() = - sqrt(simulation::temperature_field.value(p.tf_bin()) * K_BOLTZMANN); + p.sqrtkT() = simulation::temperature_field.get_sqrtkT(p.tf_bin()); } else { p.sqrtkT() = c.sqrtkT(p.cell_instance()); } diff --git a/src/particle.cpp b/src/particle.cpp index 7715e342492..bf4e39966f1 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -192,10 +192,7 @@ void Particle::event_calculate_xs() // Define temperature field cell if (settings::temperature_field_on) { - int bin = simulation::temperature_field.mesh_ptr()->get_bin(r()); - if (bin >= 0 && bin < simulation::temperature_field.values().size()) { - tf_bin() = bin; - } + tf_bin() = simulation::temperature_field.get_bin(r()); } if (!exhaustive_find_cell(*this)) { @@ -330,7 +327,7 @@ void Particle::event_advance() void Particle::event_cross_surface_temperature_field() { - // Update temperature field bin + // Update temperature field bins tf_bin_last() = tf_bin(); tf_bin() = tf_bin_next(); } @@ -405,16 +402,14 @@ void Particle::event_cross_surface() event_cross_surface_geometry(); } - if (next_event().cross_surface_temperature_field && - !next_event().cross_surface_geometry) { - // Save previous temperature + // Update particle temperature if we did not cross the geometry + if (!next_event().cross_surface_geometry) { + sqrtkT_last() = sqrtkT(); - // Update temperature of the particle if (tf_bin() != C_NONE) { // Using temperature field if inside the mesh - sqrtkT() = - sqrt(simulation::temperature_field.value(tf_bin()) * K_BOLTZMANN); + sqrtkT() = simulation::temperature_field.get_sqrtkT(tf_bin()); } else { // Using cell temperature if outside the mesh int i_cell = lowest_coord().cell(); @@ -851,12 +846,7 @@ void Particle::cross_periodic_bc( // Reassign particle's temperature field bin if (settings::temperature_field_on) { - int bin = simulation::temperature_field.mesh_ptr()->get_bin(r()); - if (bin >= 0 && bin < simulation::temperature_field.values().size()) { - tf_bin() = bin; - } else { - tf_bin() = C_NONE; - } + tf_bin() = simulation::temperature_field.get_bin(r()); } // Figure out what cell particle is in now From de054dbffa1dc195df6aa62192c12056ad8e59a7 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 1 May 2026 16:03:57 -0500 Subject: [PATCH 60/91] Update DAGMC part of transport --- src/particle.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index bf4e39966f1..13067596170 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -683,10 +683,10 @@ void Particle::cross_surface(const Surface& surf) cell_instance() = cell_instance_at_level(*this, n_coord() - 1); material() = cell->material(cell_instance()); - sqrtkT() = cell->sqrtkT(cell_instance()); - // Update temperature of the particle if temperature field - if (settings::temperature_field_on) { - simulation::temperature_field.update_particle_temperature(*this); + if (settings::temperature_field_on && tf_bin() != C_NONE) { + sqrtkT() = simulation::temperature_field.get_sqrtkT(tf_bin()); + } else { + sqrtkT() = cell->sqrtkT(cell_instance()); } density_mult() = cell->density_mult(cell_instance()); return; From 6a9bd7460f74edd58f6e1a6e8b3471064c2551d9 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 4 May 2026 17:13:15 -0500 Subject: [PATCH 61/91] Update field cpp unit tests --- tests/cpp_unit_tests/test_field.cpp | 84 +++++------------------------ 1 file changed, 14 insertions(+), 70 deletions(-) diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 58df1fc2df3..6db0fb7c2bc 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -36,39 +36,24 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") TemperatureField temp_field = TemperatureField(&mesh, values); // Get temperature - REQUIRE(temp_field.get_temperature(Position(0.5, 0.5, 0.5)) == 80.0); - REQUIRE(temp_field.get_temperature(Position(-0.5, -0.5, -0.5)) == 10.0); - REQUIRE(temp_field.get_temperature(Position(0.5, -0.5, -0.5)) == 20.0); - REQUIRE(temp_field.get_temperature(Position(-0.5, -0.5, 0.5)) == 50.0); - REQUIRE(temp_field.get_temperature(Position(0.0, 0.0, 0.0)) == 10.0); - REQUIRE(temp_field.get_temperature(Position(2.0, 2.0, 2.0)) == -1.0); + REQUIRE(temp_field.get_temperature(0) == 10.0); + REQUIRE(temp_field.get_temperature(1) == 20.0); + REQUIRE(temp_field.get_temperature(2) == 30.0); + REQUIRE(temp_field.get_temperature(6) == 70.0); + REQUIRE(temp_field.get_temperature(7) == 80.0); + REQUIRE(temp_field.get_temperature(-1) == -1.0); // Get sqrtkT - REQUIRE(temp_field.get_sqrtkT(Position(0.5, 0.5, 0.5)) == - Catch::Approx(0.083029).margin(1.0E-6)); + REQUIRE(temp_field.get_sqrtkT(7) == Catch::Approx(0.083029).margin(1.0E-6)); - // Update particle temperature - model::n_coord_levels = 1; - Particle p; - p.u() = Direction(1.0, 0.0, 0.0); - REQUIRE(p.sqrtkT() == -1.0); - p.r() = Position(0.5, 0.5, 0.5); - temp_field.update_particle_temperature(p); - REQUIRE(p.sqrtkT() == Catch::Approx(0.083029).margin(1.0E-6)); - p.r() = Position(-0.5, -0.5, -0.5); - temp_field.update_particle_temperature(p); - REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); + // Get bin + REQUIRE(temp_field.get_bin(Position(0.5, 0.5, 0.5)) == 7); + REQUIRE(temp_field.get_bin(Position(-0.5, -0.5, -0.5)) == 0); + REQUIRE(temp_field.get_bin(Position(0.5, -0.5, -0.5)) == 1); + REQUIRE(temp_field.get_bin(Position(-0.5, -0.5, 0.5)) == 4); + REQUIRE(temp_field.get_bin(Position(0.0, 0.0, 0.0)) == 0); + REQUIRE(temp_field.get_bin(Position(2.0, 2.0, 2.0)) == -1); - // Check that the temperature is not updated when outside the mesh - p.r() = Position(2.0, 2.0, 2.0); - temp_field.update_particle_temperature(p); - REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); - - // Check that the temperature is not updated when on an external surface with - // the particle is leaving the mesh - p.r() = Position(1.0, 0.0, 0.0); - temp_field.update_particle_temperature(p); - REQUIRE(p.sqrtkT() == Catch::Approx(0.029355).margin(1.0E-6)); } TEST_CASE("Test settings declaration exceptions for a temperature field", @@ -160,44 +145,3 @@ TEST_CASE("Test settings declaration exceptions for a temperature field", REQUIRE_THROWS_WITH(read_settings_xml(root), error); doc.reset(); } - -TEST_CASE("Test distance to next boundary when the point is close to a surface") -{ - // The XML data as a string - std::string xml_string = R"( - - 5 5 5 - 0.0 0.0 0.0 - 10.0 10.0 10.0 - - )"; - - // Create the mesh from a file - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); - pugi::xml_node root = doc.child("mesh"); - auto mesh = RegularMesh(root); - - // Define some temperature values - vector values = {893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, - 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0, 893.0}; - - // Create a temperature field - TemperatureField temp_field = TemperatureField(&mesh, values); - - REQUIRE( - temp_field.distance_to_next_boundary( - Position(2.4284379294472513, 2.7965340367438869, 7.9999999922776048), - Direction(-0.037401146240029437, 0.18369161574591669, - 0.98227213365980526)) == Catch::Approx(0.0000000079).margin(1.0E-10)); -} From b4954d1898c41d920f492f8bdcae9574a57e028a Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 4 May 2026 17:22:46 -0500 Subject: [PATCH 62/91] Update regular mesh unit test with new distance to next boundary interface --- tests/cpp_unit_tests/test_mesh.cpp | 34 +++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/tests/cpp_unit_tests/test_mesh.cpp b/tests/cpp_unit_tests/test_mesh.cpp index 02c53244622..598dad8fd56 100644 --- a/tests/cpp_unit_tests/test_mesh.cpp +++ b/tests/cpp_unit_tests/test_mesh.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -273,28 +274,51 @@ TEST_CASE("Test distance_to_next_boundary() - regular") pugi::xml_node root = doc.child("mesh"); auto mesh = RegularMesh(root); + int current_bin; Position r; Position u; + int next_bin; + double distance; // Test inside the mesh + current_bin = 0; r = Position(0.0, 0.0, 0.0); u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == 1.0); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == 1.0); + REQUIRE(next_bin == -1); // Test outside the mesh, going toward the mesh - r = Position(-1.5, 0.0, 0.0); + current_bin = -1; + r = Position(-2.5, 0.0, 0.0); u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.5); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == 1.5); + REQUIRE(next_bin == 0); // Test outside the mesh, not going toward the mesh + current_bin = -1; r = Position(-2.0, 0.0, 0.0); u = Position(-1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == INFTY); + REQUIRE(next_bin == -1); // Test on the mesh boundary, leaving the mesh + current_bin = 1; r = Position(1.0, 0.0, 0.0); u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == INFTY); + REQUIRE(next_bin == -1); + + // Test close to the mesh boundary + current_bin = 1; + r = Position(0.99999999999, 0.0, 0.0); + u = Position(1.0, 0.0, 0.0); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == Catch::Approx(0.00000000001).margin(1.0E-12)); + REQUIRE(next_bin == -1); } TEST_CASE("Test distance_to_next_boundary() - rectilinear") From 9cf605750eb61b603a39626bd56c23aea5c5886f Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 4 May 2026 17:23:55 -0500 Subject: [PATCH 63/91] Add distance to mesh boundary from outside for rectilinear meshes --- include/openmc/mesh.h | 6 +----- src/mesh.cpp | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 506c5e43e5b..7e1ffe90f26 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -584,11 +584,7 @@ class RectilinearMesh : public StructuredMesh { const Position& r0, const Direction& u, double l) const override; double distance_to_mesh_boundary_from_outside( - int k, const Position& r, const Direction& u) const override - { - fatal_error("Not implemented"); - return -1.0; - } + int k, const Position& r, const Direction& u) const override; std::pair, vector> plot( Position plot_ll, Position plot_ur) const override; diff --git a/src/mesh.cpp b/src/mesh.cpp index 671943a7098..d439d9247d8 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1797,6 +1797,34 @@ StructuredMesh::MeshDistance RectilinearMesh::distance_to_grid_boundary( return d; } +double RectilinearMesh::distance_to_mesh_boundary_from_outside( + int k, const Position& r, const Direction& u) const +{ + double t; + double distance = INFTY; + + if (u[k] > 0.0) { + t = (lower_left_[k] - r[k]) / u[k]; + } else { + t = (upper_right_[k] - r[k]) / u[k]; + } + + if (t > FP_COINCIDENT) { + bool reenter = true; + for (int i = 0; i < n_dimension_; i++) { + if (i != k) { + double a = r[i] + u[i] * t; + reenter &= (a >= lower_left_[i]); + reenter &= (a <= upper_right_[i]); + } + } + if (reenter) { + distance = t; + } + } + return distance; +} + int RectilinearMesh::set_grid() { shape_ = {static_cast(grid_[0].size()) - 1, From 3c9a81016834138573c6ed47e011cf62ff2d1a77 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 4 May 2026 17:24:33 -0500 Subject: [PATCH 64/91] Update unit tests for rectilinear meshes --- tests/cpp_unit_tests/test_mesh.cpp | 45 ++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/tests/cpp_unit_tests/test_mesh.cpp b/tests/cpp_unit_tests/test_mesh.cpp index 598dad8fd56..cdb6985c58e 100644 --- a/tests/cpp_unit_tests/test_mesh.cpp +++ b/tests/cpp_unit_tests/test_mesh.cpp @@ -326,9 +326,9 @@ TEST_CASE("Test distance_to_next_boundary() - rectilinear") // The XML data as a string std::string xml_string = R"( - 0.0 1.0 7.0 10.0 - -10.0 -3.0 0.0 - -10.0 2.0 10.0 + -1.0 0.5 1.0 + -1.0 0.1 1.0 + -1.0 0.2 1.0 )"; @@ -338,28 +338,51 @@ TEST_CASE("Test distance_to_next_boundary() - rectilinear") pugi::xml_node root = doc.child("mesh"); auto mesh = RectilinearMesh(root); + int current_bin; Position r; Position u; + int next_bin; + double distance; // Test inside the mesh - r = Position(5.0, -5.0, 0.0); + current_bin = 0; + r = Position(0.5, 0.1, 0.2); u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == 2.0); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == 0.5); + REQUIRE(next_bin == -1); // Test outside the mesh, going toward the mesh - r = Position(-1.0, -5.0, 0.0); + current_bin = -1; + r = Position(-2.5, 0.0, 0.0); u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == 1.0); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == 1.5); + REQUIRE(next_bin == 0); // Test outside the mesh, not going toward the mesh - r = Position(-1.0, 0.0, 0.0); + current_bin = -1; + r = Position(-2.0, 0.0, 0.0); u = Position(-1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == INFTY); + REQUIRE(next_bin == -1); // Test on the mesh boundary, leaving the mesh - r = Position(10.0, -5.0, 0.0); + current_bin = 1; + r = Position(1.0, 0.0, 0.0); u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == INFTY); + REQUIRE(next_bin == -1); + + // Test close to the mesh boundary + current_bin = 1; + r = Position(0.99999999999, 0.0, 0.0); + u = Position(1.0, 0.0, 0.0); + distance = mesh.distance_to_next_boundary(current_bin, r, u, next_bin); + REQUIRE(distance == Catch::Approx(0.00000000001).margin(1.0E-12)); + REQUIRE(next_bin == -1); } TEST_CASE("Test distance_to_next_boundary() - cylindrical") From 07ad591ef255aeaeb4c1243e4ee7599a2f5c8fe9 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 5 May 2026 11:52:12 -0500 Subject: [PATCH 65/91] Remove tests from features that are no longer available --- tests/cpp_unit_tests/test_mesh.cpp | 84 ------------------------------ 1 file changed, 84 deletions(-) diff --git a/tests/cpp_unit_tests/test_mesh.cpp b/tests/cpp_unit_tests/test_mesh.cpp index cdb6985c58e..0dc993bfbf3 100644 --- a/tests/cpp_unit_tests/test_mesh.cpp +++ b/tests/cpp_unit_tests/test_mesh.cpp @@ -384,87 +384,3 @@ TEST_CASE("Test distance_to_next_boundary() - rectilinear") REQUIRE(distance == Catch::Approx(0.00000000001).margin(1.0E-12)); REQUIRE(next_bin == -1); } - -TEST_CASE("Test distance_to_next_boundary() - cylindrical") -{ - // The XML data as a string - std::string xml_string = R"( - - 0.1 0.2 0.5 1.0 - 0.0 6.283185307179586 - 0.0 .1 0.2 0.4 0.6 1.0 - 0 0 0 - - )"; - - // Create the mesh from a file - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); - pugi::xml_node root = doc.child("mesh"); - auto mesh = CylindricalMesh(root); - - Position r; - Position u; - - // Test inside the mesh - r = Position(0.0, 0.0, 0.5); - u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.1); - - // Test outside the mesh, going toward the mesh - r = Position(-1.5, 0.0, 0.5); - u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.5); - - // Test outside the mesh, not going toward the mesh - r = Position(-2.0, 0.0, 0.0); - u = Position(-1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); - - // Test on the mesh boundary, leaving the mesh - r = Position(1.0, 0.0, 0.5); - u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); -} - -TEST_CASE("Test distance_to_next_boundary() - spherical") -{ - // The XML data as a string - std::string xml_string = R"( - - 0.1 0.2 0.5 1.0 - 0.0 3.141592653589793 - 0.0 6.283185307179586 - 0.0 0.0 0.0 - ' - )"; - - // Create the mesh from a file - pugi::xml_document doc; - pugi::xml_parse_result result = doc.load_string(xml_string.c_str()); - pugi::xml_node root = doc.child("mesh"); - auto mesh = SphericalMesh(root); - - Position r; - Position u; - - // Test inside the mesh - r = Position(0.0, 0.0, 0.0); - u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.1); - - // Test outside the mesh, going toward the mesh - r = Position(-1.5, 0.0, 0.0); - u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == 0.5); - - // Test outside the mesh, not going toward the mesh - r = Position(-2.0, 0.0, 0.0); - u = Position(-1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); - - // Test on the mesh boundary, leaving the mesh - r = Position(1.0, 0.0, 0.0); - u = Position(1.0, 0.0, 0.0); - REQUIRE(mesh.distance_to_next_boundary(r, u) == INFTY); -} From d4b21400f077788e6acf2ad09479bc63c49d5c66 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 5 May 2026 11:59:38 -0500 Subject: [PATCH 66/91] Find the temperature field cell of secondary particles for pulse-height tallies --- src/particle.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/particle.cpp b/src/particle.cpp index 13067596170..23454bbb1e4 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -543,6 +543,12 @@ void Particle::event_revive_from_secondary() // removed from the pulse-height of this cell. if (lowest_coord().cell() == C_NONE) { bool verbose = settings::verbosity >= 10 || trace(); + + // Define temperature field cell + if (settings::temperature_field_on) { + tf_bin() = simulation::temperature_field.get_bin(r()); + } + if (!exhaustive_find_cell(*this, verbose)) { mark_as_lost("Could not find the cell containing particle " + std::to_string(id())); From f397a9ad7aeed9d9439c914aa9d6988bef68711f Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 5 May 2026 17:23:11 -0500 Subject: [PATCH 67/91] Fix reflective boundary conditions with temperature field --- src/particle.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/particle.cpp b/src/particle.cpp index 23454bbb1e4..aa3f3ec4b8c 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -797,7 +797,9 @@ void Particle::cross_reflective_bc(const Surface& surf, Direction new_u) // Reassign particle's temperature field bin if (settings::temperature_field_on) { - tf_bin() = tf_bin_last(); + if (next_event().cross_surface_temperature_field) { + tf_bin() = tf_bin_last(); + } } // If a reflective surface is coincident with a lattice or universe From cd906d7c47d8f4eff154a0f982fb9d5095bf011d Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 5 May 2026 21:44:58 -0500 Subject: [PATCH 68/91] Regression test - box with reflective BC --- .../reflective_bc/verification.py | 116 ++++++++++++++++++ .../reflective_bc/__init__.py | 0 .../{ => reflective_bc}/inputs_true.dat | 11 +- .../reflective_bc/results_true.dat | 19 +++ .../{ => reflective_bc}/test.py | 12 +- .../temperature_field/results_true.dat | 2 - 6 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 tests/regression_tests/temperature_field/_verification/reflective_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/reflective_bc/__init__.py rename tests/regression_tests/temperature_field/{ => reflective_bc}/inputs_true.dat (89%) create mode 100644 tests/regression_tests/temperature_field/reflective_bc/results_true.dat rename tests/regression_tests/temperature_field/{ => reflective_bc}/test.py (83%) mode change 100755 => 100644 delete mode 100644 tests/regression_tests/temperature_field/results_true.dat diff --git a/tests/regression_tests/temperature_field/_verification/reflective_bc/verification.py b/tests/regression_tests/temperature_field/_verification/reflective_bc/verification.py new file mode 100644 index 00000000000..4c729b13378 --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/reflective_bc/verification.py @@ -0,0 +1,116 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell. +The cube has reflective boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Create cells +x_planes = [ + openmc.XPlane(x0=0., boundary_type="reflective"), + openmc.XPlane(x0=2.5), + openmc.XPlane(x0=5., boundary_type="reflective") +] + +y_planes = [ + openmc.YPlane(y0=0., boundary_type="reflective"), + openmc.YPlane(y0=2.5), + openmc.YPlane(y0=5., boundary_type="reflective") +] + +z_planes = [ + openmc.ZPlane(z0=0., boundary_type="reflective"), + openmc.ZPlane(z0=2.5), + openmc.ZPlane(z0=5., boundary_type="reflective") +] + +cells = [] +for iz in range(dim): + for iy in range(dim): + for ix in range(dim): + region = ( + +x_planes[ix] & -x_planes[ix+1] & + +y_planes[iy] & -y_planes[iy+1] & + +z_planes[iz] & -z_planes[iz+1] + ) + cell = openmc.Cell( + fill=mat, + region=region + ) + cells.append(cell) + +root_universe = openmc.Universe(cells=cells) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Set the temperature to each CSG cell +for i, c in enumerate(cells): + c.temperature = temperature_values[i] + +# Register geometry +model.geometry = openmc.Geometry(root_universe) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/reflective_bc/__init__.py b/tests/regression_tests/temperature_field/reflective_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/inputs_true.dat b/tests/regression_tests/temperature_field/reflective_bc/inputs_true.dat similarity index 89% rename from tests/regression_tests/temperature_field/inputs_true.dat rename to tests/regression_tests/temperature_field/reflective_bc/inputs_true.dat index d4fec68fb12..fba95023626 100644 --- a/tests/regression_tests/temperature_field/inputs_true.dat +++ b/tests/regression_tests/temperature_field/reflective_bc/inputs_true.dat @@ -45,4 +45,13 @@ true 1000 - + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/reflective_bc/results_true.dat b/tests/regression_tests/temperature_field/reflective_bc/results_true.dat new file mode 100644 index 00000000000..7e16b60bd4b --- /dev/null +++ b/tests/regression_tests/temperature_field/reflective_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.507597E+00 1.811642E-02 +tally 1: +4.316452E+01 +9.417474E+01 +4.216471E+01 +8.960204E+01 +4.507663E+01 +1.029737E+02 +4.127791E+01 +8.578660E+01 +4.147002E+01 +8.693664E+01 +4.161294E+01 +8.721239E+01 +4.186858E+01 +8.850129E+01 +4.247133E+01 +9.097907E+01 diff --git a/tests/regression_tests/temperature_field/test.py b/tests/regression_tests/temperature_field/reflective_bc/test.py old mode 100755 new mode 100644 similarity index 83% rename from tests/regression_tests/temperature_field/test.py rename to tests/regression_tests/temperature_field/reflective_bc/test.py index 66f2aa4afce..0e34b646f55 --- a/tests/regression_tests/temperature_field/test.py +++ b/tests/regression_tests/temperature_field/reflective_bc/test.py @@ -7,7 +7,7 @@ @pytest.fixture def temperature_field_model(): """Create a temperature field from a regular mesh over a box with - different temperature for each cell. + different temperature for each cell - reflective boundary conditions. """ model = openmc.Model() @@ -24,7 +24,7 @@ def temperature_field_model(): # Create mesh dim = 2 - lower_left = (0., 0., 0.) + lower_left = (0.0, 0.0, 0.0) upper_right = (5.0, 5.0, 5.0) mesh = openmc.RegularMesh() mesh.lower_left = lower_left @@ -58,6 +58,14 @@ def temperature_field_model(): settings.temperature = {'tolerance': 1000, 'multipole': True} model.settings = settings + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + return model diff --git a/tests/regression_tests/temperature_field/results_true.dat b/tests/regression_tests/temperature_field/results_true.dat deleted file mode 100644 index 744dbf08866..00000000000 --- a/tests/regression_tests/temperature_field/results_true.dat +++ /dev/null @@ -1,2 +0,0 @@ -k-combined: -1.507597E+00 1.811642E-02 From 6a8f4a8896ef28225ab33dd008922b6cd288b952 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 5 May 2026 21:55:16 -0500 Subject: [PATCH 69/91] Regression test - box with vacuum BC --- .../_verification/vacuum_bc/verification.py | 116 ++++++++++++++++++ .../temperature_field/vacuum_bc/__init__.py | 0 .../vacuum_bc/inputs_true.dat | 57 +++++++++ .../vacuum_bc/results_true.dat | 19 +++ .../temperature_field/vacuum_bc/test.py | 74 +++++++++++ 5 files changed, 266 insertions(+) create mode 100644 tests/regression_tests/temperature_field/_verification/vacuum_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/vacuum_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/vacuum_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/vacuum_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/vacuum_bc/test.py diff --git a/tests/regression_tests/temperature_field/_verification/vacuum_bc/verification.py b/tests/regression_tests/temperature_field/_verification/vacuum_bc/verification.py new file mode 100644 index 00000000000..7aad432f607 --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/vacuum_bc/verification.py @@ -0,0 +1,116 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell. +The cube has vacuum boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Create cells +x_planes = [ + openmc.XPlane(x0=0., boundary_type="vacuum"), + openmc.XPlane(x0=2.5), + openmc.XPlane(x0=5., boundary_type="vacuum") +] + +y_planes = [ + openmc.YPlane(y0=0., boundary_type="vacuum"), + openmc.YPlane(y0=2.5), + openmc.YPlane(y0=5., boundary_type="vacuum") +] + +z_planes = [ + openmc.ZPlane(z0=0., boundary_type="vacuum"), + openmc.ZPlane(z0=2.5), + openmc.ZPlane(z0=5., boundary_type="vacuum") +] + +cells = [] +for iz in range(dim): + for iy in range(dim): + for ix in range(dim): + region = ( + +x_planes[ix] & -x_planes[ix+1] & + +y_planes[iy] & -y_planes[iy+1] & + +z_planes[iz] & -z_planes[iz+1] + ) + cell = openmc.Cell( + fill=mat, + region=region + ) + cells.append(cell) + +root_universe = openmc.Universe(cells=cells) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Set the temperature to each CSG cell +for i, c in enumerate(cells): + c.temperature = temperature_values[i] + +# Register geometry +model.geometry = openmc.Geometry(root_universe) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/vacuum_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/vacuum_bc/inputs_true.dat b/tests/regression_tests/temperature_field/vacuum_bc/inputs_true.dat new file mode 100644 index 00000000000..63e05c58f8b --- /dev/null +++ b/tests/regression_tests/temperature_field/vacuum_bc/inputs_true.dat @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + 0.0 0.0 0.0 5.0 5.0 5.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/vacuum_bc/results_true.dat b/tests/regression_tests/temperature_field/vacuum_bc/results_true.dat new file mode 100644 index 00000000000..05ff69dd728 --- /dev/null +++ b/tests/regression_tests/temperature_field/vacuum_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +5.071104E-02 2.927355E-03 +tally 1: +2.731604E+00 +4.072231E-01 +3.388567E+00 +6.168992E-01 +2.435725E+00 +3.283434E-01 +2.724446E+00 +4.158287E-01 +3.426649E+00 +6.625678E-01 +2.929812E+00 +4.792204E-01 +2.484602E+00 +3.679255E-01 +2.506827E+00 +3.400903E-01 diff --git a/tests/regression_tests/temperature_field/vacuum_bc/test.py b/tests/regression_tests/temperature_field/vacuum_bc/test.py new file mode 100644 index 00000000000..b3b1f25210a --- /dev/null +++ b/tests/regression_tests/temperature_field/vacuum_bc/test.py @@ -0,0 +1,74 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell - vacuum boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=lower_left[0], xmax=upper_right[0], + ymin=lower_left[1], ymax=upper_right[1], + zmin=lower_left[2], zmax=upper_right[2], + boundary_type="vacuum" + ) + cell = openmc.Cell(fill=mat, region=-box) + model.geometry = openmc.Geometry([cell]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() From 549beb049f0ac1ffc51e8f1de78bdcdc1ba8a760 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 6 May 2026 10:55:55 -0500 Subject: [PATCH 70/91] Simplify the implementation and remove tf_bin_last --- include/openmc/particle.h | 2 - include/openmc/particle_data.h | 3 - src/particle.cpp | 144 +++++++++++++++------------------ 3 files changed, 65 insertions(+), 84 deletions(-) diff --git a/include/openmc/particle.h b/include/openmc/particle.h index 5fdfaeff090..2f6e6196bf1 100644 --- a/include/openmc/particle.h +++ b/include/openmc/particle.h @@ -68,8 +68,6 @@ class Particle : public ParticleData { void event_calculate_xs(); void event_advance(); void event_cross_surface(); - void event_cross_surface_geometry(); - void event_cross_surface_temperature_field(); void event_collide(); void event_revive_from_secondary(); void event_death(); diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index a2a58471cfc..f22f8ce091e 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -431,8 +431,6 @@ class GeometryState { double& density_mult_last() { return density_mult_last_; } // Temperature field related information - int& tf_bin_last() { return tf_bin_last_; } - const int& tf_bin_last() const { return tf_bin_last_; } int& tf_bin() { return tf_bin_; } const int& tf_bin() const { return tf_bin_; } int& tf_bin_next() { return tf_bin_next_; } @@ -469,7 +467,6 @@ class GeometryState { double density_mult_ {1.0}; //!< density multiplier double density_mult_last_ {1.0}; //!< last density multiplier - int tf_bin_last_ = C_NONE; //!< Previous temperature field bin int tf_bin_ = C_NONE; //!< Current temperature field bin int tf_bin_next_ = C_NONE; //!< Next temperature field bin diff --git a/src/particle.cpp b/src/particle.cpp index aa3f3ec4b8c..9c8feb769c7 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -129,7 +129,6 @@ void Particle::from_source(const SourceSite* src) zero_flux_derivs(); lifetime() = 0.0; if (settings::temperature_field_on) { - tf_bin_last() = C_NONE; tf_bin() = C_NONE; tf_bin_next() = C_NONE; } @@ -325,93 +324,78 @@ void Particle::event_advance() } } -void Particle::event_cross_surface_temperature_field() +void Particle::event_cross_surface() { - // Update temperature field bins - tf_bin_last() = tf_bin(); - tf_bin() = tf_bin_next(); -} + if (next_event().cross_surface_geometry) { -void Particle::event_cross_surface_geometry() -{ - // Saving previous cell data - for (int j = 0; j < n_coord(); ++j) { - cell_last(j) = coord(j).cell(); - } - n_coord_last() = n_coord(); - - // Set surface that particle is on and adjust coordinate levels - surface() = boundary().surface(); - n_coord() = boundary().coord_level(); - - if (boundary().lattice_translation()[0] != 0 || - boundary().lattice_translation()[1] != 0 || - boundary().lattice_translation()[2] != 0) { - // Particle crosses lattice boundary - - bool verbose = settings::verbosity >= 10 || trace(); - cross_lattice(*this, boundary(), verbose); - event() = TallyEvent::LATTICE; - - // Score cell to cell partial currents - if (!model::active_surface_tallies.empty()) { - auto& lat {*model::lattices[lowest_coord().lattice()]}; - bool is_valid; - Direction normal = - lat.get_normal(boundary().lattice_translation(), is_valid); - if (is_valid) { - normal /= normal.norm(); - score_surface_tally(*this, model::active_surface_tallies, normal); - } + // Saving previous cell data + for (int j = 0; j < n_coord(); ++j) { + cell_last(j) = coord(j).cell(); } + n_coord_last() = n_coord(); - } else { + // Set surface that particle is on and adjust coordinate levels + surface() = boundary().surface(); + n_coord() = boundary().coord_level(); + + if (boundary().lattice_translation()[0] != 0 || + boundary().lattice_translation()[1] != 0 || + boundary().lattice_translation()[2] != 0) { + // Particle crosses lattice boundary + + bool verbose = settings::verbosity >= 10 || trace(); + cross_lattice(*this, boundary(), verbose); // TODO + event() = TallyEvent::LATTICE; + + // Score cell to cell partial currents + if (!model::active_surface_tallies.empty()) { + auto& lat {*model::lattices[lowest_coord().lattice()]}; + bool is_valid; + Direction normal = + lat.get_normal(boundary().lattice_translation(), is_valid); + if (is_valid) { + normal /= normal.norm(); + score_surface_tally(*this, model::active_surface_tallies, normal); + } + } - const auto& surf {*model::surfaces[surface_index()].get()}; + } else { - // Particle crosses surface - // If BC, add particle to surface source before crossing surface - if (surf.surf_source_ && surf.bc_) { - add_surf_source_to_bank(*this, surf); - } - this->cross_surface(surf); - // If no BC, add particle to surface source after crossing surface - if (surf.surf_source_ && !surf.bc_) { - add_surf_source_to_bank(*this, surf); - } - if (settings::weight_window_checkpoint_surface) { - apply_weight_windows(*this); - } - event() = TallyEvent::SURFACE; + const auto& surf {*model::surfaces[surface_index()].get()}; - // Score cell to cell partial currents - if (!model::active_surface_tallies.empty()) { - Direction normal = surf.normal(r()); - normal /= normal.norm(); - score_surface_tally(*this, model::active_surface_tallies, normal); - } - } -} + // Particle crosses surface + // If BC, add particle to surface source before crossing surface + if (surf.surf_source_ && surf.bc_) { + add_surf_source_to_bank(*this, surf); + } + this->cross_surface(surf); + // If no BC, add particle to surface source after crossing surface + if (surf.surf_source_ && !surf.bc_) { + add_surf_source_to_bank(*this, surf); + } + if (settings::weight_window_checkpoint_surface) { + apply_weight_windows(*this); + } + event() = TallyEvent::SURFACE; -void Particle::event_cross_surface() -{ - if (next_event().cross_surface_temperature_field) { - event_cross_surface_temperature_field(); - } - if (next_event().cross_surface_geometry) { - event_cross_surface_geometry(); - } + // Score cell to cell partial currents + if (!model::active_surface_tallies.empty()) { + Direction normal = surf.normal(r()); + normal /= normal.norm(); + score_surface_tally(*this, model::active_surface_tallies, normal); + } + } - // Update particle temperature if we did not cross the geometry - if (!next_event().cross_surface_geometry) { + // Update particle temperature from the temperature field + } else if (next_event().cross_surface_temperature_field) { sqrtkT_last() = sqrtkT(); + tf_bin() = tf_bin_next(); + if (tf_bin() != C_NONE) { - // Using temperature field if inside the mesh sqrtkT() = simulation::temperature_field.get_sqrtkT(tf_bin()); } else { - // Using cell temperature if outside the mesh int i_cell = lowest_coord().cell(); Cell& c {*model::cells[i_cell]}; sqrtkT() = c.sqrtkT(cell_instance()); @@ -667,6 +651,13 @@ void Particle::cross_surface(const Surface& surf) return; } + // Update temperature field bin + if (settings::temperature_field_on) { + if (next_event().cross_surface_temperature_field) { + tf_bin() = tf_bin_next(); + } + } + // ========================================================================== // SEARCH NEIGHBOR LISTS FOR NEXT CELL @@ -795,12 +786,7 @@ void Particle::cross_reflective_bc(const Surface& surf, Direction new_u) coord(0).cell() = cell_last(0); surface() = -surface(); - // Reassign particle's temperature field bin - if (settings::temperature_field_on) { - if (next_event().cross_surface_temperature_field) { - tf_bin() = tf_bin_last(); - } - } + // Particle's temperature field bin is unchanged // If a reflective surface is coincident with a lattice or universe // boundary, it is necessary to redetermine the particle's coordinates in From a9fff5a6c8858fee2b408350a9213a928440c04d Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 6 May 2026 10:57:24 -0500 Subject: [PATCH 71/91] Fix regular mesh index calculation when point is on the lower boundary --- src/mesh.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mesh.cpp b/src/mesh.cpp index d439d9247d8..2e0921a5906 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -1556,7 +1556,11 @@ RegularMesh::RegularMesh(hid_t group) : StructuredMesh {group} int RegularMesh::get_index_in_direction(double r, int i) const { - return std::ceil((r - lower_left_[i]) / width_[i]); + if (r == lower_left_[i]) { + return 1; + } else { + return std::ceil((r - lower_left_[i]) / width_[i]); + } } const std::string RegularMesh::mesh_type = "regular"; From 2e10a5d1915fe9b9d9036189da065e4791dfb464 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 6 May 2026 11:01:32 -0500 Subject: [PATCH 72/91] Formatting --- include/openmc/particle_data.h | 2 +- src/particle.cpp | 7 ++++--- tests/cpp_unit_tests/test_field.cpp | 1 - 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/openmc/particle_data.h b/include/openmc/particle_data.h index f22f8ce091e..8496f1e3074 100644 --- a/include/openmc/particle_data.h +++ b/include/openmc/particle_data.h @@ -461,7 +461,7 @@ class GeometryState { 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_ {-1.0}; //!< sqrt(k_Boltzmann * temperature) in eV double sqrtkT_last_ {-1.0}; //!< last temperature double density_mult_ {1.0}; //!< density multiplier diff --git a/src/particle.cpp b/src/particle.cpp index 9c8feb769c7..1f9636faef1 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -289,7 +289,8 @@ void Particle::event_advance() } else { if (collision_distance() > distance_cross_surface) { next_event().event_type = EVENT_CROSS_SURFACE; - next_event().cross_surface_geometry = (std::abs(distance - boundary().distance()) <= FP_COINCIDENT); + next_event().cross_surface_geometry = + (std::abs(distance - boundary().distance()) <= FP_COINCIDENT); next_event().cross_surface_temperature_field = (std::abs(distance - distance_tmesh) <= FP_COINCIDENT); } else { @@ -386,8 +387,8 @@ void Particle::event_cross_surface() } } - // Update particle temperature from the temperature field - } else if (next_event().cross_surface_temperature_field) { + // Update particle temperature from the temperature field + } else if (next_event().cross_surface_temperature_field) { sqrtkT_last() = sqrtkT(); diff --git a/tests/cpp_unit_tests/test_field.cpp b/tests/cpp_unit_tests/test_field.cpp index 6db0fb7c2bc..2cfa6432425 100644 --- a/tests/cpp_unit_tests/test_field.cpp +++ b/tests/cpp_unit_tests/test_field.cpp @@ -53,7 +53,6 @@ TEST_CASE("Test TemperatureField functions with a regular mesh") REQUIRE(temp_field.get_bin(Position(-0.5, -0.5, 0.5)) == 4); REQUIRE(temp_field.get_bin(Position(0.0, 0.0, 0.0)) == 0); REQUIRE(temp_field.get_bin(Position(2.0, 2.0, 2.0)) == -1); - } TEST_CASE("Test settings declaration exceptions for a temperature field", From 8f9f44a887788ea37a58176b23482156f9e637f8 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 6 May 2026 11:39:47 -0500 Subject: [PATCH 73/91] Regression test - box with periodic BC --- .../_verification/periodic_bc/verification.py | 120 ++++++++++++++++++ .../temperature_field/periodic_bc/__init__.py | 0 .../periodic_bc/inputs_true.dat | 57 +++++++++ .../periodic_bc/results_true.dat | 19 +++ .../temperature_field/periodic_bc/test.py | 79 ++++++++++++ 5 files changed, 275 insertions(+) create mode 100644 tests/regression_tests/temperature_field/_verification/periodic_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/periodic_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/periodic_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/periodic_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/periodic_bc/test.py diff --git a/tests/regression_tests/temperature_field/_verification/periodic_bc/verification.py b/tests/regression_tests/temperature_field/_verification/periodic_bc/verification.py new file mode 100644 index 00000000000..b76fbc36cfa --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/periodic_bc/verification.py @@ -0,0 +1,120 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell. +The cube has periodic boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Create cells +x_planes = [ + openmc.XPlane(x0=0., boundary_type="periodic"), + openmc.XPlane(x0=2.5), + openmc.XPlane(x0=5., boundary_type="periodic") +] + +y_planes = [ + openmc.YPlane(y0=0., boundary_type="periodic"), + openmc.YPlane(y0=2.5), + openmc.YPlane(y0=5., boundary_type="periodic") +] + +z_planes = [ + openmc.ZPlane(z0=0., boundary_type="periodic"), + openmc.ZPlane(z0=2.5), + openmc.ZPlane(z0=5., boundary_type="periodic") +] + +x_planes[0].periodic_surface = x_planes[2] +y_planes[0].periodic_surface = y_planes[2] +z_planes[0].periodic_surface = z_planes[2] + +cells = [] +for iz in range(dim): + for iy in range(dim): + for ix in range(dim): + region = ( + +x_planes[ix] & -x_planes[ix+1] & + +y_planes[iy] & -y_planes[iy+1] & + +z_planes[iz] & -z_planes[iz+1] + ) + cell = openmc.Cell( + fill=mat, + region=region + ) + cells.append(cell) + +root_universe = openmc.Universe(cells=cells) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Set the temperature to each CSG cell +for i, c in enumerate(cells): + c.temperature = temperature_values[i] + +# Register geometry +model.geometry = openmc.Geometry(root_universe) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/periodic_bc/__init__.py b/tests/regression_tests/temperature_field/periodic_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/periodic_bc/inputs_true.dat b/tests/regression_tests/temperature_field/periodic_bc/inputs_true.dat new file mode 100644 index 00000000000..1d879a3adbd --- /dev/null +++ b/tests/regression_tests/temperature_field/periodic_bc/inputs_true.dat @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + 0.0 0.0 0.0 5.0 5.0 5.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/periodic_bc/results_true.dat b/tests/regression_tests/temperature_field/periodic_bc/results_true.dat new file mode 100644 index 00000000000..bfed5a15158 --- /dev/null +++ b/tests/regression_tests/temperature_field/periodic_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.511081E+00 1.098748E-02 +tally 1: +4.208340E+01 +8.914019E+01 +4.317567E+01 +9.354453E+01 +4.113208E+01 +8.499925E+01 +4.247578E+01 +9.078685E+01 +4.215567E+01 +8.953567E+01 +4.294495E+01 +9.271687E+01 +4.152245E+01 +8.657952E+01 +4.253211E+01 +9.093756E+01 diff --git a/tests/regression_tests/temperature_field/periodic_bc/test.py b/tests/regression_tests/temperature_field/periodic_bc/test.py new file mode 100644 index 00000000000..5a8e25e7f52 --- /dev/null +++ b/tests/regression_tests/temperature_field/periodic_bc/test.py @@ -0,0 +1,79 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell - periodic boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + xmin = openmc.XPlane(x0=0.0, boundary_type="periodic") + xmax = openmc.XPlane(x0=5.0, boundary_type="periodic") + ymin = openmc.YPlane(y0=0.0, boundary_type="periodic") + ymax = openmc.YPlane(y0=5.0, boundary_type="periodic") + zmin = openmc.ZPlane(z0=0.0, boundary_type="periodic") + zmax = openmc.ZPlane(z0=5.0, boundary_type="periodic") + + xmin.periodic_surface = xmax + ymin.periodic_surface = ymax + zmin.periodic_surface = zmax + + cell = openmc.Cell(fill=mat, region=(+xmin & -xmax & +ymin & -ymax & +zmin & -zmax)) + model.geometry = openmc.Geometry([cell]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() From 1ed498a86e32adf60f94b09b0bcca6e17feead84 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 6 May 2026 12:17:42 -0500 Subject: [PATCH 74/91] Regression test - surrounding box with reflective BC --- .../verification.py | 142 ++++++++++++++++++ .../partial_mesh_reflective_bc/__init__.py | 0 .../inputs_true.dat | 64 ++++++++ .../results_true.dat | 19 +++ .../partial_mesh_reflective_bc/test.py | 85 +++++++++++ 5 files changed, 310 insertions(+) create mode 100644 tests/regression_tests/temperature_field/_verification/partial_mesh_reflective_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/partial_mesh_reflective_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/partial_mesh_reflective_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/partial_mesh_reflective_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/partial_mesh_reflective_bc/test.py diff --git a/tests/regression_tests/temperature_field/_verification/partial_mesh_reflective_bc/verification.py b/tests/regression_tests/temperature_field/_verification/partial_mesh_reflective_bc/verification.py new file mode 100644 index 00000000000..1c997a122fd --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/partial_mesh_reflective_bc/verification.py @@ -0,0 +1,142 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +which is surrounded by another cube. +The surrounding cube has reflective boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Create cells +x_planes = [ + openmc.XPlane(x0=-5., boundary_type="reflective"), + openmc.XPlane(x0=0.), + openmc.XPlane(x0=2.5), + openmc.XPlane(x0=5.), + openmc.XPlane(x0=10., boundary_type="reflective") +] + +y_planes = [ + openmc.YPlane(y0=-5., boundary_type="reflective"), + openmc.YPlane(y0=0.), + openmc.YPlane(y0=2.5), + openmc.YPlane(y0=5.), + openmc.YPlane(y0=10., boundary_type="reflective") +] + +z_planes = [ + openmc.ZPlane(z0=-5., boundary_type="reflective"), + openmc.ZPlane(z0=0.), + openmc.ZPlane(z0=2.5), + openmc.ZPlane(z0=5.), + openmc.ZPlane(z0=10., boundary_type="reflective") +] + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +cells = [] +i = 0 +for iz in range(dim): + for iy in range(dim): + for ix in range(dim): + print(ix, iy, iz) + region = ( + +x_planes[ix+1] & -x_planes[ix+2] & + +y_planes[iy+1] & -y_planes[iy+2] & + +z_planes[iz+1] & -z_planes[iz+2] + ) + cell = openmc.Cell( + fill=mat, + region=region + ) + + cell.temperature = temperature_values[i] + cells.append(cell) + i += 1 + +# Surrounding cell +box1_region = (+x_planes[0] & -x_planes[1] & +y_planes[0] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box2_region = (+x_planes[-2] & -x_planes[-1] & +y_planes[0] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box3_region = (+x_planes[1] & -x_planes[-2] & +y_planes[0] & -y_planes[1] & +z_planes[0] & -z_planes[-1]) +box4_region = (+x_planes[1] & -x_planes[-2] & +y_planes[-2] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box5_region = (+x_planes[1] & -x_planes[-2] & +y_planes[1] & -y_planes[-2] & +z_planes[0] & -z_planes[1]) +box6_region = (+x_planes[1] & -x_planes[-2] & +y_planes[1] & -y_planes[-2] & +z_planes[-2] & -z_planes[-1]) + +surrounding_region = box1_region | box2_region | box3_region | box4_region | box5_region | box6_region + +cell = openmc.Cell( + fill=mat, + region=surrounding_region +) +cell.temperature = 250.0 + +cells.append(cell) + +root_universe = openmc.Universe(cells=cells) + +# Register geometry +model.geometry = openmc.Geometry(root_universe) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/__init__.py b/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/inputs_true.dat b/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/inputs_true.dat new file mode 100644 index 00000000000..e13abceaa10 --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/inputs_true.dat @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + -5.0 -5.0 -5.0 10.0 10.0 10.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/results_true.dat b/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/results_true.dat new file mode 100644 index 00000000000..22a999d7015 --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.538904E+00 1.274036E-02 +tally 1: +1.394072E+00 +1.023965E-01 +1.762378E+00 +1.743081E-01 +1.408695E+00 +1.167464E-01 +1.553015E+00 +1.441475E-01 +1.399127E+00 +1.265146E-01 +1.632604E+00 +1.759006E-01 +1.464540E+00 +1.292863E-01 +1.555127E+00 +1.389518E-01 diff --git a/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/test.py b/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/test.py new file mode 100644 index 00000000000..8438ebe60cf --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/test.py @@ -0,0 +1,85 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell. This box is surrounded by a larger + box where the temperature field is not defined and has reflective + boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=lower_left[0], xmax=upper_right[0], + ymin=lower_left[1], ymax=upper_right[1], + zmin=lower_left[2], zmax=upper_right[2] + ) + cell_1 = openmc.Cell(fill=mat, region=-box) + + surrounding_box = openmc.model.RectangularParallelepiped( + xmin=-5.0, xmax=10.0, + ymin=-5.0, ymax=10.0, + zmin=-5.0, zmax=10.0, + boundary_type="reflective" + ) + cell_2 = openmc.Cell(fill=mat, region=(-surrounding_box & +box)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_1, cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() From 29665904736caa8fafc65458a58be727ffe4a6c5 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 6 May 2026 13:37:20 -0500 Subject: [PATCH 75/91] Regression test - surrounding box with vacuum BC --- .../partial_mesh_vacuum_bc/verification.py | 142 ++++++++++++++++++ .../partial_mesh_vacuum_bc/__init__.py | 0 .../partial_mesh_vacuum_bc/inputs_true.dat | 64 ++++++++ .../partial_mesh_vacuum_bc/results_true.dat | 19 +++ .../partial_mesh_vacuum_bc/test.py | 85 +++++++++++ 5 files changed, 310 insertions(+) create mode 100644 tests/regression_tests/temperature_field/_verification/partial_mesh_vacuum_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/test.py diff --git a/tests/regression_tests/temperature_field/_verification/partial_mesh_vacuum_bc/verification.py b/tests/regression_tests/temperature_field/_verification/partial_mesh_vacuum_bc/verification.py new file mode 100644 index 00000000000..3c6ebcc7a96 --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/partial_mesh_vacuum_bc/verification.py @@ -0,0 +1,142 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +which is surrounded by another cube. +The surrounding cube has vacuum boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Create cells +x_planes = [ + openmc.XPlane(x0=-5., boundary_type="vacuum"), + openmc.XPlane(x0=0.), + openmc.XPlane(x0=2.5), + openmc.XPlane(x0=5.), + openmc.XPlane(x0=10., boundary_type="vacuum") +] + +y_planes = [ + openmc.YPlane(y0=-5., boundary_type="vacuum"), + openmc.YPlane(y0=0.), + openmc.YPlane(y0=2.5), + openmc.YPlane(y0=5.), + openmc.YPlane(y0=10., boundary_type="vacuum") +] + +z_planes = [ + openmc.ZPlane(z0=-5., boundary_type="vacuum"), + openmc.ZPlane(z0=0.), + openmc.ZPlane(z0=2.5), + openmc.ZPlane(z0=5.), + openmc.ZPlane(z0=10., boundary_type="vacuum") +] + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +cells = [] +i = 0 +for iz in range(dim): + for iy in range(dim): + for ix in range(dim): + print(ix, iy, iz) + region = ( + +x_planes[ix+1] & -x_planes[ix+2] & + +y_planes[iy+1] & -y_planes[iy+2] & + +z_planes[iz+1] & -z_planes[iz+2] + ) + cell = openmc.Cell( + fill=mat, + region=region + ) + + cell.temperature = temperature_values[i] + cells.append(cell) + i += 1 + +# Surrounding cell +box1_region = (+x_planes[0] & -x_planes[1] & +y_planes[0] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box2_region = (+x_planes[-2] & -x_planes[-1] & +y_planes[0] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box3_region = (+x_planes[1] & -x_planes[-2] & +y_planes[0] & -y_planes[1] & +z_planes[0] & -z_planes[-1]) +box4_region = (+x_planes[1] & -x_planes[-2] & +y_planes[-2] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box5_region = (+x_planes[1] & -x_planes[-2] & +y_planes[1] & -y_planes[-2] & +z_planes[0] & -z_planes[1]) +box6_region = (+x_planes[1] & -x_planes[-2] & +y_planes[1] & -y_planes[-2] & +z_planes[-2] & -z_planes[-1]) + +surrounding_region = box1_region | box2_region | box3_region | box4_region | box5_region | box6_region + +cell = openmc.Cell( + fill=mat, + region=surrounding_region +) +cell.temperature = 250.0 + +cells.append(cell) + +root_universe = openmc.Universe(cells=cells) + +# Register geometry +model.geometry = openmc.Geometry(root_universe) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/inputs_true.dat b/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/inputs_true.dat new file mode 100644 index 00000000000..437cb53c46e --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/inputs_true.dat @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + -5.0 -5.0 -5.0 10.0 10.0 10.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/results_true.dat b/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/results_true.dat new file mode 100644 index 00000000000..0a9618e2d94 --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +3.814464E-01 1.239404E-02 +tally 1: +1.191064E+00 +8.295499E-02 +1.361519E+00 +1.163894E-01 +1.096751E+00 +7.178199E-02 +9.924760E-01 +6.328229E-02 +9.884224E-01 +6.060763E-02 +1.197255E+00 +7.868653E-02 +1.132349E+00 +7.940930E-02 +9.670501E-01 +6.000441E-02 diff --git a/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/test.py b/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/test.py new file mode 100644 index 00000000000..466e70ce0b9 --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/test.py @@ -0,0 +1,85 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell. This box is surrounded by a larger + box where the temperature field is not defined and has vacuum + boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=lower_left[0], xmax=upper_right[0], + ymin=lower_left[1], ymax=upper_right[1], + zmin=lower_left[2], zmax=upper_right[2] + ) + cell_1 = openmc.Cell(fill=mat, region=-box) + + surrounding_box = openmc.model.RectangularParallelepiped( + xmin=-5.0, xmax=10.0, + ymin=-5.0, ymax=10.0, + zmin=-5.0, zmax=10.0, + boundary_type="vacuum" + ) + cell_2 = openmc.Cell(fill=mat, region=(-surrounding_box & +box)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_1, cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() From 2155926de2bccbb83f258ecc95d7abfa9cbf953b Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Wed, 6 May 2026 14:16:31 -0500 Subject: [PATCH 76/91] Regression test - surrounding box with periodic BC --- .../partial_mesh_periodic_bc/verification.py | 146 ++++++++++++++++++ .../partial_mesh_periodic_bc/__init__.py | 0 .../partial_mesh_periodic_bc/inputs_true.dat | 64 ++++++++ .../partial_mesh_periodic_bc/results_true.dat | 19 +++ .../partial_mesh_periodic_bc/test.py | 90 +++++++++++ 5 files changed, 319 insertions(+) create mode 100644 tests/regression_tests/temperature_field/_verification/partial_mesh_periodic_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/partial_mesh_periodic_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/partial_mesh_periodic_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/partial_mesh_periodic_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/partial_mesh_periodic_bc/test.py diff --git a/tests/regression_tests/temperature_field/_verification/partial_mesh_periodic_bc/verification.py b/tests/regression_tests/temperature_field/_verification/partial_mesh_periodic_bc/verification.py new file mode 100644 index 00000000000..44c4d1cb120 --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/partial_mesh_periodic_bc/verification.py @@ -0,0 +1,146 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +which is surrounded by another cube. +The surrounding cube has periodic boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Create cells +x_planes = [ + openmc.XPlane(x0=-5., boundary_type="periodic"), + openmc.XPlane(x0=0.), + openmc.XPlane(x0=2.5), + openmc.XPlane(x0=5.), + openmc.XPlane(x0=10., boundary_type="periodic") +] + +y_planes = [ + openmc.YPlane(y0=-5., boundary_type="periodic"), + openmc.YPlane(y0=0.), + openmc.YPlane(y0=2.5), + openmc.YPlane(y0=5.), + openmc.YPlane(y0=10., boundary_type="periodic") +] + +z_planes = [ + openmc.ZPlane(z0=-5., boundary_type="periodic"), + openmc.ZPlane(z0=0.), + openmc.ZPlane(z0=2.5), + openmc.ZPlane(z0=5.), + openmc.ZPlane(z0=10., boundary_type="periodic") +] + +x_planes[0].periodic_surface = x_planes[-1] +y_planes[0].periodic_surface = y_planes[-1] +z_planes[0].periodic_surface = z_planes[-1] + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +cells = [] +i = 0 +for iz in range(dim): + for iy in range(dim): + for ix in range(dim): + print(ix, iy, iz) + region = ( + +x_planes[ix+1] & -x_planes[ix+2] & + +y_planes[iy+1] & -y_planes[iy+2] & + +z_planes[iz+1] & -z_planes[iz+2] + ) + cell = openmc.Cell( + fill=mat, + region=region + ) + + cell.temperature = temperature_values[i] + cells.append(cell) + i += 1 + +# Surrounding cell +box1_region = (+x_planes[0] & -x_planes[1] & +y_planes[0] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box2_region = (+x_planes[-2] & -x_planes[-1] & +y_planes[0] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box3_region = (+x_planes[1] & -x_planes[-2] & +y_planes[0] & -y_planes[1] & +z_planes[0] & -z_planes[-1]) +box4_region = (+x_planes[1] & -x_planes[-2] & +y_planes[-2] & -y_planes[-1] & +z_planes[0] & -z_planes[-1]) +box5_region = (+x_planes[1] & -x_planes[-2] & +y_planes[1] & -y_planes[-2] & +z_planes[0] & -z_planes[1]) +box6_region = (+x_planes[1] & -x_planes[-2] & +y_planes[1] & -y_planes[-2] & +z_planes[-2] & -z_planes[-1]) + +surrounding_region = box1_region | box2_region | box3_region | box4_region | box5_region | box6_region + +cell = openmc.Cell( + fill=mat, + region=surrounding_region +) +cell.temperature = 250.0 + +cells.append(cell) + +root_universe = openmc.Universe(cells=cells) + +# Register geometry +model.geometry = openmc.Geometry(root_universe) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/__init__.py b/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/inputs_true.dat b/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/inputs_true.dat new file mode 100644 index 00000000000..9c3995177be --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/inputs_true.dat @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + -5.0 -5.0 -5.0 10.0 10.0 10.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/results_true.dat b/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/results_true.dat new file mode 100644 index 00000000000..e29ba3bde07 --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.543179E+00 1.653725E-02 +tally 1: +1.455073E+00 +1.171920E-01 +1.417464E+00 +1.144478E-01 +1.645363E+00 +1.548413E-01 +1.817800E+00 +1.966605E-01 +1.631346E+00 +1.603000E-01 +1.609351E+00 +1.517809E-01 +1.477630E+00 +1.303733E-01 +1.487139E+00 +1.239214E-01 diff --git a/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/test.py b/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/test.py new file mode 100644 index 00000000000..4b1a78ef9ea --- /dev/null +++ b/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/test.py @@ -0,0 +1,90 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell. This box is surrounded by a larger + box where the temperature field is not defined and has vacuum + boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=lower_left[0], xmax=upper_right[0], + ymin=lower_left[1], ymax=upper_right[1], + zmin=lower_left[2], zmax=upper_right[2] + ) + cell_1 = openmc.Cell(fill=mat, region=-box) + + xmin = openmc.XPlane(x0=-5.0, boundary_type="periodic") + xmax = openmc.XPlane(x0=10.0, boundary_type="periodic") + ymin = openmc.YPlane(y0=-5.0, boundary_type="periodic") + ymax = openmc.YPlane(y0=10.0, boundary_type="periodic") + zmin = openmc.ZPlane(z0=-5.0, boundary_type="periodic") + zmax = openmc.ZPlane(z0=10.0, boundary_type="periodic") + + xmin.periodic_surface = xmax + ymin.periodic_surface = ymax + zmin.periodic_surface = zmax + + cell_2 = openmc.Cell(fill=mat, region=((+xmin & -xmax & +ymin & -ymax & +zmin & -zmax) & +box)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_1, cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() From 1901a852406c5750ba82612e9ef085d7ddb261ec Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 14 May 2026 15:50:32 -0500 Subject: [PATCH 77/91] Cleaning --- src/particle.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index 1f9636faef1..d159526b437 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -260,13 +260,11 @@ void Particle::event_advance() collision_distance() = -std::log(prn(current_seed())) / macro_xs().total; } - // Find the distance to the nearest temperature mesh cell surface and clear - // the next event + // Find the distance to the nearest temperature mesh cell surface double distance_tmesh = INFTY; if (settings::temperature_field_on) { distance_tmesh = simulation::temperature_field.distance_to_next_boundary( tf_bin(), r(), u(), tf_bin_next()); - next_event().clear(); } // Calculate the distance corresponding to the time cutoff @@ -284,6 +282,7 @@ void Particle::event_advance() std::min({distance_cross_surface, collision_distance(), distance_cutoff}); // Determine next event + next_event().clear(); if (distance == distance_cutoff) { next_event().event_type = EVENT_TIME_CUTOFF; } else { From f2e1948a1d45e73d47a99d67075a9b8dffab8268 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 14 May 2026 15:52:05 -0500 Subject: [PATCH 78/91] Reset DAGMC history after crossing the temperature field only --- src/particle.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/particle.cpp b/src/particle.cpp index d159526b437..3d35453e407 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -400,6 +400,10 @@ void Particle::event_cross_surface() Cell& c {*model::cells[i_cell]}; sqrtkT() = c.sqrtkT(cell_instance()); } + +#ifdef OPENMC_DAGMC_ENABLED + history().reset(); +#endif } } From 6009008d2468526120d2994b0e21353c1d2e22cf Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 14 May 2026 16:04:34 -0500 Subject: [PATCH 79/91] Define next event in random ray transport --- src/random_ray/random_ray.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/random_ray/random_ray.cpp b/src/random_ray/random_ray.cpp index dde5023e44f..a87f63d1a8a 100644 --- a/src/random_ray/random_ray.cpp +++ b/src/random_ray/random_ray.cpp @@ -288,6 +288,11 @@ void RandomRay::event_advance_ray() boundary() = distance_to_boundary(*this); double distance = boundary().distance(); + // Define next event + next_event().clear(); + next_event().event_type = EVENT_CROSS_SURFACE; + next_event().cross_surface_geometry = true; + if (distance < 0.0) { mark_as_lost("Negative transport distance detected for particle " + std::to_string(id())); From bd6a64f23c7345a3a851c773be3c475f063b0cb9 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Thu, 14 May 2026 16:21:45 -0500 Subject: [PATCH 80/91] Warn that the temperature field is ignored with random ray --- src/random_ray/random_ray_simulation.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/random_ray/random_ray_simulation.cpp b/src/random_ray/random_ray_simulation.cpp index 80cbfc3fe5b..da4c5682e7f 100644 --- a/src/random_ray/random_ray_simulation.cpp +++ b/src/random_ray/random_ray_simulation.cpp @@ -235,6 +235,13 @@ void validate_random_ray_inputs() "quality FW-CADIS weight windows. We recommend you use flat source mode " "when generating weight windows with an overlaid mesh tally."); } + + // Warn about the presence of a temperature field (not supported) + /////////////////////////////////////////////////////////////////// + if (settings::temperature_field_on) { + warning("Temperature fields are not supported with the random ray solver. " + "It will be ignored during this simulation."); + } } void print_adjoint_header() From 0af48acaf7267c9fcd4e61924438274d5077b53b Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 15 May 2026 11:26:05 -0500 Subject: [PATCH 81/91] Update test results because of the fix on regular mesh index when the point is on the lower boundary --- .../weightwindows/survival_biasing/results_true.dat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regression_tests/weightwindows/survival_biasing/results_true.dat b/tests/regression_tests/weightwindows/survival_biasing/results_true.dat index b457a245c7c..8a4d3368cc4 100644 --- a/tests/regression_tests/weightwindows/survival_biasing/results_true.dat +++ b/tests/regression_tests/weightwindows/survival_biasing/results_true.dat @@ -1 +1 @@ -386e507008ed3c72c6e1a101aafc01cfaaff2c0b10555558d1eab6332441f7b17264b55002d721151bac2f3e7c1a8aac4c5ed243f5270533d171850f985206ed \ No newline at end of file +0d7b17d4e364bda2be21feb2e5b2aae4be69fc8ed634c4f45062e8efc61b7bd24dcf448837692fd603afe3756501fe8821d134f2d6289179618f85178ddb8793 \ No newline at end of file From 24e3659eebe0859687cd31097a864e92f1e4e09c Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Fri, 15 May 2026 16:17:48 -0500 Subject: [PATCH 82/91] Manage lattices --- src/particle.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/particle.cpp b/src/particle.cpp index 3d35453e407..480a7b566ec 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -343,8 +343,15 @@ void Particle::event_cross_surface() boundary().lattice_translation()[2] != 0) { // Particle crosses lattice boundary + // Update temperature field bin + if (settings::temperature_field_on) { + if (next_event().cross_surface_temperature_field) { + tf_bin() = tf_bin_next(); + } + } + bool verbose = settings::verbosity >= 10 || trace(); - cross_lattice(*this, boundary(), verbose); // TODO + cross_lattice(*this, boundary(), verbose); event() = TallyEvent::LATTICE; // Score cell to cell partial currents @@ -655,7 +662,7 @@ void Particle::cross_surface(const Surface& surf) return; } - // Update temperature field bin + // Update temperature field bin after handling boundary conditions if (settings::temperature_field_on) { if (next_event().cross_surface_temperature_field) { tf_bin() = tf_bin_next(); From ded9baa3502f892411ee81dfdcf4056ef1047f2f Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 18 May 2026 14:04:54 -0500 Subject: [PATCH 83/91] Reorganize regular single cube regression tests --- .../temperature_field/{ => single_cube}/periodic_bc/__init__.py | 0 .../{ => single_cube}/periodic_bc/inputs_true.dat | 0 .../{ => single_cube}/periodic_bc/results_true.dat | 0 .../temperature_field/{ => single_cube}/periodic_bc/test.py | 0 .../temperature_field/{ => single_cube}/reflective_bc/__init__.py | 0 .../{ => single_cube}/reflective_bc/inputs_true.dat | 0 .../{ => single_cube}/reflective_bc/results_true.dat | 0 .../temperature_field/{ => single_cube}/reflective_bc/test.py | 0 .../temperature_field/{ => single_cube}/vacuum_bc/__init__.py | 0 .../temperature_field/{ => single_cube}/vacuum_bc/inputs_true.dat | 0 .../{ => single_cube}/vacuum_bc/results_true.dat | 0 .../temperature_field/{ => single_cube}/vacuum_bc/test.py | 0 12 files changed, 0 insertions(+), 0 deletions(-) rename tests/regression_tests/temperature_field/{ => single_cube}/periodic_bc/__init__.py (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/periodic_bc/inputs_true.dat (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/periodic_bc/results_true.dat (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/periodic_bc/test.py (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/reflective_bc/__init__.py (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/reflective_bc/inputs_true.dat (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/reflective_bc/results_true.dat (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/reflective_bc/test.py (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/vacuum_bc/__init__.py (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/vacuum_bc/inputs_true.dat (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/vacuum_bc/results_true.dat (100%) rename tests/regression_tests/temperature_field/{ => single_cube}/vacuum_bc/test.py (100%) diff --git a/tests/regression_tests/temperature_field/periodic_bc/__init__.py b/tests/regression_tests/temperature_field/single_cube/periodic_bc/__init__.py similarity index 100% rename from tests/regression_tests/temperature_field/periodic_bc/__init__.py rename to tests/regression_tests/temperature_field/single_cube/periodic_bc/__init__.py diff --git a/tests/regression_tests/temperature_field/periodic_bc/inputs_true.dat b/tests/regression_tests/temperature_field/single_cube/periodic_bc/inputs_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/periodic_bc/inputs_true.dat rename to tests/regression_tests/temperature_field/single_cube/periodic_bc/inputs_true.dat diff --git a/tests/regression_tests/temperature_field/periodic_bc/results_true.dat b/tests/regression_tests/temperature_field/single_cube/periodic_bc/results_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/periodic_bc/results_true.dat rename to tests/regression_tests/temperature_field/single_cube/periodic_bc/results_true.dat diff --git a/tests/regression_tests/temperature_field/periodic_bc/test.py b/tests/regression_tests/temperature_field/single_cube/periodic_bc/test.py similarity index 100% rename from tests/regression_tests/temperature_field/periodic_bc/test.py rename to tests/regression_tests/temperature_field/single_cube/periodic_bc/test.py diff --git a/tests/regression_tests/temperature_field/reflective_bc/__init__.py b/tests/regression_tests/temperature_field/single_cube/reflective_bc/__init__.py similarity index 100% rename from tests/regression_tests/temperature_field/reflective_bc/__init__.py rename to tests/regression_tests/temperature_field/single_cube/reflective_bc/__init__.py diff --git a/tests/regression_tests/temperature_field/reflective_bc/inputs_true.dat b/tests/regression_tests/temperature_field/single_cube/reflective_bc/inputs_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/reflective_bc/inputs_true.dat rename to tests/regression_tests/temperature_field/single_cube/reflective_bc/inputs_true.dat diff --git a/tests/regression_tests/temperature_field/reflective_bc/results_true.dat b/tests/regression_tests/temperature_field/single_cube/reflective_bc/results_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/reflective_bc/results_true.dat rename to tests/regression_tests/temperature_field/single_cube/reflective_bc/results_true.dat diff --git a/tests/regression_tests/temperature_field/reflective_bc/test.py b/tests/regression_tests/temperature_field/single_cube/reflective_bc/test.py similarity index 100% rename from tests/regression_tests/temperature_field/reflective_bc/test.py rename to tests/regression_tests/temperature_field/single_cube/reflective_bc/test.py diff --git a/tests/regression_tests/temperature_field/vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/single_cube/vacuum_bc/__init__.py similarity index 100% rename from tests/regression_tests/temperature_field/vacuum_bc/__init__.py rename to tests/regression_tests/temperature_field/single_cube/vacuum_bc/__init__.py diff --git a/tests/regression_tests/temperature_field/vacuum_bc/inputs_true.dat b/tests/regression_tests/temperature_field/single_cube/vacuum_bc/inputs_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/vacuum_bc/inputs_true.dat rename to tests/regression_tests/temperature_field/single_cube/vacuum_bc/inputs_true.dat diff --git a/tests/regression_tests/temperature_field/vacuum_bc/results_true.dat b/tests/regression_tests/temperature_field/single_cube/vacuum_bc/results_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/vacuum_bc/results_true.dat rename to tests/regression_tests/temperature_field/single_cube/vacuum_bc/results_true.dat diff --git a/tests/regression_tests/temperature_field/vacuum_bc/test.py b/tests/regression_tests/temperature_field/single_cube/vacuum_bc/test.py similarity index 100% rename from tests/regression_tests/temperature_field/vacuum_bc/test.py rename to tests/regression_tests/temperature_field/single_cube/vacuum_bc/test.py From 7f2fd787edea971b08848d7b9fd29d989274acc8 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 18 May 2026 14:16:17 -0500 Subject: [PATCH 84/91] Reorganize regular nested cubes regression tests --- .../{partial_mesh_periodic_bc => nested_cubes}/__init__.py | 0 .../periodic_bc}/__init__.py | 0 .../periodic_bc}/inputs_true.dat | 0 .../periodic_bc}/results_true.dat | 0 .../periodic_bc}/test.py | 0 .../reflective_bc}/__init__.py | 0 .../reflective_bc}/inputs_true.dat | 0 .../reflective_bc}/results_true.dat | 0 .../reflective_bc}/test.py | 0 .../temperature_field/nested_cubes/vacuum_bc/__init__.py | 0 .../vacuum_bc}/inputs_true.dat | 0 .../vacuum_bc}/results_true.dat | 0 .../{partial_mesh_vacuum_bc => nested_cubes/vacuum_bc}/test.py | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename tests/regression_tests/temperature_field/{partial_mesh_periodic_bc => nested_cubes}/__init__.py (100%) rename tests/regression_tests/temperature_field/{partial_mesh_reflective_bc => nested_cubes/periodic_bc}/__init__.py (100%) rename tests/regression_tests/temperature_field/{partial_mesh_periodic_bc => nested_cubes/periodic_bc}/inputs_true.dat (100%) rename tests/regression_tests/temperature_field/{partial_mesh_periodic_bc => nested_cubes/periodic_bc}/results_true.dat (100%) rename tests/regression_tests/temperature_field/{partial_mesh_periodic_bc => nested_cubes/periodic_bc}/test.py (100%) rename tests/regression_tests/temperature_field/{partial_mesh_vacuum_bc => nested_cubes/reflective_bc}/__init__.py (100%) rename tests/regression_tests/temperature_field/{partial_mesh_reflective_bc => nested_cubes/reflective_bc}/inputs_true.dat (100%) rename tests/regression_tests/temperature_field/{partial_mesh_reflective_bc => nested_cubes/reflective_bc}/results_true.dat (100%) rename tests/regression_tests/temperature_field/{partial_mesh_reflective_bc => nested_cubes/reflective_bc}/test.py (100%) create mode 100644 tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/__init__.py rename tests/regression_tests/temperature_field/{partial_mesh_vacuum_bc => nested_cubes/vacuum_bc}/inputs_true.dat (100%) rename tests/regression_tests/temperature_field/{partial_mesh_vacuum_bc => nested_cubes/vacuum_bc}/results_true.dat (100%) rename tests/regression_tests/temperature_field/{partial_mesh_vacuum_bc => nested_cubes/vacuum_bc}/test.py (100%) diff --git a/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/__init__.py b/tests/regression_tests/temperature_field/nested_cubes/__init__.py similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_periodic_bc/__init__.py rename to tests/regression_tests/temperature_field/nested_cubes/__init__.py diff --git a/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/__init__.py b/tests/regression_tests/temperature_field/nested_cubes/periodic_bc/__init__.py similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_reflective_bc/__init__.py rename to tests/regression_tests/temperature_field/nested_cubes/periodic_bc/__init__.py diff --git a/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/inputs_true.dat b/tests/regression_tests/temperature_field/nested_cubes/periodic_bc/inputs_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_periodic_bc/inputs_true.dat rename to tests/regression_tests/temperature_field/nested_cubes/periodic_bc/inputs_true.dat diff --git a/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/results_true.dat b/tests/regression_tests/temperature_field/nested_cubes/periodic_bc/results_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_periodic_bc/results_true.dat rename to tests/regression_tests/temperature_field/nested_cubes/periodic_bc/results_true.dat diff --git a/tests/regression_tests/temperature_field/partial_mesh_periodic_bc/test.py b/tests/regression_tests/temperature_field/nested_cubes/periodic_bc/test.py similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_periodic_bc/test.py rename to tests/regression_tests/temperature_field/nested_cubes/periodic_bc/test.py diff --git a/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/nested_cubes/reflective_bc/__init__.py similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/__init__.py rename to tests/regression_tests/temperature_field/nested_cubes/reflective_bc/__init__.py diff --git a/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/inputs_true.dat b/tests/regression_tests/temperature_field/nested_cubes/reflective_bc/inputs_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_reflective_bc/inputs_true.dat rename to tests/regression_tests/temperature_field/nested_cubes/reflective_bc/inputs_true.dat diff --git a/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/results_true.dat b/tests/regression_tests/temperature_field/nested_cubes/reflective_bc/results_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_reflective_bc/results_true.dat rename to tests/regression_tests/temperature_field/nested_cubes/reflective_bc/results_true.dat diff --git a/tests/regression_tests/temperature_field/partial_mesh_reflective_bc/test.py b/tests/regression_tests/temperature_field/nested_cubes/reflective_bc/test.py similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_reflective_bc/test.py rename to tests/regression_tests/temperature_field/nested_cubes/reflective_bc/test.py diff --git a/tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/inputs_true.dat b/tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/inputs_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/inputs_true.dat rename to tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/inputs_true.dat diff --git a/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/results_true.dat b/tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/results_true.dat similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/results_true.dat rename to tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/results_true.dat diff --git a/tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/test.py b/tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/test.py similarity index 100% rename from tests/regression_tests/temperature_field/partial_mesh_vacuum_bc/test.py rename to tests/regression_tests/temperature_field/nested_cubes/vacuum_bc/test.py From 22f4eb1ba97e220ee781cf5de9162946edc18404 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 18 May 2026 14:16:56 -0500 Subject: [PATCH 85/91] Cleaning --- tests/regression_tests/temperature_field/single_cube/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/regression_tests/temperature_field/single_cube/__init__.py diff --git a/tests/regression_tests/temperature_field/single_cube/__init__.py b/tests/regression_tests/temperature_field/single_cube/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From edb6fb9c3dea21e9db407cfd196ae8e181c1f9d7 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 18 May 2026 14:20:35 -0500 Subject: [PATCH 86/91] Reorganize regular verification scripts --- .../periodic_bc}/verification.py | 0 .../reflective_bc}/verification.py | 0 .../vacuum_bc}/verification.py | 0 .../_verification/{ => single_cube}/periodic_bc/verification.py | 0 .../_verification/{ => single_cube}/reflective_bc/verification.py | 0 .../_verification/{ => single_cube}/vacuum_bc/verification.py | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename tests/regression_tests/temperature_field/_verification/{partial_mesh_periodic_bc => nested_cubes/periodic_bc}/verification.py (100%) rename tests/regression_tests/temperature_field/_verification/{partial_mesh_reflective_bc => nested_cubes/reflective_bc}/verification.py (100%) rename tests/regression_tests/temperature_field/_verification/{partial_mesh_vacuum_bc => nested_cubes/vacuum_bc}/verification.py (100%) rename tests/regression_tests/temperature_field/_verification/{ => single_cube}/periodic_bc/verification.py (100%) rename tests/regression_tests/temperature_field/_verification/{ => single_cube}/reflective_bc/verification.py (100%) rename tests/regression_tests/temperature_field/_verification/{ => single_cube}/vacuum_bc/verification.py (100%) diff --git a/tests/regression_tests/temperature_field/_verification/partial_mesh_periodic_bc/verification.py b/tests/regression_tests/temperature_field/_verification/nested_cubes/periodic_bc/verification.py similarity index 100% rename from tests/regression_tests/temperature_field/_verification/partial_mesh_periodic_bc/verification.py rename to tests/regression_tests/temperature_field/_verification/nested_cubes/periodic_bc/verification.py diff --git a/tests/regression_tests/temperature_field/_verification/partial_mesh_reflective_bc/verification.py b/tests/regression_tests/temperature_field/_verification/nested_cubes/reflective_bc/verification.py similarity index 100% rename from tests/regression_tests/temperature_field/_verification/partial_mesh_reflective_bc/verification.py rename to tests/regression_tests/temperature_field/_verification/nested_cubes/reflective_bc/verification.py diff --git a/tests/regression_tests/temperature_field/_verification/partial_mesh_vacuum_bc/verification.py b/tests/regression_tests/temperature_field/_verification/nested_cubes/vacuum_bc/verification.py similarity index 100% rename from tests/regression_tests/temperature_field/_verification/partial_mesh_vacuum_bc/verification.py rename to tests/regression_tests/temperature_field/_verification/nested_cubes/vacuum_bc/verification.py diff --git a/tests/regression_tests/temperature_field/_verification/periodic_bc/verification.py b/tests/regression_tests/temperature_field/_verification/single_cube/periodic_bc/verification.py similarity index 100% rename from tests/regression_tests/temperature_field/_verification/periodic_bc/verification.py rename to tests/regression_tests/temperature_field/_verification/single_cube/periodic_bc/verification.py diff --git a/tests/regression_tests/temperature_field/_verification/reflective_bc/verification.py b/tests/regression_tests/temperature_field/_verification/single_cube/reflective_bc/verification.py similarity index 100% rename from tests/regression_tests/temperature_field/_verification/reflective_bc/verification.py rename to tests/regression_tests/temperature_field/_verification/single_cube/reflective_bc/verification.py diff --git a/tests/regression_tests/temperature_field/_verification/vacuum_bc/verification.py b/tests/regression_tests/temperature_field/_verification/single_cube/vacuum_bc/verification.py similarity index 100% rename from tests/regression_tests/temperature_field/_verification/vacuum_bc/verification.py rename to tests/regression_tests/temperature_field/_verification/single_cube/vacuum_bc/verification.py From f48b0853d8e83864712f6de81b2fe7086ff79cf7 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Mon, 18 May 2026 16:21:04 -0500 Subject: [PATCH 87/91] DAGMC regression tests + verification scripts --- .../single_cube_multicell_reflecting.h5m | Bin 0 -> 46008 bytes .../single_cube/reflective_bc/verification.py | 77 +++++++++++++++++ .../single_cube_multicell_vacuum.h5m | Bin 0 -> 46008 bytes .../single_cube/vacuum_bc/verification.py | 77 +++++++++++++++++ .../temperature_field/dagmc/__init__.py | 0 .../dagmc/nested_cubes/__init__.py | 0 .../nested_cubes/reflective_bc/__init__.py | 0 .../reflective_bc/inputs_true.dat | 51 ++++++++++++ .../nested_cubes_cell_reflecting.h5m | Bin 0 -> 36816 bytes .../reflective_bc/results_true.dat | 19 +++++ .../dagmc/nested_cubes/reflective_bc/test.py | 78 ++++++++++++++++++ .../dagmc/nested_cubes/vacuum_bc/__init__.py | 0 .../nested_cubes/vacuum_bc/inputs_true.dat | 51 ++++++++++++ .../vacuum_bc/nested_cubes_cell_vacuum.h5m | Bin 0 -> 36816 bytes .../nested_cubes/vacuum_bc/results_true.dat | 19 +++++ .../dagmc/nested_cubes/vacuum_bc/test.py | 78 ++++++++++++++++++ .../dagmc/single_cube/__init__.py | 0 .../single_cube/reflective_bc/__init__.py | 0 .../single_cube/reflective_bc/inputs_true.dat | 51 ++++++++++++ .../reflective_bc/results_true.dat | 19 +++++ .../single_cube_cell_reflecting.h5m | Bin 0 -> 34476 bytes .../dagmc/single_cube/reflective_bc/test.py | 76 +++++++++++++++++ .../dagmc/single_cube/vacuum_bc/__init__.py | 0 .../single_cube/vacuum_bc/inputs_true.dat | 51 ++++++++++++ .../single_cube/vacuum_bc/results_true.dat | 19 +++++ .../vacuum_bc/single_cube_cell_vacuum.h5m | Bin 0 -> 34476 bytes .../dagmc/single_cube/vacuum_bc/test.py | 76 +++++++++++++++++ 27 files changed, 742 insertions(+) create mode 100644 tests/regression_tests/temperature_field/_verification/dagmc/single_cube/reflective_bc/single_cube_multicell_reflecting.h5m create mode 100644 tests/regression_tests/temperature_field/_verification/dagmc/single_cube/reflective_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/_verification/dagmc/single_cube/vacuum_bc/single_cube_multicell_vacuum.h5m create mode 100644 tests/regression_tests/temperature_field/_verification/dagmc/single_cube/vacuum_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/dagmc/__init__.py create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/__init__.py create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/nested_cubes_cell_reflecting.h5m create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/test.py create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/nested_cubes_cell_vacuum.h5m create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/test.py create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/__init__.py create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/single_cube_cell_reflecting.h5m create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/test.py create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/single_cube_cell_vacuum.h5m create mode 100644 tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/test.py diff --git a/tests/regression_tests/temperature_field/_verification/dagmc/single_cube/reflective_bc/single_cube_multicell_reflecting.h5m b/tests/regression_tests/temperature_field/_verification/dagmc/single_cube/reflective_bc/single_cube_multicell_reflecting.h5m new file mode 100644 index 0000000000000000000000000000000000000000..1ea62214f590fa765ca12f4d64581a6308b9aee4 GIT binary patch literal 46008 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/temperature_field/_verification/dagmc/single_cube/vacuum_bc/verification.py b/tests/regression_tests/temperature_field/_verification/dagmc/single_cube/vacuum_bc/verification.py new file mode 100644 index 00000000000..cb2f9865d1b --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/dagmc/single_cube/vacuum_bc/verification.py @@ -0,0 +1,77 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell. +The cube has vacuum boundary conditions. + +The entire geometry is defined as a DAGMC geometry which includes the +boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material(name="fuel") +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Read DAGMC universe +dag_univ = openmc.DAGMCUniverse(filename="single_cube_multicell_vacuum.h5m", auto_geom_ids=True) +model.geometry = openmc.Geometry(dag_univ) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/dagmc/__init__.py b/tests/regression_tests/temperature_field/dagmc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/__init__.py b/tests/regression_tests/temperature_field/dagmc/nested_cubes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/__init__.py b/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/inputs_true.dat b/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/inputs_true.dat new file mode 100644 index 00000000000..25c143085d0 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/inputs_true.dat @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + -5.0 -5.0 -5.0 10.0 10.0 10.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/nested_cubes_cell_reflecting.h5m b/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/nested_cubes_cell_reflecting.h5m new file mode 100644 index 0000000000000000000000000000000000000000..1932d6fe4a24a87df564fcb9b5bc32bfbb1f4b67 GIT binary patch literal 36816 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 Jw;ca_{r~LJdeQ&@ literal 0 HcmV?d00001 diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/results_true.dat b/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/results_true.dat new file mode 100644 index 00000000000..3afa4fd8117 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.515234E+00 9.683233E-03 +tally 1: +1.480117E+00 +1.283100E-01 +1.694708E+00 +1.654352E-01 +1.463603E+00 +1.204571E-01 +1.548104E+00 +1.354957E-01 +1.746713E+00 +1.729295E-01 +1.397141E+00 +1.116072E-01 +1.522349E+00 +1.334629E-01 +1.661876E+00 +1.587775E-01 diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/test.py b/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/test.py new file mode 100644 index 00000000000..b210f8833cb --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/nested_cubes/reflective_bc/test.py @@ -0,0 +1,78 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +pytestmark = pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), + reason="DAGMC is not enabled.") + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell. This box is surrounded by a larger + box where the temperature field is not defined and has reflective + boundary conditions. + + The entire geometry is defined as a DAGMC geometry which includes the + boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material(name="fuel") + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Read DAGMC universe + dag_univ = openmc.DAGMCUniverse(filename='nested_cubes_cell_reflecting.h5m', auto_geom_ids=True) + model.geometry = openmc.Geometry(dag_univ) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/inputs_true.dat b/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/inputs_true.dat new file mode 100644 index 00000000000..4844da91eea --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/inputs_true.dat @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + -5.0 -5.0 -5.0 10.0 10.0 10.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/nested_cubes_cell_vacuum.h5m b/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/nested_cubes_cell_vacuum.h5m new file mode 100644 index 0000000000000000000000000000000000000000..ffc0bc0e583bfbf00b5823e5020dd79d6e990ead GIT binary patch literal 36816 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 Hzt{f{(^Yxj literal 0 HcmV?d00001 diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/results_true.dat b/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/results_true.dat new file mode 100644 index 00000000000..5bd690224e8 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +3.512624E-01 1.436222E-02 +tally 1: +9.836991E-01 +6.331373E-02 +9.860739E-01 +7.076155E-02 +1.051687E+00 +7.416489E-02 +8.660211E-01 +5.339067E-02 +9.091784E-01 +4.821934E-02 +1.146603E+00 +8.947049E-02 +8.048224E-01 +4.748861E-02 +9.541933E-01 +6.135353E-02 diff --git a/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/test.py b/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/test.py new file mode 100644 index 00000000000..63923a935c6 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/nested_cubes/vacuum_bc/test.py @@ -0,0 +1,78 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +pytestmark = pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), + reason="DAGMC is not enabled.") + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell. This box is surrounded by a larger + box where the temperature field is not defined and has vacuum + boundary conditions. + + The entire geometry is defined as a DAGMC geometry which includes the + boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material(name="fuel") + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Read DAGMC universe + dag_univ = openmc.DAGMCUniverse(filename='nested_cubes_cell_vacuum.h5m', auto_geom_ids=True) + model.geometry = openmc.Geometry(dag_univ) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/__init__.py b/tests/regression_tests/temperature_field/dagmc/single_cube/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/__init__.py b/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/inputs_true.dat b/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/inputs_true.dat new file mode 100644 index 00000000000..90e56de9806 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/inputs_true.dat @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + 0.0 0.0 0.0 5.0 5.0 5.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/results_true.dat b/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/results_true.dat new file mode 100644 index 00000000000..7e16b60bd4b --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.507597E+00 1.811642E-02 +tally 1: +4.316452E+01 +9.417474E+01 +4.216471E+01 +8.960204E+01 +4.507663E+01 +1.029737E+02 +4.127791E+01 +8.578660E+01 +4.147002E+01 +8.693664E+01 +4.161294E+01 +8.721239E+01 +4.186858E+01 +8.850129E+01 +4.247133E+01 +9.097907E+01 diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/single_cube_cell_reflecting.h5m b/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/single_cube_cell_reflecting.h5m new file mode 100644 index 0000000000000000000000000000000000000000..b75846f83138d427e50865714c423a1fdbe35edd GIT binary patch literal 34476 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 literal 0 HcmV?d00001 diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/test.py b/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/test.py new file mode 100644 index 00000000000..d9cffcd3e17 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/single_cube/reflective_bc/test.py @@ -0,0 +1,76 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +pytestmark = pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), + reason="DAGMC is not enabled.") + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell - reflective boundary conditions. + + The entire geometry is defined as a DAGMC geometry which includes the + boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material(name="fuel") + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Read DAGMC universe + dag_univ = openmc.DAGMCUniverse(filename='single_cube_cell_reflecting.h5m', auto_geom_ids=True) + model.geometry = openmc.Geometry(dag_univ) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/inputs_true.dat b/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/inputs_true.dat new file mode 100644 index 00000000000..d94b2dcf5a9 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/inputs_true.dat @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + 0.0 0.0 0.0 5.0 5.0 5.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/results_true.dat b/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/results_true.dat new file mode 100644 index 00000000000..05ff69dd728 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +5.071104E-02 2.927355E-03 +tally 1: +2.731604E+00 +4.072231E-01 +3.388567E+00 +6.168992E-01 +2.435725E+00 +3.283434E-01 +2.724446E+00 +4.158287E-01 +3.426649E+00 +6.625678E-01 +2.929812E+00 +4.792204E-01 +2.484602E+00 +3.679255E-01 +2.506827E+00 +3.400903E-01 diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/single_cube_cell_vacuum.h5m b/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/single_cube_cell_vacuum.h5m new file mode 100644 index 0000000000000000000000000000000000000000..2590f485ac98860bcf7b02334818c5e37245694a GIT binary patch literal 34476 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 qep%iwRtwAN^4gVq>7{CQi+<8i<49U literal 0 HcmV?d00001 diff --git a/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/test.py b/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/test.py new file mode 100644 index 00000000000..f7f72b76117 --- /dev/null +++ b/tests/regression_tests/temperature_field/dagmc/single_cube/vacuum_bc/test.py @@ -0,0 +1,76 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +pytestmark = pytest.mark.skipif( + not openmc.lib._dagmc_enabled(), + reason="DAGMC is not enabled.") + + +@pytest.fixture +def temperature_field_model(): + """Create a temperature field from a regular mesh over a box with + different temperature for each cell - vacuum boundary conditions. + + The entire geometry is defined as a DAGMC geometry which includes the + boundary conditions. + + """ + model = openmc.Model() + + # Material + mat = openmc.Material(name="fuel") + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Read DAGMC universe + dag_univ = openmc.DAGMCUniverse(filename='single_cube_cell_vacuum.h5m', auto_geom_ids=True) + model.geometry = openmc.Geometry(dag_univ) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() From 0edb912a914f8e8c303050c5ace43439c1c3de2e Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 19 May 2026 15:03:09 -0500 Subject: [PATCH 88/91] Lattice verification scripts - nested cubes --- .../nested_cubes/periodic_bc/verification.py | 115 ++++++++++++++++++ .../reflective_bc/verification.py | 110 +++++++++++++++++ .../nested_cubes/vacuum_bc/verification.py | 110 +++++++++++++++++ 3 files changed, 335 insertions(+) create mode 100644 tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/periodic_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/reflective_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/vacuum_bc/verification.py diff --git a/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/periodic_bc/verification.py b/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/periodic_bc/verification.py new file mode 100644 index 00000000000..ed1127350de --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/periodic_bc/verification.py @@ -0,0 +1,115 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +which is surrounded by another cube using a lattice. +The surrounding cube has periodic boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Create geometry +box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 +) + +universes = [] +for i, t in enumerate(temperature_values): + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + c.temperature = temperature_values[i] + u.add_cell(c) + universes.append(u) + +lattice = openmc.RectLattice() +lattice.lower_left = (0., 0., 0.) +lattice.pitch = (2.5, 2.5, 2.5) +lattice.universes = [ + [[universes[2], universes[3]], [universes[0], universes[1]]], + [[universes[6], universes[7]], [universes[4], universes[5]]] +] +outer_cell = openmc.Cell(fill=mat) +outer_cell.temperature = 250.0 +lattice.outer = openmc.Universe(cells=[outer_cell]) + +xmin = openmc.XPlane(x0=-5.0, boundary_type="periodic") +xmax = openmc.XPlane(x0=10.0, boundary_type="periodic") +ymin = openmc.YPlane(y0=-5.0, boundary_type="periodic") +ymax = openmc.YPlane(y0=10.0, boundary_type="periodic") +zmin = openmc.ZPlane(z0=-5.0, boundary_type="periodic") +zmax = openmc.ZPlane(z0=10.0, boundary_type="periodic") + +xmin.periodic_surface = xmax +ymin.periodic_surface = ymax +zmin.periodic_surface = zmax + +cell_2 = openmc.Cell(fill=lattice, region=(+xmin & -xmax & +ymin & -ymax & +zmin & -zmax)) + +model.geometry = openmc.Geometry([cell_2]) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/reflective_bc/verification.py b/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/reflective_bc/verification.py new file mode 100644 index 00000000000..2eded430c14 --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/reflective_bc/verification.py @@ -0,0 +1,110 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +which is surrounded by another cube using a lattice. +The surrounding cube has reflective boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Create geometry +box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 +) + +universes = [] +for i, t in enumerate(temperature_values): + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + c.temperature = temperature_values[i] + u.add_cell(c) + universes.append(u) + +lattice = openmc.RectLattice() +lattice.lower_left = (0., 0., 0.) +lattice.pitch = (2.5, 2.5, 2.5) +lattice.universes = [ + [[universes[2], universes[3]], [universes[0], universes[1]]], + [[universes[6], universes[7]], [universes[4], universes[5]]] +] +outer_cell = openmc.Cell(fill=mat) +outer_cell.temperature = 250.0 +lattice.outer = openmc.Universe(cells=[outer_cell]) + +surrounding_box = openmc.model.RectangularParallelepiped( + xmin=-5.0, xmax=10.0, + ymin=-5.0, ymax=10.0, + zmin=-5.0, zmax=10.0, + boundary_type="reflective" +) +cell_2 = openmc.Cell(fill=lattice, region=(-surrounding_box)) + +model.geometry = openmc.Geometry([cell_2]) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/vacuum_bc/verification.py b/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/vacuum_bc/verification.py new file mode 100644 index 00000000000..f441ea6572e --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/lattices/nested_cubes/vacuum_bc/verification.py @@ -0,0 +1,110 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +which is surrounded by another cube using a lattice. +The surrounding cube has vacuum boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Create geometry +box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 +) + +universes = [] +for i, t in enumerate(temperature_values): + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + c.temperature = temperature_values[i] + u.add_cell(c) + universes.append(u) + +lattice = openmc.RectLattice() +lattice.lower_left = (0., 0., 0.) +lattice.pitch = (2.5, 2.5, 2.5) +lattice.universes = [ + [[universes[2], universes[3]], [universes[0], universes[1]]], + [[universes[6], universes[7]], [universes[4], universes[5]]] +] +outer_cell = openmc.Cell(fill=mat) +outer_cell.temperature = 250.0 +lattice.outer = openmc.Universe(cells=[outer_cell]) + +surrounding_box = openmc.model.RectangularParallelepiped( + xmin=-5.0, xmax=10.0, + ymin=-5.0, ymax=10.0, + zmin=-5.0, zmax=10.0, + boundary_type="vacuum" +) +cell_2 = openmc.Cell(fill=lattice, region=(-surrounding_box)) + +model.geometry = openmc.Geometry([cell_2]) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) From a5952fb7374fb128e6fd3993808267430e424fef Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 19 May 2026 15:11:20 -0500 Subject: [PATCH 89/91] Lattice verification scripts - single cube --- .../single_cube/periodic_bc/verification.py | 114 ++++++++++++++++++ .../single_cube/reflective_bc/verification.py | 109 +++++++++++++++++ .../single_cube/vacuum_bc/verification.py | 109 +++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 tests/regression_tests/temperature_field/_verification/lattices/single_cube/periodic_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/_verification/lattices/single_cube/reflective_bc/verification.py create mode 100644 tests/regression_tests/temperature_field/_verification/lattices/single_cube/vacuum_bc/verification.py diff --git a/tests/regression_tests/temperature_field/_verification/lattices/single_cube/periodic_bc/verification.py b/tests/regression_tests/temperature_field/_verification/lattices/single_cube/periodic_bc/verification.py new file mode 100644 index 00000000000..abbb3086fdc --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/lattices/single_cube/periodic_bc/verification.py @@ -0,0 +1,114 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +using a lattice with periodic boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Create geometry +box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 +) + +universes = [] +for i, t in enumerate(temperature_values): + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + c.temperature = temperature_values[i] + u.add_cell(c) + universes.append(u) + +lattice = openmc.RectLattice() +lattice.lower_left = (0., 0., 0.) +lattice.pitch = (2.5, 2.5, 2.5) +lattice.universes = [ + [[universes[2], universes[3]], [universes[0], universes[1]]], + [[universes[6], universes[7]], [universes[4], universes[5]]] +] +outer_cell = openmc.Cell(fill=mat) +outer_cell.temperature = 250.0 +lattice.outer = openmc.Universe(cells=[outer_cell]) + +xmin = openmc.XPlane(x0=0.0, boundary_type="periodic") +xmax = openmc.XPlane(x0=5.0, boundary_type="periodic") +ymin = openmc.YPlane(y0=0.0, boundary_type="periodic") +ymax = openmc.YPlane(y0=5.0, boundary_type="periodic") +zmin = openmc.ZPlane(z0=0.0, boundary_type="periodic") +zmax = openmc.ZPlane(z0=5.0, boundary_type="periodic") + +xmin.periodic_surface = xmax +ymin.periodic_surface = ymax +zmin.periodic_surface = zmax + +cell_2 = openmc.Cell(fill=lattice, region=(+xmin & -xmax & +ymin & -ymax & +zmin & -zmax)) + +model.geometry = openmc.Geometry([cell_2]) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/_verification/lattices/single_cube/reflective_bc/verification.py b/tests/regression_tests/temperature_field/_verification/lattices/single_cube/reflective_bc/verification.py new file mode 100644 index 00000000000..1bec0b16fa3 --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/lattices/single_cube/reflective_bc/verification.py @@ -0,0 +1,109 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +using a lattice with reflective boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Create geometry +box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 +) + +universes = [] +for i, t in enumerate(temperature_values): + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + c.temperature = temperature_values[i] + u.add_cell(c) + universes.append(u) + +lattice = openmc.RectLattice() +lattice.lower_left = (0., 0., 0.) +lattice.pitch = (2.5, 2.5, 2.5) +lattice.universes = [ + [[universes[2], universes[3]], [universes[0], universes[1]]], + [[universes[6], universes[7]], [universes[4], universes[5]]] +] +outer_cell = openmc.Cell(fill=mat) +outer_cell.temperature = 250.0 +lattice.outer = openmc.Universe(cells=[outer_cell]) + +surrounding_box = openmc.model.RectangularParallelepiped( + xmin=0.0, xmax=5.0, + ymin=0.0, ymax=5.0, + zmin=0.0, zmax=5.0, + boundary_type="reflective" +) +cell_2 = openmc.Cell(fill=lattice, region=(-surrounding_box)) + +model.geometry = openmc.Geometry([cell_2]) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) diff --git a/tests/regression_tests/temperature_field/_verification/lattices/single_cube/vacuum_bc/verification.py b/tests/regression_tests/temperature_field/_verification/lattices/single_cube/vacuum_bc/verification.py new file mode 100644 index 00000000000..9f23071be86 --- /dev/null +++ b/tests/regression_tests/temperature_field/_verification/lattices/single_cube/vacuum_bc/verification.py @@ -0,0 +1,109 @@ +"""Runs OpenMC on a 2x2x2 cube with different temperature values for each cell +using a lattice with vacuum boundary conditions. + +A "results.dat" file is created and can be compared to the "results_true.dat" +file from the corresponding case. + +""" + +import openmc +import numpy as np + +model = openmc.Model() + +# Material +mat = openmc.Material() +mat.add_nuclide("U235", 0.2) +mat.add_nuclide("U238", 0.8) +mat.add_element("O", 2.0) +mat.add_element("H", 4.0) +mat.set_density("g/cm3", 5.0) +mat.add_s_alpha_beta('c_H_in_H2O') +model.materials = openmc.Materials([mat]) + +# Create mesh for tallying +dim = 2 +lower_left = (0.0, 0.0, 0.0) +upper_right = (5.0, 5.0, 5.0) +mesh = openmc.RegularMesh() +mesh.lower_left = lower_left +mesh.upper_right = upper_right +mesh.dimension = (dim, dim, dim) + +# Temperature values +temperature_values = [294.0 + i * 100 for i in range(dim**3)] + +# Create geometry +box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 +) + +universes = [] +for i, t in enumerate(temperature_values): + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + c.temperature = temperature_values[i] + u.add_cell(c) + universes.append(u) + +lattice = openmc.RectLattice() +lattice.lower_left = (0., 0., 0.) +lattice.pitch = (2.5, 2.5, 2.5) +lattice.universes = [ + [[universes[2], universes[3]], [universes[0], universes[1]]], + [[universes[6], universes[7]], [universes[4], universes[5]]] +] +outer_cell = openmc.Cell(fill=mat) +outer_cell.temperature = 250.0 +lattice.outer = openmc.Universe(cells=[outer_cell]) + +surrounding_box = openmc.model.RectangularParallelepiped( + xmin=0.0, xmax=5.0, + ymin=0.0, ymax=5.0, + zmin=0.0, zmax=5.0, + boundary_type="vacuum" +) +cell_2 = openmc.Cell(fill=lattice, region=(-surrounding_box)) + +model.geometry = openmc.Geometry([cell_2]) + +# Settings +settings = openmc.Settings() +settings.batches = 20 +settings.particles = 200 +spatial_dist = openmc.stats.Box(lower_left, upper_right) +settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) +settings.temperature = {'tolerance': 1000, 'multipole': True} +model.settings = settings + +# Add tallies +mesh_filter = openmc.MeshFilter(mesh) +mesh_tally = openmc.Tally(name="total reaction rate") +mesh_tally.filters = [mesh_filter] +mesh_tally.scores = ["total"] +tallies = openmc.Tallies([mesh_tally]) +model.tallies = tallies + +model.run() + +with openmc.StatePoint(f"statepoint.20.h5") as sp: + + outstr = 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.keff.n, sp.keff.s) + + for i, tally_ind in enumerate(sp.tallies): + tally = sp.tallies[tally_ind] + results = np.zeros((tally.sum.size * 2, )) + results[0::2] = tally.sum.ravel() + results[1::2] = tally.sum_sq.ravel() + results = ['{0:12.6E}'.format(x) for x in results] + + outstr += 'tally {}:\n'.format(i + 1) + outstr += '\n'.join(results) + '\n' + + with open("results.dat", "w") as res: + res.write(outstr) From 7f96b45b34b2f00a1af507a588e67172d4dbd491 Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 19 May 2026 15:17:39 -0500 Subject: [PATCH 90/91] Regression tests - lattices --- .../temperature_field/lattices/__init__.py | 0 .../lattices/nested_cubes/__init__.py | 0 .../nested_cubes/periodic_bc/__init__.py | 0 .../nested_cubes/periodic_bc/inputs_true.dat | 77 +++++++++++++++ .../nested_cubes/periodic_bc/results_true.dat | 19 ++++ .../lattices/nested_cubes/periodic_bc/test.py | 97 +++++++++++++++++++ .../nested_cubes/reflective_bc/__init__.py | 0 .../reflective_bc/inputs_true.dat | 77 +++++++++++++++ .../reflective_bc/results_true.dat | 19 ++++ .../nested_cubes/reflective_bc/test.py | 92 ++++++++++++++++++ .../nested_cubes/vacuum_bc/__init__.py | 0 .../nested_cubes/vacuum_bc/inputs_true.dat | 77 +++++++++++++++ .../nested_cubes/vacuum_bc/results_true.dat | 19 ++++ .../lattices/nested_cubes/vacuum_bc/test.py | 92 ++++++++++++++++++ .../lattices/single_cube/__init__.py | 0 .../single_cube/periodic_bc/__init__.py | 0 .../single_cube/periodic_bc/inputs_true.dat | 77 +++++++++++++++ .../single_cube/periodic_bc/results_true.dat | 19 ++++ .../lattices/single_cube/periodic_bc/test.py | 97 +++++++++++++++++++ .../single_cube/reflective_bc/__init__.py | 0 .../single_cube/reflective_bc/inputs_true.dat | 77 +++++++++++++++ .../reflective_bc/results_true.dat | 19 ++++ .../single_cube/reflective_bc/test.py | 92 ++++++++++++++++++ .../single_cube/vacuum_bc/__init__.py | 0 .../single_cube/vacuum_bc/inputs_true.dat | 77 +++++++++++++++ .../single_cube/vacuum_bc/results_true.dat | 19 ++++ .../lattices/single_cube/vacuum_bc/test.py | 92 ++++++++++++++++++ 27 files changed, 1138 insertions(+) create mode 100644 tests/regression_tests/temperature_field/lattices/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/test.py create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/test.py create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/test.py create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/test.py create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/test.py create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/__init__.py create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/inputs_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/results_true.dat create mode 100644 tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/test.py diff --git a/tests/regression_tests/temperature_field/lattices/__init__.py b/tests/regression_tests/temperature_field/lattices/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/__init__.py b/tests/regression_tests/temperature_field/lattices/nested_cubes/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/__init__.py b/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/inputs_true.dat b/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/inputs_true.dat new file mode 100644 index 00000000000..6787b1debad --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/inputs_true.dat @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 3 + 2 2 2 + 0.0 0.0 0.0 + +1 1 +1 1 + +1 1 +1 1 + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + -5.0 -5.0 -5.0 10.0 10.0 10.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/results_true.dat b/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/results_true.dat new file mode 100644 index 00000000000..22829229671 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.521509E+00 1.529817E-02 +tally 1: +1.687083E+00 +1.670239E-01 +1.615093E+00 +1.537921E-01 +1.655584E+00 +1.587550E-01 +1.606709E+00 +1.634004E-01 +1.465197E+00 +1.223359E-01 +1.525386E+00 +1.332914E-01 +1.538143E+00 +1.345132E-01 +1.620989E+00 +1.522083E-01 diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/test.py b/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/test.py new file mode 100644 index 00000000000..23fdb4eb740 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/periodic_bc/test.py @@ -0,0 +1,97 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """ + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 + ) + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + u.add_cell(c) + + lattice = openmc.RectLattice() + lattice.lower_left = (0., 0., 0.) + lattice.pitch = (2.5, 2.5, 2.5) + lattice.universes = [ + [[u, u], [u, u]], + [[u, u], [u, u]] + ] + lattice.outer = openmc.Universe(cells=[openmc.Cell(fill=mat)]) + + xmin = openmc.XPlane(x0=-5.0, boundary_type="periodic") + xmax = openmc.XPlane(x0=10.0, boundary_type="periodic") + ymin = openmc.YPlane(y0=-5.0, boundary_type="periodic") + ymax = openmc.YPlane(y0=10.0, boundary_type="periodic") + zmin = openmc.ZPlane(z0=-5.0, boundary_type="periodic") + zmax = openmc.ZPlane(z0=10.0, boundary_type="periodic") + + xmin.periodic_surface = xmax + ymin.periodic_surface = ymax + zmin.periodic_surface = zmax + + cell_2 = openmc.Cell(fill=lattice, region=(+xmin & -xmax & +ymin & -ymax & +zmin & -zmax)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/__init__.py b/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/inputs_true.dat b/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/inputs_true.dat new file mode 100644 index 00000000000..a69be4ad8ca --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/inputs_true.dat @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 3 + 2 2 2 + 0.0 0.0 0.0 + +1 1 +1 1 + +1 1 +1 1 + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + -5.0 -5.0 -5.0 10.0 10.0 10.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/results_true.dat b/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/results_true.dat new file mode 100644 index 00000000000..230771043b7 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.520805E+00 1.313130E-02 +tally 1: +1.620556E+00 +1.493663E-01 +1.646724E+00 +1.566329E-01 +1.504426E+00 +1.323039E-01 +1.519558E+00 +1.371785E-01 +1.319558E+00 +1.104879E-01 +1.454862E+00 +1.313438E-01 +1.380626E+00 +1.192778E-01 +1.409276E+00 +1.269625E-01 diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/test.py b/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/test.py new file mode 100644 index 00000000000..cf767fc4677 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/reflective_bc/test.py @@ -0,0 +1,92 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """ + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 + ) + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + u.add_cell(c) + + lattice = openmc.RectLattice() + lattice.lower_left = (0., 0., 0.) + lattice.pitch = (2.5, 2.5, 2.5) + lattice.universes = [ + [[u, u], [u, u]], + [[u, u], [u, u]] + ] + lattice.outer = openmc.Universe(cells=[openmc.Cell(fill=mat)]) + + surrounding_box = openmc.model.RectangularParallelepiped( + xmin=-5.0, xmax=10.0, + ymin=-5.0, ymax=10.0, + zmin=-5.0, zmax=10.0, + boundary_type="reflective" + ) + cell_2 = openmc.Cell(fill=lattice, region=(-surrounding_box)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/inputs_true.dat b/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/inputs_true.dat new file mode 100644 index 00000000000..9c6feb9d053 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/inputs_true.dat @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 3 + 2 2 2 + 0.0 0.0 0.0 + +1 1 +1 1 + +1 1 +1 1 + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + -5.0 -5.0 -5.0 10.0 10.0 10.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/results_true.dat b/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/results_true.dat new file mode 100644 index 00000000000..b61c89f63d9 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +3.809948E-01 1.507099E-02 +tally 1: +1.047605E+00 +6.859683E-02 +9.076171E-01 +5.159802E-02 +9.946558E-01 +6.870688E-02 +1.088986E+00 +7.784666E-02 +8.504478E-01 +4.647715E-02 +1.056518E+00 +6.550817E-02 +1.037332E+00 +6.681476E-02 +8.884792E-01 +5.126623E-02 diff --git a/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/test.py b/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/test.py new file mode 100644 index 00000000000..41c3e5fdbb3 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/nested_cubes/vacuum_bc/test.py @@ -0,0 +1,92 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """ + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 + ) + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + u.add_cell(c) + + lattice = openmc.RectLattice() + lattice.lower_left = (0., 0., 0.) + lattice.pitch = (2.5, 2.5, 2.5) + lattice.universes = [ + [[u, u], [u, u]], + [[u, u], [u, u]] + ] + lattice.outer = openmc.Universe(cells=[openmc.Cell(fill=mat)]) + + surrounding_box = openmc.model.RectangularParallelepiped( + xmin=-5.0, xmax=10.0, + ymin=-5.0, ymax=10.0, + zmin=-5.0, zmax=10.0, + boundary_type="vacuum" + ) + cell_2 = openmc.Cell(fill=lattice, region=(-surrounding_box)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box((-5.0, -5.0, -5.0), (10.0, 10.0, 10.0)) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/__init__.py b/tests/regression_tests/temperature_field/lattices/single_cube/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/__init__.py b/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/inputs_true.dat b/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/inputs_true.dat new file mode 100644 index 00000000000..f12851efd38 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/inputs_true.dat @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 3 + 2 2 2 + 0.0 0.0 0.0 + +1 1 +1 1 + +1 1 +1 1 + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + 0.0 0.0 0.0 5.0 5.0 5.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/results_true.dat b/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/results_true.dat new file mode 100644 index 00000000000..bfed5a15158 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.511081E+00 1.098748E-02 +tally 1: +4.208340E+01 +8.914019E+01 +4.317567E+01 +9.354453E+01 +4.113208E+01 +8.499925E+01 +4.247578E+01 +9.078685E+01 +4.215567E+01 +8.953567E+01 +4.294495E+01 +9.271687E+01 +4.152245E+01 +8.657952E+01 +4.253211E+01 +9.093756E+01 diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/test.py b/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/test.py new file mode 100644 index 00000000000..de742e855bc --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/periodic_bc/test.py @@ -0,0 +1,97 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """ + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 + ) + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + u.add_cell(c) + + lattice = openmc.RectLattice() + lattice.lower_left = (0., 0., 0.) + lattice.pitch = (2.5, 2.5, 2.5) + lattice.universes = [ + [[u, u], [u, u]], + [[u, u], [u, u]] + ] + lattice.outer = openmc.Universe(cells=[openmc.Cell(fill=mat)]) + + xmin = openmc.XPlane(x0=0.0, boundary_type="periodic") + xmax = openmc.XPlane(x0=5.0, boundary_type="periodic") + ymin = openmc.YPlane(y0=0.0, boundary_type="periodic") + ymax = openmc.YPlane(y0=5.0, boundary_type="periodic") + zmin = openmc.ZPlane(z0=0.0, boundary_type="periodic") + zmax = openmc.ZPlane(z0=5.0, boundary_type="periodic") + + xmin.periodic_surface = xmax + ymin.periodic_surface = ymax + zmin.periodic_surface = zmax + + cell_2 = openmc.Cell(fill=lattice, region=(+xmin & -xmax & +ymin & -ymax & +zmin & -zmax)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/__init__.py b/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/inputs_true.dat b/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/inputs_true.dat new file mode 100644 index 00000000000..c459536bf00 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/inputs_true.dat @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 3 + 2 2 2 + 0.0 0.0 0.0 + +1 1 +1 1 + +1 1 +1 1 + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + 0.0 0.0 0.0 5.0 5.0 5.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/results_true.dat b/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/results_true.dat new file mode 100644 index 00000000000..7e16b60bd4b --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +1.507597E+00 1.811642E-02 +tally 1: +4.316452E+01 +9.417474E+01 +4.216471E+01 +8.960204E+01 +4.507663E+01 +1.029737E+02 +4.127791E+01 +8.578660E+01 +4.147002E+01 +8.693664E+01 +4.161294E+01 +8.721239E+01 +4.186858E+01 +8.850129E+01 +4.247133E+01 +9.097907E+01 diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/test.py b/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/test.py new file mode 100644 index 00000000000..aa70e9cc3d3 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/reflective_bc/test.py @@ -0,0 +1,92 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """ + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 + ) + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + u.add_cell(c) + + lattice = openmc.RectLattice() + lattice.lower_left = (0., 0., 0.) + lattice.pitch = (2.5, 2.5, 2.5) + lattice.universes = [ + [[u, u], [u, u]], + [[u, u], [u, u]] + ] + lattice.outer = openmc.Universe(cells=[openmc.Cell(fill=mat)]) + + surrounding_box = openmc.model.RectangularParallelepiped( + xmin=0.0, xmax=5.0, + ymin=0.0, ymax=5.0, + zmin=0.0, zmax=5.0, + boundary_type="reflective" + ) + cell_2 = openmc.Cell(fill=lattice, region=(-surrounding_box)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/__init__.py b/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/inputs_true.dat b/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/inputs_true.dat new file mode 100644 index 00000000000..b4b9e40ac34 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/inputs_true.dat @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + 2.5 2.5 2.5 + 3 + 2 2 2 + 0.0 0.0 0.0 + +1 1 +1 1 + +1 1 +1 1 + + + + + + + + + + + + + + + + eigenvalue + 200 + 20 + + + 0.0 0.0 0.0 5.0 5.0 5.0 + + + true + + + + 1 + 294.0 394.0 494.0 594.0 694.0 794.0 894.0 994.0 + + + 2 2 2 + 0.0 0.0 0.0 + 5.0 5.0 5.0 + + true + 1000 + + + + 1 + + + 1 + total + + + diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/results_true.dat b/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/results_true.dat new file mode 100644 index 00000000000..05ff69dd728 --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/results_true.dat @@ -0,0 +1,19 @@ +k-combined: +5.071104E-02 2.927355E-03 +tally 1: +2.731604E+00 +4.072231E-01 +3.388567E+00 +6.168992E-01 +2.435725E+00 +3.283434E-01 +2.724446E+00 +4.158287E-01 +3.426649E+00 +6.625678E-01 +2.929812E+00 +4.792204E-01 +2.484602E+00 +3.679255E-01 +2.506827E+00 +3.400903E-01 diff --git a/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/test.py b/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/test.py new file mode 100644 index 00000000000..e4c446974de --- /dev/null +++ b/tests/regression_tests/temperature_field/lattices/single_cube/vacuum_bc/test.py @@ -0,0 +1,92 @@ +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + + +@pytest.fixture +def temperature_field_model(): + """ + """ + model = openmc.Model() + + # Material + mat = openmc.Material() + mat.add_nuclide("U235", 0.2) + mat.add_nuclide("U238", 0.8) + mat.add_element("O", 2.0) + mat.add_element("H", 4.0) + mat.set_density("g/cm3", 5.0) + mat.add_s_alpha_beta('c_H_in_H2O') + model.materials = openmc.Materials([mat]) + + # Create mesh + dim = 2 + lower_left = (0.0, 0.0, 0.0) + upper_right = (5.0, 5.0, 5.0) + mesh = openmc.RegularMesh() + mesh.lower_left = lower_left + mesh.upper_right = upper_right + mesh.dimension = (dim, dim, dim) + + # Temperature values + temperature_values = [294.0 + i * 100 for i in range(dim**3)] + + # Create the temperature field + temperature_field = openmc.TemperatureField(mesh, temperature_values) + + # Create geometry + box = openmc.model.RectangularParallelepiped( + xmin=-1.75, xmax=1.75, + ymin=-1.75, ymax=1.75, + zmin=-1.75, zmax=1.75 + ) + u = openmc.Universe() + c = openmc.Cell(fill=mat, region=-box) + u.add_cell(c) + + lattice = openmc.RectLattice() + lattice.lower_left = (0., 0., 0.) + lattice.pitch = (2.5, 2.5, 2.5) + lattice.universes = [ + [[u, u], [u, u]], + [[u, u], [u, u]] + ] + lattice.outer = openmc.Universe(cells=[openmc.Cell(fill=mat)]) + + surrounding_box = openmc.model.RectangularParallelepiped( + xmin=0.0, xmax=5.0, + ymin=0.0, ymax=5.0, + zmin=0.0, zmax=5.0, + boundary_type="vacuum" + ) + cell_2 = openmc.Cell(fill=lattice, region=(-surrounding_box)) + cell_2.temperature = 250.0 + + model.geometry = openmc.Geometry([cell_2]) + + # Settings + settings = openmc.Settings() + settings.batches = 20 + settings.particles = 200 + settings.temperature_field = temperature_field + spatial_dist = openmc.stats.Box(lower_left, upper_right) + settings.source = openmc.IndependentSource( + space=spatial_dist, constraints={"fissionable": True}) + settings.temperature = {'tolerance': 1000, 'multipole': True} + model.settings = settings + + # Add tallies + mesh_filter = openmc.MeshFilter(mesh) + mesh_tally = openmc.Tally(name="total reaction rate") + mesh_tally.filters = [mesh_filter] + mesh_tally.scores = ["total"] + tallies = openmc.Tallies([mesh_tally]) + model.tallies = tallies + + return model + + +def test_temperature_field(temperature_field_model): + harness = PyAPITestHarness('statepoint.20.h5', temperature_field_model) + harness.main() From c2bb44df6639265d741c6e629304048ac6ecb0fc Mon Sep 17 00:00:00 2001 From: Joffrey Dorville Date: Tue, 19 May 2026 15:43:30 -0500 Subject: [PATCH 91/91] README file for temperature field regression tests --- .../temperature_field/README.rst | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/regression_tests/temperature_field/README.rst diff --git a/tests/regression_tests/temperature_field/README.rst b/tests/regression_tests/temperature_field/README.rst new file mode 100644 index 00000000000..b035c9aa601 --- /dev/null +++ b/tests/regression_tests/temperature_field/README.rst @@ -0,0 +1,29 @@ +Temperature field regression tests +================================== + +These regressions tests use two models: +- single_cube: a 5x5x5cm cube, +- nested_cubes: a 5x5x5cm cube surrounded by a 15x15x15cm cube. + +Three types of boundary conditions are tested: +- vacuum, +- reflective, +- periodic (except with DAGMC geometries). + +The temperature field uses a 2x2x2 regular mesh where each cell has its own +temperature value (built by starting with 294K and adding increments of 100K +to assign the next cell's temperature). When modeled, the temperature of the +surrounding cube is set to 250K. + +For DAGMC geometries, the boundary conditions and temperature values are +directly stored in the DAGMC files created with a modified version of +stellarmesh. + +Verifications scripts are made available in **_verification**. They are designed +to compare the results of the regression tests to a strictly equivalent model +where the tracks of particles are expected to be identical. These scripts +should be run using similar settings than what is used with the regression tests. + +.. warning:: The nested cubes verification scripts for dagmc are currently missing + because there are errors with the geometry. These errors are currently under + investigation and the scripts will be made available once the problem is fixed.