From cd444510cf701f87c5015b2c1124162c1f0a751a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 10 Jul 2019 13:09:38 -0500 Subject: [PATCH 01/32] Demonstrate Integrator class with cecm New abstract class openmc.deplete.integrate.Integrator and concrete CECMIntegrator for performing depletion analysis. Concrete classes only have to implement the __call__ method responsible for performing the time integration across a time interval. The abstract base class is responsible for iterating through all time steps, collecting and writing results to file, and executing intermediate transport solutions. --- openmc/deplete/integrator/__init__.py | 1 + openmc/deplete/integrator/abc.py | 152 ++++++++++++++++++++++++++ openmc/deplete/integrator/cecm.py | 132 +++++++--------------- 3 files changed, 194 insertions(+), 91 deletions(-) create mode 100644 openmc/deplete/integrator/abc.py diff --git a/openmc/deplete/integrator/__init__.py b/openmc/deplete/integrator/__init__.py index db906b3c1..961afefb6 100644 --- a/openmc/deplete/integrator/__init__.py +++ b/openmc/deplete/integrator/__init__.py @@ -5,6 +5,7 @@ Integrator The integrator subcomponents. """ +from .abc import Integrator from .cf4 import * from .cecm import * from .celi import * diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py new file mode 100644 index 000000000..d6f318fa4 --- /dev/null +++ b/openmc/deplete/integrator/abc.py @@ -0,0 +1,152 @@ +from copy import deepcopy +from abc import ABC, abstractmethod +from collections.abc import Iterable + +from openmc.deplete import Results + + +class Integrator(ABC): + """Abstract class for solving the time-integration for depletion + + """ + + def __init__(self, operator, timesteps, power=None, power_density=None): + """ + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + """ + self.operator = operator + self.chain = operator.chain + if not isinstance(timesteps, Iterable): + self.timesteps = [timesteps] + else: + self.timesteps = timesteps + if power is None: + if power_density is None: + raise ValueError("Either power or power density must be set") + if not isinstance(power_density, Iterable): + power = power_density * operator.heavy_metal + else: + power = [p * operator.heavy_metal for p in power_density] + + if not isinstance(power, Iterable): + # TODO Maybe use itertools.zip_longest? + # Ensure that power is single value if that is the case + power = [power] * len(self.timesteps) + + self.power = power + + @abstractmethod + def __call__(self, conc, rates, dt, power): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + + def __iter__(self): + for dt, p in zip(self.timesteps, self.power): + yield dt, p + + def __len__(self): + return len(self.timesteps) + + def _get_bos_data(self, step_index, step_power, prev_conc): + if step_index > 0 or self.operator.prev_res is None: + x = deepcopy(prev_conc) + res = self.operator(x, step_power) + else: + # Get previous concentration + x = self.operator.prev_res[-1].data[0] + + # Get reaction rates and keff + res = self.operator.prev_res[-1] + res.rates = res.rates[0] + res.k = res.k[0] + + # Scale rates by ratio of powers + res.rates *= step_power / res.power[0] + return x, res + + def _get_start_data(self): + if self.operator.prev_res is None: + return 0.0, 0 + return self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) + + def integrate_all(self): + """Perform the entire depletion process across all steps""" + with self.operator as conc: + t, i_start = self._get_start_data() + + for i, (dt, p) in enumerate(self): + conc, res = self._get_bos_data(i, p, conc) + proc_time, conc_list, res_list = self(conc, res.rates, dt, p) + + # Insert BOS concentration, transport results + conc_list.insert(0, conc) + res_list.insert(0, res) + + # Remove actual EOS concentration for next step + conc = conc_list.pop() + + self._save_results( + conc_list, res_list, [t, t + dt], p, i_start + i, + proc_time) + + t += dt + + # Final simulation + res_list = [self.operator(conc, p)] + self._save_results( + [conc], res_list, [t, t], p, i_start + len(self)) + + def _save_results(self, conc_list, results_list, time_list, power, + index, proc_time=None): + """Save the results at the end of of one step + + Abstracted to support the predictor's unique save location + """ + Results.save( + self.operator, conc_list, results_list, time_list, + power, index, proc_time) + + @classmethod + def integrate(cls, operator, timesteps, power=None, power_density=None): + """High-level interface for depleting with this integrator""" + return cls(operator, timesteps, power, power_density).integrate_all() diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 758302b02..031e4ce7b 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -1,13 +1,12 @@ """The CE/CM integrator.""" -import copy -from collections.abc import Iterable +from textwrap import dedent +from .abc import Integrator from .cram import timed_deplete -from ..results import Results -def cecm(operator, timesteps, power=None, power_density=None, print_out=True): +class CECMIntegrator(Integrator): r"""Deplete using the CE/CM algorithm. Implements the second order `CE/CM predictor-corrector algorithm @@ -24,100 +23,51 @@ def cecm(operator, timesteps, power=None, power_density=None, print_out=True): A_c &= A(y_m, t_n + h/2) \\ y_{n+1} &= \text{expm}(A_c h) y_n \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - if not isinstance(power, Iterable): - power = [power]*len(timesteps) + def __call__(self, conc, rates, dt, power): + """Integrate using CE/CM - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] - chain = operator.chain + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from transport simulations + """ + # deplete across first half of inteval + time0, x_middle = timed_deplete(self.chain, conc, rates, dt / 2) + res_middle = self.operator(x_middle, power) - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] + # deplete across entire interval with BOS concentrations, + # MOS reaction rates + time1, x_end = timed_deplete(self.chain, conc, res_middle.rates, dt) - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + return time0 + time1, [x_middle, x_end], [res_middle] - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] +def cecm(operator, timesteps, power=None, power_density=None, print_out=False): + # TODO Remove print_out since depletion timings are stored + return CECMIntegrator( + operator, timesteps, power, power_density).integrate_all() - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] - # Deplete for first half of timestep - proc_time, x_middle = timed_deplete( - chain, x[0], op_results[0].rates, dt/2, print_out) - - # Get middle-of-timestep reaction rates - x.append(x_middle) - op_results.append(operator(x_middle, p)) - - # Deplete for full timestep using beginning-of-step materials - # and middle-of-timestep reaction rates - pt_end, x_end = timed_deplete( - chain, x[0], op_results[1].rates, dt, print_out) - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t + dt], p, i_res + i, - proc_time + pt_end) - - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) +try: + cecm.__doc__ = ( + dedent(CECMIntegrator.__doc__) + dedent(Integrator.__init__.__doc__)) +except AttributeError: + pass From f432be0c055f889426ec3ab01c7316cd8d491e1d Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 16 Jul 2019 11:04:50 -0500 Subject: [PATCH 02/32] Remove cecm function for deplete.CECMIntegrator The cecm function has been removed from the python api. In order to use the cecm integration scheme, the following class based approach is now expected: >>> from openmc.deplete import CECMIntegrator >>> cecm = CECMIntegrator(operator, timesteps, power) >>> cecm.integrate() if the integrator is not needed, this above expression can be one-lined with >>> CECMIntegrator(operator, timesteps, power).integrate() Unit tests have been updated to no longer use the cecm function, and cecm has been removed from documentation. The CECMIntegrator has been added to documentation. --- docs/source/pythonapi/deplete.rst | 8 +++++++- openmc/deplete/integrator/abc.py | 10 ++-------- openmc/deplete/integrator/cecm.py | 13 ------------- openmc/deplete/operator.py | 2 +- tests/unit_tests/test_deplete_cecm.py | 4 +++- tests/unit_tests/test_deplete_restart.py | 13 +++++++++---- 6 files changed, 22 insertions(+), 28 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 532e06655..32f953ed4 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -17,7 +17,6 @@ transport-depletion coupling algorithms `_. :template: myfunction.rst integrator.predictor - integrator.cecm integrator.celi integrator.leqi integrator.cf4 @@ -25,6 +24,13 @@ transport-depletion coupling algorithms `_. integrator.si_celi integrator.si_leqi +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclassinherit.rst + + integrator.CECMIntegrator + Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index d6f318fa4..51877e5e6 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -81,8 +81,7 @@ class Integrator(ABC): """ def __iter__(self): - for dt, p in zip(self.timesteps, self.power): - yield dt, p + return zip(self.timesteps, self.power) def __len__(self): return len(self.timesteps) @@ -109,7 +108,7 @@ class Integrator(ABC): return 0.0, 0 return self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - def integrate_all(self): + def integrate(self): """Perform the entire depletion process across all steps""" with self.operator as conc: t, i_start = self._get_start_data() @@ -145,8 +144,3 @@ class Integrator(ABC): Results.save( self.operator, conc_list, results_list, time_list, power, index, proc_time) - - @classmethod - def integrate(cls, operator, timesteps, power=None, power_density=None): - """High-level interface for depleting with this integrator""" - return cls(operator, timesteps, power, power_density).integrate_all() diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 031e4ce7b..1bf6e5809 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -58,16 +58,3 @@ class CECMIntegrator(Integrator): time1, x_end = timed_deplete(self.chain, conc, res_middle.rates, dt) return time0 + time1, [x_middle, x_end], [res_middle] - - -def cecm(operator, timesteps, power=None, power_density=None, print_out=False): - # TODO Remove print_out since depletion timings are stored - return CECMIntegrator( - operator, timesteps, power, power_density).integrate_all() - - -try: - cecm.__doc__ = ( - dedent(CECMIntegrator.__doc__) + dedent(Integrator.__init__.__doc__)) -except AttributeError: - pass diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 21c96b047..a38c4e501 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -55,7 +55,7 @@ class Operator(TransportOperator): Instances of this class can be used to perform depletion using OpenMC as the transport operator. Normally, a user needn't call methods of this class directly. Instead, an instance of this class is passed to an integrator - function, such as :func:`openmc.deplete.integrator.cecm`. + class, such as :class:`openmc.deplete.CECMIntegrator`. Parameters ---------- diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index dd8b769fe..8ad1ec95e 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -5,6 +5,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. from pytest import approx import openmc.deplete +from openmc.deplete import CECMIntegrator from tests import dummy_operator @@ -18,7 +19,8 @@ def test_cecm(run_in_tmpdir): # Perform simulation using the MCNPX/MCNP6 algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.cecm(op, dt, power, print_out=False) + integrator = CECMIntegrator(op, dt, power) + integrator.integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index ec62064a4..97f0a8b70 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -6,6 +6,7 @@ problem described in dummy_geometry.py. from pytest import approx import openmc.deplete +from openmc.deplete import CECMIntegrator from tests import dummy_operator @@ -59,7 +60,8 @@ def test_restart_cecm(run_in_tmpdir): # Perform simulation using the MCNPX/MCNP6 algorithm dt = [0.75] power = 1.0 - openmc.deplete.cecm(op, dt, power, print_out=False) + cecm = CECMIntegrator(op, dt, power) + cecm.integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -69,7 +71,8 @@ def test_restart_cecm(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation using the MCNPX/MCNP6 algorithm - openmc.deplete.cecm(op, dt, power, print_out=False) + cecm_restart = CECMIntegrator(op, dt, power) + cecm_restart.integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -109,7 +112,8 @@ def test_restart_predictor_cecm(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation using the MCNPX/MCNP6 algorithm - openmc.deplete.cecm(op, dt, power, print_out=False) + cecm = CECMIntegrator(op, dt, power) + cecm.integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -139,7 +143,8 @@ def test_restart_cecm_predictor(run_in_tmpdir): # Perform simulation using the MCNPX/MCNP6 algorithm dt = [0.75] power = 1.0 - openmc.deplete.cecm(op, dt, power, print_out=False) + cecm = CECMIntegrator(op, dt, power) + cecm.integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From f0bb6002713ed622074c79513c6f8ec3e468c6fa Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 17 Jul 2019 13:36:32 -0500 Subject: [PATCH 03/32] Remove predictor function for deplete.PredictorIntegrator The depletion function openmc.deplete.predictor has been removed in favor of a class-based approach. The following syntax will replicate the behavior of the predictor integration scheme: >>> from openmc.deplete import PredictorIntegrator >>> predictor = PredictorIntegrator(operator, time, power) >>> predictor.integrate()` The expression can be reduced to a single line: >>> PredictorIntegrator(operator, time, power).integrate() --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/predictor.py | 174 +++++++++++++-------- tests/regression_tests/deplete/test.py | 2 +- tests/unit_tests/test_deplete_predictor.py | 7 +- tests/unit_tests/test_deplete_restart.py | 10 +- 5 files changed, 119 insertions(+), 76 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 32f953ed4..452361e73 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,7 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.predictor integrator.celi integrator.leqi integrator.cf4 @@ -29,6 +28,7 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myclassinherit.rst + integrator.PredictorIntegrator integrator.CECMIntegrator Each of these functions expects a "transport operator" to be passed. An operator diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index b30484c69..7345b1100 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -1,14 +1,12 @@ """First-order predictor algorithm.""" -import copy -from collections.abc import Iterable +from copy import deepcopy +from .abc import Integrator from .cram import timed_deplete -from ..results import Results -def predictor(operator, timesteps, power=None, power_density=None, - print_out=True): +class PredictorIntegrator(Integrator): r"""Deplete using a first-order predictor algorithm. Implements the first-order predictor algorithm. This algorithm is @@ -26,81 +24,125 @@ def predictor(operator, timesteps, power=None, power_density=None, operator : openmc.deplete.TransportOperator The operator object to simulate on. timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. + Array of timesteps in units of [s]. Note that values are not + cumulative. power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be specified. power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - if not isinstance(power, Iterable): - power = [power]*len(timesteps) + def __init__(self, operator, timesteps, power=None, power_density=None): + """ + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + """ + super().__init__(operator, timesteps, power, power_density) + self._dep_proc_time = None - proc_time = None + def __call__(self, conc, rates, dt, power): + """Perform the integration across one time step - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - 1 + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] - chain = operator.chain + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + conc_list : list of numpy.ndarray + Concentrations at end of interval + op_results : empty list + Kept for consistency with API. No intermediate calls to + operator with predictor - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] + """ + proc_time, conc_end = timed_deplete(self.chain, conc, rates, dt) + return proc_time, conc_end, [] - # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], p, i_res + i, proc_time) + def _get_start_data(self): + if self.operator.prev_res is None: + return 0.0, 0 + return ( + self.operator.prev_res[-1].time[-1], + len(self.operator.prev_res) - 1) + + def _get_bos_data(self, step_index, step_power, prev_conc): + if step_index > 0 or self.operator.prev_res is None: + conc = deepcopy(prev_conc) + res = self.operator(conc, step_power) + if step_index == len(self) - 1: + tvec = [self.timesteps[step_index]] * 2 else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + tvec = self.timesteps[step_index:step_index + 2] - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + self._save_results( + [conc], [res], tvec, step_power, + self._istart + step_index, self._dep_proc_time) + else: + # Get previous concentration + conc = self.operator.prev_res[-1].data[0] - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] + # Get reaction rates and keff + res = self.operator.prev_res[-1] + res.rates = res.rates[0] + res.k = res.k[0] - # Deplete for full timestep - proc_time, x_end = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out) + # Scale rates by ratio of powers + res.rates *= step_power / res.power[0] - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) + return conc, res - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] + def integrate(self): + """Perform the entire depletion process across all steps""" - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps), proc_time) + with self.operator as conc: + t, self._istart = self._get_start_data() + + for i, (dt, p) in enumerate(self): + conc, res = self._get_bos_data(i, p, conc) + # __call__ returns empty list since there aren't + # intermediate transport solutions + self._dep_proc_time, conc, _res_list = self( + conc, res.rates, dt, p) + + t += dt + + # Final simulation + res_list = [self.operator(conc, p)] + self._save_results( + [conc], res_list, [t, t], p, + self._istart + len(self), self._dep_proc_time) diff --git a/tests/regression_tests/deplete/test.py b/tests/regression_tests/deplete/test.py index 059a44ea3..5f2d8cc45 100644 --- a/tests/regression_tests/deplete/test.py +++ b/tests/regression_tests/deplete/test.py @@ -54,7 +54,7 @@ def test_full(run_in_tmpdir): power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO # Perform simulation using the predictor algorithm - openmc.deplete.integrator.predictor(op, dt, power) + openmc.deplete.PredictorIntegrator(op, dt, power).integrate() # Get path to test and reference results path_test = op.output_dir / 'depletion_results.h5' diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index 6af6f8bca..e212f4651 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import PredictorIntegrator, ResultsList from tests import dummy_operator @@ -18,10 +18,11 @@ def test_predictor(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.predictor(op, dt, power, print_out=False) + PredictorIntegrator(op, dt, power).integrate() + # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 97f0a8b70..cf7eeb0ed 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -6,7 +6,7 @@ problem described in dummy_geometry.py. from pytest import approx import openmc.deplete -from openmc.deplete import CECMIntegrator +from openmc.deplete import CECMIntegrator, PredictorIntegrator from tests import dummy_operator @@ -21,7 +21,7 @@ def test_restart_predictor(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75] power = 1.0 - openmc.deplete.predictor(op, dt, power, print_out=False) + PredictorIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -31,7 +31,7 @@ def test_restart_predictor(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation using the predictor algorithm - openmc.deplete.predictor(op, dt, power, print_out=False) + PredictorIntegrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -102,7 +102,7 @@ def test_restart_predictor_cecm(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75] power = 1.0 - openmc.deplete.predictor(op, dt, power, print_out=False) + PredictorIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -154,7 +154,7 @@ def test_restart_cecm_predictor(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation using the predictor algorithm - openmc.deplete.predictor(op, dt, power, print_out=False) + PredictorIntegrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From 1d791a16c9298a3bd97d033ceaf35f76a3aa09a8 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 17 Jul 2019 16:29:55 -0500 Subject: [PATCH 04/32] Pass iteration index to Integrator.__call__ For the higher order methods, like LEQI, a lower order method may be called for the first few steps. The LEQI method, with no restart data, will call CELI for the first iteration. To aid this, concrete Integrator classes will be passed an integer for the current iteration. Some methods, like the CECM and CELI, do not need this information. To maintain a consistent API, the iteration is an optional value to be passed to CECM.__call__, but will not be used. The default value in the call signature is ``_i=-1``, with documentation repeating that the value is not used nor needed. --- openmc/deplete/integrator/abc.py | 12 +++++++----- openmc/deplete/integrator/cecm.py | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 51877e5e6..c2324b35c 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -54,7 +54,7 @@ class Integrator(ABC): self.power = power @abstractmethod - def __call__(self, conc, rates, dt, power): + def __call__(self, conc, rates, dt, power, i): """Perform the integration across one time step Parameters @@ -67,6 +67,8 @@ class Integrator(ABC): Time in [s] for the entire depletion interval power : float Power of the system [W] + i : int + Current depletion step index Returns ------- @@ -111,11 +113,11 @@ class Integrator(ABC): def integrate(self): """Perform the entire depletion process across all steps""" with self.operator as conc: - t, i_start = self._get_start_data() + t, self._ires = self._get_start_data() for i, (dt, p) in enumerate(self): conc, res = self._get_bos_data(i, p, conc) - proc_time, conc_list, res_list = self(conc, res.rates, dt, p) + proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) # Insert BOS concentration, transport results conc_list.insert(0, conc) @@ -125,7 +127,7 @@ class Integrator(ABC): conc = conc_list.pop() self._save_results( - conc_list, res_list, [t, t + dt], p, i_start + i, + conc_list, res_list, [t, t + dt], p, self._ires + i, proc_time) t += dt @@ -133,7 +135,7 @@ class Integrator(ABC): # Final simulation res_list = [self.operator(conc, p)] self._save_results( - [conc], res_list, [t, t], p, i_start + len(self)) + [conc], res_list, [t, t], p, self._ires + len(self)) def _save_results(self, conc_list, results_list, time_list, power, index, proc_time=None): diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 1bf6e5809..b9b7dedc6 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -25,7 +25,7 @@ class CECMIntegrator(Integrator): \end{aligned} """ - def __call__(self, conc, rates, dt, power): + def __call__(self, conc, rates, dt, power, _i=-1): """Integrate using CE/CM Parameters @@ -38,6 +38,8 @@ class CECMIntegrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system [W] + _i : int, optional + Current iteration count. Not used Returns ------- From 769f903dbc85ca460604c3f0a858286dde6fa2ea Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 17 Jul 2019 16:46:30 -0500 Subject: [PATCH 05/32] Remove celi, leqi functions for CELIIntegrator, LEQIIntegrator The depletion functions openmc.deplete.celi and openmc.deplete.leqi hvae been removed in favor of a class-based approach. The following syntax will replicate the behavior of the integration schemes: >>> from openmc.deplete import CELIIntegrator >>> celi = CELIIntegrator(operator, time, power) >>> celi.integrate() The expression can be reduced to a single line: >>> LEQIIntegrator(operator, time, power).integrate() --- docs/source/pythonapi/deplete.rst | 4 +- openmc/deplete/integrator/celi.py | 179 +++++------------- openmc/deplete/integrator/leqi.py | 229 ++++++++++------------- tests/unit_tests/test_deplete_celi.py | 6 +- tests/unit_tests/test_deplete_leqi.py | 6 +- tests/unit_tests/test_deplete_restart.py | 12 +- 6 files changed, 160 insertions(+), 276 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 452361e73..99c32044d 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,8 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.celi - integrator.leqi integrator.cf4 integrator.epc_rk4 integrator.si_celi @@ -30,6 +28,8 @@ transport-depletion coupling algorithms `_. integrator.PredictorIntegrator integrator.CECMIntegrator + integrator.CELIIntegrator + integrator.LEQIIntegrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 2e80c8b73..9346347a3 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -1,25 +1,10 @@ """The CE/LI CFQ4 integrator.""" -import copy -from collections.abc import Iterable - from .cram import timed_deplete -from ..results import Results +from .abc import Integrator -# Functions to form the special matrix for depletion -def _celi_f1(chain, rates): - return 5/12 * chain.form_matrix(rates[0]) + \ - 1/12 * chain.form_matrix(rates[1]) - - -def _celi_f2(chain, rates): - return 1/12 * chain.form_matrix(rates[0]) + \ - 5/12 * chain.form_matrix(rates[1]) - - -def celi(operator, timesteps, power=None, power_density=None, - print_out=True): +class CELIIntegrator(Integrator): r"""Deplete using the CE/LI CFQ4 algorithm. Implements the CE/LI Predictor-Corrector algorithm using the `fourth order @@ -37,131 +22,57 @@ def celi(operator, timesteps, power=None, power_density=None, y_{n+1} &= \text{expm}(\frac{h}{12} A_0 + \frac{5h}{12} A1) \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - for i, (dt, p) in enumerate(zip(timesteps, power)): - vec, t, _ = celi_inner(operator, vec, p, i, i_res, t, dt, - print_out) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) - - -def celi_inner(operator, vec, p, i, i_res, t, dt, print_out): - """ The inner loop of CE/LI CFQ4. - - Parameters - ---------- - operator : Operator - The operator object to simulate on. - x : list of nuclide vector - Nuclide vector, beginning of time. - p : float - Power of the reactor in [W] - i : int - Current iteration number. - i_res : int - Starting index, for restart calculation. - t : float - Time at start of step. - dt : float - Time step. - print_out : bool - Whether or not to print out time. - - Returns - ------- - list of numpy.array - Nuclide vector, end of time. - float - Next time - OperatorResult - Operator result from beginning of step. """ - chain = operator.chain + def __call__(self, bos_conc, rates, dt, power, _i=-1): + """Perform the integration across one time step - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] + Parameters + ---------- + bos_conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + _i : int + Current iteration count. Not used - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + # deplete to end using BOS rates + proc_time, conc_ce = timed_deplete(self.chain, bos_conc, rates, dt) + res_ce = self.operator(conc_ce, power) - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + # deplete using two matrix exponeitials + list_rates = list(zip(rates, res_ce.rates)) - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] + time_le1, conc_inter = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=_celi_f1) - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] + time_le2, conc_end = timed_deplete( + self.chain, conc_inter, list_rates, dt, matrix_func=_celi_f2) - # Deplete to end - proc_time, x_new = timed_deplete(chain, x[0], op_results[0].rates, dt, print_out) - x.append(x_new) - op_results.append(operator(x[1], p)) + return proc_time + time_le1 + time_le1, [conc_ce, conc_end], [res_ce] - # Deplete with two matrix exponentials - rates = list(zip(op_results[0].rates, op_results[1].rates)) - time_1, x_end = timed_deplete(chain, x[0], rates, dt, print_out, - matrix_func=_celi_f1) - time_2, x_end = timed_deplete(chain, x_end, rates, dt, print_out, - matrix_func=_celi_f2) - # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], p, i_res + i, proc_time + time_1 + time_2) +# Functions to form the special matrix for depletion +def _celi_f1(chain, rates): + return 5/12 * chain.form_matrix(rates[0]) + \ + 1/12 * chain.form_matrix(rates[1]) - # return updated time and vectors - return x_end, t + dt, op_results[0] + +def _celi_f2(chain, rates): + return 1/12 * chain.form_matrix(rates[0]) + \ + 5/12 * chain.form_matrix(rates[1]) diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 19b1025e5..4b1c072b5 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -1,12 +1,109 @@ """The LE/QI CFQ4 integrator.""" import copy -from collections.abc import Iterable from itertools import repeat -from .celi import celi_inner +from .abc import Integrator +from .celi import CELIIntegrator from .cram import timed_deplete -from ..results import Results + + +class LEQIIntegrator(Integrator): + r"""Deplete using the LE/QI CFQ4 algorithm. + + Implements the LE/QI Predictor-Corrector algorithm using the `fourth order + commutator-free integrator `_. + + "LE/QI" stands for linear extrapolation on predictor and quadratic + interpolation on corrector. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_{last} &= A(y_{n-1}, t_n - h_1) \\ + A_0 &= A(y_n, t_n) \\ + F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0 \\ + F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0 \\ + y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n \\ + A_1 &= A(y_p, t_n + h_2) \\ + F_3 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + + \frac{h_2 (5 h_1^2 + 6 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + + \frac{h_2 h_1)}{12 (h_1 + h_2)} A_1 \\ + F_4 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + + \frac{h_2 (h_1^2 + 2 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + + \frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1 \\ + y_{n+1} &= \text{expm}(F_4) \text{expm}(F_3) y_n + \end{aligned} + + It is initialized using the CE/LI algorithm. + """ + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + if i == 0: + if self._ires <= 1: # need at least previous transport solution + self._prev_rates = bos_rates + return CELIIntegrator.__call__( + self, bos_conc, bos_rates, dt, power, i) + prev_res = self.operator.prev_res[-2] + prevdt = self.timesteps[i] - prev_res.time[0] + self._prev_rates = prev_res.rates[0] + else: + prevdt = self.timesteps[i - 1] + + # Remaining LE/QI + bos_res = self.operator(bos_conc, power) + + le_inputs = list(zip( + self._prev_rates, bos_res.rates, repeat(prevdt), repeat(dt))) + + time1, conc_inter = timed_deplete( + self.chain, bos_conc, le_inputs, dt, matrix_func=_leqi_f1) + time2, conc_eos0 = timed_deplete( + self.chain, conc_inter, le_inputs, dt, matrix_func=_leqi_f2) + + res_inter = self.operator(conc_eos0, power) + + qi_inputs = list(zip( + self._prev_rates, bos_res.rates, res_inter.rates, + repeat(prevdt), repeat(dt))) + + time3, conc_inter = timed_deplete( + self.chain, bos_conc, qi_inputs, dt, matrix_func=_leqi_f3) + time4, conc_eos1 = timed_deplete( + self.chain, conc_inter, qi_inputs, dt, matrix_func=_leqi_f4) + + # store updated rates + self._prev_rates = copy.deepcopy(bos_res.rates) + + return ( + time1 + time2 + time3 + time4, [conc_eos0, conc_eos1], + [bos_res, res_inter]) # Functions to form the special matrix for depletion @@ -42,129 +139,3 @@ def _leqi_f4(chain, inputs): return -dt**2 / (12 * dt_l * (dt + dt_l)) * f1 + \ (dt**2 + 2*dt*dt_l + dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \ (4 * dt * dt_l + 5 * dt_l**2) / (12 * dt_l * (dt + dt_l)) * f3 - - -def leqi(operator, timesteps, power=None, power_density=None, print_out=True): - r"""Deplete using the LE/QI CFQ4 algorithm. - - Implements the LE/QI Predictor-Corrector algorithm using the `fourth order - commutator-free integrator `_. - - "LE/QI" stands for linear extrapolation on predictor and quadratic - interpolation on corrector. This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_{last} &= A(y_{n-1}, t_n - h_1) \\ - A_0 &= A(y_n, t_n) \\ - F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0 \\ - F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0 \\ - y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n \\ - A_1 &= A(y_p, t_n + h_2) \\ - F_3 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + - \frac{h_2 (5 h_1^2 + 6 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + - \frac{h_2 h_1)}{12 (h_1 + h_2)} A_1 \\ - F_4 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + - \frac{h_2 (h_1^2 + 2 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + - \frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1 \\ - y_{n+1} &= \text{expm}(F_4) \text{expm}(F_3) y_n - \end{aligned} - - It is initialized using the CE/LI algorithm. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # LE/QI needs the last step results to start - # Perform CE/LI CFQ4 or restore results for the first step - if i == 0: - if i_res <= 1: - dt_l = dt - x_new, t, op_res_last = celi_inner(operator, vec, p, i, - i_res, t, dt, print_out) - continue - else: - dt_l = t - operator.prev_res[-2].time[0] - op_res_last = operator.prev_res[-2] - op_res_last.rates = op_res_last.rates[0] - x_new = operator.prev_res[-1].data[0] - - # Perform remaining LE/QI - x = [copy.deepcopy(x_new)] - op_results = [operator(x[0], p)] - - inputs = list(zip(op_res_last.rates, op_results[0].rates, - repeat(dt_l), repeat(dt))) - time_1, x_new = timed_deplete( - chain, x[0], inputs, dt, print_out, matrix_func=_leqi_f1) - time_2, x_new = timed_deplete( - chain, x_new, inputs, dt, print_out, matrix_func=_leqi_f2) - x.append(x_new) - op_results.append(operator(x[1], p)) - - inputs = list(zip(op_res_last.rates, op_results[0].rates, - op_results[1].rates, repeat(dt_l), repeat(dt))) - time_3, x_new = timed_deplete( - chain, x[0], inputs, dt, print_out, matrix_func=_leqi_f3) - time_4, x_new = timed_deplete( - chain, x_new, inputs, dt, print_out, matrix_func=_leqi_f4) - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t+dt], p, i_res+i, - time_1 + time_2 + time_3 + time_4) - - # update results - op_res_last = copy.deepcopy(op_results[0]) - t += dt - dt_l = dt - - # Perform one last simulation - x = [copy.deepcopy(x_new)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/tests/unit_tests/test_deplete_celi.py b/tests/unit_tests/test_deplete_celi.py index 47d3319fd..570ebe151 100644 --- a/tests/unit_tests/test_deplete_celi.py +++ b/tests/unit_tests/test_deplete_celi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import ResultsList, CELIIntegrator from tests import dummy_operator @@ -18,10 +18,10 @@ def test_celi(run_in_tmpdir): # Perform simulation using the celi algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.celi(op, dt, power, print_out=False) + CELIIntegrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_leqi.py b/tests/unit_tests/test_deplete_leqi.py index a26329cf4..0ab4350f6 100644 --- a/tests/unit_tests/test_deplete_leqi.py +++ b/tests/unit_tests/test_deplete_leqi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import LEQIIntegrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_leqi(run_in_tmpdir): # Perform simulation using the leqi algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.leqi(op, dt, power, print_out=False) + LEQIIntegrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index cf7eeb0ed..a914c8649 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -6,7 +6,9 @@ problem described in dummy_geometry.py. from pytest import approx import openmc.deplete -from openmc.deplete import CECMIntegrator, PredictorIntegrator +from openmc.deplete import ( + CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, +) from tests import dummy_operator @@ -261,7 +263,7 @@ def test_restart_celi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.celi(op, dt, power, print_out=False) + CELIIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -271,7 +273,7 @@ def test_restart_celi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.celi(op, dt, power, print_out=False) + CELIIntegrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -300,7 +302,7 @@ def test_restart_leqi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.leqi(op, dt, power, print_out=False) + LEQIIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -310,7 +312,7 @@ def test_restart_leqi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.leqi(op, dt, power, print_out=False) + LEQIIntegrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From dae48b77e012b660d974e5ab30cf5ac90b4d11ee Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 17 Jul 2019 17:09:13 -0500 Subject: [PATCH 06/32] Remove cf4 function for deplete.CF4Integrator The function openmc.deplete.cf4 has been removed in favor of openmc.deplete.CF4Integrator. The CF4 integration scheme can be peformed with >>> from openmc.deplete import CF4Integrator >>> CF4Integrator(operator, time, power).integrate() --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/cf4.py | 77 ++++++++++++++++++++++++++++ tests/unit_tests/test_deplete_cf4.py | 6 +-- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 99c32044d..37707e91e 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,7 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.cf4 integrator.epc_rk4 integrator.si_celi integrator.si_leqi @@ -27,6 +26,7 @@ transport-depletion coupling algorithms `_. :template: myclassinherit.rst integrator.PredictorIntegrator + integrator.CF4Integrator integrator.CECMIntegrator integrator.CELIIntegrator integrator.LEQIIntegrator diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index 8b7f3a05a..a614a4298 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -4,9 +4,86 @@ import copy from collections.abc import Iterable from .cram import timed_deplete +from .abc import Integrator from ..results import Results +class CF4Integrator(Integrator): + r"""Deplete using the CF4 algorithm. + + Implements the fourth order `commutator-free Lie algorithm + `_. + This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + F_1 &= h A(y_0) \\ + y_1 &= \text{expm}(1/2 F_1) y_0 \\ + F_2 &= h A(y_1) \\ + y_2 &= \text{expm}(1/2 F_2) y_0 \\ + F_3 &= h A(y_2) \\ + y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1 \\ + F_4 &= h A(y_3) \\ + y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4) + \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 + \end{aligned} + """ + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + bos_rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + # Step 1: deplete with matrix 1/2*A(y0) + time1, conc_eos1 = timed_deplete( + self.chain, bos_conc, bos_rates, dt, matrix_func=_cf4_f1) + res1 = self.operator(conc_eos1, power) + + # Step 2: deplete with matrix 1/2*A(y1) + time2, conc_eos2 = timed_deplete( + self.chain, bos_conc, res1.rates, dt, matrix_func=_cf4_f1) + res2 = self.operator(conc_eos2, power) + + # Step 3: deplete with matrix -1/2*A(y0)+A(y2) + list_rates = list(zip(bos_rates, res2.rates)) + time3, conc_eos3 = timed_deplete( + self.chain, conc_eos1, list_rates, dt, matrix_func=_cf4_f2) + res3 = self.operator(conc_eos3, power) + + # Step 4: deplete with two matrix exponentials + list_rates = list(zip(bos_rates, res1.rates, res2.rates, res3.rates)) + time4, conc_inter = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=_cf4_f3) + time5, conc_eos5 = timed_deplete( + self.chain, conc_inter, list_rates, dt, matrix_func=_cf4_f4) + + return (time1 + time2 + time3 + time4 + time5, + [conc_eos1, conc_eos2, conc_eos3, conc_eos5], + [res1, res2, res3]) + + # Functions to form the special matrix for depletion def _cf4_f1(chain, rates): return 1/2 * chain.form_matrix(rates) diff --git a/tests/unit_tests/test_deplete_cf4.py b/tests/unit_tests/test_deplete_cf4.py index 0c6199c3e..eb3d8a0bc 100644 --- a/tests/unit_tests/test_deplete_cf4.py +++ b/tests/unit_tests/test_deplete_cf4.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import CF4Integrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_cf4(run_in_tmpdir): # Perform simulation using the cf4 algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.cf4(op, dt, power, print_out=False) + CF4Integrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") From d3a7e73320459eac4688b21d70c0bc50aa093424 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 18 Jul 2019 08:37:17 -0500 Subject: [PATCH 07/32] Update transfer volumes test with PredictorIntegrator --- tests/unit_tests/test_transfer_volumes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/test_transfer_volumes.py b/tests/unit_tests/test_transfer_volumes.py index 9128ed969..ae2151b2d 100644 --- a/tests/unit_tests/test_transfer_volumes.py +++ b/tests/unit_tests/test_transfer_volumes.py @@ -4,7 +4,8 @@ """ from pytest import approx -import openmc.deplete +import openmc +from openmc.deplete import PredictorIntegrator, ResultsList from tests import dummy_operator @@ -18,13 +19,13 @@ def test_transfer_volumes(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75] power = 1.0 - openmc.deplete.predictor(op, dt, power, print_out=False) + PredictorIntegrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") # Create a dictionary of volumes to transfer - res[0].volume['1'] = 1.5 + res[0].volume['1'] = 1.5 res[0].volume['2'] = 2.5 # Create dummy geometry From a2545d7d7b296b419372c6918c226fe0df4c6aa3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 19 Jul 2019 16:08:19 -0500 Subject: [PATCH 08/32] Provide Integrator get bos data from openmc|restart methods All of the integrators pull the previous concentration and operator results from a restart file in the same way, but vary in how subsequent steps are handled. The predictor saves data here, while the SIE-based methods scale and reset the particles used. To this end, the _get_bos_data function has been broken into two methods: one for calling the operator and one for pulling from a restart file. --- openmc/deplete/integrator/abc.py | 33 ++++++++++++----------- openmc/deplete/integrator/predictor.py | 37 ++++++++++---------------- 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index c2324b35c..89015ea2d 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -88,23 +88,21 @@ class Integrator(ABC): def __len__(self): return len(self.timesteps) - def _get_bos_data(self, step_index, step_power, prev_conc): - if step_index > 0 or self.operator.prev_res is None: - x = deepcopy(prev_conc) - res = self.operator(x, step_power) - else: - # Get previous concentration - x = self.operator.prev_res[-1].data[0] - - # Get reaction rates and keff - res = self.operator.prev_res[-1] - res.rates = res.rates[0] - res.k = res.k[0] - - # Scale rates by ratio of powers - res.rates *= step_power / res.power[0] + def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): + x = deepcopy(bos_conc) + res = self.operator(x, step_power) return x, res + def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): + res = self.operator.prev_res[-1] + bos_conc = res.data[0] + res.rates = res.rates[0] + res.k = res.k[0] + + # Scale rates by ratio of powers + res.rates *= step_power / res.power[0] + return bos_conc, res + def _get_start_data(self): if self.operator.prev_res is None: return 0.0, 0 @@ -116,7 +114,10 @@ class Integrator(ABC): t, self._ires = self._get_start_data() for i, (dt, p) in enumerate(self): - conc, res = self._get_bos_data(i, p, conc) + if i > 0 or self.operator.prev_res is None: + conc, res = self._get_bos_data_from_openmc(i, p, conc) + else: + conc, res = self._get_bos_data_from_restart(i, p, conc) proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) # Insert BOS concentration, transport results diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 7345b1100..45783b279 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -100,30 +100,17 @@ class PredictorIntegrator(Integrator): self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - 1) - def _get_bos_data(self, step_index, step_power, prev_conc): - if step_index > 0 or self.operator.prev_res is None: - conc = deepcopy(prev_conc) - res = self.operator(conc, step_power) - if step_index == len(self) - 1: - tvec = [self.timesteps[step_index]] * 2 - else: - tvec = self.timesteps[step_index:step_index + 2] - - self._save_results( - [conc], [res], tvec, step_power, - self._istart + step_index, self._dep_proc_time) + def _get_bos_data_from_openmc(self, step_index, step_power, prev_conc): + conc = deepcopy(prev_conc) + res = self.operator(conc, step_power) + if step_index == len(self) - 1: + tvec = [self.timesteps[step_index]] * 2 else: - # Get previous concentration - conc = self.operator.prev_res[-1].data[0] - - # Get reaction rates and keff - res = self.operator.prev_res[-1] - res.rates = res.rates[0] - res.k = res.k[0] - - # Scale rates by ratio of powers - res.rates *= step_power / res.power[0] + tvec = self.timesteps[step_index:step_index + 2] + self._save_results( + [conc], [res], tvec, step_power, + self._istart + step_index, self._dep_proc_time) return conc, res def integrate(self): @@ -133,7 +120,11 @@ class PredictorIntegrator(Integrator): t, self._istart = self._get_start_data() for i, (dt, p) in enumerate(self): - conc, res = self._get_bos_data(i, p, conc) + if i > 0 or self.operator.prev_res is None: + conc, res = self._get_bos_data_from_openmc(i, p, conc) + else: + conc, res = self._get_bos_data_from_restart(i, p, conc) + # __call__ returns empty list since there aren't # intermediate transport solutions self._dep_proc_time, conc, _res_list = self( From 39b4d8a1fe0222477d6de8cf88b24d7e65617aa0 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 09:49:58 -0500 Subject: [PATCH 09/32] Remove epc_rk4 for EPC_RK4_Integrator class The function openmc.deplete.epc_rk4 has been removed in favor of openmc.deplete.EPC_RK4_Integrator. The scheme can be used with:: >>> from openmc.deplete import EPC_RK4_Integrator >>> EPC_RK4_Integrator(op, dt, power).integrate() epc_rk4 has been removed from the documentation, and the EPC_RK4_Integrator class has been added to the depletion API documentation --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/epc_rk4.py | 166 ++++++++--------------- tests/unit_tests/test_deplete_epc_rk4.py | 6 +- tests/unit_tests/test_deplete_restart.py | 5 +- 4 files changed, 60 insertions(+), 119 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 37707e91e..feb2f6dd6 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,7 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.epc_rk4 integrator.si_celi integrator.si_leqi @@ -29,6 +28,7 @@ transport-depletion coupling algorithms `_. integrator.CF4Integrator integrator.CECMIntegrator integrator.CELIIntegrator + integrator.EPC_RK4_Integrator integrator.LEQIIntegrator Each of these functions expects a "transport operator" to be passed. An operator diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index 373c323e4..ce6223c44 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -1,23 +1,10 @@ """The EPC-RK4 integrator.""" -import copy -from collections.abc import Iterable - from .cram import timed_deplete -from ..results import Results +from .abc import Integrator -# Functions to form the special matrix for depletion -def _rk4_f1(chain, rates): - return 1/2 * chain.form_matrix(rates) - -def _rk4_f4(chain, rates): - return 1/6 * chain.form_matrix(rates[0]) + \ - 1/3 * chain.form_matrix(rates[1]) + \ - 1/3 * chain.form_matrix(rates[2]) + \ - 1/6 * chain.form_matrix(rates[3]) - -def epc_rk4(operator, timesteps, power=None, power_density=None, print_out=True): +class EPC_RK4_Integrator(Integrator): r"""Deplete using the EPC-RK4 algorithm. Implements an extended predictor-corrector algorithm with traditional @@ -35,114 +22,67 @@ def epc_rk4(operator, timesteps, power=None, power_density=None, print_out=True) F_4 &= h A(y_3) \\ y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - if not isinstance(power, Iterable): - power = [power]*len(timesteps) + def __call__(self, conc, rates, dt, power, _i): + """Perform the integration across one time step - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + i : int + Current depletion step index, unused. - chain = operator.chain + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] + # Step 1: deplete with matrix A(y0) / 2 + time1, conc1 = timed_deplete( + self.chain, conc, rates, dt, matrix_func=_rk4_f1) + res1 = self.operator(conc1, power) - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + # Step 2: deplete with matrix A(y1) / 2 + time2, conc2 = timed_deplete( + self.chain, conc, res1.rates, dt, matrix_func=_rk4_f1) + res2 = self.operator(conc2, power) - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + # Step 3: deplete with matrix A(y2) + time3, conc3 = timed_deplete( + self.chain, conc, res2.rates, dt) + res3 = self.operator(conc3, power) - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] + # Step 4: deplete with matrix built from weighted rates + list_rates = list(zip(rates, res1.rates, res2.rates, res3.rates)) + time4, conc4 = timed_deplete( + self.chain, conc, list_rates, dt, matrix_func=_rk4_f4) - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] + return (time1 + time2 + time3 + time4, [conc1, conc2, conc3, conc4], + [res1, res2, res3]) - # Step 1: deplete with matrix 1/2*A(y0) - time_1, x_new = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out, - matrix_func=_rk4_f1) - x.append(x_new) - op_results.append(operator(x[1], p)) - # Step 2: deplete with matrix 1/2*A(y1) - time_2, x_new = timed_deplete( - chain, x[0], op_results[1].rates, dt, print_out, - matrix_func=_rk4_f1) - x.append(x_new) - op_results.append(operator(x[2], p)) +# Functions to form the special matrix for depletion +def _rk4_f1(chain, rates): + return 1/2 * chain.form_matrix(rates) - # Step 3: deplete with matrix A(y2) - time_3, x_new = timed_deplete( - chain, x[0], op_results[2].rates, dt, print_out) - x.append(x_new) - op_results.append(operator(x[3], p)) - # Step 4: deplete with matrix 1/6*A(y0)+1/3*A(y1)+1/3*A(y2)+1/6*A(y3) - rates = list(zip(op_results[0].rates, op_results[1].rates, - op_results[2].rates, op_results[3].rates)) - time_4, x_end = timed_deplete( - chain, x[0], rates, dt, print_out, matrix_func=_rk4_f4) - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t + dt], p, i_res + i, - time_1 + time_2 + time_3 + time_4) - - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t], p, i_res + len(timesteps)) +def _rk4_f4(chain, rates): + return 1/6 * chain.form_matrix(rates[0]) + \ + 1/3 * chain.form_matrix(rates[1]) + \ + 1/3 * chain.form_matrix(rates[2]) + \ + 1/6 * chain.form_matrix(rates[3]) diff --git a/tests/unit_tests/test_deplete_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py index ea687bfd5..71cda3b9f 100644 --- a/tests/unit_tests/test_deplete_epc_rk4.py +++ b/tests/unit_tests/test_deplete_epc_rk4.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import EPC_RK4_Integrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_epc_rk4(run_in_tmpdir): # Perform simulation using the epc_rk4 algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.epc_rk4(op, dt, power, print_out=False) + EPC_RK4_Integrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index a914c8649..9323c5779 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,6 +8,7 @@ from pytest import approx import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, + EPC_RK4_Integrator, ) from tests import dummy_operator @@ -224,7 +225,7 @@ def test_restart_epc_rk4(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.epc_rk4(op, dt, power, print_out=False) + EPC_RK4_Integrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -234,7 +235,7 @@ def test_restart_epc_rk4(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.epc_rk4(op, dt, power, print_out=False) + EPC_RK4_Integrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From e30e6e3f99edcf037f63851764adafdeafea275d Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 09:52:55 -0500 Subject: [PATCH 10/32] Use CF4Integrator in restart tests --- tests/unit_tests/test_deplete_restart.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 9323c5779..617575720 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,7 +8,7 @@ from pytest import approx import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPC_RK4_Integrator, + EPC_RK4_Integrator, CF4Integrator ) from tests import dummy_operator @@ -186,7 +186,7 @@ def test_restart_cf4(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.cf4(op, dt, power, print_out=False) + CF4Integrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -196,7 +196,7 @@ def test_restart_cf4(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.cf4(op, dt, power, print_out=False) + CF4Integrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From 58117628b8ae8384278c5ef5ef5d32e7c922fb4e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 09:53:14 -0500 Subject: [PATCH 11/32] Fully remove openmc.deplete.cf4 function from API --- openmc/deplete/integrator/cf4.py | 133 ------------------------------- 1 file changed, 133 deletions(-) diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index a614a4298..7e0b603ae 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -106,136 +106,3 @@ def _cf4_f4(chain, rates): 1/6 * chain.form_matrix(rates[1]) + \ 1/6 * chain.form_matrix(rates[2]) + \ 1/4 * chain.form_matrix(rates[3]) - - -def cf4(operator, timesteps, power=None, power_density=None, print_out=True): - r"""Deplete using the CF4 algorithm. - - Implements the fourth order `commutator-free Lie algorithm - `_. - This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - F_1 &= h A(y_0) \\ - y_1 &= \text{expm}(1/2 F_1) y_0 \\ - F_2 &= h A(y_1) \\ - y_2 &= \text{expm}(1/2 F_2) y_0 \\ - F_3 &= h A(y_2) \\ - y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1 \\ - F_4 &= h A(y_3) \\ - y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4) - \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] - - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] - - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] - - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] - - # Step 1: deplete with matrix 1/2*A(y0) - time_1, x_new = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out, - matrix_func=_cf4_f1) - x.append(x_new) - op_results.append(operator(x_new, p)) - - # Step 2: deplete with matrix 1/2*A(y1) - time_2, x_new = timed_deplete( - chain, x[0], op_results[1].rates, dt, print_out, - matrix_func=_cf4_f1) - x.append(x_new) - op_results.append(operator(x_new, p)) - - # Step 3: deplete with matrix -1/2*A(y0)+A(y2) - rates = list(zip(op_results[0].rates, op_results[2].rates)) - time_3, x_new = timed_deplete( - chain, x[1], rates, dt, print_out, matrix_func=_cf4_f2) - x.append(x_new) - op_results.append(operator(x_new, p)) - - # Step 4: deplete with two matrix exponentials - rates = list(zip(op_results[0].rates, op_results[1].rates, - op_results[2].rates, op_results[3].rates)) - time_4, x_end = timed_deplete( - chain, x[0], rates, dt, print_out, matrix_func=_cf4_f3) - time_5, x_end = timed_deplete( - chain, x_end, rates, dt, print_out, matrix_func=_cf4_f4) - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t + dt], p, i_res + i, - time_1 + time_2 + time_3 + time_4 + time_5) - - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) From 98957fb47350d8b012236f00cc89a030c38e4590 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 10:31:24 -0500 Subject: [PATCH 12/32] Store OperatorResults.k as uncertainties.ufloat This allows the propagation of the uncertainty through the SIE iterations, and alows the expressions for updating k to be preserved. The OperatorResult.k was previous stored as a tuple of two floats. The interface into the depletion_results file is not changed, as the Results.save method, which writes the OperatorResult data, breaks the ufloat into k and uncertainties. Documentation has been updated on the OperatorResult. --- openmc/deplete/abc.py | 4 ++-- openmc/deplete/integrator/abc.py | 12 +++++++----- openmc/deplete/integrator/si_celi.py | 4 +++- openmc/deplete/integrator/si_leqi.py | 4 +++- openmc/deplete/operator.py | 3 ++- openmc/deplete/results.py | 6 +++--- tests/dummy_operator.py | 4 +++- tests/unit_tests/test_deplete_integrator.py | 6 ++++-- 8 files changed, 27 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 1be3e27ff..5e2f1dc64 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -20,8 +20,8 @@ Result of applying transport operator Parameters ---------- -k : float - Resulting eigenvalue +k : uncertainties.ufloat + Resulting eigenvalue and standard deviation rates : openmc.deplete.ReactionRates Resulting reaction rates diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 89015ea2d..07151d13b 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -2,7 +2,9 @@ from copy import deepcopy from abc import ABC, abstractmethod from collections.abc import Iterable -from openmc.deplete import Results +from uncertainties import ufloat + +from openmc.deplete import Results, OperatorResult class Integrator(ABC): @@ -96,12 +98,12 @@ class Integrator(ABC): def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): res = self.operator.prev_res[-1] bos_conc = res.data[0] - res.rates = res.rates[0] - res.k = res.k[0] + rates = res.rates[0] + k = ufloat(res.k[0,0], res.k[0, 1]) # Scale rates by ratio of powers - res.rates *= step_power / res.power[0] - return bos_conc, res + rates *= step_power / res.power[0] + return bos_conc, OperatorResult(k, rates) def _get_start_data(self): if self.operator.prev_res is None: diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 26087c1f2..872863ef2 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -3,6 +3,8 @@ import copy from collections.abc import Iterable +from uncertainties import ufloat + from .cram import timed_deplete from ..results import Results from ..abc import OperatorResult @@ -82,7 +84,7 @@ def si_celi(operator, timesteps, power=None, power_density=None, op_results[0].rates = op_results[0].rates[0] # Set first stage value of keff - op_results[0].k = op_results[0].k[0] + op_results[0].k = ufloat(*op_results[0].k[0]) # Scale reaction rates by ratio of powers power_res = operator.prev_res[-1].power diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 7d01782ba..86b184038 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -4,6 +4,8 @@ import copy from collections.abc import Iterable from itertools import repeat +from uncertainties import ufloat + from .si_celi import si_celi_inner from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 from .cram import timed_deplete @@ -84,7 +86,7 @@ def si_leqi(operator, timesteps, power=None, power_density=None, op_results[0].rates = op_results[0].rates[0] # Set first stage value of keff - op_results[0].k = op_results[0].k[0] + op_results[0].k = ufloat(*op_results[0].k[0]) # Scale reaction rates by ratio of powers power_res = operator.prev_res[-1].power diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index a38c4e501..e9c41093a 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -16,6 +16,7 @@ import xml.etree.ElementTree as ET import h5py import numpy as np +from uncertainties import ufloat import openmc import openmc.capi @@ -526,7 +527,7 @@ class Operator(TransportOperator): rates[:, :, :] = 0.0 # Get k and uncertainty - k_combined = openmc.capi.keff() + k_combined = ufloat(*openmc.capi.keff()) # Extract tally bins materials = self.burnable_mats diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 3771f9683..a2462b80a 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -21,8 +21,8 @@ class Results(object): Attributes ---------- - k : list of float - Eigenvalue for each substep. + k : list of (float, float) + Eigenvalue and uncertainty for each substep. time : list of float Time at beginning, end of step, in seconds. power : float @@ -446,7 +446,7 @@ class Results(object): for mat_i in range(n_mat): results[i, mat_i, :] = x[offset + i][mat_i][:] - results.k = [r.k for r in op_results] + results.k = [(r.k.nominal_value, r.k.std_dev) for r in op_results] results.rates = [r.rates for r in op_results] results.time = t results.power = power diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index c66070ad4..23dc22288 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -1,5 +1,7 @@ import numpy as np import scipy.sparse as sp +from uncertainties import ufloat + from openmc.deplete.reaction_rates import ReactionRates from openmc.deplete.abc import TransportOperator, OperatorResult @@ -48,7 +50,7 @@ class DummyOperator(TransportOperator): reaction_rates[0, 1, 0] = vec[0][1] # Create a fake rates object - return OperatorResult(0.0, reaction_rates) + return OperatorResult(ufloat(0.0, 0.0), reaction_rates) @property def chain(self): diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 40090264a..c50ac66a9 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -11,6 +11,8 @@ import os from unittest.mock import MagicMock import numpy as np +from uncertainties import ufloat + from openmc.deplete import (ReactionRates, Results, ResultsList, comm, OperatorResult) @@ -74,8 +76,8 @@ def test_results_save(run_in_tmpdir): t1 = [0.0, 1.0] t2 = [1.0, 2.0] - op_result1 = [OperatorResult(k, rates) for k, rates in zip(eigvl1, rate1)] - op_result2 = [OperatorResult(k, rates) for k, rates in zip(eigvl2, rate2)] + op_result1 = [OperatorResult(ufloat(*k), rates) for k, rates in zip(eigvl1, rate1)] + op_result2 = [OperatorResult(ufloat(*k), rates) for k, rates in zip(eigvl2, rate2)] Results.save(op, x1, op_result1, t1, 0, 0) Results.save(op, x2, op_result2, t2, 0, 1) From d28546bcd671d7905d59ff3fa66cbb8ae36a54c5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 12:05:06 -0500 Subject: [PATCH 13/32] Add SI_Integrator, SI_CELI_Integrator classes; remove si_celi The si_celi depletion function has been removed. The SI-CELI method can be implemented with >>> from openmc.deplete import SI_CELI_Integrator >>> SI_CELI_Integrator(op, dt, power, stages).integrate() The stages parameter is optional, and defaults to 10 to be consistent with the previous default for si_celi. The SI_CELI_Integrator inherits from the new abstract base class SI_Integrator. There are a few differences in how the SI-based methods perform the integration. - The initial, t=0.0, i=0, no restart transport solution is scaled by the number of stages. - The SI methods also do not perform a new transport solution at each successive iteration [t>0, i>0]. Instead, the reaction rates are pulled from the last stage of the previous step. - There is no final transport solution once all the depletion steps have been taken. The final reaction rates are saved as those from the last stage of the last depletion step. The si_celi function has been removed from documentation and testing, and replaced with the SI_CELI_Integrator. --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/abc.py | 84 +++++++++++++++ openmc/deplete/integrator/si_celi.py | 131 +++++++++-------------- tests/unit_tests/test_deplete_restart.py | 6 +- tests/unit_tests/test_deplete_si_celi.py | 6 +- 5 files changed, 143 insertions(+), 86 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index feb2f6dd6..60628a61b 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,7 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.si_celi integrator.si_leqi .. autosummary:: @@ -30,6 +29,7 @@ transport-depletion coupling algorithms `_. integrator.CELIIntegrator integrator.EPC_RK4_Integrator integrator.LEQIIntegrator + integrator.SI_CELI_Integrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 07151d13b..422311964 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -149,3 +149,87 @@ class Integrator(ABC): Results.save( self.operator, conc_list, results_list, time_list, power, index, proc_time) + + +class SI_Integrator(Integrator): + """Abstract for the Stochastic Implicit Euler integrators + + Does not provide a ``__call__`` method, but scales and resets + the number of particles used in initial transport calculation + """ + def __init__(self, operator, timesteps, power=None, power_density=None, + n_steps=10): + """ + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_stages : int, optional + Number of stages. Default : 10 + """ + + def __init__(self, operator, timesteps, power=None, power_density=None, + n_stages=10): + super().__init__(operator, timesteps, power, power_density) + self.n_stages = n_stages + + def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): + reset_particles = False + if step_index == 0 and hasattr(self.operator, "settings"): + reset_particles = True + self.operator.settings.particles *= self.n_stages + inherited = super()._get_bos_data_from_openmc( + step_index, step_power, bos_conc) + if reset_particles: + self.operator.settings.particles //= self.n_stages + return inherited + + def integrate(self): + """Perform the entire depletion process across all steps""" + with self.operator as conc: + t, self._ires = self._get_start_data() + + for i, (dt, p) in enumerate(self): + if i == 0: + if self.operator.prev_res is None: + conc, res = self._get_bos_data_from_openmc(i, p, conc) + else: + conc, res = self._get_bos_data_from_restart(i, p, conc) + else: + # Pull rates, k from previous iteration w/o + # re-running transport + res = res_list[-1] # defined in previous i iteration + + proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) + + # Insert BOS concentration, transport results + conc_list.insert(0, conc) + res_list.insert(0, res) + + # Remove actual EOS concentration for next step + conc = conc_list.pop() + + self._save_results( + conc_list, res_list, [t, t + dt], p, self._ires + i, + proc_time) + + t += dt + + # No final simulation for SIE, use last iteration results + self._save_results( + [conc], [res_list[-1]], [t, t], p, self._ires + len(self)) diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 872863ef2..4d6ce2f08 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -1,103 +1,76 @@ """The SI-CE/LI CFQ4 integrator.""" import copy -from collections.abc import Iterable from uncertainties import ufloat +from .abc import SI_Integrator from .cram import timed_deplete -from ..results import Results -from ..abc import OperatorResult from .celi import _celi_f1, _celi_f2 +from ..abc import OperatorResult +from ..results import Results -def si_celi(operator, timesteps, power=None, power_density=None, - print_out=True, m=10): - r"""Deplete using the SI-CE/LI CFQ4 algorithm. +class SI_CELI_Integrator(SI_Integrator): + r"""Deplete using the si-ce/li cfq4 algorithm. - Implements the Stochastic Implicit CE/LI Predictor-Corrector algorithm using + Implements the stochastic implicit ce/li predictor-corrector algorithm using the `fourth order commutator-free integrator `_. - Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis + detailed algorithm can be found in section 3.2 in `colin josey's thesis `_. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - m : int, optional - Number of stages. """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] + def __call__(self, bos_conc, bos_rates, dt, power, _i): + """Perform the integration across one time step - if not isinstance(power, Iterable): - power = [power]*len(timesteps) + Parameters + ---------- + bos_conc : numpy.ndarray + Initial bos_concentrations for all nuclides in [atom] + bos_rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + _i : int + Current depletion step index. Unused - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + bos_conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final bos_concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + proc_time, eos_conc = timed_deplete( + self.chain, bos_conc, bos_rates, dt) + inter_conc = copy.deepcopy(eos_conc) - # Get the concentrations and reaction rates for the first - # beginning-of-timestep (BOS). Compute with m (stage number) times as - # many neutrons as later simulations for statistics reasons if no - # previous calculation results present - if operator.prev_res is None: - x = [copy.deepcopy(vec)] - if hasattr(operator, "settings"): - operator.settings.particles *= m - op_results = [operator(x[0], power[0])] - if hasattr(operator, "settings"): - operator.settings.particles //= m - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + # Begin iteration + for j in range(self.n_stages + 1): + inter_res = self.operator(inter_conc, power) - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + if j <= 1: + res_bar = copy.deepcopy(inter_res) + else: + rates = 1/j * inter_res.rates + (1 - 1 / j) * res_bar.rates + k = 1/j * inter_res.k + (1 - 1 / j) * res_bar.k + res_bar = OperatorResult(k, rates) - # Set first stage value of keff - op_results[0].k = ufloat(*op_results[0].k[0]) + list_rates = list(zip(bos_rates, res_bar.rates)) + time1, inter_conc = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=_celi_f1) + time2, inter_conc = timed_deplete( + self.chain, inter_conc, list_rates, dt, matrix_func=_celi_f2) + proc_time += time1 + time2 - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = power[0] / power_res - op_results[0].rates *= ratio_power[0] - - for i, (dt, p) in enumerate(zip(timesteps, power)): - x, t, op_results = si_celi_inner(operator, x, op_results, p, - i, i_res, t, dt, print_out, m) - - # Create results for last point, write to disk - Results.save( - operator, x, op_results, [t, t], p, i_res + len(timesteps)) + # end iteration + return proc_time, [eos_conc, inter_conc], [res_bar] def si_celi_inner(operator, x, op_results, p, i, i_res, t, dt, print_out, m=10): diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 617575720..01ee56a82 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,7 +8,7 @@ from pytest import approx import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPC_RK4_Integrator, CF4Integrator + EPC_RK4_Integrator, CF4Integrator, SI_CELI_Integrator ) from tests import dummy_operator @@ -341,7 +341,7 @@ def test_restart_si_celi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.si_celi(op, dt, power, print_out=False) + SI_CELI_Integrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -351,7 +351,7 @@ def test_restart_si_celi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.si_celi(op, dt, power, print_out=False) + SI_CELI_Integrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_si_celi.py b/tests/unit_tests/test_deplete_si_celi.py index 71679439c..954c9d890 100644 --- a/tests/unit_tests/test_deplete_si_celi.py +++ b/tests/unit_tests/test_deplete_si_celi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import SI_CELI_Integrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_si_celi(run_in_tmpdir): # Perform simulation using the si_celi algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.si_celi(op, dt, power, print_out=False) + SI_CELI_Integrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") From 791f7c78b783ecd3ed94b74c8ba58ef3ac488ecc Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 29 Jul 2019 08:36:09 -0500 Subject: [PATCH 14/32] Return BOS concentration as list of vectors from restarts Operator.initial_condition return list of vectors representing the initial composition for a burnable material as an element in the list. This format is expected from depletion solvers as well. Previously, when pulling from the previous results on an operator, the initial concentration was returned simply as a 2D array of compositions. This commit converts the 2D array to a list of vectors to be consistent across the integration schemes. --- openmc/deplete/integrator/abc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 422311964..24bd06395 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -97,7 +97,9 @@ class Integrator(ABC): def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): res = self.operator.prev_res[-1] - bos_conc = res.data[0] + # Depletion methods expect list of arrays + # See Operator.__call__, Operator.initial_condition + bos_conc = list(res.data[0]) rates = res.rates[0] k = ufloat(res.k[0,0], res.k[0, 1]) From 1f3e7fdac1e0433d38b82ff13f5d585754fac0de Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 29 Jul 2019 10:29:24 -0500 Subject: [PATCH 15/32] Remove si_leqi in favor of SI_LEQI_Integrator The depletion function openmc.deplete.si_leqi has been removed in favor of the SI_LEQI_Integrator class. The same depletion scheme can be obtained with the following commands: >>> leqi = openmc.deplete.SI_LEQI_Integrator(operator, dt, power) >>> leqi.integrate() The expression can be onlined for compactness. The si_celi_inner function has been removed completely now, as the SI_CELI iteration is performed by directly calling SI_CELI_Integrator.__call__ through the SI_LEQI_Integrator. This is similar to how the LEQIIntegrator handles the initial steps. Tests have been updated to use this class, and the class has been added to the documentation. No pure-function integration schemes exist anymore. --- docs/source/pythonapi/deplete.rst | 8 +- openmc/deplete/integrator/si_celi.py | 68 -------- openmc/deplete/integrator/si_leqi.py | 197 ++++++++--------------- tests/unit_tests/test_deplete_restart.py | 7 +- tests/unit_tests/test_deplete_si_leqi.py | 6 +- 5 files changed, 75 insertions(+), 211 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 60628a61b..aa1288411 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -11,13 +11,6 @@ algorithms for depletion calculations, which are described in detail in Colin Josey's thesis, `Development and analysis of high order neutron transport-depletion coupling algorithms `_. -.. autosummary:: - :toctree: generated - :nosignatures: - :template: myfunction.rst - - integrator.si_leqi - .. autosummary:: :toctree: generated :nosignatures: @@ -30,6 +23,7 @@ transport-depletion coupling algorithms `_. integrator.EPC_RK4_Integrator integrator.LEQIIntegrator integrator.SI_CELI_Integrator + integrator.SI_LEQI_Integrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 4d6ce2f08..dde3cfb81 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -71,71 +71,3 @@ class SI_CELI_Integrator(SI_Integrator): # end iteration return proc_time, [eos_conc, inter_conc], [res_bar] - - -def si_celi_inner(operator, x, op_results, p, i, i_res, t, dt, print_out, m=10): - """ The inner loop of SI-CE/LI CFQ4. - - Parameters - ---------- - operator : Operator - The operator object to simulate on. - x : list of nuclide vector - Nuclide vector, beginning of time. - op_results : list of OperatorResult - Operator result at BOS. - p : float - Power of the reactor in [W] - i : int - Current iteration number. - i_res : int - Starting index, for restart calculation. - t : float - Time at start of step. - dt : float - Time step. - print_out : bool - Whether or not to print out time. - m : int, optional - Number of stages. - - Returns - ------- - list of nuclide vector (numpy.array) - Nuclide vector, end of time. - float - Next time - list of OperatorResult - Operator result at end of time. - """ - - chain = operator.chain - - # Deplete to end - proc_time, x_new = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out) - x.append(x_new) - - for j in range(m + 1): - op_res = operator(x_new, p) - - if j <= 1: - op_res_bar = copy.deepcopy(op_res) - else: - rates = 1/j * op_res.rates + (1 - 1/j) * op_res_bar.rates - k = 1/j * op_res.k + (1 - 1/j) * op_res_bar.k - op_res_bar = OperatorResult(k, rates) - - rates = list(zip(op_results[0].rates, op_res_bar.rates)) - time_1, x_new = timed_deplete( - chain, x[0], rates, dt, print_out, matrix_func=_celi_f1) - time_2, x_new = timed_deplete( - chain, x_new, rates, dt, print_out, matrix_func=_celi_f2) - proc_time += time_1 + time_2 - - # Create results, write to disk - op_results.append(op_res_bar) - Results.save(operator, x, op_results, [t, t+dt], p, i_res+i, proc_time) - - # return updated time and vectors - return [x_new], t + dt, [op_res_bar] diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 86b184038..bd42104ea 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -6,15 +6,15 @@ from itertools import repeat from uncertainties import ufloat -from .si_celi import si_celi_inner +from .abc import SI_Integrator +from .si_celi import SI_CELI_Integrator from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 from .cram import timed_deplete from ..results import Results from ..abc import OperatorResult -def si_leqi(operator, timesteps, power=None, power_density=None, - print_out=True, m=10): +class SI_LEQI_Integrator(SI_Integrator): r"""Deplete using the SI-LE/QI CFQ4 algorithm. Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm using @@ -22,138 +22,75 @@ def si_leqi(operator, timesteps, power=None, power_density=None, Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis `_. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - m : int, optional - Number of stages. """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : list of numpy.ndarray + Initial concentrations for all nuclides in [atom] for + all depletable materials + bos_rates : list of openmc.deplete.ReactionRates + Reaction rates from operator for all depletable materials + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + if i == 0: + if self._ires <= 1: + self._prev_rates = bos_rates + # Perform CELI for initial steps + return SI_CELI_Integrator.__call__( + self, bos_conc, bos_rates, dt, power, i) + prev_res = self.operator.prev_res[-2] + prevdt = self.timesteps[i] - prev_res.time[0] + self._prev_rates = prev_res.rates[0] else: - power = [i*operator.heavy_metal for i in power_density] + prevdt = self.timesteps[i - 1] - if not isinstance(power, Iterable): - power = [power]*len(timesteps) + # Perform remaining LE/QI + inputs = list(zip(self._prev_rates, bos_rates, + repeat(prevdt), repeat(dt))) + proc_time, inter_conc = timed_deplete( + self.chain, bos_conc, inputs, dt, matrix_func=_leqi_f1) + time1, eos_conc = timed_deplete( + self.chain, inter_conc, inputs, dt, matrix_func=_leqi_f2) - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) + proc_time += time1 + inter_conc = copy.deepcopy(eos_conc) - # Get the concentrations and reaction rates for the first - # beginning-of-timestep (BOS). Compute with m (stage number) times as - # many neutrons as later simulations for statistics reasons if no - # previous calculation results present - if operator.prev_res is None: - x = [copy.deepcopy(vec)] - if hasattr(operator, "settings"): - operator.settings.particles *= m - op_results = [operator(x[0], power[0])] - if hasattr(operator, "settings"): - operator.settings.particles //= m - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + for j in range(self.n_stages + 1): + inter_res = self.operator(inter_conc, power) - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + if j <= 1: + res_bar = copy.deepcopy(inter_res) + else: + rates = 1 / j * inter_res.rates + (1 - 1 / j) * res_bar.rates + k = 1 / j * inter_res.k + (1 - 1 / j) * res_bar.k + res_bar = OperatorResult(k, rates) - # Set first stage value of keff - op_results[0].k = ufloat(*op_results[0].k[0]) + inputs = list(zip(self._prev_rates, bos_rates, res_bar.rates, + repeat(prevdt), repeat(dt))) + time1, inter_conc = timed_deplete( + self.chain, bos_conc, inputs, dt, matrix_func=_leqi_f3) + time2, inter_conc = timed_deplete( + self.chain, inter_conc, inputs, dt, matrix_func=_leqi_f4) + proc_time += time1 + time2 - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = power[0] / power_res - op_results[0].rates *= ratio_power[0] - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # LE/QI needs the last step results to start - # Perform SI-CE/LI CFQ4 or restore results for the first step - if i == 0: - dt_l = dt - if i_res <= 1: - op_res_last = copy.deepcopy(op_results[0]) - x, t, op_results = si_celi_inner(operator, x, op_results, p, - i, i_res, t, dt, print_out) - continue - else: - dt_l = t - operator.prev_res[-2].time[0] - op_res_last = operator.prev_res[-2] - op_res_last.rates = op_res_last.rates[0] - x = [operator.prev_res[-1].data[0]] - - # Perform remaining LE/QI - inputs = list(zip(op_res_last.rates, op_results[0].rates, - repeat(dt_l), repeat(dt))) - proc_time, x_new = timed_deplete( - chain, x[0], inputs, dt, print_out, matrix_func=_leqi_f1) - time_1, x_new = timed_deplete( - chain, x_new, inputs, dt, print_out, matrix_func=_leqi_f2) - x.append(x_new) - - proc_time += time_1 - - # Loop on inner - for j in range(m + 1): - op_res = operator(x_new, p) - - if j <= 1: - op_res_bar = copy.deepcopy(op_res) - else: - rates = 1/j * op_res.rates + (1 - 1/j) * op_res_bar.rates - k = 1/j * op_res.k + (1 - 1/j) * op_res_bar.k - op_res_bar = OperatorResult(k, rates) - - inputs = list(zip(op_res_last.rates, op_results[0].rates, - op_res_bar.rates, repeat(dt_l), repeat(dt))) - time_1, x_new = timed_deplete( - chain, x[0], inputs, dt, print_out, matrix_func=_leqi_f3) - time_2, x_new = timed_deplete( - chain, x_new, inputs, dt, print_out, matrix_func=_leqi_f4) - - proc_time += time_1 + time_2 - - # Create results, write to disk - op_results.append(op_res_bar) - Results.save( - operator, x, op_results, [t, t+dt], p, i_res+i, proc_time) - - # update results - x = [x_new] - op_res_last = copy.deepcopy(op_results[0]) - op_results = [op_res_bar] - t += dt - dt_l = dt - - # Create results for last point, write to disk - Results.save( - operator, x, op_results, [t, t], p, i_res+len(timesteps)) + return proc_time, [eos_conc, inter_conc], [res_bar] diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 01ee56a82..78a910d41 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,7 +8,7 @@ from pytest import approx import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPC_RK4_Integrator, CF4Integrator, SI_CELI_Integrator + EPC_RK4_Integrator, CF4Integrator, SI_CELI_Integrator, SI_LEQI_Integrator ) from tests import dummy_operator @@ -380,7 +380,8 @@ def test_restart_si_leqi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.si_leqi(op, dt, power, print_out=False) + nstages = 10 + SI_LEQI_Integrator(op, dt, power, nstages).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -390,7 +391,7 @@ def test_restart_si_leqi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.si_leqi(op, dt, power, print_out=False) + SI_LEQI_Integrator(op, dt, power, nstages).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_si_leqi.py b/tests/unit_tests/test_deplete_si_leqi.py index 1396065c0..c26c06d03 100644 --- a/tests/unit_tests/test_deplete_si_leqi.py +++ b/tests/unit_tests/test_deplete_si_leqi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import SI_LEQI_Integrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_si_leqi(run_in_tmpdir): # Perform simulation using the si_leqi algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.si_leqi(op, dt, power, print_out=False) + SI_LEQI_Integrator(op, dt, power, 10).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") From d5fa7b5c0091abc9ad124dc996d284dc9c001cc7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 29 Jul 2019 14:46:02 -0500 Subject: [PATCH 16/32] Set correct index for final nuclides in restart tests Many repetitions of the following change: - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) This causes many of the restart tests to fail, because all schemes but the predictor save repeated data under restart conditions. Performing one normal depletion event, non-restarted, and then one restart event, as these tests do, should produce vectors for time and densities like: t = [t0, t1, t2] a = [a0, a1, a2] | ^ after restart ^ non-restart run With this, the current tests would fail due to an IndexError, as the atom vectors should only have three elements. Instead, non-predictor schemes return vectors t = [t0, t1, t1, t2] a = [a0, a1, a1, a2] and one can access a[3] to obtain the desired EOS concentrations. --- tests/unit_tests/test_deplete_restart.py | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 78a910d41..493828fd8 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -90,8 +90,8 @@ def test_restart_cecm(run_in_tmpdir): assert y1[1] == approx(s1[0]) assert y2[1] == approx(s1[1]) - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_predictor_cecm(run_in_tmpdir): @@ -211,8 +211,8 @@ def test_restart_cf4(run_in_tmpdir): assert y1[1] == approx(s1[0]) assert y2[1] == approx(s1[1]) - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_epc_rk4(run_in_tmpdir): @@ -250,8 +250,8 @@ def test_restart_epc_rk4(run_in_tmpdir): assert y1[1] == approx(s1[0]) assert y2[1] == approx(s1[1]) - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_celi(run_in_tmpdir): @@ -289,8 +289,8 @@ def test_restart_celi(run_in_tmpdir): assert y1[1] == approx(s1[0]) assert y2[1] == approx(s1[1]) - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_leqi(run_in_tmpdir): @@ -328,8 +328,8 @@ def test_restart_leqi(run_in_tmpdir): assert y1[1] == approx(s1[0]) assert y2[1] == approx(s1[1]) - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_si_celi(run_in_tmpdir): """Integral regression test of integrator algorithm using SI-CELI.""" @@ -366,8 +366,8 @@ def test_restart_si_celi(run_in_tmpdir): assert y1[1] == approx(s1[0]) assert y2[1] == approx(s1[1]) - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_si_leqi(run_in_tmpdir): @@ -406,5 +406,5 @@ def test_restart_si_leqi(run_in_tmpdir): assert y1[1] == approx(s1[0]) assert y2[1] == approx(s1[1]) - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) From afe1b5b68ced2b07991097132e16fe9f17bbf6ec Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 08:46:23 -0500 Subject: [PATCH 17/32] Fix restart integration offset bugs Changes in d32139b993053912ba0d8117424d1585eb0aed0b highlighted the repeated data that was saved during non-predictor restart runs. All Integrator subclasses now have the same _get_bos_data_from_openmc and _get_bos_data_from_restart methods, where the restart index ``self._ires`` is one less than the number of previous results stored on the Operator. This allows all schemes to write their data in the correct index during restart calculations. A side benefit of this is that now, the Predictor implementation can rely entirely on the updated Integrator, without the need for re-writing the integrate method nor __init__ method. --- openmc/deplete/integrator/abc.py | 2 +- openmc/deplete/integrator/leqi.py | 2 +- openmc/deplete/integrator/predictor.py | 76 ++------------------------ openmc/deplete/integrator/si_leqi.py | 2 +- 4 files changed, 7 insertions(+), 75 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 24bd06395..ca21f2a1b 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -110,7 +110,7 @@ class Integrator(ABC): def _get_start_data(self): if self.operator.prev_res is None: return 0.0, 0 - return self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) + return self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - 1 def integrate(self): """Perform the entire depletion process across all steps""" diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 4b1c072b5..4bf4cf4d9 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -66,7 +66,7 @@ class LEQIIntegrator(Integrator): simulation """ if i == 0: - if self._ires <= 1: # need at least previous transport solution + if self._ires < 1: # need at least previous transport solution self._prev_rates = bos_rates return CELIIntegrator.__call__( self, bos_conc, bos_rates, dt, power, i) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 45783b279..0e4dce423 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -40,32 +40,7 @@ class PredictorIntegrator(Integrator): is not speficied. """ - def __init__(self, operator, timesteps, power=None, power_density=None): - """ - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - """ - super().__init__(operator, timesteps, power, power_density) - self._dep_proc_time = None - - def __call__(self, conc, rates, dt, power): + def __call__(self, conc, rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -78,6 +53,8 @@ class PredictorIntegrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system [W] + _i : int or None + Iteration index. Not used Returns ------- @@ -91,49 +68,4 @@ class PredictorIntegrator(Integrator): """ proc_time, conc_end = timed_deplete(self.chain, conc, rates, dt) - return proc_time, conc_end, [] - - def _get_start_data(self): - if self.operator.prev_res is None: - return 0.0, 0 - return ( - self.operator.prev_res[-1].time[-1], - len(self.operator.prev_res) - 1) - - def _get_bos_data_from_openmc(self, step_index, step_power, prev_conc): - conc = deepcopy(prev_conc) - res = self.operator(conc, step_power) - if step_index == len(self) - 1: - tvec = [self.timesteps[step_index]] * 2 - else: - tvec = self.timesteps[step_index:step_index + 2] - - self._save_results( - [conc], [res], tvec, step_power, - self._istart + step_index, self._dep_proc_time) - return conc, res - - def integrate(self): - """Perform the entire depletion process across all steps""" - - with self.operator as conc: - t, self._istart = self._get_start_data() - - for i, (dt, p) in enumerate(self): - if i > 0 or self.operator.prev_res is None: - conc, res = self._get_bos_data_from_openmc(i, p, conc) - else: - conc, res = self._get_bos_data_from_restart(i, p, conc) - - # __call__ returns empty list since there aren't - # intermediate transport solutions - self._dep_proc_time, conc, _res_list = self( - conc, res.rates, dt, p) - - t += dt - - # Final simulation - res_list = [self.operator(conc, p)] - self._save_results( - [conc], res_list, [t, t], p, - self._istart + len(self), self._dep_proc_time) + return proc_time, [conc_end], [] diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index bd42104ea..015650269 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -53,7 +53,7 @@ class SI_LEQI_Integrator(SI_Integrator): simulation """ if i == 0: - if self._ires <= 1: + if self._ires < 1: self._prev_rates = bos_rates # Perform CELI for initial steps return SI_CELI_Integrator.__call__( From c4567b6d9d17c6d81aa5dfb9074d4ecad3657e29 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 09:19:26 -0500 Subject: [PATCH 18/32] Remove print_out options to Operator.__call__, deplete Time spent running openmc is already presented in after each transport run. Time spent performing depletion is written in the depletion file. --- openmc/deplete/abc.py | 4 +--- openmc/deplete/integrator/cram.py | 17 +++++------------ openmc/deplete/operator.py | 11 +---------- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 5e2f1dc64..f1321379d 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -87,15 +87,13 @@ class TransportOperator(metaclass=ABCMeta): self.chain = Chain.from_xml(chain_file, fission_q) @abstractmethod - def __call__(self, vec, print_out=True): + def __call__(self, vec): """Runs a simulation. Parameters ---------- vec : list of numpy.ndarray Total atoms to be used in function. - print_out : bool, optional - Whether or not to print out time. Returns ------- diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/integrator/cram.py index f5da6d696..c57ff5313 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/integrator/cram.py @@ -14,7 +14,7 @@ import scipy.sparse.linalg as sla from .. import comm -def deplete(chain, x, rates, dt, print_out=True, matrix_func=None): +def deplete(chain, x, rates, dt, matrix_func=None): """Deplete materials using given reaction rates for a specified time Parameters @@ -27,29 +27,22 @@ def deplete(chain, x, rates, dt, print_out=True, matrix_func=None): Reaction rates (from transport operator) dt : float Time in [s] to deplete for - print_out : bool, optional - Whether to show elapsed time - maxtrix_func : function, optional - Function to form the depletion matrix + maxtrix_func : Callable, optional + Function of two variables: ``chain`` and ``rates``. + Expected to return the depletion matrix required by + :func:`CRAM48`. Returns ------- x_result : list of numpy.ndarray Updated atom number vectors for each material - """ - t_start = time.time() # Use multiprocessing pool to distribute work with Pool() as pool: iters = zip(repeat(chain), x, rates, repeat(dt), repeat(matrix_func)) x_result = list(pool.starmap(_cram_wrapper, iters)) - t_end = time.time() - if comm.rank == 0: - if print_out: - print("Time to matexp: ", t_end - t_start) - return x_result diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index e9c41093a..6ee992c4f 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -153,7 +153,7 @@ class Operator(TransportOperator): self.reaction_rates = ReactionRates( self.local_mats, self._burnable_nucs, self.chain.reactions) - def __call__(self, vec, power, print_out=True): + def __call__(self, vec, power): """Runs a simulation. Parameters @@ -162,8 +162,6 @@ class Operator(TransportOperator): Total atoms to be used in function. power : float Power of the reactor in [W] - print_out : bool, optional - Whether or not to print out time. Returns ------- @@ -192,13 +190,6 @@ class Operator(TransportOperator): # Extract results op_result = self._unpack_tallies_and_normalize(power) - if comm.rank == 0: - time_unpack = time.time() - - if print_out: - print("Time to openmc: ", time_openmc - time_start) - print("Time to unpack: ", time_unpack - time_openmc) - return copy.deepcopy(op_result) def _differentiate_burnable_mats(self): From 51add8f6e9ec4e28f025e5f17940a44c8d92f64e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 09:25:15 -0500 Subject: [PATCH 19/32] Clean up changes introduced by Integrators Remove some unused imports, redeclared __init__, long lines, etc. --- openmc/deplete/integrator/abc.py | 17 ++++++++--------- openmc/deplete/integrator/cecm.py | 2 -- openmc/deplete/integrator/predictor.py | 2 -- openmc/deplete/integrator/si_celi.py | 10 ++++------ openmc/deplete/integrator/si_leqi.py | 4 +--- tests/unit_tests/test_deplete_integrator.py | 6 ++++-- 6 files changed, 17 insertions(+), 24 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index ca21f2a1b..585c96bdb 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -101,7 +101,7 @@ class Integrator(ABC): # See Operator.__call__, Operator.initial_condition bos_conc = list(res.data[0]) rates = res.rates[0] - k = ufloat(res.k[0,0], res.k[0, 1]) + k = ufloat(res.k[0, 0], res.k[0, 1]) # Scale rates by ratio of powers rates *= step_power / res.power[0] @@ -110,7 +110,8 @@ class Integrator(ABC): def _get_start_data(self): if self.operator.prev_res is None: return 0.0, 0 - return self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - 1 + return (self.operator.prev_res[-1].time[-1], + len(self.operator.prev_res) - 1) def integrate(self): """Perform the entire depletion process across all steps""" @@ -181,14 +182,12 @@ class SI_Integrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. - n_stages : int, optional - Number of stages. Default : 10 - """ - - def __init__(self, operator, timesteps, power=None, power_density=None, - n_stages=10): + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Default : 10 + """ super().__init__(operator, timesteps, power, power_density) - self.n_stages = n_stages + self.n_steps = n_steps def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): reset_particles = False diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index b9b7dedc6..571c332cb 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -1,7 +1,5 @@ """The CE/CM integrator.""" -from textwrap import dedent - from .abc import Integrator from .cram import timed_deplete diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 0e4dce423..7234b375a 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -1,7 +1,5 @@ """First-order predictor algorithm.""" -from copy import deepcopy - from .abc import Integrator from .cram import timed_deplete diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index dde3cfb81..12021e870 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -2,20 +2,18 @@ import copy -from uncertainties import ufloat - from .abc import SI_Integrator from .cram import timed_deplete from .celi import _celi_f1, _celi_f2 from ..abc import OperatorResult -from ..results import Results class SI_CELI_Integrator(SI_Integrator): r"""Deplete using the si-ce/li cfq4 algorithm. - Implements the stochastic implicit ce/li predictor-corrector algorithm using - the `fourth order commutator-free integrator `_. + Implements the stochastic implicit ce/li predictor-corrector algorithm + using the `fourth order commutator-free integrator + `_. detailed algorithm can be found in section 3.2 in `colin josey's thesis `_. @@ -52,7 +50,7 @@ class SI_CELI_Integrator(SI_Integrator): inter_conc = copy.deepcopy(eos_conc) # Begin iteration - for j in range(self.n_stages + 1): + for j in range(self.n_steps + 1): inter_res = self.operator(inter_conc, power) if j <= 1: diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 015650269..88e723b10 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -4,8 +4,6 @@ import copy from collections.abc import Iterable from itertools import repeat -from uncertainties import ufloat - from .abc import SI_Integrator from .si_celi import SI_CELI_Integrator from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 @@ -75,7 +73,7 @@ class SI_LEQI_Integrator(SI_Integrator): proc_time += time1 inter_conc = copy.deepcopy(eos_conc) - for j in range(self.n_stages + 1): + for j in range(self.n_steps + 1): inter_res = self.operator(inter_conc, power) if j <= 1: diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index c50ac66a9..1750abd3d 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -76,8 +76,10 @@ def test_results_save(run_in_tmpdir): t1 = [0.0, 1.0] t2 = [1.0, 2.0] - op_result1 = [OperatorResult(ufloat(*k), rates) for k, rates in zip(eigvl1, rate1)] - op_result2 = [OperatorResult(ufloat(*k), rates) for k, rates in zip(eigvl2, rate2)] + op_result1 = [OperatorResult(ufloat(*k), rates) + for k, rates in zip(eigvl1, rate1)] + op_result2 = [OperatorResult(ufloat(*k), rates) + for k, rates in zip(eigvl2, rate2)] Results.save(op, x1, op_result1, t1, 0, 0) Results.save(op, x2, op_result2, t2, 0, 1) From a02feed171d9e59c908fab4bc725d0fd3d6dfedd Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 14:03:30 -0500 Subject: [PATCH 20/32] Restrict depletion restart to schemes with common stages Each scheme is capable of different amounts of concentration and reaction data, depending on the number of intermediate transport solutions of interest. The PredictorIntegrator stores data from a single solution, while the CF4Integrator and EPC_RK4_Integrator store four stages. This discrepancy can cause issues when saving data in the depletion output file, as either some data is calculated and thrown away [more stages going to fewer stages] or excess space is not utilized [fewer stages going in to a restart that previously used more stages]. Each subclass of Integrator is expected to declare a _N_STAGES attribute that provides information on the number of stages expected. Iterative schemes like SI_CELI_Integrator only report two stages, for the initial concentration and first prediction, rather than at each iteration point. Logic that checks for consistent sizes is included in the initialization of the Integrators, and has been removed from Results.save --- openmc/deplete/integrator/abc.py | 18 +++++++++ openmc/deplete/integrator/cecm.py | 1 + openmc/deplete/integrator/celi.py | 1 + openmc/deplete/integrator/cf4.py | 1 + openmc/deplete/integrator/epc_rk4.py | 1 + openmc/deplete/integrator/leqi.py | 1 + openmc/deplete/integrator/predictor.py | 1 + openmc/deplete/integrator/si_celi.py | 2 + openmc/deplete/integrator/si_leqi.py | 1 + openmc/deplete/results.py | 11 +----- tests/unit_tests/test_deplete_restart.py | 49 ++++-------------------- 11 files changed, 36 insertions(+), 51 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 585c96bdb..cb4ac13a2 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -34,6 +34,16 @@ class Integrator(ABC): initial heavy metal inventory to get total power if ``power`` is not speficied. """ + # Check number of stages previously used + if operator.prev_res is not None: + res = operator.prev_res[-1] + if res.data.shape[0] != self._N_STAGES: + raise ValueError( + "{} incompatible with previous restart calculation. " + "Previous scheme used {} intermediate solutions, while " + "this uses {}".format( + self.__class__.__name__, res.data.shape[0], + self._N_STAGES)) self.operator = operator self.chain = operator.chain if not isinstance(timesteps, Iterable): @@ -84,6 +94,14 @@ class Integrator(ABC): simulations """ + @property + @abstractmethod + def _N_STAGES(self): + """Number of intermediate transport solutions + + Needed to ensure schemes are consistent with restarts + """ + def __iter__(self): return zip(self.timesteps, self.power) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 571c332cb..8155b55de 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -22,6 +22,7 @@ class CECMIntegrator(Integrator): y_{n+1} &= \text{expm}(A_c h) y_n \end{aligned} """ + _N_STAGES = 2 def __call__(self, conc, rates, dt, power, _i=-1): """Integrate using CE/CM diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 9346347a3..b6bbe231b 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -23,6 +23,7 @@ class CELIIntegrator(Integrator): \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n \end{aligned} """ + _N_STAGES = 2 def __call__(self, bos_conc, rates, dt, power, _i=-1): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index 7e0b603ae..96c97b067 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -28,6 +28,7 @@ class CF4Integrator(Integrator): \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 \end{aligned} """ + _N_STAGES = 4 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index ce6223c44..879f176ba 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -23,6 +23,7 @@ class EPC_RK4_Integrator(Integrator): y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 \end{aligned} """ + _N_STAGES = 4 def __call__(self, conc, rates, dt, power, _i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 4bf4cf4d9..c4a819d38 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -37,6 +37,7 @@ class LEQIIntegrator(Integrator): It is initialized using the CE/LI algorithm. """ + _N_STAGES = 2 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 7234b375a..d534645b2 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -37,6 +37,7 @@ class PredictorIntegrator(Integrator): initial heavy metal inventory to get total power if ``power`` is not speficied. """ + _N_STAGES = 1 def __call__(self, conc, rates, dt, power, _i=None): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 12021e870..bfe4f7586 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -18,6 +18,8 @@ class SI_CELI_Integrator(SI_Integrator): detailed algorithm can be found in section 3.2 in `colin josey's thesis `_. """ + _N_STAGES = 2 + def __call__(self, bos_conc, bos_rates, dt, power, _i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 88e723b10..702b098ef 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -21,6 +21,7 @@ class SI_LEQI_Integrator(SI_Integrator): Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis `_. """ + _N_STAGES = 2 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index a2462b80a..2b5bde0e5 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -425,16 +425,7 @@ class Results(object): # Get indexing terms vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info() - # For a restart calculation, limit number of stages saved to meet the - # format of the hdf5 file stages = len(x) - offset = 0 - if op.prev_res is not None and op.prev_res[0].n_stages < stages: - offset = stages - op.prev_res[0].n_stages - stages = min(stages, op.prev_res[0].n_stages) - warn("Number of restart integrator stages saved limited by initial" - " depletion integrator choice to {}" - .format(op.prev_res[0].n_stages)) # Create results results = Results() @@ -444,7 +435,7 @@ class Results(object): for i in range(stages): for mat_i in range(n_mat): - results[i, mat_i, :] = x[offset + i][mat_i][:] + results[i, mat_i, :] = x[i][mat_i] results.k = [(r.k.nominal_value, r.k.std_dev) for r in op_results] results.rates = [r.rates for r in op_results] diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 493828fd8..847ad9b83 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -4,7 +4,7 @@ These tests run in two steps, a first run then a restart run, a simple test problem described in dummy_geometry.py. """ -from pytest import approx +from pytest import approx, raises import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, @@ -95,8 +95,7 @@ def test_restart_cecm(run_in_tmpdir): def test_restart_predictor_cecm(run_in_tmpdir): - """Integral regression test of integrator algorithm using predictor - for the first run then CE/CM for the restart run.""" + """Test to ensure that schemes with different stages are not compatible""" op = dummy_operator.DummyOperator() output_dir = "test_restart_predictor_cecm" @@ -114,25 +113,9 @@ def test_restart_predictor_cecm(run_in_tmpdir): op = dummy_operator.DummyOperator(prev_res) op.output_dir = output_dir - # Perform restarts simulation using the MCNPX/MCNP6 algorithm - cecm = CECMIntegrator(op, dt, power) - cecm.integrate() - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Test solution - s1 = [2.46847546272295, 0.986431226850467] - s2 = [3.09106948392, 0.607102912398] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) + # check ValueError is raised, indicating previous and current stages + with raises(ValueError, match="incompatible.* 1.*2"): + CECMIntegrator(op, dt, power) def test_restart_cecm_predictor(run_in_tmpdir): @@ -156,25 +139,9 @@ def test_restart_cecm_predictor(run_in_tmpdir): op = dummy_operator.DummyOperator(prev_res) op.output_dir = output_dir - # Perform restarts simulation using the predictor algorithm - PredictorIntegrator(op, dt, power).integrate() - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Test solution - s1 = [1.86872629872102, 1.395525772416039] - s2 = [3.32776806576, 2.391425905] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - + # check ValueError is raised, indicating previous and current stages + with raises(ValueError, match="incompatible.* 2.*1"): + PredictorIntegrator(op, dt, power) def test_restart_cf4(run_in_tmpdir): """Integral regression test of integrator algorithm using CF4.""" From 2ee14a4a0f1bfb12302d1b5a03127f4f6d1bf08e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 15:02:55 -0500 Subject: [PATCH 21/32] Write new step-specific statepoint files at BOS, EOL Closes #1283 by writing a statepoint file at the beginning of every depletion step [BOS] and at the final transport simulation, [EOL]. These files are named "openmc_simulation_n.h5", where represents the current depletion step, including restart steps. The new files are written using the C API. It is worth noting that OpenMC will still write statepoint files after each intermediate transport simulation used by various depletion schemes. This commit makes no effort to move or delete these files, nor stop their creation. It is not clear how, using the python nor C API, one can stop OpenMC from writing a statepoint file. The source bank is not written to these files as this causes a segmentation fault. Comparisons of k_combined pull from all statepoints, reference depletion_results file, and test depletion_results file have been added to depletion regression test. --- openmc/deplete/integrator/abc.py | 15 +++++++++++---- tests/regression_tests/deplete/test.py | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index cb4ac13a2..94ed2a86c 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -4,6 +4,7 @@ from collections.abc import Iterable from uncertainties import ufloat +from openmc.capi import statepoint_write from openmc.deplete import Results, OperatorResult @@ -111,6 +112,7 @@ class Integrator(ABC): def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): x = deepcopy(bos_conc) res = self.operator(x, step_power) + self._write_statepoint(step_index) return x, res def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): @@ -160,17 +162,21 @@ class Integrator(ABC): res_list = [self.operator(conc, p)] self._save_results( [conc], res_list, [t, t], p, self._ires + len(self)) + self._write_statepoint(len(self)) def _save_results(self, conc_list, results_list, time_list, power, index, proc_time=None): - """Save the results at the end of of one step - - Abstracted to support the predictor's unique save location - """ + """Save the results at the end of of one step""" Results.save( self.operator, conc_list, results_list, time_list, power, index, proc_time) + def _write_statepoint(self, step_index): + """Use capi to write a statepoint for this index""" + statepoint_write( + "openmc_simulation_n{}.h5".format(step_index + self._ires), + write_source=False) + class SI_Integrator(Integrator): """Abstract for the Stochastic Implicit Euler integrators @@ -252,3 +258,4 @@ class SI_Integrator(Integrator): # No final simulation for SIE, use last iteration results self._save_results( [conc], [res_list[-1]], [t, t], p, self._ires + len(self)) + self._write_statepoint(len(self)) diff --git a/tests/regression_tests/deplete/test.py b/tests/regression_tests/deplete/test.py index 5f2d8cc45..59eca4a54 100644 --- a/tests/regression_tests/deplete/test.py +++ b/tests/regression_tests/deplete/test.py @@ -39,7 +39,7 @@ def test_full(run_in_tmpdir): space = openmc.stats.Box(lower_left, upper_right) settings.source = openmc.Source(space=space) settings.seed = 1 - settings.verbosity = 3 + settings.verbosity = 1 # Create operator chain_file = Path(__file__).parents[2] / 'chain_simple.xml' @@ -101,3 +101,18 @@ def test_full(run_in_tmpdir): assert correct, "Discrepancy in mat {} and nuc {}\n{}\n{}".format( mat, nuc, y_old, y_test) + + # Compare statepoint files with depletion results + + t_test, k_test = res_test.get_eigenvalue() + t_ref, k_ref = res_ref.get_eigenvalue() + k_state = np.empty_like(k_ref) + + # Get statepoint files for all BOS points and EOL + for n in range(N + 1): + statepoint = openmc.StatePoint("openmc_simulation_n{}.h5".format(n)) + k_n = statepoint.k_combined + k_state[n] = [k_n.nominal_value, k_n.std_dev] + # Look for exact match pulling from statepoint and depletion_results + assert np.all(k_state == k_test) + assert np.allclose(k_test, k_ref) From b1ea5b89421f0793e8c61defaecd1674750d160a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 15:56:24 -0500 Subject: [PATCH 22/32] Add type and value checking to Integrators - SI_Integrator must be passed positive integer. - Number of powers computed must be equal to the number of time steps Added unit test to ensure that the right errors are raised. --- openmc/deplete/integrator/abc.py | 11 +++++-- tests/unit_tests/test_deplete_integrator.py | 35 +++++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 94ed2a86c..26ca6b581 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -1,9 +1,11 @@ from copy import deepcopy from abc import ABC, abstractmethod from collections.abc import Iterable +from numbers import Integral from uncertainties import ufloat +from openmc.checkvalue import check_type, check_greater_than from openmc.capi import statepoint_write from openmc.deplete import Results, OperatorResult @@ -60,9 +62,12 @@ class Integrator(ABC): power = [p * operator.heavy_metal for p in power_density] if not isinstance(power, Iterable): - # TODO Maybe use itertools.zip_longest? # Ensure that power is single value if that is the case power = [power] * len(self.timesteps) + elif len(power) != len(self.timesteps): + raise ValueError( + "Number of time steps != number of powers. {} vs {}".format( + len(self.timesteps), len(power))) self.power = power @@ -208,8 +213,10 @@ class SI_Integrator(Integrator): is not speficied. n_steps : int, optional Number of stochastic iterations per depletion interval. - Default : 10 + Must be greater than zero. Default : 10 """ + check_type("n_steps", n_steps, Integral) + check_greater_than("n_steps", n_steps, 0) super().__init__(operator, timesteps, power, power_density) self.n_steps = n_steps diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 1750abd3d..69f6ee679 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -12,9 +12,11 @@ from unittest.mock import MagicMock import numpy as np from uncertainties import ufloat +import pytest -from openmc.deplete import (ReactionRates, Results, ResultsList, comm, - OperatorResult) +from openmc.deplete import ( + ReactionRates, Results, ResultsList, comm, OperatorResult, + PredictorIntegrator, SI_CELI_Integrator) def test_results_save(run_in_tmpdir): @@ -99,3 +101,32 @@ def test_results_save(run_in_tmpdir): np.testing.assert_array_equal(res[1].k, eigvl2) np.testing.assert_array_equal(res[1].time, t2) + + +@pytest.mark.parametrize("timesteps", (1, [1])) +def test_bad_integrator_inputs(timesteps): + """Test failure modes for Integrator inputs""" + + op = MagicMock() + op.prev_res = None + op.chain = None + op.heavy_metal = 1.0 + + # No power nor power density given + with pytest.raises(ValueError, match="Either power or power density"): + PredictorIntegrator(op, timesteps) + + # Length of power != length time + with pytest.raises(ValueError, match="number of powers"): + PredictorIntegrator(op, timesteps, power=[1, 2]) + + # Length of power density != length time + with pytest.raises(ValueError, match="number of powers"): + PredictorIntegrator(op, timesteps, power_density=[1, 2]) + + # SI integrator with bad steps + with pytest.raises(TypeError, match="n_steps"): + SI_CELI_Integrator(op, timesteps, [1], n_steps=2.5) + + with pytest.raises(ValueError, match="n_steps"): + SI_CELI_Integrator(op, timesteps, [1], n_steps=0) From a7e1bca6da7cb4fc4baeb186870b38df06b8ffb3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 16:26:23 -0500 Subject: [PATCH 23/32] Apply suggestions from code review Co-Authored-By: Paul Romano --- openmc/deplete/integrator/abc.py | 6 +++--- openmc/deplete/integrator/cecm.py | 2 +- openmc/deplete/integrator/celi.py | 6 +++--- openmc/deplete/integrator/cf4.py | 4 ++-- openmc/deplete/integrator/epc_rk4.py | 4 ++-- openmc/deplete/integrator/leqi.py | 4 ++-- openmc/deplete/integrator/predictor.py | 4 ++-- openmc/deplete/integrator/si_celi.py | 4 ++-- openmc/deplete/integrator/si_leqi.py | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 26ca6b581..52061de0f 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -84,14 +84,14 @@ class Integrator(ABC): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] conc_list : list of numpy.ndarray Concentrations at each of the intermediate points with the final concentration as the last element @@ -177,7 +177,7 @@ class Integrator(ABC): power, index, proc_time) def _write_statepoint(self, step_index): - """Use capi to write a statepoint for this index""" + """Use C API to write a statepoint for this index""" statepoint_write( "openmc_simulation_n{}.h5".format(step_index + self._ires), write_source=False) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 8155b55de..54efff09a 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -43,7 +43,7 @@ class CECMIntegrator(Integrator): Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] conc_list : list of numpy.ndarray Concentrations at each of the intermediate points with the final concentration as the last element diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index b6bbe231b..137912b6c 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -37,14 +37,14 @@ class CELIIntegrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] _i : int Current iteration count. Not used Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] conc_list : list of numpy.ndarray Concentrations at each of the intermediate points with the final concentration as the last element @@ -56,7 +56,7 @@ class CELIIntegrator(Integrator): proc_time, conc_ce = timed_deplete(self.chain, bos_conc, rates, dt) res_ce = self.operator(conc_ce, power) - # deplete using two matrix exponeitials + # deplete using two matrix exponentials list_rates = list(zip(rates, res_ce.rates)) time_le1, conc_inter = timed_deplete( diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index 96c97b067..82a2dec8b 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -42,14 +42,14 @@ class CF4Integrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] conc_list : list of numpy.ndarray Concentrations at each of the intermediate points with the final concentration as the last element diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index 879f176ba..00694ac9e 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -37,14 +37,14 @@ class EPC_RK4_Integrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index, unused. Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] conc_list : list of numpy.ndarray Concentrations at each of the intermediate points with the final concentration as the last element diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index c4a819d38..5cfcd0684 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -51,14 +51,14 @@ class LEQIIntegrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] conc_list : list of numpy.ndarray Concentrations at each of the intermediate points with the final concentration as the last element diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index d534645b2..038e52fc9 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -51,14 +51,14 @@ class PredictorIntegrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] _i : int or None Iteration index. Not used Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] conc_list : list of numpy.ndarray Concentrations at end of interval op_results : empty list diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index bfe4f7586..a0fa3cae2 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -32,14 +32,14 @@ class SI_CELI_Integrator(SI_Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] _i : int Current depletion step index. Unused Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] bos_conc_list : list of numpy.ndarray Concentrations at each of the intermediate points with the final bos_concentration as the last element diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 702b098ef..e63219f13 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -36,14 +36,14 @@ class SI_LEQI_Integrator(SI_Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index Returns ------- proc_time : float - Time spent in CRAM routines for all materials + Time spent in CRAM routines for all materials in [s] conc_list : list of numpy.ndarray Concentrations at each of the intermediate points with the final concentration as the last element From 49ba67d480f5f1935a8432088b2ec4ce7f992769 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 16:53:09 -0500 Subject: [PATCH 24/32] Use Results.save directly during Integrator.integrate Removed the private method for saving to avoid unnecessary redirection. Also renamed some private attributes: 1) Integrator._ires -> Integrator._i_res 2) Integrator._N_STAGES -> Integrator._num_stages --- openmc/deplete/integrator/abc.py | 37 ++++++++++---------------- openmc/deplete/integrator/cecm.py | 2 +- openmc/deplete/integrator/celi.py | 2 +- openmc/deplete/integrator/cf4.py | 2 +- openmc/deplete/integrator/epc_rk4.py | 2 +- openmc/deplete/integrator/leqi.py | 12 ++++----- openmc/deplete/integrator/predictor.py | 2 +- openmc/deplete/integrator/si_celi.py | 2 +- openmc/deplete/integrator/si_leqi.py | 12 ++++----- 9 files changed, 32 insertions(+), 41 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 52061de0f..30d6e2da4 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -40,13 +40,13 @@ class Integrator(ABC): # Check number of stages previously used if operator.prev_res is not None: res = operator.prev_res[-1] - if res.data.shape[0] != self._N_STAGES: + if res.data.shape[0] != self._num_stages: raise ValueError( "{} incompatible with previous restart calculation. " "Previous scheme used {} intermediate solutions, while " "this uses {}".format( self.__class__.__name__, res.data.shape[0], - self._N_STAGES)) + self._num_stages)) self.operator = operator self.chain = operator.chain if not isinstance(timesteps, Iterable): @@ -102,7 +102,7 @@ class Integrator(ABC): @property @abstractmethod - def _N_STAGES(self): + def _num_stages(self): """Number of intermediate transport solutions Needed to ensure schemes are consistent with restarts @@ -141,7 +141,7 @@ class Integrator(ABC): def integrate(self): """Perform the entire depletion process across all steps""" with self.operator as conc: - t, self._ires = self._get_start_data() + t, self._i_res = self._get_start_data() for i, (dt, p) in enumerate(self): if i > 0 or self.operator.prev_res is None: @@ -157,29 +157,21 @@ class Integrator(ABC): # Remove actual EOS concentration for next step conc = conc_list.pop() - self._save_results( - conc_list, res_list, [t, t + dt], p, self._ires + i, - proc_time) + Results.save(self.operator, conc_list, res_list, [t, t + dt], + p, self._i_res + i, proc_time) t += dt # Final simulation res_list = [self.operator(conc, p)] - self._save_results( - [conc], res_list, [t, t], p, self._ires + len(self)) + Results.save(self.operator, [conc], res_list, [t, t], + p, self._i_res + len(self), proc_time) self._write_statepoint(len(self)) - def _save_results(self, conc_list, results_list, time_list, power, - index, proc_time=None): - """Save the results at the end of of one step""" - Results.save( - self.operator, conc_list, results_list, time_list, - power, index, proc_time) - def _write_statepoint(self, step_index): """Use C API to write a statepoint for this index""" statepoint_write( - "openmc_simulation_n{}.h5".format(step_index + self._ires), + "openmc_simulation_n{}.h5".format(step_index + self._i_res), write_source=False) @@ -234,7 +226,7 @@ class SI_Integrator(Integrator): def integrate(self): """Perform the entire depletion process across all steps""" with self.operator as conc: - t, self._ires = self._get_start_data() + t, self._i_res = self._get_start_data() for i, (dt, p) in enumerate(self): if i == 0: @@ -256,13 +248,12 @@ class SI_Integrator(Integrator): # Remove actual EOS concentration for next step conc = conc_list.pop() - self._save_results( - conc_list, res_list, [t, t + dt], p, self._ires + i, - proc_time) + Results.save(self.operator, conc_list, res_list, [t, t + dt], + p, self._i_res + i, proc_time) t += dt # No final simulation for SIE, use last iteration results - self._save_results( - [conc], [res_list[-1]], [t, t], p, self._ires + len(self)) + Results.save(self.operator, [conc], [res_list[-1]], [t, t], + p, self._i_res + len(self), proc_time) self._write_statepoint(len(self)) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 54efff09a..647acfe0c 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -22,7 +22,7 @@ class CECMIntegrator(Integrator): y_{n+1} &= \text{expm}(A_c h) y_n \end{aligned} """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, conc, rates, dt, power, _i=-1): """Integrate using CE/CM diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 137912b6c..6935f9f6d 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -23,7 +23,7 @@ class CELIIntegrator(Integrator): \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n \end{aligned} """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, bos_conc, rates, dt, power, _i=-1): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index 82a2dec8b..a88a6788c 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -28,7 +28,7 @@ class CF4Integrator(Integrator): \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 \end{aligned} """ - _N_STAGES = 4 + _num_stages = 4 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index 00694ac9e..c8afe8784 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -23,7 +23,7 @@ class EPC_RK4_Integrator(Integrator): y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 \end{aligned} """ - _N_STAGES = 4 + _num_stages = 4 def __call__(self, conc, rates, dt, power, _i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 5cfcd0684..551514078 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -37,7 +37,7 @@ class LEQIIntegrator(Integrator): It is initialized using the CE/LI algorithm. """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step @@ -67,21 +67,21 @@ class LEQIIntegrator(Integrator): simulation """ if i == 0: - if self._ires < 1: # need at least previous transport solution + if self._i_res < 1: # need at least previous transport solution self._prev_rates = bos_rates return CELIIntegrator.__call__( self, bos_conc, bos_rates, dt, power, i) prev_res = self.operator.prev_res[-2] - prevdt = self.timesteps[i] - prev_res.time[0] + prev_dt = self.timesteps[i] - prev_res.time[0] self._prev_rates = prev_res.rates[0] else: - prevdt = self.timesteps[i - 1] + prev_dt = self.timesteps[i - 1] # Remaining LE/QI bos_res = self.operator(bos_conc, power) le_inputs = list(zip( - self._prev_rates, bos_res.rates, repeat(prevdt), repeat(dt))) + self._prev_rates, bos_res.rates, repeat(prev_dt), repeat(dt))) time1, conc_inter = timed_deplete( self.chain, bos_conc, le_inputs, dt, matrix_func=_leqi_f1) @@ -92,7 +92,7 @@ class LEQIIntegrator(Integrator): qi_inputs = list(zip( self._prev_rates, bos_res.rates, res_inter.rates, - repeat(prevdt), repeat(dt))) + repeat(prev_dt), repeat(dt))) time3, conc_inter = timed_deplete( self.chain, bos_conc, qi_inputs, dt, matrix_func=_leqi_f3) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 038e52fc9..f57bac79c 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -37,7 +37,7 @@ class PredictorIntegrator(Integrator): initial heavy metal inventory to get total power if ``power`` is not speficied. """ - _N_STAGES = 1 + _num_stages = 1 def __call__(self, conc, rates, dt, power, _i=None): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index a0fa3cae2..14ff65bf4 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -18,7 +18,7 @@ class SI_CELI_Integrator(SI_Integrator): detailed algorithm can be found in section 3.2 in `colin josey's thesis `_. """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, bos_conc, bos_rates, dt, power, _i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index e63219f13..384b1499a 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -21,7 +21,7 @@ class SI_LEQI_Integrator(SI_Integrator): Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis `_. """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step @@ -52,20 +52,20 @@ class SI_LEQI_Integrator(SI_Integrator): simulation """ if i == 0: - if self._ires < 1: + if self._i_res < 1: self._prev_rates = bos_rates # Perform CELI for initial steps return SI_CELI_Integrator.__call__( self, bos_conc, bos_rates, dt, power, i) prev_res = self.operator.prev_res[-2] - prevdt = self.timesteps[i] - prev_res.time[0] + prev_dt = self.timesteps[i] - prev_res.time[0] self._prev_rates = prev_res.rates[0] else: - prevdt = self.timesteps[i - 1] + prev_dt = self.timesteps[i - 1] # Perform remaining LE/QI inputs = list(zip(self._prev_rates, bos_rates, - repeat(prevdt), repeat(dt))) + repeat(prev_dt), repeat(dt))) proc_time, inter_conc = timed_deplete( self.chain, bos_conc, inputs, dt, matrix_func=_leqi_f1) time1, eos_conc = timed_deplete( @@ -85,7 +85,7 @@ class SI_LEQI_Integrator(SI_Integrator): res_bar = OperatorResult(k, rates) inputs = list(zip(self._prev_rates, bos_rates, res_bar.rates, - repeat(prevdt), repeat(dt))) + repeat(prev_dt), repeat(dt))) time1, inter_conc = timed_deplete( self.chain, bos_conc, inputs, dt, matrix_func=_leqi_f3) time2, inter_conc = timed_deplete( From 78943d1086f2e77e028356cb1a3fdc5fd13d7cb1 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 16:57:21 -0500 Subject: [PATCH 25/32] Add power as argument to TransportOperator.__call__ --- openmc/deplete/abc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 770a482ab..25c45b75c 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -106,13 +106,15 @@ class TransportOperator(ABC): self._dilute_initial = value @abstractmethod - def __call__(self, vec): + def __call__(self, vec, power): """Runs a simulation. Parameters ---------- vec : list of numpy.ndarray Total atoms to be used in function. + power : float + Power of the reactor in [W] Returns ------- From e286940f8eb3e86ac318fdfe146eec6e23deed9a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 17:00:41 -0500 Subject: [PATCH 26/32] Pass prev_res onto TransportOperator.__init__ Document that this attribute will either be a ResultsList or None, both on TransportOperator and Operator. --- openmc/deplete/abc.py | 19 +++++++++++++------ openmc/deplete/operator.py | 16 ++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 25c45b75c..f074864c6 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -17,6 +17,7 @@ from numpy import nonzero, empty from openmc.data import DataLibrary, JOULE_PER_EV from openmc.checkvalue import check_type, check_greater_than from .chain import Chain +from .results_list import ResultsList OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) OperatorResult.__doc__ = """\ @@ -61,6 +62,8 @@ class TransportOperator(ABC): in initial condition to ensure they exist in the decay chain. Only done for nuclides with reaction rates. Defaults to 1.0e3. + prev_results : ResultsList, optional + Results from a previous depletion calculation. Attributes ---------- @@ -68,8 +71,12 @@ class TransportOperator(ABC): Initial atom density [atoms/cm^3] to add for nuclides that are zero in initial condition to ensure they exist in the decay chain. Only done for nuclides with reaction rates. + prev_res : ResultsList or None + Results from a previous depletion calculation. ``None`` if no + results are to be used. """ - def __init__(self, chain_file=None, fission_q=None, dilute_initial=1.0e3): + def __init__(self, chain_file=None, fission_q=None, dilute_initial=1.0e3, + prev_results=None): self.dilute_initial = dilute_initial self.output_dir = '.' @@ -93,6 +100,11 @@ class TransportOperator(ABC): "of adding depletion_chain to OPENMC_CROSS_SECTIONS", FutureWarning) self.chain = Chain.from_xml(chain_file, fission_q) + if prev_results is None: + self.prev_res = None + else: + check_type("previous results", prev_results, ResultsList) + self.prev_results = prev_res @property def dilute_initial(self): @@ -122,7 +134,6 @@ class TransportOperator(ABC): Eigenvalue and reaction rates resulting from transport operator """ - pass def __enter__(self): # Save current directory and move to specific output directory @@ -157,8 +168,6 @@ class TransportOperator(ABC): Total density for initial conditions. """ - pass - @abstractmethod def get_results_info(self): """Returns volume list, cell lists, and nuc lists. @@ -175,8 +184,6 @@ class TransportOperator(ABC): All burnable materials in the geometry. """ - pass - def finalize(self): pass diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index f60a4a2d4..f3c432a34 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -112,28 +112,24 @@ class Operator(TransportOperator): Initial heavy metal inventory local_mats : list of str All burnable material IDs being managed by a single process - prev_res : ResultsList - Results from a previous depletion calculation + prev_res : ResultsList or None + Results from a previous depletion calculation. ``None`` if no + results are to be used. diff_burnable_mats : bool Whether to differentiate burnable materials with multiple instances """ def __init__(self, geometry, settings, chain_file=None, prev_results=None, diff_burnable_mats=False, fission_q=None, dilute_initial=1.0e3): - super().__init__(chain_file, fission_q, dilute_initial) + super().__init__(chain_file, fission_q, dilute_initial, prev_results) self.round_number = False self.settings = settings self.geometry = geometry self.diff_burnable_mats = diff_burnable_mats - if prev_results is not None: + if self.prev_res is not None: # Reload volumes into geometry - prev_results[-1].transfer_volumes(geometry) - - # Store previous results in operator - self.prev_res = prev_results - else: - self.prev_res = None + self.prev_results[-1].transfer_volumes(geometry) # Differentiate burnable materials with multiple instances if self.diff_burnable_mats: From a4905c03af8842e0c58483d15678f3399fe74d7a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 17:36:22 -0500 Subject: [PATCH 27/32] Add TransportOperator.write_bos_data abstract method Called with a single integer corresponding to the current depletion step. This method is intended to document the current beginning-of-step calculations prior to any depletion at each step and at the final simulation. The Operator uses this method to write a statepoint using the C API. Also rename the Integrator _get_bos_data_from_openmc to _get_bos_data_from_operator for generality. It is here that the write_bos_data method is called. --- openmc/deplete/abc.py | 13 ++++++++++++ openmc/deplete/integrator/abc.py | 25 +++++++++-------------- openmc/deplete/operator.py | 13 ++++++++++++ tests/dummy_operator.py | 3 +++ tests/unit_tests/test_deplete_operator.py | 14 +++++++++---- 5 files changed, 49 insertions(+), 19 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index f074864c6..8e77a8ff0 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -187,6 +187,19 @@ class TransportOperator(ABC): def finalize(self): pass + @abstractmethod + def write_bos_data(self, step): + """Document beginning of step data for a given step + + Called at the beginning of a depletion step and at + the final point in the simulation. + + Parameters + ---------- + step : int + Current depletion step including restarts + """ + class ReactionRateHelper(ABC): """Abstract class for generating reaction rates for operators diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 30d6e2da4..2ed2634ee 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -114,16 +114,17 @@ class Integrator(ABC): def __len__(self): return len(self.timesteps) - def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): + def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): + """Get beginning of step concentrations, reaction rates from Operator""" x = deepcopy(bos_conc) res = self.operator(x, step_power) - self._write_statepoint(step_index) + self.operator.write_bos_data(step_index + self._i_res) return x, res def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): + """Get beginning of step concentrations, reaction rates from restart""" res = self.operator.prev_res[-1] # Depletion methods expect list of arrays - # See Operator.__call__, Operator.initial_condition bos_conc = list(res.data[0]) rates = res.rates[0] k = ufloat(res.k[0, 0], res.k[0, 1]) @@ -145,7 +146,7 @@ class Integrator(ABC): for i, (dt, p) in enumerate(self): if i > 0 or self.operator.prev_res is None: - conc, res = self._get_bos_data_from_openmc(i, p, conc) + conc, res = self._get_bos_data_from_operator(i, p, conc) else: conc, res = self._get_bos_data_from_restart(i, p, conc) proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) @@ -166,13 +167,7 @@ class Integrator(ABC): res_list = [self.operator(conc, p)] Results.save(self.operator, [conc], res_list, [t, t], p, self._i_res + len(self), proc_time) - self._write_statepoint(len(self)) - - def _write_statepoint(self, step_index): - """Use C API to write a statepoint for this index""" - statepoint_write( - "openmc_simulation_n{}.h5".format(step_index + self._i_res), - write_source=False) + self.operator.write_bos_data(len(self) + self._i_res) class SI_Integrator(Integrator): @@ -212,12 +207,12 @@ class SI_Integrator(Integrator): super().__init__(operator, timesteps, power, power_density) self.n_steps = n_steps - def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): + def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): reset_particles = False if step_index == 0 and hasattr(self.operator, "settings"): reset_particles = True self.operator.settings.particles *= self.n_stages - inherited = super()._get_bos_data_from_openmc( + inherited = super()._get_bos_data_from_operator( step_index, step_power, bos_conc) if reset_particles: self.operator.settings.particles //= self.n_stages @@ -231,7 +226,7 @@ class SI_Integrator(Integrator): for i, (dt, p) in enumerate(self): if i == 0: if self.operator.prev_res is None: - conc, res = self._get_bos_data_from_openmc(i, p, conc) + conc, res = self._get_bos_data_from_operator(i, p, conc) else: conc, res = self._get_bos_data_from_restart(i, p, conc) else: @@ -256,4 +251,4 @@ class SI_Integrator(Integrator): # No final simulation for SIE, use last iteration results Results.save(self.operator, [conc], [res_list[-1]], [t, t], p, self._i_res + len(self), proc_time) - self._write_statepoint(len(self)) + self.operator.write_bos_data(self._i_res + len(self)) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index f3c432a34..b845faad9 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -204,6 +204,19 @@ class Operator(TransportOperator): return copy.deepcopy(op_result) + @staticmethod + def write_bos_data(step): + """Write a state-point file with beginning of step data + + Parameters + ---------- + step : int + Current depletion step including restarts + """ + openmc.capi.statepoint_write( + "openmc_simulation_n{}.h5".format(step), + write_source=False) + def _differentiate_burnable_mats(self): """Assign distribmats for each burnable material diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index 23dc22288..c23abef63 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -116,6 +116,9 @@ class DummyOperator(TransportOperator): """Maps cell name to index in global geometry.""" return self.local_mats + @staticmethod + def write_bos_data(_step): + """Empty method but avoids calls to C API""" @property def reaction_rates(self): diff --git a/tests/unit_tests/test_deplete_operator.py b/tests/unit_tests/test_deplete_operator.py index 45c2aada4..3574d293d 100644 --- a/tests/unit_tests/test_deplete_operator.py +++ b/tests/unit_tests/test_deplete_operator.py @@ -37,14 +37,20 @@ def bare_xs(run_in_tmpdir): class BareDepleteOperator(TransportOperator): """Very basic class for testing the initialization.""" - # declare abstract methods so object can be created - def __call__(self, *args, **kwargs): + @staticmethod + def __call__(*args, **kwargs): pass - def initial_condition(self): + @staticmethod + def initial_condition(): pass - def get_results_info(self): + @staticmethod + def get_results_info(): + pass + + @staticmethod + def write_bos_data(): pass From 1bd663daf67897c828c7a609fb3209b82c87d839 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 17:54:12 -0500 Subject: [PATCH 28/32] Rename integrator classes to be PEP8 complaint Changes: - EPC_RK4_Integrator -> EPCRK4Integrator - SI_Integrator -> SIIntegrator - SI_CELI_Integrator -> SICELIIntegrator - SI_LEQI_Integrator -> SICELIIntegrator --- docs/source/pythonapi/deplete.rst | 6 +++--- openmc/deplete/integrator/abc.py | 2 +- openmc/deplete/integrator/epc_rk4.py | 2 +- openmc/deplete/integrator/si_celi.py | 10 +++++----- openmc/deplete/integrator/si_leqi.py | 10 ++++------ tests/unit_tests/test_deplete_epc_rk4.py | 4 ++-- tests/unit_tests/test_deplete_integrator.py | 6 +++--- tests/unit_tests/test_deplete_restart.py | 14 +++++++------- tests/unit_tests/test_deplete_si_celi.py | 4 ++-- tests/unit_tests/test_deplete_si_leqi.py | 4 ++-- 10 files changed, 30 insertions(+), 32 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 6e1afc542..29a3b5b9a 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -20,10 +20,10 @@ transport-depletion coupling algorithms `_. integrator.CF4Integrator integrator.CECMIntegrator integrator.CELIIntegrator - integrator.EPC_RK4_Integrator + integrator.EPCRK4Integrator integrator.LEQIIntegrator - integrator.SI_CELI_Integrator - integrator.SI_LEQI_Integrator + integrator.SICELIIntegrator + integrator.SILEQIIntegrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 2ed2634ee..03064d8bd 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -170,7 +170,7 @@ class Integrator(ABC): self.operator.write_bos_data(len(self) + self._i_res) -class SI_Integrator(Integrator): +class SIIntegrator(Integrator): """Abstract for the Stochastic Implicit Euler integrators Does not provide a ``__call__`` method, but scales and resets diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index c8afe8784..d5fba639f 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -4,7 +4,7 @@ from .cram import timed_deplete from .abc import Integrator -class EPC_RK4_Integrator(Integrator): +class EPCRK4Integrator(Integrator): r"""Deplete using the EPC-RK4 algorithm. Implements an extended predictor-corrector algorithm with traditional diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 14ff65bf4..69f7287ba 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -2,20 +2,20 @@ import copy -from .abc import SI_Integrator +from .abc import SIIntegrator from .cram import timed_deplete from .celi import _celi_f1, _celi_f2 from ..abc import OperatorResult -class SI_CELI_Integrator(SI_Integrator): - r"""Deplete using the si-ce/li cfq4 algorithm. +class SICELIIntegrator(SIIntegrator): + r"""Deplete using the SI-CE/LI CFQ4 algorithm. - Implements the stochastic implicit ce/li predictor-corrector algorithm + Implements the stochastic implicit CE/LI predictor-corrector algorithm using the `fourth order commutator-free integrator `_. - detailed algorithm can be found in section 3.2 in `colin josey's thesis + Detailed algorithm can be found in section 3.2 in `colin josey's thesis `_. """ _num_stages = 2 diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 384b1499a..4a39207ca 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -1,18 +1,16 @@ """The SI-LE/QI CFQ4 integrator.""" import copy -from collections.abc import Iterable from itertools import repeat -from .abc import SI_Integrator -from .si_celi import SI_CELI_Integrator +from .abc import SIIntegrator +from .si_celi import SICELIIntegrator from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 from .cram import timed_deplete -from ..results import Results from ..abc import OperatorResult -class SI_LEQI_Integrator(SI_Integrator): +class SILEQIIntegrator(SIIntegrator): r"""Deplete using the SI-LE/QI CFQ4 algorithm. Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm using @@ -55,7 +53,7 @@ class SI_LEQI_Integrator(SI_Integrator): if self._i_res < 1: self._prev_rates = bos_rates # Perform CELI for initial steps - return SI_CELI_Integrator.__call__( + return SICELIIntegrator.__call__( self, bos_conc, bos_rates, dt, power, i) prev_res = self.operator.prev_res[-2] prev_dt = self.timesteps[i] - prev_res.time[0] diff --git a/tests/unit_tests/test_deplete_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py index 71cda3b9f..8f1160164 100644 --- a/tests/unit_tests/test_deplete_epc_rk4.py +++ b/tests/unit_tests/test_deplete_epc_rk4.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -from openmc.deplete import EPC_RK4_Integrator, ResultsList +from openmc.deplete import EPCRK4Integrator, ResultsList from tests import dummy_operator @@ -18,7 +18,7 @@ def test_epc_rk4(run_in_tmpdir): # Perform simulation using the epc_rk4 algorithm dt = [0.75, 0.75] power = 1.0 - EPC_RK4_Integrator(op, dt, power).integrate() + EPCRK4Integrator(op, dt, power).integrate() # Load the files res = ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 69f6ee679..302ec28d7 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -16,7 +16,7 @@ import pytest from openmc.deplete import ( ReactionRates, Results, ResultsList, comm, OperatorResult, - PredictorIntegrator, SI_CELI_Integrator) + PredictorIntegrator, SICELIIntegrator) def test_results_save(run_in_tmpdir): @@ -126,7 +126,7 @@ def test_bad_integrator_inputs(timesteps): # SI integrator with bad steps with pytest.raises(TypeError, match="n_steps"): - SI_CELI_Integrator(op, timesteps, [1], n_steps=2.5) + SICELIIntegrator(op, timesteps, [1], n_steps=2.5) with pytest.raises(ValueError, match="n_steps"): - SI_CELI_Integrator(op, timesteps, [1], n_steps=0) + SICELIIntegrator(op, timesteps, [1], n_steps=0) diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 847ad9b83..912f7e301 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,7 +8,7 @@ from pytest import approx, raises import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPC_RK4_Integrator, CF4Integrator, SI_CELI_Integrator, SI_LEQI_Integrator + EPCRK4Integrator, CF4Integrator, SICELIIntegrator, SILEQIIntegrator ) from tests import dummy_operator @@ -192,7 +192,7 @@ def test_restart_epc_rk4(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - EPC_RK4_Integrator(op, dt, power).integrate() + EPCRK4Integrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -202,7 +202,7 @@ def test_restart_epc_rk4(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - EPC_RK4_Integrator(op, dt, power).integrate() + EPCRK4Integrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -308,7 +308,7 @@ def test_restart_si_celi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - SI_CELI_Integrator(op, dt, power).integrate() + SICELIIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -318,7 +318,7 @@ def test_restart_si_celi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - SI_CELI_Integrator(op, dt, power).integrate() + SICELIIntegrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -348,7 +348,7 @@ def test_restart_si_leqi(run_in_tmpdir): dt = [0.75] power = 1.0 nstages = 10 - SI_LEQI_Integrator(op, dt, power, nstages).integrate() + SILEQIIntegrator(op, dt, power, nstages).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -358,7 +358,7 @@ def test_restart_si_leqi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - SI_LEQI_Integrator(op, dt, power, nstages).integrate() + SILEQIIntegrator(op, dt, power, nstages).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_si_celi.py b/tests/unit_tests/test_deplete_si_celi.py index 954c9d890..0a0103510 100644 --- a/tests/unit_tests/test_deplete_si_celi.py +++ b/tests/unit_tests/test_deplete_si_celi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -from openmc.deplete import SI_CELI_Integrator, ResultsList +from openmc.deplete import SICELIIntegrator, ResultsList from tests import dummy_operator @@ -18,7 +18,7 @@ def test_si_celi(run_in_tmpdir): # Perform simulation using the si_celi algorithm dt = [0.75, 0.75] power = 1.0 - SI_CELI_Integrator(op, dt, power).integrate() + SICELIIntegrator(op, dt, power).integrate() # Load the files res = ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_si_leqi.py b/tests/unit_tests/test_deplete_si_leqi.py index c26c06d03..41215c22f 100644 --- a/tests/unit_tests/test_deplete_si_leqi.py +++ b/tests/unit_tests/test_deplete_si_leqi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -from openmc.deplete import SI_LEQI_Integrator, ResultsList +from openmc.deplete import SILEQIIntegrator, ResultsList from tests import dummy_operator @@ -18,7 +18,7 @@ def test_si_leqi(run_in_tmpdir): # Perform simulation using the si_leqi algorithm dt = [0.75, 0.75] power = 1.0 - SI_LEQI_Integrator(op, dt, power, 10).integrate() + SILEQIIntegrator(op, dt, power, 10).integrate() # Load the files res = ResultsList(op.output_dir / "depletion_results.h5") From 18c4d5f4327828721a1b21c9bec0da24de8d5a3b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 09:01:15 -0500 Subject: [PATCH 29/32] Document init parameters and attributes for Integrators --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/abc.py | 111 +++++++++++++++---------- openmc/deplete/integrator/cecm.py | 31 +++++++ openmc/deplete/integrator/celi.py | 31 +++++++ openmc/deplete/integrator/cf4.py | 31 +++++++ openmc/deplete/integrator/epc_rk4.py | 34 +++++++- openmc/deplete/integrator/leqi.py | 31 +++++++ openmc/deplete/integrator/predictor.py | 13 ++- openmc/deplete/integrator/si_celi.py | 38 ++++++++- openmc/deplete/integrator/si_leqi.py | 36 ++++++++ 10 files changed, 308 insertions(+), 50 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 29a3b5b9a..f2964c5c8 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -17,9 +17,9 @@ transport-depletion coupling algorithms `_. :template: myclassinherit.rst integrator.PredictorIntegrator - integrator.CF4Integrator integrator.CECMIntegrator integrator.CELIIntegrator + integrator.CF4Integrator integrator.EPCRK4Integrator integrator.LEQIIntegrator integrator.SICELIIntegrator diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 03064d8bd..53db536aa 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -13,30 +13,39 @@ from openmc.deplete import Results, OperatorResult class Integrator(ABC): """Abstract class for solving the time-integration for depletion + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` """ def __init__(self, operator, timesteps, power=None, power_density=None): - """ - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - """ # Check number of stages previously used if operator.prev_res is not None: res = operator.prev_res[-1] @@ -175,33 +184,45 @@ class SIIntegrator(Integrator): Does not provide a ``__call__`` method, but scales and resets the number of particles used in initial transport calculation + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval """ def __init__(self, operator, timesteps, power=None, power_density=None, n_steps=10): - """ - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - n_steps : int, optional - Number of stochastic iterations per depletion interval. - Must be greater than zero. Default : 10 - """ check_type("n_steps", n_steps, Integral) check_greater_than("n_steps", n_steps, 0) super().__init__(operator, timesteps, power, power_density) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 647acfe0c..aea4b7b39 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -21,6 +21,37 @@ class CECMIntegrator(Integrator): A_c &= A(y_m, t_n + h/2) \\ y_{n+1} &= \text{expm}(A_c h) y_n \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` """ _num_stages = 2 diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 6935f9f6d..9aa9830b2 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -22,6 +22,37 @@ class CELIIntegrator(Integrator): y_{n+1} &= \text{expm}(\frac{h}{12} A_0 + \frac{5h}{12} A1) \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` """ _num_stages = 2 diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index a88a6788c..49ae16483 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -27,6 +27,37 @@ class CF4Integrator(Integrator): y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4) \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` """ _num_stages = 4 diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index d5fba639f..c8247bf42 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -8,8 +8,7 @@ class EPCRK4Integrator(Integrator): r"""Deplete using the EPC-RK4 algorithm. Implements an extended predictor-corrector algorithm with traditional - Runge-Kutta 4 method. - This algorithm is mathematically defined as: + Runge-Kutta 4 method. This algorithm is mathematically defined as: .. math:: \begin{aligned} @@ -22,6 +21,37 @@ class EPCRK4Integrator(Integrator): F_4 &= h A(y_3) \\ y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` """ _num_stages = 4 diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 551514078..86c1ab2df 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -36,6 +36,37 @@ class LEQIIntegrator(Integrator): \end{aligned} It is initialized using the CE/LI algorithm. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` """ _num_stages = 2 diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index f57bac79c..44b1516aa 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -20,7 +20,7 @@ class PredictorIntegrator(Integrator): Parameters ---------- operator : openmc.deplete.TransportOperator - The operator object to simulate on. + Operator to perform transport simulations timesteps : iterable of float Array of timesteps in units of [s]. Note that values are not cumulative. @@ -36,6 +36,17 @@ class PredictorIntegrator(Integrator): Power density of the reactor in [W/gHM]. It is multiplied by initial heavy metal inventory to get total power if ``power`` is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` """ _num_stages = 1 diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 69f7287ba..4b4ccc1e6 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -15,8 +15,44 @@ class SICELIIntegrator(SIIntegrator): using the `fourth order commutator-free integrator `_. - Detailed algorithm can be found in section 3.2 in `colin josey's thesis + Detailed algorithm can be found in section 3.2 in `Colin Josey's thesis `_. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval """ _num_stages = 2 diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 4a39207ca..1e48c039e 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -18,6 +18,42 @@ class SILEQIIntegrator(SIIntegrator): Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis `_. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval """ _num_stages = 2 From 5adc3b5816fe33adc17f8656fe3a731196f3ca7f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 09:13:14 -0500 Subject: [PATCH 30/32] Document special and abstract methods for Integrators --- docs/source/_templates/myintegrator.rst | 9 +++++++++ docs/source/pythonapi/deplete.rst | 15 +++++++++++++-- openmc/deplete/integrator/abc.py | 4 +++- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 docs/source/_templates/myintegrator.rst diff --git a/docs/source/_templates/myintegrator.rst b/docs/source/_templates/myintegrator.rst new file mode 100644 index 000000000..803dfde06 --- /dev/null +++ b/docs/source/_templates/myintegrator.rst @@ -0,0 +1,9 @@ +{{ fullname }} +{{ underline }} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :inherited-members: + :special-members: __call__, __len__, __iter__ diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index f2964c5c8..dc95c7821 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -14,7 +14,7 @@ transport-depletion coupling algorithms `_. .. autosummary:: :toctree: generated :nosignatures: - :template: myclassinherit.rst + :template: myintegrator.rst integrator.PredictorIntegrator integrator.CECMIntegrator @@ -95,7 +95,18 @@ The following classes are abstract classes that can be used to extend the EnergyHelper TransportOperator -Each of the integrator functions also relies on a number of "helper" functions +Custom integrators can be developed by subclassing from the following abstract +base classes: + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myintegrator.rst + + Integrator + SIIntegrator + +Each of the integrator classes also relies on a number of "helper" functions as follows: .. autosummary:: diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 53db536aa..cceb6ccc3 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -118,9 +118,11 @@ class Integrator(ABC): """ def __iter__(self): + """Return pairs of time steps in [s] and powers in [W]""" return zip(self.timesteps, self.power) def __len__(self): + """Return integer number of depletion intervals""" return len(self.timesteps) def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): @@ -180,7 +182,7 @@ class Integrator(ABC): class SIIntegrator(Integrator): - """Abstract for the Stochastic Implicit Euler integrators + """Abstract class for the Stochastic Implicit Euler integrators Does not provide a ``__call__`` method, but scales and resets the number of particles used in initial transport calculation From 0bc7800a92c8b1f8bc8997219efecf40c39a239f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 10:51:19 -0500 Subject: [PATCH 31/32] Move openmc.deplete.integrator into openmc.deplete Much of the previous API is intact, with the major change being CRAM functions are imported from openmc.deplete.cram in the test_deplete_cram. The integrator abstract classes are placed in openmc/deplete/abc.py with all concrete classes going in to openmc/deplete/integrators.py Documentation updated accordingly --- docs/source/pythonapi/deplete.rst | 20 +- openmc/deplete/__init__.py | 2 +- openmc/deplete/_matrix_funcs.py | 79 +++ openmc/deplete/abc.py | 287 +++++++- openmc/deplete/{integrator => }/cram.py | 4 +- openmc/deplete/integrator/__init__.py | 17 - openmc/deplete/integrator/abc.py | 277 -------- openmc/deplete/integrator/cecm.py | 92 --- openmc/deplete/integrator/celi.py | 110 ---- openmc/deplete/integrator/cf4.py | 140 ---- openmc/deplete/integrator/epc_rk4.py | 119 ---- openmc/deplete/integrator/leqi.py | 173 ----- openmc/deplete/integrator/predictor.py | 81 --- openmc/deplete/integrator/si_celi.py | 109 --- openmc/deplete/integrator/si_leqi.py | 129 ---- openmc/deplete/integrators.py | 836 ++++++++++++++++++++++++ tests/unit_tests/test_deplete_cram.py | 2 +- 17 files changed, 1210 insertions(+), 1267 deletions(-) create mode 100644 openmc/deplete/_matrix_funcs.py rename openmc/deplete/{integrator => }/cram.py (99%) delete mode 100644 openmc/deplete/integrator/__init__.py delete mode 100644 openmc/deplete/integrator/abc.py delete mode 100644 openmc/deplete/integrator/cecm.py delete mode 100644 openmc/deplete/integrator/celi.py delete mode 100644 openmc/deplete/integrator/cf4.py delete mode 100644 openmc/deplete/integrator/epc_rk4.py delete mode 100644 openmc/deplete/integrator/leqi.py delete mode 100644 openmc/deplete/integrator/predictor.py delete mode 100644 openmc/deplete/integrator/si_celi.py delete mode 100644 openmc/deplete/integrator/si_leqi.py create mode 100644 openmc/deplete/integrators.py diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index dc95c7821..a72504ac9 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,14 +16,14 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myintegrator.rst - integrator.PredictorIntegrator - integrator.CECMIntegrator - integrator.CELIIntegrator - integrator.CF4Integrator - integrator.EPCRK4Integrator - integrator.LEQIIntegrator - integrator.SICELIIntegrator - integrator.SILEQIIntegrator + PredictorIntegrator + CECMIntegrator + CELIIntegrator + CF4Integrator + EPCRK4Integrator + LEQIIntegrator + SICELIIntegrator + SILEQIIntegrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: @@ -114,5 +114,5 @@ as follows: :nosignatures: :template: myfunction.rst - integrator.CRAM16 - integrator.CRAM48 + cram.CRAM16 + cram.CRAM48 diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index b5eb2dec2..8c19615ec 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -38,4 +38,4 @@ from .reaction_rates import * from .abc import * from .results import * from .results_list import * -from .integrator import * +from .integrators import * diff --git a/openmc/deplete/_matrix_funcs.py b/openmc/deplete/_matrix_funcs.py new file mode 100644 index 000000000..1cacbd2c0 --- /dev/null +++ b/openmc/deplete/_matrix_funcs.py @@ -0,0 +1,79 @@ +"""Functions to form the special matrix for depletion""" + + +def celi_f1(chain, rates): + return (5 / 12 * chain.form_matrix(rates[0]) + + 1 / 12 * chain.form_matrix(rates[1])) + + +def celi_f2(chain, rates): + return (1 / 12 * chain.form_matrix(rates[0]) + + 5 / 12 * chain.form_matrix(rates[1])) + + +def cf4_f1(chain, rates): + return 1 / 2 * chain.form_matrix(rates) + + +def cf4_f2(chain, rates): + return -1 / 2 * chain.form_matrix(rates[0]) + chain.form_matrix(rates[1]) + + +def cf4_f3(chain, rates): + return (1 / 4 * chain.form_matrix(rates[0]) + + 1 / 6 * chain.form_matrix(rates[1]) + + 1 / 6 * chain.form_matrix(rates[2]) + - 1 / 12 * chain.form_matrix(rates[3])) + + +def cf4_f4(chain, rates): + return (-1 / 12 * chain.form_matrix(rates[0]) + + 1 / 6 * chain.form_matrix(rates[1]) + + 1 / 6 * chain.form_matrix(rates[2]) + + 1 / 4 * chain.form_matrix(rates[3])) + + +def rk4_f1(chain, rates): + return 1 / 2 * chain.form_matrix(rates) + + +def rk4_f4(chain, rates): + return (1 / 6 * chain.form_matrix(rates[0]) + + 1 / 3 * chain.form_matrix(rates[1]) + + 1 / 3 * chain.form_matrix(rates[2]) + + 1 / 6 * chain.form_matrix(rates[3])) + + +def leqi_f1(chain, inputs): + f1 = chain.form_matrix(inputs[0]) + f2 = chain.form_matrix(inputs[1]) + dt_l, dt = inputs[2], inputs[3] + return -dt / (12 * dt_l) * f1 + (dt + 6 * dt_l) / (12 * dt_l) * f2 + + +def leqi_f2(chain, inputs): + f1 = chain.form_matrix(inputs[0]) + f2 = chain.form_matrix(inputs[1]) + dt_l, dt = inputs[2], inputs[3] + return -5 * dt / (12 * dt_l) * f1 + (5 * dt + 6 * dt_l) / (12 * dt_l) * f2 + + +def leqi_f3(chain, inputs): + f1 = chain.form_matrix(inputs[0]) + f2 = chain.form_matrix(inputs[1]) + f3 = chain.form_matrix(inputs[2]) + dt_l, dt = inputs[3], inputs[4] + return (-dt ** 2 / (12 * dt_l * (dt + dt_l)) * f1 + + (dt ** 2 + 6 * dt * dt_l + 5 * dt_l ** 2) + / (12 * dt_l * (dt + dt_l)) * f2 + dt_l / (12 * (dt + dt_l)) * f3) + + +def leqi_f4(chain, inputs): + f1 = chain.form_matrix(inputs[0]) + f2 = chain.form_matrix(inputs[1]) + f3 = chain.form_matrix(inputs[2]) + dt_l, dt = inputs[3], inputs[4] + return (-dt ** 2 / (12 * dt_l * (dt + dt_l)) * f1 + + (dt ** 2 + 2 * dt * dt_l + dt_l ** 2) + / (12 * dt_l * (dt + dt_l)) * f2 + + (4 * dt * dt_l + 5 * dt_l ** 2) / (12 * dt_l * (dt + dt_l)) * f3) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 8e77a8ff0..0f1203aeb 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -5,17 +5,20 @@ to run a full depletion simulation. """ from collections import namedtuple +from collections.abc import Iterable import os from pathlib import Path from abc import ABC, abstractmethod -from xml.etree import ElementTree as ET +from copy import deepcopy from warnings import warn -from numbers import Real +from numbers import Real, Integral from numpy import nonzero, empty +from uncertainties import ufloat from openmc.data import DataLibrary, JOULE_PER_EV from openmc.checkvalue import check_type, check_greater_than +from .results import Results from .chain import Chain from .results_list import ResultsList @@ -44,8 +47,8 @@ class TransportOperator(ABC): Each depletion integrator is written to work with a generic transport operator that takes a vector of material compositions and returns an - eigenvalue and reaction rates. This abstract class sets the requirements for - such a transport operator. Users should instantiate + eigenvalue and reaction rates. This abstract class sets the requirements + for such a transport operator. Users should instantiate :class:`openmc.deplete.Operator` rather than this class. Parameters @@ -104,7 +107,7 @@ class TransportOperator(ABC): self.prev_res = None else: check_type("previous results", prev_results, ResultsList) - self.prev_results = prev_res + self.prev_results = prev_results @property def dilute_initial(self): @@ -179,7 +182,8 @@ class TransportOperator(ABC): nuc_list : list of str A list of all nuclide names. Used for sorting the simulation. burn_list : list of int - A list of all cell IDs to be burned. Used for sorting the simulation. + A list of all cell IDs to be burned. Used for sorting the + simulation. full_burn_list : list of int All burnable materials in the geometry. """ @@ -266,7 +270,8 @@ class ReactionRateHelper(ABC): Parameters ---------- number : iterable of float - Number density [atoms/b-cm] of each nuclide tracked in the calculation. + Number density [atoms/b-cm] of each nuclide tracked in the + calculation. Returns ------- @@ -357,3 +362,271 @@ class EnergyHelper(ABC): def nuclides(self, nuclides): check_type("nuclides", nuclides, list, str) self._nuclides = nuclides + + +class Integrator(ABC): + """Abstract class for solving the time-integration for depletion + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + + def __init__(self, operator, timesteps, power=None, power_density=None): + # Check number of stages previously used + if operator.prev_res is not None: + res = operator.prev_res[-1] + if res.data.shape[0] != self._num_stages: + raise ValueError( + "{} incompatible with previous restart calculation. " + "Previous scheme used {} intermediate solutions, while " + "this uses {}".format( + self.__class__.__name__, res.data.shape[0], + self._num_stages)) + self.operator = operator + self.chain = operator.chain + if not isinstance(timesteps, Iterable): + self.timesteps = [timesteps] + else: + self.timesteps = timesteps + if power is None: + if power_density is None: + raise ValueError("Either power or power density must be set") + if not isinstance(power_density, Iterable): + power = power_density * operator.heavy_metal + else: + power = [p * operator.heavy_metal for p in power_density] + + if not isinstance(power, Iterable): + # Ensure that power is single value if that is the case + power = [power] * len(self.timesteps) + elif len(power) != len(self.timesteps): + raise ValueError( + "Number of time steps != number of powers. {} vs {}".format( + len(self.timesteps), len(power))) + + self.power = power + + @abstractmethod + def __call__(self, conc, rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + + @property + @abstractmethod + def _num_stages(self): + """Number of intermediate transport solutions + + Needed to ensure schemes are consistent with restarts + """ + + def __iter__(self): + """Return pairs of time steps in [s] and powers in [W]""" + return zip(self.timesteps, self.power) + + def __len__(self): + """Return integer number of depletion intervals""" + return len(self.timesteps) + + def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): + """Get beginning of step concentrations, reaction rates from Operator + """ + x = deepcopy(bos_conc) + res = self.operator(x, step_power) + self.operator.write_bos_data(step_index + self._i_res) + return x, res + + def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): + """Get beginning of step concentrations, reaction rates from restart""" + res = self.operator.prev_res[-1] + # Depletion methods expect list of arrays + bos_conc = list(res.data[0]) + rates = res.rates[0] + k = ufloat(res.k[0, 0], res.k[0, 1]) + + # Scale rates by ratio of powers + rates *= step_power / res.power[0] + return bos_conc, OperatorResult(k, rates) + + def _get_start_data(self): + if self.operator.prev_res is None: + return 0.0, 0 + return (self.operator.prev_res[-1].time[-1], + len(self.operator.prev_res) - 1) + + def integrate(self): + """Perform the entire depletion process across all steps""" + with self.operator as conc: + t, self._i_res = self._get_start_data() + + for i, (dt, p) in enumerate(self): + if i > 0 or self.operator.prev_res is None: + conc, res = self._get_bos_data_from_operator(i, p, conc) + else: + conc, res = self._get_bos_data_from_restart(i, p, conc) + proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) + + # Insert BOS concentration, transport results + conc_list.insert(0, conc) + res_list.insert(0, res) + + # Remove actual EOS concentration for next step + conc = conc_list.pop() + + Results.save(self.operator, conc_list, res_list, [t, t + dt], + p, self._i_res + i, proc_time) + + t += dt + + # Final simulation + res_list = [self.operator(conc, p)] + Results.save(self.operator, [conc], res_list, [t, t], + p, self._i_res + len(self), proc_time) + self.operator.write_bos_data(len(self) + self._i_res) + + +class SIIntegrator(Integrator): + """Abstract class for the Stochastic Implicit Euler integrators + + Does not provide a ``__call__`` method, but scales and resets + the number of particles used in initial transport calculation + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval + """ + def __init__(self, operator, timesteps, power=None, power_density=None, + n_steps=10): + check_type("n_steps", n_steps, Integral) + check_greater_than("n_steps", n_steps, 0) + super().__init__(operator, timesteps, power, power_density) + self.n_steps = n_steps + + def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): + reset_particles = False + if step_index == 0 and hasattr(self.operator, "settings"): + reset_particles = True + self.operator.settings.particles *= self.n_stages + inherited = super()._get_bos_data_from_operator( + step_index, step_power, bos_conc) + if reset_particles: + self.operator.settings.particles //= self.n_stages + return inherited + + def integrate(self): + """Perform the entire depletion process across all steps""" + with self.operator as conc: + t, self._i_res = self._get_start_data() + + for i, (dt, p) in enumerate(self): + if i == 0: + if self.operator.prev_res is None: + conc, res = self._get_bos_data_from_operator(i, p, conc) + else: + conc, res = self._get_bos_data_from_restart(i, p, conc) + else: + # Pull rates, k from previous iteration w/o + # re-running transport + res = res_list[-1] # defined in previous i iteration + + proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) + + # Insert BOS concentration, transport results + conc_list.insert(0, conc) + res_list.insert(0, res) + + # Remove actual EOS concentration for next step + conc = conc_list.pop() + + Results.save(self.operator, conc_list, res_list, [t, t + dt], + p, self._i_res + i, proc_time) + + t += dt + + # No final simulation for SIE, use last iteration results + Results.save(self.operator, [conc], [res_list[-1]], [t, t], + p, self._i_res + len(self), proc_time) + self.operator.write_bos_data(self._i_res + len(self)) diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/cram.py similarity index 99% rename from openmc/deplete/integrator/cram.py rename to openmc/deplete/cram.py index c57ff5313..41a4e4c71 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/cram.py @@ -11,7 +11,9 @@ import numpy as np import scipy.sparse as sp import scipy.sparse.linalg as sla -from .. import comm +from . import comm + +__all__ = ["deplete", "timed_deplete", "CRAM16", "CRAM48"] def deplete(chain, x, rates, dt, matrix_func=None): diff --git a/openmc/deplete/integrator/__init__.py b/openmc/deplete/integrator/__init__.py deleted file mode 100644 index 961afefb6..000000000 --- a/openmc/deplete/integrator/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Integrator -=========== - -The integrator subcomponents. -""" - -from .abc import Integrator -from .cf4 import * -from .cecm import * -from .celi import * -from .cram import * -from .epc_rk4 import * -from .leqi import * -from .predictor import * -from .si_celi import * -from .si_leqi import * diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py deleted file mode 100644 index cceb6ccc3..000000000 --- a/openmc/deplete/integrator/abc.py +++ /dev/null @@ -1,277 +0,0 @@ -from copy import deepcopy -from abc import ABC, abstractmethod -from collections.abc import Iterable -from numbers import Integral - -from uncertainties import ufloat - -from openmc.checkvalue import check_type, check_greater_than -from openmc.capi import statepoint_write -from openmc.deplete import Results, OperatorResult - - -class Integrator(ABC): - """Abstract class for solving the time-integration for depletion - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - """ - - def __init__(self, operator, timesteps, power=None, power_density=None): - # Check number of stages previously used - if operator.prev_res is not None: - res = operator.prev_res[-1] - if res.data.shape[0] != self._num_stages: - raise ValueError( - "{} incompatible with previous restart calculation. " - "Previous scheme used {} intermediate solutions, while " - "this uses {}".format( - self.__class__.__name__, res.data.shape[0], - self._num_stages)) - self.operator = operator - self.chain = operator.chain - if not isinstance(timesteps, Iterable): - self.timesteps = [timesteps] - else: - self.timesteps = timesteps - if power is None: - if power_density is None: - raise ValueError("Either power or power density must be set") - if not isinstance(power_density, Iterable): - power = power_density * operator.heavy_metal - else: - power = [p * operator.heavy_metal for p in power_density] - - if not isinstance(power, Iterable): - # Ensure that power is single value if that is the case - power = [power] * len(self.timesteps) - elif len(power) != len(self.timesteps): - raise ValueError( - "Number of time steps != number of powers. {} vs {}".format( - len(self.timesteps), len(power))) - - self.power = power - - @abstractmethod - def __call__(self, conc, rates, dt, power, i): - """Perform the integration across one time step - - Parameters - ---------- - conc : numpy.ndarray - Initial concentrations for all nuclides in [atom] - rates : openmc.deplete.ReactionRates - Reaction rates from operator - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system in [W] - i : int - Current depletion step index - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - conc_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulations - """ - - @property - @abstractmethod - def _num_stages(self): - """Number of intermediate transport solutions - - Needed to ensure schemes are consistent with restarts - """ - - def __iter__(self): - """Return pairs of time steps in [s] and powers in [W]""" - return zip(self.timesteps, self.power) - - def __len__(self): - """Return integer number of depletion intervals""" - return len(self.timesteps) - - def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): - """Get beginning of step concentrations, reaction rates from Operator""" - x = deepcopy(bos_conc) - res = self.operator(x, step_power) - self.operator.write_bos_data(step_index + self._i_res) - return x, res - - def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): - """Get beginning of step concentrations, reaction rates from restart""" - res = self.operator.prev_res[-1] - # Depletion methods expect list of arrays - bos_conc = list(res.data[0]) - rates = res.rates[0] - k = ufloat(res.k[0, 0], res.k[0, 1]) - - # Scale rates by ratio of powers - rates *= step_power / res.power[0] - return bos_conc, OperatorResult(k, rates) - - def _get_start_data(self): - if self.operator.prev_res is None: - return 0.0, 0 - return (self.operator.prev_res[-1].time[-1], - len(self.operator.prev_res) - 1) - - def integrate(self): - """Perform the entire depletion process across all steps""" - with self.operator as conc: - t, self._i_res = self._get_start_data() - - for i, (dt, p) in enumerate(self): - if i > 0 or self.operator.prev_res is None: - conc, res = self._get_bos_data_from_operator(i, p, conc) - else: - conc, res = self._get_bos_data_from_restart(i, p, conc) - proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) - - # Insert BOS concentration, transport results - conc_list.insert(0, conc) - res_list.insert(0, res) - - # Remove actual EOS concentration for next step - conc = conc_list.pop() - - Results.save(self.operator, conc_list, res_list, [t, t + dt], - p, self._i_res + i, proc_time) - - t += dt - - # Final simulation - res_list = [self.operator(conc, p)] - Results.save(self.operator, [conc], res_list, [t, t], - p, self._i_res + len(self), proc_time) - self.operator.write_bos_data(len(self) + self._i_res) - - -class SIIntegrator(Integrator): - """Abstract class for the Stochastic Implicit Euler integrators - - Does not provide a ``__call__`` method, but scales and resets - the number of particles used in initial transport calculation - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - n_steps : int, optional - Number of stochastic iterations per depletion interval. - Must be greater than zero. Default : 10 - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - n_steps : int - Number of stochastic iterations per depletion interval - """ - def __init__(self, operator, timesteps, power=None, power_density=None, - n_steps=10): - check_type("n_steps", n_steps, Integral) - check_greater_than("n_steps", n_steps, 0) - super().__init__(operator, timesteps, power, power_density) - self.n_steps = n_steps - - def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): - reset_particles = False - if step_index == 0 and hasattr(self.operator, "settings"): - reset_particles = True - self.operator.settings.particles *= self.n_stages - inherited = super()._get_bos_data_from_operator( - step_index, step_power, bos_conc) - if reset_particles: - self.operator.settings.particles //= self.n_stages - return inherited - - def integrate(self): - """Perform the entire depletion process across all steps""" - with self.operator as conc: - t, self._i_res = self._get_start_data() - - for i, (dt, p) in enumerate(self): - if i == 0: - if self.operator.prev_res is None: - conc, res = self._get_bos_data_from_operator(i, p, conc) - else: - conc, res = self._get_bos_data_from_restart(i, p, conc) - else: - # Pull rates, k from previous iteration w/o - # re-running transport - res = res_list[-1] # defined in previous i iteration - - proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) - - # Insert BOS concentration, transport results - conc_list.insert(0, conc) - res_list.insert(0, res) - - # Remove actual EOS concentration for next step - conc = conc_list.pop() - - Results.save(self.operator, conc_list, res_list, [t, t + dt], - p, self._i_res + i, proc_time) - - t += dt - - # No final simulation for SIE, use last iteration results - Results.save(self.operator, [conc], [res_list[-1]], [t, t], - p, self._i_res + len(self), proc_time) - self.operator.write_bos_data(self._i_res + len(self)) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py deleted file mode 100644 index aea4b7b39..000000000 --- a/openmc/deplete/integrator/cecm.py +++ /dev/null @@ -1,92 +0,0 @@ -"""The CE/CM integrator.""" - -from .abc import Integrator -from .cram import timed_deplete - - -class CECMIntegrator(Integrator): - r"""Deplete using the CE/CM algorithm. - - Implements the second order `CE/CM predictor-corrector algorithm - `_. - - "CE/CM" stands for constant extrapolation on predictor and constant - midpoint on corrector. This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_p &= A(y_n, t_n) \\ - y_m &= \text{expm}(A_p h/2) y_n \\ - A_c &= A(y_m, t_n + h/2) \\ - y_{n+1} &= \text{expm}(A_c h) y_n - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - """ - _num_stages = 2 - - def __call__(self, conc, rates, dt, power, _i=-1): - """Integrate using CE/CM - - Parameters - ---------- - conc : numpy.ndarray - Initial concentrations for all nuclides in [atom] - rates : openmc.deplete.ReactionRates - Reaction rates from operator - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system [W] - _i : int, optional - Current iteration count. Not used - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - conc_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from transport simulations - """ - # deplete across first half of inteval - time0, x_middle = timed_deplete(self.chain, conc, rates, dt / 2) - res_middle = self.operator(x_middle, power) - - # deplete across entire interval with BOS concentrations, - # MOS reaction rates - time1, x_end = timed_deplete(self.chain, conc, res_middle.rates, dt) - - return time0 + time1, [x_middle, x_end], [res_middle] diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py deleted file mode 100644 index 9aa9830b2..000000000 --- a/openmc/deplete/integrator/celi.py +++ /dev/null @@ -1,110 +0,0 @@ -"""The CE/LI CFQ4 integrator.""" - -from .cram import timed_deplete -from .abc import Integrator - - -class CELIIntegrator(Integrator): - r"""Deplete using the CE/LI CFQ4 algorithm. - - Implements the CE/LI Predictor-Corrector algorithm using the `fourth order - commutator-free integrator `_. - - "CE/LI" stands for constant extrapolation on predictor and linear - interpolation on corrector. This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_0 &= A(y_n, t_n) \\ - y_p &= \text{expm}(h A_0) y_n \\ - A_1 &= A(y_p, t_n + h) \\ - y_{n+1} &= \text{expm}(\frac{h}{12} A_0 + \frac{5h}{12} A1) - \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - """ - _num_stages = 2 - - def __call__(self, bos_conc, rates, dt, power, _i=-1): - """Perform the integration across one time step - - Parameters - ---------- - bos_conc : numpy.ndarray - Initial concentrations for all nuclides in [atom] - rates : openmc.deplete.ReactionRates - Reaction rates from operator - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system in [W] - _i : int - Current iteration count. Not used - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - conc_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulation - """ - # deplete to end using BOS rates - proc_time, conc_ce = timed_deplete(self.chain, bos_conc, rates, dt) - res_ce = self.operator(conc_ce, power) - - # deplete using two matrix exponentials - list_rates = list(zip(rates, res_ce.rates)) - - time_le1, conc_inter = timed_deplete( - self.chain, bos_conc, list_rates, dt, matrix_func=_celi_f1) - - time_le2, conc_end = timed_deplete( - self.chain, conc_inter, list_rates, dt, matrix_func=_celi_f2) - - return proc_time + time_le1 + time_le1, [conc_ce, conc_end], [res_ce] - - -# Functions to form the special matrix for depletion -def _celi_f1(chain, rates): - return 5/12 * chain.form_matrix(rates[0]) + \ - 1/12 * chain.form_matrix(rates[1]) - - -def _celi_f2(chain, rates): - return 1/12 * chain.form_matrix(rates[0]) + \ - 5/12 * chain.form_matrix(rates[1]) diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py deleted file mode 100644 index 49ae16483..000000000 --- a/openmc/deplete/integrator/cf4.py +++ /dev/null @@ -1,140 +0,0 @@ -"""The CF4 integrator.""" - -import copy -from collections.abc import Iterable - -from .cram import timed_deplete -from .abc import Integrator -from ..results import Results - - -class CF4Integrator(Integrator): - r"""Deplete using the CF4 algorithm. - - Implements the fourth order `commutator-free Lie algorithm - `_. - This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - F_1 &= h A(y_0) \\ - y_1 &= \text{expm}(1/2 F_1) y_0 \\ - F_2 &= h A(y_1) \\ - y_2 &= \text{expm}(1/2 F_2) y_0 \\ - F_3 &= h A(y_2) \\ - y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1 \\ - F_4 &= h A(y_3) \\ - y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4) - \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - """ - _num_stages = 4 - - def __call__(self, bos_conc, bos_rates, dt, power, i): - """Perform the integration across one time step - - Parameters - ---------- - bos_conc : numpy.ndarray - Initial concentrations for all nuclides in [atom] - bos_rates : openmc.deplete.ReactionRates - Reaction rates from operator - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system in [W] - i : int - Current depletion step index - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - conc_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulations - """ - # Step 1: deplete with matrix 1/2*A(y0) - time1, conc_eos1 = timed_deplete( - self.chain, bos_conc, bos_rates, dt, matrix_func=_cf4_f1) - res1 = self.operator(conc_eos1, power) - - # Step 2: deplete with matrix 1/2*A(y1) - time2, conc_eos2 = timed_deplete( - self.chain, bos_conc, res1.rates, dt, matrix_func=_cf4_f1) - res2 = self.operator(conc_eos2, power) - - # Step 3: deplete with matrix -1/2*A(y0)+A(y2) - list_rates = list(zip(bos_rates, res2.rates)) - time3, conc_eos3 = timed_deplete( - self.chain, conc_eos1, list_rates, dt, matrix_func=_cf4_f2) - res3 = self.operator(conc_eos3, power) - - # Step 4: deplete with two matrix exponentials - list_rates = list(zip(bos_rates, res1.rates, res2.rates, res3.rates)) - time4, conc_inter = timed_deplete( - self.chain, bos_conc, list_rates, dt, matrix_func=_cf4_f3) - time5, conc_eos5 = timed_deplete( - self.chain, conc_inter, list_rates, dt, matrix_func=_cf4_f4) - - return (time1 + time2 + time3 + time4 + time5, - [conc_eos1, conc_eos2, conc_eos3, conc_eos5], - [res1, res2, res3]) - - -# Functions to form the special matrix for depletion -def _cf4_f1(chain, rates): - return 1/2 * chain.form_matrix(rates) - - -def _cf4_f2(chain, rates): - return -1/2 * chain.form_matrix(rates[0]) + \ - chain.form_matrix(rates[1]) - - -def _cf4_f3(chain, rates): - return 1/4 * chain.form_matrix(rates[0]) + \ - 1/6 * chain.form_matrix(rates[1]) + \ - 1/6 * chain.form_matrix(rates[2]) + \ - -1/12 * chain.form_matrix(rates[3]) - - -def _cf4_f4(chain, rates): - return -1/12 * chain.form_matrix(rates[0]) + \ - 1/6 * chain.form_matrix(rates[1]) + \ - 1/6 * chain.form_matrix(rates[2]) + \ - 1/4 * chain.form_matrix(rates[3]) diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py deleted file mode 100644 index c8247bf42..000000000 --- a/openmc/deplete/integrator/epc_rk4.py +++ /dev/null @@ -1,119 +0,0 @@ -"""The EPC-RK4 integrator.""" - -from .cram import timed_deplete -from .abc import Integrator - - -class EPCRK4Integrator(Integrator): - r"""Deplete using the EPC-RK4 algorithm. - - Implements an extended predictor-corrector algorithm with traditional - Runge-Kutta 4 method. This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - F_1 &= h A(y_0) \\ - y_1 &= \text{expm}(1/2 F_1) y_0 \\ - F_2 &= h A(y_1) \\ - y_2 &= \text{expm}(1/2 F_2) y_0 \\ - F_3 &= h A(y_2) \\ - y_3 &= \text{expm}(F_3) y_0 \\ - F_4 &= h A(y_3) \\ - y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - """ - _num_stages = 4 - - def __call__(self, conc, rates, dt, power, _i): - """Perform the integration across one time step - - Parameters - ---------- - conc : numpy.ndarray - Initial concentrations for all nuclides in [atom] - rates : openmc.deplete.ReactionRates - Reaction rates from operator - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system in [W] - i : int - Current depletion step index, unused. - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - conc_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulations - """ - - # Step 1: deplete with matrix A(y0) / 2 - time1, conc1 = timed_deplete( - self.chain, conc, rates, dt, matrix_func=_rk4_f1) - res1 = self.operator(conc1, power) - - # Step 2: deplete with matrix A(y1) / 2 - time2, conc2 = timed_deplete( - self.chain, conc, res1.rates, dt, matrix_func=_rk4_f1) - res2 = self.operator(conc2, power) - - # Step 3: deplete with matrix A(y2) - time3, conc3 = timed_deplete( - self.chain, conc, res2.rates, dt) - res3 = self.operator(conc3, power) - - # Step 4: deplete with matrix built from weighted rates - list_rates = list(zip(rates, res1.rates, res2.rates, res3.rates)) - time4, conc4 = timed_deplete( - self.chain, conc, list_rates, dt, matrix_func=_rk4_f4) - - return (time1 + time2 + time3 + time4, [conc1, conc2, conc3, conc4], - [res1, res2, res3]) - - -# Functions to form the special matrix for depletion -def _rk4_f1(chain, rates): - return 1/2 * chain.form_matrix(rates) - - -def _rk4_f4(chain, rates): - return 1/6 * chain.form_matrix(rates[0]) + \ - 1/3 * chain.form_matrix(rates[1]) + \ - 1/3 * chain.form_matrix(rates[2]) + \ - 1/6 * chain.form_matrix(rates[3]) diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py deleted file mode 100644 index 86c1ab2df..000000000 --- a/openmc/deplete/integrator/leqi.py +++ /dev/null @@ -1,173 +0,0 @@ -"""The LE/QI CFQ4 integrator.""" - -import copy -from itertools import repeat - -from .abc import Integrator -from .celi import CELIIntegrator -from .cram import timed_deplete - - -class LEQIIntegrator(Integrator): - r"""Deplete using the LE/QI CFQ4 algorithm. - - Implements the LE/QI Predictor-Corrector algorithm using the `fourth order - commutator-free integrator `_. - - "LE/QI" stands for linear extrapolation on predictor and quadratic - interpolation on corrector. This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_{last} &= A(y_{n-1}, t_n - h_1) \\ - A_0 &= A(y_n, t_n) \\ - F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0 \\ - F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0 \\ - y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n \\ - A_1 &= A(y_p, t_n + h_2) \\ - F_3 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + - \frac{h_2 (5 h_1^2 + 6 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + - \frac{h_2 h_1)}{12 (h_1 + h_2)} A_1 \\ - F_4 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + - \frac{h_2 (h_1^2 + 2 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + - \frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1 \\ - y_{n+1} &= \text{expm}(F_4) \text{expm}(F_3) y_n - \end{aligned} - - It is initialized using the CE/LI algorithm. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - """ - _num_stages = 2 - - def __call__(self, bos_conc, bos_rates, dt, power, i): - """Perform the integration across one time step - - Parameters - ---------- - conc : numpy.ndarray - Initial concentrations for all nuclides in [atom] - rates : openmc.deplete.ReactionRates - Reaction rates from operator - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system in [W] - i : int - Current depletion step index - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - conc_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulation - """ - if i == 0: - if self._i_res < 1: # need at least previous transport solution - self._prev_rates = bos_rates - return CELIIntegrator.__call__( - self, bos_conc, bos_rates, dt, power, i) - prev_res = self.operator.prev_res[-2] - prev_dt = self.timesteps[i] - prev_res.time[0] - self._prev_rates = prev_res.rates[0] - else: - prev_dt = self.timesteps[i - 1] - - # Remaining LE/QI - bos_res = self.operator(bos_conc, power) - - le_inputs = list(zip( - self._prev_rates, bos_res.rates, repeat(prev_dt), repeat(dt))) - - time1, conc_inter = timed_deplete( - self.chain, bos_conc, le_inputs, dt, matrix_func=_leqi_f1) - time2, conc_eos0 = timed_deplete( - self.chain, conc_inter, le_inputs, dt, matrix_func=_leqi_f2) - - res_inter = self.operator(conc_eos0, power) - - qi_inputs = list(zip( - self._prev_rates, bos_res.rates, res_inter.rates, - repeat(prev_dt), repeat(dt))) - - time3, conc_inter = timed_deplete( - self.chain, bos_conc, qi_inputs, dt, matrix_func=_leqi_f3) - time4, conc_eos1 = timed_deplete( - self.chain, conc_inter, qi_inputs, dt, matrix_func=_leqi_f4) - - # store updated rates - self._prev_rates = copy.deepcopy(bos_res.rates) - - return ( - time1 + time2 + time3 + time4, [conc_eos0, conc_eos1], - [bos_res, res_inter]) - - -# Functions to form the special matrix for depletion -def _leqi_f1(chain, inputs): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - dt_l, dt = inputs[2], inputs[3] - return -dt / (12 * dt_l) * f1 + (dt + 6 * dt_l) / (12 * dt_l) * f2 - - -def _leqi_f2(chain, inputs): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - dt_l, dt = inputs[2], inputs[3] - return -5 * dt / (12 * dt_l) * f1 + (5 * dt + 6 * dt_l) / (12 * dt_l) * f2 - - -def _leqi_f3(chain, inputs): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - f3 = chain.form_matrix(inputs[2]) - dt_l, dt = inputs[3], inputs[4] - return -dt**2 / (12 * dt_l * (dt + dt_l)) * f1 + \ - (dt**2 + 6*dt*dt_l + 5*dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \ - dt_l / (12 * (dt + dt_l)) * f3 - - -def _leqi_f4(chain, inputs): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - f3 = chain.form_matrix(inputs[2]) - dt_l, dt = inputs[3], inputs[4] - return -dt**2 / (12 * dt_l * (dt + dt_l)) * f1 + \ - (dt**2 + 2*dt*dt_l + dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \ - (4 * dt * dt_l + 5 * dt_l**2) / (12 * dt_l * (dt + dt_l)) * f3 diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py deleted file mode 100644 index 44b1516aa..000000000 --- a/openmc/deplete/integrator/predictor.py +++ /dev/null @@ -1,81 +0,0 @@ -"""First-order predictor algorithm.""" - -from .abc import Integrator -from .cram import timed_deplete - - -class PredictorIntegrator(Integrator): - r"""Deplete using a first-order predictor algorithm. - - Implements the first-order predictor algorithm. This algorithm is - mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_p &= A(y_n, t_n) \\ - y_{n+1} &= \text{expm}(A_p h) y_n - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - """ - _num_stages = 1 - - def __call__(self, conc, rates, dt, power, _i=None): - """Perform the integration across one time step - - Parameters - ---------- - conc : numpy.ndarray - Initial concentrations for all nuclides in [atom] - rates : openmc.deplete.ReactionRates - Reaction rates from operator - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system in [W] - _i : int or None - Iteration index. Not used - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - conc_list : list of numpy.ndarray - Concentrations at end of interval - op_results : empty list - Kept for consistency with API. No intermediate calls to - operator with predictor - - """ - proc_time, conc_end = timed_deplete(self.chain, conc, rates, dt) - return proc_time, [conc_end], [] diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py deleted file mode 100644 index 4b4ccc1e6..000000000 --- a/openmc/deplete/integrator/si_celi.py +++ /dev/null @@ -1,109 +0,0 @@ -"""The SI-CE/LI CFQ4 integrator.""" - -import copy - -from .abc import SIIntegrator -from .cram import timed_deplete -from .celi import _celi_f1, _celi_f2 -from ..abc import OperatorResult - - -class SICELIIntegrator(SIIntegrator): - r"""Deplete using the SI-CE/LI CFQ4 algorithm. - - Implements the stochastic implicit CE/LI predictor-corrector algorithm - using the `fourth order commutator-free integrator - `_. - - Detailed algorithm can be found in section 3.2 in `Colin Josey's thesis - `_. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - n_steps : int, optional - Number of stochastic iterations per depletion interval. - Must be greater than zero. Default : 10 - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - n_steps : int - Number of stochastic iterations per depletion interval - """ - _num_stages = 2 - - def __call__(self, bos_conc, bos_rates, dt, power, _i): - """Perform the integration across one time step - - Parameters - ---------- - bos_conc : numpy.ndarray - Initial bos_concentrations for all nuclides in [atom] - bos_rates : openmc.deplete.ReactionRates - Reaction rates from operator - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system in [W] - _i : int - Current depletion step index. Unused - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - bos_conc_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final bos_concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulations - """ - proc_time, eos_conc = timed_deplete( - self.chain, bos_conc, bos_rates, dt) - inter_conc = copy.deepcopy(eos_conc) - - # Begin iteration - for j in range(self.n_steps + 1): - inter_res = self.operator(inter_conc, power) - - if j <= 1: - res_bar = copy.deepcopy(inter_res) - else: - rates = 1/j * inter_res.rates + (1 - 1 / j) * res_bar.rates - k = 1/j * inter_res.k + (1 - 1 / j) * res_bar.k - res_bar = OperatorResult(k, rates) - - list_rates = list(zip(bos_rates, res_bar.rates)) - time1, inter_conc = timed_deplete( - self.chain, bos_conc, list_rates, dt, matrix_func=_celi_f1) - time2, inter_conc = timed_deplete( - self.chain, inter_conc, list_rates, dt, matrix_func=_celi_f2) - proc_time += time1 + time2 - - # end iteration - return proc_time, [eos_conc, inter_conc], [res_bar] diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py deleted file mode 100644 index 1e48c039e..000000000 --- a/openmc/deplete/integrator/si_leqi.py +++ /dev/null @@ -1,129 +0,0 @@ -"""The SI-LE/QI CFQ4 integrator.""" - -import copy -from itertools import repeat - -from .abc import SIIntegrator -from .si_celi import SICELIIntegrator -from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 -from .cram import timed_deplete -from ..abc import OperatorResult - - -class SILEQIIntegrator(SIIntegrator): - r"""Deplete using the SI-LE/QI CFQ4 algorithm. - - Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm using - the `fourth order commutator-free integrator `_. - - Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis - `_. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not - cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that - the power is constant over all timesteps. An iterable - indicates potentially different power levels for each timestep. - For a 2D problem, the power can be given in [W/cm] as long - as the "volume" assigned to a depletion material is actually - an area in [cm^2]. Either ``power`` or ``power_density`` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by - initial heavy metal inventory to get total power if ``power`` - is not speficied. - n_steps : int, optional - Number of stochastic iterations per depletion interval. - Must be greater than zero. Default : 10 - - Attributes - ---------- - operator : openmc.deplete.TransportOperator - Operator to perform transport simulations - chain : openmc.deplete.Chain - Depletion chain - timesteps : iterable of float - Size of each depletion interval in [s] - power : iterable of float - Power of the reactor in [W] for each interval in :attr:`timesteps` - n_steps : int - Number of stochastic iterations per depletion interval - """ - _num_stages = 2 - - def __call__(self, bos_conc, bos_rates, dt, power, i): - """Perform the integration across one time step - - Parameters - ---------- - bos_conc : list of numpy.ndarray - Initial concentrations for all nuclides in [atom] for - all depletable materials - bos_rates : list of openmc.deplete.ReactionRates - Reaction rates from operator for all depletable materials - dt : float - Time in [s] for the entire depletion interval - power : float - Power of the system in [W] - i : int - Current depletion step index - - Returns - ------- - proc_time : float - Time spent in CRAM routines for all materials in [s] - conc_list : list of numpy.ndarray - Concentrations at each of the intermediate points with - the final concentration as the last element - op_results : list of openmc.deplete.OperatorResult - Eigenvalue and reaction rates from intermediate transport - simulation - """ - if i == 0: - if self._i_res < 1: - self._prev_rates = bos_rates - # Perform CELI for initial steps - return SICELIIntegrator.__call__( - self, bos_conc, bos_rates, dt, power, i) - prev_res = self.operator.prev_res[-2] - prev_dt = self.timesteps[i] - prev_res.time[0] - self._prev_rates = prev_res.rates[0] - else: - prev_dt = self.timesteps[i - 1] - - # Perform remaining LE/QI - inputs = list(zip(self._prev_rates, bos_rates, - repeat(prev_dt), repeat(dt))) - proc_time, inter_conc = timed_deplete( - self.chain, bos_conc, inputs, dt, matrix_func=_leqi_f1) - time1, eos_conc = timed_deplete( - self.chain, inter_conc, inputs, dt, matrix_func=_leqi_f2) - - proc_time += time1 - inter_conc = copy.deepcopy(eos_conc) - - for j in range(self.n_steps + 1): - inter_res = self.operator(inter_conc, power) - - if j <= 1: - res_bar = copy.deepcopy(inter_res) - else: - rates = 1 / j * inter_res.rates + (1 - 1 / j) * res_bar.rates - k = 1 / j * inter_res.k + (1 - 1 / j) * res_bar.k - res_bar = OperatorResult(k, rates) - - inputs = list(zip(self._prev_rates, bos_rates, res_bar.rates, - repeat(prev_dt), repeat(dt))) - time1, inter_conc = timed_deplete( - self.chain, bos_conc, inputs, dt, matrix_func=_leqi_f3) - time2, inter_conc = timed_deplete( - self.chain, inter_conc, inputs, dt, matrix_func=_leqi_f4) - proc_time += time1 + time2 - - return proc_time, [eos_conc, inter_conc], [res_bar] diff --git a/openmc/deplete/integrators.py b/openmc/deplete/integrators.py new file mode 100644 index 000000000..7637f4fe1 --- /dev/null +++ b/openmc/deplete/integrators.py @@ -0,0 +1,836 @@ +import copy +from itertools import repeat + +from .abc import Integrator, SIIntegrator, OperatorResult +from .cram import timed_deplete +from ._matrix_funcs import ( + cf4_f1, cf4_f2, cf4_f3, cf4_f4, celi_f1, celi_f2, + leqi_f1, leqi_f2, leqi_f3, leqi_f4, rk4_f1, rk4_f4 +) + +__all__ = [ + "PredictorIntegrator", "CECMIntegrator", "CF4Integrator", + "CELIIntegrator", "EPCRK4Integrator", "LEQIIntegrator", + "SICELIIntegrator", "SILEQIIntegrator"] + + +class PredictorIntegrator(Integrator): + r"""Deplete using a first-order predictor algorithm. + + Implements the first-order predictor algorithm. This algorithm is + mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_p &= A(y_n, t_n) \\ + y_{n+1} &= \text{expm}(A_p h) y_n + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 1 + + def __call__(self, conc, rates, dt, power, _i=None): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + _i : int or None + Iteration index. Not used + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at end of interval + op_results : empty list + Kept for consistency with API. No intermediate calls to + operator with predictor + + """ + proc_time, conc_end = timed_deplete(self.chain, conc, rates, dt) + return proc_time, [conc_end], [] + + +class CECMIntegrator(Integrator): + r"""Deplete using the CE/CM algorithm. + + Implements the second order `CE/CM predictor-corrector algorithm + `_. + + "CE/CM" stands for constant extrapolation on predictor and constant + midpoint on corrector. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_p &= A(y_n, t_n) \\ + y_m &= \text{expm}(A_p h/2) y_n \\ + A_c &= A(y_m, t_n + h/2) \\ + y_{n+1} &= \text{expm}(A_c h) y_n + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 2 + + def __call__(self, conc, rates, dt, power, _i=-1): + """Integrate using CE/CM + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + _i : int, optional + Current iteration count. Not used + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from transport simulations + """ + # deplete across first half of inteval + time0, x_middle = timed_deplete(self.chain, conc, rates, dt / 2) + res_middle = self.operator(x_middle, power) + + # deplete across entire interval with BOS concentrations, + # MOS reaction rates + time1, x_end = timed_deplete(self.chain, conc, res_middle.rates, dt) + + return time0 + time1, [x_middle, x_end], [res_middle] + + +class CF4Integrator(Integrator): + r"""Deplete using the CF4 algorithm. + + Implements the fourth order `commutator-free Lie algorithm + `_. + This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + F_1 &= h A(y_0) \\ + y_1 &= \text{expm}(1/2 F_1) y_0 \\ + F_2 &= h A(y_1) \\ + y_2 &= \text{expm}(1/2 F_2) y_0 \\ + F_3 &= h A(y_2) \\ + y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1 \\ + F_4 &= h A(y_3) \\ + y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4) + \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 4 + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + bos_rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + # Step 1: deplete with matrix 1/2*A(y0) + time1, conc_eos1 = timed_deplete( + self.chain, bos_conc, bos_rates, dt, matrix_func=cf4_f1) + res1 = self.operator(conc_eos1, power) + + # Step 2: deplete with matrix 1/2*A(y1) + time2, conc_eos2 = timed_deplete( + self.chain, bos_conc, res1.rates, dt, matrix_func=cf4_f1) + res2 = self.operator(conc_eos2, power) + + # Step 3: deplete with matrix -1/2*A(y0)+A(y2) + list_rates = list(zip(bos_rates, res2.rates)) + time3, conc_eos3 = timed_deplete( + self.chain, conc_eos1, list_rates, dt, matrix_func=cf4_f2) + res3 = self.operator(conc_eos3, power) + + # Step 4: deplete with two matrix exponentials + list_rates = list(zip(bos_rates, res1.rates, res2.rates, res3.rates)) + time4, conc_inter = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=cf4_f3) + time5, conc_eos5 = timed_deplete( + self.chain, conc_inter, list_rates, dt, matrix_func=cf4_f4) + + return (time1 + time2 + time3 + time4 + time5, + [conc_eos1, conc_eos2, conc_eos3, conc_eos5], + [res1, res2, res3]) + + +class CELIIntegrator(Integrator): + r"""Deplete using the CE/LI CFQ4 algorithm. + + Implements the CE/LI Predictor-Corrector algorithm using the `fourth order + commutator-free integrator `_. + + "CE/LI" stands for constant extrapolation on predictor and linear + interpolation on corrector. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_0 &= A(y_n, t_n) \\ + y_p &= \text{expm}(h A_0) y_n \\ + A_1 &= A(y_p, t_n + h) \\ + y_{n+1} &= \text{expm}(\frac{h}{12} A_0 + \frac{5h}{12} A1) + \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 2 + + def __call__(self, bos_conc, rates, dt, power, _i=-1): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + _i : int + Current iteration count. Not used + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + # deplete to end using BOS rates + proc_time, conc_ce = timed_deplete(self.chain, bos_conc, rates, dt) + res_ce = self.operator(conc_ce, power) + + # deplete using two matrix exponentials + list_rates = list(zip(rates, res_ce.rates)) + + time_le1, conc_inter = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=celi_f1) + + time_le2, conc_end = timed_deplete( + self.chain, conc_inter, list_rates, dt, matrix_func=celi_f2) + + return proc_time + time_le1 + time_le1, [conc_ce, conc_end], [res_ce] + + +class EPCRK4Integrator(Integrator): + r"""Deplete using the EPC-RK4 algorithm. + + Implements an extended predictor-corrector algorithm with traditional + Runge-Kutta 4 method. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + F_1 &= h A(y_0) \\ + y_1 &= \text{expm}(1/2 F_1) y_0 \\ + F_2 &= h A(y_1) \\ + y_2 &= \text{expm}(1/2 F_2) y_0 \\ + F_3 &= h A(y_2) \\ + y_3 &= \text{expm}(F_3) y_0 \\ + F_4 &= h A(y_3) \\ + y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 4 + + def __call__(self, conc, rates, dt, power, _i): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + i : int + Current depletion step index, unused. + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + + # Step 1: deplete with matrix A(y0) / 2 + time1, conc1 = timed_deplete( + self.chain, conc, rates, dt, matrix_func=rk4_f1) + res1 = self.operator(conc1, power) + + # Step 2: deplete with matrix A(y1) / 2 + time2, conc2 = timed_deplete( + self.chain, conc, res1.rates, dt, matrix_func=rk4_f1) + res2 = self.operator(conc2, power) + + # Step 3: deplete with matrix A(y2) + time3, conc3 = timed_deplete( + self.chain, conc, res2.rates, dt) + res3 = self.operator(conc3, power) + + # Step 4: deplete with matrix built from weighted rates + list_rates = list(zip(rates, res1.rates, res2.rates, res3.rates)) + time4, conc4 = timed_deplete( + self.chain, conc, list_rates, dt, matrix_func=rk4_f4) + + return (time1 + time2 + time3 + time4, [conc1, conc2, conc3, conc4], + [res1, res2, res3]) + + +class LEQIIntegrator(Integrator): + r"""Deplete using the LE/QI CFQ4 algorithm. + + Implements the LE/QI Predictor-Corrector algorithm using the `fourth order + commutator-free integrator `_. + + "LE/QI" stands for linear extrapolation on predictor and quadratic + interpolation on corrector. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_{last} &= A(y_{n-1}, t_n - h_1) \\ + A_0 &= A(y_n, t_n) \\ + F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0 \\ + F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0 \\ + y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n \\ + A_1 &= A(y_p, t_n + h_2) \\ + F_3 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + + \frac{h_2 (5 h_1^2 + 6 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + + \frac{h_2 h_1)}{12 (h_1 + h_2)} A_1 \\ + F_4 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + + \frac{h_2 (h_1^2 + 2 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + + \frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1 \\ + y_{n+1} &= \text{expm}(F_4) \text{expm}(F_3) y_n + \end{aligned} + + It is initialized using the CE/LI algorithm. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 2 + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + if i == 0: + if self._i_res < 1: # need at least previous transport solution + self._prev_rates = bos_rates + return CELIIntegrator.__call__( + self, bos_conc, bos_rates, dt, power, i) + prev_res = self.operator.prev_res[-2] + prev_dt = self.timesteps[i] - prev_res.time[0] + self._prev_rates = prev_res.rates[0] + else: + prev_dt = self.timesteps[i - 1] + + # Remaining LE/QI + bos_res = self.operator(bos_conc, power) + + le_inputs = list(zip( + self._prev_rates, bos_res.rates, repeat(prev_dt), repeat(dt))) + + time1, conc_inter = timed_deplete( + self.chain, bos_conc, le_inputs, dt, matrix_func=leqi_f1) + time2, conc_eos0 = timed_deplete( + self.chain, conc_inter, le_inputs, dt, matrix_func=leqi_f2) + + res_inter = self.operator(conc_eos0, power) + + qi_inputs = list(zip( + self._prev_rates, bos_res.rates, res_inter.rates, + repeat(prev_dt), repeat(dt))) + + time3, conc_inter = timed_deplete( + self.chain, bos_conc, qi_inputs, dt, matrix_func=leqi_f3) + time4, conc_eos1 = timed_deplete( + self.chain, conc_inter, qi_inputs, dt, matrix_func=leqi_f4) + + # store updated rates + self._prev_rates = copy.deepcopy(bos_res.rates) + + return ( + time1 + time2 + time3 + time4, [conc_eos0, conc_eos1], + [bos_res, res_inter]) + + +class SICELIIntegrator(SIIntegrator): + r"""Deplete using the SI-CE/LI CFQ4 algorithm. + + Implements the stochastic implicit CE/LI predictor-corrector algorithm + using the `fourth order commutator-free integrator + `_. + + Detailed algorithm can be found in section 3.2 in `Colin Josey's thesis + `_. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval + """ + _num_stages = 2 + + def __call__(self, bos_conc, bos_rates, dt, power, _i): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : numpy.ndarray + Initial bos_concentrations for all nuclides in [atom] + bos_rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + _i : int + Current depletion step index. Unused + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + bos_conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final bos_concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + proc_time, eos_conc = timed_deplete( + self.chain, bos_conc, bos_rates, dt) + inter_conc = copy.deepcopy(eos_conc) + + # Begin iteration + for j in range(self.n_steps + 1): + inter_res = self.operator(inter_conc, power) + + if j <= 1: + res_bar = copy.deepcopy(inter_res) + else: + rates = 1/j * inter_res.rates + (1 - 1 / j) * res_bar.rates + k = 1/j * inter_res.k + (1 - 1 / j) * res_bar.k + res_bar = OperatorResult(k, rates) + + list_rates = list(zip(bos_rates, res_bar.rates)) + time1, inter_conc = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=celi_f1) + time2, inter_conc = timed_deplete( + self.chain, inter_conc, list_rates, dt, matrix_func=celi_f2) + proc_time += time1 + time2 + + # end iteration + return proc_time, [eos_conc, inter_conc], [res_bar] + + +class SILEQIIntegrator(SIIntegrator): + r"""Deplete using the SI-LE/QI CFQ4 algorithm. + + Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm + using the `fourth order commutator-free integrator + `_. + + Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis + `_. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval + """ + _num_stages = 2 + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : list of numpy.ndarray + Initial concentrations for all nuclides in [atom] for + all depletable materials + bos_rates : list of openmc.deplete.ReactionRates + Reaction rates from operator for all depletable materials + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + if i == 0: + if self._i_res < 1: + self._prev_rates = bos_rates + # Perform CELI for initial steps + return SICELIIntegrator.__call__( + self, bos_conc, bos_rates, dt, power, i) + prev_res = self.operator.prev_res[-2] + prev_dt = self.timesteps[i] - prev_res.time[0] + self._prev_rates = prev_res.rates[0] + else: + prev_dt = self.timesteps[i - 1] + + # Perform remaining LE/QI + inputs = list(zip(self._prev_rates, bos_rates, + repeat(prev_dt), repeat(dt))) + proc_time, inter_conc = timed_deplete( + self.chain, bos_conc, inputs, dt, matrix_func=leqi_f1) + time1, eos_conc = timed_deplete( + self.chain, inter_conc, inputs, dt, matrix_func=leqi_f2) + + proc_time += time1 + inter_conc = copy.deepcopy(eos_conc) + + for j in range(self.n_steps + 1): + inter_res = self.operator(inter_conc, power) + + if j <= 1: + res_bar = copy.deepcopy(inter_res) + else: + rates = 1 / j * inter_res.rates + (1 - 1 / j) * res_bar.rates + k = 1 / j * inter_res.k + (1 - 1 / j) * res_bar.k + res_bar = OperatorResult(k, rates) + + inputs = list(zip(self._prev_rates, bos_rates, res_bar.rates, + repeat(prev_dt), repeat(dt))) + time1, inter_conc = timed_deplete( + self.chain, bos_conc, inputs, dt, matrix_func=leqi_f3) + time2, inter_conc = timed_deplete( + self.chain, inter_conc, inputs, dt, matrix_func=leqi_f4) + proc_time += time1 + time2 + + return proc_time, [eos_conc, inter_conc], [res_bar] diff --git a/tests/unit_tests/test_deplete_cram.py b/tests/unit_tests/test_deplete_cram.py index 21b93d17e..8987fbd7a 100644 --- a/tests/unit_tests/test_deplete_cram.py +++ b/tests/unit_tests/test_deplete_cram.py @@ -6,7 +6,7 @@ Compares a few Mathematica matrix exponentials to CRAM16/CRAM48. from pytest import approx import numpy as np import scipy.sparse as sp -from openmc.deplete.integrator import CRAM16, CRAM48 +from openmc.deplete.cram import CRAM16, CRAM48 def test_CRAM16(): From 2514918591fc54077173b2c8230245eea4c58df8 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 11:08:28 -0500 Subject: [PATCH 32/32] Consistently document unused depletion index The LEQI-type integrators are the only ones that need the current depletion index. All other integrators now display the same _i=None for this index in the call signature, and denote the variable as optional and unused in the docstring --- openmc/deplete/integrators.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/integrators.py b/openmc/deplete/integrators.py index 7637f4fe1..67106aa3e 100644 --- a/openmc/deplete/integrators.py +++ b/openmc/deplete/integrators.py @@ -142,7 +142,7 @@ class CECMIntegrator(Integrator): """ _num_stages = 2 - def __call__(self, conc, rates, dt, power, _i=-1): + def __call__(self, conc, rates, dt, power, _i=None): """Integrate using CE/CM Parameters @@ -232,7 +232,7 @@ class CF4Integrator(Integrator): """ _num_stages = 4 - def __call__(self, bos_conc, bos_rates, dt, power, i): + def __call__(self, bos_conc, bos_rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -245,8 +245,8 @@ class CF4Integrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system in [W] - i : int - Current depletion step index + _i : int, optional + Current depletion step index. Not used Returns ------- @@ -339,7 +339,7 @@ class CELIIntegrator(Integrator): """ _num_stages = 2 - def __call__(self, bos_conc, rates, dt, power, _i=-1): + def __call__(self, bos_conc, rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -352,7 +352,7 @@ class CELIIntegrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system in [W] - _i : int + _i : int, optional Current iteration count. Not used Returns @@ -433,7 +433,7 @@ class EPCRK4Integrator(Integrator): """ _num_stages = 4 - def __call__(self, conc, rates, dt, power, _i): + def __call__(self, conc, rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -446,7 +446,7 @@ class EPCRK4Integrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system in [W] - i : int + _i : int, optional Current depletion step index, unused. Returns @@ -663,7 +663,7 @@ class SICELIIntegrator(SIIntegrator): """ _num_stages = 2 - def __call__(self, bos_conc, bos_rates, dt, power, _i): + def __call__(self, bos_conc, bos_rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -676,8 +676,8 @@ class SICELIIntegrator(SIIntegrator): Time in [s] for the entire depletion interval power : float Power of the system in [W] - _i : int - Current depletion step index. Unused + _i : int, optional + Current depletion step index. Not used Returns -------