Skip to content

Commit dd6d31b

Browse files
gonukepshriwisepaulromano
authored
Add properties to settings w/ documentation, c++ loading of filename, and python round-trip test (#3808)
Co-authored-by: Patrick C Shriwise <pshriwise@gmail.com> Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
1 parent 4bda85f commit dd6d31b

8 files changed

Lines changed: 129 additions & 4 deletions

File tree

docs/source/io_formats/settings.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,18 @@ generator during generation of colors in plots.
555555

556556
*Default*: 1
557557

558+
.. _properties_file:
559+
560+
-----------------------------
561+
``<properties_file>`` Element
562+
-----------------------------
563+
564+
The ``properties_file`` element has no attributes and contains the path to a
565+
properties HDF5 file to load cell temperatures/densities and material
566+
densities.
567+
568+
*Default*: None
569+
558570
---------------------
559571
``<ptables>`` Element
560572
---------------------

include/openmc/settings.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ extern std::string path_sourcepoint; //!< path to a source file
115115
extern std::string path_statepoint; //!< path to a statepoint file
116116
extern std::string weight_windows_file; //!< Location of weight window file to
117117
//!< load on simulation initialization
118+
extern std::string properties_file; //!< Location of properties file to
119+
//!< load on simulation initialization
118120

119121
// This is required because the c_str() may not be the first thing in
120122
// std::string. Sometimes it is, but it seems libc++ may not be like that

openmc/lib/core.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -693,8 +693,8 @@ def __init__(self, model=None, cwd=None, **init_kwargs):
693693
self.model = model
694694

695695
# Determine MPI intercommunicator
696-
self.init_kwargs.setdefault('intracomm', comm)
697-
self.comm = self.init_kwargs['intracomm']
696+
self.comm = self.init_kwargs.get('intracomm') or comm
697+
self.init_kwargs['intracomm'] = self.comm
698698

699699
def __enter__(self):
700700
"""Initialize the OpenMC library in a temporary directory."""

openmc/settings.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@ class Settings:
183183
Initial seed for randomly generated plot colors.
184184
ptables : bool
185185
Determine whether probability tables are used.
186+
properties_file : PathLike
187+
Location of the properties file to load cell temperatures/densities
188+
and material densities
186189
random_ray : dict
187190
Options for configuring the random ray solver. Acceptable keys are:
188191
@@ -409,6 +412,7 @@ def __init__(self, **kwargs):
409412
self._atomic_relaxation = None
410413
self._plot_seed = None
411414
self._ptables = None
415+
self._properties_file = None
412416
self._uniform_source_sampling = None
413417
self._seed = None
414418
self._stride = None
@@ -1067,6 +1071,18 @@ def temperature(self, temperature: dict):
10671071

10681072
self._temperature = temperature
10691073

1074+
@property
1075+
def properties_file(self) -> PathLike | None:
1076+
return self._properties_file
1077+
1078+
@properties_file.setter
1079+
def properties_file(self, value: PathLike | None):
1080+
if value is None:
1081+
self._properties_file = None
1082+
else:
1083+
cv.check_type('properties file', value, PathLike)
1084+
self._properties_file = input_path(value)
1085+
10701086
@property
10711087
def trace(self) -> Iterable:
10721088
return self._trace
@@ -1772,6 +1788,12 @@ def _create_temperature_subelements(self, root):
17721788
else:
17731789
element.text = str(value)
17741790

1791+
def _create_properties_file_element(self, root):
1792+
if self.properties_file is not None:
1793+
element = ET.Element("properties_file")
1794+
element.text = str(self.properties_file)
1795+
root.append(element)
1796+
17751797
def _create_trace_subelement(self, root):
17761798
if self._trace is not None:
17771799
element = ET.SubElement(root, "trace")
@@ -2284,6 +2306,11 @@ def _temperature_from_xml_element(self, root):
22842306
if text is not None:
22852307
self.temperature['multipole'] = text in ('true', '1')
22862308

2309+
def _properties_file_from_xml_element(self, root):
2310+
text = get_text(root, 'properties_file')
2311+
if text is not None:
2312+
self.properties_file = text
2313+
22872314
def _trace_from_xml_element(self, root):
22882315
text = get_elem_list(root, "trace", int)
22892316
if text is not None:
@@ -2522,6 +2549,7 @@ def to_xml_element(self, mesh_memo=None):
25222549
self._create_ifp_n_generation_subelement(element)
25232550
self._create_tabular_legendre_subelements(element)
25242551
self._create_temperature_subelements(element)
2552+
self._create_properties_file_element(element)
25252553
self._create_trace_subelement(element)
25262554
self._create_track_subelement(element)
25272555
self._create_ufs_mesh_subelement(element, mesh_memo)
@@ -2639,6 +2667,7 @@ def from_xml_element(cls, elem, meshes=None):
26392667
settings._ifp_n_generation_from_xml_element(elem)
26402668
settings._tabular_legendre_from_xml_element(elem)
26412669
settings._temperature_from_xml_element(elem)
2670+
settings._properties_file_from_xml_element(elem)
26422671
settings._trace_from_xml_element(elem)
26432672
settings._track_from_xml_element(elem)
26442673
settings._ufs_mesh_from_xml_element(elem, meshes)

src/finalize.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ int openmc_finalize()
140140
settings::temperature_multipole = false;
141141
settings::temperature_range = {0.0, 0.0};
142142
settings::temperature_tolerance = 10.0;
143+
settings::properties_file.clear();
143144
settings::trigger_on = false;
144145
settings::trigger_predict = false;
145146
settings::trigger_batch_interval = 1;

src/initialize.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ int openmc_init(int argc, char* argv[], const void* intracomm)
118118
if (!read_model_xml())
119119
read_separate_xml_files();
120120

121+
if (!settings::properties_file.empty()) {
122+
openmc_properties_import(settings::properties_file.c_str());
123+
}
124+
121125
// Reset locale to previous state
122126
if (std::setlocale(LC_ALL, prev_locale.c_str()) == NULL) {
123127
fatal_error("Cannot reset locale.");

src/settings.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ std::string path_sourcepoint;
9797
std::string path_statepoint;
9898
const char* path_statepoint_c {path_statepoint.c_str()};
9999
std::string weight_windows_file;
100+
std::string properties_file;
100101

101102
int32_t n_inactive {0};
102103
int32_t max_lost_particles {10};
@@ -751,6 +752,14 @@ void read_settings_xml(pugi::xml_node root)
751752
}
752753
}
753754

755+
// read properties from file
756+
if (check_for_node(root, "properties_file")) {
757+
properties_file = get_node_value(root, "properties_file");
758+
if (!file_exists(properties_file)) {
759+
fatal_error(fmt::format("File '{}' does not exist.", properties_file));
760+
}
761+
}
762+
754763
// Particle trace
755764
if (check_for_node(root, "trace")) {
756765
auto temp = get_node_array<int64_t>(root, "trace");

tests/unit_tests/test_settings.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1+
from pathlib import Path
2+
3+
import h5py
4+
import pytest
5+
16
import openmc
7+
import openmc.lib
28
import openmc.stats
39

410

511
def test_export_to_xml(run_in_tmpdir):
12+
13+
tmp_properties_file = 'properties_test.h5'
14+
615
s = openmc.Settings(run_mode='fixed source', batches=1000, seed=17)
716
s.generations_per_batch = 10
817
s.inactive = 100
@@ -22,7 +31,7 @@ def test_export_to_xml(run_in_tmpdir):
2231
s.surf_source_read = {'path': 'surface_source_1.h5'}
2332
s.surf_source_write = {'surface_ids': [2], 'max_particles': 200}
2433
s.surface_grazing_ratio = 0.7
25-
s.surface_grazing_cutoff = 0.1
34+
s.surface_grazing_cutoff = 0.1
2635
s.confidence_intervals = True
2736
s.ptables = True
2837
s.plot_seed = 100
@@ -45,6 +54,7 @@ def test_export_to_xml(run_in_tmpdir):
4554
s.tabular_legendre = {'enable': True, 'num_points': 50}
4655
s.temperature = {'default': 293.6, 'method': 'interpolation',
4756
'multipole': True, 'range': (200., 1000.)}
57+
s.properties_file = tmp_properties_file
4858
s.trace = (10, 1, 20)
4959
s.track = [(1, 1, 1), (2, 1, 1)]
5060
s.ufs_mesh = mesh
@@ -88,6 +98,7 @@ def test_export_to_xml(run_in_tmpdir):
8898
# Make sure exporting XML works
8999
s.export_to_xml()
90100

101+
91102
# Generate settings from XML
92103
s = openmc.Settings.from_xml()
93104
assert s.run_mode == 'fixed source'
@@ -111,7 +122,7 @@ def test_export_to_xml(run_in_tmpdir):
111122
assert s.surf_source_read['path'].name == 'surface_source_1.h5'
112123
assert s.surf_source_write == {'surface_ids': [2], 'max_particles': 200}
113124
assert s.surface_grazing_ratio == 0.7
114-
assert s.surface_grazing_cutoff == 0.1
125+
assert s.surface_grazing_cutoff == 0.1
115126
assert s.confidence_intervals
116127
assert s.ptables
117128
assert s.plot_seed == 100
@@ -134,6 +145,7 @@ def test_export_to_xml(run_in_tmpdir):
134145
assert s.tabular_legendre == {'enable': True, 'num_points': 50}
135146
assert s.temperature == {'default': 293.6, 'method': 'interpolation',
136147
'multipole': True, 'range': [200., 1000.]}
148+
assert s.properties_file == Path(tmp_properties_file)
137149
assert s.trace == [10, 1, 20]
138150
assert s.track == [(1, 1, 1), (2, 1, 1)]
139151
assert isinstance(s.ufs_mesh, openmc.RegularMesh)
@@ -178,3 +190,59 @@ def test_export_to_xml(run_in_tmpdir):
178190
assert s.max_secondaries == 1_000_000
179191
assert s.source_rejection_fraction == 0.01
180192
assert s.free_gas_threshold == 800.0
193+
194+
195+
def test_properties_file_load(tmp_path, mpi_intracomm):
196+
model = openmc.examples.pwr_assembly()
197+
198+
# Session 1: export a structurally valid properties file via the C++ API,
199+
# then collect the cell/material structure so we can patch it with h5py.
200+
cell_instances = {} # {cell_id: n_instances} — material cells only
201+
mat_densities = {} # {mat_id: original atom/b-cm density}
202+
203+
props_path = tmp_path / 'properties.h5'
204+
with openmc.lib.TemporarySession(model, intracomm=mpi_intracomm):
205+
openmc.lib.export_properties(str(props_path))
206+
for cell_id, cell in openmc.lib.cells.items():
207+
try:
208+
cell.fill # raises NotImplementedError for non-material cells
209+
cell_instances[cell_id] = cell.num_instances
210+
except NotImplementedError:
211+
pass
212+
for mat_id, mat in openmc.lib.materials.items():
213+
mat_densities[mat_id] = mat.get_density('atom/b-cm')
214+
215+
assert any(n > 1 for n in cell_instances.values())
216+
217+
# Patch the exported properties file overwriting temperatures
218+
# with per-instance values and scale material atom densities.
219+
density_factor = 0.75
220+
with h5py.File(props_path, 'r+') as f:
221+
cells_grp = f['geometry/cells']
222+
for cell_id, n in cell_instances.items():
223+
cell_grp = cells_grp[f'cell {cell_id}']
224+
del cell_grp['temperature']
225+
cell_grp.create_dataset(
226+
'temperature', data=[500.0 + 5.0 * i for i in range(n)]
227+
)
228+
229+
for mat_id, orig_density in mat_densities.items():
230+
f['materials'][f'material {mat_id}'].attrs['atom_density'] = \
231+
orig_density * density_factor
232+
233+
# now apply the newly patched properties file using the settings
234+
# and load the model again, checking that the new temperature and
235+
# density values match those in the new file
236+
model.settings.properties_file = props_path
237+
238+
with openmc.lib.TemporarySession(model, intracomm=mpi_intracomm):
239+
for cell_id, n in cell_instances.items():
240+
cell = openmc.lib.cells[cell_id]
241+
for i in range(n):
242+
assert cell.get_temperature(i) == pytest.approx(500.0 + 5.0 * i)
243+
244+
for mat_id, orig_density in mat_densities.items():
245+
mat = openmc.lib.materials[mat_id]
246+
assert mat.get_density('atom/b-cm') == pytest.approx(
247+
orig_density * density_factor, rel=1e-5
248+
)

0 commit comments

Comments
 (0)