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
This commit is contained in:
yardasol 2022-07-12 11:44:12 -05:00
parent af3d460665
commit 5f8e1ff9d9
4 changed files with 123 additions and 66 deletions

View file

@ -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.

View file

@ -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:

View file

@ -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

View file

@ -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(