Skip to content

Commit 50bb0df

Browse files
church89gridleypaulromano
authored
depletion-thermochemistry: Redox control transfer rates (#2783)
Co-authored-by: Gavin Ridley <gavin.keith.ridley@gmail.com> Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
1 parent 4e24c2d commit 50bb0df

17 files changed

+185
-9
lines changed

openmc/deplete/abc.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,6 +1013,36 @@ def add_external_source_rate(
10131013
material, composition, rate, rate_units, timesteps)
10141014

10151015

1016+
def add_redox(self, material, buffer, oxidation_states, timesteps=None):
1017+
"""Add redox control to depletable material.
1018+
1019+
Parameters
1020+
----------
1021+
material : openmc.Material or str or int
1022+
Depletable material
1023+
buffer : dict
1024+
Dictionary of buffer nuclides used to maintain redox balance. Keys
1025+
are nuclide names (strings) and values are their respective
1026+
fractions (float) that collectively sum to 1.
1027+
oxidation_states : dict
1028+
User-defined oxidation states for elements. Keys are element symbols
1029+
(e.g., 'H', 'He'), and values are their corresponding oxidation
1030+
states as integers (e.g., +1, 0).
1031+
timesteps : list of int, optional
1032+
List of timestep indices where to set external source rates.
1033+
Defaults to None, which means the external source rate is set for
1034+
all timesteps.
1035+
"""
1036+
if self.transfer_rates is None:
1037+
if hasattr(self.operator, 'model'):
1038+
materials = self.operator.model.materials
1039+
elif hasattr(self.operator, 'materials'):
1040+
materials = self.operator.materials
1041+
self.transfer_rates = TransferRates(
1042+
self.operator, materials, len(self.timesteps))
1043+
1044+
self.transfer_rates.set_redox(material, buffer, oxidation_states, timesteps)
1045+
10161046
@add_params
10171047
class SIIntegrator(Integrator):
10181048
r"""Abstract class for the Stochastic Implicit Euler integrators

openmc/deplete/chain.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from io import StringIO
88
from itertools import chain
99
import math
10+
import numpy as np
1011
import re
1112
from collections import defaultdict, namedtuple
1213
from collections.abc import Mapping, Iterable
@@ -714,6 +715,59 @@ def setval(i, j, val):
714715
# Return CSC representation instead of DOK
715716
return sp.csc_matrix((vals, (rows, cols)), shape=(n, n))
716717

718+
def add_redox_term(self, matrix, buffer, oxidation_states):
719+
"""Adds a redox term to the depletion matrix from data contained in
720+
the matrix itself and a few user-inputs.
721+
722+
The redox term to add to the buffer nuclide :math:`N_j` can be written
723+
as: :math:`\frac{dN_j(t)}{dt} =
724+
\cdots - \frac{1}{OS_j}\sum_i N_i a_{ij} \cdot OS_i `
725+
726+
where :math:`OS` is the oxidation states vector and `a_{ij}` the
727+
corresponding term in the Bateman matrix.
728+
729+
Parameters
730+
----------
731+
matrix : scipy.sparse.csc_matrix
732+
Sparse matrix representing depletion
733+
buffer : dict
734+
Dictionary of buffer nuclides used to maintain anoins net balance.
735+
Keys are nuclide names (strings) and values are their respective
736+
fractions (float) that collectively sum to 1.
737+
oxidation_states : dict
738+
User-defined oxidation states for elements. Keys are element symbols
739+
(e.g., 'H', 'He'), and values are their corresponding oxidation
740+
states as integers (e.g., +1, 0).
741+
Returns
742+
-------
743+
matrix : scipy.sparse.csc_matrix
744+
Sparse matrix with redox term added
745+
"""
746+
# Elements list with the same size as self.nuclides
747+
elements = [re.split(r'\d+', nuc.name)[0] for nuc in self.nuclides]
748+
749+
# Match oxidation states with all elements and add 0 if not data
750+
os = np.array([oxidation_states[elm] if elm in oxidation_states else 0
751+
for elm in elements])
752+
753+
# Buffer idx with nuclide index as value
754+
buffer_idx = {nuc: self.nuclide_dict[nuc] for nuc in buffer}
755+
array = matrix.toarray()
756+
redox_change = np.array([])
757+
758+
# calculate the redox array
759+
for i in range(len(self)):
760+
# Net redox impact of reaction: multiply the i-th column of the
761+
# depletion matrix by the oxidation states
762+
redox_change = np.append(redox_change, sum(array[:, i]*os))
763+
764+
# Subtract redox vector to the buffer nuclides in the matrix scaling by
765+
# their respective oxidation states
766+
for nuc, idx in buffer_idx.items():
767+
array[idx] -= redox_change * buffer[nuc] / os[idx]
768+
769+
return sp.csc_matrix(array)
770+
717771
def form_rr_term(self, tr_rates, current_timestep, mats):
718772
"""Function to form the transfer rate term matrices.
719773

openmc/deplete/pool.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None,
109109
matrices = [matrix - transfer for (matrix, transfer) in zip(matrices,
110110
transfers)]
111111

112+
if transfer_rates.redox:
113+
for mat_idx, mat_id in enumerate(transfer_rates.local_mats):
114+
if mat_id in transfer_rates.redox:
115+
matrices[mat_idx] = chain.add_redox_term(matrices[mat_idx],
116+
transfer_rates.redox[mat_id][0],
117+
transfer_rates.redox[mat_id][1])
118+
112119
if current_timestep in transfer_rates.index_transfer:
113120
# Gather all on comm.rank 0
114121
matrices = comm.gather(matrices)
@@ -125,6 +132,12 @@ def deplete(func, chain, n, rates, dt, current_timestep=None, matrix_func=None,
125132
transfer_matrix = chain.form_rr_term(transfer_rates,
126133
current_timestep,
127134
mat_pair)
135+
136+
# check if destination material has a redox control
137+
if mat_pair[0] in transfer_rates.redox:
138+
transfer_matrix = chain.add_redox_term(transfer_matrix,
139+
transfer_rates.redox[mat_pair[0]][0],
140+
transfer_rates.redox[mat_pair[0]][1])
128141
transfer_pair[mat_pair] = transfer_matrix
129142

130143
# Combine all matrices together in a single matrix of matrices

openmc/deplete/transfer_rates.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ def __init__(self, operator, materials, number_of_timesteps):
4949
self.local_mats = operator.local_mats
5050
self.number_of_timesteps = number_of_timesteps
5151

52-
# initialize transfer rates container dict
52+
#initialize transfer rates container dict
5353
self.external_rates = {mat: defaultdict(list) for mat in self.burnable_mats}
5454
self.external_timesteps = []
55+
self.redox = {}
5556

5657
def _get_material_id(self, val):
5758
"""Helper method for getting material id from Material obj or name.
@@ -300,6 +301,46 @@ def set_transfer_rate(self, material, components, transfer_rate,
300301
self.external_timesteps = np.unique(np.concatenate(
301302
[self.external_timesteps, timesteps]))
302303

304+
def set_redox(self, material, buffer, oxidation_states, timesteps=None):
305+
"""Add redox control to depletable material.
306+
307+
Parameters
308+
----------
309+
material : openmc.Material or str or int
310+
Depletable material
311+
buffer : dict
312+
Dictionary of buffer nuclides used to maintain redox balance.
313+
Keys are nuclide names (strings) and values are their respective
314+
fractions (float) that collectively sum to 1.
315+
oxidation_states : dict
316+
User-defined oxidation states for elements.
317+
Keys are element symbols (e.g., 'H', 'He'), and values are their
318+
corresponding oxidation states as integers (e.g., +1, 0).
319+
timesteps : list of int, optional
320+
List of timestep indices where to set external source rates.
321+
Defaults to None, which means the external source rate is set for
322+
all timesteps.
323+
324+
"""
325+
material_id = self._get_material_id(material)
326+
if timesteps is not None:
327+
for timestep in timesteps:
328+
check_value('timestep', timestep, range(self.number_of_timesteps))
329+
timesteps = np.array(timesteps)
330+
else:
331+
timesteps = np.arange(self.number_of_timesteps)
332+
#Check nuclides in buffer exist
333+
for nuc in buffer:
334+
if nuc not in self.chain_nuclides:
335+
raise ValueError(f'{nuc} is not a valid nuclide.')
336+
# Checks element in oxidation states exist
337+
for elm in oxidation_states:
338+
if elm not in ELEMENT_SYMBOL.values():
339+
raise ValueError(f'{elm} is not a valid element.')
340+
341+
self.redox[material_id] = (buffer, oxidation_states)
342+
self.external_timesteps = np.unique(np.concatenate(
343+
[self.external_timesteps, timesteps]))
303344

304345
class ExternalSourceRates(ExternalRates):
305346
"""Class for defining external source rates.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)