From db426b66ca6d40a7988f74bc0296e7a4a78a7d69 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Tue, 27 Jan 2026 07:06:36 +0100 Subject: [PATCH] Materials name persist when running depletion simulation (#3738) Co-authored-by: GuySten Co-authored-by: GuySten <62616591+GuySten@users.noreply.github.com> Co-authored-by: Paul Romano --- docs/source/io_formats/depletion_results.rst | 1 + openmc/deplete/abc.py | 2 ++ openmc/deplete/openmc_operator.py | 11 ++++++++--- openmc/deplete/stepresult.py | 19 +++++++++++++++---- tests/dummy_operator.py | 2 +- tests/unit_tests/test_deplete_activation.py | 5 +++++ tests/unit_tests/test_deplete_decay.py | 5 +++++ tests/unit_tests/test_deplete_integrator.py | 6 ++++-- 8 files changed, 41 insertions(+), 10 deletions(-) diff --git a/docs/source/io_formats/depletion_results.rst b/docs/source/io_formats/depletion_results.rst index b2a726dad..7fb088268 100644 --- a/docs/source/io_formats/depletion_results.rst +++ b/docs/source/io_formats/depletion_results.rst @@ -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//** diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index ee742f22b..056f7c273 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -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): diff --git a/openmc/deplete/openmc_operator.py b/openmc/deplete/openmc_operator.py index 2d7694093..7928e89ee 100644 --- a/openmc/deplete/openmc_operator.py +++ b/openmc/deplete/openmc_operator.py @@ -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 diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index ff39e9acb..27420246f 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -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) diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index 1bb00129d..9595765d7 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -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": ""} diff --git a/tests/unit_tests/test_deplete_activation.py b/tests/unit_tests/test_deplete_activation.py index eace1976e..2f1713880 100644 --- a/tests/unit_tests/test_deplete_activation.py +++ b/tests/unit_tests/test_deplete_activation.py @@ -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""" diff --git a/tests/unit_tests/test_deplete_decay.py b/tests/unit_tests/test_deplete_decay.py index 6e7b0b101..db96fcbe2 100644 --- a/tests/unit_tests/test_deplete_decay.py +++ b/tests/unit_tests/test_deplete_decay.py @@ -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.' diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 6463eaa20..558cb434b 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -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"])