Skip to content

Commit

Permalink
Refactor MicroXS class and usage in IndependentOperator (#2595)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonathan Shimwell <[email protected]>
Co-authored-by: Olek <[email protected]>
  • Loading branch information
3 people authored Aug 31, 2023
1 parent c93feed commit d21b653
Show file tree
Hide file tree
Showing 14 changed files with 437 additions and 316 deletions.
10 changes: 10 additions & 0 deletions docs/source/pythonapi/deplete.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ The :class:`CoupledOperator` and :class:`IndependentOperator` classes must also
have some knowledge of how nuclides transmute and decay. This is handled by the
:class:`Chain` class.

The :class:`IndependentOperator` class requires a set of fluxes and microscopic
cross sections. The following function can be used to generate this information:

.. autosummary::
:toctree: generated
:nosignatures:
:template: myfunction.rst

get_microxs_and_flux

Minimal Example
---------------

Expand Down
149 changes: 82 additions & 67 deletions docs/source/usersguide/depletion.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,43 +197,54 @@ across all material instances.
Transport-independent depletion
===============================

.. warning::

This feature is still under heavy development and has yet to be rigorously
verified. API changes and feature additions are possible and likely in
the near future.

This category of operator uses one-group microscopic cross sections to obtain
transmutation reaction rates. The cross sections are pre-calculated, so there is
no need for direct coupling between a transport-independent operator and a
transport solver. The :mod:`openmc.deplete` module offers a single
transport-independent operator, :class:`~openmc.deplete.IndependentOperator`,
and only one operator is needed since, in theory, any transport code could
calcuate the one-group microscopic cross sections.
This category of operator uses multigroup microscopic cross sections along with
multigroup flux spectra to obtain transmutation reaction rates. The cross
sections are pre-calculated, so there is no need for direct coupling between a
transport-independent operator and a transport solver. The :mod:`openmc.deplete`
module offers a single transport-independent operator,
:class:`~openmc.deplete.IndependentOperator`, and only one operator is needed
since, in theory, any transport code could calculate the multigroup microscopic
cross sections. The :class:`~openmc.deplete.IndependentOperator` class has two
constructors. The default constructor requires a :class:`openmc.Materials`
instance, a list of multigroup flux arrays, and a list of
:class:`~openmc.deplete.MicroXS` instances containing multigroup microscopic
cross sections in units of barns. This might look like the following::

materials = openmc.Materials([m1, m2, m3])
...

The :class:`~openmc.deplete.IndependentOperator` class has two constructors.
The default constructor requires a :class:`openmc.Materials` instance, a
:class:`~openmc.deplete.MicroXS` instance containing one-group microscoic cross
sections in units of barns, and a path to a depletion chain file::
# Assign fluxes (generated from any code)
flux_m1 = numpy.array([...])
flux_m2 = numpy.array([...])
flux_m3 = numpy.array([...])
fluxes = [flux_m1, flux_m2, flux_m3]

materials = openmc.Materials()
...
# Assign microscopic cross sections
micro_m1 = openmc.deplete.MicroXS.from_csv('xs_m1.csv')
micro_m2 = openmc.deplete.MicroXS.from_csv('xs_m2.csv')
micro_m3 = openmc.deplete.MicroXS.from_csv('xs_m3.csv')
micros = [micro_m1, micro_m2, micro_m3]

# load in the microscopic cross sections
micro_xs = openmc.deplete.MicroXS.from_csv(micro_xs_path)
# Create operator
op = openmc.deplete.IndependentOperator(materials, fluxes, micros)

op = openmc.deplete.IndependentOperator(materials, micro_xs, chain_file)
For more details on the :class:`~openmc.deplete.MicroXS` class, including how to
use OpenMC's transport solver to generate microscopic cross sections and fluxes
for use with :class:`~openmc.deplete.IndependentOperator`, see :ref:`micros`.

.. note::

The same statements from :ref:`coupled-depletion` about which
materials are depleted and the requirement for depletable materials to have
a specified volume also apply here.
The same statements from :ref:`coupled-depletion` about which materials are
depleted and the requirement for depletable materials to have a specified
volume also apply here.

An alternate constructor,
:meth:`~openmc.deplete.IndependentOperator.from_nuclides`, accepts a volume and
dictionary of nuclide concentrations in place of the :class:`openmc.Materials`
instance::
instance. Note that while the normal constructor allows multiple materials to be
depleted with a single operator, the
:meth:`~openmc.deplete.IndependentOperator.from_nuclides` classmethod only works
for a single material::

nuclides = {'U234': 8.92e18,
'U235': 9.98e20,
Expand All @@ -244,6 +255,7 @@ instance::
volume = 0.5
op = openmc.deplete.IndependentOperator.from_nuclides(volume,
nuclides,
flux,
micro_xs,
chain_file,
nuc_units='atom/cm3')
Expand All @@ -253,18 +265,21 @@ 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. If cross section data is not present for
a nuclide in the depletion chain with at least one reaction, that reaction
will not be simulated.
Ideally, multigroup cross section data should be available for every reaction
in the depletion chain. If cross section data is not present for a nuclide in
the depletion chain with at least one reaction, that reaction will not be
simulated.

.. _micros:

Loading and Generating Microscopic Cross Sections
-------------------------------------------------

As mentioned earlier, any transport code could be used to calculate one-group
microscopic cross sections. The :mod:`openmc.deplete` module provides the
:class:`~openmc.deplete.MicroXS` class, which contains methods to read in
pre-calculated cross sections from a ``.csv`` file or from data arrays::
As mentioned above, any transport code could be used to calculate multigroup
microscopic cross sections and fluxes. The :mod:`openmc.deplete` module provides
the :class:`~openmc.deplete.MicroXS` class, which can either be instantiated
from pre-calculated cross sections in a ``.csv`` file or from data arrays
directly::

micro_xs = MicroXS.from_csv(micro_xs_path)

Expand All @@ -273,37 +288,31 @@ pre-calculated cross sections from a ``.csv`` file or from data arrays::
data = np.array([[0.1, 0.2],
[0.3, 0.4],
[0.01, 0.5]])
micro_xs = MicroXS.from_array(nuclides, reactions, data)
micro_xs = MicroXS(data, nuclides, reactions)

.. important::

Both :meth:`~openmc.deplete.MicroXS.from_csv()` and
:meth:`~openmc.deplete.MicroXS.from_array()` assume the cross section values
provided are in barns by defualt, but have no way of verifying this. Make
sure your cross sections are in the correct units before passing to a
The cross section values are assumed to be in units of barns. Make sure your
cross sections are in the correct units before passing to a
:class:`~openmc.deplete.IndependentOperator` object.

The :class:`~openmc.deplete.MicroXS` class also contains a method to generate one-group microscopic cross sections using OpenMC's transport solver. The
:meth:`~openmc.deplete.MicroXS.from_model()` method will produce a
:class:`~openmc.deplete.MicroXS` instance with microscopic cross section data in
units of barns::

import openmc
Additionally, a convenience function,
:func:`~openmc.deplete.get_microxs_and_flux`, can provide the needed fluxes and
cross sections using OpenMC's transport solver::

model = openmc.Model.from_xml()
model = openmc.Model()
...

micro_xs = openmc.deplete.MicroXS.from_model(model,
model.materials[0],
chain_file)
fluxes, micros = openmc.deplete.get_microxs_and_flux(model, materials)

If you are running :meth:`~openmc.deplete.MicroXS.from_model()` on a cluster
If you are running :func:`~openmc.deplete.get_microxs_and_flux` on a cluster
where temporary files are created on a local filesystem that is not shared
across nodes, you'll need to set an environment variable pointing to a local
directoy so that each MPI process knows where to store output files used to
calculate the microscopic cross sections. In order of priority, they are
:envvar:`TMPDIR`. :envvar:`TEMP`, and :envvar:`TMP`. Users interested in
further details can read the documentation for the `tempfile <https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir>`_ module.

:envvar:`TMPDIR`. :envvar:`TEMP`, and :envvar:`TMP`. Users interested in further
details can read the documentation for the `tempfile
<https://docs.python.org/3/library/tempfile.html#tempfile.gettempdir>`_ module.

Caveats
-------
Expand All @@ -325,24 +334,30 @@ normalizing reaction rates:
the time integrator is a flux, and obtains the reaction rates by multiplying
the cross sections by the ``source-rate``.
2. ``fission-q`` normalization, which uses the ``power`` or ``power_density``
provided by the time integrator to obtain reaction rates by computing a value
for the flux based on this power. The equation we use for this calculation is
provided by the time integrator to obtain normalized reaction rates by
computing a normalization factor as the ratio of the user-specified power to
the "observed" power based on fission reaction rates. The equation for the
normalization factor is

.. math::
:label: fission-q
\phi = \frac{P}{\sum\limits_i (Q_i \sigma^f_i N_i)}
f = \frac{P}{\sum\limits_m \sum\limits_i \left(Q_i N_{i,m} \sum\limits_g
\sigma^f_{i,g,m} \phi_{g,m} \right)}
where :math:`P` is the power, :math:`Q_i` is the fission Q value for nuclide
:math:`i`, :math:`\sigma_i^f` is the microscopic fission cross section for
nuclide :math:`i`, and :math:`N_i` is the number of atoms of nuclide
:math:`i`. This equation makes the same assumptions and issues as discussed
in :ref:`energy-deposition`. Unfortunately, the proposed solution in that
section does not apply here since we are decoupled from transport code.
However, there is a method to converge to a more accurate value for flux by
using substeps during time integration. `This paper
<https://doi.org/10.1016/j.anucene.2016.05.031>`_ provides a good discussion
of this method.
:math:`i`, :math:`\sigma_{i,g,m}^f` is the microscopic fission cross section
for nuclide :math:`i` in energy group :math:`g` for material :math:`m`,
:math:`\phi_{g,m}` is the neutron flux in group :math:`g` for material
:math:`m`, and :math:`N_{i,m}` is the number of atoms of nuclide :math:`i`
for material :math:`m`. Reaction rates are then multiplied by :math:`f` so
that the total fission power matches :math:`P`. This equation makes the same
assumptions and issues as discussed in :ref:`energy-deposition`.
Unfortunately, the proposed solution in that section does not apply here
since we are decoupled from transport code. However, there is a method to
converge to a more accurate value for flux by using substeps during time
integration. `This paper <https://doi.org/10.1016/j.anucene.2016.05.031>`_
provides a good discussion of this method.

.. warning::

Expand All @@ -359,14 +374,14 @@ useful for running many different cases of a particular scenario. A
transport-independent depletion simulation using ``fission-q`` normalization
will sum the fission energy values across all materials into :math:`Q_i` in
Equation :math:numref:`fission-q`, and Equation :math:numref:`fission-q`
provides the flux we use to calculate the reaction rates in each material.
provides the normalization factor applied to reaction rates in each material.
This can be useful for running a scenario with multiple depletable materials
that are part of the same reactor. This behavior may change in the future.

Time integration
~~~~~~~~~~~~~~~~

The values of the one-group microscopic cross sections passed to
The values of the microscopic cross sections passed to
:class:`openmc.deplete.IndependentOperator` are fixed for the entire depletion
simulation. This implicit assumption may produce inaccurate results for certain
scenarios.
Expand Down
8 changes: 4 additions & 4 deletions openmc/deplete/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,13 @@ def reset_tally_means(self):
"""
self._rate_tally_means_cache = None

def get_material_rates(self, mat_id, nuc_index, rx_index):
def get_material_rates(self, mat_index, nuc_index, rx_index):
"""Return an array of reaction rates for a material
Parameters
----------
mat_id : int
Unique ID for the requested material
mat_index : int
Index for the material
nuc_index : iterable of int
Index for each nuclide in :attr:`nuclides` in the
desired reaction rate matrix
Expand All @@ -216,7 +216,7 @@ def get_material_rates(self, mat_id, nuc_index, rx_index):
reaction rates in this material
"""
self._results_cache.fill(0.0)
full_tally_res = self.rate_tally_means[mat_id]
full_tally_res = self.rate_tally_means[mat_index]
for i_tally, (i_nuc, i_rx) in enumerate(product(nuc_index, rx_index)):
self._results_cache[i_nuc, i_rx] = full_tally_res[i_tally]

Expand Down
Loading

0 comments on commit d21b653

Please sign in to comment.