@@ -541,17 +541,15 @@ class Integrator(ABC):
541541 iterable of float. Alternatively, units can be specified for each step
542542 by passing an iterable of (value, unit) tuples.
543543 power : float or iterable of float, optional
544- Power of the reactor in [W]. A single value indicates that
545- the power is constant over all timesteps. An iterable
546- indicates potentially different power levels for each timestep.
547- For a 2D problem, the power can be given in [W/cm] as long
548- as the "volume" assigned to a depletion material is actually
549- an area in [cm^2]. Either ``power``, ``power_density``, or
544+ Power of the reactor in [W]. A single value indicates that the power is
545+ constant over all timesteps. An iterable indicates potentially different
546+ power levels for each timestep. For a 2D problem, the power can be given
547+ in [W/cm] as long as the "volume" assigned to a depletion material is
548+ actually an area in [cm^2]. Either ``power``, ``power_density``, or
550549 ``source_rates`` must be specified.
551550 power_density : float or iterable of float, optional
552- Power density of the reactor in [W/gHM]. It is multiplied by
553- initial heavy metal inventory to get total power if ``power``
554- is not specified.
551+ Power density of the reactor in [W/gHM]. It is multiplied by initial
552+ heavy metal inventory to get total power if ``power`` is not specified.
555553 source_rates : float or iterable of float, optional
556554 Source rate in [neutron/sec] or neutron flux in [neutron/s-cm^2] for
557555 each interval in :attr:`timesteps`
@@ -563,8 +561,8 @@ class Integrator(ABC):
563561 and 'MWd/kg' indicates that the values are given in burnup (MW-d of
564562 energy deposited per kilogram of initial heavy metal).
565563 solver : str or callable, optional
566- If a string, must be the name of the solver responsible for
567- solving the Bateman equations. Current options are:
564+ If a string, must be the name of the solver responsible for solving the
565+ Bateman equations. Current options are:
568566
569567 * ``cram16`` - 16th order IPF CRAM
570568 * ``cram48`` - 48th order IPF CRAM [default]
@@ -573,15 +571,22 @@ class Integrator(ABC):
573571 :attr:`solver`.
574572
575573 .. versionadded:: 0.12
574+ substeps : int, optional
575+ Number of substeps per depletion interval. When greater than 1, each
576+ interval is subdivided into `substeps` identical sub-intervals and LU
577+ factorizations may be reused across them, improving accuracy for
578+ nuclides with large decay-constant × timestep products.
579+
580+ .. versionadded:: 0.15.4
576581 continue_timesteps : bool, optional
577582 Whether or not to treat the current solve as a continuation of a
578583 previous simulation. Defaults to `False`. When `False`, the depletion
579584 steps provided are appended to any previous steps. If `True`, the
580- timesteps provided to the `Integrator` must exacly match any that
581- exist in the `prev_results` passed to the `Operator`. The `power`,
582- `power_density`, or `source_rates` must match as well. The
583- method of specifying `power`, `power_density`, or
584- `source_rates` should be the same as the initial run.
585+ timesteps provided to the `Integrator` must exacly match any that exist
586+ in the `prev_results` passed to the `Operator`. The `power`,
587+ `power_density`, or `source_rates` must match as well. The method of
588+ specifying `power`, `power_density`, or `source_rates` should be the
589+ same as the initial run.
585590
586591 .. versionadded:: 0.15.1
587592
@@ -601,15 +606,19 @@ class Integrator(ABC):
601606 :math:`\frac{\partial}{\partial t}\vec{n} = A_i\vec{n}_i` with a step
602607 size :math:`t_i`. Can be configured using the ``solver`` argument.
603608 User-supplied functions are expected to have the following signature:
604- ``solver(A, n0, t) -> n1`` where
609+ ``solver(A, n0, t, substeps=1) -> n1``, where
610+
611+ * ``A`` is a :class:`scipy.sparse.csc_array` making up the depletion
612+ matrix
613+ * ``n0`` is a 1-D :class:`numpy.ndarray` of initial compositions for
614+ a given material in atoms/cm3
615+ * ``t`` is a float of the time step size in seconds
616+ * ``substeps`` is an optional integer number of substeps, and
617+ * ``n1`` is a :class:`numpy.ndarray` of compositions at the next
618+ time step. Expected to be of the same shape as ``n0``
605619
606- * ``A`` is a :class:`scipy.sparse.csc_array` making up the
607- depletion matrix
608- * ``n0`` is a 1-D :class:`numpy.ndarray` of initial compositions
609- for a given material in atoms/cm3
610- * ``t`` is a float of the time step size in seconds, and
611- * ``n1`` is a :class:`numpy.ndarray` of compositions at the
612- next time step. Expected to be of the same shape as ``n0``
620+ Solvers that do not support multiple substeps should raise an exception
621+ when ``substeps > 1``.
613622
614623 transfer_rates : openmc.deplete.TransferRates
615624 Transfer rates for the depletion system used to model continuous
@@ -632,6 +641,7 @@ def __init__(
632641 source_rates : Optional [Union [float , Sequence [float ]]] = None ,
633642 timestep_units : str = 's' ,
634643 solver : str = "cram48" ,
644+ substeps : int = 1 ,
635645 continue_timesteps : bool = False ,
636646 ):
637647 if continue_timesteps and operator .prev_res is None :
@@ -653,6 +663,8 @@ def __init__(
653663 # Normalize timesteps and source rates
654664 seconds , source_rates = _normalize_timesteps (
655665 timesteps , source_rates , timestep_units , operator )
666+ check_type ("substeps" , substeps , Integral )
667+ check_greater_than ("substeps" , substeps , 0 )
656668
657669 if continue_timesteps :
658670 # Get timesteps and source rates from previous results
@@ -684,6 +696,7 @@ def __init__(
684696
685697 self .timesteps = np .asarray (seconds )
686698 self .source_rates = np .asarray (source_rates )
699+ self .substeps = substeps
687700
688701 self .transfer_rates = None
689702 self .external_source_rates = None
@@ -721,23 +734,32 @@ def solver(self, func):
721734 self ._solver = func
722735 return
723736
737+ params = list (sig .parameters .values ())
738+
724739 # Inspect arguments
725- if len (sig .parameters ) != 3 :
726- raise ValueError ("Function {} does not support three arguments: "
727- "{!s}" .format (func , sig ))
740+ if len (params ) != 4 :
741+ raise ValueError (
742+ "Function {} must support four arguments "
743+ "(A, n0, t, substeps=1): {!s}"
744+ .format (func , sig ))
728745
729- for ix , param in enumerate (sig .parameters .values ()):
730- if param .kind in {param .KEYWORD_ONLY , param .VAR_KEYWORD }:
746+ for ix , param in enumerate (params ):
747+ if param .kind in {param .KEYWORD_ONLY , param .VAR_KEYWORD ,
748+ param .VAR_POSITIONAL }:
731749 raise ValueError (
732750 f"Keyword arguments like { ix } at position { param } are not allowed" )
733751
752+ if len (params ) == 4 and params [3 ].default != 1 :
753+ raise ValueError (
754+ f"Fourth solver argument must default to 1, not { params [3 ].default } " )
755+
734756 self ._solver = func
735757
736758 def _timed_deplete (self , n , rates , dt , i = None , matrix_func = None ):
737759 start = time .time ()
738760 results = deplete (
739761 self ._solver , self .chain , n , rates , dt , i , matrix_func ,
740- self .transfer_rates , self .external_source_rates )
762+ self .transfer_rates , self .external_source_rates , self . substeps )
741763
742764 # Clip unphysical negative number densities
743765 for r in results :
@@ -1205,17 +1227,15 @@ class SIIntegrator(Integrator):
12051227 iterable of float. Alternatively, units can be specified for each step
12061228 by passing an iterable of (value, unit) tuples.
12071229 power : float or iterable of float, optional
1208- Power of the reactor in [W]. A single value indicates that
1209- the power is constant over all timesteps. An iterable
1210- indicates potentially different power levels for each timestep.
1211- For a 2D problem, the power can be given in [W/cm] as long
1212- as the "volume" assigned to a depletion material is actually
1213- an area in [cm^2]. Either ``power``, ``power_density``, or
1230+ Power of the reactor in [W]. A single value indicates that the power is
1231+ constant over all timesteps. An iterable indicates potentially different
1232+ power levels for each timestep. For a 2D problem, the power can be given
1233+ in [W/cm] as long as the "volume" assigned to a depletion material is
1234+ actually an area in [cm^2]. Either ``power``, ``power_density``, or
12141235 ``source_rates`` must be specified.
12151236 power_density : float or iterable of float, optional
1216- Power density of the reactor in [W/gHM]. It is multiplied by
1217- initial heavy metal inventory to get total power if ``power``
1218- is not specified.
1237+ Power density of the reactor in [W/gHM]. It is multiplied by initial
1238+ heavy metal inventory to get total power if ``power`` is not specified.
12191239 source_rates : float or iterable of float, optional
12201240 Source rate in [neutron/sec] or neutron flux in [neutron/s-cm^2] for
12211241 each interval in :attr:`timesteps`
@@ -1227,11 +1247,11 @@ class SIIntegrator(Integrator):
12271247 that the values are given in burnup (MW-d of energy deposited per
12281248 kilogram of initial heavy metal).
12291249 n_steps : int, optional
1230- Number of stochastic iterations per depletion interval.
1231- Must be greater than zero. Default : 10
1250+ Number of stochastic iterations per depletion interval. Must be greater
1251+ than zero. Default : 10
12321252 solver : str or callable, optional
1233- If a string, must be the name of the solver responsible for
1234- solving the Bateman equations. Current options are:
1253+ If a string, must be the name of the solver responsible for solving the
1254+ Bateman equations. Current options are:
12351255
12361256 * ``cram16`` - 16th order IPF CRAM
12371257 * ``cram48`` - 48th order IPF CRAM [default]
@@ -1240,16 +1260,23 @@ class SIIntegrator(Integrator):
12401260 :attr:`solver`.
12411261
12421262 .. versionadded:: 0.12
1263+ substeps : int, optional
1264+ Number of substeps per depletion interval. When greater than 1, each
1265+ interval is subdivided into `substeps` identical sub-intervals and LU
1266+ factorizations may be reused across them, improving accuracy for
1267+ nuclides with large decay-constant × timestep products.
1268+
1269+ .. versionadded:: 0.15.4
12431270 continue_timesteps : bool, optional
12441271 Whether or not to treat the current solve as a continuation of a
1245- previous simulation. Defaults to `False`. If `False`, all time
1246- steps and source rates will be run in an append fashion and will run
1247- after whatever time steps exist, if any. If `True`, the timesteps
1248- provided to the `Integrator` must match exactly those that exist
1249- in the `prev_results` passed to the `Opereator`. The `power`,
1250- `power_density`, or `source_rates` must match as well. The
1251- method of specifying `power `, `power_density`, or
1252- `source_rates` should be the same as the initial run.
1272+ previous simulation. Defaults to `False`. If `False`, all time steps and
1273+ source rates will be run in an append fashion and will run after
1274+ whatever time steps exist, if any. If `True`, the timesteps provided to
1275+ the `Integrator` must match exactly those that exist in the
1276+ `prev_results` passed to the `Opereator`. The `power`, `power_density `,
1277+ or `source_rates` must match as well. The method of specifying `power`,
1278+ `power_density `, or `source_rates` should be the same as the initial
1279+ run.
12531280
12541281 .. versionadded:: 0.15.1
12551282
@@ -1270,15 +1297,19 @@ class SIIntegrator(Integrator):
12701297 :math:`\frac{\partial}{\partial t}\vec{n} = A_i\vec{n}_i` with a step
12711298 size :math:`t_i`. Can be configured using the ``solver`` argument.
12721299 User-supplied functions are expected to have the following signature:
1273- ``solver(A, n0, t) -> n1`` where
1300+ ``solver(A, n0, t, substeps=1) -> n1``, where
1301+
1302+ * ``A`` is a :class:`scipy.sparse.csc_array` making up the depletion
1303+ matrix
1304+ * ``n0`` is a 1-D :class:`numpy.ndarray` of initial compositions for
1305+ a given material in atoms/cm3
1306+ * ``t`` is a float of the time step size in seconds
1307+ * ``substeps`` is an optional integer number of substeps, and
1308+ * ``n1`` is a :class:`numpy.ndarray` of compositions at the next
1309+ time step. Expected to be of the same shape as ``n0``
12741310
1275- * ``A`` is a :class:`scipy.sparse.csc_array` making up the
1276- depletion matrix
1277- * ``n0`` is a 1-D :class:`numpy.ndarray` of initial compositions
1278- for a given material in atoms/cm3
1279- * ``t`` is a float of the time step size in seconds, and
1280- * ``n1`` is a :class:`numpy.ndarray` of compositions at the
1281- next time step. Expected to be of the same shape as ``n0``
1311+ Solvers that do not support multiple substeps should raise an exception
1312+ when ``substeps > 1``.
12821313
12831314 .. versionadded:: 0.12
12841315
@@ -1294,13 +1325,16 @@ def __init__(
12941325 timestep_units : str = 's' ,
12951326 n_steps : int = 10 ,
12961327 solver : str = "cram48" ,
1328+ substeps : int = 1 ,
12971329 continue_timesteps : bool = False ,
12981330 ):
12991331 check_type ("n_steps" , n_steps , Integral )
13001332 check_greater_than ("n_steps" , n_steps , 0 )
13011333 super ().__init__ (
13021334 operator , timesteps , power , power_density , source_rates ,
1303- timestep_units = timestep_units , solver = solver , continue_timesteps = continue_timesteps )
1335+ timestep_units = timestep_units , solver = solver ,
1336+ substeps = substeps ,
1337+ continue_timesteps = continue_timesteps )
13041338 self .n_steps = n_steps
13051339
13061340 def _get_bos_data_from_operator (self , step_index , step_power , n_bos ):
@@ -1430,7 +1464,7 @@ class DepSystemSolver(ABC):
14301464 """
14311465
14321466 @abstractmethod
1433- def __call__ (self , A , n0 , dt ):
1467+ def __call__ (self , A , n0 , dt , substeps = 1 ):
14341468 """Solve the linear system of equations for depletion
14351469
14361470 Parameters
@@ -1443,6 +1477,8 @@ def __call__(self, A, n0, dt):
14431477 material or an atom density
14441478 dt : float
14451479 Time [s] of the specific interval to be solved
1480+ substeps : int, optional
1481+ Number of substeps to use when the solver supports substepping.
14461482
14471483 Returns
14481484 -------
0 commit comments