From 49363c364c8e60690a271bd5b638fdd424f43194 Mon Sep 17 00:00:00 2001 From: church89 Date: Fri, 15 May 2026 09:29:10 +0200 Subject: [PATCH 01/15] add initial patch to fix transfer rates with destination material used with external transfer rate --- openmc/deplete/abc.py | 16 +++++----- openmc/deplete/pool.py | 71 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 66bd7148d33..aa9865d6b16 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -1028,10 +1028,10 @@ def add_transfer_rate( self.transfer_rates = TransferRates( self.operator, materials, len(self.timesteps)) - if self.external_source_rates is not None and destination_material: - raise ValueError('Currently is not possible to set a transfer rate ' - 'with destination matrial in combination with ' - 'external source rates.') + # if self.external_source_rates is not None and destination_material: + # raise ValueError('Currently is not possible to set a transfer rate ' + # 'with destination matrial in combination with ' + # 'external source rates.') self.transfer_rates.set_transfer_rate( material, components, transfer_rate, transfer_rate_units, @@ -1074,10 +1074,10 @@ def add_external_source_rate( self.external_source_rates = ExternalSourceRates( self.operator, materials, len(self.timesteps)) - if self.transfer_rates is not None and self.transfer_rates.index_transfer: - raise ValueError('Currently is not possible to set an external ' - 'source rate in combination with transfer rates ' - 'with destination matrial.') + # if self.transfer_rates is not None and self.transfer_rates.index_transfer: + # raise ValueError('Currently is not possible to set an external ' + # 'source rate in combination with transfer rates ' + # 'with destination matrial.') self.external_source_rates.set_external_source_rate( material, composition, rate, rate_units, timesteps) diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index 19ad0ada503..09e1bb07bb4 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -6,10 +6,10 @@ from multiprocessing import Pool import numpy as np -from scipy.sparse import hstack +from scipy.sparse import hstack, vstack from openmc.mpi import comm -from .._sparse_compat import block_array +from .._sparse_compat import block_array, csc_array # Configurable switch that enables / disables the use of # multiprocessing routines during depletion @@ -87,7 +87,6 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, list contains the number of [atom] of each nuclide. """ - fission_yields = chain.fission_yields if len(fission_yields) == 1: fission_yields = repeat(fission_yields[0]) @@ -102,7 +101,6 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, else: matrices = map(matrix_func, repeat(chain), rates, fission_yields, *matrix_args) - if (transfer_rates is not None and current_timestep in transfer_rates.external_timesteps): # Calculate transfer rate terms as diagonal matrices @@ -119,7 +117,34 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, matrices[mat_idx] = chain.add_redox_term(matrices[mat_idx], transfer_rates.redox[mat_id][0], transfer_rates.redox[mat_id][1]) - + + # Add external sources if present + if (external_source_rates is not None and + current_timestep in external_source_rates.external_timesteps): + sources = map(chain.form_ext_source_term, repeat(external_source_rates), + repeat(current_timestep), external_source_rates.local_mats) + + # stack vector column at the end of the matrix + matrices = [ + hstack([matrix, source]) + for matrix, source in zip(matrices, sources) + ] + + # Add a last row of zeroes to the matrices and append 1 to the last row + # of the nuclide vectors if the matrix is not square + # otherwise if the matrix is square, add a row and column of zeroes to the + # matrix and append 0 to the last row of the nuclide vector + for i, matrix in enumerate(matrices): + if matrix.shape[0] + 1 == matrix.shape[1]: + matrices[i] = vstack([matrix, csc_array((1, matrix.shape[1]))]) + n[i] = np.append(n[i], 1.0) + # else: + # matrices[i] = hstack( + # [vstack([matrix, csc_array((1, matrix.shape[1]))]), + # csc_array((matrix.shape[0] + 1, 1))]) + # n[i] = np.append(n[i], 0.0) + + # Set transfer rate terms with destination material if present if current_timestep in transfer_rates.index_transfer: # Gather all on comm.rank 0 matrices = comm.gather(matrices) @@ -136,12 +161,20 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, transfer_matrix = chain.form_rr_term(transfer_rates, current_timestep, mat_pair) - # check if destination material has a redox control if mat_pair[0] in transfer_rates.redox: transfer_matrix = chain.add_redox_term(transfer_matrix, transfer_rates.redox[mat_pair[0]][0], transfer_rates.redox[mat_pair[0]][1]) + # Add external source rates if present + if (external_source_rates is not None and + current_timestep in external_source_rates.external_timesteps): + if len(external_source_rates.get_components(mat_pair[0], current_timestep)) > 0: + transfer_matrix = vstack([transfer_matrix, + csc_array((1, transfer_matrix.shape[1]))]) + if len(external_source_rates.get_components(mat_pair[1], current_timestep)) > 0: + transfer_matrix = hstack([transfer_matrix, + csc_array((transfer_matrix.shape[0], 1))]) transfer_pair[mat_pair] = transfer_matrix # Combine all matrices together in a single matrix of matrices @@ -172,6 +205,15 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Split back the nuclide vector result into the original form n_result = np.split(n_result, np.cumsum([len(i) for i in n])[:-1]) + # Remove extra value at the end of the nuclide vectors if external source rates are present + if (external_source_rates is not None and + current_timestep in external_source_rates.external_timesteps): + external_source_rates.reformat_nuclide_vectors(n) + external_source_rates.reformat_nuclide_vectors(n_result) + #Clamp negative values to 0 + for n_material in n_result: + np.maximum(n_material, 0.0, out=n_material) + else: n_result = None @@ -179,10 +221,11 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, n_result = comm.bcast(n_result) # Distribute results across MPI n_result = _distribute(n_result) - + breakpoint() return n_result - if (external_source_rates is not None and + # If only external source rates are present + elif (external_source_rates is not None and current_timestep in external_source_rates.external_timesteps): # Calculate external source term vectors sources = map(chain.form_ext_source_term, repeat(external_source_rates), @@ -193,14 +236,13 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, hstack([matrix, source]) for matrix, source in zip(matrices, sources) ] - # Add a last row of zeroes to the matrices and append 1 to the last row # of the nuclide vectors for i, matrix in enumerate(matrices): - if not np.equal(*matrix.shape): - matrix.resize(matrix.shape[1], matrix.shape[1]) + if matrix.shape[0] + 1 == matrix.shape[1]: + matrices[i] = vstack([matrix, csc_array((1, matrix.shape[1]))]) n[i] = np.append(n[i], 1.0) - + inputs = zip(matrices, n, repeat(dt), repeat(substeps)) if USE_MULTIPROCESSING: @@ -209,10 +251,13 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, else: n_result = list(starmap(func, inputs)) - # Remove extra value at the end of the nuclide vectors + # Remove extra value at the end of the nuclide vectors if external source rates are present if (external_source_rates is not None and current_timestep in external_source_rates.external_timesteps): external_source_rates.reformat_nuclide_vectors(n) external_source_rates.reformat_nuclide_vectors(n_result) + #Clamp negative values to 0 + for n_material in n_result: + np.maximum(n_material, 0.0, out=n_material) return n_result From 698f633886a2eded59727e82c214ca212936ee4b Mon Sep 17 00:00:00 2001 From: church89 Date: Fri, 22 May 2026 18:50:23 +0200 Subject: [PATCH 02/15] debugging wrong dimension of nuclide vector --- openmc/deplete/abc.py | 3 ++- openmc/deplete/pool.py | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index aa9865d6b16..6001dbdc50d 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -941,8 +941,9 @@ def integrate( n, res, keff_search_root = self._get_bos_data(i, source_rate, n) # Solve Bateman equations over time interval + breakpoint() proc_time, n_end = self(n, res.rates, dt, source_rate, i) - + breakpoint() StepResult.save( self.operator, n, diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index 09e1bb07bb4..ab4bd62ace1 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -143,7 +143,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # [vstack([matrix, csc_array((1, matrix.shape[1]))]), # csc_array((matrix.shape[0] + 1, 1))]) # n[i] = np.append(n[i], 0.0) - + # Set transfer rate terms with destination material if present if current_timestep in transfer_rates.index_transfer: # Gather all on comm.rank 0 @@ -157,7 +157,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Calculate transfer rate terms as diagonal matrices transfer_pair = {} - for mat_pair in transfer_rates.index_transfer[current_timestep]: + for mat_pair in dict.fromkeys(transfer_rates.index_transfer[current_timestep]): transfer_matrix = chain.form_rr_term(transfer_rates, current_timestep, mat_pair) @@ -176,7 +176,6 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, transfer_matrix = hstack([transfer_matrix, csc_array((transfer_matrix.shape[0], 1))]) transfer_pair[mat_pair] = transfer_matrix - # Combine all matrices together in a single matrix of matrices # to be solved in one go n_rows = n_cols = len(transfer_rates.burnable_mats) @@ -216,12 +215,12 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, else: n_result = None - # Braodcast result to other ranks n_result = comm.bcast(n_result) + n = comm.bcast(n) # Distribute results across MPI n_result = _distribute(n_result) - breakpoint() + n = _distribute(n) return n_result # If only external source rates are present From 76cf70b4f8b505a7d905ab5e8811abea826322d6 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 3 Jun 2026 15:59:39 +0200 Subject: [PATCH 03/15] fix n size, duplicating n_solve --- openmc/deplete/abc.py | 3 +-- openmc/deplete/pool.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 6001dbdc50d..aa9865d6b16 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -941,9 +941,8 @@ def integrate( n, res, keff_search_root = self._get_bos_data(i, source_rate, n) # Solve Bateman equations over time interval - breakpoint() proc_time, n_end = self(n, res.rates, dt, source_rate, i) - breakpoint() + StepResult.save( self.operator, n, diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index ab4bd62ace1..a8798402814 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -101,6 +101,8 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, else: matrices = map(matrix_func, repeat(chain), rates, fission_yields, *matrix_args) + + n_solve = n if (transfer_rates is not None and current_timestep in transfer_rates.external_timesteps): # Calculate transfer rate terms as diagonal matrices @@ -121,6 +123,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Add external sources if present if (external_source_rates is not None and current_timestep in external_source_rates.external_timesteps): + n_solve = [arr.copy() for arr in n] sources = map(chain.form_ext_source_term, repeat(external_source_rates), repeat(current_timestep), external_source_rates.local_mats) @@ -137,7 +140,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, for i, matrix in enumerate(matrices): if matrix.shape[0] + 1 == matrix.shape[1]: matrices[i] = vstack([matrix, csc_array((1, matrix.shape[1]))]) - n[i] = np.append(n[i], 1.0) + n_solve[i] = np.append(n_solve[i], 1.0) # else: # matrices[i] = hstack( # [vstack([matrix, csc_array((1, matrix.shape[1]))]), @@ -148,7 +151,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, if current_timestep in transfer_rates.index_transfer: # Gather all on comm.rank 0 matrices = comm.gather(matrices) - n = comm.gather(n) + n = comm.gather(n_solve) if comm.rank == 0: # Expand lists @@ -207,7 +210,6 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Remove extra value at the end of the nuclide vectors if external source rates are present if (external_source_rates is not None and current_timestep in external_source_rates.external_timesteps): - external_source_rates.reformat_nuclide_vectors(n) external_source_rates.reformat_nuclide_vectors(n_result) #Clamp negative values to 0 for n_material in n_result: @@ -217,15 +219,14 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, n_result = None # Braodcast result to other ranks n_result = comm.bcast(n_result) - n = comm.bcast(n) # Distribute results across MPI n_result = _distribute(n_result) - n = _distribute(n) return n_result # If only external source rates are present elif (external_source_rates is not None and current_timestep in external_source_rates.external_timesteps): + n_solve = [arr.copy() for arr in n] # Calculate external source term vectors sources = map(chain.form_ext_source_term, repeat(external_source_rates), repeat(current_timestep), external_source_rates.local_mats) @@ -240,9 +241,9 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, for i, matrix in enumerate(matrices): if matrix.shape[0] + 1 == matrix.shape[1]: matrices[i] = vstack([matrix, csc_array((1, matrix.shape[1]))]) - n[i] = np.append(n[i], 1.0) + n_solve[i] = np.append(n_solve[i], 1.0) - inputs = zip(matrices, n, repeat(dt), repeat(substeps)) + inputs = zip(matrices, n_solve, repeat(dt), repeat(substeps)) if USE_MULTIPROCESSING: with Pool(NUM_PROCESSES) as pool: @@ -253,7 +254,6 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Remove extra value at the end of the nuclide vectors if external source rates are present if (external_source_rates is not None and current_timestep in external_source_rates.external_timesteps): - external_source_rates.reformat_nuclide_vectors(n) external_source_rates.reformat_nuclide_vectors(n_result) #Clamp negative values to 0 for n_material in n_result: From bd78e118c1f341894218981e483c1d3e59e2bc70 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 3 Jun 2026 16:38:58 +0200 Subject: [PATCH 04/15] fix docstring typo --- openmc/deplete/pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index a8798402814..3a7cddb8d82 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -62,7 +62,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, Time in [s] to deplete for current_timestep : int Current timestep index - maxtrix_func : callable, optional + matrix_func : callable, optional Function to form the depletion matrix after calling ``matrix_func(chain, rates, fission_yields)``, where ``fission_yields = {parent: {product: yield_frac}}`` Expected to return the depletion matrix required by From 98aeee9e3d5f75e8daa60b5f005fc83cb84613e5 Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 4 Jun 2026 14:25:09 +0200 Subject: [PATCH 05/15] add missing external source rates docs --- docs/source/methods/depletion.rst | 54 ++++++++++++++++++++++ docs/source/usersguide/depletion.rst | 68 ++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) diff --git a/docs/source/methods/depletion.rst b/docs/source/methods/depletion.rst index edcf2c3f534..0b0756363f3 100644 --- a/docs/source/methods/depletion.rst +++ b/docs/source/methods/depletion.rst @@ -339,3 +339,57 @@ where: Note that mass conservation is guaranteed by transferring the number of atoms directly. + +-------------- +External Source Rates +-------------- + +OpenMC allows the addition of external source rates to the depletion matrix. +This is useful for modeling the feed or removal of fixed amounts of nuclides +to or from a depletable material. Rates are specified as a mass flow (default +units of g/s) and converted to atom/s source terms for the nuclides in the +composition vector. A positive rate corresponds to feed and a negative rate to +removal. + +Mathematically, this adds an external source term :math:`S_i` to the depletion +equation: + +.. math:: + + \frac{dN_i}{dt} = \sum_j A_{ij} N_j + S_i + +The resulting linear system is non-homogeneous but can be recast in homogeneous +form by augmenting the nuclide vector with a constant component equal to unity: + +.. math:: + + \frac{d}{dt}\begin{bmatrix}\mathbf{N}\\ 1\end{bmatrix} = + \begin{bmatrix} + \mathbf{A} & \mathbf{S}\\ + \mathbf{0} & 0 + \end{bmatrix} + \begin{bmatrix} + \mathbf{N}\\ + 1 + \end{bmatrix} + +where :math:`\mathbf{S}` is the vector of external source rates in atom/s. + +The resulting system can be solved with the same integration algorithms that are +used in the absence of the external source term. + +External source rates with transfer rates coupling materials +------------------------------------------------------------ + +In the presence of external source rates and coupled transfer rates between +materials, the off-diagonal transfer blocks must be augmented to the same +dimensions as the corresponding diagonal blocks. Using the transfer example +above, if an external source rate is applied to material 1 (the losing +material), the coupling matrix :math:`\mathbf{T_{21}}` is extended with an +additional column of zeroes so that its column count matches the augmented +:math:`\mathbf{A_{11}}`. If the external source is applied to material 2 (the +receiving material), :math:`\mathbf{T_{21}}` instead receives an additional row +of zeroes so that its row count matches the augmented :math:`\mathbf{A_{22}}`. + + + diff --git a/docs/source/usersguide/depletion.rst b/docs/source/usersguide/depletion.rst index 261900ce61a..b77a60f8645 100644 --- a/docs/source/usersguide/depletion.rst +++ b/docs/source/usersguide/depletion.rst @@ -449,3 +449,71 @@ to transfer xenon from one material to another, you'd use:: ... integrator.add_transfer_rate(mat1, ['Xe'], 0.1, destination_material=mat2) + +External Source Rates +===================== + +External source rates define a fixed mass feed or removal of nuclides to or +from a depletable material. Unlike transfer rates, which are proportional to +the instantaneous nuclide inventory, external source rates add a constant +source term to the depletion equations. This can be useful to model batch +refueling, makeup fuel addition, or fixed-rate off-gas removal. + +External source rates are defined by calling the +:meth:`~openmc.deplete.abc.Integrator.add_external_source_rate()` method +directly from one of the Integrator classes:: + + ... + integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power) + integrator.add_external_source_rate(...) + +Defining external source rates +------------------------------ + +The :meth:`~openmc.deplete.abc.Integrator.add_external_source_rate()` method +requires a :class:`~openmc.Material` instance (alternatively, a material id or +the name) as the depletable material, a composition dictionary giving the +relative weight fractions of elements and/or nuclides in the feed or removal +stream, and a mass flow rate. + +.. caution:: + + Make sure you set the rate value with the right sign. A positive rate + corresponds to feed, while a negative rate corresponds to removal. This is + the opposite convention used for transfer rates. + +The ``rate_units`` argument specifies the units for the mass flow rate. The +default is ``g/s``, but ``g/min``, ``g/h``, ``g/d``, and ``g/a`` are also valid +options. + +For example, to feed uranium-235 into a material at 10 g/day, you'd use:: + + mat1 = openmc.Material(material_id=1, name='fuel') + + ... + + integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power) + composition = {'U235': 1.0} + # by openmc.Material object + integrator.add_external_source_rate(mat1, composition, 10, rate_units='g/d') + # or by material id + integrator.add_external_source_rate(1, composition, 10, rate_units='g/d') + # or by material name + integrator.add_external_source_rate('fuel', composition, 10, rate_units='g/d') + +Composition keys may be nuclides (e.g., ``'U235'``) or naturally abundant +elements (e.g., ``'U'``). When an element is specified, the mass flow is +distributed across its naturally occurring isotopes according to their natural +abundances. + +The optional ``timesteps`` argument restricts the external source rate to +specific depletion step indices. If omitted, the rate is applied at every step. + +Combining with transfer rates +----------------------------- + +External source rates can be used together with transfer rates, including +transfers between materials via ``destination_material``. See +:ref:`methods_depletion` for the augmented-matrix formulation used when both +features are active. + From a22d21aaa97b3aceb633456478f6815d3f1022d9 Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 4 Jun 2026 14:26:15 +0200 Subject: [PATCH 06/15] remove commented --- openmc/deplete/pool.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index 3a7cddb8d82..f29f60438ca 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -133,19 +133,12 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, for matrix, source in zip(matrices, sources) ] - # Add a last row of zeroes to the matrices and append 1 to the last row - # of the nuclide vectors if the matrix is not square - # otherwise if the matrix is square, add a row and column of zeroes to the - # matrix and append 0 to the last row of the nuclide vector + # Homogenize diagonal matrices to be square for i, matrix in enumerate(matrices): if matrix.shape[0] + 1 == matrix.shape[1]: + # Add a row of zeroes to the matrix and append 1 to the last row of the nuclide vector matrices[i] = vstack([matrix, csc_array((1, matrix.shape[1]))]) n_solve[i] = np.append(n_solve[i], 1.0) - # else: - # matrices[i] = hstack( - # [vstack([matrix, csc_array((1, matrix.shape[1]))]), - # csc_array((matrix.shape[0] + 1, 1))]) - # n[i] = np.append(n[i], 0.0) # Set transfer rate terms with destination material if present if current_timestep in transfer_rates.index_transfer: @@ -179,8 +172,8 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, transfer_matrix = hstack([transfer_matrix, csc_array((transfer_matrix.shape[0], 1))]) transfer_pair[mat_pair] = transfer_matrix - # Combine all matrices together in a single matrix of matrices - # to be solved in one go + # Combine all matrices together in a single block matrix of matrices + # to be solved on one rank n_rows = n_cols = len(transfer_rates.burnable_mats) rows = [] for row in range(n_rows): From 4296dd02aad86f25a6af24b470f5f5b23dcf80cc Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 4 Jun 2026 14:27:02 +0200 Subject: [PATCH 07/15] add regression test of external source rate and transfer rate with destination material --- .../deplete_with_transfer_rates/test.py | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/regression_tests/deplete_with_transfer_rates/test.py b/tests/regression_tests/deplete_with_transfer_rates/test.py index 461287f6e6f..1597eb31bd7 100644 --- a/tests/regression_tests/deplete_with_transfer_rates/test.py +++ b/tests/regression_tests/deplete_with_transfer_rates/test.py @@ -126,3 +126,38 @@ def test_external_source_rates(run_in_tmpdir, model, rate, power, ref_result): assert_atoms_equal(res_ref, res_test, tol=1e-3) assert_reaction_rates_equal(res_ref, res_test, tol=1e-3) + +@pytest.mark.parametrize("external_source_rate, transfer_rate, power, ref_result", [ + (1e-1, 1e-1, 174., 'depletion_with_ext_source_and_transfer'), +]) +def test_external_source_rates_with_transfer_rates(run_in_tmpdir, model, external_source_rate, + transfer_rate, power, ref_result): + """Tests external_rates depletion class with external source rates and transfer rates""" + + chain_file = Path(__file__).parents[2] / 'chain_simple.xml' + + external_source_vector = {'U': 1} + + op = CoupledOperator(model, chain_file) + op.round_number = True + integrator = openmc.deplete.PredictorIntegrator( + op, [1], power, timestep_units='d') + integrator.add_external_source_rate('f', external_source_vector, external_source_rate) + integrator.add_transfer_rate('f', ['U235'], transfer_rate, destination_material='w') + integrator.integrate() + + # Get path to test and reference results + path_test = op.output_dir / 'depletion_results.h5' + path_reference = Path(__file__).with_name(f'ref_{ref_result}.h5') + + # If updating results, do so and return + if config['update']: + shutil.copyfile(str(path_test), str(path_reference)) + return + + # Load the reference/test results + res_ref = openmc.deplete.Results(path_reference) + res_test = openmc.deplete.Results(path_test) + + assert_atoms_equal(res_ref, res_test, tol=1e-3) + assert_reaction_rates_equal(res_ref, res_test, tol=1e-3) \ No newline at end of file From e8745971f73dae2f7e326e1df6a8c5793089c8e1 Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 4 Jun 2026 15:13:33 +0200 Subject: [PATCH 08/15] add new regression result file --- ...ef_depletion_with_ext_source_and_transfer.h5 | Bin 0 -> 33872 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source_and_transfer.h5 diff --git a/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source_and_transfer.h5 b/tests/regression_tests/deplete_with_transfer_rates/ref_depletion_with_ext_source_and_transfer.h5 new file mode 100644 index 0000000000000000000000000000000000000000..d13d210139f2d1bb995a0f8e9132db6d0f8d13ef GIT binary patch literal 33872 zcmeHPOKclO7#`b6>$U+LO2I{yYBf|Xh)2?<)I3TS?6j$bXm}JNfylbargg~+I|&AX zFi=nmt(0RfMLALlP=zWHsT_PMAS9%;2q{N%L(oGwR3wM0%V!{WBvW>g}VHHQ6mC&5MS5nYCoY<5J$dA0T0Phl3#gsz$0r^ zc|l4KQ2imFPB=&Due{XSDx@arhoo#Vwnm2xB(VHnA<)+PY`bz8{UuG_XAV%HLZY7f ztFvF%i8kA!NBBjZ?A;?)A{ig(7XiPdk)31ce2-=Z(Lq3hFGK@b2kDD-vb|4!cPL4U9;u;pz&EsimJ9`nNAvM*T#pEj(SVGw zSoH{iZyn?*`cs(t4*+PQz%G*Lp9T~bDPMl!P*iaWcF*lET|pydrz55Hj#2iYrmd^8 z=B^M^#25JEPU4jl?pf9tXTMmkKexR#yf)$Ot*+ zsmU#cdRdB#tz|T31sBEq@5HN4=#kSS*2uMoU&Q;nt(5fhruB=%_c{@_Q#@j=xJQ;7 z8R#AEldF{aPz#wbMDqsGeBCjAcD;Y(;H3+HwB*-0wRZXbm?|yAX1PuZNU1*h9_yZl z`>o_;o&1uQ3Ll6Zdfv`c1>|+3o{IOVFL^oFTkq)P+;`@#T#y5d03*N%FanGKBftnS z0*nA7zz8q`i~u80WeB+KZ>DIUhxa$D?1oiobKLefLyrAI>@T%1lONcZbM8+rc3%$d zTI#;s<(Z7+tRH@I+ zYH-{m0u8z**a=g-gW3@wLn-Rt(3_xm=0U9hK8!z9SYL+X*@ty{NDDFfNTFVq;&kjp zTGcBsKRfZtpI_Z=pPR*o2Kqw%!+l4rLi1Uf!E75&VV1zaiUy))KY`p_|ZY*h)H_P58Ppv#Sn1ghroP9g(}4l=lobM zKWcLAEk1vw8nuE+ddv?3f<;jT-1rfqeuP4m;)ioxSS~;E?Jdra@!eX(Bt_;25Mx;k z0XKda)Q?c6Qv49~1M3jkE0-Vm`L^5oYVvXIkx7m*KX8Xx7DK>|A13u9l&KUyLe1KO zLH5e!2mQ?0_4$1I32o0L$Cw`k1dE~wxbY)R{Ro9B#SdezwqTOIa`}OMOt<-C_Gz+5 zjxj%QhglXwz>Oah)Q?c6Qv5KV)E3fYuUvkhoo@UH(0gDeImZ0J9cEb!0XKeRS_-w% z@0M$S7qv%{O}qEV$bs&rJ!*-iq?%F=>SS?v*KT_SCZ$qS@vxE|mR3+wl|%bXm05vH zpL^AcomBTU?O(vI^EnFJ8?*FW1wH3_(soAH8Kij>dd_*(AiH52PoU=pR<1?zotL4@~h72xq!azUi(?y1jo^ zD^EgIzSrlq4fsXKuF>bMNguRBI$H*nDJ*Yf(3GFbU-%tc9uM7vJ-WHM_t)UJ6Nkn7 zuTOG)USH<0cF%@K2FdqS<()2J;Wpwi+qoj}(D%>d_+JV1i>3DmZ{zX2|2Obpw8Y)I z^;tLfkBZ@8CRP9*xC4E$AuAD&^(HByf4HkR-fbZ&^iLFou;NFp{*hSkFl7zJ_4~KU zf#F1#)tQjh*fr$;UqoxXW%cxQCaqYa>sV)EU|}L-ck;Tix;p^3juBu47y(9r5nu!ufdwMqwr+IW2OOmJBJ#0+2yRt^G-T`#2OSuYs8URXIdpIgcZFanGKBftnO5P`+67t-^r z7utBeut4NljS*l37y(9r5m>zlZ22a2_S+qoBIldCpV@nDU-a71?u!$@pNKA($J;i1 zQNML+rX~H?ciE>dd|CK!4#BI2Z6{;d(ma`>{Mip<4Go`O-F_w_HvM~K?uXNnD Date: Thu, 4 Jun 2026 15:35:48 +0200 Subject: [PATCH 09/15] remove commented stuff --- openmc/deplete/abc.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index aa9865d6b16..3e70b5fa726 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -1028,11 +1028,6 @@ def add_transfer_rate( self.transfer_rates = TransferRates( self.operator, materials, len(self.timesteps)) - # if self.external_source_rates is not None and destination_material: - # raise ValueError('Currently is not possible to set a transfer rate ' - # 'with destination matrial in combination with ' - # 'external source rates.') - self.transfer_rates.set_transfer_rate( material, components, transfer_rate, transfer_rate_units, timesteps, destination_material) @@ -1074,11 +1069,6 @@ def add_external_source_rate( self.external_source_rates = ExternalSourceRates( self.operator, materials, len(self.timesteps)) - # if self.transfer_rates is not None and self.transfer_rates.index_transfer: - # raise ValueError('Currently is not possible to set an external ' - # 'source rate in combination with transfer rates ' - # 'with destination matrial.') - self.external_source_rates.set_external_source_rate( material, composition, rate, rate_units, timesteps) From 4f91b84d9ae56383618ce1ec902e9de504fcb811 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 24 Jul 2026 08:18:41 -0500 Subject: [PATCH 10/15] Update documentation --- docs/source/methods/depletion.rst | 37 ++++++++++++++-------------- docs/source/usersguide/depletion.rst | 20 ++++++--------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/docs/source/methods/depletion.rst b/docs/source/methods/depletion.rst index 0b0756363f3..ecdfb9531c6 100644 --- a/docs/source/methods/depletion.rst +++ b/docs/source/methods/depletion.rst @@ -340,19 +340,19 @@ where: Note that mass conservation is guaranteed by transferring the number of atoms directly. --------------- +--------------------- External Source Rates --------------- +--------------------- OpenMC allows the addition of external source rates to the depletion matrix. This is useful for modeling the feed or removal of fixed amounts of nuclides to or from a depletable material. Rates are specified as a mass flow (default -units of g/s) and converted to atom/s source terms for the nuclides in the +units of [g/s]) and converted to [atom/s] source terms for the nuclides in the composition vector. A positive rate corresponds to feed and a negative rate to removal. -Mathematically, this adds an external source term :math:`S_i` to the depletion -equation: +Mathematically, this is represented as an external source term :math:`S_i` to +the depletion equation: .. math:: @@ -363,33 +363,32 @@ form by augmenting the nuclide vector with a constant component equal to unity: .. math:: - \frac{d}{dt}\begin{bmatrix}\mathbf{N}\\ 1\end{bmatrix} = + \frac{d}{dt}\begin{bmatrix}\mathbf{n}\\ 1\end{bmatrix} = \begin{bmatrix} - \mathbf{A} & \mathbf{S}\\ + \mathbf{A} & \mathbf{s}\\ \mathbf{0} & 0 \end{bmatrix} \begin{bmatrix} - \mathbf{N}\\ + \mathbf{n}\\ 1 \end{bmatrix} -where :math:`\mathbf{S}` is the vector of external source rates in atom/s. - -The resulting system can be solved with the same integration algorithms that are +where :math:`\mathbf{s}` is the vector of external source rates in [atom/s]. The +resulting system can be solved with the same integration algorithms that are used in the absence of the external source term. External source rates with transfer rates coupling materials ------------------------------------------------------------ In the presence of external source rates and coupled transfer rates between -materials, the off-diagonal transfer blocks must be augmented to the same -dimensions as the corresponding diagonal blocks. Using the transfer example -above, if an external source rate is applied to material 1 (the losing -material), the coupling matrix :math:`\mathbf{T_{21}}` is extended with an -additional column of zeroes so that its column count matches the augmented -:math:`\mathbf{A_{11}}`. If the external source is applied to material 2 (the -receiving material), :math:`\mathbf{T_{21}}` instead receives an additional row -of zeroes so that its row count matches the augmented :math:`\mathbf{A_{22}}`. +materials, the off-diagonal transfer blocks must match the dimensions of the +corresponding diagonal blocks. Using the transfer example above, if an external +source rate is applied to material 1 (the losing material), the coupling matrix +:math:`\mathbf{T_{21}}` is extended with an additional column of zeroes so that +its column count matches the augmented :math:`\mathbf{A_{11}}`. If the external +source is applied to material 2 (the receiving material), +:math:`\mathbf{T_{21}}` instead receives an additional row of zeroes so that its +row count matches the augmented :math:`\mathbf{A_{22}}`. diff --git a/docs/source/usersguide/depletion.rst b/docs/source/usersguide/depletion.rst index 44faf3d2fe1..4e52fefde6a 100644 --- a/docs/source/usersguide/depletion.rst +++ b/docs/source/usersguide/depletion.rst @@ -451,6 +451,7 @@ to transfer xenon from one material to another, you'd use:: integrator.add_transfer_rate(mat1, ['Xe'], 0.1, destination_material=mat2) External Source Rates +===================== External source rates define a fixed mass feed or removal of nuclides to or from a depletable material. Unlike transfer rates, which are proportional to @@ -470,10 +471,10 @@ Defining external source rates ------------------------------ The :meth:`~openmc.deplete.abc.Integrator.add_external_source_rate()` method -requires a :class:`~openmc.Material` instance (alternatively, a material id or -the name) as the depletable material, a composition dictionary giving the -relative weight fractions of elements and/or nuclides in the feed or removal -stream, and a mass flow rate. +requires a :class:`~openmc.Material` instance (or a material ID or the name) as +the depletable material, a composition dictionary giving the relative weight +fractions of elements and/or nuclides in the feed or removal stream, and a mass +flow rate. .. caution:: @@ -485,20 +486,14 @@ The ``rate_units`` argument specifies the units for the mass flow rate. The default is ``g/s``, but ``g/min``, ``g/h``, ``g/d``, and ``g/a`` are also valid options. -For example, to feed uranium-235 into a material at 10 g/day, you'd use:: - - mat1 = openmc.Material(material_id=1, name='fuel') +For example, to feed U235 into a material at 10 g/day, you'd use:: + mat = openmc.Material() ... integrator = openmc.deplete.PredictorIntegrator(op, time_steps, power) composition = {'U235': 1.0} - # by openmc.Material object integrator.add_external_source_rate(mat1, composition, 10, rate_units='g/d') - # or by material id - integrator.add_external_source_rate(1, composition, 10, rate_units='g/d') - # or by material name - integrator.add_external_source_rate('fuel', composition, 10, rate_units='g/d') Composition keys may be nuclides (e.g., ``'U235'``) or naturally abundant elements (e.g., ``'U'``). When an element is specified, the mass flow is @@ -517,6 +512,7 @@ transfers between materials via ``destination_material``. See features are active. Comparing to Other Codes +======================== Comparing depletion results from OpenMC with those from another code, such as MCNP or Serpent, requires more than constructing equivalent transport models. From 71e9ebd25486c49671943e220d088b0bd6206853 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 24 Jul 2026 08:18:59 -0500 Subject: [PATCH 11/15] Introduce transfer_active and external_active variables --- openmc/deplete/pool.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index f29f60438ca..64478d7fe95 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -102,9 +102,14 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, matrices = map(matrix_func, repeat(chain), rates, fission_yields, *matrix_args) + # Determine if transfer rates or external source rates are active + transfer_active = transfer_rates is not None and \ + current_timestep in transfer_rates.external_timesteps + external_active = external_source_rates is not None and \ + current_timestep in external_source_rates.external_timesteps + n_solve = n - if (transfer_rates is not None and - current_timestep in transfer_rates.external_timesteps): + if transfer_active: # Calculate transfer rate terms as diagonal matrices transfers = map(chain.form_rr_term, repeat(transfer_rates), repeat(current_timestep), transfer_rates.local_mats) @@ -119,10 +124,9 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, matrices[mat_idx] = chain.add_redox_term(matrices[mat_idx], transfer_rates.redox[mat_id][0], transfer_rates.redox[mat_id][1]) - + # Add external sources if present - if (external_source_rates is not None and - current_timestep in external_source_rates.external_timesteps): + if external_active: n_solve = [arr.copy() for arr in n] sources = map(chain.form_ext_source_term, repeat(external_source_rates), repeat(current_timestep), external_source_rates.local_mats) @@ -163,13 +167,12 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, transfer_rates.redox[mat_pair[0]][0], transfer_rates.redox[mat_pair[0]][1]) # Add external source rates if present - if (external_source_rates is not None and - current_timestep in external_source_rates.external_timesteps): + if external_active: if len(external_source_rates.get_components(mat_pair[0], current_timestep)) > 0: - transfer_matrix = vstack([transfer_matrix, + transfer_matrix = vstack([transfer_matrix, csc_array((1, transfer_matrix.shape[1]))]) if len(external_source_rates.get_components(mat_pair[1], current_timestep)) > 0: - transfer_matrix = hstack([transfer_matrix, + transfer_matrix = hstack([transfer_matrix, csc_array((transfer_matrix.shape[0], 1))]) transfer_pair[mat_pair] = transfer_matrix # Combine all matrices together in a single block matrix of matrices @@ -201,13 +204,12 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, n_result = np.split(n_result, np.cumsum([len(i) for i in n])[:-1]) # Remove extra value at the end of the nuclide vectors if external source rates are present - if (external_source_rates is not None and - current_timestep in external_source_rates.external_timesteps): + if external_active: external_source_rates.reformat_nuclide_vectors(n_result) #Clamp negative values to 0 for n_material in n_result: np.maximum(n_material, 0.0, out=n_material) - + else: n_result = None # Braodcast result to other ranks @@ -217,8 +219,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, return n_result # If only external source rates are present - elif (external_source_rates is not None and - current_timestep in external_source_rates.external_timesteps): + elif external_active: n_solve = [arr.copy() for arr in n] # Calculate external source term vectors sources = map(chain.form_ext_source_term, repeat(external_source_rates), @@ -235,7 +236,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, if matrix.shape[0] + 1 == matrix.shape[1]: matrices[i] = vstack([matrix, csc_array((1, matrix.shape[1]))]) n_solve[i] = np.append(n_solve[i], 1.0) - + inputs = zip(matrices, n_solve, repeat(dt), repeat(substeps)) if USE_MULTIPROCESSING: @@ -245,8 +246,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, n_result = list(starmap(func, inputs)) # Remove extra value at the end of the nuclide vectors if external source rates are present - if (external_source_rates is not None and - current_timestep in external_source_rates.external_timesteps): + if external_active: external_source_rates.reformat_nuclide_vectors(n_result) #Clamp negative values to 0 for n_material in n_result: From 0d06a777741018bbeb756f4bcc97e5c17a273a14 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 24 Jul 2026 08:49:44 -0500 Subject: [PATCH 12/15] Fix comments --- openmc/deplete/pool.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index 64478d7fe95..3486f49e320 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -206,7 +206,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Remove extra value at the end of the nuclide vectors if external source rates are present if external_active: external_source_rates.reformat_nuclide_vectors(n_result) - #Clamp negative values to 0 + # Clamp negative values to 0 for n_material in n_result: np.maximum(n_material, 0.0, out=n_material) @@ -248,7 +248,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Remove extra value at the end of the nuclide vectors if external source rates are present if external_active: external_source_rates.reformat_nuclide_vectors(n_result) - #Clamp negative values to 0 + # Clamp negative values to 0 for n_material in n_result: np.maximum(n_material, 0.0, out=n_material) From a374645c7a7052f46fb4b496146593b4837975fa Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 24 Jul 2026 08:59:35 -0500 Subject: [PATCH 13/15] Reduce repeated code with _add_external_source --- openmc/deplete/pool.py | 59 +++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 32 deletions(-) diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index 3486f49e320..be178b06d8d 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -41,6 +41,29 @@ def _distribute(items): return items[j:j + chunk_size] j += chunk_size + +def _add_external_source( + matrices, n, chain, external_source_rates, current_timestep +): + """Augment depletion matrices and nuclide vectors with external sources.""" + sources = map(chain.form_ext_source_term, repeat(external_source_rates), + repeat(current_timestep), external_source_rates.local_mats) + matrices = [ + hstack([matrix, source]) + for matrix, source in zip(matrices, sources) + ] + n_solve = [arr.copy() for arr in n] + + # Homogenize the augmented matrices and nuclide vectors + for i, matrix in enumerate(matrices): + if matrix.shape[0] + 1 == matrix.shape[1]: + matrices[i] = vstack( + [matrix, csc_array((1, matrix.shape[1]))]) + n_solve[i] = np.append(n_solve[i], 1.0) + + return matrices, n_solve + + def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, transfer_rates=None, external_source_rates=None, substeps=1, *matrix_args): @@ -127,22 +150,8 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Add external sources if present if external_active: - n_solve = [arr.copy() for arr in n] - sources = map(chain.form_ext_source_term, repeat(external_source_rates), - repeat(current_timestep), external_source_rates.local_mats) - - # stack vector column at the end of the matrix - matrices = [ - hstack([matrix, source]) - for matrix, source in zip(matrices, sources) - ] - - # Homogenize diagonal matrices to be square - for i, matrix in enumerate(matrices): - if matrix.shape[0] + 1 == matrix.shape[1]: - # Add a row of zeroes to the matrix and append 1 to the last row of the nuclide vector - matrices[i] = vstack([matrix, csc_array((1, matrix.shape[1]))]) - n_solve[i] = np.append(n_solve[i], 1.0) + matrices, n_solve = _add_external_source( + matrices, n, chain, external_source_rates, current_timestep) # Set transfer rate terms with destination material if present if current_timestep in transfer_rates.index_transfer: @@ -220,22 +229,8 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # If only external source rates are present elif external_active: - n_solve = [arr.copy() for arr in n] - # Calculate external source term vectors - sources = map(chain.form_ext_source_term, repeat(external_source_rates), - repeat(current_timestep), external_source_rates.local_mats) - - # stack vector column at the end of the matrix - matrices = [ - hstack([matrix, source]) - for matrix, source in zip(matrices, sources) - ] - # Add a last row of zeroes to the matrices and append 1 to the last row - # of the nuclide vectors - for i, matrix in enumerate(matrices): - if matrix.shape[0] + 1 == matrix.shape[1]: - matrices[i] = vstack([matrix, csc_array((1, matrix.shape[1]))]) - n_solve[i] = np.append(n_solve[i], 1.0) + matrices, n_solve = _add_external_source( + matrices, n, chain, external_source_rates, current_timestep) inputs = zip(matrices, n_solve, repeat(dt), repeat(substeps)) From f462e9a89aca6b06b980869b486180aa505fc071 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 24 Jul 2026 09:03:19 -0500 Subject: [PATCH 14/15] Make sure reformat_nuclide_vectors is called on local result, not global --- openmc/deplete/pool.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index be178b06d8d..afa075d2d9b 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -184,6 +184,7 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, transfer_matrix = hstack([transfer_matrix, csc_array((transfer_matrix.shape[0], 1))]) transfer_pair[mat_pair] = transfer_matrix + # Combine all matrices together in a single block matrix of matrices # to be solved on one rank n_rows = n_cols = len(transfer_rates.burnable_mats) @@ -211,20 +212,20 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Split back the nuclide vector result into the original form n_result = np.split(n_result, np.cumsum([len(i) for i in n])[:-1]) - - # Remove extra value at the end of the nuclide vectors if external source rates are present - if external_active: - external_source_rates.reformat_nuclide_vectors(n_result) - # Clamp negative values to 0 - for n_material in n_result: - np.maximum(n_material, 0.0, out=n_material) - else: n_result = None - # Braodcast result to other ranks + + # Broadcast result to other MPI ranks and then distribute n_result = comm.bcast(n_result) - # Distribute results across MPI n_result = _distribute(n_result) + + # Remove extra values based on the materials local to each rank + if external_active: + external_source_rates.reformat_nuclide_vectors(n_result) + # Clamp negative values to 0 + for n_material in n_result: + np.maximum(n_material, 0.0, out=n_material) + return n_result # If only external source rates are present From 87681ceb61fa1f8da56e022e25fd93e2ef726dfd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 24 Jul 2026 10:26:17 -0500 Subject: [PATCH 15/15] Remove redundant clamping --- openmc/deplete/pool.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openmc/deplete/pool.py b/openmc/deplete/pool.py index afa075d2d9b..35d000ca36e 100644 --- a/openmc/deplete/pool.py +++ b/openmc/deplete/pool.py @@ -222,9 +222,6 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Remove extra values based on the materials local to each rank if external_active: external_source_rates.reformat_nuclide_vectors(n_result) - # Clamp negative values to 0 - for n_material in n_result: - np.maximum(n_material, 0.0, out=n_material) return n_result @@ -244,8 +241,5 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None, # Remove extra value at the end of the nuclide vectors if external source rates are present if external_active: external_source_rates.reformat_nuclide_vectors(n_result) - # Clamp negative values to 0 - for n_material in n_result: - np.maximum(n_material, 0.0, out=n_material) return n_result