From 1300665a07a1ea9a2eb48b465bbaf523e449558d Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 21 Jan 2025 20:47:58 -0600 Subject: [PATCH 01/13] add continue feature for depletion --- openmc/deplete/abc.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 784023f26ff..942739b3f93 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -556,7 +556,8 @@ def __init__( power_density: Optional[Union[float, Sequence[float]]] = None, source_rates: Optional[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: @@ -629,6 +630,27 @@ def __init__( else: raise ValueError(f"Invalid timestep unit '{unit}'") + # validate existing depletion steps are consistent with those passed to operator + if continue_timesteps: + completed_timesteps = operator.prev_res.get_times() + completed_source_rates = operator.prev_res.get_source_rates() + num_previous_steps_run = len(completed_timesteps) + for step in len(completed_timesteps): + if ( + timesteps[step] == completed_timesteps[step] + and source_rates[step] == completed_source_rates[step] + ): + continue + else: + raise ValueError( + "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." + ) + seconds = seconds[num_previous_steps_run:] + source_rates = source_rates[num_previous_steps_run:] + self.timesteps = np.asarray(seconds) self.source_rates = np.asarray(source_rates) From b5fe00b04045f9e517937baaec01e571aadb53f9 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Wed, 22 Jan 2025 15:28:49 -0600 Subject: [PATCH 02/13] test new code --- tests/unit_tests/test_deplete_continue.py | 53 +++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/unit_tests/test_deplete_continue.py 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 From ec1c39ff0af8491c07615ee0b5c8ed43f19e6a65 Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Fri, 24 Jan 2025 12:57:09 -0600 Subject: [PATCH 03/13] Update openmc/deplete/abc.py re-use variable and add missing range Co-authored-by: Edgar-21 <84034227+Edgar-21@users.noreply.github.com> --- 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 942739b3f93..5652a7bf1a7 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -635,7 +635,7 @@ def __init__( completed_timesteps = operator.prev_res.get_times() completed_source_rates = operator.prev_res.get_source_rates() num_previous_steps_run = len(completed_timesteps) - for step in len(completed_timesteps): + for step in range(num_previous_steps_run): if ( timesteps[step] == completed_timesteps[step] and source_rates[step] == completed_source_rates[step] From d849921ab7f9afea44bd6105ed009cb3ee1b179f Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:17:57 -0600 Subject: [PATCH 04/13] Update openmc/deplete/abc.py Co-authored-by: Connor Moreno --- openmc/deplete/abc.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 5652a7bf1a7..49518e761af 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -635,22 +635,19 @@ def __init__( completed_timesteps = operator.prev_res.get_times() completed_source_rates = operator.prev_res.get_source_rates() num_previous_steps_run = len(completed_timesteps) - for step in range(num_previous_steps_run): - if ( - timesteps[step] == completed_timesteps[step] - and source_rates[step] == completed_source_rates[step] - ): - continue - else: - raise ValueError( - "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." - ) - seconds = seconds[num_previous_steps_run:] - source_rates = source_rates[num_previous_steps_run:] - + if ( + np.equal(completed_timesteps, timesteps[: num_previous_steps_run]) and + np.equal(completed_source_rates, source_rates[: num_previous_steps_run]) + ): + seconds = seconds[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 steps as those provided to the " + "Integrator. Please make sure you are using the correct timesteps," + "powers or power densities, and previous results file." + ) self.timesteps = np.asarray(seconds) self.source_rates = np.asarray(source_rates) From e317aa32adb1f53a81ef5d1d4970ea83113ec7a8 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 28 Jan 2025 13:24:56 -0600 Subject: [PATCH 05/13] added flags to SIIntegrator, convereted np.equal to np.array_equal, updated doc strings --- openmc/deplete/abc.py | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 49518e761af..e0426457293 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -515,6 +515,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 @@ -636,8 +648,8 @@ def __init__( completed_source_rates = operator.prev_res.get_source_rates() num_previous_steps_run = len(completed_timesteps) if ( - np.equal(completed_timesteps, timesteps[: num_previous_steps_run]) and - np.equal(completed_source_rates, source_rates[: num_previous_steps_run]) + np.array_equal(completed_timesteps, timesteps[: num_previous_steps_run]) and + np.array_equal(completed_source_rates, source_rates[: num_previous_steps_run]) ): seconds = seconds[num_previous_steps_run:] source_rates = source_rates[num_previous_steps_run:] @@ -645,7 +657,7 @@ def __init__( raise ValueError( "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," + "Integrator. Please make sure you are using the correct timesteps, " "powers or power densities, and previous results file." ) self.timesteps = np.asarray(seconds) @@ -938,6 +950,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 ---------- @@ -979,13 +1002,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 83037c5c0c4802d3c27bd0b96045ac609314c02b Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 28 Jan 2025 13:26:37 -0600 Subject: [PATCH 06/13] 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 72ea52a59ab85dcd67eb8a188b0d006f08e61e2a Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 28 Jan 2025 13:28:03 -0600 Subject: [PATCH 07/13] 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 9167aa80e1df5971fb4b895238c948d91c204724 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Tue, 28 Jan 2025 14:36:07 -0600 Subject: [PATCH 08/13] source_rate does not have index (scalar variable) --- openmc/deplete/results.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 49054694215..c7a9afa5ebb 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -476,7 +476,7 @@ def get_source_rates(self) -> np.ndarray: """ 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), ) From af30b9a4c3695b2e7019ef30d7662eb3f1dd5d4b Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:09:43 -0600 Subject: [PATCH 09/13] typo Co-authored-by: Patrick Shriwise --- 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 e0426457293..71f56b28113 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -519,7 +519,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`, From a8190228aaac734046b63afd398ff6a601766cc8 Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:09:58 -0600 Subject: [PATCH 10/13] formatting Co-authored-by: Patrick Shriwise --- openmc/deplete/abc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 71f56b28113..ff602695bfe 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -648,8 +648,8 @@ def __init__( 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]) and - np.array_equal(completed_source_rates, source_rates[: num_previous_steps_run]) + np.array_equal(completed_timesteps, timesteps[:num_previous_steps_run]) and + np.array_equal(completed_source_rates, source_rates[:num_previous_steps_run]) ): seconds = seconds[num_previous_steps_run:] source_rates = source_rates[num_previous_steps_run:] From 6ea6f530e5896dcfb46ac9dbda50c2de308b2206 Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:10:13 -0600 Subject: [PATCH 11/13] formatting Co-authored-by: Patrick Shriwise --- openmc/deplete/results.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index c7a9afa5ebb..bffdb3544a2 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -474,7 +474,6 @@ def get_source_rates(self) -> np.ndarray: with the units originally defined by the user. """ - source_rates = np.fromiter( (r.source_rate for r in self), dtype=self[0].source_rate.dtype, From 17755441d53d532dbb8ec68c14676608de76f14c Mon Sep 17 00:00:00 2001 From: Lewis Gross <43077972+lewisgross1296@users.noreply.github.com> Date: Thu, 30 Jan 2025 11:10:58 -0600 Subject: [PATCH 12/13] formatting Co-authored-by: Patrick Shriwise --- tests/unit_tests/test_deplete_continue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index f766a13f368..3e20c5a360c 100644 --- a/tests/unit_tests/test_deplete_continue.py +++ b/tests/unit_tests/test_deplete_continue.py @@ -27,7 +27,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): From 8b70123c4a51b879a5c55ea6c9cb2b350f5d0ed5 Mon Sep 17 00:00:00 2001 From: lewisgross1296 Date: Thu, 30 Jan 2025 11:35:39 -0600 Subject: [PATCH 13/13] doc string and wrap match error message --- tests/unit_tests/test_deplete_continue.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_deplete_continue.py b/tests/unit_tests/test_deplete_continue.py index 3e20c5a360c..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] @@ -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