diff --git a/openmc/executor.py b/openmc/executor.py index 9faf18b332..76d100d32a 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -1,6 +1,7 @@ from collections.abc import Iterable -import subprocess from numbers import Integral +import re +import subprocess import openmc @@ -10,6 +11,10 @@ def _run(args, output, cwd): p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + # Compile a regex object to identify statepoint writes + sp_rxprog = re.compile(r'Creating state point (.+)\.\.\.') + last_statepoint = None + # Capture and re-print OpenMC output in real-time lines = [] while True: @@ -18,6 +23,11 @@ def _run(args, output, cwd): if not line and p.poll() is not None: break + # If a statepoint was written, capture its filename + sp_match = sp_rxprog.search(line) + if sp_match: + last_statepoint = sp_match.group(1) + lines.append(line) if output: # If user requested output, print to screen @@ -27,6 +37,7 @@ def _run(args, output, cwd): if p.returncode != 0: raise subprocess.CalledProcessError(p.returncode, ' '.join(args), ''.join(lines)) + return last_statepoint def plot_geometry(output=True, openmc_exec='openmc', cwd='.'): @@ -184,6 +195,11 @@ def run(particles=None, threads=None, geometry_debug=False, event_based : bool, optional Turns on event-based parallelism, instead of default history-based + Returns + ------- + str + Name of the last written statepoint file, or None + Raises ------ subprocess.CalledProcessError @@ -213,4 +229,4 @@ def run(particles=None, threads=None, geometry_debug=False, if mpi_args is not None: args = mpi_args + args - _run(args, output, cwd) + return _run(args, output, cwd) diff --git a/openmc/model/model.py b/openmc/model/model.py index b5c190716b..08ba300dd1 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -41,6 +41,8 @@ class Model: Tallies information plots : openmc.Plots Plot information + statepoint : openmc.Statepoint + The last statepoint filename written when the model is run """ @@ -51,6 +53,7 @@ class Model: self.settings = openmc.Settings() self.tallies = openmc.Tallies() self.plots = openmc.Plots() + self.statepoint = None if geometry is not None: self.geometry = geometry @@ -197,7 +200,8 @@ class Model: self.plots.export_to_xml(d) def run(self, **kwargs): - """Creates the XML files, runs OpenMC, and returns k-effective + """Creates the XML files, runs OpenMC, and returns k-effective. + The last statepoint filename is available in model.statepoint Parameters ---------- @@ -210,14 +214,13 @@ class Model: Combined estimator of k-effective from the statepoint """ + self.export_to_xml() - openmc.run(**kwargs) + self.statepoint = openmc.run(**kwargs) - n = self.settings.batches - if self.settings.statepoint is not None: - if 'batches' in self.settings.statepoint: - n = self.settings.statepoint['batches'][-1] - - with openmc.StatePoint('statepoint.{}.h5'.format(n)) as sp: - return sp.k_combined + keff = None + if self.statepoint: + with openmc.StatePoint(self.statepoint) as sp: + keff = sp.k_combined + return keff diff --git a/tests/regression_tests/trigger_statepoint_restart/__init__.py b/tests/regression_tests/trigger_statepoint_restart/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat b/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat new file mode 100644 index 0000000000..decc87b0c5 --- /dev/null +++ b/tests/regression_tests/trigger_statepoint_restart/inputs_true.dat @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + eigenvalue + 200 + 10 + 5 + + std_dev + 0.002 + + + true + 1000 + 1 + + + + + + flux + + diff --git a/tests/regression_tests/trigger_statepoint_restart/results_true.dat b/tests/regression_tests/trigger_statepoint_restart/results_true.dat new file mode 100644 index 0000000000..84363f8696 --- /dev/null +++ b/tests/regression_tests/trigger_statepoint_restart/results_true.dat @@ -0,0 +1,5 @@ +k-combined: +2.909598E-01 6.161987E-03 +tally 1: +3.852896E+01 +2.976246E+02 diff --git a/tests/regression_tests/trigger_statepoint_restart/test.py b/tests/regression_tests/trigger_statepoint_restart/test.py new file mode 100644 index 0000000000..66416297b1 --- /dev/null +++ b/tests/regression_tests/trigger_statepoint_restart/test.py @@ -0,0 +1,120 @@ +import glob +import os + +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness +from tests.regression_tests import config + + +@pytest.fixture +def model(): + + # Materials + materials = openmc.Materials() + mat = openmc.Material() + mat.set_density('g/cm3', 4.5) + mat.add_nuclide('U235', 1.0) + materials.append(mat) + + # Geometry + sph = openmc.Sphere(r=10.0, boundary_type='vacuum') + cell = openmc.Cell(fill=mat, region=-sph) + geometry = openmc.Geometry([cell]) + + # Settings + settings = openmc.Settings() + settings.run_mode = 'eigenvalue' + settings.batches = 10 + settings.inactive = 5 + settings.particles = 200 + settings.keff_trigger = {'type': 'std_dev', 'threshold': 0.002} + settings.trigger_max_batches = 1000 + settings.trigger_batch_interval = 1 + settings.trigger_active = True + + # Tallies + tallies = openmc.Tallies() + t = openmc.Tally() + t.scores = ['flux'] + tallies.append(t) + + # Plots (none) + plots = openmc.Plots() + + # Put it all together + model = openmc.model.Model(materials=materials, + geometry=geometry, + settings=settings, + tallies=tallies, + plots=plots) + return model + +class TriggerStatepointRestartTestHarness(PyAPITestHarness): + def __init__(self, statepoint, model=None): + super().__init__(statepoint, model) + self._restart_sp = None + self._final_sp = None + + def _test_output_created(self): + """Make sure statepoint files have been created.""" + spfiles = glob.glob(self._sp_name) + assert len(spfiles) == 2, \ + 'Two statepoint files should have been created' + if self._final_sp: + # Second restart run + assert spfiles[1] == self._final_sp, \ + 'Final statepoint names were different' + else: + # First non-restart run + self._restart_sp = spfiles[0] + self._final_sp = spfiles[1] + + def execute_test(self): + """ + Perform initial and restart runs using the model.run method, + Check all inputs and outputs which should be the same as those + generated using the normal PyAPITestHarness update methods. + """ + try: + args = {'openmc_exec': config['exe'], 'event_based': config['event']} + if config['mpi']: + args['mpi_args'] = [config['mpiexec'], '-n', config['mpi_np']] + # First non-restart run + k_combined_1 = self._model.run(**args) + sp_batchno_1 = 0 + with openmc.StatePoint(self._model.statepoint) as sp: + sp_batchno_1 = sp.current_batch + assert sp_batchno_1 > 10 + self._write_inputs(self._get_inputs()) + self._compare_inputs() + self._test_output_created() + self._write_results(self._get_results()) + self._compare_results() + # Second restart run + restart_spfile = glob.glob(os.path.join(os.getcwd(), self._restart_sp)) + assert len(restart_spfile) == 1 + args['restart_file'] = restart_spfile[0] + k_combined_2 = self._model.run(**args) + sp_batchno_2 = 0 + with openmc.StatePoint(self._model.statepoint) as sp: + sp_batchno_2 = sp.current_batch + assert sp_batchno_2 > 10 + self._write_inputs(self._get_inputs()) + self._compare_inputs() + self._test_output_created() + self._write_results(self._get_results()) + self._compare_results() + assert str(k_combined_1) == str(k_combined_2), \ + 'Different final k_combined after restart' + assert sp_batchno_1 == sp_batchno_2, \ + 'Different final batch number after restart' + finally: + self._cleanup() + +def test_trigger_statepoint_restart(model): + # Assuming we converge within 1000 batches, the statepoint filename + # should include the batch number padded by at least one '0'. + harness = TriggerStatepointRestartTestHarness('statepoint.0*.h5', model) + harness.main()