From 5f8e1ff9d99f7dea76c097cf91f78344c9e3633d Mon Sep 17 00:00:00 2001 From: yardasol Date: Tue, 12 Jul 2022 11:44:12 -0500 Subject: [PATCH] Address paulromano's comments - various syntax adjustments and cleanups - remove unneeded import statements - add more information to new section in user's guide - typo fixes - remove dilute_initial - move _validate_micro_xs_inputs back into the class as a static method - update tests to reflect changes --- docs/source/usersguide/depletion.rst | 34 ++++---- openmc/deplete/flux_operator.py | 74 ++++++----------- openmc/deplete/helpers.py | 79 +++++++++++++++++++ .../unit_tests/test_flux_deplete_operator.py | 2 +- 4 files changed, 123 insertions(+), 66 deletions(-) diff --git a/docs/source/usersguide/depletion.rst b/docs/source/usersguide/depletion.rst index f4f0ac869c..fe3f12dc47 100644 --- a/docs/source/usersguide/depletion.rst +++ b/docs/source/usersguide/depletion.rst @@ -181,25 +181,25 @@ Transport-independent depletion .. note:: - This is a brand-new feature and is under heavy development. API changes are + This feature is still under heavy development. API changes are possible and likely in the near future. -OpenMC also supports transport-independent depletion calculations using the -:class:`FluxDepletionOperator` class. Rather than taking a -:class:`openmc.model.Model` object, this class accepts a volume, +OpenMC supports running depletion calculations independent of the OpenMC +transport solver using the :class:`FluxDepletionOperator` class. Rather than +taking a :class:`openmc.model.Model` object, this class accepts a volume, a dictionary of nuclide concentrations, a flux spectra, and one-group -microscopic cross sections as a pandas dataframe. The class includes -helper functions to constructe the dataframe from a csv file or from +microscopic cross sections as a :class:`pandas.DataFrame`. The class includes +helper functions to construct the dataframe from a csv file or from data arrays:: ... micro_xs = FluxDepletionOperator.create_micro_xs_from_csv(micro_xs_path) - nuclides = {'U234':8.92e18, - 'U235':9.98e20, - 'U238':2.22e22, - 'U236':4.57e18, - 'O16':4.64e22, - 'O17':1.76e19} + nuclides = {'U234': 8.92e18, + 'U235': 9.98e20, + 'U238': 2.22e22, + 'U236': 4.57e18, + 'O16': 4.64e22, + 'O17': 1.76e19} volume = 0.5 flux = 1.16e15 @@ -207,8 +207,10 @@ data arrays:: A user can then define an integrator class as they would for a coupled -transport-depletion calculation and follow the steps from there. -present in the depletion chain. +transport-depletion calculation and follow the same steps from there. -.. note:: Ideally, one-group cross section data should be available for every reaction - in the depletion chain. +.. note:: Ideally, one-group cross section data should be available for every + reaction in the depletion chain. If a nuclide that has a reaction + associated with it in the depletion chain is present in the `nuclides` + parameter but not the cross section data, that reaction will not be + simulated. diff --git a/openmc/deplete/flux_operator.py b/openmc/deplete/flux_operator.py index 1da7cf9f9c..562967e6c7 100644 --- a/openmc/deplete/flux_operator.py +++ b/openmc/deplete/flux_operator.py @@ -6,36 +6,22 @@ and one-group cross sections. """ -import copy -from collections import OrderedDict -import os from warnings import warn import numpy as np import pandas as pd from uncertainties import ufloat -import openmc from openmc.checkvalue import check_type, check_value, check_iterable_type -from openmc.data import DataLibrary -from openmc.exceptions import DataError from openmc.mpi import comm from .abc import TransportOperator, OperatorResult from .atom_number import AtomNumber -from .chain import _find_chain_file, REACTIONS +from .chain import REACTIONS from .reaction_rates import ReactionRates -from .results import Results from .helpers import ConstantFissionYieldHelper -valid_rxns = list(REACTIONS.keys()) -valid_rxns += ['fission'] - -# Convenience function for the micro_xs static methods -def _validate_micro_xs_inputs(nuclides, reactions, data): - check_iterable_type('nuclides', nuclides, str) - check_iterable_type('reactions', reactions, str) - check_type('data', data, np.ndarray, expected_iter_type=float) - [check_value('reactions', reaction, valid_rxns) for reaction in reactions] +valid_rxns = list(REACTIONS) +valid_rxns.append('fission') @@ -51,10 +37,10 @@ class FluxDepletionOperator(TransportOperator): Parameters ---------- volume : float - Volume of the material being depleted in cm^3 + Volume of the material being depleted in [cm^3] nuclides : dict of str to float Dictionary with nuclide names as keys and nuclide concentrations as - values. Nuclide concentration units are [at/cm^3]. + values. Nuclide concentration units are [atom/cm^3]. micro_xs : pandas.DataFrame DataFrame with nuclides names as index and microscopic cross section data in the columns. Cross section units are [cm^-2]. @@ -64,15 +50,10 @@ class FluxDepletionOperator(TransportOperator): Path to the depletion chain XML file. keff : 2-tuple of float, optional keff eigenvalue and uncertainty from transport calculation. - Defualt is None. + Default is None. fission_q : dict, optional Dictionary of nuclides and their fission Q values [eV]. If not given, values will be pulled from the ``chain_file``. - dilute_initial : float, optional - Initial atom density [atoms/cm^3] to add for nuclides that are zero - in initial condition to ensure they exist in the decay chain. - Only done for nuclides with reaction rates. - Defaults to 1.0e3. prev_results : Results, optional Results from a previous depletion calculation. reduce_chain : bool, optional @@ -86,10 +67,6 @@ class FluxDepletionOperator(TransportOperator): Attributes ---------- - dilute_initial : float - Initial atom density [atoms/cm^3] to add for nuclides that are zero - in initial condition to ensure they exist in the decay chain. - Only done for nuclides with reaction rates. round_number : bool Whether or not to round output to OpenMC to 8 digits. Useful in testing, as OpenMC is incredibly sensitive to exact values. @@ -117,12 +94,11 @@ class FluxDepletionOperator(TransportOperator): chain_file, keff=None, fission_q=None, - dilute_initial=1.0e3, prev_results=None, reduce_chain=False, reduce_chain_level=None, fission_yield_opts=None): - super().__init__(chain_file, fission_q, dilute_initial, prev_results) + super().__init__(chain_file, fission_q, 0.0, prev_results) self.round_number = False self.flux_spectra = flux_spectra self._init_nuclides = nuclides @@ -138,7 +114,7 @@ class FluxDepletionOperator(TransportOperator): check_type('micro_xs', micro_xs, pd.DataFrame) self._micro_xs = micro_xs - if not isinstance(keff, type(None)): + if keff is not None: check_type('keff', keff, tuple, float) keff = ufloat(keff) @@ -221,7 +197,6 @@ class FluxDepletionOperator(TransportOperator): for nuc, i_nuc_results in zip(rxn_nuclides, nuc_ind): number[i_nuc_results] = self.number[0, nuc] - # Calculate macroscopic cross sections and store them in rates array for nuc in rxn_nuclides: density = self.number.get_atom_density('0', nuc) @@ -230,8 +205,7 @@ class FluxDepletionOperator(TransportOperator): '0', nuc, rxn, - self._micro_xs[rxn].loc[nuc] * - density) + self._micro_xs[rxn, rxn] * density) # Get reaction rate in reactions/sec rates *= self.flux_spectra @@ -334,15 +308,13 @@ class FluxDepletionOperator(TransportOperator): """ # Validate inputs - try: - assert data.shape == (len(nuclides), len(reactions)) - except AssertionError: - raise SyntaxError( + if data.shape != (len(nuclides), len(reactions)): + raise ValueError( f'Nuclides list of length {len(nuclides)} and ' f'reactions array of length {len(reactions)} do not ' f'match dimensions of data array of shape {data.shape}') - _validate_micro_xs_inputs(nuclides, reactions, data) + FluxDepletionOperator._validate_micro_xs_inputs(nuclides, reactions, data) # Convert to cm^2 if units == 'barn': @@ -371,7 +343,7 @@ class FluxDepletionOperator(TransportOperator): """ micro_xs = pd.read_csv(csv_file, index_col=0) - _validate_micro_xs_inputs(list(micro_xs.index), + FluxDepletionOperator._validate_micro_xs_inputs(list(micro_xs.index), list(micro_xs.columns), micro_xs.to_numpy()) @@ -380,6 +352,16 @@ class FluxDepletionOperator(TransportOperator): return micro_xs + # Convenience function for the micro_xs static methods + @staticmethod + def _validate_micro_xs_inputs(nuclides, reactions, data): + check_iterable_type('nuclides', nuclides, str) + check_iterable_type('reactions', reactions, str) + check_type('data', data, np.ndarray, expected_iter_type=float) + for reaction in reactions: + check_value('reactions', reaction, valid_rxns) + + def _update_materials(self): """Updates material compositions in OpenMC on all processes.""" @@ -464,7 +446,7 @@ class FluxDepletionOperator(TransportOperator): return [nuc for nuc in nuc_list if nuc in self.chain] def _get_all_nuclides_in_simulation(self): - """Determine nuclides that will show up in the simulation. + """Determine nuclides that will show up in the depletion matrix. This is the union of the nuclides provided by the user and the nuclides present in the depletion chain. @@ -487,9 +469,8 @@ class FluxDepletionOperator(TransportOperator): def _get_nuclides_with_data(self): """Finds nuclides with cross section data""" - nuclides_with_data = set(self._micro_xs.index) + return set(self._micro_xs.index) - return nuclides_with_data def _extract_number(self, local_mats, volume, nuclides, prev_res=None): """Construct AtomNumber using geometry @@ -508,11 +489,6 @@ class FluxDepletionOperator(TransportOperator): """ self.number = AtomNumber(local_mats, nuclides, volume, len(self.chain)) - if self.dilute_initial != 0.0: - for nuc in self._burnable_nucs: - self.number.set_atom_density( - np.s_[:], nuc, self.dilute_initial) - # Now extract and store the number densities # From the geometry if no previous depletion results if prev_res is None: diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 33bd182d7d..5adb439987 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -2,6 +2,7 @@ Class for normalizing fission energy deposition """ import bisect +import pandas as pd from abc import abstractmethod from collections import defaultdict from copy import deepcopy @@ -124,6 +125,84 @@ class TalliedFissionYieldHelper(FissionYieldHelper): # ------------------------------------- +class FluxReactionRateHelper(ReactionRateHelper): + """Class for generating one-group reaction rates with flux and + one-group cross sections. + + This class does not generate tallies, and instead stores cross sections + for each nuclides and transmutation reaction relevant for a depletion + calculation. + + Parameters + ---------- + n_nucs : int + Number of burnable nuclides tracked by :class:`openmc.deplete.Operator` + n_react : int + Number of reactions tracked by :class:`openmc.deplete.Operator` + + Attributes + ---------- + flux : + + xs : + + + """ + def __init__(self, n_nuc, n_react): + super().__init__(n_nuc, n_react) + self._flux = None + self._micro_xs = None + + def generate_tallies(self, materials, scores): + """Unused in this case""" + + + @property + def flux(self): + """Flux in n cm^-2 s^-1""" + return self._flux + + @flux.setter + def flux(self, flux): + check_type("flux", flux, float) + self._flux = flux + + @property + def micro_xs(self): + """DataFrame of microscopic cross sections with requested reaction + for all nuclides""" + return self._micro_xs + + @micro_xs.setter + def micro_xs(self, micro_xs): + # TODO : validate micro_xs + self._micro_xs = micro_xs + + def get_material_rates(self, mat_id, nuc_index, react_index): + """Return 2D array of [nuclide, reaction] reaction rates + + Parameters + ---------- + mat_id : int + Unique ID for the requested material + nuc_index : list of str + Ordering of desired nuclides + react_index : list of str + Ordering of reactions + """ + self._results_cache.fill(0.0) + full_tally_res = self._rate_tally.mean[mat_id] + for i_tally, (i_nuc, i_react) in enumerate( + product(nuc_index, react_index)): + self._results_cache[i_nuc, i_react] = full_tally_res[i_tally] + + return self._results_cache + + + + + + class DirectReactionRateHelper(ReactionRateHelper): """Class for generating one-group reaction rates with direct tallies diff --git a/tests/unit_tests/test_flux_deplete_operator.py b/tests/unit_tests/test_flux_deplete_operator.py index a2b1504938..99c4e7c8a4 100644 --- a/tests/unit_tests/test_flux_deplete_operator.py +++ b/tests/unit_tests/test_flux_deplete_operator.py @@ -48,7 +48,7 @@ def test_create_micro_xs_from_data_array(): FluxDepletionOperator.create_micro_xs_from_data_array( nuclides, reactions, data) - with pytest.raises(SyntaxError, match=r'Nuclides list of length \d* and ' + with pytest.raises(ValueError, match=r'Nuclides list of length \d* and ' r'reactions array of length \d* do not ' r'match dimensions of data array of shape \(\d*\,d*\)'): FluxDepletionOperator.create_micro_xs_from_data_array(