Skip to content

Commit 588a846

Browse files
Add length_multiplier to scale DAGMCUniverse (openmc-dev#4033)
Co-authored-by: GuySten <62616591+GuySten@users.noreply.github.com> Co-authored-by: GuySten <guyste@post.bgu.ac.il>
1 parent 4208c89 commit 588a846

7 files changed

Lines changed: 209 additions & 13 deletions

File tree

‎docs/source/io_formats/geometry.rst‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,11 @@ Each ``<dagmc_universe>`` element can have the following attributes or sub-eleme
406406

407407
*Default*: None
408408

409+
:length_multiplier:
410+
Coordinate scaling factor applied when loading a DAGMC geometry model.
411+
412+
*Default*: 1.0
413+
409414
:cell:
410415
Zero or more ``<cell>`` sub-elements may appear to override properties of
411416
individual DAGMC volumes. Each ``<cell>`` element supports the following

‎include/openmc/dagmc.h‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,15 @@ class DAGUniverse : public Universe {
104104
//! \param[in] filename Name of the DAGMC file
105105
//! \param[in] auto_geom_ids Whether or not to automatically assign cell and
106106
//! surface IDs \param[in] auto_mat_ids Whether or not to automatically assign
107-
//! material IDs
107+
//! material IDs \param[in] length_multiplier Multiplicative factor applied to
108+
//! geometry coordinates
108109
explicit DAGUniverse(const std::string& filename, bool auto_geom_ids = false,
109-
bool auto_mat_ids = false);
110+
bool auto_mat_ids = false, double length_multiplier = 1.0);
110111

111112
//! Alternative DAGMC universe constructor for external DAGMC instance
112113
explicit DAGUniverse(std::shared_ptr<moab::DagMC> external_dagmc_ptr,
113114
const std::string& filename = "", bool auto_geom_ids = false,
114-
bool auto_mat_ids = false);
115+
bool auto_mat_ids = false, double length_multiplier = 1.0);
115116

116117
//! Initialize the DAGMC accel. data structures, indices, material
117118
//! assignments, etc.
@@ -209,6 +210,8 @@ class DAGUniverse : public Universe {
209210
//!< universe
210211
bool adjust_material_ids_; //!< Indicates whether or not to automatically
211212
//!< generate new material IDs for the universe
213+
double length_multiplier_ {
214+
1.0}; //!< Multiplicative factor applied to geometry coordinates
212215
bool has_graveyard_; //!< Indicates if the DAGMC geometry has a "graveyard"
213216
//!< volume
214217
};

‎openmc/dagmc.py‎

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from collections.abc import Iterable, Mapping
2-
from numbers import Integral
2+
from numbers import Integral, Real
33

44
import h5py
55
import lxml.etree as ET
@@ -36,6 +36,8 @@ class DAGMCUniverse(openmc.UniverseBase):
3636
auto_mat_ids : bool
3737
Set IDs automatically on initialization (True) or report overlaps in ID
3838
space between OpenMC and UWUW materials (False)
39+
length_multiplier : float
40+
Coordinate scaling factor applied when loading a DAGMC geometry model.
3941
Attributes
4042
----------
4143
id : int
@@ -50,6 +52,8 @@ class DAGMCUniverse(openmc.UniverseBase):
5052
auto_mat_ids : bool
5153
Set IDs automatically on initialization (True) or report overlaps in ID
5254
space between OpenMC and UWUW materials (False)
55+
length_multiplier : float
56+
Coordinate scaling factor applied when loading a DAGMC geometry model.
5357
bounding_box : openmc.BoundingBox
5458
Lower-left and upper-right coordinates of an axis-aligned bounding box
5559
of the universe.
@@ -79,23 +83,30 @@ def __init__(self,
7983
universe_id=None,
8084
name='',
8185
auto_geom_ids=False,
82-
auto_mat_ids=False):
86+
auto_mat_ids=False,
87+
length_multiplier: float = 1.0):
8388
super().__init__(universe_id, name)
8489
# Initialize class attributes
8590
self.filename = filename
8691
self.auto_geom_ids = auto_geom_ids
8792
self.auto_mat_ids = auto_mat_ids
93+
self.length_multiplier = length_multiplier
8894

8995
def __repr__(self):
9096
string = super().__repr__()
9197
string += '{: <16}=\t{}\n'.format('\tGeom', 'DAGMC')
9298
string += '{: <16}=\t{}\n'.format('\tFile', self.filename)
99+
if self.length_multiplier != 1.0:
100+
string += '{: <16}=\t{}\n'.format(
101+
'\tLength multiplier', self.length_multiplier
102+
)
93103
return string
94104

95105
@property
96106
def bounding_box(self):
97107
with h5py.File(self.filename) as dagmc_file:
98108
coords = dagmc_file['tstt']['nodes']['coordinates'][()]
109+
coords *= self.length_multiplier
99110
lower_left_corner = coords.min(axis=0)
100111
upper_right_corner = coords.max(axis=0)
101112
return openmc.BoundingBox(lower_left_corner, upper_right_corner)
@@ -175,6 +186,16 @@ def auto_mat_ids(self, val):
175186
cv.check_type('DAGMC automatic material ids', val, bool)
176187
self._auto_mat_ids = val
177188

189+
@property
190+
def length_multiplier(self):
191+
return self._length_multiplier
192+
193+
@length_multiplier.setter
194+
def length_multiplier(self, length_multiplier):
195+
cv.check_type('DAGMC universe length multiplier', length_multiplier, Real)
196+
cv.check_greater_than('DAGMC universe length multiplier', length_multiplier, 0.0)
197+
self._length_multiplier = length_multiplier
198+
178199
@property
179200
def material_names(self):
180201
material_tags_ascii = []
@@ -254,6 +275,8 @@ def create_xml_subelement(self, xml_element, memo=None):
254275
dagmc_element.set('auto_geom_ids', 'true')
255276
if self.auto_mat_ids:
256277
dagmc_element.set('auto_mat_ids', 'true')
278+
if self.length_multiplier != 1.0:
279+
dagmc_element.set('length_multiplier', str(self.length_multiplier))
257280
dagmc_element.set('filename', str(self.filename))
258281
if self.cells:
259282
for cell in self.cells.values():
@@ -374,8 +397,9 @@ def from_hdf5(cls, group):
374397
id = int(group.name.split('/')[-1].lstrip('universe '))
375398
fname = group['filename'][()].decode()
376399
name = group['name'][()].decode() if 'name' in group else None
400+
length_multiplier = float(group.attrs.get('length_multiplier', 1.0))
377401

378-
out = cls(fname, universe_id=id, name=name)
402+
out = cls(fname, universe_id=id, name=name, length_multiplier=length_multiplier)
379403

380404
out.auto_geom_ids = bool(group.attrs['auto_geom_ids'])
381405
out.auto_mat_ids = bool(group.attrs['auto_mat_ids'])
@@ -402,8 +426,9 @@ def from_xml_element(cls, elem, mats=None):
402426
"""
403427
id = int(get_text(elem, 'id'))
404428
fname = get_text(elem, 'filename')
429+
length_multiplier = float(get_text(elem, 'length_multiplier', 1.0))
405430

406-
out = cls(fname, universe_id=id)
431+
out = cls(fname, universe_id=id, length_multiplier=length_multiplier)
407432

408433
name = get_text(elem, 'name')
409434
if name is not None:
@@ -467,7 +492,7 @@ def _partial_deepcopy(self):
467492
its cells, as they are copied within the clone function. This should
468493
only to be used within the openmc.UniverseBase.clone() context.
469494
"""
470-
clone = openmc.DAGMCUniverse(name=self.name, filename=self.filename)
495+
clone = openmc.DAGMCUniverse(name=self.name, filename=self.filename, length_multiplier=self.length_multiplier)
471496
clone.volume = self.volume
472497
clone.auto_geom_ids = self.auto_geom_ids
473498
clone.auto_mat_ids = self.auto_mat_ids

‎src/dagmc.cpp‎

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "openmc/dagmc.h"
22

3+
#include <array>
34
#include <cassert>
45

56
#include "openmc/constants.h"
@@ -80,6 +81,10 @@ DAGUniverse::DAGUniverse(pugi::xml_node node)
8081
adjust_material_ids_ = get_node_value_bool(node, "auto_mat_ids");
8182
}
8283

84+
if (check_for_node(node, "length_multiplier")) {
85+
length_multiplier_ = std::stod(get_node_value(node, "length_multiplier"));
86+
}
87+
8388
// Get material assignment overrides from nested DAGMC cell elements.
8489
if (node.child("cell")) {
8590
for (pugi::xml_node cell_node : node.children("cell")) {
@@ -153,19 +158,21 @@ DAGUniverse::DAGUniverse(pugi::xml_node node)
153158
initialize(material_overrides, temperature_overrides, density_overrides);
154159
}
155160

156-
DAGUniverse::DAGUniverse(
157-
const std::string& filename, bool auto_geom_ids, bool auto_mat_ids)
161+
DAGUniverse::DAGUniverse(const std::string& filename, bool auto_geom_ids,
162+
bool auto_mat_ids, double length_multiplier)
158163
: filename_(filename), adjust_geometry_ids_(auto_geom_ids),
159-
adjust_material_ids_(auto_mat_ids)
164+
adjust_material_ids_(auto_mat_ids), length_multiplier_(length_multiplier)
160165
{
161166
set_id();
162167
initialize();
163168
}
164169

165170
DAGUniverse::DAGUniverse(std::shared_ptr<moab::DagMC> dagmc_ptr,
166-
const std::string& filename, bool auto_geom_ids, bool auto_mat_ids)
171+
const std::string& filename, bool auto_geom_ids, bool auto_mat_ids,
172+
double length_multiplier)
167173
: dagmc_instance_(dagmc_ptr), filename_(filename),
168-
adjust_geometry_ids_(auto_geom_ids), adjust_material_ids_(auto_mat_ids)
174+
adjust_geometry_ids_(auto_geom_ids), adjust_material_ids_(auto_mat_ids),
175+
length_multiplier_(length_multiplier)
169176
{
170177
MaterialOverrides material_overrides;
171178
TemperatureOverrides temperature_overrides;
@@ -225,6 +232,28 @@ void DAGUniverse::init_dagmc()
225232
moab::ErrorCode rval = dagmc_instance_->load_file(filename_.c_str());
226233
MB_CHK_ERR_CONT(rval);
227234

235+
if (length_multiplier_ != 1.0) {
236+
moab::Range verts;
237+
rval =
238+
dagmc_instance_->moab_instance()->get_entities_by_dimension(0, 0, verts);
239+
MB_CHK_ERR_CONT(rval);
240+
241+
for (auto vert : verts) {
242+
std::array<double, 3> coord;
243+
rval =
244+
dagmc_instance_->moab_instance()->get_coords(&vert, 1, coord.data());
245+
MB_CHK_ERR_CONT(rval);
246+
247+
for (auto& c : coord) {
248+
c *= length_multiplier_;
249+
}
250+
251+
rval =
252+
dagmc_instance_->moab_instance()->set_coords(&vert, 1, coord.data());
253+
MB_CHK_ERR_CONT(rval);
254+
}
255+
}
256+
228257
// initialize acceleration data structures
229258
rval = dagmc_instance_->init_OBBTree();
230259
MB_CHK_ERR_CONT(rval);
@@ -579,6 +608,7 @@ void DAGUniverse::to_hdf5(hid_t universes_group) const
579608
group, "auto_geom_ids", static_cast<int>(adjust_geometry_ids_));
580609
write_attribute(
581610
group, "auto_mat_ids", static_cast<int>(adjust_material_ids_));
611+
write_attribute(group, "length_multiplier", length_multiplier_);
582612

583613
close_group(group);
584614
}
86.2 KB
Binary file not shown.

‎tests/unit_tests/dagmc/test_model.py‎

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import pytest
66
import openmc
77
import openmc.lib
8+
import h5py
89
from openmc.utility_funcs import change_directory
910

1011
pytestmark = pytest.mark.skipif(
@@ -258,6 +259,86 @@ def test_dagmc_xml_reject_region_override():
258259
openmc.DAGMCUniverse.from_xml_element(elem, mats)
259260

260261

262+
def test_dagmc_length_multiplier_xml_roundtrip():
263+
dagmc_u = openmc.DAGMCUniverse(filename="dagmc.h5m", universe_id=100, length_multiplier=0.1)
264+
root = ET.Element('geometry')
265+
dagmc_u.create_xml_subelement(root)
266+
dagmc_elem = root.find('dagmc_universe')
267+
assert dagmc_elem.get('length_multiplier') == '0.1'
268+
dagmc_u_roundtrip = openmc.DAGMCUniverse.from_xml_element(dagmc_elem)
269+
assert dagmc_u_roundtrip.length_multiplier == 0.1
270+
default_elem = ET.fromstring('<dagmc_universe id="100" filename="dagmc.h5m"/>')
271+
default_univ = openmc.DAGMCUniverse.from_xml_element(default_elem)
272+
assert default_univ.length_multiplier == 1.0
273+
274+
275+
def test_dagmc_length_multiplier_from_hdf5(run_in_tmpdir):
276+
# length_multiplier explicitly defined
277+
with h5py.File("test_dagmc.h5", "w") as f:
278+
g = f.create_group("geometry/universe 100")
279+
g.create_dataset("filename", data=np.bytes_("dagmc.h5m"))
280+
g.attrs["auto_geom_ids"] = np.int32(0)
281+
g.attrs["auto_mat_ids"] = np.int32(0)
282+
g.attrs["length_multiplier"] = 0.1
283+
284+
with h5py.File("test_dagmc.h5", "r") as f:
285+
u = openmc.DAGMCUniverse.from_hdf5(f["geometry/universe 100"])
286+
287+
assert u.id == 100
288+
assert u.filename == Path("dagmc.h5m")
289+
assert u.length_multiplier == pytest.approx(0.1)
290+
291+
# length_multiplier not defined - should default to 1.0
292+
with h5py.File("test_dagmc_default.h5", "w") as f:
293+
g = f.create_group("geometry/universe 101")
294+
g.create_dataset("filename", data=np.bytes_("dagmc.h5m"))
295+
g.attrs["auto_geom_ids"] = np.int32(0)
296+
g.attrs["auto_mat_ids"] = np.int32(0)
297+
298+
with h5py.File("test_dagmc_default.h5", "r") as f:
299+
u_default = openmc.DAGMCUniverse.from_hdf5(f["geometry/universe 101"])
300+
301+
assert u_default.id == 101
302+
assert u_default.filename == Path("dagmc.h5m")
303+
assert u_default.length_multiplier == 1.0
304+
305+
306+
def test_dagmc_length_multiplier_hdf5_roundtrip(request):
307+
p = Path(request.fspath).parent / "dagmc_sphere_r5.h5m"
308+
309+
daguniv = openmc.DAGMCUniverse(p, auto_geom_ids=True, length_multiplier=0.5)
310+
root = daguniv.bounded_universe()
311+
312+
mat = openmc.Material(name="test_mat")
313+
mat.add_nuclide("H1", 1.0)
314+
mat.set_density("g/cm3", 1.0)
315+
316+
settings = openmc.Settings()
317+
settings.batches = 100
318+
settings.inactive = 10
319+
settings.particles = 1000
320+
ll, ur = daguniv.bounding_box
321+
322+
model = openmc.Model()
323+
model.geometry = openmc.Geometry(root)
324+
model.materials = openmc.Materials([mat])
325+
model.settings = settings
326+
327+
with change_directory(tmpdir=True):
328+
try:
329+
model.init_lib()
330+
model.sync_dagmc_universes()
331+
summary = openmc.Summary("summary.h5")
332+
finally:
333+
model.finalize_lib()
334+
openmc.reset_auto_ids()
335+
336+
all_univs = summary.geometry.get_all_universes()
337+
u = all_univs[daguniv.id]
338+
assert isinstance(u, openmc.DAGMCUniverse)
339+
assert u.length_multiplier == pytest.approx(0.5)
340+
341+
261342
def _legacy_xml(cell_overrides):
262343
"""Helper to build a <dagmc_universe> with old-format <material_overrides>."""
263344
inner = ''.join(
@@ -363,3 +444,51 @@ def test_dagmc_xml_temperature_roundtrip():
363444
dag_univ_roundtrip = openmc.DAGMCUniverse.from_xml_element(dagmc_elem, mats)
364445
assert dag_univ_roundtrip.cells[7].fill.id == 1
365446
assert dag_univ_roundtrip.cells[7].temperature == pytest.approx(825.0)
447+
448+
449+
def test_dagmc_length_multiplier_volume_scaling(request):
450+
"""Stochastic volume of a DAGMC sphere should scale as length_multiplier^3.
451+
A DAGMC sphere with radius 5 cm (and no graveyard) is checked against the
452+
analytical volume (4/3 * pi * r^3) for length_multiplier values of 1.0 and 10.0.
453+
"""
454+
p = Path(request.fspath).parent / "dagmc_sphere_r5.h5m"
455+
n_samples = 1000000
456+
457+
def _compute_sphere_volume(length_multiplier):
458+
openmc.reset_auto_ids()
459+
daguniv = openmc.DAGMCUniverse(p, auto_geom_ids=True,
460+
length_multiplier=length_multiplier)
461+
root = daguniv.bounded_universe()
462+
463+
mat = openmc.Material(name="test_mat")
464+
mat.add_nuclide("H1", 1.0)
465+
mat.set_density("g/cm3", 1.0)
466+
467+
settings = openmc.Settings()
468+
settings.batches = 100
469+
settings.inactive = 10
470+
settings.particles = 1000
471+
ll, ur = daguniv.bounding_box
472+
vol_calc = openmc.VolumeCalculation([mat], n_samples, ll, ur)
473+
settings.volume_calculations = [vol_calc]
474+
model = openmc.Model()
475+
model.geometry = openmc.Geometry(root)
476+
model.materials = openmc.Materials([mat])
477+
model.settings = settings
478+
with change_directory(tmpdir=True):
479+
try:
480+
model.init_lib()
481+
model.sync_dagmc_universes()
482+
model.calculate_volumes()
483+
finally:
484+
model.finalize_lib()
485+
openmc.reset_auto_ids()
486+
return mat.volume
487+
488+
v1 = _compute_sphere_volume(length_multiplier=1.0)
489+
v10 = _compute_sphere_volume(length_multiplier=10.0)
490+
expected_v1 = 4.0 / 3.0 * np.pi * 5.0**3
491+
expected_v10 = expected_v1 * 10.0**3
492+
assert v1 == pytest.approx(expected_v1, rel=0.02)
493+
assert v10 == pytest.approx(expected_v10, rel=0.02)
494+
assert v10 / v1 == pytest.approx(10.0**3, rel=0.02)

0 commit comments

Comments
 (0)