2020-05-09 14:38:46 -04:00
|
|
|
"""Dedicated module containing depletion function
|
|
|
|
|
|
|
|
|
|
Provided to avoid some circular imports
|
|
|
|
|
"""
|
2020-06-18 16:02:34 -04:00
|
|
|
from itertools import repeat, starmap
|
2020-05-09 14:38:46 -04:00
|
|
|
from multiprocessing import Pool
|
2023-07-06 14:48:36 -05:00
|
|
|
|
2023-04-28 05:11:17 +02:00
|
|
|
import numpy as np
|
2026-07-25 05:37:43 +02:00
|
|
|
from scipy.sparse import hstack, vstack
|
2023-07-06 14:48:36 -05:00
|
|
|
|
2023-06-16 06:00:20 +02:00
|
|
|
from openmc.mpi import comm
|
2026-07-25 05:37:43 +02:00
|
|
|
from .._sparse_compat import block_array, csc_array
|
2020-05-09 14:38:46 -04:00
|
|
|
|
2020-06-18 16:02:34 -04:00
|
|
|
# Configurable switch that enables / disables the use of
|
|
|
|
|
# multiprocessing routines during depletion
|
|
|
|
|
USE_MULTIPROCESSING = True
|
|
|
|
|
|
2022-03-22 06:53:47 -05:00
|
|
|
# Allow user to override the number of worker processes to use for depletion
|
|
|
|
|
# calculations
|
|
|
|
|
NUM_PROCESSES = None
|
|
|
|
|
|
2023-06-16 06:00:20 +02:00
|
|
|
def _distribute(items):
|
|
|
|
|
"""Distribute items across MPI communicator
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
items : list
|
|
|
|
|
List of items of distribute
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
list
|
|
|
|
|
Items assigned to process that called
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
min_size, extra = divmod(len(items), comm.size)
|
|
|
|
|
j = 0
|
|
|
|
|
for i in range(comm.size):
|
|
|
|
|
chunk_size = min_size + int(i < extra)
|
|
|
|
|
if comm.rank == i:
|
|
|
|
|
return items[j:j + chunk_size]
|
|
|
|
|
j += chunk_size
|
2020-06-18 16:02:34 -04:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2025-05-12 22:48:21 +02:00
|
|
|
def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None,
|
2026-04-22 16:24:34 -07:00
|
|
|
transfer_rates=None, external_source_rates=None, substeps=1,
|
|
|
|
|
*matrix_args):
|
2020-05-09 14:38:46 -04:00
|
|
|
"""Deplete materials using given reaction rates for a specified time
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
func : callable
|
2023-07-06 14:48:36 -05:00
|
|
|
Function to use to get new compositions. Expected to have the signature
|
2026-04-22 16:24:34 -07:00
|
|
|
``func(A, n0, t, substeps=1) -> n1``.
|
2020-05-09 14:38:46 -04:00
|
|
|
chain : openmc.deplete.Chain
|
|
|
|
|
Depletion chain
|
2023-07-06 14:48:36 -05:00
|
|
|
n : list of numpy.ndarray
|
|
|
|
|
List of atom number arrays for each material. Each array in the list
|
|
|
|
|
contains the number of [atom] of each nuclide.
|
2020-05-09 14:38:46 -04:00
|
|
|
rates : openmc.deplete.ReactionRates
|
|
|
|
|
Reaction rates (from transport operator)
|
|
|
|
|
dt : float
|
|
|
|
|
Time in [s] to deplete for
|
2025-05-12 22:48:21 +02:00
|
|
|
current_timestep : int
|
|
|
|
|
Current timestep index
|
2026-07-25 05:37:43 +02:00
|
|
|
matrix_func : callable, optional
|
2023-07-06 14:48:36 -05:00
|
|
|
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
|
2020-05-14 08:11:01 -04:00
|
|
|
``func``
|
2023-04-28 05:11:17 +02:00
|
|
|
transfer_rates : openmc.deplete.TransferRates, Optional
|
2025-05-12 22:48:21 +02:00
|
|
|
Transfer rates for continuous removal/feed.
|
2023-04-28 05:11:17 +02:00
|
|
|
|
2023-10-31 16:55:19 -05:00
|
|
|
.. versionadded:: 0.14.0
|
2025-05-12 22:48:21 +02:00
|
|
|
external_source_rates : openmc.deplete.ExternalSourceRates, Optional
|
|
|
|
|
External source rates for continuous removal/feed.
|
|
|
|
|
|
|
|
|
|
.. versionadded:: 0.15.3
|
2026-04-22 16:24:34 -07:00
|
|
|
substeps : int, optional
|
|
|
|
|
Number of substeps to pass to solvers that support substepping.
|
2023-04-28 05:11:17 +02:00
|
|
|
matrix_args: Any, optional
|
2023-02-16 14:50:23 +01:00
|
|
|
Additional arguments passed to matrix_func
|
2020-05-09 14:38:46 -04:00
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
2023-07-06 14:48:36 -05:00
|
|
|
n_result : list of numpy.ndarray
|
|
|
|
|
Updated list of atom number arrays for each material. Each array in the
|
|
|
|
|
list contains the number of [atom] of each nuclide.
|
2020-05-14 08:11:01 -04:00
|
|
|
|
2020-05-09 14:38:46 -04:00
|
|
|
"""
|
|
|
|
|
fission_yields = chain.fission_yields
|
|
|
|
|
if len(fission_yields) == 1:
|
|
|
|
|
fission_yields = repeat(fission_yields[0])
|
2023-07-06 14:48:36 -05:00
|
|
|
elif len(fission_yields) != len(n):
|
2020-05-09 14:38:46 -04:00
|
|
|
raise ValueError(
|
2020-06-18 16:02:34 -04:00
|
|
|
"Number of material fission yield distributions {} is not "
|
|
|
|
|
"equal to the number of compositions {}".format(
|
2023-07-06 14:48:36 -05:00
|
|
|
len(fission_yields), len(n)))
|
2020-05-09 14:38:46 -04:00
|
|
|
|
|
|
|
|
if matrix_func is None:
|
|
|
|
|
matrices = map(chain.form_matrix, rates, fission_yields)
|
|
|
|
|
else:
|
2023-01-18 14:04:41 +01:00
|
|
|
matrices = map(matrix_func, repeat(chain), rates, fission_yields,
|
2023-02-16 14:50:23 +01:00
|
|
|
*matrix_args)
|
2020-05-09 14:38:46 -04:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
# 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_active:
|
2023-04-28 05:11:17 +02:00
|
|
|
# Calculate transfer rate terms as diagonal matrices
|
|
|
|
|
transfers = map(chain.form_rr_term, repeat(transfer_rates),
|
2025-05-12 22:48:21 +02:00
|
|
|
repeat(current_timestep), transfer_rates.local_mats)
|
|
|
|
|
|
2023-04-28 05:11:17 +02:00
|
|
|
# Subtract transfer rate terms from Bateman matrices
|
|
|
|
|
matrices = [matrix - transfer for (matrix, transfer) in zip(matrices,
|
|
|
|
|
transfers)]
|
|
|
|
|
|
2025-11-12 23:45:48 +01:00
|
|
|
if transfer_rates.redox:
|
|
|
|
|
for mat_idx, mat_id in enumerate(transfer_rates.local_mats):
|
|
|
|
|
if mat_id in transfer_rates.redox:
|
|
|
|
|
matrices[mat_idx] = chain.add_redox_term(matrices[mat_idx],
|
|
|
|
|
transfer_rates.redox[mat_id][0],
|
|
|
|
|
transfer_rates.redox[mat_id][1])
|
|
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
# Add external sources if present
|
|
|
|
|
if external_active:
|
|
|
|
|
matrices, n_solve = _add_external_source(
|
|
|
|
|
matrices, n, chain, external_source_rates, current_timestep)
|
|
|
|
|
|
|
|
|
|
# Set transfer rate terms with destination material if present
|
2025-05-12 22:48:21 +02:00
|
|
|
if current_timestep in transfer_rates.index_transfer:
|
2023-06-16 06:00:20 +02:00
|
|
|
# Gather all on comm.rank 0
|
|
|
|
|
matrices = comm.gather(matrices)
|
2026-07-25 05:37:43 +02:00
|
|
|
n = comm.gather(n_solve)
|
2023-06-16 06:00:20 +02:00
|
|
|
|
|
|
|
|
if comm.rank == 0:
|
|
|
|
|
# Expand lists
|
|
|
|
|
matrices = [elm for matrix in matrices for elm in matrix]
|
2023-07-06 14:48:36 -05:00
|
|
|
n = [n_elm for n_mat in n for n_elm in n_mat]
|
2023-06-16 06:00:20 +02:00
|
|
|
|
|
|
|
|
# Calculate transfer rate terms as diagonal matrices
|
2025-05-12 22:48:21 +02:00
|
|
|
transfer_pair = {}
|
2026-07-25 05:37:43 +02:00
|
|
|
for mat_pair in dict.fromkeys(transfer_rates.index_transfer[current_timestep]):
|
2025-05-12 22:48:21 +02:00
|
|
|
transfer_matrix = chain.form_rr_term(transfer_rates,
|
|
|
|
|
current_timestep,
|
|
|
|
|
mat_pair)
|
2025-11-12 23:45:48 +01:00
|
|
|
# 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])
|
2026-07-25 05:37:43 +02:00
|
|
|
# Add external source rates if present
|
|
|
|
|
if external_active:
|
|
|
|
|
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))])
|
2025-05-12 22:48:21 +02:00
|
|
|
transfer_pair[mat_pair] = transfer_matrix
|
2023-06-16 06:00:20 +02:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
# Combine all matrices together in a single block matrix of matrices
|
|
|
|
|
# to be solved on one rank
|
2023-06-16 06:00:20 +02:00
|
|
|
n_rows = n_cols = len(transfer_rates.burnable_mats)
|
|
|
|
|
rows = []
|
|
|
|
|
for row in range(n_rows):
|
|
|
|
|
cols = []
|
|
|
|
|
for col in range(n_cols):
|
|
|
|
|
mat_pair = (transfer_rates.burnable_mats[row],
|
|
|
|
|
transfer_rates.burnable_mats[col])
|
|
|
|
|
if row == col:
|
|
|
|
|
# Fill the diagonals with the Bateman matrices
|
|
|
|
|
cols.append(matrices[row])
|
2025-05-12 22:48:21 +02:00
|
|
|
elif mat_pair in transfer_rates.index_transfer[current_timestep]:
|
2023-06-16 06:00:20 +02:00
|
|
|
# Fill the off-diagonals with the transfer pair matrices
|
|
|
|
|
cols.append(transfer_pair[mat_pair])
|
|
|
|
|
else:
|
|
|
|
|
cols.append(None)
|
|
|
|
|
|
|
|
|
|
rows.append(cols)
|
2026-01-02 06:43:25 -05:00
|
|
|
matrix = block_array(rows)
|
2023-06-16 06:00:20 +02:00
|
|
|
|
|
|
|
|
# Concatenate vectors of nuclides in one
|
2023-07-06 14:48:36 -05:00
|
|
|
n_multi = np.concatenate(n)
|
2026-04-22 16:24:34 -07:00
|
|
|
n_result = func(matrix, n_multi, dt, substeps)
|
2023-06-16 06:00:20 +02:00
|
|
|
|
|
|
|
|
# Split back the nuclide vector result into the original form
|
2023-07-06 14:48:36 -05:00
|
|
|
n_result = np.split(n_result, np.cumsum([len(i) for i in n])[:-1])
|
2023-06-16 06:00:20 +02:00
|
|
|
else:
|
2023-07-06 14:48:36 -05:00
|
|
|
n_result = None
|
2023-06-16 06:00:20 +02:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
# Broadcast result to other MPI ranks and then distribute
|
2023-07-06 14:48:36 -05:00
|
|
|
n_result = comm.bcast(n_result)
|
|
|
|
|
n_result = _distribute(n_result)
|
2023-04-28 05:11:17 +02:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
# Remove extra values based on the materials local to each rank
|
|
|
|
|
if external_active:
|
|
|
|
|
external_source_rates.reformat_nuclide_vectors(n_result)
|
2025-05-12 22:48:21 +02:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
return n_result
|
2025-05-12 22:48:21 +02:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
# If only external source rates are present
|
|
|
|
|
elif external_active:
|
|
|
|
|
matrices, n_solve = _add_external_source(
|
|
|
|
|
matrices, n, chain, external_source_rates, current_timestep)
|
2025-05-12 22:48:21 +02:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
inputs = zip(matrices, n_solve, repeat(dt), repeat(substeps))
|
2020-06-18 16:02:34 -04:00
|
|
|
|
|
|
|
|
if USE_MULTIPROCESSING:
|
2022-03-22 06:53:47 -05:00
|
|
|
with Pool(NUM_PROCESSES) as pool:
|
2023-07-06 14:48:36 -05:00
|
|
|
n_result = list(pool.starmap(func, inputs))
|
2020-06-18 16:02:34 -04:00
|
|
|
else:
|
2023-07-06 14:48:36 -05:00
|
|
|
n_result = list(starmap(func, inputs))
|
2020-05-09 14:38:46 -04:00
|
|
|
|
2026-07-25 05:37:43 +02:00
|
|
|
# Remove extra value at the end of the nuclide vectors if external source rates are present
|
|
|
|
|
if external_active:
|
2025-05-12 22:48:21 +02:00
|
|
|
external_source_rates.reformat_nuclide_vectors(n_result)
|
|
|
|
|
|
2023-07-06 14:48:36 -05:00
|
|
|
return n_result
|