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()