From 1730c21aa5edf1a4f9acb00feb44f8a448bcd176 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 21 Jan 2025 20:47:58 -0600 Subject: [PATCH 01/17] add continue feature for depletion and test new code Co-authored-by: Edgar-21 <84034227+Edgar-21@users.noreply.github.com> Co-authored-by: Connor Moreno --- openmc/deplete/abc.py | 3 +- tests/unit_tests/test_deplete_continue.py | 53 +++++++++++++++++++++++ vendor/pugixml | 2 +- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 tests/unit_tests/test_deplete_continue.py diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index fc98239a561..de42073d2cb 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -613,7 +613,8 @@ def __init__( power_density: Optional[Union[float, Sequence[float]]] = None, source_rates: Optional[Union[float, Sequence[float]]] = None, timestep_units: str = 's', - solver: str = "cram48" + solver: str = "cram48", + continue_timesteps: bool = False, ): # Check number of stages previously used if operator.prev_res is not None: diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py new file mode 100644 index 00000000000..f65d91eca00 --- /dev/null +++ b/tests/unit_tests/test_deplete_continue.py @@ -0,0 +1,53 @@ +"""Unit tests for openmc.deplete continue run capability. + +These tests run in two steps: first a normal run and then a continue run based on the prev_results +""" + +import pytest + +import openmc.deplete + +from tests import dummy_operator + +# test that the continue timesteps works when the second integrate call contains all previous timesteps +@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) +def test_continue(run_in_tmpdir, scheme): + # set up the problem + + bundle = dummy_operator.SCHEMES[scheme] + + operator = dummy_operator.DummyOperator() + + # take first step + bundle.solver(operator, [0.75], 1.0).integrate() + + # restart + prev_res = openmc.deplete.Results( + operator.output_dir / "depletion_results.h5") + operator = dummy_operator.DummyOperator(prev_res) + + # if continue run happens, test passes + bundle.solver(operator, [0.75, 0.75], [1.0, 1.0], continue_timesteps = True).integrate() + +@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) +def test_mismatched_initial_steps(run_in_tmpdir, scheme): + """Test to ensure that a continue run with different initial steps is properly caught""" + + # set up the problem + + bundle = dummy_operator.SCHEMES[scheme] + + operator = dummy_operator.DummyOperator() + + # take first step + bundle.solver(operator, [0.75, 0.75], [1.0,1.0]).integrate() + + # restart + prev_res = openmc.deplete.Results( + operator.output_dir / "depletion_results.h5") + operator = dummy_operator.DummyOperator(prev_res) + + # continue run with different previous step should cause a ValueError + # note the first step matches but the second does not while the third is a new step + with pytest.raises(ValueError): + bundle.solver(operator, [0.75, 0.5, 0.75], [1.0, 2.0, 1.0], continue_timesteps = True).integrate() \ No newline at end of file diff --git a/vendor/pugixml b/vendor/pugixml index ee86beb30e4..41b6ff21c45 160000 --- a/vendor/pugixml +++ b/vendor/pugixml @@ -1 +1 @@ -Subproject commit ee86beb30e4973f5feffe3ce63bfa4fbadf72f38 +Subproject commit 41b6ff21c455865bb8ef67c5952b7f895b62bacc From 5ed5a59107349fefa2038ac555ceb2502a43e369 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 28 Jan 2025 13:24:56 -0600 Subject: [PATCH 02/17] added flags to SIIntegrator, convereted np.equal to np.array_equal, updated doc strings --- openmc/deplete/abc.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index de42073d2cb..66f8b4c706b 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -572,6 +572,18 @@ class Integrator(ABC): :attr:`solver`. .. versionadded:: 0.12 + continue_timesteps : bool, optional + Whether or not to treat the current solve as a continuation of a + previous simulation. Defaults to `False`. If `True`, the timesteps + provided to the `Integrator` must match exactly those that exist + in the `prev_results` passed to the `Opereator`. The `power`, + `power_density`, or `source_rates` must match as well. It + is the user's responsibility to make sure that the continue + solve uses the same method of specifying `power`, `power_density`, + or `source_rates`. + + .. versionadded:: 0.15.1 + Attributes ---------- operator : openmc.deplete.abc.TransportOperator @@ -933,6 +945,17 @@ class SIIntegrator(Integrator): :attr:`solver`. .. versionadded:: 0.12 + continue_timesteps : bool, optional + Whether or not to treat the current solve as a continuation of a + previous simulation. Defaults to `False`. If `True`, the timesteps + provided to the `Integrator` must match exactly those that exist + in the `prev_results` passed to the `Opereator`. The `power`, + `power_density`, or `source_rates` must match as well. It + is the user's responsibility to make sure that the continue + solve uses the same method of specifying `power`, `power_density`, + or `source_rates`. + + .. versionadded:: 0.15.1 Attributes ---------- @@ -974,13 +997,14 @@ def __init__( source_rates: Optional[Sequence[float]] = None, timestep_units: str = 's', n_steps: int = 10, - solver: str = "cram48" + solver: str = "cram48", + continue_timesteps: bool = False, ): check_type("n_steps", n_steps, Integral) check_greater_than("n_steps", n_steps, 0) super().__init__( operator, timesteps, power, power_density, source_rates, - timestep_units=timestep_units, solver=solver) + timestep_units=timestep_units, solver=solver, continue_timesteps=continue_timesteps) self.n_steps = n_steps def _get_bos_data_from_operator(self, step_index, step_power, n_bos): From 48f84780a81a5740383858d5c2d9b5af08658ba0 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 28 Jan 2025 13:26:37 -0600 Subject: [PATCH 03/17] implement get_source_rates --- openmc/deplete/results.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index f897a88422c..49054694215 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -17,6 +17,10 @@ __all__ = ["Results", "ResultsList"] +_SECONDS_PER_MINUTE = 60 +_SECONDS_PER_HOUR = 60*60 +_SECONDS_PER_DAY = 24*60*60 +_SECONDS_PER_JULIAN_YEAR = 365.25*24*60*60 def _get_time_as(seconds: float, units: str) -> float: """Converts the time in seconds to time in different units @@ -31,13 +35,13 @@ def _get_time_as(seconds: float, units: str) -> float: """ if units == "a": - return seconds / (60 * 60 * 24 * 365.25) # 365.25 due to the leap year + return seconds / _SECONDS_PER_JULIAN_YEAR if units == "d": - return seconds / (60 * 60 * 24) + return seconds / _SECONDS_PER_DAY elif units == "h": - return seconds / (60 * 60) + return seconds / _SECONDS_PER_HOUR elif units == "min": - return seconds / 60 + return seconds / _SECONDS_PER_MINUTE else: return seconds @@ -71,7 +75,6 @@ def __init__(self, filename='depletion_results.h5'): data.append(StepResult.from_hdf5(fh, i)) super().__init__(data) - @classmethod def from_hdf5(cls, filename: PathLike): """Load in depletion results from a previous file @@ -460,6 +463,26 @@ def get_times(self, time_units: str = "d") -> np.ndarray: return _get_time_as(times, time_units) + def get_source_rates(self) -> np.ndarray: + """ + .. versionadded:: 0.15.1 + + Returns + ------- + numpy.ndarray + 1-D vector of source rates at each point in the depletion simulation + with the units originally defined by the user. + + """ + + source_rates = np.fromiter( + (r.source_rate[0] for r in self), + dtype=self[0].source_rate.dtype, + count=len(self), + ) + + return source_rates + def get_step_where( self, time, time_units: str = "d", atol: float = 1e-6, rtol: float = 1e-3 ) -> int: From 136289cd7f84e9c7e0651bd6091bf7acdd9c1402 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 28 Jan 2025 13:28:03 -0600 Subject: [PATCH 04/17] split failure cases into two tests and added match param --- tests/unit_tests/test_deplete_continue.py | 41 +++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index f65d91eca00..f766a13f368 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -30,7 +30,7 @@ def test_continue(run_in_tmpdir, scheme): bundle.solver(operator, [0.75, 0.75], [1.0, 1.0], continue_timesteps = True).integrate() @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) -def test_mismatched_initial_steps(run_in_tmpdir, scheme): +def test_mismatched_initial_times(run_in_tmpdir, scheme): """Test to ensure that a continue run with different initial steps is properly caught""" # set up the problem @@ -40,14 +40,43 @@ def test_mismatched_initial_steps(run_in_tmpdir, scheme): operator = dummy_operator.DummyOperator() # take first step - bundle.solver(operator, [0.75, 0.75], [1.0,1.0]).integrate() + bundle.solver(operator, [0.75, 0.75], [1.0, 1.0]).integrate() # restart prev_res = openmc.deplete.Results( operator.output_dir / "depletion_results.h5") operator = dummy_operator.DummyOperator(prev_res) - # continue run with different previous step should cause a ValueError - # note the first step matches but the second does not while the third is a new step - with pytest.raises(ValueError): - bundle.solver(operator, [0.75, 0.5, 0.75], [1.0, 2.0, 1.0], continue_timesteps = True).integrate() \ No newline at end of file + with pytest.raises( + ValueError, + match="You are attempting to continue a run in which the previous results do not have the same initial steps as those provided to the Integrator. Please make sure you are using the correct timesteps, powers or power densities, and previous results file.", + ): + bundle.solver( + operator, [0.75, 0.5, 0.75], [1.0, 1.0, 1.0], continue_timesteps=True + ).integrate() + +@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) +def test_mismatched_initial_source_rates(run_in_tmpdir, scheme): + """Test to ensure that a continue run with different initial steps is properly caught""" + + # set up the problem + + bundle = dummy_operator.SCHEMES[scheme] + + operator = dummy_operator.DummyOperator() + + # take first step + bundle.solver(operator, [0.75, 0.75], [1.0, 1.0]).integrate() + + # restart + prev_res = openmc.deplete.Results( + operator.output_dir / "depletion_results.h5") + operator = dummy_operator.DummyOperator(prev_res) + + with pytest.raises( + ValueError, + match="You are attempting to continue a run in which the previous results do not have the same initial steps as those provided to the Integrator. Please make sure you are using the correct timesteps, powers or power densities, and previous results file.", + ): + bundle.solver( + operator, [0.75, 0.75, 0.75], [1.0, 2.0, 1.0], continue_timesteps=True + ).integrate() From 46ab8b473694f1913b77b45b292fae4c99fc5a27 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 28 Jan 2025 14:36:07 -0600 Subject: [PATCH 05/17] source_rate does not have index (scalar variable) Co-authored-by: Patrick Shriwise doc string and wrap match error message --- openmc/deplete/abc.py | 2 +- openmc/deplete/results.py | 3 +-- tests/unit_tests/test_deplete_continue.py | 14 +++++++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 66f8b4c706b..1e34ab6e91f 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -576,7 +576,7 @@ class Integrator(ABC): Whether or not to treat the current solve as a continuation of a previous simulation. Defaults to `False`. If `True`, the timesteps provided to the `Integrator` must match exactly those that exist - in the `prev_results` passed to the `Opereator`. The `power`, + in the `prev_results` passed to the `Operator`. The `power`, `power_density`, or `source_rates` must match as well. It is the user's responsibility to make sure that the continue solve uses the same method of specifying `power`, `power_density`, diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 49054694215..bffdb3544a2 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -474,9 +474,8 @@ def get_source_rates(self) -> np.ndarray: with the units originally defined by the user. """ - source_rates = np.fromiter( - (r.source_rate[0] for r in self), + (r.source_rate for r in self), dtype=self[0].source_rate.dtype, count=len(self), ) diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index f766a13f368..0b11ed11e74 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -12,6 +12,8 @@ # test that the continue timesteps works when the second integrate call contains all previous timesteps @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_continue(run_in_tmpdir, scheme): + """Test to ensure that a properly defined continue run works""" + # set up the problem bundle = dummy_operator.SCHEMES[scheme] @@ -27,7 +29,7 @@ def test_continue(run_in_tmpdir, scheme): operator = dummy_operator.DummyOperator(prev_res) # if continue run happens, test passes - bundle.solver(operator, [0.75, 0.75], [1.0, 1.0], continue_timesteps = True).integrate() + bundle.solver(operator, [0.75, 0.75], [1.0, 1.0], continue_timesteps=True).integrate() @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_mismatched_initial_times(run_in_tmpdir, scheme): @@ -49,7 +51,10 @@ def test_mismatched_initial_times(run_in_tmpdir, scheme): with pytest.raises( ValueError, - match="You are attempting to continue a run in which the previous results do not have the same initial steps as those provided to the Integrator. Please make sure you are using the correct timesteps, powers or power densities, and previous results file.", + match = "You are attempting to continue a run in which the previous " + "results do not have the same initial steps as those provided " + "to the Integrator. Please make sure you are using the correct " + "timesteps, powers or power densities, and previous results file.", ): bundle.solver( operator, [0.75, 0.5, 0.75], [1.0, 1.0, 1.0], continue_timesteps=True @@ -75,7 +80,10 @@ def test_mismatched_initial_source_rates(run_in_tmpdir, scheme): with pytest.raises( ValueError, - match="You are attempting to continue a run in which the previous results do not have the same initial steps as those provided to the Integrator. Please make sure you are using the correct timesteps, powers or power densities, and previous results file.", + match = "You are attempting to continue a run in which the previous " + "results do not have the same initial steps as those provided " + "to the Integrator. Please make sure you are using the correct " + "timesteps, powers or power densities, and previous results file.", ): bundle.solver( operator, [0.75, 0.75, 0.75], [1.0, 2.0, 1.0], continue_timesteps=True From 4fdf333f2b26649daffe133e07e23e3108da1d9f Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 18 Feb 2025 19:58:39 -0600 Subject: [PATCH 06/17] fix off by 1 --- openmc/deplete/results.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index bffdb3544a2..b7ec8fb8c57 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -477,8 +477,8 @@ def get_source_rates(self) -> np.ndarray: source_rates = np.fromiter( (r.source_rate for r in self), dtype=self[0].source_rate.dtype, - count=len(self), - ) + count=len(self)-1, + ) # Results duplicates the final source rate at the final simulation time return source_rates From 3f96f1a0f222cabbd079bd0c8d4fb8460774bf5e Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Wed, 19 Feb 2025 12:32:40 -0600 Subject: [PATCH 07/17] split checks, need to compute dts, need timestep_units in get_times call --- openmc/deplete/abc.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 1e34ab6e91f..94214c12b71 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -655,6 +655,30 @@ def __init__( # Normalize timesteps and source rates seconds, source_rates = _normalize_timesteps( timesteps, source_rates, timestep_units, operator) + + # validate existing depletion steps are consistent with those passed to operator + if continue_timesteps: + completed_times = operator.prev_res.get_times(time_units=timestep_units) + completed_timesteps = completed_times[1:] - completed_times[:-1] # convert absolute t to dt + completed_source_rates = operator.prev_res.get_source_rates() + num_previous_steps_run = len(completed_timesteps) + if (np.array_equal(completed_timesteps, timesteps[:num_previous_steps_run])): + seconds = seconds[num_previous_steps_run:] + else: + raise ValueError( + "You are attempting to continue a run in which the previous timesteps " + "do not have the same initial timesteps as those provided to the " + "Integrator. Please make sure you are using the correct timesteps." + ) + if(np.array_equal(completed_source_rates, np.asarray(source_rates)[:num_previous_steps_run] )): + source_rates = source_rates[num_previous_steps_run:] + else: + raise ValueError( + "You are attempting to continue a run in which the previous results " + "do not have the same initial source rates, powers, or power densities " + "as those provided to the Integrator. Please make sure you are using " + "the correct powers, power densities, or source rates and previous results file." + ) self.timesteps = np.asarray(seconds) self.source_rates = np.asarray(source_rates) From f3358e4034c7b2bf99f084dc3c5e782470ce03b3 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Mon, 24 Feb 2025 10:26:42 -0600 Subject: [PATCH 08/17] make sure that continue runs with no previous results do not break --- openmc/deplete/abc.py | 3 ++ tests/unit_tests/test_deplete_continue.py | 45 ++++++++++++++--------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 94214c12b71..6e9f5e0a60c 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -638,6 +638,9 @@ def __init__( "this uses {}".format( self.__class__.__name__, res.data.shape[0], self._num_stages)) + else: + # if the user specifies a continue run without a previous results, set the flag to False for them + continue_timesteps = False self.operator = operator self.chain = operator.chain diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index 0b11ed11e74..b1f857a1c52 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -20,16 +20,27 @@ def test_continue(run_in_tmpdir, scheme): operator = dummy_operator.DummyOperator() - # take first step - bundle.solver(operator, [0.75], 1.0).integrate() + # initial depletion + bundle.solver(operator, [1.0, 2.0], [1.0, 2.0]).integrate() - # restart - prev_res = openmc.deplete.Results( - operator.output_dir / "depletion_results.h5") + # set up continue run + prev_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") operator = dummy_operator.DummyOperator(prev_res) # if continue run happens, test passes - bundle.solver(operator, [0.75, 0.75], [1.0, 1.0], continue_timesteps=True).integrate() + bundle.solver(operator, [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], continue_timesteps=True).integrate() + +@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) +def test_continue_for_null_previous(run_in_tmpdir, scheme): + """Test to ensure that a continue run works even if there are no previous results""" + # set up the problem + + bundle = dummy_operator.SCHEMES[scheme] + + operator = dummy_operator.DummyOperator() + + # initial depletion + bundle.solver(operator, [1.0, 2.0], [1.0, 2.0], continue_timesteps=True).integrate() @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_mismatched_initial_times(run_in_tmpdir, scheme): @@ -45,21 +56,20 @@ def test_mismatched_initial_times(run_in_tmpdir, scheme): bundle.solver(operator, [0.75, 0.75], [1.0, 1.0]).integrate() # restart - prev_res = openmc.deplete.Results( - operator.output_dir / "depletion_results.h5") + prev_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") operator = dummy_operator.DummyOperator(prev_res) with pytest.raises( ValueError, - match = "You are attempting to continue a run in which the previous " - "results do not have the same initial steps as those provided " - "to the Integrator. Please make sure you are using the correct " - "timesteps, powers or power densities, and previous results file.", + match="You are attempting to continue a run in which the previous timesteps " + "do not have the same initial timesteps as those provided to the " + "Integrator. Please make sure you are using the correct timesteps.", ): bundle.solver( operator, [0.75, 0.5, 0.75], [1.0, 1.0, 1.0], continue_timesteps=True ).integrate() + @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_mismatched_initial_source_rates(run_in_tmpdir, scheme): """Test to ensure that a continue run with different initial steps is properly caught""" @@ -74,16 +84,15 @@ def test_mismatched_initial_source_rates(run_in_tmpdir, scheme): bundle.solver(operator, [0.75, 0.75], [1.0, 1.0]).integrate() # restart - prev_res = openmc.deplete.Results( - operator.output_dir / "depletion_results.h5") + prev_res = openmc.deplete.Results(operator.output_dir / "depletion_results.h5") operator = dummy_operator.DummyOperator(prev_res) with pytest.raises( ValueError, - match = "You are attempting to continue a run in which the previous " - "results do not have the same initial steps as those provided " - "to the Integrator. Please make sure you are using the correct " - "timesteps, powers or power densities, and previous results file.", + match="You are attempting to continue a run in which the previous results " + "do not have the same initial source rates, powers, or power densities " + "as those provided to the Integrator. Please make sure you are using " + "the correct powers, power densities, or source rates and previous results file.", ): bundle.solver( operator, [0.75, 0.75, 0.75], [1.0, 2.0, 1.0], continue_timesteps=True From 09df7c1289b9c5a2d21a7788294059f7d91b202b Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 4 Mar 2025 11:16:19 -0600 Subject: [PATCH 09/17] fix indent issue, add some description --- openmc/deplete/abc.py | 50 +++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 6e9f5e0a60c..a987b277fc4 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -659,29 +659,33 @@ def __init__( seconds, source_rates = _normalize_timesteps( timesteps, source_rates, timestep_units, operator) - # validate existing depletion steps are consistent with those passed to operator - if continue_timesteps: - completed_times = operator.prev_res.get_times(time_units=timestep_units) - completed_timesteps = completed_times[1:] - completed_times[:-1] # convert absolute t to dt - completed_source_rates = operator.prev_res.get_source_rates() - num_previous_steps_run = len(completed_timesteps) - if (np.array_equal(completed_timesteps, timesteps[:num_previous_steps_run])): - seconds = seconds[num_previous_steps_run:] - else: - raise ValueError( - "You are attempting to continue a run in which the previous timesteps " - "do not have the same initial timesteps as those provided to the " - "Integrator. Please make sure you are using the correct timesteps." - ) - if(np.array_equal(completed_source_rates, np.asarray(source_rates)[:num_previous_steps_run] )): - source_rates = source_rates[num_previous_steps_run:] - else: - raise ValueError( - "You are attempting to continue a run in which the previous results " - "do not have the same initial source rates, powers, or power densities " - "as those provided to the Integrator. Please make sure you are using " - "the correct powers, power densities, or source rates and previous results file." - ) + # validate existing depletion steps are consistent with those passed to operator + # if the timesteps retrieved from operator.prev_res.get_times match the first set + # of time steps provided to the continue run, and the same is true for the source_rates + # retrieved from operator.prev_res.get_source_rates(), then run a depletion simulations + # with only the new time steps and source rates provided + if continue_timesteps: + completed_times = operator.prev_res.get_times(time_units=timestep_units) + completed_timesteps = completed_times[1:] - completed_times[:-1] # convert absolute t to dt + completed_source_rates = operator.prev_res.get_source_rates() + num_previous_steps_run = len(completed_timesteps) + if (np.array_equal(completed_timesteps, timesteps[:num_previous_steps_run])): + seconds = seconds[num_previous_steps_run:] + else: + raise ValueError( + "You are attempting to continue a run in which the previous timesteps " + "do not have the same initial timesteps as those provided to the " + "Integrator. Please make sure you are using the correct timesteps." + ) + if(np.array_equal(completed_source_rates, np.asarray(source_rates)[:num_previous_steps_run] )): + source_rates = source_rates[num_previous_steps_run:] + else: + raise ValueError( + "You are attempting to continue a run in which the previous results " + "do not have the same initial source rates, powers, or power densities " + "as those provided to the Integrator. Please make sure you are using " + "the correct powers, power densities, or source rates and previous results file." + ) self.timesteps = np.asarray(seconds) self.source_rates = np.asarray(source_rates) From 020aff22e2e9c891a288ccd4325387121c5b37ca Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 4 Mar 2025 12:00:03 -0600 Subject: [PATCH 10/17] addressing some of Patrick's feedback --- openmc/deplete/abc.py | 13 +++++++------ openmc/deplete/results.py | 5 +++-- tests/unit_tests/test_deplete_continue.py | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index a987b277fc4..0646a3e906f 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -662,7 +662,7 @@ def __init__( # validate existing depletion steps are consistent with those passed to operator # if the timesteps retrieved from operator.prev_res.get_times match the first set # of time steps provided to the continue run, and the same is true for the source_rates - # retrieved from operator.prev_res.get_source_rates(), then run a depletion simulations + # retrieved from operator.prev_res.get_source_rates(), then run a depletion simulation # with only the new time steps and source rates provided if continue_timesteps: completed_times = operator.prev_res.get_times(time_units=timestep_units) @@ -978,13 +978,14 @@ class SIIntegrator(Integrator): .. versionadded:: 0.12 continue_timesteps : bool, optional Whether or not to treat the current solve as a continuation of a - previous simulation. Defaults to `False`. If `True`, the timesteps + previous simulation. Defaults to `False`. If `False`, all time + steps and source rates will be run in an append fashion and will run + after whatever time steps exist, if any. If `True`, the timesteps provided to the `Integrator` must match exactly those that exist in the `prev_results` passed to the `Opereator`. The `power`, - `power_density`, or `source_rates` must match as well. It - is the user's responsibility to make sure that the continue - solve uses the same method of specifying `power`, `power_density`, - or `source_rates`. + `power_density`, or `source_rates` must match as well. The + method of specifying `power`, `power_density`, or + `source_rates` should be the same as the initial run. .. versionadded:: 0.15.1 diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index b7ec8fb8c57..e8c3aa598b5 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -20,7 +20,7 @@ _SECONDS_PER_MINUTE = 60 _SECONDS_PER_HOUR = 60*60 _SECONDS_PER_DAY = 24*60*60 -_SECONDS_PER_JULIAN_YEAR = 365.25*24*60*60 +_SECONDS_PER_JULIAN_YEAR = 365.25*24*60*60 # 365.25 due to the leap year def _get_time_as(seconds: float, units: str) -> float: """Converts the time in seconds to time in different units @@ -474,11 +474,12 @@ def get_source_rates(self) -> np.ndarray: with the units originally defined by the user. """ + # Results duplicate the final source rate at the final simulation time source_rates = np.fromiter( (r.source_rate for r in self), dtype=self[0].source_rate.dtype, count=len(self)-1, - ) # Results duplicates the final source rate at the final simulation time + ) return source_rates diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index b1f857a1c52..ad1ce5bf85e 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -52,7 +52,7 @@ def test_mismatched_initial_times(run_in_tmpdir, scheme): operator = dummy_operator.DummyOperator() - # take first step + # perform initial steps bundle.solver(operator, [0.75, 0.75], [1.0, 1.0]).integrate() # restart @@ -80,7 +80,7 @@ def test_mismatched_initial_source_rates(run_in_tmpdir, scheme): operator = dummy_operator.DummyOperator() - # take first step + # perform initial steps bundle.solver(operator, [0.75, 0.75], [1.0, 1.0]).integrate() # restart From 00e575dab9729c8eacea62e4e22b9285b723aab0 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 4 Mar 2025 13:36:33 -0600 Subject: [PATCH 11/17] remove comment in favor of docstring --- tests/unit_tests/test_deplete_continue.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index ad1ce5bf85e..427c5657207 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -9,7 +9,6 @@ from tests import dummy_operator -# test that the continue timesteps works when the second integrate call contains all previous timesteps @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_continue(run_in_tmpdir, scheme): """Test to ensure that a properly defined continue run works""" From 220f1a9f7f54f5acd6927698a7941a8877447632 Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:06:24 -0600 Subject: [PATCH 12/17] imrove docstring Co-authored-by: Patrick Shriwise --- openmc/deplete/abc.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 0646a3e906f..74d3ab626a9 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -574,13 +574,13 @@ class Integrator(ABC): .. versionadded:: 0.12 continue_timesteps : bool, optional Whether or not to treat the current solve as a continuation of a - previous simulation. Defaults to `False`. If `True`, the timesteps - provided to the `Integrator` must match exactly those that exist - in the `prev_results` passed to the `Operator`. The `power`, - `power_density`, or `source_rates` must match as well. It - is the user's responsibility to make sure that the continue - solve uses the same method of specifying `power`, `power_density`, - or `source_rates`. + previous simulation. Defaults to `False`. When `False`, the depletion + steps provided are appended to any previous steps. If `True`, the + timesteps provided to the `Integrator` must exacly match any that + exist in the `prev_results` passed to the `Operator`. The `power`, + `power_density`, or `source_rates` must match as well. The + method of specifying `power`, `power_density`, or + `source_rates` should be the same as the initial run. .. versionadded:: 0.15.1 From 1915e6d39eb90a6f4d5e6cfc916a6e1f78fc6cd9 Mon Sep 17 00:00:00 2001 From: Lewis Gross Date: Tue, 4 Mar 2025 18:55:19 -0600 Subject: [PATCH 13/17] move completed_source_rates declaration after success of timestep check --- openmc/deplete/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 74d3ab626a9..e5dd1df922f 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -667,7 +667,6 @@ def __init__( if continue_timesteps: completed_times = operator.prev_res.get_times(time_units=timestep_units) completed_timesteps = completed_times[1:] - completed_times[:-1] # convert absolute t to dt - completed_source_rates = operator.prev_res.get_source_rates() num_previous_steps_run = len(completed_timesteps) if (np.array_equal(completed_timesteps, timesteps[:num_previous_steps_run])): seconds = seconds[num_previous_steps_run:] @@ -677,6 +676,7 @@ def __init__( "do not have the same initial timesteps as those provided to the " "Integrator. Please make sure you are using the correct timesteps." ) + completed_source_rates = operator.prev_res.get_source_rates() if(np.array_equal(completed_source_rates, np.asarray(source_rates)[:num_previous_steps_run] )): source_rates = source_rates[num_previous_steps_run:] else: From 384e99ca8b253ce73564ab062ad2f0ca6dd601c5 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 5 Mar 2025 10:43:55 -0600 Subject: [PATCH 14/17] Style changes to test file --- tests/unit_tests/test_deplete_continue.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index 427c5657207..ccb6cb1ca1d 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -1,6 +1,6 @@ """Unit tests for openmc.deplete continue run capability. -These tests run in two steps: first a normal run and then a continue run based on the prev_results +These tests run in two steps: first a normal run and then a continue run using the previous results """ import pytest @@ -9,14 +9,12 @@ from tests import dummy_operator + @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_continue(run_in_tmpdir, scheme): """Test to ensure that a properly defined continue run works""" - # set up the problem - bundle = dummy_operator.SCHEMES[scheme] - operator = dummy_operator.DummyOperator() # initial depletion @@ -29,26 +27,23 @@ def test_continue(run_in_tmpdir, scheme): # if continue run happens, test passes bundle.solver(operator, [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], continue_timesteps=True).integrate() + @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_continue_for_null_previous(run_in_tmpdir, scheme): """Test to ensure that a continue run works even if there are no previous results""" # set up the problem - bundle = dummy_operator.SCHEMES[scheme] - operator = dummy_operator.DummyOperator() # initial depletion bundle.solver(operator, [1.0, 2.0], [1.0, 2.0], continue_timesteps=True).integrate() + @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_mismatched_initial_times(run_in_tmpdir, scheme): """Test to ensure that a continue run with different initial steps is properly caught""" - # set up the problem - bundle = dummy_operator.SCHEMES[scheme] - operator = dummy_operator.DummyOperator() # perform initial steps @@ -72,11 +67,8 @@ def test_mismatched_initial_times(run_in_tmpdir, scheme): @pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) def test_mismatched_initial_source_rates(run_in_tmpdir, scheme): """Test to ensure that a continue run with different initial steps is properly caught""" - # set up the problem - bundle = dummy_operator.SCHEMES[scheme] - operator = dummy_operator.DummyOperator() # perform initial steps From 471e5b6d8f84bf2b2f84cee4d18fca000178c981 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 5 Mar 2025 16:06:15 -0600 Subject: [PATCH 15/17] Revert change to pugixml submodule --- vendor/pugixml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/pugixml b/vendor/pugixml index 41b6ff21c45..ee86beb30e4 160000 --- a/vendor/pugixml +++ b/vendor/pugixml @@ -1 +1 @@ -Subproject commit 41b6ff21c455865bb8ef67c5952b7f895b62bacc +Subproject commit ee86beb30e4973f5feffe3ce63bfa4fbadf72f38 From b27c9df019e6582d1be23199764b3c04a336c656 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 5 Mar 2025 16:08:28 -0600 Subject: [PATCH 16/17] Small stylistic fixes --- openmc/deplete/abc.py | 60 ++++++++++++++++++--------------------- openmc/deplete/results.py | 3 +- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index e5dd1df922f..ab087b98e59 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -1,6 +1,7 @@ """abc module. -This module contains Abstract Base Classes for implementing operator, integrator, depletion system solver, and operator helper classes +This module contains Abstract Base Classes for implementing operator, +integrator, depletion system solver, and operator helper classes """ from __future__ import annotations @@ -24,7 +25,8 @@ from openmc import Material from .stepresult import StepResult from .chain import Chain -from .results import Results +from .results import Results, _SECONDS_PER_MINUTE, _SECONDS_PER_HOUR, \ + _SECONDS_PER_DAY, _SECONDS_PER_JULIAN_YEAR from .pool import deplete from .reaction_rates import ReactionRates from .transfer_rates import TransferRates @@ -36,12 +38,6 @@ "Integrator", "SIIntegrator", "DepSystemSolver", "add_params"] -_SECONDS_PER_MINUTE = 60 -_SECONDS_PER_HOUR = 60*60 -_SECONDS_PER_DAY = 24*60*60 -_SECONDS_PER_JULIAN_YEAR = 365.25*24*60*60 - - def _normalize_timesteps( timesteps: Sequence[float] | Sequence[tuple[float, str]], source_rates: float | Sequence[float], @@ -578,8 +574,8 @@ class Integrator(ABC): steps provided are appended to any previous steps. If `True`, the timesteps provided to the `Integrator` must exacly match any that exist in the `prev_results` passed to the `Operator`. The `power`, - `power_density`, or `source_rates` must match as well. The - method of specifying `power`, `power_density`, or + `power_density`, or `source_rates` must match as well. The + method of specifying `power`, `power_density`, or `source_rates` should be the same as the initial run. .. versionadded:: 0.15.1 @@ -638,9 +634,8 @@ def __init__( "this uses {}".format( self.__class__.__name__, res.data.shape[0], self._num_stages)) - else: - # if the user specifies a continue run without a previous results, set the flag to False for them - continue_timesteps = False + elif continue_timesteps: + raise ValueError("Continuation run requires passing prev_results.") self.operator = operator self.chain = operator.chain @@ -659,33 +654,34 @@ def __init__( seconds, source_rates = _normalize_timesteps( timesteps, source_rates, timestep_units, operator) - # validate existing depletion steps are consistent with those passed to operator - # if the timesteps retrieved from operator.prev_res.get_times match the first set - # of time steps provided to the continue run, and the same is true for the source_rates - # retrieved from operator.prev_res.get_source_rates(), then run a depletion simulation - # with only the new time steps and source rates provided if continue_timesteps: - completed_times = operator.prev_res.get_times(time_units=timestep_units) - completed_timesteps = completed_times[1:] - completed_times[:-1] # convert absolute t to dt - num_previous_steps_run = len(completed_timesteps) - if (np.array_equal(completed_timesteps, timesteps[:num_previous_steps_run])): - seconds = seconds[num_previous_steps_run:] - else: + # Get timesteps and source rates from previous results + prev_times = operator.prev_res.get_times(timestep_units) + prev_source_rates = operator.prev_res.get_source_rates() + prev_timesteps = np.diff(prev_times) + + # Make sure parameters from the previous results are consistent with + # those passed to operator + num_prev = len(prev_timesteps) + if not np.array_equal(prev_timesteps, timesteps[:num_prev]): raise ValueError( "You are attempting to continue a run in which the previous timesteps " "do not have the same initial timesteps as those provided to the " "Integrator. Please make sure you are using the correct timesteps." ) - completed_source_rates = operator.prev_res.get_source_rates() - if(np.array_equal(completed_source_rates, np.asarray(source_rates)[:num_previous_steps_run] )): - source_rates = source_rates[num_previous_steps_run:] - else: + if not np.array_equal(prev_source_rates, source_rates[:num_prev]): raise ValueError( "You are attempting to continue a run in which the previous results " "do not have the same initial source rates, powers, or power densities " "as those provided to the Integrator. Please make sure you are using " - "the correct powers, power densities, or source rates and previous results file." - ) + "the correct powers, power densities, or source rates and previous " + "results file." + ) + + # Run with only the new time steps and source rates provided + seconds = seconds[num_prev:] + source_rates = source_rates[num_prev:] + self.timesteps = np.asarray(seconds) self.source_rates = np.asarray(source_rates) @@ -983,8 +979,8 @@ class SIIntegrator(Integrator): after whatever time steps exist, if any. If `True`, the timesteps provided to the `Integrator` must match exactly those that exist in the `prev_results` passed to the `Opereator`. The `power`, - `power_density`, or `source_rates` must match as well. The - method of specifying `power`, `power_density`, or + `power_density`, or `source_rates` must match as well. The + method of specifying `power`, `power_density`, or `source_rates` should be the same as the initial run. .. versionadded:: 0.15.1 diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index e8c3aa598b5..132ec572c76 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -20,7 +20,8 @@ _SECONDS_PER_MINUTE = 60 _SECONDS_PER_HOUR = 60*60 _SECONDS_PER_DAY = 24*60*60 -_SECONDS_PER_JULIAN_YEAR = 365.25*24*60*60 # 365.25 due to the leap year +_SECONDS_PER_JULIAN_YEAR = 365.25*24*60*60 # 365.25 due to the leap year + def _get_time_as(seconds: float, units: str) -> float: """Converts the time in seconds to time in different units From e2d0ad1f15ed874da57aaa93586ff0672752f386 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 5 Mar 2025 16:14:35 -0600 Subject: [PATCH 17/17] Simplify tests for continue run --- tests/unit_tests/test_deplete_continue.py | 30 ++++++----------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index ccb6cb1ca1d..53c4c56d29f 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -4,17 +4,15 @@ """ import pytest - import openmc.deplete from tests import dummy_operator -@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) -def test_continue(run_in_tmpdir, scheme): +def test_continue(run_in_tmpdir): """Test to ensure that a properly defined continue run works""" # set up the problem - bundle = dummy_operator.SCHEMES[scheme] + bundle = dummy_operator.SCHEMES['predictor'] operator = dummy_operator.DummyOperator() # initial depletion @@ -25,25 +23,14 @@ def test_continue(run_in_tmpdir, scheme): operator = dummy_operator.DummyOperator(prev_res) # if continue run happens, test passes - bundle.solver(operator, [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], continue_timesteps=True).integrate() - - -@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) -def test_continue_for_null_previous(run_in_tmpdir, scheme): - """Test to ensure that a continue run works even if there are no previous results""" - # set up the problem - bundle = dummy_operator.SCHEMES[scheme] - operator = dummy_operator.DummyOperator() - - # initial depletion - bundle.solver(operator, [1.0, 2.0], [1.0, 2.0], continue_timesteps=True).integrate() + bundle.solver(operator, [1.0, 2.0, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0], + continue_timesteps=True).integrate() -@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) -def test_mismatched_initial_times(run_in_tmpdir, scheme): +def test_mismatched_initial_times(run_in_tmpdir): """Test to ensure that a continue run with different initial steps is properly caught""" # set up the problem - bundle = dummy_operator.SCHEMES[scheme] + bundle = dummy_operator.SCHEMES['predictor'] operator = dummy_operator.DummyOperator() # perform initial steps @@ -64,11 +51,10 @@ def test_mismatched_initial_times(run_in_tmpdir, scheme): ).integrate() -@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) -def test_mismatched_initial_source_rates(run_in_tmpdir, scheme): +def test_mismatched_initial_source_rates(run_in_tmpdir): """Test to ensure that a continue run with different initial steps is properly caught""" # set up the problem - bundle = dummy_operator.SCHEMES[scheme] + bundle = dummy_operator.SCHEMES['predictor'] operator = dummy_operator.DummyOperator() # perform initial steps