Materials name persist when running depletion simulation (#3738)

Co-authored-by: GuySten <guyste@post.bgu.ac.il>
Co-authored-by: GuySten <62616591+GuySten@users.noreply.github.com>
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
This commit is contained in:
Jonathan Shimwell 2026-01-27 07:06:36 +01:00 committed by GitHub
parent 5c502ddb3e
commit db426b66ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 41 additions and 10 deletions

View file

@ -34,6 +34,7 @@ The current version of the depletion results file format is 1.2.
:Attributes: - **index** (*int*) -- Index used in results for this material
- **volume** (*double*) -- Volume of this material in [cm^3]
- **name** (*char[]*) -- Name of this material
**/nuclides/<name>/**

View file

@ -209,6 +209,8 @@ class TransportOperator(ABC):
simulation.
full_burn_list : list of int
All burnable materials in the geometry.
name_list : list of str
Material names corresponding to materials in burn_list
"""
def finalize(self):

View file

@ -211,7 +211,7 @@ class OpenMCOperator(TransportOperator):
"section data.")
warn(msg)
if mat.depletable:
burnable_mats.add(str(mat.id))
burnable_mats.add((str(mat.id), mat.name))
if mat.volume is None:
if mat.name is None:
msg = ("Volume not specified for depletable material "
@ -229,9 +229,12 @@ class OpenMCOperator(TransportOperator):
"No depletable materials were found in the model.")
# Sort the sets
burnable_mats = sorted(burnable_mats, key=int)
burnable_mats = sorted(burnable_mats, key=lambda x: int(x[0]))
model_nuclides = sorted(model_nuclides)
# Store material names for later use
burnable_mats, self.name_list = zip(*burnable_mats)
# Construct a global nuclide dictionary, burned first
nuclides = list(self.chain.nuclide_dict)
for nuc in model_nuclides:
@ -541,6 +544,8 @@ class OpenMCOperator(TransportOperator):
A list of all material IDs to be burned. Used for sorting the simulation.
full_burn_list : list
List of all burnable material IDs
name_list : list of str
Material names corresponding to materials in burn_list
"""
nuc_list = self.number.burnable_nuclides
@ -554,4 +559,4 @@ class OpenMCOperator(TransportOperator):
volume_list = comm.allgather(volume)
volume = {k: v for d in volume_list for k, v in d.items()}
return volume, nuc_list, burn_list, self.burnable_mats
return volume, nuc_list, burn_list, self.burnable_mats, self.name_list

View file

@ -70,6 +70,7 @@ class StepResult:
self.index_mat = None
self.index_nuc = None
self.mat_to_hdf5_ind = None
self.name_list = None
self.data = None
@ -138,7 +139,7 @@ class StepResult:
def n_hdf5_mats(self):
return len(self.mat_to_hdf5_ind)
def allocate(self, volume, nuc_list, burn_list, full_burn_list):
def allocate(self, volume, nuc_list, burn_list, full_burn_list, name_list=None):
"""Allocate memory for depletion step data
Parameters
@ -151,12 +152,15 @@ class StepResult:
A list of all mat IDs to be burned. Used for sorting the simulation.
full_burn_list : list of str
List of all burnable material IDs
name_list : list of str, optional
Material names corresponding to materials in burn_list
"""
self.volume = copy.deepcopy(volume)
self.index_nuc = {nuc: i for i, nuc in enumerate(nuc_list)}
self.index_mat = {mat: i for i, mat in enumerate(burn_list)}
self.mat_to_hdf5_ind = {mat: i for i, mat in enumerate(full_burn_list)}
self.mat_to_name = dict(zip(burn_list, name_list)) if name_list is not None else {}
# Create storage array
self.data = np.zeros((self.n_mat, self.n_nuc))
@ -184,7 +188,7 @@ class StepResult:
# Direct transfer
direct_attrs = ("time", "k", "source_rate", "index_nuc",
"mat_to_hdf5_ind", "proc_time")
"mat_to_hdf5_ind", "mat_to_name", "proc_time")
for attr in direct_attrs:
setattr(new, attr, getattr(self, attr))
# Get applicable slice of data
@ -223,6 +227,8 @@ class StepResult:
f'mat_id {mat_id} not found in StepResult. Available mat_id '
f'values are {list(self.volume.keys())}'
) from e
if mat_id in self.mat_to_name:
material.name = self.mat_to_name[mat_id]
for nuc, _ in sorted(self.index_nuc.items(), key=lambda x: x[1]):
atoms = self[mat_id, nuc]
if atoms <= 0.0:
@ -313,6 +319,8 @@ class StepResult:
mat_single_group = mat_group.create_group(mat)
mat_single_group.attrs["index"] = self.mat_to_hdf5_ind[mat]
mat_single_group.attrs["volume"] = self.volume[mat]
if mat in self.mat_to_name:
mat_single_group.attrs["name"] = self.mat_to_name[mat]
nuc_group = handle.create_group("nuclides")
@ -495,6 +503,7 @@ class StepResult:
results.volume = {}
results.index_mat = {}
results.index_nuc = {}
results.mat_to_name = {}
rxn_nuc_to_ind = {}
rxn_to_ind = {}
@ -504,6 +513,8 @@ class StepResult:
results.volume[mat] = vol
results.index_mat[mat] = ind
if "name" in mat_handle.attrs:
results.mat_to_name[mat] = mat_handle.attrs["name"]
for nuc, nuc_handle in handle["/nuclides"].items():
ind_atom = nuc_handle.attrs["atom number index"]
@ -569,11 +580,11 @@ class StepResult:
.. versionadded:: 0.14.0
"""
# Get indexing terms
vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info()
vol_dict, nuc_list, burn_list, full_burn_list, name_list = op.get_results_info()
# Create results
results = StepResult()
results.allocate(vol_dict, nuc_list, burn_list, full_burn_list)
results.allocate(vol_dict, nuc_list, burn_list, full_burn_list, name_list)
n_mat = len(burn_list)

View file

@ -243,4 +243,4 @@ class DummyOperator(TransportOperator):
Maps cell name to index in global geometry.
"""
return self.volume, self.nuc_list, self.local_mats, self.burnable_mats
return self.volume, self.nuc_list, self.local_mats, self.burnable_mats, {"1": ""}

View file

@ -113,6 +113,11 @@ def test_activation(run_in_tmpdir, model, reaction_rate_mode, reaction_rate_opts
assert atoms[0] == pytest.approx(n0)
assert atoms[1] / atoms[0] == pytest.approx(0.5, rel=tolerance)
# Check that material name is preserved in depletion results
step_result = results[0]
mat_from_results = step_result.get_material(f"{w.id}")
assert mat_from_results.name == 'tungsten'
def test_decay(run_in_tmpdir):
"""Test decay-only timesteps where no transport solve is performed"""

View file

@ -82,3 +82,8 @@ def test_deplete_decay_step_fissionable(run_in_tmpdir):
_, u238 = results.get_atoms(f"{mat.id}", "U238")
assert u238[1] == pytest.approx(original_atoms)
# Check that material name is preserved in depletion results
step_result = results[0]
mat_from_results = step_result.get_material(f"{mat.id}")
assert mat_from_results.name == 'I do not decay.'

View file

@ -59,8 +59,9 @@ def test_results_save(run_in_tmpdir):
burn_list = full_burn_list[2*comm.rank: 2*comm.rank + 2]
nuc_list = ["na", "nb"]
name_list = {mat: "" for mat in full_burn_list}
op.get_results_info.return_value = (
vol_dict, nuc_list, burn_list, full_burn_list)
vol_dict, nuc_list, burn_list, full_burn_list, name_list)
# Construct end-of-step concentrations
x1 = [rng.random(2), rng.random(2)]
@ -130,7 +131,8 @@ def test_results_save_without_rates(run_in_tmpdir):
vol_dict = {"0": 1.0}
nuc_list = ["na"]
burn_list = ["0"]
op.get_results_info.return_value = (vol_dict, nuc_list, burn_list, burn_list)
name_list = {mat: "" for mat in burn_list}
op.get_results_info.return_value = (vol_dict, nuc_list, burn_list, burn_list, name_list)
x = [np.array([1.0])]
rates = ReactionRates(burn_list, nuc_list, ["ra"])