From a2919532a90f339429270006c9f432f046021fe9 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 3 Feb 2026 10:21:25 -0600 Subject: [PATCH 01/17] Initial implementation of the XDG mesh --- CMakeLists.txt | 16 ++ include/openmc/mesh.h | 41 +++- include/openmc/xdg.h | 114 +++++++++ openmc/lib/__init__.py | 3 + openmc/mesh.py | 6 +- openmc/xdg.py | 541 +++++++++++++++++++++++++++++++++++++++++ src/initialize.cpp | 9 + src/mesh.cpp | 58 ++--- src/xdg.cpp | 213 ++++++++++++++++ 9 files changed, 955 insertions(+), 46 deletions(-) create mode 100644 include/openmc/xdg.h create mode 100644 openmc/xdg.py create mode 100644 src/xdg.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9fe133a22e3..8cb5dcba71c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ option(OPENMC_BUILD_TESTS "Build tests" option(OPENMC_ENABLE_PROFILE "Compile with profiling flags" OFF) option(OPENMC_ENABLE_COVERAGE "Compile with coverage analysis flags" OFF) option(OPENMC_USE_DAGMC "Enable support for DAGMC (CAD) geometry" OFF) +option(OPENMC_USE_XDG "Enable support for XDG discretized CAD geometry" OFF) option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tallies" OFF) option(OPENMC_USE_MPI "Enable MPI" OFF) option(OPENMC_USE_UWUW "Enable UWUW" OFF) @@ -156,6 +157,15 @@ if(OPENMC_USE_DAGMC) endif() endif() +#=============================================================================== +# XDG Mesh and Geometry Support +#=============================================================================== + +if(OPENMC_USE_XDG) + find_package(XDG REQUIRED PATH_SUFFIXES lib/cmake) + message(STATUS "Found XDG: ${XDG_DIR} (version ${XDG_VERSION})") +endif() + #=============================================================================== # libMesh Unstructured Mesh Support #=============================================================================== @@ -428,6 +438,7 @@ list(APPEND libopenmc_SOURCES src/string_utils.cpp src/summary.cpp src/surface.cpp + src/xdg.cpp src/tallies/derivative.cpp src/tallies/filter.cpp src/tallies/filter_azimuthal.cpp @@ -544,6 +555,11 @@ elseif(OPENMC_USE_UWUW) message(FATAL_ERROR "DAGMC must be enabled when UWUW is enabled.") endif() +if(OPENMC_USE_XDG) + target_compile_definitions(libopenmc PRIVATE OPENMC_XDG_ENABLED) + target_link_libraries(libopenmc xdg::xdg) +endif() + if(OPENMC_USE_LIBMESH) target_compile_definitions(libopenmc PRIVATE OPENMC_LIBMESH_ENABLED) target_link_libraries(libopenmc PkgConfig::LIBMESH) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 0d8189caa1d..a242e164d45 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -47,6 +47,13 @@ namespace openmc { enum class ElementType { UNSUPPORTED = -1, LINEAR_TET, LINEAR_HEX }; +struct NextMeshCell +{ + double distance {INFTY}; + int face_idx {-1}; + std::array next_ijk; +}; + //============================================================================== // Global variables //============================================================================== @@ -771,6 +778,9 @@ class UnstructuredMesh : public Mesh { //! Get the library used for this unstructured mesh virtual std::string library() const = 0; + //! Get the mesh filename + virtual std::string filename() const { return filename_; } + // Data members bool output_ { true}; //!< Write tallies onto the unstructured mesh at the end of a run @@ -797,7 +807,36 @@ class UnstructuredMesh : public Mesh { //! \param[in] coords Coordinates of the tetrahedron //! \param[in] seed Random number generation seed //! \return Sampled position within the tetrahedron - Position sample_tet(std::array coords, uint64_t* seed) const; + template + Position sample_tet(span coords, uint64_t* seed) const + { + // Uniform distribution + double s = prn(seed); + double t = prn(seed); + double u = prn(seed); + + // From PyNE implementation of moab tet sampling C. Rocchini & P. Cignoni + // (2000) Generating Random Points in a Tetrahedron, Journal of Graphics + // Tools, 5:4, 9-12, DOI: 10.1080/10867651.2000.10487528 + if (s + t > 1) { + s = 1.0 - s; + t = 1.0 - t; + } + if (s + t + u > 1) { + if (t + u > 1) { + double old_t = t; + t = 1.0 - u; + u = 1.0 - s - old_t; + } else if (t + u <= 1) { + double old_s = s; + s = 1.0 - t - u; + u = old_s + t + u - 1; + } + } + V result = s * (coords[1] - coords[0]) + t * (coords[2] - coords[0]) + + u * (coords[3] - coords[0]) + coords[0]; + return {result[0], result[1], result[2]}; + } // Data members double length_multiplier_ { diff --git a/include/openmc/xdg.h b/include/openmc/xdg.h new file mode 100644 index 00000000000..1e045e4c2c1 --- /dev/null +++ b/include/openmc/xdg.h @@ -0,0 +1,114 @@ +#ifndef OPENMC_XDG_H +#define OPENMC_XDG_H + +namespace openmc { +extern "C" const bool XDG_ENABLED; +} + +// always include the XML interface header +#include "openmc/xml_interface.h" + +#ifdef OPENMC_XDG_ENABLED + +#include "xdg/xdg.h" + +#include "openmc/mesh.h" +#include "openmc/position.h" +namespace openmc { + +class XDGMesh : public UnstructuredMesh{ + +public: + // Constructors + XDGMesh() = default; + XDGMesh(pugi::xml_node node); + XDGMesh(hid_t group); + XDGMesh(const std::string& filename, double length_multiplier = 1.0); + XDGMesh(std::shared_ptr external_xdg); + + static const std::string mesh_lib_type; + + const std::shared_ptr& xdg_instance() const { return xdg_; } + + // Overridden Methods + + //! Perform any preparation needed to support use in mesh filters + void prepare_for_point_location() override; + + Position sample_element(int32_t bin, uint64_t* seed) const override; + + void bins_crossed(Position r0, Position r1, const Direction& u, + vector& bins, vector& lengths) const override; + + int get_bin(Position r) const override; + + bool bin_is_valid(int bin) const + { + return bin >= 0 && bin < n_bins(); + } + + xdg::MeshID bin_to_mesh_id(int bin) const; + + int mesh_id_to_bin(xdg::MeshID id) const; + + int n_bins() const override; + + int n_surface_bins() const override; + + std::pair, vector> plot( + Position plot_ll, Position plot_ur) const override; + + std::string library() const override; + + std::string mesh_library() const; + + //! Add a score to the mesh instance + void add_score(const std::string& score) override {}; + + //! Remove all scores from the mesh instance + void remove_scores() override {}; + + //! Set data for a score + void set_score_data(const std::string& score, const vector& values, + const vector& std_dev) override {}; + + //! Write the mesh with any current tally data + void write(const std::string& base_filename) const override; + + Position centroid(int bin) const override; + + int n_vertices() const override; + + Position vertex(int id) const override; + + std::vector connectivity(int id) const override; + + //! Get the volume of a mesh bin + // + //! \param[in] bin Bin to return the volume for + //! \return Volume of the bin + double volume(int bin) const override; + + //! Get the distance to the nearest boundary for a given position and direction + //! \param[in] g GeometryState object containing position and direction + //! \return NextMeshCell struct containing distance, face index, and next indices + NextMeshCell distance_to_bin_boundary(GeometryState& g) const; + + //! Get the distance to the nearest boundary for a given position and direction + //! \param[in] r Position to check + //! \param[in] u Direction to check + //! \return Distance to the nearest boundary + NextMeshCell distance_to_bin_boundary(int bin, const Position& r, const Direction& u) const; + +private: + void initialize() override; + + std::shared_ptr xdg_; //!< XDG instance + xdg::MeshLibrary mesh_library_ {xdg::MeshLibrary::LIBMESH}; //!< Mesh library type +}; + +} // namespace openmc + +#endif // OPENMC_XDG_ENABLED + +#endif // OPENMC_XDG_H \ No newline at end of file diff --git a/openmc/lib/__init__.py b/openmc/lib/__init__.py index 9b135370fe9..6074611dff7 100644 --- a/openmc/lib/__init__.py +++ b/openmc/lib/__init__.py @@ -40,6 +40,9 @@ def _dagmc_enabled(): return c_bool.in_dll(_dll, "DAGMC_ENABLED").value +def _xdg_enabled(): + return c_bool.in_dll(_dll, "XDG_ENABLED").value + def _coord_levels(): return c_int.in_dll(_dll, "n_coord_levels").value diff --git a/openmc/mesh.py b/openmc/mesh.py index 030a57218eb..413e821b04e 100644 --- a/openmc/mesh.py +++ b/openmc/mesh.py @@ -337,6 +337,8 @@ def from_xml_element(cls, elem: ET.Element): an openmc mesh object """ + from .xdg import XDGMesh + mesh_type = get_text(elem, 'type') if mesh_type == 'regular' or mesh_type is None: @@ -347,6 +349,8 @@ def from_xml_element(cls, elem: ET.Element): mesh = CylindricalMesh.from_xml_element(elem) elif mesh_type == 'spherical': mesh = SphericalMesh.from_xml_element(elem) + elif mesh_type == 'xdg': + mesh = XDGMesh.from_xml_element(elem) elif mesh_type == 'unstructured': mesh = UnstructuredMesh.from_xml_element(elem) else: @@ -2653,7 +2657,7 @@ def library(self): @library.setter def library(self, lib: str): - cv.check_value('Unstructured mesh library', lib, ('moab', 'libmesh')) + cv.check_value('Unstructured mesh library', lib, ('xdg','moab', 'libmesh')) self._library = lib @property diff --git a/openmc/xdg.py b/openmc/xdg.py new file mode 100644 index 00000000000..f25597668c4 --- /dev/null +++ b/openmc/xdg.py @@ -0,0 +1,541 @@ +from collections.abc import Iterable, Mapping +from numbers import Integral + + +import h5py +import lxml.etree as ET +import numpy as np +import warnings + +import openmc +import openmc.checkvalue as cv +from ._xml import get_text +from .checkvalue import check_type, check_value, PathLike +from .mesh import MeshBase +from .surface import _BOUNDARY_TYPES +from .bounding_box import BoundingBox +from .utility_funcs import input_path + + +class XDGMesh(openmc.MeshBase): + """A 3D unstructured mesh + + Parameters + ---------- + filename : path-like + Location of the unstructured mesh file. Supported files for 'moab' + library are .h5 and .vtk. Supported files for 'libmesh' library are + exodus mesh files .exo. + library : {'moab', 'libmesh'} + Mesh library used for the unstructured mesh tally + mesh_id : int + Unique identifier for the mesh + name : str + Name of the mesh + + Attributes + ---------- + id : int + Unique identifier for the mesh + name : str + Name of the mesh + filename : str + Name of the file containing the unstructured mesh + library : {'moab', 'libmesh'} + Mesh library used for the unstructured mesh tally + """ + def __init__(self, filename: PathLike, library: str, mesh_id: int | None = None, + name: str = ''): + super().__init__(mesh_id, name) + self.filename = filename + self.library = library + + @property + def filename(self): + return self._filename + + @filename.setter + def filename(self, filename): + cv.check_type('Unstructured Mesh filename', filename, PathLike) + self._filename = input_path(filename) + + @property + def library(self): + return self._library + + @library.setter + def library(self, lib: str): + cv.check_value('Unstructured mesh library', lib, ('moab', 'libmesh')) + self._library = lib + + @property + def dimension(self): + return (self.n_elements,) + + @property + def n_dimension(self): + return 3 + + @property + def lower_left(self): + raise NotImplementedError("XDGMesh.lower_left is not implemented") + + @property + def upper_right(self): + raise NotImplementedError("XDGMesh.upper_right is not implemented") + + @property + def n_elements(self): + raise NotImplementedError("XDGMesh.n_elements is not implemented") + + @property + def indices(self): + raise NotImplementedError("XDGMesh.indices is not implemented") + + + def __repr__(self): + string = super().__repr__() + string += '{: <16}=\t{}\n'.format('\tFilename', self.filename) + string += '{: <16}=\t{}\n'.format('\tMesh Library', self.library) + return string + + @classmethod + def from_hdf5(cls, group: h5py.Group, mesh_id: int, name: str): + raise NotImplementedError("XDGMesh.from_hdf5 is not implemented") + # TODO: add length_multiplier + + 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() + element.set("type", "xdg") + element.set("library", self._library) + subelement = ET.SubElement(element, "filename") + subelement.text = str(self.filename) + + return element + + @classmethod + def from_xml_element(cls, elem: ET.Element): + """Generate unstructured mesh object from XML element + + Parameters + ---------- + elem : lxml.etree._Element + XML element + + Returns + ------- + openmc.UnstructuredMesh + UnstructuredMesh generated from an XML element + """ + mesh_id = int(get_text(elem, 'id')) + filename = get_text(elem, 'filename') + library = get_text(elem, 'library') + return cls(filename, library, mesh_id) + + +class XDGUniverse(openmc.UniverseBase): + """A reference to a XDG file to be used in the model. + + Parameters + ---------- + mesh : openmc.XDGMesh + Mesh to use for the XDG universe + type : str + Type of XDG file to use. Options are 'volume_mesh' or 'surface_mesh'. + universe_id : int, optional + Unique identifier of the universe. If not specified, an identifier will + automatically be assigned. + name : str, optional + Name of the universe. If not specified, the name is the empty string. + auto_geom_ids : bool + Set IDs automatically on initialization (True) or report overlaps in ID + space between CSG and DAGMC (False) + + Attributes + ---------- + id : int + Unique identifier of the universe + name : str + Name of the universe + mesh : openmc.XDGMesh + Mesh to use for the XDG universe + auto_geom_ids : bool + Set IDs automatically on initialization (True) or report overlaps in ID + space between CSG and XDG geometry (False) + """ + + def __init__(self, + mesh: XDGMesh, + universe_id=None, + name='', + auto_geom_ids=False, + auto_mat_ids=False): + super().__init__(universe_id, name) + # Initialize class attributes + self.mesh = mesh + self.auto_geom_ids = auto_geom_ids + self._type = 'surface_mesh' + self._background_material = None + + def __repr__(self): + string = super().__repr__() + string += '{: <16}=\t{}\n'.format('\tGeom', 'XDG') + string += '{: <16}=\t{}\n'.format('\tAuto Geom IDs', self.auto_geom_ids) + string += '{: <16}=\t{}\n'.format('\tType', self._type) + string += '{: <16}=\t{}\n'.format('\tMesh', self._mesh) + return string + + @property + def bounding_box(self): + return BoundingBox.infinite() + + @property + def filename(self): + return self._filename + + @filename.setter + def filename(self, val: cv.PathLike): + cv.check_type('XDG filename', val, cv.PathLike) + self._filename = input_path(val) + + @property + def material_overrides(self): + raise NotImplementedError("Material overrides are not implemented for XDG") + + @property + def mesh(self): + return self._mesh + + @mesh.setter + def mesh(self, val): + cv.check_type('XDG mesh', val, XDGMesh) + self._mesh = val + + @property + def type(self): + return self._type + + @type.setter + def type(self, val): + cv.check_value('XDG type', val, ('surface_mesh', 'volume_mesh')) + self._type = val + + def replace_material_assignment(self, material_name: str, material: openmc.Material): + """Replace a material assignment within the DAGMC universe. + + Replace the material assignment of all cells filled with a material in + the DAGMC universe. The universe must be synchronized in an initialized + Model (see :meth:`~openmc.DAGMCUniverse.sync_dagmc_cells`) before + calling this method. + + .. versionadded:: 0.15.1 + + Parameters + ---------- + material_name : str + Material name to replace + material : openmc.Material + Material to replace the material_name with + + """ + raise NotImplementedError("Material overrides are not implemented for XDG") + + def add_material_override(self, key, overrides=None): + """Add a material override to the universe. + + .. versionadded:: 0.15 + + Parameters + ---------- + key : openmc.DAGMCCell or int + Cell object or ID of the Cell to override + value : openmc.Material or Iterable of openmc.Material + Material(s) to be applied to the Cell passed as the key + + """ + raise NotImplementedError("Material overrides are not implemented for XDG") + + @property + def auto_geom_ids(self): + return self._auto_geom_ids + + @auto_geom_ids.setter + def auto_geom_ids(self, val): + cv.check_type('DAGMC automatic geometry ids', val, bool) + self._auto_geom_ids = val + + @property + def material_names(self): + raise NotImplementedError("Material names is not implemented for XDG") + + @property + def background_material(self): + return self._background_material + + @background_material.setter + def background_material(self, val): + cv.check_type('XDG background material', val, openmc.Material) + self._background_material = val + + def _n_geom_elements(self, geom_type): + """ + Helper function for retrieving the number geometric entities in a DAGMC + file + + Parameters + ---------- + geom_type : str + The type of geometric entity to count. One of {'Volume', 'Surface'}. Returns + the runtime number of voumes in the DAGMC model (includes implicit complement). + + Returns + ------- + int + Number of geometry elements of the specified type + """ + raise NotImplementedError("Number of cells is not implemented for XDG") + + @property + def n_cells(self): + raise NotImplementedError("Number of cells is not implemented for XDG") + + @property + def n_surfaces(self): + raise NotImplementedError("Number of surfaces is not implemented for XDG") + + def create_xml_subelement(self, xml_element, memo=None): + if memo is None: + memo = set() + + if self in memo: + return + + memo.add(self) + + # Set xml element values + xdg_element = ET.Element('xdg_universe') + xdg_element.set('id', str(self.id)) + + if self.auto_geom_ids: + xdg_element.set('auto_geom_ids', 'true') + + xdg_element.set('mesh', str(self.mesh.id)) + + xdg_element.set('type', self._type) + + if self.background_material is not None: + xdg_element.set('background_material', str(self.background_material.id)) + + # add mesh element + xml_element.append(xdg_element) + + # if this mesh has already been added to the XML element + if self.mesh in memo: + return + memo.add(self.mesh) + + xml_element.append(self.mesh.to_xml_element()) + + def bounding_region( + self, + bounded_type: str = 'box', + boundary_type: str = 'vacuum', + starting_id: int = 10000, + padding_distance: float = 0. + ): + """Creates a either a spherical or box shaped bounding region around + the DAGMC geometry. + + .. versionadded:: 0.13.1 + + Parameters + ---------- + bounded_type : str + The type of bounding surface(s) to use when constructing the region. + Options include a single spherical surface (sphere) or a rectangle + made from six planes (box). + boundary_type : str + Boundary condition that defines the behavior for particles hitting + the surface. Defaults to vacuum boundary condition. Passed into the + surface construction. + starting_id : int + Starting ID of the surface(s) used in the region. For bounded_type + 'box', the next 5 IDs will also be used. Defaults to 10000 to reduce + the chance of an overlap of surface IDs with the DAGMC geometry. + padding_distance : float + Distance between the bounding region surfaces and the minimal + bounding box. Allows for the region to be larger than the DAGMC + geometry. + + Returns + ------- + openmc.Region + Region instance + """ + raise NotImplementedError("Bounded region is not implemented for XDG") + + def bounded_universe(self, bounding_cell_id=10000, **kwargs): + """Returns an openmc.Universe filled with this DAGMCUniverse and bounded + with a cell. Defaults to a box cell with a vacuum surface however this + can be changed using the kwargs which are passed directly to + DAGMCUniverse.bounding_region(). + + Parameters + ---------- + bounding_cell_id : int + The cell ID number to use for the bounding cell, defaults to 10000 to reduce + the chance of overlapping ID numbers with the DAGMC geometry. + + Returns + ------- + openmc.Universe + Universe instance + """ + raise NotImplementedError("Bounded universe is not implemented for XDG") + + @classmethod + def from_hdf5(cls, group): + """Create DAGMC universe from HDF5 group + + Parameters + ---------- + group : h5py.Group + Group in HDF5 file + + Returns + ------- + openmc.XDGUniverse + XDGUniverse instance + + """ + id = int(group.name.split('/')[-1].lstrip('universe ')) + fname = group['filename'][()].decode() + name = group['name'][()].decode() if 'name' in group else None + + out = cls(fname, universe_id=id, name=name) + + out.auto_geom_ids = bool(group.attrs['auto_geom_ids']) + out.auto_mat_ids = bool(group.attrs['auto_mat_ids']) + + return out + + @classmethod + def from_xml_element(cls, elem, meshes=None): + """Generate XDG universe from XML element + + Parameters + ---------- + elem : lxml.etree._Element + `` element + meshes : dict + Dictionary mapping mesh ID strings to :class:`openmc.XDGMesh` + instances (defined in :meth:`openmc.Geometry.from_xml`) + + Returns + ------- + openmc.XDGUniverse + XDGUniverse instance + + """ + id = int(get_text(elem, 'id')) + mesh_id = int(get_text(elem, 'mesh')) + + out = cls(meshes[mesh_id], universe_id=id) + + name = get_text(elem, 'name') + if name is not None: + out.name = name + + out.auto_geom_ids = bool(elem.get('auto_geom_ids')) + + if type := elem.get('type'): + out.type = type + else: + raise ValueError("XDG type is not specified in the XML element") + + if library := elem.get('library'): + out.library = library + + if background_material := elem.get('background_material'): + out.background_material = background_material + + return out + + def _partial_deepcopy(self): + """Clone all of the openmc.DAGMCUniverse object's attributes except for + its cells, as they are copied within the clone function. This should + only to be used within the openmc.UniverseBase.clone() context. + """ + clone = openmc.XDGUniverse(name=self.name, filename=self.filename) + clone.auto_geom_ids = self.auto_geom_ids + clone.auto_mat_ids = self.auto_mat_ids + clone.library = self.library + clone.type = self.type + return clone + + def add_cell(self, cell): + """Add a cell to the universe. + + Parameters + ---------- + cell : openmc.XDGCell + Cell to add + + """ + raise NotImplementedError("Add cell is not implemented for XDG") + + def remove_cell(self, cell): + """Remove a cell from the universe. + + Parameters + ---------- + cell : openmc.Cell + Cell to remove + + """ + raise NotImplementedError("Remove cell is not implemented for XDG") + + def sync_dagmc_cells(self, mats: Iterable[openmc.Material]): + """Synchronize DAGMC cell information between Python and C API + + .. versionadded:: 0.15.1 + + Parameters + ---------- + mats : iterable of openmc.Material + Iterable of materials to assign to the DAGMC cells + + """ + raise NotImplementedError("Sync cells is not implemented for XDG") + +class XDGCell(openmc.Cell): + """A cell class for XDG-based geometries. + + Parameters + ---------- + cell_id : int or None, optional + Unique identifier for the cell. If None, an identifier will be + automatically assigned. + name : str, optional + Name of the cell. + fill : openmc.Material or None, optional + Material filling the cell. If None, the cell is filled with vacuum. + + Attributes + ---------- + id : int + Unique identifier of the cell + name : str + Name of the cell + fill : openmc.Material or None, optional + Material filling the cell. If None, the cell is filled with vacuum. + """ + pass \ No newline at end of file diff --git a/src/initialize.cpp b/src/initialize.cpp index a2269ed1ea9..06f6b74966c 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -42,6 +42,10 @@ #include "libmesh/libmesh.h" #endif +#ifdef OPENMC_XDG_ENABLED +#include "xdg/config.h" +#endif + int openmc_init(int argc, char* argv[], const void* intracomm) { using namespace openmc; @@ -85,6 +89,11 @@ int openmc_init(int argc, char* argv[], const void* intracomm) settings::libmesh_comm = &(settings::libmesh_init->comm()); } +#ifdef OPENMC_XDG_ENABLED + xdg::config::external_libmesh_init = settings::libmesh_init.get(); + xdg::config::external_libmesh_comm = &(settings::libmesh_init->comm()); +#endif + #endif // Start total and initialization timer diff --git a/src/mesh.cpp b/src/mesh.cpp index 5ab7ac3988b..c820074f2a1 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -55,6 +55,8 @@ #include "moab/FileOptions.hpp" #endif +#include "openmc/xdg.h" + namespace openmc { //============================================================================== @@ -347,6 +349,10 @@ const std::unique_ptr& Mesh::create( mesh_library == MOABMesh::mesh_lib_type) { model::meshes.push_back(make_unique(dataset)); #endif +#ifdef OPENMC_XDG_ENABLED + } else if (mesh_type == "xdg") { + model::meshes.push_back(make_unique(dataset)); +#endif #ifdef OPENMC_LIBMESH_ENABLED } else if (mesh_type == UnstructuredMesh::mesh_type && mesh_library == LibMesh::mesh_lib_type) { @@ -798,12 +804,12 @@ UnstructuredMesh::UnstructuredMesh(pugi::xml_node node) : Mesh(node) n_dimension_ = 3; // check the mesh type - if (check_for_node(node, "type")) { - auto temp = get_node_value(node, "type", true, true); - if (temp != mesh_type) { - fatal_error(fmt::format("Invalid mesh type: {}", temp)); - } - } + // if (check_for_node(node, "type")) { + // auto temp = get_node_value(node, "type", true, true); + // if (temp != mesh_type) { + // fatal_error(fmt::format("Invalid mesh type: {}", temp)); + // } + // } // check if a length unit multiplier was specified if (check_for_node(node, "length_multiplier")) { @@ -894,36 +900,6 @@ void UnstructuredMesh::determine_bounds() upper_right_ = {xmax, ymax, zmax}; } -Position UnstructuredMesh::sample_tet( - std::array coords, uint64_t* seed) const -{ - // Uniform distribution - double s = prn(seed); - double t = prn(seed); - double u = prn(seed); - - // From PyNE implementation of moab tet sampling C. Rocchini & P. Cignoni - // (2000) Generating Random Points in a Tetrahedron, Journal of Graphics - // Tools, 5:4, 9-12, DOI: 10.1080/10867651.2000.10487528 - if (s + t > 1) { - s = 1.0 - s; - t = 1.0 - t; - } - if (s + t + u > 1) { - if (t + u > 1) { - double old_t = t; - t = 1.0 - u; - u = 1.0 - s - old_t; - } else if (t + u <= 1) { - double old_s = s; - s = 1.0 - t - u; - u = old_s + t + u - 1; - } - } - return s * (coords[1] - coords[0]) + t * (coords[2] - coords[0]) + - u * (coords[3] - coords[0]) + coords[0]; -} - const std::string UnstructuredMesh::mesh_type = "unstructured"; std::string UnstructuredMesh::get_mesh_type() const @@ -991,7 +967,6 @@ void UnstructuredMesh::to_hdf5_inner(hid_t mesh_group) const connectivity.slice(i) = -1; } } - // warn users that some elements were skipped if (num_elem_skipped > 0) { warning(fmt::format("The connectivity of {} elements " @@ -3159,7 +3134,6 @@ std::string MOABMesh::library() const // Sample position within a tet for MOAB type tets Position MOABMesh::sample_element(int32_t bin, uint64_t* seed) const { - moab::EntityHandle tet_ent = get_ent_handle_from_bin(bin); // Get vertex coordinates for MOAB tet @@ -3177,12 +3151,8 @@ Position MOABMesh::sample_element(int32_t bin, uint64_t* seed) const fatal_error("Failed to get tet coords"); } - std::array tet_verts; - for (int i = 0; i < 4; i++) { - tet_verts[i] = {p[i][0], p[i][1], p[i][2]}; - } // Samples position within tet using Barycentric stuff - return this->sample_tet(tet_verts, seed); + return this->sample_tet({p, 4}, seed); } double MOABMesh::tet_volume(moab::EntityHandle tet) const @@ -3674,7 +3644,7 @@ Position LibMesh::sample_element(int32_t bin, uint64_t* seed) const tet_verts[i] = {node_ref(0), node_ref(1), node_ref(2)}; } // Samples position within tet using Barycentric coordinates - Position sampled_position = this->sample_tet(tet_verts, seed); + Position sampled_position = this->sample_tet({tet_verts.begin(), tet_verts.end()}, seed); if (length_multiplier_ > 0.0) { return length_multiplier_ * sampled_position; } else { diff --git a/src/xdg.cpp b/src/xdg.cpp new file mode 100644 index 00000000000..780d48e3d59 --- /dev/null +++ b/src/xdg.cpp @@ -0,0 +1,213 @@ +#include +#include +#include +#include + +#include "openmc/xdg.h" + +#include "openmc/constants.h" +#include "openmc/container_util.h" +#include "openmc/error.h" +#include "openmc/file_utils.h" +#include "openmc/geometry.h" +#include "openmc/geometry_aux.h" +#include "openmc/hdf5_interface.h" +#include "openmc/material.h" +#include "openmc/settings.h" +#include "openmc/string_utils.h" + +#include + +#ifdef OPENMC_XDG_ENABLED +#include "xdg/xdg.h" +#endif + +namespace openmc { + +#ifdef OPENMC_XDG_ENABLED +const bool XDG_ENABLED = true; +#else +const bool XDG_ENABLED = false; +#endif + +} // namespace openmc + +#ifdef OPENMC_XDG_ENABLED + +namespace openmc { + +//============================================================================== +// XDG Mesh implementation +//============================================================================== + + +const std::string XDGMesh::mesh_lib_type = "xdg"; + +XDGMesh::XDGMesh(pugi::xml_node node) : UnstructuredMesh(node) { + std::string mesh_lib = get_node_value(node, "library", true, true); + if (mesh_lib == "moab") { + mesh_library_ = xdg::MeshLibrary::MOAB; + } else if (mesh_lib == "libmesh") { + mesh_library_ = xdg::MeshLibrary::LIBMESH; + } + initialize(); +} + +XDGMesh::XDGMesh(hid_t group) : UnstructuredMesh(group) { + std::string mesh_lib; + read_dataset(group, "library", mesh_lib); + if (mesh_lib == "moab") { + mesh_library_ = xdg::MeshLibrary::MOAB; + } else if (mesh_lib == "libmesh") { + mesh_library_ = xdg::MeshLibrary::LIBMESH; + } + initialize(); +} + + +XDGMesh::XDGMesh(const std::string& filename, double length_multiplier) { + filename_ = filename; + set_length_multiplier(length_multiplier); + initialize(); +} + +XDGMesh::XDGMesh(std::shared_ptr external_xdg) { + xdg_ = external_xdg; + filename_ = "unknown (external file)"; + initialize(); +} + +void XDGMesh::initialize() { + if (xdg_) return; + + // create XDGMesh instance + xdg_ = xdg::XDG::create(mesh_library_); + + // load XDGMesh file + if (!file_exists(filename_)) { + fatal_error(fmt::format("Mesh file \"{}\" does not exist", filename_)); + } + + xdg_->mesh_manager()->load_file(filename_); + xdg_->mesh_manager()->init(); + xdg_->mesh_manager()->parse_metadata(); +} + +void XDGMesh::prepare_for_point_location() { + xdg_->prepare_raytracer(); +} + +Position XDGMesh::sample_element(int32_t bin, uint64_t* seed) const { + // MeshIDs are 1-indexed, so we add 1 to the bin, which is 0-indexed + auto vertices = xdg_->mesh_manager()->element_vertices(bin_to_mesh_id(bin)); + return this->sample_tet(vertices, seed); +} + +void XDGMesh::bins_crossed(Position r0, Position r1, const Direction& u, + vector& bins, vector& lengths) const +{ + // TODO: Make more robust (including mesh entrance/re-entrance) + xdg::Position p0 {r0.x, r0.y, r0.z}; + xdg::Position p1 {r1.x, r1.y, r1.z}; + double length_rcp = 1 / (p1 - p0).length(); + auto track_segments = xdg_->segments(p0, p1); + // remove elements with lengths of zero + track_segments.erase(std::remove_if(track_segments.begin(), track_segments.end(), [](const std::pair& p) {return p.second == 0.0;}), track_segments.end()); + for (const auto& track_segment : track_segments ) { + bins.push_back(mesh_id_to_bin(track_segment.first)); + lengths.push_back(track_segment.second * length_rcp); + } +} + +int XDGMesh::get_bin(Position r) const +{ + xdg::Position p {r.x, r.y, r.z}; + return mesh_id_to_bin(xdg_->find_element(p)); +} + +int XDGMesh::n_bins() const { + return xdg_->mesh_manager()->num_volume_elements(); +} + +int XDGMesh::n_surface_bins() const { + return 4 * n_bins(); +} + +std::pair, vector> XDGMesh::plot( + Position plot_ll, Position plot_ur) const +{ + fatal_error("Plot of XDGMesh mesh not implemented"); + return {}; +} + +std::string XDGMesh::library() const { + return mesh_lib_type; +} + +std::string XDGMesh::mesh_library() const { + if (mesh_library_ == xdg::MeshLibrary::LIBMESH) { + return "libmesh"; + } else if (mesh_library_ == xdg::MeshLibrary::MOAB) { + return "moab"; + } +} + +void XDGMesh::write(const std::string& base_filename) const +{ + warning("XDGMesh mesh write from C++ not implemented"); +} + +Position XDGMesh::centroid(int bin) const +{ + auto element_vertices = xdg_->mesh_manager()->element_vertices(bin_to_mesh_id(bin)); + + xdg::Vertex centroid {0.0, 0.0, 0.0}; + for (const auto& v : element_vertices) { + centroid += v; + } + + centroid /= double(element_vertices.size()); + + return {centroid[0], centroid[1], centroid[1]}; +} + +int XDGMesh::n_vertices() const +{ + return xdg_->mesh_manager()->num_vertices(); +} + +Position XDGMesh::vertex(int id) const +{ + xdg::MeshID mesh_id = xdg_->mesh_manager()->vertex_id(id); + auto v = xdg_->mesh_manager()->vertex_coordinates(mesh_id); + return {v[0], v[1], v[2]}; +} + +std::vector XDGMesh::connectivity(int id) const +{ + auto conn = xdg_->mesh_manager()->connectivity(bin_to_mesh_id(id)); + for (auto& c : conn) { + c = xdg_->mesh_manager()->vertex_index(c); + } + return conn; +} + +double XDGMesh::volume(int bin) const +{ + return xdg_->mesh_manager()->element_volume(bin_to_mesh_id(bin)); +} + +xdg::MeshID XDGMesh::bin_to_mesh_id(int bin) const +{ + return xdg_->mesh_manager()->element_id(bin); +} + +int32_t XDGMesh::mesh_id_to_bin(xdg::MeshID id) const +{ + if (id < 0) return -1; + return xdg_->mesh_manager()->element_index(id); +} + +} // namespace openmc + +#endif // XDG From d85fd1f713aaee93b983d537451b083dbe235c5c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 4 Feb 2026 14:05:53 -0600 Subject: [PATCH 02/17] Accounting for XDGMesh when creating MeshFilters --- openmc/__init__.py | 1 + openmc/filter.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/openmc/__init__.py b/openmc/__init__.py index c204929c840..ce7b53b2368 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -34,6 +34,7 @@ from openmc.search import * from openmc.polynomial import * from openmc.tracks import * +from openmc.xdg import * from .config import * # Import a few names from the model module diff --git a/openmc/filter.py b/openmc/filter.py index 87aeb70c3a7..6d166f2709b 100644 --- a/openmc/filter.py +++ b/openmc/filter.py @@ -925,6 +925,8 @@ def mesh(self, mesh): self.bins = list(range(len(mesh.volumes))) else: self.bins = [] + elif isinstance(mesh, openmc.XDGMesh): + self.bins = [] else: self.bins = list(mesh.indices) From 612c891143c9e3df7a03010cadf64e70dd68904a Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 6 Feb 2026 13:39:33 -0600 Subject: [PATCH 03/17] Updating call to XDG for element connectivity --- src/xdg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xdg.cpp b/src/xdg.cpp index 780d48e3d59..e7445eb7937 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -185,7 +185,7 @@ Position XDGMesh::vertex(int id) const std::vector XDGMesh::connectivity(int id) const { - auto conn = xdg_->mesh_manager()->connectivity(bin_to_mesh_id(id)); + auto conn = xdg_->mesh_manager()->element_connectivity(bin_to_mesh_id(id)); for (auto& c : conn) { c = xdg_->mesh_manager()->vertex_index(c); } From e860ea9639bd9bc8e5448c187e7ee15b920d45ca Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 9 Feb 2026 14:26:38 -0600 Subject: [PATCH 04/17] Adding XDG unstructured mesh tallies mirroring those of the current unstructured mesh capabilities. --- .../xdg/tallies/inputs_true0_libmesh.dat | 90 +++++ .../xdg/tallies/inputs_true0_moab.dat | 90 +++++ .../xdg/tallies/inputs_true1_libmesh.dat | 90 +++++ .../xdg/tallies/inputs_true2_libmesh.dat | 90 +++++ .../xdg/tallies/inputs_true2_moab.dat | 90 +++++ .../xdg/tallies/inputs_true3_libmesh.dat | 90 +++++ .../xdg/tallies/inputs_true4_libmesh.dat | 90 +++++ .../xdg/tallies/inputs_true5_libmesh.dat | 90 +++++ tests/regression_tests/xdg/tallies/test.py | 342 ++++++++++++++++++ .../xdg/tallies/test_mesh_hexes.e | 1 + .../xdg/tallies/test_mesh_hexes.exo | 1 + .../xdg/tallies/test_mesh_tets.e | 1 + .../xdg/tallies/test_mesh_tets.exo | 1 + .../xdg/tallies/test_mesh_tets_w_holes.e | 1 + .../xdg/tallies/test_mesh_tets_w_holes.exo | 1 + 15 files changed, 1068 insertions(+) create mode 100644 tests/regression_tests/xdg/tallies/inputs_true0_libmesh.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true0_moab.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true1_libmesh.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true2_libmesh.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true2_moab.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true3_libmesh.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat create mode 100644 tests/regression_tests/xdg/tallies/test.py create mode 120000 tests/regression_tests/xdg/tallies/test_mesh_hexes.e create mode 120000 tests/regression_tests/xdg/tallies/test_mesh_hexes.exo create mode 120000 tests/regression_tests/xdg/tallies/test_mesh_tets.e create mode 120000 tests/regression_tests/xdg/tallies/test_mesh_tets.exo create mode 120000 tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.e create mode 120000 tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.exo diff --git a/tests/regression_tests/xdg/tallies/inputs_true0_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true0_libmesh.dat new file mode 100644 index 00000000000..baafa1ec5be --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true0_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true0_moab.dat b/tests/regression_tests/xdg/tallies/inputs_true0_moab.dat new file mode 100644 index 00000000000..87ac4b871de --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true0_moab.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true1_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true1_libmesh.dat new file mode 100644 index 00000000000..f47b11f8b84 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true1_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.exo + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true2_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true2_libmesh.dat new file mode 100644 index 00000000000..996a6439cb8 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true2_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true2_moab.dat b/tests/regression_tests/xdg/tallies/inputs_true2_moab.dat new file mode 100644 index 00000000000..a739e2fd209 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true2_moab.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true3_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true3_libmesh.dat new file mode 100644 index 00000000000..ff90c4cb751 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true3_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.exo + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat new file mode 100644 index 00000000000..3fc21974d5f --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_hexes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat new file mode 100644 index 00000000000..1a21db5a0bc --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_hexes.exo + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/test.py b/tests/regression_tests/xdg/tallies/test.py new file mode 100644 index 00000000000..f9c842a3546 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test.py @@ -0,0 +1,342 @@ +import glob +import os + +import numpy as np +import pytest + +import openmc +import openmc.lib +from openmc.xdg import XDGMesh + +from tests.testing_harness import PyAPITestHarness + + +class XDGMeshTallyTest(PyAPITestHarness): + + ELEM_PER_VOXEL = 12 + + def __init__( + self, + statepoint_name, + model, + inputs_true, + mesh_filename, + mesh_kind, + holes=None, + scale_factor=10.0, + ): + super().__init__(statepoint_name, model, inputs_true) + print(f"Running test with mesh file: {mesh_filename} and inputs file: {inputs_true}") + self.mesh_filename = mesh_filename + self.mesh_kind = mesh_kind + self.holes = holes + self.scale_bounding_cell(scale_factor) + + def scale_bounding_cell(self, scale_factor): + geometry = self._model.geometry + for surface in geometry.get_all_surfaces().values(): + if surface.boundary_type != 'vacuum': + continue + for coeff in surface._coefficients: + surface._coefficients[coeff] *= scale_factor + + def _compare_results(self): + with openmc.StatePoint(self._sp_name) as sp: + xdg_mesh = None + for mesh in sp.meshes.values(): + if isinstance(mesh, openmc.UnstructuredMesh) and mesh.library == "xdg": + xdg_mesh = mesh + break + + assert xdg_mesh is not None + assert os.path.basename(xdg_mesh.filename) == os.path.basename( + self.mesh_filename + ) + + if self.mesh_kind == "tet": + exp_vertex = (-10.0, -10.0, -10.0) + exp_centroid = (-8.75, -9.75, -9.25) + else: + exp_vertex = (-10.0, -10.0, 10.0) + exp_centroid = (-9.0, -9.0, 9.0) + + np.testing.assert_array_equal(xdg_mesh.vertices[0], exp_vertex) + np.testing.assert_array_equal(xdg_mesh.centroid(0), exp_centroid) + + reg_mesh_data = None + xdg_mesh_data = None + xdg_tally = None + for tally in sp.tallies.values(): + if tally.contains_filter(openmc.MeshFilter): + flt = tally.find_filter(openmc.MeshFilter) + if isinstance(flt.mesh, openmc.RegularMesh): + reg_mesh_data = self.get_mesh_tally_data(tally) + else: + xdg_tally = tally + xdg_mesh_data = self.get_mesh_tally_data(tally, True) + + assert reg_mesh_data is not None + assert xdg_mesh_data is not None + + if self.holes: + reg_mesh_data = np.delete(reg_mesh_data, self.holes) + + decimals = 10 if xdg_tally.estimator == 'collision' else 8 + np.testing.assert_array_almost_equal( + np.sort(xdg_mesh_data), + np.sort(reg_mesh_data), + decimals + ) + + def get_mesh_tally_data(self, tally, structured=False): + data = tally.get_reshaped_data(value='mean') + if structured: + data = data.reshape((-1, self.ELEM_PER_VOXEL)) + else: + data.shape = (data.size, 1) + return np.sum(data, axis=1) + + def update_results(self): + """Update inputs_true.dat without storing results_true.dat.""" + try: + self._build_inputs() + inputs = self._get_inputs() + self._write_inputs(inputs) + self._overwrite_inputs() + self._run_openmc() + self._test_output_created() + finally: + self._cleanup() + + def _cleanup(self): + super()._cleanup() + output = glob.glob('tally*.vtk') + output += glob.glob('tally*.e') + for f in output: + if os.path.exists(f): + os.remove(f) + + +@pytest.fixture +def model(): + openmc.reset_auto_ids() + + model = openmc.Model() + + ### Materials ### + materials = openmc.Materials() + + fuel_mat = openmc.Material(name="fuel") + fuel_mat.add_nuclide("U235", 1.0) + fuel_mat.set_density('g/cc', 4.5) + materials.append(fuel_mat) + + zirc_mat = openmc.Material(name="zircaloy") + zirc_mat.add_element("Zr", 1.0) + zirc_mat.set_density("g/cc", 5.77) + materials.append(zirc_mat) + + water_mat = openmc.Material(name="water") + water_mat.add_nuclide("H1", 2.0) + water_mat.add_nuclide("O16", 1.0) + water_mat.set_density("atom/b-cm", 0.07416) + materials.append(water_mat) + + model.materials = materials + + ### Geometry ### + fuel_min_x = openmc.XPlane(-5.0, name="minimum x") + fuel_max_x = openmc.XPlane(5.0, name="maximum x") + + fuel_min_y = openmc.YPlane(-5.0, name="minimum y") + fuel_max_y = openmc.YPlane(5.0, name="maximum y") + + fuel_min_z = openmc.ZPlane(-5.0, name="minimum z") + fuel_max_z = openmc.ZPlane(5.0, name="maximum z") + + fuel_cell = openmc.Cell(name="fuel") + fuel_cell.region = +fuel_min_x & -fuel_max_x & \ + +fuel_min_y & -fuel_max_y & \ + +fuel_min_z & -fuel_max_z + fuel_cell.fill = fuel_mat + + clad_min_x = openmc.XPlane(-6.0, name="minimum x") + clad_max_x = openmc.XPlane(6.0, name="maximum x") + + clad_min_y = openmc.YPlane(-6.0, name="minimum y") + clad_max_y = openmc.YPlane(6.0, name="maximum y") + + clad_min_z = openmc.ZPlane(-6.0, name="minimum z") + clad_max_z = openmc.ZPlane(6.0, name="maximum z") + + clad_cell = openmc.Cell(name="clad") + clad_cell.region = (-fuel_min_x | +fuel_max_x | + -fuel_min_y | +fuel_max_y | + -fuel_min_z | +fuel_max_z) & \ + (+clad_min_x & -clad_max_x & + +clad_min_y & -clad_max_y & + +clad_min_z & -clad_max_z) + clad_cell.fill = zirc_mat + + # set bounding cell dimension to one + # this will be updated later according to the test case parameters + water_min_x = openmc.XPlane(x0=-1.0, + name="minimum x", + boundary_type='vacuum') + water_max_x = openmc.XPlane(x0=1.0, + name="maximum x", + boundary_type='vacuum') + + water_min_y = openmc.YPlane(y0=-1.0, + name="minimum y", + boundary_type='vacuum') + water_max_y = openmc.YPlane(y0=1.0, + name="maximum y", + boundary_type='vacuum') + + water_min_z = openmc.ZPlane(z0=-1.0, + name="minimum z", + boundary_type='vacuum') + water_max_z = openmc.ZPlane(z0=1.0, + name="maximum z", + boundary_type='vacuum') + + water_cell = openmc.Cell(name="water") + water_cell.region = (-clad_min_x | +clad_max_x | + -clad_min_y | +clad_max_y | + -clad_min_z | +clad_max_z) & \ + (+water_min_x & -water_max_x & + +water_min_y & -water_max_y & + +water_min_z & -water_max_z) + water_cell.fill = water_mat + + # create a containing universe + model.geometry = openmc.Geometry([fuel_cell, clad_cell, water_cell]) + + ### Reference Tally ### + + # create meshes and mesh filters + regular_mesh = openmc.RegularMesh() + regular_mesh.dimension = (10, 10, 10) + regular_mesh.lower_left = (-10.0, -10.0, -10.0) + regular_mesh.upper_right = (10.0, 10.0, 10.0) + + regular_mesh_filter = openmc.MeshFilter(mesh=regular_mesh) + regular_mesh_tally = openmc.Tally(name="regular mesh tally") + regular_mesh_tally.filters = [regular_mesh_filter] + regular_mesh_tally.scores = ['flux'] + + model.tallies = openmc.Tallies([regular_mesh_tally]) + + ### Settings ### + settings = openmc.Settings() + settings.run_mode = 'fixed source' + settings.particles = 1000 + settings.batches = 10 + + # source setup + r = openmc.stats.Uniform(a=0.0, b=0.0) + cos_theta = openmc.stats.Discrete(x=[1.0], p=[1.0]) + phi = openmc.stats.Discrete(x=[0.0], p=[1.0]) + + space = openmc.stats.SphericalIndependent(r, cos_theta, phi) + energy = openmc.stats.Discrete(x=[15.e+06], p=[1.0]) + source = openmc.IndependentSource(space=space, energy=energy) + settings.source = source + + model.settings = settings + + return model + + +MESH_CASES = ( + { + "mesh_filename": "test_mesh_tets.e", + "mesh_kind": "tet", + "holes": None, + "elem_per_voxel": 12, + "libraries": ("moab", "libmesh"), + }, + { + "mesh_filename": "test_mesh_tets.exo", + "mesh_kind": "tet", + "holes": None, + "elem_per_voxel": 12, + "libraries": ("libmesh",), + }, + { + "mesh_filename": "test_mesh_tets_w_holes.e", + "mesh_kind": "tet", + "holes": (333, 90, 77), + "elem_per_voxel": 12, + "libraries": ("moab", "libmesh"), + }, + { + "mesh_filename": "test_mesh_tets_w_holes.exo", + "mesh_kind": "tet", + "holes": (333, 90, 77), + "elem_per_voxel": 12, + "libraries": ("libmesh",), + }, + # { + # "mesh_filename": "test_mesh_hexes.e", + # "mesh_kind": "hex", + # "holes": None, + # "elem_per_voxel": 1, + # "libraries": ("libmesh",), + # }, + # { + # "mesh_filename": "test_mesh_hexes.exo", + # "mesh_kind": "hex", + # "holes": None, + # "elem_per_voxel": 1, + # "libraries": ("libmesh",), + # }, +) + +test_cases = [] +for i, mesh_case in enumerate(MESH_CASES): + for library in mesh_case["libraries"]: + test_cases.append({ + "inputs_true": f"inputs_true{i}_{library}.dat", + "library": library, + **{k: v for k, v in mesh_case.items() if k != "libraries"}, + }) + + +@pytest.mark.parametrize("test_opts", test_cases) +def test_xdg_mesh_tallies(model, test_opts): + if not openmc.lib._xdg_enabled(): + pytest.skip("XDG is not enabled in this build.") + + if test_opts["library"] == "moab" and not openmc.lib._dagmc_enabled(): + pytest.skip("DAGMC (and MOAB) mesh not enabled in this build.") + + if test_opts["library"] == "libmesh" and not openmc.lib._libmesh_enabled(): + pytest.skip("LibMesh is not enabled in this build.") + + # reference mesh tally + regular_mesh_tally = model.tallies[0] + regular_mesh_tally.estimator = 'collision' + + # add analogous XDG mesh tally + xdg_mesh = XDGMesh(test_opts["mesh_filename"], test_opts["library"]) + xdg_filter = openmc.MeshFilter(mesh=xdg_mesh) + + xdg_tally = openmc.Tally(name="xdg mesh tally") + xdg_tally.filters = [xdg_filter] + xdg_tally.scores = ['flux'] + xdg_tally.estimator = 'collision' + model.tallies.append(xdg_tally) + + harness = XDGMeshTallyTest( + 'statepoint.10.h5', + model, + test_opts["inputs_true"], + test_opts["mesh_filename"], + test_opts["mesh_kind"], + test_opts["holes"], + scale_factor=10.0, + ) + harness.ELEM_PER_VOXEL = test_opts["elem_per_voxel"] + harness.main() diff --git a/tests/regression_tests/xdg/tallies/test_mesh_hexes.e b/tests/regression_tests/xdg/tallies/test_mesh_hexes.e new file mode 120000 index 00000000000..32a7bc68fcd --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_hexes.e @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_hexes.e \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_hexes.exo b/tests/regression_tests/xdg/tallies/test_mesh_hexes.exo new file mode 120000 index 00000000000..723a379f7bc --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_hexes.exo @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_hexes.exo \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_tets.e b/tests/regression_tests/xdg/tallies/test_mesh_tets.e new file mode 120000 index 00000000000..65723ab8170 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_tets.e @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_tets.e \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_tets.exo b/tests/regression_tests/xdg/tallies/test_mesh_tets.exo new file mode 120000 index 00000000000..676f4d799d7 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_tets.exo @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_tets.exo \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.e b/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.e new file mode 120000 index 00000000000..2dae88efc80 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.e @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_tets_w_holes.e \ No newline at end of file diff --git a/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.exo b/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.exo new file mode 120000 index 00000000000..fbd15ce88aa --- /dev/null +++ b/tests/regression_tests/xdg/tallies/test_mesh_tets_w_holes.exo @@ -0,0 +1 @@ +../../unstructured_mesh/test_mesh_tets_w_holes.exo \ No newline at end of file From f3603fcd75192bdeade7f017e09b0031ac8fa80c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 10 Feb 2026 13:21:35 -0600 Subject: [PATCH 05/17] Use explicit name for kwarg for clarity --- tests/regression_tests/xdg/tallies/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regression_tests/xdg/tallies/test.py b/tests/regression_tests/xdg/tallies/test.py index f9c842a3546..3a0a6de9943 100644 --- a/tests/regression_tests/xdg/tallies/test.py +++ b/tests/regression_tests/xdg/tallies/test.py @@ -73,7 +73,7 @@ def _compare_results(self): reg_mesh_data = self.get_mesh_tally_data(tally) else: xdg_tally = tally - xdg_mesh_data = self.get_mesh_tally_data(tally, True) + xdg_mesh_data = self.get_mesh_tally_data(tally, structured=True) assert reg_mesh_data is not None assert xdg_mesh_data is not None From 36dc4aca18315b29353173c44aa52a59f39273e1 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Mar 2026 02:44:45 -0500 Subject: [PATCH 06/17] Report XDG availability in build info --- src/output.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/output.cpp b/src/output.cpp index c66c313dce5..af4c5d9ae8c 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -317,6 +317,7 @@ void print_build_info() std::string mpi(n); std::string phdf5(n); std::string dagmc(n); + std::string xdg(n); std::string libmesh(n); std::string png(n); std::string profiling(n); @@ -334,6 +335,9 @@ void print_build_info() #ifdef OPENMC_DAGMC_ENABLED dagmc = y; #endif +#ifdef OPENMC_XDG_ENABLED + xdg = y; +#endif #ifdef OPENMC_LIBMESH_ENABLED libmesh = y; #endif @@ -368,6 +372,7 @@ void print_build_info() fmt::print("Parallel HDF5 enabled: {}\n", phdf5); fmt::print("PNG support: {}\n", png); fmt::print("DAGMC support: {}\n", dagmc); + fmt::print("XDG support: {}\n", xdg); fmt::print("libMesh support: {}\n", libmesh); fmt::print("MCPL support: {}\n", mcpl); fmt::print("Coverage testing: {}\n", coverage); From 2ec0d4744d45f4bf428a421ec4102df01112a6dd Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Mar 2026 03:27:10 -0500 Subject: [PATCH 07/17] Only set libmesh comms in XDG if it has libMesh enabled --- src/initialize.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/initialize.cpp b/src/initialize.cpp index 06f6b74966c..a13baed660d 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -90,9 +90,14 @@ int openmc_init(int argc, char* argv[], const void* intracomm) } #ifdef OPENMC_XDG_ENABLED +#ifdef XDG_ENABLE_LIBMESH + // Set the external libMesh initialization and communicator for XDG if + // libMesh was initialized externally. If libMesh was initialized internally, + // the XDG config will use the internal initialization and communicator. xdg::config::external_libmesh_init = settings::libmesh_init.get(); xdg::config::external_libmesh_comm = &(settings::libmesh_init->comm()); #endif +#endif #endif From 645c97f49fac15ac8ca3ef7f7451dbdf29fd4b7b Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Mar 2026 03:27:49 -0500 Subject: [PATCH 08/17] Adding XDG to CI builds. MOAB and libMesh should be built if XDG is enabled in the test configuration --- .github/workflows/ci.yml | 16 ++++++++++++- tools/ci/gha-build-moab.sh | 29 +++++++++++++++++++++++ tools/ci/gha-install-dagmc.sh | 37 +++++++++++------------------- tools/ci/gha-install-xdg.sh | 43 +++++++++++++++++++++++++++++++++++ tools/ci/gha-install.py | 22 ++++++++++++++---- tools/ci/gha-install.sh | 18 +++++++++++---- 6 files changed, 131 insertions(+), 34 deletions(-) create mode 100755 tools/ci/gha-build-moab.sh create mode 100755 tools/ci/gha-install-xdg.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3e232dce0fe..620e42b4c80 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,6 +47,7 @@ jobs: mpi: [n, y] omp: [n, y] dagmc: [n] + xdg: [n] libmesh: [n] event: [n] @@ -64,6 +65,11 @@ jobs: python-version: "3.12" mpi: y omp: y + - xdg: y + python-version: "3.12" + libmesh: y + mpi: n + omp: y - libmesh: y python-version: "3.12" mpi: y @@ -78,13 +84,15 @@ jobs: mpi: n name: "Python ${{ matrix.python-version }} (omp=${{ matrix.omp }}, mpi=${{ matrix.mpi }}, dagmc=${{ matrix.dagmc }}, - libmesh=${{ matrix.libmesh }}, event=${{ matrix.event }}" + xdg=${{ matrix.xdg }}, libmesh=${{ matrix.libmesh }}, + event=${{ matrix.event }}" env: MPI: ${{ matrix.mpi }} PHDF5: ${{ matrix.mpi }} OMP: ${{ matrix.omp }} DAGMC: ${{ matrix.dagmc }} + XDG: ${{ matrix.xdg }} EVENT: ${{ matrix.event }} LIBMESH: ${{ matrix.libmesh }} NPY_DISABLE_CPU_FEATURES: "AVX512F AVX512_SKX" @@ -146,6 +154,12 @@ jobs: sudo update-alternatives --set mpirun /usr/bin/mpirun.mpich sudo update-alternatives --set mpi-x86_64-linux-gnu /usr/include/x86_64-linux-gnu/mpich + - name: Optional apt dependencies for XDG + shell: bash + if: ${{ matrix.xdg == 'y' }} + run: | + sudo apt install -y libembree-dev + - name: install shell: bash run: | diff --git a/tools/ci/gha-build-moab.sh b/tools/ci/gha-build-moab.sh new file mode 100755 index 00000000000..e9ebd75a720 --- /dev/null +++ b/tools/ci/gha-build-moab.sh @@ -0,0 +1,29 @@ +#!/bin/bash +set -ex + +MOAB_BRANCH='5.5.1' +MOAB_REPO='https://bitbucket.org/fathomteam/moab/' +MOAB_INSTALL_DIR=$HOME/MOAB/ + +pushd $HOME + +if [[ -d $MOAB_INSTALL_DIR/lib ]]; then + popd + exit 0 +fi + +mkdir -p MOAB +cd MOAB +git clone -b $MOAB_BRANCH $MOAB_REPO moab +mkdir build +cd build +cmake ../moab \ + -DENABLE_HDF5=ON \ + -DENABLE_NETCDF=ON \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_INSTALL_PREFIX=$MOAB_INSTALL_DIR +make -j4 +make -j4 install +rm -rf $HOME/MOAB/moab $HOME/MOAB/build + +popd diff --git a/tools/ci/gha-install-dagmc.sh b/tools/ci/gha-install-dagmc.sh index 8d0648a5041..21133c6d3cf 100755 --- a/tools/ci/gha-install-dagmc.sh +++ b/tools/ci/gha-install-dagmc.sh @@ -2,34 +2,23 @@ #!/bin/bash set -ex -# MOAB Variables -MOAB_BRANCH='5.5.1' -MOAB_REPO='https://bitbucket.org/fathomteam/moab/' -MOAB_INSTALL_DIR=$HOME/MOAB/ - # DAGMC Variables DAGMC_BRANCH='develop' DAGMC_REPO='https://github.com/svalinn/dagmc' DAGMC_INSTALL_DIR=$HOME/DAGMC/ -CURRENT_DIR=$(pwd) - -# MOAB Install -cd $HOME -mkdir MOAB && cd MOAB -git clone -b $MOAB_BRANCH $MOAB_REPO -mkdir build && cd build -cmake ../moab -DENABLE_HDF5=ON -DENABLE_NETCDF=ON -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=$MOAB_INSTALL_DIR -make -j && make -j install -rm -rf $HOME/MOAB/moab $HOME/MOAB/build - # DAGMC Install -cd $HOME -mkdir DAGMC && cd DAGMC -git clone -b $DAGMC_BRANCH $DAGMC_REPO -mkdir build && cd build -cmake ../dagmc -DBUILD_TALLY=ON -DCMAKE_INSTALL_PREFIX=$DAGMC_INSTALL_DIR -DBUILD_STATIC_LIBS=OFF -DMOAB_DIR=$MOAB_INSTALL_DIR -make -j install +pushd $HOME +mkdir -p DAGMC +cd DAGMC +git clone -b $DAGMC_BRANCH $DAGMC_REPO dagmc +mkdir build +cd build +cmake ../dagmc \ + -DBUILD_TALLY=ON \ + -DCMAKE_INSTALL_PREFIX=$DAGMC_INSTALL_DIR \ + -DBUILD_STATIC_LIBS=OFF \ + -DMOAB_DIR=$HOME/MOAB +make -j4 install rm -rf $HOME/DAGMC/dagmc $HOME/DAGMC/build - -cd $CURRENT_DIR +popd diff --git a/tools/ci/gha-install-xdg.sh b/tools/ci/gha-install-xdg.sh new file mode 100755 index 00000000000..2dfe0f4b6d6 --- /dev/null +++ b/tools/ci/gha-install-xdg.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -ex + +XDG_BRANCH='main' +XDG_REPO='https://github.com/xdg-org/xdg.git' +XDG_INSTALL_DIR=$HOME/XDG/ + +pushd $HOME + +mkdir -p XDG +cd XDG +git clone -b $XDG_BRANCH $XDG_REPO xdg +mkdir build +cd build + +cmake_args=( + ../xdg + -DCMAKE_INSTALL_PREFIX=$XDG_INSTALL_DIR + -DXDG_BUILD_TESTS=OFF + -DXDG_BUILD_TOOLS=OFF + -DXDG_ENABLE_MOAB=ON + -DMOAB_DIR=$HOME/MOAB +) + +if [[ $LIBMESH == 'y' || $XDG == 'y' ]]; then + cmake_args+=( + -DXDG_ENABLE_LIBMESH=ON + -DCMAKE_PREFIX_PATH=$HOME/LIBMESH + ) +else + cmake_args+=(-DXDG_ENABLE_LIBMESH=OFF) +fi + +if [[ $MPI == 'y' ]]; then + cmake_args+=(-DXDG_LINK_MPI=ON) +fi + +cmake "${cmake_args[@]}" +make -j4 +make -j4 install +rm -rf $HOME/XDG/xdg $HOME/XDG/build + +popd diff --git a/tools/ci/gha-install.py b/tools/ci/gha-install.py index 5488b95476e..e352ce3a043 100644 --- a/tools/ci/gha-install.py +++ b/tools/ci/gha-install.py @@ -3,7 +3,8 @@ import subprocess -def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False): +def install(omp=False, mpi=False, phdf5=False, dagmc=False, xdg=False, + libmesh=False): # Create build directory and change to it shutil.rmtree('build', ignore_errors=True) os.mkdir('build') @@ -29,16 +30,26 @@ def install(omp=False, mpi=False, phdf5=False, dagmc=False, libmesh=False): else: cmake_cmd.append('-DHDF5_PREFER_PARALLEL=OFF') + prefix_paths = [] + if dagmc: cmake_cmd.append('-DOPENMC_USE_DAGMC=ON') cmake_cmd.append('-DOPENMC_USE_UWUW=ON') dagmc_path = os.environ.get('HOME') + '/DAGMC' - cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + dagmc_path) + prefix_paths.append(dagmc_path) + + if xdg: + cmake_cmd.append('-DOPENMC_USE_XDG=ON') + xdg_path = os.environ.get('HOME') + '/XDG' + prefix_paths.append(xdg_path) - if libmesh: + if libmesh or xdg: cmake_cmd.append('-DOPENMC_USE_LIBMESH=ON') libmesh_path = os.environ.get('HOME') + '/LIBMESH' - cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + libmesh_path) + prefix_paths.append(libmesh_path) + + if prefix_paths: + cmake_cmd.append('-DCMAKE_PREFIX_PATH=' + ';'.join(prefix_paths)) # Build in coverage mode for coverage testing cmake_cmd.append('-DOPENMC_ENABLE_COVERAGE=on') @@ -59,10 +70,11 @@ def main(): mpi = (os.environ.get('MPI') == 'y') phdf5 = (os.environ.get('PHDF5') == 'y') dagmc = (os.environ.get('DAGMC') == 'y') + xdg = (os.environ.get('XDG') == 'y') libmesh = (os.environ.get('LIBMESH') == 'y') # Build and install - install(omp, mpi, phdf5, dagmc, libmesh) + install(omp, mpi, phdf5, dagmc, xdg, libmesh) if __name__ == '__main__': main() diff --git a/tools/ci/gha-install.sh b/tools/ci/gha-install.sh index 4cf62afbb71..8a125d908b2 100755 --- a/tools/ci/gha-install.sh +++ b/tools/ci/gha-install.sh @@ -9,20 +9,30 @@ pip install --upgrade numpy # Install NJOY 2016 ./tools/ci/gha-install-njoy.sh -# Install DAGMC if needed -if [[ $DAGMC = 'y' ]]; then - ./tools/ci/gha-install-dagmc.sh +# Build MOAB if needed +if [[ $DAGMC = 'y' || $XDG = 'y' ]]; then + ./tools/ci/gha-build-moab.sh fi # Install NCrystal and verify installation pip install 'ncrystal>=4.1.0' nctool --test +# Install DAGMC if needed +if [[ $DAGMC = 'y' ]]; then + ./tools/ci/gha-install-dagmc.sh +fi + # Install libMesh if needed -if [[ $LIBMESH = 'y' ]]; then +if [[ $LIBMESH = 'y' || $XDG = 'y' ]]; then ./tools/ci/gha-install-libmesh.sh fi +# Install XDG if needed +if [[ $XDG = 'y' ]]; then + ./tools/ci/gha-install-xdg.sh +fi + # Install MCPL pip install mcpl From 1b3a9a26e7e46055a33817e219c6a18159038ad8 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Mar 2026 03:30:05 -0500 Subject: [PATCH 09/17] Adding init files for proper test discovery with pytest --- tests/regression_tests/xdg/__init__.py | 0 tests/regression_tests/xdg/tallies/__init__.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/regression_tests/xdg/__init__.py create mode 100644 tests/regression_tests/xdg/tallies/__init__.py diff --git a/tests/regression_tests/xdg/__init__.py b/tests/regression_tests/xdg/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/xdg/tallies/__init__.py b/tests/regression_tests/xdg/tallies/__init__.py new file mode 100644 index 00000000000..e69de29bb2d From 028a9adec38610228b391c0ed2201ec2f16172c9 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 13 Mar 2026 04:41:16 -0500 Subject: [PATCH 10/17] Update submodules when cloning XDG in CI --- tools/ci/gha-install-xdg.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/ci/gha-install-xdg.sh b/tools/ci/gha-install-xdg.sh index 2dfe0f4b6d6..f783fb0b324 100755 --- a/tools/ci/gha-install-xdg.sh +++ b/tools/ci/gha-install-xdg.sh @@ -9,7 +9,7 @@ pushd $HOME mkdir -p XDG cd XDG -git clone -b $XDG_BRANCH $XDG_REPO xdg +git clone -b $XDG_BRANCH --recurse-submodules $XDG_REPO xdg mkdir build cd build From 380b81b40f2547f3585a6f58611a8f4a4a233b37 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 15 Mar 2026 05:10:50 -0500 Subject: [PATCH 11/17] Add XDG to the OpenMC CMake config file --- cmake/OpenMCConfig.cmake.in | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index cc837bba711..3018de0f73b 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -16,6 +16,10 @@ if(@OPENMC_USE_LIBMESH@) pkg_check_modules(LIBMESH REQUIRED @LIBMESH_PC_FILE@>=1.7.0 IMPORTED_TARGET) endif() +if(@OPENMC_USE_XDG@) + find_package(XDG REQUIRED HINTS @XDG_DIR@) +endif() + find_package(PNG) if(NOT TARGET OpenMC::libopenmc) From ca5bd5ad77e3901301b2bd95ddd026a8e5a38361 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 15 Mar 2026 05:44:29 -0500 Subject: [PATCH 12/17] Adding more test scenarios for XDG meshes --- .../xdg/tallies/inputs_true4_libmesh.dat | 14 +-- .../xdg/tallies/inputs_true4_moab.dat | 90 +++++++++++++++++++ .../xdg/tallies/inputs_true5_libmesh.dat | 14 +-- .../xdg/tallies/inputs_true6_libmesh.dat | 90 +++++++++++++++++++ .../xdg/tallies/inputs_true6_moab.dat | 90 +++++++++++++++++++ .../xdg/tallies/inputs_true7_libmesh.dat | 90 +++++++++++++++++++ tests/regression_tests/xdg/tallies/test.py | 38 +++++++- 7 files changed, 411 insertions(+), 15 deletions(-) create mode 100644 tests/regression_tests/xdg/tallies/inputs_true4_moab.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true6_libmesh.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true6_moab.dat create mode 100644 tests/regression_tests/xdg/tallies/inputs_true7_libmesh.dat diff --git a/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat index 3fc21974d5f..9e0c8b7a4d8 100644 --- a/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat +++ b/tests/regression_tests/xdg/tallies/inputs_true4_libmesh.dat @@ -35,12 +35,12 @@ - - - - - - + + + + + + fixed source @@ -68,7 +68,7 @@ 10.0 10.0 10.0 - test_mesh_hexes.e + test_mesh_tets.e 1 diff --git a/tests/regression_tests/xdg/tallies/inputs_true4_moab.dat b/tests/regression_tests/xdg/tallies/inputs_true4_moab.dat new file mode 100644 index 00000000000..ae53681788f --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true4_moab.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat index 1a21db5a0bc..d0647600dba 100644 --- a/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat +++ b/tests/regression_tests/xdg/tallies/inputs_true5_libmesh.dat @@ -35,12 +35,12 @@ - - - - - - + + + + + + fixed source @@ -68,7 +68,7 @@ 10.0 10.0 10.0 - test_mesh_hexes.exo + test_mesh_tets.exo 1 diff --git a/tests/regression_tests/xdg/tallies/inputs_true6_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true6_libmesh.dat new file mode 100644 index 00000000000..e33a0a7c4a5 --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true6_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true6_moab.dat b/tests/regression_tests/xdg/tallies/inputs_true6_moab.dat new file mode 100644 index 00000000000..8d5b3970f9d --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true6_moab.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.e + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/inputs_true7_libmesh.dat b/tests/regression_tests/xdg/tallies/inputs_true7_libmesh.dat new file mode 100644 index 00000000000..64983e4319d --- /dev/null +++ b/tests/regression_tests/xdg/tallies/inputs_true7_libmesh.dat @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 10 + + + + + 1.0 1.0 + + + 0.0 1.0 + + + + 15000000.0 1.0 + + + + + + 10 10 10 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + test_mesh_tets_w_holes.exo + + + 1 + + + 2 + + + 1 + flux + collision + + + 2 + flux + collision + + + diff --git a/tests/regression_tests/xdg/tallies/test.py b/tests/regression_tests/xdg/tallies/test.py index 3a0a6de9943..05f6e7fc5cf 100644 --- a/tests/regression_tests/xdg/tallies/test.py +++ b/tests/regression_tests/xdg/tallies/test.py @@ -255,6 +255,7 @@ def model(): "mesh_kind": "tet", "holes": None, "elem_per_voxel": 12, + "external_geom": False, "libraries": ("moab", "libmesh"), }, { @@ -262,6 +263,7 @@ def model(): "mesh_kind": "tet", "holes": None, "elem_per_voxel": 12, + "external_geom": False, "libraries": ("libmesh",), }, { @@ -269,6 +271,7 @@ def model(): "mesh_kind": "tet", "holes": (333, 90, 77), "elem_per_voxel": 12, + "external_geom": False, "libraries": ("moab", "libmesh"), }, { @@ -276,6 +279,39 @@ def model(): "mesh_kind": "tet", "holes": (333, 90, 77), "elem_per_voxel": 12, + "external_geom": False, + "libraries": ("libmesh",), + }, + { + "mesh_filename": "test_mesh_tets.e", + "mesh_kind": "tet", + "holes": None, + "elem_per_voxel": 12, + "external_geom": True, + "libraries": ("moab", "libmesh"), + }, + { + "mesh_filename": "test_mesh_tets.exo", + "mesh_kind": "tet", + "holes": None, + "elem_per_voxel": 12, + "external_geom": True, + "libraries": ("libmesh",), + }, + { + "mesh_filename": "test_mesh_tets_w_holes.e", + "mesh_kind": "tet", + "holes": (333, 90, 77), + "elem_per_voxel": 12, + "external_geom": True, + "libraries": ("moab", "libmesh"), + }, + { + "mesh_filename": "test_mesh_tets_w_holes.exo", + "mesh_kind": "tet", + "holes": (333, 90, 77), + "elem_per_voxel": 12, + "external_geom": True, "libraries": ("libmesh",), }, # { @@ -336,7 +372,7 @@ def test_xdg_mesh_tallies(model, test_opts): test_opts["mesh_filename"], test_opts["mesh_kind"], test_opts["holes"], - scale_factor=10.0, + scale_factor=15.0 if test_opts["external_geom"] else 10.0, ) harness.ELEM_PER_VOXEL = test_opts["elem_per_voxel"] harness.main() From 8c178fdc74ab513ee3afd5d7de3db5f1eb93f6fd Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sun, 15 Mar 2026 06:05:10 -0500 Subject: [PATCH 13/17] More descriptive test labels --- tests/regression_tests/xdg/tallies/test.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/regression_tests/xdg/tallies/test.py b/tests/regression_tests/xdg/tallies/test.py index 05f6e7fc5cf..b8659148c56 100644 --- a/tests/regression_tests/xdg/tallies/test.py +++ b/tests/regression_tests/xdg/tallies/test.py @@ -331,6 +331,7 @@ def model(): ) test_cases = [] +test_case_ids = [] for i, mesh_case in enumerate(MESH_CASES): for library in mesh_case["libraries"]: test_cases.append({ @@ -338,9 +339,12 @@ def model(): "library": library, **{k: v for k, v in mesh_case.items() if k != "libraries"}, }) + mesh_stem = os.path.splitext(mesh_case["mesh_filename"])[0] + geom_mode = "external-geom" if mesh_case["external_geom"] else "bounded-geom" + holes = "holes" if mesh_case["holes"] else "solid" + test_case_ids.append(f"{library}-{mesh_stem}-{holes}-{geom_mode}") - -@pytest.mark.parametrize("test_opts", test_cases) +@pytest.mark.parametrize("test_opts", test_cases, ids=test_case_ids) def test_xdg_mesh_tallies(model, test_opts): if not openmc.lib._xdg_enabled(): pytest.skip("XDG is not enabled in this build.") From ab9b6106da6704c686c7abe6aae0aadacfa4c698 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 18 Mar 2026 14:31:43 -0500 Subject: [PATCH 14/17] Use existing vendor targets if they exist --- cmake/OpenMCConfig.cmake.in | 43 ++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/cmake/OpenMCConfig.cmake.in b/cmake/OpenMCConfig.cmake.in index 3018de0f73b..d339e63adc5 100644 --- a/cmake/OpenMCConfig.cmake.in +++ b/cmake/OpenMCConfig.cmake.in @@ -1,12 +1,11 @@ get_filename_component(OpenMC_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) -# Compute the install prefix from this file's location -get_filename_component(_OPENMC_PREFIX "${OpenMC_CMAKE_DIR}/../../.." ABSOLUTE) +include(CMakeFindDependencyMacro) -find_package(fmt CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) -find_package(pugixml CONFIG REQUIRED HINTS ${_OPENMC_PREFIX}) -if(@OPENMC_USE_DAGMC@) - find_package(DAGMC REQUIRED HINTS @DAGMC_DIR@) +if(@OPENMC_USE_XDG@) + if(NOT TARGET xdg::xdg) + find_dependency(XDG CONFIG REQUIRED HINTS "@XDG_DIR@") + endif() endif() if(@OPENMC_USE_LIBMESH@) @@ -16,22 +15,40 @@ if(@OPENMC_USE_LIBMESH@) pkg_check_modules(LIBMESH REQUIRED @LIBMESH_PC_FILE@>=1.7.0 IMPORTED_TARGET) endif() -if(@OPENMC_USE_XDG@) - find_package(XDG REQUIRED HINTS @XDG_DIR@) +if(@OPENMC_USE_DAGMC@) + if(NOT TARGET dagmc-shared) + find_dependency(DAGMC REQUIRED HINTS "@DAGMC_DIR@") + endif() endif() -find_package(PNG) +if(NOT TARGET fmt::fmt) + find_dependency(fmt CONFIG REQUIRED) +endif() -if(NOT TARGET OpenMC::libopenmc) - include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") +if(NOT TARGET pugixml::pugixml AND NOT TARGET pugixml) + find_dependency(pugixml CONFIG REQUIRED) +endif() + +if(@PNG_FOUND@) + if(NOT TARGET PNG::PNG) + find_dependency(PNG REQUIRED) + endif() endif() if(@OPENMC_USE_MPI@) - find_package(MPI REQUIRED) + if(NOT TARGET MPI::MPI_CXX) + find_dependency(MPI REQUIRED) + endif() endif() if(@OPENMC_USE_OPENMP@) - find_package(OpenMP REQUIRED) + if(NOT TARGET OpenMP::OpenMP_CXX) + find_dependency(OpenMP REQUIRED) + endif() +endif() + +if(NOT TARGET OpenMC::libopenmc) + include("${OpenMC_CMAKE_DIR}/OpenMCTargets.cmake") endif() if(@OPENMC_USE_UWUW@ AND NOT ${DAGMC_BUILD_UWUW}) From 6cfde211d8dae2f4f6ccc385beb1185160210e51 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 18 Mar 2026 15:31:22 -0500 Subject: [PATCH 15/17] Returning const string ref for mesh filename --- include/openmc/mesh.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index a242e164d45..3cd9cb83cb1 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -779,7 +779,7 @@ class UnstructuredMesh : public Mesh { virtual std::string library() const = 0; //! Get the mesh filename - virtual std::string filename() const { return filename_; } + virtual const std::string& filename() const { return filename_; } // Data members bool output_ { From 1584b938047c8f1b874b4a536ebeeaf3acc37a5f Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 18 Mar 2026 15:37:26 -0500 Subject: [PATCH 16/17] C++ style --- include/openmc/mesh.h | 5 ++--- include/openmc/xdg.h | 25 ++++++++++----------- src/mesh.cpp | 5 +++-- src/xdg.cpp | 51 ++++++++++++++++++++++++++++--------------- 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/include/openmc/mesh.h b/include/openmc/mesh.h index 3cd9cb83cb1..26e7fe0c3ec 100644 --- a/include/openmc/mesh.h +++ b/include/openmc/mesh.h @@ -47,8 +47,7 @@ namespace openmc { enum class ElementType { UNSUPPORTED = -1, LINEAR_TET, LINEAR_HEX }; -struct NextMeshCell -{ +struct NextMeshCell { double distance {INFTY}; int face_idx {-1}; std::array next_ijk; @@ -834,7 +833,7 @@ class UnstructuredMesh : public Mesh { } } V result = s * (coords[1] - coords[0]) + t * (coords[2] - coords[0]) + - u * (coords[3] - coords[0]) + coords[0]; + u * (coords[3] - coords[0]) + coords[0]; return {result[0], result[1], result[2]}; } diff --git a/include/openmc/xdg.h b/include/openmc/xdg.h index 1e045e4c2c1..fbb6a4d36ba 100644 --- a/include/openmc/xdg.h +++ b/include/openmc/xdg.h @@ -16,7 +16,7 @@ extern "C" const bool XDG_ENABLED; #include "openmc/position.h" namespace openmc { -class XDGMesh : public UnstructuredMesh{ +class XDGMesh : public UnstructuredMesh { public: // Constructors @@ -42,10 +42,7 @@ class XDGMesh : public UnstructuredMesh{ int get_bin(Position r) const override; - bool bin_is_valid(int bin) const - { - return bin >= 0 && bin < n_bins(); - } + bool bin_is_valid(int bin) const { return bin >= 0 && bin < n_bins(); } xdg::MeshID bin_to_mesh_id(int bin) const; @@ -89,22 +86,24 @@ class XDGMesh : public UnstructuredMesh{ //! \return Volume of the bin double volume(int bin) const override; - //! Get the distance to the nearest boundary for a given position and direction - //! \param[in] g GeometryState object containing position and direction - //! \return NextMeshCell struct containing distance, face index, and next indices + //! Get the distance to the nearest boundary for a given position and + //! direction \param[in] g GeometryState object containing position and + //! direction \return NextMeshCell struct containing distance, face index, and + //! next indices NextMeshCell distance_to_bin_boundary(GeometryState& g) const; - //! Get the distance to the nearest boundary for a given position and direction - //! \param[in] r Position to check - //! \param[in] u Direction to check + //! Get the distance to the nearest boundary for a given position and + //! direction \param[in] r Position to check \param[in] u Direction to check //! \return Distance to the nearest boundary - NextMeshCell distance_to_bin_boundary(int bin, const Position& r, const Direction& u) const; + NextMeshCell distance_to_bin_boundary( + int bin, const Position& r, const Direction& u) const; private: void initialize() override; std::shared_ptr xdg_; //!< XDG instance - xdg::MeshLibrary mesh_library_ {xdg::MeshLibrary::LIBMESH}; //!< Mesh library type + xdg::MeshLibrary mesh_library_ { + xdg::MeshLibrary::LIBMESH}; //!< Mesh library type }; } // namespace openmc diff --git a/src/mesh.cpp b/src/mesh.cpp index c820074f2a1..898c8fb5fbc 100644 --- a/src/mesh.cpp +++ b/src/mesh.cpp @@ -351,7 +351,7 @@ const std::unique_ptr& Mesh::create( #endif #ifdef OPENMC_XDG_ENABLED } else if (mesh_type == "xdg") { - model::meshes.push_back(make_unique(dataset)); + model::meshes.push_back(make_unique(dataset)); #endif #ifdef OPENMC_LIBMESH_ENABLED } else if (mesh_type == UnstructuredMesh::mesh_type && @@ -3644,7 +3644,8 @@ Position LibMesh::sample_element(int32_t bin, uint64_t* seed) const tet_verts[i] = {node_ref(0), node_ref(1), node_ref(2)}; } // Samples position within tet using Barycentric coordinates - Position sampled_position = this->sample_tet({tet_verts.begin(), tet_verts.end()}, seed); + Position sampled_position = + this->sample_tet({tet_verts.begin(), tet_verts.end()}, seed); if (length_multiplier_ > 0.0) { return length_multiplier_ * sampled_position; } else { diff --git a/src/xdg.cpp b/src/xdg.cpp index e7445eb7937..3ca4cf745dc 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -40,10 +40,10 @@ namespace openmc { // XDG Mesh implementation //============================================================================== - const std::string XDGMesh::mesh_lib_type = "xdg"; -XDGMesh::XDGMesh(pugi::xml_node node) : UnstructuredMesh(node) { +XDGMesh::XDGMesh(pugi::xml_node node) : UnstructuredMesh(node) +{ std::string mesh_lib = get_node_value(node, "library", true, true); if (mesh_lib == "moab") { mesh_library_ = xdg::MeshLibrary::MOAB; @@ -53,7 +53,8 @@ XDGMesh::XDGMesh(pugi::xml_node node) : UnstructuredMesh(node) { initialize(); } -XDGMesh::XDGMesh(hid_t group) : UnstructuredMesh(group) { +XDGMesh::XDGMesh(hid_t group) : UnstructuredMesh(group) +{ std::string mesh_lib; read_dataset(group, "library", mesh_lib); if (mesh_lib == "moab") { @@ -64,21 +65,24 @@ XDGMesh::XDGMesh(hid_t group) : UnstructuredMesh(group) { initialize(); } - -XDGMesh::XDGMesh(const std::string& filename, double length_multiplier) { +XDGMesh::XDGMesh(const std::string& filename, double length_multiplier) +{ filename_ = filename; set_length_multiplier(length_multiplier); initialize(); } -XDGMesh::XDGMesh(std::shared_ptr external_xdg) { +XDGMesh::XDGMesh(std::shared_ptr external_xdg) +{ xdg_ = external_xdg; filename_ = "unknown (external file)"; initialize(); } -void XDGMesh::initialize() { - if (xdg_) return; +void XDGMesh::initialize() +{ + if (xdg_) + return; // create XDGMesh instance xdg_ = xdg::XDG::create(mesh_library_); @@ -93,11 +97,13 @@ void XDGMesh::initialize() { xdg_->mesh_manager()->parse_metadata(); } -void XDGMesh::prepare_for_point_location() { +void XDGMesh::prepare_for_point_location() +{ xdg_->prepare_raytracer(); } -Position XDGMesh::sample_element(int32_t bin, uint64_t* seed) const { +Position XDGMesh::sample_element(int32_t bin, uint64_t* seed) const +{ // MeshIDs are 1-indexed, so we add 1 to the bin, which is 0-indexed auto vertices = xdg_->mesh_manager()->element_vertices(bin_to_mesh_id(bin)); return this->sample_tet(vertices, seed); @@ -112,8 +118,11 @@ void XDGMesh::bins_crossed(Position r0, Position r1, const Direction& u, double length_rcp = 1 / (p1 - p0).length(); auto track_segments = xdg_->segments(p0, p1); // remove elements with lengths of zero - track_segments.erase(std::remove_if(track_segments.begin(), track_segments.end(), [](const std::pair& p) {return p.second == 0.0;}), track_segments.end()); - for (const auto& track_segment : track_segments ) { + track_segments.erase( + std::remove_if(track_segments.begin(), track_segments.end(), + [](const std::pair& p) { return p.second == 0.0; }), + track_segments.end()); + for (const auto& track_segment : track_segments) { bins.push_back(mesh_id_to_bin(track_segment.first)); lengths.push_back(track_segment.second * length_rcp); } @@ -125,11 +134,13 @@ int XDGMesh::get_bin(Position r) const return mesh_id_to_bin(xdg_->find_element(p)); } -int XDGMesh::n_bins() const { +int XDGMesh::n_bins() const +{ return xdg_->mesh_manager()->num_volume_elements(); } -int XDGMesh::n_surface_bins() const { +int XDGMesh::n_surface_bins() const +{ return 4 * n_bins(); } @@ -140,11 +151,13 @@ std::pair, vector> XDGMesh::plot( return {}; } -std::string XDGMesh::library() const { +std::string XDGMesh::library() const +{ return mesh_lib_type; } -std::string XDGMesh::mesh_library() const { +std::string XDGMesh::mesh_library() const +{ if (mesh_library_ == xdg::MeshLibrary::LIBMESH) { return "libmesh"; } else if (mesh_library_ == xdg::MeshLibrary::MOAB) { @@ -159,7 +172,8 @@ void XDGMesh::write(const std::string& base_filename) const Position XDGMesh::centroid(int bin) const { - auto element_vertices = xdg_->mesh_manager()->element_vertices(bin_to_mesh_id(bin)); + auto element_vertices = + xdg_->mesh_manager()->element_vertices(bin_to_mesh_id(bin)); xdg::Vertex centroid {0.0, 0.0, 0.0}; for (const auto& v : element_vertices) { @@ -204,7 +218,8 @@ xdg::MeshID XDGMesh::bin_to_mesh_id(int bin) const int32_t XDGMesh::mesh_id_to_bin(xdg::MeshID id) const { - if (id < 0) return -1; + if (id < 0) + return -1; return xdg_->mesh_manager()->element_index(id); } From fe27f046288bf65856f47435d66f7c24ec46fc3c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 21 Mar 2026 12:04:10 -0500 Subject: [PATCH 17/17] Apply suggestions from @nuclearkevin Co-authored-by: Kevin Sawatzky <66632997+nuclearkevin@users.noreply.github.com> --- src/initialize.cpp | 2 +- src/xdg.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index a13baed660d..b147d784e58 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -95,7 +95,7 @@ int openmc_init(int argc, char* argv[], const void* intracomm) // libMesh was initialized externally. If libMesh was initialized internally, // the XDG config will use the internal initialization and communicator. xdg::config::external_libmesh_init = settings::libmesh_init.get(); - xdg::config::external_libmesh_comm = &(settings::libmesh_init->comm()); + xdg::config::external_libmesh_comm = settings::libmesh_comm; #endif #endif diff --git a/src/xdg.cpp b/src/xdg.cpp index 3ca4cf745dc..0bc69ad1850 100644 --- a/src/xdg.cpp +++ b/src/xdg.cpp @@ -182,7 +182,7 @@ Position XDGMesh::centroid(int bin) const centroid /= double(element_vertices.size()); - return {centroid[0], centroid[1], centroid[1]}; + return {centroid[0], centroid[1], centroid[2]}; } int XDGMesh::n_vertices() const