"""MicroXS module
A pandas.DataFrame storing microscopic cross section data with
nuclide names as row indices and reaction names as column indices.
"""
import tempfile
from copy import deepcopy
from pandas import DataFrame, read_csv
import numpy as np
from openmc.checkvalue import check_type, check_value, check_iterable_type
from openmc.exceptions import DataError
from openmc.mgxs import EnergyGroups, ArbitraryXS, FissionXS
from openmc import Tallies, StatePoint, Materials
import openmc
from .chain import Chain, REACTIONS
from .coupled_operator import _find_cross_sections, _get_nuclides_with_data
_valid_rxns = list(REACTIONS)
_valid_rxns.append('fission')
[docs]class MicroXS(DataFrame):
"""Microscopic cross section data for use in transport-independent depletion.
.. versionadded:: 0.13.1
"""
[docs] @classmethod
def from_model(cls,
model,
reaction_domain,
chain_file=None,
dilute_initial=1.0e3,
energy_bounds=(0, 20e6),
run_kwargs=None):
"""Generate a one-group cross-section dataframe using
OpenMC. Note that the ``openmc`` executable must be compiled.
Parameters
----------
model : openmc.Model
OpenMC model object. Must contain geometry, materials, and settings.
reaction_domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh
Domain in which to tally reaction rates.
chain_file : str, optional
Path to the depletion chain XML file that will be used in depletion
simulation. Used to determine cross sections for materials not
present in the inital composition. Defaults to
``openmc.config['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 cross
section data. Only done for nuclides with reaction rates.
reactions : list of str, optional
Reaction names to tally
energy_bound : 2-tuple of float, optional
Bounds for the energy group.
run_kwargs : dict, optional
Keyword arguments passed to :meth:`openmc.model.Model.run`
Returns
-------
MicroXS
Cross section data in [b]
"""
groups = EnergyGroups(energy_bounds)
# Set up the reaction tallies
original_tallies = model.tallies
original_materials = deepcopy(model.materials)
tallies = Tallies()
xs = {}
reactions, diluted_materials = cls._add_dilute_nuclides(chain_file,
model,
dilute_initial)
model.materials = diluted_materials
for rx in reactions:
if rx == 'fission':
xs[rx] = FissionXS(domain=reaction_domain,
energy_groups=groups, by_nuclide=True)
else:
xs[rx] = ArbitraryXS(rx, domain=reaction_domain,
energy_groups=groups, by_nuclide=True)
tallies += xs[rx].tallies.values()
model.tallies = tallies
# create temporary run
with tempfile.TemporaryDirectory() as temp_dir:
if run_kwargs is None:
run_kwargs = {}
run_kwargs.setdefault('cwd', temp_dir)
statepoint_path = model.run(**run_kwargs)
with StatePoint(statepoint_path) as sp:
for rx in xs:
xs[rx].load_from_statepoint(sp)
# Build the DataFrame
series = {}
for rx in xs:
df = xs[rx].get_pandas_dataframe(xs_type='micro')
series[rx] = df.set_index('nuclide')['mean']
# Revert to the original tallies and materials
model.tallies = original_tallies
model.materials = original_materials
return cls(series)
@classmethod
def _add_dilute_nuclides(cls, chain_file, model, dilute_initial):
"""
Add nuclides not present in burnable materials that have neutron data
and are present in the depletion chain to those materials. This allows
us to tally those specific nuclides for reactions to create one-group
cross sections.
Parameters
----------
chain_file : str
Path to the depletion chain XML file that will be used in depletion
simulation. Used to determine cross sections for materials not
present in the inital composition.
model : openmc.Model
Model object
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 cross
section data. Only done for nuclides with reaction rates.
Returns
-------
reactions : list of str
List of reaction names
diluted_materials : openmc.Materials
:class:`openmc.Materials` object with nuclides added to burnable
materials.
"""
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 = Chain.from_xml(chain_file)
reactions = chain.reactions
cross_sections = _find_cross_sections(model)
nuclides_with_data = _get_nuclides_with_data(cross_sections)
burnable_nucs = [nuc.name for nuc in chain.nuclides
if nuc.name in nuclides_with_data]
diluted_materials = Materials()
for material in model.materials:
if material.depletable:
nuc_densities = material.get_nuclide_atom_densities()
dilute_density = 1.0e-24 * dilute_initial
material.set_density('sum')
for nuc, density in nuc_densities.items():
material.remove_nuclide(nuc)
material.add_nuclide(nuc, density)
for burn_nuc in burnable_nucs:
if burn_nuc not in nuc_densities:
material.add_nuclide(burn_nuc,
dilute_density)
diluted_materials.append(material)
return reactions, diluted_materials
[docs] @classmethod
def from_array(cls, nuclides, reactions, data):
"""
Creates a ``MicroXS`` object from arrays.
Parameters
----------
nuclides : list of str
List of nuclide symbols for that have data for at least one
reaction.
reactions : list of str
List of reactions. All reactions must match those in
:data:`openmc.deplete.chain.REACTIONS`
data : ndarray of floats
Array containing one-group microscopic cross section values for
each nuclide and reaction. Cross section values are assumed to be
in [b].
Returns
-------
MicroXS
"""
# Validate inputs
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}')
cls._validate_micro_xs_inputs(
nuclides, reactions, data)
micro_xs = cls(index=nuclides, columns=reactions, data=data)
return micro_xs
[docs] @classmethod
def from_csv(cls, csv_file, **kwargs):
"""
Load a ``MicroXS`` object from a ``.csv`` file.
Parameters
----------
csv_file : str
Relative path to csv-file containing microscopic cross section
data. Cross section values are assumed to be in [b]
**kwargs : dict
Keyword arguments to pass to :func:`pandas.read_csv()`.
Returns
-------
MicroXS
"""
if 'float_precision' not in kwargs:
kwargs['float_precision'] = 'round_trip'
micro_xs = cls(read_csv(csv_file, index_col=0, **kwargs))
cls._validate_micro_xs_inputs(list(micro_xs.index),
list(micro_xs.columns),
micro_xs.to_numpy())
return micro_xs
@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)