Merge branch 'openmc-dev:develop' into infix-sense-evaluation

This commit is contained in:
Patrick Myers 2022-08-15 09:19:30 -05:00 committed by GitHub
commit 03deeca32d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
127 changed files with 8374 additions and 4513 deletions

1
.gitignore vendored
View file

@ -3,6 +3,7 @@
*.o
*.log
*.out
*.pkl
# Compiler python objects
*.pyc

View file

@ -37,6 +37,37 @@ option(OPENMC_USE_DAGMC "Enable support for DAGMC (CAD) geometry"
option(OPENMC_USE_LIBMESH "Enable support for libMesh unstructured mesh tallies" OFF)
option(OPENMC_USE_MPI "Enable MPI" OFF)
# Warnings for deprecated options
foreach(OLD_OPT IN ITEMS "openmp" "profile" "coverage" "dagmc" "libmesh")
if(DEFINED ${OLD_OPT})
string(TOUPPER ${OLD_OPT} OPT_UPPER)
if ("${OLD_OPT}" STREQUAL "profile" OR "${OLD_OPT}" STREQUAL "coverage")
set(NEW_OPT_PREFIX "OPENMC_ENABLE")
else()
set(NEW_OPT_PREFIX "OPENMC_USE")
endif()
message(WARNING "The OpenMC CMake option '${OLD_OPT}' has been deprecated. "
"Its value will be ignored. "
"Please use '-D${NEW_OPT_PREFIX}_${OPT_UPPER}=${${OLD_OPT}}' instead.")
unset(${OLD_OPT} CACHE)
endif()
endforeach()
foreach(OLD_BLD in ITEMS "debug" "optimize")
if(DEFINED ${OLD_BLD})
if("${OLD_BLD}" STREQUAL "debug")
set(BLD_VAR "Debug")
else()
set(BLD_VAR "Release")
endif()
message(WARNING "The OpenMC CMake option '${OLD_BLD}' has been deprecated. "
"Its value will be ignored. "
"OpenMC now uses the CMAKE_BUILD_TYPE variable to set the build mode. "
"Please use '-DCMAKE_BUILD_TYPE=${BLD_VAR}' instead.")
unset(${OLD_BLD} CACHE)
endif()
endforeach()
#===============================================================================
# Set a default build configuration if not explicitly specified
#===============================================================================

View file

@ -15,22 +15,24 @@
# sudo docker run image_name:tag_name or ID with no tag sudo docker run ID number
FROM debian:bullseye-slim AS dependencies
# global ARG as these ARGS are used in multiple stages
# By default one core is used to compile
ARG compile_cores=1
# By default this Dockerfile builds OpenMC without DAGMC and LIBMESH support
ARG build_dagmc=off
ARG build_libmesh=off
# By default one core is used to compile
ARG compile_cores=1
FROM debian:bullseye-slim AS dependencies
ARG compile_cores
ARG build_dagmc
ARG build_libmesh
# Set default value of HOME to /root
ENV HOME=/root
# OpenMC variables
ARG openmc_branch=master
ENV OPENMC_REPO='https://github.com/openmc-dev/openmc'
# Embree variables
ENV EMBREE_TAG='v3.12.2'
ENV EMBREE_REPO='https://github.com/embree/embree'
@ -60,7 +62,6 @@ ENV NJOY_REPO='https://github.com/njoy/NJOY2016'
# Setup environment variables for Docker image
ENV LD_LIBRARY_PATH=${DAGMC_INSTALL_DIR}/lib:$LD_LIBRARY_PATH \
OPENMC_CROSS_SECTIONS=/root/nndc_hdf5/cross_sections.xml \
OPENMC_ENDF_DATA=/root/endf-b-vii.1 \
DEBIAN_FRONTEND=noninteractive
@ -174,12 +175,25 @@ RUN if [ "$build_libmesh" = "on" ]; then \
FROM dependencies AS build
ENV HOME=/root
ARG openmc_branch=master
ENV OPENMC_REPO='https://github.com/openmc-dev/openmc'
ARG compile_cores
ARG build_dagmc
ARG build_libmesh
ENV DAGMC_INSTALL_DIR=$HOME/DAGMC/
ENV LIBMESH_INSTALL_DIR=$HOME/LIBMESH
# clone and install openmc
RUN mkdir -p ${HOME}/OpenMC && cd ${HOME}/OpenMC \
&& git clone --shallow-submodules --recurse-submodules --single-branch -b ${openmc_branch} --depth=1 ${OPENMC_REPO} \
&& mkdir build && cd build ; \
if [ ${build_dagmc} = "on" ] && [ ${build_libmesh} = "on" ]; then \
cmake ../openmc \
-DCMAKE_CXX_COMPILER=mpicxx \
-DOPENMC_USE_MPI=on \
-DHDF5_PREFER_PARALLEL=on \
-DOPENMC_USE_DAGMC=on \
@ -188,6 +202,7 @@ RUN mkdir -p ${HOME}/OpenMC && cd ${HOME}/OpenMC \
fi ; \
if [ ${build_dagmc} = "on" ] && [ ${build_libmesh} = "off" ]; then \
cmake ../openmc \
-DCMAKE_CXX_COMPILER=mpicxx \
-DOPENMC_USE_MPI=on \
-DHDF5_PREFER_PARALLEL=on \
-DOPENMC_USE_DAGMC=ON \
@ -195,6 +210,7 @@ RUN mkdir -p ${HOME}/OpenMC && cd ${HOME}/OpenMC \
fi ; \
if [ ${build_dagmc} = "off" ] && [ ${build_libmesh} = "on" ]; then \
cmake ../openmc \
-DCMAKE_CXX_COMPILER=mpicxx \
-DOPENMC_USE_MPI=on \
-DHDF5_PREFER_PARALLEL=on \
-DOPENMC_USE_LIBMESH=on \
@ -202,6 +218,7 @@ RUN mkdir -p ${HOME}/OpenMC && cd ${HOME}/OpenMC \
fi ; \
if [ ${build_dagmc} = "off" ] && [ ${build_libmesh} = "off" ]; then \
cmake ../openmc \
-DCMAKE_CXX_COMPILER=mpicxx \
-DOPENMC_USE_MPI=on \
-DHDF5_PREFER_PARALLEL=on ; \
fi ; \
@ -211,5 +228,8 @@ RUN mkdir -p ${HOME}/OpenMC && cd ${HOME}/OpenMC \
FROM build AS release
ENV HOME=/root
ENV OPENMC_CROSS_SECTIONS=/root/nndc_hdf5/cross_sections.xml
# Download cross sections (NNDC and WMP) and ENDF data needed by test suite
RUN ${HOME}/OpenMC/openmc/tools/ci/download-xs.sh

View file

@ -339,6 +339,16 @@ Incoherent elastic scattering
[eV\ :math:`^{-1}`].
:Attributes: - **type** (*char[]*) -- 'IncoherentElastic'
Sum of functions
----------------
:Object type: Group
:Attributes: - **type** (*char[]*) -- "Sum"
- **n** (*int*) -- Number of functions
:Datasets:
- ***func_<i>** (:ref:`function <1d_functions>`) -- Dataset for the
i-th function (indexing starts at 1)
.. _angle_energy:
--------------------------
@ -501,6 +511,19 @@ equiprobable bins.
- **skewed** (*int8_t*) -- Whether discrete angles are equi-probable
(0) or have a skewed distribution (1).
Mixed Elastic
-------------
This angle-energy distribution is used when an evaluation specifies both
coherent and incoherent elastic thermal neutron scattering.
:Object type: Group
:Attributes: - **type** (*char[]*) -- "mixed_elastic"
:Groups: - **coherent** -- Distribution for coherent elastic scattering. The
format is given in :ref:`angle_energy`.
- **incoherent** -- Distribution for incoherent elastic scattering.
The format is given in :ref:`angle_energy`.
.. _energy_distribution:
--------------------

View file

@ -79,9 +79,15 @@ The current version of the statepoint file format is 17.0.
- **width** (*double[]*) -- Width of each mesh cell in each
dimension.
- **Unstructured Mesh Only:**
- **filename** (*char[]*) -- Name of the mesh file.
- **library** (*char[]*) -- Mesh library used to represent the
mesh ("moab" or "libmesh").
- **length_multiplier** (*double*) Scaling factor applied to the mesh.
- **volumes** (*double[]*) -- Volume of each mesh cell.
- **centroids** (*double[]*) -- Location of the mesh cell
centroids.
- **vertices** (*double[]*) -- x, y, z values of the mesh vertices.
- **connectivity** (*int[]*) -- Connectivity array for the mesh
cells.
- **element_types** (*int[]*) -- Mesh element types.
**/tallies/filters/**

View file

@ -103,16 +103,17 @@ integrate over the entire timestep.
Our aim here is not to exhaustively describe all integration methods but rather
to give a few examples that elucidate the main considerations one must take into
account when choosing a method. Generally, there is a tradeoff between the
accuracy of the method and its computational expense. The expense is driven
almost entirely by the time to compute a transport solution, i.e., to evaluate
:math:`\mathbf{A}` for a given :math:`\mathbf{n}`. Thus, the cost of a method
scales with the number of :math:`\mathbf{A}` evaluations that are performed per
timestep. On the other hand, methods that require more evaluations generally
achieve higher accuracy. The predictor method only requires one evaluation and
its error converges as :math:`\mathcal{O}(h)`. The CE/CM method requires two
evaluations and is thus twice as expensive as the predictor method, but achieves
an error of :math:`\mathcal{O}(h^2)`. An exhaustive description of time
integration methods and their merits can be found in the `thesis of Colin Josey
accuracy of the method and its computational expense. In the case of
transport-coupled depletion, the expense is driven almost entirely by the time
to compute a transport solution, i.e., to evaluate :math:`\mathbf{A}` for a
given :math:`\mathbf{n}`. Thus, the cost of a method scales with the number of
:math:`\mathbf{A}` evaluations that are performed per timestep. On the other
hand, methods that require more evaluations generally achieve higher accuracy.
The predictor method only requires one evaluation and its error converges as
:math:`\mathcal{O}(h)`. The CE/CM method requires two evaluations and is thus
twice as expensive as the predictor method, but achieves an error of
:math:`\mathcal{O}(h^2)`. An exhaustive description of time integration methods
and their merits can be found in the `thesis of Colin Josey
<http://dspace.mit.edu/handle/1721.1/7582>`_.
OpenMC does not rely on a single time integration method but rather has several
@ -169,12 +170,14 @@ Data Considerations
In principle, solving Eq. :eq:`depletion-matrix` using CRAM is fairly simple:
just construct the burnup matrix at various times and solve a set of sparse
linear systems. However, constructing the burnup matrix itself involves not only
solving the transport equation to estimate transmutation reaction rates but also
a series of choices about what data to include. In OpenMC, the burnup matrix is
constructed based on data inside of a *depletion chain* file, which includes
fundamental data gathered from ENDF incident neutron, decay, and fission product
yield sublibraries. For each nuclide, this file includes:
linear systems. However, constructing the burnup matrix itself involves not
only solving the transport equation to estimate transmutation reaction rates
(in the case of transport-coupled depletion) or to obtain microscopic cross
sections (in the case of transport-independent depletion), but also a series of
choices about what data to include. In OpenMC, the burnup matrix is constructed
based on data inside of a *depletion chain* file, which includes fundamental
data gathered from ENDF incident neutron, decay, and fission product yield
sublibraries. For each nuclide, this file includes:
- What transmutation reactions are possible, their Q values, and their products;
- If a nuclide is not stable, what decay modes are possible, their branching
@ -185,9 +188,12 @@ yield sublibraries. For each nuclide, this file includes:
Transmutation Reactions
-----------------------
OpenMC will setup tallies in a problem based on what transmutation reactions are
available in a depletion chain file, so any arbitrary number of transmutation
reactions can be tracked. The pregenerated chain files that are available on
In transport-coupled depletion, OpenMC will setup tallies in a problem based on
what transmutation reactions are available in a depletion chain file, so any
arbitrary number of transmutation reactions can be tracked. In
transport-independent depletion, OpenMC will calculate reaction rates for every
reaction that is present in both the available cross sections and the depletion
chain file. The pregenerated chain files that are available on
https://openmc.org include the following transmutation reactions: fission, (n,\
:math:`\gamma`\ ), (n,2n), (n,3n), (n,4n), (n,p), and (n,\ :math:`\alpha`\ ).
@ -202,11 +208,12 @@ accurately model the branching of the capture reaction in Am241. This is
complicated by the fact that the branching ratio may depend on the incident
neutron energy causing capture.
OpenMC does not currently allow energy-dependent capture branching ratios.
However, the depletion chain file does allow a transmutation reaction to be
listed multiple times with different branching ratios resulting in different
products. Spectrum-averaged capture branching ratios have been computed in LWR
and SFR spectra and are available at https://openmc.org/depletion-chains.
OpenMC's transport solver does not currently allow energy-dependent capture
branching ratios. However, the depletion chain file does allow a transmutation
reaction to be listed multiple times with different branching ratios resulting
in different products. Spectrum-averaged capture branching ratios have been
computed in LWR and SFR spectra and are available at
https://openmc.org/depletion-chains.
Fission Product Yields
----------------------
@ -217,26 +224,31 @@ energies. It is an open question as to what the best way to handle this energy
dependence is. OpenMC includes three methods for treating the energy dependence
of FPY:
1. Use FPY data corresponding to a specified energy.
1. Use FPY data corresponding to a specified energy. This is used by default in
both transport-coupled and transport-independent depletion.
2. Tally fission rates above and below a specified cutoff energy. Assume that
all fissions below the cutoff energy correspond to thermal FPY data and all
fission above the cutoff energy correspond to fast FPY data.
fission above the cutoff energy correspond to fast FPY data. Only applicable
to transport-coupled depletion.
3. Compute the average energy at which fission events occur and use an effective
FPY by linearly interpolating between FPY provided at neighboring energies.
Only applicable to transport-coupled depletion.
The method can be selected through the ``fission_yield_mode`` argument to the
:class:`openmc.deplete.Operator` constructor.
The method for transport-coupled depletion can be selected through the
``fission_yield_mode`` argument to the :class:`openmc.deplete.CoupledOperator`
constructor.
Power Normalization
-------------------
The reaction rates provided OpenMC are given in units of reactions per source
particle. For depletion, it is necessary to compute an absolute reaction rate in
reactions per second. To do so, the reaction rates are normalized based on a
specified power. A complete description of how this normalization can be
performed is described in :ref:`usersguide_tally_normalization`. Here, we simply
note that the main depletion class, :class:`openmc.deplete.Operator`, allows the
user to choose one of two methods for estimating the heating rate, including:
In transport-coupled depletion, the reaction rates provided OpenMC are given in
units of reactions per source particle. For depletion, it is necessary to
compute an absolute reaction rate in reactions per second. To do so, the
reaction rates are normalized based on a specified power. A complete
description of how this normalization can be performed is described in
:ref:`usersguide_tally_normalization`. Here, we simply note that the main
depletion class, :class:`openmc.deplete.CoupledOperator`, allows the user to
choose one of two methods for estimating the heating rate, including:
1. Using fixed Q values from a depletion chain file (useful for comparisons to
other codes that use fixed Q values), or
@ -244,4 +256,4 @@ user to choose one of two methods for estimating the heating rate, including:
energy-dependent estimate of the true heating rate.
The method for normalization can be chosen through the ``normalization_mode``
argument to the :class:`openmc.deplete.Operator` class.
argument to the :class:`openmc.deplete.CoupledOperator` class.

View file

@ -61,11 +61,13 @@ Core Functions
atomic_mass
atomic_weight
combine_distributions
decay_constant
dose_coefficients
gnd_name
half_life
isotopes
kalbach_slope
linearize
thin
water_density
@ -116,6 +118,7 @@ Angle-Energy Distributions
IncoherentElasticAE
IncoherentElasticAEDiscrete
IncoherentInelasticAEDiscrete
MixedElasticAE
Resonance Data
--------------

View file

@ -15,16 +15,18 @@ are:
1) A transport operator
2) A time-integration scheme
The former is responsible for executing a transport code, like OpenMC,
and retaining important information required for depletion. The most common examples
are reaction rates and power normalization data. The latter is responsible for
projecting reaction rates and compositions forward in calendar time across
some step size :math:`\Delta t`, and obtaining new compositions given a power
or power density. The :class:`Operator` is provided to handle communicating with
OpenMC. Several classes are provided that implement different time-integration
algorithms for depletion calculations, which are described in detail in Colin
Josey's thesis, `Development and analysis of high order neutron
transport-depletion coupling algorithms <http://hdl.handle.net/1721.1/113721>`_.
The former is responsible for calculating and retaining important information
required for depletion. The most common examples are reaction rates and power
normalization data. The latter is responsible for projecting reaction rates and
compositions forward in calendar time across some step size :math:`\Delta t`,
and obtaining new compositions given a power or power density. The
:class:`CoupledOperator` class is provided to obtain reaction rates via tallies
through OpenMC's transport solver, and the :class:`IndependentOperator` class is
provided to obtain reaction rates from cross-section data. Several classes are
provided that implement different time-integration algorithms for depletion
calculations, which are described in detail in Colin Josey's thesis,
`Development and analysis of high order neutron transport-depletion coupling
algorithms <http://hdl.handle.net/1721.1/113721>`_.
.. autosummary::
:toctree: generated
@ -40,18 +42,20 @@ transport-depletion coupling algorithms <http://hdl.handle.net/1721.1/113721>`_.
SICELIIntegrator
SILEQIIntegrator
Each of these classes expects a "transport operator" to be passed. An operator
specific to OpenMC is available using the following class:
Each of these classes expects a "transport operator" to be passed. OpenMC
provides the following transport operator classes:
.. autosummary::
:toctree: generated
:nosignatures:
:template: mycallable.rst
Operator
CoupledOperator
IndependentOperator
The :class:`Operator` must also have some knowledge of how nuclides transmute
and decay. This is handled by the :class:`Chain`.
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.
Minimal Example
---------------
@ -64,11 +68,12 @@ A minimal example for performing depletion would be:
>>> import openmc.deplete
>>> geometry = openmc.Geometry.from_xml()
>>> settings = openmc.Settings.from_xml()
>>> model = openmc.model.Model(geometry, settings)
# Representation of a depletion chain
>>> chain_file = "chain_casl.xml"
>>> operator = openmc.deplete.Operator(
... geometry, settings, chain_file)
>>> operator = openmc.deplete.CoupledOperator(
... model, chain_file)
# Set up 5 time steps of one day each
>>> dt = [24 * 60 * 60] * 5
@ -131,6 +136,7 @@ data, such as number densities and reaction rates for each material.
:template: myclass.rst
AtomNumber
MicroXS
OperatorResult
ReactionRates
Results
@ -172,7 +178,7 @@ with :func:`cram.CRAM48` being the default.
:class:`multiprocessing.pool.Pool` class. If set to ``None`` (default), the
number returned by :func:`os.cpu_count` is used.
The following classes are used to help the :class:`openmc.deplete.Operator`
The following classes are used to help the :class:`openmc.deplete.CoupledOperator`
compute quantities like effective fission yields, reaction rates, and
total system energy.
@ -189,14 +195,45 @@ total system energy.
helpers.FissionYieldCutoffHelper
helpers.FluxCollapseHelper
The :class:`openmc.deplete.IndependentOperator` uses inner classes subclassed
from those listed above to perform similar calculations.
Intermediate Classes
--------------------
Specific implementations of abstract base classes may utilize some of
the same methods and data structures. These methods and data are stored
in intermediate classes.
Methods common to tally-based implementation of :class:`FissionYieldHelper`
are stored in :class:`helpers.TalliedFissionYieldHelper`
.. autosummary::
:toctree: generated
:nosignatures:
:template: myclass.rst
helpers.TalliedFissionYieldHelper
Methods common to OpenMC-specific implementations of :class:`TransportOperator`
are stored in :class:`openmc_operator.OpenMCOperator`
.. autosummary::
:toctree: generated
:nosignatures:
:template: mycallable.rst
openmc_operator.OpenMCOperator
Abstract Base Classes
---------------------
A good starting point for extending capabilities in :mod:`openmc.deplete` is
to examine the following abstract base classes. Custom classes can
inherit from :class:`abc.TransportOperator` to implement alternative
schemes for collecting reaction rates and other data from a transport code
prior to depleting materials
schemes for collecting reaction rates and other data prior to depleting
materials
.. autosummary::
:toctree: generated
@ -206,7 +243,9 @@ prior to depleting materials
abc.TransportOperator
The following classes are abstract classes used to pass information from
OpenMC simulations back on to the :class:`abc.TransportOperator`
transport simulations (in the case of transport-coupled depletion) or to
simply calculate these quantities directly (in the case of
transport-independent depletion) back on to the :class:`abc.TransportOperator`
.. autosummary::
:toctree: generated
@ -216,7 +255,6 @@ OpenMC simulations back on to the :class:`abc.TransportOperator`
abc.NormalizationHelper
abc.FissionYieldHelper
abc.ReactionRateHelper
abc.TalliedFissionYieldHelper
Custom integrators or depletion solvers can be developed by subclassing from
the following abstract base classes:

View file

@ -41,6 +41,7 @@ Multi-group Cross Sections
openmc.mgxs.KappaFissionXS
openmc.mgxs.MultiplicityMatrixXS
openmc.mgxs.NuFissionMatrixXS
openmc.mgxs.ReducedAbsorptionXS
openmc.mgxs.ScatterXS
openmc.mgxs.ScatterMatrixXS
openmc.mgxs.ScatterProbabilityMatrix

View file

@ -4,50 +4,31 @@
Depletion and Transmutation
===========================
OpenMC supports coupled depletion, or burnup, calculations through the
:mod:`openmc.deplete` Python module. OpenMC solves the transport equation to
obtain transmutation reaction rates, and then the reaction rates are used to
solve a set of transmutation equations that determine the evolution of nuclide
densities within a material. The nuclide densities predicted as some future time
are then used to determine updated reaction rates, and the process is repeated
for as many timesteps as are requested.
OpenMC supports transport-coupled and transport-independent depletion, or
burnup, calculations through the :mod:`openmc.deplete` Python module. OpenMC
uses transmutation reaction rates to solve a set of transmutation equations
that determine the evolution of nuclide densities within a material. The
nuclide densities predicted at some future time are then used to determine
updated reaction rates, and the process is repeated for as many timesteps as
are requested.
The depletion module is designed such that the flux/reaction rate solution (the
The depletion module is designed such that the reaction rate solution (the
transport "operator") is completely isolated from the solution of the
transmutation equations and the method used for advancing time. At present, the
:mod:`openmc.deplete` module offers a single transport operator,
:class:`openmc.deplete.Operator` (which uses the OpenMC transport solver), but
in principle additional operator classes based on other transport codes could be
implemented and no changes to the depletion solver itself would be needed. The
operator class requires a :class:`openmc.Geometry` instance and a
:class:`openmc.Settings` instance::
geom = openmc.Geometry()
settings = openmc.Settings()
...
op = openmc.deplete.Operator(geom, settings)
Any material that contains a fissionable nuclide is depleted by default, but
this can behavior can be changed with the :attr:`Material.depletable` attribute.
.. important:: The volume must be specified for each material that is depleted by
setting the :attr:`Material.volume` attribute. This is necessary
in order to calculate the proper normalization of tally results
based on the source rate.
transmutation equations and the method used for advancing time.
:mod:`openmc.deplete` supports multiple time-integration methods for determining
material compositions over time. Each method appears as a different class.
For example, :class:`openmc.deplete.CECMIntegrator` runs a depletion calculation
using the CE/CM algorithm (deplete over a timestep using the middle-of-step
reaction rates). An instance of :class:`openmc.deplete.Operator` is passed to
one of these functions along with the timesteps and power level::
reaction rates). An instance of :class:`~openmc.deplete.abc.TransportOperator`
is passed to one of these Integrator classes along with the timesteps and power
level::
power = 1200.0e6 # watts
timesteps = [10.0, 10.0, 10.0] # days
openmc.deplete.CECMIntegrator(op, timesteps, power, timestep_units='d').integrate()
The coupled transport-depletion problem is executed, and once it is done a
The depletion problem is executed, and once it is done a
``depletion_results.h5`` file is written. The results can be analyzed using the
:class:`openmc.deplete.Results` class. This class has methods that allow for
easy retrieval of k-effective, nuclide concentrations, and reaction rates over
@ -56,11 +37,41 @@ time::
results = openmc.deplete.Results("depletion_results.h5")
time, keff = results.get_keff()
Note that the coupling between the transport solver and the transmutation solver
happens in-memory rather than by reading/writing files on disk.
Note that the coupling between the reaction rate solver and the transmutation
solver happens in-memory rather than by reading/writing files on disk. OpenMC
has two categories of transport operators for obtaining transmutation reaction
rates.
.. _coupled-depletion:
Transport-coupled depletion
===========================
This category of operator solves the transport equation to obtain transmutation
reaction rates. At present, the :mod:`openmc.deplete` module offers a single
transport-coupled operator, :class:`openmc.deplete.CoupledOperator` (which uses
the OpenMC transport solver), but in principle additional transport-coupled
operator classes based on other transport codes could be implemented and no
changes to the depletion solver itself would be needed. The
:class:`openmc.deplete.CoupledOperator` class requires a :class:`~openmc.Model`
instance containing material, geometry, and settings information::
model = openmc.Model()
...
op = openmc.deplete.CoupledOperator(model)
Any material that contains a fissionable nuclide is depleted by default, but
this can behavior can be changed with the :attr:`Material.depletable` attribute.
.. important::
The volume must be specified for each material that is depleted by setting
the :attr:`Material.volume` attribute. This is necessary in order to
calculate the proper normalization of tally results based on the source rate.
Fixed-Source Transmutation
==========================
--------------------------
When the ``power`` or ``power_density`` argument is used for one of the
Integrator classes, it is assumed that OpenMC is running in k-eigenvalue mode,
@ -77,11 +88,11 @@ using the :attr:`Material.depletable` attribute::
mat = openmc.Material()
mat.depletable = True
When constructing the :class:`~openmc.deplete.Operator`, you should indicate
that normalization of tally results will be done based on the source rate rather
than a power or power density::
When constructing the :class:`~openmc.deplete.CoupledOperator`, you should
indicate that normalization of tally results will be done based on the source
rate rather than a power or power density::
op = openmc.deplete.Operator(geometry, settings, normalization_mode='source-rate')
op = openmc.deplete.CoupledOperator(model, normalization_mode='source-rate')
Finally, when creating a depletion integrator, use the ``source_rates`` argument::
@ -92,19 +103,22 @@ timestep in the calculation. A zero source rate for a given timestep will result
in a decay-only step, where all reaction rates are zero.
Caveats
=======
-------
.. _energy-deposition:
Energy Deposition
-----------------
~~~~~~~~~~~~~~~~~
The default energy deposition mode, ``"fission-q"``, instructs the
:class:`openmc.deplete.Operator` to normalize reaction rates using the product
of fission reaction rates and fission Q values taken from the depletion chain.
This approach does not consider indirect contributions to energy deposition,
such as neutron heating and energy from secondary photons. In doing this, the
energy deposited during a transport calculation will be lower than expected.
This causes the reaction rates to be over-adjusted to hit the user-specific
power, or power density, leading to an over-depletion of burnable materials.
:class:`~openmc.deplete.CoupledOperator` to normalize reaction rates using the
product of fission reaction rates and fission Q values taken from the depletion
chain. This approach does not consider indirect contributions to energy
deposition, such as neutron heating and energy from secondary photons. In doing
this, the energy deposited during a transport calculation will be lower than
expected. This causes the reaction rates to be over-adjusted to hit the
user-specific power, or power density, leading to an over-depletion of burnable
materials.
There are some remedies. First, the fission Q values can be directly set in a
variety of ways. This requires knowing what the total fission energy release
@ -113,29 +127,32 @@ should be, including indirect components. Some examples are provided below::
# use a dictionary of fission_q values
fission_q = {"U235": 202e+6} # energy in eV
# create a Model object
model = openmc.Model(geometry, settings)
# create a modified chain and write it to a new file
chain = openmc.deplete.Chain.from_xml("chain.xml", fission_q)
chain.export_to_xml("chain_mod_q.xml")
op = openmc.deplete.Operator(geometry, setting, "chain_mod_q.xml")
op = openmc.deplete.CoupledOperator(model, "chain_mod_q.xml")
# alternatively, pass the modified fission Q directly to the operator
op = openmc.deplete.Operator(geometry, setting, "chain.xml",
op = openmc.deplete.CoupledOperator(model, "chain.xml",
fission_q=fission_q)
A more complete way to model the energy deposition is to use the modified
heating reactions described in :ref:`methods_heating`. These values can be used
heating reactions described in :ref:`methods_heating`. These values can be used
to normalize reaction rates instead of using the fission reaction rates with::
op = openmc.deplete.Operator(geometry, settings, "chain.xml",
op = openmc.deplete.CoupledOperator(model, "chain.xml",
normalization_mode="energy-deposition")
These modified heating libraries can be generated by running the latest version
of :meth:`openmc.data.IncidentNeutron.from_njoy`, and will eventually be bundled
of :meth:`openmc.data.IncidentNeutron.from_njoy()`, and will eventually be bundled
into the distributed libraries.
Local Spectra and Repeated Materials
------------------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is not uncommon to explicitly create a single burnable material across many
locations. From a pure transport perspective, there is nothing wrong with
@ -160,7 +177,7 @@ the next transport step.
This can be countered by instructing the operator to treat repeated instances
of the same material as a unique material definition with::
op = openmc.deplete.Operator(geometry, settings, chain_file,
op = openmc.deplete.CoupledOperator(model, chain_file,
diff_burnable_mats=True)
For our example problem, this would deplete fuel on the outer region of the
@ -177,3 +194,164 @@ across all material instances.
This will increase the total memory usage and run time due to an increased
number of tallies and material definitions.
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 pre-calculated one-group microscopic cross
sections to obtain transmutation reaction rates. OpenMC provides the
:class:`~openmc.deplete.IndependentOperator` for this method of calculation.
While the one-group microscopic cross sections can be calculated using a
transport solver, :class:`~openmc.deplete.IndependentOperator` is not directly
coupled to any transport solver. The
:class:`~openmc.deplete.IndependentOperator` class requires a
:class:`openmc.Materials` object, a :class:`~openmc.deplete.MicroXS` object,
and a path to a depletion chain file::
# load in the microscopic cross sections
materials = openmc.Materials()
...
micro_xs = openmc.deplete.MicroXS.from_csv(micro_xs_path)
op = openmc.deplete.IndependentOperator(materials, micro_xs, chain_file)
.. 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.
An alternate constructor,
:meth:`~openmc.deplete.IndependentOperator.from_nuclides`, accepts a volume and
dictionary of nuclide concentrations in place of the :class:`openmc.Materials`
object::
nuclides = {'U234': 8.92e18,
'U235': 9.98e20,
'U238': 2.22e22,
'U236': 4.57e18,
'O16': 4.64e22,
'O17': 1.76e19}
volume = 0.5
op = openmc.deplete.IndependentOperator.from_nuclides(volume,
nuclides,
micro_xs,
chain_file,
nuc_units='atom/cm3')
A user can then define an integrator class as they would for a coupled
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 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.
Generating Microscopic Cross Sections
-------------------------------------
Users can generate the one-group microscopic cross sections needed by
:class:`~openmc.deplete.IndependentOperator` using the
:class:`~openmc.deplete.MicroXS` class::
import openmc
model = openmc.Model.from_xml()
micro_xs = openmc.deplete.MicroXS.from_model(model,
model.materials[0],
chain_file)
The :meth:`~openmc.deplete.MicroXS.from_model()` method will produce a
:class:`~openmc.deplete.MicroXS` object with microscopic cross section data in
units of barns, which is what :class:`~openmc.deplete.IndependentOperator`
expects the units to be. The :class:`~openmc.deplete.MicroXS` class also
includes functions to read in cross section data directly from a ``.csv`` file
or from data arrays::
micro_xs = MicroXS.from_csv(micro_xs_path)
nuclides = ['U234', 'U235', 'U238']
reactions = ['fission', '(n,gamma)']
data = np.array([[0.1, 0.2],
[0.3, 0.4],
[0.01, 0.5]])
micro_xs = MicroXS.from_array(nuclides, reactions, data)
.. 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
:class:`~openmc.deplete.IndependentOperator` object.
Caveats
-------
Reaction Rate Normalization
~~~~~~~~~~~~~~~~~~~~~~~~~~~
The :class:`~openmc.deplete.IndependentOperator` class supports two methods for
normalizing reaction rates:
.. important::
Make sure you set the correct parameter in the :class:`openmc.abc.Integrator`
class. Use the ``source_rates`` parameter when
``normalization_mode == source-rate``, and use ``power`` or ``power_density``
when ``normalization_mode == fission-q``.
1. ``source-rate`` normalization, which assumes the ``source_rate`` provided by
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 general equation for the flux is
.. math::
\phi = \frac{P}{V \cdot \sum_i (Q_i \cdot \sigma^f_i \cdot \n_i)}
where :math:`\sum_i` is the sum over all nuclides :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.
.. warning::
The accuracy of results when using ``fission-q`` is entirely dependent on
your depletion chain. Make sure it has sufficient data to resolve the
dynamics of your particular scenario.
Multiple Materials
~~~~~~~~~~~~~~~~~~
Running a depletion simulation with multiple materials using the
``source-rate`` normalization method treats each material as completely
separate with respect to reaction rates. This can be useful for running many
different cases of a particular scenario. However, running a depletion
simulation with multiple materials using the ``fission-q`` normalization method
treats each material as part of the same "reactor" due to how ``fission-q``
normalization accumulates energy values from each material to a single value.
This behavior may change in the future.
Time integration
~~~~~~~~~~~~~~~~
The one-group microscopic cross sections passed to
:class:`openmc.deplete.IndependentOperator` are fixed values for the entire
depletion simulation. This implicit assumption may produce inaccurate results
for certain scenarios.

View file

@ -24,7 +24,7 @@ using double_4dvec = vector<vector<vector<vector<double>>>>;
constexpr int HDF5_VERSION[] {3, 0};
// Version numbers for binary files
constexpr array<int, 2> VERSION_STATEPOINT {17, 0};
constexpr array<int, 2> VERSION_STATEPOINT {18, 0};
constexpr array<int, 2> VERSION_PARTICLE_RESTART {2, 0};
constexpr array<int, 2> VERSION_TRACK {3, 0};
constexpr array<int, 2> VERSION_SUMMARY {6, 0};

View file

@ -126,6 +126,26 @@ private:
debye_waller_; //!< Debye-Waller integral divided by atomic mass in [eV^-1]
};
//==============================================================================
//! Sum of multiple 1D functions
//==============================================================================
class Sum1D : public Function1D {
public:
// Constructors
explicit Sum1D(hid_t group);
//! Evaluate each function and sum results
//! \param[in] x independent variable
//! \return Function evaluated at x
double operator()(double E) const override;
const unique_ptr<Function1D>& functions(int i) const { return functions_[i]; }
private:
vector<unique_ptr<Function1D>> functions_; //!< individual functions
};
//! Read 1D function from HDF5 dataset
//! \param[in] group HDF5 group containing dataset
//! \param[in] name Name of dataset

View file

@ -61,6 +61,8 @@ void ensure_exists(hid_t obj_id, const char* name, bool attribute = false);
vector<std::string> group_names(hid_t group_id);
vector<hsize_t> object_shape(hid_t obj_id);
std::string object_name(hid_t obj_id);
hid_t open_object(hid_t group_id, const std::string& name);
void close_object(hid_t obj_id);
//==============================================================================
// Fortran compatibility functions

View file

@ -7,13 +7,14 @@
#include <unordered_map>
#include "hdf5.h"
#include "pugixml.hpp"
#include "xtensor/xtensor.hpp"
#include "pugixml.hpp"
#include "openmc/memory.h" // for unique_ptr
#include "openmc/particle.h"
#include "openmc/position.h"
#include "openmc/vector.h"
#include "openmc/xml_interface.h"
#ifdef DAGMC
#include "moab/AdaptiveKDTree.hpp"
@ -36,6 +37,12 @@
namespace openmc {
//==============================================================================
// Constants
//==============================================================================
enum class ElementType { UNSUPPORTED=-1, LINEAR_TET, LINEAR_HEX };
//==============================================================================
// Global variables
//==============================================================================
@ -53,7 +60,7 @@ extern vector<unique_ptr<Mesh>> meshes;
#ifdef LIBMESH
namespace settings {
// used when creating new libMesh::Mesh instances
// used when creating new libMesh::MeshBase instances
extern unique_ptr<libMesh::LibMeshInit> libmesh_init;
extern const libMesh::Parallel::Communicator* libmesh_comm;
} // namespace settings
@ -485,6 +492,23 @@ public:
//! \return The centroid of the bin
virtual Position centroid(int bin) const = 0;
//! Get the number of vertices in the mesh
//
//! \return Number of vertices
virtual int n_vertices() const = 0;
//! Retrieve a vertex of the mesh
//
//! \param[in] vertex ID
//! \return vertex coordinates
virtual Position vertex(int id) const = 0;
//! Retrieve connectivity of a mesh element
//
//! \param[in] element ID
//! \return element connectivity as IDs of the vertices
virtual std::vector<int> connectivity(int id) const = 0;
//! Get the volume of a mesh bin
//
//! \param[in] bin Bin to return the volume for
@ -557,6 +581,12 @@ public:
Position centroid(int bin) const override;
int n_vertices() const override;
Position vertex(int id) const override;
std::vector<int> connectivity(int id) const override;
double volume(int bin) const override;
private:
@ -621,6 +651,9 @@ private:
//! \return MOAB EntityHandle of tet
moab::EntityHandle get_ent_handle_from_bin(int bin) const;
//! Get a vertex index into the global range from a handle
int get_vert_idx_from_handle(moab::EntityHandle vert) const;
//! Get the bin for a given mesh cell index
//
//! \param[in] idx Index of the mesh cell.
@ -655,6 +688,7 @@ private:
// Data members
moab::Range ehs_; //!< Range of tetrahedra EntityHandle's in the mesh
moab::Range verts_; //!< Range of vertex EntityHandle's in the mesh
moab::EntityHandle tetset_; //!< EntitySet containing all tetrahedra
moab::EntityHandle kdtree_root_; //!< Root of the MOAB KDTree
std::shared_ptr<moab::Interface> mbi_; //!< MOAB instance
@ -671,7 +705,8 @@ class LibMesh : public UnstructuredMesh {
public:
// Constructors
LibMesh(pugi::xml_node node);
LibMesh(const std::string& filename, double length_multiplier = 1.0);
LibMesh(const std::string & filename, double length_multiplier = 1.0);
LibMesh(libMesh::MeshBase & input_mesh, double length_multiplier = 1.0);
static const std::string mesh_lib_type;
@ -701,10 +736,19 @@ public:
Position centroid(int bin) const override;
int n_vertices() const override;
Position vertex(int id) const override;
std::vector<int> connectivity(int id) const override;
double volume(int bin) const override;
libMesh::MeshBase* mesh_ptr() const { return m_; };
private:
void initialize() override;
void set_mesh_pointer_from_filename(const std::string& filename);
// Methods
@ -715,7 +759,8 @@ private:
int get_bin_from_element(const libMesh::Elem* elem) const;
// Data members
unique_ptr<libMesh::Mesh> m_; //!< pointer to the libMesh mesh instance
unique_ptr<libMesh::MeshBase> unique_m_ = nullptr; //!< pointer to the libMesh MeshBase instance, only used if mesh is created inside OpenMC
libMesh::MeshBase* m_; //!< pointer to libMesh MeshBase instance, always set during intialization
vector<unique_ptr<libMesh::PointLocatorBase>>
pl_; //!< per-thread point locators
unique_ptr<libMesh::EquationSystems>

View file

@ -150,6 +150,34 @@ private:
//!< each incident energy
};
//==============================================================================
//! Mixed coherent/incoherent elastic angle-energy distribution
//==============================================================================
class MixedElasticAE : public AngleEnergy {
public:
//! Construct from HDF5 file
//
//! \param[in] group HDF5 group
explicit MixedElasticAE(
hid_t group, const CoherentElasticXS& coh_xs, const Function1D& incoh_xs);
//! Sample distribution for an angle and energy
//! \param[in] E_in Incoming energy in [eV]
//! \param[out] E_out Outgoing energy in [eV]
//! \param[out] mu Outgoing cosine with respect to current direction
//! \param[inout] seed Pseudorandom number seed pointer
void sample(
double E_in, double& E_out, double& mu, uint64_t* seed) const override;
private:
CoherentElasticAE coherent_dist_; //!< Coherent distribution
unique_ptr<AngleEnergy> incoherent_dist_; //!< Incoherent distribution
const CoherentElasticXS& coherent_xs_; //!< Ref. to coherent XS
const Function1D& incoherent_xs_; //!< Polymorphic ref. to incoherent XS
};
} // namespace openmc
#endif // OPENMC_SECONDARY_THERMAL_H

View file

@ -44,6 +44,8 @@ class AngleEnergy(EqualityMixin, ABC):
return openmc.data.IncoherentInelasticAEDiscrete.from_hdf5(group)
elif dist_type == 'incoherent_inelastic':
return openmc.data.IncoherentInelasticAE.from_hdf5(group)
elif dist_type == 'mixed_elastic':
return openmc.data.MixedElasticAE.from_hdf5(group)
@staticmethod
def from_ace(ace, location_dist, location_start, rx=None):

View file

@ -9,7 +9,9 @@ from uncertainties import ufloat, UFloat
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
from openmc.stats import Discrete, Tabular, combine_distributions
from .data import ATOMIC_SYMBOL, ATOMIC_NUMBER
from .function import INTERPOLATION_SCHEME
from .endf import Evaluation, get_head_record, get_list_record, get_tab1_record
@ -314,6 +316,12 @@ class Decay(EqualityMixin):
'excited_state', 'mass', 'stable', 'spin', and 'parity'.
spectra : dict
Resulting radiation spectra for each radiation type.
sources : dict
Radioactive decay source distributions represented as a dictionary
mapping particle types (e.g., 'photon') to instances of
:class:`openmc.stats.Univariate`.
.. versionadded:: 0.13.1
"""
def __init__(self, ev_or_filename):
@ -329,6 +337,7 @@ class Decay(EqualityMixin):
self.modes = []
self.spectra = {}
self.average_energies = {}
self._sources = None
# Get head record
items = get_head_record(file_obj)
@ -495,3 +504,69 @@ class Decay(EqualityMixin):
"""
return cls(ev_or_filename)
@property
def sources(self):
"""Radioactive decay source distributions"""
# If property has been computed already, return it
# TODO: Replace with functools.cached_property when support is Python 3.9+
if self._sources is not None:
return self._sources
sources = {}
name = self.nuclide['name']
decay_constant = self.decay_constant.n
for particle, spectra in self.spectra.items():
# Set particle type based on 'particle' above
particle_type = {
'gamma': 'photon',
'beta-': 'electron',
'ec/beta+': 'positron',
'alpha': 'alpha',
'n': 'neutron',
'sf': 'fragment',
'p': 'proton',
'e-': 'electron',
'xray': 'photon',
'anti-neutrino': 'anti-neutrino',
'neutrino': 'neutrino',
}[particle]
if particle_type not in sources:
sources[particle_type] = []
# Create distribution for discrete
if spectra['continuous_flag'] in ('discrete', 'both'):
energies = []
intensities = []
for discrete_data in spectra['discrete']:
energies.append(discrete_data['energy'].n)
intensities.append(discrete_data['intensity'].n)
energies = np.array(energies)
intensity = spectra['discrete_normalization'].n
rates = decay_constant * intensity * np.array(intensities)
dist_discrete = Discrete(energies, rates)
sources[particle_type].append(dist_discrete)
# Create distribution for continuous
if spectra['continuous_flag'] in ('continuous', 'both'):
f = spectra['continuous']['probability']
if len(f.interpolation) > 1:
raise NotImplementedError("Multiple interpolation regions: {name}, {particle}")
interpolation = INTERPOLATION_SCHEME[f.interpolation[0]]
if interpolation not in ('histogram', 'linear-linear'):
raise NotImplementedError("Continuous spectra with {interpolation} interpolation ({name}, {particle}) not supported")
intensity = spectra['continuous_normalization'].n
rates = decay_constant * intensity * f.y
dist_continuous = Tabular(f.x, rates, interpolation)
sources[particle_type].append(dist_continuous)
# Combine discrete distributions
merged_sources = {}
for particle_type, dist_list in sources.items():
merged_sources[particle_type] = combine_distributions(
dist_list, [1.0]*len(dist_list))
self._sources = merged_sources
return self._sources

View file

@ -544,7 +544,7 @@ class Combination(EqualityMixin):
self._operations = operations
class Sum(EqualityMixin):
class Sum(Function1D):
"""Sum of multiple functions.
This class allows you to create a callable object which represents the sum
@ -578,6 +578,49 @@ class Sum(EqualityMixin):
cv.check_type('functions', functions, Iterable, Callable)
self._functions = functions
def to_hdf5(self, group, name='xy'):
"""Write sum of functions to an HDF5 group
.. versionadded:: 0.13.1
Parameters
----------
group : h5py.Group
HDF5 group to write to
name : str
Name of the dataset to create
"""
sum_group = group.create_group(name)
sum_group.attrs['type'] = np.string_(type(self).__name__)
sum_group.attrs['n'] = len(self.functions)
for i, f in enumerate(self.functions):
f.to_hdf5(sum_group, f'func_{i+1}')
@classmethod
def from_hdf5(cls, group):
"""Generate sum of functions from an HDF5 group
.. versionadded:: 0.13.1
Parameters
----------
group : h5py.Group
Group to read from
Returns
-------
openmc.data.Sum
Functions read from the group
"""
n = group.attrs['n']
functions = [
Function1D.from_hdf5(group[f'func_{i+1}'])
for i in range(n)
]
return cls(functions)
class Regions1D(EqualityMixin):
r"""Piecewise composition of multiple functions.

View file

@ -5,6 +5,7 @@ from warnings import warn
import numpy as np
import openmc.checkvalue as cv
from openmc.mixin import EqualityMixin
from openmc.stats import Tabular, Univariate, Discrete, Mixture
from .function import Tabulated1D, INTERPOLATION_SCHEME
from .angle_energy import AngleEnergy
@ -12,6 +13,240 @@ from .data import EV_PER_MEV
from .endf import get_list_record, get_tab2_record
class _AtomicRepresentation(EqualityMixin):
"""Atomic representation of an isotope or a particle.
Parameters
----------
z : int
Number of protons (atomic number)
a : int
Number of nucleons (mass number)
Raises
------
ValueError
When the number of protons (z) declared is higher than the number
of nucleons (a)
Attributes
----------
z : int
Number of protons (atomic number)
a : int
Number of nucleons (mass number)
n : int
Number of neutrons
za : int
ZA identifier, 1000*Z + A, where Z is the atomic number and A the mass
number
"""
def __init__(self, z, a):
# Sanity checks on values
cv.check_type('z', z, Integral)
cv.check_greater_than('z', z, 0, equality=True)
cv.check_type('a', a, Integral)
cv.check_greater_than('a', a, 0, equality=True)
if z > a:
raise ValueError(f"Number of protons ({z}) must be less than or "
f"equal to number of nucleons ({a}).")
self._z = z
self._a = a
def __add__(self, other):
"""Add two _AtomicRepresentations"""
z = self.z + other.z
a = self.a + other.a
return _AtomicRepresentation(z=z, a=a)
def __sub__(self, other):
"""Substract two _AtomicRepresentations"""
z = self.z - other.z
a = self.a - other.a
return _AtomicRepresentation(z=z, a=a)
@property
def a(self):
return self._a
@property
def z(self):
return self._z
@property
def n(self):
return self.a - self.z
@property
def za(self):
return self.z * 1000 + self.a
@classmethod
def from_za(cls, za):
"""Instantiate an _AtomicRepresentation from a ZA identifier.
Parameters
----------
za : int
ZA identifier, 1000*Z + A, where Z is the atomic number and A the
mass number
Returns
-------
_AtomicRepresentation
Atomic representation of the isotope/particle
"""
z, a = divmod(za, 1000)
return cls(z, a)
def _separation_energy(compound, nucleus, particle):
"""Calculates the separation energy as defined in ENDF-6 manual
BNL-203218-2018-INRE, Revision 215, File 6 description for LAW=1
and LANG=2. This function can be used for the incident or emitted
particle of the following reaction: A + a -> C -> B + b
Parameters
----------
compound : _AtomicRepresentation
Atomic representation of the compound (C)
nucleus : _AtomicRepresentation
Atomic representation of the nucleus (A or B)
particle : _AtomicRepresentation
Atomic representation of the particle (a or b)
Returns
-------
separation_energy : float
Separation energy in MeV
"""
# Determine A, Z, and N for compound and nucleus
A_c = compound.a
Z_c = compound.z
N_c = compound.n
A_a = nucleus.a
Z_a = nucleus.z
N_a = nucleus.n
# Determine breakup energy of incident particle (ENDF-6 Formats Manual,
# Appendix H, Table 3) in MeV
za_to_breaking_energy = {
1: 0.0,
1001: 0.0,
1002: 2.224566,
1003: 8.481798,
2003: 7.718043,
2004: 28.29566
}
I_a = za_to_breaking_energy[particle.za]
# Eq. 4 in in doi:10.1103/PhysRevC.37.2350 or ENDF-6 Formats Manual section
# 6.2.3.2
return (
15.68 * (A_c - A_a) -
28.07 * ((N_c - Z_c)**2 / A_c - (N_a - Z_a)**2 / A_a) -
18.56 * (A_c**(2./3.) - A_a**(2./3.)) +
33.22 * ((N_c - Z_c)**2 / A_c**(4./3.) - (N_a - Z_a)**2 / A_a**(4./3.)) -
0.717 * (Z_c**2 / A_c**(1./3.) - Z_a**2 / A_a**(1./3.)) +
1.211 * (Z_c**2 / A_c - Z_a**2 / A_a) -
I_a
)
def kalbach_slope(energy_projectile, energy_emitted, za_projectile,
za_emitted, za_target):
"""Returns Kalbach-Mann slope from calculations.
The associated reaction is defined as:
A + a -> C -> B + b
Where:
- A is the targeted nucleus,
- a is the projectile,
- C is the compound,
- B is the residual nucleus,
- b is the emitted particle.
The Kalbach-Mann slope calculation is done as defined in ENDF-6 manual
BNL-203218-2018-INRE, Revision 215, File 6 description for LAW=1 and
LANG=2. One exception to this, is that the entrance and emission channel
energies are not calculated with the AWR number, but approximated with
the number of mass instead.
Parameters
----------
energy_projectile : float
Energy of the projectile in the laboratory system in eV
energy_emitted : float
Energy of the emitted particle in the center of mass system in eV
za_projectile : int
ZA identifier of the projectile
za_emitted : int
ZA identifier of the emitted particle
za_target : int
ZA identifier of the targeted nucleus
Raises
------
NotImplementedError
When the projectile is not a neutron
Returns
-------
slope : float
Kalbach-Mann slope given with the same format as ACE file.
"""
# TODO: develop for photons as projectile
# TODO: test for other particles than neutron
if za_projectile != 1:
raise NotImplementedError(
"Developed and tested for neutron projectile only."
)
# Special handling of elemental carbon
if za_emitted == 6000:
za_emitted = 6012
if za_target == 6000:
za_target = 6012
projectile = _AtomicRepresentation.from_za(za_projectile)
emitted = _AtomicRepresentation.from_za(za_emitted)
target = _AtomicRepresentation.from_za(za_target)
compound = projectile + target
residual = compound - emitted
# Calculate entrance and emission channel energy in MeV, defined in section
# 6.2.3.2 in the ENDF-6 Formats Manual
epsilon_a = energy_projectile * target.a / (target.a + projectile.a) / EV_PER_MEV
epsilon_b = energy_emitted * (residual.a + emitted.a) \
/ (residual.a * EV_PER_MEV)
# Calculate separation energies using Eq. 4 in doi:10.1103/PhysRevC.37.2350
# or ENDF-6 Formats Manual section 6.2.3.2
s_a = _separation_energy(compound, target, projectile)
s_b = _separation_energy(compound, residual, emitted)
# See Eq. 10 in doi:10.1103/PhysRevC.37.2350 or section 6.2.3.2 in the
# ENDF-6 Formats Manual
za_to_M = {1: 1.0, 1001: 1.0, 1002: 1.0, 2004: 0.0}
za_to_m = {1: 0.5, 1001: 1.0, 1002: 1.0, 1003: 1.0, 2003: 1.0, 2004: 2.0}
M = za_to_M[projectile.za]
m = za_to_m[emitted.za]
e_a = epsilon_a + s_a
e_b = epsilon_b + s_b
r_1 = min(e_a, 130.)
r_3 = min(e_a, 41.)
x_1 = r_1 * e_b / e_a
x_3 = r_3 * e_b / e_a
return 0.04 * x_1 + 1.8e-6 * x_1**3 + 6.7e-7 * M * m * x_3**4
class KalbachMann(AngleEnergy):
"""Kalbach-Mann distribution
@ -319,7 +554,7 @@ class KalbachMann(AngleEnergy):
n_energy_out = int(ace.xss[idx + 1])
data = ace.xss[idx + 2:idx + 2 + 5*n_energy_out].copy()
data.shape = (5, n_energy_out)
data[0,:] *= EV_PER_MEV
data[0, :] *= EV_PER_MEV
# Create continuous distribution
eout_continuous = Tabular(data[0][n_discrete_lines:],
@ -352,13 +587,28 @@ class KalbachMann(AngleEnergy):
return cls(breakpoints, interpolation, energy, energy_out, km_r, km_a)
@classmethod
def from_endf(cls, file_obj):
"""Generate Kalbach-Mann distribution from an ENDF evaluation
def from_endf(cls, file_obj, za_emitted, za_target, projectile_mass):
"""Generate Kalbach-Mann distribution from an ENDF evaluation.
If the projectile is a neutron, the slope is calculated when it is
not given explicitly.
Parameters
----------
file_obj : file-like object
ENDF file positioned at the start of the Kalbach-Mann distribution
za_emitted : int
ZA identifier of the emitted particle
za_target : int
ZA identifier of the target
projectile_mass : float
Mass of the projectile
Warns
-----
UserWarning
If the mass of the projectile is not equal to 1 (other than
a neutron), the slope is not calculated and set to 0 if missing.
Returns
-------
@ -374,6 +624,7 @@ class KalbachMann(AngleEnergy):
energy_out = []
precompound = []
slope = []
calculated_slope = []
for i in range(ne):
items, values = get_list_record(file_obj)
energy[i] = items[1]
@ -385,19 +636,46 @@ class KalbachMann(AngleEnergy):
values.shape = (n_energy_out, n_angle + 2)
# Outgoing energy distribution at the i-th incoming energy
eout_i = values[:,0]
eout_p_i = values[:,1]
eout_i = values[:, 0]
eout_p_i = values[:, 1]
energy_out_i = Tabular(eout_i, eout_p_i, INTERPOLATION_SCHEME[lep])
energy_out.append(energy_out_i)
# Precompound and slope factors for Kalbach-Mann
r_i = values[:,2]
# Precompound factors for Kalbach-Mann
r_i = values[:, 2]
# Slope factors for Kalbach-Mann
if n_angle == 2:
a_i = values[:,3]
a_i = values[:, 3]
calculated_slope.append(False)
else:
a_i = np.zeros_like(r_i)
# Check if the projectile is not a neutron
if not np.isclose(projectile_mass, 1.0, atol=1.0e-12, rtol=0.):
warn(
"Kalbach-Mann slope calculation is only available with "
"neutrons as projectile. Slope coefficients are set to 0."
)
a_i = np.zeros_like(r_i)
calculated_slope.append(False)
else:
# TODO: retrieve ZA of the projectile
za_projectile = 1
a_i = [kalbach_slope(energy_projectile=energy[i],
energy_emitted=e,
za_projectile=za_projectile,
za_emitted=za_emitted,
za_target=za_target)
for e in eout_i]
calculated_slope.append(True)
precompound.append(Tabulated1D(eout_i, r_i))
slope.append(Tabulated1D(eout_i, a_i))
return cls(tab2.breakpoints, tab2.interpolation, energy,
energy_out, precompound, slope)
km_distribution = cls(tab2.breakpoints, tab2.interpolation, energy,
energy_out, precompound, slope)
# List of bool to indicate slope calculation by OpenMC
km_distribution._calculated_slope = calculated_slope
return km_distribution

View file

@ -127,7 +127,7 @@ acer / %%%%%%%%%%%%%%%%%%%%%%%% Write out in ACE format %%%%%%%%%%%%%%%%%%%%%%%%
1 0 1 .{ext} /
'{library}: {zsymam} at {temperature}'/
{mat} {temperature}
1 1/
1 1 {ismooth}/
/
"""
@ -248,7 +248,8 @@ def make_pendf(filename, pendf='pendf', error=0.001, stdout=False):
def make_ace(filename, temperatures=None, acer=True, xsdir=None,
output_dir=None, pendf=False, error=0.001, broadr=True,
heatr=True, gaspr=True, purr=True, evaluation=None, **kwargs):
heatr=True, gaspr=True, purr=True, evaluation=None,
smoothing=True, **kwargs):
"""Generate incident neutron ACE file from an ENDF file
File names can be passed to
@ -298,6 +299,8 @@ def make_ace(filename, temperatures=None, acer=True, xsdir=None,
evaluation : openmc.data.endf.Evaluation, optional
If the ENDF file contains multiple material evaluations, this argument
indicates which evaluation should be used.
smoothing : bool, optional
If the smoothing option (ACER card 6) is on (True) or off (False).
**kwargs
Keyword arguments passed to :func:`openmc.data.njoy.run`
@ -380,6 +383,7 @@ def make_ace(filename, temperatures=None, acer=True, xsdir=None,
# acer
if acer:
ismooth = int(smoothing)
nacer_in = nlast
for i, temperature in enumerate(temperatures):
# Extend input with an ACER run for each temperature

View file

@ -80,6 +80,14 @@ def _get_products(ev, mt):
mt : int
The MT value of the reaction to get products for
Raises
------
IOError
When the Kalbach-Mann systematics is used, but the product
is not defined in the 'center-of-mass' system. The breakup logic
is not implemented which can lead to this error being raised while
the definition of the product is correct.
Returns
-------
products : list of openmc.data.Product
@ -141,7 +149,26 @@ def _get_products(ev, mt):
if lang == 1:
p.distribution = [CorrelatedAngleEnergy.from_endf(file_obj)]
elif lang == 2:
p.distribution = [KalbachMann.from_endf(file_obj)]
# Products need to be described in the center-of-mass system
product_center_of_mass = False
if reference_frame == 'center-of-mass':
product_center_of_mass = True
elif reference_frame == 'light-heavy':
product_center_of_mass = (awr <= 4.0)
# TODO: 'breakup' logic not implemented
if product_center_of_mass is False:
raise IOError(
"Kalbach-Mann representation must be defined in the "
"'center-of-mass' system"
)
zat = ev.target["atomic_number"] * 1000 + ev.target["mass_number"]
projectile_mass = ev.projectile["mass"]
p.distribution = [KalbachMann.from_endf(file_obj,
za,
zat,
projectile_mass)]
elif law == 2:
# Discrete two-body scattering

View file

@ -299,8 +299,8 @@ def reconstruct_slbw(slbw, double E):
# Determine shift and penetration at modified energy
if slbw._competitive[i]:
Ex = E + slbw.q_value[l]*(A + 1)/A
rhoc = slbw.channel_radius[l](Ex)
rhochat = slbw.scattering_radius[l](Ex)
rhoc = k*slbw.channel_radius[l](Ex)
rhochat = k*slbw.scattering_radius[l](Ex)
P_c, S_c = penetration_shift(l, rhoc)
if Ex < 0:
P_c = 0

View file

@ -19,12 +19,12 @@ from . import HDF5_VERSION, HDF5_VERSION_MAJOR, endf
from .data import K_BOLTZMANN, ATOMIC_SYMBOL, EV_PER_MEV, isotopes
from .ace import Table, get_table, Library
from .angle_energy import AngleEnergy
from .function import Tabulated1D, Function1D
from .function import Tabulated1D, Function1D, Sum
from .njoy import make_ace_thermal
from .thermal_angle_energy import (CoherentElasticAE, IncoherentElasticAE,
IncoherentElasticAEDiscrete,
IncoherentInelasticAEDiscrete,
IncoherentInelasticAE)
IncoherentInelasticAE, MixedElasticAE)
_THERMAL_NAMES = {
@ -694,29 +694,53 @@ class ThermalScattering(EqualityMixin):
# Incoherent/coherent elastic scattering cross section
idx = ace.jxs[4]
n_mu = ace.nxs[6] + 1
if idx != 0:
n_energy = int(ace.xss[idx])
energy = ace.xss[idx + 1: idx + 1 + n_energy]*EV_PER_MEV
P = ace.xss[idx + 1 + n_energy: idx + 1 + 2 * n_energy]
if ace.nxs[5] == 4:
if ace.nxs[5] in (4, 5):
# Coherent elastic
xs = CoherentElastic(energy, P*EV_PER_MEV)
distribution = CoherentElasticAE(xs)
n_energy = int(ace.xss[idx])
energy = ace.xss[idx + 1: idx + 1 + n_energy]*EV_PER_MEV
P = ace.xss[idx + 1 + n_energy: idx + 1 + 2 * n_energy]
coherent_xs = CoherentElastic(energy, P*EV_PER_MEV)
coherent_dist = CoherentElasticAE(coherent_xs)
# Coherent elastic shouldn't have angular distributions listed
n_mu = ace.nxs[6] + 1
assert n_mu == 0
else:
# Incoherent elastic
xs = Tabulated1D(energy, P)
if ace.nxs[5] in (3, 5):
# Incoherent elastic scattering -- first determine if both
# incoherent and coherent are present (mixed)
mixed = (ace.nxs[5] == 5)
# Get cross section values
idx = ace.jxs[7] if mixed else ace.jxs[4]
n_energy = int(ace.xss[idx])
energy = ace.xss[idx + 1: idx + 1 + n_energy]*EV_PER_MEV
values = ace.xss[idx + 1 + n_energy: idx + 1 + 2 * n_energy]
incoherent_xs = Tabulated1D(energy, values)
# Angular distribution
n_mu = (ace.nxs[8] if mixed else ace.nxs[6]) + 1
assert n_mu > 0
idx = ace.jxs[6]
idx = ace.jxs[9] if mixed else ace.jxs[6]
mu_out = ace.xss[idx:idx + n_energy * n_mu]
mu_out.shape = (n_energy, n_mu)
distribution = IncoherentElasticAEDiscrete(mu_out)
incoherent_dist = IncoherentElasticAEDiscrete(mu_out)
if ace.nxs[5] == 3:
xs = incoherent_xs
dist = incoherent_dist
elif ace.nxs[5] == 4:
xs = coherent_xs
dist = coherent_dist
else:
# Create mixed cross section -- note that coherent must come
# first due to assumption on C++ side
xs = Sum([coherent_xs, incoherent_xs])
# Create mixed distribution
distribution = MixedElasticAE(coherent_dist, incoherent_dist)
table.elastic = ThermalScatteringReaction({T: xs}, {T: distribution})
@ -802,7 +826,7 @@ class ThermalScattering(EqualityMixin):
# Replace ACE data with ENDF data
rx, rx_endf = data.elastic, data_endf.elastic
for t in temperatures:
if isinstance(rx_endf.xs[t], IncoherentElastic):
if isinstance(rx_endf.xs[t], (IncoherentElastic, Sum)):
rx.xs[t] = rx_endf.xs[t]
rx.distribution[t] = rx_endf.distribution[t]
@ -832,20 +856,14 @@ class ThermalScattering(EqualityMixin):
# Read coherent/incoherent elastic data
elastic = None
if (7, 2) in ev.section:
xs = {}
distribution = {}
file_obj = StringIO(ev.section[7, 2])
lhtr = endf.get_head_record(file_obj)[2]
if lhtr == 1:
# coherent elastic
# Define helper functions to avoid duplication
def get_coherent_elastic(file_obj):
# Get structure factor at first temperature
params, S = endf.get_tab1_record(file_obj)
strT = _temperature_str(params[0])
n_temps = params[2]
bragg_edges = S.x
xs[strT] = CoherentElastic(bragg_edges, S.y)
xs = {strT: CoherentElastic(bragg_edges, S.y)}
distribution = {strT: CoherentElasticAE(xs[strT])}
# Get structure factor for subsequent temperatures
@ -854,15 +872,34 @@ class ThermalScattering(EqualityMixin):
strT = _temperature_str(params[0])
xs[strT] = CoherentElastic(bragg_edges, S)
distribution[strT] = CoherentElasticAE(xs[strT])
return xs, distribution
elif lhtr == 2:
# incoherent elastic
def get_incoherent_elastic(file_obj):
params, W = endf.get_tab1_record(file_obj)
bound_xs = params[0]
xs = {}
distribution = {}
for T, debye_waller in zip(W.x, W.y):
strT = _temperature_str(T)
xs[strT] = IncoherentElastic(bound_xs, debye_waller)
distribution[strT] = IncoherentElasticAE(debye_waller)
return xs, distribution
file_obj = StringIO(ev.section[7, 2])
lhtr = endf.get_head_record(file_obj)[2]
if lhtr == 1:
# coherent elastic
xs, distribution = get_coherent_elastic(file_obj)
elif lhtr == 2:
# incoherent elastic
xs, distribution = get_incoherent_elastic(file_obj)
elif lhtr == 3:
# mixed coherent / incoherent elastic
xs_c, dist_c = get_coherent_elastic(file_obj)
xs_i, dist_i = get_incoherent_elastic(file_obj)
assert sorted(xs_c) == sorted(xs_i)
xs = {T: Sum([xs_c[T], xs_i[T]]) for T in xs_c}
distribution = {T: MixedElasticAE(dist_c[T], dist_i[T]) for T in dist_c}
elastic = ThermalScatteringReaction(xs, distribution)

View file

@ -2,6 +2,7 @@ import numpy as np
from .angle_energy import AngleEnergy
from .correlated import CorrelatedAngleEnergy
import openmc.data
class CoherentElasticAE(AngleEnergy):
@ -43,7 +44,27 @@ class CoherentElasticAE(AngleEnergy):
"""
group.attrs['type'] = np.string_('coherent_elastic')
group['coherent_xs'] = group.parent['xs']
self.coherent_xs.to_hdf5(group, 'coherent_xs')
@classmethod
def from_hdf5(cls, group):
"""Generate coherent elastic distribution from HDF5 data
.. versionadded:: 0.13.1
Parameters
----------
group : h5py.Group
HDF5 group to read from
Returns
-------
openmc.data.CoherentElasticAE
Coherent elastic distribution
"""
coherent_xs = openmc.data.CoherentElastic.from_hdf5(group['coherent_xs'])
return cls(coherent_xs)
class IncoherentElasticAE(AngleEnergy):
@ -101,7 +122,7 @@ class IncoherentElasticAE(AngleEnergy):
Incoherent elastic distribution
"""
return cls(group['debye_waller'])
return cls(group['debye_waller'][()])
class IncoherentElasticAEDiscrete(AngleEnergy):
@ -210,3 +231,62 @@ class IncoherentInelasticAEDiscrete(AngleEnergy):
class IncoherentInelasticAE(CorrelatedAngleEnergy):
_name = 'incoherent_inelastic'
class MixedElasticAE(AngleEnergy):
"""Secondary distribution for mixed coherent/incoherent thermal elastic
.. versionadded:: 0.13.1
Parameters
----------
coherent : AngleEnergy
Secondary distribution for coherent elastic scattering
incoherent : AngleEnergy
Secondary distribution for incoherent elastic scattering
Attributes
----------
coherent : AngleEnergy
Secondary distribution for coherent elastic scattering
incoherent : AngleEnergy
Secondary distribution for incoherent elastic scattering
"""
def __init__(self, coherent, incoherent):
self.coherent = coherent
self.incoherent = incoherent
def to_hdf5(self, group):
"""Write mixed elastic distribution to an HDF5 group
Parameters
----------
group : h5py.Group
HDF5 group to write to
"""
group.attrs['type'] = np.string_('mixed_elastic')
coherent_group = group.create_group('coherent')
self.coherent.to_hdf5(coherent_group)
incoherent_group = group.create_group('incoherent')
self.incoherent.to_hdf5(incoherent_group)
@classmethod
def from_hdf5(cls, group):
"""Generate mixed thermal elastic distribution from HDF5 data
Parameters
----------
group : h5py.Group
HDF5 group to read from
Returns
-------
openmc.data.MixedElasticAE
Mixed thermal elastic distribution
"""
coherent = AngleEnergy.from_hdf5(group['coherent'])
incoherent = AngleEnergy.from_hdf5(group['incoherent'])
return cls(coherent, incoherent)

View file

@ -7,7 +7,10 @@ A depletion front-end tool.
from .nuclide import *
from .chain import *
from .operator import *
from .openmc_operator import *
from .coupled_operator import *
from .independent_operator import *
from .microxs import *
from .reaction_rates import *
from .atom_number import *
from .stepresult import *

View file

@ -1,7 +1,6 @@
"""function module.
"""abc module.
This module contains the Operator class, which is then passed to an integrator
to run a full depletion simulation.
This module contains Abstract Base Classes for implementing operator, integrator, depletion system solver, and operator helper classes
"""
from abc import ABC, abstractmethod
@ -29,8 +28,8 @@ from .pool import deplete
__all__ = [
"OperatorResult", "TransportOperator", "ReactionRateHelper",
"NormalizationHelper", "FissionYieldHelper",
"OperatorResult", "TransportOperator",
"ReactionRateHelper", "NormalizationHelper", "FissionYieldHelper",
"Integrator", "SIIntegrator", "DepSystemSolver", "add_params"]
@ -83,7 +82,8 @@ class TransportOperator(ABC):
operator that takes a vector of material compositions and returns an
eigenvalue and reaction rates. This abstract class sets the requirements
for such a transport operator. Users should instantiate
:class:`openmc.deplete.Operator` rather than this class.
:class:`openmc.deplete.CoupledOperator` or
:class:`openmc.deplete.IndependentOperator` rather than this class.
Parameters
----------
@ -221,9 +221,11 @@ class ReactionRateHelper(ABC):
Parameters
----------
n_nucs : int
Number of burnable nuclides tracked by :class:`openmc.deplete.Operator`
Number of burnable nuclides tracked by
:class:`openmc.deplete.abc.TransportOperator`
n_react : int
Number of reactions tracked by :class:`openmc.deplete.Operator`
Number of reactions tracked by
:class:`openmc.deplete.abc.TransportOperator`
Attributes
----------
@ -292,9 +294,9 @@ class NormalizationHelper(ABC):
"""Abstract class for obtaining normalization factor on tallies
This helper class determines how reaction rates calculated by an instance of
:class:`openmc.deplete.Operator` should be normalized for the purpose of
constructing a burnup matrix. Based on the method chosen, the power or
source rate provided by the user, and reaction rates from a
:class:`openmc.deplete.abc.TransportOperator` should be normalized for the
purpose of constructing a burnup matrix. Based on the method chosen, the
power or source rate provided by the user, and reaction rates from a
:class:`ReactionRateHelper`, this class will scale reaction rates to the
correct values.
@ -302,7 +304,7 @@ class NormalizationHelper(ABC):
----------
nuclides : list of str
All nuclides with desired reaction rates. Ordered to be
consistent with :class:`openmc.deplete.Operator`
consistent with :class:`openmc.deplete.abc.TransportOperator`
"""
@ -316,9 +318,9 @@ class NormalizationHelper(ABC):
def prepare(self, chain_nucs, rate_index):
"""Perform work needed to obtain energy produced
This method is called prior to the transport simulations
in :meth:`openmc.deplete.Operator.initial_condition`. Only used for
energy-based normalization.
This method is called prior to calculating the reaction rates
in :meth:`openmc.deplete.abc.TransportOperator.initial_condition`. Only
used for energy-based normalization.
Parameters
----------
@ -429,7 +431,7 @@ class FissionYieldHelper(ABC):
def unpack():
"""Unpack tally data prior to compute fission yields.
Called after a :meth:`openmc.deplete.Operator.__call__`
Called after a :meth:`openmc.deplete.abc.TransportOperator.__call__`
routine during the normalization of reaction rates.
Not necessary for all subclasses to implement, unless tallies
@ -450,7 +452,7 @@ class FissionYieldHelper(ABC):
mat_indexes : iterable of int
Indices of tallied materials that will have their fission
yields computed by this helper. Necessary as the
:class:`openmc.deplete.Operator` that uses this helper
:class:`openmc.deplete.CoupledOperator` that uses this helper
may only burn a subset of all materials when running
in parallel mode.
"""
@ -462,14 +464,15 @@ class FissionYieldHelper(ABC):
----------
nuclides : iterable of str
Nuclides with non-zero densities from the
:class:`openmc.deplete.Operator`
:class:`openmc.deplete.abc.TransportOperator`
Returns
-------
nuclides : list of str
Union of nuclides that the :class:`openmc.deplete.Operator`
says have non-zero densities at this stage and those that
have yield data. Sorted by nuclide name
Union of nuclides that the
:class:`openmc.deplete.abc.TransportOperator` says have non-zero
densities at this stage and those that have yield data. Sorted by
nuclide name
"""
return sorted(self._chain_set & set(nuclides))
@ -483,7 +486,7 @@ class FissionYieldHelper(ABC):
Parameters
----------
operator : openmc.deplete.TransportOperator
operator : openmc.deplete.abc.TransportOperator
Operator with a depletion chain
kwargs: optional
Additional keyword arguments to be used in constuction
@ -504,7 +507,7 @@ class Integrator(ABC):
_params = r"""
Parameters
----------
operator : openmc.deplete.TransportOperator
operator : openmc.deplete.abc.TransportOperator
Operator to perform transport simulations
timesteps : iterable of float or iterable of tuple
Array of timesteps. Note that values are not cumulative. The units are
@ -522,9 +525,10 @@ class Integrator(ABC):
power_density : float or iterable of float, optional
Power density of the reactor in [W/gHM]. It is multiplied by
initial heavy metal inventory to get total power if ``power``
is not speficied.
is not specified.
source_rates : float or iterable of float, optional
Source rate in [neutron/sec] for each interval in :attr:`timesteps`
Source rate in [neutron/sec] or neutron flux in [neutron/s-cm^2] for
each interval in :attr:`timesteps`
.. versionadded:: 0.12.1
timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'}
@ -546,7 +550,7 @@ class Integrator(ABC):
Attributes
----------
operator : openmc.deplete.TransportOperator
operator : openmc.deplete.abc.TransportOperator
Operator to perform transport simulations
chain : openmc.deplete.Chain
Depletion chain
@ -841,8 +845,8 @@ class SIIntegrator(Integrator):
_params = r"""
Parameters
----------
operator : openmc.deplete.TransportOperator
The operator object to simulate on.
operator : openmc.deplete.abc.TransportOperator
Operator to perform transport simulations
timesteps : iterable of float or iterable of tuple
Array of timesteps. Note that values are not cumulative. The units are
specified by the `timestep_units` argument when `timesteps` is an
@ -859,9 +863,10 @@ class SIIntegrator(Integrator):
power_density : float or iterable of float, optional
Power density of the reactor in [W/gHM]. It is multiplied by
initial heavy metal inventory to get total power if ``power``
is not speficied.
is not specified.
source_rates : float or iterable of float, optional
Source rate in [neutron/sec] for each interval in :attr:`timesteps`
Source rate in [neutron/sec] or neutron flux in [neutron/s-cm^2] for
each interval in :attr:`timesteps`
.. versionadded:: 0.12.1
timestep_units : {'s', 'min', 'h', 'd', 'MWd/kg'}
@ -886,7 +891,7 @@ class SIIntegrator(Integrator):
Attributes
----------
operator : openmc.deplete.TransportOperator
operator : openmc.deplete.abc.TransportOperator
Operator to perform transport simulations
chain : openmc.deplete.Chain
Depletion chain
@ -1006,7 +1011,7 @@ class DepSystemSolver(ABC):
Parameters
----------
A : scipy.sparse.csr_matrix
Sparse transmutation matrix ``A[j, i]`` desribing rates at
Sparse transmutation matrix ``A[j, i]`` describing rates at
which isotope ``i`` transmutes to isotope ``j``
n0 : numpy.ndarray
Initial compositions, typically given in number of atoms in some

View file

@ -126,6 +126,23 @@ class AtomNumber:
return [nuc for nuc, ind in self.index_nuc.items()
if ind < self.n_nuc_burn]
def get_mat_volume(self, mat):
"""Return material volume
Parameters
----------
mat : str, int, openmc.Material, or slice
Material index.
Returns
-------
float
Material volume in [cm^3]
"""
mat = self._get_mat_index(mat)
return self.volume[mat]
def get_atom_density(self, mat, nuc):
"""Return atom density of given material and nuclide

View file

@ -264,7 +264,8 @@ class Chain:
requires a list of ENDF incident neutron, decay, and neutron fission product
yield sublibrary files. The depletion chain used during a depletion
simulation is indicated by either an argument to
:class:`openmc.deplete.Operator` or through the
:class:`openmc.deplete.CoupledOperator` or
:class:`openmc.deplete.IndependentOperator`, or through the
``depletion_chain`` item in the :envvar:`OPENMC_CROSS_SECTIONS`
environment variable.

View file

@ -0,0 +1,546 @@
"""Transport-coupled transport operator for depletion.
This module implements a transport operator coupled to OpenMC's transport solver
so that it can be used by depletion integrators. The implementation makes use of
the Python bindings to OpenMC's C API so that reading tally results and updating
material number densities is all done in-memory instead of through the
filesystem.
"""
import copy
import os
from warnings import warn
import numpy as np
from uncertainties import ufloat
import openmc
from openmc.checkvalue import check_value
from openmc.data import DataLibrary
from openmc.exceptions import DataError
import openmc.lib
from openmc.mpi import comm
from .abc import OperatorResult
from .chain import _find_chain_file
from .openmc_operator import OpenMCOperator, _distribute
from .results import Results
from .helpers import (
DirectReactionRateHelper, ChainFissionHelper, ConstantFissionYieldHelper,
FissionYieldCutoffHelper, AveragedFissionYieldHelper, EnergyScoreHelper,
SourceRateHelper, FluxCollapseHelper)
__all__ = ["CoupledOperator", "Operator", "OperatorResult"]
def _find_cross_sections(model):
"""Determine cross sections to use for depletion
Parameters
----------
model : openmc.model.Model
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
# otherwise fallback to environment variable
cross_sections = os.environ.get("OPENMC_CROSS_SECTIONS")
if cross_sections is None:
raise DataError(
"Cross sections were not specified in Model.materials and "
"the OPENMC_CROSS_SECTIONS environment variable is not set."
)
return cross_sections
def _get_nuclides_with_data(cross_sections):
"""Loads cross_sections.xml file to find nuclides with neutron data
Parameters
----------
cross_sections : str
Path to cross_sections.xml file
Returns
-------
nuclides : set of str
Set of nuclide names that have cross secton data
"""
nuclides = set()
data_lib = DataLibrary.from_xml(cross_sections)
for library in data_lib.libraries:
if library['type'] != 'neutron':
continue
for name in library['materials']:
if name not in nuclides:
nuclides.add(name)
return nuclides
class CoupledOperator(OpenMCOperator):
"""Transport-coupled transport operator.
Instances of this class can be used to perform transport-coupled depletion
using OpenMC's transport solver. Normally, a user needn't call methods of
this class directly. Instead, an instance of this class is passed to an
integrator class, such as :class:`openmc.deplete.CECMIntegrator`.
.. versionchanged:: 0.13.0
The geometry and settings parameters have been replaced with a
model parameter that takes a :class:`~openmc.model.Model` object
Parameters
----------
model : openmc.model.Model
OpenMC model object
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the file
listed under ``depletion_chain`` in
:envvar:`OPENMC_CROSS_SECTIONS` environment variable.
prev_results : Results, optional
Results from a previous depletion calculation. If this argument is
specified, the depletion calculation will start from the latest state
in the previous results.
diff_burnable_mats : bool, optional
Whether to differentiate burnable materials with multiple instances.
Volumes are divided equally from the original material volume.
normalization_mode : {"energy-deposition", "fission-q", "source-rate"}
Indicate how tally results should be normalized. ``"energy-deposition"``
computes the total energy deposited in the system and uses the ratio of
the power to the energy produced as a normalization factor.
``"fission-q"`` uses the fission Q values from the depletion chain to
compute the total energy deposited. ``"source-rate"`` normalizes
tallies based on the source rate (for fixed source calculations).
fission_q : dict, optional
Dictionary of nuclides and their fission Q values [eV]. If not given,
values will be pulled from the ``chain_file``. Only applicable
if ``"normalization_mode" == "fission-q"``
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.
fission_yield_mode : {"constant", "cutoff", "average"}
Key indicating what fission product yield scheme to use. The
key determines what fission energy helper is used:
* "constant": :class:`~openmc.deplete.helpers.ConstantFissionYieldHelper`
* "cutoff": :class:`~openmc.deplete.helpers.FissionYieldCutoffHelper`
* "average": :class:`~openmc.deplete.helpers.AveragedFissionYieldHelper`
The documentation on these classes describe their methodology
and differences. Default: ``"constant"``
fission_yield_opts : dict of str to option, optional
Optional arguments to pass to the helper determined by
``fission_yield_mode``. Will be passed directly on to the
helper. Passing a value of None will use the defaults for
the associated helper.
reaction_rate_mode : {"direct", "flux"}, optional
Indicate how one-group reaction rates should be calculated. The "direct"
method tallies transmutation reaction rates directly. The "flux" method
tallies a multigroup flux spectrum and then collapses one-group reaction
rates after a transport solve (with an option to tally some reaction
rates directly).
.. versionadded:: 0.12.1
reaction_rate_opts : dict, optional
Keyword arguments that are passed to the reaction rate helper class.
When ``reaction_rate_mode`` is set to "flux", energy group boundaries
can be set using the "energies" key. See the
:class:`~openmc.deplete.helpers.FluxCollapseHelper` class for all
options.
.. versionadded:: 0.12.1
reduce_chain : bool, optional
If True, use :meth:`openmc.deplete.Chain.reduce` to reduce the
depletion chain up to ``reduce_chain_level``.
.. versionadded:: 0.12
reduce_chain_level : int, optional
Depth of the search when reducing the depletion chain. Only used
if ``reduce_chain`` evaluates to true. The default value of
``None`` implies no limit on the depth.
.. versionadded:: 0.12
Attributes
----------
model : openmc.model.Model
OpenMC model object
geometry : openmc.Geometry
OpenMC geometry object
settings : openmc.Settings
OpenMC settings 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 decay
chain. Only done for nuclides with reaction rates.
output_dir : pathlib.Path
Path to output directory to save results.
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.
number : openmc.deplete.AtomNumber
Total number of atoms in simulation.
nuclides_with_data : set of str
A set listing all unique nuclides available from cross_sections.xml.
chain : openmc.deplete.Chain
The depletion chain information necessary to form matrices and tallies.
reaction_rates : openmc.deplete.ReactionRates
Reaction rates from the last operator step.
burnable_mats : list of str
All burnable material IDs
heavy_metal : float
Initial heavy metal inventory [g]
local_mats : list of str
All burnable material IDs being managed by a single process
prev_res : Results or None
Results from a previous depletion calculation. ``None`` if no
results are to be used.
cleanup_when_done : bool
Whether to finalize and clear the shared library memory when the
depletion operation is complete. Defaults to clearing the library.
"""
_fission_helpers = {
"average": AveragedFissionYieldHelper,
"constant": ConstantFissionYieldHelper,
"cutoff": FissionYieldCutoffHelper,
}
def __init__(self, model, chain_file=None, prev_results=None,
diff_burnable_mats=False, normalization_mode="fission-q",
fission_q=None, dilute_initial=1.0e3,
fission_yield_mode="constant", fission_yield_opts=None,
reaction_rate_mode="direct", reaction_rate_opts=None,
reduce_chain=False, reduce_chain_level=None):
# check for old call to constructor
if isinstance(model, openmc.Geometry):
msg = "As of version 0.13.0 openmc.deplete.CoupledOperator " \
"requires an openmc.Model object rather than the " \
"openmc.Geometry and openmc.Settings parameters. Please use " \
"the geometry and settings objects passed here to create a " \
" model with which to generate the transport Operator."
raise TypeError(msg)
# Determine cross sections / depletion chain
cross_sections = _find_cross_sections(model)
if chain_file is None:
chain_file = _find_chain_file(cross_sections)
check_value('fission yield mode', fission_yield_mode,
self._fission_helpers.keys())
check_value('normalization mode', normalization_mode,
('energy-deposition', 'fission-q', 'source-rate'))
if normalization_mode != "fission-q":
if fission_q is not None:
warn("Fission Q dictionary will not be used")
fission_q = None
self.model = model
self.settings = model.settings
self.geometry = model.geometry
# determine set of materials in the model
if not model.materials:
model.materials = openmc.Materials(
model.geometry.get_all_materials().values()
)
self.cleanup_when_done = True
if reaction_rate_opts is None:
reaction_rate_opts = {}
if fission_yield_opts is None:
fission_yield_opts = {}
helper_kwargs = {
'reaction_rate_mode': reaction_rate_mode,
'normalization_mode': normalization_mode,
'fission_yield_mode': fission_yield_mode,
'reaction_rate_opts': reaction_rate_opts,
'fission_yield_opts': fission_yield_opts
}
super().__init__(
model.materials,
cross_sections,
chain_file,
prev_results,
diff_burnable_mats,
fission_q,
dilute_initial,
helper_kwargs,
reduce_chain,
reduce_chain_level)
def _differentiate_burnable_mats(self):
"""Assign distribmats for each burnable material"""
# Count the number of instances for each cell and material
self.geometry.determine_paths(instances_only=True)
# Extract all burnable materials which have multiple instances
distribmats = set(
[mat for mat in self.materials
if mat.depletable and mat.num_instances > 1])
for mat in distribmats:
if mat.volume is None:
raise RuntimeError("Volume not specified for depletable "
"material with ID={}.".format(mat.id))
mat.volume /= mat.num_instances
if distribmats:
# Assign distribmats to cells
for cell in self.geometry.get_all_material_cells().values():
if cell.fill in distribmats:
mat = cell.fill
cell.fill = [mat.clone()
for i in range(cell.num_instances)]
self.materials = openmc.Materials(
self.model.geometry.get_all_materials().values()
)
def _load_previous_results(self):
"""Load results from a previous depletion simulation"""
# Reload volumes into geometry
self.prev_res[-1].transfer_volumes(self.model)
# Store previous results in operator
# Distribute reaction rates according to those tracked
# on this process
if comm.size != 1:
prev_results = self.prev_res
self.prev_res = Results()
mat_indexes = _distribute(range(len(self.burnable_mats)))
for res_obj in prev_results:
new_res = res_obj.distribute(self.local_mats, mat_indexes)
self.prev_res.append(new_res)
def _get_nuclides_with_data(self, cross_sections):
"""Loads cross_sections.xml file to find nuclides with neutron data
Parameters
----------
cross_sections : str
Path to cross_sections.xml file
Returns
-------
nuclides : set of str
Set of nuclide names that have cross secton data
"""
return _get_nuclides_with_data(cross_sections)
def _get_helper_classes(self, helper_kwargs):
"""Create the ``_rate_helper``, ``_normalization_helper``, and
``_yield_helper`` objects.
Parameters
----------
helper_kwargs : dict
Keyword arguments for helper classes
"""
reaction_rate_mode = helper_kwargs['reaction_rate_mode']
normalization_mode = helper_kwargs['normalization_mode']
fission_yield_mode = helper_kwargs['fission_yield_mode']
reaction_rate_opts = helper_kwargs['reaction_rate_opts']
fission_yield_opts = helper_kwargs['fission_yield_opts']
# Get classes to assist working with tallies
if reaction_rate_mode == "direct":
self._rate_helper = DirectReactionRateHelper(
self.reaction_rates.n_nuc, self.reaction_rates.n_react)
elif reaction_rate_mode == "flux":
# Ensure energy group boundaries were specified
if 'energies' not in reaction_rate_opts:
raise ValueError(
"Energy group boundaries must be specified in the "
"reaction_rate_opts argument when reaction_rate_mode is"
"set to 'flux'.")
self._rate_helper = FluxCollapseHelper(
self.reaction_rates.n_nuc,
self.reaction_rates.n_react,
**reaction_rate_opts
)
else:
raise ValueError("Invalid reaction rate mode.")
if normalization_mode == "fission-q":
self._normalization_helper = ChainFissionHelper()
elif normalization_mode == "energy-deposition":
score = "heating" if self.settings.photon_transport else "heating-local"
self._normalization_helper = EnergyScoreHelper(score)
else:
self._normalization_helper = SourceRateHelper()
# Select and create fission yield helper
fission_helper = self._fission_helpers[fission_yield_mode]
self._yield_helper = fission_helper.from_operator(
self, **fission_yield_opts)
def initial_condition(self):
"""Performs final setup and returns initial condition.
Returns
-------
list of numpy.ndarray
Total density for initial conditions.
"""
# Create XML files
if comm.rank == 0:
self.geometry.export_to_xml()
self.settings.export_to_xml()
self._generate_materials_xml()
# Initialize OpenMC library
comm.barrier()
if not openmc.lib.is_initialized:
openmc.lib.init(intracomm=comm)
# Generate tallies in memory
materials = [openmc.lib.materials[int(i)] for i in self.burnable_mats]
return super().initial_condition(materials)
def _generate_materials_xml(self):
"""Creates materials.xml from self.number.
Due to uncertainty with how MPI interacts with OpenMC API, this
constructs the XML manually. The long term goal is to do this
through direct memory writing.
"""
# Sort nuclides according to order in AtomNumber object
nuclides = list(self.number.nuclides)
for mat in self.materials:
mat._nuclides.sort(key=lambda x: nuclides.index(x[0]))
self.materials.export_to_xml()
def __call__(self, vec, source_rate):
"""Runs a simulation.
Simulation will abort under the following circumstances:
1) No energy is computed using OpenMC tallies.
Parameters
----------
vec : list of numpy.ndarray
Total atoms to be used in function.
source_rate : float
Power in [W] or source rate in [neutron/sec]
Returns
-------
openmc.deplete.OperatorResult
Eigenvalue and reaction rates resulting from transport operator
"""
# Reset results in OpenMC
openmc.lib.reset()
self._update_materials_and_nuclides(vec)
# If the source rate is zero, return zero reaction rates without running
# a transport solve
if source_rate == 0.0:
rates = self.reaction_rates.copy()
rates.fill(0.0)
return OperatorResult(ufloat(0.0, 0.0), rates)
# Run OpenMC
openmc.lib.run()
openmc.lib.reset_timers()
# Extract results
rates = self._calculate_reaction_rates(source_rate)
# Get k and uncertainty
keff = ufloat(*openmc.lib.keff())
op_result = OperatorResult(keff, rates)
return copy.deepcopy(op_result)
def _update_materials(self):
"""Updates material compositions in OpenMC on all processes."""
for rank in range(comm.size):
number_i = comm.bcast(self.number, root=rank)
for mat in number_i.materials:
nuclides = []
densities = []
for nuc in number_i.nuclides:
if nuc in self.nuclides_with_data:
val = 1.0e-24 * number_i.get_atom_density(mat, nuc)
# If nuclide is zero, do not add to the problem.
if val > 0.0:
if self.round_number:
val_magnitude = np.floor(np.log10(val))
val_scaled = val / 10**val_magnitude
val_round = round(val_scaled, 8)
val = val_round * 10**val_magnitude
nuclides.append(nuc)
densities.append(val)
else:
# Only output warnings if values are significantly
# negative. CRAM does not guarantee positive
# values.
if val < -1.0e-21:
print(f'WARNING: nuclide {nuc} in material'
f'{mat} is negative (density = {val}'
' atom/b-cm)')
number_i[mat, nuc] = 0.0
# Update densities on C API side
mat_internal = openmc.lib.materials[int(mat)]
mat_internal.set_densities(nuclides, densities)
# TODO Update densities on the Python side, otherwise the
# summary.h5 file contains densities at the first time step
@staticmethod
def write_bos_data(step):
"""Write a state-point file with beginning of step data
Parameters
----------
step : int
Current depletion step including restarts
"""
openmc.lib.statepoint_write(
"openmc_simulation_n{}.h5".format(step),
write_source=False)
def finalize(self):
"""Finalize a depletion simulation and release resources."""
if self.cleanup_when_done:
openmc.lib.finalize()
# Retain deprecated name for the time being
def Operator(*args, **kwargs):
# warn of name change
warn(
"The Operator(...) class has been renamed and will "
"be removed in a future version of OpenMC. Use "
"CoupledOperator(...) instead.",
FutureWarning
)
return CoupledOperator(*args, **kwargs)

View file

@ -1,5 +1,5 @@
"""
Class for normalizing fission energy deposition
Classes for collecting and calculating quantities for reaction rate operators
"""
import bisect
from abc import abstractmethod
@ -68,7 +68,7 @@ class TalliedFissionYieldHelper(FissionYieldHelper):
mat_indexes : iterable of int
Indices of tallied materials that will have their fission
yields computed by this helper. Necessary as the
:class:`openmc.deplete.Operator` that uses this helper
:class:`openmc.deplete.CoupledOperator` that uses this helper
may only burn a subset of all materials when running
in parallel mode.
"""
@ -133,9 +133,11 @@ class DirectReactionRateHelper(ReactionRateHelper):
Parameters
----------
n_nucs : int
Number of burnable nuclides tracked by :class:`openmc.deplete.Operator`
Number of burnable nuclides tracked by
:class:`openmc.deplete.CoupledOperator`
n_react : int
Number of reactions tracked by :class:`openmc.deplete.Operator`
Number of reactions tracked by an instance of
:class:`openmc.deplete.CoupledOperator`
Attributes
----------
@ -217,9 +219,10 @@ class FluxCollapseHelper(ReactionRateHelper):
Parameters
----------
n_nucs : int
Number of burnable nuclides tracked by :class:`openmc.deplete.Operator`
Number of burnable nuclides tracked by
:class:`openmc.deplete.CoupledOperator`
n_react : int
Number of reactions tracked by :class:`openmc.deplete.Operator`
Number of reactions tracked by :class:`openmc.deplete.CoupledOperator`
energies : iterable of float
Energy group boundaries for flux spectrum in [eV]
reactions : iterable of str
@ -385,7 +388,7 @@ class ChainFissionHelper(EnergyNormalizationHelper):
----------
nuclides : list of str
All nuclides with desired reaction rates. Ordered to be
consistent with :class:`openmc.deplete.Operator`
consistent with :class:`openmc.deplete.CoupledOperator`
energy : float
Total energy [J/s/source neutron] produced in a transport simulation.
Updated in the material iteration with :meth:`update`.
@ -552,7 +555,7 @@ class ConstantFissionYieldHelper(FissionYieldHelper):
Parameters
----------
operator : openmc.deplete.TransportOperator
operator : openmc.deplete.abc.TransportOperator
operator with a depletion chain
kwargs:
Additional keyword arguments to be used in construction
@ -610,7 +613,6 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper):
Default: 0.0253 [eV]
fast_energy : float, optional
Energy of yield data corresponding to fast yields.
Default: 500 [kev]
Attributes
----------
@ -632,10 +634,10 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper):
Array of fission rate fractions with shape
``(n_mats, 2, n_nucs)``. ``results[:, 0]``
corresponds to the fraction of all fissions
that occured below ``cutoff``. The number
that occurred below ``cutoff``. The number
of materials in the first axis corresponds
to the number of materials burned by the
:class:`openmc.deplete.Operator`
:class:`openmc.deplete.CoupledOperator`
"""
def __init__(self, chain_nuclides, n_bmats, cutoff=112.0,
@ -692,7 +694,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper):
Parameters
----------
operator : openmc.deplete.Operator
operator : openmc.deplete.CoupledOperator
Operator with a chain and burnable materials
kwargs:
Additional keyword arguments to be used in construction
@ -718,7 +720,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper):
mat_indexes : iterable of int
Indices of tallied materials that will have their fission
yields computed by this helper. Necessary as the
:class:`openmc.deplete.Operator` that uses this helper
:class:`openmc.deplete.CoupledOperator` that uses this helper
may only burn a subset of all materials when running
in parallel mode.
"""
@ -788,7 +790,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper):
class AveragedFissionYieldHelper(TalliedFissionYieldHelper):
r"""Class that computes fission yields based on average fission energy
Computes average energy at which fission events occured with
Computes average energy at which fission events occurred with
.. math::
@ -843,7 +845,7 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper):
mat_indexes : iterable of int
Indices of tallied materials that will have their fission
yields computed by this helper. Necessary as the
:class:`openmc.deplete.Operator` that uses this helper
:class:`openmc.deplete.CoupledOperator` that uses this helper
may only burn a subset of all materials when running
in parallel mode.
"""
@ -906,7 +908,7 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper):
Use the computed average energy of fission
events to determine fission yields. If average
energy is between two sets of yields, linearly
interpolate bewteen the two.
interpolate between the two.
Otherwise take the closet set of yields.
Parameters
@ -956,7 +958,7 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper):
Parameters
----------
operator : openmc.deplete.TransportOperator
operator : openmc.deplete.CoupledOperator
Operator with a depletion chain
kwargs :
Additional keyword arguments to be used in construction

View file

@ -0,0 +1,425 @@
"""Transport-independent transport operator for depletion.
This module implements a transport operator that runs independently of any
transport solver by using user-provided one-group cross sections.
"""
import copy
from collections import OrderedDict
from warnings import warn
from itertools import product
import numpy as np
from uncertainties import ufloat
import openmc
from openmc.checkvalue import check_type
from openmc.mpi import comm
from .abc import ReactionRateHelper, OperatorResult
from .openmc_operator import OpenMCOperator, _distribute
from .microxs import MicroXS
from .results import Results
from .helpers import ChainFissionHelper, ConstantFissionYieldHelper, SourceRateHelper
class IndependentOperator(OpenMCOperator):
"""Transport-independent transport operator that uses one-group cross
sections to calculate reaction rates.
Instances of this class can be used to perform depletion using one-group
cross sections and constant flux or constant power. Normally, a user needn't
call methods of this class directly. Instead, an instance of this class is
passed to an integrator class, such as
:class:`openmc.deplete.CECMIntegrator`.
Parameters
----------
materials : openmc.Materials
Materials to deplete.
micro_xs : MicroXS
One-group microscopic cross sections in [b] .
chain_file : str
Path to the depletion chain XML file.
keff : 2-tuple of float, optional
keff eigenvalue and uncertainty from transport calculation.
Default is None.
prev_results : Results, optional
Results from a previous depletion calculation.
normalization_mode : {"fission-q", "source-rate"}
Indicate how reaction rates should be calculated.
``"fission-q"`` uses the fission Q values from the depletion chain to
compute the flux based on the power. ``"source-rate"`` uses a the
source rate (assumed to be neutron flux) to calculate the
reaction rates.
fission_q : dict, optional
Dictionary of nuclides and their fission Q values [eV]. If not given,
values will be pulled from the ``chain_file``. Only applicable
if ``"normalization_mode" == "fission-q"``.
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.
reduce_chain : bool, optional
If True, use :meth:`openmc.deplete.Chain.reduce` to reduce the
depletion chain up to ``reduce_chain_level``.
reduce_chain_level : int, optional
Depth of the search when reducing the depletion chain. Only used
if ``reduce_chain`` evaluates to true. The default value of
``None`` implies no limit on the depth.
fission_yield_opts : dict of str to option, optional
Optional arguments to pass to the
:class:`openmc.deplete.helpers.FissionYieldHelper` object. Will be
passed directly on to the helper. Passing a value of None will use
the defaults for the associated helper.
Attributes
----------
materials : openmc.Materials
All materials present in the model
cross_sections : MicroXS
Object containing one-group cross-sections in [cm^2].
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.
output_dir : pathlib.Path
Path to output directory to save results.
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.
number : openmc.deplete.AtomNumber
Total number of atoms in simulation.
nuclides_with_data : set of str
A set listing all unique nuclides available from cross_sections.xml.
chain : openmc.deplete.Chain
The depletion chain information necessary to form matrices and tallies.
reaction_rates : openmc.deplete.ReactionRates
Reaction rates from the last operator step.
burnable_mats : list of str
All burnable material IDs
heavy_metal : float
Initial heavy metal inventory [g]
local_mats : list of str
All burnable material IDs being managed by a single process
prev_res : Results or None
Results from a previous depletion calculation. ``None`` if no
results are to be used.
"""
def __init__(self,
materials,
micro_xs,
chain_file,
keff=None,
normalization_mode='fission-q',
fission_q=None,
dilute_initial=1.0e3,
prev_results=None,
reduce_chain=False,
reduce_chain_level=None,
fission_yield_opts=None):
# Validate micro-xs parameters
check_type('materials', materials, openmc.Materials)
check_type('micro_xs', micro_xs, MicroXS)
if keff is not None:
check_type('keff', keff, tuple, float)
keff = ufloat(*keff)
self._keff = keff
if fission_yield_opts is None:
fission_yield_opts = {}
helper_kwargs = {'normalization_mode': normalization_mode,
'fission_yield_opts': fission_yield_opts}
cross_sections = micro_xs * 1e-24
super().__init__(
materials,
cross_sections,
chain_file,
prev_results,
fission_q=fission_q,
dilute_initial=dilute_initial,
helper_kwargs=helper_kwargs,
reduce_chain=reduce_chain,
reduce_chain_level=reduce_chain_level)
@classmethod
def from_nuclides(cls, volume, nuclides,
micro_xs,
chain_file,
nuc_units='atom/b-cm',
keff=None,
normalization_mode='fission-q',
fission_q=None,
dilute_initial=1.0e3,
prev_results=None,
reduce_chain=False,
reduce_chain_level=None,
fission_yield_opts=None):
"""
Alternate constructor from a dictionary of nuclide concentrations
volume : float
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.
micro_xs : MicroXS
One-group microscopic cross sections.
chain_file : str
Path to the depletion chain XML file.
nuc_units : {'atom/cm3', 'atom/b-cm'}
Units for nuclide concentration.
keff : 2-tuple of float, optional
keff eigenvalue and uncertainty from transport calculation.
Default is None.
normalization_mode : {"fission-q", "source-rate"}
Indicate how reaction rates should be calculated.
``"fission-q"`` uses the fission Q values from the depletion
chain to compute the flux based on the power. ``"source-rate"`` uses
the source rate (assumed to be neutron flux) to calculate the
reaction rates.
fission_q : dict, optional
Dictionary of nuclides and their fission Q values [eV]. If not
given, values will be pulled from the ``chain_file``. Only
applicable if ``"normalization_mode" == "fission-q"``.
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.
prev_results : Results, optional
Results from a previous depletion calculation.
reduce_chain : bool, optional
If True, use :meth:`openmc.deplete.Chain.reduce` to reduce the
depletion chain up to ``reduce_chain_level``. Default is False.
reduce_chain_level : int, optional
Depth of the search when reducing the depletion chain. Only used
if ``reduce_chain`` evaluates to true. The default value of
``None`` implies no limit on the depth.
fission_yield_opts : dict of str to option, optional
Optional arguments to pass to the
:class:`openmc.deplete.helpers.FissionYieldHelper` class. Will be
passed directly on to the helper. Passing a value of None will use
the defaults for the associated helper.
"""
check_type('nuclides', nuclides, dict, str)
materials = cls._consolidate_nuclides_to_material(nuclides, nuc_units, volume)
return cls(materials,
micro_xs,
chain_file,
keff=keff,
normalization_mode=normalization_mode,
fission_q=fission_q,
dilute_initial=dilute_initial,
prev_results=prev_results,
reduce_chain=reduce_chain,
reduce_chain_level=reduce_chain_level,
fission_yield_opts=fission_yield_opts)
@staticmethod
def _consolidate_nuclides_to_material(nuclides, nuc_units, volume):
"""Puts nuclide list into an openmc.Materials object.
"""
openmc.reset_auto_ids()
mat = openmc.Material()
if nuc_units == 'atom/b-cm':
for nuc, conc in nuclides.items():
mat.add_nuclide(nuc, conc)
elif nuc_units == 'atom/cm3':
for nuc, conc in nuclides.items():
mat.add_nuclide(nuc, conc * 1e-24) # convert to at/b-cm
else:
raise ValueError(f"Unit '{nuc_units}' is invalid.")
mat.volume = volume
mat.depletable = True
return openmc.Materials([mat])
def _load_previous_results(self):
"""Load results from a previous depletion simulation"""
# Reload volumes into geometry
model = openmc.Model(materials=self.materials)
self.prev_res[-1].transfer_volumes(model)
self.materials = model.materials
# Store previous results in operator
# Distribute reaction rates according to those tracked
# on this process
if comm.size != 1:
prev_results = self.prev_res
self.prev_res = Results()
mat_indexes = _distribute(range(len(self.burnable_mats)))
for res_obj in prev_results:
new_res = res_obj.distribute(self.local_mats, mat_indexes)
self.prev_res.append(new_res)
def _get_nuclides_with_data(self, cross_sections):
"""Finds nuclides with cross section data"""
return set(cross_sections.index)
class _IndependentRateHelper(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 nuclide and transmutation reaction relevant for a depletion
calculation. The reaction rate is calculated by multiplying the flux by the
cross sections.
Parameters
----------
op : openmc.deplete.IndependentOperator
Reference to the object encapsulate _IndependentRateHelper.
We pass this so we don't have to duplicate the :attr:`IndependentOperator.number` object.
Attributes
----------
nuc_ind_map : dict of int to str
Dictionary mapping the nuclide index to nuclide name
rxn_ind_map : dict of int to str
Dictionary mapping reaction index to reaction name
"""
def __init__(self, op):
rates = op.reaction_rates
super().__init__(rates.n_nuc, rates.n_react)
self.nuc_ind_map = {ind: nuc for nuc, ind in rates.index_nuc.items()}
self.rxn_ind_map = {ind: rxn for rxn, ind in rates.index_rx.items()}
self._op = op
def generate_tallies(self, materials, scores):
"""Unused in this case"""
pass
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)
volume = self._op.number.get_mat_volume(mat_id)
for i_nuc, i_react in product(nuc_index, react_index):
nuc = self.nuc_ind_map[i_nuc]
rxn = self.rxn_ind_map[i_react]
density = self._op.number.get_atom_density(mat_id, nuc)
# Sigma^j_i * V = sigma^j_i * rho * V
self._results_cache[i_nuc,i_react] = \
self._op.cross_sections[rxn][nuc] * density * volume
return self._results_cache
def _get_helper_classes(self, helper_kwargs):
"""Get helper classes for calculating reaction rates and fission yields
Parameters
----------
helper_kwargs : dict
Keyword arguments for helper classes
"""
normalization_mode = helper_kwargs['normalization_mode']
fission_yield_opts = helper_kwargs['fission_yield_opts']
self._rate_helper = self._IndependentRateHelper(self)
if normalization_mode == "fission-q":
self._normalization_helper = ChainFissionHelper()
else:
self._normalization_helper = SourceRateHelper()
# Select and create fission yield helper
fission_helper = ConstantFissionYieldHelper
self._yield_helper = fission_helper.from_operator(
self, **fission_yield_opts)
def initial_condition(self):
"""Performs final setup and returns initial condition.
Returns
-------
list of numpy.ndarray
Total density for initial conditions.
"""
# Return number density vector
return super().initial_condition(self.materials)
def __call__(self, vec, source_rate):
"""Obtain the reaction rates
Parameters
----------
vec : list of numpy.ndarray
Total atoms to be used in function.
source_rate : float
Power in [W] or flux in [neutron/cm^2-s]
Returns
-------
openmc.deplete.OperatorResult
Eigenvalue and reaction rates resulting from transport operator
"""
self._update_materials_and_nuclides(vec)
rates = self._calculate_reaction_rates(source_rate)
keff = self._keff
op_result = OperatorResult(keff, rates)
return copy.deepcopy(op_result)
def _update_materials(self):
"""Updates material compositions in OpenMC on all processes."""
for rank in range(comm.size):
number_i = comm.bcast(self.number, root=rank)
for mat in number_i.materials:
nuclides = []
densities = []
for nuc in number_i.nuclides:
if nuc in self.nuclides_with_data:
val = 1.0e-24 * number_i.get_atom_density(mat, nuc)
# If nuclide is zero, do not add to the problem.
if val > 0.0:
if self.round_number:
val_magnitude = np.floor(np.log10(val))
val_scaled = val / 10**val_magnitude
val_round = round(val_scaled, 8)
val = val_round * 10**val_magnitude
nuclides.append(nuc)
densities.append(val)
else:
# Only output warnings if values are significantly
# negative. CRAM does not guarantee positive
# values.
if val < -1.0e-21:
print(f'WARNING: nuclide {nuc} in material'
f'{mat} is negative (density = {val}'
' atom/b-cm)')
number_i[mat, nuc] = 0.0

View file

@ -103,7 +103,7 @@ class CECMIntegrator(Integrator):
op_results : list of openmc.deplete.OperatorResult
Eigenvalue and reaction rates from transport simulations
"""
# deplete across first half of inteval
# deplete across first half of interval
time0, x_middle = self._timed_deplete(conc, rates, dt / 2)
res_middle = self.operator(x_middle, source_rate)

238
openmc/deplete/microxs.py Normal file
View file

@ -0,0 +1,238 @@
"""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 pathlib import Path
from copy import deepcopy
from pandas import DataFrame, read_csv, concat
import numpy as np
from openmc.checkvalue import check_type, check_value, check_iterable_type
from openmc.mgxs import EnergyGroups, ArbitraryXS, FissionXS
from openmc.data import DataLibrary
from openmc import Tallies, StatePoint, Materials, Material
from .chain import Chain, REACTIONS
from .coupled_operator import _find_cross_sections, _get_nuclides_with_data
_valid_rxns = list(REACTIONS)
_valid_rxns.append('fission')
class MicroXS(DataFrame):
"""Stores microscopic cross section data for use in
transport-independent depletion.
"""
@classmethod
def from_model(cls,
model,
reaction_domain,
chain_file,
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
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.
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.
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 for :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.
"""
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
@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
@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)

View file

@ -0,0 +1,570 @@
"""OpenMC transport operator
This module implements functions shared by both OpenMC transport-coupled and
transport-independent transport operators.
"""
from abc import abstractmethod
from collections import OrderedDict
import numpy as np
import openmc
from openmc.mpi import comm
from .abc import TransportOperator, OperatorResult
from .atom_number import AtomNumber
from .reaction_rates import ReactionRates
__all__ = ["OpenMCOperator", "OperatorResult"]
def _distribute(items):
"""Distribute items across MPI communicator
Parameters
----------
items : list
List of items of distribute
Returns
-------
list
Items assigned to process that called
"""
min_size, extra = divmod(len(items), comm.size)
j = 0
for i in range(comm.size):
chunk_size = min_size + int(i < extra)
if comm.rank == i:
return items[j:j + chunk_size]
j += chunk_size
class OpenMCOperator(TransportOperator):
"""Abstract class holding OpenMC-specific functions for running
depletion calculations.
Specific classes for running transport-coupled or transport-independent
depletion calculations are implemented as subclasses of OpenMCOperator.
Parameters
----------
materials : openmc.Materials
List of all materials in the model
cross_sections : str or pandas.DataFrame
Path to continuous energy cross section library, or object containing
one-group cross-sections.
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the file
listed under ``depletion_chain`` in
:envvar:`OPENMC_CROSS_SECTIONS` environment variable.
prev_results : Results, optional
Results from a previous depletion calculation. If this argument is
specified, the depletion calculation will start from the latest state
in the previous results.
diff_burnable_mats : bool, optional
Whether to differentiate burnable materials with multiple instances.
Volumes are divided equally from the original material volume.
fission_q : dict, optional
Dictionary of nuclides and their fission Q values [eV].
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.
helper_kwargs : dict
Keyword arguments for helper classes
reduce_chain : bool, optional
If True, use :meth:`openmc.deplete.Chain.reduce()` to reduce the
depletion chain up to ``reduce_chain_level``.
reduce_chain_level : int, optional
Depth of the search when reducing the depletion chain. Only used
if ``reduce_chain`` evaluates to true. The default value of
``None`` implies no limit on the depth.
Attributes
----------
materials : openmc.Materials
All materials present in the model
cross_sections : str or MicroXS
Path to continuous energy cross section library, or object
containing one-group cross-sections.
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.
output_dir : pathlib.Path
Path to output directory to save results.
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.
number : openmc.deplete.AtomNumber
Total number of atoms in simulation.
nuclides_with_data : set of str
A set listing all unique nuclides available from cross_sections.xml.
chain : openmc.deplete.Chain
The depletion chain information necessary to form matrices and tallies.
reaction_rates : openmc.deplete.ReactionRates
Reaction rates from the last operator step.
burnable_mats : list of str
All burnable material IDs
heavy_metal : float
Initial heavy metal inventory [g]
local_mats : list of str
All burnable material IDs being managed by a single process
prev_res : Results or None
Results from a previous depletion calculation. ``None`` if no
results are to be used.
"""
def __init__(
self,
materials=None,
cross_sections=None,
chain_file=None,
prev_results=None,
diff_burnable_mats=False,
fission_q=None,
dilute_initial=0.0,
helper_kwargs=None,
reduce_chain=False,
reduce_chain_level=None):
super().__init__(chain_file, fission_q, dilute_initial, prev_results)
self.round_number = False
self.materials = materials
self.cross_sections = cross_sections
# Reduce the chain to only those nuclides present
if reduce_chain:
init_nuclides = set()
for material in self.materials:
if not material.depletable:
continue
for name, _dens_percent, _dens_type in material.nuclides:
init_nuclides.add(name)
self.chain = self.chain.reduce(init_nuclides, reduce_chain_level)
if diff_burnable_mats:
self._differentiate_burnable_mats()
# Determine which nuclides have cross section data
# This nuclides variables contains every nuclides
# for which there is an entry in the micro_xs parameter
openmc.reset_auto_ids()
self.burnable_mats, volumes, all_nuclides = self._get_burnable_mats()
self.local_mats = _distribute(self.burnable_mats)
self._mat_index_map = {
lm: self.burnable_mats.index(lm) for lm in self.local_mats}
if self.prev_res is not None:
self._load_previous_results()
self.nuclides_with_data = self._get_nuclides_with_data(
self.cross_sections)
# Select nuclides with data that are also in the chain
self._burnable_nucs = [nuc.name for nuc in self.chain.nuclides
if nuc.name in self.nuclides_with_data]
# Extract number densities from the geometry / previous depletion run
self._extract_number(self.local_mats,
volumes,
all_nuclides,
self.prev_res)
# Create reaction rates array
self.reaction_rates = ReactionRates(
self.local_mats, self._burnable_nucs, self.chain.reactions)
self._get_helper_classes(helper_kwargs)
def _differentiate_burnable_mats(self):
"""Assign distribmats for each burnable material"""
pass
def _get_burnable_mats(self):
"""Determine depletable materials, volumes, and nuclides
Returns
-------
burnable_mats : list of str
List of burnable material IDs
volume : OrderedDict of str to float
Volume of each material in [cm^3]
nuclides : list of str
Nuclides in order of how they'll appear in the simulation.
"""
burnable_mats = set()
model_nuclides = set()
volume = OrderedDict()
self.heavy_metal = 0.0
# Iterate once through the geometry to get dictionaries
for mat in self.materials:
for nuclide in mat.get_nuclides():
model_nuclides.add(nuclide)
if mat.depletable:
burnable_mats.add(str(mat.id))
if mat.volume is None:
raise RuntimeError("Volume not specified for depletable "
"material with ID={}.".format(mat.id))
volume[str(mat.id)] = mat.volume
self.heavy_metal += mat.fissionable_mass
# Make sure there are burnable materials
if not burnable_mats:
raise RuntimeError(
"No depletable materials were found in the model.")
# Sort the sets
burnable_mats = sorted(burnable_mats, key=int)
model_nuclides = sorted(model_nuclides)
# Construct a global nuclide dictionary, burned first
nuclides = list(self.chain.nuclide_dict)
for nuc in model_nuclides:
if nuc not in nuclides:
nuclides.append(nuc)
return burnable_mats, volume, nuclides
def _load_previous_results(self):
"""Load results from a previous depletion simulation"""
pass
@abstractmethod
def _get_nuclides_with_data(self, cross_sections):
"""Find nuclides with cross section data
Parameters
----------
cross_sections : str or pandas.DataFrame
Path to continuous energy cross section library, or object
containing one-group cross-sections.
Returns
-------
nuclides : set of str
Set of nuclide names that have cross secton data
"""
def _extract_number(self, local_mats, volume, all_nuclides, prev_res=None):
"""Construct AtomNumber using geometry
Parameters
----------
local_mats : list of str
Material IDs to be managed by this process
volume : OrderedDict of str to float
Volumes for the above materials in [cm^3]
all_nuclides : list of str
Nuclides to be used in the simulation.
prev_res : Results, optional
Results from a previous depletion calculation
"""
self.number = AtomNumber(local_mats, all_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:
for mat in self.materials:
if str(mat.id) in local_mats:
self._set_number_from_mat(mat)
# Else from previous depletion results
else:
for mat in self.materials:
if str(mat.id) in local_mats:
self._set_number_from_results(mat, prev_res)
def _set_number_from_mat(self, mat):
"""Extracts material and number densities from openmc.Material
Parameters
----------
mat : openmc.Material
The material to read from
"""
mat_id = str(mat.id)
for nuclide, atom_per_bcm in mat.get_nuclide_atom_densities().items():
atom_per_cc = atom_per_bcm * 1.0e24
self.number.set_atom_density(mat_id, nuclide, atom_per_cc)
def _set_number_from_results(self, mat, prev_res):
"""Extracts material nuclides and number densities.
If the nuclide concentration's evolution is tracked, the densities come
from depletion results. Else, densities are extracted from the geometry
in the summary.
Parameters
----------
mat : openmc.Material
The material to read from
prev_res : Results
Results from a previous depletion calculation
"""
mat_id = str(mat.id)
# Get nuclide lists from geometry and depletion results
depl_nuc = prev_res[-1].nuc_to_ind
geom_nuc_densities = mat.get_nuclide_atom_densities()
# Merge lists of nuclides, with the same order for every calculation
geom_nuc_densities.update(depl_nuc)
for nuclide, atom_per_bcm in geom_nuc_densities.items():
if nuclide in depl_nuc:
concentration = prev_res.get_atoms(mat_id, nuclide)[1][-1]
volume = prev_res[-1].volume[mat_id]
atom_per_cc = concentration / volume
else:
atom_per_cc = atom_per_bcm * 1.0e24
self.number.set_atom_density(mat_id, nuclide, atom_per_cc)
@abstractmethod
def _get_helper_classes(self, helper_kwargs):
"""Create the ``_rate_helper``, ``_normalization_helper``, and
``_yield_helper`` objects.
Parameters
----------
helper_kwargs : dict
Keyword arguments for helper classes
"""
def initial_condition(self, materials):
"""Performs final setup and returns initial condition.
Parameters
----------
materials : list of str
list of material IDs
Returns
-------
list of numpy.ndarray
Total density for initial conditions.
"""
self._rate_helper.generate_tallies(materials, self.chain.reactions)
self._normalization_helper.prepare(
self.chain.nuclides, self.reaction_rates.index_nuc)
# Tell fission yield helper what materials this process is
# responsible for
self._yield_helper.generate_tallies(
materials, tuple(sorted(self._mat_index_map.values())))
# Return number density vector
return list(self.number.get_mat_slice(np.s_[:]))
def _update_materials_and_nuclides(self, vec):
"""Update the number density, material compositions, and nuclide
lists in helper objects
Parameters
----------
vec : list of numpy.ndarray
Total atoms.
"""
# Update the number densities regardless of the source rate
self.number.set_density(vec)
self._update_materials()
# Prevent OpenMC from complaining about re-creating tallies
openmc.reset_auto_ids()
# Update tally nuclides data in preparation for transport solve
nuclides = self._get_reaction_nuclides()
self._rate_helper.nuclides = nuclides
self._normalization_helper.nuclides = nuclides
self._yield_helper.update_tally_nuclides(nuclides)
@abstractmethod
def _update_materials(self):
"""Updates material compositions in OpenMC on all processes."""
def write_bos_data(self, step):
"""Document beginning of step data for a given step
Called at the beginning of a depletion step and at
the final point in the simulation.
Parameters
----------
step : int
Current depletion step including restarts
"""
# Since we aren't running a transport simulation, we simply pass
pass
def _get_reaction_nuclides(self):
"""Determine nuclides that should be tallied for reaction rates.
This method returns a list of all nuclides that have cross section data
and are listed in the depletion chain. Technically, we should count
nuclides that may not appear in the depletion chain because we still
need to get the fission reaction rate for these nuclides in order to
normalize power, but that is left as a future exercise.
Returns
-------
list of str
Nuclides with reaction rates
"""
nuc_set = set()
# Create the set of all nuclides in the decay chain in materials marked
# for burning in which the number density is greater than zero.
for nuc in self.number.nuclides:
if nuc in self.nuclides_with_data:
if np.sum(self.number[:, nuc]) > 0.0:
nuc_set.add(nuc)
# Communicate which nuclides have nonzeros to rank 0
if comm.rank == 0:
for i in range(1, comm.size):
nuc_newset = comm.recv(source=i, tag=i)
nuc_set |= nuc_newset
else:
comm.send(nuc_set, dest=0, tag=comm.rank)
if comm.rank == 0:
# Sort nuclides in the same order as self.number
nuc_list = [nuc for nuc in self.number.nuclides
if nuc in nuc_set]
else:
nuc_list = None
# Store list of nuclides on each process
nuc_list = comm.bcast(nuc_list)
return [nuc for nuc in nuc_list if nuc in self.chain]
def _calculate_reaction_rates(self, source_rate):
"""Unpack tallies from OpenMC and return an operator result
This method uses OpenMC's C API bindings to determine the k-effective
value and reaction rates from the simulation. The reaction rates are
normalized by a helper class depending on the method being used.
Parameters
----------
source_rate : float
Power in [W] or source rate in [neutron/sec]
Returns
-------
rates : openmc.deplete.ReactionRates
Reaction rates for nuclides
"""
rates = self.reaction_rates
rates.fill(0.0)
# Extract reaction nuclides
rxn_nuclides = self._rate_helper.nuclides
# Form fast map
nuc_ind = [rates.index_nuc[nuc] for nuc in rxn_nuclides]
react_ind = [rates.index_rx[react] for react in self.chain.reactions]
# Keep track of energy produced from all reactions in eV per source
# particle
self._normalization_helper.reset()
self._yield_helper.unpack()
# Store fission yield dictionaries
fission_yields = []
# Create arrays to store fission Q values, reaction rates, and nuclide
# numbers, zeroed out in material iteration
number = np.empty(rates.n_nuc)
fission_ind = rates.index_rx.get("fission")
# Extract results
for i, mat in enumerate(self.local_mats):
# Get tally index
mat_index = self._mat_index_map[mat]
# Zero out reaction rates and nuclide numbers
number.fill(0.0)
# Get new number densities
for nuc, i_nuc_results in zip(rxn_nuclides, nuc_ind):
number[i_nuc_results] = self.number[mat, nuc]
tally_rates = self._rate_helper.get_material_rates(
mat_index, nuc_ind, react_ind)
# Compute fission yields for this material
fission_yields.append(self._yield_helper.weighted_yields(i))
# Accumulate energy from fission
if fission_ind is not None:
self._normalization_helper.update(
tally_rates[:, fission_ind])
# Divide by total number and store
rates[i] = self._rate_helper.divide_by_adens(number)
# Scale reaction rates to obtain units of reactions/sec
rates *= self._normalization_helper.factor(source_rate)
# Store new fission yields on the chain
self.chain.fission_yields = fission_yields
return rates
def get_results_info(self):
"""Returns volume list, material lists, and nuc lists.
Returns
-------
volume : dict of str float
Volumes corresponding to materials in full_burn_dict
nuc_list : list of str
A list of all nuclide names. Used for sorting the simulation.
burn_list : list of int
A list of all material IDs to be burned. Used for sorting the simulation.
full_burn_list : list
List of all burnable material IDs
"""
nuc_list = self.number.burnable_nuclides
burn_list = self.local_mats
volume = {}
for i, mat in enumerate(burn_list):
volume[mat] = self.number.volume[i]
# Combine volume dictionaries across processes
volume_list = comm.allgather(volume)
volume = {k: v for d in volume_list for k, v in d.items()}
return volume, nuc_list, burn_list, self.burnable_mats

View file

@ -1,822 +0,0 @@
"""OpenMC transport operator
This module implements a transport operator for OpenMC so that it can be used by
depletion integrators. The implementation makes use of the Python bindings to
OpenMC's C API so that reading tally results and updating material number
densities is all done in-memory instead of through the filesystem.
"""
import copy
from collections import OrderedDict
import os
from warnings import warn
import numpy as np
from uncertainties import ufloat
import openmc
from openmc.checkvalue import check_value
from openmc.data import DataLibrary
from openmc.exceptions import DataError
import openmc.lib
from openmc.mpi import comm
from .abc import TransportOperator, OperatorResult
from .atom_number import AtomNumber
from .chain import _find_chain_file
from .reaction_rates import ReactionRates
from .results import Results
from .helpers import (
DirectReactionRateHelper, ChainFissionHelper, ConstantFissionYieldHelper,
FissionYieldCutoffHelper, AveragedFissionYieldHelper, EnergyScoreHelper,
SourceRateHelper, FluxCollapseHelper)
__all__ = ["Operator", "OperatorResult"]
def _distribute(items):
"""Distribute items across MPI communicator
Parameters
----------
items : list
List of items of distribute
Returns
-------
list
Items assigned to process that called
"""
min_size, extra = divmod(len(items), comm.size)
j = 0
for i in range(comm.size):
chunk_size = min_size + int(i < extra)
if comm.rank == i:
return items[j:j + chunk_size]
j += chunk_size
def _find_cross_sections(model):
"""Determine cross sections to use for depletion"""
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 = os.environ.get("OPENMC_CROSS_SECTIONS")
if cross_sections is None:
raise DataError(
"Cross sections were not specified in Model.materials and "
"the OPENMC_CROSS_SECTIONS environment variable is not set."
)
return cross_sections
class Operator(TransportOperator):
"""OpenMC transport operator for depletion.
Instances of this class can be used to perform depletion using OpenMC as the
transport operator. Normally, a user needn't call methods of this class
directly. Instead, an instance of this class is passed to an integrator
class, such as :class:`openmc.deplete.CECMIntegrator`.
.. versionchanged:: 0.13.0
The geometry and settings parameters have been replaced with a
model parameter that takes a :class:`~openmc.model.Model` object
Parameters
----------
model : openmc.model.Model
OpenMC model object
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the file
listed under ``depletion_chain`` in
:envvar:`OPENMC_CROSS_SECTIONS` environment variable.
prev_results : Results, optional
Results from a previous depletion calculation. If this argument is
specified, the depletion calculation will start from the latest state
in the previous results.
diff_burnable_mats : bool, optional
Whether to differentiate burnable materials with multiple instances.
Volumes are divided equally from the original material volume.
Default: False.
normalization_mode : {"energy-deposition", "fission-q", "source-rate"}
Indicate how tally results should be normalized. ``"energy-deposition"``
computes the total energy deposited in the system and uses the ratio of
the power to the energy produced as a normalization factor.
``"fission-q"`` uses the fission Q values from the depletion chain to
compute the total energy deposited. ``"source-rate"`` normalizes
tallies based on the source rate (for fixed source calculations).
fission_q : dict, optional
Dictionary of nuclides and their fission Q values [eV]. If not given,
values will be pulled from the ``chain_file``. Only applicable
if ``"normalization_mode" == "fission-q"``
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.
fission_yield_mode : {"constant", "cutoff", "average"}
Key indicating what fission product yield scheme to use. The
key determines what fission energy helper is used:
* "constant": :class:`~openmc.deplete.helpers.ConstantFissionYieldHelper`
* "cutoff": :class:`~openmc.deplete.helpers.FissionYieldCutoffHelper`
* "average": :class:`~openmc.deplete.helpers.AveragedFissionYieldHelper`
The documentation on these classes describe their methodology
and differences. Default: ``"constant"``
fission_yield_opts : dict of str to option, optional
Optional arguments to pass to the helper determined by
``fission_yield_mode``. Will be passed directly on to the
helper. Passing a value of None will use the defaults for
the associated helper.
reaction_rate_mode : {"direct", "flux"}, optional
Indicate how one-group reaction rates should be calculated. The "direct"
method tallies transmutation reaction rates directly. The "flux" method
tallies a multigroup flux spectrum and then collapses one-group reaction
rates after a transport solve (with an option to tally some reaction
rates directly).
.. versionadded:: 0.12.1
reaction_rate_opts : dict, optional
Keyword arguments that are passed to the reaction rate helper class.
When ``reaction_rate_mode`` is set to "flux", energy group boundaries
can be set using the "energies" key. See the
:class:`~openmc.deplete.helpers.FluxCollapseHelper` class for all
options.
.. versionadded:: 0.12.1
reduce_chain : bool, optional
If True, use :meth:`openmc.deplete.Chain.reduce` to reduce the
depletion chain up to ``reduce_chain_level``. Default is False.
.. versionadded:: 0.12
reduce_chain_level : int, optional
Depth of the search when reducing the depletion chain. Only used
if ``reduce_chain`` evaluates to true. The default value of
``None`` implies no limit on the depth.
.. versionadded:: 0.12
Attributes
----------
model : openmc.model.Model
OpenMC model object
geometry : openmc.Geometry
OpenMC geometry object
settings : openmc.Settings
OpenMC settings 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 decay
chain. Only done for nuclides with reaction rates.
output_dir : pathlib.Path
Path to output directory to save results.
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.
number : openmc.deplete.AtomNumber
Total number of atoms in simulation.
nuclides_with_data : set of str
A set listing all unique nuclides available from cross_sections.xml.
chain : openmc.deplete.Chain
The depletion chain information necessary to form matrices and tallies.
reaction_rates : openmc.deplete.ReactionRates
Reaction rates from the last operator step.
burnable_mats : list of str
All burnable material IDs
heavy_metal : float
Initial heavy metal inventory [g]
local_mats : list of str
All burnable material IDs being managed by a single process
prev_res : Results or None
Results from a previous depletion calculation. ``None`` if no
results are to be used.
cleanup_when_done : bool
Whether to finalize and clear the shared library memory when the
depletion operation is complete. Defaults to clearing the library.
"""
_fission_helpers = {
"average": AveragedFissionYieldHelper,
"constant": ConstantFissionYieldHelper,
"cutoff": FissionYieldCutoffHelper,
}
def __init__(self, model, chain_file=None, prev_results=None,
diff_burnable_mats=False, normalization_mode="fission-q",
fission_q=None, dilute_initial=1.0e3,
fission_yield_mode="constant", fission_yield_opts=None,
reaction_rate_mode="direct", reaction_rate_opts=None,
reduce_chain=False, reduce_chain_level=None):
# check for old call to constructor
if isinstance(model, openmc.Geometry):
msg = "As of version 0.13.0 openmc.deplete.Operator requires an " \
"openmc.Model object rather than the openmc.Geometry and " \
"openmc.Settings parameters. Please use the geometry and " \
"settings objects passed here to create a model with which " \
"to generate the depletion Operator."
raise TypeError(msg)
# Determine cross sections / depletion chain
cross_sections = _find_cross_sections(model)
if chain_file is None:
chain_file = _find_chain_file(cross_sections)
check_value('fission yield mode', fission_yield_mode,
self._fission_helpers.keys())
check_value('normalization mode', normalization_mode,
('energy-deposition', 'fission-q', 'source-rate'))
if normalization_mode != "fission-q":
if fission_q is not None:
warn("Fission Q dictionary will not be used")
fission_q = None
super().__init__(chain_file, fission_q, dilute_initial, prev_results)
self.round_number = False
self.model = model
self.settings = model.settings
self.geometry = model.geometry
# determine set of materials in the model
if not model.materials:
model.materials = openmc.Materials(
model.geometry.get_all_materials().values()
)
self.materials = model.materials
self.cleanup_when_done = True
# Reduce the chain before we create more materials
if reduce_chain:
all_isotopes = set()
for material in self.materials:
if not material.depletable:
continue
for name, _dens_percent, _dens_type in material.nuclides:
all_isotopes.add(name)
self.chain = self.chain.reduce(all_isotopes, reduce_chain_level)
# Differentiate burnable materials with multiple instances
if diff_burnable_mats:
self._differentiate_burnable_mats()
self.materials = openmc.Materials(
model.geometry.get_all_materials().values()
)
# Clear out OpenMC, create task lists, distribute
openmc.reset_auto_ids()
self.burnable_mats, volume, nuclides = self._get_burnable_mats()
self.local_mats = _distribute(self.burnable_mats)
# Generate map from local materials => material index
self._mat_index_map = {
lm: self.burnable_mats.index(lm) for lm in self.local_mats}
if self.prev_res is not None:
# Reload volumes into geometry
prev_results[-1].transfer_volumes(self.model)
# Store previous results in operator
# Distribute reaction rates according to those tracked
# on this process
if comm.size == 1:
self.prev_res = prev_results
else:
self.prev_res = Results()
mat_indexes = _distribute(range(len(self.burnable_mats)))
for res_obj in prev_results:
new_res = res_obj.distribute(self.local_mats, mat_indexes)
self.prev_res.append(new_res)
# Determine which nuclides have incident neutron data
self.nuclides_with_data = self._get_nuclides_with_data(cross_sections)
# Select nuclides with data that are also in the chain
self._burnable_nucs = [nuc.name for nuc in self.chain.nuclides
if nuc.name in self.nuclides_with_data]
# Extract number densities from the geometry / previous depletion run
self._extract_number(self.local_mats, volume, nuclides, self.prev_res)
# Create reaction rates array
self.reaction_rates = ReactionRates(
self.local_mats, self._burnable_nucs, self.chain.reactions)
# Get classes to assist working with tallies
if reaction_rate_mode == "direct":
self._rate_helper = DirectReactionRateHelper(
self.reaction_rates.n_nuc, self.reaction_rates.n_react)
elif reaction_rate_mode == "flux":
if reaction_rate_opts is None:
reaction_rate_opts = {}
# Ensure energy group boundaries were specified
if 'energies' not in reaction_rate_opts:
raise ValueError(
"Energy group boundaries must be specified in the "
"reaction_rate_opts argument when reaction_rate_mode is"
"set to 'flux'.")
self._rate_helper = FluxCollapseHelper(
self.reaction_rates.n_nuc,
self.reaction_rates.n_react,
**reaction_rate_opts
)
else:
raise ValueError("Invalid reaction rate mode.")
if normalization_mode == "fission-q":
self._normalization_helper = ChainFissionHelper()
elif normalization_mode == "energy-deposition":
score = "heating" if self.settings.photon_transport else "heating-local"
self._normalization_helper = EnergyScoreHelper(score)
else:
self._normalization_helper = SourceRateHelper()
# Select and create fission yield helper
fission_helper = self._fission_helpers[fission_yield_mode]
fission_yield_opts = (
{} if fission_yield_opts is None else fission_yield_opts)
self._yield_helper = fission_helper.from_operator(
self, **fission_yield_opts)
def __call__(self, vec, source_rate):
"""Runs a simulation.
Simulation will abort under the following circumstances:
1) No energy is computed using OpenMC tallies.
Parameters
----------
vec : list of numpy.ndarray
Total atoms to be used in function.
source_rate : float
Power in [W] or source rate in [neutron/sec]
Returns
-------
openmc.deplete.OperatorResult
Eigenvalue and reaction rates resulting from transport operator
"""
# Reset results in OpenMC
openmc.lib.reset()
# Update the number densities regardless of the source rate
self.number.set_density(vec)
self._update_materials()
# If the source rate is zero, return zero reaction rates without running
# a transport solve
if source_rate == 0.0:
rates = self.reaction_rates.copy()
rates.fill(0.0)
return OperatorResult(ufloat(0.0, 0.0), rates)
# Prevent OpenMC from complaining about re-creating tallies
openmc.reset_auto_ids()
# Update tally nuclides data in preparation for transport solve
nuclides = self._get_tally_nuclides()
self._rate_helper.nuclides = nuclides
self._normalization_helper.nuclides = nuclides
self._yield_helper.update_tally_nuclides(nuclides)
# Run OpenMC
openmc.lib.run()
openmc.lib.reset_timers()
# Extract results
op_result = self._unpack_tallies_and_normalize(source_rate)
return copy.deepcopy(op_result)
@staticmethod
def write_bos_data(step):
"""Write a state-point file with beginning of step data
Parameters
----------
step : int
Current depletion step including restarts
"""
openmc.lib.statepoint_write(
"openmc_simulation_n{}.h5".format(step),
write_source=False)
def _differentiate_burnable_mats(self):
"""Assign distribmats for each burnable material
"""
# Count the number of instances for each cell and material
self.geometry.determine_paths(instances_only=True)
# Extract all burnable materials which have multiple instances
distribmats = set(
[mat for mat in self.materials
if mat.depletable and mat.num_instances > 1])
for mat in distribmats:
if mat.volume is None:
raise RuntimeError("Volume not specified for depletable "
"material with ID={}.".format(mat.id))
mat.volume /= mat.num_instances
if distribmats:
# Assign distribmats to cells
for cell in self.geometry.get_all_material_cells().values():
if cell.fill in distribmats:
mat = cell.fill
cell.fill = [mat.clone()
for i in range(cell.num_instances)]
def _get_burnable_mats(self):
"""Determine depletable materials, volumes, and nuclides
Returns
-------
burnable_mats : list of str
List of burnable material IDs
volume : OrderedDict of str to float
Volume of each material in [cm^3]
nuclides : list of str
Nuclides in order of how they'll appear in the simulation.
"""
burnable_mats = set()
model_nuclides = set()
volume = OrderedDict()
self.heavy_metal = 0.0
# Iterate once through the geometry to get dictionaries
for mat in self.materials:
for nuclide in mat.get_nuclides():
model_nuclides.add(nuclide)
if mat.depletable:
burnable_mats.add(str(mat.id))
if mat.volume is None:
raise RuntimeError("Volume not specified for depletable "
"material with ID={}.".format(mat.id))
volume[str(mat.id)] = mat.volume
self.heavy_metal += mat.fissionable_mass
# Make sure there are burnable materials
if not burnable_mats:
raise RuntimeError(
"No depletable materials were found in the model.")
# Sort the sets
burnable_mats = sorted(burnable_mats, key=int)
model_nuclides = sorted(model_nuclides)
# Construct a global nuclide dictionary, burned first
nuclides = list(self.chain.nuclide_dict)
for nuc in model_nuclides:
if nuc not in nuclides:
nuclides.append(nuc)
return burnable_mats, volume, nuclides
def _extract_number(self, local_mats, volume, nuclides, prev_res=None):
"""Construct AtomNumber using geometry
Parameters
----------
local_mats : list of str
Material IDs to be managed by this process
volume : OrderedDict of str to float
Volumes for the above materials in [cm^3]
nuclides : list of str
Nuclides to be used in the simulation.
prev_res : Results, optional
Results from a previous depletion calculation
"""
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:
for mat in self.materials:
if str(mat.id) in local_mats:
self._set_number_from_mat(mat)
# Else from previous depletion results
else:
for mat in self.materials:
if str(mat.id) in local_mats:
self._set_number_from_results(mat, prev_res)
def _set_number_from_mat(self, mat):
"""Extracts material and number densities from openmc.Material
Parameters
----------
mat : openmc.Material
The material to read from
"""
mat_id = str(mat.id)
for nuclide, atom_per_bcm in mat.get_nuclide_atom_densities().items():
atom_per_cc = atom_per_bcm * 1.0e24
self.number.set_atom_density(mat_id, nuclide, atom_per_cc)
def _set_number_from_results(self, mat, prev_res):
"""Extracts material nuclides and number densities.
If the nuclide concentration's evolution is tracked, the densities come
from depletion results. Else, densities are extracted from the geometry
in the summary.
Parameters
----------
mat : openmc.Material
The material to read from
prev_res : Results
Results from a previous depletion calculation
"""
mat_id = str(mat.id)
# Get nuclide lists from geometry and depletion results
depl_nuc = prev_res[-1].nuc_to_ind
geom_nuc_densities = mat.get_nuclide_atom_densities()
# Merge lists of nuclides, with the same order for every calculation
geom_nuc_densities.update(depl_nuc)
for nuclide, atom_per_bcm in geom_nuc_densities.items():
if nuclide in depl_nuc:
concentration = prev_res.get_atoms(mat_id, nuclide)[1][-1]
volume = prev_res[-1].volume[mat_id]
atom_per_cc = concentration / volume
else:
atom_per_cc = atom_per_bcm * 1.0e24
self.number.set_atom_density(mat_id, nuclide, atom_per_cc)
def initial_condition(self):
"""Performs final setup and returns initial condition.
Returns
-------
list of numpy.ndarray
Total density for initial conditions.
"""
# Create XML files
if comm.rank == 0:
self.geometry.export_to_xml()
self.settings.export_to_xml()
self._generate_materials_xml()
# Initialize OpenMC library
comm.barrier()
if not openmc.lib.is_initialized:
openmc.lib.init(intracomm=comm)
# Generate tallies in memory
materials = [openmc.lib.materials[int(i)]
for i in self.burnable_mats]
self._rate_helper.generate_tallies(materials, self.chain.reactions)
self._normalization_helper.prepare(
self.chain.nuclides, self.reaction_rates.index_nuc)
# Tell fission yield helper what materials this process is
# responsible for
self._yield_helper.generate_tallies(
materials, tuple(sorted(self._mat_index_map.values())))
# Return number density vector
return list(self.number.get_mat_slice(np.s_[:]))
def finalize(self):
"""Finalize a depletion simulation and release resources."""
if self.cleanup_when_done:
openmc.lib.finalize()
def _update_materials(self):
"""Updates material compositions in OpenMC on all processes."""
for rank in range(comm.size):
number_i = comm.bcast(self.number, root=rank)
for mat in number_i.materials:
nuclides = []
densities = []
for nuc in number_i.nuclides:
if nuc in self.nuclides_with_data:
val = 1.0e-24 * number_i.get_atom_density(mat, nuc)
# If nuclide is zero, do not add to the problem.
if val > 0.0:
if self.round_number:
val_magnitude = np.floor(np.log10(val))
val_scaled = val / 10**val_magnitude
val_round = round(val_scaled, 8)
val = val_round * 10**val_magnitude
nuclides.append(nuc)
densities.append(val)
else:
# Only output warnings if values are significantly
# negative. CRAM does not guarantee positive values.
if val < -1.0e-21:
print("WARNING: nuclide ", nuc, " in material ", mat,
" is negative (density = ", val, " at/barn-cm)")
number_i[mat, nuc] = 0.0
# Update densities on C API side
mat_internal = openmc.lib.materials[int(mat)]
mat_internal.set_densities(nuclides, densities)
#TODO Update densities on the Python side, otherwise the
# summary.h5 file contains densities at the first time step
def _generate_materials_xml(self):
"""Creates materials.xml from self.number.
Due to uncertainty with how MPI interacts with OpenMC API, this
constructs the XML manually. The long term goal is to do this
through direct memory writing.
"""
# Sort nuclides according to order in AtomNumber object
nuclides = list(self.number.nuclides)
for mat in self.materials:
mat._nuclides.sort(key=lambda x: nuclides.index(x[0]))
self.materials.export_to_xml()
def _get_tally_nuclides(self):
"""Determine nuclides that should be tallied for reaction rates.
This method returns a list of all nuclides that have neutron data and
are listed in the depletion chain. Technically, we should tally nuclides
that may not appear in the depletion chain because we still need to get
the fission reaction rate for these nuclides in order to normalize
power, but that is left as a future exercise.
Returns
-------
list of str
Tally nuclides
"""
nuc_set = set()
# Create the set of all nuclides in the decay chain in materials marked
# for burning in which the number density is greater than zero.
for nuc in self.number.nuclides:
if nuc in self.nuclides_with_data:
if np.sum(self.number[:, nuc]) > 0.0:
nuc_set.add(nuc)
# Communicate which nuclides have nonzeros to rank 0
if comm.rank == 0:
for i in range(1, comm.size):
nuc_newset = comm.recv(source=i, tag=i)
nuc_set |= nuc_newset
else:
comm.send(nuc_set, dest=0, tag=comm.rank)
if comm.rank == 0:
# Sort nuclides in the same order as self.number
nuc_list = [nuc for nuc in self.number.nuclides
if nuc in nuc_set]
else:
nuc_list = None
# Store list of tally nuclides on each process
nuc_list = comm.bcast(nuc_list)
return [nuc for nuc in nuc_list if nuc in self.chain]
def _unpack_tallies_and_normalize(self, source_rate):
"""Unpack tallies from OpenMC and return an operator result
This method uses OpenMC's C API bindings to determine the k-effective
value and reaction rates from the simulation. The reaction rates are
normalized by a helper class depending on the method being used.
Parameters
----------
source_rate : float
Power in [W] or source rate in [neutron/sec]
Returns
-------
openmc.deplete.OperatorResult
Eigenvalue and reaction rates resulting from transport operator
"""
rates = self.reaction_rates
rates.fill(0.0)
# Get k and uncertainty
keff = ufloat(*openmc.lib.keff())
# Extract tally bins
nuclides = self._rate_helper.nuclides
# Form fast map
nuc_ind = [rates.index_nuc[nuc] for nuc in nuclides]
react_ind = [rates.index_rx[react] for react in self.chain.reactions]
# Keep track of energy produced from all reactions in eV per source
# particle
self._normalization_helper.reset()
self._yield_helper.unpack()
# Store fission yield dictionaries
fission_yields = []
# Create arrays to store fission Q values, reaction rates, and nuclide
# numbers, zeroed out in material iteration
number = np.empty(rates.n_nuc)
fission_ind = rates.index_rx.get("fission")
# Extract results
for i, mat in enumerate(self.local_mats):
# Get tally index
mat_index = self._mat_index_map[mat]
# Zero out reaction rates and nuclide numbers
number.fill(0.0)
# Get new number densities
for nuc, i_nuc_results in zip(nuclides, nuc_ind):
number[i_nuc_results] = self.number[mat, nuc]
tally_rates = self._rate_helper.get_material_rates(
mat_index, nuc_ind, react_ind)
# Compute fission yields for this material
fission_yields.append(self._yield_helper.weighted_yields(i))
# Accumulate energy from fission
if fission_ind is not None:
self._normalization_helper.update(tally_rates[:, fission_ind])
# Divide by total number and store
rates[i] = self._rate_helper.divide_by_adens(number)
# Scale reaction rates to obtain units of reactions/sec
rates *= self._normalization_helper.factor(source_rate)
# Store new fission yields on the chain
self.chain.fission_yields = fission_yields
return OperatorResult(keff, rates)
def _get_nuclides_with_data(self, cross_sections):
"""Loads cross_sections.xml file to find nuclides with neutron data"""
nuclides = set()
data_lib = DataLibrary.from_xml(cross_sections)
for library in data_lib.libraries:
if library['type'] != 'neutron':
continue
for name in library['materials']:
if name not in nuclides:
nuclides.add(name)
return nuclides
def get_results_info(self):
"""Returns volume list, material lists, and nuc lists.
Returns
-------
volume : dict of str float
Volumes corresponding to materials in full_burn_dict
nuc_list : list of str
A list of all nuclide names. Used for sorting the simulation.
burn_list : list of int
A list of all material IDs to be burned. Used for sorting the simulation.
full_burn_list : list
List of all burnable material IDs
"""
nuc_list = self.number.burnable_nuclides
burn_list = self.local_mats
volume = {}
for i, mat in enumerate(burn_list):
volume[mat] = self.number.volume[i]
# Combine volume dictionaries across processes
volume_list = comm.allgather(volume)
volume = {k: v for d in volume_list for k, v in d.items()}
return volume, nuc_list, burn_list, self.burnable_mats

View file

@ -16,6 +16,19 @@ __all__ = ["Results", "ResultsList"]
def _get_time_as(seconds, units):
"""Converts the time in seconds to time in different units
Parameters
----------
seconds : float
The time to convert expressed in seconds
units : {"s", "min", "h", "d", "a"}
The units to convert time into. Available options are seconds ``"s"``,
minutes ``"min"``, hours ``"h"`` days ``"d"``, Julian years ``"a"``
"""
if units == "a":
return seconds / (60 * 60 * 24 * 365.25) # 365.25 due to the leap year
if units == "d":
return seconds / (60 * 60 * 24)
elif units == "h":
@ -81,9 +94,9 @@ class Results(list):
.. note::
Initial values for some isotopes that do not appear in
initial concentrations may be non-zero, depending on the
value of the :attr:`openmc.deplete.Operator.dilute_initial`
attribute. The :class:`openmc.deplete.Operator` class adds isotopes
according to this setting, which can be set to zero.
value of the :attr:`openmc.deplete.CoupledOperator.dilute_initial`
attribute. The :class:`openmc.deplete.CoupledOperator` class adds
isotopes according to this setting, which can be set to zero.
Parameters
----------
@ -95,9 +108,10 @@ class Results(list):
Units for the returned concentration. Default is ``"atoms"``
.. versionadded:: 0.12
time_units : {"s", "min", "h", "d"}, optional
time_units : {"s", "min", "h", "d", "a"}, optional
Units for the returned time array. Default is ``"s"`` to
return the value in seconds.
return the value in seconds. Other options are minutes ``"min"``,
hours ``"h"``, days ``"d"``, and Julian years ``"a"``.
.. versionadded:: 0.12
@ -109,7 +123,7 @@ class Results(list):
Concentration of specified nuclide in units of ``nuc_units``
"""
cv.check_value("time_units", time_units, {"s", "d", "min", "h"})
cv.check_value("time_units", time_units, {"s", "d", "min", "h", "a"})
cv.check_value("nuc_units", nuc_units,
{"atoms", "atom/b-cm", "atom/cm3"})
@ -145,8 +159,8 @@ class Results(list):
Initial values for some isotopes that do not appear in
initial concentrations may be non-zero, depending on the
value of :class:`openmc.deplete.Operator` ``dilute_initial``
The :class:`openmc.deplete.Operator` adds isotopes according
value of :class:`openmc.deplete.CoupledOperator` ``dilute_initial``
The :class:`openmc.deplete.CoupledOperator` adds isotopes according
to this setting, which can be set to zero.
Parameters
@ -190,8 +204,10 @@ class Results(list):
Parameters
----------
time_units : {"s", "d", "h", "min"}, optional
Desired units for the times array
time_units : {"s", "d", "min", "h", "a"}, optional
Desired units for the times array. Options are seconds ``"s"``,
minutes ``"min"``, hours ``"h"``, days ``"d"``, and Julian years
``"a"``.
Returns
-------
@ -203,7 +219,7 @@ class Results(list):
1 contains the associated uncertainty
"""
cv.check_value("time_units", time_units, {"s", "d", "min", "h"})
cv.check_value("time_units", time_units, {"s", "d", "min", "h", "a"})
times = np.empty_like(self, dtype=float)
eigenvalues = np.empty((len(self), 2), dtype=float)
@ -257,9 +273,10 @@ class Results(list):
Parameters
----------
time_units : {"s", "d", "h", "min"}, optional
time_units : {"s", "d", "min", "h", "a"}, optional
Return the vector in these units. Default is to
convert to days
convert to days ``"d"``. Other options are seconds ``"s"``, minutes
``"min"``, hours ``"h"``, days ``"d"``, and Julian years ``"a"``.
Returns
-------
@ -267,7 +284,7 @@ class Results(list):
1-D vector of time points
"""
cv.check_value("time_units", time_units, {"s", "d", "min", "h"})
cv.check_value("time_units", time_units, {"s", "d", "min", "h", "a"})
times = np.fromiter(
(r.time[0] for r in self),
@ -296,8 +313,9 @@ class Results(list):
----------
time : float
Desired point in time
time_units : {"s", "d", "min", "h"}, optional
Units on ``time``. Default: days
time_units : {"s", "d", "min", "h", "a"}, optional
Units on ``time``. Default: days ``"d"``. Other options are seconds
``"s"``, minutes ``"min"``, hours ``"h"`` and Julian years ``"a"``.
atol : float, optional
Absolute tolerance (in ``time_units``) if ``time`` is not
found.
@ -399,7 +417,16 @@ class Results(list):
mat_id = str(mat.id)
if mat_id in result.mat_to_ind:
mat.volume = result.volume[mat_id]
# Change density of all nuclides in material to atom/b-cm
atoms_per_barn_cm = mat.get_nuclide_atom_densities()
for nuc, value in atoms_per_barn_cm.items():
mat.remove_nuclide(nuc)
mat.add_nuclide(nuc, value)
mat.set_density('sum')
# For nuclides in chain that have cross sections, replace
# density in original material with new density from results
for nuc in result.nuc_to_ind:
if nuc not in available_cross_sections:
continue

View file

@ -462,7 +462,7 @@ class StepResult:
Parameters
----------
op : openmc.deplete.TransportOperator
op : openmc.deplete.abc.TransportOperator
The operator used to generate these results.
x : list of list of numpy.array
The prior x vectors. Indexed [i][cell] using the above equation.
@ -495,7 +495,13 @@ class StepResult:
for mat_i in range(n_mat):
results[i, mat_i, :] = x[i][mat_i]
results.k = [(r.k.nominal_value, r.k.std_dev) for r in op_results]
ks = []
for r in op_results:
if isinstance(r.k, type(None)):
ks += [(None, None)]
else:
ks += [(r.k.nominal_value, r.k.std_dev)]
results.k = ks
results.rates = [r.rates for r in op_results]
results.time = t
results.source_rate = source_rate

View file

@ -1324,6 +1324,18 @@ class EnergyFilter(RealFilter):
cv.check_greater_than('filter value', v0, 0., equality=True)
cv.check_greater_than('filter value', v1, 0., equality=True)
@property
def lethargy_bin_width(self):
"""Calculates the base 10 log width of energy bins which is useful when
plotting the normalized flux.
Returns
-------
numpy.array
Array of bin widths
"""
return np.log10(self.bins[:, 1]/self.bins[:, 0])
@classmethod
def from_group_structure(cls, group_structure):
"""Construct an EnergyFilter instance from a standard group structure.

View file

@ -34,7 +34,9 @@ class Material(IDManagerMixin):
To create a material, one should create an instance of this class, add
nuclides or elements with :meth:`Material.add_nuclide` or
:meth:`Material.add_element`, respectively, and set the total material
density with :meth:`Material.set_density()`. The material can then be
density with :meth:`Material.set_density()`. Alternatively, you can
use :meth:`Material.add_components()` to pass a dictionary
containing all the component information. The material can then be
assigned to a cell using the :attr:`Cell.fill` attribute.
Parameters
@ -89,10 +91,6 @@ class Material(IDManagerMixin):
fissionable_mass : float
Mass of fissionable nuclides in the material in [g]. Requires that the
:attr:`volume` attribute is set.
activity : float
Activity of the material in [Bq]. Requires that the :attr:`volume`
attribute is set.
"""
next_id = 1
@ -148,11 +146,6 @@ class Material(IDManagerMixin):
return string
@property
def activity(self):
"""Returns the total activity of the material in Becquerels."""
return sum(self.get_nuclide_activity().values())
@property
def name(self):
return self._name
@ -403,6 +396,54 @@ class Material(IDManagerMixin):
self._nuclides.append(NuclideTuple(nuclide, percent, percent_type))
def add_components(self, components: dict, percent_type: str = 'ao'):
""" Add multiple elements or nuclides to a material
.. versionadded:: 0.13.1
Parameters
----------
components : dict of str to float or dict
Dictionary mapping element or nuclide names to their atom or weight
percent. To specify enrichment of an element, the entry of
``components`` for that element must instead be a dictionary
containing the keyword arguments as well as a value for
``'percent'``
percent_type : {'ao', 'wo'}
'ao' for atom percent and 'wo' for weight percent
Examples
--------
>>> mat = openmc.Material()
>>> components = {'Li': {'percent': 1.0,
>>> 'enrichment': 60.0,
>>> 'enrichment_target': 'Li7'},
>>> 'Fl': 1.0,
>>> 'Be6': 0.5}
>>> mat.add_components(components)
"""
for component, params in components.items():
cv.check_type('component', component, str)
if isinstance(params, float):
params = {'percent': params}
else:
cv.check_type('params', params, dict)
if 'percent' not in params:
raise ValueError("An entry in the dictionary does not have "
"a required key: 'percent'")
params['percent_type'] = percent_type
## check if nuclide
if str.isdigit(component[-1]):
self.add_nuclide(component, **params)
else: # is element
kwargs = params
self.add_element(component, **params)
def remove_nuclide(self, nuclide: str):
"""Remove a nuclide from the material
@ -811,7 +852,7 @@ class Material(IDManagerMixin):
elif self.density_units == 'atom/b-cm':
density = self.density
elif self.density_units == 'atom/cm3' or self.density_units == 'atom/cc':
density = 1.E-24 * self.density
density = 1.e-24 * self.density
# For ease of processing split out nuc, nuc_density,
# and nuc_density_type into separate arrays
@ -847,7 +888,7 @@ class Material(IDManagerMixin):
# Convert the mass density to an atom density
if not density_in_atom:
density = -density / self.average_molar_mass * 1.E-24 \
density = -density / self.average_molar_mass * 1.e-24 \
* openmc.data.AVOGADRO
nuc_densities = density * nuc_densities
@ -858,22 +899,46 @@ class Material(IDManagerMixin):
return nuclides
def get_nuclide_activity(self):
"""Return activity in [Bq] for each nuclide in the material
def get_activity(self, units: str = 'Bq/cm3', by_nuclide: bool = False):
"""Returns the activity of the material or for each nuclide in the
material in units of [Bq], [Bq/g] or [Bq/cm3].
.. versionadded:: 0.13.1
Parameters
----------
units : {'Bq', 'Bq/g', 'Bq/cm3'}
Specifies the type of activity to return, options include total
activity [Bq], specific [Bq/g] or volumetric activity [Bq/cm3].
Default is volumetric activity [Bq/cm3].
by_nuclide : bool
Specifies if the activity should be returned for the material as a
whole or per nuclide. Default is False.
Returns
-------
dict
Dictionary whose keys are nuclide names and values are activity in
[Bq].
Union[dict, float]
If by_nuclide is True then a dictionary whose keys are nuclide
names and values are activity is returned. Otherwise the activity
of the material is returned as a float.
"""
cv.check_value('units', units, {'Bq', 'Bq/g', 'Bq/cm3'})
cv.check_type('by_nuclide', by_nuclide, bool)
if units == 'Bq':
multiplier = self.volume
elif units == 'Bq/cm3':
multiplier = 1
elif units == 'Bq/g':
multiplier = 1.0 / self.get_mass_density()
activity = {}
for nuclide, atoms in self.get_nuclide_atoms().items():
for nuclide, atoms_per_bcm in self.get_nuclide_atom_densities().items():
inv_seconds = openmc.data.decay_constant(nuclide)
activity[nuclide] = inv_seconds * atoms
return activity
activity[nuclide] = inv_seconds * 1e24 * atoms_per_bcm * multiplier
return activity if by_nuclide else sum(activity.values())
def get_nuclide_atoms(self):
"""Return number of atoms of each nuclide in the material

View file

@ -2,6 +2,7 @@ from abc import ABC, abstractmethod
from collections.abc import Iterable
from math import pi
from numbers import Real, Integral
from pathlib import Path
import warnings
from xml.etree import ElementTree as ET
@ -1440,7 +1441,7 @@ class UnstructuredMesh(MeshBase):
Parameters
----------
filename : str
filename : str or pathlib.Path
Location of the unstructured mesh file
library : {'moab', 'libmesh'}
Mesh library used for the unstructured mesh tally
@ -1468,20 +1469,39 @@ class UnstructuredMesh(MeshBase):
be generated for this mesh
volumes : Iterable of float
Volumes of the unstructured mesh elements
centroids : numpy.ndarray
Centroids of the mesh elements with array shape (n_elements, 3)
vertices : numpy.ndarray
Coordinates of the mesh vertices with array shape (n_elements, 3)
.. versionadded:: 0.13.1
connectivity : numpy.ndarray
Connectivity of the elements with array shape (n_elements, 8)
.. versionadded:: 0.13.1
element_types : Iterable of integers
Mesh element types
.. versionadded:: 0.13.1
total_volume : float
Volume of the unstructured mesh in total
centroids : Iterable of tuple
An iterable of element centroid coordinates, e.g. [(0.0, 0.0, 0.0),
(1.0, 1.0, 1.0), ...]
"""
_UNSUPPORTED_ELEM = -1
_LINEAR_TET = 0
_LINEAR_HEX = 1
def __init__(self, filename, library, mesh_id=None, name='',
length_multiplier=1.0):
super().__init__(mesh_id, name)
self.filename = filename
self._volumes = None
self._centroids = None
self._n_elements = None
self._conectivity = None
self._vertices = None
self.library = library
self._output = True
self._output = False
self.length_multiplier = length_multiplier
@property
@ -1490,7 +1510,7 @@ class UnstructuredMesh(MeshBase):
@filename.setter
def filename(self, filename):
cv.check_type('Unstructured Mesh filename', filename, str)
cv.check_type('Unstructured Mesh filename', filename, (str, Path))
self._filename = filename
@property
@ -1542,23 +1562,33 @@ class UnstructuredMesh(MeshBase):
def total_volume(self):
return np.sum(self.volumes)
@property
def vertices(self):
return self._vertices
@property
def connectivity(self):
return self._connectivity
@property
def element_types(self):
return self._element_types
@property
def centroids(self):
return self._centroids
return np.array([self.centroid(i) for i in range(self.n_elements)])
@property
def n_elements(self):
if self._centroids is None:
if self._n_elements is None:
raise RuntimeError("No information about this mesh has "
"been loaded from a statepoint file.")
return len(self._centroids)
return self._n_elements
@centroids.setter
def centroids(self, centroids):
cv.check_type("Unstructured mesh centroids", centroids,
Iterable, Real)
self._centroids = centroids
@n_elements.setter
def n_elements(self, val):
cv.check_type('Number of elements', val, Integral)
self._n_elements = val
@property
def length_multiplier(self):
@ -1573,29 +1603,46 @@ class UnstructuredMesh(MeshBase):
@property
def dimension(self):
return self.n_elements
return (self.n_elements,)
@property
def n_dimension(self):
return 3
@property
def vertices(self):
raise NotImplementedError("Vertices for UnstructuredMesh objects are "
"not yet available")
def __repr__(self):
string = super().__repr__()
string += '{: <16}=\t{}\n'.format('\tFilename', self.filename)
string += '{: <16}=\t{}\n'.format('\tMesh Library', self.mesh_lib)
string += '{: <16}=\t{}\n'.format('\tMesh Library', self.library)
if self.length_multiplier != 1.0:
string += '{: <16}=\t{}\n'.format('\tLength multiplier',
self.length_multiplier)
return string
def write_data_to_vtk(self, filename, datasets, volume_normalization=True):
"""Map data to the unstructured mesh element centroids
to create a VTK point-cloud dataset.
def centroid(self, bin):
"""Return the vertex averaged centroid of an element
Parameters
----------
bin : int
Bin ID for the returned centroid
Returns
-------
numpy.ndarray
x, y, z values of the element centroid
"""
conn = self.connectivity[bin]
# remove invalid connectivity values
conn = conn[conn >= 0]
coords = self.vertices[conn]
return coords.mean(axis=0)
def write_vtk_mesh(self, **kwargs):
"""Map data to unstructured VTK mesh elements.
.. deprecated:: 0.13
Use :func:`UnstructuredMesh.write_data_to_vtk` instead.
Parameters
----------
@ -1607,83 +1654,103 @@ class UnstructuredMesh(MeshBase):
volume_normalization : bool
Whether or not to normalize the data by the
volume of the mesh elements
Raises
------
RuntimeError
when the size of a dataset doesn't match the number of cells
"""
warnings.warn(
"The 'UnstructuredMesh.write_vtk_mesh' method has been renamed "
"to 'write_data_to_vtk' and will be removed in a future version "
" of OpenMC.", FutureWarning
)
self.write_data_to_vtk(**kwargs)
def write_data_to_vtk(self, filename=None, datasets=None, volume_normalization=True):
"""Map data to unstructured VTK mesh elements.
Parameters
----------
filename : str or pathlib.Path
Name of the VTK file to write
datasets : dict
Dictionary whose keys are the data labels
and values are numpy appropriately sized arrays
of the data
volume_normalization : bool
Whether or not to normalize the data by the
volume of the mesh elements
"""
import vtk
from vtk.util import numpy_support as vtk_npsup
from vtk.util import numpy_support as nps
if self.centroids is None:
raise RuntimeError("No centroid information is present on this "
"unstructured mesh. Please load this "
"information from a relevant statepoint file.")
if self.connectivity is None or self.vertices is None:
raise RuntimeError('This mesh has not been '
'loaded from a statepoint file.')
if self.volumes is None and volume_normalization:
raise RuntimeError("No volume data is present on this "
"unstructured mesh. Please load the "
" mesh information from a statepoint file.")
if filename is None:
filename = f'mesh_{self.id}.vtk'
# check that the data sets are appropriately sized
errmsg = "The size of the dataset {} should be equal to the number of cells"
for label, dataset in datasets.items():
if isinstance(dataset, np.ndarray):
if not dataset.size == self.dimension[0] * self.dimension[1]* self.dimension[2]:
raise RuntimeError(errmsg.format(label))
writer = vtk.vtkUnstructuredGridWriter()
writer.SetFileName(str(filename))
grid = vtk.vtkUnstructuredGrid()
vtk_pnts = vtk.vtkPoints()
vtk_pnts.SetData(nps.numpy_to_vtk(self.vertices))
grid.SetPoints(vtk_pnts)
n_skipped = 0
elems = []
for elem_type, conn in zip(self.element_types, self.connectivity):
if elem_type == self._LINEAR_TET:
elem = vtk.vtkTetra()
elif elem_type == self._LINEAR_HEX:
elem = vtk.vtkHexahedron()
elif elem_type == self._UNSUPPORTED_ELEM:
n_skipped += 1
else:
if len(dataset) == self.dimension[0] * self.dimension[1]* self.dimension[2]:
raise RuntimeError(errmsg.format(label))
cv.check_type('label', label, str)
raise RuntimeError(f'Invalid element type {elem_type} found')
for i, c in enumerate(conn):
if c == -1:
break
elem.GetPointIds().SetId(i, c)
elems.append(elem)
# create data arrays for the cells/points
cell_dim = 1
vertices = vtk.vtkCellArray()
points = vtk.vtkPoints()
if n_skipped > 0:
warnings.warn(f'{n_skipped} elements were not written because '
'they are not of type linear tet/hex')
for centroid in self.centroids:
# create a point for each centroid
point_id = points.InsertNextPoint(centroid * self.length_multiplier)
# create a cell of type "Vertex" for each point
cell_id = vertices.InsertNextCell(cell_dim, (point_id,))
for elem in elems:
grid.InsertNextCell(elem.GetCellType(), elem.GetPointIds())
# create a VTK data object
poly_data = vtk.vtkPolyData()
poly_data.SetPoints(points)
poly_data.SetVerts(vertices)
# strange VTK nuance:
# data must be held in some container
# until the vtk file is written
data_holder = []
# create VTK arrays for each of
# the data sets
for label, dataset in datasets.items():
dataset = np.asarray(dataset).flatten()
# check that datasets are the correct size
datasets_out = []
if datasets is not None:
for name, data in datasets.items():
if data.shape != self.dimension:
raise ValueError(f'Cannot apply dataset "{name}" with '
f'shape {data.shape} to mesh {self.id} '
f'with dimensions {self.dimension}')
if volume_normalization:
dataset /= self.volumes.flatten()
for name, data in datasets.items():
if np.issubdtype(data.dtype, np.integer):
warnings.warn(f'Integer data set "{name}" will '
'not be volume-normalized.')
continue
data /= self.volumes
array = vtk.vtkDoubleArray()
array.SetName(label)
array.SetNumberOfComponents(1)
array.SetArray(vtk_npsup.numpy_to_vtk(dataset),
dataset.size,
True)
# add data to the mesh
for name, data in datasets.items():
datasets_out.append(data)
arr = vtk.vtkDoubleArray()
arr.SetName(name)
arr.SetNumberOfTuples(data.size)
data_holder.append(dataset)
poly_data.GetPointData().AddArray(array)
for i in range(data.size):
arr.SetTuple1(i, data.flat[i])
grid.GetCellData().AddArray(arr)
# set filename
if not filename.endswith(".vtk"):
filename += ".vtk"
writer.SetInputData(grid)
writer = vtk.vtkGenericDataObjectWriter()
writer.SetFileName(filename)
writer.SetInputData(poly_data)
writer.Write()
@classmethod
@ -1694,10 +1761,14 @@ class UnstructuredMesh(MeshBase):
mesh = cls(filename, library, mesh_id=mesh_id)
vol_data = group['volumes'][()]
centroids = group['centroids'][()]
mesh.volumes = np.reshape(vol_data, (vol_data.shape[0],))
mesh.centroids = np.reshape(centroids, (vol_data.shape[0], 3))
mesh.size = mesh.volumes.size
mesh.n_elements = mesh.volumes.size
vertices = group['vertices'][()]
mesh._vertices = vertices.reshape((-1, 3))
connectivity = group['connectivity'][()]
mesh._connectivity = connectivity.reshape((-1, 8))
mesh._element_types = group['element_types'][()]
if 'length_multiplier' in group:
mesh.length_multiplier = group['length_multiplier'][()]
@ -1719,7 +1790,7 @@ class UnstructuredMesh(MeshBase):
element.set("type", "unstructured")
element.set("library", self._library)
subelement = ET.SubElement(element, "filename")
subelement.text = self.filename
subelement.text = str(self.filename)
if self._length_multiplier != 1.0:
element.set("length_multiplier", str(self.length_multiplier))

File diff suppressed because it is too large Load diff

View file

@ -14,8 +14,11 @@ class EqualityMixin:
def __eq__(self, other):
if isinstance(other, type(self)):
for key, value in self.__dict__.items():
if not np.array_equal(value, other.__dict__.get(key)):
return False
if isinstance(value, np.ndarray):
if not np.array_equal(value, other.__dict__.get(key)):
return False
else:
return value == other.__dict__.get(key)
else:
return False

View file

@ -451,10 +451,10 @@ class RectangularParallelepiped(CompositeSurface):
self.zmax = openmc.ZPlane(z0=zmax, **kwargs)
def __neg__(self):
return +self.xmin & -self.xmax & +self.ymin & -self.ymax & +self.zmin & -self.zmax
return -self.xmax & +self.xmin & -self.ymax & +self.ymin & -self.zmax & +self.zmin
def __pos__(self):
return -self.xmin | +self.xmax | -self.ymin | +self.ymax | -self.zmin | +self.zmax
return +self.xmax | -self.xmin | +self.ymax | -self.ymin | +self.zmax | -self.zmin
class XConeOneSided(CompositeSurface):

View file

@ -259,13 +259,16 @@ class Region(ABC):
clone[:] = [n.clone(memo) for n in self]
return clone
def translate(self, vector, memo=None):
def translate(self, vector, inplace=False, memo=None):
"""Translate region in given direction
Parameters
----------
vector : iterable of float
Direction in which region should be translated
inplace : bool
Whether or not to return a region based on new surfaces or one based
on the original surfaces that have been modified.
memo : dict or None
Dictionary used for memoization. This parameter is used internally
and should not be specified by the user.
@ -279,7 +282,7 @@ class Region(ABC):
if memo is None:
memo = {}
return type(self)(n.translate(vector, memo) for n in self)
return type(self)(n.translate(vector, inplace, memo) for n in self)
def rotate(self, rotation, pivot=(0., 0., 0.), order='xyz', inplace=False,
memo=None):
@ -308,7 +311,7 @@ class Region(ABC):
:math:`\psi` about z. This corresponds to an x-y-z extrinsic
rotation as well as a z-y'-x'' intrinsic rotation using Tait-Bryan
angles :math:`(\phi, \theta, \psi)`.
inplace : boolean
inplace : bool
Whether or not to return a new instance of Surface or to modify the
coefficients of this Surface in place. Defaults to False.
memo : dict or None
@ -622,10 +625,10 @@ class Complement(Region):
clone.node = self.node.clone(memo)
return clone
def translate(self, vector, memo=None):
def translate(self, vector, inplace=False, memo=None):
if memo is None:
memo = {}
return type(self)(self.node.translate(vector, memo))
return type(self)(self.node.translate(vector, inplace, memo))
def rotate(self, rotation, pivot=(0., 0., 0.), order='xyz', inplace=False,
memo=None):

View file

@ -29,6 +29,11 @@ _RES_SCAT_METHODS = ['dbrc', 'rvs']
class Settings:
"""Settings used for an OpenMC simulation.
Parameters
----------
**kwargs : dict, optional
Any keyword arguments are used to set attributes on the instance.
Attributes
----------
batches : int
@ -222,7 +227,7 @@ class Settings:
Indicate whether to write the initial source distribution to file
"""
def __init__(self):
def __init__(self, **kwargs):
self._run_mode = RunMode.EIGENVALUE
self._batches = None
self._generations_per_batch = None
@ -297,6 +302,9 @@ class Settings:
self._max_splits = None
self._max_tracks = None
for key, value in kwargs.items():
setattr(self, key, value)
@property
def run_mode(self) -> str:
return self._run_mode.value

View file

@ -11,7 +11,7 @@ from uncertainties import ufloat
import openmc
import openmc.checkvalue as cv
_VERSION_STATEPOINT = 17
_VERSION_STATEPOINT = 18
class StatePoint:

View file

@ -1,6 +1,7 @@
from abc import ABC, abstractmethod
from collections import defaultdict
from collections.abc import Iterable
from copy import deepcopy
from numbers import Real
from xml.etree import ElementTree as ET
@ -78,6 +79,18 @@ class Univariate(EqualityMixin, ABC):
"""
pass
def integral(self):
"""Return integral of distribution
.. versionadded:: 0.13.1
Returns
-------
float
Integral of distribution
"""
return 1.0
class Discrete(Univariate):
"""Distribution characterized by a probability mass function.
@ -95,9 +108,9 @@ class Discrete(Univariate):
Attributes
----------
x : Iterable of float
x : numpy.ndarray
Values of the random variable
p : Iterable of float
p : numpy.ndarray
Discrete probability for each value
"""
@ -122,7 +135,7 @@ class Discrete(Univariate):
if isinstance(x, Real):
x = [x]
cv.check_type('discrete values', x, Iterable, Real)
self._x = x
self._x = np.array(x, dtype=float)
@p.setter
def p(self, p):
@ -131,14 +144,15 @@ class Discrete(Univariate):
cv.check_type('discrete probabilities', p, Iterable, Real)
for pk in p:
cv.check_greater_than('discrete probability', pk, 0.0, True)
self._p = p
self._p = np.array(p, dtype=float)
def cdf(self):
return np.insert(np.cumsum(self.p), 0, 0.0)
def sample(self, n_samples=1, seed=None):
np.random.seed(seed)
return np.random.choice(self.x, n_samples, p=self.p)
p = self.p / self.p.sum()
return np.random.choice(self.x, n_samples, p=p)
def normalize(self):
"""Normalize the probabilities stored on the distribution"""
@ -220,6 +234,18 @@ class Discrete(Univariate):
p_arr = np.array([p_merged[x] for x in x_arr])
return cls(x_arr, p_arr)
def integral(self):
"""Return integral of distribution
.. versionadded:: 0.13.1
Returns
-------
float
Integral of discrete distribution
"""
return np.sum(self.p)
class Uniform(Univariate):
"""Distribution with constant probability over a finite interval [a,b]
@ -825,9 +851,9 @@ class Tabular(Univariate):
Attributes
----------
x : Iterable of float
x : numpy.ndarray
Tabulated values of the random variable
p : Iterable of float
p : numpy.ndarray
Tabulated probabilities
interpolation : {'histogram', 'linear-linear', 'linear-log', 'log-linear', 'log-log'}, optional
Indicate whether the density function is constant between tabulated
@ -860,7 +886,7 @@ class Tabular(Univariate):
@x.setter
def x(self, x):
cv.check_type('tabulated values', x, Iterable, Real)
self._x = x
self._x = np.array(x, dtype=float)
@p.setter
def p(self, p):
@ -868,7 +894,7 @@ class Tabular(Univariate):
if not self._ignore_negative:
for pk in p:
cv.check_greater_than('tabulated probability', pk, 0.0, True)
self._p = p
self._p = np.array(p, dtype=float)
@interpolation.setter
def interpolation(self, interpolation):
@ -881,8 +907,8 @@ class Tabular(Univariate):
'distributions using histogram or '
'linear-linear interpolation')
c = np.zeros_like(self.x)
x = np.asarray(self.x)
p = np.asarray(self.p)
x = self.x
p = self.p
if self.interpolation == 'histogram':
c[1:] = p[:-1] * np.diff(x)
@ -922,7 +948,7 @@ class Tabular(Univariate):
def normalize(self):
"""Normalize the probabilities stored on the distribution"""
self.p = np.asarray(self.p) / self.cdf().max()
self.p /= self.cdf().max()
def sample(self, n_samples=1, seed=None):
if not self.interpolation in ('histogram', 'linear-linear'):
@ -931,10 +957,11 @@ class Tabular(Univariate):
'linear-linear interpolation')
np.random.seed(seed)
xi = np.random.rand(n_samples)
cdf = self.cdf()
cdf /= cdf.max()
# always use normalized probabilities when sampling
cdf = self.cdf()
p = self.p / cdf.max()
cdf /= cdf.max()
# get CDF bins that are above the
# sampled values
@ -949,7 +976,7 @@ class Tabular(Univariate):
# the random number is less than the next cdf
# entry
x_i = self.x[cdf_idx]
p_i = self.p[cdf_idx]
p_i = p[cdf_idx]
if self.interpolation == 'histogram':
# mask where probability is greater than zero
@ -967,7 +994,7 @@ class Tabular(Univariate):
# get variable and probability values for the
# next entry
x_i1 = self.x[cdf_idx + 1]
p_i1 = self.p[cdf_idx + 1]
p_i1 = p[cdf_idx + 1]
# compute slope between entries
m = (p_i1 - p_i) / (x_i1 - x_i)
# set values for zero slope
@ -1027,6 +1054,24 @@ class Tabular(Univariate):
p = params[len(params)//2:]
return cls(x, p, interpolation)
def integral(self):
"""Return integral of distribution
.. versionadded: 0.13.1
Returns
-------
float
Integral of tabular distrbution
"""
if self.interpolation == 'histogram':
return np.sum(np.diff(self.x) * self.p[:-1])
elif self.interpolation == 'linear-linear':
return np.trapz(self.p, self.x)
else:
raise NotImplementedError(
f'integral() not supported for {self.inteprolation} interpolation')
class Legendre(Univariate):
r"""Probability density given by a Legendre polynomial expansion
@ -1135,9 +1180,18 @@ class Mixture(Univariate):
def sample(self, n_samples=1, seed=None):
np.random.seed(seed)
idx = np.random.choice(self.distribution, n_samples, p=self.probability)
out = np.zeros_like(idx)
# Get probability of each distribution accounting for its intensity
p = np.array([prob*dist.integral() for prob, dist in
zip(self.probability, self.distribution)])
p /= p.sum()
# Sample from the distributions
idx = np.random.choice(range(len(self.distribution)),
n_samples, p=p)
# Draw samples from the distributions sampled above
out = np.empty_like(idx, dtype=float)
for i in np.unique(idx):
n_dist_samples = np.count_nonzero(idx == i)
samples = self.distribution[i].sample(n_dist_samples)
@ -1199,3 +1253,68 @@ class Mixture(Univariate):
distribution.append(Univariate.from_xml_element(pair.find("dist")))
return cls(probability, distribution)
def integral(self):
"""Return integral of the distribution
.. versionadded:: 0.13.1
Returns
-------
float
Integral of the distribution
"""
return sum([
p*dist.integral()
for p, dist in zip(self.probability, self.distribution)
])
def combine_distributions(dists, probs):
"""Combine distributions with specified probabilities
This function can be used to combine multiple instances of
:class:`~openmc.stats.Discrete` and `~openmc.stats.Tabular`. Multiple
discrete distributions are merged into a single distribution and the
remainder of the distributions are put into a :class:`~openmc.stats.Mixture`
distribution.
.. versionadded:: 0.13.1
Parameters
----------
dists : iterable of openmc.stats.Univariate
Distributions to combine
probs : iterable of float
Probability (or intensity) of each distribution
"""
# Get copy of distribution list so as not to modify the argument
dist_list = deepcopy(dists)
# Get list of discrete/continuous distribution indices
discrete_index = [i for i, d in enumerate(dist_list) if isinstance(d, Discrete)]
cont_index = [i for i, d in enumerate(dist_list) if isinstance(d, Tabular)]
# Apply probabilites to continuous distributions
for i in cont_index:
dist = dist_list[i]
dist.p *= probs[i]
if discrete_index:
# Create combined discrete distribution
dist_discrete = [dist_list[i] for i in discrete_index]
discrete_probs = [probs[i] for i in discrete_index]
combined_dist = Discrete.merge(dist_discrete, discrete_probs)
# Replace multiple discrete distributions with merged
for idx in reversed(discrete_index):
dist_list.pop(idx)
dist_list.append(combined_dist)
# Combine discrete and continuous if present
if len(dist_list) > 1:
probs = [1.0]*len(dist_list)
dist_list[:] = [Mixture(probs, dist_list.copy())]
return dist_list[0]

View file

@ -336,9 +336,9 @@ class Surface(IDManagerMixin, ABC):
----------
vector : iterable of float
Direction in which surface should be translated
inplace : boolean
inplace : bool
Whether or not to return a new instance of this Surface or to
modify the coefficients of this Surface. Defaults to False
modify the coefficients of this Surface.
Returns
-------
@ -374,7 +374,7 @@ class Surface(IDManagerMixin, ABC):
:math:`\psi` about z. This corresponds to an x-y-z extrinsic
rotation as well as a z-y'-x'' intrinsic rotation using Tait-Bryan
angles :math:`(\phi, \theta, \psi)`.
inplace : boolean
inplace : bool
Whether or not to return a new instance of Surface or to modify the
coefficients of this Surface in place. Defaults to False.
@ -568,9 +568,9 @@ class PlaneMixin:
----------
vector : iterable of float
Direction in which surface should be translated
inplace : boolean
inplace : bool
Whether or not to return a new instance of a Plane or to modify the
coefficients of this plane. Defaults to False
coefficients of this plane.
Returns
-------
@ -1012,9 +1012,8 @@ class QuadricMixin:
----------
vector : iterable of float
Direction in which surface should be translated
inplace : boolean
inplace : bool
Whether to return a clone of the Surface or the Surface itself.
Defaults to False
Returns
-------
@ -2563,7 +2562,7 @@ class Halfspace(Region):
clone.surface = self.surface.clone(memo)
return clone
def translate(self, vector, memo=None):
def translate(self, vector, inplace=False, memo=None):
"""Translate half-space in given direction
Parameters
@ -2585,7 +2584,7 @@ class Halfspace(Region):
# If translated surface not in memo, add it
key = (self.surface, tuple(vector))
if key not in memo:
memo[key] = self.surface.translate(vector)
memo[key] = self.surface.translate(vector, inplace)
# Return translated half-space
return type(self)(memo[key], self.side)
@ -2617,7 +2616,7 @@ class Halfspace(Region):
:math:`\psi` about z. This corresponds to an x-y-z extrinsic
rotation as well as a z-y'-x'' intrinsic rotation using Tait-Bryan
angles :math:`(\phi, \theta, \psi)`.
inplace : boolean
inplace : bool
Whether or not to return a new instance of Surface or to modify the
coefficients of this Surface in place. Defaults to False.
memo : dict or None

View file

@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
from collections import OrderedDict
from collections.abc import Iterable
from copy import deepcopy
from numbers import Real
from numbers import Integral, Real
from pathlib import Path
from tempfile import TemporaryDirectory
from xml.etree import ElementTree as ET
@ -334,6 +334,13 @@ class Universe(UniverseBase):
if seed is not None:
model.settings.seed = seed
# Determine whether any materials contains macroscopic data and if
# so, set energy mode accordingly
for mat in self.get_all_materials().values():
if mat._macroscopic is not None:
model.settings.energy_mode = 'multi-group'
break
# Create plot object matching passed arguments
plot = openmc.Plot()
plot.origin = origin
@ -671,7 +678,7 @@ class DAGMCUniverse(UniverseBase):
@filename.setter
def filename(self, val):
cv.check_type('DAGMC filename', val, str)
cv.check_type('DAGMC filename', val, (Path, str))
self._filename = val
@property
@ -713,10 +720,10 @@ class DAGMCUniverse(UniverseBase):
dagmc_element.set('auto_geom_ids', 'true')
if self.auto_mat_ids:
dagmc_element.set('auto_mat_ids', 'true')
dagmc_element.set('filename', self.filename)
dagmc_element.set('filename', str(self.filename))
xml_element.append(dagmc_element)
def bounding_region(self, bounded_type='box', boundary_type='vacuum'):
def bounding_region(self, bounded_type='box', boundary_type='vacuum', starting_id=10000):
"""Creates a either a spherical or box shaped bounding region around
the DAGMC geometry.
Parameters
@ -729,6 +736,10 @@ class DAGMCUniverse(UniverseBase):
Boundary condition that defines the behavior for particles hitting
the surface. Defaults to vacuum boundary condition. Passed into the
surface construction.
starting_id : int
Starting ID of the surface(s) used in the region. For bounded_type
'box', the next 5 IDs will also be used. Defaults to 10000 to reduce
the chance of an overlap of surface IDs with the DAGMC geometry.
Returns
-------
openmc.Region
@ -737,19 +748,20 @@ class DAGMCUniverse(UniverseBase):
check_type('boundary type', boundary_type, str)
check_value('boundary type', boundary_type, _BOUNDARY_TYPES)
check_type('starting surface id', starting_id, Integral)
check_type('bounded type', bounded_type, str)
check_value('bounded type', bounded_type, ('box', 'sphere'))
bounding_box = self.bounding_box
bbox = self.bounding_box
if bounded_type == 'sphere':
import math
bounding_box_center = (bounding_box[0] + bounding_box[1])/2
radius = math.dist(bounding_box[0], bounding_box[1])
bbox_center = (bbox[0] + bbox[1])/2
radius = np.linalg.norm(np.asarray(bbox))
bounding_surface = openmc.Sphere(
x0=bounding_box_center[0],
y0=bounding_box_center[1],
z0=bounding_box_center[2],
surface_id=starting_id,
x0=bbox_center[0],
y0=bbox_center[1],
z0=bbox_center[2],
boundary_type=boundary_type,
r=radius,
)
@ -758,14 +770,19 @@ class DAGMCUniverse(UniverseBase):
if bounded_type == 'box':
# defines plane surfaces for all six faces of the bounding box
lower_x = openmc.XPlane(bounding_box[0][0], boundary_type=boundary_type)
upper_x = openmc.XPlane(bounding_box[1][0], boundary_type=boundary_type)
lower_y = openmc.YPlane(bounding_box[0][1], boundary_type=boundary_type)
upper_y = openmc.YPlane(bounding_box[1][1], boundary_type=boundary_type)
lower_z = openmc.ZPlane(bounding_box[0][2], boundary_type=boundary_type)
upper_z = openmc.ZPlane(bounding_box[1][2], boundary_type=boundary_type)
lower_x = openmc.XPlane(bbox[0][0], surface_id=starting_id)
upper_x = openmc.XPlane(bbox[1][0], surface_id=starting_id+1)
lower_y = openmc.YPlane(bbox[0][1], surface_id=starting_id+2)
upper_y = openmc.YPlane(bbox[1][1], surface_id=starting_id+3)
lower_z = openmc.ZPlane(bbox[0][2], surface_id=starting_id+4)
upper_z = openmc.ZPlane(bbox[1][2], surface_id=starting_id+5)
return +lower_x & -upper_x & +lower_y & -upper_y & +lower_z & -upper_z
region = +lower_x & -upper_x & +lower_y & -upper_y & +lower_z & -upper_z
for surface in region.get_surfaces().values():
surface.boundary_type = boundary_type
return region
def bounded_universe(self, bounding_cell_id=10000, **kwargs):
"""Returns an openmc.Universe filled with this DAGMCUniverse and bounded
@ -776,7 +793,7 @@ class DAGMCUniverse(UniverseBase):
Parameters
----------
bounding_cell_id : int
The cell ID number to use for the bounding cell, defaults to 1000 to reduce
The cell ID number to use for the bounding cell, defaults to 10000 to reduce
the chance of overlapping ID numbers with the DAGMC geometry.
Returns
@ -784,8 +801,7 @@ class DAGMCUniverse(UniverseBase):
openmc.Universe
Universe instance
"""
bounding_cell = openmc.Cell(fill=self, cell_id =bounding_cell_id, region=self.bounding_region(**kwargs))
bounding_cell = openmc.Cell(fill=self, cell_id=bounding_cell_id, region=self.bounding_region(**kwargs))
return openmc.Universe(cells=[bounding_cell])
@classmethod

View file

@ -745,7 +745,7 @@ namespace openmc {
void read_dagmc_universes(pugi::xml_node node)
{
if (check_for_node(node, "dagmc_universe")) {
fatal_error("DAGMC Universes are present but OpenMC was not configured"
fatal_error("DAGMC Universes are present but OpenMC was not configured "
"with DAGMC");
}
};

View file

@ -90,23 +90,25 @@ bool is_inelastic_scatter(int mt)
unique_ptr<Function1D> read_function(hid_t group, const char* name)
{
hid_t dset = open_dataset(group, name);
hid_t obj_id = open_object(group, name);
std::string func_type;
read_attribute(dset, "type", func_type);
read_attribute(obj_id, "type", func_type);
unique_ptr<Function1D> func;
if (func_type == "Tabulated1D") {
func = make_unique<Tabulated1D>(dset);
func = make_unique<Tabulated1D>(obj_id);
} else if (func_type == "Polynomial") {
func = make_unique<Polynomial>(dset);
func = make_unique<Polynomial>(obj_id);
} else if (func_type == "CoherentElastic") {
func = make_unique<CoherentElasticXS>(dset);
func = make_unique<CoherentElasticXS>(obj_id);
} else if (func_type == "IncoherentElastic") {
func = make_unique<IncoherentElasticXS>(dset);
func = make_unique<IncoherentElasticXS>(obj_id);
} else if (func_type == "Sum") {
func = make_unique<Sum1D>(obj_id);
} else {
throw std::runtime_error {"Unknown function type " + func_type +
" for dataset " + object_name(dset)};
" for dataset " + object_name(obj_id)};
}
close_dataset(dset);
close_object(obj_id);
return func;
}
@ -271,4 +273,30 @@ double IncoherentElasticXS::operator()(double E) const
return bound_xs_ / 2.0 * ((1 - std::exp(-4.0 * E * W)) / (2.0 * E * W));
}
//==============================================================================
// Sum1D implementation
//==============================================================================
Sum1D::Sum1D(hid_t group)
{
// Get number of functions
int n;
read_attribute(group, "n", n);
// Get each function
for (int i = 0; i < n; ++i) {
auto dset_name = fmt::format("func_{}", i + 1);
functions_.push_back(read_function(group, dset_name.c_str()));
}
}
double Sum1D::operator()(double x) const
{
double result = 0.0;
for (auto& func : functions_) {
result += (*func)(x);
}
return result;
}
} // namespace openmc

View file

@ -118,6 +118,12 @@ void close_group(hid_t group_id)
fatal_error("Failed to close group");
}
void close_object(hid_t obj_id)
{
if (H5Oclose(obj_id) < 0)
fatal_error("Failed to close object");
}
int dataset_ndims(hid_t dset)
{
hid_t dspace = H5Dget_space(dset);
@ -394,6 +400,12 @@ hid_t open_group(hid_t group_id, const char* name)
return H5Gopen(group_id, name, H5P_DEFAULT);
}
hid_t open_object(hid_t group_id, const std::string& name)
{
ensure_exists(group_id, name.c_str());
return H5Oopen(group_id, name.c_str(), H5P_DEFAULT);
}
void read_attr(hid_t obj_id, const char* name, hid_t mem_type_id, void* buffer)
{
hid_t attr = H5Aopen(obj_id, name, H5P_DEFAULT);

View file

@ -220,21 +220,58 @@ void UnstructuredMesh::to_hdf5(hid_t group) const
write_dataset(mesh_group, "type", mesh_type);
write_dataset(mesh_group, "filename", filename_);
write_dataset(mesh_group, "library", this->library());
// write volume of each element
vector<double> tet_vols;
xt::xtensor<double, 2> centroids({static_cast<size_t>(this->n_bins()), 3});
for (int i = 0; i < this->n_bins(); i++) {
tet_vols.emplace_back(this->volume(i));
auto c = this->centroid(i);
xt::view(centroids, i, xt::all()) = xt::xarray<double>({c.x, c.y, c.z});
}
write_dataset(mesh_group, "volumes", tet_vols);
write_dataset(mesh_group, "centroids", centroids);
if (specified_length_multiplier_)
write_dataset(mesh_group, "length_multiplier", length_multiplier_);
// write vertex coordinates
xt::xtensor<double, 2> vertices({static_cast<size_t>(this->n_vertices()), 3});
for (int i = 0; i < this->n_vertices(); i++) {
auto v = this->vertex(i);
xt::view(vertices, i, xt::all()) = xt::xarray<double>({v.x, v.y, v.z});
}
write_dataset(mesh_group, "vertices", vertices);
int num_elem_skipped = 0;
// write element types and connectivity
vector<double> volumes;
xt::xtensor<int, 2> connectivity ({static_cast<size_t>(this->n_bins()), 8});
xt::xtensor<int, 2> elem_types ({static_cast<size_t>(this->n_bins()), 1});
for (int i = 0; i < this->n_bins(); i++) {
auto conn = this->connectivity(i);
volumes.emplace_back(this->volume(i));
// write linear tet element
if (conn.size() == 4) {
xt::view(elem_types, i, xt::all()) = static_cast<int>(ElementType::LINEAR_TET);
xt::view(connectivity, i, xt::all()) = xt::xarray<int>({conn[0], conn[1], conn[2], conn[3],
-1, -1, -1, -1});
// write linear hex element
} else if (conn.size() == 8) {
xt::view(elem_types, i, xt::all()) = static_cast<int>(ElementType::LINEAR_HEX);
xt::view(connectivity, i, xt::all()) = xt::xarray<int>({conn[0], conn[1], conn[2], conn[3],
conn[4], conn[5], conn[6], conn[7]});
} else {
num_elem_skipped++;
xt::view(elem_types, i, xt::all()) = static_cast<int>(ElementType::UNSUPPORTED);
xt::view(connectivity, i, xt::all()) = -1;
}
}
// warn users that some elements were skipped
if (num_elem_skipped > 0) {
warning(fmt::format("The connectivity of {} elements "
"on mesh {} were not written "
"because they are not of type linear tet/hex.",
num_elem_skipped, this->id_));
}
write_dataset(mesh_group, "volumes", volumes);
write_dataset(mesh_group, "connectivity", connectivity);
write_dataset(mesh_group, "element_types", elem_types);
close_group(mesh_group);
}
@ -1775,6 +1812,14 @@ void MOABMesh::initialize()
filename_);
}
// set member range of vertices
int vertex_dim = 0;
rval = mbi_->get_entities_by_dimension(0, vertex_dim, verts_);
if (rval != moab::MB_SUCCESS) {
fatal_error("Failed to get all vertex handles");
}
// make an entity set for all tetrahedra
// this is used for convenience later in output
rval = mbi_->create_meshset(moab::MESHSET_SET, tetset_);
@ -2124,6 +2169,14 @@ std::pair<vector<double>, vector<double>> MOABMesh::plot(
return {};
}
int MOABMesh::get_vert_idx_from_handle(moab::EntityHandle vert) const {
int idx = vert - verts_[0];
if (idx >= n_vertices()) {
fatal_error(fmt::format("Invalid vertex idx {} (# vertices {})", idx, n_vertices()));
}
return idx;
}
int MOABMesh::get_bin_from_ent_handle(moab::EntityHandle eh) const
{
int bin = eh - ehs_[0];
@ -2191,6 +2244,46 @@ Position MOABMesh::centroid(int bin) const
return {centroid[0], centroid[1], centroid[2]};
}
int MOABMesh::n_vertices() const {
return verts_.size();
}
Position MOABMesh::vertex(int id) const {
moab::ErrorCode rval;
moab::EntityHandle vert = verts_[id];
moab::CartVect coords;
rval = mbi_->get_coords(&vert, 1, coords.array());
if (rval != moab::MB_SUCCESS) {
fatal_error("Failed to get the coordinates of a vertex.");
}
return {coords[0], coords[1], coords[2]};
}
std::vector<int> MOABMesh::connectivity(int bin) const {
moab::ErrorCode rval;
auto tet = get_ent_handle_from_bin(bin);
// look up the tet connectivity
vector<moab::EntityHandle> conn;
rval = mbi_->get_connectivity(&tet, 1, conn);
if (rval != moab::MB_SUCCESS) {
fatal_error("Failed to get connectivity of a mesh element.");
return {};
}
std::vector<int> verts(4);
for (int i = 0; i < verts.size(); i++) {
verts[i] = get_vert_idx_from_handle(conn[i]);
}
return verts;
}
std::pair<moab::Tag, moab::Tag> MOABMesh::get_score_tags(
std::string score) const
{
@ -2324,16 +2417,37 @@ const std::string LibMesh::mesh_lib_type = "libmesh";
LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node)
{
// filename_ and length_multiplier_ will already be set by the UnstructuredMesh constructor
set_mesh_pointer_from_filename(filename_);
set_length_multiplier(length_multiplier_);
initialize();
}
LibMesh::LibMesh(const std::string& filename, double length_multiplier)
// create the mesh from a pointer to a libMesh Mesh
LibMesh::LibMesh(libMesh::MeshBase & input_mesh, double length_multiplier)
{
filename_ = filename;
m_ = &input_mesh;
set_length_multiplier(length_multiplier);
initialize();
}
// create the mesh from an input file
LibMesh::LibMesh(const std::string& filename, double length_multiplier)
{
set_mesh_pointer_from_filename(filename);
set_length_multiplier(length_multiplier);
initialize();
}
void LibMesh::set_mesh_pointer_from_filename(const std::string& filename)
{
filename_ = filename;
unique_m_ = make_unique<libMesh::Mesh>(*settings::libmesh_comm, n_dimension_);
m_ = unique_m_.get();
m_->read(filename_);
}
// intialize from mesh file
void LibMesh::initialize()
{
if (!settings::libmesh_comm) {
@ -2344,14 +2458,14 @@ void LibMesh::initialize()
// assuming that unstructured meshes used in OpenMC are 3D
n_dimension_ = 3;
m_ = make_unique<libMesh::Mesh>(*settings::libmesh_comm, n_dimension_);
m_->read(filename_);
if (specified_length_multiplier_) {
libMesh::MeshTools::Modification::scale(*m_, length_multiplier_);
}
m_->prepare_for_use();
// if OpenMC is managing the libMesh::MeshBase instance, prepare the mesh.
// Otherwise assume that it is prepared by its owning application
if (unique_m_) {
m_->prepare_for_use();
}
// ensure that the loaded mesh is 3 dimensional
if (m_->mesh_dimension() != n_dimension_) {
@ -2394,6 +2508,27 @@ Position LibMesh::centroid(int bin) const
return {centroid(0), centroid(1), centroid(2)};
}
int LibMesh::n_vertices() const
{
return m_->n_nodes();
}
Position LibMesh::vertex(int vertex_id) const
{
const auto node_ref = m_->node_ref(vertex_id);
return {node_ref(0), node_ref(1), node_ref(2)};
}
std::vector<int> LibMesh::connectivity(int elem_id) const
{
std::vector<int> conn;
const auto* elem_ptr = m_->elem_ptr(elem_id);
for (int i = 0; i < elem_ptr->n_nodes(); i++) {
conn.push_back(elem_ptr->node_id(i));
}
return conn;
}
std::string LibMesh::library() const
{
return mesh_lib_type;

View file

@ -332,4 +332,40 @@ void IncoherentInelasticAE::sample(
mu += std::min(mu - mu_left, mu_right - mu) * (prn(seed) - 0.5);
}
//==============================================================================
// MixedElasticAE implementation
//==============================================================================
MixedElasticAE::MixedElasticAE(
hid_t group, const CoherentElasticXS& coh_xs, const Function1D& incoh_xs)
: coherent_dist_(coh_xs), coherent_xs_(coh_xs), incoherent_xs_(incoh_xs)
{
// Read incoherent elastic distribution
hid_t incoherent_group = open_group(group, "incoherent");
std::string temp;
read_attribute(incoherent_group, "type", temp);
if (temp == "incoherent_elastic") {
incoherent_dist_ = make_unique<IncoherentElasticAE>(incoherent_group);
} else if (temp == "incoherent_elastic_discrete") {
auto xs = dynamic_cast<const Tabulated1D*>(&incoh_xs);
incoherent_dist_ =
make_unique<IncoherentElasticAEDiscrete>(incoherent_group, xs->x());
}
close_group(incoherent_group);
}
void MixedElasticAE::sample(
double E_in, double& E_out, double& mu, uint64_t* seed) const
{
// Evaluate coherent and incoherent elastic cross sections
double xs_coh = coherent_xs_(E_in);
double xs_incoh = incoherent_xs_(E_in);
if (prn(seed) * (xs_coh + xs_incoh) < xs_coh) {
coherent_dist_.sample(E_in, E_out, mu, seed);
} else {
incoherent_dist_->sample(E_in, E_out, mu, seed);
}
}
} // namespace openmc

View file

@ -818,6 +818,16 @@ void write_unstructured_mesh_results()
if (!umesh->output_)
continue;
if (umesh->library() == "moab") {
if (mpi::master)
warning(fmt::format(
"Output for a MOAB mesh (mesh {}) was "
"requested but will not be written. Please use the Python "
"API to generated the desired VTK tetrahedral mesh.",
umesh->id_));
continue;
}
// if this tally has more than one filter, print
// warning and skip writing the mesh
if (tally->filters().size() > 1) {
@ -889,12 +899,10 @@ void write_unstructured_mesh_results()
std::string filename = fmt::format("tally_{0}.{1:0{2}}", tally->id_,
simulation::current_batch, batch_width);
if (umesh->library() == "moab" && !mpi::master)
continue;
// Write the unstructured mesh and data to file
umesh->write(filename);
// remove score data added for this mesh write
umesh->remove_scores();
}
}

View file

@ -210,14 +210,22 @@ ThermalData::ThermalData(hid_t group)
if (temp == "coherent_elastic") {
auto xs = dynamic_cast<CoherentElasticXS*>(elastic_.xs.get());
elastic_.distribution = make_unique<CoherentElasticAE>(*xs);
} else {
if (temp == "incoherent_elastic") {
elastic_.distribution = make_unique<IncoherentElasticAE>(dgroup);
} else if (temp == "incoherent_elastic_discrete") {
auto xs = dynamic_cast<Tabulated1D*>(elastic_.xs.get());
elastic_.distribution =
make_unique<IncoherentElasticAEDiscrete>(dgroup, xs->x());
}
} else if (temp == "incoherent_elastic") {
elastic_.distribution = make_unique<IncoherentElasticAE>(dgroup);
} else if (temp == "incoherent_elastic_discrete") {
auto xs = dynamic_cast<Tabulated1D*>(elastic_.xs.get());
elastic_.distribution =
make_unique<IncoherentElasticAEDiscrete>(dgroup, xs->x());
} else if (temp == "mixed_elastic") {
// Get coherent/incoherent cross sections
auto mixed_xs = dynamic_cast<Sum1D*>(elastic_.xs.get());
const auto& coh_xs =
dynamic_cast<const CoherentElasticXS*>(mixed_xs->functions(0).get());
const auto& incoh_xs = mixed_xs->functions(1).get();
// Create mixed elastic distribution
elastic_.distribution =
make_unique<MixedElasticAE>(dgroup, *coh_xs, *incoh_xs);
}
close_group(elastic_group);

13
tests/micro_xs_simple.csv Normal file
View file

@ -0,0 +1,13 @@
nuclide,"(n,gamma)",fission
U234,22.231989822002454,0.4962074466374984
U235,10.479008971197121,48.41787337164606
U238,0.8673334105437321,0.1046788058876236
U236,8.651710446071224,0.31948392400019293
O16,7.497851000107522e-05,0.0
O17,0.0004079227797153372,0.0
I135,6.842395323713929,0.0
Xe135,227463.8642699061,0.0
Xe136,0.023178960347535887,0.0
Cs135,2.1721665580713623,0.0
Gd157,12786.099392370175,0.0
Gd156,3.4006085445846983,0.0
1 nuclide (n,gamma) fission
2 U234 22.231989822002454 0.4962074466374984
3 U235 10.479008971197121 48.41787337164606
4 U238 0.8673334105437321 0.1046788058876236
5 U236 8.651710446071224 0.31948392400019293
6 O16 7.497851000107522e-05 0.0
7 O17 0.0004079227797153372 0.0
8 I135 6.842395323713929 0.0
9 Xe135 227463.8642699061 0.0
10 Xe136 0.023178960347535887 0.0
11 Cs135 2.1721665580713623 0.0
12 Gd157 12786.099392370175 0.0
13 Gd156 3.4006085445846983 0.0

View file

@ -48,8 +48,8 @@ def cpp_driver(request):
finally:
# Remove local build directory when test is complete
shutil.rmtree('build')
os.remove('CMakeLists.txt')
shutil.rmtree(request.node.path.parent / 'build')
os.remove(request.node.path.parent / 'CMakeLists.txt')
@pytest.fixture

View file

@ -1,6 +1,7 @@
import openmc
import openmc.lib
from pathlib import Path
import pytest
from tests.testing_harness import PyAPITestHarness
@ -27,7 +28,7 @@ def model():
model.settings.dagmc = True
# geometry
dag_univ = openmc.DAGMCUniverse("dagmc.h5m")
dag_univ = openmc.DAGMCUniverse(Path("dagmc.h5m"))
model.geometry = openmc.Geometry(dag_univ)
# tally

View file

@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?>
<geometry>
<cell fill="12" id="13" region="22 -23 24 -25 26 -27" universe="13" />
<cell fill="12" id="13" region="9 -10 11 -12 13 -14" universe="13" />
<dagmc_universe auto_geom_ids="true" filename="dagmc.h5m" id="9" />
<lattice id="12">
<pitch>24.0 24.0</pitch>
@ -10,12 +10,12 @@
9 9
9 9 </universes>
</lattice>
<surface boundary="reflective" coeffs="-24.0" id="22" name="left" type="x-plane" />
<surface boundary="reflective" coeffs="24.0" id="23" name="right" type="x-plane" />
<surface boundary="reflective" coeffs="-24.0" id="24" name="front" type="y-plane" />
<surface boundary="reflective" coeffs="24.0" id="25" name="back" type="y-plane" />
<surface boundary="reflective" coeffs="-10.0" id="26" name="bottom" type="z-plane" />
<surface boundary="reflective" coeffs="10.0" id="27" name="top" type="z-plane" />
<surface boundary="reflective" coeffs="-24.0" id="9" name="left" type="x-plane" />
<surface boundary="reflective" coeffs="24.0" id="10" name="right" type="x-plane" />
<surface boundary="reflective" coeffs="-24.0" id="11" name="front" type="y-plane" />
<surface boundary="reflective" coeffs="24.0" id="12" name="back" type="y-plane" />
<surface boundary="reflective" coeffs="-10.0" id="13" name="bottom" type="z-plane" />
<surface boundary="reflective" coeffs="10.0" id="14" name="top" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -53,25 +53,6 @@ class DAGMCUniverseTest(PyAPITestHarness):
# assigns the bound_dag_geometry to the model to test the type checks in model.Geometry setter
model.Geometry = bound_pincell_geometry
# checks that the bounding box is calculated correctly
bounding_box = pincell_univ.bounding_box
assert bounding_box[0].tolist() == [-25., -25., -25.]
assert bounding_box[1].tolist() == [25., 25., 25.]
# checks that the bounding region is six surfaces each with a vacuum boundary type
b_region = pincell_univ.bounding_region(bounded_type='box', boundary_type='vacuum')
assert isinstance(b_region, openmc.Region)
assert len(b_region.get_surfaces()) == 6
for surface in list(b_region.get_surfaces().values()):
assert surface.boundary_type == 'vacuum'
# checks that the bounding region is a single surface with a reflective boundary type
b_region = pincell_univ.bounding_region(bounded_type='sphere', boundary_type='reflective')
assert isinstance(b_region, openmc.Region)
assert len(b_region.get_surfaces()) == 1
for surface in list(b_region.get_surfaces().values()):
assert surface.boundary_type == 'reflective'
# create a 2 x 2 lattice using the DAGMC pincell
pitch = np.asarray((24.0, 24.0))
lattice = openmc.RectLattice()

View file

@ -0,0 +1,217 @@
""" Transport-free depletion test suite """
from pathlib import Path
import numpy as np
import pytest
import openmc
import openmc.deplete
from openmc.deplete import IndependentOperator, MicroXS
@pytest.fixture(scope="module")
def fuel():
fuel = openmc.Material(name="uo2")
fuel.add_element("U", 1, percent_type="ao", enrichment=4.25)
fuel.add_element("O", 2)
fuel.set_density("g/cc", 10.4)
fuel.depletable = True
fuel.volume = np.pi * 0.42 ** 2
return fuel
@pytest.fixture(scope="module")
def micro_xs():
micro_xs_file = Path(__file__).parents[2] / 'micro_xs_simple.csv'
return MicroXS.from_csv(micro_xs_file)
@pytest.fixture(scope="module")
def chain_file():
return Path(__file__).parents[2] / 'chain_simple.xml'
@pytest.mark.parametrize("multiproc, from_nuclides, normalization_mode, power, flux", [
(True, True,'source-rate', None, 1164719970082145.0),
(False, True, 'source-rate', None, 1164719970082145.0),
(True, True, 'fission-q', 174, None),
(False, True, 'fission-q', 174, None),
(True, False,'source-rate', None, 1164719970082145.0),
(False, False, 'source-rate', None, 1164719970082145.0),
(True, False, 'fission-q', 174, None),
(False, False, 'fission-q', 174, None)])
def test_against_self(run_in_tmpdir,
fuel,
micro_xs,
chain_file,
multiproc,
from_nuclides,
normalization_mode,
power,
flux):
"""Transport free system test suite.
Runs an OpenMC transport-free depletion calculation and verifies
that the outputs match a reference file.
"""
# Create operator
op = _create_operator(from_nuclides,
fuel,
micro_xs,
chain_file,
normalization_mode)
# Power and timesteps
dt = [360] # single step
# Perform simulation using the predictor algorithm
openmc.deplete.pool.USE_MULTIPROCESSING = multiproc
openmc.deplete.PredictorIntegrator(op,
dt,
power=power,
source_rates=flux,
timestep_units='s').integrate()
# Get path to test and reference results
path_test = op.output_dir / 'depletion_results.h5'
if flux is not None:
ref_path = 'test_reference_source_rate.h5'
else:
ref_path = 'test_reference_fission_q.h5'
path_reference = Path(__file__).with_name(ref_path)
# Load the reference/test results
res_test = openmc.deplete.Results(path_test)
res_ref = openmc.deplete.Results(path_reference)
# Assert same mats
_assert_same_mats(res_test, res_ref)
tol = 1.0e-14
_assert_atoms_equal(res_test, res_ref, tol)
_assert_reaction_rates_equal(res_test, res_ref, tol)
@pytest.mark.parametrize("multiproc, dt, time_units, time_type, atom_tol, rx_tol ", [
(True, 360, 's', 'minutes', 2.0e-3, 3.0e-2),
(False, 360, 's', 'minutes', 2.0e-3, 3.0e-2),
(True, 4, 'h', 'hours', 2.0e-3, 6.0e-2),
(False,4, 'h', 'hours', 2.0e-3, 6.0e-2),
(True, 5, 'd', 'days', 2.0e-3, 5.0e-2),
(False,5, 'd', 'days', 2.0e-3, 5.0e-2),
(True, 100, 'd', 'months', 4.0e-3, 9.0e-2),
(False, 100, 'd', 'months', 4.0e-3, 9.0e-2)])
def test_against_coupled(run_in_tmpdir,
fuel,
micro_xs,
chain_file,
multiproc,
dt,
time_units,
time_type,
atom_tol,
rx_tol):
# Create operator
op = _create_operator(False, fuel, micro_xs, chain_file, 'fission-q')
# Power and timesteps
dt = [dt] # single step
# Perform simulation using the predictor algorithm
openmc.deplete.pool.USE_MULTIPROCESSING = multiproc
openmc.deplete.PredictorIntegrator(
op, dt, power=174, timestep_units=time_units).integrate()
# Get path to test and reference results
path_test = op.output_dir / 'depletion_results.h5'
ref_path = f'test_reference_coupled_{time_type}.h5'
path_reference = Path(__file__).with_name(ref_path)
# Load the reference/test results
res_test = openmc.deplete.Results(path_test)
res_ref = openmc.deplete.Results(path_reference)
# Assert same mats
_assert_same_mats(res_test, res_ref)
_assert_atoms_equal(res_test, res_ref, atom_tol)
_assert_reaction_rates_equal(res_test, res_ref, rx_tol)
def _create_operator(from_nuclides,
fuel,
micro_xs,
chain_file,
normalization_mode):
if from_nuclides:
nuclides = {}
for nuc, dens in fuel.get_nuclide_atom_densities().items():
nuclides[nuc] = dens
op = IndependentOperator.from_nuclides(fuel.volume,
nuclides,
micro_xs,
chain_file,
normalization_mode=normalization_mode)
else:
op = IndependentOperator(openmc.Materials([fuel]),
micro_xs,
chain_file,
normalization_mode=normalization_mode)
return op
def _assert_same_mats(res_ref, res_test):
for mat in res_ref[0].mat_to_ind:
assert mat in res_test[0].mat_to_ind, \
"Material {} not in new results.".format(mat)
for nuc in res_ref[0].nuc_to_ind:
assert nuc in res_test[0].nuc_to_ind, \
"Nuclide {} not in new results.".format(nuc)
for mat in res_test[0].mat_to_ind:
assert mat in res_ref[0].mat_to_ind, \
"Material {} not in old results.".format(mat)
for nuc in res_test[0].nuc_to_ind:
assert nuc in res_ref[0].nuc_to_ind, \
"Nuclide {} not in old results.".format(nuc)
def _assert_atoms_equal(res_ref, res_test, tol):
for mat in res_test[0].mat_to_ind:
for nuc in res_test[0].nuc_to_ind:
_, y_test = res_test.get_atoms(mat, nuc)
_, y_old = res_ref.get_atoms(mat, nuc)
# Test each point
correct = True
for i, ref in enumerate(y_old):
if ref != y_test[i]:
if ref != 0.0:
correct = np.abs(y_test[i] - ref) / ref <= tol
else:
correct = False
assert correct, "Discrepancy in mat {} and nuc {}\n{}\n{}".format(
mat, nuc, y_old, y_test)
def _assert_reaction_rates_equal(res_ref, res_test, tol):
for reactions in res_test[0].rates:
for mat in reactions.index_mat:
for nuc in reactions.index_nuc:
for rx in reactions.index_rx:
y_test = res_test.get_reaction_rate(mat, nuc, rx)[1] / \
res_test.get_atoms(mat, nuc)[1]
y_old = res_ref.get_reaction_rate(mat, nuc, rx)[1] / \
res_ref.get_atoms(mat, nuc)[1]
# Test each point
correct = True
for i, ref in enumerate(y_old):
if ref != y_test[i]:
if ref != 0.0:
correct = np.abs(y_test[i] - ref) / ref <= tol
else:
if y_test[i] != 0.0:
correct = False
assert correct, "Discrepancy in mat {}, nuc {}, and rx {}\n{}\n{}".format(
mat, nuc, rx, y_old, y_test)

View file

@ -12,7 +12,7 @@ from openmc.data import JOULE_PER_EV
import openmc.deplete
from tests.regression_tests import config
from example_geometry import generate_problem
from .example_geometry import generate_problem
@pytest.fixture(scope="module")

View file

@ -67,16 +67,16 @@
<filter id="6" type="legendre">
<order>1</order>
</filter>
<filter id="28" type="legendre">
<filter id="30" type="legendre">
<order>3</order>
</filter>
<filter id="52" type="energy">
<filter id="54" type="energy">
<bins>0.0 20000000.0</bins>
</filter>
<filter id="66" type="meshsurface">
<filter id="68" type="meshsurface">
<bins>1</bins>
</filter>
<filter id="77" type="delayedgroup">
<filter id="79" type="delayedgroup">
<bins>1 2 3 4 5 6</bins>
</filter>
<tally id="1">
@ -166,19 +166,19 @@
<tally id="15">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>fission</scores>
<scores>(n,2n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="16">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>(n,3n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="17">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>fission</scores>
<scores>(n,4n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="18">
@ -190,207 +190,207 @@
<tally id="19">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>absorption</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="20">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="21">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>kappa-fission</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="22">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="23">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="24">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="25">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="26">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>kappa-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="27">
<filters>1 2 5 28</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="28">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="29">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="29">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="30">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="31">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="32">
<filters>1 2</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="33">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="34">
<filters>1 2 5</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="35">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="36">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="37">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="38">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="39">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="40">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="41">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="42">
<filters>1 52</filters>
<tally id="36">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="37">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="38">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="43">
<filters>1 5</filters>
<tally id="39">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="44">
<filters>1 52</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="45">
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="46">
<tally id="40">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="41">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="42">
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="43">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="44">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="45">
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="46">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="47">
<filters>1 54</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="48">
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="49">
<filters>1 54</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="50">
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="51">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="52">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>inverse-velocity</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="48">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="49">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="50">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="51">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="52">
<filters>66 2</filters>
<nuclides>total</nuclides>
<scores>current</scores>
<estimator>analog</estimator>
</tally>
<tally id="53">
<filters>1 2</filters>
<nuclides>total</nuclides>
@ -400,7 +400,7 @@
<tally id="54">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<scores>prompt-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="55">
@ -410,91 +410,121 @@
<estimator>analog</estimator>
</tally>
<tally id="56">
<filters>1 5 6</filters>
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="57">
<filters>1 2</filters>
<filters>68 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
<scores>current</scores>
<estimator>analog</estimator>
</tally>
<tally id="58">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="59">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="60">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="60">
<tally id="61">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="62">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="63">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="64">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="65">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="61">
<tally id="66">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="62">
<filters>1 77 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="63">
<filters>1 77 52</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="64">
<filters>1 77 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="65">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="66">
<filters>1 77 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="67">
<filters>1 77</filters>
<filters>1 79 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="68">
<filters>1 77</filters>
<filters>1 79 54</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="69">
<filters>1 79 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="70">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="71">
<filters>1 79 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="72">
<filters>1 79</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="73">
<filters>1 79</filters>
<nuclides>total</nuclides>
<scores>decay-rate</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="69">
<tally id="74">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="70">
<filters>1 77 2 5</filters>
<tally id="75">
<filters>1 79 2 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>

View file

@ -24,6 +24,12 @@
3 2 2 1 1 total 0.022046 0.001594
mesh 1 group in nuclide mean std. dev.
x y z
0 1 1 1 1 total 0.021489 0.001396
2 1 2 1 1 total 0.020837 0.001436
1 2 1 1 1 total 0.021454 0.001983
3 2 2 1 1 total 0.022036 0.001594
mesh 1 group in nuclide mean std. dev.
x y z
0 1 1 1 1 total 0.011598 0.001508
2 1 2 1 1 total 0.010856 0.001613
1 2 1 1 1 total 0.011622 0.002259

View file

@ -89,10 +89,10 @@
<filter id="6" type="legendre">
<order>1</order>
</filter>
<filter id="28" type="legendre">
<filter id="30" type="legendre">
<order>3</order>
</filter>
<filter id="73" type="delayedgroup">
<filter id="75" type="delayedgroup">
<bins>1 2 3 4 5 6</bins>
</filter>
<tally id="1">
@ -182,19 +182,19 @@
<tally id="15">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>fission</scores>
<scores>(n,2n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="16">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>(n,3n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="17">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>fission</scores>
<scores>(n,4n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="18">
@ -206,305 +206,335 @@
<tally id="19">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>absorption</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="20">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="21">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>kappa-fission</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="22">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="23">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="24">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="25">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="26">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>kappa-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="27">
<filters>1 2 5 28</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="28">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="29">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="29">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="30">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="31">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="32">
<filters>1 2</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="33">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="34">
<filters>1 2 5</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="35">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="36">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="37">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="38">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="39">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="40">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="41">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="42">
<tally id="36">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="37">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="38">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="43">
<filters>1 5</filters>
<tally id="39">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="40">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="41">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="42">
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="43">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="44">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="45">
<filters>1 5</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="46">
<filters>1 2</filters>
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="47">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>inverse-velocity</scores>
<estimator>tracklength</estimator>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="48">
<filters>1 2</filters>
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="49">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="50">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="51">
<filters>1 2 5</filters>
<tally id="50">
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="52">
<tally id="51">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="52">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>inverse-velocity</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="53">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="54">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>prompt-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="55">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="56">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
<estimator>analog</estimator>
</tally>
<tally id="56">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="57">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="58">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="59">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="59">
<tally id="60">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="61">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="62">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="63">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="64">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="60">
<tally id="65">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="61">
<filters>1 73 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="62">
<filters>1 73 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="63">
<filters>1 73 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="64">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="65">
<filters>1 73 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="66">
<filters>1 73</filters>
<filters>1 75 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="67">
<filters>1 73</filters>
<filters>1 75 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="68">
<filters>1 75 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="69">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="70">
<filters>1 75 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="71">
<filters>1 75</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="72">
<filters>1 75</filters>
<nuclides>total</nuclides>
<scores>decay-rate</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="68">
<tally id="73">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="69">
<filters>1 73 2 5</filters>
<tally id="74">
<filters>1 75 2 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>

View file

@ -7,6 +7,8 @@
sum(distribcell) group in nuclide mean std. dev.
0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.06484 0.002514
sum(distribcell) group in nuclide mean std. dev.
0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.064761 0.002514
sum(distribcell) group in nuclide mean std. dev.
0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.028638 0.002713
sum(distribcell) group in nuclide mean std. dev.
0 ((0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, ...),) 1 total 0.036203 0.001449

View file

@ -67,16 +67,16 @@
<filter id="6" type="legendre">
<order>1</order>
</filter>
<filter id="28" type="legendre">
<filter id="30" type="legendre">
<order>3</order>
</filter>
<filter id="52" type="energy">
<filter id="54" type="energy">
<bins>0.0 20000000.0</bins>
</filter>
<filter id="66" type="meshsurface">
<filter id="68" type="meshsurface">
<bins>1</bins>
</filter>
<filter id="77" type="delayedgroup">
<filter id="79" type="delayedgroup">
<bins>1 2 3 4 5 6</bins>
</filter>
<tally id="1">
@ -166,19 +166,19 @@
<tally id="15">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>fission</scores>
<scores>(n,2n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="16">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>(n,3n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="17">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>fission</scores>
<scores>(n,4n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="18">
@ -190,207 +190,207 @@
<tally id="19">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>absorption</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="20">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="21">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>kappa-fission</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="22">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="23">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="24">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="25">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="26">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>kappa-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="27">
<filters>1 2 5 28</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="28">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="29">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="29">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="30">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="31">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="32">
<filters>1 2</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="33">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="34">
<filters>1 2 5</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="35">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="36">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="37">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="38">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="39">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="40">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="41">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="42">
<filters>1 52</filters>
<tally id="36">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="37">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="38">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="43">
<filters>1 5</filters>
<tally id="39">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="44">
<filters>1 52</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="45">
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="46">
<tally id="40">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="41">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="42">
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="43">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="44">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="45">
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="46">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="47">
<filters>1 54</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="48">
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="49">
<filters>1 54</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="50">
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="51">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="52">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>inverse-velocity</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="48">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="49">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="50">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="51">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="52">
<filters>66 2</filters>
<nuclides>total</nuclides>
<scores>current</scores>
<estimator>analog</estimator>
</tally>
<tally id="53">
<filters>1 2</filters>
<nuclides>total</nuclides>
@ -400,7 +400,7 @@
<tally id="54">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<scores>prompt-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="55">
@ -410,91 +410,121 @@
<estimator>analog</estimator>
</tally>
<tally id="56">
<filters>1 5 6</filters>
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="57">
<filters>1 2</filters>
<filters>68 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
<scores>current</scores>
<estimator>analog</estimator>
</tally>
<tally id="58">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="59">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="60">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="60">
<tally id="61">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="62">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="63">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="64">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="65">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="61">
<tally id="66">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="62">
<filters>1 77 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="63">
<filters>1 77 52</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="64">
<filters>1 77 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="65">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="66">
<filters>1 77 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="67">
<filters>1 77</filters>
<filters>1 79 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="68">
<filters>1 77</filters>
<filters>1 79 54</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="69">
<filters>1 79 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="70">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="71">
<filters>1 79 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="72">
<filters>1 79</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="73">
<filters>1 79</filters>
<nuclides>total</nuclides>
<scores>decay-rate</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="69">
<tally id="74">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="70">
<filters>1 77 2 5</filters>
<tally id="75">
<filters>1 79 2 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>

View file

@ -10,6 +10,9 @@ domain=1 type=nu-transport
domain=1 type=absorption
[9.18204614e-03 9.50890834e-02]
[1.02291781e-03 9.51211716e-03]
domain=1 type=reduced absorption
[9.15806809e-03 9.50890834e-02]
[1.02286279e-03 9.51211716e-03]
domain=1 type=capture
[6.76780024e-03 4.04282690e-02]
[1.01848835e-03 8.89313901e-03]

View file

@ -50,13 +50,13 @@
<filter id="6" type="legendre">
<order>1</order>
</filter>
<filter id="28" type="legendre">
<filter id="30" type="legendre">
<order>3</order>
</filter>
<filter id="66" type="meshsurface">
<filter id="68" type="meshsurface">
<bins>1</bins>
</filter>
<filter id="77" type="delayedgroup">
<filter id="79" type="delayedgroup">
<bins>1 2 3 4 5 6</bins>
</filter>
<tally id="1">
@ -146,19 +146,19 @@
<tally id="15">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>fission</scores>
<scores>(n,2n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="16">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>(n,3n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="17">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>fission</scores>
<scores>(n,4n)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="18">
@ -170,206 +170,206 @@
<tally id="19">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>absorption</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="20">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="21">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>kappa-fission</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="22">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="23">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="24">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="25">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="26">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
<scores>kappa-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="27">
<filters>1 2 5 28</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="28">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="29">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="29">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="30">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="31">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="32">
<filters>1 2</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="33">
<filters>1 2 5</filters>
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="34">
<filters>1 2 5</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="35">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="36">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="37">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="38">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="39">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="40">
<filters>1 2 5 28</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="41">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="42">
<tally id="36">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="37">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="38">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="43">
<filters>1 5</filters>
<tally id="39">
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="40">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="41">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="42">
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="43">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="44">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
<scores>scatter</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="45">
<filters>1 5</filters>
<filters>1 2 5 30</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="46">
<filters>1 2</filters>
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="47">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>inverse-velocity</scores>
<estimator>tracklength</estimator>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="48">
<filters>1 2</filters>
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
<scores>nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="49">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="50">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="51">
<filters>1 2 5</filters>
<tally id="50">
<filters>1 5</filters>
<nuclides>total</nuclides>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="52">
<filters>66 2</filters>
<tally id="51">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>current</scores>
<estimator>analog</estimator>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="52">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>inverse-velocity</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="53">
<filters>1 2</filters>
@ -380,7 +380,7 @@
<tally id="54">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<scores>prompt-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="55">
@ -390,91 +390,121 @@
<estimator>analog</estimator>
</tally>
<tally id="56">
<filters>1 5 6</filters>
<filters>1 2 5</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<scores>prompt-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="57">
<filters>1 2</filters>
<filters>68 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
<scores>current</scores>
<estimator>analog</estimator>
</tally>
<tally id="58">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="59">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="60">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="60">
<tally id="61">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="62">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="63">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>total</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="64">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="65">
<filters>1 5 6</filters>
<nuclides>total</nuclides>
<scores>nu-scatter</scores>
<estimator>analog</estimator>
</tally>
<tally id="61">
<tally id="66">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="62">
<filters>1 77 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="63">
<filters>1 77 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="64">
<filters>1 77 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="65">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="66">
<filters>1 77 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="67">
<filters>1 77</filters>
<filters>1 79 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="68">
<filters>1 77</filters>
<filters>1 79 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="69">
<filters>1 79 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>
</tally>
<tally id="70">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="71">
<filters>1 79 2</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="72">
<filters>1 79</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="73">
<filters>1 79</filters>
<nuclides>total</nuclides>
<scores>decay-rate</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="69">
<tally id="74">
<filters>1 2</filters>
<nuclides>total</nuclides>
<scores>flux</scores>
<estimator>analog</estimator>
</tally>
<tally id="70">
<filters>1 77 2 5</filters>
<tally id="75">
<filters>1 79 2 5</filters>
<nuclides>total</nuclides>
<scores>delayed-nu-fission</scores>
<estimator>analog</estimator>

View file

@ -24,6 +24,12 @@
3 2 2 1 1 total 0.013286 0.000621
mesh 1 group in nuclide mean std. dev.
x y z
0 1 1 1 1 total 0.012881 0.000835
2 1 2 1 1 total 0.013282 0.000552
1 2 1 1 1 total 0.013896 0.000714
3 2 2 1 1 total 0.013180 0.000621
mesh 1 group in nuclide mean std. dev.
x y z
0 1 1 1 1 total 0.001231 0.000979
2 1 2 1 1 total 0.001332 0.000691
1 2 1 1 1 total 0.001346 0.000770

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,10 @@ absorption
material group in nuclide mean std. dev.
1 1 1 total 0.027335 0.002038
0 1 2 total 0.263007 0.029616
reduced absorption
material group in nuclide mean std. dev.
1 1 1 total 0.027278 0.002038
0 1 2 total 0.263007 0.029616
capture
material group in nuclide mean std. dev.
1 1 1 total 0.019722 0.001980
@ -316,6 +320,10 @@ absorption
material group in nuclide mean std. dev.
1 2 1 total 0.001395 0.000129
0 2 2 total 0.005202 0.000498
reduced absorption
material group in nuclide mean std. dev.
1 2 1 total 0.001390 0.000129
0 2 2 total 0.005202 0.000498
capture
material group in nuclide mean std. dev.
1 2 1 total 0.001395 0.000129
@ -618,6 +626,10 @@ absorption
material group in nuclide mean std. dev.
1 3 1 total 0.000715 0.000032
0 3 2 total 0.031290 0.001882
reduced absorption
material group in nuclide mean std. dev.
1 3 1 total 0.000715 0.000032
0 3 2 total 0.031290 0.001882
capture
material group in nuclide mean std. dev.
1 3 1 total 0.000715 0.000032

File diff suppressed because it is too large Load diff

View file

@ -1 +1 @@
d36f8abb1131212063470d622dbedeae31602da71006389421bd5dba712c94fe96acbc8ded833cb237687fb1071c1a6c3e0ec67b18ce7cf0f7720451b86d993a
93ad567f1b36461a68d4ead0ff5cfa4a2003b05cf5241a232544545001a94a33fc7b99f21af277ea3a24861d38aac3a9ac36c8b1706c4b3b33caec589df2c90c

View file

@ -0,0 +1,52 @@
"""Test one-group cross section generation"""
from pathlib import Path
import numpy as np
import pytest
import openmc
from openmc.deplete import MicroXS
CHAIN_FILE = Path(__file__).parents[2] / "chain_simple.xml"
@pytest.fixture(scope="module")
def model():
fuel = openmc.Material(name="uo2")
fuel.add_element("U", 1, percent_type="ao", enrichment=4.25)
fuel.add_element("O", 2)
fuel.set_density("g/cc", 10.4)
clad = openmc.Material(name="clad")
clad.add_element("Zr", 1)
clad.set_density("g/cc", 6)
water = openmc.Material(name="water")
water.add_element("O", 1)
water.add_element("H", 2)
water.set_density("g/cc", 1.0)
water.add_s_alpha_beta("c_H_in_H2O")
radii = [0.42, 0.45]
fuel.volume = np.pi * radii[0] ** 2
materials = openmc.Materials([fuel, clad, water])
pin_surfaces = [openmc.ZCylinder(r=r) for r in radii]
pin_univ = openmc.model.pin(pin_surfaces, materials)
bound_box = openmc.rectangular_prism(1.24, 1.24, boundary_type="reflective")
root_cell = openmc.Cell(fill=pin_univ, region=bound_box)
geometry = openmc.Geometry([root_cell])
settings = openmc.Settings()
settings.particles = 1000
settings.inactive = 10
settings.batches = 50
return openmc.Model(geometry, materials, settings)
def test_from_model(model):
ref_xs = MicroXS.from_csv('test_reference.csv')
test_xs = MicroXS.from_model(model, model.materials[0], CHAIN_FILE)
np.testing.assert_allclose(test_xs, ref_xs, rtol=1e-11)

View file

@ -0,0 +1,13 @@
nuclide,"(n,gamma)",fission
U234,22.231989822002465,0.49620744663749855
U235,10.479008971197121,48.41787337164604
U238,0.8673334105437324,0.10467880588762352
U236,8.65171044607122,0.31948392400019293
O16,7.497851000107524e-05,0.0
O17,0.0004079227797153371,0.0
I135,6.842395323713927,0.0
Xe135,227463.86426990604,0.0
Xe136,0.02317896034753588,0.0
Cs135,2.1721665580713623,0.0
Gd157,12786.09939237018,0.0
Gd156,3.4006085445846983,0.0
1 nuclide (n,gamma) fission
2 U234 22.231989822002465 0.49620744663749855
3 U235 10.479008971197121 48.41787337164604
4 U238 0.8673334105437324 0.10467880588762352
5 U236 8.65171044607122 0.31948392400019293
6 O16 7.497851000107524e-05 0.0
7 O17 0.0004079227797153371 0.0
8 I135 6.842395323713927 0.0
9 Xe135 227463.86426990604 0.0
10 Xe136 0.02317896034753588 0.0
11 Cs135 2.1721665580713623 0.0
12 Gd157 12786.09939237018 0.0
13 Gd156 3.4006085445846983 0.0

View file

@ -3,7 +3,7 @@
<cell id="1" material="1" region="-2" universe="1" />
<cell id="2" material="1" region="-3" universe="1" />
<cell id="3" material="1" region="-1" universe="1" />
<cell id="4" material="2" region="4 -5 6 -7 8 -9 2 3 1" universe="1" />
<cell id="4" material="2" region="-5 4 -7 6 -9 8 2 3 1" universe="1" />
<surface coeffs="0.0 0.0 0.0 3 1.5 1" id="1" type="z-torus" />
<surface coeffs="6 0.0 0.0 3 1.5 1" id="2" type="x-torus" />
<surface coeffs="6 0.0 0.0 6 1 0.75" id="3" type="y-torus" />

View file

@ -0,0 +1,91 @@
<?xml version='1.0' encoding='utf-8'?>
<geometry>
<cell id="1" material="1" name="fuel" region="1 -2 3 -4 5 -6" universe="1" />
<cell id="2" material="2" name="clad" region="(-1 | 2 | -3 | 4 | -5 | 6) (7 -8 9 -10 11 -12)" universe="1" />
<cell id="3" material="3" name="water" region="(-7 | 8 | -9 | 10 | -11 | 12) (13 -14 15 -16 17 -18)" universe="1" />
<surface coeffs="-5.0" id="1" name="minimum x" type="x-plane" />
<surface coeffs="5.0" id="2" name="maximum x" type="x-plane" />
<surface coeffs="-5.0" id="3" name="minimum y" type="y-plane" />
<surface coeffs="5.0" id="4" name="maximum y" type="y-plane" />
<surface coeffs="-5.0" id="5" name="minimum z" type="z-plane" />
<surface coeffs="5.0" id="6" name="maximum z" type="z-plane" />
<surface coeffs="-6.0" id="7" name="minimum x" type="x-plane" />
<surface coeffs="6.0" id="8" name="maximum x" type="x-plane" />
<surface coeffs="-6.0" id="9" name="minimum y" type="y-plane" />
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>
<material depletable="true" id="1" name="fuel">
<density units="g/cc" value="4.5" />
<nuclide ao="1.0" name="U235" />
</material>
<material id="2" name="zircaloy">
<density units="g/cc" value="5.77" />
<nuclide ao="0.5145" name="Zr90" />
<nuclide ao="0.1122" name="Zr91" />
<nuclide ao="0.1715" name="Zr92" />
<nuclide ao="0.1738" name="Zr94" />
<nuclide ao="0.028" name="Zr96" />
</material>
<material id="3" name="water">
<density units="atom/b-cm" value="0.07416" />
<nuclide ao="2.0" name="H1" />
<nuclide ao="1.0" name="O16" />
</material>
</materials>
<?xml version='1.0' encoding='utf-8'?>
<settings>
<run_mode>fixed source</run_mode>
<particles>1000</particles>
<batches>10</batches>
<source strength="1.0">
<space origin="0.0 0.0 0.0" type="spherical">
<r parameters="0.0 0.0" type="uniform" />
<cos_theta type="discrete">
<parameters>1.0 1.0</parameters>
</cos_theta>
<phi type="discrete">
<parameters>0.0 1.0</parameters>
</phi>
</space>
<energy type="discrete">
<parameters>15000000.0 1.0</parameters>
</energy>
</source>
</settings>
<?xml version='1.0' encoding='utf-8'?>
<tallies>
<mesh id="1">
<dimension>10 10 10</dimension>
<lower_left>-10.0 -10.0 -10.0</lower_left>
<upper_right>10.0 10.0 10.0</upper_right>
</mesh>
<mesh id="2" library="libmesh" type="unstructured">
<filename>test_mesh_hexes.e</filename>
</mesh>
<filter id="1" type="mesh">
<bins>1</bins>
</filter>
<filter id="2" type="mesh">
<bins>2</bins>
</filter>
<tally id="1" name="regular mesh tally">
<filters>1</filters>
<scores>flux</scores>
<estimator>collision</estimator>
</tally>
<tally id="2" name="unstructured mesh tally">
<filters>2</filters>
<scores>flux</scores>
<estimator>collision</estimator>
</tally>
</tallies>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-15" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="15" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-15" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="15" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-15" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="15" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="15.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="15.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="15.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-15" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="15" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-15" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="15" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-15" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="15" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="15.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="15.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="15.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-15" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="15" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-15" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="15" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-15" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="15" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="15.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="15.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="15.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-15" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="15" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-15" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="15" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-15" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="15" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="15.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="15.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-15.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="15.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

View file

@ -15,12 +15,12 @@
<surface coeffs="6.0" id="10" name="maximum y" type="y-plane" />
<surface coeffs="-6.0" id="11" name="minimum z" type="z-plane" />
<surface coeffs="6.0" id="12" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10" id="18" name="maximum z" type="z-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="13" name="minimum x" type="x-plane" />
<surface boundary="vacuum" coeffs="10.0" id="14" name="maximum x" type="x-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="15" name="minimum y" type="y-plane" />
<surface boundary="vacuum" coeffs="10.0" id="16" name="maximum y" type="y-plane" />
<surface boundary="vacuum" coeffs="-10.0" id="17" name="minimum z" type="z-plane" />
<surface boundary="vacuum" coeffs="10.0" id="18" name="maximum z" type="z-plane" />
</geometry>
<?xml version='1.0' encoding='utf-8'?>
<materials>

Some files were not shown because too many files have changed in this diff Show more