From 2e635c74f4d2d87237e6acc6b4718f8783f7202a Mon Sep 17 00:00:00 2001 From: Jin Whan Bae Date: Thu, 2 Nov 2023 17:52:12 -0400 Subject: [PATCH 01/14] microxs from mg flux and chain file --- openmc/deplete/microxs.py | 114 +++++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 9 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index e145bce8763..ed112520945 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -22,6 +22,29 @@ _valid_rxns.append('fission') +def resolve_chain_file_path(chain_file:str): + # Determine what reactions and nuclides are available in chain + if chain_file is None: + if 'chain_file' in openmc.config: + raise DataError( + "No depletion chain specified and could not find depletion " + "chain in openmc.config['chain_file']" + ) + else: + chain_file = openmc.config['chain_file'] + return chain_file + + +def resolve_openmc_data_path(openmc_data_path:str=None): + if not openmc_data_path: + if 'cross_sections' not in openmc.config: + raise ValueError("`openmc_data_path` is not defined nor `openmc.config['cross_sections']` is defined.") + else: + openmc_data_path = openmc.config['cross_sections'] + return openmc_data_path + + + def get_microxs_and_flux( model: openmc.Model, domains, @@ -69,13 +92,7 @@ def get_microxs_and_flux( original_tallies = model.tallies # Determine what reactions and nuclides are available in chain - if chain_file is None: - chain_file = openmc.config.get('chain_file') - if chain_file is None: - raise DataError( - "No depletion chain specified and could not find depletion " - "chain in openmc.config['chain_file']" - ) + chain_file = resolve_chain_file_path(chain_file) chain = Chain.from_xml(chain_file) if reactions is None: reactions = chain.reactions @@ -151,6 +168,87 @@ def get_microxs_and_flux( return fluxes, micros + + +def get_microxs_from_mg_flux(energies: list or str, mg_flux: list, + chain_file_path:str=None, openmc_data_path:str=None, + temperature:int=294): + """Generated MicroXS object from a known flux and a chain file. The size of the MicroXs matrix depends + on the chain file. + + Args: + energies: iterable of float or str + Energy group boundaries in [eV] or the name of the group structure + mg_flux: iterable of float + Energy-dependent multigroup flux values + chain_file_path: str, optional + Path to the depletion chain XML file that will be used in + depletion simulation. + openmc_dat_path: str, optional + Path to the cross section XML file that contains data library paths. + temperature: int, optional + Temperature for cross section evaluation in [K]. + """ + # check energies + if isinstance(energies, str): + energies = openmc.EnergyFilter.from_group_structure(energies).values + + # check ascending (low to high) + if not all(energies[i] <= energies[i+1] for i in range(len(energies) - 1)): + raise ValueError('Energy bin must be in ascending order') + + # check dimension consistency + if not len(mg_flux) == len(energies)-1: + raise ValueError('Length of flux array should be len(energies)-1') + + chain_file_path = resolve_chain_file_path(chain_file_path) + chain = openmc.deplete.Chain.from_xml(chain_file_path) + + # resolve data library + openmc_data_path = resolve_openmc_data_path(openmc_data_path) + data_lib = openmc.data.DataLibrary.from_xml(openmc_data_path) + + # get reactions and nuclides from chain file + nuclides, reactions = chain.nuclides, chain.reactions + nuclides = [nuc.name for nuc in nuclides] + if 'fission' in reactions: + # send to back + reactions.append(reactions.pop(reactions.index('fission'))) + # convert reactions to mt file + #! I feel like this zero indexing will backfire + #! There has to be a better way for this + mts = [list(REACTIONS[reaction].mts)[0] for reaction in reactions if reaction != 'fission'] + if 'fission' in reactions: + mts.append(18) # fission is not in the REACTIONS + + # normalize flux and get energy midpoint + norm_mg_flux = mg_flux / sum(mg_flux) + energies_midpoint = [(energies[i]+energies[i+1])/2 for i in range(len(energies)-1)] + temperature_key = str(temperature) + 'K' + microxs_arr = np.zeros((len(nuclides), len(mts))) + + for nuc_indx, nuc in enumerate(nuclides): + mat_lib = data_lib.get_by_material(nuc, data_type='neutron') + if not mat_lib: # file does not exist + microxs_arr[nuc_indx, :] = 0 + continue + else: + hdf5_path = mat_lib['path'] + nuc_data = openmc.data.IncidentNeutron.from_hdf5(hdf5_path) + for mt_indx, mt in enumerate(mts): + try: # sometimes it fails cause there's no entry + total_xs = np.array(nuc_data[mt].xs[temperature_key](energies_midpoint)) + # collapse + total_norm = sum(total_xs * norm_mg_flux) + microxs_arr[nuc_indx, mt_indx] = total_norm + except KeyError: + microxs_arr[nuc_indx, mt_indx] = 0.0 + + return openmc.deplete.MicroXS.from_array(nuclides=nuclides, + reactions=reactions, + data=microxs_arr) + + class MicroXS: """Microscopic cross section data for use in transport-independent depletion. @@ -192,8 +290,6 @@ def __init__(self, data: np.ndarray, nuclides: List[str], reactions: List[str]): self._index_nuc = {nuc: i for i, nuc in enumerate(nuclides)} self._index_rx = {rx: i for i, rx in enumerate(reactions)} - # TODO: Add a classmethod for generating MicroXS directly from cross section - # data using a known flux spectrum @classmethod def from_csv(cls, csv_file, **kwargs): From e70c89647acd3cfc3c6025e36f1810ded766da33 Mon Sep 17 00:00:00 2001 From: shimwell Date: Fri, 3 Nov 2023 00:02:14 +0000 Subject: [PATCH 02/14] converted to cls method added test --- openmc/deplete/microxs.py | 123 +++++++++++++++++++++-- tests/unit_tests/test_deplete_microxs.py | 15 +++ 2 files changed, 128 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index e145bce8763..30b7e49f88e 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -6,7 +6,7 @@ from __future__ import annotations import tempfile -from typing import List, Tuple, Iterable, Optional, Union +from typing import List, Tuple, Iterable, Optional, Union, Sequence import pandas as pd import numpy as np @@ -22,6 +22,29 @@ _valid_rxns.append('fission') +def _resolve_chain_file_path(chain_file:str): + # Determine what reactions and nuclides are available in chain + if chain_file is None: + if 'chain_file' in openmc.config: + raise DataError( + "No depletion chain specified and could not find depletion " + "chain in openmc.config['chain_file']" + ) + else: + chain_file = openmc.config['chain_file'] + return chain_file + + +def _resolve_openmc_data_path(openmc_data_path:str=None): + if not openmc_data_path: + if 'cross_sections' not in openmc.config: + raise ValueError("`openmc_data_path` is not defined nor `openmc.config['cross_sections']` is defined.") + else: + openmc_data_path = openmc.config['cross_sections'] + return openmc_data_path + + + def get_microxs_and_flux( model: openmc.Model, domains, @@ -69,13 +92,7 @@ def get_microxs_and_flux( original_tallies = model.tallies # Determine what reactions and nuclides are available in chain - if chain_file is None: - chain_file = openmc.config.get('chain_file') - if chain_file is None: - raise DataError( - "No depletion chain specified and could not find depletion " - "chain in openmc.config['chain_file']" - ) + chain_file = _resolve_chain_file_path(chain_file) chain = Chain.from_xml(chain_file) if reactions is None: reactions = chain.reactions @@ -192,8 +209,94 @@ def __init__(self, data: np.ndarray, nuclides: List[str], reactions: List[str]): self._index_nuc = {nuc: i for i, nuc in enumerate(nuclides)} self._index_rx = {rx: i for i, rx in enumerate(reactions)} - # TODO: Add a classmethod for generating MicroXS directly from cross section - # data using a known flux spectrum + + @classmethod + def from_multi_group_flux( + cls, + energies: Union[Iterable[float], str], + multi_group_flux: Sequence[float], + chain_file: Optional[PathLike] = None, + openmc_data_path: Optional[PathLike] = None, + temperature:int=294 + ): + """Generated MicroXS object from a known flux and a chain file. The size of the MicroXs matrix depends + on the chain file. + + Parameters + ---------- + energies: iterable of float or str + Energy group boundaries in [eV] or the name of the group structure + multi_group_flux: iterable of float + Energy-dependent multigroup flux values + chain_file: str, optional + Path to the depletion chain XML file that will be used in + depletion simulation. + openmc_dat_path: str, optional + Path to the cross section XML file that contains data library paths. + temperature: int, optional + Temperature for cross section evaluation in [K]. + + Returns + ------- + MicroXS + """ + # check energies + if isinstance(energies, str): + energies = openmc.EnergyFilter.from_group_structure(energies).values + else: + # if user inputs energies check they are ascending (low to high) as some + # depletion codes use high energy to low energy. + if not all(energies[i] <= energies[i+1] for i in range(len(energies) - 1)): + raise ValueError('Energy bin must be in ascending order') + + # check dimension consistency + if not len(multi_group_flux) == len(energies)-1: + raise ValueError('Length of flux array should be len(energies)-1') + + chain_file_path = _resolve_chain_file_path(chain_file) + chain = openmc.deplete.Chain.from_xml(chain_file_path) + + openmc_data_path = _resolve_openmc_data_path(openmc_data_path) + data_lib = openmc.data.DataLibrary.from_xml(openmc_data_path) + + # get reactions and nuclides from chain file + nuclides, reactions = chain.nuclides, chain.reactions + nuclides = [nuc.name for nuc in nuclides] + if 'fission' in reactions: + # send to back + reactions.append(reactions.pop(reactions.index('fission'))) + # convert reactions to mt file + #! I feel like this zero indexing will backfire + #! There has to be a better way for this + mts = [list(REACTIONS[reaction].mts)[0] for reaction in reactions if reaction != 'fission'] + if 'fission' in reactions: + mts.append(18) # fission is not in the REACTIONS + + # normalize flux and get energy midpoint + norm_multi_group_flux = np.array(multi_group_flux) / sum(multi_group_flux) + energies_midpoint = [(energies[i]+energies[i+1])/2 for i in range(len(energies)-1)] + temperature_key = f'{temperature}K' + microxs_arr = np.zeros((len(nuclides), len(mts))) + + for nuc_indx, nuc in enumerate(nuclides): + mat_lib = data_lib.get_by_material(nuc, data_type='neutron') + if not mat_lib: # file does not exist + microxs_arr[nuc_indx, :] = 0 + continue + else: + hdf5_path = mat_lib['path'] + nuc_data = openmc.data.IncidentNeutron.from_hdf5(hdf5_path) + for mt_indx, mt in enumerate(mts): + try: # sometimes it fails cause there's no entry + # perhaps a multigroup mgxs should be made as chosing the xs at the energy bin center is not always fair + total_xs = np.array(nuc_data[mt].xs[temperature_key](energies_midpoint)) + # collapse + total_norm = sum(total_xs * norm_multi_group_flux) + microxs_arr[nuc_indx, mt_indx] = total_norm + except KeyError: + microxs_arr[nuc_indx, mt_indx] = 0.0 + + return cls(nuclides=nuclides, reactions=reactions, data=microxs_arr) @classmethod def from_csv(cls, csv_file, **kwargs): diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index 582c584654d..b92fd9be27f 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -58,3 +58,18 @@ def test_csv(): assert np.all(ref_xs.data == temp_xs.data) remove('temp_xs.csv') +def test_from_multi_group_flux(): + + MicroXS.from_multi_group_flux( + energies='CASMO-4', + multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], + temperature='293', + chain_file=Path(__file__).parents[1] / 'chain_simple.xml' + ) + + MicroXS.from_multi_group_flux( + energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], + multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], + temperature='293', + chain_file=Path(__file__).parents[1] / 'chain_simple.xml' + ) From 626618e32de6793254173661d044c6e6814b6f4d Mon Sep 17 00:00:00 2001 From: shimwell Date: Fri, 3 Nov 2023 00:07:00 +0000 Subject: [PATCH 03/14] asserting correct type is returned --- tests/unit_tests/test_deplete_microxs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index b92fd9be27f..fcda08ad688 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -60,16 +60,18 @@ def test_csv(): def test_from_multi_group_flux(): - MicroXS.from_multi_group_flux( + microxs = MicroXS.from_multi_group_flux( energies='CASMO-4', multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], temperature='293', chain_file=Path(__file__).parents[1] / 'chain_simple.xml' ) + assert isinstance(microxs, MicroXS) - MicroXS.from_multi_group_flux( + microxs = MicroXS.from_multi_group_flux( energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], temperature='293', chain_file=Path(__file__).parents[1] / 'chain_simple.xml' ) + assert isinstance(microxs, MicroXS) From b81245b9802f370ad9ccb22ddca316855ba2ff91 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Mon, 6 Nov 2023 20:10:29 +0000 Subject: [PATCH 04/14] Code review improvements by @yardasol Co-authored-by: Olek <45364492+yardasol@users.noreply.github.com> --- openmc/deplete/microxs.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 0be1084b2c1..69e1afacb5d 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -35,7 +35,7 @@ def _resolve_chain_file_path(chain_file:str): return chain_file -def _resolve_openmc_data_path(openmc_data_path:str=None): +def _resolve_openmc_data_path(openmc_data_path: str=None): if not openmc_data_path: if 'cross_sections' not in openmc.config: raise ValueError("`openmc_data_path` is not defined nor `openmc.config['cross_sections']` is defined.") @@ -216,23 +216,23 @@ def from_multi_group_flux( multi_group_flux: Sequence[float], chain_file: Optional[PathLike] = None, openmc_data_path: Optional[PathLike] = None, - temperature:int=294 + temperature: int=294 ): - """Generated MicroXS object from a known flux and a chain file. The size of the MicroXs matrix depends + """Generated MicroXS object from a known flux and a chain file. The size of the MicroXS matrix depends on the chain file. Parameters ---------- - energies: iterable of float or str + energies : iterable of float or str Energy group boundaries in [eV] or the name of the group structure - multi_group_flux: iterable of float + multi_group_flux : iterable of float Energy-dependent multigroup flux values - chain_file: str, optional + chain_file : str, optional Path to the depletion chain XML file that will be used in depletion simulation. - openmc_dat_path: str, optional + openmc_data_path : str, optional Path to the cross section XML file that contains data library paths. - temperature: int, optional + temperature : int, optional Temperature for cross section evaluation in [K]. Returns From 9197ee04ffe4e641aa65bbc36eacf4b1c920c944 Mon Sep 17 00:00:00 2001 From: shimwell Date: Tue, 28 Nov 2023 23:57:04 +0000 Subject: [PATCH 05/14] faster collapse with collapse_rate --- openmc/deplete/microxs.py | 65 +++++++++++++++--------- tests/unit_tests/test_deplete_microxs.py | 4 +- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 69e1afacb5d..35b210a01f5 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -5,7 +5,7 @@ """ from __future__ import annotations -import tempfile +from tempfile import TemporaryDirectory from typing import List, Tuple, Iterable, Optional, Union, Sequence import pandas as pd @@ -17,6 +17,7 @@ import openmc from .chain import Chain, REACTIONS from .coupled_operator import _find_cross_sections, _get_nuclides_with_data +import openmc.lib _valid_rxns = list(REACTIONS) _valid_rxns.append('fission') @@ -132,7 +133,7 @@ def get_microxs_and_flux( model.tallies = openmc.Tallies([rr_tally, flux_tally]) # create temporary run - with tempfile.TemporaryDirectory() as temp_dir: + with TemporaryDirectory() as temp_dir: if run_kwargs is None: run_kwargs = {} else: @@ -239,7 +240,9 @@ def from_multi_group_flux( ------- MicroXS """ - # check energies + + check_type("temperature", temperature, int) + # if energy is string then use group structure of that name if isinstance(energies, str): energies = openmc.EnergyFilter.from_group_structure(energies).values else: @@ -256,7 +259,6 @@ def from_multi_group_flux( chain = openmc.deplete.Chain.from_xml(chain_file_path) openmc_data_path = _resolve_openmc_data_path(openmc_data_path) - data_lib = openmc.data.DataLibrary.from_xml(openmc_data_path) # get reactions and nuclides from chain file nuclides, reactions = chain.nuclides, chain.reactions @@ -271,29 +273,42 @@ def from_multi_group_flux( if 'fission' in reactions: mts.append(18) # fission is not in the REACTIONS - # normalize flux and get energy midpoint - norm_multi_group_flux = np.array(multi_group_flux) / sum(multi_group_flux) - energies_midpoint = [(energies[i]+energies[i+1])/2 for i in range(len(energies)-1)] - temperature_key = f'{temperature}K' microxs_arr = np.zeros((len(nuclides), len(mts))) - for nuc_indx, nuc in enumerate(nuclides): - mat_lib = data_lib.get_by_material(nuc, data_type='neutron') - if not mat_lib: # file does not exist - microxs_arr[nuc_indx, :] = 0 - continue - else: - hdf5_path = mat_lib['path'] - nuc_data = openmc.data.IncidentNeutron.from_hdf5(hdf5_path) - for mt_indx, mt in enumerate(mts): - try: # sometimes it fails cause there's no entry - # perhaps a multigroup mgxs should be made as chosing the xs at the energy bin center is not always fair - total_xs = np.array(nuc_data[mt].xs[temperature_key](energies_midpoint)) - # collapse - total_norm = sum(total_xs * norm_multi_group_flux) - microxs_arr[nuc_indx, mt_indx] = total_norm - except KeyError: - microxs_arr[nuc_indx, mt_indx] = 0.0 + with TemporaryDirectory(): + from ctypes import c_int, c_double + + mat_all_nucs = openmc.Material() + for nuc in nuclides: + mat_all_nucs.add_nuclide(nuc, 1) + mat_all_nucs.set_density("atom/b-cm", 1) + materials = openmc.Materials([mat_all_nucs]) + + surf1 = openmc.Sphere(r=1, boundary_type="vacuum") + surf1_region = -surf1 + surf1_cell = openmc.Cell(region=surf1_region) + geometry = openmc.Geometry([surf1_cell]) + + settings = openmc.Settings() + settings.particles = 1 + settings.batches = 1 + + model = openmc.Model(geometry, materials, settings) + model.export_to_model_xml() + + c_temperature = c_double(temperature) + + openmc.lib.init() + for nuc_indx, nuc in enumerate(nuclides): + lib_nuc = openmc.lib.nuclides[nuc] + for mt_indx, mt in enumerate(mts): + c_mt = c_int(mt) + collapse = lib_nuc.collapse_rate( + c_mt, c_temperature, energies, multi_group_flux + ) + microxs_arr[nuc_indx, mt_indx] = collapse + + openmc.lib.finalize() return cls(nuclides=nuclides, reactions=reactions, data=microxs_arr) diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index fcda08ad688..11a6542f7a6 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -63,7 +63,7 @@ def test_from_multi_group_flux(): microxs = MicroXS.from_multi_group_flux( energies='CASMO-4', multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], - temperature='293', + temperature=293, chain_file=Path(__file__).parents[1] / 'chain_simple.xml' ) assert isinstance(microxs, MicroXS) @@ -71,7 +71,7 @@ def test_from_multi_group_flux(): microxs = MicroXS.from_multi_group_flux( energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], - temperature='293', + temperature=293, chain_file=Path(__file__).parents[1] / 'chain_simple.xml' ) assert isinstance(microxs, MicroXS) From 9f73cd3f7e0a7f06917c45d00df8055b531f58c6 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 29 Nov 2023 15:31:35 +0000 Subject: [PATCH 06/14] checking xs avail before adding to mat --- openmc/deplete/microxs.py | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 35b210a01f5..63913ce42c5 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -219,8 +219,11 @@ def from_multi_group_flux( openmc_data_path: Optional[PathLike] = None, temperature: int=294 ): - """Generated MicroXS object from a known flux and a chain file. The size of the MicroXS matrix depends - on the chain file. + """Generated MicroXS object from a known flux and a chain file. + + The size of the MicroXS matrix depends on the chain file and cross + sections available. MicroXS entry will be 0 if the nuclide cross section + is not found. Parameters ---------- @@ -259,6 +262,7 @@ def from_multi_group_flux( chain = openmc.deplete.Chain.from_xml(chain_file_path) openmc_data_path = _resolve_openmc_data_path(openmc_data_path) + data_lib = openmc.data.DataLibrary.from_xml(openmc_data_path) # get reactions and nuclides from chain file nuclides, reactions = chain.nuclides, chain.reactions @@ -280,7 +284,10 @@ def from_multi_group_flux( mat_all_nucs = openmc.Material() for nuc in nuclides: - mat_all_nucs.add_nuclide(nuc, 1) + mat_lib = data_lib.get_by_material(nuc, data_type="neutron") + # limits the material to nuclides that have cross sections + if mat_lib: + mat_all_nucs.add_nuclide(nuc, 1) mat_all_nucs.set_density("atom/b-cm", 1) materials = openmc.Materials([mat_all_nucs]) @@ -300,13 +307,17 @@ def from_multi_group_flux( openmc.lib.init() for nuc_indx, nuc in enumerate(nuclides): - lib_nuc = openmc.lib.nuclides[nuc] - for mt_indx, mt in enumerate(mts): - c_mt = c_int(mt) - collapse = lib_nuc.collapse_rate( - c_mt, c_temperature, energies, multi_group_flux - ) - microxs_arr[nuc_indx, mt_indx] = collapse + mat_lib = data_lib.get_by_material(nuc, data_type="neutron") + if mat_lib: + lib_nuc = openmc.lib.nuclides[nuc] + for mt_indx, mt in enumerate(mts): + c_mt = c_int(mt) + collapse = lib_nuc.collapse_rate( + c_mt, c_temperature, energies, multi_group_flux + ) + microxs_arr[nuc_indx, mt_indx] = collapse + else: + microxs_arr[nuc_indx, :] = 0 openmc.lib.finalize() From 9d6577a099eb803bafaaef25fd7f5f9ebdf57eb2 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 29 Nov 2023 16:10:44 +0000 Subject: [PATCH 07/14] no longer writing summary.h5 --- openmc/deplete/microxs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 63913ce42c5..a1a160a7ca7 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -299,7 +299,7 @@ def from_multi_group_flux( settings = openmc.Settings() settings.particles = 1 settings.batches = 1 - + settings.output = {'summary': False} model = openmc.Model(geometry, materials, settings) model.export_to_model_xml() From 8785cc5ef116c428af7f000a97c6299e7e78d1fe Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Tue, 5 Dec 2023 11:21:22 +0000 Subject: [PATCH 08/14] [skip ci] allowing nuclides to be provided --- openmc/deplete/microxs.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index a1a160a7ca7..4fa176c24b4 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -217,7 +217,8 @@ def from_multi_group_flux( multi_group_flux: Sequence[float], chain_file: Optional[PathLike] = None, openmc_data_path: Optional[PathLike] = None, - temperature: int=294 + temperature: int=294, + nuclides: Optional[Iterable[str]] = None, ): """Generated MicroXS object from a known flux and a chain file. @@ -233,11 +234,15 @@ def from_multi_group_flux( Energy-dependent multigroup flux values chain_file : str, optional Path to the depletion chain XML file that will be used in - depletion simulation. + depletion simulation. Defaults to ``openmc.config['chain_file']``. openmc_data_path : str, optional Path to the cross section XML file that contains data library paths. + Defaults to ``openmc.config['cross_sections']``. temperature : int, optional Temperature for cross section evaluation in [K]. + nuclides : list of str + Nuclides to get cross sections for. If not specified, all burnable + nuclides from the depletion chain file are used. Returns ------- @@ -262,10 +267,13 @@ def from_multi_group_flux( chain = openmc.deplete.Chain.from_xml(chain_file_path) openmc_data_path = _resolve_openmc_data_path(openmc_data_path) - data_lib = openmc.data.DataLibrary.from_xml(openmc_data_path) + cross_sections = openmc.data.DataLibrary.from_xml(openmc_data_path) + nuclides_with_data = _get_nuclides_with_data(cross_sections) # get reactions and nuclides from chain file - nuclides, reactions = chain.nuclides, chain.reactions + if not nuclides: + nuclides = chain.nuclides + reactions = chain.reactions nuclides = [nuc.name for nuc in nuclides] if 'fission' in reactions: # send to back @@ -307,8 +315,7 @@ def from_multi_group_flux( openmc.lib.init() for nuc_indx, nuc in enumerate(nuclides): - mat_lib = data_lib.get_by_material(nuc, data_type="neutron") - if mat_lib: + if nuc in nuclides_with_data: lib_nuc = openmc.lib.nuclides[nuc] for mt_indx, mt in enumerate(mts): c_mt = c_int(mt) From d7fefa7bacc9023d1135928f09cb22e4d65f6f47 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Tue, 5 Dec 2023 11:52:32 +0000 Subject: [PATCH 09/14] added testing and working for nuclides --- openmc/deplete/coupled_operator.py | 12 +++++++----- openmc/deplete/microxs.py | 22 ++++------------------ openmc/lib/nuclide.py | 2 +- tests/unit_tests/test_deplete_microxs.py | 13 +++++++++++++ 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/openmc/deplete/coupled_operator.py b/openmc/deplete/coupled_operator.py index 0d778c7450c..23d9bd8e0d1 100644 --- a/openmc/deplete/coupled_operator.py +++ b/openmc/deplete/coupled_operator.py @@ -10,6 +10,7 @@ import copy from warnings import warn +from typing import Optional import numpy as np from uncertainties import ufloat @@ -33,18 +34,19 @@ __all__ = ["CoupledOperator", "Operator", "OperatorResult"] -def _find_cross_sections(model): +def _find_cross_sections(model: Optional[str] = None): """Determine cross sections to use for depletion Parameters ---------- - model : openmc.model.Model + model : openmc.model.Model, optional Reactor model """ - if model.materials and model.materials.cross_sections is not None: - # Prefer info from Model class if available - return model.materials.cross_sections + if model: + if model.materials and model.materials.cross_sections is not None: + # Prefer info from Model class if available + return model.materials.cross_sections # otherwise fallback to environment variable cross_sections = openmc.config.get("cross_sections") diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 4fa176c24b4..41750e943c6 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -36,16 +36,6 @@ def _resolve_chain_file_path(chain_file:str): return chain_file -def _resolve_openmc_data_path(openmc_data_path: str=None): - if not openmc_data_path: - if 'cross_sections' not in openmc.config: - raise ValueError("`openmc_data_path` is not defined nor `openmc.config['cross_sections']` is defined.") - else: - openmc_data_path = openmc.config['cross_sections'] - return openmc_data_path - - - def get_microxs_and_flux( model: openmc.Model, domains, @@ -216,7 +206,6 @@ def from_multi_group_flux( energies: Union[Iterable[float], str], multi_group_flux: Sequence[float], chain_file: Optional[PathLike] = None, - openmc_data_path: Optional[PathLike] = None, temperature: int=294, nuclides: Optional[Iterable[str]] = None, ): @@ -235,9 +224,6 @@ def from_multi_group_flux( chain_file : str, optional Path to the depletion chain XML file that will be used in depletion simulation. Defaults to ``openmc.config['chain_file']``. - openmc_data_path : str, optional - Path to the cross section XML file that contains data library paths. - Defaults to ``openmc.config['cross_sections']``. temperature : int, optional Temperature for cross section evaluation in [K]. nuclides : list of str @@ -266,15 +252,15 @@ def from_multi_group_flux( chain_file_path = _resolve_chain_file_path(chain_file) chain = openmc.deplete.Chain.from_xml(chain_file_path) - openmc_data_path = _resolve_openmc_data_path(openmc_data_path) - cross_sections = openmc.data.DataLibrary.from_xml(openmc_data_path) + cross_sections = _find_cross_sections(model=None) + data_lib = openmc.data.DataLibrary.from_xml(cross_sections) nuclides_with_data = _get_nuclides_with_data(cross_sections) # get reactions and nuclides from chain file if not nuclides: - nuclides = chain.nuclides + nuclides = chain.nuclides + nuclides = [nuc.name for nuc in nuclides] reactions = chain.reactions - nuclides = [nuc.name for nuc in nuclides] if 'fission' in reactions: # send to back reactions.append(reactions.pop(reactions.index('fission'))) diff --git a/openmc/lib/nuclide.py b/openmc/lib/nuclide.py index 399bb346520..8078882cf35 100644 --- a/openmc/lib/nuclide.py +++ b/openmc/lib/nuclide.py @@ -91,7 +91,7 @@ def collapse_rate(self, MT, temperature, energy, flux): energy : iterable of float Energy group boundaries in [eV] flux : iterable of float - Flux in each energt group (not normalized per eV) + Flux in each energy group (not normalized per eV) Returns ------- diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index 11a6542f7a6..8e98fb48de5 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -60,6 +60,7 @@ def test_csv(): def test_from_multi_group_flux(): + # test with energy group structure from string microxs = MicroXS.from_multi_group_flux( energies='CASMO-4', multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], @@ -68,6 +69,7 @@ def test_from_multi_group_flux(): ) assert isinstance(microxs, MicroXS) + # test with energy group structure as floats microxs = MicroXS.from_multi_group_flux( energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], @@ -75,3 +77,14 @@ def test_from_multi_group_flux(): chain_file=Path(__file__).parents[1] / 'chain_simple.xml' ) assert isinstance(microxs, MicroXS) + + # test with nuclides provided + microxs = MicroXS.from_multi_group_flux( + energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], + multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], + temperature=293, + chain_file=Path(__file__).parents[1] / 'chain_simple.xml', + nuclides=['Gd157'] + ) + assert microxs.nuclides == ['Gd157'] + assert isinstance(microxs, MicroXS) From 296ac964b17fe4049cd007e8fa710f170e985d9a Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Tue, 5 Dec 2023 13:35:39 +0000 Subject: [PATCH 10/14] allowing floats or ints for temperature --- openmc/deplete/coupled_operator.py | 2 +- openmc/deplete/microxs.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/coupled_operator.py b/openmc/deplete/coupled_operator.py index 23d9bd8e0d1..287a4e0d37b 100644 --- a/openmc/deplete/coupled_operator.py +++ b/openmc/deplete/coupled_operator.py @@ -69,7 +69,7 @@ def _get_nuclides_with_data(cross_sections): Returns ------- nuclides : set of str - Set of nuclide names that have cross secton data + Set of nuclide names that have cross section data """ nuclides = set() diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 41750e943c6..1146e3dc80d 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -206,7 +206,7 @@ def from_multi_group_flux( energies: Union[Iterable[float], str], multi_group_flux: Sequence[float], chain_file: Optional[PathLike] = None, - temperature: int=294, + temperature: float=294., nuclides: Optional[Iterable[str]] = None, ): """Generated MicroXS object from a known flux and a chain file. @@ -235,7 +235,7 @@ def from_multi_group_flux( MicroXS """ - check_type("temperature", temperature, int) + check_type("temperature", temperature, (int, float)) # if energy is string then use group structure of that name if isinstance(energies, str): energies = openmc.EnergyFilter.from_group_structure(energies).values @@ -294,6 +294,7 @@ def from_multi_group_flux( settings.particles = 1 settings.batches = 1 settings.output = {'summary': False} + model = openmc.Model(geometry, materials, settings) model.export_to_model_xml() From 88f039888b31d7eb2bd55d1c788050a37fe3541c Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 13 Dec 2023 23:54:31 +0000 Subject: [PATCH 11/14] Norm the flux --- openmc/deplete/microxs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 1146e3dc80d..82a66c4d996 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -299,7 +299,7 @@ def from_multi_group_flux( model.export_to_model_xml() c_temperature = c_double(temperature) - + norm_multi_group_flux = [val*1/sum(multi_group_flux) for val in multi_group_flux] openmc.lib.init() for nuc_indx, nuc in enumerate(nuclides): if nuc in nuclides_with_data: @@ -307,7 +307,7 @@ def from_multi_group_flux( for mt_indx, mt in enumerate(mts): c_mt = c_int(mt) collapse = lib_nuc.collapse_rate( - c_mt, c_temperature, energies, multi_group_flux + c_mt, c_temperature, energies, norm_multi_group_flux ) microxs_arr[nuc_indx, mt_indx] = collapse else: From 917e55ac9db4e02b0c34ae94c9f69ca6633855a4 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Dec 2023 10:07:31 -0600 Subject: [PATCH 12/14] Simplify logic in from_multigroup_flux --- openmc/deplete/abc.py | 3 +- openmc/deplete/microxs.py | 116 +++++++++++------------ tests/unit_tests/test_deplete_microxs.py | 14 +-- 3 files changed, 67 insertions(+), 66 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index d135b2dae5a..40ef88c6783 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -68,8 +68,9 @@ def change_directory(output_dir): Directory to switch to. """ orig_dir = os.getcwd() + output_dir = Path(output_dir) + output_dir.mkdir(parents=True, exist_ok=True) try: - output_dir.mkdir(parents=True, exist_ok=True) os.chdir(output_dir) yield finally: diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 82a66c4d996..8dda887cd8e 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -14,7 +14,9 @@ from openmc.checkvalue import check_type, check_value, check_iterable_type, PathLike from openmc.exceptions import DataError from openmc import StatePoint +from openmc.mgxs import GROUP_STRUCTURES import openmc +from .abc import change_directory from .chain import Chain, REACTIONS from .coupled_operator import _find_cross_sections, _get_nuclides_with_data import openmc.lib @@ -23,16 +25,15 @@ _valid_rxns.append('fission') -def _resolve_chain_file_path(chain_file:str): +def _resolve_chain_file_path(chain_file: str): # Determine what reactions and nuclides are available in chain if chain_file is None: + chain_file = openmc.config.get('chain_file') if 'chain_file' in openmc.config: raise DataError( "No depletion chain specified and could not find depletion " "chain in openmc.config['chain_file']" ) - else: - chain_file = openmc.config['chain_file'] return chain_file @@ -201,15 +202,16 @@ def __init__(self, data: np.ndarray, nuclides: List[str], reactions: List[str]): self._index_rx = {rx: i for i, rx in enumerate(reactions)} @classmethod - def from_multi_group_flux( + def from_multigroup_flux( cls, - energies: Union[Iterable[float], str], - multi_group_flux: Sequence[float], + energies: Union[Sequence[float], str], + multigroup_flux: Sequence[float], chain_file: Optional[PathLike] = None, - temperature: float=294., + temperature: float = 293.6, nuclides: Optional[Iterable[str]] = None, - ): - """Generated MicroXS object from a known flux and a chain file. + **init_kwargs: dict, + ) -> MicroXS: + """Generated microscopic cross sections from a known flux. The size of the MicroXS matrix depends on the chain file and cross sections available. MicroXS entry will be 0 if the nuclide cross section @@ -229,6 +231,8 @@ def from_multi_group_flux( nuclides : list of str Nuclides to get cross sections for. If not specified, all burnable nuclides from the depletion chain file are used. + **init_kwargs : dict + Keyword arguments passed to :func:`openmc.lib.init` Returns ------- @@ -238,28 +242,29 @@ def from_multi_group_flux( check_type("temperature", temperature, (int, float)) # if energy is string then use group structure of that name if isinstance(energies, str): - energies = openmc.EnergyFilter.from_group_structure(energies).values + energies = GROUP_STRUCTURES[energies] else: - # if user inputs energies check they are ascending (low to high) as some - # depletion codes use high energy to low energy. - if not all(energies[i] <= energies[i+1] for i in range(len(energies) - 1)): - raise ValueError('Energy bin must be in ascending order') + # if user inputs energies check they are ascending (low to high) as + # some depletion codes use high energy to low energy. + if not np.all(np.diff(energies) > 0): + raise ValueError('Energy group boundaries must be in ascending order') # check dimension consistency - if not len(multi_group_flux) == len(energies)-1: + if len(multigroup_flux) != len(energies) - 1: raise ValueError('Length of flux array should be len(energies)-1') chain_file_path = _resolve_chain_file_path(chain_file) - chain = openmc.deplete.Chain.from_xml(chain_file_path) + chain = Chain.from_xml(chain_file_path) cross_sections = _find_cross_sections(model=None) - data_lib = openmc.data.DataLibrary.from_xml(cross_sections) nuclides_with_data = _get_nuclides_with_data(cross_sections) - # get reactions and nuclides from chain file + # If no nuclides were specified, default to all nuclides from the chain if not nuclides: nuclides = chain.nuclides nuclides = [nuc.name for nuc in nuclides] + + # get reactions and nuclides from chain file reactions = chain.reactions if 'fission' in reactions: # send to back @@ -273,49 +278,44 @@ def from_multi_group_flux( microxs_arr = np.zeros((len(nuclides), len(mts))) - with TemporaryDirectory(): - from ctypes import c_int, c_double - + with TemporaryDirectory() as tmpdir: + # Create a material with all nuclides mat_all_nucs = openmc.Material() for nuc in nuclides: - mat_lib = data_lib.get_by_material(nuc, data_type="neutron") - # limits the material to nuclides that have cross sections - if mat_lib: - mat_all_nucs.add_nuclide(nuc, 1) - mat_all_nucs.set_density("atom/b-cm", 1) - materials = openmc.Materials([mat_all_nucs]) - - surf1 = openmc.Sphere(r=1, boundary_type="vacuum") - surf1_region = -surf1 - surf1_cell = openmc.Cell(region=surf1_region) - geometry = openmc.Geometry([surf1_cell]) - - settings = openmc.Settings() - settings.particles = 1 - settings.batches = 1 - settings.output = {'summary': False} - - model = openmc.Model(geometry, materials, settings) - model.export_to_model_xml() - - c_temperature = c_double(temperature) - norm_multi_group_flux = [val*1/sum(multi_group_flux) for val in multi_group_flux] - openmc.lib.init() - for nuc_indx, nuc in enumerate(nuclides): if nuc in nuclides_with_data: - lib_nuc = openmc.lib.nuclides[nuc] - for mt_indx, mt in enumerate(mts): - c_mt = c_int(mt) - collapse = lib_nuc.collapse_rate( - c_mt, c_temperature, energies, norm_multi_group_flux - ) - microxs_arr[nuc_indx, mt_indx] = collapse - else: - microxs_arr[nuc_indx, :] = 0 - - openmc.lib.finalize() - - return cls(nuclides=nuclides, reactions=reactions, data=microxs_arr) + mat_all_nucs.add_nuclide(nuc, 1.0) + mat_all_nucs.set_density("atom/b-cm", 1.0) + + # Create simple model containing the above material + surf1 = openmc.Sphere(boundary_type="vacuum") + surf1_cell = openmc.Cell(fill=mat_all_nucs, region=-surf1) + model = openmc.Model() + model.geometry = openmc.Geometry([surf1_cell]) + model.settings = openmc.Settings( + particles=1, batches=1, output={'summary': False}) + + with change_directory(tmpdir): + # Export model + model.export_to_model_xml() + + # Normalize multigroup flux + multigroup_flux = np.asarray(multigroup_flux) + norm_multigroup_flux = multigroup_flux / multigroup_flux.sum() + + with openmc.lib.run_in_memory(**init_kwargs): + # For each nuclide and reaction, compute the flux-averaged + # cross section + for nuc_index, nuc in enumerate(nuclides): + if nuc not in nuclides_with_data: + continue + lib_nuc = openmc.lib.nuclides[nuc] + for mt_index, mt in enumerate(mts): + xs = lib_nuc.collapse_rate( + mt, temperature, energies, norm_multigroup_flux + ) + microxs_arr[nuc_index, mt_index] = xs + + return cls(microxs_arr, nuclides, reactions) @classmethod def from_csv(cls, csv_file, **kwargs): diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index 8e98fb48de5..1ea06b87303 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -58,30 +58,30 @@ def test_csv(): assert np.all(ref_xs.data == temp_xs.data) remove('temp_xs.csv') -def test_from_multi_group_flux(): +def test_from_multigroup_flux(): # test with energy group structure from string - microxs = MicroXS.from_multi_group_flux( + microxs = MicroXS.from_multigroup_flux( energies='CASMO-4', - multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], + multigroup_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], temperature=293, chain_file=Path(__file__).parents[1] / 'chain_simple.xml' ) assert isinstance(microxs, MicroXS) # test with energy group structure as floats - microxs = MicroXS.from_multi_group_flux( + microxs = MicroXS.from_multigroup_flux( energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], - multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], + multigroup_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], temperature=293, chain_file=Path(__file__).parents[1] / 'chain_simple.xml' ) assert isinstance(microxs, MicroXS) # test with nuclides provided - microxs = MicroXS.from_multi_group_flux( + microxs = MicroXS.from_multigroup_flux( energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], - multi_group_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], + multigroup_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], temperature=293, chain_file=Path(__file__).parents[1] / 'chain_simple.xml', nuclides=['Gd157'] From 6f09829bdd555edf7e99d6878ab7694e016e7125 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 20 Dec 2023 12:38:57 -0600 Subject: [PATCH 13/14] Add reactions argument to from_multigroup_flux --- openmc/deplete/microxs.py | 48 ++++++++++++------------ tests/unit_tests/test_deplete_microxs.py | 31 +++++++-------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 8dda887cd8e..0721535ca4f 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -15,6 +15,7 @@ from openmc.exceptions import DataError from openmc import StatePoint from openmc.mgxs import GROUP_STRUCTURES +from openmc.data import REACTION_MT import openmc from .abc import change_directory from .chain import Chain, REACTIONS @@ -208,7 +209,8 @@ def from_multigroup_flux( multigroup_flux: Sequence[float], chain_file: Optional[PathLike] = None, temperature: float = 293.6, - nuclides: Optional[Iterable[str]] = None, + nuclides: Optional[Sequence[str]] = None, + reactions: Optional[Sequence[str]] = None, **init_kwargs: dict, ) -> MicroXS: """Generated microscopic cross sections from a known flux. @@ -217,6 +219,8 @@ def from_multigroup_flux( sections available. MicroXS entry will be 0 if the nuclide cross section is not found. + .. versionadded:: 0.14.1 + Parameters ---------- energies : iterable of float or str @@ -224,13 +228,16 @@ def from_multigroup_flux( multi_group_flux : iterable of float Energy-dependent multigroup flux values chain_file : str, optional - Path to the depletion chain XML file that will be used in - depletion simulation. Defaults to ``openmc.config['chain_file']``. + Path to the depletion chain XML file that will be used in depletion + simulation. Defaults to ``openmc.config['chain_file']``. temperature : int, optional Temperature for cross section evaluation in [K]. - nuclides : list of str - Nuclides to get cross sections for. If not specified, all burnable - nuclides from the depletion chain file are used. + nuclides : list of str, optional + Nuclides to get cross sections for. If not specified, all burnable + nuclides from the depletion chain file are used. + reactions : list of str, optional + Reactions to get cross sections for. If not specified, all neutron + reactions listed in the depletion chain file are used. **init_kwargs : dict Keyword arguments passed to :func:`openmc.lib.init` @@ -264,18 +271,17 @@ def from_multigroup_flux( nuclides = chain.nuclides nuclides = [nuc.name for nuc in nuclides] - # get reactions and nuclides from chain file - reactions = chain.reactions - if 'fission' in reactions: - # send to back - reactions.append(reactions.pop(reactions.index('fission'))) - # convert reactions to mt file - #! I feel like this zero indexing will backfire - #! There has to be a better way for this - mts = [list(REACTIONS[reaction].mts)[0] for reaction in reactions if reaction != 'fission'] - if 'fission' in reactions: - mts.append(18) # fission is not in the REACTIONS + # Get reaction MT values. If no reactions specified, default to the + # reactions available in the chain file + if reactions is None: + reactions = chain.reactions + mts = [REACTION_MT[name] for name in reactions] + # Normalize multigroup flux + multigroup_flux = np.asarray(multigroup_flux) + multigroup_flux /= multigroup_flux.sum() + + # Create 2D array for microscopic cross sections microxs_arr = np.zeros((len(nuclides), len(mts))) with TemporaryDirectory() as tmpdir: @@ -295,13 +301,9 @@ def from_multigroup_flux( particles=1, batches=1, output={'summary': False}) with change_directory(tmpdir): - # Export model + # Export model within temporary directory model.export_to_model_xml() - # Normalize multigroup flux - multigroup_flux = np.asarray(multigroup_flux) - norm_multigroup_flux = multigroup_flux / multigroup_flux.sum() - with openmc.lib.run_in_memory(**init_kwargs): # For each nuclide and reaction, compute the flux-averaged # cross section @@ -311,7 +313,7 @@ def from_multigroup_flux( lib_nuc = openmc.lib.nuclides[nuc] for mt_index, mt in enumerate(mts): xs = lib_nuc.collapse_rate( - mt, temperature, energies, norm_multigroup_flux + mt, temperature, energies, multigroup_flux ) microxs_arr[nuc_index, mt_index] = xs diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index 1ea06b87303..c54f5cf1e94 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -59,32 +59,29 @@ def test_csv(): remove('temp_xs.csv') def test_from_multigroup_flux(): + energies = [0., 6.25e-1, 5.53e3, 8.21e5, 2.e7] + flux = [1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4] + chain_file = Path(__file__).parents[1] / 'chain_simple.xml' + kwargs = {'multigroup_flux': flux, 'chain_file': chain_file} # test with energy group structure from string - microxs = MicroXS.from_multigroup_flux( - energies='CASMO-4', - multigroup_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], - temperature=293, - chain_file=Path(__file__).parents[1] / 'chain_simple.xml' - ) + microxs = MicroXS.from_multigroup_flux(energies='CASMO-4', **kwargs) assert isinstance(microxs, MicroXS) # test with energy group structure as floats + microxs = MicroXS.from_multigroup_flux(energies=energies, **kwargs) + assert isinstance(microxs, MicroXS) + + # test with nuclides provided microxs = MicroXS.from_multigroup_flux( - energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], - multigroup_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], - temperature=293, - chain_file=Path(__file__).parents[1] / 'chain_simple.xml' + energies=energies, nuclides=['Gd157', 'H1'], **kwargs ) assert isinstance(microxs, MicroXS) + assert microxs.nuclides == ['Gd157', 'H1'] - # test with nuclides provided + # test with reactions provided microxs = MicroXS.from_multigroup_flux( - energies=[0., 6.25e-1, 5.53e3, 8.21e5, 2.e7], - multigroup_flux=[1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4], - temperature=293, - chain_file=Path(__file__).parents[1] / 'chain_simple.xml', - nuclides=['Gd157'] + energies=energies, reactions=['fission', '(n,2n)'], **kwargs ) - assert microxs.nuclides == ['Gd157'] assert isinstance(microxs, MicroXS) + assert microxs.reactions == ['fission', '(n,2n)'] From ed4e29cf9c05d98f7fb1f74e28f0e1b9bcf2055e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 21 Dec 2023 14:43:48 -0600 Subject: [PATCH 14/14] Add one test to confirm that micros are the same with equivalent group structures --- tests/unit_tests/test_deplete_microxs.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/unit_tests/test_deplete_microxs.py b/tests/unit_tests/test_deplete_microxs.py index c54f5cf1e94..ad54026f014 100644 --- a/tests/unit_tests/test_deplete_microxs.py +++ b/tests/unit_tests/test_deplete_microxs.py @@ -51,6 +51,7 @@ def test_from_array(): r'match dimensions of data array of shape \(\d*\, \d*\)'): MicroXS(data[:, 0], nuclides, reactions) + def test_csv(): ref_xs = MicroXS.from_csv(ONE_GROUP_XS) ref_xs.to_csv('temp_xs.csv') @@ -58,6 +59,7 @@ def test_csv(): assert np.all(ref_xs.data == temp_xs.data) remove('temp_xs.csv') + def test_from_multigroup_flux(): energies = [0., 6.25e-1, 5.53e3, 8.21e5, 2.e7] flux = [1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4] @@ -85,3 +87,25 @@ def test_from_multigroup_flux(): ) assert isinstance(microxs, MicroXS) assert microxs.reactions == ['fission', '(n,2n)'] + + +def test_multigroup_flux_same(): + chain_file = Path(__file__).parents[1] / 'chain_simple.xml' + + # Generate micro XS based on 4-group flux + energies = [0., 6.25e-1, 5.53e3, 8.21e5, 2.e7] + flux_per_ev = [0.3, 0.3, 1.0, 1.0] + flux = flux_per_ev * np.diff(energies) + microxs_4g = MicroXS.from_multigroup_flux( + energies=energies, multigroup_flux=flux, chain_file=chain_file) + + # Generate micro XS based on 2-group flux, where the boundaries line up with + # the 4 group flux and have the same flux per eV across the full energy + # range + energies = [0., 5.53e3, 2.0e7] + flux_per_ev = [0.3, 1.0] + flux = flux_per_ev * np.diff(energies) + microxs_2g = MicroXS.from_multigroup_flux( + energies=energies, multigroup_flux=flux, chain_file=chain_file) + + assert microxs_4g.data == pytest.approx(microxs_2g.data)